dsh-codex-subscription 0.2.8 → 0.3.1

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 profile; use `web` only when it is the user's target.
9
- - Use the pinned `v0.2.8` release assets for a first install, never a moving branch.
9
+ - Use the pinned `v0.3.1` release assets for a first install, never a moving branch.
10
10
  - Never print OAuth credentials, account IDs, authorization callbacks, or the
11
11
  credential store.
12
12
  - Do not start, stop, or restart DSH without explicit permission.
@@ -51,7 +51,7 @@ the user which installation is the target.
51
51
  Download the fixed release asset to a visible file, then invoke the requested action:
52
52
 
53
53
  ```powershell
54
- curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.2.8/dsh-codex.ps1 -o "$env:TEMP\dsh-codex.ps1"
54
+ curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.3.1/dsh-codex.ps1 -o "$env:TEMP\dsh-codex.ps1"
55
55
  powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex.ps1" Install
56
56
  ```
57
57
 
@@ -59,7 +59,7 @@ If `curl.exe` is unavailable, download the same pinned asset without executing
59
59
  remote text in memory:
60
60
 
61
61
  ```powershell
62
- Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.2.8/dsh-codex.ps1' -OutFile "$env:TEMP\dsh-codex.ps1"
62
+ Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.3.1/dsh-codex.ps1' -OutFile "$env:TEMP\dsh-codex.ps1"
63
63
  powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex.ps1" Install
64
64
  ```
65
65
 
@@ -89,17 +89,18 @@ profile and saved login.
89
89
 
90
90
  ## Existing DSH CLI
91
91
 
92
- When `dsh`, Node.js, and pnpm are already available, install the fixed package
93
- asset directly:
92
+ When `dsh`, Node.js, and pnpm are already available, install the pinned npm
93
+ package directly:
94
94
 
95
95
  ```sh
96
- dsh plugin --profile web add https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.2.8/dsh-codex-subscription.tgz
96
+ dsh plugin --profile web add dsh-codex-subscription@0.3.1
97
97
  ```
98
98
 
99
- Update by running the same `add` command again. When migrating from v0.2.1 by
100
- hand, remove `@wsl043/dsh-codex-subscription` only after the new package passes
101
- verification. The Windows manager performs this migration automatically and
102
- preserves the stored login. Uninstall the current package with:
99
+ Update with `dsh plugin --profile web update dsh-codex-subscription`. When
100
+ migrating from v0.2.1 by hand, remove `@wsl043/dsh-codex-subscription` only
101
+ after the new package passes verification. The Windows manager performs this
102
+ migration automatically and preserves the stored login. Uninstall the current
103
+ package with:
103
104
 
104
105
  ```sh
105
106
  dsh plugin --profile web remove dsh-codex-subscription
@@ -127,8 +128,9 @@ If the user authorizes a live check, restart DSH manually, open
127
128
  **Settings -> Codex**, and verify the page loads. Confirm that Web
128
129
  search offers both **DSH default** and **Codex subscription**, and that the Beta
129
130
  composer quota switch starts off. Weekly-only usage is valid; Spark remains a
130
- separate bucket. Do not run a quota-consuming model turn unless the user
131
- explicitly asks.
131
+ separate bucket. Confirm that `codex_image_generate` is available. Only when the
132
+ user explicitly requests a quota-consuming live check, ask the Agent to generate
133
+ one simple image and confirm that it appears in the conversation.
132
134
 
133
135
  ## Failure handling
134
136
 
package/README.en.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # DSH Codex Subscription
2
2
 
3
3
  [![CI](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml/badge.svg)](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/dsh-codex-subscription?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-codex-subscription)
4
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
5
6
  [![GitHub stars](https://img.shields.io/github/stars/WSL043/dsh-codex-subscription?style=flat&logo=github&label=stars)](https://github.com/WSL043/dsh-codex-subscription/stargazers)
6
7
 
@@ -8,7 +9,7 @@
8
9
 
9
10
  Sign in to ChatGPT and use a Codex subscription directly in DeepSeek Harness.
10
11
  No OpenAI API key or Codex CLI installation is required. DSH conversations,
11
- tools, permissions, search choice, and quota stay in one place.
12
+ tools, permissions, image generation, search choice, and quota stay in one place.
12
13
 
13
14
  [Agent install](#let-an-agent-install-it-recommended) · [Windows install](#manual-windows-install) · [Update or uninstall](#update-and-uninstall)
14
15
 
@@ -23,6 +24,7 @@ If this helps, open the [GitHub repository](https://github.com/WSL043/dsh-codex-
23
24
 
24
25
  - Uses your ChatGPT / Codex subscription directly inside DSH, with no OpenAI API key or Codex CLI required;
25
26
  - Signs in to ChatGPT from Settings and keeps credentials on the host;
27
+ - Supports Codex image generation and shows the result directly in the DSH conversation;
26
28
  - Switches between DSH's default search and Codex subscription search;
27
29
  - Shows the quota, reset time, and freshness actually returned by the backend;
28
30
  - Can show the selected Codex model's remaining quota before the model name (Beta, off by default);
@@ -79,7 +81,7 @@ once and install again. Restart DSH manually after installation.
79
81
  <summary>macOS, Linux, or an existing <code>dsh</code> command</summary>
80
82
 
81
83
  ```sh
