graph-to-log-struct.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. type IterationInfo = { iterationId: string; iterationIndex: number }
  2. type NodePlain = { nodeType: 'plain'; nodeId: string; } & Partial<IterationInfo>
  3. type NodeComplex = { nodeType: string; nodeId: string; params: (NodePlain | (NodeComplex & Partial<IterationInfo>) | Node[] | number)[] } & Partial<IterationInfo>
  4. type Node = NodePlain | NodeComplex
  5. /**
  6. * Parses a DSL string into an array of node objects.
  7. * @param dsl - The input DSL string.
  8. * @returns An array of parsed nodes.
  9. */
  10. function parseDSL(dsl: string): NodeData[] {
  11. return convertToNodeData(parseTopLevelFlow(dsl).map(nodeStr => parseNode(nodeStr)))
  12. }
  13. /**
  14. * Splits a top-level flow string by "->", respecting nested structures.
  15. * @param dsl - The DSL string to split.
  16. * @returns An array of top-level segments.
  17. */
  18. function parseTopLevelFlow(dsl: string): string[] {
  19. const segments: string[] = []
  20. let buffer = ''
  21. let nested = 0
  22. for (let i = 0; i < dsl.length; i++) {
  23. const char = dsl[i]
  24. if (char === '(') nested++
  25. if (char === ')') nested--
  26. if (char === '-' && dsl[i + 1] === '>' && nested === 0) {
  27. segments.push(buffer.trim())
  28. buffer = ''
  29. i++ // Skip the ">" character
  30. }
  31. else {
  32. buffer += char
  33. }
  34. }
  35. if (buffer.trim())
  36. segments.push(buffer.trim())
  37. return segments
  38. }
  39. /**
  40. * Parses a single node string.
  41. * If the node is complex (e.g., has parentheses), it extracts the node type, node ID, and parameters.
  42. * @param nodeStr - The node string to parse.
  43. * @param parentIterationId - The ID of the parent iteration node (if applicable).
  44. * @returns A parsed node object.
  45. */
  46. function parseNode(nodeStr: string, parentIterationId?: string): Node {
  47. // Check if the node is a complex node
  48. if (nodeStr.startsWith('(') && nodeStr.endsWith(')')) {
  49. const innerContent = nodeStr.slice(1, -1).trim() // Remove outer parentheses
  50. let nested = 0
  51. let buffer = ''
  52. const parts: string[] = []
  53. // Split the inner content by commas, respecting nested parentheses
  54. for (let i = 0; i < innerContent.length; i++) {
  55. const char = innerContent[i]
  56. if (char === '(') nested++
  57. if (char === ')') nested--
  58. if (char === ',' && nested === 0) {
  59. parts.push(buffer.trim())
  60. buffer = ''
  61. }
  62. else {
  63. buffer += char
  64. }
  65. }
  66. parts.push(buffer.trim())
  67. // Extract nodeType, nodeId, and params
  68. const [nodeType, nodeId, ...paramsRaw] = parts
  69. const params = parseParams(paramsRaw, nodeType === 'iteration' ? nodeId.trim() : parentIterationId)
  70. const complexNode = {
  71. nodeType: nodeType.trim(),
  72. nodeId: nodeId.trim(),
  73. params,
  74. }
  75. if (parentIterationId) {
  76. (complexNode as any).iterationId = parentIterationId;
  77. (complexNode as any).iterationIndex = 0 // Fixed as 0
  78. }
  79. return complexNode
  80. }
  81. // If it's not a complex node, treat it as a plain node
  82. const plainNode: NodePlain = { nodeType: 'plain', nodeId: nodeStr.trim() }
  83. if (parentIterationId) {
  84. plainNode.iterationId = parentIterationId
  85. plainNode.iterationIndex = 0 // Fixed as 0
  86. }
  87. return plainNode
  88. }
  89. /**
  90. * Parses parameters of a complex node.
  91. * Supports nested flows and complex sub-nodes.
  92. * Adds iteration-specific metadata recursively.
  93. * @param paramParts - The parameters string split by commas.
  94. * @param iterationId - The ID of the iteration node, if applicable.
  95. * @returns An array of parsed parameters (plain nodes, nested nodes, or flows).
  96. */
  97. function parseParams(paramParts: string[], iterationId?: string): (Node | Node[] | number)[] {
  98. return paramParts.map((part) => {
  99. if (part.includes('->')) {
  100. // Parse as a flow and return an array of nodes
  101. return parseTopLevelFlow(part).map(node => parseNode(node, iterationId))
  102. }
  103. else if (part.startsWith('(')) {
  104. // Parse as a nested complex node
  105. return parseNode(part, iterationId)
  106. }
  107. else if (!Number.isNaN(Number(part.trim()))) {
  108. // Parse as a numeric parameter
  109. return Number(part.trim())
  110. }
  111. else {
  112. // Parse as a plain node
  113. return parseNode(part, iterationId)
  114. }
  115. })
  116. }
  117. type NodeData = {
  118. id: string;
  119. node_id: string;
  120. title: string;
  121. node_type?: string;
  122. execution_metadata: Record<string, any>;
  123. status: string;
  124. }
  125. /**
  126. * Converts a plain node to node data.
  127. */
  128. function convertPlainNode(node: Node): NodeData[] {
  129. return [
  130. {
  131. id: node.nodeId,
  132. node_id: node.nodeId,
  133. title: node.nodeId,
  134. execution_metadata: {},
  135. status: 'succeeded',
  136. },
  137. ]
  138. }
  139. /**
  140. * Converts a retry node to node data.
  141. */
  142. function convertRetryNode(node: Node): NodeData[] {
  143. const { nodeId, iterationId, iterationIndex, params } = node as NodeComplex
  144. const retryCount = params ? Number.parseInt(params[0] as unknown as string, 10) : 0
  145. const result: NodeData[] = [
  146. {
  147. id: nodeId,
  148. node_id: nodeId,
  149. title: nodeId,
  150. execution_metadata: {},
  151. status: 'succeeded',
  152. },
  153. ]
  154. for (let i = 0; i < retryCount; i++) {
  155. result.push({
  156. id: nodeId,
  157. node_id: nodeId,
  158. title: nodeId,
  159. execution_metadata: iterationId ? {
  160. iteration_id: iterationId,
  161. iteration_index: iterationIndex || 0,
  162. } : {},
  163. status: 'retry',
  164. })
  165. }
  166. return result
  167. }
  168. /**
  169. * Converts an iteration node to node data.
  170. */
  171. function convertIterationNode(node: Node): NodeData[] {
  172. const { nodeId, params } = node as NodeComplex
  173. const result: NodeData[] = [
  174. {
  175. id: nodeId,
  176. node_id: nodeId,
  177. title: nodeId,
  178. node_type: 'iteration',
  179. status: 'succeeded',
  180. execution_metadata: {},
  181. },
  182. ]
  183. params?.forEach((param: any) => {
  184. if (Array.isArray(param)) {
  185. param.forEach((childNode: Node) => {
  186. const childData = convertToNodeData([childNode])
  187. childData.forEach((data) => {
  188. data.execution_metadata = {
  189. ...data.execution_metadata,
  190. iteration_id: nodeId,
  191. iteration_index: 0,
  192. }
  193. })
  194. result.push(...childData)
  195. })
  196. }
  197. })
  198. return result
  199. }
  200. /**
  201. * Converts a parallel node to node data.
  202. */
  203. function convertParallelNode(node: Node, parentParallelId?: string, parentStartNodeId?: string): NodeData[] {
  204. const { nodeId, params } = node as NodeComplex
  205. const result: NodeData[] = [
  206. {
  207. id: nodeId,
  208. node_id: nodeId,
  209. title: nodeId,
  210. execution_metadata: {
  211. parallel_id: nodeId,
  212. },
  213. status: 'succeeded',
  214. },
  215. ]
  216. params?.forEach((param) => {
  217. if (Array.isArray(param)) {
  218. const startNodeId = param[0]?.nodeId
  219. param.forEach((childNode: Node) => {
  220. const childData = convertToNodeData([childNode])
  221. childData.forEach((data) => {
  222. data.execution_metadata = {
  223. ...data.execution_metadata,
  224. parallel_id: nodeId,
  225. parallel_start_node_id: startNodeId,
  226. ...(parentParallelId && {
  227. parent_parallel_id: parentParallelId,
  228. parent_parallel_start_node_id: parentStartNodeId,
  229. }),
  230. }
  231. })
  232. result.push(...childData)
  233. })
  234. }
  235. else if (param && typeof param === 'object') {
  236. const startNodeId = param.nodeId
  237. const childData = convertToNodeData([param])
  238. childData.forEach((data) => {
  239. data.execution_metadata = {
  240. ...data.execution_metadata,
  241. parallel_id: nodeId,
  242. parallel_start_node_id: startNodeId,
  243. ...(parentParallelId && {
  244. parent_parallel_id: parentParallelId,
  245. parent_parallel_start_node_id: parentStartNodeId,
  246. }),
  247. }
  248. })
  249. result.push(...childData)
  250. }
  251. })
  252. return result
  253. }
  254. /**
  255. * Main function to convert nodes to node data.
  256. */
  257. function convertToNodeData(nodes: Node[], parentParallelId?: string, parentStartNodeId?: string): NodeData[] {
  258. const result: NodeData[] = []
  259. nodes.forEach((node) => {
  260. switch (node.nodeType) {
  261. case 'plain':
  262. result.push(...convertPlainNode(node))
  263. break
  264. case 'retry':
  265. result.push(...convertRetryNode(node))
  266. break
  267. case 'iteration':
  268. result.push(...convertIterationNode(node))
  269. break
  270. case 'parallel':
  271. result.push(...convertParallelNode(node, parentParallelId, parentStartNodeId))
  272. break
  273. default:
  274. throw new Error(`Unknown nodeType: ${node.nodeType}`)
  275. }
  276. })
  277. return result
  278. }
  279. export default parseDSL