@pure01fx/dsh-openai-codex-auth 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  /** WebSocket v2 Responses transport with bounded session state and sticky HTTP fallback. */
2
2
  import { createHash } from 'node:crypto';
3
+ import { networkTelemetry, trackNetwork, observeNetworkOutput } from './network-telemetry.js';
3
4
  import { LlmError, ProviderRequestId, attributionHeaders, } from '@deepseek-ai/dsh-llm';
4
5
  import { nativeCodexAuthorityHash } from './catalog.js';
5
6
  import { NATIVE_CODEX_CONNECTION_FAILED_CODE, NATIVE_CODEX_STREAM_INTERRUPTED_CODE, isNativeCodexConnectionFailure, } from './native-adapter.js';
@@ -12,7 +13,6 @@ import { NodeNativeCodexWebSocketFactory, } from './native-websocket-socket.js';
12
13
  import { NativeCodexWebSocketSessionState } from './native-websocket-session.js';
13
14
  const WS_BETA = 'responses_websockets=2026-02-06';
14
15
  const DEFAULT_IDLE_TIMEOUT_MS = 300_000;
15
- const DEFAULT_MAX_FRAME_BYTES = 64 * 1024 * 1024;
16
16
  const DEFAULT_MAX_SESSIONS = 32;
17
17
  const DEFAULT_SESSION_IDLE_MS = 30 * 60_000;
18
18
  const DEFAULT_MAX_RECONNECTS = 5;
@@ -21,14 +21,13 @@ const DEFAULT_MAX_RETRY_DELAY_MS = 10_000;
21
21
  const INITIAL_CONNECTION_RETRY_DELAY_MS = 5_000;
22
22
  const MAX_CONNECTION_RETRY_DELAY_MS = 60_000;
23
23
  const MAX_TURN_STATE_BYTES = 4096;
24
- const MAX_RETAINED_OUTPUT_BYTES = 64 * 1024 * 1024;
25
24
  function failure(message, code, cause) {
26
25
  return new LlmError(message, code, cause === undefined ? undefined : { cause });
27
26
  }
28
27
  function reconnectable(code) {
29
28
  return [
30
29
  'WS_RETRYABLE', 'WS_RETRYABLE_RESET', 'WS_PROTOCOL_ERROR',
31
- 'WS_FRAME_TOO_LARGE', 'WS_RESPONSE_TOO_LARGE', 'TIMEOUT',
30
+ 'TIMEOUT',
32
31
  NATIVE_CODEX_CONNECTION_FAILED_CODE,
33
32
  ].includes(code);
34
33
  }
