opencode-antigravity-auth-mf 1.3.0-1
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/LICENSE +21 -0
- package/README.md +630 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/src/antigravity/oauth.d.ts +31 -0
- package/dist/src/antigravity/oauth.d.ts.map +1 -0
- package/dist/src/antigravity/oauth.js +168 -0
- package/dist/src/antigravity/oauth.js.map +1 -0
- package/dist/src/constants.d.ts +99 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +135 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/hooks/auto-update-checker/cache.d.ts +3 -0
- package/dist/src/hooks/auto-update-checker/cache.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/cache.js +71 -0
- package/dist/src/hooks/auto-update-checker/cache.js.map +1 -0
- package/dist/src/hooks/auto-update-checker/checker.d.ts +16 -0
- package/dist/src/hooks/auto-update-checker/checker.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/checker.js +237 -0
- package/dist/src/hooks/auto-update-checker/checker.js.map +1 -0
- package/dist/src/hooks/auto-update-checker/constants.d.ts +9 -0
- package/dist/src/hooks/auto-update-checker/constants.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/constants.js +23 -0
- package/dist/src/hooks/auto-update-checker/constants.js.map +1 -0
- package/dist/src/hooks/auto-update-checker/index.d.ts +34 -0
- package/dist/src/hooks/auto-update-checker/index.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/index.js +125 -0
- package/dist/src/hooks/auto-update-checker/index.js.map +1 -0
- package/dist/src/hooks/auto-update-checker/types.d.ts +25 -0
- package/dist/src/hooks/auto-update-checker/types.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/types.js +1 -0
- package/dist/src/hooks/auto-update-checker/types.js.map +1 -0
- package/dist/src/plugin/accounts.d.ts +86 -0
- package/dist/src/plugin/accounts.d.ts.map +1 -0
- package/dist/src/plugin/accounts.js +609 -0
- package/dist/src/plugin/accounts.js.map +1 -0
- package/dist/src/plugin/auth.d.ts +21 -0
- package/dist/src/plugin/auth.d.ts.map +1 -0
- package/dist/src/plugin/auth.js +46 -0
- package/dist/src/plugin/auth.js.map +1 -0
- package/dist/src/plugin/cache/index.d.ts +5 -0
- package/dist/src/plugin/cache/index.d.ts.map +1 -0
- package/dist/src/plugin/cache/index.js +5 -0
- package/dist/src/plugin/cache/index.js.map +1 -0
- package/dist/src/plugin/cache/signature-cache.d.ts +111 -0
- package/dist/src/plugin/cache/signature-cache.d.ts.map +1 -0
- package/dist/src/plugin/cache/signature-cache.js +375 -0
- package/dist/src/plugin/cache/signature-cache.js.map +1 -0
- package/dist/src/plugin/cache.d.ts +44 -0
- package/dist/src/plugin/cache.d.ts.map +1 -0
- package/dist/src/plugin/cache.js +200 -0
- package/dist/src/plugin/cache.js.map +1 -0
- package/dist/src/plugin/cli.d.ts +19 -0
- package/dist/src/plugin/cli.d.ts.map +1 -0
- package/dist/src/plugin/cli.js +59 -0
- package/dist/src/plugin/cli.js.map +1 -0
- package/dist/src/plugin/config/index.d.ts +16 -0
- package/dist/src/plugin/config/index.d.ts.map +1 -0
- package/dist/src/plugin/config/index.js +16 -0
- package/dist/src/plugin/config/index.js.map +1 -0
- package/dist/src/plugin/config/loader.d.ts +37 -0
- package/dist/src/plugin/config/loader.d.ts.map +1 -0
- package/dist/src/plugin/config/loader.js +206 -0
- package/dist/src/plugin/config/loader.js.map +1 -0
- package/dist/src/plugin/config/schema.d.ts +411 -0
- package/dist/src/plugin/config/schema.d.ts.map +1 -0
- package/dist/src/plugin/config/schema.js +339 -0
- package/dist/src/plugin/config/schema.js.map +1 -0
- package/dist/src/plugin/core/streaming/index.d.ts +3 -0
- package/dist/src/plugin/core/streaming/index.d.ts.map +1 -0
- package/dist/src/plugin/core/streaming/index.js +3 -0
- package/dist/src/plugin/core/streaming/index.js.map +1 -0
- package/dist/src/plugin/core/streaming/transformer.d.ts +10 -0
- package/dist/src/plugin/core/streaming/transformer.d.ts.map +1 -0
- package/dist/src/plugin/core/streaming/transformer.js +255 -0
- package/dist/src/plugin/core/streaming/transformer.js.map +1 -0
- package/dist/src/plugin/core/streaming/types.d.ts +35 -0
- package/dist/src/plugin/core/streaming/types.d.ts.map +1 -0
- package/dist/src/plugin/core/streaming/types.js +1 -0
- package/dist/src/plugin/core/streaming/types.js.map +1 -0
- package/dist/src/plugin/debug.d.ts +68 -0
- package/dist/src/plugin/debug.d.ts.map +1 -0
- package/dist/src/plugin/debug.js +325 -0
- package/dist/src/plugin/debug.js.map +1 -0
- package/dist/src/plugin/errors.d.ts +28 -0
- package/dist/src/plugin/errors.d.ts.map +1 -0
- package/dist/src/plugin/errors.js +42 -0
- package/dist/src/plugin/errors.js.map +1 -0
- package/dist/src/plugin/image-saver.d.ts +25 -0
- package/dist/src/plugin/image-saver.d.ts.map +1 -0
- package/dist/src/plugin/image-saver.js +86 -0
- package/dist/src/plugin/image-saver.js.map +1 -0
- package/dist/src/plugin/logger.d.ts +54 -0
- package/dist/src/plugin/logger.d.ts.map +1 -0
- package/dist/src/plugin/logger.js +120 -0
- package/dist/src/plugin/logger.js.map +1 -0
- package/dist/src/plugin/project.d.ts +33 -0
- package/dist/src/plugin/project.d.ts.map +1 -0
- package/dist/src/plugin/project.js +234 -0
- package/dist/src/plugin/project.js.map +1 -0
- package/dist/src/plugin/recovery/constants.d.ts +22 -0
- package/dist/src/plugin/recovery/constants.d.ts.map +1 -0
- package/dist/src/plugin/recovery/constants.js +43 -0
- package/dist/src/plugin/recovery/constants.js.map +1 -0
- package/dist/src/plugin/recovery/index.d.ts +12 -0
- package/dist/src/plugin/recovery/index.d.ts.map +1 -0
- package/dist/src/plugin/recovery/index.js +12 -0
- package/dist/src/plugin/recovery/index.js.map +1 -0
- package/dist/src/plugin/recovery/storage.d.ts +24 -0
- package/dist/src/plugin/recovery/storage.d.ts.map +1 -0
- package/dist/src/plugin/recovery/storage.js +354 -0
- package/dist/src/plugin/recovery/storage.js.map +1 -0
- package/dist/src/plugin/recovery/types.d.ts +116 -0
- package/dist/src/plugin/recovery/types.d.ts.map +1 -0
- package/dist/src/plugin/recovery/types.js +6 -0
- package/dist/src/plugin/recovery/types.js.map +1 -0
- package/dist/src/plugin/recovery.d.ts +61 -0
- package/dist/src/plugin/recovery.d.ts.map +1 -0
- package/dist/src/plugin/recovery.js +376 -0
- package/dist/src/plugin/recovery.js.map +1 -0
- package/dist/src/plugin/refresh-queue.d.ts +101 -0
- package/dist/src/plugin/refresh-queue.d.ts.map +1 -0
- package/dist/src/plugin/refresh-queue.js +244 -0
- package/dist/src/plugin/refresh-queue.js.map +1 -0
- package/dist/src/plugin/request-helpers.d.ts +278 -0
- package/dist/src/plugin/request-helpers.d.ts.map +1 -0
- package/dist/src/plugin/request-helpers.js +2268 -0
- package/dist/src/plugin/request-helpers.js.map +1 -0
- package/dist/src/plugin/request.d.ts +91 -0
- package/dist/src/plugin/request.d.ts.map +1 -0
- package/dist/src/plugin/request.js +1302 -0
- package/dist/src/plugin/request.js.map +1 -0
- package/dist/src/plugin/rotation.d.ts +168 -0
- package/dist/src/plugin/rotation.d.ts.map +1 -0
- package/dist/src/plugin/rotation.js +302 -0
- package/dist/src/plugin/rotation.js.map +1 -0
- package/dist/src/plugin/server.d.ts +23 -0
- package/dist/src/plugin/server.d.ts.map +1 -0
- package/dist/src/plugin/server.js +324 -0
- package/dist/src/plugin/server.js.map +1 -0
- package/dist/src/plugin/storage.d.ts +92 -0
- package/dist/src/plugin/storage.d.ts.map +1 -0
- package/dist/src/plugin/storage.js +417 -0
- package/dist/src/plugin/storage.js.map +1 -0
- package/dist/src/plugin/stores/signature-store.d.ts +5 -0
- package/dist/src/plugin/stores/signature-store.d.ts.map +1 -0
- package/dist/src/plugin/stores/signature-store.js +25 -0
- package/dist/src/plugin/stores/signature-store.js.map +1 -0
- package/dist/src/plugin/thinking-recovery.d.ts +90 -0
- package/dist/src/plugin/thinking-recovery.d.ts.map +1 -0
- package/dist/src/plugin/thinking-recovery.js +316 -0
- package/dist/src/plugin/thinking-recovery.js.map +1 -0
- package/dist/src/plugin/token.d.ts +19 -0
- package/dist/src/plugin/token.d.ts.map +1 -0
- package/dist/src/plugin/token.js +128 -0
- package/dist/src/plugin/token.js.map +1 -0
- package/dist/src/plugin/transform/claude.d.ts +80 -0
- package/dist/src/plugin/transform/claude.d.ts.map +1 -0
- package/dist/src/plugin/transform/claude.js +265 -0
- package/dist/src/plugin/transform/claude.js.map +1 -0
- package/dist/src/plugin/transform/cross-model-sanitizer.d.ts +35 -0
- package/dist/src/plugin/transform/cross-model-sanitizer.d.ts.map +1 -0
- package/dist/src/plugin/transform/cross-model-sanitizer.js +225 -0
- package/dist/src/plugin/transform/cross-model-sanitizer.js.map +1 -0
- package/dist/src/plugin/transform/gemini.d.ts +112 -0
- package/dist/src/plugin/transform/gemini.d.ts.map +1 -0
- package/dist/src/plugin/transform/gemini.js +409 -0
- package/dist/src/plugin/transform/gemini.js.map +1 -0
- package/dist/src/plugin/transform/index.d.ts +15 -0
- package/dist/src/plugin/transform/index.d.ts.map +1 -0
- package/dist/src/plugin/transform/index.js +14 -0
- package/dist/src/plugin/transform/index.js.map +1 -0
- package/dist/src/plugin/transform/model-resolver.d.ts +101 -0
- package/dist/src/plugin/transform/model-resolver.d.ts.map +1 -0
- package/dist/src/plugin/transform/model-resolver.js +356 -0
- package/dist/src/plugin/transform/model-resolver.js.map +1 -0
- package/dist/src/plugin/transform/types.d.ts +106 -0
- package/dist/src/plugin/transform/types.d.ts.map +1 -0
- package/dist/src/plugin/transform/types.js +1 -0
- package/dist/src/plugin/transform/types.js.map +1 -0
- package/dist/src/plugin/types.d.ts +96 -0
- package/dist/src/plugin/types.d.ts.map +1 -0
- package/dist/src/plugin/types.js +1 -0
- package/dist/src/plugin/types.js.map +1 -0
- package/dist/src/plugin/usage-reporter.d.ts +23 -0
- package/dist/src/plugin/usage-reporter.d.ts.map +1 -0
- package/dist/src/plugin/usage-reporter.js +43 -0
- package/dist/src/plugin/usage-reporter.js.map +1 -0
- package/dist/src/plugin.d.ts +8 -0
- package/dist/src/plugin.d.ts.map +1 -0
- package/dist/src/plugin.js +1708 -0
- package/dist/src/plugin.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,1302 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { ANTIGRAVITY_HEADERS, GEMINI_CLI_HEADERS, ANTIGRAVITY_ENDPOINT, GEMINI_CLI_ENDPOINT, EMPTY_SCHEMA_PLACEHOLDER_NAME, EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION, SKIP_THOUGHT_SIGNATURE, } from "../constants";
|
|
3
|
+
import { cacheSignature, getCachedSignature } from "./cache";
|
|
4
|
+
import { createStreamingTransformer, transformSseLine, transformStreamingPayload, } from "./core/streaming";
|
|
5
|
+
import { defaultSignatureStore } from "./stores/signature-store";
|
|
6
|
+
import { DEBUG_MESSAGE_PREFIX, isDebugEnabled, logAntigravityDebugResponse, } from "./debug";
|
|
7
|
+
import { createLogger } from "./logger";
|
|
8
|
+
import { cleanJSONSchemaForAntigravity, DEFAULT_THINKING_BUDGET, deepFilterThinkingBlocks, extractThinkingConfig, extractVariantThinkingConfig, extractUsageFromSsePayload, extractUsageMetadata, fixToolResponseGrouping, validateAndFixClaudeToolPairing, applyToolPairingFixes, injectParameterSignatures, injectToolHardeningInstruction, isThinkingCapableModel, normalizeThinkingConfig, parseAntigravityApiBody, resolveThinkingConfig, rewriteAntigravityPreviewAccessError, transformThinkingParts, } from "./request-helpers";
|
|
9
|
+
import { CLAUDE_TOOL_SYSTEM_INSTRUCTION, CLAUDE_DESCRIPTION_PROMPT, ANTIGRAVITY_SYSTEM_INSTRUCTION, } from "../constants";
|
|
10
|
+
import { analyzeConversationState, closeToolLoopForThinking, needsThinkingRecovery, } from "./thinking-recovery";
|
|
11
|
+
import { sanitizeCrossModelPayloadInPlace } from "./transform/cross-model-sanitizer";
|
|
12
|
+
import { isGemini3Model, isImageGenerationModel, buildImageGenerationConfig, applyGeminiTransforms } from "./transform";
|
|
13
|
+
import { resolveModelWithTier, resolveModelWithVariant, resolveModelForHeaderStyle, isClaudeModel, isClaudeThinkingModel, CLAUDE_THINKING_MAX_OUTPUT_TOKENS, } from "./transform";
|
|
14
|
+
import { detectErrorType } from "./recovery";
|
|
15
|
+
const log = createLogger("request");
|
|
16
|
+
const PLUGIN_SESSION_ID = `-${crypto.randomUUID()}`;
|
|
17
|
+
const sessionDisplayedThinkingHashes = new Set();
|
|
18
|
+
const MIN_SIGNATURE_LENGTH = 50;
|
|
19
|
+
function buildSignatureSessionKey(sessionId, model, conversationKey, projectKey) {
|
|
20
|
+
const modelKey = typeof model === "string" && model.trim() ? model.toLowerCase() : "unknown";
|
|
21
|
+
const projectPart = typeof projectKey === "string" && projectKey.trim()
|
|
22
|
+
? projectKey.trim()
|
|
23
|
+
: "default";
|
|
24
|
+
const conversationPart = typeof conversationKey === "string" && conversationKey.trim()
|
|
25
|
+
? conversationKey.trim()
|
|
26
|
+
: "default";
|
|
27
|
+
return `${sessionId}:${modelKey}:${projectPart}:${conversationPart}`;
|
|
28
|
+
}
|
|
29
|
+
function shouldCacheThinkingSignatures(model) {
|
|
30
|
+
if (typeof model !== "string")
|
|
31
|
+
return false;
|
|
32
|
+
const lower = model.toLowerCase();
|
|
33
|
+
// Both Claude and Gemini 3 models require thought signature caching
|
|
34
|
+
// for multi-turn conversations with function calling
|
|
35
|
+
return lower.includes("claude") || lower.includes("gemini-3");
|
|
36
|
+
}
|
|
37
|
+
function hashConversationSeed(seed) {
|
|
38
|
+
return crypto.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, 16);
|
|
39
|
+
}
|
|
40
|
+
function extractTextFromContent(content) {
|
|
41
|
+
if (typeof content === "string") {
|
|
42
|
+
return content;
|
|
43
|
+
}
|
|
44
|
+
if (!Array.isArray(content)) {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
for (const block of content) {
|
|
48
|
+
if (!block || typeof block !== "object") {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const anyBlock = block;
|
|
52
|
+
if (typeof anyBlock.text === "string") {
|
|
53
|
+
return anyBlock.text;
|
|
54
|
+
}
|
|
55
|
+
if (anyBlock.text && typeof anyBlock.text === "object" && typeof anyBlock.text.text === "string") {
|
|
56
|
+
return anyBlock.text.text;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
function extractConversationSeedFromMessages(messages) {
|
|
62
|
+
const system = messages.find((message) => message?.role === "system");
|
|
63
|
+
const users = messages.filter((message) => message?.role === "user");
|
|
64
|
+
const firstUser = users[0];
|
|
65
|
+
const lastUser = users.length > 0 ? users[users.length - 1] : undefined;
|
|
66
|
+
const systemText = system ? extractTextFromContent(system.content) : "";
|
|
67
|
+
const userText = firstUser ? extractTextFromContent(firstUser.content) : "";
|
|
68
|
+
const fallbackUserText = !userText && lastUser ? extractTextFromContent(lastUser.content) : "";
|
|
69
|
+
return [systemText, userText || fallbackUserText].filter(Boolean).join("|");
|
|
70
|
+
}
|
|
71
|
+
function extractConversationSeedFromContents(contents) {
|
|
72
|
+
const users = contents.filter((content) => content?.role === "user");
|
|
73
|
+
const firstUser = users[0];
|
|
74
|
+
const lastUser = users.length > 0 ? users[users.length - 1] : undefined;
|
|
75
|
+
const primaryUser = firstUser && Array.isArray(firstUser.parts) ? extractTextFromContent(firstUser.parts) : "";
|
|
76
|
+
if (primaryUser) {
|
|
77
|
+
return primaryUser;
|
|
78
|
+
}
|
|
79
|
+
if (lastUser && Array.isArray(lastUser.parts)) {
|
|
80
|
+
return extractTextFromContent(lastUser.parts);
|
|
81
|
+
}
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
function resolveConversationKey(requestPayload) {
|
|
85
|
+
const anyPayload = requestPayload;
|
|
86
|
+
const candidates = [
|
|
87
|
+
anyPayload.conversationId,
|
|
88
|
+
anyPayload.conversation_id,
|
|
89
|
+
anyPayload.thread_id,
|
|
90
|
+
anyPayload.threadId,
|
|
91
|
+
anyPayload.chat_id,
|
|
92
|
+
anyPayload.chatId,
|
|
93
|
+
anyPayload.sessionId,
|
|
94
|
+
anyPayload.session_id,
|
|
95
|
+
anyPayload.metadata?.conversation_id,
|
|
96
|
+
anyPayload.metadata?.conversationId,
|
|
97
|
+
anyPayload.metadata?.thread_id,
|
|
98
|
+
anyPayload.metadata?.threadId,
|
|
99
|
+
];
|
|
100
|
+
for (const candidate of candidates) {
|
|
101
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
102
|
+
return candidate.trim();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const systemSeed = extractTextFromContent(anyPayload.systemInstruction?.parts
|
|
106
|
+
?? anyPayload.systemInstruction
|
|
107
|
+
?? anyPayload.system
|
|
108
|
+
?? anyPayload.system_instruction);
|
|
109
|
+
const messageSeed = Array.isArray(anyPayload.messages)
|
|
110
|
+
? extractConversationSeedFromMessages(anyPayload.messages)
|
|
111
|
+
: Array.isArray(anyPayload.contents)
|
|
112
|
+
? extractConversationSeedFromContents(anyPayload.contents)
|
|
113
|
+
: "";
|
|
114
|
+
const seed = [systemSeed, messageSeed].filter(Boolean).join("|");
|
|
115
|
+
if (!seed) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return `seed-${hashConversationSeed(seed)}`;
|
|
119
|
+
}
|
|
120
|
+
function resolveConversationKeyFromRequests(requestObjects) {
|
|
121
|
+
for (const req of requestObjects) {
|
|
122
|
+
const key = resolveConversationKey(req);
|
|
123
|
+
if (key) {
|
|
124
|
+
return key;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
function resolveProjectKey(candidate, fallback) {
|
|
130
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
131
|
+
return candidate.trim();
|
|
132
|
+
}
|
|
133
|
+
if (typeof fallback === "string" && fallback.trim()) {
|
|
134
|
+
return fallback.trim();
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
function formatDebugLinesForThinking(lines) {
|
|
139
|
+
const cleaned = lines
|
|
140
|
+
.map((line) => line.trim())
|
|
141
|
+
.filter((line) => line.length > 0)
|
|
142
|
+
.slice(-50);
|
|
143
|
+
return `${DEBUG_MESSAGE_PREFIX}\n${cleaned.map((line) => `- ${line}`).join("\n")}`;
|
|
144
|
+
}
|
|
145
|
+
function injectDebugThinking(response, debugText) {
|
|
146
|
+
if (!response || typeof response !== "object") {
|
|
147
|
+
return response;
|
|
148
|
+
}
|
|
149
|
+
const resp = response;
|
|
150
|
+
if (Array.isArray(resp.candidates) && resp.candidates.length > 0) {
|
|
151
|
+
const candidates = resp.candidates.slice();
|
|
152
|
+
const first = candidates[0];
|
|
153
|
+
if (first &&
|
|
154
|
+
typeof first === "object" &&
|
|
155
|
+
first.content &&
|
|
156
|
+
typeof first.content === "object" &&
|
|
157
|
+
Array.isArray(first.content.parts)) {
|
|
158
|
+
const parts = [{ thought: true, text: debugText }, ...first.content.parts];
|
|
159
|
+
candidates[0] = { ...first, content: { ...first.content, parts } };
|
|
160
|
+
return { ...resp, candidates };
|
|
161
|
+
}
|
|
162
|
+
return resp;
|
|
163
|
+
}
|
|
164
|
+
if (Array.isArray(resp.content)) {
|
|
165
|
+
const content = [{ type: "thinking", thinking: debugText }, ...resp.content];
|
|
166
|
+
return { ...resp, content };
|
|
167
|
+
}
|
|
168
|
+
if (!resp.reasoning_content) {
|
|
169
|
+
return { ...resp, reasoning_content: debugText };
|
|
170
|
+
}
|
|
171
|
+
return resp;
|
|
172
|
+
}
|
|
173
|
+
function stripInjectedDebugFromParts(parts) {
|
|
174
|
+
if (!Array.isArray(parts)) {
|
|
175
|
+
return parts;
|
|
176
|
+
}
|
|
177
|
+
return parts.filter((part) => {
|
|
178
|
+
if (!part || typeof part !== "object") {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
const record = part;
|
|
182
|
+
const text = typeof record.text === "string"
|
|
183
|
+
? record.text
|
|
184
|
+
: typeof record.thinking === "string"
|
|
185
|
+
? record.thinking
|
|
186
|
+
: undefined;
|
|
187
|
+
if (text && text.startsWith(DEBUG_MESSAGE_PREFIX)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function stripInjectedDebugFromRequestPayload(payload) {
|
|
194
|
+
const anyPayload = payload;
|
|
195
|
+
if (Array.isArray(anyPayload.contents)) {
|
|
196
|
+
anyPayload.contents = anyPayload.contents.map((content) => {
|
|
197
|
+
if (!content || typeof content !== "object") {
|
|
198
|
+
return content;
|
|
199
|
+
}
|
|
200
|
+
if (Array.isArray(content.parts)) {
|
|
201
|
+
return { ...content, parts: stripInjectedDebugFromParts(content.parts) };
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(content.content)) {
|
|
204
|
+
return { ...content, content: stripInjectedDebugFromParts(content.content) };
|
|
205
|
+
}
|
|
206
|
+
return content;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (Array.isArray(anyPayload.messages)) {
|
|
210
|
+
anyPayload.messages = anyPayload.messages.map((message) => {
|
|
211
|
+
if (!message || typeof message !== "object") {
|
|
212
|
+
return message;
|
|
213
|
+
}
|
|
214
|
+
if (Array.isArray(message.content)) {
|
|
215
|
+
return { ...message, content: stripInjectedDebugFromParts(message.content) };
|
|
216
|
+
}
|
|
217
|
+
return message;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function isGeminiToolUsePart(part) {
|
|
222
|
+
return !!(part && typeof part === "object" && (part.functionCall || part.tool_use || part.toolUse));
|
|
223
|
+
}
|
|
224
|
+
function isGeminiThinkingPart(part) {
|
|
225
|
+
return !!(part &&
|
|
226
|
+
typeof part === "object" &&
|
|
227
|
+
(part.thought === true || part.type === "thinking" || part.type === "reasoning"));
|
|
228
|
+
}
|
|
229
|
+
function ensureThoughtSignature(part, sessionId) {
|
|
230
|
+
if (!part || typeof part !== "object") {
|
|
231
|
+
return part;
|
|
232
|
+
}
|
|
233
|
+
const text = typeof part.text === "string" ? part.text : typeof part.thinking === "string" ? part.thinking : "";
|
|
234
|
+
if (!text) {
|
|
235
|
+
return part;
|
|
236
|
+
}
|
|
237
|
+
if (part.thought === true) {
|
|
238
|
+
if (!part.thoughtSignature) {
|
|
239
|
+
const cached = getCachedSignature(sessionId, text);
|
|
240
|
+
if (cached) {
|
|
241
|
+
return { ...part, thoughtSignature: cached };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return part;
|
|
245
|
+
}
|
|
246
|
+
if ((part.type === "thinking" || part.type === "reasoning") && !part.signature) {
|
|
247
|
+
const cached = getCachedSignature(sessionId, text);
|
|
248
|
+
if (cached) {
|
|
249
|
+
return { ...part, signature: cached };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return part;
|
|
253
|
+
}
|
|
254
|
+
function hasSignedThinkingPart(part) {
|
|
255
|
+
if (!part || typeof part !== "object") {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
if (part.thought === true) {
|
|
259
|
+
return typeof part.thoughtSignature === "string" && part.thoughtSignature.length >= MIN_SIGNATURE_LENGTH;
|
|
260
|
+
}
|
|
261
|
+
if (part.type === "thinking" || part.type === "reasoning") {
|
|
262
|
+
return typeof part.signature === "string" && part.signature.length >= MIN_SIGNATURE_LENGTH;
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
function ensureThinkingBeforeToolUseInContents(contents, signatureSessionKey) {
|
|
267
|
+
return contents.map((content) => {
|
|
268
|
+
if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
|
|
269
|
+
return content;
|
|
270
|
+
}
|
|
271
|
+
const role = content.role;
|
|
272
|
+
if (role !== "model" && role !== "assistant") {
|
|
273
|
+
return content;
|
|
274
|
+
}
|
|
275
|
+
const parts = content.parts;
|
|
276
|
+
const hasToolUse = parts.some(isGeminiToolUsePart);
|
|
277
|
+
if (!hasToolUse) {
|
|
278
|
+
return content;
|
|
279
|
+
}
|
|
280
|
+
const thinkingParts = parts.filter(isGeminiThinkingPart).map((p) => ensureThoughtSignature(p, signatureSessionKey));
|
|
281
|
+
const otherParts = parts.filter((p) => !isGeminiThinkingPart(p));
|
|
282
|
+
const hasSignedThinking = thinkingParts.some(hasSignedThinkingPart);
|
|
283
|
+
if (hasSignedThinking) {
|
|
284
|
+
return { ...content, parts: [...thinkingParts, ...otherParts] };
|
|
285
|
+
}
|
|
286
|
+
const lastThinking = defaultSignatureStore.get(signatureSessionKey);
|
|
287
|
+
if (!lastThinking) {
|
|
288
|
+
return content;
|
|
289
|
+
}
|
|
290
|
+
const injected = {
|
|
291
|
+
thought: true,
|
|
292
|
+
text: lastThinking.text,
|
|
293
|
+
thoughtSignature: lastThinking.signature,
|
|
294
|
+
};
|
|
295
|
+
return { ...content, parts: [injected, ...otherParts] };
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
function ensureMessageThinkingSignature(block, sessionId) {
|
|
299
|
+
if (!block || typeof block !== "object") {
|
|
300
|
+
return block;
|
|
301
|
+
}
|
|
302
|
+
if (block.type !== "thinking" && block.type !== "redacted_thinking") {
|
|
303
|
+
return block;
|
|
304
|
+
}
|
|
305
|
+
if (typeof block.signature === "string" && block.signature.length >= MIN_SIGNATURE_LENGTH) {
|
|
306
|
+
return block;
|
|
307
|
+
}
|
|
308
|
+
const text = typeof block.thinking === "string" ? block.thinking : typeof block.text === "string" ? block.text : "";
|
|
309
|
+
if (!text) {
|
|
310
|
+
return block;
|
|
311
|
+
}
|
|
312
|
+
const cached = getCachedSignature(sessionId, text);
|
|
313
|
+
if (cached) {
|
|
314
|
+
return { ...block, signature: cached };
|
|
315
|
+
}
|
|
316
|
+
return block;
|
|
317
|
+
}
|
|
318
|
+
function hasToolUseInContents(contents) {
|
|
319
|
+
return contents.some((content) => {
|
|
320
|
+
if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
return content.parts.some(isGeminiToolUsePart);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function hasSignedThinkingInContents(contents) {
|
|
327
|
+
return contents.some((content) => {
|
|
328
|
+
if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
return content.parts.some(hasSignedThinkingPart);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
function hasToolUseInMessages(messages) {
|
|
335
|
+
return messages.some((message) => {
|
|
336
|
+
if (!message || typeof message !== "object" || !Array.isArray(message.content)) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
return message.content.some((block) => block && typeof block === "object" && (block.type === "tool_use" || block.type === "tool_result"));
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function hasSignedThinkingInMessages(messages) {
|
|
343
|
+
return messages.some((message) => {
|
|
344
|
+
if (!message || typeof message !== "object" || !Array.isArray(message.content)) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
return message.content.some((block) => block &&
|
|
348
|
+
typeof block === "object" &&
|
|
349
|
+
(block.type === "thinking" || block.type === "redacted_thinking") &&
|
|
350
|
+
typeof block.signature === "string" &&
|
|
351
|
+
block.signature.length >= MIN_SIGNATURE_LENGTH);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
function ensureThinkingBeforeToolUseInMessages(messages, signatureSessionKey) {
|
|
355
|
+
return messages.map((message) => {
|
|
356
|
+
if (!message || typeof message !== "object" || !Array.isArray(message.content)) {
|
|
357
|
+
return message;
|
|
358
|
+
}
|
|
359
|
+
if (message.role !== "assistant") {
|
|
360
|
+
return message;
|
|
361
|
+
}
|
|
362
|
+
const blocks = message.content;
|
|
363
|
+
const hasToolUse = blocks.some((b) => b && typeof b === "object" && (b.type === "tool_use" || b.type === "tool_result"));
|
|
364
|
+
if (!hasToolUse) {
|
|
365
|
+
return message;
|
|
366
|
+
}
|
|
367
|
+
const thinkingBlocks = blocks
|
|
368
|
+
.filter((b) => b && typeof b === "object" && (b.type === "thinking" || b.type === "redacted_thinking"))
|
|
369
|
+
.map((b) => ensureMessageThinkingSignature(b, signatureSessionKey));
|
|
370
|
+
const otherBlocks = blocks.filter((b) => !(b && typeof b === "object" && (b.type === "thinking" || b.type === "redacted_thinking")));
|
|
371
|
+
const hasSignedThinking = thinkingBlocks.some((b) => typeof b.signature === "string" && b.signature.length >= MIN_SIGNATURE_LENGTH);
|
|
372
|
+
if (hasSignedThinking) {
|
|
373
|
+
return { ...message, content: [...thinkingBlocks, ...otherBlocks] };
|
|
374
|
+
}
|
|
375
|
+
const lastThinking = defaultSignatureStore.get(signatureSessionKey);
|
|
376
|
+
if (!lastThinking) {
|
|
377
|
+
// No cached signature available - use sentinel to bypass validation
|
|
378
|
+
// This handles cache miss scenarios (restart, session mismatch, expiry)
|
|
379
|
+
const existingThinking = thinkingBlocks[0];
|
|
380
|
+
const thinkingText = existingThinking?.thinking || existingThinking?.text || "";
|
|
381
|
+
log.debug("Injecting sentinel signature (cache miss)", { signatureSessionKey });
|
|
382
|
+
const sentinelBlock = {
|
|
383
|
+
type: "thinking",
|
|
384
|
+
thinking: thinkingText,
|
|
385
|
+
signature: SKIP_THOUGHT_SIGNATURE,
|
|
386
|
+
};
|
|
387
|
+
return { ...message, content: [sentinelBlock, ...otherBlocks] };
|
|
388
|
+
}
|
|
389
|
+
const injected = {
|
|
390
|
+
type: "thinking",
|
|
391
|
+
thinking: lastThinking.text,
|
|
392
|
+
signature: lastThinking.signature,
|
|
393
|
+
};
|
|
394
|
+
return { ...message, content: [injected, ...otherBlocks] };
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Gets the stable session ID for this plugin instance.
|
|
399
|
+
*/
|
|
400
|
+
export function getPluginSessionId() {
|
|
401
|
+
return PLUGIN_SESSION_ID;
|
|
402
|
+
}
|
|
403
|
+
function generateSyntheticProjectId() {
|
|
404
|
+
const adjectives = ["useful", "bright", "swift", "calm", "bold"];
|
|
405
|
+
const nouns = ["fuze", "wave", "spark", "flow", "core"];
|
|
406
|
+
const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
|
|
407
|
+
const noun = nouns[Math.floor(Math.random() * nouns.length)];
|
|
408
|
+
const randomPart = crypto.randomUUID().slice(0, 5).toLowerCase();
|
|
409
|
+
return `${adj}-${noun}-${randomPart}`;
|
|
410
|
+
}
|
|
411
|
+
const STREAM_ACTION = "streamGenerateContent";
|
|
412
|
+
/**
|
|
413
|
+
* Detects requests headed to the Google Generative Language API so we can intercept them.
|
|
414
|
+
*/
|
|
415
|
+
export function isGenerativeLanguageRequest(input) {
|
|
416
|
+
return typeof input === "string" && input.includes("generativelanguage.googleapis.com");
|
|
417
|
+
}
|
|
418
|
+
export function prepareAntigravityRequest(input, init, accessToken, projectId, endpointOverride, headerStyle = "antigravity", forceThinkingRecovery = false, options) {
|
|
419
|
+
const baseInit = { ...init };
|
|
420
|
+
const headers = new Headers(init?.headers ?? {});
|
|
421
|
+
let resolvedProjectId = projectId?.trim() || "";
|
|
422
|
+
let toolDebugMissing = 0;
|
|
423
|
+
const toolDebugSummaries = [];
|
|
424
|
+
let toolDebugPayload;
|
|
425
|
+
let sessionId;
|
|
426
|
+
let needsSignedThinkingWarmup = false;
|
|
427
|
+
let thinkingRecoveryMessage;
|
|
428
|
+
if (!isGenerativeLanguageRequest(input)) {
|
|
429
|
+
return {
|
|
430
|
+
request: input,
|
|
431
|
+
init: { ...baseInit, headers },
|
|
432
|
+
streaming: false,
|
|
433
|
+
headerStyle,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
437
|
+
headers.delete("x-api-key");
|
|
438
|
+
const match = input.match(/\/models\/([^:]+):(\w+)/);
|
|
439
|
+
if (!match) {
|
|
440
|
+
return {
|
|
441
|
+
request: input,
|
|
442
|
+
init: { ...baseInit, headers },
|
|
443
|
+
streaming: false,
|
|
444
|
+
headerStyle,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
const [, rawModel = "", rawAction = ""] = match;
|
|
448
|
+
const requestedModel = rawModel;
|
|
449
|
+
const resolved = resolveModelForHeaderStyle(rawModel, headerStyle);
|
|
450
|
+
const effectiveModel = resolved.actualModel;
|
|
451
|
+
const streaming = rawAction === STREAM_ACTION;
|
|
452
|
+
const defaultEndpoint = headerStyle === "gemini-cli" ? GEMINI_CLI_ENDPOINT : ANTIGRAVITY_ENDPOINT;
|
|
453
|
+
const baseEndpoint = endpointOverride ?? defaultEndpoint;
|
|
454
|
+
const transformedUrl = `${baseEndpoint}/v1internal:${rawAction}${streaming ? "?alt=sse" : ""}`;
|
|
455
|
+
const isClaude = isClaudeModel(resolved.actualModel);
|
|
456
|
+
const isClaudeThinking = isClaudeThinkingModel(resolved.actualModel);
|
|
457
|
+
// Tier-based thinking configuration from model resolver (can be overridden by variant config)
|
|
458
|
+
let tierThinkingBudget = resolved.thinkingBudget;
|
|
459
|
+
let tierThinkingLevel = resolved.thinkingLevel;
|
|
460
|
+
let signatureSessionKey = buildSignatureSessionKey(PLUGIN_SESSION_ID, effectiveModel, undefined, resolveProjectKey(projectId));
|
|
461
|
+
let body = baseInit.body;
|
|
462
|
+
if (typeof baseInit.body === "string" && baseInit.body) {
|
|
463
|
+
try {
|
|
464
|
+
const parsedBody = JSON.parse(baseInit.body);
|
|
465
|
+
const isWrapped = typeof parsedBody.project === "string" && "request" in parsedBody;
|
|
466
|
+
if (isWrapped) {
|
|
467
|
+
const wrappedBody = {
|
|
468
|
+
...parsedBody,
|
|
469
|
+
model: effectiveModel,
|
|
470
|
+
};
|
|
471
|
+
// Some callers may already send an Antigravity-wrapped body.
|
|
472
|
+
// We still need to sanitize Claude thinking blocks (remove cache_control)
|
|
473
|
+
// and attach a stable sessionId so multi-turn signature caching works.
|
|
474
|
+
const requestRoot = wrappedBody.request;
|
|
475
|
+
const requestObjects = [];
|
|
476
|
+
if (requestRoot && typeof requestRoot === "object") {
|
|
477
|
+
requestObjects.push(requestRoot);
|
|
478
|
+
const nested = requestRoot.request;
|
|
479
|
+
if (nested && typeof nested === "object") {
|
|
480
|
+
requestObjects.push(nested);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const conversationKey = resolveConversationKeyFromRequests(requestObjects);
|
|
484
|
+
// Strip tier suffix from model for cache key to prevent cache misses on tier change
|
|
485
|
+
// e.g., "claude-opus-4-5-thinking-high" -> "claude-opus-4-5-thinking"
|
|
486
|
+
const modelForCacheKey = effectiveModel.replace(/-(minimal|low|medium|high)$/i, "");
|
|
487
|
+
signatureSessionKey = buildSignatureSessionKey(PLUGIN_SESSION_ID, modelForCacheKey, conversationKey, resolveProjectKey(parsedBody.project));
|
|
488
|
+
if (requestObjects.length > 0) {
|
|
489
|
+
sessionId = signatureSessionKey;
|
|
490
|
+
}
|
|
491
|
+
for (const req of requestObjects) {
|
|
492
|
+
// Use stable session ID for signature caching across multi-turn conversations
|
|
493
|
+
req.sessionId = signatureSessionKey;
|
|
494
|
+
stripInjectedDebugFromRequestPayload(req);
|
|
495
|
+
if (isClaude) {
|
|
496
|
+
// Step 0: Sanitize cross-model metadata (strips Gemini signatures when sending to Claude)
|
|
497
|
+
sanitizeCrossModelPayloadInPlace(req, { targetModel: effectiveModel });
|
|
498
|
+
// Step 1: Strip corrupted/unsigned thinking blocks FIRST
|
|
499
|
+
deepFilterThinkingBlocks(req, signatureSessionKey, getCachedSignature, true);
|
|
500
|
+
// Step 2: THEN inject signed thinking from cache (after stripping)
|
|
501
|
+
if (isClaudeThinking && Array.isArray(req.contents)) {
|
|
502
|
+
req.contents = ensureThinkingBeforeToolUseInContents(req.contents, signatureSessionKey);
|
|
503
|
+
}
|
|
504
|
+
if (isClaudeThinking && Array.isArray(req.messages)) {
|
|
505
|
+
req.messages = ensureThinkingBeforeToolUseInMessages(req.messages, signatureSessionKey);
|
|
506
|
+
}
|
|
507
|
+
// Step 3: Apply tool pairing fixes (ID assignment, response matching, orphan recovery)
|
|
508
|
+
applyToolPairingFixes(req, true);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (isClaudeThinking && sessionId) {
|
|
512
|
+
const hasToolUse = requestObjects.some((req) => (Array.isArray(req.contents) && hasToolUseInContents(req.contents)) ||
|
|
513
|
+
(Array.isArray(req.messages) && hasToolUseInMessages(req.messages)));
|
|
514
|
+
const hasSignedThinking = requestObjects.some((req) => (Array.isArray(req.contents) && hasSignedThinkingInContents(req.contents)) ||
|
|
515
|
+
(Array.isArray(req.messages) && hasSignedThinkingInMessages(req.messages)));
|
|
516
|
+
const hasCachedThinking = defaultSignatureStore.has(signatureSessionKey);
|
|
517
|
+
needsSignedThinkingWarmup = hasToolUse && !hasSignedThinking && !hasCachedThinking;
|
|
518
|
+
}
|
|
519
|
+
body = JSON.stringify(wrappedBody);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
const requestPayload = { ...parsedBody };
|
|
523
|
+
const rawGenerationConfig = requestPayload.generationConfig;
|
|
524
|
+
const extraBody = requestPayload.extra_body;
|
|
525
|
+
const variantConfig = extractVariantThinkingConfig(requestPayload.providerOptions);
|
|
526
|
+
const isGemini3 = effectiveModel.toLowerCase().includes("gemini-3");
|
|
527
|
+
if (variantConfig?.thinkingLevel && isGemini3) {
|
|
528
|
+
// Gemini 3 native format - use thinkingLevel directly
|
|
529
|
+
tierThinkingLevel = variantConfig.thinkingLevel;
|
|
530
|
+
tierThinkingBudget = undefined;
|
|
531
|
+
}
|
|
532
|
+
else if (variantConfig?.thinkingBudget) {
|
|
533
|
+
if (isGemini3) {
|
|
534
|
+
// Legacy format for Gemini 3 - convert with deprecation warning
|
|
535
|
+
log.warn("[Deprecated] Using thinkingBudget for Gemini 3 model. Use thinkingLevel instead.");
|
|
536
|
+
tierThinkingLevel = variantConfig.thinkingBudget <= 8192 ? "low"
|
|
537
|
+
: variantConfig.thinkingBudget <= 16384 ? "medium" : "high";
|
|
538
|
+
tierThinkingBudget = undefined;
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
// Claude / Gemini 2.5 - use budget directly
|
|
542
|
+
tierThinkingBudget = variantConfig.thinkingBudget;
|
|
543
|
+
tierThinkingLevel = undefined;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (isClaude) {
|
|
547
|
+
if (!requestPayload.toolConfig) {
|
|
548
|
+
requestPayload.toolConfig = {};
|
|
549
|
+
}
|
|
550
|
+
if (typeof requestPayload.toolConfig === "object" && requestPayload.toolConfig !== null) {
|
|
551
|
+
const toolConfig = requestPayload.toolConfig;
|
|
552
|
+
if (!toolConfig.functionCallingConfig) {
|
|
553
|
+
toolConfig.functionCallingConfig = {};
|
|
554
|
+
}
|
|
555
|
+
if (typeof toolConfig.functionCallingConfig === "object" && toolConfig.functionCallingConfig !== null) {
|
|
556
|
+
toolConfig.functionCallingConfig.mode = "VALIDATED";
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// Resolve thinking configuration based on user settings and model capabilities
|
|
561
|
+
// Image generation models don't support thinking - skip thinking config entirely
|
|
562
|
+
const isImageModel = isImageGenerationModel(effectiveModel);
|
|
563
|
+
const userThinkingConfig = isImageModel ? undefined : extractThinkingConfig(requestPayload, rawGenerationConfig, extraBody);
|
|
564
|
+
const hasAssistantHistory = Array.isArray(requestPayload.contents) &&
|
|
565
|
+
requestPayload.contents.some((c) => c?.role === "model" || c?.role === "assistant");
|
|
566
|
+
// For claude-sonnet-4-5 (without -thinking suffix), ignore client's thinkingConfig
|
|
567
|
+
// Only claude-sonnet-4-5-thinking-* variants should have thinking enabled
|
|
568
|
+
const isClaudeSonnetNonThinking = effectiveModel.toLowerCase() === "claude-sonnet-4-5";
|
|
569
|
+
const effectiveUserThinkingConfig = (isClaudeSonnetNonThinking || isImageModel) ? undefined : userThinkingConfig;
|
|
570
|
+
// For image models, add imageConfig instead of thinkingConfig
|
|
571
|
+
if (isImageModel) {
|
|
572
|
+
const imageConfig = buildImageGenerationConfig();
|
|
573
|
+
const generationConfig = (rawGenerationConfig ?? {});
|
|
574
|
+
generationConfig.imageConfig = imageConfig;
|
|
575
|
+
// Remove any thinkingConfig that might have been set
|
|
576
|
+
delete generationConfig.thinkingConfig;
|
|
577
|
+
// Set reasonable defaults for image generation
|
|
578
|
+
if (!generationConfig.candidateCount) {
|
|
579
|
+
generationConfig.candidateCount = 1;
|
|
580
|
+
}
|
|
581
|
+
requestPayload.generationConfig = generationConfig;
|
|
582
|
+
// Add safety settings for image generation (permissive to allow creative content)
|
|
583
|
+
if (!requestPayload.safetySettings) {
|
|
584
|
+
requestPayload.safetySettings = [
|
|
585
|
+
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_ONLY_HIGH" },
|
|
586
|
+
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_ONLY_HIGH" },
|
|
587
|
+
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_ONLY_HIGH" },
|
|
588
|
+
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_ONLY_HIGH" },
|
|
589
|
+
{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_ONLY_HIGH" },
|
|
590
|
+
];
|
|
591
|
+
}
|
|
592
|
+
// Image models don't support tools - remove them entirely
|
|
593
|
+
delete requestPayload.tools;
|
|
594
|
+
delete requestPayload.toolConfig;
|
|
595
|
+
// Replace system instruction with a simple image generation prompt
|
|
596
|
+
// Image models should not receive agentic coding assistant instructions
|
|
597
|
+
requestPayload.systemInstruction = {
|
|
598
|
+
parts: [{ text: "You are an AI image generator. Generate images based on user descriptions. Focus on creating high-quality, visually appealing images that match the user's request." }]
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
const finalThinkingConfig = resolveThinkingConfig(effectiveUserThinkingConfig, isClaudeSonnetNonThinking ? false : (resolved.isThinkingModel ?? isThinkingCapableModel(effectiveModel)), isClaude, hasAssistantHistory);
|
|
603
|
+
const normalizedThinking = normalizeThinkingConfig(finalThinkingConfig);
|
|
604
|
+
if (normalizedThinking) {
|
|
605
|
+
// Use tier-based thinking budget if specified via model suffix, otherwise fall back to user config
|
|
606
|
+
const thinkingBudget = tierThinkingBudget ?? normalizedThinking.thinkingBudget;
|
|
607
|
+
// Build thinking config based on model type
|
|
608
|
+
let thinkingConfig;
|
|
609
|
+
if (isClaudeThinking) {
|
|
610
|
+
// Claude uses snake_case keys
|
|
611
|
+
thinkingConfig = {
|
|
612
|
+
include_thoughts: normalizedThinking.includeThoughts ?? true,
|
|
613
|
+
...(typeof thinkingBudget === "number" && thinkingBudget > 0
|
|
614
|
+
? { thinking_budget: thinkingBudget }
|
|
615
|
+
: {}),
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
else if (tierThinkingLevel) {
|
|
619
|
+
// Gemini 3 uses thinkingLevel string (low/medium/high)
|
|
620
|
+
thinkingConfig = {
|
|
621
|
+
includeThoughts: normalizedThinking.includeThoughts,
|
|
622
|
+
thinkingLevel: tierThinkingLevel,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
// Gemini 2.5 and others use numeric budget
|
|
627
|
+
thinkingConfig = {
|
|
628
|
+
includeThoughts: normalizedThinking.includeThoughts,
|
|
629
|
+
...(typeof thinkingBudget === "number" && thinkingBudget > 0 ? { thinkingBudget } : {}),
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
if (rawGenerationConfig) {
|
|
633
|
+
rawGenerationConfig.thinkingConfig = thinkingConfig;
|
|
634
|
+
if (isClaudeThinking && typeof thinkingBudget === "number" && thinkingBudget > 0) {
|
|
635
|
+
const currentMax = (rawGenerationConfig.maxOutputTokens ?? rawGenerationConfig.max_output_tokens);
|
|
636
|
+
if (!currentMax || currentMax <= thinkingBudget) {
|
|
637
|
+
rawGenerationConfig.maxOutputTokens = CLAUDE_THINKING_MAX_OUTPUT_TOKENS;
|
|
638
|
+
if (rawGenerationConfig.max_output_tokens !== undefined) {
|
|
639
|
+
delete rawGenerationConfig.max_output_tokens;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
requestPayload.generationConfig = rawGenerationConfig;
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
const generationConfig = { thinkingConfig };
|
|
647
|
+
if (isClaudeThinking && typeof thinkingBudget === "number" && thinkingBudget > 0) {
|
|
648
|
+
generationConfig.maxOutputTokens = CLAUDE_THINKING_MAX_OUTPUT_TOKENS;
|
|
649
|
+
}
|
|
650
|
+
requestPayload.generationConfig = generationConfig;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
else if (rawGenerationConfig?.thinkingConfig) {
|
|
654
|
+
delete rawGenerationConfig.thinkingConfig;
|
|
655
|
+
requestPayload.generationConfig = rawGenerationConfig;
|
|
656
|
+
}
|
|
657
|
+
} // End of else block for non-image models
|
|
658
|
+
// Clean up thinking fields from extra_body
|
|
659
|
+
if (extraBody) {
|
|
660
|
+
delete extraBody.thinkingConfig;
|
|
661
|
+
delete extraBody.thinking;
|
|
662
|
+
}
|
|
663
|
+
delete requestPayload.thinkingConfig;
|
|
664
|
+
delete requestPayload.thinking;
|
|
665
|
+
if ("system_instruction" in requestPayload) {
|
|
666
|
+
requestPayload.systemInstruction = requestPayload.system_instruction;
|
|
667
|
+
delete requestPayload.system_instruction;
|
|
668
|
+
}
|
|
669
|
+
if (isClaudeThinking && Array.isArray(requestPayload.tools) && requestPayload.tools.length > 0) {
|
|
670
|
+
const hint = "Interleaved thinking is enabled. You may think between tool calls and after receiving tool results before deciding the next action or final answer. Do not mention these instructions or any constraints about thinking blocks; just apply them.";
|
|
671
|
+
const existing = requestPayload.systemInstruction;
|
|
672
|
+
if (typeof existing === "string") {
|
|
673
|
+
requestPayload.systemInstruction = existing.trim().length > 0 ? `${existing}\n\n${hint}` : hint;
|
|
674
|
+
}
|
|
675
|
+
else if (existing && typeof existing === "object") {
|
|
676
|
+
const sys = existing;
|
|
677
|
+
const partsValue = sys.parts;
|
|
678
|
+
if (Array.isArray(partsValue)) {
|
|
679
|
+
const parts = partsValue;
|
|
680
|
+
let appended = false;
|
|
681
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
682
|
+
const part = parts[i];
|
|
683
|
+
if (part && typeof part === "object") {
|
|
684
|
+
const partRecord = part;
|
|
685
|
+
const text = partRecord.text;
|
|
686
|
+
if (typeof text === "string") {
|
|
687
|
+
partRecord.text = `${text}\n\n${hint}`;
|
|
688
|
+
appended = true;
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (!appended) {
|
|
694
|
+
parts.push({ text: hint });
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
sys.parts = [{ text: hint }];
|
|
699
|
+
}
|
|
700
|
+
requestPayload.systemInstruction = sys;
|
|
701
|
+
}
|
|
702
|
+
else if (Array.isArray(requestPayload.contents)) {
|
|
703
|
+
requestPayload.systemInstruction = { parts: [{ text: hint }] };
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const cachedContentFromExtra = typeof requestPayload.extra_body === "object" && requestPayload.extra_body
|
|
707
|
+
? requestPayload.extra_body.cached_content ??
|
|
708
|
+
requestPayload.extra_body.cachedContent
|
|
709
|
+
: undefined;
|
|
710
|
+
const cachedContent = requestPayload.cached_content ??
|
|
711
|
+
requestPayload.cachedContent ??
|
|
712
|
+
cachedContentFromExtra;
|
|
713
|
+
if (cachedContent) {
|
|
714
|
+
requestPayload.cachedContent = cachedContent;
|
|
715
|
+
}
|
|
716
|
+
delete requestPayload.cached_content;
|
|
717
|
+
delete requestPayload.cachedContent;
|
|
718
|
+
if (requestPayload.extra_body && typeof requestPayload.extra_body === "object") {
|
|
719
|
+
delete requestPayload.extra_body.cached_content;
|
|
720
|
+
delete requestPayload.extra_body.cachedContent;
|
|
721
|
+
if (Object.keys(requestPayload.extra_body).length === 0) {
|
|
722
|
+
delete requestPayload.extra_body;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
// Normalize tools. For Claude models, keep full function declarations (names + schemas).
|
|
726
|
+
if (Array.isArray(requestPayload.tools)) {
|
|
727
|
+
if (isClaude) {
|
|
728
|
+
const functionDeclarations = [];
|
|
729
|
+
const passthroughTools = [];
|
|
730
|
+
const normalizeSchema = (schema) => {
|
|
731
|
+
const createPlaceholderSchema = (base = {}) => ({
|
|
732
|
+
...base,
|
|
733
|
+
type: "object",
|
|
734
|
+
properties: {
|
|
735
|
+
[EMPTY_SCHEMA_PLACEHOLDER_NAME]: {
|
|
736
|
+
type: "boolean",
|
|
737
|
+
description: EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION,
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
required: [EMPTY_SCHEMA_PLACEHOLDER_NAME],
|
|
741
|
+
});
|
|
742
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
743
|
+
toolDebugMissing += 1;
|
|
744
|
+
return createPlaceholderSchema();
|
|
745
|
+
}
|
|
746
|
+
const cleaned = cleanJSONSchemaForAntigravity(schema);
|
|
747
|
+
if (!cleaned || typeof cleaned !== "object" || Array.isArray(cleaned)) {
|
|
748
|
+
toolDebugMissing += 1;
|
|
749
|
+
return createPlaceholderSchema();
|
|
750
|
+
}
|
|
751
|
+
// Claude VALIDATED mode requires tool parameters to be an object schema
|
|
752
|
+
// with at least one property.
|
|
753
|
+
const hasProperties = cleaned.properties &&
|
|
754
|
+
typeof cleaned.properties === "object" &&
|
|
755
|
+
Object.keys(cleaned.properties).length > 0;
|
|
756
|
+
cleaned.type = "object";
|
|
757
|
+
if (!hasProperties) {
|
|
758
|
+
cleaned.properties = {
|
|
759
|
+
[EMPTY_SCHEMA_PLACEHOLDER_NAME]: {
|
|
760
|
+
type: "boolean",
|
|
761
|
+
description: EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION,
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
cleaned.required = Array.isArray(cleaned.required)
|
|
765
|
+
? Array.from(new Set([...cleaned.required, EMPTY_SCHEMA_PLACEHOLDER_NAME]))
|
|
766
|
+
: [EMPTY_SCHEMA_PLACEHOLDER_NAME];
|
|
767
|
+
}
|
|
768
|
+
return cleaned;
|
|
769
|
+
};
|
|
770
|
+
requestPayload.tools.forEach((tool) => {
|
|
771
|
+
const pushDeclaration = (decl, source) => {
|
|
772
|
+
const schema = decl?.parameters ||
|
|
773
|
+
decl?.parametersJsonSchema ||
|
|
774
|
+
decl?.input_schema ||
|
|
775
|
+
decl?.inputSchema ||
|
|
776
|
+
tool.parameters ||
|
|
777
|
+
tool.parametersJsonSchema ||
|
|
778
|
+
tool.input_schema ||
|
|
779
|
+
tool.inputSchema ||
|
|
780
|
+
tool.function?.parameters ||
|
|
781
|
+
tool.function?.parametersJsonSchema ||
|
|
782
|
+
tool.function?.input_schema ||
|
|
783
|
+
tool.function?.inputSchema ||
|
|
784
|
+
tool.custom?.parameters ||
|
|
785
|
+
tool.custom?.parametersJsonSchema ||
|
|
786
|
+
tool.custom?.input_schema;
|
|
787
|
+
let name = decl?.name ||
|
|
788
|
+
tool.name ||
|
|
789
|
+
tool.function?.name ||
|
|
790
|
+
tool.custom?.name ||
|
|
791
|
+
`tool-${functionDeclarations.length}`;
|
|
792
|
+
// Sanitize tool name: must be alphanumeric with underscores, no special chars
|
|
793
|
+
name = String(name).replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
794
|
+
const description = decl?.description ||
|
|
795
|
+
tool.description ||
|
|
796
|
+
tool.function?.description ||
|
|
797
|
+
tool.custom?.description ||
|
|
798
|
+
"";
|
|
799
|
+
functionDeclarations.push({
|
|
800
|
+
name,
|
|
801
|
+
description: String(description || ""),
|
|
802
|
+
parameters: normalizeSchema(schema),
|
|
803
|
+
});
|
|
804
|
+
toolDebugSummaries.push(`decl=${name},src=${source},hasSchema=${schema ? "y" : "n"}`);
|
|
805
|
+
};
|
|
806
|
+
if (Array.isArray(tool.functionDeclarations) && tool.functionDeclarations.length > 0) {
|
|
807
|
+
tool.functionDeclarations.forEach((decl) => pushDeclaration(decl, "functionDeclarations"));
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
// Fall back to function/custom style definitions.
|
|
811
|
+
if (tool.function ||
|
|
812
|
+
tool.custom ||
|
|
813
|
+
tool.parameters ||
|
|
814
|
+
tool.input_schema ||
|
|
815
|
+
tool.inputSchema) {
|
|
816
|
+
pushDeclaration(tool.function ?? tool.custom ?? tool, "function/custom");
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
// Preserve any non-function tool entries (e.g., codeExecution) untouched.
|
|
820
|
+
passthroughTools.push(tool);
|
|
821
|
+
});
|
|
822
|
+
const finalTools = [];
|
|
823
|
+
if (functionDeclarations.length > 0) {
|
|
824
|
+
finalTools.push({ functionDeclarations });
|
|
825
|
+
}
|
|
826
|
+
requestPayload.tools = finalTools.concat(passthroughTools);
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
// Gemini-specific tool normalization and feature injection
|
|
830
|
+
// Resolve Google Search config: Variant takes precedence over global default
|
|
831
|
+
const effectiveSearchConfig = variantConfig?.googleSearch ?? options?.googleSearch;
|
|
832
|
+
const geminiResult = applyGeminiTransforms(requestPayload, {
|
|
833
|
+
model: effectiveModel,
|
|
834
|
+
normalizedThinking: undefined, // Thinking config already applied above (lines 816-880)
|
|
835
|
+
tierThinkingBudget,
|
|
836
|
+
tierThinkingLevel: tierThinkingLevel,
|
|
837
|
+
googleSearch: effectiveSearchConfig,
|
|
838
|
+
});
|
|
839
|
+
toolDebugMissing = geminiResult.toolDebugMissing;
|
|
840
|
+
toolDebugSummaries.push(...geminiResult.toolDebugSummaries);
|
|
841
|
+
}
|
|
842
|
+
try {
|
|
843
|
+
toolDebugPayload = JSON.stringify(requestPayload.tools);
|
|
844
|
+
}
|
|
845
|
+
catch {
|
|
846
|
+
toolDebugPayload = undefined;
|
|
847
|
+
}
|
|
848
|
+
// Apply Claude tool hardening (ported from LLM-API-Key-Proxy)
|
|
849
|
+
// Injects parameter signatures into descriptions and adds system instruction
|
|
850
|
+
// Can be disabled via config.claude_tool_hardening = false to reduce context size
|
|
851
|
+
const enableToolHardening = options?.claudeToolHardening ?? true;
|
|
852
|
+
if (enableToolHardening && isClaude && Array.isArray(requestPayload.tools) && requestPayload.tools.length > 0) {
|
|
853
|
+
// Inject parameter signatures into tool descriptions
|
|
854
|
+
requestPayload.tools = injectParameterSignatures(requestPayload.tools, CLAUDE_DESCRIPTION_PROMPT);
|
|
855
|
+
// Inject tool hardening system instruction
|
|
856
|
+
injectToolHardeningInstruction(requestPayload, CLAUDE_TOOL_SYSTEM_INSTRUCTION);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
const conversationKey = resolveConversationKey(requestPayload);
|
|
860
|
+
signatureSessionKey = buildSignatureSessionKey(PLUGIN_SESSION_ID, effectiveModel, conversationKey, resolveProjectKey(projectId));
|
|
861
|
+
// For Claude models, filter out unsigned thinking blocks (required by Claude API)
|
|
862
|
+
// Attempts to restore signatures from cache for multi-turn conversations
|
|
863
|
+
// Handle both Gemini-style contents[] and Anthropic-style messages[] payloads.
|
|
864
|
+
if (isClaude) {
|
|
865
|
+
// Step 0: Sanitize cross-model metadata (strips Gemini signatures when sending to Claude)
|
|
866
|
+
sanitizeCrossModelPayloadInPlace(requestPayload, { targetModel: effectiveModel });
|
|
867
|
+
// Step 1: Strip corrupted/unsigned thinking blocks FIRST
|
|
868
|
+
deepFilterThinkingBlocks(requestPayload, signatureSessionKey, getCachedSignature, true);
|
|
869
|
+
// Step 2: THEN inject signed thinking from cache (after stripping)
|
|
870
|
+
if (isClaudeThinking && Array.isArray(requestPayload.contents)) {
|
|
871
|
+
requestPayload.contents = ensureThinkingBeforeToolUseInContents(requestPayload.contents, signatureSessionKey);
|
|
872
|
+
}
|
|
873
|
+
if (isClaudeThinking && Array.isArray(requestPayload.messages)) {
|
|
874
|
+
requestPayload.messages = ensureThinkingBeforeToolUseInMessages(requestPayload.messages, signatureSessionKey);
|
|
875
|
+
}
|
|
876
|
+
// Step 3: Check if warmup needed (AFTER injection attempt)
|
|
877
|
+
if (isClaudeThinking) {
|
|
878
|
+
const hasToolUse = (Array.isArray(requestPayload.contents) && hasToolUseInContents(requestPayload.contents)) ||
|
|
879
|
+
(Array.isArray(requestPayload.messages) && hasToolUseInMessages(requestPayload.messages));
|
|
880
|
+
const hasSignedThinking = (Array.isArray(requestPayload.contents) && hasSignedThinkingInContents(requestPayload.contents)) ||
|
|
881
|
+
(Array.isArray(requestPayload.messages) && hasSignedThinkingInMessages(requestPayload.messages));
|
|
882
|
+
const hasCachedThinking = defaultSignatureStore.has(signatureSessionKey);
|
|
883
|
+
needsSignedThinkingWarmup = hasToolUse && !hasSignedThinking && !hasCachedThinking;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
// For Claude models, ensure functionCall/tool use parts carry IDs (required by Anthropic).
|
|
887
|
+
// We use a two-pass approach: first collect all functionCalls and assign IDs,
|
|
888
|
+
// then match functionResponses to their corresponding calls using a FIFO queue per function name.
|
|
889
|
+
if (isClaude && Array.isArray(requestPayload.contents)) {
|
|
890
|
+
let toolCallCounter = 0;
|
|
891
|
+
// Track pending call IDs per function name as a FIFO queue
|
|
892
|
+
const pendingCallIdsByName = new Map();
|
|
893
|
+
// First pass: assign IDs to all functionCalls and collect them
|
|
894
|
+
requestPayload.contents = requestPayload.contents.map((content) => {
|
|
895
|
+
if (!content || !Array.isArray(content.parts)) {
|
|
896
|
+
return content;
|
|
897
|
+
}
|
|
898
|
+
const newParts = content.parts.map((part) => {
|
|
899
|
+
if (part && typeof part === "object" && part.functionCall) {
|
|
900
|
+
const call = { ...part.functionCall };
|
|
901
|
+
if (!call.id) {
|
|
902
|
+
call.id = `tool-call-${++toolCallCounter}`;
|
|
903
|
+
}
|
|
904
|
+
const nameKey = typeof call.name === "string" ? call.name : `tool-${toolCallCounter}`;
|
|
905
|
+
// Push to the queue for this function name
|
|
906
|
+
const queue = pendingCallIdsByName.get(nameKey) || [];
|
|
907
|
+
queue.push(call.id);
|
|
908
|
+
pendingCallIdsByName.set(nameKey, queue);
|
|
909
|
+
return { ...part, functionCall: call };
|
|
910
|
+
}
|
|
911
|
+
return part;
|
|
912
|
+
});
|
|
913
|
+
return { ...content, parts: newParts };
|
|
914
|
+
});
|
|
915
|
+
// Second pass: match functionResponses to their corresponding calls (FIFO order)
|
|
916
|
+
requestPayload.contents = requestPayload.contents.map((content) => {
|
|
917
|
+
if (!content || !Array.isArray(content.parts)) {
|
|
918
|
+
return content;
|
|
919
|
+
}
|
|
920
|
+
const newParts = content.parts.map((part) => {
|
|
921
|
+
if (part && typeof part === "object" && part.functionResponse) {
|
|
922
|
+
const resp = { ...part.functionResponse };
|
|
923
|
+
if (!resp.id && typeof resp.name === "string") {
|
|
924
|
+
const queue = pendingCallIdsByName.get(resp.name);
|
|
925
|
+
if (queue && queue.length > 0) {
|
|
926
|
+
// Consume the first pending ID (FIFO order)
|
|
927
|
+
resp.id = queue.shift();
|
|
928
|
+
pendingCallIdsByName.set(resp.name, queue);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return { ...part, functionResponse: resp };
|
|
932
|
+
}
|
|
933
|
+
return part;
|
|
934
|
+
});
|
|
935
|
+
return { ...content, parts: newParts };
|
|
936
|
+
});
|
|
937
|
+
// Third pass: Apply orphan recovery for mismatched tool IDs
|
|
938
|
+
// This handles cases where context compaction or other processes
|
|
939
|
+
// create ID mismatches between calls and responses.
|
|
940
|
+
// Ported from LLM-API-Key-Proxy's _fix_tool_response_grouping()
|
|
941
|
+
requestPayload.contents = fixToolResponseGrouping(requestPayload.contents);
|
|
942
|
+
}
|
|
943
|
+
// Fourth pass: Fix Claude format tool pairing (defense in depth)
|
|
944
|
+
// Handles orphaned tool_use blocks in Claude's messages[] format
|
|
945
|
+
if (Array.isArray(requestPayload.messages)) {
|
|
946
|
+
requestPayload.messages = validateAndFixClaudeToolPairing(requestPayload.messages);
|
|
947
|
+
}
|
|
948
|
+
// =====================================================================
|
|
949
|
+
// LAST RESORT RECOVERY: "Let it crash and start again"
|
|
950
|
+
// =====================================================================
|
|
951
|
+
// If after all our processing we're STILL in a bad state (tool loop without
|
|
952
|
+
// thinking at turn start), don't try to fix it - just close the turn and
|
|
953
|
+
// start fresh. This prevents permanent session breakage.
|
|
954
|
+
//
|
|
955
|
+
// This handles cases where:
|
|
956
|
+
// - Context compaction stripped thinking blocks
|
|
957
|
+
// - Signature cache miss
|
|
958
|
+
// - Any other corruption we couldn't repair
|
|
959
|
+
// - API error indicated thinking_block_order issue (forceThinkingRecovery=true)
|
|
960
|
+
//
|
|
961
|
+
// The synthetic messages allow Claude to generate fresh thinking on the
|
|
962
|
+
// new turn instead of failing with "Expected thinking but found text".
|
|
963
|
+
if (isClaudeThinking && Array.isArray(requestPayload.contents)) {
|
|
964
|
+
const conversationState = analyzeConversationState(requestPayload.contents);
|
|
965
|
+
// Force recovery if API returned thinking_block_order error (retry case)
|
|
966
|
+
// or if proactive check detects we need recovery
|
|
967
|
+
if (forceThinkingRecovery || needsThinkingRecovery(conversationState)) {
|
|
968
|
+
// Set message for toast notification (shown in plugin.ts, respects quiet mode)
|
|
969
|
+
thinkingRecoveryMessage = forceThinkingRecovery
|
|
970
|
+
? "Thinking recovery: retrying with fresh turn (API error)"
|
|
971
|
+
: "Thinking recovery: restarting turn (corrupted context)";
|
|
972
|
+
requestPayload.contents = closeToolLoopForThinking(requestPayload.contents);
|
|
973
|
+
defaultSignatureStore.delete(signatureSessionKey);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if ("model" in requestPayload) {
|
|
977
|
+
delete requestPayload.model;
|
|
978
|
+
}
|
|
979
|
+
stripInjectedDebugFromRequestPayload(requestPayload);
|
|
980
|
+
const effectiveProjectId = projectId?.trim() || generateSyntheticProjectId();
|
|
981
|
+
resolvedProjectId = effectiveProjectId;
|
|
982
|
+
// Inject Antigravity system instruction with role "user" (CLIProxyAPI v6.6.89 compatibility)
|
|
983
|
+
// This sets request.systemInstruction.role = "user" and request.systemInstruction.parts[0].text
|
|
984
|
+
if (headerStyle === "antigravity") {
|
|
985
|
+
const existingSystemInstruction = requestPayload.systemInstruction;
|
|
986
|
+
if (existingSystemInstruction && typeof existingSystemInstruction === "object") {
|
|
987
|
+
const sys = existingSystemInstruction;
|
|
988
|
+
sys.role = "user";
|
|
989
|
+
if (Array.isArray(sys.parts) && sys.parts.length > 0) {
|
|
990
|
+
const firstPart = sys.parts[0];
|
|
991
|
+
if (firstPart && typeof firstPart.text === "string") {
|
|
992
|
+
firstPart.text = ANTIGRAVITY_SYSTEM_INSTRUCTION + "\n\n" + firstPart.text;
|
|
993
|
+
}
|
|
994
|
+
else {
|
|
995
|
+
sys.parts = [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }, ...sys.parts];
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
else {
|
|
999
|
+
sys.parts = [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }];
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
else if (typeof existingSystemInstruction === "string") {
|
|
1003
|
+
requestPayload.systemInstruction = {
|
|
1004
|
+
role: "user",
|
|
1005
|
+
parts: [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION + "\n\n" + existingSystemInstruction }],
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
requestPayload.systemInstruction = {
|
|
1010
|
+
role: "user",
|
|
1011
|
+
parts: [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }],
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
const wrappedBody = {
|
|
1016
|
+
project: effectiveProjectId,
|
|
1017
|
+
model: effectiveModel,
|
|
1018
|
+
request: requestPayload,
|
|
1019
|
+
requestType: "agent",
|
|
1020
|
+
};
|
|
1021
|
+
Object.assign(wrappedBody, {
|
|
1022
|
+
userAgent: "antigravity",
|
|
1023
|
+
requestId: "agent-" + crypto.randomUUID(),
|
|
1024
|
+
});
|
|
1025
|
+
if (wrappedBody.request && typeof wrappedBody.request === 'object') {
|
|
1026
|
+
// Use stable session ID for signature caching across multi-turn conversations
|
|
1027
|
+
sessionId = signatureSessionKey;
|
|
1028
|
+
wrappedBody.request.sessionId = signatureSessionKey;
|
|
1029
|
+
}
|
|
1030
|
+
body = JSON.stringify(wrappedBody);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
throw error;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (streaming) {
|
|
1038
|
+
headers.set("Accept", "text/event-stream");
|
|
1039
|
+
}
|
|
1040
|
+
// Add interleaved thinking header for Claude thinking models
|
|
1041
|
+
// This enables real-time streaming of thinking tokens
|
|
1042
|
+
if (isClaudeThinking) {
|
|
1043
|
+
const existing = headers.get("anthropic-beta");
|
|
1044
|
+
const interleavedHeader = "interleaved-thinking-2025-05-14";
|
|
1045
|
+
if (existing) {
|
|
1046
|
+
if (!existing.includes(interleavedHeader)) {
|
|
1047
|
+
headers.set("anthropic-beta", `${existing},${interleavedHeader}`);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
else {
|
|
1051
|
+
headers.set("anthropic-beta", interleavedHeader);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const selectedHeaders = headerStyle === "gemini-cli" ? GEMINI_CLI_HEADERS : ANTIGRAVITY_HEADERS;
|
|
1055
|
+
headers.set("User-Agent", selectedHeaders["User-Agent"]);
|
|
1056
|
+
headers.set("X-Goog-Api-Client", selectedHeaders["X-Goog-Api-Client"]);
|
|
1057
|
+
headers.set("Client-Metadata", selectedHeaders["Client-Metadata"]);
|
|
1058
|
+
// Optional debug header to observe tool normalization on the backend if surfaced
|
|
1059
|
+
if (toolDebugMissing > 0) {
|
|
1060
|
+
headers.set("X-Opencode-Tools-Debug", String(toolDebugMissing));
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
request: transformedUrl,
|
|
1064
|
+
init: {
|
|
1065
|
+
...baseInit,
|
|
1066
|
+
headers,
|
|
1067
|
+
body,
|
|
1068
|
+
},
|
|
1069
|
+
streaming,
|
|
1070
|
+
requestedModel,
|
|
1071
|
+
effectiveModel: effectiveModel,
|
|
1072
|
+
projectId: resolvedProjectId,
|
|
1073
|
+
endpoint: transformedUrl,
|
|
1074
|
+
sessionId,
|
|
1075
|
+
toolDebugMissing,
|
|
1076
|
+
toolDebugSummary: toolDebugSummaries.slice(0, 20).join(" | "),
|
|
1077
|
+
toolDebugPayload,
|
|
1078
|
+
needsSignedThinkingWarmup,
|
|
1079
|
+
headerStyle,
|
|
1080
|
+
thinkingRecoveryMessage,
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
export function buildThinkingWarmupBody(bodyText, isClaudeThinking) {
|
|
1084
|
+
if (!bodyText || !isClaudeThinking) {
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
let parsed;
|
|
1088
|
+
try {
|
|
1089
|
+
parsed = JSON.parse(bodyText);
|
|
1090
|
+
}
|
|
1091
|
+
catch {
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
const warmupPrompt = "Warmup request for thinking signature.";
|
|
1095
|
+
const updateRequest = (req) => {
|
|
1096
|
+
req.contents = [{ role: "user", parts: [{ text: warmupPrompt }] }];
|
|
1097
|
+
delete req.tools;
|
|
1098
|
+
delete req.toolConfig;
|
|
1099
|
+
const generationConfig = (req.generationConfig ?? {});
|
|
1100
|
+
generationConfig.thinkingConfig = {
|
|
1101
|
+
include_thoughts: true,
|
|
1102
|
+
thinking_budget: DEFAULT_THINKING_BUDGET,
|
|
1103
|
+
};
|
|
1104
|
+
generationConfig.maxOutputTokens = CLAUDE_THINKING_MAX_OUTPUT_TOKENS;
|
|
1105
|
+
req.generationConfig = generationConfig;
|
|
1106
|
+
};
|
|
1107
|
+
if (parsed.request && typeof parsed.request === "object") {
|
|
1108
|
+
updateRequest(parsed.request);
|
|
1109
|
+
const nested = parsed.request.request;
|
|
1110
|
+
if (nested && typeof nested === "object") {
|
|
1111
|
+
updateRequest(nested);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
updateRequest(parsed);
|
|
1116
|
+
}
|
|
1117
|
+
return JSON.stringify(parsed);
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Normalizes Antigravity responses: applies retry headers, extracts cache usage into headers,
|
|
1121
|
+
* rewrites preview errors, flattens streaming payloads, and logs debug metadata.
|
|
1122
|
+
*
|
|
1123
|
+
* For streaming SSE responses, uses TransformStream for true real-time incremental streaming.
|
|
1124
|
+
* Thinking/reasoning tokens are transformed and forwarded immediately as they arrive.
|
|
1125
|
+
*/
|
|
1126
|
+
export async function transformAntigravityResponse(response, streaming, debugContext, requestedModel, projectId, endpoint, effectiveModel, sessionId, toolDebugMissing, toolDebugSummary, toolDebugPayload, debugLines, onUsage) {
|
|
1127
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1128
|
+
const isJsonResponse = contentType.includes("application/json");
|
|
1129
|
+
const isEventStreamResponse = contentType.includes("text/event-stream");
|
|
1130
|
+
const debugText = isDebugEnabled() && Array.isArray(debugLines) && debugLines.length > 0
|
|
1131
|
+
? formatDebugLinesForThinking(debugLines)
|
|
1132
|
+
: undefined;
|
|
1133
|
+
const cacheSignatures = shouldCacheThinkingSignatures(effectiveModel);
|
|
1134
|
+
if (!isJsonResponse && !isEventStreamResponse) {
|
|
1135
|
+
logAntigravityDebugResponse(debugContext, response, {
|
|
1136
|
+
note: "Non-JSON response (body omitted)",
|
|
1137
|
+
});
|
|
1138
|
+
return response;
|
|
1139
|
+
}
|
|
1140
|
+
// For successful streaming responses, use TransformStream to transform SSE events
|
|
1141
|
+
// while maintaining real-time streaming (no buffering of entire response).
|
|
1142
|
+
// This enables thinking tokens to be displayed as they arrive, like the Codex plugin.
|
|
1143
|
+
if (streaming && response.ok && isEventStreamResponse && response.body) {
|
|
1144
|
+
const headers = new Headers(response.headers);
|
|
1145
|
+
logAntigravityDebugResponse(debugContext, response, {
|
|
1146
|
+
note: "Streaming SSE response (real-time transform)",
|
|
1147
|
+
});
|
|
1148
|
+
const streamingTransformer = createStreamingTransformer(defaultSignatureStore, {
|
|
1149
|
+
onCacheSignature: cacheSignature,
|
|
1150
|
+
onInjectDebug: injectDebugThinking,
|
|
1151
|
+
transformThinkingParts,
|
|
1152
|
+
onUsage,
|
|
1153
|
+
}, {
|
|
1154
|
+
signatureSessionKey: sessionId,
|
|
1155
|
+
debugText,
|
|
1156
|
+
cacheSignatures,
|
|
1157
|
+
displayedThinkingHashes: effectiveModel && isGemini3Model(effectiveModel) ? sessionDisplayedThinkingHashes : undefined,
|
|
1158
|
+
});
|
|
1159
|
+
return new Response(response.body.pipeThrough(streamingTransformer), {
|
|
1160
|
+
status: response.status,
|
|
1161
|
+
statusText: response.statusText,
|
|
1162
|
+
headers,
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
try {
|
|
1166
|
+
const headers = new Headers(response.headers);
|
|
1167
|
+
const text = await response.text();
|
|
1168
|
+
if (!response.ok) {
|
|
1169
|
+
let errorBody;
|
|
1170
|
+
try {
|
|
1171
|
+
errorBody = JSON.parse(text);
|
|
1172
|
+
}
|
|
1173
|
+
catch {
|
|
1174
|
+
errorBody = { error: { message: text } };
|
|
1175
|
+
}
|
|
1176
|
+
// Inject Debug Info
|
|
1177
|
+
if (errorBody?.error) {
|
|
1178
|
+
const debugInfo = `\n\n[Debug Info]\nRequested Model: ${requestedModel || "Unknown"}\nEffective Model: ${effectiveModel || "Unknown"}\nProject: ${projectId || "Unknown"}\nEndpoint: ${endpoint || "Unknown"}\nStatus: ${response.status}\nRequest ID: ${headers.get("x-request-id") || "N/A"}${toolDebugMissing !== undefined ? `\nTool Debug Missing: ${toolDebugMissing}` : ""}${toolDebugSummary ? `\nTool Debug Summary: ${toolDebugSummary}` : ""}${toolDebugPayload ? `\nTool Debug Payload: ${toolDebugPayload}` : ""}`;
|
|
1179
|
+
const injectedDebug = debugText ? `\n\n${debugText}` : "";
|
|
1180
|
+
errorBody.error.message = (errorBody.error.message || "Unknown error") + debugInfo + injectedDebug;
|
|
1181
|
+
// Check if this is a recoverable thinking error - throw to trigger retry
|
|
1182
|
+
const errorType = detectErrorType(errorBody.error.message || "");
|
|
1183
|
+
if (errorType === "thinking_block_order") {
|
|
1184
|
+
const recoveryError = new Error("THINKING_RECOVERY_NEEDED");
|
|
1185
|
+
recoveryError.recoveryType = errorType;
|
|
1186
|
+
recoveryError.originalError = errorBody;
|
|
1187
|
+
recoveryError.debugInfo = debugInfo;
|
|
1188
|
+
throw recoveryError;
|
|
1189
|
+
}
|
|
1190
|
+
// Detect context length / prompt too long errors - signal to caller for toast
|
|
1191
|
+
const errorMessage = errorBody.error.message?.toLowerCase() || "";
|
|
1192
|
+
if (errorMessage.includes("prompt is too long") ||
|
|
1193
|
+
errorMessage.includes("context length exceeded") ||
|
|
1194
|
+
errorMessage.includes("context_length_exceeded") ||
|
|
1195
|
+
errorMessage.includes("maximum context length")) {
|
|
1196
|
+
headers.set("x-antigravity-context-error", "prompt_too_long");
|
|
1197
|
+
}
|
|
1198
|
+
// Detect tool pairing errors - signal to caller for toast
|
|
1199
|
+
if (errorMessage.includes("tool_use") &&
|
|
1200
|
+
errorMessage.includes("tool_result") &&
|
|
1201
|
+
(errorMessage.includes("without") || errorMessage.includes("immediately after"))) {
|
|
1202
|
+
headers.set("x-antigravity-context-error", "tool_pairing");
|
|
1203
|
+
}
|
|
1204
|
+
return new Response(JSON.stringify(errorBody), {
|
|
1205
|
+
status: response.status,
|
|
1206
|
+
statusText: response.statusText,
|
|
1207
|
+
headers
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
if (errorBody?.error?.details && Array.isArray(errorBody.error.details)) {
|
|
1211
|
+
const retryInfo = errorBody.error.details.find((detail) => detail['@type'] === 'type.googleapis.com/google.rpc.RetryInfo');
|
|
1212
|
+
if (retryInfo?.retryDelay) {
|
|
1213
|
+
const match = retryInfo.retryDelay.match(/^([\d.]+)s$/);
|
|
1214
|
+
if (match && match[1]) {
|
|
1215
|
+
const retrySeconds = parseFloat(match[1]);
|
|
1216
|
+
if (!isNaN(retrySeconds) && retrySeconds > 0) {
|
|
1217
|
+
const retryAfterSec = Math.ceil(retrySeconds).toString();
|
|
1218
|
+
const retryAfterMs = Math.ceil(retrySeconds * 1000).toString();
|
|
1219
|
+
headers.set('Retry-After', retryAfterSec);
|
|
1220
|
+
headers.set('retry-after-ms', retryAfterMs);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const init = {
|
|
1227
|
+
status: response.status,
|
|
1228
|
+
statusText: response.statusText,
|
|
1229
|
+
headers,
|
|
1230
|
+
};
|
|
1231
|
+
const usageFromSse = streaming && isEventStreamResponse ? extractUsageFromSsePayload(text) : null;
|
|
1232
|
+
const parsed = !streaming || !isEventStreamResponse ? parseAntigravityApiBody(text) : null;
|
|
1233
|
+
const patched = parsed ? rewriteAntigravityPreviewAccessError(parsed, response.status, requestedModel) : null;
|
|
1234
|
+
const effectiveBody = patched ?? parsed ?? undefined;
|
|
1235
|
+
const usage = usageFromSse ?? (effectiveBody ? extractUsageMetadata(effectiveBody) : null);
|
|
1236
|
+
if (usage) {
|
|
1237
|
+
if (usage.cachedContentTokenCount !== undefined) {
|
|
1238
|
+
headers.set("x-antigravity-cached-content-token-count", String(usage.cachedContentTokenCount));
|
|
1239
|
+
}
|
|
1240
|
+
if (usage.totalTokenCount !== undefined) {
|
|
1241
|
+
headers.set("x-antigravity-total-token-count", String(usage.totalTokenCount));
|
|
1242
|
+
}
|
|
1243
|
+
if (usage.promptTokenCount !== undefined) {
|
|
1244
|
+
headers.set("x-antigravity-prompt-token-count", String(usage.promptTokenCount));
|
|
1245
|
+
}
|
|
1246
|
+
if (usage.candidatesTokenCount !== undefined) {
|
|
1247
|
+
headers.set("x-antigravity-candidates-token-count", String(usage.candidatesTokenCount));
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
logAntigravityDebugResponse(debugContext, response, {
|
|
1251
|
+
body: text,
|
|
1252
|
+
note: streaming ? "Streaming SSE payload (buffered fallback)" : undefined,
|
|
1253
|
+
headersOverride: headers,
|
|
1254
|
+
});
|
|
1255
|
+
// Note: successful streaming responses are handled above via TransformStream.
|
|
1256
|
+
// This path only handles non-streaming responses or failed streaming responses.
|
|
1257
|
+
if (!parsed) {
|
|
1258
|
+
return new Response(text, init);
|
|
1259
|
+
}
|
|
1260
|
+
if (effectiveBody?.response !== undefined) {
|
|
1261
|
+
const responseBody = debugText ? injectDebugThinking(effectiveBody.response, debugText) : effectiveBody.response;
|
|
1262
|
+
const transformed = transformThinkingParts(responseBody);
|
|
1263
|
+
return new Response(JSON.stringify(transformed), init);
|
|
1264
|
+
}
|
|
1265
|
+
if (patched) {
|
|
1266
|
+
return new Response(JSON.stringify(patched), init);
|
|
1267
|
+
}
|
|
1268
|
+
return new Response(text, init);
|
|
1269
|
+
}
|
|
1270
|
+
catch (error) {
|
|
1271
|
+
logAntigravityDebugResponse(debugContext, response, {
|
|
1272
|
+
error,
|
|
1273
|
+
note: "Failed to transform Antigravity response",
|
|
1274
|
+
});
|
|
1275
|
+
return response;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
export const __testExports = {
|
|
1279
|
+
buildSignatureSessionKey,
|
|
1280
|
+
hashConversationSeed,
|
|
1281
|
+
extractTextFromContent,
|
|
1282
|
+
extractConversationSeedFromMessages,
|
|
1283
|
+
extractConversationSeedFromContents,
|
|
1284
|
+
resolveConversationKey,
|
|
1285
|
+
resolveProjectKey,
|
|
1286
|
+
isGeminiToolUsePart,
|
|
1287
|
+
isGeminiThinkingPart,
|
|
1288
|
+
ensureThoughtSignature,
|
|
1289
|
+
hasSignedThinkingPart,
|
|
1290
|
+
hasSignedThinkingInContents,
|
|
1291
|
+
hasSignedThinkingInMessages,
|
|
1292
|
+
hasToolUseInContents,
|
|
1293
|
+
hasToolUseInMessages,
|
|
1294
|
+
ensureThinkingBeforeToolUseInContents,
|
|
1295
|
+
ensureThinkingBeforeToolUseInMessages,
|
|
1296
|
+
generateSyntheticProjectId,
|
|
1297
|
+
MIN_SIGNATURE_LENGTH,
|
|
1298
|
+
transformSseLine,
|
|
1299
|
+
transformStreamingPayload,
|
|
1300
|
+
createStreamingTransformer,
|
|
1301
|
+
};
|
|
1302
|
+
//# sourceMappingURL=request.js.map
|