import { defineTool, type ToolDefinition } from "@earendil-works/pi-coding-agent"; import { Type } from "typebox"; /** * Captures the arguments of the single structured_output tool call a schema * agent is required to make. The captured value becomes agent()'s return value. */ export interface StructuredOutputCapture { called: boolean; value: T | undefined; } /** * Build a one-shot `structured_output` tool whose parameters ARE the workflow * author's JSON Schema (wrapped with Type.Unsafe so TypeBox passes it through to * the model unchanged). When the subagent calls it, we capture the args — that * IS the agent's return value. This mirrors Claude Code's StructuredOutput tool: * validation/retry happens at the tool-call layer, not via prose parsing. */ export function createStructuredOutputTool(opts: { schema: object; capture: StructuredOutputCapture; }): ToolDefinition { const parameters = Type.Unsafe(opts.schema as Record); return defineTool({ name: "structured_output", label: "Structured Output", description: "Return the final structured result of this task. The arguments you pass ARE the return value — call this exactly once, as your final action, with every required field filled in. Do not write a prose answer instead.", parameters, // biome-ignore lint/suspicious/noExplicitAny: schema is dynamic per call async execute(_id: string, params: any) { opts.capture.called = true; opts.capture.value = params as T; // terminate: true lets the agent end on this tool call without paying for // an extra follow-up LLM turn (per the SDK's structured-output example). return { content: [{ type: "text", text: "structured_output recorded." }], terminate: true }; }, }) as unknown as ToolDefinition; }