@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.
@@ -10,8 +10,8 @@ export function parseCloudVisionArgs(value) {
10
10
  const row = value;
11
11
  if (Object.keys(row).some(key => !['prompt', 'images', 'model', 'detail', 'reasoning_effort'].includes(key)))
12
12
  throw new Error('Unknown vision argument');
13
- if (typeof row.prompt !== 'string' || !row.prompt.trim() || Buffer.byteLength(row.prompt) > 32_000)
14
- throw new Error('Vision prompt must be a nonempty bounded string');
13
+ if (typeof row.prompt !== 'string' || !row.prompt.trim())
14
+ throw new Error('Vision prompt must be a nonempty string');
15
15
  const selection = validateImageSelection(row);
16
16
  if (!selection.images)
17
17
  throw new Error('Vision requires explicit images');
@@ -73,21 +73,13 @@ export async function inspectCloudImages(args, deps) {
73
73
  ...(model.instructionsTemplate === undefined ? {} : { instructionsTemplate: model.instructionsTemplate }),
74
74
  } } : {}) });
75
75
  const texts = new Map();
76
- let visibleBytes = 0;
77
76
  let usage;
78
77
  let finished = false;
79
78
  for await (const chunk of chunks) {
80
79
  if (chunk.type === 'tool-call-delta' || (chunk.type === 'block-start' && chunk.blockType === 'tool-call'))
81
80
  throw new LlmError('Image inspection returned an unexpected tool call', 'UNSUPPORTED');
82
- if (chunk.type === 'text-delta') {
83
- visibleBytes += Buffer.byteLength(chunk.text);
84
- if (visibleBytes > 128 * 1024)
85
- throw new LlmError('Image inspection output exceeded limit', 'RESPONSE_TOO_LARGE');
86
- }
87
81
  if (chunk.type === 'block-end' && chunk.block.type === 'text') {
88
82
  texts.set(chunk.index, chunk.block.text);
89
- if ([...texts.values()].reduce((bytes, text) => bytes + Buffer.byteLength(text), 0) > 128 * 1024)
90
- throw new LlmError('Image inspection output exceeded limit', 'RESPONSE_TOO_LARGE');
91
83
  }
92
84
  if (chunk.type === 'usage')
93
85
  usage = chunk.usage;
@@ -22,7 +22,7 @@ const operations = {
22
22
  response_length: enumeration('short', 'medium', 'long'),
23
23
  };
24
24
  const parameters = object({
25
- commands: { ...object(operations), description: 'One or more nonempty command arrays; at most 100 operations total. Dependent actions require separate calls. screenshot only supports zero-indexed PDF pages.' },
25
+ commands: { ...object(operations), description: 'One or more nonempty command arrays. Dependent actions require separate calls. screenshot only supports zero-indexed PDF pages.' },
26
26
  model: { ...str, description: 'Explicit Codex catalog model; otherwise cloudTools.searchModel, then current openai-codex model. No guessed fallback.' },
27
27
  context: { ...str, description: 'Optional explicit text. Default includes last two visible human texts and at most 1000 UTF-8 bytes of visible assistant text.' },
28
28
  mode: enumeration('cached', 'indexed', 'live'), search_context_size: enumeration('low', 'medium', 'high'),
@@ -51,12 +51,8 @@ function inputImage(value) {
51
51
  async function admitSearchImages(body, attachments, signal) {
52
52
  const images = [];
53
53
  const notes = new Set();
54
- let seen = 0;
55
- let bytes = 0;
56
- const visit = async (item, depth) => {
54
+ const visit = async (item) => {
57
55
  signal.throwIfAborted();
58
- if (depth > 8)
59
- return item;
60
56
  if (inputImage(item)) {
61
57
  if (publicUrl(item.image_url)) {
62
58
  notes.add('Remote image unavailable: the public DSH web boundary has no binary image retrieval. Image and source links are preserved.');
@@ -69,12 +65,9 @@ async function admitSearchImages(body, attachments, signal) {
69
65
  try {
70
66
  if (!attachments)
71
67
  throw new Error('Attachment service unavailable');
72
- if (++seen > 4 || match[2].length > 12 * 1024 * 1024)
73
- throw new Error('Image count or byte limit');
74
68
  const data = Buffer.from(match[2], 'base64');
75
- bytes += data.length;
76
- if (!data.length || bytes > 8 * 1024 * 1024 || data.toString('base64') !== match[2])
77
- throw new Error('Invalid or oversized inline image');
69
+ if (!data.length || data.toString('base64') !== match[2])
70
+ throw new Error('Invalid inline image');
78
71
  signal.throwIfAborted();
79
72
  ref = await attachments.saveImage({ data, mediaType: match[1] });
80
73
  signal.throwIfAborted();
@@ -87,26 +80,23 @@ async function admitSearchImages(body, attachments, signal) {
87
80
  // Binary transfer data must not become historical text; unknown fields remain opaque.
88
81
  return { ...item, image_url: ref ? '[DSH attachment ' + ref.attachmentId + ']' : '[inline image unavailable]' };
89
82
  }
90
- // Bounded structural walk permits known content blocks nested in evolving result DTOs.
83
+ // Visit known content blocks while keeping unknown object fields opaque.
91
84
  if (Array.isArray(item)) {
92
- if (item.length > 100)
93
- return item; // Unknown large arrays stay opaque for bounded parsing.
94
85
  const result = [];
95
86
  for (const value of item)
96
- result.push(await visit(value, depth + 1));
87
+ result.push(await visit(value));
97
88
  return result;
98
89
  }
99
- if (record(item) && Array.isArray(item.content) && item.content.length <= 100) {
100
- return { ...item, content: await visit(item.content, depth + 1) };
90
+ if (record(item) && Array.isArray(item.content)) {
91
+ return { ...item, content: await visit(item.content) };
101
92
  }
102
93
  return item;
103
94
  };
104
95
  if (!record(body) || !Array.isArray(body.results))
105
96
  return { body, images, notes };
106
- // Leave excess entries for the response parser to mark explicitly as truncated.
107
97
  const results = [];
108
- for (let i = 0; i < body.results.length; i++)
109
- results.push(i < 100 ? await visit(body.results[i], 0) : body.results[i]);
98
+ for (const item of body.results)
99
+ results.push(await visit(item));
110
100
  return { body: { ...body, results }, images, notes };
111
101
  }
112
102
  const markerPrefix = '[codex_web context: ';
@@ -208,7 +198,7 @@ export function registerCodexWeb(ctx, deps) {
208
198
  checkReferences(args, messages, id, recent);
209
199
  const response = await deps.client.post('alpha/search', body, { credential, signal: exec.signal });
210
200
  // Validate the envelope before any attachment write; image bytes are replaced
211
- // before the final bounded result projection so large valid images can attach.
201
+ // before the final result projection so inline images become attachments.
212
202
  if (!record(response.body) || typeof response.body.output !== 'string' || (response.body.results != null && !Array.isArray(response.body.results)))
213
203
  throw new LlmError('Invalid Codex search response', 'CODEX_CLOUD_FAILED');
214
204
  const media = await admitSearchImages(response.body, agent.ctx.attachments, exec.signal);
package/lib/index.d.ts CHANGED
@@ -200,6 +200,7 @@ export declare class OpenAICodexAuth extends Service {
200
200
  private sendText;
201
201
  private trustedManagementRequest;
202
202
  private requireCsrf;
203
+ private handleNetwork;
203
204
  private handleStatus;
204
205
  private handleAccountUsage;
205
206
  private handleDeviceStart;
package/lib/index.js CHANGED
@@ -18,6 +18,7 @@ import { NativeCodexCloudClient } from './cloud-http.js';
18
18
  import { registerCodexImageTools } from './cloud-tools.js';
19
19
  import { registerCodexWeb } from './cloud-web-tool.js';
20
20
  import { mergeDirectUsage, normalizeUsage } from './usage.js';
21
+ import { networkTelemetry } from './network-telemetry.js';
21
22
  export { normalizeUsage } from './usage.js';
22
23
  export { CODEX_CLIENT_VERSION, TRACKED_CODEX_COMMIT, TRACKED_CODEX_RELEASE, TRACKED_CODEX_REPOSITORY, } from './upstream.js';
23
24
  const CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
@@ -829,6 +830,7 @@ export class OpenAICodexAuth extends Service {
829
830
  disposers.push(ctx.webServer.register({ kind: 'exact', path, handler }));
830
831
  };
831
832
  register('/openai-codex/status', (req, res) => this.handleStatus(req, res));
833
+ register('/openai-codex/network', (req, res) => this.handleNetwork(req, res));
832
834
  register('/openai-codex/device/start', (req, res) => this.handleDeviceStart(req, res));
833
835
  register('/openai-codex/browser/start', (req, res) => this.handleBrowserStart(req, res));
834
836
  register('/openai-codex/browser/prepare', (req, res) => this.handleBrowserPrepare(req, res));
@@ -2013,6 +2015,15 @@ export class OpenAICodexAuth extends Service {
2013
2015
  this.sendJson(res, 403, { error: 'Invalid CSRF token.' });
2014
2016
  return false;
2015
2017
  }
2018
+ handleNetwork(req, res) {
2019
+ if (!this.trustedManagementRequest(req, res))
2020
+ return;
2021
+ if (req.method !== 'GET') {
2022
+ this.sendJson(res, 405, { error: 'GET only' }, { allow: 'GET' });
2023
+ return;
2024
+ }
2025
+ this.sendJson(res, 200, networkTelemetry.snapshot());
2026
+ }
2016
2027
  async handleStatus(req, res) {
2017
2028
  if (!this.trustedManagementRequest(req, res))
2018
2029
  return;
@@ -1,3 +1,4 @@
1
+ import { type NetworkTelemetry, type NetworkRequestHandle } from './network-telemetry.js';
1
2
  import { type ContentBlock, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm';
2
3
  import { type NativeCodexCredential } from './catalog.js';
3
4
  import { type NativeCodexTransportMode } from './native-adapter.js';
@@ -16,15 +17,14 @@ export interface NativeCodexHttpOptions {
16
17
  resolveCredential(signal?: AbortSignal): Promise<NativeCodexCredential>;
17
18
  recoverCredential?(previous: NativeCodexCredential, signal?: AbortSignal): Promise<boolean>;
18
19
  readImage?(attachment: ImageBlock['attachment'], signal?: AbortSignal): Promise<NativeCodexImageRead>;
20
+ telemetry?: NetworkTelemetry;
19
21
  fetch?: typeof fetch;
20
22
  endpoint?: string;
21
23
  requestTimeoutMs?: number;
22
24
  streamIdleTimeoutMs?: number;
23
- maxSseEventBytes?: number;
24
25
  maxTransientRetries?: number;
25
26
  initialRetryDelayMs?: number;
26
27
  maxRetryDelayMs?: number;
27
- maxRequestBodyBytes?: number;
28
28
  random?: () => number;
29
29
  sleep?: (delayMs: number, signal?: AbortSignal) => Promise<void>;
30
30
  createRequestId?: () => string;
@@ -37,7 +37,6 @@ export interface NativeCodexPreparedRequest {
37
37
  generation: GenerateOptions;
38
38
  mode: NativeCodexTransportMode;
39
39
  request: Record<string, unknown>;
40
- body: string;
41
40
  routingId: string;
42
41
  routingHint: string;
43
42
  }
@@ -51,13 +50,13 @@ export declare class NativeCodexHttpTransport {
51
50
  private readonly maxRetries;
52
51
  private readonly initialDelayMs;
53
52
  private readonly maxDelayMs;
54
- private readonly maxBodyBytes;
55
53
  constructor(options: NativeCodexHttpOptions);
56
54
  private retryDelay;
57
55
  private wait;
58
56
  private waitForConnection;
59
57
  endpointUrl(): string;
60
58
  prepare(generation: GenerateOptions, mode?: NativeCodexTransportMode): Promise<NativeCodexPreparedRequest>;
61
- stream(generation: GenerateOptions, mode?: NativeCodexTransportMode): AsyncIterable<StreamChunk>;
59
+ stream(generation: GenerateOptions, mode?: NativeCodexTransportMode, telemetry?: NetworkRequestHandle): AsyncIterable<StreamChunk>;
60
+ private streamTracked;
62
61
  }
63
62
  export {};
@@ -1,5 +1,6 @@
1
1
  /** Native ChatGPT Codex HTTP/SSE transport with safe pre-output retries. */
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
+ import { networkTelemetry, trackNetwork, observeNetworkOutput } from './network-telemetry.js';
3
4
  import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, attributionHeaders, } from '@deepseek-ai/dsh-llm';
4
5
  import { nativeCodexAuthorityHash } from './catalog.js';
5
6
  import { nativeCodexEndpoint } from './endpoint.js';
@@ -16,7 +17,6 @@ const DEFAULT_INITIAL_RETRY_DELAY_MS = 200;
16
17
  const DEFAULT_MAX_RETRY_DELAY_MS = 10_000;
17
18
  const INITIAL_CONNECTION_RETRY_DELAY_MS = 5_000;
18
19
  const MAX_CONNECTION_RETRY_DELAY_MS = 60_000;
19
- const DEFAULT_MAX_REQUEST_BODY_BYTES = 24 * 1024 * 1024;
20
20
  const MAX_ERROR_BODY_BYTES = 64 * 1024;
21
21
  function aborted(message = 'native Codex request was aborted') {
22
22
  return new LlmError(message, 'ABORTED');
@@ -199,7 +199,7 @@ async function resolveMessages(generation, options) {
199
199
  }
200
200
  return messages;
201
201
  }
202
- async function boundedError(response, signal) {
202
+ async function boundedError(response, signal, telemetry) {
203
203
  if (response.body === null)
204
204
  return {};
205
205
  const reader = response.body.getReader();
@@ -223,6 +223,7 @@ async function boundedError(response, signal) {
223
223
  return {};
224
224
  if (done)
225
225
  break;
226
+ telemetry.activity(value.byteLength);
226
227
  total += value.byteLength;
227
228
  if (total > MAX_ERROR_BODY_BYTES) {
228
229
  await reader.cancel().catch(() => { });
@@ -276,8 +277,8 @@ function errorFacts(response, maxDelayMs) {
276
277
  : { requestId: ProviderRequestId(requestId) },
277
278
  };
278
279
  }
279
- async function httpFailure(response, maxDelayMs, signal) {
280
- const detail = await boundedError(response, signal);
280
+ async function httpFailure(response, maxDelayMs, signal, telemetry) {
281
+ const detail = await boundedError(response, signal, telemetry);
281
282
  const facts = errorFacts(response, maxDelayMs);
282
283
  const classification = `${detail.code ?? ''} ${detail.message ?? ''}`;
283
284
  if (response.status === 401 || response.status === 403) {
@@ -357,7 +358,6 @@ export class NativeCodexHttpTransport {
357
358
  maxRetries;
358
359
  initialDelayMs;
359
360
  maxDelayMs;
360
- maxBodyBytes;
361
361
  constructor(options) {
362
362
  this.options = options;
363
363
  this.fetchImpl = options.fetch ?? fetch;
@@ -367,7 +367,6 @@ export class NativeCodexHttpTransport {
367
367
  this.maxRetries = safeRetryCount(options.maxTransientRetries);
368
368
  this.initialDelayMs = safePositiveInteger(options.initialRetryDelayMs, DEFAULT_INITIAL_RETRY_DELAY_MS, 'initial retry delay');
369
369
  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
370
  }
372
371
  retryDelay(retry, providerDelay) {
373
372
  if (providerDelay !== undefined)
@@ -377,11 +376,13 @@ export class NativeCodexHttpTransport {
377
376
  const jitter = 0.9 + Math.max(0, Math.min(1, random)) * 0.2;
378
377
  return Math.max(1, Math.round(exponential * jitter));
379
378
  }
380
- async wait(retry, error, signal) {
379
+ async wait(retry, error, signal, telemetry) {
381
380
  const delay = this.retryDelay(retry, error.failure.providerRetryAfterMs);
381
+ telemetry.retry(delay);
382
382
  await (this.options.sleep ?? sleep)(delay, signal);
383
383
  }
384
- async waitForConnection(delayMs, signal) {
384
+ async waitForConnection(delayMs, signal, telemetry) {
385
+ telemetry.retry(delayMs, 'connection');
385
386
  this.options.warn?.(`native Codex network is unavailable; reconnecting in ${delayMs}ms`);
386
387
  await (this.options.sleep ?? sleep)(delayMs, signal);
387
388
  }
@@ -410,24 +411,36 @@ export class NativeCodexHttpTransport {
410
411
  sessionId: stablePromptCacheKey(sessionId),
411
412
  };
412
413
  let request;
413
- let body;
414
414
  try {
415
415
  request = codexRequestBody(wireOptions, messages, mode);
416
- body = JSON.stringify(request);
417
416
  }
418
417
  catch (error) {
419
418
  if (error instanceof LlmError)
420
419
  throw error;
421
420
  throw fixedFailure('native Codex request could not be encoded', 'INVALID_ARGS', { cause: error });
422
421
  }
423
- if (Buffer.byteLength(body) > this.maxBodyBytes) {
424
- throw fixedFailure('native Codex request exceeded the size limit', 'REQUEST_TOO_LARGE');
422
+ // WebSocket chooses a full request or an incremental suffix after preparation.
423
+ // Serialize only in the selected transport.
424
+ return { generation, mode, request, routingId, routingHint };
425
+ }
426
+ async *stream(generation, mode = {}, telemetry) {
427
+ if (telemetry !== undefined) {
428
+ yield* this.streamTracked(generation, mode, telemetry);
429
+ return;
425
430
  }
426
- return { generation, mode, request, body, routingId, routingHint };
431
+ yield* trackNetwork(this.options.telemetry ?? networkTelemetry, 'http', generation.signal, handle => this.streamTracked(generation, mode, handle));
427
432
  }
428
- async *stream(generation, mode = {}) {
433
+ async *streamTracked(generation, mode, telemetry) {
434
+ telemetry.state('preparing');
429
435
  const prepared = await this.prepare(generation, mode);
430
- const { body, routingId, routingHint } = prepared;
436
+ const { request, routingId, routingHint } = prepared;
437
+ let body;
438
+ try {
439
+ body = JSON.stringify(request);
440
+ }
441
+ catch (error) {
442
+ throw fixedFailure('native Codex request could not be encoded', 'INVALID_ARGS', { cause: error });
443
+ }
431
444
  let activeTurnState = mode.turnState;
432
445
  if (activeTurnState !== undefined
433
446
  && (activeTurnState.length === 0 || Buffer.byteLength(activeTurnState) > 4096
@@ -440,6 +453,7 @@ export class NativeCodexHttpTransport {
440
453
  let pinnedAccountId = mode.pinnedAccountId;
441
454
  while (true) {
442
455
  throwIfAborted(generation.signal);
456
+ telemetry.state('preparing');
443
457
  const watchdog = attemptWatchdog(generation.signal, this.requestTimeoutMs, this.idleTimeoutMs);
444
458
  let response;
445
459
  let credential;
@@ -458,6 +472,7 @@ export class NativeCodexHttpTransport {
458
472
  throw fixedFailure('native Codex Fast capability authority changed before request', 'FAST_CAPABILITY_UNAVAILABLE');
459
473
  }
460
474
  fetching = true;
475
+ telemetry.attempt('http');
461
476
  response = await this.fetchImpl(this.endpoint, {
462
477
  method: 'POST',
463
478
  redirect: 'error',
@@ -490,16 +505,18 @@ export class NativeCodexHttpTransport {
490
505
  : connection ?? mappedFailure(error, watchdog, generation.signal);
491
506
  watchdog.stop();
492
507
  if (connection !== undefined) {
493
- await this.waitForConnection(connectionRetryDelayMs, generation.signal);
508
+ await this.waitForConnection(connectionRetryDelayMs, generation.signal, telemetry);
494
509
  connectionRetryDelayMs = Math.min(connectionRetryDelayMs * 2, MAX_CONNECTION_RETRY_DELAY_MS);
495
510
  continue;
496
511
  }
497
512
  if (retryable(failure) && transientRetries < this.maxRetries) {
498
- await this.wait(transientRetries++, failure, generation.signal);
513
+ await this.wait(transientRetries++, failure, generation.signal, telemetry);
499
514
  continue;
500
515
  }
501
516
  throw failure;
502
517
  }
518
+ telemetry.activity();
519
+ telemetry.state('waiting');
503
520
  publishCodexRateLimits(credential.accountId, parseCodexRateLimitHeaders(response.headers), this.options.onRateLimits, this.options.warn);
504
521
  if (activeTurnState === undefined) {
505
522
  const headerTurnState = boundedCodexTurnState(response.headers.get('x-codex-turn-state'));
@@ -514,8 +531,10 @@ export class NativeCodexHttpTransport {
514
531
  const changed = await this.options.recoverCredential(credential, watchdog.signal);
515
532
  recovered = true;
516
533
  watchdog.stop();
517
- if (changed)
534
+ if (changed) {
535
+ telemetry.retry(0);
518
536
  continue;
537
+ }
519
538
  throw fixedFailure('native Codex rejected the configured credential', 'AUTH', errorFacts(response, this.maxDelayMs));
520
539
  }
521
540
  catch (error) {
@@ -523,7 +542,7 @@ export class NativeCodexHttpTransport {
523
542
  const failure = connection ?? mappedFailure(error, watchdog, generation.signal);
524
543
  watchdog.stop();
525
544
  if (connection !== undefined) {
526
- await this.waitForConnection(connectionRetryDelayMs, generation.signal);
545
+ await this.waitForConnection(connectionRetryDelayMs, generation.signal, telemetry);
527
546
  connectionRetryDelayMs = Math.min(connectionRetryDelayMs * 2, MAX_CONNECTION_RETRY_DELAY_MS);
528
547
  continue;
529
548
  }
@@ -531,13 +550,13 @@ export class NativeCodexHttpTransport {
531
550
  }
532
551
  }
533
552
  if (!response.ok) {
534
- let failure = await httpFailure(response, this.maxDelayMs, watchdog.signal);
553
+ let failure = await httpFailure(response, this.maxDelayMs, watchdog.signal, telemetry);
535
554
  if (generation.signal?.aborted || watchdog.timedOut()) {
536
555
  failure = mappedFailure(failure, watchdog, generation.signal);
537
556
  }
538
557
  watchdog.stop();
539
558
  if (retryable(failure) && transientRetries < this.maxRetries) {
540
- await this.wait(transientRetries++, failure, generation.signal);
559
+ await this.wait(transientRetries++, failure, generation.signal, telemetry);
541
560
  continue;
542
561
  }
543
562
  throw failure;
@@ -553,9 +572,7 @@ export class NativeCodexHttpTransport {
553
572
  for await (const chunk of streamResponses(response.body, {
554
573
  signal: watchdog.signal,
555
574
  onActivity: watchdog.pulse,
556
- ...this.options.maxSseEventBytes === undefined
557
- ? {}
558
- : { maxEventBytes: this.options.maxSseEventBytes },
575
+ onBytes: bytes => telemetry.activity(bytes),
559
576
  onMalformedEvent: () => {
560
577
  this.options.warn?.('native Codex ignored a malformed SSE event');
561
578
  },
@@ -578,6 +595,7 @@ export class NativeCodexHttpTransport {
578
595
  },
579
596
  })) {
580
597
  emitted = true;
598
+ observeNetworkOutput(telemetry, chunk);
581
599
  if (chunk.type === 'finish'
582
600
  && ['stop', 'tool-calls', 'max-tokens'].includes(chunk.reason.kind))
583
601
  completed = true;
@@ -596,7 +614,7 @@ export class NativeCodexHttpTransport {
596
614
  catch (error) {
597
615
  const failure = mappedFailure(error, watchdog, generation.signal);
598
616
  if (!emitted && retryable(failure) && transientRetries < this.maxRetries) {
599
- await this.wait(transientRetries++, failure, generation.signal);
617
+ await this.wait(transientRetries++, failure, generation.signal, telemetry);
600
618
  continue;
601
619
  }
602
620
  if (emitted && retryable(failure))
@@ -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
  }
@@ -10,6 +10,8 @@ export interface NativeCodexWebSocket {
10
10
  readonly responseHeaders: Readonly<Record<string, string>>;
11
11
  send(text: string, signal?: AbortSignal): Promise<void>;
12
12
  receive(signal?: AbortSignal): Promise<NativeCodexWebSocketFrame>;
13
+ /** Observe incoming payload bytes while a logical inference owns the socket. */
14
+ observeActivity?(observer: (bytes: number) => void): () => void;
13
15
  close(): void;
14
16
  }
15
17
  export interface NativeCodexWebSocketConnectOptions {
@@ -17,7 +19,6 @@ export interface NativeCodexWebSocketConnectOptions {
17
19
  headers: Record<string, string>;
18
20
  signal?: AbortSignal;
19
21
  connectTimeoutMs?: number;
20
- maxFrameBytes?: number;
21
22
  }
22
23
  export interface NativeCodexWebSocketFactory {
23
24
  connect(options: NativeCodexWebSocketConnectOptions): Promise<NativeCodexWebSocket>;
@@ -1,4 +1,4 @@
1
- /** Bounded Node WebSocket client seam with injectable deterministic factories. */
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,29 +52,29 @@ 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, maxFrameBytes) {
58
+ activityObserver;
59
+ observeActivity(observer) {
60
+ this.activityObserver = observer;
61
+ return () => { if (this.activityObserver === observer)
62
+ this.activityObserver = undefined; };
63
+ }
64
+ constructor(socket, responseHeaders) {
63
65
  this.socket = socket;
64
66
  this.responseHeaders = responseHeaders;
65
- this.maxFrameBytes = maxFrameBytes;
67
+ socket.on('ping', data => { this.activityObserver?.(data.byteLength); });
68
+ socket.on('pong', data => { this.activityObserver?.(data.byteLength); });
66
69
  socket.on('message', (data, isBinary) => {
70
+ this.activityObserver?.(Array.isArray(data) ? data.reduce((sum, part) => sum + part.byteLength, 0) : data.byteLength);
67
71
  if (isBinary) {
68
72
  this.fail(failure('native Codex WebSocket returned a binary frame', 'WS_PROTOCOL_ERROR'));
69
73
  return;
70
74
  }
71
- const bytes = Array.isArray(data)
72
- ? data.reduce((total, chunk) => total + chunk.byteLength, 0) : data.byteLength;
73
- if (bytes > maxFrameBytes) {
74
- this.fail(failure('native Codex WebSocket frame exceeded the size limit', 'WS_FRAME_TOO_LARGE'));
75
- return;
76
- }
77
- const buffer = Array.isArray(data) ? Buffer.concat(data, bytes)
75
+ const buffer = Array.isArray(data) ? Buffer.concat(data)
78
76
  : Buffer.isBuffer(data) ? data : Buffer.from(data);
79
- this.push({ type: 'text', text: buffer.toString('utf8') }, bytes);
77
+ this.push({ type: 'text', text: buffer.toString('utf8') });
80
78
  });
81
79
  socket.on('close', (code, reason) => {
82
80
  this.ended = true;
@@ -86,7 +84,7 @@ class NodeNativeCodexWebSocket {
86
84
  this.fail(failure('native Codex WebSocket transport failed', 'WS_RETRYABLE', error));
87
85
  });
88
86
  }
89
- push(value, bytes = 0) {
87
+ push(value) {
90
88
  const waiter = this.waiters.shift();
91
89
  if (waiter !== undefined) {
92
90
  if (value instanceof LlmError)
@@ -95,12 +93,7 @@ class NodeNativeCodexWebSocket {
95
93
  waiter.resolve(value);
96
94
  return;
97
95
  }
98
- if (this.queuedBytes + bytes > MAX_QUEUED_BYTES) {
99
- this.fail(failure('native Codex WebSocket queued too much response data', 'WS_RESPONSE_TOO_LARGE'));
100
- return;
101
- }
102
- this.queue.push({ value, bytes });
103
- this.queuedBytes += bytes;
96
+ this.queue.push(value);
104
97
  }
105
98
  fail(error) {
106
99
  if (!this.ended)
@@ -110,9 +103,8 @@ class NodeNativeCodexWebSocket {
110
103
  for (const waiter of waiters)
111
104
  waiter.reject(error);
112
105
  this.queue.length = 0;
113
- this.queuedBytes = 0;
114
106
  if (waiters.length === 0)
115
- this.queue.push({ value: error, bytes: 0 });
107
+ this.queue.push(error);
116
108
  }
117
109
  async send(text, signal) {
118
110
  if (signal?.aborted)
@@ -140,10 +132,9 @@ class NodeNativeCodexWebSocket {
140
132
  throw abortFailure(signal);
141
133
  const queued = this.queue.shift();
142
134
  if (queued !== undefined) {
143
- this.queuedBytes -= queued.bytes;
144
- if (queued.value instanceof LlmError)
145
- throw queued.value;
146
- return queued.value;
135
+ if (queued instanceof LlmError)
136
+ throw queued;
137
+ return queued;
147
138
  }
148
139
  return new Promise((resolve, reject) => {
149
140
  let waiter;
@@ -173,7 +164,6 @@ class NodeNativeCodexWebSocket {
173
164
  export class NodeNativeCodexWebSocketFactory {
174
165
  async connect(options) {
175
166
  const timeout = positive(options.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS, 'connect timeout');
176
- const maximum = positive(options.maxFrameBytes, DEFAULT_MAX_FRAME_BYTES, 'frame limit');
177
167
  if (options.signal?.aborted)
178
168
  throw abortFailure(options.signal);
179
169
  return new Promise((resolve, reject) => {
@@ -184,7 +174,8 @@ export class NodeNativeCodexWebSocketFactory {
184
174
  headers: options.headers,
185
175
  ...(agent === undefined ? {} : { agent }),
186
176
  handshakeTimeout: timeout,
187
- maxPayload: maximum,
177
+ // ws uses zero to disable its default message-size ceiling.
178
+ maxPayload: 0,
188
179
  perMessageDeflate: true,
189
180
  });
190
181
  const abort = () => {
@@ -215,7 +206,7 @@ export class NodeNativeCodexWebSocketFactory {
215
206
  });
216
207
  socket.on('open', () => {
217
208
  options.signal?.removeEventListener('abort', abort);
218
- resolve(new NodeNativeCodexWebSocket(socket, responseHeaders, maximum));
209
+ resolve(new NodeNativeCodexWebSocket(socket, responseHeaders));
219
210
  });
220
211
  socket.on('error', (error) => {
221
212
  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;
@@ -42,5 +40,6 @@ export declare class NativeCodexWebSocketTransport implements NativeCodexTranspo
42
40
  private exchange;
43
41
  private attempt;
44
42
  stream(generation: GenerateOptions, mode?: NativeCodexTransportMode): AsyncIterable<StreamChunk>;
43
+ private streamTracked;
45
44
  dispose(): void;
46
45
  }