@jeffreycao/copilot-api 2.5.9 → 2.5.11

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.
@@ -1,7 +1,7 @@
1
- import { P as ensurePaths, i as listEnabledProviders, j as mergeConfigWithDefaults } from "./config-2Dli6AI8.js";
2
- import { E as initOpencodeVersion, F as getMissingApiKeysMessage, P as getConfiguredApiKeys, X as state, Z as HTTPError, _ as readGitHubToken, g as GITHUB_TOKEN_ENV, n as logUser, o as setupCopilotToken, v as readGitHubTokenFromEnv, x as copilotModelsHeaders, y as copilotBaseUrl } from "./token-4p5BAEqO.js";
3
- import { a as runProviderSetup } from "./auth-zBY9SQaI.js";
4
- import { n as getLatestModelForFamily } from "./models-B1c5UcTP.js";
1
+ import { F as ensurePaths, i as listEnabledProviders, j as mergeConfigWithDefaults } from "./config-DTZzVvur.js";
2
+ import { D as initOpencodeVersion, F as getConfiguredApiKeys, I as getMissingApiKeysMessage, Q as HTTPError, S as copilotModelsHeaders, Z as state, _ as GITHUB_TOKEN_ENV, b as copilotBaseUrl, n as logUser, s as setupCopilotToken, v as readGitHubToken, y as readGitHubTokenFromEnv } from "./token-CGvXQOSn.js";
3
+ import { a as runProviderSetup } from "./auth-WqY8_EZT.js";
4
+ import { n as getLatestModelForFamily } from "./models-DAMOou_f.js";
5
5
  import { defineCommand } from "citty";
6
6
  import consola from "consola";
7
7
  import { createHash, randomUUID } from "node:crypto";
