opencode-zen-freex 0.2.8 → 0.2.9
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/LICENSE +2 -9
- package/MAINTAINING.md +44 -3
- package/NOTICE +18 -0
- package/README.md +6 -4
- package/core-tools.mjs +85 -7
- package/index.mjs +14 -4
- package/limits.mjs +221 -57
- package/models.mjs +27 -3
- package/package.json +14 -6
- package/provider.mjs +111 -22
- package/stream.mjs +119 -21
- package/tool-response.mjs +56 -11
- package/capture.mjs +0 -231
- package/limits-audit.mjs +0 -340
- package/tools-smoke.mjs +0 -234
package/LICENSE
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2026 xiaozhou26
|
|
4
|
-
Copyright (c) 2026
|
|
5
|
-
|
|
6
|
-
This is a maintained fork of the OpenCode Zen Free plugin from
|
|
7
|
-
magpie-community/plugins, which is MIT licensed and carries the notice above:
|
|
8
|
-
the `Copyright (c) 2026 xiaozhou26` line is upstream's and has to be kept with
|
|
9
|
-
it, and the `opencode-zen-free contributors` line covers this fork.
|
|
10
|
-
The fork is maintained separately because the free tier's request fingerprint
|
|
11
|
-
moves faster than that repository follows it.
|
|
4
|
+
Copyright (c) 2026 OpenCode-Zen-FreeX contributors
|
|
12
5
|
|
|
13
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -26,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
26
19
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
20
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
21
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
-
SOFTWARE.
|
|
22
|
+
SOFTWARE.
|
package/MAINTAINING.md
CHANGED
|
@@ -156,13 +156,21 @@ It reports each request as:
|
|
|
156
156
|
| `refused` | Zen did not recognise it (403) |
|
|
157
157
|
| `superseded` | Zen wants a newer OpenCode than the profile claims (426) |
|
|
158
158
|
| `failed` | Zen answered with something other than a 403, so it never judged the fingerprint |
|
|
159
|
+
| `malformed` | the request could not be built: a name in `required` has no tool behind it in `fingerprint.json`. Zen was never asked, and the line names the generation, the shape and the name |
|
|
159
160
|
| `unreachable` | Zen could not be asked at all |
|
|
160
161
|
|
|
162
|
+
A `malformed` row is a `fingerprint.json` problem, not a network one, and the
|
|
163
|
+
summary says so rather than sending the reader to `capture`. It is deliberately
|
|
164
|
+
not counted as `answered`: a run whose floors are all `malformed` exits non-zero.
|
|
165
|
+
|
|
161
166
|
The `floor` decides the exit code, because it is the shape this plugin exists to
|
|
162
167
|
send. The `full` is reported for what it says about a wording that has gone
|
|
163
168
|
stale. Asking only the full shape left a real gap: `read` alone is `answered` and
|
|
164
169
|
everything but `read` is `refused`, so a Zen that began insisting on one more
|
|
165
|
-
name would keep `full` passing while the shape actually sent was refused.
|
|
170
|
+
name would keep `full` passing while the shape actually sent was refused. The
|
|
171
|
+
diagnosis at the end reads the floor rows only: a stray 5xx on a `full` request
|
|
172
|
+
says something about that request and nothing about whether the wording is
|
|
173
|
+
current, so it must not turn "nothing was served" into "check the network".
|
|
166
174
|
|
|
167
175
|
Only a 403 and a 426 are Zen answering about the fingerprint. A 5xx, a 429 or a
|
|
168
176
|
401 says nothing about the wording, and the gate says so rather than sending the
|
|
@@ -195,6 +203,13 @@ name is served either way, which is why only the `floor` shows this.
|
|
|
195
203
|
"your client is too old", which is a different problem from 403 and is not fixed
|
|
196
204
|
by changing tools.
|
|
197
205
|
|
|
206
|
+
**`malformed`.** `fingerprint.json` names a tool in `required` that the same
|
|
207
|
+
profile does not carry under `tools`, so the gate could not build the request it
|
|
208
|
+
wants to ask with. This is not a network problem and not something Zen said: the
|
|
209
|
+
line names the generation, the shape and the missing name. Fix the file — a
|
|
210
|
+
capture from a current OpenCode is the usual way — and ask again. The run exits
|
|
211
|
+
non-zero, and the summary says the same thing in one sentence.
|
|
212
|
+
|
|
198
213
|
**`unreachable`.** Not a fingerprint problem. Check the network, and whether
|
|
199
214
|
repeated refusals from this address have put it in a cooldown — they can, and
|
|
200
215
|
the cooldown answers like a refusal.
|
|
@@ -261,9 +276,18 @@ that passes under the stricter of them, since the looser one will not last.
|
|
|
261
276
|
| `models.mjs` | which models are free, and what protocol each speaks |
|
|
262
277
|
| `limits.mjs` | what a model's limits are, where the catalogue under-reports them |
|
|
263
278
|
| `stream.mjs`, `tool-response.mjs` | replies Zen cuts short, and tool calls |
|
|
279
|
+
| `limits-audit.mjs` | asks the route which ids it still offers and which look under-declared (`limits:audit`) |
|
|
264
280
|
| `capture.mjs` | takes a new capture |
|
|
265
281
|
| `tools-smoke.mjs` | asks Zen what it accepts (`check:gate`) |
|
|
266
282
|
|
|
283
|
+
`capture.mjs`, `limits-audit.mjs` and `tools-smoke.mjs` are maintenance tools for
|
|
284
|
+
this repository, not part of what a host loads: they are not in `package.json`'s
|
|
285
|
+
`files`, so a published tarball carries only the modules a host imports plus
|
|
286
|
+
`fingerprint.json`. `package.json`'s `exports` names those public modules one by
|
|
287
|
+
one rather than mapping `./*`, so a file that was never meant to be imported
|
|
288
|
+
cannot be reached as a subpath — `capture.mjs` in particular starts a server and
|
|
289
|
+
rewrites `fingerprint.json` the moment it is imported.
|
|
290
|
+
|
|
267
291
|
## Which copy of the icon and the concurrency is authoritative
|
|
268
292
|
|
|
269
293
|
`icon` and `maxConcurrency` are declared twice, and neither copy can be read by
|
|
@@ -288,8 +312,18 @@ about the commit rather than about whatever Bun was newest that morning.
|
|
|
288
312
|
Nothing here is installed with Bun — there are no dependencies — so Bun is the
|
|
289
313
|
tools only. `import.meta.main` is what decides whether `tools-smoke.mjs` runs its
|
|
290
314
|
gate or is being imported by a test, and a Node older than 22.18 does not define
|
|
291
|
-
it: the gate falls back to comparing the entry path
|
|
292
|
-
|
|
315
|
+
it: the gate falls back to comparing the entry path. That comparison resolves
|
|
316
|
+
both paths and then their real paths, so a symlink or a junction naming this file
|
|
317
|
+
is still recognised as this file, and a hard link is compared by device and inode
|
|
318
|
+
(only where the filesystem reports an inode at all). A question it cannot answer
|
|
319
|
+
exits non-zero saying nothing was asked — never a silent success, which is the
|
|
320
|
+
one outcome a release gate must not have.
|
|
321
|
+
|
|
322
|
+
Both scripts read their endpoint and model from the environment when the
|
|
323
|
+
defaults are wrong: `ZEN_BASE_URL`, `ZEN_CHECK_MODEL` (gate), `ZEN_CATALOG_URL`,
|
|
324
|
+
`ZEN_GENERATION` (audit) and `CAPTURE_PORT` (capture). `.env.example` lists them
|
|
325
|
+
with their defaults. Nothing loads a file by itself, so export them or copy that
|
|
326
|
+
file to `.env` and export from it.
|
|
293
327
|
|
|
294
328
|
## Publishing
|
|
295
329
|
|
|
@@ -305,6 +339,13 @@ that order, so the tarball, the commit the tag names, and the tree both were
|
|
|
305
339
|
built from are one thing. `magpie plugin add opencode-zen-freex` installs it by
|
|
306
340
|
that name.
|
|
307
341
|
|
|
342
|
+
What ships is `package.json`'s `files` list, and it is deliberately narrower than
|
|
343
|
+
the repository: the three maintenance scripts (`capture.mjs`, `tools-smoke.mjs`,
|
|
344
|
+
`limits-audit.mjs`) stay here, because a host never loads them and one of them
|
|
345
|
+
rewrites the shipped `fingerprint.json` if it is ever imported. `LICENSE` is
|
|
346
|
+
verbatim MIT; the fork's provenance is in `NOTICE`, so a licence scanner reads a
|
|
347
|
+
standard MIT file and a reader still finds why this fork exists.
|
|
348
|
+
|
|
308
349
|
**The second factor is a browser, not a password.** Run `npm publish` in a
|
|
309
350
|
terminal. When the account has 2FA on, the command stops and offers the whole
|
|
310
351
|
exchange at once:
|
package/NOTICE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# NOTICE
|
|
2
|
+
|
|
3
|
+
This package is a maintained fork of the OpenCode Zen Free plugin from
|
|
4
|
+
[magpie-community/plugins](https://github.com/magpie-community/plugins), which
|
|
5
|
+
is MIT licensed. Upstream's notice, `Copyright (c) 2026 xiaozhou26`, is kept in
|
|
6
|
+
[LICENSE](./LICENSE) alongside this fork's own.
|
|
7
|
+
|
|
8
|
+
The fork is maintained separately from upstream under the name
|
|
9
|
+
`opencode-zen-freex` (https://github.com/tkhs101/OpenCode-Zen-FreeX) because the
|
|
10
|
+
free tier's request fingerprint moves faster than that repository follows it:
|
|
11
|
+
the tool wording and User-Agent Zen accepts can be recaptured here with
|
|
12
|
+
`bun run capture`, without waiting for a release upstream. `MAINTAINING.md`
|
|
13
|
+
describes that loop.
|
|
14
|
+
|
|
15
|
+
The provider id this package registers, `opencode-zen-free`, is deliberately the
|
|
16
|
+
same one upstream's plugin registers — a model is written
|
|
17
|
+
`opencode-zen-free/<model>` either way — so the two are mutually exclusive;
|
|
18
|
+
install one.
|
package/README.md
CHANGED
|
@@ -66,7 +66,9 @@ bun run check:gate
|
|
|
66
66
|
| `floor` | `required` 里的名字 | 宿主一个都没有时,插件替它补上的那几个。**这才是插件真实发出的形状,退出码由它决定** |
|
|
67
67
|
| `full` | 这一代携带的全部工具 | 宿主本来就是 OpenCode 时发出来的形状。用来看措辞有没有过期,不决定退出码 |
|
|
68
68
|
|
|
69
|
-
每一代的结论是 `answered`(正常应答)、`refused`(被拒,403)、`superseded`(426——Zen 要求的 OpenCode
|
|
69
|
+
每一代的结论是 `answered`(正常应答)、`refused`(被拒,403)、`superseded`(426——Zen 要求的 OpenCode 比这份指纹声称的更新)、`failed`(Zen 答了,但不是 403,所以它没有对指纹表态)、`malformed`(请求根本没法构造:`required` 里的某个名字在 `fingerprint.json` 里没有对应的工具。Zen 没被问到,那一行会点名是哪一代、哪种形状、缺的是哪个名字)或 `unreachable`(根本联系不上)。只要还有一代的 `floor` 被应答,退出码就是 0;`malformed` 不算应答,所以整轮都是它的时候退出码非 0,并且会说明这是指纹文件的问题、不是网络的问题。
|
|
70
|
+
|
|
71
|
+
某个 `floor` 被拒、而它自己的 `full` 被应答时会单独点出来:那意味着带着自己那套 OpenCode 工具来的宿主能用,而插件真正要服务的那种宿主会被拒。全部联系不上时它不会说指纹过期,只会说问不到——这两件事得分开,而且最后的判断只读 `floor` 那几行:某个 `full` 请求拿到 5xx 说明的是那次请求,不足以把「没有一代被服务」说成「去查网络」。
|
|
70
72
|
|
|
71
73
|
闸门问的时候用的就是插件自己那份请求:请求头的名字来自 `provider.mjs` 里的 `outboundHeaders`,工具来自插件所顶替的同一份 `required`。所以它绿了是在说「插件真发的那个请求」被应答了,而不是在说「照抄的一份」被应答了——照抄的那一份曾经少发一个 `accept`,而且没有任何东西因此报错。
|
|
72
74
|
|
|
@@ -105,7 +107,7 @@ Zen 的免费层读请求体,也读请求头,只有当整个请求都属于
|
|
|
105
107
|
| `x-opencode-client` | `cli` |
|
|
106
108
|
| `authorization` | `Bearer public`;走 Messages 时是 `x-api-key: public` |
|
|
107
109
|
|
|
108
|
-
`anthropic-version`
|
|
110
|
+
`anthropic-version` 在走 Messages 时总是发出去(宿主设了就用它的,没设就是 `2023-06-01`);`anthropic-beta` 只在宿主自己设了的时候才发。
|
|
109
111
|
|
|
110
112
|
`x-opencode-request`、`x-session-affinity` 和 `x-session-id` 都在 OpenCode 自己的请求头集合里,但没有一个是必需的,所以都不发——不过进来的同名请求头仍然会被读,用来判断这次请求属于哪个会话。这不是「一个个试过不行」得出的结论,2026-10-15 实测(1.x,其余不动):
|
|
111
113
|
|
|
@@ -119,7 +121,7 @@ Zen 的免费层读请求体,也读请求头,只有当整个请求都属于
|
|
|
119
121
|
|
|
120
122
|
两个各自无害的头放在一起会被拒,所以「某个头能不能加」不能靠单个试出来。测试按名字钉住了发出的这组请求头,因为多加一个请求头是弄坏这个插件最容易的方式。
|
|
121
123
|
|
|
122
|
-
Zen 的免费层会检查 session id 是否是 OpenCode 能生成的那种:时间戳加每毫秒计数器截成十二位十六进制,后面接十四个随机的 base62 字符;session 的那一种还会把低位取反。宿主给的 id 如果本来就是这个形状,就原样传出去;否则按会话的首条消息生成一个并记住,这样同一会话的各轮请求会共用一个 session id
|
|
124
|
+
Zen 的免费层会检查 session id 是否是 OpenCode 能生成的那种:时间戳加每毫秒计数器截成十二位十六进制,后面接十四个随机的 base62 字符;session 的那一种还会把低位取反。宿主给的 id 如果本来就是这个形状,就原样传出去;否则按会话的首条消息生成一个并记住,这样同一会话的各轮请求会共用一个 session id。宿主既不声明会话、又没有可作依据的用户消息时,这一轮就拿一个自己的 id——没有东西可以据以记住它,所以它不会被并进另一个会话里去。
|
|
123
125
|
|
|
124
126
|
偶尔 Zen 会在发出 `[DONE]` 之前就结束一个流式的 Chat Completions 回复。如果回复已经说了内容、且其中没有未完成的工具调用,就以普通回答结束,正文里会说明它被截断了;再问一次就会接着往下走。如果是带着未完成的工具调用被截断、或者什么都没说就断了、或者被折叠给一个不支持流式的客户端,请求就会失败,错误信息里写明这个回复的 id 和收到了多少。走 Anthropic Messages 或 Responses 的回复被截断同样会失败:magpie 能把它和完整的回复区分开,并且在该模型支持的情况下继续往下走。
|
|
125
127
|
|
|
@@ -140,4 +142,4 @@ Zen 的免费层会检查 session id 是否是 OpenCode 能生成的那种:时
|
|
|
140
142
|
|
|
141
143
|
## 许可
|
|
142
144
|
|
|
143
|
-
MIT,分叉自 [zen-free](https://github.com/magpie-community/plugins/tree/main/packages/zen-free)
|
|
145
|
+
MIT,分叉自 [zen-free](https://github.com/magpie-community/plugins/tree/main/packages/zen-free)。版权署名:`Copyright (c) 2026 xiaozhou26` 是上游原作者,本分支的贡献者另有署名。`LICENSE` 是原样的 MIT 正文,本分支的来源与维护缘由写在 [NOTICE](https://github.com/tkhs101/OpenCode-Zen-FreeX/blob/main/NOTICE),这样许可证扫描器读到的是一份标准的 MIT 文件,而想知道这个分支为什么存在的人仍然找得到答案。
|
package/core-tools.mjs
CHANGED
|
@@ -18,31 +18,109 @@
|
|
|
18
18
|
// because those belong to the machine the capture ran on.
|
|
19
19
|
|
|
20
20
|
import { readFileSync } from "node:fs";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
+
const CAPTURE = new URL("./fingerprint.json", import.meta.url);
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
// Read once, here, because every answer in this module comes out of it. Guarded
|
|
26
|
+
// because the failure this replaces is a bare ENOENT or SyntaxError thrown out of
|
|
27
|
+
// whichever module imported this one, which reads as *that* module being broken
|
|
28
|
+
// and never names the file to fix. One message covers both, since the remedy is
|
|
29
|
+
// the same for either: only a capture writes this file.
|
|
30
|
+
function readProfiles() {
|
|
31
|
+
let captured;
|
|
32
|
+
try {
|
|
33
|
+
captured = JSON.parse(readFileSync(CAPTURE, "utf8"));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`core-tools: could not load the capture at ${fileURLToPath(CAPTURE)}; ` +
|
|
37
|
+
"missing or invalid JSON — run `bun run capture` to write a new one",
|
|
38
|
+
{ cause: error },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
// An object carrying a non-empty map of profiles, and nothing more specific:
|
|
42
|
+
// which generations those are is the capture's to say — capture.mjs writes
|
|
43
|
+
// next.profiles[generation] — so the keys are never named here. A capture that
|
|
44
|
+
// lost every profile cannot tell one generation from another and fails now
|
|
45
|
+
// rather than at the first request.
|
|
46
|
+
const profiles = captured?.profiles;
|
|
47
|
+
if (
|
|
48
|
+
typeof profiles !== "object" ||
|
|
49
|
+
profiles === null ||
|
|
50
|
+
Array.isArray(profiles) ||
|
|
51
|
+
!Object.keys(profiles).length
|
|
52
|
+
) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`core-tools: the capture at ${fileURLToPath(CAPTURE)} has no profiles; ` +
|
|
55
|
+
"run `bun run capture` to write a new one",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return profiles;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const PROFILES = readProfiles();
|
|
25
62
|
|
|
26
63
|
/**
|
|
27
64
|
* The generation a request belongs to, told by the tools it carries. Tool names
|
|
28
65
|
* move far less often than descriptions do, so this survives a rewording that a
|
|
29
66
|
* pinned User-Agent would not.
|
|
67
|
+
*
|
|
68
|
+
* `undefined` is a normal return: it says neither generation's names were there,
|
|
69
|
+
* and the callers default it themselves (`provider.mjs` uses `?? DEFAULT_GENERATION`).
|
|
70
|
+
* A body whose `tools` is not an array is the same answer as an empty one — the
|
|
71
|
+
* body is client-supplied, so it can carry any shape at all, and "no generation
|
|
72
|
+
* recognised" is the only honest reading of one that is not a list of tools.
|
|
30
73
|
*/
|
|
31
74
|
export function generationOf(tools) {
|
|
32
|
-
const names = new Set(
|
|
75
|
+
const names = new Set(
|
|
76
|
+
(Array.isArray(tools) ? tools : []).map((tool) => tool?.function?.name ?? tool?.name),
|
|
77
|
+
);
|
|
33
78
|
if (names.has("shell")) return "2.x";
|
|
34
79
|
if (names.has("bash")) return "1.x";
|
|
35
80
|
return undefined;
|
|
36
81
|
}
|
|
37
82
|
|
|
83
|
+
// The profile a generation was captured into, or an Error naming both what was
|
|
84
|
+
// asked for and what the capture holds. Ownership rather than truthiness: a
|
|
85
|
+
// plain object answers `constructor` and `toString` out of Object.prototype, so
|
|
86
|
+
// `PROFILES[generation]` alone would hand back a function and then fail on its
|
|
87
|
+
// `.agent` with a TypeError that names nothing.
|
|
88
|
+
function profileFor(generation) {
|
|
89
|
+
if (!Object.hasOwn(PROFILES, generation)) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`core-tools: no profile for generation ${JSON.stringify(generation)}; ` +
|
|
92
|
+
`the capture holds ${Object.keys(PROFILES).join(", ")}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return PROFILES[generation];
|
|
96
|
+
}
|
|
97
|
+
|
|
38
98
|
/**
|
|
39
99
|
* The User-Agent that generation of OpenCode sends, for a protocol. Each
|
|
40
100
|
* protocol's ai-sdk package bundles its own provider-utils, so 1.x differs by
|
|
41
101
|
* protocol and 2.x does not.
|
|
42
102
|
*/
|
|
43
103
|
export function agentFor(generation, protocol = "chat") {
|
|
44
|
-
const agent =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
104
|
+
const agent = profileFor(generation).agent;
|
|
105
|
+
if (typeof agent !== "object" || agent === null) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`core-tools: the ${JSON.stringify(generation)} profile in the capture has no agent; ` +
|
|
108
|
+
"run `bun run capture` to write a new one",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
// The release shape carries no provider-utils at all, and is composed the same
|
|
112
|
+
// way whatever protocol was asked for.
|
|
113
|
+
if (!agent.providerUtils) return `opencode/${agent.channel}/${agent.opencode}/${agent.client}`;
|
|
114
|
+
// `?? "undefined"` used to be interpolated here for a protocol the capture has
|
|
115
|
+
// no provider-utils for, and a prototype member (`toString`) as the version.
|
|
116
|
+
// Zen reads this header, so a malformed one is rejected upstream with an error
|
|
117
|
+
// that says nothing about where it came from; name the miss instead.
|
|
118
|
+
if (!Object.hasOwn(agent.providerUtils, protocol)) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`core-tools: generation ${JSON.stringify(generation)} composes its User-Agent ` +
|
|
121
|
+
`per protocol, and the capture has no provider-utils for ${JSON.stringify(protocol)}; ` +
|
|
122
|
+
`it holds ${Object.keys(agent.providerUtils).join(", ")}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return `opencode/${agent.opencode} ai-sdk/provider-utils/${agent.providerUtils[protocol]} ${agent.runtime}`;
|
|
48
126
|
}
|
package/index.mjs
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import plugin from "./provider.mjs"
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// A host resolves a plugin module and reads the factory off it. Which shape it
|
|
4
|
+
// reads is the host's business and not this package's to narrow: magpie builds
|
|
5
|
+
// one from `default.server`, which is the shape provider.mjs already exports, so
|
|
6
|
+
// that object is forwarded whole rather than the factory being renamed into a
|
|
7
|
+
// shape nothing else in the package uses. The `id` comes with it, because a
|
|
8
|
+
// loader that keys plugins by id needs the same one the provider registers — and
|
|
9
|
+
// it is the provider's own id, not a second copy of it: it is read from the
|
|
10
|
+
// module, so the two cannot drift.
|
|
11
|
+
//
|
|
12
|
+
// The named export stays for anything that already imported it by name, and
|
|
13
|
+
// `server` is offered as a name too, so `mod.default.server` and `mod.server`
|
|
14
|
+
// both resolve.
|
|
7
15
|
if (typeof plugin?.server !== "function") {
|
|
8
16
|
throw new TypeError("provider.mjs must default-export a plugin with a `server` factory")
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
export const ZenFreePlugin = plugin.server
|
|
20
|
+
export const server = plugin.server
|
|
21
|
+
export default plugin
|