@jeffreycao/copilot-api 2.7.1 → 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.
Files changed (37) hide show
  1. package/dist/auth-C3_rHdk6.js +2 -0
  2. package/dist/{auth-2VWfdcpq.js → auth-DC-EO-eb.js} +68 -26
  3. package/dist/auth-DC-EO-eb.js.map +1 -0
  4. package/dist/{config-B2OaAnpL.js → config-DIDLVXMU.js} +24 -6
  5. package/dist/config-DIDLVXMU.js.map +1 -0
  6. package/dist/{config-store-CARrUljo.js → config-store-CMgB1mCV.js} +2 -1
  7. package/dist/config-store-CMgB1mCV.js.map +1 -0
  8. package/dist/{copilot-runtime-B7T_EuGD.js → copilot-runtime-CRTYwDWy.js} +3 -3
  9. package/dist/{copilot-runtime-B7T_EuGD.js.map → copilot-runtime-CRTYwDWy.js.map} +1 -1
  10. package/dist/{debug-CcfAczV9.js → debug-B0URJ4gT.js} +3 -3
  11. package/dist/{debug-CcfAczV9.js.map → debug-B0URJ4gT.js.map} +1 -1
  12. package/dist/{github-copilot-provider-DlQZgaCT.js → github-copilot-provider-CLEAHyT-.js} +2 -2
  13. package/dist/{github-copilot-provider-DlQZgaCT.js.map → github-copilot-provider-CLEAHyT-.js.map} +1 -1
  14. package/dist/main.js +6 -6
  15. package/dist/{mcp-Byz9h_xz.js → mcp-CuZyqwi8.js} +2 -2
  16. package/dist/{mcp-Byz9h_xz.js.map → mcp-CuZyqwi8.js.map} +1 -1
  17. package/dist/mcp-server-BH-SFdCD.js +2 -0
  18. package/dist/{mcp-server-D6uKVzo6.js → mcp-server-CKEydKaf.js} +2 -2
  19. package/dist/{mcp-server-D6uKVzo6.js.map → mcp-server-CKEydKaf.js.map} +1 -1
  20. package/dist/{provider-DktH3jcC.js → provider-C2Hs8Dua.js} +2 -2
  21. package/dist/{provider-DktH3jcC.js.map → provider-C2Hs8Dua.js.map} +1 -1
  22. package/dist/{provider-management-z3m7cUkp.js → provider-management-BjU3aJnC.js} +3 -3
  23. package/dist/{provider-management-z3m7cUkp.js.map → provider-management-BjU3aJnC.js.map} +1 -1
  24. package/dist/{server-BYG25POu.js → server-DbkseGH6.js} +96 -24
  25. package/dist/server-DbkseGH6.js.map +1 -0
  26. package/dist/{start-v0WnZYEn.js → start-C_Svr70X.js} +8 -8
  27. package/dist/{start-v0WnZYEn.js.map → start-C_Svr70X.js.map} +1 -1
  28. package/dist/{token-Cx07G8-R.js → token-BjzzuPSb.js} +604 -334
  29. package/dist/token-BjzzuPSb.js.map +1 -0
  30. package/package.json +1 -1
  31. package/dist/auth-2VWfdcpq.js.map +0 -1
  32. package/dist/auth-DFH2l7hT.js +0 -2
  33. package/dist/config-B2OaAnpL.js.map +0 -1
  34. package/dist/config-store-CARrUljo.js.map +0 -1
  35. package/dist/mcp-server-ouCaZXlw.js +0 -2
  36. package/dist/server-BYG25POu.js.map +0 -1
  37. package/dist/token-Cx07G8-R.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-CARrUljo.js";
2
- import { c as setProviderConfig, n as getRawProviderConfig } from "./config-B2OaAnpL.js";
3
- import { a as UpstreamHeadersTimeoutError, i as HTTPError, n as assertGitHubCopilotEnabled, o as UpstreamStreamInactivityTimeoutError } from "./github-copilot-provider-DlQZgaCT.js";
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,13 +981,13 @@ 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 REFRESH_BUFFER_MS$1 = 6e4;
990
991
  const CALLBACK_TIMEOUT_MS = 12e4;
