/** * Unified LLM Provider Types * * Provider-agnostic interfaces for working with OpenAI, Anthropic (Claude), * and Google Gemini from HoloScript scenes and AI agents. * * @module @holoscript/llm-provider * @version 1.0.0 */ import type { RealtimeSessionConfig, RealtimeSession } from './realtime'; export type MessageRole = 'system' | 'user' | 'assistant'; /** * Content for a user message that's feeding tool results back to the model. * Caller emits one `tool_result` block per `tool_use` block in the prior * assistant response. Anthropic adapter recognizes this shape and forwards. */ export interface ToolResultBlock { type: 'tool_result'; tool_use_id: string; /** * Tool output to feed back to the model. Anthropic's API also accepts an * `Array<{type:'text',text:string} | {type:'image',source:...}>` here, but * this codebase deliberately narrows to `string` because every producer * (holoscript-agent/src/tools.ts: okResult / errResult) emits string * content. If/when an image-returning tool is added, widen here AND update * the display formatter at types.ts:~164 plus runner.ts SHA-extraction. * * Revisit triggers (do not speculate-widen — wait for one of these): * 1. A vision-shaped tool is added to MESH_TOOLS (e.g. `screenshot`, * `read_media_file`) — the new producer would emit Array content. * 2. Paper 20 (Learned Scene Composition) acceptance criteria require a * headless mesh agent to ingest rendered frames. * 3. Anthropic deprecates string-form `tool_result.content` and requires * the array shape on the wire. * * Until one of those triggers fires: text-only is intentional. Routing rule * (global CLAUDE.md): multimodal verification → Gemini in Antigravity, not * a headless HoloScript mesh agent. */ content: string; /** Optional: mark the tool result as an error so the model retries / reroutes. */ is_error?: boolean; } export interface LLMMessage { role: MessageRole; /** Either plain text (most messages) OR a structured content array. * - Assistant messages mid-tool-loop carry the assistant's prior * text + tool_use blocks so the model has its own turn in context. * - User messages mid-tool-loop carry tool_result blocks. * - Anthropic file references use provider-specific blocks so callers can * pass file_id references instead of base64 payloads. */ content: string | LLMContentBlock[]; } export interface LLMSystemMessage { role: 'system'; content: string; } export interface LLMUserMessage { role: 'user'; content: string; } export interface LLMAssistantMessage { role: 'assistant'; content: string; } /** Anthropic Messages API — `output_config.effort` (adaptive thinking guidance). */ export type AnthropicEffortLevel = 'low' | 'medium' | 'high' | 'xhigh' | 'max'; /** * `thinking` on the Anthropic Messages API. `adaptive` is the supported mode * on Opus 4.7; `enabled`+`budget_tokens` is for manual / legacy paths. */ export type AnthropicThinkingParam = { type: 'adaptive'; display?: 'summarized' | 'omitted'; } | { type: 'disabled'; } | { type: 'enabled'; budget_tokens: number; display?: 'summarized' | 'omitted'; }; export interface LLMCompletionRequest { /** The messages to send to the model */ messages: LLMMessage[]; /** Maximum tokens in the response */ maxTokens?: number; /** Temperature (0-2). Higher = more creative. Default: 0.7 */ temperature?: number; /** Top-P nucleus sampling. Default: 1 */ topP?: number; /** Stop sequences - model will stop generating before these tokens */ stop?: string[]; /** Whether to stream the response */ stream?: boolean; /** * Structured-output grammar, passed through verbatim on the OpenAI-compat * LOCAL path only (llama.cpp / HoloServe request field `grammar`): a GBNF * string for llama-server, or a registered grammar NAME for HoloServe's * sovereign constrained decoder (W.780 — e.g. "containment" | "deontic" | * "composition", advertised by its /health). Output is valid-by-construction; * cloud adapters ignore this field. */ grammar?: string; /** * HoloServe's opt-in consumer payload pipeline. Passed through only by the * local OpenAI-compatible adapter as request field `holo_pipeline`; cloud * adapters ignore it and the native Ollama path rejects it rather than * silently degrading to model generation. */ holoPipeline?: 'off' | 'broker-v1'; /** * Tools the model can call. When set, the response may contain `toolUses` * blocks that the caller must execute and re-feed via a follow-up request * containing assistantBlocks (the prior response) + tool_result messages. * Anthropic adapter passes these straight through to messages.stream. * * Accepts both generic JSONSchema-shaped tools (`ToolSpec`) and Anthropic * server-side tools like the advisor (`AnthropicAdvisorToolSpec`). When an * advisor tool is present, the Anthropic adapter injects the * `anthropic-beta: advisor-tool-2026-03-01` header. Adapters for other * providers strip provider-specific shapes (they fail the function-tool * contract). */ tools?: ToolSpecUnion[]; /** * Anthropic: passed to `messages.stream({ thinking })`. If omitted, Opus 4.6/4.7 * and Sonnet 4.5/4.6 get `{ type: 'adaptive', display: 'summarized' }` by default * unless you set `thinking: { type: 'disabled' }` to opt out of extended thinking. */ thinking?: AnthropicThinkingParam; /** * Anthropic: optional shorthand for `thinking.display` (merged when `thinking` * is present, or applied together with the adaptive default for supported models). */ thinkingDisplay?: 'summarized' | 'omitted'; /** * Anthropic: `output_config.effort` on the Messages API. `max` is only * meaningful on Opus family models; on other models the adapter may downgrade * to `high` to avoid 400s. */ effort?: AnthropicEffortLevel; /** * Provider-namespaced request extensions (segregated axis). Each adapter * validates only its own namespace and ignores others — same request * object can be re-routed to a fallback provider on retry without * stripping fields. See `ProviderExtensions`. * * Anthropic-specific fields above (`thinking`/`thinkingDisplay`/`effort`) * remain valid for backward-compat; `provider.anthropic.*` is the canonical * long-term home. Migration is brain-by-brain, not breaking. */ provider?: ProviderExtensions; } /** * Spec for a tool the model is allowed to call. Schema follows the Anthropic * tool-use shape (which also matches OpenAI function-calling JSONSchema). */ export interface ToolSpec { name: string; description: string; input_schema: { type: 'object'; properties: Record; required?: string[]; }; } /** * Anthropic-defined server-side advisor tool (beta * `advisor-tool-2026-03-01`). Pairs a frontier "advisor" model (e.g. Opus 4.7) * with a cheaper executor in long-horizon HoloScript agentic loops + generated * agents — executor delegates expensive reasoning to advisor, then resumes. * * Distinct shape from `ToolSpec` (no JSON schema — `input` is a free-form * advisor sub-prompt). Including ANY tool of this shape in * `LLMCompletionRequest.tools` causes the Anthropic adapter to inject the * `anthropic-beta: advisor-tool-2026-03-01` header. Other adapters MUST * ignore the entry (it is not a callable function in their tool surface). * * See `docs/LLM_CAPABILITIES.md` § Anthropic → Built-in * server-side tools. */ export interface AnthropicAdvisorToolSpec { /** Discriminator for the Anthropic advisor beta. */ type: 'advisor_20260301'; /** The advisor tool's name MUST be the literal `'advisor'` per Anthropic API. */ name: 'advisor'; /** * Model the advisor uses for sub-inference (e.g. `claude-opus-4-7`). * Caller decides which frontier model pairs with the executor. */ model: string; /** * Which caller surfaces are allowed to invoke the advisor. Default is * `['direct']` (the executor model calls advisor directly). Add * `'code_execution_20260120'` to let code-execution sandboxes call advisor * mid-script. Omit for direct-only. */ allowed_callers?: Array<'direct' | 'code_execution_20250825' | 'code_execution_20260120'>; } /** * Union of every tool shape the unified request accepts. Generic JSONSchema * tools (`ToolSpec`) pass through to every adapter. Provider-specific tool * shapes (currently only `AnthropicAdvisorToolSpec`) opt in to a beta header * on the matching adapter and are filtered out by non-matching adapters. * * Discriminate via the `type` field: any tool with `type: 'advisor_20260301'` * is the advisor; anything without `type` (or with a future provider-specific * discriminator) is a generic function tool. */ export type ToolSpecUnion = ToolSpec | AnthropicAdvisorToolSpec; /** Type guard for the Anthropic advisor tool. */ export declare function isAnthropicAdvisorTool(t: ToolSpecUnion): t is AnthropicAdvisorToolSpec; /** Generic JSONSchema function tool guard for non-Anthropic adapters. */ export declare function isToolSpec(t: ToolSpecUnion): t is ToolSpec; /** * Filter a heterogenous tools list down to generic `ToolSpec` entries — drops * provider-specific shapes (currently only Anthropic's advisor) that other * adapters can't dispatch. Use this at adapter boundaries for OpenAI, * Gemini, Ollama, etc. so callers can opt into the advisor on Anthropic * without breaking fallback-provider retry on the same `LLMCompletionRequest`. */ export declare function filterGenericTools(tools: ToolSpecUnion[] | undefined): ToolSpec[]; /** A tool call the model wants the caller to execute. */ export interface ToolUseBlock { type: 'tool_use'; id: string; name: string; input: Record; } /** A text block from an assistant response (separate from tool_use blocks). */ export interface TextBlock { type: 'text'; text: string; } export interface CacheControlEphemeral { type: 'ephemeral'; ttl?: '5m' | '1h'; } export interface AnthropicFileSource { type: 'file'; file_id: string; } export interface AnthropicDocumentFileBlock { type: 'document'; source: AnthropicFileSource; title?: string; context?: string; citations?: { enabled: boolean; }; cache_control?: CacheControlEphemeral; } export interface AnthropicImageFileBlock { type: 'image'; source: AnthropicFileSource; cache_control?: CacheControlEphemeral; } export interface AnthropicContainerUploadBlock { type: 'container_upload'; file_id: string; cache_control?: CacheControlEphemeral; } export type AnthropicFileContentBlock = AnthropicDocumentFileBlock | AnthropicImageFileBlock | AnthropicContainerUploadBlock; export type AnthropicFileContentBlockType = AnthropicFileContentBlock['type']; export interface AnthropicFileContentBlockOptions { title?: string; context?: string; citations?: { enabled: boolean; }; cacheControl?: CacheControlEphemeral; } /** * Helper for referencing an Anthropic Files API `file_id` in messages. * * `document` and `image` blocks let Claude read uploaded PDFs/text/images. * `container_upload` mounts arbitrary uploaded files for code execution. */ export declare function anthropicFileContentBlock(fileId: string, type: AnthropicFileContentBlockType, options?: AnthropicFileContentBlockOptions): AnthropicFileContentBlock; export type LLMContentBlock = TextBlock | ToolUseBlock | ToolResultBlock | AnthropicFileContentBlock; export type AssistantContentBlock = TextBlock | ToolUseBlock; /** * Provider-agnostic stream chunk emitted by `ILLMProvider.streamCompletion`. * * Adapters MAP their native streaming surface (Anthropic SSE events, Ollama * NDJSON, OpenAI chat-completion stream, etc.) onto this discriminated union * so callers consume one shape regardless of provider. The chunk vocabulary * is intentionally minimal — text deltas, tool-use lifecycle, and a final * `message_stop` carrying the same `finishReason` + usage that * `LLMCompletionResponse` carries. Thinking/refusal/error chunks can be added * later without breaking existing consumers. * * Reading the stream: * for await (const chunk of provider.streamCompletion(req)) { * if (chunk.type === 'text_delta') sendToClient(chunk.text); * if (chunk.type === 'tool_use_end') queueTool(chunk.id, chunk.name, chunk.input); * if (chunk.type === 'message_stop') finalize(chunk.finishReason, chunk.usage); * } * * Tool-input deltas are emitted as PARTIAL JSON fragments (the exact text the * provider emitted, before any parse). The terminating `tool_use_end` chunk * carries the FULLY PARSED `input` object — callers should use `tool_use_end` * to dispatch, and use `tool_use_input_delta` only for live UX (e.g. showing * partial JSON in a spinner). */ export type LLMStreamChunk = { type: 'text_delta'; text: string; } | { type: 'tool_use_start'; id: string; name: string; } | { type: 'tool_use_input_delta'; id: string; /** Provider-emitted partial JSON fragment. NOT cumulative — each delta * is the new fragment to append to prior fragments for the same id. */ partialJson: string; } | { type: 'tool_use_end'; id: string; /** Fully parsed tool input. Empty object if the model emitted no input. */ input: Record; } | { type: 'message_stop'; finishReason: LLMCompletionResponse['finishReason']; usage: TokenUsage; model: string; /** Provider-assigned request-id (see LLMCompletionResponse.requestId). */ requestId?: string; /** Raw response headers (see LLMCompletionResponse.responseHeaders). */ responseHeaders?: Record; }; /** * Coerce LLMMessage content to a plain string for adapters/surfaces that * intentionally flatten tool-use (chat-completion compatibility, gemini, * local-llm, bitnet, mock). Non-text blocks are flattened to a JSON-ish * summary so the message still carries SOME signal about what the model said. * Native tool-capable surfaces (Anthropic, OpenAI Responses) should pass * structured blocks through unchanged. */ export declare function messageContentAsString(content: LLMMessage['content']): string; export interface LLMCompletionResponse { /** The generated text content (concatenated text blocks only) */ content: string; /** Token usage statistics */ usage: TokenUsage; /** Which model produced this response */ model: string; /** Exact provider-reported model identity, or null when the provider omitted it. */ reportedModel?: string | null; /** The provider that handled this request */ provider: LLMProviderName; /** Finish reason — `tool_use` indicates toolUses must be executed and * re-fed via a follow-up request to continue the loop. * - `refusal` — model declined for safety reasons (Claude 4+ stop_reason * `refusal`); response body may not match the requested format. Caller * must NOT retry the same prompt verbatim. * - `context_window_exceeded` — model hit the CONTEXT WINDOW limit, not * the requested max_tokens (Claude 4.5+ stop_reason * `model_context_window_exceeded`). Caller should compact / split the * conversation, not just bump max_tokens. */ finishReason: 'stop' | 'length' | 'content_filter' | 'error' | 'tool_use' | 'refusal' | 'context_window_exceeded'; /** * Tool-use blocks the model wants the caller to execute. Empty when the * model didn't request tools. Caller should run each tool, then send a * follow-up request containing this assistant turn's full content blocks * (preserved in `assistantBlocks` for round-trip fidelity) plus a user * message with `tool_result` blocks for each tool use. */ toolUses?: ToolUseBlock[]; /** Full assistant content blocks (text + tool_use), in order, for the * follow-up tool_result message construction. */ assistantBlocks?: AssistantContentBlock[]; /** * Provider-assigned request identifier for observability, debugging, and * support escalations. * * - Anthropic: the `request-id` response header (via * `MessageStream.request_id` or `APIPromise.withResponse()`). * - OpenAI: the `x-request-id` response header. * - Other providers: the closest equivalent header/field, or `undefined` * if the provider doesn't expose one. * * Without this, debugging a failing request requires reconstructing the * timeline from logs alone — the provider's own support team needs the * request-id to look up server-side traces. Capturing it here makes every * LLMCompletionResponse self-describing for incident response. */ requestId?: string; /** * Raw response headers from the provider, keyed by header name. * * Captured alongside `requestId` so callers can extract rate-limit headers, * retry-after, provider-specific metadata, etc. without parsing the `raw` * response object. Only populated by adapters that expose it (currently * Anthropic via `MessageStream.response`). Keys are lowercased. * * Deliberately `Record` rather than `Headers` — this is * a plain-data snapshot, not a live browser/Header object. Adapters * convert at capture time. */ responseHeaders?: Record; /** * Inline moderation result from the provider. * * Populated by the OpenAI adapter when `req.provider.openai.moderation` was * set on the request — the response carries the moderation verdict alongside * the generation output in a single round-trip. * * Anthropic analog: built-in safety layer surfaces as `finishReason: * "content_filter"` (mapped from `stop_reason: "refusal"`). No separate * result object is emitted; `moderationResult` will be `undefined` on * Anthropic responses. Callers that need a unified HoloDoor gate should * check both `finishReason === "content_filter"` (Anthropic) and * `moderationResult?.flagged === true` (OpenAI) at the call site. * * See `InlineModerationResult` for the field contract. */ moderationResult?: InlineModerationResult; /** Raw response from the provider (for debugging) */ raw?: unknown; } export interface TokenUsage { /** Tokens in the prompt/input */ promptTokens: number; /** Tokens in the completion/output */ completionTokens: number; /** Total tokens used */ totalTokens: number; /** False when compatibility zeroes are present because the provider omitted usage. */ reported?: boolean; } /** Per-call transport controls that must reach the underlying provider request. */ export interface LLMRequestOptions { signal?: AbortSignal; } export interface LLMFileUploadRequest { /** * Provider-native upload object. Anthropic SDK accepts Node ReadStream, * File/Blob-like values, or other Uploadable shapes. Kept `unknown` here so * the provider-neutral type does not force every adapter to depend on one * vendor SDK's upload type. */ file: unknown; /** Provider-namespaced upload extensions. */ provider?: ProviderExtensions; } export interface LLMFileMetadata { id: string; type: 'file'; filename: string; mimeType: string; sizeBytes: number; createdAt: string; downloadable?: boolean; raw?: unknown; } export interface HoloScriptGenerationRequest { /** Natural language description of the scene */ prompt: string; /** Optional system context for the model */ systemPrompt?: string; /** Maximum scene complexity (object count hint) */ maxObjects?: number; /** Target export format */ targetFormat?: 'holo' | 'hsplus' | 'hs'; /** Temperature override */ temperature?: number; } export interface HoloScriptGenerationResponse { /** The generated HoloScript code */ code: string; /** Whether the generated code passed validation */ valid: boolean; /** Validation errors if any */ errors: string[]; /** The provider that generated this */ provider: LLMProviderName; /** Token usage */ usage: TokenUsage; /** Detected traits in generated code */ detectedTraits: string[]; } export type LLMProviderName = 'openai' | 'anthropic' | 'gemini' | 'mock' | 'bitnet' | 'local-llm' | 'fleet' | 'openrouter' | 'xai' | 'brittney-cloud' | 'sovereign'; export interface LLMProviderConfig { /** API key for authentication */ apiKey: string; /** Base URL override (for proxies or self-hosted endpoints) */ baseURL?: string; /** Request timeout in milliseconds. Default: 30000 */ timeoutMs?: number; /** Maximum retry attempts on rate limits. Default: 3 */ maxRetries?: number; /** Default model to use if not specified per-request */ defaultModel?: string; } export type OpenAIApiSurface = 'responses' | 'chat-completions'; export type OpenAIReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'; export interface OpenAIProviderConfig extends LLMProviderConfig { /** OpenAI organization ID */ organization?: string; /** * OpenAI API surface. Defaults to `responses` so current models, hosted * tools, and function-call output items all use the modern wire contract. * Use `chat-completions` only for older OpenAI-compatible proxies. */ apiSurface?: OpenAIApiSurface; /** * Responses API reasoning effort. Leave unset for provider/model default. * Useful for GPT-5.x calls where HoloScript wants explicit cost/latency * control at adapter construction time. */ reasoningEffort?: OpenAIReasoningEffort; /** * Responses API persistence flag. Leave undefined to inherit OpenAI's * default for the account/project; set false for stateless private calls. */ store?: boolean; /** * Whether OpenAI Responses may emit multiple tool calls in one turn. * Defaults true to match agentic HoloScript workflows. */ parallelToolCalls?: boolean; } export interface AnthropicProviderConfig extends LLMProviderConfig { /** Anthropic API version header. Default: '2023-06-01' */ apiVersion?: string; /** * Opt in to prompt caching on the system prompt + tools. * * When `true`, the adapter sends `system` in array form with a * `cache_control: {type: "ephemeral"}` breakpoint on the last system * block. Render order is `tools → system → messages`, so the breakpoint * caches BOTH tools AND system together — exactly the pattern an agent * runner needs (stable brain composition + stable tool list across * many ticks). * * Cost shape: cache writes cost ~1.25× input on the FIRST request with * a given prefix; subsequent reads cost ~0.1× input. For a stable * `system` ≥ the model's minimum cacheable prefix (Opus 4.7 / Opus 4.6 * / Haiku 4.5: 4096 tokens; Sonnet 4.6: 2048 tokens; Opus 4.8: 1024 tokens), break-even is * 2 requests with 5-min TTL. Below the minimum the request is sent * unchanged — no error, just no cache benefit. * * Default: `true`. Caching is the right default for almost every Claude * API call: agent runners get the full ~10× per-tick reduction; code-gen * paths with stable system prompts get the same; one-off calls below the * minimum prefix get neither benefit nor extra cost. Set to `false` only * when you have measured evidence that a particular caller's prompts are * pathological for caching (e.g. a hot path with varied above-minimum * prefixes that never repeat — paying 1.25× writes with zero reads). */ enablePromptCaching?: boolean; /** * Maximum total cache breakpoints per request (Anthropic API limit: 4). * * One breakpoint is always used for the system+tools prefix when * `enablePromptCaching` is true. The remaining budget is distributed * across message turns (assistant-turn boundaries), working backwards * from the most recent turn. This gives agent tool-loops (which can hit * 30+ iterations) intermediate cache hits within the 5-min TTL window, * preventing the "cache miss after ~15 blocks" cliff. * * Default: 4 (Anthropic's hard limit). Set lower (e.g. 2) to reserve * cache budget for the system prefix only, or in pathological cases * where mid-turn breakpoints add cost without reuse. */ maxCacheBreakpoints?: number; } export interface GeminiProviderConfig extends LLMProviderConfig { /** Google Cloud project ID (for Vertex AI) */ projectId?: string; /** Google Cloud location (for Vertex AI). Default: 'us-central1' */ location?: string; } /** * Config for the real bitnet.cpp inference server. * No API key required — the server runs locally. */ export interface BitNetProviderConfig extends Omit { /** API key — unused for local servers, defaults to empty string */ apiKey?: string; /** * Base URL of the bitnet.cpp server. * Default: http://localhost:8080 */ baseURL?: string; /** BitNet model ID (HuggingFace format). Default: 'microsoft/bitnet-b1.58-2B-4T' */ model?: string; } /** * Config for a generic local OpenAI-compatible inference server. * Works with llama.cpp, Ollama, LM Studio, or any compatible server. * No API key required — the server runs locally. */ export interface LocalLLMProviderConfig extends Omit { /** API key — unused for local servers, defaults to empty string */ apiKey?: string; /** * Base URL of the local LLM server. * Default: http://localhost:8080 */ baseURL?: string; /** Model name to send in requests. Default: 'mistral-7b-instruct' */ model?: string; } /** * Config for the OpenRouter provider. * OpenRouter is an OpenAI-compatible API that routes to 200+ models. * The required HTTP-Referer and X-Title headers are set for attribution; * callers can override via referer/title config. */ export interface OpenRouterProviderConfig extends LLMProviderConfig { /** * HTTP-Referer header for attribution. * OpenRouter requires this. Default: 'https://holoscript.net' */ referer?: string; /** * X-Title header for attribution. * OpenRouter requires this. Default: 'HoloScript' */ title?: string; } /** * Config for the xAI (Grok) provider. * xAI provides an OpenAI-compatible API at https://api.x.ai/v1. */ export interface XAIProviderConfig extends LLMProviderConfig { } /** * Config for the Brittney Cloud provider. * Connects to HoloScript's first-party cloud inference gateway. */ export interface BrittneyCloudProviderConfig extends LLMProviderConfig { /** * Inference tier. 'pro' routes to Kimi K2.5 when available; * 'standard' uses the preferred available provider. * Default: 'standard'. */ tier?: 'standard' | 'pro'; } /** * Unified interface that all LLM provider adapters must implement. */ export interface ILLMProvider { /** The name of this provider */ readonly name: LLMProviderName; /** Available models for this provider */ readonly models: readonly string[]; /** Default model for HoloScript generation */ readonly defaultHoloScriptModel: string; /** * Capability manifest — drives capability-aware routing in the supervisor. * Adapters override `BaseLLMAdapter`'s `DEFAULT_CAPABILITIES` with their * actual declarations. Brain compositions declare `requires` / `prefers` * / `avoids` over capability keys; the router matches at session start. */ readonly capabilities: Capabilities; /** * Optional escape hatch — return the underlying typed SDK client for * advanced cases not covered by the universal/segregated split. Calling * `.native()` is allowed but flags routing-debt: a knob the abstraction * is missing. Each adapter narrows the return type via its own typed * override (e.g. AnthropicAdapter.native(): Anthropic). */ native?(): unknown; /** * Send a completion request to the provider. Adapters wrap the underlying * SDK/fetch call in `withRetry` (BaseLLMAdapter) so transient errors * (429, 5xx, network) retry with exponential backoff and Retry-After * honor before throwing. */ complete(request: LLMCompletionRequest, model?: string, options?: LLMRequestOptions): Promise; /** * Generate HoloScript code from a natural language description. Validates * the model output's structure (balanced braces, recognized object types) * and returns the validation result on the response. Transient-error retry * is handled inside `complete()` — this method calls it once and surfaces * any final error. */ generateHoloScript(request: HoloScriptGenerationRequest): Promise; /** * Check if the provider is correctly configured and reachable. */ healthCheck(): Promise<{ ok: boolean; latencyMs: number; error?: string; }>; /** * Stream a completion as an async iterable of provider-agnostic chunks * (`LLMStreamChunk`). Adapters that support native streaming (Anthropic * SSE, Ollama NDJSON, OpenAI chat-completion stream) translate native * events to this shape; adapters that don't yet have a streaming impl * fall back to the BaseLLMAdapter default (call `complete()`, then * yield the full text + tool-use blocks as a synthesized batch). * * Stream MUST end with exactly one `message_stop` chunk. Callers can * trust this and finalize state on `message_stop`. * * Adapters do NOT wrap streamCompletion in `withRetry` — partial-text * retries would re-emit prefix tokens and corrupt downstream state. * Pre-flight failures (auth, 429, request validation) throw before the * first chunk; mid-stream failures yield a final `message_stop` with * `finishReason: 'error'` and the partial state observed so far. */ streamCompletion(request: LLMCompletionRequest, model?: string): AsyncIterable; /** * Upload a reusable file reference on providers that support a Files API. * Adapters without native support inherit BaseLLMAdapter's explicit * unsupported-provider error. */ uploadFile(request: LLMFileUploadRequest): Promise; /** * Open a bidirectional realtime voice session. OPTIONAL — only providers * whose manifest declares `capabilities.realtimeVoice === true` implement it; * the router refuses to route a realtime brain to a provider that doesn't. * Adapters without support inherit BaseLLMAdapter's explicit * unsupported-provider throw (mirrors `uploadFile`). This is a SEPARATE * transport from `complete()` / `streamCompletion()` — it never touches the * chat path. Kept optional (like `native?`) so existing adapters compile * unchanged. Narrow with `supportsRealtime()` before calling. */ openRealtimeSession?(config: RealtimeSessionConfig): Promise; } export interface LLMProviderRegistry { openai?: ILLMProvider; anthropic?: ILLMProvider; gemini?: ILLMProvider; bitnet?: ILLMProvider; 'local-llm'?: ILLMProvider; fleet?: ILLMProvider; openrouter?: ILLMProvider; xai?: ILLMProvider; 'brittney-cloud'?: ILLMProvider; } export interface ProviderSelectionStrategy { /** Primary provider to use */ primary: LLMProviderName; /** Fallback provider if primary fails */ fallback?: LLMProviderName; /** Cost optimization: prefer cheapest available provider */ optimizeForCost?: boolean; /** Speed optimization: prefer fastest responding provider */ optimizeForSpeed?: boolean; } export declare class LLMProviderError extends Error { readonly provider: LLMProviderName; readonly statusCode?: number | undefined; readonly retryable: boolean; constructor(message: string, provider: LLMProviderName, statusCode?: number | undefined, retryable?: boolean); } export declare class LLMRateLimitError extends LLMProviderError { readonly retryAfterMs?: number | undefined; constructor(provider: LLMProviderName, retryAfterMs?: number | undefined); } export declare class LLMAuthenticationError extends LLMProviderError { constructor(provider: LLMProviderName); } export declare class LLMContextLengthError extends LLMProviderError { readonly tokenCount: number; constructor(provider: LLMProviderName, tokenCount: number); } export declare class LLMCreditExhaustedError extends LLMProviderError { constructor(provider: LLMProviderName); } /** * Capability declaration for a provider. Drives capability-aware routing: * brain compositions declare `requires` / `prefers` / `avoids` over capability * keys; the router matches against each provider's manifest at session start. * * Mirrors the cross-provider matrix in * `docs/LLM_CAPABILITIES.md`. Add new fields here when the doc * grows a new row, and update each adapter's overridden manifest. * * Universal+segregated principle (founder ruling 2026-05-06): this is the * UNIVERSAL axis (the swap contract). Provider-specific superpowers go on * the SEGREGATED axis (`req.provider..*` — see `ProviderExtensions`). * Both first-class. Never flatten one into the other. */ export interface Capabilities { /** Maximum input context window in tokens (0 = unknown / per-model). */ contextWindow: number; /** Maximum output tokens per response (0 = unknown / per-model). */ maxOutput: number; /** Per-million-token pricing in USD. Omit for $0 / variable / unknown. */ costPerMillion?: { input: number; output: number; }; streaming: boolean; tools: boolean; /** Programmatic function/tool calling surface (provider can return structured tool calls). */ programmaticToolCalling?: boolean; vision: boolean; /** >1568px long-edge images (e.g. Anthropic Opus 4.7 2576px). */ highResVision?: boolean; videoInput?: boolean; audioInput?: boolean; audioOutput?: boolean; /** Streaming TTS/audio chunks; distinct from normal text-token streaming. */ streamingSpeechGeneration?: boolean; imageGeneration?: boolean; /** Provider-family video generation, separate from normal text completion. */ videoGeneration?: boolean; /** Provider-family video editing or refinement, separate from normal text completion. */ videoEditing?: boolean; /** Still-image-to-video animation, separate from static image generation. */ imageAnimation?: boolean; /** Conversational media refinement loop, e.g. iterative video/image edits. */ conversationalMediaEditing?: boolean; /** Provider-visible reasoning artifacts/summaries; raw private CoT is not assumed. */ visibleReasoning?: boolean; /** Adjustable effort level (low/medium/high/xhigh/max). */ adjustableEffort?: boolean; /** Real-time web search (xAI Live Search, Gemini Grounding, OpenAI web tool). */ liveWebSearch?: boolean; /** Shell tool support; may be hosted or caller-executed depending on provider/runtime. */ hostedShell?: boolean; /** Server-side code execution sandbox. */ codeExecutionSandbox?: boolean; /** * GUI-automation surface: the model can drive a computer via screenshots + * click/type/scroll actions (Anthropic computer-use tool, OpenAI Responses * `computer_use`, Gemini Computer Use). UNIVERSAL routing axis — a brain * declares `requires: ["computerUse"]` (the router's satisfies() matches the * verbatim camelCase Capabilities key — NOT snake_case `computer_use`) and it * swaps to any computerUse-capable provider, giving HoloDoor ONE policy * chokepoint instead of three vendor-specific ones. The per-vendor action * DIALECTS (coordinate vs environment vs intent-based — they differ) live * segregated on `ProviderExtensions..computerUse`; do not flatten here. */ computerUse?: boolean; /** First-party file search / vector store — never source-of-truth (W.GOLD don't). */ fileSearchBuiltIn?: boolean; /** Server-side prompt caching (Anthropic cache_control, Gemini cached_content). */ promptCaching?: boolean; /** Caller can place explicit cache breakpoints or disable implicit cache selection. */ explicitPromptCacheControls?: boolean; /** Provider can persist reasoning state across stored Responses/conversation turns. */ persistedReasoning?: boolean; /** Per-loop token budget the model is aware of (Anthropic Task Budgets). */ perLoopBudget?: boolean; /** Server-side conversation compaction (Anthropic compact-2026-01-12). */ serverSideCompaction?: boolean; /** Hosted agentic loop (Anthropic Managed Agents, OpenAI Assistants/Agents). */ hostedAgenticLoop?: boolean; /** Persistent cross-session memory store (Anthropic Memory Stores, OpenAI vector store). */ persistentMemoryStore?: boolean; /** Strict JSON-schema-enforced structured outputs. */ structuredOutputs?: boolean; /** First-class embeddings endpoint. */ embeddings?: boolean; /** Batch API with discounted pricing (typically 50% off, 24h SLA). */ batchApi?: boolean; /** WebRTC/SIP/WebSocket realtime voice (OpenAI Realtime API). */ realtimeVoice?: boolean; /** Embedded chat UI components (OpenAI ChatKit). */ embeddedChatUI?: boolean; /** MCP-Apps iframe surface inside vendor's chat UI (OpenAI Apps SDK). */ appsIframeSurface?: boolean; /** This provider exposes its capabilities AS an MCP server (Codex MCP-server mode). */ mcpServerMode?: boolean; /** App worktrees / per-task git worktree lifecycle (Codex). */ appWorktrees?: boolean; /** First-party eval / prompt-optimizer pipeline (OpenAI Evals + Prompt Optimizer). */ evalsFirstParty?: boolean; /** Runs locally / offline (Ollama, Codex hardware-native). */ local?: boolean; /** $0 per-call inference cost (compute pre-paid via hardware). */ zeroMarginalInference?: boolean; /** Available via Amazon Bedrock. */ bedrockAvailable?: boolean; /** Available via Google Vertex AI. */ vertexAvailable?: boolean; /** Programmatic bearer-token API access (vs IDE-only / OAuth-only). */ bearerTokenAccess: boolean; } /** * Conservative default capability manifest. Adapters override the fields * they actually support; the router falls back to this when an adapter * hasn't declared its capabilities yet. Returning these defaults means * "I can do streaming + tools, nothing else known" — the router will * only route brains that don't `require` any superpower. */ export declare const DEFAULT_CAPABILITIES: Capabilities; /** * Provider-namespaced request extensions. Each adapter validates ONLY its * own namespace and ignores others — the same `LLMCompletionRequest` can * be routed to a fallback provider on retry without stripping fields. * * This is the SEGREGATED axis: each provider's superpowers are addressable * without leakage. The UNIVERSAL axis is the top-level fields on * `LLMCompletionRequest` (messages, maxTokens, tools, stream, ...). * * See `docs/LLM_CAPABILITIES.md` § Universal axis vs Segregated axis. */ export interface ProviderExtensions { anthropic?: AnthropicProviderExtensions; openai?: OpenAIProviderExtensions; codex?: CodexProviderExtensions; grok?: GrokProviderExtensions; gemini?: GeminiProviderExtensions; ollama?: OllamaProviderExtensions; copilot?: CopilotProviderExtensions; } /** * Anthropic-specific request extensions. Existing top-level * `thinking`/`thinkingDisplay`/`effort` fields on `LLMCompletionRequest` * remain valid for backward-compat; `provider.anthropic.*` is the canonical * long-term home. Migration is brain-by-brain, not breaking. */ export interface AnthropicProviderExtensions { thinking?: AnthropicThinkingParam; thinkingDisplay?: 'summarized' | 'omitted'; effort?: AnthropicEffortLevel; /** Beta `task-budgets-2026-03-13` — Opus 4.7 per-loop token budget visible to model. */ taskBudget?: { type: 'tokens'; total: number; }; /** Beta `compact-2026-01-12` — server-side conversation compaction (4.6+). */ compaction?: { type: 'compact_20260112'; }; /** Opt-in beta headers (e.g. `managed-agents-2026-04-01`, `advisor-tool-2026-03-01`). */ betaHeaders?: string[]; /** * Anthropic `tool_choice` — controls whether/which tool the model must call. * `{ type: 'auto' }` default, `{ type: 'any' }` forces at least one tool, * `{ type: 'tool', name: 'fn' }` forces a specific tool. */ toolChoice?: { type: 'auto' | 'any' | 'none'; } | { type: 'tool'; name: string; }; /** * Anthropic computer-use tool (Opus 4.7/4.8). Segregated action dialect: * COORDINATE-based mouse/keyboard over a virtual display, selected by a * dated tool type (`computer_YYYYMMDD`). Kept distinct from the OpenAI/Gemini * dialects on purpose — the universal `Capabilities.computerUse` axis is for * swap, this is for exploit. */ computerUse?: { /** Dated tool type, e.g. 'computer_20250124'. */ toolVersion: string; displayWidthPx: number; displayHeightPx: number; /** X11 display number for multi-display setups. */ displayNumber?: number; }; } /** * Inline moderation request object accepted by OpenAI Responses/Chat Completions. * * When present, OpenAI runs content-moderation on the generation request and * returns a `moderationResult` alongside the response — eliminating a separate * POST /v1/moderations call for HoloDoor policy gates. * * Wire form: `{ "moderation": { "input": "all" | string[], "model": "omni-moderation-latest" } }` * Source: developers.openai.com/api/docs/changelog (verified 2026-06-08 A-020). */ export interface InlineModerationRequest { /** * Which content to moderate. * - `"all"` — moderate all text/image inputs in the request. * - `string[]` — explicit item IDs to moderate (Responses API only). */ input: 'all' | string[]; /** * Moderation model to use. Defaults to `"omni-moderation-latest"` if omitted. */ model?: string; } /** * Inline moderation result returned by OpenAI alongside a generation response. * * Maps to the `moderation` field on the Responses API response object when * `moderationRequest` was set on the request. The `results` array contains one * entry per moderated item. * * Anthropic analog: built-in safety layer. When Claude stops for policy reasons * the `stop_reason` is `"refusal"` (mapped to `finishReason: "content_filter"` * in this SDK). No separate result object is emitted — the stop_reason IS the * moderation signal. Callers that need a unified gate should check both * `finishReason === "content_filter"` (Anthropic) and `moderationResult.flagged` * (OpenAI) at the call site. */ export interface InlineModerationResult { /** True if any category was flagged by the moderation model. */ flagged: boolean; /** * Per-category flag map. Keys are moderation category names * (e.g. `"hate"`, `"self-harm"`, `"sexual"`, `"violence"`). */ categories: Record; /** * Per-category confidence scores (0–1). Present when the provider returns them. */ categoryScores?: Record; /** Raw provider result for categories this SDK hasn't typed yet. */ raw?: unknown; } export interface OpenAIProviderExtensions { reasoningEffort?: OpenAIReasoningEffort; parallelToolCalls?: boolean; /** * OpenAI tool-choice mode. `required` forces at least one of the supplied * tools, while `none` disables tool calls and `auto` leaves the choice to * the model. With one supplied function plus `parallelToolCalls: false`, * `required` is the deterministic provider-native planner contract. */ toolChoice?: 'auto' | 'required' | 'none'; /** Responses API background mode — long-running task returns a token; poll for completion. */ background?: boolean; /** * Inline moderation request. When set, OpenAI runs content-moderation on * the generation request and returns a `moderationResult` on the response * — no separate POST /v1/moderations call needed. HoloDoor policy gates * use this to enforce content policy in one round-trip. * * Source: developers.openai.com/api/docs/changelog (verified 2026-06-08 A-020). */ moderation?: InlineModerationRequest; /** * GPT-5.6 explicit prompt-cache controls. Segregated OpenAI dialect: * callers can opt into explicit-only cache selection and mark content * blocks with `prompt_cache_breakpoint` when constructing provider-native * payloads. */ promptCache?: { mode?: 'auto' | 'explicit'; maxBreakpoints?: number; }; /** * Persist reasoning/conversation state with OpenAI Responses `store`. * Kept provider-scoped because other providers expose different persistence * and retention contracts. */ persistedReasoning?: boolean; /** Sora/GPT Image route hint; current text completion path ignores media route hints. */ videoEditing?: boolean; /** Sora route hint for still-image animation into video. */ imageAnimation?: boolean; /** Conversational Sora/GPT Image refinement hint for future media adapters. */ conversationalMediaEditing?: boolean; /** * OpenAI Responses `computer_use` tool. Segregated action dialect: * ENVIRONMENT-scoped screenshot + action loop (computer_call / * computer_call_output). Distinct from the Anthropic coordinate dialect and * the Gemini intent dialect. */ computerUse?: { environment: 'browser' | 'mac' | 'windows' | 'ubuntu'; displayWidth: number; displayHeight: number; }; /** * OpenAI Realtime voice transport dialect. Segregated per-vendor shape (do * NOT flatten — same rule as computerUse): OpenAI is the richest, carrying a * 3-way transport union + SIP telephony + ephemeral secrets. xAI (WebSocket * only + REST STT/TTS billed per-hour) and Gemini (live-vs-tts mode * discriminant) get their own distinct shapes on their own extension * interfaces in slice D. The universal `Capabilities.realtimeVoice` flag is * the swap axis; this is the exploit axis. */ realtimeVoice?: { transport: 'webrtc' | 'sip' | 'websocket'; /** Defaults: gpt-realtime-2.1 (full) / gpt-realtime-2.1-mini (cost). */ model: 'gpt-realtime-2.1' | 'gpt-realtime-2.1-mini' | string; /** Named voice, e.g. 'marin', 'cedar'. */ voice?: string; /** Browser/Quest surfaces mint a short-lived client secret server-side. */ ephemeralSecret?: { mint: boolean; ttlSeconds?: number; }; /** SIP telephony surface. */ sip?: { projectId: string; inbound?: boolean; outbound?: boolean; }; turnDetection?: { type: 'server_vad' | 'semantic_vad'; threshold?: number; }; audioFormat?: { input: 'pcm16' | 'g711_ulaw' | 'g711_alaw'; output: 'pcm16' | 'g711_ulaw' | 'g711_alaw'; }; }; } /** * Codex is a runtime, not an API surface — its extensions describe * execution-lane state (worktree path, AGENTS.md, hooks, MCP-server mode). * Slot is open; populated when the Codex execution lane is built. */ export interface CodexProviderExtensions { } export interface GrokProviderExtensions { /** xAI Live Search — real-time web + X-platform results. */ liveSearch?: boolean; /** Grok Imagine route hint; current chat completion path ignores media route hints. */ videoEditing?: boolean; /** Grok Imagine image-to-video route hint. */ imageAnimation?: boolean; /** Grok Imagine Agent Mode refinement hint for future media adapters. */ conversationalMediaEditing?: boolean; /** * xAI Voice Agent realtime transport dialect. Segregated per-vendor shape (do * NOT flatten — same rule as computerUse / OpenAI realtimeVoice). xAI's realtime * surface is genuinely DISTINCT from OpenAI's: a WebSocket-ONLY agent endpoint * (no WebRTC, no SIP, no ephemeral-secret mint) at `/v1/realtime`, with SEPARATE * REST STT/TTS endpoints, and a PER-HOUR billing model ($3/hr agent) rather than * OpenAI's per-token audio pricing. Pricing lives with CostGuard * (`XAI_REALTIME_PRICING_USD_PER_HOUR` + `defaultXaiRealtimePricer`, the * per-DURATION variant of the `RealtimePricer` union). * * Verified 2026-07-10 (docs/llm-capabilities/xai-grok.md § Voice Agent API): * `/v1/realtime` WebSocket + `grok-voice-think-fast-1.0` + `/v1/tts` ($15/1M * chars) + `/v1/stt` ($0.10/hr REST, $0.20/hr streaming). VERIFICATION GAP: the * exact per-message WIRE FRAMES for `/v1/realtime` are not published in that * doc — this is a typed INTENT surface (endpoints + model + transport + billing * model), NOT a byte-level wire spec. The frame schema + the session-opener * (`GrokRealtimeAdapter`) are a slice-E live-endpoint known-unknown; do not * invent frame fields here. */ realtimeVoice?: { /** * xAI has NO WebRTC/SIP surface — WebSocket only. Literal-typed to `'websocket'` * so the OpenAI transports (`'webrtc'` / `'sip'`) cannot leak onto this shape. */ transport: 'websocket'; /** Voice agent model, e.g. 'grok-voice-think-fast-1.0'. */ model: 'grok-voice-think-fast-1.0' | string; /** Realtime WebSocket endpoint. Default '/v1/realtime'. */ endpoint?: string; /** SPLIT REST speech-to-text endpoint (billed per-hour, not per-token). */ stt?: { endpoint: string; streaming?: boolean; }; /** SPLIT REST text-to-speech endpoint ($15 / 1M chars). */ tts?: { endpoint: string; }; }; } export interface GeminiProviderExtensions { /** Search Grounding — first-party Google Search citations. */ grounding?: boolean; /** Reference to a previously-cached `cached_content` resource. */ cachedContent?: string; /** systemInstruction (split from top-level `system` if upstream call needs it). */ systemInstruction?: string; /** Gemini Omni/Interactions route hint; current generateContent path ignores media route hints. */ videoEditing?: boolean; /** Gemini Omni still-image animation route hint. */ imageAnimation?: boolean; /** Gemini Omni conversational media refinement route hint. */ conversationalMediaEditing?: boolean; /** * Gemini Computer Use tool (3.5 Flash, public preview 2026-06-24). Segregated * action dialect: INTENT-based predefined UI actions across environments — * NOT coordinate-driven like Anthropic/OpenAI. Supports restricting or * excluding specific predefined actions. */ computerUse?: { environment: 'browser' | 'mobile' | 'desktop'; /** Restrict to a subset of the predefined intent actions. */ enabledActions?: string[]; /** Exclude specific predefined actions (Gemini supports exclusion). */ excludedActions?: string[]; }; /** * Gemini realtime voice transport dialect. Segregated per-vendor shape (do NOT * flatten). Genuinely DISTINCT from OpenAI (no WebRTC/SIP transport union; no SIP; * no per-token audio pricing table here) and from xAI (no per-hour REST STT/TTS * split; has no top-level `transport` field at all): the discriminant is a `mode` * between the bidirectional **Live API** (`live`) and **streaming TTS-only** * (`tts`) — two different Google surfaces — plus `responseModalities` (Live * session config) and short-lived **ephemeral tokens** for client-side Live * connections. * * Verified 2026-07-10 (docs/llm-capabilities/google-gemini.md): Live API models + * streaming TTS `gemini-3.1-flash-tts-preview` (streamGenerateContent / * Interactions `stream:true`); `GEMINI_CAPABILITIES.streamingSpeechGeneration` is * already true. VERIFICATION GAP: the Live API native-audio model id and the * ephemeral-token mint wire are a moving preview surface — encoded as a typed * INTENT surface with a `string` model fallback; the precise wire fields + the * session-opener (`GeminiRealtimeAdapter`) are a slice-E known-unknown. */ realtimeVoice?: { /** Live API (bidirectional duplex) vs streaming TTS-only. THE discriminant — no `transport` union. */ mode: 'live' | 'tts'; /** e.g. 'gemini-3-flash-preview' (Live) or 'gemini-3.1-flash-tts-preview' (TTS). */ model: 'gemini-3-flash-preview' | 'gemini-3.1-flash-tts-preview' | string; /** Live API session output modalities. */ responseModalities?: Array<'AUDIO' | 'TEXT'>; /** Client-side Live connections mint a short-lived ephemeral token server-side. */ ephemeralToken?: { mint: boolean; ttlSeconds?: number; }; /** Prebuilt voice selection (Live/TTS voice config). */ voiceConfig?: { prebuiltVoice?: string; }; }; } export interface OllamaProviderExtensions { /** Controls how long the model stays loaded after the call (e.g. '5m', '1h'). */ keepAlive?: string; /** Per-request context window override. */ numCtx?: number; /** GPU layer offload count. */ numGpu?: number; } /** * Copilot is an IDE surface, not a router-style provider — slot exists for * symmetry but is unlikely to be populated. See * `docs/LLM_CAPABILITIES.md` § Copilot for the architectural framing. */ export interface CopilotProviderExtensions { }