fix: add google provider model transform for gemini-3-flash/pro preview suffix

transformModelForProvider only handled github-copilot provider, leaving
google provider models untransformed. This caused ProviderModelNotFoundError
when google/gemini-3-flash was sent to the API (correct ID is
gemini-3-flash-preview).

Add google provider block with -preview suffix guard to prevent double
transformation.
This commit is contained in:
once
2026-02-17 21:15:38 +09:00
committed by YeonGyu-Kim
parent 31dc65e9ac
commit e5a0ab4034
3 changed files with 219 additions and 44 deletions

View File

@@ -334,48 +334,48 @@ exports[`generateModelConfig single native provider uses Gemini models when only
"model": "opencode/minimax-m2.5-free",
},
"metis": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"momus": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"multimodal-looker": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"oracle": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"prometheus": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
},
},
"categories": {
"artistry": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"quick": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"ultrabrain": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"unspecified-high": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"unspecified-low": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"visual-engineering": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"writing": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
},
}
@@ -395,48 +395,48 @@ exports[`generateModelConfig single native provider uses Gemini models with isMa
"model": "opencode/minimax-m2.5-free",
},
"metis": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"momus": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"multimodal-looker": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"oracle": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"prometheus": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
},
},
"categories": {
"artistry": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"quick": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"ultrabrain": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"unspecified-high": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
},
"unspecified-low": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"visual-engineering": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"writing": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
},
}
@@ -468,7 +468,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
"variant": "medium",
},
"multimodal-looker": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"oracle": {
"model": "openai/gpt-5.2",
@@ -485,7 +485,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
},
"categories": {
"artistry": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"deep": {
@@ -506,11 +506,11 @@ exports[`generateModelConfig all native providers uses preferred models from fal
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"writing": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
},
}
@@ -542,7 +542,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
"variant": "medium",
},
"multimodal-looker": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"oracle": {
"model": "openai/gpt-5.2",
@@ -559,7 +559,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
},
"categories": {
"artistry": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"deep": {
@@ -581,11 +581,11 @@ exports[`generateModelConfig all native providers uses preferred models with isM
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"writing": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
},
}
@@ -1230,10 +1230,10 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi
"variant": "max",
},
"multimodal-looker": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
"oracle": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"prometheus": {
@@ -1247,14 +1247,14 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi
},
"categories": {
"artistry": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"quick": {
"model": "anthropic/claude-haiku-4-5",
},
"ultrabrain": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"unspecified-high": {
@@ -1264,11 +1264,11 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"writing": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
},
}
@@ -1391,7 +1391,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
},
"categories": {
"artistry": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"deep": {
@@ -1412,11 +1412,11 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"writing": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
},
}
@@ -1465,7 +1465,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
},
"categories": {
"artistry": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"deep": {
@@ -1487,11 +1487,11 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3-pro",
"model": "google/gemini-3-pro-preview",
"variant": "high",
},
"writing": {
"model": "google/gemini-3-flash",
"model": "google/gemini-3-flash-preview",
},
},
}

View File

@@ -0,0 +1,167 @@
import { describe, expect, test } from "bun:test"
import { transformModelForProvider } from "./provider-model-id-transform"
describe("transformModelForProvider", () => {
describe("github-copilot provider", () => {
test("transforms claude-opus-4-6 to claude-opus-4.6", () => {
// #given github-copilot provider and claude-opus-4-6 model
const provider = "github-copilot"
const model = "claude-opus-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-opus-4.6
expect(result).toBe("claude-opus-4.6")
})
test("transforms claude-sonnet-4-5 to claude-sonnet-4.5", () => {
// #given github-copilot provider and claude-sonnet-4-5 model
const provider = "github-copilot"
const model = "claude-sonnet-4-5"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-sonnet-4.5
expect(result).toBe("claude-sonnet-4.5")
})
test("transforms claude-haiku-4-5 to claude-haiku-4.5", () => {
// #given github-copilot provider and claude-haiku-4-5 model
const provider = "github-copilot"
const model = "claude-haiku-4-5"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-haiku-4.5
expect(result).toBe("claude-haiku-4.5")
})
test("transforms gemini-3-pro to gemini-3-pro-preview", () => {
// #given github-copilot provider and gemini-3-pro model
const provider = "github-copilot"
const model = "gemini-3-pro"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3-pro-preview
expect(result).toBe("gemini-3-pro-preview")
})
test("transforms gemini-3-flash to gemini-3-flash-preview", () => {
// #given github-copilot provider and gemini-3-flash model
const provider = "github-copilot"
const model = "gemini-3-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3-flash-preview
expect(result).toBe("gemini-3-flash-preview")
})
})
describe("google provider", () => {
test("transforms gemini-3-flash to gemini-3-flash-preview", () => {
// #given google provider and gemini-3-flash model
const provider = "google"
const model = "gemini-3-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3-flash-preview
expect(result).toBe("gemini-3-flash-preview")
})
test("transforms gemini-3-pro to gemini-3-pro-preview", () => {
// #given google provider and gemini-3-pro model
const provider = "google"
const model = "gemini-3-pro"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3-pro-preview
expect(result).toBe("gemini-3-pro-preview")
})
test("passes through other gemini models unchanged", () => {
// #given google provider and gemini-2.5-flash model
const provider = "google"
const model = "gemini-2.5-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged
expect(result).toBe("gemini-2.5-flash")
})
test("prevents double transformation of gemini-3-flash-preview", () => {
// #given google provider and gemini-3-flash-preview model (already transformed)
const provider = "google"
const model = "gemini-3-flash-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3-flash-preview-preview
expect(result).toBe("gemini-3-flash-preview")
})
test("prevents double transformation of gemini-3-pro-preview", () => {
// #given google provider and gemini-3-pro-preview model (already transformed)
const provider = "google"
const model = "gemini-3-pro-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3-pro-preview-preview
expect(result).toBe("gemini-3-pro-preview")
})
test("does not transform claude models for google provider", () => {
// #given google provider and claude-opus-4-6 model
const provider = "google"
const model = "claude-opus-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged (google doesn't use claude)
expect(result).toBe("claude-opus-4-6")
})
})
describe("unknown provider", () => {
test("passes model through unchanged for unknown provider", () => {
// #given unknown provider and any model
const provider = "unknown-provider"
const model = "some-model"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged
expect(result).toBe("some-model")
})
test("passes gemini-3-flash through unchanged for unknown provider", () => {
// #given unknown provider and gemini-3-flash model
const provider = "unknown-provider"
const model = "gemini-3-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged (no transformation for unknown provider)
expect(result).toBe("gemini-3-flash")
})
})
})

View File

@@ -8,5 +8,13 @@ export function transformModelForProvider(provider: string, model: string): stri
.replace("gemini-3-pro", "gemini-3-pro-preview")
.replace("gemini-3-flash", "gemini-3-flash-preview")
}
if (provider === "google") {
if (!model.endsWith("-preview")) {
return model
.replace("gemini-3-pro", "gemini-3-pro-preview")
.replace("gemini-3-flash", "gemini-3-flash-preview")
}
return model
}
return model
}