dsh-zen-free 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,129 +1,108 @@
1
1
  # dsh-zen-free
2
2
 
3
- 给 DSH 加一个**免费**供应商 `zenfree`,带两个模型:
4
-
5
- | DSH 模型 id | 上游模型 | 说明 |
6
- |---|---|---|
7
- | `ds4.1f` | `deepseek-v4.1-flash-free` | DeepSeek V4.1 Flash,OpenCode Zen 免费池 |
8
- | `glm5.3f` | `glm-5.3-flash-free` | GLM 5.3 Flash,OpenCode Zen 免费池 |
3
+ 把 **OpenCode Zen 免费池**接成 DSH 的一条供应商路由 `opencode-zen`,注册进
4
+ **设置 → 模型** 的供应商列表,因此可以像其他供应商一样增删改模型、勾选图片输入、
5
+ 填上下文窗口与输出上限。
9
6
 
10
7
  - **不需要 API Key**,不需要账号,不需要 9Router。
11
- - **请求强制直连**:不走 `HTTP(S)_PROXY` / `ALL_PROXY`,也不经过 Harness 的全局代理
12
- dispatcher —— 本机 Clash(7897)从 CONNECT 隧道打 `opencode.ai` 会 TLS 失败,所以必须绕开。
13
- - **带设置页**:模型参数(上下文窗口 / 输出上限 / 图片输入)与请求统计都在
14
- 设置 → 「zenfree 模型」和「zenfree 统计」里。
15
-
16
- ## 安装
17
-
18
- ### 标准安装(npm 包)
19
-
20
- GUI 设置 → 插件 → 安装框输入包名 `dsh-zen-free`。
21
- (`installBundle` 会读本包的 `dsh.bundle.patch` → `cordis.patch.yml`,装完即启用。)
8
+ - **请求强制直连**:绕开 `HTTP(S)_PROXY` / `ALL_PROXY` 与 Harness 的全局代理
9
+ dispatcher —— 本机 Clash(7897)从 CONNECT 隧道打 `opencode.ai` 会 TLS 失败。
22
10
 
23
- 装完在模型选择器里会多出供应商 **zenfree**,下面两个模型。
11
+ ## 装完怎么用
24
12
 
25
- ### 手工挂载(本地开发)
13
+ 设置 → 模型 → 会看到 **OpenCode Zen(免费)** 一行,点「编辑」即可:
26
14
 
27
- 1. 把本目录拷进 `$DSH_HOME/profiles/<profile>/node_modules/`(目录名保持 `dsh-zen-free`)。
28
- 2. 在 `$DSH_HOME/profiles/<profile>/cordis.patch.yml` 末尾追加:
15
+ - 增删改模型(`deepseek-v4.1-flash-free` / `glm-5.3-flash-free` 是默认两条)
16
+ - 每个模型单独勾选输入模态(text / image)
17
+ - 填上下文窗口、单次输出上限
18
+ - 「获取可用模型」按钮可直接向上游拉取模型列表
29
19
 
30
- ```yaml
31
- - insert:
32
- - id: zenfree
33
- name: 'dsh-zen-free'
34
- ```
20
+ 未列出的 `-free` 模型 id 也可以手填进模型列表,会被原样透传到上游。
35
21
 
36
- 3. 生效:`cordis.patch.yml` 的改动由 HMR 即时生效;**新插件代码**首次加载若未被拾取,
37
- 关闭并重新打开 DeepSeek Harness。
22
+ ## 用量统计
38
23
 
39
- ## 设置页
24
+ **设置 → Zen 免费池统计**(独立一整页,不是「插件」下的标签页)显示:
40
25
 
41
- ### zenfree 模型
26
+ - 顶部四个总量:调用次数 / 输入 token / 输出 token / 命中缓存
27
+ - 按模型分行的明细:调用、失败、输入、输出、缓存、合计、最近使用时间
28
+ - 面板开着时每 5 秒自刷一次;底部「清零统计」按钮可重置
42
29
 
43
- 每个模型一张卡片,三个字段:
30
+ **怎么算的**:所有请求都必须经过 `127.0.0.1:18793` 的转发口(它是 pi-ai 路由的
31
+ `baseURL`),转发口在透传 SSE 的同时旁路解析末块的 `usage`,因此不额外发请求、
32
+ 不改动流的内容。token 口径与上游一致:
44
33
 
