/** Every provider odla-ai can route to. Apps should not branch on this for * portability, but it is carried on responses/events for observability. */ type ProviderId = "anthropic" | "openai" | "google"; interface TextBlock { type: "text"; text: string; /** Anthropic prompt-caching marker. Other providers ignore it. */ cacheControl?: { type: "ephemeral"; }; /** Gemini thought signature attached to this part. Opaque; carried so a * later turn can hand it back, which Gemini 3 requires. Other providers * ignore it. */ thoughtSignature?: string; } type ImageMediaType = "image/jpeg" | "image/png" | "image/gif" | "image/webp"; type ImageSource = { type: "base64"; mediaType: ImageMediaType; data: string; } | { type: "url"; url: string; }; interface ImageBlock { type: "image"; source: ImageSource; } type AudioMediaType = "audio/wav" | "audio/mp3" | "audio/mpeg" | "audio/ogg" | "audio/flac" | "audio/aac" | "audio/webm"; type AudioSource = { type: "base64"; mediaType: AudioMediaType; data: string; } | { type: "url"; url: string; }; /** Audio input. Supported by OpenAI (audio models) and Google (Gemini); NOT by * Anthropic — a request carrying an AudioBlock to a Claude model is rejected up * front by capability validation, never sent. */ interface AudioBlock { type: "audio"; source: AudioSource; } type DocumentSource = { type: "base64"; mediaType: "application/pdf"; data: string; } | { type: "url"; url: string; }; /** A document (PDF) input. Supported by Anthropic and Google. */ interface DocumentBlock { type: "document"; source: DocumentSource; } interface ToolUseBlock { type: "tool_use"; /** Correlates a `tool_result` with this call. Translated from OpenAI's * `tool_calls[].id` by the openai adapter. */ id: string; name: string; /** Already-parsed object. OpenAI's JSON-string `arguments` is parsed before it * reaches this shape — callers never JSON.parse a tool's arguments. */ input: Record; /** Gemini thought signature on the function-call part. Gemini 3 validates * that a function call echoed back carries the signature it was issued * with and rejects the request otherwise, so the adapter keeps it here and * the agent loop replays it unchanged. Other providers ignore it. */ thoughtSignature?: string; } interface ToolResultBlock { type: "tool_result"; toolUseId: string; /** String for plain-text results (most tools); a content-block array for tools * that return structured / multi-modal content. */ content: string | OracleContentBlock[]; isError?: boolean; } interface ThinkingBlock { type: "thinking"; /** Extended-thinking text (Anthropic). Empty when `display` is omitted. */ thinking: string; signature?: string; } type OracleContentBlock = TextBlock | ImageBlock | AudioBlock | DocumentBlock | ToolUseBlock | ToolResultBlock | ThinkingBlock; type OracleRole = "user" | "assistant"; interface OracleMessage { role: OracleRole; /** A plain string (shorthand for a single TextBlock) or an explicit array for * multi-modal / tool turns. */ content: string | OracleContentBlock[]; } /** * One source a provider attributed while answering a web-search request. * * Two strengths of evidence share this shape. A citation carrying `citedText` * or a `startIndex`/`endIndex` span was attributed **inline** against a specific * claim. One carrying only a `url` was merely **consulted** — the provider * listed it as a search result without tying it to any sentence, which is much * weaker evidence that the answer actually came from there. */ interface SearchCitation { /** * The source URL exactly as the provider reported it. * * Not always the publisher's URL: Gemini reports grounding chunks as * `vertexaisearch.cloud.google.com/grounding-api-redirect/…` links that * resolve to the real page only when followed. Compare hosts across providers * only after resolving those redirects. */ url: string; /** Source title, when the provider reports one. */ title?: string; /** The passage the provider says this source supports, when it attributes one. */ citedText?: string; /** Start offset into the answer text this citation supports, when reported. */ startIndex?: number; /** End offset (exclusive) into the answer text this citation supports. */ endIndex?: number; } /** * Normalize a provider's raw citations into the reported set. * * Drops entries without a URL, collapses exact duplicates, and — when a source * was attributed to at least one claim — drops the redundant bare "consulted" * entry for that same URL, so a source appears either as the spans it supports * or as a single consulted-only record, never both. */ declare function mergeCitations(citations: readonly SearchCitation[]): SearchCitation[]; /** * Token counts for one call, in the four disjoint classes Anthropic reports * (the shape every adapter normalises onto). Disjoint matters for cost: a * cache-read token is *not* also counted as an input token, so pricing sums the * classes at their own rates rather than double-charging. */ interface OracleUsage { /** Uncached prompt tokens, billed at the model's input rate. */ inputTokens: number; /** Generated tokens, billed at the output rate. Thinking tokens bill here. */ outputTokens: number; /** Tokens written into the prompt cache, billed at a premium over input. */ cacheCreationTokens?: number; /** Tokens served from the prompt cache, billed well below input. */ cacheReadTokens?: number; } /** A fresh zeroed usage tally (0 input/output tokens, no cache fields) — the seed for accumulation. */ declare function emptyUsage(): OracleUsage; /** Accumulate `more` into `into`, in place. Cache fields sum when present. */ declare function addUsage(into: OracleUsage, more: OracleUsage): void; interface OracleTool { name: string; description: string; /** JSON Schema for the tool's arguments. */ inputSchema: Record; } /** How the model may use tools this turn. `{ type: "tool", name }` forces one * specific tool — the mechanism behind `extract()`. */ type ToolChoice = "auto" | "any" | "none" | { type: "tool"; name: string; }; /** Whether the model reasons before answering. Maps to Anthropic adaptive * thinking; emulated or ignored on providers without a native equivalent. */ type ThinkingMode = "off" | "adaptive"; /** Reasoning/spend depth. Maps to Anthropic `output_config.effort`; the openai * and google adapters map it to their nearest reasoning-effort knob. */ type Effort = "low" | "medium" | "high" | "xhigh" | "max"; /** Constrain the response to valid JSON matching a schema (structured output). */ interface ResponseFormat { type: "json_schema"; name?: string; schema: Record; } /** Low-cardinality OpenTelemetry attributes attached to one provider call. */ type AiTelemetryAttributes = Record; /** Optional attribution for the automatic provider-call span. This metadata is * consumed by the odla-ai facade and is never forwarded to a provider. */ interface AiTelemetry { /** Queryable feature name. Defaults to chat, stream, extract, or search. */ operation?: string; /** Additional low-cardinality dimensions such as odla.app or odla.ai.surface. */ attributes?: AiTelemetryAttributes; } interface OracleRequest { /** Canonical model id from the catalog; resolved to a provider + native id. */ model: string; /** System prompt. Top-level for Anthropic/Google; the openai adapter folds it * into a leading system message. */ system?: string | TextBlock[]; messages: OracleMessage[]; tools?: OracleTool[]; toolChoice?: ToolChoice; maxTokens: number; temperature?: number; stopSequences?: string[]; /** Reasoning mode. Only applied on models whose capabilities allow it. */ thinking?: ThinkingMode; /** Reasoning/spend depth. Only applied on models whose capabilities allow it. */ effort?: Effort; responseFormat?: ResponseFormat; /** Enable the provider's server-side web-search tool for this request. */ webSearch?: boolean; /** Provider-specific escape hatch, passed through opaquely. Using it gives up * portability across providers. */ providerExtras?: Record; /** Cancel the provider request. Adapters pass this to the vendor SDK rather * than limiting cancellation to local tool handlers. */ signal?: AbortSignal; /** Absolute Unix timestamp in milliseconds. The SDK combines this deadline * with `signal`; whichever fires first cancels the provider request. */ deadline?: number; /** Automatic provider-call telemetry. `false` suppresses the span for this * call; metadata is never included in the provider-native request. */ telemetry?: AiTelemetry | false; } type OracleStopReason = "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal" | "error"; interface OracleResponse { id: string; model: string; /** The provider that actually answered — for observability, not branching. */ provider: ProviderId; role: "assistant"; content: OracleContentBlock[]; stopReason: OracleStopReason; usage: OracleUsage; /** * Sources the provider attributed when `webSearch` was enabled — absent on * ordinary calls, and absent from streamed responses (the stream event set * carries no citation event yet). */ citations?: SearchCitation[]; /** * What the adapter actually sent, read off the built native params. Lets a * caller prove the request was transmitted as specified rather than silently * coerced — see `verifyEcho`. Absent on streamed responses. */ echo?: RequestEcho; /** Durable accounting receipt returned by platform-hosted JSON calls. */ receipt?: OracleReceipt; } /** Platform-hosted final accounting shared by JSON and SSE responses. */ interface OracleReceipt { usageId: string; usage: OracleUsage; costMicrousd: number | null; queueWaitMs: number; ttftMs: number | null; providerLatencyMs: number; totalLatencyMs: number; status: "success" | "error"; } /** The four per-token rates one request is billed at. */ interface TokenRates { /** USD per MTok of input. */ inputPerMTok: number; /** USD per MTok of output (thinking tokens bill as output). */ outputPerMTok: number; /** USD per MTok read from the prompt cache. Falls back to `inputPerMTok`. */ cacheReadPerMTok?: number; /** USD per MTok written to the prompt cache (default-TTL tier). Falls back to * `inputPerMTok`. */ cacheWritePerMTok?: number; } /** * The rates a model charges once a request's prompt is longer than a * threshold. Every provider that prices this way replaces the rate of the * WHOLE request, not of the tokens past the threshold, so one extra prompt * token can multiply the bill. Cache rates left out here fall back to this * tier's `inputPerMTok`, never to the base tier's cheaper cache rates. */ interface LongContextPricing extends TokenRates { /** Prompt length in tokens. A request whose prompt is longer than this pays * these rates; a prompt of exactly this length still pays the base rates. */ aboveTokens: number; } /** USD prices per million tokens (MTok) for one model. Absent pricing means * "unknown" — never assume free. */ interface ModelPricing extends TokenRates { /** Rates for requests whose prompt exceeds a length threshold. Absent means * the model charges one rate at every prompt length. */ longContext?: LongContextPricing; /** Free-text qualifier, e.g. "intro $2/$10 per MTok through 2026-08-31". */ note?: string; } /** * The prompt length of one request: every token the model read, however it was * billed. The token classes in `OracleUsage` are disjoint, so a prompt served * mostly from the cache is still as long as the three classes summed — and it * is that length, not the uncached part, that selects a long-context rate. */ declare function promptTokens(usage: OracleUsage): number; /** * The rates a request with a prompt of `prompt` tokens is billed at: the * long-context tier when the model has one and the prompt is over its * threshold, the base rates otherwise. */ declare function ratesForPrompt(pricing: ModelPricing, prompt: number): TokenRates; /** * Build a long-context tier from multipliers over a model's base rates — the * way OpenAI states it ("2x input and 1.5x output"). The input multiplier * covers every prompt-side rate: input, cache read, and cache write. A cache * rate the base does not carry stays absent, so it falls back to the tier's * input rate rather than inventing a discount. */ declare function scaleLongContext(base: TokenRates, aboveTokens: number, inputFactor: number, outputFactor: number): LongContextPricing; /** * Compute the USD cost of one request from its token usage. The four token * classes in `OracleUsage` are disjoint (Anthropic semantics, which the shape * mirrors), so each is priced at its own rate and summed. Callers must check * that pricing exists first — unknown pricing is "no number", never $0. * * `usage` must be ONE request's usage. A long-context tier is chosen by that * request's prompt length, so pricing a sum of several requests would bill * short prompts at the long rate; price each request, then add the dollars. */ declare function costUsd(pricing: ModelPricing, usage: OracleUsage): number; /** A model's server-side web-search capability and its provider-native config. */ type WebSearchPower = /** Anthropic web_search server tool; `toolType` is the versioned tool type * (e.g. "web_search_20260209"). */ { type: "anthropic-web-search"; toolType: string; } /** OpenAI `web_search_options` on search-capable chat models. */ | { type: "openai-web-search"; contextSize?: "low" | "medium" | "high"; } /** Gemini `googleSearch` grounding. */ | { type: "google-search-grounding"; }; /** A model's server-side code-execution capability. Represented for catalog * display/diffing; no request plumbing yet. */ type CodeExecutionPower = { type: "anthropic-code-execution"; toolType: string; } | { type: "google-code-execution"; }; /** Provider server-side capabilities, keyed for ergonomic lookup. Extending * the surface means adding a key here plus a typed per-provider union. */ interface Superpowers { webSearch?: WebSearchPower; codeExecution?: CodeExecutionPower; } /** Provider-native default for the generic `webSearch` capability. */ declare function defaultWebSearchPower(provider: "anthropic" | "openai" | "google"): WebSearchPower; /** The kind of model — chat/multimodal is the v1 focus; the others are modeled * so embeddings, transcription, TTS, and image generation slot in later. */ type ModelKind = "chat" | "embed" | "transcribe" | "tts" | "image"; interface Capabilities { kind: ModelKind; /** Accepts text input. */ textIn: boolean; /** Accepts image input (ImageBlock). */ imageIn: boolean; /** Accepts audio input (AudioBlock). */ audioIn: boolean; /** Accepts document/PDF input (DocumentBlock). */ documentIn: boolean; /** Supports tool / function calling. */ toolUse: boolean; /** Supports a native reasoning/thinking mode. */ thinking: boolean; /** Supports an effort/reasoning-depth knob. */ effort: boolean; /** Supports streaming responses. */ streaming: boolean; /** Supports constrained JSON / structured output. */ structuredOutput: boolean; /** Supports a provider server-side web-search tool. */ webSearch: boolean; /** Tool results may carry content blocks natively (image/document blocks * inside a `tool_result`). When absent or false, the provider adapter * degrades non-text blocks in tool results to `[type]` text placeholders — * a lossy but non-fatal fallback, so this flag is discovery metadata for * consumers (e.g. vision-feeding tools) rather than a validation gate. */ toolResultBlocks?: boolean; } /** A catalog entry: a canonical model id mapped to a provider, that provider's * native model id, and what the model can do. */ interface ModelSpec { /** Canonical id used across odla-ai (e.g. "claude-opus-4-8", "gpt-5"). */ id: string; provider: ProviderId; /** The id passed to the provider SDK. Often equal to `id`. */ nativeId: string; capabilities: Capabilities; contextWindow?: number; maxOutput?: number; /** Provider display name, for catalog/dashboard rendering. */ displayName?: string; /** USD pricing per MTok; absent means unknown — never assume free. */ pricing?: ModelPricing; /** Reasoning descriptor (thinking control + effort ladder). Authoritative * over the `capabilities.thinking`/`effort` booleans when present. */ thinking?: ThinkingSupport; /** Provider server-side capabilities (web search, grounding, …) with typed * per-provider config. */ superpowers?: Superpowers; /** Which of the provider's endpoints to call. OpenAI only today: `"chat"` is * Chat Completions, `"responses"` is `/v1/responses` — the only OpenAI * endpoint where reasoning and function tools coexist. Omit to let the * adapter choose (see `openaiApiFor`). */ api?: "chat" | "responses"; } /** Sensible chat/multimodal defaults; per-model tables override what differs. */ declare function chatCapabilities(overrides?: Partial): Capabilities; /** * Validate a request against a model's capabilities. Throws CapabilityError on * the first violation. Runs before any provider call — the single choke point * that turns "audio to Claude" from an opaque upstream 400 into a clear error. */ declare function validateRequest(spec: ModelSpec, req: OracleRequest): void; /** How a model's `thinking` request field behaves. Describes the request * toggle, not whether the model reasons internally. */ type ThinkingControl = /** No native thinking toggle; `thinking: "adaptive"` is rejected. */ "none" /** Thinking cannot be disabled; `thinking: "off"` is rejected. */ | "always-on" /** Off unless the request sets `thinking: "adaptive"`. */ | "opt-in" /** Adaptive unless the request sets `thinking: "off"`. */ | "on-by-default" /** Legacy token-budget style; `"adaptive"` maps to a budget_tokens value. */ | "budget"; /** The provider-native knob one unified `Effort` level maps to. The `param` * discriminant lets each adapter assert the variant it expects and fail loudly * on a mis-authored catalog entry. */ type NativeEffort = /** Anthropic `output_config.effort`. */ { param: "outputConfigEffort"; value: Effort; } /** OpenAI `reasoning_effort`. */ | { param: "reasoningEffort"; value: string; } /** Google `thinkingConfig.thinkingBudget` (-1 = dynamic). */ | { param: "thinkingBudget"; value: number; } /** Google `thinkingConfig.thinkingLevel` (Gemini 3.x generation). */ | { param: "thinkingLevel"; value: "minimal" | "low" | "medium" | "high"; }; /** A model's reasoning descriptor: the effort-ladder levels it actually * supports and how each maps to the provider-native knob. */ interface ThinkingSupport { control: ThinkingControl; /** Ladder levels the model accepts, ascending. Omitted/empty = no effort knob. */ effortLevels?: Effort[]; /** Native mapping per supported level; keys must equal `effortLevels`. */ effortMap?: Partial>; /** `control: "budget"` only — the budget_tokens sent when a request asks for * `thinking: "adaptive"` (clamped below maxTokens by the adapter). */ defaultBudgetTokens?: number; /** The native effort value to send when the request carries FUNCTION TOOLS. * * Some reasoning models refuse tools while any reasoning is in play on the * provider's chat endpoint, and reason by default when the parameter is * omitted — so sending nothing is what fails. OpenAI's gpt-5.6 line says so * outright: "Function tools with reasoning_effort are not supported … use * /v1/responses or set reasoning_effort to 'none'". * * Set it and the adapter sends this value for tool-carrying requests instead * of leaving the model on its default. Requesting an explicit `effort` * alongside tools on such a model is a CapabilityError, not a silent * downgrade — the caller asked for depth the endpoint cannot give. */ toolsEffort?: NativeEffort; } /** The full unified effort ladder, ascending. */ declare const EFFORT_LEVELS: readonly Effort[]; /** Resolve the provider-native mapping for an effort level on a model, or * undefined when the model does not map that level. */ declare function nativeEffort(spec: ModelSpec, effort: Effort): NativeEffort | undefined; /** The effort levels a spec supports — descriptor first; specs authored before * the descriptor existed fall back to the legacy capability boolean (all * levels when `capabilities.effort` is true). */ declare function supportedEffortLevels(spec: ModelSpec): Effort[]; /** * Derive a sensible per-provider `effortMap` for a set of levels: identity * maps for Anthropic (`output_config.effort`) and OpenAI (`reasoning_effort`), * a generic token-budget ladder for Google. Used by callers (e.g. the odla * platform's catalog editor) that know a model's levels but not its native * knob values; hand-tuned catalog entries override this. */ declare function defaultEffortMap(provider: ProviderId, levels: Effort[], nativeId?: string): Partial>; /** What the adapter actually sent, read off the built native params. */ interface RequestEcho { /** The provider that built the params. */ provider: ProviderId; /** The native model id in the sent params — not the canonical catalog id. */ nativeModel: string; /** * Whether reasoning is actually active for this call. When the adapter sends * no explicit thinking parameter this reflects the model's *documented * default* from the catalog descriptor, because the default is what the model * will actually do — "we sent nothing" is not the same as "it will not think". */ thinkingActive: boolean; /** The native reasoning knob actually sent, absent when none was. */ nativeEffort?: NativeEffort; /** Max output tokens actually sent. */ maxTokens?: number; /** Temperature actually sent. Absent when the adapter dropped it — several * do on reasoning-tier models, which is exactly the kind of quiet * substitution this type exists to surface. */ temperature?: number; /** Number of tools actually sent (client tools plus server tools). */ toolCount: number; } /** Canonical id → spec. */ type Catalog = Record; /** The built-in catalog across Anthropic, OpenAI, and Google. */ declare const DEFAULT_CATALOG: Catalog; /** Merge extra specs over a base catalog (later entries win by id). */ declare function buildCatalog(base: Catalog, extra?: ModelSpec[]): Catalog; /** Look up a model, throwing a ConfigError (with the known ids) if unknown. */ declare function resolveModel(catalog: Catalog, id: string): ModelSpec; /** List every provider the catalog can route to. */ declare function providersInCatalog(catalog: Catalog): ProviderId[]; /** * Look up a model's USD pricing by canonical id. * * Returns `undefined` for an unknown model *and* for a known model whose spec * carries no `pricing` — the two are the same answer to the only question that * matters downstream: "can this call be priced?". Unlike `resolveModel`, an * unknown id is not an error, because a cost path must degrade to "unpriced" * rather than throw inside telemetry. * * @param id Canonical model id, e.g. `"claude-opus-4-8"`. * @param catalog Catalog to read; defaults to the built-in `DEFAULT_CATALOG`. */ declare function modelPricing(id: string, catalog?: Catalog): ModelPricing | undefined; /** * Price one call from a model id and its token usage. * * Returns `undefined` when the model has no known pricing, so a caller can * never mistake missing catalog data for a free call. Cache-read and * cache-write tokens are priced at their own rates when the spec declares them * and fall back to the input rate otherwise (see `costUsd`). * * @param id Canonical model id, e.g. `"gpt-5"`. * @param usage Token counts for the call. * @param catalog Catalog to read; defaults to the built-in `DEFAULT_CATALOG`. */ declare function priceCall(id: string, usage: OracleUsage, catalog?: Catalog): number | undefined; /** * The canonical ids of every model in `catalog` that carries pricing, sorted. * Useful for a coverage assertion ("does our catalog price everything we * route to?") and for rendering a pricing table. * * @param catalog Catalog to read; defaults to the built-in `DEFAULT_CATALOG`. */ declare function pricedModels(catalog?: Catalog): string[]; export { modelPricing as $, type AudioBlock as A, type Superpowers as B, type Catalog as C, type DocumentBlock as D, type Effort as E, type ThinkingControl as F, type ThinkingSupport as G, type TokenRates as H, type ImageBlock as I, type ToolChoice as J, addUsage as K, type LongContextPricing as L, type ModelSpec as M, type NativeEffort as N, type OracleUsage as O, type ProviderId as P, buildCatalog as Q, type RequestEcho as R, type SearchCitation as S, type ToolUseBlock as T, chatCapabilities as U, costUsd as V, type WebSearchPower as W, defaultEffortMap as X, defaultWebSearchPower as Y, emptyUsage as Z, mergeCitations as _, type OracleContentBlock as a, nativeEffort as a0, priceCall as a1, pricedModels as a2, promptTokens as a3, providersInCatalog as a4, ratesForPrompt as a5, resolveModel as a6, scaleLongContext as a7, supportedEffortLevels as a8, validateRequest as a9, type OracleStopReason as b, type OracleReceipt as c, type TextBlock as d, type ThinkingBlock as e, type ToolResultBlock as f, type OracleRequest as g, type OracleResponse as h, type AiTelemetry as i, type AiTelemetryAttributes as j, type OracleTool as k, type OracleMessage as l, type ThinkingMode as m, type AudioMediaType as n, type AudioSource as o, type Capabilities as p, type CodeExecutionPower as q, DEFAULT_CATALOG as r, type DocumentSource as s, EFFORT_LEVELS as t, type ImageMediaType as u, type ImageSource as v, type ModelKind as w, type ModelPricing as x, type OracleRole as y, type ResponseFormat as z };