UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

187 lines (186 loc) 8.09 kB
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { r as normalizeProviderId } from "./provider-id-DMd-TDFp.js"; import { o as resolveSessionStorePathCore } from "./paths-CXdaYWF_.js"; import { n as DEFAULT_MODEL, r as DEFAULT_PROVIDER } from "./defaults-CdX9UGcX.js"; import { A as resolveCollapsedSessionAuthPinSource, _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js"; import { t as resolveDefaultModelForAgent } from "./model-selection-config-BrdmmqKD.js"; import "./session-accessor-YsytfDtG.js"; import { d as patchSessionEntryCore, u as loadSessionEntryReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js"; import { i as resolveSessionRuntimeOverrideForProvider } from "./session-runtime-compat-DNAICzi6.js"; import { i as resolvePersistedSelectedModelRef, l as normalizeStoredOverrideModel } from "./model-selection-di2kjKCB.js"; //#region src/agents/live-model-switch.ts /** * Resolves and persists live-session model switch requests. */ const OPENAI_PROVIDER_ID = "openai"; const OPENAI_CODEX_PROVIDER_ID = "openai"; /** * Entry-snapshot variant of the selection resolver, so atomic patch callbacks * can evaluate the persisted selection against the exact row they may rewrite. */ function resolveSelectionFromSessionEntry(params) { const { cfg, entry } = params; const agentId = normalizeOptionalString(params.agentId); const defaultModelRef = agentId ? resolveDefaultModelForAgent({ cfg, agentId }) : { provider: params.defaultProvider, model: params.defaultModel }; const normalizedSelection = normalizeStoredOverrideModel({ providerOverride: entry?.providerOverride, modelOverride: entry?.modelOverride }); const persisted = resolvePersistedSelectedModelRef({ defaultProvider: defaultModelRef.provider, runtimeProvider: entry?.modelProvider, runtimeModel: entry?.model, overrideProvider: normalizedSelection.providerOverride, overrideModel: normalizedSelection.modelOverride }); const provider = persisted?.provider ?? normalizedSelection.providerOverride ?? entry?.providerOverride?.trim() ?? defaultModelRef.provider; const model = persisted?.model ?? defaultModelRef.model; const agentRuntimeOverride = resolveSessionRuntimeOverrideForProvider({ provider, entry, cfg }); const authProfileId = normalizeOptionalString(entry?.authProfileOverride); return { provider, model, ...agentRuntimeOverride ? { agentRuntimeOverride } : {}, authProfileId, authProfileIdSource: authProfileId ? resolveCollapsedSessionAuthPinSource(entry) : void 0 }; } function isAlreadyAppliedOpenAICodexRuntimePromotion(current, next) { return normalizeProviderId(current.provider) === OPENAI_CODEX_PROVIDER_ID && normalizeProviderId(next.provider) === OPENAI_PROVIDER_ID && current.model === next.model; } function hasDifferentLiveSessionModelSelection(current, next) { return (current.provider !== next.provider || current.model !== next.model) && !isAlreadyAppliedOpenAICodexRuntimePromotion(current, next) || normalizeOptionalString(current.agentRuntimeOverride) !== next.agentRuntimeOverride || normalizeOptionalString(current.authProfileId) !== next.authProfileId || (normalizeOptionalString(current.authProfileId) ? current.authProfileIdSource : void 0) !== next.authProfileIdSource; } /** * Check whether a user-initiated live model switch is pending for the given * session. Returns the persisted model selection when the session's * `liveModelSwitchPending` flag is `true` AND the persisted selection differs * from the currently running model; otherwise returns `undefined`. * * When the flag is set but the current model already matches the persisted * selection (e.g. the switch was applied as an override and the current * attempt is already using the new model), the flag is consumed (cleared) * eagerly to prevent it from persisting as stale state. * * **Deferral semantics:** The caller in `run.ts` only acts on the returned * selection when `canRestartForLiveSwitch` is `true`. If the run cannot * restart (e.g. a tool call is in progress), the flag intentionally remains * set so the switch fires on the next clean retry opportunity — even if that * falls into a subsequent user turn. * * This replaces the previous approach that used an in-memory run-state map, * which could not distinguish between * user-initiated `/model` switches and system-initiated fallback rotations. */ function shouldSwitchToLiveModel(params) { const sessionKey = params.sessionKey?.trim(); const cfg = params.cfg; if (!cfg || !sessionKey || params.sessionPersistence === "detached") return; const storePath = resolveSessionStorePathCore(cfg.session?.store, { agentId: params.agentId?.trim() }); const entry = loadSessionEntryReadOnly({ storePath, sessionKey, hydrateSkillPromptRefs: false, clone: false, readConsistency: "latest" }); if (!entry?.liveModelSwitchPending) return; const persisted = resolveSelectionFromSessionEntry({ cfg, entry, agentId: params.agentId, defaultProvider: params.defaultProvider, defaultModel: params.defaultModel }); if (!hasDifferentLiveSessionModelSelection({ provider: params.currentProvider, model: params.currentModel, agentRuntimeOverride: params.currentAgentRuntimeOverride, authProfileId: params.currentAuthProfileId, authProfileIdSource: params.currentAuthProfileIdSource }, persisted)) { clearLiveModelSwitchPending({ cfg, sessionKey, agentId: params.agentId }).catch(() => {}); return; } return persisted; } /** * Post-run consolidation: once a completed run has actually executed the * persisted selection, the pending live-switch flag is spent. CLI harness runs * never pass through the embedded attempt-recovery clear, so without this the * flag survives forever and `/status` keeps reporting a switch that already * happened. Unlike `shouldSwitchToLiveModel`, runtime/auth-profile drift is * ignored here: the selected model demonstrably ran, so keeping the flag would * only re-arm mid-run restarts that have nothing left to apply. Compare and * clear happen inside one atomic patch so a concurrent `/model` that persists * a newer selection is never consumed by this run's result. */ async function consolidateLiveModelSwitchAfterRun(params) { const sessionKey = normalizeOptionalString(params.sessionKey); const cfg = params.cfg; const providerUsed = normalizeOptionalString(params.providerUsed); const modelUsed = normalizeOptionalString(params.modelUsed); if (!cfg || !sessionKey || !providerUsed || !modelUsed) return; const agentId = resolveSessionAgentId({ sessionKey, config: cfg, agentId: params.agentId }); const storePath = resolveSessionStorePathCore(cfg.session?.store, { agentId }); if (!storePath) return; await patchSessionEntryCore({ storePath, sessionKey }, (entry) => { if (!entry.liveModelSwitchPending) return null; const persisted = resolveSelectionFromSessionEntry({ cfg, entry, agentId, defaultProvider: DEFAULT_PROVIDER, defaultModel: DEFAULT_MODEL }); if (!(providerUsed === persisted.provider && modelUsed === persisted.model || isAlreadyAppliedOpenAICodexRuntimePromotion({ provider: providerUsed, model: modelUsed }, persisted))) return null; const next = { ...entry }; delete next.liveModelSwitchPending; return next; }, { replaceEntry: true }); } /** * Clear the `liveModelSwitchPending` flag from the session entry on disk so * subsequent retry iterations do not re-trigger the switch. */ async function clearLiveModelSwitchPending(params) { const sessionKey = params.sessionKey?.trim(); const cfg = params.cfg; if (!cfg || !sessionKey) return; const storePath = resolveSessionStorePathCore(cfg.session?.store, { agentId: params.agentId?.trim() }); if (!storePath) return; await patchSessionEntryCore({ storePath, sessionKey }, (entry) => { const next = { ...entry }; delete next.liveModelSwitchPending; return next; }, { replaceEntry: true }); } //#endregion export { consolidateLiveModelSwitchAfterRun as n, shouldSwitchToLiveModel as r, clearLiveModelSwitchPending as t };