dsh-codex-subscription 1.8.0 → 1.10.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.8.0` package below; do not install a moving branch.
9
+ - Use the exact `1.10.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.
@@ -34,13 +34,13 @@ For an official DSH installation run through npm, keep the complete `npx` prefix
34
34
  run command does not create a global `dsh` command:
35
35
 
36
36
  ```sh
37
- npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.8.0
37
+ npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.10.0
38
38
  ```
39
39
 
40
40
  With an existing global `dsh` command:
41
41
 
42
42
  ```sh
43
- dsh plugin --profile web add dsh-codex-subscription@1.8.0
43
+ dsh plugin --profile web add dsh-codex-subscription@1.10.0
44
44
  ```
45
45
 
46
46
  Use the same `add` command to update or repair. This is the complete package-changing operation.
@@ -58,12 +58,12 @@ dsh --profile web --dump-config
58
58
 
59
59
  For the official npm route, run the same checks with `npx -y @deepseek-ai/dsh@0.1.1-rc.2` in place of `dsh`.
60
60
 
61
- 1. `dsh-codex-subscription` version `1.8.0` appears exactly once.
61
+ 1. `dsh-codex-subscription` version `1.10.0` appears exactly once.
62
62
  2. `codex-subscription` appears exactly once in the composed config.
63
63
  3. No unrelated plugin or profile was changed and DSH was not restarted.
64
64
 
65
65
  With permission to restart DSH, open **Settings -> Codex** and verify the settings page loads. Confirm
66
- that search offers both **DSH default** and **Codex subscription**, the composer quota switch defaults
66
+ that search offers **Auto**, **DSH default**, and **Codex subscription**, the composer quota switch defaults
67
67
  to off, and `codex_image_generate` is available. Do not consume quota merely to test installation unless
68
68
  the user explicitly asks for a live model, search, or image-generation check.
69
69
 
package/README.md CHANGED
@@ -48,7 +48,7 @@ DSH-Portable exposes the same standard plugin command, so the command above also
48
48
  | **Visible quota** | Keep backend-provided standard Codex, Spark, and other limits separate |
49
49
  | **Composer quota** | Choose a compact percentage, progress bar, or no inline quota display |
50
50
  | **Safe quota reset** | See the next expiry and deliberately try a reset with a cooldown and acknowledgement |
51
- | **Subscription search** | Explicitly choose DSH default search or Codex subscription search |
51
+ | **Subscription search** | Explicitly route search globally through DSH default search or the signed-in Codex subscription |
52
52
  | **Codex image generation and editing (Beta)** | Generate without references, or explicitly edit one selected image; preview, zoom, annotate regions, download the original, and continue in the same composer |
53
53
  | **Fast mode** | Switch between Standard and Fast directly in the composer |
54
54
  | **Model-aware context** | Keep catalog defaults, use each model's supported extended window, or enter a full numeric token limit for each model |
@@ -58,21 +58,16 @@ These capabilities reuse the same local ChatGPT sign-in. Subscription routing fa
58
58
  ## Product screen
59
59
 
60
60
  <p align="center">
61
- <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/settings-focus-en.png" width="820" alt="Codex subscription settings in DeepSeek Harness">
61
+ <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/context-settings-en.png" width="820" alt="Current Codex subscription settings in DeepSeek Harness with search, model-aware context, composer quota, and support diagnostics">
62
62
  </p>
63
63
 
64
- <details>
65
- <summary>View the complete settings screen</summary>
66
-
67
- ![Complete Codex subscription settings in DeepSeek Harness](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/settings-en.png)
68
-
69
- </details>
64
+ Captured from the installed official DeepSeek Harness `0.1.1-rc.2` product with the current plugin build.
70
65
 
71
66
  ## Prepare DSH
72
67
 
73
- This plugin is currently compatible through DeepSeek Harness `0.1.1-rc.2` and requires a ChatGPT account that currently has Codex access.
68
+ This plugin supports the latest DeepSeek Harness release recorded in its package metadata and requires a ChatGPT account that currently has Codex access.
74
69
 
