/** * Anthropic API proxy — `/v1/messages` wire format, agent-neutral. * * Reusable across any agent that speaks Anthropic (claude itself, * opencode via `@ai-sdk/anthropic`, future agents). The agent-specific * bits — tool name to redirect short-circuits to, how to resolve the * upstream URL, and the redirect tool's input shape — are passed in * by the caller. * * Strategy: never see SSE on the wire. We force `stream: false` * upstream, get a single JSON `Message` back, mutate it in place, then * synthesize the SSE event sequence the agent expects via the shared * HTTP server scaffolding. * * The two-way lie: * * When middleware short-circuits, we don't strip the tool_use (the * agent would just retry) and we can't directly prevent execution * from outside the agent. Instead we redirect the tool_use to the * caller's configured shell tool (claude: `Bash`, opencode: `bash`) * with a base64-encoded synthesis. The agent runs that, gets exactly * the bytes we wanted as the result, then sends the next request. On * that request we restore the original tool name + input in the * assistant message so the LLM never sees the redirect: it sees its * own emission and a coherent tool_result. Same trick handles mutated * args: the agent runs with mutatedArgs, the LLM sees originalArgs. */ import { type AgentProxy } from '../core/proxy/http-server.js'; import { stringifyToolResult } from '../core/proxy/middleware.js'; import type { Config, Data, Middleware, Recording, ToolCall, ToolResult } from '../core/types.js'; type ContentBlock = { type: 'text'; text: string; [k: string]: unknown; } | { type: 'tool_use'; id: string; name: string; input: unknown; [k: string]: unknown; } | { type: 'thinking'; thinking: string; signature?: string; [k: string]: unknown; } | { type: string; [k: string]: unknown; }; type AnthropicMessage = { id: string; type: 'message'; role: 'assistant'; model: string; content: ContentBlock[]; stop_reason: string | null; stop_sequence: string | null; usage: Record; [k: string]: unknown; }; /** * Build the SSE event sequence Anthropic streams for a complete `Message`. * Every required envelope field must be present, even ones that look * optional (`stop_sequence: null`, full `usage`). * * message_start → per-block (content_block_start → delta → stop) → * message_delta → message_stop */ export declare function synthesizeSse(msg: AnthropicMessage): string; export declare function splitMcpName(name: string): { server: string; tool: string; }; /** * Build a shell-tool input that, when run, emits exactly the synthetic * content as stdout and exits non-zero iff the synthetic is an error. * base64 round-tripping avoids any shell-quoting hazards. * * Returns `{ command, description }` — what claude's `Bash` tool and * opencode's `bash` tool both accept. Override via * `redirectInputBuilder` for tools with different input shapes. */ export declare function encodeAsToolRedirect(synthetic: ToolResult): { command: string; description: string; }; export type StartAnthropicProxyOpts = { evalName: string; config: Readonly; data: Data; middleware: readonly Middleware[]; abort: (reason?: unknown) => void; onToolCall: (call: ToolCall) => void; /** Override upstream URL. If provided, takes priority over `upstreamResolver`. */ upstream?: string; /** * Resolve the upstream URL when `upstream` isn't passed. Each adapter * brings its own resolver. Defaults to `process.env.ANTHROPIC_BASE_URL` * → `https://api.anthropic.com`. */ upstreamResolver?: () => string | Promise; /** * Tool name used as the redirect target on short-circuit. Claude has * `Bash`; opencode has `bash`. If your agent's redirect tool takes a * different input shape, also pass `redirectInputBuilder`. */ redirectToolName: string; /** * Build the input object passed to the redirect tool. Defaults to * `encodeAsToolRedirect`, which produces `{ command, description }`. */ redirectInputBuilder?: (synthetic: ToolResult) => unknown; /** * Capture each upstream round-trip as raw bytes — used by the * runner's `record` mode to gather exchanges for later replay. */ onExchange?: (request: string, response: string) => void; /** * Map of `tool_use_id → recorded ToolResult`. When the agent emits a * tool_use with one of these ids during the replay prefix, the proxy * auto-short-circuits it with the recorded result and skips * middleware (strict replay). Built by `extractAnthropicReplayResults` * from the captured request bytes. * * Set automatically by `startAnthropicProxy` when `replay` is * provided — callers don't normally pass this by hand. */ replayResults?: ReadonlyMap; /** * Replay the first `upTo` recorded exchanges before forwarding to * the live upstream. When set, the proxy spins up an in-process * replay-upstream (using `upstream`/`upstreamResolver` as the LIVE * URL) and routes the agent's traffic through it. Tool calls during * the prefix auto-short-circuit using the recording's tool_results. */ replay?: { recording: Recording; upTo: number; }; /** * Optional pointer-dereferencer for tools that spool large outputs * to disk and return a marker on the wire (e.g. claude's * `` for oversized Bash / MCP results). * * Per tool_result the proxy: * 1. `detect(wireResult)` — return truthy "pointer info" if this * result is a spooled-output marker, else null. * 2. `read(pointer)` — load the underlying file. The result is * handed to the middleware chain so middleware sees the full * content as if it had been inline. * 3. `write(pointer, finalResult)` — persist any middleware * mutation back to disk. The agent's next `Read` of that path * sees the mutated content. * 4. `rewriteWire(pointer, original)` — produce the wire-side * tool_result the LLM should see. Called after `write`, so the * file on disk reflects any middleware mutation by this point. * Implementations typically rebuild the inline preview from the * post-write file content (so unredacted content doesn't leak * and the preview matches what the LLM would see if it Reads * the path) while preserving the rest of the pointer envelope — * replay/record stay byte-stable in shape. * * Adapter-internal: opencode shares this provider but doesn't have * a pointer convention, so it just doesn't pass a dereferencer. */ dereferencer?: { detect: (real: ToolResult) => unknown | null | Promise; read: (pointer: unknown) => Promise; write: (pointer: unknown, result: ToolResult) => Promise; rewriteWire: (pointer: unknown, original: ToolResult) => Promise | ToolResult; }; }; /** * Walk a `Recording`'s captured request bodies and pull out every * `tool_result` block keyed by `tool_use_id`. The map is what the * proxy uses to auto-short-circuit prefix tool calls during replay. * * Only the Anthropic wire format — `messages[].content[]` containing * `{type:'tool_result', tool_use_id, content, is_error}`. */ export declare function extractAnthropicReplayResults(recording: { exchanges: Array<{ request: string; response: string; }>; }): Map; export type { AgentProxy }; export declare function startAnthropicProxy(opts: StartAnthropicProxyOpts): Promise; export { stringifyToolResult }; //# sourceMappingURL=anthropic-proxy.d.ts.map