@eddyskywalker/dsh-chatgpt-subscription 0.1.10 → 0.1.11

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +17 -7
  3. package/lib/client.js +442 -28
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +718 -247
  6. package/lib/types/client/CodexComposerQuota.d.ts +13 -0
  7. package/lib/types/client/CodexComposerQuota.d.ts.map +1 -0
  8. package/lib/types/client/CodexImageToolView.d.ts +10 -0
  9. package/lib/types/client/CodexImageToolView.d.ts.map +1 -0
  10. package/lib/types/client/CodexSubscriptionSection.d.ts.map +1 -1
  11. package/lib/types/client/api.d.ts +2 -1
  12. package/lib/types/client/api.d.ts.map +1 -1
  13. package/lib/types/client/index.d.ts.map +1 -1
  14. package/lib/types/client/locales.d.ts +57 -3
  15. package/lib/types/client/locales.d.ts.map +1 -1
  16. package/lib/types/client/quota.d.ts +9 -0
  17. package/lib/types/client/quota.d.ts.map +1 -0
  18. package/lib/types/client/styles.d.ts.map +1 -1
  19. package/lib/types/compat.d.ts +7 -0
  20. package/lib/types/compat.d.ts.map +1 -1
  21. package/lib/types/host/codex-images.d.ts +9 -0
  22. package/lib/types/host/codex-images.d.ts.map +1 -0
  23. package/lib/types/host/codex-search.d.ts +11 -0
  24. package/lib/types/host/codex-search.d.ts.map +1 -0
  25. package/lib/types/host/model-catalog.d.ts.map +1 -1
  26. package/lib/types/host/preferences.d.ts +12 -0
  27. package/lib/types/host/preferences.d.ts.map +1 -0
  28. package/lib/types/host/responses-mapper.d.ts.map +1 -1
  29. package/lib/types/host/routes.d.ts +2 -1
  30. package/lib/types/host/routes.d.ts.map +1 -1
  31. package/lib/types/host/search-provider-switcher.d.ts +15 -0
  32. package/lib/types/host/search-provider-switcher.d.ts.map +1 -0
  33. package/lib/types/host/usage-service.d.ts +2 -1
  34. package/lib/types/host/usage-service.d.ts.map +1 -1
  35. package/lib/types/index.d.ts +3 -1
  36. package/lib/types/index.d.ts.map +1 -1
  37. package/lib/types/shared/contracts.d.ts +40 -3
  38. package/lib/types/shared/contracts.d.ts.map +1 -1
  39. package/lib/types/shared/model-catalog.d.ts +81 -0
  40. package/lib/types/shared/model-catalog.d.ts.map +1 -0
  41. package/lib/types/shared/preferences.d.ts +7 -0
  42. package/lib/types/shared/preferences.d.ts.map +1 -0
  43. package/package.json +21 -2
  44. package/lib/types/host/subagent-report-scheduling-compat.d.ts +0 -21
  45. package/lib/types/host/subagent-report-scheduling-compat.d.ts.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.11 - 2026-08-18
4
+
5
+ - 移除临时子代理报告去重兼容模块:按照标记 `DSH_COMPAT_REMOVE(subagent-report-settlement-dedup)` 安全移除 `subagent-report-scheduling-compat` 模块、相关测试及插件中的 `agents` 依赖注入与挂钩清理逻辑。
6
+ - 扩展 Codex usage 解析,支持 `additional_rate_limits`、Credits、月度消费控制与 reset credits,并在设置页展示。
7
+ - 新增 ChatGPT 订阅侧 Codex 搜索 provider,可在设置页切换 DSH 默认搜索或 `codex-subscription` 搜索来源。
8
+ - 新增 `codex_image_generate` 图片生成工具,使用 Codex 图片 endpoint 生成 PNG 并保存为 DSH 图片附件,同时提供会话内工具结果渲染。
9
+ - 新增 composer 快捷用量徽标开关;当前会话选择 `codex-chatgpt` 模型时,可显示最紧张限额窗口的剩余额度。
10
+ - 统一模型目录显示名与 Codex App 风格:`5.6 Sol`、`5.6 Terra`、`5.6 Luna`、`5.5`、`5.4`、`5.4 Mini`、`5.3 Codex Spark`,并为 Spark 避免发送不兼容的 `reasoning.summary`。
11
+
3
12
  ## 0.1.10 - 2026-08-17
4
13
 
5
14
  - 移除临时 Bash Prompt 兼容模块:由于上游 DSH `0.1.0-rc.7` 已经原生修复持久化 Bash 提示符不匹配与重置问题,现按照标记 `DSH_COMPAT_REMOVE(persistent-bash-prompt-mismatch)` 安全移除全部相关兼容代码与测试。保留子代理报告去重兼容模块 `DSH_COMPAT_REMOVE(subagent-report-settlement-dedup)`。
package/README.md CHANGED
@@ -31,17 +31,27 @@
31
31
  - 固定 Codex Responses 地址,支持流式文本、reasoning summary、图片输入与工具调用/结果;
32
32
  - 原样转发 DSH 暴露的工具 schema;命令工具兼容 `pwsh` / `powershell`、`bash`、`sh` 与 `shell`,并按 PowerShell、Bash 或 POSIX sh 注入对应说明;
33
33
  - 429/5xx 由 DSH retry policy 接管;401 只强制刷新并重试一次,支持 `AbortSignal`;
