@fleetagent/pi-ai 0.0.5 → 0.0.7
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 +15 -0
- package/dist/image-models.generated.d.ts.map +1 -1
- package/dist/image-models.generated.js +15 -0
- package/dist/image-models.generated.js.map +1 -1
- package/dist/models.generated.d.ts +754 -376
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +987 -690
- package/dist/models.generated.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 +1 -1
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts.map +1 -1
- package/dist/providers/images/openrouter.js +1 -1
- package/dist/providers/images/openrouter.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +163 -81
- 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 +14 -4
- 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 +1 -1
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/types.d.ts +26 -2
- 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/overflow.d.ts +2 -1
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +5 -2
- package/dist/utils/overflow.js.map +1 -1
- package/package.json +2 -1
|
@@ -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
|
|
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 = 10_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
|
}
|
|
@@ -145,46 +203,42 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
145
203
|
// Fetch with retry logic for rate limits and transient errors
|
|
146
204
|
let response;
|
|
147
205
|
let lastError;
|
|
148
|
-
|
|
206
|
+
const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
207
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
149
208
|
if (options?.signal?.aborted) {
|
|
150
209
|
throw new Error("Request was aborted");
|
|
151
210
|
}
|
|
152
211
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
+
}
|
|
159
230
|
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
160
231
|
if (response.ok) {
|
|
161
232
|
break;
|
|
162
233
|
}
|
|
163
234
|
const errorText = await response.text();
|
|
164
|
-
if (attempt <
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
const retryAfter = response.headers.get("retry-after");
|
|
175
|
-
if (retryAfter) {
|
|
176
|
-
const seconds = Number(retryAfter);
|
|
177
|
-
if (Number.isFinite(seconds)) {
|
|
178
|
-
delayMs = Math.max(0, seconds * 1000);
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
const date = Date.parse(retryAfter);
|
|
182
|
-
if (!Number.isNaN(date)) {
|
|
183
|
-
delayMs = Math.max(0, date - Date.now());
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
235
|
+
if (attempt < maxRetries && isRetryableError(response.status, errorText)) {
|
|
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;
|
|
188
242
|
await sleep(delayMs, options?.signal);
|
|
189
243
|
continue;
|
|
190
244
|
}
|
|
@@ -204,7 +258,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
204
258
|
}
|
|
205
259
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
206
260
|
// Network errors are retryable
|
|
207
|
-
if (attempt <
|
|
261
|
+
if (attempt < maxRetries && !lastError.message.includes("usage limit")) {
|
|
208
262
|
const delayMs = BASE_DELAY_MS * 2 ** attempt;
|
|
209
263
|
await sleep(delayMs, options?.signal);
|
|
210
264
|
continue;
|
|
@@ -240,7 +294,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
240
294
|
return stream;
|
|
241
295
|
};
|
|
242
296
|
export const streamSimpleOpenAICodexResponses = (model, context, options) => {
|
|
243
|
-
const apiKey = options?.apiKey
|
|
297
|
+
const apiKey = options?.apiKey;
|
|
244
298
|
if (!apiKey) {
|
|
245
299
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
246
300
|
}
|
|
@@ -340,7 +394,7 @@ function resolveCodexWebSocketUrl(baseUrl) {
|
|
|
340
394
|
// Response Processing
|
|
341
395
|
// ============================================================================
|
|
342
396
|
async function processStream(response, output, stream, model, options) {
|
|
343
|
-
await processResponsesStream(mapCodexEvents(parseSSE(response)), output, stream, model, {
|
|
397
|
+
await processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal)), output, stream, model, {
|
|
344
398
|
serviceTier: options?.serviceTier,
|
|
345
399
|
resolveServiceTier: resolveCodexServiceTier,
|
|
346
400
|
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
|
|
@@ -407,15 +461,25 @@ function normalizeCodexStatus(status) {
|
|
|
407
461
|
// ============================================================================
|
|
408
462
|
// SSE Parsing
|
|
409
463
|
// ============================================================================
|
|
410
|
-
async function* parseSSE(response) {
|
|
464
|
+
async function* parseSSE(response, signal) {
|
|
411
465
|
if (!response.body)
|
|
412
466
|
return;
|
|
413
467
|
const reader = response.body.getReader();
|
|
414
468
|
const decoder = new TextDecoder();
|
|
415
469
|
let buffer = "";
|
|
470
|
+
const onAbort = () => {
|
|
471
|
+
void reader.cancel().catch(() => { });
|
|
472
|
+
};
|
|
473
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
416
474
|
try {
|
|
417
475
|
while (true) {
|
|
476
|
+
if (signal?.aborted) {
|
|
477
|
+
throw new Error("Request was aborted");
|
|
478
|
+
}
|
|
418
479
|
const { done, value } = await reader.read();
|
|
480
|
+
if (signal?.aborted) {
|
|
481
|
+
throw new Error("Request was aborted");
|
|
482
|
+
}
|
|
419
483
|
if (done)
|
|
420
484
|
break;
|
|
421
485
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -446,6 +510,7 @@ async function* parseSSE(response) {
|
|
|
446
510
|
}
|
|
447
511
|
}
|
|
448
512
|
finally {
|
|
513
|
+
signal?.removeEventListener("abort", onAbort);
|
|
449
514
|
try {
|
|
450
515
|
await reader.cancel();
|
|
451
516
|
}
|
|
@@ -602,7 +667,7 @@ function scheduleSessionWebSocketExpiry(sessionId, entry) {
|
|
|
602
667
|
websocketSessionCache.delete(sessionId);
|
|
603
668
|
}, SESSION_WEBSOCKET_CACHE_TTL_MS);
|
|
604
669
|
}
|
|
605
|
-
async function connectWebSocket(url, headers, signal) {
|
|
670
|
+
async function connectWebSocket(url, headers, signal, connectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS) {
|
|
606
671
|
const WebSocketCtor = await getWebSocketConstructor();
|
|
607
672
|
if (!WebSocketCtor) {
|
|
608
673
|
throw new Error("WebSocket transport is not available in this runtime");
|
|
@@ -611,6 +676,7 @@ async function connectWebSocket(url, headers, signal) {
|
|
|
611
676
|
delete wsHeaders["OpenAI-Beta"];
|
|
612
677
|
return new Promise((resolve, reject) => {
|
|
613
678
|
let settled = false;
|
|
679
|
+
let timeout;
|
|
614
680
|
let socket;
|
|
615
681
|
try {
|
|
616
682
|
socket = new WebSocketCtor(url, { headers: wsHeaders });
|
|
@@ -619,62 +685,63 @@ async function connectWebSocket(url, headers, signal) {
|
|
|
619
685
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
620
686
|
return;
|
|
621
687
|
}
|
|
622
|
-
const
|
|
623
|
-
if (
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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);
|
|
628
697
|
};
|
|
629
|
-
const
|
|
630
|
-
const error = extractWebSocketError(event);
|
|
698
|
+
const fail = (error, closeReason) => {
|
|
631
699
|
if (settled)
|
|
632
700
|
return;
|
|
633
701
|
settled = true;
|
|
634
702
|
cleanup();
|
|
703
|
+
if (closeReason) {
|
|
704
|
+
closeWebSocketSilently(socket, 1000, closeReason);
|
|
705
|
+
}
|
|
635
706
|
reject(error);
|
|
636
707
|
};
|
|
637
|
-
const
|
|
638
|
-
const error = extractWebSocketCloseError(event);
|
|
708
|
+
const onOpen = () => {
|
|
639
709
|
if (settled)
|
|
640
710
|
return;
|
|
641
711
|
settled = true;
|
|
642
712
|
cleanup();
|
|
643
|
-
|
|
713
|
+
resolve(socket);
|
|
644
714
|
};
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
return;
|
|
648
|
-
settled = true;
|
|
649
|
-
cleanup();
|
|
650
|
-
socket.close(1000, "aborted");
|
|
651
|
-
reject(new Error("Request was aborted"));
|
|
715
|
+
const onError = (event) => {
|
|
716
|
+
fail(extractWebSocketError(event));
|
|
652
717
|
};
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
718
|
+
const onClose = (event) => {
|
|
719
|
+
fail(extractWebSocketCloseError(event));
|
|
720
|
+
};
|
|
721
|
+
const onAbort = () => {
|
|
722
|
+
fail(new Error("Request was aborted"), "aborted");
|
|
658
723
|
};
|
|
659
724
|
socket.addEventListener("open", onOpen);
|
|
660
725
|
socket.addEventListener("error", onError);
|
|
661
726
|
socket.addEventListener("close", onClose);
|
|
662
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
|
+
}
|
|
663
736
|
});
|
|
664
737
|
}
|
|
665
|
-
async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
738
|
+
async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutMs) {
|
|
666
739
|
if (!sessionId) {
|
|
667
|
-
const socket = await connectWebSocket(url, headers, signal);
|
|
740
|
+
const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
|
|
668
741
|
return {
|
|
669
742
|
socket,
|
|
670
743
|
reused: false,
|
|
671
|
-
release: (
|
|
672
|
-
if (keep === false) {
|
|
673
|
-
closeWebSocketSilently(socket);
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
closeWebSocketSilently(socket);
|
|
677
|
-
},
|
|
744
|
+
release: () => closeWebSocketSilently(socket),
|
|
678
745
|
};
|
|
679
746
|
}
|
|
680
747
|
const cached = websocketSessionCache.get(sessionId);
|
|
@@ -701,7 +768,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
701
768
|
};
|
|
702
769
|
}
|
|
703
770
|
if (cached.busy) {
|
|
704
|
-
const socket = await connectWebSocket(url, headers, signal);
|
|
771
|
+
const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
|
|
705
772
|
return {
|
|
706
773
|
socket,
|
|
707
774
|
reused: false,
|
|
@@ -715,7 +782,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
715
782
|
websocketSessionCache.delete(sessionId);
|
|
716
783
|
}
|
|
717
784
|
}
|
|
718
|
-
const socket = await connectWebSocket(url, headers, signal);
|
|
785
|
+
const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
|
|
719
786
|
const entry = { socket, busy: true };
|
|
720
787
|
websocketSessionCache.set(sessionId, entry);
|
|
721
788
|
return {
|
|
@@ -791,7 +858,7 @@ async function decodeWebSocketData(data) {
|
|
|
791
858
|
}
|
|
792
859
|
return null;
|
|
793
860
|
}
|
|
794
|
-
async function* parseWebSocket(socket, signal) {
|
|
861
|
+
async function* parseWebSocket(socket, signal, idleTimeoutMs) {
|
|
795
862
|
const queue = [];
|
|
796
863
|
let pending = null;
|
|
797
864
|
let done = false;
|
|
@@ -869,8 +936,23 @@ async function* parseWebSocket(socket, signal) {
|
|
|
869
936
|
}
|
|
870
937
|
if (done)
|
|
871
938
|
break;
|
|
872
|
-
|
|
939
|
+
let timeout;
|
|
940
|
+
await new Promise((resolve, reject) => {
|
|
873
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
|
+
}
|
|
874
956
|
});
|
|
875
957
|
}
|
|
876
958
|
if (failed) {
|
|
@@ -939,8 +1021,8 @@ async function* startWebSocketOutputOnFirstEvent(events, output, stream, onStart
|
|
|
939
1021
|
yield event;
|
|
940
1022
|
}
|
|
941
1023
|
}
|
|
942
|
-
async function processWebSocketStream(url, body, headers, output, stream, model, onStart, options) {
|
|
943
|
-
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);
|
|
944
1026
|
let keepConnection = true;
|
|
945
1027
|
const useCachedContext = options?.transport === "websocket-cached" || options?.transport === "auto";
|
|
946
1028
|
// ChatGPT Codex Responses rejects `store: true` ("Store must be set to false").
|
|
@@ -972,7 +1054,7 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
|
|
|
972
1054
|
}
|
|
973
1055
|
try {
|
|
974
1056
|
socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
|
|
975
|
-
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, {
|
|
976
1058
|
serviceTier: options?.serviceTier,
|
|
977
1059
|
resolveServiceTier: resolveCodexServiceTier,
|
|
978
1060
|
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
|
|
@@ -1070,7 +1152,7 @@ function buildSSEHeaders(initHeaders, additionalHeaders, accountId, token, sessi
|
|
|
1070
1152
|
headers.set("accept", "text/event-stream");
|
|
1071
1153
|
headers.set("content-type", "application/json");
|
|
1072
1154
|
if (sessionId) {
|
|
1073
|
-
headers.set("
|
|
1155
|
+
headers.set("session-id", sessionId);
|
|
1074
1156
|
headers.set("x-client-request-id", sessionId);
|
|
1075
1157
|
}
|
|
1076
1158
|
return headers;
|
|
@@ -1083,7 +1165,7 @@ function buildWebSocketHeaders(initHeaders, additionalHeaders, accountId, token,
|
|
|
1083
1165
|
headers.delete("openai-beta");
|
|
1084
1166
|
headers.set("OpenAI-Beta", OPENAI_BETA_RESPONSES_WEBSOCKETS);
|
|
1085
1167
|
headers.set("x-client-request-id", requestId);
|
|
1086
|
-
headers.set("
|
|
1168
|
+
headers.set("session-id", requestId);
|
|
1087
1169
|
return headers;
|
|
1088
1170
|
}
|
|
1089
1171
|
//# sourceMappingURL=openai-codex-responses.js.map
|