opencode-antigravity-auth 1.3.1 → 1.3.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/src/antigravity/oauth.d.ts.map +1 -1
- package/dist/src/antigravity/oauth.js +10 -4
- package/dist/src/antigravity/oauth.js.map +1 -1
- package/dist/src/constants.d.ts +30 -3
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +80 -3
- package/dist/src/constants.js.map +1 -1
- package/dist/src/plugin/accounts.d.ts +39 -3
- package/dist/src/plugin/accounts.d.ts.map +1 -1
- package/dist/src/plugin/accounts.js +163 -17
- package/dist/src/plugin/accounts.js.map +1 -1
- package/dist/src/plugin/cli.d.ts +15 -12
- package/dist/src/plugin/cli.d.ts.map +1 -1
- package/dist/src/plugin/cli.js +40 -13
- package/dist/src/plugin/cli.js.map +1 -1
- package/dist/src/plugin/config/loader.d.ts.map +1 -1
- package/dist/src/plugin/config/loader.js +0 -13
- package/dist/src/plugin/config/loader.js.map +1 -1
- package/dist/src/plugin/config/schema.d.ts +38 -319
- package/dist/src/plugin/config/schema.d.ts.map +1 -1
- package/dist/src/plugin/config/schema.js +66 -27
- package/dist/src/plugin/config/schema.js.map +1 -1
- package/dist/src/plugin/core/streaming/transformer.d.ts.map +1 -1
- package/dist/src/plugin/core/streaming/transformer.js +37 -6
- package/dist/src/plugin/core/streaming/transformer.js.map +1 -1
- package/dist/src/plugin/core/streaming/types.d.ts.map +1 -1
- package/dist/src/plugin/debug.d.ts.map +1 -1
- package/dist/src/plugin/debug.js +14 -1
- package/dist/src/plugin/debug.js.map +1 -1
- package/dist/src/plugin/fingerprint.d.ts +70 -0
- package/dist/src/plugin/fingerprint.d.ts.map +1 -0
- package/dist/src/plugin/fingerprint.js +155 -0
- package/dist/src/plugin/fingerprint.js.map +1 -0
- package/dist/src/plugin/request-helpers.d.ts.map +1 -1
- package/dist/src/plugin/request-helpers.js +61 -23
- package/dist/src/plugin/request-helpers.js.map +1 -1
- package/dist/src/plugin/request.d.ts +4 -1
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +60 -13
- package/dist/src/plugin/request.js.map +1 -1
- package/dist/src/plugin/rotation.d.ts +5 -4
- package/dist/src/plugin/rotation.d.ts.map +1 -1
- package/dist/src/plugin/rotation.js +35 -9
- package/dist/src/plugin/rotation.js.map +1 -1
- package/dist/src/plugin/search.d.ts +32 -0
- package/dist/src/plugin/search.d.ts.map +1 -0
- package/dist/src/plugin/search.js +197 -0
- package/dist/src/plugin/search.js.map +1 -0
- package/dist/src/plugin/storage.d.ts +2 -0
- package/dist/src/plugin/storage.d.ts.map +1 -1
- package/dist/src/plugin/storage.js +15 -2
- package/dist/src/plugin/storage.js.map +1 -1
- package/dist/src/plugin/transform/gemini.d.ts +1 -13
- package/dist/src/plugin/transform/gemini.d.ts.map +1 -1
- package/dist/src/plugin/transform/gemini.js +49 -12
- package/dist/src/plugin/transform/gemini.js.map +1 -1
- package/dist/src/plugin/transform/model-resolver.d.ts.map +1 -1
- package/dist/src/plugin/transform/model-resolver.js +4 -2
- package/dist/src/plugin/transform/model-resolver.js.map +1 -1
- package/dist/src/plugin/transform/types.d.ts +5 -0
- package/dist/src/plugin/transform/types.d.ts.map +1 -1
- package/dist/src/plugin/types.d.ts +1 -0
- package/dist/src/plugin/types.d.ts.map +1 -1
- package/dist/src/plugin/ui/ansi.d.ts +32 -0
- package/dist/src/plugin/ui/ansi.d.ts.map +1 -0
- package/dist/src/plugin/ui/ansi.js +52 -0
- package/dist/src/plugin/ui/ansi.js.map +1 -0
- package/dist/src/plugin/ui/auth-menu.d.ts +24 -0
- package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -0
- package/dist/src/plugin/ui/auth-menu.js +92 -0
- package/dist/src/plugin/ui/auth-menu.js.map +1 -0
- package/dist/src/plugin/ui/confirm.d.ts +2 -0
- package/dist/src/plugin/ui/confirm.d.ts.map +1 -0
- package/dist/src/plugin/ui/confirm.js +15 -0
- package/dist/src/plugin/ui/confirm.js.map +1 -0
- package/dist/src/plugin/ui/select.d.ts +14 -0
- package/dist/src/plugin/ui/select.d.ts.map +1 -0
- package/dist/src/plugin/ui/select.js +174 -0
- package/dist/src/plugin/ui/select.js.map +1 -0
- package/dist/src/plugin.d.ts.map +1 -1
- package/dist/src/plugin.js +317 -76
- package/dist/src/plugin.js.map +1 -1
- package/package.json +4 -4
package/dist/src/plugin.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { exec } from "node:child_process";
|
|
2
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
3
|
import { ANTIGRAVITY_ENDPOINT_FALLBACKS, ANTIGRAVITY_PROVIDER_ID } from "./constants";
|
|
3
4
|
import { authorizeAntigravity, exchangeAntigravity } from "./antigravity/oauth";
|
|
4
5
|
import { accessTokenExpired, isOAuthAuth, parseRefreshParts } from "./plugin/auth";
|
|
@@ -20,6 +21,7 @@ import { initDiskSignatureCache } from "./plugin/cache";
|
|
|
20
21
|
import { createProactiveRefreshQueue } from "./plugin/refresh-queue";
|
|
21
22
|
import { initLogger, createLogger } from "./plugin/logger";
|
|
22
23
|
import { initHealthTracker, getHealthTracker, initTokenTracker, getTokenTracker } from "./plugin/rotation";
|
|
24
|
+
import { executeSearch } from "./plugin/search";
|
|
23
25
|
const MAX_OAUTH_ACCOUNTS = 10;
|
|
24
26
|
const MAX_WARMUP_SESSIONS = 1000;
|
|
25
27
|
const MAX_WARMUP_RETRIES = 2;
|
|
@@ -283,7 +285,7 @@ async function persistAccountPool(results, replaceAll = false) {
|
|
|
283
285
|
},
|
|
284
286
|
});
|
|
285
287
|
}
|
|
286
|
-
function retryAfterMsFromResponse(response) {
|
|
288
|
+
function retryAfterMsFromResponse(response, defaultRetryMs = 60_000) {
|
|
287
289
|
const retryAfterMsHeader = response.headers.get("retry-after-ms");
|
|
288
290
|
if (retryAfterMsHeader) {
|
|
289
291
|
const parsed = Number.parseInt(retryAfterMsHeader, 10);
|
|
@@ -298,20 +300,54 @@ function retryAfterMsFromResponse(response) {
|
|
|
298
300
|
return parsed * 1000;
|
|
299
301
|
}
|
|
300
302
|
}
|
|
301
|
-
return
|
|
303
|
+
return defaultRetryMs;
|
|
302
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Parse Go-style duration strings to milliseconds.
|
|
307
|
+
* Supports compound durations: "1h16m0.667s", "1.5s", "200ms", "5m30s"
|
|
308
|
+
*
|
|
309
|
+
* @param duration - Duration string in Go format
|
|
310
|
+
* @returns Duration in milliseconds, or null if parsing fails
|
|
311
|
+
*/
|
|
303
312
|
function parseDurationToMs(duration) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
313
|
+
// Handle simple formats first for backwards compatibility
|
|
314
|
+
const simpleMatch = duration.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/i);
|
|
315
|
+
if (simpleMatch) {
|
|
316
|
+
const value = parseFloat(simpleMatch[1]);
|
|
317
|
+
const unit = (simpleMatch[2] || "s").toLowerCase();
|
|
318
|
+
switch (unit) {
|
|
319
|
+
case "h": return value * 3600 * 1000;
|
|
320
|
+
case "m": return value * 60 * 1000;
|
|
321
|
+
case "s": return value * 1000;
|
|
322
|
+
case "ms": return value;
|
|
323
|
+
default: return value * 1000;
|
|
324
|
+
}
|
|
314
325
|
}
|
|
326
|
+
// Parse compound Go-style durations: "1h16m0.667s", "5m30s", etc.
|
|
327
|
+
const compoundRegex = /(\d+(?:\.\d+)?)(h|m(?!s)|s|ms)/gi;
|
|
328
|
+
let totalMs = 0;
|
|
329
|
+
let matchFound = false;
|
|
330
|
+
let match;
|
|
331
|
+
while ((match = compoundRegex.exec(duration)) !== null) {
|
|
332
|
+
matchFound = true;
|
|
333
|
+
const value = parseFloat(match[1]);
|
|
334
|
+
const unit = match[2].toLowerCase();
|
|
335
|
+
switch (unit) {
|
|
336
|
+
case "h":
|
|
337
|
+
totalMs += value * 3600 * 1000;
|
|
338
|
+
break;
|
|
339
|
+
case "m":
|
|
340
|
+
totalMs += value * 60 * 1000;
|
|
341
|
+
break;
|
|
342
|
+
case "s":
|
|
343
|
+
totalMs += value * 1000;
|
|
344
|
+
break;
|
|
345
|
+
case "ms":
|
|
346
|
+
totalMs += value;
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return matchFound ? totalMs : null;
|
|
315
351
|
}
|
|
316
352
|
function extractRateLimitBodyInfo(body) {
|
|
317
353
|
if (!body || typeof body !== "object") {
|
|
@@ -435,9 +471,10 @@ const emptyResponseAttempts = new Map();
|
|
|
435
471
|
* @param accountIndex - The account index
|
|
436
472
|
* @param quotaKey - The quota key (e.g., "gemini-cli", "gemini-antigravity", "claude")
|
|
437
473
|
* @param serverRetryAfterMs - Server-provided retry delay (if any)
|
|
474
|
+
* @param maxBackoffMs - Maximum backoff delay in milliseconds (default 60000)
|
|
438
475
|
* @returns { attempt, delayMs, isDuplicate } - isDuplicate=true if within dedup window
|
|
439
476
|
*/
|
|
440
|
-
function getRateLimitBackoff(accountIndex, quotaKey, serverRetryAfterMs) {
|
|
477
|
+
function getRateLimitBackoff(accountIndex, quotaKey, serverRetryAfterMs, maxBackoffMs = 60_000) {
|
|
441
478
|
const now = Date.now();
|
|
442
479
|
const stateKey = `${accountIndex}:${quotaKey}`;
|
|
443
480
|
const previous = rateLimitStateByAccountQuota.get(stateKey);
|
|
@@ -445,7 +482,7 @@ function getRateLimitBackoff(accountIndex, quotaKey, serverRetryAfterMs) {
|
|
|
445
482
|
if (previous && (now - previous.lastAt < RATE_LIMIT_DEDUP_WINDOW_MS)) {
|
|
446
483
|
// Same rate limit event from concurrent request - don't increment
|
|
447
484
|
const baseDelay = serverRetryAfterMs ?? 1000;
|
|
448
|
-
const backoffDelay = Math.min(baseDelay * Math.pow(2, previous.consecutive429 - 1),
|
|
485
|
+
const backoffDelay = Math.min(baseDelay * Math.pow(2, previous.consecutive429 - 1), maxBackoffMs);
|
|
449
486
|
return {
|
|
450
487
|
attempt: previous.consecutive429,
|
|
451
488
|
delayMs: Math.max(baseDelay, backoffDelay),
|
|
@@ -462,7 +499,7 @@ function getRateLimitBackoff(accountIndex, quotaKey, serverRetryAfterMs) {
|
|
|
462
499
|
quotaKey
|
|
463
500
|
});
|
|
464
501
|
const baseDelay = serverRetryAfterMs ?? 1000;
|
|
465
|
-
const backoffDelay = Math.min(baseDelay * Math.pow(2, attempt - 1),
|
|
502
|
+
const backoffDelay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxBackoffMs);
|
|
466
503
|
return { attempt, delayMs: Math.max(baseDelay, backoffDelay), isDuplicate: false };
|
|
467
504
|
}
|
|
468
505
|
/**
|
|
@@ -540,6 +577,8 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
540
577
|
// Load configuration from files and environment variables
|
|
541
578
|
const config = loadConfig(directory);
|
|
542
579
|
initRuntimeConfig(config);
|
|
580
|
+
// Cached getAuth function for tool access
|
|
581
|
+
let cachedGetAuth = null;
|
|
543
582
|
// Initialize debug with config
|
|
544
583
|
initializeDebug(config);
|
|
545
584
|
// Initialize structured logger for TUI integration
|
|
@@ -616,11 +655,55 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
616
655
|
}
|
|
617
656
|
}
|
|
618
657
|
};
|
|
658
|
+
// Create google_search tool with access to auth context
|
|
659
|
+
const googleSearchTool = tool({
|
|
660
|
+
description: "Search the web using Google Search and analyze URLs. Returns real-time information from the internet with source citations. Use this when you need up-to-date information about current events, recent developments, or any topic that may have changed. You can also provide specific URLs to analyze. IMPORTANT: If the user mentions or provides any URLs in their query, you MUST extract those URLs and pass them in the 'urls' parameter for direct analysis.",
|
|
661
|
+
args: {
|
|
662
|
+
query: tool.schema.string().describe("The search query or question to answer using web search"),
|
|
663
|
+
urls: tool.schema.array(tool.schema.string()).optional().describe("List of specific URLs to fetch and analyze. IMPORTANT: Always extract and include any URLs mentioned by the user in their query here."),
|
|
664
|
+
thinking: tool.schema.boolean().optional().default(true).describe("Enable deep thinking for more thorough analysis (default: true)"),
|
|
665
|
+
},
|
|
666
|
+
async execute(args, ctx) {
|
|
667
|
+
log.debug("Google Search tool called", { query: args.query, urlCount: args.urls?.length ?? 0 });
|
|
668
|
+
// Get current auth context
|
|
669
|
+
const auth = cachedGetAuth ? await cachedGetAuth() : null;
|
|
670
|
+
if (!auth || !isOAuthAuth(auth)) {
|
|
671
|
+
return "Error: Not authenticated with Antigravity. Please run `opencode auth login` to authenticate.";
|
|
672
|
+
}
|
|
673
|
+
// Get access token and project ID
|
|
674
|
+
const parts = parseRefreshParts(auth.refresh);
|
|
675
|
+
const projectId = parts.managedProjectId || parts.projectId || "unknown";
|
|
676
|
+
// Ensure we have a valid access token
|
|
677
|
+
let accessToken = auth.access;
|
|
678
|
+
if (!accessToken || accessTokenExpired(auth)) {
|
|
679
|
+
try {
|
|
680
|
+
const refreshed = await refreshAccessToken(auth, client, providerId);
|
|
681
|
+
accessToken = refreshed?.access;
|
|
682
|
+
}
|
|
683
|
+
catch (error) {
|
|
684
|
+
return `Error: Failed to refresh access token: ${error instanceof Error ? error.message : String(error)}`;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (!accessToken) {
|
|
688
|
+
return "Error: No valid access token available. Please run `opencode auth login` to re-authenticate.";
|
|
689
|
+
}
|
|
690
|
+
return executeSearch({
|
|
691
|
+
query: args.query,
|
|
692
|
+
urls: args.urls,
|
|
693
|
+
thinking: args.thinking,
|
|
694
|
+
}, accessToken, projectId, ctx.abort);
|
|
695
|
+
},
|
|
696
|
+
});
|
|
619
697
|
return {
|
|
620
698
|
event: eventHandler,
|
|
699
|
+
tool: {
|
|
700
|
+
google_search: googleSearchTool,
|
|
701
|
+
},
|
|
621
702
|
auth: {
|
|
622
703
|
provider: providerId,
|
|
623
704
|
loader: async (getAuth, provider) => {
|
|
705
|
+
// Cache getAuth for tool access
|
|
706
|
+
cachedGetAuth = getAuth;
|
|
624
707
|
const auth = await getAuth();
|
|
625
708
|
// If OpenCode has no valid OAuth auth, clear any stale account storage
|
|
626
709
|
if (!isOAuthAuth(auth)) {
|
|
@@ -704,10 +787,28 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
704
787
|
throw abortSignal.reason instanceof Error ? abortSignal.reason : new Error("Aborted");
|
|
705
788
|
}
|
|
706
789
|
};
|
|
707
|
-
//
|
|
790
|
+
// Use while(true) loop to handle rate limits with backoff
|
|
791
|
+
// This ensures we wait and retry when all accounts are rate-limited
|
|
792
|
+
const quietMode = config.quiet_mode;
|
|
793
|
+
// Debounce rate limit toasts to avoid spam (5s cooldown per message type)
|
|
794
|
+
const rateLimitToastCooldowns = new Map();
|
|
795
|
+
const RATE_LIMIT_TOAST_COOLDOWN_MS = 5000;
|
|
796
|
+
// Helper to show toast without blocking on abort (respects quiet_mode)
|
|
708
797
|
const showToast = async (message, variant) => {
|
|
798
|
+
if (quietMode)
|
|
799
|
+
return;
|
|
709
800
|
if (abortSignal?.aborted)
|
|
710
801
|
return;
|
|
802
|
+
// Debounce rate limit warnings to prevent toast spam
|
|
803
|
+
if (variant === "warning" && message.toLowerCase().includes("rate")) {
|
|
804
|
+
const toastKey = message.replace(/\d+/g, "X"); // Normalize numbers for grouping
|
|
805
|
+
const lastShown = rateLimitToastCooldowns.get(toastKey) ?? 0;
|
|
806
|
+
const now = Date.now();
|
|
807
|
+
if (now - lastShown < RATE_LIMIT_TOAST_COOLDOWN_MS) {
|
|
808
|
+
return; // Skip - shown recently
|
|
809
|
+
}
|
|
810
|
+
rateLimitToastCooldowns.set(toastKey, now);
|
|
811
|
+
}
|
|
711
812
|
try {
|
|
712
813
|
await client.tui.showToast({
|
|
713
814
|
body: { message, variant },
|
|
@@ -717,9 +818,6 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
717
818
|
// TUI may not be available
|
|
718
819
|
}
|
|
719
820
|
};
|
|
720
|
-
// Use while(true) loop to handle rate limits with backoff
|
|
721
|
-
// This ensures we wait and retry when all accounts are rate-limited
|
|
722
|
-
const quietMode = config.quiet_mode;
|
|
723
821
|
const hasOtherAccountWithAntigravity = (currentAccount) => {
|
|
724
822
|
if (family !== "gemini")
|
|
725
823
|
return false;
|
|
@@ -735,8 +833,10 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
735
833
|
}
|
|
736
834
|
const account = accountManager.getCurrentOrNextForFamily(family, model, config.account_selection_strategy, 'antigravity', config.pid_offset_enabled);
|
|
737
835
|
if (!account) {
|
|
836
|
+
const headerStyle = getHeaderStyleFromUrl(urlString, family);
|
|
837
|
+
const explicitQuota = isExplicitQuotaFromUrl(urlString);
|
|
738
838
|
// All accounts are rate-limited - wait and retry
|
|
739
|
-
const waitMs = accountManager.getMinWaitTimeForFamily(family, model) || 60_000;
|
|
839
|
+
const waitMs = accountManager.getMinWaitTimeForFamily(family, model, headerStyle, explicitQuota) || 60_000;
|
|
740
840
|
const waitSecValue = Math.max(1, Math.ceil(waitMs / 1000));
|
|
741
841
|
pushDebug(`all-rate-limited family=${family} accounts=${accountCount} waitMs=${waitMs}`);
|
|
742
842
|
if (isDebugEnabled()) {
|
|
@@ -773,8 +873,8 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
773
873
|
rateLimitState: account.rateLimitResetTimes,
|
|
774
874
|
});
|
|
775
875
|
}
|
|
776
|
-
// Show toast when switching to a different account (debounced,
|
|
777
|
-
if (
|
|
876
|
+
// Show toast when switching to a different account (debounced, quiet_mode handled by showToast)
|
|
877
|
+
if (accountCount > 1 && accountManager.shouldShowAccountToast(account.index)) {
|
|
778
878
|
const accountLabel = account.email || `Account ${account.index + 1}`;
|
|
779
879
|
await showToast(`Using ${accountLabel} (${account.index + 1}/${accountCount})`, "info");
|
|
780
880
|
accountManager.markToastShown(account.index);
|
|
@@ -928,6 +1028,9 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
928
1028
|
let headerStyle = getHeaderStyleFromUrl(urlString, family);
|
|
929
1029
|
const explicitQuota = isExplicitQuotaFromUrl(urlString);
|
|
930
1030
|
pushDebug(`headerStyle=${headerStyle} explicit=${explicitQuota}`);
|
|
1031
|
+
if (account.fingerprint) {
|
|
1032
|
+
pushDebug(`fingerprint: quotaUser=${account.fingerprint.quotaUser} deviceId=${account.fingerprint.deviceId.slice(0, 8)}...`);
|
|
1033
|
+
}
|
|
931
1034
|
// Check if this header style is rate-limited for this account
|
|
932
1035
|
if (accountManager.isRateLimitedForHeaderStyle(account, family, headerStyle, model)) {
|
|
933
1036
|
// Quota fallback: try alternate quota on same account (if enabled and not explicit)
|
|
@@ -936,9 +1039,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
936
1039
|
if (alternateStyle && alternateStyle !== headerStyle) {
|
|
937
1040
|
const quotaName = headerStyle === "gemini-cli" ? "Gemini CLI" : "Antigravity";
|
|
938
1041
|
const altQuotaName = alternateStyle === "gemini-cli" ? "Gemini CLI" : "Antigravity";
|
|
939
|
-
|
|
940
|
-
await showToast(`${quotaName} quota exhausted, using ${altQuotaName} quota`, "warning");
|
|
941
|
-
}
|
|
1042
|
+
await showToast(`${quotaName} quota exhausted, using ${altQuotaName} quota`, "warning");
|
|
942
1043
|
headerStyle = alternateStyle;
|
|
943
1044
|
pushDebug(`quota fallback: ${headerStyle}`);
|
|
944
1045
|
}
|
|
@@ -955,15 +1056,20 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
955
1056
|
let forceThinkingRecovery = false;
|
|
956
1057
|
// Track if token was consumed (for hybrid strategy refund on error)
|
|
957
1058
|
let tokenConsumed = false;
|
|
1059
|
+
// Track capacity retries per endpoint to prevent infinite loops
|
|
1060
|
+
let capacityRetryCount = 0;
|
|
1061
|
+
let lastEndpointIndex = -1;
|
|
958
1062
|
for (let i = 0; i < ANTIGRAVITY_ENDPOINT_FALLBACKS.length; i++) {
|
|
1063
|
+
// Reset capacity retry counter when switching to a new endpoint
|
|
1064
|
+
if (i !== lastEndpointIndex) {
|
|
1065
|
+
capacityRetryCount = 0;
|
|
1066
|
+
lastEndpointIndex = i;
|
|
1067
|
+
}
|
|
959
1068
|
const currentEndpoint = ANTIGRAVITY_ENDPOINT_FALLBACKS[i];
|
|
960
1069
|
try {
|
|
961
1070
|
const prepared = prepareAntigravityRequest(input, init, accessToken, projectContext.effectiveProjectId, currentEndpoint, headerStyle, forceThinkingRecovery, {
|
|
962
1071
|
claudeToolHardening: config.claude_tool_hardening,
|
|
963
|
-
|
|
964
|
-
mode: config.web_search.default_mode,
|
|
965
|
-
threshold: config.web_search.grounding_threshold
|
|
966
|
-
} : undefined,
|
|
1072
|
+
fingerprint: account.fingerprint,
|
|
967
1073
|
});
|
|
968
1074
|
const originalUrl = toUrlString(input);
|
|
969
1075
|
const resolvedUrl = toUrlString(prepared.request);
|
|
@@ -979,6 +1085,12 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
979
1085
|
projectId: projectContext.effectiveProjectId,
|
|
980
1086
|
});
|
|
981
1087
|
await runThinkingWarmup(prepared, projectContext.effectiveProjectId);
|
|
1088
|
+
if (config.request_jitter_max_ms > 0) {
|
|
1089
|
+
const jitterMs = Math.floor(Math.random() * config.request_jitter_max_ms);
|
|
1090
|
+
if (jitterMs > 0) {
|
|
1091
|
+
await sleep(jitterMs, abortSignal);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
982
1094
|
// Consume token for hybrid strategy
|
|
983
1095
|
// Refunded later if request fails (429 or network error)
|
|
984
1096
|
if (config.account_selection_strategy === 'hybrid') {
|
|
@@ -986,22 +1098,62 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
986
1098
|
}
|
|
987
1099
|
const response = await fetch(prepared.request, prepared.init);
|
|
988
1100
|
pushDebug(`status=${response.status} ${response.statusText}`);
|
|
989
|
-
// Handle 429 rate limit with improved logic
|
|
990
|
-
if (response.status === 429) {
|
|
1101
|
+
// Handle 429 rate limit (or Service Overloaded) with improved logic
|
|
1102
|
+
if (response.status === 429 || response.status === 503 || response.status === 529) {
|
|
991
1103
|
// Refund token on rate limit
|
|
992
1104
|
if (tokenConsumed) {
|
|
993
1105
|
getTokenTracker().refund(account.index);
|
|
994
1106
|
tokenConsumed = false;
|
|
995
1107
|
}
|
|
996
|
-
const
|
|
1108
|
+
const defaultRetryMs = (config.default_retry_after_seconds ?? 60) * 1000;
|
|
1109
|
+
const maxBackoffMs = (config.max_backoff_seconds ?? 60) * 1000;
|
|
1110
|
+
const headerRetryMs = retryAfterMsFromResponse(response, defaultRetryMs);
|
|
997
1111
|
const bodyInfo = await extractRetryInfoFromBody(response);
|
|
998
1112
|
const serverRetryMs = bodyInfo.retryDelayMs ?? headerRetryMs;
|
|
1113
|
+
// [Enhanced Parsing] Pass status to handling logic
|
|
1114
|
+
const rateLimitReason = parseRateLimitReason(bodyInfo.reason, bodyInfo.message, response.status);
|
|
1115
|
+
// STRATEGY 1: CAPACITY / SERVER ERROR (Transient)
|
|
1116
|
+
// Goal: Wait and Retry SAME Account. DO NOT LOCK.
|
|
1117
|
+
// We handle this FIRST to avoid calling getRateLimitBackoff() and polluting the global rate limit state for transient errors.
|
|
1118
|
+
if (rateLimitReason === "MODEL_CAPACITY_EXHAUSTED" || rateLimitReason === "SERVER_ERROR") {
|
|
1119
|
+
// Exponential backoff with jitter for capacity errors: 1s → 2s → 4s → 8s (max)
|
|
1120
|
+
// Matches Antigravity-Manager's ExponentialBackoff(1s, 8s)
|
|
1121
|
+
const baseDelayMs = 1000;
|
|
1122
|
+
const maxDelayMs = 8000;
|
|
1123
|
+
const exponentialDelay = Math.min(baseDelayMs * Math.pow(2, capacityRetryCount), maxDelayMs);
|
|
1124
|
+
// Add ±10% jitter to prevent thundering herd
|
|
1125
|
+
const jitter = exponentialDelay * (0.9 + Math.random() * 0.2);
|
|
1126
|
+
const waitMs = Math.round(jitter);
|
|
1127
|
+
const waitSec = Math.round(waitMs / 1000);
|
|
1128
|
+
pushDebug(`Server busy (${rateLimitReason}) on account ${account.index}, exponential backoff ${waitMs}ms (attempt ${capacityRetryCount + 1})`);
|
|
1129
|
+
await showToast(`⏳ Server busy (${response.status}). Retrying in ${waitSec}s...`, "warning");
|
|
1130
|
+
await sleep(waitMs, abortSignal);
|
|
1131
|
+
// CRITICAL FIX: Decrement i so that the loop 'continue' retries the SAME endpoint index
|
|
1132
|
+
// (i++ in the loop will bring it back to the current index)
|
|
1133
|
+
// But limit retries to prevent infinite loops (Greptile feedback)
|
|
1134
|
+
if (capacityRetryCount < 3) {
|
|
1135
|
+
capacityRetryCount++;
|
|
1136
|
+
i -= 1;
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
else {
|
|
1140
|
+
pushDebug(`Max capacity retries (3) exhausted for endpoint ${currentEndpoint}, regenerating fingerprint...`);
|
|
1141
|
+
// Regenerate fingerprint to get fresh device identity before trying next endpoint
|
|
1142
|
+
const newFingerprint = accountManager.regenerateAccountFingerprint(account.index);
|
|
1143
|
+
if (newFingerprint) {
|
|
1144
|
+
pushDebug(`Fingerprint regenerated for account ${account.index}`);
|
|
1145
|
+
}
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
// STRATEGY 2: RATE LIMIT EXCEEDED (RPM) / QUOTA EXHAUSTED / UNKNOWN
|
|
1150
|
+
// Goal: Lock and Rotate (Standard Logic)
|
|
1151
|
+
// Only now do we call getRateLimitBackoff, which increments the global failure tracker
|
|
999
1152
|
const quotaKey = headerStyleToQuotaKey(headerStyle, family);
|
|
1000
1153
|
const { attempt, delayMs, isDuplicate } = getRateLimitBackoff(account.index, quotaKey, serverRetryMs);
|
|
1001
|
-
|
|
1154
|
+
// Calculate potential backoffs
|
|
1002
1155
|
const smartBackoffMs = calculateBackoffMs(rateLimitReason, account.consecutiveFailures ?? 0, serverRetryMs);
|
|
1003
1156
|
const effectiveDelayMs = Math.max(delayMs, smartBackoffMs);
|
|
1004
|
-
const isCapacityExhausted = rateLimitReason === "MODEL_CAPACITY_EXHAUSTED";
|
|
1005
1157
|
pushDebug(`429 idx=${account.index} email=${account.email ?? ""} family=${family} delayMs=${effectiveDelayMs} attempt=${attempt} reason=${rateLimitReason}`);
|
|
1006
1158
|
if (bodyInfo.message) {
|
|
1007
1159
|
pushDebug(`429 message=${bodyInfo.message}`);
|
|
@@ -1015,36 +1167,37 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1015
1167
|
logRateLimitEvent(account.index, account.email, family, response.status, effectiveDelayMs, bodyInfo);
|
|
1016
1168
|
await logResponseBody(debugContext, response, 429);
|
|
1017
1169
|
getHealthTracker().recordRateLimit(account.index);
|
|
1018
|
-
if (isCapacityExhausted) {
|
|
1019
|
-
const capacityBackoffMs = calculateBackoffMs(rateLimitReason, account.consecutiveFailures ?? 0, serverRetryMs);
|
|
1020
|
-
accountManager.markRateLimitedWithReason(account, family, headerStyle, model, rateLimitReason, serverRetryMs);
|
|
1021
|
-
const backoffFormatted = formatWaitTime(capacityBackoffMs);
|
|
1022
|
-
const failures = account.consecutiveFailures ?? 0;
|
|
1023
|
-
pushDebug(`capacity exhausted on account ${account.index}, backoff=${capacityBackoffMs}ms (failure #${failures})`);
|
|
1024
|
-
// Check if we can switch to another account (respects switch_on_first_rate_limit config)
|
|
1025
|
-
if (config.switch_on_first_rate_limit && accountCount > 1) {
|
|
1026
|
-
await showToast(`Server at capacity. Switching account in 1s...`, "warning");
|
|
1027
|
-
await sleep(FIRST_RETRY_DELAY_MS, abortSignal);
|
|
1028
|
-
shouldSwitchAccount = true;
|
|
1029
|
-
break;
|
|
1030
|
-
}
|
|
1031
|
-
// No other accounts available or config disabled - wait the backoff
|
|
1032
|
-
await showToast(`Server at capacity. Waiting ${backoffFormatted}... (attempt ${failures})`, "warning");
|
|
1033
|
-
await sleep(capacityBackoffMs, abortSignal);
|
|
1034
|
-
continue;
|
|
1035
|
-
}
|
|
1036
1170
|
const accountLabel = account.email || `Account ${account.index + 1}`;
|
|
1037
|
-
|
|
1171
|
+
// Progressive retry for standard 429s: 1st 429 → 1s then switch (if enabled) or retry same
|
|
1172
|
+
if (attempt === 1 && rateLimitReason !== "QUOTA_EXHAUSTED") {
|
|
1038
1173
|
await showToast(`Rate limited. Quick retry in 1s...`, "warning");
|
|
1039
1174
|
await sleep(FIRST_RETRY_DELAY_MS, abortSignal);
|
|
1175
|
+
// CacheFirst mode: wait for same account if within threshold (preserves prompt cache)
|
|
1176
|
+
if (config.scheduling_mode === 'cache_first') {
|
|
1177
|
+
const maxCacheFirstWaitMs = config.max_cache_first_wait_seconds * 1000;
|
|
1178
|
+
// effectiveDelayMs is the backoff calculated for this account
|
|
1179
|
+
if (effectiveDelayMs <= maxCacheFirstWaitMs) {
|
|
1180
|
+
pushDebug(`cache_first: waiting ${effectiveDelayMs}ms for same account to recover`);
|
|
1181
|
+
await showToast(`⏳ Waiting ${Math.ceil(effectiveDelayMs / 1000)}s for same account (prompt cache preserved)...`, "info");
|
|
1182
|
+
accountManager.markRateLimitedWithReason(account, family, headerStyle, model, rateLimitReason, serverRetryMs);
|
|
1183
|
+
await sleep(effectiveDelayMs, abortSignal);
|
|
1184
|
+
// Retry same endpoint after wait
|
|
1185
|
+
i -= 1;
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
// Wait time exceeds threshold, fall through to switch
|
|
1189
|
+
pushDebug(`cache_first: wait ${effectiveDelayMs}ms exceeds max ${maxCacheFirstWaitMs}ms, switching account`);
|
|
1190
|
+
}
|
|
1040
1191
|
if (config.switch_on_first_rate_limit && accountCount > 1) {
|
|
1041
|
-
accountManager.markRateLimitedWithReason(account, family, headerStyle, model, rateLimitReason, serverRetryMs);
|
|
1192
|
+
accountManager.markRateLimitedWithReason(account, family, headerStyle, model, rateLimitReason, serverRetryMs, config.failure_ttl_seconds * 1000);
|
|
1042
1193
|
shouldSwitchAccount = true;
|
|
1043
1194
|
break;
|
|
1044
1195
|
}
|
|
1196
|
+
// Same endpoint retry for first RPM hit
|
|
1197
|
+
i -= 1;
|
|
1045
1198
|
continue;
|
|
1046
1199
|
}
|
|
1047
|
-
accountManager.markRateLimitedWithReason(account, family, headerStyle, model, rateLimitReason, serverRetryMs);
|
|
1200
|
+
accountManager.markRateLimitedWithReason(account, family, headerStyle, model, rateLimitReason, serverRetryMs, config.failure_ttl_seconds * 1000);
|
|
1048
1201
|
accountManager.requestSaveToDisk();
|
|
1049
1202
|
// For Gemini, try prioritized Antigravity across ALL accounts first
|
|
1050
1203
|
if (family === "gemini") {
|
|
@@ -1147,6 +1300,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1147
1300
|
if (response.ok) {
|
|
1148
1301
|
account.consecutiveFailures = 0;
|
|
1149
1302
|
getHealthTracker().recordSuccess(account.index);
|
|
1303
|
+
accountManager.markAccountUsed(account.index);
|
|
1150
1304
|
}
|
|
1151
1305
|
logAntigravityDebugResponse(debugContext, response, {
|
|
1152
1306
|
note: response.ok ? "Success" : `Error ${response.status}`,
|
|
@@ -1158,9 +1312,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1158
1312
|
const cloned = response.clone();
|
|
1159
1313
|
const bodyText = await cloned.text();
|
|
1160
1314
|
if (bodyText.includes("Prompt is too long") || bodyText.includes("prompt_too_long")) {
|
|
1161
|
-
|
|
1162
|
-
await showToast("Context too long - use /compact to reduce size", "warning");
|
|
1163
|
-
}
|
|
1315
|
+
await showToast("Context too long - use /compact to reduce size", "warning");
|
|
1164
1316
|
const errorMessage = `[Antigravity Error] Context is too long for this model.\n\nPlease use /compact to reduce context size, then retry your request.\n\nAlternatively, you can:\n- Use /clear to start fresh\n- Use /undo to remove recent messages\n- Switch to a model with larger context window`;
|
|
1165
1317
|
return createSyntheticErrorResponse(errorMessage, prepared.requestedModel);
|
|
1166
1318
|
}
|
|
@@ -1197,7 +1349,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1197
1349
|
const transformedResponse = await transformAntigravityResponse(response, prepared.streaming, debugContext, prepared.requestedModel, prepared.projectId, prepared.endpoint, prepared.effectiveModel, prepared.sessionId, prepared.toolDebugMissing, prepared.toolDebugSummary, prepared.toolDebugPayload, debugLines);
|
|
1198
1350
|
// Check for context errors and show appropriate toast
|
|
1199
1351
|
const contextError = transformedResponse.headers.get("x-antigravity-context-error");
|
|
1200
|
-
if (contextError
|
|
1352
|
+
if (contextError) {
|
|
1201
1353
|
if (contextError === "prompt_too_long") {
|
|
1202
1354
|
await showToast("Context too long - use /compact to reduce size, or trim your request", "warning");
|
|
1203
1355
|
}
|
|
@@ -1289,18 +1441,82 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1289
1441
|
const useManualMode = noBrowser || shouldSkipLocalServer();
|
|
1290
1442
|
// Check for existing accounts and prompt user for login mode
|
|
1291
1443
|
let startFresh = true;
|
|
1444
|
+
let refreshAccountIndex;
|
|
1292
1445
|
const existingStorage = await loadAccounts();
|
|
1293
1446
|
if (existingStorage && existingStorage.accounts.length > 0) {
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1447
|
+
const now = Date.now();
|
|
1448
|
+
const existingAccounts = existingStorage.accounts.map((acc, idx) => {
|
|
1449
|
+
let status = 'unknown';
|
|
1450
|
+
const rateLimits = acc.rateLimitResetTimes;
|
|
1451
|
+
if (rateLimits) {
|
|
1452
|
+
const isRateLimited = Object.values(rateLimits).some((resetTime) => typeof resetTime === 'number' && resetTime > now);
|
|
1453
|
+
if (isRateLimited) {
|
|
1454
|
+
status = 'rate-limited';
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
status = 'active';
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
else {
|
|
1461
|
+
status = 'active';
|
|
1462
|
+
}
|
|
1463
|
+
if (acc.coolingDownUntil && acc.coolingDownUntil > now) {
|
|
1464
|
+
status = 'rate-limited';
|
|
1465
|
+
}
|
|
1466
|
+
return {
|
|
1467
|
+
email: acc.email,
|
|
1468
|
+
index: idx,
|
|
1469
|
+
addedAt: acc.addedAt,
|
|
1470
|
+
lastUsed: acc.lastUsed,
|
|
1471
|
+
status,
|
|
1472
|
+
isCurrentAccount: idx === (existingStorage.activeIndex ?? 0),
|
|
1473
|
+
};
|
|
1474
|
+
});
|
|
1475
|
+
const menuResult = await promptLoginMode(existingAccounts);
|
|
1476
|
+
if (menuResult.mode === "cancel") {
|
|
1477
|
+
return {
|
|
1478
|
+
url: "",
|
|
1479
|
+
instructions: "Authentication cancelled",
|
|
1480
|
+
method: "auto",
|
|
1481
|
+
callback: async () => ({ type: "failed", error: "Authentication cancelled" }),
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
if (menuResult.deleteAccountIndex !== undefined) {
|
|
1485
|
+
const updatedAccounts = existingStorage.accounts.filter((_, idx) => idx !== menuResult.deleteAccountIndex);
|
|
1486
|
+
await saveAccounts({
|
|
1487
|
+
version: 3,
|
|
1488
|
+
accounts: updatedAccounts,
|
|
1489
|
+
activeIndex: 0,
|
|
1490
|
+
activeIndexByFamily: { claude: 0, gemini: 0 },
|
|
1491
|
+
});
|
|
1492
|
+
console.log("\nAccount deleted.\n");
|
|
1493
|
+
if (updatedAccounts.length > 0) {
|
|
1494
|
+
return {
|
|
1495
|
+
url: "",
|
|
1496
|
+
instructions: "Account deleted. Please run `opencode auth login` again to continue.",
|
|
1497
|
+
method: "auto",
|
|
1498
|
+
callback: async () => ({ type: "failed", error: "Account deleted - please re-run auth" }),
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
if (menuResult.refreshAccountIndex !== undefined) {
|
|
1503
|
+
refreshAccountIndex = menuResult.refreshAccountIndex;
|
|
1504
|
+
const refreshEmail = existingStorage.accounts[refreshAccountIndex]?.email;
|
|
1505
|
+
console.log(`\nRe-authenticating ${refreshEmail || 'account'}...\n`);
|
|
1506
|
+
startFresh = false;
|
|
1507
|
+
}
|
|
1508
|
+
if (menuResult.deleteAll) {
|
|
1509
|
+
await clearAccounts();
|
|
1510
|
+
console.log("\nAll accounts deleted.\n");
|
|
1511
|
+
startFresh = true;
|
|
1302
1512
|
}
|
|
1303
1513
|
else {
|
|
1514
|
+
startFresh = menuResult.mode === "fresh";
|
|
1515
|
+
}
|
|
1516
|
+
if (startFresh && !menuResult.deleteAll) {
|
|
1517
|
+
console.log("\nStarting fresh - existing accounts will be replaced.\n");
|
|
1518
|
+
}
|
|
1519
|
+
else if (!startFresh) {
|
|
1304
1520
|
console.log("\nAdding to existing accounts.\n");
|
|
1305
1521
|
}
|
|
1306
1522
|
}
|
|
@@ -1394,7 +1610,6 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1394
1610
|
break;
|
|
1395
1611
|
}
|
|
1396
1612
|
accounts.push(result);
|
|
1397
|
-
// Show toast for successful account authentication
|
|
1398
1613
|
try {
|
|
1399
1614
|
await client.tui.showToast({
|
|
1400
1615
|
body: {
|
|
@@ -1404,15 +1619,40 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1404
1619
|
});
|
|
1405
1620
|
}
|
|
1406
1621
|
catch {
|
|
1407
|
-
// TUI may not be available in CLI mode
|
|
1408
1622
|
}
|
|
1409
1623
|
try {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1624
|
+
if (refreshAccountIndex !== undefined) {
|
|
1625
|
+
const currentStorage = await loadAccounts();
|
|
1626
|
+
if (currentStorage) {
|
|
1627
|
+
const updatedAccounts = [...currentStorage.accounts];
|
|
1628
|
+
const parts = parseRefreshParts(result.refresh);
|
|
1629
|
+
if (parts.refreshToken) {
|
|
1630
|
+
updatedAccounts[refreshAccountIndex] = {
|
|
1631
|
+
email: result.email ?? updatedAccounts[refreshAccountIndex]?.email,
|
|
1632
|
+
refreshToken: parts.refreshToken,
|
|
1633
|
+
projectId: parts.projectId ?? updatedAccounts[refreshAccountIndex]?.projectId,
|
|
1634
|
+
managedProjectId: parts.managedProjectId ?? updatedAccounts[refreshAccountIndex]?.managedProjectId,
|
|
1635
|
+
addedAt: updatedAccounts[refreshAccountIndex]?.addedAt ?? Date.now(),
|
|
1636
|
+
lastUsed: Date.now(),
|
|
1637
|
+
};
|
|
1638
|
+
await saveAccounts({
|
|
1639
|
+
version: 3,
|
|
1640
|
+
accounts: updatedAccounts,
|
|
1641
|
+
activeIndex: currentStorage.activeIndex,
|
|
1642
|
+
activeIndexByFamily: currentStorage.activeIndexByFamily,
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
else {
|
|
1648
|
+
const isFirstAccount = accounts.length === 1;
|
|
1649
|
+
await persistAccountPool([result], isFirstAccount && startFresh);
|
|
1650
|
+
}
|
|
1413
1651
|
}
|
|
1414
1652
|
catch {
|
|
1415
|
-
|
|
1653
|
+
}
|
|
1654
|
+
if (refreshAccountIndex !== undefined) {
|
|
1655
|
+
break;
|
|
1416
1656
|
}
|
|
1417
1657
|
if (accounts.length >= MAX_OAUTH_ACCOUNTS) {
|
|
1418
1658
|
break;
|
|
@@ -1442,7 +1682,6 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1442
1682
|
callback: async () => ({ type: "failed", error: "Authentication cancelled" }),
|
|
1443
1683
|
};
|
|
1444
1684
|
}
|
|
1445
|
-
// Get the actual deduplicated account count from storage
|
|
1446
1685
|
let actualAccountCount = accounts.length;
|
|
1447
1686
|
try {
|
|
1448
1687
|
const finalStorage = await loadAccounts();
|
|
@@ -1451,11 +1690,13 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1451
1690
|
}
|
|
1452
1691
|
}
|
|
1453
1692
|
catch {
|
|
1454
|
-
// Fall back to accounts.length if we can't read storage
|
|
1455
1693
|
}
|
|
1694
|
+
const successMessage = refreshAccountIndex !== undefined
|
|
1695
|
+
? `Token refreshed successfully.`
|
|
1696
|
+
: `Multi-account setup complete (${actualAccountCount} account(s)).`;
|
|
1456
1697
|
return {
|
|
1457
1698
|
url: "",
|
|
1458
|
-
instructions:
|
|
1699
|
+
instructions: successMessage,
|
|
1459
1700
|
method: "auto",
|
|
1460
1701
|
callback: async () => primary,
|
|
1461
1702
|
};
|