@caupulican/pi-ai 0.75.6 → 0.78.2
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/README.md +1 -1
- package/dist/image-models.generated.d.ts.map +1 -1
- package/dist/image-models.generated.js +146 -146
- package/dist/image-models.generated.js.map +1 -1
- package/dist/models.generated.d.ts +399 -299
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +494 -455
- package/dist/models.generated.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts +1 -1
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +38 -2
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +21 -12
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +6 -10
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +1 -1
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +5 -3
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts.map +1 -1
- package/dist/providers/images/openrouter.js +3 -4
- package/dist/providers/images/openrouter.js.map +1 -1
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +2 -3
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +159 -78
- package/dist/providers/openai-codex-responses.js.map +1 -1
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +16 -11
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +4 -1
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +6 -10
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js +1 -0
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +14 -2
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +14 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/abort-signals.d.ts +6 -0
- package/dist/utils/abort-signals.d.ts.map +1 -0
- package/dist/utils/abort-signals.js +34 -0
- package/dist/utils/abort-signals.js.map +1 -0
- package/dist/utils/oauth/device-code.d.ts +9 -7
- package/dist/utils/oauth/device-code.d.ts.map +1 -1
- package/dist/utils/oauth/device-code.js +8 -7
- package/dist/utils/oauth/device-code.js.map +1 -1
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +1 -1
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +1 -1
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +1 -1
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/openai-codex.d.ts +10 -1
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/dist/utils/oauth/openai-codex.js +179 -79
- package/dist/utils/oauth/openai-codex.js.map +1 -1
- package/package.json +104 -91
|
@@ -15,9 +15,9 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
|
|
|
15
15
|
_os = m;
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
import { getEnvApiKey } from "../env-api-keys.js";
|
|
19
18
|
import { clampThinkingLevel } from "../models.js";
|
|
20
19
|
import { registerSessionResourceCleanup } from "../session-resources.js";
|
|
20
|
+
import { combineAbortSignals } from "../utils/abort-signals.js";
|
|
21
21
|
import { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic, formatThrownValue, } from "../utils/diagnostics.js";
|
|
22
22
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
23
23
|
import { headersToRecord } from "../utils/headers.js";
|
|
@@ -29,8 +29,11 @@ import { buildBaseOptions } from "./simple-options.js";
|
|
|
29
29
|
// ============================================================================
|
|
30
30
|
const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
31
31
|
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
32
|
-
const DEFAULT_MAX_RETRIES =
|
|
32
|
+
const DEFAULT_MAX_RETRIES = 0;
|
|
33
33
|
const BASE_DELAY_MS = 1000;
|
|
34
|
+
const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
|
|
35
|
+
const DEFAULT_SSE_HEADER_TIMEOUT_MS = 20_000;
|
|
36
|
+
const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;
|
|
34
37
|
const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
|
|
35
38
|
const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
|
|
36
39
|
const CODEX_RESPONSE_STATUSES = new Set([
|
|
@@ -44,12 +47,44 @@ const CODEX_RESPONSE_STATUSES = new Set([
|
|
|
44
47
|
// ============================================================================
|
|
45
48
|
// Retry Helpers
|
|
46
49
|
// ============================================================================
|
|
50
|
+
function isTerminalRateLimitError(errorText) {
|
|
51
|
+
return /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(errorText);
|
|
52
|
+
}
|
|
47
53
|
function isRetryableError(status, errorText) {
|
|
54
|
+
if (status === 429 && isTerminalRateLimitError(errorText)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
48
57
|
if (status === 429 || status === 500 || status === 502 || status === 503 || status === 504) {
|
|
49
58
|
return true;
|
|
50
59
|
}
|
|
51
60
|
return /rate.?limit|overloaded|service.?unavailable|upstream.?connect|connection.?refused/i.test(errorText);
|
|
52
61
|
}
|
|
62
|
+
function getRetryAfterDelayMs(headers) {
|
|
63
|
+
const retryAfterMs = headers.get("retry-after-ms");
|
|
64
|
+
if (retryAfterMs !== null) {
|
|
65
|
+
const millis = Number(retryAfterMs);
|
|
66
|
+
if (Number.isFinite(millis)) {
|
|
67
|
+
return Math.max(0, millis);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const retryAfter = headers.get("retry-after");
|
|
71
|
+
if (!retryAfter) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const seconds = Number(retryAfter);
|
|
75
|
+
if (Number.isFinite(seconds)) {
|
|
76
|
+
return Math.max(0, seconds * 1000);
|
|
77
|
+
}
|
|
78
|
+
const date = Date.parse(retryAfter);
|
|
79
|
+
if (!Number.isNaN(date)) {
|
|
80
|
+
return Math.max(0, date - Date.now());
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
function capRetryDelayMs(delayMs, options) {
|
|
85
|
+
const maxRetryDelayMs = options?.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
|
|
86
|
+
return maxRetryDelayMs > 0 ? Math.min(delayMs, maxRetryDelayMs) : delayMs;
|
|
87
|
+
}
|
|
53
88
|
function sleep(ms, signal) {
|
|
54
89
|
return new Promise((resolve, reject) => {
|
|
55
90
|
if (signal?.aborted) {
|
|
@@ -63,6 +98,27 @@ function sleep(ms, signal) {
|
|
|
63
98
|
});
|
|
64
99
|
});
|
|
65
100
|
}
|
|
101
|
+
function normalizeTimeoutMs(value) {
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
105
|
+
throw new Error(`Invalid timeoutMs: ${String(value)}`);
|
|
106
|
+
}
|
|
107
|
+
return Math.floor(value);
|
|
108
|
+
}
|
|
109
|
+
function createSSEHeaderTimeout() {
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
let error;
|
|
112
|
+
const timeout = setTimeout(() => {
|
|
113
|
+
error = new Error(`Codex SSE response headers timed out after ${DEFAULT_SSE_HEADER_TIMEOUT_MS}ms`);
|
|
114
|
+
controller.abort(error);
|
|
115
|
+
}, DEFAULT_SSE_HEADER_TIMEOUT_MS);
|
|
116
|
+
return {
|
|
117
|
+
signal: controller.signal,
|
|
118
|
+
clear: () => clearTimeout(timeout),
|
|
119
|
+
error: () => error,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
66
122
|
// ============================================================================
|
|
67
123
|
// Main Stream Function
|
|
68
124
|
// ============================================================================
|
|
@@ -87,7 +143,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
87
143
|
timestamp: Date.now(),
|
|
88
144
|
};
|
|
89
145
|
try {
|
|
90
|
-
const apiKey = options?.apiKey
|
|
146
|
+
const apiKey = options?.apiKey;
|
|
91
147
|
if (!apiKey) {
|
|
92
148
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
93
149
|
}
|
|
@@ -101,6 +157,8 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
101
157
|
const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
|
|
102
158
|
const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, websocketRequestId);
|
|
103
159
|
const bodyJson = JSON.stringify(body);
|
|
160
|
+
const idleTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);
|
|
161
|
+
const websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);
|
|
104
162
|
const transport = options?.transport || "auto";
|
|
105
163
|
const websocketDisabledForSession = transport !== "sse" && isWebSocketSseFallbackActive(options?.sessionId);
|
|
106
164
|
if (websocketDisabledForSession) {
|
|
@@ -111,7 +169,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
111
169
|
try {
|
|
112
170
|
await processWebSocketStream(resolveCodexWebSocketUrl(model.baseUrl), body, websocketHeaders, output, stream, model, () => {
|
|
113
171
|
websocketStarted = true;
|
|
114
|
-
}, options);
|
|
172
|
+
}, idleTimeoutMs, websocketConnectTimeoutMs, options);
|
|
115
173
|
if (options?.signal?.aborted) {
|
|
116
174
|
throw new Error("Request was aborted");
|
|
117
175
|
}
|
|
@@ -151,41 +209,36 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
151
209
|
throw new Error("Request was aborted");
|
|
152
210
|
}
|
|
153
211
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
212
|
+
const headerTimeout = createSSEHeaderTimeout();
|
|
213
|
+
const combinedSignal = combineAbortSignals([options?.signal, headerTimeout.signal]);
|
|
214
|
+
try {
|
|
215
|
+
response = await fetch(resolveCodexUrl(model.baseUrl), {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: sseHeaders,
|
|
218
|
+
body: bodyJson,
|
|
219
|
+
signal: combinedSignal.signal,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
const timeoutError = headerTimeout.error();
|
|
224
|
+
throw timeoutError && !options?.signal?.aborted ? timeoutError : error;
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
combinedSignal.cleanup();
|
|
228
|
+
headerTimeout.clear();
|
|
229
|
+
}
|
|
160
230
|
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
161
231
|
if (response.ok) {
|
|
162
232
|
break;
|
|
163
233
|
}
|
|
164
234
|
const errorText = await response.text();
|
|
165
235
|
if (attempt < maxRetries && isRetryableError(response.status, errorText)) {
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
const retryAfter = response.headers.get("retry-after");
|
|
176
|
-
if (retryAfter) {
|
|
177
|
-
const seconds = Number(retryAfter);
|
|
178
|
-
if (Number.isFinite(seconds)) {
|
|
179
|
-
delayMs = Math.max(0, seconds * 1000);
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
const date = Date.parse(retryAfter);
|
|
183
|
-
if (!Number.isNaN(date)) {
|
|
184
|
-
delayMs = Math.max(0, date - Date.now());
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
236
|
+
const retryAfterDelayMs = getRetryAfterDelayMs(response.headers);
|
|
237
|
+
const delayMs = retryAfterDelayMs === undefined
|
|
238
|
+
? BASE_DELAY_MS * 2 ** attempt
|
|
239
|
+
: response.status === 429
|
|
240
|
+
? capRetryDelayMs(retryAfterDelayMs, options)
|
|
241
|
+
: retryAfterDelayMs;
|
|
189
242
|
await sleep(delayMs, options?.signal);
|
|
190
243
|
continue;
|
|
191
244
|
}
|
|
@@ -241,7 +294,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
241
294
|
return stream;
|
|
242
295
|
};
|
|
243
296
|
export const streamSimpleOpenAICodexResponses = (model, context, options) => {
|
|
244
|
-
const apiKey = options?.apiKey
|
|
297
|
+
const apiKey = options?.apiKey;
|
|
245
298
|
if (!apiKey) {
|
|
246
299
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
247
300
|
}
|
|
@@ -341,7 +394,7 @@ function resolveCodexWebSocketUrl(baseUrl) {
|
|
|
341
394
|
// Response Processing
|
|
342
395
|
// ============================================================================
|
|
343
396
|
async function processStream(response, output, stream, model, options) {
|
|
344
|
-
await processResponsesStream(mapCodexEvents(parseSSE(response)), output, stream, model, {
|
|
397
|
+
await processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal)), output, stream, model, {
|
|
345
398
|
serviceTier: options?.serviceTier,
|
|
346
399
|
resolveServiceTier: resolveCodexServiceTier,
|
|
347
400
|
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
|
|
@@ -408,15 +461,25 @@ function normalizeCodexStatus(status) {
|
|
|
408
461
|
// ============================================================================
|
|
409
462
|
// SSE Parsing
|
|
410
463
|
// ============================================================================
|
|
411
|
-
async function* parseSSE(response) {
|
|
464
|
+
async function* parseSSE(response, signal) {
|
|
412
465
|
if (!response.body)
|
|
413
466
|
return;
|
|
414
467
|
const reader = response.body.getReader();
|
|
415
468
|
const decoder = new TextDecoder();
|
|
416
469
|
let buffer = "";
|
|
470
|
+
const onAbort = () => {
|
|
471
|
+
void reader.cancel().catch(() => { });
|
|
472
|
+
};
|
|
473
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
417
474
|
try {
|
|
418
475
|
while (true) {
|
|
476
|
+
if (signal?.aborted) {
|
|
477
|
+
throw new Error("Request was aborted");
|
|
478
|
+
}
|
|
419
479
|
const { done, value } = await reader.read();
|
|
480
|
+
if (signal?.aborted) {
|
|
481
|
+
throw new Error("Request was aborted");
|
|
482
|
+
}
|
|
420
483
|
if (done)
|
|
421
484
|
break;
|
|
422
485
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -447,6 +510,7 @@ async function* parseSSE(response) {
|
|
|
447
510
|
}
|
|
448
511
|
}
|
|
449
512
|
finally {
|
|
513
|
+
signal?.removeEventListener("abort", onAbort);
|
|
450
514
|
try {
|
|
451
515
|
await reader.cancel();
|
|
452
516
|
}
|
|
@@ -603,7 +667,7 @@ function scheduleSessionWebSocketExpiry(sessionId, entry) {
|
|
|
603
667
|
websocketSessionCache.delete(sessionId);
|
|
604
668
|
}, SESSION_WEBSOCKET_CACHE_TTL_MS);
|
|
605
669
|
}
|
|
606
|
-
async function connectWebSocket(url, headers, signal) {
|
|
670
|
+
async function connectWebSocket(url, headers, signal, connectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS) {
|
|
607
671
|
const WebSocketCtor = await getWebSocketConstructor();
|
|
608
672
|
if (!WebSocketCtor) {
|
|
609
673
|
throw new Error("WebSocket transport is not available in this runtime");
|
|
@@ -612,6 +676,7 @@ async function connectWebSocket(url, headers, signal) {
|
|
|
612
676
|
delete wsHeaders["OpenAI-Beta"];
|
|
613
677
|
return new Promise((resolve, reject) => {
|
|
614
678
|
let settled = false;
|
|
679
|
+
let timeout;
|
|
615
680
|
let socket;
|
|
616
681
|
try {
|
|
617
682
|
socket = new WebSocketCtor(url, { headers: wsHeaders });
|
|
@@ -620,62 +685,63 @@ async function connectWebSocket(url, headers, signal) {
|
|
|
620
685
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
621
686
|
return;
|
|
622
687
|
}
|
|
623
|
-
const
|
|
624
|
-
if (
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
688
|
+
const cleanup = () => {
|
|
689
|
+
if (timeout) {
|
|
690
|
+
clearTimeout(timeout);
|
|
691
|
+
timeout = undefined;
|
|
692
|
+
}
|
|
693
|
+
socket.removeEventListener("open", onOpen);
|
|
694
|
+
socket.removeEventListener("error", onError);
|
|
695
|
+
socket.removeEventListener("close", onClose);
|
|
696
|
+
signal?.removeEventListener("abort", onAbort);
|
|
629
697
|
};
|
|
630
|
-
const
|
|
631
|
-
const error = extractWebSocketError(event);
|
|
698
|
+
const fail = (error, closeReason) => {
|
|
632
699
|
if (settled)
|
|
633
700
|
return;
|
|
634
701
|
settled = true;
|
|
635
702
|
cleanup();
|
|
703
|
+
if (closeReason) {
|
|
704
|
+
closeWebSocketSilently(socket, 1000, closeReason);
|
|
705
|
+
}
|
|
636
706
|
reject(error);
|
|
637
707
|
};
|
|
638
|
-
const
|
|
639
|
-
const error = extractWebSocketCloseError(event);
|
|
708
|
+
const onOpen = () => {
|
|
640
709
|
if (settled)
|
|
641
710
|
return;
|
|
642
711
|
settled = true;
|
|
643
712
|
cleanup();
|
|
644
|
-
|
|
713
|
+
resolve(socket);
|
|
645
714
|
};
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
return;
|
|
649
|
-
settled = true;
|
|
650
|
-
cleanup();
|
|
651
|
-
socket.close(1000, "aborted");
|
|
652
|
-
reject(new Error("Request was aborted"));
|
|
715
|
+
const onError = (event) => {
|
|
716
|
+
fail(extractWebSocketError(event));
|
|
653
717
|
};
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
718
|
+
const onClose = (event) => {
|
|
719
|
+
fail(extractWebSocketCloseError(event));
|
|
720
|
+
};
|
|
721
|
+
const onAbort = () => {
|
|
722
|
+
fail(new Error("Request was aborted"), "aborted");
|
|
659
723
|
};
|
|
660
724
|
socket.addEventListener("open", onOpen);
|
|
661
725
|
socket.addEventListener("error", onError);
|
|
662
726
|
socket.addEventListener("close", onClose);
|
|
663
727
|
signal?.addEventListener("abort", onAbort);
|
|
728
|
+
if (connectTimeoutMs > 0) {
|
|
729
|
+
timeout = setTimeout(() => {
|
|
730
|
+
fail(new Error(`WebSocket connect timeout after ${connectTimeoutMs}ms`), "connect_timeout");
|
|
731
|
+
}, connectTimeoutMs);
|
|
732
|
+
}
|
|
733
|
+
if (signal?.aborted) {
|
|
734
|
+
onAbort();
|
|
735
|
+
}
|
|
664
736
|
});
|
|
665
737
|
}
|
|
666
|
-
async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
738
|
+
async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutMs) {
|
|
667
739
|
if (!sessionId) {
|
|
668
|
-
const socket = await connectWebSocket(url, headers, signal);
|
|
740
|
+
const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
|
|
669
741
|
return {
|
|
670
742
|
socket,
|
|
671
743
|
reused: false,
|
|
672
|
-
release: (
|
|
673
|
-
if (keep === false) {
|
|
674
|
-
closeWebSocketSilently(socket);
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
closeWebSocketSilently(socket);
|
|
678
|
-
},
|
|
744
|
+
release: () => closeWebSocketSilently(socket),
|
|
679
745
|
};
|
|
680
746
|
}
|
|
681
747
|
const cached = websocketSessionCache.get(sessionId);
|
|
@@ -702,7 +768,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
702
768
|
};
|
|
703
769
|
}
|
|
704
770
|
if (cached.busy) {
|
|
705
|
-
const socket = await connectWebSocket(url, headers, signal);
|
|
771
|
+
const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
|
|
706
772
|
return {
|
|
707
773
|
socket,
|
|
708
774
|
reused: false,
|
|
@@ -716,7 +782,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
716
782
|
websocketSessionCache.delete(sessionId);
|
|
717
783
|
}
|
|
718
784
|
}
|
|
719
|
-
const socket = await connectWebSocket(url, headers, signal);
|
|
785
|
+
const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
|
|
720
786
|
const entry = { socket, busy: true };
|
|
721
787
|
websocketSessionCache.set(sessionId, entry);
|
|
722
788
|
return {
|
|
@@ -792,7 +858,7 @@ async function decodeWebSocketData(data) {
|
|
|
792
858
|
}
|
|
793
859
|
return null;
|
|
794
860
|
}
|
|
795
|
-
async function* parseWebSocket(socket, signal) {
|
|
861
|
+
async function* parseWebSocket(socket, signal, idleTimeoutMs) {
|
|
796
862
|
const queue = [];
|
|
797
863
|
let pending = null;
|
|
798
864
|
let done = false;
|
|
@@ -870,8 +936,23 @@ async function* parseWebSocket(socket, signal) {
|
|
|
870
936
|
}
|
|
871
937
|
if (done)
|
|
872
938
|
break;
|
|
873
|
-
|
|
939
|
+
let timeout;
|
|
940
|
+
await new Promise((resolve, reject) => {
|
|
874
941
|
pending = resolve;
|
|
942
|
+
if (idleTimeoutMs !== undefined && idleTimeoutMs > 0) {
|
|
943
|
+
timeout = setTimeout(() => {
|
|
944
|
+
const error = new Error(`WebSocket idle timeout after ${idleTimeoutMs}ms`);
|
|
945
|
+
failed = error;
|
|
946
|
+
done = true;
|
|
947
|
+
pending = null;
|
|
948
|
+
closeWebSocketSilently(socket, 1000, "idle_timeout");
|
|
949
|
+
reject(error);
|
|
950
|
+
}, idleTimeoutMs);
|
|
951
|
+
}
|
|
952
|
+
}).finally(() => {
|
|
953
|
+
if (timeout) {
|
|
954
|
+
clearTimeout(timeout);
|
|
955
|
+
}
|
|
875
956
|
});
|
|
876
957
|
}
|
|
877
958
|
if (failed) {
|
|
@@ -940,8 +1021,8 @@ async function* startWebSocketOutputOnFirstEvent(events, output, stream, onStart
|
|
|
940
1021
|
yield event;
|
|
941
1022
|
}
|
|
942
1023
|
}
|
|
943
|
-
async function processWebSocketStream(url, body, headers, output, stream, model, onStart, options) {
|
|
944
|
-
const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
|
|
1024
|
+
async function processWebSocketStream(url, body, headers, output, stream, model, onStart, idleTimeoutMs, websocketConnectTimeoutMs, options) {
|
|
1025
|
+
const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal, websocketConnectTimeoutMs);
|
|
945
1026
|
let keepConnection = true;
|
|
946
1027
|
const useCachedContext = options?.transport === "websocket-cached" || options?.transport === "auto";
|
|
947
1028
|
// ChatGPT Codex Responses rejects `store: true` ("Store must be set to false").
|
|
@@ -973,7 +1054,7 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
|
|
|
973
1054
|
}
|
|
974
1055
|
try {
|
|
975
1056
|
socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
|
|
976
|
-
await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, onStart), output, stream, model, {
|
|
1057
|
+
await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs)), output, stream, onStart), output, stream, model, {
|
|
977
1058
|
serviceTier: options?.serviceTier,
|
|
978
1059
|
resolveServiceTier: resolveCodexServiceTier,
|
|
979
1060
|
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
|
|
@@ -1071,7 +1152,7 @@ function buildSSEHeaders(initHeaders, additionalHeaders, accountId, token, sessi
|
|
|
1071
1152
|
headers.set("accept", "text/event-stream");
|
|
1072
1153
|
headers.set("content-type", "application/json");
|
|
1073
1154
|
if (sessionId) {
|
|
1074
|
-
headers.set("
|
|
1155
|
+
headers.set("session-id", sessionId);
|
|
1075
1156
|
headers.set("x-client-request-id", sessionId);
|
|
1076
1157
|
}
|
|
1077
1158
|
return headers;
|
|
@@ -1084,7 +1165,7 @@ function buildWebSocketHeaders(initHeaders, additionalHeaders, accountId, token,
|
|
|
1084
1165
|
headers.delete("openai-beta");
|
|
1085
1166
|
headers.set("OpenAI-Beta", OPENAI_BETA_RESPONSES_WEBSOCKETS);
|
|
1086
1167
|
headers.set("x-client-request-id", requestId);
|
|
1087
|
-
headers.set("
|
|
1168
|
+
headers.set("session-id", requestId);
|
|
1088
1169
|
return headers;
|
|
1089
1170
|
}
|
|
1090
1171
|
//# sourceMappingURL=openai-codex-responses.js.map
|