Compare commits

...

1 Commits

Author SHA1 Message Date
YeonGyu-Kim
9ca259dcdc fix(runtime-fallback): preserve agent variant and reasoningEffort on model fallback (fixes #2621)
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 <noreply@anthropic.com>
2026-03-18 12:11:01 +09:00
3 changed files with 141 additions and 16 deletions

View File

@@ -101,7 +101,13 @@ export function createAutoRetryHelpers(deps: HookDeps) {
return 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) { if (!retryModelPayload) {
log(`[${HOOK_NAME}] Invalid model format (missing provider prefix): ${newModel}`) log(`[${HOOK_NAME}] Invalid model format (missing provider prefix): ${newModel}`)
const state = sessionStates.get(sessionID) const state = sessionStates.get(sessionID)

View File

@@ -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",
})
})
})

View File

@@ -2,24 +2,29 @@ import { parseModelString } from "../../tools/delegate-task/model-string-parser"
export function buildRetryModelPayload( export function buildRetryModelPayload(
model: string, 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) const parsedModel = parseModelString(model)
if (!parsedModel) { if (!parsedModel) {
return undefined return undefined
} }
return parsedModel.variant const variant = parsedModel.variant ?? agentSettings?.variant
? { const reasoningEffort = agentSettings?.reasoningEffort
model: {
providerID: parsedModel.providerID, const payload: { model: { providerID: string; modelID: string }; variant?: string; reasoningEffort?: string } = {
modelID: parsedModel.modelID,
},
variant: parsedModel.variant,
}
: {
model: { model: {
providerID: parsedModel.providerID, providerID: parsedModel.providerID,
modelID: parsedModel.modelID, modelID: parsedModel.modelID,
}, },
} }
if (variant) {
payload.variant = variant
}
if (reasoningEffort) {
payload.reasoningEffort = reasoningEffort
}
return payload
} }