oh-my-opencode 1.2.4 → 2.0.0
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/dist/hooks/auto-update-checker/checker.d.ts +2 -0
- package/dist/index.js +60 -32
- package/package.json +1 -1
- package/dist/google-auth.js +0 -1599
package/dist/google-auth.js
DELETED
|
@@ -1,1599 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
|
-
var __export = (target, all) => {
|
|
20
|
-
for (var name in all)
|
|
21
|
-
__defProp(target, name, {
|
|
22
|
-
get: all[name],
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
set: (newValue) => all[name] = () => newValue
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
var __require = import.meta.require;
|
|
29
|
-
// src/auth/antigravity/constants.ts
|
|
30
|
-
var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
31
|
-
var ANTIGRAVITY_CLIENT_SECRET = "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";
|
|
32
|
-
var ANTIGRAVITY_CALLBACK_PORT = 51121;
|
|
33
|
-
var ANTIGRAVITY_REDIRECT_URI = `http://localhost:${ANTIGRAVITY_CALLBACK_PORT}/oauth-callback`;
|
|
34
|
-
var ANTIGRAVITY_SCOPES = [
|
|
35
|
-
"https://www.googleapis.com/auth/cloud-platform",
|
|
36
|
-
"https://www.googleapis.com/auth/userinfo.email",
|
|
37
|
-
"https://www.googleapis.com/auth/userinfo.profile",
|
|
38
|
-
"https://www.googleapis.com/auth/cclog",
|
|
39
|
-
"https://www.googleapis.com/auth/experimentsandconfigs"
|
|
40
|
-
];
|
|
41
|
-
var ANTIGRAVITY_ENDPOINT_FALLBACKS = [
|
|
42
|
-
"https://daily-cloudcode-pa.sandbox.googleapis.com",
|
|
43
|
-
"https://autopush-cloudcode-pa.sandbox.googleapis.com",
|
|
44
|
-
"https://cloudcode-pa.googleapis.com"
|
|
45
|
-
];
|
|
46
|
-
var ANTIGRAVITY_API_VERSION = "v1internal";
|
|
47
|
-
var ANTIGRAVITY_HEADERS = {
|
|
48
|
-
"User-Agent": "google-api-nodejs-client/9.15.1",
|
|
49
|
-
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
|
50
|
-
"Client-Metadata": JSON.stringify({
|
|
51
|
-
ideType: "IDE_UNSPECIFIED",
|
|
52
|
-
platform: "PLATFORM_UNSPECIFIED",
|
|
53
|
-
pluginType: "GEMINI"
|
|
54
|
-
})
|
|
55
|
-
};
|
|
56
|
-
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
57
|
-
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
58
|
-
var GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo";
|
|
59
|
-
var ANTIGRAVITY_TOKEN_REFRESH_BUFFER_MS = 60000;
|
|
60
|
-
var SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";
|
|
61
|
-
// node_modules/jose/dist/browser/lib/buffer_utils.js
|
|
62
|
-
var encoder = new TextEncoder;
|
|
63
|
-
var decoder = new TextDecoder;
|
|
64
|
-
var MAX_INT32 = 2 ** 32;
|
|
65
|
-
|
|
66
|
-
// node_modules/jose/dist/browser/runtime/base64url.js
|
|
67
|
-
var encodeBase64 = (input) => {
|
|
68
|
-
let unencoded = input;
|
|
69
|
-
if (typeof unencoded === "string") {
|
|
70
|
-
unencoded = encoder.encode(unencoded);
|
|
71
|
-
}
|
|
72
|
-
const CHUNK_SIZE = 32768;
|
|
73
|
-
const arr = [];
|
|
74
|
-
for (let i = 0;i < unencoded.length; i += CHUNK_SIZE) {
|
|
75
|
-
arr.push(String.fromCharCode.apply(null, unencoded.subarray(i, i + CHUNK_SIZE)));
|
|
76
|
-
}
|
|
77
|
-
return btoa(arr.join(""));
|
|
78
|
-
};
|
|
79
|
-
var encode = (input) => {
|
|
80
|
-
return encodeBase64(input).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
81
|
-
};
|
|
82
|
-
var decodeBase64 = (encoded) => {
|
|
83
|
-
const binary = atob(encoded);
|
|
84
|
-
const bytes = new Uint8Array(binary.length);
|
|
85
|
-
for (let i = 0;i < binary.length; i++) {
|
|
86
|
-
bytes[i] = binary.charCodeAt(i);
|
|
87
|
-
}
|
|
88
|
-
return bytes;
|
|
89
|
-
};
|
|
90
|
-
var decode = (input) => {
|
|
91
|
-
let encoded = input;
|
|
92
|
-
if (encoded instanceof Uint8Array) {
|
|
93
|
-
encoded = decoder.decode(encoded);
|
|
94
|
-
}
|
|
95
|
-
encoded = encoded.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "");
|
|
96
|
-
try {
|
|
97
|
-
return decodeBase64(encoded);
|
|
98
|
-
} catch {
|
|
99
|
-
throw new TypeError("The input to be decoded is not correctly encoded.");
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// node_modules/jose/dist/browser/util/base64url.js
|
|
104
|
-
var exports_base64url = {};
|
|
105
|
-
__export(exports_base64url, {
|
|
106
|
-
encode: () => encode2,
|
|
107
|
-
decode: () => decode2
|
|
108
|
-
});
|
|
109
|
-
var encode2 = encode;
|
|
110
|
-
var decode2 = decode;
|
|
111
|
-
// node_modules/@openauthjs/openauth/dist/esm/pkce.js
|
|
112
|
-
function generateVerifier(length) {
|
|
113
|
-
const buffer = new Uint8Array(length);
|
|
114
|
-
crypto.getRandomValues(buffer);
|
|
115
|
-
return exports_base64url.encode(buffer);
|
|
116
|
-
}
|
|
117
|
-
async function generateChallenge(verifier, method) {
|
|
118
|
-
if (method === "plain")
|
|
119
|
-
return verifier;
|
|
120
|
-
const encoder2 = new TextEncoder;
|
|
121
|
-
const data = encoder2.encode(verifier);
|
|
122
|
-
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
123
|
-
return exports_base64url.encode(new Uint8Array(hash));
|
|
124
|
-
}
|
|
125
|
-
async function generatePKCE(length = 64) {
|
|
126
|
-
if (length < 43 || length > 128) {
|
|
127
|
-
throw new Error("Code verifier length must be between 43 and 128 characters");
|
|
128
|
-
}
|
|
129
|
-
const verifier = generateVerifier(length);
|
|
130
|
-
const challenge = await generateChallenge(verifier, "S256");
|
|
131
|
-
return {
|
|
132
|
-
verifier,
|
|
133
|
-
challenge,
|
|
134
|
-
method: "S256"
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// src/auth/antigravity/oauth.ts
|
|
139
|
-
async function generatePKCEPair() {
|
|
140
|
-
const pkce = await generatePKCE();
|
|
141
|
-
return {
|
|
142
|
-
verifier: pkce.verifier,
|
|
143
|
-
challenge: pkce.challenge,
|
|
144
|
-
method: pkce.method
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
function encodeState(state) {
|
|
148
|
-
const json = JSON.stringify(state);
|
|
149
|
-
return Buffer.from(json, "utf8").toString("base64url");
|
|
150
|
-
}
|
|
151
|
-
function decodeState(encoded) {
|
|
152
|
-
const normalized = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
153
|
-
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
154
|
-
const json = Buffer.from(padded, "base64").toString("utf8");
|
|
155
|
-
const parsed = JSON.parse(json);
|
|
156
|
-
if (typeof parsed.verifier !== "string") {
|
|
157
|
-
throw new Error("Missing PKCE verifier in state");
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
verifier: parsed.verifier,
|
|
161
|
-
projectId: typeof parsed.projectId === "string" ? parsed.projectId : undefined
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
async function buildAuthURL(projectId, clientId = ANTIGRAVITY_CLIENT_ID, port = ANTIGRAVITY_CALLBACK_PORT) {
|
|
165
|
-
const pkce = await generatePKCEPair();
|
|
166
|
-
const state = {
|
|
167
|
-
verifier: pkce.verifier,
|
|
168
|
-
projectId
|
|
169
|
-
};
|
|
170
|
-
const redirectUri = `http://localhost:${port}/oauth-callback`;
|
|
171
|
-
const url = new URL(GOOGLE_AUTH_URL);
|
|
172
|
-
url.searchParams.set("client_id", clientId);
|
|
173
|
-
url.searchParams.set("redirect_uri", redirectUri);
|
|
174
|
-
url.searchParams.set("response_type", "code");
|
|
175
|
-
url.searchParams.set("scope", ANTIGRAVITY_SCOPES.join(" "));
|
|
176
|
-
url.searchParams.set("state", encodeState(state));
|
|
177
|
-
url.searchParams.set("code_challenge", pkce.challenge);
|
|
178
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
179
|
-
url.searchParams.set("access_type", "offline");
|
|
180
|
-
url.searchParams.set("prompt", "consent");
|
|
181
|
-
return {
|
|
182
|
-
url: url.toString(),
|
|
183
|
-
verifier: pkce.verifier
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
async function exchangeCode(code, verifier, clientId = ANTIGRAVITY_CLIENT_ID, clientSecret = ANTIGRAVITY_CLIENT_SECRET, port = ANTIGRAVITY_CALLBACK_PORT) {
|
|
187
|
-
const redirectUri = `http://localhost:${port}/oauth-callback`;
|
|
188
|
-
const params = new URLSearchParams({
|
|
189
|
-
client_id: clientId,
|
|
190
|
-
client_secret: clientSecret,
|
|
191
|
-
code,
|
|
192
|
-
grant_type: "authorization_code",
|
|
193
|
-
redirect_uri: redirectUri,
|
|
194
|
-
code_verifier: verifier
|
|
195
|
-
});
|
|
196
|
-
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
197
|
-
method: "POST",
|
|
198
|
-
headers: {
|
|
199
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
200
|
-
},
|
|
201
|
-
body: params
|
|
202
|
-
});
|
|
203
|
-
if (!response.ok) {
|
|
204
|
-
const errorText = await response.text();
|
|
205
|
-
throw new Error(`Token exchange failed: ${response.status} - ${errorText}`);
|
|
206
|
-
}
|
|
207
|
-
const data = await response.json();
|
|
208
|
-
return {
|
|
209
|
-
access_token: data.access_token,
|
|
210
|
-
refresh_token: data.refresh_token,
|
|
211
|
-
expires_in: data.expires_in,
|
|
212
|
-
token_type: data.token_type
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
async function fetchUserInfo(accessToken) {
|
|
216
|
-
const response = await fetch(`${GOOGLE_USERINFO_URL}?alt=json`, {
|
|
217
|
-
headers: {
|
|
218
|
-
Authorization: `Bearer ${accessToken}`
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
if (!response.ok) {
|
|
222
|
-
throw new Error(`Failed to fetch user info: ${response.status}`);
|
|
223
|
-
}
|
|
224
|
-
const data = await response.json();
|
|
225
|
-
return {
|
|
226
|
-
email: data.email || "",
|
|
227
|
-
name: data.name,
|
|
228
|
-
picture: data.picture
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
function startCallbackServer(timeoutMs = 5 * 60 * 1000) {
|
|
232
|
-
let server = null;
|
|
233
|
-
let timeoutId = null;
|
|
234
|
-
let resolveCallback = null;
|
|
235
|
-
let rejectCallback = null;
|
|
236
|
-
const cleanup = () => {
|
|
237
|
-
if (timeoutId) {
|
|
238
|
-
clearTimeout(timeoutId);
|
|
239
|
-
timeoutId = null;
|
|
240
|
-
}
|
|
241
|
-
if (server) {
|
|
242
|
-
server.stop();
|
|
243
|
-
server = null;
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
server = Bun.serve({
|
|
247
|
-
port: 0,
|
|
248
|
-
fetch(request) {
|
|
249
|
-
const url = new URL(request.url);
|
|
250
|
-
if (url.pathname === "/oauth-callback") {
|
|
251
|
-
const code = url.searchParams.get("code") || "";
|
|
252
|
-
const state = url.searchParams.get("state") || "";
|
|
253
|
-
const error = url.searchParams.get("error") || undefined;
|
|
254
|
-
let responseBody;
|
|
255
|
-
if (code && !error) {
|
|
256
|
-
responseBody = "<html><body><h1>Login successful</h1><p>You can close this window.</p></body></html>";
|
|
257
|
-
} else {
|
|
258
|
-
responseBody = "<html><body><h1>Login failed</h1><p>Please check the CLI output.</p></body></html>";
|
|
259
|
-
}
|
|
260
|
-
setTimeout(() => {
|
|
261
|
-
cleanup();
|
|
262
|
-
if (resolveCallback) {
|
|
263
|
-
resolveCallback({ code, state, error });
|
|
264
|
-
}
|
|
265
|
-
}, 100);
|
|
266
|
-
return new Response(responseBody, {
|
|
267
|
-
status: 200,
|
|
268
|
-
headers: { "Content-Type": "text/html" }
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
return new Response("Not Found", { status: 404 });
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
const actualPort = server.port;
|
|
275
|
-
const waitForCallback = () => {
|
|
276
|
-
return new Promise((resolve, reject) => {
|
|
277
|
-
resolveCallback = resolve;
|
|
278
|
-
rejectCallback = reject;
|
|
279
|
-
timeoutId = setTimeout(() => {
|
|
280
|
-
cleanup();
|
|
281
|
-
reject(new Error("OAuth callback timeout"));
|
|
282
|
-
}, timeoutMs);
|
|
283
|
-
});
|
|
284
|
-
};
|
|
285
|
-
return {
|
|
286
|
-
port: actualPort,
|
|
287
|
-
waitForCallback,
|
|
288
|
-
close: cleanup
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
// src/auth/antigravity/token.ts
|
|
292
|
-
function isTokenExpired(tokens) {
|
|
293
|
-
const expirationTime = tokens.timestamp + tokens.expires_in * 1000;
|
|
294
|
-
return Date.now() >= expirationTime - ANTIGRAVITY_TOKEN_REFRESH_BUFFER_MS;
|
|
295
|
-
}
|
|
296
|
-
async function refreshAccessToken(refreshToken, clientId = ANTIGRAVITY_CLIENT_ID, clientSecret = ANTIGRAVITY_CLIENT_SECRET) {
|
|
297
|
-
const params = new URLSearchParams({
|
|
298
|
-
grant_type: "refresh_token",
|
|
299
|
-
refresh_token: refreshToken,
|
|
300
|
-
client_id: clientId,
|
|
301
|
-
client_secret: clientSecret
|
|
302
|
-
});
|
|
303
|
-
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
304
|
-
method: "POST",
|
|
305
|
-
headers: {
|
|
306
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
307
|
-
},
|
|
308
|
-
body: params
|
|
309
|
-
});
|
|
310
|
-
if (!response.ok) {
|
|
311
|
-
const errorText = await response.text().catch(() => "Unknown error");
|
|
312
|
-
throw new Error(`Token refresh failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
313
|
-
}
|
|
314
|
-
const data = await response.json();
|
|
315
|
-
return {
|
|
316
|
-
access_token: data.access_token,
|
|
317
|
-
refresh_token: data.refresh_token || refreshToken,
|
|
318
|
-
expires_in: data.expires_in,
|
|
319
|
-
token_type: data.token_type
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
function parseStoredToken(stored) {
|
|
323
|
-
const parts = stored.split("|");
|
|
324
|
-
const [refreshToken, projectId, managedProjectId] = parts;
|
|
325
|
-
return {
|
|
326
|
-
refreshToken: refreshToken || "",
|
|
327
|
-
projectId: projectId || undefined,
|
|
328
|
-
managedProjectId: managedProjectId || undefined
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
function formatTokenForStorage(refreshToken, projectId, managedProjectId) {
|
|
332
|
-
return `${refreshToken}|${projectId}|${managedProjectId || ""}`;
|
|
333
|
-
}
|
|
334
|
-
// src/auth/antigravity/project.ts
|
|
335
|
-
var projectContextCache = new Map;
|
|
336
|
-
var CODE_ASSIST_METADATA = {
|
|
337
|
-
ideType: "IDE_UNSPECIFIED",
|
|
338
|
-
platform: "PLATFORM_UNSPECIFIED",
|
|
339
|
-
pluginType: "GEMINI"
|
|
340
|
-
};
|
|
341
|
-
function extractProjectId(project) {
|
|
342
|
-
if (!project) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
if (typeof project === "string") {
|
|
346
|
-
const trimmed = project.trim();
|
|
347
|
-
return trimmed || undefined;
|
|
348
|
-
}
|
|
349
|
-
if (typeof project === "object" && "id" in project) {
|
|
350
|
-
const id = project.id;
|
|
351
|
-
if (typeof id === "string") {
|
|
352
|
-
const trimmed = id.trim();
|
|
353
|
-
return trimmed || undefined;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
async function callLoadCodeAssistAPI(accessToken) {
|
|
359
|
-
const requestBody = {
|
|
360
|
-
metadata: CODE_ASSIST_METADATA
|
|
361
|
-
};
|
|
362
|
-
const headers = {
|
|
363
|
-
Authorization: `Bearer ${accessToken}`,
|
|
364
|
-
"Content-Type": "application/json",
|
|
365
|
-
"User-Agent": ANTIGRAVITY_HEADERS["User-Agent"],
|
|
366
|
-
"X-Goog-Api-Client": ANTIGRAVITY_HEADERS["X-Goog-Api-Client"],
|
|
367
|
-
"Client-Metadata": ANTIGRAVITY_HEADERS["Client-Metadata"]
|
|
368
|
-
};
|
|
369
|
-
for (const baseEndpoint of ANTIGRAVITY_ENDPOINT_FALLBACKS) {
|
|
370
|
-
const url = `${baseEndpoint}/${ANTIGRAVITY_API_VERSION}:loadCodeAssist`;
|
|
371
|
-
try {
|
|
372
|
-
const response = await fetch(url, {
|
|
373
|
-
method: "POST",
|
|
374
|
-
headers,
|
|
375
|
-
body: JSON.stringify(requestBody)
|
|
376
|
-
});
|
|
377
|
-
if (!response.ok) {
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
const data = await response.json();
|
|
381
|
-
return data;
|
|
382
|
-
} catch {
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
async function fetchProjectContext(accessToken) {
|
|
389
|
-
const cached = projectContextCache.get(accessToken);
|
|
390
|
-
if (cached) {
|
|
391
|
-
return cached;
|
|
392
|
-
}
|
|
393
|
-
const response = await callLoadCodeAssistAPI(accessToken);
|
|
394
|
-
const projectId = response ? extractProjectId(response.cloudaicompanionProject) : undefined;
|
|
395
|
-
const result = {
|
|
396
|
-
cloudaicompanionProject: projectId || ""
|
|
397
|
-
};
|
|
398
|
-
if (projectId) {
|
|
399
|
-
projectContextCache.set(accessToken, result);
|
|
400
|
-
}
|
|
401
|
-
return result;
|
|
402
|
-
}
|
|
403
|
-
function clearProjectContextCache(accessToken) {
|
|
404
|
-
if (accessToken) {
|
|
405
|
-
projectContextCache.delete(accessToken);
|
|
406
|
-
} else {
|
|
407
|
-
projectContextCache.clear();
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
// src/auth/antigravity/request.ts
|
|
411
|
-
function buildRequestHeaders(accessToken) {
|
|
412
|
-
return {
|
|
413
|
-
Authorization: `Bearer ${accessToken}`,
|
|
414
|
-
"Content-Type": "application/json",
|
|
415
|
-
"User-Agent": ANTIGRAVITY_HEADERS["User-Agent"],
|
|
416
|
-
"X-Goog-Api-Client": ANTIGRAVITY_HEADERS["X-Goog-Api-Client"],
|
|
417
|
-
"Client-Metadata": ANTIGRAVITY_HEADERS["Client-Metadata"]
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
function extractModelFromBody(body) {
|
|
421
|
-
const model = body.model;
|
|
422
|
-
if (typeof model === "string" && model.trim()) {
|
|
423
|
-
return model.trim();
|
|
424
|
-
}
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
function extractModelFromUrl(url) {
|
|
428
|
-
const match = url.match(/\/models\/([^:]+):/);
|
|
429
|
-
if (match && match[1]) {
|
|
430
|
-
return match[1];
|
|
431
|
-
}
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
function extractActionFromUrl(url) {
|
|
435
|
-
const match = url.match(/\/models\/[^:]+:(\w+)/);
|
|
436
|
-
if (match && match[1]) {
|
|
437
|
-
return match[1];
|
|
438
|
-
}
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
function buildAntigravityUrl(baseEndpoint, action, streaming) {
|
|
442
|
-
const query = streaming ? "?alt=sse" : "";
|
|
443
|
-
return `${baseEndpoint}/${ANTIGRAVITY_API_VERSION}:${action}${query}`;
|
|
444
|
-
}
|
|
445
|
-
function getDefaultEndpoint() {
|
|
446
|
-
return ANTIGRAVITY_ENDPOINT_FALLBACKS[0];
|
|
447
|
-
}
|
|
448
|
-
function generateRequestId() {
|
|
449
|
-
return `agent-${crypto.randomUUID()}`;
|
|
450
|
-
}
|
|
451
|
-
function wrapRequestBody(body, projectId, modelName, sessionId) {
|
|
452
|
-
const requestPayload = { ...body };
|
|
453
|
-
delete requestPayload.model;
|
|
454
|
-
return {
|
|
455
|
-
project: projectId,
|
|
456
|
-
model: modelName,
|
|
457
|
-
userAgent: "antigravity",
|
|
458
|
-
requestId: generateRequestId(),
|
|
459
|
-
request: {
|
|
460
|
-
...requestPayload,
|
|
461
|
-
sessionId
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
function debugLog(message) {
|
|
466
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
467
|
-
console.log(`[antigravity-request] ${message}`);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
function injectThoughtSignatureIntoFunctionCalls(body, signature) {
|
|
471
|
-
const effectiveSignature = signature || SKIP_THOUGHT_SIGNATURE_VALIDATOR;
|
|
472
|
-
debugLog(`[TSIG][INJECT] signature=${effectiveSignature.substring(0, 30)}... (${signature ? "provided" : "default"})`);
|
|
473
|
-
debugLog(`[TSIG][INJECT] body keys: ${Object.keys(body).join(", ")}`);
|
|
474
|
-
const contents = body.contents;
|
|
475
|
-
if (!contents || !Array.isArray(contents)) {
|
|
476
|
-
debugLog(`[TSIG][INJECT] No contents array! Has messages: ${!!body.messages}`);
|
|
477
|
-
return body;
|
|
478
|
-
}
|
|
479
|
-
debugLog(`[TSIG][INJECT] Found ${contents.length} content blocks`);
|
|
480
|
-
let injectedCount = 0;
|
|
481
|
-
const modifiedContents = contents.map((content) => {
|
|
482
|
-
if (!content.parts || !Array.isArray(content.parts)) {
|
|
483
|
-
return content;
|
|
484
|
-
}
|
|
485
|
-
const modifiedParts = content.parts.map((part) => {
|
|
486
|
-
if (part.functionCall && !part.thoughtSignature) {
|
|
487
|
-
injectedCount++;
|
|
488
|
-
return {
|
|
489
|
-
...part,
|
|
490
|
-
thoughtSignature: effectiveSignature
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
return part;
|
|
494
|
-
});
|
|
495
|
-
return { ...content, parts: modifiedParts };
|
|
496
|
-
});
|
|
497
|
-
debugLog(`[TSIG][INJECT] injected signature into ${injectedCount} functionCall(s)`);
|
|
498
|
-
return { ...body, contents: modifiedContents };
|
|
499
|
-
}
|
|
500
|
-
function isStreamingRequest(url, body) {
|
|
501
|
-
const action = extractActionFromUrl(url);
|
|
502
|
-
if (action === "streamGenerateContent") {
|
|
503
|
-
return true;
|
|
504
|
-
}
|
|
505
|
-
if (body.stream === true) {
|
|
506
|
-
return true;
|
|
507
|
-
}
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
function transformRequest(options) {
|
|
511
|
-
const {
|
|
512
|
-
url,
|
|
513
|
-
body,
|
|
514
|
-
accessToken,
|
|
515
|
-
projectId,
|
|
516
|
-
sessionId,
|
|
517
|
-
modelName,
|
|
518
|
-
endpointOverride,
|
|
519
|
-
thoughtSignature
|
|
520
|
-
} = options;
|
|
521
|
-
const effectiveModel = modelName || extractModelFromBody(body) || extractModelFromUrl(url) || "gemini-3-pro-preview";
|
|
522
|
-
const streaming = isStreamingRequest(url, body);
|
|
523
|
-
const action = streaming ? "streamGenerateContent" : "generateContent";
|
|
524
|
-
const endpoint = endpointOverride || getDefaultEndpoint();
|
|
525
|
-
const transformedUrl = buildAntigravityUrl(endpoint, action, streaming);
|
|
526
|
-
const headers = buildRequestHeaders(accessToken);
|
|
527
|
-
if (streaming) {
|
|
528
|
-
headers["Accept"] = "text/event-stream";
|
|
529
|
-
}
|
|
530
|
-
const bodyWithSignature = injectThoughtSignatureIntoFunctionCalls(body, thoughtSignature);
|
|
531
|
-
const wrappedBody = wrapRequestBody(bodyWithSignature, projectId, effectiveModel, sessionId);
|
|
532
|
-
return {
|
|
533
|
-
url: transformedUrl,
|
|
534
|
-
headers,
|
|
535
|
-
body: wrappedBody,
|
|
536
|
-
streaming
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
// src/auth/antigravity/response.ts
|
|
540
|
-
function extractUsageFromHeaders(headers) {
|
|
541
|
-
const cached = headers.get("x-antigravity-cached-content-token-count");
|
|
542
|
-
const total = headers.get("x-antigravity-total-token-count");
|
|
543
|
-
const prompt = headers.get("x-antigravity-prompt-token-count");
|
|
544
|
-
const candidates = headers.get("x-antigravity-candidates-token-count");
|
|
545
|
-
if (!cached && !total && !prompt && !candidates) {
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
const usage = {};
|
|
549
|
-
if (cached) {
|
|
550
|
-
const parsed = parseInt(cached, 10);
|
|
551
|
-
if (!isNaN(parsed)) {
|
|
552
|
-
usage.cachedContentTokenCount = parsed;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
if (total) {
|
|
556
|
-
const parsed = parseInt(total, 10);
|
|
557
|
-
if (!isNaN(parsed)) {
|
|
558
|
-
usage.totalTokenCount = parsed;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
if (prompt) {
|
|
562
|
-
const parsed = parseInt(prompt, 10);
|
|
563
|
-
if (!isNaN(parsed)) {
|
|
564
|
-
usage.promptTokenCount = parsed;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
if (candidates) {
|
|
568
|
-
const parsed = parseInt(candidates, 10);
|
|
569
|
-
if (!isNaN(parsed)) {
|
|
570
|
-
usage.candidatesTokenCount = parsed;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
574
|
-
}
|
|
575
|
-
function extractRetryAfterMs(response, errorBody) {
|
|
576
|
-
const retryAfterHeader = response.headers.get("Retry-After");
|
|
577
|
-
if (retryAfterHeader) {
|
|
578
|
-
const seconds = parseFloat(retryAfterHeader);
|
|
579
|
-
if (!isNaN(seconds) && seconds > 0) {
|
|
580
|
-
return Math.ceil(seconds * 1000);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
const retryAfterMsHeader = response.headers.get("retry-after-ms");
|
|
584
|
-
if (retryAfterMsHeader) {
|
|
585
|
-
const ms = parseInt(retryAfterMsHeader, 10);
|
|
586
|
-
if (!isNaN(ms) && ms > 0) {
|
|
587
|
-
return ms;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
if (!errorBody) {
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
const error = errorBody.error;
|
|
594
|
-
if (!error?.details || !Array.isArray(error.details)) {
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
const retryInfo = error.details.find((detail) => detail["@type"] === "type.googleapis.com/google.rpc.RetryInfo");
|
|
598
|
-
if (!retryInfo?.retryDelay || typeof retryInfo.retryDelay !== "string") {
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
const match = retryInfo.retryDelay.match(/^([\d.]+)s$/);
|
|
602
|
-
if (match?.[1]) {
|
|
603
|
-
const seconds = parseFloat(match[1]);
|
|
604
|
-
if (!isNaN(seconds) && seconds > 0) {
|
|
605
|
-
return Math.ceil(seconds * 1000);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
function parseErrorBody(text) {
|
|
611
|
-
try {
|
|
612
|
-
const parsed = JSON.parse(text);
|
|
613
|
-
if (parsed.error && typeof parsed.error === "object") {
|
|
614
|
-
const errorObj = parsed.error;
|
|
615
|
-
return {
|
|
616
|
-
message: String(errorObj.message || "Unknown error"),
|
|
617
|
-
type: errorObj.type ? String(errorObj.type) : undefined,
|
|
618
|
-
code: errorObj.code
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
if (parsed.message && typeof parsed.message === "string") {
|
|
622
|
-
return {
|
|
623
|
-
message: parsed.message,
|
|
624
|
-
type: parsed.type ? String(parsed.type) : undefined,
|
|
625
|
-
code: parsed.code
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
return;
|
|
629
|
-
} catch {
|
|
630
|
-
return {
|
|
631
|
-
message: text || "Unknown error"
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
async function transformResponse(response) {
|
|
636
|
-
const headers = new Headers(response.headers);
|
|
637
|
-
const usage = extractUsageFromHeaders(headers);
|
|
638
|
-
if (!response.ok) {
|
|
639
|
-
const text = await response.text();
|
|
640
|
-
const error = parseErrorBody(text);
|
|
641
|
-
const retryAfterMs = extractRetryAfterMs(response, error ? { error } : undefined);
|
|
642
|
-
let errorBody;
|
|
643
|
-
try {
|
|
644
|
-
errorBody = JSON.parse(text);
|
|
645
|
-
} catch {
|
|
646
|
-
errorBody = { error: { message: text } };
|
|
647
|
-
}
|
|
648
|
-
const retryMs = extractRetryAfterMs(response, errorBody) ?? retryAfterMs;
|
|
649
|
-
if (retryMs) {
|
|
650
|
-
headers.set("Retry-After", String(Math.ceil(retryMs / 1000)));
|
|
651
|
-
headers.set("retry-after-ms", String(retryMs));
|
|
652
|
-
}
|
|
653
|
-
return {
|
|
654
|
-
response: new Response(text, {
|
|
655
|
-
status: response.status,
|
|
656
|
-
statusText: response.statusText,
|
|
657
|
-
headers
|
|
658
|
-
}),
|
|
659
|
-
usage,
|
|
660
|
-
retryAfterMs: retryMs,
|
|
661
|
-
error
|
|
662
|
-
};
|
|
663
|
-
}
|
|
664
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
665
|
-
const isJson = contentType.includes("application/json");
|
|
666
|
-
if (!isJson) {
|
|
667
|
-
return { response, usage };
|
|
668
|
-
}
|
|
669
|
-
try {
|
|
670
|
-
const text = await response.text();
|
|
671
|
-
const parsed = JSON.parse(text);
|
|
672
|
-
let transformedBody = parsed;
|
|
673
|
-
if (parsed.response !== undefined) {
|
|
674
|
-
transformedBody = parsed.response;
|
|
675
|
-
}
|
|
676
|
-
return {
|
|
677
|
-
response: new Response(JSON.stringify(transformedBody), {
|
|
678
|
-
status: response.status,
|
|
679
|
-
statusText: response.statusText,
|
|
680
|
-
headers
|
|
681
|
-
}),
|
|
682
|
-
usage
|
|
683
|
-
};
|
|
684
|
-
} catch {
|
|
685
|
-
return { response, usage };
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
function transformSseLine(line) {
|
|
689
|
-
if (!line.startsWith("data:")) {
|
|
690
|
-
return line;
|
|
691
|
-
}
|
|
692
|
-
const json = line.slice(5).trim();
|
|
693
|
-
if (!json || json === "[DONE]") {
|
|
694
|
-
return line;
|
|
695
|
-
}
|
|
696
|
-
try {
|
|
697
|
-
const parsed = JSON.parse(json);
|
|
698
|
-
if (parsed.response !== undefined) {
|
|
699
|
-
return `data: ${JSON.stringify(parsed.response)}`;
|
|
700
|
-
}
|
|
701
|
-
return line;
|
|
702
|
-
} catch {
|
|
703
|
-
return line;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
function createSseTransformStream() {
|
|
707
|
-
const decoder2 = new TextDecoder;
|
|
708
|
-
const encoder2 = new TextEncoder;
|
|
709
|
-
let buffer = "";
|
|
710
|
-
return new TransformStream({
|
|
711
|
-
transform(chunk, controller) {
|
|
712
|
-
buffer += decoder2.decode(chunk, { stream: true });
|
|
713
|
-
const lines = buffer.split(`
|
|
714
|
-
`);
|
|
715
|
-
buffer = lines.pop() || "";
|
|
716
|
-
for (const line of lines) {
|
|
717
|
-
const transformed = transformSseLine(line);
|
|
718
|
-
controller.enqueue(encoder2.encode(transformed + `
|
|
719
|
-
`));
|
|
720
|
-
}
|
|
721
|
-
},
|
|
722
|
-
flush(controller) {
|
|
723
|
-
if (buffer) {
|
|
724
|
-
const transformed = transformSseLine(buffer);
|
|
725
|
-
controller.enqueue(encoder2.encode(transformed));
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
async function transformStreamingResponse(response) {
|
|
731
|
-
const headers = new Headers(response.headers);
|
|
732
|
-
const usage = extractUsageFromHeaders(headers);
|
|
733
|
-
if (!response.ok) {
|
|
734
|
-
const text = await response.text();
|
|
735
|
-
const error = parseErrorBody(text);
|
|
736
|
-
let errorBody;
|
|
737
|
-
try {
|
|
738
|
-
errorBody = JSON.parse(text);
|
|
739
|
-
} catch {
|
|
740
|
-
errorBody = { error: { message: text } };
|
|
741
|
-
}
|
|
742
|
-
const retryAfterMs = extractRetryAfterMs(response, errorBody);
|
|
743
|
-
if (retryAfterMs) {
|
|
744
|
-
headers.set("Retry-After", String(Math.ceil(retryAfterMs / 1000)));
|
|
745
|
-
headers.set("retry-after-ms", String(retryAfterMs));
|
|
746
|
-
}
|
|
747
|
-
return {
|
|
748
|
-
response: new Response(text, {
|
|
749
|
-
status: response.status,
|
|
750
|
-
statusText: response.statusText,
|
|
751
|
-
headers
|
|
752
|
-
}),
|
|
753
|
-
usage,
|
|
754
|
-
retryAfterMs,
|
|
755
|
-
error
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
759
|
-
const isEventStream = contentType.includes("text/event-stream") || response.url.includes("alt=sse");
|
|
760
|
-
if (!isEventStream) {
|
|
761
|
-
const text = await response.text();
|
|
762
|
-
try {
|
|
763
|
-
const parsed = JSON.parse(text);
|
|
764
|
-
let transformedBody2 = parsed;
|
|
765
|
-
if (parsed.response !== undefined) {
|
|
766
|
-
transformedBody2 = parsed.response;
|
|
767
|
-
}
|
|
768
|
-
return {
|
|
769
|
-
response: new Response(JSON.stringify(transformedBody2), {
|
|
770
|
-
status: response.status,
|
|
771
|
-
statusText: response.statusText,
|
|
772
|
-
headers
|
|
773
|
-
}),
|
|
774
|
-
usage
|
|
775
|
-
};
|
|
776
|
-
} catch {
|
|
777
|
-
return {
|
|
778
|
-
response: new Response(text, {
|
|
779
|
-
status: response.status,
|
|
780
|
-
statusText: response.statusText,
|
|
781
|
-
headers
|
|
782
|
-
}),
|
|
783
|
-
usage
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
if (!response.body) {
|
|
788
|
-
return { response, usage };
|
|
789
|
-
}
|
|
790
|
-
headers.delete("content-length");
|
|
791
|
-
headers.delete("content-encoding");
|
|
792
|
-
headers.set("content-type", "text/event-stream; charset=utf-8");
|
|
793
|
-
const transformStream = createSseTransformStream();
|
|
794
|
-
const transformedBody = response.body.pipeThrough(transformStream);
|
|
795
|
-
return {
|
|
796
|
-
response: new Response(transformedBody, {
|
|
797
|
-
status: response.status,
|
|
798
|
-
statusText: response.statusText,
|
|
799
|
-
headers
|
|
800
|
-
}),
|
|
801
|
-
usage
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
function isStreamingResponse(response) {
|
|
805
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
806
|
-
return contentType.includes("text/event-stream") || response.url.includes("alt=sse");
|
|
807
|
-
}
|
|
808
|
-
// src/auth/antigravity/tools.ts
|
|
809
|
-
function normalizeToolsForGemini(tools) {
|
|
810
|
-
if (!tools || tools.length === 0) {
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
const functionDeclarations = [];
|
|
814
|
-
for (const tool of tools) {
|
|
815
|
-
if (!tool || typeof tool !== "object") {
|
|
816
|
-
continue;
|
|
817
|
-
}
|
|
818
|
-
const toolType = tool.type ?? "function";
|
|
819
|
-
if (toolType === "function" && tool.function) {
|
|
820
|
-
const declaration = {
|
|
821
|
-
name: tool.function.name
|
|
822
|
-
};
|
|
823
|
-
if (tool.function.description) {
|
|
824
|
-
declaration.description = tool.function.description;
|
|
825
|
-
}
|
|
826
|
-
if (tool.function.parameters) {
|
|
827
|
-
declaration.parameters = tool.function.parameters;
|
|
828
|
-
} else {
|
|
829
|
-
declaration.parameters = { type: "object", properties: {} };
|
|
830
|
-
}
|
|
831
|
-
functionDeclarations.push(declaration);
|
|
832
|
-
} else if (toolType !== "function" && process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
833
|
-
console.warn(`[antigravity-tools] Unsupported tool type: "${toolType}". Tool will be skipped.`);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
if (functionDeclarations.length === 0) {
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
return { functionDeclarations };
|
|
840
|
-
}
|
|
841
|
-
// src/auth/antigravity/thinking.ts
|
|
842
|
-
function shouldIncludeThinking(model) {
|
|
843
|
-
if (!model || typeof model !== "string") {
|
|
844
|
-
return false;
|
|
845
|
-
}
|
|
846
|
-
const lowerModel = model.toLowerCase();
|
|
847
|
-
if (lowerModel.endsWith("-high")) {
|
|
848
|
-
return true;
|
|
849
|
-
}
|
|
850
|
-
if (lowerModel.includes("thinking")) {
|
|
851
|
-
return true;
|
|
852
|
-
}
|
|
853
|
-
return false;
|
|
854
|
-
}
|
|
855
|
-
function isThinkingPart(part) {
|
|
856
|
-
if (part.thought === true) {
|
|
857
|
-
return true;
|
|
858
|
-
}
|
|
859
|
-
if (part.type === "thinking" || part.type === "reasoning") {
|
|
860
|
-
return true;
|
|
861
|
-
}
|
|
862
|
-
return false;
|
|
863
|
-
}
|
|
864
|
-
function extractThinkingBlocks(response) {
|
|
865
|
-
const thinkingBlocks = [];
|
|
866
|
-
if (response.candidates && Array.isArray(response.candidates)) {
|
|
867
|
-
for (const candidate of response.candidates) {
|
|
868
|
-
const parts = candidate.content?.parts;
|
|
869
|
-
if (!parts || !Array.isArray(parts)) {
|
|
870
|
-
continue;
|
|
871
|
-
}
|
|
872
|
-
for (let i = 0;i < parts.length; i++) {
|
|
873
|
-
const part = parts[i];
|
|
874
|
-
if (!part || typeof part !== "object") {
|
|
875
|
-
continue;
|
|
876
|
-
}
|
|
877
|
-
if (isThinkingPart(part)) {
|
|
878
|
-
const block = {
|
|
879
|
-
text: part.text || "",
|
|
880
|
-
index: thinkingBlocks.length
|
|
881
|
-
};
|
|
882
|
-
if (part.thought === true && part.thoughtSignature) {
|
|
883
|
-
block.signature = part.thoughtSignature;
|
|
884
|
-
} else if (part.signature) {
|
|
885
|
-
block.signature = part.signature;
|
|
886
|
-
}
|
|
887
|
-
thinkingBlocks.push(block);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
if (response.content && Array.isArray(response.content)) {
|
|
893
|
-
for (let i = 0;i < response.content.length; i++) {
|
|
894
|
-
const item = response.content[i];
|
|
895
|
-
if (!item || typeof item !== "object") {
|
|
896
|
-
continue;
|
|
897
|
-
}
|
|
898
|
-
if (item.type === "thinking" || item.type === "reasoning") {
|
|
899
|
-
thinkingBlocks.push({
|
|
900
|
-
text: item.text || "",
|
|
901
|
-
signature: item.signature,
|
|
902
|
-
index: thinkingBlocks.length
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
const combinedThinking = thinkingBlocks.map((b) => b.text).join(`
|
|
908
|
-
|
|
909
|
-
`);
|
|
910
|
-
return {
|
|
911
|
-
thinkingBlocks,
|
|
912
|
-
combinedThinking,
|
|
913
|
-
hasThinking: thinkingBlocks.length > 0
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
function transformCandidateThinking(candidate) {
|
|
917
|
-
if (!candidate || typeof candidate !== "object") {
|
|
918
|
-
return candidate;
|
|
919
|
-
}
|
|
920
|
-
const content = candidate.content;
|
|
921
|
-
if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
|
|
922
|
-
return candidate;
|
|
923
|
-
}
|
|
924
|
-
const thinkingTexts = [];
|
|
925
|
-
const transformedParts = content.parts.map((part) => {
|
|
926
|
-
if (part && typeof part === "object" && part.thought === true) {
|
|
927
|
-
thinkingTexts.push(part.text || "");
|
|
928
|
-
return {
|
|
929
|
-
...part,
|
|
930
|
-
type: "reasoning",
|
|
931
|
-
thought: undefined
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
return part;
|
|
935
|
-
});
|
|
936
|
-
const result = {
|
|
937
|
-
...candidate,
|
|
938
|
-
content: { ...content, parts: transformedParts }
|
|
939
|
-
};
|
|
940
|
-
if (thinkingTexts.length > 0) {
|
|
941
|
-
result.reasoning_content = thinkingTexts.join(`
|
|
942
|
-
|
|
943
|
-
`);
|
|
944
|
-
}
|
|
945
|
-
return result;
|
|
946
|
-
}
|
|
947
|
-
function transformAnthropicThinking(content) {
|
|
948
|
-
if (!content || !Array.isArray(content)) {
|
|
949
|
-
return content;
|
|
950
|
-
}
|
|
951
|
-
return content.map((block) => {
|
|
952
|
-
if (block && typeof block === "object" && block.type === "thinking") {
|
|
953
|
-
return {
|
|
954
|
-
type: "reasoning",
|
|
955
|
-
text: block.text || "",
|
|
956
|
-
...block.signature ? { signature: block.signature } : {}
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
return block;
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
function transformResponseThinking(response) {
|
|
963
|
-
if (!response || typeof response !== "object") {
|
|
964
|
-
return response;
|
|
965
|
-
}
|
|
966
|
-
const result = { ...response };
|
|
967
|
-
if (Array.isArray(result.candidates)) {
|
|
968
|
-
result.candidates = result.candidates.map(transformCandidateThinking);
|
|
969
|
-
}
|
|
970
|
-
if (Array.isArray(result.content)) {
|
|
971
|
-
result.content = transformAnthropicThinking(result.content);
|
|
972
|
-
}
|
|
973
|
-
return result;
|
|
974
|
-
}
|
|
975
|
-
// src/auth/antigravity/thought-signature-store.ts
|
|
976
|
-
var signatureStore = new Map;
|
|
977
|
-
var sessionIdStore = new Map;
|
|
978
|
-
function setThoughtSignature(sessionKey, signature) {
|
|
979
|
-
if (sessionKey && signature) {
|
|
980
|
-
signatureStore.set(sessionKey, signature);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
function getThoughtSignature(sessionKey) {
|
|
984
|
-
return signatureStore.get(sessionKey);
|
|
985
|
-
}
|
|
986
|
-
function getOrCreateSessionId(fetchInstanceId, sessionId) {
|
|
987
|
-
if (sessionId) {
|
|
988
|
-
sessionIdStore.set(fetchInstanceId, sessionId);
|
|
989
|
-
return sessionId;
|
|
990
|
-
}
|
|
991
|
-
const existing = sessionIdStore.get(fetchInstanceId);
|
|
992
|
-
if (existing) {
|
|
993
|
-
return existing;
|
|
994
|
-
}
|
|
995
|
-
const n = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
|
996
|
-
const newSessionId = `-${n}`;
|
|
997
|
-
sessionIdStore.set(fetchInstanceId, newSessionId);
|
|
998
|
-
return newSessionId;
|
|
999
|
-
}
|
|
1000
|
-
// src/auth/antigravity/message-converter.ts
|
|
1001
|
-
function debugLog2(message) {
|
|
1002
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1003
|
-
console.log(`[antigravity-converter] ${message}`);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
function convertOpenAIToGemini(messages, thoughtSignature) {
|
|
1007
|
-
debugLog2(`Converting ${messages.length} messages, signature: ${thoughtSignature ? "present" : "none"}`);
|
|
1008
|
-
const contents = [];
|
|
1009
|
-
for (const msg of messages) {
|
|
1010
|
-
if (msg.role === "system") {
|
|
1011
|
-
contents.push({
|
|
1012
|
-
role: "user",
|
|
1013
|
-
parts: [{ text: typeof msg.content === "string" ? msg.content : "" }]
|
|
1014
|
-
});
|
|
1015
|
-
continue;
|
|
1016
|
-
}
|
|
1017
|
-
if (msg.role === "user") {
|
|
1018
|
-
const parts = convertContentToParts(msg.content);
|
|
1019
|
-
contents.push({ role: "user", parts });
|
|
1020
|
-
continue;
|
|
1021
|
-
}
|
|
1022
|
-
if (msg.role === "assistant") {
|
|
1023
|
-
const parts = [];
|
|
1024
|
-
if (msg.content) {
|
|
1025
|
-
parts.push(...convertContentToParts(msg.content));
|
|
1026
|
-
}
|
|
1027
|
-
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
1028
|
-
for (const toolCall of msg.tool_calls) {
|
|
1029
|
-
let args = {};
|
|
1030
|
-
try {
|
|
1031
|
-
args = JSON.parse(toolCall.function.arguments);
|
|
1032
|
-
} catch {
|
|
1033
|
-
args = {};
|
|
1034
|
-
}
|
|
1035
|
-
const part = {
|
|
1036
|
-
functionCall: {
|
|
1037
|
-
name: toolCall.function.name,
|
|
1038
|
-
args
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
part.thoughtSignature = thoughtSignature || SKIP_THOUGHT_SIGNATURE_VALIDATOR;
|
|
1042
|
-
debugLog2(`Injected signature into functionCall: ${toolCall.function.name} (${thoughtSignature ? "provided" : "default"})`);
|
|
1043
|
-
parts.push(part);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
if (parts.length > 0) {
|
|
1047
|
-
contents.push({ role: "model", parts });
|
|
1048
|
-
}
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
if (msg.role === "tool") {
|
|
1052
|
-
let response = {};
|
|
1053
|
-
try {
|
|
1054
|
-
response = typeof msg.content === "string" ? JSON.parse(msg.content) : { result: msg.content };
|
|
1055
|
-
} catch {
|
|
1056
|
-
response = { result: msg.content };
|
|
1057
|
-
}
|
|
1058
|
-
const toolName = msg.name || "unknown";
|
|
1059
|
-
contents.push({
|
|
1060
|
-
role: "user",
|
|
1061
|
-
parts: [{
|
|
1062
|
-
functionResponse: {
|
|
1063
|
-
name: toolName,
|
|
1064
|
-
response
|
|
1065
|
-
}
|
|
1066
|
-
}]
|
|
1067
|
-
});
|
|
1068
|
-
continue;
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
debugLog2(`Converted to ${contents.length} content blocks`);
|
|
1072
|
-
return contents;
|
|
1073
|
-
}
|
|
1074
|
-
function convertContentToParts(content) {
|
|
1075
|
-
if (!content) {
|
|
1076
|
-
return [{ text: "" }];
|
|
1077
|
-
}
|
|
1078
|
-
if (typeof content === "string") {
|
|
1079
|
-
return [{ text: content }];
|
|
1080
|
-
}
|
|
1081
|
-
const parts = [];
|
|
1082
|
-
for (const part of content) {
|
|
1083
|
-
if (part.type === "text" && part.text) {
|
|
1084
|
-
parts.push({ text: part.text });
|
|
1085
|
-
} else if (part.type === "image_url" && part.image_url?.url) {
|
|
1086
|
-
const url = part.image_url.url;
|
|
1087
|
-
if (url.startsWith("data:")) {
|
|
1088
|
-
const match = url.match(/^data:([^;]+);base64,(.+)$/);
|
|
1089
|
-
if (match) {
|
|
1090
|
-
parts.push({
|
|
1091
|
-
inlineData: {
|
|
1092
|
-
mimeType: match[1],
|
|
1093
|
-
data: match[2]
|
|
1094
|
-
}
|
|
1095
|
-
});
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
return parts.length > 0 ? parts : [{ text: "" }];
|
|
1101
|
-
}
|
|
1102
|
-
function hasOpenAIMessages(body) {
|
|
1103
|
-
return Array.isArray(body.messages) && body.messages.length > 0;
|
|
1104
|
-
}
|
|
1105
|
-
function convertRequestBody(body, thoughtSignature) {
|
|
1106
|
-
if (!hasOpenAIMessages(body)) {
|
|
1107
|
-
debugLog2("No messages array found, returning body as-is");
|
|
1108
|
-
return body;
|
|
1109
|
-
}
|
|
1110
|
-
const messages = body.messages;
|
|
1111
|
-
const contents = convertOpenAIToGemini(messages, thoughtSignature);
|
|
1112
|
-
const converted = { ...body };
|
|
1113
|
-
delete converted.messages;
|
|
1114
|
-
converted.contents = contents;
|
|
1115
|
-
debugLog2(`Converted body: messages \u2192 contents (${contents.length} blocks)`);
|
|
1116
|
-
return converted;
|
|
1117
|
-
}
|
|
1118
|
-
// src/auth/antigravity/fetch.ts
|
|
1119
|
-
function debugLog3(message) {
|
|
1120
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1121
|
-
console.log(`[antigravity-fetch] ${message}`);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
function isRetryableError(status) {
|
|
1125
|
-
if (status === 0)
|
|
1126
|
-
return true;
|
|
1127
|
-
if (status === 429)
|
|
1128
|
-
return true;
|
|
1129
|
-
if (status >= 500 && status < 600)
|
|
1130
|
-
return true;
|
|
1131
|
-
return false;
|
|
1132
|
-
}
|
|
1133
|
-
var GCP_PERMISSION_ERROR_PATTERNS = [
|
|
1134
|
-
"PERMISSION_DENIED",
|
|
1135
|
-
"does not have permission",
|
|
1136
|
-
"Cloud AI Companion API has not been used",
|
|
1137
|
-
"has not been enabled"
|
|
1138
|
-
];
|
|
1139
|
-
function isGcpPermissionError(text) {
|
|
1140
|
-
return GCP_PERMISSION_ERROR_PATTERNS.some((pattern) => text.includes(pattern));
|
|
1141
|
-
}
|
|
1142
|
-
function calculateRetryDelay(attempt) {
|
|
1143
|
-
return Math.min(200 * Math.pow(2, attempt), 2000);
|
|
1144
|
-
}
|
|
1145
|
-
async function isRetryableResponse(response) {
|
|
1146
|
-
if (isRetryableError(response.status))
|
|
1147
|
-
return true;
|
|
1148
|
-
if (response.status === 403) {
|
|
1149
|
-
try {
|
|
1150
|
-
const text = await response.clone().text();
|
|
1151
|
-
if (text.includes("SUBSCRIPTION_REQUIRED") || text.includes("Gemini Code Assist license")) {
|
|
1152
|
-
debugLog3(`[RETRY] 403 SUBSCRIPTION_REQUIRED detected, will retry with next endpoint`);
|
|
1153
|
-
return true;
|
|
1154
|
-
}
|
|
1155
|
-
} catch {}
|
|
1156
|
-
}
|
|
1157
|
-
return false;
|
|
1158
|
-
}
|
|
1159
|
-
async function attemptFetch(options) {
|
|
1160
|
-
const { endpoint, url, init, accessToken, projectId, sessionId, modelName, thoughtSignature } = options;
|
|
1161
|
-
debugLog3(`Trying endpoint: ${endpoint}`);
|
|
1162
|
-
try {
|
|
1163
|
-
const rawBody = init.body;
|
|
1164
|
-
if (rawBody !== undefined && typeof rawBody !== "string") {
|
|
1165
|
-
debugLog3(`Non-string body detected (${typeof rawBody}), signaling pass-through`);
|
|
1166
|
-
return "pass-through";
|
|
1167
|
-
}
|
|
1168
|
-
let parsedBody = {};
|
|
1169
|
-
if (rawBody) {
|
|
1170
|
-
try {
|
|
1171
|
-
parsedBody = JSON.parse(rawBody);
|
|
1172
|
-
} catch {
|
|
1173
|
-
parsedBody = {};
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
debugLog3(`[BODY] Keys: ${Object.keys(parsedBody).join(", ")}`);
|
|
1177
|
-
debugLog3(`[BODY] Has contents: ${!!parsedBody.contents}, Has messages: ${!!parsedBody.messages}`);
|
|
1178
|
-
if (parsedBody.contents) {
|
|
1179
|
-
const contents = parsedBody.contents;
|
|
1180
|
-
debugLog3(`[BODY] contents length: ${contents.length}`);
|
|
1181
|
-
contents.forEach((c, i) => {
|
|
1182
|
-
debugLog3(`[BODY] contents[${i}].role: ${c.role}, parts: ${JSON.stringify(c.parts).substring(0, 200)}`);
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
if (parsedBody.tools && Array.isArray(parsedBody.tools)) {
|
|
1186
|
-
const normalizedTools = normalizeToolsForGemini(parsedBody.tools);
|
|
1187
|
-
if (normalizedTools) {
|
|
1188
|
-
parsedBody.tools = normalizedTools;
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
if (hasOpenAIMessages(parsedBody)) {
|
|
1192
|
-
debugLog3(`[CONVERT] Converting OpenAI messages to Gemini contents`);
|
|
1193
|
-
parsedBody = convertRequestBody(parsedBody, thoughtSignature);
|
|
1194
|
-
debugLog3(`[CONVERT] After conversion - Has contents: ${!!parsedBody.contents}`);
|
|
1195
|
-
}
|
|
1196
|
-
const transformed = transformRequest({
|
|
1197
|
-
url,
|
|
1198
|
-
body: parsedBody,
|
|
1199
|
-
accessToken,
|
|
1200
|
-
projectId,
|
|
1201
|
-
sessionId,
|
|
1202
|
-
modelName,
|
|
1203
|
-
endpointOverride: endpoint,
|
|
1204
|
-
thoughtSignature
|
|
1205
|
-
});
|
|
1206
|
-
debugLog3(`[REQ] streaming=${transformed.streaming}, url=${transformed.url}`);
|
|
1207
|
-
const maxPermissionRetries = 10;
|
|
1208
|
-
for (let attempt = 0;attempt <= maxPermissionRetries; attempt++) {
|
|
1209
|
-
const response = await fetch(transformed.url, {
|
|
1210
|
-
method: init.method || "POST",
|
|
1211
|
-
headers: transformed.headers,
|
|
1212
|
-
body: JSON.stringify(transformed.body),
|
|
1213
|
-
signal: init.signal
|
|
1214
|
-
});
|
|
1215
|
-
debugLog3(`[RESP] status=${response.status} content-type=${response.headers.get("content-type") ?? ""} url=${response.url}`);
|
|
1216
|
-
if (response.status === 401) {
|
|
1217
|
-
debugLog3(`[401] Unauthorized response detected, signaling token refresh needed`);
|
|
1218
|
-
return "needs-refresh";
|
|
1219
|
-
}
|
|
1220
|
-
if (response.status === 403) {
|
|
1221
|
-
try {
|
|
1222
|
-
const text = await response.clone().text();
|
|
1223
|
-
if (isGcpPermissionError(text)) {
|
|
1224
|
-
if (attempt < maxPermissionRetries) {
|
|
1225
|
-
const delay = calculateRetryDelay(attempt);
|
|
1226
|
-
debugLog3(`[RETRY] GCP permission error, retry ${attempt + 1}/${maxPermissionRetries} after ${delay}ms`);
|
|
1227
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1228
|
-
continue;
|
|
1229
|
-
}
|
|
1230
|
-
debugLog3(`[RETRY] GCP permission error, max retries exceeded`);
|
|
1231
|
-
}
|
|
1232
|
-
} catch {}
|
|
1233
|
-
}
|
|
1234
|
-
if (!response.ok && await isRetryableResponse(response)) {
|
|
1235
|
-
debugLog3(`Endpoint failed: ${endpoint} (status: ${response.status}), trying next`);
|
|
1236
|
-
return null;
|
|
1237
|
-
}
|
|
1238
|
-
return response;
|
|
1239
|
-
}
|
|
1240
|
-
return null;
|
|
1241
|
-
} catch (error) {
|
|
1242
|
-
debugLog3(`Endpoint failed: ${endpoint} (${error instanceof Error ? error.message : "Unknown error"}), trying next`);
|
|
1243
|
-
return null;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
function extractSignatureFromResponse(parsed) {
|
|
1247
|
-
if (!parsed.candidates || !Array.isArray(parsed.candidates)) {
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
for (const candidate of parsed.candidates) {
|
|
1251
|
-
const parts = candidate.content?.parts;
|
|
1252
|
-
if (!parts || !Array.isArray(parts)) {
|
|
1253
|
-
continue;
|
|
1254
|
-
}
|
|
1255
|
-
for (const part of parts) {
|
|
1256
|
-
const sig = part.thoughtSignature || part.thought_signature;
|
|
1257
|
-
if (sig && typeof sig === "string") {
|
|
1258
|
-
return sig;
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
async function transformResponseWithThinking(response, modelName, fetchInstanceId) {
|
|
1265
|
-
const streaming = isStreamingResponse(response);
|
|
1266
|
-
let result;
|
|
1267
|
-
if (streaming) {
|
|
1268
|
-
result = await transformStreamingResponse(response);
|
|
1269
|
-
} else {
|
|
1270
|
-
result = await transformResponse(response);
|
|
1271
|
-
}
|
|
1272
|
-
if (streaming) {
|
|
1273
|
-
return result.response;
|
|
1274
|
-
}
|
|
1275
|
-
try {
|
|
1276
|
-
const text = await result.response.clone().text();
|
|
1277
|
-
debugLog3(`[TSIG][RESP] Response text length: ${text.length}`);
|
|
1278
|
-
const parsed = JSON.parse(text);
|
|
1279
|
-
debugLog3(`[TSIG][RESP] Parsed keys: ${Object.keys(parsed).join(", ")}`);
|
|
1280
|
-
debugLog3(`[TSIG][RESP] Has candidates: ${!!parsed.candidates}, count: ${parsed.candidates?.length ?? 0}`);
|
|
1281
|
-
const signature = extractSignatureFromResponse(parsed);
|
|
1282
|
-
debugLog3(`[TSIG][RESP] Signature extracted: ${signature ? signature.substring(0, 30) + "..." : "NONE"}`);
|
|
1283
|
-
if (signature) {
|
|
1284
|
-
setThoughtSignature(fetchInstanceId, signature);
|
|
1285
|
-
debugLog3(`[TSIG][STORE] Stored signature for ${fetchInstanceId}`);
|
|
1286
|
-
} else {
|
|
1287
|
-
debugLog3(`[TSIG][WARN] No signature found in response!`);
|
|
1288
|
-
}
|
|
1289
|
-
if (shouldIncludeThinking(modelName)) {
|
|
1290
|
-
const thinkingResult = extractThinkingBlocks(parsed);
|
|
1291
|
-
if (thinkingResult.hasThinking) {
|
|
1292
|
-
const transformed = transformResponseThinking(parsed);
|
|
1293
|
-
return new Response(JSON.stringify(transformed), {
|
|
1294
|
-
status: result.response.status,
|
|
1295
|
-
statusText: result.response.statusText,
|
|
1296
|
-
headers: result.response.headers
|
|
1297
|
-
});
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
} catch {}
|
|
1301
|
-
return result.response;
|
|
1302
|
-
}
|
|
1303
|
-
function createAntigravityFetch(getAuth, client, providerId, clientId, clientSecret) {
|
|
1304
|
-
let cachedTokens = null;
|
|
1305
|
-
let cachedProjectId = null;
|
|
1306
|
-
const fetchInstanceId = crypto.randomUUID();
|
|
1307
|
-
return async (url, init = {}) => {
|
|
1308
|
-
debugLog3(`Intercepting request to: ${url}`);
|
|
1309
|
-
const auth = await getAuth();
|
|
1310
|
-
if (!auth.access || !auth.refresh) {
|
|
1311
|
-
throw new Error("Antigravity: No authentication tokens available");
|
|
1312
|
-
}
|
|
1313
|
-
const refreshParts = parseStoredToken(auth.refresh);
|
|
1314
|
-
if (!cachedTokens) {
|
|
1315
|
-
cachedTokens = {
|
|
1316
|
-
type: "antigravity",
|
|
1317
|
-
access_token: auth.access,
|
|
1318
|
-
refresh_token: refreshParts.refreshToken,
|
|
1319
|
-
expires_in: auth.expires ? Math.floor((auth.expires - Date.now()) / 1000) : 3600,
|
|
1320
|
-
timestamp: auth.expires ? auth.expires - 3600 * 1000 : Date.now()
|
|
1321
|
-
};
|
|
1322
|
-
} else {
|
|
1323
|
-
cachedTokens.access_token = auth.access;
|
|
1324
|
-
cachedTokens.refresh_token = refreshParts.refreshToken;
|
|
1325
|
-
}
|
|
1326
|
-
if (isTokenExpired(cachedTokens)) {
|
|
1327
|
-
debugLog3("Token expired, refreshing...");
|
|
1328
|
-
try {
|
|
1329
|
-
const newTokens = await refreshAccessToken(refreshParts.refreshToken, clientId, clientSecret);
|
|
1330
|
-
cachedTokens = {
|
|
1331
|
-
type: "antigravity",
|
|
1332
|
-
access_token: newTokens.access_token,
|
|
1333
|
-
refresh_token: newTokens.refresh_token,
|
|
1334
|
-
expires_in: newTokens.expires_in,
|
|
1335
|
-
timestamp: Date.now()
|
|
1336
|
-
};
|
|
1337
|
-
clearProjectContextCache();
|
|
1338
|
-
const formattedRefresh = formatTokenForStorage(newTokens.refresh_token, refreshParts.projectId || "", refreshParts.managedProjectId);
|
|
1339
|
-
await client.set(providerId, {
|
|
1340
|
-
access: newTokens.access_token,
|
|
1341
|
-
refresh: formattedRefresh,
|
|
1342
|
-
expires: Date.now() + newTokens.expires_in * 1000
|
|
1343
|
-
});
|
|
1344
|
-
debugLog3("Token refreshed successfully");
|
|
1345
|
-
} catch (error) {
|
|
1346
|
-
throw new Error(`Antigravity: Token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
if (!cachedProjectId) {
|
|
1350
|
-
const projectContext = await fetchProjectContext(cachedTokens.access_token);
|
|
1351
|
-
cachedProjectId = projectContext.cloudaicompanionProject || "";
|
|
1352
|
-
debugLog3(`[PROJECT] Fetched project ID: "${cachedProjectId}"`);
|
|
1353
|
-
}
|
|
1354
|
-
const projectId = cachedProjectId;
|
|
1355
|
-
debugLog3(`[PROJECT] Using project ID: "${projectId}"`);
|
|
1356
|
-
let modelName;
|
|
1357
|
-
if (init.body) {
|
|
1358
|
-
try {
|
|
1359
|
-
const body = typeof init.body === "string" ? JSON.parse(init.body) : init.body;
|
|
1360
|
-
if (typeof body.model === "string") {
|
|
1361
|
-
modelName = body.model;
|
|
1362
|
-
}
|
|
1363
|
-
} catch {}
|
|
1364
|
-
}
|
|
1365
|
-
const maxEndpoints = Math.min(ANTIGRAVITY_ENDPOINT_FALLBACKS.length, 3);
|
|
1366
|
-
const sessionId = getOrCreateSessionId(fetchInstanceId);
|
|
1367
|
-
const thoughtSignature = getThoughtSignature(fetchInstanceId);
|
|
1368
|
-
debugLog3(`[TSIG][GET] sessionId=${sessionId}, signature=${thoughtSignature ? thoughtSignature.substring(0, 20) + "..." : "none"}`);
|
|
1369
|
-
let hasRefreshedFor401 = false;
|
|
1370
|
-
const executeWithEndpoints = async () => {
|
|
1371
|
-
for (let i = 0;i < maxEndpoints; i++) {
|
|
1372
|
-
const endpoint = ANTIGRAVITY_ENDPOINT_FALLBACKS[i];
|
|
1373
|
-
const response = await attemptFetch({
|
|
1374
|
-
endpoint,
|
|
1375
|
-
url,
|
|
1376
|
-
init,
|
|
1377
|
-
accessToken: cachedTokens.access_token,
|
|
1378
|
-
projectId,
|
|
1379
|
-
sessionId,
|
|
1380
|
-
modelName,
|
|
1381
|
-
thoughtSignature
|
|
1382
|
-
});
|
|
1383
|
-
if (response === "pass-through") {
|
|
1384
|
-
debugLog3("Non-string body detected, passing through with auth headers");
|
|
1385
|
-
const headersWithAuth = {
|
|
1386
|
-
...init.headers,
|
|
1387
|
-
Authorization: `Bearer ${cachedTokens.access_token}`
|
|
1388
|
-
};
|
|
1389
|
-
return fetch(url, { ...init, headers: headersWithAuth });
|
|
1390
|
-
}
|
|
1391
|
-
if (response === "needs-refresh") {
|
|
1392
|
-
if (hasRefreshedFor401) {
|
|
1393
|
-
debugLog3("[401] Already refreshed once, returning unauthorized error");
|
|
1394
|
-
return new Response(JSON.stringify({
|
|
1395
|
-
error: {
|
|
1396
|
-
message: "Authentication failed after token refresh",
|
|
1397
|
-
type: "unauthorized",
|
|
1398
|
-
code: "token_refresh_failed"
|
|
1399
|
-
}
|
|
1400
|
-
}), {
|
|
1401
|
-
status: 401,
|
|
1402
|
-
statusText: "Unauthorized",
|
|
1403
|
-
headers: { "Content-Type": "application/json" }
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
debugLog3("[401] Refreshing token and retrying...");
|
|
1407
|
-
hasRefreshedFor401 = true;
|
|
1408
|
-
try {
|
|
1409
|
-
const newTokens = await refreshAccessToken(refreshParts.refreshToken, clientId, clientSecret);
|
|
1410
|
-
cachedTokens = {
|
|
1411
|
-
type: "antigravity",
|
|
1412
|
-
access_token: newTokens.access_token,
|
|
1413
|
-
refresh_token: newTokens.refresh_token,
|
|
1414
|
-
expires_in: newTokens.expires_in,
|
|
1415
|
-
timestamp: Date.now()
|
|
1416
|
-
};
|
|
1417
|
-
clearProjectContextCache();
|
|
1418
|
-
const formattedRefresh = formatTokenForStorage(newTokens.refresh_token, refreshParts.projectId || "", refreshParts.managedProjectId);
|
|
1419
|
-
await client.set(providerId, {
|
|
1420
|
-
access: newTokens.access_token,
|
|
1421
|
-
refresh: formattedRefresh,
|
|
1422
|
-
expires: Date.now() + newTokens.expires_in * 1000
|
|
1423
|
-
});
|
|
1424
|
-
debugLog3("[401] Token refreshed, retrying request...");
|
|
1425
|
-
return executeWithEndpoints();
|
|
1426
|
-
} catch (refreshError) {
|
|
1427
|
-
debugLog3(`[401] Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`);
|
|
1428
|
-
return new Response(JSON.stringify({
|
|
1429
|
-
error: {
|
|
1430
|
-
message: `Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`,
|
|
1431
|
-
type: "unauthorized",
|
|
1432
|
-
code: "token_refresh_failed"
|
|
1433
|
-
}
|
|
1434
|
-
}), {
|
|
1435
|
-
status: 401,
|
|
1436
|
-
statusText: "Unauthorized",
|
|
1437
|
-
headers: { "Content-Type": "application/json" }
|
|
1438
|
-
});
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
if (response) {
|
|
1442
|
-
debugLog3(`Success with endpoint: ${endpoint}`);
|
|
1443
|
-
const transformedResponse = await transformResponseWithThinking(response, modelName || "", fetchInstanceId);
|
|
1444
|
-
return transformedResponse;
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
const errorMessage = `All Antigravity endpoints failed after ${maxEndpoints} attempts`;
|
|
1448
|
-
debugLog3(errorMessage);
|
|
1449
|
-
return new Response(JSON.stringify({
|
|
1450
|
-
error: {
|
|
1451
|
-
message: errorMessage,
|
|
1452
|
-
type: "endpoint_failure",
|
|
1453
|
-
code: "all_endpoints_failed"
|
|
1454
|
-
}
|
|
1455
|
-
}), {
|
|
1456
|
-
status: 503,
|
|
1457
|
-
statusText: "Service Unavailable",
|
|
1458
|
-
headers: { "Content-Type": "application/json" }
|
|
1459
|
-
});
|
|
1460
|
-
};
|
|
1461
|
-
return executeWithEndpoints();
|
|
1462
|
-
};
|
|
1463
|
-
}
|
|
1464
|
-
// src/auth/antigravity/plugin.ts
|
|
1465
|
-
var GOOGLE_PROVIDER_ID = "google";
|
|
1466
|
-
function isOAuthAuth(auth) {
|
|
1467
|
-
return auth.type === "oauth";
|
|
1468
|
-
}
|
|
1469
|
-
async function createGoogleAntigravityAuthPlugin({
|
|
1470
|
-
client
|
|
1471
|
-
}) {
|
|
1472
|
-
let cachedClientId = ANTIGRAVITY_CLIENT_ID;
|
|
1473
|
-
let cachedClientSecret = ANTIGRAVITY_CLIENT_SECRET;
|
|
1474
|
-
const authHook = {
|
|
1475
|
-
provider: GOOGLE_PROVIDER_ID,
|
|
1476
|
-
loader: async (auth, provider) => {
|
|
1477
|
-
const currentAuth = await auth();
|
|
1478
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1479
|
-
console.log("[antigravity-plugin] loader called");
|
|
1480
|
-
console.log("[antigravity-plugin] auth type:", currentAuth?.type);
|
|
1481
|
-
console.log("[antigravity-plugin] auth keys:", Object.keys(currentAuth || {}));
|
|
1482
|
-
}
|
|
1483
|
-
if (!isOAuthAuth(currentAuth)) {
|
|
1484
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1485
|
-
console.log("[antigravity-plugin] NOT OAuth auth, returning empty");
|
|
1486
|
-
}
|
|
1487
|
-
return {};
|
|
1488
|
-
}
|
|
1489
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1490
|
-
console.log("[antigravity-plugin] OAuth auth detected, creating custom fetch");
|
|
1491
|
-
}
|
|
1492
|
-
cachedClientId = provider.options?.clientId || ANTIGRAVITY_CLIENT_ID;
|
|
1493
|
-
cachedClientSecret = provider.options?.clientSecret || ANTIGRAVITY_CLIENT_SECRET;
|
|
1494
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1" && (cachedClientId !== ANTIGRAVITY_CLIENT_ID || cachedClientSecret !== ANTIGRAVITY_CLIENT_SECRET)) {
|
|
1495
|
-
console.log("[antigravity-plugin] Using custom credentials from provider.options");
|
|
1496
|
-
}
|
|
1497
|
-
const authClient = {
|
|
1498
|
-
set: async (providerId, authData) => {
|
|
1499
|
-
await client.auth.set({
|
|
1500
|
-
body: {
|
|
1501
|
-
type: "oauth",
|
|
1502
|
-
access: authData.access || "",
|
|
1503
|
-
refresh: authData.refresh || "",
|
|
1504
|
-
expires: authData.expires || 0
|
|
1505
|
-
},
|
|
1506
|
-
path: { id: providerId }
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
const getAuth = async () => {
|
|
1511
|
-
const authState = await auth();
|
|
1512
|
-
if (isOAuthAuth(authState)) {
|
|
1513
|
-
return {
|
|
1514
|
-
access: authState.access,
|
|
1515
|
-
refresh: authState.refresh,
|
|
1516
|
-
expires: authState.expires
|
|
1517
|
-
};
|
|
1518
|
-
}
|
|
1519
|
-
return {};
|
|
1520
|
-
};
|
|
1521
|
-
const antigravityFetch = createAntigravityFetch(getAuth, authClient, GOOGLE_PROVIDER_ID, cachedClientId, cachedClientSecret);
|
|
1522
|
-
return {
|
|
1523
|
-
fetch: antigravityFetch,
|
|
1524
|
-
apiKey: "antigravity-oauth"
|
|
1525
|
-
};
|
|
1526
|
-
},
|
|
1527
|
-
methods: [
|
|
1528
|
-
{
|
|
1529
|
-
type: "oauth",
|
|
1530
|
-
label: "OAuth with Google (Antigravity)",
|
|
1531
|
-
authorize: async () => {
|
|
1532
|
-
const serverHandle = startCallbackServer();
|
|
1533
|
-
const { url, verifier } = await buildAuthURL(undefined, cachedClientId, serverHandle.port);
|
|
1534
|
-
return {
|
|
1535
|
-
url,
|
|
1536
|
-
instructions: "Complete the sign-in in your browser. We'll automatically detect when you're done.",
|
|
1537
|
-
method: "auto",
|
|
1538
|
-
callback: async () => {
|
|
1539
|
-
try {
|
|
1540
|
-
const result = await serverHandle.waitForCallback();
|
|
1541
|
-
if (result.error) {
|
|
1542
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1543
|
-
console.error(`[antigravity-plugin] OAuth error: ${result.error}`);
|
|
1544
|
-
}
|
|
1545
|
-
return { type: "failed" };
|
|
1546
|
-
}
|
|
1547
|
-
if (!result.code) {
|
|
1548
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1549
|
-
console.error("[antigravity-plugin] No authorization code received");
|
|
1550
|
-
}
|
|
1551
|
-
return { type: "failed" };
|
|
1552
|
-
}
|
|
1553
|
-
const state = decodeState(result.state);
|
|
1554
|
-
if (state.verifier !== verifier) {
|
|
1555
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1556
|
-
console.error("[antigravity-plugin] PKCE verifier mismatch");
|
|
1557
|
-
}
|
|
1558
|
-
return { type: "failed" };
|
|
1559
|
-
}
|
|
1560
|
-
const tokens = await exchangeCode(result.code, verifier, cachedClientId, cachedClientSecret, serverHandle.port);
|
|
1561
|
-
try {
|
|
1562
|
-
const userInfo = await fetchUserInfo(tokens.access_token);
|
|
1563
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1564
|
-
console.log(`[antigravity-plugin] Authenticated as: ${userInfo.email}`);
|
|
1565
|
-
}
|
|
1566
|
-
} catch {}
|
|
1567
|
-
const projectContext = await fetchProjectContext(tokens.access_token);
|
|
1568
|
-
const formattedRefresh = formatTokenForStorage(tokens.refresh_token, projectContext.cloudaicompanionProject || "", projectContext.managedProjectId);
|
|
1569
|
-
return {
|
|
1570
|
-
type: "success",
|
|
1571
|
-
access: tokens.access_token,
|
|
1572
|
-
refresh: formattedRefresh,
|
|
1573
|
-
expires: Date.now() + tokens.expires_in * 1000
|
|
1574
|
-
};
|
|
1575
|
-
} catch (error) {
|
|
1576
|
-
serverHandle.close();
|
|
1577
|
-
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
1578
|
-
console.error(`[antigravity-plugin] OAuth flow failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1579
|
-
}
|
|
1580
|
-
return { type: "failed" };
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
};
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
]
|
|
1587
|
-
};
|
|
1588
|
-
return {
|
|
1589
|
-
auth: authHook
|
|
1590
|
-
};
|
|
1591
|
-
}
|
|
1592
|
-
// src/google-auth.ts
|
|
1593
|
-
var GoogleAntigravityAuthPlugin = async (ctx) => {
|
|
1594
|
-
return createGoogleAntigravityAuthPlugin(ctx);
|
|
1595
|
-
};
|
|
1596
|
-
var google_auth_default = GoogleAntigravityAuthPlugin;
|
|
1597
|
-
export {
|
|
1598
|
-
google_auth_default as default
|
|
1599
|
-
};
|