Skip to content

Commit be6de7d

Browse files
authored
Merge branch 'main' into feat/334-code-action-localization
2 parents bd43ee8 + cef0cc3 commit be6de7d

2 files changed

Lines changed: 61 additions & 2 deletions

File tree

src/api/providers/__tests__/gemini.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,38 @@ describe("GeminiHandler", () => {
174174
expect(modelInfo.id).toBe(geminiDefaultModelId) // Default model
175175
})
176176

177+
it("should honor a custom gemini model id not present in geminiModels (#227)", () => {
178+
const customHandler = new GeminiHandler({
179+
apiModelId: "gemini-3.5-flash",
180+
geminiApiKey: "test-key",
181+
})
182+
const modelInfo = customHandler.getModel()
183+
// The configured id must be invoked, not silently swapped for the default.
184+
expect(modelInfo.id).toBe("gemini-3.5-flash")
185+
expect(modelInfo.id).not.toBe(geminiDefaultModelId)
186+
// A baseline ModelInfo is provided so downstream params resolve.
187+
expect(modelInfo.info).toBeDefined()
188+
// Pricing is unknown for a custom model, so cost should not be reported
189+
// against the default model's rates.
190+
expect(modelInfo.info.inputPrice).toBeUndefined()
191+
expect(modelInfo.info.outputPrice).toBeUndefined()
192+
expect(modelInfo.info.cacheReadsPrice).toBeUndefined()
193+
expect(modelInfo.info.cacheWritesPrice).toBeUndefined()
194+
expect(modelInfo.info.tiers).toBeUndefined()
195+
})
196+
197+
it("should not treat Object prototype keys as known models", () => {
198+
// `"toString" in geminiModels` is true via the prototype chain, which would
199+
// otherwise resolve `info` to a function. An own-property check avoids this.
200+
const protoHandler = new GeminiHandler({
201+
apiModelId: "toString",
202+
geminiApiKey: "test-key",
203+
})
204+
const modelInfo = protoHandler.getModel()
205+
expect(modelInfo.id).toBe(geminiDefaultModelId)
206+
expect(modelInfo.info).toBeDefined()
207+
})
208+
177209
it("should exclude apply_diff and include edit in tool preferences", () => {
178210
const modelInfo = handler.getModel()
179211
expect(modelInfo.info.excludedTools).toContain("apply_diff")

src/api/providers/gemini.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,35 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
490490

491491
override getModel() {
492492
const modelId = this.options.apiModelId
493-
let id = modelId && modelId in geminiModels ? (modelId as GeminiModelId) : geminiDefaultModelId
494-
let info: ModelInfo = geminiModels[id]
493+
let id: string
494+
let info: ModelInfo
495+
496+
if (modelId && Object.hasOwn(geminiModels, modelId)) {
497+
id = modelId
498+
info = geminiModels[modelId as GeminiModelId]
499+
} else if (modelId && modelId.toLowerCase().startsWith("gemini-")) {
500+
// Honor a custom/unlisted Gemini model id (e.g. a newly released model
501+
// not yet in `geminiModels`) instead of silently falling back to the
502+
// default. This mirrors the settings UI's "use custom model" option and
503+
// the `useSelectedModel` hook, which both keep the configured id. Ids
504+
// that don't look like Gemini models still fall back below.
505+
id = modelId
506+
// Use the default model's structural info as a baseline, but drop the
507+
// pricing fields we can't verify for an unknown model so cost reporting
508+
// shows "unknown" (calculateCost returns undefined) instead of charging
509+
// the default model's rates against a different model.
510+
info = {
511+
...geminiModels[geminiDefaultModelId],
512+
inputPrice: undefined,
513+
outputPrice: undefined,
514+
cacheReadsPrice: undefined,
515+
cacheWritesPrice: undefined,
516+
tiers: undefined,
517+
}
518+
} else {
519+
id = geminiDefaultModelId
520+
info = geminiModels[geminiDefaultModelId]
521+
}
495522

496523
const params = getModelParams({
497524
format: "gemini",

0 commit comments

Comments
 (0)