-
Notifications
You must be signed in to change notification settings - Fork 737
fix(providers): strip models/ prefix for Gemini OpenAI-compat endpoint (#175) #186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { describe, expect, it } from 'vitest'; | ||
| import { isGeminiOpenAICompat, normalizeGeminiModelId } from './gemini-compat'; | ||
|
|
||
| describe('isGeminiOpenAICompat', () => { | ||
| it('detects the official Gemini OpenAI-compat endpoint', () => { | ||
| expect(isGeminiOpenAICompat('https://generativelanguage.googleapis.com/v1beta/openai/')).toBe( | ||
| true, | ||
| ); | ||
| }); | ||
|
|
||
| it('returns false for non-Gemini bases', () => { | ||
| expect(isGeminiOpenAICompat('https://api.openai.com/v1')).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false when baseUrl is undefined', () => { | ||
| expect(isGeminiOpenAICompat(undefined)).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe('normalizeGeminiModelId', () => { | ||
| it('strips the models/ prefix for Gemini hosts', () => { | ||
| expect( | ||
| normalizeGeminiModelId( | ||
| 'models/gemini-3.1-pro-preview', | ||
| 'https://generativelanguage.googleapis.com/v1beta/openai/', | ||
| ), | ||
| ).toBe('gemini-3.1-pro-preview'); | ||
| }); | ||
|
|
||
| it('leaves non-Gemini model ids untouched', () => { | ||
| expect(normalizeGeminiModelId('gpt-4', 'https://api.openai.com/v1')).toBe('gpt-4'); | ||
| }); | ||
|
|
||
| it('does not strip models/ prefix when baseUrl is not a Gemini host', () => { | ||
| expect(normalizeGeminiModelId('models/foo', 'https://api.openai.com/v1')).toBe('models/foo'); | ||
| }); | ||
|
|
||
| it('is a no-op when baseUrl is undefined', () => { | ||
| expect(normalizeGeminiModelId('models/gemini-2-pro', undefined)).toBe('models/gemini-2-pro'); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /** | ||
| * Google's OpenAI-compatible endpoint | ||
| * (https://generativelanguage.googleapis.com/v1beta/openai/) accepts the same | ||
| * request shape as OpenAI Chat Completions but rejects model ids carrying the | ||
| * `models/` prefix that its own /models listing returns. Settings UI keeps the | ||
| * prefixed id (so it matches the /models response), and we strip it only on | ||
| * the wire. See issue #175. | ||
| */ | ||
|
|
||
| export function isGeminiOpenAICompat(baseUrl: string | undefined): boolean { | ||
| if (!baseUrl) return false; | ||
| return baseUrl.includes('generativelanguage.googleapis.com'); | ||
Check failureCode scanning / CodeQL Incomplete URL substring sanitization High
'
generativelanguage.googleapis.com Error loading related location Loading |
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Major] Suggested fix: export function isGeminiOpenAICompat(baseUrl: string | undefined): boolean {
if (!baseUrl) return false;
try {
const url = new URL(baseUrl);
return (
url.hostname === 'generativelanguage.googleapis.com' &&
/(^|\/)openai(\/|$)/.test(url.pathname)
);
} catch {
return false;
}
} |
||
| } | ||
|
|
||
| export function normalizeGeminiModelId(modelId: string, baseUrl: string | undefined): string { | ||
| if (!isGeminiOpenAICompat(baseUrl)) return modelId; | ||
| return modelId.replace(/^models\//, ''); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.