991
992
  const REFRESH_TIMEOUT_MS = 3e4;
992
993
  var CodexOAuthError = class extends Error {
@@ -1048,7 +1049,7 @@ function decodeJwt(accessToken) {
1048
1049
  return null;
1049
1050
  }
1050
1051
  }
1051
- function getAccountId(accessToken) {
1052
+ function getAccountId$1(accessToken) {
1052
1053
  const payload = decodeJwt(accessToken);
1053
1054
  if (!payload) return null;
1054
1055
  const authPayload = payload[JWT_CLAIM_PATH];
@@ -1122,7 +1123,7 @@ async function exchangeAuthorizationCode(code, verifier, signal) {
1122
1123
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
1123
1124
  body: new URLSearchParams({
1124
1125
  grant_type: "authorization_code",
1125
- client_id: CLIENT_ID,
1126
+ client_id: CLIENT_ID$1,
1126
1127
  code,
1127
1128
  code_verifier: verifier,
1128
1129
  redirect_uri: REDIRECT_URI
@@ -1152,7 +1153,7 @@ async function refreshAccessToken(refreshToken, options = {}) {
1152
1153
  body: new URLSearchParams({
1153
1154
  grant_type: "refresh_token",
1154
1155
  refresh_token: refreshToken,
1155
- client_id: CLIENT_ID
1156
+ client_id: CLIENT_ID$1
1156
1157
  }),
1157
1158
  signal
1158
1159
  });
@@ -1177,9 +1178,9 @@ async function createAuthorizationFlow() {
1177
1178
  const state = createState();
1178
1179
  const url = new URL(AUTHORIZE_URL);
1179
1180
  url.searchParams.set("response_type", "code");
1180
- url.searchParams.set("client_id", CLIENT_ID);
1181
+ url.searchParams.set("client_id", CLIENT_ID$1);
1181
1182
  url.searchParams.set("redirect_uri", REDIRECT_URI);
1182
- url.searchParams.set("scope", SCOPE);
1183
+ url.searchParams.set("scope", SCOPE$1);
1183
1184
  url.searchParams.set("code_challenge", challenge);
1184
1185
  url.searchParams.set("code_challenge_method", "S256");
1185
1186
  url.searchParams.set("state", state);
@@ -1295,7 +1296,7 @@ async function loginCodex(options) {
1295
1296
  if (!code) throw callbackError ?? /* @__PURE__ */ new Error("Missing Codex authorization code");
1296
1297
  const tokenResult = await exchangeAuthorizationCode(code, verifier, options.signal);
1297
1298
  options.signal?.throwIfAborted();
1298
- const accountId = getAccountId(tokenResult.accessToken);
1299
+ const accountId = getAccountId$1(tokenResult.accessToken);
1299
1300
  if (!accountId) throw new Error("Failed to extract Codex account id from access token");
1300
1301
  return {
1301
1302
  accessToken: tokenResult.accessToken,
@@ -1306,17 +1307,590 @@ async function loginCodex(options) {
1306
1307
  }
1307
1308
  async function refreshCodexCredentials(credentials, options = {}) {
1308
1309
  const tokenResult = await refreshAccessToken(credentials.refreshToken, options);
1309
- const accountId = getAccountId(tokenResult.accessToken);
1310
+ const accountId = getAccountId$1(tokenResult.accessToken);
1310
1311
  if (!accountId) throw new Error("Failed to extract Codex account id from access token");
1311
1312
  return {
1312
- accessToken: tokenResult.accessToken,
1313
- refreshToken: tokenResult.refreshToken,
1314
- expiresAt: tokenResult.expiresAt,
1315
- 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
+ })
1316
1834
  };
1317
1835
  }
1318
- function isCodexCredentialsExpired(credentials, now = Date.now()) {
1319
- return credentials.expiresAt <= now + REFRESH_BUFFER_MS;
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;
1320
1894
  }
1321
1895
  //#endregion
1322
1896
  //#region src/lib/request-auth.ts
@@ -1643,272 +2217,6 @@ const GITHUB_BASE_URL = "https://github.com";
1643
2217
  const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
1644
2218
  const GITHUB_APP_SCOPES = ["read:user"].join(" ");
1645
2219
  const OPENCODE_GITHUB_CLIENT_ID = "Ov23li8tweQw6odWQebz";
1646
- const CODEX_CREDENTIAL_LOCK_RETRY_MS = 10;
1647
- const CODEX_CREDENTIAL_LOCK_TIMEOUT_MS = 1e4;
1648
- const CODEX_CREDENTIAL_LOCK_STALE_MS = 6e4;
1649
- function isNodeError(error) {
1650
- return error instanceof Error && "code" in error;
1651
- }
1652
- function normalizeCodexAccountSelector(value) {
1653
- return value.trim().toLowerCase();
1654
- }
1655
- function parseCodexCredentialLockMetadata(value) {
1656
- try {
1657
- const candidate = JSON.parse(value);
1658
- 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;
1659
- return {
1660
- createdAt: candidate.createdAt,
1661
- owner: candidate.owner,
1662
- pid: candidate.pid
1663
- };
1664
- } catch {
1665
- return null;
1666
- }
1667
- }
1668
- function isProcessAlive(pid) {
1669
- try {
1670
- process.kill(pid, 0);
1671
- return true;
1672
- } catch (error) {
1673
- return !(isNodeError(error) && error.code === "ESRCH");
1674
- }
1675
- }
1676
- async function isCodexCredentialLockContention(error, lockPath) {
1677
- if (isNodeError(error) && error.code === "EEXIST") return true;
1678
- if (process.platform !== "win32" || !isNodeError(error) || error.code !== "EACCES" && error.code !== "EPERM") return false;
1679
- try {
1680
- await fs.stat(lockPath);
1681
- return true;
1682
- } catch (statError) {
1683
- if (isNodeError(statError) && statError.code === "ENOENT") return true;
1684
- return false;
1685
- }
1686
- }
1687
- async function removeStaleCodexCredentialLock(lockPath) {
1688
- let content;
1689
- let modifiedAt;
1690
- try {
1691
- const [lockContent, stats] = await Promise.all([fs.readFile(lockPath, "utf8"), fs.stat(lockPath)]);
1692
- content = lockContent;
1693
- modifiedAt = stats.mtimeMs;
1694
- } catch (error) {
1695
- if (isNodeError(error) && error.code === "ENOENT") return;
1696
- throw error;
1697
- }
1698
- const metadata = parseCodexCredentialLockMetadata(content);
1699
- if (Date.now() - (metadata?.createdAt ?? modifiedAt) < CODEX_CREDENTIAL_LOCK_STALE_MS && (!metadata || isProcessAlive(metadata.pid))) return;
1700
- try {
1701
- if (await fs.readFile(lockPath, "utf8") !== content) return;
1702
- await fs.unlink(lockPath);
1703
- } catch (error) {
1704
- if (isNodeError(error) && error.code === "ENOENT") return;
1705
- throw error;
1706
- }
1707
- }
1708
- async function acquireCodexCredentialLock(lockPath = `${PATHS.CODEX_CREDENTIAL_PATH}.lock`) {
1709
- await fs.mkdir(path.dirname(lockPath), { recursive: true });
1710
- const startedAt = Date.now();
1711
- while (true) {
1712
- const metadata = {
1713
- createdAt: Date.now(),
1714
- owner: `${process.pid}-${randomBytes(8).toString("hex")}`,
1715
- pid: process.pid
1716
- };
1717
- const content = `${JSON.stringify(metadata)}\n`;
1718
- try {
1719
- const fileHandle = await fs.open(lockPath, "wx", 384);
1720
- try {
1721
- await fileHandle.writeFile(content, "utf8");
1722
- await fileHandle.sync();
1723
- } catch (error) {
1724
- await fileHandle.close().catch(() => {});
1725
- await fs.unlink(lockPath).catch(() => {});
1726
- throw error;
1727
- }
1728
- return {
1729
- content,
1730
- fileHandle,
1731
- lockPath
1732
- };
1733
- } catch (error) {
1734
- if (!await isCodexCredentialLockContention(error, lockPath)) throw error;
1735
- }
1736
- await removeStaleCodexCredentialLock(lockPath);
1737
- if (Date.now() - startedAt >= CODEX_CREDENTIAL_LOCK_TIMEOUT_MS) throw new Error(`Timed out waiting for Codex credential store lock: ${lockPath}`);
1738
- await setTimeout$1(CODEX_CREDENTIAL_LOCK_RETRY_MS);
1739
- }
1740
- }
1741
- async function releaseCodexCredentialLock(lock) {
1742
- await lock.fileHandle.close();
1743
- try {
1744
- if (await fs.readFile(lock.lockPath, "utf8") !== lock.content) return;
1745
- await fs.unlink(lock.lockPath);
1746
- } catch (error) {
1747
- if (!(isNodeError(error) && error.code === "ENOENT")) throw error;
1748
- }
1749
- }
1750
- async function withCodexCredentialLock(operation, lockPath) {
1751
- const lock = await acquireCodexCredentialLock(lockPath);
1752
- let result;
1753
- try {
1754
- result = await operation();
1755
- } catch (error) {
1756
- await releaseCodexCredentialLock(lock).catch(() => {});
1757
- throw error;
1758
- }
1759
- await releaseCodexCredentialLock(lock);
1760
- return result;
1761
- }
1762
- /**
1763
- * Serializes operations that must keep Codex account selection in config.json
1764
- * consistent with the credential store. Credential writes retain their own
1765
- * narrower lock so refreshes can run without changing the selected account.
1766
- */
1767
- function withCodexAccountMutationLock(operation) {
1768
- return withCodexCredentialLock(operation, `${PATHS.CODEX_CREDENTIAL_PATH}.accounts.lock`);
1769
- }
1770
- async function readOptionalFile(filePath) {
1771
- try {
1772
- return await fs.readFile(filePath, "utf8");
1773
- } catch (error) {
1774
- if (isNodeError(error) && error.code === "ENOENT") return null;
1775
- throw error;
1776
- }
1777
- }
1778
- function writeProtectedFile(filePath, content) {
1779
- return Promise.resolve().then(() => writeFileAtomically(filePath, content));
1780
- }
1781
- function normalizeCodexCredentials(credentials) {
1782
- if (!credentials || typeof credentials !== "object") return null;
1783
- const candidate = credentials;
1784
- if (typeof candidate.accessToken !== "string" || typeof candidate.refreshToken !== "string" || typeof candidate.expiresAt !== "number" || typeof candidate.accountId !== "string") return null;
1785
- return {
1786
- accessToken: candidate.accessToken,
1787
- refreshToken: candidate.refreshToken,
1788
- expiresAt: candidate.expiresAt,
1789
- accountId: candidate.accountId
1790
- };
1791
- }
1792
- function normalizeCodexStoredAccount(account) {
1793
- const credentials = normalizeCodexCredentials(account);
1794
- if (!credentials) return null;
1795
- const aliasValue = account.alias;
1796
- if (aliasValue !== void 0 && typeof aliasValue !== "string") return null;
1797
- const alias = aliasValue?.trim();
1798
- return {
1799
- ...credentials,
1800
- ...alias ? { alias } : {}
1801
- };
1802
- }
1803
- function normalizeCodexCredentialStore(value) {
1804
- const legacyCredentials = normalizeCodexCredentials(value);
1805
- if (legacyCredentials) return {
1806
- version: 1,
1807
- accounts: [legacyCredentials]
1808
- };
1809
- if (!value || typeof value !== "object") return null;
1810
- const candidate = value;
1811
- if (candidate.version !== 1 || !Array.isArray(candidate.accounts)) return null;
1812
- if (candidate.accounts.length > 3) return null;
1813
- const accounts = [];
1814
- const accountIds = /* @__PURE__ */ new Set();
1815
- const aliases = /* @__PURE__ */ new Set();
1816
- for (const value of candidate.accounts) {
1817
- const account = normalizeCodexStoredAccount(value);
1818
- if (!account || accountIds.has(account.accountId)) return null;
1819
- const normalizedAlias = account.alias ? normalizeCodexAccountSelector(account.alias) : void 0;
1820
- if (normalizedAlias && aliases.has(normalizedAlias)) return null;
1821
- accounts.push(account);
1822
- accountIds.add(account.accountId);
1823
- if (normalizedAlias) aliases.add(normalizedAlias);
1824
- }
1825
- return {
1826
- version: 1,
1827
- accounts: accounts.map((account, index) => {
1828
- const alias = account.alias;
1829
- if (!alias || !accounts.some((candidate, candidateIndex) => candidateIndex !== index && normalizeCodexAccountSelector(candidate.accountId) === normalizeCodexAccountSelector(alias))) return account;
1830
- const { alias: _alias, ...credentials } = account;
1831
- return credentials;
1832
- })
1833
- };
1834
- }
1835
- async function writeCodexCredentialStoreUnlocked(store) {
1836
- await writeProtectedFile(PATHS.CODEX_CREDENTIAL_PATH, `${JSON.stringify(store, null, 2)}\n`);
1837
- }
1838
- async function readGitHubToken() {
1839
- return (await readOptionalFile(PATHS.GITHUB_TOKEN_PATH))?.trim() || null;
1840
- }
1841
- const GITHUB_TOKEN_ENV = "COPILOT_API_GITHUB_TOKEN";
1842
- function readGitHubTokenFromEnv() {
1843
- return process.env["COPILOT_API_GITHUB_TOKEN"]?.trim() || void 0;
1844
- }
1845
- async function writeGitHubToken(token) {
1846
- await writeProtectedFile(PATHS.GITHUB_TOKEN_PATH, token.trim());
1847
- }
1848
- async function readCodexCredentialStore() {
1849
- const raw = await readOptionalFile(PATHS.CODEX_CREDENTIAL_PATH);
1850
- if (!raw?.trim()) return null;
1851
- let parsed;
1852
- try {
1853
- parsed = JSON.parse(raw);
1854
- } catch (error) {
1855
- throw new Error(`Codex credentials file is not valid JSON: ${PATHS.CODEX_CREDENTIAL_PATH}`, { cause: error });
1856
- }
1857
- const store = normalizeCodexCredentialStore(parsed);
1858
- if (!store) throw new Error(`Codex credentials file is missing required fields: ${PATHS.CODEX_CREDENTIAL_PATH}`);
1859
- return store;
1860
- }
1861
- async function writeCodexCredentials(credentials, options = {}) {
1862
- const normalizedCredentials = normalizeCodexCredentials(credentials);
1863
- if (!normalizedCredentials) throw new Error("Codex credentials are missing required fields");
1864
- await withCodexCredentialLock(async () => {
1865
- const store = await readCodexCredentialStore() ?? {
1866
- version: 1,
1867
- accounts: []
1868
- };
1869
- const accountIdSelector = normalizeCodexAccountSelector(normalizedCredentials.accountId);
1870
- const existingIndex = store.accounts.findIndex((account) => account.accountId === normalizedCredentials.accountId);
1871
- if (existingIndex < 0 && options.insertIfMissing === false) return;
1872
- const existingRefreshToken = store.accounts[existingIndex]?.refreshToken;
1873
- if (options.expectedRefreshToken !== void 0 && existingRefreshToken !== options.expectedRefreshToken && existingRefreshToken !== normalizedCredentials.refreshToken) return;
1874
- if (existingIndex < 0 && store.accounts.some((account) => normalizeCodexAccountSelector(account.accountId) === accountIdSelector)) throw new Error(`Codex account id '${normalizedCredentials.accountId}' is already in use`);
1875
- if (existingIndex < 0 && store.accounts.length >= 3) throw new Error(`Codex supports at most 3 accounts`);
1876
- 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`);
1877
- const alias = options.alias?.trim();
1878
- if (alias) {
1879
- const aliasSelector = normalizeCodexAccountSelector(alias);
1880
- 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`);
1881
- if (store.accounts.some((account, index) => index !== existingIndex && normalizeCodexAccountSelector(account.accountId) === aliasSelector)) throw new Error(`Codex account alias '${alias}' conflicts with another account id`);
1882
- }
1883
- const existingAccount = existingIndex >= 0 ? store.accounts[existingIndex] : void 0;
1884
- const nextAccount = {
1885
- ...normalizedCredentials,
1886
- ...alias ? { alias } : existingAccount?.alias ? { alias: existingAccount.alias } : {}
1887
- };
1888
- const accounts = [...store.accounts];
1889
- if (existingIndex >= 0) accounts[existingIndex] = nextAccount;
1890
- else accounts.push(nextAccount);
1891
- await writeCodexCredentialStoreUnlocked({
1892
- version: 1,
1893
- accounts
1894
- });
1895
- });
1896
- }
1897
- async function removeCodexCredentials(accountId) {
1898
- const normalizedAccountId = accountId.trim();
1899
- if (!normalizedAccountId) throw new Error("Codex account id must be a non-empty string");
1900
- return await withCodexCredentialLock(async () => {
1901
- const store = await readCodexCredentialStore();
1902
- const index = store?.accounts.findIndex((account) => account.accountId === normalizedAccountId) ?? -1;
1903
- if (!store || index < 0) throw new Error(`Codex account '${normalizedAccountId}' was not found`);
1904
- const removedAccount = store.accounts[index];
1905
- await writeCodexCredentialStoreUnlocked({
1906
- version: 1,
1907
- accounts: store.accounts.filter((_account, accountIndex) => accountIndex !== index)
1908
- });
1909
- return removedAccount;
1910
- });
1911
- }
1912
2220
  //#endregion
1913
2221
  //#region src/services/github/get-copilot-token.ts
1914
2222
  const getCopilotToken = async () => {
@@ -2168,53 +2476,15 @@ function syncCodexProviderConfig(options) {
2168
2476
  function getConfiguredCodexAccountId() {
2169
2477
  return getRawProviderConfig("codex")?.accountId?.trim() || void 0;
2170
2478
  }
2171
- function getPersistedCodexAccountId() {
2172
- return readEditableConfigFromDisk().providers?.codex?.accountId?.trim() || void 0;
2173
- }
2174
- function resolveActiveCodexAccountId(accounts, configuredAccountId = getConfiguredCodexAccountId()) {
2175
- return configuredAccountId ?? (accounts.length === 1 ? accounts[0].accountId : void 0);
2176
- }
2177
- function normalizeCodexSelector(selector) {
2178
- const normalizedSelector = selector.trim();
2179
- if (!normalizedSelector) throw new Error("Codex account selector must be a non-empty string");
2180
- return normalizedSelector;
2181
- }
2182
- function findCodexAccount(accounts, selector) {
2183
- return accounts.find((candidate) => candidate.accountId === selector) ?? accounts.find((candidate) => candidate.alias?.toLowerCase() === selector.toLowerCase());
2184
- }
2185
- function toCodexAccountSummary(account, active) {
2186
- return {
2187
- accountId: account.accountId,
2188
- ...account.alias ? { alias: account.alias } : {},
2189
- active
2190
- };
2191
- }
2192
- async function getCodexAccounts() {
2193
- return await withCodexAccountMutationLock(async () => {
2194
- const accounts = (await readCodexCredentialStore())?.accounts ?? [];
2195
- const activeAccountId = resolveActiveCodexAccountId(accounts, getPersistedCodexAccountId());
2196
- return accounts.map((account) => toCodexAccountSummary(account, account.accountId === activeAccountId));
2197
- });
2198
- }
2199
- async function selectCodexAccount(selector) {
2200
- return await withCodexAccountMutationLock(async () => {
2201
- const normalizedSelector = normalizeCodexSelector(selector);
2202
- const account = findCodexAccount((await readCodexCredentialStore())?.accounts ?? [], normalizedSelector);
2203
- if (!account) throw new Error(`Codex account '${normalizedSelector}' was not found`);
2204
- syncCodexProviderConfig({ accountId: account.accountId });
2205
- return toCodexAccountSummary(account, true);
2206
- });
2207
- }
2208
- async function removeCodexAccount(selector) {
2209
- return await withCodexAccountMutationLock(async () => {
2210
- const normalizedSelector = normalizeCodexSelector(selector);
2211
- const accounts = (await readCodexCredentialStore())?.accounts ?? [];
2212
- const account = findCodexAccount(accounts, normalizedSelector);
2213
- if (!account) throw new Error(`Codex account '${normalizedSelector}' was not found`);
2214
- if (account.accountId === resolveActiveCodexAccountId(accounts, getPersistedCodexAccountId())) throw new Error(`Codex account '${account.accountId}' is currently in use; switch to another account before removing it`);
2215
- return toCodexAccountSummary(await removeCodexCredentials(account.accountId), false);
2216
- });
2217
- }
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;
2218
2488
  async function persistCodexCredentials(credentials, options = {}) {
2219
2489
  const generation = options.runtimeGeneration ?? codexRuntimeGeneration;
2220
2490
  const persist = async () => {
@@ -2470,6 +2740,6 @@ async function logUser(dependencies = defaultCopilotTokenDependencies) {
2470
2740
  state.tokenBasedBilling = copilotUser.token_based_billing;
2471
2741
  }
2472
2742
  //#endregion
2473
- export { state as $, compactAutoContinuePromptStarts as A, CODEX_API_BASE_URL as B, copilotHeaders as C, prepareInteractionHeaders as D, prepareForCompact as E, createAuthMiddleware as F, resolveTraceId as G, forwardCodexResponses as H, getConfiguredAdminApiKeys as I, createResponsesSafeStream as J, fetchUpstreamWithLifecycle as K, getConfiguredApiKeys as L, compactSummaryPromptStart as M, compactSystemPromptStarts as N, prepareMessageProxyHeaders as O, compactTextOnlyGuard as P, createWebSocketUrl as Q, getMissingApiKeysMessage as R, copilotBaseUrl as S, copilotWebSocketHeaders as T, generateTraceId as U, buildCodexRequestHeaders as V, requestContext as W, getResponsesStreamTerminalDisposition as X, encodePoolKeyPart as Y, createPooledWebSocketStream as Z, parseUserIdMetadata as _, removeCodexAccount as a, readGitHubToken as b, setupCopilotToken as c, generateRequestIdFromPayload as d, FULL_MODEL_CATALOG_HEADER as et, getRootSessionId as f, isResponsesStream as g, isNullish as h, persistCodexCredentials as i, compactMessageSections as j, initOpencodeVersion as k, setupGitHubToken as l, isAsyncIterable as m, invalidateCodexRuntime as n, selectCodexAccount as o, getUUID as p, createResponsesHttpEventStream as q, logUser as r, setupCodexToken as s, getCodexAccounts as t, stripInternalRequestHeaders as tt, stopCopilotRefreshLoop as u, getCopilotUsage as v, copilotModelsHeaders as w, readGitHubTokenFromEnv as x, GITHUB_TOKEN_ENV as y, loginCodex as z };
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 };
2474
2744
 
2475
- //# sourceMappingURL=token-Cx07G8-R.js.map
2745
+ //# sourceMappingURL=token-BjzzuPSb.js.map