dsh-codex-subscription 1.0.6 → 1.1.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/AGENTS.md CHANGED
@@ -6,7 +6,7 @@ Use this guide when a user asks an Agent to install, update, verify, or remove
6
6
  ## Safety
7
7
 
8
8
  - Confirm the target DSH installation and profile. Use `web` only when it is the user's target.
9
- - Use the exact `1.0.6` package below; do not install a moving branch.
9
+ - Use the exact `1.1.0` package below; do not install a moving branch.
10
10
  - Never print OAuth credentials, account IDs, authorization callbacks, or credential-store contents.
11
11
  - Preserve the DSH profile, unrelated plugins, sessions, and saved sign-in.
12
12
  - Do not start, stop, or restart DSH without explicit permission.
@@ -18,6 +18,7 @@ On Windows, check the command and common Portable entry points without recursive
18
18
 
19
19
  ```powershell
20
20
  Get-Command dsh -ErrorAction SilentlyContinue
21
+ Get-Command npx -ErrorAction SilentlyContinue
21
22
  Test-Path -LiteralPath '.\dsh.exe' -PathType Leaf
22
23
  Test-Path -LiteralPath "$env:USERPROFILE\Downloads\DSH-Portable\dsh.exe" -PathType Leaf
23
24
  ```
@@ -28,28 +29,29 @@ instead of recreating its package-manager environment in this installer.
28
29
 
29
30
  ## Install or update
30
31
 
31
- With an existing `dsh` command:
32
+ For an official DSH installation run through npm, keep the complete `npx` prefix because the official
33
+ run command does not create a global `dsh` command:
32
34
 
33
35
  ```sh
34
- dsh plugin --profile web add dsh-codex-subscription@1.0.6
36
+ npx -y @deepseek-ai/dsh plugin --profile web add dsh-codex-subscription@1.1.0
37
+ ```
38
+
39
+ With an existing global `dsh` command:
40
+
41
+ ```sh
42
+ dsh plugin --profile web add dsh-codex-subscription@1.1.0
35
43
  ```
36
44
 
37
45
  From a DSH-Portable folder:
38
46
 
39
47
  ```powershell
40
- .\dsh.exe plugin --profile web add dsh-codex-subscription@1.0.6
48
+ .\dsh.exe plugin --profile web add dsh-codex-subscription@1.1.0
41
49
  ```
42
50
 
43
51
  Use the same `add` command to update or repair. This is the complete package-changing operation.
44
52
  Do not download pnpm, create a second package store, save profile snapshots, add a resident manager,
45
53
  or restart DSH automatically. The DSH CLI owns package resolution, locking, and profile composition.
46
54
 
47
- For a legacy installation, first add and verify the current package. Only then remove the old package:
48
-
49
- ```sh
50
- dsh plugin --profile web remove @wsl043/dsh-codex-subscription
51
- ```
52
-
53
55
  ## Verify
54
56
 
55
57
  Use the same executable selected above:
@@ -61,10 +63,11 @@ dsh --profile web --dump-config
61
63
 
62
64
  For DSH-Portable, replace `dsh` with `.\dsh.exe`. Static acceptance requires:
63
65
 
64
- 1. `dsh-codex-subscription` version `1.0.6` appears exactly once.
66
+ For the official npm route, run the same checks with `npx -y @deepseek-ai/dsh` in place of `dsh`.
67
+
68
+ 1. `dsh-codex-subscription` version `1.1.0` appears exactly once.
65
69
  2. `codex-subscription` appears exactly once in the composed config.
66
- 3. The legacy package is absent after a migration.
67
- 4. No unrelated plugin or profile was changed and DSH was not restarted.
70
+ 3. No unrelated plugin or profile was changed and DSH was not restarted.
68
71
 
69
72
  With permission to restart DSH, open **Settings -> Codex** and verify the settings page loads. Confirm
70
73
  that search offers both **DSH default** and **Codex subscription**, the composer quota switch defaults
@@ -83,6 +86,12 @@ For DSH-Portable:
83
86
  .\dsh.exe plugin --profile web remove dsh-codex-subscription
