@jeffreycao/copilot-api 2.7.0 → 2.7.2
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/auth-C3_rHdk6.js +2 -0
- package/dist/{auth-CmKD_Gmp.js → auth-DC-EO-eb.js} +68 -26
- package/dist/auth-DC-EO-eb.js.map +1 -0
- package/dist/{config-B2OaAnpL.js → config-DIDLVXMU.js} +24 -6
- package/dist/config-DIDLVXMU.js.map +1 -0
- package/dist/{config-store-CARrUljo.js → config-store-CMgB1mCV.js} +2 -1
- package/dist/config-store-CMgB1mCV.js.map +1 -0
- package/dist/{copilot-runtime-B4x9RzsD.js → copilot-runtime-CRTYwDWy.js} +3 -3
- package/dist/{copilot-runtime-B4x9RzsD.js.map → copilot-runtime-CRTYwDWy.js.map} +1 -1
- package/dist/{debug-CcfAczV9.js → debug-B0URJ4gT.js} +3 -3
- package/dist/{debug-CcfAczV9.js.map → debug-B0URJ4gT.js.map} +1 -1
- package/dist/{github-copilot-provider-DlQZgaCT.js → github-copilot-provider-CLEAHyT-.js} +2 -2
- package/dist/{github-copilot-provider-DlQZgaCT.js.map → github-copilot-provider-CLEAHyT-.js.map} +1 -1
- package/dist/main.js +6 -6
- package/dist/{mcp-Byz9h_xz.js → mcp-CuZyqwi8.js} +2 -2
- package/dist/{mcp-Byz9h_xz.js.map → mcp-CuZyqwi8.js.map} +1 -1
- package/dist/mcp-server-BH-SFdCD.js +2 -0
- package/dist/{mcp-server-D6uKVzo6.js → mcp-server-CKEydKaf.js} +2 -2
- package/dist/{mcp-server-D6uKVzo6.js.map → mcp-server-CKEydKaf.js.map} +1 -1
- package/dist/{provider-DktH3jcC.js → provider-C2Hs8Dua.js} +2 -2
- package/dist/{provider-DktH3jcC.js.map → provider-C2Hs8Dua.js.map} +1 -1
- package/dist/{provider-management-z3m7cUkp.js → provider-management-BjU3aJnC.js} +3 -3
- package/dist/{provider-management-z3m7cUkp.js.map → provider-management-BjU3aJnC.js.map} +1 -1
- package/dist/{server-VjfrS2Aw.js → server-DbkseGH6.js} +101 -24
- package/dist/server-DbkseGH6.js.map +1 -0
- package/dist/{start-DX3Q5lol.js → start-C_Svr70X.js} +8 -8
- package/dist/{start-DX3Q5lol.js.map → start-C_Svr70X.js.map} +1 -1
- package/dist/{token-0pmwyQAu.js → token-BjzzuPSb.js} +649 -347
- package/dist/token-BjzzuPSb.js.map +1 -0
- package/package.json +1 -1
- package/dist/auth-B4LfP6nc.js +0 -2
- package/dist/auth-CmKD_Gmp.js.map +0 -1
- package/dist/config-B2OaAnpL.js.map +0 -1
- package/dist/config-store-CARrUljo.js.map +0 -1
- package/dist/mcp-server-ouCaZXlw.js +0 -2
- package/dist/server-VjfrS2Aw.js.map +0 -1
- package/dist/token-0pmwyQAu.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as writeFileAtomically, _ as readEditableConfigFromDisk, c as getConfig, h as isResponsesApiWebSocketEnabled, u as getUpstreamTransportConfig, x as PATHS } from "./config-store-
|
|
2
|
-
import { c as setProviderConfig, n as getRawProviderConfig } from "./config-
|
|
3
|
-
import { a as UpstreamHeadersTimeoutError, i as HTTPError, n as assertGitHubCopilotEnabled, o as UpstreamStreamInactivityTimeoutError } from "./github-copilot-provider-
|
|
1
|
+
import { C as writeFileAtomically, _ as readEditableConfigFromDisk, c as getConfig, h as isResponsesApiWebSocketEnabled, u as getUpstreamTransportConfig, x as PATHS } from "./config-store-CMgB1mCV.js";
|
|
2
|
+
import { c as setProviderConfig, n as getRawProviderConfig } from "./config-DIDLVXMU.js";
|
|
3
|
+
import { a as UpstreamHeadersTimeoutError, i as HTTPError, n as assertGitHubCopilotEnabled, o as UpstreamStreamInactivityTimeoutError } from "./github-copilot-provider-CLEAHyT-.js";
|
|
4
4
|
import consola from "consola";
|
|
5
5
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
6
6
|
import path from "node:path";
|
|
@@ -11,6 +11,7 @@ import { WebSocket } from "undici";
|
|
|
11
11
|
import { events } from "fetch-event-stream";
|
|
12
12
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
13
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
14
|
+
import { z } from "zod";
|
|
14
15
|
import { exec } from "node:child_process";
|
|
15
16
|
//#region src/lib/internal-headers.ts
|
|
16
17
|
const FULL_MODEL_CATALOG_HEADER = "x-full-model-catalog";
|
|
@@ -980,15 +981,23 @@ function shouldIncludeHeader(headerName) {
|
|
|
980
981
|
const CALLBACK_HOST = "127.0.0.1";
|
|
981
982
|
const CALLBACK_PORT = 1455;
|
|
982
983
|
const CALLBACK_PATH = "/auth/callback";
|
|
983
|
-
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
984
|
+
const CLIENT_ID$1 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
984
985
|
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
985
986
|
const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
986
987
|
const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
987
|
-
const SCOPE = "openid profile email offline_access";
|
|
988
|
+
const SCOPE$1 = "openid profile email offline_access";
|
|
988
989
|
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
989
|
-
const REFRESH_BUFFER_MS = 6e4;
|
|
990
|
-
const CALLBACK_TIMEOUT_MS =
|
|
990
|
+
const REFRESH_BUFFER_MS$1 = 6e4;
|
|
991
|
+
const CALLBACK_TIMEOUT_MS = 12e4;
|
|
991
992
|
const REFRESH_TIMEOUT_MS = 3e4;
|
|
993
|
+
var CodexOAuthError = class extends Error {
|
|
994
|
+
reason;
|
|
995
|
+
constructor(reason) {
|
|
996
|
+
super(reason === "callback_timeout" ? "Codex authorization timed out after 2 minutes" : "Could not listen for Codex authorization on port 1455");
|
|
997
|
+
this.reason = reason;
|
|
998
|
+
this.name = "CodexOAuthError";
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
992
1001
|
function base64UrlEncode(bytes) {
|
|
993
1002
|
return Buffer.from(bytes).toString("base64url");
|
|
994
1003
|
}
|
|
@@ -1040,7 +1049,7 @@ function decodeJwt(accessToken) {
|
|
|
1040
1049
|
return null;
|
|
1041
1050
|
}
|
|
1042
1051
|
}
|
|
1043
|
-
function getAccountId(accessToken) {
|
|
1052
|
+
function getAccountId$1(accessToken) {
|
|
1044
1053
|
const payload = decodeJwt(accessToken);
|
|
1045
1054
|
if (!payload) return null;
|
|
1046
1055
|
const authPayload = payload[JWT_CLAIM_PATH];
|
|
@@ -1108,17 +1117,18 @@ function renderOAuthErrorPage(message) {
|
|
|
1108
1117
|
message
|
|
1109
1118
|
});
|
|
1110
1119
|
}
|
|
1111
|
-
async function exchangeAuthorizationCode(code, verifier) {
|
|
1120
|
+
async function exchangeAuthorizationCode(code, verifier, signal) {
|
|
1112
1121
|
const response = await fetch(TOKEN_URL, {
|
|
1113
1122
|
method: "POST",
|
|
1114
1123
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1115
1124
|
body: new URLSearchParams({
|
|
1116
1125
|
grant_type: "authorization_code",
|
|
1117
|
-
client_id: CLIENT_ID,
|
|
1126
|
+
client_id: CLIENT_ID$1,
|
|
1118
1127
|
code,
|
|
1119
1128
|
code_verifier: verifier,
|
|
1120
1129
|
redirect_uri: REDIRECT_URI
|
|
1121
|
-
})
|
|
1130
|
+
}),
|
|
1131
|
+
signal
|
|
1122
1132
|
});
|
|
1123
1133
|
if (!response.ok) {
|
|
1124
1134
|
const details = await response.text().catch(() => "");
|
|
@@ -1143,7 +1153,7 @@ async function refreshAccessToken(refreshToken, options = {}) {
|
|
|
1143
1153
|
body: new URLSearchParams({
|
|
1144
1154
|
grant_type: "refresh_token",
|
|
1145
1155
|
refresh_token: refreshToken,
|
|
1146
|
-
client_id: CLIENT_ID
|
|
1156
|
+
client_id: CLIENT_ID$1
|
|
1147
1157
|
}),
|
|
1148
1158
|
signal
|
|
1149
1159
|
});
|
|
@@ -1168,9 +1178,9 @@ async function createAuthorizationFlow() {
|
|
|
1168
1178
|
const state = createState();
|
|
1169
1179
|
const url = new URL(AUTHORIZE_URL);
|
|
1170
1180
|
url.searchParams.set("response_type", "code");
|
|
1171
|
-
url.searchParams.set("client_id", CLIENT_ID);
|
|
1181
|
+
url.searchParams.set("client_id", CLIENT_ID$1);
|
|
1172
1182
|
url.searchParams.set("redirect_uri", REDIRECT_URI);
|
|
1173
|
-
url.searchParams.set("scope", SCOPE);
|
|
1183
|
+
url.searchParams.set("scope", SCOPE$1);
|
|
1174
1184
|
url.searchParams.set("code_challenge", challenge);
|
|
1175
1185
|
url.searchParams.set("code_challenge_method", "S256");
|
|
1176
1186
|
url.searchParams.set("state", state);
|
|
@@ -1183,7 +1193,8 @@ async function createAuthorizationFlow() {
|
|
|
1183
1193
|
url: url.toString()
|
|
1184
1194
|
};
|
|
1185
1195
|
}
|
|
1186
|
-
async function waitForAuthorizationCode(state) {
|
|
1196
|
+
async function waitForAuthorizationCode(state, signal) {
|
|
1197
|
+
signal?.throwIfAborted();
|
|
1187
1198
|
let resolveCode;
|
|
1188
1199
|
const waitForCode = new Promise((resolve) => {
|
|
1189
1200
|
resolveCode = resolve;
|
|
@@ -1229,14 +1240,21 @@ async function waitForAuthorizationCode(state) {
|
|
|
1229
1240
|
});
|
|
1230
1241
|
});
|
|
1231
1242
|
} catch {
|
|
1232
|
-
|
|
1243
|
+
throw new CodexOAuthError("callback_unavailable");
|
|
1233
1244
|
}
|
|
1245
|
+
let timeout;
|
|
1246
|
+
const onAbort = () => resolveCode?.(null);
|
|
1247
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1234
1248
|
try {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1249
|
+
signal?.throwIfAborted();
|
|
1250
|
+
timeout = setTimeout(() => resolveCode?.(null), CALLBACK_TIMEOUT_MS);
|
|
1251
|
+
const code = await waitForCode;
|
|
1252
|
+
signal?.throwIfAborted();
|
|
1253
|
+
if (!code) throw new CodexOAuthError("callback_timeout");
|
|
1254
|
+
return code;
|
|
1239
1255
|
} finally {
|
|
1256
|
+
clearTimeout(timeout);
|
|
1257
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1240
1258
|
await new Promise((resolve, reject) => {
|
|
1241
1259
|
server.close((error) => {
|
|
1242
1260
|
if (error) {
|
|
@@ -1245,25 +1263,40 @@ async function waitForAuthorizationCode(state) {
|
|
|
1245
1263
|
}
|
|
1246
1264
|
resolve();
|
|
1247
1265
|
});
|
|
1266
|
+
server.closeAllConnections();
|
|
1248
1267
|
}).catch(() => void 0);
|
|
1249
1268
|
}
|
|
1250
1269
|
}
|
|
1251
1270
|
async function loginCodex(options) {
|
|
1271
|
+
options.signal?.throwIfAborted();
|
|
1252
1272
|
const { verifier, state, url } = await createAuthorizationFlow();
|
|
1273
|
+
options.signal?.throwIfAborted();
|
|
1253
1274
|
options.onAuth({
|
|
1254
1275
|
url,
|
|
1255
1276
|
instructions: "Please complete the login in the browser. If the browser does not automatically redirect, please paste the callback URL or code back to the terminal."
|
|
1256
1277
|
});
|
|
1257
1278
|
options.onProgress?.("Waiting for Codex OAuth callback");
|
|
1258
|
-
let code =
|
|
1279
|
+
let code = null;
|
|
1280
|
+
let callbackError;
|
|
1281
|
+
try {
|
|
1282
|
+
code = await waitForAuthorizationCode(state, options.signal);
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
options.signal?.throwIfAborted();
|
|
1285
|
+
if (!(error instanceof CodexOAuthError)) throw error;
|
|
1286
|
+
callbackError = error;
|
|
1287
|
+
}
|
|
1288
|
+
options.signal?.throwIfAborted();
|
|
1259
1289
|
if (!code) {
|
|
1260
|
-
const
|
|
1290
|
+
const input = await options.onPrompt("Paste the authorization code or full redirect URL:");
|
|
1291
|
+
options.signal?.throwIfAborted();
|
|
1292
|
+
const parsed = parseAuthorizationInput(input);
|
|
1261
1293
|
if (parsed.state && parsed.state !== state) throw new Error("Codex OAuth state mismatch");
|
|
1262
1294
|
code = parsed.code ?? null;
|
|
1263
1295
|
}
|
|
1264
|
-
if (!code) throw new Error("Missing Codex authorization code");
|
|
1265
|
-
const tokenResult = await exchangeAuthorizationCode(code, verifier);
|
|
1266
|
-
|
|
1296
|
+
if (!code) throw callbackError ?? /* @__PURE__ */ new Error("Missing Codex authorization code");
|
|
1297
|
+
const tokenResult = await exchangeAuthorizationCode(code, verifier, options.signal);
|
|
1298
|
+
options.signal?.throwIfAborted();
|
|
1299
|
+
const accountId = getAccountId$1(tokenResult.accessToken);
|
|
1267
1300
|
if (!accountId) throw new Error("Failed to extract Codex account id from access token");
|
|
1268
1301
|
return {
|
|
1269
1302
|
accessToken: tokenResult.accessToken,
|
|
@@ -1274,17 +1307,590 @@ async function loginCodex(options) {
|
|
|
1274
1307
|
}
|
|
1275
1308
|
async function refreshCodexCredentials(credentials, options = {}) {
|
|
1276
1309
|
const tokenResult = await refreshAccessToken(credentials.refreshToken, options);
|
|
1277
|
-
const accountId = getAccountId(tokenResult.accessToken);
|
|
1310
|
+
const accountId = getAccountId$1(tokenResult.accessToken);
|
|
1278
1311
|
if (!accountId) throw new Error("Failed to extract Codex account id from access token");
|
|
1279
1312
|
return {
|
|
1280
|
-
accessToken: tokenResult.accessToken,
|
|
1281
|
-
refreshToken: tokenResult.refreshToken,
|
|
1282
|
-
expiresAt: tokenResult.expiresAt,
|
|
1283
|
-
accountId
|
|
1313
|
+
accessToken: tokenResult.accessToken,
|
|
1314
|
+
refreshToken: tokenResult.refreshToken,
|
|
1315
|
+
expiresAt: tokenResult.expiresAt,
|
|
1316
|
+
accountId
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
function isCodexCredentialsExpired(credentials, now = Date.now()) {
|
|
1320
|
+
return credentials.expiresAt <= now + REFRESH_BUFFER_MS$1;
|
|
1321
|
+
}
|
|
1322
|
+
//#endregion
|
|
1323
|
+
//#region src/lib/oauth/xai.ts
|
|
1324
|
+
const XAI_API_BASE_URL = "https://api.x.ai";
|
|
1325
|
+
const CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
1326
|
+
const ISSUER = "https://auth.x.ai/oauth2";
|
|
1327
|
+
const SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
|
1328
|
+
const REQUEST_TIMEOUT_MS = 3e4;
|
|
1329
|
+
const POLLING_SAFETY_MARGIN_MS = 3e3;
|
|
1330
|
+
const REFRESH_BUFFER_MS = 6e4;
|
|
1331
|
+
const tokenSchema = z.object({
|
|
1332
|
+
access_token: z.string().trim().min(1),
|
|
1333
|
+
refresh_token: z.string().trim().min(1).optional(),
|
|
1334
|
+
id_token: z.string().optional(),
|
|
1335
|
+
expires_in: z.number().finite().optional()
|
|
1336
|
+
});
|
|
1337
|
+
const deviceSchema = z.object({
|
|
1338
|
+
device_code: z.string().min(1),
|
|
1339
|
+
user_code: z.string().min(1),
|
|
1340
|
+
verification_uri: z.url({ protocol: /^https$/u }),
|
|
1341
|
+
verification_uri_complete: z.url({ protocol: /^https$/u }).optional(),
|
|
1342
|
+
expires_in: z.number().finite().optional(),
|
|
1343
|
+
interval: z.number().finite().optional()
|
|
1344
|
+
});
|
|
1345
|
+
const errorSchema = z.object({ error: z.string().optional() });
|
|
1346
|
+
var XaiOAuthError = class extends Error {
|
|
1347
|
+
reason;
|
|
1348
|
+
constructor(reason) {
|
|
1349
|
+
super(reason === "authorization_expired" ? "xAI device code expired. Please sign in again." : "xAI device authorization was denied.");
|
|
1350
|
+
this.name = "XaiOAuthError";
|
|
1351
|
+
this.reason = reason;
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
function positiveSeconds(value, fallback) {
|
|
1355
|
+
return value !== void 0 && value > 0 ? value : fallback;
|
|
1356
|
+
}
|
|
1357
|
+
function tokenCredentials(tokens, currentRefreshToken) {
|
|
1358
|
+
const refreshToken = tokens.refresh_token ?? currentRefreshToken;
|
|
1359
|
+
if (!refreshToken) throw new Error("xAI token response is missing refresh_token");
|
|
1360
|
+
let expiresAt = Date.now() + positiveSeconds(tokens.expires_in, 3600) * 1e3;
|
|
1361
|
+
if (!tokens.expires_in || tokens.expires_in <= 0) try {
|
|
1362
|
+
const payload = JSON.parse(Buffer.from(tokens.access_token.split(".")[1], "base64url").toString("utf8"));
|
|
1363
|
+
const claims = z.object({ exp: z.number().finite().positive() }).safeParse(payload);
|
|
1364
|
+
if (claims.success) expiresAt = claims.data.exp * 1e3;
|
|
1365
|
+
} catch {}
|
|
1366
|
+
return {
|
|
1367
|
+
accessToken: tokens.access_token,
|
|
1368
|
+
refreshToken,
|
|
1369
|
+
expiresAt
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function tokenAccountId(token) {
|
|
1373
|
+
if (!token) return void 0;
|
|
1374
|
+
try {
|
|
1375
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf8"));
|
|
1376
|
+
const claims = z.object({ sub: z.string().trim().min(1) }).safeParse(payload);
|
|
1377
|
+
return claims.success ? claims.data.sub : void 0;
|
|
1378
|
+
} catch {
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
async function getAccountId(tokens, signal, dependencies) {
|
|
1383
|
+
const accountId = tokenAccountId(tokens.id_token) ?? tokenAccountId(tokens.access_token);
|
|
1384
|
+
if (accountId) return accountId;
|
|
1385
|
+
const response = await (dependencies.fetcher ?? fetch)(`${ISSUER}/userinfo`, {
|
|
1386
|
+
headers: {
|
|
1387
|
+
Authorization: `Bearer ${tokens.access_token}`,
|
|
1388
|
+
Accept: "application/json"
|
|
1389
|
+
},
|
|
1390
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)])
|
|
1391
|
+
});
|
|
1392
|
+
if (!response.ok) throw requestError(response, await readOAuthError(response));
|
|
1393
|
+
const user = z.object({ sub: z.string().trim().min(1) }).parse(await response.json());
|
|
1394
|
+
signal.throwIfAborted();
|
|
1395
|
+
return user.sub;
|
|
1396
|
+
}
|
|
1397
|
+
async function send(endpoint, body, options = {}, dependencies = {}) {
|
|
1398
|
+
const timeoutMs = options.timeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
1399
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
1400
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
1401
|
+
try {
|
|
1402
|
+
return await (dependencies.fetcher ?? fetch)(`${ISSUER}/${endpoint}`, {
|
|
1403
|
+
method: "POST",
|
|
1404
|
+
headers: {
|
|
1405
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1406
|
+
Accept: "application/json",
|
|
1407
|
+
"User-Agent": "copilot-api"
|
|
1408
|
+
},
|
|
1409
|
+
body: new URLSearchParams(body),
|
|
1410
|
+
signal
|
|
1411
|
+
});
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
options.signal?.throwIfAborted();
|
|
1414
|
+
if (timeout.aborted) throw new Error(`xAI authorization request timed out after ${timeoutMs}ms`, { cause: error });
|
|
1415
|
+
throw error;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
async function readOAuthError(response) {
|
|
1419
|
+
const payload = await response.json().catch(() => null);
|
|
1420
|
+
const parsed = errorSchema.safeParse(payload);
|
|
1421
|
+
return parsed.success ? parsed.data.error : void 0;
|
|
1422
|
+
}
|
|
1423
|
+
function requestError(response, error) {
|
|
1424
|
+
const detail = error && /^[a-z_]+$/u.test(error) ? `: ${error}` : "";
|
|
1425
|
+
return /* @__PURE__ */ new Error(`xAI authorization request failed (${response.status})${detail}`);
|
|
1426
|
+
}
|
|
1427
|
+
async function loginXai(options, dependencies = {}) {
|
|
1428
|
+
options.signal?.throwIfAborted();
|
|
1429
|
+
const response = await send("device/code", {
|
|
1430
|
+
client_id: CLIENT_ID,
|
|
1431
|
+
scope: SCOPE,
|
|
1432
|
+
referrer: "opencode"
|
|
1433
|
+
}, options, dependencies);
|
|
1434
|
+
if (!response.ok) throw requestError(response, await readOAuthError(response));
|
|
1435
|
+
const device = deviceSchema.parse(await response.json());
|
|
1436
|
+
options.signal?.throwIfAborted();
|
|
1437
|
+
const lifetimeMs = positiveSeconds(device.expires_in, 300) * 1e3;
|
|
1438
|
+
const expiresAt = Date.now() + lifetimeMs;
|
|
1439
|
+
const timeout = AbortSignal.timeout(lifetimeMs);
|
|
1440
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
1441
|
+
options.onAuth({
|
|
1442
|
+
url: device.verification_uri_complete ?? device.verification_uri,
|
|
1443
|
+
verificationUri: device.verification_uri,
|
|
1444
|
+
userCode: device.user_code,
|
|
1445
|
+
expiresAt
|
|
1446
|
+
});
|
|
1447
|
+
let intervalMs = Math.max(positiveSeconds(device.interval, 5) * 1e3, 1e3);
|
|
1448
|
+
const sleep = dependencies.sleep ?? ((ms, abortSignal) => setTimeout$1(ms, void 0, { signal: abortSignal }));
|
|
1449
|
+
try {
|
|
1450
|
+
for (;;) {
|
|
1451
|
+
signal.throwIfAborted();
|
|
1452
|
+
const tokenResponse = await send("token", {
|
|
1453
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
1454
|
+
client_id: CLIENT_ID,
|
|
1455
|
+
device_code: device.device_code
|
|
1456
|
+
}, { signal }, dependencies);
|
|
1457
|
+
signal.throwIfAborted();
|
|
1458
|
+
if (tokenResponse.ok) {
|
|
1459
|
+
const tokens = tokenSchema.parse(await tokenResponse.json());
|
|
1460
|
+
signal.throwIfAborted();
|
|
1461
|
+
const credentials = tokenCredentials(tokens);
|
|
1462
|
+
const accountId = await getAccountId(tokens, signal, dependencies);
|
|
1463
|
+
signal.throwIfAborted();
|
|
1464
|
+
return {
|
|
1465
|
+
...credentials,
|
|
1466
|
+
accountId
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
const error = await readOAuthError(tokenResponse);
|
|
1470
|
+
if (error === "authorization_pending" || error === "slow_down") {
|
|
1471
|
+
if (error === "slow_down") intervalMs += 5e3;
|
|
1472
|
+
await sleep(intervalMs + POLLING_SAFETY_MARGIN_MS, signal);
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
if (error === "access_denied" || error === "authorization_denied") throw new XaiOAuthError("authorization_denied");
|
|
1476
|
+
if (error === "expired_token") throw new XaiOAuthError("authorization_expired");
|
|
1477
|
+
throw requestError(tokenResponse, error);
|
|
1478
|
+
}
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
options.signal?.throwIfAborted();
|
|
1481
|
+
if (timeout.aborted) throw new XaiOAuthError("authorization_expired");
|
|
1482
|
+
throw error;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
async function refreshXaiCredentials(credentials, options = {}, dependencies = {}) {
|
|
1486
|
+
const response = await send("token", {
|
|
1487
|
+
grant_type: "refresh_token",
|
|
1488
|
+
refresh_token: credentials.refreshToken,
|
|
1489
|
+
client_id: CLIENT_ID
|
|
1490
|
+
}, options, dependencies);
|
|
1491
|
+
if (!response.ok) throw requestError(response, await readOAuthError(response));
|
|
1492
|
+
const tokens = tokenSchema.parse(await response.json());
|
|
1493
|
+
const refreshedAccountId = tokenAccountId(tokens.id_token) ?? tokenAccountId(tokens.access_token);
|
|
1494
|
+
if (refreshedAccountId && refreshedAccountId !== credentials.accountId) throw new Error("xAI token refresh returned a different account");
|
|
1495
|
+
return {
|
|
1496
|
+
...tokenCredentials(tokens, credentials.refreshToken),
|
|
1497
|
+
accountId: credentials.accountId
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
function isXaiCredentialsExpired(credentials, now = Date.now()) {
|
|
1501
|
+
return credentials.expiresAt <= now + REFRESH_BUFFER_MS;
|
|
1502
|
+
}
|
|
1503
|
+
const CODEX_CREDENTIAL_LOCK_RETRY_MS = 10;
|
|
1504
|
+
const CODEX_CREDENTIAL_LOCK_TIMEOUT_MS = 1e4;
|
|
1505
|
+
const CODEX_CREDENTIAL_LOCK_STALE_MS = 6e4;
|
|
1506
|
+
function isNodeError(error) {
|
|
1507
|
+
return error instanceof Error && "code" in error;
|
|
1508
|
+
}
|
|
1509
|
+
function normalizeCodexAccountSelector(value) {
|
|
1510
|
+
return value.trim().toLowerCase();
|
|
1511
|
+
}
|
|
1512
|
+
function parseCodexCredentialLockMetadata(value) {
|
|
1513
|
+
try {
|
|
1514
|
+
const candidate = JSON.parse(value);
|
|
1515
|
+
if (typeof candidate.createdAt !== "number" || !Number.isFinite(candidate.createdAt) || typeof candidate.owner !== "string" || typeof candidate.pid !== "number" || !Number.isSafeInteger(candidate.pid) || candidate.pid <= 0) return null;
|
|
1516
|
+
return {
|
|
1517
|
+
createdAt: candidate.createdAt,
|
|
1518
|
+
owner: candidate.owner,
|
|
1519
|
+
pid: candidate.pid
|
|
1520
|
+
};
|
|
1521
|
+
} catch {
|
|
1522
|
+
return null;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
function isProcessAlive(pid) {
|
|
1526
|
+
try {
|
|
1527
|
+
process.kill(pid, 0);
|
|
1528
|
+
return true;
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
return !(isNodeError(error) && error.code === "ESRCH");
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
async function isCodexCredentialLockContention(error, lockPath) {
|
|
1534
|
+
if (isNodeError(error) && error.code === "EEXIST") return true;
|
|
1535
|
+
if (process.platform !== "win32" || !isNodeError(error) || error.code !== "EACCES" && error.code !== "EPERM") return false;
|
|
1536
|
+
try {
|
|
1537
|
+
await fs.stat(lockPath);
|
|
1538
|
+
return true;
|
|
1539
|
+
} catch (statError) {
|
|
1540
|
+
if (isNodeError(statError) && statError.code === "ENOENT") return true;
|
|
1541
|
+
return false;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
async function removeStaleCodexCredentialLock(lockPath) {
|
|
1545
|
+
let content;
|
|
1546
|
+
let modifiedAt;
|
|
1547
|
+
try {
|
|
1548
|
+
const [lockContent, stats] = await Promise.all([fs.readFile(lockPath, "utf8"), fs.stat(lockPath)]);
|
|
1549
|
+
content = lockContent;
|
|
1550
|
+
modifiedAt = stats.mtimeMs;
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
if (isNodeError(error) && error.code === "ENOENT" || await isCodexCredentialLockContention(error, lockPath)) return;
|
|
1553
|
+
throw error;
|
|
1554
|
+
}
|
|
1555
|
+
const metadata = parseCodexCredentialLockMetadata(content);
|
|
1556
|
+
if (Date.now() - (metadata?.createdAt ?? modifiedAt) < CODEX_CREDENTIAL_LOCK_STALE_MS && (!metadata || isProcessAlive(metadata.pid))) return;
|
|
1557
|
+
try {
|
|
1558
|
+
if (await fs.readFile(lockPath, "utf8") !== content) return;
|
|
1559
|
+
await fs.unlink(lockPath);
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
if (isNodeError(error) && error.code === "ENOENT" || await isCodexCredentialLockContention(error, lockPath)) return;
|
|
1562
|
+
throw error;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
async function acquireCodexCredentialLock(lockPath = `${PATHS.CODEX_CREDENTIAL_PATH}.lock`) {
|
|
1566
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
1567
|
+
const startedAt = Date.now();
|
|
1568
|
+
while (true) {
|
|
1569
|
+
const metadata = {
|
|
1570
|
+
createdAt: Date.now(),
|
|
1571
|
+
owner: `${process.pid}-${randomBytes(8).toString("hex")}`,
|
|
1572
|
+
pid: process.pid
|
|
1573
|
+
};
|
|
1574
|
+
const content = `${JSON.stringify(metadata)}\n`;
|
|
1575
|
+
try {
|
|
1576
|
+
const fileHandle = await fs.open(lockPath, "wx", 384);
|
|
1577
|
+
try {
|
|
1578
|
+
await fileHandle.writeFile(content, "utf8");
|
|
1579
|
+
await fileHandle.sync();
|
|
1580
|
+
} catch (error) {
|
|
1581
|
+
await fileHandle.close().catch(() => {});
|
|
1582
|
+
await fs.unlink(lockPath).catch(() => {});
|
|
1583
|
+
throw error;
|
|
1584
|
+
}
|
|
1585
|
+
return {
|
|
1586
|
+
content,
|
|
1587
|
+
fileHandle,
|
|
1588
|
+
lockPath
|
|
1589
|
+
};
|
|
1590
|
+
} catch (error) {
|
|
1591
|
+
if (!await isCodexCredentialLockContention(error, lockPath)) throw error;
|
|
1592
|
+
}
|
|
1593
|
+
await removeStaleCodexCredentialLock(lockPath);
|
|
1594
|
+
if (Date.now() - startedAt >= CODEX_CREDENTIAL_LOCK_TIMEOUT_MS) throw new Error(`Timed out waiting for Codex credential store lock: ${lockPath}`);
|
|
1595
|
+
await setTimeout$1(CODEX_CREDENTIAL_LOCK_RETRY_MS);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
async function releaseCodexCredentialLock(lock) {
|
|
1599
|
+
await lock.fileHandle.close();
|
|
1600
|
+
try {
|
|
1601
|
+
if (await fs.readFile(lock.lockPath, "utf8") !== lock.content) return;
|
|
1602
|
+
await fs.unlink(lock.lockPath);
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
if (!(isNodeError(error) && error.code === "ENOENT")) throw error;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
async function withCodexCredentialLock(operation, lockPath) {
|
|
1608
|
+
const lock = await acquireCodexCredentialLock(lockPath);
|
|
1609
|
+
let result;
|
|
1610
|
+
try {
|
|
1611
|
+
result = await operation();
|
|
1612
|
+
} catch (error) {
|
|
1613
|
+
await releaseCodexCredentialLock(lock).catch(() => {});
|
|
1614
|
+
throw error;
|
|
1615
|
+
}
|
|
1616
|
+
await releaseCodexCredentialLock(lock);
|
|
1617
|
+
return result;
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Serializes operations that must keep Codex account selection in config.json
|
|
1621
|
+
* consistent with the credential store. Credential writes retain their own
|
|
1622
|
+
* narrower lock so refreshes can run without changing the selected account.
|
|
1623
|
+
*/
|
|
1624
|
+
function withCodexAccountMutationLock(operation) {
|
|
1625
|
+
return withCodexCredentialLock(operation, `${PATHS.CODEX_CREDENTIAL_PATH}.accounts.lock`);
|
|
1626
|
+
}
|
|
1627
|
+
function withCredentialFileLock(credentialPath, operation) {
|
|
1628
|
+
return withCodexCredentialLock(operation, `${credentialPath}.lock`);
|
|
1629
|
+
}
|
|
1630
|
+
async function readOptionalFile(filePath) {
|
|
1631
|
+
try {
|
|
1632
|
+
return await fs.readFile(filePath, "utf8");
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
if (isNodeError(error) && error.code === "ENOENT") return null;
|
|
1635
|
+
throw error;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
function writeProtectedFile(filePath, content) {
|
|
1639
|
+
return Promise.resolve().then(() => writeFileAtomically(filePath, content));
|
|
1640
|
+
}
|
|
1641
|
+
function normalizeCodexCredentials(credentials) {
|
|
1642
|
+
if (!credentials || typeof credentials !== "object") return null;
|
|
1643
|
+
const candidate = credentials;
|
|
1644
|
+
if (typeof candidate.accessToken !== "string" || !candidate.accessToken.trim() || typeof candidate.refreshToken !== "string" || !candidate.refreshToken.trim() || typeof candidate.expiresAt !== "number" || !Number.isFinite(candidate.expiresAt) || typeof candidate.accountId !== "string" || !candidate.accountId.trim()) return null;
|
|
1645
|
+
return {
|
|
1646
|
+
accessToken: candidate.accessToken,
|
|
1647
|
+
refreshToken: candidate.refreshToken,
|
|
1648
|
+
expiresAt: candidate.expiresAt,
|
|
1649
|
+
accountId: candidate.accountId
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
function normalizeCodexStoredAccount(account) {
|
|
1653
|
+
const credentials = normalizeCodexCredentials(account);
|
|
1654
|
+
if (!credentials) return null;
|
|
1655
|
+
const aliasValue = account.alias;
|
|
1656
|
+
if (aliasValue !== void 0 && typeof aliasValue !== "string") return null;
|
|
1657
|
+
const alias = aliasValue?.trim();
|
|
1658
|
+
return {
|
|
1659
|
+
...credentials,
|
|
1660
|
+
...alias ? { alias } : {}
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
function normalizeCodexCredentialStore(value) {
|
|
1664
|
+
const legacyCredentials = normalizeCodexCredentials(value);
|
|
1665
|
+
if (legacyCredentials) return {
|
|
1666
|
+
version: 1,
|
|
1667
|
+
accounts: [legacyCredentials]
|
|
1668
|
+
};
|
|
1669
|
+
if (!value || typeof value !== "object") return null;
|
|
1670
|
+
const candidate = value;
|
|
1671
|
+
if (candidate.version !== 1 || !Array.isArray(candidate.accounts)) return null;
|
|
1672
|
+
if (candidate.accounts.length > 3) return null;
|
|
1673
|
+
const accounts = [];
|
|
1674
|
+
const accountIds = /* @__PURE__ */ new Set();
|
|
1675
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
1676
|
+
for (const value of candidate.accounts) {
|
|
1677
|
+
const account = normalizeCodexStoredAccount(value);
|
|
1678
|
+
if (!account || accountIds.has(account.accountId)) return null;
|
|
1679
|
+
const normalizedAlias = account.alias ? normalizeCodexAccountSelector(account.alias) : void 0;
|
|
1680
|
+
if (normalizedAlias && aliases.has(normalizedAlias)) return null;
|
|
1681
|
+
accounts.push(account);
|
|
1682
|
+
accountIds.add(account.accountId);
|
|
1683
|
+
if (normalizedAlias) aliases.add(normalizedAlias);
|
|
1684
|
+
}
|
|
1685
|
+
return {
|
|
1686
|
+
version: 1,
|
|
1687
|
+
accounts: accounts.map((account, index) => {
|
|
1688
|
+
const alias = account.alias;
|
|
1689
|
+
if (!alias || !accounts.some((candidate, candidateIndex) => candidateIndex !== index && normalizeCodexAccountSelector(candidate.accountId) === normalizeCodexAccountSelector(alias))) return account;
|
|
1690
|
+
const { alias: _alias, ...credentials } = account;
|
|
1691
|
+
return credentials;
|
|
1692
|
+
})
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
async function writeCodexCredentialStoreUnlocked(store, credentialPath = PATHS.CODEX_CREDENTIAL_PATH) {
|
|
1696
|
+
await writeProtectedFile(credentialPath, `${JSON.stringify(store, null, 2)}\n`);
|
|
1697
|
+
}
|
|
1698
|
+
async function readGitHubToken() {
|
|
1699
|
+
return (await readOptionalFile(PATHS.GITHUB_TOKEN_PATH))?.trim() || null;
|
|
1700
|
+
}
|
|
1701
|
+
const GITHUB_TOKEN_ENV = "COPILOT_API_GITHUB_TOKEN";
|
|
1702
|
+
function readGitHubTokenFromEnv() {
|
|
1703
|
+
return process.env["COPILOT_API_GITHUB_TOKEN"]?.trim() || void 0;
|
|
1704
|
+
}
|
|
1705
|
+
async function writeGitHubToken(token) {
|
|
1706
|
+
await writeProtectedFile(PATHS.GITHUB_TOKEN_PATH, token.trim());
|
|
1707
|
+
}
|
|
1708
|
+
function createOAuthCredentialStore(providerLabel, credentialPath) {
|
|
1709
|
+
async function readStore() {
|
|
1710
|
+
const raw = await readOptionalFile(credentialPath());
|
|
1711
|
+
if (!raw?.trim()) return null;
|
|
1712
|
+
let parsed;
|
|
1713
|
+
try {
|
|
1714
|
+
parsed = JSON.parse(raw);
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
throw new Error(`${providerLabel} credentials file is not valid JSON: ${credentialPath()}`, { cause: error });
|
|
1717
|
+
}
|
|
1718
|
+
const store = normalizeCodexCredentialStore(parsed);
|
|
1719
|
+
if (!store) throw new Error(`${providerLabel} credentials file is missing required fields: ${credentialPath()}`);
|
|
1720
|
+
return store;
|
|
1721
|
+
}
|
|
1722
|
+
async function readCredentials(accountId) {
|
|
1723
|
+
const store = await readStore();
|
|
1724
|
+
if (!store || store.accounts.length === 0) return null;
|
|
1725
|
+
const normalizedAccountId = accountId?.trim();
|
|
1726
|
+
if (normalizedAccountId) return store.accounts.find((account) => account.accountId === normalizedAccountId) ?? null;
|
|
1727
|
+
if (store.accounts.length > 1) throw new Error(`Multiple ${providerLabel} accounts found but no account is selected`);
|
|
1728
|
+
return store.accounts[0];
|
|
1729
|
+
}
|
|
1730
|
+
async function writeCredentials(credentials, options = {}) {
|
|
1731
|
+
const normalizedCredentials = normalizeCodexCredentials(credentials);
|
|
1732
|
+
if (!normalizedCredentials) throw new Error(`${providerLabel} credentials are missing required fields`);
|
|
1733
|
+
await withCredentialFileLock(credentialPath(), async () => {
|
|
1734
|
+
const store = await readStore() ?? {
|
|
1735
|
+
version: 1,
|
|
1736
|
+
accounts: []
|
|
1737
|
+
};
|
|
1738
|
+
const accountIdSelector = normalizeCodexAccountSelector(normalizedCredentials.accountId);
|
|
1739
|
+
const existingIndex = store.accounts.findIndex((account) => account.accountId === normalizedCredentials.accountId);
|
|
1740
|
+
if (existingIndex < 0 && options.insertIfMissing === false) return;
|
|
1741
|
+
const existingRefreshToken = store.accounts[existingIndex]?.refreshToken;
|
|
1742
|
+
if (options.expectedRefreshToken !== void 0 && existingRefreshToken !== options.expectedRefreshToken && existingRefreshToken !== normalizedCredentials.refreshToken) return;
|
|
1743
|
+
if (existingIndex < 0 && store.accounts.some((account) => normalizeCodexAccountSelector(account.accountId) === accountIdSelector)) throw new Error(`${providerLabel} account id '${normalizedCredentials.accountId}' is already in use`);
|
|
1744
|
+
if (existingIndex < 0 && store.accounts.length >= 3) throw new Error(`${providerLabel} supports at most 3 accounts`);
|
|
1745
|
+
if (store.accounts.some((account, index) => index !== existingIndex && account.alias && normalizeCodexAccountSelector(account.alias) === accountIdSelector)) throw new Error(`${providerLabel} account id '${normalizedCredentials.accountId}' conflicts with another account alias`);
|
|
1746
|
+
const alias = options.alias?.trim();
|
|
1747
|
+
if (alias) {
|
|
1748
|
+
const aliasSelector = normalizeCodexAccountSelector(alias);
|
|
1749
|
+
if (store.accounts.some((account, index) => index !== existingIndex && account.alias && normalizeCodexAccountSelector(account.alias) === aliasSelector)) throw new Error(`${providerLabel} account alias '${alias}' is already in use`);
|
|
1750
|
+
if (store.accounts.some((account, index) => index !== existingIndex && normalizeCodexAccountSelector(account.accountId) === aliasSelector)) throw new Error(`${providerLabel} account alias '${alias}' conflicts with another account id`);
|
|
1751
|
+
}
|
|
1752
|
+
const existingAccount = existingIndex >= 0 ? store.accounts[existingIndex] : void 0;
|
|
1753
|
+
const nextAccount = {
|
|
1754
|
+
...normalizedCredentials,
|
|
1755
|
+
...alias ? { alias } : existingAccount?.alias ? { alias: existingAccount.alias } : {}
|
|
1756
|
+
};
|
|
1757
|
+
const accounts = [...store.accounts];
|
|
1758
|
+
if (existingIndex >= 0) accounts[existingIndex] = nextAccount;
|
|
1759
|
+
else accounts.push(nextAccount);
|
|
1760
|
+
await writeCodexCredentialStoreUnlocked({
|
|
1761
|
+
version: 1,
|
|
1762
|
+
accounts
|
|
1763
|
+
}, credentialPath());
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
async function removeCredentials(accountId) {
|
|
1767
|
+
const normalizedAccountId = accountId.trim();
|
|
1768
|
+
if (!normalizedAccountId) throw new Error(`${providerLabel} account id must be a non-empty string`);
|
|
1769
|
+
return await withCredentialFileLock(credentialPath(), async () => {
|
|
1770
|
+
const store = await readStore();
|
|
1771
|
+
const index = store?.accounts.findIndex((account) => account.accountId === normalizedAccountId) ?? -1;
|
|
1772
|
+
if (!store || index < 0) throw new Error(`${providerLabel} account '${normalizedAccountId}' was not found`);
|
|
1773
|
+
const removedAccount = store.accounts[index];
|
|
1774
|
+
await writeCodexCredentialStoreUnlocked({
|
|
1775
|
+
version: 1,
|
|
1776
|
+
accounts: store.accounts.filter((_account, accountIndex) => accountIndex !== index)
|
|
1777
|
+
}, credentialPath());
|
|
1778
|
+
return removedAccount;
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
return {
|
|
1782
|
+
readStore,
|
|
1783
|
+
readCredentials,
|
|
1784
|
+
writeCredentials,
|
|
1785
|
+
removeCredentials
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
const codexCredentialStore = createOAuthCredentialStore("Codex", () => PATHS.CODEX_CREDENTIAL_PATH);
|
|
1789
|
+
const readCodexCredentialStore = codexCredentialStore.readStore;
|
|
1790
|
+
codexCredentialStore.readCredentials;
|
|
1791
|
+
const writeCodexCredentials = codexCredentialStore.writeCredentials;
|
|
1792
|
+
codexCredentialStore.removeCredentials;
|
|
1793
|
+
//#endregion
|
|
1794
|
+
//#region src/lib/oauth-accounts.ts
|
|
1795
|
+
function createOAuthAccountManager(options) {
|
|
1796
|
+
const store = createOAuthCredentialStore(options.label, options.credentialPath);
|
|
1797
|
+
const withMutationLock = (operation) => withCredentialFileLock(`${options.credentialPath()}.accounts`, operation);
|
|
1798
|
+
const activeId = (accounts) => readEditableConfigFromDisk().providers?.[options.provider]?.accountId?.trim() || (accounts.length === 1 ? accounts[0].accountId : void 0);
|
|
1799
|
+
const summarize = (account, active) => ({
|
|
1800
|
+
accountId: account.accountId,
|
|
1801
|
+
...account.alias ? { alias: account.alias } : {},
|
|
1802
|
+
active
|
|
1803
|
+
});
|
|
1804
|
+
async function findAccount(selector) {
|
|
1805
|
+
const normalized = selector.trim();
|
|
1806
|
+
if (!normalized) throw new Error(`${options.label} account selector must be a non-empty string`);
|
|
1807
|
+
const accounts = (await store.readStore())?.accounts ?? [];
|
|
1808
|
+
const account = accounts.find((candidate) => candidate.accountId === normalized) ?? accounts.find((candidate) => candidate.alias?.toLowerCase() === normalized.toLowerCase());
|
|
1809
|
+
if (!account) throw new Error(`${options.label} account '${normalized}' was not found`);
|
|
1810
|
+
return {
|
|
1811
|
+
account,
|
|
1812
|
+
accounts
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
return {
|
|
1816
|
+
store,
|
|
1817
|
+
withMutationLock,
|
|
1818
|
+
list: () => withMutationLock(async () => {
|
|
1819
|
+
const accounts = (await store.readStore())?.accounts ?? [];
|
|
1820
|
+
const active = activeId(accounts);
|
|
1821
|
+
return accounts.map((account) => summarize(account, account.accountId === active));
|
|
1822
|
+
}),
|
|
1823
|
+
select: (selector) => withMutationLock(async () => {
|
|
1824
|
+
const { account } = await findAccount(selector);
|
|
1825
|
+
options.selectAccount(account.accountId);
|
|
1826
|
+
return summarize(account, true);
|
|
1827
|
+
}),
|
|
1828
|
+
remove: (selector) => withMutationLock(async () => {
|
|
1829
|
+
const { account, accounts } = await findAccount(selector);
|
|
1830
|
+
if (account.accountId === activeId(accounts)) throw new Error(`${options.label} account '${account.accountId}' is currently in use; switch to another account before removing it`);
|
|
1831
|
+
const removed = await store.removeCredentials(account.accountId);
|
|
1832
|
+
return summarize(removed, false);
|
|
1833
|
+
})
|
|
1284
1834
|
};
|
|
1285
1835
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1836
|
+
//#endregion
|
|
1837
|
+
//#region src/lib/xai-token.ts
|
|
1838
|
+
function syncXaiProviderConfig(accountId, enable = false) {
|
|
1839
|
+
const { apiKey: _apiKey, modelsDevProviderId: _catalog, ...existing } = readEditableConfigFromDisk().providers?.xai ?? {};
|
|
1840
|
+
setProviderConfig("xai", {
|
|
1841
|
+
...existing,
|
|
1842
|
+
type: "openai-responses",
|
|
1843
|
+
enabled: enable ? true : existing.enabled,
|
|
1844
|
+
baseUrl: XAI_API_BASE_URL,
|
|
1845
|
+
authType: "oauth2",
|
|
1846
|
+
accountId,
|
|
1847
|
+
pricingCurrency: "USD"
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
const xaiAccounts = createOAuthAccountManager({
|
|
1851
|
+
provider: "xai",
|
|
1852
|
+
label: "xAI",
|
|
1853
|
+
credentialPath: () => PATHS.XAI_CREDENTIAL_PATH,
|
|
1854
|
+
selectAccount: (accountId) => syncXaiProviderConfig(accountId)
|
|
1855
|
+
});
|
|
1856
|
+
const getXaiAccounts = xaiAccounts.list;
|
|
1857
|
+
const selectXaiAccount = xaiAccounts.select;
|
|
1858
|
+
const removeXaiAccount = xaiAccounts.remove;
|
|
1859
|
+
async function readXaiCredentials(accountId = getRawProviderConfig("xai")?.accountId) {
|
|
1860
|
+
return await xaiAccounts.store.readCredentials(accountId);
|
|
1861
|
+
}
|
|
1862
|
+
function persistXaiCredentials(credentials, options = {}) {
|
|
1863
|
+
return xaiAccounts.withMutationLock(async () => {
|
|
1864
|
+
await xaiAccounts.store.writeCredentials(credentials, { alias: options.alias });
|
|
1865
|
+
syncXaiProviderConfig(credentials.accountId, true);
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
const refreshInFlight = /* @__PURE__ */ new Map();
|
|
1869
|
+
const pendingPersistence = /* @__PURE__ */ new Map();
|
|
1870
|
+
async function getXaiAccessToken() {
|
|
1871
|
+
const credentials = await readXaiCredentials();
|
|
1872
|
+
if (!credentials || !isXaiCredentialsExpired(credentials)) return credentials?.accessToken ?? null;
|
|
1873
|
+
const key = `${credentials.accountId}:${credentials.refreshToken}`;
|
|
1874
|
+
const inFlight = refreshInFlight.get(key);
|
|
1875
|
+
if (inFlight) return await inFlight;
|
|
1876
|
+
const refresh = withCredentialFileLock(`${PATHS.XAI_CREDENTIAL_PATH}.refresh`, async () => {
|
|
1877
|
+
const current = await readXaiCredentials(credentials.accountId);
|
|
1878
|
+
if (!current) return null;
|
|
1879
|
+
if (!isXaiCredentialsExpired(current)) return current.accessToken;
|
|
1880
|
+
const currentKey = `${current.accountId}:${current.refreshToken}`;
|
|
1881
|
+
const refreshed = pendingPersistence.get(currentKey) ?? await refreshXaiCredentials(current);
|
|
1882
|
+
pendingPersistence.set(currentKey, refreshed);
|
|
1883
|
+
await xaiAccounts.store.writeCredentials(refreshed, {
|
|
1884
|
+
insertIfMissing: false,
|
|
1885
|
+
expectedRefreshToken: current.refreshToken
|
|
1886
|
+
});
|
|
1887
|
+
pendingPersistence.delete(currentKey);
|
|
1888
|
+
return (await readXaiCredentials(credentials.accountId))?.accessToken ?? null;
|
|
1889
|
+
}).finally(() => {
|
|
1890
|
+
refreshInFlight.delete(key);
|
|
1891
|
+
});
|
|
1892
|
+
refreshInFlight.set(key, refresh);
|
|
1893
|
+
return await refresh;
|
|
1288
1894
|
}
|
|
1289
1895
|
//#endregion
|
|
1290
1896
|
//#region src/lib/request-auth.ts
|
|
@@ -1611,272 +2217,6 @@ const GITHUB_BASE_URL = "https://github.com";
|
|
|
1611
2217
|
const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
|
1612
2218
|
const GITHUB_APP_SCOPES = ["read:user"].join(" ");
|
|
1613
2219
|
const OPENCODE_GITHUB_CLIENT_ID = "Ov23li8tweQw6odWQebz";
|
|
1614
|
-
const CODEX_CREDENTIAL_LOCK_RETRY_MS = 10;
|
|
1615
|
-
const CODEX_CREDENTIAL_LOCK_TIMEOUT_MS = 1e4;
|
|
1616
|
-
const CODEX_CREDENTIAL_LOCK_STALE_MS = 6e4;
|
|
1617
|
-
function isNodeError(error) {
|
|
1618
|
-
return error instanceof Error && "code" in error;
|
|
1619
|
-
}
|
|
1620
|
-
function normalizeCodexAccountSelector(value) {
|
|
1621
|
-
return value.trim().toLowerCase();
|
|
1622
|
-
}
|
|
1623
|
-
function parseCodexCredentialLockMetadata(value) {
|
|
1624
|
-
try {
|
|
1625
|
-
const candidate = JSON.parse(value);
|
|
1626
|
-
if (typeof candidate.createdAt !== "number" || !Number.isFinite(candidate.createdAt) || typeof candidate.owner !== "string" || typeof candidate.pid !== "number" || !Number.isSafeInteger(candidate.pid) || candidate.pid <= 0) return null;
|
|
1627
|
-
return {
|
|
1628
|
-
createdAt: candidate.createdAt,
|
|
1629
|
-
owner: candidate.owner,
|
|
1630
|
-
pid: candidate.pid
|
|
1631
|
-
};
|
|
1632
|
-
} catch {
|
|
1633
|
-
return null;
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
function isProcessAlive(pid) {
|
|
1637
|
-
try {
|
|
1638
|
-
process.kill(pid, 0);
|
|
1639
|
-
return true;
|
|
1640
|
-
} catch (error) {
|
|
1641
|
-
return !(isNodeError(error) && error.code === "ESRCH");
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
async function isCodexCredentialLockContention(error, lockPath) {
|
|
1645
|
-
if (isNodeError(error) && error.code === "EEXIST") return true;
|
|
1646
|
-
if (process.platform !== "win32" || !isNodeError(error) || error.code !== "EACCES" && error.code !== "EPERM") return false;
|
|
1647
|
-
try {
|
|
1648
|
-
await fs.stat(lockPath);
|
|
1649
|
-
return true;
|
|
1650
|
-
} catch (statError) {
|
|
1651
|
-
if (isNodeError(statError) && statError.code === "ENOENT") return true;
|
|
1652
|
-
return false;
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
async function removeStaleCodexCredentialLock(lockPath) {
|
|
1656
|
-
let content;
|
|
1657
|
-
let modifiedAt;
|
|
1658
|
-
try {
|
|
1659
|
-
const [lockContent, stats] = await Promise.all([fs.readFile(lockPath, "utf8"), fs.stat(lockPath)]);
|
|
1660
|
-
content = lockContent;
|
|
1661
|
-
modifiedAt = stats.mtimeMs;
|
|
1662
|
-
} catch (error) {
|
|
1663
|
-
if (isNodeError(error) && error.code === "ENOENT") return;
|
|
1664
|
-
throw error;
|
|
1665
|
-
}
|
|
1666
|
-
const metadata = parseCodexCredentialLockMetadata(content);
|
|
1667
|
-
if (Date.now() - (metadata?.createdAt ?? modifiedAt) < CODEX_CREDENTIAL_LOCK_STALE_MS && (!metadata || isProcessAlive(metadata.pid))) return;
|
|
1668
|
-
try {
|
|
1669
|
-
if (await fs.readFile(lockPath, "utf8") !== content) return;
|
|
1670
|
-
await fs.unlink(lockPath);
|
|
1671
|
-
} catch (error) {
|
|
1672
|
-
if (isNodeError(error) && error.code === "ENOENT") return;
|
|
1673
|
-
throw error;
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
async function acquireCodexCredentialLock(lockPath = `${PATHS.CODEX_CREDENTIAL_PATH}.lock`) {
|
|
1677
|
-
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
1678
|
-
const startedAt = Date.now();
|
|
1679
|
-
while (true) {
|
|
1680
|
-
const metadata = {
|
|
1681
|
-
createdAt: Date.now(),
|
|
1682
|
-
owner: `${process.pid}-${randomBytes(8).toString("hex")}`,
|
|
1683
|
-
pid: process.pid
|
|
1684
|
-
};
|
|
1685
|
-
const content = `${JSON.stringify(metadata)}\n`;
|
|
1686
|
-
try {
|
|
1687
|
-
const fileHandle = await fs.open(lockPath, "wx", 384);
|
|
1688
|
-
try {
|
|
1689
|
-
await fileHandle.writeFile(content, "utf8");
|
|
1690
|
-
await fileHandle.sync();
|
|
1691
|
-
} catch (error) {
|
|
1692
|
-
await fileHandle.close().catch(() => {});
|
|
1693
|
-
await fs.unlink(lockPath).catch(() => {});
|
|
1694
|
-
throw error;
|
|
1695
|
-
}
|
|
1696
|
-
return {
|
|
1697
|
-
content,
|
|
1698
|
-
fileHandle,
|
|
1699
|
-
lockPath
|
|
1700
|
-
};
|
|
1701
|
-
} catch (error) {
|
|
1702
|
-
if (!await isCodexCredentialLockContention(error, lockPath)) throw error;
|
|
1703
|
-
}
|
|
1704
|
-
await removeStaleCodexCredentialLock(lockPath);
|
|
1705
|
-
if (Date.now() - startedAt >= CODEX_CREDENTIAL_LOCK_TIMEOUT_MS) throw new Error(`Timed out waiting for Codex credential store lock: ${lockPath}`);
|
|
1706
|
-
await setTimeout$1(CODEX_CREDENTIAL_LOCK_RETRY_MS);
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
async function releaseCodexCredentialLock(lock) {
|
|
1710
|
-
await lock.fileHandle.close();
|
|
1711
|
-
try {
|
|
1712
|
-
if (await fs.readFile(lock.lockPath, "utf8") !== lock.content) return;
|
|
1713
|
-
await fs.unlink(lock.lockPath);
|
|
1714
|
-
} catch (error) {
|
|
1715
|
-
if (!(isNodeError(error) && error.code === "ENOENT")) throw error;
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
async function withCodexCredentialLock(operation, lockPath) {
|
|
1719
|
-
const lock = await acquireCodexCredentialLock(lockPath);
|
|
1720
|
-
let result;
|
|
1721
|
-
try {
|
|
1722
|
-
result = await operation();
|
|
1723
|
-
} catch (error) {
|
|
1724
|
-
await releaseCodexCredentialLock(lock).catch(() => {});
|
|
1725
|
-
throw error;
|
|
1726
|
-
}
|
|
1727
|
-
await releaseCodexCredentialLock(lock);
|
|
1728
|
-
return result;
|
|
1729
|
-
}
|
|
1730
|
-
/**
|
|
1731
|
-
* Serializes operations that must keep Codex account selection in config.json
|
|
1732
|
-
* consistent with the credential store. Credential writes retain their own
|
|
1733
|
-
* narrower lock so refreshes can run without changing the selected account.
|
|
1734
|
-
*/
|
|
1735
|
-
function withCodexAccountMutationLock(operation) {
|
|
1736
|
-
return withCodexCredentialLock(operation, `${PATHS.CODEX_CREDENTIAL_PATH}.accounts.lock`);
|
|
1737
|
-
}
|
|
1738
|
-
async function readOptionalFile(filePath) {
|
|
1739
|
-
try {
|
|
1740
|
-
return await fs.readFile(filePath, "utf8");
|
|
1741
|
-
} catch (error) {
|
|
1742
|
-
if (isNodeError(error) && error.code === "ENOENT") return null;
|
|
1743
|
-
throw error;
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
function writeProtectedFile(filePath, content) {
|
|
1747
|
-
return Promise.resolve().then(() => writeFileAtomically(filePath, content));
|
|
1748
|
-
}
|
|
1749
|
-
function normalizeCodexCredentials(credentials) {
|
|
1750
|
-
if (!credentials || typeof credentials !== "object") return null;
|
|
1751
|
-
const candidate = credentials;
|
|
1752
|
-
if (typeof candidate.accessToken !== "string" || typeof candidate.refreshToken !== "string" || typeof candidate.expiresAt !== "number" || typeof candidate.accountId !== "string") return null;
|
|
1753
|
-
return {
|
|
1754
|
-
accessToken: candidate.accessToken,
|
|
1755
|
-
refreshToken: candidate.refreshToken,
|
|
1756
|
-
expiresAt: candidate.expiresAt,
|
|
1757
|
-
accountId: candidate.accountId
|
|
1758
|
-
};
|
|
1759
|
-
}
|
|
1760
|
-
function normalizeCodexStoredAccount(account) {
|
|
1761
|
-
const credentials = normalizeCodexCredentials(account);
|
|
1762
|
-
if (!credentials) return null;
|
|
1763
|
-
const aliasValue = account.alias;
|
|
1764
|
-
if (aliasValue !== void 0 && typeof aliasValue !== "string") return null;
|
|
1765
|
-
const alias = aliasValue?.trim();
|
|
1766
|
-
return {
|
|
1767
|
-
...credentials,
|
|
1768
|
-
...alias ? { alias } : {}
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
function normalizeCodexCredentialStore(value) {
|
|
1772
|
-
const legacyCredentials = normalizeCodexCredentials(value);
|
|
1773
|
-
if (legacyCredentials) return {
|
|
1774
|
-
version: 1,
|
|
1775
|
-
accounts: [legacyCredentials]
|
|
1776
|
-
};
|
|
1777
|
-
if (!value || typeof value !== "object") return null;
|
|
1778
|
-
const candidate = value;
|
|
1779
|
-
if (candidate.version !== 1 || !Array.isArray(candidate.accounts)) return null;
|
|
1780
|
-
if (candidate.accounts.length > 3) return null;
|
|
1781
|
-
const accounts = [];
|
|
1782
|
-
const accountIds = /* @__PURE__ */ new Set();
|
|
1783
|
-
const aliases = /* @__PURE__ */ new Set();
|
|
1784
|
-
for (const value of candidate.accounts) {
|
|
1785
|
-
const account = normalizeCodexStoredAccount(value);
|
|
1786
|
-
if (!account || accountIds.has(account.accountId)) return null;
|
|
1787
|
-
const normalizedAlias = account.alias ? normalizeCodexAccountSelector(account.alias) : void 0;
|
|
1788
|
-
if (normalizedAlias && aliases.has(normalizedAlias)) return null;
|
|
1789
|
-
accounts.push(account);
|
|
1790
|
-
accountIds.add(account.accountId);
|
|
1791
|
-
if (normalizedAlias) aliases.add(normalizedAlias);
|
|
1792
|
-
}
|
|
1793
|
-
return {
|
|
1794
|
-
version: 1,
|
|
1795
|
-
accounts: accounts.map((account, index) => {
|
|
1796
|
-
const alias = account.alias;
|
|
1797
|
-
if (!alias || !accounts.some((candidate, candidateIndex) => candidateIndex !== index && normalizeCodexAccountSelector(candidate.accountId) === normalizeCodexAccountSelector(alias))) return account;
|
|
1798
|
-
const { alias: _alias, ...credentials } = account;
|
|
1799
|
-
return credentials;
|
|
1800
|
-
})
|
|
1801
|
-
};
|
|
1802
|
-
}
|
|
1803
|
-
async function writeCodexCredentialStoreUnlocked(store) {
|
|
1804
|
-
await writeProtectedFile(PATHS.CODEX_CREDENTIAL_PATH, `${JSON.stringify(store, null, 2)}\n`);
|
|
1805
|
-
}
|
|
1806
|
-
async function readGitHubToken() {
|
|
1807
|
-
return (await readOptionalFile(PATHS.GITHUB_TOKEN_PATH))?.trim() || null;
|
|
1808
|
-
}
|
|
1809
|
-
const GITHUB_TOKEN_ENV = "COPILOT_API_GITHUB_TOKEN";
|
|
1810
|
-
function readGitHubTokenFromEnv() {
|
|
1811
|
-
return process.env["COPILOT_API_GITHUB_TOKEN"]?.trim() || void 0;
|
|
1812
|
-
}
|
|
1813
|
-
async function writeGitHubToken(token) {
|
|
1814
|
-
await writeProtectedFile(PATHS.GITHUB_TOKEN_PATH, token.trim());
|
|
1815
|
-
}
|
|
1816
|
-
async function readCodexCredentialStore() {
|
|
1817
|
-
const raw = await readOptionalFile(PATHS.CODEX_CREDENTIAL_PATH);
|
|
1818
|
-
if (!raw?.trim()) return null;
|
|
1819
|
-
let parsed;
|
|
1820
|
-
try {
|
|
1821
|
-
parsed = JSON.parse(raw);
|
|
1822
|
-
} catch (error) {
|
|
1823
|
-
throw new Error(`Codex credentials file is not valid JSON: ${PATHS.CODEX_CREDENTIAL_PATH}`, { cause: error });
|
|
1824
|
-
}
|
|
1825
|
-
const store = normalizeCodexCredentialStore(parsed);
|
|
1826
|
-
if (!store) throw new Error(`Codex credentials file is missing required fields: ${PATHS.CODEX_CREDENTIAL_PATH}`);
|
|
1827
|
-
return store;
|
|
1828
|
-
}
|
|
1829
|
-
async function writeCodexCredentials(credentials, options = {}) {
|
|
1830
|
-
const normalizedCredentials = normalizeCodexCredentials(credentials);
|
|
1831
|
-
if (!normalizedCredentials) throw new Error("Codex credentials are missing required fields");
|
|
1832
|
-
await withCodexCredentialLock(async () => {
|
|
1833
|
-
const store = await readCodexCredentialStore() ?? {
|
|
1834
|
-
version: 1,
|
|
1835
|
-
accounts: []
|
|
1836
|
-
};
|
|
1837
|
-
const accountIdSelector = normalizeCodexAccountSelector(normalizedCredentials.accountId);
|
|
1838
|
-
const existingIndex = store.accounts.findIndex((account) => account.accountId === normalizedCredentials.accountId);
|
|
1839
|
-
if (existingIndex < 0 && options.insertIfMissing === false) return;
|
|
1840
|
-
const existingRefreshToken = store.accounts[existingIndex]?.refreshToken;
|
|
1841
|
-
if (options.expectedRefreshToken !== void 0 && existingRefreshToken !== options.expectedRefreshToken && existingRefreshToken !== normalizedCredentials.refreshToken) return;
|
|
1842
|
-
if (existingIndex < 0 && store.accounts.some((account) => normalizeCodexAccountSelector(account.accountId) === accountIdSelector)) throw new Error(`Codex account id '${normalizedCredentials.accountId}' is already in use`);
|
|
1843
|
-
if (existingIndex < 0 && store.accounts.length >= 3) throw new Error(`Codex supports at most 3 accounts`);
|
|
1844
|
-
if (store.accounts.some((account, index) => index !== existingIndex && account.alias && normalizeCodexAccountSelector(account.alias) === accountIdSelector)) throw new Error(`Codex account id '${normalizedCredentials.accountId}' conflicts with another account alias`);
|
|
1845
|
-
const alias = options.alias?.trim();
|
|
1846
|
-
if (alias) {
|
|
1847
|
-
const aliasSelector = normalizeCodexAccountSelector(alias);
|
|
1848
|
-
if (store.accounts.some((account, index) => index !== existingIndex && account.alias && normalizeCodexAccountSelector(account.alias) === aliasSelector)) throw new Error(`Codex account alias '${alias}' is already in use`);
|
|
1849
|
-
if (store.accounts.some((account, index) => index !== existingIndex && normalizeCodexAccountSelector(account.accountId) === aliasSelector)) throw new Error(`Codex account alias '${alias}' conflicts with another account id`);
|
|
1850
|
-
}
|
|
1851
|
-
const existingAccount = existingIndex >= 0 ? store.accounts[existingIndex] : void 0;
|
|
1852
|
-
const nextAccount = {
|
|
1853
|
-
...normalizedCredentials,
|
|
1854
|
-
...alias ? { alias } : existingAccount?.alias ? { alias: existingAccount.alias } : {}
|
|
1855
|
-
};
|
|
1856
|
-
const accounts = [...store.accounts];
|
|
1857
|
-
if (existingIndex >= 0) accounts[existingIndex] = nextAccount;
|
|
1858
|
-
else accounts.push(nextAccount);
|
|
1859
|
-
await writeCodexCredentialStoreUnlocked({
|
|
1860
|
-
version: 1,
|
|
1861
|
-
accounts
|
|
1862
|
-
});
|
|
1863
|
-
});
|
|
1864
|
-
}
|
|
1865
|
-
async function removeCodexCredentials(accountId) {
|
|
1866
|
-
const normalizedAccountId = accountId.trim();
|
|
1867
|
-
if (!normalizedAccountId) throw new Error("Codex account id must be a non-empty string");
|
|
1868
|
-
return await withCodexCredentialLock(async () => {
|
|
1869
|
-
const store = await readCodexCredentialStore();
|
|
1870
|
-
const index = store?.accounts.findIndex((account) => account.accountId === normalizedAccountId) ?? -1;
|
|
1871
|
-
if (!store || index < 0) throw new Error(`Codex account '${normalizedAccountId}' was not found`);
|
|
1872
|
-
const removedAccount = store.accounts[index];
|
|
1873
|
-
await writeCodexCredentialStoreUnlocked({
|
|
1874
|
-
version: 1,
|
|
1875
|
-
accounts: store.accounts.filter((_account, accountIndex) => accountIndex !== index)
|
|
1876
|
-
});
|
|
1877
|
-
return removedAccount;
|
|
1878
|
-
});
|
|
1879
|
-
}
|
|
1880
2220
|
//#endregion
|
|
1881
2221
|
//#region src/services/github/get-copilot-token.ts
|
|
1882
2222
|
const getCopilotToken = async () => {
|
|
@@ -2136,53 +2476,15 @@ function syncCodexProviderConfig(options) {
|
|
|
2136
2476
|
function getConfiguredCodexAccountId() {
|
|
2137
2477
|
return getRawProviderConfig("codex")?.accountId?.trim() || void 0;
|
|
2138
2478
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
return normalizedSelector;
|
|
2149
|
-
}
|
|
2150
|
-
function findCodexAccount(accounts, selector) {
|
|
2151
|
-
return accounts.find((candidate) => candidate.accountId === selector) ?? accounts.find((candidate) => candidate.alias?.toLowerCase() === selector.toLowerCase());
|
|
2152
|
-
}
|
|
2153
|
-
function toCodexAccountSummary(account, active) {
|
|
2154
|
-
return {
|
|
2155
|
-
accountId: account.accountId,
|
|
2156
|
-
...account.alias ? { alias: account.alias } : {},
|
|
2157
|
-
active
|
|
2158
|
-
};
|
|
2159
|
-
}
|
|
2160
|
-
async function getCodexAccounts() {
|
|
2161
|
-
return await withCodexAccountMutationLock(async () => {
|
|
2162
|
-
const accounts = (await readCodexCredentialStore())?.accounts ?? [];
|
|
2163
|
-
const activeAccountId = resolveActiveCodexAccountId(accounts, getPersistedCodexAccountId());
|
|
2164
|
-
return accounts.map((account) => toCodexAccountSummary(account, account.accountId === activeAccountId));
|
|
2165
|
-
});
|
|
2166
|
-
}
|
|
2167
|
-
async function selectCodexAccount(selector) {
|
|
2168
|
-
return await withCodexAccountMutationLock(async () => {
|
|
2169
|
-
const normalizedSelector = normalizeCodexSelector(selector);
|
|
2170
|
-
const account = findCodexAccount((await readCodexCredentialStore())?.accounts ?? [], normalizedSelector);
|
|
2171
|
-
if (!account) throw new Error(`Codex account '${normalizedSelector}' was not found`);
|
|
2172
|
-
syncCodexProviderConfig({ accountId: account.accountId });
|
|
2173
|
-
return toCodexAccountSummary(account, true);
|
|
2174
|
-
});
|
|
2175
|
-
}
|
|
2176
|
-
async function removeCodexAccount(selector) {
|
|
2177
|
-
return await withCodexAccountMutationLock(async () => {
|
|
2178
|
-
const normalizedSelector = normalizeCodexSelector(selector);
|
|
2179
|
-
const accounts = (await readCodexCredentialStore())?.accounts ?? [];
|
|
2180
|
-
const account = findCodexAccount(accounts, normalizedSelector);
|
|
2181
|
-
if (!account) throw new Error(`Codex account '${normalizedSelector}' was not found`);
|
|
2182
|
-
if (account.accountId === resolveActiveCodexAccountId(accounts, getPersistedCodexAccountId())) throw new Error(`Codex account '${account.accountId}' is currently in use; switch to another account before removing it`);
|
|
2183
|
-
return toCodexAccountSummary(await removeCodexCredentials(account.accountId), false);
|
|
2184
|
-
});
|
|
2185
|
-
}
|
|
2479
|
+
const codexAccounts = createOAuthAccountManager({
|
|
2480
|
+
provider: "codex",
|
|
2481
|
+
label: "Codex",
|
|
2482
|
+
credentialPath: () => PATHS.CODEX_CREDENTIAL_PATH,
|
|
2483
|
+
selectAccount: (accountId) => syncCodexProviderConfig({ accountId })
|
|
2484
|
+
});
|
|
2485
|
+
const getCodexAccounts = codexAccounts.list;
|
|
2486
|
+
const selectCodexAccount = codexAccounts.select;
|
|
2487
|
+
const removeCodexAccount = codexAccounts.remove;
|
|
2186
2488
|
async function persistCodexCredentials(credentials, options = {}) {
|
|
2187
2489
|
const generation = options.runtimeGeneration ?? codexRuntimeGeneration;
|
|
2188
2490
|
const persist = async () => {
|
|
@@ -2438,6 +2740,6 @@ async function logUser(dependencies = defaultCopilotTokenDependencies) {
|
|
|
2438
2740
|
state.tokenBasedBilling = copilotUser.token_based_billing;
|
|
2439
2741
|
}
|
|
2440
2742
|
//#endregion
|
|
2441
|
-
export {
|
|
2743
|
+
export { createResponsesHttpEventStream as $, compactSystemPromptStarts as A, selectXaiAccount as B, prepareForCompact as C, compactAutoContinuePromptStarts as D, initOpencodeVersion as E, getMissingApiKeysMessage as F, loginCodex as G, readGitHubToken as H, getXaiAccessToken as I, forwardCodexResponses as J, CODEX_API_BASE_URL as K, getXaiAccounts as L, createAuthMiddleware as M, getConfiguredAdminApiKeys as N, compactMessageSections as O, getConfiguredApiKeys as P, fetchUpstreamWithLifecycle as Q, persistXaiCredentials as R, copilotWebSocketHeaders as S, prepareMessageProxyHeaders as T, readGitHubTokenFromEnv as U, GITHUB_TOKEN_ENV as V, loginXai as W, requestContext as X, generateTraceId as Y, resolveTraceId as Z, parseUserIdMetadata as _, removeCodexAccount as a, state as at, copilotHeaders as b, setupCopilotToken as c, generateRequestIdFromPayload as d, createResponsesSafeStream as et, getRootSessionId as f, isResponsesStream as g, isNullish as h, persistCodexCredentials as i, createWebSocketUrl as it, compactTextOnlyGuard as j, compactSummaryPromptStart as k, setupGitHubToken as l, isAsyncIterable as m, invalidateCodexRuntime as n, getResponsesStreamTerminalDisposition as nt, selectCodexAccount as o, FULL_MODEL_CATALOG_HEADER as ot, getUUID as p, buildCodexRequestHeaders as q, logUser as r, createPooledWebSocketStream as rt, setupCodexToken as s, stripInternalRequestHeaders as st, getCodexAccounts as t, encodePoolKeyPart as tt, stopCopilotRefreshLoop as u, getCopilotUsage as v, prepareInteractionHeaders as w, copilotModelsHeaders as x, copilotBaseUrl as y, removeXaiAccount as z };
|
|
2442
2744
|
|
|
2443
|
-
//# sourceMappingURL=token-
|
|
2745
|
+
//# sourceMappingURL=token-BjzzuPSb.js.map
|