@polka-codes/core 0.10.11 → 0.10.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/dist/Agent/backoff.d.ts +7 -0
  2. package/dist/Agent/backoff.js +21 -0
  3. package/dist/Agent/backoff.js.map +1 -0
  4. package/dist/Agent/index.d.ts +2 -0
  5. package/dist/Agent/index.js +3 -0
  6. package/dist/Agent/index.js.map +1 -0
  7. package/dist/Agent/parseJsonFromMarkdown.d.ts +8 -0
  8. package/dist/Agent/parseJsonFromMarkdown.js +34 -0
  9. package/dist/Agent/parseJsonFromMarkdown.js.map +1 -0
  10. package/dist/Agent/parseJsonFromMarkdown.test.d.ts +1 -0
  11. package/dist/Agent/parseJsonFromMarkdown.test.js +70 -0
  12. package/dist/Agent/parseJsonFromMarkdown.test.js.map +1 -0
  13. package/dist/Agent/prompts.d.ts +9 -0
  14. package/dist/Agent/prompts.js +107 -0
  15. package/dist/Agent/prompts.js.map +1 -0
  16. package/dist/UsageMeter.d.ts +101 -0
  17. package/dist/UsageMeter.js +299 -0
  18. package/dist/UsageMeter.js.map +1 -0
  19. package/dist/UsageMeter.test.d.ts +4 -0
  20. package/dist/UsageMeter.test.js +556 -0
  21. package/dist/UsageMeter.test.js.map +1 -0
  22. package/dist/config/base.d.ts +68 -0
  23. package/dist/config/base.js +56 -0
  24. package/dist/config/base.js.map +1 -0
  25. package/dist/config/memory.d.ts +24 -0
  26. package/dist/config/memory.js +36 -0
  27. package/dist/config/memory.js.map +1 -0
  28. package/dist/config.d.ts +236 -0
  29. package/dist/config.js +184 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/errors/base.d.ts +31 -0
  32. package/dist/errors/base.js +60 -0
  33. package/dist/errors/base.js.map +1 -0
  34. package/dist/errors/index.d.ts +1 -0
  35. package/dist/errors/index.js +3 -0
  36. package/dist/errors/index.js.map +1 -0
  37. package/dist/fs/index.d.ts +2 -0
  38. package/dist/fs/index.js +3 -0
  39. package/dist/fs/index.js.map +1 -0
  40. package/dist/fs/node-provider.d.ts +16 -0
  41. package/dist/fs/node-provider.js +47 -0
  42. package/dist/fs/node-provider.js.map +1 -0
  43. package/dist/fs/provider.d.ts +61 -0
  44. package/dist/fs/provider.js +3 -0
  45. package/dist/fs/provider.js.map +1 -0
  46. package/dist/index.d.ts +20 -191
  47. package/dist/index.js +21 -4123
  48. package/dist/index.js.map +1 -0
  49. package/dist/memory/index.d.ts +1 -0
  50. package/dist/memory/index.js +2 -0
  51. package/dist/memory/index.js.map +1 -0
  52. package/dist/memory/types.d.ts +136 -0
  53. package/dist/memory/types.js +2 -0
  54. package/dist/memory/types.js.map +1 -0
  55. package/dist/path.d.ts +9 -0
  56. package/dist/path.js +68 -0
  57. package/dist/path.js.map +1 -0
  58. package/dist/path.test.d.ts +1 -0
  59. package/dist/path.test.js +82 -0
  60. package/dist/path.test.js.map +1 -0
  61. package/dist/pricing/converter.d.ts +6 -0
  62. package/dist/pricing/converter.js +13 -0
  63. package/dist/pricing/converter.js.map +1 -0
  64. package/dist/pricing/converter.test.d.ts +1 -0
  65. package/dist/pricing/converter.test.js +54 -0
  66. package/dist/pricing/converter.test.js.map +1 -0
  67. package/dist/pricing/index.d.ts +2 -0
  68. package/dist/pricing/index.js +2 -0
  69. package/dist/pricing/index.js.map +1 -0
  70. package/dist/pricing/portkey-client.d.ts +2 -0
  71. package/dist/pricing/portkey-client.js +57 -0
  72. package/dist/pricing/portkey-client.js.map +1 -0
  73. package/dist/pricing/pricing-service.d.ts +6 -0
  74. package/dist/pricing/pricing-service.js +125 -0
  75. package/dist/pricing/pricing-service.js.map +1 -0
  76. package/dist/pricing/pricing-service.test.d.ts +1 -0
  77. package/dist/pricing/pricing-service.test.js +141 -0
  78. package/dist/pricing/pricing-service.test.js.map +1 -0
  79. package/dist/pricing/types.d.ts +24 -0
  80. package/dist/pricing/types.js +2 -0
  81. package/dist/pricing/types.js.map +1 -0
  82. package/dist/skills/__tests__/discovery.test.d.ts +1 -0
  83. package/dist/skills/__tests__/discovery.test.js +254 -0
  84. package/dist/skills/__tests__/discovery.test.js.map +1 -0
  85. package/dist/skills/__tests__/validation.test.d.ts +1 -0
  86. package/dist/skills/__tests__/validation.test.js +221 -0
  87. package/dist/skills/__tests__/validation.test.js.map +1 -0
  88. package/dist/skills/constants.d.ts +32 -0
  89. package/dist/skills/constants.js +50 -0
  90. package/dist/skills/constants.js.map +1 -0
  91. package/dist/skills/discovery.d.ts +56 -0
  92. package/dist/skills/discovery.js +392 -0
  93. package/dist/skills/discovery.js.map +1 -0
  94. package/dist/skills/index.d.ts +4 -0
  95. package/dist/skills/index.js +6 -0
  96. package/dist/skills/index.js.map +1 -0
  97. package/dist/skills/tools/index.d.ts +3 -0
  98. package/dist/skills/tools/index.js +5 -0
  99. package/dist/skills/tools/index.js.map +1 -0
  100. package/dist/skills/tools/listSkills.d.ts +54 -0
  101. package/dist/skills/tools/listSkills.js +52 -0
  102. package/dist/skills/tools/listSkills.js.map +1 -0
  103. package/dist/skills/tools/loadSkill.d.ts +52 -0
  104. package/dist/skills/tools/loadSkill.js +86 -0
  105. package/dist/skills/tools/loadSkill.js.map +1 -0
  106. package/dist/skills/tools/readSkillFile.d.ts +43 -0
  107. package/dist/skills/tools/readSkillFile.js +68 -0
  108. package/dist/skills/tools/readSkillFile.js.map +1 -0
  109. package/dist/skills/types.d.ts +83 -0
  110. package/dist/skills/types.js +42 -0
  111. package/dist/skills/types.js.map +1 -0
  112. package/dist/skills/validation.d.ts +30 -0
  113. package/dist/skills/validation.js +133 -0
  114. package/dist/skills/validation.js.map +1 -0
  115. package/dist/tool.d.ts +51 -0
  116. package/dist/tool.js +2 -0
  117. package/dist/tool.js.map +1 -0
  118. package/dist/tools/askFollowupQuestion.d.ts +35 -0
  119. package/dist/tools/askFollowupQuestion.js +105 -0
  120. package/dist/tools/askFollowupQuestion.js.map +1 -0
  121. package/dist/tools/askFollowupQuestion.test.d.ts +1 -0
  122. package/dist/tools/askFollowupQuestion.test.js +80 -0
  123. package/dist/tools/askFollowupQuestion.test.js.map +1 -0
  124. package/dist/tools/executeCommand.d.ts +29 -0
  125. package/dist/tools/executeCommand.js +82 -0
  126. package/dist/tools/executeCommand.js.map +1 -0
  127. package/dist/tools/executeCommand.test.d.ts +1 -0
  128. package/dist/tools/executeCommand.test.js +60 -0
  129. package/dist/tools/executeCommand.test.js.map +1 -0
  130. package/dist/tools/fetchUrl.d.ts +26 -0
  131. package/dist/tools/fetchUrl.js +85 -0
  132. package/dist/tools/fetchUrl.js.map +1 -0
  133. package/dist/tools/index.d.ts +15 -0
  134. package/dist/tools/index.js +17 -0
  135. package/dist/tools/index.js.map +1 -0
  136. package/dist/tools/listFiles.d.ts +35 -0
  137. package/dist/tools/listFiles.js +61 -0
  138. package/dist/tools/listFiles.js.map +1 -0
  139. package/dist/tools/listFiles.test.d.ts +1 -0
  140. package/dist/tools/listFiles.test.js +59 -0
  141. package/dist/tools/listFiles.test.js.map +1 -0
  142. package/dist/tools/provider.d.ts +76 -0
  143. package/dist/tools/provider.js +60 -0
  144. package/dist/tools/provider.js.map +1 -0
  145. package/dist/tools/readBinaryFile.d.ts +26 -0
  146. package/dist/tools/readBinaryFile.js +52 -0
  147. package/dist/tools/readBinaryFile.js.map +1 -0
  148. package/dist/tools/readFile.d.ts +35 -0
  149. package/dist/tools/readFile.js +128 -0
  150. package/dist/tools/readFile.js.map +1 -0
  151. package/dist/tools/readFile.test.d.ts +1 -0
  152. package/dist/tools/readFile.test.js +37 -0
  153. package/dist/tools/readFile.test.js.map +1 -0
  154. package/dist/tools/removeFile.d.ts +26 -0
  155. package/dist/tools/removeFile.js +49 -0
  156. package/dist/tools/removeFile.js.map +1 -0
  157. package/dist/tools/removeFile.test.d.ts +1 -0
  158. package/dist/tools/removeFile.test.js +32 -0
  159. package/dist/tools/removeFile.test.js.map +1 -0
  160. package/dist/tools/renameFile.d.ts +29 -0
  161. package/dist/tools/renameFile.js +48 -0
  162. package/dist/tools/renameFile.js.map +1 -0
  163. package/dist/tools/renameFile.test.d.ts +1 -0
  164. package/dist/tools/renameFile.test.js +53 -0
  165. package/dist/tools/renameFile.test.js.map +1 -0
  166. package/dist/tools/replaceInFile.d.ts +29 -0
  167. package/dist/tools/replaceInFile.js +233 -0
  168. package/dist/tools/replaceInFile.js.map +1 -0
  169. package/dist/tools/replaceInFile.test.d.ts +1 -0
  170. package/dist/tools/replaceInFile.test.js +79 -0
  171. package/dist/tools/replaceInFile.test.js.map +1 -0
  172. package/dist/tools/response-builders.d.ts +64 -0
  173. package/dist/tools/response-builders.js +88 -0
  174. package/dist/tools/response-builders.js.map +1 -0
  175. package/dist/tools/search.d.ts +26 -0
  176. package/dist/tools/search.js +56 -0
  177. package/dist/tools/search.js.map +1 -0
  178. package/dist/tools/search.test.d.ts +1 -0
  179. package/dist/tools/search.test.js +22 -0
  180. package/dist/tools/search.test.js.map +1 -0
  181. package/dist/tools/searchFiles.d.ts +32 -0
  182. package/dist/tools/searchFiles.js +86 -0
  183. package/dist/tools/searchFiles.js.map +1 -0
  184. package/dist/tools/todo.d.ts +37 -0
  185. package/dist/tools/todo.js +41 -0
  186. package/dist/tools/todo.js.map +1 -0
  187. package/dist/tools/utils/index.d.ts +1 -0
  188. package/dist/tools/utils/index.js +2 -0
  189. package/dist/tools/utils/index.js.map +1 -0
  190. package/dist/tools/utils/replaceInFile.d.ts +7 -0
  191. package/dist/tools/utils/replaceInFile.js +133 -0
  192. package/dist/tools/utils/replaceInFile.js.map +1 -0
  193. package/dist/tools/utils/replaceInFile.test.d.ts +1 -0
  194. package/dist/tools/utils/replaceInFile.test.js +308 -0
  195. package/dist/tools/utils/replaceInFile.test.js.map +1 -0
  196. package/dist/tools/utils.d.ts +10 -0
  197. package/dist/tools/utils.js +27 -0
  198. package/dist/tools/utils.js.map +1 -0
  199. package/dist/tools/writeToFile.d.ts +29 -0
  200. package/dist/tools/writeToFile.js +85 -0
  201. package/dist/tools/writeToFile.js.map +1 -0
  202. package/dist/tools/writeToFile.test.d.ts +1 -0
  203. package/dist/tools/writeToFile.test.js +46 -0
  204. package/dist/tools/writeToFile.test.js.map +1 -0
  205. package/dist/utils/index.d.ts +1 -0
  206. package/dist/utils/index.js +3 -0
  207. package/dist/utils/index.js.map +1 -0
  208. package/dist/utils/merge.d.ts +26 -0
  209. package/dist/utils/merge.js +45 -0
  210. package/dist/utils/merge.js.map +1 -0
  211. package/dist/workflow/agent.workflow.d.ts +39 -0
  212. package/dist/workflow/agent.workflow.js +166 -0
  213. package/dist/workflow/agent.workflow.js.map +1 -0
  214. package/dist/workflow/agent.workflow.test.d.ts +1 -0
  215. package/dist/workflow/agent.workflow.test.js +175 -0
  216. package/dist/workflow/agent.workflow.test.js.map +1 -0
  217. package/dist/workflow/control-flow.test.d.ts +1 -0
  218. package/dist/workflow/control-flow.test.js +323 -0
  219. package/dist/workflow/control-flow.test.js.map +1 -0
  220. package/dist/workflow/dynamic-edge-cases.test.d.ts +1 -0
  221. package/dist/workflow/dynamic-edge-cases.test.js +486 -0
  222. package/dist/workflow/dynamic-edge-cases.test.js.map +1 -0
  223. package/dist/workflow/dynamic-types.d.ts +124 -0
  224. package/dist/workflow/dynamic-types.js +105 -0
  225. package/dist/workflow/dynamic-types.js.map +1 -0
  226. package/dist/workflow/dynamic.d.ts +118 -0
  227. package/dist/workflow/dynamic.js +999 -0
  228. package/dist/workflow/dynamic.js.map +1 -0
  229. package/dist/workflow/index.d.ts +6 -0
  230. package/dist/workflow/index.js +8 -0
  231. package/dist/workflow/index.js.map +1 -0
  232. package/dist/workflow/json-ai-types.d.ts +122 -0
  233. package/dist/workflow/json-ai-types.js +144 -0
  234. package/dist/workflow/json-ai-types.js.map +1 -0
  235. package/dist/workflow/json-schema-conversion.test.d.ts +1 -0
  236. package/dist/workflow/json-schema-conversion.test.js +371 -0
  237. package/dist/workflow/json-schema-conversion.test.js.map +1 -0
  238. package/dist/workflow/try-catch.test.d.ts +1 -0
  239. package/dist/workflow/try-catch.test.js +443 -0
  240. package/dist/workflow/try-catch.test.js.map +1 -0
  241. package/dist/workflow/types.d.ts +103 -0
  242. package/dist/workflow/types.js +17 -0
  243. package/dist/workflow/types.js.map +1 -0
  244. package/dist/workflow/workflow.d.ts +29 -0
  245. package/dist/workflow/workflow.js +57 -0
  246. package/dist/workflow/workflow.js.map +1 -0
  247. package/dist/workflow/workflow.test.d.ts +1 -0
  248. package/dist/workflow/workflow.test.js +189 -0
  249. package/dist/workflow/workflow.test.js.map +1 -0
  250. package/package.json +9 -1
