/** * @verevoir/llm — provider-agnostic LLM call surface. * * The core export holds shared types, accounting helpers, and the * `chat()` contract that every provider adapter implements. SDK * adapters live in subpaths (./anthropic, ./google planned). Consumers * import the subpath(s) they use; the unused SDK never enters their * bundle. */ /** * Model-class semantic — what kind of work the call is doing. * * - `reasoning`: open-ended judgement, cross-document synthesis, * nuanced chat. The conservative default for sites that have to * think across context. * - `drafting`: produce substantial new content — code, prose, drafts. * The mid-tier workhorse, between reasoning and extraction. Maps to * Sonnet on Anthropic; on providers with no distinct mid it resolves * up to the reasoning model. * - `extraction`: structured turn-text-into-shape tasks (URLs from a * conversation, config from a paragraph). Quick, predictable, * constrained output. Smallest competent model. * * Adapters map each class to a concrete provider model id (e.g. * `reasoning` → `claude-opus-4-7` on Anthropic). The class travels * through to {@link TokenUsage} as `direction` so cost rollups can * break down spend per direction natively. */ export type ModelClass = 'reasoning' | 'drafting' | 'extraction'; export * from './pricing.js'; export { setModelSpanSink, emitModelSpan, type ModelSpan, type ModelSpanSink, } from './audit-hook.js'; export { withAdvisor, type AdvisorConfig, type ConsultInfo } from './pair.js'; /** * Per-turn content. Plain `string` is the convenient v0 shape for * pure-text conversations. `ContentBlock[]` carries structured * content needed for multi-turn tool loops (tool_use + tool_result * blocks) and lays the schema for future multimodal blocks (image, * document) without a second migration. * * Adapters accept either form; pass a string if you have no * structure to preserve. */ export type TurnContent = string | ContentBlock[]; /** * Discriminated union of message-content blocks. Mirrors Anthropic's * native shape so the adapter doesn't have to translate. `image` and * `document` carry inline base64 data the same way Anthropic's own * SDK does, so `@verevoir/llm/anthropic` passes them through unchanged * — both as user-turn content and inside a `tool_result`'s own * `content`. Adapters that don't support a given block kind (every * adapter except Anthropic, for `image`/`document`; Google for * `tool_result`, etc.) surface {@link UnsupportedContentBlockError} * rather than silently dropping or stringifying it — see {@link * collapseContentToText}. */ export type ContentBlock = { type: 'text'; text: string; } | { type: 'image'; source: { type: 'base64'; media_type: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp'; data: string; }; } | { type: 'document'; source: { type: 'base64'; media_type: 'application/pdf'; data: string; }; } | { type: 'tool_use'; id: string; name: string; input: Record; } | { type: 'tool_result'; tool_use_id: string; /** * Result returned by the tool. A plain `string` behaves exactly * as before this field widened — use JSON for structured text * results. `ContentBlock[]` lets a tool return an `image` or * `document` (or `text`) block straight through on an adapter * that can carry it (currently only `@verevoir/llm/anthropic` — * see {@link ToolExecutor}); an adapter that cannot carry a * block found here throws {@link UnsupportedContentBlockError} * rather than silently dropping or stringifying it. */ content: string | ContentBlock[]; /** True when the tool errored; the model treats it as a failure. */ is_error?: boolean; }; /** * Thrown by {@link collapseContentToText} when a provider that can * only carry text content — every adapter except * `@verevoir/llm/anthropic` — is handed a {@link ContentBlock} it * cannot represent (an `image`, `document`, `tool_use`, or * `tool_result` block appearing where only `text` is meaningful). * Named by `provider` and `blockType` so the failure is attributable, * never a silent drop or a stringified block object. */ export declare class UnsupportedContentBlockError extends Error { readonly provider: string; readonly blockType: string; constructor(provider: string, blockType: string); } /** * Collapse {@link TurnContent} — or a {@link ContentBlock}'s own * `tool_result.content` — to the plain string a text-only provider * needs. A `string` passes through unchanged. A `ContentBlock[]` * collapses to its concatenated `text` when EVERY block is `type: * 'text'`; the first block of any other type throws {@link * UnsupportedContentBlockError} naming `provider` and that block's * type, rather than silently dropping it or stringifying the block * object. * * Used by every adapter except `@verevoir/llm/anthropic`, whose own * native wire shape already mirrors `ContentBlock` — it passes * `image` and `document` blocks through unchanged instead of calling * this. */ export declare function collapseContentToText(provider: string, content: TurnContent): string; /** A single message in a conversation. */ export interface Turn { role: 'user' | 'assistant'; content: TurnContent; } /** * Which credential mechanism actually authenticated a call — reported * POSITIVELY on every {@link TokenUsage}, never left to be inferred from * what is absent. Every adapter sets this at the exact point it already * knows the answer (e.g. the Anthropic adapter's `resolveClient` already * computes whether a call used the subscription token or the metered key; * before this field existed, that fact was computed and discarded rather * than reported). * * `'api-key'` covers every metered-key provider, and — for now — any * adapter with only one credential path (Google, the OpenAI-compatible * factory). `'subscription-oauth'` is the Anthropic Claude subscription * token path. `'mixed'` is for an AGGREGATED result (e.g. * {@link ChatWithToolLoopResult.usage}, which sums usage across several * underlying calls) whose calls did not all use the same route — reported * rather than picking one arbitrarily and hiding that the other happened. * * New adapters or new mechanisms add new values here rather than reusing * one that does not describe them. */ export type CredentialRoute = 'api-key' | 'subscription-oauth' | 'mixed'; /** * Per-call token + provenance record. Returned alongside every reply. * The (provider, model, direction) triple is the model-direction grid * used for cost rollups. */ export interface TokenUsage { /** Provider id, e.g. `anthropic`, `google`. */ provider: string; /** Concrete model id used for the call, e.g. `claude-opus-4-7`. */ model: string; /** The model-class semantic the caller asked for. */ direction: ModelClass; /** Which credential mechanism served this call — see {@link CredentialRoute}. */ route: CredentialRoute; /** * The version of the SUBSTRATE that served this call, when the substrate * is an external program whose version can drift independently of this * package's own release — e.g. the installed `claude` CLI binary * (`@verevoir/llm/claude-cli`). `undefined` for a direct API adapter * (Anthropic, Google, OpenAI-compatible): there the wire protocol is * this package's own responsibility, so nothing about "which version * answered" is unknown or needs reporting the same way. * * Same field family as {@link route}, one level down: `route` answers * WHICH credential mechanism served a call; this answers WHICH VERSION * of that mechanism did. Recorded for the same reason `route` is — a * call whose substrate version is not attributable cannot be compared * across a version change. Concretely, this is what lets a controlled * CLI upgrade be isolated as the one variable that changed between two * otherwise-identical runs, rather than an unrecorded confound. */ substrateVersion?: string; /** Standard input tokens. */ inputTokens: number; /** Output tokens (completion + tool-use args). */ outputTokens: number; /** Tokens written to provider cache on this call. */ cacheCreationInputTokens: number; /** Tokens read from provider cache on this call. */ cacheReadInputTokens: number; /** * Anthropic's 5-minute-TTL cache-write tokens, when the call used extended * cache TTLs and the provider reported the split. `undefined` on every * other call — most calls don't use extended TTLs, and no adapter besides * Anthropic's reports this split. See `src/otel.ts` for the matching span * attribute (not part of the GenAI semconv, so it's a custom one there). */ cacheCreationEphemeral5mInputTokens?: number; /** * Anthropic's 1-hour-TTL cache-write tokens — see * {@link cacheCreationEphemeral5mInputTokens}. */ cacheCreationEphemeral1hInputTokens?: number; /** * The provider's own id for this call, when `chat()`'s adapter can get * one — real, but not always a literal HTTP `request-id` header: the * Anthropic adapter uses the message id (its streaming surface doesn't * expose response headers); the OpenAI, DeepSeek and OpenAI-compatible * adapters use the SDK response object's own `id` field. `undefined` * when the adapter has none to offer (currently: Google), and on every * adapter's `chatWithTools` / `chatWithToolLoop` paths, which don't yet * wire this through. */ providerRequestId?: string; /** * The provider's own reported cost for this call, in USD, when the * provider's payload carries one (e.g. Claude Code's terminal result * event's `total_cost_usd`). NEVER COMPUTED from a price table — this * is the provider's own figure, relayed as-is, or `undefined` when the * adapter's transport doesn't see one. */ costUsdReported?: number; /** * claude-cli only: the CLI's own session id for this call, kept purely * for correlation. Reused across EVERY turn on a held session, so — * unlike {@link providerRequestId} — it is never unique per call, and * must never be read as one. `undefined` on every other adapter, and * on claude-cli's own one-shot path too (there it would just duplicate * `providerRequestId`'s own value). */ sessionId?: string; } /** Live-progress narration emitted by the model during a long call. */ export interface ProgressInfo { /** Progress in 5–90 (the system handles 0 and 100). */ percent: number; /** Short customer-facing status message. */ message: string; } /** Information passed to {@link ChatOptions.onRetry} before each retry. */ export interface ChatRetryInfo { /** 1-indexed retry attempt; the original call is attempt 0. */ attempt: number; /** Total retries that will be made before giving up. */ maxAttempts: number; /** Milliseconds we're about to wait before this retry. */ delayMs: number; /** Short human-readable reason — suitable for chat narration. */ reason: string; } /** Options for a single LLM call. */ export interface ChatOptions { systemPrompt: string; turns: Turn[]; /** * Per-user (BYOK) API key. When null/undefined the adapter falls * back to the platform default (e.g. an env var). */ apiKey?: string | null; /** Defaults to `'reasoning'` — the conservative choice. */ modelClass?: ModelClass; /** * Optional per-call output-token ceiling override. Each adapter has an * internal default appropriate to its own transport (the Anthropic * adapter's is documented at its `MAX_TOKENS` constant, itself raised * once already on direct evidence — a real diff that truncated four of * five review lenses at the previous ceiling). `undefined` uses the * adapter's own default, so an existing caller sees no change. * * Not every adapter has an equivalent lever: `@verevoir/llm/claude-cli` * shells out to `claude -p`, which has no confirmed flag for an * output-token ceiling (unlike `--model`, nothing in its own `--help` * documents one) — that adapter accepts and silently ignores this * field rather than throwing, so the shared `ChatOptions` shape stays * uniform across adapters; its own file header discloses the gap * rather than leaving a caller to discover it by the field quietly * doing nothing. */ maxTokens?: number; /** * Optional EXACT model id, bypassing `modelClass` resolution entirely. * * `modelClass` is deliberately class-based — a family-level semantic * ("reasoning", "drafting", "extraction") that each adapter resolves to * its own current id, so decisions key on provider/family and a * version bump upgrades every caller in one place (see * {@link ModelCatalogEntry}'s own doc comment). That is the right * default for almost every caller, and stays the default here: setting * only `modelClass` (or nothing) behaves exactly as before this field * existed. * * `model` exists for the narrow, real exception: a caller for whom the * SAME model answering across two different transports of the SAME * provider is itself the thing being verified. Two substrates of one * provider — a direct API call (`@verevoir/llm/anthropic`) and * `claude -p` (`@verevoir/llm/claude-cli`) — can each resolve * `modelClass` to a different concrete id: the API path takes whatever * `models[modelClass]` currently is in its own catalog; the CLI path, * having no model catalog of its own by design (see that adapter's own * file header — it is deliberately never registered into the shared * catalog), takes whatever the local `claude` install defaults to. A * caller that judges the same commit twice, once through each * transport, and treats disagreement between the two as a signal, * needs BOTH calls to have used the identical model — not two models * that merely share a class label. `model`, set identically on both * calls, is that guarantee. * * When set, this WINS over `modelClass` for the id an adapter actually * sends/reports — `modelClass` may still be read for other purposes * (e.g. `TokenUsage.direction`, which stays a class semantic even when * the id is pinned) but is never used to choose the id once `model` is * present. Reaching for this as the DEFAULT way to select a model * forfeits the catalog's forward-compatibility — a pinned id stays * pinned until the caller updates it by hand, which is exactly what * `modelClass` exists to avoid for everyone without this specific * need. */ model?: string; /** * Optional hook invoked before each retry. Best-effort; the adapter * never lets a callback failure block the retry. */ onRetry?: (info: ChatRetryInfo) => Promise; /** Optional hook invoked after a successful call with token usage. */ onUsage?: (usage: TokenUsage) => Promise; /** * Optional hook invoked LIVE during the call when the model emits * `report_progress`. When set, the adapter auto-injects the * `report_progress` tool into the request. */ onProgress?: (info: ProgressInfo) => Promise; /** * Optional `AbortSignal` for cancelling the call. When the signal * is aborted, the adapter throws the signal's `reason` (or a * generic AbortError when none was provided). Checked between * loop iterations in `chatWithToolLoop` — in-flight LLM calls * complete and their tokens are still recorded, but no further * iterations start once the signal is aborted. * * The canonical use case: an `onUsage` hook that aborts the * controller when an aggregate budget is exceeded. The hook's own * throws are still swallowed by the adapter for back-compat; * abort is the supported escape hatch. */ abortSignal?: AbortSignal; } /** Result of a single LLM call via {@link chat}. */ export interface ChatReply { /** The assembled text content of the reply. */ content: string; /** Token usage for the call. */ usage: TokenUsage; /** The provider's stop reason — `'end_turn'` for normal completion. */ stopReason: string; } /** Definition of a tool the model can choose to invoke. */ export interface ToolDef { name: string; description: string; input_schema: { type: 'object'; properties: Record; required?: string[]; }; } /** A single tool_use the model emitted in its reply. */ export interface ToolUse { id: string; name: string; input: Record; } /** Options for {@link chatWithTools}. */ export interface ChatWithToolsOptions extends ChatOptions { tools: ToolDef[]; } /** Result of a single LLM call via {@link chatWithTools}. */ export interface ChatWithToolsResult { toolUses: ToolUse[]; text: string; stopReason: string; usage: TokenUsage; } /** * Caller-supplied function that executes a single tool_use the model * emitted, returning what the model will see as the matching * `tool_result`. A plain `string` behaves exactly as before this type * widened. Returning `ContentBlock[]` lets a tool hand back an * `image` or `document` (e.g. a screenshot, a generated PDF) instead * of text — `@verevoir/llm/anthropic` passes it through to the model * unchanged; an adapter that cannot carry the block throws {@link * UnsupportedContentBlockError} (via {@link collapseContentToText}) * naming the provider and the block type, rather than silently * dropping it or stringifying it. Throw to indicate a tool FAILURE — * the loop will surface it to the model with `is_error: true` so the * model can react (apologise, try a different approach, etc.) rather * than crashing the entire conversation. */ export type ToolExecutor = (toolUse: ToolUse) => Promise; /** Options for {@link chatWithToolLoop}. */ export interface ChatWithToolLoopOptions extends ChatWithToolsOptions { /** Runs each tool_use the model emits. Required. */ executor: ToolExecutor; /** * Cap on tool-call iterations. The loop exits after this many * tool-using assistant turns regardless of whether the model would * have continued. Default 5 — generous for typical conversations * (each tool round costs a full LLM call + execution). */ maxIterations?: number; /** * Optional hook fired after each iteration with a compact summary — * useful for narrating progress into a chat surface ("Looking up * issues...", "Reading file..."). */ onIteration?: (info: { iteration: number; toolUses: ToolUse[]; stopReason: string; }) => Promise; } /** Result of a multi-turn tool conversation. */ export interface ChatWithToolLoopResult { /** The model's final text response (after all tool iterations). */ text: string; /** All tool_uses across every iteration of the loop, in order. */ toolUses: ToolUse[]; /** Per-tool-use, what the executor returned (or the error message as a * plain string) — {@link ContentBlock}[] only ever appears here on * `@verevoir/llm/anthropic`; every other adapter has already collapsed * it to text via {@link collapseContentToText} by this point, or thrown * {@link UnsupportedContentBlockError}. */ toolResults: { toolUseId: string; content: string | ContentBlock[]; isError: boolean; }[]; /** How many model calls happened (1 = pure text, no tools used). */ iterations: number; /** Aggregated usage across all iterations of the loop. */ usage: TokenUsage; } /** * Per-model rollup keyed by concrete model id. Produced by * {@link sumUsages}; consumed by display + cost helpers. * * `cacheRead` / `cacheWrite` are optional so older persisted rollups * (which carried only `in` / `out`) still parse — every helper coalesces * a missing cache field to 0. Keeping cache tokens *separate* from `in` * is what lets {@link estimateCostUSD} price them at their real rates * (a cache read is ~0.1× the input rate) instead of burying them in the * standard input figure. */ export type PerModelUsage = Record; /** * Parse a JSON-serialised {@link PerModelUsage} produced by a previous * call, returning an empty rollup on malformed/empty input. */ export declare function parseUsage(json: string): PerModelUsage; /** * Sum multiple {@link PerModelUsage} rollups into one. Output entries * always carry all four counters (a missing input field counts as 0). */ export declare function sumUsages(usages: PerModelUsage[]): PerModelUsage; /** * Total tokens across all models in a rollup — input + output + cache * read + cache write, all at face value. This is the right figure for a * runaway-loop budget guard: a cache *read* costs less in dollars but is * still a token the model had to process. */ export declare function totalTokens(usage: PerModelUsage): number; /** * Compact token total — `123`, `12.3k`, `12k`, `1.2M`. Drops trailing * `.0` so round numbers don't look gawky. */ export declare function formatTokensCompact(n: number): string; /** * Per-model USD rates per million tokens: * `[input, output, cacheRead?, cacheWrite?]`. The cache entries are * optional; when an adapter omits them {@link estimateCostUSD} falls * back to the Anthropic-standard multipliers (cache read 0.1×, cache * write 1.25× of the input rate). Existing two-element tables stay * valid. * * @deprecated Part of the old static-rates cost API. Use * `createPriceBook(...)` / `book.costOf(...)` (see `src/pricing.ts`), * which prices from LiteLLM's live table (the bundled snapshot as * fallback) and reports an unmatched model as `unpriced` rather than * `0`. Kept, unchanged, while the 5 provider adapters still call * `estimateCostUSD`. */ export type RateTuple = readonly [number, number, number?, number?]; /** * Per-model rate table — `model id → RateTuple`. * @deprecated Part of the old static-rates cost API — use * `createPriceBook(...)` / `book.costOf(...)` instead. */ export type RatesTable = Readonly>; /** * Estimate the USD cost of a per-model rollup against an adapter's rate * table. Input + output price at the table's standard rates; cache reads * and cache writes price at their own rates — either the explicit 3rd / * 4th rate-tuple entries, or the Anthropic-standard fallbacks (read * 0.1×, write 1.25× of input). Pricing cache tokens separately is what * makes prompt-cache savings *visible* in the estimate, rather than * hidden behind a worst-case full-input-rate treatment. * * Unknown models contribute 0 to the total — the caller can detect * coverage gaps by checking that every model in the rollup is in the * rates table. * * @deprecated Static per-adapter rate tables, silent on an unknown model * (contributes `0`, never surfaced as missing). Use * `createPriceBook(...)` / `book.costOf(...)` instead (see * `src/pricing.ts`) — it prices from LiteLLM's live table (the bundled * snapshot as fallback) and reports an unmatched model as `unpriced`. * Kept, unchanged, for now: all 5 provider adapters still call this. * Decision recorded in the PR body ("Decision: two cost APIs") and the * 0.33.0 CHANGELOG entry. */ export declare function estimateCostUSD(usage: PerModelUsage, rates: RatesTable): number; /** Register friendly labels for the given model ids. Idempotent. */ export declare function registerModelLabels(labels: Record): void; /** Display label for a model id, falling back to the catalog's family label * (so a new, unregistered version of a known family still labels sensibly) * and finally to the bare id. */ export declare function modelLabel(id: string): string; /** * A version-free model identity — `{ provider, family }`, e.g. * `{ anthropic, haiku }`. This is the **decision key**: routing, tier pools, * and pricing branch on it, never on the exact versioned id. The concrete * version (`claude-haiku-4-5-20251001`) is reporting metadata only. */ export interface ModelIdentity { provider: string; family: string; } /** * A catalog entry — the single source of truth for one model family. An * adapter registers its families once (via {@link registerModelCatalog}) and * derives its `models` / `rates` / labels from the same entries, so a version * bump is a one-line `currentId` change rather than edits rippling across * three hand-maintained tables. */ export interface ModelCatalogEntry { /** Provider id, e.g. `anthropic`. */ provider: string; /** Decision-key family, e.g. `opus` | `sonnet` | `haiku`. */ family: string; /** The concrete versioned id used for the actual API call + reporting. */ currentId: string; /** Pricing at the family level (version-stable). */ rates: RateTuple; /** Display label, e.g. `Haiku`. */ label: string; /** Which model-class this family serves, if the adapter routes by class. */ modelClass?: ModelClass; /** Older / alternate exact ids that also normalise to this family, so * historical usage still prices and labels. */ aliases?: string[]; /** Id prefixes that resolve **forward** to this family — a new, unseen * version of a known family (e.g. `claude-haiku-…`) still normalises * rather than dropping to "unknown". */ prefixes?: string[]; } /** * Register model families into the catalog. Idempotent per `provider/family` * (re-registering replaces). Also registers each entry's `currentId` and * `aliases` as labels, so the existing {@link modelLabel} path keeps working * without a separate {@link registerModelLabels} call. */ export declare function registerModelCatalog(entries: ModelCatalogEntry[]): void; /** * Normalise a concrete model id to its `{ provider, family }`, or `null` when * no registered family claims it (the **loud-on-miss** signal: callers should * surface a null rather than silently mis-route). Match order: exact * `currentId`, exact `alias`, then `prefix` — the prefix pass is what lets a * brand-new version of a known family resolve forward instead of falling off. */ export declare function normalizeModelId(id: string): ModelIdentity | null; /** * The catalog entry a concrete id normalises to, or `null`. * @deprecated Part of the old static-rates cost API (used by * `estimateCostUSD` / `uncoveredModels`). Use `createPriceBook(...)` / * `book.costOf(...)` instead. */ export declare function catalogEntryFor(id: string): ModelCatalogEntry | null; /** * The model ids in a usage rollup that price to nothing — neither in the * supplied `rates` table nor resolvable via the catalog. This is the **loud** * surface for "we billed something we can't price": a cost display should warn * on a non-empty result rather than quietly showing an under-count. Distinct * from {@link estimateCostUSD}, which stays silent (back-compat) and treats an * uncovered model as $0. * * @deprecated Part of the old static-rates cost API. `createPriceBook(...)` * / `book.costOf(...)` makes this unnecessary: an unmatched model there is * always `unpriced`, so there's no silent-zero case left to surface. */ export declare function uncoveredModels(usage: PerModelUsage, rates?: RatesTable): string[]; /** * Resolve a client base URL: the `_BASE_URL` env override when set * (and non-empty), else the provider's `fallback` (or `undefined` to use the * SDK default). Lets any provider be pointed at a gateway, proxy, regional, or * self-hosted endpoint without a code change — keyed by provider/endpoint, the * same convention as `_API_KEY`. */ export declare function resolveBaseUrl(envVar?: string, fallback?: string): string | undefined; /** * A placeholder API key for a keyless LOCAL endpoint. When a provider's base-URL * override is set but its API key is blank, the endpoint is presumed local — LM * Studio / Ollama / vLLM expose OpenAI-compatible servers that need no key, yet * the SDK still wants a non-empty string. Returns `undefined` when no base-URL * override is set, so a missing key against the *canonical* endpoint still errors * loudly rather than silently building an unauthenticated client. */ export declare function localEndpointKey(baseUrlEnv?: string): string | undefined; /** How to connect to a provider: the envs that hold its credential + endpoint. */ export interface ProviderConnection { provider: string; /** Env var holding the API key (e.g. `SAMBA_NOVA_API_KEY`). */ apiKeyEnv: string; /** * Additional env vars whose presence ALSO makes this provider usable — a * credential that is not the API key but authenticates just as well. Anthropic * declares `CLAUDE_CODE_OAUTH_TOKEN` here: the adapter prefers that token over * the metered key, so a caller holding only the subscription token has a * perfectly usable provider. Without this, {@link isProviderConfigured} would * look only at `apiKeyEnv`, report the provider unconfigured, and routing would * refuse to pick it — even though every call would have succeeded. The credential * CHECK must recognise every credential the CALL path accepts, or the two * disagree and the caller is told to set a key it does not need. */ altKeyEnvs?: readonly string[]; /** Env var that overrides the base URL (e.g. `SAMBA_NOVA_BASE_URL`), if any. */ baseUrlEnv?: string; /** * The provider's default OpenAI-compatible base URL, for a raw caller (a * delegate worker) that drives the endpoint directly rather than via the SDK. * Set for the OpenAI-compatible providers (OpenAI, DeepSeek, SambaNova, * Mistral); omitted for SDK-only providers (Anthropic, Gemini) — which a raw * OpenAI-compatible caller cannot drive, so they resolve to no connection. */ defaultBaseUrl?: string; /** * Whether a base-URL override **without a key** makes this provider usable — * true only for the generic OpenAI-compatible client pointed at a keyless * LOCAL server (LM Studio / Ollama / vLLM). Hosted providers (Anthropic, * Gemini, SambaNova, Mistral, DeepSeek) always need a key, so a base-URL * override alone (a regional/proxy endpoint) does NOT make them usable. * Default false. */ keylessCapable?: boolean; } /** Register how to connect to a provider. Idempotent per provider. */ export declare function registerProviderConnection(c: ProviderConnection): void; /** The registered connection for a provider, or `undefined`. */ export declare function providerConnection(provider: string): ProviderConnection | undefined; /** * Whether a provider is usable right now: its API key is set, one of its * alternative credentials ({@link ProviderConnection.altKeyEnvs} — e.g. * Anthropic's `CLAUDE_CODE_OAUTH_TOKEN`) is set, or a base-URL override points it * at a keyless local endpoint. Routing must never pick an endpoint with no * credential — but it must equally never refuse one that HAS a credential the * call path accepts, which is why the alt keys are consulted here. */ export declare function isProviderConfigured(provider: string): boolean; /** The providers that are configured right now (have a usable connection). */ export declare function configuredProviders(): string[]; /** Every catalog entry serving a family, across providers — the inverse of the * per-provider catalog. Empty when no registered provider serves it. */ export declare function providersForFamily(family: string): ModelCatalogEntry[]; /** How to pick when several providers serve the same model. */ export interface ResolveModelOptions { /** Restrict to one family (e.g. `deepseek-v3`). */ family?: string; /** Restrict to one class (reasoning / drafting / extraction). */ modelClass?: ModelClass; /** Only consider providers configured right now. Default true. */ configuredOnly?: boolean; /** Provider priority — earlier wins; overrides cheapest. */ prefer?: string[]; } /** * Resolve a desired model — by family and/or class — to a concrete catalog * entry, across all providers. This is what lets "I want DeepSeek-V3" pick * deepseek.com *or* SambaNova by policy, rather than the caller knowing which * providers serve it. Policy: a `prefer` provider order if given, else cheapest * by input rate; configured providers only unless told otherwise. Returns * `null` when nothing matches (loud-on-miss, like {@link normalizeModelId}). */ export declare function resolveModel(opts?: ResolveModelOptions): ModelCatalogEntry | null; /** * Resolve a loose human term ("deepseek") OR an exact family/id to a catalog * entry, across providers — so config can name a model by family, not a pinned * version. Matches the term (case-insensitive) against family, currentId, label, * and prefixes; exact family/id wins over a substring, then `prefer` order, then * cheapest, then newest id. Configured providers only unless told otherwise. * Null on no match (loud-on-miss, like {@link normalizeModelId}). * * IT REFUSES A VERSION IT CANNOT SERVE, rather than resolving to a neighbour. * This is SELECTION — every caller takes `entry.currentId` and calls it — and * selection and normalisation want opposite rules from the same `prefixes` * field. Normalising is backward-looking: an id that was *already used* must * still price and label, so `claude-opus-9-1` arriving in a usage report should * fall to the opus family rather than "unknown", and {@link modelLabel} matches * forward for exactly that reason. Selecting is forward-looking: `claude-opus-6` * asked *for* is a request the catalog cannot fill, and answering it with * `claude-opus-5` runs a different model with no symptom at all — not a * warning, not a slower answer, just a different mind doing the work while * every log says otherwise. * * So a term carrying a version the catalog does not hold is null — including a * PARTIAL one: `claude-opus-4` is refused while `claude-opus-4-8` resolves (as a * superseded alias), because 4 is not 4-8 and answering it with 4-8 is the same * substitution one digit smaller. A term naming no version is unaffected: * "opus", "claude" and * the bare prefix `claude-opus-` all ask for a class and get the current member * of it, which is the naming to prefer and the only one that stays true across * a release. */ export declare function resolveModelByTerm(term: string, opts?: Omit): ModelCatalogEntry | null; /** A usable OpenAI-compatible connection for a resolved model — everything a * raw caller (a delegate worker, a per-tier model slot) needs to make the call, * without importing the provider's SDK. */ export interface ModelConnection { provider: string; /** The concrete current model id (e.g. `DeepSeek-V3.2`). */ modelId: string; /** The OpenAI-compatible base URL (env override, else the provider default). */ baseUrl: string; /** The API key from env, a local placeholder, or null. */ apiKey: string | null; } /** * Resolve a term ("deepseek", or an exact id) to a usable OpenAI-compatible * connection: provider + concrete model id + endpoint + key, read from the * registered connection + env. This is what lets a config name a model by * family and have it bind to a real endpoint at resolve time. Null when nothing * matches, the provider isn't configured, or it has no OpenAI-compatible * endpoint (Anthropic / Gemini are SDK-only and can't be driven raw). */ export declare function modelConnection(term: string, opts?: Omit): ModelConnection | null; //# sourceMappingURL=index.d.ts.map