75
- - Do not want to configure Node.js? Use [DSH-Portable](https://github.com/WSL043/DSH-Portable), a community desktop distribution with portable and installed editions for Windows plus desktop packages for macOS and Linux.
70
+ - Do not want to configure Node.js? Use [DSH-Portable](https://github.com/WSL043/DSH-Portable), a community portable desktop distribution for Windows, macOS, and Linux.
76
71
  - Prefer the official route? Follow the [DeepSeek Harness run guide](https://github.com/deepseek-ai/deepseek-harness#run).
77
72
 
78
73
  ## Install
@@ -142,7 +137,7 @@ Restart DSH manually after installation, then:
142
137
 
143
138
  - ChatGPT OAuth sign-in with credentials kept on the host;
144
139
  - Codex models and Beta image generation/editing directly inside DSH conversations;
145
- - A clear choice between DSH default search and Codex subscription search;
140
+ - A clear global choice between DSH default search and Codex subscription search; it applies to every model and session rather than following the selected model;
146
141
  - Actual backend-provided quota, reset time, and freshness;
147
142
  - Separate standard Codex, Codex-Spark, Credits, and other independent limits;
148
143
  - Visible quota-reset count and earliest expiry, with deliberate early redemption, layered confirmation, and no automatic retry;
@@ -224,9 +219,10 @@ dsh plugin --profile web remove dsh-codex-subscription
224
219
  The ChatGPT Codex backend and DSH can change independently. This community project is not affiliated with or endorsed by DeepSeek or OpenAI.
225
220
 
226
221
  Use the [bug report form](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml) for project feedback.
222
+ Use the [feature request form](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=feature-request.yml) for focused product suggestions.
227
223
  For DSH plugin discussion, visit [DeepSeek Harness Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions).
228
224
  Read [SECURITY.md](SECURITY.md) before reporting sensitive issues.
229
225
 
230
226
  If this project is useful, the [Star button](https://github.com/WSL043/dsh-codex-subscription/stargazers) helps more DSH users find it.
231
227
 
232
- [简体中文](README.md) · [MIT](LICENSE)
228
+ [简体中文](README.zh-CN.md) · [MIT](LICENSE)
package/README.zh-CN.md CHANGED
@@ -49,8 +49,8 @@ DSH-Portable 也提供相同的标准插件命令,因此同样使用上面的
49
49
  | **额度可见** | 普通 Codex、Spark 等服务端实际返回的额度分开显示 |
50
50
  | **输入框额度** | 可选择紧凑百分比、进度条或完全关闭输入框额度显示 |
51
51
  | **安全额度重置** | 直接查看最早到期时间,并通过冷静期和知情确认主动尝试重置 |
52
- | **订阅搜索** | 可在 DSH 默认搜索与 Codex 订阅搜索之间明确切换 |
53
- | **Codex 图片生成与编辑(Beta)** | 可无参考图全新生成,也可明确选择会话图片继续编辑;支持预览、缩放和下载原图 |
52
+ | **订阅搜索** | 可将全部模型的搜索明确路由到 DSH 默认搜索或已登录的 Codex 订阅 |
53
+ | **Codex 图片生成与编辑(Beta)** | 可无参考图全新生成,也可明确选择会话图片继续编辑;支持预览、缩放、区域备注、下载原图并衔接回当前输入框 |
54
54
  | **高速模式** | 直接在输入框切换标准或高速,无需离开当前会话 |
55
55
  | **模型感知上下文** | 可保留目录默认值、按模型启用扩展窗口,或为每个模型填写完整数字 Token 上限 |
56
56
 
@@ -59,21 +59,16 @@ DSH-Portable 也提供相同的标准插件命令,因此同样使用上面的
59
59
  ## 实际界面
60
60
 
61
61
  <p align="center">
62
- <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/settings-focus.png" width="820" alt="DeepSeek Harness 中的 Codex 订阅设置">
62
+ <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/context-settings.png" width="820" alt="当前 DeepSeek Harness Codex 订阅设置,包含搜索来源、模型感知上下文、输入框额度和支持诊断">
63
63
  </p>
64
64
 
65
- <details>
66
- <summary>查看完整设置界面</summary>
67
-
68
- ![DeepSeek Harness 的完整 Codex 订阅设置](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/settings.png)
69
-
70
- </details>
65
+ 图片来自已安装的官方 DeepSeek Harness `0.1.1-rc.2` 与当前插件构建。
71
66
 
72
67
  ## 准备 DSH
73
68
 
74
- 本插件当前适配到 DeepSeek Harness `0.1.1-rc.2`,并需要一个当前具有 Codex 使用资格的 ChatGPT 账户。
69
+ 本插件支持软件包元数据中记录的最新版 DeepSeek Harness,并需要一个当前具有 Codex 使用资格的 ChatGPT 账户。
75
70
 
76
- - 不想配置 Node.js:使用 [DSH-Portable](https://github.com/WSL043/DSH-Portable)。这是社区桌面分发,提供 Windows 便携版和安装版,以及 macOS、Linux 桌面包;
71
+ - 不想配置 Node.js:使用 [DSH-Portable](https://github.com/WSL043/DSH-Portable)。这是面向 Windows、macOS Linux 的社区便携桌面分发;
77
72
  - 想按官方方式运行:查看 [DeepSeek Harness 官方说明](https://github.com/deepseek-ai/deepseek-harness#run)。
78
73
 
79
74
  ## 安装
@@ -142,7 +137,7 @@ dsh --profile web --dump-config
142
137
 
143
138
  - ChatGPT OAuth 登录,凭据保留在本机;
144
139
  - Codex 模型和 Beta 图片生成与编辑直接出现在 DSH 会话中;
145
- - DSH 默认搜索与 Codex 订阅搜索可随时切换;
140
+ - 搜索来源是全局设置,可在 DSH 默认搜索与 Codex 订阅搜索之间切换;它对所有模型和会话生效,不会随当前模型自动切换;
146
141
  - 设置页显示服务端返回的额度、重置时间和更新时间;
147
142
  - 普通 Codex、Codex-Spark、Credits 等独立额度分开显示;
148
143
  - 显示重置卡数量与最早到期时间,也允许在额度未完全用尽时主动尝试,并经过分层确认且不会自动重试;
@@ -222,6 +217,7 @@ dsh plugin --profile web remove dsh-codex-subscription
222
217
  ChatGPT Codex 后端和 DSH 可能独立变化;本项目为社区项目,与 DeepSeek、OpenAI 无隶属或背书关系。
223
218
 
224
219
  本项目的问题反馈请使用[使用问题表单](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml);
220
+ 明确的产品建议请使用[功能建议表单](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=feature-request.yml);
225
221
  DSH 插件交流可前往 [DeepSeek Harness Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions)。
226
222
  敏感问题请先阅读 [SECURITY.md](SECURITY.md)。
227
223
 
package/lib/client.js CHANGED
@@ -49,17 +49,35 @@ window.__ModuleLoader__.load({
49
49
  }
50
50
  const ForwardRef = /*#__PURE__*/ react.forwardRef(BoltIcon);
51
51
  //#endregion
52
+ //#region src/image-edit.js
53
+ function buildImageEditDraft({ prompt = "", annotations = [], translate }) {
54
+ if (typeof translate !== "function") throw new TypeError("translate must be a function");
55
+ const notes = annotations.map((annotation, index) => ({
56
+ number: index + 1,
57
+ note: typeof annotation?.note === "string" ? annotation.note.trim() : ""
58
+ })).filter((annotation) => annotation.note !== "").map((annotation) => String(annotation.number) + ". " + annotation.note);
59
+ const base = prompt.trim() === "" ? translate("imageEditDefault") : prompt.trim();
60
+ return notes.length === 0 ? base : base + "\n\n" + translate("imageRegionNotes") + "\n" + notes.join("\n");
61
+ }
62
+ //#endregion
52
63
  //#region src/settings-contract.js
53
64
  const SETTINGS_NAMESPACE = "codex-subscription";
54
65
  const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
55
66
  const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
56
67
  const QUICK_QUOTA_MODE_PERCENT = "percent";
57
68
  const SEARCH_PROVIDER_FIELD = "searchProvider";
69
+ const SEARCH_PROVIDER_AUTO = "auto";
58
70
  const SEARCH_PROVIDER_CODEX = "codex";
71
+ const DEFAULT_SEARCH_PROVIDER = SEARCH_PROVIDER_AUTO;
59
72
  const SPEED_MODE_FIELD = "speedMode";
60
73
  const SPEED_MODE_STANDARD = "standard";
61
74
  const SPEED_MODE_FAST = "fast";
62
75
  const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
76
+ const OUTPUT_VERBOSITY_FIELD = "outputVerbosity";
77
+ const OUTPUT_VERBOSITY_DEFAULT = "default";
78
+ const OUTPUT_VERBOSITY_MEDIUM = "medium";
79
+ const OUTPUT_VERBOSITY_HIGH = "high";
80
+ const DEFAULT_OUTPUT_VERBOSITY = OUTPUT_VERBOSITY_DEFAULT;
63
81
  const CONTEXT_MODE_FIELD = "contextMode";
64
82
  const CONTEXT_MODE_STANDARD = "standard";
65
83
  const CONTEXT_MODE_EXTENDED = "extended";
@@ -87,7 +105,17 @@ window.__ModuleLoader__.load({
87
105
  "gpt-5.5": 272e3,
88
106
  "gpt-5.6": 272e3
89
107
  });
90
- const normalizeSearchProvider = (value) => ["dsh", "codex"].includes(value) ? value : "dsh";
108
+ const normalizeSearchProvider = (value) => [
109
+ "auto",
110
+ "dsh",
111
+ "codex"
112
+ ].includes(value) ? value : DEFAULT_SEARCH_PROVIDER;
113
+ const normalizeOutputVerbosity = (value) => [
114
+ "default",
115
+ "low",
116
+ "medium",
117
+ "high"
118
+ ].includes(value) ? value : DEFAULT_OUTPUT_VERBOSITY;
91
119
  const normalizeSpeedMode = (value) => ["standard", "fast"].includes(value) ? value : DEFAULT_SPEED_MODE;
92
120
  const normalizeContextMode = (value) => [
93
121
  "standard",
@@ -212,10 +240,13 @@ window.__ModuleLoader__.load({
212
240
  diagnosticsFailed: "无法生成诊断信息。",
213
241
  feedbackOpen: "反馈问题",
214
242
  searchTitle: "搜索来源",
243
+ searchScope: "自动按当前会话模型分流;手动选择会覆盖所有模型和会话。",
244
+ searchAuto: "自动",
245
+ searchAutoHint: "Codex 模型用订阅搜索,其他模型用 DSH",
215
246
  searchDsh: "DSH 默认",
216
- searchDshHint: "当前搜索服务",
247
+ searchDshHint: "所有模型使用 DSH 当前搜索服务",
217
248
  searchCodex: "Codex 订阅",
218
- searchCodexHint: "ChatGPT 订阅搜索",
249
+ searchCodexHint: "所有模型通过已登录的 ChatGPT 订阅搜索",
219
250
  preferenceFailed: "设置未保存。",
220
251
  preferenceRetry: "重试",
221
252
  usage: "订阅额度",
@@ -289,7 +320,16 @@ window.__ModuleLoader__.load({
289
320
  speedStandardHint: "标准速度",
290
321
  speedFast: "高速",
291
322
  speedFastHint: "1.5 倍,消耗更多 Credits",
292
- modelMenuAria: "模型、推理等级与速度",
323
+ verbosityTitle: "输出详略",
324
+ verbosityDefault: "模型默认",
325
+ verbosityDefaultHint: "使用官方模型目录推荐值",
326
+ verbosityLow: "简洁",
327
+ verbosityLowHint: "更短、更直接",
328
+ verbosityMedium: "均衡",
329
+ verbosityMediumHint: "兼顾完整性与长度",
330
+ verbosityHigh: "详细",
331
+ verbosityHighHint: "更充分的说明与结构",
332
+ modelMenuAria: "模型、推理等级、速度与输出详略",
293
333
  modelLabel: "模型",
294
334
  effortLabel: "推理等级",
295
335
  providerDefault: "Default",
@@ -354,10 +394,13 @@ window.__ModuleLoader__.load({
354
394
  diagnosticsFailed: "Could not create diagnostics.",
355
395
  feedbackOpen: "Report a problem",
356
396
  searchTitle: "Search source",
397
+ searchScope: "Auto follows the current session model; an explicit choice overrides every model and session.",
398
+ searchAuto: "Auto",
399
+ searchAutoHint: "Codex models use subscription search; other models use DSH",
357
400
  searchDsh: "DSH default",
358
- searchDshHint: "Current search service",
401
+ searchDshHint: "Use DSH's current search service for every model",
359
402
  searchCodex: "Codex subscription",
360
- searchCodexHint: "ChatGPT subscription search",
403
+ searchCodexHint: "Search through the signed-in ChatGPT subscription for every model",
361
404
  preferenceFailed: "The setting was not saved.",
362
405
  preferenceRetry: "Retry",
363
406
  usage: "Subscription quota",
@@ -431,7 +474,16 @@ window.__ModuleLoader__.load({
431
474
  speedStandardHint: "Standard speed",
432
475
  speedFast: "Fast",
433
476
  speedFastHint: "1.5x; higher Credits use",
434
- modelMenuAria: "Model, effort, and speed",
477
+ verbosityTitle: "Output detail",
478
+ verbosityDefault: "Model default",
479
+ verbosityDefaultHint: "Use the official model catalog recommendation",
480
+ verbosityLow: "Concise",
481
+ verbosityLowHint: "Shorter and more direct",
482
+ verbosityMedium: "Balanced",
483
+ verbosityMediumHint: "Balance completeness and length",
484
+ verbosityHigh: "Detailed",
485
+ verbosityHighHint: "More explanation and structure",
486
+ modelMenuAria: "Model, effort, speed, and output detail",
435
487
  modelLabel: "Model",
436
488
  effortLabel: "Effort",
437
489
  providerDefault: "Default",
@@ -471,6 +523,7 @@ window.__ModuleLoader__.load({
471
523
  imageRemoveAnnotation: "Remove note"
472
524
  };
473
525
  const STYLE = `
526
+ .codexSubscriptionSearchHead{display:flex;flex-direction:column;gap:1px}.codexSubscriptionSearchScope{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
474
527
  .codexSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
475
528
  .codexSubscription h2,.codexSubscription h3,.codexSubscription p{margin:0}.codexSubscriptionHead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
476
529
  .codexSubscription h2{font-size:16px;line-height:24px;font-weight:500}.codexSubscription h3{font-size:14px;line-height:22px;font-weight:500}
@@ -481,7 +534,7 @@ window.__ModuleLoader__.load({
481
534
  .codexSubscriptionQuotaModes{display:flex;align-items:center;gap:3px;padding:2px;border-radius:9px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionQuotaMode{position:relative;display:flex;align-items:center;justify-content:center;min-height:26px;padding:0 9px;border-radius:7px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;cursor:pointer}.codexSubscriptionQuotaMode:has(input:checked){background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 0 0 1px var(--dsw-alias-border-l3)}.codexSubscriptionQuotaMode:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexSubscriptionQuotaMode:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionQuotaMode input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
482
535
  .codexSubscriptionContext{display:flex;flex-direction:column;gap:8px}.codexSubscriptionContextHead{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.codexSubscriptionContextCopy{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.codexSubscriptionContextHint{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionContextTrigger{height:32px;min-width:108px;display:inline-flex;align-items:center;justify-content:space-between;gap:10px;padding:0 10px 0 12px;border:0;border-radius:999px;outline:0;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.codexSubscriptionContextTrigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.codexSubscriptionContextTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.codexSubscriptionContextTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:not-allowed}.codexSubscriptionContextTrigger svg{color:var(--dsw-alias-label-tertiary);transition:transform 120ms var(--ds-ease-in-out)}.codexSubscriptionContextTrigger[aria-expanded=true] svg{transform:rotate(180deg)}.codexSubscriptionContextModels{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}.codexSubscriptionContextModel{min-height:42px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.codexSubscriptionContextModel:last-child{border-bottom:0}.codexSubscriptionContextModelCopy{display:flex;min-width:0;flex-direction:column}.codexSubscriptionContextModelCopy strong{font-size:12px;line-height:18px;font-weight:500}.codexSubscriptionContextModelCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionContextInput{width:116px}
483
536
  .codexSubscriptionSwitch{position:relative;flex:0 0 auto;width:32px;height:18px;padding:0;border:1px solid var(--dsw-alias-border-l3);border-radius:999px;background:var(--dsw-alias-bg-module-platform);cursor:pointer}.codexSubscriptionSwitch:disabled{cursor:not-allowed;opacity:.5}.codexSubscriptionSwitch[aria-checked=true]{background:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-label-secondary)}.codexSubscriptionSwitchKnob{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:var(--dsw-alias-bg-layer-1);transition:transform 120ms var(--ds-ease-in-out)}.codexSubscriptionSwitch[aria-checked=true] .codexSubscriptionSwitchKnob{transform:translateX(14px)}
484
- .codexSubscriptionSearch{display:flex;flex-direction:column;gap:7px}.codexSubscriptionSearchChoices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.codexSubscriptionSearchChoice{display:grid;grid-template-columns:14px minmax(0,1fr);align-items:center;column-gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:9px 10px;text-align:left;cursor:pointer}.codexSubscriptionSearchChoice:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionSearchChoice:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.codexSubscriptionSearchChoice:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.codexSubscriptionSearchInput{width:14px;height:14px;margin:0;accent-color:var(--dsw-alias-label-primary);cursor:inherit}.codexSubscriptionSearchCopy{display:block;min-width:0;pointer-events:none}.codexSubscriptionSearchCopy strong,.codexSubscriptionSearchCopy span{display:block}.codexSubscriptionSearchCopy strong{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}.codexSubscriptionSearchChoice:has(input:checked) strong{color:var(--dsw-alias-label-primary)}.codexSubscriptionSearchCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionDivider{height:1px;background:var(--dsw-alias-border-l2)}
537
+ .codexSubscriptionSearch{display:flex;flex-direction:column;gap:7px}.codexSubscriptionSearchChoices{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:6px}.codexSubscriptionSearchChoice{display:grid;grid-template-columns:14px minmax(0,1fr);align-items:center;column-gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:9px 10px;text-align:left;cursor:pointer}.codexSubscriptionSearchChoice:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionSearchChoice:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.codexSubscriptionSearchChoice:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.codexSubscriptionSearchInput{width:14px;height:14px;margin:0;accent-color:var(--dsw-alias-label-primary);cursor:inherit}.codexSubscriptionSearchCopy{display:block;min-width:0;pointer-events:none}.codexSubscriptionSearchCopy strong,.codexSubscriptionSearchCopy span{display:block}.codexSubscriptionSearchCopy strong{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}.codexSubscriptionSearchChoice:has(input:checked) strong{color:var(--dsw-alias-label-primary)}.codexSubscriptionSearchCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionDivider{height:1px;background:var(--dsw-alias-border-l2)}
485
538
  .codexSubscriptionAccountRow,.codexSubscriptionSectionHead{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionStatus{display:flex;align-items:center;gap:8px;font-size:14px;line-height:22px;font-weight:500}
486
539
  .codexSubscriptionDot{width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-dimmed)}.codexSubscriptionDot[data-state=connected]{background:var(--dsw-alias-state-success-primary)}.codexSubscriptionDot[data-state=disconnected]{background:var(--dsw-alias-state-error-primary)}
487
540
  .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)}
@@ -552,15 +605,7 @@ window.__ModuleLoader__.load({
552
605
  return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
553
606
  };
554
607
  const imageByteSize = (bytes) => bytes < 1024 * 1024 ? `${Math.max(.1, bytes / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} KB` : `${(bytes / 1024 / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} MB`;
555
- function editDraft(prompt, annotations, t) {
556
- const notes = annotations.map((annotation, index) => ({
557
- number: index + 1,
558
- note: annotation.note.trim()
559
- })).filter((annotation) => annotation.note !== "").map((annotation) => String(annotation.number) + ". " + annotation.note);
560
- const base = prompt.trim() === "" ? t("imageEditDefault") : prompt.trim();
561
- return notes.length === 0 ? base : base + "\n\n" + t("imageRegionNotes") + "\n" + notes.join("\n");
562
- }
563
- function CodexGeneratedImage({ attachment, loadImage, attachForEdit, t }) {
608
+ function CodexGeneratedImage({ attachment, loadImage, attachForEdit, getImageViewer, t }) {
564
609
  const [attempt, setAttempt] = (0, react.useState)(0);
565
610
  const [error, setError] = (0, react.useState)(false);
566
611
  const [open, setOpen] = (0, react.useState)(false);
@@ -646,7 +691,11 @@ window.__ModuleLoader__.load({
646
691
  setEditBusy(true);
647
692
  setEditError(false);
648
693
  try {
649
- await attachForEdit(src, downloadName, editDraft(prompt, annotations, t));
694
+ await attachForEdit(src, downloadName, buildImageEditDraft({
695
+ prompt,
696
+ annotations,
697
+ translate: t
698
+ }));
650
699
  close();
651
700
  } catch {
652
701
  setEditError(true);
@@ -654,6 +703,35 @@ window.__ModuleLoader__.load({
654
703
  setEditBusy(false);
655
704
  }
656
705
  };
706
+ const openImage = () => {
707
+ if (src === void 0) return;
708
+ if ((getImageViewer?.())?.open?.({
709
+ items: [{
710
+ id: attachment.attachmentId ?? downloadName,
711
+ src,
712
+ name: label,
713
+ width: attachment.width,
714
+ height: attachment.height,
715
+ bytes: attachment.bytes
716
+ }],
717
+ opener: triggerRef.current,
718
+ source: "codex-generated",
719
+ annotations: true,
720
+ editor: {
721
+ label: t("imageEdit"),
722
+ busyLabel: t("imageEditPreparing"),
723
+ errorLabel: t("imageEditFailed"),
724
+ placeholder: t("imageEditPrompt"),
725
+ onSubmit: ({ prompt: nextPrompt, annotations: nextAnnotations }) => attachForEdit(src, downloadName, buildImageEditDraft({
726
+ prompt: nextPrompt,
727
+ annotations: nextAnnotations,
728
+ translate: t
729
+ }))
730
+ }
731
+ }) === true) return;
732
+ setZoom(1);
733
+ setOpen(true);
734
+ };
657
735
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
658
736
  type: "button",
659
737
  className: "codexGeneratedImageRetry",
@@ -819,19 +897,14 @@ window.__ModuleLoader__.load({
819
897
  className: "codexGeneratedImageFrame",
820
898
  title: t("imageOpen"),
821
899
  "aria-label": fill(t("imageOpenNamed"), { value: label }),
822
- onClick: () => {
823
- if (src !== void 0) {
824
- setZoom(1);
825
- setOpen(true);
826
- }
827
- },
900
+ onClick: openImage,
828
901
  children: src === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageLoading") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
829
902
  src,
830
903
  alt: label
831
904
  })
832
905
  }), lightbox] });
833
906
  }
834
- function CodexImageToolRow({ block, loadImage, attachForEdit, t }) {
907
+ function CodexImageToolRow({ block, loadImage, attachForEdit, getImageViewer, t }) {
835
908
  const settled = block?.kind === "tool-result";
836
909
  const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
837
910
  const failed = settled && block.isError === true;
@@ -869,6 +942,7 @@ window.__ModuleLoader__.load({
869
942
  attachment: image.attachment,
870
943
  loadImage,
871
944
  attachForEdit,
945
+ getImageViewer,
872
946
  t
873
947
  })
874
948
  }),
@@ -887,6 +961,7 @@ window.__ModuleLoader__.load({
887
961
  let failedPatch;
888
962
  let generation = 0;
889
963
  let contextModels = [];
964
+ let verbosityModels = [];
890
965
  const nativeSnapshot = () => scope.getSnapshot();
891
966
  const read = () => {
892
967
  const native = nativeSnapshot();
@@ -896,10 +971,12 @@ window.__ModuleLoader__.load({
896
971
  quickQuotaMode: normalizeQuickQuotaMode(current.value?.[QUICK_QUOTA_MODE_FIELD], current.value?.[LEGACY_QUICK_QUOTA_FIELD]),
897
972
  searchProvider: normalizeSearchProvider(current.value?.[SEARCH_PROVIDER_FIELD]),
898
973
  speedMode: normalizeSpeedMode(current.value?.[SPEED_MODE_FIELD]),
974
+ outputVerbosity: normalizeOutputVerbosity(current.value?.[OUTPUT_VERBOSITY_FIELD]),
899
975
  contextMode: normalizeContextMode(current.value?.[CONTEXT_MODE_FIELD]),
900
976
  customContextWindow: normalizeCustomContextWindow(current.value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
901
977
  customContextWindows: Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [modelKey, normalizeCustomContextWindow(current.value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
902
978
  contextModels,
979
+ verbosityModels,
903
980
  writable: !updating && current.status === "ready" && current.writable === true,
904
981
  error
905
982
  });
@@ -917,6 +994,7 @@ window.__ModuleLoader__.load({
917
994
  });
918
995
  const acceptFallback = (value) => {
919
996
  contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
997
+ verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
920
998
  fallbackStatus = "ready";
921
999
  fallback = {
922
1000
  status: "ready",
@@ -924,6 +1002,7 @@ window.__ModuleLoader__.load({
924
1002
  [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(value?.[QUICK_QUOTA_MODE_FIELD], value?.[LEGACY_QUICK_QUOTA_FIELD]),
925
1003
  [SEARCH_PROVIDER_FIELD]: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD]),
926
1004
  [SPEED_MODE_FIELD]: normalizeSpeedMode(value?.[SPEED_MODE_FIELD]),
1005
+ [OUTPUT_VERBOSITY_FIELD]: normalizeOutputVerbosity(value?.[OUTPUT_VERBOSITY_FIELD]),
927
1006
  [CONTEXT_MODE_FIELD]: normalizeContextMode(value?.[CONTEXT_MODE_FIELD]),
928
1007
  [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
929
1008
  ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])]))
@@ -942,8 +1021,10 @@ window.__ModuleLoader__.load({
942
1021
  try {
943
1022
  const value = unwrap(await rpc.call(CHANNEL, "preferences/status", {}));
944
1023
  if (current !== generation) return;
945
- if (nativeSnapshot().status === "ready") contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
946
- else acceptFallback(value);
1024
+ if (nativeSnapshot().status === "ready") {
1025
+ contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1026
+ verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1027
+ } else acceptFallback(value);
947
1028
  publish();
948
1029
  } catch {
949
1030
  if (current !== generation || nativeSnapshot().status === "ready") return;
@@ -1100,11 +1181,21 @@ window.__ModuleLoader__.load({
1100
1181
  });
1101
1182
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1102
1183
  className: "codexSubscriptionSearch",
1103
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("searchTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1184
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1185
+ className: "codexSubscriptionSearchHead",
1186
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("searchTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1187
+ className: "codexSubscriptionSearchScope",
1188
+ children: t("searchScope")
1189
+ })]
1190
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1104
1191
  className: "codexSubscriptionSearchChoices",
1105
1192
  role: "radiogroup",
1106
1193
  "aria-label": t("searchTitle"),
1107
- children: [choice("dsh", t("searchDsh"), t("searchDshHint")), choice(SEARCH_PROVIDER_CODEX, t("searchCodex"), t("searchCodexHint"))]
1194
+ children: [
1195
+ choice(SEARCH_PROVIDER_AUTO, t("searchAuto"), t("searchAutoHint")),
1196
+ choice("dsh", t("searchDsh"), t("searchDshHint")),
1197
+ choice(SEARCH_PROVIDER_CODEX, t("searchCodex"), t("searchCodexHint"))
1198
+ ]
1108
1199
  })]
1109
1200
  });
1110
1201
  }
@@ -1322,6 +1413,31 @@ window.__ModuleLoader__.load({
1322
1413
  const speedSupported = state.current?.provider === "openai-codex" && supportsCodexFastMode(state.current?.model);
1323
1414
  const speedWritable = preferenceSnapshot.status === "ready" && preferenceSnapshot.writable === true;
1324
1415
  const fast = speedSupported && preferenceSnapshot.speedMode === "fast";
1416
+ const verbositySupported = state.current?.provider === "openai-codex" && preferenceSnapshot.verbosityModels.includes(state.current?.model);
1417
+ const verbosityWritable = preferenceSnapshot.status === "ready" && preferenceSnapshot.writable === true;
1418
+ const verbosityItems = [
1419
+ {
1420
+ id: OUTPUT_VERBOSITY_DEFAULT,
1421
+ label: t("verbosityDefault"),
1422
+ description: t("verbosityDefaultHint")
1423
+ },
1424
+ {
1425
+ id: "low",
1426
+ label: t("verbosityLow"),
1427
+ description: t("verbosityLowHint")
1428
+ },
1429
+ {
1430
+ id: OUTPUT_VERBOSITY_MEDIUM,
1431
+ label: t("verbosityMedium"),
1432
+ description: t("verbosityMediumHint")
1433
+ },
1434
+ {
1435
+ id: OUTPUT_VERBOSITY_HIGH,
1436
+ label: t("verbosityHigh"),
1437
+ description: t("verbosityHighHint")
1438
+ }
1439
+ ];
1440
+ const verbosityLabel = verbosityItems.find((item) => item.id === preferenceSnapshot.outputVerbosity)?.label ?? t("verbosityDefault");
1325
1441
  const busy = state.status === "selecting";
1326
1442
  (0, react.useEffect)(() => {
1327
1443
  if (available) load();
@@ -1339,7 +1455,12 @@ window.__ModuleLoader__.load({
1339
1455
  }, [open]);
1340
1456
  (0, react.useEffect)(() => {
1341
1457
  if (!speedSupported && pane === "speed") setPane("root");
1342
- }, [pane, speedSupported]);
1458
+ if (!verbositySupported && pane === "verbosity") setPane("root");
1459
+ }, [
1460
+ pane,
1461
+ speedSupported,
1462
+ verbositySupported
1463
+ ]);
1343
1464
  if (!available) return null;
1344
1465
  const close = (restoreFocus = false) => {
1345
1466
  setOpen(false);
@@ -1372,6 +1493,10 @@ window.__ModuleLoader__.load({
1372
1493
  close(true);
1373
1494
  preference.set({ [SPEED_MODE_FIELD]: speedMode });
1374
1495
  };
1496
+ const chooseVerbosity = (outputVerbosity) => {
1497
+ close(true);
1498
+ preference.set({ [OUTPUT_VERBOSITY_FIELD]: outputVerbosity });
1499
+ };
1375
1500
  const option = ({ key, label, description, selected, disabled, onClick }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1376
1501
  type: "button",
1377
1502
  role: "menuitemradio",
@@ -1504,6 +1629,19 @@ window.__ModuleLoader__.load({
1504
1629
  onClick: () => chooseSpeed(SPEED_MODE_FAST)
1505
1630
  })]
1506
1631
  });
1632
+ else if (pane === "verbosity") submenu = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1633
+ className: "codexModelSelectSubmenu",
1634
+ role: "menu",
1635
+ "aria-label": t("verbosityTitle"),
1636
+ children: verbosityItems.map((item) => option({
1637
+ key: item.id,
1638
+ label: item.label,
1639
+ description: item.description,
1640
+ selected: preferenceSnapshot.outputVerbosity === item.id,
1641
+ disabled: !verbosityWritable,
1642
+ onClick: () => chooseVerbosity(item.id)
1643
+ }))
1644
+ });
1507
1645
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1508
1646
  className: "codexModelSelect",
1509
1647
  ref: rootRef,
@@ -1549,6 +1687,7 @@ window.__ModuleLoader__.load({
1549
1687
  cell("model", t("modelLabel"), modelLabel),
1550
1688
  reasoning === void 0 ? null : cell("effort", t("effortLabel"), effortLabel),
1551
1689
  speedSupported && cell("speed", t("speedTitle"), t(fast ? "speedFast" : "speedStandard")),
1690
+ verbositySupported && cell("verbosity", t("verbosityTitle"), verbosityLabel),
1552
1691
  submenu
1553
1692
  ]
1554
1693
  }) : null]
@@ -2337,6 +2476,13 @@ window.__ModuleLoader__.load({
2337
2476
  inject: (sessionId) => ({
2338
2477
  t,
2339
2478
  loadImage: (attachment) => conversation.resolveImage(sessionId, attachment),
2479
+ getImageViewer: () => {
2480
+ try {
2481
+ return ctx.get("nativeImageViewer");
2482
+ } catch {
2483
+ return;
2484
+ }
2485
+ },
2340
2486
  attachForEdit: async (src, filename, draft) => {
2341
2487
  const actx = sessions.scope(sessionId);
2342
2488
  if (actx === void 0 || typeof conversation.createDraftImages !== "function" || conversation.input?.for === void 0) throw new Error("This DSH version does not provide the image composer bridge");
package/lib/index.js CHANGED
@@ -726,11 +726,18 @@ const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
726
726
  const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
727
727
  const QUICK_QUOTA_MODE_PERCENT = "percent";
728
728
  const SEARCH_PROVIDER_FIELD = "searchProvider";
729
+ const SEARCH_PROVIDER_AUTO = "auto";
729
730
  const SEARCH_PROVIDER_CODEX = "codex";
731
+ const DEFAULT_SEARCH_PROVIDER = SEARCH_PROVIDER_AUTO;
730
732
  const SPEED_MODE_FIELD = "speedMode";
731
733
  const SPEED_MODE_STANDARD = "standard";
732
734
  const SPEED_MODE_FAST = "fast";
733
735
  const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
736
+ const OUTPUT_VERBOSITY_FIELD = "outputVerbosity";
737
+ const OUTPUT_VERBOSITY_DEFAULT = "default";
738
+ const OUTPUT_VERBOSITY_MEDIUM = "medium";
739
+ const OUTPUT_VERBOSITY_HIGH = "high";
740
+ const DEFAULT_OUTPUT_VERBOSITY = OUTPUT_VERBOSITY_DEFAULT;
734
741
  const CONTEXT_MODE_FIELD = "contextMode";
735
742
  const CONTEXT_MODE_STANDARD = "standard";
736
743
  const CONTEXT_MODE_EXTENDED = "extended";
@@ -758,6 +765,12 @@ const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
758
765
  "gpt-5.5": 272e3,
759
766
  "gpt-5.6": 272e3
760
767
  });
768
+ const normalizeOutputVerbosity = (value) => [
769
+ "default",
770
+ "low",
771
+ "medium",
772
+ "high"
773
+ ].includes(value) ? value : DEFAULT_OUTPUT_VERBOSITY;
761
774
  const normalizeContextMode = (value) => [
762
775
  "standard",
763
776
  "extended",
@@ -829,7 +842,7 @@ const EXTENDED_CONTEXT_WINDOWS = Object.freeze({
829
842
  "gpt-5.6-sol": 1e6,
830
843
  "gpt-5.6-terra": 1e6
831
844
  });
832
- function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, runNetwork = (_area, operation) => operation() } = {}) {
845
+ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveOutputVerbosity = () => OUTPUT_VERBOSITY_DEFAULT, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, catalog, runNetwork = (_area, operation) => operation() } = {}) {
833
846
  const provider = createOpenAICodexProvider();
834
847
  const requestToken = Object.freeze({
835
848
  name: "DSH-managed Codex OAuth request token",
@@ -842,25 +855,40 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
842
855
  };
843
856
  }
844
857
  });
845
- const withSpeed = (model, options = {}) => {
846
- if (resolveSpeedMode() !== "fast" || !supportsCodexFastMode(model?.id)) return options;
858
+ const modelMetadata = (model) => catalog?.metadata(model?.id);
859
+ const supportsVerbosity = (model) => modelMetadata(model)?.supportVerbosity ?? model?.id !== "gpt-5.3-codex-spark";
860
+ const withPreferences = (model, options = {}) => {
861
+ const metadata = modelMetadata(model);
862
+ const requestedVerbosity = resolveOutputVerbosity();
863
+ const textVerbosity = supportsVerbosity(model) ? requestedVerbosity === "default" ? metadata?.defaultVerbosity ?? "medium" : requestedVerbosity : void 0;
864
+ const fast = resolveSpeedMode() === "fast" && (metadata?.supportsFast ?? supportsCodexFastMode(model?.id));
847
865
  const onPayload = options.onPayload;
848
866
  return {
849
867
  ...options,
850
- serviceTier: FAST_SERVICE_TIER,
868
+ ...textVerbosity === void 0 ? {} : { textVerbosity },
869
+ ...fast ? { serviceTier: FAST_SERVICE_TIER } : {},
851
870
  async onPayload(payload, requestModel) {
852
- const fastPayload = {
871
+ const preferred = {
853
872
  ...payload,
854
- service_tier: FAST_SERVICE_TIER
873
+ ...textVerbosity === void 0 ? {} : { text: {
874
+ ...payload.text ?? {},
875
+ verbosity: textVerbosity
876
+ } },
877
+ ...fast ? { service_tier: FAST_SERVICE_TIER } : {}
855
878
  };
879
+ const next = await onPayload?.(preferred, requestModel);
856
880
  return {
857
- ...await onPayload?.(fastPayload, requestModel) ?? fastPayload,
858
- service_tier: FAST_SERVICE_TIER
881
+ ...next ?? preferred,
882
+ ...textVerbosity === void 0 ? {} : { text: {
883
+ ...(next ?? preferred).text ?? {},
884
+ verbosity: textVerbosity
885
+ } },
886
+ ...fast ? { service_tier: FAST_SERVICE_TIER } : {}
859
887
  };
860
888
  }
861
889
  };
862
890
  };
863
- const getModels = () => provider.getModels().map((model) => {
891
+ const getModels = () => (catalog?.getModels(provider.getModels()) ?? provider.getModels()).map((model) => {
864
892
  const maximum = EXTENDED_CONTEXT_WINDOWS[model.id];
865
893
  const mode = resolveContextMode();
866
894
  if (maximum === void 0 || !["extended", "custom"].includes(mode)) return model;
@@ -896,17 +924,147 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
896
924
  apiKey: requestToken
897
925
  }),
898
926
  getModels,
899
- stream: (model, context, options) => networkIterable(() => provider.stream(model, context, withSpeed(model, options))),
900
- streamSimple: (model, context, options) => networkIterable(() => provider.streamSimple(model, context, withSpeed(model, options)))
927
+ stream: (model, context, options) => networkIterable(() => provider.stream(model, context, withPreferences(model, options))),
928
+ streamSimple: (model, context, options) => networkIterable(() => provider.streamSimple(model, context, withPreferences(model, options)))
901
929
  });
902
930
  }
903
931
  //#endregion
904
932
  //#region src/version.js
905
- const PACKAGE_VERSION = "1.8.0";
933
+ const PACKAGE_VERSION = "1.10.0";
906
934
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
907
935
  //#endregion
936
+ //#region src/model-catalog.js
937
+ const CODEX_MODELS_URL = `https://chatgpt.com/backend-api/codex/models?client_version=${encodeURIComponent(PACKAGE_VERSION)}`;
938
+ const LEVELS = [
939
+ "off",
940
+ "minimal",
941
+ "low",
942
+ "medium",
943
+ "high",
944
+ "xhigh",
945
+ "max"
946
+ ];
947
+ const record$4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
948
+ const nonEmpty$2 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
949
+ const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0 ? value : void 0;
950
+ function reasoningMap(levels) {
951
+ const supported = new Set((Array.isArray(levels) ? levels : []).map((level) => nonEmpty$2(record$4(level) ? level.effort : void 0)).filter(Boolean));
952
+ const map = Object.fromEntries(LEVELS.map((level) => [level, null]));
953
+ if (supported.has("none")) map.off = "none";
954
+ for (const level of LEVELS.slice(1)) if (supported.has(level)) map[level] = level;
955
+ return map;
956
+ }
957
+ function visibleModel(value) {
958
+ if (!record$4(value)) return void 0;
959
+ const id = nonEmpty$2(value.slug);
960
+ if (id === void 0 || value.supported_in_api !== true || value.visibility !== "list") return void 0;
961
+ const supported = Array.isArray(value.supported_reasoning_levels) ? value.supported_reasoning_levels : [];
962
+ const input = Array.isArray(value.input_modalities) ? value.input_modalities.filter((item) => ["text", "image"].includes(item)) : ["text", "image"];
963
+ return {
964
+ id,
965
+ name: nonEmpty$2(value.display_name) ?? id,
966
+ description: nonEmpty$2(value.description),
967
+ priority: Number.isFinite(value.priority) ? value.priority : 0,
968
+ input: input.length > 0 ? input : ["text"],
969
+ contextWindow: positiveInteger(value.context_window) ?? positiveInteger(value.max_context_window),
970
+ reasoning: supported.length > 0,
971
+ thinkingLevelMap: reasoningMap(supported),
972
+ supportVerbosity: value.support_verbosity === true,
973
+ defaultVerbosity: [
974
+ "low",
975
+ "medium",
976
+ "high"
977
+ ].includes(value.default_verbosity) ? value.default_verbosity : void 0,
978
+ supportsFast: [...Array.isArray(value.additional_speed_tiers) ? value.additional_speed_tiers : [], ...Array.isArray(value.service_tiers) ? value.service_tiers.map((tier) => tier?.id) : []].some((tier) => tier === "fast" || tier === "priority")
979
+ };
980
+ }
981
+ function parseOfficialModelCatalog(value) {
982
+ if (!record$4(value) || !Array.isArray(value.models)) throw new Error("Codex returned a malformed model catalog");
983
+ const seen = /* @__PURE__ */ new Set();
984
+ return value.models.map(visibleModel).filter((model) => model !== void 0 && !seen.has(model.id) && seen.add(model.id)).sort((left, right) => right.priority - left.priority);
985
+ }
986
+ function mergeModel(baseModels, remote) {
987
+ const base = baseModels.find((model) => model.id === remote.id) ?? baseModels.find((model) => model.id !== "gpt-5.3-codex-spark") ?? baseModels[0];
988
+ if (base === void 0) return void 0;
989
+ return {
990
+ ...base,
991
+ id: remote.id,
992
+ name: remote.name,
993
+ input: remote.input,
994
+ reasoning: remote.reasoning,
995
+ thinkingLevelMap: remote.thinkingLevelMap,
996
+ ...remote.contextWindow === void 0 ? {} : { contextWindow: remote.contextWindow },
997
+ ...base.id === remote.id ? {} : { cost: {
998
+ input: 0,
999
+ output: 0,
1000
+ cacheRead: 0,
1001
+ cacheWrite: 0
1002
+ } }
1003
+ };
1004
+ }
1005
+ function createOfficialModelCatalog(options = {}) {
1006
+ const fetchCatalog = options.fetch ?? fetch;
1007
+ let models;
1008
+ let metadata = /* @__PURE__ */ new Map();
1009
+ let etag;
1010
+ let revision = 0;
1011
+ let refreshing;
1012
+ const refresh = async ({ signal } = {}) => {
1013
+ if (refreshing !== void 0) return refreshing;
1014
+ refreshing = (async () => {
1015
+ const auth = await options.getAuth({ signal });
1016
+ const credential = await options.readCredential({ signal });
1017
+ const access = auth?.auth?.apiKey;
1018
+ const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
1019
+ if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) return false;
1020
+ const headers = {
1021
+ authorization: `Bearer ${access}`,
1022
+ "chatgpt-account-id": accountId,
1023
+ accept: "application/json",
1024
+ originator: "pi",
1025
+ "user-agent": USER_AGENT,
1026
+ ...etag === void 0 ? {} : { "if-none-match": etag }
1027
+ };
1028
+ const response = await fetchCatalog(CODEX_MODELS_URL, {
1029
+ method: "GET",
1030
+ redirect: "error",
1031
+ headers,
1032
+ signal
1033
+ });
1034
+ if (response.status === 304) return false;
1035
+ if (!response.ok) throw new Error(`Codex model catalog failed (HTTP ${response.status})`);
1036
+ const remote = parseOfficialModelCatalog(await response.json());
1037
+ if (remote.length === 0) throw new Error("Codex returned an empty model catalog");
1038
+ const baseModels = options.baseModels();
1039
+ const next = remote.map((model) => mergeModel(baseModels, model)).filter(Boolean);
1040
+ if (next.length === 0) throw new Error("Codex model catalog has no compatible models");
1041
+ models = next;
1042
+ metadata = new Map(remote.map((model) => [model.id, model]));
1043
+ etag = nonEmpty$2(response.headers.get("etag")) ?? etag;
1044
+ revision += 1;
1045
+ return true;
1046
+ })().finally(() => {
1047
+ refreshing = void 0;
1048
+ });
1049
+ return refreshing;
1050
+ };
1051
+ return Object.freeze({
1052
+ refresh,
1053
+ getModels: (fallback) => models ?? fallback,
1054
+ metadata: (modelId) => metadata.get(modelId),
1055
+ revision: () => revision,
1056
+ clear() {
1057
+ models = void 0;
1058
+ metadata = /* @__PURE__ */ new Map();
1059
+ etag = void 0;
1060
+ revision += 1;
1061
+ }
1062
+ });
1063
+ }
1064
+ //#endregion
908
1065
  //#region src/codex-search.js
909
1066
  const CODEX_SEARCH_PROVIDER_ID = "codex-subscription";
1067
+ const CODEX_AUTO_SEARCH_PROVIDER_ID = "codex-subscription-auto";
910
1068
  const CODEX_SEARCH_URL = "https://chatgpt.com/backend-api/codex/alpha/search";
911
1069
  const DEFAULT_MODEL = "gpt-5.6-luna";
912
1070
  const MAX_OUTPUT_TOKENS = 4096;
@@ -1021,6 +1179,19 @@ function createCodexSearchProvider(options) {
1021
1179
  }
1022
1180
  });
1023
1181
  }
1182
+ /** Route each request by its initiating model without changing the user's explicit overrides. */
1183
+ function createCodexAutoSearchProvider(options) {
1184
+ return Object.freeze({
1185
+ id: CODEX_AUTO_SEARCH_PROVIDER_ID,
1186
+ available: () => true,
1187
+ async search(request, signal) {
1188
+ if (options.resolveModelProvider?.() === "openai-codex") return options.codex.search(request, signal);
1189
+ const provider = options.resolveDshProvider?.();
1190
+ if (provider === void 0 || provider.id === "codex-subscription-auto" || provider.id === "codex-subscription" || provider.available() !== true) throw new WebError("DSH default search is unavailable", "WEB_PROVIDER_UNAVAILABLE");
1191
+ return provider.search(request, signal);
1192
+ }
1193
+ });
1194
+ }
1024
1195
  //#endregion
1025
1196
  //#region src/codex-images.js
1026
1197
  const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
@@ -1029,6 +1200,17 @@ const CODEX_IMAGE_EDIT_URL = "https://chatgpt.com/backend-api/codex/images/edits
1029
1200
  const IMAGE_MODEL = "gpt-image-2";
1030
1201
  const MAX_REFERENCE_IMAGES = 5;
1031
1202
  const RESPONSE_ENVELOPE_BYTES = 1024 * 1024;
1203
+ const IMAGE_QUALITIES = /* @__PURE__ */ new Set([
1204
+ "auto",
1205
+ "low",
1206
+ "medium",
1207
+ "high"
1208
+ ]);
1209
+ const IMAGE_BACKGROUNDS = /* @__PURE__ */ new Set([
1210
+ "auto",
1211
+ "transparent",
1212
+ "opaque"
1213
+ ]);
1032
1214
  const PNG_SIGNATURE = Buffer.from([
1033
1215
  137,
1034
1216
  80,
@@ -1041,6 +1223,27 @@ const PNG_SIGNATURE = Buffer.from([
1041
1223
  ]);
1042
1224
  const record$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1043
1225
  const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1226
+ function normalizeImageOptions(args) {
1227
+ const quality = nonEmpty(args?.quality) ?? "auto";
1228
+ const background = nonEmpty(args?.background) ?? "auto";
1229
+ const size = nonEmpty(args?.size) ?? "auto";
1230
+ if (!IMAGE_QUALITIES.has(quality)) throw new Error("quality must be auto, low, medium, or high");
1231
+ if (!IMAGE_BACKGROUNDS.has(background)) throw new Error("background must be auto, transparent, or opaque");
1232
+ if (size !== "auto") {
1233
+ const match = /^(\d+)x(\d+)$/u.exec(size);
1234
+ const width = Number(match?.[1]);
1235
+ const height = Number(match?.[2]);
1236
+ const short = Math.min(width, height);
1237
+ const long = Math.max(width, height);
1238
+ const pixels = width * height;
1239
+ if (match === null || width % 16 !== 0 || height % 16 !== 0 || long > 3840 || long > short * 3 || pixels < 655360 || pixels > 8294400) throw new Error("size must be auto or a valid GPT Image 2 widthxheight resolution");
1240
+ }
1241
+ return {
1242
+ quality,
1243
+ background,
1244
+ size
1245
+ };
1246
+ }
1044
1247
  function encodedLimit(decodedBytes) {
1045
1248
  return Math.ceil(decodedBytes / 3) * 4;
1046
1249
  }
@@ -1187,6 +1390,29 @@ function createCodexImageTool(options) {
1187
1390
  required: true,
1188
1391
  description: "A complete, production-ready description of the image to generate."
1189
1392
  },
1393
+ size: {
1394
+ type: "string",
1395
+ description: "Optional GPT Image 2 output size. Use auto unless the user requests an exact valid widthxheight resolution."
1396
+ },
1397
+ quality: {
1398
+ type: "string",
1399
+ enum: [
1400
+ "auto",
1401
+ "low",
1402
+ "medium",
1403
+ "high"
1404
+ ],
1405
+ description: "Optional rendering quality. Use auto unless the user requests draft speed or final quality."
1406
+ },
1407
+ background: {
1408
+ type: "string",
1409
+ enum: [
1410
+ "auto",
1411
+ "transparent",
1412
+ "opaque"
1413
+ ],
1414
+ description: "Optional background mode. Request transparent only when the user needs transparency."
1415
+ },
1190
1416
  referenceImages: {
1191
1417
  type: "array",
1192
1418
  description: "Optional explicit references to 1-5 prior images to edit. Omit for a new image.",
@@ -1228,6 +1454,7 @@ function createCodexImageTool(options) {
1228
1454
  async execute(args, exec) {
1229
1455
  const prompt = nonEmpty(args.prompt);
1230
1456
  if (prompt === void 0) throw new Error("prompt must be a non-empty string");
1457
+ const imageOptions = normalizeImageOptions(args);
1231
1458
  const auth = await options.getAuth({ signal: exec.signal });
1232
1459
  const credential = await options.readCredential({ signal: exec.signal });
1233
1460
  const access = auth?.auth?.apiKey;
@@ -1254,10 +1481,10 @@ function createCodexImageTool(options) {
1254
1481
  body: JSON.stringify({
1255
1482
  ...images === void 0 ? {} : { images },
1256
1483
  prompt,
1257
- background: "auto",
1484
+ background: imageOptions.background,
1258
1485
  model: IMAGE_MODEL,
1259
- quality: "auto",
1260
- size: "auto"
1486
+ quality: imageOptions.quality,
1487
+ size: imageOptions.size
1261
1488
  }),
1262
1489
  signal: exec.signal
1263
1490
  });
@@ -1367,6 +1594,7 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
1367
1594
  configuration: {
1368
1595
  contextMode: preference.contextMode,
1369
1596
  quickQuotaMode: preference.quickQuotaMode,
1597
+ ...typeof preference.outputVerbosity === "string" ? { outputVerbosity: preference.outputVerbosity } : {},
1370
1598
  searchProvider: preference.searchProvider,
1371
1599
  speedMode: preference.speedMode,
1372
1600
  writable: preference.writable === true
@@ -1783,7 +2011,7 @@ const publicError = (code, message) => ({
1783
2011
  details: { issues: [] }
1784
2012
  }
1785
2013
  });
1786
- function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, diagnosticsReader }) {
2014
+ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, diagnosticsReader, modelCatalog }) {
1787
2015
  return async (endpoint, payload, signal) => {
1788
2016
  if (endpoint === "diagnostics") try {
1789
2017
  signal.throwIfAborted();
@@ -1808,13 +2036,26 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1808
2036
  patch[QUICK_QUOTA_MODE_FIELD] = payload[QUICK_QUOTA_MODE_FIELD];
1809
2037
  }
1810
2038
  if (Object.hasOwn(payload ?? {}, "searchProvider")) {
1811
- if (!["dsh", "codex"].includes(payload["searchProvider"])) return publicError("internal", "Invalid search provider preference");
2039
+ if (![
2040
+ "auto",
2041
+ "dsh",
2042
+ "codex"
2043
+ ].includes(payload["searchProvider"])) return publicError("internal", "Invalid search provider preference");
1812
2044
  patch[SEARCH_PROVIDER_FIELD] = payload[SEARCH_PROVIDER_FIELD];
1813
2045
  }
1814
2046
  if (Object.hasOwn(payload ?? {}, "speedMode")) {
1815
2047
  if (!["standard", "fast"].includes(payload["speedMode"])) return publicError("internal", "Invalid speed mode preference");
1816
2048
  patch[SPEED_MODE_FIELD] = payload[SPEED_MODE_FIELD];
1817
2049
  }
2050
+ if (Object.hasOwn(payload ?? {}, "outputVerbosity")) {
2051
+ if (![
2052
+ "default",
2053
+ "low",
2054
+ "medium",
2055
+ "high"
2056
+ ].includes(payload["outputVerbosity"])) return publicError("internal", "Invalid output verbosity preference");
2057
+ patch[OUTPUT_VERBOSITY_FIELD] = payload[OUTPUT_VERBOSITY_FIELD];
2058
+ }
1818
2059
  if (Object.hasOwn(payload ?? {}, "contextMode")) {
1819
2060
  if (![
1820
2061
  "standard",
@@ -1890,26 +2131,34 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1890
2131
  if (endpoint === "logout" && result.ok === true) {
1891
2132
  usageReader.clear();
1892
2133
  resetCreditService.clear();
1893
- }
2134
+ modelCatalog?.clear();
2135
+ } else if (result.ok === true && (endpoint === "status" || result.value?.authenticated === true)) modelCatalog?.refresh({ signal: void 0 }).catch(() => {});
1894
2136
  return result;
1895
2137
  };
1896
2138
  }
1897
2139
  function createSearchProviderSwitcher(loader) {
1898
2140
  const webEntry = () => [...loader.entries()].find((entry) => entry.options?.id === WEB_ENTRY_ID);
1899
- return Object.freeze({ async select(selection) {
1900
- const entry = webEntry();
1901
- const fiber = entry?.fiber;
1902
- if (entry === void 0 || fiber === void 0 || typeof fiber.update !== "function") throw new Error("DSH web runtime is unavailable");
1903
- const baseConfig = entry.options?.config ?? {};
1904
- const currentConfig = fiber.config ?? baseConfig;
1905
- const dshProvider = typeof baseConfig.searchProvider === "string" && baseConfig.searchProvider.length > 0 ? baseConfig.searchProvider : DSH_SEARCH_PROVIDER_FALLBACK;
1906
- const provider = selection === "codex" ? CODEX_SEARCH_PROVIDER_ID : dshProvider;
1907
- if (currentConfig.searchProvider === provider) return;
1908
- await fiber.update({
1909
- ...currentConfig,
1910
- searchProvider: provider
1911
- }, true);
1912
- } });
2141
+ const dshProviderId = () => {
2142
+ const baseConfig = webEntry()?.options?.config ?? {};
2143
+ return typeof baseConfig.searchProvider === "string" && baseConfig.searchProvider.length > 0 ? baseConfig.searchProvider : DSH_SEARCH_PROVIDER_FALLBACK;
2144
+ };
2145
+ return Object.freeze({
2146
+ dshProviderId,
2147
+ async select(selection) {
2148
+ const entry = webEntry();
2149
+ const fiber = entry?.fiber;
2150
+ if (entry === void 0 || fiber === void 0 || typeof fiber.update !== "function") throw new Error("DSH web runtime is unavailable");
2151
+ const baseConfig = entry.options?.config ?? {};
2152
+ const currentConfig = fiber.config ?? baseConfig;
2153
+ const dshProvider = dshProviderId();
2154
+ const provider = selection === "codex" ? CODEX_SEARCH_PROVIDER_ID : selection === "auto" ? CODEX_AUTO_SEARCH_PROVIDER_ID : dshProvider;
2155
+ if (currentConfig.searchProvider === provider) return;
2156
+ await fiber.update({
2157
+ ...currentConfig,
2158
+ searchProvider: provider
2159
+ }, true);
2160
+ }
2161
+ });
1913
2162
  }
1914
2163
  function apply(ctx) {
1915
2164
  const settings = ctx.settings.register(settingsNamespace(SETTINGS_NAMESPACE), z.object({
@@ -1919,8 +2168,18 @@ function apply(ctx) {
1919
2168
  "bar"
1920
2169
  ]),
1921
2170
  [LEGACY_QUICK_QUOTA_FIELD]: z.boolean(),
1922
- [SEARCH_PROVIDER_FIELD]: z.union(["dsh", SEARCH_PROVIDER_CODEX]).default("dsh"),
2171
+ [SEARCH_PROVIDER_FIELD]: z.union([
2172
+ SEARCH_PROVIDER_AUTO,
2173
+ "dsh",
2174
+ SEARCH_PROVIDER_CODEX
2175
+ ]).default(DEFAULT_SEARCH_PROVIDER),
1923
2176
  [SPEED_MODE_FIELD]: z.union([SPEED_MODE_STANDARD, SPEED_MODE_FAST]).default(DEFAULT_SPEED_MODE),
2177
+ [OUTPUT_VERBOSITY_FIELD]: z.union([
2178
+ OUTPUT_VERBOSITY_DEFAULT,
2179
+ "low",
2180
+ OUTPUT_VERBOSITY_MEDIUM,
2181
+ OUTPUT_VERBOSITY_HIGH
2182
+ ]).default(DEFAULT_OUTPUT_VERBOSITY),
1924
2183
  [CONTEXT_MODE_FIELD]: z.union([
1925
2184
  CONTEXT_MODE_STANDARD,
1926
2185
  CONTEXT_MODE_EXTENDED,
@@ -1931,13 +2190,24 @@ function apply(ctx) {
1931
2190
  }));
1932
2191
  const searchProvider = createSearchProviderSwitcher(ctx.loader);
1933
2192
  const network = createCodexNetworkTransport();
2193
+ const store = new DshOAuthCredentialStore(ctx.credentials, CREDENTIAL_REF, [LEGACY_CREDENTIAL_REF]);
2194
+ const baseProvider = createOpenAICodexProvider();
2195
+ let resolveAuth = async () => void 0;
2196
+ const modelCatalog = createOfficialModelCatalog({
2197
+ getAuth: (options) => resolveAuth(options),
2198
+ readCredential: (options) => store.read(PROVIDER, options),
2199
+ baseModels: () => baseProvider.getModels(),
2200
+ fetch: (input, init) => network.fetch("catalog", input, init)
2201
+ });
1934
2202
  const provider = openaiCodexSubscriptionProvider({
1935
2203
  resolveSpeedMode: () => settings.get()[SPEED_MODE_FIELD],
2204
+ resolveOutputVerbosity: () => normalizeOutputVerbosity(settings.get()[OUTPUT_VERBOSITY_FIELD]),
1936
2205
  resolveContextMode: () => normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
1937
2206
  resolveCustomContextWindow: (modelKey) => {
1938
2207
  const field = CUSTOM_CONTEXT_MODEL_FIELDS[modelKey];
1939
2208
  return normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey]);
1940
2209
  },
2210
+ catalog: modelCatalog,
1941
2211
  runNetwork: network.run
1942
2212
  });
1943
2213
  const preferences = {
@@ -1945,15 +2215,16 @@ function apply(ctx) {
1945
2215
  [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]),
1946
2216
  [SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
1947
2217
  [SPEED_MODE_FIELD]: settings.get()[SPEED_MODE_FIELD],
2218
+ [OUTPUT_VERBOSITY_FIELD]: normalizeOutputVerbosity(settings.get()[OUTPUT_VERBOSITY_FIELD]),
1948
2219
  [CONTEXT_MODE_FIELD]: normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
1949
2220
  [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(settings.get()[CUSTOM_CONTEXT_WINDOW_FIELD]),
1950
2221
  ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
1951
2222
  contextModels: contextModelGroups(provider.getModels()),
2223
+ verbosityModels: provider.getModels().filter((model) => modelCatalog.metadata(model.id)?.supportVerbosity ?? model.id !== "gpt-5.3-codex-spark").map((model) => model.id),
1952
2224
  writable: ctx.settings.writable
1953
2225
  }),
1954
2226
  update: (patch) => settings.update(patch)
1955
2227
  };
1956
- const store = new DshOAuthCredentialStore(ctx.credentials, CREDENTIAL_REF, [LEGACY_CREDENTIAL_REF]);
1957
2228
  const authModels = createModels({ credentials: store });
1958
2229
  authModels.setProvider(provider);
1959
2230
  const profile = Object.freeze({
@@ -1971,14 +2242,18 @@ function apply(ctx) {
1971
2242
  let profileKey;
1972
2243
  let profileSnapshot;
1973
2244
  const profiles = () => {
1974
- const key = [normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]), ...Object.values(CUSTOM_CONTEXT_MODEL_FIELDS).map((field) => settings.get()[field])].join(":");
2245
+ const key = [
2246
+ modelCatalog.revision(),
2247
+ normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
2248
+ ...Object.values(CUSTOM_CONTEXT_MODEL_FIELDS).map((field) => settings.get()[field])
2249
+ ].join(":");
1975
2250
  if (key !== profileKey) {
1976
2251
  profileKey = key;
1977
2252
  profileSnapshot = /* @__PURE__ */ new Map([[PROVIDER, profile]]);
1978
2253
  }
1979
2254
  return profileSnapshot;
1980
2255
  };
1981
- const resolveAuth = () => authModels.getAuth(PROVIDER);
2256
+ resolveAuth = () => authModels.getAuth(PROVIDER);
1982
2257
  const adapterAuth = Object.freeze({
1983
2258
  credentials: store,
1984
2259
  authContext: Object.freeze({
@@ -2009,7 +2284,7 @@ function apply(ctx) {
2009
2284
  });
2010
2285
  ctx.llm.registerAdapter([PROVIDER], adapter);
2011
2286
  const currentAgent = () => ctx.get?.("agents")?.currentInitiator?.();
2012
- ctx.web.registerSearchProvider(createCodexSearchProvider({
2287
+ const codexSearch = createCodexSearchProvider({
2013
2288
  getAuth: resolveAuth,
2014
2289
  readCredential: (options) => store.read(PROVIDER, options),
2015
2290
  resolveModel: () => {
@@ -2018,6 +2293,12 @@ function apply(ctx) {
2018
2293
  },
2019
2294
  resolveSessionId: () => currentAgent()?.session.id,
2020
2295
  fetch: (input, init) => network.fetch("search", input, init)
2296
+ });
2297
+ ctx.web.registerSearchProvider(codexSearch);
2298
+ ctx.web.registerSearchProvider(createCodexAutoSearchProvider({
2299
+ codex: codexSearch,
2300
+ resolveModelProvider: () => currentAgent()?.session.requestContext?.()?.provider,
2301
+ resolveDshProvider: () => ctx.web.searchProviders?.get(searchProvider.dshProviderId())
2021
2302
  }));
2022
2303
  ctx.effect(() => {
2023
2304
  const select = async (value) => {
@@ -2053,8 +2334,12 @@ function apply(ctx) {
2053
2334
  preferences,
2054
2335
  login: coordinator.supportState(),
2055
2336
  network
2056
- })
2337
+ }),
2338
+ modelCatalog
2057
2339
  });
2340
+ ctx.effect(() => {
2341
+ modelCatalog.refresh().catch((error) => ctx.logger?.debug?.("could not refresh Codex model catalog: %s", error.message));
2342
+ }, "codex-subscription: official model catalog");
2058
2343
  ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "loopback" }), "codex-subscription: loopback account RPC");
2059
2344
  }
2060
2345
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",