82
- dsh plugin --profile web add https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.2.8/dsh-codex-subscription.tgz
84
+ dsh plugin --profile web add dsh-codex-subscription
83
85
  dsh plugin --profile web list dsh-codex-subscription --depth 0
84
86
  dsh --profile web --dump-config
85
87
  ```
@@ -96,6 +98,8 @@ contain one `codex-subscription` entry.
96
98
  3. Choose DSH default search or Codex subscription search.
97
99
  4. Choose a Codex model from the model selector.
98
100
 
101
+ Describe the image you want. The Agent can generate it through Codex and show the result in the conversation.
102
+
99
103
  Codex subscription search reuses the same ChatGPT sign-in and does not need an
100
104
  OpenAI API key. Changing the search source does not change the conversation
101
105
  model, and a failed source never silently falls back to another paid service.
@@ -136,7 +140,7 @@ the two Windows first-install commands above once.
136
140
  Update and verify:
137
141
 
138
142
  ```sh
139
- dsh plugin --profile web add https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.2.8/dsh-codex-subscription.tgz
143
+ dsh plugin --profile web update dsh-codex-subscription
140
144
  dsh plugin --profile web list dsh-codex-subscription --depth 0
141
145
  dsh --profile web --dump-config
142
146
  ```
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # DSH Codex Subscription
2
2
 
3
3
  [![CI](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml/badge.svg)](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/dsh-codex-subscription?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-codex-subscription)
4
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
5
6
  [![GitHub stars](https://img.shields.io/github/stars/WSL043/dsh-codex-subscription?style=flat&logo=github&label=stars)](https://github.com/WSL043/dsh-codex-subscription/stargazers)
6
7
 
7
8
  [English](README.en.md)
8
9
 
9
10
  在 DeepSeek Harness 中直接登录 ChatGPT 并使用 Codex 订阅,不需要 OpenAI API Key,
10
- 也不依赖 Codex CLI。保留 DSH 原有的会话、工具和权限,还能切换联网搜索来源并查看额度。
11
+ 也不依赖 Codex CLI。保留 DSH 原有的会话、工具和权限,还能生成图片、切换联网搜索来源并查看额度。
11
12
 
12
13
  [交给 Agent 安装](#交给-agent推荐) · [Windows 手动安装](#windows-手动安装) · [更新与卸载](#更新与卸载)
13
14
 
@@ -21,6 +22,7 @@
21
22
 
22
23
  - 在 DSH 中直接使用 ChatGPT / Codex 订阅,不需要 OpenAI API Key 或 Codex CLI;
23
24
  - 在设置页登录 ChatGPT,凭据保留在本机;
25
+ - 支持 Codex 图片生成,结果直接显示在 DSH 会话中;
24
26
  - 可在 DSH 默认搜索与 Codex 订阅搜索之间切换;
25
27
  - 展示服务端实际返回的额度、重置时间和更新时间;
26
28
  - 可在模型名称左侧显示当前 Codex 模型的剩余额度(Beta,默认关闭);
@@ -70,7 +72,7 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex.ps1
70
72
  <summary>macOS、Linux,或已有 <code>dsh</code> 命令</summary>
71
73
 
72
74
  ```sh
73
- dsh plugin --profile web add https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.2.8/dsh-codex-subscription.tgz
75
+ dsh plugin --profile web add dsh-codex-subscription
74
76
  dsh plugin --profile web list dsh-codex-subscription --depth 0
75
77
  dsh --profile web --dump-config
76
78
  ```
@@ -87,6 +89,8 @@ dsh --profile web --dump-config
87
89
  3. 选择使用 DSH 默认搜索,或使用 Codex 订阅搜索;
88
90
  4. 在模型选择器中选择 Codex 模型。
89
91
 
92
+ 需要图片时直接描述想要的画面,Agent 会调用 Codex 图片生成并在会话中显示结果。
93
+
90
94
  Codex 订阅搜索复用同一份 ChatGPT 登录,不需要 OpenAI API Key。切换搜索来源不会改变
91
95
  当前对话模型,也不会在失败时自动改走另一个付费服务。升级后默认保留 DSH 原有搜索,
92
96
  需要使用 Codex 订阅搜索时再主动切换。
@@ -124,7 +128,7 @@ Windows 首次安装命令即可。
124
128
  更新并检查:
125
129
 
126
130
  ```sh
127
- dsh plugin --profile web add https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.2.8/dsh-codex-subscription.tgz
131
+ dsh plugin --profile web update dsh-codex-subscription
128
132
  dsh plugin --profile web list dsh-codex-subscription --depth 0
129
133
  dsh --profile web --dump-config
