opencode-zen-freex 0.2.0 → 0.2.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 +105 -125
- package/core-tools.mjs +0 -5
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,125 +1,105 @@
|
|
|
1
|
-
# OpenCode Zen Free
|
|
2
|
-
|
|
3
|
-
OpenCode
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
```sh
|
|
23
|
-
magpie plugin add opencode-zen-freex
|
|
24
|
-
magpie plugin login opencode-zen-free
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
`plugin-auth.json
|
|
32
|
-
|
|
33
|
-
##
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
not. That note is phrased about the one tool on purpose: a blanket "these
|
|
107
|
-
tools are not available" was measured to stop the model using the host's
|
|
108
|
-
own tools as well.
|
|
109
|
-
|
|
110
|
-
The headers sent are exactly those of one generation and no more, and that
|
|
111
|
-
set is a deliberate subset of everything Zen has been seen to accept.
|
|
112
|
-
`x-opencode-request` was measured refused on 2026-10-14 and has never been
|
|
113
|
-
measured to be needed, so it is not sent. `x-session-affinity` and
|
|
114
|
-
`x-session-id` are in OpenCode's own header set upstream and have been
|
|
115
|
-
measured refused, so they are not sent either. A test pins the set by name,
|
|
116
|
-
because adding a header is the easiest way to break this plugin.
|
|
117
|
-
|
|
118
|
-
Now and then Zen closes a streamed Chat Completions reply before its
|
|
119
|
-
`[DONE]`. When the reply has said something and no tool call of it is
|
|
120
|
-
unfinished, it ends there as an ordinary answer, and its text says it was
|
|
121
|
-
cut short; asking again goes on. A reply cut short with a tool call
|
|
122
|
-
unfinished, with nothing said, or collapsed for a client that doesn't
|
|
123
|
-
stream fails with an error naming the reply's id and how much of it came.
|
|
124
|
-
An Anthropic Messages or Responses reply cut short fails too: magpie
|
|
125
|
-
tells it from a whole one and, where the model can, goes on with it.
|
|
1
|
+
# OpenCode Zen Free
|
|
2
|
+
|
|
3
|
+
给非 OpenCode 宿主使用的 OpenCode Zen 免费模型。provider id:`opencode-zen-free`。
|
|
4
|
+
|
|
5
|
+
Zen 的免费层会拒绝一切不像 OpenCode 发出的请求,回答 403 FreeTierError("can only be used from within OpenCode")。它不只看请求头,也读请求体:凡是它认得出名字的工具,描述都必须和 OpenCode 自己写的一样。这个插件直接去问真正提供免费层的那个端点,把工具按 OpenCode 的样子呈现出去,同时保留宿主自己的工具,所以模型发出的调用仍然跑得起来。
|
|
6
|
+
|
|
7
|
+
不需要注册账号,也没有费用。凭据就是 `public` 这一个词。
|
|
8
|
+
|
|
9
|
+
本项目是 `magpie-community/plugins` 里那个插件的维护分支,同样是 MIT。分叉的理由是那份指纹的变动比那个仓库跟得快:它最后一个版本仍然对着一个早已不提供免费层的端点。两个插件只能装一个——它们注册的是同一个 provider id,都装上的话谁生效取决于加载顺序。维护步骤见 [MAINTAINING.md](MAINTAINING.md);想确认现在还能不能用,跑 `bun run check:gate`。
|
|
10
|
+
|
|
11
|
+
## 目录
|
|
12
|
+
|
|
13
|
+
- [安装](#安装)
|
|
14
|
+
- [登录](#登录)
|
|
15
|
+
- [模型](#模型)
|
|
16
|
+
- [免费层不再应答时](#免费层不再应答时)
|
|
17
|
+
- [请求](#请求)
|
|
18
|
+
- [排查](#排查)
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
magpie plugin add opencode-zen-freex
|
|
24
|
+
magpie plugin login opencode-zen-free
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 登录
|
|
28
|
+
|
|
29
|
+
在密钥一栏填 `public`。不需要个人的 API key。
|
|
30
|
+
|
|
31
|
+
凭据保存在 OpenCode 的 `auth.json` 或 magpie 的 `plugin-auth.json` 里。
|
|
32
|
+
|
|
33
|
+
## 模型
|
|
34
|
+
|
|
35
|
+
插件会去发现 Zen 当前的免费模型,包括它们的上下文窗口和思考(reasoning)等级。用 `opencode-zen-free/<模型名>`,例如 `opencode-zen-free/big-pickle`。
|
|
36
|
+
|
|
37
|
+
已下线的模型和 SystemOne 模型会被排除。这份列表在宿主每次列举模型时重新获取(magpie 在启动时做一次,之后每小时一次):Zen 不再免费开放的模型会退出列表,新转为免费的模型会加入列表。如果读不到 Zen 的列表,就沿用上一次的结果;第一次还没读到之前,用的是打包在插件里的那份,所以这个 provider 不会空着。请求一个当前并不免费的模型,会得到一个 404 并列出当时免费的模型;若某个模型是在上次列举之后才转免费的,请求会在重新获取列表后照常服务(最多每分钟一次)。可用性和限流由 Zen 决定,插件这边拿不到剩余额度。
|
|
38
|
+
|
|
39
|
+
## 免费层不再应答时
|
|
40
|
+
|
|
41
|
+
过期的指纹和好用的指纹看起来一模一样,直到有人被拒绝。要问 Zen 它还认哪几代 OpenCode:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
bun run check:gate
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
每一代的结论是 `answered`(正常应答)、`refused`(被拒,403)、`superseded`(426——Zen 要求的 OpenCode 比这份指纹声称的更新)或 `unreachable`(根本联系不上)。只要还有一代能用,一代被拒就不算失败;只有全部应答时退出码才是 0。发版之前、以及有人报告 403 的时候,都该跑一次。`ZEN_CHECK_MODEL` 可以指定探测用的模型,`ZEN_BASE_URL` 可以指定端点,默认值不合适时就改这两个。
|
|
48
|
+
|
|
49
|
+
如果每一代都被拒,就从当前版本的 OpenCode 重新采集措辞:把一个 release 指向本地服务器,从它发出的请求里把工具读出来。`bun run capture -- --dry` 取一份新采集并报告差异,`bun run capture` 才真正写入。插件在拒绝时会直接说出发出去的措辞是哪个版本、去哪里找,而不是让人自己猜。
|
|
50
|
+
|
|
51
|
+
要注意这个闸门并不稳定:摸索它的过程中它一天之内挪动过不止一次,而且同一个地址反复被拒之后,接下来一段时间反而会被应答。这里的结论只能读成"此时此刻",信之前再跑一遍。
|
|
52
|
+
|
|
53
|
+
`bun test` 是这个循环的另一半。它从不联网,所以它回答不了指纹是否还有效——它只能说明这个包的行为和以前是否一致。
|
|
54
|
+
|
|
55
|
+
## 请求
|
|
56
|
+
|
|
57
|
+
请求发往 `https://opencode.ai/inference/v1`,按每个模型原生的 Chat Completions、Responses 或 Anthropic Messages 接口来走。流式和普通 JSON 两种回复都支持。
|
|
58
|
+
|
|
59
|
+
宿主自己的工具会被完整保留。Zen 的模型经常去调 OpenCode 的工具名;这种调用会被对上宿主那个只差大小写的工具(`bash` 对 `Bash`),或者只差大小写和 `_`/`-` 的那个(`todowrite` 对 `todo_write`、`webfetch` 对 `web_fetch`),前提是恰好只有一个对得上。找不到对应工具(或者调用根本没有名字)的,不放进回复里,回复的正文会说明哪个名字被略去了;如果这一轮所有调用都被略去,这一轮就以一段普通回答结束。工具仍然在宿主里执行。
|
|
60
|
+
|
|
61
|
+
Zen 的免费层读请求体,也读请求头,只有当整个请求都属于 OpenCode 的同一代时才作答:User-Agent、请求头和工具三者必须一致。当前一共有两代,请求带的工具说明是哪一代:`shell` 和 `read` 属于 2.x,`bash`、`glob`、`grep` 和 `read` 属于 1.x。如果宿主本身就是 OpenCode,它自己的 User-Agent 已经说明是哪一代,这个值会被原样传出去,而不是被钉死,所以永远不会落后。没有任何一方说明时,以工具为准,User-Agent 跟着工具走。
|
|
62
|
+
|
|
63
|
+
在同一代之内,Zen 认得名字的工具——`bash`、`glob`、`grep`、`read`、`write`、`edit`——必须在场,并且要按 OpenCode 的说法描述。前四个在宿主缺了的时候会补上;后两个只在宿主本来就有的时候才去读。工具永远不会被丢弃:宿主自己的工具原封不动地保留,只有发往上游的措辞被替换掉,所以模型发出的调用仍然适配真正执行它的那个工具。别的名字的工具一概不看。
|
|
64
|
+
|
|
65
|
+
被*补上*的工具背后什么都没有:宿主没有这个工具,调它也会被丢弃。但 Zen 要求按 OpenCode 的样子描述它,而这么描述又会让模型想去调它,所以在 Zen 会读的正文开头之后、它不读的那段收尾之前,插一句说明。那句话是只针对这一个工具写的:实测过一句笼统的"这些工具不可用",会连宿主自己的工具也一起不用。
|
|
66
|
+
|
|
67
|
+
发出去的请求头就是下面这些,一个不多,而这份清单是刻意从 Zen 曾经接受过的全部头里挑出来的子集:
|
|
68
|
+
|
|
69
|
+
| 请求头 | 值 |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `content-type` | `application/json` |
|
|
72
|
+
| `accept` | `*/*` |
|
|
73
|
+
| `user-agent` | 上面说的那一代的值 |
|
|
74
|
+
| `x-opencode-project` | 宿主的 project id,或 `global` |
|
|
75
|
+
| `x-opencode-session` | 整个会话共用的一个 id |
|
|
76
|
+
| `x-opencode-session-id` | 同一个 id |
|
|
77
|
+
| `x-opencode-client` | `cli` |
|
|
78
|
+
| `authorization` | `Bearer public`;走 Messages 时是 `x-api-key: public` |
|
|
79
|
+
|
|
80
|
+
`anthropic-version` 和 `anthropic-beta` 也会发出去,但只在走 Messages 时,而且只在宿主自己设了它们的时候。
|
|
81
|
+
|
|
82
|
+
`x-opencode-request` 在 2026-10-14 实测被拒,而且从未测出它是必需的,所以不发。`x-session-affinity` 和 `x-session-id` 出现在 OpenCode 上游自己的请求头集合里,实测被拒,所以同样不发——不过进来的同名请求头仍然会被读,用来判断这次请求属于哪个会话。测试按名字钉住了发出的这组请求头,因为多加一个请求头是弄坏这个插件最容易的方式。
|
|
83
|
+
|
|
84
|
+
Zen 的免费层会检查 session id 是否是 OpenCode 能生成的那种:时间戳加每毫秒计数器截成十二位十六进制,后面接十四个随机的 base62 字符;session 的那一种还会把低位取反。宿主给的 id 如果本来就是这个形状,就原样传出去;否则按会话的首条消息生成一个并记住,这样同一会话的各轮请求会共用一个 session id。
|
|
85
|
+
|
|
86
|
+
偶尔 Zen 会在发出 `[DONE]` 之前就结束一个流式的 Chat Completions 回复。如果回复已经说了内容、且其中没有未完成的工具调用,就以普通回答结束,正文里会说明它被截断了;再问一次就会接着往下走。如果是带着未完成的工具调用被截断、或者什么都没说就断了、或者被折叠给一个不支持流式的客户端,请求就会失败,错误信息里写明这个回复的 id 和收到了多少。走 Anthropic Messages 或 Responses 的回复被截断同样会失败:magpie 能把它和完整的回复区分开,并且在该模型支持的情况下继续往下走。
|
|
87
|
+
|
|
88
|
+
## 排查
|
|
89
|
+
|
|
90
|
+
| 看到的现象 | 这是什么 | 怎么办 |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| 每个请求都是 `403 FreeTierError` | `fingerprint.json` 里的措辞过期了 | 跑 `bun run check:gate`;如果每一代都被拒,跑 `bun run capture` |
|
|
93
|
+
| `426 Upgrade Required` | 这个包自称的 OpenCode 版本比 Zen 要的旧 | 升级插件,或者在更新的 OpenCode 里运行 |
|
|
94
|
+
| `404 … not available as an OpenCode Zen free model now` | Zen 不再免费开放这个模型 | 从错误信息列出的模型里换一个 |
|
|
95
|
+
| `Model is not an available free model for this protocol` | 请求发来时列表变了 | 让宿主刷新模型列表再试 |
|
|
96
|
+
| `uses the public credential` | 存的密钥不是 `public` | 用 `public` 重新登录 |
|
|
97
|
+
| 不带 `FreeTierError` 的 403 | 是别的 403,原样透传 | 按 Zen 自己的错误来读,别当成这个插件的 |
|
|
98
|
+
|
|
99
|
+
这个插件给出的 403 会写明发出去的措辞来自哪个 OpenCode 版本。那正是 `check:gate` 在问的那份指纹,所以从它开始查,而不是从请求头开始查。
|
|
100
|
+
|
|
101
|
+
模型的可用性和限流归 Zen 管,插件拿不到剩余额度可显示。同一个地址反复被拒,也可能被放进一个冷却期,表现得和被拒很像。
|
|
102
|
+
|
|
103
|
+
## 许可
|
|
104
|
+
|
|
105
|
+
MIT。分叉自 `magpie-community/plugins`(MIT)。
|
package/core-tools.mjs
CHANGED
|
@@ -23,9 +23,6 @@ const captured = JSON.parse(readFileSync(new URL("./fingerprint.json", import.me
|
|
|
23
23
|
|
|
24
24
|
export const PROFILES = captured.profiles;
|
|
25
25
|
|
|
26
|
-
/** The names Zen reads the wording of, for a generation. */
|
|
27
|
-
const readNames = (generation) => Object.keys(PROFILES[generation].tools);
|
|
28
|
-
|
|
29
26
|
/**
|
|
30
27
|
* The generation a request belongs to, told by the tools it carries. Tool names
|
|
31
28
|
* move far less often than descriptions do, so this survives a rewording that a
|
|
@@ -49,5 +46,3 @@ export function agentFor(generation, protocol = "chat") {
|
|
|
49
46
|
? `opencode/${agent.opencode} ai-sdk/provider-utils/${agent.providerUtils[protocol]} ${agent.runtime}`
|
|
50
47
|
: `opencode/${agent.channel}/${agent.opencode}/${agent.client}`;
|
|
51
48
|
}
|
|
52
|
-
|
|
53
|
-
export { readNames };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-zen-freex",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "OpenCode Zen's free models, for hosts that are not OpenCode: the public credential, automatic discovery and native streaming protocols",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
],
|
|
18
18
|
"keywords": ["opencode", "opencode-plugin", "zen", "free", "magpie"],
|
|
19
19
|
"license": "MIT",
|
|
20
|
-
"repository": { "type": "git", "url": "git+https://github.com/tkhs101/
|
|
21
|
-
"bugs": { "url": "https://github.com/tkhs101/
|
|
22
|
-
"homepage": "https://github.com/tkhs101/
|
|
20
|
+
"repository": { "type": "git", "url": "git+https://github.com/tkhs101/OpenCode-Zen-FreeX.git" },
|
|
21
|
+
"bugs": { "url": "https://github.com/tkhs101/OpenCode-Zen-FreeX/issues" },
|
|
22
|
+
"homepage": "https://github.com/tkhs101/OpenCode-Zen-FreeX#readme",
|
|
23
23
|
"scripts": {
|
|
24
24
|
"test": "bun test",
|
|
25
25
|
"check:gate": "bun tools-smoke.mjs",
|