dsh-codex-subscription 1.0.5 → 1.1.0-beta.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.5` package below; do not install a moving branch.
9
+ - Use the exact `1.0.6` 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.
@@ -31,13 +31,13 @@ instead of recreating its package-manager environment in this installer.
31
31
  With an existing `dsh` command:
32
32
 
33
33
  ```sh
34
- dsh plugin --profile web add dsh-codex-subscription@1.0.5
34
+ dsh plugin --profile web add dsh-codex-subscription@1.0.6
35
35
  ```
36
36
 
37
37
  From a DSH-Portable folder:
38
38
 
39
39
  ```powershell
40
- .\dsh.exe plugin --profile web add dsh-codex-subscription@1.0.5
40
+ .\dsh.exe plugin --profile web add dsh-codex-subscription@1.0.6
41
41
  ```
42
42
 
43
43
  Use the same `add` command to update or repair. This is the complete package-changing operation.
@@ -61,7 +61,7 @@ dsh --profile web --dump-config
61
61
 
62
62
  For DSH-Portable, replace `dsh` with `.\dsh.exe`. Static acceptance requires:
63
63
 
64
- 1. `dsh-codex-subscription` version `1.0.5` appears exactly once.
64
+ 1. `dsh-codex-subscription` version `1.0.6` appears exactly once.
65
65
  2. `codex-subscription` appears exactly once in the composed config.
66
66
  3. The legacy package is absent after a migration.
67
67
  4. No unrelated plugin or profile was changed and DSH was not restarted.
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
 
@@ -52,6 +54,12 @@ This plugin supports DeepSeek Harness `0.1.0-rc.6`, `0.1.0-rc.7`, and `0.1.0-rc.
52
54
 
53
55
  ## Install
54
56
 
57
+ To preview the composer Fast mode without replacing npm `latest`, install the Beta explicitly:
58
+
59
+ ```sh
60
+ dsh plugin --profile web add dsh-codex-subscription@1.1.0-beta.0
61
+ ```
62
+
55
63
  ### Let an Agent install it (recommended)
56
64
 
57
65
  Send this link directly to your Agent:
@@ -106,6 +114,7 @@ Restart DSH manually after installation, then:
106
114
  - Actual backend-provided quota, reset time, and freshness;
107
115
  - Separate standard Codex, Codex-Spark, Credits, and other independent limits;
108
116
  - Optional composer quota for the selected Codex model (Beta, off by default);
117
+ - Standard or Fast mode for supported Codex models directly in the composer (Beta);
109
118
  - Visible errors when subscription routing is unavailable, with no silent paid fallback.
110
119
 
111
120
  ### Composer quota
@@ -118,6 +127,12 @@ The compact percentage appears only for a selected Codex model. Standard Codex u
118
127
  window returned by the service; Spark uses its independent quota. The plugin does not hard-code a
119
128
  “5-hour + weekly” layout or invent Credits and spending caps that the service did not return.
120
129
 
130
+ ### Composer speed (Beta)
131
+
132
+ With a supported Codex model selected, `1×` or a lightning icon appears immediately before the model name.
133
+ Click it to choose Standard or Fast. Spark does not show this control. Fast mode increases speed and uses more Credits;
134
+ see the [OpenAI Codex Speed documentation](https://learn.chatgpt.com/docs/agent-configuration/speed) for the current rules.
135
+
121
136
  ## Update and uninstall
122
137
 
123
138
  On Windows, rerun the one-line setup above to update. Use the official DSH command to uninstall. Both
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
 
@@ -53,6 +55,12 @@
53
55
 
54
56
  ## 安装
55
57
 
58
+ 想先体验输入框高速模式,可安装 Beta;它不会替换 npm 的稳定版:
59
+
60
+ ```sh
61
+ dsh plugin --profile web add dsh-codex-subscription@1.1.0-beta.0
62
+ ```
63
+
56
64
  ### 交给 Agent(推荐)
57
65
 
58
66
  把这个链接直接发给 Agent:
@@ -107,6 +115,7 @@ dsh --profile web --dump-config
107
115
  - 设置页显示服务端返回的额度、重置时间和更新时间;
108
116
  - 普通 Codex、Codex-Spark、Credits 等独立额度分开显示;
109
117
  - 输入框可显示当前 Codex 模型的剩余额度(Beta,默认关闭);
118
+ - 输入框可为支持的 Codex 模型切换标准或高速模式(Beta);
110
119
  - 订阅路由不可用时明确报错,不会静默切换到其他付费路由。
111
120
 
112
121
  ### 输入框额度
@@ -118,6 +127,12 @@ dsh --profile web --dump-config
118
127
  快捷百分比只在选择 Codex 模型时显示。普通 Codex 使用服务端返回窗口中剩余最少的一项,
119
128
  Spark 使用独立额度。插件不会写死“5 小时 + 每周”,也不会虚构服务端没有返回的 Credits 或消费上限。
120
129
 
130
+ ### 输入框速度(Beta)
131
+
132
+ 选择支持的 Codex 模型后,模型名称左侧会显示 `1×` 或闪电。点击即可切换标准与高速;
133
+ Spark 不显示这个入口。高速模式会提高速度,也会消耗更多 Credits;具体规则见
134
+ [OpenAI Codex Speed 文档](https://learn.chatgpt.com/docs/agent-configuration/speed)。
135
+
121
136
  ## 更新与卸载
122
137
 
123
138
  Windows 用户重新运行上面的单行助手即可更新。卸载使用 DSH 官方命令;两种操作都保留
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.5`;
497
+ const USER_AGENT = `dsh-codex-subscription/1.1.0-beta.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.5",
3
+ "version": "1.1.0-beta.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",