130
134
  ```
package/dsh-codex.ps1 CHANGED
@@ -32,8 +32,8 @@ if ($Managed -and -not $PSBoundParameters.ContainsKey('Action')) {
32
32
 
33
33
  $PackageName = 'dsh-codex-subscription'
34
34
  $LegacyPackageName = '@wsl043/dsh-codex-subscription'
35
- $PackageVersion = '0.2.8'
36
- $PackageSpec = 'https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.2.8/dsh-codex-subscription.tgz'
35
+ $PackageVersion = '0.3.1'
36
+ $PackageSpec = 'https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.3.1/dsh-codex-subscription.tgz'
37
37
  $PnpmVersion = '11.19.0'
38
38
  $PnpmUrl = 'https://registry.npmjs.org/pnpm/-/pnpm-11.19.0.tgz'
39
39
  $PnpmSha512 = '7881F3ED590D472C4A955E2B88B2121791116066DCC88CBCA3849EC9B60F1BBAA6D2CCB221FA91DA4E1C65BEF2BCBE379365AEA7AC539C7BF86DEDC3A1B22DCE'
@@ -71,8 +71,12 @@ function Invoke-LatestManager {
71
71
  param([Parameter(Mandatory = $true)][string] $InstalledCommandRoot)
72
72
 
73
73
  Write-Host 'Checking the latest immutable release...'
74
- $releaseResponse = Invoke-WebRequest -UseBasicParsing -Uri $ReleaseApi -Headers @{
74
+ $releaseSeparator = if ($ReleaseApi.Contains('?')) { '&' } else { '?' }
75
+ $releaseUri = $ReleaseApi + $releaseSeparator + 'cache_bust=' + [DateTime]::UtcNow.Ticks
76
+ $releaseResponse = Invoke-WebRequest -UseBasicParsing -Uri $releaseUri -Headers @{
75
77
  Accept = 'application/vnd.github+json'
78
+ 'Cache-Control' = 'no-cache'
79
+ Pragma = 'no-cache'
76
80
  'User-Agent' = 'dsh-codex'
77
81
  }
78
82
  try {
@@ -599,7 +603,7 @@ function Invoke-DshCommand {
599
603
  if ($LASTEXITCODE -ne 0) { throw "dsh failed with exit code $LASTEXITCODE." }
600
604
  }
601
605
 
602
- function Get-InstalledPackageNames {
606
+ function Get-InstalledPackages {
603
607
  param([Parameter(Mandatory = $true)] $Target)
604
608
 
605
609
  $json = Invoke-DshCommand -Target $Target -Arguments @(
@@ -618,7 +622,17 @@ function Get-InstalledPackageNames {
618
622
  $dependenciesProperty = $project.PSObject.Properties['dependencies']
619
623
  if ($null -eq $dependenciesProperty -or $null -eq $dependenciesProperty.Value) { continue }
620
624
  foreach ($property in $dependenciesProperty.Value.PSObject.Properties) {
621
- Write-Output ([string] $property.Name)
625
+ $version = $null
626
+ if ($null -ne $property.Value) {
627
+ $versionProperty = $property.Value.PSObject.Properties['version']
628
+ if ($null -ne $versionProperty -and $null -ne $versionProperty.Value) {
629
+ $version = [string] $versionProperty.Value
630
+ }
631
+ }
632
+ Write-Output ([pscustomobject]@{
633
+ Name = [string] $property.Name
634
+ Version = $version
635
+ })
622
636
  }
623
637
  }
624
638
  }
@@ -686,9 +700,10 @@ try {
686
700
  $env:npm_config_store_dir = $pnpmStore
687
701
  }
688
702
 
689
- $installedBefore = @(Get-InstalledPackageNames -Target $target)
690
- $hadPackage = $installedBefore -contains $PackageName
691
- $hadLegacyPackage = $installedBefore -contains $LegacyPackageName
703
+ $installedBefore = @(Get-InstalledPackages -Target $target)
704
+ $installedBeforeNames = @($installedBefore | ForEach-Object { $_.Name })
705
+ $hadPackage = $installedBeforeNames -contains $PackageName
706
+ $hadLegacyPackage = $installedBeforeNames -contains $LegacyPackageName
692
707
 
693
708
  if ($Action -eq 'Uninstall') {
694
709
  if ($hadPackage) {
@@ -726,9 +741,10 @@ try {
726
741
  $config = Invoke-DshCommand -Target $target -Arguments @('--profile', $Profile, '--dump-config') -Capture
727
742
  $entryCount = ([regex]::Matches($config, '(?<![A-Za-z0-9_-])codex-subscription(?![A-Za-z0-9_-])')).Count
728
743
  $legacyEntryCount = ([regex]::Matches($config, '(?<![A-Za-z0-9_-])wsl043-codex-subscription(?![A-Za-z0-9_-])')).Count
729
- $installedAfter = @(Get-InstalledPackageNames -Target $target)
744
+ $installedAfter = @(Get-InstalledPackages -Target $target)
745
+ $installedAfterNames = @($installedAfter | ForEach-Object { $_.Name })
730
746
  if ($Action -eq 'Uninstall') {
731
- if (($installedAfter -contains $PackageName) -or ($installedAfter -contains $LegacyPackageName)) {
747
+ if (($installedAfterNames -contains $PackageName) -or ($installedAfterNames -contains $LegacyPackageName)) {
732
748
  throw 'The plugin package is still present after uninstall.'
733
749
  }
734
750
  if ($entryCount -ne 0 -or $legacyEntryCount -ne 0) {
@@ -737,8 +753,13 @@ try {
737
753
  Remove-ManagerCommand -Directory $managerCommandRoot
738
754
  Write-Host 'Uninstalled. The DSH profile and saved credentials were kept.'
739
755
  } else {
740
- if (-not ($installedAfter -contains $PackageName)) { throw 'The installed package did not appear in the DSH plugin list.' }
741
- if ($installedAfter -contains $LegacyPackageName) { throw 'The legacy package is still present after migration.' }
756
+ $installedPackage = @($installedAfter | Where-Object { $_.Name -eq $PackageName } | Select-Object -First 1)
757
+ if ($installedPackage.Count -eq 0) { throw 'The installed package did not appear in the DSH plugin list.' }
758
+ if ($installedPackage[0].Version -ne $PackageVersion) {
759
+ $foundVersion = if ($installedPackage[0].Version) { $installedPackage[0].Version } else { 'unknown' }
760
+ throw "DSH did not install the requested package version: expected $PackageVersion, found $foundVersion."
761
+ }
762
+ if ($installedAfterNames -contains $LegacyPackageName) { throw 'The legacy package is still present after migration.' }
742
763
  if ($entryCount -ne 1) { throw "Expected one plugin profile entry, found $entryCount." }
743
764
  if ($legacyEntryCount -ne 0) { throw 'The legacy plugin profile entry is still present after migration.' }
744
765
  Install-ManagerCommand -Directory $managerCommandRoot
package/lib/client.js CHANGED
@@ -6,6 +6,7 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let _deepseek_ai_dsh_client_ui_attachment = require("@deepseek-ai/dsh-client-ui-attachment");
9
10
  let react_jsx_runtime = require("react/jsx-runtime");
10
11
  //#region src/settings-contract.js
11
12
  const QUICK_QUOTA_FIELD = "quickQuotaVisible";
@@ -36,7 +37,8 @@ window.__ModuleLoader__.load({
36
37
  "slots",
37
38
  "locale",
38
39
  "connection",
39
- "modelDirectories"
40
+ "modelDirectories",
41
+ "conversation"
40
42
  ];
41
43
  const NS = "settings.codexSubscription";
42
44
  const CHANNEL = "/codex-subscription";
@@ -94,7 +96,18 @@ window.__ModuleLoader__.load({
94
96
  unavailable: "暂无数据",
95
97
  quickQuotaSetting: "输入框额度",
96
98
  quickQuotaBeta: "Beta",
97
- quickQuotaStatus: "Codex 剩余额度 {value}%"
99
+ quickQuotaStatus: "Codex 剩余额度 {value}%",
100
+ imageGenerate: "生成图片",
101
+ imageGenerating: "正在生成…",
102
+ imageGenerated: "已生成",
103
+ imageFailed: "生成失败",
104
+ imageLabel: "生成的图片",
105
+ imageOpen: "查看原图",
106
+ imageOpenNamed: "查看 {value}",
107
+ imageLoading: "正在加载图片…",
108
+ imageLoadFailed: "图片加载失败,点击重试",
109
+ imagePreview: "图片预览",
110
+ imageClosePreview: "关闭预览"
98
111
  };
99
112
  const en = {
100
113
  nav: "Codex",
@@ -148,7 +161,18 @@ window.__ModuleLoader__.load({
148
161
  unavailable: "No data yet",
149
162
  quickQuotaSetting: "Composer quota",
150
163
  quickQuotaBeta: "Beta",
151
- quickQuotaStatus: "Codex quota: {value}% remaining"
164
+ quickQuotaStatus: "Codex quota: {value}% remaining",
165
+ imageGenerate: "Generate image",
166
+ imageGenerating: "Generating…",
167
+ imageGenerated: "Generated",
168
+ imageFailed: "Generation failed",
169
+ imageLabel: "Generated image",
170
+ imageOpen: "View original",
171
+ imageOpenNamed: "View {value}",
172
+ imageLoading: "Loading image…",
173
+ imageLoadFailed: "Image failed to load. Click to retry",
174
+ imagePreview: "Image preview",
175
+ imageClosePreview: "Close preview"
152
176
  };
153
177
  const STYLE = `
