@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.
- package/dist/agent-executor.d.ts +62 -0
- package/dist/agent-executor.d.ts.map +1 -0
- package/dist/agent-executor.js +324 -0
- package/dist/agent-executor.js.map +1 -0
- package/dist/agent-step-executor.d.ts +28 -0
- package/dist/agent-step-executor.d.ts.map +1 -0
- package/dist/agent-step-executor.js +99 -0
- package/dist/agent-step-executor.js.map +1 -0
- package/dist/agent-workflow-runner.d.ts +31 -0
- package/dist/agent-workflow-runner.d.ts.map +1 -0
- package/dist/agent-workflow-runner.js +124 -0
- package/dist/agent-workflow-runner.js.map +1 -0
- package/dist/agent.d.ts +115 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +587 -0
- package/dist/agent.js.map +1 -0
- package/dist/base-agent.d.ts +42 -0
- package/dist/base-agent.d.ts.map +1 -0
- package/dist/base-agent.js +29 -0
- package/dist/base-agent.js.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -0
- package/dist/graph-builder.d.ts +49 -0
- package/dist/graph-builder.d.ts.map +1 -0
- package/dist/graph-builder.js +206 -0
- package/dist/graph-builder.js.map +1 -0
- package/dist/graph-planner.d.ts +56 -0
- package/dist/graph-planner.d.ts.map +1 -0
- package/dist/graph-planner.js +311 -0
- package/dist/graph-planner.js.map +1 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/js-sandbox.d.ts +84 -0
- package/dist/js-sandbox.d.ts.map +1 -0
- package/dist/js-sandbox.js +654 -0
- package/dist/js-sandbox.js.map +1 -0
- package/dist/multi-mode-agent.d.ts +86 -0
- package/dist/multi-mode-agent.d.ts.map +1 -0
- package/dist/multi-mode-agent.js +476 -0
- package/dist/multi-mode-agent.js.map +1 -0
- package/dist/output-format.d.ts +10 -0
- package/dist/output-format.d.ts.map +1 -0
- package/dist/output-format.js +17 -0
- package/dist/output-format.js.map +1 -0
- package/dist/parallel-task-executor.d.ts +87 -0
- package/dist/parallel-task-executor.d.ts.map +1 -0
- package/dist/parallel-task-executor.js +332 -0
- package/dist/parallel-task-executor.js.map +1 -0
- package/dist/prompts/graph-planner-prompt.d.ts +48 -0
- package/dist/prompts/graph-planner-prompt.d.ts.map +1 -0
- package/dist/prompts/graph-planner-prompt.js +180 -0
- package/dist/prompts/graph-planner-prompt.js.map +1 -0
- package/dist/reject-agentic-provider.d.ts +21 -0
- package/dist/reject-agentic-provider.d.ts.map +1 -0
- package/dist/reject-agentic-provider.js +32 -0
- package/dist/reject-agentic-provider.js.map +1 -0
- package/dist/simple-agent.d.ts +29 -0
- package/dist/simple-agent.d.ts.map +1 -0
- package/dist/simple-agent.js +54 -0
- package/dist/simple-agent.js.map +1 -0
- package/dist/step-executor.d.ts +162 -0
- package/dist/step-executor.d.ts.map +1 -0
- package/dist/step-executor.js +1114 -0
- package/dist/step-executor.js.map +1 -0
- package/dist/sub-agent-planner.d.ts +33 -0
- package/dist/sub-agent-planner.d.ts.map +1 -0
- package/dist/sub-agent-planner.js +203 -0
- package/dist/sub-agent-planner.js.map +1 -0
- package/dist/task-executor.d.ts +89 -0
- package/dist/task-executor.d.ts.map +1 -0
- package/dist/task-executor.js +359 -0
- package/dist/task-executor.js.map +1 -0
- package/dist/task-planner.d.ts +44 -0
- package/dist/task-planner.d.ts.map +1 -0
- package/dist/task-planner.js +532 -0
- package/dist/task-planner.js.map +1 -0
- package/dist/team/db-task-board.d.ts +54 -0
- package/dist/team/db-task-board.d.ts.map +1 -0
- package/dist/team/db-task-board.js +361 -0
- package/dist/team/db-task-board.js.map +1 -0
- package/dist/team/edge-message-bus.d.ts +51 -0
- package/dist/team/edge-message-bus.d.ts.map +1 -0
- package/dist/team/edge-message-bus.js +169 -0
- package/dist/team/edge-message-bus.js.map +1 -0
- package/dist/team/edge-task-board.d.ts +77 -0
- package/dist/team/edge-task-board.d.ts.map +1 -0
- package/dist/team/edge-task-board.js +140 -0
- package/dist/team/edge-task-board.js.map +1 -0
- package/dist/team/index.d.ts +16 -0
- package/dist/team/index.d.ts.map +1 -0
- package/dist/team/index.js +17 -0
- package/dist/team/index.js.map +1 -0
- package/dist/team/message-bus.d.ts +58 -0
- package/dist/team/message-bus.d.ts.map +1 -0
- package/dist/team/message-bus.js +151 -0
- package/dist/team/message-bus.js.map +1 -0
- package/dist/team/task-board.d.ts +106 -0
- package/dist/team/task-board.d.ts.map +1 -0
- package/dist/team/task-board.js +370 -0
- package/dist/team/task-board.js.map +1 -0
- package/dist/team/team-executor.d.ts +72 -0
- package/dist/team/team-executor.d.ts.map +1 -0
- package/dist/team/team-executor.js +466 -0
- package/dist/team/team-executor.js.map +1 -0
- package/dist/team/team-tools.d.ts +252 -0
- package/dist/team/team-tools.d.ts.map +1 -0
- package/dist/team/team-tools.js +412 -0
- package/dist/team/team-tools.js.map +1 -0
- package/dist/team/types.d.ts +198 -0
- package/dist/team/types.d.ts.map +1 -0
- package/dist/team/types.js +8 -0
- package/dist/team/types.js.map +1 -0
- package/dist/tools/add-edge-tool.d.ts +18 -0
- package/dist/tools/add-edge-tool.d.ts.map +1 -0
- package/dist/tools/add-edge-tool.js +92 -0
- package/dist/tools/add-edge-tool.js.map +1 -0
- package/dist/tools/add-node-tool.d.ts +22 -0
- package/dist/tools/add-node-tool.d.ts.map +1 -0
- package/dist/tools/add-node-tool.js +128 -0
- package/dist/tools/add-node-tool.js.map +1 -0
- package/dist/tools/asset-tools.d.ts +58 -0
- package/dist/tools/asset-tools.d.ts.map +1 -0
- package/dist/tools/asset-tools.js +191 -0
- package/dist/tools/asset-tools.js.map +1 -0
- package/dist/tools/base-tool.d.ts +25 -0
- package/dist/tools/base-tool.d.ts.map +1 -0
- package/dist/tools/base-tool.js +24 -0
- package/dist/tools/base-tool.js.map +1 -0
- package/dist/tools/browser-tools.d.ts +32 -0
- package/dist/tools/browser-tools.d.ts.map +1 -0
- package/dist/tools/browser-tools.js +208 -0
- package/dist/tools/browser-tools.js.map +1 -0
- package/dist/tools/calculator-tool.d.ts +24 -0
- package/dist/tools/calculator-tool.d.ts.map +1 -0
- package/dist/tools/calculator-tool.js +64 -0
- package/dist/tools/calculator-tool.js.map +1 -0
- package/dist/tools/claude-code-tools.d.ts +96 -0
- package/dist/tools/claude-code-tools.d.ts.map +1 -0
- package/dist/tools/claude-code-tools.js +463 -0
- package/dist/tools/claude-code-tools.js.map +1 -0
- package/dist/tools/code-tools.d.ts +26 -0
- package/dist/tools/code-tools.d.ts.map +1 -0
- package/dist/tools/code-tools.js +82 -0
- package/dist/tools/code-tools.js.map +1 -0
- package/dist/tools/control-tool.d.ts +47 -0
- package/dist/tools/control-tool.d.ts.map +1 -0
- package/dist/tools/control-tool.js +101 -0
- package/dist/tools/control-tool.js.map +1 -0
- package/dist/tools/create-plan-tool.d.ts +29 -0
- package/dist/tools/create-plan-tool.d.ts.map +1 -0
- package/dist/tools/create-plan-tool.js +355 -0
- package/dist/tools/create-plan-tool.js.map +1 -0
- package/dist/tools/create-task-tool.d.ts +28 -0
- package/dist/tools/create-task-tool.d.ts.map +1 -0
- package/dist/tools/create-task-tool.js +254 -0
- package/dist/tools/create-task-tool.js.map +1 -0
- package/dist/tools/dataseo-tools.d.ts +38 -0
- package/dist/tools/dataseo-tools.d.ts.map +1 -0
- package/dist/tools/dataseo-tools.js +329 -0
- package/dist/tools/dataseo-tools.js.map +1 -0
- package/dist/tools/email-tools.d.ts +76 -0
- package/dist/tools/email-tools.d.ts.map +1 -0
- package/dist/tools/email-tools.js +259 -0
- package/dist/tools/email-tools.js.map +1 -0
- package/dist/tools/filesystem-tools.d.ts +77 -0
- package/dist/tools/filesystem-tools.d.ts.map +1 -0
- package/dist/tools/filesystem-tools.js +334 -0
- package/dist/tools/filesystem-tools.js.map +1 -0
- package/dist/tools/find-model-tool.d.ts +25 -0
- package/dist/tools/find-model-tool.d.ts.map +1 -0
- package/dist/tools/find-model-tool.js +236 -0
- package/dist/tools/find-model-tool.js.map +1 -0
- package/dist/tools/finish-graph-tool.d.ts +23 -0
- package/dist/tools/finish-graph-tool.d.ts.map +1 -0
- package/dist/tools/finish-graph-tool.js +46 -0
- package/dist/tools/finish-graph-tool.js.map +1 -0
- package/dist/tools/finish-step-tool.d.ts +16 -0
- package/dist/tools/finish-step-tool.d.ts.map +1 -0
- package/dist/tools/finish-step-tool.js +68 -0
- package/dist/tools/finish-step-tool.js.map +1 -0
- package/dist/tools/google-tools.d.ts +44 -0
- package/dist/tools/google-tools.d.ts.map +1 -0
- package/dist/tools/google-tools.js +176 -0
- package/dist/tools/google-tools.js.map +1 -0
- package/dist/tools/http-tools.d.ts +56 -0
- package/dist/tools/http-tools.d.ts.map +1 -0
- package/dist/tools/http-tools.js +180 -0
- package/dist/tools/http-tools.js.map +1 -0
- package/dist/tools/js-code-tool.d.ts +30 -0
- package/dist/tools/js-code-tool.d.ts.map +1 -0
- package/dist/tools/js-code-tool.js +98 -0
- package/dist/tools/js-code-tool.js.map +1 -0
- package/dist/tools/local-get-node-info-tool.d.ts +19 -0
- package/dist/tools/local-get-node-info-tool.d.ts.map +1 -0
- package/dist/tools/local-get-node-info-tool.js +75 -0
- package/dist/tools/local-get-node-info-tool.js.map +1 -0
- package/dist/tools/local-list-nodes-tool.d.ts +19 -0
- package/dist/tools/local-list-nodes-tool.d.ts.map +1 -0
- package/dist/tools/local-list-nodes-tool.js +69 -0
- package/dist/tools/local-list-nodes-tool.js.map +1 -0
- package/dist/tools/local-search-nodes-tool.d.ts +21 -0
- package/dist/tools/local-search-nodes-tool.d.ts.map +1 -0
- package/dist/tools/local-search-nodes-tool.js +111 -0
- package/dist/tools/local-search-nodes-tool.js.map +1 -0
- package/dist/tools/math-tools.d.ts +156 -0
- package/dist/tools/math-tools.d.ts.map +1 -0
- package/dist/tools/math-tools.js +422 -0
- package/dist/tools/math-tools.js.map +1 -0
- package/dist/tools/mcp-tools.d.ts +413 -0
- package/dist/tools/mcp-tools.d.ts.map +1 -0
- package/dist/tools/mcp-tools.js +618 -0
- package/dist/tools/mcp-tools.js.map +1 -0
- package/dist/tools/media-tools.d.ts +277 -0
- package/dist/tools/media-tools.d.ts.map +1 -0
- package/dist/tools/media-tools.js +682 -0
- package/dist/tools/media-tools.js.map +1 -0
- package/dist/tools/model-tools.d.ts +25 -0
- package/dist/tools/model-tools.d.ts.map +1 -0
- package/dist/tools/model-tools.js +48 -0
- package/dist/tools/model-tools.js.map +1 -0
- package/dist/tools/openai-tools.d.ts +73 -0
- package/dist/tools/openai-tools.d.ts.map +1 -0
- package/dist/tools/openai-tools.js +193 -0
- package/dist/tools/openai-tools.js.map +1 -0
- package/dist/tools/pdf-tools.d.ts +152 -0
- package/dist/tools/pdf-tools.d.ts.map +1 -0
- package/dist/tools/pdf-tools.js +339 -0
- package/dist/tools/pdf-tools.js.map +1 -0
- package/dist/tools/plan-builder-tools.d.ts +72 -0
- package/dist/tools/plan-builder-tools.d.ts.map +1 -0
- package/dist/tools/plan-builder-tools.js +438 -0
- package/dist/tools/plan-builder-tools.js.map +1 -0
- package/dist/tools/search-tools.d.ts +39 -0
- package/dist/tools/search-tools.d.ts.map +1 -0
- package/dist/tools/search-tools.js +218 -0
- package/dist/tools/search-tools.js.map +1 -0
- package/dist/tools/serp-providers/dataforseo-provider.d.ts +14 -0
- package/dist/tools/serp-providers/dataforseo-provider.d.ts.map +1 -0
- package/dist/tools/serp-providers/dataforseo-provider.js +114 -0
- package/dist/tools/serp-providers/dataforseo-provider.js.map +1 -0
- package/dist/tools/serp-providers/index.d.ts +35 -0
- package/dist/tools/serp-providers/index.d.ts.map +1 -0
- package/dist/tools/serp-providers/index.js +14 -0
- package/dist/tools/serp-providers/index.js.map +1 -0
- package/dist/tools/serp-providers/serpapi-provider.d.ts +18 -0
- package/dist/tools/serp-providers/serpapi-provider.d.ts.map +1 -0
- package/dist/tools/serp-providers/serpapi-provider.js +66 -0
- package/dist/tools/serp-providers/serpapi-provider.js.map +1 -0
- package/dist/tools/tool-registry.d.ts +9 -0
- package/dist/tools/tool-registry.d.ts.map +1 -0
- package/dist/tools/tool-registry.js +17 -0
- package/dist/tools/tool-registry.js.map +1 -0
- package/dist/tools/vector-tools.d.ts +224 -0
- package/dist/tools/vector-tools.d.ts.map +1 -0
- package/dist/tools/vector-tools.js +582 -0
- package/dist/tools/vector-tools.js.map +1 -0
- package/dist/tools/workspace-tools.d.ts +58 -0
- package/dist/tools/workspace-tools.d.ts.map +1 -0
- package/dist/tools/workspace-tools.js +152 -0
- package/dist/tools/workspace-tools.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/json-parser.d.ts +15 -0
- package/dist/utils/json-parser.d.ts.map +1 -0
- package/dist/utils/json-parser.js +78 -0
- package/dist/utils/json-parser.js.map +1 -0
- package/dist/utils/remove-base64-images.d.ts +12 -0
- package/dist/utils/remove-base64-images.d.ts.map +1 -0
- package/dist/utils/remove-base64-images.js +20 -0
- package/dist/utils/remove-base64-images.js.map +1 -0
- package/dist/utils/wrap-generators-parallel.d.ts +11 -0
- package/dist/utils/wrap-generators-parallel.d.ts.map +1 -0
- package/dist/utils/wrap-generators-parallel.js +38 -0
- package/dist/utils/wrap-generators-parallel.js.map +1 -0
- 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
|