@prestyj/core 5.11.0 → 5.13.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,
@@ -387,6 +397,7 @@ var import_node_path3 = __toESM(require("path"), 1);
387
397
  var import_node_crypto2 = require("crypto");
388
398
  var import_ai = require("@prestyj/ai");
389
399
  var MAX_BYTES = 10 * 1024 * 1024;
400
+ var MIN_HEADROOM_BYTES = 64 * 1024;
390
401
  var fd = null;
391
402
  var bytesWritten = 0;
392
403
  var capped = false;
@@ -397,7 +408,7 @@ var exactSecrets = [];
397
408
  function rotateIfNeeded(filePath) {
398
409
  try {
399
410
  const st = import_node_fs2.default.statSync(filePath);
400
- if (st.size < MAX_BYTES) return;
411
+ if (st.size <= MAX_BYTES - MIN_HEADROOM_BYTES) return;
401
412
  const rotated = `${filePath}.1`;
402
413
  try {
403
414
  import_node_fs2.default.unlinkSync(rotated);
@@ -692,6 +703,9 @@ var TOKEN_URL = "https://auth.openai.com/oauth/token";
692
703
  var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
693
704
  var SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
694
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;
695
709
  async function loginOpenAI(callbacks) {
696
710
  const { verifier, challenge } = await generatePKCE();
697
711
  const state = import_node_crypto4.default.randomBytes(16).toString("hex");
@@ -707,20 +721,7 @@ async function loginOpenAI(callbacks) {
707
721
  url.searchParams.set("id_token_add_organizations", "true");
708
722
  url.searchParams.set("codex_cli_simplified_flow", "true");
709
723
  url.searchParams.set("originator", "ezcoder");
710
- let code;
711
- try {
712
- code = await loginWithServer(url.toString(), state, callbacks);
713
- } catch {
714
- callbacks.onOpenUrl(url.toString());
715
- const raw = await callbacks.onPromptCode(
716
- "Could not start local server. Paste the callback URL or code from the browser:"
717
- );
718
- const parsed = parseAuthorizationInput(raw);
719
- if (!parsed.code) {
720
- throw new Error("No authorization code found in input.");
721
- }
722
- code = parsed.code;
723
- }
724
+ const code = await acquireAuthorizationCode(url.toString(), state, callbacks);
724
725
  const creds = await exchangeOpenAICode(code, verifier);
725
726
  const accountId = getAccountId(creds.accessToken);
726
727
  if (!accountId) {
@@ -751,6 +752,7 @@ function parseAuthorizationInput(input) {
751
752
  state: params.get("state") ?? void 0
752
753
  };
753
754
  }
755
+ if (/\s/.test(value) || value.length < MIN_RAW_CODE_LENGTH) return {};
754
756
  return { code: value };
755
757
  }
756
758
  function decodeJwt(token) {
@@ -769,7 +771,55 @@ function getAccountId(accessToken) {
769
771
  const accountId = auth?.chatgpt_account_id;
770
772
  return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
771
773
  }
772
- 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) {
773
823
  return new Promise((resolve, reject) => {
774
824
  let receivedCode = null;
775
825
  const server = import_node_http.default.createServer((req, res) => {
@@ -792,18 +842,18 @@ async function loginWithServer(authUrl, expectedState, callbacks) {
792
842
  server.on("error", (err) => {
793
843
  reject(err);
794
844
  });
795
- server.listen(1455, "127.0.0.1", () => {
796
- callbacks.onOpenUrl(authUrl);
797
- callbacks.onStatus("Waiting for browser callback...");
798
- });
845
+ server.listen(1455, "127.0.0.1");
799
846
  const timeout = setTimeout(() => {
800
- if (!receivedCode) {
801
- server.close();
802
- }
803
- }, 12e4);
847
+ if (!receivedCode) server.close();
848
+ }, CALLBACK_TIMEOUT_MS);
804
849
  timeout.unref();
850
+ const onAbort = () => {
851
+ server.close();
852
+ };
853
+ signal.addEventListener("abort", onAbort, { once: true });
805
854
  server.on("close", () => {
806
855
  clearTimeout(timeout);
856
+ signal.removeEventListener("abort", onAbort);
807
857
  if (receivedCode) {
808
858
  resolve(receivedCode);
809
859
  } else {
@@ -865,8 +915,8 @@ async function refreshOpenAIToken(refreshToken) {
865
915
  // src/oauth/gemini.ts
866
916
  var import_node_http2 = __toESM(require("http"), 1);
867
917
  var import_node_crypto5 = __toESM(require("crypto"), 1);
868
- var CLIENT_ID_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_ID";
869
- 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";
870
920
  var DEFAULT_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
871
921
  var DEFAULT_CLIENT_SECRET = "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";
872
922
  var AUTHORIZE_URL3 = "https://accounts.google.com/o/oauth2/v2/auth";
@@ -896,13 +946,13 @@ var CodeAssistHttpError = class extends Error {
896
946
  }
897
947
  };
898
948
  async function loginGemini(callbacks) {
899
- const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
949
+ const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
900
950
  const { verifier, challenge } = await generatePKCE();
901
951
  const state = import_node_crypto5.default.randomBytes(32).toString("hex");
902
952
  const redirectUri = await getLoopbackRedirectUri();
903
953
  const url = new URL(AUTHORIZE_URL3);
904
954
  url.searchParams.set("response_type", "code");
905
- url.searchParams.set("client_id", clientId);
955
+ url.searchParams.set("client_id", clientId2);
906
956
  url.searchParams.set("redirect_uri", redirectUri);
907
957
  url.searchParams.set("scope", SCOPE2);
908
958
  url.searchParams.set("access_type", "offline");
@@ -912,7 +962,7 @@ async function loginGemini(callbacks) {
912
962
  url.searchParams.set("state", state);
913
963
  let code;
914
964
  try {
915
- code = await loginWithServer2(url.toString(), redirectUri, state, callbacks);
965
+ code = await loginWithServer(url.toString(), redirectUri, state, callbacks);
916
966
  } catch {
917
967
  callbacks.onOpenUrl(url.toString());
918
968
  const raw = await callbacks.onPromptCode(
@@ -927,7 +977,7 @@ async function loginGemini(callbacks) {
927
977
  }
928
978
  code = parsed.code;
929
979
  }
930
- const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId, clientSecret);
980
+ const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret);
931
981
  callbacks.onStatus("Setting up Gemini Code Assist access...");
932
982
  const projectId = await setupCodeAssistProject(creds.accessToken, callbacks);
933
983
  return {
@@ -936,11 +986,11 @@ async function loginGemini(callbacks) {
936
986
  };
937
987
  }
938
988
  async function refreshGeminiToken(refreshToken) {
939
- const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
989
+ const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
940
990
  const data = await postTokenRequest2({
941
991
  grant_type: "refresh_token",
942
992
  refresh_token: refreshToken,
943
- client_id: clientId,
993
+ client_id: clientId2,
944
994
  client_secret: clientSecret
945
995
  });
946
996
  return {
@@ -950,9 +1000,9 @@ async function refreshGeminiToken(refreshToken) {
950
1000
  };
951
1001
  }
952
1002
  function getGeminiOAuthClientCredentials() {
953
- const clientId = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
1003
+ const clientId2 = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
954
1004
  const clientSecret = process.env[CLIENT_SECRET_ENV]?.trim() || DEFAULT_CLIENT_SECRET;
955
- return { clientId, clientSecret };
1005
+ return { clientId: clientId2, clientSecret };
956
1006
  }
957
1007
  async function getLoopbackRedirectUri() {
958
1008
  return new Promise((resolve, reject) => {
@@ -990,7 +1040,7 @@ function parseAuthorizationInput2(input) {
990
1040
  }
991
1041
  return { code: value };
992
1042
  }
993
- async function loginWithServer2(authUrl, redirectUri, expectedState, callbacks) {
1043
+ async function loginWithServer(authUrl, redirectUri, expectedState, callbacks) {
994
1044
  const redirect = new URL(redirectUri);
995
1045
  const port = Number(redirect.port);
996
1046
  return new Promise((resolve, reject) => {
@@ -1031,10 +1081,10 @@ async function loginWithServer2(authUrl, redirectUri, expectedState, callbacks)
1031
1081
  });
1032
1082
  });
1033
1083
  }
1034
- async function exchangeGeminiCode(code, verifier, redirectUri, clientId, clientSecret) {
1084
+ async function exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret) {
1035
1085
  const data = await postTokenRequest2({
1036
1086
  grant_type: "authorization_code",
1037
- client_id: clientId,
1087
+ client_id: clientId2,
1038
1088
  client_secret: clientSecret,
1039
1089
  code,
1040
1090
  redirect_uri: redirectUri,
@@ -1225,6 +1275,204 @@ function codeAssistHeaders(accessToken) {
1225
1275
  };
1226
1276
  }
1227
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
+
1228
1476
  // src/file-lock.ts
1229
1477
  var import_promises2 = __toESM(require("fs/promises"), 1);
1230
1478
  var import_promises3 = require("timers/promises");
@@ -1289,18 +1537,49 @@ function isAlive(pid) {
1289
1537
 
1290
1538
  // src/auth-storage.ts
1291
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
+ }
1292
1570
  var XIAOMI_CREDITS_KEY = "xiaomi-credits";
1293
1571
  var LOCAL_AUTH_KEY_PREFIX = "local:";
1294
1572
  var LOCAL_CREDENTIAL_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
1295
1573
  function activeBaseUrlEntry(data, provider) {
1296
- if (provider === "moonshot") {
1297
- const oauth = data[MOONSHOT_OAUTH_KEY];
1574
+ const dual = dualAuthProvider(provider);
1575
+ if (dual) {
1576
+ const oauth = data[dual.oauthKey];
1298
1577
  if (oauth) {
1299
1578
  const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
1300
- if (Date.now() < exhaustedUntil && data["moonshot"]) return data["moonshot"];
1579
+ if (Date.now() < exhaustedUntil && data[dual.provider]) return data[dual.provider];
1301
1580
  return oauth;
1302
1581
  }
1303
- return data["moonshot"];
1582
+ return data[dual.provider];
1304
1583
  }
1305
1584
  return data[provider];
1306
1585
  }
@@ -1335,6 +1614,16 @@ var AuthStorage = class {
1335
1614
  data = {};
1336
1615
  filePath;
1337
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;
1338
1627
  /** Per-provider lock to serialize concurrent refresh calls. */
1339
1628
  refreshLocks = /* @__PURE__ */ new Map();
1340
1629
  constructor(filePath) {
@@ -1346,12 +1635,12 @@ var AuthStorage = class {
1346
1635
  }
1347
1636
  /** List provider keys with stored credentials. */
1348
1637
  async listProviders() {
1349
- await this.ensureLoaded();
1638
+ await this.ensureFresh();
1350
1639
  return Object.keys(this.data);
1351
1640
  }
1352
1641
  /** True if credentials exist for `provider`. */
1353
1642
  async hasCredentials(provider) {
1354
- await this.ensureLoaded();
1643
+ await this.ensureFresh();
1355
1644
  return Boolean(this.data[provider]);
1356
1645
  }
1357
1646
  /**
@@ -1362,18 +1651,19 @@ var AuthStorage = class {
1362
1651
  * instead of re-deriving the same order.
1363
1652
  */
1364
1653
  async pickStorageKey(keys) {
1365
- await this.ensureLoaded();
1654
+ await this.ensureFresh();
1366
1655
  return keys.find((key) => Boolean(this.data[key]));
1367
1656
  }
1368
1657
  /**
1369
- * True if the user has any usable auth for the logical provider. For
1370
- * `moonshot` this is satisfied by either the Kimi OAuth credential or the
1371
- * 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.
1372
1661
  */
1373
1662
  async hasProviderAuth(provider) {
1374
- await this.ensureLoaded();
1375
- if (provider === "moonshot") {
1376
- 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]);
1377
1667
  }
1378
1668
  if (provider === "xiaomi") {
1379
1669
  return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
@@ -1407,14 +1697,16 @@ var AuthStorage = class {
1407
1697
  }
1408
1698
  /**
1409
1699
  * True if the active credential for `provider` is a static API key with no
1410
- * refresh mechanism. For `moonshot` this is only true when the Kimi OAuth
1411
- * 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).
1412
1702
  */
1413
1703
  async isStaticApiKey(provider) {
1414
- await this.ensureLoaded();
1415
- if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
1416
- const exhaustedUntil = this.data[MOONSHOT_OAUTH_KEY].usageExhaustedUntil ?? 0;
1417
- 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]);
1418
1710
  if (!apiKeyActive) return false;
1419
1711
  }
1420
1712
  return STATIC_API_KEY_PROVIDERS.has(provider);
@@ -1422,26 +1714,30 @@ var AuthStorage = class {
1422
1714
  /**
1423
1715
  * The base URL on the credential that is active right now, if any.
1424
1716
  * Synchronous — call only after load()/resolveCredentials() populated the
1425
- * snapshot. For `moonshot` this is the Kimi For Coding URL whenever the
1426
- * OAuth entry is the one resolveCredentials would serve (i.e. not currently
1427
- * 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).
1428
1721
  */
1429
1722
  getStoredBaseUrl(provider) {
1430
1723
  return activeBaseUrlEntry(this.data, provider)?.baseUrl;
1431
1724
  }
1432
1725
  async load() {
1726
+ const first = !this.loaded;
1433
1727
  await withFileLock(this.filePath, async () => {
1434
1728
  try {
1435
1729
  const content = await import_promises4.default.readFile(this.filePath, "utf-8");
1436
1730
  this.data = JSON.parse(content);
1437
- log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
1438
- providers: Object.keys(this.data).join(",") || "(none)"
1439
- });
1731
+ if (first) {
1732
+ log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
1733
+ providers: Object.keys(this.data).join(",") || "(none)"
1734
+ });
1735
+ }
1440
1736
  } catch (err) {
1441
1737
  this.data = {};
1442
1738
  const code = err.code;
1443
1739
  if (code === "ENOENT") {
1444
- 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)`);
1445
1741
  } else {
1446
1742
  log(
1447
1743
  "ERROR",
@@ -1453,6 +1749,7 @@ var AuthStorage = class {
1453
1749
  }
1454
1750
  });
1455
1751
  this.loaded = true;
1752
+ await this.rememberSnapshot();
1456
1753
  }
1457
1754
  async ensureLoaded() {
1458
1755
  if (!this.loaded) await this.load();
@@ -1466,6 +1763,42 @@ var AuthStorage = class {
1466
1763
  this.loaded = false;
1467
1764
  await this.load();
1468
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
+ }
1469
1802
  /**
1470
1803
  * Apply one provider-scoped mutation to the latest on-disk snapshot.
1471
1804
  * AuthStorage instances live in every app session/process, so writing this
@@ -1481,14 +1814,16 @@ var AuthStorage = class {
1481
1814
  await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
1482
1815
  this.data = latest;
1483
1816
  });
1817
+ await this.rememberSnapshot();
1484
1818
  }
1485
1819
  async reloadLatest() {
1486
1820
  await withFileLock(this.filePath, async () => {
1487
1821
  this.data = await readAuthData(this.filePath);
1488
1822
  });
1823
+ await this.rememberSnapshot();
1489
1824
  }
1490
1825
  async getCredentials(provider) {
1491
- await this.ensureLoaded();
1826
+ await this.ensureFresh();
1492
1827
  return this.data[provider];
1493
1828
  }
1494
1829
  async setCredentials(provider, creds) {
@@ -1506,7 +1841,7 @@ var AuthStorage = class {
1506
1841
  * `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
1507
1842
  * 15-minute default when no reset time is known. While the mark is in the
1508
1843
  * future, `resolveCredentials("moonshot")` serves the Moonshot API key
1509
- * instead of the Kimi OAuth credential (when both are configured) — OAuth
1844
+ * instead of the subscription OAuth credential (when both are configured) — OAuth
1510
1845
  * stays the preferred credential and is retried automatically once the mark
1511
1846
  * lapses. Persisted to auth.json so a restart (or another ezcoder-app window)
1512
1847
  * doesn't burn a request rediscovering the same exhausted window. No-op if
@@ -1537,13 +1872,21 @@ var AuthStorage = class {
1537
1872
  }
1538
1873
  /**
1539
1874
  * Returns valid credentials, auto-refreshing if expired.
1875
+ *
1540
1876
  * If `forceRefresh` is true, refreshes even if the token hasn't expired
1541
- * (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
+ *
1542
1881
  * Throws if not logged in.
1543
1882
  */
1544
1883
  async resolveCredentials(provider, opts) {
1545
- await this.ensureLoaded();
1546
- 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);
1547
1890
  if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
1548
1891
  await this.reloadLatest();
1549
1892
  }
@@ -1554,28 +1897,31 @@ var AuthStorage = class {
1554
1897
  }
1555
1898
  throw new NotLoggedInError(provider);
1556
1899
  }
1557
- if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
1558
- const exhaustedUntil = this.data[MOONSHOT_OAUTH_KEY].usageExhaustedUntil ?? 0;
1559
- 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]) {
1560
1905
  log(
1561
1906
  "WARN",
1562
1907
  "auth",
1563
- `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).`
1564
1909
  );
1565
- return this.data["moonshot"];
1910
+ return this.data[dual.provider];
1566
1911
  }
1567
1912
  try {
1568
- return await this.resolveCredentials(MOONSHOT_OAUTH_KEY, {
1569
- ...opts?.forceRefresh ? { forceRefresh: true } : {}
1913
+ return await this.resolveCredentials(dual.oauthKey, {
1914
+ ...opts?.forceRefresh ? { forceRefresh: true } : {},
1915
+ ...opts?.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
1570
1916
  });
1571
1917
  } catch (err) {
1572
- if (err instanceof NotLoggedInError && this.data["moonshot"]) {
1918
+ if (err instanceof NotLoggedInError && this.data[dual.provider]) {
1573
1919
  log(
1574
1920
  "WARN",
1575
1921
  "auth",
1576
- '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}`
1577
1923
  );
1578
- return this.data["moonshot"];
1924
+ return this.data[dual.provider];
1579
1925
  }
1580
1926
  throw err;
1581
1927
  }
@@ -1600,11 +1946,19 @@ var AuthStorage = class {
1600
1946
  throw new NotLoggedInError(provider);
1601
1947
  }
1602
1948
  const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
1603
- 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
+ }
1604
1958
  this.data = latest;
1605
1959
  return latestCreds;
1606
1960
  }
1607
- 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;
1608
1962
  let refreshed;
1609
1963
  try {
1610
1964
  refreshed = await refreshFn(latestCreds.refreshToken);
@@ -1638,6 +1992,7 @@ var AuthStorage = class {
1638
1992
  return await refreshPromise;
1639
1993
  } finally {
1640
1994
  this.refreshLocks.delete(provider);
1995
+ await this.rememberSnapshot();
1641
1996
  }
1642
1997
  }
1643
1998
  /**
@@ -1901,7 +2256,7 @@ var MODELS = [
1901
2256
  {
1902
2257
  // Gemini 3.1 Pro is public preview — gated behind Code Assist preview
1903
2258
  // enablement, so free/personal OAuth accounts 404 on it (see
1904
- // ACCOUNT_GATED_MODELS in gg-ai's gemini provider).
2259
+ // ACCOUNT_GATED_MODELS in @prestyj/ai's gemini provider).
1905
2260
  id: "gemini-3.1-pro-preview",
1906
2261
  name: "Gemini 3.1 Pro (Preview)",
1907
2262
  provider: "gemini",
@@ -1949,11 +2304,16 @@ var MODELS = [
1949
2304
  maxThinkingLevel: "high"
1950
2305
  },
1951
2306
  // ── Z.AI (GLM) ─────────────────────────────────────────
1952
- // GLM-5.2: coding-first flagship with a usable 1M-token context window
1953
- // (5x jump over GLM-5.1's ~200K) and 131K max output. Released 2026-06-13.
2307
+ // GLM-5.3 is the only GLM entry: it supersedes 5.2 (same GLM-5 base, all
2308
+ // gains from post-training) and the coding endpoint already answers
2309
+ // `glm-5.2` requests as glm-5.3, so the older ids were menu clutter that
2310
+ // routed to strictly worse coding for the same plan quota.
2311
+ // Released 2026-08-14; live on the coding endpoint (verified), while the
2312
+ // standard paas API is still "coming soon". `max` is both the ceiling and
2313
+ // Z.AI's own default — the rungs below it live in thinking-level.ts.
1954
2314
  {
1955
- id: "glm-5.2",
1956
- name: "GLM-5.2",
2315
+ id: "glm-5.3",
2316
+ name: "GLM-5.3",
1957
2317
  provider: "glm",
1958
2318
  contextWindow: 1e6,
1959
2319
  maxOutputTokens: 131072,
@@ -1961,43 +2321,7 @@ var MODELS = [
1961
2321
  supportsImages: false,
1962
2322
  supportsVideo: false,
1963
2323
  costTier: "medium",
1964
- maxThinkingLevel: "high"
1965
- },
1966
- {
1967
- id: "glm-5.1",
1968
- name: "GLM-5.1",
1969
- provider: "glm",
1970
- contextWindow: 204800,
1971
- maxOutputTokens: 131072,
1972
- supportsThinking: true,
1973
- supportsImages: false,
1974
- supportsVideo: false,
1975
- costTier: "medium",
1976
- maxThinkingLevel: "high"
1977
- },
1978
- {
1979
- id: "glm-4.7",
1980
- name: "GLM-4.7",
1981
- provider: "glm",
1982
- contextWindow: 2e5,
1983
- maxOutputTokens: 131072,
1984
- supportsThinking: true,
1985
- supportsImages: false,
1986
- supportsVideo: false,
1987
- costTier: "low",
1988
- maxThinkingLevel: "high"
1989
- },
1990
- {
1991
- id: "glm-4.7-flash",
1992
- name: "GLM-4.7 Flash",
1993
- provider: "glm",
1994
- contextWindow: 2e5,
1995
- maxOutputTokens: 131072,
1996
- supportsThinking: true,
1997
- supportsImages: false,
1998
- supportsVideo: false,
1999
- costTier: "low",
2000
- maxThinkingLevel: "high"
2324
+ maxThinkingLevel: "max"
2001
2325
  },
2002
2326
  // ── MiniMax ────────────────────────────────────────────
2003
2327
  {
@@ -2142,7 +2466,7 @@ function getDefaultModel(provider) {
2142
2466
  if (provider === "xiaomi") return MODELS.find((m) => m.id === "mimo-v2.5-pro");
2143
2467
  if (provider === "openai") return MODELS.find((m) => m.id === "gpt-5.6-sol");
2144
2468
  if (provider === "gemini") return MODELS.find((m) => m.id === "gemini-3.1-flash-lite");
2145
- if (provider === "glm") return MODELS.find((m) => m.id === "glm-5.2");
2469
+ if (provider === "glm") return MODELS.find((m) => m.id === "glm-5.3");
2146
2470
  if (provider === "moonshot") return MODELS.find((m) => m.id === "kimi-k3");
2147
2471
  if (provider === "minimax") return MODELS.find((m) => m.id === "MiniMax-M3");
2148
2472
  if (provider === "deepseek") return MODELS.find((m) => m.id === "deepseek-v4-pro");
@@ -2229,6 +2553,7 @@ var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
2229
2553
  "max"
2230
2554
  ];
2231
2555
  var MOONSHOT_K3_THINKING_LEVELS = ["low", "high", "max"];
2556
+ var GLM_THINKING_LEVELS = ["low", "medium", "high", "xhigh", "max"];
2232
2557
  var LOCAL_THINKING_LEVELS = ["low", "medium", "high", "max"];
2233
2558
  function isOpenAIGptModel(provider, model) {
2234
2559
  return provider === "openai" && model.startsWith("gpt-");
@@ -2242,6 +2567,9 @@ function isXaiModel(provider) {
2242
2567
  function isMoonshotK3Model(provider, model) {
2243
2568
  return provider === "moonshot" && model === "kimi-k3";
2244
2569
  }
2570
+ function isGlmModel(provider) {
2571
+ return provider === "glm";
2572
+ }
2245
2573
  function isAnthropicXhighModel(provider, model) {
2246
2574
  return provider === "anthropic" && /opus-5|opus-4-8|opus-4-7/.test(model);
2247
2575
  }
@@ -2273,6 +2601,11 @@ function getSupportedThinkingLevels(provider, model) {
2273
2601
  return XAI_THINKING_LEVELS.slice(0, maxIndex2 + 1);
2274
2602
  }
2275
2603
  if (isMoonshotK3Model(provider, model)) return MOONSHOT_K3_THINKING_LEVELS;
2604
+ if (isGlmModel(provider)) {
2605
+ const maxIndex2 = GLM_THINKING_LEVELS.indexOf(maxLevel);
2606
+ if (maxIndex2 === -1) return GLM_THINKING_LEVELS;
2607
+ return GLM_THINKING_LEVELS.slice(0, maxIndex2 + 1);
2608
+ }
2276
2609
  if (!isOpenAIGptModel(provider, model)) return [maxLevel];
2277
2610
  const levels = model.startsWith("gpt-5.6-") ? OPENAI_GPT_56_THINKING_LEVELS : OPENAI_GPT_THINKING_LEVELS;
2278
2611
  const maxIndex = levels.indexOf(maxLevel);
@@ -2284,7 +2617,7 @@ function isThinkingLevelSupported(provider, model, level) {
2284
2617
  }
2285
2618
  function getNextThinkingLevel(provider, model, current) {
2286
2619
  const supportedLevels = getSupportedThinkingLevels(provider, model);
2287
- const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider) || isMoonshotK3Model(provider, model) || // Local servers take a real effort level, not just on/off: Ollama accepts
2620
+ const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider) || isMoonshotK3Model(provider, model) || isGlmModel(provider) || // Local servers take a real effort level, not just on/off: Ollama accepts
2288
2621
  // low/medium/high on `reasoning_effort` (verified against 0.32) and the
2289
2622
  // other OpenAI-compatible servers use the same three. A model that can't
2290
2623
  // reason at all already has no supported levels, so it never gets here.
@@ -2837,7 +3170,7 @@ var TelegramBot = class {
2837
3170
  } catch (err) {
2838
3171
  if (!this.running) break;
2839
3172
  console.error(`[telegram] Poll error: ${err instanceof Error ? err.message : err}`);
2840
- await sleep2(3e3);
3173
+ await sleep3(3e3);
2841
3174
  }
2842
3175
  }
2843
3176
  }
@@ -3007,7 +3340,7 @@ function splitMessage(text) {
3007
3340
  }
3008
3341
  return chunks;
3009
3342
  }
3010
- function sleep2(ms) {
3343
+ function sleep3(ms) {
3011
3344
  return new Promise((r) => setTimeout(r, ms));
3012
3345
  }
3013
3346
 
@@ -3272,6 +3605,7 @@ function createAutoUpdater(config) {
3272
3605
  NotLoggedInError,
3273
3606
  SubscriptionUsageError,
3274
3607
  TelegramBot,
3608
+ XAI_OAUTH_KEY,
3275
3609
  XIAOMI_CREDITS_KEY,
3276
3610
  clearLocalDiscoveryCache,
3277
3611
  clearRuntimeModels,
@@ -3280,6 +3614,8 @@ function createAutoUpdater(config) {
3280
3614
  decodeOggOpus,
3281
3615
  discoverLocalModels,
3282
3616
  downmixToMono,
3617
+ dualAuthProvider,
3618
+ dualAuthProviderByOAuthKey,
3283
3619
  endpointRoot,
3284
3620
  fetchSubscriptionUsage,
3285
3621
  findProbedModel,
@@ -3304,6 +3640,9 @@ function createAutoUpdater(config) {
3304
3640
  getSupportedThinkingLevels,
3305
3641
  getToolResultCharLimit,
3306
3642
  getVideoByteLimit,
3643
+ grokCliBaseUrl,
3644
+ grokCliHeaders,
3645
+ isGrokCliEndpoint,
3307
3646
  isKimiCodingEndpoint,
3308
3647
  isLocalModelId,
3309
3648
  isLoggerOpen,
@@ -3317,14 +3656,18 @@ function createAutoUpdater(config) {
3317
3656
  loginGemini,
3318
3657
  loginKimi,
3319
3658
  loginOpenAI,
3659
+ loginXai,
3660
+ oauthStorageKey,
3320
3661
  openLog,
3321
3662
  parseLocalModelId,
3322
3663
  probeEndpoint,
3664
+ providerStorageKeys,
3323
3665
  readStoredBaseUrlSync,
3324
3666
  refreshAnthropicToken,
3325
3667
  refreshGeminiToken,
3326
3668
  refreshKimiToken,
3327
3669
  refreshOpenAIToken,
3670
+ refreshXaiToken,
3328
3671
  registerLogCleanup,
3329
3672
  registerRuntimeModels,
3330
3673
  resample,