@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.
package/dist/index.cjs CHANGED
@@ -397,6 +397,7 @@ var import_node_path3 = __toESM(require("path"), 1);
397
397
  var import_node_crypto2 = require("crypto");
398
398
  var import_gg_ai = require("@kenkaiiii/gg-ai");
399
399
  var MAX_BYTES = 10 * 1024 * 1024;
400
+ var MIN_HEADROOM_BYTES = 64 * 1024;
400
401
  var fd = null;
401
402
  var bytesWritten = 0;
402
403
  var capped = false;
@@ -407,7 +408,7 @@ var exactSecrets = [];
407
408
  function rotateIfNeeded(filePath) {
408
409
  try {
409
410
  const st = import_node_fs2.default.statSync(filePath);
410
- if (st.size < MAX_BYTES) return;
411
+ if (st.size <= MAX_BYTES - MIN_HEADROOM_BYTES) return;
411
412
  const rotated = `${filePath}.1`;
412
413
  try {
413
414
  import_node_fs2.default.unlinkSync(rotated);
@@ -702,6 +703,9 @@ var TOKEN_URL = "https://auth.openai.com/oauth/token";
702
703
  var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
703
704
  var SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
704
705
  var JWT_CLAIM_PATH = "https://api.openai.com/auth";
706
+ var CALLBACK_TIMEOUT_MS = 12e4;
707
+ var MAX_PASTE_ATTEMPTS = 3;
708
+ var MIN_RAW_CODE_LENGTH = 10;
705
709
  async function loginOpenAI(callbacks) {
706
710
  const { verifier, challenge } = await generatePKCE();
707
711
  const state = import_node_crypto4.default.randomBytes(16).toString("hex");
@@ -717,20 +721,7 @@ async function loginOpenAI(callbacks) {
717
721
  url.searchParams.set("id_token_add_organizations", "true");
718
722
  url.searchParams.set("codex_cli_simplified_flow", "true");
719
723
  url.searchParams.set("originator", "ggcoder");
720
- let code;
721
- try {
722
- code = await loginWithServer(url.toString(), state, callbacks);
723
- } catch {
724
- callbacks.onOpenUrl(url.toString());
725
- const raw = await callbacks.onPromptCode(
726
- "Could not start local server. Paste the callback URL or code from the browser:"
727
- );
728
- const parsed = parseAuthorizationInput(raw);
729
- if (!parsed.code) {
730
- throw new Error("No authorization code found in input.");
731
- }
732
- code = parsed.code;
733
- }
724
+ const code = await acquireAuthorizationCode(url.toString(), state, callbacks);
734
725
  const creds = await exchangeOpenAICode(code, verifier);
735
726
  const accountId = getAccountId(creds.accessToken);
736
727
  if (!accountId) {
@@ -761,6 +752,7 @@ function parseAuthorizationInput(input) {
761
752
  state: params.get("state") ?? void 0
762
753
  };
763
754
  }
755
+ if (/\s/.test(value) || value.length < MIN_RAW_CODE_LENGTH) return {};
764
756
  return { code: value };
765
757
  }
766
758
  function decodeJwt(token) {
@@ -779,7 +771,55 @@ function getAccountId(accessToken) {
779
771
  const accountId = auth?.chatgpt_account_id;
780
772
  return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
781
773
  }
782
- async function loginWithServer(authUrl, expectedState, callbacks) {
774
+ async function acquireAuthorizationCode(authUrl, expectedState, callbacks) {
775
+ callbacks.onOpenUrl(authUrl);
776
+ callbacks.onStatus("Waiting for browser callback...");
777
+ const done = new AbortController();
778
+ try {
779
+ return await new Promise((resolve, reject) => {
780
+ let serverFailed = false;
781
+ let pasteError;
782
+ const failIfExhausted = () => {
783
+ if (!serverFailed || pasteError === void 0) return;
784
+ reject(
785
+ pasteError instanceof Error ? pasteError : new Error("Could not obtain an authorization code.")
786
+ );
787
+ };
788
+ listenForCallback(expectedState, done.signal).then(resolve, () => {
789
+ serverFailed = true;
790
+ failIfExhausted();
791
+ });
792
+ promptForCode(expectedState, callbacks, done.signal).then(resolve, (err) => {
793
+ if (err instanceof FatalLoginError) {
794
+ reject(err);
795
+ return;
796
+ }
797
+ pasteError = err;
798
+ failIfExhausted();
799
+ });
800
+ });
801
+ } finally {
802
+ done.abort();
803
+ }
804
+ }
805
+ var FatalLoginError = class extends Error {
806
+ };
807
+ async function promptForCode(expectedState, callbacks, signal) {
808
+ let message = "If the browser is on another machine (SSH/headless), paste the callback URL or code here:";
809
+ for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
810
+ if (signal.aborted) throw new Error("Authorization code already received.");
811
+ const raw = await callbacks.onPromptCode(message, signal);
812
+ if (signal.aborted) throw new Error("Authorization code already received.");
813
+ const parsed = parseAuthorizationInput(raw);
814
+ if (parsed.code && parsed.state && parsed.state !== expectedState) {
815
+ throw new FatalLoginError("Authorization state mismatch \u2014 start the login again.");
816
+ }
817
+ if (parsed.code) return parsed.code;
818
+ message = "That didn't contain an authorization code. Paste the full callback URL:";
819
+ }
820
+ throw new Error("No authorization code found in input.");
821
+ }
822
+ async function listenForCallback(expectedState, signal) {
783
823
  return new Promise((resolve, reject) => {
784
824
  let receivedCode = null;
785
825
  const server = import_node_http.default.createServer((req, res) => {
@@ -802,18 +842,18 @@ async function loginWithServer(authUrl, expectedState, callbacks) {
802
842
  server.on("error", (err) => {
803
843
  reject(err);
804
844
  });
805
- server.listen(1455, "127.0.0.1", () => {
806
- callbacks.onOpenUrl(authUrl);
807
- callbacks.onStatus("Waiting for browser callback...");
808
- });
845
+ server.listen(1455, "127.0.0.1");
809
846
  const timeout = setTimeout(() => {
810
- if (!receivedCode) {
811
- server.close();
812
- }
813
- }, 12e4);
847
+ if (!receivedCode) server.close();
848
+ }, CALLBACK_TIMEOUT_MS);
814
849
  timeout.unref();
850
+ const onAbort = () => {
851
+ server.close();
852
+ };
853
+ signal.addEventListener("abort", onAbort, { once: true });
815
854
  server.on("close", () => {
816
855
  clearTimeout(timeout);
856
+ signal.removeEventListener("abort", onAbort);
817
857
  if (receivedCode) {
818
858
  resolve(receivedCode);
819
859
  } else {
@@ -922,7 +962,7 @@ async function loginGemini(callbacks) {
922
962
  url.searchParams.set("state", state);
923
963
  let code;
924
964
  try {
925
- code = await loginWithServer2(url.toString(), redirectUri, state, callbacks);
965
+ code = await loginWithServer(url.toString(), redirectUri, state, callbacks);
926
966
  } catch {
927
967
  callbacks.onOpenUrl(url.toString());
928
968
  const raw = await callbacks.onPromptCode(
@@ -1000,7 +1040,7 @@ function parseAuthorizationInput2(input) {
1000
1040
  }
1001
1041
  return { code: value };
1002
1042
  }
1003
- async function loginWithServer2(authUrl, redirectUri, expectedState, callbacks) {
1043
+ async function loginWithServer(authUrl, redirectUri, expectedState, callbacks) {
1004
1044
  const redirect = new URL(redirectUri);
1005
1045
  const port = Number(redirect.port);
1006
1046
  return new Promise((resolve, reject) => {
@@ -1826,12 +1866,20 @@ var AuthStorage = class {
1826
1866
  }
1827
1867
  /**
1828
1868
  * Returns valid credentials, auto-refreshing if expired.
1869
+ *
1829
1870
  * If `forceRefresh` is true, refreshes even if the token hasn't expired
1830
- * (useful when the provider rejects a token with 401 before its stored expiry).
1871
+ * (useful when the provider rejects a token with 401 before its stored
1872
+ * expiry). Callers recovering from a rejection should also pass
1873
+ * `rejectedToken` — see the stampede guard below.
1874
+ *
1831
1875
  * Throws if not logged in.
1832
1876
  */
1833
1877
  async resolveCredentials(provider, opts) {
1834
- await this.ensureLoaded();
1878
+ if (opts?.forceRefresh) {
1879
+ await this.ensureLoaded();
1880
+ } else {
1881
+ await this.ensureFresh();
1882
+ }
1835
1883
  const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
1836
1884
  if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
1837
1885
  await this.reloadLatest();
@@ -1857,7 +1905,8 @@ var AuthStorage = class {
1857
1905
  }
1858
1906
  try {
1859
1907
  return await this.resolveCredentials(dual.oauthKey, {
1860
- ...opts?.forceRefresh ? { forceRefresh: true } : {}
1908
+ ...opts?.forceRefresh ? { forceRefresh: true } : {},
1909
+ ...opts?.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
1861
1910
  });
1862
1911
  } catch (err) {
1863
1912
  if (err instanceof NotLoggedInError && this.data[dual.provider]) {
@@ -1891,7 +1940,15 @@ var AuthStorage = class {
1891
1940
  throw new NotLoggedInError(provider);
1892
1941
  }
1893
1942
  const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
1894
- if (credentialWasReplaced || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
1943
+ const rotatedBySibling = opts?.rejectedToken !== void 0 && latestCreds.accessToken !== opts.rejectedToken;
1944
+ if (credentialWasReplaced || rotatedBySibling || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
1945
+ if (rotatedBySibling && !credentialWasReplaced) {
1946
+ log(
1947
+ "INFO",
1948
+ "auth",
1949
+ `${provider} token was rotated by another gg process \u2014 adopting it instead of refreshing again`
1950
+ );
1951
+ }
1895
1952
  this.data = latest;
1896
1953
  return latestCreds;
1897
1954
  }
@@ -1929,6 +1986,7 @@ var AuthStorage = class {
1929
1986
  return await refreshPromise;
1930
1987
  } finally {
1931
1988
  this.refreshLocks.delete(provider);
1989
+ await this.rememberSnapshot();
1932
1990
  }
1933
1991
  }
1934
1992
  /**