@@ -211,7 +210,6 @@ export class NativeCodexWebSocketTransport {
211
210
  sessions = new Map();
212
211
  connectTimeoutMs;
213
212
  idleTimeoutMs;
214
- maxFrameBytes;
215
213
  maxSessions;
216
214
  sessionIdleMs;
217
215
  maxReconnects;
@@ -226,7 +224,6 @@ export class NativeCodexWebSocketTransport {
226
224
  this.factory = options.webSocketFactory ?? new NodeNativeCodexWebSocketFactory();
227
225
  this.connectTimeoutMs = boundedPositive(options.webSocketConnectTimeoutMs, 10_000, 120_000, 'WebSocket connect timeout');
228
226
  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
227
  this.maxSessions = boundedPositive(options.maxWebSocketSessions, DEFAULT_MAX_SESSIONS, 256, 'WebSocket session limit');
231
228
  this.sessionIdleMs = boundedPositive(options.webSocketSessionIdleMs, DEFAULT_SESSION_IDLE_MS, 24 * 60 * 60_000, 'WebSocket session idle limit');
232
229
  this.maxReconnects = retryCount(options.maxWebSocketReconnects);
@@ -242,11 +239,13 @@ export class NativeCodexWebSocketTransport {
242
239
  const jitter = 0.9 + Math.max(0, Math.min(1, random)) * 0.2;
243
240
  return Math.max(1, Math.round(exponential * jitter));
244
241
  }
245
- async wait(retry, error, signal) {
242
+ async wait(retry, error, signal, telemetry) {
246
243
  const delay = this.retryDelay(retry, error);
244
+ telemetry.retry(delay);
247
245
  await (this.options.sleep ?? sleep)(delay, signal);
248
246
  }
249
- async waitForConnection(delayMs, signal) {
247
+ async waitForConnection(delayMs, signal, telemetry) {
248
+ telemetry.retry(delayMs, 'connection');
250
249
  this.options.warn?.(`native Codex network is unavailable; reconnecting in ${delayMs}ms`);
251
250
  await (this.options.sleep ?? sleep)(delayMs, signal);
252
251
  }
@@ -328,7 +327,7 @@ export class NativeCodexWebSocketTransport {
328
327
  ...attributionHeaders(),
329
328
  };
330
329
  }
331
- async ensureSocket(entry, prepared, credential, signal) {
330
+ async ensureSocket(entry, prepared, credential, signal, telemetry) {
332
331
  const fingerprint = socketCredential(credential);
333
332
  if (entry.socket !== undefined && entry.socketCredential === fingerprint)
334
333
  return;
@@ -338,12 +337,12 @@ export class NativeCodexWebSocketTransport {
338
337
  headers: this.headers(prepared, credential),
339
338
  signal,
340
339
  connectTimeoutMs: this.connectTimeoutMs,
341
- maxFrameBytes: this.maxFrameBytes,
342
340
  });
343
341
  if (this.disposed) {
344
342
  socket.close();
345
343
  throw failure('native Codex WebSocket transport was disposed', 'DISPOSED');
346
344
  }
345
+ telemetry.activity();
347
346
  entry.socket = socket;
348
347
  entry.socketCredential = fingerprint;
349
348
  publishCodexRateLimits(credential.accountId, parseCodexRateLimitHeaders(socket.responseHeaders), this.options.onRateLimits, this.options.warn);
@@ -377,22 +376,21 @@ export class NativeCodexWebSocketTransport {
377
376
  clearTimeout(timer);
378
377
  }
379
378
  }
380
- async *exchange(entry, payload, generation, mode, accountId, prewarm, signal) {
379
+ async *exchange(entry, payload, generation, mode, accountId, prewarm, signal, telemetry) {
381
380
  if (entry.socket === undefined)
382
381
  throw failure('native Codex WebSocket is unavailable', 'WS_RETRYABLE');
383
382
  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
383
  await entry.socket.send(encoded, signal);
384
+ telemetry.state('waiting');
388
385
  const translator = new ResponsesStreamTranslator(prewarm ? undefined : {
389
386
  provider: generation.provider,
390
387
  model: mode.publicModel ?? generation.model,
391
388
  });
392
389
  const outputItems = [];
393
- let outputBytes = 0;
394
390
  while (true) {
395
391
  const text = await this.receive(entry, signal);
392
+ if (entry.socket?.observeActivity === undefined)
393
+ telemetry.activity(Buffer.byteLength(text));
396
394
  let event;
397
395
  try {
398
396
  event = JSON.parse(text);
@@ -420,14 +418,8 @@ export class NativeCodexWebSocketTransport {
420
418
  entry.turnState = nextTurnState;
421
419
  }
422
420
  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
- }
421
+ if (output !== undefined)
428
422
  outputItems.push(output);
429
- outputBytes = nextOutputBytes;
430
- }
431
423
  if (event.type === 'response.completed') {
432
424
  const response = typeof event.response === 'object'
433
425
  && event.response !== null
@@ -447,28 +439,38 @@ export class NativeCodexWebSocketTransport {
447
439
  }
448
440
  }
449
441
  }
450
- async *attempt(entry, prepared, credential, signal) {
451
- await this.ensureSocket(entry, prepared, credential, signal);
452
- let justPrewarmed = false;
453
- if (!entry.prewarmAttempted) {
454
- entry.prewarmAttempted = true;
455
- const warm = entry.protocol.prewarm(withRequestMetadata(prepared.request, entry.turnState, prepared.mode.responsesLite !== undefined));
456
- for await (const _chunk of this.exchange(entry, warm.payload, prepared.generation, prepared.mode, credential.accountId, true, signal)) { /* prewarm is invisible */ }
457
- entry.prewarmSucceeded = true;
458
- justPrewarmed = true;
459
- }
460
- const plan = entry.protocol.plan(withRequestMetadata(prepared.request, entry.turnState, prepared.mode.responsesLite !== undefined), justPrewarmed);
461
- yield* this.exchange(entry, plan.payload, prepared.generation, prepared.mode, credential.accountId, false, signal);
462
- if (this.options.onCompleted !== undefined) {
463
- try {
464
- this.options.onCompleted();
442
+ async *attempt(entry, prepared, credential, signal, telemetry) {
443
+ telemetry.attempt('websocket');
444
+ await this.ensureSocket(entry, prepared, credential, signal, telemetry);
445
+ const stopActivity = entry.socket?.observeActivity?.(bytes => telemetry.activity(bytes));
446
+ try {
447
+ let justPrewarmed = false;
448
+ if (!entry.prewarmAttempted) {
449
+ entry.prewarmAttempted = true;
450
+ const warm = entry.protocol.prewarm(withRequestMetadata(prepared.request, entry.turnState, prepared.mode.responsesLite !== undefined));
451
+ for await (const _chunk of this.exchange(entry, warm.payload, prepared.generation, prepared.mode, credential.accountId, true, signal, telemetry)) { /* prewarm is invisible */ }
452
+ entry.prewarmSucceeded = true;
453
+ justPrewarmed = true;
465
454
  }
466
- catch {
467
- this.options.warn?.('native Codex usage refresh could not be scheduled');
455
+ const plan = entry.protocol.plan(withRequestMetadata(prepared.request, entry.turnState, prepared.mode.responsesLite !== undefined), justPrewarmed);
456
+ yield* this.exchange(entry, plan.payload, prepared.generation, prepared.mode, credential.accountId, false, signal, telemetry);
457
+ if (this.options.onCompleted !== undefined) {
458
+ try {
459
+ this.options.onCompleted();
460
+ }
461
+ catch {
462
+ this.options.warn?.('native Codex usage refresh could not be scheduled');
463
+ }
468
464
  }
469
465
  }
466
+ finally {
467
+ stopActivity?.();
468
+ }
470
469
  }
471
470
  async *stream(generation, mode = {}) {
471
+ yield* trackNetwork(this.options.telemetry ?? networkTelemetry, 'websocket', generation.signal, handle => this.streamTracked(generation, mode, handle));
472
+ }
473
+ async *streamTracked(generation, mode, telemetry) {
472
474
  if (this.disposed)
473
475
  throw failure('native Codex WebSocket transport was disposed', 'DISPOSED');
474
476
  const lifecycle = new AbortController();
@@ -515,7 +517,7 @@ export class NativeCodexWebSocketTransport {
515
517
  let requestCompleted = false;
516
518
  try {
517
519
  if (entry.disabled) {
518
- yield* this.http.stream(activeGeneration, fallbackMode());
520
+ yield* this.http.stream(activeGeneration, fallbackMode(), telemetry);
519
521
  requestCompleted = true;
520
522
  return;
521
523
  }
@@ -524,6 +526,7 @@ export class NativeCodexWebSocketTransport {
524
526
  let attemptedCredential;
525
527
  const enteringPrewarm = !entry.prewarmAttempted;
526
528
  try {
529
+ telemetry.state('preparing');
527
530
  const credential = await this.options.resolveCredential(signal);
528
531
  attemptedCredential = credential;
529
532
  if (pinnedAccountId === undefined)
@@ -532,8 +535,9 @@ export class NativeCodexWebSocketTransport {
532
535
  throw failure('native Codex account changed during request', 'AUTH');
533
536
  }
534
537
  this.assertFastAuthority(credential, mode);
535
- for await (const chunk of this.attempt(entry, prepared, credential, signal)) {
538
+ for await (const chunk of this.attempt(entry, prepared, credential, signal, telemetry)) {
536
539
  emitted = true;
540
+ observeNetworkOutput(telemetry, chunk);
537
541
  yield chunk;
538
542
  }
539
543
  requestCompleted = true;
@@ -558,7 +562,7 @@ export class NativeCodexWebSocketTransport {
558
562
  // the DSH turn in an invisible infinite reconnect loop.
559
563
  if (attemptedCredential === undefined
560
564
  && isNativeCodexConnectionFailure(failureValue)) {
561
- await this.waitForConnection(connectionRetryDelayMs, signal);
565
+ await this.waitForConnection(connectionRetryDelayMs, signal, telemetry);
562
566
  connectionRetryDelayMs = Math.min(connectionRetryDelayMs * 2, MAX_CONNECTION_RETRY_DELAY_MS);
563
567
  continue;
564
568
  }
@@ -570,8 +574,10 @@ export class NativeCodexWebSocketTransport {
570
574
  try {
571
575
  const changed = await this.options.recoverCredential(attemptedCredential, signal);
572
576
  recovered = true;
573
- if (changed)
577
+ if (changed) {
578
+ telemetry.retry(0);
574
579
  continue;
580
+ }
575
581
  throw failure('native Codex rejected the configured credential', 'AUTH');
576
582
  }
577
583
  catch (recoveryError) {
@@ -581,7 +587,7 @@ export class NativeCodexWebSocketTransport {
581
587
  if (generation.signal?.aborted)
582
588
  throw recoveryError;
583
589
  if (isNativeCodexConnectionFailure(recoveryError)) {
584
- await this.waitForConnection(connectionRetryDelayMs, signal);
590
+ await this.waitForConnection(connectionRetryDelayMs, signal, telemetry);
585
591
  connectionRetryDelayMs = Math.min(connectionRetryDelayMs * 2, MAX_CONNECTION_RETRY_DELAY_MS);
586
592
  continue;
587
593
  }
@@ -592,18 +598,19 @@ export class NativeCodexWebSocketTransport {
592
598
  if (failureValue.code !== 'WS_UPGRADE_REQUIRED' && retryable
593
599
  && enteringPrewarm && !entry.prewarmSucceeded) {
594
600
  entry.prewarmAttempted = true;
595
- await this.wait(0, failureValue, signal);
601
+ await this.wait(0, failureValue, signal, telemetry);
596
602
  continue;
597
603
  }
598
604
  if (failureValue.code !== 'WS_UPGRADE_REQUIRED' && retryable
599
605
  && reconnects < this.maxReconnects) {
600
- await this.wait(reconnects, failureValue, signal);
606
+ await this.wait(reconnects, failureValue, signal, telemetry);
601
607
  reconnects++;
602
608
  continue;
603
609
  }
604
610
  if (failureValue.code === 'WS_UPGRADE_REQUIRED' || retryable) {
605
611
  entry.disabled = true;
606
- yield* this.http.stream(activeGeneration, fallbackMode());
612
+ telemetry.retry(0);
613
+ yield* this.http.stream(activeGeneration, fallbackMode(), telemetry);
607
614
  requestCompleted = true;
608
615
  return;
609
616
  }
@@ -0,0 +1,61 @@
1
+ /** Process-wide, content-free inference telemetry. All mutations are synchronous. */
2
+ import type { StreamChunk } from '@deepseek-ai/dsh-llm';
3
+ export type NetworkState = 'preparing' | 'connecting' | 'waiting' | 'streaming' | 'retrying' | 'completed' | 'failed' | 'cancelled';
4
+ export type NetworkTransport = 'http' | 'websocket';
5
+ export interface NetworkRequest {
6
+ id: string;
7
+ state: NetworkState;
8
+ transport: NetworkTransport;
9
+ startedAt: number;
10
+ lastActivityAt: number | null;
11
+ lastOutputAt: number | null;
12
+ retryAt: number | null;
13
+ retryKind: 'transient' | 'connection' | null;
14
+ retries: number;
15
+ receivedBytes: number;
16
+ attempts: number;
17
+ }
18
+ export interface NetworkSnapshot {
19
+ scope: 'process';
20
+ startedAt: number;
21
+ updatedAt: number;
22
+ activeCount: number;
23
+ totals: {
24
+ requests: number;
25
+ succeeded: number;
26
+ failed: number;
27
+ cancelled: number;
28
+ retries: number;
29
+ receivedBytes: number;
30
+ };
31
+ requests: NetworkRequest[];
32
+ truncated: boolean;
33
+ }
34
+ export interface NetworkRequestHandle {
35
+ state(state: 'preparing' | 'waiting'): void;
36
+ attempt(transport: NetworkTransport): void;
37
+ activity(bytes?: number): void;
38
+ output(): void;
39
+ retry(delayMs: number, kind?: 'transient' | 'connection'): void;
40
+ finish(state: 'completed' | 'failed' | 'cancelled'): void;
41
+ }
42
+ /** Keeps at most limit records, preferentially evicting terminal records. Handles
43
+ * remain owned by running generators; overflow never loses aggregate accounting.
44
+ * No timers, request content, external identifiers, or errors are retained. */
45
+ export declare class NetworkTelemetry {
46
+ private readonly limit;
47
+ private readonly now;
48
+ private readonly rows;
49
+ private readonly startedAt;
50
+ private activeCount;
51
+ private sequence;
52
+ private evicted;
53
+ private readonly totals;
54
+ constructor(limit?: number, now?: () => number);
55
+ begin(transport: NetworkTransport): NetworkRequestHandle;
56
+ snapshot(): NetworkSnapshot;
57
+ }
58
+ export declare const networkTelemetry: NetworkTelemetry;
59
+ export declare function observeNetworkOutput(handle: NetworkRequestHandle, chunk: StreamChunk): void;
60
+ /** Generator return/break is cancellation; fallback delegates with the same handle. */
61
+ export declare function trackNetwork<T>(telemetry: NetworkTelemetry, transport: NetworkTransport, signal: AbortSignal | undefined, run: (handle: NetworkRequestHandle) => AsyncIterable<T>): AsyncIterable<T>;
@@ -0,0 +1,104 @@
1
+ /** Keeps at most limit records, preferentially evicting terminal records. Handles
2
+ * remain owned by running generators; overflow never loses aggregate accounting.
3
+ * No timers, request content, external identifiers, or errors are retained. */
4
+ export class NetworkTelemetry {
5
+ limit;
6
+ now;
7
+ rows = new Map();
8
+ startedAt;
9
+ activeCount = 0;
10
+ sequence = 0;
11
+ evicted = false;
12
+ totals = { requests: 0, succeeded: 0, failed: 0, cancelled: 0, retries: 0, receivedBytes: 0 };
13
+ constructor(limit = 128, now = Date.now) {
14
+ this.limit = limit;
15
+ this.now = now;
16
+ if (!Number.isSafeInteger(limit) || limit < 1)
17
+ throw new RangeError('Invalid telemetry limit');
18
+ this.startedAt = now();
19
+ }
20
+ begin(transport) {
21
+ const row = {
22
+ id: String(++this.sequence), state: 'preparing', transport, startedAt: this.now(),
23
+ lastActivityAt: null, lastOutputAt: null, retryAt: null, retryKind: null, retries: 0, receivedBytes: 0, attempts: 0,
24
+ };
25
+ if (this.rows.size >= this.limit) {
26
+ const terminal = [...this.rows.values()].find(item => ['completed', 'failed', 'cancelled'].includes(item.state));
27
+ this.rows.delete(terminal?.id ?? this.rows.keys().next().value);
28
+ this.evicted = true;
29
+ }
30
+ this.rows.set(row.id, row);
31
+ this.activeCount++;
32
+ this.totals.requests++;
33
+ let finished = false;
34
+ const mutate = (fn) => {
35
+ if (finished)
36
+ return;
37
+ fn();
38
+ };
39
+ return {
40
+ state: state => mutate(() => { row.state = state; row.retryAt = null; if (state === 'waiting')
41
+ row.retryKind = null; }),
42
+ attempt: transport => mutate(() => {
43
+ row.transport = transport;
44
+ row.state = 'connecting';
45
+ row.retryAt = null;
46
+ row.attempts++;
47
+ }),
48
+ activity: (bytes = 0) => mutate(() => {
49
+ row.lastActivityAt = this.now();
50
+ if (Number.isSafeInteger(bytes) && bytes > 0) {
51
+ row.receivedBytes += bytes;
52
+ this.totals.receivedBytes += bytes;
53
+ }
54
+ }),
55
+ output: () => mutate(() => { row.state = 'streaming'; row.retryKind = null; row.lastOutputAt = this.now(); }),
56
+ retry: (delayMs, kind = 'transient') => mutate(() => {
57
+ row.retryKind = kind;
58
+ row.state = 'retrying';
59
+ row.retryAt = this.now() + Math.max(0, delayMs);
60
+ row.retries++;
61
+ this.totals.retries++;
62
+ }),
63
+ finish: state => mutate(() => {
64
+ row.state = state;
65
+ row.retryAt = null;
66
+ row.retryKind = null;
67
+ this.activeCount--;
68
+ this.totals[state === 'completed' ? 'succeeded' : state]++;
69
+ finished = true;
70
+ }),
71
+ };
72
+ }
73
+ snapshot() {
74
+ return {
75
+ scope: 'process', startedAt: this.startedAt, updatedAt: this.now(),
76
+ activeCount: this.activeCount, totals: { ...this.totals },
77
+ requests: [...this.rows.values()].reverse().map(row => ({ ...row })), truncated: this.evicted,
78
+ };
79
+ }
80
+ }
81
+ export const networkTelemetry = new NetworkTelemetry();
82
+ export function observeNetworkOutput(handle, chunk) {
83
+ if ((chunk.type === 'text-delta' || chunk.type === 'reasoning-delta') && chunk.text.length > 0
84
+ || chunk.type === 'tool-call-delta')
85
+ handle.output();
86
+ if (chunk.type === 'finish' && chunk.reason.kind === 'error')
87
+ handle.finish('failed');
88
+ }
89
+ /** Generator return/break is cancellation; fallback delegates with the same handle. */
90
+ export async function* trackNetwork(telemetry, transport, signal, run) {
91
+ const handle = telemetry.begin(transport);
92
+ try {
93
+ yield* run(handle);
94
+ handle.finish('completed');
95
+ }
96
+ catch (error) {
97
+ const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;
98
+ handle.finish(signal?.aborted || code === 'ABORTED' || code === 'DISPOSED' ? 'cancelled' : 'failed');
99
+ throw error;
100
+ }
101
+ finally {
102
+ handle.finish('cancelled');
103
+ }
104
+ }
package/lib/replay.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** Bounded, versioned Codex Responses continuation state. */
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 byte-bounded accumulator; no ciphertext can grow unchecked before completion. */
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
- /** Bounded, versioned Codex Responses continuation state. */
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 boundedString(value, maximum = 256) {
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 safeStateSize(value, code) {
35
- let serialized;
27
+ function validateStateJson(value) {
36
28
  try {
37
- serialized = JSON.stringify(value);
29
+ JSON.stringify(value);
38
30
  }
39
31
  catch {
40
- throw failure('native Codex replay state is not lossless JSON', code);
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 : boundedString(row.id, MAX_REPLAY_ITEM_ID_BYTES);
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 : boundedString(row.encryptedContent, MAX_REPLAY_CIPHERTEXT_BYTES);
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 : boundedString(row.namespace);
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
- safeStateSize(payload, 'INVALID_REPLAY_STATE');
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 = boundedString(row.provider);
120
- const model = boundedString(row.model, 512);
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 byte-bounded accumulator; no ciphertext can grow unchecked before completion. */
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
  }
@@ -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
@@ -6,7 +6,6 @@ import { NativeCodexReplayCapture, replayAssistantInput, replayableItemId, } fro
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);
@@ -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 },
@@ -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 || Buffer.byteLength(item.namespace) > 256))
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 bounded SSE frames. */
10
+ /** Decode a byte stream into SSE frames. */
13
11
  export declare function parseSse(stream: ReadableStream<Uint8Array>, options?: ParseSseOptions): AsyncGenerator<SseEvent>;