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