@mars-sea/dsh-commandcode-provider 0.2.2 → 0.2.4
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/CHANGELOG.md +16 -0
- package/LICENSE +1 -7
- package/NOTICE +10 -0
- package/README.md +56 -97
- package/README.zh-CN.md +56 -96
- package/assets/screenshots/model-picker.png +0 -0
- package/assets/screenshots/usage-dashboard.png +0 -0
- package/lib/client.js +2 -2
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +8 -4
- package/lib/index.js +50 -6
- package/lib/index.js.map +1 -1
- package/package.json +4 -2
package/README.zh-CN.md
CHANGED
|
@@ -2,26 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
[English](./README.md) | **简体中文**
|
|
4
4
|
|
|
5
|
+
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
6
|
+
[](https://github.com/Mars-Sea/dsh-commandcode-provider/stargazers)
|
|
7
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
8
|
+
[](https://github.com/Mars-Sea/dsh-commandcode-provider/pulls)
|
|
5
9
|
[](https://github.com/Mars-Sea/dsh-commandcode-provider/actions/workflows/ci.yml)
|
|
6
10
|
[](https://opensource.org/licenses/MIT)
|
|
7
11
|
[](https://www.npmjs.com/package/@mars-sea/dsh-commandcode-provider)
|
|
8
12
|
|
|
9
|
-
非官方 [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) 的 LLM provider 插件,用于 **Command Code**,移植自 [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider)(MIT 协议)。它注册了一个 `commandcode`
|
|
13
|
+
非官方 [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) 的 LLM provider 插件,用于 **Command Code**,移植自 [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider)(MIT 协议)。它注册了一个 `commandcode` provider,将请求转换为 Command Code 的 Provider API(`POST /alpha/generate`,由 pi 插件逆向工程,对应 `command-code@1.26.0`)。
|
|
10
14
|
|
|
11
15
|
> 这是一个社区集成。你需要自己的 Command Code 账号、API key 或订阅,并遵守 Command Code 的服务条款。本项目与 Command Code, Inc. 无关。
|
|
12
16
|
|
|
13
17
|
## 功能一览
|
|
14
18
|
|
|
15
|
-
- **插件包**:可通过 `dsh plugin add` 安装到任意 dsh
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- **Image 与上下文标注**:支持视觉的模型显示 `Image`,并显示人类可读的上下文长度(`1M`、`256K`、`262K`);纯文本模型两者都不显示——套餐 + 上下文已足够。
|
|
24
|
-
- **支持视觉模型的图片输入**:官方注册表中带 Vision 能力的模型(如 `claude-sonnet-5`、`gpt-5.4`、`google/gemini-3.5-flash` 等)可接收附加图片——通过 dsh 附件服务解析字节,并以官方 Command Code wire 格式发送。纯文本模型(如 `deepseek/deepseek-v4-flash`、`zai-org/GLM-5.3`)会明确拒绝图片而非静默丢弃。
|
|
19
|
+
- **插件包**:可通过 `dsh plugin add` 安装到任意 dsh 配置,并提供 **`commandcode` provider 路由**,带实时模型目录(`GET {apiBase}/provider/v1/models`,缓存于 `~/.commandcode/models-cache.json`)。
|
|
20
|
+
- **专属"Command Code"设置页**(设置 → **Command Code**),带 **API key 输入框** 与连接参数(API 地址、工作目录、请求/流超时)。密钥通过 dsh 凭据服务存储;连接参数写入 `llm-commandcode` 设置段,对下一次请求即刻生效,无需重启。
|
|
21
|
+
- **API key 解析顺序**:`config.apiKey` → 凭据引用 `apiKeyEnv`(默认 `COMMANDCODE_API_KEY`)→ 启动环境变量 → 官方 CLI 认证文件(`~/.commandcode/auth.json`,由 `command-code login` 写入)。
|
|
22
|
+
- **模型选择器标注**:每个模型显示包含它的**最低套餐**(`KNOWN_PLANS`)、**活动折扣**或 `FREE` 徽章(`KNOWN_DEALS`,到期自动隐藏已失效折扣)、Vision 模型的 **`Image`** 标记,以及**上下文长度**(`1M` / `256K` / `262K`)——例如 *"Go · 50% off · Image · 1M"*。列表**按套餐排序**(Go → GOAT → Pro → Provider/Max),你当前套餐能用的模型总是排在最前。
|
|
23
|
+
- **推理强度(reasoning-effort)支持**:针对官方目录标为推理模型的模型(`KNOWN_EFFORTS`,与 `command-code@1.26.0` 一致);没有可选档位但仍支持思考的模型会自动思考,与官方 CLI 行为完全一致。
|
|
24
|
+
- **支持视觉模型的图片输入**(通过 dsh 附件服务、以官方 wire 格式发送);纯文本模型会明确拒绝图片(`UNSUPPORTED_CONTENT`)而非静默丢弃。
|
|
25
|
+
|
|
26
|
+
<img src="assets/screenshots/model-picker.png" alt="带套餐、折扣、图片与上下文标注的模型选择器" width="250">
|
|
25
27
|
|
|
26
28
|
## 获取 API key
|
|
27
29
|
|
|
@@ -32,13 +34,13 @@ npm i -g command-code@latest
|
|
|
32
34
|
cmd login # macOS/Linux;Windows 原生版:cmdc login
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
`cmd login`
|
|
37
|
+
`cmd login` 会打开浏览器认证;成功后 key 写入 `~/.commandcode/auth.json`——本插件会自动读取(最后兜底)。也可以直接在浏览器创建 key([Command Code Studio](https://commandcode.ai/studio/auth/cli))并粘贴到 **设置 → Command Code**,或 `export COMMANDCODE_API_KEY="user_..."`。
|
|
36
38
|
|
|
37
39
|
## 安装
|
|
38
40
|
|
|
39
41
|
### 从 npm 安装(推荐)
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
npm 上裸名 `dsh-commandcode-provider` 已被无关包占用,因此本插件以 **`@mars-sea/dsh-commandcode-provider`** 发布:
|
|
42
44
|
|
|
43
45
|
```sh
|
|
44
46
|
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider
|
|
@@ -48,21 +50,21 @@ dsh plugin --profile web add @mars-sea/dsh-commandcode-provider
|
|
|
48
50
|
|
|
49
51
|
```sh
|
|
50
52
|
# 推荐:锁定发布 tag(可读、不可变)
|
|
51
|
-
dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#v0.2.
|
|
53
|
+
dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#v0.2.2
|
|
52
54
|
# 或按完整 commit SHA 锁定任意提交
|
|
53
55
|
dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#<完整-commit-sha>
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
`#<ref>`
|
|
58
|
+
`#<ref>` 后缀把源码锁定到**某一个精确版本**(pnpm 的 git 依赖语法)。不加 `#` 则跟随默认分支,后续 push 会悄悄改变你装到的内容。
|
|
57
59
|
|
|
58
|
-
git 安装会拉取**源码**,因此包的 `prepare` 脚本会在安装后构建 `lib/`。pnpm ≥10
|
|
60
|
+
git 安装会拉取**源码**,因此包的 `prepare` 脚本会在安装后构建 `lib/`。pnpm ≥10 默认阻止该脚本——先运行 `add`,再把 pnpm 打印的**确切包 key** 复制到 `~/.dsh/profiles/web/pnpm-workspace.yaml`:
|
|
59
61
|
|
|
60
62
|
```yaml
|
|
61
63
|
allowBuilds:
|
|
62
64
|
'@mars-sea/dsh-commandcode-provider@github:Mars-Sea/dsh-commandcode-provider#<完整-commit-sha>': true
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
然后重新运行 `add
|
|
67
|
+
然后重新运行 `add`。
|
|
66
68
|
|
|
67
69
|
### 从本地检出安装
|
|
68
70
|
|
|
@@ -72,11 +74,11 @@ npm run build # git/压缩包安装通过 `prepare` 自
|
|
|
72
74
|
dsh plugin --profile web add /path/to/dsh-commandcode-provider
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
修改 `src/` 后需重新运行 `npm run build` 并重启应用。
|
|
76
78
|
|
|
77
79
|
### 安装做了什么
|
|
78
80
|
|
|
79
|
-
`dsh plugin add`
|
|
81
|
+
`dsh plugin add` 会把包链接到配置目录(pnpm 按**真实包名** `@mars-sea/dsh-commandcode-provider` 记录),追加到 `dsh.profile.bundles`,并激活 `cordis.patch.yml` 层:
|
|
80
82
|
|
|
81
83
|
```yaml
|
|
82
84
|
- insert:
|
|
@@ -86,7 +88,7 @@ dsh plugin --profile web add /path/to/dsh-commandcode-provider
|
|
|
86
88
|
apiKeyEnv: COMMANDCODE_API_KEY
|
|
87
89
|
```
|
|
88
90
|
|
|
89
|
-
patch
|
|
91
|
+
patch 里的 `name` 必须是**带引号的完整包名**——loader 会把它当作模块从 profile 的 `node_modules` 导入,而 pnpm 只会链接带 scope 的名字。写成裸名会报 `ERR_MODULE_NOT_FOUND` 并在启动时崩溃;不引号的 `@mars-sea/...` 会导致 YAML 解析失败(见[故障排查](#故障排查))。
|
|
90
92
|
|
|
91
93
|
验证合成后的层,然后(重新)启动 Web 应用:
|
|
92
94
|
|
|
@@ -97,78 +99,50 @@ dsh web # 或重启你正在运行的实例
|
|
|
97
99
|
|
|
98
100
|
## 更新
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
按安装方式选择更新命令:
|
|
102
|
+
patch 层在每次启动时都从**已安装的包**读取,更新包本身就会带入修复后的行——**不需要**手工改 `cordis.patch.yml`(除非你把它的内容复制到了自己 profile 的层里)。
|
|
103
103
|
|
|
104
104
|
```sh
|
|
105
|
-
#
|
|
105
|
+
# npm:总是升到最新发布版本
|
|
106
106
|
dsh plugin --profile web update @mars-sea/dsh-commandcode-provider
|
|
107
107
|
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#v0.2.1
|
|
108
|
+
# GitHub(按 tag):指向新 tag——无需先卸载,pnpm 会就地替换
|
|
109
|
+
dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#v0.2.2
|
|
111
110
|
|
|
112
|
-
#
|
|
111
|
+
# 本地检出:拉取、重新构建、重启
|
|
113
112
|
git -C /path/to/dsh-commandcode-provider pull
|
|
114
113
|
npm run build --prefix /path/to/dsh-commandcode-provider
|
|
115
114
|
dsh web
|
|
116
115
|
```
|
|
117
116
|
|
|
118
|
-
然后重启 Web
|
|
117
|
+
然后重启 Web 应用。用 `dsh --profile web --dump-config` 验证——层里应显示 `name: '@mars-sea/dsh-commandcode-provider'`。
|
|
119
118
|
|
|
120
|
-
> **`update` 提示 "Already up to date" 但版本没变(pnpm ≥ 11)?** pnpm 11 的 `minimumReleaseAge`
|
|
121
|
-
>
|
|
122
|
-
> ```sh
|
|
123
|
-
> dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.1.9
|
|
124
|
-
> ```
|
|
125
|
-
>
|
|
126
|
-
> `add` 加显式版本会真正装上新版本(并把你的 spec 升为 `^0.1.9`)。如果你信任所用 registry,也可以在 profile 目录里执行 `pnpm config set minimumReleaseAge 0 --location project` 关掉该门禁(或删除 pnpm 写进 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 条目)。
|
|
119
|
+
> **`update` 提示 "Already up to date" 但版本没变(pnpm ≥ 11)?** pnpm 11 的 `minimumReleaseAge` 供应链策略可能拒绝刚发布的新版本。请改用显式版本:`dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.2.2`(或在 profile 目录里 `pnpm config set minimumReleaseAge 0 --location project`)。
|
|
127
120
|
|
|
128
|
-
> **从 ≤0.1.6 升级**(或手改坏的 profile
|
|
121
|
+
> **从 ≤0.1.6 升级**(或手改坏的 profile):如果你之前**手工复制**过旧的 patch 行到你 profile 自己的 `cordis.patch.yml`,那份拷贝会覆盖 bundle 层——请改成 `name: "@mars-sea/dsh-commandcode-provider"` 或删掉它(见[故障排查](#故障排查))。
|
|
129
122
|
|
|
130
|
-
>
|
|
123
|
+
> **想卸载而不是升级**:`dsh plugin --profile web remove @mars-sea/dsh-commandcode-provider`(用 **scoped** 名——pnpm 按真实包名记录依赖)。你在 dsh 凭据库和 `~/.commandcode/auth.json` 里的 API key 不受影响。
|
|
131
124
|
|
|
132
125
|
## 验证是否生效
|
|
133
126
|
|
|
134
|
-
|
|
127
|
+
重启后:**设置 → Command Code** 显示专属页面——填入 API key 并点击**保存**(徽章变为"已配置"即表示 Host 已存储)。**设置 → Models** 会显示 **Command Code** 卡片;模型选择器会在 **commandcode** 下列出实时目录。发送消息并选择你套餐中包含的模型——`deepseek/deepseek-v4-flash` 适用于入门级套餐,开放权重模型(DeepSeek/Qwen/Kimi/MiniMax)通常都可用,而前沿模型(Claude/GPT/Gemini/Grok)可能需要 Pro/Max 套餐或按需计费。
|
|
135
128
|
|
|
136
129
|
## 用量面板
|
|
137
130
|
|
|
138
|
-
插件注册了一个 `/commandcode` 斜杠命令(需要 dsh 的 `commands` 服务,标准 web profile
|
|
131
|
+
插件注册了一个 `/commandcode` 斜杠命令(需要 dsh 的 `commands` 服务,标准 web profile 自带),从官方账户端点显示你的账户状态:
|
|
139
132
|
|
|
140
133
|
```text
|
|
141
134
|
/commandcode (或 /commandcode status)
|
|
142
135
|
```
|
|
143
136
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```text
|
|
147
|
-
📊 Command Code 用量 (mars-sea)
|
|
148
|
-
|
|
149
|
-
── 请求 ──────────────────────────────
|
|
150
|
-
💬 请求 992 次 / 失败 0 成功率 100%
|
|
151
|
-
💰 花费 $1.4446 ($1.44 credits)
|
|
152
|
-
🔤 Token 205.3M 入 / 808.8K 出
|
|
153
|
-
|
|
154
|
-
── 信用 ──────────────────────────────
|
|
155
|
-
💳 月额度 $8.54 (已购 $0.00 / 赠送 $0.00)
|
|
156
|
-
└ ██████████ 100%
|
|
157
|
-
|
|
158
|
-
── 窗口用量 ──────────────────────────
|
|
159
|
-
⏱ 5 小时 $0.18 / $3.00
|
|
160
|
-
└ █░░░░░░░░░ 重置 8/15/2026, 2:39:36 PM
|
|
161
|
-
📅 每周 $1.46 / $6.00
|
|
162
|
-
└ ██░░░░░░░░ 重置 8/21/2026, 7:10:57 PM
|
|
163
|
-
```
|
|
137
|
+

|
|
164
138
|
|
|
165
|
-
|
|
139
|
+
每个端点独立降级——某个端点临时失败不会影响其他数据,并会在末尾内联提示失败。
|
|
166
140
|
|
|
167
141
|
## 配置
|
|
168
142
|
|
|
169
|
-
**设置 → Command Code** 是主要配置入口:**API key** 输入框(通过凭据服务存储在 `$DSH_HOME/.credentials.yaml`,只写不回显,并显示是否已配置),以及 **API
|
|
143
|
+
**设置 → Command Code** 是主要配置入口:**API key** 输入框(通过凭据服务存储在 `$DSH_HOME/.credentials.yaml`,只写不回显,并显示是否已配置),以及 **API 地址**、**工作目录**、**请求/流超时**字段,全部写入 `llm-commandcode` 设置段。没有 key 也可以浏览模型目录。**工作目录可选**——留空即可,占位符会显示它实际使用的进程 cwd。
|
|
170
144
|
|
|
171
|
-
同一组选项也位于 `$DSH_HOME/settings.yaml
|
|
145
|
+
同一组选项也位于 `$DSH_HOME/settings.yaml`(按请求覆盖,无需重启):
|
|
172
146
|
|
|
173
147
|
```yaml
|
|
174
148
|
llm-commandcode:
|
|
@@ -177,59 +151,45 @@ llm-commandcode:
|
|
|
177
151
|
workingDir: /path/to/project # 上报给 API(项目 slug、配置块)
|
|
178
152
|
modelsCachePath: ~/.commandcode/models-cache.json
|
|
179
153
|
requestTimeoutMs: 60000 # 等待首个响应字节的最长时间(默认 60s)
|
|
180
|
-
streamIdleTimeoutMs:
|
|
154
|
+
streamIdleTimeoutMs: 300000 # 流停顿超过该时长即视为死连接(默认 300s——刻意放宽,避免切断长思考模型)
|
|
181
155
|
```
|
|
182
156
|
|
|
183
|
-
组合入口配置(`cordis.patch.yml
|
|
157
|
+
组合入口配置(`cordis.patch.yml`)接受相同的键;那里的字面量 `apiKey` 优先于凭据引用。
|
|
184
158
|
|
|
185
159
|
## 故障排查
|
|
186
160
|
|
|
187
|
-
- **`Command Code API request to .../alpha/generate failed
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
-
|
|
193
|
-
- **
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
- **Models 页面卡片显示"未配置"但请求可用** ——key 来自 `~/.commandcode/auth.json`(`cmd login` 兜底),而不是 dsh 凭据存储。把它粘贴到卡片一次即可让卡片显示为已配置;两者可以共存。
|
|
197
|
-
- **推理模型在短请求下不返回可见文本** ——推理模型(如 `deepseek/deepseek-v4-*`)会先消耗输出 token 进行推理;`maxTokens` 较小时可能在出现可见文本前就用完。这属于正常现象。
|
|
198
|
-
- **git 安装时 `dsh plugin add` 报 `allowBuilds` 错误** ——把 pnpm 打印的确切包 key(含 commit hash)复制到 `pnpm-workspace.yaml` 并重新运行(见[从 GitHub 安装](#从-github-安装))。
|
|
161
|
+
- **`Command Code API request to .../alpha/generate failed`,且不停重试** ——这是**传输层失败**:`fetch()` 根本没拿到 HTTP 响应(401/403/429 会显示 "API error")。0.1.8 起错误消息会点名**真实根因**(`ECONNREFUSED`、`ENOTFOUND`、`CERT_HAS_EXPIRED`、`socket hang up` 等)。常见原因:**需要代理**(Node 的 `fetch`/undici 不读取 `HTTP_PROXY`/`HTTPS_PROXY`——配置 dispatcher 或把 `api.commandcode.ai` 加入白名单)、**连接被中途重置/限速**(防火墙、GFW 类干扰、Wi-Fi 不稳)、**TLS 被中间人替换**(企业 MITM),或只是重试能恢复的瞬时抖动。
|
|
162
|
+
- **长回答生成到一半中断** ——0.1.8 起,adapter 会在 `requestTimeoutMs`(60s)内拿不到首字节时中止,并在流停顿超过 `streamIdleTimeoutMs`(默认 300s)时判为死连接。两者都以 `TIMEOUT` 呈现并附带停顿时长;网络慢但稳定可调大这两个值。
|
|
163
|
+
- **推理模型思考较久时"反复重连"** ——流空闲看门狗原默认 120s,比前沿推理模型(xhigh/max effort)的静默思考期还短——它们可以数分钟不吐 token,而官方 CLI 根本不设空闲上限。0.2.3 起默认改为 **300s**;若极长思考仍触发误判,在 `llm-commandcode` 配置段或设置页调大 `streamIdleTimeoutMs`。
|
|
164
|
+
- **启动崩溃:`ERR_MODULE_NOT_FOUND: Cannot find package 'dsh-commandcode-provider'`** ——patch 行的 `name` 是裸名,但 pnpm 只链接带 scope 的名字。改成 `name: "@mars-sea/dsh-commandcode-provider"`——注意**必须加引号**(不引号的 `@` 开头标量会导致 YAML 解析失败)——然后重启。
|
|
165
|
+
- **`MODEL_NOT_IN_PLAN` (403)** ——所选模型不在你的套餐内。选一个开放权重模型或升级套餐;错误会指明模型并附官方文档链接。
|
|
166
|
+
- **`MISSING_CREDENTIAL`** ——任何地方都没有 key。在设置页存一个、`export COMMANDCODE_API_KEY`、设置 `config.apiKey`,或运行 `command-code login`。没有 key 时路由与目录仍可浏览。
|
|
167
|
+
- **Models 卡片显示"未配置"但请求可用** ——key 来自 `~/.commandcode/auth.json`(`cmd login` 兜底),而非凭据存储。把它粘贴到卡片一次即可;两者可共存。
|
|
168
|
+
- **推理模型在短请求下不返回可见文本** ——它先消耗输出 token 做推理;`maxTokens` 较小时可能在可见文本前就用完。属正常现象。
|
|
169
|
+
- **git 安装时 `dsh plugin add` 报 `allowBuilds` 错误** ——把 pnpm 打印的确切包 key 复制到 `pnpm-workspace.yaml` 并重新运行(见[从 GitHub 安装](#从-github-安装))。
|
|
199
170
|
|
|
200
171
|
## 注意事项与限制
|
|
201
172
|
|
|
202
|
-
-
|
|
203
|
-
- **在含图片的会话里切换到纯文本模型会被 dsh 自身拒绝**——这是 harness 层的守卫(`dsh-host-apiproxy` 的 `selectModel
|
|
204
|
-
- **不支持 `stop`
|
|
205
|
-
- 推理块**不会**重放到后续轮次(与官方 CLI
|
|
206
|
-
-
|
|
207
|
-
- 模型目录端点是公开的;对 `/alpha/generate` 的请求需要上述 key。
|
|
173
|
+
- **图片输入按模型能力限制**:只有官方注册表标记为 Vision 的模型接受图片(见 `src/adapter.ts` 的 `KNOWN_IMAGE_MODELS`)。纯文本模型会抛 `UNSUPPORTED_CONTENT`;官方 CLI 对纯文本模型的客户端 *VISION* 副调用在此**不复现**——请改用支持 Vision 的模型。图片输入还需要 dsh 的**附件服务**。
|
|
174
|
+
- **在含图片的会话里切换到纯文本模型会被 dsh 自身拒绝**——这是 harness 层的守卫(`dsh-host-apiproxy` 的 `selectModel`),无法从插件侧放宽。本 bundle 通过客户端插件让提示更友好:把拒绝改写为「当前会话已包含图片,而模型 `<model>` 不支持图片输入;请选择支持图片的模型,或先移除会话中的图片。」(错误码与 details 原样透传)。选择带 *`Image`* 标记的模型、先移除图片,或安装图片路由 bundle(如 `@deepseek-ai/dsh-llm-image-routing`)。
|
|
175
|
+
- **不支持 `stop` 序列**(线上格式没有该字段):携带它的请求会抛 `UNSUPPORTED_OPTION`。
|
|
176
|
+
- 推理块**不会**重放到后续轮次(与官方 CLI 一致);只有带配对工具结果的工具调用会被重放。
|
|
177
|
+
- 模型目录端点是公开的;`/alpha/generate` 需要你的 key。
|
|
208
178
|
|
|
209
179
|
## 权限与隐私
|
|
210
180
|
|
|
211
|
-
本插件完全在你的 dsh profile 和你的 Command Code
|
|
212
|
-
|
|
213
|
-
- **本地文件**
|
|
214
|
-
- 仅在**最后兜底**时读取 `~/.commandcode/auth.json`(官方 CLI 登录文件)。
|
|
215
|
-
- 读写 `~/.commandcode/models-cache.json`(模型目录缓存)。
|
|
216
|
-
- 通过标准凭据 seam 从 dsh 凭据库(`$DSH_HOME/.credentials.yaml`)读取 API key——key 永不记录日志,也只会发送给 Command Code API。
|
|
217
|
-
- **网络**
|
|
218
|
-
- `GET {apiBase}/provider/v1/models` —— 公开模型目录(无需 key)。
|
|
219
|
-
- `POST {apiBase}/alpha/generate` —— 模型请求本身,使用你的 key 认证。
|
|
220
|
-
- 请求体包含你配置的 `workingDir`(项目路径,默认进程 cwd),作为 Command Code 的 `config.workingDir` 发送。
|
|
221
|
-
- **无遥测**:无分析、无追踪、无第三方端点。唯一的对外主机是 Command Code API(默认 `api.commandcode.ai`,可通过 `apiBase` 配置)。
|
|
181
|
+
本插件完全在你的 dsh profile 和你的 Command Code 账号内运行。**本地文件**:仅在最后兜底时读取 `~/.commandcode/auth.json`;读写 `~/.commandcode/models-cache.json`;通过标准凭据 seam 从 `$DSH_HOME/.credentials.yaml` 读取 key(永不记录日志)。**网络**:`GET {apiBase}/provider/v1/models`(公开目录)与 `POST {apiBase}/alpha/generate`(你的请求,已认证),请求体包含你配置的 `workingDir`。**无遥测**——唯一的对外主机是 Command Code API(默认 `api.commandcode.ai`,可通过 `apiBase` 配置)。
|
|
222
182
|
|
|
223
183
|
## 关闭 / 卸载
|
|
224
184
|
|
|
225
|
-
- **禁用**(不删除):编辑你 profile 的 `cordis.patch.yml`,注释掉(或移除)`llm-commandcode`
|
|
185
|
+
- **禁用**(不删除):编辑你 profile 的 `cordis.patch.yml`,注释掉(或移除)`llm-commandcode` 行,或设置 `disabled: true`,然后重启。
|
|
226
186
|
- **完全卸载**:
|
|
227
187
|
|
|
228
188
|
```sh
|
|
229
189
|
dsh plugin --profile web remove @mars-sea/dsh-commandcode-provider
|
|
230
190
|
```
|
|
231
191
|
|
|
232
|
-
这会移除 bundle
|
|
192
|
+
这会移除 bundle 依赖及其配置层;你在 dsh 凭据库和 `~/.commandcode/auth.json` 中的 API key 不会被改动。
|
|
233
193
|
|
|
234
194
|
## 开发
|
|
235
195
|
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -579,7 +579,7 @@ window.__ModuleLoader__.load({
|
|
|
579
579
|
requestTimeoutMs: "请求超时(毫秒)",
|
|
580
580
|
requestTimeoutMsHint: "等待响应首个字节的超时;默认 60000。",
|
|
581
581
|
streamIdleTimeoutMs: "流空闲超时(毫秒)",
|
|
582
|
-
streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认
|
|
582
|
+
streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。",
|
|
583
583
|
overridden: "已覆盖",
|
|
584
584
|
reset: "重置",
|
|
585
585
|
invalidNumber: "无效数字",
|
|
@@ -607,7 +607,7 @@ window.__ModuleLoader__.load({
|
|
|
607
607
|
requestTimeoutMs: "Request timeout (ms)",
|
|
608
608
|
requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
|
|
609
609
|
streamIdleTimeoutMs: "Stream idle timeout (ms)",
|
|
610
|
-
streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default
|
|
610
|
+
streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous — long-thinking models can stay silent for minutes).",
|
|
611
611
|
overridden: "Overridden",
|
|
612
612
|
reset: "Reset",
|
|
613
613
|
invalidNumber: "Invalid number",
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["Button","createSnapshotStore"],"sources":["../src/client/sessions.ts","../src/client/settings.ts","../src/client/section.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Friendly-error wrapper for the harness's image-session gate.\n *\n * The host rejects switching to a text-only model while the session already\n * contains images with a `model-unavailable` error\n * (`dsh-host-apiproxy`'s `session.selectModel` handler). That rejection is\n * intentional and cannot be relaxed from the plugin side — the adapter's\n * `inputModalities` is exactly what makes the guard work. What we CAN do is\n * make the error message friendlier: wrap the shared\n * `connection.api.sessions.selectModel` face so a `model-unavailable`\n * rejection shows a clear, actionable hint (with the requested model name)\n * instead of the raw English harness message.\n *\n * The wrapper is deliberately narrow: only the `model-unavailable` code is\n * rewritten, only when the message matches the image-session gate, and only\n * the message text changes — the error code and details pass through\n * untouched so any caller that switches on `error.code` keeps working.\n *\n * The wire types are spelled structurally here (not imported from\n * `@deepseek-ai/dsh-host-apiproxy`) so this client bundle does not drag an\n * extra peer dependency into the package; the shapes are stable and the\n * client build inlines them anyway.\n *\n * This module is deliberately free of React and other client-platform\n * imports so the node test runner can exercise it directly.\n */\n\n/** The `model-unavailable` error details: provider + model id. */\ninterface ModelUnavailableDetails {\n provider: string\n model: string\n}\n\n/** The narrow slice of the RPC error we need to inspect and rewrite. */\ninterface RpcErrorLike {\n code: string\n message: string\n details?: ModelUnavailableDetails\n}\n\n/**\n * The narrow slice of a unary RPC result we need to inspect and rewrite.\n * The wire shape from `sessions.selectModel` (via `AbstractApiClient.callUnary`)\n * is the full envelope `{ rpcId, result: { ok, error? } }` — the error lives\n * under `result.result`, not at the top level. `RpcResultLike` models that.\n */\ninterface RpcResultLike {\n rpcId: string\n result:\n | { ok: true; value?: unknown }\n | { ok: false; error: RpcErrorLike }\n}\n\n/** One selectModel call: payload in, envelope out. */\ntype SelectModelCall = (\n payload: { sessionId: string; provider: string; model: string; reasoningEffort?: string },\n signal?: AbortSignal,\n) => Promise<RpcResultLike>\n\n/** The shared sessions wire face we wrap. */\ninterface SessionsLike {\n selectModel: SelectModelCall\n}\n\n/** Whether a selectModel rejection is the harness's image-session gate. */\nexport function isImageSessionRejection(\n result: RpcResultLike,\n): result is RpcResultLike & { result: { ok: false; error: RpcErrorLike } } {\n return (\n !result.result.ok &&\n result.result.error.code === 'model-unavailable' &&\n result.result.error.message.includes('does not accept image input')\n )\n}\n\n/** Wrap the shared sessions API so selectModel failures read friendlier. */\nexport function withFriendlyImageError(sessions: SessionsLike): SessionsLike {\n const selectModel = sessions.selectModel.bind(sessions)\n return {\n ...sessions,\n selectModel: async (payload, signal) => {\n const result = await selectModel(payload, signal)\n if (!isImageSessionRejection(result)) return result\n const model = result.result.error.details?.model ?? payload.model\n return {\n ...result,\n result: {\n ...result.result,\n error: {\n ...result.result.error,\n message:\n `当前会话已包含图片,而模型 ${model} 不支持图片输入;`\n + '请选择支持图片的模型,或先移除会话中的图片。',\n },\n },\n }\n },\n }\n}\n\n/** The connection handle shape we read `api.sessions` from. */\nexport interface ConnectionLike {\n api: { sessions: SessionsLike }\n}\n\n/** Install the wrapper on a connection's shared sessions face. */\nexport function installFriendlyImageError(connection: ConnectionLike): void {\n connection.api.sessions = withFriendlyImageError(connection.api.sessions)\n}\n","/**\n * Browser controller for the \"Command Code\" settings page.\n *\n * The page lives at the same settings-nav level as General / Models / Plugins\n * (a `settings.section` entry, id `commandcode`). It exists because the\n * Models page renders an unknown-adapter-family card for the `commandcode`\n * provider and deliberately disables its submit — the API key cannot be\n * configured there. This page owns the connection facts the plugin resolves\n * per request:\n *\n * - API key -> written through the credentials domain under the reference\n * the plugin resolves (`apiKeyEnv`, default\n * `COMMANDCODE_API_KEY`). The literal never rides a response,\n * so the control only reports whether one is configured.\n * - API base -> the `llm-commandcode` settings namespace (`apiBase`), same\n * namespace the Models page card addresses.\n * - Working dir, request/stream timeouts -> the same namespace.\n *\n * The controller mirrors the plugin-card pattern from the harness's own\n * settings UI: it binds the `llm-commandcode` namespace through the\n * `settingsScope` service, keeps a staged draft of edits, and writes them on\n * save through `scope.set` / the credentials domain. The Host stays the\n * single fact source; the snapshot is republished after each accepted write.\n *\n * This module is deliberately free of JSX — it only produces the state face\n * the React component renders.\n */\n\nimport type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The settings namespace the plugin registers (host half, src/index.ts). */\nexport const COMMANDCODE_NS = 'llm-commandcode'\n/** Default credential reference the plugin resolves when none is named. */\nexport const DEFAULT_API_KEY_REF = 'COMMANDCODE_API_KEY'\n\n/** The narrow slice of the wire face this controller needs. */\nexport interface SettingsPageApi {\n credentials: {\n describe(request: { refs: string[] }): Promise<{\n result: { ok: true; value: { credentials: Record<string, { configured: boolean; writable: boolean }> } } | { ok: false; error: { message: string } }\n }>\n set(request: { ref: string; value: string }): Promise<{ result: { ok: true; value?: unknown } | { ok: false; error: { message: string } } }>\n }\n}\n\n/** The Host-description observable the page reads the process cwd from. */\nexport interface HostDescriptionSource {\n getSnapshot(): { cwd?: string } | undefined\n subscribe(fn: () => void): () => void\n}\n\n/** One editable text field's staged state (blank = keep stored value). */\nexport interface StagedField {\n /** Live draft text the input shows. */\n text: string\n /** Whether the user explicitly cleared the field (reset to inherited). */\n clear: boolean\n /** Whether the user layer carries this field (marks it overridden). */\n overridden: boolean\n /** Whether the staged draft fails to parse (blocks save). */\n invalid: boolean\n}\n\n/** The page's full state face, projected from the scope + drafts + credential. */\nexport interface SettingsPageState {\n /** Whether the namespace snapshot is ready. */\n available: boolean\n /** Whether the Host document accepts writes. */\n writable: boolean\n /** Whether the API key is currently configured (Host-reported). */\n apiKeyConfigured: boolean\n /** Whether the credentials domain can store the key. */\n apiKeyWritable: boolean\n /** The API key draft (write-only; starts blank, never echoes the stored key). */\n apiKey: StagedField\n /** apiBase draft. */\n apiBase: StagedField\n /** workingDir draft. */\n workingDir: StagedField\n /**\n * The working directory a blank `workingDir` resolves to: the Host\n * process cwd (`host.describe().cwd`). Shown as the field's placeholder so\n * the user sees what \"leave it empty\" means — no configuration needed.\n */\n defaultWorkingDir: string | undefined\n /** requestTimeoutMs draft. */\n requestTimeoutMs: StagedField\n /** streamIdleTimeoutMs draft. */\n streamIdleTimeoutMs: StagedField\n /** Whether any staged edit differs from the stored section. */\n dirty: boolean\n /** Whether a staged numeric field fails to parse (save blocked). */\n invalid: boolean\n /** Whether a save is in flight. */\n saving: boolean\n /** Whether the last save failed (drafts retained for correction). */\n failed: boolean\n}\n\n/** Parsed outcome of one field's draft. */\ntype Parsed = { kind: 'set'; value: string | number } | { kind: 'clear' } | { kind: 'invalid' }\n\n/** One field's staged draft (internal; the public face adds derived flags). */\ninterface Staged {\n text: string\n clear: boolean\n}\n\n/** A field conversion spec. */\ninterface FieldSpec {\n field: string\n format(value: unknown): string\n parse(text: string): Parsed\n}\n\n/** A free-text field; an empty draft clears it. */\nfunction textField(field: string): FieldSpec {\n return {\n field,\n format: (value) => (typeof value === 'string' ? value : ''),\n parse: (text) => {\n const trimmed = text.trim()\n return trimmed === '' ? { kind: 'clear' } : { kind: 'set', value: trimmed }\n },\n }\n}\n\n/** A whole-number field; an empty draft clears it, anything non-numeric blocks save. */\nfunction numberField(field: string): FieldSpec {\n return {\n field,\n format: (value) => (typeof value === 'number' ? String(value) : ''),\n parse: (text) => {\n const trimmed = text.trim()\n if (trimmed === '') return { kind: 'clear' }\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? { kind: 'set', value: parsed } : { kind: 'invalid' }\n },\n }\n}\n\n/** The fields this page edits inside the `llm-commandcode` namespace. */\nconst SECTION_FIELDS: FieldSpec[] = [\n textField('apiBase'),\n textField('workingDir'),\n numberField('requestTimeoutMs'),\n numberField('streamIdleTimeoutMs'),\n]\n\n/**\n * Controller bridging the `llm-commandcode` scope and the credentials domain\n * onto the page. Public API mirrors the harness's CardForm actions, so the\n * component stays thin.\n */\nexport class CommandCodeSettingsController {\n private readonly scope: SettingsScope<Record<string, unknown>>\n private readonly api: SettingsPageApi\n private readonly specs = new Map(SECTION_FIELDS.map((spec) => [spec.field, spec]))\n private readonly staged = new Map<string, Staged>()\n private readonly listeners = new Set<() => void>()\n private readonly disposers: Array<() => void> = []\n private disposed = false\n private defaultWorkingDir: string | undefined\n private credential = { ref: DEFAULT_API_KEY_REF, configured: false, writable: true }\n private saving = false\n private failed = false\n\n /**\n * @param scope - bound scope for the `llm-commandcode` namespace.\n * @param api - credentials wire face.\n * @param hostDescription - the Host-description observable whose `cwd` is\n * shown as the placeholder a blank `workingDir` field resolves to.\n */\n constructor(\n scope: SettingsScope<Record<string, unknown>>,\n api: SettingsPageApi,\n hostDescription?: HostDescriptionSource,\n ) {\n this.scope = scope\n this.api = api\n this.disposers.push(scope.subscribe(() => {\n this.recomputeCredentialRef()\n this.publish()\n }))\n if (hostDescription !== undefined) {\n this.defaultWorkingDir = hostDescription.getSnapshot()?.cwd\n this.disposers.push(hostDescription.subscribe(() => {\n if (this.disposed) return\n const cwd = hostDescription.getSnapshot()?.cwd\n if (cwd !== this.defaultWorkingDir) {\n this.defaultWorkingDir = cwd\n this.publish()\n }\n }))\n }\n this.recomputeCredentialRef()\n void this.readCredential()\n }\n\n /** Release every subscription held on external sources. Idempotent. */\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n for (const dispose of this.disposers) dispose()\n this.disposers.length = 0\n this.listeners.clear()\n }\n\n /**\n * The credential reference the section names, or the provider default. A\n * user who renamed `apiKeyEnv` in `settings.yaml` (or the composition\n * config) gets a page that addresses the renamed ref instead of silently\n * writing the default — mirroring the Models page's `refFor()`.\n */\n private recomputeCredentialRef(): void {\n const snapshot = this.scope.getSnapshot()\n const named = typeof snapshot.value?.apiKeyEnv === 'string' && snapshot.value.apiKeyEnv.length > 0\n ? snapshot.value.apiKeyEnv\n : DEFAULT_API_KEY_REF\n if (named === this.credential.ref) return\n this.credential = { ref: named, configured: false, writable: true }\n void this.readCredential()\n }\n\n /** Subscribe to state projections. @returns the disposer. */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** Build the current page state face. */\n state(): SettingsPageState {\n const snapshot = this.scope.getSnapshot()\n const plan = this.plan()\n return {\n available: snapshot.status === 'ready',\n writable: snapshot.writable,\n apiKeyConfigured: this.credential.configured,\n apiKeyWritable: this.credential.writable,\n apiKey: {\n text: this.staged.get('apiKey')?.text ?? '',\n clear: false,\n overridden: false,\n invalid: false,\n },\n apiBase: this.field('apiBase'),\n workingDir: this.field('workingDir'),\n defaultWorkingDir: this.defaultWorkingDir,\n requestTimeoutMs: this.field('requestTimeoutMs'),\n streamIdleTimeoutMs: this.field('streamIdleTimeoutMs'),\n dirty: plan.length > 0,\n invalid: plan.some((item) => item.run === undefined),\n saving: this.saving,\n failed: this.failed,\n }\n }\n\n /** Stage one field's draft text. */\n edit(field: string, text: string): void {\n this.staged.set(field, { text, clear: false })\n this.failed = false\n this.publish()\n }\n\n /** Reset one section field to its inherited (composition) value. */\n resetField(field: string): void {\n if (field === 'apiKey') {\n this.staged.delete('apiKey')\n this.failed = false\n this.publish()\n return\n }\n const spec = this.spec(field)\n this.staged.set(field, { text: spec.format(this.baseValue(field)), clear: true })\n this.failed = false\n this.publish()\n }\n\n /** Discard every staged edit. */\n discard(): void {\n if (this.staged.size === 0 && !this.failed) return\n this.staged.clear()\n this.failed = false\n this.publish()\n }\n\n /** Write every staged edit, then re-read the Host's accepted state. */\n async save(): Promise<void> {\n const plan = this.plan()\n if (plan.length === 0 || this.saving) return\n const runs: Array<() => Promise<boolean>> = []\n for (const item of plan) {\n if (item.run === undefined) return\n runs.push(item.run)\n }\n this.saving = true\n this.failed = false\n this.publish()\n let landed = true\n for (const run of runs) landed = (await run()) && landed\n this.saving = false\n this.failed = !landed\n if (landed) this.staged.clear()\n this.publish()\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private spec(field: string): FieldSpec {\n const spec = this.specs.get(field)\n if (spec === undefined) throw new Error(`commandcode settings page has no field ${field}`)\n return spec\n }\n\n /** One field's rendered state: draft text, whether it is user-overridden, invalid. */\n private field(field: string): StagedField {\n const spec = this.spec(field)\n const staged = this.staged.get(field)\n if (staged === undefined) {\n return {\n text: spec.format(this.sectionValue(field)),\n clear: false,\n overridden: this.stored(field),\n invalid: false,\n }\n }\n const parsed = staged.clear ? { kind: 'clear' as const } : spec.parse(staged.text)\n return {\n text: staged.text,\n clear: staged.clear,\n overridden: parsed.kind === 'set',\n invalid: parsed.kind === 'invalid',\n }\n }\n\n private sectionValue(field: string): unknown {\n return this.scope.getSnapshot().value?.[field]\n }\n\n private baseValue(field: string): unknown {\n const base = this.scope.getSnapshot().base\n return typeof base === 'object' && base !== null && !Array.isArray(base)\n ? (base as Record<string, unknown>)[field]\n : undefined\n }\n\n private userLayer(): Record<string, unknown> | undefined {\n const user = this.scope.getSnapshot().user\n return typeof user === 'object' && user !== null && !Array.isArray(user)\n ? (user as Record<string, unknown>)\n : undefined\n }\n\n private stored(field: string): boolean {\n const user = this.userLayer()\n return user !== undefined && Object.prototype.hasOwnProperty.call(user, field)\n }\n\n /**\n * The writes a save would perform, in staged order. A field whose draft is\n * not a value its spec accepts carries no write (the save refuses).\n */\n private plan(): Array<{ field: string; run: (() => Promise<boolean>) | undefined }> {\n const plan: Array<{ field: string; run: (() => Promise<boolean>) | undefined }> = []\n for (const [field, staged] of this.staged) {\n if (field === 'apiKey') {\n const value = staged.text.trim()\n if (value !== '') {\n plan.push({ field, run: () => this.writeKey(value) })\n }\n continue\n }\n const spec = this.spec(field)\n if (staged.clear) {\n if (this.stored(field)) plan.push({ field, run: () => this.clear(field) })\n continue\n }\n if (staged.text === spec.format(this.sectionValue(field))) continue\n const parsed = spec.parse(staged.text)\n if (parsed.kind === 'invalid') plan.push({ field, run: undefined })\n else if (parsed.kind === 'clear') plan.push({ field, run: () => this.clear(field) })\n else plan.push({ field, run: () => this.store(field, parsed.value) })\n }\n return plan\n }\n\n private async clear(field: string): Promise<boolean> {\n await this.scope.unset(field)\n return !this.stored(field)\n }\n\n private async store(field: string, value: string | number): Promise<boolean> {\n await this.scope.set(field, value)\n return this.userLayer()?.[field] === value\n }\n\n /** Write the staged key, then re-read whether the Host now holds one. */\n private async writeKey(value: string): Promise<boolean> {\n try {\n const response = await this.api.credentials.set({ ref: this.credential.ref, value })\n if (!response.result.ok) return false\n } catch {\n return false\n }\n await this.readCredential()\n return this.credential.configured\n }\n\n /** Ask the credentials domain about the reference this page writes. */\n private async readCredential(): Promise<void> {\n const ref = this.credential.ref\n let response: Awaited<ReturnType<SettingsPageApi['credentials']['describe']>>\n try {\n response = await this.api.credentials.describe({ refs: [ref] })\n } catch {\n return\n }\n if (!response.result.ok) return\n const view = response.result.value.credentials[ref]\n const next = {\n ref,\n configured: view?.configured ?? false,\n writable: view?.writable ?? true,\n }\n if (next.configured === this.credential.configured && next.writable === this.credential.writable) return\n this.credential = next\n this.publish()\n }\n\n private publish(): void {\n if (this.disposed) return\n for (const listener of this.listeners) listener()\n }\n}\n","/**\n * React component for the \"Command Code\" settings page (browser half).\n *\n * Renders as a `settings.section` entry — a page at the same settings-nav\n * level as General / Models / Plugins. The shell supplies the nav row and\n * renders this body inside the content column. All copy comes from the\n * `settings.commandcode` locale namespace; all state comes from the\n * `CommandCodeSettingsController` injected by the slot registration.\n *\n * The layout mirrors the harness's settings pages: a max-width content\n * column, labelled fields with hints, a reset affordance, and a\n * save/discard footer. Styles are injected once by the client entry\n * (see src/client/index.ts) and class-prefixed `cc-` to stay local.\n */\n\nimport { Button } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { Translate } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SettingsCommandCodeKey } from './locales.ts'\nimport type { SettingsPageState, StagedField } from './settings.ts'\n\n/** Props composed by the slot registration: locale seat + injected face. */\nexport interface CommandCodeSettingsProps {\n t: Translate<SettingsCommandCodeKey>\n useCommandCodeSettings<T>(selector: (state: SettingsPageState) => T): T\n edit(field: string, text: string): void\n resetField(field: string): void\n save(): void\n discard(): void\n}\n\n/** One labelled field row in the page body. */\nfunction Field({\n id,\n label,\n hint,\n state,\n disabled,\n numeric,\n placeholder,\n onEdit,\n onReset,\n t,\n}: {\n id: string\n label: string\n hint: string\n state: StagedField\n disabled: boolean\n numeric?: boolean\n placeholder?: string | undefined\n onEdit(text: string): void\n onReset(): void\n t: Translate<SettingsCommandCodeKey>\n}) {\n return (\n <div className=\"cc-field\">\n <div className=\"cc-fieldHead\">\n <label className=\"cc-label\" htmlFor={id}>{label}</label>\n <span className=\"cc-badges\">\n {state.overridden ? <span className=\"cc-badge\">{t('overridden')}</span> : null}\n <button type=\"button\" className=\"cc-reset\" disabled={disabled} onClick={onReset}>{t('reset')}</button>\n </span>\n </div>\n <input\n id={id}\n className={state.invalid ? 'cc-input cc-inputInvalid' : 'cc-input'}\n type=\"text\"\n inputMode={numeric ? 'numeric' : undefined}\n value={state.text}\n placeholder={placeholder}\n disabled={disabled}\n onChange={(event) => onEdit(event.target.value)}\n />\n <p className={state.invalid ? 'cc-invalid' : 'cc-hint'}>\n {state.invalid ? t('invalidNumber') : hint}\n </p>\n </div>\n )\n}\n\n/** The API-key control: write-only, reports configured state, never echoes the key. */\nfunction SecretKeyField({\n label,\n hint,\n state,\n disabled,\n configured,\n configuredLabel,\n unconfiguredLabel,\n onEdit,\n}: {\n label: string\n hint: string\n state: StagedField\n disabled: boolean\n configured: boolean\n configuredLabel: string\n unconfiguredLabel: string\n onEdit(text: string): void\n}) {\n return (\n <div className=\"cc-field\">\n <div className=\"cc-fieldHead\">\n <label className=\"cc-label\" htmlFor=\"cc-api-key\">{label}</label>\n <span className=\"cc-badges\">\n <span className={configured ? 'cc-badge' : 'cc-badgeMuted'}>\n {configured ? configuredLabel : unconfiguredLabel}\n </span>\n </span>\n </div>\n <input\n id=\"cc-api-key\"\n className=\"cc-input\"\n type=\"password\"\n autoComplete=\"off\"\n value={state.text}\n disabled={disabled}\n onChange={(event) => onEdit(event.target.value)}\n />\n <p className=\"cc-hint\">{hint}</p>\n </div>\n )\n}\n\n/** The settings page body: connection facts for the Command Code provider. */\nexport function CommandCodeSettingsPage(props: CommandCodeSettingsProps) {\n const { t } = props\n const state = props.useCommandCodeSettings((snapshot) => snapshot)\n const disabled = !state.writable\n const keyLocked = !state.apiKeyWritable\n return (\n <section className=\"cc-section\" aria-label={t('title')}>\n <h2 className=\"cc-title\">{t('title')}</h2>\n <p className=\"cc-intro\">{t('intro')}</p>\n {!state.writable ? <p className=\"cc-readOnly\" role=\"status\">{t('readOnly')}</p> : null}\n <div className=\"cc-card\">\n <SecretKeyField\n label={t('apiKey')}\n hint={keyLocked ? t('apiKeyLocked') : t('apiKeyHint')}\n state={state.apiKey}\n disabled={disabled || keyLocked}\n configured={state.apiKeyConfigured}\n configuredLabel={t('apiKeySet')}\n unconfiguredLabel={t('apiKeyUnset')}\n onEdit={(text) => props.edit('apiKey', text)}\n />\n <Field\n id=\"cc-api-base\"\n label={t('apiBase')}\n hint={t('apiBaseHint')}\n state={state.apiBase}\n disabled={disabled}\n onEdit={(text) => props.edit('apiBase', text)}\n onReset={() => props.resetField('apiBase')}\n t={t}\n />\n <Field\n id=\"cc-working-dir\"\n label={t('workingDir')}\n hint={t('workingDirHint')}\n state={state.workingDir}\n disabled={disabled}\n placeholder={state.defaultWorkingDir}\n onEdit={(text) => props.edit('workingDir', text)}\n onReset={() => props.resetField('workingDir')}\n t={t}\n />\n <Field\n id=\"cc-request-timeout\"\n label={t('requestTimeoutMs')}\n hint={t('requestTimeoutMsHint')}\n state={state.requestTimeoutMs}\n disabled={disabled}\n numeric\n onEdit={(text) => props.edit('requestTimeoutMs', text)}\n onReset={() => props.resetField('requestTimeoutMs')}\n t={t}\n />\n <Field\n id=\"cc-stream-idle-timeout\"\n label={t('streamIdleTimeoutMs')}\n hint={t('streamIdleTimeoutMsHint')}\n state={state.streamIdleTimeoutMs}\n disabled={disabled}\n numeric\n onEdit={(text) => props.edit('streamIdleTimeoutMs', text)}\n onReset={() => props.resetField('streamIdleTimeoutMs')}\n t={t}\n />\n </div>\n <div className=\"cc-footer\">\n {state.failed ? <p className=\"cc-failed\" role=\"status\">{t('saveFailed')}</p> : null}\n <Button variant=\"ghost\" size=\"sm\" disabled={!state.dirty || state.saving} onClick={props.discard}>\n {t('discard')}\n </Button>\n <Button\n variant=\"primary\"\n size=\"sm\"\n disabled={!state.dirty || state.invalid || state.saving}\n onClick={props.save}\n >\n {t(state.saving ? 'saving' : 'save')}\n </Button>\n </div>\n </section>\n )\n}\n","/**\n * Locale copy for the \"Command Code\" settings page, and the declaration that\n * merges the page's namespace into the framework's `LocaleNamespaceMap` so\n * `ctx.locale.register` / `ctx.slots.register(..., { locale })` are typed.\n *\n * zh is the source of truth for the key set (repo convention); en must carry\n * the exact same keys — a mismatch is a compile error at the register site.\n */\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Copy of the Command Code settings page. */\n 'settings.commandcode': SettingsCommandCodeKey\n }\n}\n\n/** Dictionary keys of the Command Code settings page. */\nexport type SettingsCommandCodeKey =\n | 'nav'\n | 'title'\n | 'intro'\n | 'apiKey'\n | 'apiKeyHint'\n | 'apiKeySet'\n | 'apiKeyUnset'\n | 'apiKeyLocked'\n | 'apiBase'\n | 'apiBaseHint'\n | 'workingDir'\n | 'workingDirHint'\n | 'requestTimeoutMs'\n | 'requestTimeoutMsHint'\n | 'streamIdleTimeoutMs'\n | 'streamIdleTimeoutMsHint'\n | 'overridden'\n | 'reset'\n | 'invalidNumber'\n | 'readOnly'\n | 'unsaved'\n | 'save'\n | 'saving'\n | 'saveFailed'\n | 'discard'\n | 'cancel'\n\nexport const zh: Record<SettingsCommandCodeKey, string> = {\n nav: 'Command Code',\n title: 'Command Code',\n intro:\n '配置 Command Code Provider 连接。API 密钥仅保存在本机凭据服务中,不会回显;'\n + '其他字段写入用户设置,下次请求即生效。',\n apiKey: 'API 密钥',\n apiKeyHint: '在 commandcode.ai 控制台创建。留空保存不会覆盖已存储的密钥。',\n apiKeySet: '已配置',\n apiKeyUnset: '未配置',\n apiKeyLocked: '密钥由只读来源提供',\n apiBase: 'API 地址',\n apiBaseHint: '默认 https://api.commandcode.ai,一般无需修改。',\n workingDir: '工作目录',\n workingDirHint: '可选。留空时使用占位符显示的进程工作目录;仅在需要固定路径时填写。',\n requestTimeoutMs: '请求超时(毫秒)',\n requestTimeoutMsHint: '等待响应首个字节的超时;默认 60000。',\n streamIdleTimeoutMs: '流空闲超时(毫秒)',\n streamIdleTimeoutMsHint: '生成流停滞多久视为断连;默认 120000。',\n overridden: '已覆盖',\n reset: '重置',\n invalidNumber: '无效数字',\n readOnly: '当前配置为只读。',\n unsaved: '未保存',\n save: '保存',\n saving: '保存中',\n saveFailed: '保存失败,请重试。',\n discard: '放弃',\n cancel: '取消',\n}\n\nexport const en: Record<SettingsCommandCodeKey, string> = {\n nav: 'Command Code',\n title: 'Command Code',\n intro:\n 'Configure the Command Code Provider connection. The API key is stored only'\n + ' in the local credential service and never echoed; other fields are written'\n + ' to user settings and take effect on the next request.',\n apiKey: 'API key',\n apiKeyHint: 'Create one in the commandcode.ai console. Saving with this field'\n + ' blank keeps the stored key.',\n apiKeySet: 'Configured',\n apiKeyUnset: 'Not configured',\n apiKeyLocked: 'Key provided by a read-only source',\n apiBase: 'API base URL',\n apiBaseHint: 'Defaults to https://api.commandcode.ai; usually leave as-is.',\n workingDir: 'Working directory',\n workingDirHint: 'Optional. Leave blank to use the process cwd shown as the'\n + ' placeholder; fill in only to pin a specific path.',\n requestTimeoutMs: 'Request timeout (ms)',\n requestTimeoutMsHint: 'Time to wait for the first response byte; default 60000.',\n streamIdleTimeoutMs: 'Stream idle timeout (ms)',\n streamIdleTimeoutMsHint: 'How long a stalled stream is treated as dead; default 120000.',\n overridden: 'Overridden',\n reset: 'Reset',\n invalidNumber: 'Invalid number',\n readOnly: 'Settings are read-only.',\n unsaved: 'Unsaved',\n save: 'Save',\n saving: 'Saving',\n saveFailed: 'Save failed, please retry.',\n discard: 'Discard',\n cancel: 'Cancel',\n}\n","/**\n * Browser half of the dsh-commandcode-provider bundle.\n *\n * Two responsibilities:\n *\n * 1. A \"Command Code\" settings page (a `settings.section` entry at the same\n * nav level as General / Models / Plugins). The Models page renders an\n * unknown-adapter-family card for the `commandcode` provider and disables\n * its submit, so the API key cannot be configured there; this page is the\n * dedicated surface. It writes the API key through the credentials domain\n * (the `COMMANDCODE_API_KEY` reference the plugin resolves) and the\n * connection facts through the `llm-commandcode` settings namespace, so a\n * saved key or endpoint reaches the very next request.\n *\n * 2. The friendly-error wrapper for the harness's image-session gate — see\n * `./sessions.ts`. The wrapper is deliberately narrow: only the\n * `model-unavailable` code is rewritten, only when the message matches the\n * image-session gate, and only the message text changes.\n *\n * The wire types are spelled structurally in `./sessions.ts` (not imported\n * from `@deepseek-ai/dsh-host-apiproxy`) so this client bundle does not drag\n * an extra peer dependency into the package; the shapes are stable and the\n * client build inlines them anyway.\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only imports that pull in the client-service augmentations\n// (`slots`/`remote`/`locale` on Context) and the `settings.section` SlotMap\n// entry (`settingsScope` arrives through dsh-client-ui-settings).\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { installFriendlyImageError } from './sessions.ts'\nimport { CommandCodeSettingsController, COMMANDCODE_NS, type SettingsPageState } from './settings.ts'\nimport { CommandCodeSettingsPage } from './section.tsx'\nimport { zh, en } from './locales.ts'\n\nexport { isImageSessionRejection, withFriendlyImageError } from './sessions.ts'\nimport type { ConnectionLike } from './sessions.ts'\n\n/** CSS for the settings page, injected once (harness bundle convention). */\nconst PAGE_CSS = `\n.cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}\n.cc-title{margin:0;font-size:18px;font-weight:600}\n.cc-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:1.5}\n.cc-readOnly{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}\n.cc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:4px 16px}\n.cc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}\n.cc-field+.cc-field{border-top:1px solid var(--dsw-alias-border-l2)}\n.cc-fieldHead{align-items:center;gap:8px;display:flex}\n.cc-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}\n.cc-badges{align-items:center;gap:8px;display:inline-flex}\n.cc-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}\n.cc-badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}\n.cc-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}\n.cc-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}\n.cc-reset:disabled{cursor:default;opacity:.5}\n.cc-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}\n.cc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}\n.cc-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}\n.cc-inputInvalid{border-color:var(--dsw-alias-label-error)}\n.cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}\n.cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}\n.cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}\n.cc-failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}\n`\n\n/** Inject the page stylesheet once (idempotent per tag). */\nfunction injectPageCss(): void {\n if (typeof document === 'undefined') return\n const id = '@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css'\n if (document.querySelector(`style[data-plugin-css=\"${id}\"]`) !== null) return\n const tag = document.createElement('style')\n tag.dataset.plugin = '@mars-sea/dsh-commandcode-provider'\n tag.dataset.pluginCss = id\n tag.textContent = PAGE_CSS\n document.head.appendChild(tag)\n}\n\n/**\n * Client plugin body. Gates on the services the settings page needs\n * (`slots`, `locale`, `connection`, `remote`, `settingsScope`) plus the\n * `connection` used by the friendly-error wrapper — the same inject list the\n * harness's own settings-surface plugins declare.\n */\nexport function apply(ctx: Context): void {\n injectPageCss()\n\n // Friendly image-gate error wrapper (unchanged behaviour).\n const connection = ctx.get('connection') as ConnectionLike | undefined\n if (connection !== undefined) {\n installFriendlyImageError(connection)\n }\n\n // The \"Command Code\" settings page: register the section once the\n // `settings.section` declaration is on the ledger (ui-settings-general\n // owns the shell; registration order relative to it is not constrained —\n // `slots.inject` waits for the declaration).\n ctx.effect(() => ctx.locale.register('settings.commandcode', { zh, en }), 'dsh-commandcode-provider: page copy')\n\n const api = ctx.get('connection').api\n const hostDescription = ctx.get('connection').hostDescription\n const scope = ctx.settingsScope.bind<Record<string, unknown>>({ namespace: COMMANDCODE_NS })\n const controller = new CommandCodeSettingsController(scope, { credentials: api.credentials }, hostDescription)\n ctx.effect(() => () => controller.dispose(), 'dsh-commandcode-provider: settings controller')\n const store = createSnapshotStore<SettingsPageState>(controller.state())\n controller.subscribe(() => store.set(controller.state()))\n const injected = () => ({\n hooks: { commandCodeSettings: store },\n edit: (field: string, text: string) => controller.edit(field, text),\n resetField: (field: string) => controller.resetField(field),\n save: () => void controller.save(),\n discard: () => controller.discard(),\n })\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'commandcode',\n order: 12,\n label: () => ctx.locale.bind('settings.commandcode')('nav'),\n locale: 'settings.commandcode',\n inject: injected,\n }, CommandCodeSettingsPage))\n}\n\nexport const inject: readonly string[] = [\n 'slots',\n 'locale',\n 'connection',\n 'remote',\n 'settingsScope',\n]\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["Button","createSnapshotStore"],"sources":["../src/client/sessions.ts","../src/client/settings.ts","../src/client/section.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Friendly-error wrapper for the harness's image-session gate.\n *\n * The host rejects switching to a text-only model while the session already\n * contains images with a `model-unavailable` error\n * (`dsh-host-apiproxy`'s `session.selectModel` handler). That rejection is\n * intentional and cannot be relaxed from the plugin side — the adapter's\n * `inputModalities` is exactly what makes the guard work. What we CAN do is\n * make the error message friendlier: wrap the shared\n * `connection.api.sessions.selectModel` face so a `model-unavailable`\n * rejection shows a clear, actionable hint (with the requested model name)\n * instead of the raw English harness message.\n *\n * The wrapper is deliberately narrow: only the `model-unavailable` code is\n * rewritten, only when the message matches the image-session gate, and only\n * the message text changes — the error code and details pass through\n * untouched so any caller that switches on `error.code` keeps working.\n *\n * The wire types are spelled structurally here (not imported from\n * `@deepseek-ai/dsh-host-apiproxy`) so this client bundle does not drag an\n * extra peer dependency into the package; the shapes are stable and the\n * client build inlines them anyway.\n *\n * This module is deliberately free of React and other client-platform\n * imports so the node test runner can exercise it directly.\n */\n\n/** The `model-unavailable` error details: provider + model id. */\ninterface ModelUnavailableDetails {\n provider: string\n model: string\n}\n\n/** The narrow slice of the RPC error we need to inspect and rewrite. */\ninterface RpcErrorLike {\n code: string\n message: string\n details?: ModelUnavailableDetails\n}\n\n/**\n * The narrow slice of a unary RPC result we need to inspect and rewrite.\n * The wire shape from `sessions.selectModel` (via `AbstractApiClient.callUnary`)\n * is the full envelope `{ rpcId, result: { ok, error? } }` — the error lives\n * under `result.result`, not at the top level. `RpcResultLike` models that.\n */\ninterface RpcResultLike {\n rpcId: string\n result:\n | { ok: true; value?: unknown }\n | { ok: false; error: RpcErrorLike }\n}\n\n/** One selectModel call: payload in, envelope out. */\ntype SelectModelCall = (\n payload: { sessionId: string; provider: string; model: string; reasoningEffort?: string },\n signal?: AbortSignal,\n) => Promise<RpcResultLike>\n\n/** The shared sessions wire face we wrap. */\ninterface SessionsLike {\n selectModel: SelectModelCall\n}\n\n/** Whether a selectModel rejection is the harness's image-session gate. */\nexport function isImageSessionRejection(\n result: RpcResultLike,\n): result is RpcResultLike & { result: { ok: false; error: RpcErrorLike } } {\n return (\n !result.result.ok &&\n result.result.error.code === 'model-unavailable' &&\n result.result.error.message.includes('does not accept image input')\n )\n}\n\n/** Wrap the shared sessions API so selectModel failures read friendlier. */\nexport function withFriendlyImageError(sessions: SessionsLike): SessionsLike {\n const selectModel = sessions.selectModel.bind(sessions)\n return {\n ...sessions,\n selectModel: async (payload, signal) => {\n const result = await selectModel(payload, signal)\n if (!isImageSessionRejection(result)) return result\n const model = result.result.error.details?.model ?? payload.model\n return {\n ...result,\n result: {\n ...result.result,\n error: {\n ...result.result.error,\n message:\n `当前会话已包含图片,而模型 ${model} 不支持图片输入;`\n + '请选择支持图片的模型,或先移除会话中的图片。',\n },\n },\n }\n },\n }\n}\n\n/** The connection handle shape we read `api.sessions` from. */\nexport interface ConnectionLike {\n api: { sessions: SessionsLike }\n}\n\n/** Install the wrapper on a connection's shared sessions face. */\nexport function installFriendlyImageError(connection: ConnectionLike): void {\n connection.api.sessions = withFriendlyImageError(connection.api.sessions)\n}\n","/**\n * Browser controller for the \"Command Code\" settings page.\n *\n * The page lives at the same settings-nav level as General / Models / Plugins\n * (a `settings.section` entry, id `commandcode`). It exists because the\n * Models page renders an unknown-adapter-family card for the `commandcode`\n * provider and deliberately disables its submit — the API key cannot be\n * configured there. This page owns the connection facts the plugin resolves\n * per request:\n *\n * - API key -> written through the credentials domain under the reference\n * the plugin resolves (`apiKeyEnv`, default\n * `COMMANDCODE_API_KEY`). The literal never rides a response,\n * so the control only reports whether one is configured.\n * - API base -> the `llm-commandcode` settings namespace (`apiBase`), same\n * namespace the Models page card addresses.\n * - Working dir, request/stream timeouts -> the same namespace.\n *\n * The controller mirrors the plugin-card pattern from the harness's own\n * settings UI: it binds the `llm-commandcode` namespace through the\n * `settingsScope` service, keeps a staged draft of edits, and writes them on\n * save through `scope.set` / the credentials domain. The Host stays the\n * single fact source; the snapshot is republished after each accepted write.\n *\n * This module is deliberately free of JSX — it only produces the state face\n * the React component renders.\n */\n\nimport type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The settings namespace the plugin registers (host half, src/index.ts). */\nexport const COMMANDCODE_NS = 'llm-commandcode'\n/** Default credential reference the plugin resolves when none is named. */\nexport const DEFAULT_API_KEY_REF = 'COMMANDCODE_API_KEY'\n\n/** The narrow slice of the wire face this controller needs. */\nexport interface SettingsPageApi {\n credentials: {\n describe(request: { refs: string[] }): Promise<{\n result: { ok: true; value: { credentials: Record<string, { configured: boolean; writable: boolean }> } } | { ok: false; error: { message: string } }\n }>\n set(request: { ref: string; value: string }): Promise<{ result: { ok: true; value?: unknown } | { ok: false; error: { message: string } } }>\n }\n}\n\n/** The Host-description observable the page reads the process cwd from. */\nexport interface HostDescriptionSource {\n getSnapshot(): { cwd?: string } | undefined\n subscribe(fn: () => void): () => void\n}\n\n/** One editable text field's staged state (blank = keep stored value). */\nexport interface StagedField {\n /** Live draft text the input shows. */\n text: string\n /** Whether the user explicitly cleared the field (reset to inherited). */\n clear: boolean\n /** Whether the user layer carries this field (marks it overridden). */\n overridden: boolean\n /** Whether the staged draft fails to parse (blocks save). */\n invalid: boolean\n}\n\n/** The page's full state face, projected from the scope + drafts + credential. */\nexport interface SettingsPageState {\n /** Whether the namespace snapshot is ready. */\n available: boolean\n /** Whether the Host document accepts writes. */\n writable: boolean\n /** Whether the API key is currently configured (Host-reported). */\n apiKeyConfigured: boolean\n /** Whether the credentials domain can store the key. */\n apiKeyWritable: boolean\n /** The API key draft (write-only; starts blank, never echoes the stored key). */\n apiKey: StagedField\n /** apiBase draft. */\n apiBase: StagedField\n /** workingDir draft. */\n workingDir: StagedField\n /**\n * The working directory a blank `workingDir` resolves to: the Host\n * process cwd (`host.describe().cwd`). Shown as the field's placeholder so\n * the user sees what \"leave it empty\" means — no configuration needed.\n */\n defaultWorkingDir: string | undefined\n /** requestTimeoutMs draft. */\n requestTimeoutMs: StagedField\n /** streamIdleTimeoutMs draft. */\n streamIdleTimeoutMs: StagedField\n /** Whether any staged edit differs from the stored section. */\n dirty: boolean\n /** Whether a staged numeric field fails to parse (save blocked). */\n invalid: boolean\n /** Whether a save is in flight. */\n saving: boolean\n /** Whether the last save failed (drafts retained for correction). */\n failed: boolean\n}\n\n/** Parsed outcome of one field's draft. */\ntype Parsed = { kind: 'set'; value: string | number } | { kind: 'clear' } | { kind: 'invalid' }\n\n/** One field's staged draft (internal; the public face adds derived flags). */\ninterface Staged {\n text: string\n clear: boolean\n}\n\n/** A field conversion spec. */\ninterface FieldSpec {\n field: string\n format(value: unknown): string\n parse(text: string): Parsed\n}\n\n/** A free-text field; an empty draft clears it. */\nfunction textField(field: string): FieldSpec {\n return {\n field,\n format: (value) => (typeof value === 'string' ? value : ''),\n parse: (text) => {\n const trimmed = text.trim()\n return trimmed === '' ? { kind: 'clear' } : { kind: 'set', value: trimmed }\n },\n }\n}\n\n/** A whole-number field; an empty draft clears it, anything non-numeric blocks save. */\nfunction numberField(field: string): FieldSpec {\n return {\n field,\n format: (value) => (typeof value === 'number' ? String(value) : ''),\n parse: (text) => {\n const trimmed = text.trim()\n if (trimmed === '') return { kind: 'clear' }\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? { kind: 'set', value: parsed } : { kind: 'invalid' }\n },\n }\n}\n\n/** The fields this page edits inside the `llm-commandcode` namespace. */\nconst SECTION_FIELDS: FieldSpec[] = [\n textField('apiBase'),\n textField('workingDir'),\n numberField('requestTimeoutMs'),\n numberField('streamIdleTimeoutMs'),\n]\n\n/**\n * Controller bridging the `llm-commandcode` scope and the credentials domain\n * onto the page. Public API mirrors the harness's CardForm actions, so the\n * component stays thin.\n */\nexport class CommandCodeSettingsController {\n private readonly scope: SettingsScope<Record<string, unknown>>\n private readonly api: SettingsPageApi\n private readonly specs = new Map(SECTION_FIELDS.map((spec) => [spec.field, spec]))\n private readonly staged = new Map<string, Staged>()\n private readonly listeners = new Set<() => void>()\n private readonly disposers: Array<() => void> = []\n private disposed = false\n private defaultWorkingDir: string | undefined\n private credential = { ref: DEFAULT_API_KEY_REF, configured: false, writable: true }\n private saving = false\n private failed = false\n\n /**\n * @param scope - bound scope for the `llm-commandcode` namespace.\n * @param api - credentials wire face.\n * @param hostDescription - the Host-description observable whose `cwd` is\n * shown as the placeholder a blank `workingDir` field resolves to.\n */\n constructor(\n scope: SettingsScope<Record<string, unknown>>,\n api: SettingsPageApi,\n hostDescription?: HostDescriptionSource,\n ) {\n this.scope = scope\n this.api = api\n this.disposers.push(scope.subscribe(() => {\n this.recomputeCredentialRef()\n this.publish()\n }))\n if (hostDescription !== undefined) {\n this.defaultWorkingDir = hostDescription.getSnapshot()?.cwd\n this.disposers.push(hostDescription.subscribe(() => {\n if (this.disposed) return\n const cwd = hostDescription.getSnapshot()?.cwd\n if (cwd !== this.defaultWorkingDir) {\n this.defaultWorkingDir = cwd\n this.publish()\n }\n }))\n }\n this.recomputeCredentialRef()\n void this.readCredential()\n }\n\n /** Release every subscription held on external sources. Idempotent. */\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n for (const dispose of this.disposers) dispose()\n this.disposers.length = 0\n this.listeners.clear()\n }\n\n /**\n * The credential reference the section names, or the provider default. A\n * user who renamed `apiKeyEnv` in `settings.yaml` (or the composition\n * config) gets a page that addresses the renamed ref instead of silently\n * writing the default — mirroring the Models page's `refFor()`.\n */\n private recomputeCredentialRef(): void {\n const snapshot = this.scope.getSnapshot()\n const named = typeof snapshot.value?.apiKeyEnv === 'string' && snapshot.value.apiKeyEnv.length > 0\n ? snapshot.value.apiKeyEnv\n : DEFAULT_API_KEY_REF\n if (named === this.credential.ref) return\n this.credential = { ref: named, configured: false, writable: true }\n void this.readCredential()\n }\n\n /** Subscribe to state projections. @returns the disposer. */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** Build the current page state face. */\n state(): SettingsPageState {\n const snapshot = this.scope.getSnapshot()\n const plan = this.plan()\n return {\n available: snapshot.status === 'ready',\n writable: snapshot.writable,\n apiKeyConfigured: this.credential.configured,\n apiKeyWritable: this.credential.writable,\n apiKey: {\n text: this.staged.get('apiKey')?.text ?? '',\n clear: false,\n overridden: false,\n invalid: false,\n },\n apiBase: this.field('apiBase'),\n workingDir: this.field('workingDir'),\n defaultWorkingDir: this.defaultWorkingDir,\n requestTimeoutMs: this.field('requestTimeoutMs'),\n streamIdleTimeoutMs: this.field('streamIdleTimeoutMs'),\n dirty: plan.length > 0,\n invalid: plan.some((item) => item.run === undefined),\n saving: this.saving,\n failed: this.failed,\n }\n }\n\n /** Stage one field's draft text. */\n edit(field: string, text: string): void {\n this.staged.set(field, { text, clear: false })\n this.failed = false\n this.publish()\n }\n\n /** Reset one section field to its inherited (composition) value. */\n resetField(field: string): void {\n if (field === 'apiKey') {\n this.staged.delete('apiKey')\n this.failed = false\n this.publish()\n return\n }\n const spec = this.spec(field)\n this.staged.set(field, { text: spec.format(this.baseValue(field)), clear: true })\n this.failed = false\n this.publish()\n }\n\n /** Discard every staged edit. */\n discard(): void {\n if (this.staged.size === 0 && !this.failed) return\n this.staged.clear()\n this.failed = false\n this.publish()\n }\n\n /** Write every staged edit, then re-read the Host's accepted state. */\n async save(): Promise<void> {\n const plan = this.plan()\n if (plan.length === 0 || this.saving) return\n const runs: Array<() => Promise<boolean>> = []\n for (const item of plan) {\n if (item.run === undefined) return\n runs.push(item.run)\n }\n this.saving = true\n this.failed = false\n this.publish()\n let landed = true\n for (const run of runs) landed = (await run()) && landed\n this.saving = false\n this.failed = !landed\n if (landed) this.staged.clear()\n this.publish()\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private spec(field: string): FieldSpec {\n const spec = this.specs.get(field)\n if (spec === undefined) throw new Error(`commandcode settings page has no field ${field}`)\n return spec\n }\n\n /** One field's rendered state: draft text, whether it is user-overridden, invalid. */\n private field(field: string): StagedField {\n const spec = this.spec(field)\n const staged = this.staged.get(field)\n if (staged === undefined) {\n return {\n text: spec.format(this.sectionValue(field)),\n clear: false,\n overridden: this.stored(field),\n invalid: false,\n }\n }\n const parsed = staged.clear ? { kind: 'clear' as const } : spec.parse(staged.text)\n return {\n text: staged.text,\n clear: staged.clear,\n overridden: parsed.kind === 'set',\n invalid: parsed.kind === 'invalid',\n }\n }\n\n private sectionValue(field: string): unknown {\n return this.scope.getSnapshot().value?.[field]\n }\n\n private baseValue(field: string): unknown {\n const base = this.scope.getSnapshot().base\n return typeof base === 'object' && base !== null && !Array.isArray(base)\n ? (base as Record<string, unknown>)[field]\n : undefined\n }\n\n private userLayer(): Record<string, unknown> | undefined {\n const user = this.scope.getSnapshot().user\n return typeof user === 'object' && user !== null && !Array.isArray(user)\n ? (user as Record<string, unknown>)\n : undefined\n }\n\n private stored(field: string): boolean {\n const user = this.userLayer()\n return user !== undefined && Object.prototype.hasOwnProperty.call(user, field)\n }\n\n /**\n * The writes a save would perform, in staged order. A field whose draft is\n * not a value its spec accepts carries no write (the save refuses).\n */\n private plan(): Array<{ field: string; run: (() => Promise<boolean>) | undefined }> {\n const plan: Array<{ field: string; run: (() => Promise<boolean>) | undefined }> = []\n for (const [field, staged] of this.staged) {\n if (field === 'apiKey') {\n const value = staged.text.trim()\n if (value !== '') {\n plan.push({ field, run: () => this.writeKey(value) })\n }\n continue\n }\n const spec = this.spec(field)\n if (staged.clear) {\n if (this.stored(field)) plan.push({ field, run: () => this.clear(field) })\n continue\n }\n if (staged.text === spec.format(this.sectionValue(field))) continue\n const parsed = spec.parse(staged.text)\n if (parsed.kind === 'invalid') plan.push({ field, run: undefined })\n else if (parsed.kind === 'clear') plan.push({ field, run: () => this.clear(field) })\n else plan.push({ field, run: () => this.store(field, parsed.value) })\n }\n return plan\n }\n\n private async clear(field: string): Promise<boolean> {\n await this.scope.unset(field)\n return !this.stored(field)\n }\n\n private async store(field: string, value: string | number): Promise<boolean> {\n await this.scope.set(field, value)\n return this.userLayer()?.[field] === value\n }\n\n /** Write the staged key, then re-read whether the Host now holds one. */\n private async writeKey(value: string): Promise<boolean> {\n try {\n const response = await this.api.credentials.set({ ref: this.credential.ref, value })\n if (!response.result.ok) return false\n } catch {\n return false\n }\n await this.readCredential()\n return this.credential.configured\n }\n\n /** Ask the credentials domain about the reference this page writes. */\n private async readCredential(): Promise<void> {\n const ref = this.credential.ref\n let response: Awaited<ReturnType<SettingsPageApi['credentials']['describe']>>\n try {\n response = await this.api.credentials.describe({ refs: [ref] })\n } catch {\n return\n }\n if (!response.result.ok) return\n const view = response.result.value.credentials[ref]\n const next = {\n ref,\n configured: view?.configured ?? false,\n writable: view?.writable ?? true,\n }\n if (next.configured === this.credential.configured && next.writable === this.credential.writable) return\n this.credential = next\n this.publish()\n }\n\n private publish(): void {\n if (this.disposed) return\n for (const listener of this.listeners) listener()\n }\n}\n","/**\n * React component for the \"Command Code\" settings page (browser half).\n *\n * Renders as a `settings.section` entry — a page at the same settings-nav\n * level as General / Models / Plugins. The shell supplies the nav row and\n * renders this body inside the content column. All copy comes from the\n * `settings.commandcode` locale namespace; all state comes from the\n * `CommandCodeSettingsController` injected by the slot registration.\n *\n * The layout mirrors the harness's settings pages: a max-width content\n * column, labelled fields with hints, a reset affordance, and a\n * save/discard footer. Styles are injected once by the client entry\n * (see src/client/index.ts) and class-prefixed `cc-` to stay local.\n */\n\nimport { Button } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { Translate } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SettingsCommandCodeKey } from './locales.ts'\nimport type { SettingsPageState, StagedField } from './settings.ts'\n\n/** Props composed by the slot registration: locale seat + injected face. */\nexport interface CommandCodeSettingsProps {\n t: Translate<SettingsCommandCodeKey>\n useCommandCodeSettings<T>(selector: (state: SettingsPageState) => T): T\n edit(field: string, text: string): void\n resetField(field: string): void\n save(): void\n discard(): void\n}\n\n/** One labelled field row in the page body. */\nfunction Field({\n id,\n label,\n hint,\n state,\n disabled,\n numeric,\n placeholder,\n onEdit,\n onReset,\n t,\n}: {\n id: string\n label: string\n hint: string\n state: StagedField\n disabled: boolean\n numeric?: boolean\n placeholder?: string | undefined\n onEdit(text: string): void\n onReset(): void\n t: Translate<SettingsCommandCodeKey>\n}) {\n return (\n <div className=\"cc-field\">\n <div className=\"cc-fieldHead\">\n <label className=\"cc-label\" htmlFor={id}>{label}</label>\n <span className=\"cc-badges\">\n {state.overridden ? <span className=\"cc-badge\">{t('overridden')}</span> : null}\n <button type=\"button\" className=\"cc-reset\" disabled={disabled} onClick={onReset}>{t('reset')}</button>\n </span>\n </div>\n <input\n id={id}\n className={state.invalid ? 'cc-input cc-inputInvalid' : 'cc-input'}\n type=\"text\"\n inputMode={numeric ? 'numeric' : undefined}\n value={state.text}\n placeholder={placeholder}\n disabled={disabled}\n onChange={(event) => onEdit(event.target.value)}\n />\n <p className={state.invalid ? 'cc-invalid' : 'cc-hint'}>\n {state.invalid ? t('invalidNumber') : hint}\n </p>\n </div>\n )\n}\n\n/** The API-key control: write-only, reports configured state, never echoes the key. */\nfunction SecretKeyField({\n label,\n hint,\n state,\n disabled,\n configured,\n configuredLabel,\n unconfiguredLabel,\n onEdit,\n}: {\n label: string\n hint: string\n state: StagedField\n disabled: boolean\n configured: boolean\n configuredLabel: string\n unconfiguredLabel: string\n onEdit(text: string): void\n}) {\n return (\n <div className=\"cc-field\">\n <div className=\"cc-fieldHead\">\n <label className=\"cc-label\" htmlFor=\"cc-api-key\">{label}</label>\n <span className=\"cc-badges\">\n <span className={configured ? 'cc-badge' : 'cc-badgeMuted'}>\n {configured ? configuredLabel : unconfiguredLabel}\n </span>\n </span>\n </div>\n <input\n id=\"cc-api-key\"\n className=\"cc-input\"\n type=\"password\"\n autoComplete=\"off\"\n value={state.text}\n disabled={disabled}\n onChange={(event) => onEdit(event.target.value)}\n />\n <p className=\"cc-hint\">{hint}</p>\n </div>\n )\n}\n\n/** The settings page body: connection facts for the Command Code provider. */\nexport function CommandCodeSettingsPage(props: CommandCodeSettingsProps) {\n const { t } = props\n const state = props.useCommandCodeSettings((snapshot) => snapshot)\n const disabled = !state.writable\n const keyLocked = !state.apiKeyWritable\n return (\n <section className=\"cc-section\" aria-label={t('title')}>\n <h2 className=\"cc-title\">{t('title')}</h2>\n <p className=\"cc-intro\">{t('intro')}</p>\n {!state.writable ? <p className=\"cc-readOnly\" role=\"status\">{t('readOnly')}</p> : null}\n <div className=\"cc-card\">\n <SecretKeyField\n label={t('apiKey')}\n hint={keyLocked ? t('apiKeyLocked') : t('apiKeyHint')}\n state={state.apiKey}\n disabled={disabled || keyLocked}\n configured={state.apiKeyConfigured}\n configuredLabel={t('apiKeySet')}\n unconfiguredLabel={t('apiKeyUnset')}\n onEdit={(text) => props.edit('apiKey', text)}\n />\n <Field\n id=\"cc-api-base\"\n label={t('apiBase')}\n hint={t('apiBaseHint')}\n state={state.apiBase}\n disabled={disabled}\n onEdit={(text) => props.edit('apiBase', text)}\n onReset={() => props.resetField('apiBase')}\n t={t}\n />\n <Field\n id=\"cc-working-dir\"\n label={t('workingDir')}\n hint={t('workingDirHint')}\n state={state.workingDir}\n disabled={disabled}\n placeholder={state.defaultWorkingDir}\n onEdit={(text) => props.edit('workingDir', text)}\n onReset={() => props.resetField('workingDir')}\n t={t}\n />\n <Field\n id=\"cc-request-timeout\"\n label={t('requestTimeoutMs')}\n hint={t('requestTimeoutMsHint')}\n state={state.requestTimeoutMs}\n disabled={disabled}\n numeric\n onEdit={(text) => props.edit('requestTimeoutMs', text)}\n onReset={() => props.resetField('requestTimeoutMs')}\n t={t}\n />\n <Field\n id=\"cc-stream-idle-timeout\"\n label={t('streamIdleTimeoutMs')}\n hint={t('streamIdleTimeoutMsHint')}\n state={state.streamIdleTimeoutMs}\n disabled={disabled}\n numeric\n onEdit={(text) => props.edit('streamIdleTimeoutMs', text)}\n onReset={() => props.resetField('streamIdleTimeoutMs')}\n t={t}\n />\n </div>\n <div className=\"cc-footer\">\n {state.failed ? <p className=\"cc-failed\" role=\"status\">{t('saveFailed')}</p> : null}\n <Button variant=\"ghost\" size=\"sm\" disabled={!state.dirty || state.saving} onClick={props.discard}>\n {t('discard')}\n </Button>\n <Button\n variant=\"primary\"\n size=\"sm\"\n disabled={!state.dirty || state.invalid || state.saving}\n onClick={props.save}\n >\n {t(state.saving ? 'saving' : 'save')}\n </Button>\n </div>\n </section>\n )\n}\n","/**\n * Locale copy for the \"Command Code\" settings page, and the declaration that\n * merges the page's namespace into the framework's `LocaleNamespaceMap` so\n * `ctx.locale.register` / `ctx.slots.register(..., { locale })` are typed.\n *\n * zh is the source of truth for the key set (repo convention); en must carry\n * the exact same keys — a mismatch is a compile error at the register site.\n */\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Copy of the Command Code settings page. */\n 'settings.commandcode': SettingsCommandCodeKey\n }\n}\n\n/** Dictionary keys of the Command Code settings page. */\nexport type SettingsCommandCodeKey =\n | 'nav'\n | 'title'\n | 'intro'\n | 'apiKey'\n | 'apiKeyHint'\n | 'apiKeySet'\n | 'apiKeyUnset'\n | 'apiKeyLocked'\n | 'apiBase'\n | 'apiBaseHint'\n | 'workingDir'\n | 'workingDirHint'\n | 'requestTimeoutMs'\n | 'requestTimeoutMsHint'\n | 'streamIdleTimeoutMs'\n | 'streamIdleTimeoutMsHint'\n | 'overridden'\n | 'reset'\n | 'invalidNumber'\n | 'readOnly'\n | 'unsaved'\n | 'save'\n | 'saving'\n | 'saveFailed'\n | 'discard'\n | 'cancel'\n\nexport const zh: Record<SettingsCommandCodeKey, string> = {\n nav: 'Command Code',\n title: 'Command Code',\n intro:\n '配置 Command Code Provider 连接。API 密钥仅保存在本机凭据服务中,不会回显;'\n + '其他字段写入用户设置,下次请求即生效。',\n apiKey: 'API 密钥',\n apiKeyHint: '在 commandcode.ai 控制台创建。留空保存不会覆盖已存储的密钥。',\n apiKeySet: '已配置',\n apiKeyUnset: '未配置',\n apiKeyLocked: '密钥由只读来源提供',\n apiBase: 'API 地址',\n apiBaseHint: '默认 https://api.commandcode.ai,一般无需修改。',\n workingDir: '工作目录',\n workingDirHint: '可选。留空时使用占位符显示的进程工作目录;仅在需要固定路径时填写。',\n requestTimeoutMs: '请求超时(毫秒)',\n requestTimeoutMsHint: '等待响应首个字节的超时;默认 60000。',\n streamIdleTimeoutMs: '流空闲超时(毫秒)',\n streamIdleTimeoutMsHint: '生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。',\n overridden: '已覆盖',\n reset: '重置',\n invalidNumber: '无效数字',\n readOnly: '当前配置为只读。',\n unsaved: '未保存',\n save: '保存',\n saving: '保存中',\n saveFailed: '保存失败,请重试。',\n discard: '放弃',\n cancel: '取消',\n}\n\nexport const en: Record<SettingsCommandCodeKey, string> = {\n nav: 'Command Code',\n title: 'Command Code',\n intro:\n 'Configure the Command Code Provider connection. The API key is stored only'\n + ' in the local credential service and never echoed; other fields are written'\n + ' to user settings and take effect on the next request.',\n apiKey: 'API key',\n apiKeyHint: 'Create one in the commandcode.ai console. Saving with this field'\n + ' blank keeps the stored key.',\n apiKeySet: 'Configured',\n apiKeyUnset: 'Not configured',\n apiKeyLocked: 'Key provided by a read-only source',\n apiBase: 'API base URL',\n apiBaseHint: 'Defaults to https://api.commandcode.ai; usually leave as-is.',\n workingDir: 'Working directory',\n workingDirHint: 'Optional. Leave blank to use the process cwd shown as the'\n + ' placeholder; fill in only to pin a specific path.',\n requestTimeoutMs: 'Request timeout (ms)',\n requestTimeoutMsHint: 'Time to wait for the first response byte; default 60000.',\n streamIdleTimeoutMs: 'Stream idle timeout (ms)',\n streamIdleTimeoutMsHint: 'How long a stalled stream is treated as dead; default 300000'\n + ' (deliberately generous — long-thinking models can stay silent for minutes).',\n overridden: 'Overridden',\n reset: 'Reset',\n invalidNumber: 'Invalid number',\n readOnly: 'Settings are read-only.',\n unsaved: 'Unsaved',\n save: 'Save',\n saving: 'Saving',\n saveFailed: 'Save failed, please retry.',\n discard: 'Discard',\n cancel: 'Cancel',\n}\n","/**\n * Browser half of the dsh-commandcode-provider bundle.\n *\n * Two responsibilities:\n *\n * 1. A \"Command Code\" settings page (a `settings.section` entry at the same\n * nav level as General / Models / Plugins). The Models page renders an\n * unknown-adapter-family card for the `commandcode` provider and disables\n * its submit, so the API key cannot be configured there; this page is the\n * dedicated surface. It writes the API key through the credentials domain\n * (the `COMMANDCODE_API_KEY` reference the plugin resolves) and the\n * connection facts through the `llm-commandcode` settings namespace, so a\n * saved key or endpoint reaches the very next request.\n *\n * 2. The friendly-error wrapper for the harness's image-session gate — see\n * `./sessions.ts`. The wrapper is deliberately narrow: only the\n * `model-unavailable` code is rewritten, only when the message matches the\n * image-session gate, and only the message text changes.\n *\n * The wire types are spelled structurally in `./sessions.ts` (not imported\n * from `@deepseek-ai/dsh-host-apiproxy`) so this client bundle does not drag\n * an extra peer dependency into the package; the shapes are stable and the\n * client build inlines them anyway.\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only imports that pull in the client-service augmentations\n// (`slots`/`remote`/`locale` on Context) and the `settings.section` SlotMap\n// entry (`settingsScope` arrives through dsh-client-ui-settings).\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { installFriendlyImageError } from './sessions.ts'\nimport { CommandCodeSettingsController, COMMANDCODE_NS, type SettingsPageState } from './settings.ts'\nimport { CommandCodeSettingsPage } from './section.tsx'\nimport { zh, en } from './locales.ts'\n\nexport { isImageSessionRejection, withFriendlyImageError } from './sessions.ts'\nimport type { ConnectionLike } from './sessions.ts'\n\n/** CSS for the settings page, injected once (harness bundle convention). */\nconst PAGE_CSS = `\n.cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}\n.cc-title{margin:0;font-size:18px;font-weight:600}\n.cc-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:1.5}\n.cc-readOnly{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}\n.cc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:4px 16px}\n.cc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}\n.cc-field+.cc-field{border-top:1px solid var(--dsw-alias-border-l2)}\n.cc-fieldHead{align-items:center;gap:8px;display:flex}\n.cc-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}\n.cc-badges{align-items:center;gap:8px;display:inline-flex}\n.cc-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}\n.cc-badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}\n.cc-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}\n.cc-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}\n.cc-reset:disabled{cursor:default;opacity:.5}\n.cc-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}\n.cc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}\n.cc-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}\n.cc-inputInvalid{border-color:var(--dsw-alias-label-error)}\n.cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}\n.cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}\n.cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}\n.cc-failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}\n`\n\n/** Inject the page stylesheet once (idempotent per tag). */\nfunction injectPageCss(): void {\n if (typeof document === 'undefined') return\n const id = '@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css'\n if (document.querySelector(`style[data-plugin-css=\"${id}\"]`) !== null) return\n const tag = document.createElement('style')\n tag.dataset.plugin = '@mars-sea/dsh-commandcode-provider'\n tag.dataset.pluginCss = id\n tag.textContent = PAGE_CSS\n document.head.appendChild(tag)\n}\n\n/**\n * Client plugin body. Gates on the services the settings page needs\n * (`slots`, `locale`, `connection`, `remote`, `settingsScope`) plus the\n * `connection` used by the friendly-error wrapper — the same inject list the\n * harness's own settings-surface plugins declare.\n */\nexport function apply(ctx: Context): void {\n injectPageCss()\n\n // Friendly image-gate error wrapper (unchanged behaviour).\n const connection = ctx.get('connection') as ConnectionLike | undefined\n if (connection !== undefined) {\n installFriendlyImageError(connection)\n }\n\n // The \"Command Code\" settings page: register the section once the\n // `settings.section` declaration is on the ledger (ui-settings-general\n // owns the shell; registration order relative to it is not constrained —\n // `slots.inject` waits for the declaration).\n ctx.effect(() => ctx.locale.register('settings.commandcode', { zh, en }), 'dsh-commandcode-provider: page copy')\n\n const api = ctx.get('connection').api\n const hostDescription = ctx.get('connection').hostDescription\n const scope = ctx.settingsScope.bind<Record<string, unknown>>({ namespace: COMMANDCODE_NS })\n const controller = new CommandCodeSettingsController(scope, { credentials: api.credentials }, hostDescription)\n ctx.effect(() => () => controller.dispose(), 'dsh-commandcode-provider: settings controller')\n const store = createSnapshotStore<SettingsPageState>(controller.state())\n controller.subscribe(() => store.set(controller.state()))\n const injected = () => ({\n hooks: { commandCodeSettings: store },\n edit: (field: string, text: string) => controller.edit(field, text),\n resetField: (field: string) => controller.resetField(field),\n save: () => void controller.save(),\n discard: () => controller.discard(),\n })\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'commandcode',\n order: 12,\n label: () => ctx.locale.bind('settings.commandcode')('nav'),\n locale: 'settings.commandcode',\n inject: injected,\n }, CommandCodeSettingsPage))\n}\n\nexport const inject: readonly string[] = [\n 'slots',\n 'locale',\n 'connection',\n 'remote',\n 'settingsScope',\n]\n"],"mappings":"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"}
|