@pure01fx/dsh-openai-codex-auth 0.10.0 → 0.11.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/CHANGELOG.md +13 -0
- package/CODEX-COMPATIBILITY.md +7 -1
- package/README.md +5 -1
- package/client.js +23 -2
- package/lib/catalog.js +2 -7
- package/lib/cloud-context.d.ts +1 -1
- package/lib/cloud-context.js +3 -6
- package/lib/cloud-http.d.ts +0 -3
- package/lib/cloud-http.js +3 -12
- package/lib/cloud-images.d.ts +2 -3
- package/lib/cloud-images.js +9 -14
- package/lib/cloud-media.js +9 -12
- package/lib/cloud-search.d.ts +1 -1
- package/lib/cloud-search.js +11 -30
- package/lib/cloud-tools.js +2 -2
- package/lib/cloud-vision.js +2 -10
- package/lib/cloud-web-tool.js +11 -21
- package/lib/native-http.d.ts +0 -4
- package/lib/native-http.js +11 -13
- package/lib/native-websocket-session.js +1 -3
- package/lib/native-websocket-socket.d.ts +0 -1
- package/lib/native-websocket-socket.js +15 -29
- package/lib/native-websocket.d.ts +0 -2
- package/lib/native-websocket.js +2 -17
- package/lib/replay.d.ts +2 -3
- package/lib/replay.js +13 -43
- package/lib/responses.d.ts +2 -4
- package/lib/responses.js +5 -18
- package/lib/sse.d.ts +1 -3
- package/lib/sse.js +2 -17
- package/package.json +38 -33
package/lib/native-http.d.ts
CHANGED
|
@@ -20,11 +20,9 @@ export interface NativeCodexHttpOptions {
|
|
|
20
20
|
endpoint?: string;
|
|
21
21
|
requestTimeoutMs?: number;
|
|
22
22
|
streamIdleTimeoutMs?: number;
|
|
23
|
-
maxSseEventBytes?: number;
|
|
24
23
|
maxTransientRetries?: number;
|
|
25
24
|
initialRetryDelayMs?: number;
|
|
26
25
|
maxRetryDelayMs?: number;
|
|
27
|
-
maxRequestBodyBytes?: number;
|
|
28
26
|
random?: () => number;
|
|
29
27
|
sleep?: (delayMs: number, signal?: AbortSignal) => Promise<void>;
|
|
30
28
|
createRequestId?: () => string;
|
|
@@ -37,7 +35,6 @@ export interface NativeCodexPreparedRequest {
|
|
|
37
35
|
generation: GenerateOptions;
|
|
38
36
|
mode: NativeCodexTransportMode;
|
|
39
37
|
request: Record<string, unknown>;
|
|
40
|
-
body: string;
|
|
41
38
|
routingId: string;
|
|
42
39
|
routingHint: string;
|
|
43
40
|
}
|
|
@@ -51,7 +48,6 @@ export declare class NativeCodexHttpTransport {
|
|
|
51
48
|
private readonly maxRetries;
|
|
52
49
|
private readonly initialDelayMs;
|
|
53
50
|
private readonly maxDelayMs;
|
|
54
|
-
private readonly maxBodyBytes;
|
|
55
51
|
constructor(options: NativeCodexHttpOptions);
|
|
56
52
|
private retryDelay;
|
|
57
53
|
private wait;
|
package/lib/native-http.js
CHANGED
|
@@ -16,7 +16,6 @@ const DEFAULT_INITIAL_RETRY_DELAY_MS = 200;
|
|
|
16
16
|
const DEFAULT_MAX_RETRY_DELAY_MS = 10_000;
|
|
17
17
|
const INITIAL_CONNECTION_RETRY_DELAY_MS = 5_000;
|
|
18
18
|
const MAX_CONNECTION_RETRY_DELAY_MS = 60_000;
|
|
19
|
-
const DEFAULT_MAX_REQUEST_BODY_BYTES = 24 * 1024 * 1024;
|
|
20
19
|
const MAX_ERROR_BODY_BYTES = 64 * 1024;
|
|
21
20
|
function aborted(message = 'native Codex request was aborted') {
|
|
22
21
|
return new LlmError(message, 'ABORTED');
|
|
@@ -357,7 +356,6 @@ export class NativeCodexHttpTransport {
|
|
|
357
356
|
maxRetries;
|
|
358
357
|
initialDelayMs;
|
|
359
358
|
maxDelayMs;
|
|
360
|
-
maxBodyBytes;
|
|
361
359
|
constructor(options) {
|
|
362
360
|
this.options = options;
|
|
363
361
|
this.fetchImpl = options.fetch ?? fetch;
|
|
@@ -367,7 +365,6 @@ export class NativeCodexHttpTransport {
|
|
|
367
365
|
this.maxRetries = safeRetryCount(options.maxTransientRetries);
|
|
368
366
|
this.initialDelayMs = safePositiveInteger(options.initialRetryDelayMs, DEFAULT_INITIAL_RETRY_DELAY_MS, 'initial retry delay');
|
|
369
367
|
this.maxDelayMs = safePositiveInteger(options.maxRetryDelayMs, DEFAULT_MAX_RETRY_DELAY_MS, 'maximum retry delay');
|
|
370
|
-
this.maxBodyBytes = safePositiveInteger(options.maxRequestBodyBytes, DEFAULT_MAX_REQUEST_BODY_BYTES, 'request body limit');
|
|
371
368
|
}
|
|
372
369
|
retryDelay(retry, providerDelay) {
|
|
373
370
|
if (providerDelay !== undefined)
|
|
@@ -410,24 +407,28 @@ export class NativeCodexHttpTransport {
|
|
|
410
407
|
sessionId: stablePromptCacheKey(sessionId),
|
|
411
408
|
};
|
|
412
409
|
let request;
|
|
413
|
-
let body;
|
|
414
410
|
try {
|
|
415
411
|
request = codexRequestBody(wireOptions, messages, mode);
|
|
416
|
-
body = JSON.stringify(request);
|
|
417
412
|
}
|
|
418
413
|
catch (error) {
|
|
419
414
|
if (error instanceof LlmError)
|
|
420
415
|
throw error;
|
|
421
416
|
throw fixedFailure('native Codex request could not be encoded', 'INVALID_ARGS', { cause: error });
|
|
422
417
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
return { generation, mode, request, body, routingId, routingHint };
|
|
418
|
+
// WebSocket chooses a full request or an incremental suffix after preparation.
|
|
419
|
+
// Serialize only in the selected transport.
|
|
420
|
+
return { generation, mode, request, routingId, routingHint };
|
|
427
421
|
}
|
|
428
422
|
async *stream(generation, mode = {}) {
|
|
429
423
|
const prepared = await this.prepare(generation, mode);
|
|
430
|
-
const {
|
|
424
|
+
const { request, routingId, routingHint } = prepared;
|
|
425
|
+
let body;
|
|
426
|
+
try {
|
|
427
|
+
body = JSON.stringify(request);
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
throw fixedFailure('native Codex request could not be encoded', 'INVALID_ARGS', { cause: error });
|
|
431
|
+
}
|
|
431
432
|
let activeTurnState = mode.turnState;
|
|
432
433
|
if (activeTurnState !== undefined
|
|
433
434
|
&& (activeTurnState.length === 0 || Buffer.byteLength(activeTurnState) > 4096
|
|
@@ -553,9 +554,6 @@ export class NativeCodexHttpTransport {
|
|
|
553
554
|
for await (const chunk of streamResponses(response.body, {
|
|
554
555
|
signal: watchdog.signal,
|
|
555
556
|
onActivity: watchdog.pulse,
|
|
556
|
-
...this.options.maxSseEventBytes === undefined
|
|
557
|
-
? {}
|
|
558
|
-
: { maxEventBytes: this.options.maxSseEventBytes },
|
|
559
557
|
onMalformedEvent: () => {
|
|
560
558
|
this.options.warn?.('native Codex ignored a malformed SSE event');
|
|
561
559
|
},
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/** Pure WebSocket v2 previous-response and incremental suffix state. */
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
4
|
-
const MAX_RESPONSE_ID_BYTES = 256;
|
|
5
4
|
const IGNORED_REUSE_FIELDS = new Set([
|
|
6
5
|
'input', 'previous_response_id', 'generate', 'client_metadata',
|
|
7
6
|
'stream_options', 'access_programs',
|
|
@@ -84,8 +83,7 @@ export class NativeCodexWebSocketSessionState {
|
|
|
84
83
|
return { ...plan, payload: { ...plan.payload, generate: false } };
|
|
85
84
|
}
|
|
86
85
|
complete(responseId, outputItems) {
|
|
87
|
-
if (this.pending === undefined || responseId.length === 0
|
|
88
|
-
|| Buffer.byteLength(responseId) > MAX_RESPONSE_ID_BYTES) {
|
|
86
|
+
if (this.pending === undefined || responseId.length === 0) {
|
|
89
87
|
this.reset();
|
|
90
88
|
throw failure('native Codex WebSocket completion identity is invalid');
|
|
91
89
|
}
|
|
@@ -17,7 +17,6 @@ export interface NativeCodexWebSocketConnectOptions {
|
|
|
17
17
|
headers: Record<string, string>;
|
|
18
18
|
signal?: AbortSignal;
|
|
19
19
|
connectTimeoutMs?: number;
|
|
20
|
-
maxFrameBytes?: number;
|
|
21
20
|
}
|
|
22
21
|
export interface NativeCodexWebSocketFactory {
|
|
23
22
|
connect(options: NativeCodexWebSocketConnectOptions): Promise<NativeCodexWebSocket>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Node WebSocket client seam with injectable deterministic factories. */
|
|
2
2
|
import { LlmError, ProviderRequestId } from '@deepseek-ai/dsh-llm';
|
|
3
3
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
4
4
|
import { getProxyForUrl } from 'proxy-from-env';
|
|
@@ -6,8 +6,6 @@ import { nativeCodexEndpoint } from './endpoint.js';
|
|
|
6
6
|
import { NATIVE_CODEX_CONNECTION_FAILED_CODE, isNativeCodexConnectionFailure, } from './native-adapter.js';
|
|
7
7
|
import WebSocket from 'ws';
|
|
8
8
|
const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
|
|
9
|
-
const DEFAULT_MAX_FRAME_BYTES = 64 * 1024 * 1024;
|
|
10
|
-
const MAX_QUEUED_BYTES = 64 * 1024 * 1024;
|
|
11
9
|
function failure(message, code, cause) {
|
|
12
10
|
return new LlmError(message, code, cause === undefined ? undefined : { cause });
|
|
13
11
|
}
|
|
@@ -54,25 +52,20 @@ export function nativeCodexWebSocketUrl(endpoint) {
|
|
|
54
52
|
class NodeNativeCodexWebSocket {
|
|
55
53
|
socket;
|
|
56
54
|
responseHeaders;
|
|
57
|
-
maxFrameBytes;
|
|
58
55
|
queue = [];
|
|
59
|
-
queuedBytes = 0;
|
|
60
56
|
waiters = [];
|
|
61
57
|
ended = false;
|
|
62
|
-
constructor(socket, responseHeaders
|
|
58
|
+
constructor(socket, responseHeaders) {
|
|
63
59
|
this.socket = socket;
|
|
64
60
|
this.responseHeaders = responseHeaders;
|
|
65
|
-
this.maxFrameBytes = maxFrameBytes;
|
|
66
61
|
socket.on('message', (data, isBinary) => {
|
|
67
62
|
if (isBinary) {
|
|
68
63
|
this.fail(failure('native Codex WebSocket returned a binary frame', 'WS_PROTOCOL_ERROR'));
|
|
69
64
|
return;
|
|
70
65
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
this.push({ type: 'text', text: data.toString('utf8') }, data.byteLength);
|
|
66
|
+
const buffer = Array.isArray(data) ? Buffer.concat(data)
|
|
67
|
+
: Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
68
|
+
this.push({ type: 'text', text: buffer.toString('utf8') });
|
|
76
69
|
});
|
|
77
70
|
socket.on('close', (code, reason) => {
|
|
78
71
|
this.ended = true;
|
|
@@ -82,7 +75,7 @@ class NodeNativeCodexWebSocket {
|
|
|
82
75
|
this.fail(failure('native Codex WebSocket transport failed', 'WS_RETRYABLE', error));
|
|
83
76
|
});
|
|
84
77
|
}
|
|
85
|
-
push(value
|
|
78
|
+
push(value) {
|
|
86
79
|
const waiter = this.waiters.shift();
|
|
87
80
|
if (waiter !== undefined) {
|
|
88
81
|
if (value instanceof LlmError)
|
|
@@ -91,12 +84,7 @@ class NodeNativeCodexWebSocket {
|
|
|
91
84
|
waiter.resolve(value);
|
|
92
85
|
return;
|
|
93
86
|
}
|
|
94
|
-
|
|
95
|
-
this.fail(failure('native Codex WebSocket queued too much response data', 'WS_RESPONSE_TOO_LARGE'));
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
this.queue.push({ value, bytes });
|
|
99
|
-
this.queuedBytes += bytes;
|
|
87
|
+
this.queue.push(value);
|
|
100
88
|
}
|
|
101
89
|
fail(error) {
|
|
102
90
|
if (!this.ended)
|
|
@@ -106,9 +94,8 @@ class NodeNativeCodexWebSocket {
|
|
|
106
94
|
for (const waiter of waiters)
|
|
107
95
|
waiter.reject(error);
|
|
108
96
|
this.queue.length = 0;
|
|
109
|
-
this.queuedBytes = 0;
|
|
110
97
|
if (waiters.length === 0)
|
|
111
|
-
this.queue.push(
|
|
98
|
+
this.queue.push(error);
|
|
112
99
|
}
|
|
113
100
|
async send(text, signal) {
|
|
114
101
|
if (signal?.aborted)
|
|
@@ -124,7 +111,7 @@ class NodeNativeCodexWebSocket {
|
|
|
124
111
|
signal?.addEventListener('abort', abort, { once: true });
|
|
125
112
|
this.socket.send(text, (error) => {
|
|
126
113
|
signal?.removeEventListener('abort', abort);
|
|
127
|
-
if (error === undefined)
|
|
114
|
+
if (error === undefined || error === null)
|
|
128
115
|
resolve();
|
|
129
116
|
else
|
|
130
117
|
reject(failure('native Codex WebSocket send failed', 'WS_RETRYABLE', error));
|
|
@@ -136,10 +123,9 @@ class NodeNativeCodexWebSocket {
|
|
|
136
123
|
throw abortFailure(signal);
|
|
137
124
|
const queued = this.queue.shift();
|
|
138
125
|
if (queued !== undefined) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return queued.value;
|
|
126
|
+
if (queued instanceof LlmError)
|
|
127
|
+
throw queued;
|
|
128
|
+
return queued;
|
|
143
129
|
}
|
|
144
130
|
return new Promise((resolve, reject) => {
|
|
145
131
|
let waiter;
|
|
@@ -169,7 +155,6 @@ class NodeNativeCodexWebSocket {
|
|
|
169
155
|
export class NodeNativeCodexWebSocketFactory {
|
|
170
156
|
async connect(options) {
|
|
171
157
|
const timeout = positive(options.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS, 'connect timeout');
|
|
172
|
-
const maximum = positive(options.maxFrameBytes, DEFAULT_MAX_FRAME_BYTES, 'frame limit');
|
|
173
158
|
if (options.signal?.aborted)
|
|
174
159
|
throw abortFailure(options.signal);
|
|
175
160
|
return new Promise((resolve, reject) => {
|
|
@@ -180,7 +165,8 @@ export class NodeNativeCodexWebSocketFactory {
|
|
|
180
165
|
headers: options.headers,
|
|
181
166
|
...(agent === undefined ? {} : { agent }),
|
|
182
167
|
handshakeTimeout: timeout,
|
|
183
|
-
|
|
168
|
+
// ws uses zero to disable its default message-size ceiling.
|
|
169
|
+
maxPayload: 0,
|
|
184
170
|
perMessageDeflate: true,
|
|
185
171
|
});
|
|
186
172
|
const abort = () => {
|
|
@@ -211,7 +197,7 @@ export class NodeNativeCodexWebSocketFactory {
|
|
|
211
197
|
});
|
|
212
198
|
socket.on('open', () => {
|
|
213
199
|
options.signal?.removeEventListener('abort', abort);
|
|
214
|
-
resolve(new NodeNativeCodexWebSocket(socket, responseHeaders
|
|
200
|
+
resolve(new NodeNativeCodexWebSocket(socket, responseHeaders));
|
|
215
201
|
});
|
|
216
202
|
socket.on('error', (error) => {
|
|
217
203
|
options.signal?.removeEventListener('abort', abort);
|
|
@@ -6,7 +6,6 @@ export interface NativeCodexWebSocketTransportOptions extends NativeCodexHttpOpt
|
|
|
6
6
|
webSocketFactory?: NativeCodexWebSocketFactory;
|
|
7
7
|
webSocketConnectTimeoutMs?: number;
|
|
8
8
|
webSocketIdleTimeoutMs?: number;
|
|
9
|
-
maxWebSocketFrameBytes?: number;
|
|
10
9
|
maxWebSocketSessions?: number;
|
|
11
10
|
webSocketSessionIdleMs?: number;
|
|
12
11
|
maxWebSocketReconnects?: number;
|
|
@@ -19,7 +18,6 @@ export declare class NativeCodexWebSocketTransport implements NativeCodexTranspo
|
|
|
19
18
|
private readonly sessions;
|
|
20
19
|
private readonly connectTimeoutMs;
|
|
21
20
|
private readonly idleTimeoutMs;
|
|
22
|
-
private readonly maxFrameBytes;
|
|
23
21
|
private readonly maxSessions;
|
|
24
22
|
private readonly sessionIdleMs;
|
|
25
23
|
private readonly maxReconnects;
|
package/lib/native-websocket.js
CHANGED
|
@@ -12,7 +12,6 @@ import { NodeNativeCodexWebSocketFactory, } from './native-websocket-socket.js';
|
|
|
12
12
|
import { NativeCodexWebSocketSessionState } from './native-websocket-session.js';
|
|
13
13
|
const WS_BETA = 'responses_websockets=2026-02-06';
|
|
14
14
|
const DEFAULT_IDLE_TIMEOUT_MS = 300_000;
|
|
15
|
-
const DEFAULT_MAX_FRAME_BYTES = 64 * 1024 * 1024;
|
|
16
15
|
const DEFAULT_MAX_SESSIONS = 32;
|
|
17
16
|
const DEFAULT_SESSION_IDLE_MS = 30 * 60_000;
|
|
18
17
|
const DEFAULT_MAX_RECONNECTS = 5;
|
|
@@ -21,14 +20,13 @@ const DEFAULT_MAX_RETRY_DELAY_MS = 10_000;
|
|
|
21
20
|
const INITIAL_CONNECTION_RETRY_DELAY_MS = 5_000;
|
|
22
21
|
const MAX_CONNECTION_RETRY_DELAY_MS = 60_000;
|
|
23
22
|
const MAX_TURN_STATE_BYTES = 4096;
|
|
24
|
-
const MAX_RETAINED_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
25
23
|
function failure(message, code, cause) {
|
|
26
24
|
return new LlmError(message, code, cause === undefined ? undefined : { cause });
|
|
27
25
|
}
|
|
28
26
|
function reconnectable(code) {
|
|
29
27
|
return [
|
|
30
28
|
'WS_RETRYABLE', 'WS_RETRYABLE_RESET', 'WS_PROTOCOL_ERROR',
|
|
31
|
-
'
|
|
29
|
+
'TIMEOUT',
|
|
32
30
|
NATIVE_CODEX_CONNECTION_FAILED_CODE,
|
|
33
31
|
].includes(code);
|
|
34
32
|
}
|
|
@@ -211,7 +209,6 @@ export class NativeCodexWebSocketTransport {
|
|
|
211
209
|
sessions = new Map();
|
|
212
210
|
connectTimeoutMs;
|
|
213
211
|
idleTimeoutMs;
|
|
214
|
-
maxFrameBytes;
|
|
215
212
|
maxSessions;
|
|
216
213
|
sessionIdleMs;
|
|
217
214
|
maxReconnects;
|
|
@@ -226,7 +223,6 @@ export class NativeCodexWebSocketTransport {
|
|
|
226
223
|
this.factory = options.webSocketFactory ?? new NodeNativeCodexWebSocketFactory();
|
|
227
224
|
this.connectTimeoutMs = boundedPositive(options.webSocketConnectTimeoutMs, 10_000, 120_000, 'WebSocket connect timeout');
|
|
228
225
|
this.idleTimeoutMs = boundedPositive(options.webSocketIdleTimeoutMs, DEFAULT_IDLE_TIMEOUT_MS, 60 * 60_000, 'WebSocket idle timeout');
|
|
229
|
-
this.maxFrameBytes = boundedPositive(options.maxWebSocketFrameBytes, DEFAULT_MAX_FRAME_BYTES, DEFAULT_MAX_FRAME_BYTES, 'WebSocket frame limit');
|
|
230
226
|
this.maxSessions = boundedPositive(options.maxWebSocketSessions, DEFAULT_MAX_SESSIONS, 256, 'WebSocket session limit');
|
|
231
227
|
this.sessionIdleMs = boundedPositive(options.webSocketSessionIdleMs, DEFAULT_SESSION_IDLE_MS, 24 * 60 * 60_000, 'WebSocket session idle limit');
|
|
232
228
|
this.maxReconnects = retryCount(options.maxWebSocketReconnects);
|
|
@@ -338,7 +334,6 @@ export class NativeCodexWebSocketTransport {
|
|
|
338
334
|
headers: this.headers(prepared, credential),
|
|
339
335
|
signal,
|
|
340
336
|
connectTimeoutMs: this.connectTimeoutMs,
|
|
341
|
-
maxFrameBytes: this.maxFrameBytes,
|
|
342
337
|
});
|
|
343
338
|
if (this.disposed) {
|
|
344
339
|
socket.close();
|
|
@@ -381,16 +376,12 @@ export class NativeCodexWebSocketTransport {
|
|
|
381
376
|
if (entry.socket === undefined)
|
|
382
377
|
throw failure('native Codex WebSocket is unavailable', 'WS_RETRYABLE');
|
|
383
378
|
const encoded = JSON.stringify(payload);
|
|
384
|
-
if (Buffer.byteLength(encoded) > 24 * 1024 * 1024) {
|
|
385
|
-
throw failure('native Codex WebSocket request exceeded the size limit', 'REQUEST_TOO_LARGE');
|
|
386
|
-
}
|
|
387
379
|
await entry.socket.send(encoded, signal);
|
|
388
380
|
const translator = new ResponsesStreamTranslator(prewarm ? undefined : {
|
|
389
381
|
provider: generation.provider,
|
|
390
382
|
model: mode.publicModel ?? generation.model,
|
|
391
383
|
});
|
|
392
384
|
const outputItems = [];
|
|
393
|
-
let outputBytes = 0;
|
|
394
385
|
while (true) {
|
|
395
386
|
const text = await this.receive(entry, signal);
|
|
396
387
|
let event;
|
|
@@ -420,14 +411,8 @@ export class NativeCodexWebSocketTransport {
|
|
|
420
411
|
entry.turnState = nextTurnState;
|
|
421
412
|
}
|
|
422
413
|
const output = normalizedOutputItem(event);
|
|
423
|
-
if (output !== undefined)
|
|
424
|
-
const nextOutputBytes = outputBytes + Buffer.byteLength(JSON.stringify(output));
|
|
425
|
-
if (nextOutputBytes > MAX_RETAINED_OUTPUT_BYTES) {
|
|
426
|
-
throw failure('native Codex WebSocket retained output exceeded the size limit', 'WS_RESPONSE_TOO_LARGE');
|
|
427
|
-
}
|
|
414
|
+
if (output !== undefined)
|
|
428
415
|
outputItems.push(output);
|
|
429
|
-
outputBytes = nextOutputBytes;
|
|
430
|
-
}
|
|
431
416
|
if (event.type === 'response.completed') {
|
|
432
417
|
const response = typeof event.response === 'object'
|
|
433
418
|
&& event.response !== null
|
package/lib/replay.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Versioned Codex Responses continuation state. */
|
|
2
2
|
import { type ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
3
3
|
export declare const NATIVE_CODEX_REPLAY_KIND = "openai-codex-native.responses-replay";
|
|
4
4
|
export declare const NATIVE_CODEX_REPLAY_VERSION = 1;
|
|
@@ -33,12 +33,11 @@ export interface NativeCodexReplaySource {
|
|
|
33
33
|
export declare function replayableItemId(value: string | undefined): string | undefined;
|
|
34
34
|
/** True only for legacy raw state or an rc.2 envelope emitted by this package. */
|
|
35
35
|
export declare function hasNativeCodexReplayKind(value: unknown): boolean;
|
|
36
|
-
/** Attempt-local
|
|
36
|
+
/** Attempt-local accumulator for completed replay descriptors. */
|
|
37
37
|
export declare class NativeCodexReplayCapture {
|
|
38
38
|
private readonly provider;
|
|
39
39
|
private readonly model;
|
|
40
40
|
private readonly descriptors;
|
|
41
|
-
private stateBytes;
|
|
42
41
|
constructor(provider: string, model: string);
|
|
43
42
|
add(item: NativeCodexReplayDescriptor): void;
|
|
44
43
|
finish(): NativeCodexReplayState | undefined;
|
package/lib/replay.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Versioned Codex Responses continuation state. */
|
|
2
2
|
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
3
3
|
export const NATIVE_CODEX_REPLAY_KIND = 'openai-codex-native.responses-replay';
|
|
4
4
|
export const NATIVE_CODEX_REPLAY_VERSION = 1;
|
|
5
|
-
const MAX_REPLAY_ITEM_ID_BYTES = 256;
|
|
6
|
-
const MAX_REPLAY_CIPHERTEXT_BYTES = 64 * 1024 * 1024;
|
|
7
|
-
const MAX_REPLAY_STATE_BYTES = 64 * 1024 * 1024;
|
|
8
5
|
function failure(message, code = 'INVALID_REPLAY_STATE') {
|
|
9
6
|
return new LlmError(message, code);
|
|
10
7
|
}
|
|
@@ -17,30 +14,22 @@ function onlyKeys(row, keys) {
|
|
|
17
14
|
const allowed = new Set(keys);
|
|
18
15
|
return Object.keys(row).every(key => allowed.has(key));
|
|
19
16
|
}
|
|
20
|
-
function
|
|
21
|
-
return typeof value === 'string' && value.length > 0
|
|
22
|
-
&& Buffer.byteLength(value) <= maximum ? value : undefined;
|
|
17
|
+
function nonemptyString(value) {
|
|
18
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
23
19
|
}
|
|
24
20
|
/** Preserve only server item IDs that Codex itself would replay. */
|
|
25
21
|
export function replayableItemId(value) {
|
|
26
22
|
if (value === undefined)
|
|
27
23
|
return undefined;
|
|
28
|
-
if (Buffer.byteLength(value) > MAX_REPLAY_ITEM_ID_BYTES) {
|
|
29
|
-
throw failure('native Codex response item identity exceeded the replay limit', 'MALFORMED_RESPONSE');
|
|
30
|
-
}
|
|
31
24
|
const split = value.indexOf('_');
|
|
32
25
|
return split > 0 && split < value.length - 1 ? value : undefined;
|
|
33
26
|
}
|
|
34
|
-
function
|
|
35
|
-
let serialized;
|
|
27
|
+
function validateStateJson(value) {
|
|
36
28
|
try {
|
|
37
|
-
|
|
29
|
+
JSON.stringify(value);
|
|
38
30
|
}
|
|
39
31
|
catch {
|
|
40
|
-
throw failure('native Codex replay state is not lossless JSON'
|
|
41
|
-
}
|
|
42
|
-
if (Buffer.byteLength(serialized) > MAX_REPLAY_STATE_BYTES) {
|
|
43
|
-
throw failure('native Codex replay state exceeded the size limit', code);
|
|
32
|
+
throw failure('native Codex replay state is not lossless JSON');
|
|
44
33
|
}
|
|
45
34
|
}
|
|
46
35
|
function validateBlockArray(value) {
|
|
@@ -59,7 +48,7 @@ function parseDescriptor(value) {
|
|
|
59
48
|
if (row === undefined || typeof row.type !== 'string') {
|
|
60
49
|
throw failure('native Codex replay descriptor is invalid');
|
|
61
50
|
}
|
|
62
|
-
const id = row.id === undefined ? undefined :
|
|
51
|
+
const id = row.id === undefined ? undefined : nonemptyString(row.id);
|
|
63
52
|
const split = id?.indexOf('_') ?? -1;
|
|
64
53
|
if (row.id !== undefined && (id === undefined || split <= 0 || split >= id.length - 1)) {
|
|
65
54
|
throw failure('native Codex replay item identity is invalid');
|
|
@@ -74,7 +63,7 @@ function parseDescriptor(value) {
|
|
|
74
63
|
if (row.type === 'reasoning') {
|
|
75
64
|
const blocks = validateBlockArray(row.blocks);
|
|
76
65
|
const encryptedContent = row.encryptedContent === undefined
|
|
77
|
-
? undefined :
|
|
66
|
+
? undefined : nonemptyString(row.encryptedContent);
|
|
78
67
|
if (blocks === undefined
|
|
79
68
|
|| (row.encryptedContent !== undefined && encryptedContent === undefined)
|
|
80
69
|
|| !onlyKeys(row, ['type', 'id', 'blocks', 'encryptedContent'])) {
|
|
@@ -86,7 +75,7 @@ function parseDescriptor(value) {
|
|
|
86
75
|
};
|
|
87
76
|
}
|
|
88
77
|
if (row.type === 'function_call') {
|
|
89
|
-
const namespace = row.namespace === undefined ? undefined :
|
|
78
|
+
const namespace = row.namespace === undefined ? undefined : nonemptyString(row.namespace);
|
|
90
79
|
if (!Number.isSafeInteger(row.block) || Number(row.block) < 0
|
|
91
80
|
|| (row.namespace !== undefined && namespace === undefined)
|
|
92
81
|
|| !onlyKeys(row, ['type', 'id', 'namespace', 'block'])) {
|
|
@@ -109,15 +98,15 @@ export function hasNativeCodexReplayKind(value) {
|
|
|
109
98
|
}
|
|
110
99
|
function parseState(value) {
|
|
111
100
|
const payload = replayPayload(value);
|
|
112
|
-
|
|
101
|
+
validateStateJson(payload);
|
|
113
102
|
const row = object(payload);
|
|
114
103
|
if (row === undefined || row.kind !== NATIVE_CODEX_REPLAY_KIND
|
|
115
104
|
|| row.version !== NATIVE_CODEX_REPLAY_VERSION
|
|
116
105
|
|| !onlyKeys(row, ['kind', 'version', 'provider', 'model', 'items'])) {
|
|
117
106
|
throw failure('native Codex replay state kind or version is invalid');
|
|
118
107
|
}
|
|
119
|
-
const provider =
|
|
120
|
-
const model =
|
|
108
|
+
const provider = nonemptyString(row.provider);
|
|
109
|
+
const model = nonemptyString(row.model);
|
|
121
110
|
if (provider === undefined || model === undefined || !Array.isArray(row.items)
|
|
122
111
|
|| row.items.length === 0) {
|
|
123
112
|
throw failure('native Codex replay state metadata is invalid');
|
|
@@ -131,35 +120,17 @@ function parseState(value) {
|
|
|
131
120
|
items,
|
|
132
121
|
};
|
|
133
122
|
}
|
|
134
|
-
/** Attempt-local
|
|
123
|
+
/** Attempt-local accumulator for completed replay descriptors. */
|
|
135
124
|
export class NativeCodexReplayCapture {
|
|
136
125
|
provider;
|
|
137
126
|
model;
|
|
138
127
|
descriptors = [];
|
|
139
|
-
stateBytes;
|
|
140
128
|
constructor(provider, model) {
|
|
141
129
|
this.provider = provider;
|
|
142
130
|
this.model = model;
|
|
143
|
-
this.stateBytes = Buffer.byteLength(JSON.stringify({
|
|
144
|
-
kind: NATIVE_CODEX_REPLAY_KIND,
|
|
145
|
-
version: NATIVE_CODEX_REPLAY_VERSION,
|
|
146
|
-
provider,
|
|
147
|
-
model,
|
|
148
|
-
items: [],
|
|
149
|
-
}));
|
|
150
131
|
}
|
|
151
132
|
add(item) {
|
|
152
|
-
if (item.type === 'reasoning' && item.encryptedContent !== undefined
|
|
153
|
-
&& Buffer.byteLength(item.encryptedContent) > MAX_REPLAY_CIPHERTEXT_BYTES) {
|
|
154
|
-
throw failure('native Codex encrypted reasoning exceeded the replay limit', 'MALFORMED_RESPONSE');
|
|
155
|
-
}
|
|
156
|
-
const itemBytes = Buffer.byteLength(JSON.stringify(item));
|
|
157
|
-
const nextBytes = this.stateBytes + itemBytes + (this.descriptors.length === 0 ? 0 : 1);
|
|
158
|
-
if (nextBytes > MAX_REPLAY_STATE_BYTES) {
|
|
159
|
-
throw failure('native Codex replay state exceeded the size limit', 'REPLAY_STATE_TOO_LARGE');
|
|
160
|
-
}
|
|
161
133
|
this.descriptors.push(item);
|
|
162
|
-
this.stateBytes = nextBytes;
|
|
163
134
|
}
|
|
164
135
|
finish() {
|
|
165
136
|
return createNativeCodexReplayState(this.provider, this.model, this.descriptors);
|
|
@@ -176,7 +147,6 @@ export function createNativeCodexReplayState(provider, model, items) {
|
|
|
176
147
|
model,
|
|
177
148
|
items: items.map(item => ({ ...item })),
|
|
178
149
|
};
|
|
179
|
-
safeStateSize(state, 'REPLAY_STATE_TOO_LARGE');
|
|
180
150
|
try {
|
|
181
151
|
return parseState(state);
|
|
182
152
|
}
|
package/lib/responses.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ToolCallId, LlmError, type ContentBlock, type GenerateOptions, type StreamChunk, type TokenUsage, type ToolSchema } from '@deepseek-ai/dsh-llm';
|
|
2
2
|
import { type ParseSseOptions } from './sse.js';
|
|
3
3
|
import { type NativeCodexReplaySource } from './replay.js';
|
|
4
4
|
export declare const DEFAULT_CODEX_INSTRUCTIONS = "You are Codex, an AI coding agent. Help the user with software engineering tasks.";
|
|
@@ -11,7 +11,7 @@ export interface ResolvedImagePart {
|
|
|
11
11
|
}
|
|
12
12
|
export interface ResolvedToolResultPart {
|
|
13
13
|
type: 'tool-result';
|
|
14
|
-
toolCallId:
|
|
14
|
+
toolCallId: ToolCallId;
|
|
15
15
|
content: readonly ResolvedContentPart[];
|
|
16
16
|
isError?: boolean;
|
|
17
17
|
}
|
|
@@ -109,11 +109,9 @@ export declare class ResponsesStreamTranslator {
|
|
|
109
109
|
private readonly order;
|
|
110
110
|
private readonly replayCapture;
|
|
111
111
|
private nextIndex;
|
|
112
|
-
private retainedBytes;
|
|
113
112
|
private sawToolCall;
|
|
114
113
|
terminated: boolean;
|
|
115
114
|
constructor(replayContext?: ResponsesReplayContext | undefined);
|
|
116
|
-
private reserve;
|
|
117
115
|
private append;
|
|
118
116
|
private fill;
|
|
119
117
|
private open;
|
package/lib/responses.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/** Pure DSH-to-Codex Responses request and stream translation. */
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import {
|
|
3
|
+
import { ToolCallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE, } from '@deepseek-ai/dsh-llm';
|
|
4
4
|
import { parseSse } from './sse.js';
|
|
5
5
|
import { NativeCodexReplayCapture, replayAssistantInput, replayableItemId, } from './replay.js';
|
|
6
6
|
export const DEFAULT_CODEX_INSTRUCTIONS = 'You are Codex, an AI coding agent. Help the user with software engineering tasks.';
|
|
7
7
|
const CALL_ID_MAX_LENGTH = 64;
|
|
8
8
|
const CALL_ID_PREFIX = 'call_';
|
|
9
|
-
const MAX_RETAINED_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
10
9
|
const UUID_NAMESPACE_OID = Buffer.from('6ba7b8129dad11d180b400c04fd430c8', 'hex');
|
|
11
10
|
function uuidV5(namespace, name) {
|
|
12
11
|
const bytes = createHash('sha1').update(namespace).update(name).digest().subarray(0, 16);
|
|
@@ -312,7 +311,7 @@ function closeBlock(block) {
|
|
|
312
311
|
return { type: 'text', text: block.text };
|
|
313
312
|
if (block.kind === 'reasoning')
|
|
314
313
|
return { type: 'reasoning', text: block.text };
|
|
315
|
-
return { type: 'tool-call', id:
|
|
314
|
+
return { type: 'tool-call', id: ToolCallId(block.callId), name: block.name ?? '', arguments: block.text };
|
|
316
315
|
}
|
|
317
316
|
/** Stateful, transport-free Responses event to DSH chunk translator. */
|
|
318
317
|
export class ResponsesStreamTranslator {
|
|
@@ -321,7 +320,6 @@ export class ResponsesStreamTranslator {
|
|
|
321
320
|
order = [];
|
|
322
321
|
replayCapture;
|
|
323
322
|
nextIndex = 0;
|
|
324
|
-
retainedBytes = 0;
|
|
325
323
|
sawToolCall = false;
|
|
326
324
|
terminated = false;
|
|
327
325
|
constructor(replayContext) {
|
|
@@ -330,26 +328,15 @@ export class ResponsesStreamTranslator {
|
|
|
330
328
|
? undefined
|
|
331
329
|
: new NativeCodexReplayCapture(replayContext.provider, replayContext.model);
|
|
332
330
|
}
|
|
333
|
-
reserve(bytes) {
|
|
334
|
-
const nextBytes = this.retainedBytes + bytes;
|
|
335
|
-
if (!Number.isSafeInteger(nextBytes) || nextBytes > MAX_RETAINED_RESPONSE_BYTES) {
|
|
336
|
-
throw fixedError('native Codex response retained content exceeded the size limit', 'RESPONSE_TOO_LARGE');
|
|
337
|
-
}
|
|
338
|
-
this.retainedBytes = nextBytes;
|
|
339
|
-
}
|
|
340
331
|
append(block, delta) {
|
|
341
|
-
this.reserve(Buffer.byteLength(delta));
|
|
342
332
|
block.text += delta;
|
|
343
333
|
}
|
|
344
334
|
fill(block, text) {
|
|
345
335
|
if (block.text.length > 0)
|
|
346
336
|
return;
|
|
347
|
-
this.reserve(Buffer.byteLength(text));
|
|
348
337
|
block.text = text;
|
|
349
338
|
}
|
|
350
339
|
open(key, kind, chunks, callId = '', name) {
|
|
351
|
-
this.reserve(128 + Buffer.byteLength(key) + Buffer.byteLength(callId)
|
|
352
|
-
+ (name === undefined ? 0 : Buffer.byteLength(name)));
|
|
353
340
|
const block = {
|
|
354
341
|
index: this.nextIndex++, kind, text: '', callId,
|
|
355
342
|
...name === undefined ? {} : { name },
|
|
@@ -398,7 +385,7 @@ export class ResponsesStreamTranslator {
|
|
|
398
385
|
this.sawToolCall = true;
|
|
399
386
|
const block = this.open(`${item.id}:call`, 'tool-call', chunks, item.call_id, item.name);
|
|
400
387
|
chunks.push({
|
|
401
|
-
type: 'tool-call-delta', index: block.index, id:
|
|
388
|
+
type: 'tool-call-delta', index: block.index, id: ToolCallId(block.callId),
|
|
402
389
|
name: item.name, argumentsDelta: '',
|
|
403
390
|
});
|
|
404
391
|
return chunks;
|
|
@@ -430,7 +417,7 @@ export class ResponsesStreamTranslator {
|
|
|
430
417
|
const delta = eventDelta(event);
|
|
431
418
|
this.append(block, delta);
|
|
432
419
|
chunks.push({
|
|
433
|
-
type: 'tool-call-delta', index: block.index, id:
|
|
420
|
+
type: 'tool-call-delta', index: block.index, id: ToolCallId(block.callId),
|
|
434
421
|
...block.name === undefined ? {} : { name: block.name }, argumentsDelta: delta,
|
|
435
422
|
});
|
|
436
423
|
return chunks;
|
|
@@ -445,7 +432,7 @@ export class ResponsesStreamTranslator {
|
|
|
445
432
|
if (item.call_id === undefined || item.call_id.length === 0
|
|
446
433
|
|| item.name === undefined || item.name.length === 0
|
|
447
434
|
|| (item.namespace !== undefined && (typeof item.namespace !== 'string'
|
|
448
|
-
|| item.namespace.length === 0
|
|
435
|
+
|| item.namespace.length === 0))
|
|
449
436
|
|| typeof item.arguments !== 'string') {
|
|
450
437
|
throw fixedError('native Codex function call has invalid content', 'MALFORMED_RESPONSE');
|
|
451
438
|
}
|
package/lib/sse.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export declare const DEFAULT_MAX_SSE_EVENT_BYTES: number;
|
|
2
1
|
export interface SseEvent {
|
|
3
2
|
data: string;
|
|
4
3
|
event?: string;
|
|
@@ -7,7 +6,6 @@ export interface ParseSseOptions {
|
|
|
7
6
|
signal?: AbortSignal;
|
|
8
7
|
onActivity?: () => void;
|
|
9
8
|
onBytes?: (bytes: number) => void;
|
|
10
|
-
maxEventBytes?: number;
|
|
11
9
|
}
|
|
12
|
-
/** Decode a byte stream into
|
|
10
|
+
/** Decode a byte stream into SSE frames. */
|
|
13
11
|
export declare function parseSse(stream: ReadableStream<Uint8Array>, options?: ParseSseOptions): AsyncGenerator<SseEvent>;
|