import type { DiversityPreference, StableId } from "../config/types.js"; import type { PoolId } from "../pools/index.js"; /** Infrastructure failures are deliberately separate from quality outcomes. */ export type FailureClass = "quota_exhausted" | "rate_limited" | "authentication_failed" | "timeout" | "transport_error" | "provider_unavailable" | "model_unavailable" | "invalid_request" | "protocol_error" | "cancelled" | "unknown"; export type FailureAction = "RETRY_SAME_ROUTE" | "FALLBACK_NEXT_ROUTE" | "STOP"; export type DiversityMode = "none" | "prefer" | "require"; export type RouteAvailability = "available" | "missing" | "stale" | "unavailable" | "unknown"; export interface FailureInput { readonly status?: number; readonly code?: string; readonly category?: string; readonly timeout?: boolean; readonly cancelled?: boolean; readonly retryAfterMs?: number; readonly retryAfterAt?: string; /** Optional provider text is used only as an isolated heuristic and is never persisted. */ readonly message?: string; } export interface FailureClassification { readonly class: FailureClass; readonly retryAfterMs?: number; readonly retryAfterAt?: string; readonly status?: number; readonly safeMessage: string; } export interface RouteHealthView { readonly circuit?: "unknown" | "healthy" | "degraded" | "open" | "probing"; readonly cooldownUntil?: string; readonly cooldownReason?: FailureClass; readonly consecutiveFailures?: number; readonly probeInFlight?: boolean; } export interface RoutingCandidate { readonly routeId: StableId; readonly poolId: PoolId; readonly poolPosition: number; readonly poolEnabled: boolean; readonly globalEnabled: boolean; readonly remoteModelId: string; readonly resourceId?: StableId; readonly tags?: readonly string[]; readonly underlyingFamily?: string; readonly availability?: RouteAvailability; readonly available?: boolean; readonly health?: RouteHealthView; readonly maxAttempts?: number; } export interface DiversityContext { readonly avoidRouteIds?: readonly StableId[]; readonly avoidRemoteModelIds?: readonly string[]; readonly avoidResourceIds?: readonly StableId[]; readonly avoidTags?: readonly string[]; readonly mode?: DiversityMode; } export interface RoutingPolicy { readonly maxAttempts: number; readonly timeoutMs: number; readonly rateLimitCooldownMs: number; readonly quotaCooldownMs: number; readonly fallback: { readonly enabled: boolean; }; readonly diversityPreference: DiversityPreference | DiversityMode; } export interface RoutingRequest { readonly poolId: PoolId; readonly candidates: readonly RoutingCandidate[]; readonly policy: RoutingPolicy; readonly now: Date | string; readonly attemptedRouteIds?: readonly StableId[]; readonly excludedRouteIds?: readonly StableId[]; readonly diversity?: DiversityContext; } export interface CandidateEvaluation { readonly routeId: StableId; readonly poolPosition: number; readonly eligible: boolean; readonly reasons: readonly string[]; readonly diversityConflict: boolean; readonly retryAt?: string; } export interface SelectedRoute { readonly kind: "SELECTED"; readonly routeId: StableId; readonly poolId: PoolId; readonly poolPosition: number; readonly reason: string; readonly health: RouteHealthView | undefined; readonly diversityStatus: "clear" | "preferred-conflict" | "required-conflict"; readonly evaluations: readonly CandidateEvaluation[]; } export interface NoEligibleRoute { readonly kind: "NO_ELIGIBLE_ROUTE"; readonly poolId: PoolId; readonly reasons: readonly CandidateEvaluation[]; readonly earliestRetryAt?: string; } export type RoutingDecision = SelectedRoute | NoEligibleRoute; export interface AttemptChain { readonly poolId: PoolId; readonly startedAt: string; readonly attemptedRouteIds: readonly StableId[]; readonly retryCounts: Readonly>; readonly currentRouteId?: StableId; readonly lastFailure?: FailureClassification; } export interface FailureDecisionInput { readonly classification: FailureClassification; readonly retryCount: number; readonly maxSameRouteRetries: number; readonly fallbackEnabled: boolean; } /** * Classify only safe structured transport metadata first. Message matching is * a bounded fallback and never survives into health persistence. */ export declare function classifyFailure(input: FailureInput): FailureClassification; export declare function failureMessage(failureClass: FailureClass): string; export declare function decideFailureAction(input: FailureDecisionInput): FailureAction; export declare function createAttemptChain(poolId: PoolId, startedAt: Date | string): AttemptChain; export declare function recordAttempt(chain: AttemptChain, routeId: StableId, failure?: FailureClassification): AttemptChain; export declare function nextAfterFailure(chain: AttemptChain, classification: FailureClassification, policy: RoutingPolicy): FailureAction; export declare function selectRoute(request: RoutingRequest): RoutingDecision; export declare const previewRouting: typeof selectRoute;