34
- - CodexCode review 多窗口额度,60 秒缓存、15 秒上游节流并遵守 `Retry-After`;
35
- - 临时兼容 DSH `0.1.0-rc.6` 的子代理最终报告去重:若 settlement 已向父 Agent 提供同一子代理、完全相同的最终内容,则删除仍在 inbox 中的重复 report;部分报告、不同内容和不同子代理不受影响。兼容代码以 `DSH_COMPAT_REMOVE(subagent-report-settlement-dedup)` 集中标记,待 DSH 原生修复后删除。
34
+ - CodexCode review 及上游返回的额外窗口额度,支持 Credits、月度消费控制与 reset credits 展示;60 秒缓存、15 秒上游节流并遵守 `Retry-After`;
35
+ - 提供 ChatGPT 订阅侧 Codex 搜索 provider,可在设置页切换 DSH 默认搜索或 Codex 订阅搜索;
36
+ - 新增 `codex_image_generate` 工具,生成图片后通过 DSH 附件系统保存并在会话中渲染;
37
+ - 可选 composer 快捷用量徽标,按当前 `codex-chatgpt` 模型显示最紧张窗口的剩余额度。
36
38
 
37
39
  **设置页**
38
40
 
39
- - 展示账号(脱敏 email、套餐、账号 ID 后四位)、连接状态与额度;
41
+ - 展示账号(脱敏 email、套餐、账号 ID 后四位)、连接状态、额度与订阅增强功能开关;
40
42
  - 可访问的进度条、窄窗口/200% 缩放布局、深浅主题与 reduced-motion。
41
43
 
42
44
  ## 模型目录
43
45
 
44
- `gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna`、`gpt-5.5`、`gpt-5.4`、`gpt-5.4-mini`、`gpt-5.2`
46
+ | 显示名 | 模型 slug |
47
+ | --- | --- |
48
+ | 5.6 Sol | `gpt-5.6-sol` |
49
+ | 5.6 Terra | `gpt-5.6-terra` |
50
+ | 5.6 Luna | `gpt-5.6-luna` |
51
+ | 5.5 | `gpt-5.5` |
52
+ | 5.4 | `gpt-5.4` |
53
+ | 5.4 Mini | `gpt-5.4-mini` |
54
+ | 5.3 Codex Spark | `gpt-5.3-codex-spark` |
45
55
 
46
56
  > 目录只用于展示;账号实际可用的模型由 ChatGPT 套餐、workspace 策略与上游兼容状态决定。
47
57
 
@@ -113,7 +123,7 @@ DSH 模型选择器应显示 **“Codex(ChatGPT 订阅)”**。
113
123
 
114
124
  - OAuth 回调固定为 `http://localhost:1455/auth/callback`,登录任务五分钟超时,同一时刻只允许一个;
115
125
  - OAuth token 只发送到 `https://auth.openai.com/oauth/token`;
116
- - 模型与额度地址分别固定为 `https://chatgpt.com/backend-api/codex/responses` 和 `https://chatgpt.com/backend-api/wham/usage`,没有 endpoint override;
126
+ - 模型、图片、搜索与额度地址分别固定为 `https://chatgpt.com/backend-api/codex/responses`、`https://chatgpt.com/backend-api/codex/images/generations`、`https://chatgpt.com/backend-api/codex/alpha/search` 和 `https://chatgpt.com/backend-api/wham/usage`,没有 endpoint override;
117
127
  - Windows token bundle 使用 CurrentUser DPAPI;明文只经过 Host 内存和 PowerShell stdin/stdout;
118
128
  - Linux token bundle 原子写入当前用户私有文件并在读取时校验普通文件、所有者与 `0600` 权限,同时拒绝符号链接;该文件没有应用层加密,同 UID 进程、root、备份和磁盘快照仍可读取;
119
129
  - 两个平台的 token 都不会进入浏览器、`settings.yaml` 或日志;
@@ -134,6 +144,7 @@ DSH 模型选择器应显示 **“Codex(ChatGPT 订阅)”**。
134
144
  | POST | `/token/refresh` | 刷新 token |
135
145
  | POST | `/quota/refresh` | 刷新额度 |
136
146
  | POST | `/connection/test` | 测试连接 |
147
+ | POST | `/preferences/update` | 更新搜索来源和 composer 快捷用量偏好 |
137
148
 
138
149
  状态响应只包含脱敏 email、套餐、账号 ID 后四位、token 到期时间和额度 DTO。
139
150
 
@@ -159,5 +170,4 @@ npm pack --dry-run
159
170
  | 模型不可用 | 检查 ChatGPT 套餐、workspace 权限与当前模型可用性 |
160
171
  | DPAPI 读取失败 | 确认 DSH 以创建凭据时的同一 Windows 用户运行;必要时清理凭据后重新登录 |
161
172
  | Linux 凭据存储不可用 | 确认凭据属于当前用户且权限为 `0600`,父目录权限为 `0700`;修复权限或注销后重新登录 |
