openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
614 lines (613 loc) • 25.9 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { t as createLazyImportLoader } from "./lazy-promise-DGqyc4Y4.mjs";
import { n as sanitizeForLog } from "./ansi-BeFJ0Uu7.mjs";
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs";
import { a as emitFailoverEvent } from "./diagnostic-events-BADdmgZf.mjs";
import { t as createSubsystemLogger } from "./subsystem-I-uqdx4f.mjs";
import { i as isActiveUnusableWindow } from "./profile-usage-stats-BLwxBqRR.mjs";
import { t as hasAnyAuthProfileStoreSource } from "./source-check-BwAkkwWI.mjs";
import { n as assertOperatorModelAllowed, t as assertAdmittedRunOperatorAuthority } from "./admitted-run-context-QJnFIxUZ.mjs";
import { i as externalCliDiscoveryScoped } from "./external-cli-discovery-CzdLbOjb.mjs";
import { p as resolveSubscriptionAuthModeForProfiles } from "./order-BJ9-s7yV.mjs";
import { a as isFailoverError, t as FailoverError } from "./error-B1Zb0HrJ.mjs";
import { n as resolveModelCandidateChain } from "./model-fallback-candidates-DNfpNFJt.mjs";
import { a as isMissingAgentHarnessError, i as isAgentHarnessPreflightError, s as resolveAgentHarnessPreflightOwner } from "./errors-Bd6GQRkh.mjs";
import { i as isLikelyContextOverflowError } from "./classify-CR6_I_v_.mjs";
import { c as hasProviderRequestSizeCeiling, d as isNonProviderRuntimeCoordinationError, i as describeFailoverError, n as buildProviderReauthCommand, r as coerceToFailoverError } from "./failover-error-CYuKSasF.mjs";
import { a as recordFailedCandidateAttempt, c as resolveLiveSessionModelSwitchRedirectIndex, d as resolveNextFallbackCandidateIndex, f as runFallbackAttempt, g as logModelFallbackDecision, h as isModelFallbackDecisionLogEnabled, i as isTranscriptNotContinuableError, l as resolveModelFallbackCandidateAgentRuntime, m as throwFallbackFailureSummary, n as hasDifferentLiveSessionRuntimeSelection, o as resolveFallbackAuthScope, p as shouldDiscardDeferredSessionSuspension, s as resolveFallbackSoonestCooldownExpiry, t as appendFailedCandidateAttempt, u as resolveModelFallbackCandidateHarnessAuthPrecheck } from "./model-fallback-attempt-Bddw6kqw.mjs";
import { o as suspendSession, r as resolveSessionSuspensionReason } from "./session-suspension-BarqyfE5.mjs";
import { n as isFallbackCandidateSkipped, r as markFallbackCandidateSkipped, t as getFallbackCandidateSkipReason } from "./fallback-skip-cache-CjQQzXuS.mjs";
//#region src/agents/live-model-switch-error.ts
/** Control-flow error used to request a live session model switch. */
var LiveSessionModelSwitchError = class extends Error {
constructor(selection) {
super(`Live session model switch requested: ${selection.provider}/${selection.model}`);
this.name = "LiveSessionModelSwitchError";
this.provider = selection.provider;
this.model = selection.model;
this.agentRuntimeOverride = selection.agentRuntimeOverride;
this.authProfileId = selection.authProfileId;
this.authProfileIdSource = selection.authProfileIdSource;
}
};
//#endregion
//#region src/agents/failover-policy.ts
/** Returns true when a failed model can be probed during cooldown. */
function shouldAllowCooldownProbeForReason(reason) {
return reason === "billing" || shouldUseTransientCooldownProbeSlot(reason);
}
/** Returns true when a transient failure should consume a cooldown probe slot. */
function shouldUseTransientCooldownProbeSlot(reason) {
return reason === "rate_limit" || reason === "overloaded" || reason === "unknown" || reason === "empty_response" || reason === "no_error_details" || reason === "unclassified" || reason === "timeout";
}
/** Returns true when a non-transient failure should leave transient probe budget intact. */
function shouldPreserveTransientCooldownProbeSlot(reason) {
return reason === "model_not_found" || reason === "format" || reason === "auth" || reason === "auth_permanent" || reason === "session_expired" || reason === "tls_certificate";
}
//#endregion
//#region src/agents/model-fallback-cooldown.ts
const lastProbeAttempt = /* @__PURE__ */ new Map();
const MIN_PROBE_INTERVAL_MS = 3e4;
const PROBE_MARGIN_MS = 12e4;
const PROBE_SCOPE_DELIMITER = "::";
const PROBE_STATE_TTL_MS = 864e5;
const MAX_PROBE_KEYS = 256;
function resolveProbeThrottleKey(provider, agentDir) {
const scope = normalizeOptionalString(agentDir) ?? "";
return scope ? `${scope}${PROBE_SCOPE_DELIMITER}${provider}` : provider;
}
function pruneProbeState(now) {
for (const [key, ts] of lastProbeAttempt) if (!Number.isFinite(ts) || ts <= 0 || now - ts > PROBE_STATE_TTL_MS) lastProbeAttempt.delete(key);
}
function enforceProbeStateCap() {
while (lastProbeAttempt.size > MAX_PROBE_KEYS) {
let oldestKey = null;
let oldestTs = Number.POSITIVE_INFINITY;
for (const [key, ts] of lastProbeAttempt) if (ts < oldestTs) {
oldestKey = key;
oldestTs = ts;
}
if (!oldestKey) break;
lastProbeAttempt.delete(oldestKey);
}
}
function isProbeThrottleOpen(now, throttleKey) {
pruneProbeState(now);
return now - (lastProbeAttempt.get(throttleKey) ?? 0) >= MIN_PROBE_INTERVAL_MS;
}
function markProbeAttempt(now, throttleKey) {
pruneProbeState(now);
lastProbeAttempt.set(throttleKey, now);
enforceProbeStateCap();
}
function hasActiveProviderRateLimitResetWindow(params) {
return params.profileIds.some((profileId) => {
const stats = params.authStore.usageStats?.[profileId];
if (!stats || !isActiveUnusableWindow(stats.blockedUntil, params.now)) return false;
if (stats.blockedReason !== "subscription_limit" || !stats.blockedSource) return false;
return !stats.blockedModel || stats.blockedModel === params.model;
});
}
function shouldProbePrimaryDuringCooldown(params) {
if (!params.isPrimary || !isProbeThrottleOpen(params.now, params.throttleKey)) return false;
if (!params.hasFallbackCandidates) return true;
const soonest = params.authRuntime.getSoonestCooldownExpiry(params.authStore, params.profileIds, {
now: params.now,
forModel: params.model
});
if (params.reason === "rate_limit" && !hasActiveProviderRateLimitResetWindow(params)) return true;
if (soonest === null || !Number.isFinite(soonest)) return true;
return params.now >= soonest - PROBE_MARGIN_MS;
}
function resolveCooldownDecision(params) {
const inferredReason = params.authRuntime.resolveProfilesUnavailableReason({
store: params.authStore,
profileIds: params.profileIds,
now: params.now
}) ?? "unknown";
const shouldProbe = shouldProbePrimaryDuringCooldown({
isPrimary: params.isPrimary,
hasFallbackCandidates: params.hasFallbackCandidates,
reason: inferredReason,
now: params.now,
throttleKey: params.probeThrottleKey,
authRuntime: params.authRuntime,
authStore: params.authStore,
profileIds: params.profileIds,
model: params.candidate.model
});
if (inferredReason === "auth" || inferredReason === "auth_permanent") return {
type: "skip",
reason: inferredReason,
error: `Provider ${params.candidate.provider} has ${inferredReason} issue (skipping all models)`
};
if (!(inferredReason === "billing" ? params.isPrimary && shouldProbe : params.isPrimary && (!params.requestedModel || shouldProbe) || !params.isPrimary && shouldUseTransientCooldownProbeSlot(inferredReason))) return {
type: "suspend_session",
reason: inferredReason
};
return {
type: "attempt",
reason: inferredReason,
markProbe: params.isPrimary && shouldProbe
};
}
//#endregion
//#region src/agents/model-fallback.types.ts
const MODEL_FALLBACK_SKIPPED_CODE = "MODEL_FALLBACK_SKIPPED";
//#endregion
//#region src/agents/model-fallback-runner.ts
const log = createSubsystemLogger("model-fallback");
const modelFallbackAuthRuntimeLoader = createLazyImportLoader(() => import("./agents/auth-profiles.runtime.js"));
function flushDeferredSessionSuspension(state) {
const pending = state.pending;
if (!pending) return;
state.pending = void 0;
suspendSession(pending);
}
async function runWithModelFallback(params) {
const deferredSuspension = {};
try {
const result = await runWithModelFallbackInternal(params, deferredSuspension);
if (result.outcome === "exhausted") flushDeferredSessionSuspension(deferredSuspension);
return result;
} catch (err) {
if (!shouldDiscardDeferredSessionSuspension({
error: err,
abortSignal: params.abortSignal
})) flushDeferredSessionSuspension(deferredSuspension);
throw err;
}
}
async function runWithModelFallbackInternal(params, deferredSuspension) {
const operatorAuthority = params.operatorAuthority;
if (operatorAuthority) {
assertAdmittedRunOperatorAuthority(operatorAuthority);
operatorAuthority.assertCurrent();
}
const plannedCandidates = resolveModelCandidateChain({
cfg: params.cfg,
agentId: params.agentId,
provider: params.provider,
model: params.model,
fallbacksOverride: params.fallbacksOverride,
requestedRouteResolution: params.requestedRouteResolution,
manifestPlugins: params.manifestPlugins
});
const operatorModelPolicy = operatorAuthority?.modelPolicy;
const candidates = operatorModelPolicy ? plannedCandidates.filter(operatorModelPolicy.allows) : plannedCandidates;
if (operatorModelPolicy && candidates.length === 0) assertOperatorModelAllowed(operatorAuthority, void 0);
await params.prepareCandidateChain?.(candidates);
const userLockedAuthProfileId = params.userLockedAuthProfileId?.trim() || void 0;
const authRuntime = !params.skipAuthProfileRuntime && params.cfg && (userLockedAuthProfileId || hasAnyAuthProfileStoreSource(params.agentDir)) ? await modelFallbackAuthRuntimeLoader.load() : null;
const authStore = authRuntime ? authRuntime.ensureAuthProfileStore(params.agentDir, {
profileId: userLockedAuthProfileId,
externalCli: externalCliDiscoveryScoped({
config: params.cfg,
allowKeychainPrompt: false,
providerIds: candidates.map((candidate) => candidate.provider),
...userLockedAuthProfileId ? { profileIds: [userLockedAuthProfileId] } : {}
})
}) : null;
const attempts = [];
const profileIdsByCandidate = /* @__PURE__ */ new Map();
let lastError;
let selectionChanged = false;
let latestClassifiedResult;
let exhaustionResult;
const cooldownProbeUsedProviders = /* @__PURE__ */ new Set();
const tlsFailedProviders = /* @__PURE__ */ new Set();
const notifyFallbackStep = async (step) => {
try {
await params.onFallbackStep?.(step);
} catch {
log.warn("Model fallback observer failed; preserving execution outcome.");
}
};
const observeDecision = async (decision) => {
if (!params.onFallbackStep && !isModelFallbackDecisionLogEnabled()) return;
const fallbackStep = logModelFallbackDecision(decision);
if (fallbackStep) await notifyFallbackStep(fallbackStep);
};
const observeFailedCandidate = async (failedAttempt) => {
if (!params.onFallbackStep && !isModelFallbackDecisionLogEnabled()) appendFailedCandidateAttempt(failedAttempt);
else {
const fallbackStep = recordFailedCandidateAttempt(failedAttempt);
if (fallbackStep) await notifyFallbackStep(fallbackStep);
}
if (params.sessionId && failedAttempt.nextCandidate) {
const described = describeFailoverError(failedAttempt.error);
emitFailoverEvent({
sessionId: params.sessionId,
sessionKey: params.sessionKey,
lane: params.lane,
fromProvider: failedAttempt.candidate.provider,
fromModel: failedAttempt.candidate.model,
toProvider: failedAttempt.nextCandidate.provider,
toModel: failedAttempt.nextCandidate.model,
reason: described.reason ?? "unknown",
cascadeDepth: failedAttempt.attempt - 1,
suspended: false
});
}
};
const hasFallbackCandidates = candidates.length > 1;
const requestedCandidate = candidates.find((candidate) => candidate.routeOrigin === "requested");
const runAttribution = {
sessionId: params.sessionId,
lane: params.lane
};
const runObs = {
runId: params.runId,
...runAttribution,
requestedProvider: params.provider,
requestedModel: params.model,
fallbackConfigured: hasFallbackCandidates
};
for (let i = 0; i < candidates.length; i += 1) {
const candidate = candidates.at(i);
if (!candidate) throw new Error(`Missing model fallback candidate at index ${i}`);
if (tlsFailedProviders.has(candidate.provider)) continue;
const candidateRef = {
provider: candidate.provider,
model: candidate.model
};
operatorAuthority?.assertCurrent();
if (operatorAuthority?.modelPolicy?.allows(candidateRef) === false) continue;
const nextCandidate = candidates[resolveNextFallbackCandidateIndex({
candidates,
currentIndex: i,
excludedProviders: tlsFailedProviders
})];
const hasRemainingCandidate = nextCandidate !== void 0;
const candidateHarnessAuth = await resolveModelFallbackCandidateHarnessAuthPrecheck({
cfg: params.cfg,
agentId: params.agentId,
sessionKey: params.sessionKey,
resolveAgentHarnessRuntimeOverride: params.resolveAgentHarnessRuntimeOverride,
prepareAgentHarnessRuntime: params.prepareAgentHarnessRuntime,
...candidate
});
const isPrimary = candidate.routeOrigin === "requested";
const requestedModel = requestedCandidate !== void 0 && candidate.provider === requestedCandidate.provider && candidate.model === requestedCandidate.model;
const attemptContext = {
attempt: i + 1,
total: candidates.length
};
const candObs = {
...runObs,
candidate,
...attemptContext,
nextCandidate,
isPrimary,
requestedModelMatched: requestedModel
};
const observeCandidateDecision = (decision, extra = {}) => observeDecision({
decision,
...candObs,
...extra
});
const pushSkippedAttempt = (error, reason, authMode) => attempts.push({
...candidateRef,
error,
reason,
code: MODEL_FALLBACK_SKIPPED_CODE,
authMode
});
const recordFailure = async (error, next) => {
await observeFailedCandidate({
attempts,
...candObs,
error,
nextCandidate: next
});
await params.onError?.({
...candidateRef,
error,
...attemptContext
});
};
let candidateAuthProfileIds;
let quotaRequiresAuthPreparation = false;
let userLockedAuthProfileEligible = false;
if (authRuntime && authStore) {
userLockedAuthProfileEligible = userLockedAuthProfileId !== void 0 && authRuntime.resolveAuthProfileEligibility({
cfg: params.cfg,
store: authStore,
provider: candidate.provider,
profileId: userLockedAuthProfileId,
includePendingOAuthRefresh: true
}).eligible;
let profileIds = authRuntime.resolveAuthProfileOrder({
cfg: params.cfg,
store: authStore,
provider: candidate.provider,
forModel: candidate.model,
includePendingOAuthRefresh: true
});
if (userLockedAuthProfileEligible && userLockedAuthProfileId) profileIds = [.../* @__PURE__ */ new Set([userLockedAuthProfileId, ...profileIds])];
quotaRequiresAuthPreparation = (await authRuntime.maybeReprobeWhamBlockedProfiles({
store: authStore,
profileIds,
agentDir: params.agentDir,
cfg: params.cfg,
forModel: candidate.model
}))?.requiresAuthPreparation === true;
if (!candidateHarnessAuth.skipsProviderAuthCooldown) {
candidateAuthProfileIds = profileIds;
profileIdsByCandidate.set(candidate, candidateAuthProfileIds);
}
}
const candidateAuthScope = resolveFallbackAuthScope({
userLockedAuthProfileId: userLockedAuthProfileEligible ? userLockedAuthProfileId : void 0,
profileIds: candidateAuthProfileIds
});
if (!isPrimary && params.sessionId) {
if (isFallbackCandidateSkipped({
sessionId: params.sessionId,
...candidateRef,
authScope: candidateAuthScope
})) {
const skipReason = getFallbackCandidateSkipReason({
sessionId: params.sessionId,
...candidateRef,
authScope: candidateAuthScope
}) ?? "auth";
const reauthCommand = buildProviderReauthCommand(candidate.provider);
const reauthHint = reauthCommand ? `run \`${reauthCommand}\` to re-authenticate` : "re-authenticate that provider";
const error = `Skipping ${candidate.provider}/${candidate.model}: recent ${skipReason} failure in this session (${reauthHint})`;
pushSkippedAttempt(error, skipReason);
await observeCandidateDecision("skip_candidate", {
reason: skipReason,
error
});
continue;
}
}
let runOptions;
let attemptedDuringCooldown = false;
let transientProbeProviderForAttempt = null;
if (authRuntime && authStore && candidateAuthProfileIds && !quotaRequiresAuthPreparation && !candidateHarnessAuth.skipsProviderAuthCooldown) {
const profileIds = candidateAuthProfileIds;
const isAnyProfileAvailable = profileIds.some((id) => !authRuntime.isProfileInCooldown(authStore, id, void 0, candidate.model));
if (profileIds.length > 0 && !isAnyProfileAvailable) {
const now = Date.now();
const probeThrottleKey = resolveProbeThrottleKey(candidate.provider, params.agentDir);
const decision = resolveCooldownDecision({
candidate,
isPrimary,
requestedModel,
hasFallbackCandidates,
now,
probeThrottleKey,
authRuntime,
authStore,
profileIds
});
const authMode = decision.reason === "billing" || decision.reason === "auth" || decision.reason === "auth_permanent" || decision.reason === "session_expired" ? resolveSubscriptionAuthModeForProfiles({
store: authStore,
profileIds
}) : void 0;
if (decision.type !== "attempt") {
const error = decision.type === "skip" ? decision.error : `Provider ${candidate.provider} is in cooldown`;
pushSkippedAttempt(error, decision.reason, authMode);
if (decision.type === "suspend_session" && params.sessionId) {
emitFailoverEvent({
sessionId: params.sessionId,
lane: params.lane,
fromProvider: candidate.provider,
fromModel: candidate.model,
reason: decision.reason,
suspended: !hasRemainingCandidate
});
if (!hasRemainingCandidate) {
deferredSuspension.pending = void 0;
suspendSession({
cfg: params.cfg,
agentId: params.agentId,
agentDir: params.agentDir,
sessionId: params.sessionId,
reason: resolveSessionSuspensionReason(decision.reason),
failedProvider: candidate.provider,
failedModel: candidate.model
});
}
}
await observeCandidateDecision("skip_candidate", {
reason: decision.reason,
error,
profileCount: profileIds.length
});
continue;
}
if (decision.markProbe) markProbeAttempt(now, probeThrottleKey);
if (shouldAllowCooldownProbeForReason(decision.reason)) {
const isTransientCooldownReason = shouldUseTransientCooldownProbeSlot(decision.reason);
if (isTransientCooldownReason && cooldownProbeUsedProviders.has(candidate.provider)) {
const error = `Provider ${candidate.provider} is in cooldown (probe already attempted this run)`;
pushSkippedAttempt(error, decision.reason, authMode);
await observeCandidateDecision("skip_candidate", {
reason: decision.reason,
error,
profileCount: profileIds.length
});
continue;
}
runOptions = { allowTransientCooldownProbe: true };
if (isTransientCooldownReason) transientProbeProviderForAttempt = candidate.provider;
}
attemptedDuringCooldown = true;
await observeCandidateDecision("probe_cooldown_candidate", {
reason: decision.reason,
allowTransientCooldownProbe: runOptions?.allowTransientCooldownProbe,
profileCount: profileIds.length
});
}
}
operatorAuthority?.assertCurrent();
if (operatorAuthority?.modelPolicy?.allows(candidateRef) === false) continue;
const attemptRun = await runFallbackAttempt({
run: params.run,
...candidate,
attempts,
captureHarnessPreflight: true,
options: {
...runOptions,
isFinalFallbackAttempt: !hasRemainingCandidate,
modelRoutingProvenance: {
requestedProvider: params.provider,
requestedModel: params.model,
stage: isPrimary ? "initial" : "fallback",
selectionChanged,
fallbackReason: isPrimary ? void 0 : attempts.at(-1)?.reason
}
},
deferSessionSuspension: hasRemainingCandidate,
onDeferredSessionSuspension: (suspension) => {
deferredSuspension.pending = suspension;
},
classifyResult: params.classifyResult,
...attemptContext,
attribution: runAttribution,
abortSignal: params.abortSignal
});
if ("success" in attemptRun) {
if (!attemptRun.stopped && (i > 0 || attempts.length > 0 || attemptedDuringCooldown)) {
await observeCandidateDecision("candidate_succeeded", { previousAttempts: attempts });
const notFoundAttempt = i > 0 ? attempts.find((a) => a.reason === "model_not_found") : void 0;
if (notFoundAttempt) log.warn(`Model "${sanitizeForLog(notFoundAttempt.provider)}/${sanitizeForLog(notFoundAttempt.model)}" not found. Fell back to "${sanitizeForLog(candidate.provider)}/${sanitizeForLog(candidate.model)}".`);
}
return attemptRun.success;
}
const err = attemptRun.error;
if (isAgentHarnessPreflightError(err)) {
const failedHarnessId = resolveAgentHarnessPreflightOwner(err);
if (!failedHarnessId) throw err;
let nextEligibleIndex = candidates.length;
for (let index = i + 1; index < candidates.length; index += 1) {
const next = candidates[index];
if (!next || tlsFailedProviders.has(next.provider)) continue;
if (resolveModelFallbackCandidateAgentRuntime({
cfg: params.cfg,
agentId: params.agentId,
sessionKey: params.sessionKey,
resolveAgentHarnessRuntimeOverride: params.resolveAgentHarnessRuntimeOverride,
...next
}).runtime !== failedHarnessId) {
nextEligibleIndex = index;
break;
}
}
const nextEligibleCandidate = candidates[nextEligibleIndex];
if (!nextEligibleCandidate) throw err;
lastError = err;
await recordFailure(err, nextEligibleCandidate);
i = nextEligibleIndex - 1;
continue;
}
if (!attemptRun.classifiedResult && params.canFallbackAfterError && !await params.canFallbackAfterError({
...candidateRef,
error: err,
...attemptContext
})) throw err;
if (attemptRun.classifiedResult) latestClassifiedResult = attemptRun.classifiedResult;
if (attemptRun.exhaustionResult && (!exhaustionResult || attemptRun.exhaustionResult.priority >= exhaustionResult.priority)) exhaustionResult = attemptRun.exhaustionResult;
if (isNonProviderRuntimeCoordinationError(err) || isTranscriptNotContinuableError(err)) throw err;
if (transientProbeProviderForAttempt) {
const probeFailureReason = describeFailoverError(err).reason;
if (!shouldPreserveTransientCooldownProbeSlot(probeFailureReason)) cooldownProbeUsedProviders.add(transientProbeProviderForAttempt);
}
const errMessage = formatErrorMessage(err);
if (isLikelyContextOverflowError(errMessage) && !hasProviderRequestSizeCeiling(err)) throw err;
if (isMissingAgentHarnessError(err)) throw err;
const normalized = coerceToFailoverError(err, {
...candidateRef,
...runAttribution
}) ?? err;
if (err instanceof LiveSessionModelSwitchError) {
selectionChanged = true;
if (hasDifferentLiveSessionRuntimeSelection({
error: err,
currentAgentHarnessRuntimeOverride: candidateHarnessAuth.agentHarnessRuntimeOverride
})) throw err;
const liveSwitchTargetIndex = resolveLiveSessionModelSwitchRedirectIndex({
error: err,
candidates,
currentIndex: i
});
if (liveSwitchTargetIndex !== null) {
i = liveSwitchTargetIndex - 1;
continue;
}
const switchMsg = err.message;
const switchNormalized = new FailoverError(switchMsg, {
reason: "unknown",
...candidateRef,
...runAttribution
});
lastError = switchNormalized;
await observeFailedCandidate({
attempts,
...candObs,
error: switchNormalized
});
continue;
}
const isKnownFailover = isFailoverError(normalized);
if (!isKnownFailover && !hasRemainingCandidate) throw err;
if (isKnownFailover && !isPrimary && params.sessionId && (normalized.reason === "auth" || normalized.reason === "auth_permanent")) markFallbackCandidateSkipped({
sessionId: params.sessionId,
...candidateRef,
authScope: normalized.profileId?.trim() || candidateAuthScope,
reason: normalized.reason
});
if (isKnownFailover && normalized.reason === "tls_certificate") tlsFailedProviders.add(candidate.provider);
const failedNextCandidateIndex = resolveNextFallbackCandidateIndex({
candidates,
currentIndex: i,
excludedProviders: tlsFailedProviders
});
lastError = normalized;
await recordFailure(normalized, candidates[failedNextCandidateIndex]);
if (failedNextCandidateIndex > i + 1) i = failedNextCandidateIndex - 1;
}
if (exhaustionResult) {
const selected = latestClassifiedResult && params.mergeExhaustedResult ? {
...latestClassifiedResult,
result: params.mergeExhaustedResult({
latestResult: latestClassifiedResult.result,
preferredResult: exhaustionResult.result
})
} : exhaustionResult;
return {
outcome: "exhausted",
result: selected.result,
provider: selected.provider,
model: selected.model,
attempts
};
}
if (operatorAuthority?.modelPolicy && attempts.length === 0) assertOperatorModelAllowed(operatorAuthority, void 0);
return throwFallbackFailureSummary({
attempts,
candidates,
lastError,
label: "models",
formatAttempt: (attempt) => `${attempt.provider}/${attempt.model}: ${attempt.error}${attempt.reason ? ` (${attempt.reason})` : ""}`,
soonestCooldownExpiry: resolveFallbackSoonestCooldownExpiry({
authRuntime,
userLockedAuthProfileId,
agentDir: params.agentDir,
cfg: params.cfg,
profileIdsByCandidate
}),
attribution: {
sessionId: params.sessionId,
lane: params.lane
},
cfg: params.cfg,
agentId: params.agentId,
agentDir: params.agentDir
});
}
//#endregion
export { LiveSessionModelSwitchError as i, MODEL_FALLBACK_SKIPPED_CODE as n, shouldUseTransientCooldownProbeSlot as r, runWithModelFallback as t };