@pure01fx/dsh-openai-codex-auth 0.10.1 → 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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.11.0
6
+
7
+ - Removes plugin payload byte ceilings for native and cloud requests, SSE events, WebSocket messages and queues, response accumulation, encrypted replay state, model catalogs, and image data; removes the corresponding transport options, including `maxRequestBodyBytes`. WebSocket disables the underlying `ws` message-size ceiling explicitly.
8
+ - Keeps WebSocket incremental request preparation separate from HTTP serialization, preserving image history across incremental reuse, reconnects, and HTTP fallback.
9
+ - Preserves complete search output and result arrays, processes all inline result images, forwards selected human search context in full, and removes search batch/domain count ceilings and image-tool prompt length ceilings. Protocol validation, service errors, cancellation, retry/session lifecycle controls, and DSH attachment/file policies still apply.
10
+
5
11
  ## 0.10.1
6
12
 
7
13
  - Targets DSH 0.1.5-rc.1 with exact SDK peers and runtime helpers; migrates Responses tool calls to ToolCallId.
package/README.md CHANGED
@@ -17,10 +17,12 @@
17
17
 
18
18
  ## 快速开始
19
19
 
20
- 版本 `0.10.1` 明确适配 DeepSeek Harness `0.1.5-rc.1`,SDK peers/runtime helpers 精确锁定该版本;不声明兼容旧 SDK。旧 DSH `0.1.1-rc.2` profile 应保留原插件 `0.10.0`,升级时在独立 profile 安装新版插件并保留原 profile/锁文件以便回滚。无需迁移账号文件或浏览器隐藏列表;`dsh.engines` 仅作兼容性说明,不当作宿主硬门禁。
20
+ 版本 `0.11.0` 明确适配 DeepSeek Harness `0.1.5-rc.1`,SDK peers/runtime helpers 精确锁定该版本;不声明兼容旧 SDK。旧 DSH `0.1.1-rc.2` profile 应保留原插件 `0.10.0`,升级时在独立 profile 安装新版插件并保留原 profile/锁文件以便回滚。无需迁移账号文件或浏览器隐藏列表;`dsh.engines` 仅作兼容性说明,不当作宿主硬门禁。
21
21
 
22
22
  本版本通过真实目标 LLM/tools 服务与本地 HTTP/SSE/WebSocket 回放、错误/取消回归;模型隐藏覆盖真实目录/catalog/store 的投影更新、重连和重新加载。验证命令为 `pnpm typecheck && pnpm build && pnpm test`;当前版本未执行线上 OAuth/付费模型验收。
23
23
 
24
+ `0.11.0` 移除了插件额外设置的请求、响应、SSE/WebSocket、图片和重放状态大小上限,以及相应的 transport 选项(含 `maxRequestBodyBytes`);旧配置中的该字段应删除。搜索结果完整保留,WebSocket 继续优先复用增量上下文。服务端限制、协议校验及 DSH 文件/附件策略仍生效。
25
+
24
26
  将插件安装到 DSH 的 `web` profile:
25
27
 
