mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
The v3.8.50 close left 134 post-freeze commits on release/v3.8.50 that never
reached the cycle branch (the freeze cut release/v3.8.51 at 3192eb88d5). A
plain merge of main reproduces all of them through the `Release v3.8.50`
squash against a July merge-base and conflicted on 551 files; merging the
release tip first, against the recent common ancestor, narrows the real
conflicts to 102 (51 generated, 51 judged file by file with a proof each —
see _tasks/postmortems/2026-08-25-release-v3.8.50-pipeline-eficiencia.md,
Parte IV). Step 2 brings main's own post-tag fixes and the finalized
CHANGELOG through scripts/release/sync-next-cycle.mjs.
Resolution rules applied, in order of evidence:
- generated files regenerated with the repo's own generators
(sync-llm-mirrors, gen-budget-card-svg, gen-provider-reference);
- where release/v3.8.51 already carried the same fix in a newer shape
(#11524 search sweep, #11551 catalog scheduler, Google BYOP retry, KIE
Market id map, Docker worker budget measured in #7518) its version stays;
- where release/v3.8.50 carried the newer shape (Volcengine cookie-domain
CodeQL fix + shared Zod schemas, #11355/#10534 cooldown release helper,
positive-anchor tests for security-hardening and cli-oneproxy) it wins;
- GPL-retired Raycast/Hailuo (#11691) stay retired: nothing of theirs comes
back and the public-route test keeps the retired route out;
- the ten changelog.d fragments of v3.8.50 are dropped — they are already
aggregated in main's CHANGELOG and would double-aggregate at v3.8.51.
Three things git's auto-merge silently produced were caught by a per-line
detector and fixed: providerLimits.ts lost T's imports and the
windowStillExhaustedAfterRealReset helper; catalogCache.ts and
providerLimits.ts kept both sides' identical copies of three declarations;
contextHandoff.ts's new provider-allowlist skip returned undefined against
the #11552 outcome type. Every decision was re-run through the tests both
sides own for it.
107 lines
3.8 KiB
TypeScript
107 lines
3.8 KiB
TypeScript
/**
|
|
* Parses the Google Generative Language `v1beta/models` listing into discovery models.
|
|
*
|
|
* Each model's `supportedGenerationMethods` is mapped to OmniRoute endpoints:
|
|
* - generateContent / generateAnswer → "chat"
|
|
* - predict → "images" (Imagen image generation)
|
|
* - predictLongRunning → "videos" (Veo video generation)
|
|
* - embedContent → "embeddings"
|
|
* - 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:
|
|
* - id contains "veo" → ensure "videos"
|
|
* - id contains "imagen" → force "images" (never "videos")
|
|
*
|
|
* Note: `gemini-*-image` models (e.g. gemini-3-pro-image) generate images via the
|
|
* regular `generateContent` path, so they stay "chat" (image output is a chat
|
|
* modality) and are intentionally NOT reclassified as "images".
|
|
*
|
|
* This is shared by the `gemini` discovery config and the `vertex` /
|
|
* `vertex-partner` (incl. Vertex AI Express key) discovery branches, so every
|
|
* 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> = {
|
|
generateContent: "chat",
|
|
embedContent: "embeddings",
|
|
predict: "images",
|
|
predictLongRunning: "videos",
|
|
bidiGenerateContent: "audio",
|
|
generateAnswer: "chat",
|
|
};
|
|
|
|
const IGNORED_METHODS = new Set([
|
|
"countTokens",
|
|
"countTextTokens",
|
|
"createCachedContent",
|
|
"batchGenerateContent",
|
|
"asyncBatchEmbedContent",
|
|
"bidiGenerateContent",
|
|
]);
|
|
|
|
const RETIRED_GEMINI_MODEL_IDS = new Set(["gemini-3.5-flash"]);
|
|
|
|
export interface GeminiDiscoveryModel {
|
|
id: string;
|
|
name: string;
|
|
supportedEndpoints: string[];
|
|
inputTokenLimit?: number;
|
|
outputTokenLimit?: number;
|
|
description?: string;
|
|
supportsThinking?: boolean;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export function parseGeminiModelsList(data: any): GeminiDiscoveryModel[] {
|
|
return (data?.models || [])
|
|
.map((m: Record<string, unknown>) => {
|
|
const methods: string[] = Array.isArray(m.supportedGenerationMethods)
|
|
? (m.supportedGenerationMethods as string[])
|
|
: [];
|
|
|
|
const endpoints = new Set<string>(
|
|
methods
|
|
.filter((method) => !IGNORED_METHODS.has(method))
|
|
.map((method) => METHOD_TO_ENDPOINT[method] || "chat")
|
|
);
|
|
|
|
const id = ((m.name as string) || (m.id as string) || "").replace(/^models\//, "");
|
|
const lowerId = id.toLowerCase();
|
|
|
|
// Google exposes Imagen (image) and Veo (video) via long-running methods; the
|
|
// method alone can't always distinguish them, so refine by model id.
|
|
if (lowerId.includes("veo")) {
|
|
endpoints.add("videos");
|
|
}
|
|
if (lowerId.includes("imagen")) {
|
|
endpoints.delete("videos");
|
|
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 {
|
|
...m,
|
|
id,
|
|
name: (m.displayName as string) || id,
|
|
supportedEndpoints: [...endpoints],
|
|
...(typeof m.inputTokenLimit === "number" ? { inputTokenLimit: m.inputTokenLimit } : {}),
|
|
...(typeof m.outputTokenLimit === "number" ? { outputTokenLimit: m.outputTokenLimit } : {}),
|
|
...(typeof m.description === "string" ? { description: m.description } : {}),
|
|
...(m.thinking === true ? { supportsThinking: true } : {}),
|
|
} as GeminiDiscoveryModel;
|
|
})
|
|
.filter(
|
|
(model: GeminiDiscoveryModel | null): model is GeminiDiscoveryModel =>
|
|
Boolean(model) && !RETIRED_GEMINI_MODEL_IDS.has(model.id)
|
|
);
|
|
}
|