Files
openclaw/src/agents/model-thinking-default.ts
Gio Della-Libera 8c9ec0724e fix(agents): honor disabled reasoning in thinking policy (#81454)
* fix(agents): honor disabled reasoning in thinking policy

* test: refresh thinking policy CI fixtures

* test: align thinking policy CI guardrails

---------

Co-authored-by: Gio Della-Libera <giodl@microsoft.com>
2026-05-15 22:33:43 -07:00

112 lines
4.4 KiB
TypeScript

import { resolveThinkingDefaultForModel } from "../auto-reply/thinking.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "../shared/string-coerce.js";
import type { ModelCatalogEntry } from "./model-catalog.types.js";
import { legacyModelKey, modelKey, normalizeProviderId } from "./model-selection-normalize.js";
import { normalizeModelSelection } from "./model-selection-resolve.js";
import { buildConfiguredModelCatalog } from "./model-selection-shared.js";
type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive" | "max";
export function resolveThinkingDefault(params: {
cfg: OpenClawConfig;
provider: string;
model: string;
catalog?: ModelCatalogEntry[];
}): ThinkLevel {
const normalizedProvider = normalizeProviderId(params.provider);
const normalizedModel = normalizeLowercaseStringOrEmpty(params.model).replace(/\./g, "-");
const catalog = Array.isArray(params.catalog)
? params.catalog
: buildConfiguredModelCatalog({ cfg: params.cfg });
const catalogCandidate = catalog.find(
(entry) => entry.provider === params.provider && entry.id === params.model,
);
const configuredModels = params.cfg.agents?.defaults?.models;
const canonicalKey = modelKey(params.provider, params.model);
const legacyKey = legacyModelKey(params.provider, params.model);
const normalizedCanonicalKey = normalizeLowercaseStringOrEmpty(canonicalKey);
const normalizedLegacyKey = normalizeOptionalLowercaseString(legacyKey);
const primarySelection = normalizeModelSelection(params.cfg.agents?.defaults?.model);
const normalizedPrimarySelection = normalizeOptionalLowercaseString(primarySelection);
const explicitModelConfigured =
(configuredModels ? canonicalKey in configuredModels : false) ||
Boolean(legacyKey && configuredModels && legacyKey in configuredModels) ||
normalizedPrimarySelection === normalizedCanonicalKey ||
Boolean(normalizedLegacyKey && normalizedPrimarySelection === normalizedLegacyKey) ||
normalizedPrimarySelection === normalizeLowercaseStringOrEmpty(params.model);
const perModelThinking =
configuredModels?.[canonicalKey]?.params?.thinking ??
(legacyKey ? configuredModels?.[legacyKey]?.params?.thinking : undefined);
if (
perModelThinking === "off" ||
perModelThinking === "minimal" ||
perModelThinking === "low" ||
perModelThinking === "medium" ||
perModelThinking === "high" ||
perModelThinking === "xhigh" ||
perModelThinking === "adaptive" ||
perModelThinking === "max"
) {
return perModelThinking;
}
const configured = params.cfg.agents?.defaults?.thinkingDefault;
if (configured) {
return configured;
}
if (
normalizedProvider === "anthropic" &&
(normalizedModel.startsWith("claude-opus-4-7") || normalizedModel.startsWith("claude-opus-4.7"))
) {
return "off";
}
if (
normalizedProvider === "anthropic" &&
explicitModelConfigured &&
typeof catalogCandidate?.name === "string" &&
/4\.6\b/.test(catalogCandidate.name) &&
(normalizedModel.startsWith("claude-opus-4-6") ||
normalizedModel.startsWith("claude-sonnet-4-6"))
) {
return "adaptive";
}
return resolveThinkingDefaultForModel({
provider: params.provider,
model: params.model,
catalog,
});
}
export async function resolveThinkingDefaultWithRuntimeCatalog(params: {
cfg: OpenClawConfig;
provider: string;
model: string;
loadModelCatalog: () => Promise<ModelCatalogEntry[]>;
}): Promise<ThinkLevel> {
const configuredCatalog = buildConfiguredModelCatalog({ cfg: params.cfg });
const configuredSelectedEntry = configuredCatalog.find(
(entry) => entry.provider === params.provider && entry.id === params.model,
);
const needsRuntimeCatalog =
configuredCatalog.length === 0 ||
!configuredSelectedEntry ||
configuredSelectedEntry.reasoning === undefined;
const runtimeCatalog = needsRuntimeCatalog ? await params.loadModelCatalog() : undefined;
const runtimeSelectedEntry = runtimeCatalog?.find(
(entry) => entry.provider === params.provider && entry.id === params.model,
);
const catalog =
runtimeSelectedEntry || configuredCatalog.length === 0
? (runtimeCatalog ?? configuredCatalog)
: configuredCatalog;
return resolveThinkingDefault({
cfg: params.cfg,
provider: params.provider,
model: params.model,
catalog,
});
}