154
178
  .codexSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
@@ -176,6 +200,7 @@ window.__ModuleLoader__.load({
176
200
  .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}
177
201
  .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}
178
202
  .codexComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;margin-right:-4px;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}
203
+ .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}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
179
204
  @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
180
205
  @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
181
206
  @media(max-width:640px){.codexSubscriptionCard{padding:14px}}
@@ -200,6 +225,61 @@ window.__ModuleLoader__.load({
200
225
  const date = new Date(value);
201
226
  return Number.isFinite(date.getTime()) ? date : void 0;
202
227
  };
228
+ const imageGalleryLabels = (t) => ({
229
+ image: t("imageLabel"),
230
+ open: t("imageOpen"),
231
+ openNamed: (value) => fill(t("imageOpenNamed"), { value }),
232
+ loading: t("imageLoading"),
233
+ loadFailed: t("imageLoadFailed"),
234
+ lightbox: {
235
+ dialog: t("imagePreview"),
236
+ close: t("imageClosePreview")
237
+ }
238
+ });
239
+ function CodexImageToolRow({ block, loadImage, t }) {
240
+ const settled = block?.kind === "tool-result";
241
+ const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
242
+ const failed = settled && block.isError === true;
243
+ const state = !settled ? "running" : failed ? "error" : "done";
244
+ const status = !settled ? t("imageGenerating") : failed ? t("imageFailed") : t("imageGenerated");
245
+ const error = failed ? block.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text : void 0;
246
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
247
+ className: "codexImageTool",
248
+ "data-state": state,
249
+ children: [
250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
251
+ className: "codexImageToolRow",
252
+ children: [
253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
254
+ className: "codexImageToolIcon",
255
+ "aria-hidden": "true"
256
+ }),
257
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
258
+ className: "codexImageToolTitle",
259
+ children: t("imageGenerate")
260
+ }),
261
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
262
+ className: "codexImageToolState",
263
+ children: status
264
+ })
265
+ ]
266
+ }),
267
+ image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
268
+ className: "codexImageToolGallery",
269
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_attachment.ImageGallery, {
270
+ images: [{ attachment: image.attachment }],
271
+ load: loadImage,
272
+ align: "start",
273
+ labels: imageGalleryLabels(t)
274
+ })
275
+ }),
276
+ error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
277
+ className: "codexImageToolError",
278
+ children: error
279
+ })
280
+ ]
281
+ });
282
+ }
203
283
  function createPreferenceController(rpc) {
204
284
  let snapshot = Object.freeze({
205
285
  status: "loading",
@@ -854,6 +934,16 @@ window.__ModuleLoader__.load({
854
934
  directory: ctx.modelDirectories.directoryFor(sessionId).store
855
935
  })
856
936
  }, CodexComposerQuota));
