import type { FakeScript, FakeStep, FakeToolResult } from "@umt/agent"; import { z } from "zod"; // fake 模式脚本(默认上游 provider = "fake"):测试直接注入 FakeScript;设置里的 agent.fakeScript 是可序列化子集(JSON): // { "match": "收进来" | { "regex": "..." }, "steps": [{ "text"?, "toolCalls": [{ "capability", "input" }] }], "finalText": "..." } // 或它们的数组。input 里的字符串可以写 "{{download.plan.plan.id}}":取该能力最近一次成功结果里的路径值。 const JsonStep = z.object({ thinking: z.string().optional(), text: z.string().optional(), toolCalls: z .array(z.object({ capability: z.string().min(3), input: z.record(z.string(), z.unknown()).default({}) })) .optional(), }); const JsonScript = z.object({ name: z.string().optional(), match: z.union([z.string(), z.object({ regex: z.string() })]), steps: z.array(JsonStep), finalText: z.string(), }); const JsonScripts = z.union([JsonScript, z.array(JsonScript)]); const TEMPLATE = /^\{\{\s*([a-z][a-z0-9]*\.[a-z][a-z0-9_]*)((?:\.[A-Za-z0-9_]+)*)\s*\}\}$/; function lookup(results: FakeToolResult[], capability: string, path: string[]): unknown { const hit = [...results].reverse().find((r) => r.capabilityId === capability && !r.isError); let cur: unknown = hit?.json; for (const k of path) { if (cur === null || cur === undefined) return undefined; cur = Array.isArray(cur) ? cur[Number(k)] : (cur as Record)[k]; } return cur; } function render(v: unknown, results: FakeToolResult[]): unknown { if (typeof v === "string") { const m = TEMPLATE.exec(v); if (!m) return v; return lookup(results, m[1]!, (m[2] ?? "").split(".").filter(Boolean)) ?? null; } if (Array.isArray(v)) return v.map((x) => render(x, results)); if (v && typeof v === "object") { return Object.fromEntries(Object.entries(v as Record).map(([k, x]) => [k, render(x, results)])); } return v; } /** 解析 settings.agent.fakeScript;不合法时抛错(运行失败并给出原因) */ export function parseFakeScripts(json: string): FakeScript[] { const parsed = JsonScripts.parse(JSON.parse(json)); const list = Array.isArray(parsed) ? parsed : [parsed]; return list.map((s) => ({ ...(s.name ? { name: s.name } : {}), match: typeof s.match === "string" ? s.match : new RegExp(s.match.regex), steps: s.steps.map( (st): FakeStep => ({ ...(st.thinking ? { thinking: st.thinking } : {}), ...(st.text ? { text: st.text } : {}), ...(st.toolCalls ? { toolCalls: st.toolCalls.map((c) => ({ capability: c.capability, input: (results: FakeToolResult[]) => render(c.input, results) as never, })), } : {}), }), ), finalText: s.finalText, })); } /** 内置演示脚本:任何目标都先查库存再如实汇报(不做写操作) */ export const DEMO_SCRIPTS: FakeScript[] = [ { name: "demo", match: () => true, steps: [{ text: "[fake] 先查本地库存。", toolCalls: [{ capability: "inventory.query", input: { limit: 5 } }] }], finalText: (results) => { const r = results.find((x) => x.capabilityId === "inventory.query"); const n = (r?.json as { items?: unknown[] } | undefined)?.items?.length ?? 0; return `[fake] 演示模式:库存查询返回 ${n} 个作品。演示模型不提交下载;要真实执行,在设置里配置模型服务。`; }, }, ];