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.
Files changed (3) hide show
  1. package/README.md +105 -125
  2. package/core-tools.mjs +0 -5
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -1,125 +1,105 @@
1
- # OpenCode Zen Free
2
-
3
- OpenCode Zen's free models for a host that is not OpenCode. Provider id:
4
- `opencode-zen-free`.
5
-
6
- Zen's free tier answers 403 FreeTierError ("can only be used from within
7
- OpenCode") to a request that isn't OpenCode's, and it reads the request body
8
- as well as its headers: the tools it knows by name have to be described the
9
- way OpenCode describes them. This plugin asks the endpoint that serves the
10
- free tier, and presents the tools the way OpenCode presents them while
11
- keeping the host's own tools, so its calls still run.
12
-
13
- This is a maintained fork of the plugin from `magpie-community/plugins`,
14
- which is MIT licensed. It is forked because that fingerprint moves faster
15
- than that repository follows it: its last release is still built for an
16
- endpoint that no longer serves the free tier. See [MAINTAINING.md](MAINTAINING.md)
17
- for how to keep it working, and `bun run check:gate` for whether it is
18
- working now.
19
-
20
- ## Install
21
-
22
- ```sh
23
- magpie plugin add opencode-zen-freex
24
- magpie plugin login opencode-zen-free
25
- ```
26
-
27
- ## Sign in
28
-
29
- Enter `public` in the key field. No personal API key is needed.
30
- The credential is kept in OpenCode's `auth.json` or magpie's
31
- `plugin-auth.json`.
32
-
33
- ## Models
34
-
35
- The plugin discovers Zen's current free models, including their context
36
- windows and reasoning levels. Use `opencode-zen-free/<model>`, for example
37
- `opencode-zen-free/big-pickle`.
38
-
39
- Retired models and SystemOne models are excluded. The list is asked
40
- again each time the host lists models (magpie does this on start and every
41
- hour): a model Zen no longer gives free leaves it, and a new free one joins
42
- it. When Zen's list can't be read, the last list is kept. A request for a
43
- model that isn't free now gets a 404 naming the free ones; one made free
44
- since the last list is served after the list is asked again (at most once a
45
- minute). Availability and rate limits are set by Zen; the plugin has no
46
- remaining-quota data.
47
-
48
- ## When the free tier stops answering
49
-
50
- A stale fingerprint does not look different from a working one until
51
- somebody is refused. Ask Zen which generations the plugin still speaks:
52
-
53
- ```sh
54
- bun run check:gate
55
- ```
56
-
57
- Each profile is reported as `answered`, `refused` (403), `superseded`
58
- (426 — Zen wants a newer OpenCode than the profile claims) or
59
- `unreachable`. Run it before a release and whenever a 403 is reported.
60
-
61
- If every profile is refused, capture the wording again from a current
62
- OpenCode: point a release at a local server and read the tools out of the
63
- request it makes. A refusal from the plugin says which wording went out
64
- and where to look, rather than leaving it to be guessed at.
65
-
66
- Note that the gate is not steady: it moved more than once in a day while
67
- this was being worked out, and repeated refusals from one address can
68
- themselves be answered for a while. Read a verdict here as "at this
69
- moment", and run it again before believing a change.
70
-
71
- ## Requests
72
-
73
- Requests go to `https://opencode.ai/inference/v1` using each model's
74
- native Chat Completions, Responses or Anthropic Messages API. Streaming and
75
- ordinary JSON replies are supported.
76
-
77
- The agent's tools are preserved. Zen's models often call OpenCode's own
78
- tool names; such a call is given the agent's tool whose name differs only
79
- in case (`bash` to `Bash`), or else only in case and `_`/`-` (`todowrite`
80
- to `todo_write`, `webfetch` to `web_fetch`), when exactly one does. A
81
- call with no such tool (or with no name) is left out of the reply, and
82
- the reply's text says which name was left out; a turn whose every call
83
- was left out ends as an ordinary answer. Tools run in the agent.
84
-
85
- Zen's free tier reads the body as well as the headers, and answers a
86
- request only when the whole of it belongs to one generation of OpenCode:
87
- the User-Agent, the headers and the tools have to agree. Two generations
88
- are current, and the tools a request carries say which: `shell` and `read`
89
- are 2.x, `bash`, `glob`, `grep` and `read` are 1.x. A host that is OpenCode
90
- itself says which it is in its own User-Agent, and that is passed on rather
91
- than pinned, so it is never behind. Where nothing says, the tools decide
92
- and the User-Agent follows them.
93
-
94
- Within a generation, the tools Zen knows by name — `bash`, `glob`, `grep`,
95
- `read`, `write`, `edit` — have to be there and described the way OpenCode
96
- describes them. The first four are added when the host lacks them; the
97
- other two are only read when the host has them. A tool is never
98
- discarded: the host's own tool is kept whole, and only the wording sent
99
- upstream is replaced, so the call the model makes still fits the tool that
100
- runs it. A tool of any other name is not looked at.
101
-
102
- A tool that was *added* has nothing behind it: the agent has no such tool
103
- and a call to it is dropped. Describing it as OpenCode describes it is what
104
- Zen asks for, and it also makes the model want to call it, so a note saying
105
- so is placed after the opening Zen reads and before the closing it does
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.0",
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/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",
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",