opencode-antigravity-auth 1.6.5-beta.0 → 2.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.
Files changed (143) hide show
  1. package/README.md +34 -694
  2. package/dist/index.d.ts +10 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +6 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/src/antigravity/oauth.d.ts +0 -28
  7. package/dist/src/antigravity/oauth.d.ts.map +1 -1
  8. package/dist/src/antigravity/oauth.js +25 -103
  9. package/dist/src/antigravity/oauth.js.map +1 -1
  10. package/dist/src/constants.d.ts +53 -32
  11. package/dist/src/constants.d.ts.map +1 -1
  12. package/dist/src/constants.js +64 -56
  13. package/dist/src/constants.js.map +1 -1
  14. package/dist/src/hooks/auto-update-checker/cache.js +1 -1
  15. package/dist/src/hooks/auto-update-checker/checker.d.ts +1 -1
  16. package/dist/src/hooks/auto-update-checker/checker.js +2 -2
  17. package/dist/src/hooks/auto-update-checker/index.d.ts +4 -4
  18. package/dist/src/hooks/auto-update-checker/index.js +6 -6
  19. package/dist/src/hooks/auto-update-checker/logging.js +1 -1
  20. package/dist/src/plugin/account-management.d.ts +67 -0
  21. package/dist/src/plugin/account-management.d.ts.map +1 -0
  22. package/dist/src/plugin/account-management.js +174 -0
  23. package/dist/src/plugin/account-management.js.map +1 -0
  24. package/dist/src/plugin/account-rpc.d.ts +70 -0
  25. package/dist/src/plugin/account-rpc.d.ts.map +1 -0
  26. package/dist/src/plugin/account-rpc.js +28 -0
  27. package/dist/src/plugin/account-rpc.js.map +1 -0
  28. package/dist/src/plugin/accounts.d.ts +32 -19
  29. package/dist/src/plugin/accounts.d.ts.map +1 -1
  30. package/dist/src/plugin/accounts.js +203 -34
  31. package/dist/src/plugin/accounts.js.map +1 -1
  32. package/dist/src/plugin/auth.d.ts +1 -1
  33. package/dist/src/plugin/cache/index.d.ts +1 -1
  34. package/dist/src/plugin/cache/index.js +1 -1
  35. package/dist/src/plugin/cache/signature-cache.d.ts +1 -1
  36. package/dist/src/plugin/cache/signature-cache.js +1 -1
  37. package/dist/src/plugin/cache.d.ts +7 -5
  38. package/dist/src/plugin/cache.d.ts.map +1 -1
  39. package/dist/src/plugin/cache.js +20 -3
  40. package/dist/src/plugin/cache.js.map +1 -1
  41. package/dist/src/plugin/cli.d.ts +4 -6
  42. package/dist/src/plugin/cli.d.ts.map +1 -1
  43. package/dist/src/plugin/cli.js +5 -11
  44. package/dist/src/plugin/cli.js.map +1 -1
  45. package/dist/src/plugin/config/index.d.ts +2 -2
  46. package/dist/src/plugin/config/index.js +2 -2
  47. package/dist/src/plugin/config/loader.d.ts +1 -1
  48. package/dist/src/plugin/config/loader.js +2 -2
  49. package/dist/src/plugin/config/models-v2.d.ts +54 -0
  50. package/dist/src/plugin/config/models-v2.d.ts.map +1 -0
  51. package/dist/src/plugin/config/models-v2.js +62 -0
  52. package/dist/src/plugin/config/models-v2.js.map +1 -0
  53. package/dist/src/plugin/config/models.d.ts +1 -1
  54. package/dist/src/plugin/config/updater.d.ts +16 -3
  55. package/dist/src/plugin/config/updater.d.ts.map +1 -1
  56. package/dist/src/plugin/config/updater.js +53 -10
  57. package/dist/src/plugin/config/updater.js.map +1 -1
  58. package/dist/src/plugin/core/streaming/index.d.ts +2 -2
  59. package/dist/src/plugin/core/streaming/index.js +2 -2
  60. package/dist/src/plugin/core/streaming/transformer.d.ts +1 -1
  61. package/dist/src/plugin/core/streaming/transformer.js +1 -1
  62. package/dist/src/plugin/debug.d.ts +1 -1
  63. package/dist/src/plugin/debug.js +2 -2
  64. package/dist/src/plugin/fingerprint.d.ts +8 -5
  65. package/dist/src/plugin/fingerprint.d.ts.map +1 -1
  66. package/dist/src/plugin/fingerprint.js +23 -41
  67. package/dist/src/plugin/fingerprint.js.map +1 -1
  68. package/dist/src/plugin/logger.d.ts +1 -1
  69. package/dist/src/plugin/logger.js +2 -2
  70. package/dist/src/plugin/project.d.ts +4 -4
  71. package/dist/src/plugin/project.d.ts.map +1 -1
  72. package/dist/src/plugin/project.js +95 -94
  73. package/dist/src/plugin/project.js.map +1 -1
  74. package/dist/src/plugin/quota.d.ts +35 -3
  75. package/dist/src/plugin/quota.d.ts.map +1 -1
  76. package/dist/src/plugin/quota.js +54 -25
  77. package/dist/src/plugin/quota.js.map +1 -1
  78. package/dist/src/plugin/recovery/index.d.ts +3 -3
  79. package/dist/src/plugin/recovery/index.js +3 -3
  80. package/dist/src/plugin/recovery/storage.d.ts +1 -1
  81. package/dist/src/plugin/recovery/storage.js +1 -1
  82. package/dist/src/plugin/recovery.d.ts +3 -3
  83. package/dist/src/plugin/recovery.js +3 -3
  84. package/dist/src/plugin/refresh-queue.d.ts +6 -3
  85. package/dist/src/plugin/refresh-queue.d.ts.map +1 -1
  86. package/dist/src/plugin/refresh-queue.js +23 -6
  87. package/dist/src/plugin/refresh-queue.js.map +1 -1
  88. package/dist/src/plugin/request-helpers.d.ts +1 -1
  89. package/dist/src/plugin/request-helpers.js +5 -5
  90. package/dist/src/plugin/request.d.ts +9 -6
  91. package/dist/src/plugin/request.d.ts.map +1 -1
  92. package/dist/src/plugin/request.js +23 -23
  93. package/dist/src/plugin/request.js.map +1 -1
  94. package/dist/src/plugin/search.js +7 -7
  95. package/dist/src/plugin/search.js.map +1 -1
  96. package/dist/src/plugin/server.js +1 -1
  97. package/dist/src/plugin/storage.d.ts +18 -5
  98. package/dist/src/plugin/storage.d.ts.map +1 -1
  99. package/dist/src/plugin/storage.js +117 -69
  100. package/dist/src/plugin/storage.js.map +1 -1
  101. package/dist/src/plugin/stores/signature-store.d.ts +1 -1
  102. package/dist/src/plugin/token.d.ts +2 -4
  103. package/dist/src/plugin/token.d.ts.map +1 -1
  104. package/dist/src/plugin/token.js +48 -8
  105. package/dist/src/plugin/token.js.map +1 -1
  106. package/dist/src/plugin/transform/claude.d.ts +1 -1
  107. package/dist/src/plugin/transform/claude.js +1 -1
  108. package/dist/src/plugin/transform/cross-model-sanitizer.js +2 -2
  109. package/dist/src/plugin/transform/gemini.d.ts +1 -1
  110. package/dist/src/plugin/transform/index.d.ts +9 -9
  111. package/dist/src/plugin/transform/index.js +4 -4
  112. package/dist/src/plugin/transform/model-resolver.d.ts +9 -1
  113. package/dist/src/plugin/transform/model-resolver.d.ts.map +1 -1
  114. package/dist/src/plugin/transform/model-resolver.js +34 -4
  115. package/dist/src/plugin/transform/model-resolver.js.map +1 -1
  116. package/dist/src/plugin/transform/types.d.ts +1 -1
  117. package/dist/src/plugin/types.d.ts +31 -2
  118. package/dist/src/plugin/types.d.ts.map +1 -1
  119. package/dist/src/plugin/ui/auth-menu.d.ts +1 -4
  120. package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -1
  121. package/dist/src/plugin/ui/auth-menu.js +8 -16
  122. package/dist/src/plugin/ui/auth-menu.js.map +1 -1
  123. package/dist/src/plugin/ui/confirm.js +1 -1
  124. package/dist/src/plugin/ui/select.js +1 -1
  125. package/dist/src/plugin/version.d.ts +35 -6
  126. package/dist/src/plugin/version.d.ts.map +1 -1
  127. package/dist/src/plugin/version.js +138 -42
  128. package/dist/src/plugin/version.js.map +1 -1
  129. package/dist/src/plugin.d.ts +11 -8
  130. package/dist/src/plugin.d.ts.map +1 -1
  131. package/dist/src/plugin.js +170 -291
  132. package/dist/src/plugin.js.map +1 -1
  133. package/dist/src/tui.d.ts +64 -0
  134. package/dist/src/tui.d.ts.map +1 -0
  135. package/dist/src/tui.js +124 -0
  136. package/dist/src/tui.js.map +1 -0
  137. package/dist/src/v2.d.ts +4 -0
  138. package/dist/src/v2.d.ts.map +1 -0
  139. package/dist/src/v2.js +672 -0
  140. package/dist/src/v2.js.map +1 -0
  141. package/index.js +4 -0
  142. package/package.json +21 -7
  143. package/tui.js +1 -0
