dsh-codex-subscription 1.7.3 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -6,7 +6,7 @@ Use this guide when a user asks an Agent to install, update, verify, or remove
6
6
  ## Safety
7
7
 
8
8
  - Confirm the target DSH installation and profile. Use `web` only when it is the user's target.
9
- - Use the exact `1.7.3` package below; do not install a moving branch.
9
+ - Use the exact `1.8.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.7.3
37
+ npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.8.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.7.3
43
+ dsh plugin --profile web add dsh-codex-subscription@1.8.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.7.3` appears exactly once.
61
+ 1. `dsh-codex-subscription` version `1.8.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
@@ -18,7 +18,7 @@ Use a ChatGPT subscription in DeepSeek Harness through independent OAuth sign-in
18
18
  - Codex CLI required: no
19
19
  - OpenAI API key required: no
20
20
  - Platforms: official DSH plus DSH-Portable product forms for Windows, macOS, and Linux
21
- - Main capabilities: Codex models, quota and expiring resets, web search, Beta image generation/editing with explicit references and native preview/download, optional composer quota, Fast mode, secret-free support diagnostics and direct feedback
21
+ - Main capabilities: Codex models, model-aware context windows, quota and expiring resets, web search, Beta image generation/editing with explicit references and native preview/download, optional composer quota, Fast mode, existing-proxy adaptation, secret-free actionable 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
@@ -51,6 +51,7 @@ DSH-Portable exposes the same standard plugin command, so the command above also
51
51
  | **Subscription search** | Explicitly choose DSH default search or Codex subscription search |
52
52
  | **Codex image generation and editing (Beta)** | Generate without references, or explicitly edit one selected image; preview, zoom, annotate regions, download the original, and continue in the same composer |
53
53
  | **Fast mode** | Switch between Standard and Fast directly in the composer |
54
+ | **Model-aware context** | Keep catalog defaults, use each model's supported extended window, or enter a full numeric token limit for each model |
54
55
 
55
56
  These capabilities reuse the same local ChatGPT sign-in. Subscription routing failures stay visible and never silently switch to another paid route.
56
57
 
@@ -96,12 +97,13 @@ Open PowerShell and paste this one line:
96
97
  irm 'https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/dsh-codex-setup.ps1' | iex
97
98
  ```
98
99
 
