/** * Provider Capability Registry * * Unified capability contracts per provider/model for routing decisions. * Routing choices are fully explainable from capability records. */ import { type CacheType } from './cache-capability.js'; import type { LLMProvider } from './interface.js'; /** * Unified capability contract describing what a provider/model can do. * All fields are required, use `getCapability()` which always returns a * fully-resolved record derived from provider defaults and model overrides. */ export interface ProviderCapability { /** Whether the provider streams responses incrementally. */ streaming: boolean; /** Whether the model accepts tool/function definitions in requests. */ toolCalling: boolean; /** Whether the model can execute multiple tool calls in one turn. */ parallelTools: boolean; /** Whether the provider supports JSON mode / structured output. */ jsonMode: boolean; /** Whether the model exposes reasoning effort / budget controls. */ reasoningControls: boolean; /** Maximum tokens the model can receive (context window). */ maxContextTokens: number; /** Maximum tokens the model can generate in one response. */ maxOutputTokens: number; /** Provider-level request timeout in milliseconds. */ timeoutMs: number; /** Prompt-caching strategy supported by this provider. */ caching: CacheType; } /** * A request profile describing what capabilities are needed to handle a * particular task. All fields are optional, omitted means "no requirement". */ export interface RequestProfile { /** Whether the request requires streaming output. */ requiresStreaming?: boolean | undefined; /** Whether the request submits tool definitions. */ requiresToolCalling?: boolean | undefined; /** Whether the request expects parallel tool execution. */ requiresParallelTools?: boolean | undefined; /** Whether the request expects a JSON-mode / structured output response. */ requiresJsonMode?: boolean | undefined; /** Whether the request tunes reasoning effort (budget, effort label). */ requiresReasoningControls?: boolean | undefined; /** Minimum context window size the request needs (in tokens). */ minContextTokens?: number | undefined; /** Minimum output capacity the request needs (in tokens). */ minOutputTokens?: number | undefined; } /** * Typed reason codes for routing rejections. * Use these instead of free-form strings so callers can branch on them. */ export declare const RouteRejectionCode: { readonly NO_STREAMING: "NO_STREAMING"; readonly NO_TOOL_CALLING: "NO_TOOL_CALLING"; readonly NO_PARALLEL_TOOLS: "NO_PARALLEL_TOOLS"; readonly NO_JSON_MODE: "NO_JSON_MODE"; readonly NO_REASONING_CONTROLS: "NO_REASONING_CONTROLS"; readonly CONTEXT_TOO_SMALL: "CONTEXT_TOO_SMALL"; readonly OUTPUT_TOO_SMALL: "OUTPUT_TOO_SMALL"; }; export type RouteRejectionCode = (typeof RouteRejectionCode)[keyof typeof RouteRejectionCode]; /** A single capability requirement that was not met. */ export interface RouteRejectionDetail { /** Machine-readable rejection code. */ code: RouteRejectionCode; /** Human-readable description of why this requirement failed. */ reason: string; /** Actual capability value on the provider. */ actual: boolean | number | string; /** Required value from the request profile. */ required: boolean | number | string; } /** Structured result of a routing decision for a provider/model/request triple. */ export type RouteExplanation = { accepted: true; providerId: string; modelId: string; /** Human-readable summary of why this route was chosen. */ summary: string; /** The resolved capability record used for this decision. */ capability: ProviderCapability; } | { accepted: false; providerId: string; modelId: string; /** Human-readable summary of why this route was rejected. */ summary: string; /** Ordered list of unmet requirements (non-empty when accepted=false). */ rejections: RouteRejectionDetail[]; /** The resolved capability record used for this decision. */ capability: ProviderCapability; }; /** * Per-model facts a live source can answer, supplied by `setModelFactsSource`. * * This is the layer that should carry the fleet. The model catalog already * publishes each model's context window, output cap and reasoning support * per model; restating those in a hand-maintained table means the table is * wrong for every model released after the last edit, which is what happened * (see MODEL_LIMIT_FALLBACKS below). * * A field left undefined means "this source has nothing to say", and the * static fallbacks answer instead. It never means zero. */ export interface ModelCapabilityFacts { readonly maxContextTokens?: number | undefined; readonly maxOutputTokens?: number | undefined; readonly reasoningControls?: boolean | undefined; } /** Resolve per-model facts from a live source. Undefined when the source does not know the model. */ export type ModelCapabilityFactsSource = (providerId: string, modelId: string) => ModelCapabilityFacts | undefined; /** * Registry that resolves and caches capability records per provider/model, * and provides explainable routing decisions. * * Merge order (lowest to highest priority): * 1. `GLOBAL_DEFAULTS`, conservative baseline * 2. `PROVIDER_DEFAULTS[providerId]`, provider-level defaults * 3. `LLMProvider.capabilities`, self-declared by the provider instance * 4. `MODEL_OVERRIDES`, static per-model overrides * * Exception: the `caching` field is always sourced from `getCacheCapability(providerId)` * (falling back to `MODEL_OVERRIDES.caching` if present), so self-declared caching * from `LLMProvider.capabilities` is intentionally ignored. * * The cache key is `${providerId}::${modelId}`. Call `invalidate()` after dynamic * provider registration to avoid stale entries. */ export declare class ProviderCapabilityRegistry { private readonly cache; private _factsSource; /** * Wire a live per-model capability source (see `ModelCapabilityFacts`). * * Invalidates the cache, because every resolved record was computed without * it. Passing `undefined` unwires the source and falls back to the static * tables, which is the state a build with no catalog data is in. */ setModelFactsSource(source: ModelCapabilityFactsSource | undefined): void; /** * Resolve the full capability record for a provider/model pair. * * @param providerId - The registered provider name (e.g. `'anthropic'`). * @param modelId - The model ID (e.g. `'claude-opus-4-5'`). * @param provider - Optional provider instance for self-declared capabilities. * @returns A fully-resolved, immutable `ProviderCapability`. */ getCapability(providerId: string, modelId: string, provider?: Pick): ProviderCapability; /** * Build a stable, order-independent key fragment from a provider's * self-declared capabilities so cache entries with vs without an instance * (or with differing declarations) never collide. */ private _selfCapabilitiesKey; /** * Invalidate all cached capability records. * Call after dynamic provider registration or model discovery. */ invalidate(): void; /** * Check whether a resolved capability record satisfies a request profile. * * @param capability - Resolved capability from `getCapability()`. * @param request - The request profile describing requirements. * @returns `true` if every requirement in the profile is satisfied. */ canHandle(capability: ProviderCapability, request: RequestProfile): boolean; /** * Produce a structured routing explanation for a provider/model/request triple. * Always returns a complete `RouteExplanation`, never throws. * * @param providerId - The registered provider name. * @param modelId - The model ID. * @param request - The request profile. * @param provider - Optional provider instance for self-declared capabilities. * @returns A `RouteExplanation` with `accepted` flag, rejections, and capability. */ getRouteExplanation(providerId: string, modelId: string, request: RequestProfile, provider?: Pick): RouteExplanation; private _resolve; private _collectRejections; } //# sourceMappingURL=capabilities.d.ts.map