162
- | Linux 上工具调用语法错误 | 确认 DSH 暴露的是 `bash`、`sh` 或 `shell`,并使用相应的 Bash/POSIX 语法与 `/` 路径 |
163
- | 父 Agent 已收到子代理结果,但相同报告仍显示为排队消息 | 本插件为 DSH `0.1.0-rc.6` 提供临时 report/settlement 精确去重;升级或重载插件并新建会话后验证。若未来 DSH 已原生修复,可移除所有 `DSH_COMPAT_REMOVE(subagent-report-settlement-dedup)` 标记代码 |
173
+ | Linux 上工具调用语法错误 | 确认 DSH 暴露的是 `bash`、`sh` 或 `shell`,并使用相应的 Bash/POSIX 语法与 `/` 路径 |
package/lib/client.js CHANGED
@@ -7,6 +7,242 @@ window.__ModuleLoader__.load({
7
7
  let react = require("react");
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
9
  const ROUTE_PREFIX = "/api/dsh-chatgpt-subscription";
10
+ const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
11
+ const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
12
+ //#endregion
13
+ //#region src/client/quota.ts
14
+ function selectQuotaForModel(quota, modelId) {
15
+ if (quota === void 0 || quota.buckets.length === 0) return null;
16
+ const bucket = (modelId?.toLowerCase() ?? "").includes("spark") ? quota.buckets.find((candidate) => `${candidate.id} ${candidate.name}`.toLowerCase().includes("spark")) : quota.buckets.find((candidate) => candidate.id === "codex") ?? quota.buckets.find((candidate) => candidate.name.toLowerCase() === "codex");
17
+ if (bucket === void 0) return null;
18
+ const windows = quotaWindows(bucket);
19
+ if (windows.length === 0) return null;
20
+ const window = windows.reduce((tightest, candidate) => candidate.usedPercent > tightest.usedPercent ? candidate : tightest, windows[0]);
21
+ return {
22
+ bucket,
23
+ window,
24
+ remainingPercent: Math.max(0, 100 - window.usedPercent)
25
+ };
26
+ }
27
+ function quotaWindows(bucket) {
28
+ if (bucket.windows.length > 0) return bucket.windows;
29
+ return [bucket.primary, bucket.secondary].filter((window) => window !== null);
30
+ }
31
+ //#endregion
32
+ //#region src/client/CodexComposerQuota.tsx
33
+ function CodexComposerQuota({ api, directory, loadModelDirectory, t }) {
34
+ const modelState = useStore(directory);
35
+ const [status, setStatus] = (0, react.useState)(null);
36
+ const [loading, setLoading] = (0, react.useState)(false);
37
+ const mountedRef = (0, react.useRef)(false);
38
+ const selected = modelState.current;
39
+ const isCodex = selected?.provider === CODEX_CHATGPT_PROVIDER_ID;
40
+ (0, react.useEffect)(() => {
41
+ loadModelDirectory();
42
+ }, [loadModelDirectory]);
43
+ (0, react.useEffect)(() => {
44
+ mountedRef.current = true;
45
+ return () => {
46
+ mountedRef.current = false;
47
+ };
48
+ }, []);
49
+ (0, react.useEffect)(() => {
50
+ if (!isCodex) return;
51
+ let disposed = false;
52
+ const refresh = async () => {
53
+ setLoading(true);
54
+ try {
55
+ const next = await api.status();
56
+ if (!disposed && mountedRef.current) setStatus(next);
57
+ } catch {
58
+ if (!disposed && mountedRef.current) setStatus(null);
59
+ } finally {
60
+ if (!disposed && mountedRef.current) setLoading(false);
61
+ }
62
+ };
63
+ refresh();
64
+ const timer = window.setInterval(() => {
65
+ if (document.visibilityState === "visible") refresh();
66
+ }, 6e4);
67
+ return () => {
68
+ disposed = true;
69
+ window.clearInterval(timer);
70
+ };
71
+ }, [api, isCodex]);
72
+ const quota = (0, react.useMemo)(() => selectQuotaForModel(status?.quota, selected?.model), [selected?.model, status?.quota]);
73
+ if (!isCodex || status?.preferences.quickQuotaVisible !== true) return null;
74
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
75
+ className: "dsh-codex-composer-quota",
76
+ "data-level": quota === null ? "normal" : quota.remainingPercent <= 5 ? "danger" : quota.remainingPercent <= 20 ? "warning" : "normal",
77
+ "aria-label": quota === null ? t("quickQuotaLoading") : `${t("quickQuotaLabel")}: ${formatPercent$1(quota.remainingPercent)}`,
78
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("quickQuotaLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: quota === null ? loading ? t("quickQuotaLoading") : "—" : formatPercent$1(quota.remainingPercent) })]
79
+ });
80
+ }
81
+ function useStore(store) {
82
+ return (0, react.useSyncExternalStore)((listener) => store.subscribe(listener), () => store.getSnapshot(), () => store.getSnapshot());
83
+ }
84
+ function formatPercent$1(value) {
85
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 0 }).format(value)}%`;
86
+ }
87
+ //#endregion
88
+ //#region src/client/CodexImageToolView.tsx
89
+ function CodexImageToolView({ block, loadImage, t }) {
90
+ const settled = "kind" in block;
91
+ const isError = settled ? block.isError : false;
92
+ const prompt = promptFromBlock(block);
93
+ const images = settled && !isError ? imageBlocks(block.content) : [];
94
+ const label = isError ? t("imageToolFailed") : settled ? t("imageToolDone") : t("imageToolRunning");
95
+ const summary = prompt ?? textSummary(settled ? block.content : []);
96
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
97
+ className: "dsh-codex-image-tool",
98
+ "data-state": isError ? "error" : settled ? "done" : "running",
99
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
100
+ className: "dsh-codex-image-tool-head",
101
+ children: [
102
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
103
+ className: "dsh-codex-image-dot",
104
+ "aria-hidden": "true"
105
+ }),
106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
107
+ className: "dsh-codex-image-title",
108
+ children: label
109
+ }),
110
+ summary !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
111
+ className: "dsh-codex-image-summary",
112
+ children: summary
113
+ }) : null
114
+ ]
115
+ }), images.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
116
+ className: "dsh-codex-image-grid",
117
+ children: images.map((attachment) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GeneratedImage, {
118
+ attachment,
119
+ load: loadImage,
120
+ label: attachment.name ?? t("image"),
121
+ t
122
+ }, String(attachment.attachmentId)))
123
+ }) : null]
124
+ });
125
+ }
126
+ function GeneratedImage({ attachment, load, label, t }) {
127
+ const [src, setSrc] = (0, react.useState)(null);
128
+ const [failed, setFailed] = (0, react.useState)(false);
129
+ (0, react.useEffect)(() => {
130
+ let disposed = false;
131
+ setSrc(null);
132
+ setFailed(false);
133
+ load(attachment).then((url) => {
134
+ if (!disposed) setSrc(url);
135
+ }, () => {
136
+ if (!disposed) setFailed(true);
137
+ });
138
+ return () => {
139
+ disposed = true;
140
+ };
141
+ }, [attachment, load]);
142
+ if (failed) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
143
+ className: "dsh-codex-image-failed",
144
+ children: t("imageLoadFailed")
145
+ });
146
+ if (src === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
147
+ className: "dsh-codex-image-loading",
148
+ children: t("imageLoading")
149
+ });
150
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
151
+ className: "dsh-codex-image-preview",
152
+ src,
153
+ alt: label
154
+ });
155
+ }
156
+ function promptFromBlock(block) {
157
+ const raw = "kind" in block ? block.call?.argsRaw : block.argsRaw;
158
+ if (typeof raw !== "string" || raw.trim() === "") return null;
159
+ try {
160
+ const value = JSON.parse(raw);
161
+ return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.prompt === "string" ? value.prompt : null;
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
166
+ function imageBlocks(blocks) {
167
+ const result = [];
168
+ for (const block of blocks) {
169
+ if (block.type === "image") result.push(block.attachment);
170
+ if (block.type === "tool-result") result.push(...imageBlocks(block.content));
171
+ }
172
+ return result;
173
+ }
174
+ function textSummary(blocks) {
175
+ const text = blocks.map((block) => block.type === "text" || block.type === "reasoning" ? block.text : "").filter(Boolean).join(" ").trim();
176
+ return text === "" ? null : text;
177
+ }
178
+ //#endregion
179
+ //#region src/shared/model-catalog.ts
180
+ const CODEX_MODEL_CATALOG = [
181
+ {
182
+ id: "gpt-5.6-sol",
183
+ name: "5.6 Sol",
184
+ contextWindow: 272e3,
185
+ inputModalities: ["text", "image"],
186
+ defaultReasoningEffort: "medium",
187
+ reasoningProfile: "gpt-5.6",
188
+ supportsReasoningSummary: true
189
+ },
190
+ {
191
+ id: "gpt-5.6-terra",
192
+ name: "5.6 Terra",
193
+ contextWindow: 272e3,
194
+ inputModalities: ["text", "image"],
195
+ defaultReasoningEffort: "medium",
196
+ reasoningProfile: "gpt-5.6",
197
+ supportsReasoningSummary: true
198
+ },
199
+ {
200
+ id: "gpt-5.6-luna",
201
+ name: "5.6 Luna",
202
+ contextWindow: 272e3,
203
+ inputModalities: ["text", "image"],
204
+ defaultReasoningEffort: "medium",
205
+ reasoningProfile: "gpt-5.6",
206
+ supportsReasoningSummary: true
207
+ },
208
+ {
209
+ id: "gpt-5.5",
210
+ name: "5.5",
211
+ contextWindow: 272e3,
212
+ inputModalities: ["text", "image"],
213
+ defaultReasoningEffort: "medium",
214
+ reasoningProfile: "standard",
215
+ supportsReasoningSummary: true
216
+ },
217
+ {
218
+ id: "gpt-5.4",
219
+ name: "5.4",
220
+ contextWindow: 272e3,
221
+ inputModalities: ["text", "image"],
222
+ defaultReasoningEffort: "none",
223
+ reasoningProfile: "standard",
224
+ supportsReasoningSummary: true
225
+ },
226
+ {
227
+ id: "gpt-5.4-mini",
228
+ name: "5.4 Mini",
229
+ contextWindow: 272e3,
230
+ inputModalities: ["text", "image"],
231
+ defaultReasoningEffort: "none",
232
+ reasoningProfile: "standard",
233
+ supportsReasoningSummary: true
234
+ },
235
+ {
236
+ id: "gpt-5.3-codex-spark",
237
+ name: "5.3 Codex Spark",
238
+ contextWindow: 258e3,
239
+ inputModalities: ["text"],
240
+ defaultReasoningEffort: "high",
241
+ reasoningProfile: "standard",
242
+ supportsReasoningSummary: false
243
+ }
244
+ ];
245
+ CODEX_MODEL_CATALOG[0];
10
246
  //#endregion
11
247
  //#region src/client/api.ts
12
248
  var SubscriptionApi = class {
@@ -31,6 +267,9 @@ window.__ModuleLoader__.load({
31
267
  testConnection() {
32
268
  return post(`${ROUTE_PREFIX}/connection/test`, {});
33
269
  }
270
+ updatePreferences(patch) {
271
+ return post(`${ROUTE_PREFIX}/preferences/update`, patch);
272
+ }
34
273
  events(loginId) {
35
274
  return new EventSource(`${ROUTE_PREFIX}/login/events?loginId=${encodeURIComponent(loginId)}`);
36
275
  }
@@ -61,15 +300,6 @@ window.__ModuleLoader__.load({
61
300
  }
62
301
  //#endregion
63
302
  //#region src/client/CodexSubscriptionSection.tsx
64
- const MODELS = [
65
- "gpt-5.6-sol",
66
- "gpt-5.6-terra",
67
- "gpt-5.6-luna",
68
- "gpt-5.5",
69
- "gpt-5.4",
70
- "gpt-5.4-mini",
71
- "gpt-5.2"
72
- ];
73
303
  function CodexSubscriptionSection({ t }) {
74
304
  const apiRef = (0, react.useRef)(new SubscriptionApi());
75
305
  const eventSourceRef = (0, react.useRef)(null);
@@ -182,6 +412,13 @@ window.__ModuleLoader__.load({
182
412
  checkedAt: result.checkedAt
183
413
  });
184
414
  });
415
+ const updatePreferences = async (patch) => run("preferences", async () => {
416
+ const preferences = await apiRef.current.updatePreferences(patch);
417
+ setStatus((current) => current === null ? current : {
418
+ ...current,
419
+ preferences
420
+ });
421
+ });
185
422
  const logout = async () => run("logout", async () => {
186
423
  await apiRef.current.logout();
187
424
  eventSourceRef.current?.close();
@@ -310,7 +547,10 @@ window.__ModuleLoader__.load({
310
547
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
311
548
  className: "dsh-codex-models",
312
549
  "aria-label": t("models"),
313
- children: MODELS.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: model }, model))
550
+ children: CODEX_MODEL_CATALOG.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
551
+ title: model.id,
552
+ children: model.name
553
+ }, model.id))
314
554
  }),
315
555
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
316
556
  className: "dsh-codex-actions",
@@ -322,6 +562,41 @@ window.__ModuleLoader__.load({
322
562
  })
323
563
  ]
324
564
  }),
565
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
566
+ title: t("enhancements"),
567
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
568
+ className: "dsh-codex-pref-row",
569
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("searchProvider") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
570
+ className: "dsh-codex-muted",
571
+ children: t("searchProviderHint")
572
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
573
+ className: "dsh-codex-segments",
574
+ role: "radiogroup",
575
+ "aria-label": t("searchProvider"),
576
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
577
+ type: "radio",
578
+ name: "dsh-codex-search-provider",
579
+ checked: status?.preferences.searchProvider === "dsh",
580
+ disabled: busy !== null,
581
+ onChange: () => updatePreferences({ searchProvider: "dsh" })
582
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("searchProviderDsh") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
583
+ type: "radio",
584
+ name: "dsh-codex-search-provider",
585
+ checked: status?.preferences.searchProvider === "codex",
586
+ disabled: busy !== null,
587
+ onChange: () => updatePreferences({ searchProvider: "codex" })
588
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("searchProviderCodex") })] })]
589
+ })]
590
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
591
+ className: "dsh-codex-check",
592
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
593
+ type: "checkbox",
594
+ checked: status?.preferences.quickQuotaVisible === true,
595
+ disabled: busy !== null,
596
+ onChange: (event) => updatePreferences({ quickQuotaVisible: event.currentTarget.checked })
597
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("quickQuota") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("quickQuotaHint") })] })]
598
+ })]
599
+ }),
325
600
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
326
601
  title: t("quota"),
327
602
  aside: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
@@ -342,6 +617,23 @@ window.__ModuleLoader__.load({
342
617
  bucket,
343
618
  t
344
619
  }, bucket.id)),
620
+ status?.quota.credits !== null && status?.quota.credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaFact, {
621
+ label: t("credits"),
622
+ value: status.quota.credits.unlimited ? t("unlimited") : status.quota.credits.balance ?? (status.quota.credits.hasCredits ? t("available") : t("unavailable"))
623
+ }) : null,
624
+ status?.quota.individualLimit !== null && status?.quota.individualLimit !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaFact, {
625
+ label: t("monthlySpend"),
626
+ value: individualLimitLabel(status.quota.individualLimit, t)
627
+ }) : null,
628
+ status?.quota.resetCredits !== null && status?.quota.resetCredits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaFact, {
629
+ label: t("resetCredits"),
630
+ value: String(status.quota.resetCredits.availableCount)
631
+ }) : null,
632
+ status?.quota.spendControlReached === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
633
+ className: "dsh-codex-warning",
634
+ role: "status",
635
+ children: t("spendControlReached")
636
+ }) : null,
345
637
  status?.quota.state === "empty" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
346
638
  className: "dsh-codex-empty",
347
639
  children: t("noQuota")
@@ -420,24 +712,23 @@ window.__ModuleLoader__.load({
420
712
  return t("securityUnavailable");
421
713
  }
422
714
  function QuotaBucket({ bucket, t }) {
715
+ const windows = quotaWindows(bucket);
423
716
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
424
717
  className: "dsh-codex-quota-card",
425
- children: [
426
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
427
- className: "dsh-codex-quota-title",
428
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: bucket.name }), bucket.planType ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: bucket.planType }) : null]
429
- }),
430
- bucket.primary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaBar, {
431
- label: windowLabel(bucket.primary.windowDurationMins, t),
432
- window: bucket.primary,
433
- t
434
- }) : null,
435
- bucket.secondary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaBar, {
436
- label: windowLabel(bucket.secondary.windowDurationMins, t),
437
- window: bucket.secondary,
438
- t
439
- }) : null
440
- ]
718
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
719
+ className: "dsh-codex-quota-title",
720
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: bucket.name }), bucket.planType ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: bucket.planType }) : null]
721
+ }), windows.map((window, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaBar, {
722
+ label: windowLabel(window.windowDurationMins, t),
723
+ window,
724
+ t
725
+ }, `${window.windowDurationMins ?? "x"}:${window.resetsAt ?? "x"}:${index}`))]
726
+ });
727
+ }
728
+ function QuotaFact({ label, value }) {
729
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
730
+ className: "dsh-codex-quota-fact",
731
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: value })]
441
732
  });
442
733
  }
443
734
  function QuotaBar({ label, window, t }) {
@@ -487,6 +778,15 @@ window.__ModuleLoader__.load({
487
778
  function formatPercent(value) {
488
779
  return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value)}%`;