@@ -1,30 +1,29 @@
1
1
  import { exec } from "node:child_process";
2
- import { tool } from "@opencode-ai/plugin";
3
- import { ANTIGRAVITY_DEFAULT_PROJECT_ID, ANTIGRAVITY_ENDPOINT_FALLBACKS, ANTIGRAVITY_ENDPOINT_PROD, ANTIGRAVITY_PROVIDER_ID, ANTIGRAVITY_REDIRECT_URI, getAntigravityHeaders, } from "./constants";
4
- import { authorizeAntigravity, exchangeAntigravity } from "./antigravity/oauth";
5
- import { authorizeGeminiCli, exchangeGeminiCli } from "./antigravity/oauth";
6
- import { accessTokenExpired, isOAuthAuth, parseRefreshParts, formatRefreshParts } from "./plugin/auth";
7
- import { promptAddAnotherAccount, promptLoginMode, promptProjectId } from "./plugin/cli";
8
- import { ensureProjectContext } from "./plugin/project";
9
- import { startAntigravityDebugRequest, logAntigravityDebugResponse, logAccountContext, logRateLimitEvent, logRateLimitSnapshot, logResponseBody, logModelFamily, isDebugEnabled, getLogFilePath, initializeDebug, } from "./plugin/debug";
10
- import { buildThinkingWarmupBody, isGenerativeLanguageRequest, prepareAntigravityRequest, transformAntigravityResponse, } from "./plugin/request";
11
- import { resolveModelWithTier } from "./plugin/transform/model-resolver";
12
- import { isEmptyResponseBody, createSyntheticErrorResponse, } from "./plugin/request-helpers";
13
- import { EmptyResponseError } from "./plugin/errors";
14
- import { AntigravityTokenRefreshError, refreshAccessToken } from "./plugin/token";
15
- import { startOAuthListener } from "./plugin/server";
16
- import { clearAccounts, loadAccounts, saveAccounts, saveAccountsReplace } from "./plugin/storage";
17
- import { AccountManager, parseRateLimitReason, calculateBackoffMs, computeSoftQuotaCacheTtlMs } from "./plugin/accounts";
18
- import { createAutoUpdateCheckerHook } from "./hooks/auto-update-checker";
19
- import { loadConfig, initRuntimeConfig } from "./plugin/config";
20
- import { createSessionRecoveryHook, getRecoverySuccessToast } from "./plugin/recovery";
21
- import { checkAccountsQuota } from "./plugin/quota";
22
- import { initDiskSignatureCache } from "./plugin/cache";
23
- import { createProactiveRefreshQueue } from "./plugin/refresh-queue";
24
- import { initLogger, createLogger } from "./plugin/logger";
25
- import { initHealthTracker, getHealthTracker, initTokenTracker, getTokenTracker } from "./plugin/rotation";
26
- import { initAntigravityVersion } from "./plugin/version";
27
- import { executeSearch } from "./plugin/search";
2
+ import { tool } from "@opencode-ai/plugin/tool";
3
+ import { ANTIGRAVITY_DEFAULT_PROJECT_ID, ANTIGRAVITY_ENDPOINT, ANTIGRAVITY_PROVIDER_ID, getAntigravityHeaders, getEndpointFallbacks, } from "./constants.js";
4
+ import { authorizeAntigravity, exchangeAntigravity } from "./antigravity/oauth.js";
5
+ import { accessTokenExpired, isOAuthAuth, parseRefreshParts, formatRefreshParts } from "./plugin/auth.js";
6
+ import { promptAddAnotherAccount, promptLoginMode, promptProjectId } from "./plugin/cli.js";
7
+ import { ensureProjectContext } from "./plugin/project.js";
8
+ import { startAntigravityDebugRequest, logAntigravityDebugResponse, logAccountContext, logRateLimitEvent, logRateLimitSnapshot, logResponseBody, logModelFamily, isDebugEnabled, getLogFilePath, initializeDebug, } from "./plugin/debug.js";
9
+ import { buildThinkingWarmupBody, isGenerativeLanguageRequest, prepareAntigravityRequest, transformAntigravityResponse, } from "./plugin/request.js";
10
+ import { resolveModelWithTier } from "./plugin/transform/model-resolver.js";
11
+ import { isEmptyResponseBody, createSyntheticErrorResponse, } from "./plugin/request-helpers.js";
12
+ import { EmptyResponseError } from "./plugin/errors.js";
13
+ import { AntigravityTokenRefreshError, refreshAccessToken } from "./plugin/token.js";
14
+ import { startOAuthListener } from "./plugin/server.js";
15
+ import { clearAccounts, loadAccounts, saveAccountCredentials, saveAccounts, saveAccountsReplace, updateAccountCredentials, updateAccounts } from "./plugin/storage.js";
16
+ import { AccountManager, parseRateLimitReason, calculateBackoffMs, computeSoftQuotaCacheTtlMs } from "./plugin/accounts.js";
17
+ import { createAutoUpdateCheckerHook } from "./hooks/auto-update-checker/index.js";
18
+ import { loadConfig, initRuntimeConfig } from "./plugin/config/index.js";
19
+ import { createSessionRecoveryHook, getRecoverySuccessToast } from "./plugin/recovery.js";
20
+ import { checkAccountsQuota } from "./plugin/quota.js";
21
+ import { acquireDiskSignatureCache } from "./plugin/cache.js";
22
+ import { createProactiveRefreshQueue } from "./plugin/refresh-queue.js";
23
+ import { initLogger, createLogger } from "./plugin/logger.js";
24
+ import { initHealthTracker, getHealthTracker, initTokenTracker, getTokenTracker } from "./plugin/rotation.js";
25
+ import { refreshAntigravityVersion, ANTIGRAVITY_VERSION_REFRESH_INTERVAL_MS } from "./plugin/version.js";
26
+ import { executeSearch } from "./plugin/search.js";
28
27
  const MAX_OAUTH_ACCOUNTS = 10;