937
+ const conversation = ctx.get("conversation");
938
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
939
+ name: "tool.call.toolview",
940
+ key: "codex_image_generate",
941
+ locale: NS,
942
+ inject: (sessionId) => ({
943
+ t,
944
+ loadImage: (attachment) => conversation.resolveImage(sessionId, attachment)
945
+ })
946
+ }, CodexImageToolRow));
857
947
  }
858
948
  //#endregion
859
949
  exports.apply = apply;
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
2
- import { LlmError } from "@deepseek-ai/dsh-llm";
2
+ 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";
@@ -8,6 +8,7 @@ import { openaiCodexProvider as createOpenAICodexProvider } from "@earendil-work
8
8
  import { createModels } from "@earendil-works/pi-ai";
9
9
  import { randomUUID } from "node:crypto";
10
10
  import { WebError } from "@deepseek-ai/dsh-web";
11
+ import { defineTool } from "@deepseek-ai/dsh-tools";
11
12
  //#region src/credential-store.js
12
13
  const PROVIDER$1 = "openai-codex";
13
14
  const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
@@ -466,10 +467,10 @@ const CODEX_SEARCH_URL = "https://chatgpt.com/backend-api/codex/alpha/search";
466
467
  const DEFAULT_MODEL = "gpt-5.6-luna";
467
468
  const MAX_OUTPUT_TOKENS = 4096;
468
469
  const MAX_SOURCE_DATE = 64;
469
- const record$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
470
- const nonEmpty = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
470
+ const record$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
471
+ const nonEmpty$1 = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
471
472
  const displayText = (value) => {
472
- const text = nonEmpty(value)?.replace(/\s+/gu, " ").trim();
473
+ const text = nonEmpty$1(value)?.replace(/\s+/gu, " ").trim();
473
474
  if (text === void 0 || text.length === 0) return void 0;
474
475
  return text;
475
476
  };
@@ -479,8 +480,8 @@ const boundedDisplayText = (value, maximum) => {
479
480
  return `${text.slice(0, maximum - 1)}…`;
480
481
  };
