Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
b52c236
feat(google-vertex): support live Publisher Models discovery and gemi…
xdober Nov 12, 2026
76b5232
refactor(google-vertex): decouple Vertex model discovery from src/oauth
xdober Nov 12, 2026
9087c22
feat(google-vertex): add gemini-4-hydrogen support and xhigh thinking…
xdober Nov 13, 2026
f8e6c93
Merge remote-tracking branch 'origin/dev' into feat/vertex-model-disc…
xdober Nov 13, 2026
27305bd
Merge remote-tracking branch 'origin/dev' into feat/vertex-model-disc…
lidge-jun Nov 13, 2026
414be98
fix(google-vertex): close the remaining #8583 review findings in Vert…
lidge-jun Nov 13, 2026
7370c11
docs(google-vertex): document Vertex discovery and the Gemini 4 tool-…
lidge-jun Nov 13, 2026
aaf8026
fix(google-vertex): reject foreign follow-up pages and share one disc…
lidge-jun Nov 13, 2026
d4b2170
Merge remote-tracking branch 'origin/dev' into feat/vertex-model-disc…
lidge-jun Nov 13, 2026
c4183f5
fix(google-vertex): select the discovery credential on the same bytes…
lidge-jun Nov 13, 2026
df8528e
fix(google-vertex): bind a discovery token to the selection that issu…
lidge-jun Nov 13, 2026
c08f85e
docs(google-vertex): add the Spanish Vertex AI page the localization …
lidge-jun Nov 13, 2026
e0acabf
fix(google-vertex): bind discovery ADC tokens to the authority that m…
lidge-jun Nov 13, 2026
e809ee6
Merge remote-tracking branch 'origin/dev' into feat/vertex-model-disc…
lidge-jun Nov 13, 2026
27a8c28
Merge remote-tracking branch 'origin/dev' into feat/vertex-model-disc…
lidge-jun Nov 13, 2026
dda2a54
fix(google-vertex): bind ambient-key discovery auth and follow a toke…
lidge-jun Nov 13, 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 docs-site/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export default defineConfig({
{ label: "Response Inspection", translations: { es: "Inspección de respuestas", fr: "Inspection des réponses et réponses volumineuses", ko: "응답 검사와 대용량 응답", "zh-CN": "响应检查与大型响应", "zh-TW": "回應檢查與大型回應", ru: "Проверка ответов и большие ответы", ja: "レスポンスの検査と大きなレスポンス", tr: "Yanıt incelemesi ve büyük yanıtlar" }, slug: "guides/response-inspection" },
{ label: "Remote Workspace", translations: { es: "Espacio de trabajo remoto", fr: "Espace de travail distant", ko: "원격 워크스페이스", "zh-CN": "远程工作区", "zh-TW": "遠端工作區", ru: "Удалённая рабочая область", ja: "リモートワークスペース", tr: "Uzak Çalışma Alanı" }, slug: "guides/remote-workspace" },
{ label: "Providers", translations: { es: "Proveedores", fr: "Fournisseurs", ko: "프로바이더", "zh-CN": "提供商", "zh-TW": "供應商", ru: "Провайдеры", ja: "プロバイダー", tr: "Sağlayıcılar" }, slug: "guides/providers" },
{ label: "Google Vertex AI", translations: { es: "Google Vertex AI" }, slug: "guides/google-vertex" },
{ label: "Factory Droid Bridge", translations: { es: "Puente Factory Droid", fr: "Pont Factory Droid", ko: "Factory Droid 브리지", "zh-CN": "Factory Droid 桥接", "zh-TW": "Factory Droid 橋接", ru: "Мост Factory Droid", ja: "Factory Droid ブリッジ", tr: "Factory Droid köprüsü" }, slug: "guides/factory-droid" },
{ label: "Ollama-Compatible Clients", translations: { es: "Clientes compatibles con Ollama", fr: "Clients compatibles Ollama", ko: "Ollama 호환 클라이언트", "zh-CN": "Ollama 兼容客户端", "zh-TW": "Ollama 相容用戶端", ru: "Клиенты, совместимые с Ollama", ja: "Ollama 互換クライアント", tr: "Ollama uyumlu istemciler" }, slug: "guides/ollama-clients" },
{ label: "Cursor Private Inference", translations: { es: "Inferencia privada de Cursor", ko: "Cursor Private Inference", fr: "Cursor Private Inference", "zh-CN": "Cursor Private Inference", "zh-TW": "Cursor Private Inference", ru: "Cursor Private Inference", ja: "Cursor Private Inference", tr: "Cursor Private Inference" }, slug: "guides/cursor-private-inference" },
Expand Down
39 changes: 39 additions & 0 deletions docs-site/src/content/docs/es/guides/google-vertex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: "Google Vertex AI"
description: Cómo un proveedor de Vertex AI descubre sus modelos, qué credencial usa el descubrimiento y cómo los modelos Gemini 4 reciben imágenes de herramientas.
---

Esta página describe el comportamiento propio del proveedor Google Vertex AI (`google-vertex`, o
cualquier proveedor de Google con `googleMode: "vertex"`). La configuración y las credenciales se
explican en [Proveedores](/es/guides/providers/).

## Descubrimiento de modelos

Un proveedor de Vertex AI con modelos en vivo lista los modelos de editor de Google y recorre todas
las páginas del listado. Muestra los modelos de chat Gemini y Gemma y omite los modelos de
embeddings, voz, Live API, Imagen y Veo, así como los modelos Gemini que generan imágenes, como
`gemini-3.1-flash-image`, cuyas fichas de modelo indican que no admiten llamadas a funciones.

El descubrimiento usa la credencial del propio proveedor: una cuenta de servicio `gcp-sa:`, una
clave o, si no hay ninguna configurada, `GOOGLE_CLOUD_API_KEY` y después las credenciales
predeterminadas de la aplicación (ADC). Si no se puede leer una clave configurada como `keychain:`
o `$VARIABLE`, el descubrimiento se detiene en lugar de probar otra credencial, igual que las
solicitudes se niegan a empezar. Cuando el listado no se puede leer completo, OpenCodex conserva los
modelos anteriores o configurados en lugar de publicar solo una parte.

Los niveles de razonamiento siguen el preajuste: Gemini 4 Hydrogen ofrece low, medium, high (el
predeterminado) y xhigh; Gemini 4 Carbon y Argon ofrecen low, medium y high. Un modelo al que el
preajuste no asigna niveles, como `gemini-3-pro`, no muestra ninguno, porque sus solicitudes no
llevan nivel de razonamiento.

## Imágenes de herramientas en Gemini 4

En Vertex AI, `gemini-4-argon`, `gemini-4-carbon` y `gemini-4-hydrogen` reciben las imágenes
que devuelven las herramientas, como capturas de pantalla, dentro de la respuesta de función
correspondiente. En Argon esto evita el error 400 del servicio "Requests ending with a model turn
are not supported" tras una llamada a herramienta que produce imágenes; Carbon y Hydrogen usan la
misma forma documentada de respuesta de función, aunque ese error no se reprodujo en ellos. Las
imágenes de resultados duplicados o sin llamada correspondiente se conservan en un mensaje aparte
después de las respuestas de herramientas, con una marca que explica que no se pudieron emparejar.
No hace falta cambiar la configuración; este tratamiento es específico de estos modelos Gemini 4 en
Vertex.
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/es/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ La pestaña Cuentas Antigravity muestra un nivel de membresía observado como un

Gemini 4 Argon (`gemini-4-argon`) aparece en el preajuste `google` con niveles de pensamiento bajos, medios (el predeterminado) y altos. Google lo ofrece solo en el nivel pagado Gemini API, no en el nivel gratuito. El `google-antigravity`, Argon aparece con los mismos tres niveles, que se asignan a los modelos del protocolo `gemini-4-argon-low`, `-medium` y `-high`. Google lo limita a las cuentas AI Ultra y Enterprise elegibles. Cloud Code Assist sirve Argon solo al cliente Antigravity CLI, por lo que OpenCodex envía todas las solicitudes de modelos Gemini 4 y el descubrimiento de modelos con el CLI User-Agent (`antigravity/cli/1.3.4` ) y mantiene el IDE User-Agent para cualquier otro modelo. Gemini 4 Hydrogen (low, medium, high por defecto y xhigh; verificado en Vertex AI el 2026-11-13, aún no visto en Antigravity) aparece en `google-antigravity` solo cuando la lista de modelos de tu cuenta lo incluye, y cada nivel se asigna a su modelo del protocolo `gemini-4-hydrogen-*`.

En Vertex AI, `gemini-4-argon` recibe imágenes devueltas por herramientas, como capturas de pantalla, dentro de la respuesta de función coincidente. Esto evita el error 400 aguas arriba "Las solicitudes que terminan con un giro de modelo no son compatibles" después de una llamada de herramienta de producción de imágenes mientras se conservan las imágenes. Las imágenes de resultados duplicados o resultados sin una llamada coincidente se conservan en un mensaje separado después de las respuestas de la herramienta, con un marcador que explica que no se pudieron emparejar. No se necesita ningún cambio de configuración; este manejo es específico para Vertex Argon.
En Vertex AI, `gemini-4-argon`, `gemini-4-carbon` y `gemini-4-hydrogen` reciben las imágenes devueltas por herramientas, como capturas de pantalla, dentro de la respuesta de función correspondiente. El descubrimiento de modelos de Vertex AI, sus reglas de credenciales y este tratamiento se describen en [Google Vertex AI](/es/guides/google-vertex/).

Para Gemini en Antigravity, incluyendo Argon, los resúmenes de pensamiento marcados por el proveedor se mantienen separados de la respuesta: Responses usa elementos de resumen de razonamiento, Chat Completions usa `reasoning_content` y Messages usa `thinking` bloques. Los clientes deciden cómo mostrar esos campos. Un cliente Responses puede solicitar `reasoning.summary: "none"` para suprimir resúmenes; esto no desactiva el razonamiento interno del modelo. Una firma de pensamiento es metadatos de continuación y puede acompañar al texto ordinario, por lo que su presencia por sí sola no oculta ese texto. OpenCodex conserva texto de respuesta sin marcar incluso cuando su redacción suena como planificación o análisis.

Expand Down
36 changes: 36 additions & 0 deletions docs-site/src/content/docs/guides/google-vertex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: "Google Vertex AI"
description: How a Vertex AI provider discovers its models, which credential discovery uses, and how Gemini 4 models handle tool images.
---

This page covers behavior specific to the Google Vertex AI provider (`google-vertex`, or any
Google provider with `googleMode: "vertex"`). Setup and credentials are described in
[Providers](/guides/providers/).

## Model discovery

A Vertex AI provider with live models lists Google's publisher models and follows every page of
the listing. It shows Gemini and Gemma chat models and leaves out embedding, speech, Live API,
Imagen and Veo models, and image-output Gemini models such as `gemini-3.1-flash-image`, whose
model cards list function calling as unsupported.

Discovery uses the provider's own credential: a `gcp-sa:` service account, a key, or with neither
configured, `GOOGLE_CLOUD_API_KEY` and then Application Default Credentials. If a configured
`keychain:` or `$VARIABLE` key cannot be read, discovery stops instead of trying another
credential, the same way requests refuse to start. When the listing cannot be read completely,
OpenCodex keeps the previous or configured models rather than publishing part of the list.

Reasoning levels follow the preset: Gemini 4 Hydrogen offers low, medium, high (the default) and
xhigh; Gemini 4 Carbon and Argon offer low, medium and high. A model the preset gives no levels
to, such as `gemini-3-pro`, shows none, because requests to it carry no thinking level.

## Tool images on Gemini 4

On Vertex AI, `gemini-4-argon`, `gemini-4-carbon` and `gemini-4-hydrogen` receive images
returned by tools, such as screenshots, inside the matching function response. On Argon this
avoids the upstream 400 error "Requests ending with a model turn are not supported" after an
image-producing tool call; Carbon and Hydrogen use the same documented function-response form,
though that error was not reproduced on them. Images from duplicate results or results
without a matching call are preserved in a separate message after the tool responses, with a
marker explaining that they could not be paired. No configuration change is needed; this handling
is specific to these Gemini 4 models on Vertex.
8 changes: 2 additions & 6 deletions docs-site/src/content/docs/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,8 @@ OpenCodex sends every Gemini 4 model request and model discovery with the CLI Us
(`antigravity/cli/1.3.4`) and keeps the IDE User-Agent for every other model. Gemini 4 Hydrogen (low, medium, high by default, and xhigh; verified on Vertex AI on 2026-11-13, not yet seen
on Antigravity) appears on `google-antigravity` only when your account's model list includes it, each level mapping to its `gemini-4-hydrogen-*` wire model.

On Vertex AI, `gemini-4-argon` receives images returned by tools, such as screenshots, inside
the matching function response. This avoids the upstream 400 error "Requests ending with a
model turn are not supported" after an image-producing tool call while preserving the images.
Images from duplicate results or results without a matching call are preserved in a separate
message after the tool responses, with a marker explaining that they could not be paired.
No configuration change is needed; this handling is specific to Vertex Argon.
Vertex AI model discovery, its credential rules and how Gemini 4 Argon, Carbon and Hydrogen
receive tool images are described in [Google Vertex AI](/guides/google-vertex/).

For Gemini on Antigravity, including Argon, provider-marked thought summaries stay separate
from the answer: Responses uses reasoning summary items, Chat Completions uses
Expand Down
9 changes: 3 additions & 6 deletions docs-site/src/content/docs/ko/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,9 @@ ocx logout <provider>

Google Antigravity 계정·제공자 할당량 확인은 모델 목록 폴백을 포함해 고정된 Google 회계 엔드포인트를 사용합니다. 해당 목적지의 투명 Fake-IP DNS를 지원하며 TLS 검증, 리다이렉트 거부, 사설 주소 검사는 유지합니다. 사용자 지정 base URL은 모델 요청에만 적용되며 할당량 목적지는 바꾸지 않습니다. `NO_PROXY`는 기존 직접 연결 정책을 유지합니다.

Vertex AI의 `gemini-4-argon`에는 스크린샷 같은 도구 결과 이미지를 해당 함수 응답 안에 담아
전송합니다. 이미지가 있는 도구 호출 뒤에 발생하는 400 오류
"Requests ending with a model turn are not supported"를 방지하며 이미지를 보존합니다.
중복된 결과나 일치하는 호출이 없는 결과의 이미지는 도구 응답 뒤의 별도 메시지로 보존하고,
호출과 연결할 수 없었다는 표시를 남깁니다.
별도의 설정 변경은 필요하지 않으며, 이 처리는 Vertex Argon에만 적용됩니다.
Vertex AI의 `gemini-4-argon`, `gemini-4-carbon`, `gemini-4-hydrogen`은 스크린샷 같은 도구 결과 이미지를
해당 함수 응답 안에 담아 받습니다. Vertex AI 모델 탐색, 자격 증명 규칙과 이 처리 방식은
[Google Vertex AI](/ko/guides/google-vertex/)에 설명되어 있습니다.

### Google 도구 스키마 손실 진단

Expand Down
38 changes: 38 additions & 0 deletions scripts/model-metadata.source.json
Original file line number Diff line number Diff line change
Expand Up @@ -13290,6 +13290,44 @@
"maxLevel": "high"
}
},
"gemini-4-carbon": {
"id": "gemini-4-carbon",
"name": "Gemini 4 Carbon",
"api": "google-generative-ai",
"provider": "google",
"baseUrl": "https://generativelanguage.googleapis.com/v1beta",
"reasoning": true,
"input": [
"text",
"image"
],
"contextWindow": 1048576,
"maxTokens": 65536,
"thinking": {
"mode": "google-level",
"minLevel": "low",
"maxLevel": "high"
}
},
"gemini-4-hydrogen": {
"id": "gemini-4-hydrogen",
"name": "Gemini 4 Hydrogen",
"api": "google-generative-ai",
"provider": "google",
"baseUrl": "https://generativelanguage.googleapis.com/v1beta",
"reasoning": true,
"input": [
"text",
"image"
],
"contextWindow": 1048576,
"maxTokens": 65536,
"thinking": {
"mode": "google-level",
"minLevel": "low",
"maxLevel": "xhigh"
}
},
"gemini-flash-latest": {
"id": "gemini-flash-latest",
"name": "Gemini Flash Latest",
Expand Down
1 change: 1 addition & 0 deletions scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -1886,6 +1886,7 @@
"vercel-gateway-provider-routing.test.ts": "providers",
"version-line.test.ts": "ci-workflows",
"vertex-catalog.test.ts": "adapters/google",
"vertex-model-discovery.test.ts": "adapters/google",
"vision-anthropic.test.ts": "vision",
"vision-backend-union.test.ts": "vision",
"vision-cache.test.ts": "vision",
Expand Down
4 changes: 2 additions & 2 deletions src/adapters/google-wire-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
type JsonObject = Record<string, unknown>;

const GOOGLE_TOOL_NAME = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
const GOOGLE_THINKING_LEVELS = new Set(["minimal", "low", "medium", "high"]);
const GOOGLE_THINKING_LEVELS = new Set(["minimal", "low", "medium", "high", "xhigh"]);

export class GoogleToolSchemaPolicyError extends Error {
readonly phase = "initial" as const;
Expand Down Expand Up @@ -169,7 +169,7 @@ function compileGenerationConfig(value: unknown): JsonObject | undefined {
const raw = value.thinkingConfig.thinkingLevel.toLowerCase();
const thinkingLevel = GOOGLE_THINKING_LEVELS.has(raw)
? raw
: (["xhigh", "max", "ultra"].includes(raw) ? "high" : undefined);
: (["max", "ultra"].includes(raw) ? "high" : undefined);
if (thinkingLevel) thinking.thinkingLevel = thinkingLevel;
}
const rawBudget = finiteNumber(value.thinkingConfig.thinkingBudget ?? value.thinkingConfig.thinking_budget);
Expand Down
11 changes: 9 additions & 2 deletions src/adapters/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
identityModelId,
stripRejectedClaudeSdkParagraph,
isCloudCodeAssist,
provider.googleMode === "vertex" && parsed.modelId === "gemini-4-argon",
provider.googleMode === "vertex" && (
parsed.modelId === "gemini-4-argon"
|| parsed.modelId === "gemini-4-carbon"
|| parsed.modelId === "gemini-4-hydrogen"
),
isCloudCodeAssist && /^claude-/i.test(routedModelId),
);
lastInjectedCallIds = [...replayedCallIds];
Expand Down Expand Up @@ -1100,9 +1104,12 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
// current behavior; Vertex participates only through an explicitly configured ladder (the
// seed google-vertex entry ships none). Image models are excluded — thinkingConfig would
// suppress the responseModalities fallback below. CCA maps effort on its envelope path.
const thinkingLevel = thinkingEligible
const rawThinkingLevel = thinkingEligible
? mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning)
: undefined;
const thinkingLevel = rawThinkingLevel === "xhigh" && configuredEfforts === undefined
? "high"
: rawThinkingLevel;
const includeThoughts = thinkingEligible
&& provider.showThinkingSummary === true
&& parsed.options.hideThinkingSummary !== true
Expand Down
10 changes: 8 additions & 2 deletions src/codex/catalog/gather-capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from "../../oauth";
import { getAccountSet } from "../../oauth/store";
import { mayResolveModelsOAuth } from "../../oauth/model-discovery-auth";
import { buildVertexProviderModelsRequest } from "../../providers/google-vertex-model-discovery";
import type { OcxConfig, OcxProviderConfig } from "../../types";
import { modelInList } from "../../types";
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
Expand Down Expand Up @@ -129,6 +130,8 @@ export interface ModelsAuthResolution {
readonly oauthGeneration?: string;
readonly oauthApiBaseUrl?: string;
readonly oauthProjectId?: string;
/** Vertex discovery credential bound to its authority (passive gathers). */
readonly vertexAuth?: import("../../providers/google-vertex-model-discovery").VertexDiscoveryAuth;
}

