@prestyj/core 5.10.0 → 5.12.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
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  NotLoggedInError: () => NotLoggedInError,
42
42
  SubscriptionUsageError: () => SubscriptionUsageError,
43
43
  TelegramBot: () => TelegramBot,
44
+ XAI_OAUTH_KEY: () => XAI_OAUTH_KEY,
44
45
  XIAOMI_CREDITS_KEY: () => XIAOMI_CREDITS_KEY,
45
46
  clearLocalDiscoveryCache: () => clearLocalDiscoveryCache,
46
47
  clearRuntimeModels: () => clearRuntimeModels,
@@ -49,6 +50,8 @@ __export(index_exports, {
49
50
  decodeOggOpus: () => decodeOggOpus,
50
51
  discoverLocalModels: () => discoverLocalModels,
51
52
  downmixToMono: () => downmixToMono,
53
+ dualAuthProvider: () => dualAuthProvider,
54
+ dualAuthProviderByOAuthKey: () => dualAuthProviderByOAuthKey,
52
55
  endpointRoot: () => endpointRoot,
53
56
  fetchSubscriptionUsage: () => fetchSubscriptionUsage,
54
57
  findProbedModel: () => findProbedModel,
@@ -73,6 +76,9 @@ __export(index_exports, {
73
76
  getSupportedThinkingLevels: () => getSupportedThinkingLevels,
74
77
  getToolResultCharLimit: () => getToolResultCharLimit,
75
78
  getVideoByteLimit: () => getVideoByteLimit,
79
+ grokCliBaseUrl: () => grokCliBaseUrl,
80
+ grokCliHeaders: () => grokCliHeaders,
81
+ isGrokCliEndpoint: () => isGrokCliEndpoint,
76
82
  isKimiCodingEndpoint: () => isKimiCodingEndpoint,
77
83
  isLocalModelId: () => isLocalModelId,
78
84
  isLoggerOpen: () => isLoggerOpen,
@@ -86,14 +92,18 @@ __export(index_exports, {
86
92
  loginGemini: () => loginGemini,
87
93
  loginKimi: () => loginKimi,
88
94
  loginOpenAI: () => loginOpenAI,
95
+ loginXai: () => loginXai,
96
+ oauthStorageKey: () => oauthStorageKey,
89
97
  openLog: () => openLog,
90
98
  parseLocalModelId: () => parseLocalModelId,
91
99
  probeEndpoint: () => probeEndpoint,
100
+ providerStorageKeys: () => providerStorageKeys,
92
101
  readStoredBaseUrlSync: () => readStoredBaseUrlSync,
93
102
  refreshAnthropicToken: () => refreshAnthropicToken,
94
103
  refreshGeminiToken: () => refreshGeminiToken,
95
104
  refreshKimiToken: () => refreshKimiToken,
96
105
  refreshOpenAIToken: () => refreshOpenAIToken,
106
+ refreshXaiToken: () => refreshXaiToken,
97
107
  registerLogCleanup: () => registerLogCleanup,
98
108
  registerRuntimeModels: () => registerRuntimeModels,
99
109
  resample: () => resample,
@@ -128,6 +138,7 @@ function getAppPaths() {
128
138
  agentHomeFile: import_node_path.default.join(agentDir, "agent-home.json"),
129
139
  mcpFile: import_node_path.default.join(agentDir, "mcp.json"),
130
140
  mcpAuthFile: import_node_path.default.join(agentDir, "mcp-auth.json"),
141
+ mcpCatalogFile: import_node_path.default.join(agentDir, "mcp-catalog.json"),
131
142
  logFile: import_node_path.default.join(agentDir, "debug.log"),
132
143
  skillsDir: import_node_path.default.join(agentDir, "skills"),
133
144
  extensionsDir: import_node_path.default.join(agentDir, "extensions"),
@@ -386,6 +397,7 @@ var import_node_path3 = __toESM(require("path"), 1);
386
397
  var import_node_crypto2 = require("crypto");
387
398
  var import_ai = require("@prestyj/ai");
388
399
  var MAX_BYTES = 10 * 1024 * 1024;
400
+ var MIN_HEADROOM_BYTES = 64 * 1024;
389
401
  var fd = null;
390
402
  var bytesWritten = 0;
391
403
  var capped = false;
@@ -396,7 +408,7 @@ var exactSecrets = [];
396
408
  function rotateIfNeeded(filePath) {
397
409
  try {
398
410
  const st = import_node_fs2.default.statSync(filePath);
399
- if (st.size < MAX_BYTES) return;
411
+ if (st.size <= MAX_BYTES - MIN_HEADROOM_BYTES) return;
400
412
  const rotated = `${filePath}.1`;
401
413
  try {
402
414
  import_node_fs2.default.unlinkSync(rotated);
@@ -691,6 +703,9 @@ var TOKEN_URL = "https://auth.openai.com/oauth/token";
691
703
  var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
692
704
  var SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
693
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;
694
709
  async function loginOpenAI(callbacks) {
695
710
  const { verifier, challenge } = await generatePKCE();
696
711
  const state = import_node_crypto4.default.randomBytes(16).toString("hex");
@@ -706,20 +721,7 @@ async function loginOpenAI(callbacks) {
706
721
  url.searchParams.set("id_token_add_organizations", "true");
707
722
  url.searchParams.set("codex_cli_simplified_flow", "true");
708
723
  url.searchParams.set("originator", "ezcoder");
709
- let code;
710
- try {
711
- code = await loginWithServer(url.toString(), state, callbacks);
712
- } catch {
713
- callbacks.onOpenUrl(url.toString());
714
- const raw = await callbacks.onPromptCode(
715
- "Could not start local server. Paste the callback URL or code from the browser:"
716
- );
717
- const parsed = parseAuthorizationInput(raw);
718
- if (!parsed.code) {
719
- throw new Error("No authorization code found in input.");
720
- }
721
- code = parsed.code;
722
- }
724
+ const code = await acquireAuthorizationCode(url.toString(), state, callbacks);
723
725
  const creds = await exchangeOpenAICode(code, verifier);
724
726
  const accountId = getAccountId(creds.accessToken);
725
727
  if (!accountId) {
@@ -750,6 +752,7 @@ function parseAuthorizationInput(input) {
750
752
  state: params.get("state") ?? void 0
751
753
  };
752
754
  }
755
+ if (/\s/.test(value) || value.length < MIN_RAW_CODE_LENGTH) return {};
753
756
  return { code: value };
754
757
  }
755
758
  function decodeJwt(token) {
@@ -768,7 +771,55 @@ function getAccountId(accessToken) {
768
771
  const accountId = auth?.chatgpt_account_id;
769
772
  return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
770
773
  }
771
- 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) {
772
823
  return new Promise((resolve, reject) => {
773
824
  let receivedCode = null;
774
825
  const server = import_node_http.default.createServer((req, res) => {
@@ -791,18 +842,18 @@ async function loginWithServer(authUrl, expectedState, callbacks) {
791
842
  server.on("error", (err) => {
792
843
  reject(err);
793
844
  });
794
- server.listen(1455, "127.0.0.1", () => {
795
- callbacks.onOpenUrl(authUrl);
796
- callbacks.onStatus("Waiting for browser callback...");
797
- });
845
+ server.listen(1455, "127.0.0.1");
798
846
  const timeout = setTimeout(() => {
799
- if (!receivedCode) {
800
- server.close();
801
- }
802
- }, 12e4);
847
+ if (!receivedCode) server.close();
848
+ }, CALLBACK_TIMEOUT_MS);
803
849
  timeout.unref();
850
+ const onAbort = () => {
851
+ server.close();
852
+ };
853
+ signal.addEventListener("abort", onAbort, { once: true });
804
854
  server.on("close", () => {
805
855
  clearTimeout(timeout);
856
+ signal.removeEventListener("abort", onAbort);
806
857
  if (receivedCode) {
807
858
  resolve(receivedCode);
808
859
  } else {
@@ -864,8 +915,8 @@ async function refreshOpenAIToken(refreshToken) {
864
915
  // src/oauth/gemini.ts
865
916
  var import_node_http2 = __toESM(require("http"), 1);
866
917
  var import_node_crypto5 = __toESM(require("crypto"), 1);
867
- var CLIENT_ID_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_ID";
868
- var CLIENT_SECRET_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_SECRET";
918
+ var CLIENT_ID_ENV = "EZCODER_GEMINI_OAUTH_CLIENT_ID";
919
+ var CLIENT_SECRET_ENV = "EZCODER_GEMINI_OAUTH_CLIENT_SECRET";
869
920
  var DEFAULT_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
870
921
  var DEFAULT_CLIENT_SECRET = "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";
871
922
  var AUTHORIZE_URL3 = "https://accounts.google.com/o/oauth2/v2/auth";
@@ -895,13 +946,13 @@ var CodeAssistHttpError = class extends Error {
895
946
  }
896
947
  };
897
948
  async function loginGemini(callbacks) {
898
- const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
949
+ const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
899
950
  const { verifier, challenge } = await generatePKCE();
900
951
  const state = import_node_crypto5.default.randomBytes(32).toString("hex");
901
952
  const redirectUri = await getLoopbackRedirectUri();
902
953
  const url = new URL(AUTHORIZE_URL3);
903
954
  url.searchParams.set("response_type", "code");
904
- url.searchParams.set("client_id", clientId);
955
+ url.searchParams.set("client_id", clientId2);
905
956
  url.searchParams.set("redirect_uri", redirectUri);
906
957
  url.searchParams.set("scope", SCOPE2);
907
958
  url.searchParams.set("access_type", "offline");
@@ -911,7 +962,7 @@ async function loginGemini(callbacks) {
911
962
  url.searchParams.set("state", state);
912
963
  let code;
913
964
  try {
914
- code = await loginWithServer2(url.toString(), redirectUri, state, callbacks);
965
+ code = await loginWithServer(url.toString(), redirectUri, state, callbacks);
915
966
  } catch {
916
967
  callbacks.onOpenUrl(url.toString());
917
968
  const raw = await callbacks.onPromptCode(
@@ -926,7 +977,7 @@ async function loginGemini(callbacks) {
926
977
  }
927
978
  code = parsed.code;
928
979
  }
929
- const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId, clientSecret);
980
+ const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret);
930
981
  callbacks.onStatus("Setting up Gemini Code Assist access...");
931
982
  const projectId = await setupCodeAssistProject(creds.accessToken, callbacks);
932
983
  return {
@@ -935,11 +986,11 @@ async function loginGemini(callbacks) {
935
986
  };
936
987
  }
937
988
  async function refreshGeminiToken(refreshToken) {
938
- const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
989
+ const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
939
990
  const data = await postTokenRequest2({
940
991
  grant_type: "refresh_token",
941
992
  refresh_token: refreshToken,
942
- client_id: clientId,
993
+ client_id: clientId2,
943
994
  client_secret: clientSecret
944
995
  });
945
996
  return {
@@ -949,9 +1000,9 @@ async function refreshGeminiToken(refreshToken) {
949
1000
  };
950
1001
  }
951
1002
  function getGeminiOAuthClientCredentials() {
952
- const clientId = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
1003
+ const clientId2 = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
953
1004
  const clientSecret = process.env[CLIENT_SECRET_ENV]?.trim() || DEFAULT_CLIENT_SECRET;
954
- return { clientId, clientSecret };
1005
+ return { clientId: clientId2, clientSecret };
955
1006
  }
956
1007
  async function getLoopbackRedirectUri() {
957
1008
  return new Promise((resolve, reject) => {
@@ -989,7 +1040,7 @@ function parseAuthorizationInput2(input) {
989
1040
  }
990
1041
  return { code: value };
991
1042
  }
992
- async function loginWithServer2(authUrl, redirectUri, expectedState, callbacks) {
1043
+ async function loginWithServer(authUrl, redirectUri, expectedState, callbacks) {
993
1044
  const redirect = new URL(redirectUri);
994
1045
  const port = Number(redirect.port);
995
1046
  return new Promise((resolve, reject) => {
@@ -1030,10 +1081,10 @@ async function loginWithServer2(authUrl, redirectUri, expectedState, callbacks)
1030
1081
  });
1031
1082
  });
1032
1083
  }
1033
- async function exchangeGeminiCode(code, verifier, redirectUri, clientId, clientSecret) {
1084
+ async function exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret) {
1034
1085
  const data = await postTokenRequest2({
1035
1086
  grant_type: "authorization_code",
1036
- client_id: clientId,
1087
+ client_id: clientId2,
1037
1088
  client_secret: clientSecret,
1038
1089
  code,
1039
1090
  redirect_uri: redirectUri,
@@ -1224,6 +1275,204 @@ function codeAssistHeaders(accessToken) {
1224
1275
  };
1225
1276
  }
1226
1277
 
1278
+ // src/oauth/xai.ts
1279
+ var DEFAULT_CLIENT_ID2 = "b1a00492-073a-47ea-816f-4c329264a828";
1280
+ var DEFAULT_ISSUER = "https://auth.x.ai";
1281
+ var DEFAULT_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
1282
+ var OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
1283
+ var DEFAULT_GROK_CLI_VERSION = "0.2.101";
1284
+ var DEVICE_TIMEOUT_FALLBACK_MS = 10 * 60 * 1e3;
1285
+ var DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
1286
+ function issuer() {
1287
+ return (process.env.XAI_OAUTH_ISSUER ?? DEFAULT_ISSUER).replace(/\/+$/, "");
1288
+ }
1289
+ function clientId() {
1290
+ return process.env.XAI_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID2;
1291
+ }
1292
+ function grokCliBaseUrl() {
1293
+ return (process.env.XAI_CLI_BASE_URL ?? DEFAULT_CLI_BASE_URL).replace(/\/+$/, "");
1294
+ }
1295
+ function grokCliVersion() {
1296
+ const raw = process.env.GROK_CLI_VERSION ?? DEFAULT_GROK_CLI_VERSION;
1297
+ const cleaned = raw.replace(/[^\u0020-\u007E]/g, "").trim();
1298
+ return cleaned.length > 0 ? cleaned : DEFAULT_GROK_CLI_VERSION;
1299
+ }
1300
+ function grokCliHeaders(modelId) {
1301
+ return {
1302
+ "X-XAI-Token-Auth": "xai-grok-cli",
1303
+ "x-grok-client-version": grokCliVersion(),
1304
+ "x-grok-client-identifier": "ezcoder",
1305
+ ...modelId ? { "x-grok-model-override": modelId } : {}
1306
+ };
1307
+ }
1308
+ function isGrokCliEndpoint(baseUrl) {
1309
+ if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
1310
+ const normalized = baseUrl.replace(/\/+$/, "");
1311
+ return normalized === grokCliBaseUrl() || /(^|\.)grok\.com/i.test(normalized);
1312
+ }
1313
+ async function postForm2(endpoint, params) {
1314
+ const response = await fetch(`${issuer()}${endpoint}`, {
1315
+ method: "POST",
1316
+ headers: {
1317
+ "Content-Type": "application/x-www-form-urlencoded",
1318
+ Accept: "application/json",
1319
+ "User-Agent": `grok-cli/${grokCliVersion()}`
1320
+ },
1321
+ body: new URLSearchParams(params).toString()
1322
+ });
1323
+ let data = {};
1324
+ try {
1325
+ const parsed = await response.json();
1326
+ if (parsed && typeof parsed === "object") data = parsed;
1327
+ } catch {
1328
+ }
1329
+ return { status: response.status, data };
1330
+ }
1331
+ function errorDetail2(data) {
1332
+ const desc = data.error_description ?? data.message ?? data.error;
1333
+ return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
1334
+ }
1335
+ function jwtExpirySeconds(token) {
1336
+ const payload = token.split(".")[1];
1337
+ if (!payload) return void 0;
1338
+ try {
1339
+ const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
1340
+ "utf-8"
1341
+ );
1342
+ const claims = JSON.parse(json);
1343
+ if (!claims || typeof claims !== "object") return void 0;
1344
+ const exp = claims.exp;
1345
+ return typeof exp === "number" && Number.isFinite(exp) && exp > 0 ? exp : void 0;
1346
+ } catch {
1347
+ return void 0;
1348
+ }
1349
+ }
1350
+ function credsFromTokenResponse2(data, opts) {
1351
+ const accessToken = data.access_token;
1352
+ const responseRefreshToken = data.refresh_token;
1353
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
1354
+ throw new Error("Grok OAuth response missing access_token.");
1355
+ }
1356
+ const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
1357
+ if (refreshToken.length === 0) {
1358
+ throw new Error(
1359
+ "Grok OAuth response missing refresh_token \u2014 the offline_access scope was not granted."
1360
+ );
1361
+ }
1362
+ const responseExpiresIn = Number(data.expires_in);
1363
+ let expiresIn;
1364
+ if (Number.isFinite(responseExpiresIn) && responseExpiresIn > 0) {
1365
+ expiresIn = responseExpiresIn;
1366
+ } else {
1367
+ const exp = jwtExpirySeconds(accessToken);
1368
+ const fromJwt = exp !== void 0 ? exp - Math.floor(Date.now() / 1e3) : 0;
1369
+ expiresIn = fromJwt > 0 ? fromJwt : DEFAULT_TOKEN_LIFETIME_SECONDS;
1370
+ }
1371
+ return {
1372
+ accessToken,
1373
+ refreshToken,
1374
+ expiresAt: Date.now() + expiresIn * 1e3,
1375
+ expiresIn,
1376
+ baseUrl: grokCliBaseUrl()
1377
+ };
1378
+ }
1379
+ async function requestDeviceAuthorization2() {
1380
+ const { status, data } = await postForm2("/oauth2/device/code", {
1381
+ client_id: clientId(),
1382
+ scope: OAUTH_SCOPE
1383
+ });
1384
+ if (status !== 200) {
1385
+ throw new Error(`Grok device authorization failed (${status}): ${errorDetail2(data)}`);
1386
+ }
1387
+ const userCode = data.user_code;
1388
+ const deviceCode = data.device_code;
1389
+ const verificationUriComplete = data.verification_uri_complete;
1390
+ if (typeof userCode !== "string" || typeof deviceCode !== "string") {
1391
+ throw new Error("Grok device authorization response missing user_code/device_code.");
1392
+ }
1393
+ return {
1394
+ userCode,
1395
+ deviceCode,
1396
+ verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
1397
+ verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
1398
+ interval: Number(data.interval ?? 5) || 5,
1399
+ // RFC 8628 §3.2: the server states how long the device code lives. Honor it
1400
+ // rather than imposing our own budget — polling a code we know is dead only
1401
+ // burns requests, and a longer local window would keep a user waiting past
1402
+ // the point the code can ever succeed.
1403
+ expiresInMs: (Number(data.expires_in) || 0) > 0 ? Number(data.expires_in) * 1e3 : DEVICE_TIMEOUT_FALLBACK_MS
1404
+ };
1405
+ }
1406
+ async function pollDeviceToken2(deviceCode) {
1407
+ const { status, data } = await postForm2("/oauth2/token", {
1408
+ client_id: clientId(),
1409
+ device_code: deviceCode,
1410
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
1411
+ });
1412
+ if (status === 200 && typeof data.access_token === "string") {
1413
+ return { kind: "success", creds: credsFromTokenResponse2(data) };
1414
+ }
1415
+ if (status >= 500) {
1416
+ throw new Error(`Grok token polling server error (${status}): ${errorDetail2(data)}`);
1417
+ }
1418
+ const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
1419
+ switch (errorCode) {
1420
+ case "authorization_pending":
1421
+ return { kind: "pending" };
1422
+ case "slow_down":
1423
+ return { kind: "slow_down" };
1424
+ case "expired_token":
1425
+ return { kind: "expired" };
1426
+ case "access_denied":
1427
+ return { kind: "denied" };
1428
+ default:
1429
+ throw new Error(`Grok token polling failed (${status}): ${errorDetail2(data)}`);
1430
+ }
1431
+ }
1432
+ function sleep2(ms) {
1433
+ return new Promise((resolve) => {
1434
+ setTimeout(resolve, ms);
1435
+ });
1436
+ }
1437
+ async function loginXai(callbacks) {
1438
+ const auth = await requestDeviceAuthorization2();
1439
+ callbacks.onStatus(
1440
+ `Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
1441
+ );
1442
+ callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
1443
+ callbacks.onStatus("Waiting for you to authorize in the browser...");
1444
+ const deadline = Date.now() + auth.expiresInMs;
1445
+ let interval = Math.max(auth.interval, 1);
1446
+ while (Date.now() < deadline) {
1447
+ const remaining = deadline - Date.now();
1448
+ await sleep2(Math.min(interval * 1e3, remaining));
1449
+ const result = await pollDeviceToken2(auth.deviceCode);
1450
+ if (result.kind === "success") return result.creds;
1451
+ if (result.kind === "denied") {
1452
+ throw new Error("Grok authorization was denied.");
1453
+ }
1454
+ if (result.kind === "expired") {
1455
+ throw new Error("Grok device code expired. Please run login again.");
1456
+ }
1457
+ if (result.kind === "slow_down") {
1458
+ interval += 5;
1459
+ }
1460
+ }
1461
+ throw new Error("Grok login timed out. Please run login again.");
1462
+ }
1463
+ async function refreshXaiToken(refreshToken) {
1464
+ const { status, data } = await postForm2("/oauth2/token", {
1465
+ client_id: clientId(),
1466
+ grant_type: "refresh_token",
1467
+ refresh_token: refreshToken
1468
+ });
1469
+ if (status === 200 && typeof data.access_token === "string") {
1470
+ return credsFromTokenResponse2(data, { fallbackRefreshToken: refreshToken });
1471
+ }
1472
+ const errorCode = typeof data.error === "string" ? data.error : "";
1473
+ throw new Error(`Grok token refresh failed (${status}): ${errorCode || errorDetail2(data)}`);
1474
+ }
1475
+
1227
1476
  // src/file-lock.ts
1228
1477
  var import_promises2 = __toESM(require("fs/promises"), 1);
1229
1478
  var import_promises3 = require("timers/promises");
@@ -1288,18 +1537,49 @@ function isAlive(pid) {
1288
1537
 
1289
1538
  // src/auth-storage.ts
1290
1539
  var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
1540
+ var XAI_OAUTH_KEY = "xai-oauth";
1541
+ var DUAL_AUTH_PROVIDERS = [
1542
+ {
1543
+ provider: "moonshot",
1544
+ oauthKey: MOONSHOT_OAUTH_KEY,
1545
+ oauthLabel: "Kimi OAuth",
1546
+ apiKeyLabel: "Moonshot API key",
1547
+ restoreHint: 'Run "ezcoder login" and choose Kimi OAuth to restore OAuth auth.'
1548
+ },
1549
+ {
1550
+ provider: "xai",
1551
+ oauthKey: XAI_OAUTH_KEY,
1552
+ oauthLabel: "Grok OAuth",
1553
+ apiKeyLabel: "xAI API key",
1554
+ restoreHint: 'Run "ezcoder login" and choose Grok OAuth to restore OAuth auth.'
1555
+ }
1556
+ ];
1557
+ function dualAuthProvider(provider) {
1558
+ return DUAL_AUTH_PROVIDERS.find((entry) => entry.provider === provider);
1559
+ }
1560
+ function dualAuthProviderByOAuthKey(storageKey) {
1561
+ return DUAL_AUTH_PROVIDERS.find((entry) => entry.oauthKey === storageKey);
1562
+ }
1563
+ function oauthStorageKey(provider) {
1564
+ return dualAuthProvider(provider)?.oauthKey;
1565
+ }
1566
+ function providerStorageKeys(provider) {
1567
+ const dual = dualAuthProvider(provider);
1568
+ return dual ? [dual.oauthKey, dual.provider] : [provider];
1569
+ }
1291
1570
  var XIAOMI_CREDITS_KEY = "xiaomi-credits";
1292
1571
  var LOCAL_AUTH_KEY_PREFIX = "local:";
1293
1572
  var LOCAL_CREDENTIAL_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
1294
1573
  function activeBaseUrlEntry(data, provider) {
1295
- if (provider === "moonshot") {
1296
- const oauth = data[MOONSHOT_OAUTH_KEY];
1574
+ const dual = dualAuthProvider(provider);
1575
+ if (dual) {
1576
+ const oauth = data[dual.oauthKey];
1297
1577
  if (oauth) {
1298
1578
  const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
1299
- if (Date.now() < exhaustedUntil && data["moonshot"]) return data["moonshot"];
1579
+ if (Date.now() < exhaustedUntil && data[dual.provider]) return data[dual.provider];
1300
1580
  return oauth;
1301
1581
  }
1302
- return data["moonshot"];
1582
+ return data[dual.provider];
1303
1583
  }
1304
1584
  return data[provider];
1305
1585
  }
@@ -1334,6 +1614,16 @@ var AuthStorage = class {
1334
1614
  data = {};
1335
1615
  filePath;
1336
1616
  loaded = false;
1617
+ /**
1618
+ * mtime+size of the file as of the cached snapshot (`size: -1` = no file).
1619
+ * auth.json is shared: the desktop app writes API keys and disconnects
1620
+ * NATIVELY (so they work with no daemon running), and every window/process has
1621
+ * its own AuthStorage. A load-once cache therefore goes stale — the sidecar
1622
+ * would keep listing models for a provider just disconnected, and hide the
1623
+ * ones just connected, until the daemon restarted.
1624
+ */
1625
+ snapshotMtimeMs = 0;
1626
+ snapshotSize = -1;
1337
1627
  /** Per-provider lock to serialize concurrent refresh calls. */
1338
1628
  refreshLocks = /* @__PURE__ */ new Map();
1339
1629
  constructor(filePath) {
@@ -1345,12 +1635,12 @@ var AuthStorage = class {
1345
1635
  }
1346
1636
  /** List provider keys with stored credentials. */
1347
1637
  async listProviders() {
1348
- await this.ensureLoaded();
1638
+ await this.ensureFresh();
1349
1639
  return Object.keys(this.data);
1350
1640
  }
1351
1641
  /** True if credentials exist for `provider`. */
1352
1642
  async hasCredentials(provider) {
1353
- await this.ensureLoaded();
1643
+ await this.ensureFresh();
1354
1644
  return Boolean(this.data[provider]);
1355
1645
  }
1356
1646
  /**
@@ -1361,18 +1651,19 @@ var AuthStorage = class {
1361
1651
  * instead of re-deriving the same order.
1362
1652
  */
1363
1653
  async pickStorageKey(keys) {
1364
- await this.ensureLoaded();
1654
+ await this.ensureFresh();
1365
1655
  return keys.find((key) => Boolean(this.data[key]));
1366
1656
  }
1367
1657
  /**
1368
- * True if the user has any usable auth for the logical provider. For
1369
- * `moonshot` this is satisfied by either the Kimi OAuth credential or the
1370
- * Moonshot API key.
1658
+ * True if the user has any usable auth for the logical provider. For a
1659
+ * dual-auth provider (Kimi/Grok) either the OAuth credential or the API key
1660
+ * satisfies it.
1371
1661
  */
1372
1662
  async hasProviderAuth(provider) {
1373
- await this.ensureLoaded();
1374
- if (provider === "moonshot") {
1375
- return Boolean(this.data[MOONSHOT_OAUTH_KEY] || this.data["moonshot"]);
1663
+ await this.ensureFresh();
1664
+ const dual = dualAuthProvider(provider);
1665
+ if (dual) {
1666
+ return Boolean(this.data[dual.oauthKey] || this.data[dual.provider]);
1376
1667
  }
1377
1668
  if (provider === "xiaomi") {
1378
1669
  return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
@@ -1406,14 +1697,16 @@ var AuthStorage = class {
1406
1697
  }
1407
1698
  /**
1408
1699
  * True if the active credential for `provider` is a static API key with no
1409
- * refresh mechanism. For `moonshot` this is only true when the Kimi OAuth
1410
- * credential is absent (a present OAuth credential is refreshable).
1700
+ * refresh mechanism. For a dual-auth provider this is only true when its OAuth
1701
+ * credential is absent or sidelined (a live OAuth credential is refreshable).
1411
1702
  */
1412
1703
  async isStaticApiKey(provider) {
1413
- await this.ensureLoaded();
1414
- if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
1415
- const exhaustedUntil = this.data[MOONSHOT_OAUTH_KEY].usageExhaustedUntil ?? 0;
1416
- const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data["moonshot"]);
1704
+ await this.ensureFresh();
1705
+ const dual = dualAuthProvider(provider);
1706
+ const oauthCreds = dual ? this.data[dual.oauthKey] : void 0;
1707
+ if (dual && oauthCreds) {
1708
+ const exhaustedUntil = oauthCreds.usageExhaustedUntil ?? 0;
1709
+ const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data[dual.provider]);
1417
1710
  if (!apiKeyActive) return false;
1418
1711
  }
1419
1712
  return STATIC_API_KEY_PROVIDERS.has(provider);
@@ -1421,26 +1714,30 @@ var AuthStorage = class {
1421
1714
  /**
1422
1715
  * The base URL on the credential that is active right now, if any.
1423
1716
  * Synchronous — call only after load()/resolveCredentials() populated the
1424
- * snapshot. For `moonshot` this is the Kimi For Coding URL whenever the
1425
- * OAuth entry is the one resolveCredentials would serve (i.e. not currently
1426
- * usage-exhausted with an API key configured).
1717
+ * snapshot. For a dual-auth provider this is the subscription endpoint (Kimi
1718
+ * For Coding, the Grok CLI proxy) whenever the OAuth entry is the one
1719
+ * resolveCredentials would serve (i.e. not currently usage-exhausted with an
1720
+ * API key configured).
1427
1721
  */
1428
1722
  getStoredBaseUrl(provider) {
1429
1723
  return activeBaseUrlEntry(this.data, provider)?.baseUrl;
1430
1724
  }
1431
1725
  async load() {
1726
+ const first = !this.loaded;
1432
1727
  await withFileLock(this.filePath, async () => {
1433
1728
  try {
1434
1729
  const content = await import_promises4.default.readFile(this.filePath, "utf-8");
1435
1730
  this.data = JSON.parse(content);
1436
- log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
1437
- providers: Object.keys(this.data).join(",") || "(none)"
1438
- });
1731
+ if (first) {
1732
+ log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
1733
+ providers: Object.keys(this.data).join(",") || "(none)"
1734
+ });
1735
+ }
1439
1736
  } catch (err) {
1440
1737
  this.data = {};
1441
1738
  const code = err.code;
1442
1739
  if (code === "ENOENT") {
1443
- log("INFO", "auth", `No auth file found at ${this.filePath} (first run)`);
1740
+ if (first) log("INFO", "auth", `No auth file found at ${this.filePath} (first run)`);
1444
1741
  } else {
1445
1742
  log(
1446
1743
  "ERROR",
@@ -1452,6 +1749,7 @@ var AuthStorage = class {
1452
1749
  }
1453
1750
  });
1454
1751
  this.loaded = true;
1752
+ await this.rememberSnapshot();
1455
1753
  }
1456
1754
  async ensureLoaded() {
1457
1755
  if (!this.loaded) await this.load();
@@ -1465,6 +1763,42 @@ var AuthStorage = class {
1465
1763
  this.loaded = false;
1466
1764
  await this.load();
1467
1765
  }
1766
+ /**
1767
+ * Like {@link ensureLoaded}, but re-reads when the file changed since this
1768
+ * snapshot — a cheap stat, not a re-parse. Used by the "what is connected?"
1769
+ * readers, which must reflect writes made by another window, the CLI, or the
1770
+ * desktop app's native (daemon-free) API-key and disconnect paths.
1771
+ *
1772
+ * Deliberately NOT used by {@link resolveCredentials}: that path compares the
1773
+ * caller's snapshot against the latest file to detect a concurrent re-login,
1774
+ * and silently refreshing this instance's view first would destroy the
1775
+ * evidence that the token it just had rejected has already been replaced.
1776
+ */
1777
+ async ensureFresh() {
1778
+ if (!this.loaded) {
1779
+ await this.load();
1780
+ return;
1781
+ }
1782
+ let changed;
1783
+ try {
1784
+ const stat = await import_promises4.default.stat(this.filePath);
1785
+ changed = stat.mtimeMs !== this.snapshotMtimeMs || stat.size !== this.snapshotSize;
1786
+ } catch {
1787
+ changed = this.snapshotSize !== -1;
1788
+ }
1789
+ if (changed) await this.load();
1790
+ }
1791
+ /** Record the file identity behind the current snapshot. */
1792
+ async rememberSnapshot() {
1793
+ try {
1794
+ const stat = await import_promises4.default.stat(this.filePath);
1795
+ this.snapshotMtimeMs = stat.mtimeMs;
1796
+ this.snapshotSize = stat.size;
1797
+ } catch {
1798
+ this.snapshotMtimeMs = 0;
1799
+ this.snapshotSize = -1;
1800
+ }
1801
+ }
1468
1802
  /**
1469
1803
  * Apply one provider-scoped mutation to the latest on-disk snapshot.
1470
1804
  * AuthStorage instances live in every app session/process, so writing this
@@ -1480,14 +1814,16 @@ var AuthStorage = class {
1480
1814
  await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
1481
1815
  this.data = latest;
1482
1816
  });
1817
+ await this.rememberSnapshot();
1483
1818
  }
1484
1819
  async reloadLatest() {
1485
1820
  await withFileLock(this.filePath, async () => {
1486
1821
  this.data = await readAuthData(this.filePath);
1487
1822
  });
1823
+ await this.rememberSnapshot();
1488
1824
  }
1489
1825
  async getCredentials(provider) {
1490
- await this.ensureLoaded();
1826
+ await this.ensureFresh();
1491
1827
  return this.data[provider];
1492
1828
  }
1493
1829
  async setCredentials(provider, creds) {
@@ -1505,7 +1841,7 @@ var AuthStorage = class {
1505
1841
  * `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
1506
1842
  * 15-minute default when no reset time is known. While the mark is in the
1507
1843
  * future, `resolveCredentials("moonshot")` serves the Moonshot API key
1508
- * instead of the Kimi OAuth credential (when both are configured) — OAuth
1844
+ * instead of the subscription OAuth credential (when both are configured) — OAuth
1509
1845
  * stays the preferred credential and is retried automatically once the mark
1510
1846
  * lapses. Persisted to auth.json so a restart (or another ezcoder-app window)
1511
1847
  * doesn't burn a request rediscovering the same exhausted window. No-op if
@@ -1536,13 +1872,21 @@ var AuthStorage = class {
1536
1872
  }
1537
1873
  /**
1538
1874
  * Returns valid credentials, auto-refreshing if expired.
1875
+ *
1539
1876
  * If `forceRefresh` is true, refreshes even if the token hasn't expired
1540
- * (useful when the provider rejects a token with 401 before its stored expiry).
1877
+ * (useful when the provider rejects a token with 401 before its stored
1878
+ * expiry). Callers recovering from a rejection should also pass
1879
+ * `rejectedToken` — see the stampede guard below.
1880
+ *
1541
1881
  * Throws if not logged in.
1542
1882
  */
1543
1883
  async resolveCredentials(provider, opts) {
1544
- await this.ensureLoaded();
1545
- const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : provider === "moonshot" ? [MOONSHOT_OAUTH_KEY, "moonshot"] : [provider];
1884
+ if (opts?.forceRefresh) {
1885
+ await this.ensureLoaded();
1886
+ } else {
1887
+ await this.ensureFresh();
1888
+ }
1889
+ const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
1546
1890
  if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
1547
1891
  await this.reloadLatest();
1548
1892
  }
@@ -1553,28 +1897,31 @@ var AuthStorage = class {
1553
1897
  }
1554
1898
  throw new NotLoggedInError(provider);
1555
1899
  }
1556
- if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
1557
- const exhaustedUntil = this.data[MOONSHOT_OAUTH_KEY].usageExhaustedUntil ?? 0;
1558
- if (Date.now() < exhaustedUntil && this.data["moonshot"]) {
1900
+ const dual = dualAuthProvider(provider);
1901
+ const dualOAuthCreds = dual ? this.data[dual.oauthKey] : void 0;
1902
+ if (dual && dualOAuthCreds) {
1903
+ const exhaustedUntil = dualOAuthCreds.usageExhaustedUntil ?? 0;
1904
+ if (Date.now() < exhaustedUntil && this.data[dual.provider]) {
1559
1905
  log(
1560
1906
  "WARN",
1561
1907
  "auth",
1562
- `Kimi OAuth usage window is exhausted \u2014 using the Moonshot API key until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
1908
+ `${dual.oauthLabel} usage window is exhausted \u2014 using the ${dual.apiKeyLabel} until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
1563
1909
  );
1564
- return this.data["moonshot"];
1910
+ return this.data[dual.provider];
1565
1911
  }
1566
1912
  try {
1567
- return await this.resolveCredentials(MOONSHOT_OAUTH_KEY, {
1568
- ...opts?.forceRefresh ? { forceRefresh: true } : {}
1913
+ return await this.resolveCredentials(dual.oauthKey, {
1914
+ ...opts?.forceRefresh ? { forceRefresh: true } : {},
1915
+ ...opts?.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
1569
1916
  });
1570
1917
  } catch (err) {
1571
- if (err instanceof NotLoggedInError && this.data["moonshot"]) {
1918
+ if (err instanceof NotLoggedInError && this.data[dual.provider]) {
1572
1919
  log(
1573
1920
  "WARN",
1574
1921
  "auth",
1575
- 'Kimi OAuth credential is no longer valid \u2014 falling back to the Moonshot API key. Run "ezcoder login" and choose Kimi OAuth to restore OAuth auth.'
1922
+ `${dual.oauthLabel} credential is no longer valid \u2014 falling back to the ${dual.apiKeyLabel}. ${dual.restoreHint}`
1576
1923
  );
1577
- return this.data["moonshot"];
1924
+ return this.data[dual.provider];
1578
1925
  }
1579
1926
  throw err;
1580
1927
  }
@@ -1599,11 +1946,19 @@ var AuthStorage = class {
1599
1946
  throw new NotLoggedInError(provider);
1600
1947
  }
1601
1948
  const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
1602
- if (credentialWasReplaced || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
1949
+ const rotatedBySibling = opts?.rejectedToken !== void 0 && latestCreds.accessToken !== opts.rejectedToken;
1950
+ if (credentialWasReplaced || rotatedBySibling || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
1951
+ if (rotatedBySibling && !credentialWasReplaced) {
1952
+ log(
1953
+ "INFO",
1954
+ "auth",
1955
+ `${provider} token was rotated by another EZ Coder process \u2014 adopting it instead of refreshing again`
1956
+ );
1957
+ }
1603
1958
  this.data = latest;
1604
1959
  return latestCreds;
1605
1960
  }
1606
- const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
1961
+ const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : provider === XAI_OAUTH_KEY ? refreshXaiToken : refreshOpenAIToken;
1607
1962
  let refreshed;
1608
1963
  try {
1609
1964
  refreshed = await refreshFn(latestCreds.refreshToken);
@@ -1637,6 +1992,7 @@ var AuthStorage = class {
1637
1992
  return await refreshPromise;
1638
1993
  } finally {
1639
1994
  this.refreshLocks.delete(provider);
1995
+ await this.rememberSnapshot();
1640
1996
  }
1641
1997
  }
1642
1998
  /**
@@ -1900,7 +2256,7 @@ var MODELS = [
1900
2256
  {
1901
2257
  // Gemini 3.1 Pro is public preview — gated behind Code Assist preview
1902
2258
  // enablement, so free/personal OAuth accounts 404 on it (see
1903
- // ACCOUNT_GATED_MODELS in gg-ai's gemini provider).
2259
+ // ACCOUNT_GATED_MODELS in @prestyj/ai's gemini provider).
1904
2260
  id: "gemini-3.1-pro-preview",
1905
2261
  name: "Gemini 3.1 Pro (Preview)",
1906
2262
  provider: "gemini",
@@ -2836,7 +3192,7 @@ var TelegramBot = class {
2836
3192
  } catch (err) {
2837
3193
  if (!this.running) break;
2838
3194
  console.error(`[telegram] Poll error: ${err instanceof Error ? err.message : err}`);
2839
- await sleep2(3e3);
3195
+ await sleep3(3e3);
2840
3196
  }
2841
3197
  }
2842
3198
  }
@@ -3006,7 +3362,7 @@ function splitMessage(text) {
3006
3362
  }
3007
3363
  return chunks;
3008
3364
  }
3009
- function sleep2(ms) {
3365
+ function sleep3(ms) {
3010
3366
  return new Promise((r) => setTimeout(r, ms));
3011
3367
  }
3012
3368
 
@@ -3271,6 +3627,7 @@ function createAutoUpdater(config) {
3271
3627
  NotLoggedInError,
3272
3628
  SubscriptionUsageError,
3273
3629
  TelegramBot,
3630
+ XAI_OAUTH_KEY,
3274
3631
  XIAOMI_CREDITS_KEY,
3275
3632
  clearLocalDiscoveryCache,
3276
3633
  clearRuntimeModels,
@@ -3279,6 +3636,8 @@ function createAutoUpdater(config) {
3279
3636
  decodeOggOpus,
3280
3637
  discoverLocalModels,
3281
3638
  downmixToMono,
3639
+ dualAuthProvider,
3640
+ dualAuthProviderByOAuthKey,
3282
3641
  endpointRoot,
3283
3642
  fetchSubscriptionUsage,
3284
3643
  findProbedModel,
@@ -3303,6 +3662,9 @@ function createAutoUpdater(config) {
3303
3662
  getSupportedThinkingLevels,
3304
3663
  getToolResultCharLimit,
3305
3664
  getVideoByteLimit,
3665
+ grokCliBaseUrl,
3666
+ grokCliHeaders,
3667
+ isGrokCliEndpoint,
3306
3668
  isKimiCodingEndpoint,
3307
3669
  isLocalModelId,
3308
3670
  isLoggerOpen,
@@ -3316,14 +3678,18 @@ function createAutoUpdater(config) {
3316
3678
  loginGemini,
3317
3679
  loginKimi,
3318
3680
  loginOpenAI,
3681
+ loginXai,
3682
+ oauthStorageKey,
3319
3683
  openLog,
3320
3684
  parseLocalModelId,
3321
3685
  probeEndpoint,
3686
+ providerStorageKeys,
3322
3687
  readStoredBaseUrlSync,
3323
3688
  refreshAnthropicToken,
3324
3689
  refreshGeminiToken,
3325
3690
  refreshKimiToken,
3326
3691
  refreshOpenAIToken,
3692
+ refreshXaiToken,
3327
3693
  registerLogCleanup,
3328
3694
  registerRuntimeModels,
3329
3695
  resample,