@jeffreycao/copilot-api 1.10.9 → 1.10.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.
Files changed (33) hide show
  1. package/README.md +53 -213
  2. package/README.zh-CN.md +47 -209
  3. package/dist/auth-BO_SkMVw.js +116 -0
  4. package/dist/auth-BO_SkMVw.js.map +1 -0
  5. package/dist/{check-usage-BdXGp1Wr.js → check-usage-D-W6VD7k.js} +3 -4
  6. package/dist/{check-usage-BdXGp1Wr.js.map → check-usage-D-W6VD7k.js.map} +1 -1
  7. package/dist/{proxy-DvlF9a-7.js → config-ztdkLu9o.js} +83 -70
  8. package/dist/config-ztdkLu9o.js.map +1 -0
  9. package/dist/{debug-C_TBkyUw.js → debug-BVHmoCzY.js} +17 -7
  10. package/dist/debug-BVHmoCzY.js.map +1 -0
  11. package/dist/main.js +5 -5
  12. package/dist/{mcp-CTb-DbQH.js → mcp-DZgcvqQY.js} +2 -2
  13. package/dist/{mcp-CTb-DbQH.js.map → mcp-DZgcvqQY.js.map} +1 -1
  14. package/dist/{server-FPXzFkg9.js → server-2tRe3sDu.js} +1798 -1713
  15. package/dist/server-2tRe3sDu.js.map +1 -0
  16. package/dist/{start-CbKg_0bY.js → start-CM-b3DRX.js} +4 -6
  17. package/dist/{start-CbKg_0bY.js.map → start-CM-b3DRX.js.map} +1 -1
  18. package/dist/token-BVXHiYEl.js +1875 -0
  19. package/dist/token-BVXHiYEl.js.map +1 -0
  20. package/dist/{tool-search-D3SN0jX-.js → tool-search-wA-fLduL.js} +1 -1
  21. package/dist/{tool-search-D3SN0jX-.js.map → tool-search-wA-fLduL.js.map} +1 -1
  22. package/package.json +2 -2
  23. package/dist/auth-BHa2OHXf.js +0 -45
  24. package/dist/auth-BHa2OHXf.js.map +0 -1
  25. package/dist/debug-C_TBkyUw.js.map +0 -1
  26. package/dist/paths-DC-mqCY3.js +0 -30
  27. package/dist/paths-DC-mqCY3.js.map +0 -1
  28. package/dist/proxy-DvlF9a-7.js.map +0 -1
  29. package/dist/server-FPXzFkg9.js.map +0 -1
  30. package/dist/token-Dj8XsAxn.js +0 -170
  31. package/dist/token-Dj8XsAxn.js.map +0 -1
  32. package/dist/utils-jHLgqAq2.js +0 -657
  33. package/dist/utils-jHLgqAq2.js.map +0 -1
