@nodetool-ai/agents 0.7.0-rc.11

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 (281) hide show
  1. package/dist/agent-executor.d.ts +62 -0
  2. package/dist/agent-executor.d.ts.map +1 -0
  3. package/dist/agent-executor.js +324 -0
  4. package/dist/agent-executor.js.map +1 -0
  5. package/dist/agent-step-executor.d.ts +28 -0
  6. package/dist/agent-step-executor.d.ts.map +1 -0
  7. package/dist/agent-step-executor.js +99 -0
  8. package/dist/agent-step-executor.js.map +1 -0
  9. package/dist/agent-workflow-runner.d.ts +31 -0
  10. package/dist/agent-workflow-runner.d.ts.map +1 -0
  11. package/dist/agent-workflow-runner.js +124 -0
  12. package/dist/agent-workflow-runner.js.map +1 -0
  13. package/dist/agent.d.ts +115 -0
  14. package/dist/agent.d.ts.map +1 -0
  15. package/dist/agent.js +587 -0
  16. package/dist/agent.js.map +1 -0
  17. package/dist/base-agent.d.ts +42 -0
  18. package/dist/base-agent.d.ts.map +1 -0
  19. package/dist/base-agent.js +29 -0
  20. package/dist/base-agent.js.map +1 -0
  21. package/dist/constants.d.ts +8 -0
  22. package/dist/constants.d.ts.map +1 -0
  23. package/dist/constants.js +8 -0
  24. package/dist/constants.js.map +1 -0
  25. package/dist/graph-builder.d.ts +49 -0
  26. package/dist/graph-builder.d.ts.map +1 -0
  27. package/dist/graph-builder.js +206 -0
  28. package/dist/graph-builder.js.map +1 -0
  29. package/dist/graph-planner.d.ts +56 -0
  30. package/dist/graph-planner.d.ts.map +1 -0
  31. package/dist/graph-planner.js +311 -0
  32. package/dist/graph-planner.js.map +1 -0
  33. package/dist/index.d.ts +79 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +81 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/js-sandbox.d.ts +84 -0
  38. package/dist/js-sandbox.d.ts.map +1 -0
  39. package/dist/js-sandbox.js +654 -0
  40. package/dist/js-sandbox.js.map +1 -0
  41. package/dist/multi-mode-agent.d.ts +86 -0
  42. package/dist/multi-mode-agent.d.ts.map +1 -0
  43. package/dist/multi-mode-agent.js +476 -0
  44. package/dist/multi-mode-agent.js.map +1 -0
  45. package/dist/output-format.d.ts +10 -0
  46. package/dist/output-format.d.ts.map +1 -0
  47. package/dist/output-format.js +17 -0
  48. package/dist/output-format.js.map +1 -0
  49. package/dist/parallel-task-executor.d.ts +87 -0
  50. package/dist/parallel-task-executor.d.ts.map +1 -0
  51. package/dist/parallel-task-executor.js +332 -0
  52. package/dist/parallel-task-executor.js.map +1 -0
  53. package/dist/prompts/graph-planner-prompt.d.ts +48 -0
  54. package/dist/prompts/graph-planner-prompt.d.ts.map +1 -0
  55. package/dist/prompts/graph-planner-prompt.js +180 -0
  56. package/dist/prompts/graph-planner-prompt.js.map +1 -0
  57. package/dist/reject-agentic-provider.d.ts +21 -0
  58. package/dist/reject-agentic-provider.d.ts.map +1 -0
  59. package/dist/reject-agentic-provider.js +32 -0
  60. package/dist/reject-agentic-provider.js.map +1 -0
  61. package/dist/simple-agent.d.ts +29 -0
  62. package/dist/simple-agent.d.ts.map +1 -0
  63. package/dist/simple-agent.js +54 -0
  64. package/dist/simple-agent.js.map +1 -0
  65. package/dist/step-executor.d.ts +162 -0
  66. package/dist/step-executor.d.ts.map +1 -0
  67. package/dist/step-executor.js +1114 -0
  68. package/dist/step-executor.js.map +1 -0
  69. package/dist/sub-agent-planner.d.ts +33 -0
  70. package/dist/sub-agent-planner.d.ts.map +1 -0
  71. package/dist/sub-agent-planner.js +203 -0
  72. package/dist/sub-agent-planner.js.map +1 -0
  73. package/dist/task-executor.d.ts +89 -0
  74. package/dist/task-executor.d.ts.map +1 -0
  75. package/dist/task-executor.js +359 -0
  76. package/dist/task-executor.js.map +1 -0
  77. package/dist/task-planner.d.ts +44 -0
  78. package/dist/task-planner.d.ts.map +1 -0
  79. package/dist/task-planner.js +532 -0
  80. package/dist/task-planner.js.map +1 -0
  81. package/dist/team/db-task-board.d.ts +54 -0
  82. package/dist/team/db-task-board.d.ts.map +1 -0
  83. package/dist/team/db-task-board.js +361 -0
  84. package/dist/team/db-task-board.js.map +1 -0
  85. package/dist/team/edge-message-bus.d.ts +51 -0
  86. package/dist/team/edge-message-bus.d.ts.map +1 -0
  87. package/dist/team/edge-message-bus.js +169 -0
  88. package/dist/team/edge-message-bus.js.map +1 -0
  89. package/dist/team/edge-task-board.d.ts +77 -0
  90. package/dist/team/edge-task-board.d.ts.map +1 -0
  91. package/dist/team/edge-task-board.js +140 -0
  92. package/dist/team/edge-task-board.js.map +1 -0
  93. package/dist/team/index.d.ts +16 -0
  94. package/dist/team/index.d.ts.map +1 -0
  95. package/dist/team/index.js +17 -0
  96. package/dist/team/index.js.map +1 -0
  97. package/dist/team/message-bus.d.ts +58 -0
  98. package/dist/team/message-bus.d.ts.map +1 -0
  99. package/dist/team/message-bus.js +151 -0
  100. package/dist/team/message-bus.js.map +1 -0
  101. package/dist/team/task-board.d.ts +106 -0
  102. package/dist/team/task-board.d.ts.map +1 -0
  103. package/dist/team/task-board.js +370 -0
  104. package/dist/team/task-board.js.map +1 -0
  105. package/dist/team/team-executor.d.ts +72 -0
  106. package/dist/team/team-executor.d.ts.map +1 -0
  107. package/dist/team/team-executor.js +466 -0
  108. package/dist/team/team-executor.js.map +1 -0
  109. package/dist/team/team-tools.d.ts +252 -0
  110. package/dist/team/team-tools.d.ts.map +1 -0
  111. package/dist/team/team-tools.js +412 -0
  112. package/dist/team/team-tools.js.map +1 -0
  113. package/dist/team/types.d.ts +198 -0
  114. package/dist/team/types.d.ts.map +1 -0
  115. package/dist/team/types.js +8 -0
  116. package/dist/team/types.js.map +1 -0
  117. package/dist/tools/add-edge-tool.d.ts +18 -0
  118. package/dist/tools/add-edge-tool.d.ts.map +1 -0
  119. package/dist/tools/add-edge-tool.js +92 -0
  120. package/dist/tools/add-edge-tool.js.map +1 -0
  121. package/dist/tools/add-node-tool.d.ts +22 -0
  122. package/dist/tools/add-node-tool.d.ts.map +1 -0
  123. package/dist/tools/add-node-tool.js +128 -0
  124. package/dist/tools/add-node-tool.js.map +1 -0
  125. package/dist/tools/asset-tools.d.ts +58 -0
  126. package/dist/tools/asset-tools.d.ts.map +1 -0
  127. package/dist/tools/asset-tools.js +191 -0
  128. package/dist/tools/asset-tools.js.map +1 -0
  129. package/dist/tools/base-tool.d.ts +25 -0
  130. package/dist/tools/base-tool.d.ts.map +1 -0
  131. package/dist/tools/base-tool.js +24 -0
  132. package/dist/tools/base-tool.js.map +1 -0
  133. package/dist/tools/browser-tools.d.ts +32 -0
  134. package/dist/tools/browser-tools.d.ts.map +1 -0
  135. package/dist/tools/browser-tools.js +208 -0
  136. package/dist/tools/browser-tools.js.map +1 -0
  137. package/dist/tools/calculator-tool.d.ts +24 -0
  138. package/dist/tools/calculator-tool.d.ts.map +1 -0
  139. package/dist/tools/calculator-tool.js +64 -0
  140. package/dist/tools/calculator-tool.js.map +1 -0
  141. package/dist/tools/claude-code-tools.d.ts +96 -0
  142. package/dist/tools/claude-code-tools.d.ts.map +1 -0
  143. package/dist/tools/claude-code-tools.js +463 -0
  144. package/dist/tools/claude-code-tools.js.map +1 -0
  145. package/dist/tools/code-tools.d.ts +26 -0
  146. package/dist/tools/code-tools.d.ts.map +1 -0
  147. package/dist/tools/code-tools.js +82 -0
  148. package/dist/tools/code-tools.js.map +1 -0
  149. package/dist/tools/control-tool.d.ts +47 -0
  150. package/dist/tools/control-tool.d.ts.map +1 -0
  151. package/dist/tools/control-tool.js +101 -0
  152. package/dist/tools/control-tool.js.map +1 -0
  153. package/dist/tools/create-plan-tool.d.ts +29 -0
  154. package/dist/tools/create-plan-tool.d.ts.map +1 -0
  155. package/dist/tools/create-plan-tool.js +355 -0
  156. package/dist/tools/create-plan-tool.js.map +1 -0
  157. package/dist/tools/create-task-tool.d.ts +28 -0
  158. package/dist/tools/create-task-tool.d.ts.map +1 -0
  159. package/dist/tools/create-task-tool.js +254 -0
  160. package/dist/tools/create-task-tool.js.map +1 -0
  161. package/dist/tools/dataseo-tools.d.ts +38 -0
  162. package/dist/tools/dataseo-tools.d.ts.map +1 -0
  163. package/dist/tools/dataseo-tools.js +329 -0
  164. package/dist/tools/dataseo-tools.js.map +1 -0
  165. package/dist/tools/email-tools.d.ts +76 -0
  166. package/dist/tools/email-tools.d.ts.map +1 -0
  167. package/dist/tools/email-tools.js +259 -0
  168. package/dist/tools/email-tools.js.map +1 -0
  169. package/dist/tools/filesystem-tools.d.ts +77 -0
  170. package/dist/tools/filesystem-tools.d.ts.map +1 -0
  171. package/dist/tools/filesystem-tools.js +334 -0
  172. package/dist/tools/filesystem-tools.js.map +1 -0
  173. package/dist/tools/find-model-tool.d.ts +25 -0
  174. package/dist/tools/find-model-tool.d.ts.map +1 -0
  175. package/dist/tools/find-model-tool.js +236 -0
  176. package/dist/tools/find-model-tool.js.map +1 -0
  177. package/dist/tools/finish-graph-tool.d.ts +23 -0
  178. package/dist/tools/finish-graph-tool.d.ts.map +1 -0
  179. package/dist/tools/finish-graph-tool.js +46 -0
  180. package/dist/tools/finish-graph-tool.js.map +1 -0
  181. package/dist/tools/finish-step-tool.d.ts +16 -0
  182. package/dist/tools/finish-step-tool.d.ts.map +1 -0
  183. package/dist/tools/finish-step-tool.js +68 -0
  184. package/dist/tools/finish-step-tool.js.map +1 -0
  185. package/dist/tools/google-tools.d.ts +44 -0
  186. package/dist/tools/google-tools.d.ts.map +1 -0
  187. package/dist/tools/google-tools.js +176 -0
  188. package/dist/tools/google-tools.js.map +1 -0
  189. package/dist/tools/http-tools.d.ts +56 -0
  190. package/dist/tools/http-tools.d.ts.map +1 -0
  191. package/dist/tools/http-tools.js +180 -0
  192. package/dist/tools/http-tools.js.map +1 -0
  193. package/dist/tools/js-code-tool.d.ts +30 -0
  194. package/dist/tools/js-code-tool.d.ts.map +1 -0
  195. package/dist/tools/js-code-tool.js +98 -0
  196. package/dist/tools/js-code-tool.js.map +1 -0
  197. package/dist/tools/local-get-node-info-tool.d.ts +19 -0
  198. package/dist/tools/local-get-node-info-tool.d.ts.map +1 -0
  199. package/dist/tools/local-get-node-info-tool.js +75 -0
  200. package/dist/tools/local-get-node-info-tool.js.map +1 -0
  201. package/dist/tools/local-list-nodes-tool.d.ts +19 -0
  202. package/dist/tools/local-list-nodes-tool.d.ts.map +1 -0
  203. package/dist/tools/local-list-nodes-tool.js +69 -0
  204. package/dist/tools/local-list-nodes-tool.js.map +1 -0
  205. package/dist/tools/local-search-nodes-tool.d.ts +21 -0
  206. package/dist/tools/local-search-nodes-tool.d.ts.map +1 -0
  207. package/dist/tools/local-search-nodes-tool.js +111 -0
  208. package/dist/tools/local-search-nodes-tool.js.map +1 -0
  209. package/dist/tools/math-tools.d.ts +156 -0
  210. package/dist/tools/math-tools.d.ts.map +1 -0
  211. package/dist/tools/math-tools.js +422 -0
  212. package/dist/tools/math-tools.js.map +1 -0
  213. package/dist/tools/mcp-tools.d.ts +413 -0
  214. package/dist/tools/mcp-tools.d.ts.map +1 -0
  215. package/dist/tools/mcp-tools.js +618 -0
  216. package/dist/tools/mcp-tools.js.map +1 -0
  217. package/dist/tools/media-tools.d.ts +277 -0
  218. package/dist/tools/media-tools.d.ts.map +1 -0
  219. package/dist/tools/media-tools.js +682 -0
  220. package/dist/tools/media-tools.js.map +1 -0
  221. package/dist/tools/model-tools.d.ts +25 -0
  222. package/dist/tools/model-tools.d.ts.map +1 -0
  223. package/dist/tools/model-tools.js +48 -0
  224. package/dist/tools/model-tools.js.map +1 -0
  225. package/dist/tools/openai-tools.d.ts +73 -0
  226. package/dist/tools/openai-tools.d.ts.map +1 -0
  227. package/dist/tools/openai-tools.js +193 -0
  228. package/dist/tools/openai-tools.js.map +1 -0
  229. package/dist/tools/pdf-tools.d.ts +152 -0
  230. package/dist/tools/pdf-tools.d.ts.map +1 -0
  231. package/dist/tools/pdf-tools.js +339 -0
  232. package/dist/tools/pdf-tools.js.map +1 -0
  233. package/dist/tools/plan-builder-tools.d.ts +72 -0
  234. package/dist/tools/plan-builder-tools.d.ts.map +1 -0
  235. package/dist/tools/plan-builder-tools.js +438 -0
  236. package/dist/tools/plan-builder-tools.js.map +1 -0
  237. package/dist/tools/search-tools.d.ts +39 -0
  238. package/dist/tools/search-tools.d.ts.map +1 -0
  239. package/dist/tools/search-tools.js +218 -0
  240. package/dist/tools/search-tools.js.map +1 -0
  241. package/dist/tools/serp-providers/dataforseo-provider.d.ts +14 -0
  242. package/dist/tools/serp-providers/dataforseo-provider.d.ts.map +1 -0
  243. package/dist/tools/serp-providers/dataforseo-provider.js +114 -0
  244. package/dist/tools/serp-providers/dataforseo-provider.js.map +1 -0
  245. package/dist/tools/serp-providers/index.d.ts +35 -0
  246. package/dist/tools/serp-providers/index.d.ts.map +1 -0
  247. package/dist/tools/serp-providers/index.js +14 -0
  248. package/dist/tools/serp-providers/index.js.map +1 -0
  249. package/dist/tools/serp-providers/serpapi-provider.d.ts +18 -0
  250. package/dist/tools/serp-providers/serpapi-provider.d.ts.map +1 -0
  251. package/dist/tools/serp-providers/serpapi-provider.js +66 -0
  252. package/dist/tools/serp-providers/serpapi-provider.js.map +1 -0
  253. package/dist/tools/tool-registry.d.ts +9 -0
  254. package/dist/tools/tool-registry.d.ts.map +1 -0
  255. package/dist/tools/tool-registry.js +17 -0
  256. package/dist/tools/tool-registry.js.map +1 -0
  257. package/dist/tools/vector-tools.d.ts +224 -0
  258. package/dist/tools/vector-tools.d.ts.map +1 -0
  259. package/dist/tools/vector-tools.js +582 -0
  260. package/dist/tools/vector-tools.js.map +1 -0
  261. package/dist/tools/workspace-tools.d.ts +58 -0
  262. package/dist/tools/workspace-tools.d.ts.map +1 -0
  263. package/dist/tools/workspace-tools.js +152 -0
  264. package/dist/tools/workspace-tools.js.map +1 -0
  265. package/dist/types.d.ts +65 -0
  266. package/dist/types.d.ts.map +1 -0
  267. package/dist/types.js +5 -0
  268. package/dist/types.js.map +1 -0
  269. package/dist/utils/json-parser.d.ts +15 -0
  270. package/dist/utils/json-parser.d.ts.map +1 -0
  271. package/dist/utils/json-parser.js +78 -0
  272. package/dist/utils/json-parser.js.map +1 -0
  273. package/dist/utils/remove-base64-images.d.ts +12 -0
  274. package/dist/utils/remove-base64-images.d.ts.map +1 -0
  275. package/dist/utils/remove-base64-images.js +20 -0
  276. package/dist/utils/remove-base64-images.js.map +1 -0
  277. package/dist/utils/wrap-generators-parallel.d.ts +11 -0
  278. package/dist/utils/wrap-generators-parallel.d.ts.map +1 -0
  279. package/dist/utils/wrap-generators-parallel.js +38 -0
  280. package/dist/utils/wrap-generators-parallel.js.map +1 -0
  281. package/package.json +65 -0