99
- The lightweight setup checks the current folder, the system command, common locations, and any running official DSH or
100
- [DSH-Portable](https://github.com/WSL043/DSH-Portable), then invokes the official `plugin add`
101
- operation once. It does not recursively scan disks, install pnpm, create a resident command, snapshot a
102
- profile, or download the plugin twice. It needs no administrator access and never restarts DSH. Only
103
- when no existing DSH is found does it use the official npm route pinned to `0.1.1-rc.2`; the setup warns
104
- that first-time dependency resolution can take a while.
100
+ The lightweight setup checks the current folder, the system command, common locations, and running official DSH or
101
+ [DSH-Portable](https://github.com/WSL043/DSH-Portable). It asks before choosing when more than one target is available.
102
+ Portable keeps using its public launcher. For official DSH, setup uses the selected profile home and a checksum-verified
103
+ pnpm helper pinned to the accepted DSH release; the helper is cached under the current Windows user when no working pnpm
104
+ command is available. It does not recursively scan disks, create a resident command, snapshot a profile, require
105
+ administrator access, or restart DSH. A recognized release-age rejection may retry the same pinned add once for that
106
+ command only.
105
107
 
106
108
  <details>
107
109
  <summary>Official npm route (Node.js installed)</summary>
@@ -146,9 +148,12 @@ Restart DSH manually after installation, then:
146
148
  - Visible quota-reset count and earliest expiry, with deliberate early redemption, layered confirmation, and no automatic retry;
147
149
  - Optional percentage or progress bar for the selected Codex model (off by default);
148
150
  - 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;
151
+ - Standard, Extended, and per-model Custom context windows; Custom accepts a full numeric token count, stays within each audited model capacity, and feeds DSH's native agent compaction policy;
152
+ - A copyable support report and direct feedback link in Settings; the report includes bounded request stages, HTTP/transport classes, elapsed ranges, and route source types while excluding OAuth credentials, account identifiers, proxy addresses, and authorization timestamps;
150
153
  - Visible errors when subscription routing is unavailable, with no silent paid fallback.
151
154
 
155
+ The plugin can follow an existing HTTPS proxy from the process environment or operating-system proxy settings for official OpenAI and ChatGPT requests. It does not provide a proxy, relay, node list, or system-proxy configuration.
156
+
152
157
  ### Composer quota
153
158
 
154
159
  <p align="center">
@@ -214,7 +219,7 @@ dsh plugin --profile web remove dsh-codex-subscription
214
219
  - **`dsh` is not recognized:** the official npm route does not create a global `dsh` command; use the complete `npx -y @deepseek-ai/dsh@0.1.1-rc.2 ...` command above;
215
220
  - **More than one DSH exists:** run the standard command from the intended DSH environment. For an Agent, specify the target or pass `-DshPath` explicitly;
216
221
  - **Setup still fails:** send the Agent guide above to an Agent. Do not delete the profile or change the system PATH to force an install.
217
- - **Need to report a problem:** generate a **Support diagnostics** report at the bottom of Settings, then open the [bug report form](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml). The report includes the OS/runtime and a bounded sign-in phase, but excludes credentials, account identifiers, raw errors, and full logs. Paste it into the required diagnostics field; never attach sign-in URLs, authorization codes, or browser callback addresses.
222
+ - **Need to report a problem:** generate a **Support diagnostics** report at the bottom of Settings, then open the [bug report form](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml). The report includes the OS/runtime, bounded sign-in phase, and safe request-failure categories, but excludes credentials, account identifiers, proxy addresses, raw responses, and full logs. Paste it into the required diagnostics field; never attach sign-in URLs, authorization codes, or browser callback addresses.
218
223
 
219
224
  The ChatGPT Codex backend and DSH can change independently. This community project is not affiliated with or endorsed by DeepSeek or OpenAI.
220
225
 
package/README.zh-CN.md CHANGED
@@ -52,6 +52,7 @@ DSH-Portable 也提供相同的标准插件命令,因此同样使用上面的
52
52
  | **订阅搜索** | 可在 DSH 默认搜索与 Codex 订阅搜索之间明确切换 |
53
53
  | **Codex 图片生成与编辑(Beta)** | 可无参考图全新生成,也可明确选择会话图片继续编辑;支持预览、缩放和下载原图 |
54
54
  | **高速模式** | 直接在输入框切换标准或高速,无需离开当前会话 |
55
+ | **模型感知上下文** | 可保留目录默认值、按模型启用扩展窗口,或为每个模型填写完整数字 Token 上限 |
55
56
 
56
57
  这些能力共用同一份本机 ChatGPT 登录。订阅路由失败时会明确报错,不会静默切换到其他付费路由。
57
58
 
@@ -98,10 +99,11 @@ irm 'https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/d
98
99
  ```
99
100
 
100
101
  这个轻量助手会检查当前目录、系统命令、常见位置以及正在运行的官方 DSH 或
101
- [DSH-Portable](https://github.com/WSL043/DSH-Portable),然后调用一次官方
102
- `plugin add`。它不会递归扫盘、安装 pnpm、创建常驻命令、保存 profile 快照或重复下载插件。
103
- 无需管理员权限,也不会擅自重启 DSH。找不到现有 DSH 时才会使用固定为 `0.1.1-rc.2`
104
- 的官方 npm 运行方式;首次解析依赖可能较慢,安装器会先明确提示。
102
+ [DSH-Portable](https://github.com/WSL043/DSH-Portable);存在多个目标时会先让用户选择。
103
+ Portable 继续使用它的公开启动器。安装到官方 DSH 时,助手会使用选定的配置目录和固定到已验收
104
+ DSH 版本的 pnpm 助手;系统没有可用 pnpm 时,会在当前 Windows 用户目录下缓存经过校验的副本。
105
+ 它不会递归扫盘、创建常驻命令、保存 profile 快照、要求管理员权限或擅自重启 DSH。只有识别到
106
+ 发布时间策略拦截时,才会仅对同一个固定安装命令重试一次。
105
107
 
106
108
  <details>
107
109
  <summary>官方 npm 方式(已安装 Node.js)</summary>
@@ -146,9 +148,12 @@ dsh --profile web --dump-config
146
148
  - 显示重置卡数量与最早到期时间,也允许在额度未完全用尽时主动尝试,并经过分层确认且不会自动重试;
147
149
  - 输入框可用百分比或进度条显示当前 Codex 模型的剩余额度(默认关闭);
148
150
  - 输入框可为支持的 Codex 模型切换标准或高速模式;
149
- - 设置页可生成并复制无敏感信息的支持诊断,并直接打开反馈入口;报告不包含 OAuth 凭据、账号标识或授权时间;
151
+ - 上下文窗口提供标准、扩展和逐模型自定义;自定义直接填写完整 Token 数值,并在已审核的模型容量内交给 DSH 原生 Agent 压缩策略处理;
152
+ - 设置页可生成并复制无敏感信息的支持诊断,并直接打开反馈入口;报告包含有限的请求阶段、HTTP/网络分类、耗时区间和路由来源类型,但不包含 OAuth 凭据、账号标识、代理地址或授权时间;
150
153
  - 订阅路由不可用时明确报错,不会静默切换到其他付费路由。
151
154
 
155
+ 对于 OpenAI 与 ChatGPT 官方请求,插件可以沿用进程环境或操作系统中已有的 HTTPS 代理。插件本身不提供代理、转发服务、节点列表,也不会修改系统代理设置。
156
+
152
157
  ### 输入框额度
153
158
 
154
159
  <p align="center">
@@ -210,7 +215,7 @@ dsh plugin --profile web remove dsh-codex-subscription
210
215
  - **`dsh` 无法识别**:官方 npm 方式本来就不会创建全局 `dsh` 命令,请使用上面的完整 `npx -y @deepseek-ai/dsh@0.1.1-rc.2 ...` 命令;
211
216
  - **电脑上有多个 DSH**:请在目标 DSH 环境中运行标准命令;交给 Agent 时明确目标,或传入 `-DshPath`;
212
217
  - **安装仍然失败**:把上面的 Agent 文档链接发给 Agent,不要删除 profile 或随意修改系统 PATH。
213
- - **需要提交问题**:在设置页底部生成“支持诊断”,然后打开[使用问题表单](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml)。报告包含系统/运行时和有限的登录阶段,但不含凭据、账号标识、原始错误或完整日志;请粘贴到必填诊断栏,且不要附上登录链接、授权码或浏览器回调地址。
218
+ - **需要提交问题**:在设置页底部生成“支持诊断”,然后打开[使用问题表单](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml)。报告包含系统/运行时、有限的登录阶段和安全的请求失败分类,但不含凭据、账号标识、代理地址、原始响应或完整日志;请粘贴到必填诊断栏,且不要附上登录链接、授权码或浏览器回调地址。
214
219
 
215
220
  ## 边界与支持
216
221
 
@@ -8,6 +8,7 @@ This project depends on software distributed under its own terms. The dependency
8
8
  | `@earendil-works/pi-ai` 0.82.1 | OpenAI Codex OAuth, model catalog, Responses transport, and WebSocket continuation | MIT | https://github.com/earendil-works/pi |
9
9
  | React | DSH settings component runtime | MIT | https://github.com/facebook/react |
10
10
  | Heroicons | Composer speed icon | MIT | https://github.com/tailwindlabs/heroicons |
11
+ | `https-proxy-agent` 7.0.6 | HTTPS proxy transport for Codex OAuth token requests | MIT | https://github.com/TooTallNate/proxy-agents |
11
12
  | tsdown | Development-time bundler | MIT | https://github.com/rolldown/tsdown |
12
13
 
13
14
  No third-party project endorses this community plugin. See each installed package for its complete license text and transitive dependency notices.
package/lib/client.js CHANGED
@@ -60,8 +60,50 @@ window.__ModuleLoader__.load({
60
60
  const SPEED_MODE_STANDARD = "standard";
61
61
  const SPEED_MODE_FAST = "fast";
62
62
  const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
63
+ const CONTEXT_MODE_FIELD = "contextMode";
64
+ const CONTEXT_MODE_STANDARD = "standard";
65
+ const CONTEXT_MODE_EXTENDED = "extended";
66
+ const CONTEXT_MODE_CUSTOM = "custom";
67
+ const DEFAULT_CONTEXT_MODE = CONTEXT_MODE_STANDARD;
68
+ const CUSTOM_CONTEXT_WINDOW_FIELD = "customContextWindow";
69
+ const DEFAULT_CUSTOM_CONTEXT_WINDOW = 272e3;
70
+ const MIN_CUSTOM_CONTEXT_WINDOW = 128e3;
71
+ const MAX_CUSTOM_CONTEXT_WINDOW = 1e6;
72
+ const CUSTOM_CONTEXT_MODEL_FIELDS = Object.freeze({
73
+ "gpt-5.4": "customContextGpt54",
74
+ "gpt-5.4-mini": "customContextGpt54Mini",
75
+ "gpt-5.5": "customContextGpt55",
76
+ "gpt-5.6": "customContextGpt56"
77
+ });
78
+ const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
79
+ "gpt-5.4": 1e6,
80
+ "gpt-5.4-mini": 4e5,
81
+ "gpt-5.5": 1e6,
82
+ "gpt-5.6": 1e6
83
+ });
84
+ const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
85
+ "gpt-5.4": 272e3,
86
+ "gpt-5.4-mini": 272e3,
87
+ "gpt-5.5": 272e3,
88
+ "gpt-5.6": 272e3
89
+ });
63
90
  const normalizeSearchProvider = (value) => ["dsh", "codex"].includes(value) ? value : "dsh";
64
91
  const normalizeSpeedMode = (value) => ["standard", "fast"].includes(value) ? value : DEFAULT_SPEED_MODE;
92
+ const normalizeContextMode = (value) => [
93
+ "standard",
94
+ "extended",
95
+ "custom"
96
+ ].includes(value) ? value : DEFAULT_CONTEXT_MODE;
97
+ const normalizeCustomContextWindow = (value, maximum = MAX_CUSTOM_CONTEXT_WINDOW) => {
98
+ if (!Number.isInteger(value)) return DEFAULT_CUSTOM_CONTEXT_WINDOW;
99
+ return Math.min(Math.max(value, MIN_CUSTOM_CONTEXT_WINDOW), maximum);
100
+ };
101
+ const formatContextWindow = (value) => value === 1e6 ? "1M" : `${Math.round(value / 1e3)}K`;
102
+ const parseContextWindow = (value) => {
103
+ const match = /^\s*(\d+)\s*$/u.exec(String(value));
104
+ if (match === null) return NaN;
105
+ return Number(match[1]);
106
+ };
65
107
  const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
66
108
  "off",
67
109
  "percent",
@@ -231,6 +273,16 @@ window.__ModuleLoader__.load({
231
273
  quickQuotaOff: "关闭",
232
274
  quickQuotaPercent: "百分比",
233
275
  quickQuotaBar: "进度条",
276
+ contextTitle: "上下文窗口",
277
+ contextStandard: "标准",
278
+ contextStandardHint: "使用模型目录默认值;官方 Agent 预设会自动管理上下文。",
279
+ contextExtended: "扩展",
280
+ contextExtendedHint: "按模型使用 400K 或 1M;超过 272K 后可能消耗更多额度。",
281
+ contextCustom: "自定义",
282
+ contextCustomHint: "输入完整 Token 数值;较低数值会让官方 Agent 预设更早压缩上下文。",
283
+ contextTokens: "Token 上限",
284
+ contextFixed: "固定 {value}",
285
+ contextMaximum: "范围 128000–{value}",
234
286
  quickQuotaStatus: "Codex 剩余额度 {value}%",
235
287
  speedTitle: "速度",
236
288
  speedStandard: "标准",
@@ -363,6 +415,16 @@ window.__ModuleLoader__.load({
363
415
  quickQuotaOff: "Off",
364
416
  quickQuotaPercent: "Percent",
365
417
  quickQuotaBar: "Progress bar",
418
+ contextTitle: "Context window",
419
+ contextStandard: "Standard",
420
+ contextStandardHint: "Use the model catalog default; official agent presets manage context automatically.",
421
+ contextExtended: "Extended",
422
+ contextExtendedHint: "Uses 400K or 1M by model; usage above 272K may consume more quota.",
423
+ contextCustom: "Custom",
424
+ contextCustomHint: "Enter the full token count; lower values make official agent presets compact sooner.",
425
+ contextTokens: "Token limit",
426
+ contextFixed: "Fixed {value}",
427
+ contextMaximum: "128000–{value}",
366
428
  quickQuotaStatus: "Codex quota: {value}% remaining",
367
429
  speedTitle: "Speed",
368
430
  speedStandard: "Standard",
@@ -417,6 +479,7 @@ window.__ModuleLoader__.load({
417
479
  .codexSubscriptionCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px 16px;display:flex;flex-direction:column;gap:12px}
418
480
  .codexSubscriptionUsageCard{padding:12px 14px;gap:9px}.codexSubscriptionPreferencesCard{padding:12px 14px;gap:10px}.codexSubscriptionPreference{min-height:32px;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:12px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.codexSubscriptionPreferenceCopy{display:flex;min-width:0;flex-direction:column;gap:2px}.codexSubscriptionPreferenceLabel{display:flex;align-items:center;gap:6px}
419
481
  .codexSubscriptionQuotaModes{display:flex;align-items:center;gap:3px;padding:2px;border-radius:9px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionQuotaMode{position:relative;display:flex;align-items:center;justify-content:center;min-height:26px;padding:0 9px;border-radius:7px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;cursor:pointer}.codexSubscriptionQuotaMode:has(input:checked){background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 0 0 1px var(--dsw-alias-border-l3)}.codexSubscriptionQuotaMode:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexSubscriptionQuotaMode:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionQuotaMode input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
482
+ .codexSubscriptionContext{display:flex;flex-direction:column;gap:8px}.codexSubscriptionContextHead{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.codexSubscriptionContextCopy{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.codexSubscriptionContextHint{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionContextTrigger{height:32px;min-width:108px;display:inline-flex;align-items:center;justify-content:space-between;gap:10px;padding:0 10px 0 12px;border:0;border-radius:999px;outline:0;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.codexSubscriptionContextTrigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.codexSubscriptionContextTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.codexSubscriptionContextTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:not-allowed}.codexSubscriptionContextTrigger svg{color:var(--dsw-alias-label-tertiary);transition:transform 120ms var(--ds-ease-in-out)}.codexSubscriptionContextTrigger[aria-expanded=true] svg{transform:rotate(180deg)}.codexSubscriptionContextModels{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}.codexSubscriptionContextModel{min-height:42px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.codexSubscriptionContextModel:last-child{border-bottom:0}.codexSubscriptionContextModelCopy{display:flex;min-width:0;flex-direction:column}.codexSubscriptionContextModelCopy strong{font-size:12px;line-height:18px;font-weight:500}.codexSubscriptionContextModelCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionContextInput{width:116px}
420
483
  .codexSubscriptionSwitch{position:relative;flex:0 0 auto;width:32px;height:18px;padding:0;border:1px solid var(--dsw-alias-border-l3);border-radius:999px;background:var(--dsw-alias-bg-module-platform);cursor:pointer}.codexSubscriptionSwitch:disabled{cursor:not-allowed;opacity:.5}.codexSubscriptionSwitch[aria-checked=true]{background:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-label-secondary)}.codexSubscriptionSwitchKnob{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:var(--dsw-alias-bg-layer-1);transition:transform 120ms var(--ds-ease-in-out)}.codexSubscriptionSwitch[aria-checked=true] .codexSubscriptionSwitchKnob{transform:translateX(14px)}
421
484
  .codexSubscriptionSearch{display:flex;flex-direction:column;gap:7px}.codexSubscriptionSearchChoices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.codexSubscriptionSearchChoice{display:grid;grid-template-columns:14px minmax(0,1fr);align-items:center;column-gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:9px 10px;text-align:left;cursor:pointer}.codexSubscriptionSearchChoice:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionSearchChoice:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.codexSubscriptionSearchChoice:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.codexSubscriptionSearchInput{width:14px;height:14px;margin:0;accent-color:var(--dsw-alias-label-primary);cursor:inherit}.codexSubscriptionSearchCopy{display:block;min-width:0;pointer-events:none}.codexSubscriptionSearchCopy strong,.codexSubscriptionSearchCopy span{display:block}.codexSubscriptionSearchCopy strong{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}.codexSubscriptionSearchChoice:has(input:checked) strong{color:var(--dsw-alias-label-primary)}.codexSubscriptionSearchCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionDivider{height:1px;background:var(--dsw-alias-border-l2)}
422
485
  .codexSubscriptionAccountRow,.codexSubscriptionSectionHead{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionStatus{display:flex;align-items:center;gap:8px;font-size:14px;line-height:22px;font-weight:500}
@@ -823,6 +886,7 @@ window.__ModuleLoader__.load({
823
886
  let fallback;
824
887
  let failedPatch;
825
888
  let generation = 0;
889
+ let contextModels = [];
826
890
  const nativeSnapshot = () => scope.getSnapshot();
827
891
  const read = () => {
828
892
  const native = nativeSnapshot();
@@ -832,6 +896,10 @@ window.__ModuleLoader__.load({
832
896
  quickQuotaMode: normalizeQuickQuotaMode(current.value?.[QUICK_QUOTA_MODE_FIELD], current.value?.[LEGACY_QUICK_QUOTA_FIELD]),
833
897
  searchProvider: normalizeSearchProvider(current.value?.[SEARCH_PROVIDER_FIELD]),
834
898
  speedMode: normalizeSpeedMode(current.value?.[SPEED_MODE_FIELD]),
899
+ contextMode: normalizeContextMode(current.value?.[CONTEXT_MODE_FIELD]),
900
+ customContextWindow: normalizeCustomContextWindow(current.value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
901
+ customContextWindows: Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [modelKey, normalizeCustomContextWindow(current.value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
902
+ contextModels,
835
903
  writable: !updating && current.status === "ready" && current.writable === true,
836
904
  error
837
905
  });
@@ -848,13 +916,17 @@ window.__ModuleLoader__.load({
848
916
  publish();
849
917
  });
850
918
  const acceptFallback = (value) => {
919
+ contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
851
920
  fallbackStatus = "ready";
852
921
  fallback = {
853
922
  status: "ready",
854
923
  value: {
855
924
  [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(value?.[QUICK_QUOTA_MODE_FIELD], value?.[LEGACY_QUICK_QUOTA_FIELD]),
856
925
  [SEARCH_PROVIDER_FIELD]: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD]),
857
- [SPEED_MODE_FIELD]: normalizeSpeedMode(value?.[SPEED_MODE_FIELD])
926
+ [SPEED_MODE_FIELD]: normalizeSpeedMode(value?.[SPEED_MODE_FIELD]),
927
+ [CONTEXT_MODE_FIELD]: normalizeContextMode(value?.[CONTEXT_MODE_FIELD]),
928
+ [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
929
+ ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])]))
858
930
  },
859
931
  writable: value?.writable === true
860
932
  };
@@ -866,11 +938,12 @@ window.__ModuleLoader__.load({
866
938
  fallback = void 0;
867
939
  error = false;
868
940
  publish();
869
- if (nativeSnapshot().status === "ready") return;
941
+ nativeSnapshot();
870
942
  try {
871
943
  const value = unwrap(await rpc.call(CHANNEL, "preferences/status", {}));
872
- if (current !== generation || nativeSnapshot().status === "ready") return;
873
- acceptFallback(value);
944
+ if (current !== generation) return;
945
+ if (nativeSnapshot().status === "ready") contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
946
+ else acceptFallback(value);
874
947
  publish();
875
948
  } catch {
876
949
  if (current !== generation || nativeSnapshot().status === "ready") return;
@@ -1035,6 +1108,125 @@ window.__ModuleLoader__.load({
1035
1108
  })]
1036
1109
  });
1037
1110
  }
1111
+ function ContextWindowPreference({ preference, t }) {
1112
+ const snapshot = usePreferenceSnapshot(preference);
1113
+ const writable = snapshot.status === "ready" && snapshot.writable === true;
1114
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
1115
+ const modelRows = snapshot.contextModels.filter((model) => model.fixed !== true);
1116
+ const fixedRows = snapshot.contextModels.filter((model) => model.fixed === true);
1117
+ const [drafts, setDrafts] = (0, react.useState)({});
1118
+ (0, react.useEffect)(() => setDrafts(Object.fromEntries(modelRows.map((model) => [model.key, String(snapshot.customContextWindows[model.key])]))), [snapshot.customContextWindows, snapshot.contextModels]);
1119
+ const hint = snapshot.contextMode === "extended" ? t("contextExtendedHint") : snapshot.contextMode === "custom" ? t("contextCustomHint") : t("contextStandardHint");
1120
+ const commit = (modelKey) => {
1121
+ const parsed = parseContextWindow(drafts[modelKey]);
1122
+ if (!Number.isInteger(parsed)) {
1123
+ setDrafts((current) => ({
1124
+ ...current,
1125
+ [modelKey]: String(snapshot.customContextWindows[modelKey])
1126
+ }));
1127
+ return;
1128
+ }
1129
+ const value = normalizeCustomContextWindow(parsed, CUSTOM_CONTEXT_MODEL_CAPS[modelKey]);
1130
+ setDrafts((current) => ({
1131
+ ...current,
1132
+ [modelKey]: String(value)
1133
+ }));
1134
+ if (value !== snapshot.customContextWindows[modelKey]) preference.set({ [CUSTOM_CONTEXT_MODEL_FIELDS[modelKey]]: value });
1135
+ };
1136
+ const contextModeItems = [
1137
+ {
1138
+ id: CONTEXT_MODE_STANDARD,
1139
+ label: t("contextStandard")
1140
+ },
1141
+ {
1142
+ id: CONTEXT_MODE_EXTENDED,
1143
+ label: t("contextExtended")
1144
+ },
1145
+ {
1146
+ id: CONTEXT_MODE_CUSTOM,
1147
+ label: t("contextCustom")
1148
+ }
1149
+ ];
1150
+ const selectedMode = contextModeItems.find((item) => item.id === snapshot.contextMode)?.label ?? t("contextStandard");
1151
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1152
+ className: "codexSubscriptionContext",
1153
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1154
+ className: "codexSubscriptionContextHead",
1155
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1156
+ className: "codexSubscriptionContextCopy",
1157
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1158
+ className: "codexSubscriptionPreferenceLabel",
1159
+ children: t("contextTitle")
1160
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1161
+ className: "codexSubscriptionContextHint",
1162
+ children: hint
1163
+ })]
1164
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1165
+ open: menuOpen,
1166
+ items: contextModeItems,
1167
+ selectedId: snapshot.contextMode,
1168
+ onSelect: (value) => {
1169
+ setMenuOpen(false);
1170
+ preference.set({ [CONTEXT_MODE_FIELD]: value });
1171
+ },
1172
+ onClose: () => setMenuOpen(false),
1173
+ align: "end",
1174
+ side: "bottom",
1175
+ portal: true,
1176
+ compact: true,
1177
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1178
+ className: "codexSubscriptionContextTrigger",
1179
+ type: "button",
1180
+ "aria-label": t("contextTitle"),
1181
+ "aria-haspopup": "menu",
1182
+ "aria-expanded": menuOpen,
1183
+ disabled: !writable,
1184
+ onClick: () => setMenuOpen((value) => !value),
1185
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedMode }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})]
1186
+ })
1187
+ })]
1188
+ }), snapshot.contextMode === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1189
+ className: "codexSubscriptionContextModels",
1190
+ children: [modelRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1191
+ className: "codexSubscriptionContextModel",
1192
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1193
+ className: "codexSubscriptionContextModelCopy",
1194
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: model.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fill(t("contextMaximum"), { value: String(model.maximum) }) })]
1195
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1196
+ "aria-label": `${model.label} ${t("contextTokens")}`,
1197
+ className: "codexSubscriptionContextInput",
1198
+ type: "number",
1199
+ inputMode: "numeric",
1200
+ min: MIN_CUSTOM_CONTEXT_WINDOW,
1201
+ max: model.maximum,
1202
+ step: 1,
1203
+ value: drafts[model.key] ?? "",
1204
+ disabled: !writable,
1205
+ onChange: (event) => {
1206
+ const nextValue = event.currentTarget.value;
1207
+ setDrafts((current) => ({
1208
+ ...current,
1209
+ [model.key]: nextValue
1210
+ }));
1211
+ },
1212
+ onBlur: () => commit(model.key),
1213
+ onKeyDown: (event) => {
1214
+ if (event.key === "Enter") event.currentTarget.blur();
1215
+ }
1216
+ })]
1217
+ }, model.key)), fixedRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1218
+ className: "codexSubscriptionContextModel",
1219
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1220
+ className: "codexSubscriptionContextModelCopy",
1221
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: model.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fill(t("contextFixed"), { value: formatContextWindow(model.maximum) }) })]
1222
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1223
+ className: "codexSubscriptionContextHint",
1224
+ children: formatContextWindow(model.maximum)
1225
+ })]
1226
+ }, model.key))]
1227
+ }) : null]
1228
+ });
1229
+ }
1038
1230
  function PreferencesCard({ preference, t }) {
1039
1231
  const snapshot = usePreferenceSnapshot(preference);
1040
1232
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1045,6 +1237,11 @@ window.__ModuleLoader__.load({
1045
1237
  t
1046
1238
  }),
1047
1239
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "codexSubscriptionDivider" }),
1240
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextWindowPreference, {
1241
+ preference,
1242
+ t
1243
+ }),
1244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "codexSubscriptionDivider" }),
1048
1245
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuickQuotaPreference, {
1049
1246
  preference,
1050
1247
  t
package/lib/index.js CHANGED
@@ -3,7 +3,12 @@ import { LlmError, createUserMessage } from "@deepseek-ai/dsh-llm";
3
3
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
4
4
  import { settingsNamespace } from "@deepseek-ai/dsh-settings";
5
5
  import z from "@deepseek-ai/schemastery";
6
- import { spawn } from "node:child_process";
6
+ import { execFile, spawn } from "node:child_process";
7
+ import { request } from "node:https";
8
+ import { AsyncLocalStorage } from "node:async_hooks";
9
+ import { Readable } from "node:stream";
10
+ import { promisify } from "node:util";
11
+ import { HttpsProxyAgent } from "https-proxy-agent";
7
12
  import { openaiCodexProvider as createOpenAICodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
8
13
  import { createModels } from "@earendil-works/pi-ai";
9
14
  import { randomUUID } from "node:crypto";
@@ -104,7 +109,8 @@ var DshOAuthCredentialStore = class {
104
109
  }
105
110
  };
106
111
  /** Return only account state that is safe to expose to the browser client. */
107
- function createCodexAuthService(models, store) {
112
+ function createCodexAuthService(models, store, options = {}) {
113
+ const runLogin = options.runLogin ?? ((run) => run());
108
114
  return Object.freeze({
109
115
  async status(options) {
110
116
  const current = await store.read(PROVIDER$1, options);
@@ -120,7 +126,7 @@ function createCodexAuthService(models, store) {
120
126
  };
121
127
  },
122
128
  login(interaction) {
123
- return models.login(PROVIDER$1, "oauth", interaction);
129
+ return runLogin(() => models.login(PROVIDER$1, "oauth", interaction));
124
130
  },
125
131
  logout(options) {
126
132
  return models.logout(PROVIDER$1, options);
@@ -221,6 +227,7 @@ const publicPrompt = (prompt) => ({
221
227
  function classifyLoginFailure(error) {
222
228
  const message = error instanceof Error ? error.message : "";
223
229
  if (/token exchange failed/iu.test(message)) return "token-exchange";
230
+ if (/fetch failed|\b(?:ECONN|ENOTFOUND|ETIMEDOUT|CERT_|socket|network)\b/iu.test(message)) return "network";
224
231
  if (/extract accountId|account[_ -]?id/iu.test(message)) return "account-claim";
225
232
  if (/credential|credentials-local|OAuth JSON/iu.test(message)) return "credential-store";
226
233
  if (/Missing authorization code|State mismatch|callback/iu.test(message)) return "callback";
@@ -469,6 +476,250 @@ function createCodexRpcHandler(coordinator, options = {}) {
469
476
  };
470
477
  }
471
478
  //#endregion
479
+ //#region src/oauth-network.js
480
+ const execFileAsync = promisify(execFile);
481
+ const CODEX_AUTH_HOST = "auth.openai.com";
482
+ const CODEX_HOSTS = /* @__PURE__ */ new Set([CODEX_AUTH_HOST, "chatgpt.com"]);
483
+ const networkScope = new AsyncLocalStorage();
484
+ let activeScopes = 0;
485
+ let baseFetch;
486
+ let scopedFetch;
487
+ function normalizeProxy(raw) {
488
+ if (typeof raw !== "string" || raw.trim() === "") return void 0;
489
+ const value = raw.trim().includes("://") ? raw.trim() : `http://${raw.trim()}`;
490
+ try {
491
+ const url = new URL(value);
492
+ if (!["http:", "https:"].includes(url.protocol) || url.hostname === "") return void 0;
493
+ return url.toString();
494
+ } catch {
495
+ return;
496
+ }
497
+ }
498
+ function bypassesProxy(hostname, port, rawNoProxy) {
499
+ if (typeof rawNoProxy !== "string" || rawNoProxy.trim() === "") return false;
500
+ return rawNoProxy.split(/[\s,]+/u).some((raw) => {
501
+ const entry = raw.trim().toLowerCase();
502
+ if (entry === "*") return true;
503
+ if (entry === "") return false;
504
+ const match = /^(.*?)(?::(\d+))?$/u.exec(entry);
505
+ const host = match?.[1]?.replace(/^\./u, "");
506
+ const entryPort = match?.[2];
507
+ if (!host || entryPort && entryPort !== port) return false;
508
+ return hostname === host || hostname.endsWith(`.${host}`);
509
+ });
510
+ }
511
+ function proxyFromEnvironment(env = process.env, target = new URL(`https://${CODEX_AUTH_HOST}/`)) {
512
+ if (bypassesProxy(target.hostname.toLowerCase(), target.port || "443", env.NO_PROXY ?? env.no_proxy)) return void 0;
513
+ return normalizeProxy(env.HTTPS_PROXY ?? env.https_proxy ?? env.ALL_PROXY ?? env.all_proxy);
514
+ }
515
+ function selectWindowsProxy(value) {
516
+ if (typeof value !== "string") return void 0;
517
+ const entries = value.split(";").map((item) => item.trim()).filter(Boolean);
518
+ const https = entries.find((item) => /^https=/iu.test(item));
519
+ const http = entries.find((item) => /^http=/iu.test(item));
520
+ const selected = (https ?? http ?? entries.find((item) => !item.includes("=")))?.replace(/^[^=]+=/u, "");
521
+ return normalizeProxy(selected);
522
+ }
523
+ async function windowsSystemProxy(options = {}) {
524
+ const run = options.execFile ?? execFileAsync;
525
+ const reg = `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\reg.exe`;
526
+ const key = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
527
+ try {
528
+ const enabled = await run(reg, [
529
+ "query",
530
+ key,
531
+ "/v",
532
+ "ProxyEnable"
533
+ ], {
534
+ windowsHide: true,
535
+ encoding: "utf8"
536
+ });
537
+ if (!/REG_DWORD\s+0x1\b/iu.test(enabled.stdout)) return void 0;
538
+ const configured = await run(reg, [
539
+ "query",
540
+ key,
541
+ "/v",
542
+ "ProxyServer"
543
+ ], {
544
+ windowsHide: true,
545
+ encoding: "utf8"
546
+ });
547
+ return selectWindowsProxy(/^\s*ProxyServer\s+REG_\w+\s+(.+)$/imu.exec(configured.stdout)?.[1]);
548
+ } catch {
549
+ return;
550
+ }
551
+ }
552
+ async function macSystemProxy(options = {}) {
553
+ const run = options.execFile ?? execFileAsync;
554
+ try {
555
+ const result = await run("/usr/sbin/scutil", ["--proxy"], { encoding: "utf8" });
556
+ if (!/^\s*HTTPSEnable\s*:\s*1\s*$/imu.test(result.stdout)) return void 0;
557
+ const host = /^\s*HTTPSProxy\s*:\s*(\S+)\s*$/imu.exec(result.stdout)?.[1];
558
+ const port = /^\s*HTTPSPort\s*:\s*(\d+)\s*$/imu.exec(result.stdout)?.[1];
559
+ return normalizeProxy(host && port ? `${host}:${port}` : void 0);
560
+ } catch {
561
+ return;
562
+ }
563
+ }
564
+ async function resolveCodexProxy(options = {}) {
565
+ const target = options.target ?? new URL(`https://${CODEX_AUTH_HOST}/`);
566
+ const env = options.env ?? process.env;
567
+ if (bypassesProxy(target.hostname.toLowerCase(), target.port || "443", env.NO_PROXY ?? env.no_proxy)) return {
568
+ url: void 0,
569
+ source: "bypass"
570
+ };
571
+ const envProxy = proxyFromEnvironment(env, target);
572
+ if (envProxy) return {
573
+ url: envProxy,
574
+ source: "environment"
575
+ };
576
+ const platform = options.platform ?? process.platform;
577
+ const system = platform === "win32" ? await windowsSystemProxy(options) : platform === "darwin" ? await macSystemProxy(options) : void 0;
578
+ return system ? {
579
+ url: system,
580
+ source: "system"
581
+ } : {
582
+ url: void 0,
583
+ source: "direct"
584
+ };
585
+ }
586
+ function bodyBytes(body) {
587
+ if (body === void 0 || body === null) return void 0;
588
+ if (typeof body === "string") return Buffer.from(body);
589
+ if (body instanceof URLSearchParams) return Buffer.from(body.toString());
590
+ if (body instanceof Uint8Array) return Buffer.from(body);
591
+ throw new TypeError("Unsupported Codex OAuth request body");
592
+ }
593
+ function fetchThroughProxy(input, init, proxyUrl) {
594
+ const target = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
595
+ const body = bodyBytes(init?.body);
596
+ const headers = new Headers(init?.headers);
597
+ if (body && !headers.has("content-length")) headers.set("content-length", String(body.byteLength));
598
+ return new Promise((resolve, reject) => {
599
+ const request$1 = request(target, {
600
+ method: init?.method ?? "GET",
601
+ headers: Object.fromEntries(headers.entries()),
602
+ agent: new HttpsProxyAgent(proxyUrl),
603
+ signal: init?.signal
604
+ }, (response) => {
605
+ const responseHeaders = new Headers();
606
+ for (const [name, value] of Object.entries(response.headers)) if (Array.isArray(value)) value.forEach((item) => responseHeaders.append(name, item));
607
+ else if (value !== void 0) responseHeaders.set(name, value);
608
+ const status = response.statusCode ?? 500;
609
+ const empty = init?.method === "HEAD" || [
610
+ 204,
611
+ 205,
612
+ 304
613
+ ].includes(status);
614
+ resolve(new Response(empty ? null : Readable.toWeb(response), {
615
+ status,
616
+ statusText: response.statusMessage,
617
+ headers: responseHeaders
618
+ }));
619
+ });
620
+ request$1.on("error", reject);
621
+ if (body) request$1.write(body);
622
+ request$1.end();
623
+ });
624
+ }
625
+ async function withCodexNetwork(run, options = {}) {
626
+ if (activeScopes === 0) {
627
+ baseFetch = globalThis.fetch;
628
+ scopedFetch = async (input, init) => {
629
+ const scope = networkScope.getStore();
630
+ if (scope === void 0) return baseFetch(input, init);
631
+ const { options: scopedOptions, allowedHosts, resolved } = scope;
632
+ const proxyFetch = scopedOptions.fetchThroughProxy ?? fetchThroughProxy;
633
+ const target = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
634
+ if (target.protocol !== "https:" || !allowedHosts.has(target.hostname)) return baseFetch(input, init);
635
+ let proxy = resolved.get(target.hostname);
636
+ if (proxy === void 0) {
637
+ proxy = resolveCodexProxy({
638
+ ...scopedOptions,
639
+ target
640
+ });
641
+ resolved.set(target.hostname, proxy);
642
+ }
643
+ const route = await proxy;
644
+ scopedOptions.onRoute?.(route.source);
645
+ return route.url === void 0 ? baseFetch(input, init) : proxyFetch(input, init, route.url);
646
+ };
647
+ globalThis.fetch = scopedFetch;
648
+ }
649
+ activeScopes += 1;
650
+ const scope = {
651
+ options,
652
+ allowedHosts: options.hosts ?? CODEX_HOSTS,
653
+ resolved: /* @__PURE__ */ new Map()
654
+ };
655
+ try {
656
+ return await networkScope.run(scope, run);
657
+ } finally {
658
+ activeScopes -= 1;
659
+ if (activeScopes === 0) {
660
+ if (globalThis.fetch === scopedFetch) globalThis.fetch = baseFetch;
661
+ baseFetch = void 0;
662
+ scopedFetch = void 0;
663
+ }
664
+ }
665
+ }
666
+ function classifyTransportError(error) {
667
+ const name = error?.name;
668
+ const code = String(error?.code ?? error?.cause?.code ?? "");
669
+ if (name === "AbortError" || name === "TimeoutError" || /ETIMEDOUT|UND_ERR_CONNECT_TIMEOUT/u.test(code)) return "timeout";
670
+ if (/ENOTFOUND|EAI_AGAIN/u.test(code)) return "dns";
671
+ if (/CERT_|TLS|SSL/u.test(code)) return "tls";
672
+ if (/ECONN|EPIPE|UND_ERR_SOCKET/u.test(code)) return "connection";
673
+ return "network";
674
+ }
675
+ const elapsedBucket = (elapsed) => elapsed < 1e3 ? "under-1s" : elapsed < 5e3 ? "1-5s" : elapsed < 15e3 ? "5-15s" : "over-15s";
676
+ function createCodexNetworkTransport(options = {}) {
677
+ const attempts = /* @__PURE__ */ new Map();
678
+ const now = options.now ?? Date.now;
679
+ const run = async (area, operation) => {
680
+ const startedAt = now();
681
+ let route = attempts.get(area)?.route ?? "direct";
682
+ let routed = false;
683
+ try {
684
+ const value = await withCodexNetwork(operation, {
685
+ ...options,
686
+ onRoute: (source) => {
687
+ route = source;
688
+ routed = true;
689
+ }
690
+ });
691
+ if (value instanceof Response && !value.ok) attempts.set(area, {
692
+ status: "failed",
693
+ stage: "http",
694
+ code: "http-error",
695
+ httpStatus: value.status,
696
+ route,
697
+ elapsed: elapsedBucket(now() - startedAt)
698
+ });
699
+ else if (routed || value instanceof Response) attempts.set(area, {
700
+ status: "ok",
701
+ route,
702
+ elapsed: elapsedBucket(now() - startedAt)
703
+ });
704
+ return value;
705
+ } catch (error) {
706
+ if (routed) attempts.set(area, {
707
+ status: "failed",
708
+ stage: "transport",
709
+ code: classifyTransportError(error),
710
+ route,
711
+ elapsed: elapsedBucket(now() - startedAt)
712
+ });
713
+ throw error;
714
+ }
715
+ };
716
+ return Object.freeze({
717
+ run,
718
+ fetch: (area, input, init) => run(area, () => globalThis.fetch(input, init)),
719
+ snapshot: () => Object.fromEntries([...attempts].map(([area, value]) => [area, { ...value }]))
720
+ });
721
+ }
722
+ //#endregion
472
723
  //#region src/settings-contract.js
473
724
  const SETTINGS_NAMESPACE = "codex-subscription";
474
725
  const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
@@ -480,6 +731,75 @@ const SPEED_MODE_FIELD = "speedMode";
480
731
  const SPEED_MODE_STANDARD = "standard";
481
732
  const SPEED_MODE_FAST = "fast";
482
733
  const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
734
+ const CONTEXT_MODE_FIELD = "contextMode";
735
+ const CONTEXT_MODE_STANDARD = "standard";
736
+ const CONTEXT_MODE_EXTENDED = "extended";
737
+ const CONTEXT_MODE_CUSTOM = "custom";
738
+ const DEFAULT_CONTEXT_MODE = CONTEXT_MODE_STANDARD;
739
+ const CUSTOM_CONTEXT_WINDOW_FIELD = "customContextWindow";
740
+ const DEFAULT_CUSTOM_CONTEXT_WINDOW = 272e3;
741
+ const MIN_CUSTOM_CONTEXT_WINDOW = 128e3;
742
+ const MAX_CUSTOM_CONTEXT_WINDOW = 1e6;
743
+ const CUSTOM_CONTEXT_MODEL_FIELDS = Object.freeze({
744
+ "gpt-5.4": "customContextGpt54",
745
+ "gpt-5.4-mini": "customContextGpt54Mini",
746
+ "gpt-5.5": "customContextGpt55",
747
+ "gpt-5.6": "customContextGpt56"
748
+ });
749
+ const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
750
+ "gpt-5.4": 1e6,
751
+ "gpt-5.4-mini": 4e5,
752
+ "gpt-5.5": 1e6,
753
+ "gpt-5.6": 1e6
754
+ });
755
+ const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
756
+ "gpt-5.4": 272e3,
757
+ "gpt-5.4-mini": 272e3,
758
+ "gpt-5.5": 272e3,
759
+ "gpt-5.6": 272e3
760
+ });
761
+ const normalizeContextMode = (value) => [
762
+ "standard",
763
+ "extended",
764
+ "custom"
765
+ ].includes(value) ? value : DEFAULT_CONTEXT_MODE;
766
+ const normalizeCustomContextWindow = (value, maximum = MAX_CUSTOM_CONTEXT_WINDOW) => {
767
+ if (!Number.isInteger(value)) return DEFAULT_CUSTOM_CONTEXT_WINDOW;
768
+ return Math.min(Math.max(value, MIN_CUSTOM_CONTEXT_WINDOW), maximum);
769
+ };
770
+ const customContextModelKey = (modelId) => modelId?.startsWith("gpt-5.6-") ? "gpt-5.6" : modelId;
771
+ function contextModelGroups(models) {
772
+ const groups = /* @__PURE__ */ new Map();
773
+ for (const model of models ?? []) {
774
+ if (model?.id === "gpt-5.3-codex-spark") {
775
+ groups.set(model.id, {
776
+ key: model.id,
777
+ label: model.name ?? model.id,
778
+ maximum: 128e3,
779
+ fixed: true
780
+ });
781
+ continue;
782
+ }
783
+ const key = customContextModelKey(model?.id);
784
+ if (!Object.hasOwn(CUSTOM_CONTEXT_MODEL_FIELDS, key)) continue;
785
+ if (key !== "gpt-5.6") {
786
+ groups.set(key, {
787
+ key,
788
+ label: model.name ?? model.id,
789
+ maximum: CUSTOM_CONTEXT_MODEL_CAPS[key]
790
+ });
791
+ continue;
792
+ }
793
+ const variant = String(model.name ?? model.id).replace(/^GPT-5\.6[ -]/iu, "");
794
+ const current = groups.get(key);
795
+ groups.set(key, {
796
+ key,
797
+ label: `GPT-5.6 ${current === void 0 ? variant : `${current.label.replace(/^GPT-5\.6 /u, "")} / ${variant}`}`,
798
+ maximum: CUSTOM_CONTEXT_MODEL_CAPS[key]
799
+ });
800
+ }
801
+ return [...groups.values()];
802
+ }
483
803
  const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
