@kenkaiiii/gg-core 5.35.1 → 5.37.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-C6Q3GFWE.js → chunk-XDE6VUI4.js} +349 -49
- package/dist/chunk-XDE6VUI4.js.map +1 -0
- package/dist/index.cjs +360 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -10
- package/dist/index.d.ts +138 -10
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +3 -0
- package/dist/model-registry.cjs.map +1 -1
- package/dist/model-registry.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-C6Q3GFWE.js.map +0 -1
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,
|
|
@@ -896,13 +906,13 @@ var CodeAssistHttpError = class extends Error {
|
|
|
896
906
|
}
|
|
897
907
|
};
|
|
898
908
|
async function loginGemini(callbacks) {
|
|
899
|
-
const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
|
|
909
|
+
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
900
910
|
const { verifier, challenge } = await generatePKCE();
|
|
901
911
|
const state = import_node_crypto5.default.randomBytes(32).toString("hex");
|
|
902
912
|
const redirectUri = await getLoopbackRedirectUri();
|
|
903
913
|
const url = new URL(AUTHORIZE_URL3);
|
|
904
914
|
url.searchParams.set("response_type", "code");
|
|
905
|
-
url.searchParams.set("client_id",
|
|
915
|
+
url.searchParams.set("client_id", clientId2);
|
|
906
916
|
url.searchParams.set("redirect_uri", redirectUri);
|
|
907
917
|
url.searchParams.set("scope", SCOPE2);
|
|
908
918
|
url.searchParams.set("access_type", "offline");
|
|
@@ -927,7 +937,7 @@ async function loginGemini(callbacks) {
|
|
|
927
937
|
}
|
|
928
938
|
code = parsed.code;
|
|
929
939
|
}
|
|
930
|
-
const creds = await exchangeGeminiCode(code, verifier, redirectUri,
|
|
940
|
+
const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret);
|
|
931
941
|
callbacks.onStatus("Setting up Gemini Code Assist access...");
|
|
932
942
|
const projectId = await setupCodeAssistProject(creds.accessToken, callbacks);
|
|
933
943
|
return {
|
|
@@ -936,11 +946,11 @@ async function loginGemini(callbacks) {
|
|
|
936
946
|
};
|
|
937
947
|
}
|
|
938
948
|
async function refreshGeminiToken(refreshToken) {
|
|
939
|
-
const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
|
|
949
|
+
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
940
950
|
const data = await postTokenRequest2({
|
|
941
951
|
grant_type: "refresh_token",
|
|
942
952
|
refresh_token: refreshToken,
|
|
943
|
-
client_id:
|
|
953
|
+
client_id: clientId2,
|
|
944
954
|
client_secret: clientSecret
|
|
945
955
|
});
|
|
946
956
|
return {
|
|
@@ -950,9 +960,9 @@ async function refreshGeminiToken(refreshToken) {
|
|
|
950
960
|
};
|
|
951
961
|
}
|
|
952
962
|
function getGeminiOAuthClientCredentials() {
|
|
953
|
-
const
|
|
963
|
+
const clientId2 = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
|
|
954
964
|
const clientSecret = process.env[CLIENT_SECRET_ENV]?.trim() || DEFAULT_CLIENT_SECRET;
|
|
955
|
-
return { clientId, clientSecret };
|
|
965
|
+
return { clientId: clientId2, clientSecret };
|
|
956
966
|
}
|
|
957
967
|
async function getLoopbackRedirectUri() {
|
|
958
968
|
return new Promise((resolve, reject) => {
|
|
@@ -1031,10 +1041,10 @@ async function loginWithServer2(authUrl, redirectUri, expectedState, callbacks)
|
|
|
1031
1041
|
});
|
|
1032
1042
|
});
|
|
1033
1043
|
}
|
|
1034
|
-
async function exchangeGeminiCode(code, verifier, redirectUri,
|
|
1044
|
+
async function exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret) {
|
|
1035
1045
|
const data = await postTokenRequest2({
|
|
1036
1046
|
grant_type: "authorization_code",
|
|
1037
|
-
client_id:
|
|
1047
|
+
client_id: clientId2,
|
|
1038
1048
|
client_secret: clientSecret,
|
|
1039
1049
|
code,
|
|
1040
1050
|
redirect_uri: redirectUri,
|
|
@@ -1225,6 +1235,204 @@ function codeAssistHeaders(accessToken) {
|
|
|
1225
1235
|
};
|
|
1226
1236
|
}
|
|
1227
1237
|
|
|
1238
|
+
// src/oauth/xai.ts
|
|
1239
|
+
var DEFAULT_CLIENT_ID2 = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
1240
|
+
var DEFAULT_ISSUER = "https://auth.x.ai";
|
|
1241
|
+
var DEFAULT_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
1242
|
+
var OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
|
1243
|
+
var DEFAULT_GROK_CLI_VERSION = "0.2.101";
|
|
1244
|
+
var DEVICE_TIMEOUT_FALLBACK_MS = 10 * 60 * 1e3;
|
|
1245
|
+
var DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
|
|
1246
|
+
function issuer() {
|
|
1247
|
+
return (process.env.XAI_OAUTH_ISSUER ?? DEFAULT_ISSUER).replace(/\/+$/, "");
|
|
1248
|
+
}
|
|
1249
|
+
function clientId() {
|
|
1250
|
+
return process.env.XAI_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID2;
|
|
1251
|
+
}
|
|
1252
|
+
function grokCliBaseUrl() {
|
|
1253
|
+
return (process.env.XAI_CLI_BASE_URL ?? DEFAULT_CLI_BASE_URL).replace(/\/+$/, "");
|
|
1254
|
+
}
|
|
1255
|
+
function grokCliVersion() {
|
|
1256
|
+
const raw = process.env.GROK_CLI_VERSION ?? DEFAULT_GROK_CLI_VERSION;
|
|
1257
|
+
const cleaned = raw.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
1258
|
+
return cleaned.length > 0 ? cleaned : DEFAULT_GROK_CLI_VERSION;
|
|
1259
|
+
}
|
|
1260
|
+
function grokCliHeaders(modelId) {
|
|
1261
|
+
return {
|
|
1262
|
+
"X-XAI-Token-Auth": "xai-grok-cli",
|
|
1263
|
+
"x-grok-client-version": grokCliVersion(),
|
|
1264
|
+
"x-grok-client-identifier": "ggcoder",
|
|
1265
|
+
...modelId ? { "x-grok-model-override": modelId } : {}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
function isGrokCliEndpoint(baseUrl) {
|
|
1269
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
1270
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
1271
|
+
return normalized === grokCliBaseUrl() || /(^|\.)grok\.com/i.test(normalized);
|
|
1272
|
+
}
|
|
1273
|
+
async function postForm2(endpoint, params) {
|
|
1274
|
+
const response = await fetch(`${issuer()}${endpoint}`, {
|
|
1275
|
+
method: "POST",
|
|
1276
|
+
headers: {
|
|
1277
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1278
|
+
Accept: "application/json",
|
|
1279
|
+
"User-Agent": `grok-cli/${grokCliVersion()}`
|
|
1280
|
+
},
|
|
1281
|
+
body: new URLSearchParams(params).toString()
|
|
1282
|
+
});
|
|
1283
|
+
let data = {};
|
|
1284
|
+
try {
|
|
1285
|
+
const parsed = await response.json();
|
|
1286
|
+
if (parsed && typeof parsed === "object") data = parsed;
|
|
1287
|
+
} catch {
|
|
1288
|
+
}
|
|
1289
|
+
return { status: response.status, data };
|
|
1290
|
+
}
|
|
1291
|
+
function errorDetail2(data) {
|
|
1292
|
+
const desc = data.error_description ?? data.message ?? data.error;
|
|
1293
|
+
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
1294
|
+
}
|
|
1295
|
+
function jwtExpirySeconds(token) {
|
|
1296
|
+
const payload = token.split(".")[1];
|
|
1297
|
+
if (!payload) return void 0;
|
|
1298
|
+
try {
|
|
1299
|
+
const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
|
|
1300
|
+
"utf-8"
|
|
1301
|
+
);
|
|
1302
|
+
const claims = JSON.parse(json);
|
|
1303
|
+
if (!claims || typeof claims !== "object") return void 0;
|
|
1304
|
+
const exp = claims.exp;
|
|
1305
|
+
return typeof exp === "number" && Number.isFinite(exp) && exp > 0 ? exp : void 0;
|
|
1306
|
+
} catch {
|
|
1307
|
+
return void 0;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function credsFromTokenResponse2(data, opts) {
|
|
1311
|
+
const accessToken = data.access_token;
|
|
1312
|
+
const responseRefreshToken = data.refresh_token;
|
|
1313
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
1314
|
+
throw new Error("Grok OAuth response missing access_token.");
|
|
1315
|
+
}
|
|
1316
|
+
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
1317
|
+
if (refreshToken.length === 0) {
|
|
1318
|
+
throw new Error(
|
|
1319
|
+
"Grok OAuth response missing refresh_token \u2014 the offline_access scope was not granted."
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
const responseExpiresIn = Number(data.expires_in);
|
|
1323
|
+
let expiresIn;
|
|
1324
|
+
if (Number.isFinite(responseExpiresIn) && responseExpiresIn > 0) {
|
|
1325
|
+
expiresIn = responseExpiresIn;
|
|
1326
|
+
} else {
|
|
1327
|
+
const exp = jwtExpirySeconds(accessToken);
|
|
1328
|
+
const fromJwt = exp !== void 0 ? exp - Math.floor(Date.now() / 1e3) : 0;
|
|
1329
|
+
expiresIn = fromJwt > 0 ? fromJwt : DEFAULT_TOKEN_LIFETIME_SECONDS;
|
|
1330
|
+
}
|
|
1331
|
+
return {
|
|
1332
|
+
accessToken,
|
|
1333
|
+
refreshToken,
|
|
1334
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
1335
|
+
expiresIn,
|
|
1336
|
+
baseUrl: grokCliBaseUrl()
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
async function requestDeviceAuthorization2() {
|
|
1340
|
+
const { status, data } = await postForm2("/oauth2/device/code", {
|
|
1341
|
+
client_id: clientId(),
|
|
1342
|
+
scope: OAUTH_SCOPE
|
|
1343
|
+
});
|
|
1344
|
+
if (status !== 200) {
|
|
1345
|
+
throw new Error(`Grok device authorization failed (${status}): ${errorDetail2(data)}`);
|
|
1346
|
+
}
|
|
1347
|
+
const userCode = data.user_code;
|
|
1348
|
+
const deviceCode = data.device_code;
|
|
1349
|
+
const verificationUriComplete = data.verification_uri_complete;
|
|
1350
|
+
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
1351
|
+
throw new Error("Grok device authorization response missing user_code/device_code.");
|
|
1352
|
+
}
|
|
1353
|
+
return {
|
|
1354
|
+
userCode,
|
|
1355
|
+
deviceCode,
|
|
1356
|
+
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
1357
|
+
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
1358
|
+
interval: Number(data.interval ?? 5) || 5,
|
|
1359
|
+
// RFC 8628 §3.2: the server states how long the device code lives. Honor it
|
|
1360
|
+
// rather than imposing our own budget — polling a code we know is dead only
|
|
1361
|
+
// burns requests, and a longer local window would keep a user waiting past
|
|
1362
|
+
// the point the code can ever succeed.
|
|
1363
|
+
expiresInMs: (Number(data.expires_in) || 0) > 0 ? Number(data.expires_in) * 1e3 : DEVICE_TIMEOUT_FALLBACK_MS
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
async function pollDeviceToken2(deviceCode) {
|
|
1367
|
+
const { status, data } = await postForm2("/oauth2/token", {
|
|
1368
|
+
client_id: clientId(),
|
|
1369
|
+
device_code: deviceCode,
|
|
1370
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1371
|
+
});
|
|
1372
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
1373
|
+
return { kind: "success", creds: credsFromTokenResponse2(data) };
|
|
1374
|
+
}
|
|
1375
|
+
if (status >= 500) {
|
|
1376
|
+
throw new Error(`Grok token polling server error (${status}): ${errorDetail2(data)}`);
|
|
1377
|
+
}
|
|
1378
|
+
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
1379
|
+
switch (errorCode) {
|
|
1380
|
+
case "authorization_pending":
|
|
1381
|
+
return { kind: "pending" };
|
|
1382
|
+
case "slow_down":
|
|
1383
|
+
return { kind: "slow_down" };
|
|
1384
|
+
case "expired_token":
|
|
1385
|
+
return { kind: "expired" };
|
|
1386
|
+
case "access_denied":
|
|
1387
|
+
return { kind: "denied" };
|
|
1388
|
+
default:
|
|
1389
|
+
throw new Error(`Grok token polling failed (${status}): ${errorDetail2(data)}`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
function sleep2(ms) {
|
|
1393
|
+
return new Promise((resolve) => {
|
|
1394
|
+
setTimeout(resolve, ms);
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
async function loginXai(callbacks) {
|
|
1398
|
+
const auth = await requestDeviceAuthorization2();
|
|
1399
|
+
callbacks.onStatus(
|
|
1400
|
+
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
1401
|
+
);
|
|
1402
|
+
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
1403
|
+
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
1404
|
+
const deadline = Date.now() + auth.expiresInMs;
|
|
1405
|
+
let interval = Math.max(auth.interval, 1);
|
|
1406
|
+
while (Date.now() < deadline) {
|
|
1407
|
+
const remaining = deadline - Date.now();
|
|
1408
|
+
await sleep2(Math.min(interval * 1e3, remaining));
|
|
1409
|
+
const result = await pollDeviceToken2(auth.deviceCode);
|
|
1410
|
+
if (result.kind === "success") return result.creds;
|
|
1411
|
+
if (result.kind === "denied") {
|
|
1412
|
+
throw new Error("Grok authorization was denied.");
|
|
1413
|
+
}
|
|
1414
|
+
if (result.kind === "expired") {
|
|
1415
|
+
throw new Error("Grok device code expired. Please run login again.");
|
|
1416
|
+
}
|
|
1417
|
+
if (result.kind === "slow_down") {
|
|
1418
|
+
interval += 5;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
throw new Error("Grok login timed out. Please run login again.");
|
|
1422
|
+
}
|
|
1423
|
+
async function refreshXaiToken(refreshToken) {
|
|
1424
|
+
const { status, data } = await postForm2("/oauth2/token", {
|
|
1425
|
+
client_id: clientId(),
|
|
1426
|
+
grant_type: "refresh_token",
|
|
1427
|
+
refresh_token: refreshToken
|
|
1428
|
+
});
|
|
1429
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
1430
|
+
return credsFromTokenResponse2(data, { fallbackRefreshToken: refreshToken });
|
|
1431
|
+
}
|
|
1432
|
+
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
1433
|
+
throw new Error(`Grok token refresh failed (${status}): ${errorCode || errorDetail2(data)}`);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1228
1436
|
// src/file-lock.ts
|
|
1229
1437
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
1230
1438
|
var import_promises3 = require("timers/promises");
|
|
@@ -1289,18 +1497,49 @@ function isAlive(pid) {
|
|
|
1289
1497
|
|
|
1290
1498
|
// src/auth-storage.ts
|
|
1291
1499
|
var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
|
|
1500
|
+
var XAI_OAUTH_KEY = "xai-oauth";
|
|
1501
|
+
var DUAL_AUTH_PROVIDERS = [
|
|
1502
|
+
{
|
|
1503
|
+
provider: "moonshot",
|
|
1504
|
+
oauthKey: MOONSHOT_OAUTH_KEY,
|
|
1505
|
+
oauthLabel: "Kimi OAuth",
|
|
1506
|
+
apiKeyLabel: "Moonshot API key",
|
|
1507
|
+
restoreHint: 'Run "ggcoder login" and choose Kimi OAuth to restore OAuth auth.'
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
provider: "xai",
|
|
1511
|
+
oauthKey: XAI_OAUTH_KEY,
|
|
1512
|
+
oauthLabel: "Grok OAuth",
|
|
1513
|
+
apiKeyLabel: "xAI API key",
|
|
1514
|
+
restoreHint: 'Run "ggcoder login" and choose Grok OAuth to restore OAuth auth.'
|
|
1515
|
+
}
|
|
1516
|
+
];
|
|
1517
|
+
function dualAuthProvider(provider) {
|
|
1518
|
+
return DUAL_AUTH_PROVIDERS.find((entry) => entry.provider === provider);
|
|
1519
|
+
}
|
|
1520
|
+
function dualAuthProviderByOAuthKey(storageKey) {
|
|
1521
|
+
return DUAL_AUTH_PROVIDERS.find((entry) => entry.oauthKey === storageKey);
|
|
1522
|
+
}
|
|
1523
|
+
function oauthStorageKey(provider) {
|
|
1524
|
+
return dualAuthProvider(provider)?.oauthKey;
|
|
1525
|
+
}
|
|
1526
|
+
function providerStorageKeys(provider) {
|
|
1527
|
+
const dual = dualAuthProvider(provider);
|
|
1528
|
+
return dual ? [dual.oauthKey, dual.provider] : [provider];
|
|
1529
|
+
}
|
|
1292
1530
|
var XIAOMI_CREDITS_KEY = "xiaomi-credits";
|
|
1293
1531
|
var LOCAL_AUTH_KEY_PREFIX = "local:";
|
|
1294
1532
|
var LOCAL_CREDENTIAL_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
1295
1533
|
function activeBaseUrlEntry(data, provider) {
|
|
1296
|
-
|
|
1297
|
-
|
|
1534
|
+
const dual = dualAuthProvider(provider);
|
|
1535
|
+
if (dual) {
|
|
1536
|
+
const oauth = data[dual.oauthKey];
|
|
1298
1537
|
if (oauth) {
|
|
1299
1538
|
const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
|
|
1300
|
-
if (Date.now() < exhaustedUntil && data[
|
|
1539
|
+
if (Date.now() < exhaustedUntil && data[dual.provider]) return data[dual.provider];
|
|
1301
1540
|
return oauth;
|
|
1302
1541
|
}
|
|
1303
|
-
return data[
|
|
1542
|
+
return data[dual.provider];
|
|
1304
1543
|
}
|
|
1305
1544
|
return data[provider];
|
|
1306
1545
|
}
|
|
@@ -1335,6 +1574,16 @@ var AuthStorage = class {
|
|
|
1335
1574
|
data = {};
|
|
1336
1575
|
filePath;
|
|
1337
1576
|
loaded = false;
|
|
1577
|
+
/**
|
|
1578
|
+
* mtime+size of the file as of the cached snapshot (`size: -1` = no file).
|
|
1579
|
+
* auth.json is shared: the desktop app writes API keys and disconnects
|
|
1580
|
+
* NATIVELY (so they work with no daemon running), and every window/process has
|
|
1581
|
+
* its own AuthStorage. A load-once cache therefore goes stale — the sidecar
|
|
1582
|
+
* would keep listing models for a provider just disconnected, and hide the
|
|
1583
|
+
* ones just connected, until the daemon restarted.
|
|
1584
|
+
*/
|
|
1585
|
+
snapshotMtimeMs = 0;
|
|
1586
|
+
snapshotSize = -1;
|
|
1338
1587
|
/** Per-provider lock to serialize concurrent refresh calls. */
|
|
1339
1588
|
refreshLocks = /* @__PURE__ */ new Map();
|
|
1340
1589
|
constructor(filePath) {
|
|
@@ -1346,12 +1595,12 @@ var AuthStorage = class {
|
|
|
1346
1595
|
}
|
|
1347
1596
|
/** List provider keys with stored credentials. */
|
|
1348
1597
|
async listProviders() {
|
|
1349
|
-
await this.
|
|
1598
|
+
await this.ensureFresh();
|
|
1350
1599
|
return Object.keys(this.data);
|
|
1351
1600
|
}
|
|
1352
1601
|
/** True if credentials exist for `provider`. */
|
|
1353
1602
|
async hasCredentials(provider) {
|
|
1354
|
-
await this.
|
|
1603
|
+
await this.ensureFresh();
|
|
1355
1604
|
return Boolean(this.data[provider]);
|
|
1356
1605
|
}
|
|
1357
1606
|
/**
|
|
@@ -1362,18 +1611,19 @@ var AuthStorage = class {
|
|
|
1362
1611
|
* instead of re-deriving the same order.
|
|
1363
1612
|
*/
|
|
1364
1613
|
async pickStorageKey(keys) {
|
|
1365
|
-
await this.
|
|
1614
|
+
await this.ensureFresh();
|
|
1366
1615
|
return keys.find((key) => Boolean(this.data[key]));
|
|
1367
1616
|
}
|
|
1368
1617
|
/**
|
|
1369
|
-
* True if the user has any usable auth for the logical provider. For
|
|
1370
|
-
*
|
|
1371
|
-
*
|
|
1618
|
+
* True if the user has any usable auth for the logical provider. For a
|
|
1619
|
+
* dual-auth provider (Kimi/Grok) either the OAuth credential or the API key
|
|
1620
|
+
* satisfies it.
|
|
1372
1621
|
*/
|
|
1373
1622
|
async hasProviderAuth(provider) {
|
|
1374
|
-
await this.
|
|
1375
|
-
|
|
1376
|
-
|
|
1623
|
+
await this.ensureFresh();
|
|
1624
|
+
const dual = dualAuthProvider(provider);
|
|
1625
|
+
if (dual) {
|
|
1626
|
+
return Boolean(this.data[dual.oauthKey] || this.data[dual.provider]);
|
|
1377
1627
|
}
|
|
1378
1628
|
if (provider === "xiaomi") {
|
|
1379
1629
|
return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
|
|
@@ -1407,14 +1657,16 @@ var AuthStorage = class {
|
|
|
1407
1657
|
}
|
|
1408
1658
|
/**
|
|
1409
1659
|
* True if the active credential for `provider` is a static API key with no
|
|
1410
|
-
* refresh mechanism. For
|
|
1411
|
-
* credential is absent (a
|
|
1660
|
+
* refresh mechanism. For a dual-auth provider this is only true when its OAuth
|
|
1661
|
+
* credential is absent or sidelined (a live OAuth credential is refreshable).
|
|
1412
1662
|
*/
|
|
1413
1663
|
async isStaticApiKey(provider) {
|
|
1414
|
-
await this.
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1664
|
+
await this.ensureFresh();
|
|
1665
|
+
const dual = dualAuthProvider(provider);
|
|
1666
|
+
const oauthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
1667
|
+
if (dual && oauthCreds) {
|
|
1668
|
+
const exhaustedUntil = oauthCreds.usageExhaustedUntil ?? 0;
|
|
1669
|
+
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data[dual.provider]);
|
|
1418
1670
|
if (!apiKeyActive) return false;
|
|
1419
1671
|
}
|
|
1420
1672
|
return STATIC_API_KEY_PROVIDERS.has(provider);
|
|
@@ -1422,26 +1674,30 @@ var AuthStorage = class {
|
|
|
1422
1674
|
/**
|
|
1423
1675
|
* The base URL on the credential that is active right now, if any.
|
|
1424
1676
|
* Synchronous — call only after load()/resolveCredentials() populated the
|
|
1425
|
-
* snapshot. For
|
|
1426
|
-
*
|
|
1427
|
-
* usage-exhausted with an
|
|
1677
|
+
* snapshot. For a dual-auth provider this is the subscription endpoint (Kimi
|
|
1678
|
+
* For Coding, the Grok CLI proxy) whenever the OAuth entry is the one
|
|
1679
|
+
* resolveCredentials would serve (i.e. not currently usage-exhausted with an
|
|
1680
|
+
* API key configured).
|
|
1428
1681
|
*/
|
|
1429
1682
|
getStoredBaseUrl(provider) {
|
|
1430
1683
|
return activeBaseUrlEntry(this.data, provider)?.baseUrl;
|
|
1431
1684
|
}
|
|
1432
1685
|
async load() {
|
|
1686
|
+
const first = !this.loaded;
|
|
1433
1687
|
await withFileLock(this.filePath, async () => {
|
|
1434
1688
|
try {
|
|
1435
1689
|
const content = await import_promises4.default.readFile(this.filePath, "utf-8");
|
|
1436
1690
|
this.data = JSON.parse(content);
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1691
|
+
if (first) {
|
|
1692
|
+
log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
|
|
1693
|
+
providers: Object.keys(this.data).join(",") || "(none)"
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1440
1696
|
} catch (err) {
|
|
1441
1697
|
this.data = {};
|
|
1442
1698
|
const code = err.code;
|
|
1443
1699
|
if (code === "ENOENT") {
|
|
1444
|
-
log("INFO", "auth", `No auth file found at ${this.filePath} (first run)`);
|
|
1700
|
+
if (first) log("INFO", "auth", `No auth file found at ${this.filePath} (first run)`);
|
|
1445
1701
|
} else {
|
|
1446
1702
|
log(
|
|
1447
1703
|
"ERROR",
|
|
@@ -1453,10 +1709,50 @@ var AuthStorage = class {
|
|
|
1453
1709
|
}
|
|
1454
1710
|
});
|
|
1455
1711
|
this.loaded = true;
|
|
1712
|
+
await this.rememberSnapshot();
|
|
1456
1713
|
}
|
|
1457
1714
|
async ensureLoaded() {
|
|
1458
1715
|
if (!this.loaded) await this.load();
|
|
1459
1716
|
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Like {@link ensureLoaded}, but re-reads when the file changed since this
|
|
1719
|
+
* snapshot — a cheap stat, not a re-parse. Used by the "what is connected?"
|
|
1720
|
+
* readers, which must reflect writes made by another window, the CLI, or the
|
|
1721
|
+
* desktop app's native (daemon-free) API-key and disconnect paths.
|
|
1722
|
+
*
|
|
1723
|
+
* Deliberately NOT used by {@link resolveCredentials}: that path compares the
|
|
1724
|
+
* caller's snapshot against the latest file to detect a concurrent re-login,
|
|
1725
|
+
* and silently refreshing this instance's view first would destroy the
|
|
1726
|
+
* evidence that the token it just had rejected has already been replaced.
|
|
1727
|
+
*/
|
|
1728
|
+
async ensureFresh() {
|
|
1729
|
+
if (!this.loaded) {
|
|
1730
|
+
await this.load();
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
let changed;
|
|
1734
|
+
try {
|
|
1735
|
+
const stat = await import_promises4.default.stat(this.filePath);
|
|
1736
|
+
changed = stat.mtimeMs !== this.snapshotMtimeMs || stat.size !== this.snapshotSize;
|
|
1737
|
+
} catch {
|
|
1738
|
+
changed = this.snapshotSize !== -1;
|
|
1739
|
+
}
|
|
1740
|
+
if (changed) await this.load();
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Record the file identity behind the current snapshot, so {@link ensureLoaded}
|
|
1744
|
+
* can tell "someone else wrote" from "this is our own write".
|
|
1745
|
+
*/
|
|
1746
|
+
async rememberSnapshot() {
|
|
1747
|
+
try {
|
|
1748
|
+
const stat = await import_promises4.default.stat(this.filePath);
|
|
1749
|
+
this.snapshotMtimeMs = stat.mtimeMs;
|
|
1750
|
+
this.snapshotSize = stat.size;
|
|
1751
|
+
} catch {
|
|
1752
|
+
this.snapshotMtimeMs = 0;
|
|
1753
|
+
this.snapshotSize = -1;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1460
1756
|
/**
|
|
1461
1757
|
* Apply one provider-scoped mutation to the latest on-disk snapshot.
|
|
1462
1758
|
* AuthStorage instances live in every app session/process, so writing this
|
|
@@ -1472,14 +1768,16 @@ var AuthStorage = class {
|
|
|
1472
1768
|
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1473
1769
|
this.data = latest;
|
|
1474
1770
|
});
|
|
1771
|
+
await this.rememberSnapshot();
|
|
1475
1772
|
}
|
|
1476
1773
|
async reloadLatest() {
|
|
1477
1774
|
await withFileLock(this.filePath, async () => {
|
|
1478
1775
|
this.data = await readAuthData(this.filePath);
|
|
1479
1776
|
});
|
|
1777
|
+
await this.rememberSnapshot();
|
|
1480
1778
|
}
|
|
1481
1779
|
async getCredentials(provider) {
|
|
1482
|
-
await this.
|
|
1780
|
+
await this.ensureFresh();
|
|
1483
1781
|
return this.data[provider];
|
|
1484
1782
|
}
|
|
1485
1783
|
async setCredentials(provider, creds) {
|
|
@@ -1497,7 +1795,7 @@ var AuthStorage = class {
|
|
|
1497
1795
|
* `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
|
|
1498
1796
|
* 15-minute default when no reset time is known. While the mark is in the
|
|
1499
1797
|
* future, `resolveCredentials("moonshot")` serves the Moonshot API key
|
|
1500
|
-
* instead of the
|
|
1798
|
+
* instead of the subscription OAuth credential (when both are configured) — OAuth
|
|
1501
1799
|
* stays the preferred credential and is retried automatically once the mark
|
|
1502
1800
|
* lapses. Persisted to auth.json so a restart (or another gg-app window)
|
|
1503
1801
|
* doesn't burn a request rediscovering the same exhausted window. No-op if
|
|
@@ -1534,7 +1832,7 @@ var AuthStorage = class {
|
|
|
1534
1832
|
*/
|
|
1535
1833
|
async resolveCredentials(provider, opts) {
|
|
1536
1834
|
await this.ensureLoaded();
|
|
1537
|
-
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : provider
|
|
1835
|
+
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
|
|
1538
1836
|
if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
|
|
1539
1837
|
await this.reloadLatest();
|
|
1540
1838
|
}
|
|
@@ -1545,28 +1843,30 @@ var AuthStorage = class {
|
|
|
1545
1843
|
}
|
|
1546
1844
|
throw new NotLoggedInError(provider);
|
|
1547
1845
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1846
|
+
const dual = dualAuthProvider(provider);
|
|
1847
|
+
const dualOAuthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
1848
|
+
if (dual && dualOAuthCreds) {
|
|
1849
|
+
const exhaustedUntil = dualOAuthCreds.usageExhaustedUntil ?? 0;
|
|
1850
|
+
if (Date.now() < exhaustedUntil && this.data[dual.provider]) {
|
|
1551
1851
|
log(
|
|
1552
1852
|
"WARN",
|
|
1553
1853
|
"auth",
|
|
1554
|
-
|
|
1854
|
+
`${dual.oauthLabel} usage window is exhausted \u2014 using the ${dual.apiKeyLabel} until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
|
|
1555
1855
|
);
|
|
1556
|
-
return this.data[
|
|
1856
|
+
return this.data[dual.provider];
|
|
1557
1857
|
}
|
|
1558
1858
|
try {
|
|
1559
|
-
return await this.resolveCredentials(
|
|
1859
|
+
return await this.resolveCredentials(dual.oauthKey, {
|
|
1560
1860
|
...opts?.forceRefresh ? { forceRefresh: true } : {}
|
|
1561
1861
|
});
|
|
1562
1862
|
} catch (err) {
|
|
1563
|
-
if (err instanceof NotLoggedInError && this.data[
|
|
1863
|
+
if (err instanceof NotLoggedInError && this.data[dual.provider]) {
|
|
1564
1864
|
log(
|
|
1565
1865
|
"WARN",
|
|
1566
1866
|
"auth",
|
|
1567
|
-
|
|
1867
|
+
`${dual.oauthLabel} credential is no longer valid \u2014 falling back to the ${dual.apiKeyLabel}. ${dual.restoreHint}`
|
|
1568
1868
|
);
|
|
1569
|
-
return this.data[
|
|
1869
|
+
return this.data[dual.provider];
|
|
1570
1870
|
}
|
|
1571
1871
|
throw err;
|
|
1572
1872
|
}
|
|
@@ -1595,7 +1895,7 @@ var AuthStorage = class {
|
|
|
1595
1895
|
this.data = latest;
|
|
1596
1896
|
return latestCreds;
|
|
1597
1897
|
}
|
|
1598
|
-
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
|
|
1898
|
+
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : provider === XAI_OAUTH_KEY ? refreshXaiToken : refreshOpenAIToken;
|
|
1599
1899
|
let refreshed;
|
|
1600
1900
|
try {
|
|
1601
1901
|
refreshed = await refreshFn(latestCreds.refreshToken);
|
|
@@ -2828,7 +3128,7 @@ var TelegramBot = class {
|
|
|
2828
3128
|
} catch (err) {
|
|
2829
3129
|
if (!this.running) break;
|
|
2830
3130
|
console.error(`[telegram] Poll error: ${err instanceof Error ? err.message : err}`);
|
|
2831
|
-
await
|
|
3131
|
+
await sleep3(3e3);
|
|
2832
3132
|
}
|
|
2833
3133
|
}
|
|
2834
3134
|
}
|
|
@@ -2998,7 +3298,7 @@ function splitMessage(text) {
|
|
|
2998
3298
|
}
|
|
2999
3299
|
return chunks;
|
|
3000
3300
|
}
|
|
3001
|
-
function
|
|
3301
|
+
function sleep3(ms) {
|
|
3002
3302
|
return new Promise((r) => setTimeout(r, ms));
|
|
3003
3303
|
}
|
|
3004
3304
|
|
|
@@ -3263,6 +3563,7 @@ function createAutoUpdater(config) {
|
|
|
3263
3563
|
NotLoggedInError,
|
|
3264
3564
|
SubscriptionUsageError,
|
|
3265
3565
|
TelegramBot,
|
|
3566
|
+
XAI_OAUTH_KEY,
|
|
3266
3567
|
XIAOMI_CREDITS_KEY,
|
|
3267
3568
|
clearLocalDiscoveryCache,
|
|
3268
3569
|
clearRuntimeModels,
|
|
@@ -3271,6 +3572,8 @@ function createAutoUpdater(config) {
|
|
|
3271
3572
|
decodeOggOpus,
|
|
3272
3573
|
discoverLocalModels,
|
|
3273
3574
|
downmixToMono,
|
|
3575
|
+
dualAuthProvider,
|
|
3576
|
+
dualAuthProviderByOAuthKey,
|
|
3274
3577
|
endpointRoot,
|
|
3275
3578
|
fetchSubscriptionUsage,
|
|
3276
3579
|
findProbedModel,
|
|
@@ -3295,6 +3598,9 @@ function createAutoUpdater(config) {
|
|
|
3295
3598
|
getSupportedThinkingLevels,
|
|
3296
3599
|
getToolResultCharLimit,
|
|
3297
3600
|
getVideoByteLimit,
|
|
3601
|
+
grokCliBaseUrl,
|
|
3602
|
+
grokCliHeaders,
|
|
3603
|
+
isGrokCliEndpoint,
|
|
3298
3604
|
isKimiCodingEndpoint,
|
|
3299
3605
|
isLocalModelId,
|
|
3300
3606
|
isLoggerOpen,
|
|
@@ -3308,14 +3614,18 @@ function createAutoUpdater(config) {
|
|
|
3308
3614
|
loginGemini,
|
|
3309
3615
|
loginKimi,
|
|
3310
3616
|
loginOpenAI,
|
|
3617
|
+
loginXai,
|
|
3618
|
+
oauthStorageKey,
|
|
3311
3619
|
openLog,
|
|
3312
3620
|
parseLocalModelId,
|
|
3313
3621
|
probeEndpoint,
|
|
3622
|
+
providerStorageKeys,
|
|
3314
3623
|
readStoredBaseUrlSync,
|
|
3315
3624
|
refreshAnthropicToken,
|
|
3316
3625
|
refreshGeminiToken,
|
|
3317
3626
|
refreshKimiToken,
|
|
3318
3627
|
refreshOpenAIToken,
|
|
3628
|
+
refreshXaiToken,
|
|
3319
3629
|
registerLogCleanup,
|
|
3320
3630
|
registerRuntimeModels,
|
|
3321
3631
|
resample,
|