import type { FallbackState, FallbackResult } from "./types" import { HOOK_NAME } from "./constants" import { log } from "../../shared/logger" import type { RuntimeFallbackConfig } from "../../config" export function createFallbackState(originalModel: string): FallbackState { return { originalModel, currentModel: originalModel, fallbackIndex: -1, failedModels: new Map(), attemptCount: 0, pendingFallbackModel: undefined, } } export function isModelInCooldown(model: string, state: FallbackState, cooldownSeconds: number): boolean { const failedAt = state.failedModels.get(model) if (failedAt === undefined) return false const cooldownMs = cooldownSeconds * 1000 return Date.now() - failedAt < cooldownMs } export function findNextAvailableFallback( state: FallbackState, fallbackModels: string[], cooldownSeconds: number ): string | undefined { for (let i = state.fallbackIndex + 1; i < fallbackModels.length; i++) { const candidate = fallbackModels[i] if (!isModelInCooldown(candidate, state, cooldownSeconds)) { return candidate } log(`[${HOOK_NAME}] Skipping fallback model in cooldown`, { model: candidate, index: i }) } return undefined } export function prepareFallback( sessionID: string, state: FallbackState, fallbackModels: string[], config: Required ): FallbackResult { if (state.attemptCount >= config.max_fallback_attempts) { log(`[${HOOK_NAME}] Max fallback attempts reached`, { sessionID, attempts: state.attemptCount }) return { success: false, error: "Max fallback attempts reached", maxAttemptsReached: true } } const nextModel = findNextAvailableFallback(state, fallbackModels, config.cooldown_seconds) if (!nextModel) { log(`[${HOOK_NAME}] No available fallback models`, { sessionID }) return { success: false, error: "No available fallback models (all in cooldown or exhausted)" } } log(`[${HOOK_NAME}] Preparing fallback`, { sessionID, from: state.currentModel, to: nextModel, attempt: state.attemptCount + 1, }) const failedModel = state.currentModel const now = Date.now() state.fallbackIndex = fallbackModels.indexOf(nextModel) state.failedModels.set(failedModel, now) state.attemptCount++ state.currentModel = nextModel state.pendingFallbackModel = nextModel return { success: true, newModel: nextModel } }