Files
Moltbot/src/auto-reply/reply/session-reset-model.ts

203 lines
5.4 KiB
TypeScript

import type { OpenClawConfig } from "../../config/config.js";
import type { SessionEntry } from "../../config/sessions.js";
import type { MsgContext, TemplateContext } from "../templating.js";
import { loadModelCatalog } from "../../agents/model-catalog.js";
import {
buildAllowedModelSet,
modelKey,
normalizeProviderId,
resolveModelRefFromString,
type ModelAliasIndex,
} from "../../agents/model-selection.js";
import { updateSessionStore } from "../../config/sessions.js";
import { applyModelOverrideToSessionEntry } from "../../sessions/model-overrides.js";
import { formatInboundBodyWithSenderMeta } from "./inbound-sender-meta.js";
import { resolveModelDirectiveSelection, type ModelDirectiveSelection } from "./model-selection.js";
type ResetModelResult = {
selection?: ModelDirectiveSelection;
cleanedBody?: string;
};
function splitBody(body: string) {
const tokens = body.split(/\s+/).filter(Boolean);
return {
tokens,
first: tokens[0],
second: tokens[1],
rest: tokens.slice(2),
};
}
function buildSelectionFromExplicit(params: {
raw: string;
defaultProvider: string;
defaultModel: string;
aliasIndex: ModelAliasIndex;
allowedModelKeys: Set<string>;
}): ModelDirectiveSelection | undefined {
const resolved = resolveModelRefFromString({
raw: params.raw,
defaultProvider: params.defaultProvider,
aliasIndex: params.aliasIndex,
});
if (!resolved) {
return undefined;
}
const key = modelKey(resolved.ref.provider, resolved.ref.model);
if (params.allowedModelKeys.size > 0 && !params.allowedModelKeys.has(key)) {
return undefined;
}
const isDefault =
resolved.ref.provider === params.defaultProvider && resolved.ref.model === params.defaultModel;
return {
provider: resolved.ref.provider,
model: resolved.ref.model,
isDefault,
...(resolved.alias ? { alias: resolved.alias } : undefined),
};
}
function applySelectionToSession(params: {
selection: ModelDirectiveSelection;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionKey?: string;
storePath?: string;
}) {
const { selection, sessionEntry, sessionStore, sessionKey, storePath } = params;
if (!sessionEntry || !sessionStore || !sessionKey) {
return;
}
const { updated } = applyModelOverrideToSessionEntry({
entry: sessionEntry,
selection,
});
if (!updated) {
return;
}
sessionStore[sessionKey] = sessionEntry;
if (storePath) {
updateSessionStore(storePath, (store) => {
store[sessionKey] = sessionEntry;
}).catch(() => {
// Ignore persistence errors; session still proceeds.
});
}
}
export async function applyResetModelOverride(params: {
cfg: OpenClawConfig;
resetTriggered: boolean;
bodyStripped?: string;
sessionCtx: TemplateContext;
ctx: MsgContext;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionKey?: string;
storePath?: string;
defaultProvider: string;
defaultModel: string;
aliasIndex: ModelAliasIndex;
}): Promise<ResetModelResult> {
if (!params.resetTriggered) {
return {};
}
const rawBody = params.bodyStripped?.trim();
if (!rawBody) {
return {};
}
const { tokens, first, second } = splitBody(rawBody);
if (!first) {
return {};
}
const catalog = await loadModelCatalog({ config: params.cfg });
const allowed = buildAllowedModelSet({
cfg: params.cfg,
catalog,
defaultProvider: params.defaultProvider,
defaultModel: params.defaultModel,
});
const allowedModelKeys = allowed.allowedKeys;
if (allowedModelKeys.size === 0) {
return {};
}
const providers = new Set<string>();
for (const key of allowedModelKeys) {
const slash = key.indexOf("/");
if (slash <= 0) {
continue;
}
providers.add(normalizeProviderId(key.slice(0, slash)));
}
const resolveSelection = (raw: string) =>
resolveModelDirectiveSelection({
raw,
defaultProvider: params.defaultProvider,
defaultModel: params.defaultModel,
aliasIndex: params.aliasIndex,
allowedModelKeys,
});
let selection: ModelDirectiveSelection | undefined;
let consumed = 0;
if (providers.has(normalizeProviderId(first)) && second) {
const composite = `${normalizeProviderId(first)}/${second}`;
const resolved = resolveSelection(composite);
if (resolved.selection) {
selection = resolved.selection;
consumed = 2;
}
}
if (!selection) {
selection = buildSelectionFromExplicit({
raw: first,
defaultProvider: params.defaultProvider,
defaultModel: params.defaultModel,
aliasIndex: params.aliasIndex,
allowedModelKeys,
});
if (selection) {
consumed = 1;
}
}
if (!selection) {
const resolved = resolveSelection(first);
const allowFuzzy = providers.has(normalizeProviderId(first)) || first.trim().length >= 6;
if (allowFuzzy) {
selection = resolved.selection;
if (selection) {
consumed = 1;
}
}
}
if (!selection) {
return {};
}
const cleanedBody = tokens.slice(consumed).join(" ").trim();
params.sessionCtx.BodyStripped = formatInboundBodyWithSenderMeta({
ctx: params.ctx,
body: cleanedBody,
});
params.sessionCtx.BodyForCommands = cleanedBody;
applySelectionToSession({
selection,
sessionEntry: params.sessionEntry,
sessionStore: params.sessionStore,
sessionKey: params.sessionKey,
storePath: params.storePath,
});
return { selection, cleanedBody };
}