pi-antigravity-rotator 1.12.2 → 1.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/compat.ts +221 -47
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.12.4] - 2026-05-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Claude `cache_control` stripping**: Anthropic requests often include `cache_control` objects which Google Cloud Code Assist API rejects with "Extra inputs are not permitted". The proxy now safely strips `cache_control` from all system and message content blocks before forwarding them to Gemini.
|
|
7
|
+
- **Claude `VALIDATED` Function Calling**: Automatically enforces `toolConfig: { functionCallingConfig: { mode: "VALIDATED" } }` for Claude models when tools are present, ensuring stricter schema adherence.
|
|
8
|
+
- **Adaptive Thinking Budgets**: Replaced static thinking budget values with a dynamic `MODEL_SPECS` mapping. `gemini-3-flash` now correctly uses adaptive thinking budgets (`-1`) which allows the model to decide its own optimal reasoning length, while Pro models use strict budgets (e.g. `10001` for high).
|
|
9
|
+
- **Max Output Tokens Enforcement**: The proxy now enforces hard `maxOutputTokens` caps based on the specific model's upper limits (e.g. `65535` vs `64000`), dynamically adjusting them to ensure there is enough room for both the thinking budget and the final output response without triggering upstream validation errors.
|
|
10
|
+
|
|
11
|
+
## [1.12.3] - 2026-05-18
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **Gemini 3.1 Pro High Deprecation (`400 Invalid Argument`)**: Google Cloud Code Assist deprecated the internal string `"gemini-3.1-pro-high"` and replaced it with `"gemini-pro-agent"`. The proxy now automatically maps `"gemini-3.1-pro-high"` to `"gemini-pro-agent"` under the hood when constructing the upstream payload, preventing `400` validation errors while allowing clients to continue using the `-high` alias.
|
|
15
|
+
- **Missing `thought_signature` on Tool Calls (`400 Invalid Argument`)**: Gemini thinking models strictly require a cryptographic Base64 `thought_signature` for all `functionCall` history parts, which the proxy normally caches in RAM. To prevent API rejection on cache misses (e.g. after a proxy restart or when using synthetic tool IDs), the proxy now gracefully collapses the orphaned tool exchange into a neutral user summary (`[Context: The assistant used tools...]`). This preserves the conversation context without triggering the `400` error or teaching the model bad tool-calling formats.
|
|
16
|
+
|
|
3
17
|
## [1.12.2] - 2026-05-18
|
|
4
18
|
|
|
5
19
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-antigravity-rotator",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.4",
|
|
4
4
|
"description": "Multi-account rotation proxy for Google Antigravity with per-model routing, real-time quota tracking, and infringement detection",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/compat.ts
CHANGED
|
@@ -86,6 +86,64 @@ export interface CompatCompletion {
|
|
|
86
86
|
toolCalls?: OpenAIToolCall[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Model-specific specs — mirrors Antigravity-Manager model_specs.json
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
interface ModelSpec {
|
|
93
|
+
maxOutputTokens: number;
|
|
94
|
+
thinkingBudget: number; // -1 = adaptive (model decides), >=0 = fixed
|
|
95
|
+
isThinking: boolean;
|
|
96
|
+
}
|
|
97
|
+
const MODEL_SPECS: Record<string, ModelSpec> = {
|
|
98
|
+
"gemini-pro-agent": { maxOutputTokens: 65535, thinkingBudget: 10001, isThinking: true },
|
|
99
|
+
"gemini-3-flash-agent": { maxOutputTokens: 65536, thinkingBudget: -1, isThinking: true },
|
|
100
|
+
"gemini-3-pro-high": { maxOutputTokens: 65535, thinkingBudget: 10001, isThinking: true },
|
|
101
|
+
"gemini-3-pro-low": { maxOutputTokens: 65535, thinkingBudget: 1001, isThinking: true },
|
|
102
|
+
"gemini-3.1-pro-high": { maxOutputTokens: 65535, thinkingBudget: 10001, isThinking: true },
|
|
103
|
+
"gemini-3.1-pro-low": { maxOutputTokens: 65535, thinkingBudget: 1001, isThinking: true },
|
|
104
|
+
"gemini-3.1-pro-preview": { maxOutputTokens: 65535, thinkingBudget: 10001, isThinking: true },
|
|
105
|
+
"gemini-3-flash": { maxOutputTokens: 65536, thinkingBudget: 32768, isThinking: true },
|
|
106
|
+
"gemini-2.5-flash": { maxOutputTokens: 65535, thinkingBudget: 24576, isThinking: true },
|
|
107
|
+
"gemini-2.5-pro": { maxOutputTokens: 65535, thinkingBudget: 1024, isThinking: true },
|
|
108
|
+
"claude-sonnet-4-6": { maxOutputTokens: 64000, thinkingBudget: 32768, isThinking: true },
|
|
109
|
+
"claude-sonnet-4-6-thinking":{ maxOutputTokens: 64000, thinkingBudget: 32768, isThinking: true },
|
|
110
|
+
"claude-opus-4-6-thinking": { maxOutputTokens: 64000, thinkingBudget: 32768, isThinking: true },
|
|
111
|
+
};
|
|
112
|
+
const GEMINI_MAX_OUTPUT_TOKENS = 65536;
|
|
113
|
+
const CLAUDE_MAX_OUTPUT_TOKENS = 64000;
|
|
114
|
+
const FALLBACK_THINKING_BUDGET = 24576;
|
|
115
|
+
const CLAUDE_DEFAULT_THINKING_BUDGET = 32768;
|
|
116
|
+
|
|
117
|
+
function getModelFamily(model: string): "claude" | "gemini" | "unknown" {
|
|
118
|
+
const l = model.toLowerCase();
|
|
119
|
+
if (l.includes("claude")) return "claude";
|
|
120
|
+
if (l.includes("gemini")) return "gemini";
|
|
121
|
+
return "unknown";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getModelSpec(model: string): ModelSpec {
|
|
125
|
+
const lower = model.toLowerCase();
|
|
126
|
+
if (MODEL_SPECS[lower]) return MODEL_SPECS[lower];
|
|
127
|
+
for (const [key, spec] of Object.entries(MODEL_SPECS)) {
|
|
128
|
+
if (lower.includes(key)) return spec;
|
|
129
|
+
}
|
|
130
|
+
const family = getModelFamily(model);
|
|
131
|
+
if (family === "claude") return { maxOutputTokens: CLAUDE_MAX_OUTPUT_TOKENS, thinkingBudget: CLAUDE_DEFAULT_THINKING_BUDGET, isThinking: true };
|
|
132
|
+
if (family === "gemini") return { maxOutputTokens: GEMINI_MAX_OUTPUT_TOKENS, thinkingBudget: FALLBACK_THINKING_BUDGET, isThinking: true };
|
|
133
|
+
return { maxOutputTokens: 65536, thinkingBudget: FALLBACK_THINKING_BUDGET, isThinking: false };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isThinkingModel(model: string): boolean {
|
|
137
|
+
const spec = getModelSpec(model);
|
|
138
|
+
if (spec.isThinking) return true;
|
|
139
|
+
const l = model.toLowerCase();
|
|
140
|
+
if (l.includes("gemini")) {
|
|
141
|
+
const m = l.match(/gemini-(\d+)/);
|
|
142
|
+
if (m && parseInt(m[1], 10) >= 3) return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
89
147
|
type AntigravityPart = { text: string } | { inlineData: { mimeType: string; data: string } };
|
|
90
148
|
|
|
91
149
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -120,12 +178,29 @@ function cacheThoughtSignature(callId: string, signature: string): void {
|
|
|
120
178
|
thoughtSignatureCache.set(callId, signature);
|
|
121
179
|
}
|
|
122
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Strip cache_control fields from content blocks.
|
|
183
|
+
* Cloud Code API rejects cache_control with "Extra inputs are not permitted".
|
|
184
|
+
*/
|
|
185
|
+
function cleanCacheControl<T>(content: T): T {
|
|
186
|
+
if (!Array.isArray(content)) return content;
|
|
187
|
+
return content.map((block: Record<string, unknown>) => {
|
|
188
|
+
if (!block || typeof block !== "object") return block;
|
|
189
|
+
if ("cache_control" in block) {
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
191
|
+
const { cache_control: _cc, ...rest } = block;
|
|
192
|
+
return rest;
|
|
193
|
+
}
|
|
194
|
+
return block;
|
|
195
|
+
}) as T;
|
|
196
|
+
}
|
|
197
|
+
|
|
123
198
|
function extractText(content: ChatMessage["content"]): string {
|
|
124
199
|
if (typeof content === "string") return content;
|
|
125
200
|
if (!Array.isArray(content)) return "";
|
|
126
|
-
return content
|
|
127
|
-
.filter((p) => (p.type === "text" && typeof p.text === "string") || (p.type === "thinking" && typeof p.thinking === "string"))
|
|
128
|
-
.map((p) => p.type === "thinking" ? `[Thinking]\n${p.thinking}\n[/Thinking]` : p.text)
|
|
201
|
+
return cleanCacheControl(content)
|
|
202
|
+
.filter((p: { type?: string; text?: string; thinking?: string }) => (p.type === "text" && typeof p.text === "string") || (p.type === "thinking" && typeof p.thinking === "string"))
|
|
203
|
+
.map((p: { type?: string; text?: string; thinking?: string }) => p.type === "thinking" ? `[Thinking]\n${p.thinking}\n[/Thinking]` : (p.text as string))
|
|
129
204
|
.join("\n");
|
|
130
205
|
}
|
|
131
206
|
|
|
@@ -246,12 +321,15 @@ function sanitizeClaudeViaGeminiSchema(schema: unknown): unknown {
|
|
|
246
321
|
if (!isRecord(schema)) return schema;
|
|
247
322
|
|
|
248
323
|
// Only remove fields that Gemini's API layer truly rejects at the network level.
|
|
249
|
-
// We keep Draft 2020-12 keywords
|
|
324
|
+
// We keep standard Draft 2020-12 keywords but must strip exclusiveMinimum/exclusiveMaximum
|
|
325
|
+
// as boolean values (Draft 4) — the API layer rejects them even for Claude-bound requests.
|
|
250
326
|
const UNSUPPORTED = new Set([
|
|
251
327
|
"$schema", "$id", "$ref", "$defs", "definitions",
|
|
252
328
|
"if", "then", "else", "not",
|
|
253
329
|
"patternProperties", "unevaluatedProperties", "unevaluatedItems",
|
|
254
330
|
"contentEncoding", "contentMediaType",
|
|
331
|
+
// Gemini's protobuf layer rejects these regardless of target model
|
|
332
|
+
"exclusiveMinimum", "exclusiveMaximum",
|
|
255
333
|
]);
|
|
256
334
|
|
|
257
335
|
const out: Record<string, unknown> = {};
|
|
@@ -411,10 +489,41 @@ export function openAIToAntigravityBody(input: OpenAIChatCompletionRequest): Req
|
|
|
411
489
|
// Determine if model is Claude — affects schema sanitization and tool call ID handling
|
|
412
490
|
const isClaude = /^claude-/i.test(input.model);
|
|
413
491
|
|
|
492
|
+
// Use model specs to determine thinking support
|
|
493
|
+
const isThinking = isThinkingModel(input.model);
|
|
494
|
+
const isGeminiThinking = !isClaude && isThinking;
|
|
495
|
+
|
|
414
496
|
const contents: GeminiContent[] = [];
|
|
415
497
|
for (let i = 0; i < conversationMessages.length; i++) {
|
|
416
498
|
const msg = conversationMessages[i];
|
|
417
499
|
if (msg.role === "assistant") {
|
|
500
|
+
// Check if this is a thinking model turn with tool calls that have no cached signatures.
|
|
501
|
+
// If so, we collapse the tool exchange into a neutral user summary instead of
|
|
502
|
+
// injecting [Tool call: ...] text that the model will learn to mimic.
|
|
503
|
+
const hasMissingSig =
|
|
504
|
+
isGeminiThinking &&
|
|
505
|
+
Array.isArray(msg.tool_calls) &&
|
|
506
|
+
msg.tool_calls.length > 0 &&
|
|
507
|
+
!thoughtSignatureCache.has(msg.tool_calls[0].id);
|
|
508
|
+
|
|
509
|
+
if (hasMissingSig) {
|
|
510
|
+
// Build a summary of what the model did and what results came back.
|
|
511
|
+
// We collect the paired tool result(s) from the immediately following messages.
|
|
512
|
+
const toolNames = msg.tool_calls!.map((tc) => tc.function.name).join(", ");
|
|
513
|
+
const resultParts: string[] = [];
|
|
514
|
+
while (i + 1 < conversationMessages.length && conversationMessages[i + 1].role === "tool") {
|
|
515
|
+
i++;
|
|
516
|
+
const toolMsg = conversationMessages[i];
|
|
517
|
+
const toolText = typeof toolMsg.content === "string" ? toolMsg.content : extractText(toolMsg.content);
|
|
518
|
+
resultParts.push(`${toolMsg.name || "tool"}: ${toolText.slice(0, 500)}`);
|
|
519
|
+
}
|
|
520
|
+
const summaryText = `[Context: The assistant used tools (${toolNames}) and received results:\n${resultParts.join("\n")}]`;
|
|
521
|
+
contents.push({ role: "user", parts: [{ text: summaryText }] });
|
|
522
|
+
// Add a minimal model acknowledgement to avoid consecutive user turns
|
|
523
|
+
contents.push({ role: "model", parts: [{ text: "Understood, I have the tool results." }] });
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
|
|
418
527
|
const parts: unknown[] = [];
|
|
419
528
|
if (msg.content) {
|
|
420
529
|
const textContent = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
@@ -427,28 +536,24 @@ export function openAIToAntigravityBody(input: OpenAIChatCompletionRequest): Req
|
|
|
427
536
|
// signatures on older historical turns are silently ignored.
|
|
428
537
|
let isFirstInMessage = true;
|
|
429
538
|
for (const tc of msg.tool_calls) {
|
|
539
|
+
let args: unknown;
|
|
430
540
|
try {
|
|
431
|
-
|
|
432
|
-
// Only the first functionCall part in a model turn needs the signature
|
|
433
|
-
const cachedSig = isFirstInMessage ? thoughtSignatureCache.get(tc.id) : undefined;
|
|
434
|
-
parts.push({
|
|
435
|
-
...(cachedSig ? { thoughtSignature: cachedSig } : {}),
|
|
436
|
-
// Include id only for Claude — Gemini native models reject the id field
|
|
437
|
-
functionCall: { ...(isClaude ? { id: tc.id } : {}), name: tc.function.name, args },
|
|
438
|
-
});
|
|
541
|
+
args = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
|
|
439
542
|
} catch {
|
|
440
|
-
|
|
441
|
-
parts.push({
|
|
442
|
-
...(cachedSig ? { thoughtSignature: cachedSig } : {}),
|
|
443
|
-
functionCall: { ...(isClaude ? { id: tc.id } : {}), name: tc.function.name, args: {} },
|
|
444
|
-
});
|
|
543
|
+
args = {};
|
|
445
544
|
}
|
|
545
|
+
// Only the first functionCall part in a model turn needs the signature
|
|
546
|
+
const cachedSig = isFirstInMessage ? thoughtSignatureCache.get(tc.id) : undefined;
|
|
547
|
+
parts.push({
|
|
548
|
+
...(cachedSig ? { thoughtSignature: cachedSig } : {}),
|
|
549
|
+
// Include id only for Claude — Gemini native models reject the id field
|
|
550
|
+
functionCall: { ...(isClaude ? { id: tc.id } : {}), name: tc.function.name, args },
|
|
551
|
+
});
|
|
446
552
|
isFirstInMessage = false;
|
|
447
553
|
}
|
|
448
554
|
}
|
|
449
555
|
if (parts.length > 0) contents.push({ role: "model", parts });
|
|
450
556
|
} else if (msg.role === "tool") {
|
|
451
|
-
const prevMsg = conversationMessages[i - 1];
|
|
452
557
|
const responseText = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
453
558
|
const fnName = msg.name || "unknown";
|
|
454
559
|
// Include tool_call_id so Gemini can pass it as tool_use_id to Claude
|
|
@@ -460,6 +565,7 @@ export function openAIToAntigravityBody(input: OpenAIChatCompletionRequest): Req
|
|
|
460
565
|
} else {
|
|
461
566
|
// user message
|
|
462
567
|
const msgParts = extractParts(msg.content);
|
|
568
|
+
|
|
463
569
|
if (msgParts.length > 0) contents.push({ role: "user", parts: msgParts });
|
|
464
570
|
}
|
|
465
571
|
}
|
|
@@ -472,35 +578,84 @@ export function openAIToAntigravityBody(input: OpenAIChatCompletionRequest): Req
|
|
|
472
578
|
const geminiTools = convertOpenAIToolsToGemini(inputTools, isClaude);
|
|
473
579
|
const geminiToolConfig = input.tool_choice !== undefined ? convertToolChoiceToGemini(input.tool_choice) : undefined;
|
|
474
580
|
|
|
475
|
-
//
|
|
476
|
-
const
|
|
581
|
+
// Cap maxOutputTokens to model limits and build thinkingConfig
|
|
582
|
+
const modelSpec = getModelSpec(input.model);
|
|
583
|
+
const modelFamily = getModelFamily(input.model);
|
|
584
|
+
let maxOutputTokens = typeof input.max_tokens === "number" ? input.max_tokens : undefined;
|
|
585
|
+
if (maxOutputTokens && maxOutputTokens > modelSpec.maxOutputTokens) {
|
|
586
|
+
compatLogger.debug(`Capping ${input.model} maxOutputTokens ${maxOutputTokens} → ${modelSpec.maxOutputTokens}`);
|
|
587
|
+
maxOutputTokens = modelSpec.maxOutputTokens;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
let thinkingConfigObj: Record<string, unknown> | undefined;
|
|
591
|
+
if (modelFamily === "claude" && isThinking) {
|
|
592
|
+
// Claude: snake_case keys required by v1internal
|
|
593
|
+
const tb = modelSpec.thinkingBudget;
|
|
594
|
+
thinkingConfigObj = { include_thoughts: true, thinking_budget: tb };
|
|
595
|
+
if (!maxOutputTokens || maxOutputTokens <= tb) {
|
|
596
|
+
maxOutputTokens = Math.min(tb + 8192, modelSpec.maxOutputTokens);
|
|
597
|
+
compatLogger.debug(`Adjusted Claude maxOutputTokens → ${maxOutputTokens}`);
|
|
598
|
+
}
|
|
599
|
+
} else if (isThinking) {
|
|
600
|
+
// Gemini: camelCase keys; thinkingBudget=-1 means adaptive (omit the field)
|
|
601
|
+
const tb = modelSpec.thinkingBudget;
|
|
602
|
+
thinkingConfigObj = tb === -1
|
|
603
|
+
? { includeThoughts: true }
|
|
604
|
+
: { includeThoughts: true, thinkingBudget: tb };
|
|
605
|
+
if (tb !== -1 && (!maxOutputTokens || maxOutputTokens <= tb)) {
|
|
606
|
+
maxOutputTokens = Math.min(tb + 8192, modelSpec.maxOutputTokens);
|
|
607
|
+
compatLogger.debug(`Adjusted Gemini maxOutputTokens → ${maxOutputTokens}`);
|
|
608
|
+
}
|
|
609
|
+
} else if (input.reasoning_effort) {
|
|
610
|
+
// Non-thinking models with explicit reasoning_effort hint
|
|
611
|
+
const budgets: Record<string, number> = { low: Math.round(modelSpec.thinkingBudget / 4), medium: Math.round(modelSpec.thinkingBudget / 2), high: modelSpec.thinkingBudget };
|
|
612
|
+
const b = budgets[input.reasoning_effort.toLowerCase()];
|
|
613
|
+
if (b) thinkingConfigObj = { includeThoughts: true, thinkingBudget: b };
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const generationConfig: Record<string, unknown> = {
|
|
617
|
+
...(typeof input.temperature === "number" ? { temperature: input.temperature } : {}),
|
|
618
|
+
...(maxOutputTokens ? { maxOutputTokens } : {}),
|
|
619
|
+
...(thinkingConfigObj ? { thinkingConfig: thinkingConfigObj } : {}),
|
|
620
|
+
};
|
|
477
621
|
|
|
478
622
|
const request: Record<string, unknown> = {
|
|
479
623
|
contents,
|
|
480
|
-
generationConfig
|
|
481
|
-
...(typeof input.temperature === "number" ? { temperature: input.temperature } : {}),
|
|
482
|
-
...(typeof input.max_tokens === "number" ? { maxOutputTokens: input.max_tokens } : {}),
|
|
483
|
-
// Always request thought blocks. Models that don't support thinking ignore this.
|
|
484
|
-
thinkingConfig: {
|
|
485
|
-
includeThoughts: true,
|
|
486
|
-
...(thinkingLevel ? { thinkingLevel } : {}),
|
|
487
|
-
},
|
|
488
|
-
},
|
|
624
|
+
generationConfig,
|
|
489
625
|
};
|
|
490
626
|
|
|
491
627
|
if (systemParts.length > 0) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
628
|
+
if (!isClaude && isThinking) {
|
|
629
|
+
// Gemini thinking models (gemini-3.1-pro-high/low) reject the systemInstruction
|
|
630
|
+
// field entirely — prepend system prompt to the first user content turn instead.
|
|
631
|
+
const firstTurn = contents[0];
|
|
632
|
+
if (firstTurn && firstTurn.role === "user" && (firstTurn.parts[0] as any)?.text !== undefined) {
|
|
633
|
+
(firstTurn.parts[0] as any).text = systemParts.join("\n\n") + "\n\n" + (firstTurn.parts[0] as any).text;
|
|
634
|
+
} else if (firstTurn && firstTurn.role === "user") {
|
|
635
|
+
firstTurn.parts.unshift({ text: systemParts.join("\n\n") + "\n\n" });
|
|
636
|
+
} else {
|
|
637
|
+
contents.unshift({
|
|
638
|
+
role: "user",
|
|
639
|
+
parts: [{ text: systemParts.join("\n\n") }],
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
request.systemInstruction = {
|
|
644
|
+
role: "system",
|
|
645
|
+
parts: [{ text: systemParts.join("\n\n") }],
|
|
646
|
+
};
|
|
647
|
+
}
|
|
496
648
|
}
|
|
497
649
|
|
|
498
650
|
if (geminiTools.length > 0) request.tools = geminiTools;
|
|
499
651
|
if (geminiToolConfig) request.toolConfig = geminiToolConfig;
|
|
500
652
|
|
|
653
|
+
let mappedModel = input.model;
|
|
654
|
+
if (mappedModel === "gemini-3.1-pro-high") mappedModel = "gemini-pro-agent";
|
|
655
|
+
|
|
501
656
|
return {
|
|
502
657
|
project: "compat-placeholder",
|
|
503
|
-
model:
|
|
658
|
+
model: mappedModel,
|
|
504
659
|
userAgent: "antigravity",
|
|
505
660
|
requestType: "agent",
|
|
506
661
|
request,
|
|
@@ -522,28 +677,47 @@ export function anthropicToAntigravityBody(input: AnthropicMessagesRequest): Req
|
|
|
522
677
|
}
|
|
523
678
|
|
|
524
679
|
/**
|
|
525
|
-
* Maps an OpenAI reasoning_effort
|
|
526
|
-
*
|
|
680
|
+
* Maps an OpenAI reasoning_effort / model name suffix to a Gemini thinkingBudget integer.
|
|
681
|
+
* Cloud Code Assist uses thinkingBudget (integer token count), not thinkingLevel (string).
|
|
682
|
+
* Values match models.json: -high=10001, -low=1001, flash=dynamic(-1 means dynamic).
|
|
683
|
+
* Returns undefined for models that don't need an explicit budget (e.g. Claude, plain flash).
|
|
527
684
|
*/
|
|
528
|
-
function mapReasoningEffortToThinkingLevel(effort: string | undefined, modelId: string):
|
|
529
|
-
const
|
|
530
|
-
|
|
685
|
+
function mapReasoningEffortToThinkingLevel(effort: string | undefined, modelId: string): number | undefined {
|
|
686
|
+
const lowerModel = modelId.toLowerCase();
|
|
687
|
+
const isGemini31Pro = /gemini-3\.1-pro/i.test(modelId);
|
|
688
|
+
const isGemini3Flash = lowerModel.includes("gemini-3-flash");
|
|
689
|
+
|
|
531
690
|
let effectiveEffort = effort;
|
|
532
691
|
if (!effectiveEffort) {
|
|
533
|
-
|
|
534
|
-
if (lowerModel.endsWith("-high") || lowerModel.includes("claude-")) effectiveEffort = "high";
|
|
692
|
+
if (lowerModel.endsWith("-high") || lowerModel.includes("gemini-pro-agent")) effectiveEffort = "high";
|
|
535
693
|
else if (lowerModel.endsWith("-low")) effectiveEffort = "low";
|
|
536
|
-
else if (
|
|
694
|
+
else if (isGemini3Flash) effectiveEffort = "high";
|
|
695
|
+
// Claude models: skip — thinking is handled by the anthropic-beta header
|
|
537
696
|
}
|
|
538
697
|
|
|
539
698
|
if (!effectiveEffort) return undefined;
|
|
540
699
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
700
|
+
// Gemini 3.1 Pro uses fixed budgets matching models.json
|
|
701
|
+
if (isGemini31Pro) {
|
|
702
|
+
switch (effectiveEffort.toLowerCase()) {
|
|
703
|
+
case "high": return 10001;
|
|
704
|
+
case "medium": return 5000;
|
|
705
|
+
case "low": return 1001;
|
|
706
|
+
default: return undefined;
|
|
707
|
+
}
|
|
546
708
|
}
|
|
709
|
+
|
|
710
|
+
// Flash uses dynamic budget (-1 means let the model decide)
|
|
711
|
+
if (isGemini3Flash) {
|
|
712
|
+
switch (effectiveEffort.toLowerCase()) {
|
|
713
|
+
case "high": return -1;
|
|
714
|
+
case "medium": return 4096;
|
|
715
|
+
case "low": return 1024;
|
|
716
|
+
default: return undefined;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return undefined;
|
|
547
721
|
}
|
|
548
722
|
|
|
549
723
|
export function parseAntigravitySse(raw: string): CompatCompletion {
|