@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/{chunk-WAG5K2MH.js → chunk-S6QBRJ4D.js} +448 -124
- package/dist/chunk-S6QBRJ4D.js.map +1 -0
- package/dist/index.cjs +469 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +161 -13
- package/dist/index.d.ts +161 -13
- package/dist/index.js +31 -2
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +16 -43
- package/dist/model-registry.cjs.map +1 -1
- package/dist/model-registry.d.cts +5 -6
- package/dist/model-registry.d.ts +5 -6
- package/dist/model-registry.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-WAG5K2MH.js.map +0 -1
|
@@ -256,6 +256,7 @@ import path2 from "path";
|
|
|
256
256
|
import { randomBytes } from "crypto";
|
|
257
257
|
import { environmentSecrets, redactText, redactValue } from "@prestyj/ai";
|
|
258
258
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
259
|
+
var MIN_HEADROOM_BYTES = 64 * 1024;
|
|
259
260
|
var fd = null;
|
|
260
261
|
var bytesWritten = 0;
|
|
261
262
|
var capped = false;
|
|
@@ -266,7 +267,7 @@ var exactSecrets = [];
|
|
|
266
267
|
function rotateIfNeeded(filePath) {
|
|
267
268
|
try {
|
|
268
269
|
const st = fs.statSync(filePath);
|
|
269
|
-
if (st.size
|
|
270
|
+
if (st.size <= MAX_BYTES - MIN_HEADROOM_BYTES) return;
|
|
270
271
|
const rotated = `${filePath}.1`;
|
|
271
272
|
try {
|
|
272
273
|
fs.unlinkSync(rotated);
|
|
@@ -561,6 +562,9 @@ var TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
|
561
562
|
var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
|
|
562
563
|
var SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
563
564
|
var JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
565
|
+
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
566
|
+
var MAX_PASTE_ATTEMPTS = 3;
|
|
567
|
+
var MIN_RAW_CODE_LENGTH = 10;
|
|
564
568
|
async function loginOpenAI(callbacks) {
|
|
565
569
|
const { verifier, challenge } = await generatePKCE();
|
|
566
570
|
const state = crypto3.randomBytes(16).toString("hex");
|
|
@@ -576,20 +580,7 @@ async function loginOpenAI(callbacks) {
|
|
|
576
580
|
url.searchParams.set("id_token_add_organizations", "true");
|
|
577
581
|
url.searchParams.set("codex_cli_simplified_flow", "true");
|
|
578
582
|
url.searchParams.set("originator", "ezcoder");
|
|
579
|
-
|
|
580
|
-
try {
|
|
581
|
-
code = await loginWithServer(url.toString(), state, callbacks);
|
|
582
|
-
} catch {
|
|
583
|
-
callbacks.onOpenUrl(url.toString());
|
|
584
|
-
const raw = await callbacks.onPromptCode(
|
|
585
|
-
"Could not start local server. Paste the callback URL or code from the browser:"
|
|
586
|
-
);
|
|
587
|
-
const parsed = parseAuthorizationInput(raw);
|
|
588
|
-
if (!parsed.code) {
|
|
589
|
-
throw new Error("No authorization code found in input.");
|
|
590
|
-
}
|
|
591
|
-
code = parsed.code;
|
|
592
|
-
}
|
|
583
|
+
const code = await acquireAuthorizationCode(url.toString(), state, callbacks);
|
|
593
584
|
const creds = await exchangeOpenAICode(code, verifier);
|
|
594
585
|
const accountId = getAccountId(creds.accessToken);
|
|
595
586
|
if (!accountId) {
|
|
@@ -620,6 +611,7 @@ function parseAuthorizationInput(input) {
|
|
|
620
611
|
state: params.get("state") ?? void 0
|
|
621
612
|
};
|
|
622
613
|
}
|
|
614
|
+
if (/\s/.test(value) || value.length < MIN_RAW_CODE_LENGTH) return {};
|
|
623
615
|
return { code: value };
|
|
624
616
|
}
|
|
625
617
|
function decodeJwt(token) {
|
|
@@ -638,7 +630,55 @@ function getAccountId(accessToken) {
|
|
|
638
630
|
const accountId = auth?.chatgpt_account_id;
|
|
639
631
|
return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
|
|
640
632
|
}
|
|
641
|
-
async function
|
|
633
|
+
async function acquireAuthorizationCode(authUrl, expectedState, callbacks) {
|
|
634
|
+
callbacks.onOpenUrl(authUrl);
|
|
635
|
+
callbacks.onStatus("Waiting for browser callback...");
|
|
636
|
+
const done = new AbortController();
|
|
637
|
+
try {
|
|
638
|
+
return await new Promise((resolve, reject) => {
|
|
639
|
+
let serverFailed = false;
|
|
640
|
+
let pasteError;
|
|
641
|
+
const failIfExhausted = () => {
|
|
642
|
+
if (!serverFailed || pasteError === void 0) return;
|
|
643
|
+
reject(
|
|
644
|
+
pasteError instanceof Error ? pasteError : new Error("Could not obtain an authorization code.")
|
|
645
|
+
);
|
|
646
|
+
};
|
|
647
|
+
listenForCallback(expectedState, done.signal).then(resolve, () => {
|
|
648
|
+
serverFailed = true;
|
|
649
|
+
failIfExhausted();
|
|
650
|
+
});
|
|
651
|
+
promptForCode(expectedState, callbacks, done.signal).then(resolve, (err) => {
|
|
652
|
+
if (err instanceof FatalLoginError) {
|
|
653
|
+
reject(err);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
pasteError = err;
|
|
657
|
+
failIfExhausted();
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
} finally {
|
|
661
|
+
done.abort();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
var FatalLoginError = class extends Error {
|
|
665
|
+
};
|
|
666
|
+
async function promptForCode(expectedState, callbacks, signal) {
|
|
667
|
+
let message = "If the browser is on another machine (SSH/headless), paste the callback URL or code here:";
|
|
668
|
+
for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
|
|
669
|
+
if (signal.aborted) throw new Error("Authorization code already received.");
|
|
670
|
+
const raw = await callbacks.onPromptCode(message, signal);
|
|
671
|
+
if (signal.aborted) throw new Error("Authorization code already received.");
|
|
672
|
+
const parsed = parseAuthorizationInput(raw);
|
|
673
|
+
if (parsed.code && parsed.state && parsed.state !== expectedState) {
|
|
674
|
+
throw new FatalLoginError("Authorization state mismatch \u2014 start the login again.");
|
|
675
|
+
}
|
|
676
|
+
if (parsed.code) return parsed.code;
|
|
677
|
+
message = "That didn't contain an authorization code. Paste the full callback URL:";
|
|
678
|
+
}
|
|
679
|
+
throw new Error("No authorization code found in input.");
|
|
680
|
+
}
|
|
681
|
+
async function listenForCallback(expectedState, signal) {
|
|
642
682
|
return new Promise((resolve, reject) => {
|
|
643
683
|
let receivedCode = null;
|
|
644
684
|
const server = http.createServer((req, res) => {
|
|
@@ -661,18 +701,18 @@ async function loginWithServer(authUrl, expectedState, callbacks) {
|
|
|
661
701
|
server.on("error", (err) => {
|
|
662
702
|
reject(err);
|
|
663
703
|
});
|
|
664
|
-
server.listen(1455, "127.0.0.1"
|
|
665
|
-
callbacks.onOpenUrl(authUrl);
|
|
666
|
-
callbacks.onStatus("Waiting for browser callback...");
|
|
667
|
-
});
|
|
704
|
+
server.listen(1455, "127.0.0.1");
|
|
668
705
|
const timeout = setTimeout(() => {
|
|
669
|
-
if (!receivedCode)
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
}, 12e4);
|
|
706
|
+
if (!receivedCode) server.close();
|
|
707
|
+
}, CALLBACK_TIMEOUT_MS);
|
|
673
708
|
timeout.unref();
|
|
709
|
+
const onAbort = () => {
|
|
710
|
+
server.close();
|
|
711
|
+
};
|
|
712
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
674
713
|
server.on("close", () => {
|
|
675
714
|
clearTimeout(timeout);
|
|
715
|
+
signal.removeEventListener("abort", onAbort);
|
|
676
716
|
if (receivedCode) {
|
|
677
717
|
resolve(receivedCode);
|
|
678
718
|
} else {
|
|
@@ -734,8 +774,8 @@ async function refreshOpenAIToken(refreshToken) {
|
|
|
734
774
|
// src/oauth/gemini.ts
|
|
735
775
|
import http2 from "http";
|
|
736
776
|
import crypto4 from "crypto";
|
|
737
|
-
var CLIENT_ID_ENV = "
|
|
738
|
-
var CLIENT_SECRET_ENV = "
|
|
777
|
+
var CLIENT_ID_ENV = "EZCODER_GEMINI_OAUTH_CLIENT_ID";
|
|
778
|
+
var CLIENT_SECRET_ENV = "EZCODER_GEMINI_OAUTH_CLIENT_SECRET";
|
|
739
779
|
var DEFAULT_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
|
|
740
780
|
var DEFAULT_CLIENT_SECRET = "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";
|
|
741
781
|
var AUTHORIZE_URL3 = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
@@ -765,13 +805,13 @@ var CodeAssistHttpError = class extends Error {
|
|
|
765
805
|
}
|
|
766
806
|
};
|
|
767
807
|
async function loginGemini(callbacks) {
|
|
768
|
-
const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
|
|
808
|
+
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
769
809
|
const { verifier, challenge } = await generatePKCE();
|
|
770
810
|
const state = crypto4.randomBytes(32).toString("hex");
|
|
771
811
|
const redirectUri = await getLoopbackRedirectUri();
|
|
772
812
|
const url = new URL(AUTHORIZE_URL3);
|
|
773
813
|
url.searchParams.set("response_type", "code");
|
|
774
|
-
url.searchParams.set("client_id",
|
|
814
|
+
url.searchParams.set("client_id", clientId2);
|
|
775
815
|
url.searchParams.set("redirect_uri", redirectUri);
|
|
776
816
|
url.searchParams.set("scope", SCOPE2);
|
|
777
817
|
url.searchParams.set("access_type", "offline");
|
|
@@ -781,7 +821,7 @@ async function loginGemini(callbacks) {
|
|
|
781
821
|
url.searchParams.set("state", state);
|
|
782
822
|
let code;
|
|
783
823
|
try {
|
|
784
|
-
code = await
|
|
824
|
+
code = await loginWithServer(url.toString(), redirectUri, state, callbacks);
|
|
785
825
|
} catch {
|
|
786
826
|
callbacks.onOpenUrl(url.toString());
|
|
787
827
|
const raw = await callbacks.onPromptCode(
|
|
@@ -796,7 +836,7 @@ async function loginGemini(callbacks) {
|
|
|
796
836
|
}
|
|
797
837
|
code = parsed.code;
|
|
798
838
|
}
|
|
799
|
-
const creds = await exchangeGeminiCode(code, verifier, redirectUri,
|
|
839
|
+
const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret);
|
|
800
840
|
callbacks.onStatus("Setting up Gemini Code Assist access...");
|
|
801
841
|
const projectId = await setupCodeAssistProject(creds.accessToken, callbacks);
|
|
802
842
|
return {
|
|
@@ -805,11 +845,11 @@ async function loginGemini(callbacks) {
|
|
|
805
845
|
};
|
|
806
846
|
}
|
|
807
847
|
async function refreshGeminiToken(refreshToken) {
|
|
808
|
-
const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
|
|
848
|
+
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
809
849
|
const data = await postTokenRequest2({
|
|
810
850
|
grant_type: "refresh_token",
|
|
811
851
|
refresh_token: refreshToken,
|
|
812
|
-
client_id:
|
|
852
|
+
client_id: clientId2,
|
|
813
853
|
client_secret: clientSecret
|
|
814
854
|
});
|
|
815
855
|
return {
|
|
@@ -819,9 +859,9 @@ async function refreshGeminiToken(refreshToken) {
|
|
|
819
859
|
};
|
|
820
860
|
}
|
|
821
861
|
function getGeminiOAuthClientCredentials() {
|
|
822
|
-
const
|
|
862
|
+
const clientId2 = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
|
|
823
863
|
const clientSecret = process.env[CLIENT_SECRET_ENV]?.trim() || DEFAULT_CLIENT_SECRET;
|
|
824
|
-
return { clientId, clientSecret };
|
|
864
|
+
return { clientId: clientId2, clientSecret };
|
|
825
865
|
}
|
|
826
866
|
async function getLoopbackRedirectUri() {
|
|
827
867
|
return new Promise((resolve, reject) => {
|
|
@@ -859,7 +899,7 @@ function parseAuthorizationInput2(input) {
|
|
|
859
899
|
}
|
|
860
900
|
return { code: value };
|
|
861
901
|
}
|
|
862
|
-
async function
|
|
902
|
+
async function loginWithServer(authUrl, redirectUri, expectedState, callbacks) {
|
|
863
903
|
const redirect = new URL(redirectUri);
|
|
864
904
|
const port = Number(redirect.port);
|
|
865
905
|
return new Promise((resolve, reject) => {
|
|
@@ -900,10 +940,10 @@ async function loginWithServer2(authUrl, redirectUri, expectedState, callbacks)
|
|
|
900
940
|
});
|
|
901
941
|
});
|
|
902
942
|
}
|
|
903
|
-
async function exchangeGeminiCode(code, verifier, redirectUri,
|
|
943
|
+
async function exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret) {
|
|
904
944
|
const data = await postTokenRequest2({
|
|
905
945
|
grant_type: "authorization_code",
|
|
906
|
-
client_id:
|
|
946
|
+
client_id: clientId2,
|
|
907
947
|
client_secret: clientSecret,
|
|
908
948
|
code,
|
|
909
949
|
redirect_uri: redirectUri,
|
|
@@ -1094,6 +1134,204 @@ function codeAssistHeaders(accessToken) {
|
|
|
1094
1134
|
};
|
|
1095
1135
|
}
|
|
1096
1136
|
|
|
1137
|
+
// src/oauth/xai.ts
|
|
1138
|
+
var DEFAULT_CLIENT_ID2 = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
1139
|
+
var DEFAULT_ISSUER = "https://auth.x.ai";
|
|
1140
|
+
var DEFAULT_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
1141
|
+
var OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
|
1142
|
+
var DEFAULT_GROK_CLI_VERSION = "0.2.101";
|
|
1143
|
+
var DEVICE_TIMEOUT_FALLBACK_MS = 10 * 60 * 1e3;
|
|
1144
|
+
var DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
|
|
1145
|
+
function issuer() {
|
|
1146
|
+
return (process.env.XAI_OAUTH_ISSUER ?? DEFAULT_ISSUER).replace(/\/+$/, "");
|
|
1147
|
+
}
|
|
1148
|
+
function clientId() {
|
|
1149
|
+
return process.env.XAI_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID2;
|
|
1150
|
+
}
|
|
1151
|
+
function grokCliBaseUrl() {
|
|
1152
|
+
return (process.env.XAI_CLI_BASE_URL ?? DEFAULT_CLI_BASE_URL).replace(/\/+$/, "");
|
|
1153
|
+
}
|
|
1154
|
+
function grokCliVersion() {
|
|
1155
|
+
const raw = process.env.GROK_CLI_VERSION ?? DEFAULT_GROK_CLI_VERSION;
|
|
1156
|
+
const cleaned = raw.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
1157
|
+
return cleaned.length > 0 ? cleaned : DEFAULT_GROK_CLI_VERSION;
|
|
1158
|
+
}
|
|
1159
|
+
function grokCliHeaders(modelId) {
|
|
1160
|
+
return {
|
|
1161
|
+
"X-XAI-Token-Auth": "xai-grok-cli",
|
|
1162
|
+
"x-grok-client-version": grokCliVersion(),
|
|
1163
|
+
"x-grok-client-identifier": "ezcoder",
|
|
1164
|
+
...modelId ? { "x-grok-model-override": modelId } : {}
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function isGrokCliEndpoint(baseUrl) {
|
|
1168
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
1169
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
1170
|
+
return normalized === grokCliBaseUrl() || /(^|\.)grok\.com/i.test(normalized);
|
|
1171
|
+
}
|
|
1172
|
+
async function postForm2(endpoint, params) {
|
|
1173
|
+
const response = await fetch(`${issuer()}${endpoint}`, {
|
|
1174
|
+
method: "POST",
|
|
1175
|
+
headers: {
|
|
1176
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1177
|
+
Accept: "application/json",
|
|
1178
|
+
"User-Agent": `grok-cli/${grokCliVersion()}`
|
|
1179
|
+
},
|
|
1180
|
+
body: new URLSearchParams(params).toString()
|
|
1181
|
+
});
|
|
1182
|
+
let data = {};
|
|
1183
|
+
try {
|
|
1184
|
+
const parsed = await response.json();
|
|
1185
|
+
if (parsed && typeof parsed === "object") data = parsed;
|
|
1186
|
+
} catch {
|
|
1187
|
+
}
|
|
1188
|
+
return { status: response.status, data };
|
|
1189
|
+
}
|
|
1190
|
+
function errorDetail2(data) {
|
|
1191
|
+
const desc = data.error_description ?? data.message ?? data.error;
|
|
1192
|
+
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
1193
|
+
}
|
|
1194
|
+
function jwtExpirySeconds(token) {
|
|
1195
|
+
const payload = token.split(".")[1];
|
|
1196
|
+
if (!payload) return void 0;
|
|
1197
|
+
try {
|
|
1198
|
+
const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
|
|
1199
|
+
"utf-8"
|
|
1200
|
+
);
|
|
1201
|
+
const claims = JSON.parse(json);
|
|
1202
|
+
if (!claims || typeof claims !== "object") return void 0;
|
|
1203
|
+
const exp = claims.exp;
|
|
1204
|
+
return typeof exp === "number" && Number.isFinite(exp) && exp > 0 ? exp : void 0;
|
|
1205
|
+
} catch {
|
|
1206
|
+
return void 0;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function credsFromTokenResponse2(data, opts) {
|
|
1210
|
+
const accessToken = data.access_token;
|
|
1211
|
+
const responseRefreshToken = data.refresh_token;
|
|
1212
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
1213
|
+
throw new Error("Grok OAuth response missing access_token.");
|
|
1214
|
+
}
|
|
1215
|
+
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
1216
|
+
if (refreshToken.length === 0) {
|
|
1217
|
+
throw new Error(
|
|
1218
|
+
"Grok OAuth response missing refresh_token \u2014 the offline_access scope was not granted."
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
const responseExpiresIn = Number(data.expires_in);
|
|
1222
|
+
let expiresIn;
|
|
1223
|
+
if (Number.isFinite(responseExpiresIn) && responseExpiresIn > 0) {
|
|
1224
|
+
expiresIn = responseExpiresIn;
|
|
1225
|
+
} else {
|
|
1226
|
+
const exp = jwtExpirySeconds(accessToken);
|
|
1227
|
+
const fromJwt = exp !== void 0 ? exp - Math.floor(Date.now() / 1e3) : 0;
|
|
1228
|
+
expiresIn = fromJwt > 0 ? fromJwt : DEFAULT_TOKEN_LIFETIME_SECONDS;
|
|
1229
|
+
}
|
|
1230
|
+
return {
|
|
1231
|
+
accessToken,
|
|
1232
|
+
refreshToken,
|
|
1233
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
1234
|
+
expiresIn,
|
|
1235
|
+
baseUrl: grokCliBaseUrl()
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
async function requestDeviceAuthorization2() {
|
|
1239
|
+
const { status, data } = await postForm2("/oauth2/device/code", {
|
|
1240
|
+
client_id: clientId(),
|
|
1241
|
+
scope: OAUTH_SCOPE
|
|
1242
|
+
});
|
|
1243
|
+
if (status !== 200) {
|
|
1244
|
+
throw new Error(`Grok device authorization failed (${status}): ${errorDetail2(data)}`);
|
|
1245
|
+
}
|
|
1246
|
+
const userCode = data.user_code;
|
|
1247
|
+
const deviceCode = data.device_code;
|
|
1248
|
+
const verificationUriComplete = data.verification_uri_complete;
|
|
1249
|
+
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
1250
|
+
throw new Error("Grok device authorization response missing user_code/device_code.");
|
|
1251
|
+
}
|
|
1252
|
+
return {
|
|
1253
|
+
userCode,
|
|
1254
|
+
deviceCode,
|
|
1255
|
+
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
1256
|
+
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
1257
|
+
interval: Number(data.interval ?? 5) || 5,
|
|
1258
|
+
// RFC 8628 §3.2: the server states how long the device code lives. Honor it
|
|
1259
|
+
// rather than imposing our own budget — polling a code we know is dead only
|
|
1260
|
+
// burns requests, and a longer local window would keep a user waiting past
|
|
1261
|
+
// the point the code can ever succeed.
|
|
1262
|
+
expiresInMs: (Number(data.expires_in) || 0) > 0 ? Number(data.expires_in) * 1e3 : DEVICE_TIMEOUT_FALLBACK_MS
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
async function pollDeviceToken2(deviceCode) {
|
|
1266
|
+
const { status, data } = await postForm2("/oauth2/token", {
|
|
1267
|
+
client_id: clientId(),
|
|
1268
|
+
device_code: deviceCode,
|
|
1269
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1270
|
+
});
|
|
1271
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
1272
|
+
return { kind: "success", creds: credsFromTokenResponse2(data) };
|
|
1273
|
+
}
|
|
1274
|
+
if (status >= 500) {
|
|
1275
|
+
throw new Error(`Grok token polling server error (${status}): ${errorDetail2(data)}`);
|
|
1276
|
+
}
|
|
1277
|
+
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
1278
|
+
switch (errorCode) {
|
|
1279
|
+
case "authorization_pending":
|
|
1280
|
+
return { kind: "pending" };
|
|
1281
|
+
case "slow_down":
|
|
1282
|
+
return { kind: "slow_down" };
|
|
1283
|
+
case "expired_token":
|
|
1284
|
+
return { kind: "expired" };
|
|
1285
|
+
case "access_denied":
|
|
1286
|
+
return { kind: "denied" };
|
|
1287
|
+
default:
|
|
1288
|
+
throw new Error(`Grok token polling failed (${status}): ${errorDetail2(data)}`);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function sleep2(ms) {
|
|
1292
|
+
return new Promise((resolve) => {
|
|
1293
|
+
setTimeout(resolve, ms);
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
async function loginXai(callbacks) {
|
|
1297
|
+
const auth = await requestDeviceAuthorization2();
|
|
1298
|
+
callbacks.onStatus(
|
|
1299
|
+
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
1300
|
+
);
|
|
1301
|
+
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
1302
|
+
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
1303
|
+
const deadline = Date.now() + auth.expiresInMs;
|
|
1304
|
+
let interval = Math.max(auth.interval, 1);
|
|
1305
|
+
while (Date.now() < deadline) {
|
|
1306
|
+
const remaining = deadline - Date.now();
|
|
1307
|
+
await sleep2(Math.min(interval * 1e3, remaining));
|
|
1308
|
+
const result = await pollDeviceToken2(auth.deviceCode);
|
|
1309
|
+
if (result.kind === "success") return result.creds;
|
|
1310
|
+
if (result.kind === "denied") {
|
|
1311
|
+
throw new Error("Grok authorization was denied.");
|
|
1312
|
+
}
|
|
1313
|
+
if (result.kind === "expired") {
|
|
1314
|
+
throw new Error("Grok device code expired. Please run login again.");
|
|
1315
|
+
}
|
|
1316
|
+
if (result.kind === "slow_down") {
|
|
1317
|
+
interval += 5;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
throw new Error("Grok login timed out. Please run login again.");
|
|
1321
|
+
}
|
|
1322
|
+
async function refreshXaiToken(refreshToken) {
|
|
1323
|
+
const { status, data } = await postForm2("/oauth2/token", {
|
|
1324
|
+
client_id: clientId(),
|
|
1325
|
+
grant_type: "refresh_token",
|
|
1326
|
+
refresh_token: refreshToken
|
|
1327
|
+
});
|
|
1328
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
1329
|
+
return credsFromTokenResponse2(data, { fallbackRefreshToken: refreshToken });
|
|
1330
|
+
}
|
|
1331
|
+
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
1332
|
+
throw new Error(`Grok token refresh failed (${status}): ${errorCode || errorDetail2(data)}`);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1097
1335
|
// src/file-lock.ts
|
|
1098
1336
|
import fs3 from "fs/promises";
|
|
1099
1337
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
@@ -1158,18 +1396,49 @@ function isAlive(pid) {
|
|
|
1158
1396
|
|
|
1159
1397
|
// src/auth-storage.ts
|
|
1160
1398
|
var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
|
|
1399
|
+
var XAI_OAUTH_KEY = "xai-oauth";
|
|
1400
|
+
var DUAL_AUTH_PROVIDERS = [
|
|
1401
|
+
{
|
|
1402
|
+
provider: "moonshot",
|
|
1403
|
+
oauthKey: MOONSHOT_OAUTH_KEY,
|
|
1404
|
+
oauthLabel: "Kimi OAuth",
|
|
1405
|
+
apiKeyLabel: "Moonshot API key",
|
|
1406
|
+
restoreHint: 'Run "ezcoder login" and choose Kimi OAuth to restore OAuth auth.'
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
provider: "xai",
|
|
1410
|
+
oauthKey: XAI_OAUTH_KEY,
|
|
1411
|
+
oauthLabel: "Grok OAuth",
|
|
1412
|
+
apiKeyLabel: "xAI API key",
|
|
1413
|
+
restoreHint: 'Run "ezcoder login" and choose Grok OAuth to restore OAuth auth.'
|
|
1414
|
+
}
|
|
1415
|
+
];
|
|
1416
|
+
function dualAuthProvider(provider) {
|
|
1417
|
+
return DUAL_AUTH_PROVIDERS.find((entry) => entry.provider === provider);
|
|
1418
|
+
}
|
|
1419
|
+
function dualAuthProviderByOAuthKey(storageKey) {
|
|
1420
|
+
return DUAL_AUTH_PROVIDERS.find((entry) => entry.oauthKey === storageKey);
|
|
1421
|
+
}
|
|
1422
|
+
function oauthStorageKey(provider) {
|
|
1423
|
+
return dualAuthProvider(provider)?.oauthKey;
|
|
1424
|
+
}
|
|
1425
|
+
function providerStorageKeys(provider) {
|
|
1426
|
+
const dual = dualAuthProvider(provider);
|
|
1427
|
+
return dual ? [dual.oauthKey, dual.provider] : [provider];
|
|
1428
|
+
}
|
|
1161
1429
|
var XIAOMI_CREDITS_KEY = "xiaomi-credits";
|
|
1162
1430
|
var LOCAL_AUTH_KEY_PREFIX = "local:";
|
|
1163
1431
|
var LOCAL_CREDENTIAL_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
1164
1432
|
function activeBaseUrlEntry(data, provider) {
|
|
1165
|
-
|
|
1166
|
-
|
|
1433
|
+
const dual = dualAuthProvider(provider);
|
|
1434
|
+
if (dual) {
|
|
1435
|
+
const oauth = data[dual.oauthKey];
|
|
1167
1436
|
if (oauth) {
|
|
1168
1437
|
const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
|
|
1169
|
-
if (Date.now() < exhaustedUntil && data[
|
|
1438
|
+
if (Date.now() < exhaustedUntil && data[dual.provider]) return data[dual.provider];
|
|
1170
1439
|
return oauth;
|
|
1171
1440
|
}
|
|
1172
|
-
return data[
|
|
1441
|
+
return data[dual.provider];
|
|
1173
1442
|
}
|
|
1174
1443
|
return data[provider];
|
|
1175
1444
|
}
|
|
@@ -1204,6 +1473,16 @@ var AuthStorage = class {
|
|
|
1204
1473
|
data = {};
|
|
1205
1474
|
filePath;
|
|
1206
1475
|
loaded = false;
|
|
1476
|
+
/**
|
|
1477
|
+
* mtime+size of the file as of the cached snapshot (`size: -1` = no file).
|
|
1478
|
+
* auth.json is shared: the desktop app writes API keys and disconnects
|
|
1479
|
+
* NATIVELY (so they work with no daemon running), and every window/process has
|
|
1480
|
+
* its own AuthStorage. A load-once cache therefore goes stale — the sidecar
|
|
1481
|
+
* would keep listing models for a provider just disconnected, and hide the
|
|
1482
|
+
* ones just connected, until the daemon restarted.
|
|
1483
|
+
*/
|
|
1484
|
+
snapshotMtimeMs = 0;
|
|
1485
|
+
snapshotSize = -1;
|
|
1207
1486
|
/** Per-provider lock to serialize concurrent refresh calls. */
|
|
1208
1487
|
refreshLocks = /* @__PURE__ */ new Map();
|
|
1209
1488
|
constructor(filePath) {
|
|
@@ -1215,12 +1494,12 @@ var AuthStorage = class {
|
|
|
1215
1494
|
}
|
|
1216
1495
|
/** List provider keys with stored credentials. */
|
|
1217
1496
|
async listProviders() {
|
|
1218
|
-
await this.
|
|
1497
|
+
await this.ensureFresh();
|
|
1219
1498
|
return Object.keys(this.data);
|
|
1220
1499
|
}
|
|
1221
1500
|
/** True if credentials exist for `provider`. */
|
|
1222
1501
|
async hasCredentials(provider) {
|
|
1223
|
-
await this.
|
|
1502
|
+
await this.ensureFresh();
|
|
1224
1503
|
return Boolean(this.data[provider]);
|
|
1225
1504
|
}
|
|
1226
1505
|
/**
|
|
@@ -1231,18 +1510,19 @@ var AuthStorage = class {
|
|
|
1231
1510
|
* instead of re-deriving the same order.
|
|
1232
1511
|
*/
|
|
1233
1512
|
async pickStorageKey(keys) {
|
|
1234
|
-
await this.
|
|
1513
|
+
await this.ensureFresh();
|
|
1235
1514
|
return keys.find((key) => Boolean(this.data[key]));
|
|
1236
1515
|
}
|
|
1237
1516
|
/**
|
|
1238
|
-
* True if the user has any usable auth for the logical provider. For
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1517
|
+
* True if the user has any usable auth for the logical provider. For a
|
|
1518
|
+
* dual-auth provider (Kimi/Grok) either the OAuth credential or the API key
|
|
1519
|
+
* satisfies it.
|
|
1241
1520
|
*/
|
|
1242
1521
|
async hasProviderAuth(provider) {
|
|
1243
|
-
await this.
|
|
1244
|
-
|
|
1245
|
-
|
|
1522
|
+
await this.ensureFresh();
|
|
1523
|
+
const dual = dualAuthProvider(provider);
|
|
1524
|
+
if (dual) {
|
|
1525
|
+
return Boolean(this.data[dual.oauthKey] || this.data[dual.provider]);
|
|
1246
1526
|
}
|
|
1247
1527
|
if (provider === "xiaomi") {
|
|
1248
1528
|
return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
|
|
@@ -1276,14 +1556,16 @@ var AuthStorage = class {
|
|
|
1276
1556
|
}
|
|
1277
1557
|
/**
|
|
1278
1558
|
* True if the active credential for `provider` is a static API key with no
|
|
1279
|
-
* refresh mechanism. For
|
|
1280
|
-
* credential is absent (a
|
|
1559
|
+
* refresh mechanism. For a dual-auth provider this is only true when its OAuth
|
|
1560
|
+
* credential is absent or sidelined (a live OAuth credential is refreshable).
|
|
1281
1561
|
*/
|
|
1282
1562
|
async isStaticApiKey(provider) {
|
|
1283
|
-
await this.
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1563
|
+
await this.ensureFresh();
|
|
1564
|
+
const dual = dualAuthProvider(provider);
|
|
1565
|
+
const oauthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
1566
|
+
if (dual && oauthCreds) {
|
|
1567
|
+
const exhaustedUntil = oauthCreds.usageExhaustedUntil ?? 0;
|
|
1568
|
+
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data[dual.provider]);
|
|
1287
1569
|
if (!apiKeyActive) return false;
|
|
1288
1570
|
}
|
|
1289
1571
|
return STATIC_API_KEY_PROVIDERS.has(provider);
|
|
@@ -1291,26 +1573,30 @@ var AuthStorage = class {
|
|
|
1291
1573
|
/**
|
|
1292
1574
|
* The base URL on the credential that is active right now, if any.
|
|
1293
1575
|
* Synchronous — call only after load()/resolveCredentials() populated the
|
|
1294
|
-
* snapshot. For
|
|
1295
|
-
*
|
|
1296
|
-
* usage-exhausted with an
|
|
1576
|
+
* snapshot. For a dual-auth provider this is the subscription endpoint (Kimi
|
|
1577
|
+
* For Coding, the Grok CLI proxy) whenever the OAuth entry is the one
|
|
1578
|
+
* resolveCredentials would serve (i.e. not currently usage-exhausted with an
|
|
1579
|
+
* API key configured).
|
|
1297
1580
|
*/
|
|
1298
1581
|
getStoredBaseUrl(provider) {
|
|
1299
1582
|
return activeBaseUrlEntry(this.data, provider)?.baseUrl;
|
|
1300
1583
|
}
|
|
1301
1584
|
async load() {
|
|
1585
|
+
const first = !this.loaded;
|
|
1302
1586
|
await withFileLock(this.filePath, async () => {
|
|
1303
1587
|
try {
|
|
1304
1588
|
const content = await fs4.readFile(this.filePath, "utf-8");
|
|
1305
1589
|
this.data = JSON.parse(content);
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1590
|
+
if (first) {
|
|
1591
|
+
log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
|
|
1592
|
+
providers: Object.keys(this.data).join(",") || "(none)"
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1309
1595
|
} catch (err) {
|
|
1310
1596
|
this.data = {};
|
|
1311
1597
|
const code = err.code;
|
|
1312
1598
|
if (code === "ENOENT") {
|
|
1313
|
-
log("INFO", "auth", `No auth file found at ${this.filePath} (first run)`);
|
|
1599
|
+
if (first) log("INFO", "auth", `No auth file found at ${this.filePath} (first run)`);
|
|
1314
1600
|
} else {
|
|
1315
1601
|
log(
|
|
1316
1602
|
"ERROR",
|
|
@@ -1322,6 +1608,7 @@ var AuthStorage = class {
|
|
|
1322
1608
|
}
|
|
1323
1609
|
});
|
|
1324
1610
|
this.loaded = true;
|
|
1611
|
+
await this.rememberSnapshot();
|
|
1325
1612
|
}
|
|
1326
1613
|
async ensureLoaded() {
|
|
1327
1614
|
if (!this.loaded) await this.load();
|
|
@@ -1335,6 +1622,42 @@ var AuthStorage = class {
|
|
|
1335
1622
|
this.loaded = false;
|
|
1336
1623
|
await this.load();
|
|
1337
1624
|
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Like {@link ensureLoaded}, but re-reads when the file changed since this
|
|
1627
|
+
* snapshot — a cheap stat, not a re-parse. Used by the "what is connected?"
|
|
1628
|
+
* readers, which must reflect writes made by another window, the CLI, or the
|
|
1629
|
+
* desktop app's native (daemon-free) API-key and disconnect paths.
|
|
1630
|
+
*
|
|
1631
|
+
* Deliberately NOT used by {@link resolveCredentials}: that path compares the
|
|
1632
|
+
* caller's snapshot against the latest file to detect a concurrent re-login,
|
|
1633
|
+
* and silently refreshing this instance's view first would destroy the
|
|
1634
|
+
* evidence that the token it just had rejected has already been replaced.
|
|
1635
|
+
*/
|
|
1636
|
+
async ensureFresh() {
|
|
1637
|
+
if (!this.loaded) {
|
|
1638
|
+
await this.load();
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
let changed;
|
|
1642
|
+
try {
|
|
1643
|
+
const stat = await fs4.stat(this.filePath);
|
|
1644
|
+
changed = stat.mtimeMs !== this.snapshotMtimeMs || stat.size !== this.snapshotSize;
|
|
1645
|
+
} catch {
|
|
1646
|
+
changed = this.snapshotSize !== -1;
|
|
1647
|
+
}
|
|
1648
|
+
if (changed) await this.load();
|
|
1649
|
+
}
|
|
1650
|
+
/** Record the file identity behind the current snapshot. */
|
|
1651
|
+
async rememberSnapshot() {
|
|
1652
|
+
try {
|
|
1653
|
+
const stat = await fs4.stat(this.filePath);
|
|
1654
|
+
this.snapshotMtimeMs = stat.mtimeMs;
|
|
1655
|
+
this.snapshotSize = stat.size;
|
|
1656
|
+
} catch {
|
|
1657
|
+
this.snapshotMtimeMs = 0;
|
|
1658
|
+
this.snapshotSize = -1;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1338
1661
|
/**
|
|
1339
1662
|
* Apply one provider-scoped mutation to the latest on-disk snapshot.
|
|
1340
1663
|
* AuthStorage instances live in every app session/process, so writing this
|
|
@@ -1350,14 +1673,16 @@ var AuthStorage = class {
|
|
|
1350
1673
|
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1351
1674
|
this.data = latest;
|
|
1352
1675
|
});
|
|
1676
|
+
await this.rememberSnapshot();
|
|
1353
1677
|
}
|
|
1354
1678
|
async reloadLatest() {
|
|
1355
1679
|
await withFileLock(this.filePath, async () => {
|
|
1356
1680
|
this.data = await readAuthData(this.filePath);
|
|
1357
1681
|
});
|
|
1682
|
+
await this.rememberSnapshot();
|
|
1358
1683
|
}
|
|
1359
1684
|
async getCredentials(provider) {
|
|
1360
|
-
await this.
|
|
1685
|
+
await this.ensureFresh();
|
|
1361
1686
|
return this.data[provider];
|
|
1362
1687
|
}
|
|
1363
1688
|
async setCredentials(provider, creds) {
|
|
@@ -1375,7 +1700,7 @@ var AuthStorage = class {
|
|
|
1375
1700
|
* `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
|
|
1376
1701
|
* 15-minute default when no reset time is known. While the mark is in the
|
|
1377
1702
|
* future, `resolveCredentials("moonshot")` serves the Moonshot API key
|
|
1378
|
-
* instead of the
|
|
1703
|
+
* instead of the subscription OAuth credential (when both are configured) — OAuth
|
|
1379
1704
|
* stays the preferred credential and is retried automatically once the mark
|
|
1380
1705
|
* lapses. Persisted to auth.json so a restart (or another ezcoder-app window)
|
|
1381
1706
|
* doesn't burn a request rediscovering the same exhausted window. No-op if
|
|
@@ -1406,13 +1731,21 @@ var AuthStorage = class {
|
|
|
1406
1731
|
}
|
|
1407
1732
|
/**
|
|
1408
1733
|
* Returns valid credentials, auto-refreshing if expired.
|
|
1734
|
+
*
|
|
1409
1735
|
* If `forceRefresh` is true, refreshes even if the token hasn't expired
|
|
1410
|
-
* (useful when the provider rejects a token with 401 before its stored
|
|
1736
|
+
* (useful when the provider rejects a token with 401 before its stored
|
|
1737
|
+
* expiry). Callers recovering from a rejection should also pass
|
|
1738
|
+
* `rejectedToken` — see the stampede guard below.
|
|
1739
|
+
*
|
|
1411
1740
|
* Throws if not logged in.
|
|
1412
1741
|
*/
|
|
1413
1742
|
async resolveCredentials(provider, opts) {
|
|
1414
|
-
|
|
1415
|
-
|
|
1743
|
+
if (opts?.forceRefresh) {
|
|
1744
|
+
await this.ensureLoaded();
|
|
1745
|
+
} else {
|
|
1746
|
+
await this.ensureFresh();
|
|
1747
|
+
}
|
|
1748
|
+
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
|
|
1416
1749
|
if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
|
|
1417
1750
|
await this.reloadLatest();
|
|
1418
1751
|
}
|
|
@@ -1423,28 +1756,31 @@ var AuthStorage = class {
|
|
|
1423
1756
|
}
|
|
1424
1757
|
throw new NotLoggedInError(provider);
|
|
1425
1758
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1759
|
+
const dual = dualAuthProvider(provider);
|
|
1760
|
+
const dualOAuthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
1761
|
+
if (dual && dualOAuthCreds) {
|
|
1762
|
+
const exhaustedUntil = dualOAuthCreds.usageExhaustedUntil ?? 0;
|
|
1763
|
+
if (Date.now() < exhaustedUntil && this.data[dual.provider]) {
|
|
1429
1764
|
log(
|
|
1430
1765
|
"WARN",
|
|
1431
1766
|
"auth",
|
|
1432
|
-
|
|
1767
|
+
`${dual.oauthLabel} usage window is exhausted \u2014 using the ${dual.apiKeyLabel} until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
|
|
1433
1768
|
);
|
|
1434
|
-
return this.data[
|
|
1769
|
+
return this.data[dual.provider];
|
|
1435
1770
|
}
|
|
1436
1771
|
try {
|
|
1437
|
-
return await this.resolveCredentials(
|
|
1438
|
-
...opts?.forceRefresh ? { forceRefresh: true } : {}
|
|
1772
|
+
return await this.resolveCredentials(dual.oauthKey, {
|
|
1773
|
+
...opts?.forceRefresh ? { forceRefresh: true } : {},
|
|
1774
|
+
...opts?.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
|
|
1439
1775
|
});
|
|
1440
1776
|
} catch (err) {
|
|
1441
|
-
if (err instanceof NotLoggedInError && this.data[
|
|
1777
|
+
if (err instanceof NotLoggedInError && this.data[dual.provider]) {
|
|
1442
1778
|
log(
|
|
1443
1779
|
"WARN",
|
|
1444
1780
|
"auth",
|
|
1445
|
-
|
|
1781
|
+
`${dual.oauthLabel} credential is no longer valid \u2014 falling back to the ${dual.apiKeyLabel}. ${dual.restoreHint}`
|
|
1446
1782
|
);
|
|
1447
|
-
return this.data[
|
|
1783
|
+
return this.data[dual.provider];
|
|
1448
1784
|
}
|
|
1449
1785
|
throw err;
|
|
1450
1786
|
}
|
|
@@ -1469,11 +1805,19 @@ var AuthStorage = class {
|
|
|
1469
1805
|
throw new NotLoggedInError(provider);
|
|
1470
1806
|
}
|
|
1471
1807
|
const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
|
|
1472
|
-
|
|
1808
|
+
const rotatedBySibling = opts?.rejectedToken !== void 0 && latestCreds.accessToken !== opts.rejectedToken;
|
|
1809
|
+
if (credentialWasReplaced || rotatedBySibling || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
|
|
1810
|
+
if (rotatedBySibling && !credentialWasReplaced) {
|
|
1811
|
+
log(
|
|
1812
|
+
"INFO",
|
|
1813
|
+
"auth",
|
|
1814
|
+
`${provider} token was rotated by another EZ Coder process \u2014 adopting it instead of refreshing again`
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1473
1817
|
this.data = latest;
|
|
1474
1818
|
return latestCreds;
|
|
1475
1819
|
}
|
|
1476
|
-
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
|
|
1820
|
+
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : provider === XAI_OAUTH_KEY ? refreshXaiToken : refreshOpenAIToken;
|
|
1477
1821
|
let refreshed;
|
|
1478
1822
|
try {
|
|
1479
1823
|
refreshed = await refreshFn(latestCreds.refreshToken);
|
|
@@ -1507,6 +1851,7 @@ var AuthStorage = class {
|
|
|
1507
1851
|
return await refreshPromise;
|
|
1508
1852
|
} finally {
|
|
1509
1853
|
this.refreshLocks.delete(provider);
|
|
1854
|
+
await this.rememberSnapshot();
|
|
1510
1855
|
}
|
|
1511
1856
|
}
|
|
1512
1857
|
/**
|
|
@@ -1770,7 +2115,7 @@ var MODELS = [
|
|
|
1770
2115
|
{
|
|
1771
2116
|
// Gemini 3.1 Pro is public preview — gated behind Code Assist preview
|
|
1772
2117
|
// enablement, so free/personal OAuth accounts 404 on it (see
|
|
1773
|
-
// ACCOUNT_GATED_MODELS in
|
|
2118
|
+
// ACCOUNT_GATED_MODELS in @prestyj/ai's gemini provider).
|
|
1774
2119
|
id: "gemini-3.1-pro-preview",
|
|
1775
2120
|
name: "Gemini 3.1 Pro (Preview)",
|
|
1776
2121
|
provider: "gemini",
|
|
@@ -1818,11 +2163,16 @@ var MODELS = [
|
|
|
1818
2163
|
maxThinkingLevel: "high"
|
|
1819
2164
|
},
|
|
1820
2165
|
// ── Z.AI (GLM) ─────────────────────────────────────────
|
|
1821
|
-
// GLM-5.
|
|
1822
|
-
//
|
|
2166
|
+
// GLM-5.3 is the only GLM entry: it supersedes 5.2 (same GLM-5 base, all
|
|
2167
|
+
// gains from post-training) and the coding endpoint already answers
|
|
2168
|
+
// `glm-5.2` requests as glm-5.3, so the older ids were menu clutter that
|
|
2169
|
+
// routed to strictly worse coding for the same plan quota.
|
|
2170
|
+
// Released 2026-08-14; live on the coding endpoint (verified), while the
|
|
2171
|
+
// standard paas API is still "coming soon". `max` is both the ceiling and
|
|
2172
|
+
// Z.AI's own default — the rungs below it live in thinking-level.ts.
|
|
1823
2173
|
{
|
|
1824
|
-
id: "glm-5.
|
|
1825
|
-
name: "GLM-5.
|
|
2174
|
+
id: "glm-5.3",
|
|
2175
|
+
name: "GLM-5.3",
|
|
1826
2176
|
provider: "glm",
|
|
1827
2177
|
contextWindow: 1e6,
|
|
1828
2178
|
maxOutputTokens: 131072,
|
|
@@ -1830,43 +2180,7 @@ var MODELS = [
|
|
|
1830
2180
|
supportsImages: false,
|
|
1831
2181
|
supportsVideo: false,
|
|
1832
2182
|
costTier: "medium",
|
|
1833
|
-
maxThinkingLevel: "
|
|
1834
|
-
},
|
|
1835
|
-
{
|
|
1836
|
-
id: "glm-5.1",
|
|
1837
|
-
name: "GLM-5.1",
|
|
1838
|
-
provider: "glm",
|
|
1839
|
-
contextWindow: 204800,
|
|
1840
|
-
maxOutputTokens: 131072,
|
|
1841
|
-
supportsThinking: true,
|
|
1842
|
-
supportsImages: false,
|
|
1843
|
-
supportsVideo: false,
|
|
1844
|
-
costTier: "medium",
|
|
1845
|
-
maxThinkingLevel: "high"
|
|
1846
|
-
},
|
|
1847
|
-
{
|
|
1848
|
-
id: "glm-4.7",
|
|
1849
|
-
name: "GLM-4.7",
|
|
1850
|
-
provider: "glm",
|
|
1851
|
-
contextWindow: 2e5,
|
|
1852
|
-
maxOutputTokens: 131072,
|
|
1853
|
-
supportsThinking: true,
|
|
1854
|
-
supportsImages: false,
|
|
1855
|
-
supportsVideo: false,
|
|
1856
|
-
costTier: "low",
|
|
1857
|
-
maxThinkingLevel: "high"
|
|
1858
|
-
},
|
|
1859
|
-
{
|
|
1860
|
-
id: "glm-4.7-flash",
|
|
1861
|
-
name: "GLM-4.7 Flash",
|
|
1862
|
-
provider: "glm",
|
|
1863
|
-
contextWindow: 2e5,
|
|
1864
|
-
maxOutputTokens: 131072,
|
|
1865
|
-
supportsThinking: true,
|
|
1866
|
-
supportsImages: false,
|
|
1867
|
-
supportsVideo: false,
|
|
1868
|
-
costTier: "low",
|
|
1869
|
-
maxThinkingLevel: "high"
|
|
2183
|
+
maxThinkingLevel: "max"
|
|
1870
2184
|
},
|
|
1871
2185
|
// ── MiniMax ────────────────────────────────────────────
|
|
1872
2186
|
{
|
|
@@ -2011,7 +2325,7 @@ function getDefaultModel(provider) {
|
|
|
2011
2325
|
if (provider === "xiaomi") return MODELS.find((m) => m.id === "mimo-v2.5-pro");
|
|
2012
2326
|
if (provider === "openai") return MODELS.find((m) => m.id === "gpt-5.6-sol");
|
|
2013
2327
|
if (provider === "gemini") return MODELS.find((m) => m.id === "gemini-3.1-flash-lite");
|
|
2014
|
-
if (provider === "glm") return MODELS.find((m) => m.id === "glm-5.
|
|
2328
|
+
if (provider === "glm") return MODELS.find((m) => m.id === "glm-5.3");
|
|
2015
2329
|
if (provider === "moonshot") return MODELS.find((m) => m.id === "kimi-k3");
|
|
2016
2330
|
if (provider === "minimax") return MODELS.find((m) => m.id === "MiniMax-M3");
|
|
2017
2331
|
if (provider === "deepseek") return MODELS.find((m) => m.id === "deepseek-v4-pro");
|
|
@@ -2093,8 +2407,18 @@ export {
|
|
|
2093
2407
|
refreshOpenAIToken,
|
|
2094
2408
|
loginGemini,
|
|
2095
2409
|
refreshGeminiToken,
|
|
2410
|
+
grokCliBaseUrl,
|
|
2411
|
+
grokCliHeaders,
|
|
2412
|
+
isGrokCliEndpoint,
|
|
2413
|
+
loginXai,
|
|
2414
|
+
refreshXaiToken,
|
|
2096
2415
|
withFileLock,
|
|
2097
2416
|
MOONSHOT_OAUTH_KEY,
|
|
2417
|
+
XAI_OAUTH_KEY,
|
|
2418
|
+
dualAuthProvider,
|
|
2419
|
+
dualAuthProviderByOAuthKey,
|
|
2420
|
+
oauthStorageKey,
|
|
2421
|
+
providerStorageKeys,
|
|
2098
2422
|
XIAOMI_CREDITS_KEY,
|
|
2099
2423
|
LOCAL_AUTH_KEY_PREFIX,
|
|
2100
2424
|
readStoredBaseUrlSync,
|
|
@@ -2119,4 +2443,4 @@ export {
|
|
|
2119
2443
|
getSummaryModel,
|
|
2120
2444
|
getFastModel
|
|
2121
2445
|
};
|
|
2122
|
-
//# sourceMappingURL=chunk-
|
|
2446
|
+
//# sourceMappingURL=chunk-S6QBRJ4D.js.map
|