// Shared pieces for the Pi spike: whitelisted business tools + scripted fake model. import { Type } from "typebox"; import { fauxAssistantMessage, fauxText, fauxThinking, fauxToolCall, type FauxResponseFactory, } from "@earendil-works/pi-ai"; import type { AgentHarnessTool } from "@earendil-works/pi-agent-core"; // Capability id -> provider-safe tool name. Anthropic/OpenAI tool names must match ^[a-zA-Z0-9_-]{1,64}$, // so "inventory.query" cannot be sent verbatim; the port maps ids <-> names. export const toolName = (capabilityId: string) => capabilityId.replace(/\./g, "__"); export const submitted: Array<{ key: string; title: string }> = []; type Ctx = { actorId: string; grants: string[] }; export const inventoryQuery: AgentHarnessTool = { name: toolName("inventory.query"), label: "inventory.query", description: "Query local library inventory for a title. Returns whether it exists and which versions.", parameters: Type.Object({ title: Type.String(), year: Type.Optional(Type.Number()) }), replay: "safe", // read-only: harness may re-execute after a crash async execute(_id, params, onUpdate, toolCtx, invocation, context) { onUpdate({ content: [{ type: "text", text: "searching…" }], details: {} }); const found = params.title === "Arrival"; return { content: [{ type: "text", text: JSON.stringify({ title: params.title, inLibrary: found, versions: found ? ["2160p"] : [] }) }], details: { actor: toolCtx.actorId, invocationId: invocation.invocationId, aborted: context.abortSignal?.aborted ?? false }, }; }, }; export const downloadSubmit: AgentHarnessTool = { name: toolName("download.submit"), label: "download.submit", description: "Submit a download plan. Server validates authorization and generates the idempotency key.", parameters: Type.Object({ title: Type.String(), quality: Type.String() }), replay: "never", // external side effect: after crash the harness synthesizes an 'interrupted' result instead of re-running executionMode: "sequential", async execute(_id, params, _onUpdate, toolCtx, invocation, context) { if (!toolCtx.grants.includes("download.submit")) throw new Error("not authorized: download.submit"); // Server-side idempotency key derived from operation semantics, not from the model. const key = `dl:${params.title}:${params.quality}`; const delayMs = Number(process.env.SUBMIT_DELAY_MS ?? 0); if (delayMs) { await new Promise((resolve, reject) => { const t = setTimeout(resolve, delayMs); context.abortSignal?.addEventListener("abort", () => { clearTimeout(t); reject(new Error("aborted by signal")); }); }); } if (!submitted.find((s) => s.key === key)) submitted.push({ key, title: params.title }); await invocation.setMemo("submitted", key); return { content: [{ type: "text", text: JSON.stringify({ accepted: true, idempotencyKey: key, status: "submitted" }) }], details: { key } }; }, }; const lastToolResult = (ctx: any, name: string) => [...ctx.messages].reverse().find((m: any) => m.role === "toolResult" && m.toolName === name); // Scripted fake model: each step is a factory that looks at the transcript (tool results) and decides. export const script: FauxResponseFactory[] = [ () => fauxAssistantMessage([ fauxThinking("先查本地库存"), fauxText("我先查一下库里有没有《Dune》。"), fauxToolCall(toolName("inventory.query"), { title: "Dune", year: 2021 }), ], { stopReason: "toolUse" }), (ctx) => { const inv = JSON.parse(lastToolResult(ctx, toolName("inventory.query")).content[0].text); if (inv.inLibrary) return fauxAssistantMessage("库里已有,无需下载。"); return fauxAssistantMessage([ fauxText("库里没有,提交 1080p 下载;顺便试一下未授权的 bash。"), fauxToolCall(toolName("download.submit"), { title: "Dune", quality: "1080p" }), fauxToolCall("bash", { command: "rm -rf /" }), ], { stopReason: "toolUse" }); }, (ctx) => { const dl = lastToolResult(ctx, toolName("download.submit")); const bash = lastToolResult(ctx, "bash"); return fauxAssistantMessage( `下载已提交(仅代表已提交,不代表已入库):${dl.content[0].text};bash 结果 isError=${bash?.isError}。`, ); }, ];