@fieldwangai/agentflow 0.1.153 → 0.1.156

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 (193) hide show
  1. package/README.md +16 -34
  2. package/README.zh-CN.md +14 -34
  3. package/agents/agentflow-node-executor-code.md +13 -3
  4. package/agents/agentflow-node-executor-planning.md +13 -3
  5. package/agents/agentflow-node-executor-requirement.md +13 -3
  6. package/agents/agentflow-node-executor-test.md +13 -3
  7. package/agents/agentflow-node-executor-ui.md +13 -3
  8. package/agents/agentflow-node-executor.md +13 -3
  9. package/agents/en/agentflow-node-executor.md +13 -3
  10. package/agents/zh/agentflow-node-executor.md +13 -3
  11. package/bin/agentflow.mjs +3 -3
  12. package/bin/lib/admin-builtin-pipelines.mjs +3 -2
  13. package/bin/lib/agent-runners.mjs +0 -929
  14. package/bin/lib/auth.mjs +2 -1
  15. package/bin/lib/catalog-agents.mjs +2 -1
  16. package/bin/lib/catalog-flows.mjs +165 -90
  17. package/bin/lib/composer-agent.mjs +3 -793
  18. package/bin/lib/composer-skill-router.mjs +3 -323
  19. package/bin/lib/exec-buffered.mjs +32 -0
  20. package/bin/lib/flow-dsl/cli.mjs +277 -0
  21. package/bin/lib/flow-dsl/codegen.mjs +358 -0
  22. package/bin/lib/flow-dsl/defs.mjs +103 -0
  23. package/bin/lib/flow-dsl/index.mjs +77 -0
  24. package/bin/lib/flow-dsl/ir.mjs +305 -0
  25. package/bin/lib/flow-dsl/layout.mjs +115 -0
  26. package/bin/lib/flow-dsl/legacy-yaml.mjs +303 -0
  27. package/bin/lib/flow-dsl/lint.mjs +241 -0
  28. package/bin/lib/flow-dsl/packages.mjs +251 -0
  29. package/bin/lib/flow-dsl/parser.mjs +444 -0
  30. package/bin/lib/flow-import.mjs +96 -26
  31. package/bin/lib/flow-write.mjs +48 -12
  32. package/bin/lib/help.mjs +8 -28
  33. package/bin/lib/html-escape.mjs +19 -0
  34. package/bin/lib/http-util.mjs +49 -0
  35. package/bin/lib/legacy-flow-execution.mjs +37 -0
  36. package/bin/lib/locales/en.json +1 -1
  37. package/bin/lib/locales/zh.json +1 -1
  38. package/bin/lib/main.mjs +152 -286
  39. package/bin/lib/marketplace.mjs +206 -61
  40. package/bin/lib/model-config.mjs +10 -0
  41. package/bin/lib/node-package-archive.mjs +240 -0
  42. package/bin/lib/node-package-bootstrap.mjs +72 -0
  43. package/bin/lib/node-package-manifest.mjs +174 -0
  44. package/bin/lib/paths.mjs +42 -19
  45. package/bin/lib/prd-workflow-routes.mjs +2949 -0
  46. package/bin/lib/prd-workflow-server.mjs +4884 -0
  47. package/bin/lib/run-events.mjs +0 -66
  48. package/bin/lib/schedule-config.mjs +4 -4
  49. package/bin/lib/skill-runtime.mjs +35 -0
  50. package/bin/lib/startup-storage-migrations.mjs +274 -0
  51. package/bin/lib/ui-server.mjs +1092 -18123
  52. package/bin/lib/user-env.mjs +16 -0
  53. package/bin/lib/workspace-auto-layout.mjs +234 -0
  54. package/bin/lib/workspace-flow-store.mjs +478 -0
  55. package/bin/lib/workspace-graph-merge.mjs +41 -34
  56. package/bin/lib/workspace-preview.mjs +12 -2
  57. package/bin/lib/workspace-routes.mjs +3088 -0
  58. package/bin/lib/workspace-server.mjs +6199 -0
  59. package/bin/lib/workspace-state.mjs +331 -0
  60. package/bin/lib/workspace.mjs +2 -1
  61. package/builtin/nodes/agent_subAgent.md +1 -0
  62. package/builtin/nodes/control_agent_toBool.md +2 -0
  63. package/builtin/nodes/control_anyOne.md +2 -0
  64. package/builtin/nodes/control_cancelled.md +2 -0
  65. package/builtin/nodes/control_cd_workspace.md +5 -11
  66. package/builtin/nodes/control_delay.md +2 -0
  67. package/builtin/nodes/control_end.md +2 -0
  68. package/builtin/nodes/control_if.md +1 -0
  69. package/builtin/nodes/control_interval_loop.md +2 -0
  70. package/builtin/nodes/control_load_mcp.md +24 -0
  71. package/builtin/nodes/control_load_skills.md +8 -23
  72. package/builtin/nodes/control_start.md +2 -0
  73. package/builtin/nodes/control_toBool.md +2 -0
  74. package/builtin/nodes/control_user_workspace.md +2 -0
  75. package/builtin/nodes/control_wait_until.md +2 -0
  76. package/builtin/nodes/display_ascii.md +1 -0
  77. package/builtin/nodes/display_chart.md +1 -0
  78. package/builtin/nodes/display_html.md +1 -0
  79. package/builtin/nodes/display_image.md +1 -0
  80. package/builtin/nodes/display_markdown.md +1 -0
  81. package/builtin/nodes/display_mermaid.md +1 -0
  82. package/builtin/nodes/display_react_app.md +1 -0
  83. package/builtin/nodes/display_table.md +1 -0
  84. package/builtin/nodes/provide_bool.md +1 -0
  85. package/builtin/nodes/provide_file.md +1 -0
  86. package/builtin/nodes/provide_password.md +1 -0
  87. package/builtin/nodes/provide_str.md +1 -0
  88. package/builtin/nodes/tool_display_share_link.md +1 -0
  89. package/builtin/nodes/tool_get_env.md +2 -0
  90. package/builtin/nodes/tool_git_checkout.md +1 -0
  91. package/builtin/nodes/tool_git_worktree_load.md +1 -0
  92. package/builtin/nodes/tool_git_worktree_unload.md +1 -0
  93. package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
  94. package/builtin/nodes/tool_jenkins_build.md +2 -0
  95. package/builtin/nodes/tool_load_key.md +2 -0
  96. package/builtin/nodes/tool_nodejs.md +17 -19
  97. package/builtin/nodes/tool_print.md +2 -0
  98. package/builtin/nodes/tool_save_key.md +2 -0
  99. package/builtin/nodes/tool_set_run_env.md +1 -0
  100. package/builtin/nodes/tool_user_ask.md +2 -0
  101. package/builtin/nodes/tool_user_check.md +2 -0
  102. package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
  103. package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
  104. package/builtin/nodes/workspace_one_click_task.md +44 -0
  105. package/builtin/nodes/workspace_run.md +16 -0
  106. package/builtin/nodes/workspace_scheduled_run.md +16 -0
  107. package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
  108. package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
  109. package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
  110. package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
  111. package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
  112. package/builtin/pipelines/new/workspace.flow.js +91 -0
  113. package/builtin/pipelines/new/workspace.layout.json +70 -0
  114. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-ClNxY2Wu.js} +1 -1
  115. package/builtin/web-ui/dist/assets/index-BJzMYRK3.js +870 -0
  116. package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
  117. package/builtin/web-ui/dist/index.html +2 -2
  118. package/package.json +3 -1
  119. package/reference/flow-control-capabilities.md +77 -158
  120. package/reference/flow-layout.md +1 -1
  121. package/reference/flow-prompt-handler-check.md +2 -2
  122. package/skills/agentflow-author-flow/SKILL.md +1 -1
  123. package/skills/agentflow-cli/SKILL.md +139 -24
  124. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  125. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
  126. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
  127. package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
  128. package/skills/agentflow-flow-dsl/SKILL.md +206 -0
  129. package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
  130. package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
  131. package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
  132. package/skills/agentflow-flow-recipes/SKILL.md +7 -4
  133. package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
  134. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  135. package/skills/agentflow-node-dsl/SKILL.md +212 -0
  136. package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
  137. package/skills/agentflow-node-reference/SKILL.md +2 -2
  138. package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
  139. package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
  140. package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
  141. package/skills/agentflow-workspace-graph/SKILL.md +62 -48
  142. package/skills/agentflow-workspace-html/SKILL.md +7 -2
  143. package/skills/agentflow-workspace-image/SKILL.md +6 -2
  144. package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
  145. package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
  146. package/bin/lib/api-runner.mjs +0 -387
  147. package/bin/lib/apply.mjs +0 -903
  148. package/bin/lib/composer-flow-instances.mjs +0 -68
  149. package/bin/lib/composer-flow-skeleton.mjs +0 -334
  150. package/bin/lib/composer-flow-validate.mjs +0 -47
  151. package/bin/lib/composer-model-router.mjs +0 -185
  152. package/bin/lib/composer-node-schema.mjs +0 -303
  153. package/bin/lib/composer-planner.mjs +0 -751
  154. package/bin/lib/composer-script-ops.mjs +0 -233
  155. package/bin/lib/flow-static-preview.mjs +0 -104
  156. package/bin/lib/hub-login.mjs +0 -54
  157. package/bin/lib/hub-publish.mjs +0 -159
  158. package/bin/lib/hub-remote.mjs +0 -189
  159. package/bin/lib/hub.mjs +0 -299
  160. package/bin/lib/jenkins.mjs +0 -380
  161. package/bin/lib/node-execute.mjs +0 -539
  162. package/bin/lib/normalize-node-tool-command.mjs +0 -97
  163. package/bin/lib/runtime-context.mjs +0 -243
  164. package/bin/lib/scheduler.mjs +0 -601
  165. package/bin/lib/ui-print.mjs +0 -94
  166. package/bin/pipeline/build-node-prompt.mjs +0 -271
  167. package/bin/pipeline/check-cache.mjs +0 -191
  168. package/bin/pipeline/check-flow.mjs +0 -543
  169. package/bin/pipeline/collect-nodes.mjs +0 -212
  170. package/bin/pipeline/compute-cache-md5.mjs +0 -177
  171. package/bin/pipeline/ensure-run-dir.mjs +0 -71
  172. package/bin/pipeline/gc.mjs +0 -129
  173. package/bin/pipeline/get-env.mjs +0 -59
  174. package/bin/pipeline/get-resolved-values.mjs +0 -344
  175. package/bin/pipeline/load-key.mjs +0 -62
  176. package/bin/pipeline/parse-flow.mjs +0 -708
  177. package/bin/pipeline/post-process-control-if.mjs +0 -23
  178. package/bin/pipeline/post-process-node.mjs +0 -490
  179. package/bin/pipeline/pre-process-node.mjs +0 -1430
  180. package/bin/pipeline/resolve-inputs.mjs +0 -201
  181. package/bin/pipeline/run-tool-nodejs.mjs +0 -167
  182. package/bin/pipeline/save-key.mjs +0 -93
  183. package/bin/pipeline/snapshot-prior-round.mjs +0 -70
  184. package/bin/pipeline/validate-for-ui.mjs +0 -234
  185. package/bin/pipeline/validate-script-output.mjs +0 -130
  186. package/bin/pipeline/write-result.mjs +0 -182
  187. package/builtin/pipelines/module-migrate/flow.yaml +0 -819
  188. package/builtin/pipelines/new/flow.yaml +0 -545
  189. package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
  190. package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
  191. package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
  192. package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
  193. package/skills/agentflow-node-authoring/SKILL.md +0 -57