481
482
  function sourceOf(value) {
482
- if (!record$1(value)) return void 0;
483
- const url = nonEmpty(value.url);
483
+ if (!record$2(value)) return void 0;
484
+ const url = nonEmpty$1(value.url);
484
485
  if (url === void 0) return void 0;
485
486
  let parsed;
486
487
  try {
@@ -500,7 +501,7 @@ function sourceOf(value) {
500
501
  };
501
502
  }
502
503
  function parseSearchResponse(value) {
503
- if (!record$1(value) || !Array.isArray(value.results)) throw new Error("Codex returned a malformed search response");
504
+ if (!record$2(value) || !Array.isArray(value.results)) throw new Error("Codex returned a malformed search response");
504
505
  const seen = /* @__PURE__ */ new Set();
505
506
  const sources = [];
506
507
  for (const result of value.results ?? []) {
@@ -526,8 +527,8 @@ function createCodexSearchProvider(options) {
526
527
  const access = auth?.auth?.apiKey;
527
528
  const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
528
529
  if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new WebError("ChatGPT subscription is not signed in", "WEB_PROVIDER_CREDENTIAL_MISSING");
529
- const model = nonEmpty(options.resolveModel?.()) ?? DEFAULT_MODEL;
530
- const id = nonEmpty(options.resolveSessionId?.()) ?? randomUUID();
530
+ const model = nonEmpty$1(options.resolveModel?.()) ?? DEFAULT_MODEL;
531
+ const id = nonEmpty$1(options.resolveSessionId?.()) ?? randomUUID();
531
532
  let response;
532
533
  try {
533
534
  response = await fetchSearch(CODEX_SEARCH_URL, {
@@ -539,7 +540,7 @@ function createCodexSearchProvider(options) {
539
540
  accept: "application/json",
540
541
  "content-type": "application/json",
541
542
  originator: "pi",
542
- "user-agent": "dsh-codex-subscription/0.2.8"
543
+ "user-agent": "dsh-codex-subscription/0.3.1"
543
544
  },
544
545
  body: JSON.stringify({
545
546
  id,
@@ -577,6 +578,215 @@ function createCodexSearchProvider(options) {
577
578
  });
578
579
  }
579
580
  //#endregion
581
+ //#region src/codex-images.js
582
+ const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
583
+ const CODEX_IMAGE_GENERATION_URL = "https://chatgpt.com/backend-api/codex/images/generations";
584
+ const IMAGE_MODEL = "gpt-image-2";
585
+ const RESPONSE_ENVELOPE_BYTES = 1024 * 1024;
586
+ const PNG_SIGNATURE = Buffer.from([
587
+ 137,
588
+ 80,
589
+ 78,
590
+ 71,
591
+ 13,
592
+ 10,
593
+ 26,
594
+ 10
595
+ ]);
596
+ const record$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
597
+ const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
598
+ function encodedLimit(decodedBytes) {
599
+ return Math.ceil(decodedBytes / 3) * 4;
600
+ }
601
+ async function readJsonWithin(response, maximumBytes) {
602
+ const contentLength = Number(response.headers.get("content-length"));
603
+ if (Number.isFinite(contentLength) && contentLength > maximumBytes) throw new Error("Codex image response exceeds the image size limit");
604
+ if (response.body === null) throw new Error("Codex returned an unreadable image response");
605
+ const reader = response.body.getReader();
606
+ const chunks = [];
607
+ let bytes = 0;
608
+ while (true) {
609
+ const { done, value } = await reader.read();
610
+ if (done) break;
611
+ bytes += value.byteLength;
612
+ if (bytes > maximumBytes) {
613
+ await reader.cancel();
614
+ throw new Error("Codex image response exceeds the image size limit");
615
+ }
616
+ chunks.push(value);
617
+ }
618
+ const body = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), bytes).toString("utf8");
619
+ try {
620
+ return JSON.parse(body);
621
+ } catch {
622
+ throw new Error("Codex returned an unreadable image response");
623
+ }
624
+ }
625
+ /** Strictly decode one PNG returned by the subscription backend. */
626
+ function decodeCodexPng(value, maximumBytes) {
627
+ const encoded = nonEmpty(value);
628
+ if (encoded === void 0 || encoded.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(encoded)) throw new Error("Codex returned an invalid base64 PNG");
629
+ const padding = encoded.endsWith("==") ? 2 : encoded.endsWith("=") ? 1 : 0;
630
+ const decodedBytes = encoded.length / 4 * 3 - padding;
631
+ if (decodedBytes > maximumBytes) throw new Error("Codex image exceeds the image size limit");
632
+ const data = Buffer.from(encoded, "base64");
633
+ if (data.length !== decodedBytes || data.length < PNG_SIGNATURE.length || !data.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) throw new Error("Codex returned an invalid PNG");
634
+ return new Uint8Array(data);
635
+ }
636
+ function imageReference(value) {
637
+ return {
638
+ attachmentId: value.attachmentId,
639
+ mediaType: value.mediaType,
640
+ bytes: value.bytes,
641
+ width: value.width,
642
+ height: value.height,
643
+ ...value.name === void 0 ? {} : { name: value.name }
644
+ };
645
+ }
646
+ function imageContent(value) {
647
+ return [{
648
+ type: "text",
649
+ text: typeof value.size === "string" && value.size.length > 0 ? `Generated a ${value.size} image.` : "Generated an image."
650
+ }, {
651
+ type: "image",
652
+ attachment: imageReference(value.image)
653
+ }];
654
+ }
655
+ function imageOutputSchema() {
656
+ return {
657
+ type: "object",
658
+ additionalProperties: false,
659
+ properties: {
660
+ image: {
661
+ type: "object",
662
+ required: true,
663
+ additionalProperties: false,
664
+ properties: {
665
+ attachmentId: {
666
+ type: "string",
667
+ required: true
668
+ },
669
+ mediaType: {
670
+ type: "string",
671
+ enum: ["image/png"],
672
+ required: true
673
+ },
674
+ bytes: {
675
+ type: "integer",
676
+ required: true
677
+ },
678
+ width: {
679
+ type: "integer",
680
+ required: true
681
+ },
682
+ height: {
683
+ type: "integer",
684
+ required: true
685
+ },
686
+ name: { type: "string" }
687
+ }
688
+ },
689
+ background: { type: "string" },
690
+ quality: { type: "string" },
691
+ size: { type: "string" }
692
+ }
693
+ };
694
+ }
695
+ function responseMetadata(value) {
696
+ const data = Array.isArray(value?.data) ? value.data[0] : void 0;
697
+ const encoded = record$1(data) ? data.b64_json : void 0;
698
+ if (typeof encoded !== "string") throw new Error("Codex returned no image data");
699
+ return {
700
+ encoded,
701
+ background: nonEmpty(value.background),
702
+ quality: nonEmpty(value.quality),
703
+ size: nonEmpty(value.size)
704
+ };
705
+ }
706
+ /** Create the DSH-native image-generation tool backed only by the ChatGPT subscription. */
707
+ function createCodexImageTool(options) {
708
+ const fetchImage = options.fetch ?? fetch;
709
+ const attachments = options.attachments;
710
+ return defineTool({
711
+ name: CODEX_IMAGE_TOOL_NAME,
712
+ description: "Generate an image from a detailed description using the signed-in Codex subscription. Use this whenever the user asks to create an image.",
713
+ parameters: { prompt: {
714
+ type: "string",
715
+ required: true,
716
+ description: "A complete, production-ready description of the image to generate."
717
+ } },
718
+ output: {
719
+ schema: imageOutputSchema(),
720
+ render: (_args, value) => imageContent(value)
721
+ },
722
+ timeoutMs: 300 * 1e3,
723
+ isConcurrencySafe: () => false,
724
+ async execute(args, exec) {
725
+ const prompt = nonEmpty(args.prompt);
726
+ if (prompt === void 0) throw new Error("prompt must be a non-empty string");
727
+ const auth = await options.getAuth({ signal: exec.signal });
728
+ const credential = await options.readCredential({ signal: exec.signal });
729
+ const access = auth?.auth?.apiKey;
730
+ const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
731
+ if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new Error("ChatGPT subscription is not signed in");
732
+ if (!attachments.imageLimits.mediaTypes.includes("image/png")) throw new Error("This DSH installation does not accept PNG image attachments");
733
+ const maximumBytes = Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
734
+ let response;
735
+ try {
736
+ response = await fetchImage(CODEX_IMAGE_GENERATION_URL, {
737
+ method: "POST",
738
+ redirect: "error",
739
+ headers: {
740
+ authorization: `Bearer ${access}`,
741
+ "chatgpt-account-id": accountId,
742
+ accept: "application/json",
743
+ "content-type": "application/json",
744
+ originator: "pi",
745
+ "x-codex-image-turn-id": String(exec.callId),
746
+ "user-agent": "dsh-codex-subscription"
747
+ },
748
+ body: JSON.stringify({
749
+ prompt,
750
+ background: "auto",
751
+ model: IMAGE_MODEL,
752
+ quality: "auto",
753
+ size: "auto"
754
+ }),
755
+ signal: exec.signal
756
+ });
757
+ } catch (error) {
758
+ if (exec.signal.aborted) throw exec.signal.reason;
759
+ throw new Error("Codex image generation request failed", { cause: error });
760
+ }
761
+ if (!response.ok) {
762
+ if (response.status === 401 || response.status === 403) throw new Error("ChatGPT sign-in needs to be renewed");
763
+ if (response.status === 429) throw new Error("Codex image generation quota is unavailable");
764
+ throw new Error(`Codex image generation failed (HTTP ${response.status})`);
765
+ }
766
+ const metadata = responseMetadata(await readJsonWithin(response, encodedLimit(maximumBytes) + RESPONSE_ENVELOPE_BYTES));
767
+ const data = decodeCodexPng(metadata.encoded, maximumBytes);
768
+ const result = {
769
+ image: imageReference(await attachments.saveImage({
770
+ data,
771
+ mediaType: "image/png",
772
+ name: "codex-generated.png"
773
+ })),
774
+ ...metadata.background === void 0 ? {} : { background: metadata.background },
775
+ ...metadata.quality === void 0 ? {} : { quality: metadata.quality },
776
+ ...metadata.size === void 0 ? {} : { size: metadata.size }
777
+ };
778
+ if (exec.parent !== void 0) exec.deferContext(createUserMessage({
779
+ content: imageContent(result),
780
+ source: {
781
+ kind: "plugin",
782
+ plugin: "codex-subscription"
783
+ }
784
+ }));
785
+ return result;
786
+ }
787
+ });
788
+ }
789
+ //#endregion
580
790
  //#region src/settings-contract.js
581
791
  const SETTINGS_NAMESPACE = "codex-subscription";
582
792
  const QUICK_QUOTA_FIELD = "quickQuotaVisible";
@@ -719,7 +929,7 @@ function createCodexUsageReader(options) {
719
929
  "chatgpt-account-id": accountId,
720
930
  accept: "application/json",
721
931
  "cache-control": "no-store",
722
- "user-agent": "dsh-codex-subscription/0.2.8"
932
+ "user-agent": "dsh-codex-subscription/0.3.1"
723
933
  },
724
934
  signal: requestSignal(signal, timeoutMs)
725
935
  });
@@ -762,7 +972,9 @@ const inject = [
762
972
  "connection",
763
973
  "settings",
764
974
  "web",
765
- "loader"
975
+ "loader",
976
+ "tools",
977
+ "attachments"
766
978
  ];
767
979
  const PROVIDER = "openai-codex";
768
980
  const CREDENTIAL_REF = credentialRef("OPENAI_CODEX_SUBSCRIPTION_OAUTH");
@@ -879,6 +1091,11 @@ function apply(ctx) {
879
1091
  });
880
1092
  const profiles = /* @__PURE__ */ new Map([[PROVIDER, profile]]);
881
1093
  const resolveAuth = () => authModels.getAuth(PROVIDER);
1094
+ ctx.tools.register(createCodexImageTool({
1095
+ getAuth: resolveAuth,
1096
+ readCredential: (options) => store.read(PROVIDER, options),
1097
+ attachments: ctx.attachments
1098
+ }));
882
1099
  const adapter = new PiAiAdapter({
883
1100
  profiles: () => profiles,
884
1101
  resolveApiKey: async () => {
@@ -922,4 +1139,4 @@ function apply(ctx) {
922
1139
  ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "loopback" }), "codex-subscription: loopback account RPC");
923
1140
  }
924
1141
  //#endregion
925
- export { CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore, apply, assertCodexAuthUrl, commandForCodexAuthUrl, createCodexAuthService, createCodexRpcHandler, createCodexUsageReader, createSearchProviderSwitcher, createSubscriptionRpcHandler, inject, name, openCodexAuthUrl, parseCodexUsage };
1142
+ export { CODEX_IMAGE_GENERATION_URL, CODEX_IMAGE_TOOL_NAME, CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore, apply, assertCodexAuthUrl, commandForCodexAuthUrl, createCodexAuthService, createCodexImageTool, createCodexRpcHandler, createCodexUsageReader, createSearchProviderSwitcher, createSubscriptionRpcHandler, decodeCodexPng, inject, name, openCodexAuthUrl, parseCodexUsage };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "0.2.8",
3
+ "version": "0.3.1",
4
4
  "packageManager": "pnpm@11.19.0",
5
- "description": "ChatGPT and Codex subscriptions for DeepSeek Harness with OAuth login, quota, and subscription web search",
5
+ "description": "ChatGPT and Codex subscriptions for DeepSeek Harness with OAuth login, image generation, quota, and subscription web search",
6
6
  "type": "module",
7
7
  "main": "./lib/index.js",
8
8
  "exports": {
@@ -34,7 +34,8 @@
34
34
  "@deepseek-ai/dsh-client-runtime",
35
35
  "@deepseek-ai/dsh-client-ui-conversation",
36
36
  "@deepseek-ai/dsh-client-ui-model-selection",
37
- "@deepseek-ai/dsh-client-ui-settings"
37
+ "@deepseek-ai/dsh-client-ui-settings",
38
+ "@deepseek-ai/dsh-client-ui-tool"
38
39
  ],
39
40
  "platform": "web"
40
41
  }
@@ -57,7 +58,8 @@
57
58
  "quota",
58
59
  "usage",
59
60
  "oauth",
60
- "web-search"
61
+ "web-search",
62
+ "image-generation"
61
63
  ],
