@monotykamary/pi-better-openai 0.1.23

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,445 @@
1
+ import { HttpsProxyAgent } from "https-proxy-agent";
2
+ import { getProxyForUrl } from "proxy-from-env";
3
+ import { fetch as undiciFetch, ProxyAgent } from "undici";
4
+ import WebSocket, { type RawData } from "ws";
5
+ import type { CodexCredentials } from "../codex-auth.ts";
6
+ import { generateCodexAttestation } from "./attestation.ts";
7
+ import { loadLiveNative, type LiveNativeBindings, type LiveWebRtcPeerInstance } from "./native.ts";
8
+ import {
9
+ buildLiveSessionPayload,
10
+ type LiveClientMessage,
11
+ type LiveServerEvent,
12
+ parseLiveServerEvent,
13
+ } from "./protocol.ts";
14
+
15
+ const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
16
+ const CODEX_CLIENT_VERSION = "0.144.1";
17
+ const SIGNALING_URL = `${CODEX_BASE_URL}/codex/realtime/calls?intent=quicksilver&architecture=avas`;
18
+ const MAX_ERROR_BODY_LENGTH = 2_048;
19
+ const SIDEBAND_CONNECT_ATTEMPTS = 5;
20
+ const SIDEBAND_CONNECT_TIMEOUT_MS = 15_000;
21
+ const LIVE_ORIGINATOR = "Codex Desktop";
22
+ const LIVE_CALL_ID_PATTERN = /^rtc_[\w-]+$/;
23
+
24
+ type Lifecycle = "idle" | "connecting" | "connected" | "closing" | "closed";
25
+
26
+ type LiveSignalingResult = {
27
+ answer: string;
28
+ callId: string;
29
+ credentials: CodexCredentials;
30
+ attestation: string | undefined;
31
+ };
32
+
33
+ export interface LiveTransportCallbacks {
34
+ onEvent(event: LiveServerEvent): void;
35
+ onOutputLevel(level: number): void;
36
+ }
37
+
38
+ export interface LiveTransportOptions {
39
+ getCredentials(signal?: AbortSignal): Promise<CodexCredentials | undefined>;
40
+ sessionId: string;
41
+ instructions: string;
42
+ voice: string;
43
+ callbacks: LiveTransportCallbacks;
44
+ signal?: AbortSignal;
45
+ native?: LiveNativeBindings;
46
+ }
47
+
48
+ function errorFrom(cause: unknown): Error {
49
+ return cause instanceof Error ? cause : new Error(String(cause));
50
+ }
51
+
52
+ function abortReason(signal: AbortSignal | undefined): Error {
53
+ if (signal?.reason instanceof Error) return signal.reason;
54
+ return new DOMException("Live connection aborted", "AbortError");
55
+ }
56
+
57
+ function boundedErrorBody(body: string, statusText: string): string {
58
+ const normalized = body.trim().replaceAll(/\s+/g, " ");
59
+ if (!normalized) return statusText || "empty response body";
60
+ if (normalized.length <= MAX_ERROR_BODY_LENGTH) return normalized;
61
+ return `${normalized.slice(0, MAX_ERROR_BODY_LENGTH)}…`;
62
+ }
63
+
64
+ function wait(delayMs: number, signal?: AbortSignal): Promise<void> {
65
+ if (signal?.aborted) return Promise.reject(abortReason(signal));
66
+ return new Promise((resolve, reject) => {
67
+ const onAbort = () => {
68
+ clearTimeout(timeout);
69
+ reject(abortReason(signal));
70
+ };
71
+ const timeout = setTimeout(() => {
72
+ signal?.removeEventListener("abort", onAbort);
73
+ resolve();
74
+ }, delayMs);
75
+ timeout.unref?.();
76
+ signal?.addEventListener("abort", onAbort, { once: true });
77
+ });
78
+ }
79
+
80
+ export function parseLiveCallId(location: string | null): string | undefined {
81
+ if (!location) return undefined;
82
+ return location
83
+ .split("?", 1)[0]
84
+ ?.split("/")
85
+ .find((segment) => LIVE_CALL_ID_PATTERN.test(segment));
86
+ }
87
+
88
+ export function buildLiveSidebandUrl(callId: string): string {
89
+ const url = new URL(`https://api.openai.com/v1/live/${encodeURIComponent(callId)}`);
90
+ url.protocol = "wss:";
91
+ return url.toString();
92
+ }
93
+
94
+ export function buildLiveHeaders(
95
+ credentials: CodexCredentials,
96
+ sessionId: string,
97
+ realtimeSessionId: string,
98
+ attestation: string | undefined,
99
+ ): Record<string, string> {
100
+ const headers: Record<string, string> = {
101
+ Authorization: `Bearer ${credentials.accessToken}`,
102
+ "OpenAI-Alpha": "quicksilver=v2",
103
+ "User-Agent": `Codex Desktop/${CODEX_CLIENT_VERSION}`,
104
+ "x-session-id": realtimeSessionId,
105
+ originator: LIVE_ORIGINATOR,
106
+ version: CODEX_CLIENT_VERSION,
107
+ "session-id": sessionId,
108
+ "thread-id": sessionId,
109
+ "chatgpt-account-id": credentials.accountId,
110
+ };
111
+ if (attestation) headers["x-oai-attestation"] = attestation;
112
+ return headers;
113
+ }
114
+
115
+ export class CodexLiveTransport {
116
+ readonly #options: LiveTransportOptions;
117
+ readonly #native: LiveNativeBindings;
118
+ readonly #realtimeSessionId = crypto.randomUUID();
119
+ readonly #closeController = new AbortController();
120
+ readonly #operationSignal: AbortSignal;
121
+ readonly #abortListener: () => void;
122
+ #peer: LiveWebRtcPeerInstance | undefined;
123
+ #sideband: WebSocket | undefined;
124
+ #state: Lifecycle = "idle";
125
+ #connectPromise: Promise<void> | undefined;
126
+ #closePromise: Promise<void> | undefined;
127
+ #sendTail: Promise<void> = Promise.resolve();
128
+ #muted = false;
129
+ #unexpectedFailureReported = false;
130
+
131
+ constructor(options: LiveTransportOptions) {
132
+ this.#options = options;
133
+ this.#native = options.native ?? loadLiveNative();
134
+ this.#operationSignal = options.signal
135
+ ? AbortSignal.any([options.signal, this.#closeController.signal])
136
+ : this.#closeController.signal;
137
+ this.#abortListener = () => {
138
+ void this.close();
139
+ };
140
+ if (!options.signal?.aborted) {
141
+ options.signal?.addEventListener("abort", this.#abortListener, { once: true });
142
+ }
143
+ }
144
+
145
+ connect(): Promise<void> {
146
+ if (this.#state === "connected") return Promise.resolve();
147
+ if (this.#connectPromise) return this.#connectPromise;
148
+ if (this.#state === "closing" || this.#state === "closed") {
149
+ return Promise.reject(new Error("Live transport is closed."));
150
+ }
151
+ if (this.#operationSignal.aborted) return Promise.reject(abortReason(this.#operationSignal));
152
+ this.#state = "connecting";
153
+ const operation = this.#connect().catch(async (cause) => {
154
+ await this.close();
155
+ throw cause;
156
+ });
157
+ this.#connectPromise = operation;
158
+ return operation;
159
+ }
160
+
161
+ async #connect(): Promise<void> {
162
+ const peer = new this.#native.LiveWebRtcPeer(
163
+ (error, payload) => {
164
+ if (error) this.#handlePeerFailure(error.message);
165
+ else this.#handleServerEvent(payload);
166
+ },
167
+ (error, level) => {
168
+ if (error) this.#handlePeerFailure(error.message);
169
+ else this.#handleOutputLevel(level);
170
+ },
171
+ (error, message) => this.#handlePeerFailure(error?.message ?? message),
172
+ );
173
+ this.#peer = peer;
174
+ const offer = await peer.createOffer();
175
+ if (this.#state !== "connecting") throw abortReason(this.#operationSignal);
176
+ const signaling = await this.#signal(offer);
177
+ await peer.acceptAnswer(signaling.answer);
178
+ peer.setMuted(this.#muted);
179
+ await peer.waitForOpen();
180
+ if (this.#state !== "connecting") throw abortReason(this.#operationSignal);
181
+ await this.#connectSideband(signaling.callId, signaling.credentials, signaling.attestation);
182
+ if (this.#state !== "connecting") throw abortReason(this.#operationSignal);
183
+ this.#state = "connected";
184
+ }
185
+
186
+ async #signal(offer: string): Promise<LiveSignalingResult> {
187
+ const credentials = await this.#options.getCredentials(this.#operationSignal);
188
+ if (!credentials) {
189
+ throw new Error("Missing openai-codex OAuth credentials. Run /login openai-codex.");
190
+ }
191
+ const attestation = await generateCodexAttestation(this.#native);
192
+ const headers = buildLiveHeaders(
193
+ credentials,
194
+ this.#options.sessionId,
195
+ this.#realtimeSessionId,
196
+ attestation,
197
+ );
198
+ const proxyUrl = getProxyForUrl(SIGNALING_URL);
199
+ const dispatcher = proxyUrl ? new ProxyAgent(proxyUrl) : undefined;
200
+ try {
201
+ const response = await undiciFetch(SIGNALING_URL, {
202
+ method: "POST",
203
+ headers: {
204
+ ...headers,
205
+ Accept: "*/*",
206
+ "Content-Type": "application/json",
207
+ },
208
+ body: JSON.stringify({
209
+ sdp: offer,
210
+ session: buildLiveSessionPayload(this.#options.instructions, this.#options.voice),
211
+ }),
212
+ signal: this.#operationSignal,
213
+ ...(dispatcher ? { dispatcher } : {}),
214
+ });
215
+ const responseBody = await response.text();
216
+ if (!response.ok) {
217
+ const detail = boundedErrorBody(responseBody, response.statusText);
218
+ throw new Error(`Codex live signaling failed (${response.status}): ${detail}`);
219
+ }
220
+ if (!responseBody.trim()) {
221
+ throw new Error("Codex live signaling returned an empty SDP answer.");
222
+ }
223
+ const callId = parseLiveCallId(response.headers.get("location"));
224
+ if (!callId) throw new Error("Codex live signaling returned no valid call ID.");
225
+ return { answer: responseBody, callId, credentials, attestation };
226
+ } finally {
227
+ await dispatcher?.close();
228
+ }
229
+ }
230
+
231
+ async #connectSideband(
232
+ callId: string,
233
+ credentials: CodexCredentials,
234
+ attestation: string | undefined,
235
+ ): Promise<void> {
236
+ let failure = new Error("Codex live sideband connection failed.");
237
+ for (let attempt = 0; attempt < SIDEBAND_CONNECT_ATTEMPTS; attempt += 1) {
238
+ try {
239
+ await this.#openSideband(callId, credentials, attestation);
240
+ return;
241
+ } catch (cause) {
242
+ failure = errorFrom(cause);
243
+ if (this.#operationSignal.aborted) throw abortReason(this.#operationSignal);
244
+ if (attempt + 1 < SIDEBAND_CONNECT_ATTEMPTS) {
245
+ await wait(200 * 2 ** attempt, this.#operationSignal);
246
+ }
247
+ }
248
+ }
249
+ throw failure;
250
+ }
251
+
252
+ #openSideband(
253
+ callId: string,
254
+ credentials: CodexCredentials,
255
+ attestation: string | undefined,
256
+ ): Promise<void> {
257
+ const url = buildLiveSidebandUrl(callId);
258
+ const proxyUrl = getProxyForUrl(url);
259
+ const socket = new WebSocket(url, {
260
+ headers: buildLiveHeaders(
261
+ credentials,
262
+ this.#options.sessionId,
263
+ this.#realtimeSessionId,
264
+ attestation,
265
+ ),
266
+ ...(proxyUrl ? { agent: new HttpsProxyAgent(proxyUrl) } : {}),
267
+ });
268
+ return new Promise((resolve, reject) => {
269
+ let opened = false;
270
+ let settled = false;
271
+ let timeout: NodeJS.Timeout | undefined;
272
+
273
+ const cleanupConnect = () => {
274
+ if (timeout) clearTimeout(timeout);
275
+ timeout = undefined;
276
+ this.#operationSignal.removeEventListener("abort", onAbort);
277
+ };
278
+ const rejectConnect = (error: Error) => {
279
+ if (settled) return;
280
+ settled = true;
281
+ cleanupConnect();
282
+ reject(error);
283
+ };
284
+ const onAbort = () => {
285
+ socket.close(1000, "aborted");
286
+ rejectConnect(abortReason(this.#operationSignal));
287
+ };
288
+
289
+ socket.once("open", () => {
290
+ if (settled) {
291
+ socket.close(1000, "stale");
292
+ return;
293
+ }
294
+ opened = true;
295
+ settled = true;
296
+ cleanupConnect();
297
+ this.#sideband = socket;
298
+ resolve();
299
+ });
300
+ socket.on("message", (data: RawData, isBinary: boolean) => {
301
+ if (isBinary) {
302
+ this.#reportFailure("Codex live sideband returned an unexpected binary frame.");
303
+ return;
304
+ }
305
+ this.#handleSidebandEvent(data.toString());
306
+ });
307
+ socket.on("error", (cause) => {
308
+ const detail = cause.message ? `: ${cause.message}` : "";
309
+ if (!opened) {
310
+ rejectConnect(new Error(`Codex live sideband connection failed${detail}`));
311
+ socket.terminate();
312
+ return;
313
+ }
314
+ this.#reportFailure(`Codex live sideband failed${detail}`);
315
+ });
316
+ socket.on("close", (code, reason) => {
317
+ if (!opened) {
318
+ rejectConnect(new Error(`Codex live sideband closed before connecting (${code}).`));
319
+ return;
320
+ }
321
+ if (this.#sideband !== socket) return;
322
+ this.#sideband = undefined;
323
+ if (this.#state === "connecting" || this.#state === "connected") {
324
+ const detail = reason.length > 0 ? `: ${reason.toString()}` : "";
325
+ this.#reportFailure(`Codex live sideband closed (${code})${detail}`);
326
+ }
327
+ });
328
+
329
+ if (this.#operationSignal.aborted) {
330
+ onAbort();
331
+ } else {
332
+ this.#operationSignal.addEventListener("abort", onAbort, { once: true });
333
+ timeout = setTimeout(() => {
334
+ socket.close(1000, "connect timeout");
335
+ rejectConnect(new Error("Codex live sideband connection timed out."));
336
+ }, SIDEBAND_CONNECT_TIMEOUT_MS);
337
+ timeout.unref?.();
338
+ }
339
+ });
340
+ }
341
+
342
+ #handleSidebandEvent(payload: string): void {
343
+ if (this.#state === "closing" || this.#state === "closed") return;
344
+ const event = parseLiveServerEvent(payload);
345
+ if (!event) return;
346
+ try {
347
+ this.#options.callbacks.onEvent(event);
348
+ } catch {
349
+ // UI callbacks are isolated from the transport lifecycle.
350
+ }
351
+ }
352
+
353
+ #handleServerEvent(payload: string): void {
354
+ if (this.#state === "closing" || this.#state === "closed") return;
355
+ const event = parseLiveServerEvent(payload);
356
+ if (!event || (this.#sideband?.readyState === WebSocket.OPEN && event.type !== "error")) return;
357
+ try {
358
+ this.#options.callbacks.onEvent(event);
359
+ } catch {
360
+ // UI callbacks are isolated from the transport lifecycle.
361
+ }
362
+ }
363
+
364
+ #handleOutputLevel(level: number): void {
365
+ if (this.#state !== "connected" || !Number.isFinite(level)) return;
366
+ try {
367
+ this.#options.callbacks.onOutputLevel(Math.min(1, Math.max(0, level)));
368
+ } catch {
369
+ // UI callbacks are isolated from the transport lifecycle.
370
+ }
371
+ }
372
+
373
+ #handlePeerFailure(message: string): void {
374
+ this.#reportFailure(message);
375
+ }
376
+
377
+ #reportFailure(message: string): void {
378
+ if (
379
+ (this.#state !== "connecting" && this.#state !== "connected") ||
380
+ this.#unexpectedFailureReported
381
+ ) {
382
+ return;
383
+ }
384
+ this.#unexpectedFailureReported = true;
385
+ try {
386
+ this.#options.callbacks.onEvent({ type: "error", message });
387
+ } catch {
388
+ // UI callbacks are isolated from the transport lifecycle.
389
+ }
390
+ }
391
+
392
+ send(message: LiveClientMessage): Promise<void> {
393
+ const operation = this.#sendTail.then(() => {
394
+ if (this.#state !== "connected") throw new Error("Live transport is not connected.");
395
+ const sideband = this.#sideband;
396
+ if (!sideband || sideband.readyState !== WebSocket.OPEN) {
397
+ throw new Error("Codex live sideband is not connected.");
398
+ }
399
+ sideband.send(JSON.stringify(message));
400
+ });
401
+ this.#sendTail = operation.catch(() => undefined);
402
+ return operation;
403
+ }
404
+
405
+ pushAudio(samples: Float32Array): void {
406
+ if (this.#state !== "connected" || this.#muted || samples.length === 0) return;
407
+ this.#peer?.pushAudio(samples);
408
+ }
409
+
410
+ setMuted(muted: boolean): void {
411
+ this.#muted = muted;
412
+ if (this.#state === "connected") this.#peer?.setMuted(muted);
413
+ }
414
+
415
+ close(): Promise<void> {
416
+ if (this.#closePromise) return this.#closePromise;
417
+ this.#state = "closing";
418
+ this.#closeController.abort(new DOMException("Live transport closed", "AbortError"));
419
+ const operation = this.#close();
420
+ this.#closePromise = operation;
421
+ return operation;
422
+ }
423
+
424
+ async #close(): Promise<void> {
425
+ this.#options.signal?.removeEventListener("abort", this.#abortListener);
426
+ const sideband = this.#sideband;
427
+ const peer = this.#peer;
428
+ this.#sideband = undefined;
429
+ this.#peer = undefined;
430
+ if (
431
+ sideband &&
432
+ (sideband.readyState === WebSocket.OPEN || sideband.readyState === WebSocket.CONNECTING)
433
+ ) {
434
+ sideband.close(1000, "done");
435
+ }
436
+ if (peer) {
437
+ try {
438
+ await peer.close();
439
+ } catch {
440
+ // Closing is best-effort after a terminal transport failure.
441
+ }
442
+ }
443
+ this.#state = "closed";
444
+ }
445
+ }