openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
127 lines (126 loc) • 5.49 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { i as normalizeProviderId } from "./provider-id-Dq06Bcx6.js";
import { u as resolveStorePath } from "./paths-NEwU8m3X.js";
import { t as loadSessionStore } from "./store-load-C4uq6Lrq.js";
import { d as updateSessionStore } from "./store-Qsgtu-0y.js";
import { c as resolveDefaultModelForAgent, d as resolvePersistedSelectedModelRef, i as normalizeStoredOverrideModel } from "./model-selection-CA_65iXi.js";
import "./runs-B4dP_Q30.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";
function resolveLiveSessionModelSelection(params) {
const sessionKey = normalizeOptionalString(params.sessionKey);
const cfg = params.cfg;
if (!cfg || !sessionKey) return null;
const agentId = normalizeOptionalString(params.agentId);
const defaultModelRef = agentId ? resolveDefaultModelForAgent({
cfg,
agentId
}) : {
provider: params.defaultProvider,
model: params.defaultModel
};
const entry = loadSessionStore(resolveStorePath(cfg.session?.store, { agentId }), {
hydrateSkillPromptRefs: false,
skipCache: true
})[sessionKey];
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 authProfileId = normalizeOptionalString(entry?.authProfileOverride);
return {
provider,
model,
authProfileId,
authProfileIdSource: authProfileId ? entry?.authProfileOverrideSource : 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) {
if (!next) return false;
return (current.provider !== next.provider || current.model !== next.model) && !isAlreadyAppliedOpenAICodexRuntimePromotion(current, next) || 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 map
* (`consumeEmbeddedRunModelSwitch`) 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) return;
if (!loadSessionStore(resolveStorePath(cfg.session?.store, { agentId: params.agentId?.trim() }), {
hydrateSkillPromptRefs: false,
skipCache: true,
clone: false
})[sessionKey]?.liveModelSwitchPending) return;
const persisted = resolveLiveSessionModelSelection({
cfg,
sessionKey,
agentId: params.agentId,
defaultProvider: params.defaultProvider,
defaultModel: params.defaultModel
});
if (!hasDifferentLiveSessionModelSelection({
provider: params.currentProvider,
model: params.currentModel,
authProfileId: params.currentAuthProfileId,
authProfileIdSource: params.currentAuthProfileIdSource
}, persisted)) {
clearLiveModelSwitchPending({
cfg,
sessionKey,
agentId: params.agentId
}).catch(() => {});
return;
}
return persisted ?? void 0;
}
/**
* 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 = resolveStorePath(cfg.session?.store, { agentId: params.agentId?.trim() });
if (!storePath) return;
await updateSessionStore(storePath, (store) => {
const entry = store[sessionKey];
if (entry) delete entry.liveModelSwitchPending;
});
}
//#endregion
export { shouldSwitchToLiveModel as n, clearLiveModelSwitchPending as t };