29
28
  const MAX_WARMUP_SESSIONS = 1000;
30
29
  const MAX_WARMUP_RETRIES = 2;
@@ -99,7 +98,19 @@ async function triggerAsyncQuotaRefreshForAccount(accountManager, accountIndex,
99
98
  quotaRefreshInProgressByEmail.delete(accountKey);
100
99
  return;
101
100
  }
102
- const results = await checkAccountsQuota([singleAccount], client, providerId);
101
+ const results = await checkAccountsQuota([singleAccount], client, providerId, { onAccountUpdated: saveAccountCredentials });
102
+ const updatedAccount = results[0]?.updatedAccount;
103
+ if (updatedAccount) {
104
+ const saved = await updateAccounts(storage => {
105
+ const current = storage.accounts.find(item => singleAccount.id ? item.id === singleAccount.id : item.refreshToken === singleAccount.refreshToken);
106
+ if (current?.refreshToken === singleAccount.refreshToken)
107
+ updateAccountCredentials(current, updatedAccount);
108
+ });
109
+ const current = saved?.accounts.find(item => singleAccount.id ? item.id === singleAccount.id : item.refreshToken === updatedAccount.refreshToken);
110
+ if (current && account.parts.refreshToken === singleAccount.refreshToken) {
111
+ accountManager.updateFromAuth(account, { ...accountManager.toAuthDetails(account), refresh: formatRefreshParts(current) }, singleAccount.refreshToken);
112
+ }
113
+ }
103
114
  if (results[0]?.status === "ok" && results[0]?.quota?.groups) {
104
115
  accountManager.updateQuotaCache(accountIndex, results[0].quota.groups);
105
116
  accountManager.requestSaveToDisk();
@@ -259,16 +270,6 @@ function extractVerificationErrorDetails(bodyText) {
259
270
  let validationRequired = lowerBody.includes("validation_required");
260
271
  let message;
261
272
  const verificationUrls = new Set();
262
- // Detect SUBSCRIPTION_REQUIRED / Gemini Code Assist license errors
263
- const isSubscriptionRequired = lowerBody.includes("subscription_required") ||
264
- lowerBody.includes("lack a gemini code assist license");
265
- // Detect Cloud Code Private API not enabled errors
266
- const isApiEnableRequired = lowerBody.includes("cloud code private api has not been used") ||
267
- lowerBody.includes("cloudcode-pa.googleapis.com/overview");
268
- // Mark as validation required if we detect these specific error types
269
- if (isSubscriptionRequired || isApiEnableRequired) {
270
- validationRequired = true;
271
- }
272
273
  const collectUrlsFromText = (text) => {
273
274
  for (const match of text.matchAll(/https:\/\/accounts\.google\.com\/[^\s"'<>]+/gi)) {
274
275
  if (match[0]) {
@@ -351,27 +352,15 @@ function extractVerificationErrorDetails(bodyText) {
351
352
  const fallback = decodedBody
352
353
  .split("\n")
353
354
  .map((line) => line.trim())
354
- .find((line) => line && !line.startsWith("data:") && /(verify|validation|required|subscription|license)/i.test(line));
355
+ .find((line) => line && !line.startsWith("data:") && /(verify|validation|required)/i.test(line));
355
356
  if (fallback) {
356
357
  message = fallback;
357
358
  }
358
359
  }
359
- // Classify the verification type
360
- let verificationType = "unknown";
361
- if (isSubscriptionRequired) {
362
- verificationType = "gemini-cli";
363
- }
364
- else if (isApiEnableRequired) {
365
- verificationType = "api-enable";
366
- }
367
- else if (verificationUrls.size > 0 && [...verificationUrls].some(u => u.includes("accounts.google.com"))) {
368
- verificationType = "google-account";
369
- }
370
360
  return {
371
361
  validationRequired,
372
362
  message,
373
363
  verifyUrl: selectBestVerificationUrl([...verificationUrls]),
374
- verificationType,
375
364
  };
376
365
  }
377
366
  async function verifyAccountAccess(account, client, providerId) {
@@ -412,11 +401,12 @@ async function verifyAccountAccess(account, client, providerId) {
412
401
  Authorization: `Bearer ${refreshedAuth.access}`,
413
402
  "Content-Type": "application/json",
414
403
  };
415
- if (projectId) {
416
- headers["x-goog-user-project"] = projectId;
417
- }
418
404
  const requestBody = {
405
+ project: projectId,
419
406
  model: "gemini-3-flash",
407
+ requestType: "agent",
408
+ userAgent: "antigravity",
409
+ requestId: `agent-${crypto.randomUUID()}`,
420
410
  request: {
421
411
  model: "gemini-3-flash",
422
412
  contents: [{ role: "user", parts: [{ text: "ping" }] }],
@@ -427,7 +417,7 @@ async function verifyAccountAccess(account, client, providerId) {
427
417
  const timeoutId = setTimeout(() => controller.abort(), 20000);
428
418
  let response;
429
419
  try {
430
- response = await fetch(`${ANTIGRAVITY_ENDPOINT_PROD}/v1internal:streamGenerateContent?alt=sse`, {
420
+ response = await fetch(`${ANTIGRAVITY_ENDPOINT}/v1internal:streamGenerateContent?alt=sse`, {
431
421
  method: "POST",
432
422
  headers,
433
423
  body: JSON.stringify(requestBody),
@@ -459,14 +449,12 @@ async function verifyAccountAccess(account, client, providerId) {
459
449
  status: "blocked",
460
450
  message: extracted.message ?? "Google requires additional account verification.",
461
451
  verifyUrl: extracted.verifyUrl,
462
- verificationType: extracted.verificationType,
463
452
  };
464
453
  }
465
454
  const fallbackMessage = extracted.message ?? `Request failed (${response.status} ${response.statusText}).`;
466
455
  return {
467
456
  status: "error",
468
457
  message: fallbackMessage,
469
- verificationType: extracted.verificationType !== "unknown" ? extracted.verificationType : undefined,
470
458
  };
471
459
  }
472
460
  async function promptAccountIndexForVerification(accounts) {
@@ -507,7 +495,7 @@ async function promptOpenVerificationUrl() {
507
495
  const answer = (await promptOAuthCallbackValue("Open verification URL in your browser now? [Y/n]: ")).trim().toLowerCase();
508
496
  return answer === "" || answer === "y" || answer === "yes";
509
497
  }
510
- function markStoredAccountVerificationRequired(account, reason, verifyUrl, verificationType) {
498
+ function markStoredAccountVerificationRequired(account, reason, verifyUrl) {
511
499
  let changed = false;
512
500
  const wasVerificationRequired = account.verificationRequired === true;
513
501
  if (!wasVerificationRequired) {
@@ -528,11 +516,6 @@ function markStoredAccountVerificationRequired(account, reason, verifyUrl, verif
528
516
  account.verificationUrl = normalizedUrl;
529
517
  changed = true;
530
518
  }
531
- const normalizedType = verificationType?.trim();
532
- if (normalizedType && account.verificationRequiredType !== normalizedType) {
533
- account.verificationRequiredType = normalizedType;
534
- changed = true;
535
- }
536
519
  if (account.enabled !== false) {
537
520
  account.enabled = false;
538
521
  changed = true;
@@ -554,10 +537,6 @@ function clearStoredAccountVerificationRequired(account, enableIfRequired = fals
554
537
  account.verificationRequiredReason = undefined;
555
538
  changed = true;
556
539
  }
557
- if (account.verificationRequiredType !== undefined) {
558
- account.verificationRequiredType = undefined;
559
- changed = true;
560
- }
561
540
  if (account.verificationUrl !== undefined) {
562
541
  account.verificationUrl = undefined;
563
542
  changed = true;
@@ -636,7 +615,7 @@ function clampInt(value, min, max) {
636
615
  }
637
616
  return Math.min(max, Math.max(min, Math.floor(value)));
638
617
  }
639
- async function persistAccountPool(results, replaceAll = false) {
618
+ export async function persistAccountPool(results, replaceAll = false) {
640
619
  if (results.length === 0) {
641
620
  return;
642
621
  }
@@ -1026,18 +1005,25 @@ function sleep(ms, signal) {
1026
1005
  /**
1027
1006
  * Creates an Antigravity OAuth plugin for a specific provider ID.
1028
1007
  */
1029
- export const createAntigravityPlugin = (providerId) => async ({ client, directory }) => {
1008
+ export const createAntigravityPlugin = (providerId) => async ({ client, directory, runtimeVersion = 1, fetch: sendFetch = globalThis.fetch, canUseAccount }) => {
1030
1009
  // Load configuration from files and environment variables
1031
1010
  const config = loadConfig(directory);
1032
1011
  initRuntimeConfig(config);
1033
1012
  // Cached getAuth function for tool access
1034
1013
  let cachedGetAuth = null;
1014
+ const refreshQueues = new Set();
1015
+ const quotaRefreshes = new Set();
1016
+ const transportManagers = new Set();
1017
+ let disposed = false;
1018
+ let managerGeneration = 0;
1035
1019
  // Initialize debug with config
1036
1020
  initializeDebug(config);
1037
1021
  // Initialize structured logger for TUI integration
1038
1022
  initLogger(client);
1039
1023
  // Fetch latest Antigravity version from remote API (non-blocking, falls back to hardcoded)
1040
- await initAntigravityVersion();
1024
+ await refreshAntigravityVersion();
1025
+ const versionRefreshTimer = setInterval(() => { void refreshAntigravityVersion(); }, ANTIGRAVITY_VERSION_REFRESH_INTERVAL_MS);
1026
+ versionRefreshTimer.unref?.();
1041
1027
  // Initialize health tracker for hybrid strategy
1042
1028
  if (config.health_score) {
1043
1029
  initHealthTracker({
@@ -1060,19 +1046,21 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1060
1046
  }
1061
1047
  // Initialize disk signature cache if keep_thinking is enabled
1062
1048
  // This integrates with the in-memory cacheSignature/getCachedSignature functions
1063
- if (config.keep_thinking) {
1064
- initDiskSignatureCache(config.signature_cache);
1065
- }
1049
+ const releaseSignatureCache = config.keep_thinking
1050
+ ? acquireDiskSignatureCache(config.signature_cache)
1051
+ : undefined;
1066
1052
  // Initialize session recovery hook with full context
1067
- const sessionRecovery = createSessionRecoveryHook({ client, directory }, config);
1068
- const updateChecker = createAutoUpdateCheckerHook(client, directory, {
1053
+ const sessionRecovery = runtimeVersion === 1
1054
+ ? createSessionRecoveryHook({ client, directory }, config)
1055
+ : null;
1056
+ const updateChecker = runtimeVersion === 1 ? createAutoUpdateCheckerHook(client, directory, {
1069
1057
  showStartupToast: true,
1070
1058
  autoUpdate: config.auto_update,
1071
- });
1059
+ }) : null;
1072
1060
  // Event handler for session recovery and updates
1073
1061
  const eventHandler = async (input) => {
1074
1062
  // Forward to update checker
1075
- await updateChecker.event(input);
1063
+ await updateChecker?.event(input);
1076
1064
  // Track if this is a child session (subagent, background task)
1077
1065
  // This is used to filter toasts based on toast_scope config
1078
1066
  if (input.event.type === "session.created") {
@@ -1169,6 +1157,27 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1169
1157
  },
1170
1158
  });
1171
1159
  return {
1160
+ async flush() {
1161
+ managerGeneration += 1;
1162
+ await Promise.allSettled([...refreshQueues].map((queue) => queue.stop()));
1163
+ refreshQueues.clear();
1164
+ await Promise.allSettled([...transportManagers].map((manager) => manager.retire()));
1165
+ transportManagers.clear();
1166
+ },
1167
+ async dispose() {
1168
+ if (disposed)
1169
+ return;
1170
+ disposed = true;
1171
+ managerGeneration += 1;
1172
+ releaseSignatureCache?.();
1173
+ clearInterval(versionRefreshTimer);
1174
+ await Promise.allSettled([...refreshQueues].map((queue) => queue.stop()));
1175
+ refreshQueues.clear();
1176
+ await Promise.allSettled([...quotaRefreshes]);
1177
+ await Promise.allSettled([...transportManagers].map((manager) => manager.retire()));
1178
+ transportManagers.clear();
1179
+ cachedGetAuth = null;
1180
+ },
1172
1181
  event: eventHandler,
1173
1182
  tool: {
1174
1183
  google_search: googleSearchTool,
@@ -1176,11 +1185,22 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1176
1185
  auth: {
1177
1186
  provider: providerId,
1178
1187
  loader: async (getAuth, provider) => {
1188
+ if (disposed)
1189
+ throw new Error("Antigravity plugin has been disposed");
1190
+ const expectedGeneration = managerGeneration;
1191
+ if (runtimeVersion === 2) {
1192
+ await Promise.allSettled([...refreshQueues].map((queue) => queue.stop()));
1193
+ refreshQueues.clear();
1194
+ await Promise.allSettled([...transportManagers].map((manager) => manager.retire()));
1195
+ transportManagers.clear();
1196
+ }
1179
1197
  // Cache getAuth for tool access
1180
1198
  cachedGetAuth = getAuth;
1181
1199
  const auth = await getAuth();
1182
1200
  // If OpenCode has no valid OAuth auth, clear any stale account storage
1183
1201
  if (!isOAuthAuth(auth)) {
1202
+ if (runtimeVersion === 2)
1203
+ return {};
1184
1204
  try {
1185
1205
  await clearAccounts();
1186
1206
  }
@@ -1194,8 +1214,22 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1194
1214
  const authParts = parseRefreshParts(auth.refresh);
1195
1215
  const storedAccounts = await loadAccounts();
1196
1216
  // Note: AccountManager now ensures the current auth is always included in accounts
1197
- const accountManager = await AccountManager.loadFromDisk(auth);
1217
+ const accountManager = await AccountManager.loadFromDisk(auth, runtimeVersion === 2);
1218
+ if (runtimeVersion === 2)
1219
+ accountManager.useManagedStorage();
1220
+ const abandonManager = async () => {
1221
+ await accountManager.retire();
1222
+ transportManagers.delete(accountManager);
1223
+ throw new Error(disposed ? "Antigravity plugin has been disposed" : "Antigravity accounts changed");
1224
+ };
1225
+ if (disposed || managerGeneration !== expectedGeneration)
1226
+ return abandonManager();
1198
1227
  activeAccountManager = accountManager;
1228
+ transportManagers.add(accountManager);
1229
+ if (runtimeVersion === 2)
1230
+ await accountManager.saveToDisk();
1231
+ if (disposed || managerGeneration !== expectedGeneration)
1232
+ return abandonManager();
1199
1233
  if (accountManager.getAccountCount() > 0) {
1200
1234
  accountManager.requestSaveToDisk();
1201
1235
  }
@@ -1209,6 +1243,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1209
1243
  });
1210
1244
  refreshQueue.setAccountManager(accountManager);
1211
1245
  refreshQueue.start();
1246
+ refreshQueues.add(refreshQueue);
1212
1247
  }
1213
1248
  if (isDebugEnabled()) {
1214
1249
  const logPath = getLogFilePath();
@@ -1231,14 +1266,20 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1231
1266
  }
1232
1267
  }
1233
1268
  return {
1234
- apiKey: "",
1269
+ apiKey: "antigravity-oauth",
1235
1270
  async fetch(input, init) {
1236
1271
  if (!isGenerativeLanguageRequest(input)) {
1237
- return fetch(input, init);
1272
+ return sendFetch(input, init);
1238
1273
  }
1274
+ const checkLifetime = () => {
1275
+ if (disposed || (runtimeVersion === 2 && managerGeneration !== expectedGeneration))
1276
+ throw new Error("Antigravity accounts changed; retry the request");
1277
+ };
1278
+ checkLifetime();
1239
1279
  const latestAuth = await getAuth();
1280
+ checkLifetime();
1240
1281
  if (!isOAuthAuth(latestAuth)) {
1241
- return fetch(input, init);
1282
+ return sendFetch(input, init);
1242
1283
  }
1243
1284
  if (accountManager.getAccountCount() === 0) {
1244
1285
  throw new Error("No Antigravity accounts configured. Run `opencode auth login`.");
@@ -1246,6 +1287,10 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1246
1287
  const urlString = toUrlString(input);
1247
1288
  const family = getModelFamilyFromUrl(urlString);
1248
1289
  const model = extractModelFromUrl(urlString);
1290
+ const eligible = (account) => !canUseAccount || canUseAccount({ id: account.id, refreshToken: account.parts.refreshToken }, model ?? "");
1291
+ if (!accountManager.getAccounts().some(account => account.enabled !== false && eligible(account))) {
1292
+ throw new Error("No enabled Antigravity account has access to this model. Refresh available models from /antigravity.");
1293
+ }
1249
1294
  const debugLines = [];
1250
1295
  const pushDebug = (line) => {
1251
1296
  if (!isDebugEnabled())
@@ -1297,30 +1342,30 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1297
1342
  if (family !== "gemini")
1298
1343
  return false;
1299
1344
  // Use AccountManager method which properly checks for disabled/cooling-down accounts
1300
- return accountManager.hasOtherAccountWithAntigravityAvailable(currentAccount.index, family, model);
1345
+ return accountManager.hasOtherAccountWithAntigravityAvailable(currentAccount.index, family, model, eligible);
1301
1346
  };
1302
1347
  while (true) {
1303
1348
  // Check for abort at the start of each iteration
1304
1349
  checkAborted();
1305
- const accountCount = accountManager.getAccountCount();
1350
+ const accountCount = accountManager.getEnabledAccounts().filter(eligible).length;
1306
1351
  const routingDecision = resolveHeaderRoutingDecision(urlString, family, config);
1307
1352
  const { cliFirst, preferredHeaderStyle, explicitQuota, allowQuotaFallback, } = routingDecision;
1308
1353
  if (accountCount === 0) {
1309
1354
  throw new Error("No Antigravity accounts available. Run `opencode auth login`.");
1310
1355
  }
1311
1356
  const softQuotaCacheTtlMs = computeSoftQuotaCacheTtlMs(config.soft_quota_cache_ttl_minutes, config.quota_refresh_interval_minutes);
1312
- let account = accountManager.getCurrentOrNextForFamily(family, model, config.account_selection_strategy, preferredHeaderStyle, config.pid_offset_enabled, config.soft_quota_threshold_percent, softQuotaCacheTtlMs);
1357
+ let account = accountManager.getCurrentOrNextForFamily(family, model, config.account_selection_strategy, preferredHeaderStyle, config.pid_offset_enabled, config.soft_quota_threshold_percent, softQuotaCacheTtlMs, eligible);
1313
1358
  if (!account && allowQuotaFallback) {
1314
1359
  const alternateHeaderStyle = preferredHeaderStyle === "antigravity" ? "gemini-cli" : "antigravity";
1315
- account = accountManager.getCurrentOrNextForFamily(family, model, config.account_selection_strategy, alternateHeaderStyle, config.pid_offset_enabled, config.soft_quota_threshold_percent, softQuotaCacheTtlMs);
1360
+ account = accountManager.getCurrentOrNextForFamily(family, model, config.account_selection_strategy, alternateHeaderStyle, config.pid_offset_enabled, config.soft_quota_threshold_percent, softQuotaCacheTtlMs, eligible);
1316
1361
  if (account) {
1317
1362
  pushDebug(`selected-by-fallback idx=${account.index} preferred=${preferredHeaderStyle} alternate=${alternateHeaderStyle}`);
1318
1363
  }
1319
1364
  }
1320
1365
  if (!account) {
1321
- if (accountManager.areAllAccountsOverSoftQuota(family, config.soft_quota_threshold_percent, softQuotaCacheTtlMs, model)) {
1366
+ if (accountManager.areAllAccountsOverSoftQuota(family, config.soft_quota_threshold_percent, softQuotaCacheTtlMs, model, eligible)) {
1322
1367
  const threshold = config.soft_quota_threshold_percent;
1323
- const softQuotaWaitMs = accountManager.getMinWaitTimeForSoftQuota(family, threshold, softQuotaCacheTtlMs, model);
1368
+ const softQuotaWaitMs = accountManager.getMinWaitTimeForSoftQuota(family, threshold, softQuotaCacheTtlMs, model, eligible);
1324
1369
  const maxWaitMs = (config.max_rate_limit_wait_seconds ?? 300) * 1000;
1325
1370
  if (softQuotaWaitMs === null || (maxWaitMs > 0 && softQuotaWaitMs > maxWaitMs)) {
1326
1371
  const waitTimeFormatted = softQuotaWaitMs ? formatWaitTime(softQuotaWaitMs) : "unknown";
@@ -1340,7 +1385,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1340
1385
  }
1341
1386
  const strictWait = !allowQuotaFallback;
1342
1387
  // All accounts are rate-limited - wait and retry
1343
- const waitMs = accountManager.getMinWaitTimeForFamily(family, model, preferredHeaderStyle, strictWait) || 60_000;
1388
+ const waitMs = accountManager.getMinWaitTimeForFamily(family, model, preferredHeaderStyle, strictWait, eligible) || 60_000;
1344
1389
  const waitSecValue = Math.max(1, Math.ceil(waitMs / 1000));
1345
1390
  pushDebug(`all-rate-limited family=${family} accounts=${accountCount} waitMs=${waitMs}`);
1346
1391
  if (isDebugEnabled()) {
@@ -1408,7 +1453,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1408
1453
  continue;
1409
1454
  }
1410
1455
  resetAccountFailureState(account.index);
1411
- accountManager.updateFromAuth(account, refreshed);
1456
+ accountManager.updateFromAuth(account, refreshed, parseRefreshParts(authRecord.refresh).refreshToken);
1412
1457
  authRecord = refreshed;
1413
1458
  try {
1414
1459
  await accountManager.saveToDisk();
@@ -1419,9 +1464,24 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1419
1464
  }
1420
1465
  catch (error) {
1421
1466
  if (error instanceof AntigravityTokenRefreshError && error.code === "invalid_grant") {
1422
- const removed = accountManager.removeAccount(account);
1467
+ if (runtimeVersion === 2) {
1468
+ const rejectedToken = parseRefreshParts(authRecord.refresh).refreshToken;
1469
+ const saved = await updateAccounts(storage => {
1470
+ const current = storage.accounts.find(item => account.id ? item.id === account.id : item.refreshToken === rejectedToken);
1471
+ if (current?.refreshToken === rejectedToken)
1472
+ current.enabled = false;
1473
+ });
1474
+ const current = saved?.accounts.find(item => account.id ? item.id === account.id : item.refreshToken === rejectedToken);
1475
+ if (current && current.refreshToken !== rejectedToken) {
1476
+ accountManager.updateFromAuth(account, { type: "oauth", refresh: formatRefreshParts(current) });
1477
+ continue;
1478
+ }
1479
+ }
1480
+ const removed = runtimeVersion === 2
1481
+ ? accountManager.setAccountEnabled(account.index, false)
1482
+ : accountManager.removeAccount(account);
1423
1483
  if (removed) {
1424
- log.warn("Removed revoked account from pool - reauthenticate via `opencode auth login`");
1484
+ log.warn("Revoked account excluded from rotation - reauthenticate via `opencode auth login`");
1425
1485
  try {
1426
1486
  await accountManager.saveToDisk();
1427
1487
  }
@@ -1481,7 +1541,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1481
1541
  }
1482
1542
  if (projectContext.auth.refresh !== authRecord.refresh ||
1483
1543
  projectContext.auth.access !== authRecord.access) {
1484
- accountManager.updateFromAuth(account, projectContext.auth);
1544
+ accountManager.updateFromAuth(account, projectContext.auth, parseRefreshParts(authRecord.refresh).refreshToken);
1485
1545
  authRecord = projectContext.auth;
1486
1546
  try {
1487
1547
  await accountManager.saveToDisk();
@@ -1521,7 +1581,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1521
1581
  });
1522
1582
  try {
1523
1583
  pushDebug("thinking-warmup: start");
1524
- const warmupResponse = await fetch(warmupUrl, warmupInit);
1584
+ const warmupResponse = await sendFetch(warmupUrl, warmupInit);
1525
1585
  const transformed = await transformAntigravityResponse(warmupResponse, true, warmupDebugContext, prepared.requestedModel, projectId, warmupUrl, prepared.effectiveModel, prepared.sessionId);
1526
1586
  await transformed.text();
1527
1587
  markWarmupSuccess(prepared.sessionId);
@@ -1548,7 +1608,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1548
1608
  // Antigravity-first fallback: exhaust antigravity across ALL accounts before gemini-cli
1549
1609
  if (allowQuotaFallback && family === "gemini" && headerStyle === "antigravity") {
1550
1610
  // Check if ANY other account has antigravity available
1551
- if (accountManager.hasOtherAccountWithAntigravityAvailable(account.index, family, model)) {
1611
+ if (accountManager.hasOtherAccountWithAntigravityAvailable(account.index, family, model, eligible)) {
1552
1612
  // Switch to another account with antigravity (preserve antigravity priority)
1553
1613
  pushDebug(`antigravity rate-limited on account ${account.index}, but available on other accounts. Switching.`);
1554
1614
  shouldSwitchAccount = true;
@@ -1602,19 +1662,14 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1602
1662
  // Track capacity retries per endpoint to prevent infinite loops
1603
1663
  let capacityRetryCount = 0;
1604
1664
  let lastEndpointIndex = -1;
1605
- for (let i = 0; i < ANTIGRAVITY_ENDPOINT_FALLBACKS.length; i++) {
1665
+ const endpoints = getEndpointFallbacks(headerStyle);
1666
+ for (let i = 0; i < endpoints.length; i++) {
1606
1667
  // Reset capacity retry counter when switching to a new endpoint
1607
1668
  if (i !== lastEndpointIndex) {
1608
1669
  capacityRetryCount = 0;
1609
1670
  lastEndpointIndex = i;
1610
1671
  }
1611
- const currentEndpoint = ANTIGRAVITY_ENDPOINT_FALLBACKS[i];
1612
- // Skip sandbox endpoints for Gemini CLI models - they only work with Antigravity quota
1613
- // Gemini CLI models must use production endpoint (cloudcode-pa.googleapis.com)
1614
- if (headerStyle === "gemini-cli" && currentEndpoint !== ANTIGRAVITY_ENDPOINT_PROD) {
1615
- pushDebug(`Skipping sandbox endpoint ${currentEndpoint} for gemini-cli headerStyle`);
1616
- continue;
1617
- }
1672
+ const currentEndpoint = endpoints[i];
1618
1673
  try {
1619
1674
  const prepared = prepareAntigravityRequest(input, init, accessToken, projectContext.effectiveProjectId, currentEndpoint, headerStyle, forceThinkingRecovery, {
1620
1675
  claudeToolHardening: config.claude_tool_hardening,
@@ -1659,7 +1714,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1659
1714
  if (config.account_selection_strategy === 'hybrid') {
1660
1715
  tokenConsumed = getTokenTracker().consume(account.index);
1661
1716
  }
1662
- const response = await fetch(prepared.request, prepared.init);
1717
+ const response = await sendFetch(prepared.request, prepared.init);
1663
1718
  pushDebug(`status=${response.status} ${response.statusText}`);
1664
1719
  // Handle 429 rate limit (or Service Overloaded) with improved logic
1665
1720
  if (response.status === 429 || response.status === 503 || response.status === 529) {
@@ -1838,27 +1893,12 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1838
1893
  if (extracted.validationRequired) {
1839
1894
  const verificationReason = extracted.message ?? "Google requires account verification.";
1840
1895
  const cooldownMs = 10 * 60 * 1000;
1841
- accountManager.markAccountVerificationRequired(account.index, verificationReason, extracted.verifyUrl, extracted.verificationType);
1896
+ accountManager.markAccountVerificationRequired(account.index, verificationReason, extracted.verifyUrl);
1842
1897
  accountManager.markAccountCoolingDown(account, cooldownMs, "validation-required");
1843
1898
  accountManager.markRateLimited(account, cooldownMs, family, headerStyle, model);
1844
1899
  const label = account.email || `Account ${account.index + 1}`;
1845
1900
  if (accountManager.shouldShowAccountToast(account.index, 60000)) {
1846
- let toastMessage;
1847
- switch (extracted.verificationType) {
1848
- case "gemini-cli":
1849
- toastMessage = `⚠ ${label} needs Gemini CLI login. Run 'opencode auth login' → Gemini CLI Login.`;
1850
- break;
1851
- case "api-enable":
1852
- toastMessage = `⚠ ${label}: Cloud Code API not enabled. Run 'opencode auth login' → Verify accounts.`;
1853
- break;
1854
- case "google-account":
1855
- toastMessage = `⚠ ${label} needs Google verification. Run 'opencode auth login' → Verify accounts.`;
1856
- break;
1857
- default:
1858
- toastMessage = `⚠ ${label} needs verification. Run 'opencode auth login' and use Verify accounts.`;
1859
- break;
1860
- }
1861
- await showToast(toastMessage, "warning");
1901
+ await showToast(`⚠ ${label} needs verification. Run 'opencode auth login' and use Verify accounts.`, "warning");
1862
1902
  accountManager.markToastShown(account.index);
1863
1903
  }
1864
1904
  pushDebug(`verification-required: disabled account ${account.index}`);
@@ -1871,7 +1911,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1871
1911
  const shouldRetryEndpoint = (response.status === 403 ||
1872
1912
  response.status === 404 ||
1873
1913
  response.status >= 500);
1874
- if (shouldRetryEndpoint && i < ANTIGRAVITY_ENDPOINT_FALLBACKS.length - 1) {
1914
+ if (shouldRetryEndpoint && i < endpoints.length - 1) {
1875
1915
  await logResponseBody(debugContext, response, response.status);
1876
1916
  lastFailure = createFailureContext(response);
1877
1917
  continue;
@@ -1881,7 +1921,11 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1881
1921
  account.consecutiveFailures = 0;
1882
1922
  getHealthTracker().recordSuccess(account.index);
1883
1923
  accountManager.markAccountUsed(account.index);
1884
- void triggerAsyncQuotaRefreshForAccount(accountManager, account.index, client, providerId, config.quota_refresh_interval_minutes);
1924
+ if (!disposed) {
1925
+ const quotaRefresh = triggerAsyncQuotaRefreshForAccount(accountManager, account.index, client, providerId, config.quota_refresh_interval_minutes);
1926
+ quotaRefreshes.add(quotaRefresh);
1927
+ void quotaRefresh.then(() => quotaRefreshes.delete(quotaRefresh), () => quotaRefreshes.delete(quotaRefresh));
1928
+ }
1885
1929
  }
1886
1930
  logAntigravityDebugResponse(debugContext, response, {
1887
1931
  note: response.ok ? "Success" : `Error ${response.status}`,
@@ -1973,7 +2017,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
1973
2017
  headers: { "Content-Type": "application/json" }
1974
2018
  });
1975
2019
  }
1976
- if (i < ANTIGRAVITY_ENDPOINT_FALLBACKS.length - 1) {
2020
+ if (i < endpoints.length - 1) {
1977
2021
  lastError = error instanceof Error ? error : new Error(String(error));
1978
2022
  continue;
1979
2023
  }
@@ -2035,19 +2079,6 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
2035
2079
  let status = 'unknown';
2036
2080
  if (acc.verificationRequired) {
2037
2081
  status = 'verification-required';
2038
- // Backfill verificationRequiredType for accounts flagged before this field existed
2039
- if (!acc.verificationRequiredType && acc.verificationRequiredReason) {
2040
- const lowerReason = acc.verificationRequiredReason.toLowerCase();
2041
- if (lowerReason.includes("subscription_required") || lowerReason.includes("gemini code assist license")) {
2042
- acc.verificationRequiredType = "gemini-cli";
2043
- }
2044
- else if (lowerReason.includes("cloud code private api has not been used") || lowerReason.includes("cloudcode-pa.googleapis.com/overview")) {
2045
- acc.verificationRequiredType = "api-enable";
2046
- }
2047
- else if (lowerReason.includes("accounts.google.com")) {
2048
- acc.verificationRequiredType = "google-account";
2049
- }
2050
- }
2051
2082
  }
2052
2083
  else {
2053
2084
  const rateLimits = acc.rateLimitResetTimes;
@@ -2075,7 +2106,6 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
2075
2106
  status,
2076
2107
  isCurrentAccount: idx === (existingStorage.activeIndex ?? 0),
2077
2108
  enabled: acc.enabled !== false,
2078
- verificationRequiredType: acc.verificationRequiredType,
2079
2109
  };
2080
2110
  });
2081
2111
  menuResult = await promptLoginMode(existingAccounts);
@@ -2218,95 +2248,6 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
2218
2248
  }
2219
2249
  continue;
2220
2250
  }
2221
- if (menuResult.mode === "gemini-cli-login") {
2222
- if (existingStorage.accounts.length === 0) {
2223
- console.log("\nNo accounts available. Add an account first.\n");
2224
- continue;
2225
- }
2226
- // Show account picker
2227
- const accountIndex = await promptAccountIndexForVerification(existingStorage.accounts.map((acc, idx) => ({
2228
- email: acc.email,
2229
- index: idx,
2230
- })));
2231
- if (accountIndex === undefined) {
2232
- console.log("\nGemini CLI login cancelled.\n");
2233
- continue;
2234
- }
2235
- const targetAccount = existingStorage.accounts[accountIndex];
2236
- if (!targetAccount) {
2237
- console.log(`\nAccount ${accountIndex + 1} not found.\n`);
2238
- continue;
2239
- }
2240
- const accountLabel = targetAccount.email || `Account ${accountIndex + 1}`;
2241
- console.log(`\nStarting Gemini CLI login for ${accountLabel}...`);
2242
- console.log("This will open your browser to authorize Gemini Code Assist.\n");
2243
- let oauthListener;
2244
- try {
2245
- oauthListener = await startOAuthListener();
2246
- const geminiRedirectUri = ANTIGRAVITY_REDIRECT_URI;
2247
- const authorization = await authorizeGeminiCli(geminiRedirectUri, targetAccount.email);
2248
- const opened = await openBrowser(authorization.url);
2249
- if (opened) {
2250
- console.log("Opened authorization URL in your browser.");
2251
- }
2252
- else {
2253
- console.log("Could not open browser. Please open this URL manually:");
2254
- console.log(`\n${authorization.url}\n`);
2255
- }
2256
- console.log("Waiting for authorization...\n");
2257
- const callbackUrl = await oauthListener.waitForCallback();
2258
- const callbackParams = new URL(callbackUrl, "http://localhost").searchParams;
2259
- const code = callbackParams.get("code");
2260
- if (!code) {
2261
- console.log("✗ Authorization failed: no code received.\n");
2262
- await promptOAuthCallbackValue("\nPress Enter to return to menu...");
2263
- continue;
2264
- }
2265
- const result = await exchangeGeminiCli(code, authorization.verifier, geminiRedirectUri);
2266
- if (result.type === "success") {
2267
- const provisionedEmail = result.email || accountLabel;
2268
- console.log(`✓ Gemini CLI access provisioned for ${provisionedEmail}`);
2269
- // Verify the account actually works now
2270
- console.log("Verifying account access...");
2271
- const postVerification = await verifyAccountAccess(targetAccount, client, providerId);
2272
- if (postVerification.status === "ok") {
2273
- // Clear any verification flags and re-enable the account
2274
- if (targetAccount.verificationRequired) {
2275
- const { changed } = clearStoredAccountVerificationRequired(targetAccount, true);
2276
- if (changed) {
2277
- await saveAccounts(existingStorage);
2278
- }
2279
- activeAccountManager?.clearAccountVerificationRequired(accountIndex, true);
2280
- console.log(`✓ ${provisionedEmail} verified and re-enabled.\n`);
2281
- }
2282
- else if (targetAccount.enabled === false) {
2283
- targetAccount.enabled = true;
2284
- await saveAccounts(existingStorage);
2285
- activeAccountManager?.setAccountEnabled(accountIndex, true);
2286
- console.log(`✓ ${provisionedEmail} verified and re-enabled.\n`);
2287
- }
2288
- else {
2289
- console.log(`✓ ${provisionedEmail} verified and ready for requests.\n`);
2290
- }
2291
- }
2292
- else {
2293
- console.log(`⚠ Verification probe still failing: ${postVerification.message}`);
2294
- console.log("The API may take a few minutes to propagate. Try verifying again shortly.\n");
2295
- }
2296
- }
2297
- else {
2298
- console.log(`✗ Gemini CLI login failed: ${result.error}\n`);
2299
- }
2300
- }
2301
- catch (error) {
2302
- console.log(`✗ Gemini CLI login error: ${error instanceof Error ? error.message : String(error)}\n`);
2303
- }
2304
- finally {
2305
- oauthListener?.close();
2306
- }
2307
- await promptOAuthCallbackValue("\nPress Enter to return to menu...");
2308
- continue;
2309
- }
2310
2251
  if (menuResult.mode === "verify" || menuResult.mode === "verify-all") {
2311
2252
  const verifyAll = menuResult.mode === "verify-all" || menuResult.verifyAll === true;
2312
2253
  if (verifyAll) {
@@ -2338,11 +2279,11 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
2338
2279
  continue;
2339
2280
  }
2340
2281
  if (verification.status === "blocked") {
2341
- const changed = markStoredAccountVerificationRequired(account, verification.message, verification.verifyUrl, verification.verificationType);
2282
+ const changed = markStoredAccountVerificationRequired(account, verification.message, verification.verifyUrl);
2342
2283
  if (changed) {
2343
2284
  storageUpdated = true;
2344
2285
  }
2345
- activeAccountManager?.markAccountVerificationRequired(i, verification.message, verification.verifyUrl, verification.verificationType);
2286
+ activeAccountManager?.markAccountVerificationRequired(i, verification.message, verification.verifyUrl);
2346
2287
  blockedCount += 1;
2347
2288
  console.log("needs verification");
2348
2289
  const verifyUrl = verification.verifyUrl ?? account.verificationUrl;
@@ -2390,68 +2331,9 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
2390
2331
  const account = existingStorage.accounts[verifyAccountIndex];
2391
2332
  if (!account) {
2392
2333
  console.log(`\nAccount ${verifyAccountIndex + 1} not found.\n`);
2393
- await promptOAuthCallbackValue("\nPress Enter to return to menu...");
2394
2334
  continue;
2395
2335
  }
2396
2336
  const label = account.email || `Account ${verifyAccountIndex + 1}`;
2397
- // Smart verify: if account is flagged as needing Gemini CLI login,
2398
- // trigger the OAuth flow directly instead of just probing
2399
- if (account.verificationRequired && account.verificationRequiredType === "gemini-cli") {
2400
- console.log(`\n${label} needs Gemini CLI login. Starting OAuth flow...\n`);
2401
- let oauthListener;
2402
- try {
2403
- oauthListener = await startOAuthListener();
2404
- const geminiRedirectUri = ANTIGRAVITY_REDIRECT_URI;
2405
- const authorization = await authorizeGeminiCli(geminiRedirectUri, account.email);
2406
- const opened = await openBrowser(authorization.url);
2407
- if (opened) {
2408
- console.log("Opened authorization URL in your browser.");
2409
- }
2410
- else {
2411
- console.log("Could not open browser. Please open this URL manually:");
2412
- console.log(`\n${authorization.url}\n`);
2413
- }
2414
- console.log("Waiting for authorization...\n");
2415
- const callbackUrl = await oauthListener.waitForCallback();
2416
- const callbackParams = new URL(callbackUrl, "http://localhost").searchParams;
2417
- const code = callbackParams.get("code");
2418
- if (!code) {
2419
- console.log("✗ Authorization failed: no code received.\n");
2420
- await promptOAuthCallbackValue("\nPress Enter to return to menu...");
2421
- continue;
2422
- }
2423
- const result = await exchangeGeminiCli(code, authorization.verifier, geminiRedirectUri);
2424
- if (result.type === "success") {
2425
- // Verify the account actually works now
2426
- console.log("Verifying account access...");
2427
- const postVerification = await verifyAccountAccess(account, client, providerId);
2428
- if (postVerification.status === "ok") {
2429
- const { changed } = clearStoredAccountVerificationRequired(account, true);
2430
- if (changed) {
2431
- await saveAccounts(existingStorage);
2432
- }
2433
- activeAccountManager?.clearAccountVerificationRequired(verifyAccountIndex, true);
2434
- console.log(`✓ ${label} Gemini CLI access provisioned and verified. Account re-enabled.\n`);
2435
- }
2436
- else {
2437
- console.log(`✓ Gemini CLI login completed for ${result.email || label}.`);
2438
- console.log(`⚠ Verification probe still failing: ${postVerification.message}`);
2439
- console.log("The API may take a few minutes to propagate. Try verifying again shortly.\n");
2440
- }
2441
- }
2442
- else {
2443
- console.log(`✗ Gemini CLI login failed: ${result.error}\n`);
2444
- }
2445
- }
2446
- catch (error) {
2447
- console.log(`✗ Gemini CLI login error: ${error instanceof Error ? error.message : String(error)}\n`);
2448
- }
2449
- finally {
2450
- oauthListener?.close();
2451
- }
2452
- await promptOAuthCallbackValue("\nPress Enter to return to menu...");
2453
- continue;
2454
- }
2455
2337
  console.log(`\nChecking verification status for ${label}...\n`);
2456
2338
  const verification = await verifyAccountAccess(account, client, providerId);
2457
2339
  if (verification.status === "ok") {
@@ -2466,15 +2348,14 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
2466
2348
  else {
2467
2349
  console.log(`✓ ${label} is ready for requests.\n`);
2468
2350
  }
2469
- await promptOAuthCallbackValue("\nPress Enter to return to menu...");
2470
2351
  continue;
2471
2352
  }
2472
2353
  if (verification.status === "blocked") {
2473
- const changed = markStoredAccountVerificationRequired(account, verification.message, verification.verifyUrl, verification.verificationType);
2354
+ const changed = markStoredAccountVerificationRequired(account, verification.message, verification.verifyUrl);
2474
2355
  if (changed) {
2475
2356
  await saveAccounts(existingStorage);
2476
2357
  }
2477
- activeAccountManager?.markAccountVerificationRequired(verifyAccountIndex, verification.message, verification.verifyUrl, verification.verificationType);
2358
+ activeAccountManager?.markAccountVerificationRequired(verifyAccountIndex, verification.message, verification.verifyUrl);
2478
2359
  const verifyUrl = verification.verifyUrl ?? account.verificationUrl;
2479
2360
  console.log(`⚠ ${label} needs Google verification before it can be used.`);
2480
2361
  if (verification.message) {
@@ -2496,11 +2377,9 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
2496
2377
  else {
2497
2378
  console.log("No verification URL was returned. Try re-authenticating this account.\n");
2498
2379
  }
2499
- await promptOAuthCallbackValue("\nPress Enter to return to menu...");
2500
2380
  continue;
2501
2381
  }
2502
2382
  console.log(`✗ ${label}: ${verification.message}\n`);
2503
- await promptOAuthCallbackValue("\nPress Enter to return to menu...");
2504
2383
  continue;
2505
2384
  }
2506
2385
  break;