@kenkaiiii/gg-core 5.36.0 → 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
|
@@ -765,13 +765,13 @@ var CodeAssistHttpError = class extends Error {
|
|
|
765
765
|
}
|
|
766
766
|
};
|
|
767
767
|
async function loginGemini(callbacks) {
|
|
768
|
-
const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
|
|
768
|
+
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
769
769
|
const { verifier, challenge } = await generatePKCE();
|
|
770
770
|
const state = crypto4.randomBytes(32).toString("hex");
|
|
771
771
|
const redirectUri = await getLoopbackRedirectUri();
|
|
772
772
|
const url = new URL(AUTHORIZE_URL3);
|
|
773
773
|
url.searchParams.set("response_type", "code");
|
|
774
|
-
url.searchParams.set("client_id",
|
|
774
|
+
url.searchParams.set("client_id", clientId2);
|
|
775
775
|
url.searchParams.set("redirect_uri", redirectUri);
|
|
776
776
|
url.searchParams.set("scope", SCOPE2);
|
|
777
777
|
url.searchParams.set("access_type", "offline");
|
|
@@ -796,7 +796,7 @@ async function loginGemini(callbacks) {
|
|
|
796
796
|
}
|
|
797
797
|
code = parsed.code;
|
|
798
798
|
}
|
|
799
|
-
const creds = await exchangeGeminiCode(code, verifier, redirectUri,
|
|
799
|
+
const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret);
|
|
800
800
|
callbacks.onStatus("Setting up Gemini Code Assist access...");
|
|
801
801
|
const projectId = await setupCodeAssistProject(creds.accessToken, callbacks);
|
|
802
802
|
return {
|
|
@@ -805,11 +805,11 @@ async function loginGemini(callbacks) {
|
|
|
805
805
|
};
|
|
806
806
|
}
|
|
807
807
|
async function refreshGeminiToken(refreshToken) {
|
|
808
|
-
const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
|
|
808
|
+
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
809
809
|
const data = await postTokenRequest2({
|
|
810
810
|
grant_type: "refresh_token",
|
|
811
811
|
refresh_token: refreshToken,
|
|
812
|
-
client_id:
|
|
812
|
+
client_id: clientId2,
|
|
813
813
|
client_secret: clientSecret
|
|
814
814
|
});
|
|
815
815
|
return {
|
|
@@ -819,9 +819,9 @@ async function refreshGeminiToken(refreshToken) {
|
|
|
819
819
|
};
|
|
820
820
|
}
|
|
821
821
|
function getGeminiOAuthClientCredentials() {
|
|
822
|
-
const
|
|
822
|
+
const clientId2 = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
|
|
823
823
|
const clientSecret = process.env[CLIENT_SECRET_ENV]?.trim() || DEFAULT_CLIENT_SECRET;
|
|
824
|
-
return { clientId, clientSecret };
|
|
824
|
+
return { clientId: clientId2, clientSecret };
|
|
825
825
|
}
|
|
826
826
|
async function getLoopbackRedirectUri() {
|
|
827
827
|
return new Promise((resolve, reject) => {
|
|
@@ -900,10 +900,10 @@ async function loginWithServer2(authUrl, redirectUri, expectedState, callbacks)
|
|
|
900
900
|
});
|
|
901
901
|
});
|
|
902
902
|
}
|
|
903
|
-
async function exchangeGeminiCode(code, verifier, redirectUri,
|
|
903
|
+
async function exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret) {
|
|
904
904
|
const data = await postTokenRequest2({
|
|
905
905
|
grant_type: "authorization_code",
|
|
906
|
-
client_id:
|
|
906
|
+
client_id: clientId2,
|
|
907
907
|
client_secret: clientSecret,
|
|
908
908
|
code,
|
|
909
909
|
redirect_uri: redirectUri,
|
|
@@ -1094,6 +1094,204 @@ function codeAssistHeaders(accessToken) {
|
|
|
1094
1094
|
};
|
|
1095
1095
|
}
|
|
1096
1096
|
|
|
1097
|
+
// src/oauth/xai.ts
|
|
1098
|
+
var DEFAULT_CLIENT_ID2 = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
1099
|
+
var DEFAULT_ISSUER = "https://auth.x.ai";
|
|
1100
|
+
var DEFAULT_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
1101
|
+
var OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
|
1102
|
+
var DEFAULT_GROK_CLI_VERSION = "0.2.101";
|
|
1103
|
+
var DEVICE_TIMEOUT_FALLBACK_MS = 10 * 60 * 1e3;
|
|
1104
|
+
var DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
|
|
1105
|
+
function issuer() {
|
|
1106
|
+
return (process.env.XAI_OAUTH_ISSUER ?? DEFAULT_ISSUER).replace(/\/+$/, "");
|
|
1107
|
+
}
|
|
1108
|
+
function clientId() {
|
|
1109
|
+
return process.env.XAI_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID2;
|
|
1110
|
+
}
|
|
1111
|
+
function grokCliBaseUrl() {
|
|
1112
|
+
return (process.env.XAI_CLI_BASE_URL ?? DEFAULT_CLI_BASE_URL).replace(/\/+$/, "");
|
|
1113
|
+
}
|
|
1114
|
+
function grokCliVersion() {
|
|
1115
|
+
const raw = process.env.GROK_CLI_VERSION ?? DEFAULT_GROK_CLI_VERSION;
|
|
1116
|
+
const cleaned = raw.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
1117
|
+
return cleaned.length > 0 ? cleaned : DEFAULT_GROK_CLI_VERSION;
|
|
1118
|
+
}
|
|
1119
|
+
function grokCliHeaders(modelId) {
|
|
1120
|
+
return {
|
|
1121
|
+
"X-XAI-Token-Auth": "xai-grok-cli",
|
|
1122
|
+
"x-grok-client-version": grokCliVersion(),
|
|
1123
|
+
"x-grok-client-identifier": "ggcoder",
|
|
1124
|
+
...modelId ? { "x-grok-model-override": modelId } : {}
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function isGrokCliEndpoint(baseUrl) {
|
|
1128
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
1129
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
1130
|
+
return normalized === grokCliBaseUrl() || /(^|\.)grok\.com/i.test(normalized);
|
|
1131
|
+
}
|
|
1132
|
+
async function postForm2(endpoint, params) {
|
|
1133
|
+
const response = await fetch(`${issuer()}${endpoint}`, {
|
|
1134
|
+
method: "POST",
|
|
1135
|
+
headers: {
|
|
1136
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1137
|
+
Accept: "application/json",
|
|
1138
|
+
"User-Agent": `grok-cli/${grokCliVersion()}`
|
|
1139
|
+
},
|
|
1140
|
+
body: new URLSearchParams(params).toString()
|
|
1141
|
+
});
|
|
1142
|
+
let data = {};
|
|
1143
|
+
try {
|
|
1144
|
+
const parsed = await response.json();
|
|
1145
|
+
if (parsed && typeof parsed === "object") data = parsed;
|
|
1146
|
+
} catch {
|
|
1147
|
+
}
|
|
1148
|
+
return { status: response.status, data };
|
|
1149
|
+
}
|
|
1150
|
+
function errorDetail2(data) {
|
|
1151
|
+
const desc = data.error_description ?? data.message ?? data.error;
|
|
1152
|
+
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
1153
|
+
}
|
|
1154
|
+
function jwtExpirySeconds(token) {
|
|
1155
|
+
const payload = token.split(".")[1];
|
|
1156
|
+
if (!payload) return void 0;
|
|
1157
|
+
try {
|
|
1158
|
+
const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
|
|
1159
|
+
"utf-8"
|
|
1160
|
+
);
|
|
1161
|
+
const claims = JSON.parse(json);
|
|
1162
|
+
if (!claims || typeof claims !== "object") return void 0;
|
|
1163
|
+
const exp = claims.exp;
|
|
1164
|
+
return typeof exp === "number" && Number.isFinite(exp) && exp > 0 ? exp : void 0;
|
|
1165
|
+
} catch {
|
|
1166
|
+
return void 0;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function credsFromTokenResponse2(data, opts) {
|
|
1170
|
+
const accessToken = data.access_token;
|
|
1171
|
+
const responseRefreshToken = data.refresh_token;
|
|
1172
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
1173
|
+
throw new Error("Grok OAuth response missing access_token.");
|
|
1174
|
+
}
|
|
1175
|
+
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
1176
|
+
if (refreshToken.length === 0) {
|
|
1177
|
+
throw new Error(
|
|
1178
|
+
"Grok OAuth response missing refresh_token \u2014 the offline_access scope was not granted."
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
const responseExpiresIn = Number(data.expires_in);
|
|
1182
|
+
let expiresIn;
|
|
1183
|
+
if (Number.isFinite(responseExpiresIn) && responseExpiresIn > 0) {
|
|
1184
|
+
expiresIn = responseExpiresIn;
|
|
1185
|
+
} else {
|
|
1186
|
+
const exp = jwtExpirySeconds(accessToken);
|
|
1187
|
+
const fromJwt = exp !== void 0 ? exp - Math.floor(Date.now() / 1e3) : 0;
|
|
1188
|
+
expiresIn = fromJwt > 0 ? fromJwt : DEFAULT_TOKEN_LIFETIME_SECONDS;
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
accessToken,
|
|
1192
|
+
refreshToken,
|
|
1193
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
1194
|
+
expiresIn,
|
|
1195
|
+
baseUrl: grokCliBaseUrl()
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
async function requestDeviceAuthorization2() {
|
|
1199
|
+
const { status, data } = await postForm2("/oauth2/device/code", {
|
|
1200
|
+
client_id: clientId(),
|
|
1201
|
+
scope: OAUTH_SCOPE
|
|
1202
|
+
});
|
|
1203
|
+
if (status !== 200) {
|
|
1204
|
+
throw new Error(`Grok device authorization failed (${status}): ${errorDetail2(data)}`);
|
|
1205
|
+
}
|
|
1206
|
+
const userCode = data.user_code;
|
|
1207
|
+
const deviceCode = data.device_code;
|
|
1208
|
+
const verificationUriComplete = data.verification_uri_complete;
|
|
1209
|
+
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
1210
|
+
throw new Error("Grok device authorization response missing user_code/device_code.");
|
|
1211
|
+
}
|
|
1212
|
+
return {
|
|
1213
|
+
userCode,
|
|
1214
|
+
deviceCode,
|
|
1215
|
+
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
1216
|
+
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
1217
|
+
interval: Number(data.interval ?? 5) || 5,
|
|
1218
|
+
// RFC 8628 §3.2: the server states how long the device code lives. Honor it
|
|
1219
|
+
// rather than imposing our own budget — polling a code we know is dead only
|
|
1220
|
+
// burns requests, and a longer local window would keep a user waiting past
|
|
1221
|
+
// the point the code can ever succeed.
|
|
1222
|
+
expiresInMs: (Number(data.expires_in) || 0) > 0 ? Number(data.expires_in) * 1e3 : DEVICE_TIMEOUT_FALLBACK_MS
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
async function pollDeviceToken2(deviceCode) {
|
|
1226
|
+
const { status, data } = await postForm2("/oauth2/token", {
|
|
1227
|
+
client_id: clientId(),
|
|
1228
|
+
device_code: deviceCode,
|
|
1229
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1230
|
+
});
|
|
1231
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
1232
|
+
return { kind: "success", creds: credsFromTokenResponse2(data) };
|
|
1233
|
+
}
|
|
1234
|
+
if (status >= 500) {
|
|
1235
|
+
throw new Error(`Grok token polling server error (${status}): ${errorDetail2(data)}`);
|
|
1236
|
+
}
|
|
1237
|
+
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
1238
|
+
switch (errorCode) {
|
|
1239
|
+
case "authorization_pending":
|
|
1240
|
+
return { kind: "pending" };
|
|
1241
|
+
case "slow_down":
|
|
1242
|
+
return { kind: "slow_down" };
|
|
1243
|
+
case "expired_token":
|
|
1244
|
+
return { kind: "expired" };
|
|
1245
|
+
case "access_denied":
|
|
1246
|
+
return { kind: "denied" };
|
|
1247
|
+
default:
|
|
1248
|
+
throw new Error(`Grok token polling failed (${status}): ${errorDetail2(data)}`);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function sleep2(ms) {
|
|
1252
|
+
return new Promise((resolve) => {
|
|
1253
|
+
setTimeout(resolve, ms);
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
async function loginXai(callbacks) {
|
|
1257
|
+
const auth = await requestDeviceAuthorization2();
|
|
1258
|
+
callbacks.onStatus(
|
|
1259
|
+
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
1260
|
+
);
|
|
1261
|
+
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
1262
|
+
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
1263
|
+
const deadline = Date.now() + auth.expiresInMs;
|
|
1264
|
+
let interval = Math.max(auth.interval, 1);
|
|
1265
|
+
while (Date.now() < deadline) {
|
|
1266
|
+
const remaining = deadline - Date.now();
|
|
1267
|
+
await sleep2(Math.min(interval * 1e3, remaining));
|
|
1268
|
+
const result = await pollDeviceToken2(auth.deviceCode);
|
|
1269
|
+
if (result.kind === "success") return result.creds;
|
|
1270
|
+
if (result.kind === "denied") {
|
|
1271
|
+
throw new Error("Grok authorization was denied.");
|
|
1272
|
+
}
|
|
1273
|
+
if (result.kind === "expired") {
|
|
1274
|
+
throw new Error("Grok device code expired. Please run login again.");
|
|
1275
|
+
}
|
|
1276
|
+
if (result.kind === "slow_down") {
|
|
1277
|
+
interval += 5;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
throw new Error("Grok login timed out. Please run login again.");
|
|
1281
|
+
}
|
|
1282
|
+
async function refreshXaiToken(refreshToken) {
|
|
1283
|
+
const { status, data } = await postForm2("/oauth2/token", {
|
|
1284
|
+
client_id: clientId(),
|
|
1285
|
+
grant_type: "refresh_token",
|
|
1286
|
+
refresh_token: refreshToken
|
|
1287
|
+
});
|
|
1288
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
1289
|
+
return credsFromTokenResponse2(data, { fallbackRefreshToken: refreshToken });
|
|
1290
|
+
}
|
|
1291
|
+
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
1292
|
+
throw new Error(`Grok token refresh failed (${status}): ${errorCode || errorDetail2(data)}`);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1097
1295
|
// src/file-lock.ts
|
|
1098
1296
|
import fs3 from "fs/promises";
|
|
1099
1297
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
@@ -1158,18 +1356,49 @@ function isAlive(pid) {
|
|
|
1158
1356
|
|
|
1159
1357
|
// src/auth-storage.ts
|
|
1160
1358
|
var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
|
|
1359
|
+
var XAI_OAUTH_KEY = "xai-oauth";
|
|
1360
|
+
var DUAL_AUTH_PROVIDERS = [
|
|
1361
|
+
{
|
|
1362
|
+
provider: "moonshot",
|
|
1363
|
+
oauthKey: MOONSHOT_OAUTH_KEY,
|
|
1364
|
+
oauthLabel: "Kimi OAuth",
|
|
1365
|
+
apiKeyLabel: "Moonshot API key",
|
|
1366
|
+
restoreHint: 'Run "ggcoder login" and choose Kimi OAuth to restore OAuth auth.'
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
provider: "xai",
|
|
1370
|
+
oauthKey: XAI_OAUTH_KEY,
|
|
1371
|
+
oauthLabel: "Grok OAuth",
|
|
1372
|
+
apiKeyLabel: "xAI API key",
|
|
1373
|
+
restoreHint: 'Run "ggcoder login" and choose Grok OAuth to restore OAuth auth.'
|
|
1374
|
+
}
|
|
1375
|
+
];
|
|
1376
|
+
function dualAuthProvider(provider) {
|
|
1377
|
+
return DUAL_AUTH_PROVIDERS.find((entry) => entry.provider === provider);
|
|
1378
|
+
}
|
|
1379
|
+
function dualAuthProviderByOAuthKey(storageKey) {
|
|
1380
|
+
return DUAL_AUTH_PROVIDERS.find((entry) => entry.oauthKey === storageKey);
|
|
1381
|
+
}
|
|
1382
|
+
function oauthStorageKey(provider) {
|
|
1383
|
+
return dualAuthProvider(provider)?.oauthKey;
|
|
1384
|
+
}
|
|
1385
|
+
function providerStorageKeys(provider) {
|
|
1386
|
+
const dual = dualAuthProvider(provider);
|
|
1387
|
+
return dual ? [dual.oauthKey, dual.provider] : [provider];
|
|
1388
|
+
}
|
|
1161
1389
|
var XIAOMI_CREDITS_KEY = "xiaomi-credits";
|
|
1162
1390
|
var LOCAL_AUTH_KEY_PREFIX = "local:";
|
|
1163
1391
|
var LOCAL_CREDENTIAL_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
1164
1392
|
function activeBaseUrlEntry(data, provider) {
|
|
1165
|
-
|
|
1166
|
-
|
|
1393
|
+
const dual = dualAuthProvider(provider);
|
|
1394
|
+
if (dual) {
|
|
1395
|
+
const oauth = data[dual.oauthKey];
|
|
1167
1396
|
if (oauth) {
|
|
1168
1397
|
const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
|
|
1169
|
-
if (Date.now() < exhaustedUntil && data[
|
|
1398
|
+
if (Date.now() < exhaustedUntil && data[dual.provider]) return data[dual.provider];
|
|
1170
1399
|
return oauth;
|
|
1171
1400
|
}
|
|
1172
|
-
return data[
|
|
1401
|
+
return data[dual.provider];
|
|
1173
1402
|
}
|
|
1174
1403
|
return data[provider];
|
|
1175
1404
|
}
|
|
@@ -1204,6 +1433,16 @@ var AuthStorage = class {
|
|
|
1204
1433
|
data = {};
|
|
1205
1434
|
filePath;
|
|
1206
1435
|
loaded = false;
|
|
1436
|
+
/**
|
|
1437
|
+
* mtime+size of the file as of the cached snapshot (`size: -1` = no file).
|
|
1438
|
+
* auth.json is shared: the desktop app writes API keys and disconnects
|
|
1439
|
+
* NATIVELY (so they work with no daemon running), and every window/process has
|
|
1440
|
+
* its own AuthStorage. A load-once cache therefore goes stale — the sidecar
|
|
1441
|
+
* would keep listing models for a provider just disconnected, and hide the
|
|
1442
|
+
* ones just connected, until the daemon restarted.
|
|
1443
|
+
*/
|
|
1444
|
+
snapshotMtimeMs = 0;
|
|
1445
|
+
snapshotSize = -1;
|
|
1207
1446
|
/** Per-provider lock to serialize concurrent refresh calls. */
|
|
1208
1447
|
refreshLocks = /* @__PURE__ */ new Map();
|
|
1209
1448
|
constructor(filePath) {
|
|
@@ -1215,12 +1454,12 @@ var AuthStorage = class {
|
|
|
1215
1454
|
}
|
|
1216
1455
|
/** List provider keys with stored credentials. */
|
|
1217
1456
|
async listProviders() {
|
|
1218
|
-
await this.
|
|
1457
|
+
await this.ensureFresh();
|
|
1219
1458
|
return Object.keys(this.data);
|
|
1220
1459
|
}
|
|
1221
1460
|
/** True if credentials exist for `provider`. */
|
|
1222
1461
|
async hasCredentials(provider) {
|
|
1223
|
-
await this.
|
|
1462
|
+
await this.ensureFresh();
|
|
1224
1463
|
return Boolean(this.data[provider]);
|
|
1225
1464
|
}
|
|
1226
1465
|
/**
|
|
@@ -1231,18 +1470,19 @@ var AuthStorage = class {
|
|
|
1231
1470
|
* instead of re-deriving the same order.
|
|
1232
1471
|
*/
|
|
1233
1472
|
async pickStorageKey(keys) {
|
|
1234
|
-
await this.
|
|
1473
|
+
await this.ensureFresh();
|
|
1235
1474
|
return keys.find((key) => Boolean(this.data[key]));
|
|
1236
1475
|
}
|
|
1237
1476
|
/**
|
|
1238
|
-
* True if the user has any usable auth for the logical provider. For
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1477
|
+
* True if the user has any usable auth for the logical provider. For a
|
|
1478
|
+
* dual-auth provider (Kimi/Grok) either the OAuth credential or the API key
|
|
1479
|
+
* satisfies it.
|
|
1241
1480
|
*/
|
|
1242
1481
|
async hasProviderAuth(provider) {
|
|
1243
|
-
await this.
|
|
1244
|
-
|
|
1245
|
-
|
|
1482
|
+
await this.ensureFresh();
|
|
1483
|
+
const dual = dualAuthProvider(provider);
|
|
1484
|
+
if (dual) {
|
|
1485
|
+
return Boolean(this.data[dual.oauthKey] || this.data[dual.provider]);
|
|
1246
1486
|
}
|
|
1247
1487
|
if (provider === "xiaomi") {
|
|
1248
1488
|
return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
|
|
@@ -1276,14 +1516,16 @@ var AuthStorage = class {
|
|
|
1276
1516
|
}
|
|
1277
1517
|
/**
|
|
1278
1518
|
* True if the active credential for `provider` is a static API key with no
|
|
1279
|
-
* refresh mechanism. For
|
|
1280
|
-
* credential is absent (a
|
|
1519
|
+
* refresh mechanism. For a dual-auth provider this is only true when its OAuth
|
|
1520
|
+
* credential is absent or sidelined (a live OAuth credential is refreshable).
|
|
1281
1521
|
*/
|
|
1282
1522
|
async isStaticApiKey(provider) {
|
|
1283
|
-
await this.
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1523
|
+
await this.ensureFresh();
|
|
1524
|
+
const dual = dualAuthProvider(provider);
|
|
1525
|
+
const oauthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
1526
|
+
if (dual && oauthCreds) {
|
|
1527
|
+
const exhaustedUntil = oauthCreds.usageExhaustedUntil ?? 0;
|
|
1528
|
+
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data[dual.provider]);
|
|
1287
1529
|
if (!apiKeyActive) return false;
|
|
1288
1530
|
}
|
|
1289
1531
|
return STATIC_API_KEY_PROVIDERS.has(provider);
|
|
@@ -1291,26 +1533,30 @@ var AuthStorage = class {
|
|
|
1291
1533
|
/**
|
|
1292
1534
|
* The base URL on the credential that is active right now, if any.
|
|
1293
1535
|
* Synchronous — call only after load()/resolveCredentials() populated the
|
|
1294
|
-
* snapshot. For
|
|
1295
|
-
*
|
|
1296
|
-
* usage-exhausted with an
|
|
1536
|
+
* snapshot. For a dual-auth provider this is the subscription endpoint (Kimi
|
|
1537
|
+
* For Coding, the Grok CLI proxy) whenever the OAuth entry is the one
|
|
1538
|
+
* resolveCredentials would serve (i.e. not currently usage-exhausted with an
|
|
1539
|
+
* API key configured).
|
|
1297
1540
|
*/
|
|
1298
1541
|
getStoredBaseUrl(provider) {
|
|
1299
1542
|
return activeBaseUrlEntry(this.data, provider)?.baseUrl;
|
|
1300
1543
|
}
|
|
1301
1544
|
async load() {
|
|
1545
|
+
const first = !this.loaded;
|
|
1302
1546
|
await withFileLock(this.filePath, async () => {
|
|
1303
1547
|
try {
|
|
1304
1548
|
const content = await fs4.readFile(this.filePath, "utf-8");
|
|
1305
1549
|
this.data = JSON.parse(content);
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1550
|
+
if (first) {
|
|
1551
|
+
log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
|
|
1552
|
+
providers: Object.keys(this.data).join(",") || "(none)"
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1309
1555
|
} catch (err) {
|
|
1310
1556
|
this.data = {};
|
|
1311
1557
|
const code = err.code;
|
|
1312
1558
|
if (code === "ENOENT") {
|
|
1313
|
-
log("INFO", "auth", `No auth file found at ${this.filePath} (first run)`);
|
|
1559
|
+
if (first) log("INFO", "auth", `No auth file found at ${this.filePath} (first run)`);
|
|
1314
1560
|
} else {
|
|
1315
1561
|
log(
|
|
1316
1562
|
"ERROR",
|
|
@@ -1322,10 +1568,50 @@ var AuthStorage = class {
|
|
|
1322
1568
|
}
|
|
1323
1569
|
});
|
|
1324
1570
|
this.loaded = true;
|
|
1571
|
+
await this.rememberSnapshot();
|
|
1325
1572
|
}
|
|
1326
1573
|
async ensureLoaded() {
|
|
1327
1574
|
if (!this.loaded) await this.load();
|
|
1328
1575
|
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Like {@link ensureLoaded}, but re-reads when the file changed since this
|
|
1578
|
+
* snapshot — a cheap stat, not a re-parse. Used by the "what is connected?"
|
|
1579
|
+
* readers, which must reflect writes made by another window, the CLI, or the
|
|
1580
|
+
* desktop app's native (daemon-free) API-key and disconnect paths.
|
|
1581
|
+
*
|
|
1582
|
+
* Deliberately NOT used by {@link resolveCredentials}: that path compares the
|
|
1583
|
+
* caller's snapshot against the latest file to detect a concurrent re-login,
|
|
1584
|
+
* and silently refreshing this instance's view first would destroy the
|
|
1585
|
+
* evidence that the token it just had rejected has already been replaced.
|
|
1586
|
+
*/
|
|
1587
|
+
async ensureFresh() {
|
|
1588
|
+
if (!this.loaded) {
|
|
1589
|
+
await this.load();
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
let changed;
|
|
1593
|
+
try {
|
|
1594
|
+
const stat = await fs4.stat(this.filePath);
|
|
1595
|
+
changed = stat.mtimeMs !== this.snapshotMtimeMs || stat.size !== this.snapshotSize;
|
|
1596
|
+
} catch {
|
|
1597
|
+
changed = this.snapshotSize !== -1;
|
|
1598
|
+
}
|
|
1599
|
+
if (changed) await this.load();
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Record the file identity behind the current snapshot, so {@link ensureLoaded}
|
|
1603
|
+
* can tell "someone else wrote" from "this is our own write".
|
|
1604
|
+
*/
|
|
1605
|
+
async rememberSnapshot() {
|
|
1606
|
+
try {
|
|
1607
|
+
const stat = await fs4.stat(this.filePath);
|
|
1608
|
+
this.snapshotMtimeMs = stat.mtimeMs;
|
|
1609
|
+
this.snapshotSize = stat.size;
|
|
1610
|
+
} catch {
|
|
1611
|
+
this.snapshotMtimeMs = 0;
|
|
1612
|
+
this.snapshotSize = -1;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1329
1615
|
/**
|
|
1330
1616
|
* Apply one provider-scoped mutation to the latest on-disk snapshot.
|
|
1331
1617
|
* AuthStorage instances live in every app session/process, so writing this
|
|
@@ -1341,14 +1627,16 @@ var AuthStorage = class {
|
|
|
1341
1627
|
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1342
1628
|
this.data = latest;
|
|
1343
1629
|
});
|
|
1630
|
+
await this.rememberSnapshot();
|
|
1344
1631
|
}
|
|
1345
1632
|
async reloadLatest() {
|
|
1346
1633
|
await withFileLock(this.filePath, async () => {
|
|
1347
1634
|
this.data = await readAuthData(this.filePath);
|
|
1348
1635
|
});
|
|
1636
|
+
await this.rememberSnapshot();
|
|
1349
1637
|
}
|
|
1350
1638
|
async getCredentials(provider) {
|
|
1351
|
-
await this.
|
|
1639
|
+
await this.ensureFresh();
|
|
1352
1640
|
return this.data[provider];
|
|
1353
1641
|
}
|
|
1354
1642
|
async setCredentials(provider, creds) {
|
|
@@ -1366,7 +1654,7 @@ var AuthStorage = class {
|
|
|
1366
1654
|
* `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
|
|
1367
1655
|
* 15-minute default when no reset time is known. While the mark is in the
|
|
1368
1656
|
* future, `resolveCredentials("moonshot")` serves the Moonshot API key
|
|
1369
|
-
* instead of the
|
|
1657
|
+
* instead of the subscription OAuth credential (when both are configured) — OAuth
|
|
1370
1658
|
* stays the preferred credential and is retried automatically once the mark
|
|
1371
1659
|
* lapses. Persisted to auth.json so a restart (or another gg-app window)
|
|
1372
1660
|
* doesn't burn a request rediscovering the same exhausted window. No-op if
|
|
@@ -1403,7 +1691,7 @@ var AuthStorage = class {
|
|
|
1403
1691
|
*/
|
|
1404
1692
|
async resolveCredentials(provider, opts) {
|
|
1405
1693
|
await this.ensureLoaded();
|
|
1406
|
-
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : provider
|
|
1694
|
+
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
|
|
1407
1695
|
if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
|
|
1408
1696
|
await this.reloadLatest();
|
|
1409
1697
|
}
|
|
@@ -1414,28 +1702,30 @@ var AuthStorage = class {
|
|
|
1414
1702
|
}
|
|
1415
1703
|
throw new NotLoggedInError(provider);
|
|
1416
1704
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1705
|
+
const dual = dualAuthProvider(provider);
|
|
1706
|
+
const dualOAuthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
1707
|
+
if (dual && dualOAuthCreds) {
|
|
1708
|
+
const exhaustedUntil = dualOAuthCreds.usageExhaustedUntil ?? 0;
|
|
1709
|
+
if (Date.now() < exhaustedUntil && this.data[dual.provider]) {
|
|
1420
1710
|
log(
|
|
1421
1711
|
"WARN",
|
|
1422
1712
|
"auth",
|
|
1423
|
-
|
|
1713
|
+
`${dual.oauthLabel} usage window is exhausted \u2014 using the ${dual.apiKeyLabel} until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
|
|
1424
1714
|
);
|
|
1425
|
-
return this.data[
|
|
1715
|
+
return this.data[dual.provider];
|
|
1426
1716
|
}
|
|
1427
1717
|
try {
|
|
1428
|
-
return await this.resolveCredentials(
|
|
1718
|
+
return await this.resolveCredentials(dual.oauthKey, {
|
|
1429
1719
|
...opts?.forceRefresh ? { forceRefresh: true } : {}
|
|
1430
1720
|
});
|
|
1431
1721
|
} catch (err) {
|
|
1432
|
-
if (err instanceof NotLoggedInError && this.data[
|
|
1722
|
+
if (err instanceof NotLoggedInError && this.data[dual.provider]) {
|
|
1433
1723
|
log(
|
|
1434
1724
|
"WARN",
|
|
1435
1725
|
"auth",
|
|
1436
|
-
|
|
1726
|
+
`${dual.oauthLabel} credential is no longer valid \u2014 falling back to the ${dual.apiKeyLabel}. ${dual.restoreHint}`
|
|
1437
1727
|
);
|
|
1438
|
-
return this.data[
|
|
1728
|
+
return this.data[dual.provider];
|
|
1439
1729
|
}
|
|
1440
1730
|
throw err;
|
|
1441
1731
|
}
|
|
@@ -1464,7 +1754,7 @@ var AuthStorage = class {
|
|
|
1464
1754
|
this.data = latest;
|
|
1465
1755
|
return latestCreds;
|
|
1466
1756
|
}
|
|
1467
|
-
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
|
|
1757
|
+
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : provider === XAI_OAUTH_KEY ? refreshXaiToken : refreshOpenAIToken;
|
|
1468
1758
|
let refreshed;
|
|
1469
1759
|
try {
|
|
1470
1760
|
refreshed = await refreshFn(latestCreds.refreshToken);
|
|
@@ -2084,8 +2374,18 @@ export {
|
|
|
2084
2374
|
refreshOpenAIToken,
|
|
2085
2375
|
loginGemini,
|
|
2086
2376
|
refreshGeminiToken,
|
|
2377
|
+
grokCliBaseUrl,
|
|
2378
|
+
grokCliHeaders,
|
|
2379
|
+
isGrokCliEndpoint,
|
|
2380
|
+
loginXai,
|
|
2381
|
+
refreshXaiToken,
|
|
2087
2382
|
withFileLock,
|
|
2088
2383
|
MOONSHOT_OAUTH_KEY,
|
|
2384
|
+
XAI_OAUTH_KEY,
|
|
2385
|
+
dualAuthProvider,
|
|
2386
|
+
dualAuthProviderByOAuthKey,
|
|
2387
|
+
oauthStorageKey,
|
|
2388
|
+
providerStorageKeys,
|
|
2089
2389
|
XIAOMI_CREDITS_KEY,
|
|
2090
2390
|
LOCAL_AUTH_KEY_PREFIX,
|
|
2091
2391
|
readStoredBaseUrlSync,
|
|
@@ -2110,4 +2410,4 @@ export {
|
|
|
2110
2410
|
getSummaryModel,
|
|
2111
2411
|
getFastModel
|
|
2112
2412
|
};
|
|
2113
|
-
//# sourceMappingURL=chunk-
|
|
2413
|
+
//# sourceMappingURL=chunk-XDE6VUI4.js.map
|