/** * `vendo.agentTools` — this deployment's door, already wired, for an agent loop * the host writes by hand. * * A host on the AI SDK or Mastra gets `vendoTools(vendo)` and is done. A host * driving `@anthropic-ai/sdk` directly had to write the plumbing itself: mint a * badge, stand up an MCP client and transport, map the tool format, keep ONE * session for the whole conversation (the door pins a parked approval to the * session that parked it, so a per-request reconnect parks forever), re-mint * when the ten minutes run out, and collect the typed envelopes the page * renders. None of that is a decision the host wants to make. It is this file. * * IN-PROCESS, like `vendo.tokenFor`: every request rides `vendo.handler`, so a * deployment never has to be able to reach itself over the network. The client * is ours rather than the stock MCP SDK for exactly that reason — the SDK's * streamable-HTTP transport is built around a URL it fetches and a background * SSE stream someone has to close, and it is a devDependency here, not a * dependency. What the door speaks is JSON-RPC over one POST; that is the whole * client, below. */ import { type VendoToolEnvelope } from "./core/index.js"; import type { VendoComposition } from "./compose-context.js"; /** One tool, in the shape `messages.create({ tools })` takes. Structural on * purpose: this package does not depend on `@anthropic-ai/sdk`, and a host * should not have to annotate anything to pass the list straight through. */ export interface VendoAgentTool { name: string; description: string; input_schema: { type: "object"; [key: string]: unknown; }; } /** The assistant message you got back, as much of it as this needs: the content * blocks. `Anthropic.Message` satisfies it. */ export interface VendoAgentMessage { content: readonly { readonly type: string; }[]; } /** One `tool_result` block, ready to push as the next user message's content. */ export interface VendoAgentToolResult { type: "tool_result"; tool_use_id: string; content: { type: "text"; text: string; }[]; is_error: boolean; } /** One conversation's connection to the door. Hold it for the whole * conversation: the session it opens is what a parked approval resumes on. */ export interface VendoAgentTools { /** What this user may call, as the model wants to read it. A snapshot taken * when the door opened — the conversation's tool list is the one its history * refers to. * * A mutable array, not a `readonly` one, so it goes STRAIGHT into * `messages.create({ tools })`: the Messages API asks for `Tool[]`, and a * `readonly Tool[]` does not satisfy it, so the reader-friendly modifier * would cost every caller a `[...door.tools]` at the one call site this * whole method exists to shorten. */ readonly tools: VendoAgentTool[]; /** * Run every `tool_use` block in an assistant message through the door and * answer the `tool_result` blocks that go back. * * `[]` when the model called nothing, which is how the loop knows it is done: * * ```ts * const results = await door.results(reply); * if (results.length === 0) break; * messages.push({ role: "user", content: results }); * ``` * * These EXECUTE. Calling it twice on the same message runs the same tools * twice — the loop calls it once per assistant turn. */ results(message: VendoAgentMessage): Promise; /** Every typed envelope this conversation's calls produced, in order — the * approval refs and app refs the host's page renders with ``. * It grows as `results` runs; a plain tool output is not one of these. */ readonly embeds: readonly VendoToolEnvelope[]; } export declare function composeAgentTools(composition: VendoComposition, handler: (request: Request) => Promise, tokenFor: (who: Request | string) => Promise): (who: Request | string) => Promise;