484
804
  "off",
485
805
  "percent",
@@ -501,7 +821,15 @@ const FAST_SERVICE_TIER = "priority";
501
821
  * persistence, headers, transport, and model behavior remain owned by the
502
822
  * original provider.
503
823
  */
504
- function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0 } = {}) {
824
+ const EXTENDED_CONTEXT_WINDOWS = Object.freeze({
825
+ "gpt-5.4": 1e6,
826
+ "gpt-5.4-mini": 4e5,
827
+ "gpt-5.5": 1e6,
828
+ "gpt-5.6-luna": 1e6,
829
+ "gpt-5.6-sol": 1e6,
830
+ "gpt-5.6-terra": 1e6
831
+ });
832
+ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, runNetwork = (_area, operation) => operation() } = {}) {
505
833
  const provider = createOpenAICodexProvider();
506
834
  const requestToken = Object.freeze({
507
835
  name: "DSH-managed Codex OAuth request token",
@@ -532,19 +860,49 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0 } = {
532
860
  }
533
861
  };
534
862
  };
863
+ const getModels = () => provider.getModels().map((model) => {
864
+ const maximum = EXTENDED_CONTEXT_WINDOWS[model.id];
865
+ const mode = resolveContextMode();
866
+ if (maximum === void 0 || !["extended", "custom"].includes(mode)) return model;
867
+ if (mode === "extended") return {
868
+ ...model,
869
+ contextWindow: Math.max(model.contextWindow, maximum)
870
+ };
871
+ const requested = normalizeCustomContextWindow(resolveCustomContextWindow(customContextModelKey(model.id)), maximum);
872
+ return {
873
+ ...model,
874
+ contextWindow: requested
875
+ };
876
+ });
877
+ const networkIterable = (factory) => {
878
+ let iterator;
879
+ const getIterator = () => iterator ??= factory()[Symbol.asyncIterator]();
880
+ return {
881
+ [Symbol.asyncIterator]() {
882
+ return this;
883
+ },
884
+ next: (value) => runNetwork("model", () => getIterator().next(value)),
885
+ return: (value) => runNetwork("model", () => getIterator().return?.(value) ?? Promise.resolve({
886
+ done: true,
887
+ value
888
+ })),
889
+ throw: (error) => runNetwork("model", () => getIterator().throw?.(error) ?? Promise.reject(error))
890
+ };
891
+ };
535
892
  return Object.freeze({
536
893
  ...provider,
537
894
  auth: Object.freeze({
538
895
  ...provider.auth,
539
896
  apiKey: requestToken
540
897
  }),
541
- stream: (model, context, options) => provider.stream(model, context, withSpeed(model, options)),
542
- streamSimple: (model, context, options) => provider.streamSimple(model, context, withSpeed(model, options))
898
+ getModels,
899
+ stream: (model, context, options) => networkIterable(() => provider.stream(model, context, withSpeed(model, options))),
900
+ streamSimple: (model, context, options) => networkIterable(() => provider.streamSimple(model, context, withSpeed(model, options)))
543
901
  });