62
64
  "author": "WSL043",
63
65
  "license": "MIT",
@@ -80,15 +82,18 @@
80
82
  "@deepseek-ai/dsh-client-connection": "0.1.0-rc.6",
81
83
  "@deepseek-ai/dsh-client-locale": "0.1.0-rc.6",
82
84
  "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.6",
85
+ "@deepseek-ai/dsh-client-ui-attachment": "0.1.0-rc.6",
83
86
  "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.6",
84
87
  "@deepseek-ai/dsh-client-ui-model-selection": "0.1.0-rc.6",
85
88
  "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.6",
86
89
  "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.6",
87
90
  "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6",
91
+ "@deepseek-ai/dsh-client-ui-tool": "0.1.0-rc.6",
88
92
  "@deepseek-ai/dsh-credentials": "0.1.0-rc.6",
89
93
  "@deepseek-ai/dsh-llm": "0.1.0-rc.6",
90
94
  "@deepseek-ai/dsh-llm-pi-ai": "0.1.0-rc.6",
91
95
  "@deepseek-ai/dsh-settings": "0.1.0-rc.6",
96
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.6",
92
97
  "@deepseek-ai/dsh-web": "0.1.0-rc.6",
93
98
  "@deepseek-ai/schemastery": "3.18.1",
94
99
  "@earendil-works/pi-ai": "0.82.1",
@@ -99,15 +104,18 @@
99
104
  "@deepseek-ai/dsh-client-connection": "0.1.0-rc.6",
100
105
  "@deepseek-ai/dsh-client-locale": "0.1.0-rc.6",
101
106
  "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.6",
107
+ "@deepseek-ai/dsh-client-ui-attachment": "0.1.0-rc.6",
102
108
  "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.6",
103
109
  "@deepseek-ai/dsh-client-ui-model-selection": "0.1.0-rc.6",
104
110
  "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.6",
105
111
  "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.6",
106
112
  "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6",
113
+ "@deepseek-ai/dsh-client-ui-tool": "0.1.0-rc.6",
107
114
  "@deepseek-ai/dsh-credentials": "0.1.0-rc.6",
108
115
  "@deepseek-ai/dsh-llm": "0.1.0-rc.6",
109
116
  "@deepseek-ai/dsh-llm-pi-ai": "0.1.0-rc.6",
110
117
  "@deepseek-ai/dsh-settings": "0.1.0-rc.6",
118
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.6",
111
119
  "@deepseek-ai/dsh-web": "0.1.0-rc.6",
112
120
  "@deepseek-ai/schemastery": "3.18.1",
113
121
  "@earendil-works/pi-ai": "0.82.1",