UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

179 lines (178 loc) • 7.47 kB
/** * anthropicMessagesWire — the Anthropic Messages body, spoken by more than one * transport. * * Pattern: shared pure helpers (no I/O). Role: outer ring, below the adapters. * * Two adapters put the SAME body on different wires: * - `browserAnthropic()` — POST api.anthropic.com/v1/messages, with a `model` * field and an `anthropic-version` header; * - `invokeModelGateway()` — POST {gateway}/model/{id}/invoke, with NO `model` * field (the id is in the path) and `anthropic_version` in the body. * * Everything else — `system` as a top-level field, content blocks, `tool_use` / * `tool_result`, thinking blocks, cache markers, the SSE event sequence — is one * shape, so it has ONE owner here instead of a copy per adapter. The functions * were moved out of `BrowserAnthropicProvider.ts` unchanged; its tests pin them. * * Deliberately NOT here: the transport (URL, headers, fetch, status handling) * and the SSE framing (the browser adapter reads `event:` lines; a gateway that * decoded AWS's binary framing may send bare `data:` lines). Each adapter turns * its bytes into {@link AnthropicStreamEvent}s and hands them to * {@link assembleAnthropicStream}. */ import type { LLMChunk, LLMRequest, LLMResponse } from '../../provider-contract/types.js'; import { type AnthropicThinkingParam } from './anthropicThinkingWire.js'; import type { WireToolManifest } from './wireManifest.js'; import type { ThinkingMode } from '../../thinking/types.js'; import type { MessageReplay, ThinkingReplayOmission } from '../../thinking/replay.js'; export { toAnthropicMessages, toAnthropicTool } from './anthropicMessageProjection.js'; /** The Messages body WITHOUT `model` — each transport adds its own framing. */ export interface AnthropicMessagesBody { max_tokens: number; messages: AnthropicMessageParam[]; system?: string; tools?: AnthropicTool[]; temperature?: number; stop_sequences?: string[]; thinking?: AnthropicThinkingParam; tool_choice?: { type: 'auto'; disable_parallel_tool_use: true; } | { type: 'tool'; name: string; }; output_config?: { format: { type: 'json_schema'; schema: Record<string, unknown>; }; }; } export interface AnthropicMessageParam { role: 'user' | 'assistant'; content: string | AnthropicContentBlock[]; } export type AnthropicContentBlock = { type: 'text'; text: string; citations?: null | readonly []; } | { type: 'tool_use'; id: string; name: string; input: Record<string, unknown>; caller?: { readonly type: 'direct'; }; toolset_name?: null; } | { type: 'tool_result'; tool_use_id: string; content: string; is_error?: boolean; } | { type: 'thinking'; thinking: string; signature?: string; } | { type: 'redacted_thinking'; data: string; }; export interface AnthropicTool { name: string; description: string; input_schema: Record<string, unknown>; } export interface AnthropicMessage { id: string; model: string; role: 'assistant'; content: AnthropicContentBlock[]; stop_reason: string; usage: { input_tokens: number; output_tokens: number; cache_read_input_tokens?: number; cache_creation_input_tokens?: number; }; } /** One decoded stream event: its name (the SSE `event:` line, or the JSON's `type`) and its data. */ export interface AnthropicStreamEvent { readonly event: string; readonly data: unknown; } /** How one adapter wants its Messages body built. */ export interface MessagesBodyOptions { /** The model the request goes to, the adapter's shorthand already resolved. */ readonly model: string; /** * The adapter's ONE mode function — the very one it declares as * `LLMProvider.thinkingMode` (`thinkingModeWith`, anthropicThinkingWire.ts) — * so the shape sent is the shape the agent checked. */ readonly thinkingMode: (model: string) => ThinkingMode; /** The adapter's name, for the refusals the thinking plan raises. */ readonly provider: string; /** `max_tokens` when the request sets none. */ readonly maxTokensDefault: number; /** `false` caps a reply at one tool call. */ readonly parallelToolCalls?: boolean; /** * The adapter's declared `supportsNativeOutputSchema` — whether `model` * takes `output_config.format`. ABSENT means the adapter does not carry the * format at all (`invokeModelGateway()` in this release), so a request that * asks for one is refused by name rather than sent without it. */ readonly nativeOutputSchema?: (model: string) => boolean; } /** * Build the Messages body (minus `model`) from a framework request. * * ONE owner: `anthropic()`, `browserAnthropic()` and `invokeModelGateway()` * all build their body here, so a rule about the body — like which thinking * shape a model takes — is written once. * * @throws UnsupportedThinkingError before anything is sent, when the request * asks to think in a way the model cannot take (see `anthropicThinkingPlan`). */ export declare function buildMessagesBody(req: LLMRequest, options: MessagesBodyOptions): AnthropicMessagesBody; export declare function fromAnthropicResponse(message: AnthropicMessage, binding?: MessageReplay['binding'], argumentFailures?: ReadonlyMap<number, NonNullable<LLMResponse['toolCalls'][number]['argumentsFailure']>>): LLMResponse; export declare function normalizeStopReason(raw: string): string; /** A tool call whose streamed argument JSON did not parse. */ export interface MalformedToolArgs { readonly id: string; readonly name: string; readonly index: number; /** The accumulated `partial_json` text. */ readonly raw: string; } export interface StreamAssemblyOptions { /** Capture binding from the actual sent body once the response identity is known. */ readonly bindingFactory?: (id: string) => MessageReplay['binding']; /** Omissions already reported by the leaf before this attempt was sent. */ readonly replayOmissions?: readonly ThinkingReplayOmission[]; /** Read off the request body that was sent — see wireManifest.ts. */ readonly wireManifest: WireToolManifest; /** * What to do with tool arguments that do not parse: return the args to use, * or throw. A returned placeholder is always accompanied by finite failure * evidence, so it cannot authorize execution. The gateway keeps its stronger * whole-response refusal. */ readonly onMalformedToolArgs: (call: MalformedToolArgs) => Record<string, unknown>; } /** * Anthropic's streaming event sequence → `LLMChunk`s. * * message_start (input usage) → content_block_start (tool_use id + name, or a * thinking block) → content_block_delta (`text_delta` yielded as a chunk; * `input_json_delta` / `thinking_delta` / `signature_delta` accumulated per * content-block INDEX, since blocks can interleave) → content_block_stop (the * block is finished and parsed) → message_delta (stop reason, output usage). * * The TERMINAL chunk (`done: true`) carries the assembled `response` — always, * because consumers read `chunk.response` for tool calls and usage. */ export declare function assembleAnthropicStream(events: AsyncIterable<AnthropicStreamEvent>, options: StreamAssemblyOptions): AsyncIterable<LLMChunk>;