dsh-codex-subscription 1.4.1 → 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 +4 -4
- package/DIRECTORY.md +3 -3
- package/README.md +21 -13
- package/README.zh-CN.md +18 -12
- package/lib/client.js +218 -98
- package/lib/index.js +138 -57
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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, web search, image generation, optional composer quota,
|
|
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
|
[](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml)
|
|
10
10
|
[](https://www.npmjs.com/package/dsh-codex-subscription)
|
|
11
|
-
[](https://www.npmjs.com/package/dsh-codex-subscription)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
[](https://github.com/WSL043/dsh-codex-subscription/stargazers)
|
|
14
14
|
|
|
@@ -47,10 +47,10 @@ DSH-Portable exposes the same standard plugin command, so the command above also
|
|
|
47
47
|
| **Recoverable and diagnosable** | Sign-in state reconciles automatically; Settings can create a support report without credentials or account identifiers |
|
|
48
48
|
| **Visible quota** | Keep backend-provided standard Codex, Spark, and other limits separate |
|
|
49
49
|
| **Composer quota** | Choose a compact percentage, progress bar, or no inline quota display |
|
|
50
|
-
| **Safe quota reset** |
|
|
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** |
|
|
53
|
-
| **Fast mode
|
|
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,14 +139,14 @@ 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
|
|
149
|
-
- A copyable support report in Settings
|
|
148
|
+
- Standard or Fast mode for supported Codex models directly in the composer;
|
|
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
|
|
|
152
152
|
### Composer quota
|
|
@@ -161,12 +161,20 @@ Standard Codex uses the lowest remaining window returned by the service; Spark u
|
|
|
161
161
|
|
|
162
162
|
### Safe quota reset
|
|
163
163
|
|
|
164
|
-
If ChatGPT reports an available quota reset, Settings shows
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
If ChatGPT reports an available quota reset, Settings shows its count and earliest disclosed expiry in a compact row.
|
|
165
|
+
You may deliberately try it before a quota reaches 100%, which is useful for a reset nearing expiry. ChatGPT still
|
|
166
|
+
decides whether a window needs resetting and may return **nothing to reset** without spending the reset. The final
|
|
167
|
+
action requires an acknowledgement checkbox and five-second cooldown. Cancel never consumes a reset, rapid repeated
|
|
168
|
+
clicks are single-flight, and an uncertain network result is never retried automatically.
|
|
168
169
|
|
|
169
|
-
###
|
|
170
|
+
### Image generation and editing (Beta)
|
|
171
|
+
|
|
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
|
|
170
178
|
|
|
171
179
|
With a supported Codex model selected, open the composer's model menu to choose Standard or Fast.
|
|
172
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
|
[](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml)
|
|
11
11
|
[](https://www.npmjs.com/package/dsh-codex-subscription)
|
|
12
|
-
[](https://www.npmjs.com/package/dsh-codex-subscription)
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](https://github.com/WSL043/dsh-codex-subscription/stargazers)
|
|
15
15
|
|
|
@@ -48,10 +48,10 @@ DSH-Portable 也提供相同的标准插件命令,因此同样使用上面的
|
|
|
48
48
|
| **可恢复、可诊断** | 登录状态会自动对账;设置页可生成不含凭据和账号标识的支持报告 |
|
|
49
49
|
| **额度可见** | 普通 Codex、Spark 等服务端实际返回的额度分开显示 |
|
|
50
50
|
| **输入框额度** | 可选择紧凑百分比、进度条或完全关闭输入框额度显示 |
|
|
51
|
-
| **安全额度重置** |
|
|
51
|
+
| **安全额度重置** | 直接查看最早到期时间,并通过冷静期和知情确认主动尝试重置 |
|
|
52
52
|
| **订阅搜索** | 可在 DSH 默认搜索与 Codex 订阅搜索之间明确切换 |
|
|
53
|
-
| **Codex
|
|
54
|
-
|
|
|
53
|
+
| **Codex 图片生成与编辑(Beta)** | 可无参考图全新生成,也可明确选择会话图片继续编辑;支持预览、缩放和下载原图 |
|
|
54
|
+
| **高速模式** | 直接在输入框切换标准或高速,无需离开当前会话 |
|
|
55
55
|
|
|
56
56
|
这些能力共用同一份本机 ChatGPT 登录。订阅路由失败时会明确报错,不会静默切换到其他付费路由。
|
|
57
57
|
|
|
@@ -139,14 +139,14 @@ dsh --profile web --dump-config
|
|
|
139
139
|
## 功能
|
|
140
140
|
|
|
141
141
|
- ChatGPT OAuth 登录,凭据保留在本机;
|
|
142
|
-
- Codex
|
|
142
|
+
- Codex 模型和 Beta 图片生成与编辑直接出现在 DSH 会话中;
|
|
143
143
|
- DSH 默认搜索与 Codex 订阅搜索可随时切换;
|
|
144
144
|
- 设置页显示服务端返回的额度、重置时间和更新时间;
|
|
145
145
|
- 普通 Codex、Codex-Spark、Credits 等独立额度分开显示;
|
|
146
|
-
-
|
|
146
|
+
- 显示重置卡数量与最早到期时间,也允许在额度未完全用尽时主动尝试,并经过分层确认且不会自动重试;
|
|
147
147
|
- 输入框可用百分比或进度条显示当前 Codex 模型的剩余额度(默认关闭);
|
|
148
|
-
- 输入框可为支持的 Codex
|
|
149
|
-
-
|
|
148
|
+
- 输入框可为支持的 Codex 模型切换标准或高速模式;
|
|
149
|
+
- 设置页可生成并复制无敏感信息的支持诊断,并直接打开反馈入口;报告不包含 OAuth 凭据、账号标识或授权时间;
|
|
150
150
|
- 订阅路由不可用时明确报错,不会静默切换到其他付费路由。
|
|
151
151
|
|
|
152
152
|
### 输入框额度
|
|
@@ -160,11 +160,17 @@ Spark 使用独立额度。插件不会写死“5 小时 + 每周”,也不会
|
|
|
160
160
|
|
|
161
161
|
### 安全使用额度重置
|
|
162
162
|
|
|
163
|
-
ChatGPT
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
ChatGPT 返回可用重置卡时,设置页会用紧凑的一行显示数量和服务端提供的最早到期时间。即使额度尚未到 100%,
|
|
164
|
+
也可以主动尝试使用,适合重置卡即将过期的情况;是否需要重置仍由 ChatGPT 判断,服务端可能返回“当前无需重置”且不扣次数。
|
|
165
|
+
最终操作需要勾选知情确认并等待 5 秒。取消不会消耗,快速连续点击只允许一次请求,网络结果不确定时也不会自动重试。
|
|
166
166
|
|
|
167
|
-
###
|
|
167
|
+
### 图片生成与编辑(Beta)
|
|
168
|
+
|
|
169
|
+
生成的图片会在 DSH 原生风格的查看器中打开,支持缩放、适合窗口、查看尺寸和下载原图。全新生成不会静默带入历史图片;
|
|
170
|
+
需要继续修改时,要在同一会话明确说明编辑这张图,插件只会发送明确选中的持久图片引用,最多 5 张。GPT Image 2 的耗时可能明显长于
|
|
171
|
+
文本回复,复杂文字、精确构图和连续角色一致性也可能需要再次调整。
|
|
172
|
+
|
|
173
|
+
### 输入框速度
|
|
168
174
|
|
|
169
175
|
选择支持的 Codex 模型后,可在输入框的模型菜单中切换标准与高速。标准模式不增加图标,
|
|
170
176
|
只有高速模式会在模型名称左侧显示闪电;Spark 不显示速度入口。高速模式会提高速度,也会消耗更多 Credits;具体规则见
|
package/lib/client.js
CHANGED
|
@@ -127,6 +127,7 @@ window.__ModuleLoader__.load({
|
|
|
127
127
|
];
|
|
128
128
|
const NS = "settings.codexSubscription";
|
|
129
129
|
const CHANNEL = "/codex-subscription";
|
|
130
|
+
const SUPPORT_ISSUE_URL = "https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml";
|
|
130
131
|
const QUICK_QUOTA_REFRESH_EVENT = "dsh-codex-subscription:refresh-quick-quota";
|
|
131
132
|
const QUICK_QUOTA_REFRESH_MS = 6e4;
|
|
132
133
|
const zh = {
|
|
@@ -153,6 +154,7 @@ window.__ModuleLoader__.load({
|
|
|
153
154
|
diagnosticsCopy: "复制诊断",
|
|
154
155
|
diagnosticsCopied: "已复制",
|
|
155
156
|
diagnosticsFailed: "无法生成诊断信息。",
|
|
157
|
+
feedbackOpen: "反馈问题",
|
|
156
158
|
searchTitle: "搜索来源",
|
|
157
159
|
searchDsh: "DSH 默认",
|
|
158
160
|
searchDshHint: "当前搜索服务",
|
|
@@ -183,13 +185,16 @@ window.__ModuleLoader__.load({
|
|
|
183
185
|
monthlyCreditLimit: "Credits 月度消费上限",
|
|
184
186
|
resetCredits: "可用额度重置次数",
|
|
185
187
|
resetCreditsValue: "{count} 次",
|
|
186
|
-
resetUse: "
|
|
187
|
-
resetUnavailable: "仅当至少一个 Codex 模型额度已用尽时可使用。",
|
|
188
|
+
resetUse: "使用重置",
|
|
188
189
|
resetPreparing: "正在读取重置详情…",
|
|
189
190
|
resetConfirmTitle: "确认使用额度重置",
|
|
190
|
-
resetWarning: "
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
resetWarning: "若 ChatGPT 执行重置,将立即消耗 1 次且无法撤销。",
|
|
192
|
+
resetEarlyWarning: "当前模型额度尚未用尽;ChatGPT 可能执行重置,也可能判定暂无需重置且不消耗次数。",
|
|
193
|
+
resetAcknowledge: "我知道这次操作可能立即消耗 1 次重置",
|
|
194
|
+
resetCreditExpires: "到期:{value}",
|
|
195
|
+
resetCreditExpiryUnknown: "到期时间未提供",
|
|
196
|
+
resetCreditExpiryLoading: "正在读取到期时间…",
|
|
197
|
+
resetCreditExpiryFailed: "无法读取到期时间",
|
|
193
198
|
resetWait: "请再等待 {count} 秒",
|
|
194
199
|
resetFinal: "消耗 1 次并重置额度",
|
|
195
200
|
resetUsing: "正在重置…",
|
|
@@ -202,8 +207,7 @@ window.__ModuleLoader__.load({
|
|
|
202
207
|
resetExpired: "本次确认已失效,请重新开始。",
|
|
203
208
|
resetInProgress: "额度重置正在处理中。",
|
|
204
209
|
resetTooEarly: "请等待冷静期结束后再确认。",
|
|
205
|
-
|
|
206
|
-
resetNotExhausted: "当前 Codex 模型额度尚未用尽。",
|
|
210
|
+
resetAcknowledgeRequired: "请先确认已了解这次操作可能消耗重置次数。",
|
|
207
211
|
resetAccountChanged: "登录账号已变更,请重新开始。",
|
|
208
212
|
creditsNote: "仅显示 Codex 为此账户或工作区实际返回的额外 Credits、消费上限或额度重置次数;三者不是同一项。",
|
|
209
213
|
creditsUsed: "已用 {used} / {limit} credits",
|
|
@@ -231,6 +235,7 @@ window.__ModuleLoader__.load({
|
|
|
231
235
|
modelFailed: "模型目录加载失败:{value}",
|
|
232
236
|
groupFailed: "{name}:{value}",
|
|
233
237
|
imageGenerate: "生成图片",
|
|
238
|
+
imageBeta: "Beta",
|
|
234
239
|
imageGenerating: "正在生成…",
|
|
235
240
|
imageGenerated: "已生成",
|
|
236
241
|
imageFailed: "生成失败",
|
|
@@ -240,7 +245,13 @@ window.__ModuleLoader__.load({
|
|
|
240
245
|
imageLoading: "正在加载图片…",
|
|
241
246
|
imageLoadFailed: "图片加载失败,点击重试",
|
|
242
247
|
imagePreview: "图片预览",
|
|
243
|
-
imageClosePreview: "关闭预览"
|
|
248
|
+
imageClosePreview: "关闭预览",
|
|
249
|
+
imageDownload: "下载原图",
|
|
250
|
+
imageZoomOut: "缩小",
|
|
251
|
+
imageZoomIn: "放大",
|
|
252
|
+
imageFit: "适合窗口",
|
|
253
|
+
imageRefineHint: "要修改这张图,请在当前会话明确说“编辑这张图片……”;插件只会把这张图作为参考。",
|
|
254
|
+
imageNewHint: "要重新生成,请直接描述一张新图片;不会自动带入历史图片。"
|
|
244
255
|
};
|
|
245
256
|
const en = {
|
|
246
257
|
nav: "Codex",
|
|
@@ -266,6 +277,7 @@ window.__ModuleLoader__.load({
|
|
|
266
277
|
diagnosticsCopy: "Copy report",
|
|
267
278
|
diagnosticsCopied: "Copied",
|
|
268
279
|
diagnosticsFailed: "Could not create diagnostics.",
|
|
280
|
+
feedbackOpen: "Report a problem",
|
|
269
281
|
searchTitle: "Search source",
|
|
270
282
|
searchDsh: "DSH default",
|
|
271
283
|
searchDshHint: "Current search service",
|
|
@@ -296,13 +308,16 @@ window.__ModuleLoader__.load({
|
|
|
296
308
|
monthlyCreditLimit: "Monthly Credits spending cap",
|
|
297
309
|
resetCredits: "Available quota resets",
|
|
298
310
|
resetCreditsValue: "{count} available",
|
|
299
|
-
resetUse: "Use
|
|
300
|
-
resetUnavailable: "Available only when at least one Codex model quota is exhausted.",
|
|
311
|
+
resetUse: "Use reset",
|
|
301
312
|
resetPreparing: "Reading reset details…",
|
|
302
313
|
resetConfirmTitle: "Confirm quota reset",
|
|
303
|
-
resetWarning: "
|
|
304
|
-
|
|
305
|
-
|
|
314
|
+
resetWarning: "If ChatGPT performs the reset, one reset is consumed immediately and cannot be restored.",
|
|
315
|
+
resetEarlyWarning: "No model quota is exhausted. ChatGPT may reset it, or report that nothing needs resetting without consuming a reset.",
|
|
316
|
+
resetAcknowledge: "I understand this may consume one reset now",
|
|
317
|
+
resetCreditExpires: "Expires {value}",
|
|
318
|
+
resetCreditExpiryUnknown: "Expiration time not provided",
|
|
319
|
+
resetCreditExpiryLoading: "Reading expiration…",
|
|
320
|
+
resetCreditExpiryFailed: "Could not read expiration",
|
|
306
321
|
resetWait: "Wait {count} more seconds",
|
|
307
322
|
resetFinal: "Consume one reset",
|
|
308
323
|
resetUsing: "Resetting…",
|
|
@@ -315,8 +330,7 @@ window.__ModuleLoader__.load({
|
|
|
315
330
|
resetExpired: "This confirmation expired. Start again.",
|
|
316
331
|
resetInProgress: "A quota reset is already in progress.",
|
|
317
332
|
resetTooEarly: "Wait for the cooldown before confirming.",
|
|
318
|
-
|
|
319
|
-
resetNotExhausted: "The current Codex model quota is not exhausted.",
|
|
333
|
+
resetAcknowledgeRequired: "Confirm that you understand this may consume a reset.",
|
|
320
334
|
resetAccountChanged: "The signed-in account changed. Start again.",
|
|
321
335
|
creditsNote: "Shows only extra Credits, spending caps, or quota resets returned for this account or workspace; these are separate items.",
|
|
322
336
|
creditsUsed: "{used} / {limit} credits used",
|
|
@@ -344,6 +358,7 @@ window.__ModuleLoader__.load({
|
|
|
344
358
|
modelFailed: "Could not load models: {value}",
|
|
345
359
|
groupFailed: "{name}: {value}",
|
|
346
360
|
imageGenerate: "Generate image",
|
|
361
|
+
imageBeta: "Beta",
|
|
347
362
|
imageGenerating: "Generating…",
|
|
348
363
|
imageGenerated: "Generated",
|
|
349
364
|
imageFailed: "Generation failed",
|
|
@@ -353,7 +368,13 @@ window.__ModuleLoader__.load({
|
|
|
353
368
|
imageLoading: "Loading image…",
|
|
354
369
|
imageLoadFailed: "Image failed to load. Click to retry",
|
|
355
370
|
imagePreview: "Image preview",
|
|
356
|
-
imageClosePreview: "Close preview"
|
|
371
|
+
imageClosePreview: "Close preview",
|
|
372
|
+
imageDownload: "Download original",
|
|
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."
|
|
357
378
|
};
|
|
358
379
|
const STYLE = `
|
|
359
380
|
.codexSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
|
|
@@ -372,7 +393,7 @@ window.__ModuleLoader__.load({
|
|
|
372
393
|
.codexSubscriptionFlow p{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCode{width:max-content;max-width:100%;font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.08em;overflow-wrap:anywhere}
|
|
373
394
|
.codexSubscriptionError{font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}.codexSubscriptionInput{width:100%;box-sizing:border-box}
|
|
374
395
|
.codexSubscriptionRecover{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionRecover .codexSubscriptionError{flex:1}.codexSubscriptionRecover button{flex:0 0 auto}
|
|
375
|
-
.codexSubscriptionDiagnostics pre{max-height:240px;margin:0;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-secondary)}
|
|
396
|
+
.codexSubscriptionDiagnostics pre{max-height:240px;margin:0;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-secondary)}.codexSubscriptionLink{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;text-decoration:none;white-space:nowrap}.codexSubscriptionLink:hover{background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionLink:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}
|
|
376
397
|
.codexSubscriptionSectionTitle{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px}.codexSubscriptionFreshness{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
|
|
377
398
|
.codexSubscriptionRefresh{flex:0 0 auto;min-width:72px;width:max-content;white-space:nowrap!important;word-break:keep-all!important;overflow-wrap:normal!important;writing-mode:horizontal-tb!important}.codexSubscriptionRefresh *{white-space:nowrap!important;word-break:keep-all!important;writing-mode:horizontal-tb!important}
|
|
378
399
|
.codexSubscriptionEmpty{padding:18px;border:1px dashed var(--dsw-alias-border-l3);border-radius:10px;text-align:center;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
|
|
@@ -382,13 +403,14 @@ window.__ModuleLoader__.load({
|
|
|
382
403
|
.codexSubscriptionLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionLimitMeta{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
|
|
383
404
|
.codexSubscriptionCreditSection{display:flex;flex-direction:column;gap:7px}.codexSubscriptionCreditNote{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionCreditRows{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}.codexSubscriptionCreditBalance,.codexSubscriptionSpendLimit{min-width:0;border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-module-platform)}
|
|
384
405
|
.codexSubscriptionCreditBalance{display:flex;flex-direction:column;gap:6px}.codexSubscriptionCreditBalance span,.codexSubscriptionCreditLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCreditBalance strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
|
|
385
|
-
.codexSubscriptionResetSummary{display:flex;align-items:center;justify-content:space-between;gap:10px}.codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}.codexSubscriptionResetBalance .codexSubscriptionActions{justify-content:flex-start}.codexSubscriptionResetFlow{display:flex;flex-direction:column;gap:
|
|
406
|
+
.codexSubscriptionResetSummary{display:flex;align-items:center;justify-content:space-between;gap:10px}.codexSubscriptionResetMeta{display:flex;min-width:0;flex-direction:column;gap:1px}.codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}.codexSubscriptionResetBalance .codexSubscriptionActions{justify-content:flex-start}.codexSubscriptionResetFlow{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px}.codexSubscriptionResetFlow h4{margin:0;font-size:13px;line-height:20px;font-weight:500}.codexSubscriptionResetWarning{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionResetExpiry{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionResetCheck{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);cursor:pointer}.codexSubscriptionResetCheck input{margin:3px 0 0;accent-color:var(--dsw-alias-label-primary)}.codexSubscriptionResetFinal{border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.codexSubscriptionResetResult{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
|
|
386
407
|
.codexSubscriptionSpendLimit{display:flex;flex-direction:column;gap:8px}.codexSubscriptionSpendTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}.codexSubscriptionSpendTop strong{font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}.codexSubscriptionSpendLimit progress{width:100%;height:6px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-brand-primary,#3964fe);-webkit-appearance:none;appearance:none}.codexSubscriptionSpendLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionSpendLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionSpendLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
|
|
387
408
|
.codexComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;padding:0;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}.codexComposerQuotaBar{display:block;width:40px;height:4px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-label-secondary);-webkit-appearance:none;appearance:none}.codexComposerQuotaBar::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexComposerQuotaBar::-webkit-progress-value{background:var(--dsw-alias-label-secondary);border-radius:999px}.codexComposerQuotaBar::-moz-progress-bar{background:var(--dsw-alias-label-secondary);border-radius:999px}
|
|
388
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)}
|
|
389
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}
|
|
390
411
|
.codexModelSelectMenu{overflow:visible}
|
|
391
|
-
.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}.
|
|
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}
|
|
392
414
|
@container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
|
|
393
415
|
@container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
|
|
394
416
|
@media(max-width:640px){.codexSubscriptionCard{padding:14px}}
|
|
@@ -413,12 +435,20 @@ window.__ModuleLoader__.load({
|
|
|
413
435
|
const date = new Date(value);
|
|
414
436
|
return Number.isFinite(date.getTime()) ? date : void 0;
|
|
415
437
|
};
|
|
438
|
+
const imageDownloadName = (attachment) => {
|
|
439
|
+
const fallback = "codex-generated-image.png";
|
|
440
|
+
if (typeof attachment?.name !== "string") return fallback;
|
|
441
|
+
const cleaned = attachment.name.replace(/[<>:"/\\|?*\u0000-\u001f]/gu, "-").replace(/[. ]+$/u, "").trim();
|
|
442
|
+
if (cleaned === "") return fallback;
|
|
443
|
+
return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
|
|
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`;
|
|
416
446
|
function CodexGeneratedImage({ attachment, loadImage, t }) {
|
|
417
447
|
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
418
448
|
const [error, setError] = (0, react.useState)(false);
|
|
419
449
|
const [open, setOpen] = (0, react.useState)(false);
|
|
420
450
|
const [src, setSrc] = (0, react.useState)();
|
|
421
|
-
const
|
|
451
|
+
const [zoom, setZoom] = (0, react.useState)(1);
|
|
422
452
|
const triggerRef = (0, react.useRef)(null);
|
|
423
453
|
(0, react.useEffect)(() => {
|
|
424
454
|
let live = true;
|
|
@@ -437,25 +467,13 @@ window.__ModuleLoader__.load({
|
|
|
437
467
|
loadImage,
|
|
438
468
|
attempt
|
|
439
469
|
]);
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
event.preventDefault();
|
|
445
|
-
setOpen(false);
|
|
446
|
-
} else if (event.key === "Tab") {
|
|
447
|
-
event.preventDefault();
|
|
448
|
-
closeRef.current?.focus();
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
452
|
-
closeRef.current?.focus();
|
|
453
|
-
return () => {
|
|
454
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
455
|
-
triggerRef.current?.focus();
|
|
456
|
-
};
|
|
457
|
-
}, [open]);
|
|
470
|
+
const close = () => {
|
|
471
|
+
setOpen(false);
|
|
472
|
+
window.requestAnimationFrame(() => triggerRef.current?.focus());
|
|
473
|
+
};
|
|
458
474
|
const label = attachment.name ?? t("imageLabel");
|
|
475
|
+
const downloadName = imageDownloadName(attachment);
|
|
476
|
+
const imageMeta = `${attachment.width} × ${attachment.height} · ${imageByteSize(attachment.bytes)}`;
|
|
459
477
|
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
460
478
|
type: "button",
|
|
461
479
|
className: "codexGeneratedImageRetry",
|
|
@@ -469,30 +487,84 @@ window.__ModuleLoader__.load({
|
|
|
469
487
|
title: t("imageOpen"),
|
|
470
488
|
"aria-label": fill(t("imageOpenNamed"), { value: label }),
|
|
471
489
|
onClick: () => {
|
|
472
|
-
if (src !== void 0)
|
|
490
|
+
if (src !== void 0) {
|
|
491
|
+
setZoom(1);
|
|
492
|
+
setOpen(true);
|
|
493
|
+
}
|
|
473
494
|
},
|
|
474
495
|
children: src === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageLoading") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
475
496
|
src,
|
|
476
497
|
alt: label
|
|
477
498
|
})
|
|
478
|
-
}),
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
+
})
|
|
496
568
|
})] });
|
|
497
569
|
}
|
|
498
570
|
function CodexImageToolRow({ block, loadImage, t }) {
|
|
@@ -517,6 +589,10 @@ window.__ModuleLoader__.load({
|
|
|
517
589
|
className: "codexImageToolTitle",
|
|
518
590
|
children: t("imageGenerate")
|
|
519
591
|
}),
|
|
592
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
593
|
+
className: "codexImageBeta",
|
|
594
|
+
children: t("imageBeta")
|
|
595
|
+
}),
|
|
520
596
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
521
597
|
className: "codexImageToolState",
|
|
522
598
|
children: status
|
|
@@ -1311,18 +1387,28 @@ window.__ModuleLoader__.load({
|
|
|
1311
1387
|
})]
|
|
1312
1388
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1313
1389
|
className: "codexSubscriptionActions",
|
|
1314
|
-
children: [
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1390
|
+
children: [
|
|
1391
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1392
|
+
type: "button",
|
|
1393
|
+
variant: "outline",
|
|
1394
|
+
disabled: busy,
|
|
1395
|
+
onClick: load,
|
|
1396
|
+
children: t("diagnosticsLoad")
|
|
1397
|
+
}),
|
|
1398
|
+
report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1399
|
+
type: "button",
|
|
1400
|
+
variant: "outline",
|
|
1401
|
+
onClick: copy,
|
|
1402
|
+
children: copied ? t("diagnosticsCopied") : t("diagnosticsCopy")
|
|
1403
|
+
}),
|
|
1404
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1405
|
+
className: "codexSubscriptionLink",
|
|
1406
|
+
href: SUPPORT_ISSUE_URL,
|
|
1407
|
+
target: "_blank",
|
|
1408
|
+
rel: "noreferrer",
|
|
1409
|
+
children: t("feedbackOpen")
|
|
1410
|
+
})
|
|
1411
|
+
]
|
|
1326
1412
|
})]
|
|
1327
1413
|
}),
|
|
1328
1414
|
report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: JSON.stringify(report, null, 2) }),
|
|
@@ -1349,14 +1435,49 @@ window.__ModuleLoader__.load({
|
|
|
1349
1435
|
children: fill(t("resets"), { value })
|
|
1350
1436
|
});
|
|
1351
1437
|
}
|
|
1352
|
-
function
|
|
1438
|
+
function ResetCreditExpiry({ expiresAt, t }) {
|
|
1439
|
+
const date = validDate(expiresAt);
|
|
1440
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1441
|
+
className: "codexSubscriptionResetExpiry",
|
|
1442
|
+
children: date === void 0 ? t("resetCreditExpiryUnknown") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
1443
|
+
dateTime: date.toISOString(),
|
|
1444
|
+
title: date.toLocaleString(),
|
|
1445
|
+
children: fill(t("resetCreditExpires"), { value: date.toLocaleString() })
|
|
1446
|
+
})
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
function ResetCreditControl({ rpc, t, count, nextExpiresAt, hasExhaustedQuota, onConsumed }) {
|
|
1353
1450
|
const [challenge, setChallenge] = (0, react.useState)();
|
|
1354
1451
|
const [resetBusy, setResetBusy] = (0, react.useState)(false);
|
|
1355
1452
|
const [resetAcknowledged, setResetAcknowledged] = (0, react.useState)(false);
|
|
1356
|
-
const [resetPhrase, setResetPhrase] = (0, react.useState)("");
|
|
1357
1453
|
const [resetCountdown, setResetCountdown] = (0, react.useState)(0);
|
|
1358
1454
|
const [resetError, setResetError] = (0, react.useState)();
|
|
1359
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
|
+
]);
|
|
1360
1481
|
(0, react.useEffect)(() => {
|
|
1361
1482
|
if (challenge === void 0) {
|
|
1362
1483
|
setResetCountdown(0);
|
|
@@ -1368,24 +1489,22 @@ window.__ModuleLoader__.load({
|
|
|
1368
1489
|
return () => window.clearInterval(timer);
|
|
1369
1490
|
}, [challenge]);
|
|
1370
1491
|
const prepareReset = () => {
|
|
1371
|
-
if (resetBusy
|
|
1492
|
+
if (resetBusy) return;
|
|
1372
1493
|
setResetBusy(true);
|
|
1373
1494
|
setResetError(void 0);
|
|
1374
1495
|
setResetResult(void 0);
|
|
1375
1496
|
rpc.call(CHANNEL, "reset-credit/prepare", {}).then(unwrap).then((next) => {
|
|
1376
1497
|
setChallenge(next);
|
|
1377
1498
|
setResetAcknowledged(false);
|
|
1378
|
-
setResetPhrase("");
|
|
1379
1499
|
}).catch((error) => setResetError(resetCreditErrorText(error, t))).finally(() => setResetBusy(false));
|
|
1380
1500
|
};
|
|
1381
1501
|
const cancelReset = () => {
|
|
1382
1502
|
if (resetBusy) return;
|
|
1383
1503
|
setChallenge(void 0);
|
|
1384
1504
|
setResetAcknowledged(false);
|
|
1385
|
-
setResetPhrase("");
|
|
1386
1505
|
setResetError(void 0);
|
|
1387
1506
|
};
|
|
1388
|
-
const resetReady = challenge !== void 0 && resetAcknowledged &&
|
|
1507
|
+
const resetReady = challenge !== void 0 && resetAcknowledged && resetCountdown === 0;
|
|
1389
1508
|
const consumeReset = () => {
|
|
1390
1509
|
if (resetBusy) return;
|
|
1391
1510
|
if (!resetReady) return;
|
|
@@ -1394,11 +1513,10 @@ window.__ModuleLoader__.load({
|
|
|
1394
1513
|
setResetResult(void 0);
|
|
1395
1514
|
rpc.call(CHANNEL, "reset-credit/consume", {
|
|
1396
1515
|
challengeId: challenge.challengeId,
|
|
1397
|
-
|
|
1516
|
+
acknowledged: resetAcknowledged
|
|
1398
1517
|
}).then(unwrap).then((result) => {
|
|
1399
1518
|
setChallenge(void 0);
|
|
1400
1519
|
setResetAcknowledged(false);
|
|
1401
|
-
setResetPhrase("");
|
|
1402
1520
|
const message = result.code === "reset" ? t("resetSuccess") : result.code === "nothing_to_reset" ? t("resetNothing") : result.code === "no_credit" ? t("resetNoCredit") : t("resetAlready");
|
|
1403
1521
|
setResetResult(message);
|
|
1404
1522
|
onConsumed();
|
|
@@ -1409,14 +1527,26 @@ window.__ModuleLoader__.load({
|
|
|
1409
1527
|
children: [
|
|
1410
1528
|
challenge === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1411
1529
|
className: "codexSubscriptionResetSummary",
|
|
1412
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1530
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1531
|
+
className: "codexSubscriptionResetMeta",
|
|
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,
|
|
1541
|
+
t
|
|
1542
|
+
})]
|
|
1543
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1413
1544
|
className: "codexSubscriptionActions",
|
|
1414
1545
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1415
1546
|
type: "button",
|
|
1416
1547
|
variant: "outline",
|
|
1417
|
-
disabled:
|
|
1548
|
+
disabled: resetBusy,
|
|
1418
1549
|
"aria-busy": resetBusy,
|
|
1419
|
-
title: !exhausted ? t("resetUnavailable") : void 0,
|
|
1420
1550
|
onClick: prepareReset,
|
|
1421
1551
|
children: resetBusy ? t("resetPreparing") : t("resetUse")
|
|
1422
1552
|
})
|
|
@@ -1434,9 +1564,13 @@ window.__ModuleLoader__.load({
|
|
|
1434
1564
|
className: "codexSubscriptionResetWarning",
|
|
1435
1565
|
children: challenge.description
|
|
1436
1566
|
}) : null,
|
|
1567
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditExpiry, {
|
|
1568
|
+
expiresAt: challenge.creditExpiresAt,
|
|
1569
|
+
t
|
|
1570
|
+
}),
|
|
1437
1571
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1438
1572
|
className: "codexSubscriptionResetWarning",
|
|
1439
|
-
children: t("resetWarning")
|
|
1573
|
+
children: t(hasExhaustedQuota ? "resetWarning" : "resetEarlyWarning")
|
|
1440
1574
|
}),
|
|
1441
1575
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1442
1576
|
className: "codexSubscriptionResetCheck",
|
|
@@ -1447,20 +1581,6 @@ window.__ModuleLoader__.load({
|
|
|
1447
1581
|
onChange: (event) => setResetAcknowledged(event.target.checked)
|
|
1448
1582
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetAcknowledge") })]
|
|
1449
1583
|
}),
|
|
1450
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1451
|
-
className: "codexSubscriptionPreferenceCopy",
|
|
1452
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1453
|
-
className: "codexSubscriptionCreditLabel",
|
|
1454
|
-
children: fill(t("resetTypeHint"), { phrase: challenge.confirmPhrase })
|
|
1455
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1456
|
-
className: "codexSubscriptionInput codexSubscriptionResetPhrase",
|
|
1457
|
-
value: resetPhrase,
|
|
1458
|
-
disabled: resetBusy,
|
|
1459
|
-
autoComplete: "off",
|
|
1460
|
-
spellCheck: false,
|
|
1461
|
-
onChange: (event) => setResetPhrase(event.target.value)
|
|
1462
|
-
})]
|
|
1463
|
-
}),
|
|
1464
1584
|
resetCountdown > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1465
1585
|
className: "codexSubscriptionCreditNote",
|
|
1466
1586
|
role: "status",
|
|
@@ -1509,8 +1629,7 @@ window.__ModuleLoader__.load({
|
|
|
1509
1629
|
["This quota reset confirmation is no longer valid", "resetExpired"],
|
|
1510
1630
|
["This quota reset is already in progress", "resetInProgress"],
|
|
1511
1631
|
["Wait before confirming this quota reset", "resetTooEarly"],
|
|
1512
|
-
["
|
|
1513
|
-
["The current Codex quota is not exhausted", "resetNotExhausted"],
|
|
1632
|
+
["You must acknowledge that this may consume one quota reset", "resetAcknowledgeRequired"],
|
|
1514
1633
|
["The signed-in ChatGPT account changed", "resetAccountChanged"]
|
|
1515
1634
|
])).get(error instanceof Error ? error.message : "") ?? "resetFailed");
|
|
1516
1635
|
}
|
|
@@ -1648,7 +1767,8 @@ window.__ModuleLoader__.load({
|
|
|
1648
1767
|
rpc,
|
|
1649
1768
|
t,
|
|
1650
1769
|
count: visibleUsage.resetCredits.availableCount,
|
|
1651
|
-
|
|
1770
|
+
nextExpiresAt: visibleUsage.resetCredits.nextExpiresAt,
|
|
1771
|
+
hasExhaustedQuota: exhausted,
|
|
1652
1772
|
onConsumed: () => load(true)
|
|
1653
1773
|
})]
|
|
1654
1774
|
}) : null,
|
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:
|
|
482
|
+
serviceTier: FAST_SERVICE_TIER,
|
|
482
483
|
async onPayload(payload, requestModel) {
|
|
483
484
|
const fastPayload = {
|
|
484
485
|
...payload,
|
|
485
|
-
service_tier:
|
|
486
|
+
service_tier: FAST_SERVICE_TIER
|
|
486
487
|
};
|
|
487
488
|
return {
|
|
488
489
|
...await onPayload?.(fastPayload, requestModel) ?? fastPayload,
|
|
489
|
-
service_tier:
|
|
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.
|
|
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: "
|
|
765
|
-
parameters: {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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(
|
|
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);
|
|
@@ -948,7 +1005,18 @@ function spendControlReachedOf(value) {
|
|
|
948
1005
|
function resetCreditsOf(value) {
|
|
949
1006
|
if (value === void 0 || value === null) return void 0;
|
|
950
1007
|
if (!record$1(value) || !Number.isSafeInteger(value.available_count) || value.available_count < 0) throw new Error("Codex returned malformed reset credit details");
|
|
951
|
-
|
|
1008
|
+
if (value.credits !== void 0 && value.credits !== null && !Array.isArray(value.credits)) throw new Error("Codex returned malformed reset credit details");
|
|
1009
|
+
const expirations = (value.credits ?? []).filter((credit) => record$1(credit) && credit.status?.toLowerCase?.() === "available").map((credit) => {
|
|
1010
|
+
if (Number.isSafeInteger(credit.expires_at) && credit.expires_at > 0) return credit.expires_at * 1e3;
|
|
1011
|
+
if (typeof credit.expires_at === "string" && credit.expires_at.length <= 64) {
|
|
1012
|
+
const parsed = Date.parse(credit.expires_at);
|
|
1013
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
1014
|
+
}
|
|
1015
|
+
}).filter((expiration) => expiration !== void 0);
|
|
1016
|
+
return {
|
|
1017
|
+
availableCount: value.available_count,
|
|
1018
|
+
...expirations.length === 0 ? {} : { nextExpiresAt: Math.min(...expirations) }
|
|
1019
|
+
};
|
|
952
1020
|
}
|
|
953
1021
|
/** Reduce the provider payload to a browser-safe quota projection. */
|
|
954
1022
|
function parseCodexUsage(value) {
|
|
@@ -1054,7 +1122,6 @@ function createCodexUsageReader(options) {
|
|
|
1054
1122
|
//#region src/reset-credits.js
|
|
1055
1123
|
const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
1056
1124
|
const CODEX_RESET_CONSUME_URL = `${CODEX_RESET_CREDITS_URL}/consume`;
|
|
1057
|
-
const RESET_CONFIRM_PHRASE = "USE RESET";
|
|
1058
1125
|
const DEFAULT_CONFIRM_DELAY_MS = 5e3;
|
|
1059
1126
|
const DEFAULT_CHALLENGE_TTL_MS = 6e4;
|
|
1060
1127
|
const DEFAULT_TIMEOUT_MS = 15e3;
|
|
@@ -1076,15 +1143,22 @@ function credentialsOf(auth, credential) {
|
|
|
1076
1143
|
accountId
|
|
1077
1144
|
};
|
|
1078
1145
|
}
|
|
1146
|
+
function expirationOf(value) {
|
|
1147
|
+
if (value === void 0 || value === null) return void 0;
|
|
1148
|
+
if (Number.isSafeInteger(value) && value > 0) return value * 1e3;
|
|
1149
|
+
if (typeof value === "string" && value.length > 0 && value.length <= 64) {
|
|
1150
|
+
const parsed = Date.parse(value);
|
|
1151
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
1152
|
+
}
|
|
1153
|
+
throw new Error("ChatGPT returned malformed quota reset details");
|
|
1154
|
+
}
|
|
1079
1155
|
function parseDetails(value, now) {
|
|
1080
1156
|
if (!record(value) || !Number.isSafeInteger(value.available_count) || value.available_count < 0 || !Array.isArray(value.credits)) throw new Error("ChatGPT returned malformed quota reset details");
|
|
1081
1157
|
if (value.available_count === 0) throw new Error("No quota reset is available");
|
|
1082
1158
|
const available = value.credits.filter((credit) => record(credit) && typeof credit.id === "string" && credit.id.length > 0 && credit.id.length <= 256 && typeof credit.status === "string" && credit.status.toLowerCase() === "available").map((credit) => {
|
|
1083
|
-
const expiresAt = credit.expires_at === void 0 || credit.expires_at === null ? void 0 : credit.expires_at * 1e3;
|
|
1084
|
-
if (expiresAt !== void 0 && (!Number.isSafeInteger(credit.expires_at) || credit.expires_at <= 0)) throw new Error("ChatGPT returned malformed quota reset details");
|
|
1085
1159
|
return {
|
|
1086
1160
|
credit,
|
|
1087
|
-
expiresAt
|
|
1161
|
+
expiresAt: expirationOf(credit.expires_at)
|
|
1088
1162
|
};
|
|
1089
1163
|
}).filter(({ expiresAt }) => expiresAt === void 0 || expiresAt > now).sort((a, b) => (a.expiresAt ?? Number.MAX_SAFE_INTEGER) - (b.expiresAt ?? Number.MAX_SAFE_INTEGER));
|
|
1090
1164
|
if (available.length === 0) throw new Error("No usable quota reset is available");
|
|
@@ -1104,14 +1178,13 @@ function parseConsumeResult(value) {
|
|
|
1104
1178
|
"already_redeemed"
|
|
1105
1179
|
].includes(value.code)) throw new Error("ChatGPT returned an unreadable quota reset response");
|
|
1106
1180
|
const windowsReset = Array.isArray(value.windows_reset) ? value.windows_reset.filter((item) => typeof item === "string").slice(0, 16) : [];
|
|
1181
|
+
const windowsResetCount = Number.isSafeInteger(value.windows_reset) && value.windows_reset >= 0 && value.windows_reset <= 16 ? value.windows_reset : void 0;
|
|
1107
1182
|
return {
|
|
1108
1183
|
code: value.code,
|
|
1109
|
-
windowsReset
|
|
1184
|
+
windowsReset,
|
|
1185
|
+
...windowsResetCount === void 0 ? {} : { windowsResetCount }
|
|
1110
1186
|
};
|
|
1111
1187
|
}
|
|
1112
|
-
function quotaIsExhausted(usage) {
|
|
1113
|
-
return Array.isArray(usage?.rateLimits) && usage.rateLimits.some((limit) => limit?.id !== "code_review" && Array.isArray(limit?.windows) && limit.windows.some((window) => Number.isFinite(window?.usedPercent) && window.usedPercent >= 100));
|
|
1114
|
-
}
|
|
1115
1188
|
/**
|
|
1116
1189
|
* Host-only reset redemption. The browser receives an opaque, short-lived
|
|
1117
1190
|
* challenge; account ids, bearer tokens, credit ids, and idempotency keys stay
|
|
@@ -1129,29 +1202,42 @@ function createCodexResetCreditService(options) {
|
|
|
1129
1202
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1130
1203
|
const challenges = /* @__PURE__ */ new Map();
|
|
1131
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
|
+
};
|
|
1132
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
|
+
},
|
|
1133
1239
|
async prepare({ signal } = {}) {
|
|
1134
|
-
const {
|
|
1135
|
-
const response = await fetchReset(CODEX_RESET_CREDITS_URL, {
|
|
1136
|
-
method: "GET",
|
|
1137
|
-
redirect: "error",
|
|
1138
|
-
headers: {
|
|
1139
|
-
authorization: `Bearer ${access}`,
|
|
1140
|
-
"chatgpt-account-id": accountId,
|
|
1141
|
-
accept: "application/json",
|
|
1142
|
-
"cache-control": "no-store",
|
|
1143
|
-
"user-agent": USER_AGENT
|
|
1144
|
-
},
|
|
1145
|
-
signal: requestSignal(signal, timeoutMs)
|
|
1146
|
-
});
|
|
1147
|
-
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})`);
|
|
1148
|
-
let raw;
|
|
1149
|
-
try {
|
|
1150
|
-
raw = await response.json();
|
|
1151
|
-
} catch {
|
|
1152
|
-
throw new Error("ChatGPT returned unreadable quota reset details");
|
|
1153
|
-
}
|
|
1154
|
-
const details = parseDetails(raw, now());
|
|
1240
|
+
const { accountId, details } = await readDetails(signal);
|
|
1155
1241
|
const preparedAt = now();
|
|
1156
1242
|
const readyAt = preparedAt + confirmDelayMs;
|
|
1157
1243
|
const expiresAt = Math.min(preparedAt + challengeTtlMs, details.creditExpiresAt ?? Number.MAX_SAFE_INTEGER);
|
|
@@ -1168,14 +1254,14 @@ function createCodexResetCreditService(options) {
|
|
|
1168
1254
|
return {
|
|
1169
1255
|
challengeId,
|
|
1170
1256
|
availableCount: details.availableCount,
|
|
1171
|
-
confirmPhrase: RESET_CONFIRM_PHRASE,
|
|
1172
1257
|
readyAt,
|
|
1173
1258
|
expiresAt,
|
|
1259
|
+
...details.creditExpiresAt === void 0 ? {} : { creditExpiresAt: details.creditExpiresAt },
|
|
1174
1260
|
...details.title === void 0 ? {} : { title: details.title },
|
|
1175
1261
|
...details.description === void 0 ? {} : { description: details.description }
|
|
1176
1262
|
};
|
|
1177
1263
|
},
|
|
1178
|
-
async consume({ challengeId,
|
|
1264
|
+
async consume({ challengeId, acknowledged, signal } = {}) {
|
|
1179
1265
|
const challenge = typeof challengeId === "string" ? challenges.get(challengeId) : void 0;
|
|
1180
1266
|
if (challenge === void 0) throw new Error("This quota reset confirmation is no longer valid");
|
|
1181
1267
|
if (challenge.state === "pending") throw new Error("This quota reset is already in progress");
|
|
@@ -1184,13 +1270,9 @@ function createCodexResetCreditService(options) {
|
|
|
1184
1270
|
challenges.delete(challengeId);
|
|
1185
1271
|
throw new Error("This quota reset confirmation is no longer valid");
|
|
1186
1272
|
}
|
|
1187
|
-
if (
|
|
1273
|
+
if (acknowledged !== true) throw new Error("You must acknowledge that this may consume one quota reset");
|
|
1188
1274
|
challenge.state = "pending";
|
|
1189
1275
|
try {
|
|
1190
|
-
if (!quotaIsExhausted(await usageReader.read({
|
|
1191
|
-
force: true,
|
|
1192
|
-
signal
|
|
1193
|
-
}))) throw new Error("The current Codex quota is not exhausted");
|
|
1194
1276
|
const { access, accountId } = await resolveCredentials(signal);
|
|
1195
1277
|
if (accountId !== challenge.accountId) throw new Error("The signed-in ChatGPT account changed");
|
|
1196
1278
|
const response = await fetchReset(CODEX_RESET_CONSUME_URL, {
|
|
@@ -1317,13 +1399,13 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
1317
1399
|
const message = error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage";
|
|
1318
1400
|
return publicError("internal", message);
|
|
1319
1401
|
}
|
|
1320
|
-
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 {
|
|
1321
1403
|
signal.throwIfAborted();
|
|
1322
1404
|
return {
|
|
1323
1405
|
ok: true,
|
|
1324
|
-
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({
|
|
1325
1407
|
challengeId: payload?.challengeId,
|
|
1326
|
-
|
|
1408
|
+
acknowledged: payload?.acknowledged,
|
|
1327
1409
|
signal
|
|
1328
1410
|
})
|
|
1329
1411
|
};
|
|
@@ -1338,11 +1420,10 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
1338
1420
|
"This quota reset confirmation is no longer valid",
|
|
1339
1421
|
"This quota reset is already in progress",
|
|
1340
1422
|
"Wait before confirming this quota reset",
|
|
1341
|
-
"
|
|
1342
|
-
"The current Codex quota is not exhausted",
|
|
1423
|
+
"You must acknowledge that one quota reset will be consumed",
|
|
1343
1424
|
"The signed-in ChatGPT account changed"
|
|
1344
1425
|
]);
|
|
1345
|
-
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";
|
|
1346
1427
|
const message = error instanceof Error && known.has(error.message) ? error.message : fallback;
|
|
1347
1428
|
return publicError("internal", message);
|
|
1348
1429
|
}
|
|
@@ -1483,4 +1564,4 @@ function apply(ctx) {
|
|
|
1483
1564
|
ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "loopback" }), "codex-subscription: loopback account RPC");
|
|
1484
1565
|
}
|
|
1485
1566
|
//#endregion
|
|
1486
|
-
export { CODEX_IMAGE_GENERATION_URL, CODEX_IMAGE_TOOL_NAME, CODEX_RESET_CONSUME_URL, CODEX_RESET_CREDITS_URL, CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore,
|
|
1567
|
+
export { CODEX_IMAGE_GENERATION_URL, CODEX_IMAGE_TOOL_NAME, CODEX_RESET_CONSUME_URL, CODEX_RESET_CREDITS_URL, CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore, apply, assertCodexAuthUrl, commandForCodexAuthUrl, createCodexAuthService, createCodexImageTool, createCodexResetCreditService, createCodexRpcHandler, createCodexUsageReader, createSearchProviderSwitcher, createSubscriptionDiagnostics, createSubscriptionRpcHandler, decodeCodexPng, inject, name, openCodexAuthUrl, parseCodexUsage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-codex-subscription",
|
|
3
|
-
"version": "1.
|
|
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",
|