dsh-codex-subscription 1.5.0 → 1.6.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.5.0` package below; do not install a moving branch.
9
+ - Use the exact `1.6.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.5.0
37
+ npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.6.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.5.0
43
+ dsh plugin --profile web add dsh-codex-subscription@1.6.0
44
44
  ```
45
45
 
46
46
  Use the same `add` command to update or repair. This is the complete package-changing operation.
@@ -58,7 +58,7 @@ 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.5.0` appears exactly once.
61
+ 1. `dsh-codex-subscription` version `1.6.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
 
package/DIRECTORY.md CHANGED
@@ -4,11 +4,11 @@ Use this factual copy when listing `dsh-codex-subscription` in a DSH plugin dire
4
4
 
5
5
  ## Short description
6
6
 
7
- Use a ChatGPT subscription in DeepSeek Harness through independent OAuth sign-in, with Codex models, backend-reported quota, subscription web search, image generation, and no silent paid fallback.
7
+ Use a ChatGPT subscription in DeepSeek Harness through independent OAuth sign-in, with Codex models, backend-reported quota, subscription web search, Beta image generation/editing, and no silent paid fallback.
8
8
 
9
9
  ## 中文短描述
10
10
 
11
- 通过独立 OAuth 登录在 DeepSeek Harness 中使用 ChatGPT 订阅,提供 Codex 模型、服务端实际额度、订阅搜索和图片生成,不会静默切换其他付费路由。
11
+ 通过独立 OAuth 登录在 DeepSeek Harness 中使用 ChatGPT 订阅,提供 Codex 模型、服务端实际额度、订阅搜索和 Beta 图片生成与编辑,不会静默切换其他付费路由。
12
12
 
13
13
  ## Facts
14
14
 
@@ -18,7 +18,7 @@ Use a ChatGPT subscription in DeepSeek Harness through independent OAuth sign-in
18
18
  - Codex CLI required: no
19
19
  - OpenAI API key required: no
20
20
  - Platforms: official DSH plus DSH-Portable product forms for Windows, macOS, and Linux
21
- - Main capabilities: Codex models, quota and expiring resets, web search, image preview/download/refinement, optional composer quota, optional Fast mode, secret-free support diagnostics and direct feedback
21
+ - Main capabilities: Codex models, quota and expiring resets, web search, Beta image generation/editing with explicit references and native preview/download, optional composer quota, Fast mode, secret-free support diagnostics and direct feedback
22
22
  - Safety boundary: subscription failures remain visible and never silently fall back to another paid provider
23
23
 
24
24
  ## Install
package/README.md CHANGED
@@ -8,7 +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
+ [![total npm downloads](https://img.shields.io/npm/dt/dsh-codex-subscription?logo=npm&label=total%20downloads)](https://www.npmjs.com/package/dsh-codex-subscription)
12
12
  [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
13
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)
14
14
 
@@ -49,8 +49,8 @@ DSH-Portable exposes the same standard plugin command, so the command above also
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
51
  | **Subscription search** | Explicitly choose DSH default search or Codex subscription search |
52
- | **Codex image generation** | Preview or download the original, then describe refinements in the same conversation |
53
- | **Fast mode (Beta)** | Switch between Standard and Fast directly in the composer |
52
+ | **Codex image generation and editing (Beta)** | Generate without references, or explicitly edit selected conversation images; preview, zoom, and download the original |
53
+ | **Fast mode** | Switch between Standard and Fast directly in the composer |
54
54
 
55
55
  These capabilities reuse the same local ChatGPT sign-in. Subscription routing failures stay visible and never silently switch to another paid route.
56
56
 
@@ -139,13 +139,13 @@ Restart DSH manually after installation, then:
139
139
  ## Features
140
140
 
141
141
  - ChatGPT OAuth sign-in with credentials kept on the host;
142
- - Codex models and image generation directly inside DSH conversations;
142
+ - Codex models and Beta image generation/editing directly inside DSH conversations;
143
143
  - A clear choice between DSH default search and Codex subscription search;
144
144
  - Actual backend-provided quota, reset time, and freshness;
145
145
  - Separate standard Codex, Codex-Spark, Credits, and other independent limits;
146
146
  - Visible quota-reset count and earliest expiry, with deliberate early redemption, layered confirmation, and no automatic retry;
147
147
  - Optional percentage or progress bar for the selected Codex model (off by default);
148
- - Standard or Fast mode for supported Codex models directly in the composer (Beta);
148
+ - Standard or Fast mode for supported Codex models directly in the composer;
149
149
  - A copyable support report and direct feedback link in Settings; the report excludes OAuth credentials, account identifiers, and authorization timestamps;
150
150
  - Visible errors when subscription routing is unavailable, with no silent paid fallback.
151
151
 
@@ -167,10 +167,14 @@ decides whether a window needs resetting and may return **nothing to reset** wit
167
167
  action requires an acknowledgement checkbox and five-second cooldown. Cancel never consumes a reset, rapid repeated
168
168
  clicks are single-flight, and an uncertain network result is never retried automatically.
169
169
 
170
- Generated images open in a plugin-owned preview with **Download original**. To refine one, continue describing the
171
- change in the same conversation; the durable conversation image remains available to the next model turn.
170
+ ### Image generation and editing (Beta)
172
171
 
173
- ### Composer speed (Beta)
172
+ Generated images open in a DSH-native preview with zoom, fit, dimensions, and **Download original**. A new image request
173
+ does not silently include earlier images. To refine a result, explicitly ask to edit that image in the same conversation;
174
+ the plugin sends only the selected durable image references, up to five. GPT Image 2 can take longer than a normal text
175
+ turn, and detailed text, exact composition, or repeated-character consistency may still need another pass.
176
+
177
+ ### Composer speed
174
178
 
175
179
  With a supported Codex model selected, open the composer's model menu to choose Standard or Fast.
176
180
  Standard adds no icon; only Fast shows a lightning icon before the model name. Spark does not show the speed entry. Fast mode increases speed and uses more Credits;
package/README.zh-CN.md CHANGED
@@ -9,7 +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
+ [![npm 总下载量](https://img.shields.io/npm/dt/dsh-codex-subscription?logo=npm&label=%E6%80%BB%E4%B8%8B%E8%BD%BD%E9%87%8F)](https://www.npmjs.com/package/dsh-codex-subscription)
13
13
  [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
14
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)
15
15
 
@@ -50,8 +50,8 @@ DSH-Portable 也提供相同的标准插件命令,因此同样使用上面的
50
50
  | **输入框额度** | 可选择紧凑百分比、进度条或完全关闭输入框额度显示 |
51
51
  | **安全额度重置** | 直接查看最早到期时间,并通过冷静期和知情确认主动尝试重置 |
52
52
  | **订阅搜索** | 可在 DSH 默认搜索与 Codex 订阅搜索之间明确切换 |
53
- | **Codex 图片生成** | 预览或下载原图,并在同一会话继续描述修改内容 |
54
- | **高速模式(Beta)** | 直接在输入框切换标准或高速,无需离开当前会话 |
53
+ | **Codex 图片生成与编辑(Beta)** | 可无参考图全新生成,也可明确选择会话图片继续编辑;支持预览、缩放和下载原图 |
54
+ | **高速模式** | 直接在输入框切换标准或高速,无需离开当前会话 |
55
55
 
56
56
  这些能力共用同一份本机 ChatGPT 登录。订阅路由失败时会明确报错,不会静默切换到其他付费路由。
57
57
 
@@ -139,13 +139,13 @@ dsh --profile web --dump-config
139
139
  ## 功能
140
140
 
141
141
  - ChatGPT OAuth 登录,凭据保留在本机;
142
- - Codex 模型和图片生成直接出现在 DSH 会话中;
142
+ - Codex 模型和 Beta 图片生成与编辑直接出现在 DSH 会话中;
143
143
  - DSH 默认搜索与 Codex 订阅搜索可随时切换;
144
144
  - 设置页显示服务端返回的额度、重置时间和更新时间;
145
145
  - 普通 Codex、Codex-Spark、Credits 等独立额度分开显示;
146
146
  - 显示重置卡数量与最早到期时间,也允许在额度未完全用尽时主动尝试,并经过分层确认且不会自动重试;
147
147
  - 输入框可用百分比或进度条显示当前 Codex 模型的剩余额度(默认关闭);
148
- - 输入框可为支持的 Codex 模型切换标准或高速模式(Beta);
148
+ - 输入框可为支持的 Codex 模型切换标准或高速模式;
149
149
  - 设置页可生成并复制无敏感信息的支持诊断,并直接打开反馈入口;报告不包含 OAuth 凭据、账号标识或授权时间;
150
150
  - 订阅路由不可用时明确报错,不会静默切换到其他付费路由。
151
151
 
@@ -164,9 +164,13 @@ ChatGPT 返回可用重置卡时,设置页会用紧凑的一行显示数量和
164
164
  也可以主动尝试使用,适合重置卡即将过期的情况;是否需要重置仍由 ChatGPT 判断,服务端可能返回“当前无需重置”且不扣次数。
165
165
  最终操作需要勾选知情确认并等待 5 秒。取消不会消耗,快速连续点击只允许一次请求,网络结果不确定时也不会自动重试。
166
166
 
167
- 生成的图片可在插件自有预览中下载原图。需要继续修改时,直接在同一会话描述改动;持久化的会话图片会继续提供给下一轮模型。
167
+ ### 图片生成与编辑(Beta)
168
168
 
169
- ### 输入框速度(Beta)
169
+ 生成的图片会在 DSH 原生风格的查看器中打开,支持缩放、适合窗口、查看尺寸和下载原图。全新生成不会静默带入历史图片;
170
+ 需要继续修改时,要在同一会话明确说明编辑这张图,插件只会发送明确选中的持久图片引用,最多 5 张。GPT Image 2 的耗时可能明显长于
171
+ 文本回复,复杂文字、精确构图和连续角色一致性也可能需要再次调整。
172
+
173
+ ### 输入框速度
170
174
 
171
175
  选择支持的 Codex 模型后,可在输入框的模型菜单中切换标准与高速。标准模式不增加图标,
172
176
  只有高速模式会在模型名称左侧显示闪电;Spark 不显示速度入口。高速模式会提高速度,也会消耗更多 Credits;具体规则见
package/lib/client.js CHANGED
@@ -185,14 +185,16 @@ window.__ModuleLoader__.load({
185
185
  monthlyCreditLimit: "Credits 月度消费上限",
186
186
  resetCredits: "可用额度重置次数",
187
187
  resetCreditsValue: "{count} 次",
188
- resetUse: "查看并使用",
188
+ resetUse: "使用重置",
189
189
  resetPreparing: "正在读取重置详情…",
190
190
  resetConfirmTitle: "确认使用额度重置",
191
191
  resetWarning: "若 ChatGPT 执行重置,将立即消耗 1 次且无法撤销。",
192
192
  resetEarlyWarning: "当前模型额度尚未用尽;ChatGPT 可能执行重置,也可能判定暂无需重置且不消耗次数。",
193
193
  resetAcknowledge: "我知道这次操作可能立即消耗 1 次重置",
194
- resetCreditExpires: "最早到期:{value}",
194
+ resetCreditExpires: "到期:{value}",
195
195
  resetCreditExpiryUnknown: "到期时间未提供",
196
+ resetCreditExpiryLoading: "正在读取到期时间…",
197
+ resetCreditExpiryFailed: "无法读取到期时间",
196
198
  resetWait: "请再等待 {count} 秒",
197
199
  resetFinal: "消耗 1 次并重置额度",
198
200
  resetUsing: "正在重置…",
@@ -233,6 +235,7 @@ window.__ModuleLoader__.load({
233
235
  modelFailed: "模型目录加载失败:{value}",
234
236
  groupFailed: "{name}:{value}",
235
237
  imageGenerate: "生成图片",
238
+ imageBeta: "Beta",
236
239
  imageGenerating: "正在生成…",
237
240
  imageGenerated: "已生成",
238
241
  imageFailed: "生成失败",
@@ -244,7 +247,11 @@ window.__ModuleLoader__.load({
244
247
  imagePreview: "图片预览",
245
248
  imageClosePreview: "关闭预览",
246
249
  imageDownload: "下载原图",
247
- imageRefineHint: "继续在当前会话描述修改内容,即可基于这张图片继续调整。"
250
+ imageZoomOut: "缩小",
251
+ imageZoomIn: "放大",
252
+ imageFit: "适合窗口",
253
+ imageRefineHint: "要修改这张图,请在当前会话明确说“编辑这张图片……”;插件只会把这张图作为参考。",
254
+ imageNewHint: "要重新生成,请直接描述一张新图片;不会自动带入历史图片。"
248
255
  };
249
256
  const en = {
250
257
  nav: "Codex",
@@ -301,14 +308,16 @@ window.__ModuleLoader__.load({
301
308
  monthlyCreditLimit: "Monthly Credits spending cap",
302
309
  resetCredits: "Available quota resets",
303
310
  resetCreditsValue: "{count} available",
304
- resetUse: "Review and use",
311
+ resetUse: "Use reset",
305
312
  resetPreparing: "Reading reset details…",
306
313
  resetConfirmTitle: "Confirm quota reset",
307
314
  resetWarning: "If ChatGPT performs the reset, one reset is consumed immediately and cannot be restored.",
308
315
  resetEarlyWarning: "No model quota is exhausted. ChatGPT may reset it, or report that nothing needs resetting without consuming a reset.",
309
316
  resetAcknowledge: "I understand this may consume one reset now",
310
- resetCreditExpires: "Earliest expiry: {value}",
317
+ resetCreditExpires: "Expires {value}",
311
318
  resetCreditExpiryUnknown: "Expiration time not provided",
319
+ resetCreditExpiryLoading: "Reading expiration…",
320
+ resetCreditExpiryFailed: "Could not read expiration",
312
321
  resetWait: "Wait {count} more seconds",
313
322
  resetFinal: "Consume one reset",
314
323
  resetUsing: "Resetting…",
@@ -349,6 +358,7 @@ window.__ModuleLoader__.load({
349
358
  modelFailed: "Could not load models: {value}",
350
359
  groupFailed: "{name}: {value}",
351
360
  imageGenerate: "Generate image",
361
+ imageBeta: "Beta",
352
362
  imageGenerating: "Generating…",
353
363
  imageGenerated: "Generated",
354
364
  imageFailed: "Generation failed",
@@ -360,7 +370,11 @@ window.__ModuleLoader__.load({
360
370
  imagePreview: "Image preview",
361
371
  imageClosePreview: "Close preview",
362
372
  imageDownload: "Download original",
363
- imageRefineHint: "Describe the changes in this conversation to continue refining this image."
373
+ imageZoomOut: "Zoom out",
374
+ imageZoomIn: "Zoom in",
375
+ imageFit: "Fit to window",
376
+ imageRefineHint: "To change this image, say “Edit this image…” in this conversation; only this image is sent as a reference.",
377
+ imageNewHint: "To start over, describe a new image; prior images are not included automatically."
364
378
  };
365
379
  const STYLE = `