84
87
  ```
85
88
 
89
+ For the official npm route:
90
+
91
+ ```sh
92
+ npx -y @deepseek-ai/dsh plugin --profile web remove dsh-codex-subscription
93
+ ```
94
+
86
95
  Uninstall removes only this plugin. It must preserve the profile, sessions, other plugins, and saved
87
96
  ChatGPT sign-in. Signing out is a separate action and requires explicit permission.
88
97
 
package/README.en.md CHANGED
@@ -8,6 +8,7 @@ No OpenAI API key or Codex CLI. Models, search, quota, and image generation stay
8
8
 
9
9
  [![CI](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml/badge.svg)](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml)
10
10
  [![npm](https://img.shields.io/npm/v/dsh-codex-subscription?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-codex-subscription)
11
+ [![npm downloads](https://img.shields.io/npm/dt/dsh-codex-subscription?logo=npm&label=downloads)](https://www.npmjs.com/package/dsh-codex-subscription)
11
12
  [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
12
13
  [![Star](https://img.shields.io/github/stars/WSL043/dsh-codex-subscription?style=flat&logo=github&label=Star)](https://github.com/WSL043/dsh-codex-subscription/stargazers)
13
14
 
@@ -27,8 +28,9 @@ No OpenAI API key or Codex CLI. Models, search, quota, and image generation stay
27
28
  | **Subscription models** | Sign in to ChatGPT and use Codex without an OpenAI API key or Codex CLI |
28
29
  | **Subscription search** | Explicitly choose DSH default search or Codex subscription search |
29
30
  | **Visible quota** | Keep backend-provided standard Codex, Spark, and other limits separate |
31
+ | **Fast mode (Beta)** | Switch between Standard and Fast directly in the composer |
30
32
 
31
- All four capabilities reuse the same local ChatGPT sign-in. Subscription routing failures stay visible and never silently switch to another paid route.
33
+ These capabilities reuse the same local ChatGPT sign-in. Subscription routing failures stay visible and never silently switch to another paid route.
32
34
 
33
35
  ## Product screen
34
36
 
@@ -45,7 +47,7 @@ All four capabilities reuse the same local ChatGPT sign-in. Subscription routing
45
47
 
46
48
  ## Prepare DSH
47
49
 
48
- This plugin supports DeepSeek Harness `0.1.0-rc.6`, `0.1.0-rc.7`, and `0.1.0-rc.8`, and requires a ChatGPT account that currently has Codex access.
50
+ This plugin is currently compatible through DeepSeek Harness `0.1.0-rc.8` and requires a ChatGPT account that currently has Codex access.
49
51
 
50
52
  - Do not want to configure Node.js? Use the [community DSH-Portable package](https://github.com/WSL043/DSH-Portable).
51
53
  - Prefer the official route? Follow the [DeepSeek Harness run guide](https://github.com/deepseek-ai/deepseek-harness#run).
@@ -78,7 +80,20 @@ operation once. It does not recursively scan disks, install pnpm, create a resid
78
80
  profile, or download the plugin twice. It needs no administrator access and never restarts DSH.
79
81
 
80
82
  <details>
81
- <summary>macOS, Linux, or an existing <code>dsh</code> command (universal install)</summary>
83
+ <summary>Official npm route (Node.js installed)</summary>
84
+
85
+ The official `npx @deepseek-ai/dsh web` command does not create a global `dsh` command. Keep the full `npx` prefix when installing the plugin:
86
+
87
+ ```sh
88
+ npx -y @deepseek-ai/dsh plugin --profile web add dsh-codex-subscription
89
+ npx -y @deepseek-ai/dsh plugin --profile web list dsh-codex-subscription --depth 0
90
+ npx -y @deepseek-ai/dsh --profile web --dump-config
91
+ ```
92
+
93
+ </details>
94
+
95
+ <details>
96
+ <summary>An existing <code>dsh</code> command or a DSH-Portable folder</summary>
82
97
 
83
98
  ```sh
84
99
  dsh plugin --profile web add dsh-codex-subscription
@@ -86,8 +101,8 @@ dsh plugin --profile web list dsh-codex-subscription --depth 0
86
101
  dsh --profile web --dump-config
87
102
  ```
88
103
 
89
- The plugin list should contain one `dsh-codex-subscription`, and the config should contain one
90
- `codex-subscription` entry.
104
+ For DSH-Portable, replace `dsh` with `./dsh` (`.\dsh.exe` in PowerShell). The plugin list should contain
105
+ one `dsh-codex-subscription`, and the config should contain one `codex-subscription` entry.
91
106
 
92
107
  </details>
93
108
 
@@ -106,6 +121,7 @@ Restart DSH manually after installation, then:
106
121
  - Actual backend-provided quota, reset time, and freshness;
107
122
  - Separate standard Codex, Codex-Spark, Credits, and other independent limits;
108
123
  - Optional composer quota for the selected Codex model (Beta, off by default);
124
+ - Standard or Fast mode for supported Codex models directly in the composer (Beta);
109
125
  - Visible errors when subscription routing is unavailable, with no silent paid fallback.
110
126
 
111
127
  ### Composer quota
@@ -118,10 +134,24 @@ The compact percentage appears only for a selected Codex model. Standard Codex u
118
134
  window returned by the service; Spark uses its independent quota. The plugin does not hard-code a
119
135
  “5-hour + weekly” layout or invent Credits and spending caps that the service did not return.
120
136
 
137
+ ### Composer speed (Beta)
138
+
139
+ With a supported Codex model selected, `1×` or a lightning icon appears immediately before the model name.
140
+ Click it to choose Standard or Fast. Spark does not show this control. Fast mode increases speed and uses more Credits;
141
+ see the [OpenAI Codex Speed documentation](https://learn.chatgpt.com/docs/agent-configuration/speed) for the current rules.
142
+
121
143
  ## Update and uninstall
122
144
 
123
- On Windows, rerun the one-line setup above to update. Use the official DSH command to uninstall. Both
124
- operations preserve the DSH profile, other plugins, and saved sign-in.
145
+ On Windows, rerun the one-line setup above to update. For the official npm route, use:
146
+
147
+ ```sh
148
+ npx -y @deepseek-ai/dsh plugin --profile web update dsh-codex-subscription
149
+ npx -y @deepseek-ai/dsh plugin --profile web list dsh-codex-subscription --depth 0
150
+ npx -y @deepseek-ai/dsh --profile web --dump-config
151
+ npx -y @deepseek-ai/dsh plugin --profile web remove dsh-codex-subscription
152
+ ```
153
+
154
+ These operations preserve the DSH profile, other plugins, and saved sign-in.
125
155
 
126
156
  <details>
127
157
  <summary>Update or uninstall with an existing <code>dsh</code> command</summary>
@@ -133,18 +163,14 @@ dsh --profile web --dump-config
133
163
  dsh plugin --profile web remove dsh-codex-subscription
134
164
  ```
135
165
 
136
- When migrating manually from v0.2.1, remove the old package only after the new one installs successfully:
137
-
138
- ```sh
139
- dsh plugin --profile web remove @wsl043/dsh-codex-subscription
140
- ```
141
-
142
166
  </details>
143
167
 
144
168
  From a DSH-Portable folder, replace `dsh` above with `.\dsh.exe`.