@@ -109,7 +109,7 @@ function isLoopbackHostname(hostname) {
109
109
  function resolveServerBinding(hostname, hasApiKeys) {
110
110
  const normalizedHostname = normalizeServerHostname(hostname);
111
111
  const networkExposed = !isLoopbackHostname(normalizedHostname);
112
- if (networkExposed && !hasApiKeys) throw new Error(`Refusing to listen on non-loopback host ${JSON.stringify(normalizedHostname)} without gateway API keys. Run \`npx copilot-api auth keys --add <key>\` first, or use \`--host ${DEFAULT_SERVER_HOST}\`.`);
112
+ if (networkExposed && !hasApiKeys) throw new Error(`Refusing to listen on non-loopback host ${JSON.stringify(normalizedHostname)} without gateway API keys. Run \`npx @jeffreycao/copilot-api@latest auth keys --add <key>\` first, or use \`--host ${DEFAULT_SERVER_HOST}\`.`);
113
113
  return {
114
114
  hostname: normalizedHostname,
115
115
  clientHostname: resolveClientHostname(normalizedHostname),
@@ -547,7 +547,7 @@ async function runServer(options) {
547
547
  if (resolvedGitHubToken) await setupCopilotMode(resolvedGitHubToken.token, resolvedGitHubToken.source, serverUrl, options.claudeCode);
548
548
  else await setupProviderMode(serverUrl, options.claudeCode);
549
549
  consola.box(`🌐 Usage Viewer: ${serverUrl}/usage-viewer?endpoint=${serverUrl}/usage`);
550
- const { createServer } = await import("./server-D0Wx-DQF.js");
550
+ const { createServer } = await import("./server-CTAJaZn-.js");
551
551
  const server = createServer({ networkExposed: binding.networkExposed });
552
552
  serve({
553
553
  fetch: server.fetch,
@@ -616,4 +616,4 @@ const start = defineCommand({
616
616
  //#endregion
617
617
  export { start };
618
618
 
619
- //# sourceMappingURL=start-zddonYc0.js.map
619
+ //# sourceMappingURL=start-DDNVmyLY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-DDNVmyLY.js","names":["get","process","getCopilotModels"],"sources":["../src/lib/proxy.ts","../src/lib/server-host-shared.ts","../src/lib/server-host.ts","../src/lib/shell.ts","../src/services/copilot/get-models.ts","../src/services/copilot/models-cache.ts","../src/lib/deviceid.ts","../src/services/get-vscode-version.ts","../src/services/vscode-env.ts","../src/start.ts"],"sourcesContent":["import consola from \"consola\"\nimport { getProxyForUrl } from \"proxy-from-env\"\nimport { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from \"undici\"\n\nlet proxyEnvDispatcher: Dispatcher | undefined\n\nexport function getProxyEnvDispatcher(): Dispatcher | undefined {\n return proxyEnvDispatcher\n}\n\nexport function initProxyFromEnv(): void {\n try {\n const direct = new Agent()\n const proxies = new Map<string, ProxyAgent>()\n\n // We only need a minimal dispatcher that implements `dispatch` at runtime.\n // Typing the object as `Dispatcher` forces TypeScript to require many\n // additional methods. Instead, keep a plain object and cast when passing\n // to `setGlobalDispatcher`.\n const dispatcher = {\n dispatch(\n options: Dispatcher.DispatchOptions,\n handler: Dispatcher.DispatchHandler,\n ) {\n try {\n const origin =\n typeof options.origin === \"string\" ?\n new URL(options.origin)\n : (options.origin as URL)\n const get = getProxyForUrl as unknown as (\n u: string,\n ) => string | undefined\n const raw = get(origin.toString())\n const proxyUrl = raw && raw.length > 0 ? raw : undefined\n if (!proxyUrl) {\n consola.debug(`HTTP proxy bypass: ${origin.hostname}`)\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n let agent = proxies.get(proxyUrl)\n if (!agent) {\n agent = new ProxyAgent(proxyUrl)\n proxies.set(proxyUrl, agent)\n }\n let label = proxyUrl\n try {\n const u = new URL(proxyUrl)\n label = `${u.protocol}//${u.host}`\n } catch {\n /* noop */\n }\n consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`)\n return (agent as unknown as Dispatcher).dispatch(options, handler)\n } catch {\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n },\n close() {\n return direct.close()\n },\n destroy() {\n return direct.destroy()\n },\n }\n\n proxyEnvDispatcher = dispatcher as unknown as Dispatcher\n\n if (typeof Bun !== \"undefined\") {\n consola.debug(\"WebSocket proxy configured from environment (per-URL)\")\n return\n }\n\n setGlobalDispatcher(proxyEnvDispatcher)\n consola.debug(\"HTTP proxy configured from environment (per-URL)\")\n } catch (err) {\n consola.debug(\"Proxy setup skipped:\", err)\n }\n}\n","// Pure hostname helpers shared by the gateway server (Node) and the desktop\n// app (including the renderer bundle). This module must stay free of\n// `node:*` imports because the renderer cannot use `node:net`; all\n// address-family checks live in `server-host.ts`.\nexport const INVALID_HOST_CHARACTERS = /[\\s/?#]/\n\n// Bind hosts that listen on every interface, in normalized form (no\n// brackets, so \"[::]\" is covered via `stripIpv6Brackets`).\nexport const NORMALIZED_WILDCARD_HOSTS = new Set([\"0.0.0.0\", \"::\"])\n\nexport function stripIpv6Brackets(hostname: string): string {\n if (hostname.startsWith(\"[\") && hostname.endsWith(\"]\")) {\n return hostname.slice(1, -1)\n }\n return hostname\n}\n\nexport function normalizeHostnameBase(hostname: string): string {\n const normalized = stripIpv6Brackets(hostname.trim())\n if (!normalized || INVALID_HOST_CHARACTERS.test(normalized)) {\n throw new Error(`Invalid server host: ${JSON.stringify(hostname)}`)\n }\n return normalized\n}\n\nexport function isWildcardHostname(hostname: string): boolean {\n return NORMALIZED_WILDCARD_HOSTS.has(stripIpv6Brackets(hostname.trim()))\n}\n","import { isIP } from \"node:net\"\n\nimport { isWildcardHostname, normalizeHostnameBase } from \"./server-host-shared\"\n\nexport const DEFAULT_SERVER_HOST = \"127.0.0.1\"\n\n// Listen failures that mean the hostname itself cannot be bound, as opposed\n// to the port being occupied (EADDRINUSE) or forbidden (EACCES). ENOTFOUND\n// covers unresolvable names such as typos, EADDRNOTAVAIL covers valid IPs\n// that are not assigned to this machine.\nconst INVALID_BIND_ERROR_CODES = new Set([\n \"ENOTFOUND\",\n \"EADDRNOTAVAIL\",\n \"EINVAL\",\n \"EAFNOSUPPORT\",\n \"ENXIO\",\n])\n\nexport function isInvalidBindErrorCode(code?: string): boolean {\n return !!code && INVALID_BIND_ERROR_CODES.has(code)\n}\n\nexport interface ServerBinding {\n hostname: string\n clientHostname: string\n networkExposed: boolean\n}\n\nfunction isLoopbackIpv4(hostname: string): boolean {\n return isIP(hostname) === 4 && hostname.split(\".\", 1)[0] === \"127\"\n}\n\nfunction isLoopbackMappedIpv4(hostname: string): boolean {\n try {\n const canonicalHostname = new URL(`http://[${hostname}]/`).hostname.slice(\n 1,\n -1,\n )\n const mappedIpv4 = canonicalHostname.match(\n /^::ffff:([\\da-f]{1,4}):[\\da-f]{1,4}$/,\n )\n if (!mappedIpv4) {\n return false\n }\n\n return (Number.parseInt(mappedIpv4[1], 16) & 0xff00) === 0x7f00\n } catch {\n return false\n }\n}\n\nexport function normalizeServerHostname(hostname: string): string {\n return normalizeHostnameBase(hostname)\n}\n\nexport function isLoopbackHostname(hostname: string): boolean {\n const normalized = normalizeServerHostname(hostname).toLowerCase()\n if (normalized === \"localhost\" || normalized === \"localhost.\") {\n return true\n }\n if (isLoopbackIpv4(normalized)) {\n return true\n }\n if (isIP(normalized) !== 6) {\n return false\n }\n\n if (normalized === \"::1\" || normalized === \"0:0:0:0:0:0:0:1\") {\n return true\n }\n\n return isLoopbackMappedIpv4(normalized)\n}\n\nexport function resolveServerBinding(\n hostname: string,\n hasApiKeys: boolean,\n): ServerBinding {\n const normalizedHostname = normalizeServerHostname(hostname)\n const networkExposed = !isLoopbackHostname(normalizedHostname)\n\n if (networkExposed && !hasApiKeys) {\n throw new Error(\n `Refusing to listen on non-loopback host ${JSON.stringify(normalizedHostname)} without gateway API keys. Run \\`npx @jeffreycao/copilot-api@latest auth keys --add <key>\\` first, or use \\`--host ${DEFAULT_SERVER_HOST}\\`.`,\n )\n }\n\n return {\n hostname: normalizedHostname,\n clientHostname: resolveClientHostname(normalizedHostname),\n networkExposed,\n }\n}\n\nexport function resolveClientHostname(hostname: string): string {\n const normalizedHostname = normalizeServerHostname(hostname)\n // Wildcard binds listen on every interface, which clients cannot dial\n // directly (0.0.0.0 is not a routable destination).\n return isWildcardHostname(normalizedHostname) ? DEFAULT_SERVER_HOST : (\n normalizedHostname.toLowerCase()\n )\n}\n\nexport function resolveClientHostnameOrDefault(\n hostname: string | null | undefined,\n): string {\n const normalizedHostname = hostname?.trim()\n return normalizedHostname ?\n resolveClientHostname(normalizedHostname)\n : DEFAULT_SERVER_HOST\n}\n\nexport function formatServerUrl(hostname: string, port: number): string {\n const urlHostname =\n isIP(hostname) === 6 ? `[${hostname.replace(\"%\", \"%25\")}]` : hostname\n return `http://${urlHostname}:${port}`\n}\n","import { execSync } from \"node:child_process\"\nimport process from \"node:process\"\n\ntype ShellName = \"bash\" | \"zsh\" | \"fish\" | \"powershell\" | \"cmd\" | \"sh\"\ntype EnvVars = Record<string, string | undefined>\n\nfunction getShell(): ShellName {\n const { platform, ppid, env } = process\n\n if (platform === \"win32\") {\n try {\n const command = `wmic process get ParentProcessId,Name | findstr \"${ppid}\"`\n const parentProcess = execSync(command, { stdio: \"pipe\" }).toString()\n\n if (parentProcess.toLowerCase().includes(\"powershell.exe\")) {\n return \"powershell\"\n }\n } catch {\n return \"cmd\"\n }\n\n return \"cmd\"\n } else {\n const shellPath = env.SHELL\n if (shellPath) {\n if (shellPath.endsWith(\"zsh\")) return \"zsh\"\n if (shellPath.endsWith(\"fish\")) return \"fish\"\n if (shellPath.endsWith(\"bash\")) return \"bash\"\n }\n\n return \"sh\"\n }\n}\n\n/**\n * Generates a copy-pasteable script to set multiple environment variables\n * and run a subsequent command.\n * @param {EnvVars} envVars - An object of environment variables to set.\n * @param {string} commandToRun - The command to run after setting the variables.\n * @returns {string} The formatted script string.\n */\nexport function generateEnvScript(\n envVars: EnvVars,\n commandToRun: string = \"\",\n): string {\n const shell = getShell()\n const filteredEnvVars = Object.entries(envVars).filter(\n ([, value]) => value !== undefined,\n ) as Array<[string, string]>\n\n let commandBlock: string\n\n switch (shell) {\n case \"powershell\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `$env:${key} = ${value}`)\n .join(\"; \")\n break\n }\n case \"cmd\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set ${key}=${value}`)\n .join(\" & \")\n break\n }\n case \"fish\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set -gx ${key} ${value}`)\n .join(\"; \")\n break\n }\n default: {\n // bash, zsh, sh\n const assignments = filteredEnvVars\n .map(([key, value]) => `${key}=${value}`)\n .join(\" \")\n commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : \"\"\n break\n }\n }\n\n if (commandBlock && commandToRun) {\n const separator = shell === \"cmd\" ? \" & \" : \" && \"\n return `${commandBlock}${separator}${commandToRun}`\n }\n\n return commandBlock || commandToRun\n}\n","import consola from \"consola\"\n\nimport { copilotBaseUrl, copilotModelsHeaders } from \"~/lib/api-config\"\n\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\nimport type { ModelsResponse } from \"~/lib/types/models\"\n\nexport const getModels = async () => {\n consola.info(`Fetching models from ${copilotBaseUrl(state)}/models`)\n const response = await fetch(`${copilotBaseUrl(state)}/models`, {\n headers: copilotModelsHeaders(state),\n })\n\n if (!response.ok) {\n const errorText = await response.clone().text()\n\n consola.error(\"Failed to get models response body\", errorText)\n\n throw new HTTPError(\"Failed to get models\", response)\n }\n\n return (await response.json()) as ModelsResponse\n}\n","import consola from \"consola\"\n\nimport { state } from \"~/lib/state\"\nimport { getModels as getCopilotModels } from \"~/services/copilot/get-models\"\n\n// Periodically refresh models so long-running daemons pick up new SKUs.\nconst MODELS_REFRESH_BASE_MS = 30 * 60 * 1000\nlet modelsRefreshTimer: ReturnType<typeof setTimeout> | null = null\n\nexport const stopModelsRefreshLoop = () => {\n if (modelsRefreshTimer) {\n clearTimeout(modelsRefreshTimer)\n modelsRefreshTimer = null\n }\n}\n\ntype ModelsFetcher = typeof getCopilotModels\n\nconst refreshModels = async (fetcher: ModelsFetcher) => {\n const prevIds = new Set(state.models?.data.map((m) => m.id) ?? [])\n const models = await fetcher()\n state.models = {\n ...models,\n data: models.data.filter(\n (model) =>\n model.policy?.state !== \"disabled\"\n && (model.model_picker_enabled\n || model.capabilities.type === \"embeddings\"),\n ),\n }\n const nextIds = state.models.data.map((m) => m.id)\n const added = nextIds.filter((id) => !prevIds.has(id))\n if (added.length > 0) {\n consola.info(`Models refresh: ${added.length} new`)\n } else {\n consola.debug(`Models refresh: no changes (${nextIds.length} total)`)\n }\n}\n\nconst scheduleModelsRefresh = (fetcher: ModelsFetcher, intervalMs: number) => {\n const jitter = Math.floor(Math.random() * (intervalMs / 6))\n const delay = intervalMs + jitter\n consola.debug(\n `Scheduling next models refresh in ${Math.round(delay / 1000)} seconds`,\n )\n\n stopModelsRefreshLoop()\n modelsRefreshTimer = setTimeout(async () => {\n try {\n await refreshModels(fetcher)\n } catch (error) {\n consola.warn(\"Failed to refresh models, keeping previous cache.\", error)\n } finally {\n scheduleModelsRefresh(fetcher, intervalMs)\n }\n }, delay)\n}\n\nexport async function cacheModels(\n fetcher: ModelsFetcher = getCopilotModels,\n intervalMs: number = MODELS_REFRESH_BASE_MS,\n): Promise<void> {\n await refreshModels(fetcher)\n scheduleModelsRefresh(fetcher, intervalMs)\n}\n","import consola from \"consola\"\nimport { randomUUID } from \"node:crypto\"\nimport path from \"node:path\"\n\nconst WINDOWS_DEVICE_ID_KEY = String.raw`\\SOFTWARE\\Microsoft\\DeveloperTools`\nconst WINDOWS_DEVICE_ID_NAME = \"deviceid\"\n\ntype RegistryArch = \"x86\" | \"x64\"\n\ninterface WinregConstructor {\n new (options: {\n hive: string\n key: string\n arch?: RegistryArch\n }): WinregRegistry\n HKCU: string\n REG_SZ: string\n}\n\ninterface WinregRegistry {\n get(\n name: string,\n callback: (error: RegistryError | null, item: RegistryItem | null) => void,\n ): void\n set(\n name: string,\n type: string,\n value: string,\n callback: (error: RegistryError | null) => void,\n ): void\n}\n\ninterface RegistryItem {\n value?: string\n}\n\ninterface RegistryError extends Error {\n code?: number | string\n}\n\nconst windows64Architectures = new Set([\"AMD64\", \"ARM64\", \"IA64\"])\n\nconst getPosixHomeDir = (): string => {\n if (!process.env.HOME) {\n throw new Error(\"Home directory not found\")\n }\n\n return process.env.HOME\n}\n\nconst getDeviceIdFilePath = (): string => {\n let folder: string\n\n switch (process.platform) {\n case \"darwin\": {\n folder = path.posix.join(\n getPosixHomeDir(),\n \"Library\",\n \"Application Support\",\n )\n break\n }\n case \"linux\": {\n folder =\n process.env.XDG_CACHE_HOME\n ?? path.posix.join(getPosixHomeDir(), \".cache\")\n break\n }\n default: {\n throw new Error(\"Unsupported platform\")\n }\n }\n\n return path.posix.join(folder, \"Microsoft\", \"DeveloperTools\", \"deviceid\")\n}\n\nconst isMissingFileError = (error: unknown): error is NodeJS.ErrnoException => {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\"\n}\n\nconst readStoredDeviceIdFile = async (\n filePath: string,\n): Promise<string | undefined> => {\n const { readFile } = await import(\"node:fs/promises\")\n\n try {\n return await readFile(filePath, \"utf8\")\n } catch (error) {\n if (isMissingFileError(error)) {\n return undefined\n }\n\n throw error\n }\n}\n\nconst writeStoredDeviceIdFile = async (\n filePath: string,\n deviceId: string,\n): Promise<void> => {\n const { mkdir, writeFile } = await import(\"node:fs/promises\")\n\n await mkdir(path.posix.dirname(filePath), { recursive: true })\n await writeFile(filePath, deviceId, \"utf8\")\n}\n\nconst getWindowsRegistryArch = (): RegistryArch | undefined => {\n const architecture = (\n process.env.PROCESSOR_ARCHITEW6432 ?? process.env.PROCESSOR_ARCHITECTURE\n )?.toUpperCase()\n\n return architecture && windows64Architectures.has(architecture) ?\n \"x64\"\n : undefined\n}\n\nconst loadWinreg = async (): Promise<WinregConstructor> => {\n const module = await import(\"winreg\")\n const winreg =\n \"default\" in module ? (module.default as unknown) : (module as unknown)\n\n return winreg as WinregConstructor\n}\n\nconst isMissingRegistryError = (error: RegistryError | null): boolean => {\n if (!error) {\n return false\n }\n\n const errorCode = Number(error.code)\n\n return Number.isFinite(errorCode) && errorCode === 1\n}\n\nconst createWindowsRegistry = async (): Promise<{\n registry: WinregRegistry\n regSz: string\n}> => {\n const Winreg = await loadWinreg()\n\n return {\n registry: new Winreg({\n hive: Winreg.HKCU,\n key: WINDOWS_DEVICE_ID_KEY,\n arch: getWindowsRegistryArch(),\n }),\n regSz: Winreg.REG_SZ,\n }\n}\n\nconst readRegistryString = async (\n registry: WinregRegistry,\n name: string,\n): Promise<string | undefined> => {\n return new Promise((resolve, reject) => {\n registry.get(name, (error, item) => {\n if (isMissingRegistryError(error)) {\n resolve(undefined)\n return\n }\n\n if (error) {\n reject(\n error instanceof Error ? error : new Error(\"Unknown registry error\"),\n )\n return\n }\n\n resolve(item?.value)\n })\n })\n}\n\nconst writeRegistryString = async ({\n registry,\n regSz,\n name,\n value,\n}: {\n registry: WinregRegistry\n regSz: string\n name: string\n value: string\n}): Promise<void> => {\n return new Promise((resolve, reject) => {\n registry.set(name, regSz, value, (error) => {\n if (error) {\n reject(\n error instanceof Error ? error : new Error(\"Unknown registry error\"),\n )\n return\n }\n\n resolve()\n })\n })\n}\n\nexport const getStoredVSCodeDeviceId = async (): Promise<\n string | undefined\n> => {\n switch (process.platform) {\n case \"win32\": {\n const { registry } = await createWindowsRegistry()\n\n return readRegistryString(registry, WINDOWS_DEVICE_ID_NAME)\n }\n case \"darwin\":\n case \"linux\": {\n return readStoredDeviceIdFile(getDeviceIdFilePath())\n }\n default: {\n throw new Error(\"Unsupported platform\")\n }\n }\n}\n\nconst setStoredVSCodeDeviceId = async (deviceId: string): Promise<void> => {\n switch (process.platform) {\n case \"win32\": {\n const { registry, regSz } = await createWindowsRegistry()\n\n await writeRegistryString({\n registry,\n regSz,\n name: WINDOWS_DEVICE_ID_NAME,\n value: deviceId,\n })\n return\n }\n case \"darwin\":\n case \"linux\": {\n await writeStoredDeviceIdFile(getDeviceIdFilePath(), deviceId)\n return\n }\n default: {\n throw new Error(\"Unsupported platform\")\n }\n }\n}\n\nconst createVSCodeDeviceId = (): string => randomUUID().toLowerCase()\n\nexport async function getVSCodeDeviceId(): Promise<string> {\n let deviceId: string | undefined\n\n try {\n deviceId = await getStoredVSCodeDeviceId()\n } catch (error) {\n consola.debug(\"Failed to read VSCode device id\", error)\n }\n\n if (deviceId) {\n return deviceId\n }\n\n const newDeviceId = createVSCodeDeviceId()\n\n try {\n await setStoredVSCodeDeviceId(newDeviceId)\n } catch (error) {\n consola.warn(\n \"Failed to persist VSCode device id, using ephemeral id\",\n error,\n )\n }\n\n return newDeviceId\n}\n","const FALLBACK = \"1.130.0\"\n\nexport async function getVSCodeVersion() {\n await Promise.resolve()\n return FALLBACK\n}\n","import consola from \"consola\"\nimport { createHash, randomUUID } from \"node:crypto\"\nimport { networkInterfaces } from \"node:os\"\n\nimport { getVSCodeDeviceId } from \"~/lib/deviceid\"\nimport { state } from \"~/lib/state\"\nimport { getVSCodeVersion } from \"~/services/get-vscode-version\"\n\nexport const cacheVSCodeVersion = async () => {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n\n consola.info(`Using VSCode version: ${response}`)\n}\n\nconst invalidMacAddresses = new Set([\n \"00:00:00:00:00:00\",\n \"ff:ff:ff:ff:ff:ff\",\n \"ac:de:48:00:11:22\",\n])\n\nfunction validateMacAddress(candidate: string): boolean {\n const tempCandidate = candidate.replaceAll(\"-\", \":\").toLowerCase()\n return !invalidMacAddresses.has(tempCandidate)\n}\n\nexport function getMac(): string | null {\n const ifaces = networkInterfaces()\n // eslint-disable-next-line guard-for-in\n for (const name in ifaces) {\n const networkInterface = ifaces[name]\n if (networkInterface) {\n for (const { mac } of networkInterface) {\n if (validateMacAddress(mac)) {\n return mac\n }\n }\n }\n }\n return null\n}\n\nexport const cacheMacMachineId = () => {\n const macAddress = getMac() ?? randomUUID()\n state.macMachineId = createHash(\"sha256\")\n .update(macAddress, \"utf8\")\n .digest(\"hex\")\n consola.debug(`Using machine ID: ${state.macMachineId}`)\n}\n\nexport const cacheVsCodeDeviceId = async () => {\n state.vsCodeDeviceId = await getVSCodeDeviceId()\n consola.debug(`Using VSCode device ID: ${state.vsCodeDeviceId}`)\n}\n\nconst SESSION_REFRESH_BASE_MS = 60 * 60 * 1000\nconst SESSION_REFRESH_JITTER_MS = 20 * 60 * 1000\nlet vsCodeSessionRefreshTimer: ReturnType<typeof setTimeout> | null = null\n\nconst generateSessionId = () => {\n state.vsCodeSessionId = randomUUID() + Date.now().toString()\n consola.debug(`Generated VSCode session ID: ${state.vsCodeSessionId}`)\n}\n\nexport const stopVsCodeSessionRefreshLoop = () => {\n if (vsCodeSessionRefreshTimer) {\n clearTimeout(vsCodeSessionRefreshTimer)\n vsCodeSessionRefreshTimer = null\n }\n}\n\nconst scheduleSessionIdRefresh = () => {\n const randomDelay = Math.floor(Math.random() * SESSION_REFRESH_JITTER_MS)\n const delay = SESSION_REFRESH_BASE_MS + randomDelay\n consola.debug(\n `Scheduling next VSCode session ID refresh in ${Math.round(\n delay / 1000,\n )} seconds`,\n )\n\n stopVsCodeSessionRefreshLoop()\n vsCodeSessionRefreshTimer = setTimeout(() => {\n try {\n generateSessionId()\n } catch (error) {\n consola.error(\"Failed to refresh session ID, rescheduling...\", error)\n } finally {\n scheduleSessionIdRefresh()\n }\n }, delay)\n}\n\nexport const cacheVsCodeSessionId = () => {\n stopVsCodeSessionRefreshLoop()\n generateSessionId()\n scheduleSessionIdRefresh()\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport clipboard from \"clipboardy\"\nimport consola from \"consola\"\nimport { serve, type ServerHandler } from \"srvx\"\nimport invariant from \"tiny-invariant\"\n\nimport { runProviderSetup } from \"./auth\"\nimport { listEnabledProviders, mergeConfigWithDefaults } from \"./lib/config\"\nimport {\n GITHUB_TOKEN_ENV,\n readGitHubToken,\n readGitHubTokenFromEnv,\n} from \"./lib/credential-store\"\nimport { getLatestModelForFamily } from \"./lib/models\"\nimport { initOpencodeVersion } from \"./lib/opencode\"\nimport { ensurePaths } from \"./lib/paths\"\nimport { initProxyFromEnv } from \"./lib/proxy\"\nimport {\n getConfiguredApiKeys,\n getMissingApiKeysMessage,\n} from \"./lib/request-auth\"\nimport {\n DEFAULT_SERVER_HOST,\n formatServerUrl,\n resolveServerBinding,\n} from \"./lib/server-host\"\nimport { generateEnvScript } from \"./lib/shell\"\nimport { state } from \"./lib/state\"\nimport { logUser, setupCopilotToken } from \"./lib/token\"\nimport { cacheModels } from \"./services/copilot/models-cache\"\nimport {\n cacheMacMachineId,\n cacheVSCodeVersion,\n cacheVsCodeSessionId,\n cacheVsCodeDeviceId,\n} from \"./services/vscode-env\"\n\ninterface RunServerOptions {\n host: string\n port: number\n verbose: boolean\n githubToken?: string\n claudeCode: boolean\n showToken: boolean\n proxyEnv: boolean\n}\n\ntype GitHubTokenSource = \"cli\" | \"env\" | \"file\"\n\n// The environment is preferred over the token file so the token never has to\n// travel through the process list; --github-token stays first for callers that\n// opt in explicitly.\nasync function resolveGitHubToken(\n cliToken: string | undefined,\n): Promise<{ token: string; source: GitHubTokenSource } | null> {\n if (cliToken) return { token: cliToken, source: \"cli\" }\n\n const envToken = readGitHubTokenFromEnv()\n if (envToken) return { token: envToken, source: \"env\" }\n\n const fileToken = await readGitHubToken()\n if (fileToken) return { token: fileToken, source: \"file\" }\n\n return null\n}\n\nasync function setupCopilotMode(\n githubToken: string,\n source: GitHubTokenSource,\n serverUrl: string,\n claudeCode: boolean,\n): Promise<void> {\n state.githubToken = githubToken\n consola.info(\n source === \"cli\" ? \"Using provided GitHub token\"\n : source === \"env\" ?\n `Using GitHub token from the ${GITHUB_TOKEN_ENV} environment variable`\n : \"Using GitHub token from local file\",\n )\n\n await logUser()\n\n await cacheVSCodeVersion()\n cacheMacMachineId()\n cacheVsCodeSessionId()\n await cacheVsCodeDeviceId()\n\n await setupCopilotToken()\n await cacheModels()\n\n consola.info(\n `Available models: \\n${state.models?.data.map((model) => `- ${model.id}`).join(\"\\n\")}`,\n )\n\n if (claudeCode) {\n runClaudeCode(serverUrl)\n }\n}\n\nfunction runClaudeCode(serverUrl: string): void {\n consola.log(\n \"\\n💡 Tip: The --claude-code flag simply generates a clipboard command for launching Claude Code. \\n\"\n + \"All models remain fully accessible without this flag, just configure the model ID directly in your settings.json file.\",\n )\n\n invariant(state.models, \"Models should be loaded by now\")\n\n // Default to the latest available model for each Claude Code size tier so\n // opus maps to opus, sonnet maps to sonnet, and haiku maps to haiku.\n const opusModel = getLatestModelForFamily(\"opus\")?.id\n const sonnetModel = getLatestModelForFamily(\"sonnet\")?.id\n const haikuModel = getLatestModelForFamily(\"haiku\")?.id\n\n consola.info(\n \"Selected default Claude Code models:\\n\"\n + `- Opus: ${opusModel ?? \"(none available)\"}\\n`\n + `- Sonnet: ${sonnetModel ?? \"(none available)\"}\\n`\n + `- Haiku: ${haikuModel ?? \"(none available)\"}`,\n )\n\n const command = generateEnvScript(\n {\n ANTHROPIC_BASE_URL: serverUrl,\n ANTHROPIC_AUTH_TOKEN: \"dummy\",\n ANTHROPIC_MODEL: sonnetModel ?? opusModel,\n ANTHROPIC_DEFAULT_OPUS_MODEL: opusModel,\n ANTHROPIC_DEFAULT_SONNET_MODEL: sonnetModel,\n ANTHROPIC_DEFAULT_HAIKU_MODEL: haikuModel,\n CLAUDE_CODE_USE_VERTEX: \"0\",\n CLAUDE_CODE_USE_BEDROCK: \"0\",\n DISABLE_NON_ESSENTIAL_MODEL_CALLS: \"1\",\n CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: \"1\",\n CLAUDE_CODE_ATTRIBUTION_HEADER: \"0\",\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: \"false\",\n CLAUDE_CODE_DISABLE_TERMINAL_TITLE: \"true\",\n CLAUDE_CODE_ENABLE_AWAY_SUMMARY: \"0\",\n CLAUDE_CODE_TOTAL_TOKENS_REMINDER: \"off\",\n CLAUDE_CODE_EFFORT_LEVEL: \"max\",\n MCP_CONNECT_TIMEOUT_MS: \"20000\",\n },\n \"claude\",\n )\n\n try {\n clipboard.writeSync(command)\n consola.success(\"Copied Claude Code command to clipboard!\")\n } catch {\n consola.warn(\n \"Failed to copy to clipboard. Here is the Claude Code command:\",\n )\n consola.log(command)\n }\n}\n\nasync function setupProviderMode(\n serverUrl: string,\n claudeCode: boolean,\n): Promise<void> {\n const enabledProviders = listEnabledProviders()\n\n if (enabledProviders.length > 0) {\n consola.info(`Using enabled providers: ${enabledProviders.join(\", \")}`)\n return\n }\n\n consola.info(\"No enabled providers found. Setting one up...\")\n await runProviderSetup()\n\n if (state.githubToken) {\n // The setup flow persisted the token with the credential store.\n await setupCopilotMode(state.githubToken, \"file\", serverUrl, claudeCode)\n return\n }\n\n const providersAfterSetup = listEnabledProviders()\n if (providersAfterSetup.length === 0) {\n throw new Error(\n \"Failed to configure any provider. Run `copilot-api auth login` to set one up.\",\n )\n }\n consola.info(`Configured providers: ${providersAfterSetup.join(\", \")}`)\n}\n\nexport async function runServer(options: RunServerOptions): Promise<void> {\n const tlsModule = await import(\"./lib/tls\")\n tlsModule.enableSystemCACompat()\n\n consola.options.throttle = 0\n\n mergeConfigWithDefaults()\n\n const configuredApiKeys = getConfiguredApiKeys()\n const binding = resolveServerBinding(\n options.host,\n configuredApiKeys.length > 0,\n )\n\n const missingApiKeysMessage = getMissingApiKeysMessage()\n if (missingApiKeysMessage) {\n consola.info(missingApiKeysMessage)\n }\n\n await initOpencodeVersion()\n\n if (options.proxyEnv) {\n initProxyFromEnv()\n }\n\n state.verbose = options.verbose\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.showToken = options.showToken\n\n await ensurePaths()\n\n const serverUrl = formatServerUrl(binding.clientHostname, options.port)\n\n const resolvedGitHubToken = await resolveGitHubToken(options.githubToken)\n if (resolvedGitHubToken) {\n await setupCopilotMode(\n resolvedGitHubToken.token,\n resolvedGitHubToken.source,\n serverUrl,\n options.claudeCode,\n )\n } else {\n await setupProviderMode(serverUrl, options.claudeCode)\n }\n\n consola.box(\n `🌐 Usage Viewer: ${serverUrl}/usage-viewer?endpoint=${serverUrl}/usage`,\n )\n\n const { createServer } = await import(\"./server\")\n const server = createServer({ networkExposed: binding.networkExposed })\n\n serve({\n fetch: server.fetch as ServerHandler,\n hostname: binding.hostname,\n port: options.port,\n bun: {\n idleTimeout: 0,\n },\n })\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the Copilot API server\",\n },\n args: {\n host: {\n type: \"string\",\n default: process.env.HOST?.trim() || DEFAULT_SERVER_HOST,\n description: \"Host to listen on\",\n },\n port: {\n alias: \"p\",\n type: \"string\",\n default: \"4141\",\n description: \"Port to listen on\",\n },\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"github-token\": {\n alias: \"g\",\n type: \"string\",\n description:\n \"Provide GitHub token directly (must be generated using the `auth` subcommand)\",\n },\n \"claude-code\": {\n alias: \"c\",\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Claude Code with Copilot API config\",\n },\n \"show-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub and Copilot tokens on fetch and refresh\",\n },\n \"proxy-env\": {\n type: \"boolean\",\n default: false,\n description: \"Initialize proxy from environment variables\",\n },\n },\n run({ args }) {\n return runServer({\n host: args.host,\n port: Number.parseInt(args.port, 10),\n verbose: args.verbose,\n githubToken: args[\"github-token\"],\n claudeCode: args[\"claude-code\"],\n showToken: args[\"show-token\"],\n proxyEnv: args[\"proxy-env\"],\n })\n },\n})\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { A as isResponsesApiWebSocketEnabled, E as getUpstreamTransportConfig, F as writeFileAtomically, N as PATHS, c as setProviderConfig, n as getRawProviderConfig, w as getConfig } from "./config-2Dli6AI8.js";
1
+ import { A as isResponsesApiWebSocketEnabled, E as getUpstreamTransportConfig, I as writeFileAtomically, M as readEditableConfigFromDisk, P as PATHS, c as setProviderConfig, n as getRawProviderConfig, w as getConfig } from "./config-DTZzVvur.js";
2
2
  import consola from "consola";
3
3
  import { createHash, randomBytes, randomUUID } from "node:crypto";
4
4
  import path from "node:path";
@@ -1269,7 +1269,7 @@ function getConfiguredApiKeys() {
1269
1269
  }
1270
1270
  function getMissingApiKeysMessage() {
1271
1271
  if (getConfiguredApiKeys().length > 0) return null;
1272
- return ["Requests currently bypass authentication.", "Run `npx copilot-api auth keys --add <key>` to enable API key auth."].join(" ");
1272
+ return ["Requests currently bypass authentication.", "Run `npx @jeffreycao/copilot-api@latest auth keys --add <key>` to enable API key auth."].join(" ");
1273
1273
  }
1274
1274
  function normalizeApiKey(apiKey) {
1275
1275
  if (typeof apiKey !== "string") return null;
@@ -1574,9 +1574,130 @@ const GITHUB_BASE_URL = "https://github.com";
1574
1574
  const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
1575
1575
  const GITHUB_APP_SCOPES = ["read:user"].join(" ");
1576
1576
  const OPENCODE_GITHUB_CLIENT_ID = "Ov23li8tweQw6odWQebz";
1577
+ const CODEX_CREDENTIAL_LOCK_RETRY_MS = 10;
1578
+ const CODEX_CREDENTIAL_LOCK_TIMEOUT_MS = 1e4;
1579
+ const CODEX_CREDENTIAL_LOCK_STALE_MS = 6e4;
1577
1580
  function isNodeError(error) {
1578
1581
  return error instanceof Error && "code" in error;
1579
1582
  }
1583
+ function normalizeCodexAccountSelector(value) {
1584
+ return value.trim().toLowerCase();
1585
+ }
1586
+ function parseCodexCredentialLockMetadata(value) {
1587
+ try {
1588
+ const candidate = JSON.parse(value);
1589
+ 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;
1590
+ return {
1591
+ createdAt: candidate.createdAt,
1592
+ owner: candidate.owner,
1593
+ pid: candidate.pid
1594
+ };
1595
+ } catch {
1596
+ return null;
1597
+ }
1598
+ }
1599
+ function isProcessAlive(pid) {
1600
+ try {
1601
+ process.kill(pid, 0);
1602
+ return true;
1603
+ } catch (error) {
1604
+ return !(isNodeError(error) && error.code === "ESRCH");
1605
+ }
1606
+ }
1607
+ async function isCodexCredentialLockContention(error, lockPath) {
1608
+ if (isNodeError(error) && error.code === "EEXIST") return true;
1609
+ if (process.platform !== "win32" || !isNodeError(error) || error.code !== "EACCES" && error.code !== "EPERM") return false;
1610
+ try {
1611
+ await fs.stat(lockPath);
1612
+ return true;
1613
+ } catch (statError) {
1614
+ if (isNodeError(statError) && statError.code === "ENOENT") return true;
1615
+ return false;
1616
+ }
1617
+ }
1618
+ async function removeStaleCodexCredentialLock(lockPath) {
1619
+ let content;
1620
+ let modifiedAt;
1621
+ try {
1622
+ const [lockContent, stats] = await Promise.all([fs.readFile(lockPath, "utf8"), fs.stat(lockPath)]);
1623
+ content = lockContent;
1624
+ modifiedAt = stats.mtimeMs;
1625
+ } catch (error) {
1626
+ if (isNodeError(error) && error.code === "ENOENT") return;
1627
+ throw error;
1628
+ }
1629
+ const metadata = parseCodexCredentialLockMetadata(content);
1630
+ if (Date.now() - (metadata?.createdAt ?? modifiedAt) < CODEX_CREDENTIAL_LOCK_STALE_MS && (!metadata || isProcessAlive(metadata.pid))) return;
1631
+ try {
1632
+ if (await fs.readFile(lockPath, "utf8") !== content) return;
1633
+ await fs.unlink(lockPath);
1634
+ } catch (error) {
1635
+ if (isNodeError(error) && error.code === "ENOENT") return;
1636
+ throw error;
1637
+ }
1638
+ }
1639
+ async function acquireCodexCredentialLock(lockPath = `${PATHS.CODEX_CREDENTIAL_PATH}.lock`) {
1640
+ await fs.mkdir(path.dirname(lockPath), { recursive: true });
1641
+ const startedAt = Date.now();
1642
+ while (true) {
1643
+ const metadata = {
1644
+ createdAt: Date.now(),
1645
+ owner: `${process.pid}-${randomBytes(8).toString("hex")}`,
1646
+ pid: process.pid
1647
+ };
1648
+ const content = `${JSON.stringify(metadata)}\n`;
1649
+ try {
1650
+ const fileHandle = await fs.open(lockPath, "wx", 384);
1651
+ try {
1652
+ await fileHandle.writeFile(content, "utf8");
1653
+ await fileHandle.sync();
1654
+ } catch (error) {
1655
+ await fileHandle.close().catch(() => {});
1656
+ await fs.unlink(lockPath).catch(() => {});
1657
+ throw error;
1658
+ }
1659
+ return {
1660
+ content,
1661
+ fileHandle,
1662
+ lockPath
1663
+ };
1664
+ } catch (error) {
1665
+ if (!await isCodexCredentialLockContention(error, lockPath)) throw error;
1666
+ }
1667
+ await removeStaleCodexCredentialLock(lockPath);
1668
+ if (Date.now() - startedAt >= CODEX_CREDENTIAL_LOCK_TIMEOUT_MS) throw new Error(`Timed out waiting for Codex credential store lock: ${lockPath}`);
1669
+ await setTimeout$1(CODEX_CREDENTIAL_LOCK_RETRY_MS);
1670
+ }
1671
+ }
1672
+ async function releaseCodexCredentialLock(lock) {
1673
+ await lock.fileHandle.close();
1674
+ try {
1675
+ if (await fs.readFile(lock.lockPath, "utf8") !== lock.content) return;
1676
+ await fs.unlink(lock.lockPath);
1677
+ } catch (error) {
1678
+ if (!(isNodeError(error) && error.code === "ENOENT")) throw error;
1679
+ }
1680
+ }
1681
+ async function withCodexCredentialLock(operation, lockPath) {
1682
+ const lock = await acquireCodexCredentialLock(lockPath);
1683
+ let result;
1684
+ try {
1685
+ result = await operation();
1686
+ } catch (error) {
1687
+ await releaseCodexCredentialLock(lock).catch(() => {});
1688
+ throw error;
1689
+ }
1690
+ await releaseCodexCredentialLock(lock);
1691
+ return result;
1692
+ }
1693
+ /**
1694
+ * Serializes operations that must keep Codex account selection in config.json
1695
+ * consistent with the credential store. Credential writes retain their own
1696
+ * narrower lock so refreshes can run without changing the selected account.
1697
+ */
1698
+ function withCodexAccountMutationLock(operation) {
1699
+ return withCodexCredentialLock(operation, `${PATHS.CODEX_CREDENTIAL_PATH}.accounts.lock`);
1700
+ }
1580
1701
  async function readOptionalFile(filePath) {
1581
1702
  try {
1582
1703
  return await fs.readFile(filePath, "utf8");
@@ -1626,7 +1747,7 @@ function normalizeCodexCredentialStore(value) {
1626
1747
  for (const value of candidate.accounts) {
1627
1748
  const account = normalizeCodexStoredAccount(value);
1628
1749
  if (!account || accountIds.has(account.accountId)) return null;
1629
- const normalizedAlias = account.alias?.toLowerCase();
1750
+ const normalizedAlias = account.alias ? normalizeCodexAccountSelector(account.alias) : void 0;
1630
1751
  if (normalizedAlias && aliases.has(normalizedAlias)) return null;
1631
1752
  accounts.push(account);
1632
1753
  accountIds.add(account.accountId);
@@ -1634,10 +1755,15 @@ function normalizeCodexCredentialStore(value) {
1634
1755
  }
1635
1756
  return {
1636
1757
  version: 1,
1637
- accounts
1758
+ accounts: accounts.map((account, index) => {
1759
+ const alias = account.alias;
1760
+ if (!alias || !accounts.some((candidate, candidateIndex) => candidateIndex !== index && normalizeCodexAccountSelector(candidate.accountId) === normalizeCodexAccountSelector(alias))) return account;
1761
+ const { alias: _alias, ...credentials } = account;
1762
+ return credentials;
1763
+ })
1638
1764
  };
1639
1765
  }
1640
- async function writeCodexCredentialStore(store) {
1766
+ async function writeCodexCredentialStoreUnlocked(store) {
1641
1767
  await writeProtectedFile(PATHS.CODEX_CREDENTIAL_PATH, `${JSON.stringify(store, null, 2)}\n`);
1642
1768
  }
1643
1769
  async function readGitHubToken() {
@@ -1666,25 +1792,50 @@ async function readCodexCredentialStore() {
1666
1792
  async function writeCodexCredentials(credentials, options = {}) {
1667
1793
  const normalizedCredentials = normalizeCodexCredentials(credentials);
1668
1794
  if (!normalizedCredentials) throw new Error("Codex credentials are missing required fields");
1669
- const store = await readCodexCredentialStore() ?? {
1670
- version: 1,
1671
- accounts: []
1672
- };
1673
- const existingIndex = store.accounts.findIndex((account) => account.accountId === normalizedCredentials.accountId);
1674
- if (existingIndex < 0 && store.accounts.length >= 3) throw new Error(`Codex supports at most 3 accounts`);
1675
- const alias = options.alias?.trim();
1676
- if (alias && store.accounts.some((account, index) => index !== existingIndex && account.alias?.toLowerCase() === alias.toLowerCase())) throw new Error(`Codex account alias '${alias}' is already in use`);
1677
- const existingAccount = existingIndex >= 0 ? store.accounts[existingIndex] : void 0;
1678
- const nextAccount = {
1679
- ...normalizedCredentials,
1680
- ...alias ? { alias } : existingAccount?.alias ? { alias: existingAccount.alias } : {}
1681
- };
1682
- const accounts = [...store.accounts];
1683
- if (existingIndex >= 0) accounts[existingIndex] = nextAccount;
1684
- else accounts.push(nextAccount);
1685
- await writeCodexCredentialStore({
1686
- version: 1,
1687
- accounts
1795
+ await withCodexCredentialLock(async () => {
1796
+ const store = await readCodexCredentialStore() ?? {
1797
+ version: 1,
1798
+ accounts: []
1799
+ };
1800
+ const accountIdSelector = normalizeCodexAccountSelector(normalizedCredentials.accountId);
1801
+ const existingIndex = store.accounts.findIndex((account) => account.accountId === normalizedCredentials.accountId);
1802
+ if (existingIndex < 0 && options.insertIfMissing === false) return;
1803
+ if (existingIndex < 0 && store.accounts.some((account) => normalizeCodexAccountSelector(account.accountId) === accountIdSelector)) throw new Error(`Codex account id '${normalizedCredentials.accountId}' is already in use`);
1804
+ if (existingIndex < 0 && store.accounts.length >= 3) throw new Error(`Codex supports at most 3 accounts`);
1805
+ 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`);
1806
+ const alias = options.alias?.trim();
1807
+ if (alias) {
1808
+ const aliasSelector = normalizeCodexAccountSelector(alias);
1809
+ 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`);
1810
+ if (store.accounts.some((account, index) => index !== existingIndex && normalizeCodexAccountSelector(account.accountId) === aliasSelector)) throw new Error(`Codex account alias '${alias}' conflicts with another account id`);
1811
+ }
1812
+ const existingAccount = existingIndex >= 0 ? store.accounts[existingIndex] : void 0;
1813
+ const nextAccount = {
1814
+ ...normalizedCredentials,
1815
+ ...alias ? { alias } : existingAccount?.alias ? { alias: existingAccount.alias } : {}
1816
+ };
1817
+ const accounts = [...store.accounts];
1818
+ if (existingIndex >= 0) accounts[existingIndex] = nextAccount;
1819
+ else accounts.push(nextAccount);
1820
+ await writeCodexCredentialStoreUnlocked({
1821
+ version: 1,
1822
+ accounts
1823
+ });
1824
+ });
1825
+ }
1826
+ async function removeCodexCredentials(accountId) {
1827
+ const normalizedAccountId = accountId.trim();
1828
+ if (!normalizedAccountId) throw new Error("Codex account id must be a non-empty string");
1829
+ return await withCodexCredentialLock(async () => {
1830
+ const store = await readCodexCredentialStore();
1831
+ const index = store?.accounts.findIndex((account) => account.accountId === normalizedAccountId) ?? -1;
1832
+ if (!store || index < 0) throw new Error(`Codex account '${normalizedAccountId}' was not found`);
1833
+ const removedAccount = store.accounts[index];
1834
+ await writeCodexCredentialStoreUnlocked({
1835
+ version: 1,
1836
+ accounts: store.accounts.filter((_account, accountIndex) => accountIndex !== index)
1837
+ });
1838
+ return removedAccount;
1688
1839
  });
1689
1840
  }
1690
1841
  //#endregion
@@ -1868,7 +2019,7 @@ function getLoadedCodexCredentials() {
1868
2019
  };
1869
2020
  }
1870
2021
  function syncCodexProviderConfig(options) {
1871
- const existingProviderConfig = getRawProviderConfig("codex") ?? {};
2022
+ const existingProviderConfig = readEditableConfigFromDisk().providers?.codex ?? {};
1872
2023
  setProviderConfig("codex", {
1873
2024
  ...existingProviderConfig,
1874
2025
  type: "openai-responses",
@@ -1882,40 +2033,78 @@ function syncCodexProviderConfig(options) {
1882
2033
  function getConfiguredCodexAccountId() {
1883
2034
  return getRawProviderConfig("codex")?.accountId?.trim() || void 0;
1884
2035
  }
1885
- async function getCodexAccounts() {
1886
- const accounts = (await readCodexCredentialStore())?.accounts ?? [];
1887
- const implicitAccountId = getConfiguredCodexAccountId() ?? (accounts.length === 1 ? accounts[0].accountId : void 0);
1888
- return accounts.map((account) => ({
1889
- accountId: account.accountId,
1890
- ...account.alias ? { alias: account.alias } : {},
1891
- active: account.accountId === implicitAccountId
1892
- }));
2036
+ function getPersistedCodexAccountId() {
2037
+ return readEditableConfigFromDisk().providers?.codex?.accountId?.trim() || void 0;
1893
2038
  }
1894
- async function selectCodexAccount(selector) {
2039
+ function resolveActiveCodexAccountId(accounts, configuredAccountId = getConfiguredCodexAccountId()) {
2040
+ return configuredAccountId ?? (accounts.length === 1 ? accounts[0].accountId : void 0);
2041
+ }
2042
+ function normalizeCodexSelector(selector) {
1895
2043
  const normalizedSelector = selector.trim();
1896
2044
  if (!normalizedSelector) throw new Error("Codex account selector must be a non-empty string");
1897
- const accounts = (await readCodexCredentialStore())?.accounts ?? [];
1898
- const account = accounts.find((candidate) => candidate.accountId === normalizedSelector) ?? accounts.find((candidate) => candidate.alias?.toLowerCase() === normalizedSelector.toLowerCase());
1899
- if (!account) throw new Error(`Codex account '${normalizedSelector}' was not found`);
1900
- syncCodexProviderConfig({ accountId: account.accountId });
2045
+ return normalizedSelector;
2046
+ }
2047
+ function findCodexAccount(accounts, selector) {
2048
+ return accounts.find((candidate) => candidate.accountId === selector) ?? accounts.find((candidate) => candidate.alias?.toLowerCase() === selector.toLowerCase());
2049
+ }
2050
+ function toCodexAccountSummary(account, active) {
1901
2051
  return {
1902
2052
  accountId: account.accountId,
1903
2053
  ...account.alias ? { alias: account.alias } : {},
1904
- active: true
2054
+ active
1905
2055
  };
1906
2056
  }
1907
- async function persistCodexCredentials(credentials, options = {}) {
1908
- await writeCodexCredentials(credentials, { alias: options.alias });
1909
- if (options.syncProvider !== false) syncCodexProviderConfig({
1910
- accountId: options.activateAccount ? credentials.accountId : void 0,
1911
- enabled: options.enableProvider ? true : void 0
2057
+ async function getCodexAccounts() {
2058
+ return await withCodexAccountMutationLock(async () => {
2059
+ const accounts = (await readCodexCredentialStore())?.accounts ?? [];
2060
+ const activeAccountId = resolveActiveCodexAccountId(accounts, getPersistedCodexAccountId());
2061
+ return accounts.map((account) => toCodexAccountSummary(account, account.accountId === activeAccountId));
2062
+ });
2063
+ }
2064
+ async function selectCodexAccount(selector) {
2065
+ return await withCodexAccountMutationLock(async () => {
2066
+ const normalizedSelector = normalizeCodexSelector(selector);
2067
+ const account = findCodexAccount((await readCodexCredentialStore())?.accounts ?? [], normalizedSelector);
2068
+ if (!account) throw new Error(`Codex account '${normalizedSelector}' was not found`);
2069
+ syncCodexProviderConfig({ accountId: account.accountId });
2070
+ return toCodexAccountSummary(account, true);
2071
+ });
2072
+ }
2073
+ async function removeCodexAccount(selector) {
2074
+ return await withCodexAccountMutationLock(async () => {
2075
+ const normalizedSelector = normalizeCodexSelector(selector);
2076
+ const accounts = (await readCodexCredentialStore())?.accounts ?? [];
2077
+ const account = findCodexAccount(accounts, normalizedSelector);
2078
+ if (!account) throw new Error(`Codex account '${normalizedSelector}' was not found`);
2079
+ if (account.accountId === resolveActiveCodexAccountId(accounts, getPersistedCodexAccountId())) throw new Error(`Codex account '${account.accountId}' is currently in use; switch to another account before removing it`);
2080
+ return toCodexAccountSummary(await removeCodexCredentials(account.accountId), false);
1912
2081
  });
1913
- applyCodexCredentials(credentials);
1914
- codexCredentialsPendingPersistence = null;
2082
+ }
2083
+ async function persistCodexCredentials(credentials, options = {}) {
2084
+ const persist = async () => {
2085
+ await writeCodexCredentials(credentials, {
2086
+ alias: options.alias,
2087
+ insertIfMissing: options.insertIfMissing
2088
+ });
2089
+ if (options.syncProvider !== false) syncCodexProviderConfig({
2090
+ accountId: options.activateAccount ? credentials.accountId : void 0,
2091
+ enabled: options.enableProvider ? true : void 0
2092
+ });
2093
+ applyCodexCredentials(credentials);
2094
+ codexCredentialsPendingPersistence = null;
2095
+ };
2096
+ if (options.syncProvider === false) {
2097
+ await persist();
2098
+ return;
2099
+ }
2100
+ await withCodexAccountMutationLock(persist);
1915
2101
  }
1916
2102
  const defaultCodexRefreshDependencies = {
1917
2103
  getCurrentCredentials: getLoadedCodexCredentials,
1918
- persistCodexCredentials: (credentials) => persistCodexCredentials(credentials, { syncProvider: false }),
2104
+ persistCodexCredentials: (credentials) => persistCodexCredentials(credentials, {
2105
+ insertIfMissing: false,
2106
+ syncProvider: false
2107
+ }),
1919
2108
  refreshCodexCredentials
1920
2109
  };
1921
2110
  /**
@@ -2119,6 +2308,6 @@ async function logUser(dependencies = defaultCopilotTokenDependencies) {
2119
2308
  state.tokenBasedBilling = copilotUser.token_based_billing;
2120
2309
  }
2121
2310
  //#endregion
2122
- export { compactSystemPromptStarts as A, generateTraceId as B, prepareForCompact as C, compactAutoContinuePromptStarts as D, initOpencodeVersion as E, getMissingApiKeysMessage as F, createResponsesSafeStream as G, resolveTraceId as H, loginCodex as I, createPooledWebSocketStream as J, encodePoolKeyPart as K, CODEX_API_BASE_URL as L, createAuthMiddleware as M, getConfiguredAdminApiKeys as N, compactMessageSections as O, getConfiguredApiKeys as P, forwardError as Q, buildCodexRequestHeaders as R, copilotWebSocketHeaders as S, prepareMessageProxyHeaders as T, fetchUpstreamWithLifecycle as U, requestContext as V, createResponsesHttpEventStream as W, state as X, createWebSocketUrl as Y, HTTPError as Z, readGitHubToken as _, setupCodexToken as a, copilotHeaders as b, generateRequestIdFromPayload as c, isAsyncIterable as d, isNullish as f, GITHUB_TOKEN_ENV as g, getCopilotUsage as h, selectCodexAccount as i, compactTextOnlyGuard as j, compactSummaryPromptStart as k, getRootSessionId as l, parseUserIdMetadata as m, logUser as n, setupCopilotToken as o, isResponsesStream as p, isTerminalResponsesStreamChunk as q, persistCodexCredentials as r, setupGitHubToken as s, getCodexAccounts as t, getUUID as u, readGitHubTokenFromEnv as v, prepareInteractionHeaders as w, copilotModelsHeaders as x, copilotBaseUrl as y, forwardCodexResponses as z };
2311
+ export { forwardError as $, compactSummaryPromptStart as A, forwardCodexResponses as B, copilotWebSocketHeaders as C, initOpencodeVersion as D, prepareMessageProxyHeaders as E, getConfiguredApiKeys as F, createResponsesHttpEventStream as G, requestContext as H, getMissingApiKeysMessage as I, isTerminalResponsesStreamChunk as J, createResponsesSafeStream as K, loginCodex as L, compactTextOnlyGuard as M, createAuthMiddleware as N, compactAutoContinuePromptStarts as O, getConfiguredAdminApiKeys as P, HTTPError as Q, CODEX_API_BASE_URL as R, copilotModelsHeaders as S, prepareInteractionHeaders as T, resolveTraceId as U, generateTraceId as V, fetchUpstreamWithLifecycle as W, createWebSocketUrl as X, createPooledWebSocketStream as Y, state as Z, GITHUB_TOKEN_ENV as _, selectCodexAccount as a, copilotBaseUrl as b, setupGitHubToken as c, getUUID as d, isAsyncIterable as f, getCopilotUsage as g, parseUserIdMetadata as h, removeCodexAccount as i, compactSystemPromptStarts as j, compactMessageSections as k, generateRequestIdFromPayload as l, isResponsesStream as m, logUser as n, setupCodexToken as o, isNullish as p, encodePoolKeyPart as q, persistCodexCredentials as r, setupCopilotToken as s, getCodexAccounts as t, getRootSessionId as u, readGitHubToken as v, prepareForCompact as w, copilotHeaders as x, readGitHubTokenFromEnv as y, buildCodexRequestHeaders as z };
2123
2312
 
2124
- //# sourceMappingURL=token-4p5BAEqO.js.map
2313
+ //# sourceMappingURL=token-CGvXQOSn.js.map