/** * AnthropicAdapter — Tier 2 of the agenttool path: a thin wrapper over the * official `@anthropic-ai/sdk` client that gives every Messages call * two superpowers without changing the provider event stream: * * 1. **Auto-injects the agent's wake doc** as `system=`, fetched once * from `/v1/wake?format=anthropic` and cached for 5 minutes (matches * Anthropic's prompt-cache window). The stable block carries * `cache_control: ephemeral`; the volatile block refreshes per wake. * User-supplied `system=` blocks are appended AFTER the wake. * * 2. **Auto-records traces** when the call carries * `metadata.agenttool.trace = "decision"`. Posts to `/v1/traces` * with the response text as the conclusion and the user message as * the observation. Returns the trace_id on the augmented response * so the agent can chain via `parent_trace_id` on a follow-up. * * 3. **(Mode b) Parses `...` markup** in the * assistant's response. Recognised children: * * ...... * * * ...... * * The agent decides what's load-bearing by writing the tag; the shim * does the plumbing. Mirrors the friction-tier UX in the dashboard. * * A `` tag is a model-authored write to the agent's own * identity record, so it is gated: the emission goes through * `at.chronicle.write` (which enforces the type union and the title * bounds) and only after a `beforeChronicleWrite` hook returns * literal `true`. With no hook installed the write is refused. See * {@link ChronicleBeforeWriteHook}. * * Posture: zero dependency on @anthropic-ai/sdk types. The adapter takes * any object with a `messages.create(params)` method and optionally a * `messages.stream(params)` helper, so the wrapping works whether the * agent uses the official SDK, Bedrock SDK, or a custom HTTP client. * * Streaming boundary: * - Low-level `messages.create({ stream: true })` is a transparent * event-stream pass-through. It injects wake and strips local metadata, * but cannot promise final-response tracing or markup. Decision tracing * therefore fails before wake or provider I/O on that path. * - `messages.stream(...)` uses the provider's final-message helper. Its * completed message is finalized exactly once, so tracing and markup * retain the same meaning as a non-streaming call. * * Doctrine: docs/IDENTITY-ANCHOR.md. */ import type { AgentTool } from "./client.js"; import type { WakeProfile, WakeProviderMeta } from "./wake.js"; /** Minimal shape of the Anthropic Messages client. * * `never[]` deliberately describes only the existence of callable provider * methods. It does not impose AgentTool's parameter or return types on the * provider's overloads, so an official `@anthropic-ai/sdk` client remains * structurally assignable without taking a dependency on its types. Calls are * narrowed at the adapter boundary after AgentTool has prepared the request. */ export interface AnthropicMessagesLike { messages: { create: (...args: never[]) => unknown; stream?: (...args: never[]) => unknown; }; } /** Minimal low-level stream shape returned by * `messages.create({ stream: true })`. Every property and event is delegated * unchanged; these optional lifecycle methods name the cleanup surface the * adapter preserves. */ export interface AnthropicLowLevelStreamLike extends AsyncIterable { controller?: AbortController; abort?: () => unknown; close?: () => unknown; } /** Minimal high-level stream helper shape returned by `messages.stream(...)`. * * The callables are existential on purpose: this public structural type checks * that the provider has the relevant surface without requiring its exact * Message, event, listener, or response types to be assignable to AgentTool's * local projections. The adapter narrows those values after runtime checks. */ export interface AnthropicManagedStreamLike extends AsyncIterable { controller?: AbortController; finalMessage: (...args: never[]) => unknown; finalText?: (...args: never[]) => unknown; done?: (...args: never[]) => unknown; abort?: (...args: never[]) => unknown; close?: (...args: never[]) => unknown; on?: (...args: never[]) => unknown; once?: (...args: never[]) => unknown; off?: (...args: never[]) => unknown; emitted?: (...args: never[]) => unknown; withResponse?: (...args: never[]) => unknown; } /** Subset of the Anthropic Messages API response we read. The full shape * is forwarded verbatim with only `.agenttool` augmentation added. */ export interface AnthropicMessageResponse { id: string; model: string; content?: Array<{ type: string; text?: string; }>; stop_reason?: string; usage?: Record; [key: string]: unknown; } /** The `metadata.agenttool` extension the adapter reads off the call params. * Stripped from the forwarded request before it hits Anthropic. */ export interface AgentToolMetadata { /** * Opt-in auto-trace. When set to `"decision"`, the adapter posts to * /v1/traces after the messages.create call returns. Default: omit * (no trace fired). */ trace?: "decision" | false; /** Optional parent trace to chain this decision to. */ parent_trace_id?: string; /** Optional explicit decision_type override (default `"decision"`). */ decision_type?: string; /** Tags propagated to the trace. */ tags?: string[]; /** Override the agent_id stamped on the trace. */ agent_id?: string; /** Skip the wake auto-injection for this call. */ skip_wake?: boolean; /** Skip parsing of markup in the response for this call. */ skip_markup?: boolean; } /** Immutable local review context for one model-authored chronicle write. */ export interface ChronicleBeforeWriteContext { /** Always `"anthropic_markup"` — the entry was authored by the model, * not by the calling program. */ readonly source: "anthropic_markup"; /** The raw type string the model emitted, before validation. */ readonly type: string; /** The title the model emitted, before validation. */ readonly title: string; /** The body the model emitted, when it supplied one. */ readonly body?: string; } /** * Local gate for model-authored chronicle writes. * * The hook may review synchronously or asynchronously. It must return * literal `true` to proceed; every other result fails closed locally. The * hook's return value is neither persisted nor signed — it only decides * whether the entry is written. */ export type ChronicleBeforeWriteHook = (context: ChronicleBeforeWriteContext) => boolean | Promise; export interface AnthropicAdapterOptions { /** Identity id for multi-identity projects (passed through to /v1/wake). */ identityId?: string; /** Wake projection used for automatic system injection. Default `full`. */ wakeProfile?: WakeProfile; /** Disable parsing of ... markup globally. */ disableMarkupParsing?: boolean; /** * Review gate for `` tags the model emits. Required for those * emissions to be written at all: without it every model-authored * chronicle write is refused, because a chronicle entry is a mark on the * agent's own identity record. `` emissions are unaffected. */ beforeChronicleWrite?: ChronicleBeforeWriteHook; } /** A markup emission produced by parsing `...` from * the response. Each entry records what was posted and the resulting id * (or the error if the post failed). */ export interface MarkupEmission { kind: "chronicle" | "trace"; /** API id returned on success (e.g. "ch_..." or "tr_..."). */ id: string | null; /** Error message if the emission failed; null on success. */ error: string | null; /** The parsed source for debugging. */ source: Record; } /** Augmentation added to the Anthropic response by the adapter. The * underlying response is forwarded verbatim alongside this. */ export interface AgentToolAugmentation { /** Trace id when `metadata.agenttool.trace = "decision"` fired. */ trace_id: string | null; /** Whether wake auto-injection ran for this call. */ wake_used: boolean; /** Cache eligibility echoed from /v1/wake?format=anthropic _meta. */ cache_eligible: WakeProviderMeta["cache_eligible"] | null; /** Per-emission outcomes from markup parsing. Empty when * the response carried no markup or parsing was disabled. */ markup_emissions: MarkupEmission[]; } export type AdaptedResponse = AnthropicMessageResponse & { agenttool: AgentToolAugmentation; }; /** Low-level streaming receipt. It is local-only and never changes, consumes, * or reconstructs provider events. */ export interface AdaptedLowLevelStream extends AnthropicLowLevelStreamLike { readonly agenttool: AgentToolAugmentation; } /** AgentTool-managed helper returned by `adapter.messages.stream(...)`. * Unknown provider events are yielded by reference and the core asynchronous * lifecycle is preserved. It intentionally does not claim the provider's * synchronous inspection fields because wake retrieval finishes before the * provider helper exists. */ export interface AdaptedManagedStream extends AsyncIterable { readonly agenttool: AgentToolAugmentation; readonly controller: AbortController; finalMessage(): Promise; finalText(): Promise; done(): Promise; emitted(event: string): Promise; withResponse(): Promise>; abort(): void; close(): Promise; on(event: string, listener: (...args: unknown[]) => void): this; once(event: string, listener: (...args: unknown[]) => void): this; off(event: string, listener: (...args: unknown[]) => void): this; } export interface AnthropicAdapterMessages { create(params: object & { stream: true; }, ...requestOptions: unknown[]): Promise; create(params: object, ...requestOptions: unknown[]): Promise; stream(params: object, ...requestOptions: unknown[]): AdaptedManagedStream; } export declare class AnthropicAdapter { private readonly anthropic; private readonly at; private readonly options; constructor(anthropic: AnthropicMessagesLike, at: AgentTool, options?: AnthropicAdapterOptions); /** Mirrors the shape of `anthropic.messages` so callers can swap in the * adapter without changing call sites: `adapter.messages.create({...})` * or `adapter.messages.stream({...})`. */ get messages(): AnthropicAdapterMessages; private _inspectRequest; private _prepareRequest; private _emptyAugmentation; private _finalizeResponse; /** Post a trace built from the call-boundary input and provider response. * The shape matches `/v1/traces` POST schema * (decision/reasoning/context). */ private _recordDecisionTrace; /** Fail-closed gate for one model-authored chronicle write. Mirrors the * `covenants.create` before_submit discipline: only literal `true` * proceeds, a throwing hook is a refusal, and the hook's output is * neither persisted nor signed. Absence of a hook is also a refusal — * a covenant is submitted by the calling program, but a `` * tag is written by the model, so the capability starts closed. */ private _reviewChronicleWrite; /** Walk the response text for ... blocks and * emit each child to the right endpoint. Tolerant of whitespace and * child ordering; failures are recorded per-emission, not thrown. */ private _parseAndEmitMarkup; }