45
- | 字段 | 含义 | 默认 |
46
- |---|---|---|
47
- | 上下文窗口 | 该模型声明给 Harness 的上下文长度 | `200000` |
48
- | 单次输出上限 | `max_tokens` | `32768` |
49
- | 图片输入 | 是否接受图片(关闭后图片以占位文字发给上游) | 开启 |
50
-
51
- 改动写入插件的运行期配置,点「保存」立即生效,不用重启。校验从严:非正整数、
52
- 输出上限大于上下文窗口都会被拒并显示原因。
34
+ | 面板列 | 来源 |
35
+ |---|---|
36
+ | 输入 | `usage.prompt_tokens` |
37
+ | 输出 | `usage.completion_tokens` |
38
+ | 命中缓存 | `usage.prompt_tokens_details.cached_tokens` |
39
+ | 推理 | `usage.completion_tokens_details.reasoning_tokens` |
53
40
 
54
- 也可以在 `cordis.patch.yml` 的挂载行直接写配置(页面改的是同一份设置):
41
+ 非 2xx 响应(403 限流、426 UA 被拒等)不解析 usage,但仍计一次**失败调用**,
42
+ 所以面板能区分「用了多少」和「被挡了多少」。
55
43
 
56
- ```yaml
57
- - insert:
58
- - id: zenfree
59
- name: 'dsh-zen-free'
60
- config:
61
- models:
62
- ds4.1f: { contextWindow: 200000, maxTokens: 32768, input: [text, image] }
63
- glm5.3f: { contextWindow: 200000, maxTokens: 32768, input: [text] }
64
- ```
44
+ **数据在哪**:`$DSH_HOME/zen-free-usage.json`(缺省 `~/.dsh/zen-free-usage.json`)。
45
+ 写盘做了 500ms 去抖,进程退出时补一次 flush。文件损坏或不存在时按空统计处理,
46
+ 绝不会因为统计把转发口搞挂。
65
47
 
66
- `input` 里 `text` 始终保留(没有文本模态的模型在 Harness 里不可用),
67
- `image` 完全由配置决定。
48
+ **接口**:`GET /zen-free/api` 取汇总、`POST /zen-free/api/reset` 清零。挂在宿主的
49
+ webServer 上而不是转发口 —— 转发口在 18793、页面在 19387,跨源会被浏览器拦,
50
+ 也不该为一个只读面板放开 CORS。接口有 Host 回环校验与跨站拦截。
68
51
 
69
- ### zenfree 统计
52
+ > ⚠️ 客户端面板(`lib/client.js`)属于**插件代码**,改动后需要重启一次 DSH 才生效;
53
+ > 热重载只覆盖配置,不覆盖插件代码。重启后设置侧边栏才会出现「Zen 免费池统计」。
70
54
 
71
- 进程内的请求统计,5 秒自动刷新一次:
55
+ ## 它是怎么工作的
72
56
 
73
- - 请求总数 / 成功 / 失败 / 输入 tokens / 输出 tokens / 图片数;
74
- - 按模型分布(请求数、失败数、平均耗时、token 与图片数);
75
- - 最近 20 次请求明细(时间、模型、结果、耗时、token);
76
- - 错误码直方图(`RATE_LIMIT` / `TRANSPORT` / `EMPTY_RESPONSE` …)。
57
+ 本插件**不实现 LlmAdapter**,只做两件事:
77
58
 
78
- 数据只在内存里,**进程重启清零** —— 它是「这次用得怎么样」的观察窗,不是持久账单。
59
+ 1. 在 `127.0.0.1:18793` 起一个本地直连转发口,把请求用 `node:https` + 显式自建
60
+ `https.Agent` 转发到 `opencode.ai`(不读任何代理环境变量,也不复用进程级
61
+ undici dispatcher)。
62
+ 2. 通过 profile 的 `cordis.patch.yml` 把 `opencode-zen` 声明成 `llm-pi-ai` 的一条
63
+ provider,`baseURL` 指向上面那个转发口。
79
64
 
80
- ## 免费是怎么成立的
65
+ 请求本身由官方 `llm-pi-ai` 适配器发出,所以设置页的编辑器对该路由完全可用。
81
66
 
