@bitkyc08/opencodex 0.2.2 → 1.9.1

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.
@@ -0,0 +1,359 @@
1
+ import type { ServerWebSocket } from "bun";
2
+ import { FORWARD_HEADERS } from "./adapters/openai-responses";
3
+
4
+ const OPEN = 1;
5
+ const TERMINAL_TYPES = new Set(["response.completed", "response.failed", "response.incomplete"]);
6
+ const SAFE_RESPONSE_HEADER_EXACT = new Set([
7
+ "retry-after",
8
+ "x-request-id",
9
+ "openai-request-id",
10
+ "x-codex-turn-state",
11
+ "openai-model",
12
+ "x-models-etag",
13
+ "x-reasoning-included",
14
+ ]);
15
+
16
+ export interface WsData {
17
+ headers?: Headers; // selected inbound upgrade headers only; never store full cookies/handshake internals
18
+ cancel?: () => void; // cancels the in-flight stream reader/fetch
19
+ turnId?: number; // monotonically increasing per socket; prevents stale frames after replacement turns
20
+ }
21
+
22
+ export class WsSendDroppedError extends Error {
23
+ constructor() {
24
+ super("websocket send dropped the message");
25
+ }
26
+ }
27
+
28
+ export function selectForwardHeaders(headers: Headers): Headers {
29
+ const selected = new Headers();
30
+ for (const name of FORWARD_HEADERS) {
31
+ const value = headers.get(name);
32
+ if (value) selected.set(name, value);
33
+ }
34
+ return selected;
35
+ }
36
+
37
+ export function safeResponseHeaders(headers: Headers): Record<string, string> {
38
+ const out: Record<string, string> = {};
39
+ for (const [name, value] of headers) {
40
+ const lower = name.toLowerCase();
41
+ if (
42
+ SAFE_RESPONSE_HEADER_EXACT.has(lower) ||
43
+ lower.startsWith("x-ratelimit-") ||
44
+ /^x-codex(?:-[a-z0-9-]+)?-(primary|secondary)-(used-percent|window-minutes|reset-at)$/.test(lower) ||
45
+ /^x-codex(?:-[a-z0-9-]+)?-limit-name$/.test(lower)
46
+ ) {
47
+ out[lower] = value;
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+
53
+ export function buildWarmupCompletionFrames(frame: Record<string, unknown>): string[] {
54
+ const createdAt = Math.floor(Date.now() / 1000);
55
+ const baseResponse: Record<string, unknown> = {
56
+ id: "",
57
+ object: "response",
58
+ created_at: createdAt,
59
+ model: typeof frame.model === "string" ? frame.model : undefined,
60
+ output: [],
61
+ };
62
+ return [
63
+ JSON.stringify({
64
+ type: "response.created",
65
+ sequence_number: 0,
66
+ response: { ...baseResponse, status: "in_progress" },
67
+ }),
68
+ JSON.stringify({
69
+ type: "response.completed",
70
+ sequence_number: 1,
71
+ response: { ...baseResponse, status: "completed" },
72
+ }),
73
+ ];
74
+ }
75
+
76
+ export function sendTextFrame(ws: ServerWebSocket<WsData>, payload: string): void {
77
+ if (ws.readyState !== OPEN) throw new WsSendDroppedError();
78
+ const result = ws.send(payload);
79
+ if (result === 0) throw new WsSendDroppedError();
80
+ // Bun returns -1 when queued with backpressure. That is accepted; a later 0 is the hard failure.
81
+ }
82
+
83
+ export function sendJsonFrame(ws: ServerWebSocket<WsData>, payload: Record<string, unknown>): void {
84
+ sendTextFrame(ws, JSON.stringify(payload));
85
+ }
86
+
87
+ export function buildWsErrorFrame(
88
+ status: number,
89
+ error: Record<string, unknown>,
90
+ headers?: Headers,
91
+ ): Record<string, unknown> {
92
+ return {
93
+ type: "error",
94
+ status,
95
+ error,
96
+ headers: headers ? safeResponseHeaders(headers) : {},
97
+ };
98
+ }
99
+
100
+ function parseSseBlock(block: string): string | null {
101
+ const data: string[] = [];
102
+ for (const line of block.split(/\r?\n/)) {
103
+ if (line.startsWith("data:")) {
104
+ const value = line.slice(5);
105
+ data.push(value.startsWith(" ") ? value.slice(1) : value);
106
+ }
107
+ }
108
+ return data.length > 0 ? data.join("\n") : null;
109
+ }
110
+
111
+ function nextSseBlock(buffer: string): { block: string; rest: string } | null {
112
+ const match = buffer.match(/\r?\n\r?\n/);
113
+ if (!match || match.index === undefined) return null;
114
+ return {
115
+ block: buffer.slice(0, match.index),
116
+ rest: buffer.slice(match.index + match[0].length),
117
+ };
118
+ }
119
+
120
+ function payloadType(payload: string): string | null {
121
+ try {
122
+ const json = JSON.parse(payload) as { type?: unknown };
123
+ return typeof json.type === "string" ? json.type : null;
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ function protocolError(message: string): Record<string, unknown> {
130
+ return {
131
+ type: "protocol_error",
132
+ code: "websocket_protocol_error",
133
+ message,
134
+ };
135
+ }
136
+
137
+ function sendProtocolError(ws: ServerWebSocket<WsData>, status: number, message: string): void {
138
+ sendJsonFrame(ws, buildWsErrorFrame(status, protocolError(message)));
139
+ }
140
+
141
+ export async function pumpResponsesSseToWebSocket(
142
+ ws: ServerWebSocket<WsData>,
143
+ sseStream: ReadableStream<Uint8Array>,
144
+ options: { isCurrent?: () => boolean } = {},
145
+ ): Promise<void> {
146
+ const reader = sseStream.getReader();
147
+ const isCurrent = options.isCurrent ?? (() => true);
148
+ const cancel = () => {
149
+ void reader.cancel().catch(() => {});
150
+ };
151
+ ws.data.cancel = cancel;
152
+
153
+ const decoder = new TextDecoder();
154
+ let buffer = "";
155
+ let terminalSeen = false;
156
+
157
+ const handlePayload = (payload: string): boolean => {
158
+ if (!isCurrent()) return true;
159
+ if (payload === "[DONE]") return false;
160
+ const type = payloadType(payload);
161
+ if (!type) {
162
+ sendProtocolError(ws, 502, "Invalid JSON payload in upstream SSE frame");
163
+ terminalSeen = true;
164
+ void reader.cancel().catch(() => {});
165
+ return true;
166
+ }
167
+ if (terminalSeen) return true;
168
+ sendTextFrame(ws, payload);
169
+ if (TERMINAL_TYPES.has(type)) {
170
+ terminalSeen = true;
171
+ void reader.cancel().catch(() => {});
172
+ return true;
173
+ }
174
+ return false;
175
+ };
176
+
177
+ try {
178
+ while (!terminalSeen) {
179
+ const { done, value } = await reader.read();
180
+ if (done) break;
181
+ buffer += decoder.decode(value, { stream: true });
182
+ let next: { block: string; rest: string } | null;
183
+ while ((next = nextSseBlock(buffer))) {
184
+ buffer = next.rest;
185
+ const payload = parseSseBlock(next.block);
186
+ if (payload && handlePayload(payload)) break;
187
+ }
188
+ }
189
+ buffer += decoder.decode();
190
+ if (!terminalSeen && buffer.trim()) {
191
+ const payload = parseSseBlock(buffer);
192
+ if (payload) handlePayload(payload);
193
+ }
194
+ if (!terminalSeen && isCurrent()) {
195
+ sendProtocolError(ws, 502, "Upstream stream ended before response terminal event");
196
+ }
197
+ } catch (err) {
198
+ if (!terminalSeen && isCurrent() && ws.readyState === OPEN) {
199
+ sendProtocolError(ws, 502, err instanceof Error ? err.message : String(err));
200
+ }
201
+ } finally {
202
+ if (ws.data.cancel === cancel) ws.data.cancel = undefined;
203
+ }
204
+ }
205
+
206
+ export function sendResponsesJsonAsEvents(
207
+ ws: ServerWebSocket<WsData>,
208
+ response: Record<string, unknown>,
209
+ ): void {
210
+ const output = Array.isArray(response.output) ? response.output : [];
211
+ sendJsonFrame(ws, {
212
+ type: "response.created",
213
+ response: { ...response, status: "in_progress", output: [] },
214
+ });
215
+ output.forEach((item, outputIndex) => {
216
+ sendJsonFrame(ws, {
217
+ type: "response.output_item.done",
218
+ output_index: outputIndex,
219
+ item,
220
+ });
221
+ });
222
+ sendJsonFrame(ws, {
223
+ type: "response.completed",
224
+ response: { ...response, status: "completed" },
225
+ });
226
+ }
227
+
228
+ function errorPayloadFromText(text: string): Record<string, unknown> {
229
+ try {
230
+ const json = JSON.parse(text) as { error?: unknown };
231
+ if (json.error && typeof json.error === "object" && !Array.isArray(json.error)) {
232
+ return json.error as Record<string, unknown>;
233
+ }
234
+ } catch {
235
+ /* fall through */
236
+ }
237
+ return {
238
+ type: "upstream_error",
239
+ message: text ? text.slice(0, 500) : "Upstream request failed",
240
+ };
241
+ }
242
+
243
+ export async function sendResponseToWebSocket(
244
+ ws: ServerWebSocket<WsData>,
245
+ response: Response,
246
+ isCurrent: () => boolean,
247
+ ): Promise<void> {
248
+ if (!isCurrent()) {
249
+ await response.body?.cancel().catch(() => {});
250
+ return;
251
+ }
252
+
253
+ if (!response.ok) {
254
+ const text = await response.text().catch(() => "");
255
+ if (!isCurrent()) return;
256
+ sendJsonFrame(ws, buildWsErrorFrame(response.status, errorPayloadFromText(text), response.headers));
257
+ return;
258
+ }
259
+
260
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
261
+ if (!response.body) {
262
+ sendJsonFrame(ws, buildWsErrorFrame(502, {
263
+ type: "protocol_error",
264
+ code: "websocket_protocol_error",
265
+ message: `Unexpected successful upstream response without a body (${response.status})`,
266
+ }, response.headers));
267
+ return;
268
+ }
269
+
270
+ if (contentType.includes("text/event-stream")) {
271
+ await pumpResponsesSseToWebSocket(ws, response.body, { isCurrent });
272
+ return;
273
+ }
274
+
275
+ if (contentType.includes("application/json")) {
276
+ const text = await response.text();
277
+ if (!isCurrent()) return;
278
+ const json = JSON.parse(text) as Record<string, unknown>;
279
+ sendResponsesJsonAsEvents(ws, json);
280
+ return;
281
+ }
282
+
283
+ const { prefix, stream } = await readBoundedPrefix(response.body);
284
+ if (!isCurrent()) {
285
+ await stream.cancel().catch(() => {});
286
+ return;
287
+ }
288
+ if (looksLikeSse(prefix)) {
289
+ await pumpResponsesSseToWebSocket(ws, stream, { isCurrent });
290
+ return;
291
+ }
292
+
293
+ const text = await new Response(stream).text();
294
+ if (!isCurrent()) return;
295
+ const trimmed = text.trim();
296
+ if (trimmed.startsWith("{")) {
297
+ const json = JSON.parse(trimmed) as Record<string, unknown>;
298
+ sendResponsesJsonAsEvents(ws, json);
299
+ return;
300
+ }
301
+
302
+ sendJsonFrame(ws, buildWsErrorFrame(502, {
303
+ type: "protocol_error",
304
+ code: "websocket_protocol_error",
305
+ message: `Unexpected successful non-SSE upstream response (${contentType || "missing content-type"})`,
306
+ }, response.headers));
307
+ }
308
+
309
+ export async function readBoundedPrefix(
310
+ body: ReadableStream<Uint8Array>,
311
+ maxBytes = 4096,
312
+ ): Promise<{ prefix: Uint8Array; stream: ReadableStream<Uint8Array> }> {
313
+ const reader = body.getReader();
314
+ const chunks: Uint8Array[] = [];
315
+ let remainder: Uint8Array | undefined;
316
+ let total = 0;
317
+ while (total < maxBytes) {
318
+ const { done, value } = await reader.read();
319
+ if (done) break;
320
+ const take = Math.min(value.byteLength, maxBytes - total);
321
+ if (take > 0) {
322
+ chunks.push(value.slice(0, take));
323
+ total += take;
324
+ }
325
+ if (take < value.byteLength) {
326
+ remainder = value.slice(take);
327
+ break;
328
+ }
329
+ }
330
+ const prefix = new Uint8Array(total);
331
+ let offset = 0;
332
+ for (const chunk of chunks) {
333
+ prefix.set(chunk, offset);
334
+ offset += chunk.byteLength;
335
+ }
336
+ const stream = new ReadableStream<Uint8Array>({
337
+ start(controller) {
338
+ if (prefix.byteLength > 0) controller.enqueue(prefix);
339
+ if (remainder && remainder.byteLength > 0) controller.enqueue(remainder);
340
+ },
341
+ async pull(controller) {
342
+ const { done, value } = await reader.read();
343
+ if (done) {
344
+ controller.close();
345
+ return;
346
+ }
347
+ controller.enqueue(value);
348
+ },
349
+ cancel(reason) {
350
+ return reader.cancel(reason);
351
+ },
352
+ });
353
+ return { prefix, stream };
354
+ }
355
+
356
+ export function looksLikeSse(prefix: Uint8Array): boolean {
357
+ const text = new TextDecoder().decode(prefix);
358
+ return /^\s*(event:|data:)/.test(text);
359
+ }