@@ -0,0 +1,999 @@
1
+ import { parse } from 'yaml';
2
+ import { z } from 'zod';
3
+ import { parseJsonFromMarkdown } from '../Agent/parseJsonFromMarkdown';
4
+ import { agentWorkflow } from './agent.workflow';
5
+ import { WorkflowFileSchema, } from './dynamic-types';
6
+ /**
7
+ * Maximum iterations for while loops to prevent infinite loops
8
+ */
9
+ const MAX_WHILE_LOOP_ITERATIONS = 1000;
10
+ /**
11
+ * Convert a JSON Schema to a Zod schema
12
+ * Supports a subset of JSON SchemaDraft 7
13
+ *
14
+ * This is exported to allow reuse in other parts of the codebase that need to
15
+ * convert JSON schemas to Zod schemas (e.g., MCP server tool schema conversion).
16
+ */
17
+ export function convertJsonSchemaToZod(schema) {
18
+ // Handle enum types
19
+ if (schema.enum) {
20
+ // JSON Schema enums can contain strings, numbers, or booleans
21
+ // For non-string or mixed enums, use z.union with z.literal
22
+ // For string-only enums, use z.enum for better performance
23
+ const enumValues = schema.enum;
24
+ // Handle empty enum - no valid values
25
+ if (enumValues.length === 0) {
26
+ return z.never();
27
+ }
28
+ // Check if all values are strings
29
+ if (enumValues.every((v) => typeof v === 'string')) {
30
+ return z.enum(enumValues);
31
+ }
32
+ // Mixed or non-string enums: use z.union with z.literal
33
+ const literals = enumValues.map((v) => z.literal(v));
34
+ if (literals.length === 1) {
35
+ return literals[0];
36
+ }
37
+ // z.union can take an array of schemas
38
+ // Cast to any because Zod's union type inference is complex
39
+ return z.union([literals[0], literals[1], ...literals.slice(2)]);
40
+ }
41
+ // Handle union types (type: ["string", "null"])
42
+ if (Array.isArray(schema.type)) {
43
+ const types = schema.type;
44
+ if (types.includes('null') && types.length === 2) {
45
+ const nonNullType = types.find((t) => t !== 'null');
46
+ if (nonNullType === 'string')
47
+ return z.string().nullable();
48
+ if (nonNullType === 'number')
49
+ return z.number().nullable();
50
+ if (nonNullType === 'integer')
51
+ return z
52
+ .number()
53
+ .refine((val) => Number.isInteger(val))
54
+ .nullable();
55
+ if (nonNullType === 'boolean')
56
+ return z.boolean().nullable();
57
+ if (nonNullType === 'object') {
58
+ // Handle object with nullable - need to preserve properties
59
+ const shape = {};
60
+ if (schema.properties) {
61
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
62
+ const propZod = convertJsonSchemaToZod(propSchema);
63
+ const isRequired = schema.required?.includes(propName);
64
+ shape[propName] = isRequired ? propZod : propZod.optional();
65
+ }
66
+ }
67
+ return z.object(shape).nullable();
68
+ }
69
+ if (nonNullType === 'array')
70
+ return z.array(z.any()).nullable();
71
+ }
72
+ // Fallback for complex unions
73
+ return z.any();
74
+ }
75
+ const type = schema.type;
76
+ switch (type) {
77
+ case 'string':
78
+ return z.string();
79
+ case 'number':
80
+ return z.number();
81
+ case 'integer':
82
+ // Zod v4 doesn't have .int(), use custom validation
83
+ return z.number().refine((val) => Number.isInteger(val), { message: 'Expected an integer' });
84
+ case 'boolean':
85
+ return z.boolean();
86
+ case 'null':
87
+ return z.null();
88
+ case 'object': {
89
+ const shape = {};
90
+ // Convert properties
91
+ if (schema.properties) {
92
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
93
+ const propZod = convertJsonSchemaToZod(propSchema);
94
+ // Check if property is required
95
+ const isRequired = schema.required?.includes(propName);
96
+ shape[propName] = isRequired ? propZod : propZod.optional();
97
+ }
98
+ }
99
+ // Handle additionalProperties
100
+ if (schema.additionalProperties === true) {
101
+ // Use passthrough to allow additional properties without validation
102
+ return z.object(shape).passthrough();
103
+ }
104
+ if (typeof schema.additionalProperties === 'object') {
105
+ const additionalSchema = convertJsonSchemaToZod(schema.additionalProperties);
106
+ // Use explicit intersection for additional properties
107
+ // Note: We use z.intersection() instead of .and() for better readability
108
+ // The cast to z.ZodTypeAny is necessary because Zod's intersection types
109
+ // have complex type inference that TypeScript cannot always resolve correctly.
110
+ // This is a known limitation of Zod's type system.
111
+ return z.intersection(z.object(shape), z.record(z.string(), additionalSchema));
112
+ }
113
+ // No additionalProperties (defaults to false) - strict object
114
+ return z.object(shape);
115
+ }
116
+ case 'array': {
117
+ if (!schema.items) {
118
+ return z.array(z.any());
119
+ }
120
+ const itemSchema = convertJsonSchemaToZod(schema.items);
121
+ return z.array(itemSchema);
122
+ }
123
+ default:
124
+ return z.any();
125
+ }
126
+ }
127
+ /**
128
+ * Tool groups that can be used in step.tools arrays.
129
+ * - "readonly": File reading operations only
130
+ * - "readwrite": Full file system access
131
+ * - "internet": Network operations (fetch, search)
132
+ * - "all": All available tools (special keyword, not in this map)
133
+ */
134
+ export const TOOL_GROUPS = {
135
+ readonly: ['readFile', 'readBinaryFile', 'listFiles', 'searchFiles'],
136
+ readwrite: ['readFile', 'readBinaryFile', 'listFiles', 'searchFiles', 'writeToFile', 'replaceInFile', 'removeFile', 'renameFile'],
137
+ internet: ['fetchUrl', 'search'],
138
+ };
139
+ /**
140
+ * Validate a workflow file for common issues
141
+ */
142
+ export function validateWorkflowFile(definition) {
143
+ const errors = [];
144
+ // Check each workflow
145
+ for (const [workflowId, workflow] of Object.entries(definition.workflows)) {
146
+ // Validate steps exist
147
+ if (!workflow.steps || workflow.steps.length === 0) {
148
+ errors.push(`Workflow '${workflowId}' has no steps`);
149
+ continue;
150
+ }
151
+ // Check for break/continue outside loops
152
+ const checkBreakOutsideLoop = (steps, inLoop, path) => {
153
+ for (const step of steps) {
154
+ if (isBreakStep(step) || isContinueStep(step)) {
155
+ if (!inLoop) {
156
+ errors.push(`${path} has break/continue outside of a loop`);
157
+ }
158
+ }
159
+ if (isWhileLoopStep(step)) {
160
+ checkBreakOutsideLoop(step.while.steps, true, `${path}/${step.id}`);
161
+ }
162
+ if (isIfElseStep(step)) {
163
+ if (step.if.thenBranch) {
164
+ checkBreakOutsideLoop(step.if.thenBranch, inLoop, `${path}/${step.id}/then`);
165
+ }
166
+ if (step.if.elseBranch) {
167
+ checkBreakOutsideLoop(step.if.elseBranch, inLoop, `${path}/${step.id}/else`);
168
+ }
169
+ }
170
+ if (isTryCatchStep(step)) {
171
+ checkBreakOutsideLoop(step.try.trySteps, inLoop, `${path}/${step.id}/try`);
172
+ checkBreakOutsideLoop(step.try.catchSteps, inLoop, `${path}/${step.id}/catch`);
173
+ }
174
+ }
175
+ };
176
+ checkBreakOutsideLoop(workflow.steps, false, workflowId);
177
+ // Check for runWorkflow calls to non-existent workflows
178
+ const findRunWorkflowCalls = (steps, path) => {
179
+ for (const step of steps) {
180
+ if (isWhileLoopStep(step)) {
181
+ findRunWorkflowCalls(step.while.steps, `${path}/${step.id}`);
182
+ }
183
+ if (isIfElseStep(step)) {
184
+ if (step.if.thenBranch) {
185
+ findRunWorkflowCalls(step.if.thenBranch, `${path}/${step.id}/then`);
186
+ }
187
+ if (step.if.elseBranch) {
188
+ findRunWorkflowCalls(step.if.elseBranch, `${path}/${step.id}/else`);
189
+ }
190
+ }
191
+ if (isTryCatchStep(step)) {
192
+ findRunWorkflowCalls(step.try.trySteps, `${path}/${step.id}/try`);
193
+ findRunWorkflowCalls(step.try.catchSteps, `${path}/${step.id}/catch`);
194
+ }
195
+ }
196
+ };
197
+ findRunWorkflowCalls(workflow.steps, workflowId);
198
+ }
199
+ if (errors.length > 0) {
200
+ return { success: false, errors };
201
+ }
202
+ return { success: true };
203
+ }
204
+ export function parseDynamicWorkflowDefinition(source) {
205
+ try {
206
+ const raw = parse(source);
207
+ const validated = WorkflowFileSchema.safeParse(raw);
208
+ if (!validated.success) {
209
+ return { success: false, error: z.prettifyError(validated.error) };
210
+ }
211
+ // Additional validation for structural issues
212
+ const validation = validateWorkflowFile(validated.data);
213
+ if (!validation.success) {
214
+ return { success: false, error: `Workflow validation failed:\n${validation.errors.map((e) => ` - ${e}`).join('\n')}` };
215
+ }
216
+ return { success: true, definition: validated.data };
217
+ }
218
+ catch (error) {
219
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
220
+ }
221
+ }
222
+ function validateAndApplyDefaults(workflowId, workflow, input) {
223
+ if (!workflow.inputs || workflow.inputs.length === 0) {
224
+ return input;
225
+ }
226
+ const validatedInput = { ...input };
227
+ const errors = [];
228
+ for (const inputDef of workflow.inputs) {
229
+ const providedValue = input[inputDef.id];
230
+ if (providedValue !== undefined && providedValue !== null) {
231
+ validatedInput[inputDef.id] = providedValue;
232
+ }
233
+ else if (inputDef.default !== undefined && inputDef.default !== null) {
234
+ validatedInput[inputDef.id] = inputDef.default;
235
+ }
236
+ else {
237
+ errors.push(`Missing required input '${inputDef.id}'${inputDef.description ? `: ${inputDef.description}` : ''}`);
238
+ }
239
+ }
240
+ if (errors.length > 0) {
241
+ throw new Error(`Workflow '${workflowId}' input validation failed:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
242
+ }
243
+ return validatedInput;
244
+ }
245
+ /**
246
+ * Safely evaluate a condition expression with access to input and state
247
+ *
248
+ * Security: When allowUnsafeCodeExecution is false (default), only supports:
249
+ * - Property access: input.foo, state.bar
250
+ * - Comparisons: ==, !=, ===, !==, <, >, <=, >=
251
+ * - Logical operators: &&, ||, !
252
+ * - Parentheses for grouping
253
+ * - Literals: strings, numbers, booleans, null
254
+ *
255
+ * When allowUnsafeCodeExecution is true, arbitrary JavaScript is executed.
256
+ * WARNING: Only set to true for trusted workflow definitions!
257
+ */
258
+ function evaluateCondition(condition, input, state,
259
+ // SECURITY: Default must remain false for safe evaluation of untrusted workflows
260
+ // Only set to true for trusted, vetted workflow definitions
261
+ allowUnsafeCodeExecution = false, logger) {
262
+ if (allowUnsafeCodeExecution) {
263
+ // SECURITY WARNING: Unsafe code execution allows arbitrary JavaScript execution
264
+ // This should only be used for trusted, vetted workflow definitions
265
+ if (logger) {
266
+ logger.warn(`[SECURITY] Executing unsafe code evaluation for condition: ${condition}. This allows arbitrary JavaScript execution and should only be used for trusted workflows.`);
267
+ }
268
+ // Unsafe mode: use new Function for full JavaScript support
269
+ const functionBody = `
270
+ try {
271
+ return ${condition};
272
+ } catch (error) {
273
+ throw new Error('Condition evaluation failed: ' + (error instanceof Error ? error.message : String(error)));
274
+ }
275
+ `;
276
+ try {
277
+ const fn = new Function('input', 'state', functionBody);
278
+ const result = fn(input, state);
279
+ return Boolean(result);
280
+ }
281
+ catch (error) {
282
+ throw new Error(`Failed to evaluate condition: ${condition}. Error: ${error instanceof Error ? error.message : String(error)}`);
283
+ }
284
+ }
285
+ else {
286
+ // Safe mode: use a simple, restricted evaluator
287
+ return evaluateConditionSafe(condition, input, state);
288
+ }
289
+ }
290
+ /**
291
+ * Safe condition evaluator that supports a restricted subset of JavaScript
292
+ * Prevents code injection by not using eval or new Function
293
+ *
294
+ * Operator precedence (from lowest to highest):
295
+ * 1. || (logical OR)
296
+ * 2. && (logical AND)
297
+ * 3. ===, !==, ==, !=, >=, <=, >, < (comparisons)
298
+ * 4. ! (negation)
299
+ * 5. (...) (parentheses - highest precedence, evaluated first)
300
+ */
301
+ function evaluateConditionSafe(condition, input, state) {
302
+ // Trim whitespace
303
+ condition = condition.trim();
304
+ // Handle simple boolean literals
305
+ if (condition === 'true')
306
+ return true;
307
+ if (condition === 'false')
308
+ return false;
309
+ // Handle logical OR (lowest precedence)
310
+ const orIndex = findTopLevelOperator(condition, '||');
311
+ if (orIndex !== -1) {
312
+ const left = condition.slice(0, orIndex).trim();
313
+ const right = condition.slice(orIndex + 2).trim();
314
+ return evaluateConditionSafe(left, input, state) || evaluateConditionSafe(right, input, state);
315
+ }
316
+ // Handle logical AND
317
+ const andIndex = findTopLevelOperator(condition, '&&');
318
+ if (andIndex !== -1) {
319
+ const left = condition.slice(0, andIndex).trim();
320
+ const right = condition.slice(andIndex + 2).trim();
321
+ return evaluateConditionSafe(left, input, state) && evaluateConditionSafe(right, input, state);
322
+ }
323
+ // Handle comparisons
324
+ const comparisonOps = ['===', '!==', '==', '!=', '>=', '<=', '>', '<'];
325
+ for (const op of comparisonOps) {
326
+ const opIndex = findTopLevelOperator(condition, op);
327
+ if (opIndex !== -1) {
328
+ const left = evaluateValue(condition.slice(0, opIndex).trim(), input, state);
329
+ const right = evaluateValue(condition.slice(opIndex + op.length).trim(), input, state);
330
+ return compareValues(left, right, op);
331
+ }
332
+ }
333
+ // Handle negation (higher precedence than comparisons)
334
+ if (condition.startsWith('!')) {
335
+ return !evaluateConditionSafe(condition.slice(1).trim(), input, state);
336
+ }
337
+ // Handle parentheses (highest precedence)
338
+ if (hasEnclosingParens(condition)) {
339
+ const inner = condition.slice(1, -1);
340
+ return evaluateConditionSafe(inner, input, state);
341
+ }
342
+ // If we get here, it's a simple value
343
+ const value = evaluateValue(condition, input, state);
344
+ return Boolean(value);
345
+ }
346
+ /**
347
+ * Find index of operator at top level (not inside parentheses or string literals)
348
+ */
349
+ function findTopLevelOperator(expr, op) {
350
+ let parenDepth = 0;
351
+ let inString = false;
352
+ let stringChar = '';
353
+ let escapeNext = false;
354
+ for (let i = 0; i <= expr.length - op.length; i++) {
355
+ const char = expr[i];
356
+ if (escapeNext) {
357
+ escapeNext = false;
358
+ continue;
359
+ }
360
+ if (char === '\\') {
361
+ escapeNext = true;
362
+ continue;
363
+ }
364
+ if (!inString && (char === '"' || char === "'")) {
365
+ inString = true;
366
+ stringChar = char;
367
+ continue;
368
+ }
369
+ if (inString && char === stringChar) {
370
+ inString = false;
371
+ stringChar = '';
372
+ continue;
373
+ }
374
+ if (inString)
375
+ continue;
376
+ if (char === '(')
377
+ parenDepth++;
378
+ if (char === ')')
379
+ parenDepth--;
380
+ if (parenDepth === 0 && expr.slice(i, i + op.length) === op) {
381
+ return i;
382
+ }
383
+ }
384
+ return -1;
385
+ }
386
+ /**
387
+ * Check if expression is wrapped in enclosing parentheses
388
+ * (e.g., "(A && B)" returns true, "(A) && (B)" returns false)
389
+ */
390
+ function hasEnclosingParens(expr) {
391
+ expr = expr.trim();
392
+ if (!expr.startsWith('(') || !expr.endsWith(')')) {
393
+ return false;
394
+ }
395
+ // Check if the opening and closing parentheses enclose the entire expression
396
+ let depth = 0;
397
+ let inString = false;
398
+ let stringChar = '';
399
+ let escapeNext = false;
400
+ for (let i = 0; i < expr.length; i++) {
401
+ const char = expr[i];
402
+ if (escapeNext) {
403
+ escapeNext = false;
404
+ continue;
405
+ }
406
+ if (char === '\\') {
407
+ escapeNext = true;
408
+ continue;
409
+ }
410
+ if (!inString && (char === '"' || char === "'")) {
411
+ inString = true;
412
+ stringChar = char;
413
+ continue;
414
+ }
415
+ if (inString && char === stringChar) {
416
+ inString = false;
417
+ stringChar = '';
418
+ continue;
419
+ }
420
+ if (inString)
421
+ continue;
422
+ if (char === '(') {
423
+ depth++;
424
+ // First paren is at index 0
425
+ if (i === 0)
426
+ depth = 1;
427
+ }
428
+ if (char === ')') {
429
+ depth--;
430
+ // Last paren should be at the last index
431
+ if (depth === 0 && i === expr.length - 1) {
432
+ return true;
433
+ }
434
+ if (depth === 0 && i < expr.length - 1) {
435
+ // Found closing paren before end of expression
436
+ return false;
437
+ }
438
+ }
439
+ }
440
+ return false;
441
+ }
442
+ /**
443
+ * Evaluate a simple value (property access or literal)
444
+ */
445
+ function evaluateValue(expr, input, state) {
446
+ expr = expr.trim();
447
+ // String literals (with proper handling of escaped quotes)
448
+ // Use stricter check - ensure entire expression is a quoted string
449
+ const stringMatch = expr.match(/^(["'])(?:(?=(\\?))\2.)*?\1$/);
450
+ if (stringMatch) {
451
+ const quote = stringMatch[1];
452
+ if (quote === '"') {
453
+ // Use JSON.parse for double-quoted strings (handles all JSON escape sequences correctly)
454
+ try {
455
+ return JSON.parse(expr);
456
+ }
457
+ catch (error) {
458
+ throw new Error(`Invalid string literal: "${expr}". Error: ${error instanceof Error ? error.message : String(error)}`);
459
+ }
460
+ }
461
+ else {
462
+ // Single-quoted strings: convert to double-quoted and use JSON.parse
463
+ // Need to handle both \' and \" escape sequences
464
+ let inner = expr.slice(1, -1);
465
+ // Replace \' with ' (unescape single quotes)
466
+ inner = inner.replace(/\\'/g, "'");
467
+ // Replace \" with " (unescape double quotes)
468
+ inner = inner.replace(/\\"/g, '"');
469
+ // Now escape any double quotes and backslashes for JSON
470
+ const converted = `"${inner.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
471
+ try {
472
+ return JSON.parse(converted);
473
+ }
474
+ catch (error) {
475
+ throw new Error(`Invalid string literal: "${expr}". Error: ${error instanceof Error ? error.message : String(error)}`);
476
+ }
477
+ }
478
+ }
479
+ // Number literals (more permissive regex)
480
+ if (/^-?\d*\.?\d+(?:[eE][+-]?\d+)?$/.test(expr)) {
481
+ return Number.parseFloat(expr);
482
+ }
483
+ // Boolean literals
484
+ if (expr === 'true')
485
+ return true;
486
+ if (expr === 'false')
487
+ return false;
488
+ if (expr === 'null')
489
+ return null;
490
+ // Property access: input.foo or state.bar.baz
491
+ if (expr.startsWith('input.')) {
492
+ return getNestedProperty(input, expr.slice(6));
493
+ }
494
+ if (expr.startsWith('state.')) {
495
+ return getNestedProperty(state, expr.slice(6));
496
+ }
497
+ // If we get here, the expression is not recognized
498
+ throw new Error(`Unrecognized expression in condition: "${expr}". Valid expressions are: string literals, numbers, boolean literals, null, or property access like "input.foo" or "state.bar"`);
499
+ }
500
+ /**
501
+ * Get nested property from object
502
+ */
503
+ function getNestedProperty(obj, path) {
504
+ const parts = path.split('.');
505
+ let current = obj;
506
+ for (const part of parts) {
507
+ if (current == null)
508
+ return undefined;
509
+ if (typeof current !== 'object')
510
+ return undefined;
511
+ current = current[part];
512
+ }
513
+ return current;
514
+ }
515
+ /**
516
+ * Compare two values using the specified operator
517
+ * For comparison operators, we assume the values are comparable (strings, numbers, etc.)
518
+ *
519
+ * NOTE: Using 'as any' for comparisons because values are typed as 'unknown'
520
+ * This is an unsafe operation that relies on runtime behavior (string/number comparisons work)
521
+ * but violates type safety. Using 'as any' explicitly acknowledges this limitation.
522
+ */
523
+ function compareValues(left, right, op) {
524
+ switch (op) {
525
+ case '===':
526
+ return left === right;
527
+ case '!==':
528
+ return left !== right;
529
+ case '==':
530
+ return Object.is(left, right);
531
+ case '!=':
532
+ return !Object.is(left, right);
533
+ case '>=':
534
+ return left >= right;
535
+ case '<=':
536
+ return left <= right;
537
+ case '>':
538
+ return left > right;
539
+ case '<':
540
+ return left < right;
541
+ default:
542
+ throw new Error(`Unknown comparison operator: ${op}`);
543
+ }
544
+ }
545
+ function createRunWorkflowFn(args) {
546
+ return async (subWorkflowId, subInput) => {
547
+ const mergedInput = { ...args.input, ...args.state, ...(subInput ?? {}) };
548
+ return await args.runInternal(subWorkflowId, mergedInput, args.context, args.state);
549
+ };
550
+ }
551
+ async function executeStepWithAgent(stepDef, workflowId, input, state, context, options, runInternal) {
552
+ const tools = context.tools;
553
+ if (typeof tools.generateText !== 'function' || typeof tools.invokeTool !== 'function' || typeof tools.taskEvent !== 'function') {
554
+ throw new Error(`Step '${stepDef.id}' in workflow '${workflowId}' requires agent execution, but AgentToolRegistry tools are not available.`);
555
+ }
556
+ if (!options.toolInfo) {
557
+ throw new Error(`Step '${stepDef.id}' in workflow '${workflowId}' requires agent execution, but no toolInfo was provided to DynamicWorkflowRunner.`);
558
+ }
559
+ const rawAllowedToolNames = stepDef.tools;
560
+ let toolsForAgent;
561
+ if (rawAllowedToolNames) {
562
+ const expandedToolNames = new Set();
563
+ let includeAll = false;
564
+ for (const name of rawAllowedToolNames) {
565
+ if (name === 'all') {
566
+ includeAll = true;
567
+ break;
568
+ }
569
+ if (Object.hasOwn(TOOL_GROUPS, name)) {
570
+ for (const tool of TOOL_GROUPS[name]) {
571
+ expandedToolNames.add(tool);
572
+ }
573
+ }
574
+ else {
575
+ expandedToolNames.add(name);
576
+ }
577
+ }
578
+ if (includeAll) {
579
+ toolsForAgent = [...options.toolInfo];
580
+ }
581
+ else {
582
+ toolsForAgent = options.toolInfo.filter((t) => expandedToolNames.has(t.name));
583
+ }
584
+ }
585
+ else {
586
+ toolsForAgent = [...options.toolInfo];
587
+ }
588
+ if (!rawAllowedToolNames || rawAllowedToolNames.includes('all') || rawAllowedToolNames.includes('runWorkflow')) {
589
+ toolsForAgent.push({
590
+ name: 'runWorkflow',
591
+ description: 'Run a named sub-workflow defined in the current workflow file.',
592
+ parameters: z.object({
593
+ workflowId: z.string().describe('Sub-workflow id to run'),
594
+ input: z.any().nullish().describe('Optional input object for the sub-workflow'),
595
+ }),
596
+ handler: async () => {
597
+ return { success: false, message: { type: 'error-text', value: 'runWorkflow is virtual.' } };
598
+ },
599
+ });
600
+ }
601
+ const allowedToolNameSet = new Set(toolsForAgent.map((t) => t.name));
602
+ context.logger.debug(`[Agent] Available tools for step '${stepDef.id}': ${toolsForAgent.map((t) => t.name).join(', ')}`);
603
+ const systemPrompt = options.stepSystemPrompt?.({ workflowId, step: stepDef, input, state }) ??
604
+ [
605
+ `You are an AI assistant executing a workflow step.`,
606
+ '',
607
+ '# Instructions',
608
+ '- Execute the task defined in the user message.',
609
+ '- Use the provided tools to accomplish the task.',
610
+ '- Return the step output as valid JSON in markdown.',
611
+ '- Do not ask for user input. If information is missing, make a reasonable assumption or fail.',
612
+ ]
613
+ .filter(Boolean)
614
+ .join('\n');
615
+ const userContent = [
616
+ `Workflow: ${workflowId}`,
617
+ `Step: ${stepDef.id}`,
618
+ `Task: ${stepDef.task}`,
619
+ stepDef.expected_outcome ? `Expected outcome: ${stepDef.expected_outcome}` : '',
620
+ `Workflow Input: ${JSON.stringify(input)}`,
621
+ `Current State: ${JSON.stringify(state)}`,
622
+ ]
623
+ .filter(Boolean)
624
+ .join('\n');
625
+ const runWorkflow = createRunWorkflowFn({ input, state, context, runInternal });
626
+ const agentTools = {
627
+ generateText: tools.generateText.bind(tools),
628
+ taskEvent: tools.taskEvent.bind(tools),
629
+ invokeTool: async ({ toolName, input: toolInput }) => {
630
+ if (!allowedToolNameSet.has(toolName)) {
631
+ return {
632
+ success: false,
633
+ message: { type: 'error-text', value: `Tool '${toolName}' is not allowed in this step.` },
634
+ };
635
+ }
636
+ if (toolName === 'runWorkflow') {
637
+ // Type guard for runWorkflow input
638
+ const runWorkflowInput = toolInput;
639
+ const subWorkflowId = runWorkflowInput?.workflowId;
640
+ const subInput = runWorkflowInput?.input;
641
+ if (typeof subWorkflowId !== 'string') {
642
+ return {
643
+ success: false,
644
+ message: { type: 'error-text', value: 'runWorkflow.workflowId must be a string.' },
645
+ };
646
+ }
647
+ try {
648
+ const output = await runWorkflow(subWorkflowId, subInput);
649
+ const jsonResult = { type: 'json', value: output };
650
+ return { success: true, message: jsonResult };
651
+ }
652
+ catch (error) {
653
+ return {
654
+ success: false,
655
+ message: { type: 'error-text', value: error instanceof Error ? error.message : String(error) },
656
+ };
657
+ }
658
+ }
659
+ return await tools.invokeTool({ toolName, input: toolInput });
660
+ },
661
+ };
662
+ const result = await agentWorkflow({
663
+ tools: toolsForAgent,
664
+ systemPrompt,
665
+ userMessage: [{ role: 'user', content: userContent }],
666
+ maxToolRoundTrips: options.maxToolRoundTrips,
667
+ model: options.model,
668
+ }, { ...context, tools: agentTools });
669
+ if (result.type === 'Exit') {
670
+ // Prefer structured object output
671
+ if (result.object !== undefined) {
672
+ return result.object;
673
+ }
674
+ // Try to parse JSON from message
675
+ const parsed = parseJsonFromMarkdown(result.message);
676
+ if (parsed.success) {
677
+ return parsed.data;
678
+ }
679
+ // If message is a simple string, wrap it in an object for consistency if enabled
680
+ if (options.wrapAgentResultInObject) {
681
+ context.logger.warn(`[Agent] Step '${stepDef.id}' returned plain text instead of JSON. Wrapping in {result: ...}`);
682
+ return { result: result.message };
683
+ }
684
+ return result.message;
685
+ }
686
+ if (result.type === 'Error') {
687
+ throw new Error(`Agent step '${stepDef.id}' in workflow '${workflowId}' failed: ${result.error?.message || 'Unknown error'}`);
688
+ }
689
+ if (result.type === 'UsageExceeded') {
690
+ throw new Error(`Agent step '${stepDef.id}' in workflow '${workflowId}' exceeded usage limits (tokens or rounds)`);
691
+ }
692
+ // Exhaustive check: TypeScript should ensure all result types are handled above
693
+ const _exhaustiveCheck = result;
694
+ throw new Error(`Agent step '${stepDef.id}' in workflow '${workflowId}' exited unexpectedly with unhandled type`);
695
+ }
696
+ async function executeStepWithTimeout(stepDef, workflowId, input, state, context, options, runInternal) {
697
+ const executeStepLogic = async () => {
698
+ context.logger.debug(`[Step] Executing step '${stepDef.id}' with agent`);
699
+ const result = await executeStepWithAgent(stepDef, workflowId, input, state, context, options, runInternal);
700
+ context.logger.debug(`[Step] Agent execution completed for step '${stepDef.id}'`);
701
+ return result;
702
+ };
703
+ // Apply timeout if specified
704
+ if (stepDef.timeout && stepDef.timeout > 0) {
705
+ context.logger.debug(`[Step] Step '${stepDef.id}' has timeout of ${stepDef.timeout}ms`);
706
+ let timeoutId;
707
+ const timeoutPromise = new Promise((_, reject) => {
708
+ timeoutId = setTimeout(() => reject(new Error(`Step '${stepDef.id}' in workflow '${workflowId}' timed out after ${stepDef.timeout}ms`)), stepDef.timeout);
709
+ });
710
+ try {
711
+ return await Promise.race([executeStepLogic(), timeoutPromise]);
712
+ }
713
+ finally {
714
+ if (timeoutId)
715
+ clearTimeout(timeoutId);
716
+ }
717
+ }
718
+ return await executeStepLogic();
719
+ }
720
+ async function executeStep(stepDef, workflowId, input, state, context, options, runInternal) {
721
+ const result = await executeStepWithTimeout(stepDef, workflowId, input, state, context, options, runInternal);
722
+ // Validate output against schema if provided
723
+ if (stepDef.outputSchema) {
724
+ try {
725
+ context.logger.debug(`[Step] Validating output for step '${stepDef.id}' against schema`);
726
+ // Convert JSON Schema to Zod schema
727
+ // Type assertion is safe here as JsonSchema structure is validated by convertJsonSchemaToZod
728
+ const zodSchema = convertJsonSchemaToZod(stepDef.outputSchema);
729
+ // Validate the result
730
+ const validationResult = zodSchema.safeParse(result);
731
+ if (!validationResult.success) {
732
+ const errorDetails = validationResult.error.issues.map((e) => ` - ${e.path.join('.') || 'root'}: ${e.message}`).join('\n');
733
+ throw new Error(`Output does not match expected schema:\n${errorDetails}`);
734
+ }
735
+ context.logger.debug(`[Step] Output validation successful for step '${stepDef.id}'`);
736
+ }
737
+ catch (error) {
738
+ throw new Error(`Step '${stepDef.id}' in workflow '${workflowId}' output validation failed: ${error instanceof Error ? error.message : String(error)}`);
739
+ }
740
+ }
741
+ return result;
742
+ }
743
+ /**
744
+ * Check if a step is a break statement
745
+ */
746
+ function isBreakStep(step) {
747
+ return typeof step === 'object' && step !== null && 'break' in step && step.break === true;
748
+ }
749
+ /**
750
+ * Check if a step is a continue statement
751
+ */
752
+ function isContinueStep(step) {
753
+ return typeof step === 'object' && step !== null && 'continue' in step && step.continue === true;
754
+ }
755
+ /**
756
+ * Check if a step is a while loop
757
+ */
758
+ function isWhileLoopStep(step) {
759
+ return typeof step === 'object' && step !== null && 'while' in step;
760
+ }
761
+ /**
762
+ * Check if a step is an if/else branch
763
+ */
764
+ function isIfElseStep(step) {
765
+ return typeof step === 'object' && step !== null && 'if' in step;
766
+ }
767
+ /**
768
+ * Check if a step is a try/catch block
769
+ */
770
+ function isTryCatchStep(step) {
771
+ return typeof step === 'object' && step !== null && 'try' in step;
772
+ }
773
+ /**
774
+ * Store step output in state if output key is specified
775
+ */
776
+ function storeStepOutput(step, result, state) {
777
+ if ('id' in step && step.output) {
778
+ const outputKey = step.output;
779
+ state[outputKey] = result;
780
+ }
781
+ }
782
+ /**
783
+ * Get step ID for logging purposes
784
+ */
785
+ function getStepId(step) {
786
+ if ('id' in step && step.id) {
787
+ return step.id;
788
+ }
789
+ if (isWhileLoopStep(step)) {
790
+ return 'while';
791
+ }
792
+ if (isIfElseStep(step)) {
793
+ return 'if';
794
+ }
795
+ if (isTryCatchStep(step)) {
796
+ return 'try';
797
+ }
798
+ return 'control';
799
+ }
800
+ /**
801
+ * Execute a single control flow step (basic step, while loop, if/else, break, continue)
802
+ */
803
+ async function executeControlFlowStep(step, workflowId, input, state, context, options, runInternal, loopDepth, breakFlag, continueFlag) {
804
+ // Handle break statement
805
+ if (isBreakStep(step)) {
806
+ if (loopDepth === 0) {
807
+ throw new Error(`'break' statement found outside of a loop in workflow '${workflowId}'`);
808
+ }
809
+ context.logger.debug(`[ControlFlow] Executing break statement (loop depth: ${loopDepth})`);
810
+ return { result: undefined, shouldBreak: true, shouldContinue: false };
811
+ }
812
+ // Handle continue statement
813
+ if (isContinueStep(step)) {
814
+ if (loopDepth === 0) {
815
+ throw new Error(`'continue' statement found outside of a loop in workflow '${workflowId}'`);
816
+ }
817
+ context.logger.debug(`[ControlFlow] Executing continue statement (loop depth: ${loopDepth})`);
818
+ return { result: undefined, shouldBreak: false, shouldContinue: true };
819
+ }
820
+ // Handle while loop
821
+ if (isWhileLoopStep(step)) {
822
+ context.logger.info(`[ControlFlow] Executing while loop '${step.id}'`);
823
+ context.logger.debug(`[ControlFlow] Condition: ${step.while.condition}`);
824
+ context.logger.debug(`[ControlFlow] Loop body has ${step.while.steps.length} step(s)`);
825
+ let iterationCount = 0;
826
+ let loopResult;
827
+ while (true) {
828
+ iterationCount++;
829
+ if (iterationCount > MAX_WHILE_LOOP_ITERATIONS) {
830
+ throw new Error(`While loop '${step.id}' in workflow '${workflowId}' exceeded maximum iteration limit of ${MAX_WHILE_LOOP_ITERATIONS}`);
831
+ }
832
+ // Evaluate condition
833
+ const conditionResult = evaluateCondition(step.while.condition, input, state, options.allowUnsafeCodeExecution);
834
+ context.logger.debug(`[ControlFlow] While loop '${step.id}' iteration ${iterationCount}: condition = ${conditionResult}`);
835
+ if (!conditionResult) {
836
+ context.logger.info(`[ControlFlow] While loop '${step.id}' terminated after ${iterationCount - 1} iteration(s)`);
837
+ break;
838
+ }
839
+ // Execute loop body steps
840
+ for (const bodyStep of step.while.steps) {
841
+ const { result, shouldBreak, shouldContinue } = await executeControlFlowStep(bodyStep, workflowId, input, state, context, options, runInternal, loopDepth + 1, breakFlag, continueFlag);
842
+ if (shouldBreak) {
843
+ context.logger.debug(`[ControlFlow] Breaking from while loop '${step.id}'`);
844
+ breakFlag.value = false;
845
+ return { result: loopResult, shouldBreak: false, shouldContinue: false };
846
+ }
847
+ if (shouldContinue) {
848
+ context.logger.debug(`[ControlFlow] Continuing to next iteration of while loop '${step.id}'`);
849
+ continueFlag.value = false;
850
+ break;
851
+ }
852
+ // Store output if specified
853
+ storeStepOutput(bodyStep, result, state);
854
+ // Last result becomes loop result
855
+ loopResult = result;
856
+ }
857
+ }
858
+ // Store loop output if specified
859
+ const outputKey = step.output ?? step.id;
860
+ state[outputKey] = loopResult;
861
+ context.logger.debug(`[ControlFlow] While loop '${step.id}' stored output as '${outputKey}'`);
862
+ return { result: loopResult, shouldBreak: false, shouldContinue: false };
863
+ }
864
+ // Handle if/else branch
865
+ if (isIfElseStep(step)) {
866
+ const ifStep = step;
867
+ context.logger.info(`[ControlFlow] Executing if/else branch '${ifStep.id}'`);
868
+ context.logger.debug(`[ControlFlow] Condition: ${ifStep.if.condition}`);
869
+ context.logger.debug(`[ControlFlow] Then branch has ${ifStep.if.thenBranch.length} step(s)`);
870
+ if (ifStep.if.elseBranch) {
871
+ context.logger.debug(`[ControlFlow] Else branch has ${ifStep.if.elseBranch.length} step(s)`);
872
+ }
873
+ const conditionResult = evaluateCondition(ifStep.if.condition, input, state, options.allowUnsafeCodeExecution);
874
+ context.logger.debug(`[ControlFlow] If/else '${ifStep.id}' condition = ${conditionResult}`);
875
+ const branchSteps = conditionResult ? ifStep.if.thenBranch : (ifStep.if.elseBranch ?? []);
876
+ const branchName = conditionResult ? 'then' : ifStep.if.elseBranch ? 'else' : 'else (empty)';
877
+ context.logger.info(`[ControlFlow] Taking '${branchName}' branch of '${ifStep.id}'`);
878
+ let branchResult;
879
+ for (const branchStep of branchSteps) {
880
+ const { result, shouldBreak, shouldContinue } = await executeControlFlowStep(branchStep, workflowId, input, state, context, options, runInternal, loopDepth, breakFlag, continueFlag);
881
+ // Propagate break/continue from within branches
882
+ if (shouldBreak || shouldContinue) {
883
+ return { result, shouldBreak, shouldContinue };
884
+ }
885
+ // Store output if specified
886
+ storeStepOutput(branchStep, result, state);
887
+ // Last result becomes branch result
888
+ branchResult = result;
889
+ }
890
+ // Store branch output if specified
891
+ const outputKey = ifStep.output ?? ifStep.id;
892
+ state[outputKey] = branchResult;
893
+ context.logger.debug(`[ControlFlow] If/else '${ifStep.id}' stored output as '${outputKey}'`);
894
+ return { result: branchResult, shouldBreak: false, shouldContinue: false };
895
+ }
896
+ // Handle try/catch block
897
+ if (isTryCatchStep(step)) {
898
+ const tryStep = step;
899
+ context.logger.info(`[ControlFlow] Executing try/catch block '${tryStep.id}'`);
900
+ context.logger.debug(`[ControlFlow] Try block has ${tryStep.try.trySteps.length} step(s)`);
901
+ context.logger.debug(`[ControlFlow] Catch block has ${tryStep.try.catchSteps.length} step(s)`);
902
+ let tryResult;
903
+ let caughtError;
904
+ try {
905
+ // Execute try steps
906
+ for (const tryStepItem of tryStep.try.trySteps) {
907
+ const { result } = await executeControlFlowStep(tryStepItem, workflowId, input, state, context, options, runInternal, loopDepth, breakFlag, continueFlag);
908
+ // Store output if specified
909
+ storeStepOutput(tryStepItem, result, state);
910
+ // Last result becomes try result
911
+ tryResult = result;
912
+ }
913
+ // Store try/catch output if specified
914
+ const outputKey = tryStep.output ?? tryStep.id;
915
+ state[outputKey] = tryResult;
916
+ context.logger.debug(`[ControlFlow] Try/catch '${tryStep.id}' completed successfully`);
917
+ return { result: tryResult, shouldBreak: false, shouldContinue: false };
918
+ }
919
+ catch (error) {
920
+ caughtError = error instanceof Error ? error : new Error(String(error));
921
+ context.logger.warn(`[ControlFlow] Try/catch '${tryStep.id}' caught error: ${caughtError.message}`);
922
+ // Execute catch steps
923
+ let catchResult;
924
+ for (const catchStepItem of tryStep.try.catchSteps) {
925
+ const { result } = await executeControlFlowStep(catchStepItem, workflowId, input, state, context, options, runInternal, loopDepth, breakFlag, continueFlag);
926
+ // Store output if specified
927
+ storeStepOutput(catchStepItem, result, state);
928
+ // Last result becomes catch result
929
+ catchResult = result;
930
+ }
931
+ // Store try/catch output if specified
932
+ const outputKey = tryStep.output ?? tryStep.id;
933
+ state[outputKey] = catchResult;
934
+ context.logger.debug(`[ControlFlow] Try/catch '${tryStep.id}' caught error and executed catch block`);
935
+ return { result: catchResult, shouldBreak: false, shouldContinue: false };
936
+ }
937
+ }
938
+ // Handle basic step (must be WorkflowStepDefinition at this point)
939
+ const stepDef = step;
940
+ const stepResult = await executeStep(stepDef, workflowId, input, state, context, options, runInternal);
941
+ return { result: stepResult, shouldBreak: false, shouldContinue: false };
942
+ }
943
+ export function createDynamicWorkflow(definition, options = {}) {
944
+ if (typeof definition === 'string') {
945
+ const res = parseDynamicWorkflowDefinition(definition);
946
+ if (!res.success) {
947
+ throw new Error(res.error);
948
+ }
949
+ definition = res.definition;
950
+ }
951
+ const runInternal = async (workflowId, input, context, inheritedState) => {
952
+ const workflow = definition.workflows[workflowId];
953
+ if (!workflow) {
954
+ const builtIn = options.builtInWorkflows?.[workflowId];
955
+ if (builtIn) {
956
+ context.logger.info(`[Workflow] Delegating to built-in workflow '${workflowId}'`);
957
+ // Built-in workflows are typed as WorkflowFn<any, any, any>, so we need to cast context
958
+ // TODO: Improve built-in workflow typing to preserve context type constraints
959
+ return await builtIn(input, context);
960
+ }
961
+ throw new Error(`Workflow '${workflowId}' not found`);
962
+ }
963
+ // Validate inputs and apply defaults
964
+ const validatedInput = validateAndApplyDefaults(workflowId, workflow, input);
965
+ context.logger.info(`[Workflow] Starting workflow '${workflowId}'`);
966
+ context.logger.debug(`[Workflow] Input: ${JSON.stringify(validatedInput)}`);
967
+ context.logger.debug(`[Workflow] Inherited state: ${JSON.stringify(inheritedState)}`);
968
+ context.logger.debug(`[Workflow] Steps: ${workflow.steps.map((s) => ('id' in s ? s.id : '<control flow>')).join(', ')}`);
969
+ const state = { ...inheritedState };
970
+ let lastOutput;
971
+ const breakFlag = { value: false };
972
+ const continueFlag = { value: false };
973
+ for (let i = 0; i < workflow.steps.length; i++) {
974
+ const stepDef = workflow.steps[i];
975
+ const stepId = getStepId(stepDef);
976
+ context.logger.info(`[Workflow] Step ${i + 1}/${workflow.steps.length}: ${stepId}`);
977
+ // Execute control flow step
978
+ const { result } = await executeControlFlowStep(stepDef, workflowId, validatedInput, state, context, options, runInternal, 0, // loop depth
979
+ breakFlag, continueFlag);
980
+ lastOutput = result;
981
+ // Store output if specified
982
+ storeStepOutput(stepDef, result, state);
983
+ if ('id' in stepDef && stepDef.output) {
984
+ context.logger.debug(`[Workflow] Step output stored as '${stepDef.output}': ${typeof lastOutput === 'object' ? JSON.stringify(lastOutput).substring(0, 200) : lastOutput}`);
985
+ }
986
+ }
987
+ context.logger.info(`[Workflow] Completed workflow '${workflowId}'`);
988
+ if (workflow.output) {
989
+ context.logger.debug(`[Workflow] Returning output field: ${workflow.output}`);
990
+ return state[workflow.output];
991
+ }
992
+ context.logger.debug(`[Workflow] Returning full state with keys: ${Object.keys(state).join(', ')}`);
993
+ return state;
994
+ };
995
+ return async (workflowId, input, context) => {
996
+ return await runInternal(workflowId, input, context, {});
997
+ };
998
+ }
999
+ //# sourceMappingURL=dynamic.js.map