82
- OpenCode Zen 的免费池不校验密钥,只校验「你像不像 OpenCode 客户端」。三条准入规则:
67
+ **为什么必须走 pi-ai**:设置页的供应商编辑器只对 `llm-pi-ai` 与 `llm-deepseek`
68
+ 两个内置命名空间渲染可提交的表单;第三方插件自注册的命名空间一律是只读的
69
+ `unknown` layout。
83
70
 
84
- 1. `User-Agent` 能解析出 `opencode/<major>.<minor>.<patch>` 且 **≥ 1.18.0**
85
- → 本插件固定发 `opencode/1.18.31`。
86
- 2. `x-opencode-session` 必须是结构合法的 opencode 会话 id:
87
- `ses_` + 12 位小写 hex + 14 位 base62,且那 12 位 hex 取反后低 12 位(counter)
88
- **≤ 63**(时间戳那 36 位服务端不校验)→ 本插件每次请求新生成一个 counter = 1 的 id。
89
- 3. 请求体 `tools` 必须同时声明 **`bash` / `glob` / `grep` / `read`** 四个名字
90
- → 本插件把 Harness 自带的同名工具原样发出,缺的那个(Windows 上是 `bash`)补一个
91
- 占位条目(描述写明「本机不提供、不要调用」,因为服务端只看名字)。
71
+ ## 免费池的三条准入(2026-10-17 直连实测)
92
72
 
93
- ## 强制直连是怎么做的
73
+ | # | 条件 | 本插件怎么做 |
74
+ |---|---|---|
75
+ | 1 | `User-Agent` 解析出 `opencode/<x.y.z>` 且 **≥ 1.18.0** | 转发口注入 `opencode/1.18.31` |
76
+ | 2 | `x-opencode-session` 是结构合法的会话 id:`ses_` + 12 位小写 hex + 14 位 base62,hex 取反后低 12 位(counter)≤ 63 | 转发口注入一个进程级固定 id;实测同一 id 可无限复用 |
77
+ | 3 | 请求体 `tools` 非空**且含名为 `read` 的工具**(大小写敏感) | DSH 自带 `read`,pi-ai 原样发出,天然满足 |
94
78
 
95
- - 请求用 `node:https` 发出,并**显式传入自建 `https.Agent`**:
96
- - 显式 agent 覆盖 Node `NODE_USE_ENV_PROXY`(`--use-env-proxy`)安装的默认 agent;
97
- - `node:https` 本身不读 `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`;
98
- - 完全绕开 `globalThis.fetch` 走的 undici 全局 dispatcher —— 那正是 Harness
99
- `dsh-http-proxy` 安装代理的入口。
100
- - 该 Agent 不带任何 `proxy` 选项,只做 TCP/TLS 直连 + keep-alive。
79
+ > 旧版 README 说第 3 条要求同时声明 `bash`/`glob`/`grep`/`read` 四个名字,
80
+ > **这是错的**。实测:只发 `read` → 200;只发 `bash`、`glob`、`grep` → 均 403;
81
+ > 发五个任意名(`a`–`e`)→ 403。真正的要求是「含 `read`」。
101
82
 
102
83
  ## 已知限制
103
84
 
104
- - **占位 `bash` 工具**:准入只认名字,Harness 在 Windows 上没有 `bash`(只有 `pwsh`)。
105
- 若模型仍然去调用这个占位工具,Harness 会给出「未知工具」的工具结果。
106
- - **限流挂在出口 IP**:免费池按出口 IP / 账号限流,命中时失败码是 `RATE_LIMIT`(429)。
107
- - **地区限制**:部分免费模型对某些地区返回 `403 RegionError`。那与准入门槛无关,是另一道闸。
108
- - 未列出的模型 id 会**原样透传**到上游,因此也可以手输其它 `-free` 模型。
109
- - 设置页里的模型参数是**运行期配置**,重启 DSH 后回到 `cordis.patch.yml` 里写的值。
110
- 想永久生效就写进 patch 的 `config`。
85
+ - **图片**:两个免费模型在默认配置里只声明 `text`。若上游实际支持图片,可在设置页
86
+ 给模型勾上 image;但免费池模型大概率不收图,建议保持 text。
87
+ - **限流挂在出口 IP**:命中时上游返回 429,由 `dsh-llm-retry` 按默认策略重试。
88
+ - **地区限制**:部分免费模型对某些地区返回 `403 RegionError`,与准入门槛无关。
89
+ - **端口占用**:转发口固定用 `18793`。若被占用,日志会报错,此时需改
90
+ `lib/index.js` 的 `RELAY_PORT` 与 profile 里 `baseURL` 的端口,两者保持一致。
111
91
 