544
902
  }
545
903
  //#endregion
546
904
  //#region src/version.js
547
- const PACKAGE_VERSION = "1.7.3";
905
+ const PACKAGE_VERSION = "1.8.0";
548
906
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
549
907
  //#endregion
550
908
  //#region src/codex-search.js
@@ -937,8 +1295,55 @@ function createCodexImageTool(options) {
937
1295
  }
938
1296
  //#endregion
939
1297
  //#region src/diagnostics.js
1298
+ const requestAreas = /* @__PURE__ */ new Set([
1299
+ "login",
1300
+ "model",
1301
+ "quota",
1302
+ "quota-reset",
1303
+ "search",
1304
+ "image"
1305
+ ]);
1306
+ const statuses = /* @__PURE__ */ new Set(["ok", "failed"]);
1307
+ const stages = /* @__PURE__ */ new Set(["transport", "http"]);
1308
+ const codes = /* @__PURE__ */ new Set([
1309
+ "timeout",
1310
+ "dns",
1311
+ "tls",
1312
+ "connection",
1313
+ "network",
1314
+ "http-error"
1315
+ ]);
1316
+ const routes = /* @__PURE__ */ new Set([
1317
+ "direct",
1318
+ "environment",
1319
+ "system",
1320
+ "bypass"
1321
+ ]);
1322
+ const elapsedBuckets = /* @__PURE__ */ new Set([
1323
+ "under-1s",
1324
+ "1-5s",
1325
+ "5-15s",
1326
+ "over-15s"
1327
+ ]);
1328
+ function safeRequests(network) {
1329
+ const raw = network?.snapshot?.() ?? {};
1330
+ const result = {};
1331
+ for (const [area, value] of Object.entries(raw)) {
1332
+ if (!requestAreas.has(area) || value === null || typeof value !== "object") continue;
1333
+ if (!statuses.has(value.status) || !routes.has(value.route) || !elapsedBuckets.has(value.elapsed)) continue;
1334
+ result[area] = {
1335
+ status: value.status,
1336
+ ...stages.has(value.stage) ? { stage: value.stage } : {},
1337
+ ...codes.has(value.code) ? { code: value.code } : {},
1338
+ ...Number.isInteger(value.httpStatus) && value.httpStatus >= 100 && value.httpStatus <= 599 ? { httpStatus: value.httpStatus } : {},
1339
+ route: value.route,
1340
+ elapsed: value.elapsed
1341
+ };
1342
+ }
1343
+ return result;
1344
+ }
940
1345
  /** Build a support report that deliberately excludes OAuth and account metadata. */
