dsh-codex-connect 0.1.0-alpha.4.37 → 0.1.0-alpha.4.38

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/INSTALL.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Installation Runbook for CLI Agents
2
2
 
3
- Published Alpha 4.35 is verified with DSH `0.1.2-rc.1` and pi-ai `0.84.4` within `^0.84.2`, and with each exact DSH `0.1.5-alpha.1`, `0.1.5-rc.1`, and `0.1.5-rc.2` model-runtime pairing using pi-ai `0.85.1`.
3
+ Published Alpha 4.37 is verified with DSH `0.1.2-rc.1` and pi-ai `0.84.4` within `^0.84.2`, and with each exact DSH `0.1.5-alpha.1`, `0.1.5-rc.1`, and `0.1.5-rc.2` model-runtime pairing using pi-ai `0.85.1`.
4
4
 
5
5
  Install `dsh-codex-connect` into one requested DeepSeek Harness profile without changing its current default model, search route, global configuration, or OAuth state.
6
6
 
7
- Channel snapshot on 2026-09-11: npm `alpha` points to `0.1.0-alpha.4.35`; `latest` intentionally remains on `0.1.0-alpha.4.34`. Use the exact-version commands below for 4.35. Publishing an Alpha and promoting the default installation channel are separate actions.
7
+ Channel snapshot on 2026-09-19: npm `alpha` points to `0.1.0-alpha.4.37`; `latest` intentionally remains on `0.1.0-alpha.4.34`. Use the exact-version commands below for 4.37. Publishing an Alpha and promoting the default installation channel are separate actions.
8
8
 
9
9
  ## Safety requirements
10
10
 
@@ -25,15 +25,19 @@ Check `dsh --version` before changing the requested profile. Use `dsh --help` to
25
25
  | `0.1.0-rc.7` | `0.1.0-alpha.4.14` |
26
26
  | `0.1.1-rc.2` | `0.1.0-alpha.4.21` |
27
27
  | `0.1.2-alpha.2` | `0.1.0-alpha.4.23` |
28
- | `0.1.2-rc.1` | `0.1.0-alpha.4.35` |
28
+ | `0.1.2-rc.1` | `0.1.0-alpha.4.37` |
29
29
  | `0.1.2-alpha.5` | `0.1.0-alpha.4.25` |
30
- | `0.1.5-alpha.1` | `0.1.0-alpha.4.35` |
31
- | `0.1.5-rc.1` | `0.1.0-alpha.4.35` |
32
- | `0.1.5-rc.2` | `0.1.0-alpha.4.35` |
30
+ | `0.1.5-alpha.1` | `0.1.0-alpha.4.37` |
31
+ | `0.1.5-rc.1` | `0.1.0-alpha.4.37` |
32
+ | `0.1.5-rc.2` | `0.1.0-alpha.4.37` |
33
33
 
34
34
  If your exact DSH version is unknown or not listed, preserve the installed host, report that the combination is unverified, and verify it before making installation changes. A missing record does not prove incompatibility, and the catalog's latest verified DSH version is not the latest upstream release. Do not recommend upgrading or downgrading DSH merely to match a row. Investigate any specific failure and seek verification of the installed combination. Do not blindly install `dsh-codex-connect@alpha`: `alpha` is a moving tag, not a compatibility guarantee. Do not infer support for newer DSH versions from these rows.
35
35
 
36
- Alpha 4.35 requires one consistent DSH plugin API version: `0.1.2-rc.1` with `@earendil-works/pi-ai` `^0.84.2`, or one of `0.1.5-alpha.1`, `0.1.5-rc.1`, and `0.1.5-rc.2` with pi-ai `0.85.1`; Node.js remains `^22.19.0 || >=24.0.0`. Mixed host versions and other DSH/pi-ai combinations remain unverified. Alpha 4.25 remains the verified choice for DSH `0.1.2-alpha.5`, Alpha 4.23 remains the verified choice for DSH `0.1.2-alpha.2`, Alpha 4.21 remains the verified choice for DSH `0.1.1-rc.2`, and staying on DSH `0.1.0-rc.7` means selecting Alpha 4.14. Changing DSH is a separate operation requiring the user's explicit request; a plugin update request does not authorize it. The repository's `pnpm --silent run check:compatibility` remains a strict development/release dependency gate, not a recommendation to change a user's host.
36
+ Alpha 4.37 requires one consistent DSH plugin API version: `0.1.2-rc.1` with `@earendil-works/pi-ai` `^0.84.2`, or one of `0.1.5-alpha.1`, `0.1.5-rc.1`, and `0.1.5-rc.2` with pi-ai `0.85.1`; Node.js remains `^22.19.0 || >=24.0.0`. Mixed host versions and other DSH/pi-ai combinations remain unverified. Alpha 4.25 remains the verified choice for DSH `0.1.2-alpha.5`, Alpha 4.23 remains the verified choice for DSH `0.1.2-alpha.2`, Alpha 4.21 remains the verified choice for DSH `0.1.1-rc.2`, and staying on DSH `0.1.0-rc.7` means selecting Alpha 4.14. Changing DSH is a separate operation requiring the user's explicit request; a plugin update request does not authorize it. The repository's `pnpm --silent run check:compatibility` remains a strict development/release dependency gate, not a recommendation to change a user's host.
37
+
38
+ The Alpha 4.37 recommendation follows successful exact-release main CI, 975 local tests, 32 Chromium tests, and a four-host same-artifact installation matrix with 40 fresh native-compaction lifecycle processes. Independent post-publication download verified that npm's archive is byte-identical to the tested artifact and the release workflow's verified package; the Git tag resolves to the exact release commit. These are synthetic-provider installation/lifecycle checks, not new real-account acceptance or a newly exercised published-package upgrade. See [.github/ALPHA_437_PUBLICATION.md](.github/ALPHA_437_PUBLICATION.md). Native context management remains opt-in. The preview-only DSH checkpoint-retry patch is not installed by this plugin, and stock DSH 0.1.6-alpha.2 remains undeclared.
39
+
40
+ Historical Alpha 4.35 evidence (not relabeled as 4.37):
37
41
 
38
42
  The Alpha 4.35 rows reflect successful release-commit CI on Node 22.19.0 and 24.20.0 (840 tests each), 28 Chromium tests, Windows canary contracts, and the four-host installation/Reserve matrix. Independent post-publication checks installed the exact npm version on all four hosts, matched all 63 installed plugin files to the verified published archive, and exercised a 4.34-to-4.35 upgrade on rc.2. All eight advertised models resolved and prepared, defaults were unchanged, all optional capabilities remained disabled, and provider disposal and synthetic Reserve transitions passed. These checks are not fresh real OAuth, live Reserve/model/tool/image, or full Windows application acceptance. See [.github/ALPHA_435_RELEASE_READINESS.md](.github/ALPHA_435_RELEASE_READINESS.md) for publication, installation evidence, and limitations. Historical rows remain the repository's existing verification record. This guidance does not change upstream DSH behavior or resolve [Issue #64](https://github.com/franksong2702/dsh-codex-connect/issues/64).
39
43
 
@@ -60,10 +64,10 @@ Alpha 4.33 omits the `modelErrors` profile field required by RC model packages,
60
64
  dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.23
61
65
  ```
62
66
 
63
- For DSH `0.1.2-rc.1`, `0.1.5-alpha.1`, `0.1.5-rc.1`, or `0.1.5-rc.2`, use Alpha 4.35:
67
+ For DSH `0.1.2-rc.1`, `0.1.5-alpha.1`, `0.1.5-rc.1`, or `0.1.5-rc.2`, use Alpha 4.37:
64
68
 
65
69
  ```sh
66
- dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.35
70
+ dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.37
67
71
  ```
68
72
 
69
73
  For DSH `0.1.2-alpha.5`, use Alpha 4.25:
@@ -72,7 +76,7 @@ Alpha 4.33 omits the `modelErrors` profile field required by RC model packages,
72
76
  dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.25
73
77
  ```
74
78
 
75
- If npm is unavailable after the matching GitHub prerelease is created, use `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.21'` only for the DSH `0.1.1-rc.2` combination, `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.23'` only for the DSH `0.1.2-alpha.2` combination, `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.25'` only for the DSH `0.1.2-alpha.5` combination, or `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.35'` only for the DSH `0.1.2-rc.1`, `0.1.5-alpha.1`, `0.1.5-rc.1`, or `0.1.5-rc.2` combinations.
79
+ If npm is unavailable after the matching GitHub prerelease is created, use `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.21'` only for the DSH `0.1.1-rc.2` combination, `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.23'` only for the DSH `0.1.2-alpha.2` combination, `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.25'` only for the DSH `0.1.2-alpha.5` combination, or `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.37'` only for the DSH `0.1.2-rc.1`, `0.1.5-alpha.1`, `0.1.5-rc.1`, or `0.1.5-rc.2` combinations.
76
80
 
77
81
  3. Run `dsh web --help` once to compose the installed profile without starting the server. DSH `0.1.2-rc.1` prepares profile plugin dependency fallback during this step.
78
82
  4. Run `dsh --profile web --dump-config` and require exactly one `llm-openai-codex` row loading `dsh-codex-connect`.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # git hash-object README.md docs/README.zh.md
5
- README.md: 5c2bff3d29c1366e52035ac809f119a2fdca47fc
6
- docs/README.zh.md: f629b3789be4b361ffd4045dff7fa6acf92770ff
5
+ README.md: 59ddc56fb06a92c01d60d3d516095cc8cad5cd68
6
+ docs/README.zh.md: c4784c0b2aece6af6f81f7944e12ec89f1631b46
package/README.md CHANGED
@@ -16,17 +16,17 @@ This guide describes the published pairings below. Check `dsh --version` first a
16
16
 
17
17
  | Requirement | Verified pairing |
18
18
  |---|---|
19
- | Codex Connect | `0.1.0-alpha.4.35` |
19
+ | Codex Connect | `0.1.0-alpha.4.37` |
20
20
  | DeepSeek Harness | `0.1.2-rc.1`, `0.1.5-alpha.1`, `0.1.5-rc.1`, or `0.1.5-rc.2` |
21
21
  | Node.js | `^22.19.0 \|\| >=24.0.0` |
22
22
  | Account | ChatGPT OAuth with access to the requested Codex model; availability is decided by OpenAI |
23
23
 
24
- As of 2026-09-11, npm `alpha` points to 4.35 while `latest` intentionally remains on 4.34. Use the exact version below for 4.35; this recommendation does not promote the default installation channel.
24
+ As of 2026-09-19, npm `alpha` points to 4.37 while `latest` intentionally remains on 4.34. Use the exact version below for 4.37; this recommendation does not promote the default installation channel.
25
25
 
26
26
  ### 1. Install
27
27
 
28
28
  ```sh
29
- dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.35
29
+ dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.37
30
30
  dsh web
31
31
  ```
32
32
 
