dsh-opencode-go 0.1.14 → 0.1.15
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/README.en.md +11 -3
- package/README.md +11 -3
- package/docs/fork-review-2026-09-25.md +191 -0
- package/docs/verification.md +10 -0
- package/lib/client.js +38 -11
- package/lib/index.js +9 -4
- package/lib/types/client/locales.d.ts +2 -0
- package/lib/types/client/section-controller.d.ts +4 -0
- package/lib/types/client/staged-form.d.ts +4 -3
- package/lib/types/config.d.ts +2 -0
- package/lib/types/conversion/context.d.ts +3 -1
- package/lib/types/conversion/image-offload.d.ts +1 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -36,7 +36,7 @@ If your DSH version does not have an **Add plugin** entry, use the command-line
|
|
|
36
36
|
### Command-line installation (alternative)
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
|
-
dsh plugin --profile web add dsh-opencode-go@0.1.
|
|
39
|
+
dsh plugin --profile web add dsh-opencode-go@0.1.15
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Start or restart `dsh web`, then:
|
|
@@ -50,7 +50,7 @@ Start or restart `dsh web`, then:
|
|
|
50
50
|
Install the plugin into the Headless profile:
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
|
-
dsh plugin --profile headless add dsh-opencode-go@0.1.
|
|
53
|
+
dsh plugin --profile headless add dsh-opencode-go@0.1.15
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
Save the following as `headless.patch.yml` to select a default model:
|
|
@@ -77,7 +77,7 @@ To build from source and install a local package:
|
|
|
77
77
|
```sh
|
|
78
78
|
npm ci --legacy-peer-deps
|
|
79
79
|
npm pack
|
|
80
|
-
dsh plugin --profile web add ./dsh-opencode-go-0.1.
|
|
80
|
+
dsh plugin --profile web add ./dsh-opencode-go-0.1.15.tgz
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
The development dependencies include real test packages from multiple DSH generations, so installation requires `--legacy-peer-deps`. For Headless, replace `web` with `headless`.
|
|
@@ -120,6 +120,14 @@ Only the listed IDs receive explicit overrides. Switches affect model pickers on
|
|
|
120
120
|
|
|
121
121
|
The older `showDeprecatedModels` and `visibleModelIds` fields no longer control visibility. Use the individual switches or `modelVisibility`; retaining old fields does not prevent the plugin from loading.
|
|
122
122
|
|
|
123
|
+
## Image count cap
|
|
124
|
+
|
|
125
|
+
Under **Settings → OpenCode Go → Advanced settings**, set `maxImages` to a positive integer and save; no restart is needed. It is unset by default, with no image-count cap. Clearing or resetting removes the user override and inherits the base configuration; if the base has no cap either, the count is unlimited.
|
|
126
|
+
|
|
127
|
+
The count covers the full history sent in one request, including tool-result images. Repeated occurrences of the same attachment count separately. With a cap of `30`, 30 images pass through; 31 images cause the oldest occurrence to be offloaded before the request continues. Offloading replaces image content with a text placeholder and keeps the original attachment, but the model cannot see the offloaded image content in that request.
|
|
128
|
+
|
|
129
|
+
DSH 0.1.5 offloads only for the current request. DSH 0.1.6 and later record offloaded occurrences and retry through the host's image-offload mechanism; raising or clearing the cap does not automatically restore images already offloaded from history. Existing payload, pixel, and per-image byte budgets still apply independently and may require more images to be offloaded. This optional compatibility setting does not change upstream service limits.
|
|
130
|
+
|
|
123
131
|
## FAQ
|
|
124
132
|
|
|
125
133
|
### The `opencode-go` route is already in use
|
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
### 命令行安装(备选)
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
|
-
dsh plugin --profile web add dsh-opencode-go@0.1.
|
|
39
|
+
dsh plugin --profile web add dsh-opencode-go@0.1.15
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
安装后启动或重启 `dsh web`,然后:
|
|
@@ -50,7 +50,7 @@ dsh plugin --profile web add dsh-opencode-go@0.1.14
|
|
|
50
50
|
安装到 Headless profile:
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
|
-
dsh plugin --profile headless add dsh-opencode-go@0.1.
|
|
53
|
+
dsh plugin --profile headless add dsh-opencode-go@0.1.15
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
将以下内容保存为 `headless.patch.yml`,选择默认模型:
|
|
@@ -77,7 +77,7 @@ dsh --profile headless --patch ./headless.patch.yml "你好"
|
|
|
77
77
|
```sh
|
|
78
78
|
npm ci --legacy-peer-deps
|
|
79
79
|
npm pack
|
|
80
|
-
dsh plugin --profile web add ./dsh-opencode-go-0.1.
|
|
80
|
+
dsh plugin --profile web add ./dsh-opencode-go-0.1.15.tgz
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
开发依赖包含多代 DSH 的真实测试包,安装时需要 `--legacy-peer-deps`。Headless 用户将 `web` 换成 `headless`。
|
|
@@ -120,6 +120,14 @@ modelVisibility:
|
|
|
120
120
|
|
|
121
121
|
旧配置中的 `showDeprecatedModels` 和 `visibleModelIds` 不再控制显示状态。请使用逐模型开关或 `modelVisibility`;旧字段可以保留,不会妨碍插件加载。
|
|
122
122
|
|
|
123
|
+
## 图片数量上限
|
|
124
|
+
|
|
125
|
+
在「设置 → OpenCode Go → 高级设置」中可以设置 `maxImages`,填写正整数后保存即可生效,无需重启。默认不设置,不限制图片张数;清空或重置会移除用户覆盖,重新继承基础配置(基础配置也未设置时没有张数上限)。
|
|
126
|
+
|
|
127
|
+
数量按单次请求携带的完整历史统计,包括工具输出里的图片;同一附件出现多次也分别计数。例如设置为 `30` 时,30 张正常发送,31 张会先卸载最旧的 1 张,再继续请求。卸载将图片内容替换为文字占位,原始附件仍保留,但模型在该次请求中看不到被卸载图片的内容。
|
|
128
|
+
|
|
129
|
+
DSH 0.1.5 的卸载只影响本次请求;DSH 0.1.6 及以上版本通过宿主的图片卸载机制记录卸载状态并重试,调高或清空上限不会自动恢复已卸载的历史图片。现有图片载荷、像素和单图字节预算仍独立生效,可能需要卸载更多图片。`maxImages` 是可选的兼容设置,不会改变上游服务自身的限制。
|
|
130
|
+
|
|
123
131
|
## 常见问题
|
|
124
132
|
|
|
125
133
|
### 提示 `opencode-go` 路由已被占用
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# 8 个 fork 的改进与吸收建议
|
|
2
|
+
|
|
3
|
+
检查日期:2026-09-25。基线为本项目 `main` / `origin/main` 的 `8590c5752189658b60240b729666f814f12b27ff`(0.1.12);已通过 GitHub API 确认远端也是同一提交。检查范围是 GitHub 返回的 8 个公开 fork、全部 14 个公开分支,并结合 PR 状态、实际 diff 和当前实现判断。此次只增加研究记录,没有合并 fork、修改产品代码或发布包。[上游基线](https://github.com/Duskriver/dsh-opencode-go/commit/8590c5752189658b60240b729666f814f12b27ff) · [fork 列表 API](https://api.github.com/repos/Duskriver/dsh-opencode-go/forks?per_page=100)
|
|
4
|
+
|
|
5
|
+
## 结论
|
|
6
|
+
|
|
7
|
+
建议优先吸收两个独立小改动,另保留一个低优先级边界修复:
|
|
8
|
+
|
|
9
|
+
1. **Guojing6:为 models.dev 设置独立下载超时。** 网关列表继续 10 秒,约 5 MB 的元数据快照允许更长下载时间。其 `7f699a4` 只改一个文件,可干净应用到当前 main;仍应补慢响应和超时回退测试,并评估 60 秒等待对模型选择器的影响。
|
|
10
|
+
2. **vjcspy:PR #11 的历史输入校验。** 预先拒绝目前无法表达的 developer 消息、工具增删块和延迟加载工具;补丁小、没有依赖升级,已通过本次隔离类型检查和定向测试。没有已知线上触发来源,属于预防性加固。
|
|
11
|
+
3. **x6nux:宿主降级后处理新版图片卸载标记。** 本次用合成历史在真实 0.1.5-rc.1 LLM 模块上复现当前转换函数崩溃,fork 同输入通过。适合只移植能力判断,并补回归测试;没有验证真实用户会话从新版降级的完整迁移流程。
|
|
12
|
+
|
|
13
|
+
不建议整体合入 Guojing6 的兼容分支或 vjcspy 的 develop。前者将大量核心能力改为宽泛回退,后者包含发布安装脚本问题;两者的依赖和历史表示改造远大于目前已经支持 rc.1/rc.2 所需的改动。
|
|
14
|
+
|
|
15
|
+
## 每个 fork 做了什么
|
|
16
|
+
|
|
17
|
+
下表的“独有提交”按 `main..fork` 计算。它不等于“尚缺功能”:nam278 的功能通过整合提交进入主线,因此原始提交仍显示独有。
|
|
18
|
+
|
|
19
|
+
| Fork | 分支和独有提交 | 主要改进 | 当前判断 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| [liujwcn](https://github.com/liujwcn/dsh-opencode-go) | `main`:1 | 声明 DSH 0.1.7-rc.1 支持,增加独立宿主 fixture 与兼容测试 | 当前 0.1.12 已覆盖,并进一步支持 rc.2,不必再合 |
|
|
22
|
+
| [Guojing6](https://github.com/Guojing6/dsh-opencode-go) | `main`:0;`feat/dsh-0.1.7-rc1-host-drift`:3 | rc.1 开发依赖、宿主 API 回退、旧工具结果转换、Windows 沙箱构建/测试调整、自包含 headless smoke、元数据 60 秒超时 | 建议单独吸收超时改动;其余按具体需求拆分 |
|
|
23
|
+
| [RaulLazaro](https://github.com/RaulLazaro/dsh-opencode-go) | `main` / `fix/default-reasoning-effort`:均 0 | 对不传档位就关闭思考的协议设置默认推理档位 | PR #6 已合,并修正最高档位选择,不必再合 |
|
|
24
|
+
| [xwxYa](https://github.com/xwxYa/dsh-opencode-go) | `main` / `fix/handle-undecoded-compressed-json`:均 0 | 处理缺失编码响应头的压缩 JSON,并限制读取/解压大小 | PR #5 已合;后来演进为请求 `identity` 的实现,不应恢复旧解压逻辑 |
|
|
25
|
+
| [OHeroJ](https://github.com/OHeroJ/dsh-opencode-go) | `main`:0 | 仅保留较早上游快照 | 没有独有改进 |
|
|
26
|
+
| [nam278](https://github.com/nam278/dsh-opencode-go) | `main` / `feat/model-limits-ui`:同一独有提交 | 按模型覆盖上下文和输出容量,设置页搜索、重置、保存/放弃 | 已通过 `9d5cea7` 整合并修复边界问题,不必重复引入 |
|
|
27
|
+
| [x6nux](https://github.com/x6nux/dsh-opencode-go) | `main`:1 | 0.1.5/0.1.6 图片卸载兼容、同时传像素预算和目标尺寸、处理降级历史标记 | 前两项已有等价能力;降级标记处理仍值得单独借鉴 |
|
|
28
|
+
| [vjcspy](https://github.com/vjcspy/dsh-opencode-go) | `master`:0;`develop`:6;`feat/history-guards`:1 | rc.1 开发基线与测试改造、历史转换及 3 类不支持输入校验 | 建议 PR #11;不整体合 develop |
|
|
29
|
+
|
|
30
|
+
## Guojing6:最有实际收益的是慢网络超时修复
|
|
31
|
+
|
|
32
|
+
特性分支相对当前 main 独有 `4dec86b`、`da9f044`、`7f699a4` 三个提交,共改 30 个文件,1183 行新增、520 行删除;默认 `main` 没有独有提交。这个分支以 `f65a04b` 为基线,尚未包含本项目最后的 rc.2 支持和用量进度条修复。[完整分支比较](https://github.com/Duskriver/dsh-opencode-go/compare/8590c5752189658b60240b729666f814f12b27ff...Guojing6:7f699a4e5b91792fe5e72951a1229639a775bf2f)
|
|
33
|
+
|
|
34
|
+
### 建议单独吸收:元数据下载超时
|
|
35
|
+
|
|
36
|
+
当前 `src/catalog.ts` 的 `/models` 和 `models.dev/api.json` 都使用 10 秒完整请求期限。当前 JSON 请求还明确使用 `accept-encoding: identity`,元数据体积明显大于网关 ID 列表。fork 新增 `MODEL_METADATA_FETCH_TIMEOUT_MS = 60_000`,只用于元数据请求,保留网关 10 秒期限。作者报告代理冷连接下载用时 13.7 秒;这是作者测量,本次未在其网络环境复测。[独立提交](https://github.com/Guojing6/dsh-opencode-go/commit/7f699a4e5b91792fe5e72951a1229639a775bf2f) · [当前目录实现](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/catalog.ts) · [当前 identity 请求实现](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/json-response.ts)
|
|
37
|
+
|
|
38
|
+
本次 `git apply --check` 验证该提交补丁可直接应用到当前 main,无需引入 `host.ts` 或升级依赖。移植时需要覆盖:元数据慢于 10 秒但快于新期限、超时复用已缓存元数据、首次失败仅用已有备用配置、网关仍按 10 秒失败。代码用 `Promise.allSettled` 等待两个请求,目录刷新和需要新快照的请求可能因此最多等待约 60 秒;不能只考虑下载成功率。[当前 snapshot/build 流程](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/catalog.ts#L115)
|
|
39
|
+
|
|
40
|
+
### 可以借鉴,但不整体搬入
|
|
41
|
+
|
|
42
|
+
- **Headless smoke 自动安装 tarball、通过 Node 启动 CLI**:在新建 `DSH_HOME` 中先安装插件,使验证脚本的前置条件更明确,也有助于避开 Windows `.bin` 启动差异。移植时保留本项目对 `standalone-ok` 输出的断言;fork 切换继承 stdio 后删除了该断言。[提交 da9f044](https://github.com/Guojing6/dsh-opencode-go/commit/da9f044) · [fork 脚本](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/scripts/headless-smoke.mjs#L34)
|
|
43
|
+
- **Windows 沙箱构建/测试适配**:改用 esbuild CLI、复制 shadow 源码树并重写 CSS import、Vitest threads、继承子进程 stdio。它解决的是作者环境下的 spawn EPERM;本次没有 Windows 环境验证。增加的构建维护成本和移除输出断言的代价,暂不值得随功能补丁一并引入。[构建脚本](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/scripts/build.mjs) · [宿主测试](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/tests/host-compatibility.spec.ts)
|
|
44
|
+
- **统一 `host.ts` 回退**:namespace 读取适用于已知跨版本导出差异,但这里扩展到了错误类、凭据、图片、远程服务及超时机制。例如缺少 `idleWatchdog` 时直接透传 iterator,流式空闲期限失效;缺少 LlmAdapter 时 apply 记录日志后直接返回。当前已验证的 8 代宿主不需要这些假设性降级,不建议整体采用。[host.ts](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/src/host.ts#L246) · [apply](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/src/index.ts)
|
|
45
|
+
- **旧 nested tool-result 图片兼容**:新增递归图片检查和已卸载图片投影,但 adapter 的图片入口仍调用宿主的 `contentHasImage`,现代宿主仅扫描顶层;仅修改 context 无法使整条请求链路完整支持旧形状。应先确认宿主会否传入这种混合历史,再统一能力检查、预算计算及转换测试。[fork context](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/src/conversion/context.ts) · [fork adapter](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/src/adapter.ts)
|
|
46
|
+
|
|
47
|
+
## x6nux:大部分已覆盖,但降级历史边界仍有价值
|
|
48
|
+
|
|
49
|
+
`0d6e9af` 的主要改动是通过宿主能力选择图片卸载协议,并同时给附件服务传递 `maxPixels/maxBytes` 和 `width/height`。本项目已经通过 `bd3ba1f` 与 `1785150` 实现相应兼容,而且现实现保留了图片准备前的估算投影与准备后的精确投影,不宜换回整个旧模块。[fork 提交](https://github.com/x6nux/dsh-opencode-go/commit/0d6e9af74bc654358197f5f5dd323a2e8c7b39ed) · [主线图片兼容提交](https://github.com/Duskriver/dsh-opencode-go/commit/178515041e6ade11cf8e292f6880171c2d46adfb) · [当前投影模块](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/conversion/image-offload.ts)
|
|
50
|
+
|
|
51
|
+
尚有差异的是 `isOffloaded()`:fork 只有在宿主具备新版 surface offload API 时才把 `offloaded: true` 当作已卸载;0.1.5 不理解这个标记,仍需按自己的请求预算准备图片。当前 `collectImageRefs` 不分宿主跳过所有带标记图片,旧宿主后续访问 requestImages 中缺失的图片版本时会出错。[fork 能力判断](https://github.com/x6nux/dsh-opencode-go/blob/0d6e9af74bc654358197f5f5dd323a2e8c7b39ed/src/conversion/host-image-offload.ts#L72) · [当前 collectImageRefs](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/conversion/context.ts#L106)
|
|
52
|
+
|
|
53
|
+
本次把两份 context 分别编译到临时目录,使用实际 `dsh-llm-v015-rc1` / `dsh-attachment-v015-rc1` 模块、模拟附件读取服务和同一合成输入进行对照:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
输入:user.content = [{ type: 'image', attachment: 1 字节图片引用, offloaded: true }]
|
|
57
|
+
图片请求预算:100 bytes
|
|
58
|
+
main:读取附件 0 次;TypeError: Cannot read properties of undefined (reading 'bytes')
|
|
59
|
+
x6nux:读取附件 1 次;转换成功,输出 1 个 image 块
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
这是转换层复现,没有通过真实 CLI 打开降级会话,也没有真实模型请求。因此建议低优先级移植这个能力判断,并明确降级时是按旧宿主规则重发图片还是统一转成占位文本;不要把 fork 的 mock 测试或本次合成测试等同于完整跨版本数据迁移验证。
|
|
63
|
+
|
|
64
|
+
## vjcspy/dsh-opencode-go
|
|
65
|
+
|
|
66
|
+
检查日期:2026-09-25。比较基线为本项目当前提交 `8590c5752189658b60240b729666f814f12b27ff`(0.1.12)。本节检查了该 fork 的全部 3 个公开分支、实际代码差异、PR #10 / #11 及官方 DSH 参考实现;没有修改本项目生产代码,没有安装或执行 `develop` 的生命周期脚本。
|
|
67
|
+
|
|
68
|
+
### 分支范围与结论
|
|
69
|
+
|
|
70
|
+
| 分支 | 固定提交 SHA | 相对本项目基线 | 结论 |
|
|
71
|
+
| --- | --- | --- | --- |
|
|
72
|
+
| `master` | `8590c5752189658b60240b729666f814f12b27ff` | 完全相同 | 无新增内容 |
|
|
73
|
+
| `feat/history-guards` | `57646f55c57b7291b783ccf80f47acf6bc64e39b` | 领先 1、落后 0;2 文件,+71 / -6 | **建议吸收**;对应开放的 PR #11,是独立、低风险的输入校验补丁 |
|
|
74
|
+
| `develop` | `3f5e0550a1c5cdfedcac88445a20a4d5de9cc373` | 领先 6、落后 0;23 文件,+930 / -1224 | **不建议整分支合入**;主要是开发依赖升级适配,混入安装回归与未完成的旧图片历史兼容 |
|
|
75
|
+
|
|
76
|
+
来源:[公开分支](https://github.com/vjcspy/dsh-opencode-go/branches)、[guards 固定比较](https://github.com/vjcspy/dsh-opencode-go/compare/8590c5752189658b60240b729666f814f12b27ff...57646f55c57b7291b783ccf80f47acf6bc64e39b)、[develop 固定比较](https://github.com/vjcspy/dsh-opencode-go/compare/8590c5752189658b60240b729666f814f12b27ff...3f5e0550a1c5cdfedcac88445a20a4d5de9cc373)。以上数量由 GitHub Compare API 与本地 Git 交叉核对。
|
|
77
|
+
|
|
78
|
+
### 建议吸收:历史与工具声明的三项保护
|
|
79
|
+
|
|
80
|
+
PR #11 的提交直接以本项目当前 HEAD 为父提交,不需要先迁移 `develop`,也不更改依赖、锁文件、已支持宿主范围或持久化格式。可以直接 cherry-pick `57646f55c57b7291b783ccf80f47acf6bc64e39b`,再按本项目流程验证合入。检查时 PR 为 open,GitHub 报告 `mergeable: true`、`mergeable_state: clean`。本次仅评估,没有合并、关闭或评论该 PR。[PR #11](https://github.com/Duskriver/dsh-opencode-go/pull/11)
|
|
81
|
+
|
|
82
|
+
实际改变如下:
|
|
83
|
+
|
|
84
|
+
| 输入 | 现有代码行为 | 补丁行为及收益 |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| 历史中出现 `developer` 角色 | 未识别的角色会落入普通用户消息分支,可能丢失角色语义 | 转换时抛出 `LlmError`,错误码 `UNSUPPORTED_CONTENT`,避免把不支持的高优先级历史按普通用户文本发送 |
|
|
87
|
+
| 顶层内容块为 `tool-addition` / `tool-removal` | 不属于已支持的内容词汇,可能被转换忽略 | 明确拒绝,避免工具状态变更被悄悄丢掉 |
|
|
88
|
+
| 工具声明含 `deferLoading: true` | `toolsOf()` 仅抽取 name / description / parameters,延迟加载标志会丢失 | 明确拒绝,避免把延迟工具当作普通工具发送 |
|
|
89
|
+
|
|
90
|
+
以上前后行为来自实际转换代码。不能笼统称作“原来一定会得到 provider 的 malformed request”:有些输入会得到可发送但语义错误或内容缺失的请求,这也是该补丁值得吸收的原因。[当前转换实现](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/conversion/context.ts)、[补丁实现](https://github.com/vjcspy/dsh-opencode-go/blob/57646f55c57b7291b783ccf80f47acf6bc64e39b/src/conversion/context.ts#L51-L66)
|
|
91
|
+
|
|
92
|
+
两个历史 guard 放在现有图片角色校验之前,并由同步、异步转换路径共同调用。工具 guard 放在 `toolsOf()`,不依赖历史是否非空。三个错误与 DSH 官方 `dsh-v0.1.7-rc.2` 的参考 pi-ai adapter 一致;该标签固定到 `477b4f420553e8a52c2fbccc464d7561b239c443`,已读取实际源码核实。因为本项目开发类型仍是 `dsh-llm@0.1.6-alpha.1`,使用结构类型收窄是保留旧版本编译支持的适配手段。[fork 工具 guard](https://github.com/vjcspy/dsh-opencode-go/blob/57646f55c57b7291b783ccf80f47acf6bc64e39b/src/conversion/context.ts#L143-L155)、[官方历史 guard](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/llm/llm-pi-ai/src/context.ts#L49-L66)、[官方工具 guard](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/llm/llm-pi-ai/src/context.ts#L129-L143)
|
|
93
|
+
|
|
94
|
+
安全与兼容性判断:补丁只读取输入、抛出错误,不写历史、附件、设置或凭据,也没有新增网络端点。正常支持的消息仍使用原有转换路径;传入这三类原本不支持的输入会由“尝试转换”变成“明确失败”,属于预期的收紧行为。PR 作者明确表示目前没有已知生产来源,这一点应按作者声明理解,本次没有独立证明所有外部插件均不可能生成它们。因此优先级是预防性加固,不应宣传为已复现的线上事故修复。[PR 的范围说明](https://github.com/Duskriver/dsh-opencode-go/pull/11)
|
|
95
|
+
|
|
96
|
+
一个非阻塞细节:异步路径的 `deferLoading` 校验仍在最后构造 envelope 时执行,可能先读取图片附件;它保证生成请求前失败,但不是所有图片工作前都失败。此顺序来自共享的 `piContext()` / `toolsOf()` 调用,且与官方实现一致。若将来要求“无效工具声明绝不读取附件”,应单独提前做 options 校验。[异步路径](https://github.com/vjcspy/dsh-opencode-go/blob/57646f55c57b7291b783ccf80f47acf6bc64e39b/src/conversion/context.ts#L325-L405)
|
|
97
|
+
|
|
98
|
+
### 已执行的 PR #11 验证
|
|
99
|
+
|
|
100
|
+
在独立 `/tmp` Git worktree 中使用当前项目已经安装的依赖,显式运行可信的 Vitest 和 TypeScript CLI;没有运行 fork 的 `npm install`、`postinstall` 或 `pretest`。环境:macOS arm64、Node `v24.14.1`、Vitest `4.1.11`。
|
|
101
|
+
|
|
102
|
+
| 检查 | 本次实际结果 |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| 基线 `8590c575…` 的 `tests/conversion-context.spec.ts` | 22 / 22 通过 |
|
|
105
|
+
| guards 提交 `57646f55…` 的同一测试文件 | 25 / 25 通过 |
|
|
106
|
+
| guards 提交的 Host `tsc -p tsconfig.host.json --noEmit` | 通过 |
|
|
107
|
+
| guards 提交的 Client `tsc -p tsconfig.client.json --noEmit` | 通过 |
|
|
108
|
+
|
|
109
|
+
新增 3 个测试分别覆盖 developer、tool-addition、deferLoading,并各检查同步与异步两条入口;同步侧还断言 `LlmError` 类型,两个入口均断言错误码与完整信息。尚未单独覆盖 `tool-removal`、空 history 配合 deferred tools、`deferLoading: false` 的正常路径,可以在吸收时补上边界用例;它们不是当前发现的实现错误。[新增测试](https://github.com/vjcspy/dsh-opencode-go/blob/57646f55c57b7291b783ccf80f47acf6bc64e39b/tests/conversion-context.spec.ts#L571-L625)
|
|
110
|
+
|
|
111
|
+
“304 tests / build 通过”是 PR 作者提供的验证声明,本子任务没有将其冒充为本次完整验证;本次直接执行的范围见上表。主项目整体检查结果应在总报告中另列。
|
|
112
|
+
|
|
113
|
+
### develop 的 6 个提交究竟增加了什么
|
|
114
|
+
|
|
115
|
+
| 固定提交 | 改动与价值 | 吸收建议 |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| [`bbef5f458b5785166ddd81aff39ce1ad5100d337`](https://github.com/vjcspy/dsh-opencode-go/commit/bbef5f458b5785166ddd81aff39ce1ad5100d337) | 开发依赖迁移到 DSH rc.1;运行时 `dsh-brand` 升到 rc.1、Schemastery 升到 3.18.4;新增 `.npmrc` 与 fixture symlink `postinstall` | 暂不迁移;并非仅 devDependency 变化,且引入下述发布包安装问题 |
|
|
118
|
+
| [`50c788830508f9552ab853496ba6745eb1f33ffe`](https://github.com/vjcspy/dsh-opencode-go/commit/50c788830508f9552ab853496ba6745eb1f33ffe) | 适配 rc.1 类型:`RequestMessage`、settings / client 的结构访问、图标 Medium 兜底;中间版本同时删除旧 settings 和 nested tool-result 路径 | 不能单独挑这个提交;后续两次 fix 正是在恢复这次删除的向后兼容 |
|
|
119
|
+
| [`455de34af4c9448f2b823db889e5e42ed5a7a321`](https://github.com/vjcspy/dsh-opencode-go/commit/455de34af4c9448f2b823db889e5e42ed5a7a321) | 用 `SettingsCompat` 结构类型恢复 0.1.5 / 0.1.6 `installSection` | 本项目原本就有 fallback,主要是 fork 的类型适配与回归修复 |
|
|
120
|
+
| [`10bf28b85972f3b8893743ad8bcfe85b1f94ab15`](https://github.com/vjcspy/dsh-opencode-go/commit/10bf28b85972f3b8893743ad8bcfe85b1f94ab15) | 结构方式恢复旧 nested tool-result;增加深层图片检测、nested offloaded image 文本替换和 3 个 guard | guard 选用 PR #11 的独立版本;不要原样搬入其余图片逻辑 |
|
|
121
|
+
| [`873ee67f4b4b0e8b7ac46572fd951db653b41d18`](https://github.com/vjcspy/dsh-opencode-go/commit/873ee67f4b4b0e8b7ac46572fd951db653b41d18) | 动态配置测试改走新版 entry form,增加 settings fallback 定向测试,改消息 source fixture,固定测试 `NODE_ENV` | 可提取少量独立测试;不宜把旧 settings 文件验证整体替换掉 |
|
|
122
|
+
| [`3f5e0550a1c5cdfedcac88445a20a4d5de9cc373`](https://github.com/vjcspy/dsh-opencode-go/commit/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373) | 私有 fork 版本名改为 `0.1.12-vjcspy.1` | 无上游产品价值,不吸收 |
|
|
123
|
+
|
|
124
|
+
需要纠正“develop 才新增 rc.1 / rc.2 兼容”这一印象:当前基线已经支持新的独立 `tool` role、`configForms`、`configure` 优先 / `installSection` fallback 和 Regular / 14 图标兼容;peer 范围与八代宿主矩阵也已包含 rc.1 / rc.2。develop 中许多结构 cast 与 `RequestMessage` 类型替换,目的是用新开发依赖重新编译原有跨版本行为,不是当前用户缺失的新能力。关闭的 [PR #10](https://github.com/Duskriver/dsh-opencode-go/pull/10) 也不应重复当成待移植的新特性。[当前 settings 实现](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/index.ts#L206-L240)、[当前客户端入口](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/client/index.ts#L43-L84)、[当前验证记录](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/docs/verification.md)
|
|
125
|
+
|
|
126
|
+
### develop 的两个主要问题
|
|
127
|
+
|
|
128
|
+
**1. 发布包增加了会引用缺失文件的 postinstall。**
|
|
129
|
+
|
|
130
|
+
已验证事实:`package.json` 第 61 行增加 `postinstall: node scripts/materialize-fixture-links.mjs`,但 `files` 白名单第 32–38 行只包含 `lib`、patch、notices、docs、examples;`scripts` 与 `tests/hosts` 均不在包内。对固定 develop 提交执行 `npm pack --dry-run --json --ignore-scripts --workspaces=false`,确认打包清单没有该脚本与 tests 目录。这个 dry-run 未构建,所以也没有生成 lib;这里只用它核实受白名单控制的脚本/测试文件缺失,不把未构建的 lib 当作缺陷。[manifest](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/package.json#L32-L62)
|
|
131
|
+
|
|
132
|
+
由此可以确定:按当前清单发布后,在执行生命周期脚本的普通 npm 安装中,该 postinstall 会引用不存在的文件,预期报 `MODULE_NOT_FOUND`。本次没有执行消费者安装,因此具体错误是基于文件清单与 Node 入口语义的推断,并非抓取到的安装日志。即使仅补发脚本仍不完整:脚本无条件 `readdirSync(tests/hosts)`,发布包没有此目录时又会失败;它本质上只服务开发 fixture,应留在显式开发准备步骤,或至少同时解决发布环境跳过逻辑。[脚本入口及目录遍历](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/scripts/materialize-fixture-links.mjs#L23-L35)
|
|
133
|
+
|
|
134
|
+
**2. 新增旧 nested 图片支持只补了 context,入口选择和总预算仍不一致。**
|
|
135
|
+
|
|
136
|
+
已验证的代码事实:develop 在 context 内新增 `contentHasImageDeep()`,递归收集图片并处理 nested offloaded image;但 `src/adapter.ts` 没有任何变更,仍使用宿主的 `contentHasImage()` 决定图片模型校验和是否选择异步图片转换。官方 rc.2 的这个函数仅检查顶层;其 `requiredImageOffload()` / `projectOffloadedImages()` 也只遍历顶层。develop 的 `image-offload.ts` 只换了类型,仍把原始结构传给宿主 API。[deep 检测](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/conversion/context.ts#L60-L75)、[adapter 分支选择](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/adapter.ts#L269-L300)、[官方顶层检测](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/llm/llm/src/content.ts#L126-L128)、[官方图片遍历与预算](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/llm/llm/src/content.ts#L216-L333)
|
|
137
|
+
|
|
138
|
+
据上述控制流推导,在 rc.1 / rc.2 宿主接受原始旧嵌套历史的前提下:
|
|
139
|
+
|
|
140
|
+
- 若所有图片都在旧 `tool-result` 块内部,adapter 判定没有图片,进入同步转换;新增 deep 检测随后报“需要附件服务”,即使实际上已配置服务也无法重放。它从静默丢图变成明确拒绝有价值,但不能称为完成了兼容。
|
|
141
|
+
- 若还有一张顶层图片触发异步路径,context 会读出并发送嵌套图片,但宿主的总量检查漏计嵌套图片,因此可能超过 `maxRequestImageBytes`。已有 offloaded 标记的嵌套图片可由新增分支替换为文本,但这不解决未 offload 图片的预算问题。[同步拒绝](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/conversion/context.ts#L257-L265)、[嵌套图片转换](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/conversion/context.ts#L115-L152)、[图片预算桥接](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/conversion/image-offload.ts#L25-L47)
|
|
142
|
+
|
|
143
|
+
这两个情形是静态控制流推断,本次未运行完整 develop,也未证明当前生产宿主会把这种历史原样交给插件。develop 自己的提交说明亦称相关 nested 分支在已发布宿主上不可达。若以后确需迁移旧历史,应明确规范化策略,覆盖 adapter 入口、tool 配对、图片预算、offload 协议和不可变历史,而不是只移植深层 helper。新增 legacy 测试仅验证同步文本 tool-result,没有覆盖上述混合图片场景。[develop 新增测试](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/tests/conversion-context.spec.ts#L573-L618)
|
|
144
|
+
|
|
145
|
+
### develop 中值得单独借鉴的内容
|
|
146
|
+
|
|
147
|
+
- **优先借鉴 settings fallback 的定向回归测试。** `tests/settings-compat.spec.ts` 检查旧服务注册、source / onChange 驱动路由、新服务优先 configure 三点,能防止以后升级开发类型时误删旧分支。上游无需为这些测试一起迁移依赖或改生产逻辑;迁入时补足 Context dispose,并保持现有真实宿主矩阵。[测试文件](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/tests/settings-compat.spec.ts)
|
|
148
|
+
- **新版 profile fixture 可以作为新增覆盖,不应替代旧文件存储测试。** develop 通过真实 Loader / Settings 加一个内存 config editor 验证 entry form 更新,思路合理;但它替换了现有旧 FileSettings 测试,且删掉“非法 baseURL 拒绝后 settings.yaml 原文仍为空”的磁盘断言。内存 fixture 无法证明持久化文件没有被改写。当前上游另有 profile compatibility fixture,新增前应先查重。[fixture](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/tests/support/profile-fixture.ts)、[动态配置测试改动](https://github.com/vjcspy/dsh-opencode-go/commit/873ee67f4b4b0e8b7ac46572fd951db653b41d18)
|
|
149
|
+
- **`NODE_ENV=test` 可按实际问题选择吸收。** 无条件在 Vitest 配置内设置 test 环境,是开发测试的稳定性调整,不改变发布运行时;fork 注释声称修复 production shell 影响 Vite 解析的问题,本次没有复现该场景,不能据此认定当前环境必需。[Vitest 配置](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/vitest.config.ts#L1-L8)
|
|
150
|
+
- **图标第三重 Medium fallback 当前收益很小。** 基线已有 Regular → 14,rc.2 包同时导出 Regular 与 Medium,所以现有目标宿主不需要走第三重兜底;develop 直接引用 `IconChevronDownOutlineMedium` 的类型也是为升级开发依赖服务,不能将这段源码机械复制回旧类型基线。[fork 图标适配](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/client/Section.tsx#L22-L29)
|
|
151
|
+
|
|
152
|
+
最终建议:先吸收 PR #11 的单提交保护;单独提取 settings fallback 回归测试作为后续小改动;保留目前经过多代宿主验证的依赖与运行时适配。develop 的整套依赖迁移、postinstall、版本名和不完整图片历史兼容均不直接合入。
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
## 已经吸收或已被当前实现覆盖的工作
|
|
156
|
+
|
|
157
|
+
- **liujwcn / PR #10**:`f3acbb8` 增加 rc.1 engine/peer 范围、fixture 和测试。PR 已关闭但没有 merge commit;应按当前代码覆盖情况判断。本项目 `8590c57` 已覆盖 rc.1,还增加 rc.2 和真实 Web/Headless bundle admission 测试,因此没有缺少的 rc.1 功能。[PR #10](https://github.com/Duskriver/dsh-opencode-go/pull/10) · [当前宿主矩阵](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/tests/host-compatibility.spec.ts)
|
|
158
|
+
- **RaulLazaro / PR #6**:对 deepseek、zai、qwen、qwen-chat-template 设置默认 effort,优先 high,否则最高非 off 档位。提交 `f4c919f` 已在 main 历史中;主线合并时还修正原代码回退选成最低档位的问题。当前的默认与显式档位验证已经覆盖各代宿主。[PR #6](https://github.com/Duskriver/dsh-opencode-go/pull/6) · [修正提交](https://github.com/Duskriver/dsh-opencode-go/commit/c371872) · [当前 adapter](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/adapter.ts#L196)
|
|
159
|
+
- **xwxYa / PR #5**:针对响应头缺失而收到原始 gzip/br/deflate 字节时 JSON 解析失败,加入安全读取/解压路径。PR 已合,分支 tip `13c3436` 是 main 祖先。当前主线改为显式请求 `accept-encoding: identity`,共享有大小上限的 JSON reader,并处理瞬时连接重置;旧手动解压猜测逻辑已被移除,不应再次搬回。[PR #5](https://github.com/Duskriver/dsh-opencode-go/pull/5) · [当前 JSON reader](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/json-response.ts) · [主线验证记录](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/docs/verification.md)
|
|
160
|
+
- **nam278 / PR #3**:模型容量覆盖、搜索和重置 UI、保存/放弃流程已通过 `9d5cea7` 整合并保留贡献者署名。整合实现进一步限制显式输出预算、保留原始目录值和缓存、支持继承配置的 null 重置、避免异步混用配置;再 cherry-pick 原始版本会重复或削弱已有行为。[PR #3 关闭说明](https://github.com/Duskriver/dsh-opencode-go/pull/3#issuecomment-5774872925) · [整合提交](https://github.com/Duskriver/dsh-opencode-go/commit/9d5cea7f287e636283123b5abad1889fe6ec0797) · [容量回归测试](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/tests/model-limits.spec.ts)
|
|
161
|
+
- **OHeroJ**:唯一公开分支 `main` 指向 `acefef5`,是当前 main 的祖先,独有提交 0,落后 22 个提交;没有额外实现可吸收。[固定快照](https://github.com/OHeroJ/dsh-opencode-go/tree/acefef59422bde84b1b2cedc58680d86e262613e)
|
|
162
|
+
|
|
163
|
+
## 本次验证与范围
|
|
164
|
+
|
|
165
|
+
- GitHub API 列出 8 个 fork / 14 个分支;在隔离 Git 仓库比较固定提交的祖先关系、独有提交和 diff,未更改当前仓库 remotes 或分支。
|
|
166
|
+
- 当前 main 实际执行 `npm test`:Host/Client 类型检查、构建成功,**22 个测试文件、301 个测试通过**。仍有宿主 UI 包缺少 source map 的已有警告。
|
|
167
|
+
- PR #11 在临时副本执行定向测试:基线 **22/22**,补丁后 **25/25**;Host/Client `tsc --noEmit` 均通过。未声称此次重跑了该 PR 的完整 304 项套件。
|
|
168
|
+
- Guojing6 的 `7f699a4` 通过 `git apply --check`;本次没有复测作者代理网络的 13.7 秒下载,也未执行该 fork 的完整测试套件。
|
|
169
|
+
- x6nux 的降级图片标记差异已做转换层运行对照;没有验证真实历史文件迁移、Windows UI 或付费模型请求。
|
|
170
|
+
|
|
171
|
+
## 分支快照清单
|
|
172
|
+
|
|
173
|
+
所有 ahead/behind 均相对固定上游 `8590c5752189658b60240b729666f814f12b27ff`,ahead 是 fork 独有提交数。
|
|
174
|
+
|
|
175
|
+
| 仓库 | 分支 | Tip | ahead / behind |
|
|
176
|
+
| --- | --- | --- | --- |
|
|
177
|
+
| liujwcn | main | `f3acbb8f736809e1bd8e751bf03bb77972a71d4c` | 1 / 1 |
|
|
178
|
+
| Guojing6 | main | `f65a04bd8b8e38e4edb1ff7474b3ad946ed3b271` | 0 / 1 |
|
|
179
|
+
| Guojing6 | feat/dsh-0.1.7-rc1-host-drift | `7f699a4e5b91792fe5e72951a1229639a775bf2f` | 3 / 1 |
|
|
180
|
+
| RaulLazaro | main | `7c2c6364b175deb0a58b22bdb5cea9887545a571` | 0 / 7 |
|
|
181
|
+
| RaulLazaro | fix/default-reasoning-effort | `f4c919f60577213cf472e0ad556ba05d5f895863` | 0 / 6 |
|
|
182
|
+
| xwxYa | main | `6bbe0b3f1df95506376cdda6577db91bd7e18e80` | 0 / 12 |
|
|
183
|
+
| xwxYa | fix/handle-undecoded-compressed-json | `13c343641000b5e4a41e213db17fc8ce6d0585ce` | 0 / 8 |
|
|
184
|
+
| OHeroJ | main | `acefef59422bde84b1b2cedc58680d86e262613e` | 0 / 22 |
|
|
185
|
+
| nam278 | main | `293f01941c3fcde4e3cb3fced87a38f9c813e622` | 1 / 22 |
|
|
186
|
+
| nam278 | feat/model-limits-ui | `293f01941c3fcde4e3cb3fced87a38f9c813e622` | 1 / 22 |
|
|
187
|
+
| x6nux | main | `0d6e9af74bc654358197f5f5dd323a2e8c7b39ed` | 1 / 26 |
|
|
188
|
+
| vjcspy | master | `8590c5752189658b60240b729666f814f12b27ff` | 0 / 0 |
|
|
189
|
+
| vjcspy | develop | `3f5e0550a1c5cdfedcac88445a20a4d5de9cc373` | 6 / 0 |
|
|
190
|
+
| vjcspy | feat/history-guards | `57646f55c57b7291b783ccf80f47acf6bc64e39b` | 1 / 0 |
|
|
191
|
+
|
package/docs/verification.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Verification
|
|
2
2
|
|
|
3
|
+
## Optional request image count cap and usage panel polish (plugin 0.1.15, 2026-09-26)
|
|
4
|
+
|
|
5
|
+
The OpenCode Go settings page now exposes `maxImages` as an optional positive integer. It has no default count limit, so existing image capability is unchanged until a user configures the field after an upstream image-count error. Clearing the field restores the inherited base value, or no count limit when the base has none. Counts include repeated image occurrences and images nested in tool results. The existing byte, pixel, and per-image budgets continue to apply independently.
|
|
6
|
+
|
|
7
|
+
On DSH 0.1.5, excess oldest occurrences become request-local text placeholders. On newer hosts, the adapter requests durable offloading through the Host's `IMAGE_OFFLOAD_REQUIRED` protocol; raising the limit later does not restore occurrences already marked offloaded. Compatibility fixtures exercise the count boundary, nested oldest image, combined count and byte budgets, live settings updates, clearing, and both legacy and modern Host paths.
|
|
8
|
+
|
|
9
|
+
The usage panel also includes PR #17's opaque background and severity colors for high and rate-limited usage windows. Its component test checks that normal, high, and limited windows receive distinct bar styles.
|
|
10
|
+
|
|
11
|
+
Validation: `npm test` passed the Host/Client type checks, production build, and **338 tests in 24 files**, including the published-Host compatibility matrix. The published UI dependencies still emit missing-source-map warnings. Tests use local HTTP and attachment fixtures; no live model request was made.
|
|
12
|
+
|
|
3
13
|
## Account usage panel and unsupported-history guards (plugin 0.1.14, 2026-09-26)
|
|
4
14
|
|
|
5
15
|
The OpenCode Go usage panel again shows only the account's rolling, weekly, and monthly limits. Session cache statistics introduced in 0.1.13 have been removed, including their event subscription, component, styles, and translations; token statistics remain available in DSH's own interface. The original account polling, refresh failure handling, and retry behavior are retained. This supersedes the session-cache panel behavior described in the earlier verification entry below.
|
package/lib/client.js
CHANGED
|
@@ -130,16 +130,16 @@ var modelsRemote = {
|
|
|
130
130
|
}]
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
// plugin-css:/Users/tanglei
|
|
133
|
+
// plugin-css:/Users/tanglei/Desktop/dsh-opencode-go/src/client/Section.module.css
|
|
134
134
|
var id = "dsh-opencode-go/Section.module.css";
|
|
135
135
|
if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")) {
|
|
136
136
|
const style = document.createElement("style");
|
|
137
137
|
style.dataset.plugin = "dsh-opencode-go";
|
|
138
138
|
style.dataset.pluginCss = id;
|
|
139
|
-
style.textContent = '.
|
|
139
|
+
style.textContent = '.BtKYva_page{flex-direction:column;min-width:0;height:100%;min-height:0;display:flex;container-type:size}.BtKYva_pageBody{scrollbar-gutter:stable;flex:1;min-height:0;padding-right:4px;overflow-y:auto}.BtKYva_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.BtKYva_field+.BtKYva_field{border-top:.5px solid var(--dsw-alias-border-l2)}.BtKYva_head{align-items:center;gap:8px;display:flex}.BtKYva_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.BtKYva_badges{align-items:center;gap:8px;display:inline-flex}.BtKYva_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}.BtKYva_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.BtKYva_reset:disabled{cursor:default}.BtKYva_reset:focus-visible,.BtKYva_disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.BtKYva_input{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.BtKYva_input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;border-color:var(--dsw-alias-brand-primary)}.BtKYva_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.BtKYva_inputInvalid{border-color:var(--dsw-alias-label-error);}.BtKYva_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.BtKYva_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.BtKYva_intro{color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-size:13px;line-height:1.5}.BtKYva_disclosure{font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;margin:0;padding:0;display:flex}.BtKYva_disclosure:hover .BtKYva_label{color:var(--dsw-alias-brand-primary)}.BtKYva_chevron,.BtKYva_chevronOpen{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none}.BtKYva_chevron{transform:rotate(-90deg)}.BtKYva_advanced{flex-direction:column;gap:4px;padding:4px 0 0 22px;display:flex}.BtKYva_advanced .BtKYva_field+.BtKYva_field{border-top:none}.BtKYva_chips{flex-wrap:wrap;gap:6px;display:flex}.BtKYva_limitsEditor{flex-direction:column;gap:10px;padding-top:4px;display:flex}.BtKYva_limitsToolbar{grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:6px 10px;display:grid}.BtKYva_limitsFilterLabel{color:var(--dsw-alias-label-secondary);grid-column:1/-1;font-size:12px;font-weight:500;line-height:1.5}.BtKYva_limitsSummary{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-bottom:8px;font-size:12px;line-height:1.5}.BtKYva_limitsId,.BtKYva_limitsModelId{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.BtKYva_limitsId{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.4}.BtKYva_limitsModelId{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.4}.BtKYva_visuallyHidden{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=560px){.BtKYva_limitsToolbar{grid-template-columns:1fr}.BtKYva_limitsSummary{padding-bottom:0}}.BtKYva_actions{border-top:.5px solid var(--dsw-alias-border-l2);flex-wrap:wrap;flex:none;align-items:center;gap:8px;padding:12px 0;display:flex}.BtKYva_starLink{max-width:100%;color:var(--dsw-alias-label-secondary);border-radius:4px;align-items:center;gap:7px;margin-left:auto;padding:6px 0;font-size:12px;line-height:1.5;text-decoration:none;display:inline-flex}.BtKYva_starLink svg{flex:none}.BtKYva_starLink:hover{color:var(--dsw-alias-brand-primary)}.BtKYva_starLink:hover span{text-underline-offset:3px;text-decoration:underline}.BtKYva_starLink:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.BtKYva_failedNote{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.BtKYva_pageTop{align-items:flex-start;gap:16px;display:flex}.BtKYva_pageTop .BtKYva_intro{flex:1}.BtKYva_advancedTrigger{flex-shrink:0;margin-left:auto;}.BtKYva_advancedTrigger .BtKYva_label{color:var(--dsw-alias-label-tertiary);font-size:12px}.BtKYva_filters{flex-wrap:wrap;gap:6px;display:flex}.BtKYva_filter{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:6px 9px;font-size:12px}.BtKYva_filter[aria-pressed=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3)}.BtKYva_filter span{color:var(--dsw-alias-label-tertiary);margin-left:4px}.BtKYva_modelLayout{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;grid-template-columns:minmax(0,43fr) minmax(0,57fr);height:clamp(240px,100cqh - 460px,480px);display:grid;overflow:hidden}.BtKYva_modelList{border-right:.5px solid var(--dsw-alias-border-l2);min-height:0;padding:6px;overflow-y:auto}.BtKYva_modelRow{align-items:center;gap:8px;padding-right:6px;display:flex}.BtKYva_modelRow>[role=switch]{flex-shrink:0}.BtKYva_modelChoice{text-align:left;min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:1;padding:12px 9px;display:block}.BtKYva_modelChoice[aria-pressed=true],.BtKYva_modelChoice:hover{background:var(--dsw-alias-bg-layer-3)}.BtKYva_modelName{overflow-wrap:anywhere;flex-wrap:wrap;align-items:center;gap:5px;font-size:13px;display:flex}.BtKYva_modelDetails{flex-direction:column;align-items:stretch;gap:10px;min-width:0;padding:18px;display:flex;overflow-y:auto}.BtKYva_modelDetails h3{overflow-wrap:anywhere;margin:0;font-size:16px}.BtKYva_modelHeading{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.BtKYva_modelDetails .BtKYva_field{border:0;padding:8px 0}.BtKYva_modelDetails .BtKYva_input{box-sizing:border-box;width:100%;min-width:0}.BtKYva_modelDetails .BtKYva_reset{align-self:flex-start}.BtKYva_newBadge{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:4px;padding:2px 4px;font-size:9px}.BtKYva_releaseDate{color:var(--dsw-alias-label-tertiary);margin-top:4px;font-size:10px;display:block}.BtKYva_modelChoice:focus-visible,.BtKYva_filter:focus-visible,.BtKYva_advancedTrigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}@container (width<=440px){.BtKYva_pageTop{flex-wrap:wrap}.BtKYva_modelLayout{grid-template-columns:minmax(0,1fr);height:auto}.BtKYva_modelList{border-right:0;border-bottom:.5px solid var(--dsw-alias-border-l2);max-height:240px}.BtKYva_modelDetails{max-height:460px}}.BtKYva_keySection{border-top:.5px solid var(--dsw-alias-border-l2);padding:12px 0}.BtKYva_keySection summary{cursor:pointer;list-style:none}.BtKYva_keySection summary:after{content:"\u2304";color:var(--dsw-alias-label-tertiary)}.BtKYva_keySection summary::-webkit-details-marker{display:none}.BtKYva_keySection>.BtKYva_input{box-sizing:border-box;width:100%;margin:10px 0 6px}.BtKYva_keySection summary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}';
|
|
140
140
|
document.head.appendChild(style);
|
|
141
141
|
}
|
|
142
|
-
var Section_default = { "
|
|
142
|
+
var Section_default = { "actions": "BtKYva_actions", "filter": "BtKYva_filter", "modelLayout": "BtKYva_modelLayout", "disclosure": "BtKYva_disclosure", "limitsEditor": "BtKYva_limitsEditor", "limitsModelId": "BtKYva_limitsModelId", "hint": "BtKYva_hint", "pageBody": "BtKYva_pageBody", "modelHeading": "BtKYva_modelHeading", "reset": "BtKYva_reset", "modelChoice": "BtKYva_modelChoice", "modelRow": "BtKYva_modelRow", "filters": "BtKYva_filters", "page": "BtKYva_page", "visuallyHidden": "BtKYva_visuallyHidden", "advancedTrigger": "BtKYva_advancedTrigger BtKYva_disclosure", "chevronOpen": "BtKYva_chevronOpen", "inputInvalid": "BtKYva_inputInvalid BtKYva_input", "invalid": "BtKYva_invalid", "field": "BtKYva_field", "modelList": "BtKYva_modelList", "badges": "BtKYva_badges", "limitsSummary": "BtKYva_limitsSummary", "label": "BtKYva_label", "starLink": "BtKYva_starLink", "limitsId": "BtKYva_limitsId", "modelDetails": "BtKYva_modelDetails", "intro": "BtKYva_intro", "releaseDate": "BtKYva_releaseDate", "input": "BtKYva_input", "head": "BtKYva_head", "limitsFilterLabel": "BtKYva_limitsFilterLabel", "pageTop": "BtKYva_pageTop", "newBadge": "BtKYva_newBadge", "failedNote": "BtKYva_failedNote", "advanced": "BtKYva_advanced", "chips": "BtKYva_chips", "modelName": "BtKYva_modelName", "keySection": "BtKYva_keySection", "chevron": "BtKYva_chevron", "limitsToolbar": "BtKYva_limitsToolbar" };
|
|
143
143
|
|
|
144
144
|
// src/client/ModelEditor.tsx
|
|
145
145
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -439,7 +439,7 @@ function Loaded(props) {
|
|
|
439
439
|
resetLabel: t("reset"),
|
|
440
440
|
disabled
|
|
441
441
|
};
|
|
442
|
-
const advancedOverridden = state.apiKeyEnv.overridden || state.baseURL.overridden || state.refreshMinutes.overridden || state.streamIdleTimeoutMs.overridden || state.maxRequestImageBytes.overridden || state.requestImagePixelBudget.overridden || state.requestImageMaxBytes.overridden;
|
|
442
|
+
const advancedOverridden = state.apiKeyEnv.overridden || state.baseURL.overridden || state.refreshMinutes.overridden || state.streamIdleTimeoutMs.overridden || state.maxImages.overridden || state.maxRequestImageBytes.overridden || state.requestImagePixelBudget.overridden || state.requestImageMaxBytes.overridden;
|
|
443
443
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: Section_default.page, children: [
|
|
444
444
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: Section_default.pageBody, children: [
|
|
445
445
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: Section_default.pageTop, children: [
|
|
@@ -530,6 +530,23 @@ function Loaded(props) {
|
|
|
530
530
|
}
|
|
531
531
|
}
|
|
532
532
|
),
|
|
533
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
534
|
+
ValueField,
|
|
535
|
+
{
|
|
536
|
+
id: "opencode-go-max-images",
|
|
537
|
+
label: t("maxImagesLabel"),
|
|
538
|
+
hint: t("maxImagesHint"),
|
|
539
|
+
field: state.maxImages,
|
|
540
|
+
numeric: true,
|
|
541
|
+
...fieldProps,
|
|
542
|
+
onEdit: (text) => {
|
|
543
|
+
props.edit("maxImages", text);
|
|
544
|
+
},
|
|
545
|
+
onReset: () => {
|
|
546
|
+
props.resetField("maxImages");
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
),
|
|
533
550
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
534
551
|
ValueField,
|
|
535
552
|
{
|
|
@@ -685,7 +702,7 @@ function Loaded(props) {
|
|
|
685
702
|
|
|
686
703
|
// src/client/staged-form.ts
|
|
687
704
|
var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
688
|
-
function numberField(field) {
|
|
705
|
+
function numberField(field, validate = () => true) {
|
|
689
706
|
return {
|
|
690
707
|
field,
|
|
691
708
|
format: (value) => typeof value === "number" ? String(value) : "",
|
|
@@ -693,7 +710,7 @@ function numberField(field) {
|
|
|
693
710
|
const trimmed = text.trim();
|
|
694
711
|
if (trimmed === "") return { kind: "clear" };
|
|
695
712
|
const parsed = Number(trimmed);
|
|
696
|
-
return Number.isFinite(parsed) ? { kind: "set", value: parsed } : void 0;
|
|
713
|
+
return Number.isFinite(parsed) && validate(parsed) ? { kind: "set", value: parsed } : void 0;
|
|
697
714
|
}
|
|
698
715
|
};
|
|
699
716
|
}
|
|
@@ -963,6 +980,7 @@ var OpencodeGoSectionController = class {
|
|
|
963
980
|
textField("baseURL"),
|
|
964
981
|
numberField("refreshMinutes"),
|
|
965
982
|
numberField("streamIdleTimeoutMs"),
|
|
983
|
+
numberField("maxImages", (value) => Number.isSafeInteger(value) && value > 0),
|
|
966
984
|
numberField("maxRequestImageBytes"),
|
|
967
985
|
numberField("requestImagePixelBudget"),
|
|
968
986
|
numberField("requestImageMaxBytes"),
|
|
@@ -1010,6 +1028,7 @@ var OpencodeGoSectionController = class {
|
|
|
1010
1028
|
baseURL: this.form.field("baseURL"),
|
|
1011
1029
|
refreshMinutes: this.form.field("refreshMinutes"),
|
|
1012
1030
|
streamIdleTimeoutMs: this.form.field("streamIdleTimeoutMs"),
|
|
1031
|
+
maxImages: this.form.field("maxImages"),
|
|
1013
1032
|
maxRequestImageBytes: this.form.field("maxRequestImageBytes"),
|
|
1014
1033
|
requestImagePixelBudget: this.form.field("requestImagePixelBudget"),
|
|
1015
1034
|
requestImageMaxBytes: this.form.field("requestImageMaxBytes"),
|
|
@@ -1320,6 +1339,8 @@ var en = {
|
|
|
1320
1339
|
refreshMinutesHint: "Catalog cache lifetime for conversations and model pickers. Opening this settings page or clicking Refresh checks the gateway and online model metadata immediately.",
|
|
1321
1340
|
streamIdleTimeoutMsLabel: "Stream idle timeout (ms)",
|
|
1322
1341
|
streamIdleTimeoutMsHint: "Largest quiet gap between stream events before the request fails.",
|
|
1342
|
+
maxImagesLabel: "Request image count cap (maxImages)",
|
|
1343
|
+
maxImagesHint: "Optional positive integer; no count cap by default. Excess oldest images become text placeholders so the conversation can continue. Original attachments are kept. On DSH 0.1.6+, raising or clearing the cap does not restore images already offloaded. Clearing inherits the base configuration.",
|
|
1323
1344
|
maxRequestImageBytesLabel: "Request image payload cap (bytes)",
|
|
1324
1345
|
maxRequestImageBytesHint: "Accumulated base64 image payload bound for one request.",
|
|
1325
1346
|
requestImagePixelBudgetLabel: "Image pixel budget",
|
|
@@ -1428,6 +1449,8 @@ var zh = {
|
|
|
1428
1449
|
refreshMinutesHint: "\u4F1A\u8BDD\u8BF7\u6C42\u548C\u6A21\u578B\u9009\u62E9\u5668\u7684\u76EE\u5F55\u7F13\u5B58\u65F6\u957F\u3002\u6253\u5F00\u672C\u8BBE\u7F6E\u9875\u6216\u70B9\u51FB\u5237\u65B0\u65F6\uFF0C\u4F1A\u7ACB\u5373\u6838\u5BF9\u7F51\u5173\u4E0E\u5728\u7EBF\u6A21\u578B\u914D\u7F6E\u3002",
|
|
1429
1450
|
streamIdleTimeoutMsLabel: "\u6D41\u7A7A\u95F2\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
|
|
1430
1451
|
streamIdleTimeoutMsHint: "\u6D41\u4E8B\u4EF6\u95F4\u5141\u8BB8\u7684\u6700\u5927\u9759\u9ED8\u95F4\u9694\uFF0C\u8D85\u51FA\u540E\u8BF7\u6C42\u5931\u8D25\u3002",
|
|
1452
|
+
maxImagesLabel: "\u8BF7\u6C42\u56FE\u7247\u6570\u91CF\u4E0A\u9650\uFF08maxImages\uFF09",
|
|
1453
|
+
maxImagesHint: "\u53EF\u9009\u6B63\u6574\u6570\uFF0C\u9ED8\u8BA4\u4E0D\u9650\u5236\u5F20\u6570\u3002\u8D85\u9650\u65F6\u5C06\u6700\u65E7\u56FE\u7247\u66FF\u6362\u4E3A\u6587\u5B57\u5360\u4F4D\u5E76\u7EE7\u7EED\u4F1A\u8BDD\uFF0C\u539F\u59CB\u9644\u4EF6\u4FDD\u7559\u3002DSH 0.1.6 \u53CA\u4EE5\u4E0A\u7248\u672C\u5DF2\u5378\u8F7D\u7684\u56FE\u7247\u4E0D\u4F1A\u56E0\u8C03\u9AD8\u6216\u6E05\u7A7A\u4E0A\u9650\u81EA\u52A8\u6062\u590D\u3002\u6E05\u7A7A\u540E\u7EE7\u627F\u57FA\u7840\u914D\u7F6E\u3002",
|
|
1431
1454
|
maxRequestImageBytesLabel: "\u8BF7\u6C42\u56FE\u7247\u8F7D\u8377\u4E0A\u9650\uFF08\u5B57\u8282\uFF09",
|
|
1432
1455
|
maxRequestImageBytesHint: "\u5355\u4E2A\u8BF7\u6C42\u7D2F\u8BA1 base64 \u56FE\u7247\u8F7D\u8377\u7684\u4E0A\u9650\u3002",
|
|
1433
1456
|
requestImagePixelBudgetLabel: "\u56FE\u7247\u50CF\u7D20\u9884\u7B97",
|
|
@@ -1493,16 +1516,16 @@ var goRemote = {
|
|
|
1493
1516
|
// src/client/UsagePill.tsx
|
|
1494
1517
|
var import_react3 = require("react");
|
|
1495
1518
|
|
|
1496
|
-
// plugin-css:/Users/tanglei
|
|
1519
|
+
// plugin-css:/Users/tanglei/Desktop/dsh-opencode-go/src/client/UsagePill.module.css
|
|
1497
1520
|
var id2 = "dsh-opencode-go/UsagePill.module.css";
|
|
1498
1521
|
if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]")) {
|
|
1499
1522
|
const style = document.createElement("style");
|
|
1500
1523
|
style.dataset.plugin = "dsh-opencode-go";
|
|
1501
1524
|
style.dataset.pluginCss = id2;
|
|
1502
|
-
style.textContent = ".
|
|
1525
|
+
style.textContent = ".dmPm3q_root{min-width:0;display:inline-flex;position:relative}.dmPm3q_trigger{color:inherit;opacity:.75;font:inherit;cursor:pointer;white-space:nowrap;background:0 0;border:0;border-radius:6px;padding:4px 6px;font-size:12px}.dmPm3q_trigger:hover,.dmPm3q_trigger:focus-visible{opacity:1;background:color-mix(in srgb, currentColor 8%, transparent)}.dmPm3q_panel{z-index:100;box-sizing:border-box;border:1px solid color-mix(in srgb, currentColor 18%, transparent);width:280px;max-width:calc(100vw - 32px);max-height:70vh;color:var(--dsw-alias-label-primary,CanvasText);isolation:isolate;background-color:canvas;background-image:linear-gradient(var(--dsw-specific-menu,transparent), var(--dsw-specific-menu,transparent)), linear-gradient(var(--dsw-alias-bg-layer-2,Canvas), var(--dsw-alias-bg-layer-2,Canvas));border-radius:12px;padding:16px;font-size:12px;position:absolute;bottom:calc(100% + 12px);right:0;overflow-y:auto;box-shadow:0 8px 30px #0003}.dmPm3q_hint{opacity:.65;font-size:11px;line-height:1.6}.dmPm3q_warning{background:color-mix(in srgb, currentColor 6%, transparent);overflow-wrap:anywhere;border-radius:6px;margin:10px 0;padding:10px;line-height:1.5}.dmPm3q_warning p{margin:5px 0 0}.dmPm3q_retry{border:1px solid color-mix(in srgb, currentColor 25%, transparent);color:inherit;font:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:5px 9px}.dmPm3q_retry:disabled{opacity:.6;cursor:default}.dmPm3q_retry:focus-visible{outline-offset:2px;outline:2px solid}.dmPm3q_window{margin-top:14px}.dmPm3q_row{justify-content:space-between;margin-bottom:5px;display:flex}.dmPm3q_window progress{appearance:none;background:color-mix(in srgb, currentColor 15%, transparent);border:0;border-radius:4px;width:100%;height:5px;margin-bottom:5px;display:block}.dmPm3q_window progress::-webkit-progress-bar{background:0 0;border-radius:4px}.dmPm3q_window progress::-webkit-progress-value{background:#39a878;border-radius:4px}.dmPm3q_window progress::-moz-progress-bar{background:#39a878;border-radius:4px}.dmPm3q_window progress.dmPm3q_high::-webkit-progress-value{background:#e0a100}.dmPm3q_window progress.dmPm3q_high::-moz-progress-bar{background:#e0a100}.dmPm3q_window progress.dmPm3q_limited::-webkit-progress-value{background:#e5484d}.dmPm3q_window progress.dmPm3q_limited::-moz-progress-bar{background:#e5484d}.dmPm3q_limitedText{color:#e5484d;margin-top:2px;font-weight:600}";
|
|
1503
1526
|
document.head.appendChild(style);
|
|
1504
1527
|
}
|
|
1505
|
-
var UsagePill_default = { "
|
|
1528
|
+
var UsagePill_default = { "row": "dmPm3q_row", "limitedText": "dmPm3q_limitedText", "high": "dmPm3q_high", "hint": "dmPm3q_hint", "warning": "dmPm3q_warning", "window": "dmPm3q_window", "panel": "dmPm3q_panel", "root": "dmPm3q_root", "retry": "dmPm3q_retry", "limited": "dmPm3q_limited", "trigger": "dmPm3q_trigger" };
|
|
1506
1529
|
|
|
1507
1530
|
// src/client/UsagePill.tsx
|
|
1508
1531
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -1517,6 +1540,10 @@ function usageFailure(error) {
|
|
|
1517
1540
|
}
|
|
1518
1541
|
return { retainPrevious: false };
|
|
1519
1542
|
}
|
|
1543
|
+
function usageLevel(window) {
|
|
1544
|
+
if (window.status === "rate-limited" || window.percent >= 100) return UsagePill_default.limited;
|
|
1545
|
+
return window.percent >= 80 ? UsagePill_default.high : void 0;
|
|
1546
|
+
}
|
|
1520
1547
|
function UsagePill({ directory, ...props }) {
|
|
1521
1548
|
const state = (0, import_react3.useSyncExternalStore)(directory.subscribe, directory.getSnapshot, directory.getSnapshot);
|
|
1522
1549
|
return state.current?.provider === "opencode-go" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActiveUsage, { ...props }) : null;
|
|
@@ -1642,13 +1669,13 @@ function ActiveUsage({ readUsage, t, getLocale }) {
|
|
|
1642
1669
|
"%"
|
|
1643
1670
|
] })
|
|
1644
1671
|
] }),
|
|
1645
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("progress", { "aria-label": t(`usage_${key}`), max: 100, value: Math.min(100, usage[key].percent) }),
|
|
1672
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("progress", { className: usageLevel(usage[key]), "aria-label": t(`usage_${key}`), max: 100, value: Math.min(100, usage[key].percent) }),
|
|
1646
1673
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: UsagePill_default.hint, children: [
|
|
1647
1674
|
t("usageResets"),
|
|
1648
1675
|
" ",
|
|
1649
1676
|
new Date(usage[key].resetsAt).toLocaleString(getLocale?.())
|
|
1650
1677
|
] }),
|
|
1651
|
-
usage[key].status === "rate-limited" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: t("usageLimited") })
|
|
1678
|
+
usage[key].status === "rate-limited" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: UsagePill_default.limitedText, children: t("usageLimited") })
|
|
1652
1679
|
] }, key)) : failure ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: t("usageLoading") })
|
|
1653
1680
|
] })
|
|
1654
1681
|
] });
|
package/lib/index.js
CHANGED
|
@@ -273,15 +273,15 @@ var api = llm;
|
|
|
273
273
|
function projectRequestImages(messages, policy) {
|
|
274
274
|
if (api.requiredImageOffload !== void 0 && api.projectOffloadedImages !== void 0) {
|
|
275
275
|
if (!policy.exact) return messages;
|
|
276
|
-
if (policy.maxBytes !== void 0) {
|
|
276
|
+
if (policy.maxBytes !== void 0 || policy.maxImages !== void 0) {
|
|
277
277
|
const offloadImages = api.requiredImageOffload(
|
|
278
278
|
messages,
|
|
279
|
-
{ representation: "base64", maxBytes: policy.maxBytes },
|
|
279
|
+
{ representation: "base64", maxBytes: policy.maxBytes, maxImages: policy.maxImages, countQuantum: 1 },
|
|
280
280
|
(block) => policy.byteLength(block.attachment)
|
|
281
281
|
);
|
|
282
282
|
if (offloadImages > 0) {
|
|
283
283
|
throw new llm.LlmError(
|
|
284
|
-
`pi-ai request images exceed the
|
|
284
|
+
`pi-ai request images exceed the configured image count or base64 payload bound; ${offloadImages} more oldest occurrence(s) must be offloaded.`,
|
|
285
285
|
api.IMAGE_OFFLOAD_REQUIRED_CODE ?? "IMAGE_OFFLOAD_REQUIRED",
|
|
286
286
|
{ offloadImages }
|
|
287
287
|
);
|
|
@@ -295,6 +295,8 @@ function projectRequestImages(messages, policy) {
|
|
|
295
295
|
return api.offloadRequestImagesWithPolicy(messages, {
|
|
296
296
|
representation: "base64",
|
|
297
297
|
byteQuantum: 1,
|
|
298
|
+
countQuantum: 1,
|
|
299
|
+
maxImages: policy.maxImages,
|
|
298
300
|
maxBytes: policy.maxBytes,
|
|
299
301
|
byteLength: policy.byteLength,
|
|
300
302
|
placeholder: policy.placeholder
|
|
@@ -478,7 +480,7 @@ function toPiContext(options, images, onReplayDegrade) {
|
|
|
478
480
|
return images === void 0 ? textOnlyContext(options, onReplayDegrade) : toPiContextWithImages(options, images, onReplayDegrade);
|
|
479
481
|
}
|
|
480
482
|
async function toPiContextWithImages(options, images, onReplayDegrade) {
|
|
481
|
-
const { attachments, resolveImageAccess, maxRequestImageBytes } = images;
|
|
483
|
+
const { attachments, resolveImageAccess, maxImages, maxRequestImageBytes } = images;
|
|
482
484
|
const requestImagePolicy = images.requestImagePolicy ?? {
|
|
483
485
|
maxPixels: DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
|
|
484
486
|
maxBytes: DEFAULT_REQUEST_IMAGE_MAX_BYTES
|
|
@@ -486,6 +488,7 @@ async function toPiContextWithImages(options, images, onReplayDegrade) {
|
|
|
486
488
|
assertSupportedHistory(options.messages);
|
|
487
489
|
const split = splitSystemPrompt(options);
|
|
488
490
|
const projection = {
|
|
491
|
+
maxImages,
|
|
489
492
|
maxBytes: maxRequestImageBytes,
|
|
490
493
|
placeholder: (ref) => offloadedImageText(ref, resolveImageAccess(ref))
|
|
491
494
|
};
|
|
@@ -1286,6 +1289,7 @@ var fields = {
|
|
|
1286
1289
|
baseURL: z.string().default(DEFAULT_BASE_URL),
|
|
1287
1290
|
refreshMinutes: z.number().step(1).min(1).max(7 * 24 * 60).default(DEFAULT_REFRESH_MINUTES),
|
|
1288
1291
|
streamIdleTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),
|
|
1292
|
+
maxImages: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER),
|
|
1289
1293
|
// The image defaults are the generic pi-ai adapter's: one normalized
|
|
1290
1294
|
// request image fits the budget, and fifteen of them fit the payload cap.
|
|
1291
1295
|
maxRequestImageBytes: z.number().step(1).min(1).default(DEFAULT_MAX_REQUEST_IMAGE_BYTES),
|
|
@@ -1476,6 +1480,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
|
|
|
1476
1480
|
imageRequest = {
|
|
1477
1481
|
attachments: store,
|
|
1478
1482
|
resolveImageAccess: (ref) => access.resolveImageAccess(store, ref),
|
|
1483
|
+
maxImages: config.maxImages ?? void 0,
|
|
1479
1484
|
maxRequestImageBytes: config.maxRequestImageBytes,
|
|
1480
1485
|
requestImagePolicy: {
|
|
1481
1486
|
maxPixels: config.requestImagePixelBudget,
|
|
@@ -91,6 +91,8 @@ export declare const en: {
|
|
|
91
91
|
refreshMinutesHint: string;
|
|
92
92
|
streamIdleTimeoutMsLabel: string;
|
|
93
93
|
streamIdleTimeoutMsHint: string;
|
|
94
|
+
maxImagesLabel: string;
|
|
95
|
+
maxImagesHint: string;
|
|
94
96
|
maxRequestImageBytesLabel: string;
|
|
95
97
|
maxRequestImageBytesHint: string;
|
|
96
98
|
requestImagePixelBudgetLabel: string;
|
|
@@ -30,6 +30,8 @@ export interface OpencodeGoSettings {
|
|
|
30
30
|
refreshMinutes?: number;
|
|
31
31
|
/** Largest idle gap between stream events, in milliseconds. */
|
|
32
32
|
streamIdleTimeoutMs?: number;
|
|
33
|
+
/** Optional retained image occurrence cap per request. */
|
|
34
|
+
maxImages?: number | null;
|
|
33
35
|
/** Accumulated base64 image payload bound for one request. */
|
|
34
36
|
maxRequestImageBytes?: number;
|
|
35
37
|
/** Total-pixel budget for one request image. */
|
|
@@ -94,6 +96,8 @@ export interface OpencodeGoSectionState extends FormShell {
|
|
|
94
96
|
refreshMinutes: FieldState;
|
|
95
97
|
/** Largest idle gap between stream events, in milliseconds. */
|
|
96
98
|
streamIdleTimeoutMs: FieldState;
|
|
99
|
+
/** Optional retained image occurrence cap; blank inherits the base configuration. */
|
|
100
|
+
maxImages: FieldState;
|
|
97
101
|
/** Accumulated base64 image payload bound for one request. */
|
|
98
102
|
maxRequestImageBytes: FieldState;
|
|
99
103
|
/** Total-pixel budget for one request image. */
|
|
@@ -88,12 +88,13 @@ export interface FormActions {
|
|
|
88
88
|
discard: () => void;
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* A
|
|
92
|
-
*
|
|
91
|
+
* A numeric field. An empty draft clears the field; other drafts must be
|
|
92
|
+
* finite numbers and satisfy the optional constraint before saving.
|
|
93
93
|
* @param field - field name inside the namespace section.
|
|
94
|
+
* @param validate - additional numeric constraint, if needed.
|
|
94
95
|
* @returns the field's conversion spec.
|
|
95
96
|
*/
|
|
96
|
-
export declare function numberField(field: string): FieldSpec;
|
|
97
|
+
export declare function numberField(field: string, validate?: (value: number) => boolean): FieldSpec;
|
|
97
98
|
/**
|
|
98
99
|
* A two-state field. Unlike the text fields, the draft is the boolean itself
|
|
99
100
|
* rather than text: the control that renders it reports the state the user
|
package/lib/types/config.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface OpencodeGoConfig {
|
|
|
48
48
|
refreshMinutes: number;
|
|
49
49
|
/** Largest idle gap between stream events before the request fails. */
|
|
50
50
|
streamIdleTimeoutMs: number;
|
|
51
|
+
/** Optional retained image occurrence cap per request; omission or null leaves the count unlimited. */
|
|
52
|
+
maxImages?: number | null;
|
|
51
53
|
/** Request-level bound on base64-encoded image payload, in bytes. */
|
|
52
54
|
maxRequestImageBytes: number;
|
|
53
55
|
/** Total-pixel budget for one request image. */
|
|
@@ -12,6 +12,8 @@ export interface PiImageRequestContext {
|
|
|
12
12
|
attachments: AttachmentStore;
|
|
13
13
|
/** Resolve current tool access separately from deterministic request-image versions. */
|
|
14
14
|
resolveImageAccess: ImageAttachmentAccessResolver;
|
|
15
|
+
/** Maximum retained image occurrences across request history; omission leaves the count unlimited. */
|
|
16
|
+
maxImages?: number;
|
|
15
17
|
/** Request-level bound on the base64-encoded payload of retained images; omission leaves the bound unchecked. */
|
|
16
18
|
maxRequestImageBytes?: number;
|
|
17
19
|
/** Route pixel and raw encoded-byte budgets. */
|
|
@@ -39,7 +41,7 @@ export declare function toPiContext(options: GenerateOptions, images?: undefined
|
|
|
39
41
|
* Tool result names are recovered from preceding assistant tool calls. On DSH
|
|
40
42
|
* 0.1.5, oldest images over the request budget become transient placeholders.
|
|
41
43
|
* On newer hosts, occurrences the surface marks offloaded become placeholders; when the
|
|
42
|
-
* retained occurrences
|
|
44
|
+
* retained occurrences exceed `maxImages` or their exact base64 payload exceeds
|
|
43
45
|
* `maxRequestImageBytes`, the call fails with `IMAGE_OFFLOAD_REQUIRED` naming
|
|
44
46
|
* how many more oldest occurrences must be offloaded.
|
|
45
47
|
* @param options - the harness request; `options.system`, else a leading `system` message, maps to pi-ai's single `systemPrompt` slot.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Message } from '@deepseek-ai/dsh-llm';
|
|
2
2
|
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
3
3
|
interface ImageProjectionPolicy {
|
|
4
|
+
maxImages?: number;
|
|
4
5
|
maxBytes?: number;
|
|
5
6
|
byteLength: (ref: ImageAttachmentRef) => number;
|
|
6
7
|
placeholder: (ref: ImageAttachmentRef) => string;
|