opencode-antigravity-auth 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -99
- package/dist/src/hooks/auto-update-checker/cache.d.ts +3 -0
- package/dist/src/hooks/auto-update-checker/cache.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/cache.js +71 -0
- package/dist/src/hooks/auto-update-checker/cache.js.map +1 -0
- package/dist/src/hooks/auto-update-checker/checker.d.ts +16 -0
- package/dist/src/hooks/auto-update-checker/checker.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/checker.js +237 -0
- package/dist/src/hooks/auto-update-checker/checker.js.map +1 -0
- package/dist/src/hooks/auto-update-checker/constants.d.ts +9 -0
- package/dist/src/hooks/auto-update-checker/constants.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/constants.js +23 -0
- package/dist/src/hooks/auto-update-checker/constants.js.map +1 -0
- package/dist/src/hooks/auto-update-checker/index.d.ts +34 -0
- package/dist/src/hooks/auto-update-checker/index.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/index.js +121 -0
- package/dist/src/hooks/auto-update-checker/index.js.map +1 -0
- package/dist/src/hooks/auto-update-checker/types.d.ts +25 -0
- package/dist/src/hooks/auto-update-checker/types.d.ts.map +1 -0
- package/dist/src/hooks/auto-update-checker/types.js +1 -0
- package/dist/src/hooks/auto-update-checker/types.js.map +1 -0
- package/dist/src/plugin/accounts.d.ts +21 -10
- package/dist/src/plugin/accounts.d.ts.map +1 -1
- package/dist/src/plugin/accounts.js +101 -55
- package/dist/src/plugin/accounts.js.map +1 -1
- package/dist/src/plugin/debug.d.ts +32 -0
- package/dist/src/plugin/debug.d.ts.map +1 -1
- package/dist/src/plugin/debug.js +140 -12
- package/dist/src/plugin/debug.js.map +1 -1
- package/dist/src/plugin/request.d.ts +3 -1
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +352 -16
- package/dist/src/plugin/request.js.map +1 -1
- package/dist/src/plugin/storage.d.ts +23 -7
- package/dist/src/plugin/storage.d.ts.map +1 -1
- package/dist/src/plugin/storage.js +54 -10
- package/dist/src/plugin/storage.js.map +1 -1
- package/dist/src/plugin/types.d.ts +8 -0
- package/dist/src/plugin/types.d.ts.map +1 -1
- package/dist/src/plugin.d.ts +3 -3
- package/dist/src/plugin.d.ts.map +1 -1
- package/dist/src/plugin.js +779 -476
- package/dist/src/plugin.js.map +1 -1
- package/package.json +1 -1
package/dist/src/plugin.js
CHANGED
|
@@ -4,13 +4,31 @@ import { authorizeAntigravity, exchangeAntigravity } from "./antigravity/oauth";
|
|
|
4
4
|
import { accessTokenExpired, isOAuthAuth, parseRefreshParts } from "./plugin/auth";
|
|
5
5
|
import { promptAddAnotherAccount, promptLoginMode, promptProjectId } from "./plugin/cli";
|
|
6
6
|
import { ensureProjectContext } from "./plugin/project";
|
|
7
|
-
import { startAntigravityDebugRequest } from "./plugin/debug";
|
|
8
|
-
import { isGenerativeLanguageRequest, prepareAntigravityRequest, transformAntigravityResponse, } from "./plugin/request";
|
|
7
|
+
import { startAntigravityDebugRequest, logAntigravityDebugResponse, logAccountContext, logRateLimitEvent, logRateLimitSnapshot, logResponseBody, logModelFamily, isDebugEnabled, getLogFilePath, } from "./plugin/debug";
|
|
8
|
+
import { buildThinkingWarmupBody, isGenerativeLanguageRequest, prepareAntigravityRequest, transformAntigravityResponse, } from "./plugin/request";
|
|
9
9
|
import { AntigravityTokenRefreshError, refreshAccessToken } from "./plugin/token";
|
|
10
10
|
import { startOAuthListener } from "./plugin/server";
|
|
11
11
|
import { clearAccounts, loadAccounts, saveAccounts } from "./plugin/storage";
|
|
12
12
|
import { AccountManager } from "./plugin/accounts";
|
|
13
|
+
import { createAutoUpdateCheckerHook } from "./hooks/auto-update-checker";
|
|
13
14
|
const MAX_OAUTH_ACCOUNTS = 10;
|
|
15
|
+
const MAX_WARMUP_SESSIONS = 1000;
|
|
16
|
+
const warmupAttemptedSessionIds = new Set();
|
|
17
|
+
function trackWarmupSession(sessionId) {
|
|
18
|
+
if (warmupAttemptedSessionIds.has(sessionId)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (warmupAttemptedSessionIds.size >= MAX_WARMUP_SESSIONS) {
|
|
22
|
+
const first = warmupAttemptedSessionIds.values().next().value;
|
|
23
|
+
if (first)
|
|
24
|
+
warmupAttemptedSessionIds.delete(first);
|
|
25
|
+
}
|
|
26
|
+
warmupAttemptedSessionIds.add(sessionId);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
function untrackWarmupSession(sessionId) {
|
|
30
|
+
warmupAttemptedSessionIds.delete(sessionId);
|
|
31
|
+
}
|
|
14
32
|
async function openBrowser(url) {
|
|
15
33
|
try {
|
|
16
34
|
if (process.platform === "darwin") {
|
|
@@ -115,8 +133,6 @@ async function persistAccountPool(results, replaceAll = false) {
|
|
|
115
133
|
managedProjectId: parts.managedProjectId,
|
|
116
134
|
addedAt: now,
|
|
117
135
|
lastUsed: now,
|
|
118
|
-
isRateLimited: false,
|
|
119
|
-
rateLimitResetTime: 0,
|
|
120
136
|
});
|
|
121
137
|
continue;
|
|
122
138
|
}
|
|
@@ -140,7 +156,7 @@ async function persistAccountPool(results, replaceAll = false) {
|
|
|
140
156
|
? 0
|
|
141
157
|
: (typeof stored?.activeIndex === "number" && Number.isFinite(stored.activeIndex) ? stored.activeIndex : 0);
|
|
142
158
|
await saveAccounts({
|
|
143
|
-
version:
|
|
159
|
+
version: 2,
|
|
144
160
|
accounts,
|
|
145
161
|
activeIndex: clampInt(activeIndex, 0, accounts.length - 1),
|
|
146
162
|
});
|
|
@@ -162,6 +178,130 @@ function retryAfterMsFromResponse(response) {
|
|
|
162
178
|
}
|
|
163
179
|
return 60_000;
|
|
164
180
|
}
|
|
181
|
+
function parseDurationToMs(duration) {
|
|
182
|
+
const match = duration.match(/^(\d+(?:\.\d+)?)(s|m|h)?$/i);
|
|
183
|
+
if (!match)
|
|
184
|
+
return null;
|
|
185
|
+
const value = parseFloat(match[1]);
|
|
186
|
+
const unit = (match[2] || "s").toLowerCase();
|
|
187
|
+
switch (unit) {
|
|
188
|
+
case "h": return value * 3600 * 1000;
|
|
189
|
+
case "m": return value * 60 * 1000;
|
|
190
|
+
case "s": return value * 1000;
|
|
191
|
+
default: return value * 1000;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function extractRateLimitBodyInfo(body) {
|
|
195
|
+
if (!body || typeof body !== "object") {
|
|
196
|
+
return { retryDelayMs: null };
|
|
197
|
+
}
|
|
198
|
+
const error = body.error;
|
|
199
|
+
const message = error && typeof error === "object"
|
|
200
|
+
? error.message
|
|
201
|
+
: undefined;
|
|
202
|
+
const details = error && typeof error === "object"
|
|
203
|
+
? error.details
|
|
204
|
+
: undefined;
|
|
205
|
+
let reason;
|
|
206
|
+
if (Array.isArray(details)) {
|
|
207
|
+
for (const detail of details) {
|
|
208
|
+
if (!detail || typeof detail !== "object")
|
|
209
|
+
continue;
|
|
210
|
+
const type = detail["@type"];
|
|
211
|
+
if (typeof type === "string" && type.includes("google.rpc.ErrorInfo")) {
|
|
212
|
+
const detailReason = detail.reason;
|
|
213
|
+
if (typeof detailReason === "string") {
|
|
214
|
+
reason = detailReason;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
for (const detail of details) {
|
|
220
|
+
if (!detail || typeof detail !== "object")
|
|
221
|
+
continue;
|
|
222
|
+
const type = detail["@type"];
|
|
223
|
+
if (typeof type === "string" && type.includes("google.rpc.RetryInfo")) {
|
|
224
|
+
const retryDelay = detail.retryDelay;
|
|
225
|
+
if (typeof retryDelay === "string") {
|
|
226
|
+
const retryDelayMs = parseDurationToMs(retryDelay);
|
|
227
|
+
if (retryDelayMs !== null) {
|
|
228
|
+
return { retryDelayMs, message, reason };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const detail of details) {
|
|
234
|
+
if (!detail || typeof detail !== "object")
|
|
235
|
+
continue;
|
|
236
|
+
const metadata = detail.metadata;
|
|
237
|
+
if (metadata && typeof metadata === "object") {
|
|
238
|
+
const quotaResetDelay = metadata.quotaResetDelay;
|
|
239
|
+
const quotaResetTime = metadata.quotaResetTimeStamp;
|
|
240
|
+
if (typeof quotaResetDelay === "string") {
|
|
241
|
+
const quotaResetDelayMs = parseDurationToMs(quotaResetDelay);
|
|
242
|
+
if (quotaResetDelayMs !== null) {
|
|
243
|
+
return { retryDelayMs: quotaResetDelayMs, message, quotaResetTime, reason };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (message) {
|
|
250
|
+
const afterMatch = message.match(/reset after\s+([0-9hms.]+)/i);
|
|
251
|
+
const rawDuration = afterMatch?.[1];
|
|
252
|
+
if (rawDuration) {
|
|
253
|
+
const parsed = parseDurationToMs(rawDuration);
|
|
254
|
+
if (parsed !== null) {
|
|
255
|
+
return { retryDelayMs: parsed, message, reason };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return { retryDelayMs: null, message, reason };
|
|
260
|
+
}
|
|
261
|
+
async function extractRetryInfoFromBody(response) {
|
|
262
|
+
try {
|
|
263
|
+
const text = await response.clone().text();
|
|
264
|
+
try {
|
|
265
|
+
const parsed = JSON.parse(text);
|
|
266
|
+
return extractRateLimitBodyInfo(parsed);
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return { retryDelayMs: null };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return { retryDelayMs: null };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function formatWaitTime(ms) {
|
|
277
|
+
if (ms < 1000)
|
|
278
|
+
return `${ms}ms`;
|
|
279
|
+
const seconds = Math.ceil(ms / 1000);
|
|
280
|
+
if (seconds < 60)
|
|
281
|
+
return `${seconds}s`;
|
|
282
|
+
const minutes = Math.floor(seconds / 60);
|
|
283
|
+
const remainingSeconds = seconds % 60;
|
|
284
|
+
if (minutes < 60) {
|
|
285
|
+
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
|
|
286
|
+
}
|
|
287
|
+
const hours = Math.floor(minutes / 60);
|
|
288
|
+
const remainingMinutes = minutes % 60;
|
|
289
|
+
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
|
|
290
|
+
}
|
|
291
|
+
const SHORT_RETRY_THRESHOLD_MS = 5000;
|
|
292
|
+
const rateLimitStateByAccount = new Map();
|
|
293
|
+
function getRateLimitBackoff(accountIndex, serverRetryAfterMs) {
|
|
294
|
+
const now = Date.now();
|
|
295
|
+
const previous = rateLimitStateByAccount.get(accountIndex);
|
|
296
|
+
const attempt = previous && (now - previous.lastAt < 120_000) ? previous.consecutive429 + 1 : 1;
|
|
297
|
+
rateLimitStateByAccount.set(accountIndex, { consecutive429: attempt, lastAt: now });
|
|
298
|
+
const baseDelay = serverRetryAfterMs ?? 1000;
|
|
299
|
+
const backoffDelay = Math.min(baseDelay * Math.pow(2, attempt - 1), 60_000);
|
|
300
|
+
return { attempt, delayMs: Math.max(baseDelay, backoffDelay) };
|
|
301
|
+
}
|
|
302
|
+
function resetRateLimitState(accountIndex) {
|
|
303
|
+
rateLimitStateByAccount.delete(accountIndex);
|
|
304
|
+
}
|
|
165
305
|
/**
|
|
166
306
|
* Sleep for a given number of milliseconds, respecting an abort signal.
|
|
167
307
|
*/
|
|
@@ -189,231 +329,401 @@ function sleep(ms, signal) {
|
|
|
189
329
|
/**
|
|
190
330
|
* Creates an Antigravity OAuth plugin for a specific provider ID.
|
|
191
331
|
*/
|
|
192
|
-
export const createAntigravityPlugin = (providerId) => async ({ client }) =>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return {};
|
|
206
|
-
}
|
|
207
|
-
// Validate that stored accounts are in sync with OpenCode's auth
|
|
208
|
-
// If OpenCode's refresh token doesn't match any stored account, clear stale storage
|
|
209
|
-
const authParts = parseRefreshParts(auth.refresh);
|
|
210
|
-
const storedAccounts = await loadAccounts();
|
|
211
|
-
if (storedAccounts && storedAccounts.accounts.length > 0 && authParts.refreshToken) {
|
|
212
|
-
const hasMatchingAccount = storedAccounts.accounts.some((acc) => acc.refreshToken === authParts.refreshToken);
|
|
213
|
-
if (!hasMatchingAccount) {
|
|
214
|
-
// OpenCode's auth doesn't match any stored account - storage is stale
|
|
215
|
-
// Clear it and let the user re-authenticate
|
|
216
|
-
console.warn("[opencode-antigravity-auth] Stored accounts don't match OpenCode's auth. Clearing stale storage.");
|
|
332
|
+
export const createAntigravityPlugin = (providerId) => async ({ client, directory }) => {
|
|
333
|
+
const updateChecker = createAutoUpdateCheckerHook(client, directory, {
|
|
334
|
+
showStartupToast: true,
|
|
335
|
+
autoUpdate: true,
|
|
336
|
+
});
|
|
337
|
+
return {
|
|
338
|
+
event: updateChecker.event,
|
|
339
|
+
auth: {
|
|
340
|
+
provider: providerId,
|
|
341
|
+
loader: async (getAuth, provider) => {
|
|
342
|
+
const auth = await getAuth();
|
|
343
|
+
// If OpenCode has no valid OAuth auth, clear any stale account storage
|
|
344
|
+
if (!isOAuthAuth(auth)) {
|
|
217
345
|
try {
|
|
218
346
|
await clearAccounts();
|
|
219
347
|
}
|
|
220
348
|
catch {
|
|
221
349
|
// ignore
|
|
222
350
|
}
|
|
351
|
+
return {};
|
|
223
352
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
353
|
+
// Validate that stored accounts are in sync with OpenCode's auth
|
|
354
|
+
// If OpenCode's refresh token doesn't match any stored account, clear stale storage
|
|
355
|
+
const authParts = parseRefreshParts(auth.refresh);
|
|
356
|
+
const storedAccounts = await loadAccounts();
|
|
357
|
+
if (storedAccounts && storedAccounts.accounts.length > 0 && authParts.refreshToken) {
|
|
358
|
+
const hasMatchingAccount = storedAccounts.accounts.some((acc) => acc.refreshToken === authParts.refreshToken);
|
|
359
|
+
if (!hasMatchingAccount) {
|
|
360
|
+
// OpenCode's auth doesn't match any stored account - storage is stale
|
|
361
|
+
// Clear it and let the user re-authenticate
|
|
362
|
+
console.warn("[opencode-antigravity-auth] Stored accounts don't match OpenCode's auth. Clearing stale storage.");
|
|
363
|
+
try {
|
|
364
|
+
await clearAccounts();
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
// ignore
|
|
368
|
+
}
|
|
238
369
|
}
|
|
239
370
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// If the request is for the *other* provider, we might still want to intercept if URL matches
|
|
245
|
-
// But strict compliance means we only handle requests if the auth provider matches.
|
|
246
|
-
// Since loader is instantiated per provider, we are good.
|
|
247
|
-
if (!isGenerativeLanguageRequest(input)) {
|
|
248
|
-
return fetch(input, init);
|
|
249
|
-
}
|
|
250
|
-
const latestAuth = await getAuth();
|
|
251
|
-
if (!isOAuthAuth(latestAuth)) {
|
|
252
|
-
return fetch(input, init);
|
|
371
|
+
const accountManager = await AccountManager.loadFromDisk(auth);
|
|
372
|
+
if (accountManager.getAccountCount() > 0) {
|
|
373
|
+
try {
|
|
374
|
+
await accountManager.saveToDisk();
|
|
253
375
|
}
|
|
254
|
-
|
|
255
|
-
|
|
376
|
+
catch (error) {
|
|
377
|
+
console.error("[opencode-antigravity-auth] Failed to persist initial account pool:", error);
|
|
256
378
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
// This is scoped to the fetch call so it resets per-request
|
|
262
|
-
let previousAccountIndex = null;
|
|
263
|
-
// Helper to check if request was aborted
|
|
264
|
-
const checkAborted = () => {
|
|
265
|
-
if (abortSignal?.aborted) {
|
|
266
|
-
throw abortSignal.reason instanceof Error ? abortSignal.reason : new Error("Aborted");
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
// Helper to show toast without blocking on abort
|
|
270
|
-
const showToast = async (message, variant) => {
|
|
271
|
-
if (abortSignal?.aborted)
|
|
272
|
-
return;
|
|
379
|
+
}
|
|
380
|
+
if (isDebugEnabled()) {
|
|
381
|
+
const logPath = getLogFilePath();
|
|
382
|
+
if (logPath) {
|
|
273
383
|
try {
|
|
274
384
|
await client.tui.showToast({
|
|
275
|
-
body: { message
|
|
385
|
+
body: { message: `Debug log: ${logPath}`, variant: "info" },
|
|
276
386
|
});
|
|
277
387
|
}
|
|
278
388
|
catch {
|
|
279
389
|
// TUI may not be available
|
|
280
390
|
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const accountCount = accountManager.getAccountCount();
|
|
288
|
-
if (accountCount === 0) {
|
|
289
|
-
throw new Error("No Antigravity accounts available. Run `opencode auth login`.");
|
|
290
|
-
}
|
|
291
|
-
const account = accountManager.pickNext();
|
|
292
|
-
if (!account) {
|
|
293
|
-
// All accounts are rate-limited - wait and retry
|
|
294
|
-
const waitMs = accountManager.getMinWaitTimeMs() || 60_000;
|
|
295
|
-
const waitSec = Math.max(1, Math.ceil(waitMs / 1000));
|
|
296
|
-
await showToast(`All ${accountCount} account(s) rate-limited. Waiting ${waitSec}s...`, "warning");
|
|
297
|
-
// Wait for the cooldown to expire
|
|
298
|
-
await sleep(waitMs, abortSignal);
|
|
299
|
-
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (provider.models) {
|
|
394
|
+
for (const model of Object.values(provider.models)) {
|
|
395
|
+
if (model) {
|
|
396
|
+
model.cost = { input: 0, output: 0 };
|
|
300
397
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
apiKey: "",
|
|
402
|
+
async fetch(input, init) {
|
|
403
|
+
// If the request is for the *other* provider, we might still want to intercept if URL matches
|
|
404
|
+
// But strict compliance means we only handle requests if the auth provider matches.
|
|
405
|
+
// Since loader is instantiated per provider, we are good.
|
|
406
|
+
if (!isGenerativeLanguageRequest(input)) {
|
|
407
|
+
return fetch(input, init);
|
|
306
408
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
409
|
+
const latestAuth = await getAuth();
|
|
410
|
+
if (!isOAuthAuth(latestAuth)) {
|
|
411
|
+
return fetch(input, init);
|
|
310
412
|
}
|
|
311
|
-
|
|
312
|
-
|
|
413
|
+
if (accountManager.getAccountCount() === 0) {
|
|
414
|
+
throw new Error("No Antigravity accounts configured. Run `opencode auth login`.");
|
|
313
415
|
}
|
|
314
|
-
|
|
315
|
-
|
|
416
|
+
const urlString = toUrlString(input);
|
|
417
|
+
const family = getModelFamilyFromUrl(urlString);
|
|
418
|
+
const debugLines = [];
|
|
419
|
+
const pushDebug = (line) => {
|
|
420
|
+
if (!isDebugEnabled())
|
|
421
|
+
return;
|
|
422
|
+
debugLines.push(line);
|
|
423
|
+
};
|
|
424
|
+
pushDebug(`request=${urlString}`);
|
|
425
|
+
let lastFailure = null;
|
|
426
|
+
let lastError = null;
|
|
427
|
+
const abortSignal = init?.signal ?? undefined;
|
|
428
|
+
// Helper to check if request was aborted
|
|
429
|
+
const checkAborted = () => {
|
|
430
|
+
if (abortSignal?.aborted) {
|
|
431
|
+
throw abortSignal.reason instanceof Error ? abortSignal.reason : new Error("Aborted");
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
// Helper to show toast without blocking on abort
|
|
435
|
+
const showToast = async (message, variant) => {
|
|
436
|
+
if (abortSignal?.aborted)
|
|
437
|
+
return;
|
|
316
438
|
try {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
439
|
+
await client.tui.showToast({
|
|
440
|
+
body: { message, variant },
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// TUI may not be available
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
// Use while(true) loop to handle rate limits with backoff
|
|
448
|
+
// This ensures we wait and retry when all accounts are rate-limited
|
|
449
|
+
const quietMode = process.env.OPENCODE_ANTIGRAVITY_QUIET === "1";
|
|
450
|
+
while (true) {
|
|
451
|
+
// Check for abort at the start of each iteration
|
|
452
|
+
checkAborted();
|
|
453
|
+
const accountCount = accountManager.getAccountCount();
|
|
454
|
+
if (accountCount === 0) {
|
|
455
|
+
throw new Error("No Antigravity accounts available. Run `opencode auth login`.");
|
|
456
|
+
}
|
|
457
|
+
const account = accountManager.getCurrentOrNextForFamily(family);
|
|
458
|
+
if (!account) {
|
|
459
|
+
// All accounts are rate-limited - wait and retry
|
|
460
|
+
const waitMs = accountManager.getMinWaitTimeForFamily(family) || 60_000;
|
|
461
|
+
const waitSec = Math.max(1, Math.ceil(waitMs / 1000));
|
|
462
|
+
pushDebug(`all-rate-limited family=${family} accounts=${accountCount}`);
|
|
463
|
+
if (isDebugEnabled()) {
|
|
464
|
+
logAccountContext("All accounts rate-limited", {
|
|
465
|
+
index: -1,
|
|
466
|
+
family,
|
|
467
|
+
totalAccounts: accountCount,
|
|
468
|
+
});
|
|
469
|
+
logRateLimitSnapshot(family, accountManager.getAccountsSnapshot());
|
|
329
470
|
}
|
|
471
|
+
await showToast(`All ${accountCount} account(s) rate-limited for ${family}. Waiting ${waitSec}s...`, "warning");
|
|
472
|
+
// Wait for the cooldown to expire
|
|
473
|
+
await sleep(waitMs, abortSignal);
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
pushDebug(`selected idx=${account.index} email=${account.email ?? ""} family=${family} accounts=${accountCount}`);
|
|
477
|
+
if (isDebugEnabled()) {
|
|
478
|
+
logAccountContext("Selected", {
|
|
479
|
+
index: account.index,
|
|
480
|
+
email: account.email,
|
|
481
|
+
family,
|
|
482
|
+
totalAccounts: accountCount,
|
|
483
|
+
rateLimitState: account.rateLimitResetTimes,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
// Show toast when switching to a different account (debounced, respects quiet mode)
|
|
487
|
+
if (!quietMode && accountCount > 1 && accountManager.shouldShowAccountToast(account.index)) {
|
|
488
|
+
const accountLabel = account.email || `Account ${account.index + 1}`;
|
|
489
|
+
await showToast(`Using ${accountLabel} (${account.index + 1}/${accountCount})`, "info");
|
|
490
|
+
accountManager.markToastShown(account.index);
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
await accountManager.saveToDisk();
|
|
330
494
|
}
|
|
331
495
|
catch (error) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
496
|
+
console.error("[opencode-antigravity-auth] Failed to persist rotation state:", error);
|
|
497
|
+
}
|
|
498
|
+
let authRecord = accountManager.toAuthDetails(account);
|
|
499
|
+
if (accessTokenExpired(authRecord)) {
|
|
500
|
+
try {
|
|
501
|
+
const refreshed = await refreshAccessToken(authRecord, client, providerId);
|
|
502
|
+
if (!refreshed) {
|
|
503
|
+
lastError = new Error("Antigravity token refresh failed");
|
|
504
|
+
continue;
|
|
342
505
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
506
|
+
accountManager.updateFromAuth(account, refreshed);
|
|
507
|
+
authRecord = refreshed;
|
|
508
|
+
try {
|
|
509
|
+
await accountManager.saveToDisk();
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
console.error("[opencode-antigravity-auth] Failed to persist refreshed auth:", error);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
if (error instanceof AntigravityTokenRefreshError && error.code === "invalid_grant") {
|
|
517
|
+
const removed = accountManager.removeAccount(account);
|
|
518
|
+
if (removed) {
|
|
519
|
+
console.warn("[opencode-antigravity-auth] Removed revoked account from pool. Reauthenticate it via `opencode auth login` to add it back.");
|
|
520
|
+
try {
|
|
521
|
+
await accountManager.saveToDisk();
|
|
522
|
+
}
|
|
523
|
+
catch (persistError) {
|
|
524
|
+
console.error("[opencode-antigravity-auth] Failed to persist revoked account removal:", persistError);
|
|
525
|
+
}
|
|
349
526
|
}
|
|
350
|
-
|
|
351
|
-
|
|
527
|
+
if (accountManager.getAccountCount() === 0) {
|
|
528
|
+
try {
|
|
529
|
+
await client.auth.set({
|
|
530
|
+
path: { id: providerId },
|
|
531
|
+
body: { type: "oauth", refresh: "", access: "", expires: 0 },
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
catch (storeError) {
|
|
535
|
+
console.error("Failed to clear stored Antigravity OAuth credentials:", storeError);
|
|
536
|
+
}
|
|
537
|
+
throw new Error("All Antigravity accounts have invalid refresh tokens. Run `opencode auth login` and reauthenticate.");
|
|
352
538
|
}
|
|
353
|
-
|
|
539
|
+
lastError = error;
|
|
540
|
+
continue;
|
|
354
541
|
}
|
|
355
|
-
lastError = error;
|
|
542
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
356
543
|
continue;
|
|
357
544
|
}
|
|
358
|
-
|
|
545
|
+
}
|
|
546
|
+
const accessToken = authRecord.access;
|
|
547
|
+
if (!accessToken) {
|
|
548
|
+
lastError = new Error("Missing access token");
|
|
359
549
|
continue;
|
|
360
550
|
}
|
|
361
|
-
|
|
362
|
-
const accessToken = authRecord.access;
|
|
363
|
-
if (!accessToken) {
|
|
364
|
-
lastError = new Error("Missing access token");
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
let projectContext;
|
|
368
|
-
try {
|
|
369
|
-
projectContext = await ensureProjectContext(authRecord);
|
|
370
|
-
}
|
|
371
|
-
catch (error) {
|
|
372
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
if (projectContext.auth !== authRecord) {
|
|
376
|
-
accountManager.updateFromAuth(account, projectContext.auth);
|
|
377
|
-
authRecord = projectContext.auth;
|
|
551
|
+
let projectContext;
|
|
378
552
|
try {
|
|
379
|
-
await
|
|
553
|
+
projectContext = await ensureProjectContext(authRecord);
|
|
380
554
|
}
|
|
381
555
|
catch (error) {
|
|
382
|
-
|
|
556
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
557
|
+
continue;
|
|
383
558
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
559
|
+
if (projectContext.auth !== authRecord) {
|
|
560
|
+
accountManager.updateFromAuth(account, projectContext.auth);
|
|
561
|
+
authRecord = projectContext.auth;
|
|
562
|
+
try {
|
|
563
|
+
await accountManager.saveToDisk();
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
console.error("[opencode-antigravity-auth] Failed to persist project context:", error);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const runThinkingWarmup = async (prepared, projectId) => {
|
|
570
|
+
if (!prepared.needsSignedThinkingWarmup || !prepared.sessionId) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (!trackWarmupSession(prepared.sessionId)) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const warmupBody = buildThinkingWarmupBody(typeof prepared.init.body === "string" ? prepared.init.body : undefined, Boolean(prepared.effectiveModel?.toLowerCase().includes("claude") && prepared.effectiveModel?.toLowerCase().includes("thinking")));
|
|
577
|
+
if (!warmupBody) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const warmupUrl = toWarmupStreamUrl(prepared.request);
|
|
581
|
+
const warmupHeaders = new Headers(prepared.init.headers ?? {});
|
|
582
|
+
warmupHeaders.set("accept", "text/event-stream");
|
|
583
|
+
const warmupInit = {
|
|
584
|
+
...prepared.init,
|
|
585
|
+
method: prepared.init.method ?? "POST",
|
|
586
|
+
headers: warmupHeaders,
|
|
587
|
+
body: warmupBody,
|
|
588
|
+
};
|
|
589
|
+
const warmupDebugContext = startAntigravityDebugRequest({
|
|
590
|
+
originalUrl: warmupUrl,
|
|
591
|
+
resolvedUrl: warmupUrl,
|
|
592
|
+
method: warmupInit.method,
|
|
593
|
+
headers: warmupHeaders,
|
|
594
|
+
body: warmupBody,
|
|
595
|
+
streaming: true,
|
|
596
|
+
projectId,
|
|
401
597
|
});
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
598
|
+
try {
|
|
599
|
+
pushDebug("thinking-warmup: start");
|
|
600
|
+
const warmupResponse = await fetch(warmupUrl, warmupInit);
|
|
601
|
+
const transformed = await transformAntigravityResponse(warmupResponse, true, warmupDebugContext, prepared.requestedModel, projectId, warmupUrl, prepared.effectiveModel, prepared.sessionId);
|
|
602
|
+
await transformed.text();
|
|
603
|
+
pushDebug("thinking-warmup: done");
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
untrackWarmupSession(prepared.sessionId);
|
|
607
|
+
pushDebug(`thinking-warmup: failed ${error instanceof Error ? error.message : String(error)}`);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
// Try endpoint fallbacks
|
|
611
|
+
let shouldSwitchAccount = false;
|
|
612
|
+
for (let i = 0; i < ANTIGRAVITY_ENDPOINT_FALLBACKS.length; i++) {
|
|
613
|
+
const currentEndpoint = ANTIGRAVITY_ENDPOINT_FALLBACKS[i];
|
|
614
|
+
try {
|
|
615
|
+
const prepared = prepareAntigravityRequest(input, init, accessToken, projectContext.effectiveProjectId, currentEndpoint);
|
|
616
|
+
const originalUrl = toUrlString(input);
|
|
617
|
+
const resolvedUrl = toUrlString(prepared.request);
|
|
618
|
+
pushDebug(`endpoint=${currentEndpoint}`);
|
|
619
|
+
pushDebug(`resolved=${resolvedUrl}`);
|
|
620
|
+
const debugContext = startAntigravityDebugRequest({
|
|
621
|
+
originalUrl,
|
|
622
|
+
resolvedUrl,
|
|
623
|
+
method: prepared.init.method,
|
|
624
|
+
headers: prepared.init.headers,
|
|
625
|
+
body: prepared.init.body,
|
|
626
|
+
streaming: prepared.streaming,
|
|
627
|
+
projectId: projectContext.effectiveProjectId,
|
|
628
|
+
});
|
|
629
|
+
await runThinkingWarmup(prepared, projectContext.effectiveProjectId);
|
|
630
|
+
const response = await fetch(prepared.request, prepared.init);
|
|
631
|
+
pushDebug(`status=${response.status} ${response.statusText}`);
|
|
632
|
+
// Handle 429 rate limit with improved logic
|
|
633
|
+
if (response.status === 429) {
|
|
634
|
+
const headerRetryMs = retryAfterMsFromResponse(response);
|
|
635
|
+
const bodyInfo = await extractRetryInfoFromBody(response);
|
|
636
|
+
const serverRetryMs = bodyInfo.retryDelayMs ?? headerRetryMs;
|
|
637
|
+
const { attempt, delayMs } = getRateLimitBackoff(account.index, serverRetryMs);
|
|
638
|
+
const waitTimeFormatted = formatWaitTime(delayMs);
|
|
639
|
+
const isCapacityExhausted = bodyInfo.reason === "MODEL_CAPACITY_EXHAUSTED" ||
|
|
640
|
+
(typeof bodyInfo.message === "string" && bodyInfo.message.toLowerCase().includes("no capacity"));
|
|
641
|
+
pushDebug(`429 idx=${account.index} email=${account.email ?? ""} family=${family} delayMs=${delayMs} attempt=${attempt}`);
|
|
642
|
+
if (bodyInfo.message) {
|
|
643
|
+
pushDebug(`429 message=${bodyInfo.message}`);
|
|
644
|
+
}
|
|
645
|
+
if (bodyInfo.quotaResetTime) {
|
|
646
|
+
pushDebug(`429 quotaResetTime=${bodyInfo.quotaResetTime}`);
|
|
647
|
+
}
|
|
648
|
+
if (bodyInfo.reason) {
|
|
649
|
+
pushDebug(`429 reason=${bodyInfo.reason}`);
|
|
650
|
+
}
|
|
651
|
+
logRateLimitEvent(account.index, account.email, family, response.status, delayMs, bodyInfo);
|
|
652
|
+
await logResponseBody(debugContext, response, 429);
|
|
653
|
+
if (isCapacityExhausted) {
|
|
654
|
+
accountManager.markRateLimited(account, delayMs, family);
|
|
655
|
+
await showToast(`Model capacity exhausted for ${family}. Retrying in ${waitTimeFormatted} (attempt ${attempt})...`, "warning");
|
|
656
|
+
await sleep(delayMs, abortSignal);
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
const accountLabel = account.email || `Account ${account.index + 1}`;
|
|
660
|
+
// Short retry: if delay is small, just wait and retry same account
|
|
661
|
+
if (delayMs <= SHORT_RETRY_THRESHOLD_MS) {
|
|
662
|
+
await showToast(`Rate limited. Retrying in ${waitTimeFormatted} (attempt ${attempt})...`, "warning");
|
|
663
|
+
await sleep(delayMs, abortSignal);
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
// Mark account as rate-limited for this family
|
|
667
|
+
accountManager.markRateLimited(account, delayMs, family);
|
|
668
|
+
try {
|
|
669
|
+
await accountManager.saveToDisk();
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
console.error("[opencode-antigravity-auth] Failed to persist rate-limit state:", error);
|
|
673
|
+
}
|
|
674
|
+
if (accountCount > 1) {
|
|
675
|
+
const quotaMsg = bodyInfo.quotaResetTime
|
|
676
|
+
? ` (quota resets ${bodyInfo.quotaResetTime})`
|
|
677
|
+
: ` (retry in ${waitTimeFormatted})`;
|
|
678
|
+
await showToast(`Rate limited on ${accountLabel}${quotaMsg}. Switching...`, "warning");
|
|
679
|
+
lastFailure = {
|
|
680
|
+
response,
|
|
681
|
+
streaming: prepared.streaming,
|
|
682
|
+
debugContext,
|
|
683
|
+
requestedModel: prepared.requestedModel,
|
|
684
|
+
projectId: prepared.projectId,
|
|
685
|
+
endpoint: prepared.endpoint,
|
|
686
|
+
effectiveModel: prepared.effectiveModel,
|
|
687
|
+
sessionId: prepared.sessionId,
|
|
688
|
+
toolDebugMissing: prepared.toolDebugMissing,
|
|
689
|
+
toolDebugSummary: prepared.toolDebugSummary,
|
|
690
|
+
toolDebugPayload: prepared.toolDebugPayload,
|
|
691
|
+
};
|
|
692
|
+
shouldSwitchAccount = true;
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
const quotaMsg = bodyInfo.quotaResetTime
|
|
697
|
+
? `Quota resets ${bodyInfo.quotaResetTime}`
|
|
698
|
+
: `Waiting ${waitTimeFormatted}`;
|
|
699
|
+
await showToast(`Rate limited. ${quotaMsg} (attempt ${attempt})...`, "warning");
|
|
700
|
+
lastFailure = {
|
|
701
|
+
response,
|
|
702
|
+
streaming: prepared.streaming,
|
|
703
|
+
debugContext,
|
|
704
|
+
requestedModel: prepared.requestedModel,
|
|
705
|
+
projectId: prepared.projectId,
|
|
706
|
+
endpoint: prepared.endpoint,
|
|
707
|
+
effectiveModel: prepared.effectiveModel,
|
|
708
|
+
sessionId: prepared.sessionId,
|
|
709
|
+
toolDebugMissing: prepared.toolDebugMissing,
|
|
710
|
+
toolDebugSummary: prepared.toolDebugSummary,
|
|
711
|
+
toolDebugPayload: prepared.toolDebugPayload,
|
|
712
|
+
};
|
|
713
|
+
await sleep(delayMs, abortSignal);
|
|
714
|
+
shouldSwitchAccount = true;
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
409
717
|
}
|
|
410
|
-
|
|
411
|
-
|
|
718
|
+
// Success - reset rate limit backoff state
|
|
719
|
+
resetRateLimitState(account.index);
|
|
720
|
+
const shouldRetryEndpoint = (response.status === 403 ||
|
|
721
|
+
response.status === 404 ||
|
|
722
|
+
response.status >= 500);
|
|
723
|
+
if (shouldRetryEndpoint) {
|
|
724
|
+
await logResponseBody(debugContext, response, response.status);
|
|
412
725
|
}
|
|
413
|
-
|
|
414
|
-
if (accountManager.getAccountCount() > 1) {
|
|
415
|
-
// Multiple accounts - switch to next
|
|
416
|
-
await showToast(`Rate limited on ${accountLabel}. Switching...`, "warning");
|
|
726
|
+
if (shouldRetryEndpoint && i < ANTIGRAVITY_ENDPOINT_FALLBACKS.length - 1) {
|
|
417
727
|
lastFailure = {
|
|
418
728
|
response,
|
|
419
729
|
streaming: prepared.streaming,
|
|
@@ -427,133 +737,239 @@ export const createAntigravityPlugin = (providerId) => async ({ client }) => ({
|
|
|
427
737
|
toolDebugSummary: prepared.toolDebugSummary,
|
|
428
738
|
toolDebugPayload: prepared.toolDebugPayload,
|
|
429
739
|
};
|
|
430
|
-
|
|
431
|
-
break;
|
|
740
|
+
continue;
|
|
432
741
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
streaming: prepared.streaming,
|
|
440
|
-
debugContext,
|
|
441
|
-
requestedModel: prepared.requestedModel,
|
|
442
|
-
projectId: prepared.projectId,
|
|
443
|
-
endpoint: prepared.endpoint,
|
|
444
|
-
effectiveModel: prepared.effectiveModel,
|
|
445
|
-
sessionId: prepared.sessionId,
|
|
446
|
-
toolDebugMissing: prepared.toolDebugMissing,
|
|
447
|
-
toolDebugSummary: prepared.toolDebugSummary,
|
|
448
|
-
toolDebugPayload: prepared.toolDebugPayload,
|
|
449
|
-
};
|
|
450
|
-
// Wait and let the outer loop retry
|
|
451
|
-
await sleep(retryAfterMs, abortSignal);
|
|
452
|
-
shouldSwitchAccount = true;
|
|
453
|
-
break;
|
|
742
|
+
// Success or non-retryable error - return the response
|
|
743
|
+
logAntigravityDebugResponse(debugContext, response, {
|
|
744
|
+
note: response.ok ? "Success" : `Error ${response.status}`,
|
|
745
|
+
});
|
|
746
|
+
if (!response.ok) {
|
|
747
|
+
await logResponseBody(debugContext, response, response.status);
|
|
454
748
|
}
|
|
749
|
+
return transformAntigravityResponse(response, prepared.streaming, debugContext, prepared.requestedModel, prepared.projectId, prepared.endpoint, prepared.effectiveModel, prepared.sessionId, prepared.toolDebugMissing, prepared.toolDebugSummary, prepared.toolDebugPayload, debugLines);
|
|
455
750
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
streaming: prepared.streaming,
|
|
463
|
-
debugContext,
|
|
464
|
-
requestedModel: prepared.requestedModel,
|
|
465
|
-
projectId: prepared.projectId,
|
|
466
|
-
endpoint: prepared.endpoint,
|
|
467
|
-
effectiveModel: prepared.effectiveModel,
|
|
468
|
-
sessionId: prepared.sessionId,
|
|
469
|
-
toolDebugMissing: prepared.toolDebugMissing,
|
|
470
|
-
toolDebugSummary: prepared.toolDebugSummary,
|
|
471
|
-
toolDebugPayload: prepared.toolDebugPayload,
|
|
472
|
-
};
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
// Success or non-retryable error - return the response
|
|
476
|
-
return transformAntigravityResponse(response, prepared.streaming, debugContext, prepared.requestedModel, prepared.projectId, prepared.endpoint, prepared.effectiveModel, prepared.sessionId, prepared.toolDebugMissing, prepared.toolDebugSummary, prepared.toolDebugPayload);
|
|
477
|
-
}
|
|
478
|
-
catch (error) {
|
|
479
|
-
if (i < ANTIGRAVITY_ENDPOINT_FALLBACKS.length - 1) {
|
|
751
|
+
catch (error) {
|
|
752
|
+
if (i < ANTIGRAVITY_ENDPOINT_FALLBACKS.length - 1) {
|
|
753
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
// All endpoints failed for this account - try next account
|
|
480
757
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
481
|
-
|
|
758
|
+
shouldSwitchAccount = true;
|
|
759
|
+
break;
|
|
482
760
|
}
|
|
483
|
-
// All endpoints failed for this account - try next account
|
|
484
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
485
|
-
shouldSwitchAccount = true;
|
|
486
|
-
break;
|
|
487
761
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
continue;
|
|
491
|
-
}
|
|
492
|
-
// If we get here without returning, something went wrong
|
|
493
|
-
if (lastFailure) {
|
|
494
|
-
return transformAntigravityResponse(lastFailure.response, lastFailure.streaming, lastFailure.debugContext, lastFailure.requestedModel, lastFailure.projectId, lastFailure.endpoint, lastFailure.effectiveModel, lastFailure.sessionId, lastFailure.toolDebugMissing, lastFailure.toolDebugSummary, lastFailure.toolDebugPayload);
|
|
495
|
-
}
|
|
496
|
-
throw lastError || new Error("All Antigravity accounts failed");
|
|
497
|
-
}
|
|
498
|
-
},
|
|
499
|
-
};
|
|
500
|
-
},
|
|
501
|
-
methods: [
|
|
502
|
-
{
|
|
503
|
-
label: "OAuth with Google (Antigravity)",
|
|
504
|
-
type: "oauth",
|
|
505
|
-
authorize: async (inputs) => {
|
|
506
|
-
const isHeadless = !!(process.env.SSH_CONNECTION ||
|
|
507
|
-
process.env.SSH_CLIENT ||
|
|
508
|
-
process.env.SSH_TTY ||
|
|
509
|
-
process.env.OPENCODE_HEADLESS);
|
|
510
|
-
// CLI flow (`opencode auth login`) passes an inputs object.
|
|
511
|
-
if (inputs) {
|
|
512
|
-
const accounts = [];
|
|
513
|
-
// Check for existing accounts and prompt user for login mode
|
|
514
|
-
let startFresh = true;
|
|
515
|
-
const existingStorage = await loadAccounts();
|
|
516
|
-
if (existingStorage && existingStorage.accounts.length > 0) {
|
|
517
|
-
const existingAccounts = existingStorage.accounts.map((acc, idx) => ({
|
|
518
|
-
email: acc.email,
|
|
519
|
-
index: idx,
|
|
520
|
-
}));
|
|
521
|
-
const loginMode = await promptLoginMode(existingAccounts);
|
|
522
|
-
startFresh = loginMode === "fresh";
|
|
523
|
-
if (startFresh) {
|
|
524
|
-
console.log("\nStarting fresh - existing accounts will be replaced.\n");
|
|
762
|
+
if (shouldSwitchAccount) {
|
|
763
|
+
continue;
|
|
525
764
|
}
|
|
526
|
-
|
|
527
|
-
|
|
765
|
+
// If we get here without returning, something went wrong
|
|
766
|
+
if (lastFailure) {
|
|
767
|
+
return transformAntigravityResponse(lastFailure.response, lastFailure.streaming, lastFailure.debugContext, lastFailure.requestedModel, lastFailure.projectId, lastFailure.endpoint, lastFailure.effectiveModel, lastFailure.sessionId, lastFailure.toolDebugMissing, lastFailure.toolDebugSummary, lastFailure.toolDebugPayload, debugLines);
|
|
528
768
|
}
|
|
769
|
+
throw lastError || new Error("All Antigravity accounts failed");
|
|
529
770
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
771
|
+
},
|
|
772
|
+
};
|
|
773
|
+
},
|
|
774
|
+
methods: [
|
|
775
|
+
{
|
|
776
|
+
label: "OAuth with Google (Antigravity)",
|
|
777
|
+
type: "oauth",
|
|
778
|
+
authorize: async (inputs) => {
|
|
779
|
+
const isHeadless = !!(process.env.SSH_CONNECTION ||
|
|
780
|
+
process.env.SSH_CLIENT ||
|
|
781
|
+
process.env.SSH_TTY ||
|
|
782
|
+
process.env.OPENCODE_HEADLESS);
|
|
783
|
+
// CLI flow (`opencode auth login`) passes an inputs object.
|
|
784
|
+
if (inputs) {
|
|
785
|
+
const accounts = [];
|
|
786
|
+
// Check for existing accounts and prompt user for login mode
|
|
787
|
+
let startFresh = true;
|
|
788
|
+
const existingStorage = await loadAccounts();
|
|
789
|
+
if (existingStorage && existingStorage.accounts.length > 0) {
|
|
790
|
+
const existingAccounts = existingStorage.accounts.map((acc, idx) => ({
|
|
791
|
+
email: acc.email,
|
|
792
|
+
index: idx,
|
|
793
|
+
}));
|
|
794
|
+
const loginMode = await promptLoginMode(existingAccounts);
|
|
795
|
+
startFresh = loginMode === "fresh";
|
|
796
|
+
if (startFresh) {
|
|
797
|
+
console.log("\nStarting fresh - existing accounts will be replaced.\n");
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
console.log("\nAdding to existing accounts.\n");
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
while (accounts.length < MAX_OAUTH_ACCOUNTS) {
|
|
804
|
+
console.log(`\n=== Antigravity OAuth (Account ${accounts.length + 1}) ===`);
|
|
805
|
+
const projectId = await promptProjectId();
|
|
806
|
+
const result = await (async () => {
|
|
807
|
+
let listener = null;
|
|
808
|
+
if (!isHeadless) {
|
|
809
|
+
try {
|
|
810
|
+
listener = await startOAuthListener();
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
listener = null;
|
|
814
|
+
}
|
|
538
815
|
}
|
|
539
|
-
|
|
540
|
-
|
|
816
|
+
const authorization = await authorizeAntigravity(projectId);
|
|
817
|
+
const fallbackState = getStateFromAuthorizationUrl(authorization.url);
|
|
818
|
+
console.log("\nOAuth URL:\n" + authorization.url + "\n");
|
|
819
|
+
if (!isHeadless) {
|
|
820
|
+
await openBrowser(authorization.url);
|
|
821
|
+
}
|
|
822
|
+
if (listener) {
|
|
823
|
+
try {
|
|
824
|
+
const callbackUrl = await listener.waitForCallback();
|
|
825
|
+
const params = extractOAuthCallbackParams(callbackUrl);
|
|
826
|
+
if (!params) {
|
|
827
|
+
return { type: "failed", error: "Missing code or state in callback URL" };
|
|
828
|
+
}
|
|
829
|
+
return exchangeAntigravity(params.code, params.state);
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
return {
|
|
833
|
+
type: "failed",
|
|
834
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
finally {
|
|
838
|
+
try {
|
|
839
|
+
await listener.close();
|
|
840
|
+
}
|
|
841
|
+
catch {
|
|
842
|
+
// ignore
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
console.log("1. Open the URL below in your browser and complete Google sign-in.");
|
|
847
|
+
console.log("2. After approving, copy the full redirected localhost URL from the address bar.");
|
|
848
|
+
console.log("3. Paste it back here.");
|
|
849
|
+
const callbackInput = await promptOAuthCallbackValue("Paste the redirect URL (or just the code) here: ");
|
|
850
|
+
const params = parseOAuthCallbackInput(callbackInput, fallbackState);
|
|
851
|
+
if ("error" in params) {
|
|
852
|
+
return { type: "failed", error: params.error };
|
|
853
|
+
}
|
|
854
|
+
return exchangeAntigravity(params.code, params.state);
|
|
855
|
+
})();
|
|
856
|
+
if (result.type === "failed") {
|
|
857
|
+
if (accounts.length === 0) {
|
|
858
|
+
return {
|
|
859
|
+
url: "",
|
|
860
|
+
instructions: `Authentication failed: ${result.error}`,
|
|
861
|
+
method: "auto",
|
|
862
|
+
callback: async () => result,
|
|
863
|
+
};
|
|
541
864
|
}
|
|
865
|
+
console.warn(`[opencode-antigravity-auth] Skipping failed account ${accounts.length + 1}: ${result.error}`);
|
|
866
|
+
break;
|
|
542
867
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
868
|
+
accounts.push(result);
|
|
869
|
+
// Show toast for successful account authentication
|
|
870
|
+
try {
|
|
871
|
+
await client.tui.showToast({
|
|
872
|
+
body: {
|
|
873
|
+
message: `Account ${accounts.length} authenticated${result.email ? ` (${result.email})` : ""}`,
|
|
874
|
+
variant: "success",
|
|
875
|
+
},
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
catch {
|
|
879
|
+
// TUI may not be available in CLI mode
|
|
880
|
+
}
|
|
881
|
+
try {
|
|
882
|
+
// Use startFresh only on first account, subsequent accounts always append
|
|
883
|
+
const isFirstAccount = accounts.length === 1;
|
|
884
|
+
await persistAccountPool([result], isFirstAccount && startFresh);
|
|
548
885
|
}
|
|
549
|
-
|
|
886
|
+
catch {
|
|
887
|
+
// ignore
|
|
888
|
+
}
|
|
889
|
+
if (accounts.length >= MAX_OAUTH_ACCOUNTS) {
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
const addAnother = await promptAddAnotherAccount(accounts.length);
|
|
893
|
+
if (!addAnother) {
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
const primary = accounts[0];
|
|
898
|
+
if (!primary) {
|
|
899
|
+
return {
|
|
900
|
+
url: "",
|
|
901
|
+
instructions: "Authentication cancelled",
|
|
902
|
+
method: "auto",
|
|
903
|
+
callback: async () => ({ type: "failed", error: "Authentication cancelled" }),
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
return {
|
|
907
|
+
url: "",
|
|
908
|
+
instructions: `Multi-account setup complete (${accounts.length} account(s)).`,
|
|
909
|
+
method: "auto",
|
|
910
|
+
callback: async () => primary,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
// TUI flow (`/connect`) does not support per-account prompts.
|
|
914
|
+
// Default to adding new accounts (non-destructive).
|
|
915
|
+
// Users can run `opencode auth logout` first if they want a fresh start.
|
|
916
|
+
const projectId = "";
|
|
917
|
+
// Check existing accounts count for toast message
|
|
918
|
+
const existingStorage = await loadAccounts();
|
|
919
|
+
const existingCount = existingStorage?.accounts.length ?? 0;
|
|
920
|
+
let listener = null;
|
|
921
|
+
if (!isHeadless) {
|
|
922
|
+
try {
|
|
923
|
+
listener = await startOAuthListener();
|
|
924
|
+
}
|
|
925
|
+
catch {
|
|
926
|
+
listener = null;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
const authorization = await authorizeAntigravity(projectId);
|
|
930
|
+
const fallbackState = getStateFromAuthorizationUrl(authorization.url);
|
|
931
|
+
if (!isHeadless) {
|
|
932
|
+
await openBrowser(authorization.url);
|
|
933
|
+
}
|
|
934
|
+
if (listener) {
|
|
935
|
+
return {
|
|
936
|
+
url: authorization.url,
|
|
937
|
+
instructions: "Complete sign-in in your browser. We'll automatically detect the redirect back to localhost.",
|
|
938
|
+
method: "auto",
|
|
939
|
+
callback: async () => {
|
|
550
940
|
try {
|
|
551
941
|
const callbackUrl = await listener.waitForCallback();
|
|
552
942
|
const params = extractOAuthCallbackParams(callbackUrl);
|
|
553
943
|
if (!params) {
|
|
554
944
|
return { type: "failed", error: "Missing code or state in callback URL" };
|
|
555
945
|
}
|
|
556
|
-
|
|
946
|
+
const result = await exchangeAntigravity(params.code, params.state);
|
|
947
|
+
if (result.type === "success") {
|
|
948
|
+
try {
|
|
949
|
+
// TUI flow adds to existing accounts (non-destructive)
|
|
950
|
+
await persistAccountPool([result], false);
|
|
951
|
+
}
|
|
952
|
+
catch {
|
|
953
|
+
// ignore
|
|
954
|
+
}
|
|
955
|
+
// Show appropriate toast message
|
|
956
|
+
const newTotal = existingCount + 1;
|
|
957
|
+
const toastMessage = existingCount > 0
|
|
958
|
+
? `Added account${result.email ? ` (${result.email})` : ""} - ${newTotal} total`
|
|
959
|
+
: `Authenticated${result.email ? ` (${result.email})` : ""}`;
|
|
960
|
+
try {
|
|
961
|
+
await client.tui.showToast({
|
|
962
|
+
body: {
|
|
963
|
+
message: toastMessage,
|
|
964
|
+
variant: "success",
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
// TUI may not be available
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return result;
|
|
557
973
|
}
|
|
558
974
|
catch (error) {
|
|
559
975
|
return {
|
|
@@ -569,199 +985,57 @@ export const createAntigravityPlugin = (providerId) => async ({ client }) => ({
|
|
|
569
985
|
// ignore
|
|
570
986
|
}
|
|
571
987
|
}
|
|
572
|
-
}
|
|
573
|
-
console.log("1. Open the URL below in your browser and complete Google sign-in.");
|
|
574
|
-
console.log("2. After approving, copy the full redirected localhost URL from the address bar.");
|
|
575
|
-
console.log("3. Paste it back here.");
|
|
576
|
-
const callbackInput = await promptOAuthCallbackValue("Paste the redirect URL (or just the code) here: ");
|
|
577
|
-
const params = parseOAuthCallbackInput(callbackInput, fallbackState);
|
|
578
|
-
if ("error" in params) {
|
|
579
|
-
return { type: "failed", error: params.error };
|
|
580
|
-
}
|
|
581
|
-
return exchangeAntigravity(params.code, params.state);
|
|
582
|
-
})();
|
|
583
|
-
if (result.type === "failed") {
|
|
584
|
-
if (accounts.length === 0) {
|
|
585
|
-
return {
|
|
586
|
-
url: "",
|
|
587
|
-
instructions: `Authentication failed: ${result.error}`,
|
|
588
|
-
method: "auto",
|
|
589
|
-
callback: async () => result,
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
console.warn(`[opencode-antigravity-auth] Skipping failed account ${accounts.length + 1}: ${result.error}`);
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
accounts.push(result);
|
|
596
|
-
// Show toast for successful account authentication
|
|
597
|
-
try {
|
|
598
|
-
await client.tui.showToast({
|
|
599
|
-
body: {
|
|
600
|
-
message: `Account ${accounts.length} authenticated${result.email ? ` (${result.email})` : ""}`,
|
|
601
|
-
variant: "success",
|
|
602
|
-
},
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
catch {
|
|
606
|
-
// TUI may not be available in CLI mode
|
|
607
|
-
}
|
|
608
|
-
try {
|
|
609
|
-
// Use startFresh only on first account, subsequent accounts always append
|
|
610
|
-
const isFirstAccount = accounts.length === 1;
|
|
611
|
-
await persistAccountPool([result], isFirstAccount && startFresh);
|
|
612
|
-
}
|
|
613
|
-
catch {
|
|
614
|
-
// ignore
|
|
615
|
-
}
|
|
616
|
-
if (accounts.length >= MAX_OAUTH_ACCOUNTS) {
|
|
617
|
-
break;
|
|
618
|
-
}
|
|
619
|
-
const addAnother = await promptAddAnotherAccount(accounts.length);
|
|
620
|
-
if (!addAnother) {
|
|
621
|
-
break;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
const primary = accounts[0];
|
|
625
|
-
if (!primary) {
|
|
626
|
-
return {
|
|
627
|
-
url: "",
|
|
628
|
-
instructions: "Authentication cancelled",
|
|
629
|
-
method: "auto",
|
|
630
|
-
callback: async () => ({ type: "failed", error: "Authentication cancelled" }),
|
|
988
|
+
},
|
|
631
989
|
};
|
|
632
990
|
}
|
|
633
|
-
return {
|
|
634
|
-
url: "",
|
|
635
|
-
instructions: `Multi-account setup complete (${accounts.length} account(s)).`,
|
|
636
|
-
method: "auto",
|
|
637
|
-
callback: async () => primary,
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
// TUI flow (`/connect`) does not support per-account prompts.
|
|
641
|
-
// Default to adding new accounts (non-destructive).
|
|
642
|
-
// Users can run `opencode auth logout` first if they want a fresh start.
|
|
643
|
-
const projectId = "";
|
|
644
|
-
// Check existing accounts count for toast message
|
|
645
|
-
const existingStorage = await loadAccounts();
|
|
646
|
-
const existingCount = existingStorage?.accounts.length ?? 0;
|
|
647
|
-
let listener = null;
|
|
648
|
-
if (!isHeadless) {
|
|
649
|
-
try {
|
|
650
|
-
listener = await startOAuthListener();
|
|
651
|
-
}
|
|
652
|
-
catch {
|
|
653
|
-
listener = null;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
const authorization = await authorizeAntigravity(projectId);
|
|
657
|
-
const fallbackState = getStateFromAuthorizationUrl(authorization.url);
|
|
658
|
-
if (!isHeadless) {
|
|
659
|
-
await openBrowser(authorization.url);
|
|
660
|
-
}
|
|
661
|
-
if (listener) {
|
|
662
991
|
return {
|
|
663
992
|
url: authorization.url,
|
|
664
|
-
instructions: "
|
|
665
|
-
method: "
|
|
666
|
-
callback: async () => {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (!params) {
|
|
671
|
-
return { type: "failed", error: "Missing code or state in callback URL" };
|
|
672
|
-
}
|
|
673
|
-
const result = await exchangeAntigravity(params.code, params.state);
|
|
674
|
-
if (result.type === "success") {
|
|
675
|
-
try {
|
|
676
|
-
// TUI flow adds to existing accounts (non-destructive)
|
|
677
|
-
await persistAccountPool([result], false);
|
|
678
|
-
}
|
|
679
|
-
catch {
|
|
680
|
-
// ignore
|
|
681
|
-
}
|
|
682
|
-
// Show appropriate toast message
|
|
683
|
-
const newTotal = existingCount + 1;
|
|
684
|
-
const toastMessage = existingCount > 0
|
|
685
|
-
? `Added account${result.email ? ` (${result.email})` : ""} - ${newTotal} total`
|
|
686
|
-
: `Authenticated${result.email ? ` (${result.email})` : ""}`;
|
|
687
|
-
try {
|
|
688
|
-
await client.tui.showToast({
|
|
689
|
-
body: {
|
|
690
|
-
message: toastMessage,
|
|
691
|
-
variant: "success",
|
|
692
|
-
},
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
catch {
|
|
696
|
-
// TUI may not be available
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
return result;
|
|
700
|
-
}
|
|
701
|
-
catch (error) {
|
|
702
|
-
return {
|
|
703
|
-
type: "failed",
|
|
704
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
705
|
-
};
|
|
993
|
+
instructions: "Visit the URL above, complete OAuth, then paste either the full redirect URL or the authorization code.",
|
|
994
|
+
method: "code",
|
|
995
|
+
callback: async (codeInput) => {
|
|
996
|
+
const params = parseOAuthCallbackInput(codeInput, fallbackState);
|
|
997
|
+
if ("error" in params) {
|
|
998
|
+
return { type: "failed", error: params.error };
|
|
706
999
|
}
|
|
707
|
-
|
|
1000
|
+
const result = await exchangeAntigravity(params.code, params.state);
|
|
1001
|
+
if (result.type === "success") {
|
|
708
1002
|
try {
|
|
709
|
-
|
|
1003
|
+
// TUI flow adds to existing accounts (non-destructive)
|
|
1004
|
+
await persistAccountPool([result], false);
|
|
710
1005
|
}
|
|
711
1006
|
catch {
|
|
712
1007
|
// ignore
|
|
713
1008
|
}
|
|
1009
|
+
// Show appropriate toast message
|
|
1010
|
+
const newTotal = existingCount + 1;
|
|
1011
|
+
const toastMessage = existingCount > 0
|
|
1012
|
+
? `Added account${result.email ? ` (${result.email})` : ""} - ${newTotal} total`
|
|
1013
|
+
: `Authenticated${result.email ? ` (${result.email})` : ""}`;
|
|
1014
|
+
try {
|
|
1015
|
+
await client.tui.showToast({
|
|
1016
|
+
body: {
|
|
1017
|
+
message: toastMessage,
|
|
1018
|
+
variant: "success",
|
|
1019
|
+
},
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
catch {
|
|
1023
|
+
// TUI may not be available
|
|
1024
|
+
}
|
|
714
1025
|
}
|
|
1026
|
+
return result;
|
|
715
1027
|
},
|
|
716
1028
|
};
|
|
717
|
-
}
|
|
718
|
-
return {
|
|
719
|
-
url: authorization.url,
|
|
720
|
-
instructions: "Visit the URL above, complete OAuth, then paste either the full redirect URL or the authorization code.",
|
|
721
|
-
method: "code",
|
|
722
|
-
callback: async (codeInput) => {
|
|
723
|
-
const params = parseOAuthCallbackInput(codeInput, fallbackState);
|
|
724
|
-
if ("error" in params) {
|
|
725
|
-
return { type: "failed", error: params.error };
|
|
726
|
-
}
|
|
727
|
-
const result = await exchangeAntigravity(params.code, params.state);
|
|
728
|
-
if (result.type === "success") {
|
|
729
|
-
try {
|
|
730
|
-
// TUI flow adds to existing accounts (non-destructive)
|
|
731
|
-
await persistAccountPool([result], false);
|
|
732
|
-
}
|
|
733
|
-
catch {
|
|
734
|
-
// ignore
|
|
735
|
-
}
|
|
736
|
-
// Show appropriate toast message
|
|
737
|
-
const newTotal = existingCount + 1;
|
|
738
|
-
const toastMessage = existingCount > 0
|
|
739
|
-
? `Added account${result.email ? ` (${result.email})` : ""} - ${newTotal} total`
|
|
740
|
-
: `Authenticated${result.email ? ` (${result.email})` : ""}`;
|
|
741
|
-
try {
|
|
742
|
-
await client.tui.showToast({
|
|
743
|
-
body: {
|
|
744
|
-
message: toastMessage,
|
|
745
|
-
variant: "success",
|
|
746
|
-
},
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
catch {
|
|
750
|
-
// TUI may not be available
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
return result;
|
|
754
|
-
},
|
|
755
|
-
};
|
|
1029
|
+
},
|
|
756
1030
|
},
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}
|
|
764
|
-
}
|
|
1031
|
+
{
|
|
1032
|
+
label: "Manually enter API Key",
|
|
1033
|
+
type: "api",
|
|
1034
|
+
},
|
|
1035
|
+
],
|
|
1036
|
+
},
|
|
1037
|
+
};
|
|
1038
|
+
};
|
|
765
1039
|
export const AntigravityCLIOAuthPlugin = createAntigravityPlugin(ANTIGRAVITY_PROVIDER_ID);
|
|
766
1040
|
export const GoogleOAuthPlugin = AntigravityCLIOAuthPlugin;
|
|
767
1041
|
function toUrlString(value) {
|
|
@@ -774,4 +1048,33 @@ function toUrlString(value) {
|
|
|
774
1048
|
}
|
|
775
1049
|
return value.toString();
|
|
776
1050
|
}
|
|
1051
|
+
function toWarmupStreamUrl(value) {
|
|
1052
|
+
const urlString = toUrlString(value);
|
|
1053
|
+
try {
|
|
1054
|
+
const url = new URL(urlString);
|
|
1055
|
+
if (!url.pathname.includes(":streamGenerateContent")) {
|
|
1056
|
+
url.pathname = url.pathname.replace(":generateContent", ":streamGenerateContent");
|
|
1057
|
+
}
|
|
1058
|
+
url.searchParams.set("alt", "sse");
|
|
1059
|
+
return url.toString();
|
|
1060
|
+
}
|
|
1061
|
+
catch {
|
|
1062
|
+
return urlString;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function extractModelFromUrl(urlString) {
|
|
1066
|
+
const match = urlString.match(/\/models\/([^:\/?]+)(?::\w+)?/);
|
|
1067
|
+
return match?.[1] ?? null;
|
|
1068
|
+
}
|
|
1069
|
+
function getModelFamilyFromUrl(urlString) {
|
|
1070
|
+
const model = extractModelFromUrl(urlString);
|
|
1071
|
+
let family = "gemini";
|
|
1072
|
+
if (model && model.includes("claude")) {
|
|
1073
|
+
family = "claude";
|
|
1074
|
+
}
|
|
1075
|
+
if (isDebugEnabled()) {
|
|
1076
|
+
logModelFamily(urlString, model, family);
|
|
1077
|
+
}
|
|
1078
|
+
return family;
|
|
1079
|
+
}
|
|
777
1080
|
//# sourceMappingURL=plugin.js.map
|