@@ -56,7 +56,7 @@ dsh plugin --profile web exec dsh-codex-connect doctor --json
56
56
  - **Accounts:** save up to 16 accounts on the DSH host and manually select the active account for subsequent requests. Account selection is not a per-session binding. Requests keep their captured account; the plugin does not rotate accounts or silently fail over.
57
57
  - **Models and Astra support:** the currently verified DSH and plugin combination supports `gpt-6-astra`. The plugin supplies its missing model definition with Low, Medium, High, Xhigh, and Max reasoning levels; Default preserves the provider default. Saved Off/Minimal selections require an [explicit update](MIGRATION.md#astra-reasoning-selections). When the installed dependency catalog includes Astra, the plugin preserves its native metadata while retaining these five calibrated reasoning choices. A model appearing in the list does not mean the current account has permission to use it; overall compatibility with new dependency versions still requires separate verification.
58
58
  - **Fast Mode:** request priority service for one conversation, off by default. Actual speed and quota consumption depend on the service; no fixed speed multiplier is guaranteed.
59
- - **Quota:** show the server-returned `5h` and `7d` windows and reset times, normally refreshed every 60 seconds while signed in. Missing windows are not invented; Spark uses its separate quota bucket.
59
+ - **Quota:** show the server-returned `5h` and `7d` windows and reset times, normally refreshed every 60 seconds while signed in and the tab is visible; failures back off. Missing windows are not invented; Spark uses its separate quota bucket.
60
60
  - **Plugin updates:** check for newer Codex Connect releases without installing anything or recommending changes to DSH. Host compatibility is available through explicit local diagnostics.
61
61
 
62
62
  <p align="center">
@@ -78,7 +78,7 @@ All options below are off on a fresh installation. Edit them in **Settings → P
78
78
 
79
79
  **Published experiment:** Alpha 4.35 includes Luna Reserve fallback, disabled by default. Real-account Reserve entry and recovery remain unverified; Alpha 4.34 does not include this feature.
80
80
 
81
- With `enableReserveFallback: true`, the account UI and agent routing share one identity-bound quota state. Background refresh follows the returned quota windows; fresh state is reused across agent steps. The plugin enters `gpt-reserve` only with complete, non-FedRAMP account/user identity and backend Luna Reserve authorization, then restores the session's previous model and reasoning effort after confirmed ordinary-usage recovery. Reserve has its own allowance, is hidden from the model picker, and is not unlimited. The backend decides eligibility; reset times alone do not authorize a switch. This version supports known `gpt-5.6-luna` metadata only. See [Luna Reserve fallback](docs/reference.md#luna-reserve-fallback) for refresh, identity, and verification limits.
81
+ With `enableReserveFallback: true`, the account UI and agent routing share one identity-bound quota state. Background refresh follows the returned quota windows while recently in use; fresh state is reused across agent steps. Ordinary quota reads also share the cache, even with Reserve disabled. The plugin enters `gpt-reserve` only with complete, non-FedRAMP account/user identity and backend Luna Reserve authorization, then restores the session's previous model and reasoning effort after confirmed ordinary-usage recovery. Reserve has its own allowance, is hidden from the model picker, and is not unlimited. The backend decides eligibility; reset times alone do not authorize a switch. This version supports known `gpt-5.6-luna` metadata only. See [Luna Reserve fallback](docs/reference.md#luna-reserve-fallback) for refresh, identity, and verification limits.
82
82
 
83
83
  Use the image generation capability included with your current GPT subscription. Generated originals are stored separately from attachment previews; disabling the capability or uninstalling the plugin does not delete them. See [Configuration and recovery](docs/reference.md#search-and-image-tools) for storage and access rules.
84
84
 
@@ -100,8 +100,14 @@ Subsequent Codex requests use the selected active account; conversations do not
100
100
 
101
101
  ### Why does a listed model fail?
102
102
 
103
+ Model HTTP/SSE failures include bounded, request-local diagnostic metadata after the existing error message. An `overloaded` message alone does not establish an account block. See [persistent-error diagnostics](docs/experiments/issue-219-diagnostics.md) for scope and reproduction.
104
+
103
105
  Account permissions, plugin/host compatibility, and network conditions all affect availability. Access on another client does not guarantee this integration will work. OpenAI controls model access, quota, context capacity, and service behavior; catalog entries are not proof of entitlement.
104
106
 
107
+ ### Does changing client headers prevent persistent authorization failures?
108
+
109
+ No such guarantee is established. Codex Connect is a third-party integration; it does not impersonate Codex Desktop or fabricate installation/attestation headers. The pi-ai model/OAuth route and auxiliary routes currently identify themselves differently. OpenAI's [App Server documentation](https://developers.openai.com/codex/app-server/) asks integrations to identify their own client with `clientInfo`; it does not establish acceptance rules for this plugin's direct backend calls. An `overloaded` message is not proof of a block. Capture the bounded error metadata and compare successful and failed windows before attributing the cause; share request IDs privately, never tokens or full session archives.
110
+
105
111
  ### Can I keep the original `dsh-codex` plugin installed?
106
112
 
107
113
  Not in the same effective configuration: both register `openai-codex`. Follow [MIGRATION.md](MIGRATION.md); remove only the confirmed conflicting entry, not credentials or unrelated providers.
package/docs/README.zh.md CHANGED
@@ -16,17 +16,17 @@ Codex Connect 为标准 Harness agent loop 添加 `openai-codex` 模型提供方
16
16
 
17
17
  | 要求 | 已验证组合 |
18
18
  |---|---|
19
- | Codex Connect | `0.1.0-alpha.4.35` |
19
+ | Codex Connect | `0.1.0-alpha.4.37` |
20
20
  | DeepSeek Harness | `0.1.2-rc.1`、`0.1.5-alpha.1`、`0.1.5-rc.1` 或 `0.1.5-rc.2` |
21
21
  | Node.js | `^22.19.0 \|\| >=24.0.0` |
22
22
  | 账户 | 通过 ChatGPT OAuth 使用所请求的 Codex 模型;可用性由 OpenAI 决定 |
23
23
 
24
- 截至 2026-09-11,npm `alpha` 指向 4.35,`latest` 则有意保留在 4.34。安装 4.35 请使用下方精确版本命令;文档推荐更新不代表默认安装渠道已提升。
24
+ 截至 2026-09-19,npm `alpha` 指向 4.37,`latest` 则有意保留在 4.34。安装 4.37 请使用下方精确版本命令;文档推荐更新不代表默认安装渠道已提升。
25
25
 
26
26
  ### 1. 安装
27
27
 
28
28
  ```sh
29
- dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.35
29
+ dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.37
30
30
  dsh web
31
31
  ```
32
32
 
@@ -56,7 +56,7 @@ dsh plugin --profile web exec dsh-codex-connect doctor --json
56
56
  - **账户:**在 DSH 主机上保存最多 16 个账户,手动选择后续请求使用的活动账户,不按会话绑定。请求保持已固定的账户,插件不会自动轮换或静默故障切换。
57
57
  - **模型与 Astra 支持:**当前已验证的 DSH 与插件组合已支持 `gpt-6-astra`。插件补充缺失的模型定义,提供 Low、Medium、High、Xhigh 和 Max 五档推理强度;Default 保持提供方默认值。已保存的 Off/Minimal 选择需要[明确更新](../MIGRATION.md#astra-reasoning-selections)。安装的依赖目录包含 Astra 时,插件保留其原生元数据,同时维持这五档已校准的推理选择。模型出现在列表中,不代表当前账户具有调用权限;新依赖版本的整体兼容性仍需单独验证。
58
58
  - **Fast Mode:**为单个对话请求优先服务,默认关闭。实际速度和额度消耗取决于服务端,不保证固定提速倍数。
59
- - **额度:**显示服务端返回的 `5h`、`7d` 窗口及重置时间,已登录时通常每 60 秒刷新一次。不虚构缺失窗口;Spark 使用独立额度桶。
59
+ - **额度:**显示服务端返回的 `5h`、`7d` 窗口及重置时间,已登录且标签页可见时通常每 60 秒刷新一次;失败后延长重试间隔。不虚构缺失窗口;Spark 使用独立额度桶。
60
60
  - **插件更新:**检查 Codex Connect 新版本,不自动安装,也不建议更改 DSH。宿主兼容性信息通过主动运行的本地诊断查看。
61
61
 
62
62
  <p align="center">
@@ -78,7 +78,7 @@ dsh plugin --profile web exec dsh-codex-connect doctor --json
78
78
 
79
79
  **已发布的实验功能:** Alpha 4.35 包含 Luna Reserve 回退,仍默认关闭。真实账户进入 Reserve 及恢复普通模型的过程仍未验证;Alpha 4.34 不包含该功能。
80
80
 
81
- 启用 `enableReserveFallback: true` 后,账户 UI 和 agent 路由共用一份绑定身份的额度状态,按服务端返回的额度窗口后台刷新;有效状态可跨 agent step 复用。只有身份完整、非 FedRAMP 且服务端授权时,插件才进入 `gpt-reserve`;普通额度确认恢复后,切回该会话先前的模型和推理强度。Reserve 有自己的额度,不出现在模型选择器中,也不是无限额度。资格由服务端决定,重置时间本身不授权切换。当前版本只支持已知的 `gpt-5.6-luna` 元数据。刷新、身份和验证限制见 [Luna Reserve 回退](reference.zh.md#luna-reserve-回退)。
81
+ 启用 `enableReserveFallback: true` 后,账户 UI 和 agent 路由共用一份绑定身份的额度状态,最近有使用需求时按服务端返回的额度窗口后台刷新;有效状态可跨 agent step 复用。普通额度读取也共用缓存,即使 Reserve 已关闭。只有身份完整、非 FedRAMP 且服务端授权时,插件才进入 `gpt-reserve`;普通额度确认恢复后,切回该会话先前的模型和推理强度。Reserve 有自己的额度,不出现在模型选择器中,也不是无限额度。资格由服务端决定,重置时间本身不授权切换。当前版本只支持已知的 `gpt-5.6-luna` 元数据。刷新、身份和验证限制见 [Luna Reserve 回退](reference.zh.md#luna-reserve-回退)。
82
82
 
83
83
  使用你当前 GPT 订阅计划提供的图片生成能力。生成原文件与附件预览分开保存;关闭能力或卸载插件不会删除这些文件。存储和访问规则见[配置与恢复](reference.zh.md#搜索与图片工具)。
84
84
 
@@ -100,8 +100,14 @@ OAuth 凭据保存在运行 DSH 的主机上,由该主机用于向 OpenAI 认
100
100
 
101
101
  ### 为什么列表中的模型调用失败?
102
102
 
103
+ 模型 HTTP/SSE 请求失败时,会在原错误消息后附加有界、仅属于该请求的诊断信息。单凭 `overloaded` 消息不能认定账户被封锁。范围与复现方式见[持续错误诊断](experiments/issue-219-diagnostics.md)。
104
+
103
105
  账户权限、插件与宿主兼容性、网络条件都会影响可用性。其他客户端可以使用,不代表此集成一定可用。OpenAI 控制模型权限、额度、上下文容量和服务行为;目录条目不是账户权限证明。
104
106
 
107
+ ### 修改客户端请求头就能避免持续授权失败吗?
108
+
109
+ 目前没有这样的保证。Codex Connect 是第三方集成,不冒充 Codex Desktop,也不伪造安装标识或 attestation 请求头。pi-ai 模型/OAuth 路由与辅助路由目前采用不同的客户端身份。OpenAI 的 [App Server 文档](https://developers.openai.com/codex/app-server/)要求集成通过 `clientInfo` 标识自己的客户端,但这不能证明本插件直接调用后端接口的接受规则。`overloaded` 本身不是封锁证据。应保留有界诊断信息,对照成功和失败时段后再判断原因;请求 ID 仅私下提供,不公开 token 或完整会话归档。
110
+
105
111
  ### 可以保留原来的 `dsh-codex` 插件吗?
106
112
 
107
113
  不能在同一份有效配置中并存:两者都会注册 `openai-codex`。请遵循 [MIGRATION.md](../MIGRATION.md),只移除已经确认冲突的条目,不删除凭据或无关提供方。
@@ -1,4 +1,8 @@
1
- # Alpha 4.37 preparation checkpoint — 2026-09-19
1
+ # Alpha 4.37 published checkpoint — 2026-09-19
2
+
3
+ Alpha `0.1.0-alpha.4.37` was published from `5cbd0d330d12c81f0bf37515b65bc799e480aa78` through successful workflow `35439115133`. The npm archive equals the final tested artifact; the Git tag and published GitHub prerelease match the release commit. `alpha` is 4.37; `latest` remains 4.34. See [publication verification](../../.github/ALPHA_437_PUBLICATION.md). No service or default changed, and no new live-model request was made. The local DSH repair, Think and Split are not included.
4
+
5
+ ## Historical preparation checkpoint — 2026-09-19
2
6
 
3
7
  #216 was normally squash-merged at `525e01b6e1c2b7d23ba70e29510ef1fd31fb0168`; the merge tree equals the reviewed `1e05677` tree. Main CI `35436177342` passed. This branch prepares 0.1.0-alpha.4.37. The maintainer subsequently authorized publication through the normal candidate-review/main-CI/OIDC workflow on 2026-09-19; a prepared branch is not publication evidence. `latest`, running services and live model calls remain outside scope. See [release readiness](../../.github/ALPHA_437_RELEASE_READINESS.md) and [draft release notes](../release-notes/alpha-4.37.md).
4
8
 
@@ -0,0 +1,49 @@
1
+ # Issue 219: persistent SSE failures and quota traffic
2
+
3
+ Implementation baseline: main commit e5772cd8a5c47f30b5ab14fe73d2901914348463 (alpha 4.37), 2026-09-20.
4
+ These changes are diagnostic and traffic-management fixes, not evidence that the reported authorization session was blocked or that an account-side problem has been resolved.
5
+
6
+ ## Model request diagnostics
7
+
8
+ The adapter uses the provider's request-local fetch extension, not a global fetch patch or a modified pi-ai installation. It observes bounded complete SSE frames while forwarding the original bytes, response metadata, backpressure and cancellation. It does not retry or replay failed model turns.
9
+
10
+ Each HTTP attempt receives a fresh x-client-request-id. Session affinity and originator/User-Agent are unchanged. The final Harness error message receives a compact JSON diagnostic suffix only after Harness has classified the original error, so numeric request ids cannot change PI_AI_ERROR into AUTH/RATE_LIMIT. Concurrent turns do not share diagnostic state.
11
+
12
+ Fields are limited to HTTP status, locally generated attempt id, bounded server request ids, first SSE error event type, schema-shaped error code/type, and a finite HTTP Retry-After delay. Missing fields remain missing. Arbitrary event fields, full headers, tokens, account ids, generated content and raw payloads are not added to the suffix. A maximum 16 KiB decoded frame is observed; larger or malformed frames stop further SSE attribution for that response, preserving HTTP metadata without guessing at later frames; incomplete frames are not inferred. The original bytes sent to pi-ai are unchanged. Existing provider error text remains unchanged.
13
+
14
+ Coverage is the model adapter's HTTP/SSE route through a pi-ai version honoring the fetch extension. It does not add persisted history to doctor, infer blocked-vs-capacity from a message, change standalone Search/Image/Auto-review diagnostics, or instrument native compaction's separate direct fetch. WebSocket diagnostics are not claimed; the plugin's model profile uses SSE.
15
+
16
+ ## Quota traffic
17
+
18
+ Ordinary quota reads now coalesce and cache even when Reserve is off. Cache keys bind the account and a hash of the exact access credential; no raw token is retained as a key. Renewed credentials do not inherit an old session's rejected snapshot. Configuration/account mutations and disposal still invalidate authority.
19
+
20
+ Successful ordinary snapshots last 60 seconds. Reserve retains its existing adaptive freshness requirements while in use, but its timer cannot renew its own activity lease. After two minutes without a foreground consumer it stops fetching. Expiry still revokes stale routing permits without a network request. Enabling Reserve remains explicit; none of these changes infer server authorization from elapsed time or a generic error.
21
+
22
+ Transient errors use 60/120/240/480/900-second backoff, positive jitter bounded to 10% (and a 900-second local ceiling), and any longer valid server Retry-After. A successful refresh resets the failure count. Authentication rejection and non-retryable 4xx failures latch for the current credential/state. Retry hints outside the timer range cannot become immediate timers.
23
+
24
+ The browser does not poll while hidden, does not overlap slow requests, and respects its existing cooldown when refocused. Both local network failures and server quotaError responses cause browser backoff. Foreground retry requests cannot bypass the backend's cached failure deadline. These caches are per plugin instance, not a cross-process rate limiter.
25
+
26
+ ## Offline verification
27
+
28
+ Run from the working checkout:
29
+
30
+ ~~~sh
31
+ pnpm exec vitest run tests/issue-219-diagnostics.spec.ts tests/issue-219-quota.client.spec.tsx tests/quota-state.spec.ts tests/openai-codex-quota-indicator.client.spec.tsx tests/usage.spec.ts tests/adapter.spec.ts tests/adapter-auth-boundary.spec.ts
32
+ pnpm run check
33
+ ~~~
34
+
35
+ The targeted fixtures use synthetic credentials and mocked HTTP/SSE, including an actual pi-ai -> Harness adapter pass. They cover flat/nested/response.failed errors, request-id isolation, host classification, hooks, malformed/large/split frames, cancellation, visibility, ordinary-mode cache sharing, repeated errors, retry hints, reauthorization and idle expiry. They do not reproduce the reporter's 23-hour account condition or constitute Windows/live-account acceptance.
36
+
37
+ A follow-up review reproduced an expiry/foreground-refresh race introduced in 420ce42: when a foreground refresh starts at the old snapshot deadline before its already-queued timer fires, the timer could abort the replacement request using the old deadline. The timer now skips entries with a pending refresh; starting that refresh has already revoked the old snapshot. The regression test queues both operations at the same deadline and verifies that the old permit expires, the new request survives, and concurrent readers reuse its result. The test fails before the fix and passes after it; account invalidation and disposal still cancel pending work.
38
+
39
+ A second review reproduced incorrect attribution of an unread later error after malformed JSON, an oversized earlier error, or a completed empty response in one network chunk. Three real-adapter regressions fail on 0bc83ee and pass after stopping observation at an ambiguous or terminal boundary. HTTP request metadata remains available when SSE attribution stops.
40
+
41
+ When collecting future evidence, compare a failed request's timestamp, HTTP status, SSE code/type and server request id with its successful window. Share request ids privately with the service operator; never publish OAuth tokens, credential files, account ids or a complete session archive.
42
+
43
+ ## Issue closure criteria
44
+
45
+ The release candidate is 0.1.0-alpha.4.38. Its client-side acceptance covers hidden-page suspension, ordinary cache/coalescing, failure backoff, renewed-credential isolation, bounded diagnostics and the expiry/attribution regressions. CI and synthetic fixtures cannot establish the cause or recovery of a 23-hour upstream failure. Do not auto-close #219 merely because this PR merges or the package publishes.
46
+
47
+ The reporter should confirm the exact installed plugin/DSH/pi-ai versions and whether normal use remains healthy through a comparable observation window. If it recurs, retain the timestamp, HTTP status, error code/type and request IDs from the new suffix; send request IDs privately to the service operator. Compare ordinary single-turn use with optional Search/Image/Reserve/Auto-review disabled, then re-enable only the needed routes one at a time. This is controlled diagnosis, not a high-volume stress test or a guarantee of recovery. Do not revoke all devices, delete credentials, rotate accounts, or rewrite the client identity automatically.
48
+
49
+ On 2026-09-20 a repository-scoped issue search for `overloaded` found #219 only. This is not a claim that no similar upstream report exists. Header classification, backend eligibility and account/session-level restrictions require evidence from the service operator; official-client binary strings are not a published backend contract.
@@ -1,4 +1,4 @@
1
1
  # Bilingual-pair consistency record for the operational reference. Re-record with:
2
2
  # git hash-object docs/reference.md docs/reference.zh.md
3
- docs/reference.md: b4a7480a0bd95cc5cfee5f713a10a7820ee61342
4
- docs/reference.zh.md: cffb2fd685a2f0189d75c4ae3cbf6716697e063c
3
+ docs/reference.md: b6d177da34691432d0c39c27644ef4b083af0d2a
4
+ docs/reference.zh.md: 926b475eb378191b56b9de6c386b7a03a9292d30
package/docs/reference.md CHANGED
@@ -28,7 +28,7 @@ An explicit OAuth `invalid_grant` rejection during refresh shows the reauthoriza
28
28
  For GPT Codex conversations, the Composer shows Fast Mode and quota:
29
29
 
30
30
  - **Fast Mode** requests priority service (`service_tier: 'priority'`) for that conversation only. It is off by default and does not change the model. Actual speed and quota consumption depend on the service; a fixed speed multiplier is not guaranteed.
31
- - **Quota bars** normally refresh every 60 seconds while signed in and show only the `5h` and `7d` windows returned by the server, with the exact remaining percentage and reset time. `gpt-5.3-codex-spark` uses its separate Spark bucket. Codex Connect never invents missing windows or suppresses returned windows based on a plan name.
31
+ - **Quota bars** normally refresh every 60 seconds while signed in and the tab is visible (hidden tabs pause; failures back off) and show only the `5h` and `7d` windows returned by the server, with the exact remaining percentage and reset time. `gpt-5.3-codex-spark` uses its separate Spark bucket. Codex Connect never invents missing windows or suppresses returned windows based on a plan name.
32
32
 
33
33
  <p align="center">
34
34
  <img src="https://raw.githubusercontent.com/franksong2702/dsh-codex-connect/main/docs/assets/composer-capabilities.jpg" alt="Fast Mode and quota controls in the DeepSeek Harness Composer" width="820">
@@ -61,9 +61,9 @@ Direct connection is the default. An enabled credential-free HTTP(S) proxy appli
61
61
 
62
62
  Published Alpha 4.35 includes this default-off experiment; Alpha 4.34 does not include it. Real-account Reserve entry and recovery remain unverified. See the [publication and installation evidence](../.github/ALPHA_435_RELEASE_READINESS.md).
63
63
 
64
- `enableReserveFallback: true` opts agent requests into backend-authorized Luna Reserve fallback. The account UI and routing share an in-memory account/user-bound quota snapshot; concurrent reads coalesce and fresh reads do not issue another quota `GET`. A cold or stale read waits for refresh. After a successful fetch, background refresh runs at 60/30/15/5 seconds for usage below 75%, at least 75%, at least 90%, and at least 99%, using the highest consumption across ordinary and relevant model windows. Future reset times shorten the next refresh to reset plus one second; they never establish recovery. Cache reads do not postpone that deadline. Failed fetches discard cached decisions and retry after five seconds. Account mutations, settings changes, and plugin disposal invalidate the state; UI receives only the public quota projection.
64
+ `enableReserveFallback: true` opts agent requests into backend-authorized Luna Reserve fallback. The account UI and routing share an in-memory account/user-bound quota snapshot; concurrent reads coalesce and fresh reads do not issue another quota `GET`. A cold or stale read waits for refresh. After a successful fetch, background refresh runs at 60/30/15/5 seconds for usage below 75%, at least 75%, at least 90%, and at least 99%, using the highest consumption across ordinary and relevant model windows. Future reset times shorten the next refresh to reset plus one second; they never establish recovery. Cache reads do not postpone that deadline. Transient failures discard cached decisions and use exponential backoff starting at 60 seconds, with up to 10% positive jitter and a 15-minute local cap; a longer valid `Retry-After` takes precedence. Authentication rejections and non-retryable 4xx errors stop automatic usage requests for that credential until credentials/state change. Delays beyond the timer range stop automatic retry rather than overflowing. Background GETs stop after two minutes without a foreground quota consumer; stale routing authority still expires. Account mutations, settings changes, and plugin disposal invalidate the state; UI receives only the public quota projection.
65
65
 
66
- A valid shared decision issues a private one-shot permit for the next `gpt-reserve` dispatch in that session/account. This local dispatch guard is not a server-side per-call authorization requirement. Cancellation, replacement, an agent error, turn stopping, quota invalidation, snapshot refresh, or cache eviction revokes an unused permit. Return-target I/O rechecks that authority before restoring an ordinary model. Direct and auxiliary Reserve calls fail before a model request. With fallback disabled, ordinary agent steps do not query quota, account UI reads remain passive, and there is no background quota poller. An already-Reserve session requires an explicitly selected ordinary model.
66
+ A valid shared decision issues a private one-shot permit for the next `gpt-reserve` dispatch in that session/account. This local dispatch guard is not a server-side per-call authorization requirement. Cancellation, replacement, an agent error, turn stopping, quota invalidation, snapshot refresh, or cache eviction revokes an unused permit. Return-target I/O rechecks that authority before restoring an ordinary model. Direct and auxiliary Reserve calls fail before a model request. With fallback disabled, ordinary agent steps do not query quota and there is no background quota poller; account UI reads share a 60-second credential-bound cache, concurrent GET coalescing, and failure cooldowns. An already-Reserve session requires an explicitly selected ordinary model.
67
67
 
68
68
  The access token must contain non-empty `chatgpt_account_id` and `chatgpt_user_id` (or `user_id`) claims under `https://api.openai.com/auth`, and `chatgpt_account_is_fedramp` must be absent or `false`. Missing, partial, FedRAMP, changed, or response-mismatched identity disables fallback for that step. The plugin does not guess identity from an email address or subscription plan.
69
69
 
@@ -28,7 +28,7 @@ Codex 目录来自已安装的 `@earendil-works/pi-ai` 包,不是实时查询
28
28
  GPT Codex 对话的 Composer 会显示 Fast Mode 与额度:
29
29
 
30
30
  - **Fast Mode** 只为当前对话请求优先服务(`service_tier: 'priority'`)。默认关闭,也不会更换模型。实际速度和额度消耗取决于服务端,不保证固定提速倍数。
31
- - **额度条**在已登录时通常每 60 秒刷新一次,只显示服务端实际返回的 `5h` 和 `7d` 窗口,并显示精确剩余百分比与重置时间。`gpt-5.3-codex-spark` 使用独立的 Spark 额度桶。Codex Connect 不会虚构缺失窗口,也不会根据套餐名称隐藏已返回窗口。
31
+ - **额度条**在已登录且标签页可见时通常每 60 秒刷新一次(隐藏时暂停,失败后延长重试间隔),只显示服务端实际返回的 `5h` 和 `7d` 窗口,并显示精确剩余百分比与重置时间。`gpt-5.3-codex-spark` 使用独立的 Spark 额度桶。Codex Connect 不会虚构缺失窗口,也不会根据套餐名称隐藏已返回窗口。
32
32
 
33
33
  <p align="center">
34
34
  <img src="https://raw.githubusercontent.com/franksong2702/dsh-codex-connect/main/docs/assets/composer-capabilities.jpg" alt="DeepSeek Harness Composer 中的 Fast Mode 与额度控件" width="820">
@@ -61,9 +61,9 @@ GPT Codex 对话的 Composer 会显示 Fast Mode 与额度:
61
61
 
62
62
  已发布的 Alpha 4.35 包含这项默认关闭的实验功能;Alpha 4.34 不包含该功能。真实账户进入 Reserve 及恢复普通模型的过程仍未完成验证。详见[发布与安装验证记录](../.github/ALPHA_435_RELEASE_READINESS.md)。
63
63
 
64
- `enableReserveFallback: true` 为 agent 请求启用由后端授权的 Luna Reserve 回退。账户 UI 和路由共用绑定账户与用户的内存额度快照;并发读取合并,有效状态不再发起额度 `GET`。首次或过期读取等待刷新。查询成功后,根据普通额度和相关模型窗口中的最高消耗,低于 75%、达到 75%、达到 90%、达到 99% 时,分别每 60/30/15/5 秒后台刷新。未来重置时间可将下次刷新提前到重置后一秒,但不证明额度恢复。缓存读取不会推迟刷新期限。查询失败会清除缓存决策,五秒后重试。账户修改、设置变更和插件卸载会使状态失效;UI 只接收公开额度投影。
64
+ `enableReserveFallback: true` 为 agent 请求启用由后端授权的 Luna Reserve 回退。账户 UI 和路由共用绑定账户与用户的内存额度快照;并发读取合并,有效状态不再发起额度 `GET`。首次或过期读取等待刷新。查询成功后,根据普通额度和相关模型窗口中的最高消耗,低于 75%、达到 75%、达到 90%、达到 99% 时,分别每 60/30/15/5 秒后台刷新。未来重置时间可将下次刷新提前到重置后一秒,但不证明额度恢复。缓存读取不会推迟刷新期限。临时查询失败会清除缓存决策,并从 60 秒开始指数退避,附加最多 10% 的正向随机延迟,本地等待上限为 15 分钟;服务端有效的 `Retry-After` 更长时优先遵循。认证拒绝和不可重试的 4xx 错误会停止该凭据的自动额度请求,直到凭据或状态改变。超出定时器范围的延迟会停止自动重试,而不会溢出为立即重试。两分钟没有前台额度消费者后,后台停止发送 GET;过期的路由授权仍会失效。账户修改、设置变更和插件卸载会使状态失效;UI 只接收公开额度投影。
65
65
 
66
- 有效的共享决策为该会话和账户的下一次 `gpt-reserve` 调用签发私有一次性许可。这是本地调用保护,不代表服务端要求每次调用单独认证额度。取消、替换请求、agent 出错、回合停止、额度状态失效、快照刷新或缓存淘汰都会撤销未使用的许可。恢复普通模型前,返回记录的读取也会重新检查该授权是否有效。直接和辅助 Reserve 调用会在发送前失败。关闭回退时,普通 agent 步骤不查询额度,账户 UI 保持被动读取,也不创建后台额度轮询;已处于 Reserve 的会话需要显式选择普通模型。
66
+ 有效的共享决策为该会话和账户的下一次 `gpt-reserve` 调用签发私有一次性许可。这是本地调用保护,不代表服务端要求每次调用单独认证额度。取消、替换请求、agent 出错、回合停止、额度状态失效、快照刷新或缓存淘汰都会撤销未使用的许可。恢复普通模型前,返回记录的读取也会重新检查该授权是否有效。直接和辅助 Reserve 调用会在发送前失败。关闭回退时,普通 agent 步骤不查询额度,也不创建后台额度轮询;账户 UI 读取共用绑定凭据的 60 秒缓存、并发 GET 合并与失败冷却;已处于 Reserve 的会话需要显式选择普通模型。
67
67
 
68
68
  Access token 的 `https://api.openai.com/auth` 中必须包含非空的 `chatgpt_account_id` 和 `chatgpt_user_id`(或 `user_id`),且 `chatgpt_account_is_fedramp` 必须缺省或为 `false`。身份缺失、不完整、属于 FedRAMP、发生变化或与额度响应不匹配时,该步骤不会启用回退。插件不会从邮箱地址或订阅套餐推测身份。
69
69
 
@@ -1,6 +1,6 @@
1
- # Codex Connect 0.1.0-alpha.4.37 — draft release notes
1
+ # Codex Connect 0.1.0-alpha.4.37 — release notes
2
2
 
3
- **Preparation only; not published.**
3
+ **Published on 2026-09-19.** [GitHub prerelease](https://github.com/franksong2702/dsh-codex-connect/releases/tag/v0.1.0-alpha.4.37); npm `alpha` points to 4.37 and `latest` remains 4.34. The [publication record](../../.github/ALPHA_437_PUBLICATION.md) verifies the immutable release commit and archive. This post-publication document update does not republish the package.
4
4
 
5
5
  ## Changes
6
6
 
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as DSH_PLUGIN_API_PACKAGES, B as isSupportedDshPluginApiVersion, Ft as loginOpenAICodex, Gt as openAICodexAuthPath, It as logoutOpenAICodex, Lt as openAICodexAuthStatus, Pt as openAICodexModelCatalog, Rt as publicAuthError, Ut as OPENAI_CODEX_PROVIDER, V as readInstalledPackageVersion, Wt as OpenAICodexCredentialStore, c as migrateOpenAICodexSearchHistory, d as OPENAI_CODEX_BASE_URL, jt as normalizeOpenAICodexProxyUrl, l as CODEX_AUTO_REVIEW_MODEL, nt as OpenAICodexTrustedOriginsStore, rt as normalizeTrustedOrigin, u as probeCodexAutoReview, x as CODEX_CONNECT_VERSION, y as diagnoseOpenAICodex, z as evaluateCompatibility } from "./src-CceJ4VNv.js";
2
+ import { A as DSH_PLUGIN_API_PACKAGES, B as isSupportedDshPluginApiVersion, Ft as loginOpenAICodex, Gt as openAICodexAuthPath, It as logoutOpenAICodex, Lt as openAICodexAuthStatus, Pt as openAICodexModelCatalog, Rt as publicAuthError, Ut as OPENAI_CODEX_PROVIDER, V as readInstalledPackageVersion, Wt as OpenAICodexCredentialStore, c as migrateOpenAICodexSearchHistory, d as OPENAI_CODEX_BASE_URL, jt as normalizeOpenAICodexProxyUrl, l as CODEX_AUTO_REVIEW_MODEL, nt as OpenAICodexTrustedOriginsStore, rt as normalizeTrustedOrigin, u as probeCodexAutoReview, x as CODEX_CONNECT_VERSION, y as diagnoseOpenAICodex, z as evaluateCompatibility } from "./src-hB8Ph2Et.js";
3
3
  import { i as fetch, r as ProxyAgent, t as Agent } from "./undici-runtime-H2uktiw6.js";
4
4
  import { createHash } from "node:crypto";
5
5
  import { realpathSync } from "node:fs";
package/lib/client.js CHANGED
@@ -4244,7 +4244,7 @@ window.__ModuleLoader__.load({
4244
4244
  return typeof remainingPercent === "number" && Number.isFinite(remainingPercent) && remainingPercent >= 0 && remainingPercent <= 100 && typeof windowSeconds === "number" && Number.isSafeInteger(windowSeconds) && windowSeconds > 0 && (resetAt === void 0 || typeof resetAt === "number" && Number.isSafeInteger(resetAt) && resetAt > 0 && Number.isFinite((/* @__PURE__ */ new Date(resetAt * 1e3)).getTime()));
4245
4245
  }
4246
4246
  function usageFromStatus(value) {
4247
- if (!isRecord$1(value) || value["status"] !== "signed-in") return void 0;
4247
+ if (!isRecord$1(value) || value["status"] !== "signed-in" || typeof value["quotaError"] === "string") return void 0;
4248
4248
  const usage = value["usage"];
4249
4249
  if (!isRecord$1(usage) || !Array.isArray(usage["rateLimits"])) return void 0;
4250
4250
  const rateLimits = usage["rateLimits"];
@@ -4308,8 +4308,18 @@ window.__ModuleLoader__.load({
4308
4308
  const controller = new AbortController();
4309
4309
  let inFlight = false;
4310
4310
  let disposed = false;
4311
+ let failures = 0;
4312
+ let nextAt = 0;
4313
+ let timer;
4314
+ const schedule = () => {
4315
+ window.clearTimeout(timer);
4316
+ timer = void 0;
4317
+ if (!disposed && !document.hidden && !inFlight) timer = window.setTimeout(() => {
4318
+ refresh();
4319
+ }, Math.max(0, nextAt - Date.now()));
4320
+ };
4311
4321
  const refresh = async () => {
4312
- if (inFlight || disposed) return;
4322
+ if (inFlight || disposed || document.hidden) return;
4313
4323
  inFlight = true;
4314
4324
  try {
4315
4325
  const response = await fetch(OPENAI_CODEX_AUTH_STATUS_PATH, {
@@ -4320,24 +4330,27 @@ window.__ModuleLoader__.load({
4320
4330
  });
4321
4331
  const value = await response.json().catch(() => void 0);
4322
4332
  const usage = response.ok ? usageFromStatus(value) : void 0;
4333
+ failures = usage === void 0 ? Math.min(failures + 1, 5) : 0;
4323
4334
  if (!disposed && !controller.signal.aborted) setRequest(usage === void 0 ? { status: "hidden" } : {
4324
4335
  status: "ready",
4325
4336
  usage
4326
4337
  });
4327
4338
  } catch {
4339
+ failures = Math.min(failures + 1, 5);
4328
4340
  if (!disposed && !controller.signal.aborted) setRequest({ status: "hidden" });
4329
4341
  } finally {
4330
4342
  inFlight = false;
4343
+ nextAt = Date.now() + Math.min(9e5, USAGE_POLL_INTERVAL_MS * 2 ** Math.max(0, failures - 1));
4344
+ schedule();
4331
4345
  }
4332
4346
  };
4333
4347
  setRequest({ status: "loading" });
4334
4348
  refresh();
4335
- const timer = window.setInterval(() => {
4336
- refresh();
4337
- }, USAGE_POLL_INTERVAL_MS);
4349
+ document.addEventListener("visibilitychange", schedule);
4338
4350
  return () => {
4339
4351
  disposed = true;
4340
- window.clearInterval(timer);
4352
+ document.removeEventListener("visibilitychange", schedule);
4353
+ window.clearTimeout(timer);
4341
4354
  controller.abort();
4342
4355
  };
4343
4356
  }, [eligible]);
@@ -6530,7 +6543,7 @@ window.__ModuleLoader__.load({
6530
6543
  }
6531
6544
  //#endregion
6532
6545
  //#region src/version.ts
6533
- const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.37";
6546
+ const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.38";
6534
6547
  //#endregion
6535
6548
  //#region src/client/OpenAICodexModelsCard.tsx
6536
6549
  /** Compact Models account entry with quota disclosure and shared configuration. */
package/lib/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as OPENAI_CODEX_FAST_MODE_MAX_SESSIONS, A as DSH_PLUGIN_API_PACKAGES, At as isValidOpenAICodexProxyUrl, Bt as OPENAI_CODEX_AUTH_DOCUMENT_LIMIT, C as checkForOpenAICodexUpdate, Ct as DEFAULT_OPENAI_CODEX_SEARCH_MODE, D as COMPATIBILITY_CONTRACT, Dt as decodeOpenAICodexSettings, E as parseOpenAICodexVersion, Et as OPENAI_CODEX_SETTINGS_NAMESPACE, F as SUPPORTED_NODE_RANGE, Ft as loginOpenAICodex, G as OPENAI_CODEX_PROXY_CANDIDATE_LIMIT, Gt as openAICodexAuthPath, H as OPENAI_CODEX_PROXY_DETECT_PATH, Ht as OPENAI_CODEX_AUTH_V1_BACKUP_SUFFIX, I as SUPPORTED_PI_AI_RANGE, It as logoutOpenAICodex, J as OpenAICodexProxyManager, K as OPENAI_CODEX_PROXY_PROBE_TIMEOUT_MS, L as assessCompatibility, Lt as openAICodexAuthStatus, M as SUPPORTED_DSH_PLUGIN_API_RANGE, Mt as resolveOpenAICodexProxyUrl, N as SUPPORTED_DSH_PLUGIN_API_VERSION, Nt as resolveOpenAICodexSettings, O as COMPATIBILITY_PACKAGES, Ot as isValidOpenAICodexContextWindowOverrides, P as SUPPORTED_DSH_PLUGIN_API_VERSIONS, Q as FastModeRegistry, R as detectCompatibility, S as OPENAI_CODEX_UPDATE_PATH, St as DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS, T as parseOpenAICodexUpdateResult, Tt as DEFAULT_OPENAI_CODEX_SETTINGS, U as OPENAI_CODEX_PROXY_TEST_PATH, Ut as OPENAI_CODEX_PROVIDER, Vt as OPENAI_CODEX_AUTH_FILENAME, W as OPENAI_CODEX_LOCAL_PROXY_CANDIDATES, Wt as OpenAICodexCredentialStore, X as listOpenAICodexProxyCandidates, Y as detectOpenAICodexProxies, Z as OPENAI_CODEX_FAST_MODE_PATH, _ as IMAGE_GENERATE_TOOL_NAME, _t as OpenAICodexTransportError, a as name, at as parseOpenAICodexUsage, b as openAICodexConflictMessage, bt as DEFAULT_OPENAI_CODEX_PROXY_URL, c as migrateOpenAICodexSearchHistory, ct as OPENAI_CODEX_IMAGE_MAX_COUNT, d as OPENAI_CODEX_BASE_URL, dt as OPENAI_CODEX_IMAGE_PROMPT_MAX_LENGTH, et as OPENAI_CODEX_FAST_MODE_MAX_SESSION_ID_LENGTH, f as OPENAI_CODEX_SEARCH_PROVIDER, ft as OPENAI_CODEX_IMAGE_REQUEST_TIMEOUT_MS, g as VIEW_IMAGE_TOOL_NAME, gt as OpenAICodexTransport, h as mapOpenAICodexSearchResponse, ht as OPENAI_CODEX_TRANSPORT_SERVICE, i as inject, it as OPENAI_CODEX_USAGE_URL, j as PI_AI_PACKAGE, k as COMPATIBILITY_SCHEMA_VERSION, kt as isValidOpenAICodexImageModelHint, lt as OPENAI_CODEX_IMAGE_MAX_ERROR_BYTES, m as OpenAICodexSearchProvider, mt as OPENAI_CODEX_TRANSPORT_ERROR_CODES, n as OPENAI_CODEX_SETTINGS_NS, o as OPENAI_CODEX_HISTORY_BACKUP_SUFFIX, ot as readOpenAICodexRateLimits, p as OPENAI_CODEX_SEARCH_URL, pt as OPENAI_CODEX_TRANSPORT_API_VERSION, q as OPENAI_CODEX_PROXY_PROBE_URL, r as apply, s as OPENAI_CODEX_SEARCH_MODEL_REQUEST_EVENT, st as OPENAI_CODEX_IMAGE_GENERATION_URL, t as Config, tt as isFastModeSessionId, ut as OPENAI_CODEX_IMAGE_MAX_RESPONSE_BYTES, v as assertNoOpenAICodexProviderConflict, vt as isOpenAICodexTransportError, w as compareOpenAICodexVersions, wt as DEFAULT_OPENAI_CODEX_SEARCH_MODEL, xt as DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE, y as diagnoseOpenAICodex, yt as DEFAULT_OPENAI_CODEX_IMAGE_MODEL_HINT, z as evaluateCompatibility, zt as OPENAI_CODEX_ACCOUNT_LIMIT } from "./src-CceJ4VNv.js";
1
+ import { $ as OPENAI_CODEX_FAST_MODE_MAX_SESSIONS, A as DSH_PLUGIN_API_PACKAGES, At as isValidOpenAICodexProxyUrl, Bt as OPENAI_CODEX_AUTH_DOCUMENT_LIMIT, C as checkForOpenAICodexUpdate, Ct as DEFAULT_OPENAI_CODEX_SEARCH_MODE, D as COMPATIBILITY_CONTRACT, Dt as decodeOpenAICodexSettings, E as parseOpenAICodexVersion, Et as OPENAI_CODEX_SETTINGS_NAMESPACE, F as SUPPORTED_NODE_RANGE, Ft as loginOpenAICodex, G as OPENAI_CODEX_PROXY_CANDIDATE_LIMIT, Gt as openAICodexAuthPath, H as OPENAI_CODEX_PROXY_DETECT_PATH, Ht as OPENAI_CODEX_AUTH_V1_BACKUP_SUFFIX, I as SUPPORTED_PI_AI_RANGE, It as logoutOpenAICodex, J as OpenAICodexProxyManager, K as OPENAI_CODEX_PROXY_PROBE_TIMEOUT_MS, L as assessCompatibility, Lt as openAICodexAuthStatus, M as SUPPORTED_DSH_PLUGIN_API_RANGE, Mt as resolveOpenAICodexProxyUrl, N as SUPPORTED_DSH_PLUGIN_API_VERSION, Nt as resolveOpenAICodexSettings, O as COMPATIBILITY_PACKAGES, Ot as isValidOpenAICodexContextWindowOverrides, P as SUPPORTED_DSH_PLUGIN_API_VERSIONS, Q as FastModeRegistry, R as detectCompatibility, S as OPENAI_CODEX_UPDATE_PATH, St as DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS, T as parseOpenAICodexUpdateResult, Tt as DEFAULT_OPENAI_CODEX_SETTINGS, U as OPENAI_CODEX_PROXY_TEST_PATH, Ut as OPENAI_CODEX_PROVIDER, Vt as OPENAI_CODEX_AUTH_FILENAME, W as OPENAI_CODEX_LOCAL_PROXY_CANDIDATES, Wt as OpenAICodexCredentialStore, X as listOpenAICodexProxyCandidates, Y as detectOpenAICodexProxies, Z as OPENAI_CODEX_FAST_MODE_PATH, _ as IMAGE_GENERATE_TOOL_NAME, _t as OpenAICodexTransportError, a as name, at as parseOpenAICodexUsage, b as openAICodexConflictMessage, bt as DEFAULT_OPENAI_CODEX_PROXY_URL, c as migrateOpenAICodexSearchHistory, ct as OPENAI_CODEX_IMAGE_MAX_COUNT, d as OPENAI_CODEX_BASE_URL, dt as OPENAI_CODEX_IMAGE_PROMPT_MAX_LENGTH, et as OPENAI_CODEX_FAST_MODE_MAX_SESSION_ID_LENGTH, f as OPENAI_CODEX_SEARCH_PROVIDER, ft as OPENAI_CODEX_IMAGE_REQUEST_TIMEOUT_MS, g as VIEW_IMAGE_TOOL_NAME, gt as OpenAICodexTransport, h as mapOpenAICodexSearchResponse, ht as OPENAI_CODEX_TRANSPORT_SERVICE, i as inject, it as OPENAI_CODEX_USAGE_URL, j as PI_AI_PACKAGE, k as COMPATIBILITY_SCHEMA_VERSION, kt as isValidOpenAICodexImageModelHint, lt as OPENAI_CODEX_IMAGE_MAX_ERROR_BYTES, m as OpenAICodexSearchProvider, mt as OPENAI_CODEX_TRANSPORT_ERROR_CODES, n as OPENAI_CODEX_SETTINGS_NS, o as OPENAI_CODEX_HISTORY_BACKUP_SUFFIX, ot as readOpenAICodexRateLimits, p as OPENAI_CODEX_SEARCH_URL, pt as OPENAI_CODEX_TRANSPORT_API_VERSION, q as OPENAI_CODEX_PROXY_PROBE_URL, r as apply, s as OPENAI_CODEX_SEARCH_MODEL_REQUEST_EVENT, st as OPENAI_CODEX_IMAGE_GENERATION_URL, t as Config, tt as isFastModeSessionId, ut as OPENAI_CODEX_IMAGE_MAX_RESPONSE_BYTES, v as assertNoOpenAICodexProviderConflict, vt as isOpenAICodexTransportError, w as compareOpenAICodexVersions, wt as DEFAULT_OPENAI_CODEX_SEARCH_MODEL, xt as DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE, y as diagnoseOpenAICodex, yt as DEFAULT_OPENAI_CODEX_IMAGE_MODEL_HINT, z as evaluateCompatibility, zt as OPENAI_CODEX_ACCOUNT_LIMIT } from "./src-hB8Ph2Et.js";
2
2
  export { COMPATIBILITY_CONTRACT, COMPATIBILITY_PACKAGES, COMPATIBILITY_SCHEMA_VERSION, Config, DEFAULT_OPENAI_CODEX_IMAGE_MODEL_HINT, DEFAULT_OPENAI_CODEX_PROXY_URL, DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE, DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS, DEFAULT_OPENAI_CODEX_SEARCH_MODE, DEFAULT_OPENAI_CODEX_SEARCH_MODEL, DEFAULT_OPENAI_CODEX_SETTINGS, DSH_PLUGIN_API_PACKAGES, FastModeRegistry, FastModeRegistry as OpenAICodexFastModeRegistry, IMAGE_GENERATE_TOOL_NAME, OPENAI_CODEX_ACCOUNT_LIMIT, OPENAI_CODEX_AUTH_DOCUMENT_LIMIT, OPENAI_CODEX_AUTH_FILENAME, OPENAI_CODEX_AUTH_V1_BACKUP_SUFFIX, OPENAI_CODEX_BASE_URL, OPENAI_CODEX_FAST_MODE_MAX_SESSIONS, OPENAI_CODEX_FAST_MODE_MAX_SESSION_ID_LENGTH, OPENAI_CODEX_FAST_MODE_PATH, OPENAI_CODEX_HISTORY_BACKUP_SUFFIX, OPENAI_CODEX_IMAGE_GENERATION_URL, OPENAI_CODEX_IMAGE_MAX_COUNT, OPENAI_CODEX_IMAGE_MAX_ERROR_BYTES, OPENAI_CODEX_IMAGE_MAX_RESPONSE_BYTES, OPENAI_CODEX_IMAGE_PROMPT_MAX_LENGTH, OPENAI_CODEX_IMAGE_REQUEST_TIMEOUT_MS, OPENAI_CODEX_LOCAL_PROXY_CANDIDATES, OPENAI_CODEX_PROVIDER, OPENAI_CODEX_PROXY_CANDIDATE_LIMIT, OPENAI_CODEX_PROXY_DETECT_PATH, OPENAI_CODEX_PROXY_PROBE_TIMEOUT_MS, OPENAI_CODEX_PROXY_PROBE_URL, OPENAI_CODEX_PROXY_TEST_PATH, OPENAI_CODEX_SEARCH_MODEL_REQUEST_EVENT, OPENAI_CODEX_SEARCH_PROVIDER, OPENAI_CODEX_SEARCH_URL, OPENAI_CODEX_SETTINGS_NAMESPACE, OPENAI_CODEX_SETTINGS_NS, OPENAI_CODEX_TRANSPORT_API_VERSION, OPENAI_CODEX_TRANSPORT_ERROR_CODES, OPENAI_CODEX_TRANSPORT_SERVICE, OPENAI_CODEX_UPDATE_PATH, OPENAI_CODEX_USAGE_URL, OpenAICodexCredentialStore, OpenAICodexProxyManager, OpenAICodexSearchProvider, OpenAICodexTransport, OpenAICodexTransportError, PI_AI_PACKAGE, SUPPORTED_DSH_PLUGIN_API_RANGE, SUPPORTED_DSH_PLUGIN_API_VERSION, SUPPORTED_DSH_PLUGIN_API_VERSIONS, SUPPORTED_NODE_RANGE, SUPPORTED_PI_AI_RANGE, VIEW_IMAGE_TOOL_NAME, apply, assertNoOpenAICodexProviderConflict, assessCompatibility, checkForOpenAICodexUpdate, compareOpenAICodexVersions, decodeOpenAICodexSettings, detectCompatibility, detectOpenAICodexProxies, diagnoseOpenAICodex, evaluateCompatibility, inject, isFastModeSessionId, isOpenAICodexTransportError, isValidOpenAICodexContextWindowOverrides, isValidOpenAICodexImageModelHint, isValidOpenAICodexProxyUrl, listOpenAICodexProxyCandidates, loginOpenAICodex, logoutOpenAICodex, mapOpenAICodexSearchResponse, migrateOpenAICodexSearchHistory, name, openAICodexAuthPath, openAICodexAuthStatus, openAICodexConflictMessage, parseOpenAICodexUpdateResult, parseOpenAICodexUsage, parseOpenAICodexVersion, readOpenAICodexRateLimits, resolveOpenAICodexProxyUrl, resolveOpenAICodexSettings };
@@ -1836,6 +1836,239 @@ function withOpenAICodexNativeCompaction(provider) {
1836
1836
  }
1837
1837
  };
1838
1838
  }
1839
+ //#endregion
1840
+ //#region src/request-backoff.ts
1841
+ /** Parse server retry hints without allowing invalid or overflowing timer delays. */
1842
+ function readRetryAfterMs(headers, now = Date.now()) {
1843
+ const milliseconds = headers.get("retry-after-ms");
1844
+ const seconds = headers.get("retry-after");
1845
+ const numeric = (value) => {
1846
+ if (value === null || !/^\d+(?:\.\d+)?$/u.test(value.trim())) return void 0;
1847
+ const result = Number(value);
1848
+ return Number.isFinite(result) && result >= 0 ? result : void 0;
1849
+ };
1850
+ const ms = numeric(milliseconds);
1851
+ const delay = numeric(seconds);
1852
+ const date = seconds !== null && /[A-Za-z]/u.test(seconds) ? Date.parse(seconds) : NaN;
1853
+ const result = ms ?? (delay === void 0 ? Number.isFinite(date) ? Math.max(0, date - now) : void 0 : delay * 1e3);
1854
+ return result === void 0 ? void 0 : result > 2147483647 ? Infinity : Math.ceil(result);
1855
+ }
1856
+ //#endregion
1857
+ //#region src/request-diagnostics.ts
1858
+ /** Request-local, bounded diagnostics for Codex HTTP/SSE failures. Never logs payloads. */
1859
+ const requestScope = new AsyncLocalStorage();
1860
+ const MAX_FRAME_CHARS = 16384;
1861
+ function record$3(value) {
1862
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1863
+ }
1864
+ function requestId(value) {
1865
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u.test(value) && !/^(?:eyJ|sk-|Bearer)/iu.test(value) ? value : void 0;
1866
+ }
1867
+ function errorCode(value) {
1868
+ return typeof value === "string" && /^[a-z][a-z0-9_]{0,79}$/u.test(value) ? value : void 0;
1869
+ }
1870
+ /** Observe bounded frames only while their attribution is unambiguous; never scan past a terminal. */
1871
+ var ErrorFrameObserver = class {
1872
+ diagnostic;
1873
+ decoder = new TextDecoder();
1874
+ line = "";
1875
+ data = "";
1876
+ size = 0;
1877
+ stopped = false;
1878
+ previousCR = false;
1879
+ lineHasText = false;
1880
+ constructor(diagnostic) {
1881
+ this.diagnostic = diagnostic;
1882
+ }
1883
+ feed(chunk) {
1884
+ for (let offset = 0; !this.stopped && offset < chunk.byteLength; offset += 4096) this.text(this.decoder.decode(chunk.subarray(offset, offset + 4096), { stream: true }));
1885
+ }
1886
+ finish() {
1887
+ this.text(this.decoder.decode());
1888
+ this.line = "";
1889
+ this.data = "";
1890
+ this.size = 0;
1891
+ }
1892
+ stop() {
1893
+ this.stopped = true;
1894
+ this.line = "";
1895
+ this.data = "";
1896
+ this.size = 0;
1897
+ }
1898
+ text(text) {
1899
+ for (const character of text) {
1900
+ if (this.stopped) return;
1901
+ if (character === "\n" && this.previousCR) {
1902
+ this.previousCR = false;
1903
+ continue;
1904
+ }
1905
+ this.previousCR = character === "\r";
1906
+ if (character === "\r" || character === "\n") this.completeLine();
1907
+ else {
1908
+ this.lineHasText = true;
1909
+ this.size += 1;
1910
+ if (this.size > MAX_FRAME_CHARS) {
1911
+ this.stop();
1912
+ return;
1913
+ }
1914
+ this.line += character;
1915
+ }
1916
+ }
1917
+ }
1918
+ completeLine() {
1919
+ if (!this.lineHasText) {
1920
+ if (this.data) this.observe(this.data);
1921
+ this.data = "";
1922
+ this.size = 0;
1923
+ } else if (this.line.startsWith("data:")) {
1924
+ this.data += this.line.slice(5).replace(/^ /u, "") + "\n";
1925
+ this.size += 1;
1926
+ if (this.size > MAX_FRAME_CHARS) this.stop();
1927
+ }
1928
+ this.line = "";
1929
+ this.lineHasText = false;
1930
+ }
1931
+ observe(data) {
1932
+ if (data.trim() === "[DONE]") {
1933
+ this.stop();
1934
+ return;
1935
+ }
1936
+ let event;
1937
+ try {
1938
+ event = JSON.parse(data);
1939
+ } catch {
1940
+ this.stop();
1941
+ return;
1942
+ }
1943
+ if (!record$3(event)) {
1944
+ this.stop();
1945
+ return;
1946
+ }
1947
+ if ([
1948
+ "response.completed",
1949
+ "response.done",
1950
+ "response.incomplete"
1951
+ ].includes(String(event["type"]))) {
1952
+ this.stop();
1953
+ return;
1954
+ }
1955
+ if (event["type"] !== "error" && event["type"] !== "response.failed") return;
1956
+ this.stop();
1957
+ const response = record$3(event["response"]) ? event["response"] : void 0;
1958
+ const nested = record$3(event["error"]) ? event["error"] : response !== void 0 && record$3(response["error"]) ? response["error"] : void 0;
1959
+ this.diagnostic.eventType = event["type"];
1960
+ const code = errorCode(event["code"]) ?? errorCode(nested?.["code"]);
1961
+ const type = errorCode(nested?.["type"]);
1962
+ const id = requestId(event["request_id"]) ?? requestId(nested?.["request_id"]);
1963
+ if (code !== void 0) this.diagnostic.errorCode = code;
1964
+ if (type !== void 0) this.diagnostic.errorType = type;
1965
+ if (id !== void 0) this.diagnostic.sseRequestId = id;
1966
+ }
1967
+ };
1968
+ function observeResponse(response, diagnostic) {
1969
+ if (response.body === null || !response.headers.get("content-type")?.toLowerCase().includes("text/event-stream")) return response;
1970
+ const observer = new ErrorFrameObserver(diagnostic);
1971
+ const reader = response.body.getReader();
1972
+ let released = false;
1973
+ const release = () => {
1974
+ if (!released) {
1975
+ released = true;
1976
+ reader.releaseLock();
1977
+ }
1978
+ };
1979
+ const body = new ReadableStream({
1980
+ async pull(controller) {
1981
+ try {
1982
+ const { done, value } = await reader.read();
1983
+ if (done) {
1984
+ observer.finish();
1985
+ release();
1986
+ controller.close();
1987
+ return;
1988
+ }
1989
+ observer.feed(value);
1990
+ controller.enqueue(value);
1991
+ } catch (error) {
1992
+ release();
1993
+ controller.error(error);
1994
+ }
1995
+ },
1996
+ async cancel(reason) {
1997
+ try {
1998
+ await reader.cancel(reason);
1999
+ } finally {
2000
+ release();
2001
+ }
2002
+ }
2003
+ }, { highWaterMark: 0 });
2004
+ const wrapped = new Response(body, {
2005
+ status: response.status,
2006
+ statusText: response.statusText,
2007
+ headers: response.headers
2008
+ });
2009
+ for (const key of [
2010
+ "url",
2011
+ "redirected",
2012
+ "type"
2013
+ ]) Object.defineProperty(wrapped, key, { value: response[key] });
2014
+ return wrapped;
2015
+ }
2016
+ /** Inject a request-local fetch seam, preserving existing provider hooks and dispatchers. */
2017
+ function withCodexDiagnosticFetch(options) {
2018
+ const scope = requestScope.getStore();
2019
+ if (scope === void 0) return options;
2020
+ const fetch = options?.fetch ?? globalThis.fetch;
2021
+ return {
2022
+ ...options,
2023
+ async fetch(input, init) {
2024
+ const diagnostic = { clientRequestId: randomUUID() };
2025
+ scope.current = diagnostic;
2026
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
2027
+ headers.set("x-client-request-id", diagnostic.clientRequestId);
2028
+ const response = await fetch(input, {
2029
+ ...init,
2030
+ headers
2031
+ });
2032
+ diagnostic.httpStatus = response.status;
2033
+ const id = requestId(response.headers.get("x-request-id")) ?? requestId(response.headers.get("request-id"));
2034
+ if (id !== void 0) diagnostic.httpRequestId = id;
2035
+ const retry = readRetryAfterMs(response.headers);
2036
+ if (retry !== void 0 && Number.isFinite(retry)) diagnostic.retryAfterMs = retry;
2037
+ return observeResponse(response, diagnostic);
2038
+ }
2039
+ };
2040
+ }
2041
+ /** Append diagnostics after DSH has classified the error; request ids must not affect that classification. */
2042
+ function streamWithCodexRequestDiagnostics(stream, options) {
2043
+ return { async *[Symbol.asyncIterator]() {
2044
+ const scope = {};
2045
+ const iterator = requestScope.run(scope, () => stream(options)[Symbol.asyncIterator]());
2046
+ try {
2047
+ while (true) {
2048
+ const next = await requestScope.run(scope, () => iterator.next());
2049
+ if (next.done) return;
2050
+ const chunk = next.value;
2051
+ if (chunk.type === "finish" && chunk.reason.kind === "error" && chunk.reason.failure !== void 0 && scope.current !== void 0) yield {
2052
+ ...chunk,
2053
+ reason: {
2054
+ ...chunk.reason,
2055
+ failure: {
2056
+ ...chunk.reason.failure,
2057
+ message: chunk.reason.failure.message + "\n[Codex diagnostics: " + JSON.stringify(scope.current) + "]"
2058
+ }
2059
+ }
2060
+ };
2061
+ else yield chunk;
2062
+ }
2063
+ } finally {
2064
+ try {
2065
+ await requestScope.run(scope, () => iterator.return?.());
2066
+ } finally {
2067
+ delete scope.current;
2068
+ }
2069
+ }
2070
+ } };
2071
+ }
1839
2072
  const OPENAI_CODEX_ASTRA_MODEL = {
1840
2073
  id: "gpt-6-astra",
1841
2074
  name: "GPT-6-Astra",
@@ -1955,7 +2188,7 @@ function requestProvider(provider, fastMode, proxyManager, resolveProxyUrl) {
1955
2188
  ...configured,
1956
2189
  streamSimple(model, context, options) {
1957
2190
  const proxyUrl = resolveProxyUrl?.();
1958
- const operation = () => streamSimple.call(configured, model, context, options);
2191
+ const operation = () => streamSimple.call(configured, model, context, withCodexDiagnosticFetch(options));
1959
2192
  return proxyManager?.runStream(proxyUrl, operation) ?? operation();
1960
2193
  },
1961
2194
  auth: {
@@ -2043,7 +2276,7 @@ function createOpenAICodexAdapter(credentials, resolveAttachments, fastMode, vis
2043
2276
  };
2044
2277
  class OpenAICodexAdapter extends PiAiAdapter {
2045
2278
  streamPrepared(stream, options) {
2046
- return streamWithNativeCompactionScope(stream, options, nativeCompactionEnabled?.() === true);
2279
+ return streamWithCodexRequestDiagnostics((next) => streamWithNativeCompactionScope(stream, next, nativeCompactionEnabled?.() === true), options);
2047
2280
  }
2048
2281
  async prepareCall(providerId, model, signal) {
2049
2282
  const prepared = await super.prepareCall(providerId, model, signal);
@@ -2510,6 +2743,17 @@ var OpenAICodexReauthRequiredError = class extends Error {
2510
2743
  this.name = "OpenAICodexReauthRequiredError";
2511
2744
  }
2512
2745
  };
2746
+ /** Safe HTTP status and retry hint; never retains response bodies or credentials. */
2747
+ var OpenAICodexUsageHttpError = class extends Error {
2748
+ status;
2749
+ retryAfterMs;
2750
+ constructor(status, retryAfterMs) {
2751
+ super(`OpenAI Codex usage request failed with HTTP ${status}`);
2752
+ this.status = status;
2753
+ this.retryAfterMs = retryAfterMs;
2754
+ this.name = "OpenAICodexUsageHttpError";
2755
+ }
2756
+ };
2513
2757
  /** Identify the dedicated reauthorization failure without comparing messages. */
2514
2758
  function isOpenAICodexReauthRequiredError(error) {
2515
2759
  return error instanceof OpenAICodexReauthRequiredError;
@@ -2644,7 +2888,7 @@ async function readOpenAICodexUsageResponse(auth, signal, supportsReserve) {
2644
2888
  if (!response.ok) {
2645
2889
  await cancelDiscardedResponseBody(response);
2646
2890
  if (response.status === 401 || response.status === 403) throw new OpenAICodexReauthRequiredError();
2647
- throw new Error(`OpenAI Codex usage request failed with HTTP ${response.status}`);
2891
+ throw new OpenAICodexUsageHttpError(response.status, readRetryAfterMs(response.headers));
2648
2892
  }
2649
2893
  try {
2650
2894
  const bytes = await readOpenAICodexBoundedBody(response, OPENAI_CODEX_USAGE_MAX_BYTES);
@@ -4676,7 +4920,7 @@ function registerOpenAICodexOriginalImageRoute(ctx, trustedOrigins, assets) {
4676
4920
  }
4677
4921
  //#endregion
4678
4922
  //#region src/version.ts
4679
- const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.37";
4923
+ const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.38";
4680
4924
  //#endregion
4681
4925
  //#region src/doctor.ts
4682
4926
  /** Secret-free diagnostics and duplicate-provider guidance. */
@@ -7060,19 +7304,27 @@ function abortable(promise, signal) {
7060
7304
  });
7061
7305
  }
7062
7306
  function safeFailure(error) {
7063
- if (error instanceof OpenAICodexReauthRequiredError) return error;
7307
+ if (error instanceof OpenAICodexReauthRequiredError || error instanceof OpenAICodexUsageHttpError) return error;
7064
7308
  if (error instanceof OpenAICodexRequestAuthError) return error.code === "REAUTH_REQUIRED" ? new OpenAICodexReauthRequiredError() : error;
7065
7309
  if (error instanceof Error && /^OpenAI Codex usage request failed with HTTP [1-5][0-9]{2}$/u.test(error.message)) return new Error(error.message);
7066
7310
  return /* @__PURE__ */ new Error("OpenAI Codex quota is temporarily unavailable");
7067
7311
  }
7068
- function refreshDeadline(snapshot, model, fetchedAt) {
7312
+ function refreshDeadline(snapshot, model, fetchedAt, adaptive) {
7313
+ if (!adaptive) return fetchedAt + 6e4;
7069
7314
  const windows = snapshot.usage.rateLimits.filter((limit) => limit.id === "codex" || model !== void 0 && limit.name === model || (snapshot.decision.kind === "reserve" || snapshot.decision.kind === "exhausted") && limit.name === "gpt-reserve").flatMap((limit) => limit.windows);
7070
7315
  const highest = Math.max(0, ...windows.map((window) => 100 - window.remainingPercent));
7071
7316
  const interval = highest >= 99 ? 5e3 : highest >= 90 ? 15e3 : highest >= 75 ? 3e4 : 6e4;
7072
7317
  const resets = windows.flatMap((window) => window.resetAt !== void 0 && window.resetAt * 1e3 > fetchedAt ? [window.resetAt * 1e3 + 1e3] : []);
7073
7318
  return Math.max(fetchedAt + 1e3, Math.min(fetchedAt + interval, ...resets));
7074
7319
  }
7075
- /** Owns quota cache, Reserve negotiation, and lazy adaptive background refresh. */
7320
+ function failureDelay(error, failures) {
7321
+ if (error instanceof OpenAICodexReauthRequiredError) return Infinity;
7322
+ if (error instanceof OpenAICodexUsageHttpError && error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 429) return Infinity;
7323
+ const backoff = Math.min(9e5, 6e4 * 2 ** Math.min(failures - 1, 4));
7324
+ const jittered = Math.min(9e5, backoff + Math.floor(Math.random() * backoff * .1));
7325
+ return Math.max(jittered, error instanceof OpenAICodexUsageHttpError ? error.retryAfterMs ?? 0 : 0);
7326
+ }
7327
+ /** Owns quota cache, Reserve negotiation, and demand-bounded background refresh. */
7076
7328
  var OpenAICodexQuotaState = class {
7077
7329
  options;
7078
7330
  cache = /* @__PURE__ */ new Map();
@@ -7081,6 +7333,7 @@ var OpenAICodexQuotaState = class {
7081
7333
  configuration;
7082
7334
  timer;
7083
7335
  timerAt = Infinity;
7336
+ activeUntil = 0;
7084
7337
  model;
7085
7338
  disposed = false;
7086
7339
  disposal;
@@ -7092,23 +7345,31 @@ var OpenAICodexQuotaState = class {
7092
7345
  this.epoch.abort();
7093
7346
  this.epoch = new AbortController();
7094
7347
  this.cache.clear();
7348
+ this.activeUntil = 0;
7095
7349
  if (this.timer !== void 0) clearTimeout(this.timer);
7096
7350
  this.timer = void 0;
7097
7351
  this.timerAt = Infinity;
7098
7352
  }
7099
7353
  schedule(at) {
7100
- if (this.disposed || !this.options.enabled() || this.timerAt <= at) return;
7354
+ if (this.disposed || !this.options.enabled() || !Number.isFinite(at) || this.timerAt <= at) return;
7101
7355
  if (this.timer !== void 0) clearTimeout(this.timer);
7102
7356
  this.timerAt = at;
7103
7357
  this.timer = setTimeout(() => {
7104
7358
  this.timer = void 0;
7105
7359
  this.timerAt = Infinity;
7106
- this.read(void 0, void 0, this.model).catch(() => void 0);
7360
+ for (const entry of this.cache.values()) if (entry.pending === void 0 && Date.now() >= entry.refreshAt) entry.authority?.abort();
7361
+ const nextExpiry = Math.min(...[...this.cache.values()].filter((entry) => entry.snapshot !== void 0 && entry.authority?.signal.aborted === false && entry.refreshAt > Date.now()).map((entry) => entry.refreshAt));
7362
+ if (Number.isFinite(nextExpiry)) this.schedule(nextExpiry);
7363
+ if (Date.now() >= this.activeUntil) return;
7364
+ this.readSnapshot(void 0, void 0, this.model, true).catch(() => void 0);
7107
7365
  }, Math.max(0, at - Date.now()));
7108
7366
  this.timer.unref?.();
7109
7367
  }
7110
7368
  /** Read a fresh snapshot, coalescing GETs without tying shared work to one caller. */
7111
7369
  read(snapshot, signal, model) {
7370
+ return this.readSnapshot(snapshot, signal, model, false);
7371
+ }
7372
+ readSnapshot(snapshot, signal, model, background) {
7112
7373
  if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("OpenAI Codex quota state has been disposed"));
7113
7374
  if (signal?.aborted) return Promise.reject(new DOMException("The operation was aborted", "AbortError"));
7114
7375
  const enabled = this.options.enabled();
@@ -7116,6 +7377,7 @@ var OpenAICodexQuotaState = class {
7116
7377
  const configuration = JSON.stringify([enabled, proxy]);
7117
7378
  if (this.configuration !== void 0 && this.configuration !== configuration) this.invalidate();
7118
7379
  this.configuration = configuration;
7380
+ if (!background) this.activeUntil = Date.now() + 12e4;
7119
7381
  if (model !== void 0) this.model = model;
7120
7382
  const epoch = this.epoch.signal;
7121
7383
  const operation = this.readInternal(snapshot ?? this.options.credentials, enabled, proxy, epoch, model).catch((error) => {
@@ -7132,7 +7394,7 @@ var OpenAICodexQuotaState = class {
7132
7394
  const candidate = enabled ? reserveIdentity(auth.access) : void 0;
7133
7395
  const identity = candidate?.accountId === auth.accountId ? candidate : void 0;
7134
7396
  const fetch = async (authoritySignal = epoch) => {
7135
- const value = await this.options.proxyManager.run(proxy, () => readOpenAICodexUsageResponse(auth, epoch, enabled && identity !== void 0));
7397
+ const value = await this.options.proxyManager.run(proxy, () => readOpenAICodexUsageResponse(auth, authoritySignal, enabled && identity !== void 0));
7136
7398
  epoch.throwIfAborted();
7137
7399
  return {
7138
7400
  usage: parseOpenAICodexUsage(value),
@@ -7141,8 +7403,7 @@ var OpenAICodexQuotaState = class {
7141
7403
  authoritySignal
7142
7404
  };
7143
7405
  };
7144
- if (!enabled) return fetch();
7145
- const key = JSON.stringify([auth.accountId, identity?.key]);
7406
+ const key = JSON.stringify([auth.accountId, createHash("sha256").update(auth.access).digest("hex")]);
7146
7407
  let entry = this.cache.get(key);
7147
7408
  if (entry === void 0) {
7148
7409
  if (this.cache.size >= 16) {
@@ -7153,18 +7414,19 @@ var OpenAICodexQuotaState = class {
7153
7414
  }
7154
7415
  entry = {
7155
7416
  fetchedAt: 0,
7156
- refreshAt: 0
7417
+ refreshAt: 0,
7418
+ failures: 0
7157
7419
  };
7158
7420
  this.cache.set(key, entry);
7159
7421
  }
7160
7422
  if (entry.pending !== void 0) {
7161
7423
  const result = await entry.pending;
7162
7424
  epoch.throwIfAborted();
7163
- entry.refreshAt = Math.min(entry.refreshAt, refreshDeadline(result, model, entry.fetchedAt));
7425
+ entry.refreshAt = Math.min(entry.refreshAt, refreshDeadline(result, model, entry.fetchedAt, enabled));
7164
7426
  this.schedule(entry.refreshAt);
7165
7427
  return result;
7166
7428
  }
7167
- if (entry.snapshot !== void 0) entry.refreshAt = Math.min(entry.refreshAt, refreshDeadline(entry.snapshot, model, entry.fetchedAt));
7429
+ if (entry.snapshot !== void 0) entry.refreshAt = Math.min(entry.refreshAt, refreshDeadline(entry.snapshot, model, entry.fetchedAt, enabled));
7168
7430
  if (Date.now() < entry.refreshAt) {
7169
7431
  this.schedule(entry.refreshAt);
7170
7432
  if (entry.error !== void 0) throw entry.error;
@@ -7179,14 +7441,16 @@ var OpenAICodexQuotaState = class {
7179
7441
  current.snapshot = result;
7180
7442
  delete current.error;
7181
7443
  current.fetchedAt = Date.now();
7182
- current.refreshAt = refreshDeadline(result, model, current.fetchedAt);
7444
+ current.failures = 0;
7445
+ current.refreshAt = refreshDeadline(result, model, current.fetchedAt, enabled);
7183
7446
  this.schedule(current.refreshAt);
7184
7447
  return result;
7185
7448
  }, (error) => {
7186
7449
  epoch.throwIfAborted();
7187
7450
  delete current.snapshot;
7188
7451
  current.error = safeFailure(error);
7189
- current.refreshAt = Date.now() + 5e3;
7452
+ current.failures += 1;
7453
+ current.refreshAt = Date.now() + failureDelay(current.error, current.failures);
7190
7454
  this.schedule(current.refreshAt);
7191
7455
  throw current.error;
7192
7456
  }).finally(() => {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-codex-connect",
3
3
  "displayName": "Codex Connect",
4
4
  "description": "ChatGPT OAuth and Codex models for DeepSeek Harness.",
5
- "version": "0.1.0-alpha.4.37",
5
+ "version": "0.1.0-alpha.4.38",
6
6
  "author": "Frank Song",
7
7
  "contributors": [
8
8
  "Yan-Zero (original dsh-codex author)"