Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e1d9a3a
fix(vertex): support Express catalog and xAI MaaS models
JxnLexn Sep 2, 2026
5cce951
fix(vertex): generalize partner discovery and routing
JxnLexn Sep 2, 2026
08cf825
fix(vertex): infer API key project for partner models
JxnLexn Sep 2, 2026
4e8800f
fix(vertex): load live publisher catalog correctly
JxnLexn Sep 2, 2026
8e8c242
fix(vertex): discover Google models from Model Garden
JxnLexn Sep 2, 2026
7b2d3f1
Merge origin/release/v3.8.51 into dev/fix-vertex-ai-model-discovery
JxnLexn Sep 3, 2026
98e6001
fix(i18n): add vertexCredentialLabel/Hint to the remaining locales
JxnLexn Sep 3, 2026
0f68d99
fix(vertex): extract helpers and restore namespaced Model Garden catalog
JxnLexn Sep 3, 2026
705b807
Merge origin/release/v3.8.51 into dev/fix-vertex-ai-model-discovery
JxnLexn Sep 4, 2026
6414c02
fix(docs): sync free-tier catalog counts after Vertex catalog merge
JxnLexn Sep 4, 2026
c0e6166
fix(vertex): enrich live models with token metadata
JxnLexn Sep 4, 2026
2166163
build: preserve cached dependency layer
JxnLexn Sep 4, 2026
febc24e
fix(vertex): integrate metadata enrichment with discovery module
JxnLexn Sep 4, 2026
3a0e902
fix(vertex): avoid model metadata heap exhaustion
JxnLexn Sep 4, 2026
b49d624
fix(vertex): address lint and preserve transient discovery failures
JxnLexn Sep 15, 2026
5b8f338
Merge remote-tracking branch 'origin/release/v3.8.51' into dev/review…
JxnLexn Sep 15, 2026
3e9a6df
Merge remote-tracking branch 'origin/release/v3.8.51' into dev/review…
JxnLexn Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/fixes/vertex-express-xai-models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(vertex):** prefer live model discovery for OAuth, Service Account, and service-account-bound authorization-key credentials while falling back cleanly to the Gemini-only Express catalog for standard API keys. Model Garden resources now route by publisher protocol: Claude and Mistral use their native `rawPredict` APIs, and Grok plus current or future open MaaS publishers use Vertex's OpenAI-compatible endpoint with normalized request IDs such as `xai/grok-4.6`. Distinguish intentional API-key catalog rejection from transient HTTP or network failures. ([#12471](https://github.com/diegosouzapw/OmniRoute/pull/12471)) — thanks @JxnLexn
4 changes: 4 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@ const nextConfig = {
"keytar",
"wreq-js",
"zod",
// jsdom relies on Node class relationships that Turbopack's server-chunk transform can break
// (observed as "Class extends value undefined" during Vertex metadata sync). Keep the native
// package boundary; standalone file tracing still copies the runtime dependency.
"jsdom",
"@ngrok/ngrok",
"@huggingface/transformers",
// The ESM entry imports tiktoken_bg.wasm as a module. Turbopack can compile
Expand Down
16 changes: 11 additions & 5 deletions open-sse/config/freeModelCatalog.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,17 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [
{ provider: "vertex", modelId: "gemini-3.1-pro-preview", displayName: "Gemini 3.1 Pro Preview (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemini-3.1-flash-lite", displayName: "Gemini 3.1 Flash Lite (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemini-3-flash-preview", displayName: "Gemini 3 Flash Preview (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemma-4-31b-it", displayName: "Gemma 4 31B (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "DeepSeek-V4-Flash", displayName: "DeepSeek V4 Flash (Vertex Partner)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "DeepSeek-V4-Pro", displayName: "DeepSeek V4 Pro (Vertex Partner)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "Qwen3.6-35B-A3B", displayName: "Qwen3.6 35B A3B (Vertex Partner)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "GLM-5.1-FP8", displayName: "GLM-5.1 (Vertex Partner)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemini-3.7-flash", displayName: "Gemini 3.7 Flash (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemini-3.6-flash", displayName: "Gemini 3.6 Flash (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemini-3.5-flash", displayName: "Gemini 3.5 Flash (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemini-3.5-flash-lite", displayName: "Gemini 3.5 Flash-Lite (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemini-2.5-pro", displayName: "Gemini 2.5 Pro (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "gemini-2.5-flash-lite", displayName: "Gemini 2.5 Flash-Lite (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "deepseek-ai/deepseek-v3.2-maas", displayName: "DeepSeek V3.2 (Vertex MaaS)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "deepseek-ai/deepseek-v3.1-maas", displayName: "DeepSeek V3.1 (Vertex MaaS)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "qwen/qwen3-next-80b-a3b-instruct-maas", displayName: "Qwen3 Next 80B Instruct (Vertex MaaS)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "zai-org/glm-5-maas", displayName: "GLM 5 (Vertex MaaS)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "claude-opus-4-7", displayName: "Claude Opus 4.7 (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "vertex", modelId: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6 (Vertex)", monthlyTokens: 0, creditTokens: 300000000, freeType: "one-time-initial", poolKey: "vertex", tos: "caution" },
{ provider: "requesty", modelId: "openai/gpt-oss-120b", displayName: "GPT-OSS 120B (Requesty free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "requesty-free", tos: "ok" },
Expand Down
8 changes: 5 additions & 3 deletions open-sse/config/providerModels.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { generateModels, generateAliasMap, type RegistryModel } from "./providerRegistry.ts";
import { getVertexModelTargetFormat } from "./vertexModels.ts";

// Lazy PROVIDER_MODELS: deferred until first property access to speed up startup.
// The Proxy defers `generateModels()` from module-evaluation time to the first read.
Expand Down Expand Up @@ -228,9 +229,10 @@ export function getModelTargetFormat(aliasOrId: string, modelId: string): string
// executor's /codex/i routing, 9router#102). Scoped to the openai alias so other
// providers shipping *-pro ids keep their own endpoint semantics.
if (alias === "openai" && /-pro$/i.test(bareModelId)) return "openai-responses";
// ponytail: Claude models on Vertex use rawPredict with Anthropic Messages format,
// not the Gemini generateContent format. Mirrors executor isClaudeModel() check.
if ((alias === "vertex" || alias === "vp") && /^claude-/i.test(bareModelId)) return "claude";
// Vertex uses three protocol families: Gemini generateContent, Anthropic Messages rawPredict,
// and OpenAI-shaped Mistral/Open-MaaS requests. Resource names retain enough publisher data to
// route future dynamically-synced models without adding another pinned prefix here.
if (alias === "vertex" || alias === "vp") return getVertexModelTargetFormat(bareModelId);
// Model-level targetFormat is provider-scoped: a catalog entry declares how THIS
// provider's endpoint serves the model — do NOT import another provider's tag.
// #9994 scoped this for providers WITH a catalog; #10072 extends it to catalogless
Expand Down
35 changes: 29 additions & 6 deletions open-sse/config/providers/registry/vertex/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RegistryEntry } from "../../shared.ts";
import { VERTEX_XAI_MODELS } from "../../../vertexModels.ts";

export const vertexProvider: RegistryEntry = {
id: "vertex",
Expand All @@ -9,7 +10,7 @@ export const vertexProvider: RegistryEntry = {
// URL uses {project_id} and {region} from providerSpecificData — handled by custom executor or fallback
// Default to us-central1 / generic endpoint; users configure project via providerSpecificData
baseUrl: "https://us-central1-aiplatform.googleapis.com/v1/projects",
urlBuilder: (base, model, stream) => {
urlBuilder: (_base, model, stream) => {
// Full URL: {base}/{project}/locations/{region}/publishers/google/models/{model}:{action}
// For a generic fallback, we build a Gemini-compatible URL
// The actual project/region are configured via providerSpecificData in the DB connection
Expand All @@ -19,14 +20,33 @@ export const vertexProvider: RegistryEntry = {
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "gemini-3.7-flash", name: "Gemini 3.7 Flash (Vertex)" },
{ id: "gemini-3.6-flash", name: "Gemini 3.6 Flash (Vertex)" },
{ id: "gemini-3.5-flash", name: "Gemini 3.5 Flash (Vertex)" },
{ id: "gemini-3.5-flash-lite", name: "Gemini 3.5 Flash-Lite (Vertex)" },
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview (Vertex)" },
{ id: "gemini-3.1-flash-lite", name: "Gemini 3.1 Flash Lite (Vertex)" },
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview (Vertex)" },
{ id: "gemma-4-31b-it", name: "Gemma 4 31B (Vertex)" },
{ id: "DeepSeek-V4-Flash", name: "DeepSeek V4 Flash (Vertex Partner)" },
{ id: "DeepSeek-V4-Pro", name: "DeepSeek V4 Pro (Vertex Partner)" },
{ id: "Qwen3.6-35B-A3B", name: "Qwen3.6 35B A3B (Vertex Partner)" },
{ id: "GLM-5.1-FP8", name: "GLM-5.1 (Vertex Partner)" },
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro (Vertex)" },
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash (Vertex)" },
{ id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash-Lite (Vertex)" },
{
id: "deepseek-ai/deepseek-v3.2-maas",
name: "DeepSeek V3.2 (Vertex MaaS)",
targetFormat: "openai",
},
{
id: "deepseek-ai/deepseek-v3.1-maas",
name: "DeepSeek V3.1 (Vertex MaaS)",
targetFormat: "openai",
},
{
id: "qwen/qwen3-next-80b-a3b-instruct-maas",
name: "Qwen3 Next 80B Instruct (Vertex MaaS)",
targetFormat: "openai",
},
{ id: "zai-org/glm-5-maas", name: "GLM 5 (Vertex MaaS)", targetFormat: "openai" },
...VERTEX_XAI_MODELS,
{ id: "claude-fable-5-1", name: "Claude Fable 5.1 (Vertex)", targetFormat: "claude" },
{ id: "claude-fable-5", name: "Claude Fable 5 (Vertex)", targetFormat: "claude" },
{ id: "claude-opus-5", name: "Claude Opus 5 (Vertex)", targetFormat: "claude" },
Expand All @@ -41,4 +61,7 @@ export const vertexProvider: RegistryEntry = {
{ id: "claude-haiku-4-5", name: "Claude Haiku 4.5 (Vertex)", targetFormat: "claude" },
],
passthroughModels: true,
// Gemini + publisher discovery are independent APIs. A partial successful response must not
// hide curated partner models omitted by another publisher catalog.
liveCatalogAuthoritative: false,
};
15 changes: 11 additions & 4 deletions open-sse/config/providers/registry/vertex/partner/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RegistryEntry } from "../../../shared.ts";
import { VERTEX_XAI_MODELS } from "../../../../vertexModels.ts";

export const vertex_partnerProvider: RegistryEntry = {
id: "vertex-partner",
Expand All @@ -9,10 +10,15 @@ export const vertex_partnerProvider: RegistryEntry = {
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "DeepSeek-V4-Flash", name: "DeepSeek V4 Flash" },
{ id: "DeepSeek-V4-Pro", name: "DeepSeek V4 Pro" },
{ id: "Qwen3.6-35B-A3B", name: "Qwen 3.6 35B A3B" },
{ id: "GLM-5.1-FP8", name: "GLM 5.1" },
{ id: "deepseek-ai/deepseek-v3.2-maas", name: "DeepSeek V3.2", targetFormat: "openai" },
{ id: "deepseek-ai/deepseek-v3.1-maas", name: "DeepSeek V3.1", targetFormat: "openai" },
{
id: "qwen/qwen3-next-80b-a3b-instruct-maas",
name: "Qwen3 Next 80B Instruct",
targetFormat: "openai",
},
{ id: "zai-org/glm-5-maas", name: "GLM 5", targetFormat: "openai" },
...VERTEX_XAI_MODELS,
{ id: "claude-fable-5-1", name: "Claude Fable 5.1", targetFormat: "claude" },
{ id: "claude-fable-5", name: "Claude Fable 5", targetFormat: "claude" },
{ id: "claude-opus-5", name: "Claude Opus 5", targetFormat: "claude" },
Expand All @@ -27,4 +33,5 @@ export const vertex_partnerProvider: RegistryEntry = {
{ id: "claude-opus-4-5", name: "Claude Opus 4.5", targetFormat: "claude" },
{ id: "claude-haiku-4-5", name: "Claude Haiku 4.5", targetFormat: "claude" },
],
liveCatalogAuthoritative: false,
};
106 changes: 106 additions & 0 deletions open-sse/config/vertexModels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { RegistryModel } from "./providers/shared.ts";

const VERTEX_PUBLISHER_RESOURCE_PATTERN =
/^(?:(?:projects\/[^/]+\/locations\/[^/]+\/)?publishers\/([^/]+)\/models\/|([^/]+)\/models\/)(.+)$/i;
const NATIVE_PUBLISHERS = new Set(["google", "anthropic", "mistralai"]);

const LEGACY_OPENAI_MAAS_PUBLISHERS = [
{ prefix: "deepseek-", publisher: "deepseek-ai" },
{ prefix: "qwen", publisher: "qwen" },
{ prefix: "llama-", publisher: "meta" },
{ prefix: "glm-", publisher: "zai-org" },
{ prefix: "gpt-oss-", publisher: "openai" },
{ prefix: "kimi-", publisher: "moonshotai" },
{ prefix: "minimax-", publisher: "minimaxai" },
] as const;

export type VertexModelTransport = "gemini" | "anthropic" | "mistral" | "openai";

function normalizePublisherModel(publisher: string, model: string): string {
const normalizedPublisher = publisher.toLowerCase();
if (NATIVE_PUBLISHERS.has(normalizedPublisher)) return model;
return `${normalizedPublisher}/${model}`;
}

/**
* Convert Model Garden resource/version names to the model value accepted by its inference API.
* Native Google, Anthropic, and Mistral endpoints encode the publisher in the URL and take a bare
* model id. OpenAI-compatible MaaS requests keep the publisher namespace in the request body.
*/
export function normalizeVertexModelId(model: string): string {
const trimmed = model.trim().replace(/^(?:vertex|vp)\//i, "");
const resourceMatch = trimmed.match(VERTEX_PUBLISHER_RESOURCE_PATTERN);
if (resourceMatch) {
const publisher = resourceMatch[1] || resourceMatch[2];
const modelId = resourceMatch[3];
if (publisher && modelId) return normalizePublisherModel(publisher, modelId);
}

const namespacedMatch = trimmed.match(/^([^/]+)\/(.+)$/);
if (namespacedMatch && NATIVE_PUBLISHERS.has(namespacedMatch[1].toLowerCase())) {
return namespacedMatch[2];
}

// Keep manually-added historical Grok ids working while emitting Google's documented MaaS
// request form (`xai/grok-*`) for the OpenAI-compatible endpoint.
if (/^grok-/i.test(trimmed)) return `xai/${trimmed}`;
const legacyMaaS = LEGACY_OPENAI_MAAS_PUBLISHERS.find(({ prefix }) =>
trimmed.toLowerCase().startsWith(prefix)
);
if (legacyMaaS) return `${legacyMaaS.publisher}/${trimmed}`;
return trimmed;
}

/** Resolve the Vertex transport from either a request id or a Model Garden resource name. */
export function getVertexModelTransport(model: string): VertexModelTransport {
const normalized = normalizeVertexModelId(model).toLowerCase();
if (normalized.startsWith("claude-")) return "anthropic";
if (normalized.startsWith("mistral-")) return "mistral";
if (normalized.includes("/")) return "openai";
return "gemini";
}

/** Wire format used before the request reaches the provider-specific Vertex executor. */
export function getVertexModelTargetFormat(model: string): "claude" | "openai" | null {
const transport = getVertexModelTransport(model);
if (transport === "anthropic") return "claude";
if (transport === "mistral" || transport === "openai") return "openai";
return null;
}

/** True for xAI models served through Vertex's OpenAI-compatible MaaS endpoint. */
export function isVertexXaiModel(model: string): boolean {
return /^xai\/grok-/i.test(normalizeVertexModelId(model));
}

/** True only for models exposed by the project-less Vertex Express API. */
export function isVertexExpressModel(model: string): boolean {
return (
getVertexModelTransport(model) === "gemini" && /^gemini-/i.test(normalizeVertexModelId(model))
);
}

/** Curated fallback metadata for Vertex xAI MaaS models. */
export const VERTEX_XAI_MODELS = [
{
id: "xai/grok-4.6",
name: "Grok 4.6",
contextLength: 524288,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
targetFormat: "openai",
},
{ id: "xai/grok-4.3", name: "Grok 4.3", targetFormat: "openai" },
{
id: "xai/grok-4.20-reasoning",
name: "Grok 4.20 Reasoning",
supportsReasoning: true,
targetFormat: "openai",
},
{
id: "xai/grok-4.20-non-reasoning",
name: "Grok 4.20 Non-Reasoning",
targetFormat: "openai",
},
] as const satisfies readonly RegistryModel[];
Loading
Loading