489
780
  }
781
+ function individualLimitLabel(limit, t) {
782
+ const parts = [
783
+ limit.remainingPercent !== null ? `${formatPercent(limit.remainingPercent)} ${t("remaining")}` : null,
784
+ limit.limit !== null ? `${t("limit")}: ${limit.limit}` : null,
785
+ limit.used !== null ? `${t("used")}: ${limit.used}` : null,
786
+ limit.resetsAt !== null ? `${t("resets")}: ${formatReset(limit.resetsAt)}` : null
787
+ ].filter((part) => part !== null);
788
+ return parts.length > 0 ? parts.join(" · ") : t("unknown");
789
+ }
490
790
  function formatDate(seconds) {
491
791
  if (seconds === void 0) return "—";
492
792
  return new Intl.DateTimeFormat(void 0, {
@@ -544,6 +844,13 @@ window.__ModuleLoader__.load({
544
844
  testing: "测试中…",
545
845
  latency: "最近延迟",
546
846
  models: "可用模型",
847
+ enhancements: "增强功能",
848
+ searchProvider: "搜索来源",
849
+ searchProviderHint: "选择 DSH Web 搜索工具使用默认来源,或改用当前 ChatGPT 订阅的 Codex 搜索来源。",
850
+ searchProviderDsh: "DSH 默认",
851
+ searchProviderCodex: "Codex 订阅",
852
+ quickQuota: "在输入框旁显示快捷用量",
853
+ quickQuotaHint: "仅在当前会话选中 codex-chatgpt 模型时显示。",
547
854
  quota: "用量与限额",
548
855
  quotaIntro: "数据来自 ChatGPT Codex 用量服务。页面可见时最多每 60 秒刷新一次。",
549
856
  refreshQuota: "刷新用量",
@@ -557,8 +864,28 @@ window.__ModuleLoader__.load({
557
864
  limitWindow: "额度",
558
865
  used: "已使用",
559
866
  remaining: "剩余",
867
+ available: "可用",
868
+ unavailable: "不可用",
869
+ unlimited: "无限",
870
+ credits: "Credits",
871
+ monthlySpend: "月度消费控制",
872
+ resetCredits: "重置次数",
873
+ spendControlReached: "已触发月度消费控制,新的订阅调用可能被限制。",
874
+ limit: "上限",
560
875
  exhausted: "额度已用尽",
561
876
  resets: "重置",
877
+ quickQuotaLabel: "Codex 剩余",
878
+ quickQuotaLoading: "用量…",
879
+ imageToolRunning: "正在生成图片",
880
+ imageToolDone: "已生成图片",
881
+ imageToolFailed: "图片生成失败",
882
+ image: "图片",
883
+ openImage: "打开图片",
884
+ openNamedImage: "打开 {name}",
885
+ imageLoading: "加载图片…",
886
+ imageLoadFailed: "图片加载失败,点击重试",
887
+ imagePreviewClose: "关闭",
888
+ imagePreviewOpenOriginal: "打开原图",
562
889
  retry: "重试",
563
890
  unknown: "未知"
564
891
  },
@@ -598,6 +925,13 @@ window.__ModuleLoader__.load({
598
925
  testing: "Testing…",
599
926
  latency: "Last latency",
600
927
  models: "Available models",
928
+ enhancements: "Enhancements",
929
+ searchProvider: "Search provider",
930
+ searchProviderHint: "Choose whether DSH web search uses its default provider or the Codex search source from this ChatGPT subscription.",
931
+ searchProviderDsh: "DSH default",
932
+ searchProviderCodex: "Codex subscription",
933
+ quickQuota: "Show quick usage beside the composer",
934
+ quickQuotaHint: "Shown only when the current session uses a codex-chatgpt model.",
601
935
  quota: "Usage and limits",
602
936
  quotaIntro: "Data comes from the ChatGPT Codex usage service and refreshes at most once per minute while visible.",
603
937
  refreshQuota: "Refresh usage",
@@ -611,8 +945,28 @@ window.__ModuleLoader__.load({
611
945
  limitWindow: "limit",
612
946
  used: "used",
613
947
  remaining: "remaining",
948
+ available: "Available",
949
+ unavailable: "Unavailable",
950
+ unlimited: "Unlimited",
951
+ credits: "Credits",
952
+ monthlySpend: "Monthly spend control",
953
+ resetCredits: "Reset credits",
954
+ spendControlReached: "Monthly spend control has been reached; new subscription calls may be limited.",
955
+ limit: "Limit",
614
956
  exhausted: "Quota exhausted",
615
957
  resets: "Resets",
958
+ quickQuotaLabel: "Codex left",
959
+ quickQuotaLoading: "Usage…",
960
+ imageToolRunning: "Generating image",
961
+ imageToolDone: "Generated image",
962
+ imageToolFailed: "Image generation failed",
963
+ image: "Image",
964
+ openImage: "Open image",
965
+ openNamedImage: "Open {name}",
966
+ imageLoading: "Loading image…",
967
+ imageLoadFailed: "Image failed to load. Click to retry",
968
+ imagePreviewClose: "Close",
969
+ imagePreviewOpenOriginal: "Open original",
616
970
  retry: "Retry",
617
971
  unknown: "Unknown"
618
972
  }
@@ -645,9 +999,24 @@ window.__ModuleLoader__.load({
645
999
  .dsh-codex-link{color:var(--dsw-alias-label-link,#3278d4);display:inline-block;font-size:13px;margin-top:8px}
646
1000
  .dsh-codex-models{display:flex;flex-wrap:wrap;gap:6px;padding-top:12px}
647
1001
  .dsh-codex-models code{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:5px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;padding:4px 6px}
1002
+ .dsh-codex-pref-row{align-items:center;border-bottom:1px solid var(--dsw-alias-border-l2);display:flex;gap:16px;justify-content:space-between;min-height:58px;padding:10px 0}
1003
+ .dsh-codex-pref-row strong,.dsh-codex-check strong{display:block;font-size:13px;font-weight:600;line-height:1.35}
1004
+ .dsh-codex-segments{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;display:flex;flex:none;gap:2px;padding:2px}
1005
+ .dsh-codex-segments label{cursor:pointer;display:block}
1006
+ .dsh-codex-segments input{position:absolute;opacity:0;pointer-events:none}
1007
+ .dsh-codex-segments span{border-radius:6px;color:var(--dsw-alias-label-secondary);display:block;font-size:12px;line-height:1;padding:7px 10px;white-space:nowrap}
1008
+ .dsh-codex-segments input:checked+span{background:var(--dsw-alias-bg-base);box-shadow:0 1px 2px rgba(0,0,0,.08);color:var(--dsw-alias-label-primary)}
1009
+ .dsh-codex-segments input:focus-visible+span{outline:2px solid var(--dsw-alias-button-info-fill,#397ee8);outline-offset:2px}
1010
+ .dsh-codex-segments input:disabled+span{cursor:default;opacity:.5}
1011
+ .dsh-codex-check{align-items:flex-start;border-bottom:1px solid var(--dsw-alias-border-l2);cursor:pointer;display:flex;gap:10px;padding:12px 0}
1012
+ .dsh-codex-check input{flex:none;margin-top:2px}
1013
+ .dsh-codex-check small{color:var(--dsw-alias-label-secondary);display:block;font-size:12px;line-height:1.45;margin-top:2px}
648
1014
  .dsh-codex-quota-card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;margin-top:12px;padding:12px}
649
1015
  .dsh-codex-quota-title{align-items:center;display:flex;font-size:13px;gap:8px;justify-content:space-between}
650
1016
  .dsh-codex-quota-title span{color:var(--dsw-alias-label-tertiary);font-size:11px;text-transform:uppercase}
1017
+ .dsh-codex-quota-fact{align-items:flex-start;border-bottom:1px solid var(--dsw-alias-border-l2);display:flex;font-size:12px;gap:12px;justify-content:space-between;line-height:1.45;padding:10px 0}
1018
+ .dsh-codex-quota-fact span{color:var(--dsw-alias-label-secondary);flex:none}
1019
+ .dsh-codex-quota-fact strong{font-weight:600;min-width:0;overflow-wrap:anywhere;text-align:right}
651
1020
  .dsh-codex-meter-wrap{margin-top:13px}
652
1021
  .dsh-codex-meter-label,.dsh-codex-meter-meta{display:flex;gap:10px;justify-content:space-between}
653
1022
  .dsh-codex-meter-label{font-size:12px;margin-bottom:6px}
@@ -662,8 +1031,22 @@ window.__ModuleLoader__.load({
662
1031
  .dsh-codex-skeleton span{animation:dsh-codex-pulse 1.4s ease-in-out infinite;background:var(--dsw-alias-bg-layer-2);border-radius:5px;height:42px}
663
1032
  .dsh-codex-skeleton span:nth-child(2){animation-delay:.12s}.dsh-codex-skeleton span:nth-child(3){animation-delay:.24s}
664
1033
  .dsh-codex-sr{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0 0 0 0);white-space:nowrap}
1034
+ .dsh-codex-composer-quota{align-items:center;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;color:var(--dsw-alias-label-secondary);display:inline-flex;font-size:11px;gap:6px;height:28px;line-height:1;max-width:160px;padding:0 9px;white-space:nowrap}
1035
+ .dsh-codex-composer-quota strong{color:var(--dsw-alias-label-primary);font-size:11px;font-weight:650}
1036
+ .dsh-codex-composer-quota[data-level=warning] strong{color:var(--dsw-alias-label-warning,#d58a24)}
1037
+ .dsh-codex-composer-quota[data-level=danger] strong{color:var(--dsw-alias-label-danger,#d94b4b)}
1038
+ .dsh-codex-image-tool{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;display:flex;flex-direction:column;gap:8px;margin:4px 0 4px 4px;max-width:520px;padding:10px 12px}
1039
+ .dsh-codex-image-tool-head{align-items:center;display:flex;font-size:13px;gap:8px;min-width:0}
1040
+ .dsh-codex-image-dot{background:var(--dsw-alias-button-info-fill,#397ee8);border-radius:50%;display:inline-block;flex:none;height:8px;width:8px}
1041
+ .dsh-codex-image-tool[data-state=error] .dsh-codex-image-dot{background:var(--dsw-alias-label-danger,#d94b4b)}
1042
+ .dsh-codex-image-title{font-weight:600}
1043
+ .dsh-codex-image-summary{color:var(--dsw-alias-label-tertiary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1044
+ .dsh-codex-image-grid{display:flex;flex-wrap:wrap;gap:8px}
1045
+ .dsh-codex-image-preview{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;display:block;max-height:260px;max-width:min(100%,360px);object-fit:contain}
1046
+ .dsh-codex-image-loading,.dsh-codex-image-failed{border:1px dashed var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-tertiary);display:inline-flex;font-size:12px;line-height:1.4;padding:18px 20px}
1047
+ .dsh-codex-image-failed{color:var(--dsw-alias-label-danger,#d94b4b)}
665
1048
  @keyframes dsh-codex-pulse{0%,100%{opacity:.55}50%{opacity:1}}
666
- @media(max-width:560px){.dsh-codex-row{align-items:flex-start;flex-direction:column;gap:3px}.dsh-codex-value{text-align:left}.dsh-codex-actions{justify-content:flex-start}.dsh-codex-grouphead{align-items:flex-start;flex-direction:column;gap:0;padding:12px 0}.dsh-codex-meter-meta{align-items:flex-start;flex-direction:column;gap:2px}.dsh-codex-errorbar{align-items:flex-start;flex-direction:column}}
1049
+ @media(max-width:560px){.dsh-codex-row,.dsh-codex-pref-row,.dsh-codex-quota-fact{align-items:flex-start;flex-direction:column;gap:3px}.dsh-codex-value,.dsh-codex-quota-fact strong{text-align:left}.dsh-codex-actions{justify-content:flex-start}.dsh-codex-grouphead{align-items:flex-start;flex-direction:column;gap:0;padding:12px 0}.dsh-codex-meter-meta{align-items:flex-start;flex-direction:column;gap:2px}.dsh-codex-errorbar{align-items:flex-start;flex-direction:column}.dsh-codex-segments{width:100%}.dsh-codex-segments label{flex:1}.dsh-codex-segments span{text-align:center}.dsh-codex-image-tool{max-width:100%;margin-left:0}}
667
1050
  @media(prefers-reduced-motion:reduce){.dsh-codex-meter>span{transition:none}.dsh-codex-skeleton span{animation:none}}
668
1051
  `;
669
1052
  function installStyles() {
@@ -677,7 +1060,12 @@ window.__ModuleLoader__.load({
677
1060
  }
678
1061
  //#endregion
679
1062
  //#region src/client/index.tsx
680
- const inject = ["slots", "locale"];
1063
+ const inject = [
1064
+ "slots",
1065
+ "locale",
1066
+ "modelDirectories",
1067
+ "conversation"
1068
+ ];
681
1069
  function apply(ctx) {
682
1070
  ctx.effect(() => ctx.locale.register(NS, dictionaries), "dsh-chatgpt-subscription: dictionaries");
683
1071
  ctx.effect(() => installStyles(), "dsh-chatgpt-subscription: styles");
@@ -688,6 +1076,32 @@ window.__ModuleLoader__.load({
688
1076
  label: "Codex 订阅",
689
1077
  locale: NS
690
1078
  }, CodexSubscriptionSection));
1079
+ ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
1080
+ name: "conversation.input.right",
1081
+ id: "codex-subscription-quota",
1082
+ order: 35,
1083
+ locale: NS,
1084
+ inject: (sessionId) => {
1085
+ const directory = ctx.modelDirectories.directoryFor(sessionId);
1086
+ return {
1087
+ api: new SubscriptionApi(),
1088
+ directory: directory.store,
1089
+ loadModelDirectory: () => {
1090
+ directory.load().catch(() => void 0);
1091
+ }
1092
+ };
1093
+ }
1094
+ }, CodexComposerQuota));
1095
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1096
+ name: "tool.call.toolview",
1097
+ key: CODEX_IMAGE_TOOL_NAME,
1098
+ locale: NS,
1099
+ inject: (sessionId) => ({ loadImage: imageLoader(ctx, sessionId) })
1100
+ }, CodexImageToolView));
1101
+ }
1102
+ function imageLoader(ctx, sessionId) {
1103
+ const conversation = ctx.conversation;
1104
+ return (attachment) => conversation.resolveImage(sessionId, attachment);
691
1105
  }
692
1106
  //#endregion
693
1107
  exports.apply = apply;