/** * What to do when the model you are on stops being usable. * * Three things end a run that did not have to end: the credit for a model runs out, a gateway's * upstream starts failing, or a rate limit closes the door for a while. In every one of them there * is usually another model on the same provider, or another provider entirely, that would have * finished the work. KONECK stopped and reported the error, which is correct behaviour and a poor * outcome — the user comes back to a dead run and does by hand what could have been done in a * second. * * So this is a policy, and deliberately a policy rather than a cleverness. Switching model * mid-task changes the thing doing the work: a different model may write in a different style, * cost a different amount, or be worse. That is a decision about someone else's money and someone * else's code, so the default is to ask, and everything about it is stated rather than inferred: * * - `on` which failures count. Out of credit is not the same as a bad gateway. * - `scope` another model on this provider, another provider, or both. * - `decide` ask, or go ahead and say so afterwards. * - `order` the sequence given, or shuffled — a fixed order concentrates load on whatever is * first, which is exactly what you do not want when the first one just failed. * - `models` the candidates, in the order they should be tried. * - `discover` when the list is empty, ask the provider what it serves rather than guessing. * * Nothing here is on by default. A tool that silently moves your work to a different model is not * a tool that respects the user. */ /** Why a switch is being considered. Each is a different question, so each is separately opt-in. */ export type FailoverTrigger = /** The account or key has no credit left for this model. */ 'exhausted' /** The provider says this model is failing, unavailable, or its upstream is down. */ | 'failing' /** Rate limited: it would work later, and later may be too late. */ | 'limited' /** The conversation no longer fits this model's context window. */ | 'too-long'; export declare const TRIGGERS: readonly FailoverTrigger[]; export declare const TRIGGER_BLURB: Record; export type FailoverScope = 'model' | 'provider' | 'both'; export type FailoverDecision = 'ask' | 'auto'; export type FailoverOrder = 'sequence' | 'random'; /** One place to move to. A bare model name means the provider stays as it is. */ export interface Candidate { provider?: string; model: string; } export interface FailoverPolicy { enabled: boolean; on: readonly FailoverTrigger[]; scope: FailoverScope; decide: FailoverDecision; order: FailoverOrder; /** Candidates in the order they should be tried. Empty means fall back on discovery. */ models: readonly Candidate[]; /** Ask the provider what it serves when no candidates were given. */ discover: boolean; /** How many switches one run may make, so a bad afternoon cannot walk the whole catalogue. */ limit: number; } /** Off, and explicit about it. */ export declare const FAILOVER_OFF: FailoverPolicy; /** * A candidate from a string. * * `provider/model` names both; anything else is a model on whichever provider is current. Model ids * contain slashes of their own — `anthropic/claude-sonnet-4.5` is one model, not a provider and a * model — so a prefix only counts when it is a provider that actually exists. */ export declare function candidateFrom(text: string, knownProvider: (name: string) => boolean): Candidate; /** A policy from whatever a config file holds, with every field defaulted rather than assumed. */ export declare function policyFrom(raw: unknown, knownProvider?: (name: string) => boolean): FailoverPolicy; /** * Which trigger a failure is, if it is one at all. * * Read from what the provider said rather than from a status code alone, because the codes are * shared: a 400 is a context overflow and a malformed request and an unsupported parameter, and * treating all of them as "move to another model" would move away from a model that was fine. */ export declare function triggerFor(err: unknown): FailoverTrigger | null; /** * The next place to try, or null when there is nowhere left. * * `tried` is every candidate already used this run, including the one started on — otherwise a * shuffled order returns to a model that has just failed, and a fixed order can loop on it * forever. Candidates outside the scope are filtered here rather than at the edges, so a policy * that says "models only" cannot move provider by way of a candidate that named one. */ export declare function pickNext(policy: FailoverPolicy, current: Candidate, tried: readonly string[], discovered?: readonly Candidate[], random?: () => number, /** * Whether a candidate can actually be used, if the caller knows. * * Moving to a model whose provider has no reachable credential is not a recovery, it is a second * failure with an extra step — and worse than the first, because everything spawned afterwards * inherits the dead end. That is what happened: a run failed over twice, and the sub-agents it * started next all died on a credential error for a provider that had never had one. * * Optional so the pure tests can drive this without a credential store, and so a caller that * genuinely cannot tell keeps the old behaviour rather than silently filtering everything out. */ usable?: (candidate: Candidate) => boolean): Candidate | null; /** The name a candidate is remembered under, so "already tried" survives a missing provider. */ export declare function keyOf(c: Candidate): string; /** What to tell the user, in one line, whichever surface is asking. */ export declare function describeSwitch(from: Candidate, to: Candidate, trigger: FailoverTrigger): string; /** The same thing as a question, for a policy that asks first. */ export declare function askSwitch(from: Candidate, to: Candidate, trigger: FailoverTrigger): string; //# sourceMappingURL=failover.d.ts.map