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