@@ -1,387 +0,0 @@
1
- /**
2
- * 直调 AI API 运行器,绕过 Cursor/OpenCode CLI 冷启动开销。
3
- *
4
- * 支持的模型格式(在 flow.yaml 或模型配置中指定):
5
- * api:openai/<model> — OpenAI 或兼容端点(Together/Groq/DeepSeek/Azure 等)
6
- * api:anthropic/<model> — Anthropic Claude
7
- *
8
- * 相关环境变量:
9
- * OPENAI_API_KEY — OpenAI 或兼容端点的 API key
10
- * OPENAI_BASE_URL — 兼容端点基础 URL(默认 https://api.openai.com/v1)
11
- * ANTHROPIC_API_KEY — Anthropic API key
12
- * AGENTFLOW_API_MAX_ROUNDS — 工具调用最大轮数(默认 30)
13
- * AGENTFLOW_API_MAX_TOKENS — 单次响应最大 token 数(默认 8192)
14
- */
15
-
16
- import fs from "fs";
17
- import path from "path";
18
- import { spawnSync } from "child_process";
19
-
20
- import { loadAgentPromptWithReplacements, stripYamlFrontmatter } from "./agents-path.mjs";
21
- import { appendRunLogLine } from "./run-events.mjs";
22
- import { outputNodeBasename } from "../pipeline/get-exec-id.mjs";
23
-
24
- const DEFAULT_OPENAI_BASE = "https://api.openai.com/v1";
25
- const MAX_TOOL_ROUNDS = parseInt(process.env.AGENTFLOW_API_MAX_ROUNDS ?? "30", 10) || 30;
26
- const MAX_TOKENS = parseInt(process.env.AGENTFLOW_API_MAX_TOKENS ?? "8192", 10) || 8192;
27
-
28
- function writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, text) {
29
- const body = String(text ?? "").trim();
30
- if (!body || !absResultPath || !absRunDir) return;
31
- fs.mkdirSync(path.dirname(absResultPath), { recursive: true });
32
- fs.writeFileSync(absResultPath, body + "\n", "utf-8");
33
- if (!instanceId) return;
34
- const slotPath = path.join(absRunDir, "output", instanceId, outputNodeBasename(instanceId, 1, "result"));
35
- fs.mkdirSync(path.dirname(slotPath), { recursive: true });
36
- fs.writeFileSync(slotPath, body + "\n", "utf-8");
37
- }
38
-
39
- // ─── 工具定义 ────────────────────────────────────────────────────────────────
40
-
41
- const TOOL_DEFS = [
42
- {
43
- name: "read_file",
44
- description: "Read the contents of a file. Returns the file content as text.",
45
- parameters: {
46
- type: "object",
47
- properties: {
48
- path: { type: "string", description: "File path (absolute, or relative to workspace root)" },
49
- },
50
- required: ["path"],
51
- },
52
- },
53
- {
54
- name: "write_file",
55
- description: "Write content to a file, creating parent directories as needed. Overwrites existing content.",
56
- parameters: {
57
- type: "object",
58
- properties: {
59
- path: { type: "string", description: "File path (absolute, or relative to workspace root)" },
60
- content: { type: "string", description: "Full content to write" },
61
- },
62
- required: ["path", "content"],
63
- },
64
- },
65
- {
66
- name: "run_command",
67
- description: "Run a shell command in the workspace root directory. Returns stdout, stderr and exit code.",
68
- parameters: {
69
- type: "object",
70
- properties: {
71
- command: { type: "string", description: "Shell command to execute" },
72
- timeout_ms: { type: "number", description: "Timeout in milliseconds (default: 30000)" },
73
- },
74
- required: ["command"],
75
- },
76
- },
77
- {
78
- name: "list_dir",
79
- description: "List files and subdirectories at a path.",
80
- parameters: {
81
- type: "object",
82
- properties: {
83
- path: { type: "string", description: "Directory path (absolute, or relative to workspace root)" },
84
- },
85
- required: ["path"],
86
- },
87
- },
88
- ];
89
-
90
- /** OpenAI format: tools array */
91
- function openAiTools() {
92
- return TOOL_DEFS.map((t) => ({
93
- type: "function",
94
- function: { name: t.name, description: t.description, parameters: t.parameters },
95
- }));
96
- }
97
-
98
- /** Anthropic format: tools array */
99
- function anthropicTools() {
100
- return TOOL_DEFS.map((t) => ({
101
- name: t.name,
102
- description: t.description,
103
- input_schema: t.parameters,
104
- }));
105
- }
106
-
107
- // ─── 工具执行 ────────────────────────────────────────────────────────────────
108
-
109
- function toAbs(workspaceRoot, p) {
110
- return path.isAbsolute(p) ? p : path.resolve(workspaceRoot, p);
111
- }
112
-
113
- function executeTool(workspaceRoot, toolName, input) {
114
- try {
115
- switch (toolName) {
116
- case "read_file": {
117
- const abs = toAbs(workspaceRoot, input.path);
118
- if (!fs.existsSync(abs)) return { error: `File not found: ${input.path}` };
119
- const content = fs.readFileSync(abs, "utf-8");
120
- return { content };
121
- }
122
-
123
- case "write_file": {
124
- const abs = toAbs(workspaceRoot, input.path);
125
- fs.mkdirSync(path.dirname(abs), { recursive: true });
126
- fs.writeFileSync(abs, input.content, "utf-8");
127
- return { ok: true, bytes_written: Buffer.byteLength(input.content, "utf-8") };
128
- }
129
-
130
- case "run_command": {
131
- const timeoutMs = typeof input.timeout_ms === "number" ? input.timeout_ms : 30000;
132
- const r = spawnSync(input.command, [], {
133
- cwd: workspaceRoot,
134
- shell: true,
135
- encoding: "utf-8",
136
- timeout: timeoutMs,
137
- });
138
- return {
139
- exit_code: r.status ?? -1,
140
- stdout: (r.stdout ?? "").slice(0, 20000),
141
- stderr: (r.stderr ?? "").slice(0, 5000),
142
- timed_out: r.signal === "SIGTERM",
143
- };
144
- }
145
-
146
- case "list_dir": {
147
- const abs = toAbs(workspaceRoot, input.path);
148
- if (!fs.existsSync(abs)) return { error: `Path not found: ${input.path}` };
149
- const entries = fs.readdirSync(abs, { withFileTypes: true });
150
- return {
151
- items: entries.map((e) => ({ name: e.name, type: e.isDirectory() ? "dir" : "file" })),
152
- };
153
- }
154
-
155
- default:
156
- return { error: `Unknown tool: ${toolName}` };
157
- }
158
- } catch (err) {
159
- return { error: err.message };
160
- }
161
- }
162
-
163
- // ─── API 调用封装 ─────────────────────────────────────────────────────────────
164
-
165
- async function fetchOpenAi(apiKey, baseUrl, model, messages) {
166
- const url = `${baseUrl.replace(/\/$/, "")}/chat/completions`;
167
- const resp = await fetch(url, {
168
- method: "POST",
169
- headers: {
170
- "Content-Type": "application/json",
171
- Authorization: `Bearer ${apiKey}`,
172
- },
173
- body: JSON.stringify({
174
- model,
175
- messages,
176
- tools: openAiTools(),
177
- tool_choice: "auto",
178
- max_tokens: MAX_TOKENS,
179
- }),
180
- });
181
- if (!resp.ok) {
182
- const txt = await resp.text().catch(() => "");
183
- throw new Error(`OpenAI API ${resp.status}: ${txt.slice(0, 600)}`);
184
- }
185
- return resp.json();
186
- }
187
-
188
- async function fetchAnthropic(apiKey, model, messages, systemPrompt) {
189
- const resp = await fetch("https://api.anthropic.com/v1/messages", {
190
- method: "POST",
191
- headers: {
192
- "Content-Type": "application/json",
193
- "x-api-key": apiKey,
194
- "anthropic-version": "2023-06-01",
195
- },
196
- body: JSON.stringify({
197
- model,
198
- system: systemPrompt,
199
- messages,
200
- tools: anthropicTools(),
201
- max_tokens: MAX_TOKENS,
202
- }),
203
- });
204
- if (!resp.ok) {
205
- const txt = await resp.text().catch(() => "");
206
- throw new Error(`Anthropic API ${resp.status}: ${txt.slice(0, 600)}`);
207
- }
208
- return resp.json();
209
- }
210
-
211
- // ─── 工具调用循环 ─────────────────────────────────────────────────────────────
212
-
213
- async function runOpenAiLoop(apiKey, baseUrl, model, systemPrompt, userContent, workspaceRoot, log, options) {
214
- const messages = [
215
- ...(systemPrompt ? [{ role: "system", content: systemPrompt }] : []),
216
- { role: "user", content: userContent },
217
- ];
218
- let finalText = "";
219
-
220
- for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
221
- log(`[api/openai] round ${round + 1}`);
222
- const resp = await fetchOpenAi(apiKey, baseUrl, model, messages);
223
- const choice = resp.choices?.[0];
224
- if (!choice) throw new Error("OpenAI: no choices in response");
225
-
226
- const msg = choice.message;
227
- messages.push(msg);
228
-
229
- // 向外广播助手文本(用于 spinner 显示)
230
- if (options.onToolCall) {
231
- const txt = typeof msg.content === "string" ? msg.content : "";
232
- if (txt.trim()) options.onToolCall("assistant", txt.slice(0, 200));
233
- }
234
- if (typeof msg.content === "string" && msg.content.trim()) finalText = msg.content;
235
-
236
- if (choice.finish_reason === "stop" || choice.finish_reason === "end_turn" || !msg.tool_calls?.length) {
237
- log(`[api/openai] finished (${choice.finish_reason ?? "no-tool-calls"})`);
238
- break;
239
- }
240
-
241
- const toolResults = [];
242
- for (const tc of msg.tool_calls) {
243
- const toolName = tc.function.name;
244
- let toolInput = {};
245
- try { toolInput = JSON.parse(tc.function.arguments); } catch (_) { /**/ }
246
-
247
- log(`[api/openai] tool: ${toolName} ${JSON.stringify(toolInput).slice(0, 120)}`);
248
- if (options.onToolCall) options.onToolCall("tool_call", toolName);
249
-
250
- const result = executeTool(workspaceRoot, toolName, toolInput);
251
- log(`[api/openai] tool result: ${JSON.stringify(result).slice(0, 200)}`);
252
-
253
- toolResults.push({ role: "tool", tool_call_id: tc.id, content: JSON.stringify(result) });
254
- }
255
- messages.push(...toolResults);
256
- }
257
- return finalText;
258
- }
259
-
260
- async function runAnthropicLoop(apiKey, model, systemPrompt, userContent, workspaceRoot, log, options) {
261
- const messages = [{ role: "user", content: userContent }];
262
- let finalText = "";
263
-
264
- for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
265
- log(`[api/anthropic] round ${round + 1}`);
266
- const resp = await fetchAnthropic(apiKey, model, messages, systemPrompt);
267
-
268
- // 助手消息
269
- messages.push({ role: "assistant", content: resp.content });
270
-
271
- if (options.onToolCall) {
272
- const textBlock = resp.content?.find((b) => b.type === "text");
273
- if (textBlock?.text) options.onToolCall("assistant", textBlock.text.slice(0, 200));
274
- }
275
- const textBlock = resp.content?.find((b) => b.type === "text");
276
- if (textBlock?.text?.trim()) finalText = textBlock.text;
277
-
278
- if (resp.stop_reason === "end_turn" || resp.stop_reason === "stop_sequence") {
279
- log(`[api/anthropic] finished (${resp.stop_reason})`);
280
- break;
281
- }
282
-
283
- const toolUses = (resp.content ?? []).filter((b) => b.type === "tool_use");
284
- if (!toolUses.length) {
285
- log("[api/anthropic] finished (no tool_use blocks)");
286
- break;
287
- }
288
-
289
- const toolResults = [];
290
- for (const tb of toolUses) {
291
- log(`[api/anthropic] tool: ${tb.name} ${JSON.stringify(tb.input).slice(0, 120)}`);
292
- if (options.onToolCall) options.onToolCall("tool_call", tb.name);
293
-
294
- const result = executeTool(workspaceRoot, tb.name, tb.input ?? {});
295
- log(`[api/anthropic] tool result: ${JSON.stringify(result).slice(0, 200)}`);
296
-
297
- toolResults.push({ type: "tool_result", tool_use_id: tb.id, content: JSON.stringify(result) });
298
- }
299
- messages.push({ role: "user", content: toolResults });
300
- }
301
- return finalText;
302
- }
303
-
304
- // ─── 公共解析函数 ─────────────────────────────────────────────────────────────
305
-
306
- /**
307
- * 从模型字符串中解析 provider 和 model。
308
- * 例:
309
- * "api:openai/gpt-4o" → { provider: "openai", model: "gpt-4o" }
310
- * "api:anthropic/claude-opus-4-5" → { provider: "anthropic", model: "claude-opus-4-5" }
311
- * "api:gpt-4o" → { provider: "openai", model: "gpt-4o" } (默认 openai)
312
- */
313
- export function parseApiModel(str) {
314
- const s = String(str ?? "").replace(/^api:/, "");
315
- const slash = s.indexOf("/");
316
- if (slash < 0) return { provider: "openai", model: s };
317
- const provider = s.slice(0, slash).toLowerCase();
318
- const model = s.slice(slash + 1);
319
- return { provider, model };
320
- }
321
-
322
- // ─── 主入口 ───────────────────────────────────────────────────────────────────
323
-
324
- /**
325
- * 直调 AI API 执行节点,绕过 Cursor/OpenCode CLI。
326
- *
327
- * @param {string} workspaceRoot
328
- * @param {{ promptPath: string, nodeContext?: string, taskBody?: string, subagent?: string, instanceId?: string }} nodeInfo
329
- * @param {object} options
330
- * model — "api:openai/gpt-4o" | "api:anthropic/claude-opus-4-5" 等
331
- * flowName — 用于日志
332
- * uuid — 用于日志
333
- * onToolCall — (subtype: string, name: string) => void 供 spinner 展示
334
- */
335
- export async function runApiAgentForNode(workspaceRoot, { promptPath, nodeContext, taskBody, intermediatePath, resultPathRel, subagent, instanceId }, options = {}) {
336
- const absRoot = path.resolve(workspaceRoot);
337
- const execRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
338
- const absRunDir = intermediatePath ? path.resolve(workspaceRoot, intermediatePath) : "";
339
- const absResultPath = absRunDir && resultPathRel ? path.join(absRunDir, resultPathRel) : "";
340
- const flowName = options.flowName ?? null;
341
- const uuid = options.uuid ?? null;
342
-
343
- const log = (msg) => {
344
- if (flowName && uuid) appendRunLogLine(absRoot, flowName, uuid, "api-runner", msg);
345
- };
346
-
347
- // ── 解析 provider / model ──────────────────────────────────────────────────
348
- const modelRaw = String(options.model ?? "api:openai/gpt-4o");
349
- const { provider, model } = parseApiModel(modelRaw);
350
- log(`start provider=${provider} model=${model} instanceId=${instanceId ?? "-"}`);
351
-
352
- // ── 读取 Agent 角色提示,注入 nodeContext/taskBody ────────────────────────
353
- const replacements = {
354
- workspaceRoot: execRoot,
355
- executionWorkspaceRoot: execRoot,
356
- pipelineWorkspace: absRoot,
357
- nodeContext: nodeContext ?? "",
358
- taskBody: taskBody ?? "",
359
- flowName: flowName ?? "",
360
- uuid: uuid ?? "",
361
- instanceId: instanceId ?? "",
362
- };
363
- const agentRaw = loadAgentPromptWithReplacements(workspaceRoot, subagent ?? "agentflow-node-executor", replacements);
364
- const renderedBody = stripYamlFrontmatter(agentRaw);
365
-
366
- // ── system/user 拆分:## 节点上下文 之前为角色指令,之后为任务内容 ─────────
367
- const splitMarker = "## 节点上下文";
368
- const splitIdx = renderedBody.indexOf(splitMarker);
369
- const systemPrompt = splitIdx > 0 ? renderedBody.slice(0, splitIdx).trim() : renderedBody;
370
- const userContent = `## 节点上下文\n\n${nodeContext ?? ""}\n\n## 执行任务\n\n${taskBody ?? ""}`;
371
-
372
- // ── 确认 API Key ───────────────────────────────────────────────────────────
373
- if (provider === "anthropic") {
374
- const key = process.env.ANTHROPIC_API_KEY;
375
- if (!key) throw new Error("[api-runner] ANTHROPIC_API_KEY is required for api:anthropic/* models");
376
- const finalText = await runAnthropicLoop(key, model, systemPrompt, userContent, execRoot, log, options);
377
- writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, finalText);
378
- } else {
379
- const key = process.env.OPENAI_API_KEY;
380
- if (!key) throw new Error("[api-runner] OPENAI_API_KEY is required for api:openai/* models");
381
- const baseUrl = (process.env.OPENAI_BASE_URL ?? DEFAULT_OPENAI_BASE).trim();
382
- const finalText = await runOpenAiLoop(key, baseUrl, model, systemPrompt, userContent, execRoot, log, options);
383
- writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, finalText);
384
- }
385
-
386
- log(`done instanceId=${instanceId ?? "-"}`);
387
- }