941
- async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" } }) {
1346
+ async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network }) {
942
1347
  let account = { status: "unknown" };
943
1348
  const issues = [];
944
1349
  try {
@@ -946,8 +1351,9 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
946
1351
  } catch {
947
1352
  issues.push({ code: "account-status-unavailable" });
948
1353
  }
1354
+ const preference = preferences.status();
949
1355
  return {
950
- schemaVersion: 2,
1356
+ schemaVersion: 3,
951
1357
  package: "dsh-codex-subscription",
952
1358
  version: PACKAGE_VERSION,
953
1359
  runtime: {
@@ -957,23 +1363,14 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
957
1363
  },
958
1364
  account,
959
1365
  login,
960
- provider: {
961
- id: "openai-codex",
962
- transport: "sse",
963
- cache: {
964
- owner: "dsh/pi-ai",
965
- retention: "short"
966
- }
967
- },
968
- capabilities: {
969
- models: true,
970
- quota: true,
971
- search: true,
972
- imageGeneration: true,
973
- composerQuota: true,
974
- speedMode: true
1366
+ requests: safeRequests(network),
1367
+ configuration: {
1368
+ contextMode: preference.contextMode,
1369
+ quickQuotaMode: preference.quickQuotaMode,
1370
+ searchProvider: preference.searchProvider,
1371
+ speedMode: preference.speedMode,
1372
+ writable: preference.writable === true
975
1373
  },
976
- configuration: preferences.status(),
977
1374
  issues
978
1375
  };
979
1376
  }
@@ -1418,6 +1815,23 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1418
1815
  if (!["standard", "fast"].includes(payload["speedMode"])) return publicError("internal", "Invalid speed mode preference");
1419
1816
  patch[SPEED_MODE_FIELD] = payload[SPEED_MODE_FIELD];
1420
1817
  }
