fix(models): hide unsupported Gemini Live discovery (#11710)

Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Follow-up correto de #10590/#10591 — omite modelos Gemini Live-only da descoberta dinâmica, preserva chat para modelos híbridos.
- Teste focado atualizado: `tests/unit/gemini-models-parser.test.ts` — 6/6 verde.
- Gates estáticos do lote OK (typecheck, lint sem regressão, complexity, cognitive, file-size, changelog-integrity).
This commit is contained in:
Ravi Tharuma
2026-08-28 03:14:45 +02:00
committed by GitHub
parent a45e80af43
commit 9be4fd0a0e
3 changed files with 26 additions and 8 deletions

View File

@@ -0,0 +1 @@
- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol.

View File

@@ -6,7 +6,7 @@
* - predict → "images" (Imagen image generation)
* - predictLongRunning → "videos" (Veo video generation)
* - embedContent → "embeddings"
* - bidiGenerateContent → "audio" (Live real-time audio)
* - bidiGenerateContent → ignored (Gemini Live is not proxied)
*
* Model-id heuristics refine the long-running bucket because Google exposes both
* Imagen and Veo via long-running methods on the same endpoint:
@@ -19,7 +19,7 @@
*
* This is shared by the `gemini` discovery config and the `vertex` /
* `vertex-partner` (incl. Vertex AI Express key) discovery branches, so every
* model the account can access — chat, image, video, audio and embeddings —
* supported model the account can access — chat, image, video and embeddings —
* surfaces dynamically instead of being limited to the small static registry.
*/
const METHOD_TO_ENDPOINT: Record<string, string> = {
@@ -27,7 +27,6 @@ const METHOD_TO_ENDPOINT: Record<string, string> = {
embedContent: "embeddings",
predict: "images",
predictLongRunning: "videos",
bidiGenerateContent: "audio",
generateAnswer: "chat",
};
@@ -37,6 +36,7 @@ const IGNORED_METHODS = new Set([
"createCachedContent",
"batchGenerateContent",
"asyncBatchEmbedContent",
"bidiGenerateContent",
]);
const RETIRED_GEMINI_MODEL_IDS = new Set(["gemini-3.5-flash"]);
@@ -78,6 +78,13 @@ export function parseGeminiModelsList(data: any): GeminiDiscoveryModel[] {
endpoints.add("images");
}
if (
endpoints.size === 0 &&
methods.length > 0 &&
methods.every((method) => IGNORED_METHODS.has(method))
) {
return null;
}
if (endpoints.size === 0) endpoints.add("chat");
return {
@@ -91,5 +98,8 @@ export function parseGeminiModelsList(data: any): GeminiDiscoveryModel[] {
...(m.thinking === true ? { supportsThinking: true } : {}),
} as GeminiDiscoveryModel;
})
.filter((model: GeminiDiscoveryModel) => !RETIRED_GEMINI_MODEL_IDS.has(model.id));
.filter(
(model: GeminiDiscoveryModel | null): model is GeminiDiscoveryModel =>
Boolean(model) && !RETIRED_GEMINI_MODEL_IDS.has(model.id)
);
}

View File

@@ -72,14 +72,21 @@ test("parseGeminiModelsList maps generateContent image models to the chat endpoi
assert.deepEqual(proImage!.supportedEndpoints, ["chat"]);
});
test("parseGeminiModelsList maps embedContent and bidiGenerateContent", () => {
test("parseGeminiModelsList maps embeddings without advertising unsupported Gemini Live", () => {
const models = parseGeminiModelsList(SAMPLE);
assert.deepEqual(models.find((m) => m.id === "text-embedding-004")!.supportedEndpoints, [
"embeddings",
]);
assert.deepEqual(models.find((m) => m.id === "gemini-live-2.5-flash")!.supportedEndpoints, [
"audio",
]);
assert.equal(models.some((m) => m.id === "gemini-live-2.5-flash"), false);
const [hybrid] = parseGeminiModelsList({
models: [
{
name: "models/gemini-live-hybrid",
supportedGenerationMethods: ["generateContent", "bidiGenerateContent"],
},
],
});
assert.deepEqual(hybrid.supportedEndpoints, ["chat"]);
});
test("parseGeminiModelsList maps Veo predictLongRunning models to the videos endpoint", () => {