diff --git a/src/agents/agent-command.ts b/src/agents/agent-command.ts index 993a253156f..319bdf88800 100644 --- a/src/agents/agent-command.ts +++ b/src/agents/agent-command.ts @@ -48,6 +48,7 @@ import { resolveAgentRunContext } from "./command/run-context.js"; import { resolveSession } from "./command/session.js"; import type { AgentCommandIngressOpts, AgentCommandOpts } from "./command/types.js"; import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js"; +import { resolveFastModeState } from "./fast-mode.js"; import { AGENT_LANE_SUBAGENT } from "./lanes.js"; import { LiveSessionModelSwitchError } from "./live-model-switch.js"; import { loadModelCatalog } from "./model-catalog.js"; @@ -978,6 +979,13 @@ async function agentCommandInternal( body, isFallbackRetry, resolvedThinkLevel, + fastMode: resolveFastModeState({ + cfg, + provider: providerOverride, + model: modelOverride, + agentId: sessionAgentId, + sessionEntry, + }).enabled, timeoutMs, runId, opts, diff --git a/src/agents/command/attempt-execution.ts b/src/agents/command/attempt-execution.ts index 7ad64588577..99aa33fbb79 100644 --- a/src/agents/command/attempt-execution.ts +++ b/src/agents/command/attempt-execution.ts @@ -324,6 +324,7 @@ export function runAgentAttempt(params: { body: string; isFallbackRetry: boolean; resolvedThinkLevel: ThinkLevel; + fastMode?: boolean; timeoutMs: number; runId: string; opts: AgentCommandOpts & { senderIsOwner: boolean }; @@ -577,6 +578,7 @@ export function runAgentAttempt(params: { authProfileId, authProfileIdSource: authProfileId ? harnessAuthSelection.authProfileIdSource : undefined, thinkLevel: params.resolvedThinkLevel, + fastMode: params.fastMode, verboseLevel: params.resolvedVerboseLevel, timeoutMs: params.timeoutMs, runId: params.runId, diff --git a/src/agents/fast-mode.test.ts b/src/agents/fast-mode.test.ts index e4ec70edd22..59428116ee5 100644 --- a/src/agents/fast-mode.test.ts +++ b/src/agents/fast-mode.test.ts @@ -54,6 +54,69 @@ describe("resolveFastModeState", () => { expect(state.source).toBe("config"); }); + it("uses model config when the runtime passes a provider-qualified model ref", () => { + const cfg = { + agents: { + defaults: { + models: { + "openai/gpt-5.5": { params: { fastMode: true } }, + }, + }, + }, + } as OpenClawConfig; + + const state = resolveFastModeState({ + cfg, + provider: "openai", + model: "openai/gpt-5.5", + }); + + expect(state.enabled).toBe(true); + expect(state.source).toBe("config"); + }); + + it("uses canonical provider/model config for slash-containing model ids", () => { + const cfg = { + agents: { + defaults: { + models: { + "openrouter/anthropic/claude-sonnet-4-6": { params: { fastMode: true } }, + }, + }, + }, + } as OpenClawConfig; + + const state = resolveFastModeState({ + cfg, + provider: "openrouter", + model: "anthropic/claude-sonnet-4-6", + }); + + expect(state.enabled).toBe(true); + expect(state.source).toBe("config"); + }); + + it("does not use another provider's slash-containing model config", () => { + const cfg = { + agents: { + defaults: { + models: { + "anthropic/claude-sonnet-4-6": { params: { fastMode: true } }, + }, + }, + }, + } as OpenClawConfig; + + const state = resolveFastModeState({ + cfg, + provider: "openrouter", + model: "anthropic/claude-sonnet-4-6", + }); + + expect(state.enabled).toBe(false); + expect(state.source).toBe("default"); + }); + it("defaults to off when unset", () => { const state = resolveFastModeState({ cfg: {} as OpenClawConfig, diff --git a/src/agents/fast-mode.ts b/src/agents/fast-mode.ts index 4851838fc1c..b9b09220749 100644 --- a/src/agents/fast-mode.ts +++ b/src/agents/fast-mode.ts @@ -2,6 +2,7 @@ import { normalizeFastMode } from "../auto-reply/thinking.shared.js"; import type { SessionEntry } from "../config/sessions.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { resolveAgentConfig } from "./agent-scope.js"; +import { modelKey } from "./model-ref-shared.js"; export type FastModeState = { enabled: boolean; @@ -13,8 +14,8 @@ function resolveConfiguredFastModeRaw(params: { provider: string; model: string; }): unknown { - const modelKey = `${params.provider}/${params.model}`; - const modelConfig = params.cfg?.agents?.defaults?.models?.[modelKey]; + const modelConfig = + params.cfg?.agents?.defaults?.models?.[modelKey(params.provider, params.model)]; return modelConfig?.params?.fastMode ?? modelConfig?.params?.fast_mode; } diff --git a/src/commands/agent.test.ts b/src/commands/agent.test.ts index 30e4e43b753..347561a49bd 100644 --- a/src/commands/agent.test.ts +++ b/src/commands/agent.test.ts @@ -105,6 +105,7 @@ vi.mock("../agents/command/attempt-execution.runtime.js", () => { authProfileId, authProfileIdSource: authProfileId ? sessionEntry?.authProfileOverrideSource : undefined, thinkLevel: params.resolvedThinkLevel, + fastMode: params.fastMode, verboseLevel: params.resolvedVerboseLevel, timeoutMs: params.timeoutMs, runId: params.runId, @@ -385,6 +386,29 @@ describe("agentCommand", () => { }); }); + it("passes configured fast mode to embedded runs", async () => { + await withTempHome(async (home) => { + const store = path.join(home, "sessions.json"); + mockConfig(home, store, { + model: "openai/gpt-5.5", + models: { + "openai/gpt-5.5": { params: { fastMode: true } }, + }, + }); + + await agentCommand({ message: "ping", agentId: "main" }, runtime); + + const callArgs = getLastEmbeddedCall(); + expect(callArgs).toEqual( + expect.objectContaining({ + provider: "openai", + model: "gpt-5.5", + fastMode: true, + }), + ); + }); + }); + it("does not load the full model catalog for trusted explicit overrides without an allowlist", async () => { await withTempHome(async (home) => { const store = path.join(home, "sessions.json");