export type ModelsAuthResolver =
Expand Down Expand Up @@ -332,15 +335,18 @@ export function captureTrustedOpenAiApiPolicy(
});
}

/** Capture credential-free and sentinel-bearing model discovery requests for flight deduplication. */
export function captureModelsRequest(
name: string,
provider: OcxProviderConfig,
oauthApiBaseUrl: string | undefined,
workbuddy?: import("../../oauth/types").WorkBuddyOAuthMetadata,
): CapturedModelsRequest {
const observed = { oauthApiBaseUrl, workbuddy };
const withoutCredential = buildModelsRequest(provider, undefined, name, observed);
const withCredential = buildModelsRequest(provider, REQUEST_CREDENTIAL_SENTINEL, name, observed);
const withoutCredential = buildVertexProviderModelsRequest(name, provider, undefined)
?? buildModelsRequest(provider, undefined, name, observed);
const withCredential = buildVertexProviderModelsRequest(name, provider, REQUEST_CREDENTIAL_SENTINEL)
?? buildModelsRequest(provider, REQUEST_CREDENTIAL_SENTINEL, name, observed);
const method = withoutCredential.method ?? "GET";
if (withoutCredential.url !== withCredential.url || method !== (withCredential.method ?? "GET")) {
throw new TypeError(`Provider model discovery URL for ${name} depends on credential bytes.`);
Expand Down
Loading
Loading