@pure01fx/dsh-openai-codex-auth 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +2 -0
- package/lib/native-websocket-session.js +0 -5
- package/lib/native-websocket-socket.js +3 -4
- package/lib/native-websocket.js +10 -16
- package/lib/replay.d.ts +1 -2
- package/lib/replay.js +7 -20
- package/lib/responses.d.ts +4 -2
- package/lib/responses.js +34 -32
- package/lib/sse.js +1 -1
- package/package.json +7 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.7.2
|
|
6
|
+
|
|
7
|
+
### Auxiliary-call compatibility
|
|
8
|
+
|
|
9
|
+
- Accepts DSH's purpose-tagged compaction and session-title `maxTokens` budgets without serializing an unsupported Native Codex output-cap field, while ordinary model calls continue to reject unsupported explicit caps.
|
|
10
|
+
- Restores automatic compaction for long Native Codex sessions that previously accumulated failed `compaction/end` events.
|
|
11
|
+
|
|
12
|
+
## 0.7.1
|
|
13
|
+
|
|
14
|
+
### Native stream compatibility
|
|
15
|
+
|
|
16
|
+
- Aligns long native Codex streams with codex-rs by removing aggregate event/wire-byte, output-item, and replay-item ceilings, using a 300-second WebSocket idle timeout, and retaining 64 MiB single-event/frame, queued-byte, translated-content, retained-output, and replay-state safeguards.
|
|
17
|
+
|
|
5
18
|
## 0.7.0
|
|
6
19
|
|
|
7
20
|
### DSH 0.1.1 compatibility
|
package/README.md
CHANGED
|
@@ -187,6 +187,8 @@ $DSH_HOME/openai-codex-auth.json
|
|
|
187
187
|
|
|
188
188
|
本包只定义 Codex Provider,不发布或改写任何具体 Profile。整合包必须在挂载本插件前释放已有的 `openai-codex` route,例如从自己的 `llm-pi-ai` provider map 中移除该项;本包不会清空未知的 sibling providers。只想复用 OAuth/UI 并继续使用外部 Provider 的组合可以显式设置 `nativeAdapter: false`。从 pi-ai 切到 Native 时,旧 foreign replay 会降级为可见持久历史;反向切回 pi-ai 后,已产生 Native replay 的进行中 session 不保证可续跑,应新建 session。
|
|
189
189
|
|
|
190
|
+
上游 Native Codex Responses 请求没有输出 token 上限字段,因此普通会话若显式设置 `maxTokens` 仍会在 Provider I/O 前失败。DSH 的压缩与会话标题辅助调用会固定携带 `maxTokens`,并分别通过 `purpose: compaction` 与 `purpose: session-title` 标识;本适配器允许这两类 hint 通过,但不会把它序列化为 `max_output_tokens`,实际输出长度仍由 Native Codex 决定。
|
|
191
|
+
|
|
190
192
|
原生 route 默认使用 Responses WebSocket v2,在会话首个请求上执行 `generate: false` prewarm,并仅在请求历史严格延伸时发送 `previous_response_id` 与增量后缀。连接重建会清除增量链;纯 WebSocket/HTTP 连接建立失败不消耗普通 stream retry 预算,而是按 Codex 的网络恢复策略从 5 秒指数退避到 60 秒并持续等待成功或取消,因此可跨越半分钟断网。连接已经建立后的首个 DSH chunk 前错误仍使用默认 5 次 stream retry 与 200ms 起步的有界指数退避,安全重试耗尽或握手返回 HTTP 426 后,该 DSH 会话会确定性地回退到 HTTP/SSE。transport 层在任何 DSH chunk 已输出后仍不会直接重放原始 stream;它会把瞬态中断重新分类为只允许 durable failed-step 恢复的错误,标准 Agent Profile 中的 `dsh-llm-retry` 再按默认 5 次策略重建请求。该外层策略不会再次匹配 transport 已在首个 chunk 前耗尽的有限 stream 错误,避免两层预算相乘;失败 attempt 的原始事件可以留作非 surface 诊断,但不会组装成模型可见的持久 assistant message。未加载该恢复插件的直接 `ctx.llm.stream()` 调用在首个 chunk 后仍保持 single-attempt,避免重复输出。WebSocket `codex.rate_limits` 事件、握手/错误 metadata 以及 HTTP/SSE response headers 中的 `x-codex-*` quota 会被边界校验后直接写入 Host 用量缓存;额外 metered limit 不会覆盖默认 `codex` 套餐卡片。`response.completed.usage_metadata.amount` 会按原始高精度字符串保留,并作为当前账号的最近响应观测出现在 status JSON 中。
|
|
191
193
|
|
|
192
194
|
`<base>-fast` 只是公开选择别名:wire model 仍是 `<base>`,请求携带 `service_tier: priority`。若账号目录不声明 priority 能力,或请求前账号 authority 已改变,Fast 会直接失败,不会静默降级。Reasoning 选择在 adapter 边界按上游规则转换:`persistent` 在线上请求中写为 `disabled`;`ultra` 优先使用 Catalog 的 `multi_agent_reasoning_effort`,否则回退到 `max`、最高非 Ultra 档或 `medium`。
|
|
@@ -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_OUTPUT_ITEMS = 2048;
|
|
5
4
|
const MAX_RESPONSE_ID_BYTES = 256;
|
|
6
5
|
const IGNORED_REUSE_FIELDS = new Set([
|
|
7
6
|
'input', 'previous_response_id', 'generate', 'client_metadata',
|
|
@@ -90,10 +89,6 @@ export class NativeCodexWebSocketSessionState {
|
|
|
90
89
|
this.reset();
|
|
91
90
|
throw failure('native Codex WebSocket completion identity is invalid');
|
|
92
91
|
}
|
|
93
|
-
if (outputItems.length > MAX_OUTPUT_ITEMS) {
|
|
94
|
-
this.reset();
|
|
95
|
-
throw failure('native Codex WebSocket response has too many items');
|
|
96
|
-
}
|
|
97
92
|
this.completed = {
|
|
98
93
|
propertyHash: this.pending.propertyHash,
|
|
99
94
|
contextLength: this.pending.inputLength + outputItems.length,
|
|
@@ -4,9 +4,8 @@ import { nativeCodexEndpoint } from './endpoint.js';
|
|
|
4
4
|
import { NATIVE_CODEX_CONNECTION_FAILED_CODE, isNativeCodexConnectionFailure, } from './native-adapter.js';
|
|
5
5
|
import WebSocket from 'ws';
|
|
6
6
|
const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
|
|
7
|
-
const DEFAULT_MAX_FRAME_BYTES = 1024 * 1024;
|
|
8
|
-
const
|
|
9
|
-
const MAX_QUEUED_BYTES = 24 * 1024 * 1024;
|
|
7
|
+
const DEFAULT_MAX_FRAME_BYTES = 64 * 1024 * 1024;
|
|
8
|
+
const MAX_QUEUED_BYTES = 64 * 1024 * 1024;
|
|
10
9
|
function failure(message, code, cause) {
|
|
11
10
|
return new LlmError(message, code, cause === undefined ? undefined : { cause });
|
|
12
11
|
}
|
|
@@ -83,7 +82,7 @@ class NodeNativeCodexWebSocket {
|
|
|
83
82
|
waiter.resolve(value);
|
|
84
83
|
return;
|
|
85
84
|
}
|
|
86
|
-
if (this.
|
|
85
|
+
if (this.queuedBytes + bytes > MAX_QUEUED_BYTES) {
|
|
87
86
|
this.fail(failure('native Codex WebSocket queued too much response data', 'WS_RESPONSE_TOO_LARGE'));
|
|
88
87
|
return;
|
|
89
88
|
}
|
package/lib/native-websocket.js
CHANGED
|
@@ -11,8 +11,8 @@ import { ResponsesStreamTranslator, codexResponseTurnState, } from './responses.
|
|
|
11
11
|
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
|
-
const DEFAULT_IDLE_TIMEOUT_MS =
|
|
15
|
-
const DEFAULT_MAX_FRAME_BYTES = 1024 * 1024;
|
|
14
|
+
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,9 +21,7 @@ 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
|
|
25
|
-
const MAX_OUTPUT_ITEMS_PER_RESPONSE = 2048;
|
|
26
|
-
const MAX_RESPONSE_BYTES = 24 * 1024 * 1024;
|
|
24
|
+
const MAX_RETAINED_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
27
25
|
function failure(message, code, cause) {
|
|
28
26
|
return new LlmError(message, code, cause === undefined ? undefined : { cause });
|
|
29
27
|
}
|
|
@@ -221,7 +219,7 @@ export class NativeCodexWebSocketTransport {
|
|
|
221
219
|
this.factory = options.webSocketFactory ?? new NodeNativeCodexWebSocketFactory();
|
|
222
220
|
this.connectTimeoutMs = boundedPositive(options.webSocketConnectTimeoutMs, 10_000, 120_000, 'WebSocket connect timeout');
|
|
223
221
|
this.idleTimeoutMs = boundedPositive(options.webSocketIdleTimeoutMs, DEFAULT_IDLE_TIMEOUT_MS, 60 * 60_000, 'WebSocket idle timeout');
|
|
224
|
-
this.maxFrameBytes = boundedPositive(options.maxWebSocketFrameBytes, DEFAULT_MAX_FRAME_BYTES,
|
|
222
|
+
this.maxFrameBytes = boundedPositive(options.maxWebSocketFrameBytes, DEFAULT_MAX_FRAME_BYTES, DEFAULT_MAX_FRAME_BYTES, 'WebSocket frame limit');
|
|
225
223
|
this.maxSessions = boundedPositive(options.maxWebSocketSessions, DEFAULT_MAX_SESSIONS, 256, 'WebSocket session limit');
|
|
226
224
|
this.sessionIdleMs = boundedPositive(options.webSocketSessionIdleMs, DEFAULT_SESSION_IDLE_MS, 24 * 60 * 60_000, 'WebSocket session idle limit');
|
|
227
225
|
this.maxReconnects = retryCount(options.maxWebSocketReconnects);
|
|
@@ -385,14 +383,9 @@ export class NativeCodexWebSocketTransport {
|
|
|
385
383
|
model: mode.publicModel ?? generation.model,
|
|
386
384
|
});
|
|
387
385
|
const outputItems = [];
|
|
388
|
-
let
|
|
389
|
-
|
|
390
|
-
while (events++ < MAX_EVENTS_PER_RESPONSE) {
|
|
386
|
+
let outputBytes = 0;
|
|
387
|
+
while (true) {
|
|
391
388
|
const text = await this.receive(entry, signal);
|
|
392
|
-
responseBytes += Buffer.byteLength(text);
|
|
393
|
-
if (responseBytes > MAX_RESPONSE_BYTES) {
|
|
394
|
-
throw failure('native Codex WebSocket response exceeded the size limit', 'WS_RESPONSE_TOO_LARGE');
|
|
395
|
-
}
|
|
396
389
|
let event;
|
|
397
390
|
try {
|
|
398
391
|
event = JSON.parse(text);
|
|
@@ -421,10 +414,12 @@ export class NativeCodexWebSocketTransport {
|
|
|
421
414
|
}
|
|
422
415
|
const output = normalizedOutputItem(event);
|
|
423
416
|
if (output !== undefined) {
|
|
424
|
-
|
|
425
|
-
|
|
417
|
+
const nextOutputBytes = outputBytes + Buffer.byteLength(JSON.stringify(output));
|
|
418
|
+
if (nextOutputBytes > MAX_RETAINED_OUTPUT_BYTES) {
|
|
419
|
+
throw failure('native Codex WebSocket retained output exceeded the size limit', 'WS_RESPONSE_TOO_LARGE');
|
|
426
420
|
}
|
|
427
421
|
outputItems.push(output);
|
|
422
|
+
outputBytes = nextOutputBytes;
|
|
428
423
|
}
|
|
429
424
|
if (event.type === 'response.completed') {
|
|
430
425
|
const response = typeof event.response === 'object'
|
|
@@ -444,7 +439,6 @@ export class NativeCodexWebSocketTransport {
|
|
|
444
439
|
return;
|
|
445
440
|
}
|
|
446
441
|
}
|
|
447
|
-
throw failure('native Codex WebSocket response had too many events', 'WS_PROTOCOL_ERROR');
|
|
448
442
|
}
|
|
449
443
|
async *attempt(entry, prepared, credential, signal) {
|
|
450
444
|
await this.ensureSocket(entry, prepared, credential, signal);
|
package/lib/replay.d.ts
CHANGED
|
@@ -32,12 +32,11 @@ export interface NativeCodexReplaySource {
|
|
|
32
32
|
export declare function replayableItemId(value: string | undefined): string | undefined;
|
|
33
33
|
/** True only for legacy raw state or an rc.2 envelope emitted by this package. */
|
|
34
34
|
export declare function hasNativeCodexReplayKind(value: unknown): boolean;
|
|
35
|
-
/** Attempt-local bounded accumulator; no ciphertext can grow unchecked before completion. */
|
|
35
|
+
/** Attempt-local byte-bounded accumulator; no ciphertext can grow unchecked before completion. */
|
|
36
36
|
export declare class NativeCodexReplayCapture {
|
|
37
37
|
private readonly provider;
|
|
38
38
|
private readonly model;
|
|
39
39
|
private readonly descriptors;
|
|
40
|
-
private references;
|
|
41
40
|
private stateBytes;
|
|
42
41
|
constructor(provider: string, model: string);
|
|
43
42
|
add(item: NativeCodexReplayDescriptor): void;
|
package/lib/replay.js
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
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_DESCRIPTORS = 128;
|
|
6
|
-
const MAX_REPLAY_BLOCK_REFS = 256;
|
|
7
5
|
const MAX_REPLAY_ITEM_ID_BYTES = 256;
|
|
8
|
-
const MAX_REPLAY_CIPHERTEXT_BYTES = 1024 * 1024;
|
|
9
|
-
const MAX_REPLAY_STATE_BYTES =
|
|
6
|
+
const MAX_REPLAY_CIPHERTEXT_BYTES = 64 * 1024 * 1024;
|
|
7
|
+
const MAX_REPLAY_STATE_BYTES = 64 * 1024 * 1024;
|
|
10
8
|
function failure(message, code = 'INVALID_REPLAY_STATE') {
|
|
11
9
|
return new LlmError(message, code);
|
|
12
10
|
}
|
|
@@ -105,8 +103,9 @@ export function hasNativeCodexReplayKind(value) {
|
|
|
105
103
|
return object(replayPayload(value))?.kind === NATIVE_CODEX_REPLAY_KIND;
|
|
106
104
|
}
|
|
107
105
|
function parseState(value) {
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
const payload = replayPayload(value);
|
|
107
|
+
safeStateSize(payload, 'INVALID_REPLAY_STATE');
|
|
108
|
+
const row = object(payload);
|
|
110
109
|
if (row === undefined || row.kind !== NATIVE_CODEX_REPLAY_KIND
|
|
111
110
|
|| row.version !== NATIVE_CODEX_REPLAY_VERSION
|
|
112
111
|
|| !onlyKeys(row, ['kind', 'version', 'provider', 'model', 'items'])) {
|
|
@@ -115,13 +114,10 @@ function parseState(value) {
|
|
|
115
114
|
const provider = boundedString(row.provider);
|
|
116
115
|
const model = boundedString(row.model, 512);
|
|
117
116
|
if (provider === undefined || model === undefined || !Array.isArray(row.items)
|
|
118
|
-
|| row.items.length === 0
|
|
117
|
+
|| row.items.length === 0) {
|
|
119
118
|
throw failure('native Codex replay state metadata is invalid');
|
|
120
119
|
}
|
|
121
120
|
const items = row.items.map(parseDescriptor);
|
|
122
|
-
const refs = items.reduce((total, item) => total + (item.type === 'function_call' ? 1 : item.blocks.length), 0);
|
|
123
|
-
if (refs > MAX_REPLAY_BLOCK_REFS)
|
|
124
|
-
throw failure('native Codex replay state has too many block references');
|
|
125
121
|
return {
|
|
126
122
|
kind: NATIVE_CODEX_REPLAY_KIND,
|
|
127
123
|
version: NATIVE_CODEX_REPLAY_VERSION,
|
|
@@ -130,12 +126,11 @@ function parseState(value) {
|
|
|
130
126
|
items,
|
|
131
127
|
};
|
|
132
128
|
}
|
|
133
|
-
/** Attempt-local bounded accumulator; no ciphertext can grow unchecked before completion. */
|
|
129
|
+
/** Attempt-local byte-bounded accumulator; no ciphertext can grow unchecked before completion. */
|
|
134
130
|
export class NativeCodexReplayCapture {
|
|
135
131
|
provider;
|
|
136
132
|
model;
|
|
137
133
|
descriptors = [];
|
|
138
|
-
references = 0;
|
|
139
134
|
stateBytes;
|
|
140
135
|
constructor(provider, model) {
|
|
141
136
|
this.provider = provider;
|
|
@@ -149,13 +144,6 @@ export class NativeCodexReplayCapture {
|
|
|
149
144
|
}));
|
|
150
145
|
}
|
|
151
146
|
add(item) {
|
|
152
|
-
if (this.descriptors.length >= MAX_REPLAY_DESCRIPTORS) {
|
|
153
|
-
throw failure('native Codex response has too many replay descriptors', 'MALFORMED_RESPONSE');
|
|
154
|
-
}
|
|
155
|
-
const addedReferences = item.type === 'function_call' ? 1 : item.blocks.length;
|
|
156
|
-
if (this.references + addedReferences > MAX_REPLAY_BLOCK_REFS) {
|
|
157
|
-
throw failure('native Codex response has too many replay block references', 'MALFORMED_RESPONSE');
|
|
158
|
-
}
|
|
159
147
|
if (item.type === 'reasoning' && item.encryptedContent !== undefined
|
|
160
148
|
&& Buffer.byteLength(item.encryptedContent) > MAX_REPLAY_CIPHERTEXT_BYTES) {
|
|
161
149
|
throw failure('native Codex encrypted reasoning exceeded the replay limit', 'MALFORMED_RESPONSE');
|
|
@@ -166,7 +154,6 @@ export class NativeCodexReplayCapture {
|
|
|
166
154
|
throw failure('native Codex replay state exceeded the size limit', 'REPLAY_STATE_TOO_LARGE');
|
|
167
155
|
}
|
|
168
156
|
this.descriptors.push(item);
|
|
169
|
-
this.references += addedReferences;
|
|
170
157
|
this.stateBytes = nextBytes;
|
|
171
158
|
}
|
|
172
159
|
finish() {
|
package/lib/responses.d.ts
CHANGED
|
@@ -100,9 +100,13 @@ export declare class ResponsesStreamTranslator {
|
|
|
100
100
|
private readonly order;
|
|
101
101
|
private readonly replayCapture;
|
|
102
102
|
private nextIndex;
|
|
103
|
+
private retainedBytes;
|
|
103
104
|
private sawToolCall;
|
|
104
105
|
terminated: boolean;
|
|
105
106
|
constructor(replayContext?: ResponsesReplayContext | undefined);
|
|
107
|
+
private reserve;
|
|
108
|
+
private append;
|
|
109
|
+
private fill;
|
|
106
110
|
private open;
|
|
107
111
|
private close;
|
|
108
112
|
private closeItem;
|
|
@@ -114,8 +118,6 @@ export interface StreamResponsesOptions extends ParseSseOptions {
|
|
|
114
118
|
onMalformedEvent?: () => void;
|
|
115
119
|
onEvent?: (event: ResponsesStreamEvent) => void;
|
|
116
120
|
replayContext?: ResponsesReplayContext;
|
|
117
|
-
maxResponseBytes?: number;
|
|
118
|
-
maxResponseEvents?: number;
|
|
119
121
|
}
|
|
120
122
|
/** Validate one opaque sticky turn token before retaining or forwarding it. */
|
|
121
123
|
export declare function boundedCodexTurnState(value: unknown): string | undefined;
|
package/lib/responses.js
CHANGED
|
@@ -6,6 +6,7 @@ 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;
|
|
9
10
|
function fixedError(message, code) { return new LlmError(message, code); }
|
|
10
11
|
function imageItem(image) {
|
|
11
12
|
if (!/^image[/][a-z0-9.+-]+$/i.test(image.mediaType) || image.dataBase64.length === 0) {
|
|
@@ -131,7 +132,12 @@ function assertSupportedOptions(options) {
|
|
|
131
132
|
if (options.temperature !== undefined) {
|
|
132
133
|
throw fixedError('native Codex does not support temperature', 'UNSUPPORTED');
|
|
133
134
|
}
|
|
134
|
-
|
|
135
|
+
// DSH's compaction and title helpers always carry a bounded output budget, but
|
|
136
|
+
// Codex's native Responses wire has no corresponding request field. Accept the
|
|
137
|
+
// hint only for those purpose-tagged auxiliary calls and leave it off the wire.
|
|
138
|
+
if (options.maxTokens !== undefined
|
|
139
|
+
&& options.purpose !== 'compaction'
|
|
140
|
+
&& options.purpose !== 'session-title') {
|
|
135
141
|
throw fixedError('native Codex does not support maxTokens', 'UNSUPPORTED');
|
|
136
142
|
}
|
|
137
143
|
if (options.stop !== undefined) {
|
|
@@ -242,6 +248,7 @@ export class ResponsesStreamTranslator {
|
|
|
242
248
|
order = [];
|
|
243
249
|
replayCapture;
|
|
244
250
|
nextIndex = 0;
|
|
251
|
+
retainedBytes = 0;
|
|
245
252
|
sawToolCall = false;
|
|
246
253
|
terminated = false;
|
|
247
254
|
constructor(replayContext) {
|
|
@@ -250,7 +257,26 @@ export class ResponsesStreamTranslator {
|
|
|
250
257
|
? undefined
|
|
251
258
|
: new NativeCodexReplayCapture(replayContext.provider, replayContext.model);
|
|
252
259
|
}
|
|
260
|
+
reserve(bytes) {
|
|
261
|
+
const nextBytes = this.retainedBytes + bytes;
|
|
262
|
+
if (!Number.isSafeInteger(nextBytes) || nextBytes > MAX_RETAINED_RESPONSE_BYTES) {
|
|
263
|
+
throw fixedError('native Codex response retained content exceeded the size limit', 'RESPONSE_TOO_LARGE');
|
|
264
|
+
}
|
|
265
|
+
this.retainedBytes = nextBytes;
|
|
266
|
+
}
|
|
267
|
+
append(block, delta) {
|
|
268
|
+
this.reserve(Buffer.byteLength(delta));
|
|
269
|
+
block.text += delta;
|
|
270
|
+
}
|
|
271
|
+
fill(block, text) {
|
|
272
|
+
if (block.text.length > 0)
|
|
273
|
+
return;
|
|
274
|
+
this.reserve(Buffer.byteLength(text));
|
|
275
|
+
block.text = text;
|
|
276
|
+
}
|
|
253
277
|
open(key, kind, chunks, callId = '', name) {
|
|
278
|
+
this.reserve(128 + Buffer.byteLength(key) + Buffer.byteLength(callId)
|
|
279
|
+
+ (name === undefined ? 0 : Buffer.byteLength(name)));
|
|
254
280
|
const block = {
|
|
255
281
|
index: this.nextIndex++, kind, text: '', callId,
|
|
256
282
|
...name === undefined ? {} : { name },
|
|
@@ -308,7 +334,7 @@ export class ResponsesStreamTranslator {
|
|
|
308
334
|
const key = `${eventItemId(event)}:text:${String(event.content_index ?? 0)}`;
|
|
309
335
|
const block = this.blocks.get(key) ?? this.open(key, 'text', chunks);
|
|
310
336
|
const delta = eventDelta(event);
|
|
311
|
-
block
|
|
337
|
+
this.append(block, delta);
|
|
312
338
|
chunks.push({ type: 'text-delta', index: block.index, text: delta });
|
|
313
339
|
return chunks;
|
|
314
340
|
}
|
|
@@ -316,7 +342,7 @@ export class ResponsesStreamTranslator {
|
|
|
316
342
|
const key = `${eventItemId(event)}:summary:${String(event.summary_index ?? 0)}`;
|
|
317
343
|
const block = this.blocks.get(key) ?? this.open(key, 'reasoning', chunks);
|
|
318
344
|
const delta = eventDelta(event);
|
|
319
|
-
block
|
|
345
|
+
this.append(block, delta);
|
|
320
346
|
chunks.push({ type: 'reasoning-delta', index: block.index, text: delta });
|
|
321
347
|
return chunks;
|
|
322
348
|
}
|
|
@@ -329,7 +355,7 @@ export class ResponsesStreamTranslator {
|
|
|
329
355
|
throw fixedError('native Codex function arguments have no open call', 'MALFORMED_RESPONSE');
|
|
330
356
|
}
|
|
331
357
|
const delta = eventDelta(event);
|
|
332
|
-
block
|
|
358
|
+
this.append(block, delta);
|
|
333
359
|
chunks.push({
|
|
334
360
|
type: 'tool-call-delta', index: block.index, id: CallId(block.callId),
|
|
335
361
|
...block.name === undefined ? {} : { name: block.name }, argumentsDelta: delta,
|
|
@@ -360,8 +386,7 @@ export class ResponsesStreamTranslator {
|
|
|
360
386
|
}
|
|
361
387
|
block.callId = item.call_id;
|
|
362
388
|
block.name = item.name;
|
|
363
|
-
|
|
364
|
-
block.text = item.arguments;
|
|
389
|
+
this.fill(block, item.arguments);
|
|
365
390
|
this.close(key, chunks);
|
|
366
391
|
if (this.replayContext !== undefined)
|
|
367
392
|
this.replayCapture?.add({
|
|
@@ -385,8 +410,7 @@ export class ResponsesStreamTranslator {
|
|
|
385
410
|
if (block.text.length > 0 && block.text !== part.text) {
|
|
386
411
|
throw fixedError('native Codex text changed during streaming', 'MALFORMED_RESPONSE');
|
|
387
412
|
}
|
|
388
|
-
|
|
389
|
-
block.text = part.text;
|
|
413
|
+
this.fill(block, part.text);
|
|
390
414
|
refs.push(block.index);
|
|
391
415
|
this.close(key, chunks);
|
|
392
416
|
}
|
|
@@ -417,8 +441,7 @@ export class ResponsesStreamTranslator {
|
|
|
417
441
|
if (block.text.length > 0 && block.text !== text) {
|
|
418
442
|
throw fixedError('native Codex reasoning summary changed during streaming', 'MALFORMED_RESPONSE');
|
|
419
443
|
}
|
|
420
|
-
|
|
421
|
-
block.text = text;
|
|
444
|
+
this.fill(block, text);
|
|
422
445
|
refs.push(block.index);
|
|
423
446
|
this.close(key, chunks);
|
|
424
447
|
}
|
|
@@ -528,29 +551,8 @@ export function codexResponseTurnState(event) {
|
|
|
528
551
|
}
|
|
529
552
|
/** Consume framed SSE JSON into DSH chunks. */
|
|
530
553
|
export async function* streamResponses(stream, options = {}) {
|
|
531
|
-
const byteLimit = options.maxResponseBytes ?? 24 * 1024 * 1024;
|
|
532
|
-
const eventLimit = options.maxResponseEvents ?? 4096;
|
|
533
|
-
if (!Number.isSafeInteger(byteLimit) || byteLimit <= 0 || byteLimit > 24 * 1024 * 1024
|
|
534
|
-
|| !Number.isSafeInteger(eventLimit) || eventLimit <= 0 || eventLimit > 4096) {
|
|
535
|
-
throw fixedError('native Codex response limit is invalid', 'INVALID_CONFIG');
|
|
536
|
-
}
|
|
537
|
-
let responseBytes = 0;
|
|
538
|
-
let responseEvents = 0;
|
|
539
554
|
const translator = new ResponsesStreamTranslator(options.replayContext);
|
|
540
|
-
for await (const frame of parseSse(stream, {
|
|
541
|
-
...options,
|
|
542
|
-
onBytes: (bytes) => {
|
|
543
|
-
options.onBytes?.(bytes);
|
|
544
|
-
responseBytes += bytes;
|
|
545
|
-
if (responseBytes > byteLimit) {
|
|
546
|
-
throw fixedError('native Codex response exceeded the size limit', 'RESPONSE_TOO_LARGE');
|
|
547
|
-
}
|
|
548
|
-
},
|
|
549
|
-
})) {
|
|
550
|
-
responseEvents += 1;
|
|
551
|
-
if (responseEvents > eventLimit) {
|
|
552
|
-
throw fixedError('native Codex response had too many events', 'RESPONSE_TOO_LARGE');
|
|
553
|
-
}
|
|
555
|
+
for await (const frame of parseSse(stream, options)) {
|
|
554
556
|
let event;
|
|
555
557
|
try {
|
|
556
558
|
event = JSON.parse(frame.data);
|
package/lib/sse.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Bounded, cancellable Server-Sent Events byte framing. */
|
|
2
2
|
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
3
|
-
export const DEFAULT_MAX_SSE_EVENT_BYTES = 1024 * 1024;
|
|
3
|
+
export const DEFAULT_MAX_SSE_EVENT_BYTES = 64 * 1024 * 1024;
|
|
4
4
|
function aborted() {
|
|
5
5
|
return new LlmError('native Codex SSE stream was cancelled', 'ABORTED');
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pure01fx/dsh-openai-codex-auth",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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": {
|
|
@@ -69,6 +69,11 @@
|
|
|
69
69
|
"CHANGELOG.md",
|
|
70
70
|
"LICENSE"
|
|
71
71
|
],
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "tsc",
|
|
74
|
+
"test": "vitest run",
|
|
75
|
+
"prepack": "pnpm build"
|
|
76
|
+
},
|
|
72
77
|
"dsh": {
|
|
73
78
|
"engines": {
|
|
74
79
|
"dsh": "0.1.1-rc.2"
|
|
@@ -107,9 +112,5 @@
|
|
|
107
112
|
"@types/node": "^22.20.0",
|
|
108
113
|
"typescript": "^6.0.3",
|
|
109
114
|
"vitest": "^4.1.8"
|
|
110
|
-
},
|
|
111
|
-
"scripts": {
|
|
112
|
-
"build": "tsc",
|
|
113
|
-
"test": "vitest run"
|
|
114
115
|
}
|
|
115
|
-
}
|
|
116
|
+
}
|