From 9ca259dcdc837ac5706aaaa49ca4cf55dbfc8d69 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 18 Mar 2026 12:11:01 +0900 Subject: [PATCH] fix(runtime-fallback): preserve agent variant and reasoningEffort on model fallback (fixes #2621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When runtime fallback switches to a different model, the agent's configured variant and reasoningEffort were lost because buildRetryModelPayload only extracted variant from the fallback model string itself. Now buildRetryModelPayload accepts optional agentSettings and uses the agent's variant as fallback when the model string doesn't include one. reasoningEffort is also passed through. 🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) Co-Authored-By: Claude Opus 4.6 --- src/hooks/runtime-fallback/auto-retry.ts | 8 +- .../retry-model-payload.test.ts | 114 ++++++++++++++++++ .../runtime-fallback/retry-model-payload.ts | 35 +++--- 3 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 src/hooks/runtime-fallback/retry-model-payload.test.ts diff --git a/src/hooks/runtime-fallback/auto-retry.ts b/src/hooks/runtime-fallback/auto-retry.ts index e521037d5..43d4e1b5f 100644 --- a/src/hooks/runtime-fallback/auto-retry.ts +++ b/src/hooks/runtime-fallback/auto-retry.ts @@ -101,7 +101,13 @@ export function createAutoRetryHelpers(deps: HookDeps) { return } - const retryModelPayload = buildRetryModelPayload(newModel) + const agentSettings = resolvedAgent + ? pluginConfig?.agents?.[resolvedAgent as keyof typeof pluginConfig.agents] + : undefined + const retryModelPayload = buildRetryModelPayload(newModel, agentSettings ? { + variant: agentSettings.variant, + reasoningEffort: agentSettings.reasoningEffort, + } : undefined) if (!retryModelPayload) { log(`[${HOOK_NAME}] Invalid model format (missing provider prefix): ${newModel}`) const state = sessionStates.get(sessionID) diff --git a/src/hooks/runtime-fallback/retry-model-payload.test.ts b/src/hooks/runtime-fallback/retry-model-payload.test.ts new file mode 100644 index 000000000..06a0e2af1 --- /dev/null +++ b/src/hooks/runtime-fallback/retry-model-payload.test.ts @@ -0,0 +1,114 @@ +import { describe, test, expect } from "bun:test" +import { buildRetryModelPayload } from "./retry-model-payload" + +describe("buildRetryModelPayload", () => { + test("should return undefined for empty model string", () => { + // given + const model = "" + + // when + const result = buildRetryModelPayload(model) + + // then + expect(result).toBeUndefined() + }) + + test("should return undefined for model without provider prefix", () => { + // given + const model = "kimi-k2.5" + + // when + const result = buildRetryModelPayload(model) + + // then + expect(result).toBeUndefined() + }) + + test("should parse provider and model ID", () => { + // given + const model = "chutes/kimi-k2.5" + + // when + const result = buildRetryModelPayload(model) + + // then + expect(result).toEqual({ + model: { providerID: "chutes", modelID: "kimi-k2.5" }, + }) + }) + + test("should include variant from model string", () => { + // given + const model = "anthropic/claude-sonnet-4-5 high" + + // when + const result = buildRetryModelPayload(model) + + // then + expect(result).toEqual({ + model: { providerID: "anthropic", modelID: "claude-sonnet-4-5" }, + variant: "high", + }) + }) + + test("should use agent variant when model string has no variant", () => { + // given + const model = "chutes/kimi-k2.5" + const agentSettings = { variant: "max" } + + // when + const result = buildRetryModelPayload(model, agentSettings) + + // then + expect(result).toEqual({ + model: { providerID: "chutes", modelID: "kimi-k2.5" }, + variant: "max", + }) + }) + + test("should prefer model string variant over agent variant", () => { + // given + const model = "anthropic/claude-sonnet-4-5 high" + const agentSettings = { variant: "max" } + + // when + const result = buildRetryModelPayload(model, agentSettings) + + // then + expect(result).toEqual({ + model: { providerID: "anthropic", modelID: "claude-sonnet-4-5" }, + variant: "high", + }) + }) + + test("should include reasoningEffort from agent settings", () => { + // given + const model = "openai/gpt-5.4" + const agentSettings = { variant: "high", reasoningEffort: "xhigh" } + + // when + const result = buildRetryModelPayload(model, agentSettings) + + // then + expect(result).toEqual({ + model: { providerID: "openai", modelID: "gpt-5.4" }, + variant: "high", + reasoningEffort: "xhigh", + }) + }) + + test("should not include reasoningEffort when agent settings has none", () => { + // given + const model = "chutes/kimi-k2.5" + const agentSettings = { variant: "medium" } + + // when + const result = buildRetryModelPayload(model, agentSettings) + + // then + expect(result).toEqual({ + model: { providerID: "chutes", modelID: "kimi-k2.5" }, + variant: "medium", + }) + }) +}) diff --git a/src/hooks/runtime-fallback/retry-model-payload.ts b/src/hooks/runtime-fallback/retry-model-payload.ts index 17d04aa90..0c9ed0c9a 100644 --- a/src/hooks/runtime-fallback/retry-model-payload.ts +++ b/src/hooks/runtime-fallback/retry-model-payload.ts @@ -2,24 +2,29 @@ import { parseModelString } from "../../tools/delegate-task/model-string-parser" export function buildRetryModelPayload( model: string, -): { model: { providerID: string; modelID: string }; variant?: string } | undefined { + agentSettings?: { variant?: string; reasoningEffort?: string }, +): { model: { providerID: string; modelID: string }; variant?: string; reasoningEffort?: string } | undefined { const parsedModel = parseModelString(model) if (!parsedModel) { return undefined } - return parsedModel.variant - ? { - model: { - providerID: parsedModel.providerID, - modelID: parsedModel.modelID, - }, - variant: parsedModel.variant, - } - : { - model: { - providerID: parsedModel.providerID, - modelID: parsedModel.modelID, - }, - } + const variant = parsedModel.variant ?? agentSettings?.variant + const reasoningEffort = agentSettings?.reasoningEffort + + const payload: { model: { providerID: string; modelID: string }; variant?: string; reasoningEffort?: string } = { + model: { + providerID: parsedModel.providerID, + modelID: parsedModel.modelID, + }, + } + + if (variant) { + payload.variant = variant + } + if (reasoningEffort) { + payload.reasoningEffort = reasoningEffort + } + + return payload }