1818
+ if (Object.hasOwn(payload ?? {}, "contextMode")) {
1819
+ if (![
1820
+ "standard",
1821
+ "extended",
1822
+ "custom"
1823
+ ].includes(payload["contextMode"])) return publicError("internal", "Invalid context mode preference");
1824
+ patch[CONTEXT_MODE_FIELD] = payload[CONTEXT_MODE_FIELD];
1825
+ }
1826
+ if (Object.hasOwn(payload ?? {}, "customContextWindow")) {
1827
+ if (normalizeCustomContextWindow(payload["customContextWindow"]) !== payload["customContextWindow"]) return publicError("internal", "Invalid custom context window");
1828
+ patch[CUSTOM_CONTEXT_WINDOW_FIELD] = payload[CUSTOM_CONTEXT_WINDOW_FIELD];
1829
+ }
1830
+ for (const [modelKey, field] of Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS)) {
1831
+ if (!Object.hasOwn(payload ?? {}, field)) continue;
1832
+ if (normalizeCustomContextWindow(payload[field], CUSTOM_CONTEXT_MODEL_CAPS[modelKey]) !== payload[field]) return publicError("internal", "Invalid custom model context window");
1833
+ patch[field] = payload[field];
1834
+ }
1421
1835
  if (Object.keys(patch).length === 0) return publicError("internal", "Invalid preference update");
