fix(agents): honor per-model thinking defaults

This commit is contained in:
Mark L
2026-03-01 16:05:48 +08:00
committed by Peter Steinberger
parent c9f0d6ac8e
commit 28d0576fd1
9 changed files with 110 additions and 27 deletions

View File

@@ -11,6 +11,7 @@ import {
modelKey,
resolveAllowedModelRef,
resolveConfiguredModelRef,
resolveThinkingDefault,
resolveModelRefFromString,
} from "./model-selection.js";
@@ -470,6 +471,32 @@ describe("model-selection", () => {
expect(result).toEqual({ provider: "openai", model: "gpt-4" });
});
});
describe("resolveThinkingDefault", () => {
it("prefers per-model params.thinking over global thinkingDefault", () => {
const cfg = {
agents: {
defaults: {
thinkingDefault: "low",
models: {
"anthropic/claude-opus-4-6": {
params: { thinking: "high" },
},
},
},
},
} as OpenClawConfig;
expect(
resolveThinkingDefault({
cfg,
provider: "anthropic",
model: "claude-opus-4-6",
catalog: [{ provider: "anthropic", id: "claude-opus-4-6", reasoning: true }],
}),
).toBe("high");
});
});
});
describe("normalizeModelSelection", () => {

View File

@@ -525,6 +525,19 @@ export function resolveThinkingDefault(params: {
model: string;
catalog?: ModelCatalogEntry[];
}): ThinkLevel {
const perModelThinking =
params.cfg.agents?.defaults?.models?.[modelKey(params.provider, params.model)]?.params
?.thinking;
if (
perModelThinking === "off" ||
perModelThinking === "minimal" ||
perModelThinking === "low" ||
perModelThinking === "medium" ||
perModelThinking === "high" ||
perModelThinking === "xhigh"
) {
return perModelThinking;
}
const configured = params.cfg.agents?.defaults?.thinkingDefault;
if (configured) {
return configured;

View File

@@ -0,0 +1,36 @@
import { describe, expect, it, vi } from "vitest";
import { resolveCurrentDirectiveLevels } from "./directive-handling.levels.js";
describe("resolveCurrentDirectiveLevels", () => {
it("prefers resolved model default over agent thinkingDefault", async () => {
const resolveDefaultThinkingLevel = vi.fn().mockResolvedValue("high");
const result = await resolveCurrentDirectiveLevels({
sessionEntry: {},
agentCfg: {
thinkingDefault: "low",
},
resolveDefaultThinkingLevel,
});
expect(result.currentThinkLevel).toBe("high");
expect(resolveDefaultThinkingLevel).toHaveBeenCalledTimes(1);
});
it("keeps session thinking override without consulting defaults", async () => {
const resolveDefaultThinkingLevel = vi.fn().mockResolvedValue("high");
const result = await resolveCurrentDirectiveLevels({
sessionEntry: {
thinkingLevel: "minimal",
},
agentCfg: {
thinkingDefault: "low",
},
resolveDefaultThinkingLevel,
});
expect(result.currentThinkLevel).toBe("minimal");
expect(resolveDefaultThinkingLevel).not.toHaveBeenCalled();
});
});

View File

@@ -21,8 +21,8 @@ export async function resolveCurrentDirectiveLevels(params: {
}> {
const resolvedDefaultThinkLevel =
(params.sessionEntry?.thinkingLevel as ThinkLevel | undefined) ??
(params.agentCfg?.thinkingDefault as ThinkLevel | undefined) ??
(await params.resolveDefaultThinkingLevel());
(await params.resolveDefaultThinkingLevel()) ??
(params.agentCfg?.thinkingDefault as ThinkLevel | undefined);
const currentThinkLevel = resolvedDefaultThinkLevel;
const currentVerboseLevel =
(params.sessionEntry?.verboseLevel as VerboseLevel | undefined) ??

View File

@@ -339,9 +339,7 @@ export async function resolveReplyDirectives(params: {
});
const defaultActivation = defaultGroupActivation(requireMention);
const resolvedThinkLevel =
directives.thinkLevel ??
(sessionEntry?.thinkingLevel as ThinkLevel | undefined) ??
(agentCfg?.thinkingDefault as ThinkLevel | undefined);
directives.thinkLevel ?? (sessionEntry?.thinkingLevel as ThinkLevel | undefined);
const resolvedVerboseLevel =
directives.verboseLevel ??

View File

@@ -734,6 +734,25 @@ describe("agentCommand", () => {
});
});
it("prefers per-model thinking over global thinkingDefault", async () => {
await withTempHome(async (home) => {
const store = path.join(home, "sessions.json");
mockConfig(home, store, {
thinkingDefault: "low",
models: {
"anthropic/claude-opus-4-5": {
params: { thinking: "high" },
},
},
});
await agentCommand({ message: "hi", to: "+1555" }, runtime);
const callArgs = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0];
expect(callArgs?.thinkLevel).toBe("high");
});
});
it("prints JSON payload when requested", async () => {
await withTempHome(async (home) => {
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({

View File

@@ -588,11 +588,7 @@ export async function agentCommand(
});
}
let resolvedThinkLevel =
thinkOnce ??
thinkOverride ??
persistedThinking ??
(agentCfg?.thinkingDefault as ThinkLevel | undefined);
let resolvedThinkLevel = thinkOnce ?? thinkOverride ?? persistedThinking;
const resolvedVerboseLevel =
verboseOverride ?? persistedVerbose ?? (agentCfg?.verboseDefault as VerboseLevel | undefined);

View File

@@ -299,16 +299,15 @@ export async function runCronIsolatedAgentTurn(params: {
}
}
// Resolve thinking level - job thinking > hooks.gmail.thinking > agent default
// Resolve thinking level - job thinking > hooks.gmail.thinking > model/global defaults
const hooksGmailThinking = isGmailHook
? normalizeThinkLevel(params.cfg.hooks?.gmail?.thinking)
: undefined;
const thinkOverride = normalizeThinkLevel(agentCfg?.thinkingDefault);
const jobThink = normalizeThinkLevel(
(params.job.payload.kind === "agentTurn" ? params.job.payload.thinking : undefined) ??
undefined,
);
let thinkLevel = jobThink ?? hooksGmailThinking ?? thinkOverride;
let thinkLevel = jobThink ?? hooksGmailThinking;
if (!thinkLevel) {
thinkLevel = resolveThinkingDefault({
cfg: cfgWithAgentDefaults,

View File

@@ -574,20 +574,15 @@ export const chatHandlers: GatewayRequestHandlers = {
}
let thinkingLevel = entry?.thinkingLevel;
if (!thinkingLevel) {
const configured = cfg.agents?.defaults?.thinkingDefault;
if (configured) {
thinkingLevel = configured;
} else {
const sessionAgentId = resolveSessionAgentId({ sessionKey, config: cfg });
const { provider, model } = resolveSessionModelRef(cfg, entry, sessionAgentId);
const catalog = await context.loadGatewayModelCatalog();
thinkingLevel = resolveThinkingDefault({
cfg,
provider,
model,
catalog,
});
}
const sessionAgentId = resolveSessionAgentId({ sessionKey, config: cfg });
const { provider, model } = resolveSessionModelRef(cfg, entry, sessionAgentId);
const catalog = await context.loadGatewayModelCatalog();
thinkingLevel = resolveThinkingDefault({
cfg,
provider,
model,
catalog,
});
}
const verboseLevel = entry?.verboseLevel ?? cfg.agents?.defaults?.verboseDefault;
respond(true, {