Merge pull request #2080 from Firstbober/dev

fix: model format normalization and explicit config cache bypass
This commit is contained in:
YeonGyu-Kim
2026-03-02 23:28:06 +09:00
committed by GitHub
4 changed files with 75 additions and 7 deletions

View File

@@ -0,0 +1,46 @@
import { describe, it, expect } from "bun:test"
import { normalizeModelFormat } from "./model-format-normalizer"
describe("normalizeModelFormat", () => {
describe("string format input", () => {
it("splits provider/model format correctly", () => {
const result = normalizeModelFormat("opencode/glm-5-free")
expect(result).toEqual({ providerID: "opencode", modelID: "glm-5-free" })
})
it("handles provider with multiple slashes", () => {
const result = normalizeModelFormat("anthropic/claude-opus-4-6/max")
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6/max" })
})
it("returns undefined for malformed string without separator", () => {
const result = normalizeModelFormat("invalid")
expect(result).toBeUndefined()
})
it("returns undefined for empty string", () => {
const result = normalizeModelFormat("")
expect(result).toBeUndefined()
})
})
describe("object format input", () => {
it("passthroughs object format unchanged", () => {
const input = { providerID: "opencode", modelID: "glm-5-free" }
const result = normalizeModelFormat(input)
expect(result).toEqual(input)
})
})
describe("edge cases", () => {
it("returns undefined for null", () => {
const result = normalizeModelFormat(null)
expect(result).toBeUndefined()
})
it("returns undefined for undefined", () => {
const result = normalizeModelFormat(undefined)
expect(result).toBeUndefined()
})
})
})

View File

@@ -0,0 +1,20 @@
export function normalizeModelFormat(
model: string | { providerID: string; modelID: string }
): { providerID: string; modelID: string } | undefined {
if (!model) {
return undefined
}
if (typeof model === "object" && "providerID" in model && "modelID" in model) {
return { providerID: model.providerID, modelID: model.modelID }
}
if (typeof model === "string") {
const parts = model.split("/")
if (parts.length >= 2) {
return { providerID: parts[0], modelID: parts.slice(1).join("/") }
}
}
return undefined
}

View File

@@ -34,6 +34,7 @@ export type ModelResolutionResult = {
variant?: string
attempted?: string[]
reason?: string
explicitUserConfig?: boolean
}
@@ -55,7 +56,7 @@ export function resolveModelPipeline(
const normalizedUserModel = normalizeModel(intent?.userModel)
if (normalizedUserModel) {
log("Model resolved via config override", { model: normalizedUserModel })
return { model: normalizedUserModel, provenance: "override" }
return { model: normalizedUserModel, provenance: "override", explicitUserConfig: true }
}
const normalizedCategoryDefault = normalizeModel(intent?.categoryDefaultModel)

View File

@@ -2,7 +2,7 @@ import type { DelegateTaskArgs } from "./types"
import type { ExecutorContext } from "./executor-types"
import { isPlanFamily } from "./constants"
import { SISYPHUS_JUNIOR_AGENT } from "./sisyphus-junior-agent"
import { parseModelString } from "./model-string-parser"
import { normalizeModelFormat } from "../../shared/model-format-normalizer"
import { AGENT_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
import { getAgentDisplayName, getAgentConfigKey } from "../../shared/agent-display-names"
import { normalizeSDKResponse } from "../../shared"
@@ -99,8 +99,9 @@ Create the work plan directly - that's your job as the planning agent.`,
if (agentOverride?.model || agentRequirement || matchedAgent.model) {
const availableModels = await getAvailableModelsForDelegateTask(client)
const matchedAgentModelStr = matchedAgent.model
? `${matchedAgent.model.providerID}/${matchedAgent.model.modelID}`
const normalizedMatchedModel = normalizeModelFormat(matchedAgent.model as Parameters<typeof normalizeModelFormat>[0])
const matchedAgentModelStr = normalizedMatchedModel
? `${normalizedMatchedModel.providerID}/${normalizedMatchedModel.modelID}`
: undefined
const resolution = resolveModelForDelegateTask({
@@ -112,10 +113,10 @@ Create the work plan directly - that's your job as the planning agent.`,
})
if (resolution) {
const parsed = parseModelString(resolution.model)
if (parsed) {
const normalized = normalizeModelFormat(resolution.model)
if (normalized) {
const variantToUse = agentOverride?.variant ?? resolution.variant
categoryModel = variantToUse ? { ...parsed, variant: variantToUse } : parsed
categoryModel = variantToUse ? { ...normalized, variant: variantToUse } : normalized
}
}
}