@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.
- package/README.md +16 -34
- package/README.zh-CN.md +14 -34
- package/agents/agentflow-node-executor-code.md +13 -3
- package/agents/agentflow-node-executor-planning.md +13 -3
- package/agents/agentflow-node-executor-requirement.md +13 -3
- package/agents/agentflow-node-executor-test.md +13 -3
- package/agents/agentflow-node-executor-ui.md +13 -3
- package/agents/agentflow-node-executor.md +13 -3
- package/agents/en/agentflow-node-executor.md +13 -3
- package/agents/zh/agentflow-node-executor.md +13 -3
- package/bin/agentflow.mjs +3 -3
- package/bin/lib/admin-builtin-pipelines.mjs +3 -2
- package/bin/lib/agent-runners.mjs +0 -929
- package/bin/lib/auth.mjs +2 -1
- package/bin/lib/catalog-agents.mjs +2 -1
- package/bin/lib/catalog-flows.mjs +165 -90
- package/bin/lib/composer-agent.mjs +3 -793
- package/bin/lib/composer-skill-router.mjs +3 -323
- package/bin/lib/exec-buffered.mjs +32 -0
- package/bin/lib/flow-dsl/cli.mjs +277 -0
- package/bin/lib/flow-dsl/codegen.mjs +358 -0
- package/bin/lib/flow-dsl/defs.mjs +103 -0
- package/bin/lib/flow-dsl/index.mjs +77 -0
- package/bin/lib/flow-dsl/ir.mjs +305 -0
- package/bin/lib/flow-dsl/layout.mjs +115 -0
- package/bin/lib/flow-dsl/legacy-yaml.mjs +303 -0
- package/bin/lib/flow-dsl/lint.mjs +241 -0
- package/bin/lib/flow-dsl/packages.mjs +251 -0
- package/bin/lib/flow-dsl/parser.mjs +444 -0
- package/bin/lib/flow-import.mjs +96 -26
- package/bin/lib/flow-write.mjs +48 -12
- package/bin/lib/help.mjs +8 -28
- package/bin/lib/html-escape.mjs +19 -0
- package/bin/lib/http-util.mjs +49 -0
- package/bin/lib/legacy-flow-execution.mjs +37 -0
- package/bin/lib/locales/en.json +1 -1
- package/bin/lib/locales/zh.json +1 -1
- package/bin/lib/main.mjs +152 -286
- package/bin/lib/marketplace.mjs +206 -61
- package/bin/lib/model-config.mjs +10 -0
- package/bin/lib/node-package-archive.mjs +240 -0
- package/bin/lib/node-package-bootstrap.mjs +72 -0
- package/bin/lib/node-package-manifest.mjs +174 -0
- package/bin/lib/paths.mjs +42 -19
- package/bin/lib/prd-workflow-routes.mjs +2949 -0
- package/bin/lib/prd-workflow-server.mjs +4884 -0
- package/bin/lib/run-events.mjs +0 -66
- package/bin/lib/schedule-config.mjs +4 -4
- package/bin/lib/skill-runtime.mjs +35 -0
- package/bin/lib/startup-storage-migrations.mjs +274 -0
- package/bin/lib/ui-server.mjs +1092 -18123
- package/bin/lib/user-env.mjs +16 -0
- package/bin/lib/workspace-auto-layout.mjs +234 -0
- package/bin/lib/workspace-flow-store.mjs +478 -0
- package/bin/lib/workspace-graph-merge.mjs +41 -34
- package/bin/lib/workspace-preview.mjs +12 -2
- package/bin/lib/workspace-routes.mjs +3088 -0
- package/bin/lib/workspace-server.mjs +6199 -0
- package/bin/lib/workspace-state.mjs +331 -0
- package/bin/lib/workspace.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +1 -0
- package/builtin/nodes/control_agent_toBool.md +2 -0
- package/builtin/nodes/control_anyOne.md +2 -0
- package/builtin/nodes/control_cancelled.md +2 -0
- package/builtin/nodes/control_cd_workspace.md +5 -11
- package/builtin/nodes/control_delay.md +2 -0
- package/builtin/nodes/control_end.md +2 -0
- package/builtin/nodes/control_if.md +1 -0
- package/builtin/nodes/control_interval_loop.md +2 -0
- package/builtin/nodes/control_load_mcp.md +24 -0
- package/builtin/nodes/control_load_skills.md +8 -23
- package/builtin/nodes/control_start.md +2 -0
- package/builtin/nodes/control_toBool.md +2 -0
- package/builtin/nodes/control_user_workspace.md +2 -0
- package/builtin/nodes/control_wait_until.md +2 -0
- package/builtin/nodes/display_ascii.md +1 -0
- package/builtin/nodes/display_chart.md +1 -0
- package/builtin/nodes/display_html.md +1 -0
- package/builtin/nodes/display_image.md +1 -0
- package/builtin/nodes/display_markdown.md +1 -0
- package/builtin/nodes/display_mermaid.md +1 -0
- package/builtin/nodes/display_react_app.md +1 -0
- package/builtin/nodes/display_table.md +1 -0
- package/builtin/nodes/provide_bool.md +1 -0
- package/builtin/nodes/provide_file.md +1 -0
- package/builtin/nodes/provide_password.md +1 -0
- package/builtin/nodes/provide_str.md +1 -0
- package/builtin/nodes/tool_display_share_link.md +1 -0
- package/builtin/nodes/tool_get_env.md +2 -0
- package/builtin/nodes/tool_git_checkout.md +1 -0
- package/builtin/nodes/tool_git_worktree_load.md +1 -0
- package/builtin/nodes/tool_git_worktree_unload.md +1 -0
- package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
- package/builtin/nodes/tool_jenkins_build.md +2 -0
- package/builtin/nodes/tool_load_key.md +2 -0
- package/builtin/nodes/tool_nodejs.md +17 -19
- package/builtin/nodes/tool_print.md +2 -0
- package/builtin/nodes/tool_save_key.md +2 -0
- package/builtin/nodes/tool_set_run_env.md +1 -0
- package/builtin/nodes/tool_user_ask.md +2 -0
- package/builtin/nodes/tool_user_check.md +2 -0
- package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
- package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
- package/builtin/nodes/workspace_one_click_task.md +44 -0
- package/builtin/nodes/workspace_run.md +16 -0
- package/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
- package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
- package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
- package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
- package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
- package/builtin/pipelines/new/workspace.flow.js +91 -0
- package/builtin/pipelines/new/workspace.layout.json +70 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-ClNxY2Wu.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BJzMYRK3.js +870 -0
- package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +3 -1
- package/reference/flow-control-capabilities.md +77 -158
- package/reference/flow-layout.md +1 -1
- package/reference/flow-prompt-handler-check.md +2 -2
- package/skills/agentflow-author-flow/SKILL.md +1 -1
- package/skills/agentflow-cli/SKILL.md +139 -24
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
- package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
- package/skills/agentflow-flow-dsl/SKILL.md +206 -0
- package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
- package/skills/agentflow-flow-recipes/SKILL.md +7 -4
- package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
- package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
- package/skills/agentflow-node-dsl/SKILL.md +212 -0
- package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-node-reference/SKILL.md +2 -2
- package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
- package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
- package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
- package/skills/agentflow-workspace-graph/SKILL.md +62 -48
- package/skills/agentflow-workspace-html/SKILL.md +7 -2
- package/skills/agentflow-workspace-image/SKILL.md +6 -2
- package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
- package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
- package/bin/lib/api-runner.mjs +0 -387
- package/bin/lib/apply.mjs +0 -903
- package/bin/lib/composer-flow-instances.mjs +0 -68
- package/bin/lib/composer-flow-skeleton.mjs +0 -334
- package/bin/lib/composer-flow-validate.mjs +0 -47
- package/bin/lib/composer-model-router.mjs +0 -185
- package/bin/lib/composer-node-schema.mjs +0 -303
- package/bin/lib/composer-planner.mjs +0 -751
- package/bin/lib/composer-script-ops.mjs +0 -233
- package/bin/lib/flow-static-preview.mjs +0 -104
- package/bin/lib/hub-login.mjs +0 -54
- package/bin/lib/hub-publish.mjs +0 -159
- package/bin/lib/hub-remote.mjs +0 -189
- package/bin/lib/hub.mjs +0 -299
- package/bin/lib/jenkins.mjs +0 -380
- package/bin/lib/node-execute.mjs +0 -539
- package/bin/lib/normalize-node-tool-command.mjs +0 -97
- package/bin/lib/runtime-context.mjs +0 -243
- package/bin/lib/scheduler.mjs +0 -601
- package/bin/lib/ui-print.mjs +0 -94
- package/bin/pipeline/build-node-prompt.mjs +0 -271
- package/bin/pipeline/check-cache.mjs +0 -191
- package/bin/pipeline/check-flow.mjs +0 -543
- package/bin/pipeline/collect-nodes.mjs +0 -212
- package/bin/pipeline/compute-cache-md5.mjs +0 -177
- package/bin/pipeline/ensure-run-dir.mjs +0 -71
- package/bin/pipeline/gc.mjs +0 -129
- package/bin/pipeline/get-env.mjs +0 -59
- package/bin/pipeline/get-resolved-values.mjs +0 -344
- package/bin/pipeline/load-key.mjs +0 -62
- package/bin/pipeline/parse-flow.mjs +0 -708
- package/bin/pipeline/post-process-control-if.mjs +0 -23
- package/bin/pipeline/post-process-node.mjs +0 -490
- package/bin/pipeline/pre-process-node.mjs +0 -1430
- package/bin/pipeline/resolve-inputs.mjs +0 -201
- package/bin/pipeline/run-tool-nodejs.mjs +0 -167
- package/bin/pipeline/save-key.mjs +0 -93
- package/bin/pipeline/snapshot-prior-round.mjs +0 -70
- package/bin/pipeline/validate-for-ui.mjs +0 -234
- package/bin/pipeline/validate-script-output.mjs +0 -130
- package/bin/pipeline/write-result.mjs +0 -182
- package/builtin/pipelines/module-migrate/flow.yaml +0 -819
- package/builtin/pipelines/new/flow.yaml +0 -545
- package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
- package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
- package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
- package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
- package/skills/agentflow-node-authoring/SKILL.md +0 -57
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 从 flow.yaml 解析实例的 role / model,供 Composer 多步规划与路由使用。
|
|
3
|
-
*/
|
|
4
|
-
import yaml from "js-yaml";
|
|
5
|
-
import { translateRole, normalizeRoleToKey } from "./i18n.mjs";
|
|
6
|
-
|
|
7
|
-
const VALID_ROLES = ["requirement", "planning", "code", "test", "normal"];
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} [flowYaml]
|
|
11
|
-
* @returns {Record<string, { role: string, model?: string, label: string, definitionId?: string, hasScript?: boolean }>}
|
|
12
|
-
*/
|
|
13
|
-
export function parseInstanceRoleModelMap(flowYaml) {
|
|
14
|
-
const map = {};
|
|
15
|
-
if (!flowYaml || typeof flowYaml !== "string" || !flowYaml.trim()) return map;
|
|
16
|
-
try {
|
|
17
|
-
const raw = yaml.load(flowYaml);
|
|
18
|
-
const instances = raw?.instances && typeof raw.instances === "object" ? raw.instances : {};
|
|
19
|
-
for (const [id, inst] of Object.entries(instances)) {
|
|
20
|
-
if (!inst || typeof inst !== "object") continue;
|
|
21
|
-
const rawRole = inst.role != null ? String(inst.role).trim() : "";
|
|
22
|
-
const normalizedRole = normalizeRoleToKey(rawRole);
|
|
23
|
-
const role = VALID_ROLES.includes(normalizedRole) ? normalizedRole : "normal";
|
|
24
|
-
let model = inst.model != null ? String(inst.model).trim() : "";
|
|
25
|
-
if (model === "default") model = "";
|
|
26
|
-
const label = inst.label != null ? String(inst.label) : id;
|
|
27
|
-
const definitionId = inst.definitionId != null ? String(inst.definitionId).trim() : undefined;
|
|
28
|
-
const hasScript = definitionId === "tool_nodejs" && inst.script != null && String(inst.script).trim() !== "";
|
|
29
|
-
map[id] = { role, model: model || undefined, label, definitionId, hasScript };
|
|
30
|
-
}
|
|
31
|
-
} catch {
|
|
32
|
-
/* ignore */
|
|
33
|
-
}
|
|
34
|
-
return map;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 供规划器 user 消息附加:明确实例上的角色、模型与节点类型,便于输出带 instanceId / nodeRole 的步骤。
|
|
39
|
-
* @param {string} [flowYaml]
|
|
40
|
-
* @returns {string}
|
|
41
|
-
*/
|
|
42
|
-
export function formatInstancePlannerHint(flowYaml) {
|
|
43
|
-
const map = parseInstanceRoleModelMap(flowYaml);
|
|
44
|
-
const entries = Object.entries(map);
|
|
45
|
-
if (entries.length === 0) return "";
|
|
46
|
-
|
|
47
|
-
const nodejsMissing = [];
|
|
48
|
-
const lines = entries.map(([id, v]) => {
|
|
49
|
-
const m = v.model ? ` · 模型 \`${v.model}\`` : "";
|
|
50
|
-
const defHint = v.definitionId ? ` · \`${v.definitionId}\`` : "";
|
|
51
|
-
const scriptHint = v.definitionId === "tool_nodejs" && !v.hasScript ? " ⚠️ **缺 script**" : "";
|
|
52
|
-
if (v.definitionId === "tool_nodejs" && !v.hasScript) nodejsMissing.push(id);
|
|
53
|
-
return `- \`${id}\`(${v.label}):角色 **${translateRole(v.role)}**${defHint}${m}${scriptHint}`;
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
let warn = "";
|
|
57
|
-
if (nodejsMissing.length > 0) {
|
|
58
|
-
warn = `\n\n⚠️ **以下 tool_nodejs 节点缺少 script 字段**(无 script 则节点无法执行):${nodejsMissing.map(id => `\`${id}\``).join("、")}。` +
|
|
59
|
-
"必须为它们写入可执行的 `script`(shell/node 命令),或改用 `agent_subAgent`。`body` 不会被执行。";
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
"\n## 画布实例(类型 / 角色 / 模型)\n" +
|
|
64
|
-
"agent 步骤请尽量填写 `instanceId`(本步主要操作的实例),`nodeRole` 应与该实例的 role 一致;可选用 `executorModel` 覆盖执行模型(否则使用该实例在 YAML 中的 model,再否则用用户全局模型)。\n" +
|
|
65
|
-
lines.join("\n") +
|
|
66
|
-
warn
|
|
67
|
-
);
|
|
68
|
-
}
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composer 阶段一 skeleton 生成器:在 AI 真正执行前,由脚本写入:
|
|
3
|
-
* 1. flow.yaml — 含 control_start + control_end + 一条主链 edge + nodePositions
|
|
4
|
-
* 2. composer-node-spec.md — 含三段 section 模板(整体框架/节点职责/计划数据槽)
|
|
5
|
-
*
|
|
6
|
-
* 设计原则:
|
|
7
|
-
* - **幂等**:只有当目标文件不存在 / 完全空 / instances 为空时才写入。
|
|
8
|
-
* 如已有内容,**不动**(避免覆盖用户工作)。
|
|
9
|
-
* - **最小骨架**:让 AI 只做"插入节点 + 调整边"而非"从零写 YAML"。
|
|
10
|
-
* - **正确 YAML**:start/end 槽位结构、type、name 严格对齐 schema。
|
|
11
|
-
*/
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
import path from "path";
|
|
14
|
-
import yaml from "js-yaml";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 写入 flow.yaml skeleton(如已有非空 instances 则跳过)。
|
|
18
|
-
* @param {string} flowYamlAbs flow.yaml 绝对路径
|
|
19
|
-
* @returns {{ created: boolean, reason: string }}
|
|
20
|
-
*/
|
|
21
|
-
export function ensureFlowSkeleton(flowYamlAbs) {
|
|
22
|
-
if (!flowYamlAbs) return { created: false, reason: "missing flowYamlAbs" };
|
|
23
|
-
|
|
24
|
-
const exists = fs.existsSync(flowYamlAbs);
|
|
25
|
-
if (exists) {
|
|
26
|
-
try {
|
|
27
|
-
const raw = fs.readFileSync(flowYamlAbs, "utf-8");
|
|
28
|
-
const data = yaml.load(raw);
|
|
29
|
-
const instCount = data && data.instances && typeof data.instances === "object"
|
|
30
|
-
? Object.keys(data.instances).length
|
|
31
|
-
: 0;
|
|
32
|
-
if (instCount > 0) {
|
|
33
|
-
return { created: false, reason: `flow.yaml already has ${instCount} instances` };
|
|
34
|
-
}
|
|
35
|
-
} catch {
|
|
36
|
-
/* 解析失败也覆盖(视为坏文件) */
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
fs.mkdirSync(path.dirname(flowYamlAbs), { recursive: true });
|
|
41
|
-
|
|
42
|
-
const skeleton = {
|
|
43
|
-
instances: {
|
|
44
|
-
control_start: {
|
|
45
|
-
definitionId: "control_start",
|
|
46
|
-
label: "Start",
|
|
47
|
-
input: [],
|
|
48
|
-
output: [
|
|
49
|
-
{ type: "node", name: "next", value: "" },
|
|
50
|
-
],
|
|
51
|
-
body: "流程入口",
|
|
52
|
-
},
|
|
53
|
-
control_end: {
|
|
54
|
-
definitionId: "control_end",
|
|
55
|
-
label: "End",
|
|
56
|
-
input: [
|
|
57
|
-
{ type: "node", name: "prev", value: "" },
|
|
58
|
-
],
|
|
59
|
-
output: [],
|
|
60
|
-
body: "流程出口",
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
edges: [
|
|
64
|
-
{
|
|
65
|
-
source: "control_start",
|
|
66
|
-
target: "control_end",
|
|
67
|
-
sourceHandle: "output-0",
|
|
68
|
-
targetHandle: "input-0",
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
ui: {
|
|
72
|
-
nodePositions: {
|
|
73
|
-
control_start: { x: 100, y: 300 },
|
|
74
|
-
control_end: { x: 380, y: 300 },
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const text = yaml.dump(skeleton, { lineWidth: -1, noRefs: true });
|
|
80
|
-
fs.writeFileSync(flowYamlAbs, text, "utf-8");
|
|
81
|
-
return { created: true, reason: "wrote start+end skeleton" };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* 写入 composer-node-spec.md 模板(如已存在则跳过)。
|
|
86
|
-
* @param {string} specMdAbs
|
|
87
|
-
* @param {{ flowId?: string, userRequest?: string }} [meta]
|
|
88
|
-
* @returns {{ created: boolean, reason: string }}
|
|
89
|
-
*/
|
|
90
|
-
export function ensureNodeSpecSkeleton(specMdAbs, meta = {}) {
|
|
91
|
-
if (!specMdAbs) return { created: false, reason: "missing specMdAbs" };
|
|
92
|
-
|
|
93
|
-
if (fs.existsSync(specMdAbs)) {
|
|
94
|
-
try {
|
|
95
|
-
const raw = fs.readFileSync(specMdAbs, "utf-8").trim();
|
|
96
|
-
if (raw.length > 0) {
|
|
97
|
-
return { created: false, reason: "spec.md already exists" };
|
|
98
|
-
}
|
|
99
|
-
} catch { /* fallthrough */ }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
fs.mkdirSync(path.dirname(specMdAbs), { recursive: true });
|
|
103
|
-
|
|
104
|
-
const flowId = meta.flowId || "(unknown)";
|
|
105
|
-
const userRequest = (meta.userRequest || "").trim() || "(未提供)";
|
|
106
|
-
const ts = new Date().toISOString();
|
|
107
|
-
|
|
108
|
-
const tmpl = `# composer-node-spec — ${flowId}
|
|
109
|
-
|
|
110
|
-
> 生成时间:${ts}
|
|
111
|
-
> 用户原始需求:
|
|
112
|
-
${userRequest.split("\n").map((l) => "> " + l).join("\n")}
|
|
113
|
-
|
|
114
|
-
## 整体框架
|
|
115
|
-
|
|
116
|
-
<!--
|
|
117
|
-
AI 在此填写整体设计意图:
|
|
118
|
-
- 主链节点序列(Start → ... → End)
|
|
119
|
-
- 分支结构(control_if 的真假分支去向)
|
|
120
|
-
- 循环结构(control_anyOne + control_toBool + control_if 的环路)
|
|
121
|
-
- 并行结构(如有)
|
|
122
|
-
- 全局数据(SaveKey / LoadKey 跨节点共享的 key)
|
|
123
|
-
-->
|
|
124
|
-
|
|
125
|
-
## 节点职责
|
|
126
|
-
|
|
127
|
-
<!--
|
|
128
|
-
按 instanceId 分小节,**每节点必须写齐 4 项**:
|
|
129
|
-
**职责**:一句话说该节点做什么
|
|
130
|
-
**输入**:列出每个 input 槽 → 语义 + 来源(上游 instanceId.slot)
|
|
131
|
-
**输出**:列出每个 output 槽 → 语义 + 去向(下游 instanceId.slot)
|
|
132
|
-
**实现要点**:tool_nodejs 写脚本路径(scripts/xxx.mjs)、agent_subAgent 写 body 关键点、control_* 写判定/汇合规则、provide_* 写固定值
|
|
133
|
-
说明:
|
|
134
|
-
- **输入/输出**就是阶段三连线的依据——把每个槽位的「上游来源」和「下游去向」写清,连边 sourceHandle/targetHandle 就一目了然
|
|
135
|
-
- 控制流槽(prev/next)也要写,标「控制流:来自 <upstream>.next」即可
|
|
136
|
-
- 业务数据槽(text/file)必须写清语义,让阶段二节点补充时知道 body/script 该用什么 \${slot} 占位符
|
|
137
|
-
- tool_nodejs 的可执行代码留到阶段二写,本阶段只需脚本路径与 I/O 语义
|
|
138
|
-
-->
|
|
139
|
-
|
|
140
|
-
### control_start
|
|
141
|
-
**职责**:流程入口
|
|
142
|
-
**输入**:—
|
|
143
|
-
**输出**:next:node → 给 <下一个节点>.prev(控制流,启动主链)
|
|
144
|
-
|
|
145
|
-
### control_end
|
|
146
|
-
**职责**:流程出口
|
|
147
|
-
**输入**:prev:node ← 来自 <最后一个节点>.next(控制流)
|
|
148
|
-
**输出**:—
|
|
149
|
-
|
|
150
|
-
<!-- 在此后追加每个新节点的小节 -->
|
|
151
|
-
|
|
152
|
-
## 计划数据槽(仅 ★ 可扩展节点需追加的业务槽)
|
|
153
|
-
|
|
154
|
-
<!--
|
|
155
|
-
本 section 是**完整业务数据槽设计**——是 spec.md 的权威记录与阶段二/三对账依据。
|
|
156
|
-
仅对 ★ 可扩展节点(agent_subAgent / tool_nodejs / tool_user_check)列出需要在 input/output 数组**末尾追加**的业务数据槽。
|
|
157
|
-
固定槽位节点(control_*、provide_*、tool_load_key、tool_save_key、tool_get_env、tool_print、tool_user_ask)槽位结构由 schema 锁死,**不在此列出**。
|
|
158
|
-
|
|
159
|
-
**重要:阶段一 AI 应该同时把这些槽落到 flow.yaml**(鼓励一次写到位)。
|
|
160
|
-
- 已落到 yaml → 阶段二按 name 去重跳过,不影响幂等
|
|
161
|
-
- 未落到 yaml → 阶段二脚本/agent 据此 section 补齐
|
|
162
|
-
|
|
163
|
-
格式(机器可解析):
|
|
164
|
-
<instanceId>:
|
|
165
|
-
input += [<name>:<text|file>, ...]
|
|
166
|
-
output += [<name>:<text|file>, ...]
|
|
167
|
-
type 仅可用 \`text\`(短串/路径/JSON)或 \`file\`(大块产物路径),**禁止 node**。
|
|
168
|
-
命名建议与上下游语义对齐(如下游 \`fromapp\` 的入参,上游就用同名 output)便于连线。
|
|
169
|
-
示例:
|
|
170
|
-
agent_analyze:
|
|
171
|
-
input += [fromapp:text, page_name:text]
|
|
172
|
-
output += [analysis:file]
|
|
173
|
-
agent_convert:
|
|
174
|
-
input += [analysis:file, toapp:text]
|
|
175
|
-
output += [converted_code:file]
|
|
176
|
-
agent_check_compile:
|
|
177
|
-
input += [converted_code:file]
|
|
178
|
-
output += [compile_result:text]
|
|
179
|
-
-->
|
|
180
|
-
`;
|
|
181
|
-
|
|
182
|
-
fs.writeFileSync(specMdAbs, tmpl, "utf-8");
|
|
183
|
-
return { created: true, reason: "wrote spec.md template" };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* 阶段一前置:同时确保 flow.yaml + spec.md 两份骨架。
|
|
188
|
-
* @param {{ flowYamlAbs: string, composerSpecAbs?: string, flowId?: string, userRequest?: string }} opts
|
|
189
|
-
* @returns {{ flow: ReturnType<typeof ensureFlowSkeleton>, spec: ReturnType<typeof ensureNodeSpecSkeleton> }}
|
|
190
|
-
*/
|
|
191
|
-
export function ensurePhase1Skeletons(opts) {
|
|
192
|
-
const flow = ensureFlowSkeleton(opts.flowYamlAbs);
|
|
193
|
-
const spec = opts.composerSpecAbs
|
|
194
|
-
? ensureNodeSpecSkeleton(opts.composerSpecAbs, { flowId: opts.flowId, userRequest: opts.userRequest })
|
|
195
|
-
: { created: false, reason: "missing composerSpecAbs" };
|
|
196
|
-
return { flow, spec };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ─── 解析 spec.md「计划数据槽」+ 合并到 flow.yaml ──────────────────────────
|
|
200
|
-
|
|
201
|
-
const VALID_BUSINESS_TYPES = new Set(["text", "file"]);
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* 从 spec.md 文本中解析「计划数据槽」section。
|
|
205
|
-
* 期望格式:
|
|
206
|
-
* ## 计划数据槽(...)
|
|
207
|
-
* <instanceId>:
|
|
208
|
-
* input += [<name>:<type>, <name>:<type>]
|
|
209
|
-
* output += [<name>:<type>]
|
|
210
|
-
* @param {string} specText
|
|
211
|
-
* @returns {Record<string, { input: Array<{name,type}>, output: Array<{name,type}> }>}
|
|
212
|
-
*/
|
|
213
|
-
export function parsePlannedSlotsFromSpec(specText) {
|
|
214
|
-
if (!specText || typeof specText !== "string") return {};
|
|
215
|
-
const idx = specText.search(/^##\s+计划数据槽/m);
|
|
216
|
-
if (idx < 0) return {};
|
|
217
|
-
const section = specText.slice(idx);
|
|
218
|
-
// 截到下一个 ## 或 EOF
|
|
219
|
-
const nextH = section.slice(2).search(/^##\s/m);
|
|
220
|
-
const body = nextH >= 0 ? section.slice(0, nextH + 2) : section;
|
|
221
|
-
|
|
222
|
-
const result = {};
|
|
223
|
-
// 按 instanceId 块分割:行首非缩进的 "<id>:" 起一个块
|
|
224
|
-
const lines = body.split("\n");
|
|
225
|
-
let currentId = null;
|
|
226
|
-
for (const rawLine of lines) {
|
|
227
|
-
const line = rawLine.replace(/\r$/, "");
|
|
228
|
-
// 跳过 HTML 注释、标题、空行
|
|
229
|
-
if (/^\s*$/.test(line)) continue;
|
|
230
|
-
if (/^<!--|-->\s*$|^##\s/.test(line)) continue;
|
|
231
|
-
|
|
232
|
-
const idMatch = /^\s{0,2}([A-Za-z][A-Za-z0-9_]*)\s*:\s*$/.exec(line);
|
|
233
|
-
if (idMatch) {
|
|
234
|
-
currentId = idMatch[1];
|
|
235
|
-
if (!result[currentId]) result[currentId] = { input: [], output: [] };
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
// input += [...] / output += [...]
|
|
239
|
-
const slotMatch = /^\s*(input|output)\s*\+?=\s*\[([^\]]*)\]\s*$/.exec(line);
|
|
240
|
-
if (slotMatch && currentId) {
|
|
241
|
-
const kind = slotMatch[1];
|
|
242
|
-
const items = slotMatch[2].split(",").map((s) => s.trim()).filter(Boolean);
|
|
243
|
-
for (const item of items) {
|
|
244
|
-
const m = /^([A-Za-z][A-Za-z0-9_]*)\s*:\s*([a-z]+)$/.exec(item);
|
|
245
|
-
if (!m) continue;
|
|
246
|
-
const name = m[1];
|
|
247
|
-
const type = m[2];
|
|
248
|
-
if (!VALID_BUSINESS_TYPES.has(type)) continue;
|
|
249
|
-
result[currentId][kind].push({ name, type });
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return result;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* 把 spec.md 解析出的业务槽合并到 flow.yaml(幂等:同 name 跳过)。
|
|
258
|
-
* 仅对 ★ 可扩展节点(agent_subAgent / tool_nodejs / tool_user_check)操作;
|
|
259
|
-
* 其它 definitionId 上的 planned 数据**忽略**(属于 spec.md 误写,不要破坏固定槽位)。
|
|
260
|
-
*
|
|
261
|
-
* @param {string} flowYamlAbs
|
|
262
|
-
* @param {string} specMdAbs
|
|
263
|
-
* @returns {{ ok: boolean, applied: Array<{instanceId, addedInputs: string[], addedOutputs: string[]}>, skipped: Array<{instanceId, reason: string}>, error?: string }}
|
|
264
|
-
*/
|
|
265
|
-
export function applyPlannedSlotsFromSpec(flowYamlAbs, specMdAbs) {
|
|
266
|
-
if (!flowYamlAbs || !fs.existsSync(flowYamlAbs)) return { ok: false, applied: [], skipped: [], error: "flow.yaml not found" };
|
|
267
|
-
if (!specMdAbs || !fs.existsSync(specMdAbs)) return { ok: false, applied: [], skipped: [], error: "spec.md not found" };
|
|
268
|
-
|
|
269
|
-
const EXTENSIBLE = new Set(["agent_subAgent", "tool_nodejs", "tool_user_check"]);
|
|
270
|
-
const applied = [];
|
|
271
|
-
const skipped = [];
|
|
272
|
-
|
|
273
|
-
let flow;
|
|
274
|
-
try {
|
|
275
|
-
flow = yaml.load(fs.readFileSync(flowYamlAbs, "utf-8"));
|
|
276
|
-
} catch (e) {
|
|
277
|
-
return { ok: false, applied: [], skipped: [], error: `failed to parse flow.yaml: ${e.message}` };
|
|
278
|
-
}
|
|
279
|
-
if (!flow || typeof flow !== "object" || !flow.instances) {
|
|
280
|
-
return { ok: false, applied: [], skipped: [], error: "flow.yaml has no instances" };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
let planned;
|
|
284
|
-
try {
|
|
285
|
-
const specText = fs.readFileSync(specMdAbs, "utf-8");
|
|
286
|
-
planned = parsePlannedSlotsFromSpec(specText);
|
|
287
|
-
} catch (e) {
|
|
288
|
-
return { ok: false, applied: [], skipped: [], error: `failed to parse spec.md: ${e.message}` };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
let mutated = false;
|
|
292
|
-
for (const [instanceId, plan] of Object.entries(planned)) {
|
|
293
|
-
const inst = flow.instances[instanceId];
|
|
294
|
-
if (!inst || typeof inst !== "object") {
|
|
295
|
-
skipped.push({ instanceId, reason: "instance not in flow.yaml" });
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
if (!EXTENSIBLE.has(inst.definitionId)) {
|
|
299
|
-
skipped.push({ instanceId, reason: `non-extensible definitionId: ${inst.definitionId}` });
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
if (!Array.isArray(inst.input)) inst.input = [];
|
|
303
|
-
if (!Array.isArray(inst.output)) inst.output = [];
|
|
304
|
-
|
|
305
|
-
const existingInputNames = new Set(inst.input.map((s) => s && s.name).filter(Boolean));
|
|
306
|
-
const existingOutputNames = new Set(inst.output.map((s) => s && s.name).filter(Boolean));
|
|
307
|
-
const addedInputs = [];
|
|
308
|
-
const addedOutputs = [];
|
|
309
|
-
|
|
310
|
-
for (const { name, type } of plan.input) {
|
|
311
|
-
if (existingInputNames.has(name)) continue;
|
|
312
|
-
inst.input.push({ type, name, value: "" });
|
|
313
|
-
existingInputNames.add(name);
|
|
314
|
-
addedInputs.push(`${name}:${type}`);
|
|
315
|
-
mutated = true;
|
|
316
|
-
}
|
|
317
|
-
for (const { name, type } of plan.output) {
|
|
318
|
-
if (existingOutputNames.has(name)) continue;
|
|
319
|
-
inst.output.push({ type, name, value: "" });
|
|
320
|
-
existingOutputNames.add(name);
|
|
321
|
-
addedOutputs.push(`${name}:${type}`);
|
|
322
|
-
mutated = true;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (addedInputs.length > 0 || addedOutputs.length > 0) {
|
|
326
|
-
applied.push({ instanceId, addedInputs, addedOutputs });
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (mutated) {
|
|
331
|
-
fs.writeFileSync(flowYamlAbs, yaml.dump(flow, { lineWidth: -1, noRefs: true }), "utf-8");
|
|
332
|
-
}
|
|
333
|
-
return { ok: true, applied, skipped };
|
|
334
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Web UI Composer:对当前 flow.yaml 调用与 CLI 一致的 validate-flow 逻辑。
|
|
3
|
-
* agent 有时会把 flow.yaml 写到 workspace 路径而非 user 路径(或反之),
|
|
4
|
-
* 当原路径找不到时,尝试在 workspace 下的 .workspace/agentflow/pipelines/ 和
|
|
5
|
-
* agentflow/pipelines/ 中查找同名目录。
|
|
6
|
-
*/
|
|
7
|
-
import fs from "fs";
|
|
8
|
-
import path from "path";
|
|
9
|
-
import { runValidateFlow } from "../pipeline/validate-flow.mjs";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {string} flowYamlAbs
|
|
13
|
-
* @param {string} workspaceRoot
|
|
14
|
-
* @returns {{ ok: boolean, errors: string[], warnings: string[] }}
|
|
15
|
-
*/
|
|
16
|
-
export function validateComposerFlowYaml(flowYamlAbs, workspaceRoot) {
|
|
17
|
-
let flowDir = path.dirname(path.resolve(flowYamlAbs));
|
|
18
|
-
const root = path.resolve(workspaceRoot);
|
|
19
|
-
|
|
20
|
-
if (!fs.existsSync(path.join(flowDir, "flow.yaml"))) {
|
|
21
|
-
const flowId = path.basename(flowDir);
|
|
22
|
-
const candidates = [
|
|
23
|
-
path.join(root, ".workspace", "agentflow", "pipelines", flowId),
|
|
24
|
-
path.join(root, "agentflow", "pipelines", flowId),
|
|
25
|
-
path.join(root, ".cursor", "agentflow", "pipelines", flowId),
|
|
26
|
-
];
|
|
27
|
-
for (const c of candidates) {
|
|
28
|
-
if (fs.existsSync(path.join(c, "flow.yaml"))) {
|
|
29
|
-
flowDir = c;
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return runValidateFlow(flowDir, root);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @param {{ ok?: boolean, errors?: string[] }} result
|
|
40
|
-
* @returns {string}
|
|
41
|
-
*/
|
|
42
|
-
export function formatValidationErrorsBlock(result) {
|
|
43
|
-
if (!result) return "(校验无返回)";
|
|
44
|
-
const errs = Array.isArray(result.errors) ? result.errors : [];
|
|
45
|
-
if (errs.length === 0) return "(无 errors 字段,请检查 flow.yaml 结构)";
|
|
46
|
-
return errs.map((e, i) => `${i + 1}. ${e}`).join("\n");
|
|
47
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composer 模型路由器:基于 model-lists.json 按任务复杂度分配合适的模型。
|
|
3
|
-
*
|
|
4
|
-
* 模型分三个档次:
|
|
5
|
-
* - fast — 简单/一般任务(改文案、添加节点、日常编辑)
|
|
6
|
-
* - balanced — 中等任务(多步推理、较复杂设计)
|
|
7
|
-
* - capable — 复杂任务(流程重构、深度分析)
|
|
8
|
-
*
|
|
9
|
-
* 路由策略:simple → fast,medium → fast,complex → capable
|
|
10
|
-
* "auto" 条目不参与自动路由(auto 本身就是让 Cursor 自己选,不是明确的模型选择)。
|
|
11
|
-
*/
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
import { getModelListsAbs } from "./paths.mjs";
|
|
14
|
-
import { loadModelConfig, normalizeCursorModelForCli } from "./model-config.mjs";
|
|
15
|
-
|
|
16
|
-
// ─── 模型分类规则 ──────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
const FAST_PATTERNS = [
|
|
19
|
-
/\bflash\b/i,
|
|
20
|
-
/\bmini\b/i,
|
|
21
|
-
/\bhaiku\b/i,
|
|
22
|
-
/\blite\b/i,
|
|
23
|
-
/\binstant\b/i,
|
|
24
|
-
/\bfast\b/i,
|
|
25
|
-
/\b4o-mini\b/i,
|
|
26
|
-
/\bgpt-4\.1-mini\b/i,
|
|
27
|
-
/\bgpt-4\.1-nano\b/i,
|
|
28
|
-
/\bclaude-3\.5-haiku\b/i,
|
|
29
|
-
/\bgemini.*flash\b/i,
|
|
30
|
-
/\bdeepseek-v3\b/i,
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
const CAPABLE_PATTERNS = [
|
|
34
|
-
/\bopus\b/i,
|
|
35
|
-
/\bo1\b/i,
|
|
36
|
-
/\bo3\b/i,
|
|
37
|
-
/\bo4-mini\b/i,
|
|
38
|
-
/\bsonnet.*think/i,
|
|
39
|
-
/\bthinking\b/i,
|
|
40
|
-
/\bmax\b/i,
|
|
41
|
-
/\bcodex\b/i,
|
|
42
|
-
/\bgpt-5/i,
|
|
43
|
-
/\bclaude-4/i,
|
|
44
|
-
/\bdeepseek-r1\b/i,
|
|
45
|
-
/\bgemini.*pro.*think/i,
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
function classifyModel(modelId) {
|
|
49
|
-
const s = String(modelId || "");
|
|
50
|
-
for (const re of FAST_PATTERNS) {
|
|
51
|
-
if (re.test(s)) return "fast";
|
|
52
|
-
}
|
|
53
|
-
for (const re of CAPABLE_PATTERNS) {
|
|
54
|
-
if (re.test(s)) return "capable";
|
|
55
|
-
}
|
|
56
|
-
return "balanced";
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ─── 读取可用模型列表 ──────────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
function loadModelLists() {
|
|
62
|
-
try {
|
|
63
|
-
const p = getModelListsAbs();
|
|
64
|
-
if (!fs.existsSync(p)) return { cursor: [], opencode: [], claudeCode: [], codex: [] };
|
|
65
|
-
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
66
|
-
return {
|
|
67
|
-
cursor: Array.isArray(data.cursor) ? data.cursor.map(String) : [],
|
|
68
|
-
opencode: Array.isArray(data.opencode) ? data.opencode.map(String) : [],
|
|
69
|
-
claudeCode: Array.isArray(data.claudeCode) ? data.claudeCode.map(String) : [],
|
|
70
|
-
codex: Array.isArray(data.codex) ? data.codex.map(String) : [],
|
|
71
|
-
};
|
|
72
|
-
} catch {
|
|
73
|
-
return { cursor: [], opencode: [], claudeCode: [], codex: [] };
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isAutoEntry(entry) {
|
|
78
|
-
const id = String(entry || "").split(" - ")[0].trim().toLowerCase();
|
|
79
|
-
return id === "auto" || id === "";
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function buildModelTiers(modelList) {
|
|
83
|
-
const tiers = { fast: [], balanced: [], capable: [] };
|
|
84
|
-
for (const m of modelList) {
|
|
85
|
-
if (isAutoEntry(m)) continue;
|
|
86
|
-
const tier = classifyModel(m);
|
|
87
|
-
tiers[tier].push(m);
|
|
88
|
-
}
|
|
89
|
-
return tiers;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function modelEntryId(entry) {
|
|
93
|
-
const idx = String(entry || "").indexOf(" - ");
|
|
94
|
-
return idx >= 0 ? entry.slice(0, idx).trim() : String(entry || "").trim();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function prefixedModels(models, prefix) {
|
|
98
|
-
return (Array.isArray(models) ? models : [])
|
|
99
|
-
.map((m) => modelEntryId(m))
|
|
100
|
-
.filter(Boolean)
|
|
101
|
-
.map((m) => `${prefix}:${m}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── 公开接口 ──────────────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 根据任务复杂度从可用模型列表中选择合适的模型。
|
|
108
|
-
*
|
|
109
|
-
* @param {"simple" | "medium" | "complex"} complexity
|
|
110
|
-
* @param {object} [opts]
|
|
111
|
-
* @param {string} [opts.userPreferredModel] 用户在 Composer 下拉选择的模型(优先级最高)
|
|
112
|
-
* @param {string} [opts.workspaceRoot] 工作区根目录(加载 models.json)
|
|
113
|
-
* @returns {{ model: string | null, tier: string, source: string }}
|
|
114
|
-
*/
|
|
115
|
-
export function routeModel(complexity, opts = {}) {
|
|
116
|
-
if (opts.userPreferredModel && String(opts.userPreferredModel).trim()) {
|
|
117
|
-
const m = String(opts.userPreferredModel).trim();
|
|
118
|
-
return { model: m, tier: classifyModel(m), source: "user-selected" };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const lists = loadModelLists();
|
|
122
|
-
const allModels = [
|
|
123
|
-
...lists.cursor,
|
|
124
|
-
...prefixedModels(lists.codex, "codex"),
|
|
125
|
-
...prefixedModels(lists.claudeCode, "claude-code"),
|
|
126
|
-
...prefixedModels(lists.opencode, "opencode"),
|
|
127
|
-
];
|
|
128
|
-
if (allModels.length === 0) {
|
|
129
|
-
return { model: null, tier: complexity === "complex" ? "capable" : complexity === "simple" ? "fast" : "balanced", source: "no-models-available" };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const tiers = buildModelTiers(allModels);
|
|
133
|
-
|
|
134
|
-
// simple 和 medium 都优先用 fast 模型,complex 才升级到 capable
|
|
135
|
-
const tierMap = {
|
|
136
|
-
simple: "fast",
|
|
137
|
-
medium: "fast",
|
|
138
|
-
complex: "capable",
|
|
139
|
-
};
|
|
140
|
-
const targetTier = tierMap[complexity] || "fast";
|
|
141
|
-
|
|
142
|
-
if (tiers[targetTier].length > 0) {
|
|
143
|
-
return { model: tiers[targetTier][0], tier: targetTier, source: "auto-routed" };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// fallback 链:capable → balanced → fast → any
|
|
147
|
-
if (targetTier === "capable") {
|
|
148
|
-
if (tiers.balanced.length > 0) return { model: tiers.balanced[0], tier: "balanced", source: "auto-routed-fallback" };
|
|
149
|
-
if (tiers.fast.length > 0) return { model: tiers.fast[0], tier: "fast", source: "auto-routed-fallback" };
|
|
150
|
-
}
|
|
151
|
-
if (targetTier === "fast") {
|
|
152
|
-
if (tiers.balanced.length > 0) return { model: tiers.balanced[0], tier: "balanced", source: "auto-routed-fallback" };
|
|
153
|
-
if (tiers.capable.length > 0) return { model: tiers.capable[0], tier: "capable", source: "auto-routed-fallback" };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const firstReal = allModels.find((m) => !isAutoEntry(m));
|
|
157
|
-
if (firstReal) return { model: firstReal, tier: classifyModel(firstReal), source: "auto-routed-fallback" };
|
|
158
|
-
return { model: allModels[0], tier: classifyModel(allModels[0]), source: "auto-routed-fallback" };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* 返回模型列表的分层信息(供 UI 或调试用)。
|
|
163
|
-
*/
|
|
164
|
-
export function getModelTierInfo() {
|
|
165
|
-
const lists = loadModelLists();
|
|
166
|
-
const cursorTiers = buildModelTiers(lists.cursor);
|
|
167
|
-
const opencodeTiers = buildModelTiers(lists.opencode);
|
|
168
|
-
const claudeCodeTiers = buildModelTiers(lists.claudeCode);
|
|
169
|
-
const codexTiers = buildModelTiers(lists.codex);
|
|
170
|
-
return {
|
|
171
|
-
cursor: cursorTiers,
|
|
172
|
-
opencode: opencodeTiers,
|
|
173
|
-
claudeCode: claudeCodeTiers,
|
|
174
|
-
codex: codexTiers,
|
|
175
|
-
totalCursor: lists.cursor.length,
|
|
176
|
-
totalOpencode: lists.opencode.length,
|
|
177
|
-
totalClaudeCode: lists.claudeCode.length,
|
|
178
|
-
totalCodex: lists.codex.length,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* 对单个模型 ID 进行分类。
|
|
184
|
-
*/
|
|
185
|
-
export { classifyModel };
|