/** * Cortiq Router LlmAdapter. * * The adapter owns one provider route (`cortiq-router`). Every call it serves * is classified by the allaigate router (task label + complexity), turned into * an ordered candidate chain by the routing table, and dispatched to the first * candidate a registered provider actually serves: route → select → delegate. * * Delegation goes back through the `llm` seam (`delegate`, wired in index.ts to * `ctx.llm.stream`) rather than into another adapter instance directly. The * target's call-config resolution, credentials, replay state, and retry policy * are its own business, and re-implementing any of them here would diverge from * what the same model does when picked directly. For the same reason this * route's own retry policy is a single attempt (`maxRetries: 0`): a retry of a * routed call is the delegated call's job, and retrying at both levels would * multiply attempts. * * A candidate chain is only as good as its ability to skip a candidate that * cannot answer. Whether a provider *can* answer is not knowable from the * catalog — an unconfigured key looks exactly like a configured one until the * call is made — so a candidate whose provider refuses the call before emitting * anything is stepped over and the next candidate takes it. Once a chunk has * reached the caller the route is committed: a mid-stream failure is that * provider's answer, not an invitation to start over somewhere else. */ import { LlmAdapter, type GenerateOptions, type LlmModelInfo, type LlmProviderInfo, type LlmResolvedModelInfo, type ModelModality, type ResolvedRetryPolicy, type StreamChunk } from '@deepseek-ai/dsh-llm'; import type { CredentialRef } from '@deepseek-ai/dsh-credentials'; import { type Locale } from './i18n.js'; import { ComplexityBands } from './routing-table.js'; /** Context window advertised for a catalog model that configures none. */ export declare const DEFAULT_CONTEXT_WINDOW = 128000; /** Which part of the conversation is handed to the classifier. */ export type TextStrategy = 'last_user' | 'last_user_plus_system' | 'concat_all'; /** One catalog entry, with every optional field already defaulted. */ export interface ResolvedCatalogModel { id: string; /** Configured override; absent means this plugin's own translated label. */ name?: string; description?: string; contextWindow: number; inputModalities: readonly ModelModality[]; } /** Validated connection and routing facts, re-resolved on every settings change. */ export interface ResolvedRouterOptions { /** Configured copy language, or `auto` to follow the harness UI language. */ language: 'auto' | Locale; routerUrl: string; /** Credential reference (env var name), never the key itself. */ apiKeyEnv: CredentialRef; verifyTls: boolean; taxonomyId: string; timeoutMs: number; textStrategy: TextStrategy; maxChars: number; routerProfile: string; globalTiers: Record; taskRules: Record; defaultModel: string; complexityBands: ComplexityBands | null; echoRouting: boolean; models: ResolvedCatalogModel[]; } /** One live provider route serving a candidate model id. */ export interface RoutedTarget { /** Registered provider route that serves the model. */ provider: string; /** Model id as that provider names it. */ model: string; } /** The subset of the cordis logger this adapter uses. */ export interface RouterLogger { info(...args: unknown[]): void; warn(...args: unknown[]): void; error(...args: unknown[]): void; } export interface CortiqRouterAdapterOptions { /** Current resolved options (connection + routing config). */ options: () => ResolvedRouterOptions; /** Resolve the router API key from its credential reference. Throws when unset. */ resolveApiKey: (ref: CredentialRef) => Promise; /** * Find the live provider route serving one candidate model id, or `undefined` * when no registered adapter advertises it. */ resolveTarget: (modelId: string, signal?: AbortSignal) => Promise; /** Dispatch the routed call through the llm seam. */ delegate: (options: GenerateOptions) => AsyncIterable; /** The locale this plugin's user-facing copy is rendered in, read per use. */ resolveLocale: () => Locale; logger?: RouterLogger; } /** Routing facts for the last call this adapter served, for diagnostics. */ export interface CortiqRoutingMeta { taskLabel: string; complexityScore: number; complexityTier: string; selectedModel: string; selectedProvider: string; routeSource: string; confidence: number; routerRequestId?: string; } /** * Extract the text the classifier sees. The strategies mirror * cortiq-gateway's: the last user turn alone routes best, the system prompt * helps when it carries the task, and the whole transcript is the blunt option. * @param options - the request being routed. * @param strategy - configured extraction strategy. * @param maxChars - hard bound on what leaves the process. * @returns the text to classify, truncated to `maxChars`. */ export declare function extractText(options: Pick, strategy: TextStrategy, maxChars: number): string; export declare class CortiqRouterAdapter extends LlmAdapter { private readonly opts; private routerClient; private clientSignature; private routingTable; private routingSignature; private lastRouteMeta; private missingKeyReported; constructor(opts: CortiqRouterAdapterOptions); providerInfo(provider: string): LlmProviderInfo; providerRetryPolicy(_provider: string): ResolvedRetryPolicy; listModels(provider: string): Promise; resolveModel(provider: string, model: string, _signal?: AbortSignal): Promise; /** * The name one catalog entry shows in the picker: the configured override * when there is one, this plugin's translated label for an entry it ships, * and the bare id for anything else. */ private labelOf; /** * Route one call: classify, build the candidate chain, dispatch to the first * candidate a registered provider serves. * @param options - the request, carrying this route as `provider`. * @returns the delegated provider's chunk stream, unmodified. */ stream(options: GenerateOptions): AsyncIterable; /** * Classify the request. Every router-side failure degrades to `null` — an * unreachable or unpaid classifier must not take the conversation down with * it, it only costs the call its task-specific routing. */ private classify; /** * The ordered candidate chain for one decision. * * Unclassified calls lead with the configured fallback model, as that setting * promises, but do not stop there: the medium tier backs it up, so a * degraded call still has somewhere to go when the fallback's provider turns * out to be unusable. */ private candidates; /** Rebuild the router client whenever the connection facts or the key change. */ private ensureClient; /** Rebuild the routing table whenever the routing config changes. */ private ensureRoutingTable; /** Routing facts for the last call this adapter served. */ getLastRoutingMeta(): CortiqRoutingMeta | null; }