@kenkaiiii/gg-core 5.39.3 → 5.40.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.
@@ -256,6 +256,7 @@ import path2 from "path";
256
256
  import { randomBytes } from "crypto";
257
257
  import { environmentSecrets, redactText, redactValue } from "@kenkaiiii/gg-ai";
258
258
  var MAX_BYTES = 10 * 1024 * 1024;
259
+ var MIN_HEADROOM_BYTES = 64 * 1024;
259
260
  var fd = null;
260
261
  var bytesWritten = 0;
261
262
  var capped = false;
@@ -266,7 +267,7 @@ var exactSecrets = [];
266
267
  function rotateIfNeeded(filePath) {
267
268
  try {
268
269
  const st = fs.statSync(filePath);
269
- if (st.size < MAX_BYTES) return;
270
+ if (st.size <= MAX_BYTES - MIN_HEADROOM_BYTES) return;
270
271
  const rotated = `${filePath}.1`;
271
272
  try {
272
273
  fs.unlinkSync(rotated);
@@ -561,6 +562,9 @@ var TOKEN_URL = "https://auth.openai.com/oauth/token";
561
562
  var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
562
563
  var SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
563
564
  var JWT_CLAIM_PATH = "https://api.openai.com/auth";
565
+ var CALLBACK_TIMEOUT_MS = 12e4;
566
+ var MAX_PASTE_ATTEMPTS = 3;
567
+ var MIN_RAW_CODE_LENGTH = 10;
564
568
  async function loginOpenAI(callbacks) {
565
569
  const { verifier, challenge } = await generatePKCE();
566
570
  const state = crypto3.randomBytes(16).toString("hex");
@@ -576,20 +580,7 @@ async function loginOpenAI(callbacks) {
576
580
  url.searchParams.set("id_token_add_organizations", "true");
577
581
  url.searchParams.set("codex_cli_simplified_flow", "true");
578
582
  url.searchParams.set("originator", "ggcoder");
579
- let code;
580
- try {
581
- code = await loginWithServer(url.toString(), state, callbacks);
582
- } catch {
583
- callbacks.onOpenUrl(url.toString());
584
- const raw = await callbacks.onPromptCode(
585
- "Could not start local server. Paste the callback URL or code from the browser:"
586
- );
587
- const parsed = parseAuthorizationInput(raw);
588
- if (!parsed.code) {
589
- throw new Error("No authorization code found in input.");
590
- }
591
- code = parsed.code;
592
- }
583
+ const code = await acquireAuthorizationCode(url.toString(), state, callbacks);
593
584
  const creds = await exchangeOpenAICode(code, verifier);
594
585
  const accountId = getAccountId(creds.accessToken);
595
586
  if (!accountId) {
@@ -620,6 +611,7 @@ function parseAuthorizationInput(input) {
620
611
  state: params.get("state") ?? void 0
621
612
  };
622
613
  }
614
+ if (/\s/.test(value) || value.length < MIN_RAW_CODE_LENGTH) return {};
623
615
  return { code: value };
624
616
  }
625
617
  function decodeJwt(token) {
@@ -638,7 +630,55 @@ function getAccountId(accessToken) {
638
630
  const accountId = auth?.chatgpt_account_id;
639
631
  return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
640
632
  }
641
- async function loginWithServer(authUrl, expectedState, callbacks) {
633
+ async function acquireAuthorizationCode(authUrl, expectedState, callbacks) {
634
+ callbacks.onOpenUrl(authUrl);
635
+ callbacks.onStatus("Waiting for browser callback...");
636
+ const done = new AbortController();
637
+ try {
638
+ return await new Promise((resolve, reject) => {
639
+ let serverFailed = false;
640
+ let pasteError;
641
+ const failIfExhausted = () => {
642
+ if (!serverFailed || pasteError === void 0) return;
643
+ reject(
644
+ pasteError instanceof Error ? pasteError : new Error("Could not obtain an authorization code.")
645
+ );
646
+ };
647
+ listenForCallback(expectedState, done.signal).then(resolve, () => {
648
+ serverFailed = true;
649
+ failIfExhausted();
650
+ });
651
+ promptForCode(expectedState, callbacks, done.signal).then(resolve, (err) => {
652
+ if (err instanceof FatalLoginError) {
653
+ reject(err);
654
+ return;
655
+ }
656
+ pasteError = err;
657
+ failIfExhausted();
658
+ });
659
+ });
660
+ } finally {
661
+ done.abort();
662
+ }
663
+ }
664
+ var FatalLoginError = class extends Error {
665
+ };
666
+ async function promptForCode(expectedState, callbacks, signal) {
667
+ let message = "If the browser is on another machine (SSH/headless), paste the callback URL or code here:";
668
+ for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
669
+ if (signal.aborted) throw new Error("Authorization code already received.");
670
+ const raw = await callbacks.onPromptCode(message, signal);
671
+ if (signal.aborted) throw new Error("Authorization code already received.");
672
+ const parsed = parseAuthorizationInput(raw);
673
+ if (parsed.code && parsed.state && parsed.state !== expectedState) {
674
+ throw new FatalLoginError("Authorization state mismatch \u2014 start the login again.");
675
+ }
676
+ if (parsed.code) return parsed.code;
677
+ message = "That didn't contain an authorization code. Paste the full callback URL:";
678
+ }
679
+ throw new Error("No authorization code found in input.");
680
+ }
681
+ async function listenForCallback(expectedState, signal) {
642
682
  return new Promise((resolve, reject) => {
643
683
  let receivedCode = null;
644
684
  const server = http.createServer((req, res) => {
@@ -661,18 +701,18 @@ async function loginWithServer(authUrl, expectedState, callbacks) {
661
701
  server.on("error", (err) => {
662
702
  reject(err);
663
703
  });
664
- server.listen(1455, "127.0.0.1", () => {
665
- callbacks.onOpenUrl(authUrl);
666
- callbacks.onStatus("Waiting for browser callback...");
667
- });
704
+ server.listen(1455, "127.0.0.1");
668
705
  const timeout = setTimeout(() => {
669
- if (!receivedCode) {
670
- server.close();
671
- }
672
- }, 12e4);
706
+ if (!receivedCode) server.close();
707
+ }, CALLBACK_TIMEOUT_MS);
673
708
  timeout.unref();
709
+ const onAbort = () => {
710
+ server.close();
711
+ };
712
+ signal.addEventListener("abort", onAbort, { once: true });
674
713
  server.on("close", () => {
675
714
  clearTimeout(timeout);
715
+ signal.removeEventListener("abort", onAbort);
676
716
  if (receivedCode) {
677
717
  resolve(receivedCode);
678
718
  } else {
@@ -781,7 +821,7 @@ async function loginGemini(callbacks) {
781
821
  url.searchParams.set("state", state);
782
822
  let code;
783
823
  try {
784
- code = await loginWithServer2(url.toString(), redirectUri, state, callbacks);
824
+ code = await loginWithServer(url.toString(), redirectUri, state, callbacks);
785
825
  } catch {
786
826
  callbacks.onOpenUrl(url.toString());
787
827
  const raw = await callbacks.onPromptCode(
@@ -859,7 +899,7 @@ function parseAuthorizationInput2(input) {
859
899
  }
860
900
  return { code: value };
861
901
  }
862
- async function loginWithServer2(authUrl, redirectUri, expectedState, callbacks) {
902
+ async function loginWithServer(authUrl, redirectUri, expectedState, callbacks) {
863
903
  const redirect = new URL(redirectUri);
864
904
  const port = Number(redirect.port);
865
905
  return new Promise((resolve, reject) => {
@@ -1685,12 +1725,20 @@ var AuthStorage = class {
1685
1725
  }
1686
1726
  /**
1687
1727
  * Returns valid credentials, auto-refreshing if expired.
1728
+ *
1688
1729
  * If `forceRefresh` is true, refreshes even if the token hasn't expired
1689
- * (useful when the provider rejects a token with 401 before its stored expiry).
1730
+ * (useful when the provider rejects a token with 401 before its stored
1731
+ * expiry). Callers recovering from a rejection should also pass
1732
+ * `rejectedToken` — see the stampede guard below.
1733
+ *
1690
1734
  * Throws if not logged in.
1691
1735
  */
1692
1736
  async resolveCredentials(provider, opts) {
1693
- await this.ensureLoaded();
1737
+ if (opts?.forceRefresh) {
1738
+ await this.ensureLoaded();
1739
+ } else {
1740
+ await this.ensureFresh();
1741
+ }
1694
1742
  const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
1695
1743
  if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
1696
1744
  await this.reloadLatest();
@@ -1716,7 +1764,8 @@ var AuthStorage = class {
1716
1764
  }
1717
1765
  try {
1718
1766
  return await this.resolveCredentials(dual.oauthKey, {
1719
- ...opts?.forceRefresh ? { forceRefresh: true } : {}
1767
+ ...opts?.forceRefresh ? { forceRefresh: true } : {},
1768
+ ...opts?.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
1720
1769
  });
1721
1770
  } catch (err) {
1722
1771
  if (err instanceof NotLoggedInError && this.data[dual.provider]) {
@@ -1750,7 +1799,15 @@ var AuthStorage = class {
1750
1799
  throw new NotLoggedInError(provider);
1751
1800
  }
1752
1801
  const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
1753
- if (credentialWasReplaced || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
1802
+ const rotatedBySibling = opts?.rejectedToken !== void 0 && latestCreds.accessToken !== opts.rejectedToken;
1803
+ if (credentialWasReplaced || rotatedBySibling || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
1804
+ if (rotatedBySibling && !credentialWasReplaced) {
1805
+ log(
1806
+ "INFO",
1807
+ "auth",
1808
+ `${provider} token was rotated by another gg process \u2014 adopting it instead of refreshing again`
1809
+ );
1810
+ }
1754
1811
  this.data = latest;
1755
1812
  return latestCreds;
1756
1813
  }
@@ -1788,6 +1845,7 @@ var AuthStorage = class {
1788
1845
  return await refreshPromise;
1789
1846
  } finally {
1790
1847
  this.refreshLocks.delete(provider);
1848
+ await this.rememberSnapshot();
1791
1849
  }
1792
1850
  }
1793
1851
  /**
@@ -2410,4 +2468,4 @@ export {
2410
2468
  getSummaryModel,
2411
2469
  getFastModel
2412
2470
  };
2413
- //# sourceMappingURL=chunk-XDE6VUI4.js.map
2471
+ //# sourceMappingURL=chunk-JH7OVHH5.js.map