@@ -0,0 +1,1875 @@
1
+ import { p as isResponsesApiWebSocketEnabled, s as getRawProviderConfig, v as setProviderConfig, y as PATHS } from "./config-ztdkLu9o.js";
2
+ import consola from "consola";
3
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
4
+ import { createServer } from "node:http";
5
+ import fs, { readFile } from "node:fs/promises";
6
+ import { networkInterfaces } from "node:os";
7
+ import path from "node:path";
8
+ import { Agent, ProxyAgent, WebSocket, setGlobalDispatcher } from "undici";
9
+ import { getProxyForUrl } from "proxy-from-env";
10
+ import { AsyncLocalStorage } from "node:async_hooks";
11
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
12
+ import { exec } from "node:child_process";
13
+ //#region src/lib/codex-rate-limit.ts
14
+ const codexRateLimitScopes = ["primary", "secondary"];
15
+ const formatCodexRateLimitResetAt = (resetAt) => {
16
+ const date = /* @__PURE__ */ new Date(resetAt * 1e3);
17
+ return Number.isNaN(date.getTime()) ? String(resetAt) : date.toLocaleString();
18
+ };
19
+ const logCodexRateLimitsEvent = (event) => {
20
+ if (!event || typeof event !== "object") return;
21
+ const eventRecord = event;
22
+ if (eventRecord.type !== "codex.rate_limits") return;
23
+ const rateLimits = eventRecord.rate_limits;
24
+ if (!rateLimits || typeof rateLimits !== "object") return;
25
+ const planType = typeof eventRecord.plan_type === "string" ? eventRecord.plan_type : null;
26
+ const rateLimitRecord = rateLimits;
27
+ const allowed = typeof rateLimitRecord.allowed === "boolean" ? rateLimitRecord.allowed : null;
28
+ const limitReached = typeof rateLimitRecord.limit_reached === "boolean" ? rateLimitRecord.limit_reached : null;
29
+ for (const scope of codexRateLimitScopes) {
30
+ const window = rateLimitRecord[scope];
31
+ if (!isCodexRateLimitWindow(window)) continue;
32
+ const summary = [];
33
+ if (allowed !== null) summary.push(`allowed=${allowed}`);
34
+ if (limitReached !== null) summary.push(`limit_reached=${limitReached}`);
35
+ summary.push(`used=${window.used_percent}%`, `reset_at=${formatCodexRateLimitResetAt(window.reset_at)}`);
36
+ const label = planType ? `Codex ${scope} rate limit (${planType})` : `Codex ${scope} rate limit`;
37
+ consola.log(`${label}: ${summary.join(", ")}`);
38
+ }
39
+ };
40
+ const isCodexRateLimitWindow = (value) => {
41
+ if (!value || typeof value !== "object") return false;
42
+ const record = value;
43
+ return typeof record.reset_after_seconds === "number" && typeof record.reset_at === "number" && typeof record.used_percent === "number" && typeof record.window_minutes === "number";
44
+ };
45
+ //#endregion
46
+ //#region src/lib/state.ts
47
+ const state = {
48
+ accountType: "individual",
49
+ manualApprove: false,
50
+ rateLimitWait: false,
51
+ showToken: false,
52
+ verbose: false,
53
+ vsCodeDeviceId: randomUUID()
54
+ };
55
+ //#endregion
56
+ //#region src/lib/proxy.ts
57
+ let proxyEnvDispatcher;
58
+ function getProxyEnvDispatcher() {
59
+ return proxyEnvDispatcher;
60
+ }
61
+ function initProxyFromEnv() {
62
+ try {
63
+ const direct = new Agent();
64
+ const proxies = /* @__PURE__ */ new Map();
65
+ proxyEnvDispatcher = {
66
+ dispatch(options, handler) {
67
+ try {
68
+ const origin = typeof options.origin === "string" ? new URL(options.origin) : options.origin;
69
+ const raw = getProxyForUrl(origin.toString());
70
+ const proxyUrl = raw && raw.length > 0 ? raw : void 0;
71
+ if (!proxyUrl) {
72
+ consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
73
+ return direct.dispatch(options, handler);
74
+ }
75
+ let agent = proxies.get(proxyUrl);
76
+ if (!agent) {
77
+ agent = new ProxyAgent(proxyUrl);
78
+ proxies.set(proxyUrl, agent);
79
+ }
80
+ let label = proxyUrl;
81
+ try {
82
+ const u = new URL(proxyUrl);
83
+ label = `${u.protocol}//${u.host}`;
84
+ } catch {}
85
+ consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`);
86
+ return agent.dispatch(options, handler);
87
+ } catch {
88
+ return direct.dispatch(options, handler);
89
+ }
90
+ },
91
+ close() {
92
+ return direct.close();
93
+ },
94
+ destroy() {
95
+ return direct.destroy();
96
+ }
97
+ };
98
+ if (typeof Bun !== "undefined") {
99
+ consola.debug("WebSocket proxy configured from environment (per-URL)");
100
+ return;
101
+ }
102
+ setGlobalDispatcher(proxyEnvDispatcher);
103
+ consola.debug("HTTP proxy configured from environment (per-URL)");
104
+ } catch (err) {
105
+ consola.debug("Proxy setup skipped:", err);
106
+ }
107
+ }
108
+ //#endregion
109
+ //#region src/services/responses-websocket.ts
110
+ const DEFAULT_WEBSOCKET_IDLE_TIMEOUT_MS = 6e4;
111
+ const websocketPool = /* @__PURE__ */ new Map();
112
+ const websocketActiveRequests = /* @__PURE__ */ new Map();
113
+ const createWebSocketUrl = (url) => {
114
+ const websocketUrl = new URL(url);
115
+ if (websocketUrl.protocol === "https:") websocketUrl.protocol = "wss:";
116
+ else if (websocketUrl.protocol === "http:") websocketUrl.protocol = "ws:";
117
+ return websocketUrl.toString();
118
+ };
119
+ const createPooledWebSocketStream = (request, options) => runPooledWebSocketRequest(request, options);
120
+ const runPooledWebSocketRequest = async function* (request, options) {
121
+ const { entry, pooled } = getPooledWebSocketRequestTarget(request, options);
122
+ const release = acquirePooledWebSocketEntry(request.poolKey, entry, pooled, options);
123
+ try {
124
+ const websocket = await getReadyPooledWebSocket(request.poolKey, entry, pooled, options);
125
+ websocket.send(JSON.stringify(request.payload));
126
+ for await (const data of createWebSocketMessageStream(websocket, options)) {
127
+ const chunk = options.createChunk(data);
128
+ yield chunk;
129
+ if (options.isTerminalChunk(chunk)) return;
130
+ }
131
+ removePooledWebSocketEntry(request.poolKey, entry);
132
+ throw new Error(options.terminalChunkMissingMessage);
133
+ } catch (error) {
134
+ removePooledWebSocketEntry(request.poolKey, entry);
135
+ throw toError(error);
136
+ } finally {
137
+ release();
138
+ }
139
+ };
140
+ const getPooledWebSocketRequestTarget = (request, options) => {
141
+ if (getPooledWebSocketActiveRequestCount(request.poolKey) > 0) return {
142
+ entry: createPooledWebSocketEntry(request, options),
143
+ pooled: false
144
+ };
145
+ const existing = websocketPool.get(request.poolKey);
146
+ if (existing && !existing.closed) {
147
+ clearPooledWebSocketIdleTimer(existing);
148
+ return {
149
+ entry: existing,
150
+ pooled: true
151
+ };
152
+ }
153
+ const entry = createPooledWebSocketEntry(request, options);
154
+ websocketPool.set(request.poolKey, entry);
155
+ return {
156
+ entry,
157
+ pooled: true
158
+ };
159
+ };
160
+ const createPooledWebSocketEntry = (request, options) => {
161
+ const entry = {
162
+ closed: false,
163
+ idleTimer: null,
164
+ requestCount: 0,
165
+ websocketPromise: openWebSocket({
166
+ headers: request.headers,
167
+ openErrorMessage: options.openErrorMessage,
168
+ url: request.url
169
+ })
170
+ };
171
+ entry.websocketPromise.then((websocket) => {
172
+ websocket.addEventListener("close", () => {
173
+ removePooledWebSocketEntry(request.poolKey, entry);
174
+ });
175
+ websocket.addEventListener("error", () => {
176
+ removePooledWebSocketEntry(request.poolKey, entry);
177
+ });
178
+ }).catch(() => {
179
+ removePooledWebSocketEntry(request.poolKey, entry);
180
+ });
181
+ return entry;
182
+ };
183
+ const acquirePooledWebSocketEntry = (poolKey, entry, pooled, options) => {
184
+ clearPooledWebSocketIdleTimer(entry);
185
+ incrementPooledWebSocketActiveRequestCount(poolKey);
186
+ entry.requestCount += 1;
187
+ let released = false;
188
+ return () => {
189
+ if (released) return;
190
+ released = true;
191
+ entry.requestCount -= 1;
192
+ decrementPooledWebSocketActiveRequestCount(poolKey);
193
+ if (entry.closed || entry.requestCount > 0) return;
194
+ if (pooled && websocketPool.get(poolKey) === entry) {
195
+ schedulePooledWebSocketIdleClose(poolKey, entry, options);
196
+ return;
197
+ }
198
+ removePooledWebSocketEntry(poolKey, entry);
199
+ };
200
+ };
201
+ const getReadyPooledWebSocket = async (poolKey, entry, pooled, options) => {
202
+ const unavailableErrorMessage = options?.unavailableErrorMessage ?? "Websocket connection became unavailable before the request started";
203
+ if (entry.closed) throw new Error(unavailableErrorMessage);
204
+ const websocket = await entry.websocketPromise;
205
+ if (entry.closed || pooled && websocketPool.get(poolKey) !== entry) throw new Error(unavailableErrorMessage);
206
+ if (websocket.readyState !== WebSocket.OPEN) {
207
+ removePooledWebSocketEntry(poolKey, entry);
208
+ throw new Error(unavailableErrorMessage);
209
+ }
210
+ return websocket;
211
+ };
212
+ const schedulePooledWebSocketIdleClose = (poolKey, entry, options) => {
213
+ clearPooledWebSocketIdleTimer(entry);
214
+ entry.idleTimer = setTimeout(() => {
215
+ removePooledWebSocketEntry(poolKey, entry);
216
+ }, options.idleTimeoutMs ?? DEFAULT_WEBSOCKET_IDLE_TIMEOUT_MS);
217
+ unrefTimer(entry.idleTimer);
218
+ };
219
+ const clearPooledWebSocketIdleTimer = (entry) => {
220
+ if (entry.idleTimer) {
221
+ clearTimeout(entry.idleTimer);
222
+ entry.idleTimer = null;
223
+ }
224
+ };
225
+ const getPooledWebSocketActiveRequestCount = (poolKey) => websocketActiveRequests.get(poolKey) ?? 0;
226
+ const incrementPooledWebSocketActiveRequestCount = (poolKey) => {
227
+ websocketActiveRequests.set(poolKey, getPooledWebSocketActiveRequestCount(poolKey) + 1);
228
+ };
229
+ const decrementPooledWebSocketActiveRequestCount = (poolKey) => {
230
+ const nextCount = getPooledWebSocketActiveRequestCount(poolKey) - 1;
231
+ if (nextCount <= 0) {
232
+ websocketActiveRequests.delete(poolKey);
233
+ return;
234
+ }
235
+ websocketActiveRequests.set(poolKey, nextCount);
236
+ };
237
+ const removePooledWebSocketEntry = (poolKey, entry) => {
238
+ if (websocketPool.get(poolKey) === entry) websocketPool.delete(poolKey);
239
+ if (entry.closed) return;
240
+ entry.closed = true;
241
+ clearPooledWebSocketIdleTimer(entry);
242
+ entry.websocketPromise.then(closeWebSocket).catch(() => {});
243
+ };
244
+ const unrefTimer = (timer) => {
245
+ if (typeof timer === "object" && "unref" in timer && typeof timer.unref === "function") timer.unref();
246
+ };
247
+ const createWebSocketError = (message, event) => {
248
+ const reason = event?.error ?? event?.message;
249
+ if (reason === void 0 || reason === "") return new Error(message);
250
+ const cause = toError(reason);
251
+ return new Error(`${message}: ${cause.message}`, { cause });
252
+ };
253
+ const openWebSocket = async ({ headers, openErrorMessage, url }) => await new Promise((resolve, reject) => {
254
+ const dispatcher = getProxyEnvDispatcher();
255
+ const websocket = new WebSocket(url, dispatcher ? {
256
+ dispatcher,
257
+ headers
258
+ } : { headers });
259
+ const cleanup = () => {
260
+ websocket.removeEventListener("open", onOpen);
261
+ websocket.removeEventListener("error", onError);
262
+ };
263
+ const onOpen = () => {
264
+ cleanup();
265
+ resolve(websocket);
266
+ };
267
+ const onError = (event) => {
268
+ cleanup();
269
+ reject(createWebSocketError(openErrorMessage, event));
270
+ };
271
+ websocket.addEventListener("open", onOpen);
272
+ websocket.addEventListener("error", onError);
273
+ });
274
+ const createWebSocketMessageStream = async function* (websocket, options) {
275
+ const queue = [];
276
+ let closed = false;
277
+ let error = null;
278
+ let notify = null;
279
+ const wake = () => {
280
+ notify?.();
281
+ notify = null;
282
+ };
283
+ const onMessage = (event) => {
284
+ queue.push(normalizeWebSocketMessageData(event.data));
285
+ wake();
286
+ };
287
+ const onClose = () => {
288
+ closed = true;
289
+ wake();
290
+ };
291
+ const onError = (event) => {
292
+ error = createWebSocketError(options.streamErrorMessage, event);
293
+ wake();
294
+ };
295
+ websocket.addEventListener("message", onMessage);
296
+ websocket.addEventListener("close", onClose);
297
+ websocket.addEventListener("error", onError);
298
+ try {
299
+ while (true) {
300
+ const item = queue.shift();
301
+ if (item) {
302
+ yield await item;
303
+ continue;
304
+ }
305
+ if (error) throw toError(error);
306
+ if (closed) break;
307
+ await new Promise((resolve) => {
308
+ notify = resolve;
309
+ });
310
+ }
311
+ } finally {
312
+ websocket.removeEventListener("message", onMessage);
313
+ websocket.removeEventListener("close", onClose);
314
+ websocket.removeEventListener("error", onError);
315
+ }
316
+ };
317
+ const normalizeWebSocketMessageData = async (data) => {
318
+ if (typeof data === "string") return data;
319
+ if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
320
+ if (ArrayBuffer.isView(data)) {
321
+ const view = data;
322
+ return new TextDecoder().decode(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
323
+ }
324
+ if (isTextReadable(data)) return await data.text();
325
+ return String(data);
326
+ };
327
+ const isTextReadable = (value) => {
328
+ if (!value || typeof value !== "object" || !("text" in value)) return false;
329
+ return typeof value.text === "function";
330
+ };
331
+ const toError = (value) => {
332
+ if (value instanceof Error) return value;
333
+ return new Error(String(value));
334
+ };
335
+ const closeWebSocket = (websocket) => {
336
+ if (websocket.readyState === WebSocket.CONNECTING || websocket.readyState === WebSocket.OPEN) websocket.close();
337
+ };
338
+ //#endregion
339
+ //#region src/lib/request-context.ts
340
+ const TRACE_ID_MAX_LENGTH = 64;
341
+ const TRACE_ID_PATTERN = /^\w[\w.-]*$/;
342
+ const asyncLocalStorage = new AsyncLocalStorage();
343
+ const requestContext = {
344
+ getStore: () => asyncLocalStorage.getStore(),
345
+ run: (context, callback) => asyncLocalStorage.run(context, callback)
346
+ };
347
+ function generateTraceId() {
348
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
349
+ }
350
+ function resolveTraceId(traceId) {
351
+ const candidate = traceId?.trim();
352
+ if (!candidate || candidate.length > TRACE_ID_MAX_LENGTH || !TRACE_ID_PATTERN.test(candidate)) return generateTraceId();
353
+ return candidate;
354
+ }
355
+ //#endregion
356
+ //#region src/services/codex/create-responses.ts
357
+ const CODEX_API_BASE_URL = "https://chatgpt.com/backend-api";
358
+ const STRIPPED_CODEX_REQUEST_HEADERS = new Set([
359
+ "authorization",
360
+ "connection",
361
+ "content-length",
362
+ "host",
363
+ "keep-alive",
364
+ "proxy-authenticate",
365
+ "proxy-authorization",
366
+ "te",
367
+ "trailer",
368
+ "transfer-encoding",
369
+ "upgrade",
370
+ "x-api-key"
371
+ ]);
372
+ const STRIPPED_CODEX_WEBSOCKET_HEADERS = new Set(["accept", "content-type"]);
373
+ const CODEX_RESPONSE_STATUSES = new Set([
374
+ "completed",
375
+ "incomplete",
376
+ "failed",
377
+ "cancelled",
378
+ "queued",
379
+ "in_progress"
380
+ ]);
381
+ const requireCodexAuthContext = () => {
382
+ const accessToken = state.codexAccessToken;
383
+ const accountId = state.codexAccountId;
384
+ if (!accessToken) throw new Error("Codex access token is not loaded");
385
+ if (!accountId) throw new Error("Codex account id is not loaded");
386
+ return {
387
+ accessToken,
388
+ accountId
389
+ };
390
+ };
391
+ function resolveCodexResponsesUrl(baseUrl = CODEX_API_BASE_URL) {
392
+ const normalized = baseUrl.trim().replace(/\/+$/, "");
393
+ if (!normalized) return `${CODEX_API_BASE_URL}/codex/responses`;
394
+ if (normalized.endsWith("/codex/responses")) return normalized;
395
+ if (normalized.endsWith("/codex")) return `${normalized}/responses`;
396
+ return `${normalized}/codex/responses`;
397
+ }
398
+ function buildCodexResponsesHeaders(requestHeaders, options = {}) {
399
+ const { accessToken, accountId } = requireCodexAuthContext();
400
+ const headers = new Headers();
401
+ for (const [headerName, headerValue] of requestHeaders) {
402
+ const headerNameLower = headerName.toLowerCase();
403
+ if (STRIPPED_CODEX_REQUEST_HEADERS.has(headerNameLower)) continue;
404
+ headers.set(headerName, headerValue);
405
+ }
406
+ if (!headers.has("accept")) headers.set("accept", options.stream ? "text/event-stream" : "application/json");
407
+ headers.set("authorization", `Bearer ${accessToken}`);
408
+ headers.set("chatgpt-account-id", accountId);
409
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
410
+ if (!headers.has("OpenAI-Beta")) headers.set("OpenAI-Beta", "responses=experimental");
411
+ if (!headers.has("originator")) headers.set("originator", "copilot-api");
412
+ if (!headers.has("user-agent")) headers.set("user-agent", "copilot-api");
413
+ if (headers.get("user-agent")?.startsWith("opencode")) {
414
+ headers.set("originator", "opencode");
415
+ const sessionId = requestContext.getStore()?.sessionAffinity;
416
+ if (sessionId) headers.set("session_id", sessionId);
417
+ }
418
+ return headers;
419
+ }
420
+ function resolveCodexResponsesTransport(transport) {
421
+ return transport ?? (isResponsesApiWebSocketEnabled() ? "websocket" : "http");
422
+ }
423
+ function buildCodexResponsesWebSocketHeaders(requestHeaders) {
424
+ const headers = buildCodexResponsesHeaders(requestHeaders);
425
+ for (const headerName of STRIPPED_CODEX_WEBSOCKET_HEADERS) headers.delete(headerName);
426
+ return Object.fromEntries(headers);
427
+ }
428
+ function buildCodexResponsesWebSocketPayload(payload) {
429
+ const websocketPayload = {
430
+ ...payload,
431
+ type: "response.create"
432
+ };
433
+ delete websocketPayload.stream;
434
+ return websocketPayload;
435
+ }
436
+ function buildCodexResponsesWebSocketUrl(baseUrl = CODEX_API_BASE_URL) {
437
+ return createWebSocketUrl(resolveCodexResponsesUrl(baseUrl));
438
+ }
439
+ function prepareCodexResponsesWebSocketRequest(payload, requestHeaders, baseUrl = CODEX_API_BASE_URL) {
440
+ const headers = buildCodexResponsesWebSocketHeaders(requestHeaders);
441
+ return {
442
+ headers,
443
+ payload: buildCodexResponsesWebSocketPayload(payload),
444
+ poolKey: buildCodexResponsesWebSocketPoolKey(payload, headers, baseUrl),
445
+ url: buildCodexResponsesWebSocketUrl(baseUrl)
446
+ };
447
+ }
448
+ async function forwardCodexResponses(payload, requestHeaders, baseUrl = CODEX_API_BASE_URL, options = {}) {
449
+ const normalizedPayload = {
450
+ ...payload,
451
+ store: false,
452
+ temperature: void 0,
453
+ top_p: void 0,
454
+ max_output_tokens: void 0,
455
+ metadata: void 0
456
+ };
457
+ if (normalizedPayload.stream && resolveCodexResponsesTransport(options.transport) === "websocket") return await forwardCodexResponsesOverWebSocket(normalizedPayload, requestHeaders, baseUrl);
458
+ return await fetch(resolveCodexResponsesUrl(baseUrl), {
459
+ method: "POST",
460
+ headers: buildCodexResponsesHeaders(requestHeaders, { stream: normalizedPayload.stream }),
461
+ body: JSON.stringify(normalizedPayload)
462
+ });
463
+ }
464
+ const buildCodexResponsesWebSocketPoolKey = (payload, headers, baseUrl) => {
465
+ const authFingerprint = createHash("sha256").update(`${state.codexAccessToken ?? "missing-token"}:${state.codexAccountId ?? "missing-account"}`).digest("hex").slice(0, 16);
466
+ const headerFingerprint = createHash("sha256").update(JSON.stringify(Object.entries(headers).sort(([left], [right]) => left.localeCompare(right)))).digest("hex").slice(0, 16);
467
+ return [
468
+ "codex",
469
+ resolveCodexResponsesUrl(baseUrl),
470
+ payload.model,
471
+ authFingerprint,
472
+ headerFingerprint
473
+ ].map(encodePoolKeyPart).join("|");
474
+ };
475
+ const forwardCodexResponsesOverWebSocket = async (payload, requestHeaders, baseUrl) => {
476
+ const stream = createCodexResponsesWebSocketStream(prepareCodexResponsesWebSocketRequest(payload, requestHeaders, baseUrl));
477
+ if (payload.stream) return createCodexResponsesWebSocketProxyResponse(stream);
478
+ const response = await consumeCodexResponsesWebSocketStream(stream);
479
+ return new Response(JSON.stringify(response), {
480
+ headers: { "content-type": "application/json; charset=utf-8" },
481
+ status: 200
482
+ });
483
+ };
484
+ const createCodexResponsesWebSocketStream = (request) => createPooledWebSocketStream(request, {
485
+ createChunk: createCodexResponsesWebSocketStreamChunk,
486
+ isTerminalChunk: isTerminalCodexResponsesWebSocketChunk,
487
+ openErrorMessage: "Failed to create codex responses websocket",
488
+ streamErrorMessage: "Codex responses websocket stream error",
489
+ terminalChunkMissingMessage: "Codex responses websocket ended without a terminal response"
490
+ });
491
+ const createCodexResponsesWebSocketStreamChunk = (data) => {
492
+ if (data === "[DONE]") return { data };
493
+ try {
494
+ const parsed = JSON.parse(data);
495
+ return {
496
+ data: JSON.stringify(parsed),
497
+ event: typeof parsed.type === "string" ? parsed.type : void 0,
498
+ id: typeof parsed.id === "string" ? parsed.id : void 0
499
+ };
500
+ } catch {
501
+ return { data };
502
+ }
503
+ };
504
+ const isTerminalCodexResponsesWebSocketChunk = (chunk) => {
505
+ if (!chunk.data || chunk.data === "[DONE]") return false;
506
+ try {
507
+ const parsed = JSON.parse(chunk.data);
508
+ return parsed.type === "response.completed" || parsed.type === "response.done" || parsed.type === "response.failed" || parsed.type === "response.incomplete" || parsed.type === "error";
509
+ } catch {
510
+ return false;
511
+ }
512
+ };
513
+ const consumeCodexResponsesWebSocketStream = async (stream) => {
514
+ for await (const chunk of stream) {
515
+ if (!chunk.data || chunk.data === "[DONE]") continue;
516
+ const event = JSON.parse(chunk.data);
517
+ if (event.type === "error") throw new Error(typeof event.message === "string" ? event.message : "Codex responses websocket returned an error event");
518
+ if ((event.type === "response.completed" || event.type === "response.done") && event.response) return event.response;
519
+ if ((event.type === "response.failed" || event.type === "response.incomplete") && event.response) return event.response;
520
+ }
521
+ throw new Error("Codex responses websocket ended without a terminal response");
522
+ };
523
+ const createCodexResponsesWebSocketProxyResponse = (stream) => {
524
+ return new Response(createServerSentEventStream(normalizeCodexResponsesWebSocketStream(stream)), {
525
+ headers: {
526
+ "cache-control": "no-cache",
527
+ "content-type": "text/event-stream; charset=utf-8"
528
+ },
529
+ status: 200
530
+ });
531
+ };
532
+ const createStandardizedCodexResponsesEventStream = (source) => {
533
+ return createServerSentEventStream(normalizeCodexResponsesStandardStream(source));
534
+ };
535
+ const normalizeCodexResponsesWebSocketStream = async function* (stream) {
536
+ for await (const chunk of stream) yield normalizeCodexResponsesProxyChunk(chunk);
537
+ };
538
+ const normalizeCodexResponsesStandardStream = async function* (stream) {
539
+ for await (const chunk of stream) yield normalizeCodexResponsesStandardChunk(chunk);
540
+ };
541
+ const normalizeCodexResponsesProxyChunk = (chunk) => {
542
+ if (!chunk.data || chunk.data === "[DONE]") return chunk;
543
+ try {
544
+ const parsed = JSON.parse(chunk.data);
545
+ if (parsed.type !== "response.completed") return chunk;
546
+ return {
547
+ ...chunk,
548
+ data: JSON.stringify({
549
+ ...parsed,
550
+ type: "response.done"
551
+ }),
552
+ event: "response.done"
553
+ };
554
+ } catch {
555
+ return chunk;
556
+ }
557
+ };
558
+ const normalizeCodexResponsesStandardChunk = (chunk) => {
559
+ if (!chunk.data || chunk.data === "[DONE]") return chunk;
560
+ try {
561
+ const parsed = JSON.parse(chunk.data);
562
+ logCodexRateLimitsEvent(parsed);
563
+ const normalized = normalizeCodexResponsesEvent(parsed);
564
+ if (!normalized) return chunk;
565
+ return {
566
+ ...chunk,
567
+ data: JSON.stringify(normalized),
568
+ event: normalized.type
569
+ };
570
+ } catch {
571
+ return chunk;
572
+ }
573
+ };
574
+ const createServerSentEventStream = (source) => {
575
+ const encoder = new TextEncoder();
576
+ const iterator = source[Symbol.asyncIterator]();
577
+ return new ReadableStream({
578
+ async cancel() {
579
+ await iterator.return?.();
580
+ },
581
+ async pull(controller) {
582
+ try {
583
+ const result = await iterator.next();
584
+ if (result.done) {
585
+ controller.close();
586
+ return;
587
+ }
588
+ controller.enqueue(encoder.encode(serializeServerSentEvent(result.value)));
589
+ } catch (error) {
590
+ controller.enqueue(encoder.encode(serializeServerSentEvent(createResponsesErrorServerSentEventChunk(getErrorMessage(error)))));
591
+ controller.close();
592
+ }
593
+ }
594
+ });
595
+ };
596
+ const createResponsesErrorServerSentEventChunk = (message) => {
597
+ const errorEvent = {
598
+ code: null,
599
+ message,
600
+ param: null,
601
+ sequence_number: 0,
602
+ type: "error"
603
+ };
604
+ return {
605
+ data: JSON.stringify(errorEvent),
606
+ event: errorEvent.type
607
+ };
608
+ };
609
+ const serializeServerSentEvent = (chunk) => {
610
+ const lines = [];
611
+ if (chunk.id) lines.push(`id: ${chunk.id}`);
612
+ if (chunk.event) lines.push(`event: ${chunk.event}`);
613
+ if (chunk.data !== void 0) for (const line of String(chunk.data).split(/\r?\n/u)) lines.push(`data: ${line}`);
614
+ return `${lines.join("\n")}\n\n`;
615
+ };
616
+ const getErrorMessage = (error) => {
617
+ if (error instanceof Error && error.message) return error.message;
618
+ return String(error);
619
+ };
620
+ const encodePoolKeyPart = (value) => encodeURIComponent(value);
621
+ function normalizeCodexResponseStatus(status) {
622
+ return typeof status === "string" && CODEX_RESPONSE_STATUSES.has(status) ? status : void 0;
623
+ }
624
+ function normalizeCodexResponseRecord(eventRecord) {
625
+ const response = eventRecord.response;
626
+ if (!response || typeof response !== "object") return eventRecord;
627
+ const normalizedStatus = normalizeCodexResponseStatus(response.status);
628
+ if (!normalizedStatus) return eventRecord;
629
+ return {
630
+ ...eventRecord,
631
+ response: {
632
+ ...response,
633
+ status: normalizedStatus
634
+ }
635
+ };
636
+ }
637
+ function normalizeCodexResponsesEvent(event) {
638
+ if (!event || typeof event !== "object") return null;
639
+ const eventRecord = normalizeCodexResponseRecord(event);
640
+ const type = eventRecord.type;
641
+ if (typeof type !== "string") return null;
642
+ if (type === "response.done") return {
643
+ ...eventRecord,
644
+ type: "response.completed"
645
+ };
646
+ return eventRecord;
647
+ }
648
+ //#endregion
649
+ //#region src/lib/oauth/codex.ts
650
+ const CALLBACK_HOST = "127.0.0.1";
651
+ const CALLBACK_PORT = 1455;
652
+ const CALLBACK_PATH = "/auth/callback";
653
+ const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
654
+ const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
655
+ const TOKEN_URL = "https://auth.openai.com/oauth/token";
656
+ const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
657
+ const SCOPE = "openid profile email offline_access";
658
+ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
659
+ const REFRESH_BUFFER_MS = 6e4;
660
+ function base64UrlEncode(bytes) {
661
+ return Buffer.from(bytes).toString("base64url");
662
+ }
663
+ async function generatePkce() {
664
+ const verifierBytes = new Uint8Array(32);
665
+ crypto.getRandomValues(verifierBytes);
666
+ const verifier = base64UrlEncode(verifierBytes);
667
+ const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
668
+ return {
669
+ verifier,
670
+ challenge: base64UrlEncode(new Uint8Array(hashBuffer))
671
+ };
672
+ }
673
+ function createState() {
674
+ return randomBytes(16).toString("hex");
675
+ }
676
+ function parseAuthorizationInput(input) {
677
+ const value = input.trim();
678
+ if (!value) return {};
679
+ try {
680
+ const url = new URL(value);
681
+ return {
682
+ code: url.searchParams.get("code") ?? void 0,
683
+ state: url.searchParams.get("state") ?? void 0
684
+ };
685
+ } catch {}
686
+ if (value.includes("#")) {
687
+ const [code, state] = value.split("#", 2);
688
+ return {
689
+ code,
690
+ state
691
+ };
692
+ }
693
+ if (value.includes("code=")) {
694
+ const params = new URLSearchParams(value);
695
+ return {
696
+ code: params.get("code") ?? void 0,
697
+ state: params.get("state") ?? void 0
698
+ };
699
+ }
700
+ return { code: value };
701
+ }
702
+ function decodeJwt(accessToken) {
703
+ try {
704
+ const payload = accessToken.split(".")[1];
705
+ if (!payload) return null;
706
+ return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
707
+ } catch {
708
+ return null;
709
+ }
710
+ }
711
+ function getAccountId(accessToken) {
712
+ const payload = decodeJwt(accessToken);
713
+ if (!payload) return null;
714
+ const authPayload = payload[JWT_CLAIM_PATH];
715
+ if (!authPayload || typeof authPayload !== "object") return null;
716
+ const accountId = authPayload.chatgpt_account_id;
717
+ return typeof accountId === "string" && accountId ? accountId : null;
718
+ }
719
+ function renderOAuthPage(options) {
720
+ return `<!doctype html>
721
+ <html lang="en">
722
+ <head>
723
+ <meta charset="utf-8" />
724
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
725
+ <title>${escapeHtml(options.title)}</title>
726
+ <style>
727
+ body {
728
+ margin: 0;
729
+ min-height: 100vh;
730
+ display: flex;
731
+ align-items: center;
732
+ justify-content: center;
733
+ padding: 24px;
734
+ background: #09090b;
735
+ color: #fafafa;
736
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
737
+ text-align: center;
738
+ }
739
+ main {
740
+ max-width: 560px;
741
+ }
742
+ h1 {
743
+ margin: 0 0 12px;
744
+ font-size: 28px;
745
+ line-height: 1.15;
746
+ }
747
+ p {
748
+ margin: 0;
749
+ color: #a1a1aa;
750
+ line-height: 1.6;
751
+ }
752
+ </style>
753
+ </head>
754
+ <body>
755
+ <main>
756
+ <h1>${escapeHtml(options.heading)}</h1>
757
+ <p>${escapeHtml(options.message)}</p>
758
+ </main>
759
+ </body>
760
+ </html>`;
761
+ }
762
+ function escapeHtml(value) {
763
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
764
+ }
765
+ function renderOAuthSuccessPage(message) {
766
+ return renderOAuthPage({
767
+ title: "Authentication successful",
768
+ heading: "Authentication successful",
769
+ message
770
+ });
771
+ }
772
+ function renderOAuthErrorPage(message) {
773
+ return renderOAuthPage({
774
+ title: "Authentication failed",
775
+ heading: "Authentication failed",
776
+ message
777
+ });
778
+ }
779
+ async function exchangeAuthorizationCode(code, verifier) {
780
+ const response = await fetch(TOKEN_URL, {
781
+ method: "POST",
782
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
783
+ body: new URLSearchParams({
784
+ grant_type: "authorization_code",
785
+ client_id: CLIENT_ID,
786
+ code,
787
+ code_verifier: verifier,
788
+ redirect_uri: REDIRECT_URI
789
+ })
790
+ });
791
+ if (!response.ok) {
792
+ const details = await response.text().catch(() => "");
793
+ throw new Error(`Codex token exchange failed (${response.status}): ${details || response.statusText}`);
794
+ }
795
+ const payload = await response.json();
796
+ if (typeof payload.access_token !== "string" || typeof payload.refresh_token !== "string" || typeof payload.expires_in !== "number") throw new TypeError(`Codex token exchange response missing fields: ${JSON.stringify(payload)}`);
797
+ return {
798
+ accessToken: payload.access_token,
799
+ refreshToken: payload.refresh_token,
800
+ expiresAt: Date.now() + payload.expires_in * 1e3
801
+ };
802
+ }
803
+ async function refreshAccessToken(refreshToken) {
804
+ const response = await fetch(TOKEN_URL, {
805
+ method: "POST",
806
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
807
+ body: new URLSearchParams({
808
+ grant_type: "refresh_token",
809
+ refresh_token: refreshToken,
810
+ client_id: CLIENT_ID
811
+ })
812
+ });
813
+ if (!response.ok) {
814
+ const details = await response.text().catch(() => "");
815
+ throw new Error(`Codex token refresh failed (${response.status}): ${details || response.statusText}`);
816
+ }
817
+ const payload = await response.json();
818
+ if (typeof payload.access_token !== "string" || typeof payload.refresh_token !== "string" || typeof payload.expires_in !== "number") throw new TypeError(`Codex token refresh response missing fields: ${JSON.stringify(payload)}`);
819
+ return {
820
+ accessToken: payload.access_token,
821
+ refreshToken: payload.refresh_token,
822
+ expiresAt: Date.now() + payload.expires_in * 1e3
823
+ };
824
+ }
825
+ async function createAuthorizationFlow() {
826
+ const { verifier, challenge } = await generatePkce();
827
+ const state = createState();
828
+ const url = new URL(AUTHORIZE_URL);
829
+ url.searchParams.set("response_type", "code");
830
+ url.searchParams.set("client_id", CLIENT_ID);
831
+ url.searchParams.set("redirect_uri", REDIRECT_URI);
832
+ url.searchParams.set("scope", SCOPE);
833
+ url.searchParams.set("code_challenge", challenge);
834
+ url.searchParams.set("code_challenge_method", "S256");
835
+ url.searchParams.set("state", state);
836
+ url.searchParams.set("id_token_add_organizations", "true");
837
+ url.searchParams.set("codex_cli_simplified_flow", "true");
838
+ url.searchParams.set("originator", "copilot-api");
839
+ return {
840
+ verifier,
841
+ state,
842
+ url: url.toString()
843
+ };
844
+ }
845
+ async function waitForAuthorizationCode(state) {
846
+ let resolveCode;
847
+ const waitForCode = new Promise((resolve) => {
848
+ resolveCode = resolve;
849
+ });
850
+ const server = createServer((request, response) => {
851
+ try {
852
+ const url = new URL(request.url || "", "http://localhost");
853
+ if (url.pathname !== CALLBACK_PATH) {
854
+ response.statusCode = 404;
855
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
856
+ response.end(renderOAuthErrorPage("Callback route not found."));
857
+ return;
858
+ }
859
+ if (url.searchParams.get("state") !== state) {
860
+ response.statusCode = 400;
861
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
862
+ response.end(renderOAuthErrorPage("State mismatch."));
863
+ return;
864
+ }
865
+ const code = url.searchParams.get("code");
866
+ if (!code) {
867
+ response.statusCode = 400;
868
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
869
+ response.end(renderOAuthErrorPage("Missing authorization code."));
870
+ return;
871
+ }
872
+ response.statusCode = 200;
873
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
874
+ response.end(renderOAuthSuccessPage("OpenAI Codex authentication completed. You can close this window."));
875
+ resolveCode?.(code);
876
+ } catch {
877
+ response.statusCode = 500;
878
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
879
+ response.end(renderOAuthErrorPage("Internal error while processing OAuth callback."));
880
+ }
881
+ });
882
+ try {
883
+ await new Promise((resolve, reject) => {
884
+ server.once("error", reject);
885
+ server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
886
+ server.off("error", reject);
887
+ resolve();
888
+ });
889
+ });
890
+ } catch {
891
+ return null;
892
+ }
893
+ try {
894
+ return await waitForCode;
895
+ } finally {
896
+ await new Promise((resolve, reject) => {
897
+ server.close((error) => {
898
+ if (error) {
899
+ reject(error);
900
+ return;
901
+ }
902
+ resolve();
903
+ });
904
+ }).catch(() => void 0);
905
+ }
906
+ }
907
+ async function loginCodex(options) {
908
+ const { verifier, state, url } = await createAuthorizationFlow();
909
+ options.onAuth({
910
+ url,
911
+ instructions: "Please complete the login in the browser. If the browser does not automatically redirect, please paste the callback URL or code back to the terminal."
912
+ });
913
+ options.onProgress?.("Waiting for Codex OAuth callback");
914
+ let code = await waitForAuthorizationCode(state);
915
+ if (!code) {
916
+ const parsed = parseAuthorizationInput(await options.onPrompt("Paste the authorization code or full redirect URL:"));
917
+ if (parsed.state && parsed.state !== state) throw new Error("Codex OAuth state mismatch");
918
+ code = parsed.code ?? null;
919
+ }
920
+ if (!code) throw new Error("Missing Codex authorization code");
921
+ const tokenResult = await exchangeAuthorizationCode(code, verifier);
922
+ const accountId = getAccountId(tokenResult.accessToken);
923
+ if (!accountId) throw new Error("Failed to extract Codex account id from access token");
924
+ return {
925
+ accessToken: tokenResult.accessToken,
926
+ refreshToken: tokenResult.refreshToken,
927
+ expiresAt: tokenResult.expiresAt,
928
+ accountId
929
+ };
930
+ }
931
+ async function refreshCodexCredentials(credentials) {
932
+ const tokenResult = await refreshAccessToken(credentials.refreshToken);
933
+ const accountId = getAccountId(tokenResult.accessToken);
934
+ if (!accountId) throw new Error("Failed to extract Codex account id from access token");
935
+ return {
936
+ accessToken: tokenResult.accessToken,
937
+ refreshToken: tokenResult.refreshToken,
938
+ expiresAt: tokenResult.expiresAt,
939
+ accountId
940
+ };
941
+ }
942
+ function isCodexCredentialsExpired(credentials, now = Date.now()) {
943
+ return credentials.expiresAt <= now + REFRESH_BUFFER_MS;
944
+ }
945
+ const compactSystemPromptStarts = ["You are a helpful AI assistant tasked with summarizing conversations", "You are an anchored context summarization assistant for coding sessions."];
946
+ const compactTextOnlyGuard = "CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.";
947
+ const compactSummaryPromptStart = "Your task is to create a detailed summary of the conversation so far";
948
+ const compactAutoContinuePromptStarts = [
949
+ "This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.",
950
+ "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.",
951
+ "The previous request exceeded the provider's size limit due to large media attachments. The conversation was compacted and media files were removed from context."
952
+ ];
953
+ const compactMessageSections = ["Pending Tasks:", "Current Work:"];
954
+ //#endregion
955
+ //#region src/lib/opencode.ts
956
+ const execAsync = (command) => {
957
+ return new Promise((resolve, reject) => {
958
+ exec(command, (error, stdout) => {
959
+ if (error) {
960
+ reject(error);
961
+ return;
962
+ }
963
+ resolve(stdout);
964
+ });
965
+ });
966
+ };
967
+ let opencodeVersionCache;
968
+ const getGlobalNpmRoot = async () => {
969
+ return (await execAsync("npm root -g")).trim();
970
+ };
971
+ async function resolveOpencodeVersion() {
972
+ try {
973
+ const npmRootPath = await getGlobalNpmRoot();
974
+ const packageJson = await readFile(path.join(npmRootPath, "opencode-ai", "package.json"), "utf8");
975
+ const { version } = JSON.parse(packageJson);
976
+ opencodeVersionCache = version;
977
+ } catch (error) {
978
+ consola.warn(`Failed to resolve opencode version`, error);
979
+ }
980
+ }
981
+ const initOpencodeVersion = () => {
982
+ if (process.env.COPILOT_API_OAUTH_APP?.trim() !== "opencode") return Promise.resolve();
983
+ return resolveOpencodeVersion();
984
+ };
985
+ const getCachedOpencodeVersion = () => {
986
+ return opencodeVersionCache;
987
+ };
988
+ //#endregion
989
+ //#region src/lib/api-config.ts
990
+ const isOpencodeOauthApp = () => {
991
+ return process.env.COPILOT_API_OAUTH_APP?.trim() === "opencode";
992
+ };
993
+ const normalizeDomain = (input) => {
994
+ return input.trim().replace(/^https?:\/\//u, "").replace(/\/+$/u, "");
995
+ };
996
+ const getEnterpriseDomain = () => {
997
+ const raw = (process.env.COPILOT_API_ENTERPRISE_URL ?? "").trim();
998
+ if (!raw) return null;
999
+ return normalizeDomain(raw) || null;
1000
+ };
1001
+ const getGitHubBaseUrl = () => {
1002
+ const resolvedDomain = getEnterpriseDomain();
1003
+ return resolvedDomain ? `https://${resolvedDomain}` : GITHUB_BASE_URL;
1004
+ };
1005
+ const getGitHubApiBaseUrl = () => {
1006
+ const resolvedDomain = getEnterpriseDomain();
1007
+ return resolvedDomain ? `https://api.${resolvedDomain}` : GITHUB_API_BASE_URL;
1008
+ };
1009
+ const getOpencodeOauthHeaders = () => {
1010
+ return {
1011
+ Accept: "application/json",
1012
+ "Content-Type": "application/json",
1013
+ "User-Agent": getOpencodeVersion()
1014
+ };
1015
+ };
1016
+ const getOpencodeLLMHeaders = () => {
1017
+ return {
1018
+ Accept: "application/json",
1019
+ "Content-Type": "application/json",
1020
+ "User-Agent": OPENCODE_LLM_USER_AGENT
1021
+ };
1022
+ };
1023
+ const normalizeOpencodeUserAgent = (userAgent) => {
1024
+ const candidate = userAgent.trim();
1025
+ const opencodeProduct = candidate.match(/^opencode\/[^\s,]+/u)?.[0];
1026
+ if (!opencodeProduct || candidate.includes(`, ${opencodeProduct}`)) return candidate;
1027
+ return `${candidate}, ${opencodeProduct}`;
1028
+ };
1029
+ const getOauthUrls = () => {
1030
+ const githubBaseUrl = getGitHubBaseUrl();
1031
+ return {
1032
+ deviceCodeUrl: `${githubBaseUrl}/login/device/code`,
1033
+ accessTokenUrl: `${githubBaseUrl}/login/oauth/access_token`
1034
+ };
1035
+ };
1036
+ const getOauthAppConfig = () => {
1037
+ if (isOpencodeOauthApp()) return {
1038
+ clientId: OPENCODE_GITHUB_CLIENT_ID,
1039
+ headers: getOpencodeOauthHeaders(),
1040
+ scope: GITHUB_APP_SCOPES
1041
+ };
1042
+ return {
1043
+ clientId: GITHUB_CLIENT_ID,
1044
+ headers: standardHeaders(),
1045
+ scope: GITHUB_APP_SCOPES
1046
+ };
1047
+ };
1048
+ const prepareForCompact = (headers, compactType) => {
1049
+ if (compactType) {
1050
+ headers["x-initiator"] = "agent";
1051
+ if (!isOpencodeOauthApp() && compactType === 1) {
1052
+ headers["x-interaction-type"] = "conversation-compaction";
1053
+ headers["openai-intent"] = "conversation-agent";
1054
+ }
1055
+ }
1056
+ };
1057
+ const prepareInteractionHeaders = (sessionId, isSubagent, headers) => {
1058
+ const sendInteractionHeaders = !isOpencodeOauthApp();
1059
+ if (isSubagent) {
1060
+ headers["x-initiator"] = "agent";
1061
+ if (sendInteractionHeaders) headers["x-interaction-type"] = "conversation-subagent";
1062
+ }
1063
+ if (sessionId && sendInteractionHeaders) headers["x-interaction-id"] = sessionId;
1064
+ };
1065
+ const standardHeaders = () => ({
1066
+ "content-type": "application/json",
1067
+ accept: "application/json"
1068
+ });
1069
+ const getOpencodeVersion = () => {
1070
+ const version = getCachedOpencodeVersion();
1071
+ if (version) return "opencode/" + version;
1072
+ return OPENCODE_VERSION;
1073
+ };
1074
+ const OPENCODE_VERSION = "opencode/1.14.29";
1075
+ const OPENCODE_LLM_USER_AGENT = "opencode/1.14.29 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.13, opencode/1.14.29";
1076
+ const COPILOT_VERSION = "0.47.1";
1077
+ const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
1078
+ const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
1079
+ const CLAUDE_AGENT_USER_AGENT = "vscode_claude_code/2.1.112 (external, sdk-ts, agent-sdk/0.2.112)";
1080
+ const EDITOR_WEBSOCKET_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
1081
+ const API_VERSION = "2026-01-09";
1082
+ const WEBSOCKET_API_VERSION = API_VERSION;
1083
+ const copilotBaseUrl = (state) => {
1084
+ const enterpriseDomain = getEnterpriseDomain();
1085
+ if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;
1086
+ if (isOpencodeOauthApp()) return "https://api.githubcopilot.com";
1087
+ if (state.copilotApiUrl) return state.copilotApiUrl;
1088
+ return state.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state.accountType}.githubcopilot.com`;
1089
+ };
1090
+ const prepareMessageProxyHeaders = (headers) => {
1091
+ if (isOpencodeOauthApp()) return;
1092
+ const requestIdValue = randomUUID();
1093
+ headers["x-agent-task-id"] = requestIdValue;
1094
+ headers["x-request-id"] = requestIdValue;
1095
+ headers["x-interaction-type"] = "messages-proxy";
1096
+ headers["openai-intent"] = "messages-proxy";
1097
+ headers["user-agent"] = CLAUDE_AGENT_USER_AGENT;
1098
+ delete headers["copilot-integration-id"];
1099
+ };
1100
+ const githubUserHeaders = (state) => {
1101
+ if (isOpencodeOauthApp()) return {
1102
+ Authorization: `Bearer ${state.githubToken}`,
1103
+ "User-Agent": getOpencodeVersion()
1104
+ };
1105
+ return {
1106
+ accept: "application/vnd.github+json",
1107
+ authorization: `token ${state.githubToken}`,
1108
+ "user-agent": USER_AGENT,
1109
+ "x-github-api-version": "2022-11-28",
1110
+ "x-vscode-user-agent-library-version": "electron-fetch"
1111
+ };
1112
+ };
1113
+ const copilotModelsHeaders = (state) => {
1114
+ if (isOpencodeOauthApp()) return {
1115
+ Authorization: `Bearer ${state.copilotToken}`,
1116
+ "User-Agent": getOpencodeVersion()
1117
+ };
1118
+ const headers = githubCopilotHeaders(state);
1119
+ headers["x-interaction-type"] = "model-access";
1120
+ headers["openai-intent"] = "model-access";
1121
+ delete headers["x-interaction-id"];
1122
+ delete headers["content-type"];
1123
+ return headers;
1124
+ };
1125
+ const copilotHeaders = (state, requestId, vision = false) => {
1126
+ if (isOpencodeOauthApp()) {
1127
+ const headers = {
1128
+ Authorization: `Bearer ${state.copilotToken}`,
1129
+ ...getOpencodeLLMHeaders(),
1130
+ "Openai-Intent": "conversation-edits"
1131
+ };
1132
+ const store = requestContext.getStore();
1133
+ const userAgent = store?.userAgent.trim();
1134
+ if (userAgent?.startsWith("opencode/")) headers["User-Agent"] = normalizeOpencodeUserAgent(userAgent);
1135
+ if (store?.sessionAffinity) headers["x-session-affinity"] = store.sessionAffinity;
1136
+ if (store?.parentSessionId) headers["x-parent-session-id"] = store.parentSessionId;
1137
+ if (vision) headers["Copilot-Vision-Request"] = "true";
1138
+ return headers;
1139
+ }
1140
+ return githubCopilotHeaders(state, requestId, vision);
1141
+ };
1142
+ const copilotWebSocketHeaders = (preparedHeaders) => {
1143
+ if (isOpencodeOauthApp()) return omitHeader(preparedHeaders, "x-initiator");
1144
+ const requestId = getPreparedHeader(preparedHeaders, "x-request-id") ?? randomUUID();
1145
+ const source = createHeaderResolver(preparedHeaders);
1146
+ const headers = {
1147
+ Authorization: source("authorization"),
1148
+ "X-Request-Id": requestId,
1149
+ "OpenAI-Intent": source("openai-intent", "conversation-agent"),
1150
+ "X-GitHub-Api-Version": source("x-github-api-version", WEBSOCKET_API_VERSION),
1151
+ "X-Interaction-Id": source("x-interaction-id", requestId),
1152
+ "X-Interaction-Type": source("x-interaction-type", "conversation-agent"),
1153
+ "X-Agent-Task-Id": source("x-agent-task-id", requestId)
1154
+ };
1155
+ setPreparedHeader(headers, "VScode-SessionId", preparedHeaders, "vscode-sessionid");
1156
+ setPreparedHeader(headers, "VScode-MachineId", preparedHeaders, "vscode-machineid");
1157
+ Object.assign(headers, {
1158
+ "Editor-Device-Id": source("editor-device-id"),
1159
+ "Editor-Plugin-Version": source("editor-plugin-version", EDITOR_WEBSOCKET_PLUGIN_VERSION),
1160
+ "Editor-Version": source("editor-version"),
1161
+ "Copilot-Integration-Id": source("copilot-integration-id", "vscode-chat")
1162
+ });
1163
+ setPreparedHeader(headers, "Copilot-Vision-Request", preparedHeaders, "copilot-vision-request");
1164
+ headers["user-agent"] = "node";
1165
+ return headers;
1166
+ };
1167
+ const createHeaderResolver = (headers) => (headerName, fallback = "") => getPreparedHeader(headers, headerName) ?? fallback;
1168
+ const getPreparedHeader = (headers, headerName) => {
1169
+ const normalizedHeaderName = headerName.toLowerCase();
1170
+ return Object.entries(headers).find(([key]) => key.toLowerCase() === normalizedHeaderName)?.[1];
1171
+ };
1172
+ const setPreparedHeader = (target, targetHeaderName, source, sourceHeaderName) => {
1173
+ const value = getPreparedHeader(source, sourceHeaderName);
1174
+ if (value) target[targetHeaderName] = value;
1175
+ };
1176
+ const omitHeader = (headers, headerName) => {
1177
+ const normalizedHeaderName = headerName.toLowerCase();
1178
+ return Object.fromEntries(Object.entries(headers).filter(([key]) => key.toLowerCase() !== normalizedHeaderName));
1179
+ };
1180
+ const githubCopilotHeaders = (state, requestId, vision = false) => {
1181
+ const requestIdValue = requestId ?? randomUUID();
1182
+ const headers = {
1183
+ Authorization: `Bearer ${state.copilotToken}`,
1184
+ "content-type": standardHeaders()["content-type"],
1185
+ "copilot-integration-id": "vscode-chat",
1186
+ "editor-device-id": state.vsCodeDeviceId,
1187
+ "editor-version": `vscode/${state.vsCodeVersion}`,
1188
+ "editor-plugin-version": EDITOR_PLUGIN_VERSION,
1189
+ "user-agent": USER_AGENT,
1190
+ "openai-intent": "conversation-agent",
1191
+ "x-github-api-version": API_VERSION,
1192
+ "x-request-id": requestIdValue,
1193
+ "x-vscode-user-agent-library-version": "electron-fetch",
1194
+ "x-agent-task-id": requestIdValue,
1195
+ "x-interaction-type": "conversation-agent"
1196
+ };
1197
+ if (vision) headers["copilot-vision-request"] = "true";
1198
+ if (state.macMachineId) headers["vscode-machineid"] = state.macMachineId;
1199
+ if (state.vsCodeSessionId) headers["vscode-sessionid"] = state.vsCodeSessionId;
1200
+ return headers;
1201
+ };
1202
+ const GITHUB_API_BASE_URL = "https://api.github.com";
1203
+ const githubHeaders = (state) => {
1204
+ if (isOpencodeOauthApp()) return {
1205
+ Authorization: `Bearer ${state.githubToken}`,
1206
+ ...getOpencodeOauthHeaders()
1207
+ };
1208
+ return {
1209
+ authorization: `token ${state.githubToken}`,
1210
+ "user-agent": USER_AGENT,
1211
+ "x-github-api-version": "2025-04-01",
1212
+ "x-vscode-user-agent-library-version": "electron-fetch"
1213
+ };
1214
+ };
1215
+ const GITHUB_BASE_URL = "https://github.com";
1216
+ const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
1217
+ const GITHUB_APP_SCOPES = ["read:user"].join(" ");
1218
+ const OPENCODE_GITHUB_CLIENT_ID = "Ov23li8tweQw6odWQebz";
1219
+ //#endregion
1220
+ //#region src/lib/credential-store.ts
1221
+ function isNodeError(error) {
1222
+ return error instanceof Error && "code" in error;
1223
+ }
1224
+ async function readOptionalFile(filePath) {
1225
+ try {
1226
+ return await fs.readFile(filePath, "utf8");
1227
+ } catch (error) {
1228
+ if (isNodeError(error) && error.code === "ENOENT") return null;
1229
+ throw error;
1230
+ }
1231
+ }
1232
+ async function writeProtectedFile(filePath, content) {
1233
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
1234
+ await fs.writeFile(filePath, content, "utf8");
1235
+ try {
1236
+ await fs.chmod(filePath, 384);
1237
+ } catch {
1238
+ return;
1239
+ }
1240
+ }
1241
+ function normalizeCodexCredentials(credentials) {
1242
+ if (!credentials || typeof credentials !== "object") return null;
1243
+ const candidate = credentials;
1244
+ if (typeof candidate.accessToken !== "string" || typeof candidate.refreshToken !== "string" || typeof candidate.expiresAt !== "number" || typeof candidate.accountId !== "string") return null;
1245
+ return {
1246
+ accessToken: candidate.accessToken,
1247
+ refreshToken: candidate.refreshToken,
1248
+ expiresAt: candidate.expiresAt,
1249
+ accountId: candidate.accountId
1250
+ };
1251
+ }
1252
+ async function readGitHubToken() {
1253
+ return (await readOptionalFile(PATHS.GITHUB_TOKEN_PATH))?.trim() || null;
1254
+ }
1255
+ async function writeGitHubToken(token) {
1256
+ await writeProtectedFile(PATHS.GITHUB_TOKEN_PATH, token.trim());
1257
+ }
1258
+ async function readCodexCredentials() {
1259
+ const raw = await readOptionalFile(PATHS.CODEX_CREDENTIAL_PATH);
1260
+ if (!raw?.trim()) return null;
1261
+ let parsed;
1262
+ try {
1263
+ parsed = JSON.parse(raw);
1264
+ } catch (error) {
1265
+ throw new Error(`Codex credentials file is not valid JSON: ${PATHS.CODEX_CREDENTIAL_PATH}`, { cause: error });
1266
+ }
1267
+ const credentials = normalizeCodexCredentials(parsed);
1268
+ if (!credentials) throw new Error(`Codex credentials file is missing required fields: ${PATHS.CODEX_CREDENTIAL_PATH}`);
1269
+ return credentials;
1270
+ }
1271
+ async function writeCodexCredentials(credentials) {
1272
+ await writeProtectedFile(PATHS.CODEX_CREDENTIAL_PATH, `${JSON.stringify(credentials, null, 2)}\n`);
1273
+ }
1274
+ //#endregion
1275
+ //#region src/lib/error.ts
1276
+ var HTTPError = class extends Error {
1277
+ response;
1278
+ constructor(message, response) {
1279
+ super(message);
1280
+ this.response = response;
1281
+ }
1282
+ };
1283
+ async function forwardError(c, error) {
1284
+ consola.error("Error occurred:", error);
1285
+ if (error instanceof HTTPError) {
1286
+ if (error.response.status === 429) for (const [name, value] of error.response.headers) {
1287
+ const lowerName = name.toLowerCase();
1288
+ if (lowerName === "retry-after" || lowerName.startsWith("x-")) c.header(name, value);
1289
+ }
1290
+ const errorText = await error.response.text();
1291
+ let errorJson;
1292
+ try {
1293
+ errorJson = JSON.parse(errorText);
1294
+ } catch {
1295
+ errorJson = errorText;
1296
+ }
1297
+ consola.error("HTTP error:", errorJson);
1298
+ return c.json({ error: {
1299
+ message: errorText,
1300
+ type: "error"
1301
+ } }, error.response.status);
1302
+ }
1303
+ return c.json({ error: {
1304
+ message: error.message,
1305
+ type: "error"
1306
+ } }, 500);
1307
+ }
1308
+ //#endregion
1309
+ //#region src/services/github/get-copilot-token.ts
1310
+ const getCopilotToken = async () => {
1311
+ const response = await fetch(`${getGitHubApiBaseUrl()}/copilot_internal/v2/token`, { headers: githubHeaders(state) });
1312
+ if (!response.ok) {
1313
+ const errorText = await response.clone().text();
1314
+ consola.error("Failed to get Copilot token response body", errorText);
1315
+ throw new HTTPError("Failed to get Copilot token", response);
1316
+ }
1317
+ return await response.json();
1318
+ };
1319
+ //#endregion
1320
+ //#region src/services/github/get-copilot-usage.ts
1321
+ const getCopilotUsage = async (githubToken) => {
1322
+ const resolvedGithubToken = githubToken ?? state.githubToken;
1323
+ if (!resolvedGithubToken) throw new Error("GitHub token not found");
1324
+ const authState = {
1325
+ ...state,
1326
+ githubToken: resolvedGithubToken
1327
+ };
1328
+ const response = await fetch(`${getGitHubApiBaseUrl()}/copilot_internal/user`, { headers: githubHeaders(authState) });
1329
+ if (!response.ok) throw new HTTPError("Failed to get Copilot usage", response);
1330
+ return await response.json();
1331
+ };
1332
+ //#endregion
1333
+ //#region src/services/github/get-device-code.ts
1334
+ async function getDeviceCode() {
1335
+ const { clientId, headers, scope } = getOauthAppConfig();
1336
+ const { deviceCodeUrl } = getOauthUrls();
1337
+ const response = await fetch(deviceCodeUrl, {
1338
+ method: "POST",
1339
+ headers,
1340
+ body: JSON.stringify({
1341
+ client_id: clientId,
1342
+ scope
1343
+ })
1344
+ });
1345
+ if (!response.ok) throw new HTTPError("Failed to get device code", response);
1346
+ return await response.json();
1347
+ }
1348
+ //#endregion
1349
+ //#region src/services/github/get-user.ts
1350
+ async function getGitHubUser(githubToken) {
1351
+ const resolvedGithubToken = githubToken ?? state.githubToken;
1352
+ if (!resolvedGithubToken) throw new Error("GitHub token not found");
1353
+ const authState = {
1354
+ ...state,
1355
+ githubToken: resolvedGithubToken
1356
+ };
1357
+ const response = await fetch(`${getGitHubApiBaseUrl()}/user`, { headers: githubUserHeaders(authState) });
1358
+ if (!response.ok) throw new HTTPError("Failed to get GitHub user", response);
1359
+ return await response.json();
1360
+ }
1361
+ //#endregion
1362
+ //#region src/services/copilot/get-models.ts
1363
+ const getModels = async () => {
1364
+ consola.info(`Fetching models from ${copilotBaseUrl(state)}/models`);
1365
+ const response = await fetch(`${copilotBaseUrl(state)}/models`, { headers: copilotModelsHeaders(state) });
1366
+ if (!response.ok) {
1367
+ const errorText = await response.clone().text();
1368
+ consola.error("Failed to get models response body", errorText);
1369
+ throw new HTTPError("Failed to get models", response);
1370
+ }
1371
+ return await response.json();
1372
+ };
1373
+ //#endregion
1374
+ //#region src/services/get-vscode-version.ts
1375
+ const FALLBACK = "1.119.1";
1376
+ async function getVSCodeVersion() {
1377
+ await Promise.resolve();
1378
+ return FALLBACK;
1379
+ }
1380
+ //#endregion
1381
+ //#region src/lib/deviceid.ts
1382
+ const WINDOWS_DEVICE_ID_KEY = String.raw`\SOFTWARE\Microsoft\DeveloperTools`;
1383
+ const WINDOWS_DEVICE_ID_NAME = "deviceid";
1384
+ const windows64Architectures = new Set([
1385
+ "AMD64",
1386
+ "ARM64",
1387
+ "IA64"
1388
+ ]);
1389
+ const getPosixHomeDir = () => {
1390
+ if (!process.env.HOME) throw new Error("Home directory not found");
1391
+ return process.env.HOME;
1392
+ };
1393
+ const getDeviceIdFilePath = () => {
1394
+ let folder;
1395
+ switch (process.platform) {
1396
+ case "darwin":
1397
+ folder = path.posix.join(getPosixHomeDir(), "Library", "Application Support");
1398
+ break;
1399
+ case "linux":
1400
+ folder = process.env.XDG_CACHE_HOME ?? path.posix.join(getPosixHomeDir(), ".cache");
1401
+ break;
1402
+ default: throw new Error("Unsupported platform");
1403
+ }
1404
+ return path.posix.join(folder, "Microsoft", "DeveloperTools", "deviceid");
1405
+ };
1406
+ const isMissingFileError = (error) => {
1407
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
1408
+ };
1409
+ const readStoredDeviceIdFile = async (filePath) => {
1410
+ const { readFile } = await import("node:fs/promises");
1411
+ try {
1412
+ return await readFile(filePath, "utf8");
1413
+ } catch (error) {
1414
+ if (isMissingFileError(error)) return;
1415
+ throw error;
1416
+ }
1417
+ };
1418
+ const writeStoredDeviceIdFile = async (filePath, deviceId) => {
1419
+ const { mkdir, writeFile } = await import("node:fs/promises");
1420
+ await mkdir(path.posix.dirname(filePath), { recursive: true });
1421
+ await writeFile(filePath, deviceId, "utf8");
1422
+ };
1423
+ const getWindowsRegistryArch = () => {
1424
+ const architecture = (process.env.PROCESSOR_ARCHITEW6432 ?? process.env.PROCESSOR_ARCHITECTURE)?.toUpperCase();
1425
+ return architecture && windows64Architectures.has(architecture) ? "x64" : void 0;
1426
+ };
1427
+ const loadWinreg = async () => {
1428
+ const module = await import("winreg");
1429
+ return "default" in module ? module.default : module;
1430
+ };
1431
+ const isMissingRegistryError = (error) => {
1432
+ if (!error) return false;
1433
+ const errorCode = Number(error.code);
1434
+ return Number.isFinite(errorCode) && errorCode === 1;
1435
+ };
1436
+ const createWindowsRegistry = async () => {
1437
+ const Winreg = await loadWinreg();
1438
+ return {
1439
+ registry: new Winreg({
1440
+ hive: Winreg.HKCU,
1441
+ key: WINDOWS_DEVICE_ID_KEY,
1442
+ arch: getWindowsRegistryArch()
1443
+ }),
1444
+ regSz: Winreg.REG_SZ
1445
+ };
1446
+ };
1447
+ const readRegistryString = async (registry, name) => {
1448
+ return new Promise((resolve, reject) => {
1449
+ registry.get(name, (error, item) => {
1450
+ if (isMissingRegistryError(error)) {
1451
+ resolve(void 0);
1452
+ return;
1453
+ }
1454
+ if (error) {
1455
+ reject(error instanceof Error ? error : /* @__PURE__ */ new Error("Unknown registry error"));
1456
+ return;
1457
+ }
1458
+ resolve(item?.value);
1459
+ });
1460
+ });
1461
+ };
1462
+ const writeRegistryString = async ({ registry, regSz, name, value }) => {
1463
+ return new Promise((resolve, reject) => {
1464
+ registry.set(name, regSz, value, (error) => {
1465
+ if (error) {
1466
+ reject(error instanceof Error ? error : /* @__PURE__ */ new Error("Unknown registry error"));
1467
+ return;
1468
+ }
1469
+ resolve();
1470
+ });
1471
+ });
1472
+ };
1473
+ const getStoredVSCodeDeviceId = async () => {
1474
+ switch (process.platform) {
1475
+ case "win32": {
1476
+ const { registry } = await createWindowsRegistry();
1477
+ return readRegistryString(registry, WINDOWS_DEVICE_ID_NAME);
1478
+ }
1479
+ case "darwin":
1480
+ case "linux": return readStoredDeviceIdFile(getDeviceIdFilePath());
1481
+ default: throw new Error("Unsupported platform");
1482
+ }
1483
+ };
1484
+ const setStoredVSCodeDeviceId = async (deviceId) => {
1485
+ switch (process.platform) {
1486
+ case "win32": {
1487
+ const { registry, regSz } = await createWindowsRegistry();
1488
+ await writeRegistryString({
1489
+ registry,
1490
+ regSz,
1491
+ name: WINDOWS_DEVICE_ID_NAME,
1492
+ value: deviceId
1493
+ });
1494
+ return;
1495
+ }
1496
+ case "darwin":
1497
+ case "linux":
1498
+ await writeStoredDeviceIdFile(getDeviceIdFilePath(), deviceId);
1499
+ return;
1500
+ default: throw new Error("Unsupported platform");
1501
+ }
1502
+ };
1503
+ const createVSCodeDeviceId = () => randomUUID().toLowerCase();
1504
+ async function getVSCodeDeviceId() {
1505
+ let deviceId;
1506
+ try {
1507
+ deviceId = await getStoredVSCodeDeviceId();
1508
+ } catch (error) {
1509
+ consola.debug("Failed to read VSCode device id", error);
1510
+ }
1511
+ if (deviceId) return deviceId;
1512
+ const newDeviceId = createVSCodeDeviceId();
1513
+ try {
1514
+ await setStoredVSCodeDeviceId(newDeviceId);
1515
+ } catch (error) {
1516
+ consola.warn("Failed to persist VSCode device id, using ephemeral id", error);
1517
+ }
1518
+ return newDeviceId;
1519
+ }
1520
+ //#endregion
1521
+ //#region src/lib/utils.ts
1522
+ const sleep = (ms) => new Promise((resolve) => {
1523
+ setTimeout(resolve, ms);
1524
+ });
1525
+ const isNullish = (value) => value === null || value === void 0;
1526
+ async function cacheModels() {
1527
+ const models = await getModels();
1528
+ state.models = {
1529
+ ...models,
1530
+ data: models.data.filter((model) => model.model_picker_enabled || model.capabilities.type === "embeddings")
1531
+ };
1532
+ }
1533
+ const cacheVSCodeVersion = async () => {
1534
+ const response = await getVSCodeVersion();
1535
+ state.vsCodeVersion = response;
1536
+ consola.info(`Using VSCode version: ${response}`);
1537
+ };
1538
+ const invalidMacAddresses = new Set([
1539
+ "00:00:00:00:00:00",
1540
+ "ff:ff:ff:ff:ff:ff",
1541
+ "ac:de:48:00:11:22"
1542
+ ]);
1543
+ function validateMacAddress(candidate) {
1544
+ const tempCandidate = candidate.replaceAll("-", ":").toLowerCase();
1545
+ return !invalidMacAddresses.has(tempCandidate);
1546
+ }
1547
+ function getMac() {
1548
+ const ifaces = networkInterfaces();
1549
+ for (const name in ifaces) {
1550
+ const networkInterface = ifaces[name];
1551
+ if (networkInterface) {
1552
+ for (const { mac } of networkInterface) if (validateMacAddress(mac)) return mac;
1553
+ }
1554
+ }
1555
+ return null;
1556
+ }
1557
+ const cacheMacMachineId = () => {
1558
+ const macAddress = getMac() ?? randomUUID();
1559
+ state.macMachineId = createHash("sha256").update(macAddress, "utf8").digest("hex");
1560
+ consola.debug(`Using machine ID: ${state.macMachineId}`);
1561
+ };
1562
+ const cacheVsCodeDeviceId = async () => {
1563
+ state.vsCodeDeviceId = await getVSCodeDeviceId();
1564
+ consola.debug(`Using VSCode device ID: ${state.vsCodeDeviceId}`);
1565
+ };
1566
+ const SESSION_REFRESH_BASE_MS = 3600 * 1e3;
1567
+ const SESSION_REFRESH_JITTER_MS = 1200 * 1e3;
1568
+ let vsCodeSessionRefreshTimer = null;
1569
+ const generateSessionId = () => {
1570
+ state.vsCodeSessionId = randomUUID() + Date.now().toString();
1571
+ consola.debug(`Generated VSCode session ID: ${state.vsCodeSessionId}`);
1572
+ };
1573
+ const stopVsCodeSessionRefreshLoop = () => {
1574
+ if (vsCodeSessionRefreshTimer) {
1575
+ clearTimeout(vsCodeSessionRefreshTimer);
1576
+ vsCodeSessionRefreshTimer = null;
1577
+ }
1578
+ };
1579
+ const scheduleSessionIdRefresh = () => {
1580
+ const delay = SESSION_REFRESH_BASE_MS + Math.floor(Math.random() * SESSION_REFRESH_JITTER_MS);
1581
+ consola.debug(`Scheduling next VSCode session ID refresh in ${Math.round(delay / 1e3)} seconds`);
1582
+ stopVsCodeSessionRefreshLoop();
1583
+ vsCodeSessionRefreshTimer = setTimeout(() => {
1584
+ try {
1585
+ generateSessionId();
1586
+ } catch (error) {
1587
+ consola.error("Failed to refresh session ID, rescheduling...", error);
1588
+ } finally {
1589
+ scheduleSessionIdRefresh();
1590
+ }
1591
+ }, delay);
1592
+ };
1593
+ const cacheVsCodeSessionId = () => {
1594
+ stopVsCodeSessionRefreshLoop();
1595
+ generateSessionId();
1596
+ scheduleSessionIdRefresh();
1597
+ };
1598
+ const isRecord = (value) => typeof value === "object" && value !== null;
1599
+ const getUserIdJsonField = (userIdPayload, field) => {
1600
+ const value = userIdPayload?.[field];
1601
+ return typeof value === "string" && value.length > 0 ? value : null;
1602
+ };
1603
+ const parseJsonUserId = (userId) => {
1604
+ try {
1605
+ const parsed = JSON.parse(userId);
1606
+ return isRecord(parsed) ? parsed : null;
1607
+ } catch {
1608
+ return null;
1609
+ }
1610
+ };
1611
+ const parseUserIdMetadata = (userId) => {
1612
+ if (!userId || typeof userId !== "string") return {
1613
+ safetyIdentifier: null,
1614
+ sessionId: null
1615
+ };
1616
+ const legacySafetyIdentifier = userId.match(/user_([^_]+)_account/)?.[1] ?? null;
1617
+ const legacySessionId = userId.match(/_session_(.+)$/)?.[1] ?? null;
1618
+ const parsedUserId = legacySafetyIdentifier && legacySessionId ? null : parseJsonUserId(userId);
1619
+ return {
1620
+ safetyIdentifier: legacySafetyIdentifier ?? getUserIdJsonField(parsedUserId, "device_id") ?? getUserIdJsonField(parsedUserId, "account_uuid"),
1621
+ sessionId: legacySessionId ?? getUserIdJsonField(parsedUserId, "session_id")
1622
+ };
1623
+ };
1624
+ const findLastUserContent = (messages) => {
1625
+ for (let i = messages.length - 1; i >= 0; i--) {
1626
+ const msg = messages[i];
1627
+ if (msg.role === "user" && msg.content) {
1628
+ if (typeof msg.content === "string") return msg.content;
1629
+ else if (Array.isArray(msg.content)) {
1630
+ const array = msg.content.filter((n) => n.type !== "tool_result").map((n) => ({
1631
+ ...n,
1632
+ cache_control: void 0
1633
+ }));
1634
+ if (array.length > 0) return JSON.stringify(array);
1635
+ }
1636
+ }
1637
+ }
1638
+ return null;
1639
+ };
1640
+ const generateRequestIdFromPayload = (payload, sessionId) => {
1641
+ const messages = payload.messages;
1642
+ if (messages) {
1643
+ const lastUserContent = typeof messages === "string" ? messages : findLastUserContent(messages);
1644
+ if (lastUserContent) return getUUID((sessionId ?? "") + (state.macMachineId ?? "") + lastUserContent);
1645
+ }
1646
+ return randomUUID();
1647
+ };
1648
+ const getRootSessionId = (anthropicPayload, c) => {
1649
+ const userId = anthropicPayload.metadata?.user_id;
1650
+ const sessionId = userId ? parseUserIdMetadata(userId).sessionId || void 0 : c.req.header("x-session-id");
1651
+ return sessionId ? getUUID(sessionId) : sessionId;
1652
+ };
1653
+ const getUUID = (content) => {
1654
+ const uuidBytes = createHash("sha256").update(content).digest().subarray(0, 16);
1655
+ uuidBytes[6] = uuidBytes[6] & 15 | 64;
1656
+ uuidBytes[8] = uuidBytes[8] & 63 | 128;
1657
+ const uuidHex = uuidBytes.toString("hex");
1658
+ return `${uuidHex.slice(0, 8)}-${uuidHex.slice(8, 12)}-${uuidHex.slice(12, 16)}-${uuidHex.slice(16, 20)}-${uuidHex.slice(20)}`;
1659
+ };
1660
+ //#endregion
1661
+ //#region src/services/github/poll-access-token.ts
1662
+ async function pollAccessToken(deviceCode) {
1663
+ const { clientId, headers } = getOauthAppConfig();
1664
+ const { accessTokenUrl } = getOauthUrls();
1665
+ const sleepDuration = (deviceCode.interval + 1) * 1e3;
1666
+ consola.debug(`Polling access token with interval of ${sleepDuration}ms`);
1667
+ while (true) {
1668
+ const response = await fetch(accessTokenUrl, {
1669
+ method: "POST",
1670
+ headers,
1671
+ body: JSON.stringify({
1672
+ client_id: clientId,
1673
+ device_code: deviceCode.device_code,
1674
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
1675
+ })
1676
+ });
1677
+ if (!response.ok) {
1678
+ await sleep(sleepDuration);
1679
+ consola.error("Failed to poll access token:", await response.text());
1680
+ continue;
1681
+ }
1682
+ const json = await response.json();
1683
+ consola.debug("Polling access token response:", json);
1684
+ const { access_token } = json;
1685
+ if (access_token) return access_token;
1686
+ else await sleep(sleepDuration);
1687
+ }
1688
+ }
1689
+ //#endregion
1690
+ //#region src/lib/token.ts
1691
+ let copilotRefreshLoopController = null;
1692
+ let codexRefreshLoopController = null;
1693
+ const stopCopilotRefreshLoop = () => {
1694
+ if (!copilotRefreshLoopController) return;
1695
+ copilotRefreshLoopController.abort();
1696
+ copilotRefreshLoopController = null;
1697
+ };
1698
+ const stopCodexRefreshLoop = () => {
1699
+ if (!codexRefreshLoopController) return;
1700
+ codexRefreshLoopController.abort();
1701
+ codexRefreshLoopController = null;
1702
+ };
1703
+ function applyCodexCredentials(credentials) {
1704
+ state.codexAccessToken = credentials.accessToken;
1705
+ state.codexRefreshToken = credentials.refreshToken;
1706
+ state.codexExpiresAt = credentials.expiresAt;
1707
+ state.codexAccountId = credentials.accountId;
1708
+ consola.debug("Codex credentials loaded successfully");
1709
+ if (state.showToken) consola.info("Codex access token:", credentials.accessToken);
1710
+ }
1711
+ function getLoadedCodexCredentials() {
1712
+ if (!state.codexAccessToken || !state.codexRefreshToken || !state.codexExpiresAt || !state.codexAccountId) return null;
1713
+ return {
1714
+ accessToken: state.codexAccessToken,
1715
+ refreshToken: state.codexRefreshToken,
1716
+ expiresAt: state.codexExpiresAt,
1717
+ accountId: state.codexAccountId
1718
+ };
1719
+ }
1720
+ function syncCodexProviderConfig(options) {
1721
+ const existingProviderConfig = getRawProviderConfig("codex") ?? {};
1722
+ setProviderConfig("codex", {
1723
+ ...existingProviderConfig,
1724
+ type: "openai-responses",
1725
+ enabled: options?.enabled ?? existingProviderConfig.enabled,
1726
+ baseUrl: CODEX_API_BASE_URL,
1727
+ authType: "oauth2"
1728
+ });
1729
+ }
1730
+ async function persistCodexCredentials(credentials, options) {
1731
+ await writeCodexCredentials(credentials);
1732
+ syncCodexProviderConfig({ enabled: options?.enableProvider ? true : void 0 });
1733
+ applyCodexCredentials(credentials);
1734
+ }
1735
+ const setupCopilotToken = async () => {
1736
+ if (isOpencodeOauthApp()) {
1737
+ if (!state.githubToken) throw new Error(`opencode token not found`);
1738
+ state.copilotToken = state.githubToken;
1739
+ consola.debug("GitHub Copilot token set from opencode auth token");
1740
+ if (state.showToken) consola.info("Copilot token:", state.copilotToken);
1741
+ stopCopilotRefreshLoop();
1742
+ return;
1743
+ }
1744
+ const { token, refresh_in } = await getCopilotToken();
1745
+ state.copilotToken = token;
1746
+ consola.debug("GitHub Copilot Token fetched successfully!");
1747
+ if (state.showToken) consola.info("Copilot token:", token);
1748
+ stopCopilotRefreshLoop();
1749
+ const controller = new AbortController();
1750
+ copilotRefreshLoopController = controller;
1751
+ runCopilotRefreshLoop(refresh_in, controller.signal).catch(() => {
1752
+ consola.warn("Copilot token refresh loop stopped");
1753
+ }).finally(() => {
1754
+ if (copilotRefreshLoopController === controller) copilotRefreshLoopController = null;
1755
+ });
1756
+ };
1757
+ const setupCodexToken = async () => {
1758
+ const loadedCredentials = getLoadedCodexCredentials();
1759
+ if (loadedCredentials && !isCodexCredentialsExpired(loadedCredentials)) {
1760
+ if (codexRefreshLoopController) return;
1761
+ applyCodexCredentials(loadedCredentials);
1762
+ }
1763
+ const credentials = loadedCredentials ?? await readCodexCredentials();
1764
+ if (!credentials) throw new Error(`Codex credentials not found. Run \`copilot-api auth login --provider codex\` first.`);
1765
+ syncCodexProviderConfig();
1766
+ let nextCredentials = credentials;
1767
+ if (isCodexCredentialsExpired(credentials)) {
1768
+ consola.debug("Refreshing expired Codex credentials");
1769
+ nextCredentials = await refreshCodexCredentials(credentials);
1770
+ await persistCodexCredentials(nextCredentials);
1771
+ }
1772
+ applyCodexCredentials(nextCredentials);
1773
+ stopCodexRefreshLoop();
1774
+ const controller = new AbortController();
1775
+ codexRefreshLoopController = controller;
1776
+ runCodexRefreshLoop(controller.signal).catch(() => {
1777
+ consola.warn("Codex token refresh loop stopped");
1778
+ }).finally(() => {
1779
+ if (codexRefreshLoopController === controller) codexRefreshLoopController = null;
1780
+ });
1781
+ };
1782
+ const REFRESH_POLL_INTERVAL_MS = 15e3;
1783
+ const EARLY_REFRESH_BUFFER_MS = 6e4;
1784
+ const RETRY_REFRESH_DELAY_MS = 15e3;
1785
+ const MIN_REFRESH_DELAY_MS = 1e3;
1786
+ const getRefreshDeadlineMs = (refreshIn, nowMs = Date.now()) => nowMs + Math.max(refreshIn * 1e3 - EARLY_REFRESH_BUFFER_MS, MIN_REFRESH_DELAY_MS);
1787
+ const getRefreshPollDelayMs = (refreshAtMs, nowMs = Date.now()) => Math.min(Math.max(refreshAtMs - nowMs, 0), REFRESH_POLL_INTERVAL_MS);
1788
+ const runCopilotRefreshLoop = async (refreshIn, signal) => {
1789
+ let refreshAtMs = getRefreshDeadlineMs(refreshIn);
1790
+ while (!signal.aborted) {
1791
+ const nextDelayMs = getRefreshPollDelayMs(refreshAtMs);
1792
+ if (nextDelayMs > 0) {
1793
+ await setTimeout$1(nextDelayMs, void 0, { signal });
1794
+ continue;
1795
+ }
1796
+ consola.debug("Refreshing Copilot token");
1797
+ try {
1798
+ const { token, refresh_in } = await getCopilotToken();
1799
+ state.copilotToken = token;
1800
+ refreshAtMs = getRefreshDeadlineMs(refresh_in);
1801
+ consola.debug("Copilot token refreshed");
1802
+ if (state.showToken) consola.info("Refreshed Copilot token:", token);
1803
+ } catch (error) {
1804
+ consola.error("Failed to refresh Copilot token:", error);
1805
+ refreshAtMs = Date.now() + RETRY_REFRESH_DELAY_MS;
1806
+ consola.warn(`Retrying Copilot token refresh in ${RETRY_REFRESH_DELAY_MS / 1e3}s`);
1807
+ }
1808
+ }
1809
+ };
1810
+ const runCodexRefreshLoop = async (signal) => {
1811
+ let refreshAtMs = Math.max((state.codexExpiresAt ?? Date.now()) - EARLY_REFRESH_BUFFER_MS, Date.now());
1812
+ while (!signal.aborted) {
1813
+ const expiresAt = state.codexExpiresAt;
1814
+ const refreshToken = state.codexRefreshToken;
1815
+ if (!expiresAt || !refreshToken) return;
1816
+ const nextDelayMs = getRefreshPollDelayMs(refreshAtMs);
1817
+ if (nextDelayMs > 0) {
1818
+ await setTimeout$1(nextDelayMs, void 0, { signal });
1819
+ continue;
1820
+ }
1821
+ consola.debug("Refreshing Codex credentials");
1822
+ try {
1823
+ const credentials = await refreshCodexCredentials({
1824
+ accessToken: state.codexAccessToken ?? "",
1825
+ refreshToken,
1826
+ expiresAt,
1827
+ accountId: state.codexAccountId ?? ""
1828
+ });
1829
+ await persistCodexCredentials(credentials);
1830
+ refreshAtMs = Math.max(credentials.expiresAt - EARLY_REFRESH_BUFFER_MS, Date.now());
1831
+ consola.debug("Codex credentials refreshed");
1832
+ } catch (error) {
1833
+ consola.error("Failed to refresh Codex credentials:", error);
1834
+ refreshAtMs = Date.now() + RETRY_REFRESH_DELAY_MS;
1835
+ consola.warn(`Retrying Codex token refresh in ${RETRY_REFRESH_DELAY_MS / 1e3}s`);
1836
+ }
1837
+ }
1838
+ };
1839
+ async function setupGitHubToken(options) {
1840
+ try {
1841
+ const githubToken = await readGitHubToken();
1842
+ if (githubToken && !options?.force) {
1843
+ state.githubToken = githubToken;
1844
+ if (state.showToken) consola.info("GitHub token:", githubToken);
1845
+ await logUser();
1846
+ return;
1847
+ }
1848
+ consola.info("Not logged in, getting new access token");
1849
+ const response = await getDeviceCode();
1850
+ consola.debug("Device code response:", response);
1851
+ consola.info(`Please enter the code "${response.user_code}" in ${response.verification_uri}`);
1852
+ const token = await pollAccessToken(response);
1853
+ await writeGitHubToken(token);
1854
+ state.githubToken = token;
1855
+ if (state.showToken) consola.info("GitHub token:", token);
1856
+ await logUser();
1857
+ } catch (error) {
1858
+ if (error instanceof HTTPError) {
1859
+ consola.error("Failed to get GitHub token:", await error.response.json());
1860
+ throw error;
1861
+ }
1862
+ consola.error("Failed to get GitHub token:", error);
1863
+ throw error;
1864
+ }
1865
+ }
1866
+ async function logUser() {
1867
+ const user = await getGitHubUser();
1868
+ state.userName = user.login;
1869
+ consola.info(`Logged in as ${user.login}`);
1870
+ state.copilotApiUrl = (await getCopilotUsage()).endpoints.api;
1871
+ }
1872
+ //#endregion
1873
+ export { compactSystemPromptStarts as A, createWebSocketUrl as B, prepareForCompact as C, compactAutoContinuePromptStarts as D, initOpencodeVersion as E, normalizeCodexResponsesEvent as F, state as H, generateTraceId as I, requestContext as L, loginCodex as M, createStandardizedCodexResponsesEventStream as N, compactMessageSections as O, forwardCodexResponses as P, resolveTraceId as R, copilotWebSocketHeaders as S, prepareMessageProxyHeaders as T, logCodexRateLimitsEvent as U, initProxyFromEnv as V, getCopilotUsage as _, setupGitHubToken as a, copilotBaseUrl as b, cacheVSCodeVersion as c, generateRequestIdFromPayload as d, getRootSessionId as f, sleep as g, parseUserIdMetadata as h, setupCopilotToken as i, compactTextOnlyGuard as j, compactSummaryPromptStart as k, cacheVsCodeDeviceId as l, isNullish as m, persistCodexCredentials as n, cacheMacMachineId as o, getUUID as p, setupCodexToken as r, cacheModels as s, logUser as t, cacheVsCodeSessionId as u, HTTPError as v, prepareInteractionHeaders as w, copilotHeaders as x, forwardError as y, createPooledWebSocketStream as z };
1874
+
1875
+ //# sourceMappingURL=token-BVXHiYEl.js.map