@pure01fx/dsh-openai-codex-auth 0.12.0 → 0.13.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 +15 -0
- package/CODEX-COMPATIBILITY.md +39 -0
- package/README.md +2 -2
- package/lib/native-adapter.js +2 -1
- package/lib/native-http.js +28 -50
- package/lib/native-websocket-socket.js +4 -5
- package/lib/native-websocket.js +29 -44
- package/lib/replay.d.ts +3 -0
- package/lib/replay.js +8 -2
- package/lib/responses.d.ts +8 -2
- package/lib/responses.js +43 -20
- package/lib/retry-after.d.ts +9 -0
- package/lib/retry-after.js +109 -0
- package/lib/upstream.d.ts +2 -2
- package/lib/upstream.js +2 -2
- package/package.json +32 -30
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.13.0
|
|
6
|
+
|
|
7
|
+
- Targets DSH 0.2.0-rc.2 with exact SDK peers, development dependencies and runtime helpers; retains the tool-message, shell and provenance migration below.
|
|
8
|
+
- Merges GPT-6.1 catalog routing, shared Retry-After handling, assistant phases and deterministic developer instructions from the latest source.
|
|
9
|
+
- Target build/typecheck and all 517 regression tests pass on DSH 0.2.0-rc.2. No live OAuth/model requests were made; collection-level Web validation is recorded separately.
|
|
10
|
+
|
|
5
11
|
## 0.12.0
|
|
6
12
|
|
|
7
13
|
### Migration
|
|
@@ -22,6 +28,15 @@
|
|
|
22
28
|
- Developer messages: preserved in Responses API requests
|
|
23
29
|
- Cloud context: requires toolCallId === source.callId and !isError
|
|
24
30
|
|
|
31
|
+
- Shares Retry-After parsing across HTTP, SSE, and WebSocket errors, including nested JSON headers and HTTP dates; preserves server deadlines across retries and HTTP fallback without truncating advice to local backoff limits.
|
|
32
|
+
|
|
33
|
+
- Preserves commentary, partial_answer, and final_answer phases through response events, durable replay, and WebSocket continuation matching; legacy phase-less replay remains readable.
|
|
34
|
+
|
|
35
|
+
- Moves Standard base instructions into the same deterministic developer-message prefix used by Lite, preserving each protocol’s tool and reasoning controls; changed instructions invalidate WebSocket prefix reuse.
|
|
36
|
+
|
|
37
|
+
- Requests the live model catalog as Codex 0.162.0 to discover GPT-6.1 Sol; verified that 0.155.0 omits it for the same account despite the bundled 0.153.0 minimum. Adds its exact Lite fallback and dynamic metadata/routing regressions without fabricating catalog availability.
|
|
38
|
+
|
|
39
|
+
|
|
25
40
|
## 0.11.2
|
|
26
41
|
|
|
27
42
|
- Requests the live model catalog as Codex 0.155.0 so version-gated GPT-6 Sol can be advertised; preserves dynamic account-specific discovery and routes exact Sol requests through Responses Lite when catalog metadata is unavailable. Fast remains catalog-authorized.
|
package/CODEX-COMPATIBILITY.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Codex 云端能力兼容性跟踪
|
|
2
2
|
|
|
3
|
+
## 重试、消息阶段与 Standard 指令对齐(Unreleased)
|
|
4
|
+
|
|
5
|
+
本节记录对 `ea27864f` 的三项增量适配,取代下方 Retry-After、phase 和 Standard 指令位置的历史待办;其余工作仍独立跟踪。
|
|
6
|
+
|
|
7
|
+
- Standard 基础指令改为 input 首部的 developer message,和 Lite 共用稳定 UUID 与 `model.base_instructions` metadata 构造。Standard 顶层 tools、并行工具与 reasoning 参数保留各自协议;Lite 的工具前缀及目录模板/DSH 指令组合保持独立。回归验证相同指令保持 WS 前缀复用,改变指令会触发完整请求,HTTP 历史、图片、工具输出和加密 reasoning 顺序不变。
|
|
8
|
+
- 消息 phase 已接收并保留 commentary / partial_answer / final_answer,通过 v1 描述符的可选字段持久化再回放,并让 WS 复用比较使用同一有效 phase。added-only 的 phase 保留到 done,done 的显式 phase 优先;流中的 null/缺失值表示未提供,非法枚举拒绝。测试覆盖 JSON 保存/恢复、分叉和重启的请求构造、真实传输类的增量匹配与 phase 改变后完整重发。缺少 phase 的旧会话可读;旧插件的严格字段校验无法读取含 phase 的新描述符,降级时需考虑这一限制。
|
|
9
|
+
- Retry-After 共用秒数/HTTP-date 解析,支持 JSON 数字值,嵌套错误 headers 优先;保留单次收到建议的单调时钟截止时间,不用本地退避上限截短服务端等待。HTTP 读取错误正文及其超时不会重置或丢弃等待,WS 最后一次失败后切换 HTTP 也遵守剩余等待。长等待分段使用 Node timer,仍可取消。宿主 `LlmError` 只公开正数 duration,无法跨宿主持久化表达单调时钟 deadline;本插件默认 normal 重试策略在宿主等待超上限时交还错误,不会提前重试。宿主另行配置的 always 策略可能忽略长等待,不属于本次插件内改动。
|
|
10
|
+
|
|
11
|
+
验证:`npm run build`、`npm test`(27 文件 / 511 项)、`npm run typecheck`、`git diff --check`、`npm pack --dry-run --ignore-scripts` 均通过;所有生成模块与新鲜编译一致,新 retry-after 模块已加入打包清单。独立审查发现的非法日期归一化问题已修复,并加入内层非法日期回退外层有效等待的回归。本轮为源码、模拟传输与 loopback 验证,未执行线上推理、发布、替换 hu profile 或重启 GUI。
|
|
12
|
+
|
|
13
|
+
## GPT-6.1 Sol 增量对齐(Unreleased,2026-10-08)
|
|
14
|
+
|
|
15
|
+
`../codex` 已 fast-forward 到 `ea27864f99f0b086cec2f9f0251b7190fb9844f1`(本次获取的 origin/main),工作树干净。此轮针对模型发现与 Lite 路由,不宣称完整运行时对齐,原 Provider/cloud 跟踪提交保持不变。
|
|
16
|
+
|
|
17
|
+
### 已确认的缺失原因与修复
|
|
18
|
+
|
|
19
|
+
对同一当前账号执行只读线上目录请求(未调用推理、未记录凭据):
|
|
20
|
+
|
|
21
|
+
| client_version | originator | HTTP | 返回目录 |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| 0.153.4 | dsh | 200 | 不含 gpt-6-sol / gpt-6.1-sol |
|
|
24
|
+
| 0.155.0 | dsh | 200 | 含 gpt-6-sol,不含 gpt-6.1-sol |
|
|
25
|
+
| 0.162.0 | dsh | 200 | 含 gpt-6.1-sol,visibility=list |
|
|
26
|
+
| 0.162.0 | codex_cli_rs | 200 | 同样含 gpt-6.1-sol |
|
|
27
|
+
|
|
28
|
+
上游 `models-manager/models.json` 将 6.1 Sol 的 minimal_client_version 标为 0.153.0,但它不等于线上实际发现门槛。请求版本升至本次已验证的 0.162.0(上游已有 rust-v0.162.0-alpha.20;目录发送去预发布后缀的整版本)。不更改 originator,不将静态模型强行加入账号目录。
|
|
29
|
+
|
|
30
|
+
补充精确 `gpt-6.1-sol` 的 Lite 后备,目录存在时仍以 metadata 为准。新增精简上游 fixture,验证动态发现、六档 reasoning、默认 low / multi-agent xhigh、Fast 授权、metadata 优先及未知近似名称不误用 Lite。
|
|
31
|
+
|
|
32
|
+
当前 3080 GUI 的 hu profile 安装的是插件 0.11.1,请求版本 0.153.4。本轮只修改并构建仓库,未替换 profile 安装或重启当前会话;仓库修改不会自动改变正在运行的 GUI。
|
|
33
|
+
|
|
34
|
+
验证:`npm run build`、`npm test`(26 文件 / 469 项)、`npm run typecheck`、`git diff --check` 全部通过;生成产物一致性测试通过。使用新构建的 NativeCodexCatalog + NativeCodexAdapter 查询当前账号,已返回 `gpt-6.1-sol` 与 `gpt-6.1-sol-fast`,六档推理配置及默认 low 解析成功。本轮未执行模型推理,因此此结果验证发现与元数据,不代表线上推理端到端验收。
|
|
35
|
+
|
|
36
|
+
### 独立后续兼容性工作
|
|
37
|
+
|
|
38
|
+
- Retry-After:新上游从 response.failed.response.error.headers 读取等待提示,WebSocket 优先嵌套 headers,支持数字 JSON 值和 HTTP 日期;插件仍遗漏部分嵌套提示并将等待上限压到 120 秒。建议单独对齐共享解析及 deadline 传递,避免早于服务端建议重试。依据 `codex-api/src/sse/responses_error.rs`、`endpoint/responses_websocket.rs`、`http-client/src/retry_after.rs`。这不是 6.1 模型目录缺失原因。
|
|
39
|
+
- 数字自定义 reasoning effort、Standard instructions 移入 developer input、partial_answer phase 回放保真分别存在增量差异,应单独设计与验证。6.1 现有命名 effort 和 Lite 路径不依赖这些改动。
|
|
40
|
+
|
|
41
|
+
|
|
3
42
|
## 已实现:Sol 与错误协议增量适配(0.11.2)
|
|
4
43
|
|
|
5
44
|
参考上游 `c44deff7b1083e9660ac55d02122481f1cdf139b`,在既有 0.11.1 工作树上完成以下必要改动。此节取代下方历史审查中相应的“待适配”结论;不代表完整上游运行时对齐。
|
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
## 快速开始
|
|
19
19
|
|
|
20
|
-
版本 `0.
|
|
20
|
+
版本 `0.13.0` 明确适配 DeepSeek Harness `0.2.0-rc.2`,SDK peers/runtime helpers 精确锁定该版本;不声明兼容旧 SDK。旧 DSH `0.1.5-rc.1` profile 应保留原插件 `0.11.2`,升级时在独立 profile 安装新版插件并保留原 profile/锁文件以便回滚。无需迁移账号文件或浏览器隐藏列表;`dsh.engines` 仅作兼容性说明,不当作宿主硬门禁。
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
回归范围包含真实目标 LLM/tools 服务、本地 HTTP/SSE/WebSocket 回放、错误/取消,以及模型隐藏在真实目录/catalog/store 的投影更新、重连和重新加载。验证命令为 `pnpm typecheck && pnpm build && pnpm test`;0.13.0 候选版已通过目标版本构建、类型检查和 517 项测试;当前未执行线上 OAuth/付费模型验收。
|
|
23
23
|
|
|
24
24
|
`0.11.0` 移除了插件额外设置的请求、响应、SSE/WebSocket、图片和重放状态大小上限,以及相应的 transport 选项(含 `maxRequestBodyBytes`);旧配置中的该字段应删除。搜索结果完整保留,WebSocket 继续优先复用增量上下文。服务端限制、协议校验及 DSH 文件/附件策略仍生效。
|
|
25
25
|
|
package/lib/native-adapter.js
CHANGED
|
@@ -57,7 +57,8 @@ function responsesLiteMode(model, requestedModel = model?.slug) {
|
|
|
57
57
|
if (model !== undefined && !model.useResponsesLite)
|
|
58
58
|
return {};
|
|
59
59
|
// Never downgrade known Lite-only slugs to Standard Responses during catalog outages.
|
|
60
|
-
if (model === undefined && requestedModel !== 'gpt-6-astra'
|
|
60
|
+
if (model === undefined && requestedModel !== 'gpt-6-astra'
|
|
61
|
+
&& requestedModel !== 'gpt-6-sol' && requestedModel !== 'gpt-6.1-sol')
|
|
61
62
|
return {};
|
|
62
63
|
return {
|
|
63
64
|
responsesLite: {
|
package/lib/native-http.js
CHANGED
|
@@ -4,6 +4,7 @@ import { networkTelemetry, trackNetwork, observeNetworkOutput } from './network-
|
|
|
4
4
|
import { EMPTY_RESPONSE_CODE, LlmError, ProviderRequestId, attributionHeaders, } from '@deepseek-ai/dsh-llm';
|
|
5
5
|
import { nativeCodexAuthorityHash } from './catalog.js';
|
|
6
6
|
import { nativeCodexEndpoint } from './endpoint.js';
|
|
7
|
+
import { retryAfterMs, retryError, remainingRetryDelay, sleepRetry, inheritRetryDeadline } from './retry-after.js';
|
|
7
8
|
import { NATIVE_CODEX_CONNECTION_FAILED_CODE, NATIVE_CODEX_STREAM_INTERRUPTED_CODE, isNativeCodexConnectionFailure, } from './native-adapter.js';
|
|
8
9
|
import { boundedCodexTurnState, codexRequestBody, codexResponseTurnState, streamResponses, responsesFailure, } from './responses.js';
|
|
9
10
|
import { hasNativeCodexReplayKind } from './replay.js';
|
|
@@ -19,7 +20,7 @@ const INITIAL_CONNECTION_RETRY_DELAY_MS = 5_000;
|
|
|
19
20
|
const MAX_CONNECTION_RETRY_DELAY_MS = 60_000;
|
|
20
21
|
const MAX_ERROR_BODY_BYTES = 64 * 1024;
|
|
21
22
|
function aborted(message = 'native Codex request was aborted') {
|
|
22
|
-
return
|
|
23
|
+
return retryError(message, 'ABORTED');
|
|
23
24
|
}
|
|
24
25
|
function throwIfAborted(signal) {
|
|
25
26
|
if (!signal?.aborted)
|
|
@@ -29,7 +30,7 @@ function throwIfAborted(signal) {
|
|
|
29
30
|
throw aborted();
|
|
30
31
|
}
|
|
31
32
|
function fixedFailure(message, code, options) {
|
|
32
|
-
return
|
|
33
|
+
return retryError(message, code, options);
|
|
33
34
|
}
|
|
34
35
|
function safePositiveInteger(value, fallback, name) {
|
|
35
36
|
const resolved = value ?? fallback;
|
|
@@ -52,24 +53,6 @@ function stablePromptCacheKey(sessionId) {
|
|
|
52
53
|
const hex = bytes.toString('hex');
|
|
53
54
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
54
55
|
}
|
|
55
|
-
function sleep(delayMs, signal) {
|
|
56
|
-
throwIfAborted(signal);
|
|
57
|
-
return new Promise((resolve, reject) => {
|
|
58
|
-
const timer = setTimeout(done, delayMs);
|
|
59
|
-
const abort = () => {
|
|
60
|
-
clearTimeout(timer);
|
|
61
|
-
signal?.removeEventListener('abort', abort);
|
|
62
|
-
const reason = signal?.reason;
|
|
63
|
-
reject(reason instanceof LlmError
|
|
64
|
-
? reason : aborted('native Codex retry wait was aborted'));
|
|
65
|
-
};
|
|
66
|
-
function done() {
|
|
67
|
-
signal?.removeEventListener('abort', abort);
|
|
68
|
-
resolve();
|
|
69
|
-
}
|
|
70
|
-
signal?.addEventListener('abort', abort, { once: true });
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
56
|
function attemptWatchdog(parent, requestMs, idleMs) {
|
|
74
57
|
const controller = new AbortController();
|
|
75
58
|
let timeout;
|
|
@@ -252,18 +235,8 @@ async function boundedError(response, signal, telemetry) {
|
|
|
252
235
|
return {};
|
|
253
236
|
}
|
|
254
237
|
}
|
|
255
|
-
function
|
|
256
|
-
const
|
|
257
|
-
if (raw === null)
|
|
258
|
-
return undefined;
|
|
259
|
-
const seconds = Number(raw);
|
|
260
|
-
const value = Number.isFinite(seconds)
|
|
261
|
-
? seconds * 1_000
|
|
262
|
-
: Date.parse(raw) - Date.now();
|
|
263
|
-
return Number.isFinite(value) && value > 0 ? Math.min(value, maxDelayMs) : undefined;
|
|
264
|
-
}
|
|
265
|
-
function errorFacts(response, maxDelayMs) {
|
|
266
|
-
const providerRetryAfterMs = retryAfterMs(response, maxDelayMs);
|
|
238
|
+
function errorFacts(response) {
|
|
239
|
+
const providerRetryAfterMs = retryAfterMs(response.headers.get('retry-after'));
|
|
267
240
|
const requestId = response.headers.get('x-request-id');
|
|
268
241
|
return {
|
|
269
242
|
status: response.status,
|
|
@@ -273,37 +246,43 @@ function errorFacts(response, maxDelayMs) {
|
|
|
273
246
|
: { requestId: ProviderRequestId(requestId) },
|
|
274
247
|
};
|
|
275
248
|
}
|
|
276
|
-
async function httpFailure(response,
|
|
249
|
+
async function httpFailure(response, signal, telemetry) {
|
|
250
|
+
const facts = errorFacts(response);
|
|
251
|
+
const advice = retryError('native Codex HTTP retry advice', 'SERVER', facts);
|
|
252
|
+
const fail = (message, code, options) => inheritRetryDeadline(fixedFailure(message, code, options), advice);
|
|
277
253
|
const detail = await boundedError(response, signal, telemetry);
|
|
278
|
-
const facts = errorFacts(response, maxDelayMs);
|
|
279
254
|
const classified = responsesFailure(detail.code, detail.message);
|
|
280
255
|
if (response.status === 401 || response.status === 403) {
|
|
281
|
-
return
|
|
256
|
+
return fail('native Codex rejected the configured credential', 'AUTH', facts);
|
|
282
257
|
}
|
|
283
258
|
if (classified.code !== 'SERVER') {
|
|
284
259
|
const delay = facts?.providerRetryAfterMs ?? classified.failure.providerRetryAfterMs;
|
|
285
|
-
return
|
|
260
|
+
return fail(classified.message, classified.code, {
|
|
286
261
|
...facts,
|
|
287
|
-
...(delay === undefined ? {} : { providerRetryAfterMs:
|
|
262
|
+
...(delay === undefined ? {} : { providerRetryAfterMs: delay }),
|
|
288
263
|
});
|
|
289
264
|
}
|
|
290
265
|
if (response.status === 429) {
|
|
291
|
-
return
|
|
266
|
+
return fail('native Codex request was rate limited', 'RATE_LIMIT', facts);
|
|
292
267
|
}
|
|
293
268
|
if (response.status === 408 || response.status === 504) {
|
|
294
|
-
return
|
|
269
|
+
return fail('native Codex request timed out', 'TIMEOUT', facts);
|
|
295
270
|
}
|
|
296
271
|
if (response.status >= 500) {
|
|
297
|
-
return
|
|
272
|
+
return fail('native Codex server request failed', 'SERVER', facts);
|
|
298
273
|
}
|
|
299
|
-
return
|
|
274
|
+
return fail('native Codex request was rejected', 'INVALID_REQUEST', facts);
|
|
300
275
|
}
|
|
301
276
|
function mappedFailure(error, watchdog, parent) {
|
|
302
277
|
if (parent?.aborted) {
|
|
303
278
|
return parent.reason instanceof LlmError ? parent.reason : aborted();
|
|
304
279
|
}
|
|
305
280
|
if (watchdog.timedOut())
|
|
306
|
-
return fixedFailure('native Codex request timed out', 'TIMEOUT'
|
|
281
|
+
return fixedFailure('native Codex request timed out', 'TIMEOUT', {
|
|
282
|
+
cause: error,
|
|
283
|
+
...(error instanceof LlmError && error.failure.providerRetryAfterMs !== undefined
|
|
284
|
+
? { providerRetryAfterMs: error.failure.providerRetryAfterMs } : {}),
|
|
285
|
+
});
|
|
307
286
|
if (error instanceof LlmError)
|
|
308
287
|
return error;
|
|
309
288
|
return fixedFailure('native Codex transport failed', 'TRANSPORT', { cause: error });
|
|
@@ -331,8 +310,7 @@ function retryable(error) {
|
|
|
331
310
|
}
|
|
332
311
|
function failedStepRetry(error) {
|
|
333
312
|
const failure = error.failure;
|
|
334
|
-
const providerRetryAfterMs = failure.providerRetryAfterMs
|
|
335
|
-
? undefined : Math.min(failure.providerRetryAfterMs, DEFAULT_MAX_RETRY_DELAY_MS);
|
|
313
|
+
const providerRetryAfterMs = failure.providerRetryAfterMs;
|
|
336
314
|
return fixedFailure(`native Codex response stream was interrupted: ${error.message}`, NATIVE_CODEX_STREAM_INTERRUPTED_CODE, {
|
|
337
315
|
cause: error,
|
|
338
316
|
...(failure.status === undefined ? {} : { status: failure.status }),
|
|
@@ -362,21 +340,21 @@ export class NativeCodexHttpTransport {
|
|
|
362
340
|
}
|
|
363
341
|
retryDelay(retry, providerDelay) {
|
|
364
342
|
if (providerDelay !== undefined)
|
|
365
|
-
return
|
|
343
|
+
return providerDelay;
|
|
366
344
|
const exponential = Math.min(this.initialDelayMs * (2 ** retry), this.maxDelayMs);
|
|
367
345
|
const random = this.options.random?.() ?? Math.random();
|
|
368
346
|
const jitter = 0.9 + Math.max(0, Math.min(1, random)) * 0.2;
|
|
369
347
|
return Math.max(1, Math.round(exponential * jitter));
|
|
370
348
|
}
|
|
371
349
|
async wait(retry, error, signal, telemetry) {
|
|
372
|
-
const delay = this.retryDelay(retry, error
|
|
350
|
+
const delay = this.retryDelay(retry, remainingRetryDelay(error));
|
|
373
351
|
telemetry.retry(delay);
|
|
374
|
-
await (this.options.sleep ??
|
|
352
|
+
await (this.options.sleep ?? sleepRetry)(delay, signal);
|
|
375
353
|
}
|
|
376
354
|
async waitForConnection(delayMs, signal, telemetry) {
|
|
377
355
|
telemetry.retry(delayMs, 'connection');
|
|
378
356
|
this.options.warn?.(`native Codex network is unavailable; reconnecting in ${delayMs}ms`);
|
|
379
|
-
await (this.options.sleep ??
|
|
357
|
+
await (this.options.sleep ?? sleepRetry)(delayMs, signal);
|
|
380
358
|
}
|
|
381
359
|
endpointUrl() { return this.endpoint; }
|
|
382
360
|
async prepare(generation, mode = {}) {
|
|
@@ -527,7 +505,7 @@ export class NativeCodexHttpTransport {
|
|
|
527
505
|
telemetry.retry(0);
|
|
528
506
|
continue;
|
|
529
507
|
}
|
|
530
|
-
throw fixedFailure('native Codex rejected the configured credential', 'AUTH', errorFacts(response
|
|
508
|
+
throw fixedFailure('native Codex rejected the configured credential', 'AUTH', errorFacts(response));
|
|
531
509
|
}
|
|
532
510
|
catch (error) {
|
|
533
511
|
const connection = connectionFailure(error, watchdog, generation.signal);
|
|
@@ -542,7 +520,7 @@ export class NativeCodexHttpTransport {
|
|
|
542
520
|
}
|
|
543
521
|
}
|
|
544
522
|
if (!response.ok) {
|
|
545
|
-
let failure = await httpFailure(response,
|
|
523
|
+
let failure = await httpFailure(response, watchdog.signal, telemetry);
|
|
546
524
|
if (generation.signal?.aborted || watchdog.timedOut()) {
|
|
547
525
|
failure = mappedFailure(failure, watchdog, generation.signal);
|
|
548
526
|
}
|
|
@@ -3,11 +3,12 @@ import { LlmError, ProviderRequestId } from '@deepseek-ai/dsh-llm';
|
|
|
3
3
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
4
4
|
import { getProxyForUrl } from 'proxy-from-env';
|
|
5
5
|
import { nativeCodexEndpoint } from './endpoint.js';
|
|
6
|
+
import { retryAfterMs, retryError } from './retry-after.js';
|
|
6
7
|
import { NATIVE_CODEX_CONNECTION_FAILED_CODE, isNativeCodexConnectionFailure, } from './native-adapter.js';
|
|
7
8
|
import WebSocket from 'ws';
|
|
8
9
|
const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
|
|
9
10
|
function failure(message, code, cause) {
|
|
10
|
-
return
|
|
11
|
+
return retryError(message, code, cause === undefined ? undefined : { cause });
|
|
11
12
|
}
|
|
12
13
|
function abortFailure(signal) {
|
|
13
14
|
return signal?.reason instanceof LlmError
|
|
@@ -17,9 +18,7 @@ function abortFailure(signal) {
|
|
|
17
18
|
function handshakeFacts(status, headers) {
|
|
18
19
|
const first = (value) => Array.isArray(value) ? value[0] : value;
|
|
19
20
|
const requestId = first(headers['x-request-id']);
|
|
20
|
-
const
|
|
21
|
-
const providerRetryAfterMs = Number.isFinite(retryAfter) && retryAfter > 0
|
|
22
|
-
? Math.min(retryAfter * 1_000, 120_000) : undefined;
|
|
21
|
+
const providerRetryAfterMs = retryAfterMs(first(headers['retry-after']));
|
|
23
22
|
return {
|
|
24
23
|
...(status === undefined ? {} : { status }),
|
|
25
24
|
...(providerRetryAfterMs === undefined ? {} : { providerRetryAfterMs }),
|
|
@@ -202,7 +201,7 @@ export class NodeNativeCodexWebSocketFactory {
|
|
|
202
201
|
? 'WS_RETRYABLE'
|
|
203
202
|
: response.statusCode !== undefined && response.statusCode >= 400
|
|
204
203
|
? 'INVALID_REQUEST' : 'WS_HANDSHAKE';
|
|
205
|
-
reject(
|
|
204
|
+
reject(retryError(`native Codex WebSocket handshake failed (HTTP ${response.statusCode ?? 0})`, code, handshakeFacts(response.statusCode, response.headers)));
|
|
206
205
|
});
|
|
207
206
|
socket.on('open', () => {
|
|
208
207
|
options.signal?.removeEventListener('abort', abort);
|
package/lib/native-websocket.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** WebSocket v2 Responses transport with bounded session state and sticky HTTP fallback. */
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
+
import { retryError, remainingRetryDelay, sleepRetry, jsonRetryAfterMs } from './retry-after.js';
|
|
3
4
|
import { networkTelemetry, trackNetwork, observeNetworkOutput } from './network-telemetry.js';
|
|
4
5
|
import { LlmError, ProviderRequestId, attributionHeaders, } from '@deepseek-ai/dsh-llm';
|
|
5
6
|
import { nativeCodexAuthorityHash } from './catalog.js';
|
|
@@ -22,7 +23,7 @@ const INITIAL_CONNECTION_RETRY_DELAY_MS = 5_000;
|
|
|
22
23
|
const MAX_CONNECTION_RETRY_DELAY_MS = 60_000;
|
|
23
24
|
const MAX_TURN_STATE_BYTES = 4096;
|
|
24
25
|
function failure(message, code, cause) {
|
|
25
|
-
return
|
|
26
|
+
return retryError(message, code, cause === undefined ? undefined : { cause });
|
|
26
27
|
}
|
|
27
28
|
function reconnectable(code) {
|
|
28
29
|
return [
|
|
@@ -36,9 +37,8 @@ function failedStepRetryable(code) {
|
|
|
36
37
|
}
|
|
37
38
|
function failedStepRetry(error) {
|
|
38
39
|
const facts = error.failure;
|
|
39
|
-
const providerRetryAfterMs = facts.providerRetryAfterMs
|
|
40
|
-
|
|
41
|
-
return new LlmError(`native Codex response stream was interrupted: ${error.message}`, NATIVE_CODEX_STREAM_INTERRUPTED_CODE, {
|
|
40
|
+
const providerRetryAfterMs = facts.providerRetryAfterMs;
|
|
41
|
+
return retryError(`native Codex response stream was interrupted: ${error.message}`, NATIVE_CODEX_STREAM_INTERRUPTED_CODE, {
|
|
42
42
|
cause: error,
|
|
43
43
|
...(facts.status === undefined ? {} : { status: facts.status }),
|
|
44
44
|
...(providerRetryAfterMs === undefined ? {} : { providerRetryAfterMs }),
|
|
@@ -65,26 +65,6 @@ function retryCount(value) {
|
|
|
65
65
|
}
|
|
66
66
|
return resolved;
|
|
67
67
|
}
|
|
68
|
-
function sleep(delayMs, signal) {
|
|
69
|
-
if (signal?.aborted) {
|
|
70
|
-
return Promise.reject(failure('native Codex WebSocket retry wait was aborted', 'ABORTED'));
|
|
71
|
-
}
|
|
72
|
-
return new Promise((resolve, reject) => {
|
|
73
|
-
const timer = setTimeout(done, delayMs);
|
|
74
|
-
const abort = () => {
|
|
75
|
-
clearTimeout(timer);
|
|
76
|
-
signal?.removeEventListener('abort', abort);
|
|
77
|
-
const reason = signal?.reason;
|
|
78
|
-
reject(reason instanceof LlmError
|
|
79
|
-
? reason : failure('native Codex WebSocket retry wait was aborted', 'ABORTED'));
|
|
80
|
-
};
|
|
81
|
-
function done() {
|
|
82
|
-
signal?.removeEventListener('abort', abort);
|
|
83
|
-
resolve();
|
|
84
|
-
}
|
|
85
|
-
signal?.addEventListener('abort', abort, { once: true });
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
68
|
function socketCredential(credential) {
|
|
89
69
|
return createHash('sha256')
|
|
90
70
|
.update(credential.accountId).update('\0').update(credential.accessToken)
|
|
@@ -123,15 +103,17 @@ function withRequestMetadata(request, turnState, responsesLite) {
|
|
|
123
103
|
},
|
|
124
104
|
};
|
|
125
105
|
}
|
|
126
|
-
function normalizedOutputItem(event) {
|
|
106
|
+
function normalizedOutputItem(event, translator) {
|
|
127
107
|
if (event.type !== 'response.output_item.done' || event.item === undefined)
|
|
128
108
|
return undefined;
|
|
129
109
|
const item = event.item;
|
|
130
110
|
const id = replayableItemId(item.id);
|
|
131
111
|
if (item.type === 'message') {
|
|
112
|
+
const phase = item.id === undefined ? undefined : translator.messagePhase(item.id);
|
|
132
113
|
return {
|
|
133
114
|
type: 'message', ...(id === undefined ? {} : { id }), role: 'assistant',
|
|
134
115
|
content: (item.content ?? []).map(part => ({ type: 'output_text', text: part.text })),
|
|
116
|
+
...(phase === undefined ? {} : { phase }),
|
|
135
117
|
};
|
|
136
118
|
}
|
|
137
119
|
if (item.type === 'reasoning') {
|
|
@@ -164,10 +146,9 @@ function eventHeader(event, name) {
|
|
|
164
146
|
}
|
|
165
147
|
function eventErrorFacts(event, status) {
|
|
166
148
|
const requestId = eventHeader(event, 'x-request-id');
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
const retryMs =
|
|
170
|
-
? Math.min(seconds * 1_000, 120_000) : undefined;
|
|
149
|
+
const nested = typeof event.error === 'object' && event.error !== null
|
|
150
|
+
? event.error : undefined;
|
|
151
|
+
const retryMs = jsonRetryAfterMs(nested?.headers) ?? jsonRetryAfterMs(event.headers);
|
|
171
152
|
return {
|
|
172
153
|
...(status === undefined ? {} : { status }),
|
|
173
154
|
...(retryMs === undefined ? {} : { providerRetryAfterMs: retryMs }),
|
|
@@ -187,28 +168,28 @@ function eventFailure(event) {
|
|
|
187
168
|
const facts = eventErrorFacts(event, status);
|
|
188
169
|
if (providerCode === 'websocket_connection_limit_reached'
|
|
189
170
|
|| providerCode === 'previous_response_not_found') {
|
|
190
|
-
return
|
|
171
|
+
return retryError('native Codex WebSocket requested a fresh connection', 'WS_RETRYABLE_RESET', facts);
|
|
191
172
|
}
|
|
192
173
|
if (providerCode === 'unauthorized' || providerCode === 'invalid_api_key' || status === 401) {
|
|
193
|
-
return
|
|
174
|
+
return retryError('native Codex WebSocket authentication failed', 'WS_AUTH', facts);
|
|
194
175
|
}
|
|
195
176
|
if (status === 403)
|
|
196
|
-
return
|
|
177
|
+
return retryError('native Codex WebSocket authorization failed', 'AUTH', facts);
|
|
197
178
|
const classified = responsesFailure(providerCode, typeof error.message === 'string' ? error.message : undefined);
|
|
198
179
|
if (classified.code !== 'SERVER') {
|
|
199
180
|
const delay = facts?.providerRetryAfterMs ?? classified.failure.providerRetryAfterMs;
|
|
200
|
-
return
|
|
181
|
+
return retryError(classified.message, classified.code === 'RATE_LIMIT' ? 'RATE_LIMITED' : classified.code, {
|
|
201
182
|
...facts,
|
|
202
|
-
...(delay === undefined ? {} : { providerRetryAfterMs:
|
|
183
|
+
...(delay === undefined ? {} : { providerRetryAfterMs: delay }),
|
|
203
184
|
});
|
|
204
185
|
}
|
|
205
186
|
if (providerCode === 'usage_limit_reached' || status === 429) {
|
|
206
|
-
return
|
|
187
|
+
return retryError('native Codex WebSocket rate limit was reached', 'RATE_LIMITED', facts);
|
|
207
188
|
}
|
|
208
189
|
if (status !== undefined && status >= 500) {
|
|
209
|
-
return
|
|
190
|
+
return retryError('native Codex WebSocket service failed', 'WS_RETRYABLE', facts);
|
|
210
191
|
}
|
|
211
|
-
return
|
|
192
|
+
return retryError('native Codex WebSocket request was rejected', 'INVALID_REQUEST', facts);
|
|
212
193
|
}
|
|
213
194
|
/** Prefer WebSocket v2; once safe retries exhaust, keep that DSH session on HTTP. */
|
|
214
195
|
export class NativeCodexWebSocketTransport {
|
|
@@ -239,9 +220,9 @@ export class NativeCodexWebSocketTransport {
|
|
|
239
220
|
this.maxRetryDelayMs = boundedPositive(options.maxRetryDelayMs, DEFAULT_MAX_RETRY_DELAY_MS, 120_000, 'WebSocket maximum retry delay');
|
|
240
221
|
}
|
|
241
222
|
retryDelay(retry, error) {
|
|
242
|
-
const providerDelay = error
|
|
223
|
+
const providerDelay = remainingRetryDelay(error);
|
|
243
224
|
if (providerDelay !== undefined)
|
|
244
|
-
return
|
|
225
|
+
return providerDelay;
|
|
245
226
|
const exponential = Math.min(this.initialRetryDelayMs * (2 ** retry), this.maxRetryDelayMs);
|
|
246
227
|
const random = this.options.random?.() ?? Math.random();
|
|
247
228
|
const jitter = 0.9 + Math.max(0, Math.min(1, random)) * 0.2;
|
|
@@ -250,12 +231,12 @@ export class NativeCodexWebSocketTransport {
|
|
|
250
231
|
async wait(retry, error, signal, telemetry) {
|
|
251
232
|
const delay = this.retryDelay(retry, error);
|
|
252
233
|
telemetry.retry(delay);
|
|
253
|
-
await (this.options.sleep ??
|
|
234
|
+
await (this.options.sleep ?? sleepRetry)(delay, signal);
|
|
254
235
|
}
|
|
255
236
|
async waitForConnection(delayMs, signal, telemetry) {
|
|
256
237
|
telemetry.retry(delayMs, 'connection');
|
|
257
238
|
this.options.warn?.(`native Codex network is unavailable; reconnecting in ${delayMs}ms`);
|
|
258
|
-
await (this.options.sleep ??
|
|
239
|
+
await (this.options.sleep ?? sleepRetry)(delayMs, signal);
|
|
259
240
|
}
|
|
260
241
|
closeEntry(entry) {
|
|
261
242
|
entry.socket?.close();
|
|
@@ -425,7 +406,8 @@ export class NativeCodexWebSocketTransport {
|
|
|
425
406
|
if (nextTurnState !== undefined)
|
|
426
407
|
entry.turnState = nextTurnState;
|
|
427
408
|
}
|
|
428
|
-
const
|
|
409
|
+
const chunks = translator.push(event);
|
|
410
|
+
const output = normalizedOutputItem(event, translator);
|
|
429
411
|
if (output !== undefined)
|
|
430
412
|
outputItems.push(output);
|
|
431
413
|
if (event.type === 'response.completed') {
|
|
@@ -436,7 +418,6 @@ export class NativeCodexWebSocketTransport {
|
|
|
436
418
|
const responseId = typeof response?.id === 'string' ? response.id : '';
|
|
437
419
|
entry.protocol.complete(responseId, outputItems);
|
|
438
420
|
}
|
|
439
|
-
const chunks = translator.push(event);
|
|
440
421
|
if (!prewarm)
|
|
441
422
|
for (const chunk of chunks)
|
|
442
423
|
yield chunk;
|
|
@@ -617,7 +598,11 @@ export class NativeCodexWebSocketTransport {
|
|
|
617
598
|
}
|
|
618
599
|
if (failureValue.code === 'WS_UPGRADE_REQUIRED' || retryable) {
|
|
619
600
|
entry.disabled = true;
|
|
620
|
-
|
|
601
|
+
if (remainingRetryDelay(failureValue) !== undefined) {
|
|
602
|
+
await this.wait(reconnects, failureValue, signal, telemetry);
|
|
603
|
+
}
|
|
604
|
+
else
|
|
605
|
+
telemetry.retry(0);
|
|
621
606
|
yield* this.http.stream(activeGeneration, fallbackMode(), telemetry);
|
|
622
607
|
requestCompleted = true;
|
|
623
608
|
return;
|
package/lib/replay.d.ts
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
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;
|
|
5
|
+
export type CodexMessagePhase = 'commentary' | 'partial_answer' | 'final_answer';
|
|
6
|
+
export declare function isCodexMessagePhase(value: unknown): value is CodexMessagePhase;
|
|
5
7
|
export type NativeCodexReplayDescriptor = {
|
|
6
8
|
type: 'message';
|
|
7
9
|
id?: string;
|
|
8
10
|
blocks: number[];
|
|
11
|
+
phase?: CodexMessagePhase;
|
|
9
12
|
} | {
|
|
10
13
|
type: 'reasoning';
|
|
11
14
|
id?: string;
|
package/lib/replay.js
CHANGED
|
@@ -2,6 +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
|
+
export function isCodexMessagePhase(value) {
|
|
6
|
+
return value === 'commentary' || value === 'partial_answer' || value === 'final_answer';
|
|
7
|
+
}
|
|
5
8
|
function failure(message, code = 'INVALID_REPLAY_STATE') {
|
|
6
9
|
return new LlmError(message, code);
|
|
7
10
|
}
|
|
@@ -55,10 +58,12 @@ function parseDescriptor(value) {
|
|
|
55
58
|
}
|
|
56
59
|
if (row.type === 'message') {
|
|
57
60
|
const blocks = validateBlockArray(row.blocks);
|
|
58
|
-
if (blocks === undefined ||
|
|
61
|
+
if (blocks === undefined || (row.phase !== undefined && !isCodexMessagePhase(row.phase))
|
|
62
|
+
|| !onlyKeys(row, ['type', 'id', 'blocks', 'phase'])) {
|
|
59
63
|
throw failure('native Codex message replay descriptor is invalid');
|
|
60
64
|
}
|
|
61
|
-
return { type: 'message', ...(id === undefined ? {} : { id }), blocks
|
|
65
|
+
return { type: 'message', ...(id === undefined ? {} : { id }), blocks,
|
|
66
|
+
...(row.phase === undefined ? {} : { phase: row.phase }) };
|
|
62
67
|
}
|
|
63
68
|
if (row.type === 'reasoning') {
|
|
64
69
|
const blocks = validateBlockArray(row.blocks);
|
|
@@ -179,6 +184,7 @@ export function replayAssistantInput(content, source) {
|
|
|
179
184
|
input.push({
|
|
180
185
|
type: 'message', ...(item.id === undefined ? {} : { id: item.id }),
|
|
181
186
|
role: 'assistant', content: parts,
|
|
187
|
+
...(item.phase === undefined ? {} : { phase: item.phase }),
|
|
182
188
|
});
|
|
183
189
|
}
|
|
184
190
|
else if (item.type === 'reasoning') {
|
package/lib/responses.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ToolCallId, LlmError, type ContentBlock, type GenerateOptions, type StreamChunk, type TokenUsage, type ToolSchema } from '@deepseek-ai/dsh-llm';
|
|
2
2
|
import { type ParseSseOptions } from './sse.js';
|
|
3
|
-
import { type NativeCodexReplaySource } from './replay.js';
|
|
3
|
+
import { type CodexMessagePhase, type NativeCodexReplaySource } from './replay.js';
|
|
4
4
|
export declare const DEFAULT_CODEX_INSTRUCTIONS = "You are Codex, an AI coding agent. Help the user with software engineering tasks.";
|
|
5
5
|
export type CodexImageDetail = 'auto' | 'low' | 'high' | 'original';
|
|
6
6
|
export interface ResolvedImagePart {
|
|
@@ -53,7 +53,7 @@ export interface ResponsesUsage {
|
|
|
53
53
|
/** Map provider totals to DSH's strict disjoint token counts. */
|
|
54
54
|
export declare function mapResponsesUsage(usage: ResponsesUsage): TokenUsage;
|
|
55
55
|
/** Classify in-band failure data without reflecting provider text. */
|
|
56
|
-
export declare function responsesFailure(code?: string, message?: string): LlmError;
|
|
56
|
+
export declare function responsesFailure(code?: string, message?: string, headers?: unknown): LlmError;
|
|
57
57
|
interface ResponsesOutputItem {
|
|
58
58
|
type?: string;
|
|
59
59
|
id?: string;
|
|
@@ -64,6 +64,7 @@ interface ResponsesOutputItem {
|
|
|
64
64
|
encrypted_content?: string;
|
|
65
65
|
summary?: unknown[];
|
|
66
66
|
status?: string;
|
|
67
|
+
phase?: CodexMessagePhase | null;
|
|
67
68
|
content?: Array<{
|
|
68
69
|
type?: string;
|
|
69
70
|
text?: string;
|
|
@@ -86,6 +87,7 @@ export interface ResponsesStreamEvent {
|
|
|
86
87
|
error?: {
|
|
87
88
|
code?: string;
|
|
88
89
|
message?: string;
|
|
90
|
+
headers?: unknown;
|
|
89
91
|
};
|
|
90
92
|
incomplete_details?: {
|
|
91
93
|
reason?: string;
|
|
@@ -103,11 +105,15 @@ export declare class ResponsesStreamTranslator {
|
|
|
103
105
|
private readonly replayContext?;
|
|
104
106
|
private readonly blocks;
|
|
105
107
|
private readonly order;
|
|
108
|
+
private readonly messagePhases;
|
|
106
109
|
private readonly replayCapture;
|
|
107
110
|
private nextIndex;
|
|
108
111
|
private sawToolCall;
|
|
109
112
|
terminated: boolean;
|
|
110
113
|
constructor(replayContext?: ResponsesReplayContext | undefined);
|
|
114
|
+
/** The same effective phase feeds durable replay and WebSocket prefix comparison. */
|
|
115
|
+
messagePhase(id: string): CodexMessagePhase | undefined;
|
|
116
|
+
private captureMessagePhase;
|
|
111
117
|
private append;
|
|
112
118
|
private fill;
|
|
113
119
|
private open;
|
package/lib/responses.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { ToolCallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE, } from '@deepseek-ai/dsh-llm';
|
|
4
4
|
import { parseSse } from './sse.js';
|
|
5
|
-
import {
|
|
5
|
+
import { jsonRetryAfterMs, retryError } from './retry-after.js';
|
|
6
|
+
import { NativeCodexReplayCapture, isCodexMessagePhase, replayAssistantInput, replayableItemId, } from './replay.js';
|
|
6
7
|
export const DEFAULT_CODEX_INSTRUCTIONS = 'You are Codex, an AI coding agent. Help the user with software engineering tasks.';
|
|
7
8
|
const CALL_ID_MAX_LENGTH = 64;
|
|
8
9
|
const CALL_ID_PREFIX = 'call_';
|
|
@@ -211,11 +212,21 @@ export function codexRequestBody(options, messages, mode = {}) {
|
|
|
211
212
|
...mode.serviceTier === undefined ? {} : { service_tier: mode.serviceTier },
|
|
212
213
|
...options.sessionId === undefined ? {} : { prompt_cache_key: String(options.sessionId) },
|
|
213
214
|
};
|
|
215
|
+
const baseInstructions = mode.responsesLite?.instructionsTemplate ?? instructions;
|
|
216
|
+
const prefixNamespace = uuidBytes(uuidV5(UUID_NAMESPACE_OID, String(options.sessionId ?? options.model)));
|
|
217
|
+
const instructionPrefix = baseInstructions.length === 0 ? [] : [{
|
|
218
|
+
type: 'message',
|
|
219
|
+
id: `msg_${uuidV5(prefixNamespace, baseInstructions)}`,
|
|
220
|
+
role: 'developer',
|
|
221
|
+
content: [{ type: 'input_text', text: baseInstructions }],
|
|
222
|
+
internal_chat_message_metadata_passthrough: {
|
|
223
|
+
content_item_kinds: ['model.base_instructions'],
|
|
224
|
+
},
|
|
225
|
+
}];
|
|
214
226
|
if (mode.responsesLite === undefined) {
|
|
215
227
|
return {
|
|
216
228
|
...common,
|
|
217
|
-
|
|
218
|
-
input,
|
|
229
|
+
input: [...instructionPrefix, ...input],
|
|
219
230
|
...options.tools !== undefined && options.tools.length > 0
|
|
220
231
|
? { tools: toResponsesTools(options.tools) } : {},
|
|
221
232
|
parallel_tool_calls: true,
|
|
@@ -224,7 +235,6 @@ export function codexRequestBody(options, messages, mode = {}) {
|
|
|
224
235
|
};
|
|
225
236
|
}
|
|
226
237
|
const tools = toResponsesLiteTools(options.tools ?? []);
|
|
227
|
-
const baseInstructions = mode.responsesLite.instructionsTemplate ?? instructions;
|
|
228
238
|
const contextualInstructions = mode.responsesLite.instructionsTemplate !== undefined
|
|
229
239
|
&& resolved.instructions !== undefined && resolved.instructions !== baseInstructions
|
|
230
240
|
? [{
|
|
@@ -232,21 +242,12 @@ export function codexRequestBody(options, messages, mode = {}) {
|
|
|
232
242
|
content: [{ type: 'input_text', text: resolved.instructions }],
|
|
233
243
|
}]
|
|
234
244
|
: [];
|
|
235
|
-
const prefixNamespace = uuidBytes(uuidV5(UUID_NAMESPACE_OID, String(options.sessionId ?? options.model)));
|
|
236
245
|
const prefix = [{
|
|
237
246
|
type: 'additional_tools',
|
|
238
247
|
id: `at_${uuidV5(prefixNamespace, JSON.stringify(tools))}`,
|
|
239
248
|
role: 'developer',
|
|
240
249
|
tools,
|
|
241
|
-
},
|
|
242
|
-
type: 'message',
|
|
243
|
-
id: `msg_${uuidV5(prefixNamespace, baseInstructions)}`,
|
|
244
|
-
role: 'developer',
|
|
245
|
-
content: [{ type: 'input_text', text: baseInstructions }],
|
|
246
|
-
internal_chat_message_metadata_passthrough: {
|
|
247
|
-
content_item_kinds: ['model.base_instructions'],
|
|
248
|
-
},
|
|
249
|
-
}];
|
|
250
|
+
}, ...instructionPrefix];
|
|
250
251
|
return {
|
|
251
252
|
...common,
|
|
252
253
|
input: [...prefix, ...contextualInstructions, ...input],
|
|
@@ -293,10 +294,11 @@ function retryDelay(message) {
|
|
|
293
294
|
const value = Number(match[1]);
|
|
294
295
|
if (!Number.isFinite(value) || value <= 0)
|
|
295
296
|
return undefined;
|
|
296
|
-
|
|
297
|
+
const delay = match[2]?.toLowerCase() === 'ms' ? value : value * 1000;
|
|
298
|
+
return Number.isFinite(delay) && delay <= Number.MAX_SAFE_INTEGER ? delay : undefined;
|
|
297
299
|
}
|
|
298
300
|
/** Classify in-band failure data without reflecting provider text. */
|
|
299
|
-
export function responsesFailure(code, message) {
|
|
301
|
+
export function responsesFailure(code, message, headers) {
|
|
300
302
|
const detail = `${code ?? ''} ${message ?? ''}`;
|
|
301
303
|
if (code === 'context_length_exceeded' || code === 'context_window_exceeded'
|
|
302
304
|
|| isContextWindowExceededError(detail)) {
|
|
@@ -308,8 +310,8 @@ export function responsesFailure(code, message) {
|
|
|
308
310
|
return fixedError('native Codex account quota is exhausted', QUOTA_EXCEEDED_CODE);
|
|
309
311
|
}
|
|
310
312
|
if (code === 'rate_limit_exceeded' || code === 'slow_down') {
|
|
311
|
-
const delay = retryDelay(message);
|
|
312
|
-
return
|
|
313
|
+
const delay = jsonRetryAfterMs(headers) ?? retryDelay(message);
|
|
314
|
+
return retryError('native Codex request was rate limited', 'RATE_LIMIT', {
|
|
313
315
|
...delay === undefined ? {} : { providerRetryAfterMs: delay },
|
|
314
316
|
});
|
|
315
317
|
}
|
|
@@ -319,7 +321,10 @@ export function responsesFailure(code, message) {
|
|
|
319
321
|
if (code === 'bio_policy') {
|
|
320
322
|
return fixedError('native Codex rejected the request', 'INVALID_REQUEST');
|
|
321
323
|
}
|
|
322
|
-
|
|
324
|
+
const delay = jsonRetryAfterMs(headers);
|
|
325
|
+
return retryError('native Codex reported a failed response', 'SERVER', {
|
|
326
|
+
...delay === undefined ? {} : { providerRetryAfterMs: delay },
|
|
327
|
+
});
|
|
323
328
|
}
|
|
324
329
|
function eventItemId(event) {
|
|
325
330
|
if (typeof event.item_id === 'string' && event.item_id.length > 0)
|
|
@@ -343,6 +348,7 @@ export class ResponsesStreamTranslator {
|
|
|
343
348
|
replayContext;
|
|
344
349
|
blocks = new Map();
|
|
345
350
|
order = [];
|
|
351
|
+
messagePhases = new Map();
|
|
346
352
|
replayCapture;
|
|
347
353
|
nextIndex = 0;
|
|
348
354
|
sawToolCall = false;
|
|
@@ -353,6 +359,18 @@ export class ResponsesStreamTranslator {
|
|
|
353
359
|
? undefined
|
|
354
360
|
: new NativeCodexReplayCapture(replayContext.provider, replayContext.model);
|
|
355
361
|
}
|
|
362
|
+
/** The same effective phase feeds durable replay and WebSocket prefix comparison. */
|
|
363
|
+
messagePhase(id) {
|
|
364
|
+
return this.messagePhases.get(id);
|
|
365
|
+
}
|
|
366
|
+
captureMessagePhase(item) {
|
|
367
|
+
if (item.phase === undefined || item.phase === null)
|
|
368
|
+
return;
|
|
369
|
+
if (!isCodexMessagePhase(item.phase) || typeof item.id !== 'string' || item.id.length === 0) {
|
|
370
|
+
throw fixedError('native Codex message phase is invalid', 'MALFORMED_RESPONSE');
|
|
371
|
+
}
|
|
372
|
+
this.messagePhases.set(item.id, item.phase);
|
|
373
|
+
}
|
|
356
374
|
append(block, delta) {
|
|
357
375
|
block.text += delta;
|
|
358
376
|
}
|
|
@@ -400,6 +418,8 @@ export class ResponsesStreamTranslator {
|
|
|
400
418
|
switch (event.type) {
|
|
401
419
|
case 'response.output_item.added': {
|
|
402
420
|
const item = event.item;
|
|
421
|
+
if (item?.type === 'message')
|
|
422
|
+
this.captureMessagePhase(item);
|
|
403
423
|
if (item?.type !== 'function_call')
|
|
404
424
|
return chunks;
|
|
405
425
|
if (item.id === undefined || item.id.length === 0
|
|
@@ -483,6 +503,8 @@ export class ResponsesStreamTranslator {
|
|
|
483
503
|
});
|
|
484
504
|
}
|
|
485
505
|
else if (item.type === 'message') {
|
|
506
|
+
this.captureMessagePhase(item);
|
|
507
|
+
const phase = this.messagePhase(item.id);
|
|
486
508
|
if (!Array.isArray(item.content)) {
|
|
487
509
|
throw fixedError('native Codex message item has invalid content', 'MALFORMED_RESPONSE');
|
|
488
510
|
}
|
|
@@ -507,6 +529,7 @@ export class ResponsesStreamTranslator {
|
|
|
507
529
|
if (this.replayContext !== undefined)
|
|
508
530
|
this.replayCapture?.add({
|
|
509
531
|
type: 'message', ...(replayId === undefined ? {} : { id: replayId }), blocks: refs,
|
|
532
|
+
...(phase === undefined ? {} : { phase }),
|
|
510
533
|
});
|
|
511
534
|
}
|
|
512
535
|
else if (item.type === 'reasoning') {
|
|
@@ -595,7 +618,7 @@ export class ResponsesStreamTranslator {
|
|
|
595
618
|
return chunks;
|
|
596
619
|
}
|
|
597
620
|
case 'response.failed':
|
|
598
|
-
throw responsesFailure(event.response?.error?.code, event.response?.error?.message);
|
|
621
|
+
throw responsesFailure(event.response?.error?.code, event.response?.error?.message, event.response?.error?.headers);
|
|
599
622
|
case 'error':
|
|
600
623
|
throw responsesFailure(event.code, event.message);
|
|
601
624
|
default:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Server advice is separate from the client's bounded exponential backoff. */
|
|
2
|
+
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
export declare function retryAfterMs(raw: unknown): number | undefined;
|
|
4
|
+
export declare function jsonRetryAfterMs(headers: unknown): number | undefined;
|
|
5
|
+
export declare function retryError(message: string, code: string, options?: ConstructorParameters<typeof LlmError>[2]): LlmError;
|
|
6
|
+
export declare function inheritRetryDeadline(error: LlmError, source: LlmError): LlmError;
|
|
7
|
+
export declare function remainingRetryDelay(error: LlmError): number | undefined;
|
|
8
|
+
/** Node timers overflow above 2^31-1; chunk long waits without shortening advice. */
|
|
9
|
+
export declare function sleepRetry(delay: number, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** Server advice is separate from the client's bounded exponential backoff. */
|
|
2
|
+
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
const deadlines = new WeakMap();
|
|
4
|
+
function httpDate(value) {
|
|
5
|
+
const standard = /^([A-Za-z]{3}), ([0-9]{2}) ([A-Za-z]{3}) ([0-9]{4}) ([0-9]{2}:[0-9]{2}:[0-9]{2}) GMT$/.exec(value);
|
|
6
|
+
const obsolete = /^([A-Za-z]+), ([0-9]{2})-([A-Za-z]{3})-([0-9]{2}) ([0-9]{2}:[0-9]{2}:[0-9]{2}) GMT$/.exec(value);
|
|
7
|
+
const ascii = /^([A-Za-z]{3}) ([A-Za-z]{3}) ([ 0-9][0-9]) ([0-9]{2}:[0-9]{2}:[0-9]{2}) ([0-9]{4})$/.exec(value);
|
|
8
|
+
const parts = standard ?? obsolete;
|
|
9
|
+
const weekday = parts?.[1] ?? ascii?.[1];
|
|
10
|
+
const day = parts?.[2] ?? ascii?.[3];
|
|
11
|
+
const month = parts?.[3] ?? ascii?.[2];
|
|
12
|
+
const year = parts?.[4] ?? ascii?.[5];
|
|
13
|
+
const time = parts?.[5] ?? ascii?.[4];
|
|
14
|
+
if (!weekday || !day || !month || !year || !time)
|
|
15
|
+
return undefined;
|
|
16
|
+
const timestamp = Date.parse(value.endsWith('GMT') ? value : value + ' GMT');
|
|
17
|
+
if (!Number.isFinite(timestamp))
|
|
18
|
+
return undefined;
|
|
19
|
+
const date = new Date(timestamp);
|
|
20
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
21
|
+
const weekdays = obsolete
|
|
22
|
+
? ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
|
23
|
+
: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
24
|
+
// Date.parse normalizes impossible dates; HTTP header parsing must reject them.
|
|
25
|
+
if (date.getUTCDate() !== Number(day) || months[date.getUTCMonth()] !== month
|
|
26
|
+
|| weekdays[date.getUTCDay()] !== weekday
|
|
27
|
+
|| (obsolete ? date.getUTCFullYear() % 100 : date.getUTCFullYear()) !== Number(year)
|
|
28
|
+
|| date.toISOString().slice(11, 19) !== time)
|
|
29
|
+
return undefined;
|
|
30
|
+
return timestamp;
|
|
31
|
+
}
|
|
32
|
+
export function retryAfterMs(raw) {
|
|
33
|
+
if (typeof raw !== 'string' && typeof raw !== 'number')
|
|
34
|
+
return undefined;
|
|
35
|
+
const text = String(raw);
|
|
36
|
+
if (/[^\t\x20-\x7e]/.test(text))
|
|
37
|
+
return undefined;
|
|
38
|
+
const value = text.trim();
|
|
39
|
+
let delay;
|
|
40
|
+
if (/^\d+$/.test(value))
|
|
41
|
+
delay = Number(value) * 1000;
|
|
42
|
+
else {
|
|
43
|
+
const timestamp = httpDate(value);
|
|
44
|
+
if (timestamp === undefined)
|
|
45
|
+
return undefined;
|
|
46
|
+
delay = Math.max(0, timestamp - Date.now());
|
|
47
|
+
}
|
|
48
|
+
return Number.isSafeInteger(delay) && delay >= 0 ? delay : undefined;
|
|
49
|
+
}
|
|
50
|
+
export function jsonRetryAfterMs(headers) {
|
|
51
|
+
if (typeof headers !== 'object' || headers === null || Array.isArray(headers))
|
|
52
|
+
return undefined;
|
|
53
|
+
let value;
|
|
54
|
+
// serde_json's ordered map inserts later case variants over earlier ones.
|
|
55
|
+
for (const key of Object.keys(headers).sort()) {
|
|
56
|
+
const raw = headers[key];
|
|
57
|
+
if (key.toLowerCase() === 'retry-after'
|
|
58
|
+
&& (typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean')
|
|
59
|
+
&& !/[^\t\x20-\x7e]/.test(String(raw)))
|
|
60
|
+
value = raw;
|
|
61
|
+
}
|
|
62
|
+
return retryAfterMs(value);
|
|
63
|
+
}
|
|
64
|
+
export function retryError(message, code, options) {
|
|
65
|
+
const delay = options?.providerRetryAfterMs;
|
|
66
|
+
// The host permits only positive duration metadata; retain zero advice locally.
|
|
67
|
+
const normalized = { ...options };
|
|
68
|
+
if (delay === 0)
|
|
69
|
+
delete normalized.providerRetryAfterMs;
|
|
70
|
+
const error = new LlmError(message, code, normalized);
|
|
71
|
+
const inherited = options?.cause instanceof LlmError ? deadlines.get(options.cause) : undefined;
|
|
72
|
+
if (inherited !== undefined || delay !== undefined) {
|
|
73
|
+
deadlines.set(error, inherited ?? performance.now() + delay);
|
|
74
|
+
}
|
|
75
|
+
return error;
|
|
76
|
+
}
|
|
77
|
+
export function inheritRetryDeadline(error, source) {
|
|
78
|
+
const deadline = deadlines.get(source);
|
|
79
|
+
if (deadline !== undefined)
|
|
80
|
+
deadlines.set(error, deadline);
|
|
81
|
+
return error;
|
|
82
|
+
}
|
|
83
|
+
export function remainingRetryDelay(error) {
|
|
84
|
+
let deadline = deadlines.get(error);
|
|
85
|
+
if (deadline === undefined) {
|
|
86
|
+
const delay = error.failure.providerRetryAfterMs;
|
|
87
|
+
if (delay === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
deadline = performance.now() + delay;
|
|
90
|
+
deadlines.set(error, deadline);
|
|
91
|
+
}
|
|
92
|
+
return Math.max(0, Math.ceil(deadline - performance.now()));
|
|
93
|
+
}
|
|
94
|
+
/** Node timers overflow above 2^31-1; chunk long waits without shortening advice. */
|
|
95
|
+
export async function sleepRetry(delay, signal) {
|
|
96
|
+
const deadline = performance.now() + delay;
|
|
97
|
+
do {
|
|
98
|
+
if (signal?.aborted)
|
|
99
|
+
throw signal.reason instanceof LlmError ? signal.reason : new LlmError('native Codex retry wait was aborted', 'ABORTED');
|
|
100
|
+
const remaining = Math.max(0, Math.ceil(deadline - performance.now()));
|
|
101
|
+
if (remaining === 0)
|
|
102
|
+
return;
|
|
103
|
+
await new Promise((resolve, reject) => {
|
|
104
|
+
const abort = () => { clearTimeout(timer); reject(signal?.reason instanceof LlmError ? signal.reason : new LlmError('native Codex retry wait was aborted', 'ABORTED')); };
|
|
105
|
+
const timer = setTimeout(() => { signal?.removeEventListener('abort', abort); resolve(); }, Math.min(remaining, 2_147_483_647));
|
|
106
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
107
|
+
});
|
|
108
|
+
} while (true);
|
|
109
|
+
}
|
package/lib/upstream.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export declare const TRACKED_CODEX_COMMIT = "ddf04ad26789d040f9ef6a96736f76602e3
|
|
|
4
4
|
export declare const TRACKED_CODEX_RELEASE = "main@ddf04ad";
|
|
5
5
|
/** Independently audited cloud search/image contracts; not local Codex executor parity. */
|
|
6
6
|
export declare const TRACKED_CODEX_CLOUD_COMMIT = "b348fc26674189f758d5941cdab3f78f258b2aa7";
|
|
7
|
-
/** Whole
|
|
8
|
-
export declare const CODEX_CLIENT_VERSION = "0.
|
|
7
|
+
/** Whole client version sent to the catalog; live gating can exceed bundled model minima. */
|
|
8
|
+
export declare const CODEX_CLIENT_VERSION = "0.162.0";
|
package/lib/upstream.js
CHANGED
|
@@ -4,5 +4,5 @@ export const TRACKED_CODEX_COMMIT = 'ddf04ad26789d040f9ef6a96736f76602e35a6cc';
|
|
|
4
4
|
export const TRACKED_CODEX_RELEASE = 'main@ddf04ad';
|
|
5
5
|
/** Independently audited cloud search/image contracts; not local Codex executor parity. */
|
|
6
6
|
export const TRACKED_CODEX_CLOUD_COMMIT = 'b348fc26674189f758d5941cdab3f78f258b2aa7';
|
|
7
|
-
/** Whole
|
|
8
|
-
export const CODEX_CLIENT_VERSION = '0.
|
|
7
|
+
/** Whole client version sent to the catalog; live gating can exceed bundled model minima. */
|
|
8
|
+
export const CODEX_CLIENT_VERSION = '0.162.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pure01fx/dsh-openai-codex-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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": {
|
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
"lib/responses.d.ts",
|
|
54
54
|
"lib/replay.js",
|
|
55
55
|
"lib/replay.d.ts",
|
|
56
|
+
"lib/retry-after.js",
|
|
57
|
+
"lib/retry-after.d.ts",
|
|
56
58
|
"lib/rate-limits.js",
|
|
57
59
|
"lib/rate-limits.d.ts",
|
|
58
60
|
"lib/response-usage.js",
|
|
@@ -96,7 +98,7 @@
|
|
|
96
98
|
},
|
|
97
99
|
"dsh": {
|
|
98
100
|
"engines": {
|
|
99
|
-
"dsh": "0.
|
|
101
|
+
"dsh": "0.2.0-rc.2"
|
|
100
102
|
},
|
|
101
103
|
"bundle": {
|
|
102
104
|
"patch": "./cordis.patch.yml"
|
|
@@ -113,19 +115,19 @@
|
|
|
113
115
|
},
|
|
114
116
|
"peerDependencies": {
|
|
115
117
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
116
|
-
"@deepseek-ai/dsh-attachment": "0.
|
|
117
|
-
"@deepseek-ai/dsh-credentials": "0.
|
|
118
|
-
"@deepseek-ai/dsh-fs": "0.
|
|
119
|
-
"@deepseek-ai/dsh-host-webserver": "0.
|
|
120
|
-
"@deepseek-ai/dsh-llm": "0.
|
|
121
|
-
"@deepseek-ai/dsh-sandbox": "0.
|
|
122
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.
|
|
123
|
-
"@deepseek-ai/dsh-shell": "0.
|
|
124
|
-
"@deepseek-ai/dsh-tools": "0.
|
|
118
|
+
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
|
|
119
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
120
|
+
"@deepseek-ai/dsh-fs": "0.2.0-rc.2",
|
|
121
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
|
|
122
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
123
|
+
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
|
|
124
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
|
|
125
|
+
"@deepseek-ai/dsh-shell": "0.2.0-rc.2",
|
|
126
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2"
|
|
125
127
|
},
|
|
126
128
|
"dependencies": {
|
|
127
|
-
"@deepseek-ai/dsh-atomic-write": "0.
|
|
128
|
-
"@deepseek-ai/dsh-home-paths": "0.
|
|
129
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2",
|
|
130
|
+
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
|
|
129
131
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
130
132
|
"https-proxy-agent": "7.0.6",
|
|
131
133
|
"image-size": "^2.0.2",
|
|
@@ -134,23 +136,23 @@
|
|
|
134
136
|
},
|
|
135
137
|
"devDependencies": {
|
|
136
138
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
137
|
-
"@deepseek-ai/dsh-agent": "0.
|
|
138
|
-
"@deepseek-ai/dsh-attachment": "0.
|
|
139
|
-
"@deepseek-ai/dsh-attachment-local": "0.
|
|
140
|
-
"@deepseek-ai/dsh-client-store": "0.
|
|
141
|
-
"@deepseek-ai/dsh-client-ui-model-selection": "0.
|
|
142
|
-
"@deepseek-ai/dsh-credentials": "0.
|
|
143
|
-
"@deepseek-ai/dsh-fs": "0.
|
|
144
|
-
"@deepseek-ai/dsh-host-webserver": "0.
|
|
145
|
-
"@deepseek-ai/dsh-llm": "0.
|
|
146
|
-
"@deepseek-ai/dsh-sandbox": "0.
|
|
147
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.
|
|
148
|
-
"@deepseek-ai/dsh-scope": "0.
|
|
149
|
-
"@deepseek-ai/dsh-session": "0.
|
|
150
|
-
"@deepseek-ai/dsh-shell": "0.
|
|
151
|
-
"@deepseek-ai/dsh-system-prompt": "0.
|
|
152
|
-
"@deepseek-ai/dsh-tools": "0.
|
|
153
|
-
"@deepseek-ai/dsh-web-frontend": "0.
|
|
139
|
+
"@deepseek-ai/dsh-agent": "0.2.0-rc.2",
|
|
140
|
+
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
|
|
141
|
+
"@deepseek-ai/dsh-attachment-local": "0.2.0-rc.2",
|
|
142
|
+
"@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
|
|
143
|
+
"@deepseek-ai/dsh-client-ui-model-selection": "0.2.0-rc.2",
|
|
144
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
145
|
+
"@deepseek-ai/dsh-fs": "0.2.0-rc.2",
|
|
146
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
|
|
147
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
148
|
+
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
|
|
149
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
|
|
150
|
+
"@deepseek-ai/dsh-scope": "0.2.0-rc.2",
|
|
151
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
152
|
+
"@deepseek-ai/dsh-shell": "0.2.0-rc.2",
|
|
153
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
|
|
154
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2",
|
|
155
|
+
"@deepseek-ai/dsh-web-frontend": "0.2.0-rc.2",
|
|
154
156
|
"@types/node": "^22.20.0",
|
|
155
157
|
"@types/proxy-from-env": "1.0.4",
|
|
156
158
|
"@types/ws": "8.18.1",
|