@anthonyhaussman/opencode-agy-auth 1.0.16 → 1.0.17-alpha.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/dist/index.js +1872 -1860
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -270,1485 +270,1197 @@ function accessTokenExpired(auth) {
|
|
|
270
270
|
return auth.expires <= Date.now() + ACCESS_TOKEN_EXPIRY_BUFFER_MS;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
// src/sdk/
|
|
274
|
-
var
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
273
|
+
// src/sdk/retry/quota.ts
|
|
274
|
+
var CLOUDCODE_DOMAINS = /* @__PURE__ */ new Set([
|
|
275
|
+
"cloudcode-pa.googleapis.com",
|
|
276
|
+
"staging-cloudcode-pa.googleapis.com",
|
|
277
|
+
"autopush-cloudcode-pa.googleapis.com",
|
|
278
|
+
"cloudaicompanion.googleapis.com",
|
|
279
|
+
"daily-cloudcode-pa.googleapis.com"
|
|
280
|
+
]);
|
|
281
|
+
async function classifyQuotaResponse(response) {
|
|
282
|
+
const payload = await parseErrorBody(response);
|
|
283
|
+
if (!payload) {
|
|
284
|
+
return null;
|
|
279
285
|
}
|
|
280
|
-
|
|
281
|
-
|
|
286
|
+
const details = Array.isArray(payload.details) ? payload.details : [];
|
|
287
|
+
const retryInfo = details.find(
|
|
288
|
+
(detail) => isObject(detail) && detail["@type"] === "type.googleapis.com/google.rpc.RetryInfo"
|
|
289
|
+
);
|
|
290
|
+
const retryDelayMs = (retryInfo?.retryDelay ? parseRetryDelayValue(retryInfo.retryDelay) : null) ?? parseRetryDelayFromMessage(payload.message ?? "") ?? void 0;
|
|
291
|
+
const errorInfo = details.find(
|
|
292
|
+
(detail) => isObject(detail) && detail["@type"] === "type.googleapis.com/google.rpc.ErrorInfo"
|
|
293
|
+
);
|
|
294
|
+
if (errorInfo?.domain && !CLOUDCODE_DOMAINS.has(errorInfo.domain)) {
|
|
295
|
+
return null;
|
|
282
296
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return true;
|
|
297
|
+
if (errorInfo?.reason === "QUOTA_EXHAUSTED") {
|
|
298
|
+
return { terminal: true, retryDelayMs, reason: errorInfo.reason };
|
|
286
299
|
}
|
|
287
|
-
if (
|
|
288
|
-
return
|
|
300
|
+
if (errorInfo?.reason === "RATE_LIMIT_EXCEEDED") {
|
|
301
|
+
return { terminal: false, retryDelayMs: retryDelayMs ?? 1e4, reason: errorInfo.reason };
|
|
289
302
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
303
|
+
if (errorInfo?.reason === "MODEL_CAPACITY_EXHAUSTED") {
|
|
304
|
+
return {
|
|
305
|
+
terminal: retryDelayMs === void 0,
|
|
306
|
+
retryDelayMs,
|
|
307
|
+
reason: errorInfo.reason
|
|
308
|
+
};
|
|
293
309
|
}
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
310
|
+
const quotaFailure = details.find(
|
|
311
|
+
(detail) => isObject(detail) && detail["@type"] === "type.googleapis.com/google.rpc.QuotaFailure"
|
|
312
|
+
);
|
|
313
|
+
if (quotaFailure?.violations?.length) {
|
|
314
|
+
const allTexts = quotaFailure.violations.flatMap((violation) => [violation.quotaId ?? "", violation.description ?? ""]).join(" ").toLowerCase();
|
|
315
|
+
if (allTexts.includes("perday") || allTexts.includes("daily") || allTexts.includes("per day")) {
|
|
316
|
+
return { terminal: true, retryDelayMs, reason: errorInfo?.reason };
|
|
299
317
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
function formatHyperlink(url2, text) {
|
|
304
|
-
if (!supportsOsc8Hyperlinks()) {
|
|
305
|
-
if (text) {
|
|
306
|
-
return text === url2 ? text : `${text} (${url2})`;
|
|
318
|
+
if (allTexts.includes("perminute") || allTexts.includes("per minute")) {
|
|
319
|
+
return { terminal: false, retryDelayMs: retryDelayMs ?? 6e4, reason: errorInfo?.reason };
|
|
307
320
|
}
|
|
308
|
-
return
|
|
321
|
+
return { terminal: false, retryDelayMs, reason: errorInfo?.reason };
|
|
309
322
|
}
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// src/plugin/project/types.ts
|
|
315
|
-
var FREE_TIER_ID = "free-tier";
|
|
316
|
-
var LEGACY_TIER_ID = "legacy-tier";
|
|
317
|
-
var CODE_ASSIST_METADATA = {
|
|
318
|
-
ideType: "ANTIGRAVITY"
|
|
319
|
-
};
|
|
320
|
-
var ProjectIdRequiredError = class extends Error {
|
|
321
|
-
constructor() {
|
|
322
|
-
super(
|
|
323
|
-
"Google Gemini/Agy requires a Google Cloud project. Enable the Gemini for Google Cloud API on a project you control, then set `provider.google.options.projectId` in your Opencode config (or set OPENCODE_AGY_PROJECT_ID / GOOGLE_CLOUD_PROJECT)."
|
|
324
|
-
);
|
|
323
|
+
const quotaLimit = errorInfo?.metadata?.quota_limit?.toLowerCase() ?? "";
|
|
324
|
+
if (quotaLimit.includes("perminute") || quotaLimit.includes("per minute")) {
|
|
325
|
+
return { terminal: false, retryDelayMs: retryDelayMs ?? 6e4, reason: errorInfo?.reason };
|
|
325
326
|
}
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
super(`Access denied to ${projectStr}. Ensure the Gemini for Google Cloud API is enabled and you have the correct IAM permissions.${msg}`);
|
|
333
|
-
this.name = "ProjectAccessDeniedError";
|
|
327
|
+
return { terminal: false, retryDelayMs, reason: errorInfo?.reason };
|
|
328
|
+
}
|
|
329
|
+
async function parseRetryDelayFromBody(response) {
|
|
330
|
+
const payload = await parseErrorBody(response);
|
|
331
|
+
if (!payload) {
|
|
332
|
+
return null;
|
|
334
333
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
if (validationLearnMoreUrl) {
|
|
345
|
-
parts.push(`Learn more: ${validationLearnMoreUrl}`);
|
|
334
|
+
const details = Array.isArray(payload.details) ? payload.details : [];
|
|
335
|
+
const retryInfo = details.find(
|
|
336
|
+
(detail) => isObject(detail) && detail["@type"] === "type.googleapis.com/google.rpc.RetryInfo"
|
|
337
|
+
);
|
|
338
|
+
if (retryInfo?.retryDelay) {
|
|
339
|
+
const delayMs = parseRetryDelayValue(retryInfo.retryDelay);
|
|
340
|
+
if (delayMs !== null) {
|
|
341
|
+
return delayMs;
|
|
346
342
|
}
|
|
347
|
-
super(parts.join("\n"));
|
|
348
|
-
this.name = "AccountValidationRequiredError";
|
|
349
|
-
this.validationUrl = validationUrl;
|
|
350
|
-
this.validationLearnMoreUrl = validationLearnMoreUrl;
|
|
351
343
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// src/plugin/project/utils.ts
|
|
355
|
-
function buildMetadata(projectId, includeDuetProject = true) {
|
|
356
|
-
const metadata = {
|
|
357
|
-
...CODE_ASSIST_METADATA
|
|
358
|
-
};
|
|
359
|
-
if (projectId && includeDuetProject) {
|
|
360
|
-
metadata.duetProject = projectId;
|
|
344
|
+
if (typeof payload.message === "string") {
|
|
345
|
+
return parseRetryDelayFromMessage(payload.message);
|
|
361
346
|
}
|
|
362
|
-
return
|
|
347
|
+
return null;
|
|
363
348
|
}
|
|
364
|
-
function
|
|
365
|
-
if (!value) {
|
|
366
|
-
return void 0;
|
|
367
|
-
}
|
|
349
|
+
function parseRetryDelayValue(value) {
|
|
368
350
|
if (typeof value === "string") {
|
|
369
351
|
const trimmed = value.trim();
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (typeof value === "object" && typeof value.id === "string") {
|
|
373
|
-
const trimmed = value.id.trim();
|
|
374
|
-
return trimmed ? trimmed : void 0;
|
|
375
|
-
}
|
|
376
|
-
return void 0;
|
|
377
|
-
}
|
|
378
|
-
function pickOnboardTier(allowedTiers) {
|
|
379
|
-
if (allowedTiers && allowedTiers.length > 0) {
|
|
380
|
-
for (const tier of allowedTiers) {
|
|
381
|
-
if (tier?.isDefault) {
|
|
382
|
-
return tier;
|
|
383
|
-
}
|
|
352
|
+
if (!trimmed) {
|
|
353
|
+
return null;
|
|
384
354
|
}
|
|
385
|
-
|
|
355
|
+
if (trimmed.endsWith("ms")) {
|
|
356
|
+
const milliseconds = Number(trimmed.slice(0, -2));
|
|
357
|
+
return Number.isFinite(milliseconds) && milliseconds > 0 ? Math.round(milliseconds) : null;
|
|
358
|
+
}
|
|
359
|
+
const match = trimmed.match(/^([\d.]+)s$/);
|
|
360
|
+
if (!match?.[1]) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
const seconds2 = Number(match[1]);
|
|
364
|
+
return Number.isFinite(seconds2) && seconds2 > 0 ? Math.round(seconds2 * 1e3) : null;
|
|
386
365
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return void 0;
|
|
366
|
+
const seconds = typeof value.seconds === "number" ? value.seconds : 0;
|
|
367
|
+
const nanos = typeof value.nanos === "number" ? value.nanos : 0;
|
|
368
|
+
if (!Number.isFinite(seconds) || !Number.isFinite(nanos)) {
|
|
369
|
+
return null;
|
|
392
370
|
}
|
|
393
|
-
const
|
|
394
|
-
return
|
|
371
|
+
const totalMs = Math.round(seconds * 1e3 + nanos / 1e6);
|
|
372
|
+
return totalMs > 0 ? totalMs : null;
|
|
395
373
|
}
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
374
|
+
function parseRetryDelayFromMessage(message) {
|
|
375
|
+
const retryMatch = message.match(/Please retry in ([0-9.]+(?:ms|s))/i);
|
|
376
|
+
if (retryMatch?.[1]) {
|
|
377
|
+
return parseRetryDelayValue(retryMatch[1]);
|
|
399
378
|
}
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
return
|
|
403
|
-
});
|
|
404
|
-
if (!validationTier) {
|
|
405
|
-
return;
|
|
379
|
+
const afterMatch = message.match(/after\s+([0-9.]+(?:ms|s))/i);
|
|
380
|
+
if (afterMatch?.[1]) {
|
|
381
|
+
return parseRetryDelayValue(afterMatch[1]);
|
|
406
382
|
}
|
|
407
|
-
|
|
408
|
-
validationTier.reasonMessage?.trim() || "Verify your account to continue.",
|
|
409
|
-
validationTier.validationUrl?.trim(),
|
|
410
|
-
validationTier.validationLearnMoreUrl?.trim()
|
|
411
|
-
);
|
|
383
|
+
return null;
|
|
412
384
|
}
|
|
413
|
-
function
|
|
414
|
-
|
|
415
|
-
|
|
385
|
+
async function parseErrorBody(response) {
|
|
386
|
+
let text = "";
|
|
387
|
+
try {
|
|
388
|
+
text = await response.clone().text();
|
|
389
|
+
} catch {
|
|
390
|
+
return null;
|
|
416
391
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
return false;
|
|
392
|
+
if (!text) {
|
|
393
|
+
return null;
|
|
420
394
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
395
|
+
let parsed;
|
|
396
|
+
try {
|
|
397
|
+
parsed = JSON.parse(text);
|
|
398
|
+
} catch {
|
|
399
|
+
return null;
|
|
424
400
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
return detail.reason === "SECURITY_POLICY_VIOLATED";
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
function wait(ms) {
|
|
433
|
-
return new Promise((resolve) => {
|
|
434
|
-
setTimeout(resolve, ms);
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
function getCacheKey(auth) {
|
|
438
|
-
const refresh = auth.refresh?.trim();
|
|
439
|
-
if (!refresh) {
|
|
440
|
-
return void 0;
|
|
401
|
+
const normalized = normalizeErrorEnvelope(parsed);
|
|
402
|
+
if (!normalized || !isObject(normalized.error)) {
|
|
403
|
+
return null;
|
|
441
404
|
}
|
|
442
|
-
const
|
|
443
|
-
return
|
|
405
|
+
const error45 = normalized.error;
|
|
406
|
+
return {
|
|
407
|
+
message: typeof error45.message === "string" ? error45.message : void 0,
|
|
408
|
+
details: Array.isArray(error45.details) ? error45.details : void 0
|
|
409
|
+
};
|
|
444
410
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
requestBody.cloudaicompanionProject = projectId;
|
|
453
|
-
}
|
|
454
|
-
const url2 = `${AGY_CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`;
|
|
455
|
-
if (process.env.OPENCODE_AGY_VERBOSE_LOGS === "1") {
|
|
456
|
-
console.warn(`[Agy Auth] loadManagedProject calling URL: ${formatHyperlink(url2)} with project: ${projectId || "none"}`);
|
|
457
|
-
}
|
|
458
|
-
const headers = buildCodeAssistHeaders2(accessToken, userAgentModel);
|
|
459
|
-
const response = await agyFetch(url2, {
|
|
460
|
-
method: "POST",
|
|
461
|
-
headers,
|
|
462
|
-
body: JSON.stringify(requestBody)
|
|
463
|
-
});
|
|
464
|
-
if (!response.ok) {
|
|
465
|
-
if (response.status === 403 || response.status === 404) {
|
|
466
|
-
console.warn(`[Agy Auth] loadManagedProject failed with ${response.status} (possible Cloud API mismatch/unauthorized). URL: ${formatHyperlink(url2)}, Project: ${projectId}`);
|
|
467
|
-
const responseText = await readResponseTextIfNeeded(response, true);
|
|
468
|
-
if (responseText && isVpcScError(responseText)) {
|
|
469
|
-
console.warn(`[Agy Auth] loadManagedProject: Detected VPC Service Controls block`);
|
|
470
|
-
}
|
|
471
|
-
throw new ProjectAccessDeniedError(projectId, responseText);
|
|
472
|
-
} else {
|
|
473
|
-
const cleanStatusText = response.statusText.replace(/[\r\n]+/g, " ").trim();
|
|
474
|
-
console.warn(`[Agy Auth] loadManagedProject failed with ${response.status} ${cleanStatusText}`);
|
|
475
|
-
}
|
|
476
|
-
return null;
|
|
477
|
-
}
|
|
478
|
-
const responseJson = await response.json();
|
|
479
|
-
return responseJson;
|
|
480
|
-
} catch (error45) {
|
|
481
|
-
const errStr = error45 instanceof Error ? error45.stack || error45.message : String(error45);
|
|
482
|
-
console.warn(`[Agy Auth] Failed to load code assist project: ${errStr}`);
|
|
483
|
-
return null;
|
|
411
|
+
function isObject(value) {
|
|
412
|
+
return !!value && typeof value === "object";
|
|
413
|
+
}
|
|
414
|
+
function normalizeErrorEnvelope(parsed) {
|
|
415
|
+
if (Array.isArray(parsed)) {
|
|
416
|
+
const first = parsed[0];
|
|
417
|
+
return isObject(first) ? first : null;
|
|
484
418
|
}
|
|
419
|
+
return isObject(parsed) ? parsed : null;
|
|
485
420
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
421
|
+
|
|
422
|
+
// src/sdk/retry/helpers.ts
|
|
423
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
424
|
+
var DEFAULT_INITIAL_DELAY_MS = 5e3;
|
|
425
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
426
|
+
var RETRYABLE_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
427
|
+
"ECONNRESET",
|
|
428
|
+
"ETIMEDOUT",
|
|
429
|
+
"EPIPE",
|
|
430
|
+
"ENOTFOUND",
|
|
431
|
+
"EAI_AGAIN",
|
|
432
|
+
"ECONNREFUSED",
|
|
433
|
+
"ERR_SSL_SSLV3_ALERT_BAD_RECORD_MAC",
|
|
434
|
+
"ERR_SSL_WRONG_VERSION_NUMBER",
|
|
435
|
+
"ERR_SSL_DECRYPTION_FAILED_OR_BAD_RECORD_MAC",
|
|
436
|
+
"ERR_SSL_BAD_RECORD_MAC",
|
|
437
|
+
"EPROTO"
|
|
438
|
+
]);
|
|
439
|
+
function canRetryRequest(init) {
|
|
440
|
+
if (!init?.body) {
|
|
441
|
+
return true;
|
|
495
442
|
}
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
console.warn(`[Agy Auth] onboardManagedProject calling URL: ${formatHyperlink(onboardUrl)} with project: ${projectId || "none"}`);
|
|
443
|
+
const body = init.body;
|
|
444
|
+
if (typeof body === "string") {
|
|
445
|
+
return true;
|
|
500
446
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
447
|
+
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) {
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
if (typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer) {
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
if (typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(body)) {
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
function isRetryableStatus(status) {
|
|
462
|
+
return status === 429 || status >= 500 && status < 600;
|
|
463
|
+
}
|
|
464
|
+
function isRetryableNetworkError(error45) {
|
|
465
|
+
const code = getNetworkErrorCode(error45);
|
|
466
|
+
if (code && RETRYABLE_NETWORK_CODES.has(code)) {
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
return error45 instanceof Error && error45.message.toLowerCase().includes("fetch failed");
|
|
470
|
+
}
|
|
471
|
+
async function resolveRetryDelayMs(response, attempt, quotaDelayMs) {
|
|
472
|
+
const retryAfterMsHeader = parseRetryAfterMs(response.headers.get("retry-after-ms"));
|
|
473
|
+
if (retryAfterMsHeader !== null) {
|
|
474
|
+
return clampDelay(retryAfterMsHeader);
|
|
475
|
+
}
|
|
476
|
+
const retryAfterHeader = parseRetryAfter(response.headers.get("retry-after"));
|
|
477
|
+
if (retryAfterHeader !== null) {
|
|
478
|
+
return clampDelay(retryAfterHeader);
|
|
479
|
+
}
|
|
480
|
+
if (quotaDelayMs !== void 0) {
|
|
481
|
+
return clampDelay(quotaDelayMs);
|
|
482
|
+
}
|
|
483
|
+
const bodyDelay = await parseRetryDelayFromBody(response);
|
|
484
|
+
if (bodyDelay !== null) {
|
|
485
|
+
return clampDelay(bodyDelay);
|
|
486
|
+
}
|
|
487
|
+
return getExponentialDelayWithJitter(attempt);
|
|
488
|
+
}
|
|
489
|
+
function getExponentialDelayWithJitter(attempt) {
|
|
490
|
+
const base = Math.min(DEFAULT_MAX_DELAY_MS, DEFAULT_INITIAL_DELAY_MS * Math.pow(2, attempt - 1));
|
|
491
|
+
const jitter = base * 0.3 * (Math.random() * 2 - 1);
|
|
492
|
+
return clampDelay(base + jitter);
|
|
493
|
+
}
|
|
494
|
+
function wait(ms) {
|
|
495
|
+
return new Promise((resolve) => {
|
|
496
|
+
setTimeout(resolve, ms);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
function getNetworkErrorCode(error45) {
|
|
500
|
+
const readCode = (value) => {
|
|
501
|
+
if (!value || typeof value !== "object") {
|
|
512
502
|
return void 0;
|
|
513
503
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
517
|
-
await wait(delayMs);
|
|
518
|
-
const operationUrl = `${baseUrl}/${payload.name}`;
|
|
519
|
-
const opResponse = await fetchWithDebug(
|
|
520
|
-
operationUrl,
|
|
521
|
-
"GET",
|
|
522
|
-
buildCodeAssistHeaders2(accessToken, userAgentModel),
|
|
523
|
-
void 0,
|
|
524
|
-
projectId
|
|
525
|
-
);
|
|
526
|
-
if (!opResponse.ok) {
|
|
527
|
-
return void 0;
|
|
528
|
-
}
|
|
529
|
-
payload = await opResponse.json();
|
|
530
|
-
if (payload.done) {
|
|
531
|
-
break;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
504
|
+
if ("code" in value && typeof value.code === "string") {
|
|
505
|
+
return value.code;
|
|
534
506
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
507
|
+
return void 0;
|
|
508
|
+
};
|
|
509
|
+
const direct = readCode(error45);
|
|
510
|
+
if (direct) {
|
|
511
|
+
return direct;
|
|
512
|
+
}
|
|
513
|
+
let cursor = error45;
|
|
514
|
+
for (let depth = 0; depth < 5; depth += 1) {
|
|
515
|
+
if (!cursor || typeof cursor !== "object" || !("cause" in cursor)) {
|
|
516
|
+
break;
|
|
538
517
|
}
|
|
539
|
-
|
|
540
|
-
|
|
518
|
+
cursor = cursor.cause;
|
|
519
|
+
const code = readCode(cursor);
|
|
520
|
+
if (code) {
|
|
521
|
+
return code;
|
|
541
522
|
}
|
|
542
|
-
} catch (error45) {
|
|
543
|
-
const errStr = error45 instanceof Error ? error45.stack || error45.message : String(error45);
|
|
544
|
-
console.warn(`[Agy Auth] Failed to onboard Antigravity managed project: ${errStr}`);
|
|
545
|
-
return void 0;
|
|
546
523
|
}
|
|
547
524
|
return void 0;
|
|
548
525
|
}
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
"Content-Type": "application/json",
|
|
553
|
-
Authorization: `Bearer ${accessToken}`,
|
|
554
|
-
"User-Agent": userAgent
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
async function fetchWithDebug(url2, method, headers, body, projectId) {
|
|
558
|
-
const response = await agyFetch(url2, {
|
|
559
|
-
method,
|
|
560
|
-
headers,
|
|
561
|
-
body: body ? JSON.stringify(body) : void 0
|
|
562
|
-
});
|
|
563
|
-
return response;
|
|
564
|
-
}
|
|
565
|
-
async function readResponseTextIfNeeded(response, needed) {
|
|
566
|
-
if (!needed && response.ok) {
|
|
567
|
-
return void 0;
|
|
526
|
+
function parseRetryAfterMs(value) {
|
|
527
|
+
if (!value) {
|
|
528
|
+
return null;
|
|
568
529
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
return void 0;
|
|
530
|
+
const parsed = Number(value.trim());
|
|
531
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
532
|
+
return null;
|
|
573
533
|
}
|
|
534
|
+
return Math.round(parsed);
|
|
574
535
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
var projectContextPendingCache = /* @__PURE__ */ new Map();
|
|
579
|
-
function invalidateProjectContextCache(refresh) {
|
|
580
|
-
if (!refresh) {
|
|
581
|
-
projectContextPendingCache.clear();
|
|
582
|
-
projectContextResultCache.clear();
|
|
583
|
-
return;
|
|
536
|
+
function parseRetryAfter(value) {
|
|
537
|
+
if (!value) {
|
|
538
|
+
return null;
|
|
584
539
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
for (const key of projectContextPendingCache.keys()) {
|
|
589
|
-
if (key.startsWith(prefix)) {
|
|
590
|
-
projectContextPendingCache.delete(key);
|
|
591
|
-
}
|
|
540
|
+
const trimmed = value.trim();
|
|
541
|
+
if (!trimmed) {
|
|
542
|
+
return null;
|
|
592
543
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
544
|
+
const seconds = Number(trimmed);
|
|
545
|
+
if (Number.isFinite(seconds)) {
|
|
546
|
+
return Math.max(0, Math.round(seconds * 1e3));
|
|
547
|
+
}
|
|
548
|
+
const parsedDate = Date.parse(trimmed);
|
|
549
|
+
if (!Number.isNaN(parsedDate)) {
|
|
550
|
+
return Math.max(0, parsedDate - Date.now());
|
|
597
551
|
}
|
|
552
|
+
return null;
|
|
598
553
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const projectId = configuredProject || parts.projectId;
|
|
603
|
-
if (!configuredProject && (projectId || parts.managedProjectId)) {
|
|
604
|
-
return {
|
|
605
|
-
auth,
|
|
606
|
-
effectiveProjectId: projectId || parts.managedProjectId || ""
|
|
607
|
-
};
|
|
554
|
+
function clampDelay(delayMs) {
|
|
555
|
+
if (!Number.isFinite(delayMs)) {
|
|
556
|
+
return DEFAULT_MAX_DELAY_MS;
|
|
608
557
|
}
|
|
609
|
-
|
|
558
|
+
return Math.min(Math.max(0, Math.round(delayMs)), DEFAULT_MAX_DELAY_MS);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/sdk/retry/cooldown-store.ts
|
|
562
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "fs";
|
|
563
|
+
import { join, dirname } from "path";
|
|
564
|
+
import { homedir, tmpdir } from "os";
|
|
565
|
+
var WRITE_THROTTLE_MS = 5e3;
|
|
566
|
+
function getConfigDir() {
|
|
567
|
+
const platform2 = process.platform;
|
|
568
|
+
if (platform2 === "win32") {
|
|
569
|
+
return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "opencode");
|
|
570
|
+
}
|
|
571
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
572
|
+
return join(xdgConfig, "opencode");
|
|
573
|
+
}
|
|
574
|
+
function getCooldownFilePath() {
|
|
575
|
+
return join(getConfigDir(), "antigravity-retry-cooldowns.json");
|
|
576
|
+
}
|
|
577
|
+
function loadCooldowns() {
|
|
578
|
+
const result = /* @__PURE__ */ new Map();
|
|
610
579
|
try {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
throw error45;
|
|
580
|
+
const filePath = getCooldownFilePath();
|
|
581
|
+
if (!existsSync(filePath)) {
|
|
582
|
+
return result;
|
|
615
583
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
throw new ProjectIdRequiredError();
|
|
621
|
-
}
|
|
622
|
-
const managedProjectId = normalizeProjectId(loadPayload.cloudaicompanionProject);
|
|
623
|
-
if (managedProjectId) {
|
|
624
|
-
const updatedAuth = withProjectAuth(auth, parts.refreshToken, projectId, managedProjectId);
|
|
625
|
-
if (persistAuth) {
|
|
626
|
-
await persistAuth(updatedAuth);
|
|
584
|
+
const content = readFileSync(filePath, "utf-8");
|
|
585
|
+
const data = JSON.parse(content);
|
|
586
|
+
if (data.version !== "1.0") {
|
|
587
|
+
return result;
|
|
627
588
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
589
|
+
const now = Date.now();
|
|
590
|
+
for (const [key, expiresAt] of Object.entries(data.entries)) {
|
|
591
|
+
if (typeof expiresAt === "number" && expiresAt > now) {
|
|
592
|
+
result.set(key, expiresAt);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
} catch {
|
|
633
596
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
function saveCooldowns(entries) {
|
|
600
|
+
try {
|
|
601
|
+
const filePath = getCooldownFilePath();
|
|
602
|
+
const dir = dirname(filePath);
|
|
603
|
+
if (!existsSync(dir)) {
|
|
604
|
+
mkdirSync(dir, { recursive: true });
|
|
637
605
|
}
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
606
|
+
const now = Date.now();
|
|
607
|
+
const serializable = {};
|
|
608
|
+
for (const [key, expiresAt] of entries.entries()) {
|
|
609
|
+
if (expiresAt > now) {
|
|
610
|
+
serializable[key] = expiresAt;
|
|
611
|
+
}
|
|
641
612
|
}
|
|
642
|
-
|
|
613
|
+
const data = {
|
|
614
|
+
version: "1.0",
|
|
615
|
+
entries: serializable,
|
|
616
|
+
updatedAt: now
|
|
617
|
+
};
|
|
618
|
+
const tmpPath = join(tmpdir(), `antigravity-cooldowns-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`);
|
|
619
|
+
writeFileSync(tmpPath, JSON.stringify(data), "utf-8");
|
|
620
|
+
try {
|
|
621
|
+
renameSync(tmpPath, filePath);
|
|
622
|
+
} catch {
|
|
623
|
+
writeFileSync(filePath, readFileSync(tmpPath));
|
|
624
|
+
try {
|
|
625
|
+
unlinkSync(tmpPath);
|
|
626
|
+
} catch {
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return true;
|
|
630
|
+
} catch {
|
|
631
|
+
return false;
|
|
643
632
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
633
|
+
}
|
|
634
|
+
var CooldownStore = class {
|
|
635
|
+
dirty = false;
|
|
636
|
+
lastWriteTime = 0;
|
|
637
|
+
writeTimer = null;
|
|
638
|
+
entries = /* @__PURE__ */ new Map();
|
|
639
|
+
bind(entries) {
|
|
640
|
+
this.entries = entries;
|
|
648
641
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
projectId,
|
|
653
|
-
userAgentModel
|
|
654
|
-
);
|
|
655
|
-
if (onboardedProjectId) {
|
|
656
|
-
const updatedAuth = withProjectAuth(auth, parts.refreshToken, projectId, onboardedProjectId);
|
|
657
|
-
if (persistAuth) {
|
|
658
|
-
await persistAuth(updatedAuth);
|
|
659
|
-
}
|
|
660
|
-
return { auth: updatedAuth, effectiveProjectId: onboardedProjectId };
|
|
642
|
+
markDirty() {
|
|
643
|
+
this.dirty = true;
|
|
644
|
+
this.scheduleThrottledWrite();
|
|
661
645
|
}
|
|
662
|
-
|
|
663
|
-
|
|
646
|
+
flush() {
|
|
647
|
+
this.dirty = false;
|
|
648
|
+
this.clearWriteTimer();
|
|
649
|
+
this.lastWriteTime = Date.now();
|
|
650
|
+
return saveCooldowns(this.entries);
|
|
664
651
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
return { auth, effectiveProjectId: "" };
|
|
652
|
+
shutdown() {
|
|
653
|
+
this.clearWriteTimer();
|
|
654
|
+
if (this.dirty) {
|
|
655
|
+
saveCooldowns(this.entries);
|
|
656
|
+
this.dirty = false;
|
|
657
|
+
}
|
|
672
658
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
if (cached2) {
|
|
677
|
-
return cached2;
|
|
659
|
+
scheduleThrottledWrite() {
|
|
660
|
+
if (this.writeTimer) {
|
|
661
|
+
return;
|
|
678
662
|
}
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
663
|
+
const elapsed = Date.now() - this.lastWriteTime;
|
|
664
|
+
const remaining = Math.max(0, WRITE_THROTTLE_MS - elapsed);
|
|
665
|
+
this.writeTimer = setTimeout(() => {
|
|
666
|
+
this.writeTimer = null;
|
|
667
|
+
this.lastWriteTime = Date.now();
|
|
668
|
+
if (this.dirty) {
|
|
669
|
+
this.dirty = false;
|
|
670
|
+
saveCooldowns(this.entries);
|
|
671
|
+
}
|
|
672
|
+
}, remaining);
|
|
673
|
+
if (this.writeTimer && typeof this.writeTimer === "object" && "unref" in this.writeTimer) {
|
|
674
|
+
this.writeTimer.unref();
|
|
682
675
|
}
|
|
683
676
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
async (updatedAuth) => {
|
|
689
|
-
await client.auth.set({
|
|
690
|
-
path: { id: AGY_PROVIDER_ID },
|
|
691
|
-
body: updatedAuth
|
|
692
|
-
});
|
|
693
|
-
},
|
|
694
|
-
userAgentModel
|
|
695
|
-
);
|
|
696
|
-
if (!cacheKey) {
|
|
697
|
-
return resolveContext();
|
|
698
|
-
}
|
|
699
|
-
const promise2 = resolveContext().then((result) => {
|
|
700
|
-
const nextKey = getCacheKey(result.auth) ?? cacheKey;
|
|
701
|
-
projectContextPendingCache.delete(cacheKey);
|
|
702
|
-
projectContextResultCache.set(nextKey, result);
|
|
703
|
-
if (nextKey !== cacheKey) {
|
|
704
|
-
projectContextResultCache.delete(cacheKey);
|
|
677
|
+
clearWriteTimer() {
|
|
678
|
+
if (this.writeTimer) {
|
|
679
|
+
clearTimeout(this.writeTimer);
|
|
680
|
+
this.writeTimer = null;
|
|
705
681
|
}
|
|
706
|
-
return result;
|
|
707
|
-
}).catch((error45) => {
|
|
708
|
-
projectContextPendingCache.delete(cacheKey);
|
|
709
|
-
throw error45;
|
|
710
|
-
});
|
|
711
|
-
projectContextPendingCache.set(cacheKey, promise2);
|
|
712
|
-
return promise2;
|
|
713
|
-
}
|
|
714
|
-
function withProjectAuth(auth, refreshToken, projectId, managedProjectId) {
|
|
715
|
-
return {
|
|
716
|
-
...auth,
|
|
717
|
-
refresh: formatRefreshParts({
|
|
718
|
-
refreshToken,
|
|
719
|
-
projectId,
|
|
720
|
-
managedProjectId
|
|
721
|
-
})
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
function buildProjectCacheKey(auth, configuredProjectId) {
|
|
725
|
-
const base = getCacheKey(auth);
|
|
726
|
-
if (!base) {
|
|
727
|
-
return void 0;
|
|
728
682
|
}
|
|
729
|
-
|
|
730
|
-
return project ? `${base}|cfg:${project}` : base;
|
|
731
|
-
}
|
|
683
|
+
};
|
|
732
684
|
|
|
733
|
-
// src/
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
685
|
+
// src/sdk/retry/index.ts
|
|
686
|
+
var retryCooldownByKey = /* @__PURE__ */ new Map();
|
|
687
|
+
var cooldownStore = new CooldownStore();
|
|
688
|
+
var cooldownPersistenceInitialized = false;
|
|
689
|
+
function initCooldownPersistence() {
|
|
690
|
+
if (cooldownPersistenceInitialized) return;
|
|
691
|
+
cooldownPersistenceInitialized = true;
|
|
692
|
+
try {
|
|
693
|
+
const persisted = loadCooldowns();
|
|
694
|
+
for (const [key, expiresAt] of persisted.entries()) {
|
|
695
|
+
retryCooldownByKey.set(key, expiresAt);
|
|
696
|
+
}
|
|
697
|
+
cooldownStore.bind(retryCooldownByKey);
|
|
698
|
+
if (typeof process !== "undefined") {
|
|
699
|
+
process.on("exit", () => {
|
|
700
|
+
cooldownStore.shutdown();
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
} catch {
|
|
704
|
+
cooldownStore.bind(retryCooldownByKey);
|
|
741
705
|
}
|
|
742
|
-
return normalizeProjectId2(provider.options?.projectId);
|
|
743
706
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
707
|
+
var MODEL_CAPACITY_COOLDOWN_MS = 8e3;
|
|
708
|
+
async function fetchWithRetry(input, init) {
|
|
709
|
+
if (!cooldownPersistenceInitialized) initCooldownPersistence();
|
|
710
|
+
if (!canRetryRequest(init)) {
|
|
711
|
+
return agyFetch(input, init);
|
|
747
712
|
}
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
function normalizeProjectId2(value) {
|
|
763
|
-
if (typeof value !== "string") {
|
|
764
|
-
return void 0;
|
|
765
|
-
}
|
|
766
|
-
const trimmed = value.trim();
|
|
767
|
-
return trimmed || void 0;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// src/plugin/oauth-authorize.ts
|
|
771
|
-
function createOAuthAuthorizeMethod(options) {
|
|
772
|
-
return async () => {
|
|
773
|
-
const maybeHydrateProjectId = async (result) => {
|
|
774
|
-
if (result.type !== "success" || !result.access) {
|
|
775
|
-
return result;
|
|
713
|
+
const retryInit = cloneRetryableInit(init);
|
|
714
|
+
const throttleKey = buildRetryThrottleKey(input, retryInit);
|
|
715
|
+
await waitForRetryCooldown(throttleKey, retryInit.signal);
|
|
716
|
+
let attempt = 1;
|
|
717
|
+
const url2 = readRequestUrl(input);
|
|
718
|
+
while (attempt <= DEFAULT_MAX_ATTEMPTS) {
|
|
719
|
+
let response;
|
|
720
|
+
try {
|
|
721
|
+
response = await agyFetch(input, retryInit);
|
|
722
|
+
} catch (error45) {
|
|
723
|
+
if (attempt >= DEFAULT_MAX_ATTEMPTS || !isRetryableNetworkError(error45)) {
|
|
724
|
+
throw error45;
|
|
776
725
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
});
|
|
780
|
-
try {
|
|
781
|
-
const initialRefresh = formatRefreshParts({
|
|
782
|
-
refreshToken: result.refresh
|
|
783
|
-
});
|
|
784
|
-
const authSnapshot = {
|
|
785
|
-
type: "oauth",
|
|
786
|
-
refresh: initialRefresh,
|
|
787
|
-
access: result.access,
|
|
788
|
-
expires: result.expires
|
|
789
|
-
};
|
|
790
|
-
const projectContext = await resolveProjectContextFromAccessToken(
|
|
791
|
-
authSnapshot,
|
|
792
|
-
result.access,
|
|
793
|
-
configuredProjectId,
|
|
794
|
-
void 0,
|
|
795
|
-
await options?.getUserAgentModel?.()
|
|
796
|
-
);
|
|
797
|
-
return projectContext.auth.refresh !== initialRefresh ? { ...result, refresh: projectContext.auth.refresh } : { ...result, refresh: initialRefresh };
|
|
798
|
-
} catch (error45) {
|
|
799
|
-
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
800
|
-
console.warn(`[OAuth] Project resolution skipped: ${message}`);
|
|
801
|
-
if (options?.client?.tui?.showToast) {
|
|
802
|
-
const message2 = error45 instanceof Error ? error45.message : String(error45);
|
|
803
|
-
options.client.tui.showToast({
|
|
804
|
-
body: {
|
|
805
|
-
title: "Failed to bind project context",
|
|
806
|
-
message: `Authorized successfully but failed to bind project, real models will be unavailable: ${message2}`,
|
|
807
|
-
variant: "warning",
|
|
808
|
-
duration: 15e3
|
|
809
|
-
}
|
|
810
|
-
}).catch(() => {
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
const initialRefresh = formatRefreshParts({
|
|
814
|
-
refreshToken: result.refresh
|
|
815
|
-
});
|
|
816
|
-
return { ...result, refresh: initialRefresh };
|
|
726
|
+
if (retryInit.signal?.aborted) {
|
|
727
|
+
throw error45;
|
|
817
728
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
openBrowserUrl(authorization.url);
|
|
729
|
+
const delayMs2 = getExponentialDelayWithJitter(attempt);
|
|
730
|
+
await wait(delayMs2);
|
|
731
|
+
attempt += 1;
|
|
732
|
+
continue;
|
|
823
733
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
return { type: "failed", error: "Missing authorization code in callback input" };
|
|
833
|
-
}
|
|
834
|
-
if (state && state !== authorization.state) {
|
|
835
|
-
return { type: "failed", error: "State mismatch in callback input (possible CSRF attempt)" };
|
|
836
|
-
}
|
|
837
|
-
const exchangeResult = await exchangeAgyWithVerifier(code, authorization.verifier);
|
|
838
|
-
return await maybeHydrateProjectId(exchangeResult);
|
|
839
|
-
} catch (error45) {
|
|
840
|
-
return {
|
|
841
|
-
type: "failed",
|
|
842
|
-
error: error45 instanceof Error ? error45.message : "Unknown error"
|
|
843
|
-
};
|
|
844
|
-
}
|
|
734
|
+
if (!isRetryableStatus(response.status)) {
|
|
735
|
+
return response;
|
|
736
|
+
}
|
|
737
|
+
const quotaContext = response.status === 429 ? await classifyQuotaResponse(response) : null;
|
|
738
|
+
if (response.status === 429 && quotaContext?.terminal) {
|
|
739
|
+
if (quotaContext.reason === "MODEL_CAPACITY_EXHAUSTED") {
|
|
740
|
+
const cooldownMs = quotaContext.retryDelayMs ?? MODEL_CAPACITY_COOLDOWN_MS;
|
|
741
|
+
setRetryCooldown(throttleKey, cooldownMs);
|
|
845
742
|
}
|
|
846
|
-
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
function parseOAuthCallbackInput(input) {
|
|
850
|
-
const trimmed = input.trim();
|
|
851
|
-
if (!trimmed) {
|
|
852
|
-
return {};
|
|
853
|
-
}
|
|
854
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
855
|
-
try {
|
|
856
|
-
const url2 = new URL(trimmed);
|
|
857
|
-
return {
|
|
858
|
-
code: url2.searchParams.get("code") || void 0,
|
|
859
|
-
state: url2.searchParams.get("state") || void 0
|
|
860
|
-
};
|
|
861
|
-
} catch {
|
|
862
|
-
return {};
|
|
743
|
+
return response;
|
|
863
744
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
if (candidate.includes("=")) {
|
|
867
|
-
const params = new URLSearchParams(candidate);
|
|
868
|
-
const code = params.get("code") || void 0;
|
|
869
|
-
const state = params.get("state") || void 0;
|
|
870
|
-
if (code || state) {
|
|
871
|
-
return { code, state };
|
|
745
|
+
if (attempt >= DEFAULT_MAX_ATTEMPTS || retryInit.signal?.aborted) {
|
|
746
|
+
return response;
|
|
872
747
|
}
|
|
748
|
+
const delayMs = await resolveRetryDelayMs(response, attempt, quotaContext?.retryDelayMs);
|
|
749
|
+
if (delayMs > 0 && response.status === 429) {
|
|
750
|
+
setRetryCooldown(throttleKey, delayMs);
|
|
751
|
+
}
|
|
752
|
+
if (delayMs > 0) {
|
|
753
|
+
await wait(delayMs);
|
|
754
|
+
}
|
|
755
|
+
attempt += 1;
|
|
873
756
|
}
|
|
874
|
-
return
|
|
757
|
+
return agyFetch(input, retryInit);
|
|
875
758
|
}
|
|
876
|
-
function
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const command = platform2 === "darwin" ? "open" : platform2 === "win32" ? "rundll32" : "xdg-open";
|
|
880
|
-
const args = platform2 === "win32" ? ["url.dll,FileProtocolHandler", url2] : [url2];
|
|
881
|
-
const child = spawn(command, args, {
|
|
882
|
-
stdio: "ignore",
|
|
883
|
-
detached: true
|
|
884
|
-
});
|
|
885
|
-
child.unref?.();
|
|
886
|
-
} catch {
|
|
759
|
+
function cloneRetryableInit(init) {
|
|
760
|
+
if (!init) {
|
|
761
|
+
return {};
|
|
887
762
|
}
|
|
763
|
+
return {
|
|
764
|
+
...init,
|
|
765
|
+
headers: new Headers(init.headers ?? {})
|
|
766
|
+
};
|
|
888
767
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "opencode");
|
|
768
|
+
function buildRetryThrottleKey(input, init) {
|
|
769
|
+
const url2 = readRequestUrl(input);
|
|
770
|
+
const body = typeof init.body === "string" ? safeParseBody(init.body) : null;
|
|
771
|
+
const project = readString(body?.project);
|
|
772
|
+
const model = readString(body?.model);
|
|
773
|
+
return `${url2}|${project ?? ""}|${model ?? ""}`;
|
|
774
|
+
}
|
|
775
|
+
async function waitForRetryCooldown(key, signal) {
|
|
776
|
+
const until = retryCooldownByKey.get(key);
|
|
777
|
+
if (!until) {
|
|
778
|
+
return;
|
|
901
779
|
}
|
|
902
|
-
const
|
|
903
|
-
|
|
780
|
+
const remaining = until - Date.now();
|
|
781
|
+
if (remaining <= 0) {
|
|
782
|
+
retryCooldownByKey.delete(key);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (signal?.aborted) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
await wait(remaining);
|
|
789
|
+
retryCooldownByKey.delete(key);
|
|
904
790
|
}
|
|
905
|
-
function
|
|
906
|
-
|
|
791
|
+
function setRetryCooldown(key, delayMs) {
|
|
792
|
+
if (!cooldownPersistenceInitialized) initCooldownPersistence();
|
|
793
|
+
const next = Date.now() + delayMs;
|
|
794
|
+
const current = retryCooldownByKey.get(key) ?? 0;
|
|
795
|
+
retryCooldownByKey.set(key, Math.max(current, next));
|
|
796
|
+
cooldownStore.markDirty();
|
|
907
797
|
}
|
|
908
|
-
function
|
|
909
|
-
|
|
910
|
-
|
|
798
|
+
function readRequestUrl(input) {
|
|
799
|
+
if (typeof input === "string") {
|
|
800
|
+
return input;
|
|
801
|
+
}
|
|
802
|
+
if (input instanceof URL) {
|
|
803
|
+
return input.toString();
|
|
804
|
+
}
|
|
805
|
+
const request = input;
|
|
806
|
+
if (request.url) {
|
|
807
|
+
return request.url;
|
|
808
|
+
}
|
|
809
|
+
return input.toString();
|
|
810
|
+
}
|
|
811
|
+
function safeParseBody(body) {
|
|
812
|
+
if (!body) {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
911
815
|
try {
|
|
912
|
-
|
|
913
|
-
if (
|
|
914
|
-
|
|
915
|
-
}
|
|
916
|
-
const existingLines = content.split("\n").map((line) => line.trim());
|
|
917
|
-
const missing = entries.filter((e) => !existingLines.includes(e));
|
|
918
|
-
if (missing.length === 0) return;
|
|
919
|
-
if (content === "") {
|
|
920
|
-
writeFileSync(gitignorePath, missing.join("\n") + "\n", "utf-8");
|
|
921
|
-
} else {
|
|
922
|
-
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
923
|
-
appendFileSync(gitignorePath, suffix + missing.join("\n") + "\n", "utf-8");
|
|
816
|
+
const parsed = JSON.parse(body);
|
|
817
|
+
if (parsed && typeof parsed === "object") {
|
|
818
|
+
return parsed;
|
|
924
819
|
}
|
|
925
820
|
} catch {
|
|
926
821
|
}
|
|
822
|
+
return null;
|
|
927
823
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
dirty = false;
|
|
939
|
-
writeTimer = null;
|
|
940
|
-
cleanupTimer = null;
|
|
941
|
-
// Statistical metrics
|
|
942
|
-
stats = {
|
|
943
|
-
memoryHits: 0,
|
|
944
|
-
diskHits: 0,
|
|
945
|
-
misses: 0,
|
|
946
|
-
writes: 0
|
|
947
|
-
};
|
|
948
|
-
constructor(config2) {
|
|
949
|
-
this.enabled = config2.enabled;
|
|
950
|
-
this.memoryTtlMs = config2.memory_ttl_seconds * 1e3;
|
|
951
|
-
this.diskTtlMs = config2.disk_ttl_seconds * 1e3;
|
|
952
|
-
this.writeIntervalMs = config2.write_interval_seconds * 1e3;
|
|
953
|
-
this.cacheFilePath = getCacheFilePath();
|
|
954
|
-
if (this.enabled) {
|
|
955
|
-
this.loadFromDisk();
|
|
956
|
-
this.startBackgroundTasks();
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
// ===========================================================================
|
|
960
|
-
// Public Signature API
|
|
961
|
-
// ===========================================================================
|
|
962
|
-
/**
|
|
963
|
-
* Generates a unique cache key based on session ID and model ID
|
|
964
|
-
*/
|
|
965
|
-
static makeKey(sessionId, modelId) {
|
|
966
|
-
return `${sessionId}:${modelId}`;
|
|
824
|
+
function readString(value) {
|
|
825
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/sdk/terminal-hyperlink.ts
|
|
829
|
+
var OSC8_OPEN = "\x1B]8;;";
|
|
830
|
+
var OSC8_CLOSE = "\x07";
|
|
831
|
+
function supportsOsc8Hyperlinks() {
|
|
832
|
+
if (process.stdout && !process.stdout.isTTY) {
|
|
833
|
+
return false;
|
|
967
834
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
*/
|
|
971
|
-
store(key, signature) {
|
|
972
|
-
if (!this.enabled) return;
|
|
973
|
-
this.cache.set(key, {
|
|
974
|
-
value: signature,
|
|
975
|
-
timestamp: Date.now()
|
|
976
|
-
});
|
|
977
|
-
this.dirty = true;
|
|
835
|
+
if (process.env.OPENCODE_HEADLESS) {
|
|
836
|
+
return false;
|
|
978
837
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
*/
|
|
983
|
-
retrieve(key) {
|
|
984
|
-
if (!this.enabled) return null;
|
|
985
|
-
const entry = this.cache.get(key);
|
|
986
|
-
if (entry) {
|
|
987
|
-
const age = Date.now() - entry.timestamp;
|
|
988
|
-
if (age <= this.memoryTtlMs) {
|
|
989
|
-
this.stats.memoryHits++;
|
|
990
|
-
return entry.value;
|
|
991
|
-
}
|
|
992
|
-
this.cache.delete(key);
|
|
993
|
-
}
|
|
994
|
-
this.stats.misses++;
|
|
995
|
-
return null;
|
|
838
|
+
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
839
|
+
if (termProgram === "iterm.app" || termProgram === "wezterm" || termProgram === "ghostty") {
|
|
840
|
+
return true;
|
|
996
841
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
*/
|
|
1000
|
-
has(key) {
|
|
1001
|
-
if (!this.enabled) return false;
|
|
1002
|
-
const entry = this.cache.get(key);
|
|
1003
|
-
if (!entry) return false;
|
|
1004
|
-
const age = Date.now() - entry.timestamp;
|
|
1005
|
-
return age <= this.memoryTtlMs;
|
|
842
|
+
if (process.env.KITTY_WINDOW_ID) {
|
|
843
|
+
return true;
|
|
1006
844
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
/**
|
|
1011
|
-
* Caches the full thought chain text content and signature
|
|
1012
|
-
* Allows self-healing and recovery of historical thought blocks even if the context is subsequently compressed.
|
|
1013
|
-
*/
|
|
1014
|
-
storeThinking(key, thinkingText, signature, toolIds) {
|
|
1015
|
-
if (!this.enabled || !thinkingText || !signature) return;
|
|
1016
|
-
this.cache.set(key, {
|
|
1017
|
-
value: signature,
|
|
1018
|
-
timestamp: Date.now(),
|
|
1019
|
-
thinkingText,
|
|
1020
|
-
textPreview: thinkingText.slice(0, 100),
|
|
1021
|
-
toolIds
|
|
1022
|
-
});
|
|
1023
|
-
this.dirty = true;
|
|
845
|
+
const vteVersion = parseInt(process.env.VTE_VERSION ?? "0", 10);
|
|
846
|
+
if (vteVersion >= 5e3) {
|
|
847
|
+
return true;
|
|
1024
848
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
const entry = this.cache.get(key);
|
|
1031
|
-
if (!entry || !entry.thinkingText) return null;
|
|
1032
|
-
const age = Date.now() - entry.timestamp;
|
|
1033
|
-
if (age > this.memoryTtlMs) {
|
|
1034
|
-
this.cache.delete(key);
|
|
1035
|
-
return null;
|
|
849
|
+
const colorTerm = process.env.COLORTERM?.toLowerCase() ?? "";
|
|
850
|
+
if (colorTerm === "truecolor" || colorTerm === "24bit") {
|
|
851
|
+
const term = process.env.TERM?.toLowerCase() ?? "";
|
|
852
|
+
if (term.startsWith("xterm") || term.startsWith("alacritty") || term === "termion") {
|
|
853
|
+
return true;
|
|
1036
854
|
}
|
|
1037
|
-
this.stats.memoryHits++;
|
|
1038
|
-
return {
|
|
1039
|
-
text: entry.thinkingText,
|
|
1040
|
-
signature: entry.value,
|
|
1041
|
-
toolIds: entry.toolIds
|
|
1042
|
-
};
|
|
1043
855
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
if (
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
return age <= this.memoryTtlMs;
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
function formatHyperlink(url2, text) {
|
|
859
|
+
if (!supportsOsc8Hyperlinks()) {
|
|
860
|
+
if (text) {
|
|
861
|
+
return text === url2 ? text : `${text} (${url2})`;
|
|
862
|
+
}
|
|
863
|
+
return url2;
|
|
1053
864
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
865
|
+
const displayText = text ?? url2;
|
|
866
|
+
return `${OSC8_OPEN}${url2}${OSC8_CLOSE}${displayText}${OSC8_OPEN}${OSC8_CLOSE}`;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// src/plugin/project/types.ts
|
|
870
|
+
var FREE_TIER_ID = "free-tier";
|
|
871
|
+
var LEGACY_TIER_ID = "legacy-tier";
|
|
872
|
+
var CODE_ASSIST_METADATA = {
|
|
873
|
+
ideType: "ANTIGRAVITY"
|
|
874
|
+
};
|
|
875
|
+
var ProjectIdRequiredError = class extends Error {
|
|
876
|
+
constructor() {
|
|
877
|
+
super(
|
|
878
|
+
"Google Gemini/Agy requires a Google Cloud project. Enable the Gemini for Google Cloud API on a project you control, then set `provider.google.options.projectId` in your Opencode config (or set OPENCODE_AGY_PROJECT_ID / GOOGLE_CLOUD_PROJECT)."
|
|
879
|
+
);
|
|
1064
880
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
881
|
+
};
|
|
882
|
+
var ProjectAccessDeniedError = class extends Error {
|
|
883
|
+
constructor(projectId, backendMessage) {
|
|
884
|
+
const projectStr = projectId ? `project '${projectId}'` : "the requested project";
|
|
885
|
+
const msg = backendMessage ? `
|
|
886
|
+
Backend response: ${backendMessage}` : "";
|
|
887
|
+
super(`Access denied to ${projectStr}. Ensure the Gemini for Google Cloud API is enabled and you have the correct IAM permissions.${msg}`);
|
|
888
|
+
this.name = "ProjectAccessDeniedError";
|
|
1071
889
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
if (this.cleanupTimer) {
|
|
1081
|
-
clearInterval(this.cleanupTimer);
|
|
1082
|
-
this.cleanupTimer = null;
|
|
890
|
+
};
|
|
891
|
+
var AccountValidationRequiredError = class extends Error {
|
|
892
|
+
validationUrl;
|
|
893
|
+
validationLearnMoreUrl;
|
|
894
|
+
constructor(message, validationUrl, validationLearnMoreUrl) {
|
|
895
|
+
const parts = [message.trim()];
|
|
896
|
+
if (validationUrl) {
|
|
897
|
+
parts.push(`Complete validation: ${validationUrl}`);
|
|
1083
898
|
}
|
|
1084
|
-
if (
|
|
1085
|
-
|
|
899
|
+
if (validationLearnMoreUrl) {
|
|
900
|
+
parts.push(`Learn more: ${validationLearnMoreUrl}`);
|
|
1086
901
|
}
|
|
902
|
+
super(parts.join("\n"));
|
|
903
|
+
this.name = "AccountValidationRequiredError";
|
|
904
|
+
this.validationUrl = validationUrl;
|
|
905
|
+
this.validationLearnMoreUrl = validationLearnMoreUrl;
|
|
1087
906
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
const content = readFileSync(this.cacheFilePath, "utf-8");
|
|
1100
|
-
const data = JSON.parse(content);
|
|
1101
|
-
if (data.version !== "1.0") {
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
const now = Date.now();
|
|
1105
|
-
for (const [key, entry] of Object.entries(data.entries)) {
|
|
1106
|
-
const age = now - entry.timestamp;
|
|
1107
|
-
if (age <= this.diskTtlMs) {
|
|
1108
|
-
this.cache.set(key, {
|
|
1109
|
-
value: entry.value,
|
|
1110
|
-
timestamp: entry.timestamp,
|
|
1111
|
-
thinkingText: entry.thinkingText,
|
|
1112
|
-
textPreview: entry.textPreview,
|
|
1113
|
-
toolIds: entry.toolIds
|
|
1114
|
-
});
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
} catch {
|
|
1118
|
-
}
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// src/plugin/project/utils.ts
|
|
910
|
+
function buildMetadata(projectId, includeDuetProject = true) {
|
|
911
|
+
const metadata = {
|
|
912
|
+
...CODE_ASSIST_METADATA
|
|
913
|
+
};
|
|
914
|
+
if (projectId && includeDuetProject) {
|
|
915
|
+
metadata.duetProject = projectId;
|
|
1119
916
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
try {
|
|
1126
|
-
const dir = dirname(this.cacheFilePath);
|
|
1127
|
-
if (!existsSync(dir)) {
|
|
1128
|
-
mkdirSync(dir, { recursive: true });
|
|
1129
|
-
}
|
|
1130
|
-
ensureGitignoreSync(dir);
|
|
1131
|
-
const now = Date.now();
|
|
1132
|
-
let existingEntries = {};
|
|
1133
|
-
if (existsSync(this.cacheFilePath)) {
|
|
1134
|
-
try {
|
|
1135
|
-
const content = readFileSync(this.cacheFilePath, "utf-8");
|
|
1136
|
-
const data = JSON.parse(content);
|
|
1137
|
-
existingEntries = data.entries || {};
|
|
1138
|
-
} catch {
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
const validDiskEntries = {};
|
|
1142
|
-
for (const [key, entry] of Object.entries(existingEntries)) {
|
|
1143
|
-
const age = now - entry.timestamp;
|
|
1144
|
-
if (age <= this.diskTtlMs) {
|
|
1145
|
-
validDiskEntries[key] = entry;
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
const mergedEntries = { ...validDiskEntries };
|
|
1149
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
1150
|
-
mergedEntries[key] = {
|
|
1151
|
-
value: entry.value,
|
|
1152
|
-
timestamp: entry.timestamp,
|
|
1153
|
-
thinkingText: entry.thinkingText,
|
|
1154
|
-
textPreview: entry.textPreview,
|
|
1155
|
-
toolIds: entry.toolIds
|
|
1156
|
-
};
|
|
1157
|
-
}
|
|
1158
|
-
const cacheData = {
|
|
1159
|
-
version: "1.0",
|
|
1160
|
-
memory_ttl_seconds: this.memoryTtlMs / 1e3,
|
|
1161
|
-
disk_ttl_seconds: this.diskTtlMs / 1e3,
|
|
1162
|
-
entries: mergedEntries,
|
|
1163
|
-
statistics: {
|
|
1164
|
-
memory_hits: this.stats.memoryHits,
|
|
1165
|
-
disk_hits: this.stats.diskHits,
|
|
1166
|
-
misses: this.stats.misses,
|
|
1167
|
-
writes: this.stats.writes + 1,
|
|
1168
|
-
last_write: now
|
|
1169
|
-
}
|
|
1170
|
-
};
|
|
1171
|
-
const tmpPath = join(tmpdir(), `antigravity-cache-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`);
|
|
1172
|
-
writeFileSync(tmpPath, JSON.stringify(cacheData, null, 2), "utf-8");
|
|
1173
|
-
try {
|
|
1174
|
-
renameSync(tmpPath, this.cacheFilePath);
|
|
1175
|
-
} catch {
|
|
1176
|
-
writeFileSync(this.cacheFilePath, readFileSync(tmpPath));
|
|
1177
|
-
try {
|
|
1178
|
-
unlinkSync(tmpPath);
|
|
1179
|
-
} catch {
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
this.stats.writes++;
|
|
1183
|
-
this.dirty = false;
|
|
1184
|
-
return true;
|
|
1185
|
-
} catch {
|
|
1186
|
-
return false;
|
|
1187
|
-
}
|
|
917
|
+
return metadata;
|
|
918
|
+
}
|
|
919
|
+
function normalizeProjectId(value) {
|
|
920
|
+
if (!value) {
|
|
921
|
+
return void 0;
|
|
1188
922
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
/**
|
|
1193
|
-
* Starts timers for auto-saving and auto-cleaning expired memory entries
|
|
1194
|
-
*/
|
|
1195
|
-
startBackgroundTasks() {
|
|
1196
|
-
this.writeTimer = setInterval(() => {
|
|
1197
|
-
if (this.dirty) {
|
|
1198
|
-
this.saveToDisk();
|
|
1199
|
-
}
|
|
1200
|
-
}, this.writeIntervalMs);
|
|
1201
|
-
this.cleanupTimer = setInterval(() => {
|
|
1202
|
-
this.cleanupExpired();
|
|
1203
|
-
}, 30 * 60 * 1e3);
|
|
923
|
+
if (typeof value === "string") {
|
|
924
|
+
const trimmed = value.trim();
|
|
925
|
+
return trimmed ? trimmed : void 0;
|
|
1204
926
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
927
|
+
if (typeof value === "object" && typeof value.id === "string") {
|
|
928
|
+
const trimmed = value.id.trim();
|
|
929
|
+
return trimmed ? trimmed : void 0;
|
|
930
|
+
}
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
function pickOnboardTier(allowedTiers) {
|
|
934
|
+
if (allowedTiers && allowedTiers.length > 0) {
|
|
935
|
+
for (const tier of allowedTiers) {
|
|
936
|
+
if (tier?.isDefault) {
|
|
937
|
+
return tier;
|
|
1214
938
|
}
|
|
1215
939
|
}
|
|
940
|
+
return allowedTiers[0] ?? { id: LEGACY_TIER_ID, userDefinedCloudaicompanionProject: true };
|
|
1216
941
|
}
|
|
1217
|
-
};
|
|
1218
|
-
function createSignatureCache(config2) {
|
|
1219
|
-
if (!config2 || !config2.enabled) {
|
|
1220
|
-
return null;
|
|
1221
|
-
}
|
|
1222
|
-
return new SignatureCache(config2);
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// src/plugin/cache.ts
|
|
1226
|
-
var authCache = /* @__PURE__ */ new Map();
|
|
1227
|
-
function normalizeRefreshKey(refresh) {
|
|
1228
|
-
const key = refresh?.trim();
|
|
1229
|
-
return key ? key : void 0;
|
|
942
|
+
return { id: LEGACY_TIER_ID, userDefinedCloudaicompanionProject: true };
|
|
1230
943
|
}
|
|
1231
|
-
function
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
return auth;
|
|
1235
|
-
}
|
|
1236
|
-
const cached2 = authCache.get(key);
|
|
1237
|
-
if (!cached2) {
|
|
1238
|
-
authCache.set(key, auth);
|
|
1239
|
-
return auth;
|
|
1240
|
-
}
|
|
1241
|
-
if (!accessTokenExpired(auth)) {
|
|
1242
|
-
authCache.set(key, auth);
|
|
1243
|
-
return auth;
|
|
1244
|
-
}
|
|
1245
|
-
if (!accessTokenExpired(cached2)) {
|
|
1246
|
-
return cached2;
|
|
944
|
+
function buildIneligibleTierMessage(tiers) {
|
|
945
|
+
if (!tiers || tiers.length === 0) {
|
|
946
|
+
return void 0;
|
|
1247
947
|
}
|
|
1248
|
-
|
|
1249
|
-
return
|
|
948
|
+
const reasons = tiers.map((tier) => tier?.reasonMessage?.trim()).filter((message) => !!message);
|
|
949
|
+
return reasons.length > 0 ? reasons.join(", ") : void 0;
|
|
1250
950
|
}
|
|
1251
|
-
function
|
|
1252
|
-
|
|
1253
|
-
if (!key) {
|
|
951
|
+
function throwIfValidationRequired(tiers) {
|
|
952
|
+
if (!tiers || tiers.length === 0) {
|
|
1254
953
|
return;
|
|
1255
954
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
955
|
+
const validationTier = tiers.find((tier) => {
|
|
956
|
+
const reasonCode = tier?.reasonCode?.trim().toUpperCase();
|
|
957
|
+
return reasonCode === "VALIDATION_REQUIRED" && !!tier.validationUrl?.trim();
|
|
958
|
+
});
|
|
959
|
+
if (!validationTier) {
|
|
1261
960
|
return;
|
|
1262
961
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
var signatureCache = /* @__PURE__ */ new Map();
|
|
1269
|
-
var SIGNATURE_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
1270
|
-
var MAX_ENTRIES_PER_SESSION = 100;
|
|
1271
|
-
var SIGNATURE_TEXT_HASH_HEX_LEN = 16;
|
|
1272
|
-
var diskCache = null;
|
|
1273
|
-
function initDiskSignatureCache(config2) {
|
|
1274
|
-
diskCache = createSignatureCache(config2);
|
|
1275
|
-
return diskCache;
|
|
1276
|
-
}
|
|
1277
|
-
function hashText(text) {
|
|
1278
|
-
return createHash("sha256").update(text, "utf8").digest("hex").slice(0, SIGNATURE_TEXT_HASH_HEX_LEN);
|
|
1279
|
-
}
|
|
1280
|
-
function makeDiskKey(sessionId, textHash) {
|
|
1281
|
-
return `${sessionId}:${textHash}`;
|
|
962
|
+
throw new AccountValidationRequiredError(
|
|
963
|
+
validationTier.reasonMessage?.trim() || "Verify your account to continue.",
|
|
964
|
+
validationTier.validationUrl?.trim(),
|
|
965
|
+
validationTier.validationLearnMoreUrl?.trim()
|
|
966
|
+
);
|
|
1282
967
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const textHash = hashText(text);
|
|
1287
|
-
let sessionMemCache = signatureCache.get(sessionId);
|
|
1288
|
-
if (!sessionMemCache) {
|
|
1289
|
-
sessionMemCache = /* @__PURE__ */ new Map();
|
|
1290
|
-
signatureCache.set(sessionId, sessionMemCache);
|
|
968
|
+
function isVpcScError(payload) {
|
|
969
|
+
if (!payload || typeof payload !== "object") {
|
|
970
|
+
return false;
|
|
1291
971
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
if (now - entry.timestamp > SIGNATURE_CACHE_TTL_MS) {
|
|
1296
|
-
sessionMemCache.delete(key);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
if (sessionMemCache.size >= MAX_ENTRIES_PER_SESSION) {
|
|
1300
|
-
const entries = Array.from(sessionMemCache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
1301
|
-
const toRemove = entries.slice(0, Math.floor(MAX_ENTRIES_PER_SESSION / 4));
|
|
1302
|
-
for (const [key] of toRemove) {
|
|
1303
|
-
sessionMemCache.delete(key);
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
972
|
+
const error45 = payload.error;
|
|
973
|
+
if (!error45 || typeof error45 !== "object") {
|
|
974
|
+
return false;
|
|
1306
975
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
const diskKey = makeDiskKey(sessionId, textHash);
|
|
1311
|
-
diskCache.store(diskKey, signature);
|
|
1312
|
-
diskCache.store(sessionId, signature);
|
|
976
|
+
const details = error45.details;
|
|
977
|
+
if (!Array.isArray(details)) {
|
|
978
|
+
return false;
|
|
1313
979
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
const memValue = latestSignatureMap.get(sessionId);
|
|
1318
|
-
if (memValue) return memValue;
|
|
1319
|
-
if (diskCache) {
|
|
1320
|
-
const diskValue = diskCache.retrieve(sessionId);
|
|
1321
|
-
if (diskValue) {
|
|
1322
|
-
latestSignatureMap.set(sessionId, diskValue);
|
|
1323
|
-
return diskValue;
|
|
980
|
+
return details.some((detail) => {
|
|
981
|
+
if (!detail || typeof detail !== "object") {
|
|
982
|
+
return false;
|
|
1324
983
|
}
|
|
984
|
+
return detail.reason === "SECURITY_POLICY_VIOLATED";
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
function wait2(ms) {
|
|
988
|
+
return new Promise((resolve) => {
|
|
989
|
+
setTimeout(resolve, ms);
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
function getCacheKey(auth) {
|
|
993
|
+
const refresh = auth.refresh?.trim();
|
|
994
|
+
if (!refresh) {
|
|
995
|
+
return void 0;
|
|
1325
996
|
}
|
|
1326
|
-
|
|
997
|
+
const [baseRefreshToken = ""] = refresh.split("|");
|
|
998
|
+
return baseRefreshToken ? baseRefreshToken : void 0;
|
|
1327
999
|
}
|
|
1328
1000
|
|
|
1329
|
-
// src/sdk/
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
function createSignatureStore() {
|
|
1337
|
-
const store = /* @__PURE__ */ new Map();
|
|
1338
|
-
return {
|
|
1339
|
-
get: (key) => store.get(key),
|
|
1340
|
-
set: (key, value) => {
|
|
1341
|
-
store.set(key, value);
|
|
1342
|
-
},
|
|
1343
|
-
has: (key) => store.has(key),
|
|
1344
|
-
delete: (key) => {
|
|
1345
|
-
store.delete(key);
|
|
1001
|
+
// src/sdk/fetch_project.ts
|
|
1002
|
+
async function loadManagedProject(accessToken, projectId, userAgentModel) {
|
|
1003
|
+
try {
|
|
1004
|
+
const metadata = buildMetadata(projectId);
|
|
1005
|
+
const requestBody = { metadata };
|
|
1006
|
+
if (projectId) {
|
|
1007
|
+
requestBody.cloudaicompanionProject = projectId;
|
|
1346
1008
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1009
|
+
const url2 = `${AGY_CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`;
|
|
1010
|
+
if (process.env.OPENCODE_AGY_VERBOSE_LOGS === "1") {
|
|
1011
|
+
console.warn(`[Agy Auth] loadManagedProject calling URL: ${formatHyperlink(url2)} with project: ${projectId || "none"}`);
|
|
1012
|
+
}
|
|
1013
|
+
const headers = buildCodeAssistHeaders2(accessToken, userAgentModel);
|
|
1014
|
+
const response = await fetchWithRetry(url2, {
|
|
1015
|
+
method: "POST",
|
|
1016
|
+
headers,
|
|
1017
|
+
body: JSON.stringify(requestBody)
|
|
1018
|
+
});
|
|
1019
|
+
if (!response.ok) {
|
|
1020
|
+
if (response.status === 403 || response.status === 404) {
|
|
1021
|
+
console.warn(`[Agy Auth] loadManagedProject failed with ${response.status} (possible Cloud API mismatch/unauthorized). URL: ${formatHyperlink(url2)}, Project: ${projectId}`);
|
|
1022
|
+
const responseText = await readResponseTextIfNeeded(response, true);
|
|
1023
|
+
if (responseText && isVpcScError(responseText)) {
|
|
1024
|
+
console.warn(`[Agy Auth] loadManagedProject: Detected VPC Service Controls block`);
|
|
1025
|
+
}
|
|
1026
|
+
throw new ProjectAccessDeniedError(projectId, responseText);
|
|
1027
|
+
} else {
|
|
1028
|
+
const cleanStatusText = response.statusText.replace(/[\r\n]+/g, " ").trim();
|
|
1029
|
+
if (response.status === 429) {
|
|
1030
|
+
console.warn(`[Agy Auth] loadManagedProject failed with 429 ${cleanStatusText} (rate limited after retries; see Retry-After / quota exhaustion). URL: ${formatHyperlink(url2)}, Project: ${projectId}`);
|
|
1031
|
+
} else {
|
|
1032
|
+
console.warn(`[Agy Auth] loadManagedProject failed with ${response.status} ${cleanStatusText}`);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
const responseJson = await response.json();
|
|
1038
|
+
return responseJson;
|
|
1039
|
+
} catch (error45) {
|
|
1040
|
+
if (error45 instanceof ProjectAccessDeniedError) {
|
|
1041
|
+
throw error45;
|
|
1042
|
+
}
|
|
1043
|
+
const errStr = error45 instanceof Error ? error45.stack || error45.message : String(error45);
|
|
1044
|
+
console.warn(`[Agy Auth] Failed to load code assist project: ${errStr}`);
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1378
1047
|
}
|
|
1379
|
-
function
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1048
|
+
async function onboardManagedProject(accessToken, tierId, projectId, userAgentModel, attempts = 10, delayMs = 5e3) {
|
|
1049
|
+
const isFreeTier = tierId === FREE_TIER_ID;
|
|
1050
|
+
const metadata = buildMetadata(projectId, !isFreeTier);
|
|
1051
|
+
const requestBody = { tierId, metadata };
|
|
1052
|
+
if (!isFreeTier) {
|
|
1053
|
+
if (!projectId) {
|
|
1054
|
+
throw new ProjectIdRequiredError();
|
|
1055
|
+
}
|
|
1056
|
+
requestBody.cloudaicompanionProject = projectId;
|
|
1383
1057
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1058
|
+
const baseUrl = `${AGY_CODE_ASSIST_ENDPOINT}/v1internal`;
|
|
1059
|
+
const onboardUrl = `${baseUrl}:onboardUser`;
|
|
1060
|
+
if (process.env.OPENCODE_AGY_VERBOSE_LOGS === "1") {
|
|
1061
|
+
console.warn(`[Agy Auth] onboardManagedProject calling URL: ${formatHyperlink(onboardUrl)} with project: ${projectId || "none"}`);
|
|
1062
|
+
}
|
|
1063
|
+
try {
|
|
1064
|
+
const response = await fetchWithDebug(
|
|
1065
|
+
onboardUrl,
|
|
1066
|
+
"POST",
|
|
1067
|
+
buildCodeAssistHeaders2(accessToken, userAgentModel),
|
|
1068
|
+
requestBody,
|
|
1069
|
+
projectId
|
|
1387
1070
|
);
|
|
1071
|
+
if (!response.ok) {
|
|
1072
|
+
const cleanStatusText = response.statusText.replace(/[\r\n]+/g, " ").trim();
|
|
1073
|
+
console.warn(`[Agy Auth] onboardManagedProject response not ok: status ${response.status} ${cleanStatusText}`);
|
|
1074
|
+
return void 0;
|
|
1075
|
+
}
|
|
1076
|
+
let payload = await response.json();
|
|
1077
|
+
if (!payload.done && payload.name) {
|
|
1078
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1079
|
+
await wait2(delayMs);
|
|
1080
|
+
const operationUrl = `${baseUrl}/${payload.name}`;
|
|
1081
|
+
const opResponse = await fetchWithDebug(
|
|
1082
|
+
operationUrl,
|
|
1083
|
+
"GET",
|
|
1084
|
+
buildCodeAssistHeaders2(accessToken, userAgentModel),
|
|
1085
|
+
void 0,
|
|
1086
|
+
projectId
|
|
1087
|
+
);
|
|
1088
|
+
if (!opResponse.ok) {
|
|
1089
|
+
return void 0;
|
|
1090
|
+
}
|
|
1091
|
+
payload = await opResponse.json();
|
|
1092
|
+
if (payload.done) {
|
|
1093
|
+
break;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
const managedProjectId = payload.response?.cloudaicompanionProject?.id;
|
|
1098
|
+
if (payload.done && managedProjectId) {
|
|
1099
|
+
return managedProjectId;
|
|
1100
|
+
}
|
|
1101
|
+
if (payload.done && projectId) {
|
|
1102
|
+
return projectId;
|
|
1103
|
+
}
|
|
1104
|
+
} catch (error45) {
|
|
1105
|
+
const errStr = error45 instanceof Error ? error45.stack || error45.message : String(error45);
|
|
1106
|
+
console.warn(`[Agy Auth] Failed to onboard Antigravity managed project: ${errStr}`);
|
|
1107
|
+
return void 0;
|
|
1388
1108
|
}
|
|
1389
|
-
return
|
|
1109
|
+
return void 0;
|
|
1390
1110
|
}
|
|
1391
|
-
function
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1111
|
+
function buildCodeAssistHeaders2(accessToken, userAgentModel) {
|
|
1112
|
+
const userAgent = buildAgyCliUserAgent(userAgentModel);
|
|
1113
|
+
return {
|
|
1114
|
+
"Content-Type": "application/json",
|
|
1115
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1116
|
+
"User-Agent": userAgent
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
async function fetchWithDebug(url2, method, headers, body, projectId) {
|
|
1120
|
+
const response = await agyFetch(url2, {
|
|
1121
|
+
method,
|
|
1122
|
+
headers,
|
|
1123
|
+
body: body ? JSON.stringify(body) : void 0
|
|
1124
|
+
});
|
|
1125
|
+
return response;
|
|
1126
|
+
}
|
|
1127
|
+
async function readResponseTextIfNeeded(response, needed) {
|
|
1128
|
+
if (!needed && response.ok) {
|
|
1129
|
+
return void 0;
|
|
1395
1130
|
}
|
|
1396
|
-
|
|
1397
|
-
return
|
|
1131
|
+
try {
|
|
1132
|
+
return await response.clone().text();
|
|
1133
|
+
} catch {
|
|
1134
|
+
return void 0;
|
|
1398
1135
|
}
|
|
1399
|
-
return false;
|
|
1400
1136
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
if (!Array.isArray(contents) || contents.length === 0) {
|
|
1411
|
-
return state;
|
|
1137
|
+
|
|
1138
|
+
// src/plugin/project/context.ts
|
|
1139
|
+
var projectContextResultCache = /* @__PURE__ */ new Map();
|
|
1140
|
+
var projectContextPendingCache = /* @__PURE__ */ new Map();
|
|
1141
|
+
function invalidateProjectContextCache(refresh) {
|
|
1142
|
+
if (!refresh) {
|
|
1143
|
+
projectContextPendingCache.clear();
|
|
1144
|
+
projectContextResultCache.clear();
|
|
1145
|
+
return;
|
|
1412
1146
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1147
|
+
projectContextPendingCache.delete(refresh);
|
|
1148
|
+
projectContextResultCache.delete(refresh);
|
|
1149
|
+
const prefix = `${refresh}|cfg:`;
|
|
1150
|
+
for (const key of projectContextPendingCache.keys()) {
|
|
1151
|
+
if (key.startsWith(prefix)) {
|
|
1152
|
+
projectContextPendingCache.delete(key);
|
|
1418
1153
|
}
|
|
1419
1154
|
}
|
|
1420
|
-
for (
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
if (role === "model" || role === "assistant") {
|
|
1424
|
-
const hasThinking = messageHasThinking(msg);
|
|
1425
|
-
const hasToolCalls = messageHasToolCalls(msg);
|
|
1426
|
-
if (i > lastRealUserIdx && state.turnStartIdx === -1) {
|
|
1427
|
-
state.turnStartIdx = i;
|
|
1428
|
-
state.turnHasThinking = hasThinking;
|
|
1429
|
-
}
|
|
1430
|
-
state.lastModelIdx = i;
|
|
1431
|
-
state.lastModelHasToolCalls = hasToolCalls;
|
|
1432
|
-
state.lastModelHasThinking = hasThinking;
|
|
1155
|
+
for (const key of projectContextResultCache.keys()) {
|
|
1156
|
+
if (key.startsWith(prefix)) {
|
|
1157
|
+
projectContextResultCache.delete(key);
|
|
1433
1158
|
}
|
|
1434
1159
|
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1160
|
+
}
|
|
1161
|
+
async function resolveProjectContextFromAccessToken(auth, accessToken, configuredProjectId, persistAuth, userAgentModel) {
|
|
1162
|
+
const parts = parseRefreshParts(auth.refresh);
|
|
1163
|
+
const configuredProject = configuredProjectId?.trim();
|
|
1164
|
+
const projectId = configuredProject || parts.projectId;
|
|
1165
|
+
if (!configuredProject && (projectId || parts.managedProjectId)) {
|
|
1166
|
+
return {
|
|
1167
|
+
auth,
|
|
1168
|
+
effectiveProjectId: projectId || parts.managedProjectId || ""
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
let loadPayload = null;
|
|
1172
|
+
try {
|
|
1173
|
+
loadPayload = await loadManagedProject(accessToken, projectId, userAgentModel);
|
|
1174
|
+
} catch (error45) {
|
|
1175
|
+
if (error45 instanceof ProjectAccessDeniedError) {
|
|
1176
|
+
throw error45;
|
|
1439
1177
|
}
|
|
1178
|
+
console.warn(`[Agy Auth] loadManagedProject returned an error for project: ${projectId || "none"}, ${error45}`);
|
|
1440
1179
|
}
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
if (msg?.role === "user") {
|
|
1448
|
-
const parts = msg.parts || [];
|
|
1449
|
-
const functionResponses = parts.filter(isFunctionResponsePart);
|
|
1450
|
-
if (functionResponses.length > 0) {
|
|
1451
|
-
count += functionResponses.length;
|
|
1452
|
-
} else {
|
|
1453
|
-
break;
|
|
1454
|
-
}
|
|
1455
|
-
} else if (msg?.role === "model" || msg?.role === "assistant") {
|
|
1456
|
-
break;
|
|
1180
|
+
if (!loadPayload) {
|
|
1181
|
+
console.warn(`[Agy Auth] loadManagedProject returned null for project: ${projectId || "none"} (possible 429 rate limit / quota exhaustion / transient backend error - not necessarily a missing project config)`);
|
|
1182
|
+
if (projectId) {
|
|
1183
|
+
throw new Error(
|
|
1184
|
+
`Failed to load project context for '${projectId}'. This may be due to a rate limit, quota exhaustion, or transient backend error. Please try again later.`
|
|
1185
|
+
);
|
|
1457
1186
|
}
|
|
1187
|
+
throw new ProjectIdRequiredError();
|
|
1458
1188
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
syntheticModelContent = "[Processing prev ctx.]";
|
|
1467
|
-
} else if (toolResultCount === 1) {
|
|
1468
|
-
syntheticModelContent = "[Tool exec completed.]";
|
|
1469
|
-
} else {
|
|
1470
|
-
syntheticModelContent = `[${toolResultCount} tool executions completed.]`;
|
|
1189
|
+
const managedProjectId = normalizeProjectId(loadPayload.cloudaicompanionProject);
|
|
1190
|
+
if (managedProjectId) {
|
|
1191
|
+
const updatedAuth = withProjectAuth(auth, parts.refreshToken, projectId, managedProjectId);
|
|
1192
|
+
if (persistAuth) {
|
|
1193
|
+
await persistAuth(updatedAuth);
|
|
1194
|
+
}
|
|
1195
|
+
return { auth: updatedAuth, effectiveProjectId: managedProjectId };
|
|
1471
1196
|
}
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1197
|
+
const currentTierId = loadPayload.currentTier?.id;
|
|
1198
|
+
if (!currentTierId) {
|
|
1199
|
+
throwIfValidationRequired(loadPayload.ineligibleTiers);
|
|
1200
|
+
}
|
|
1201
|
+
if (currentTierId) {
|
|
1202
|
+
if (projectId) {
|
|
1203
|
+
return { auth, effectiveProjectId: projectId };
|
|
1204
|
+
}
|
|
1205
|
+
const ineligibleMessage = buildIneligibleTierMessage(loadPayload.ineligibleTiers);
|
|
1206
|
+
if (ineligibleMessage) {
|
|
1207
|
+
throw new Error(ineligibleMessage);
|
|
1208
|
+
}
|
|
1209
|
+
throw new ProjectIdRequiredError();
|
|
1210
|
+
}
|
|
1211
|
+
const tier = pickOnboardTier(loadPayload.allowedTiers);
|
|
1212
|
+
const tierId = tier.id ?? LEGACY_TIER_ID;
|
|
1213
|
+
if (tierId !== FREE_TIER_ID && !projectId) {
|
|
1214
|
+
throw new ProjectIdRequiredError();
|
|
1215
|
+
}
|
|
1216
|
+
const onboardedProjectId = await onboardManagedProject(
|
|
1217
|
+
accessToken,
|
|
1218
|
+
tierId,
|
|
1219
|
+
projectId,
|
|
1220
|
+
userAgentModel
|
|
1221
|
+
);
|
|
1222
|
+
if (onboardedProjectId) {
|
|
1223
|
+
const updatedAuth = withProjectAuth(auth, parts.refreshToken, projectId, onboardedProjectId);
|
|
1224
|
+
if (persistAuth) {
|
|
1225
|
+
await persistAuth(updatedAuth);
|
|
1226
|
+
}
|
|
1227
|
+
return { auth: updatedAuth, effectiveProjectId: onboardedProjectId };
|
|
1228
|
+
}
|
|
1229
|
+
if (projectId) {
|
|
1230
|
+
return { auth, effectiveProjectId: projectId };
|
|
1231
|
+
}
|
|
1232
|
+
console.warn(`[Agy Auth] onboardManagedProject failed to resolve a project ID for tier: ${tierId}, configured project: ${projectId || "none"}`);
|
|
1233
|
+
throw new ProjectIdRequiredError();
|
|
1481
1234
|
}
|
|
1482
|
-
function
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
sentBuffer.set(index, fullText);
|
|
1507
|
-
if (delta) {
|
|
1508
|
-
return { ...p, text: delta, thinking: delta };
|
|
1509
|
-
}
|
|
1510
|
-
return null;
|
|
1511
|
-
}
|
|
1512
|
-
sentBuffer.set(index, fullText);
|
|
1513
|
-
return part;
|
|
1514
|
-
}
|
|
1515
|
-
return part;
|
|
1235
|
+
async function ensureProjectContext(auth, client, configuredProjectId, userAgentModel) {
|
|
1236
|
+
const accessToken = auth.access;
|
|
1237
|
+
if (!accessToken) {
|
|
1238
|
+
return { auth, effectiveProjectId: "" };
|
|
1239
|
+
}
|
|
1240
|
+
const cacheKey = buildProjectCacheKey(auth, configuredProjectId);
|
|
1241
|
+
if (cacheKey) {
|
|
1242
|
+
const cached2 = projectContextResultCache.get(cacheKey);
|
|
1243
|
+
if (cached2) {
|
|
1244
|
+
return cached2;
|
|
1245
|
+
}
|
|
1246
|
+
const pending = projectContextPendingCache.get(cacheKey);
|
|
1247
|
+
if (pending) {
|
|
1248
|
+
return pending;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
const resolveContext = async () => resolveProjectContextFromAccessToken(
|
|
1252
|
+
auth,
|
|
1253
|
+
accessToken,
|
|
1254
|
+
configuredProjectId,
|
|
1255
|
+
async (updatedAuth) => {
|
|
1256
|
+
await client.auth.set({
|
|
1257
|
+
path: { id: AGY_PROVIDER_ID },
|
|
1258
|
+
body: updatedAuth
|
|
1516
1259
|
});
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
});
|
|
1523
|
-
return { ...resp, candidates: newCandidates };
|
|
1260
|
+
},
|
|
1261
|
+
userAgentModel
|
|
1262
|
+
);
|
|
1263
|
+
if (!cacheKey) {
|
|
1264
|
+
return resolveContext();
|
|
1524
1265
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
if (displayedThinkingHashes) {
|
|
1532
|
-
const hash2 = hashString(fullText);
|
|
1533
|
-
if (displayedThinkingHashes.has(hash2)) {
|
|
1534
|
-
sentBuffer.set(thinkingIndex, fullText);
|
|
1535
|
-
thinkingIndex++;
|
|
1536
|
-
return null;
|
|
1537
|
-
}
|
|
1538
|
-
displayedThinkingHashes.add(hash2);
|
|
1539
|
-
}
|
|
1540
|
-
const sentText = sentBuffer.get(thinkingIndex) ?? "";
|
|
1541
|
-
if (fullText.startsWith(sentText)) {
|
|
1542
|
-
const delta = fullText.slice(sentText.length);
|
|
1543
|
-
sentBuffer.set(thinkingIndex, fullText);
|
|
1544
|
-
thinkingIndex++;
|
|
1545
|
-
if (delta) {
|
|
1546
|
-
return { ...b, thinking: delta, text: delta };
|
|
1547
|
-
}
|
|
1548
|
-
return null;
|
|
1549
|
-
}
|
|
1550
|
-
sentBuffer.set(thinkingIndex, fullText);
|
|
1551
|
-
thinkingIndex++;
|
|
1552
|
-
return block;
|
|
1553
|
-
}
|
|
1554
|
-
return block;
|
|
1555
|
-
});
|
|
1556
|
-
const filteredContent = newContent.filter((b) => b !== null);
|
|
1557
|
-
if (filteredContent.length === 0) {
|
|
1558
|
-
return { ...resp, content: [] };
|
|
1266
|
+
const promise2 = resolveContext().then((result) => {
|
|
1267
|
+
const nextKey = getCacheKey(result.auth) ?? cacheKey;
|
|
1268
|
+
projectContextPendingCache.delete(cacheKey);
|
|
1269
|
+
projectContextResultCache.set(nextKey, result);
|
|
1270
|
+
if (nextKey !== cacheKey) {
|
|
1271
|
+
projectContextResultCache.delete(cacheKey);
|
|
1559
1272
|
}
|
|
1560
|
-
return
|
|
1273
|
+
return result;
|
|
1274
|
+
}).catch((error45) => {
|
|
1275
|
+
projectContextPendingCache.delete(cacheKey);
|
|
1276
|
+
throw error45;
|
|
1277
|
+
});
|
|
1278
|
+
projectContextPendingCache.set(cacheKey, promise2);
|
|
1279
|
+
return promise2;
|
|
1280
|
+
}
|
|
1281
|
+
function withProjectAuth(auth, refreshToken, projectId, managedProjectId) {
|
|
1282
|
+
return {
|
|
1283
|
+
...auth,
|
|
1284
|
+
refresh: formatRefreshParts({
|
|
1285
|
+
refreshToken,
|
|
1286
|
+
projectId,
|
|
1287
|
+
managedProjectId
|
|
1288
|
+
})
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
function buildProjectCacheKey(auth, configuredProjectId) {
|
|
1292
|
+
const base = getCacheKey(auth);
|
|
1293
|
+
if (!base) {
|
|
1294
|
+
return void 0;
|
|
1561
1295
|
}
|
|
1562
|
-
|
|
1296
|
+
const project = configuredProjectId?.trim() ?? "";
|
|
1297
|
+
return project ? `${base}|cfg:${project}` : base;
|
|
1563
1298
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
content.parts.forEach((part) => {
|
|
1574
|
-
const p = part;
|
|
1575
|
-
if (p.thought === true || p.type === "thinking") {
|
|
1576
|
-
const text = p.text || p.thinking || "";
|
|
1577
|
-
if (text) {
|
|
1578
|
-
const current = thoughtBuffer.get(index) ?? "";
|
|
1579
|
-
thoughtBuffer.set(index, current + text);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
if (p.thoughtSignature) {
|
|
1583
|
-
const fullText = thoughtBuffer.get(index) ?? "";
|
|
1584
|
-
if (fullText) {
|
|
1585
|
-
const signature = p.thoughtSignature;
|
|
1586
|
-
onCacheSignature?.(signatureSessionKey, fullText, signature);
|
|
1587
|
-
signatureStore.set(signatureSessionKey, { text: fullText, signature });
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
});
|
|
1591
|
-
});
|
|
1299
|
+
|
|
1300
|
+
// src/plugin/provider.ts
|
|
1301
|
+
function resolveConfiguredProjectId(input = {}) {
|
|
1302
|
+
const env = input.env ?? process.env;
|
|
1303
|
+
return normalizeProjectId2(env.OPENCODE_AGY_PROJECT_ID) ?? resolveConfiguredProjectIdFromProvider(input.provider) ?? normalizeProjectId2(input.configProjectId) ?? resolveConfiguredProjectIdFromConfig(input.config) ?? normalizeProjectId2(env.GOOGLE_CLOUD_PROJECT) ?? normalizeProjectId2(env.GOOGLE_CLOUD_PROJECT_ID);
|
|
1304
|
+
}
|
|
1305
|
+
function resolveConfiguredProjectIdFromProvider(provider) {
|
|
1306
|
+
if (!provider || typeof provider !== "object") {
|
|
1307
|
+
return void 0;
|
|
1592
1308
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1309
|
+
return normalizeProjectId2(provider.options?.projectId);
|
|
1310
|
+
}
|
|
1311
|
+
function resolveConfiguredProjectIdFromConfig(config2) {
|
|
1312
|
+
if (!config2?.provider || typeof config2.provider !== "object") {
|
|
1313
|
+
return void 0;
|
|
1314
|
+
}
|
|
1315
|
+
const providerConfig = config2.provider[AGY_PROVIDER_ID];
|
|
1316
|
+
return normalizeProjectId2(providerConfig?.options?.projectId);
|
|
1317
|
+
}
|
|
1318
|
+
async function resolveConfiguredProjectIdFromClient(client) {
|
|
1319
|
+
if (!client?.config?.get) {
|
|
1320
|
+
return void 0;
|
|
1321
|
+
}
|
|
1322
|
+
try {
|
|
1323
|
+
const result = await client.config.get();
|
|
1324
|
+
return resolveConfiguredProjectIdFromConfig(result?.data);
|
|
1325
|
+
} catch {
|
|
1326
|
+
return void 0;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
function normalizeProjectId2(value) {
|
|
1330
|
+
if (typeof value !== "string") {
|
|
1331
|
+
return void 0;
|
|
1332
|
+
}
|
|
1333
|
+
const trimmed = value.trim();
|
|
1334
|
+
return trimmed || void 0;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// src/plugin/oauth-authorize.ts
|
|
1338
|
+
function createOAuthAuthorizeMethod(options) {
|
|
1339
|
+
return async () => {
|
|
1340
|
+
const maybeHydrateProjectId = async (result) => {
|
|
1341
|
+
if (result.type !== "success" || !result.access) {
|
|
1342
|
+
return result;
|
|
1343
|
+
}
|
|
1344
|
+
const configuredProjectId = resolveConfiguredProjectId({
|
|
1345
|
+
configProjectId: await options?.getConfiguredProjectId?.()
|
|
1346
|
+
});
|
|
1347
|
+
try {
|
|
1348
|
+
const initialRefresh = formatRefreshParts({
|
|
1349
|
+
refreshToken: result.refresh
|
|
1350
|
+
});
|
|
1351
|
+
const authSnapshot = {
|
|
1352
|
+
type: "oauth",
|
|
1353
|
+
refresh: initialRefresh,
|
|
1354
|
+
access: result.access,
|
|
1355
|
+
expires: result.expires
|
|
1356
|
+
};
|
|
1357
|
+
const projectContext = await resolveProjectContextFromAccessToken(
|
|
1358
|
+
authSnapshot,
|
|
1359
|
+
result.access,
|
|
1360
|
+
configuredProjectId,
|
|
1361
|
+
void 0,
|
|
1362
|
+
await options?.getUserAgentModel?.()
|
|
1363
|
+
);
|
|
1364
|
+
return projectContext.auth.refresh !== initialRefresh ? { ...result, refresh: projectContext.auth.refresh } : { ...result, refresh: initialRefresh };
|
|
1365
|
+
} catch (error45) {
|
|
1366
|
+
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
1367
|
+
console.warn(`[OAuth] Project resolution skipped: ${message}`);
|
|
1368
|
+
if (options?.client?.tui?.showToast) {
|
|
1369
|
+
const message2 = error45 instanceof Error ? error45.message : String(error45);
|
|
1370
|
+
options.client.tui.showToast({
|
|
1371
|
+
body: {
|
|
1372
|
+
title: "Failed to bind project context",
|
|
1373
|
+
message: `Authorized successfully but failed to bind project, real models will be unavailable: ${message2}`,
|
|
1374
|
+
variant: "warning",
|
|
1375
|
+
duration: 15e3
|
|
1376
|
+
}
|
|
1377
|
+
}).catch(() => {
|
|
1378
|
+
});
|
|
1602
1379
|
}
|
|
1380
|
+
const initialRefresh = formatRefreshParts({
|
|
1381
|
+
refreshToken: result.refresh
|
|
1382
|
+
});
|
|
1383
|
+
return { ...result, refresh: initialRefresh };
|
|
1603
1384
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1385
|
+
};
|
|
1386
|
+
const isHeadless = !!(process.env.SSH_CONNECTION || process.env.SSH_CLIENT || process.env.SSH_TTY || process.env.OPENCODE_HEADLESS);
|
|
1387
|
+
const authorization = await authorizeAgy();
|
|
1388
|
+
if (!isHeadless) {
|
|
1389
|
+
openBrowserUrl(authorization.url);
|
|
1390
|
+
}
|
|
1391
|
+
return {
|
|
1392
|
+
url: authorization.url,
|
|
1393
|
+
instructions: "Please complete Google account authorization in your browser. After authorization, the page will redirect to https://antigravity.google/oauth-callback?code=... . Please copy the full redirect URL from your browser address bar, or just the code parameter value, and paste it into the input box below:",
|
|
1394
|
+
method: "code",
|
|
1395
|
+
callback: async (callbackUrl) => {
|
|
1396
|
+
try {
|
|
1397
|
+
const { code, state } = parseOAuthCallbackInput(callbackUrl);
|
|
1398
|
+
if (!code) {
|
|
1399
|
+
return { type: "failed", error: "Missing authorization code in callback input" };
|
|
1400
|
+
}
|
|
1401
|
+
if (state && state !== authorization.state) {
|
|
1402
|
+
return { type: "failed", error: "State mismatch in callback input (possible CSRF attempt)" };
|
|
1403
|
+
}
|
|
1404
|
+
const exchangeResult = await exchangeAgyWithVerifier(code, authorization.verifier);
|
|
1405
|
+
return await maybeHydrateProjectId(exchangeResult);
|
|
1406
|
+
} catch (error45) {
|
|
1407
|
+
return {
|
|
1408
|
+
type: "failed",
|
|
1409
|
+
error: error45 instanceof Error ? error45.message : "Unknown error"
|
|
1410
|
+
};
|
|
1610
1411
|
}
|
|
1611
1412
|
}
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1413
|
+
};
|
|
1414
|
+
};
|
|
1614
1415
|
}
|
|
1615
|
-
function
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
for (const line of lines) {
|
|
1620
|
-
if (line.startsWith("data:")) {
|
|
1621
|
-
isDataEvent = true;
|
|
1622
|
-
dataLines.push(line.slice(5).trim());
|
|
1623
|
-
}
|
|
1416
|
+
function parseOAuthCallbackInput(input) {
|
|
1417
|
+
const trimmed = input.trim();
|
|
1418
|
+
if (!trimmed) {
|
|
1419
|
+
return {};
|
|
1624
1420
|
}
|
|
1625
|
-
if (
|
|
1626
|
-
|
|
1421
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
1422
|
+
try {
|
|
1423
|
+
const url2 = new URL(trimmed);
|
|
1424
|
+
return {
|
|
1425
|
+
code: url2.searchParams.get("code") || void 0,
|
|
1426
|
+
state: url2.searchParams.get("state") || void 0
|
|
1427
|
+
};
|
|
1428
|
+
} catch {
|
|
1429
|
+
return {};
|
|
1430
|
+
}
|
|
1627
1431
|
}
|
|
1628
|
-
const
|
|
1629
|
-
if (
|
|
1630
|
-
|
|
1432
|
+
const candidate = trimmed.startsWith("?") ? trimmed.slice(1) : trimmed;
|
|
1433
|
+
if (candidate.includes("=")) {
|
|
1434
|
+
const params = new URLSearchParams(candidate);
|
|
1435
|
+
const code = params.get("code") || void 0;
|
|
1436
|
+
const state = params.get("state") || void 0;
|
|
1437
|
+
if (code || state) {
|
|
1438
|
+
return { code, state };
|
|
1439
|
+
}
|
|
1631
1440
|
}
|
|
1441
|
+
return { code: trimmed };
|
|
1442
|
+
}
|
|
1443
|
+
function openBrowserUrl(url2) {
|
|
1632
1444
|
try {
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
);
|
|
1643
|
-
}
|
|
1644
|
-
let response = deduplicateThinkingText(
|
|
1645
|
-
parsed.response,
|
|
1646
|
-
sentThinkingBuffer,
|
|
1647
|
-
options.displayedThinkingHashes
|
|
1648
|
-
);
|
|
1649
|
-
if (options.debugText && callbacks.onInjectDebug && !debugState.injected) {
|
|
1650
|
-
response = callbacks.onInjectDebug(response, options.debugText);
|
|
1651
|
-
debugState.injected = true;
|
|
1652
|
-
}
|
|
1653
|
-
const transformed = callbacks.transformThinkingParts ? callbacks.transformThinkingParts(response) : response;
|
|
1654
|
-
return `data: ${JSON.stringify(transformed)}`;
|
|
1655
|
-
}
|
|
1656
|
-
} catch (_) {
|
|
1445
|
+
const platform2 = process.platform;
|
|
1446
|
+
const command = platform2 === "darwin" ? "open" : platform2 === "win32" ? "rundll32" : "xdg-open";
|
|
1447
|
+
const args = platform2 === "win32" ? ["url.dll,FileProtocolHandler", url2] : [url2];
|
|
1448
|
+
const child = spawn(command, args, {
|
|
1449
|
+
stdio: "ignore",
|
|
1450
|
+
detached: true
|
|
1451
|
+
});
|
|
1452
|
+
child.unref?.();
|
|
1453
|
+
} catch {
|
|
1657
1454
|
}
|
|
1658
|
-
return eventText;
|
|
1659
1455
|
}
|
|
1660
|
-
function createStreamingTransformer(signatureStore, callbacks, options = {}) {
|
|
1661
|
-
const decoder2 = new TextDecoder();
|
|
1662
|
-
const encoder2 = new TextEncoder();
|
|
1663
|
-
let buffer = "";
|
|
1664
|
-
const thoughtBuffer = createThoughtBuffer();
|
|
1665
|
-
const sentThinkingBuffer = createThoughtBuffer();
|
|
1666
|
-
const debugState = { injected: false };
|
|
1667
|
-
let hasSeenUsageMetadata = false;
|
|
1668
|
-
let streamHasThinking = false;
|
|
1669
|
-
let streamHasToolCalls = false;
|
|
1670
|
-
const displayedThinkingHashes = options.displayedThinkingHashes ?? /* @__PURE__ */ new Set();
|
|
1671
|
-
const mergedOptions = { ...options, displayedThinkingHashes };
|
|
1672
|
-
return new TransformStream({
|
|
1673
|
-
transform(chunk, controller) {
|
|
1674
|
-
buffer += decoder2.decode(chunk, { stream: true });
|
|
1675
|
-
const events = buffer.split(/\r?\n\r?\n/);
|
|
1676
|
-
buffer = events.pop() || "";
|
|
1677
|
-
for (const event of events) {
|
|
1678
|
-
if (!event.trim()) continue;
|
|
1679
|
-
if (event.includes("usageMetadata")) {
|
|
1680
|
-
hasSeenUsageMetadata = true;
|
|
1681
|
-
}
|
|
1682
|
-
if (!streamHasThinking) {
|
|
1683
|
-
streamHasThinking = event.includes('"thought":true') || event.includes('"type":"thinking"');
|
|
1684
|
-
}
|
|
1685
|
-
if (!streamHasToolCalls) {
|
|
1686
|
-
streamHasToolCalls = event.includes('"functionCall"');
|
|
1687
|
-
}
|
|
1688
|
-
const transformedEvent = transformSseEvent(
|
|
1689
|
-
event,
|
|
1690
|
-
signatureStore,
|
|
1691
|
-
thoughtBuffer,
|
|
1692
|
-
sentThinkingBuffer,
|
|
1693
|
-
callbacks,
|
|
1694
|
-
mergedOptions,
|
|
1695
|
-
debugState
|
|
1696
|
-
);
|
|
1697
|
-
controller.enqueue(encoder2.encode(transformedEvent + "\n\n"));
|
|
1698
|
-
}
|
|
1699
|
-
},
|
|
1700
|
-
flush(controller) {
|
|
1701
|
-
buffer += decoder2.decode();
|
|
1702
|
-
if (buffer.trim()) {
|
|
1703
|
-
if (buffer.includes("usageMetadata")) {
|
|
1704
|
-
hasSeenUsageMetadata = true;
|
|
1705
|
-
}
|
|
1706
|
-
if (!streamHasThinking) {
|
|
1707
|
-
streamHasThinking = buffer.includes('"thought":true') || buffer.includes('"type":"thinking"');
|
|
1708
|
-
}
|
|
1709
|
-
if (!streamHasToolCalls) {
|
|
1710
|
-
streamHasToolCalls = buffer.includes('"functionCall"');
|
|
1711
|
-
}
|
|
1712
|
-
const transformedEvent = transformSseEvent(
|
|
1713
|
-
buffer,
|
|
1714
|
-
signatureStore,
|
|
1715
|
-
thoughtBuffer,
|
|
1716
|
-
sentThinkingBuffer,
|
|
1717
|
-
callbacks,
|
|
1718
|
-
mergedOptions,
|
|
1719
|
-
debugState
|
|
1720
|
-
);
|
|
1721
|
-
controller.enqueue(encoder2.encode(transformedEvent + "\n\n"));
|
|
1722
|
-
}
|
|
1723
|
-
if (!hasSeenUsageMetadata) {
|
|
1724
|
-
const syntheticUsage = {
|
|
1725
|
-
candidates: [
|
|
1726
|
-
{
|
|
1727
|
-
finishReason: "STOP"
|
|
1728
|
-
}
|
|
1729
|
-
],
|
|
1730
|
-
usageMetadata: {
|
|
1731
|
-
promptTokenCount: 0,
|
|
1732
|
-
candidatesTokenCount: 0,
|
|
1733
|
-
totalTokenCount: 0
|
|
1734
|
-
}
|
|
1735
|
-
};
|
|
1736
|
-
controller.enqueue(encoder2.encode(`data: ${JSON.stringify(syntheticUsage)}
|
|
1737
1456
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
if (callbacks.onTurnStateUpdate && options.signatureSessionKey) {
|
|
1741
|
-
callbacks.onTurnStateUpdate(options.signatureSessionKey, {
|
|
1742
|
-
turnHasThinking: streamHasThinking,
|
|
1743
|
-
lastModelHasToolCalls: streamHasToolCalls
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
});
|
|
1748
|
-
}
|
|
1457
|
+
// src/plugin/cache.ts
|
|
1458
|
+
import { createHash } from "crypto";
|
|
1749
1459
|
|
|
1750
|
-
// src/sdk/
|
|
1751
|
-
|
|
1460
|
+
// src/sdk/cache/signature-cache.ts
|
|
1461
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2, appendFileSync } from "fs";
|
|
1462
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
1463
|
+
import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
|
|
1752
1464
|
function getConfigDir2() {
|
|
1753
1465
|
const platform2 = process.platform;
|
|
1754
1466
|
if (platform2 === "win32") {
|
|
@@ -1757,458 +1469,852 @@ function getConfigDir2() {
|
|
|
1757
1469
|
const xdgConfig = process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config");
|
|
1758
1470
|
return join2(xdgConfig, "opencode");
|
|
1759
1471
|
}
|
|
1760
|
-
function
|
|
1761
|
-
return join2(getConfigDir2(), "antigravity-
|
|
1762
|
-
}
|
|
1763
|
-
function loadTurnStatesFromDisk() {
|
|
1764
|
-
const result = /* @__PURE__ */ new Map();
|
|
1765
|
-
try {
|
|
1766
|
-
const filePath = getTurnStateFilePath();
|
|
1767
|
-
if (!existsSync2(filePath)) {
|
|
1768
|
-
return result;
|
|
1769
|
-
}
|
|
1770
|
-
const content = readFileSync2(filePath, "utf-8");
|
|
1771
|
-
const data = JSON.parse(content);
|
|
1772
|
-
if (data.version !== "1.0") {
|
|
1773
|
-
return result;
|
|
1774
|
-
}
|
|
1775
|
-
const now = Date.now();
|
|
1776
|
-
const maxAge = 24 * 60 * 60 * 1e3;
|
|
1777
|
-
for (const [key, record2] of Object.entries(data.entries)) {
|
|
1778
|
-
if (record2.state && typeof record2.state === "object" && now - record2.updatedAt < maxAge) {
|
|
1779
|
-
result.set(key, record2);
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
} catch {
|
|
1783
|
-
}
|
|
1784
|
-
return result;
|
|
1472
|
+
function getCacheFilePath() {
|
|
1473
|
+
return join2(getConfigDir2(), "antigravity-signature-cache.json");
|
|
1785
1474
|
}
|
|
1786
|
-
function
|
|
1475
|
+
function ensureGitignoreSync(configDir) {
|
|
1476
|
+
const gitignorePath = join2(configDir, ".gitignore");
|
|
1477
|
+
const entries = [".gitignore", "antigravity-signature-cache.json"];
|
|
1787
1478
|
try {
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
mkdirSync2(dir, { recursive: true });
|
|
1792
|
-
}
|
|
1793
|
-
const now = Date.now();
|
|
1794
|
-
const maxAge = 24 * 60 * 60 * 1e3;
|
|
1795
|
-
const serializable = {};
|
|
1796
|
-
for (const [key, record2] of entries.entries()) {
|
|
1797
|
-
if (now - record2.updatedAt < maxAge) {
|
|
1798
|
-
serializable[key] = record2;
|
|
1799
|
-
}
|
|
1479
|
+
let content = "";
|
|
1480
|
+
if (existsSync2(gitignorePath)) {
|
|
1481
|
+
content = readFileSync2(gitignorePath, "utf-8");
|
|
1800
1482
|
}
|
|
1801
|
-
const
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
renameSync2(tmpPath, filePath);
|
|
1810
|
-
} catch {
|
|
1811
|
-
writeFileSync2(filePath, readFileSync2(tmpPath));
|
|
1812
|
-
try {
|
|
1813
|
-
unlinkSync2(tmpPath);
|
|
1814
|
-
} catch {
|
|
1815
|
-
}
|
|
1483
|
+
const existingLines = content.split("\n").map((line) => line.trim());
|
|
1484
|
+
const missing = entries.filter((e) => !existingLines.includes(e));
|
|
1485
|
+
if (missing.length === 0) return;
|
|
1486
|
+
if (content === "") {
|
|
1487
|
+
writeFileSync2(gitignorePath, missing.join("\n") + "\n", "utf-8");
|
|
1488
|
+
} else {
|
|
1489
|
+
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
1490
|
+
appendFileSync(gitignorePath, suffix + missing.join("\n") + "\n", "utf-8");
|
|
1816
1491
|
}
|
|
1817
|
-
return true;
|
|
1818
1492
|
} catch {
|
|
1819
|
-
return false;
|
|
1820
1493
|
}
|
|
1821
1494
|
}
|
|
1822
|
-
var
|
|
1823
|
-
|
|
1495
|
+
var SignatureCache = class {
|
|
1496
|
+
// Memory cache map
|
|
1497
|
+
cache = /* @__PURE__ */ new Map();
|
|
1498
|
+
// Configuration options
|
|
1499
|
+
memoryTtlMs;
|
|
1500
|
+
diskTtlMs;
|
|
1501
|
+
writeIntervalMs;
|
|
1502
|
+
cacheFilePath;
|
|
1503
|
+
enabled;
|
|
1504
|
+
// State variables
|
|
1824
1505
|
dirty = false;
|
|
1825
|
-
lastWriteTime = 0;
|
|
1826
1506
|
writeTimer = null;
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1507
|
+
cleanupTimer = null;
|
|
1508
|
+
// Statistical metrics
|
|
1509
|
+
stats = {
|
|
1510
|
+
memoryHits: 0,
|
|
1511
|
+
diskHits: 0,
|
|
1512
|
+
misses: 0,
|
|
1513
|
+
writes: 0
|
|
1514
|
+
};
|
|
1515
|
+
constructor(config2) {
|
|
1516
|
+
this.enabled = config2.enabled;
|
|
1517
|
+
this.memoryTtlMs = config2.memory_ttl_seconds * 1e3;
|
|
1518
|
+
this.diskTtlMs = config2.disk_ttl_seconds * 1e3;
|
|
1519
|
+
this.writeIntervalMs = config2.write_interval_seconds * 1e3;
|
|
1520
|
+
this.cacheFilePath = getCacheFilePath();
|
|
1521
|
+
if (this.enabled) {
|
|
1522
|
+
this.loadFromDisk();
|
|
1523
|
+
this.startBackgroundTasks();
|
|
1832
1524
|
}
|
|
1833
1525
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1526
|
+
// ===========================================================================
|
|
1527
|
+
// Public Signature API
|
|
1528
|
+
// ===========================================================================
|
|
1529
|
+
/**
|
|
1530
|
+
* Generates a unique cache key based on session ID and model ID
|
|
1531
|
+
*/
|
|
1532
|
+
static makeKey(sessionId, modelId) {
|
|
1533
|
+
return `${sessionId}:${modelId}`;
|
|
1838
1534
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1535
|
+
/**
|
|
1536
|
+
* Stores a signature in cache (marks as dirty, awaits background disk write)
|
|
1537
|
+
*/
|
|
1538
|
+
store(key, signature) {
|
|
1539
|
+
if (!this.enabled) return;
|
|
1540
|
+
this.cache.set(key, {
|
|
1541
|
+
value: signature,
|
|
1542
|
+
timestamp: Date.now()
|
|
1543
|
+
});
|
|
1544
|
+
this.dirty = true;
|
|
1843
1545
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1546
|
+
/**
|
|
1547
|
+
* Retrieves a signature from cache and updates hit stats
|
|
1548
|
+
* Returns null if expired or missing
|
|
1549
|
+
*/
|
|
1550
|
+
retrieve(key) {
|
|
1551
|
+
if (!this.enabled) return null;
|
|
1552
|
+
const entry = this.cache.get(key);
|
|
1553
|
+
if (entry) {
|
|
1554
|
+
const age = Date.now() - entry.timestamp;
|
|
1555
|
+
if (age <= this.memoryTtlMs) {
|
|
1556
|
+
this.stats.memoryHits++;
|
|
1557
|
+
return entry.value;
|
|
1558
|
+
}
|
|
1559
|
+
this.cache.delete(key);
|
|
1560
|
+
}
|
|
1561
|
+
this.stats.misses++;
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Checks if a key is valid and unexpired in cache (without affecting stats)
|
|
1566
|
+
*/
|
|
1567
|
+
has(key) {
|
|
1568
|
+
if (!this.enabled) return false;
|
|
1569
|
+
const entry = this.cache.get(key);
|
|
1570
|
+
if (!entry) return false;
|
|
1571
|
+
const age = Date.now() - entry.timestamp;
|
|
1572
|
+
return age <= this.memoryTtlMs;
|
|
1573
|
+
}
|
|
1574
|
+
// ===========================================================================
|
|
1575
|
+
// Full Thinking Cache API
|
|
1576
|
+
// ===========================================================================
|
|
1577
|
+
/**
|
|
1578
|
+
* Caches the full thought chain text content and signature
|
|
1579
|
+
* Allows self-healing and recovery of historical thought blocks even if the context is subsequently compressed.
|
|
1580
|
+
*/
|
|
1581
|
+
storeThinking(key, thinkingText, signature, toolIds) {
|
|
1582
|
+
if (!this.enabled || !thinkingText || !signature) return;
|
|
1583
|
+
this.cache.set(key, {
|
|
1584
|
+
value: signature,
|
|
1585
|
+
timestamp: Date.now(),
|
|
1586
|
+
thinkingText,
|
|
1587
|
+
textPreview: thinkingText.slice(0, 100),
|
|
1588
|
+
toolIds
|
|
1589
|
+
});
|
|
1846
1590
|
this.dirty = true;
|
|
1847
|
-
this.scheduleThrottledWrite();
|
|
1848
1591
|
}
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1592
|
+
/**
|
|
1593
|
+
* Extracts full thought chain info from cache
|
|
1594
|
+
*/
|
|
1595
|
+
retrieveThinking(key) {
|
|
1596
|
+
if (!this.enabled) return null;
|
|
1597
|
+
const entry = this.cache.get(key);
|
|
1598
|
+
if (!entry || !entry.thinkingText) return null;
|
|
1599
|
+
const age = Date.now() - entry.timestamp;
|
|
1600
|
+
if (age > this.memoryTtlMs) {
|
|
1601
|
+
this.cache.delete(key);
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
this.stats.memoryHits++;
|
|
1605
|
+
return {
|
|
1606
|
+
text: entry.thinkingText,
|
|
1607
|
+
signature: entry.value,
|
|
1608
|
+
toolIds: entry.toolIds
|
|
1856
1609
|
};
|
|
1857
|
-
this.entries.set(sessionId, { state: turnState, updatedAt: Date.now() });
|
|
1858
|
-
this.dirty = true;
|
|
1859
|
-
this.scheduleThrottledWrite();
|
|
1860
|
-
return turnState;
|
|
1861
1610
|
}
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1611
|
+
/**
|
|
1612
|
+
* Checks if full thought chain content exists for a key
|
|
1613
|
+
*/
|
|
1614
|
+
hasThinking(key) {
|
|
1615
|
+
if (!this.enabled) return false;
|
|
1616
|
+
const entry = this.cache.get(key);
|
|
1617
|
+
if (!entry || !entry.thinkingText) return false;
|
|
1618
|
+
const age = Date.now() - entry.timestamp;
|
|
1619
|
+
return age <= this.memoryTtlMs;
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Gets current cache stats and memory footprint
|
|
1623
|
+
*/
|
|
1624
|
+
getStats() {
|
|
1625
|
+
return {
|
|
1626
|
+
...this.stats,
|
|
1627
|
+
memoryEntries: this.cache.size,
|
|
1628
|
+
dirty: this.dirty,
|
|
1629
|
+
diskEnabled: this.enabled
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Manually triggers immediate save to disk
|
|
1634
|
+
*/
|
|
1635
|
+
async flush() {
|
|
1636
|
+
if (!this.enabled) return true;
|
|
1637
|
+
return this.saveToDisk();
|
|
1866
1638
|
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Graceful shutdown: stops all timers and flushes unsaved data to disk
|
|
1641
|
+
*/
|
|
1867
1642
|
shutdown() {
|
|
1868
|
-
this.
|
|
1869
|
-
|
|
1870
|
-
|
|
1643
|
+
if (this.writeTimer) {
|
|
1644
|
+
clearInterval(this.writeTimer);
|
|
1645
|
+
this.writeTimer = null;
|
|
1646
|
+
}
|
|
1647
|
+
if (this.cleanupTimer) {
|
|
1648
|
+
clearInterval(this.cleanupTimer);
|
|
1649
|
+
this.cleanupTimer = null;
|
|
1650
|
+
}
|
|
1651
|
+
if (this.dirty && this.enabled) {
|
|
1652
|
+
this.saveToDisk();
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
// ===========================================================================
|
|
1656
|
+
// Disk Operations
|
|
1657
|
+
// ===========================================================================
|
|
1658
|
+
/**
|
|
1659
|
+
* Loads signature cache from disk and validates TTL state
|
|
1660
|
+
*/
|
|
1661
|
+
loadFromDisk() {
|
|
1662
|
+
try {
|
|
1663
|
+
if (!existsSync2(this.cacheFilePath)) {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const content = readFileSync2(this.cacheFilePath, "utf-8");
|
|
1667
|
+
const data = JSON.parse(content);
|
|
1668
|
+
if (data.version !== "1.0") {
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
const now = Date.now();
|
|
1672
|
+
for (const [key, entry] of Object.entries(data.entries)) {
|
|
1673
|
+
const age = now - entry.timestamp;
|
|
1674
|
+
if (age <= this.diskTtlMs) {
|
|
1675
|
+
this.cache.set(key, {
|
|
1676
|
+
value: entry.value,
|
|
1677
|
+
timestamp: entry.timestamp,
|
|
1678
|
+
thinkingText: entry.thinkingText,
|
|
1679
|
+
textPreview: entry.textPreview,
|
|
1680
|
+
toolIds: entry.toolIds
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
} catch {
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Synchronously saves memory cache to disk (using atomic write: temp file then rename)
|
|
1689
|
+
* Merges with existing unexpired entries on disk during write
|
|
1690
|
+
*/
|
|
1691
|
+
saveToDisk() {
|
|
1692
|
+
try {
|
|
1693
|
+
const dir = dirname2(this.cacheFilePath);
|
|
1694
|
+
if (!existsSync2(dir)) {
|
|
1695
|
+
mkdirSync2(dir, { recursive: true });
|
|
1696
|
+
}
|
|
1697
|
+
ensureGitignoreSync(dir);
|
|
1698
|
+
const now = Date.now();
|
|
1699
|
+
let existingEntries = {};
|
|
1700
|
+
if (existsSync2(this.cacheFilePath)) {
|
|
1701
|
+
try {
|
|
1702
|
+
const content = readFileSync2(this.cacheFilePath, "utf-8");
|
|
1703
|
+
const data = JSON.parse(content);
|
|
1704
|
+
existingEntries = data.entries || {};
|
|
1705
|
+
} catch {
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
const validDiskEntries = {};
|
|
1709
|
+
for (const [key, entry] of Object.entries(existingEntries)) {
|
|
1710
|
+
const age = now - entry.timestamp;
|
|
1711
|
+
if (age <= this.diskTtlMs) {
|
|
1712
|
+
validDiskEntries[key] = entry;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
const mergedEntries = { ...validDiskEntries };
|
|
1716
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
1717
|
+
mergedEntries[key] = {
|
|
1718
|
+
value: entry.value,
|
|
1719
|
+
timestamp: entry.timestamp,
|
|
1720
|
+
thinkingText: entry.thinkingText,
|
|
1721
|
+
textPreview: entry.textPreview,
|
|
1722
|
+
toolIds: entry.toolIds
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
const cacheData = {
|
|
1726
|
+
version: "1.0",
|
|
1727
|
+
memory_ttl_seconds: this.memoryTtlMs / 1e3,
|
|
1728
|
+
disk_ttl_seconds: this.diskTtlMs / 1e3,
|
|
1729
|
+
entries: mergedEntries,
|
|
1730
|
+
statistics: {
|
|
1731
|
+
memory_hits: this.stats.memoryHits,
|
|
1732
|
+
disk_hits: this.stats.diskHits,
|
|
1733
|
+
misses: this.stats.misses,
|
|
1734
|
+
writes: this.stats.writes + 1,
|
|
1735
|
+
last_write: now
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
const tmpPath = join2(tmpdir2(), `antigravity-cache-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`);
|
|
1739
|
+
writeFileSync2(tmpPath, JSON.stringify(cacheData, null, 2), "utf-8");
|
|
1740
|
+
try {
|
|
1741
|
+
renameSync2(tmpPath, this.cacheFilePath);
|
|
1742
|
+
} catch {
|
|
1743
|
+
writeFileSync2(this.cacheFilePath, readFileSync2(tmpPath));
|
|
1744
|
+
try {
|
|
1745
|
+
unlinkSync2(tmpPath);
|
|
1746
|
+
} catch {
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
this.stats.writes++;
|
|
1871
1750
|
this.dirty = false;
|
|
1751
|
+
return true;
|
|
1752
|
+
} catch {
|
|
1753
|
+
return false;
|
|
1872
1754
|
}
|
|
1873
1755
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
this.writeTimer =
|
|
1882
|
-
this.writeTimer = null;
|
|
1883
|
-
this.lastWriteTime = Date.now();
|
|
1756
|
+
// ===========================================================================
|
|
1757
|
+
// Background Tasks
|
|
1758
|
+
// ===========================================================================
|
|
1759
|
+
/**
|
|
1760
|
+
* Starts timers for auto-saving and auto-cleaning expired memory entries
|
|
1761
|
+
*/
|
|
1762
|
+
startBackgroundTasks() {
|
|
1763
|
+
this.writeTimer = setInterval(() => {
|
|
1884
1764
|
if (this.dirty) {
|
|
1885
|
-
this.
|
|
1886
|
-
saveTurnStatesToDisk(this.entries);
|
|
1765
|
+
this.saveToDisk();
|
|
1887
1766
|
}
|
|
1888
|
-
},
|
|
1889
|
-
|
|
1890
|
-
this.
|
|
1891
|
-
}
|
|
1767
|
+
}, this.writeIntervalMs);
|
|
1768
|
+
this.cleanupTimer = setInterval(() => {
|
|
1769
|
+
this.cleanupExpired();
|
|
1770
|
+
}, 30 * 60 * 1e3);
|
|
1892
1771
|
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1772
|
+
/**
|
|
1773
|
+
* Removes memory cache entries exceeding their TTL
|
|
1774
|
+
*/
|
|
1775
|
+
cleanupExpired() {
|
|
1776
|
+
const now = Date.now();
|
|
1777
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
1778
|
+
const age = now - entry.timestamp;
|
|
1779
|
+
if (age > this.memoryTtlMs) {
|
|
1780
|
+
this.cache.delete(key);
|
|
1781
|
+
}
|
|
1897
1782
|
}
|
|
1898
1783
|
}
|
|
1899
1784
|
};
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
try {
|
|
1904
|
-
trackerInstance = new TurnStateTracker(true);
|
|
1905
|
-
if (typeof process !== "undefined") {
|
|
1906
|
-
process.on("exit", () => {
|
|
1907
|
-
trackerInstance?.shutdown();
|
|
1908
|
-
});
|
|
1909
|
-
}
|
|
1910
|
-
} catch {
|
|
1911
|
-
trackerInstance = new TurnStateTracker(false);
|
|
1912
|
-
}
|
|
1785
|
+
function createSignatureCache(config2) {
|
|
1786
|
+
if (!config2 || !config2.enabled) {
|
|
1787
|
+
return null;
|
|
1913
1788
|
}
|
|
1914
|
-
return
|
|
1915
|
-
}
|
|
1916
|
-
function getTurnStateTracker() {
|
|
1917
|
-
return trackerInstance;
|
|
1789
|
+
return new SignatureCache(config2);
|
|
1918
1790
|
}
|
|
1919
1791
|
|
|
1920
|
-
// src/
|
|
1921
|
-
var
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
if (!payload) {
|
|
1931
|
-
return null;
|
|
1932
|
-
}
|
|
1933
|
-
const details = Array.isArray(payload.details) ? payload.details : [];
|
|
1934
|
-
const retryInfo = details.find(
|
|
1935
|
-
(detail) => isObject(detail) && detail["@type"] === "type.googleapis.com/google.rpc.RetryInfo"
|
|
1936
|
-
);
|
|
1937
|
-
const retryDelayMs = (retryInfo?.retryDelay ? parseRetryDelayValue(retryInfo.retryDelay) : null) ?? parseRetryDelayFromMessage(payload.message ?? "") ?? void 0;
|
|
1938
|
-
const errorInfo = details.find(
|
|
1939
|
-
(detail) => isObject(detail) && detail["@type"] === "type.googleapis.com/google.rpc.ErrorInfo"
|
|
1940
|
-
);
|
|
1941
|
-
if (errorInfo?.domain && !CLOUDCODE_DOMAINS.has(errorInfo.domain)) {
|
|
1942
|
-
return null;
|
|
1943
|
-
}
|
|
1944
|
-
if (errorInfo?.reason === "QUOTA_EXHAUSTED") {
|
|
1945
|
-
return { terminal: true, retryDelayMs, reason: errorInfo.reason };
|
|
1946
|
-
}
|
|
1947
|
-
if (errorInfo?.reason === "RATE_LIMIT_EXCEEDED") {
|
|
1948
|
-
return { terminal: false, retryDelayMs: retryDelayMs ?? 1e4, reason: errorInfo.reason };
|
|
1792
|
+
// src/plugin/cache.ts
|
|
1793
|
+
var authCache = /* @__PURE__ */ new Map();
|
|
1794
|
+
function normalizeRefreshKey(refresh) {
|
|
1795
|
+
const key = refresh?.trim();
|
|
1796
|
+
return key ? key : void 0;
|
|
1797
|
+
}
|
|
1798
|
+
function resolveCachedAuth(auth) {
|
|
1799
|
+
const key = normalizeRefreshKey(auth.refresh);
|
|
1800
|
+
if (!key) {
|
|
1801
|
+
return auth;
|
|
1949
1802
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
reason: errorInfo.reason
|
|
1955
|
-
};
|
|
1803
|
+
const cached2 = authCache.get(key);
|
|
1804
|
+
if (!cached2) {
|
|
1805
|
+
authCache.set(key, auth);
|
|
1806
|
+
return auth;
|
|
1956
1807
|
}
|
|
1957
|
-
|
|
1958
|
-
(
|
|
1959
|
-
|
|
1960
|
-
if (quotaFailure?.violations?.length) {
|
|
1961
|
-
const allTexts = quotaFailure.violations.flatMap((violation) => [violation.quotaId ?? "", violation.description ?? ""]).join(" ").toLowerCase();
|
|
1962
|
-
if (allTexts.includes("perday") || allTexts.includes("daily") || allTexts.includes("per day")) {
|
|
1963
|
-
return { terminal: true, retryDelayMs, reason: errorInfo?.reason };
|
|
1964
|
-
}
|
|
1965
|
-
if (allTexts.includes("perminute") || allTexts.includes("per minute")) {
|
|
1966
|
-
return { terminal: false, retryDelayMs: retryDelayMs ?? 6e4, reason: errorInfo?.reason };
|
|
1967
|
-
}
|
|
1968
|
-
return { terminal: false, retryDelayMs, reason: errorInfo?.reason };
|
|
1808
|
+
if (!accessTokenExpired(auth)) {
|
|
1809
|
+
authCache.set(key, auth);
|
|
1810
|
+
return auth;
|
|
1969
1811
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
return { terminal: false, retryDelayMs: retryDelayMs ?? 6e4, reason: errorInfo?.reason };
|
|
1812
|
+
if (!accessTokenExpired(cached2)) {
|
|
1813
|
+
return cached2;
|
|
1973
1814
|
}
|
|
1974
|
-
|
|
1815
|
+
authCache.set(key, auth);
|
|
1816
|
+
return auth;
|
|
1975
1817
|
}
|
|
1976
|
-
|
|
1977
|
-
const
|
|
1978
|
-
if (!
|
|
1979
|
-
return
|
|
1980
|
-
}
|
|
1981
|
-
const details = Array.isArray(payload.details) ? payload.details : [];
|
|
1982
|
-
const retryInfo = details.find(
|
|
1983
|
-
(detail) => isObject(detail) && detail["@type"] === "type.googleapis.com/google.rpc.RetryInfo"
|
|
1984
|
-
);
|
|
1985
|
-
if (retryInfo?.retryDelay) {
|
|
1986
|
-
const delayMs = parseRetryDelayValue(retryInfo.retryDelay);
|
|
1987
|
-
if (delayMs !== null) {
|
|
1988
|
-
return delayMs;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
if (typeof payload.message === "string") {
|
|
1992
|
-
return parseRetryDelayFromMessage(payload.message);
|
|
1818
|
+
function storeCachedAuth(auth) {
|
|
1819
|
+
const key = normalizeRefreshKey(auth.refresh);
|
|
1820
|
+
if (!key) {
|
|
1821
|
+
return;
|
|
1993
1822
|
}
|
|
1994
|
-
|
|
1823
|
+
authCache.set(key, auth);
|
|
1995
1824
|
}
|
|
1996
|
-
function
|
|
1997
|
-
if (
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
return null;
|
|
2001
|
-
}
|
|
2002
|
-
if (trimmed.endsWith("ms")) {
|
|
2003
|
-
const milliseconds = Number(trimmed.slice(0, -2));
|
|
2004
|
-
return Number.isFinite(milliseconds) && milliseconds > 0 ? Math.round(milliseconds) : null;
|
|
2005
|
-
}
|
|
2006
|
-
const match = trimmed.match(/^([\d.]+)s$/);
|
|
2007
|
-
if (!match?.[1]) {
|
|
2008
|
-
return null;
|
|
2009
|
-
}
|
|
2010
|
-
const seconds2 = Number(match[1]);
|
|
2011
|
-
return Number.isFinite(seconds2) && seconds2 > 0 ? Math.round(seconds2 * 1e3) : null;
|
|
1825
|
+
function clearCachedAuth(refresh) {
|
|
1826
|
+
if (!refresh) {
|
|
1827
|
+
authCache.clear();
|
|
1828
|
+
return;
|
|
2012
1829
|
}
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
return null;
|
|
1830
|
+
const key = normalizeRefreshKey(refresh);
|
|
1831
|
+
if (key) {
|
|
1832
|
+
authCache.delete(key);
|
|
2017
1833
|
}
|
|
2018
|
-
const totalMs = Math.round(seconds * 1e3 + nanos / 1e6);
|
|
2019
|
-
return totalMs > 0 ? totalMs : null;
|
|
2020
1834
|
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
}
|
|
2030
|
-
return null;
|
|
1835
|
+
var signatureCache = /* @__PURE__ */ new Map();
|
|
1836
|
+
var SIGNATURE_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
1837
|
+
var MAX_ENTRIES_PER_SESSION = 100;
|
|
1838
|
+
var SIGNATURE_TEXT_HASH_HEX_LEN = 16;
|
|
1839
|
+
var diskCache = null;
|
|
1840
|
+
function initDiskSignatureCache(config2) {
|
|
1841
|
+
diskCache = createSignatureCache(config2);
|
|
1842
|
+
return diskCache;
|
|
2031
1843
|
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
1844
|
+
function hashText(text) {
|
|
1845
|
+
return createHash("sha256").update(text, "utf8").digest("hex").slice(0, SIGNATURE_TEXT_HASH_HEX_LEN);
|
|
1846
|
+
}
|
|
1847
|
+
function makeDiskKey(sessionId, textHash) {
|
|
1848
|
+
return `${sessionId}:${textHash}`;
|
|
1849
|
+
}
|
|
1850
|
+
var latestSignatureMap = /* @__PURE__ */ new Map();
|
|
1851
|
+
function cacheSignature(sessionId, text, signature) {
|
|
1852
|
+
if (!sessionId || !text || !signature) return;
|
|
1853
|
+
const textHash = hashText(text);
|
|
1854
|
+
let sessionMemCache = signatureCache.get(sessionId);
|
|
1855
|
+
if (!sessionMemCache) {
|
|
1856
|
+
sessionMemCache = /* @__PURE__ */ new Map();
|
|
1857
|
+
signatureCache.set(sessionId, sessionMemCache);
|
|
2038
1858
|
}
|
|
2039
|
-
if (
|
|
2040
|
-
|
|
1859
|
+
if (sessionMemCache.size >= MAX_ENTRIES_PER_SESSION) {
|
|
1860
|
+
const now = Date.now();
|
|
1861
|
+
for (const [key, entry] of sessionMemCache.entries()) {
|
|
1862
|
+
if (now - entry.timestamp > SIGNATURE_CACHE_TTL_MS) {
|
|
1863
|
+
sessionMemCache.delete(key);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
if (sessionMemCache.size >= MAX_ENTRIES_PER_SESSION) {
|
|
1867
|
+
const entries = Array.from(sessionMemCache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
1868
|
+
const toRemove = entries.slice(0, Math.floor(MAX_ENTRIES_PER_SESSION / 4));
|
|
1869
|
+
for (const [key] of toRemove) {
|
|
1870
|
+
sessionMemCache.delete(key);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
2041
1873
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
1874
|
+
sessionMemCache.set(textHash, { signature, timestamp: Date.now() });
|
|
1875
|
+
latestSignatureMap.set(sessionId, signature);
|
|
1876
|
+
if (diskCache) {
|
|
1877
|
+
const diskKey = makeDiskKey(sessionId, textHash);
|
|
1878
|
+
diskCache.store(diskKey, signature);
|
|
1879
|
+
diskCache.store(sessionId, signature);
|
|
2047
1880
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
1881
|
+
}
|
|
1882
|
+
function getLatestSignature(sessionId) {
|
|
1883
|
+
if (!sessionId) return void 0;
|
|
1884
|
+
const memValue = latestSignatureMap.get(sessionId);
|
|
1885
|
+
if (memValue) return memValue;
|
|
1886
|
+
if (diskCache) {
|
|
1887
|
+
const diskValue = diskCache.retrieve(sessionId);
|
|
1888
|
+
if (diskValue) {
|
|
1889
|
+
latestSignatureMap.set(sessionId, diskValue);
|
|
1890
|
+
return diskValue;
|
|
1891
|
+
}
|
|
2051
1892
|
}
|
|
2052
|
-
|
|
1893
|
+
return void 0;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// src/sdk/request/turn-state-tracker.ts
|
|
1897
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
1898
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
1899
|
+
import { homedir as homedir3, tmpdir as tmpdir3 } from "os";
|
|
1900
|
+
|
|
1901
|
+
// src/sdk/request/thinking.ts
|
|
1902
|
+
import { createHash as createHash2 } from "crypto";
|
|
1903
|
+
function createSignatureStore() {
|
|
1904
|
+
const store = /* @__PURE__ */ new Map();
|
|
2053
1905
|
return {
|
|
2054
|
-
|
|
2055
|
-
|
|
1906
|
+
get: (key) => store.get(key),
|
|
1907
|
+
set: (key, value) => {
|
|
1908
|
+
store.set(key, value);
|
|
1909
|
+
},
|
|
1910
|
+
has: (key) => store.has(key),
|
|
1911
|
+
delete: (key) => {
|
|
1912
|
+
store.delete(key);
|
|
1913
|
+
}
|
|
2056
1914
|
};
|
|
2057
1915
|
}
|
|
2058
|
-
function
|
|
2059
|
-
|
|
1916
|
+
function createThoughtBuffer() {
|
|
1917
|
+
const buffer = /* @__PURE__ */ new Map();
|
|
1918
|
+
return {
|
|
1919
|
+
get: (index) => buffer.get(index),
|
|
1920
|
+
set: (index, text) => {
|
|
1921
|
+
buffer.set(index, text);
|
|
1922
|
+
},
|
|
1923
|
+
clear: () => buffer.clear()
|
|
1924
|
+
};
|
|
2060
1925
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
}
|
|
2066
|
-
return isObject(parsed) ? parsed : null;
|
|
1926
|
+
var defaultSignatureStore = createSignatureStore();
|
|
1927
|
+
var THINKING_HASH_HEX_LEN = 16;
|
|
1928
|
+
function hashString(str) {
|
|
1929
|
+
return createHash2("sha256").update(str, "utf8").digest("hex").slice(0, THINKING_HASH_HEX_LEN);
|
|
2067
1930
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
"
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
"
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
"
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
if (!init?.body) {
|
|
2088
|
-
return true;
|
|
2089
|
-
}
|
|
2090
|
-
const body = init.body;
|
|
2091
|
-
if (typeof body === "string") {
|
|
2092
|
-
return true;
|
|
2093
|
-
}
|
|
2094
|
-
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) {
|
|
2095
|
-
return true;
|
|
2096
|
-
}
|
|
2097
|
-
if (typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer) {
|
|
2098
|
-
return true;
|
|
2099
|
-
}
|
|
2100
|
-
if (typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(body)) {
|
|
2101
|
-
return true;
|
|
1931
|
+
function isThinkingPart(part) {
|
|
1932
|
+
if (!part || typeof part !== "object") return false;
|
|
1933
|
+
return part.thought === true || part.type === "thinking" || part.type === "redacted_thinking";
|
|
1934
|
+
}
|
|
1935
|
+
function isFunctionResponsePart(part) {
|
|
1936
|
+
return part && typeof part === "object" && "functionResponse" in part;
|
|
1937
|
+
}
|
|
1938
|
+
function isFunctionCallPart(part) {
|
|
1939
|
+
return part && typeof part === "object" && "functionCall" in part;
|
|
1940
|
+
}
|
|
1941
|
+
function isToolResultMessage(msg) {
|
|
1942
|
+
if (!msg || msg.role !== "user") return false;
|
|
1943
|
+
const parts = msg.parts || [];
|
|
1944
|
+
return parts.some(isFunctionResponsePart);
|
|
1945
|
+
}
|
|
1946
|
+
function messageHasThinking(msg) {
|
|
1947
|
+
if (!msg || typeof msg !== "object") return false;
|
|
1948
|
+
if (Array.isArray(msg.parts)) {
|
|
1949
|
+
return msg.parts.some(isThinkingPart);
|
|
2102
1950
|
}
|
|
2103
|
-
if (
|
|
2104
|
-
return
|
|
1951
|
+
if (Array.isArray(msg.content)) {
|
|
1952
|
+
return msg.content.some(
|
|
1953
|
+
(block) => block?.type === "thinking" || block?.type === "redacted_thinking"
|
|
1954
|
+
);
|
|
2105
1955
|
}
|
|
2106
1956
|
return false;
|
|
2107
1957
|
}
|
|
2108
|
-
function
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
const code = getNetworkErrorCode(error45);
|
|
2113
|
-
if (code && RETRYABLE_NETWORK_CODES.has(code)) {
|
|
2114
|
-
return true;
|
|
1958
|
+
function messageHasToolCalls(msg) {
|
|
1959
|
+
if (!msg || typeof msg !== "object") return false;
|
|
1960
|
+
if (Array.isArray(msg.parts)) {
|
|
1961
|
+
return msg.parts.some(isFunctionCallPart);
|
|
2115
1962
|
}
|
|
2116
|
-
|
|
1963
|
+
if (Array.isArray(msg.content)) {
|
|
1964
|
+
return msg.content.some((block) => block?.type === "tool_use");
|
|
1965
|
+
}
|
|
1966
|
+
return false;
|
|
2117
1967
|
}
|
|
2118
|
-
|
|
2119
|
-
const
|
|
2120
|
-
|
|
2121
|
-
|
|
1968
|
+
function analyzeConversationState(contents) {
|
|
1969
|
+
const state = {
|
|
1970
|
+
inToolLoop: false,
|
|
1971
|
+
turnStartIdx: -1,
|
|
1972
|
+
turnHasThinking: false,
|
|
1973
|
+
lastModelIdx: -1,
|
|
1974
|
+
lastModelHasThinking: false,
|
|
1975
|
+
lastModelHasToolCalls: false
|
|
1976
|
+
};
|
|
1977
|
+
if (!Array.isArray(contents) || contents.length === 0) {
|
|
1978
|
+
return state;
|
|
2122
1979
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
1980
|
+
let lastRealUserIdx = -1;
|
|
1981
|
+
for (let i = 0; i < contents.length; i++) {
|
|
1982
|
+
const msg = contents[i];
|
|
1983
|
+
if (msg?.role === "user" && !isToolResultMessage(msg)) {
|
|
1984
|
+
lastRealUserIdx = i;
|
|
1985
|
+
}
|
|
2126
1986
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
1987
|
+
for (let i = 0; i < contents.length; i++) {
|
|
1988
|
+
const msg = contents[i];
|
|
1989
|
+
const role = msg?.role;
|
|
1990
|
+
if (role === "model" || role === "assistant") {
|
|
1991
|
+
const hasThinking = messageHasThinking(msg);
|
|
1992
|
+
const hasToolCalls = messageHasToolCalls(msg);
|
|
1993
|
+
if (i > lastRealUserIdx && state.turnStartIdx === -1) {
|
|
1994
|
+
state.turnStartIdx = i;
|
|
1995
|
+
state.turnHasThinking = hasThinking;
|
|
1996
|
+
}
|
|
1997
|
+
state.lastModelIdx = i;
|
|
1998
|
+
state.lastModelHasToolCalls = hasToolCalls;
|
|
1999
|
+
state.lastModelHasThinking = hasThinking;
|
|
2000
|
+
}
|
|
2129
2001
|
}
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2002
|
+
if (contents.length > 0) {
|
|
2003
|
+
const lastMsg = contents[contents.length - 1];
|
|
2004
|
+
if (lastMsg?.role === "user" && isToolResultMessage(lastMsg)) {
|
|
2005
|
+
state.inToolLoop = true;
|
|
2006
|
+
}
|
|
2133
2007
|
}
|
|
2134
|
-
return
|
|
2135
|
-
}
|
|
2136
|
-
function getExponentialDelayWithJitter(attempt) {
|
|
2137
|
-
const base = Math.min(DEFAULT_MAX_DELAY_MS, DEFAULT_INITIAL_DELAY_MS * Math.pow(2, attempt - 1));
|
|
2138
|
-
const jitter = base * 0.3 * (Math.random() * 2 - 1);
|
|
2139
|
-
return clampDelay(base + jitter);
|
|
2140
|
-
}
|
|
2141
|
-
function wait2(ms) {
|
|
2142
|
-
return new Promise((resolve) => {
|
|
2143
|
-
setTimeout(resolve, ms);
|
|
2144
|
-
});
|
|
2008
|
+
return state;
|
|
2145
2009
|
}
|
|
2146
|
-
function
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2010
|
+
function countTrailingToolResults(contents) {
|
|
2011
|
+
let count = 0;
|
|
2012
|
+
for (let i = contents.length - 1; i >= 0; i--) {
|
|
2013
|
+
const msg = contents[i];
|
|
2014
|
+
if (msg?.role === "user") {
|
|
2015
|
+
const parts = msg.parts || [];
|
|
2016
|
+
const functionResponses = parts.filter(isFunctionResponsePart);
|
|
2017
|
+
if (functionResponses.length > 0) {
|
|
2018
|
+
count += functionResponses.length;
|
|
2019
|
+
} else {
|
|
2020
|
+
break;
|
|
2021
|
+
}
|
|
2022
|
+
} else if (msg?.role === "model" || msg?.role === "assistant") {
|
|
2023
|
+
break;
|
|
2153
2024
|
}
|
|
2154
|
-
|
|
2025
|
+
}
|
|
2026
|
+
return count;
|
|
2027
|
+
}
|
|
2028
|
+
function closeToolLoopForThinking(contents) {
|
|
2029
|
+
const strippedContents = contents;
|
|
2030
|
+
const toolResultCount = countTrailingToolResults(strippedContents);
|
|
2031
|
+
let syntheticModelContent;
|
|
2032
|
+
if (toolResultCount === 0) {
|
|
2033
|
+
syntheticModelContent = "[Processing prev ctx.]";
|
|
2034
|
+
} else if (toolResultCount === 1) {
|
|
2035
|
+
syntheticModelContent = "[Tool exec completed.]";
|
|
2036
|
+
} else {
|
|
2037
|
+
syntheticModelContent = `[${toolResultCount} tool executions completed.]`;
|
|
2038
|
+
}
|
|
2039
|
+
const syntheticModel = {
|
|
2040
|
+
role: "model",
|
|
2041
|
+
parts: [{ text: syntheticModelContent }]
|
|
2155
2042
|
};
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
|
|
2043
|
+
const syntheticUser = {
|
|
2044
|
+
role: "user",
|
|
2045
|
+
parts: [{ text: "[Continue]" }]
|
|
2046
|
+
};
|
|
2047
|
+
return [...strippedContents, syntheticModel, syntheticUser];
|
|
2048
|
+
}
|
|
2049
|
+
function deduplicateThinkingText(response, sentBuffer, displayedThinkingHashes) {
|
|
2050
|
+
if (!response || typeof response !== "object") return response;
|
|
2051
|
+
const resp = response;
|
|
2052
|
+
if (Array.isArray(resp.candidates)) {
|
|
2053
|
+
const newCandidates = resp.candidates.map((candidate, index) => {
|
|
2054
|
+
const cand = candidate;
|
|
2055
|
+
if (!cand?.content) return candidate;
|
|
2056
|
+
const content = cand.content;
|
|
2057
|
+
if (!Array.isArray(content.parts)) return candidate;
|
|
2058
|
+
const newParts = content.parts.map((part) => {
|
|
2059
|
+
const p = part;
|
|
2060
|
+
if (p.thought === true || p.type === "thinking") {
|
|
2061
|
+
const fullText = p.text || p.thinking || "";
|
|
2062
|
+
if (displayedThinkingHashes) {
|
|
2063
|
+
const hash2 = hashString(fullText);
|
|
2064
|
+
if (displayedThinkingHashes.has(hash2)) {
|
|
2065
|
+
sentBuffer.set(index, fullText);
|
|
2066
|
+
return null;
|
|
2067
|
+
}
|
|
2068
|
+
displayedThinkingHashes.add(hash2);
|
|
2069
|
+
}
|
|
2070
|
+
const sentText = sentBuffer.get(index) ?? "";
|
|
2071
|
+
if (fullText.startsWith(sentText)) {
|
|
2072
|
+
const delta = fullText.slice(sentText.length);
|
|
2073
|
+
sentBuffer.set(index, fullText);
|
|
2074
|
+
if (delta) {
|
|
2075
|
+
return { ...p, text: delta, thinking: delta };
|
|
2076
|
+
}
|
|
2077
|
+
return null;
|
|
2078
|
+
}
|
|
2079
|
+
sentBuffer.set(index, fullText);
|
|
2080
|
+
return part;
|
|
2081
|
+
}
|
|
2082
|
+
return part;
|
|
2083
|
+
});
|
|
2084
|
+
const filteredParts = newParts.filter((p) => p !== null);
|
|
2085
|
+
return {
|
|
2086
|
+
...cand,
|
|
2087
|
+
content: { ...content, parts: filteredParts }
|
|
2088
|
+
};
|
|
2089
|
+
});
|
|
2090
|
+
return { ...resp, candidates: newCandidates };
|
|
2159
2091
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2092
|
+
if (Array.isArray(resp.content)) {
|
|
2093
|
+
let thinkingIndex = 0;
|
|
2094
|
+
const newContent = resp.content.map((block) => {
|
|
2095
|
+
const b = block;
|
|
2096
|
+
if (b?.type === "thinking") {
|
|
2097
|
+
const fullText = b.thinking || b.text || "";
|
|
2098
|
+
if (displayedThinkingHashes) {
|
|
2099
|
+
const hash2 = hashString(fullText);
|
|
2100
|
+
if (displayedThinkingHashes.has(hash2)) {
|
|
2101
|
+
sentBuffer.set(thinkingIndex, fullText);
|
|
2102
|
+
thinkingIndex++;
|
|
2103
|
+
return null;
|
|
2104
|
+
}
|
|
2105
|
+
displayedThinkingHashes.add(hash2);
|
|
2106
|
+
}
|
|
2107
|
+
const sentText = sentBuffer.get(thinkingIndex) ?? "";
|
|
2108
|
+
if (fullText.startsWith(sentText)) {
|
|
2109
|
+
const delta = fullText.slice(sentText.length);
|
|
2110
|
+
sentBuffer.set(thinkingIndex, fullText);
|
|
2111
|
+
thinkingIndex++;
|
|
2112
|
+
if (delta) {
|
|
2113
|
+
return { ...b, thinking: delta, text: delta };
|
|
2114
|
+
}
|
|
2115
|
+
return null;
|
|
2116
|
+
}
|
|
2117
|
+
sentBuffer.set(thinkingIndex, fullText);
|
|
2118
|
+
thinkingIndex++;
|
|
2119
|
+
return block;
|
|
2120
|
+
}
|
|
2121
|
+
return block;
|
|
2122
|
+
});
|
|
2123
|
+
const filteredContent = newContent.filter((b) => b !== null);
|
|
2124
|
+
if (filteredContent.length === 0) {
|
|
2125
|
+
return { ...resp, content: [] };
|
|
2169
2126
|
}
|
|
2127
|
+
return { ...resp, content: filteredContent };
|
|
2170
2128
|
}
|
|
2171
|
-
return
|
|
2129
|
+
return response;
|
|
2172
2130
|
}
|
|
2173
|
-
function
|
|
2174
|
-
if (!
|
|
2175
|
-
|
|
2131
|
+
function cacheThinkingSignaturesFromResponse(response, signatureSessionKey, signatureStore, thoughtBuffer, onCacheSignature) {
|
|
2132
|
+
if (!response || typeof response !== "object") return;
|
|
2133
|
+
const resp = response;
|
|
2134
|
+
if (Array.isArray(resp.candidates)) {
|
|
2135
|
+
resp.candidates.forEach((candidate, index) => {
|
|
2136
|
+
const cand = candidate;
|
|
2137
|
+
if (!cand?.content) return;
|
|
2138
|
+
const content = cand.content;
|
|
2139
|
+
if (!Array.isArray(content.parts)) return;
|
|
2140
|
+
content.parts.forEach((part) => {
|
|
2141
|
+
const p = part;
|
|
2142
|
+
if (p.thought === true || p.type === "thinking") {
|
|
2143
|
+
const text = p.text || p.thinking || "";
|
|
2144
|
+
if (text) {
|
|
2145
|
+
const current = thoughtBuffer.get(index) ?? "";
|
|
2146
|
+
thoughtBuffer.set(index, current + text);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
if (p.thoughtSignature) {
|
|
2150
|
+
const fullText = thoughtBuffer.get(index) ?? "";
|
|
2151
|
+
if (fullText) {
|
|
2152
|
+
const signature = p.thoughtSignature;
|
|
2153
|
+
onCacheSignature?.(signatureSessionKey, fullText, signature);
|
|
2154
|
+
signatureStore.set(signatureSessionKey, { text: fullText, signature });
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
});
|
|
2158
|
+
});
|
|
2176
2159
|
}
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2160
|
+
if (Array.isArray(resp.content)) {
|
|
2161
|
+
const CLAUDE_BUFFER_KEY = 0;
|
|
2162
|
+
resp.content.forEach((block) => {
|
|
2163
|
+
const b = block;
|
|
2164
|
+
if (b?.type === "thinking") {
|
|
2165
|
+
const text = b.thinking || b.text || "";
|
|
2166
|
+
if (text) {
|
|
2167
|
+
const current = thoughtBuffer.get(CLAUDE_BUFFER_KEY) ?? "";
|
|
2168
|
+
thoughtBuffer.set(CLAUDE_BUFFER_KEY, current + text);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
if (b?.signature) {
|
|
2172
|
+
const fullText = thoughtBuffer.get(CLAUDE_BUFFER_KEY) ?? "";
|
|
2173
|
+
if (fullText) {
|
|
2174
|
+
const signature = b.signature;
|
|
2175
|
+
onCacheSignature?.(signatureSessionKey, fullText, signature);
|
|
2176
|
+
signatureStore.set(signatureSessionKey, { text: fullText, signature });
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
});
|
|
2180
2180
|
}
|
|
2181
|
-
return Math.round(parsed);
|
|
2182
2181
|
}
|
|
2183
|
-
function
|
|
2184
|
-
|
|
2185
|
-
|
|
2182
|
+
function transformSseEvent(eventText, signatureStore, thoughtBuffer, sentThinkingBuffer, callbacks, options, debugState) {
|
|
2183
|
+
const dataLines = [];
|
|
2184
|
+
const lines = eventText.split(/\r?\n/);
|
|
2185
|
+
let isDataEvent = false;
|
|
2186
|
+
for (const line of lines) {
|
|
2187
|
+
if (line.startsWith("data:")) {
|
|
2188
|
+
isDataEvent = true;
|
|
2189
|
+
dataLines.push(line.slice(5).trim());
|
|
2190
|
+
}
|
|
2186
2191
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
return null;
|
|
2192
|
+
if (!isDataEvent) {
|
|
2193
|
+
return eventText;
|
|
2190
2194
|
}
|
|
2191
|
-
const
|
|
2192
|
-
if (
|
|
2193
|
-
return
|
|
2195
|
+
const jsonString = dataLines.join("\n").trim();
|
|
2196
|
+
if (!jsonString) {
|
|
2197
|
+
return eventText;
|
|
2194
2198
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2199
|
+
try {
|
|
2200
|
+
const parsed = JSON.parse(jsonString);
|
|
2201
|
+
if (parsed && typeof parsed === "object" && parsed.response !== void 0) {
|
|
2202
|
+
if (options.cacheSignatures && options.signatureSessionKey) {
|
|
2203
|
+
cacheThinkingSignaturesFromResponse(
|
|
2204
|
+
parsed.response,
|
|
2205
|
+
options.signatureSessionKey,
|
|
2206
|
+
signatureStore,
|
|
2207
|
+
thoughtBuffer,
|
|
2208
|
+
callbacks.onCacheSignature
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
let response = deduplicateThinkingText(
|
|
2212
|
+
parsed.response,
|
|
2213
|
+
sentThinkingBuffer,
|
|
2214
|
+
options.displayedThinkingHashes
|
|
2215
|
+
);
|
|
2216
|
+
if (options.debugText && callbacks.onInjectDebug && !debugState.injected) {
|
|
2217
|
+
response = callbacks.onInjectDebug(response, options.debugText);
|
|
2218
|
+
debugState.injected = true;
|
|
2219
|
+
}
|
|
2220
|
+
const transformed = callbacks.transformThinkingParts ? callbacks.transformThinkingParts(response) : response;
|
|
2221
|
+
return `data: ${JSON.stringify(transformed)}`;
|
|
2222
|
+
}
|
|
2223
|
+
} catch (_) {
|
|
2198
2224
|
}
|
|
2199
|
-
return
|
|
2225
|
+
return eventText;
|
|
2200
2226
|
}
|
|
2201
|
-
function
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2227
|
+
function createStreamingTransformer(signatureStore, callbacks, options = {}) {
|
|
2228
|
+
const decoder2 = new TextDecoder();
|
|
2229
|
+
const encoder2 = new TextEncoder();
|
|
2230
|
+
let buffer = "";
|
|
2231
|
+
const thoughtBuffer = createThoughtBuffer();
|
|
2232
|
+
const sentThinkingBuffer = createThoughtBuffer();
|
|
2233
|
+
const debugState = { injected: false };
|
|
2234
|
+
let hasSeenUsageMetadata = false;
|
|
2235
|
+
let streamHasThinking = false;
|
|
2236
|
+
let streamHasToolCalls = false;
|
|
2237
|
+
const displayedThinkingHashes = options.displayedThinkingHashes ?? /* @__PURE__ */ new Set();
|
|
2238
|
+
const mergedOptions = { ...options, displayedThinkingHashes };
|
|
2239
|
+
return new TransformStream({
|
|
2240
|
+
transform(chunk, controller) {
|
|
2241
|
+
buffer += decoder2.decode(chunk, { stream: true });
|
|
2242
|
+
const events = buffer.split(/\r?\n\r?\n/);
|
|
2243
|
+
buffer = events.pop() || "";
|
|
2244
|
+
for (const event of events) {
|
|
2245
|
+
if (!event.trim()) continue;
|
|
2246
|
+
if (event.includes("usageMetadata")) {
|
|
2247
|
+
hasSeenUsageMetadata = true;
|
|
2248
|
+
}
|
|
2249
|
+
if (!streamHasThinking) {
|
|
2250
|
+
streamHasThinking = event.includes('"thought":true') || event.includes('"type":"thinking"');
|
|
2251
|
+
}
|
|
2252
|
+
if (!streamHasToolCalls) {
|
|
2253
|
+
streamHasToolCalls = event.includes('"functionCall"');
|
|
2254
|
+
}
|
|
2255
|
+
const transformedEvent = transformSseEvent(
|
|
2256
|
+
event,
|
|
2257
|
+
signatureStore,
|
|
2258
|
+
thoughtBuffer,
|
|
2259
|
+
sentThinkingBuffer,
|
|
2260
|
+
callbacks,
|
|
2261
|
+
mergedOptions,
|
|
2262
|
+
debugState
|
|
2263
|
+
);
|
|
2264
|
+
controller.enqueue(encoder2.encode(transformedEvent + "\n\n"));
|
|
2265
|
+
}
|
|
2266
|
+
},
|
|
2267
|
+
flush(controller) {
|
|
2268
|
+
buffer += decoder2.decode();
|
|
2269
|
+
if (buffer.trim()) {
|
|
2270
|
+
if (buffer.includes("usageMetadata")) {
|
|
2271
|
+
hasSeenUsageMetadata = true;
|
|
2272
|
+
}
|
|
2273
|
+
if (!streamHasThinking) {
|
|
2274
|
+
streamHasThinking = buffer.includes('"thought":true') || buffer.includes('"type":"thinking"');
|
|
2275
|
+
}
|
|
2276
|
+
if (!streamHasToolCalls) {
|
|
2277
|
+
streamHasToolCalls = buffer.includes('"functionCall"');
|
|
2278
|
+
}
|
|
2279
|
+
const transformedEvent = transformSseEvent(
|
|
2280
|
+
buffer,
|
|
2281
|
+
signatureStore,
|
|
2282
|
+
thoughtBuffer,
|
|
2283
|
+
sentThinkingBuffer,
|
|
2284
|
+
callbacks,
|
|
2285
|
+
mergedOptions,
|
|
2286
|
+
debugState
|
|
2287
|
+
);
|
|
2288
|
+
controller.enqueue(encoder2.encode(transformedEvent + "\n\n"));
|
|
2289
|
+
}
|
|
2290
|
+
if (!hasSeenUsageMetadata) {
|
|
2291
|
+
const syntheticUsage = {
|
|
2292
|
+
candidates: [
|
|
2293
|
+
{
|
|
2294
|
+
finishReason: "STOP"
|
|
2295
|
+
}
|
|
2296
|
+
],
|
|
2297
|
+
usageMetadata: {
|
|
2298
|
+
promptTokenCount: 0,
|
|
2299
|
+
candidatesTokenCount: 0,
|
|
2300
|
+
totalTokenCount: 0
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
controller.enqueue(encoder2.encode(`data: ${JSON.stringify(syntheticUsage)}
|
|
2304
|
+
|
|
2305
|
+
`));
|
|
2306
|
+
}
|
|
2307
|
+
if (callbacks.onTurnStateUpdate && options.signatureSessionKey) {
|
|
2308
|
+
callbacks.onTurnStateUpdate(options.signatureSessionKey, {
|
|
2309
|
+
turnHasThinking: streamHasThinking,
|
|
2310
|
+
lastModelHasToolCalls: streamHasToolCalls
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2206
2315
|
}
|
|
2207
2316
|
|
|
2208
|
-
// src/sdk/
|
|
2209
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
2210
|
-
import { join as join3, dirname as dirname3 } from "path";
|
|
2211
|
-
import { homedir as homedir3, tmpdir as tmpdir3 } from "os";
|
|
2317
|
+
// src/sdk/request/turn-state-tracker.ts
|
|
2212
2318
|
var WRITE_THROTTLE_MS2 = 5e3;
|
|
2213
2319
|
function getConfigDir3() {
|
|
2214
2320
|
const platform2 = process.platform;
|
|
@@ -2218,13 +2324,13 @@ function getConfigDir3() {
|
|
|
2218
2324
|
const xdgConfig = process.env.XDG_CONFIG_HOME || join3(homedir3(), ".config");
|
|
2219
2325
|
return join3(xdgConfig, "opencode");
|
|
2220
2326
|
}
|
|
2221
|
-
function
|
|
2222
|
-
return join3(getConfigDir3(), "antigravity-
|
|
2327
|
+
function getTurnStateFilePath() {
|
|
2328
|
+
return join3(getConfigDir3(), "antigravity-turn-states.json");
|
|
2223
2329
|
}
|
|
2224
|
-
function
|
|
2330
|
+
function loadTurnStatesFromDisk() {
|
|
2225
2331
|
const result = /* @__PURE__ */ new Map();
|
|
2226
2332
|
try {
|
|
2227
|
-
const filePath =
|
|
2333
|
+
const filePath = getTurnStateFilePath();
|
|
2228
2334
|
if (!existsSync3(filePath)) {
|
|
2229
2335
|
return result;
|
|
2230
2336
|
}
|
|
@@ -2234,27 +2340,29 @@ function loadCooldowns() {
|
|
|
2234
2340
|
return result;
|
|
2235
2341
|
}
|
|
2236
2342
|
const now = Date.now();
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2343
|
+
const maxAge = 24 * 60 * 60 * 1e3;
|
|
2344
|
+
for (const [key, record2] of Object.entries(data.entries)) {
|
|
2345
|
+
if (record2.state && typeof record2.state === "object" && now - record2.updatedAt < maxAge) {
|
|
2346
|
+
result.set(key, record2);
|
|
2240
2347
|
}
|
|
2241
2348
|
}
|
|
2242
2349
|
} catch {
|
|
2243
2350
|
}
|
|
2244
2351
|
return result;
|
|
2245
2352
|
}
|
|
2246
|
-
function
|
|
2353
|
+
function saveTurnStatesToDisk(entries) {
|
|
2247
2354
|
try {
|
|
2248
|
-
const filePath =
|
|
2355
|
+
const filePath = getTurnStateFilePath();
|
|
2249
2356
|
const dir = dirname3(filePath);
|
|
2250
2357
|
if (!existsSync3(dir)) {
|
|
2251
2358
|
mkdirSync3(dir, { recursive: true });
|
|
2252
2359
|
}
|
|
2253
2360
|
const now = Date.now();
|
|
2361
|
+
const maxAge = 24 * 60 * 60 * 1e3;
|
|
2254
2362
|
const serializable = {};
|
|
2255
|
-
for (const [key,
|
|
2256
|
-
if (
|
|
2257
|
-
serializable[key] =
|
|
2363
|
+
for (const [key, record2] of entries.entries()) {
|
|
2364
|
+
if (now - record2.updatedAt < maxAge) {
|
|
2365
|
+
serializable[key] = record2;
|
|
2258
2366
|
}
|
|
2259
2367
|
}
|
|
2260
2368
|
const data = {
|
|
@@ -2262,7 +2370,7 @@ function saveCooldowns(entries) {
|
|
|
2262
2370
|
entries: serializable,
|
|
2263
2371
|
updatedAt: now
|
|
2264
2372
|
};
|
|
2265
|
-
const tmpPath = join3(tmpdir3(), `antigravity-
|
|
2373
|
+
const tmpPath = join3(tmpdir3(), `antigravity-turn-states-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`);
|
|
2266
2374
|
writeFileSync3(tmpPath, JSON.stringify(data), "utf-8");
|
|
2267
2375
|
try {
|
|
2268
2376
|
renameSync3(tmpPath, filePath);
|
|
@@ -2278,32 +2386,60 @@ function saveCooldowns(entries) {
|
|
|
2278
2386
|
return false;
|
|
2279
2387
|
}
|
|
2280
2388
|
}
|
|
2281
|
-
var
|
|
2389
|
+
var TurnStateTracker = class {
|
|
2390
|
+
entries = /* @__PURE__ */ new Map();
|
|
2282
2391
|
dirty = false;
|
|
2283
2392
|
lastWriteTime = 0;
|
|
2284
2393
|
writeTimer = null;
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
this.
|
|
2394
|
+
diskEnabled;
|
|
2395
|
+
constructor(diskEnabled = true) {
|
|
2396
|
+
this.diskEnabled = diskEnabled;
|
|
2397
|
+
if (diskEnabled) {
|
|
2398
|
+
this.entries = loadTurnStatesFromDisk();
|
|
2399
|
+
}
|
|
2288
2400
|
}
|
|
2289
|
-
|
|
2401
|
+
getState(sessionId) {
|
|
2402
|
+
const record2 = this.entries.get(sessionId);
|
|
2403
|
+
if (!record2) return void 0;
|
|
2404
|
+
return record2.state;
|
|
2405
|
+
}
|
|
2406
|
+
needsThinkingRecovery(sessionId) {
|
|
2407
|
+
const state = this.entries.get(sessionId);
|
|
2408
|
+
if (!state) return false;
|
|
2409
|
+
return state.state.inToolLoop && !state.state.turnHasThinking;
|
|
2410
|
+
}
|
|
2411
|
+
updateAfterResponse(sessionId, newState) {
|
|
2412
|
+
this.entries.set(sessionId, { state: newState, updatedAt: Date.now() });
|
|
2290
2413
|
this.dirty = true;
|
|
2291
2414
|
this.scheduleThrottledWrite();
|
|
2292
2415
|
}
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2416
|
+
recoverFromContents(sessionId, contents) {
|
|
2417
|
+
const fullState = analyzeConversationState(contents);
|
|
2418
|
+
const turnState = {
|
|
2419
|
+
inToolLoop: fullState.inToolLoop,
|
|
2420
|
+
turnHasThinking: fullState.turnHasThinking,
|
|
2421
|
+
lastModelHasThinking: fullState.lastModelHasThinking,
|
|
2422
|
+
lastModelHasToolCalls: fullState.lastModelHasToolCalls
|
|
2423
|
+
};
|
|
2424
|
+
this.entries.set(sessionId, { state: turnState, updatedAt: Date.now() });
|
|
2425
|
+
this.dirty = true;
|
|
2426
|
+
this.scheduleThrottledWrite();
|
|
2427
|
+
return turnState;
|
|
2428
|
+
}
|
|
2429
|
+
clear(sessionId) {
|
|
2430
|
+
this.entries.delete(sessionId);
|
|
2431
|
+
this.dirty = true;
|
|
2432
|
+
this.scheduleThrottledWrite();
|
|
2298
2433
|
}
|
|
2299
2434
|
shutdown() {
|
|
2300
2435
|
this.clearWriteTimer();
|
|
2301
|
-
if (this.dirty) {
|
|
2302
|
-
|
|
2436
|
+
if (this.dirty && this.diskEnabled) {
|
|
2437
|
+
saveTurnStatesToDisk(this.entries);
|
|
2303
2438
|
this.dirty = false;
|
|
2304
2439
|
}
|
|
2305
2440
|
}
|
|
2306
2441
|
scheduleThrottledWrite() {
|
|
2442
|
+
if (!this.diskEnabled) return;
|
|
2307
2443
|
if (this.writeTimer) {
|
|
2308
2444
|
return;
|
|
2309
2445
|
}
|
|
@@ -2314,7 +2450,7 @@ var CooldownStore = class {
|
|
|
2314
2450
|
this.lastWriteTime = Date.now();
|
|
2315
2451
|
if (this.dirty) {
|
|
2316
2452
|
this.dirty = false;
|
|
2317
|
-
|
|
2453
|
+
saveTurnStatesToDisk(this.entries);
|
|
2318
2454
|
}
|
|
2319
2455
|
}, remaining);
|
|
2320
2456
|
if (this.writeTimer && typeof this.writeTimer === "object" && "unref" in this.writeTimer) {
|
|
@@ -2328,148 +2464,24 @@ var CooldownStore = class {
|
|
|
2328
2464
|
}
|
|
2329
2465
|
}
|
|
2330
2466
|
};
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
var cooldownStore = new CooldownStore();
|
|
2335
|
-
var cooldownPersistenceInitialized = false;
|
|
2336
|
-
function initCooldownPersistence() {
|
|
2337
|
-
if (cooldownPersistenceInitialized) return;
|
|
2338
|
-
cooldownPersistenceInitialized = true;
|
|
2339
|
-
try {
|
|
2340
|
-
const persisted = loadCooldowns();
|
|
2341
|
-
for (const [key, expiresAt] of persisted.entries()) {
|
|
2342
|
-
retryCooldownByKey.set(key, expiresAt);
|
|
2343
|
-
}
|
|
2344
|
-
cooldownStore.bind(retryCooldownByKey);
|
|
2345
|
-
if (typeof process !== "undefined") {
|
|
2346
|
-
process.on("exit", () => {
|
|
2347
|
-
cooldownStore.shutdown();
|
|
2348
|
-
});
|
|
2349
|
-
}
|
|
2350
|
-
} catch {
|
|
2351
|
-
cooldownStore.bind(retryCooldownByKey);
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
var MODEL_CAPACITY_COOLDOWN_MS = 8e3;
|
|
2355
|
-
async function fetchWithRetry(input, init) {
|
|
2356
|
-
if (!cooldownPersistenceInitialized) initCooldownPersistence();
|
|
2357
|
-
if (!canRetryRequest(init)) {
|
|
2358
|
-
return agyFetch(input, init);
|
|
2359
|
-
}
|
|
2360
|
-
const retryInit = cloneRetryableInit(init);
|
|
2361
|
-
const throttleKey = buildRetryThrottleKey(input, retryInit);
|
|
2362
|
-
await waitForRetryCooldown(throttleKey, retryInit.signal);
|
|
2363
|
-
let attempt = 1;
|
|
2364
|
-
const url2 = readRequestUrl(input);
|
|
2365
|
-
while (attempt <= DEFAULT_MAX_ATTEMPTS) {
|
|
2366
|
-
let response;
|
|
2467
|
+
var trackerInstance = null;
|
|
2468
|
+
function initTurnStateTracker() {
|
|
2469
|
+
if (!trackerInstance) {
|
|
2367
2470
|
try {
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
if (retryInit.signal?.aborted) {
|
|
2374
|
-
throw error45;
|
|
2375
|
-
}
|
|
2376
|
-
const delayMs2 = getExponentialDelayWithJitter(attempt);
|
|
2377
|
-
await wait2(delayMs2);
|
|
2378
|
-
attempt += 1;
|
|
2379
|
-
continue;
|
|
2380
|
-
}
|
|
2381
|
-
if (!isRetryableStatus(response.status)) {
|
|
2382
|
-
return response;
|
|
2383
|
-
}
|
|
2384
|
-
const quotaContext = response.status === 429 ? await classifyQuotaResponse(response) : null;
|
|
2385
|
-
if (response.status === 429 && quotaContext?.terminal) {
|
|
2386
|
-
if (quotaContext.reason === "MODEL_CAPACITY_EXHAUSTED") {
|
|
2387
|
-
const cooldownMs = quotaContext.retryDelayMs ?? MODEL_CAPACITY_COOLDOWN_MS;
|
|
2388
|
-
setRetryCooldown(throttleKey, cooldownMs);
|
|
2471
|
+
trackerInstance = new TurnStateTracker(true);
|
|
2472
|
+
if (typeof process !== "undefined") {
|
|
2473
|
+
process.on("exit", () => {
|
|
2474
|
+
trackerInstance?.shutdown();
|
|
2475
|
+
});
|
|
2389
2476
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
if (attempt >= DEFAULT_MAX_ATTEMPTS || retryInit.signal?.aborted) {
|
|
2393
|
-
return response;
|
|
2394
|
-
}
|
|
2395
|
-
const delayMs = await resolveRetryDelayMs(response, attempt, quotaContext?.retryDelayMs);
|
|
2396
|
-
if (delayMs > 0 && response.status === 429) {
|
|
2397
|
-
setRetryCooldown(throttleKey, delayMs);
|
|
2398
|
-
}
|
|
2399
|
-
if (delayMs > 0) {
|
|
2400
|
-
await wait2(delayMs);
|
|
2401
|
-
}
|
|
2402
|
-
attempt += 1;
|
|
2403
|
-
}
|
|
2404
|
-
return agyFetch(input, retryInit);
|
|
2405
|
-
}
|
|
2406
|
-
function cloneRetryableInit(init) {
|
|
2407
|
-
if (!init) {
|
|
2408
|
-
return {};
|
|
2409
|
-
}
|
|
2410
|
-
return {
|
|
2411
|
-
...init,
|
|
2412
|
-
headers: new Headers(init.headers ?? {})
|
|
2413
|
-
};
|
|
2414
|
-
}
|
|
2415
|
-
function buildRetryThrottleKey(input, init) {
|
|
2416
|
-
const url2 = readRequestUrl(input);
|
|
2417
|
-
const body = typeof init.body === "string" ? safeParseBody(init.body) : null;
|
|
2418
|
-
const project = readString(body?.project);
|
|
2419
|
-
const model = readString(body?.model);
|
|
2420
|
-
return `${url2}|${project ?? ""}|${model ?? ""}`;
|
|
2421
|
-
}
|
|
2422
|
-
async function waitForRetryCooldown(key, signal) {
|
|
2423
|
-
const until = retryCooldownByKey.get(key);
|
|
2424
|
-
if (!until) {
|
|
2425
|
-
return;
|
|
2426
|
-
}
|
|
2427
|
-
const remaining = until - Date.now();
|
|
2428
|
-
if (remaining <= 0) {
|
|
2429
|
-
retryCooldownByKey.delete(key);
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
if (signal?.aborted) {
|
|
2433
|
-
return;
|
|
2434
|
-
}
|
|
2435
|
-
await wait2(remaining);
|
|
2436
|
-
retryCooldownByKey.delete(key);
|
|
2437
|
-
}
|
|
2438
|
-
function setRetryCooldown(key, delayMs) {
|
|
2439
|
-
if (!cooldownPersistenceInitialized) initCooldownPersistence();
|
|
2440
|
-
const next = Date.now() + delayMs;
|
|
2441
|
-
const current = retryCooldownByKey.get(key) ?? 0;
|
|
2442
|
-
retryCooldownByKey.set(key, Math.max(current, next));
|
|
2443
|
-
cooldownStore.markDirty();
|
|
2444
|
-
}
|
|
2445
|
-
function readRequestUrl(input) {
|
|
2446
|
-
if (typeof input === "string") {
|
|
2447
|
-
return input;
|
|
2448
|
-
}
|
|
2449
|
-
if (input instanceof URL) {
|
|
2450
|
-
return input.toString();
|
|
2451
|
-
}
|
|
2452
|
-
const request = input;
|
|
2453
|
-
if (request.url) {
|
|
2454
|
-
return request.url;
|
|
2455
|
-
}
|
|
2456
|
-
return input.toString();
|
|
2457
|
-
}
|
|
2458
|
-
function safeParseBody(body) {
|
|
2459
|
-
if (!body) {
|
|
2460
|
-
return null;
|
|
2461
|
-
}
|
|
2462
|
-
try {
|
|
2463
|
-
const parsed = JSON.parse(body);
|
|
2464
|
-
if (parsed && typeof parsed === "object") {
|
|
2465
|
-
return parsed;
|
|
2477
|
+
} catch {
|
|
2478
|
+
trackerInstance = new TurnStateTracker(false);
|
|
2466
2479
|
}
|
|
2467
|
-
} catch {
|
|
2468
2480
|
}
|
|
2469
|
-
return
|
|
2481
|
+
return trackerInstance;
|
|
2470
2482
|
}
|
|
2471
|
-
function
|
|
2472
|
-
return
|
|
2483
|
+
function getTurnStateTracker() {
|
|
2484
|
+
return trackerInstance;
|
|
2473
2485
|
}
|
|
2474
2486
|
|
|
2475
2487
|
// node_modules/zod/v4/classic/external.js
|
|
@@ -15043,7 +15055,7 @@ async function fetchTokenRefresh(refreshToken) {
|
|
|
15043
15055
|
}
|
|
15044
15056
|
const delayMs = await resolveRetryDelayMs(response, attempt);
|
|
15045
15057
|
if (delayMs > 0) {
|
|
15046
|
-
await
|
|
15058
|
+
await wait(delayMs);
|
|
15047
15059
|
}
|
|
15048
15060
|
attempt += 1;
|
|
15049
15061
|
continue;
|
|
@@ -15051,7 +15063,7 @@ async function fetchTokenRefresh(refreshToken) {
|
|
|
15051
15063
|
if (attempt >= DEFAULT_MAX_ATTEMPTS || !isRetryableNetworkError(error45)) {
|
|
15052
15064
|
throw error45;
|
|
15053
15065
|
}
|
|
15054
|
-
await
|
|
15066
|
+
await wait(getExponentialDelayWithJitter(attempt));
|
|
15055
15067
|
attempt += 1;
|
|
15056
15068
|
}
|
|
15057
15069
|
}
|
|
@@ -15630,7 +15642,7 @@ async function sendWithRetry(label, url2, init) {
|
|
|
15630
15642
|
return;
|
|
15631
15643
|
}
|
|
15632
15644
|
}
|
|
15633
|
-
await
|
|
15645
|
+
await wait(getExponentialDelayWithJitter(attempt + 1));
|
|
15634
15646
|
}
|
|
15635
15647
|
}
|
|
15636
15648
|
var lastTrafficTime = 0;
|