fix(providers): strip models/ prefix for Gemini OpenAI-compat endpoint (#175)#186
Merged
fix(providers): strip models/ prefix for Gemini OpenAI-compat endpoint (#175)#186
Conversation
#175) Google's OpenAI-compatible endpoint (https://generativelanguage.googleapis.com/v1beta/openai/) rejects model ids carrying the `models/` prefix that its own /models listing returns, yielding an opaque `400 status code (no body)`. Normalize on the wire only: Settings keeps the prefixed form so the UI stays consistent with what /models returns, while requests drop the prefix before hitting pi-ai. Signed-off-by: hqhq1025 <1506751656@qq.com>
Contributor
There was a problem hiding this comment.
Findings
- [Major] Overly broad Gemini endpoint detection can rewrite model IDs for non-Gemini or spoofed URLs —
isGeminiOpenAICompatmatches any string containinggenerativelanguage.googleapis.com, so values likehttps://generativelanguage.googleapis.com.evil.example/v1beta/openai/are treated as Gemini andmodels/is stripped unexpectedly. This can cause wrong model routing and hard-to-debug provider failures. Evidencepackages/providers/src/gemini-compat.ts:12, used atpackages/providers/src/index.ts:228.
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; } }
Summary
- Review mode: initial
- 1 issue found (Major): Gemini endpoint detection is too permissive and can incorrectly normalize model IDs.
- Context docs status:
docs/VISION.mdanddocs/PRINCIPLES.mdare not present in this checkout (Not found in repo/docs).
Testing
- Not run (automation)
open-codesign Bot
|
|
||
| export function isGeminiOpenAICompat(baseUrl: string | undefined): boolean { | ||
| if (!baseUrl) return false; | ||
| return baseUrl.includes('generativelanguage.googleapis.com'); |
Contributor
There was a problem hiding this comment.
[Major] includes('generativelanguage.googleapis.com') accepts spoofed/non-canonical URLs and can trigger unintended models/ stripping. Prefer strict URL parsing with exact hostname and OpenAI-compat path checks.
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;
}
}…low-up) Signed-off-by: hqhq1025 <1506751656@qq.com>
Contributor
There was a problem hiding this comment.
Findings
- [Major] Gemini endpoint detection is still too broad and can normalize model IDs on non-OpenAI-compat paths —
isGeminiOpenAICompatmatches by hostname only, so URLs likehttps://generativelanguage.googleapis.com/v1beta/modelswill still return true and stripmodels/, causing incorrect model routing and avoidablePROVIDER_MODEL_UNKNOWNerrors. Evidencepackages/providers/src/gemini-compat.ts:14,packages/providers/src/gemini-compat.ts:23, usage atpackages/providers/src/index.ts:228.
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; } }
Summary
- Review mode: follow-up after new commits
- 1 issue found (Major): host-only Gemini endpoint detection can trigger unintended model-id normalization outside OpenAI-compat paths.
- Context docs status:
docs/VISION.mdanddocs/PRINCIPLES.mdare not present in this checkout (Not found in repo/docs).
Testing
- Not run (automation)
open-codesign Bot
| if (!baseUrl) return false; | ||
| try { | ||
| const { hostname } = new URL(baseUrl); | ||
| return ( |
Contributor
There was a problem hiding this comment.
[Major] This matcher currently keys off hostname only, so non-OpenAI-compat URLs on the same host (for example /v1beta/models) still trigger models/ stripping. That can misroute model lookup and synthesize the wrong id.
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;
}
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #175. Google's OpenAI-compatible endpoint at
https://generativelanguage.googleapis.com/v1beta/openai/accepts the same request shape as OpenAI Chat Completions but rejects model ids carrying themodels/prefix that its own/modelslisting returns, producing an opaque400 status code (no body)when routed through a custom provider (OpenAI Chat wire) configured with that baseUrl.packages/providers/src/gemini-compat.tsexposesisGeminiOpenAICompat(baseUrl)andnormalizeGeminiModelId(modelId, baseUrl).complete()inpackages/providers/src/index.tsnormalizes the modelId on the wire only — Settings keeps the prefixed form so provider/model UX stays in sync with/models, while requests drop the prefix before hitting pi-ai.No changes to retry / errors / Settings UI / agent.ts / core. Skipped the OpenAI-specific param stripping step (presence_penalty / frequency_penalty / response_format): none of those keywords exist in
packages/providers,packages/core, orapps/desktop/src/main, so pi-ai is not being handed Chat-specific knobs that Gemini would reject.Test plan
isGeminiOpenAICompatandnormalizeGeminiModelId(Gemini host, OpenAI host, undefined baseUrl, non-Gemini models/ id preserved).index.test.tsverifyingcomplete({ modelId: 'models/gemini-2-pro', baseUrl: '.../generativelanguage.googleapis.com/...' })sends baregemini-2-proto pi-ai.pnpm exec vitest runinpackages/providers: 10 files, 141 tests passed.pnpm typecheckandpnpm lintgreen.PRINCIPLES §5b