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 }