// 探索工具 server 的框架扩展(阶段 D)。 // // 边界合同中的工具层:给创作智能体暴露只读探索工具。本扩展不自带任何查询逻辑, // 工具登记表与实现都在 read_tools.py(Python,只读连接、未知工具拒绝、结果有界)。 // 加载时执行 `read_tools.py --list` 取登记表,按登记表动态注册工具,保证单一事实源; // 每次工具执行把参数原样转给 `read_tools.py <工具名> ''`,stdout 即结果。 // // 派发器经环境变量定位实现:MUSE_READ_TOOLS_PYTHON(解释器)、MUSE_READ_TOOLS_SCRIPT(脚本)。 // 未经派发器注入这两个变量时不注册任何工具(静默跳过),隔离会话不受影响。 import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { Type } from "typebox"; interface ToolSpec { description: string; tables: string[]; args: Record; } function runReadTools(python: string, script: string, argv: string[]): string { const bun = (globalThis as { Bun?: any }).Bun; if (bun?.spawnSync) { const res = bun.spawnSync([python, script, ...argv], { stdout: "pipe", stderr: "pipe", }); const stdout = new TextDecoder().decode(res.stdout); if (res.exitCode !== 0) { const stderr = new TextDecoder().decode(res.stderr).trim(); throw new Error(stderr || `read_tools 退出码 ${res.exitCode}`); } return stdout; } // Node 运行时的回退路径。 // eslint-disable-next-line @typescript-eslint/no-var-requires const { execFileSync } = require("node:child_process"); return execFileSync(python, [script, ...argv], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], }); } export default function (pi: ExtensionAPI) { const python = process.env.MUSE_READ_TOOLS_PYTHON; const script = process.env.MUSE_READ_TOOLS_SCRIPT; if (!python || !script) { return; // 非工具 server 派发:不注册任何工具。 } let registry: Record = {}; try { registry = JSON.parse(runReadTools(python, script, ["--list"])); } catch { return; // 登记表不可读时保持无工具,不放行裸查询。 } for (const [name, spec] of Object.entries(registry)) { const props: Record = {}; for (const [rawName, argType] of Object.entries(spec.args ?? {})) { const optional = rawName.endsWith("?"); const argName = optional ? rawName.slice(0, -1) : rawName; const base = argType === "int" ? Type.Integer({ description: argName }) : Type.String({ description: argName }); props[argName] = optional ? Type.Optional(base) : base; } pi.registerTool({ name, label: name, description: `${spec.description}(只读;读取 ${spec.tables.join("、")})`, promptGuidelines: [ `Use ${name} only for read-only exploration; it never modifies data.`, ], parameters: Type.Object(props), async execute(_toolCallId, params) { try { const stdout = runReadTools(python, script, [ name, JSON.stringify(params ?? {}), ]); return { content: [{ type: "text", text: stdout }] }; } catch (error) { // 抛出错误使框架把该工具调用标记为失败,模型可据此调整探索。 throw new Error( error instanceof Error ? error.message : String(error) ); } }, }); } }