1422
1836
  await preferences.update(patch);
1423
1837
  }
@@ -1506,20 +1920,40 @@ function apply(ctx) {
1506
1920
  ]),
1507
1921
  [LEGACY_QUICK_QUOTA_FIELD]: z.boolean(),
1508
1922
  [SEARCH_PROVIDER_FIELD]: z.union(["dsh", SEARCH_PROVIDER_CODEX]).default("dsh"),
1509
- [SPEED_MODE_FIELD]: z.union([SPEED_MODE_STANDARD, SPEED_MODE_FAST]).default(DEFAULT_SPEED_MODE)
1923
+ [SPEED_MODE_FIELD]: z.union([SPEED_MODE_STANDARD, SPEED_MODE_FAST]).default(DEFAULT_SPEED_MODE),
1924
+ [CONTEXT_MODE_FIELD]: z.union([
1925
+ CONTEXT_MODE_STANDARD,
1926
+ CONTEXT_MODE_EXTENDED,
1927
+ CONTEXT_MODE_CUSTOM
1928
+ ]).default(DEFAULT_CONTEXT_MODE),
1929
+ [CUSTOM_CONTEXT_WINDOW_FIELD]: z.number().step(1).min(128e3).max(1e6).default(DEFAULT_CUSTOM_CONTEXT_WINDOW),
1930
+ ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, z.number().step(1).min(128e3).max(CUSTOM_CONTEXT_MODEL_CAPS[modelKey]).default(CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey])]))
1510
1931
  }));