366
380
  .codexSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
@@ -395,7 +409,8 @@ window.__ModuleLoader__.load({
395
409
  .codexModelSelect{position:relative;min-width:0}.codexModelSelectTrigger{display:flex;align-items:center;gap:4px;min-width:0;max-width:min(360px,45cqw);height:28px;padding:0 4px 0 8px;border:0;border-radius:24px;outline:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:500;line-height:20px;cursor:pointer}.codexModelSelectTrigger:hover:not(:disabled),.codexModelSelectTrigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.codexModelSelectTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectBolt{display:block;flex:none;width:14px;height:14px;color:var(--dsw-alias-label-primary)}.codexModelSelectLabel{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectEffort{flex:none;color:var(--dsw-alias-label-caption)}.codexModelSelectChevron{flex:none;color:var(--dsw-alias-label-caption);transition:transform 120ms}.codexModelSelectTrigger[aria-expanded=true] .codexModelSelectChevron{transform:rotate(180deg)}
396
410
  .codexModelSelectMenu,.codexModelSelectSubmenu{position:absolute;z-index:30;box-sizing:border-box;width:max-content;min-width:min(240px,calc(100vw - 32px));max-width:min(420px,calc(100vw - 32px));max-height:min(360px,calc(100vh - 96px));padding:4px;border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);overflow:hidden}.codexModelSelectMenu{right:0;bottom:calc(100% + 8px)}.codexModelSelectSubmenu{right:calc(100% + 8px);bottom:0;min-width:min(230px,calc(100vw - 32px))}.codexModelSelectCell{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;height:40px;box-sizing:border-box;padding:0 10px;border:0;border-radius:10px;background:transparent;color:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer}.codexModelSelectCell:hover,.codexModelSelectCell:focus-visible,.codexModelSelectCell[data-open=true]{background:var(--dsw-alias-interactive-bg-hover);outline:0}.codexModelSelectCell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectCellLabel{flex:none;white-space:nowrap}.codexModelSelectCellValue{flex:auto;min-width:0;overflow:hidden;color:var(--dsw-alias-label-tertiary);text-align:right;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCellChevron{flex:none;color:var(--dsw-alias-label-tertiary)}.codexModelSelectGroups{min-height:0;max-height:352px;overflow-y:auto}.codexModelSelectGroup+.codexModelSelectGroup{margin-top:4px}.codexModelSelectGroupTitle{position:sticky;top:0;z-index:1;padding:5px 8px 3px;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:500;line-height:18px}.codexModelSelectOption{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;min-height:38px;box-sizing:border-box;padding:6px 8px;border:0;border-radius:10px;outline:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.codexModelSelectOption:hover:not(:disabled),.codexModelSelectOption:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectOption:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectOptionCopy{display:flex;flex:1;min-width:0;flex-direction:column}.codexModelSelectOptionName{overflow:hidden;color:inherit;font-size:14px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectOptionDescription{overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCheck{display:grid;place-items:center;flex:0 0 18px;color:var(--dsw-alias-label-primary)}.codexModelSelectStatus,.codexModelSelectEmpty{padding:10px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.codexModelSelectError,.codexModelSelectWarning{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:4px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.codexModelSelectWarning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}.codexModelSelectRetry{flex:none;padding:0;border:0;background:transparent;color:inherit;font:inherit;font-weight:600;cursor:pointer}
397
411
  .codexModelSelectMenu{overflow:visible}
398
- .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 - 112px);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}.codexGeneratedImageActions{position:absolute;left:50%;bottom:18px;transform:translateX(-50%);display:flex;align-items:center;gap:10px;max-width:min(90vw,720px);padding:6px 8px 6px 12px;border:1px solid rgba(255,255,255,.2);border-radius:999px;background:rgba(20,20,20,.82);color:rgba(255,255,255,.72);font-size:12px;line-height:18px;backdrop-filter:blur(12px)}.codexGeneratedImageDownload{flex:0 0 auto;padding:5px 10px;border-radius:999px;background:rgba(255,255,255,.12);color:#fff;text-decoration:none;white-space:nowrap}.codexGeneratedImageDownload:hover{background:rgba(255,255,255,.2)}.codexGeneratedImageDownload:focus-visible,.codexGeneratedImageClose:focus-visible{outline:2px solid #fff;outline-offset:2px}.codexGeneratedImageRefineHint{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
412
+ .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}.codexGeneratedImageModal{width:min(920px,calc(100vw - 32px));max-height:calc(100vh - 32px)}.codexGeneratedImageModalContent{min-height:0;overflow:hidden}.codexGeneratedImageViewer{display:flex;min-width:0;flex-direction:column;gap:12px}.codexGeneratedImageStage{display:grid;place-items:center;min-height:280px;max-height:calc(100vh - 260px);overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-module-platform)}.codexGeneratedImageStage img{display:block;max-width:100%;max-height:calc(100vh - 280px);object-fit:contain;transform-origin:center;transition:transform 120ms ease}.codexGeneratedImageMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.codexGeneratedImageToolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.codexGeneratedImageZoom{display:flex;align-items:center;gap:6px}.codexGeneratedImageZoomValue{min-width:44px;color:var(--dsw-alias-label-secondary);font-size:12px;text-align:center}.codexGeneratedImageDownload{display:inline-flex;align-items:center;gap:6px;min-height:32px;box-sizing:border-box;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font-size:13px;text-decoration:none}.codexGeneratedImageDownload:hover{background:var(--dsw-alias-interactive-bg-hover)}.codexGeneratedImageGuidance{display:flex;flex-direction:column;gap:2px;padding-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
413
+ .codexImageBeta{padding:0 5px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:16px}
399
414
  @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
400
415
  @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
401
416
  @media(max-width:640px){.codexSubscriptionCard{padding:14px}}
@@ -427,13 +442,13 @@ window.__ModuleLoader__.load({
427
442
  if (cleaned === "") return fallback;
428
443
  return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
429
444
  };
445
+ 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`;
430
446
  function CodexGeneratedImage({ attachment, loadImage, t }) {
431
447
  const [attempt, setAttempt] = (0, react.useState)(0);
432
448
  const [error, setError] = (0, react.useState)(false);
433
449
  const [open, setOpen] = (0, react.useState)(false);
434
450
  const [src, setSrc] = (0, react.useState)();
435
- const closeRef = (0, react.useRef)(null);
436
- const downloadRef = (0, react.useRef)(null);
451
+ const [zoom, setZoom] = (0, react.useState)(1);
437
452
  const triggerRef = (0, react.useRef)(null);
438
453
  (0, react.useEffect)(() => {
439
454
  let live = true;
@@ -452,28 +467,13 @@ window.__ModuleLoader__.load({
452
467
  loadImage,
453
468
  attempt
454
469
  ]);
455
- (0, react.useEffect)(() => {
456
- if (!open) return void 0;
457
- const handleKeyDown = (event) => {
458
- if (event.key === "Escape") {
459
- event.preventDefault();
460
- setOpen(false);
461
- } else if (event.key === "Tab") {
462
- event.preventDefault();
463
- const controls = [downloadRef.current, closeRef.current].filter(Boolean);
464
- const current = controls.indexOf(document.activeElement);
465
- controls[event.shiftKey ? current <= 0 ? controls.length - 1 : current - 1 : (current + 1) % controls.length]?.focus();
466
- }
467
- };
468
- document.addEventListener("keydown", handleKeyDown);
469
- closeRef.current?.focus();
470
- return () => {
471
- document.removeEventListener("keydown", handleKeyDown);
472
- triggerRef.current?.focus();
473
- };
474
- }, [open]);
470
+ const close = () => {
471
+ setOpen(false);
472
+ window.requestAnimationFrame(() => triggerRef.current?.focus());
473
+ };
475
474
  const label = attachment.name ?? t("imageLabel");
476
475
  const downloadName = imageDownloadName(attachment);
476
+ const imageMeta = `${attachment.width} × ${attachment.height} · ${imageByteSize(attachment.bytes)}`;
477
477
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
478
478
  type: "button",
479
479
  className: "codexGeneratedImageRetry",
@@ -487,47 +487,84 @@ window.__ModuleLoader__.load({
487
487
  title: t("imageOpen"),
488
488
  "aria-label": fill(t("imageOpenNamed"), { value: label }),
489
489
  onClick: () => {
490
- if (src !== void 0) setOpen(true);
490
+ if (src !== void 0) {
491
+ setZoom(1);
492
+ setOpen(true);
493
+ }
491
494
  },
492
495
  children: src === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageLoading") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
493
496
  src,
494
497
  alt: label
495
498
  })
496
- }), !open || src === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
497
- className: "codexGeneratedImageLightbox",
498
- role: "dialog",
499
- "aria-modal": "true",
500
- "aria-label": t("imagePreview"),
501
- onClick: () => setOpen(false),
502
- children: [
503
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
504
- src,
505
- alt: label,
506
- onClick: (event) => event.stopPropagation()
507
- }),
508
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
509
- className: "codexGeneratedImageActions",
510
- onClick: (event) => event.stopPropagation(),
511
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
512
- className: "codexGeneratedImageRefineHint",
513
- children: t("imageRefineHint")
514
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
515
- ref: downloadRef,
516
- className: "codexGeneratedImageDownload",
517
- href: src,
518
- download: downloadName,
519
- children: t("imageDownload")
520
- })]
521
- }),
522
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
523
- ref: closeRef,
524
- type: "button",
525
- className: "codexGeneratedImageClose",
526
- "aria-label": t("imageClosePreview"),
527
- onClick: () => setOpen(false),
528
- children: "×"
529
- })
530
- ]
499
+ }), src === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
500
+ open,
501
+ onClose: close,
502
+ title: t("imagePreview"),
503
+ closeLabel: t("imageClosePreview"),
504
+ className: "codexGeneratedImageModal",
505
+ contentClassName: "codexGeneratedImageModalContent",
506
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
507
+ className: "codexGeneratedImageViewer",
508
+ children: [
509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
510
+ className: "codexGeneratedImageStage",
511
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
512
+ src,
513
+ alt: label,
514
+ style: { transform: `scale(${zoom})` }
515
+ })
516
+ }),
517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
518
+ className: "codexGeneratedImageMeta",
519
+ children: imageMeta
520
+ }),
521
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
522
+ className: "codexGeneratedImageToolbar",
523
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
524
+ className: "codexGeneratedImageZoom",
525
+ children: [
526
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
527
+ type: "button",
528
+ variant: "outline",
529
+ "aria-label": t("imageZoomOut"),
530
+ title: t("imageZoomOut"),
531
+ disabled: zoom <= .5,
532
+ onClick: () => setZoom((value) => Math.max(.5, value - .25)),
533
+ children: "−"
534
+ }),
535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
536
+ className: "codexGeneratedImageZoomValue",
537
+ children: [Math.round(zoom * 100), "%"]
538
+ }),
539
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
540
+ type: "button",
541
+ variant: "outline",
542
+ "aria-label": t("imageZoomIn"),
543
+ title: t("imageZoomIn"),
544
+ disabled: zoom >= 3,
545
+ onClick: () => setZoom((value) => Math.min(3, value + .25)),
546
+ children: "+"
547
+ }),
548
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
549
+ type: "button",
550
+ variant: "outline",
551
+ onClick: () => setZoom(1),
552
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { "aria-hidden": "true" }), t("imageFit")]
553
+ })
554
+ ]
555
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
556
+ className: "codexGeneratedImageDownload",
557
+ href: src,
558
+ download: downloadName,
559
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { "aria-hidden": "true" }), t("imageDownload")]
560
+ })]
561
+ }),
562
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
563
+ className: "codexGeneratedImageGuidance",
564
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageRefineHint") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageNewHint") })]
565
+ })
566
+ ]
567
+ })
531
568
  })] });
532
569
  }
533
570
  function CodexImageToolRow({ block, loadImage, t }) {
@@ -552,6 +589,10 @@ window.__ModuleLoader__.load({
552
589
  className: "codexImageToolTitle",
553
590
  children: t("imageGenerate")
554
591
  }),
592
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
593
+ className: "codexImageBeta",
594
+ children: t("imageBeta")
595
+ }),
555
596
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
556
597
  className: "codexImageToolState",
557
598
  children: status
@@ -1412,6 +1453,31 @@ window.__ModuleLoader__.load({
1412
1453
  const [resetCountdown, setResetCountdown] = (0, react.useState)(0);
1413
1454
  const [resetError, setResetError] = (0, react.useState)();
1414
1455
  const [resetResult, setResetResult] = (0, react.useState)();
1456
+ const [inspectedExpiry, setInspectedExpiry] = (0, react.useState)(nextExpiresAt);
1457
+ const [expiryState, setExpiryState] = (0, react.useState)(nextExpiresAt === void 0 ? "loading" : "ready");
1458
+ (0, react.useEffect)(() => {
1459
+ if (nextExpiresAt !== void 0) {
1460
+ setInspectedExpiry(nextExpiresAt);
1461
+ setExpiryState("ready");
1462
+ return;
1463
+ }
1464
+ let live = true;
1465
+ setExpiryState("loading");
1466
+ rpc.call(CHANNEL, "reset-credit/inspect", {}).then(unwrap).then((value) => {
1467
+ if (!live) return;
1468
+ setInspectedExpiry(value.nextExpiresAt);
1469
+ setExpiryState("ready");
1470
+ }).catch(() => {
1471
+ if (live) setExpiryState("error");
1472
+ });
1473
+ return () => {
1474
+ live = false;
1475
+ };
1476
+ }, [
1477
+ rpc,
1478
+ count,
1479
+ nextExpiresAt
1480
+ ]);
1415
1481
  (0, react.useEffect)(() => {
1416
1482
  if (challenge === void 0) {
1417
1483
  setResetCountdown(0);
@@ -1463,8 +1529,15 @@ window.__ModuleLoader__.load({
1463
1529
  className: "codexSubscriptionResetSummary",
1464
1530
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1465
1531
  className: "codexSubscriptionResetMeta",
1466
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: fill(t("resetCreditsValue"), { count }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditExpiry, {
1467
- expiresAt: nextExpiresAt,
1532
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: fill(t("resetCreditsValue"), { count }) }), expiryState === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1533
+ className: "codexSubscriptionResetExpiry",
1534
+ role: "status",
1535
+ children: t("resetCreditExpiryLoading")
1536
+ }) : expiryState === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1537
+ className: "codexSubscriptionResetExpiry",
1538
+ children: t("resetCreditExpiryFailed")
1539
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditExpiry, {
1540
+ expiresAt: inspectedExpiry,
1468
1541
  t
1469
1542
  })]
1470
1543
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
package/lib/index.js CHANGED
@@ -448,6 +448,7 @@ const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
448
448
  const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
449
449
  //#endregion
450
450
  //#region src/pi-ai-runtime.js
451
+ const FAST_SERVICE_TIER = "priority";
451
452
  /**
452
453
  * Preserve pi-ai's native Codex OAuth provider while allowing DSH's generic
453
454
  * PiAiAdapter to pass the access token resolved by the host credential store.
@@ -478,15 +479,15 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0 } = {
478
479
  const onPayload = options.onPayload;
479
480
  return {
480
481
  ...options,
481
- serviceTier: "fast",
482
+ serviceTier: FAST_SERVICE_TIER,
482
483
  async onPayload(payload, requestModel) {
483
484
  const fastPayload = {
484
485
  ...payload,
485
- service_tier: "fast"
486
+ service_tier: FAST_SERVICE_TIER
486
487
  };
487
488
  return {
488
489
  ...await onPayload?.(fastPayload, requestModel) ?? fastPayload,
489
- service_tier: "fast"
490
+ service_tier: FAST_SERVICE_TIER
490
491
  };
491
492
  }
492
493
  };
@@ -503,7 +504,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0 } = {
503
504
  }
504
505
  //#endregion
505
506
  //#region src/version.js
506
- const PACKAGE_VERSION = "1.5.0";
507
+ const PACKAGE_VERSION = "1.6.0";
507
508
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
508
509
  //#endregion
509
510
  //#region src/codex-search.js
@@ -626,7 +627,9 @@ function createCodexSearchProvider(options) {
626
627
  //#region src/codex-images.js
627
628
  const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
628
629
  const CODEX_IMAGE_GENERATION_URL = "https://chatgpt.com/backend-api/codex/images/generations";
630
+ const CODEX_IMAGE_EDIT_URL = "https://chatgpt.com/backend-api/codex/images/edits";
629
631
  const IMAGE_MODEL = "gpt-image-2";
632
+ const MAX_REFERENCE_IMAGES = 5;
630
633
  const RESPONSE_ENVELOPE_BYTES = 1024 * 1024;
631
634
  const PNG_SIGNATURE = Buffer.from([
632
635
  137,
@@ -695,6 +698,24 @@ function imageReference(value) {
695
698
  ...value.name === void 0 ? {} : { name: value.name }
696
699
  };
697
700
  }
701
+ function referenceOf(value, attachments) {
702
+ if (!record$2(value) || typeof value.attachmentId !== "string" || value.attachmentId.length === 0 || value.attachmentId.length > 256 || !attachments.imageLimits.mediaTypes.includes(value.mediaType) || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || !Number.isSafeInteger(value.width) || value.width <= 0 || !Number.isSafeInteger(value.height) || value.height <= 0 || value.name !== void 0 && (typeof value.name !== "string" || value.name.length > 256)) throw new Error("referenceImages contains an invalid image reference");
703
+ return imageReference(value);
704
+ }
705
+ async function editImages(values, attachments, signal) {
706
+ if (!Array.isArray(values) || values.length === 0 || values.length > MAX_REFERENCE_IMAGES) throw new Error(`referenceImages must contain between 1 and ${MAX_REFERENCE_IMAGES} images`);
707
+ const references = values.map((value) => referenceOf(value, attachments));
708
+ if (new Set(references.map((value) => value.attachmentId)).size !== references.length) throw new Error("referenceImages must not contain duplicates");
709
+ const images = [];
710
+ let totalBytes = 0;
711
+ for (const reference of references) {
712
+ const stored = await attachments.readImage(reference, signal);
713
+ totalBytes += stored.data.byteLength;
714
+ if (totalBytes > attachments.imageLimits.maxMessageImageBytes) throw new Error("referenceImages exceed the DSH message image limit");
715
+ images.push({ image_url: `data:${stored.ref.mediaType};base64,${Buffer.from(stored.data).toString("base64")}` });
716
+ }
717
+ return images;
718
+ }
698
719
  function imageContent(value) {
699
720
  return [{
700
721
  type: "text",
@@ -761,12 +782,45 @@ function createCodexImageTool(options) {
761
782
  const attachments = options.attachments;
762
783
  return defineTool({
763
784
  name: CODEX_IMAGE_TOOL_NAME,
764
- description: "Generate an image from a detailed description using the signed-in Codex subscription. Use this whenever the user asks to create an image.",
765
- parameters: { prompt: {
766
- type: "string",
767
- required: true,
768
- description: "A complete, production-ready description of the image to generate."
769
- } },
785
+ description: "Create a new image or explicitly edit selected prior images using the signed-in Codex subscription. Omit referenceImages for a completely new image. Include only the exact prior image references the user asked to edit; never assume every image in the conversation is a reference.",
786
+ parameters: {
787
+ prompt: {
788
+ type: "string",
789
+ required: true,
790
+ description: "A complete, production-ready description of the image to generate."
791
+ },
792
+ referenceImages: {
793
+ type: "array",
794
+ description: "Optional explicit references to 1-5 prior images to edit. Omit for a new image.",
795
+ items: {
796
+ type: "object",
797
+ additionalProperties: false,
798
+ properties: {
799
+ attachmentId: {
800
+ type: "string",
801
+ required: true
802
+ },
803
+ mediaType: {
804
+ type: "string",
805
+ required: true
806
+ },
807
+ bytes: {
808
+ type: "integer",
809
+ required: true
810
+ },
811
+ width: {
812
+ type: "integer",
813
+ required: true
814
+ },
815
+ height: {
816
+ type: "integer",
817
+ required: true
818
+ },
819
+ name: { type: "string" }
820
+ }
821
+ }
822
+ }
823
+ },
770
824
  output: {
771
825
  schema: imageOutputSchema(),
772
826
  render: (_args, value) => imageContent(value)
@@ -783,9 +837,11 @@ function createCodexImageTool(options) {
783
837
  if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new Error("ChatGPT subscription is not signed in");
784
838
  if (!attachments.imageLimits.mediaTypes.includes("image/png")) throw new Error("This DSH installation does not accept PNG image attachments");
785
839
  const maximumBytes = Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
840
+ const editing = args.referenceImages !== void 0;
841
+ const images = editing ? await editImages(args.referenceImages, attachments, exec.signal) : void 0;
786
842
  let response;
787
843
  try {
788
- response = await fetchImage(CODEX_IMAGE_GENERATION_URL, {
844
+ response = await fetchImage(editing ? CODEX_IMAGE_EDIT_URL : CODEX_IMAGE_GENERATION_URL, {
789
845
  method: "POST",
790
846
  redirect: "error",
791
847
  headers: {
@@ -798,6 +854,7 @@ function createCodexImageTool(options) {
798
854
  "user-agent": USER_AGENT
799
855
  },
800
856
  body: JSON.stringify({
857
+ ...images === void 0 ? {} : { images },
801
858
  prompt,
802
859
  background: "auto",
803
860
  model: IMAGE_MODEL,
@@ -808,12 +865,12 @@ function createCodexImageTool(options) {
808
865
  });
809
866
  } catch (error) {
810
867
  if (exec.signal.aborted) throw exec.signal.reason;
811
- throw new Error("Codex image generation request failed", { cause: error });
868
+ throw new Error(`Codex image ${editing ? "edit" : "generation"} request failed`, { cause: error });
812
869
  }
813
870
  if (!response.ok) {
814
871
  if (response.status === 401 || response.status === 403) throw new Error("ChatGPT sign-in needs to be renewed");
815
872
  if (response.status === 429) throw new Error("Codex image generation quota is unavailable");
816
- throw new Error(`Codex image generation failed (HTTP ${response.status})`);
873
+ throw new Error(`Codex image ${editing ? "edit" : "generation"} failed (HTTP ${response.status})`);
817
874
  }
818
875
  const metadata = responseMetadata(await readJsonWithin(response, encodedLimit(maximumBytes) + RESPONSE_ENVELOPE_BYTES));
819
876
  const data = decodeCodexPng(metadata.encoded, maximumBytes);
@@ -1145,29 +1202,42 @@ function createCodexResetCreditService(options) {
1145
1202
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1146
1203
  const challenges = /* @__PURE__ */ new Map();
1147
1204
  const resolveCredentials = async (signal) => credentialsOf(await getAuth({ signal }), await readCredential({ signal }));
1205
+ const readDetails = async (signal) => {
1206
+ const { access, accountId } = await resolveCredentials(signal);
1207
+ const response = await fetchReset(CODEX_RESET_CREDITS_URL, {
1208
+ method: "GET",
1209
+ redirect: "error",
1210
+ headers: {
1211
+ authorization: `Bearer ${access}`,
1212
+ "chatgpt-account-id": accountId,
1213
+ accept: "application/json",
1214
+ "cache-control": "no-store",
1215
+ "user-agent": USER_AGENT
1216
+ },
1217
+ signal: requestSignal(signal, timeoutMs)
1218
+ });
1219
+ if (!response.ok) throw new Error(response.status === 401 || response.status === 403 ? "ChatGPT sign-in needs to be renewed" : `ChatGPT quota reset request failed (HTTP ${response.status})`);
1220
+ let raw;
1221
+ try {
1222
+ raw = await response.json();
1223
+ } catch {
1224
+ throw new Error("ChatGPT returned unreadable quota reset details");
1225
+ }
1226
+ return {
1227
+ accountId,
1228
+ details: parseDetails(raw, now())
1229
+ };
1230
+ };
1148
1231
  return Object.freeze({
1232
+ async inspect({ signal } = {}) {
1233
+ const { details } = await readDetails(signal);
1234
+ return {
1235
+ availableCount: details.availableCount,
1236
+ ...details.creditExpiresAt === void 0 ? {} : { nextExpiresAt: details.creditExpiresAt }
1237
+ };
1238
+ },
1149
1239
  async prepare({ signal } = {}) {
1150
- const { access, accountId } = await resolveCredentials(signal);
1151
- const response = await fetchReset(CODEX_RESET_CREDITS_URL, {
1152
- method: "GET",
1153
- redirect: "error",
1154
- headers: {
1155
- authorization: `Bearer ${access}`,
1156
- "chatgpt-account-id": accountId,
1157
- accept: "application/json",
1158
- "cache-control": "no-store",
1159
- "user-agent": USER_AGENT
1160
- },
1161
- signal: requestSignal(signal, timeoutMs)
1162
- });
1163
- if (!response.ok) throw new Error(response.status === 401 || response.status === 403 ? "ChatGPT sign-in needs to be renewed" : `ChatGPT quota reset request failed (HTTP ${response.status})`);
1164
- let raw;
1165
- try {
1166
- raw = await response.json();
1167
- } catch {
1168
- throw new Error("ChatGPT returned unreadable quota reset details");
1169
- }
1170
- const details = parseDetails(raw, now());
1240
+ const { accountId, details } = await readDetails(signal);
1171
1241
  const preparedAt = now();
1172
1242
  const readyAt = preparedAt + confirmDelayMs;
1173
1243
  const expiresAt = Math.min(preparedAt + challengeTtlMs, details.creditExpiresAt ?? Number.MAX_SAFE_INTEGER);
@@ -1329,11 +1399,11 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1329
1399
  const message = error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage";
1330
1400
  return publicError("internal", message);
1331
1401
  }
1332
- if (endpoint === "reset-credit/prepare" || endpoint === "reset-credit/consume") try {
1402
+ if (endpoint === "reset-credit/inspect" || endpoint === "reset-credit/prepare" || endpoint === "reset-credit/consume") try {
1333
1403
  signal.throwIfAborted();
1334
1404
  return {
1335
1405
  ok: true,
1336
- value: endpoint === "reset-credit/prepare" ? await resetCreditService.prepare({ signal }) : await resetCreditService.consume({
1406
+ value: endpoint === "reset-credit/inspect" ? await resetCreditService.inspect({ signal }) : endpoint === "reset-credit/prepare" ? await resetCreditService.prepare({ signal }) : await resetCreditService.consume({
1337
1407
  challengeId: payload?.challengeId,
1338
1408
  acknowledged: payload?.acknowledged,
1339
1409
  signal
@@ -1353,7 +1423,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1353
1423
  "You must acknowledge that one quota reset will be consumed",
1354
1424
  "The signed-in ChatGPT account changed"
1355
1425
  ]);
1356
- const fallback = endpoint === "reset-credit/prepare" ? "Could not prepare a quota reset" : "Could not use the quota reset";
1426
+ const fallback = endpoint === "reset-credit/inspect" ? "Could not read quota reset details" : endpoint === "reset-credit/prepare" ? "Could not prepare a quota reset" : "Could not use the quota reset";
1357
1427
  const message = error instanceof Error && known.has(error.message) ? error.message : fallback;
1358
1428
  return publicError("internal", message);
1359
1429
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.5.0",
3
+ "version": "1.6.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",