import type { LanguageModelV2 } from "@ai-sdk/provider"; export function isProviderAuthenticationFailure(error: unknown, depth = 0): boolean { if (!error || depth > 3) return false; const value = typeof error === "object" ? error as Record : {}; const status = Number(value.statusCode || value.status || value.httpStatus); const message = String(value.message || (typeof error === "string" ? error : "")).toLowerCase(); return status === 401 || /\b401\b/.test(message) || /invalid authentication|invalid[ _]api[ _]key|authentication[_ ](?:failed|error)|密钥鉴权失败/.test(message) || isProviderAuthenticationFailure(value.cause, depth + 1); } export function isCredentialFailureBan(reason?: string | null) { return /^provider_(?:authentication_failed|access_forbidden)\b/.test(String(reason || "")); } // Retry only an explicitly rejected authentication request, before the provider // returns a stream. Never replay accepted streams, tools, timeouts or 5xx POSTs. export function withAuthenticationFailover(params: { primary: LanguageModelV2; onAuthenticationFailure: (error: unknown) => Promise; resolveFallback: () => Promise; }): LanguageModelV2 { let active = params.primary; let recovery: Promise | undefined; const invoke = async (call: (model: LanguageModelV2) => PromiseLike, signal?: AbortSignal): Promise => { const attempted = active; try { return await call(attempted); } catch (error) { if (signal?.aborted || attempted !== params.primary || !isProviderAuthenticationFailure(error)) throw error; recovery ||= (async () => { await params.onAuthenticationFailure(error); const fallback = await params.resolveFallback(); if (fallback && fallback !== params.primary) active = fallback; return fallback; })(); const fallback = await recovery; if (!fallback || fallback === attempted) throw error; signal?.throwIfAborted(); return await call(fallback); } }; return { specificationVersion: "v2", get provider() { return active.provider; }, get modelId() { return active.modelId; }, get supportedUrls() { return active.supportedUrls; }, doGenerate: (options) => invoke((model) => model.doGenerate(options), options.abortSignal), doStream: (options) => invoke((model) => model.doStream(options), options.abortSignal), }; }