Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/reference/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,8 @@ MiMo model Command Code serves.
opaque `thoughtSignature` values so tool-result continuations retain Gemini reasoning continuity.
The signature cache is snapshotted to the config directory, so continuations also survive proxy
restarts.
- Antigravity's Gemini 4 Argon requests include the selected thinking tier when thought summaries
are enabled, allowing its thought summaries to appear in supported clients.
- **Malformed response shapes fail closed.** A claimed candidate, its `content`, or its
`content.parts` that is not the documented container terminates the turn with a
`google response contained invalid …` error naming the structural reason and the offending
Expand Down
14 changes: 7 additions & 7 deletions src/adapters/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1130,21 +1130,21 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
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);
// CCA Gemini exposes provider-authored summaries only when explicitly requested.
const includeThoughts = provider.showThinkingSummary === true
&& parsed.options.hideThinkingSummary !== true
&& /^gemini-/.test(routedModelId)
&& !isImageCapableModel(parsed.modelId);
const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel(
parsed.modelId,
mappedEffort,
provider.baseUrl,
{ includeThoughts },
);
antigravityModel = wireModelId;
antigravitySession = sessionId;
// CCA Gemini exposes provider-authored thought summaries with includeThoughts.
// Other CCA model families do not share this request contract.
const includeThoughts = provider.showThinkingSummary === true
&& parsed.options.hideThinkingSummary !== true
&& /^gemini-/.test(wireModelId)
&& !isImageCapableModel(parsed.modelId);
// Effort → thinkingConfig for CCA (CLIProxyAPI proven: request.generationConfig.thinkingConfig).
// Suffix/compat IDs return thinkingLevel=undefined — the suffix IS the effort, no contradiction.
// Argon summaries repeat the suffix tier; other suffix IDs keep their existing shape.
if (thinkingLevel || includeThoughts) {
const gc = (body.generationConfig ?? {}) as Record<string, unknown>;
gc.thinkingConfig = {
Expand Down
57 changes: 45 additions & 12 deletions src/providers/antigravity-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,41 @@ const ANTIGRAVITY_EFFORT_WIRE_MAP: Record<string, Record<string, string>> = {
* Sending `thinkingLevel` beside such a suffix states the effort twice, and CCA does not reject
* the contradiction — a `-low` wire id paired with `HIGH` returns 200, so the tier that actually
* ran becomes unknowable from the response. Membership also makes static resolution
* byte-identical to the discovery path, which never emits a thinking level.
* byte-identical to discovery, except when an opted-in Argon summary needs its matching level.
*
* `gemini-3.1-pro` is deliberately absent: its `high` rung is `gemini-pro-agent`, which carries
* no tier suffix, so there the level is the only thing naming the effort.
*/
const ANTIGRAVITY_SUFFIX_TIER_MODELS = new Set([GEMINI_ARGON, "gemini-3.8-flash", CLAUDE_OPUS_55, CLAUDE_SONNET_55]);

/**
* Suffix-tier models that withhold `thought: true` text unless the request ALSO names its tier
* in `thinkingConfig.thinkingLevel`.
*
* Measured against live CCA (2026-10-27): `gemini-4-argon-high` with
* `{includeThoughts: true}` alone answers 200 with `thoughtsTokenCount` in the thousands and
* zero thought parts — the whole plan arrives as ordinary visible text. Sending
* `{thinkingLevel: "high", includeThoughts: true}` on the same wire id returns the thought
* parts. `gemini-3.8-flash-high` does not behave this way: its include-only request still
* returns thoughts, so it stays out of this set and keeps the one-field shape.
*/
const ANTIGRAVITY_THOUGHT_LEVEL_REQUIRED_SUFFIX_MODELS = new Set([GEMINI_ARGON]);

function suffixThoughtLevel(wireModelId: string, includeThoughts: boolean | undefined): string | undefined {
if (!includeThoughts) return undefined;
for (const base of ANTIGRAVITY_THOUGHT_LEVEL_REQUIRED_SUFFIX_MODELS) {
for (const tier of ANTIGRAVITY_DISCOVERY_EFFORTS) {
if (wireModelId === `${base}-${tier}`) return tier;
}
}
return undefined;
}

function suffixThoughtWire(wireModelId: string, includeThoughts: boolean | undefined): { wireModelId: string; thinkingLevel?: string } {
const thinkingLevel = suffixThoughtLevel(wireModelId, includeThoughts);
return { wireModelId, ...(thinkingLevel ? { thinkingLevel } : {}) };
}

function completeDiscoveredEffortWireModelIds(
pickerId: string,
available: ReadonlyMap<string, Record<string, unknown>>,
Expand Down Expand Up @@ -719,8 +747,8 @@ export function resolveAntigravityWireModelId(modelId: string, baseUrl?: string)

/**
* Whether the given model ID is a suffix wire ID or compat alias that already encodes
* an effort level. For these IDs, the caller must NOT send thinkingConfig — the suffix
* IS the effort, and sending both creates a contradictory request.
* an effort level. The suffix names the effort; an opted-in Argon summary additionally
* repeats that same level in thinkingConfig.
*/
export function isAntigravitySuffixModelId(modelId: string): boolean {
return !ANTIGRAVITY_KNOWN_PICKER_MODELS.includes(modelId);
Expand All @@ -735,19 +763,22 @@ export function retiredAntigravityFlashTier(modelId: string): string | undefined
* Resolve a picker-visible base model + optional reasoning effort to the CCA wire model ID.
*
* Precedence (evaluated in order):
* 1. Suffix wire ID or compat alias → resolve via `resolveAntigravityWireModelId`, no thinkingConfig.
* 1. Suffix wire ID or compat alias → resolve via `resolveAntigravityWireModelId`.
* 2. Mapped Gemini base with effort → return mapped wire ID + thinkingLevel.
* 3. Mapped Gemini base without effort → return default-effort wire ID, no thinkingConfig.
* 3. Mapped Gemini base without effort → return default-effort wire ID.
* 4. Claude Opus with effort → return identity + thinkingLevel (no suffix variants exist).
* 5. All other IDs → return `resolveAntigravityWireModelId(modelId)`, no thinkingConfig.
*/
export function resolveAntigravityEffortWireModel(
modelId: string,
effort?: string,
baseUrl?: string,
options?: { includeThoughts?: boolean },
): { wireModelId: string; thinkingLevel?: string } {
const discoveredEffortWireModelId = discoveredAntigravityEffortWireModelId(modelId, effort, baseUrl);
if (discoveredEffortWireModelId) return { wireModelId: discoveredEffortWireModelId };
if (discoveredEffortWireModelId) {
return suffixThoughtWire(discoveredEffortWireModelId, options?.includeThoughts);
}

// A collapsed picker row reports ONE representative wire id (whichever tier CCA
// listed first), so live discovery cannot describe a ladder — it can only name a
Expand All @@ -763,11 +794,12 @@ export function resolveAntigravityEffortWireModel(
: discoveredAntigravityWireModelId(modelId, baseUrl);
if (discoveredWireModelId && (discoveredWireModelId !== modelId || isAntigravitySuffixModelId(modelId))) {
const defaultLevel = ANTIGRAVITY_THINKING_LEVEL_MODELS[modelId];
const suffixLevel = suffixThoughtLevel(discoveredWireModelId, options?.includeThoughts);
return {
wireModelId: discoveredWireModelId,
...(defaultLevel
? { thinkingLevel: effort ? resolveAntigravityThinkingLevel(effort) ?? defaultLevel : defaultLevel }
: {}),
: suffixLevel ? { thinkingLevel: suffixLevel } : {}),
};
}

Expand All @@ -786,7 +818,8 @@ export function resolveAntigravityEffortWireModel(

// Rule 1: suffix/compat alias — suffix IS the effort.
if (isAntigravitySuffixModelId(modelId)) {
return { wireModelId: resolveAntigravityWireModelId(modelId, baseUrl) };
const wireModelId = resolveAntigravityWireModelId(modelId, baseUrl);
return suffixThoughtWire(wireModelId, options?.includeThoughts);
}

// Rule 1b: single-wire-id Gemini model whose tiers ride on thinkingLevel. Without
Expand All @@ -809,13 +842,13 @@ export function resolveAntigravityEffortWireModel(
const requested = suffixTiered && effort
? resolveAntigravityThinkingLevel(effort) ?? effort
: effort;
if (requested && requested in effortMap) {
if (requested && Object.hasOwn(effortMap, requested)) {
const wireModelId = effortMap[requested]!;
// The suffix already names the tier; see ANTIGRAVITY_SUFFIX_TIER_MODELS.
return suffixTiered ? { wireModelId } : { wireModelId, thinkingLevel: requested };
return suffixTiered ? suffixThoughtWire(wireModelId, options?.includeThoughts)
: { wireModelId, thinkingLevel: requested };
}
const defaultEffort = ANTIGRAVITY_DEFAULT_EFFORT[modelId]!;
return { wireModelId: effortMap[defaultEffort]! };
return suffixThoughtWire(effortMap[defaultEffort]!, options?.includeThoughts);
}

// Rule 4: Claude models — effort via thinkingConfig only (no suffix variants).
Expand Down
2 changes: 1 addition & 1 deletion structure/providers-and-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ Preflight heartbeat retention keeps `replayUnsafe` sticky in the replayed tail,

## Antigravity effort families

Antigravity discovery in `src/providers/antigravity-models.ts` collapses complete low/medium/high wire families without a version allowlist. Partial families retain exact wire IDs. The adapter sends the selected suffix without a second thinking level; saved suffix requests keep their tier. Existing Claude 4.6 single-wire thinking levels and Gemini irregular aliases keep their contracts. Grouped context is the minimum known across every tier; image support requires agreement, with an explicit negative taking precedence. Mappings are scoped to config home, normalized destination (including path case), and accepted cache generation. `src/providers/antigravity-wire-snapshot.ts` atomically saves bounded exact suffix maps before synthetic catalog rows are published. Hash-named files contain no destination URLs or credentials. First-use restoration after restart supplies routing aliases only; current live discovery replaces the snapshot, and invalidation tombstones prevent stale restoration. A failed snapshot write leaves the previous coherent discovery result.
Antigravity discovery in `src/providers/antigravity-models.ts` collapses complete low/medium/high wire families without a version allowlist. Partial families retain exact wire IDs. The adapter sends the selected suffix without a second thinking level, except Gemini 4 Argon: its wire ids already carry the tier, but CCA returns thought parts only when `thinkingLevel` is also named alongside `includeThoughts`, so the adapter repeats the selected tier only when summaries are requested, including for a lone discovered Argon tier (`tests/adapters/google/gemini-4-argon-antigravity.test.ts`, `tests/adapters/google/antigravity-discovered-families.test.ts`); summary-disabled requests and the web-search sidecar retain their suffix-only shape; `gemini-3.8-flash` keeps its one-field shape; saved suffix requests keep their tier. Existing Claude 4.6 single-wire thinking levels and Gemini irregular aliases keep their contracts. Grouped context is the minimum known across every tier; image support requires agreement, with an explicit negative taking precedence. Mappings are scoped to config home, normalized destination (including path case), and accepted cache generation. `src/providers/antigravity-wire-snapshot.ts` atomically saves bounded exact suffix maps before synthetic catalog rows are published. Hash-named files contain no destination URLs or credentials. First-use restoration after restart supplies routing aliases only; current live discovery replaces the snapshot, and invalidation tombstones prevent stale restoration. A failed snapshot write leaves the previous coherent discovery result.

Antigravity catalog rows carry the observed complete effort map from `src/providers/antigravity-models.ts` through the in-memory cache. `src/providers/antigravity-effort-families.ts` uses that evidence to group public rows and project suffix allowlists into the base selection, including both final catalog merge callers. Internal retained suffix rows remain available to combo resolution. Authoritative new-model reconciliation normalizes both the prior baseline and policy input before detecting arrivals: an existing suffix family is not a new model. All-disabled tiers transfer a base disable once; a known base keeps the operator's later choice. Genuinely new families still follow new-model policy. Saved defaults, combo targets, suffix disables and provider selections remain unchanged. A discovered ladder advertises only its wire-backed efforts unless explicitly overridden; unknown single-wire models gain no invented ladder.

Expand Down
2 changes: 2 additions & 0 deletions structure/providers/google.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ Other Google wires without the opt-in, non-Gemini models and uninitialized adapt
explicit wire summary none win. Eligible CCA, Vertex and direct Gemini requests use
`includeThoughts: true` only when provider opt-in and per-request display both allow it. Thought signatures remain attached
to their tool calls independently; they never become Anthropic thinking signatures.
For CCA Argon summaries, the adapter also repeats the selected wire suffix tier as `thinkingLevel`;
when summaries are disabled, Argon and the web-search sidecar retain their suffix-only request shape.

The visibility marker is the boolean `thought: true`, not the presence of a `thoughtSignature`
or the wording of the text. An unmarked signed text part remains ordinary assistant output;
Expand Down
18 changes: 18 additions & 0 deletions tests/adapters/google/antigravity-discovered-families.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ function payload(base: string, tiers: readonly string[] = efforts) {
}
afterEach(() => { registerAntigravityDiscoveredWireModels(baseUrl, []); clearModelCache("family-test"); });

test("discovered Argon repeats the selected tier only for requested summaries", () => {
registerAntigravityDiscoveredWireModels(baseUrl, parseAntigravityAvailableModels(payload("gemini-4-argon"))!);
for (const [effort, tier] of [["low", "low"], ["medium", "medium"], ["high", "high"],
["xhigh", "high"], [undefined, "medium"], ["unknown", "medium"], ["toString", "medium"]] as const) {
expect(resolveAntigravityEffortWireModel("gemini-4-argon", effort, baseUrl, { includeThoughts: true }))
.toEqual({ wireModelId: `gemini-4-argon-${tier}`, thinkingLevel: tier });
expect(resolveAntigravityEffortWireModel("gemini-4-argon", effort, baseUrl))
.toEqual({ wireModelId: `gemini-4-argon-${tier}` });
}
});

for (const base of ["claude-opus-5-5", "claude-sonnet-5-5", "claude-opus-6-2", "future-flash"]) {
test(`${base}: complete discovery collapses independent of version, label and order`, () => {
const rows = parseAntigravityAvailableModels(payload(base, ["high", "low", "medium"]))!;
Expand Down Expand Up @@ -98,3 +109,10 @@ test("an evidenced overlapping base takes routing precedence over a parent tier
expect(resolveAntigravityEffortWireModel("foo-low", "high", baseUrl)).toEqual({ wireModelId: "foo-low-high" });
expect(resolveAntigravityEffortWireModel("foo", "low", baseUrl)).toEqual({ wireModelId: "foo-low" });
});

test("a lone discovered Argon tier keeps its level so thought text still streams", () => {
const rows = parseAntigravityAvailableModels(payload("gemini-4-argon", ["high"]))!;
registerAntigravityDiscoveredWireModels(baseUrl, rows);
expect(resolveAntigravityEffortWireModel("gemini-4-argon-high", undefined, baseUrl, { includeThoughts: true }))
.toEqual({ wireModelId: "gemini-4-argon-high", thinkingLevel: "high" });
});
Loading
Loading