26
28
  ```sh
package/lib/catalog.js CHANGED
@@ -8,7 +8,6 @@ export const CODEX_MODELS_URL = 'https://chatgpt.com/backend-api/codex/models';
8
8
  export const CODEX_CATALOG_CACHE_TTL_MS = 5 * 60_000;
9
9
  const CODEX_CATALOG_MAX_STALE_MS = 7 * 24 * 60 * 60_000;
10
10
  const CODEX_CATALOG_TIMEOUT_MS = 5_000;
11
- const MAX_CATALOG_BODY_LENGTH = 2 * 1024 * 1024;
12
11
  const ORIGINATOR = 'dsh';
13
12
  class CatalogFetchError extends LlmError {
14
13
  allowsStale;
@@ -92,7 +91,7 @@ function parseServiceTiers(value) {
92
91
  }
93
92
  return tiers;
94
93
  }
95
- async function boundedResponseText(response) {
94
+ async function responseText(response) {
96
95
  if (response.body === null)
97
96
  return '';
98
97
  const reader = response.body.getReader();
@@ -104,10 +103,6 @@ async function boundedResponseText(response) {
104
103
  if (done)
105
104
  break;
106
105
  total += value.byteLength;
107
- if (total > MAX_CATALOG_BODY_LENGTH) {
108
- await reader.cancel();
109
- throw new CatalogFetchError('native Codex catalog response exceeded the size limit', 'CATALOG_INVALID_RESPONSE', true);
110
- }
111
106
  chunks.push(value);
112
107
  }
113
108
  }
@@ -366,7 +361,7 @@ export class NativeCodexCatalog {
366
361
  }
367
362
  let body;
368
363
  try {
369
- body = await boundedResponseText(response);
364
+ body = await responseText(response);
370
365
  }
371
366
  catch (error) {
372
367
  if (error instanceof CatalogFetchError)
@@ -13,7 +13,7 @@ export declare function chooseCloudModel(params: {
13
13
  export declare function visibleCloudImages(messages: readonly Message[]): ImageAttachmentRef[];
14
14
  /** Last two human texts plus intervening visible assistant text. UTF-8 bytes conservatively
15
15
  * upper-bound byte-level tokenizer tokens; a single 1000-byte assistant budget avoids
16
- * relying on an unavailable model tokenizer. User text has a separate 64 KiB cap. */
16
+ * relying on an unavailable model tokenizer. The selected human texts are preserved in full. */
17
17
  export declare function recentSearchInput(messages: readonly Message[]): {
18
18
  role: 'user' | 'assistant';
19
19
  content: string;
@@ -61,14 +61,13 @@ function boundedText(text, bytes, tail = false) {
61
61
  }
62
62
  /** Last two human texts plus intervening visible assistant text. UTF-8 bytes conservatively
63
63
  * upper-bound byte-level tokenizer tokens; a single 1000-byte assistant budget avoids
64
- * relying on an unavailable model tokenizer. User text has a separate 64 KiB cap. */
64
+ * relying on an unavailable model tokenizer. The selected human texts are preserved in full. */
65
65
  export function recentSearchInput(messages) {
66
66
  const users = messages.map((message, i) => message.role === 'user' && message.source.kind === 'user' && message.content.some(block => block.type === 'text') ? i : -1).filter(i => i >= 0).slice(-2);
67
67
  if (!users.length)
68
68
  return [];
69
69
  const selected = [];
70
70
  let assistantBytes = 1000;
71
- let userBytes = 64 * 1024;
72
71
  // Budget newest text first, then restore chronology.
73
72
  for (let i = messages.length - 1; i >= users[0]; i--) {
74
73
  const message = messages[i];
@@ -76,12 +75,10 @@ export function recentSearchInput(messages) {
76
75
  if (!user && (message.role !== 'assistant' || message.source.kind !== 'model'))
77
76
  continue;
78
77
  const text = message.content.filter(block => block.type === 'text').map(block => block.text).join('\n');
79
- const content = boundedText(text, user ? userBytes : assistantBytes, true);
78
+ const content = user ? text : boundedText(text, assistantBytes, true);
80
79
  if (!content)
81
80
  continue;
82
- if (user)
83
- userBytes -= Buffer.byteLength(content);
84
- else
81
+ if (!user)
85
82
  assistantBytes -= Buffer.byteLength(content);
86
83
  selected.push({ role: user ? 'user' : 'assistant', content });
87
84
  }
@@ -5,15 +5,12 @@ export interface NativeCodexCloudOptions {
5
5
  fetch?: typeof fetch;
6
6
  endpoint?: string;
7
7
  requestTimeoutMs?: number;
8
- maxRequestBodyBytes?: number;
9
- maxResponseBytes?: number;
10
8
  }
11
9
  export interface NativeCodexCloudPostOptions {
12
10
  credential: NativeCodexCredential;
13
11
  signal: AbortSignal;
14
12
  headers?: Record<string, string>;
15
13
  timeoutMs?: number;
16
- maxResponseBytes?: number;
17
14
  }
18
15
  export type NativeCodexCloudPath = 'alpha/search' | 'images/generations' | 'images/edits';
19
16
  export declare class NativeCodexCloudClient {
package/lib/cloud-http.js CHANGED
@@ -1,4 +1,4 @@
1
- /** Bounded, nonstreaming Codex cloud requests. Never retry ambiguous POST failures. */
1
+ /** Nonstreaming Codex cloud requests. Never retry ambiguous POST failures. */
2
2
  import { attributionHeaders, LlmError } from '@deepseek-ai/dsh-llm';
3
3
  import { nativeCodexEndpoint } from './endpoint.js';
4
4
  const fail = (message, code = 'CODEX_CLOUD_FAILED') => new LlmError(message, code);
@@ -54,8 +54,6 @@ export class NativeCodexCloudClient {
54
54
  if (!['alpha/search', 'images/generations', 'images/edits'].includes(path))
55
55
  throw fail('Invalid Codex cloud path', 'INVALID_ARGS');
56
56
  const timeoutMs = positive(options.timeoutMs ?? this.options.requestTimeoutMs, 120_000);
57
- const maxBytes = positive(options.maxResponseBytes ?? this.options.maxResponseBytes, 48 * 1024 * 1024);
58
- const requestLimit = positive(this.options.maxRequestBodyBytes, 48 * 1024 * 1024);
59
57
  let encoded;
60
58
  try {
61
59
  encoded = JSON.stringify(body);
@@ -63,8 +61,8 @@ export class NativeCodexCloudClient {
63
61
  catch {
64
62
  throw fail('Invalid cloud JSON request', 'INVALID_ARGS');
65
63
  }
66
- if (typeof encoded !== 'string' || Buffer.byteLength(encoded) > requestLimit)
67
- throw fail('Cloud request exceeds byte limit', 'INVALID_ARGS');
64
+ if (typeof encoded !== 'string')
65
+ throw fail('Invalid cloud JSON request', 'INVALID_ARGS');
68
66
  let credential = credentialCopy(options.credential);
69
67
  const accountId = credential.accountId;
70
68
  const controller = new AbortController();
@@ -118,11 +116,7 @@ export class NativeCodexCloudClient {
118
116
  if (!reader)
119
117
  throw fail('Codex cloud response is empty');
120
118
  const chunks = [];
121
- let size = 0;
122
119
  try {
123
- const declared = response.headers.get('content-length');
124
- if (declared !== null && Number(declared) > maxBytes)
125
- throw fail('Codex cloud response exceeds byte limit');
126
120
  while (true) {
127
121
  let part;
128
122
  try {
@@ -133,9 +127,6 @@ export class NativeCodexCloudClient {
133
127
  }
134
128
  if (part.done)
135
129
  break;
136
- size += part.value.byteLength;
137
- if (size > maxBytes)
138
- throw fail('Codex cloud response exceeds byte limit');
139
130
  chunks.push(part.value);
140
131
  }
141
132
  }
@@ -1,6 +1,5 @@
1
- /** Pure image endpoint validation and bounded decoding. */
1
+ /** Pure image endpoint validation and decoding. */
2
2
  import type { ImageMediaType } from '@deepseek-ai/dsh-attachment';
3
- export declare const MAX_IMAGE_BYTES: number;
4
3
  export type ImageSource = {
5
4
  attachment_id: string;
6
5
  } | {
@@ -36,7 +35,7 @@ export declare function buildImageEditRequest(value: unknown, images: readonly {
36
35
  size: string;
37
36
  n?: number;
38
37
  };
39
- export declare function decodeImageBase64(value: unknown, maxBytes?: number): Uint8Array;
38
+ export declare function decodeImageBase64(value: unknown): Uint8Array;
40
39
  export declare function imageMediaType(data: Uint8Array): ImageMediaType;
41
40
  export interface ParsedCloudImage {
42
41
  data: Uint8Array;
@@ -1,12 +1,11 @@
1
- export const MAX_IMAGE_BYTES = 32 * 1024 * 1024;
2
1
  function object(value, label) {
3
2
  if (!value || typeof value !== 'object' || Array.isArray(value))
4
3
  throw new Error(label + ' must be an object');
5
4
  return value;
6
5
  }
7
- function text(value, label, max = 32_000) {
8
- if (typeof value !== 'string' || !value.trim() || value.length > max)
9
- throw new Error(label + ' must be a nonempty bounded string');
6
+ function text(value, label, max) {
7
+ if (typeof value !== 'string' || !value.trim() || (max !== undefined && value.length > max))
8
+ throw new Error(label + ' must be a valid nonempty string');
10
9
  return value;
11
10
  }
12
11
  function count(value, label, max) {
@@ -68,16 +67,14 @@ export function buildImageEditRequest(value, images, defaultModel) {
68
67
  }
69
68
  return { ...validateImageGenerateArgs(value, defaultModel), images: images.map(({ image_url }) => ({ image_url })) };
70
69
  }
71
- export function decodeImageBase64(value, maxBytes = MAX_IMAGE_BYTES) {
72
- const cap = Math.min(maxBytes, MAX_IMAGE_BYTES);
73
- if (typeof value !== 'string' || !value.length || value.length > 4 * Math.ceil(cap / 3))
74
- throw new Error('Image base64 is empty or exceeds byte limit');
75
- // Check length before scanning or allocating the decoded payload.
70
+ export function decodeImageBase64(value) {
71
+ if (typeof value !== 'string' || !value.length)
72
+ throw new Error('Image base64 is empty');
76
73
  if (value.length % 4 !== 0 || /[^A-Za-z0-9+/=]/.test(value) || !/^[A-Za-z0-9+/]*={0,2}$/.test(value))
77
74
  throw new Error('Invalid image base64');
78
75
  const data = Buffer.from(value, 'base64');
79
- if (!data.length || data.length > cap || data.toString('base64') !== value)
80
- throw new Error('Invalid image base64 or byte limit exceeded');
76
+ if (!data.length || data.toString('base64') !== value)
77
+ throw new Error('Invalid image base64');
81
78
  return data;
82
79
  }
83
80
  export function imageMediaType(data) {
@@ -108,11 +105,9 @@ export function parseImageResponse(value, requestId) {
108
105
  metadata.size = text(response.size, 'response size', 64);
109
106
  if (requestId !== undefined)
110
107
  metadata.request_id = text(requestId, 'request_id', 512);
111
- let total = 0;
112
108
  const images = response.data.map((entry) => {
113
109
  const item = object(entry, 'Image response data');
114
- const data = decodeImageBase64(item.b64_json, MAX_IMAGE_BYTES - total);
115
- total += data.length;
110
+ const data = decodeImageBase64(item.b64_json);
116
111
  const generation_id = item.generation_id == null ? undefined : text(item.generation_id, 'generation_id', 512);
117
112
  // Unknown fields may contain payloads or private service state; never copy them to history.
118
113
  return { data, mediaType: imageMediaType(data), metadata: generation_id === undefined ? {} : { generation_id },
@@ -2,7 +2,7 @@
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { posix, win32 } from 'node:path';
4
4
  import { imageSize } from 'image-size';
5
- import { MAX_IMAGE_BYTES, imageMediaType, validateImageSelection } from './cloud-images.js';
5
+ import { imageMediaType, validateImageSelection } from './cloud-images.js';
6
6
  // Check both process-world grammars without resolving against the plugin host OS.
7
7
  function absoluteWorldPath(path) {
8
8
  return !path.includes('\0') && (posix.isAbsolute(path) || (win32.isAbsolute(path) && /^[A-Za-z]:|^\\\\[^\\]+\\[^\\]+/.test(path)));
@@ -25,7 +25,6 @@ export async function resolveImageSources(value, deps) {
25
25
  if ('attachment_id' in source && !refs.has(source.attachment_id))
26
26
  throw new Error('Image attachment is not visible in this session: ' + source.attachment_id);
27
27
  const result = [];
28
- let total = 0;
29
28
  for (const source of sources) {
30
29
  deps.signal?.throwIfAborted();
31
30
  let data;
@@ -33,8 +32,8 @@ export async function resolveImageSources(value, deps) {
33
32
  let path;
34
33
  if ('attachment_id' in source) {
35
34
  attachment = refs.get(source.attachment_id);
36
- if (!Number.isSafeInteger(attachment.bytes) || attachment.bytes <= 0 || attachment.bytes > MAX_IMAGE_BYTES - total)
37
- throw new Error('Image inputs exceed byte limit');
35
+ if (!Number.isSafeInteger(attachment.bytes) || attachment.bytes <= 0)
36
+ throw new Error('Invalid image byte count');
38
37
  const stored = await deps.attachments.readImage(attachment, deps.signal);
39
38
  data = stored.data;
40
39
  if (stored.ref.attachmentId !== attachment.attachmentId || stored.ref.bytes !== attachment.bytes ||
@@ -46,12 +45,12 @@ export async function resolveImageSources(value, deps) {
46
45
  if (!absoluteWorldPath(source.path))
47
46
  throw new Error('Image path must be absolute');
48
47
  const target = await deps.fs.resolve(source.path, { cwd: deps.workspace, ...(deps.signal ? { signal: deps.signal } : {}) });
49
- data = await deps.fs.readBytes(target, deps.signal, MAX_IMAGE_BYTES - total);
48
+ // The filesystem API requires a numeric bound; impose no plugin-specific byte ceiling.
49
+ data = await deps.fs.readBytes(target, deps.signal, Number.MAX_SAFE_INTEGER);
50
50
  path = deps.fs.processPath(target);
51
51
  }
52
- total += data.length;
53
- if (!data.length || total > MAX_IMAGE_BYTES)
54
- throw new Error('Image inputs are empty or exceed byte limit');
52
+ if (!data.length)
53
+ throw new Error('Image inputs are empty');
55
54
  const mediaType = imageMediaType(data);
56
55
  await deps.attachments.validateImage({ data, mediaType });
57
56
  const dimensions = encodedDimensions(data);
@@ -74,13 +73,11 @@ export async function saveImageOutputs(response, deps) {
74
73
  throw new Error('Caller workspace must be absolute');
75
74
  if (!deps.policy || !deps.shell.sandboxMode)
76
75
  throw new Error('Original image writes require a policy-enforcing DSH shell');
77
- let total = 0;
78
76
  const dimensions = [];
79
77
  for (const image of response.images) {
80
78
  deps.signal?.throwIfAborted();
81
- total += image.data.length;
82
- if (!image.data.length || total > MAX_IMAGE_BYTES)
83
- throw new Error('Image outputs exceed byte limit');
79
+ if (!image.data.length)
80
+ throw new Error('Image outputs are empty');
84
81
  if (imageMediaType(image.data) !== image.mediaType)
85
82
  throw new Error('Output media type does not match bytes');
86
83
  await deps.attachments.validateImage({ data: image.data, mediaType: image.mediaType });
@@ -22,7 +22,7 @@ export declare function buildCloudSearchRequest(args: CloudSearchArgs, context:
22
22
  model: string;
23
23
  input?: unknown;
24
24
  }): Record<string, unknown>;
25
- /** Keep whole opaque result entries and identify any omitted data explicitly. */
25
+ /** Preserve complete output and opaque result entries. */
26
26
  export declare function parseCloudSearchResponse(body: unknown): {
27
27
  output: string;
28
28
  results?: unknown[];
@@ -1,9 +1,6 @@
1
1
  /** Wire contract: Codex b348fc26674189f758d5941cdab3f78f258b2aa7, codex-api/src/search.rs. */
2
2
  import { createHash } from 'node:crypto';
3
3
  import { LlmError } from '@deepseek-ai/dsh-llm';
4
- const MAX_REQUEST_BYTES = 256 * 1024;
5
- const MAX_OUTPUT_BYTES = 128 * 1024;
6
- const MAX_RESULTS_BYTES = 256 * 1024;
7
4
  const invalid = () => new LlmError('Invalid codex_web arguments; check command fields and limits', 'INVALID_ARGS');
8
5
  function object(value) {
9
6
  if (!value || typeof value !== 'object' || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype)
@@ -11,7 +8,7 @@ function object(value) {
11
8
  return value;
12
9
  }
13
10
  function text(value, empty = false) {
14
- if (typeof value !== 'string' || (!empty && !value.trim()) || value.length > MAX_REQUEST_BYTES)
11
+ if (typeof value !== 'string' || (!empty && !value.trim()))
15
12
  throw invalid();
16
13
  return value;
17
14
  }
@@ -22,7 +19,7 @@ const uint = value => {
22
19
  return value;
23
20
  };
24
21
  const strings = value => {
25
- if (!Array.isArray(value) || value.length > 100)
22
+ if (!Array.isArray(value))
26
23
  throw invalid();
27
24
  return value.map(item => text(item));
28
25
  };
@@ -67,10 +64,9 @@ const operations = {
67
64
  return s;
68
65
  } }, required: ['utc_offset'] },
69
66
  };
70
- function boundedRequest(value) {
67
+ function validateRequestJson(value) {
71
68
  try {
72
- if (Buffer.byteLength(JSON.stringify(value)) > MAX_REQUEST_BYTES)
73
- throw invalid();
69
+ JSON.stringify(value);
74
70
  }
75
71
  catch {
76
72
  throw invalid();
@@ -88,13 +84,13 @@ export function parseCloudSearchArgs(value) {
88
84
  result[key] = choice('short', 'medium', 'long')(items);
89
85
  continue;
90
86
  }
91
- if (!Object.hasOwn(operations, key) || !Array.isArray(items) || items.length === 0 || items.length > 100)
87
+ if (!Object.hasOwn(operations, key) || !Array.isArray(items) || items.length === 0)
92
88
  throw invalid();
93
89
  const operation = operations[key];
94
90
  count += items.length;
95
91
  result[key] = items.map(item => fields(item, operation.rules, operation.required));
96
92
  }
97
- if (count === 0 || count > 100)
93
+ if (count === 0)
98
94
  throw invalid();
99
95
  return result;
100
96
  },
@@ -104,7 +100,7 @@ export function parseCloudSearchArgs(value) {
104
100
  filters: value => fields(value, { allowed_domains: strings, blocked_domains: strings }),
105
101
  image_settings: value => fields(value, { max_results: uint, caption: bool }),
106
102
  }, ['commands']);
107
- boundedRequest(parsed);
103
+ validateRequestJson(parsed);
108
104
  return parsed;
109
105
  }
110
106
  /** The caller selects the model and visible context using the same request credential. */
@@ -122,10 +118,10 @@ export function buildCloudSearchRequest(args, context) {
122
118
  if (input !== undefined && typeof input !== 'string' && !Array.isArray(input))
123
119
  throw invalid();
124
120
  const result = { id, model: text(context.model), commands: parsed.commands, settings, ...(input === undefined ? {} : { input }) };
125
- boundedRequest(result);
121
+ validateRequestJson(result);
126
122
  return result;
127
123
  }
128
- /** Keep whole opaque result entries and identify any omitted data explicitly. */
124
+ /** Preserve complete output and opaque result entries. */
129
125
  export function parseCloudSearchResponse(body) {
130
126
  let data;
131
127
  try {
@@ -136,17 +132,10 @@ export function parseCloudSearchResponse(body) {
136
132
  }
137
133
  if (typeof data.output !== 'string' || (data.results != null && !Array.isArray(data.results)))
138
134
  throw new LlmError('Invalid Codex search response', 'CODEX_CLOUD_FAILED');
139
- let output = data.output;
140
- let truncated = false;
141
- if (Buffer.byteLength(output) > MAX_OUTPUT_BYTES) {
142
- // Truncate only at complete UTF-8 boundaries; the notice warns that references may be omitted.
143
- output = new TextDecoder().decode(Buffer.from(output).subarray(0, MAX_OUTPUT_BYTES)).replace(/\uFFFD$/, '');
144
- truncated = true;
145
- }
135
+ const output = data.output;
146
136
  let results;
147
137
  if (Array.isArray(data.results)) {
148
138
  results = [];
149
- let bytes = 2;
150
139
  for (const item of data.results) {
151
140
  let serialized;
152
141
  try {
@@ -157,16 +146,8 @@ export function parseCloudSearchResponse(body) {
157
146
  }
158
147
  if (serialized === undefined)
159
148
  throw new LlmError('Invalid Codex search results', 'CODEX_CLOUD_FAILED');
160
- const size = Buffer.byteLength(serialized) + 1;
161
- if (results.length >= 100 || bytes + size > MAX_RESULTS_BYTES) {
162
- truncated = true;
163
- continue;
164
- }
165
149
  results.push(JSON.parse(serialized));
166
- bytes += size;
167
150
  }
168
151
  }
169
- if (truncated)
170
- output += '\n[Search response truncated; some content or references were omitted. Narrow the request to retrieve them.]';
171
- return { output, ...(results === undefined ? {} : { results }), truncated };
152
+ return { output, ...(results === undefined ? {} : { results }), truncated: false };
172
153
  }
@@ -10,7 +10,7 @@ const imageRefs = { type: 'array', minItems: 1, maxItems: 5, description: 'Image
10
10
  ],
11
11
  } };
12
12
  const common = {
13
- prompt: { type: 'string', description: 'Describe the image to generate or the edits to perform.', minLength: 1, maxLength: 32000 },
13
+ prompt: { type: 'string', description: 'Describe the image to generate or the edits to perform.', minLength: 1 },
14
14
  model: { type: 'string', description: 'Optional Codex image model; defaults to configured imageModel or gpt-image-2.' },
15
15
  quality: { type: 'string', enum: ['auto', 'low', 'medium', 'high'] },
16
16
  background: { type: 'string', enum: ['auto', 'transparent', 'opaque'] },
@@ -105,7 +105,7 @@ export function registerCodexImageTools(ctx, deps) {
105
105
  name: 'codex_image_inspect',
106
106
  description: 'Ask a Codex vision model to analyze images, OCR, screenshots or charts. This makes one additional cloud inference call, without recursive tools. Use explicit model or configured visionModel for non-Codex callers. Reports effective detail and actual dimensions.',
107
107
  parameters: { type: 'object', additionalProperties: false, required: ['prompt', 'images'], properties: {
108
- prompt: { type: 'string', minLength: 1, maxLength: 32000 }, images: imageRefs,
108
+ prompt: { type: 'string', minLength: 1 }, images: imageRefs,
109
109
  model: { type: 'string' }, detail: { type: 'string', enum: ['auto', 'low', 'high', 'original'] }, reasoning_effort: { type: 'string' },
110
110
  } },
111
111
  output, timeoutMs: 120_000, isConcurrencySafe: () => true,
@@ -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);
@@ -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;
@@ -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
- if (Buffer.byteLength(body) > this.maxBodyBytes) {
424
- throw fixedFailure('native Codex request exceeded the size limit', 'REQUEST_TOO_LARGE');
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 { body, routingId, routingHint } = prepared;
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
- /** 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,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, maxFrameBytes) {
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
- 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)
66
+ const buffer = Array.isArray(data) ? Buffer.concat(data)
78
67
  : Buffer.isBuffer(data) ? data : Buffer.from(data);
79
- this.push({ type: 'text', text: buffer.toString('utf8') }, bytes);
68
+ this.push({ type: 'text', text: buffer.toString('utf8') });
80
69
  });
81
70
  socket.on('close', (code, reason) => {
82
71
  this.ended = true;
@@ -86,7 +75,7 @@ class NodeNativeCodexWebSocket {
86
75
  this.fail(failure('native Codex WebSocket transport failed', 'WS_RETRYABLE', error));
87
76
  });
88
77
  }
89
- push(value, bytes = 0) {
78
+ push(value) {
90
79
  const waiter = this.waiters.shift();
91
80
  if (waiter !== undefined) {
92
81
  if (value instanceof LlmError)
@@ -95,12 +84,7 @@ class NodeNativeCodexWebSocket {
95
84
  waiter.resolve(value);
96
85
  return;
97
86
  }
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;
87
+ this.queue.push(value);
104
88
  }
105
89
  fail(error) {
106
90
  if (!this.ended)
@@ -110,9 +94,8 @@ class NodeNativeCodexWebSocket {
110
94
  for (const waiter of waiters)
111
95
  waiter.reject(error);
112
96
  this.queue.length = 0;
113
- this.queuedBytes = 0;
114
97
  if (waiters.length === 0)
115
- this.queue.push({ value: error, bytes: 0 });
98
+ this.queue.push(error);
116
99
  }
117
100
  async send(text, signal) {
118
101
  if (signal?.aborted)
@@ -140,10 +123,9 @@ class NodeNativeCodexWebSocket {
140
123
  throw abortFailure(signal);
141
124
  const queued = this.queue.shift();
142
125
  if (queued !== undefined) {
143
- this.queuedBytes -= queued.bytes;
144
- if (queued.value instanceof LlmError)
145
- throw queued.value;
146
- return queued.value;
126
+ if (queued instanceof LlmError)
127
+ throw queued;
128
+ return queued;
147
129
  }
148
130
  return new Promise((resolve, reject) => {
149
131
  let waiter;
@@ -173,7 +155,6 @@ class NodeNativeCodexWebSocket {
173
155
  export class NodeNativeCodexWebSocketFactory {
174
156
  async connect(options) {
175
157
  const timeout = positive(options.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS, 'connect timeout');
176
- const maximum = positive(options.maxFrameBytes, DEFAULT_MAX_FRAME_BYTES, 'frame limit');
177
158
  if (options.signal?.aborted)
178
159
  throw abortFailure(options.signal);
179
160
  return new Promise((resolve, reject) => {
@@ -184,7 +165,8 @@ export class NodeNativeCodexWebSocketFactory {
184
165
  headers: options.headers,
185
166
  ...(agent === undefined ? {} : { agent }),
186
167
  handshakeTimeout: timeout,
187
- maxPayload: maximum,
168
+ // ws uses zero to disable its default message-size ceiling.
169
+ maxPayload: 0,
188
170
  perMessageDeflate: true,
189
171
  });
190
172
  const abort = () => {
@@ -215,7 +197,7 @@ export class NodeNativeCodexWebSocketFactory {
215
197
  });
216
198
  socket.on('open', () => {
217
199
  options.signal?.removeEventListener('abort', abort);
218
- resolve(new NodeNativeCodexWebSocket(socket, responseHeaders, maximum));
200
+ resolve(new NodeNativeCodexWebSocket(socket, responseHeaders));
219
201
  });
220
202
  socket.on('error', (error) => {
221
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;
@@ -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
- 'WS_FRAME_TOO_LARGE', 'WS_RESPONSE_TOO_LARGE', 'TIMEOUT',
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
- /** 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>;
package/lib/sse.js CHANGED
@@ -1,18 +1,10 @@
1
- /** Bounded, cancellable Server-Sent Events byte framing. */
1
+ /** Cancellable Server-Sent Events byte framing. */
2
2
  import { LlmError } from '@deepseek-ai/dsh-llm';
3
- export const DEFAULT_MAX_SSE_EVENT_BYTES = 64 * 1024 * 1024;
4
3
  function aborted() {
5
4
  return new LlmError('native Codex SSE stream was cancelled', 'ABORTED');
6
5
  }
7
- function tooLarge() {
8
- return new LlmError('native Codex SSE event exceeded the size limit', 'SSE_EVENT_TOO_LARGE');
9
- }
10
- /** Decode a byte stream into bounded SSE frames. */
6
+ /** Decode a byte stream into SSE frames. */
11
7
  export async function* parseSse(stream, options = {}) {
12
- const limit = options.maxEventBytes ?? DEFAULT_MAX_SSE_EVENT_BYTES;
13
- if (!Number.isSafeInteger(limit) || limit <= 0) {
14
- throw new LlmError('native Codex SSE size limit is invalid', 'INVALID_CONFIG');
15
- }
16
8
  if (options.signal?.aborted === true)
17
9
  throw aborted();
18
10
  const reader = stream.getReader();
@@ -20,7 +12,6 @@ export async function* parseSse(stream, options = {}) {
20
12
  let pending = '';
21
13
  let dataLines = [];
22
14
  let eventName;
23
- let eventBytes = 0;
24
15
  let cancelled = false;
25
16
  const onAbort = () => {
26
17
  cancelled = true;
@@ -39,8 +30,6 @@ export async function* parseSse(stream, options = {}) {
39
30
  options.onActivity?.();
40
31
  options.onBytes?.(value.byteLength);
41
32
  pending += decoder.decode(value, { stream: true });
42
- if (Buffer.byteLength(pending) > limit && !pending.includes('\n'))
43
- throw tooLarge();
44
33
  let newline = pending.indexOf('\n');
45
34
  while (newline >= 0) {
46
35
  let line = pending.slice(0, newline);
@@ -57,12 +46,8 @@ export async function* parseSse(stream, options = {}) {
57
46
  }
58
47
  dataLines = [];
59
48
  eventName = undefined;
60
- eventBytes = 0;
61
49
  continue;
62
50
  }
63
- eventBytes += Buffer.byteLength(line) + 1;
64
- if (eventBytes > limit)
65
- throw tooLarge();
66
51
  if (line.startsWith(':'))
67
52
  options.onActivity?.();
68
53
  else if (line.startsWith('data:'))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pure01fx/dsh-openai-codex-auth",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "Native ChatGPT Codex provider, device-code-first login, and same-origin Web integration for DeepSeek Harness",
5
5
  "license": "MIT",
6
6
  "publishConfig": {