145
169
 
146
170
  ## Troubleshooting
147
171
 
172
+ - **`dsh` is not recognized:** the official npm route does not create a global `dsh` command; use the complete `npx -y @deepseek-ai/dsh ...` command above;
173
+ - **`dsh.exe` is not recognized:** that file is not in the current folder; enter the DSH-Portable folder first or use the one-line Windows setup;
148
174
  - **DSH-Portable is not found:** enter its folder and rerun setup, or run `.\dsh.exe plugin --profile web add dsh-codex-subscription` directly;
149
175
  - **More than one DSH exists:** enter the intended DSH-Portable folder before running setup;
150
176
  - **Setup still fails:** send the Agent guide above to an Agent. Do not delete the profile or change the system PATH to force an install.
package/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
 
10
10
  [![CI](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml/badge.svg)](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml)
11
11
  [![npm](https://img.shields.io/npm/v/dsh-codex-subscription?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-codex-subscription)
12
+ [![npm downloads](https://img.shields.io/npm/dt/dsh-codex-subscription?logo=npm&label=downloads)](https://www.npmjs.com/package/dsh-codex-subscription)
12
13
  [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
13
14
  [![Star](https://img.shields.io/github/stars/WSL043/dsh-codex-subscription?style=flat&logo=github&label=Star)](https://github.com/WSL043/dsh-codex-subscription/stargazers)
14
15
 
@@ -28,6 +29,7 @@
28
29
  | **订阅模型直连** | 登录 ChatGPT 后直接使用 Codex,不需要 OpenAI API Key 或 Codex CLI |
29
30
  | **订阅搜索** | 可在 DSH 默认搜索与 Codex 订阅搜索之间明确切换 |
30
31
  | **额度可见** | 普通 Codex、Spark 等服务端实际返回的额度分开显示 |
32
+ | **高速模式(Beta)** | 直接在输入框切换标准或高速,无需离开当前会话 |
31
33
 
32
34
  这些能力共用同一份本机 ChatGPT 登录。订阅路由失败时会明确报错,不会静默切换到其他付费路由。
33
35
 
@@ -46,7 +48,7 @@
46
48
 
47
49
  ## 准备 DSH
48
50
 
49
- 本插件适配 DeepSeek Harness `0.1.0-rc.6`、`0.1.0-rc.7` 与 `0.1.0-rc.8`,并需要一个当前具有 Codex 使用资格的 ChatGPT 账户。
51
+ 本插件当前适配到 DeepSeek Harness `0.1.0-rc.8`,并需要一个当前具有 Codex 使用资格的 ChatGPT 账户。
50
52
 
51
53
  - 不想配置 Node.js:使用 [DSH-Portable(社区便携包)](https://github.com/WSL043/DSH-Portable);
52
54
  - 想按官方方式运行:查看 [DeepSeek Harness 官方说明](https://github.com/deepseek-ai/deepseek-harness#run)。
@@ -79,7 +81,20 @@ irm 'https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/d
79
81
  无需管理员权限,也不会擅自重启 DSH。
80
82
 
81
83
  <details>
82
- <summary>macOS、Linux,或已有 <code>dsh</code> 命令(通用安装)</summary>
84
+ <summary>官方 npm 方式(已安装 Node.js)</summary>
85
+
86
+ 官方的 `npx @deepseek-ai/dsh web` 不会创建全局 `dsh` 命令,因此安装插件时也要保留完整的 `npx` 前缀:
87
+
88
+ ```sh
89
+ npx -y @deepseek-ai/dsh plugin --profile web add dsh-codex-subscription
90
+ npx -y @deepseek-ai/dsh plugin --profile web list dsh-codex-subscription --depth 0
91
+ npx -y @deepseek-ai/dsh --profile web --dump-config
92
+ ```
93
+
94
+ </details>
95
+
96
+ <details>
97
+ <summary>已经能运行 <code>dsh</code>,或位于 DSH-Portable 目录</summary>
83
98
 
84
99
  ```sh
85
100
  dsh plugin --profile web add dsh-codex-subscription
@@ -87,8 +102,8 @@ dsh plugin --profile web list dsh-codex-subscription --depth 0
87
102
  dsh --profile web --dump-config
88
103
  ```
89
104
 
90
- 安装列表中应只有一个 `dsh-codex-subscription`,配置中应只有一个
91
- `codex-subscription` 条目。
105
+ DSH-Portable 请把 `dsh` 换成 `./dsh`(PowerShell 使用 `.\dsh.exe`)。安装列表中应只有一个
106
+ `dsh-codex-subscription`,配置中应只有一个 `codex-subscription` 条目。
92
107
 
93
108
  </details>
94
109
 
@@ -107,6 +122,7 @@ dsh --profile web --dump-config
107
122
  - 设置页显示服务端返回的额度、重置时间和更新时间;
108
123
  - 普通 Codex、Codex-Spark、Credits 等独立额度分开显示;
109
124
  - 输入框可显示当前 Codex 模型的剩余额度(Beta,默认关闭);
125
+ - 输入框可为支持的 Codex 模型切换标准或高速模式(Beta);
110
126
  - 订阅路由不可用时明确报错,不会静默切换到其他付费路由。
111
127
 
112
128
  ### 输入框额度
@@ -118,10 +134,24 @@ dsh --profile web --dump-config
118
134
  快捷百分比只在选择 Codex 模型时显示。普通 Codex 使用服务端返回窗口中剩余最少的一项,
119
135
  Spark 使用独立额度。插件不会写死“5 小时 + 每周”,也不会虚构服务端没有返回的 Credits 或消费上限。
120
136
 
137
+ ### 输入框速度(Beta)
138
+
139
+ 选择支持的 Codex 模型后,模型名称左侧会显示 `1×` 或闪电。点击即可切换标准与高速;
140
+ Spark 不显示这个入口。高速模式会提高速度,也会消耗更多 Credits;具体规则见
141
+ [OpenAI Codex Speed 文档](https://learn.chatgpt.com/docs/agent-configuration/speed)。
142
+
121
143
  ## 更新与卸载
122
144
 
123
- Windows 用户重新运行上面的单行助手即可更新。卸载使用 DSH 官方命令;两种操作都保留
124
- DSH profile、其他插件和登录信息。
145
+ Windows 用户重新运行上面的单行助手即可更新。官方 npm 用户使用:
146
+
147
+ ```sh
148
+ npx -y @deepseek-ai/dsh plugin --profile web update dsh-codex-subscription
149
+ npx -y @deepseek-ai/dsh plugin --profile web list dsh-codex-subscription --depth 0
150
+ npx -y @deepseek-ai/dsh --profile web --dump-config
151
+ npx -y @deepseek-ai/dsh plugin --profile web remove dsh-codex-subscription
152
+ ```
153
+
154
+ 这些操作会保留 DSH profile、其他插件和登录信息。
125
155
 
126
156
  <details>
127
157
  <summary>使用现有 <code>dsh</code> 命令更新或卸载</summary>
@@ -133,18 +163,14 @@ dsh --profile web --dump-config
133
163
  dsh plugin --profile web remove dsh-codex-subscription
134
164
  ```
135
165
 
136
- 从 v0.2.1 手动迁移时,确认新包安装成功后再移除旧包名:
137
-
138
- ```sh
139
- dsh plugin --profile web remove @wsl043/dsh-codex-subscription
140
- ```
141
-
142
166
  </details>
143
167
 
144
168
  DSH-Portable 在其目录中把上述 `dsh` 换成 `.\dsh.exe`。
145
169
 
146
170
  ## 常见问题
147
171
 
172
+ - **`dsh` 无法识别**:官方 npm 方式本来就不会创建全局 `dsh` 命令,请使用上面的完整 `npx -y @deepseek-ai/dsh ...` 命令;
173
+ - **`dsh.exe` 无法识别**:当前目录没有该文件。请先进入 DSH-Portable 目录,或使用 Windows 单行助手;
148
174
  - **找不到 DSH-Portable**:进入它的目录后重新执行安装命令,或直接运行 `.\dsh.exe plugin --profile web add dsh-codex-subscription`;
149
175
  - **电脑上有多个 DSH**:进入要使用的那个 DSH-Portable 目录再运行助手;
150
176
  - **安装仍然失败**:把上面的 Agent 文档链接发给 Agent,不要删除 profile 或随意修改系统 PATH。
package/lib/client.js CHANGED
@@ -12,7 +12,13 @@ window.__ModuleLoader__.load({
12
12
  const QUICK_QUOTA_FIELD = "quickQuotaVisible";
13
13
  const SEARCH_PROVIDER_FIELD = "searchProvider";
14
14
  const SEARCH_PROVIDER_CODEX = "codex";
15
+ const SPEED_MODE_FIELD = "speedMode";
16
+ const SPEED_MODE_STANDARD = "standard";
17
+ const SPEED_MODE_FAST = "fast";
18
+ const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
15
19
  const normalizeSearchProvider = (value) => ["dsh", "codex"].includes(value) ? value : "dsh";
20
+ const normalizeSpeedMode = (value) => ["standard", "fast"].includes(value) ? value : DEFAULT_SPEED_MODE;
21
+ const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
16
22
  //#endregion
17
23
  //#region src/sidebar-quota.js
18
24
  const isDisplayableWindow = (window) => Number.isFinite(window?.remainingPercent) && window.remainingPercent >= 0 && window.remainingPercent <= 100 && Number.isFinite(window?.windowSeconds) && window.windowSeconds > 0;
@@ -62,13 +68,15 @@ window.__ModuleLoader__.load({
62
68
  deviceHint: "在登录页输入此设备代码:",
63
69
  waiting: "正在等待登录完成…",
64
70
  failed: "登录失败,请重试。",
65
- loadFailed: "无法读取 Codex 状态。",
71
+ loadFailed: "无法读取账户状态。",
72
+ accountRetry: "重试",
66
73
  searchTitle: "搜索来源",
67
74
  searchDsh: "DSH 默认",
68
75
  searchDshHint: "当前搜索服务",
69
76
  searchCodex: "Codex 订阅",
70
77
  searchCodexHint: "ChatGPT 订阅搜索",
71
- preferenceFailed: "无法更新设置,请重试。",
78
+ preferenceFailed: "设置未保存。",
79
+ preferenceRetry: "重试",
72
80
  usage: "订阅额度",
73
81
  refresh: "刷新",
74
82
  refreshing: "刷新中…",
@@ -99,6 +107,11 @@ window.__ModuleLoader__.load({
99
107
  quickQuotaSetting: "输入框额度",
100
108
  quickQuotaBeta: "Beta",
101
109
  quickQuotaStatus: "Codex 剩余额度 {value}%",
110
+ speedTitle: "速度",
111
+ speedStandard: "标准",
112
+ speedStandardHint: "标准速度",
113
+ speedFast: "高速",
114
+ speedFastHint: "1.5 倍,消耗更多 Credits",
102
115
  imageGenerate: "生成图片",
103
116
  imageGenerating: "正在生成…",
104
117
  imageGenerated: "已生成",
@@ -127,13 +140,15 @@ window.__ModuleLoader__.load({
127
140
  deviceHint: "Enter this device code on the sign-in page:",
128
141
  waiting: "Waiting for sign-in to finish…",
129
142
  failed: "Sign-in failed. Try again.",
130
- loadFailed: "Could not read Codex state.",
143
+ loadFailed: "Could not read account status.",
144
+ accountRetry: "Retry",
131
145
  searchTitle: "Search source",
132
146
  searchDsh: "DSH default",
133
147
  searchDshHint: "Current search service",
134
148
  searchCodex: "Codex subscription",
135
149
  searchCodexHint: "ChatGPT subscription search",
136
- preferenceFailed: "Could not update the setting. Try again.",
150
+ preferenceFailed: "The setting was not saved.",
151
+ preferenceRetry: "Retry",
137
152
  usage: "Subscription quota",
138
153
  refresh: "Refresh",
139
154
  refreshing: "Refreshing…",
@@ -164,6 +179,11 @@ window.__ModuleLoader__.load({
164
179
  quickQuotaSetting: "Composer quota",
165
180
  quickQuotaBeta: "Beta",
166
181
  quickQuotaStatus: "Codex quota: {value}% remaining",
182
+ speedTitle: "Speed",
183
+ speedStandard: "Standard",
184
+ speedStandardHint: "Standard speed",
185
+ speedFast: "Fast",
186
+ speedFastHint: "1.5x; higher Credits use",
167
187
  imageGenerate: "Generate image",
168
188
  imageGenerating: "Generating…",
169
189
  imageGenerated: "Generated",
@@ -191,6 +211,7 @@ window.__ModuleLoader__.load({
191
211
  .codexSubscriptionActions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.codexSubscriptionFlow{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border-radius:10px;background:var(--dsw-alias-bg-module-platform)}
192
212
  .codexSubscriptionFlow p{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCode{width:max-content;max-width:100%;font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.08em;overflow-wrap:anywhere}
193
213
  .codexSubscriptionError{font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}.codexSubscriptionInput{width:100%;box-sizing:border-box}
214
+ .codexSubscriptionRecover{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionRecover .codexSubscriptionError{flex:1}.codexSubscriptionRecover button{flex:0 0 auto}
194
215
  .codexSubscriptionSectionTitle{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px}.codexSubscriptionFreshness{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
195
216
  .codexSubscriptionRefresh{flex:0 0 auto;min-width:72px;width:max-content;white-space:nowrap!important;word-break:keep-all!important;overflow-wrap:normal!important;writing-mode:horizontal-tb!important}.codexSubscriptionRefresh *{white-space:nowrap!important;word-break:keep-all!important;writing-mode:horizontal-tb!important}
196
217
  .codexSubscriptionEmpty{padding:18px;border:1px dashed var(--dsw-alias-border-l3);border-radius:10px;text-align:center;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
@@ -202,6 +223,7 @@ window.__ModuleLoader__.load({
202
223
  .codexSubscriptionCreditBalance{display:flex;flex-direction:column;gap:6px}.codexSubscriptionCreditBalance span,.codexSubscriptionCreditLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCreditBalance strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
203
224
  .codexSubscriptionSpendLimit{display:flex;flex-direction:column;gap:8px}.codexSubscriptionSpendTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}.codexSubscriptionSpendTop strong{font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}.codexSubscriptionSpendLimit progress{width:100%;height:6px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-brand-primary,#3964fe);-webkit-appearance:none;appearance:none}.codexSubscriptionSpendLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionSpendLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionSpendLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
204
225
  .codexComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;margin-right:-4px;padding:0;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}
226
+ .codexComposerControls{display:inline-flex;align-items:center;gap:6px;flex:0 0 auto}.codexComposerSpeed{display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:28px;box-sizing:border-box;padding:0 7px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font:500 12px/20px inherit;cursor:pointer;white-space:nowrap}.codexComposerSpeed:hover,.codexComposerSpeed[aria-expanded=true]{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.codexComposerSpeed:disabled{cursor:not-allowed;opacity:.5}.codexComposerSpeedFast{font-size:14px}.codexComposerSpeedChoice{display:flex;min-width:150px;flex-direction:column;gap:1px}.codexComposerSpeedChoice strong{font-size:13px;line-height:18px;font-weight:500}.codexComposerSpeedChoice span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}
205
227
  .codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}.codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}.codexImageToolIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}.codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}.codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}.codexImageTool[data-state=error] .codexImageToolIcon::before{border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-primary)}.codexImageToolTitle{font-weight:500}.codexImageToolState{color:var(--dsw-alias-label-tertiary)}.codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}.codexImageToolGallery{margin-left:24px}.codexGeneratedImageFrame{display:flex;align-items:center;justify-content:center;width:min(240px,100%);min-height:120px;padding:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);cursor:pointer}.codexGeneratedImageFrame img{display:block;width:100%;max-height:240px;object-fit:contain}.codexGeneratedImageRetry{min-height:36px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);cursor:pointer}.codexGeneratedImageLightbox{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:32px;border:0;background:rgba(0,0,0,.72)}.codexGeneratedImageLightbox img{display:block;max-width:min(1100px,calc(100vw - 64px));max-height:calc(100vh - 64px);object-fit:contain}.codexGeneratedImageClose{position:absolute;top:16px;right:16px;width:36px;height:36px;border:1px solid rgba(255,255,255,.35);border-radius:50%;background:rgba(0,0,0,.48);color:#fff;font-size:20px;line-height:1;cursor:pointer}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
206
228
  @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
207
229
  @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
@@ -357,6 +379,7 @@ window.__ModuleLoader__.load({
357
379
  let error = false;
358
380
  let fallbackStatus = "loading";
359
381
  let fallback;
382
+ let failedPatch;
360
383
  let generation = 0;
361
384
  const nativeSnapshot = () => scope.getSnapshot();
362
385
  const read = () => {
@@ -366,6 +389,7 @@ window.__ModuleLoader__.load({
366
389
  status: updating ? "updating" : current.status,
367
390
  visible: current.value?.[QUICK_QUOTA_FIELD] === true,
368
391
  searchProvider: normalizeSearchProvider(current.value?.[SEARCH_PROVIDER_FIELD]),
392
+ speedMode: normalizeSpeedMode(current.value?.[SPEED_MODE_FIELD]),
369
393
  writable: !updating && current.status === "ready" && current.writable === true,
370
394
  error
371
395
  });
@@ -378,6 +402,7 @@ window.__ModuleLoader__.load({
378
402
  };
379
403
  const disposeScope = scope.subscribe(() => {
380
404
  error = false;
405
+ failedPatch = void 0;
381
406
  publish();
382
407
  });
383
408
  const acceptFallback = (value) => {
@@ -386,7 +411,8 @@ window.__ModuleLoader__.load({
386
411
  status: "ready",
387
412
  value: {
388
413
  [QUICK_QUOTA_FIELD]: value?.[QUICK_QUOTA_FIELD] === true,
389
- [SEARCH_PROVIDER_FIELD]: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD])
414
+ [SEARCH_PROVIDER_FIELD]: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD]),
415
+ [SPEED_MODE_FIELD]: normalizeSpeedMode(value?.[SPEED_MODE_FIELD])
390
416
  },
391
417
  writable: value?.writable === true
392
418
  };
@@ -416,6 +442,7 @@ window.__ModuleLoader__.load({
416
442
  const entries = Object.entries(patch);
417
443
  updating = true;
418
444
  error = false;
445
+ failedPatch = void 0;
419
446
  publish();
420
447
  try {
421
448
  if (nativeSnapshot().status === "ready") {
@@ -432,7 +459,10 @@ window.__ModuleLoader__.load({
432
459
  acceptFallback(value);
433
460
  }
434
461
  } catch {
435
- if (current === generation) error = true;
462
+ if (current === generation) {
463
+ error = true;
464
+ failedPatch = patch;
465
+ }
436
466
  } finally {
437
467
  if (current === generation) {
438
468
  updating = false;
@@ -448,6 +478,7 @@ window.__ModuleLoader__.load({
448
478
  },
449
479
  load,
450
480
  set,
481
+ retry: () => failedPatch === void 0 ? load() : set(failedPatch),
451
482
  dispose: disposeScope
452
483
  };
453
484
  }
@@ -573,28 +604,86 @@ window.__ModuleLoader__.load({
573
604
  preference,
574
605
  t
575
606
  }),
576
- snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
577
- className: "codexSubscriptionError",
607
+ snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
608
+ className: "codexSubscriptionRecover",
578
609
  role: "alert",
579
- children: t("preferenceFailed")
610
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
611
+ className: "codexSubscriptionError",
612
+ children: t("preferenceFailed")
613
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
614
+ type: "button",
615
+ variant: "outline",
616
+ onClick: () => {
617
+ preference.retry();
618
+ },
619
+ children: t("preferenceRetry")
620
+ })]
580
621
  }) : null
581
622
  ]
582
623
  });
583
624
  }
584
- function CodexComposerQuota({ preference, rpc, t, directory }) {
625
+ function CodexComposerControls({ preference, rpc, t, directory }) {
626
+ const [speedOpen, setSpeedOpen] = (0, react.useState)(false);
585
627
  const preferenceSnapshot = usePreferenceSnapshot(preference);
586
628
  const current = (0, react.useSyncExternalStore)((listener) => directory.subscribe(listener), () => directory.getSnapshot()).current;
587
- const enabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.visible && current?.provider === "openai-codex";
588
- const quota = useQuickQuota(rpc, enabled, current?.model);
589
- if (!enabled || quota === void 0) return null;
590
- const value = percent(quota.remainingPercent);
591
- const label = fill(t("quickQuotaStatus"), { value });
629
+ const codex = current?.provider === "openai-codex";
630
+ const speedSupported = codex && supportsCodexFastMode(current?.model);
631
+ const speedWritable = preferenceSnapshot.status === "ready" && preferenceSnapshot.writable === true;
632
+ const fast = preferenceSnapshot.speedMode === SPEED_MODE_FAST;
633
+ const quotaEnabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.visible && codex;
634
+ const quota = useQuickQuota(rpc, quotaEnabled, current?.model);
635
+ if (!speedSupported && (!quotaEnabled || quota === void 0)) return null;
636
+ const speedItems = [{
637
+ id: SPEED_MODE_STANDARD,
638
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
639
+ className: "codexComposerSpeedChoice",
640
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("speedStandard") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("speedStandardHint") })]
641
+ })
642
+ }, {
643
+ id: SPEED_MODE_FAST,
644
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
645
+ className: "codexComposerSpeedChoice",
646
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("speedFast") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("speedFastHint") })]
647
+ })
648
+ }];
649
+ const speed = speedSupported ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
650
+ open: speedOpen,
651
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
652
+ className: `codexComposerSpeed${fast ? " codexComposerSpeedFast" : ""}`,
653
+ type: "button",
654
+ "aria-label": `${t("speedTitle")}: ${t(fast ? "speedFast" : "speedStandard")}`,
655
+ "aria-expanded": speedOpen,
656
+ disabled: !speedWritable,
657
+ onClick: () => setSpeedOpen((value) => !value),
658
+ children: fast ? "⚡" : "1×"
659
+ }),
660
+ items: speedItems,
661
+ selectedId: preferenceSnapshot.speedMode,
662
+ onSelect: (id) => {
663
+ setSpeedOpen(false);
664
+ preference.set({ [SPEED_MODE_FIELD]: id });
665
+ },
666
+ onClose: () => setSpeedOpen(false),
667
+ align: "end",
668
+ side: "top",
669
+ portal: true,
670
+ compact: true
671
+ }) : null;
672
+ let quotaView = null;
673
+ if (quotaEnabled && quota !== void 0) {
674
+ const value = percent(quota.remainingPercent);
675
+ const label = fill(t("quickQuotaStatus"), { value });
676
+ quotaView = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
677
+ className: "codexComposerQuota",
678
+ role: "status",
679
+ "aria-label": label,
680
+ title: label,
681
+ children: [value, "%"]
682
+ });
683
+ }
592
684
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
593
- className: "codexComposerQuota",
594
- role: "status",
595
- "aria-label": label,
596
- title: label,
597
- children: [value, "%"]
685
+ className: "codexComposerControls",
686
+ children: [quotaView, speed]
598
687
  });
599
688
  }
600
689
  function AccountCard({ rpc, t, account, setAccount, onSignedOut }) {
@@ -767,6 +856,21 @@ window.__ModuleLoader__.load({
767
856
  ]
768
857
  });
769
858
  }
859
+ function AccountFailureCard({ retry, t }) {
860
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
861
+ className: "codexSubscriptionCard codexSubscriptionRecover",
862
+ role: "alert",
863
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
864
+ className: "codexSubscriptionError",
865
+ children: t("loadFailed")
866
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
867
+ type: "button",
868
+ variant: "outline",
869
+ onClick: retry,
870
+ children: t("accountRetry")
871
+ })]
872
+ });
873
+ }
770
874
  function ResetTime({ resetsAt, t }) {
771
875
  const date = Number.isSafeInteger(resetsAt) ? validDate(resetsAt * 1e3) : void 0;
772
876
  if (date === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetUnknown") });
@@ -948,17 +1052,23 @@ window.__ModuleLoader__.load({
948
1052
  }
949
1053
  function CodexSection({ preference, rpc, t }) {
950
1054
  const [account, setAccount] = (0, react.useState)();
951
- const [error, setError] = (0, react.useState)();
1055
+ const [accountError, setAccountError] = (0, react.useState)();
952
1056
  const [resetKey, setResetKey] = (0, react.useState)(0);
953
- (0, react.useEffect)(() => {
954
- let live = true;
1057
+ const accountRequest = (0, react.useRef)(0);
1058
+ const loadAccount = () => {
1059
+ const id = ++accountRequest.current;
1060
+ setAccount(void 0);
1061
+ setAccountError(void 0);
955
1062
  rpc.call(CHANNEL, "status", {}).then(unwrap).then((next) => {
956
- if (live) setAccount(next);
1063
+ if (accountRequest.current === id) setAccount(next);
957
1064
  }).catch(() => {
958
- if (live) setError(t("loadFailed"));
1065
+ if (accountRequest.current === id) setAccountError(true);
959
1066
  });
1067
+ };
1068
+ (0, react.useEffect)(() => {
1069
+ loadAccount();
960
1070
  return () => {
961
- live = false;
1071
+ accountRequest.current += 1;
962
1072
  };
963
1073
  }, []);
964
1074
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -968,26 +1078,24 @@ window.__ModuleLoader__.load({
968
1078
  className: "codexSubscriptionHead",
969
1079
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("title") })
970
1080
  }),
971
- error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
972
- className: "codexSubscriptionError",
973
- role: "alert",
974
- children: error
975
- }),
976
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCard, {
1081
+ accountError === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCard, {
977
1082
  rpc,
978
1083
  t,
979
1084
  account,
980
1085
  setAccount,
981
1086
  onSignedOut: () => setResetKey((value) => value + 1)
1087
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountFailureCard, {
1088
+ retry: loadAccount,
1089
+ t
982
1090
  }),
983
1091
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreferencesCard, {
984
1092
  preference,
985
1093
  t
986
1094
  }),
987
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
1095
+ account === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
988
1096
  rpc,
989
1097
  t,
990
- signedIn: account?.authenticated === true,
1098
+ signedIn: account.authenticated === true,
991
1099
  resetKey
992
1100
  })
993
1101
  ]
@@ -1041,7 +1149,7 @@ window.__ModuleLoader__.load({
1041
1149
  t,
1042
1150
  directory: ctx.modelDirectories.directoryFor(sessionId).store
1043
1151
  })
1044
- }, CodexComposerQuota));
1152
+ }, CodexComposerControls));
1045
1153
  const conversation = ctx.get("conversation");
1046
1154
  ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1047
1155
  name: "tool.call.toolview",
package/lib/index.js CHANGED
@@ -430,6 +430,17 @@ function createCodexRpcHandler(coordinator, options = {}) {
430
430
  };
431
431
  }
432
432
  //#endregion
433
+ //#region src/settings-contract.js
434
+ const SETTINGS_NAMESPACE = "codex-subscription";
435
+ const QUICK_QUOTA_FIELD = "quickQuotaVisible";
436
+ const SEARCH_PROVIDER_FIELD = "searchProvider";
437
+ const SEARCH_PROVIDER_CODEX = "codex";
438
+ const SPEED_MODE_FIELD = "speedMode";
439
+ const SPEED_MODE_STANDARD = "standard";
440
+ const SPEED_MODE_FAST = "fast";
441
+ const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
442
+ const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
443
+ //#endregion
433
444
  //#region src/pi-ai-runtime.js
434
445
  /**
435
446
  * Preserve pi-ai's native Codex OAuth provider while allowing DSH's generic
@@ -442,7 +453,7 @@ function createCodexRpcHandler(coordinator, options = {}) {
442
453
  * token for this request; login, refresh, persistence, headers, transport, and
443
454
  * model behavior remain owned by the original provider.
444
455
  */
445
- function openaiCodexSubscriptionProvider() {
456
+ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0 } = {}) {
446
457
  const provider = createOpenAICodexProvider();
447
458
  const requestToken = Object.freeze({
448
459
  name: "DSH-managed Codex OAuth request token",
@@ -455,15 +466,35 @@ function openaiCodexSubscriptionProvider() {
455
466
  };
456
467
  }
457
468
  });
469
+ const withSpeed = (model, options = {}) => {
470
+ if (resolveSpeedMode() !== "fast" || !supportsCodexFastMode(model?.id)) return options;
471
+ const onPayload = options.onPayload;
472
+ return {
473
+ ...options,
474
+ serviceTier: "fast",
475
+ async onPayload(payload, requestModel) {
476
+ const fastPayload = {
477
+ ...payload,
478
+ service_tier: "fast"
479
+ };
480
+ return {
481
+ ...await onPayload?.(fastPayload, requestModel) ?? fastPayload,
482
+ service_tier: "fast"
483
+ };
484
+ }
485
+ };
486
+ };
458
487
  return Object.freeze({
459
488
  ...provider,
460
489
  auth: Object.freeze({
461
490
  ...provider.auth,
462
491
  apiKey: requestToken
463
- })
492
+ }),
493
+ stream: (model, context, options) => provider.stream(model, context, withSpeed(model, options)),
494
+ streamSimple: (model, context, options) => provider.streamSimple(model, context, withSpeed(model, options))
464
495
  });
465
496
  }
466
- const USER_AGENT = `dsh-codex-subscription/1.0.6`;
497
+ const USER_AGENT = `dsh-codex-subscription/1.1.0`;
467
498
  //#endregion
468
499
  //#region src/codex-search.js
469
500
  const CODEX_SEARCH_PROVIDER_ID = "codex-subscription";
@@ -791,12 +822,6 @@ function createCodexImageTool(options) {
791
822
  });
792
823
  }
793
824
  //#endregion
794
- //#region src/settings-contract.js
795
- const SETTINGS_NAMESPACE = "codex-subscription";
796
- const QUICK_QUOTA_FIELD = "quickQuotaVisible";
797
- const SEARCH_PROVIDER_FIELD = "searchProvider";
798
- const SEARCH_PROVIDER_CODEX = "codex";
799
- //#endregion
800
825
  //#region src/usage.js
801
826
  const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
802
827
  const DEFAULT_TTL_MS = 6e4;
@@ -1012,6 +1037,10 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, preferences })
1012
1037
  if (!["dsh", "codex"].includes(payload["searchProvider"])) return publicError("internal", "Invalid search provider preference");
1013
1038
  patch[SEARCH_PROVIDER_FIELD] = payload[SEARCH_PROVIDER_FIELD];
1014
1039
  }
1040
+ if (Object.hasOwn(payload ?? {}, "speedMode")) {
1041
+ if (!["standard", "fast"].includes(payload["speedMode"])) return publicError("internal", "Invalid speed mode preference");
1042
+ patch[SPEED_MODE_FIELD] = payload[SPEED_MODE_FIELD];
1043
+ }
1015
1044
  if (Object.keys(patch).length === 0) return publicError("internal", "Invalid preference update");
1016
1045
  await preferences.update(patch);
1017
1046
  }
@@ -1063,19 +1092,21 @@ function createSearchProviderSwitcher(loader) {
1063
1092
  function apply(ctx) {
1064
1093
  const settings = ctx.settings.register(settingsNamespace(SETTINGS_NAMESPACE), z.object({
1065
1094
  [QUICK_QUOTA_FIELD]: z.boolean().default(false),
1066
- [SEARCH_PROVIDER_FIELD]: z.union(["dsh", SEARCH_PROVIDER_CODEX]).default("dsh")
1095
+ [SEARCH_PROVIDER_FIELD]: z.union(["dsh", SEARCH_PROVIDER_CODEX]).default("dsh"),
1096
+ [SPEED_MODE_FIELD]: z.union([SPEED_MODE_STANDARD, SPEED_MODE_FAST]).default(DEFAULT_SPEED_MODE)
1067
1097
  }));
1068
1098
  const searchProvider = createSearchProviderSwitcher(ctx.loader);
1069
1099
  const preferences = {
1070
1100
  status: () => ({
1071
1101
  [QUICK_QUOTA_FIELD]: settings.get()[QUICK_QUOTA_FIELD],
1072
1102
  [SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
1103
+ [SPEED_MODE_FIELD]: settings.get()[SPEED_MODE_FIELD],
1073
1104
  writable: ctx.settings.writable
1074
1105
  }),
1075
1106
  update: (patch) => settings.update(patch)
1076
1107
  };
1077
1108
  const store = new DshOAuthCredentialStore(ctx.credentials, CREDENTIAL_REF, [LEGACY_CREDENTIAL_REF]);
1078
- const provider = openaiCodexSubscriptionProvider();
1109
+ const provider = openaiCodexSubscriptionProvider({ resolveSpeedMode: () => settings.get()[SPEED_MODE_FIELD] });
1079
1110
  const authModels = createModels({ credentials: store });
1080
1111
  authModels.setProvider(provider);
1081
1112
  const profile = Object.freeze({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "packageManager": "pnpm@11.19.0",
5
5
  "description": "ChatGPT and Codex subscriptions for DeepSeek Harness with OAuth login, image generation, quota, and subscription web search",
6
6
  "type": "module",