Client or integration
Other
Provider or upstream service
google-antigravity / Google Antigravity
OpenCodex version
2.7.43 (d1f544bbc22d25b9b2bd3c8e776fb8e3242b4ed5). Also reproduced on current dev (bfacc3bcf71077a77afc25d9b613644d3409750c).
Endpoint or capability
Model discovery and provider connection test
Current behaviour
The built-in google-antigravity preset has a maintained six-model catalog but does not declare liveModels: false.
After a successful OAuth login, model discovery sends:
GET https://daily-cloudcode-pa.googleapis.com/models
That endpoint returns HTTP 404. OpenCodex preserves the configured catalog, but /api/providers permanently reports discovery as failed. The Models dashboard therefore displays Discovery failed beside an otherwise usable provider, and the connection test can report the provider as unavailable even though inference and quota retrieval work.
A redacted live check confirmed that the three selected configured models remained available. A minimal inference request through google-antigravity/gemini-3.6-flash completed successfully.
Expected behaviour
A built-in provider without a generic GET /models endpoint should not be represented as a failed live-discovery provider or probed repeatedly through an unsupported endpoint.
The minimal safe behaviour is to treat Google Antigravity as a static-catalog provider. If live discovery is intended instead, it should use the provider-specific authenticated POST /v1internal:fetchAvailableModels contract and normalize its response before publishing models.
Minimal redacted request or reproduction
1. Install OpenCodex 2.7.43.
2. Complete the built-in google-antigravity OAuth login.
3. Open the Models dashboard.
4. Request GET /api/providers with the local management credential redacted.
5. Observe that the configured catalog remains available while discovery is failed/http/404.
6. Send a minimal inference request through a selected configured model and observe that it succeeds.
Actual response or error
{
"name": "google-antigravity",
"liveModels": true,
"models": ["<six configured model IDs>"],
"discovery": {
"status": "failed",
"reason": "http",
"httpStatus": 404
}
}
Sanitized service log:
Provider model discovery for "google-antigravity" failed with HTTP 404 [urlClass=provider-models, fallback=configured].
The configured fallback remains usable. This report concerns the deterministic false-failure state, unnecessary probe, and misleading connection result rather than an inference outage.
Upstream documentation
No public Google Antigravity model-discovery specification was found.
Google's official Gemini CLI shows OAuth Code Assist using the private v1internal POST API family:
https://github.com/google-gemini/gemini-cli/blob/3c1bb8c35d9b43128636cdee24af01bf717c66fb/packages/core/src/code_assist/server.ts#L73-L74
OpenCodex already calls POST /v1internal:fetchAvailableModels for Google Antigravity quota/model availability:
|
async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaReport | null> { |
|
const credential = getCredential("google-antigravity"); |
|
if (!credential?.projectId) return null; |
|
let accessToken: string; |
|
try { |
|
accessToken = await getValidAccessToken("google-antigravity"); |
|
} catch { |
|
return null; |
|
} |
|
const baseUrl = (config.baseUrl || "https://daily-cloudcode-pa.googleapis.com").replace(/\/+$/, ""); |
|
const response = await fetch(`${baseUrl}/v1internal:fetchAvailableModels`, { |
|
method: "POST", |
|
headers: { |
|
Accept: "application/json", |
|
"Content-Type": "application/json", |
|
"User-Agent": antigravityUserAgent(), |
|
Authorization: `Bearer ${accessToken}`, |
|
}, |
|
body: JSON.stringify({ project: credential.projectId }), |
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), |
|
}); |
|
if (!response.ok) return null; |
|
const body = asRecord(await response.json().catch(() => null)); |
|
const models = asRecord(body?.models); |
|
if (!models) return null; |
|
|
|
const windows = new Map<string, ProviderQuotaWindow>(); |
|
for (const [modelId, rawModelInfo] of Object.entries(models)) { |
|
const modelInfo = asRecord(rawModelInfo); |
|
if (!modelInfo) continue; |
|
for (const quotaInfo of quotaInfoEntries(modelInfo)) { |
|
const label = classifyAntigravityFamily(modelId, modelInfo, quotaInfo); |
|
if (!label || windows.has(label)) continue; |
|
const percent = antigravityUsedPercent(quotaInfo); |
|
if (percent === undefined) continue; |
|
windows.set(label, { |
|
label, |
|
percent, |
|
...(normalizeResetAt(quotaInfo.resetTime) !== undefined ? { resetAt: normalizeResetAt(quotaInfo.resetTime) } : {}), |
|
}); |
|
} |
|
} |
|
|
|
const customWindows = ["Gem", "Cla"].flatMap(label => { |
|
const window = windows.get(label); |
|
return window ? [window] : []; |
|
}); |
|
if (customWindows.length === 0) return null; |
|
return report(provider, "google-antigravity:fetchAvailableModels", { |
|
customWindows, |
|
updatedAt: Date.now(), |
|
}); |
Suggested mapping or implementation notes
Preferred minimal fix:
- Set the built-in
google-antigravity registry entry to liveModels: false and use its maintained static catalog.
- Ensure existing OAuth configurations without an explicit override inherit the registry value.
- Clear or suppress discovery-failure state and skip the futile generic probe.
- Add regression coverage for registry derivation,
/api/providers, the configured catalog, and the connection test.
Changing only modelDiscovery.path is insufficient because the current discovery contract assumes a GET request, while the provider-specific endpoint is an authenticated POST request with a different response shape.
Additional context and attachments
Relevant source paths:
- Built-in preset:
|
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, defaultModel: "gemini-3.6-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] }, |
- Generic model-discovery branch:
|
// Generative Language API: API key goes in x-goog-api-key (never Authorization: Bearer), |
|
// models live under /v1beta (v1 misses preview models), and pageSize maxes at 1000 — |
|
// enough to list everything without a pageToken loop. Vertex/antigravity keep the |
|
// generic branch (they fall back to their static model lists). |
|
if (apiKey) headers["x-goog-api-key"] = apiKey; |
|
return { url: `${effectiveProvider.baseUrl}/v1beta/models?pageSize=1000`, headers }; |
|
} |
|
if (effectiveProvider.adapter === "anthropic") { |
|
const base = effectiveProvider.baseUrl.replace(/\/v1\/?$/, ""); |
|
headers["anthropic-version"] = "2023-06-01"; |
|
if (effectiveProvider.authMode === "oauth") { |
|
headers["anthropic-beta"] = ANTHROPIC_OAUTH_BETA; |
|
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`; |
|
} else if (apiKey) { |
|
if (effectiveProvider.apiKeyTransport === "bearer") headers["Authorization"] = `Bearer ${apiKey}`; |
|
else headers["x-api-key"] = apiKey; |
|
} |
|
return { url: `${base}/v1/models?limit=1000`, headers }; |
|
} |
|
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`; |
|
return { url: `${effectiveProvider.baseUrl}/models`, headers }; |
- Working inference path:
|
if (!token) throw new Error("google-antigravity oauth token missing — run ocx login google-antigravity"); |
|
const base = provider.baseUrl?.trim(); |
|
if (!base) throw new Error("google-antigravity requires a non-empty baseUrl"); |
|
const url = `${base}/v1internal:${method}${streamParam}`; |
|
const project = provider.project; |
|
if (!project) throw new Error("Antigravity requires a discovered Cloud Code Assist project id (re-run `ocx login google-antigravity`)."); |
|
const sessionId = antigravitySessionId(parsed); |
|
const mappedEffort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning); |
|
const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel(parsed.modelId, mappedEffort); |
|
antigravityModel = wireModelId; |
|
antigravitySession = sessionId; |
|
// Effort → thinkingConfig for CCA (CLIProxyAPI proven: request.generationConfig.thinkingConfig). |
|
// Suffix/compat IDs return thinkingLevel=undefined — the suffix IS the effort, no contradiction. |
|
if (thinkingLevel) { |
|
const gc = (body.generationConfig ?? {}) as Record<string, unknown>; |
|
gc.thinkingConfig = { thinkingLevel }; |
|
body.generationConfig = gc; |
|
} |
|
// Reasoning continuity: Gemini models re-inject cached thoughtSignatures; Claude-on-Antigravity |
|
// sanitizes signatures inline (no cache). Both guard against the upstream 400 on bad signatures. |
|
// The real Antigravity client puts the session id ONLY at `request.sessionId` (camelCase, |
|
// nested) — matching CLIProxyAPI `generateStableSessionID`. An extra top-level/snake_case |
|
// spelling is a non-first-party key, so we send the single canonical location. |
|
const draftRequest: Record<string, unknown> = { ...body, sessionId }; |
|
// Claude-on-Antigravity forces VALIDATED function calling (the real client always sets it). |
|
if (/claude/i.test(wireModelId)) { |
|
const existing = (draftRequest.toolConfig ?? {}) as Record<string, unknown>; |
|
const fcc = (existing.functionCallingConfig ?? {}) as Record<string, unknown>; |
|
draftRequest.toolConfig = { ...existing, functionCallingConfig: { ...fcc, mode: "VALIDATED" } }; |
|
} |
|
const compiled = compileGoogleWireBody(draftRequest); |
|
const request = compiled.body; |
|
restoreGoogleToolName = compiled.restoreToolName; |
|
// Compile names before replay: signatures are keyed by the exact provider-visible name. |
|
if (Array.isArray((request as { contents?: unknown[] }).contents)) { |
|
const contents = (request as { contents: unknown[] }).contents; |
|
if (antigravityUsesReplayCache(wireModelId)) { |
|
applyAntigravityReplay(wireModelId, sessionId, contents); |
|
} else { |
|
sanitizeAntigravityClaudeSignatures(contents); |
|
} |
|
} |
|
const envelope = { |
|
model: wireModelId, |
|
// The envelope's `userAgent` field is a protocol constant ("antigravity"), distinct from |
|
// the HTTP `User-Agent` header (the real CLI UA). CLIProxyAPI `geminiToAntigravity` hardcodes |
|
// the body field; only the header carries the versioned client string. |
|
userAgent: "antigravity", |
|
requestType: "agent", |
|
project, |
|
requestId: `agent-${crypto.randomUUID()}`, |
|
request, |
|
}; |
|
headers["User-Agent"] = ANTIGRAVITY_REQUEST_UA; |
|
headers["Authorization"] = `Bearer ${token}`; |
This is distinct from #395, which concerns repeated logs for a custom provider, and from #358, which intentionally keeps genuine discovery failures visible even when fallback models exist.
Checks
Client or integration
Other
Provider or upstream service
google-antigravity / Google Antigravity
OpenCodex version
2.7.43 (
d1f544bbc22d25b9b2bd3c8e776fb8e3242b4ed5). Also reproduced on currentdev(bfacc3bcf71077a77afc25d9b613644d3409750c).Endpoint or capability
Model discovery and provider connection test
Current behaviour
The built-in
google-antigravitypreset has a maintained six-model catalog but does not declareliveModels: false.After a successful OAuth login, model discovery sends:
That endpoint returns HTTP 404. OpenCodex preserves the configured catalog, but
/api/providerspermanently reports discovery as failed. The Models dashboard therefore displaysDiscovery failedbeside an otherwise usable provider, and the connection test can report the provider as unavailable even though inference and quota retrieval work.A redacted live check confirmed that the three selected configured models remained available. A minimal inference request through
google-antigravity/gemini-3.6-flashcompleted successfully.Expected behaviour
A built-in provider without a generic
GET /modelsendpoint should not be represented as a failed live-discovery provider or probed repeatedly through an unsupported endpoint.The minimal safe behaviour is to treat Google Antigravity as a static-catalog provider. If live discovery is intended instead, it should use the provider-specific authenticated
POST /v1internal:fetchAvailableModelscontract and normalize its response before publishing models.Minimal redacted request or reproduction
Actual response or error
{ "name": "google-antigravity", "liveModels": true, "models": ["<six configured model IDs>"], "discovery": { "status": "failed", "reason": "http", "httpStatus": 404 } }Sanitized service log:
The configured fallback remains usable. This report concerns the deterministic false-failure state, unnecessary probe, and misleading connection result rather than an inference outage.
Upstream documentation
No public Google Antigravity model-discovery specification was found.
Google's official Gemini CLI shows OAuth Code Assist using the private
v1internalPOST API family:https://github.com/google-gemini/gemini-cli/blob/3c1bb8c35d9b43128636cdee24af01bf717c66fb/packages/core/src/code_assist/server.ts#L73-L74
OpenCodex already calls
POST /v1internal:fetchAvailableModelsfor Google Antigravity quota/model availability:opencodex/src/providers/quota.ts
Lines 783 to 834 in d1f544b
Suggested mapping or implementation notes
Preferred minimal fix:
google-antigravityregistry entry toliveModels: falseand use its maintained static catalog./api/providers, the configured catalog, and the connection test.Changing only
modelDiscovery.pathis insufficient because the current discovery contract assumes a GET request, while the provider-specific endpoint is an authenticated POST request with a different response shape.Additional context and attachments
Relevant source paths:
opencodex/src/providers/registry.ts
Line 720 in d1f544b
opencodex/src/oauth/index.ts
Lines 490 to 510 in d1f544b
opencodex/src/adapters/google.ts
Lines 321 to 375 in d1f544b
This is distinct from #395, which concerns repeated logs for a custom provider, and from #358, which intentionally keeps genuine discovery failures visible even when fallback models exist.
Checks