112
92
  ## 结构
113
93
 
114
94
  | 文件 | 作用 |
115
95
  |---|---|
116
- | `lib/index.js` | 适配器 + 设置页的 host 侧服务(无第三方依赖,只用 `node:https` 与 `node:crypto`) |
117
- | `lib/client.js` | 设置页的浏览器侧(`__ModuleLoader__` 入口,`dsh.client` 声明) |
96
+ | `lib/index.js` | 本地直连转发口(无第三方依赖,只用 `node:http` / `node:https` / `node:crypto`)+ 统计接口 |
97
+ | `lib/usage.js` | 按模型累计调用次数与 token,落盘 `$DSH_HOME/zen-free-usage.json` |
98
+ | `lib/client.js` | 浏览器半边:设置 → Zen 免费池统计 面板(`settings.section`) |
118
99
  | `cordis.patch.yml` | bundle patch:一条 loader 挂载行 |
119
- | `package.json` | 标准 DSH 插件包契约(`dsh.bundle.patch` + `dsh.client` + `files` + peer) |
100
+ | `package.json` | 标准 DSH 插件包契约(`dsh.bundle.patch` + `dsh.client` + `exports["./client"]`) |
101
+
102
+ `opencode-zen` 路由的配置写在 **profile 的** `cordis.patch.yml` 的 `llm-pi-ai`
103
+ 条目下(默认值层)。用户在设置页的改动写入 user 层并覆盖它,因此不会被本插件冲掉。
120
104
 
121
- 本包**不 import** 任何 `@deepseek-ai/*` 运行时包:第三方插件装在 profile 的
122
- `node_modules` 下,解析不到 asar 内的官方包。所需的 `LlmAdapter` 方法形状按契约实现,
123
- 失败码则通过 Error 上的自有 `code` / `failure` 数据属性给出 —— Harness 的
124
- `normalizeLlmFailure` 专门采纳这条跨包副本路径(`AUTH` / `RATE_LIMIT` /
125
- `TRANSPORT` / `TIMEOUT` / `ABORTED` …)。
105
+ ## 源码位置
126
106
 
127
- 设置页走 typert 的 Remote 通道:host 侧用 `ctx.typert.register()` 写入 invocation
128
- descriptor 并 `ctx.provide()` 一个带 `typertRemote` 绑定的服务,浏览器侧用同一套
129
- descriptor 通过 `ctx.remote.$mount()` 消费。两边的 descriptor 必须逐字段一致。
107
+ 本包在 profile 的 `node_modules` 下,**`pnpm install` 会把它覆盖回 npm 上的版本**。
108
+ 改代码请改 `C:\Users\XIAOPAN\Desktop\dsh\dsh-zen-free-src`,再同步过来。
package/cordis.patch.yml CHANGED
@@ -1,15 +1,13 @@
1
1
  # dsh-zen-free bundle patch — one loader entry mounting the plugin package.
2
2
  #
3
- # 挂载行是普通的 cordis loader entry(id + 包名)。插件在 apply() 里向 llm
4
- # 服务注册 zenfree 路由,因此不需要任何 config。
3
+ # 挂载行是普通的 cordis loader entry(id + 包名)。插件在 apply() 里起本地直连
4
+ # 转发口,因此不需要任何 config。
5
5
  #
6
- # 可选配置(全部可省略):
7
- # models:
8
- # ds4.1f: { contextWindow: 200000, maxTokens: 32768, input: [text, image] }
9
- # glm5.3f: { contextWindow: 200000, maxTokens: 32768, input: [text, image] }
6
+ # loader id 不叫 opencode-zen —— 那是 llm-pi-ai 里 provider 的路由名,撞名会让
7
+ # profile 配置难以阅读。这里用 dsh-zen-free-relay。
10
8
  #
11
9
  # 手工挂载:把同一行加进 profile 的 cordis.patch.yml。
12
10
  # 标准安装:GUI 插件页安装框(installBundle 会自己读本文件)。
13
11
  - insert:
14
- - id: zenfree
12
+ - id: dsh-zen-free-relay
15
13
  name: 'dsh-zen-free'