@cortexkit/opencode-antigravity-auth 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -8
- package/dist/index.js +1299 -431
- package/dist/index.js.map +4 -4
- package/dist/src/plugin/accounts.d.ts +29 -9
- package/dist/src/plugin/accounts.d.ts.map +1 -1
- package/dist/src/plugin/accounts.js +253 -52
- package/dist/src/plugin/accounts.js.map +1 -1
- package/dist/src/plugin/agy-request-metadata.d.ts +2 -0
- package/dist/src/plugin/agy-request-metadata.d.ts.map +1 -0
- package/dist/src/plugin/agy-request-metadata.js +2 -0
- package/dist/src/plugin/agy-request-metadata.js.map +1 -0
- package/dist/src/plugin/auth-doctor.d.ts +1 -1
- package/dist/src/plugin/auth-doctor.d.ts.map +1 -1
- package/dist/src/plugin/auth-doctor.js +9 -0
- package/dist/src/plugin/auth-doctor.js.map +1 -1
- package/dist/src/plugin/image-saver.d.ts +2 -21
- package/dist/src/plugin/image-saver.d.ts.map +1 -1
- package/dist/src/plugin/image-saver.js +29 -63
- package/dist/src/plugin/image-saver.js.map +1 -1
- package/dist/src/plugin/request-helpers.d.ts +1 -0
- package/dist/src/plugin/request-helpers.d.ts.map +1 -1
- package/dist/src/plugin/request-helpers.js +9 -4
- package/dist/src/plugin/request-helpers.js.map +1 -1
- package/dist/src/plugin/request.d.ts +6 -0
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +230 -57
- package/dist/src/plugin/request.js.map +1 -1
- package/dist/src/plugin/session-context.d.ts +21 -0
- package/dist/src/plugin/session-context.d.ts.map +1 -0
- package/dist/src/plugin/session-context.js +63 -0
- package/dist/src/plugin/session-context.js.map +1 -0
- package/dist/src/plugin/storage.d.ts +6 -0
- package/dist/src/plugin/storage.d.ts.map +1 -1
- package/dist/src/plugin/storage.js +14 -3
- package/dist/src/plugin/storage.js.map +1 -1
- package/dist/src/plugin/ui/auth-menu.d.ts +1 -1
- package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -1
- package/dist/src/plugin/ui/auth-menu.js +3 -1
- package/dist/src/plugin/ui/auth-menu.js.map +1 -1
- package/dist/src/plugin/ui/quota-status.d.ts.map +1 -1
- package/dist/src/plugin/ui/quota-status.js +2 -1
- package/dist/src/plugin/ui/quota-status.js.map +1 -1
- package/dist/src/plugin.d.ts +16 -0
- package/dist/src/plugin.d.ts.map +1 -1
- package/dist/src/plugin.js +261 -101
- package/dist/src/plugin.js.map +1 -1
- package/package.json +2 -2
package/dist/src/plugin.js
CHANGED
|
@@ -9,9 +9,9 @@ import { fetchWithAgyCliTransport } from "./plugin/agy-transport";
|
|
|
9
9
|
import { buildFingerprintHeaders, getSessionFingerprint } from "./plugin/fingerprint";
|
|
10
10
|
import { clearProvisionFailedKeys, ensureProjectContext } from "./plugin/project";
|
|
11
11
|
import { startAntigravityDebugRequest, logAntigravityDebugResponse, logAccountContext, logRateLimitEvent, logRateLimitSnapshot, logResponseBody, logModelFamily, isDebugEnabled, getLogFilePath, initializeDebug, } from "./plugin/debug";
|
|
12
|
-
import { buildThinkingWarmupBody, getLastCacheStats, isGenerativeLanguageRequest, prepareAntigravityRequest, transformAntigravityResponse, } from "./plugin/request";
|
|
12
|
+
import { buildThinkingWarmupBody, getLastCacheStats, isGenerativeLanguageRequest, getImageModelLocalTitle, prepareAntigravityRequest, transformAntigravityResponse, } from "./plugin/request";
|
|
13
13
|
import { resolveModelWithTier } from "./plugin/transform/model-resolver";
|
|
14
|
-
import { isEmptyResponseBody, createSyntheticErrorResponse, } from "./plugin/request-helpers";
|
|
14
|
+
import { isEmptyResponseBody, createSyntheticErrorResponse, createSyntheticTextResponse, } from "./plugin/request-helpers";
|
|
15
15
|
import { AntigravityTokenRefreshError, refreshAccessToken } from "./plugin/token";
|
|
16
16
|
import { startOAuthListener } from "./plugin/server";
|
|
17
17
|
import { clearAccounts, loadAccounts, saveAccounts, saveAccountsReplace } from "./plugin/storage";
|
|
@@ -30,6 +30,8 @@ import { initHealthTracker, getHealthTracker, initTokenTracker, getTokenTracker
|
|
|
30
30
|
import { getAntigravityVersionResolution, initAntigravityVersion } from "./plugin/version";
|
|
31
31
|
import { executeSearch } from "./plugin/search";
|
|
32
32
|
import { resolvePromptContext } from "./plugin/prompt-context";
|
|
33
|
+
import { AgySessionRegistry, extractOpenCodeSessionIdentity } from "./plugin/session-context";
|
|
34
|
+
import { buildAgyAgentRequestMetadata, createAgyRequestSessionContext, orderAgyRequestPayloadInPlace, } from "./plugin/agy-request-metadata";
|
|
33
35
|
import { dumpGeminiRequest, executeGeminiDumpCommand, GEMINI_DUMP_COMMAND_NAME, noteGeminiDumpResponse, parseGeminiDumpCommandAction, setGeminiDumpEnabled, } from "./plugin/gemini-dump";
|
|
34
36
|
import { getAntigravityOpencodeModelIds, OPENCODE_MODEL_DEFINITIONS } from "./plugin/model-registry";
|
|
35
37
|
const MAX_OAUTH_ACCOUNTS = 10;
|
|
@@ -46,10 +48,6 @@ function getCapacityBackoffDelay(consecutiveFailures) {
|
|
|
46
48
|
}
|
|
47
49
|
const warmupAttemptedSessionIds = new Set();
|
|
48
50
|
const warmupSucceededSessionIds = new Set();
|
|
49
|
-
// Track if this plugin instance is running in a child session (subagent, background task)
|
|
50
|
-
// Used to filter toasts based on toast_scope config
|
|
51
|
-
let isChildSession = false;
|
|
52
|
-
let childSessionParentID = undefined;
|
|
53
51
|
const log = createLogger("plugin");
|
|
54
52
|
// Module-level toast debounce to persist across requests (fixes toast spam)
|
|
55
53
|
const rateLimitToastCooldowns = new Map();
|
|
@@ -264,10 +262,12 @@ function selectBestVerificationUrl(urls) {
|
|
|
264
262
|
});
|
|
265
263
|
return unique[0];
|
|
266
264
|
}
|
|
267
|
-
function
|
|
265
|
+
function extractAccountAccessErrorDetails(bodyText) {
|
|
268
266
|
const decodedBody = decodeEscapedText(bodyText);
|
|
269
267
|
const lowerBody = decodedBody.toLowerCase();
|
|
270
268
|
let validationRequired = lowerBody.includes("validation_required");
|
|
269
|
+
const ineligiblePattern = /(^|[^a-z0-9_])account_ineligible([^a-z0-9_]|$)/i;
|
|
270
|
+
let accountIneligible = ineligiblePattern.test(decodedBody);
|
|
271
271
|
let message;
|
|
272
272
|
const verificationUrls = new Set();
|
|
273
273
|
const collectUrlsFromText = (text) => {
|
|
@@ -312,6 +312,9 @@ function extractVerificationErrorDetails(bodyText) {
|
|
|
312
312
|
if (lowerValue.includes("validation_required")) {
|
|
313
313
|
validationRequired = true;
|
|
314
314
|
}
|
|
315
|
+
if (ineligiblePattern.test(normalizedValue)) {
|
|
316
|
+
accountIneligible = true;
|
|
317
|
+
}
|
|
315
318
|
if (!message &&
|
|
316
319
|
(lowerKey.includes("message") || lowerKey.includes("detail") || lowerKey.includes("description"))) {
|
|
317
320
|
message = normalizedValue;
|
|
@@ -352,17 +355,68 @@ function extractVerificationErrorDetails(bodyText) {
|
|
|
352
355
|
const fallback = decodedBody
|
|
353
356
|
.split("\n")
|
|
354
357
|
.map((line) => line.trim())
|
|
355
|
-
.find((line) => line && !line.startsWith("data:") && /(verify|validation|required)/i.test(line));
|
|
358
|
+
.find((line) => line && !line.startsWith("data:") && /(verify|validation|required|ineligible)/i.test(line));
|
|
356
359
|
if (fallback) {
|
|
357
360
|
message = fallback;
|
|
358
361
|
}
|
|
359
362
|
}
|
|
360
363
|
return {
|
|
361
364
|
validationRequired,
|
|
365
|
+
accountIneligible,
|
|
362
366
|
message,
|
|
363
367
|
verifyUrl: selectBestVerificationUrl([...verificationUrls]),
|
|
364
368
|
};
|
|
365
369
|
}
|
|
370
|
+
function buildAccountAccessProbeRequest(projectId) {
|
|
371
|
+
const wireModel = "gemini-3.5-flash-low";
|
|
372
|
+
const request = {
|
|
373
|
+
contents: [{ role: "user", parts: [{ text: "ping" }] }],
|
|
374
|
+
generationConfig: { maxOutputTokens: 1, temperature: 0 },
|
|
375
|
+
};
|
|
376
|
+
const requestMetadata = buildAgyAgentRequestMetadata(createAgyRequestSessionContext(""), request, wireModel);
|
|
377
|
+
request.labels = requestMetadata.labels;
|
|
378
|
+
request.sessionId = requestMetadata.sessionId;
|
|
379
|
+
orderAgyRequestPayloadInPlace(request);
|
|
380
|
+
return {
|
|
381
|
+
project: projectId,
|
|
382
|
+
requestId: requestMetadata.requestId,
|
|
383
|
+
request,
|
|
384
|
+
model: wireModel,
|
|
385
|
+
userAgent: "antigravity",
|
|
386
|
+
requestType: "agent",
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
async function interpretAccountAccessProbeResponse(response) {
|
|
390
|
+
if (response.ok) {
|
|
391
|
+
await response.body?.cancel().catch(() => { });
|
|
392
|
+
return { status: "ok", message: "Account verification check passed." };
|
|
393
|
+
}
|
|
394
|
+
let responseBody = "";
|
|
395
|
+
try {
|
|
396
|
+
responseBody = await response.text();
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
responseBody = "";
|
|
400
|
+
}
|
|
401
|
+
const extracted = extractAccountAccessErrorDetails(responseBody);
|
|
402
|
+
if (response.status === 403 && extracted.accountIneligible) {
|
|
403
|
+
return {
|
|
404
|
+
status: "ineligible",
|
|
405
|
+
message: extracted.message ?? "Google marked this account as ineligible for Antigravity.",
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
if (response.status === 403 && extracted.validationRequired) {
|
|
409
|
+
return {
|
|
410
|
+
status: "verification-required",
|
|
411
|
+
message: extracted.message ?? "Google requires additional account verification.",
|
|
412
|
+
verifyUrl: extracted.verifyUrl,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
status: "error",
|
|
417
|
+
message: extracted.message ?? `Request failed (${response.status} ${response.statusText}).`,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
366
420
|
async function verifyAccountAccess(account, client, providerId) {
|
|
367
421
|
const parsed = parseRefreshParts(account.refreshToken);
|
|
368
422
|
if (!parsed.refreshToken) {
|
|
@@ -403,18 +457,7 @@ async function verifyAccountAccess(account, client, providerId) {
|
|
|
403
457
|
"Content-Type": "application/json",
|
|
404
458
|
"Accept-Encoding": "gzip",
|
|
405
459
|
};
|
|
406
|
-
const requestBody =
|
|
407
|
-
project: projectId,
|
|
408
|
-
requestId: `agent/${crypto.randomUUID()}/${Date.now()}/${crypto.randomUUID()}/2`,
|
|
409
|
-
request: {
|
|
410
|
-
model: "gemini-3-flash",
|
|
411
|
-
contents: [{ role: "user", parts: [{ text: "ping" }] }],
|
|
412
|
-
generationConfig: { maxOutputTokens: 1, temperature: 0 },
|
|
413
|
-
},
|
|
414
|
-
model: "gemini-3-flash",
|
|
415
|
-
userAgent: "antigravity",
|
|
416
|
-
requestType: "agent",
|
|
417
|
-
};
|
|
460
|
+
const requestBody = buildAccountAccessProbeRequest(projectId);
|
|
418
461
|
const controller = new AbortController();
|
|
419
462
|
const timeoutId = setTimeout(() => controller.abort(), 20000);
|
|
420
463
|
let response;
|
|
@@ -434,29 +477,7 @@ async function verifyAccountAccess(account, client, providerId) {
|
|
|
434
477
|
finally {
|
|
435
478
|
clearTimeout(timeoutId);
|
|
436
479
|
}
|
|
437
|
-
|
|
438
|
-
try {
|
|
439
|
-
responseBody = await response.text();
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
responseBody = "";
|
|
443
|
-
}
|
|
444
|
-
if (response.ok) {
|
|
445
|
-
return { status: "ok", message: "Account verification check passed." };
|
|
446
|
-
}
|
|
447
|
-
const extracted = extractVerificationErrorDetails(responseBody);
|
|
448
|
-
if (response.status === 403 && extracted.validationRequired) {
|
|
449
|
-
return {
|
|
450
|
-
status: "blocked",
|
|
451
|
-
message: extracted.message ?? "Google requires additional account verification.",
|
|
452
|
-
verifyUrl: extracted.verifyUrl,
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
const fallbackMessage = extracted.message ?? `Request failed (${response.status} ${response.statusText}).`;
|
|
456
|
-
return {
|
|
457
|
-
status: "error",
|
|
458
|
-
message: fallbackMessage,
|
|
459
|
-
};
|
|
480
|
+
return interpretAccountAccessProbeResponse(response);
|
|
460
481
|
}
|
|
461
482
|
async function promptAccountIndexForVerification(accounts) {
|
|
462
483
|
const { createInterface } = await import("node:readline/promises");
|
|
@@ -499,12 +520,22 @@ async function promptOpenVerificationUrl() {
|
|
|
499
520
|
function markStoredAccountVerificationRequired(account, reason, verifyUrl) {
|
|
500
521
|
let changed = false;
|
|
501
522
|
const wasVerificationRequired = account.verificationRequired === true;
|
|
523
|
+
const timestamp = Date.now();
|
|
502
524
|
if (!wasVerificationRequired) {
|
|
503
525
|
account.verificationRequired = true;
|
|
504
526
|
changed = true;
|
|
505
527
|
}
|
|
506
528
|
if (!wasVerificationRequired || account.verificationRequiredAt === undefined) {
|
|
507
|
-
account.verificationRequiredAt =
|
|
529
|
+
account.verificationRequiredAt = timestamp;
|
|
530
|
+
changed = true;
|
|
531
|
+
}
|
|
532
|
+
if (account.accountIneligible === true ||
|
|
533
|
+
account.accountIneligibleAt !== undefined ||
|
|
534
|
+
account.accountIneligibleReason !== undefined) {
|
|
535
|
+
account.accountIneligible = false;
|
|
536
|
+
account.accountIneligibleAt = undefined;
|
|
537
|
+
account.accountIneligibleReason = undefined;
|
|
538
|
+
account.eligibilityStateUpdatedAt = timestamp;
|
|
508
539
|
changed = true;
|
|
509
540
|
}
|
|
510
541
|
const normalizedReason = reason.trim();
|
|
@@ -523,8 +554,31 @@ function markStoredAccountVerificationRequired(account, reason, verifyUrl) {
|
|
|
523
554
|
}
|
|
524
555
|
return changed;
|
|
525
556
|
}
|
|
526
|
-
function
|
|
557
|
+
function markStoredAccountIneligible(account, reason) {
|
|
558
|
+
const timestamp = Date.now();
|
|
559
|
+
const normalizedReason = reason.trim() || "Google marked this account as ineligible.";
|
|
560
|
+
const changed = account.accountIneligible !== true ||
|
|
561
|
+
account.accountIneligibleReason !== normalizedReason ||
|
|
562
|
+
account.verificationRequired === true ||
|
|
563
|
+
account.verificationRequiredAt !== undefined ||
|
|
564
|
+
account.verificationRequiredReason !== undefined ||
|
|
565
|
+
account.verificationUrl !== undefined ||
|
|
566
|
+
account.enabled !== false;
|
|
567
|
+
account.accountIneligible = true;
|
|
568
|
+
account.accountIneligibleAt = timestamp;
|
|
569
|
+
account.accountIneligibleReason = normalizedReason;
|
|
570
|
+
account.eligibilityStateUpdatedAt = timestamp;
|
|
571
|
+
account.verificationRequired = false;
|
|
572
|
+
account.verificationRequiredAt = undefined;
|
|
573
|
+
account.verificationRequiredReason = undefined;
|
|
574
|
+
account.verificationUrl = undefined;
|
|
575
|
+
account.enabled = false;
|
|
576
|
+
return changed;
|
|
577
|
+
}
|
|
578
|
+
function clearStoredAccountAccessBlocks(account, enableIfBlocked = false) {
|
|
527
579
|
const wasVerificationRequired = account.verificationRequired === true;
|
|
580
|
+
const wasIneligible = account.accountIneligible === true;
|
|
581
|
+
const wasAccessBlocked = wasVerificationRequired || wasIneligible;
|
|
528
582
|
let changed = false;
|
|
529
583
|
if (account.verificationRequired !== false) {
|
|
530
584
|
account.verificationRequired = false;
|
|
@@ -542,11 +596,27 @@ function clearStoredAccountVerificationRequired(account, enableIfRequired = fals
|
|
|
542
596
|
account.verificationUrl = undefined;
|
|
543
597
|
changed = true;
|
|
544
598
|
}
|
|
545
|
-
if (
|
|
599
|
+
if (account.accountIneligible !== false) {
|
|
600
|
+
account.accountIneligible = false;
|
|
601
|
+
changed = true;
|
|
602
|
+
}
|
|
603
|
+
if (account.accountIneligibleAt !== undefined) {
|
|
604
|
+
account.accountIneligibleAt = undefined;
|
|
605
|
+
changed = true;
|
|
606
|
+
}
|
|
607
|
+
if (account.accountIneligibleReason !== undefined) {
|
|
608
|
+
account.accountIneligibleReason = undefined;
|
|
609
|
+
changed = true;
|
|
610
|
+
}
|
|
611
|
+
if (wasIneligible || account.eligibilityStateUpdatedAt !== undefined) {
|
|
612
|
+
account.eligibilityStateUpdatedAt = Date.now();
|
|
613
|
+
changed = true;
|
|
614
|
+
}
|
|
615
|
+
if (enableIfBlocked && wasAccessBlocked && account.enabled === false) {
|
|
546
616
|
account.enabled = true;
|
|
547
617
|
changed = true;
|
|
548
618
|
}
|
|
549
|
-
return { changed,
|
|
619
|
+
return { changed, wasAccessBlocked };
|
|
550
620
|
}
|
|
551
621
|
async function promptOAuthCallbackValue(message) {
|
|
552
622
|
const { createInterface } = await import("node:readline/promises");
|
|
@@ -1044,6 +1114,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1044
1114
|
// Load configuration from files and environment variables
|
|
1045
1115
|
const config = loadConfig(directory);
|
|
1046
1116
|
initRuntimeConfig(config);
|
|
1117
|
+
const agySessionRegistry = new AgySessionRegistry(directory);
|
|
1047
1118
|
// Cached getAuth function for tool access
|
|
1048
1119
|
let cachedGetAuth = null;
|
|
1049
1120
|
// Initialize debug with config
|
|
@@ -1087,31 +1158,36 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1087
1158
|
const eventHandler = async (input) => {
|
|
1088
1159
|
// Forward to update checker
|
|
1089
1160
|
await updateChecker.event(input);
|
|
1090
|
-
// Track if this is a child session (subagent, background task)
|
|
1091
|
-
// This is used to filter toasts based on toast_scope config
|
|
1092
1161
|
if (input.event.type === "session.created") {
|
|
1093
|
-
// Log previous session's quota summary before resetting for new session
|
|
1094
|
-
const prevSummary = activeAccountManager?.getSessionSummary();
|
|
1095
|
-
if (prevSummary && (prevSummary.totalClaude > 0 || prevSummary.totalGemini > 0)) {
|
|
1096
|
-
log.debug("prev-session-quota-summary", {
|
|
1097
|
-
durationMinutes: prevSummary.durationMinutes,
|
|
1098
|
-
totalClaude: prevSummary.totalClaude,
|
|
1099
|
-
totalGemini: prevSummary.totalGemini,
|
|
1100
|
-
requestsPerHour: prevSummary.requestsPerHour,
|
|
1101
|
-
accountsUsed: prevSummary.accountsUsed,
|
|
1102
|
-
});
|
|
1103
|
-
}
|
|
1104
1162
|
const props = input.event.properties;
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1163
|
+
const sessionId = props?.info?.id;
|
|
1164
|
+
const parentSessionId = props?.info?.parentID ?? null;
|
|
1165
|
+
if (sessionId) {
|
|
1166
|
+
agySessionRegistry.register(sessionId, parentSessionId);
|
|
1167
|
+
}
|
|
1168
|
+
if (parentSessionId) {
|
|
1169
|
+
log.debug("child-session-detected", { sessionId, parentID: parentSessionId });
|
|
1109
1170
|
}
|
|
1110
1171
|
else {
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1172
|
+
const prevSummary = activeAccountManager?.getSessionSummary();
|
|
1173
|
+
if (prevSummary && (prevSummary.totalClaude > 0 || prevSummary.totalGemini > 0)) {
|
|
1174
|
+
log.debug("prev-session-quota-summary", {
|
|
1175
|
+
durationMinutes: prevSummary.durationMinutes,
|
|
1176
|
+
totalClaude: prevSummary.totalClaude,
|
|
1177
|
+
totalGemini: prevSummary.totalGemini,
|
|
1178
|
+
requestsPerHour: prevSummary.requestsPerHour,
|
|
1179
|
+
accountsUsed: prevSummary.accountsUsed,
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
log.debug("root-session-detected", { sessionId });
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
if (input.event.type === "session.deleted") {
|
|
1186
|
+
const props = input.event.properties;
|
|
1187
|
+
const sessionId = props?.sessionID ?? props?.info?.id;
|
|
1188
|
+
if (sessionId) {
|
|
1189
|
+
agySessionRegistry.delete(sessionId);
|
|
1190
|
+
activeAccountManager?.deleteSessionState(sessionId);
|
|
1115
1191
|
}
|
|
1116
1192
|
}
|
|
1117
1193
|
// Handle session recovery
|
|
@@ -1140,8 +1216,13 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1140
1216
|
}).catch(() => { });
|
|
1141
1217
|
// Show success toast (respects toast_scope for child sessions)
|
|
1142
1218
|
const successToast = getRecoverySuccessToast();
|
|
1143
|
-
|
|
1144
|
-
|
|
1219
|
+
const isChildRecovery = agySessionRegistry.getParentSessionId(sessionID) !== null;
|
|
1220
|
+
log.debug("recovery-toast", {
|
|
1221
|
+
...successToast,
|
|
1222
|
+
isChildSession: isChildRecovery,
|
|
1223
|
+
toastScope: config.toast_scope,
|
|
1224
|
+
});
|
|
1225
|
+
if (!(config.toast_scope === "root_only" && isChildRecovery)) {
|
|
1145
1226
|
await client.tui.showToast({
|
|
1146
1227
|
body: {
|
|
1147
1228
|
title: successToast.title,
|
|
@@ -1341,6 +1422,22 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1341
1422
|
input = String(input.href ?? input);
|
|
1342
1423
|
}
|
|
1343
1424
|
}
|
|
1425
|
+
const localImageTitle = getImageModelLocalTitle(input, init);
|
|
1426
|
+
if (localImageTitle !== undefined) {
|
|
1427
|
+
return createSyntheticTextResponse(localImageTitle, {
|
|
1428
|
+
"X-Antigravity-Response-Type": "local_title",
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
const requestSessionIdentity = extractOpenCodeSessionIdentity(init?.headers);
|
|
1432
|
+
const agyRequestScope = agySessionRegistry.beginRequest(requestSessionIdentity);
|
|
1433
|
+
const agyRequestSession = agyRequestScope.session;
|
|
1434
|
+
const accountSessionIdentity = requestSessionIdentity.sessionId
|
|
1435
|
+
? {
|
|
1436
|
+
id: requestSessionIdentity.sessionId,
|
|
1437
|
+
parentId: requestSessionIdentity.parentSessionId,
|
|
1438
|
+
}
|
|
1439
|
+
: undefined;
|
|
1440
|
+
const isChildRequest = requestSessionIdentity.parentSessionId !== null;
|
|
1344
1441
|
if (accountManager.getAccountCount() === 0) {
|
|
1345
1442
|
return createSyntheticErrorResponse("No Antigravity accounts configured. Run `opencode auth login`.", "unknown");
|
|
1346
1443
|
}
|
|
@@ -1354,6 +1451,11 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1354
1451
|
debugLines.push(line);
|
|
1355
1452
|
};
|
|
1356
1453
|
pushDebug(`request=${urlString}`);
|
|
1454
|
+
if (requestSessionIdentity.sessionId) {
|
|
1455
|
+
pushDebug(`[Session] id=${requestSessionIdentity.sessionId}` +
|
|
1456
|
+
` parent=${requestSessionIdentity.parentSessionId ?? "none"}` +
|
|
1457
|
+
` child=${isChildRequest}`);
|
|
1458
|
+
}
|
|
1357
1459
|
const cachedStats = getLastCacheStats();
|
|
1358
1460
|
if (cachedStats) {
|
|
1359
1461
|
const label = cachedStats.hitRate > 0 ? "HIT" : "MISS";
|
|
@@ -1375,14 +1477,18 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1375
1477
|
// Helper to show toast without blocking on abort (respects quiet_mode and toast_scope)
|
|
1376
1478
|
const showToast = async (message, variant) => {
|
|
1377
1479
|
// Always log to debug regardless of toast filtering
|
|
1378
|
-
log.debug("toast", { message, variant, isChildSession, toastScope });
|
|
1480
|
+
log.debug("toast", { message, variant, isChildSession: isChildRequest, toastScope });
|
|
1379
1481
|
if (quietMode)
|
|
1380
1482
|
return;
|
|
1381
1483
|
if (abortSignal?.aborted)
|
|
1382
1484
|
return;
|
|
1383
1485
|
// Filter toasts for child sessions when toast_scope is "root_only"
|
|
1384
|
-
if (toastScope === "root_only" &&
|
|
1385
|
-
log.debug("toast-suppressed-child-session", {
|
|
1486
|
+
if (toastScope === "root_only" && isChildRequest) {
|
|
1487
|
+
log.debug("toast-suppressed-child-session", {
|
|
1488
|
+
message,
|
|
1489
|
+
variant,
|
|
1490
|
+
parentID: requestSessionIdentity.parentSessionId,
|
|
1491
|
+
});
|
|
1386
1492
|
return;
|
|
1387
1493
|
}
|
|
1388
1494
|
if (variant === "warning" && message.toLowerCase().includes("rate")) {
|
|
@@ -1419,10 +1525,10 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1419
1525
|
return createSyntheticErrorResponse("No Antigravity accounts available. Run `opencode auth login`.", model ?? "unknown");
|
|
1420
1526
|
}
|
|
1421
1527
|
const softQuotaCacheTtlMs = computeSoftQuotaCacheTtlMs(config.soft_quota_cache_ttl_minutes, config.quota_refresh_interval_minutes);
|
|
1422
|
-
let account = accountManager.getCurrentOrNextForFamily(family, model, config.account_selection_strategy, preferredHeaderStyle, config.pid_offset_enabled, config.soft_quota_threshold_percent, softQuotaCacheTtlMs);
|
|
1528
|
+
let account = accountManager.getCurrentOrNextForFamily(family, model, config.account_selection_strategy, preferredHeaderStyle, config.pid_offset_enabled, config.soft_quota_threshold_percent, softQuotaCacheTtlMs, accountSessionIdentity);
|
|
1423
1529
|
if (!account && allowQuotaFallback) {
|
|
1424
1530
|
const alternateHeaderStyle = preferredHeaderStyle === "antigravity" ? "gemini-cli" : "antigravity";
|
|
1425
|
-
account = accountManager.getCurrentOrNextForFamily(family, model, config.account_selection_strategy, alternateHeaderStyle, config.pid_offset_enabled, config.soft_quota_threshold_percent, softQuotaCacheTtlMs);
|
|
1531
|
+
account = accountManager.getCurrentOrNextForFamily(family, model, config.account_selection_strategy, alternateHeaderStyle, config.pid_offset_enabled, config.soft_quota_threshold_percent, softQuotaCacheTtlMs, accountSessionIdentity);
|
|
1426
1532
|
if (account) {
|
|
1427
1533
|
pushDebug(`selected-by-fallback idx=${account.index} preferred=${preferredHeaderStyle} alternate=${alternateHeaderStyle}`);
|
|
1428
1534
|
}
|
|
@@ -1488,7 +1594,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1488
1594
|
pushDebug(`account-switch: ${previousAccountIndex} → ${account.index}, warmup=${needsCacheWarmup}`);
|
|
1489
1595
|
}
|
|
1490
1596
|
previousAccountIndex = account.index;
|
|
1491
|
-
accountManager.recordSessionUsage(account.index);
|
|
1597
|
+
accountManager.recordSessionUsage(account.index, accountSessionIdentity);
|
|
1492
1598
|
if (isDebugEnabled()) {
|
|
1493
1599
|
logAccountContext("Selected", {
|
|
1494
1600
|
index: account.index,
|
|
@@ -1796,6 +1902,8 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
1796
1902
|
claudeToolHardening: config.claude_tool_hardening,
|
|
1797
1903
|
claudePromptAutoCaching: config.claude_prompt_auto_caching,
|
|
1798
1904
|
fingerprint: account.fingerprint,
|
|
1905
|
+
agySession: agyRequestSession,
|
|
1906
|
+
agyRequestTimestamp: agyRequestScope.timestamp,
|
|
1799
1907
|
});
|
|
1800
1908
|
const originalUrl = toUrlString(input);
|
|
1801
1909
|
const resolvedUrl = toUrlString(prepared.request);
|
|
@@ -2041,7 +2149,23 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2041
2149
|
resetAccountFailureState(account.index);
|
|
2042
2150
|
if (response.status === 403) {
|
|
2043
2151
|
const errorBodyText = await response.clone().text().catch(() => "");
|
|
2044
|
-
const extracted =
|
|
2152
|
+
const extracted = extractAccountAccessErrorDetails(errorBodyText);
|
|
2153
|
+
if (extracted.accountIneligible) {
|
|
2154
|
+
const ineligibleReason = extracted.message ??
|
|
2155
|
+
"Google marked this account as ineligible for Antigravity.";
|
|
2156
|
+
accountManager.markAccountIneligible(account.index, ineligibleReason);
|
|
2157
|
+
const label = account.email || `Account ${account.index + 1}`;
|
|
2158
|
+
if (accountManager.shouldShowAccountToast(account.index, 60000)) {
|
|
2159
|
+
await showToast(`${label} is not eligible for Antigravity and has been disabled. ` +
|
|
2160
|
+
"Recheck it from opencode auth login > Verify accounts.", "warning");
|
|
2161
|
+
accountManager.markToastShown(account.index);
|
|
2162
|
+
}
|
|
2163
|
+
pushDebug(`account-ineligible: disabled account ${account.index}`);
|
|
2164
|
+
getHealthTracker().recordFailure(account.index);
|
|
2165
|
+
lastFailure = createFailureContext(response);
|
|
2166
|
+
shouldSwitchAccount = true;
|
|
2167
|
+
break;
|
|
2168
|
+
}
|
|
2045
2169
|
if (extracted.validationRequired) {
|
|
2046
2170
|
const verificationReason = extracted.message ?? "Google requires account verification.";
|
|
2047
2171
|
const cooldownMs = 10 * 60 * 1000;
|
|
@@ -2077,13 +2201,14 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2077
2201
|
// Proactive rotation: if current account quota is low, pre-switch
|
|
2078
2202
|
// to a warm-cache account so the NEXT request avoids a cold cache miss
|
|
2079
2203
|
const proactiveThreshold = config.proactive_rotation_threshold_percent ?? 20;
|
|
2080
|
-
if (proactiveThreshold > 0 && accountManager.shouldProactivelyRotate(family, model, proactiveThreshold, softQuotaCacheTtlMs)) {
|
|
2081
|
-
const rotated = accountManager.proactivelyRotateForFamily(family, model, headerStyle, config.soft_quota_threshold_percent, softQuotaCacheTtlMs);
|
|
2204
|
+
if (proactiveThreshold > 0 && accountManager.shouldProactivelyRotate(family, model, proactiveThreshold, softQuotaCacheTtlMs, accountSessionIdentity)) {
|
|
2205
|
+
const rotated = accountManager.proactivelyRotateForFamily(family, model, headerStyle, config.soft_quota_threshold_percent, softQuotaCacheTtlMs, accountSessionIdentity);
|
|
2082
2206
|
if (rotated) {
|
|
2083
2207
|
const remaining = account.cachedQuota?.[resolveQuotaGroup(family, model)]?.remainingFraction;
|
|
2084
2208
|
const remainingPct = remaining != null ? `${(remaining * 100).toFixed(1)}%` : "?";
|
|
2085
2209
|
pushDebug(`[ProactiveRotation] account ${account.index} quota ${remainingPct} < ${proactiveThreshold}%, pre-switched to account ${rotated.index} for next request`);
|
|
2086
|
-
pushDebug(`[ProactiveRotation] ${account.index} → ${rotated.index}
|
|
2210
|
+
pushDebug(`[ProactiveRotation] ${account.index} → ${rotated.index}` +
|
|
2211
|
+
` (warm=${accountManager.wasUsedInSession(rotated.index, accountSessionIdentity)})`);
|
|
2087
2212
|
}
|
|
2088
2213
|
}
|
|
2089
2214
|
}
|
|
@@ -2157,7 +2282,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2157
2282
|
// Post-request quota state: show cached remaining quota for this account
|
|
2158
2283
|
const cachedQuota = account.cachedQuota;
|
|
2159
2284
|
if (cachedQuota) {
|
|
2160
|
-
const quotaFamily = family
|
|
2285
|
+
const quotaFamily = resolveQuotaGroup(family, model);
|
|
2161
2286
|
const groupQuota = cachedQuota[quotaFamily];
|
|
2162
2287
|
if (groupQuota?.remainingFraction != null) {
|
|
2163
2288
|
const pct = Math.round(groupQuota.remainingFraction * 100);
|
|
@@ -2273,26 +2398,29 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2273
2398
|
while (true) {
|
|
2274
2399
|
const now = Date.now();
|
|
2275
2400
|
const existingAccounts = existingStorage.accounts.map((acc, idx) => {
|
|
2276
|
-
let status =
|
|
2277
|
-
if (acc.
|
|
2278
|
-
status =
|
|
2401
|
+
let status = "unknown";
|
|
2402
|
+
if (acc.accountIneligible) {
|
|
2403
|
+
status = "ineligible";
|
|
2404
|
+
}
|
|
2405
|
+
else if (acc.verificationRequired) {
|
|
2406
|
+
status = "verification-required";
|
|
2279
2407
|
}
|
|
2280
2408
|
else {
|
|
2281
2409
|
const rateLimits = acc.rateLimitResetTimes;
|
|
2282
2410
|
if (rateLimits) {
|
|
2283
|
-
const isRateLimited = Object.values(rateLimits).some((resetTime) => typeof resetTime ===
|
|
2411
|
+
const isRateLimited = Object.values(rateLimits).some((resetTime) => typeof resetTime === "number" && resetTime > now);
|
|
2284
2412
|
if (isRateLimited) {
|
|
2285
|
-
status =
|
|
2413
|
+
status = "rate-limited";
|
|
2286
2414
|
}
|
|
2287
2415
|
else {
|
|
2288
|
-
status =
|
|
2416
|
+
status = "active";
|
|
2289
2417
|
}
|
|
2290
2418
|
}
|
|
2291
2419
|
else {
|
|
2292
|
-
status =
|
|
2420
|
+
status = "active";
|
|
2293
2421
|
}
|
|
2294
2422
|
if (acc.coolingDownUntil && acc.coolingDownUntil > now) {
|
|
2295
|
-
status =
|
|
2423
|
+
status = "rate-limited";
|
|
2296
2424
|
}
|
|
2297
2425
|
}
|
|
2298
2426
|
const cooldownMs = (acc.coolingDownUntil && acc.coolingDownUntil > now)
|
|
@@ -2306,11 +2434,11 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2306
2434
|
|| (now - acc.cachedQuotaUpdatedAt) > DISPLAY_QUOTA_MAX_AGE_MS;
|
|
2307
2435
|
const displayQuota = quotaIsStale ? undefined : acc.cachedQuota;
|
|
2308
2436
|
const displayPerModelQuota = quotaIsStale ? undefined : acc.cachedPerModelQuota;
|
|
2309
|
-
if (status ===
|
|
2437
|
+
if (status === "active" && displayQuota) {
|
|
2310
2438
|
const groups = Object.values(displayQuota);
|
|
2311
2439
|
const allExhausted = groups.length > 0 && groups.every((group) => typeof group.remainingFraction === "number" && group.remainingFraction <= 0);
|
|
2312
2440
|
if (allExhausted) {
|
|
2313
|
-
status =
|
|
2441
|
+
status = "rate-limited";
|
|
2314
2442
|
}
|
|
2315
2443
|
}
|
|
2316
2444
|
return {
|
|
@@ -2430,6 +2558,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2430
2558
|
{ name: "Claude", data: groups.claude },
|
|
2431
2559
|
{ name: "Gemini 3 Pro", data: groups["gemini-pro"] },
|
|
2432
2560
|
{ name: "Gemini 3 Flash", data: groups["gemini-flash"] },
|
|
2561
|
+
{ name: "GPT-OSS", data: groups["gpt-oss"] },
|
|
2433
2562
|
].filter(g => g.data);
|
|
2434
2563
|
groupEntries.forEach((g, idx) => {
|
|
2435
2564
|
const isLast = idx === groupEntries.length - 1;
|
|
@@ -2487,10 +2616,16 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2487
2616
|
if (menuResult.toggleAccountIndex !== undefined) {
|
|
2488
2617
|
const acc = existingStorage.accounts[menuResult.toggleAccountIndex];
|
|
2489
2618
|
if (acc) {
|
|
2490
|
-
|
|
2619
|
+
const shouldEnable = acc.enabled === false;
|
|
2620
|
+
if (shouldEnable && acc.accountIneligible) {
|
|
2621
|
+
console.log(`\n${acc.email || `Account ${menuResult.toggleAccountIndex + 1}`} remains disabled. ` +
|
|
2622
|
+
"Use Verify accounts to recheck eligibility.\n");
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
acc.enabled = shouldEnable;
|
|
2491
2626
|
await saveAccounts(existingStorage);
|
|
2492
2627
|
activeAccountManager?.setAccountEnabled(menuResult.toggleAccountIndex, acc.enabled);
|
|
2493
|
-
console.log(`\nAccount ${acc.email || menuResult.toggleAccountIndex + 1} ${acc.enabled ?
|
|
2628
|
+
console.log(`\nAccount ${acc.email || menuResult.toggleAccountIndex + 1} ${acc.enabled ? "enabled" : "disabled"}.\n`);
|
|
2494
2629
|
}
|
|
2495
2630
|
}
|
|
2496
2631
|
continue;
|
|
@@ -2505,6 +2640,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2505
2640
|
console.log(`\nChecking verification status for ${existingStorage.accounts.length} account(s)...\n`);
|
|
2506
2641
|
let okCount = 0;
|
|
2507
2642
|
let blockedCount = 0;
|
|
2643
|
+
let ineligibleCount = 0;
|
|
2508
2644
|
let errorCount = 0;
|
|
2509
2645
|
let storageUpdated = false;
|
|
2510
2646
|
const blockedResults = [];
|
|
@@ -2516,16 +2652,16 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2516
2652
|
process.stdout.write(`- [${i + 1}/${existingStorage.accounts.length}] ${label} ... `);
|
|
2517
2653
|
const verification = await verifyAccountAccess(account, client, providerId);
|
|
2518
2654
|
if (verification.status === "ok") {
|
|
2519
|
-
const { changed,
|
|
2655
|
+
const { changed, wasAccessBlocked } = clearStoredAccountAccessBlocks(account, true);
|
|
2520
2656
|
if (changed) {
|
|
2521
2657
|
storageUpdated = true;
|
|
2522
2658
|
}
|
|
2523
|
-
activeAccountManager?.
|
|
2659
|
+
activeAccountManager?.clearAccountAccessBlocks(i, wasAccessBlocked);
|
|
2524
2660
|
okCount += 1;
|
|
2525
2661
|
console.log("ok");
|
|
2526
2662
|
continue;
|
|
2527
2663
|
}
|
|
2528
|
-
if (verification.status === "
|
|
2664
|
+
if (verification.status === "verification-required") {
|
|
2529
2665
|
const changed = markStoredAccountVerificationRequired(account, verification.message, verification.verifyUrl);
|
|
2530
2666
|
if (changed) {
|
|
2531
2667
|
storageUpdated = true;
|
|
@@ -2541,13 +2677,24 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2541
2677
|
});
|
|
2542
2678
|
continue;
|
|
2543
2679
|
}
|
|
2680
|
+
if (verification.status === "ineligible") {
|
|
2681
|
+
const changed = markStoredAccountIneligible(account, verification.message);
|
|
2682
|
+
if (changed) {
|
|
2683
|
+
storageUpdated = true;
|
|
2684
|
+
}
|
|
2685
|
+
activeAccountManager?.markAccountIneligible(i, verification.message);
|
|
2686
|
+
ineligibleCount += 1;
|
|
2687
|
+
console.log("ineligible");
|
|
2688
|
+
continue;
|
|
2689
|
+
}
|
|
2544
2690
|
errorCount += 1;
|
|
2545
2691
|
console.log(`error (${verification.message})`);
|
|
2546
2692
|
}
|
|
2547
2693
|
if (storageUpdated) {
|
|
2548
2694
|
await saveAccounts(existingStorage);
|
|
2549
2695
|
}
|
|
2550
|
-
console.log(`\nVerification summary: ${okCount} ready, ${blockedCount} need verification,
|
|
2696
|
+
console.log(`\nVerification summary: ${okCount} ready, ${blockedCount} need verification, ` +
|
|
2697
|
+
`${ineligibleCount} ineligible, ${errorCount} errors.`);
|
|
2551
2698
|
if (blockedResults.length > 0) {
|
|
2552
2699
|
console.log("\nAccounts needing verification:");
|
|
2553
2700
|
for (const result of blockedResults) {
|
|
@@ -2584,12 +2731,12 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2584
2731
|
console.log(`\nChecking verification status for ${label}...\n`);
|
|
2585
2732
|
const verification = await verifyAccountAccess(account, client, providerId);
|
|
2586
2733
|
if (verification.status === "ok") {
|
|
2587
|
-
const { changed,
|
|
2734
|
+
const { changed, wasAccessBlocked } = clearStoredAccountAccessBlocks(account, true);
|
|
2588
2735
|
if (changed) {
|
|
2589
2736
|
await saveAccounts(existingStorage);
|
|
2590
2737
|
}
|
|
2591
|
-
activeAccountManager?.
|
|
2592
|
-
if (
|
|
2738
|
+
activeAccountManager?.clearAccountAccessBlocks(verifyAccountIndex, wasAccessBlocked);
|
|
2739
|
+
if (wasAccessBlocked) {
|
|
2593
2740
|
console.log(`✓ ${label} is ready for requests and has been re-enabled.\n`);
|
|
2594
2741
|
}
|
|
2595
2742
|
else {
|
|
@@ -2597,7 +2744,7 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2597
2744
|
}
|
|
2598
2745
|
continue;
|
|
2599
2746
|
}
|
|
2600
|
-
if (verification.status === "
|
|
2747
|
+
if (verification.status === "verification-required") {
|
|
2601
2748
|
const changed = markStoredAccountVerificationRequired(account, verification.message, verification.verifyUrl);
|
|
2602
2749
|
if (changed) {
|
|
2603
2750
|
await saveAccounts(existingStorage);
|
|
@@ -2626,6 +2773,16 @@ export const createAntigravityPlugin = (providerId) => async ({ client, director
|
|
|
2626
2773
|
}
|
|
2627
2774
|
continue;
|
|
2628
2775
|
}
|
|
2776
|
+
if (verification.status === "ineligible") {
|
|
2777
|
+
const changed = markStoredAccountIneligible(account, verification.message);
|
|
2778
|
+
if (changed) {
|
|
2779
|
+
await saveAccounts(existingStorage);
|
|
2780
|
+
}
|
|
2781
|
+
activeAccountManager?.markAccountIneligible(verifyAccountIndex, verification.message);
|
|
2782
|
+
console.log(`⚠ ${label} is not eligible for Antigravity and has been disabled.`);
|
|
2783
|
+
console.log(`${verification.message}\n`);
|
|
2784
|
+
continue;
|
|
2785
|
+
}
|
|
2629
2786
|
console.log(`✗ ${label}: ${verification.message}\n`);
|
|
2630
2787
|
continue;
|
|
2631
2788
|
}
|
|
@@ -3143,6 +3300,9 @@ function isExplicitQuotaFromUrl(urlString) {
|
|
|
3143
3300
|
return explicitQuota ?? false;
|
|
3144
3301
|
}
|
|
3145
3302
|
export const __testExports = {
|
|
3303
|
+
buildAccountAccessProbeRequest,
|
|
3304
|
+
extractAccountAccessErrorDetails,
|
|
3305
|
+
interpretAccountAccessProbeResponse,
|
|
3146
3306
|
getHeaderStyleFromUrl,
|
|
3147
3307
|
isCapacityRetryBudgetExhausted,
|
|
3148
3308
|
resolveHeaderRoutingDecision,
|