opencode-antigravity-auth 1.1.4 → 1.2.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/README.md +223 -99
- 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 +121 -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 +21 -10
- package/dist/src/plugin/accounts.d.ts.map +1 -1
- package/dist/src/plugin/accounts.js +101 -55
- package/dist/src/plugin/accounts.js.map +1 -1
- package/dist/src/plugin/cache.d.ts +14 -0
- package/dist/src/plugin/cache.d.ts.map +1 -1
- package/dist/src/plugin/cache.js +82 -0
- package/dist/src/plugin/cache.js.map +1 -1
- package/dist/src/plugin/debug.d.ts +32 -0
- package/dist/src/plugin/debug.d.ts.map +1 -1
- package/dist/src/plugin/debug.js +140 -12
- package/dist/src/plugin/debug.js.map +1 -1
- package/dist/src/plugin/request-helpers.d.ts +13 -4
- package/dist/src/plugin/request-helpers.d.ts.map +1 -1
- package/dist/src/plugin/request-helpers.js +171 -18
- package/dist/src/plugin/request-helpers.js.map +1 -1
- package/dist/src/plugin/request.d.ts +10 -2
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +614 -67
- package/dist/src/plugin/request.js.map +1 -1
- package/dist/src/plugin/storage.d.ts +23 -7
- package/dist/src/plugin/storage.d.ts.map +1 -1
- package/dist/src/plugin/storage.js +54 -10
- package/dist/src/plugin/storage.js.map +1 -1
- package/dist/src/plugin/types.d.ts +13 -1
- package/dist/src/plugin/types.d.ts.map +1 -1
- package/dist/src/plugin.d.ts +3 -3
- package/dist/src/plugin.d.ts.map +1 -1
- package/dist/src/plugin.js +780 -474
- package/dist/src/plugin.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/plugin/accounts.test.d.ts +0 -2
- package/dist/src/plugin/accounts.test.d.ts.map +0 -1
- package/dist/src/plugin/accounts.test.js +0 -139
- package/dist/src/plugin/accounts.test.js.map +0 -1
- package/dist/src/plugin/token.test.d.ts +0 -2
- package/dist/src/plugin/token.test.d.ts.map +0 -1
- package/dist/src/plugin/token.test.js +0 -64
- package/dist/src/plugin/token.test.js.map +0 -1
|
@@ -1,7 +1,273 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { ANTIGRAVITY_HEADERS, ANTIGRAVITY_ENDPOINT, } from "../constants";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { cacheSignature, getCachedSignature } from "./cache";
|
|
4
|
+
import { DEBUG_MESSAGE_PREFIX, isDebugEnabled, logAntigravityDebugResponse, } from "./debug";
|
|
5
|
+
import { DEFAULT_THINKING_BUDGET, extractThinkingConfig, extractUsageFromSsePayload, extractUsageMetadata, filterUnsignedThinkingBlocks, filterMessagesThinkingBlocks, isThinkingCapableModel, normalizeThinkingConfig, parseAntigravityApiBody, resolveThinkingConfig, rewriteAntigravityPreviewAccessError, transformThinkingParts, } from "./request-helpers";
|
|
6
|
+
/**
|
|
7
|
+
* Stable session ID for the plugin's lifetime.
|
|
8
|
+
* This is used for caching thinking signatures across multi-turn conversations.
|
|
9
|
+
* Generated once at plugin load time and reused for all requests.
|
|
10
|
+
*/
|
|
11
|
+
const PLUGIN_SESSION_ID = `-${crypto.randomUUID()}`;
|
|
12
|
+
// Claude thinking models need a sufficiently large max output token limit when thinking is enabled.
|
|
13
|
+
const CLAUDE_THINKING_MAX_OUTPUT_TOKENS = 64_000;
|
|
14
|
+
const MIN_SIGNATURE_LENGTH = 50;
|
|
15
|
+
const lastSignedThinkingBySessionId = new Map();
|
|
16
|
+
function formatDebugLinesForThinking(lines) {
|
|
17
|
+
const cleaned = lines
|
|
18
|
+
.map((line) => line.trim())
|
|
19
|
+
.filter((line) => line.length > 0)
|
|
20
|
+
.slice(-50);
|
|
21
|
+
return `${DEBUG_MESSAGE_PREFIX}\n${cleaned.map((line) => `- ${line}`).join("\n")}`;
|
|
22
|
+
}
|
|
23
|
+
function injectDebugThinking(response, debugText) {
|
|
24
|
+
if (!response || typeof response !== "object") {
|
|
25
|
+
return response;
|
|
26
|
+
}
|
|
27
|
+
const resp = response;
|
|
28
|
+
if (Array.isArray(resp.candidates) && resp.candidates.length > 0) {
|
|
29
|
+
const candidates = resp.candidates.slice();
|
|
30
|
+
const first = candidates[0];
|
|
31
|
+
if (first &&
|
|
32
|
+
typeof first === "object" &&
|
|
33
|
+
first.content &&
|
|
34
|
+
typeof first.content === "object" &&
|
|
35
|
+
Array.isArray(first.content.parts)) {
|
|
36
|
+
const parts = [{ thought: true, text: debugText }, ...first.content.parts];
|
|
37
|
+
candidates[0] = { ...first, content: { ...first.content, parts } };
|
|
38
|
+
return { ...resp, candidates };
|
|
39
|
+
}
|
|
40
|
+
return resp;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(resp.content)) {
|
|
43
|
+
const content = [{ type: "thinking", thinking: debugText }, ...resp.content];
|
|
44
|
+
return { ...resp, content };
|
|
45
|
+
}
|
|
46
|
+
if (!resp.reasoning_content) {
|
|
47
|
+
return { ...resp, reasoning_content: debugText };
|
|
48
|
+
}
|
|
49
|
+
return resp;
|
|
50
|
+
}
|
|
51
|
+
function stripInjectedDebugFromParts(parts) {
|
|
52
|
+
if (!Array.isArray(parts)) {
|
|
53
|
+
return parts;
|
|
54
|
+
}
|
|
55
|
+
return parts.filter((part) => {
|
|
56
|
+
if (!part || typeof part !== "object") {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
const record = part;
|
|
60
|
+
const text = typeof record.text === "string"
|
|
61
|
+
? record.text
|
|
62
|
+
: typeof record.thinking === "string"
|
|
63
|
+
? record.thinking
|
|
64
|
+
: undefined;
|
|
65
|
+
if (text && text.startsWith(DEBUG_MESSAGE_PREFIX)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function stripInjectedDebugFromRequestPayload(payload) {
|
|
72
|
+
const anyPayload = payload;
|
|
73
|
+
if (Array.isArray(anyPayload.contents)) {
|
|
74
|
+
anyPayload.contents = anyPayload.contents.map((content) => {
|
|
75
|
+
if (!content || typeof content !== "object") {
|
|
76
|
+
return content;
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(content.parts)) {
|
|
79
|
+
return { ...content, parts: stripInjectedDebugFromParts(content.parts) };
|
|
80
|
+
}
|
|
81
|
+
if (Array.isArray(content.content)) {
|
|
82
|
+
return { ...content, content: stripInjectedDebugFromParts(content.content) };
|
|
83
|
+
}
|
|
84
|
+
return content;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(anyPayload.messages)) {
|
|
88
|
+
anyPayload.messages = anyPayload.messages.map((message) => {
|
|
89
|
+
if (!message || typeof message !== "object") {
|
|
90
|
+
return message;
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(message.content)) {
|
|
93
|
+
return { ...message, content: stripInjectedDebugFromParts(message.content) };
|
|
94
|
+
}
|
|
95
|
+
return message;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function isGeminiToolUsePart(part) {
|
|
100
|
+
return !!(part && typeof part === "object" && (part.functionCall || part.tool_use || part.toolUse));
|
|
101
|
+
}
|
|
102
|
+
function isGeminiThinkingPart(part) {
|
|
103
|
+
return !!(part &&
|
|
104
|
+
typeof part === "object" &&
|
|
105
|
+
(part.thought === true || part.type === "thinking" || part.type === "reasoning"));
|
|
106
|
+
}
|
|
107
|
+
function ensureThoughtSignature(part, sessionId) {
|
|
108
|
+
if (!part || typeof part !== "object") {
|
|
109
|
+
return part;
|
|
110
|
+
}
|
|
111
|
+
const text = typeof part.text === "string" ? part.text : typeof part.thinking === "string" ? part.thinking : "";
|
|
112
|
+
if (!text) {
|
|
113
|
+
return part;
|
|
114
|
+
}
|
|
115
|
+
if (part.thought === true) {
|
|
116
|
+
if (!part.thoughtSignature) {
|
|
117
|
+
const cached = getCachedSignature(sessionId, text);
|
|
118
|
+
if (cached) {
|
|
119
|
+
return { ...part, thoughtSignature: cached };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return part;
|
|
123
|
+
}
|
|
124
|
+
if ((part.type === "thinking" || part.type === "reasoning") && !part.signature) {
|
|
125
|
+
const cached = getCachedSignature(sessionId, text);
|
|
126
|
+
if (cached) {
|
|
127
|
+
return { ...part, signature: cached };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return part;
|
|
131
|
+
}
|
|
132
|
+
function hasSignedThinkingPart(part) {
|
|
133
|
+
if (!part || typeof part !== "object") {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (part.thought === true) {
|
|
137
|
+
return typeof part.thoughtSignature === "string" && part.thoughtSignature.length >= MIN_SIGNATURE_LENGTH;
|
|
138
|
+
}
|
|
139
|
+
if (part.type === "thinking" || part.type === "reasoning") {
|
|
140
|
+
return typeof part.signature === "string" && part.signature.length >= MIN_SIGNATURE_LENGTH;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
function ensureThinkingBeforeToolUseInContents(contents, sessionId) {
|
|
145
|
+
return contents.map((content) => {
|
|
146
|
+
if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
|
|
147
|
+
return content;
|
|
148
|
+
}
|
|
149
|
+
const role = content.role;
|
|
150
|
+
if (role !== "model" && role !== "assistant") {
|
|
151
|
+
return content;
|
|
152
|
+
}
|
|
153
|
+
const parts = content.parts;
|
|
154
|
+
const hasToolUse = parts.some(isGeminiToolUsePart);
|
|
155
|
+
if (!hasToolUse) {
|
|
156
|
+
return content;
|
|
157
|
+
}
|
|
158
|
+
const thinkingParts = parts.filter(isGeminiThinkingPart).map((p) => ensureThoughtSignature(p, sessionId));
|
|
159
|
+
const otherParts = parts.filter((p) => !isGeminiThinkingPart(p));
|
|
160
|
+
const hasSignedThinking = thinkingParts.some(hasSignedThinkingPart);
|
|
161
|
+
if (hasSignedThinking) {
|
|
162
|
+
return { ...content, parts: [...thinkingParts, ...otherParts] };
|
|
163
|
+
}
|
|
164
|
+
const lastThinking = lastSignedThinkingBySessionId.get(sessionId);
|
|
165
|
+
if (!lastThinking) {
|
|
166
|
+
return content;
|
|
167
|
+
}
|
|
168
|
+
const injected = {
|
|
169
|
+
thought: true,
|
|
170
|
+
text: lastThinking.text,
|
|
171
|
+
thoughtSignature: lastThinking.signature,
|
|
172
|
+
};
|
|
173
|
+
return { ...content, parts: [injected, ...otherParts] };
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function ensureMessageThinkingSignature(block, sessionId) {
|
|
177
|
+
if (!block || typeof block !== "object") {
|
|
178
|
+
return block;
|
|
179
|
+
}
|
|
180
|
+
if (block.type !== "thinking" && block.type !== "redacted_thinking") {
|
|
181
|
+
return block;
|
|
182
|
+
}
|
|
183
|
+
if (typeof block.signature === "string" && block.signature.length >= MIN_SIGNATURE_LENGTH) {
|
|
184
|
+
return block;
|
|
185
|
+
}
|
|
186
|
+
const text = typeof block.thinking === "string" ? block.thinking : typeof block.text === "string" ? block.text : "";
|
|
187
|
+
if (!text) {
|
|
188
|
+
return block;
|
|
189
|
+
}
|
|
190
|
+
const cached = getCachedSignature(sessionId, text);
|
|
191
|
+
if (cached) {
|
|
192
|
+
return { ...block, signature: cached };
|
|
193
|
+
}
|
|
194
|
+
return block;
|
|
195
|
+
}
|
|
196
|
+
function hasToolUseInContents(contents) {
|
|
197
|
+
return contents.some((content) => {
|
|
198
|
+
if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
return content.parts.some(isGeminiToolUsePart);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function hasSignedThinkingInContents(contents) {
|
|
205
|
+
return contents.some((content) => {
|
|
206
|
+
if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
return content.parts.some(hasSignedThinkingPart);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function hasToolUseInMessages(messages) {
|
|
213
|
+
return messages.some((message) => {
|
|
214
|
+
if (!message || typeof message !== "object" || !Array.isArray(message.content)) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return message.content.some((block) => block && typeof block === "object" && (block.type === "tool_use" || block.type === "tool_result"));
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function hasSignedThinkingInMessages(messages) {
|
|
221
|
+
return messages.some((message) => {
|
|
222
|
+
if (!message || typeof message !== "object" || !Array.isArray(message.content)) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return message.content.some((block) => block &&
|
|
226
|
+
typeof block === "object" &&
|
|
227
|
+
(block.type === "thinking" || block.type === "redacted_thinking") &&
|
|
228
|
+
typeof block.signature === "string" &&
|
|
229
|
+
block.signature.length >= MIN_SIGNATURE_LENGTH);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function ensureThinkingBeforeToolUseInMessages(messages, sessionId) {
|
|
233
|
+
return messages.map((message) => {
|
|
234
|
+
if (!message || typeof message !== "object" || !Array.isArray(message.content)) {
|
|
235
|
+
return message;
|
|
236
|
+
}
|
|
237
|
+
if (message.role !== "assistant") {
|
|
238
|
+
return message;
|
|
239
|
+
}
|
|
240
|
+
const blocks = message.content;
|
|
241
|
+
const hasToolUse = blocks.some((b) => b && typeof b === "object" && (b.type === "tool_use" || b.type === "tool_result"));
|
|
242
|
+
if (!hasToolUse) {
|
|
243
|
+
return message;
|
|
244
|
+
}
|
|
245
|
+
const thinkingBlocks = blocks
|
|
246
|
+
.filter((b) => b && typeof b === "object" && (b.type === "thinking" || b.type === "redacted_thinking"))
|
|
247
|
+
.map((b) => ensureMessageThinkingSignature(b, sessionId));
|
|
248
|
+
const otherBlocks = blocks.filter((b) => !(b && typeof b === "object" && (b.type === "thinking" || b.type === "redacted_thinking")));
|
|
249
|
+
const hasSignedThinking = thinkingBlocks.some((b) => typeof b.signature === "string" && b.signature.length >= MIN_SIGNATURE_LENGTH);
|
|
250
|
+
if (hasSignedThinking) {
|
|
251
|
+
return { ...message, content: [...thinkingBlocks, ...otherBlocks] };
|
|
252
|
+
}
|
|
253
|
+
const lastThinking = lastSignedThinkingBySessionId.get(sessionId);
|
|
254
|
+
if (!lastThinking) {
|
|
255
|
+
return message;
|
|
256
|
+
}
|
|
257
|
+
const injected = {
|
|
258
|
+
type: "thinking",
|
|
259
|
+
thinking: lastThinking.text,
|
|
260
|
+
signature: lastThinking.signature,
|
|
261
|
+
};
|
|
262
|
+
return { ...message, content: [injected, ...otherBlocks] };
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Gets the stable session ID for this plugin instance.
|
|
267
|
+
*/
|
|
268
|
+
export function getPluginSessionId() {
|
|
269
|
+
return PLUGIN_SESSION_ID;
|
|
270
|
+
}
|
|
5
271
|
function generateSyntheticProjectId() {
|
|
6
272
|
const adjectives = ["useful", "bright", "swift", "calm", "bold"];
|
|
7
273
|
const nouns = ["fuze", "wave", "spark", "flow", "core"];
|
|
@@ -46,28 +312,36 @@ function transformStreamingPayload(payload) {
|
|
|
46
312
|
}
|
|
47
313
|
/**
|
|
48
314
|
* Creates a TransformStream that processes SSE chunks incrementally,
|
|
49
|
-
* transforming each line as it arrives for true streaming support.
|
|
315
|
+
* transforming each line as it arrives for true real-time streaming support.
|
|
316
|
+
* Optionally caches thinking signatures for Claude multi-turn conversations.
|
|
50
317
|
*/
|
|
51
|
-
function createStreamingTransformer() {
|
|
318
|
+
function createStreamingTransformer(sessionId, debugText) {
|
|
52
319
|
const decoder = new TextDecoder();
|
|
53
320
|
const encoder = new TextEncoder();
|
|
54
321
|
let buffer = "";
|
|
322
|
+
// Buffer for accumulating thinking text per candidate index (for signature caching)
|
|
323
|
+
const thoughtBuffer = new Map();
|
|
324
|
+
const debugState = { injected: false };
|
|
55
325
|
return new TransformStream({
|
|
56
326
|
transform(chunk, controller) {
|
|
327
|
+
// Decode chunk with stream: true to handle multi-byte characters correctly
|
|
57
328
|
buffer += decoder.decode(chunk, { stream: true });
|
|
58
|
-
// Process complete lines
|
|
329
|
+
// Process complete lines immediately for real-time streaming
|
|
59
330
|
const lines = buffer.split("\n");
|
|
60
331
|
// Keep the last incomplete line in buffer
|
|
61
332
|
buffer = lines.pop() || "";
|
|
62
333
|
for (const line of lines) {
|
|
63
|
-
|
|
334
|
+
// Transform and forward each line immediately
|
|
335
|
+
const transformedLine = transformSseLine(line, sessionId, thoughtBuffer, debugText, debugState);
|
|
64
336
|
controller.enqueue(encoder.encode(transformedLine + "\n"));
|
|
65
337
|
}
|
|
66
338
|
},
|
|
67
339
|
flush(controller) {
|
|
340
|
+
// Flush any remaining bytes from TextDecoder
|
|
341
|
+
buffer += decoder.decode();
|
|
68
342
|
// Process any remaining data in buffer
|
|
69
343
|
if (buffer) {
|
|
70
|
-
const transformedLine = transformSseLine(buffer);
|
|
344
|
+
const transformedLine = transformSseLine(buffer, sessionId, thoughtBuffer, debugText, debugState);
|
|
71
345
|
controller.enqueue(encoder.encode(transformedLine));
|
|
72
346
|
}
|
|
73
347
|
},
|
|
@@ -75,8 +349,9 @@ function createStreamingTransformer() {
|
|
|
75
349
|
}
|
|
76
350
|
/**
|
|
77
351
|
* Transforms a single SSE line, extracting and transforming the inner response.
|
|
352
|
+
* Optionally caches thinking signatures for Claude multi-turn support.
|
|
78
353
|
*/
|
|
79
|
-
function transformSseLine(line) {
|
|
354
|
+
function transformSseLine(line, sessionId, thoughtBuffer, debugText, debugState) {
|
|
80
355
|
if (!line.startsWith("data:")) {
|
|
81
356
|
return line;
|
|
82
357
|
}
|
|
@@ -87,13 +362,67 @@ function transformSseLine(line) {
|
|
|
87
362
|
try {
|
|
88
363
|
const parsed = JSON.parse(json);
|
|
89
364
|
if (parsed.response !== undefined) {
|
|
90
|
-
|
|
365
|
+
if (sessionId && thoughtBuffer) {
|
|
366
|
+
cacheThinkingSignatures(parsed.response, sessionId, thoughtBuffer);
|
|
367
|
+
}
|
|
368
|
+
let response = parsed.response;
|
|
369
|
+
if (debugText && debugState && !debugState.injected) {
|
|
370
|
+
response = injectDebugThinking(response, debugText);
|
|
371
|
+
debugState.injected = true;
|
|
372
|
+
}
|
|
373
|
+
const transformed = transformThinkingParts(response);
|
|
91
374
|
return `data: ${JSON.stringify(transformed)}`;
|
|
92
375
|
}
|
|
93
376
|
}
|
|
94
377
|
catch (_) { }
|
|
95
378
|
return line;
|
|
96
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Extracts and caches thinking signatures from a response for Claude multi-turn support.
|
|
382
|
+
*/
|
|
383
|
+
function cacheThinkingSignatures(response, sessionId, thoughtBuffer) {
|
|
384
|
+
if (!response || typeof response !== "object")
|
|
385
|
+
return;
|
|
386
|
+
const resp = response;
|
|
387
|
+
// Handle Gemini-style candidates array (Claude through Antigravity uses this format)
|
|
388
|
+
if (Array.isArray(resp.candidates)) {
|
|
389
|
+
resp.candidates.forEach((candidate, index) => {
|
|
390
|
+
if (!candidate?.content?.parts)
|
|
391
|
+
return;
|
|
392
|
+
candidate.content.parts.forEach((part) => {
|
|
393
|
+
// Collect thinking text
|
|
394
|
+
if (part.thought === true || part.type === "thinking") {
|
|
395
|
+
const text = part.text || part.thinking || "";
|
|
396
|
+
if (text) {
|
|
397
|
+
const current = thoughtBuffer.get(index) ?? "";
|
|
398
|
+
thoughtBuffer.set(index, current + text);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// Cache signature when we receive it
|
|
402
|
+
if (part.thoughtSignature) {
|
|
403
|
+
const fullText = thoughtBuffer.get(index) ?? "";
|
|
404
|
+
if (fullText && sessionId) {
|
|
405
|
+
cacheSignature(sessionId, fullText, part.thoughtSignature);
|
|
406
|
+
lastSignedThinkingBySessionId.set(sessionId, { text: fullText, signature: part.thoughtSignature });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
// Handle Anthropic-style content array
|
|
413
|
+
if (Array.isArray(resp.content)) {
|
|
414
|
+
let thinkingText = "";
|
|
415
|
+
resp.content.forEach((block) => {
|
|
416
|
+
if (block?.type === "thinking") {
|
|
417
|
+
thinkingText += block.thinking || block.text || "";
|
|
418
|
+
}
|
|
419
|
+
if (block?.signature && thinkingText && sessionId) {
|
|
420
|
+
cacheSignature(sessionId, thinkingText, block.signature);
|
|
421
|
+
lastSignedThinkingBySessionId.set(sessionId, { text: thinkingText, signature: block.signature });
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
97
426
|
/**
|
|
98
427
|
* Rewrites OpenAI-style requests into Antigravity shape, normalizing model, headers,
|
|
99
428
|
* optional cached_content, and thinking config. Also toggles streaming mode for SSE actions.
|
|
@@ -105,6 +434,8 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
105
434
|
let toolDebugMissing = 0;
|
|
106
435
|
const toolDebugSummaries = [];
|
|
107
436
|
let toolDebugPayload;
|
|
437
|
+
let sessionId;
|
|
438
|
+
let needsSignedThinkingWarmup = false;
|
|
108
439
|
if (!isGenerativeLanguageRequest(input)) {
|
|
109
440
|
return {
|
|
110
441
|
request: input,
|
|
@@ -123,12 +454,17 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
123
454
|
};
|
|
124
455
|
}
|
|
125
456
|
const [, rawModel = "", rawAction = ""] = match;
|
|
126
|
-
const
|
|
127
|
-
|
|
457
|
+
const requestedModel = rawModel;
|
|
458
|
+
let upstreamModel = rawModel;
|
|
459
|
+
if (upstreamModel === "gemini-2.5-flash-image") {
|
|
460
|
+
upstreamModel = "gemini-2.5-flash";
|
|
461
|
+
}
|
|
462
|
+
const effectiveModel = upstreamModel;
|
|
128
463
|
const streaming = rawAction === STREAM_ACTION;
|
|
129
464
|
const baseEndpoint = endpointOverride ?? ANTIGRAVITY_ENDPOINT;
|
|
130
465
|
const transformedUrl = `${baseEndpoint}/v1internal:${rawAction}${streaming ? "?alt=sse" : ""}`;
|
|
131
466
|
const isClaudeModel = upstreamModel.toLowerCase().includes("claude");
|
|
467
|
+
const isClaudeThinkingModel = isClaudeModel && upstreamModel.toLowerCase().includes("thinking");
|
|
132
468
|
let body = baseInit.body;
|
|
133
469
|
if (typeof baseInit.body === "string" && baseInit.body) {
|
|
134
470
|
try {
|
|
@@ -139,12 +475,68 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
139
475
|
...parsedBody,
|
|
140
476
|
model: effectiveModel,
|
|
141
477
|
};
|
|
478
|
+
// Some callers may already send an Antigravity-wrapped body.
|
|
479
|
+
// We still need to sanitize Claude thinking blocks (remove cache_control)
|
|
480
|
+
// and attach a stable sessionId so multi-turn signature caching works.
|
|
481
|
+
const requestRoot = wrappedBody.request;
|
|
482
|
+
const requestObjects = [];
|
|
483
|
+
if (requestRoot && typeof requestRoot === "object") {
|
|
484
|
+
requestObjects.push(requestRoot);
|
|
485
|
+
const nested = requestRoot.request;
|
|
486
|
+
if (nested && typeof nested === "object") {
|
|
487
|
+
requestObjects.push(nested);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (requestObjects.length > 0) {
|
|
491
|
+
sessionId = PLUGIN_SESSION_ID;
|
|
492
|
+
}
|
|
493
|
+
for (const req of requestObjects) {
|
|
494
|
+
// Use stable session ID for signature caching across multi-turn conversations
|
|
495
|
+
req.sessionId = PLUGIN_SESSION_ID;
|
|
496
|
+
stripInjectedDebugFromRequestPayload(req);
|
|
497
|
+
if (isClaudeModel) {
|
|
498
|
+
if (isClaudeThinkingModel && Array.isArray(req.contents)) {
|
|
499
|
+
req.contents = ensureThinkingBeforeToolUseInContents(req.contents, PLUGIN_SESSION_ID);
|
|
500
|
+
}
|
|
501
|
+
if (isClaudeThinkingModel && Array.isArray(req.messages)) {
|
|
502
|
+
req.messages = ensureThinkingBeforeToolUseInMessages(req.messages, PLUGIN_SESSION_ID);
|
|
503
|
+
}
|
|
504
|
+
if (Array.isArray(req.contents)) {
|
|
505
|
+
req.contents = filterUnsignedThinkingBlocks(req.contents, PLUGIN_SESSION_ID, getCachedSignature);
|
|
506
|
+
}
|
|
507
|
+
if (Array.isArray(req.messages)) {
|
|
508
|
+
req.messages = filterMessagesThinkingBlocks(req.messages, PLUGIN_SESSION_ID, getCachedSignature);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (isClaudeThinkingModel && sessionId) {
|
|
513
|
+
const hasToolUse = requestObjects.some((req) => (Array.isArray(req.contents) && hasToolUseInContents(req.contents)) ||
|
|
514
|
+
(Array.isArray(req.messages) && hasToolUseInMessages(req.messages)));
|
|
515
|
+
const hasSignedThinking = requestObjects.some((req) => (Array.isArray(req.contents) && hasSignedThinkingInContents(req.contents)) ||
|
|
516
|
+
(Array.isArray(req.messages) && hasSignedThinkingInMessages(req.messages)));
|
|
517
|
+
const hasCachedThinking = lastSignedThinkingBySessionId.has(sessionId);
|
|
518
|
+
needsSignedThinkingWarmup = hasToolUse && !hasSignedThinking && !hasCachedThinking;
|
|
519
|
+
}
|
|
142
520
|
body = JSON.stringify(wrappedBody);
|
|
143
521
|
}
|
|
144
522
|
else {
|
|
145
523
|
const requestPayload = { ...parsedBody };
|
|
146
524
|
const rawGenerationConfig = requestPayload.generationConfig;
|
|
147
525
|
const extraBody = requestPayload.extra_body;
|
|
526
|
+
if (isClaudeModel) {
|
|
527
|
+
if (!requestPayload.toolConfig) {
|
|
528
|
+
requestPayload.toolConfig = {};
|
|
529
|
+
}
|
|
530
|
+
if (typeof requestPayload.toolConfig === "object" && requestPayload.toolConfig !== null) {
|
|
531
|
+
const toolConfig = requestPayload.toolConfig;
|
|
532
|
+
if (!toolConfig.functionCallingConfig) {
|
|
533
|
+
toolConfig.functionCallingConfig = {};
|
|
534
|
+
}
|
|
535
|
+
if (typeof toolConfig.functionCallingConfig === "object" && toolConfig.functionCallingConfig !== null) {
|
|
536
|
+
toolConfig.functionCallingConfig.mode = "VALIDATED";
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
148
540
|
// Resolve thinking configuration based on user settings and model capabilities
|
|
149
541
|
const userThinkingConfig = extractThinkingConfig(requestPayload, rawGenerationConfig, extraBody);
|
|
150
542
|
const hasAssistantHistory = Array.isArray(requestPayload.contents) &&
|
|
@@ -152,12 +544,37 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
152
544
|
const finalThinkingConfig = resolveThinkingConfig(userThinkingConfig, isThinkingCapableModel(upstreamModel), isClaudeModel, hasAssistantHistory);
|
|
153
545
|
const normalizedThinking = normalizeThinkingConfig(finalThinkingConfig);
|
|
154
546
|
if (normalizedThinking) {
|
|
547
|
+
const thinkingBudget = normalizedThinking.thinkingBudget;
|
|
548
|
+
const thinkingConfig = isClaudeThinkingModel
|
|
549
|
+
? {
|
|
550
|
+
include_thoughts: normalizedThinking.includeThoughts ?? true,
|
|
551
|
+
...(typeof thinkingBudget === "number" && thinkingBudget > 0
|
|
552
|
+
? { thinking_budget: thinkingBudget }
|
|
553
|
+
: {}),
|
|
554
|
+
}
|
|
555
|
+
: {
|
|
556
|
+
includeThoughts: normalizedThinking.includeThoughts,
|
|
557
|
+
...(typeof thinkingBudget === "number" && thinkingBudget > 0 ? { thinkingBudget } : {}),
|
|
558
|
+
};
|
|
155
559
|
if (rawGenerationConfig) {
|
|
156
|
-
rawGenerationConfig.thinkingConfig =
|
|
560
|
+
rawGenerationConfig.thinkingConfig = thinkingConfig;
|
|
561
|
+
if (isClaudeThinkingModel && typeof thinkingBudget === "number" && thinkingBudget > 0) {
|
|
562
|
+
const currentMax = (rawGenerationConfig.maxOutputTokens ?? rawGenerationConfig.max_output_tokens);
|
|
563
|
+
if (!currentMax || currentMax <= thinkingBudget) {
|
|
564
|
+
rawGenerationConfig.maxOutputTokens = CLAUDE_THINKING_MAX_OUTPUT_TOKENS;
|
|
565
|
+
if (rawGenerationConfig.max_output_tokens !== undefined) {
|
|
566
|
+
delete rawGenerationConfig.max_output_tokens;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
157
570
|
requestPayload.generationConfig = rawGenerationConfig;
|
|
158
571
|
}
|
|
159
572
|
else {
|
|
160
|
-
|
|
573
|
+
const generationConfig = { thinkingConfig };
|
|
574
|
+
if (isClaudeThinkingModel && typeof thinkingBudget === "number" && thinkingBudget > 0) {
|
|
575
|
+
generationConfig.maxOutputTokens = CLAUDE_THINKING_MAX_OUTPUT_TOKENS;
|
|
576
|
+
}
|
|
577
|
+
requestPayload.generationConfig = generationConfig;
|
|
161
578
|
}
|
|
162
579
|
}
|
|
163
580
|
else if (rawGenerationConfig?.thinkingConfig) {
|
|
@@ -175,6 +592,43 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
175
592
|
requestPayload.systemInstruction = requestPayload.system_instruction;
|
|
176
593
|
delete requestPayload.system_instruction;
|
|
177
594
|
}
|
|
595
|
+
if (isClaudeThinkingModel && Array.isArray(requestPayload.tools) && requestPayload.tools.length > 0) {
|
|
596
|
+
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.";
|
|
597
|
+
const existing = requestPayload.systemInstruction;
|
|
598
|
+
if (typeof existing === "string") {
|
|
599
|
+
requestPayload.systemInstruction = existing.trim().length > 0 ? `${existing}\n\n${hint}` : hint;
|
|
600
|
+
}
|
|
601
|
+
else if (existing && typeof existing === "object") {
|
|
602
|
+
const sys = existing;
|
|
603
|
+
const partsValue = sys.parts;
|
|
604
|
+
if (Array.isArray(partsValue)) {
|
|
605
|
+
const parts = partsValue;
|
|
606
|
+
let appended = false;
|
|
607
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
608
|
+
const part = parts[i];
|
|
609
|
+
if (part && typeof part === "object") {
|
|
610
|
+
const partRecord = part;
|
|
611
|
+
const text = partRecord.text;
|
|
612
|
+
if (typeof text === "string") {
|
|
613
|
+
partRecord.text = `${text}\n\n${hint}`;
|
|
614
|
+
appended = true;
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (!appended) {
|
|
620
|
+
parts.push({ text: hint });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
sys.parts = [{ text: hint }];
|
|
625
|
+
}
|
|
626
|
+
requestPayload.systemInstruction = sys;
|
|
627
|
+
}
|
|
628
|
+
else if (Array.isArray(requestPayload.contents)) {
|
|
629
|
+
requestPayload.systemInstruction = { parts: [{ text: hint }] };
|
|
630
|
+
}
|
|
631
|
+
}
|
|
178
632
|
const cachedContentFromExtra = typeof requestPayload.extra_body === "object" && requestPayload.extra_body
|
|
179
633
|
? requestPayload.extra_body.cached_content ??
|
|
180
634
|
requestPayload.extra_body.cachedContent
|
|
@@ -199,26 +653,44 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
199
653
|
if (isClaudeModel) {
|
|
200
654
|
const functionDeclarations = [];
|
|
201
655
|
const passthroughTools = [];
|
|
202
|
-
// Sanitize schema -
|
|
203
|
-
//
|
|
656
|
+
// Sanitize schema using ALLOWLIST approach - only keep basic features needed for function calling
|
|
657
|
+
// This is more aggressive than blocklisting, ensuring any unknown/unsupported features are stripped
|
|
658
|
+
// See docs/ANTIGRAVITY_API_SPEC.md for full list of unsupported features
|
|
204
659
|
const sanitizeSchema = (schema) => {
|
|
205
660
|
if (!schema || typeof schema !== "object") {
|
|
206
661
|
return schema;
|
|
207
662
|
}
|
|
663
|
+
// Only keep these basic schema features (allowlist approach)
|
|
664
|
+
// Everything else gets stripped automatically
|
|
665
|
+
const ALLOWED_KEYS = new Set([
|
|
666
|
+
"type",
|
|
667
|
+
"properties",
|
|
668
|
+
"required",
|
|
669
|
+
"description",
|
|
670
|
+
"enum",
|
|
671
|
+
"items",
|
|
672
|
+
"additionalProperties",
|
|
673
|
+
]);
|
|
208
674
|
const sanitized = {};
|
|
209
675
|
for (const key of Object.keys(schema)) {
|
|
210
|
-
//
|
|
211
|
-
if (key === "
|
|
676
|
+
// Convert "const" to "enum: [value]" (const is not supported but enum is)
|
|
677
|
+
if (key === "const") {
|
|
678
|
+
sanitized.enum = [schema[key]];
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
// Skip keys not in allowlist
|
|
682
|
+
if (!ALLOWED_KEYS.has(key)) {
|
|
212
683
|
continue;
|
|
213
684
|
}
|
|
214
685
|
const value = schema[key];
|
|
215
686
|
if (key === "items" && value && typeof value === "object") {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
687
|
+
const sanitizedItems = sanitizeSchema(value);
|
|
688
|
+
// Empty items schema {} is invalid - convert to permissive string type
|
|
689
|
+
if (Object.keys(sanitizedItems).length === 0) {
|
|
690
|
+
sanitized.items = { type: "string" };
|
|
219
691
|
}
|
|
220
692
|
else {
|
|
221
|
-
sanitized.items =
|
|
693
|
+
sanitized.items = sanitizedItems;
|
|
222
694
|
}
|
|
223
695
|
}
|
|
224
696
|
else if (key === "properties" && value && typeof value === "object") {
|
|
@@ -238,13 +710,32 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
238
710
|
return sanitized;
|
|
239
711
|
};
|
|
240
712
|
const normalizeSchema = (schema) => {
|
|
713
|
+
// Helper to create a placeholder schema for empty parameter tools
|
|
714
|
+
// Antigravity API in VALIDATED mode cannot handle truly empty schemas
|
|
715
|
+
// The placeholder must be REQUIRED so the model sends a non-empty args object
|
|
716
|
+
const createPlaceholderSchema = (base = {}) => ({
|
|
717
|
+
...base,
|
|
718
|
+
type: "object",
|
|
719
|
+
properties: {
|
|
720
|
+
reason: {
|
|
721
|
+
type: "string",
|
|
722
|
+
description: "Brief explanation of why you are calling this tool",
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
required: ["reason"],
|
|
726
|
+
});
|
|
241
727
|
if (!schema || typeof schema !== "object") {
|
|
242
728
|
toolDebugMissing += 1;
|
|
243
|
-
//
|
|
244
|
-
return
|
|
729
|
+
// Fallback for tools without schemas - add dummy property for Antigravity API
|
|
730
|
+
return createPlaceholderSchema();
|
|
731
|
+
}
|
|
732
|
+
const sanitized = sanitizeSchema(schema);
|
|
733
|
+
// Check if schema is effectively empty (type: object with no properties)
|
|
734
|
+
if (sanitized.type === "object" &&
|
|
735
|
+
(!sanitized.properties || Object.keys(sanitized.properties).length === 0)) {
|
|
736
|
+
return createPlaceholderSchema(sanitized);
|
|
245
737
|
}
|
|
246
|
-
|
|
247
|
-
return sanitizeSchema(schema);
|
|
738
|
+
return sanitized;
|
|
248
739
|
};
|
|
249
740
|
requestPayload.tools.forEach((tool, idx) => {
|
|
250
741
|
const pushDeclaration = (decl, source) => {
|
|
@@ -359,8 +850,30 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
359
850
|
}
|
|
360
851
|
}
|
|
361
852
|
// For Claude models, filter out unsigned thinking blocks (required by Claude API)
|
|
362
|
-
|
|
363
|
-
|
|
853
|
+
// Attempts to restore signatures from cache for multi-turn conversations
|
|
854
|
+
// Handle both Gemini-style contents[] and Anthropic-style messages[] payloads.
|
|
855
|
+
if (isClaudeModel) {
|
|
856
|
+
if (isClaudeThinkingModel && Array.isArray(requestPayload.contents)) {
|
|
857
|
+
requestPayload.contents = ensureThinkingBeforeToolUseInContents(requestPayload.contents, PLUGIN_SESSION_ID);
|
|
858
|
+
}
|
|
859
|
+
if (isClaudeThinkingModel && Array.isArray(requestPayload.messages)) {
|
|
860
|
+
requestPayload.messages = ensureThinkingBeforeToolUseInMessages(requestPayload.messages, PLUGIN_SESSION_ID);
|
|
861
|
+
}
|
|
862
|
+
if (isClaudeThinkingModel) {
|
|
863
|
+
const sessionKey = PLUGIN_SESSION_ID;
|
|
864
|
+
const hasToolUse = (Array.isArray(requestPayload.contents) && hasToolUseInContents(requestPayload.contents)) ||
|
|
865
|
+
(Array.isArray(requestPayload.messages) && hasToolUseInMessages(requestPayload.messages));
|
|
866
|
+
const hasSignedThinking = (Array.isArray(requestPayload.contents) && hasSignedThinkingInContents(requestPayload.contents)) ||
|
|
867
|
+
(Array.isArray(requestPayload.messages) && hasSignedThinkingInMessages(requestPayload.messages));
|
|
868
|
+
const hasCachedThinking = lastSignedThinkingBySessionId.has(sessionKey);
|
|
869
|
+
needsSignedThinkingWarmup = hasToolUse && !hasSignedThinking && !hasCachedThinking;
|
|
870
|
+
}
|
|
871
|
+
if (Array.isArray(requestPayload.contents)) {
|
|
872
|
+
requestPayload.contents = filterUnsignedThinkingBlocks(requestPayload.contents, PLUGIN_SESSION_ID, getCachedSignature);
|
|
873
|
+
}
|
|
874
|
+
if (Array.isArray(requestPayload.messages)) {
|
|
875
|
+
requestPayload.messages = filterMessagesThinkingBlocks(requestPayload.messages, PLUGIN_SESSION_ID, getCachedSignature);
|
|
876
|
+
}
|
|
364
877
|
}
|
|
365
878
|
// For Claude models, ensure functionCall/tool use parts carry IDs (required by Anthropic).
|
|
366
879
|
// We use a two-pass approach: first collect all functionCalls and assign IDs,
|
|
@@ -417,6 +930,7 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
417
930
|
if ("model" in requestPayload) {
|
|
418
931
|
delete requestPayload.model;
|
|
419
932
|
}
|
|
933
|
+
stripInjectedDebugFromRequestPayload(requestPayload);
|
|
420
934
|
const effectiveProjectId = projectId?.trim() || generateSyntheticProjectId();
|
|
421
935
|
resolvedProjectId = effectiveProjectId;
|
|
422
936
|
const wrappedBody = {
|
|
@@ -430,7 +944,9 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
430
944
|
requestId: "agent-" + crypto.randomUUID(),
|
|
431
945
|
});
|
|
432
946
|
if (wrappedBody.request && typeof wrappedBody.request === 'object') {
|
|
433
|
-
|
|
947
|
+
// Use stable session ID for signature caching across multi-turn conversations
|
|
948
|
+
sessionId = PLUGIN_SESSION_ID;
|
|
949
|
+
wrappedBody.request.sessionId = sessionId;
|
|
434
950
|
}
|
|
435
951
|
body = JSON.stringify(wrappedBody);
|
|
436
952
|
}
|
|
@@ -442,6 +958,20 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
442
958
|
if (streaming) {
|
|
443
959
|
headers.set("Accept", "text/event-stream");
|
|
444
960
|
}
|
|
961
|
+
// Add interleaved thinking header for Claude thinking models
|
|
962
|
+
// This enables real-time streaming of thinking tokens
|
|
963
|
+
if (isClaudeThinkingModel) {
|
|
964
|
+
const existing = headers.get("anthropic-beta");
|
|
965
|
+
const interleavedHeader = "interleaved-thinking-2025-05-14";
|
|
966
|
+
if (existing) {
|
|
967
|
+
if (!existing.includes(interleavedHeader)) {
|
|
968
|
+
headers.set("anthropic-beta", `${existing},${interleavedHeader}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
headers.set("anthropic-beta", interleavedHeader);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
445
975
|
headers.set("User-Agent", ANTIGRAVITY_HEADERS["User-Agent"]);
|
|
446
976
|
headers.set("X-Goog-Api-Client", ANTIGRAVITY_HEADERS["X-Goog-Api-Client"]);
|
|
447
977
|
headers.set("Client-Metadata", ANTIGRAVITY_HEADERS["Client-Metadata"]);
|
|
@@ -457,25 +987,67 @@ export function prepareAntigravityRequest(input, init, accessToken, projectId, e
|
|
|
457
987
|
body,
|
|
458
988
|
},
|
|
459
989
|
streaming,
|
|
460
|
-
requestedModel
|
|
990
|
+
requestedModel,
|
|
461
991
|
effectiveModel: upstreamModel,
|
|
462
992
|
projectId: resolvedProjectId,
|
|
463
993
|
endpoint: transformedUrl,
|
|
994
|
+
sessionId,
|
|
464
995
|
toolDebugMissing,
|
|
465
996
|
toolDebugSummary: toolDebugSummaries.slice(0, 20).join(" | "),
|
|
466
997
|
toolDebugPayload,
|
|
998
|
+
needsSignedThinkingWarmup,
|
|
467
999
|
};
|
|
468
1000
|
}
|
|
1001
|
+
export function buildThinkingWarmupBody(bodyText, isClaudeThinkingModel) {
|
|
1002
|
+
if (!bodyText || !isClaudeThinkingModel) {
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
let parsed;
|
|
1006
|
+
try {
|
|
1007
|
+
parsed = JSON.parse(bodyText);
|
|
1008
|
+
}
|
|
1009
|
+
catch {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
const warmupPrompt = "Warmup request for thinking signature.";
|
|
1013
|
+
const updateRequest = (req) => {
|
|
1014
|
+
req.contents = [{ role: "user", parts: [{ text: warmupPrompt }] }];
|
|
1015
|
+
delete req.tools;
|
|
1016
|
+
delete req.toolConfig;
|
|
1017
|
+
const generationConfig = (req.generationConfig ?? {});
|
|
1018
|
+
generationConfig.thinkingConfig = {
|
|
1019
|
+
include_thoughts: true,
|
|
1020
|
+
thinking_budget: DEFAULT_THINKING_BUDGET,
|
|
1021
|
+
};
|
|
1022
|
+
generationConfig.maxOutputTokens = CLAUDE_THINKING_MAX_OUTPUT_TOKENS;
|
|
1023
|
+
req.generationConfig = generationConfig;
|
|
1024
|
+
};
|
|
1025
|
+
if (parsed.request && typeof parsed.request === "object") {
|
|
1026
|
+
updateRequest(parsed.request);
|
|
1027
|
+
const nested = parsed.request.request;
|
|
1028
|
+
if (nested && typeof nested === "object") {
|
|
1029
|
+
updateRequest(nested);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
updateRequest(parsed);
|
|
1034
|
+
}
|
|
1035
|
+
return JSON.stringify(parsed);
|
|
1036
|
+
}
|
|
469
1037
|
/**
|
|
470
1038
|
* Normalizes Antigravity responses: applies retry headers, extracts cache usage into headers,
|
|
471
1039
|
* rewrites preview errors, flattens streaming payloads, and logs debug metadata.
|
|
472
1040
|
*
|
|
473
|
-
* For streaming SSE responses, uses TransformStream for true incremental streaming.
|
|
1041
|
+
* For streaming SSE responses, uses TransformStream for true real-time incremental streaming.
|
|
1042
|
+
* Thinking/reasoning tokens are transformed and forwarded immediately as they arrive.
|
|
474
1043
|
*/
|
|
475
|
-
export async function transformAntigravityResponse(response, streaming, debugContext, requestedModel, projectId, endpoint, effectiveModel, toolDebugMissing, toolDebugSummary, toolDebugPayload) {
|
|
1044
|
+
export async function transformAntigravityResponse(response, streaming, debugContext, requestedModel, projectId, endpoint, effectiveModel, sessionId, toolDebugMissing, toolDebugSummary, toolDebugPayload, debugLines) {
|
|
476
1045
|
const contentType = response.headers.get("content-type") ?? "";
|
|
477
1046
|
const isJsonResponse = contentType.includes("application/json");
|
|
478
1047
|
const isEventStreamResponse = contentType.includes("text/event-stream");
|
|
1048
|
+
const debugText = isDebugEnabled() && Array.isArray(debugLines) && debugLines.length > 0
|
|
1049
|
+
? formatDebugLinesForThinking(debugLines)
|
|
1050
|
+
: undefined;
|
|
479
1051
|
if (!isJsonResponse && !isEventStreamResponse) {
|
|
480
1052
|
logAntigravityDebugResponse(debugContext, response, {
|
|
481
1053
|
note: "Non-JSON response (body omitted)",
|
|
@@ -483,43 +1055,16 @@ export async function transformAntigravityResponse(response, streaming, debugCon
|
|
|
483
1055
|
return response;
|
|
484
1056
|
}
|
|
485
1057
|
// For successful streaming responses, use TransformStream to transform SSE events
|
|
486
|
-
// while maintaining real-time streaming (no buffering of entire response)
|
|
1058
|
+
// while maintaining real-time streaming (no buffering of entire response).
|
|
1059
|
+
// This enables thinking tokens to be displayed as they arrive, like the Codex plugin.
|
|
487
1060
|
if (streaming && response.ok && isEventStreamResponse && response.body) {
|
|
488
1061
|
const headers = new Headers(response.headers);
|
|
489
|
-
// Buffer for partial SSE events that span chunks
|
|
490
|
-
let buffer = "";
|
|
491
|
-
const decoder = new TextDecoder();
|
|
492
|
-
const encoder = new TextEncoder();
|
|
493
|
-
const transformStream = new TransformStream({
|
|
494
|
-
transform(chunk, controller) {
|
|
495
|
-
// Decode chunk with stream: true to handle multi-byte characters
|
|
496
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
497
|
-
// Split on double newline (SSE event delimiter)
|
|
498
|
-
const events = buffer.split("\n\n");
|
|
499
|
-
// Keep last part in buffer (may be incomplete)
|
|
500
|
-
buffer = events.pop() || "";
|
|
501
|
-
// Process and forward complete events immediately
|
|
502
|
-
for (const event of events) {
|
|
503
|
-
if (event.trim()) {
|
|
504
|
-
const transformed = transformStreamingPayload(event);
|
|
505
|
-
controller.enqueue(encoder.encode(transformed + "\n\n"));
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
},
|
|
509
|
-
flush(controller) {
|
|
510
|
-
// Flush any remaining bytes from TextDecoder
|
|
511
|
-
buffer += decoder.decode();
|
|
512
|
-
// Handle any remaining data at stream end
|
|
513
|
-
if (buffer.trim()) {
|
|
514
|
-
const transformed = transformStreamingPayload(buffer);
|
|
515
|
-
controller.enqueue(encoder.encode(transformed));
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
1062
|
logAntigravityDebugResponse(debugContext, response, {
|
|
520
|
-
note: "Streaming SSE response (
|
|
1063
|
+
note: "Streaming SSE response (real-time transform)",
|
|
521
1064
|
});
|
|
522
|
-
|
|
1065
|
+
// Use the optimized line-by-line transformer for immediate forwarding
|
|
1066
|
+
// This ensures thinking/reasoning content streams in real-time
|
|
1067
|
+
return new Response(response.body.pipeThrough(createStreamingTransformer(sessionId, debugText)), {
|
|
523
1068
|
status: response.status,
|
|
524
1069
|
statusText: response.statusText,
|
|
525
1070
|
headers,
|
|
@@ -538,8 +1083,9 @@ export async function transformAntigravityResponse(response, streaming, debugCon
|
|
|
538
1083
|
}
|
|
539
1084
|
// Inject Debug Info
|
|
540
1085
|
if (errorBody?.error) {
|
|
541
|
-
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(
|
|
542
|
-
|
|
1086
|
+
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}` : ""}`;
|
|
1087
|
+
const injectedDebug = debugText ? `\n\n${debugText}` : "";
|
|
1088
|
+
errorBody.error.message = (errorBody.error.message || "Unknown error") + debugInfo + injectedDebug;
|
|
543
1089
|
return new Response(JSON.stringify(errorBody), {
|
|
544
1090
|
status: response.status,
|
|
545
1091
|
statusText: response.statusText,
|
|
@@ -595,7 +1141,8 @@ export async function transformAntigravityResponse(response, streaming, debugCon
|
|
|
595
1141
|
return new Response(text, init);
|
|
596
1142
|
}
|
|
597
1143
|
if (effectiveBody?.response !== undefined) {
|
|
598
|
-
const
|
|
1144
|
+
const responseBody = debugText ? injectDebugThinking(effectiveBody.response, debugText) : effectiveBody.response;
|
|
1145
|
+
const transformed = transformThinkingParts(responseBody);
|
|
599
1146
|
return new Response(JSON.stringify(transformed), init);
|
|
600
1147
|
}
|
|
601
1148
|
if (patched) {
|