@@ -0,0 +1,1114 @@
1
+ /**
2
+ * StepExecutor -- the core execution engine for a single step.
3
+ *
4
+ * Port of src/nodetool/agents/step_executor.py
5
+ *
6
+ * Manages the LLM interaction loop: sending messages, handling tool calls,
7
+ * monitoring token limits, and capturing the step result via finish_step
8
+ * or inline JSON extraction.
9
+ */
10
+ import * as fs from "node:fs/promises";
11
+ import * as path from "node:path";
12
+ import { createHash } from "node:crypto";
13
+ import { withAgentSpanGen } from "@nodetool-ai/runtime";
14
+ import { createLogger } from "@nodetool-ai/config";
15
+ import { TaskUpdateEvent } from "@nodetool-ai/protocol";
16
+ import { ControlNodeTool } from "./tools/control-tool.js";
17
+ import { FinishStepTool } from "./tools/finish-step-tool.js";
18
+ import { extractJSON } from "./utils/json-parser.js";
19
+ import { DEFAULT_TOKEN_LIMIT, MAX_TOOL_RESULT_CHARS } from "./constants.js";
20
+ import { rejectAgenticProvider } from "./reject-agentic-provider.js";
21
+ const log = createLogger("nodetool.agents.step-executor");
22
+ const DEFAULT_MAX_ITERATIONS = 30;
23
+ const JSON_FAILURE_ALERT_THRESHOLD = 3;
24
+ const MAX_JSON_PARSE_FAILURES = 6;
25
+ // ---------------------------------------------------------------------------
26
+ // Shared prompt fragments (DRY)
27
+ // ---------------------------------------------------------------------------
28
+ const PROMPT_NO_HUMAN_FEEDBACK = `# Hard Constraint: No Human Feedback
29
+ - Do NOT ask clarifying questions or request user input.
30
+ - If something is ambiguous or missing, choose the simplest reasonable assumption, record it in your reasoning, and proceed.`;
31
+ const PROMPT_SCHEMA_STRICT = `# Output Discipline
32
+ - Never invent fields: the final result must match the schema exactly (no extra keys; include all required keys).
33
+ - Do not reveal chain-of-thought or internal reasoning traces in assistant text.
34
+ - Keep non-tool messages concise and factual.`;
35
+ const PROMPT_OUTPUT_SCHEMA = `# Output Schema
36
+ The final result must match this JSON schema exactly:
37
+ \`\`\`json
38
+ {{ output_schema_json }}
39
+ \`\`\``;
40
+ const PROMPT_TOOL_USE = `# Tool Use
41
+ - Use tools only when they materially improve correctness or are required.
42
+ - Avoid exploratory or repeated tool calls that are unlikely to change the outcome.
43
+ - Before each tool call, emit a one-sentence rationale describing what you're doing and why.
44
+
45
+ ## File Tools
46
+ - Use \`read_file\` to read files. Do not use \`run_code\` with cat/head/tail.
47
+ - Use \`edit_file\` for modifying existing files — it performs exact string replacement and only sends the diff. Prefer it over \`write_file\` for modifications.
48
+ - Use \`write_file\` only for creating new files or complete rewrites.
49
+ - Use \`glob\` to find files by name pattern (e.g. "**/*.ts"). Do not use \`run_code\` with find or ls.
50
+ - Use \`grep\` to search file contents with regex. Do not use \`run_code\` with grep or rg.
51
+ - Use \`run_code\` with language="bash" only for system commands and operations that require shell execution.
52
+
53
+ ## Web Tools
54
+ - Use \`browser\` to fetch and read the content of a web page. It returns cleaned, readable text extracted from HTML. This is the right choice for reading articles, documentation, or any page content.
55
+ - Use \`http_request\` for API calls, POST/PUT/PATCH requests, or when you need raw response headers and body. Do not use it for reading page content — use \`browser\` instead.
56
+ - Use \`download_file\` to save a file (binary or text) from a URL to disk.`;
57
+ const PROMPT_FINISH_STEP = `# Completion (Tool Call Only)
58
+ - When done, CALL \`finish_step\` exactly once with:
59
+ {"result": <result>}
60
+ - Do NOT output the final result in assistant text — only use the tool.
61
+ - Stop immediately after calling \`finish_step\`.`;
62
+ // ---------------------------------------------------------------------------
63
+ // Assembled system prompts
64
+ // ---------------------------------------------------------------------------
65
+ const DEFAULT_EXECUTION_SYSTEM_PROMPT = `# Role
66
+ You are executing EXACTLY one step within a larger plan. Complete this step end-to-end without asking for clarification.
67
+
68
+ # Objective
69
+ {{ step_content }}
70
+
71
+ ${PROMPT_NO_HUMAN_FEEDBACK}
72
+
73
+ # Scope & Discipline
74
+ - Do ONLY what is required to satisfy this step objective; avoid tangents and extra work.
75
+ - Use upstream step results already present in context; do not ask for them again.
76
+ ${PROMPT_SCHEMA_STRICT}
77
+
78
+ ${PROMPT_OUTPUT_SCHEMA}
79
+
80
+ ${PROMPT_TOOL_USE}
81
+
82
+ ${PROMPT_FINISH_STEP}`;
83
+ const DEFAULT_FINISH_TASK_SYSTEM_PROMPT = `# Role
84
+ You are completing the final aggregation task, synthesizing results from prior steps into a single coherent deliverable.
85
+
86
+ ${PROMPT_NO_HUMAN_FEEDBACK}
87
+
88
+ # Scope & Discipline
89
+ - Focus on synthesis and aggregation only. Do NOT do additional research or data gathering.
90
+ - Preserve every concrete artifact from upstream results (image URLs, file paths, tables, key facts) — never drop or paraphrase them away.
91
+ - Use upstream step results already present in context; do not ask for them again.
92
+ ${PROMPT_SCHEMA_STRICT}
93
+
94
+ ${PROMPT_OUTPUT_SCHEMA}
95
+
96
+ ${PROMPT_TOOL_USE}
97
+
98
+ ${PROMPT_FINISH_STEP}`;
99
+ const DEFAULT_UNSTRUCTURED_SYSTEM_PROMPT = `# Role
100
+ You are executing a task. Your job is to complete it end-to-end without asking for clarification.
101
+
102
+ # Objective
103
+ {{ step_content }}
104
+
105
+ ${PROMPT_NO_HUMAN_FEEDBACK}
106
+
107
+ # Operating Mode
108
+ - Use tools as needed to achieve the objective.
109
+ - When you have the final answer or have completed the task, provide the result as your final response.
110
+ - Do not reveal chain-of-thought or internal reasoning traces.
111
+
112
+ # Communication Pattern (Tool Preambles)
113
+ Before making tool calls, provide clear progress updates:
114
+ 1. First assistant message: Restate the objective in one sentence, then list a short numbered plan (1-3 steps).
115
+ 2. Before each tool call: Emit a one-sentence message describing what you're doing and why.
116
+ 3. After tool results: Provide a brief update only if the result changes your plan.
117
+
118
+ ${PROMPT_TOOL_USE}`;
119
+ // ---------------------------------------------------------------------------
120
+ // Schema validation helpers
121
+ // ---------------------------------------------------------------------------
122
+ function validateAndSanitizeSchema(schema, defaultDescription = "Result object") {
123
+ if (schema === null || schema === undefined) {
124
+ throw new Error("Schema is null or undefined");
125
+ }
126
+ let parsed = schema;
127
+ if (typeof parsed === "string") {
128
+ parsed = JSON.parse(parsed);
129
+ }
130
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
131
+ throw new Error(`Schema is not an object (type: ${typeof parsed})`);
132
+ }
133
+ // Deep copy
134
+ const result = JSON.parse(JSON.stringify(parsed));
135
+ if (!("type" in result)) {
136
+ result["type"] = "object";
137
+ }
138
+ if (!("description" in result)) {
139
+ result["description"] = defaultDescription;
140
+ }
141
+ const disallowedExtensionKeys = new Set([
142
+ "oneOf",
143
+ "anyOf",
144
+ "allOf",
145
+ "not",
146
+ "if",
147
+ "then",
148
+ "else",
149
+ "patternProperties"
150
+ ]);
151
+ function shouldDefaultAdditionalProperties(obj) {
152
+ if ("additionalProperties" in obj)
153
+ return false;
154
+ if (Object.keys(obj).some((k) => disallowedExtensionKeys.has(k)))
155
+ return false;
156
+ const schemaType = obj["type"];
157
+ if (Array.isArray(schemaType)) {
158
+ if (schemaType.length !== 1 || schemaType[0] !== "object")
159
+ return false;
160
+ }
161
+ else if (schemaType !== undefined && schemaType !== "object") {
162
+ return false;
163
+ }
164
+ return (schemaType === "object" ||
165
+ (schemaType === undefined && obj["properties"] !== undefined));
166
+ }
167
+ function cleanSchemaRecursive(obj) {
168
+ if (Array.isArray(obj)) {
169
+ return obj.map(cleanSchemaRecursive);
170
+ }
171
+ if (obj !== null && typeof obj === "object") {
172
+ const cleaned = {};
173
+ for (const [key, value] of Object.entries(obj)) {
174
+ cleaned[key] = cleanSchemaRecursive(value);
175
+ }
176
+ if (shouldDefaultAdditionalProperties(cleaned)) {
177
+ cleaned["additionalProperties"] = false;
178
+ }
179
+ return cleaned;
180
+ }
181
+ return obj;
182
+ }
183
+ return cleanSchemaRecursive(result);
184
+ }
185
+ // ---------------------------------------------------------------------------
186
+ // Think-tag removal
187
+ // ---------------------------------------------------------------------------
188
+ function removeThinkTags(text) {
189
+ if (!text)
190
+ return "";
191
+ return text
192
+ .replace(/<think>[\s\S]*?<\/(?:redacted_thinking|think)>/g, "")
193
+ .replace(/<think>[\s\S]*/g, "")
194
+ .trim();
195
+ }
196
+ // ---------------------------------------------------------------------------
197
+ // Tool result normalization
198
+ // ---------------------------------------------------------------------------
199
+ function normalizeToolResult(value) {
200
+ if (value === null || value === undefined)
201
+ return value;
202
+ if (typeof value === "string" ||
203
+ typeof value === "number" ||
204
+ typeof value === "boolean")
205
+ return value;
206
+ if (Array.isArray(value))
207
+ return value.map(normalizeToolResult);
208
+ if (typeof value === "object") {
209
+ const obj = value;
210
+ if (typeof obj["toJSON"] === "function") {
211
+ return normalizeToolResult(obj.toJSON());
212
+ }
213
+ const result = {};
214
+ for (const [k, v] of Object.entries(obj)) {
215
+ result[k] = normalizeToolResult(v);
216
+ }
217
+ return result;
218
+ }
219
+ return String(value);
220
+ }
221
+ // ---------------------------------------------------------------------------
222
+ // Simple template rendering (replaces Jinja2)
223
+ // ---------------------------------------------------------------------------
224
+ function renderTemplate(template, context) {
225
+ let result = template;
226
+ for (const [key, value] of Object.entries(context)) {
227
+ result = result.replaceAll(`{{ ${key} }}`, value);
228
+ result = result.replaceAll(`{{${key}}}`, value);
229
+ }
230
+ return result;
231
+ }
232
+ export class StepExecutor {
233
+ history = [];
234
+ step;
235
+ task;
236
+ tools;
237
+ provider;
238
+ model;
239
+ context;
240
+ systemPrompt;
241
+ maxTokenLimit;
242
+ maxIterations;
243
+ useFinishTask;
244
+ inConclusionStage = false;
245
+ result = null;
246
+ finishStepTool = null;
247
+ resultSchema = null;
248
+ iterations = 0;
249
+ jsonParseFailures = 0;
250
+ generationFailures = 0;
251
+ sources = [];
252
+ sourcesSet = new Set();
253
+ inputTokensTotal = 0;
254
+ outputTokensTotal = 0;
255
+ _controlEvents = [];
256
+ threadId;
257
+ constructor(opts) {
258
+ this.task = opts.task;
259
+ this.step = opts.step;
260
+ this.context = opts.context;
261
+ this.provider = opts.provider;
262
+ this.model = opts.model;
263
+ this.tools = opts.tools ? [...opts.tools] : [];
264
+ this.maxTokenLimit = opts.maxTokenLimit ?? DEFAULT_TOKEN_LIMIT;
265
+ this.maxIterations = opts.maxIterations ?? DEFAULT_MAX_ITERATIONS;
266
+ this.useFinishTask = opts.useFinishTask ?? false;
267
+ this.threadId = opts.threadId;
268
+ // Load and sanitize the output schema
269
+ this.resultSchema = this.loadResultSchema();
270
+ // Setup finish_step tool if we have a schema
271
+ if (this.resultSchema) {
272
+ this.finishStepTool = new FinishStepTool(this.resultSchema);
273
+ this.tools.push(this.finishStepTool);
274
+ }
275
+ // Build the system prompt from templates
276
+ this.systemPrompt = this.buildSystemPrompt(opts.systemPrompt);
277
+ }
278
+ /**
279
+ * Rough token estimate based on JSON serialized history length / 4.
280
+ */
281
+ estimateTokens() {
282
+ return Math.ceil(JSON.stringify(this.history).length / 4);
283
+ }
284
+ /**
285
+ * Parse and sanitize the declared output schema for this step.
286
+ */
287
+ loadResultSchema() {
288
+ if (!this.step.outputSchema)
289
+ return null;
290
+ const defaultDescription = this.useFinishTask
291
+ ? "The task result"
292
+ : "The step result";
293
+ try {
294
+ const raw = typeof this.step.outputSchema === "string"
295
+ ? JSON.parse(this.step.outputSchema)
296
+ : this.step.outputSchema;
297
+ return validateAndSanitizeSchema(raw, defaultDescription);
298
+ }
299
+ catch {
300
+ // Fallback: permissive object schema
301
+ return { type: "object", description: defaultDescription };
302
+ }
303
+ }
304
+ /**
305
+ * Build the system prompt for this step using templates.
306
+ */
307
+ buildSystemPrompt(customPrompt) {
308
+ let basePrompt;
309
+ const templateContext = {};
310
+ if (this.resultSchema) {
311
+ const schemaJson = JSON.stringify(this.resultSchema, null, 2);
312
+ templateContext["output_schema_json"] = schemaJson;
313
+ if (this.useFinishTask) {
314
+ basePrompt = customPrompt ?? DEFAULT_FINISH_TASK_SYSTEM_PROMPT;
315
+ templateContext["step_content"] = this.step.instructions;
316
+ }
317
+ else {
318
+ basePrompt = customPrompt ?? DEFAULT_EXECUTION_SYSTEM_PROMPT;
319
+ templateContext["step_content"] = this.step.instructions;
320
+ }
321
+ }
322
+ else {
323
+ basePrompt = customPrompt ?? DEFAULT_UNSTRUCTURED_SYSTEM_PROMPT;
324
+ templateContext["step_content"] = this.step.instructions;
325
+ }
326
+ let prompt = renderTemplate(basePrompt, templateContext);
327
+ prompt += `\n\nToday's date is ${new Date().toISOString().slice(0, 10)}`;
328
+ return prompt;
329
+ }
330
+ /**
331
+ * Validate a result payload against the declared schema.
332
+ * Returns [isValid, errorDetail, normalizedResult].
333
+ */
334
+ validateResultPayload(resultPayload) {
335
+ const normalized = normalizeToolResult(resultPayload);
336
+ if (this.resultSchema === null) {
337
+ return [true, null, normalized];
338
+ }
339
+ // Basic structural validation: check required keys from schema
340
+ if (this.resultSchema["type"] === "object" &&
341
+ typeof normalized === "object" &&
342
+ normalized !== null) {
343
+ const requiredKeys = this.resultSchema["required"];
344
+ if (Array.isArray(requiredKeys)) {
345
+ const obj = normalized;
346
+ for (const key of requiredKeys) {
347
+ if (!(key in obj)) {
348
+ return [false, `Missing required key: ${key}`, normalized];
349
+ }
350
+ }
351
+ }
352
+ }
353
+ // Type check
354
+ const expectedType = this.resultSchema["type"];
355
+ if (expectedType === "string" && typeof normalized !== "string") {
356
+ return [false, `Expected string, got ${typeof normalized}`, normalized];
357
+ }
358
+ if (expectedType === "object" &&
359
+ (typeof normalized !== "object" || normalized === null)) {
360
+ return [false, `Expected object, got ${typeof normalized}`, normalized];
361
+ }
362
+ if (expectedType === "array" && !Array.isArray(normalized)) {
363
+ return [false, `Expected array, got ${typeof normalized}`, normalized];
364
+ }
365
+ return [true, null, normalized];
366
+ }
367
+ /**
368
+ * Persist the final result and mark the step as completed.
369
+ */
370
+ async storeCompletionResult(normalizedResult) {
371
+ this.step.completed = true;
372
+ this.step.endTime = Date.now();
373
+ await this.context.storeStepResult(this.step.id, normalizedResult);
374
+ if (this.useFinishTask) {
375
+ await this.context.storeStepResult(this.task.id, normalizedResult);
376
+ }
377
+ this.result = normalizedResult;
378
+ }
379
+ /**
380
+ * Append a system message instructing the LLM to complete via finish_step.
381
+ */
382
+ appendCompletionFeedback(detail, submittedResult) {
383
+ const schemaStr = JSON.stringify(this.resultSchema, null, 2);
384
+ const lines = [
385
+ "SYSTEM: Step completion must be signaled via the `finish_step` tool.",
386
+ `Detail: ${detail}`,
387
+ "Call `finish_step` exactly once with:",
388
+ '{"result": <result>}',
389
+ "Schema for `result`:",
390
+ schemaStr
391
+ ];
392
+ if (submittedResult !== undefined) {
393
+ try {
394
+ const preview = JSON.stringify(normalizeToolResult(submittedResult), null, 2);
395
+ lines.push("Previous submission preview:", preview);
396
+ }
397
+ catch {
398
+ lines.push("Previous submission preview:", String(submittedResult));
399
+ }
400
+ }
401
+ this.history.push({ role: "system", content: lines.join("\n") });
402
+ }
403
+ /**
404
+ * Attempt to parse and store a completion payload from the assistant message.
405
+ */
406
+ maybeFinalizeFromMessage(message) {
407
+ if (!message)
408
+ return [false, null];
409
+ // For unstructured steps (no schema), accept text content as the result
410
+ if (this.resultSchema === null) {
411
+ if (!message.toolCalls || message.toolCalls.length === 0) {
412
+ return [true, message.content];
413
+ }
414
+ return [false, null];
415
+ }
416
+ if (!message.content || typeof message.content !== "string")
417
+ return [false, null];
418
+ const parsed = extractJSON(message.content);
419
+ if (!parsed || typeof parsed !== "object")
420
+ return [false, null];
421
+ const obj = parsed;
422
+ const status = obj["status"];
423
+ if (status !== undefined && status !== "completed")
424
+ return [false, null];
425
+ if (status === "completed" && !("result" in obj)) {
426
+ this.history.push({
427
+ role: "system",
428
+ content: 'Missing \'result\' in completion payload. Provide: {"status": "completed", "result": <your_result>}.'
429
+ });
430
+ return [false, null];
431
+ }
432
+ const candidateResult = "result" in obj ? obj["result"] : parsed;
433
+ const [isValid, errorDetail, normalizedResult] = this.validateResultPayload(candidateResult);
434
+ if (!isValid ||
435
+ normalizedResult === null ||
436
+ normalizedResult === undefined) {
437
+ this.history.push({
438
+ role: "system",
439
+ content: `Schema validation failed: ${errorDetail ?? "unknown error"}`
440
+ });
441
+ return [false, null];
442
+ }
443
+ return [true, normalizedResult];
444
+ }
445
+ /**
446
+ * Track JSON parsing/validation failures and enforce a hard stop.
447
+ */
448
+ registerJsonFailure(detail) {
449
+ this.jsonParseFailures++;
450
+ if (this.jsonParseFailures === JSON_FAILURE_ALERT_THRESHOLD) {
451
+ const reminder = "SYSTEM: Do NOT output completion JSON in assistant text. You MUST call " +
452
+ "`finish_step` with {'result': <result>} matching the schema, with no extra keys.";
453
+ this.history.push({ role: "system", content: reminder });
454
+ this.inConclusionStage = true;
455
+ }
456
+ if (this.jsonParseFailures >= MAX_JSON_PARSE_FAILURES) {
457
+ throw new Error(`Exceeded maximum JSON parse attempts (${MAX_JSON_PARSE_FAILURES}) for step ${this.step.id}. Last failure: ${detail}`);
458
+ }
459
+ }
460
+ /**
461
+ * Filter tool calls based on whether we're in the conclusion stage.
462
+ */
463
+ filterToolCallsForCurrentStage(toolCalls) {
464
+ if (!this.inConclusionStage)
465
+ return toolCalls;
466
+ return toolCalls.filter((tc) => tc.name === "finish_step");
467
+ }
468
+ /**
469
+ * Get the tools available for the current stage.
470
+ */
471
+ getCurrentTools() {
472
+ if (this.inConclusionStage) {
473
+ return this.finishStepTool ? [this.finishStepTool] : [];
474
+ }
475
+ return [...this.tools];
476
+ }
477
+ /**
478
+ * Transition to conclusion stage: restrict tools to finish_step only.
479
+ */
480
+ enterConclusionStage() {
481
+ if (this.inConclusionStage)
482
+ return;
483
+ this.inConclusionStage = true;
484
+ const hasFinishTool = !!this.finishStepTool;
485
+ const message = hasFinishTool
486
+ ? `SYSTEM: The conversation history is approaching the token limit (${this.maxTokenLimit} tokens).\n` +
487
+ "ENTERING CONCLUSION STAGE: You MUST now synthesize all gathered information and finalize the step.\n" +
488
+ "Only the `finish_step` tool is available. Call `finish_step` exactly once with:\n" +
489
+ '{"result": <result>} where <result> matches the declared schema.'
490
+ : `SYSTEM: The conversation history is approaching the token limit (${this.maxTokenLimit} tokens).\n` +
491
+ "ENTERING CONCLUSION STAGE: You MUST now synthesize all gathered information and finalize the step.\n" +
492
+ "Tools are not available. Provide the final answer concisely.";
493
+ // Prevent duplicate conclusion messages
494
+ if (!this.history.some((m) => m.role === "system" &&
495
+ typeof m.content === "string" &&
496
+ m.content.includes("ENTERING CONCLUSION STAGE"))) {
497
+ this.history.push({ role: "system", content: message });
498
+ }
499
+ }
500
+ /**
501
+ * Summarize older messages into a concise, factual summary.
502
+ */
503
+ async summarizeMessages(messages) {
504
+ const joined = messages
505
+ .filter((m) => m.content)
506
+ .map((m) => `${(m.role ?? "").toUpperCase()}: ${m.content}`)
507
+ .join("\n");
508
+ const prompt = "Summarize the following conversation concisely while preserving key facts, " +
509
+ "decisions, and results:\n\n" +
510
+ joined;
511
+ try {
512
+ const msg = await this.provider.generateMessageTraced({
513
+ messages: [
514
+ { role: "system", content: "Summarize previous context." },
515
+ { role: "user", content: prompt }
516
+ ],
517
+ model: this.model,
518
+ tools: [],
519
+ maxTokens: 512
520
+ });
521
+ return String(msg.content ?? "").trim();
522
+ }
523
+ catch {
524
+ return "Summary unavailable due to compression error.";
525
+ }
526
+ }
527
+ /**
528
+ * Trim or summarize older messages to stay within token limits.
529
+ */
530
+ async trimHistoryIfNeeded() {
531
+ const tokenCount = this.estimateTokens();
532
+ if (tokenCount < this.maxTokenLimit * 0.9)
533
+ return;
534
+ // Preserve the last 6 messages
535
+ const preserved = [];
536
+ for (let i = this.history.length - 1; i >= 0 && preserved.length < 6; i--) {
537
+ preserved.unshift(this.history[i]);
538
+ }
539
+ const earlierCount = this.history.length - preserved.length;
540
+ const earlierContext = earlierCount > 1 ? this.history.slice(1, earlierCount) : [];
541
+ if (earlierContext.length > 0) {
542
+ const summary = await this.summarizeMessages(earlierContext);
543
+ const systemPrompt = this.history[0];
544
+ this.history = [];
545
+ if (systemPrompt) {
546
+ this.history.push(systemPrompt);
547
+ }
548
+ this.history.push({
549
+ role: "system",
550
+ content: `Summary of previous context:\n${summary}`
551
+ });
552
+ }
553
+ else {
554
+ this.history = this.history.slice(0, 1);
555
+ }
556
+ this.history.push(...preserved);
557
+ // Trim further if still over budget
558
+ let currentTokens = this.estimateTokens();
559
+ while (currentTokens > this.maxTokenLimit * 0.85 &&
560
+ this.history.length > 2) {
561
+ this.history.splice(2, 1);
562
+ currentTokens = this.estimateTokens();
563
+ }
564
+ }
565
+ /**
566
+ * Serialize a tool result for history, with truncation.
567
+ */
568
+ serializeToolResultForHistory(toolResult, _toolName) {
569
+ if (toolResult === null || toolResult === undefined) {
570
+ return "Tool returned no output.";
571
+ }
572
+ try {
573
+ const normalized = normalizeToolResult(toolResult);
574
+ const serialized = JSON.stringify(normalized);
575
+ if (serialized.length > MAX_TOOL_RESULT_CHARS) {
576
+ return (serialized.slice(0, MAX_TOOL_RESULT_CHARS) +
577
+ "... [truncated to maintain context size]");
578
+ }
579
+ return serialized;
580
+ }
581
+ catch (e) {
582
+ return JSON.stringify({
583
+ error: `Failed to serialize tool result: ${e}`,
584
+ result_repr: String(toolResult).slice(0, 500)
585
+ });
586
+ }
587
+ }
588
+ /**
589
+ * Save base64 encoded binary data (images, audio) from tool results to workspace files.
590
+ * Mirrors Python's StepExecutor._handle_binary_artifact().
591
+ */
592
+ async handleBinaryArtifact(toolResult) {
593
+ if (typeof toolResult !== "object" ||
594
+ toolResult === null ||
595
+ Array.isArray(toolResult)) {
596
+ return toolResult;
597
+ }
598
+ const result = { ...toolResult };
599
+ const workspaceDir = this.context.workspaceDir;
600
+ if (!workspaceDir)
601
+ return result;
602
+ for (const field of ["image", "audio"]) {
603
+ const value = result[field];
604
+ if (typeof value !== "string")
605
+ continue;
606
+ const dataUriMatch = value.match(/^data:([^;]+);base64,(.+)$/s);
607
+ if (!dataUriMatch)
608
+ continue;
609
+ const [, mimeType, base64Data] = dataUriMatch;
610
+ const ext = mimeType.split("/")[1] ?? "bin";
611
+ const hash = createHash("sha256")
612
+ .update(base64Data)
613
+ .digest("hex")
614
+ .slice(0, 16);
615
+ const filename = `artifact_${hash}.${ext}`;
616
+ const artifactsDir = path.join(workspaceDir, "artifacts");
617
+ await fs.mkdir(artifactsDir, { recursive: true });
618
+ const filepath = path.join(artifactsDir, filename);
619
+ await fs.writeFile(filepath, Buffer.from(base64Data, "base64"));
620
+ result[field] = filepath;
621
+ if (!this.sourcesSet.has(filepath)) {
622
+ this.sources.push(filepath);
623
+ this.sourcesSet.add(filepath);
624
+ }
625
+ }
626
+ return result;
627
+ }
628
+ /**
629
+ * Persist important sandbox outputs (downloads, screenshots, generated artifacts)
630
+ * as assets so they survive ephemeral workspace/container cleanup.
631
+ */
632
+ async capturePersistentSandboxOutputs(toolName, toolArgs, toolResult) {
633
+ if (typeof toolResult !== "object" ||
634
+ toolResult === null ||
635
+ Array.isArray(toolResult)) {
636
+ return toolResult;
637
+ }
638
+ const result = { ...toolResult };
639
+ if (result.success === false) {
640
+ return result;
641
+ }
642
+ const candidates = [];
643
+ const candidateKeys = new Set();
644
+ const isExternalUri = (value) => {
645
+ const lower = value.toLowerCase();
646
+ return (lower.startsWith("http://") ||
647
+ lower.startsWith("https://") ||
648
+ lower.startsWith("asset://") ||
649
+ lower.startsWith("memory://") ||
650
+ lower.startsWith("s3://") ||
651
+ lower.startsWith("file://"));
652
+ };
653
+ const maybeAdd = (label, value) => {
654
+ if (typeof value === "string" &&
655
+ value.trim().length > 0 &&
656
+ !value.startsWith("data:") &&
657
+ !isExternalUri(value)) {
658
+ const key = `${label}:${value}`;
659
+ if (!candidateKeys.has(key)) {
660
+ candidates.push({ label, path: value });
661
+ candidateKeys.add(key);
662
+ }
663
+ }
664
+ };
665
+ maybeAdd("output_file", result.output_file);
666
+ if (result.success === true) {
667
+ maybeAdd("output_file", toolArgs?.["output_file"]);
668
+ }
669
+ maybeAdd("image", result.image);
670
+ maybeAdd("audio", result.audio);
671
+ if (candidates.length === 0) {
672
+ return result;
673
+ }
674
+ const refs = {};
675
+ for (const candidate of candidates) {
676
+ try {
677
+ const ref = await this.context.sandboxToAsset(candidate.path);
678
+ refs[candidate.label] = ref;
679
+ const uri = ref.uri;
680
+ if (typeof uri === "string" && uri && !this.sourcesSet.has(uri)) {
681
+ this.sources.push(uri);
682
+ this.sourcesSet.add(uri);
683
+ }
684
+ }
685
+ catch (error) {
686
+ log.warn("Failed to persist sandbox output as asset", {
687
+ stepId: this.step.id,
688
+ toolName,
689
+ path: candidate.path,
690
+ error: error instanceof Error ? error.message : String(error)
691
+ });
692
+ }
693
+ }
694
+ if (Object.keys(refs).length > 0) {
695
+ const existing = result.asset_refs;
696
+ if (typeof existing === "object" &&
697
+ existing !== null &&
698
+ !Array.isArray(existing)) {
699
+ result.asset_refs = { ...existing, ...refs };
700
+ }
701
+ else {
702
+ result.asset_refs = refs;
703
+ }
704
+ }
705
+ return result;
706
+ }
707
+ /**
708
+ * Track URLs from browser tool navigation results.
709
+ * Mirrors Python's _process_special_tool_side_effects().
710
+ */
711
+ trackToolSideEffects(toolName, result) {
712
+ if (toolName === "browser" &&
713
+ typeof result === "object" &&
714
+ result !== null) {
715
+ const url = result.url;
716
+ if (typeof url === "string" && url && !this.sourcesSet.has(url)) {
717
+ this.sources.push(url);
718
+ this.sourcesSet.add(url);
719
+ }
720
+ }
721
+ }
722
+ /**
723
+ * Generate a user-facing message for a tool call.
724
+ */
725
+ generateToolCallMessage(toolCall) {
726
+ for (const tool of this.tools) {
727
+ if (tool.name === toolCall.name) {
728
+ return tool.userMessage(toolCall.args);
729
+ }
730
+ }
731
+ return `Running ${toolCall.name}`;
732
+ }
733
+ /**
734
+ * Build the initial user message with instructions and dependency results.
735
+ */
736
+ async buildUserMessage() {
737
+ const parts = [this.step.instructions];
738
+ if (this.step.dependsOn.length > 0) {
739
+ for (const depId of this.step.dependsOn) {
740
+ const depResult = await this.context.loadStepResult(depId);
741
+ if (depResult !== undefined && depResult !== null) {
742
+ parts.push(`**Result from Task ${depId}:**\n${JSON.stringify(depResult, null, 2)}\n`);
743
+ }
744
+ }
745
+ }
746
+ parts.push("Please perform the step based on the provided context, instructions, and upstream task results.");
747
+ return parts.join("\n");
748
+ }
749
+ /**
750
+ * Execute the step, yielding ProcessingMessages as progress updates.
751
+ */
752
+ async *execute() {
753
+ yield* withAgentSpanGen("step", {
754
+ provider: this.provider.provider,
755
+ model: this.model,
756
+ task: this.step.instructions,
757
+ toolsCount: this.tools.length,
758
+ extra: {
759
+ "agent.step.id": this.step.id,
760
+ "agent.task.id": this.task.id
761
+ }
762
+ }, () => this._executeImpl());
763
+ }
764
+ async *_executeImpl() {
765
+ log.debug("Step started", {
766
+ stepId: this.step.id,
767
+ instructions: this.step.instructions.slice(0, 60)
768
+ });
769
+ // Initialize history with system prompt
770
+ this.history.push({ role: "system", content: this.systemPrompt });
771
+ // Build user message with instructions and dependency results
772
+ const userContent = await this.buildUserMessage();
773
+ this.history.push({ role: "user", content: userContent });
774
+ // Yield task update: step started
775
+ this.step.startTime = Date.now();
776
+ yield {
777
+ type: "task_update",
778
+ node_id: this.step.id,
779
+ task: { id: this.task.id, title: this.task.title },
780
+ step: { id: this.step.id, instructions: this.step.instructions },
781
+ event: TaskUpdateEvent.StepStarted
782
+ };
783
+ rejectAgenticProvider(this.provider, "StepExecutor.execute");
784
+ // --- Standard multi-iteration loop ---
785
+ while (!this.step.completed && this.iterations < this.maxIterations) {
786
+ this.iterations++;
787
+ // Check token budget
788
+ const tokenCount = this.estimateTokens();
789
+ if (tokenCount > this.maxTokenLimit && !this.inConclusionStage) {
790
+ this.enterConclusionStage();
791
+ yield {
792
+ type: "task_update",
793
+ node_id: this.step.id,
794
+ task: { id: this.task.id, title: this.task.title },
795
+ step: { id: this.step.id, instructions: this.step.instructions },
796
+ event: TaskUpdateEvent.EnteredConclusionStage
797
+ };
798
+ }
799
+ // Trim history if needed
800
+ await this.trimHistoryIfNeeded();
801
+ // Determine available tools
802
+ const currentTools = this.getCurrentTools();
803
+ const providerTools = currentTools.map((t) => t.toProviderTool());
804
+ // Yield log update
805
+ yield {
806
+ type: "log_update",
807
+ node_id: this.step.id,
808
+ node_name: `Step: ${this.step.id}`,
809
+ content: !this.inConclusionStage
810
+ ? "Generating next steps..."
811
+ : "Synthesizing final answer...",
812
+ severity: "info"
813
+ };
814
+ // Track input tokens
815
+ this.inputTokensTotal += this.estimateTokens();
816
+ // Call LLM
817
+ let content = "";
818
+ const toolCalls = [];
819
+ let message;
820
+ try {
821
+ const stream = this.provider.generateMessagesTraced({
822
+ messages: [...this.history],
823
+ model: this.model,
824
+ tools: providerTools.length > 0 ? providerTools : undefined,
825
+ threadId: this.threadId
826
+ });
827
+ for await (const item of stream) {
828
+ if (isChunk(item)) {
829
+ content += item.content ?? "";
830
+ yield {
831
+ type: "chunk",
832
+ node_id: this.step.id,
833
+ content: item.content,
834
+ done: false
835
+ };
836
+ }
837
+ if (isToolCall(item)) {
838
+ log.debug("Tool call", { name: item.name });
839
+ toolCalls.push(item);
840
+ }
841
+ }
842
+ // Clean think tags from content
843
+ content = removeThinkTags(content);
844
+ message = { role: "assistant", content: content ?? "" };
845
+ if (toolCalls.length > 0) {
846
+ message.toolCalls = toolCalls;
847
+ }
848
+ // Estimate output tokens
849
+ this.outputTokensTotal += Math.ceil((content.length + JSON.stringify(toolCalls).length) / 4);
850
+ }
851
+ catch (e) {
852
+ log.error("Step failed", { stepId: this.step.id, error: String(e) });
853
+ this.generationFailures++;
854
+ if (this.generationFailures >= 3)
855
+ throw e;
856
+ message = {
857
+ role: "assistant",
858
+ content: `Error generating message: ${e}`
859
+ };
860
+ }
861
+ // Filter tool calls for current stage
862
+ const filteredToolCalls = message.toolCalls
863
+ ? this.filterToolCallsForCurrentStage(message.toolCalls)
864
+ : [];
865
+ message.toolCalls =
866
+ filteredToolCalls.length > 0 ? filteredToolCalls : undefined;
867
+ // Add assistant message to history
868
+ this.history.push(message);
869
+ // Process tool calls
870
+ if (filteredToolCalls.length > 0) {
871
+ // Check for finish_step tool call first
872
+ const finishStepCall = filteredToolCalls.find((tc) => tc.name === "finish_step");
873
+ if (finishStepCall && this.finishStepTool) {
874
+ // Yield tool call update
875
+ yield {
876
+ type: "tool_call_update",
877
+ node_id: this.step.id,
878
+ name: finishStepCall.name,
879
+ args: finishStepCall.args,
880
+ message: this.generateToolCallMessage(finishStepCall)
881
+ };
882
+ // Extract and validate result
883
+ const resultPayload = finishStepCall.args?.["result"] ?? finishStepCall.args;
884
+ if (resultPayload !== undefined && resultPayload !== null) {
885
+ const [isValid, errorDetail, normalizedResult] = this.validateResultPayload(resultPayload);
886
+ if (isValid &&
887
+ normalizedResult !== null &&
888
+ normalizedResult !== undefined) {
889
+ // Add tool result to history
890
+ this.history.push({
891
+ role: "tool",
892
+ toolCallId: finishStepCall.id,
893
+ content: '{"status": "completed"}'
894
+ });
895
+ await this.storeCompletionResult(normalizedResult);
896
+ log.debug("Step completed", { stepId: this.step.id });
897
+ yield {
898
+ type: "task_update",
899
+ node_id: this.step.id,
900
+ task: { id: this.task.id, title: this.task.title },
901
+ step: {
902
+ id: this.step.id,
903
+ instructions: this.step.instructions
904
+ },
905
+ event: TaskUpdateEvent.StepCompleted
906
+ };
907
+ yield {
908
+ type: "step_result",
909
+ step: {
910
+ id: this.step.id,
911
+ instructions: this.step.instructions
912
+ },
913
+ result: normalizedResult,
914
+ is_task_result: this.useFinishTask
915
+ };
916
+ break;
917
+ }
918
+ else {
919
+ // Invalid result - add feedback and continue loop
920
+ this.history.push({
921
+ role: "tool",
922
+ toolCallId: finishStepCall.id,
923
+ content: JSON.stringify({
924
+ error: `Result validation failed: ${errorDetail}`
925
+ })
926
+ });
927
+ this.appendCompletionFeedback(errorDetail ?? "Result failed schema validation.", resultPayload);
928
+ }
929
+ }
930
+ else {
931
+ this.history.push({
932
+ role: "tool",
933
+ toolCallId: finishStepCall.id,
934
+ content: '{"error": "Missing result in finish_step call"}'
935
+ });
936
+ }
937
+ }
938
+ else {
939
+ // Process non-finish_step tool calls
940
+ const regularToolCalls = filteredToolCalls.filter((tc) => tc.name !== "finish_step");
941
+ // Yield tool call updates
942
+ for (const tc of regularToolCalls) {
943
+ yield {
944
+ type: "tool_call_update",
945
+ node_id: this.step.id,
946
+ name: tc.name,
947
+ args: tc.args,
948
+ message: this.generateToolCallMessage(tc)
949
+ };
950
+ }
951
+ const toolNamesStr = regularToolCalls.map((tc) => tc.name).join(", ");
952
+ // Standard execution path: run tools ourselves and add results to history
953
+ yield {
954
+ type: "log_update",
955
+ node_id: this.step.id,
956
+ node_name: `Step: ${this.step.id}`,
957
+ content: `Executing tools: ${toolNamesStr}...`,
958
+ severity: "info"
959
+ };
960
+ // Execute tool calls in parallel
961
+ const toolResults = await Promise.allSettled(regularToolCalls.map(async (tc) => {
962
+ const tool = this.tools.find((t) => t.name === tc.name);
963
+ if (!tool)
964
+ return { error: `Unknown tool: ${tc.name}` };
965
+ // Intercept ControlNodeTool: create event instead of calling process()
966
+ if (tool instanceof ControlNodeTool) {
967
+ const event = tool.createControlEvent(tc.args ?? {});
968
+ this._controlEvents.push({
969
+ targetNodeId: tool.targetNodeId,
970
+ event
971
+ });
972
+ return tool.userMessage(tc.args ?? {});
973
+ }
974
+ try {
975
+ const result = await tool.process(this.context, tc.args ?? {});
976
+ return result;
977
+ }
978
+ catch (e) {
979
+ return { error: String(e) };
980
+ }
981
+ }));
982
+ // Add tool results to history
983
+ for (let i = 0; i < regularToolCalls.length; i++) {
984
+ const tc = regularToolCalls[i];
985
+ const settledResult = toolResults[i];
986
+ let toolResult;
987
+ if (settledResult.status === "fulfilled") {
988
+ toolResult = settledResult.value;
989
+ }
990
+ else {
991
+ toolResult = {
992
+ error: `Tool execution failed: ${settledResult.reason}`
993
+ };
994
+ }
995
+ // Save base64 binary artifacts (images, audio) to workspace files
996
+ toolResult = await this.handleBinaryArtifact(toolResult);
997
+ toolResult = await this.capturePersistentSandboxOutputs(tc.name, tc.args, toolResult);
998
+ // Track browser URLs for source lineage (from args and results)
999
+ if (tc.name === "browser" && tc.args?.["url"]) {
1000
+ const url = String(tc.args["url"]);
1001
+ if (!this.sourcesSet.has(url)) {
1002
+ this.sources.push(url);
1003
+ this.sourcesSet.add(url);
1004
+ }
1005
+ }
1006
+ this.trackToolSideEffects(tc.name, toolResult);
1007
+ const resultStr = this.serializeToolResultForHistory(toolResult, tc.name);
1008
+ this.history.push({
1009
+ role: "tool",
1010
+ toolCallId: tc.id,
1011
+ content: resultStr
1012
+ });
1013
+ }
1014
+ yield {
1015
+ type: "log_update",
1016
+ node_id: this.step.id,
1017
+ node_name: `Step: ${this.step.id}`,
1018
+ content: `Completed tool execution: ${toolNamesStr}.`,
1019
+ severity: "info"
1020
+ };
1021
+ }
1022
+ }
1023
+ // Try to finalize from message content (inline JSON completion).
1024
+ if (!this.step.completed) {
1025
+ const [completed, normalizedResult] = this.maybeFinalizeFromMessage(message);
1026
+ if (completed &&
1027
+ normalizedResult !== null &&
1028
+ normalizedResult !== undefined) {
1029
+ await this.storeCompletionResult(normalizedResult);
1030
+ yield {
1031
+ type: "task_update",
1032
+ node_id: this.step.id,
1033
+ task: { id: this.task.id, title: this.task.title },
1034
+ step: { id: this.step.id, instructions: this.step.instructions },
1035
+ event: TaskUpdateEvent.StepCompleted
1036
+ };
1037
+ yield {
1038
+ type: "step_result",
1039
+ step: { id: this.step.id, instructions: this.step.instructions },
1040
+ result: normalizedResult,
1041
+ is_task_result: this.useFinishTask
1042
+ };
1043
+ break;
1044
+ }
1045
+ }
1046
+ }
1047
+ // If we exhausted iterations without completing, yield a failure event
1048
+ // and a step_result so downstream steps see an explicit error rather than undefined.
1049
+ if (!this.step.completed) {
1050
+ this.step.completed = true;
1051
+ this.step.endTime = Date.now();
1052
+ const errorResult = {
1053
+ error: `Step failed: exceeded ${this.maxIterations} iterations without completion`
1054
+ };
1055
+ this.result = errorResult;
1056
+ await this.context.storeStepResult(this.step.id, errorResult);
1057
+ yield {
1058
+ type: "task_update",
1059
+ node_id: this.step.id,
1060
+ task: { id: this.task.id, title: this.task.title },
1061
+ step: { id: this.step.id, instructions: this.step.instructions },
1062
+ event: TaskUpdateEvent.StepFailed
1063
+ };
1064
+ yield {
1065
+ type: "step_result",
1066
+ step: { id: this.step.id, instructions: this.step.instructions },
1067
+ result: errorResult,
1068
+ is_task_result: this.useFinishTask
1069
+ };
1070
+ }
1071
+ }
1072
+ /**
1073
+ * Get the captured result after execution completes.
1074
+ */
1075
+ getResult() {
1076
+ return this.result;
1077
+ }
1078
+ /**
1079
+ * Get tracked sources (e.g. browser URLs).
1080
+ */
1081
+ getSources() {
1082
+ return [...this.sources];
1083
+ }
1084
+ /**
1085
+ * Get token usage statistics.
1086
+ */
1087
+ getTokenUsage() {
1088
+ return {
1089
+ inputTokensTotal: this.inputTokensTotal,
1090
+ outputTokensTotal: this.outputTokensTotal
1091
+ };
1092
+ }
1093
+ /**
1094
+ * Get control events emitted during execution (from ControlNodeTool calls).
1095
+ * The caller (workflow actor/runner) is responsible for dispatching these.
1096
+ */
1097
+ getControlEvents() {
1098
+ return [...this._controlEvents];
1099
+ }
1100
+ }
1101
+ // ---------------------------------------------------------------------------
1102
+ // Helpers to discriminate ProviderStreamItem union members
1103
+ // ---------------------------------------------------------------------------
1104
+ function isChunk(item) {
1105
+ return ("type" in item &&
1106
+ item["type"] === "chunk" &&
1107
+ typeof item["content"] === "string");
1108
+ }
1109
+ function isToolCall(item) {
1110
+ return ("name" in item &&
1111
+ typeof item["name"] === "string" &&
1112
+ "id" in item);
1113
+ }
1114
+ //# sourceMappingURL=step-executor.js.map