pi-provider-antigravity 0.1.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/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/index.js +1430 -0
- package/dist/index.js.map +7 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1430 @@
|
|
|
1
|
+
// src/auth.ts
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
// src/protocol.ts
|
|
5
|
+
var DEFAULT_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
|
|
6
|
+
var DEFAULT_USER_AGENT = "antigravity/cli/1.1.26 (aidev_client; os_type=linux; arch=amd64; cl=976013059; auth_method=consumer)";
|
|
7
|
+
var PROVIDER_ID = "antigravity";
|
|
8
|
+
function buildAntigravityHeaders(token, userAgent = DEFAULT_USER_AGENT) {
|
|
9
|
+
return {
|
|
10
|
+
Host: "daily-cloudcode-pa.googleapis.com",
|
|
11
|
+
"User-Agent": userAgent,
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
Authorization: `Bearer ${token}`
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/auth.ts
|
|
18
|
+
var REDIRECT_URI = "https://antigravity.google/oauth-callback";
|
|
19
|
+
var AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
|
|
20
|
+
var TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
21
|
+
var OAUTH_CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
22
|
+
var SCOPES = [
|
|
23
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
24
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
25
|
+
"https://www.googleapis.com/auth/userinfo.profile",
|
|
26
|
+
"https://www.googleapis.com/auth/cclog",
|
|
27
|
+
"https://www.googleapis.com/auth/experimentsandconfigs",
|
|
28
|
+
"https://www.googleapis.com/auth/aicode",
|
|
29
|
+
"openid"
|
|
30
|
+
];
|
|
31
|
+
var CLIENT_ID = process.env.ANTIGRAVITY_CLIENT_ID || Buffer.from(
|
|
32
|
+
"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==",
|
|
33
|
+
"base64"
|
|
34
|
+
).toString("utf-8");
|
|
35
|
+
var CLIENT_SECRET = process.env.ANTIGRAVITY_CLIENT_SECRET || Buffer.from("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=", "base64").toString("utf-8");
|
|
36
|
+
function base64Url(buf) {
|
|
37
|
+
return buf.toString("base64url");
|
|
38
|
+
}
|
|
39
|
+
function extractCodeFromInput(rawInput) {
|
|
40
|
+
let code = rawInput.trim();
|
|
41
|
+
if (!code) {
|
|
42
|
+
throw new Error("No authorization code provided");
|
|
43
|
+
}
|
|
44
|
+
if (code.startsWith("code=")) {
|
|
45
|
+
code = code.slice(5).trim();
|
|
46
|
+
if (!code) {
|
|
47
|
+
throw new Error("No authorization code provided");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (/[\s:?#&=%+]/.test(code)) {
|
|
51
|
+
throw new Error("Invalid authorization code: paste the code value itself, not the callback URL");
|
|
52
|
+
}
|
|
53
|
+
return code;
|
|
54
|
+
}
|
|
55
|
+
function generatePKCE() {
|
|
56
|
+
const verifier = base64Url(randomBytes(32));
|
|
57
|
+
const challenge = base64Url(createHash("sha256").update(verifier).digest());
|
|
58
|
+
return { verifier, challenge };
|
|
59
|
+
}
|
|
60
|
+
async function fetchProjectId(token, endpoint = DEFAULT_ENDPOINT) {
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: buildAntigravityHeaders(token),
|
|
65
|
+
body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } })
|
|
66
|
+
});
|
|
67
|
+
if (res.ok) {
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
if (data.cloudaicompanionProject) {
|
|
70
|
+
return data.cloudaicompanionProject;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
return "aicode-consumers";
|
|
76
|
+
}
|
|
77
|
+
async function loginAntigravity(callbacks) {
|
|
78
|
+
const { verifier, challenge } = generatePKCE();
|
|
79
|
+
const state = randomBytes(16).toString("hex");
|
|
80
|
+
const authParams = new URLSearchParams({
|
|
81
|
+
client_id: CLIENT_ID,
|
|
82
|
+
response_type: "code",
|
|
83
|
+
redirect_uri: REDIRECT_URI,
|
|
84
|
+
scope: SCOPES.join(" "),
|
|
85
|
+
code_challenge: challenge,
|
|
86
|
+
code_challenge_method: "S256",
|
|
87
|
+
state,
|
|
88
|
+
access_type: "offline",
|
|
89
|
+
prompt: "consent"
|
|
90
|
+
});
|
|
91
|
+
const authUrl = `${AUTH_URL}?${authParams.toString()}`;
|
|
92
|
+
callbacks.onAuth({ url: authUrl });
|
|
93
|
+
const manualPromptPromise = (async () => {
|
|
94
|
+
const input = await callbacks.onPrompt({
|
|
95
|
+
message: "Paste authorization code from browser:"
|
|
96
|
+
});
|
|
97
|
+
return extractCodeFromInput(input);
|
|
98
|
+
})();
|
|
99
|
+
const timeout = new Promise(
|
|
100
|
+
(_, rej) => setTimeout(() => rej(new Error("Timed out waiting for authorization code (5 minutes)")), OAUTH_CALLBACK_TIMEOUT_MS)
|
|
101
|
+
);
|
|
102
|
+
const code = await Promise.race([manualPromptPromise, timeout]);
|
|
103
|
+
const tokenParams = new URLSearchParams({
|
|
104
|
+
client_id: CLIENT_ID,
|
|
105
|
+
client_secret: CLIENT_SECRET,
|
|
106
|
+
code,
|
|
107
|
+
grant_type: "authorization_code",
|
|
108
|
+
redirect_uri: REDIRECT_URI,
|
|
109
|
+
code_verifier: verifier
|
|
110
|
+
});
|
|
111
|
+
const tokenRes = await fetch(TOKEN_URL, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
114
|
+
body: tokenParams.toString()
|
|
115
|
+
});
|
|
116
|
+
if (!tokenRes.ok) {
|
|
117
|
+
const errText = await tokenRes.text();
|
|
118
|
+
throw new Error(`Failed to exchange token (${tokenRes.status}): ${errText}`);
|
|
119
|
+
}
|
|
120
|
+
const tokens = await tokenRes.json();
|
|
121
|
+
const projectId = await fetchProjectId(tokens.access_token);
|
|
122
|
+
return {
|
|
123
|
+
refresh: tokens.refresh_token || "",
|
|
124
|
+
access: JSON.stringify({ token: tokens.access_token, projectId }),
|
|
125
|
+
expires: Date.now() + (tokens.expires_in || 3600) * 1e3
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async function refreshAntigravityToken(credentials, signal) {
|
|
129
|
+
const params = new URLSearchParams({
|
|
130
|
+
client_id: CLIENT_ID,
|
|
131
|
+
client_secret: CLIENT_SECRET,
|
|
132
|
+
refresh_token: credentials.refresh,
|
|
133
|
+
grant_type: "refresh_token"
|
|
134
|
+
});
|
|
135
|
+
const res = await fetch(TOKEN_URL, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
138
|
+
body: params.toString(),
|
|
139
|
+
signal
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
const errText = await res.text();
|
|
143
|
+
throw new Error(`Failed to refresh token (${res.status}): ${errText}`);
|
|
144
|
+
}
|
|
145
|
+
const tokens = await res.json();
|
|
146
|
+
let existingProjectId = "aicode-consumers";
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(credentials.access);
|
|
149
|
+
if (parsed.projectId) existingProjectId = parsed.projectId;
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
refresh: tokens.refresh_token || credentials.refresh,
|
|
154
|
+
access: JSON.stringify({ token: tokens.access_token, projectId: existingProjectId }),
|
|
155
|
+
expires: Date.now() + (tokens.expires_in || 3600) * 1e3
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function getApiKey(credentials) {
|
|
159
|
+
return credentials.access;
|
|
160
|
+
}
|
|
161
|
+
function parseStoredCredentials(rawApiKey) {
|
|
162
|
+
try {
|
|
163
|
+
const parsed = JSON.parse(rawApiKey);
|
|
164
|
+
if (parsed.token) {
|
|
165
|
+
return { token: parsed.token, projectId: parsed.projectId || "aicode-consumers" };
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
return { token: rawApiKey, projectId: "aicode-consumers" };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/catalog.ts
|
|
173
|
+
function extractBaseModelId(runtimeId) {
|
|
174
|
+
if (runtimeId === "gemini-pro-agent") return "gemini-3.1-pro";
|
|
175
|
+
if (runtimeId.startsWith("gemini-3.1-pro-")) return "gemini-3.1-pro";
|
|
176
|
+
return runtimeId.replace(/-(?:high|medium|low|tiered|thinking|agent|extra-low)$/, "");
|
|
177
|
+
}
|
|
178
|
+
var STATIC_MODEL_ENUMS = {
|
|
179
|
+
// Gemini 3.8 Flash
|
|
180
|
+
"gemini-3.8-flash": "MODEL_PLACEHOLDER_M318",
|
|
181
|
+
"gemini-3.8-flash-high": "MODEL_PLACEHOLDER_M318",
|
|
182
|
+
"gemini-3.8-flash-medium": "MODEL_PLACEHOLDER_M319",
|
|
183
|
+
"gemini-3.8-flash-low": "MODEL_PLACEHOLDER_M320",
|
|
184
|
+
"gemini-3.8-flash-tiered": "MODEL_PLACEHOLDER_M322",
|
|
185
|
+
// Gemini 3.7 Flash
|
|
186
|
+
"gemini-3.7-flash": "MODEL_PLACEHOLDER_M298",
|
|
187
|
+
"gemini-3.7-flash-high": "MODEL_PLACEHOLDER_M298",
|
|
188
|
+
"gemini-3.7-flash-medium": "MODEL_PLACEHOLDER_M299",
|
|
189
|
+
"gemini-3.7-flash-low": "MODEL_PLACEHOLDER_M300",
|
|
190
|
+
"gemini-3.7-flash-tiered": "MODEL_PLACEHOLDER_M301",
|
|
191
|
+
// Gemini 3.6 Flash
|
|
192
|
+
"gemini-3.6-flash": "MODEL_PLACEHOLDER_M71",
|
|
193
|
+
"gemini-3.6-flash-high": "MODEL_PLACEHOLDER_M71",
|
|
194
|
+
"gemini-3.6-flash-medium": "MODEL_PLACEHOLDER_M72",
|
|
195
|
+
"gemini-3.6-flash-low": "MODEL_PLACEHOLDER_M73",
|
|
196
|
+
// Gemini 3.5 Flash
|
|
197
|
+
"gemini-3.5-flash": "MODEL_PLACEHOLDER_M84",
|
|
198
|
+
"gemini-3.5-flash-low": "MODEL_PLACEHOLDER_M20",
|
|
199
|
+
"gemini-3.5-flash-extra-low": "MODEL_PLACEHOLDER_M187",
|
|
200
|
+
"gemini-3-flash-agent": "MODEL_PLACEHOLDER_M84",
|
|
201
|
+
// Gemini 3.1 Pro
|
|
202
|
+
"gemini-3.1-pro": "MODEL_PLACEHOLDER_M16",
|
|
203
|
+
"gemini-3.1-pro-high": "MODEL_PLACEHOLDER_M37",
|
|
204
|
+
"gemini-3.1-pro-low": "MODEL_PLACEHOLDER_M36",
|
|
205
|
+
"gemini-pro-agent": "MODEL_PLACEHOLDER_M16",
|
|
206
|
+
// Claude
|
|
207
|
+
"claude-sonnet-4-6": "MODEL_PLACEHOLDER_M35",
|
|
208
|
+
"claude-opus-4-6": "MODEL_PLACEHOLDER_M26",
|
|
209
|
+
"claude-opus-4-6-thinking": "MODEL_PLACEHOLDER_M26",
|
|
210
|
+
// GPT-OSS
|
|
211
|
+
"gpt-oss-120b": "MODEL_OPENAI_GPT_OSS_120B_MEDIUM",
|
|
212
|
+
"gpt-oss-120b-medium": "MODEL_OPENAI_GPT_OSS_120B_MEDIUM"
|
|
213
|
+
};
|
|
214
|
+
function getThinkingConfig(modelId, effort) {
|
|
215
|
+
const isOff = effort === "off" || effort === "none";
|
|
216
|
+
if (isOff) {
|
|
217
|
+
return { includeThoughts: false, thinkingBudget: 0 };
|
|
218
|
+
}
|
|
219
|
+
if (modelId.includes("flash")) {
|
|
220
|
+
if (effort === "high" || effort === "xhigh" || modelId.endsWith("-high") || modelId.endsWith("-agent")) {
|
|
221
|
+
return { includeThoughts: true, thinkingBudget: -1 };
|
|
222
|
+
}
|
|
223
|
+
if (effort === "medium" || modelId.endsWith("-medium")) {
|
|
224
|
+
return { includeThoughts: true, thinkingBudget: 4e3 };
|
|
225
|
+
}
|
|
226
|
+
if (effort === "low" || effort === "minimal" || modelId.endsWith("-low")) {
|
|
227
|
+
return { includeThoughts: true, thinkingBudget: 1e3 };
|
|
228
|
+
}
|
|
229
|
+
return { includeThoughts: true, thinkingBudget: -1 };
|
|
230
|
+
}
|
|
231
|
+
if (modelId.includes("pro")) {
|
|
232
|
+
if (effort === "low" || effort === "minimal" || modelId.endsWith("-low")) {
|
|
233
|
+
return { includeThoughts: true, thinkingBudget: 1001 };
|
|
234
|
+
}
|
|
235
|
+
return { includeThoughts: true, thinkingBudget: 10001 };
|
|
236
|
+
}
|
|
237
|
+
if (modelId.startsWith("claude-")) {
|
|
238
|
+
return { includeThoughts: true, thinkingBudget: 1024 };
|
|
239
|
+
}
|
|
240
|
+
if (modelId.startsWith("gpt-oss-")) {
|
|
241
|
+
return { includeThoughts: true, thinkingBudget: 8192 };
|
|
242
|
+
}
|
|
243
|
+
return { includeThoughts: true, thinkingBudget: -1 };
|
|
244
|
+
}
|
|
245
|
+
var activeStore = {
|
|
246
|
+
enums: { ...STATIC_MODEL_ENUMS },
|
|
247
|
+
runtimeIds: [],
|
|
248
|
+
version: 0
|
|
249
|
+
};
|
|
250
|
+
function getCatalogSnapshot() {
|
|
251
|
+
return {
|
|
252
|
+
enums: { ...activeStore.enums },
|
|
253
|
+
runtimeIds: [...activeStore.runtimeIds],
|
|
254
|
+
version: activeStore.version
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function updateCatalogStore(enums, runtimeIds) {
|
|
258
|
+
activeStore = {
|
|
259
|
+
enums: { ...STATIC_MODEL_ENUMS, ...activeStore.enums, ...enums },
|
|
260
|
+
runtimeIds: [.../* @__PURE__ */ new Set([...activeStore.runtimeIds, ...runtimeIds])],
|
|
261
|
+
version: activeStore.version + 1
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function resolveRuntimeModelId(modelId, effort, availableRuntimeIds = activeStore.runtimeIds) {
|
|
265
|
+
if (modelId.endsWith("-high") || modelId.endsWith("-medium") || modelId.endsWith("-low") || modelId.endsWith("-thinking") || modelId.endsWith("-agent") || modelId.endsWith("-extra-low") || modelId.endsWith("-tiered")) {
|
|
266
|
+
return modelId;
|
|
267
|
+
}
|
|
268
|
+
const isOff = effort === "off" || effort === "none";
|
|
269
|
+
const isLow = isOff || effort === "low" || effort === "minimal";
|
|
270
|
+
const isMedium = effort === "medium";
|
|
271
|
+
if (modelId === "gemini-3.8-flash") {
|
|
272
|
+
if (isLow) return "gemini-3.8-flash-low";
|
|
273
|
+
if (isMedium) return "gemini-3.8-flash-medium";
|
|
274
|
+
return "gemini-3.8-flash-high";
|
|
275
|
+
}
|
|
276
|
+
if (modelId === "gemini-3.7-flash") {
|
|
277
|
+
if (isLow) return "gemini-3.7-flash-low";
|
|
278
|
+
if (isMedium) return "gemini-3.7-flash-medium";
|
|
279
|
+
return "gemini-3.7-flash-high";
|
|
280
|
+
}
|
|
281
|
+
if (modelId === "gemini-3.6-flash") {
|
|
282
|
+
if (isLow) return "gemini-3.6-flash-low";
|
|
283
|
+
if (isMedium) return "gemini-3.6-flash-medium";
|
|
284
|
+
return "gemini-3.6-flash-high";
|
|
285
|
+
}
|
|
286
|
+
if (modelId === "gemini-3.5-flash") {
|
|
287
|
+
if (isLow) return "gemini-3.5-flash-extra-low";
|
|
288
|
+
if (isMedium) return "gemini-3.5-flash-low";
|
|
289
|
+
return "gemini-3-flash-agent";
|
|
290
|
+
}
|
|
291
|
+
if (modelId === "gemini-3.1-pro") {
|
|
292
|
+
if (effort === "high" || effort === "xhigh") return "gemini-pro-agent";
|
|
293
|
+
return "gemini-3.1-pro-low";
|
|
294
|
+
}
|
|
295
|
+
if (modelId === "claude-opus-4-6") {
|
|
296
|
+
return "claude-opus-4-6-thinking";
|
|
297
|
+
}
|
|
298
|
+
if (modelId === "claude-sonnet-4-6") {
|
|
299
|
+
return "claude-sonnet-4-6";
|
|
300
|
+
}
|
|
301
|
+
if (modelId === "gpt-oss-120b") {
|
|
302
|
+
return "gpt-oss-120b-medium";
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(availableRuntimeIds) && availableRuntimeIds.length > 0) {
|
|
305
|
+
const candidates = [];
|
|
306
|
+
if (isLow) {
|
|
307
|
+
candidates.push(`${modelId}-low`, `${modelId}-extra-low`, modelId);
|
|
308
|
+
} else if (isMedium) {
|
|
309
|
+
candidates.push(`${modelId}-medium`, modelId);
|
|
310
|
+
} else {
|
|
311
|
+
candidates.push(`${modelId}-high`, `${modelId}-thinking`, `${modelId}-agent`, modelId);
|
|
312
|
+
}
|
|
313
|
+
for (const cand of candidates) {
|
|
314
|
+
if (availableRuntimeIds.includes(cand)) {
|
|
315
|
+
return cand;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (modelId.includes("flash")) {
|
|
320
|
+
if (isLow) return `${modelId}-low`;
|
|
321
|
+
if (isMedium) return `${modelId}-medium`;
|
|
322
|
+
return `${modelId}-high`;
|
|
323
|
+
}
|
|
324
|
+
if (modelId.includes("pro")) {
|
|
325
|
+
if (isLow) return `${modelId}-low`;
|
|
326
|
+
return `${modelId}-high`;
|
|
327
|
+
}
|
|
328
|
+
if (modelId.startsWith("claude-")) {
|
|
329
|
+
if (modelId.includes("opus")) return `${modelId}-thinking`;
|
|
330
|
+
return modelId;
|
|
331
|
+
}
|
|
332
|
+
if (modelId.startsWith("gpt-")) {
|
|
333
|
+
return `${modelId}-medium`;
|
|
334
|
+
}
|
|
335
|
+
return modelId;
|
|
336
|
+
}
|
|
337
|
+
function resolveModelPlan(publicModelId, effort, snapshot = getCatalogSnapshot()) {
|
|
338
|
+
const runtimeModelId = resolveRuntimeModelId(publicModelId, effort, snapshot.runtimeIds);
|
|
339
|
+
return {
|
|
340
|
+
runtimeModelId,
|
|
341
|
+
modelEnum: snapshot.enums[runtimeModelId] || "",
|
|
342
|
+
thinkingConfig: getThinkingConfig(runtimeModelId, effort),
|
|
343
|
+
isNonGemini: !runtimeModelId.startsWith("gemini-"),
|
|
344
|
+
isClaude: runtimeModelId.startsWith("claude-")
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function formatModelDisplayName(baseId, rawDisplayName) {
|
|
348
|
+
if (rawDisplayName) {
|
|
349
|
+
const cleaned = rawDisplayName.replace(/\s*\([^)]*\)/g, "").trim();
|
|
350
|
+
if (cleaned.length > 0) {
|
|
351
|
+
return cleaned;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const words = baseId.split("-").map((w) => {
|
|
355
|
+
if (/^\d+(\.\d+)?$/.test(w)) return w;
|
|
356
|
+
return w.charAt(0).toUpperCase() + w.slice(1);
|
|
357
|
+
});
|
|
358
|
+
return words.join(" ");
|
|
359
|
+
}
|
|
360
|
+
function estimateModelCost(baseId) {
|
|
361
|
+
if (baseId.includes("flash")) {
|
|
362
|
+
return { input: 0.1, output: 0.4, cacheRead: 0.025, cacheWrite: 0.1 };
|
|
363
|
+
}
|
|
364
|
+
if (baseId.includes("pro")) {
|
|
365
|
+
return { input: 1.25, output: 5, cacheRead: 0.3, cacheWrite: 1.25 };
|
|
366
|
+
}
|
|
367
|
+
if (baseId.startsWith("claude-opus")) {
|
|
368
|
+
return { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 };
|
|
369
|
+
}
|
|
370
|
+
if (baseId.startsWith("claude-")) {
|
|
371
|
+
return { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
|
|
372
|
+
}
|
|
373
|
+
if (baseId.startsWith("gpt-oss-")) {
|
|
374
|
+
return { input: 0.5, output: 2, cacheRead: 0.1, cacheWrite: 0.5 };
|
|
375
|
+
}
|
|
376
|
+
return { input: 1, output: 5, cacheRead: 0.25, cacheWrite: 1 };
|
|
377
|
+
}
|
|
378
|
+
function synthesizeDynamicModel(baseId, items) {
|
|
379
|
+
const repItem = items.find((it) => it.id === `${baseId}-high` || it.id === baseId) || items[0];
|
|
380
|
+
const isFlash = baseId.includes("flash");
|
|
381
|
+
const isClaude = baseId.startsWith("claude-");
|
|
382
|
+
const isGpt = baseId.startsWith("gpt-");
|
|
383
|
+
const defaultContext = isFlash ? 1048576 : isClaude ? 2e5 : isGpt ? 128e3 : 1048576;
|
|
384
|
+
const defaultMaxOutput = isGpt ? 32768 : 65536;
|
|
385
|
+
return {
|
|
386
|
+
id: baseId,
|
|
387
|
+
name: formatModelDisplayName(baseId, repItem?.displayName),
|
|
388
|
+
provider: PROVIDER_ID,
|
|
389
|
+
api: "antigravity-api",
|
|
390
|
+
baseUrl: DEFAULT_ENDPOINT,
|
|
391
|
+
reasoning: repItem?.supportsThinking ?? true,
|
|
392
|
+
input: repItem?.supportsImages ? ["text", "image"] : ["text"],
|
|
393
|
+
cost: estimateModelCost(baseId),
|
|
394
|
+
contextWindow: repItem?.maxTokens || defaultContext,
|
|
395
|
+
maxTokens: repItem?.maxOutputTokens || defaultMaxOutput
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function buildDynamicPublicModels(catalog) {
|
|
399
|
+
if (!catalog || !Array.isArray(catalog.models) || catalog.models.length === 0) {
|
|
400
|
+
return [];
|
|
401
|
+
}
|
|
402
|
+
const agentSortIds = catalog.agentModelSorts;
|
|
403
|
+
const eligibleItems = Array.isArray(agentSortIds) && agentSortIds.length > 0 ? catalog.models.filter((m) => agentSortIds.includes(m.id)) : catalog.models;
|
|
404
|
+
const runtimeGroups = /* @__PURE__ */ new Map();
|
|
405
|
+
for (const item of eligibleItems) {
|
|
406
|
+
const baseId = extractBaseModelId(item.id);
|
|
407
|
+
const list = runtimeGroups.get(baseId) || [];
|
|
408
|
+
list.push(item);
|
|
409
|
+
runtimeGroups.set(baseId, list);
|
|
410
|
+
}
|
|
411
|
+
const orderedBaseIds = [];
|
|
412
|
+
if (Array.isArray(agentSortIds)) {
|
|
413
|
+
for (const sortId of agentSortIds) {
|
|
414
|
+
const baseId = extractBaseModelId(sortId);
|
|
415
|
+
if (runtimeGroups.has(baseId) && !orderedBaseIds.includes(baseId)) {
|
|
416
|
+
orderedBaseIds.push(baseId);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
for (const baseId of runtimeGroups.keys()) {
|
|
421
|
+
if (!orderedBaseIds.includes(baseId)) {
|
|
422
|
+
orderedBaseIds.push(baseId);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return orderedBaseIds.map((baseId) => synthesizeDynamicModel(baseId, runtimeGroups.get(baseId)));
|
|
426
|
+
}
|
|
427
|
+
async function refreshCatalog(context) {
|
|
428
|
+
const storedEnums = context.stored?.["pi-provider-antigravity"]?.modelEnums;
|
|
429
|
+
const storedRuntimeIds = context.stored?.["pi-provider-antigravity"]?.runtimeIds;
|
|
430
|
+
if (storedEnums || storedRuntimeIds) {
|
|
431
|
+
updateCatalogStore(storedEnums || {}, storedRuntimeIds || []);
|
|
432
|
+
}
|
|
433
|
+
if (!context.allowNetwork) {
|
|
434
|
+
return context.stored?.models || [];
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
const apiKey = context.credential?.access;
|
|
438
|
+
if (!apiKey) {
|
|
439
|
+
return context.stored?.models || [];
|
|
440
|
+
}
|
|
441
|
+
const { token, projectId } = parseStoredCredentials(apiKey);
|
|
442
|
+
const catalog = await fetchAvailableModelsCatalog(token, projectId, DEFAULT_ENDPOINT, context.signal);
|
|
443
|
+
updateCatalogStore(catalog.modelEnums, catalog.models.map((m) => m.id));
|
|
444
|
+
const dynamicModels = buildDynamicPublicModels(catalog);
|
|
445
|
+
if (context.publish) {
|
|
446
|
+
await context.publish({
|
|
447
|
+
persist: {
|
|
448
|
+
models: dynamicModels,
|
|
449
|
+
checkedAt: Date.now(),
|
|
450
|
+
"pi-provider-antigravity": {
|
|
451
|
+
modelEnums: catalog.modelEnums,
|
|
452
|
+
runtimeIds: catalog.models.map((m) => m.id)
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return dynamicModels;
|
|
458
|
+
} catch {
|
|
459
|
+
return context.stored?.models || [];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function parseAvailableModels(data) {
|
|
463
|
+
const models = [];
|
|
464
|
+
const modelEnums = {};
|
|
465
|
+
const modelsObj = data.models || {};
|
|
466
|
+
for (const [id, info] of Object.entries(modelsObj)) {
|
|
467
|
+
if (id.startsWith("tab_") || id.startsWith("chat_")) continue;
|
|
468
|
+
const modelEnum = typeof info.model === "string" ? info.model : void 0;
|
|
469
|
+
if (modelEnum) {
|
|
470
|
+
modelEnums[id] = modelEnum;
|
|
471
|
+
}
|
|
472
|
+
const quotaInfo = info.quotaInfo || {};
|
|
473
|
+
models.push({
|
|
474
|
+
id,
|
|
475
|
+
displayName: info.displayName || info.label || id,
|
|
476
|
+
modelEnum,
|
|
477
|
+
remainingFraction: quotaInfo.remainingFraction,
|
|
478
|
+
resetTime: quotaInfo.resetTime,
|
|
479
|
+
supportsThinking: Boolean(info.supportsThinking),
|
|
480
|
+
supportsImages: Boolean(info.supportsImages),
|
|
481
|
+
maxTokens: typeof info.maxTokens === "number" ? info.maxTokens : void 0,
|
|
482
|
+
maxOutputTokens: typeof info.maxOutputTokens === "number" ? info.maxOutputTokens : void 0
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
const agentModelSorts = [];
|
|
486
|
+
if (Array.isArray(data.agentModelSorts)) {
|
|
487
|
+
for (const sortGroup of data.agentModelSorts) {
|
|
488
|
+
if (Array.isArray(sortGroup.groups)) {
|
|
489
|
+
for (const group of sortGroup.groups) {
|
|
490
|
+
if (Array.isArray(group.modelIds)) {
|
|
491
|
+
agentModelSorts.push(...group.modelIds);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
models.sort((a, b) => a.id.localeCompare(b.id));
|
|
498
|
+
return {
|
|
499
|
+
models,
|
|
500
|
+
modelEnums,
|
|
501
|
+
...agentModelSorts.length > 0 ? { agentModelSorts } : {}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
async function fetchAvailableModelsCatalog(token, projectId = "aicode-consumers", endpoint = DEFAULT_ENDPOINT, signal) {
|
|
505
|
+
const res = await fetch(`${endpoint}/v1internal:fetchAvailableModels`, {
|
|
506
|
+
method: "POST",
|
|
507
|
+
headers: buildAntigravityHeaders(token),
|
|
508
|
+
body: JSON.stringify({ project: projectId }),
|
|
509
|
+
signal
|
|
510
|
+
});
|
|
511
|
+
if (!res.ok) {
|
|
512
|
+
const errText = await res.text();
|
|
513
|
+
throw new Error(`Failed to fetch models (${res.status}): ${errText}`);
|
|
514
|
+
}
|
|
515
|
+
const json = await res.json();
|
|
516
|
+
return parseAvailableModels(json);
|
|
517
|
+
}
|
|
518
|
+
function formatTokenCount(n) {
|
|
519
|
+
if (typeof n !== "number") return "n/a";
|
|
520
|
+
if (n % 1048576 === 0) return `${n / 1048576}M`;
|
|
521
|
+
if (n % 1024 === 0) return `${n / 1024}k`;
|
|
522
|
+
if (n % 1e3 === 0) return `${n / 1e3}k`;
|
|
523
|
+
return `${Math.round(n / 1024)}k`;
|
|
524
|
+
}
|
|
525
|
+
function formatModelsList(catalog) {
|
|
526
|
+
const sorts = catalog.agentModelSorts;
|
|
527
|
+
const recommendedOnly = Array.isArray(sorts) && sorts.length > 0;
|
|
528
|
+
const rank = new Map((sorts || []).map((id, i) => [id, i]));
|
|
529
|
+
const models = (recommendedOnly ? catalog.models.filter((m) => rank.has(m.id)) : catalog.models.slice()).sort(
|
|
530
|
+
(a, b) => (rank.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b.id) ?? Number.MAX_SAFE_INTEGER) || a.id.localeCompare(b.id)
|
|
531
|
+
);
|
|
532
|
+
const rows = models.map((m) => {
|
|
533
|
+
const flags = [];
|
|
534
|
+
if (m.supportsThinking) flags.push("thinking");
|
|
535
|
+
if (m.supportsImages) flags.push("images");
|
|
536
|
+
return {
|
|
537
|
+
id: m.id,
|
|
538
|
+
name: formatModelDisplayName(m.id, m.displayName || m.id),
|
|
539
|
+
ctx: `${formatTokenCount(m.maxTokens)}/${formatTokenCount(m.maxOutputTokens)}`,
|
|
540
|
+
feat: flags.length > 0 ? flags.join(", ") : "-",
|
|
541
|
+
rem: typeof m.remainingFraction === "number" ? `${Math.round(m.remainingFraction * 100)}%` : "N/A"
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
const headers = { id: "Model", name: "Name", ctx: "Context", feat: "Features", rem: "Rem" };
|
|
545
|
+
const w = {
|
|
546
|
+
id: Math.max(headers.id.length, ...rows.map((r) => r.id.length)),
|
|
547
|
+
name: Math.max(headers.name.length, ...rows.map((r) => r.name.length)),
|
|
548
|
+
ctx: Math.max(headers.ctx.length, ...rows.map((r) => r.ctx.length)),
|
|
549
|
+
feat: Math.max(headers.feat.length, ...rows.map((r) => r.feat.length))
|
|
550
|
+
};
|
|
551
|
+
const lines = [
|
|
552
|
+
`Available Antigravity Models (${models.length}${recommendedOnly ? " recommended" : ""}):`,
|
|
553
|
+
` ${headers.id.padEnd(w.id)} ${headers.name.padEnd(w.name)} ${headers.ctx.padEnd(w.ctx)} ${headers.feat.padEnd(w.feat)} ${headers.rem}`
|
|
554
|
+
];
|
|
555
|
+
for (const r of rows) {
|
|
556
|
+
lines.push(
|
|
557
|
+
` ${r.id.padEnd(w.id)} ${r.name.padEnd(w.name)} ${r.ctx.padEnd(w.ctx)} ${r.feat.padEnd(w.feat)} ${r.rem.padStart(4)}`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
return lines.join("\n");
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/stream.ts
|
|
564
|
+
import {
|
|
565
|
+
createAssistantMessageEventStream
|
|
566
|
+
} from "@earendil-works/pi-ai";
|
|
567
|
+
|
|
568
|
+
// src/parser.ts
|
|
569
|
+
function createSseFeed() {
|
|
570
|
+
const content = [];
|
|
571
|
+
const usage = {
|
|
572
|
+
input: 0,
|
|
573
|
+
output: 0,
|
|
574
|
+
cacheRead: 0,
|
|
575
|
+
total: 0
|
|
576
|
+
};
|
|
577
|
+
let stopReason = "stop";
|
|
578
|
+
let lastThoughtSignature;
|
|
579
|
+
let buffer = "";
|
|
580
|
+
let openType = null;
|
|
581
|
+
const snapshot = (events) => ({
|
|
582
|
+
events,
|
|
583
|
+
usage: { ...usage },
|
|
584
|
+
stopReason,
|
|
585
|
+
...lastThoughtSignature ? { thoughtSignature: lastThoughtSignature } : {}
|
|
586
|
+
});
|
|
587
|
+
const closeOpenBlock = (events) => {
|
|
588
|
+
if (openType === null) return;
|
|
589
|
+
const index = content.length - 1;
|
|
590
|
+
const block = content[index];
|
|
591
|
+
if (openType === "thinking") {
|
|
592
|
+
events.push({ kind: "thinking_end", index, content: block.thinking || "" });
|
|
593
|
+
} else {
|
|
594
|
+
events.push({ kind: "text_end", index, content: block.text || "" });
|
|
595
|
+
}
|
|
596
|
+
openType = null;
|
|
597
|
+
};
|
|
598
|
+
const processLine = (rawLine, events) => {
|
|
599
|
+
const line = rawLine.trim();
|
|
600
|
+
if (!line.startsWith("data:")) return;
|
|
601
|
+
const dataStr = line.slice(5).trim();
|
|
602
|
+
if (!dataStr || dataStr === "[DONE]") return;
|
|
603
|
+
let payload;
|
|
604
|
+
try {
|
|
605
|
+
payload = JSON.parse(dataStr);
|
|
606
|
+
} catch {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const response = payload.response || payload;
|
|
610
|
+
if (response.usageMetadata) {
|
|
611
|
+
const um = response.usageMetadata;
|
|
612
|
+
const promptTokens = typeof um.promptTokenCount === "number" ? um.promptTokenCount : 0;
|
|
613
|
+
const cacheRead = typeof um.cachedContentTokenCount === "number" ? um.cachedContentTokenCount : 0;
|
|
614
|
+
usage.input = Math.max(0, promptTokens - cacheRead);
|
|
615
|
+
usage.cacheRead = cacheRead;
|
|
616
|
+
if (typeof um.candidatesTokenCount === "number") usage.output = um.candidatesTokenCount;
|
|
617
|
+
if (typeof um.totalTokenCount === "number") usage.total = um.totalTokenCount;
|
|
618
|
+
}
|
|
619
|
+
const candidate = response.candidates?.[0];
|
|
620
|
+
if (!candidate) return;
|
|
621
|
+
if (candidate.finishReason === "MAX_TOKENS") {
|
|
622
|
+
stopReason = "length";
|
|
623
|
+
} else if (candidate.finishReason === "SAFETY") {
|
|
624
|
+
stopReason = "error";
|
|
625
|
+
}
|
|
626
|
+
const parts = candidate.content?.parts || [];
|
|
627
|
+
for (const part of parts) {
|
|
628
|
+
if (part.thoughtSignature) {
|
|
629
|
+
lastThoughtSignature = part.thoughtSignature;
|
|
630
|
+
}
|
|
631
|
+
if (part.thought) {
|
|
632
|
+
if (openType !== "thinking") {
|
|
633
|
+
closeOpenBlock(events);
|
|
634
|
+
content.push({ type: "thinking", thinking: "" });
|
|
635
|
+
events.push({ kind: "thinking_start", index: content.length - 1 });
|
|
636
|
+
openType = "thinking";
|
|
637
|
+
}
|
|
638
|
+
const block = content[content.length - 1];
|
|
639
|
+
const delta = part.text || "";
|
|
640
|
+
block.thinking = (block.thinking || "") + delta;
|
|
641
|
+
if (part.thoughtSignature) {
|
|
642
|
+
block.thoughtSignature = part.thoughtSignature;
|
|
643
|
+
}
|
|
644
|
+
events.push({
|
|
645
|
+
kind: "thinking_delta",
|
|
646
|
+
index: content.length - 1,
|
|
647
|
+
delta,
|
|
648
|
+
...part.thoughtSignature ? { thoughtSignature: part.thoughtSignature } : {}
|
|
649
|
+
});
|
|
650
|
+
} else if (part.functionCall) {
|
|
651
|
+
closeOpenBlock(events);
|
|
652
|
+
stopReason = "toolUse";
|
|
653
|
+
const block = {
|
|
654
|
+
type: "toolCall",
|
|
655
|
+
id: part.functionCall.id || `call_${content.length}`,
|
|
656
|
+
name: part.functionCall.name,
|
|
657
|
+
arguments: part.functionCall.args || {},
|
|
658
|
+
thoughtSignature: part.thoughtSignature || lastThoughtSignature
|
|
659
|
+
};
|
|
660
|
+
content.push(block);
|
|
661
|
+
events.push({ kind: "toolCall", index: content.length - 1, block });
|
|
662
|
+
} else if (part.text !== void 0) {
|
|
663
|
+
if (openType !== "text") {
|
|
664
|
+
closeOpenBlock(events);
|
|
665
|
+
content.push({ type: "text", text: "" });
|
|
666
|
+
events.push({ kind: "text_start", index: content.length - 1 });
|
|
667
|
+
openType = "text";
|
|
668
|
+
}
|
|
669
|
+
const block = content[content.length - 1];
|
|
670
|
+
block.text = (block.text || "") + part.text;
|
|
671
|
+
events.push({ kind: "text_delta", index: content.length - 1, delta: part.text });
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
return {
|
|
676
|
+
feed(chunk) {
|
|
677
|
+
const events = [];
|
|
678
|
+
buffer += chunk;
|
|
679
|
+
const lines = buffer.split("\n");
|
|
680
|
+
buffer = lines.pop() || "";
|
|
681
|
+
for (const rawLine of lines) processLine(rawLine, events);
|
|
682
|
+
return snapshot(events);
|
|
683
|
+
},
|
|
684
|
+
close() {
|
|
685
|
+
const events = [];
|
|
686
|
+
if (buffer.trim() !== "") processLine(buffer, events);
|
|
687
|
+
buffer = "";
|
|
688
|
+
closeOpenBlock(events);
|
|
689
|
+
return snapshot(events);
|
|
690
|
+
},
|
|
691
|
+
result() {
|
|
692
|
+
return {
|
|
693
|
+
content,
|
|
694
|
+
usage: { ...usage },
|
|
695
|
+
stopReason
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/builder.ts
|
|
702
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
703
|
+
var BASE64_SIGNATURE_PATTERN = /^[A-Za-z0-9+/_-]+={0,2}$/;
|
|
704
|
+
function isValidThoughtSignature(sig) {
|
|
705
|
+
if (typeof sig !== "string" || sig.length === 0) return false;
|
|
706
|
+
if (sig.includes("=") && sig.length % 4 !== 0) return false;
|
|
707
|
+
return BASE64_SIGNATURE_PATTERN.test(sig);
|
|
708
|
+
}
|
|
709
|
+
function resolveThoughtSignature(isSameProviderAndModel, sig) {
|
|
710
|
+
if (!isSameProviderAndModel) return void 0;
|
|
711
|
+
return isValidThoughtSignature(sig) ? sig : void 0;
|
|
712
|
+
}
|
|
713
|
+
function isCompatibleModelFamily(msgModel, targetModelId) {
|
|
714
|
+
if (!msgModel || !targetModelId) return true;
|
|
715
|
+
if (msgModel === targetModelId) return true;
|
|
716
|
+
const isMsgGemini = msgModel.startsWith("gemini-");
|
|
717
|
+
const isTargetGemini = targetModelId.startsWith("gemini-");
|
|
718
|
+
if (isMsgGemini && isTargetGemini) return true;
|
|
719
|
+
const isMsgClaude = msgModel.startsWith("claude-");
|
|
720
|
+
const isTargetClaude = targetModelId.startsWith("claude-");
|
|
721
|
+
if (isMsgClaude && isTargetClaude) return true;
|
|
722
|
+
const isMsgGpt = msgModel.startsWith("gpt-");
|
|
723
|
+
const isTargetGpt = targetModelId.startsWith("gpt-");
|
|
724
|
+
if (isMsgGpt && isTargetGpt) return true;
|
|
725
|
+
return extractBaseModelId(msgModel) === extractBaseModelId(targetModelId);
|
|
726
|
+
}
|
|
727
|
+
function deriveSessionId(context, explicitSessionId) {
|
|
728
|
+
if (explicitSessionId && typeof explicitSessionId === "string" && explicitSessionId.trim().length > 0) {
|
|
729
|
+
return explicitSessionId.trim();
|
|
730
|
+
}
|
|
731
|
+
const firstMsg = context.messages?.[0];
|
|
732
|
+
if (!firstMsg) {
|
|
733
|
+
return randomUUID();
|
|
734
|
+
}
|
|
735
|
+
const seedContent = typeof firstMsg.content === "string" ? firstMsg.content : JSON.stringify(firstMsg.content || "");
|
|
736
|
+
const hash = createHash2("sha256").update(`antigravity:conversation:${seedContent}`).digest().subarray(0, 16);
|
|
737
|
+
hash[6] = hash[6] & 15 | 64;
|
|
738
|
+
hash[8] = hash[8] & 63 | 128;
|
|
739
|
+
const hex = [...hash].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
740
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
741
|
+
}
|
|
742
|
+
function resolveSessionTrajectory(sessionId) {
|
|
743
|
+
const bytes = createHash2("sha1").update(`antigravity:${sessionId}`).digest().subarray(0, 16);
|
|
744
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
745
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
746
|
+
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
747
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
748
|
+
}
|
|
749
|
+
function resolveNumericSessionId(sessionId) {
|
|
750
|
+
if (/^-?\d+$/.test(sessionId)) {
|
|
751
|
+
return sessionId;
|
|
752
|
+
}
|
|
753
|
+
const hash = createHash2("sha256").update(`antigravity:session:${sessionId}`).digest();
|
|
754
|
+
return hash.readBigInt64BE(0).toString();
|
|
755
|
+
}
|
|
756
|
+
function normalizeToolCallId(id) {
|
|
757
|
+
if (!id || typeof id !== "string") return id;
|
|
758
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
759
|
+
}
|
|
760
|
+
var CUSTOM_TOOL_SCHEMA_ALLOW = /* @__PURE__ */ new Set([
|
|
761
|
+
"type",
|
|
762
|
+
"description",
|
|
763
|
+
"properties",
|
|
764
|
+
"required",
|
|
765
|
+
"items",
|
|
766
|
+
"enum"
|
|
767
|
+
]);
|
|
768
|
+
function stripMetaSchema(schema) {
|
|
769
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return schema;
|
|
770
|
+
const omit = /* @__PURE__ */ new Set(["$schema", "$id", "$defs", "definitions"]);
|
|
771
|
+
const out = {};
|
|
772
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
773
|
+
if (!omit.has(key)) out[key] = stripMetaSchema(value);
|
|
774
|
+
}
|
|
775
|
+
return out;
|
|
776
|
+
}
|
|
777
|
+
function normalizeCustomToolSchema(schema) {
|
|
778
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
779
|
+
if (Array.isArray(schema)) return schema.map(normalizeCustomToolSchema);
|
|
780
|
+
const s = schema;
|
|
781
|
+
const out = {};
|
|
782
|
+
for (const [key, value] of Object.entries(s)) {
|
|
783
|
+
if (!CUSTOM_TOOL_SCHEMA_ALLOW.has(key)) {
|
|
784
|
+
if (key === "const" && s.enum === void 0 && typeof value === "string") {
|
|
785
|
+
out.enum = [value];
|
|
786
|
+
}
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (key === "type" && Array.isArray(value)) {
|
|
790
|
+
const scalar = value.find((e) => typeof e === "string" && e !== "null");
|
|
791
|
+
if (scalar) out.type = scalar;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
if (key === "properties" && value && typeof value === "object" && !Array.isArray(value)) {
|
|
795
|
+
const props = {};
|
|
796
|
+
for (const [propName, propSchema] of Object.entries(value)) {
|
|
797
|
+
props[propName] = normalizeCustomToolSchema(propSchema);
|
|
798
|
+
}
|
|
799
|
+
out.properties = props;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (key === "enum" && Array.isArray(value) && !value.every((e) => typeof e === "string")) {
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
out[key] = normalizeCustomToolSchema(value);
|
|
806
|
+
}
|
|
807
|
+
return out;
|
|
808
|
+
}
|
|
809
|
+
function convertTools(tools, useLegacyParameters = false) {
|
|
810
|
+
if (!tools?.length) return void 0;
|
|
811
|
+
return [
|
|
812
|
+
{
|
|
813
|
+
functionDeclarations: tools.map((tool) => {
|
|
814
|
+
const schema = stripMetaSchema(tool.parameters) || { type: "object", properties: {} };
|
|
815
|
+
return {
|
|
816
|
+
name: tool.name,
|
|
817
|
+
description: tool.description,
|
|
818
|
+
...useLegacyParameters ? { parameters: normalizeCustomToolSchema(schema) } : { parametersJsonSchema: schema }
|
|
819
|
+
};
|
|
820
|
+
})
|
|
821
|
+
}
|
|
822
|
+
];
|
|
823
|
+
}
|
|
824
|
+
function translateTurnTrace(context, runtimeModelId) {
|
|
825
|
+
const contents = [];
|
|
826
|
+
const messages = context.messages || [];
|
|
827
|
+
for (const msg of messages) {
|
|
828
|
+
if (msg.role === "user") {
|
|
829
|
+
const parts = [];
|
|
830
|
+
if (typeof msg.content === "string") {
|
|
831
|
+
parts.push({ text: msg.content });
|
|
832
|
+
} else if (Array.isArray(msg.content)) {
|
|
833
|
+
for (const item of msg.content) {
|
|
834
|
+
if (item.type === "text") {
|
|
835
|
+
parts.push({ text: item.text });
|
|
836
|
+
} else if (item.type === "image") {
|
|
837
|
+
parts.push({
|
|
838
|
+
inlineData: {
|
|
839
|
+
mimeType: item.mimeType || "image/png",
|
|
840
|
+
data: item.data
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (parts.length > 0) {
|
|
847
|
+
contents.push({ role: "user", parts });
|
|
848
|
+
}
|
|
849
|
+
} else if (msg.role === "assistant") {
|
|
850
|
+
if (msg.stopReason === "error" || msg.stopReason === "aborted") {
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
const isSameProvider = !msg.provider || msg.provider === PROVIDER_ID;
|
|
854
|
+
const isSameModel = isCompatibleModelFamily(msg.model, runtimeModelId);
|
|
855
|
+
const isSameProviderAndModel = isSameProvider && isSameModel;
|
|
856
|
+
const parts = [];
|
|
857
|
+
if (typeof msg.content === "string") {
|
|
858
|
+
parts.push({ text: msg.content });
|
|
859
|
+
} else if (Array.isArray(msg.content)) {
|
|
860
|
+
for (const item of msg.content) {
|
|
861
|
+
if (item.type === "text") {
|
|
862
|
+
const part = { text: item.text };
|
|
863
|
+
const sig = resolveThoughtSignature(
|
|
864
|
+
isSameProviderAndModel,
|
|
865
|
+
item.thoughtSignature || item.textSignature
|
|
866
|
+
);
|
|
867
|
+
if (sig) {
|
|
868
|
+
part.thoughtSignature = sig;
|
|
869
|
+
}
|
|
870
|
+
parts.push(part);
|
|
871
|
+
} else if (item.type === "thinking") {
|
|
872
|
+
const candidateSig = item.thoughtSignature || item.thinkingSignature || msg.thoughtSignature;
|
|
873
|
+
const isForeignReasoning = !isSameProviderAndModel || typeof candidateSig === "string" && candidateSig.trim().startsWith("{");
|
|
874
|
+
if (!isForeignReasoning) {
|
|
875
|
+
const part = {
|
|
876
|
+
thought: true,
|
|
877
|
+
text: item.thinking || ""
|
|
878
|
+
};
|
|
879
|
+
const sig = resolveThoughtSignature(true, candidateSig);
|
|
880
|
+
if (sig) {
|
|
881
|
+
part.thoughtSignature = sig;
|
|
882
|
+
}
|
|
883
|
+
parts.push(part);
|
|
884
|
+
} else {
|
|
885
|
+
let text = item.thinking || "";
|
|
886
|
+
if (!text && typeof candidateSig === "string" && candidateSig.trim().startsWith("{")) {
|
|
887
|
+
try {
|
|
888
|
+
const parsed = JSON.parse(candidateSig);
|
|
889
|
+
if (Array.isArray(parsed.summary)) {
|
|
890
|
+
text = parsed.summary.filter((s) => s && typeof s.text === "string").map((s) => s.text).join("\n");
|
|
891
|
+
}
|
|
892
|
+
} catch {
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (text && text.trim() !== "") {
|
|
896
|
+
parts.push({ text });
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
} else if (item.type === "tool_use" || item.type === "toolCall") {
|
|
900
|
+
const part = {
|
|
901
|
+
functionCall: {
|
|
902
|
+
id: normalizeToolCallId(item.id),
|
|
903
|
+
name: item.name,
|
|
904
|
+
args: item.input || item.arguments || {}
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const sig = resolveThoughtSignature(
|
|
908
|
+
isSameProviderAndModel,
|
|
909
|
+
item.thoughtSignature || msg.thoughtSignature
|
|
910
|
+
);
|
|
911
|
+
if (sig) {
|
|
912
|
+
part.thoughtSignature = sig;
|
|
913
|
+
}
|
|
914
|
+
parts.push(part);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (parts.length > 0) {
|
|
919
|
+
const msgSig = resolveThoughtSignature(
|
|
920
|
+
isSameProviderAndModel,
|
|
921
|
+
msg.thoughtSignature
|
|
922
|
+
);
|
|
923
|
+
if (msgSig && !parts.some((p) => p.thoughtSignature)) {
|
|
924
|
+
parts[parts.length - 1].thoughtSignature = msgSig;
|
|
925
|
+
}
|
|
926
|
+
contents.push({ role: "model", parts });
|
|
927
|
+
}
|
|
928
|
+
} else if (msg.role === "toolResult") {
|
|
929
|
+
let toolName = msg.toolName || msg.name;
|
|
930
|
+
if (!toolName && (msg.toolCallId || msg.id)) {
|
|
931
|
+
const rawTargetId = msg.toolCallId || msg.id;
|
|
932
|
+
const normTargetId = normalizeToolCallId(rawTargetId);
|
|
933
|
+
for (const prev of messages) {
|
|
934
|
+
if (prev.role === "assistant" && Array.isArray(prev.content)) {
|
|
935
|
+
const found = prev.content.find(
|
|
936
|
+
(c) => (c.type === "tool_use" || c.type === "toolCall") && (c.id === rawTargetId || normalizeToolCallId(c.id) === normTargetId)
|
|
937
|
+
);
|
|
938
|
+
if (found) {
|
|
939
|
+
toolName = found.name;
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
let toolText = "";
|
|
946
|
+
const imageParts = [];
|
|
947
|
+
if (Array.isArray(msg.content)) {
|
|
948
|
+
const textParts = [];
|
|
949
|
+
for (const c of msg.content) {
|
|
950
|
+
if (typeof c === "string") {
|
|
951
|
+
textParts.push(c);
|
|
952
|
+
} else if (c.type === "text") {
|
|
953
|
+
textParts.push(c.text);
|
|
954
|
+
} else if (c.type === "image") {
|
|
955
|
+
imageParts.push({
|
|
956
|
+
inlineData: {
|
|
957
|
+
mimeType: c.mimeType || "image/png",
|
|
958
|
+
data: c.data
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
} else {
|
|
962
|
+
textParts.push(JSON.stringify(c));
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
toolText = textParts.join("\n");
|
|
966
|
+
} else if (typeof msg.content === "string") {
|
|
967
|
+
toolText = msg.content;
|
|
968
|
+
} else if (msg.content) {
|
|
969
|
+
toolText = JSON.stringify(msg.content);
|
|
970
|
+
}
|
|
971
|
+
const responseObj = { output: toolText };
|
|
972
|
+
const functionResponse = {
|
|
973
|
+
name: toolName || "tool",
|
|
974
|
+
response: responseObj
|
|
975
|
+
};
|
|
976
|
+
const rawCallId = msg.toolCallId || msg.id;
|
|
977
|
+
const callId = normalizeToolCallId(rawCallId);
|
|
978
|
+
if (callId) {
|
|
979
|
+
functionResponse.id = callId;
|
|
980
|
+
}
|
|
981
|
+
if (imageParts.length > 0) {
|
|
982
|
+
functionResponse.parts = imageParts;
|
|
983
|
+
}
|
|
984
|
+
contents.push({
|
|
985
|
+
role: "model",
|
|
986
|
+
parts: [{ functionResponse }]
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (contents.length > 0) {
|
|
991
|
+
const lastContent = contents[contents.length - 1];
|
|
992
|
+
if (lastContent.role === "model" && !lastContent.parts?.some((p) => p.functionResponse)) {
|
|
993
|
+
contents.push({
|
|
994
|
+
role: "user",
|
|
995
|
+
parts: [{ text: "continue" }]
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return contents;
|
|
1000
|
+
}
|
|
1001
|
+
function buildAntigravityRequestBody(params) {
|
|
1002
|
+
const {
|
|
1003
|
+
projectId,
|
|
1004
|
+
plan,
|
|
1005
|
+
context,
|
|
1006
|
+
maxOutputTokens = 65536
|
|
1007
|
+
} = params;
|
|
1008
|
+
const runtimeModelId = plan.runtimeModelId;
|
|
1009
|
+
const sessionId = deriveSessionId(context, params.sessionId);
|
|
1010
|
+
const trajectoryId = params.trajectoryId && typeof params.trajectoryId === "string" && params.trajectoryId.trim().length > 0 ? params.trajectoryId.trim() : resolveSessionTrajectory(sessionId);
|
|
1011
|
+
const contents = translateTurnTrace(context, runtimeModelId);
|
|
1012
|
+
const requestIndex = context.messages.filter((m) => m.role === "assistant").length;
|
|
1013
|
+
const requestId = `agent/${sessionId}/${Date.now()}/${trajectoryId}/${contents.length}`;
|
|
1014
|
+
const modelEnum = plan.modelEnum;
|
|
1015
|
+
const labels = {
|
|
1016
|
+
last_step_index: String(Math.max(0, contents.length - 1)),
|
|
1017
|
+
model_enum: modelEnum,
|
|
1018
|
+
request_id: `${trajectoryId}-${requestIndex}`,
|
|
1019
|
+
trajectory_id: trajectoryId,
|
|
1020
|
+
used_claude: String(plan.isClaude),
|
|
1021
|
+
used_claude_conservative: String(plan.isClaude),
|
|
1022
|
+
used_non_gemini_model: String(plan.isNonGemini)
|
|
1023
|
+
};
|
|
1024
|
+
const generationConfig = {
|
|
1025
|
+
maxOutputTokens,
|
|
1026
|
+
thinkingConfig: plan.thinkingConfig
|
|
1027
|
+
};
|
|
1028
|
+
const request = {
|
|
1029
|
+
contents
|
|
1030
|
+
};
|
|
1031
|
+
if (context.systemPrompt) {
|
|
1032
|
+
request.systemInstruction = {
|
|
1033
|
+
role: "user",
|
|
1034
|
+
parts: [{ text: context.systemPrompt }]
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
const tools = convertTools(context.tools, plan.isNonGemini);
|
|
1038
|
+
if (tools) {
|
|
1039
|
+
request.tools = tools;
|
|
1040
|
+
}
|
|
1041
|
+
if (params.toolChoice && params.toolChoice !== "auto" && params.toolChoice !== "Auto") {
|
|
1042
|
+
let mode = "AUTO";
|
|
1043
|
+
const tc = String(params.toolChoice).toLowerCase();
|
|
1044
|
+
if (tc === "none") mode = "NONE";
|
|
1045
|
+
else if (tc === "any" || tc === "required") mode = "ANY";
|
|
1046
|
+
request.toolConfig = {
|
|
1047
|
+
functionCallingConfig: {
|
|
1048
|
+
mode
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
request.labels = labels;
|
|
1053
|
+
request.generationConfig = generationConfig;
|
|
1054
|
+
request.sessionId = resolveNumericSessionId(sessionId);
|
|
1055
|
+
return {
|
|
1056
|
+
project: projectId,
|
|
1057
|
+
requestId,
|
|
1058
|
+
request,
|
|
1059
|
+
model: runtimeModelId,
|
|
1060
|
+
userAgent: "antigravity",
|
|
1061
|
+
requestType: "agent"
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// src/stream.ts
|
|
1066
|
+
function streamAntigravity(model, context, options) {
|
|
1067
|
+
const stream = createAssistantMessageEventStream();
|
|
1068
|
+
(async () => {
|
|
1069
|
+
const output = {
|
|
1070
|
+
role: "assistant",
|
|
1071
|
+
content: [],
|
|
1072
|
+
api: model.api,
|
|
1073
|
+
provider: model.provider,
|
|
1074
|
+
model: model.id,
|
|
1075
|
+
usage: {
|
|
1076
|
+
input: 0,
|
|
1077
|
+
output: 0,
|
|
1078
|
+
cacheRead: 0,
|
|
1079
|
+
cacheWrite: 0,
|
|
1080
|
+
totalTokens: 0,
|
|
1081
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
1082
|
+
},
|
|
1083
|
+
stopReason: "pending",
|
|
1084
|
+
timestamp: Date.now()
|
|
1085
|
+
};
|
|
1086
|
+
try {
|
|
1087
|
+
stream.push({ type: "start", partial: output });
|
|
1088
|
+
const rawApiKey = options?.apiKey;
|
|
1089
|
+
if (!rawApiKey) {
|
|
1090
|
+
throw new Error("Missing Antigravity credentials. Run /login antigravity first.");
|
|
1091
|
+
}
|
|
1092
|
+
const { token, projectId } = parseStoredCredentials(rawApiKey);
|
|
1093
|
+
const effort = options?.reasoning || options?.reasoningEffort || options?.thinking;
|
|
1094
|
+
const snapshot = getCatalogSnapshot();
|
|
1095
|
+
const plan = resolveModelPlan(model.id, effort, snapshot);
|
|
1096
|
+
const requestBody = buildAntigravityRequestBody({
|
|
1097
|
+
projectId,
|
|
1098
|
+
plan,
|
|
1099
|
+
context,
|
|
1100
|
+
sessionId: options?.sessionId,
|
|
1101
|
+
trajectoryId: options?.trajectoryId,
|
|
1102
|
+
maxOutputTokens: model.maxTokens,
|
|
1103
|
+
toolChoice: options?.toolChoice
|
|
1104
|
+
});
|
|
1105
|
+
const res = await fetch(`${DEFAULT_ENDPOINT}/v1internal:streamGenerateContent?alt=sse`, {
|
|
1106
|
+
method: "POST",
|
|
1107
|
+
headers: buildAntigravityHeaders(token),
|
|
1108
|
+
body: JSON.stringify(requestBody),
|
|
1109
|
+
signal: options?.signal
|
|
1110
|
+
});
|
|
1111
|
+
if (!res.ok) {
|
|
1112
|
+
const errText = await res.text();
|
|
1113
|
+
throw new Error(`Antigravity stream failed (${res.status}): ${errText}`);
|
|
1114
|
+
}
|
|
1115
|
+
if (!res.body) {
|
|
1116
|
+
throw new Error("No response stream received from Antigravity.");
|
|
1117
|
+
}
|
|
1118
|
+
const reader = res.body.getReader();
|
|
1119
|
+
const decoder = new TextDecoder();
|
|
1120
|
+
const feed = createSseFeed();
|
|
1121
|
+
const applyUsage = (u) => {
|
|
1122
|
+
output.usage.input = u.input;
|
|
1123
|
+
output.usage.cacheRead = u.cacheRead;
|
|
1124
|
+
output.usage.output = u.output;
|
|
1125
|
+
output.usage.totalTokens = u.total;
|
|
1126
|
+
if (model.cost) {
|
|
1127
|
+
const c = model.cost;
|
|
1128
|
+
const inputCost = output.usage.input * (c.input || 0) / 1e6;
|
|
1129
|
+
const outputCost = output.usage.output * (c.output || 0) / 1e6;
|
|
1130
|
+
const cacheCost = output.usage.cacheRead * (c.cacheRead || 0) / 1e6;
|
|
1131
|
+
output.usage.cost = {
|
|
1132
|
+
input: inputCost,
|
|
1133
|
+
output: outputCost,
|
|
1134
|
+
cacheRead: cacheCost,
|
|
1135
|
+
cacheWrite: 0,
|
|
1136
|
+
total: inputCost + outputCost + cacheCost
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
const applyStopReason = (r) => {
|
|
1141
|
+
if (r === "stop") {
|
|
1142
|
+
if (output.stopReason === "pending") output.stopReason = "stop";
|
|
1143
|
+
} else {
|
|
1144
|
+
output.stopReason = r;
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
const translate = (events) => {
|
|
1148
|
+
for (const ev of events) {
|
|
1149
|
+
if (ev.kind === "text_start" || ev.kind === "thinking_start") {
|
|
1150
|
+
const isThinking = ev.kind === "thinking_start";
|
|
1151
|
+
output.content.push(
|
|
1152
|
+
isThinking ? { type: "thinking", thinking: "" } : { type: "text", text: "" }
|
|
1153
|
+
);
|
|
1154
|
+
stream.push(
|
|
1155
|
+
isThinking ? { type: "thinking_start", contentIndex: ev.index, partial: output } : { type: "text_start", contentIndex: ev.index, partial: output }
|
|
1156
|
+
);
|
|
1157
|
+
} else if (ev.kind === "text_delta" || ev.kind === "thinking_delta") {
|
|
1158
|
+
const block = output.content[ev.index];
|
|
1159
|
+
if (ev.kind === "text_delta") {
|
|
1160
|
+
block.text += ev.delta;
|
|
1161
|
+
stream.push({ type: "text_delta", contentIndex: ev.index, delta: ev.delta, partial: output });
|
|
1162
|
+
} else {
|
|
1163
|
+
block.thinking += ev.delta;
|
|
1164
|
+
if (ev.thoughtSignature) block.thoughtSignature = ev.thoughtSignature;
|
|
1165
|
+
stream.push({ type: "thinking_delta", contentIndex: ev.index, delta: ev.delta, partial: output });
|
|
1166
|
+
}
|
|
1167
|
+
} else if (ev.kind === "text_end" || ev.kind === "thinking_end") {
|
|
1168
|
+
stream.push(
|
|
1169
|
+
ev.kind === "text_end" ? { type: "text_end", contentIndex: ev.index, content: ev.content, partial: output } : { type: "thinking_end", contentIndex: ev.index, content: ev.content, partial: output }
|
|
1170
|
+
);
|
|
1171
|
+
} else if (ev.kind === "toolCall") {
|
|
1172
|
+
const toolCall = {
|
|
1173
|
+
type: "toolCall",
|
|
1174
|
+
id: ev.block.id || `call_${output.content.length}`,
|
|
1175
|
+
name: ev.block.name,
|
|
1176
|
+
arguments: ev.block.arguments || {},
|
|
1177
|
+
thoughtSignature: ev.block.thoughtSignature
|
|
1178
|
+
};
|
|
1179
|
+
output.content.push(toolCall);
|
|
1180
|
+
stream.push({ type: "toolcall_start", contentIndex: ev.index, partial: output });
|
|
1181
|
+
stream.push({
|
|
1182
|
+
type: "toolcall_delta",
|
|
1183
|
+
contentIndex: ev.index,
|
|
1184
|
+
delta: JSON.stringify(toolCall.arguments),
|
|
1185
|
+
partial: output
|
|
1186
|
+
});
|
|
1187
|
+
stream.push({
|
|
1188
|
+
type: "toolcall_end",
|
|
1189
|
+
contentIndex: ev.index,
|
|
1190
|
+
toolCall,
|
|
1191
|
+
partial: output
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
while (true) {
|
|
1197
|
+
const { done, value } = await reader.read();
|
|
1198
|
+
if (done) break;
|
|
1199
|
+
const fed = feed.feed(decoder.decode(value, { stream: true }));
|
|
1200
|
+
applyUsage(fed.usage);
|
|
1201
|
+
applyStopReason(fed.stopReason);
|
|
1202
|
+
translate(fed.events);
|
|
1203
|
+
}
|
|
1204
|
+
const closing = feed.close();
|
|
1205
|
+
applyUsage(closing.usage);
|
|
1206
|
+
applyStopReason(closing.stopReason);
|
|
1207
|
+
translate(closing.events);
|
|
1208
|
+
if (closing.thoughtSignature) {
|
|
1209
|
+
const thinkingBlock = output.content.find((c) => c.type === "thinking");
|
|
1210
|
+
if (thinkingBlock && !thinkingBlock.thoughtSignature) {
|
|
1211
|
+
thinkingBlock.thoughtSignature = closing.thoughtSignature;
|
|
1212
|
+
}
|
|
1213
|
+
output.thoughtSignature = closing.thoughtSignature;
|
|
1214
|
+
}
|
|
1215
|
+
const doneReason = output.stopReason === "toolUse" || output.stopReason === "length" ? output.stopReason : "stop";
|
|
1216
|
+
stream.push({
|
|
1217
|
+
type: "done",
|
|
1218
|
+
reason: doneReason,
|
|
1219
|
+
message: output
|
|
1220
|
+
});
|
|
1221
|
+
stream.end();
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
1224
|
+
output.errorMessage = error instanceof Error ? error.message : String(error);
|
|
1225
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
1226
|
+
stream.end();
|
|
1227
|
+
}
|
|
1228
|
+
})();
|
|
1229
|
+
return stream;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/quota.ts
|
|
1233
|
+
function renderProgressBar(fraction, width = 10) {
|
|
1234
|
+
const clamped = Math.max(0, Math.min(1, fraction));
|
|
1235
|
+
const filled = Math.round(clamped * width);
|
|
1236
|
+
return `[${"#".repeat(filled)}${"-".repeat(width - filled)}]`;
|
|
1237
|
+
}
|
|
1238
|
+
var QUOTA_WINDOW_RANK = {
|
|
1239
|
+
"5h": 0,
|
|
1240
|
+
weekly: 1,
|
|
1241
|
+
other: 2
|
|
1242
|
+
};
|
|
1243
|
+
function classifyQuotaWindow(bucket) {
|
|
1244
|
+
const s = `${bucket.window || ""} ${bucket.displayName || ""} ${bucket.bucketId || ""}`.toLowerCase();
|
|
1245
|
+
if (s.includes("5h") || s.includes("five") || s.includes("rolling")) return "5h";
|
|
1246
|
+
if (s.includes("week")) return "weekly";
|
|
1247
|
+
return "other";
|
|
1248
|
+
}
|
|
1249
|
+
function shortWindowLabel(bucket, kind) {
|
|
1250
|
+
if (kind === "5h") return "5h";
|
|
1251
|
+
if (kind === "weekly") return "Wk";
|
|
1252
|
+
return bucket.displayName || bucket.bucketId || "Quota";
|
|
1253
|
+
}
|
|
1254
|
+
function sortQuotaBuckets(buckets) {
|
|
1255
|
+
return buckets.slice().sort((a, b) => QUOTA_WINDOW_RANK[classifyQuotaWindow(a)] - QUOTA_WINDOW_RANK[classifyQuotaWindow(b)]);
|
|
1256
|
+
}
|
|
1257
|
+
function formatResetRemaining(resetTime) {
|
|
1258
|
+
if (!resetTime) return null;
|
|
1259
|
+
const ts = Date.parse(resetTime);
|
|
1260
|
+
if (!Number.isFinite(ts)) return null;
|
|
1261
|
+
const delta = ts - Date.now();
|
|
1262
|
+
if (delta <= 0) return "ready";
|
|
1263
|
+
const totalMin = Math.ceil(delta / 6e4);
|
|
1264
|
+
const days = Math.floor(totalMin / (60 * 24));
|
|
1265
|
+
const hours = Math.floor(totalMin % (60 * 24) / 60);
|
|
1266
|
+
const mins = totalMin % 60;
|
|
1267
|
+
if (days > 0) return `in ${days}d ${hours}h`;
|
|
1268
|
+
if (hours > 0) return `in ${hours}h ${mins}m`;
|
|
1269
|
+
return `in ${mins}m`;
|
|
1270
|
+
}
|
|
1271
|
+
function parseQuotaSummary(data) {
|
|
1272
|
+
const groups = [];
|
|
1273
|
+
for (const g of data.groups || []) {
|
|
1274
|
+
const buckets = [];
|
|
1275
|
+
for (const b of g.buckets || []) {
|
|
1276
|
+
buckets.push({
|
|
1277
|
+
bucketId: b.bucketId || "unknown",
|
|
1278
|
+
displayName: b.displayName || b.bucketId || "Limit",
|
|
1279
|
+
window: b.window,
|
|
1280
|
+
resetTime: b.resetTime,
|
|
1281
|
+
description: b.description,
|
|
1282
|
+
remainingFraction: typeof b.remainingFraction === "number" ? b.remainingFraction : 1
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
groups.push({
|
|
1286
|
+
displayName: g.displayName || "Quota Group",
|
|
1287
|
+
description: g.description,
|
|
1288
|
+
buckets
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
return {
|
|
1292
|
+
groups,
|
|
1293
|
+
description: data.description
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
function formatQuotaSummary(summary) {
|
|
1297
|
+
const lines = [];
|
|
1298
|
+
for (const group of summary.groups) {
|
|
1299
|
+
if (lines.length > 0) lines.push("");
|
|
1300
|
+
lines.push(`${group.displayName}`);
|
|
1301
|
+
for (const b of sortQuotaBuckets(group.buckets)) {
|
|
1302
|
+
const pct = Math.round(b.remainingFraction * 100);
|
|
1303
|
+
const label = shortWindowLabel(b, classifyQuotaWindow(b)).padEnd(3);
|
|
1304
|
+
const reset = formatResetRemaining(b.resetTime);
|
|
1305
|
+
const suffix = reset && pct < 100 ? ` (${reset})` : "";
|
|
1306
|
+
lines.push(` ${label} ${renderProgressBar(b.remainingFraction)} ${String(pct).padStart(3)}% left${suffix}`);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return lines.join("\n");
|
|
1310
|
+
}
|
|
1311
|
+
async function fetchQuotaSummary(token, projectId = "aicode-consumers", endpoint = DEFAULT_ENDPOINT, signal) {
|
|
1312
|
+
const res = await fetch(`${endpoint}/v1internal:retrieveUserQuotaSummary`, {
|
|
1313
|
+
method: "POST",
|
|
1314
|
+
headers: buildAntigravityHeaders(token),
|
|
1315
|
+
body: JSON.stringify({ project: projectId }),
|
|
1316
|
+
signal
|
|
1317
|
+
});
|
|
1318
|
+
if (!res.ok) {
|
|
1319
|
+
const errText = await res.text();
|
|
1320
|
+
throw new Error(`Failed to fetch quota summary (${res.status}): ${errText}`);
|
|
1321
|
+
}
|
|
1322
|
+
const json = await res.json();
|
|
1323
|
+
return parseQuotaSummary(json);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// src/commands.ts
|
|
1327
|
+
var USAGE_TEXT = "Usage: /antigravity <command>\n\nCommands:\n usage Show 5h and weekly quota pool limits\n models List recommended models with context window and remaining quota (alias: model)\n refresh Force refresh model catalog\n login Run /login antigravity";
|
|
1328
|
+
var FETCH_SUBCOMMANDS = {
|
|
1329
|
+
usage: {
|
|
1330
|
+
progress: "Fetching quota summary\u2026",
|
|
1331
|
+
errLabel: "usage",
|
|
1332
|
+
run: (token, projectId, signal) => fetchQuotaSummary(token, projectId, DEFAULT_ENDPOINT, signal).then(formatQuotaSummary)
|
|
1333
|
+
},
|
|
1334
|
+
models: {
|
|
1335
|
+
progress: "Fetching available models\u2026",
|
|
1336
|
+
errLabel: "models",
|
|
1337
|
+
run: (token, projectId, signal) => fetchAvailableModelsCatalog(token, projectId, DEFAULT_ENDPOINT, signal).then(formatModelsList)
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
function emitOutput(ctx, text, type = "info") {
|
|
1341
|
+
if (ctx.hasUI) {
|
|
1342
|
+
ctx.ui.notify(text, type);
|
|
1343
|
+
} else {
|
|
1344
|
+
if (type === "error" || type === "warning") console.error(text);
|
|
1345
|
+
else console.log(text);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
async function resolveToken(ctx) {
|
|
1349
|
+
const apiKey = await ctx.modelRegistry?.getApiKeyForProvider?.(PROVIDER_ID);
|
|
1350
|
+
if (!apiKey) return null;
|
|
1351
|
+
return parseStoredCredentials(apiKey);
|
|
1352
|
+
}
|
|
1353
|
+
async function runWithToken(ctx, cmd) {
|
|
1354
|
+
const creds = await resolveToken(ctx);
|
|
1355
|
+
if (!creds) {
|
|
1356
|
+
emitOutput(ctx, "Not logged in. Run /login antigravity first.", "warning");
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
try {
|
|
1360
|
+
if (ctx.hasUI) ctx.ui.notify(cmd.progress, "info");
|
|
1361
|
+
emitOutput(ctx, await cmd.run(creds.token, creds.projectId, ctx.signal));
|
|
1362
|
+
} catch (err) {
|
|
1363
|
+
emitOutput(ctx, `Failed to fetch ${cmd.errLabel}: ${err.message}`, "error");
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
function parseAntigravitySubcommand(args) {
|
|
1367
|
+
const sub = (args || "").trim().toLowerCase();
|
|
1368
|
+
return sub === "model" ? "models" : sub;
|
|
1369
|
+
}
|
|
1370
|
+
async function runAntigravitySubcommand(args, ctx) {
|
|
1371
|
+
const sub = parseAntigravitySubcommand(args);
|
|
1372
|
+
const fetchCmd = FETCH_SUBCOMMANDS[sub];
|
|
1373
|
+
if (fetchCmd) {
|
|
1374
|
+
await runWithToken(ctx, fetchCmd);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
if (sub === "refresh") {
|
|
1378
|
+
try {
|
|
1379
|
+
if (ctx.hasUI) ctx.ui.notify("Refreshing models\u2026", "info");
|
|
1380
|
+
await ctx.modelRegistry?.refresh?.({ force: true, providers: [PROVIDER_ID], signal: ctx.signal });
|
|
1381
|
+
emitOutput(ctx, "Antigravity models refreshed successfully.");
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
emitOutput(ctx, `Failed to refresh: ${err.message}`, "error");
|
|
1384
|
+
}
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
if (sub === "login") {
|
|
1388
|
+
if (ctx.hasUI) {
|
|
1389
|
+
ctx.ui.setEditorText("/login antigravity");
|
|
1390
|
+
ctx.ui.notify("Press Enter to log in to Antigravity.", "info");
|
|
1391
|
+
} else {
|
|
1392
|
+
emitOutput(ctx, "Please run /login antigravity to authenticate.");
|
|
1393
|
+
}
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
emitOutput(ctx, USAGE_TEXT);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// src/index.ts
|
|
1400
|
+
var PROVIDER_NAME = "Antigravity";
|
|
1401
|
+
function index_default(pi) {
|
|
1402
|
+
pi.registerProvider(PROVIDER_ID, {
|
|
1403
|
+
name: PROVIDER_NAME,
|
|
1404
|
+
baseUrl: DEFAULT_ENDPOINT,
|
|
1405
|
+
api: "antigravity-api",
|
|
1406
|
+
models: [],
|
|
1407
|
+
// Purely dynamic provider per Pi SDK architecture
|
|
1408
|
+
oauth: {
|
|
1409
|
+
name: PROVIDER_NAME,
|
|
1410
|
+
login: loginAntigravity,
|
|
1411
|
+
refreshToken: refreshAntigravityToken,
|
|
1412
|
+
getApiKey
|
|
1413
|
+
},
|
|
1414
|
+
streamSimple: streamAntigravity,
|
|
1415
|
+
refreshModels: async (context) => refreshCatalog(context)
|
|
1416
|
+
});
|
|
1417
|
+
pi.registerCommand("antigravity", {
|
|
1418
|
+
description: "Antigravity provider commands (/antigravity [usage|models|refresh|login])",
|
|
1419
|
+
handler: async (args, ctx) => {
|
|
1420
|
+
await runAntigravitySubcommand(args, ctx);
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
export {
|
|
1425
|
+
PROVIDER_ID,
|
|
1426
|
+
PROVIDER_NAME,
|
|
1427
|
+
index_default as default,
|
|
1428
|
+
extractBaseModelId
|
|
1429
|
+
};
|
|
1430
|
+
//# sourceMappingURL=index.js.map
|