1511
1932
  const searchProvider = createSearchProviderSwitcher(ctx.loader);
1933
+ const network = createCodexNetworkTransport();
1934
+ const provider = openaiCodexSubscriptionProvider({
1935
+ resolveSpeedMode: () => settings.get()[SPEED_MODE_FIELD],
1936
+ resolveContextMode: () => normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
1937
+ resolveCustomContextWindow: (modelKey) => {
1938
+ const field = CUSTOM_CONTEXT_MODEL_FIELDS[modelKey];
1939
+ return normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey]);
1940
+ },
1941
+ runNetwork: network.run
1942
+ });
1512
1943
  const preferences = {
1513
1944
  status: () => ({
1514
1945
  [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]),
1515
1946
  [SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
1516
1947
  [SPEED_MODE_FIELD]: settings.get()[SPEED_MODE_FIELD],
1948
+ [CONTEXT_MODE_FIELD]: normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
1949
+ [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(settings.get()[CUSTOM_CONTEXT_WINDOW_FIELD]),
1950
+ ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
1951
+ contextModels: contextModelGroups(provider.getModels()),
1517
1952
  writable: ctx.settings.writable
1518
1953
  }),
1519
1954
  update: (patch) => settings.update(patch)
1520
1955
  };
1521
1956
  const store = new DshOAuthCredentialStore(ctx.credentials, CREDENTIAL_REF, [LEGACY_CREDENTIAL_REF]);
1522
- const provider = openaiCodexSubscriptionProvider({ resolveSpeedMode: () => settings.get()[SPEED_MODE_FIELD] });
1523
1957
  const authModels = createModels({ credentials: store });
1524
1958
  authModels.setProvider(provider);
1525
1959
  const profile = Object.freeze({
@@ -1534,7 +1968,16 @@ function apply(ctx) {
1534
1968
  cacheRetention: "short",
1535
1969
  transport: "sse"
1536
1970
  });
1537
- const profiles = /* @__PURE__ */ new Map([[PROVIDER, profile]]);
1971
+ let profileKey;
1972
+ let profileSnapshot;
1973
+ const profiles = () => {
1974
+ const key = [normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]), ...Object.values(CUSTOM_CONTEXT_MODEL_FIELDS).map((field) => settings.get()[field])].join(":");
1975
+ if (key !== profileKey) {
1976
+ profileKey = key;
1977
+ profileSnapshot = /* @__PURE__ */ new Map([[PROVIDER, profile]]);
1978
+ }
1979
+ return profileSnapshot;
1980
+ };
1538
1981
  const resolveAuth = () => authModels.getAuth(PROVIDER);
1539
1982
  const adapterAuth = Object.freeze({
1540
1983
  credentials: store,
@@ -1546,10 +1989,11 @@ function apply(ctx) {
1546
1989
  ctx.tools.register(createCodexImageTool({
1547
1990
  getAuth: resolveAuth,
1548
1991
  readCredential: (options) => store.read(PROVIDER, options),
1549
- attachments: ctx.attachments
1992
+ attachments: ctx.attachments,
1993
+ fetch: (input, init) => network.fetch("image", input, init)
1550
1994
  }));
1551
1995
  const adapter = new PiAiAdapter({
1552
- profiles: () => profiles,
1996
+ profiles,
1553
1997
  resolveApiKey: async () => {
1554
1998
  let resolved;
1555
1999
  try {
@@ -1572,7 +2016,8 @@ function apply(ctx) {
1572
2016
  const request = currentAgent()?.session.requestContext?.();
1573
2017
  return request?.provider === PROVIDER ? request.model : void 0;
1574
2018
  },
1575
- resolveSessionId: () => currentAgent()?.session.id
2019
+ resolveSessionId: () => currentAgent()?.session.id,
2020
+ fetch: (input, init) => network.fetch("search", input, init)
1576
2021
  }));
1577
2022
  ctx.effect(() => {
1578
2023
  const select = async (value) => {
@@ -1585,16 +2030,18 @@ function apply(ctx) {
1585
2030
  select(settings.get());
1586
2031
  return settings.watch(select);
1587
2032
  }, "codex-subscription: search provider selection");
1588
- const auth = createCodexAuthService(authModels, store);
2033
+ const auth = createCodexAuthService(authModels, store, { runLogin: (operation) => network.run("login", operation) });
1589
2034
  const coordinator = new CodexLoginCoordinator(auth);
1590
2035
  const usageReader = createCodexUsageReader({
1591
2036
  getAuth: resolveAuth,
1592
- readCredential: (options) => store.read(PROVIDER, options)
2037
+ readCredential: (options) => store.read(PROVIDER, options),
2038
+ fetch: (input, init) => network.fetch("quota", input, init)
1593
2039
  });
1594
2040
  const resetCreditService = createCodexResetCreditService({
1595
2041
  getAuth: resolveAuth,
1596
2042
  readCredential: (options) => store.read(PROVIDER, options),
1597
- usageReader
2043
+ usageReader,
2044
+ fetch: (input, init) => network.fetch("quota-reset", input, init)
1598
2045
  });
1599
2046
  const handler = createSubscriptionRpcHandler({
1600
2047
  authHandler: createCodexRpcHandler(coordinator, { openExternal: openCodexAuthUrl }),
@@ -1604,10 +2051,11 @@ function apply(ctx) {
1604
2051
  diagnosticsReader: () => createSubscriptionDiagnostics({
1605
2052
  auth,
1606
2053
  preferences,
1607
- login: coordinator.supportState()
2054
+ login: coordinator.supportState(),
2055
+ network
1608
2056
  })
1609
2057
  });
1610
2058
  ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "loopback" }), "codex-subscription: loopback account RPC");
1611
2059
  }
1612
2060
  //#endregion
1613
- 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 };
2061
+ 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, normalizeContextMode, normalizeCustomContextWindow, openCodexAuthUrl, parseCodexUsage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.7.3",
3
+ "version": "1.8.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",
@@ -122,6 +122,9 @@
122
122
  "react-dom": "18.3.1",
123
123
  "tsdown": "0.22.2"
124
124
  },
125
+ "dependencies": {
126
+ "https-proxy-agent": "7.0.6"
127
+ },
125
128
  "scripts": {
126
129
  "build": "tsdown --config tsdown.config.mjs",
127
130
  "test": "node --test tests/*.test.mjs",