@eddyskywalker/dsh-chatgpt-subscription 0.1.9 → 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 (46) hide show
  1. package/CHANGELOG.md +11 -1
  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 +720 -248
  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 +1 -2
  29. package/lib/types/host/responses-mapper.d.ts.map +1 -1
  30. package/lib/types/host/routes.d.ts +2 -1
  31. package/lib/types/host/routes.d.ts.map +1 -1
  32. package/lib/types/host/search-provider-switcher.d.ts +15 -0
  33. package/lib/types/host/search-provider-switcher.d.ts.map +1 -0
  34. package/lib/types/host/usage-service.d.ts +2 -1
  35. package/lib/types/host/usage-service.d.ts.map +1 -1
  36. package/lib/types/index.d.ts +3 -1
  37. package/lib/types/index.d.ts.map +1 -1
  38. package/lib/types/shared/contracts.d.ts +40 -3
  39. package/lib/types/shared/contracts.d.ts.map +1 -1
  40. package/lib/types/shared/model-catalog.d.ts +81 -0
  41. package/lib/types/shared/model-catalog.d.ts.map +1 -0
  42. package/lib/types/shared/preferences.d.ts +7 -0
  43. package/lib/types/shared/preferences.d.ts.map +1 -0
  44. package/package.json +21 -2
  45. package/lib/types/host/subagent-report-scheduling-compat.d.ts +0 -21
  46. package/lib/types/host/subagent-report-scheduling-compat.d.ts.map +0 -1
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;