把 OpenCode Zen 的匿名免费模型接进 ZCode,像内置模型一样在模型选择器里直接选。
仿照 JasonYu0822/opencode2pi-desktop (MIT)写的 ZCode 版。那边是把 OpenCode 免费模型接进 PI-Desktop,这边接进 ZCode。
原项目是个 PI-Desktop 插件,靠两样东西工作:
| PI-Desktop 能力 | 作用 |
|---|---|
contributes.providers(manifest 静态声明) |
让宿主在启动插件进程之前就枚举到 provider,模型选择器里才有这一组 |
contributes.services(后台服务) |
插件进程常驻一个回环 HTTP 端点,承接宿主发来的对话请求 |
ZCode 的插件没有这两样。 ZCode 的 .zcode-plugin/plugin.json 只能贡献四类组件:
skills / commands / hooks / mcpServers
官方 manifest 参考还明确写了 Codex 的 interface、可用性/鉴权 policy、.app.json
不属于 ZCode 的契约。也就是说:ZCode 插件无法注册 provider,也无法常驻后台服务,
原项目那个架构在 ZCode 上没法照搬。
但 ZCode 在配置层有等价能力,而且更正式:个人 Provider 配置。 所以本工具改成「本进程跑回环代理 + 把 provider 声明写进 ZCode 的个人 Provider 配置」, 对应关系如下:
| opencode2pi-desktop | opencode2zcode |
|---|---|
manifest.json 的 contributes.providers(静态声明) |
写 provider_config.json 的 providerConfigRules |
| 改写 manifest 并重载插件来更新模型列表 | 改写 provider_config.json,ZCode 每 1 秒轮询该文件,热生效、不用重启 |
contributes.services 的 zen-proxy 回环端点 |
本工具的 zen-proxy.mjs(需你自己启动) |
authKind: "none",宿主不存密钥 |
access.type: "api-key" + apiKey: "public"(匿名占位,不是密钥) |
| 模型元数据内联在 provider 声明里 | 走 modelConfigRules.providerModelRules(smart 车道,部分稀疏配置) |
两个 provider 分列,沿用原项目「一个 provider 只绑一种线上协议」的规则:
opencode-free(openai-chat-completions)和 opencode-free-responses(openai-responses)。
下载 opencode2zcode.exe 这一个文件就够了——2.0 MB,自包含,对方不需要装
任何东西(不用 Node.js、不用配环境变量、不用改 PATH)。
装一次,之后再也不用管:
opencode2zcode.exe --install
它往 ZCode 的 CLI 配置里写一条 SessionStart 钩子,之后每次启动 ZCode 都会
自动把代理拉起来——没有黑窗口,不用双击,不用记着开。装完直接去
ZCode 的 设置 → 模型服务 看「OpenCode 免费模型」分组。
想彻底还原:
opencode2zcode.exe --uninstall
会删掉那条钩子、并把本工具的两个 Provider 从你的个人配置里摘掉,你的其它 Provider 和其它钩子一个字都不动。
源码包(想自己改代码 / 想看实现)
rust/ 是 Rust 源码,cargo build --release 产出同一个 exe。
仓库里还留着等价的 Node 实现 zen-proxy.mjs(需要 Node 18+),它既是
Rust 版的对照物,也是重写时的行为基准。两者由对拍测试保证输出一致。
不想装自启,手动跑(调试用)
node zen-proxy.mjs # 起代理 + 写配置,窗口要一直开着
opencode2zcode.exe # 同上,exe 形态窗口关掉代理就停。适合排查问题,不适合日常用。
```bash
--install 装上「随 ZCode 自动启动」(幂等,推荐)
--uninstall 卸掉钩子 + 摘掉本工具的两个 Provider
--ensure 检查代理在不在,不在就静默拉起(供钩子调用,一般不用手敲)
--detect 只列出候选数据目录与判据
--print-config 只打印将要写入的配置(所有 apiKey 已脱敏),不落盘不起服务
--write-config-only 只写配置后退出
--remove-provider 只从 ZCode 配置里摘掉本工具的两个 Provider
--port <n> 指定回环端口,默认 41930
--config <path> 直接指定 provider_config.json 完整路径
--help 帮助
跑自测(本地 mock 上游,不打真实网络,22 项):
node selftest.mjs| 变量 | 说明 |
|---|---|
ZEN_BASE_URL |
覆盖上游根地址,默认 https://opencode.ai/zen。指向本地 mock 便于自测 |
ZCODE_DATA_BASE_DIR |
个人 Provider 配置落点的最高优先级依据;没有它就自动探测,不是直接退到用户主目录 |
curl http://127.0.0.1:41930/healthz
curl http://127.0.0.1:41930/v1/models/healthz 会回目录状态(live / cache / static)、暴露的模型数、请求数与错误数。
与 ZCode 的 getAppConfigDir() 一致,形如:
{数据根}/.zcode/v2/provider_config.json
配置路径按这个顺序决定,不写死任何机器专属路径:
--config > ZCODE_DATA_BASE_DIR 环境变量 > 自动探测 > 旁路文件 > 用户主目录兜底
⚠ 最容易踩的坑:
ZCODE_DATA_BASE_DIR不是持久环境变量。 它注册表里没有(用户级、系统级都没有),只是 ZCode 启动自己子进程时注入的。 所以从资源管理器双击.cmd拿不到它——早先版本会直接退到用户主目录 (C:\Users\<你>\.zcode\v2\provider_config.json),而 ZCode 读的是另一个目录, 表现就是「代理明明在跑,设置里就是不出现模型分组」。
现在改为自动探测:给若干候选目录打分,判据是 {数据根}/.zcode/v2/runtime
是否存在(ZCode 准备运行资源时会建它),并会扫各固定盘的盘根与盘根下一层目录。
得分最高的那个就是答案,探测结果会记进 zcode-data-dir.txt 供 .cmd 走快路径。
想看探测明细:
node zen-proxy.mjs --detect没探测到时会打印警告,提醒你很可能写错了地方,而不是静默写错。
写入行为:
- 读取现有文件,只替换
opencode-free/opencode-free-responses这两个 providerId, 你已有的其它 Provider(GPT / Grok / DeepSeek / OpenCode Zen …)原样保留,providerOrder也保留。 - 写入前在同目录留一份
provider_config.json.bak-<时间戳>。 - 原子写(同目录临时文件 + rename)。
- 目录内容真实变化时才重写;每 5 分钟的刷新若没变化不会白写。
移植自原项目 lib/zen.js 的部分:
- CLI 一致的伪装头 —
user-agent: opencode/1.18.31 (…)、x-opencode-client: cli, 以及会话/请求/项目三族关联头。session id 按对话首条用户消息派生, 形状规范化为ses_ + 12hex + 14base62,同一对话恒定。 - 体门禁 — 原项目实测:不流式、或缺名为
bash和read的函数工具的请求直接 403。 所以强制stream: true;缺哪个补哪个(描述和参数上游不检查); 原本一个工具都没有时补tool_choice: "none",免得模型真去调这两个占位桩。 - SSE 逐块透传 — 两端同一套方言,不重编码,流式零损耗。
- 目录 — S1(Zen
/v1/models,带伪装头)∩ S2(models.dev 里判为免费且未下架), 5 分钟刷新;磁盘缓存 7 天。没有编译期写死的模型名单——网络断了或 目录失效时,用「最近一次成功解析的结果」(磁盘缓存 + 内存快照)顶上; 连一次都没成功过就如实返回空,绝不编一份会过期的假名单。
桌面版的判定就一行(packages/app/src/components/dialog-select-model.tsx):
const isFree = (provider, cost) => provider === "opencode" && (!cost || cost.input === 0)本工具照搬,并额外排除 status == "deprecated"。三点容易踩:
- 只看
input,不看output;并且「没有 cost 数据」也算免费。 - 必须排除已下架。Zen 把下架模型留在公开
/models和目录里,标价仍是 0。 不排除的话,模型选择器里会出现点下去必报错的选项。 - 因此「目录标称免费」里过滤后剩下的与桌面版完全相同,数量随上游增减
自动跟随,不在这里列死名单。某个时刻的样子可以用
curl http://127.0.0.1:41930/v1/models看。
早先版本要求
input和output都为 0、且不排除下架,于是多放进来了mimo-v2.5-free和muse-spark-1.2-contributor-free(两个都已下架)。selftest.mjs里有针对这一条的回归测试。
一个模型走哪条线,看官方目录里它自己声明的 SDK:@ai-sdk/openai 的挂
/v1/responses,openai-compatible 的走 /v1/chat/completions,不再按模型名
前缀猜。两条都要过同一道免费档门禁——请求体里必须有
bash 和 read 两个工具定义,缺了就是:
403 OpenCode's free tier can only be used from within OpenCode
这个门禁是纯请求体判定,跟账号、key、地区都无关:没登录的 OpenCode 桌面版 同样能调通,本工具也能。踩过的两个坑:
| 坑 | 后果 |
|---|---|
responses 道补工具时套了 chat 道的嵌套格式 {type,function:{name}} |
上游 400 tools[0] missing required field name |
照搬了 chat 道的 tool_choice: "none" |
上游 400,Responses API 只接受 auto / required / 具体工具名 |
第二个坑特别隐蔽:当时据此下了「responses 道过不去、没有救」的结论。真相是
门禁规则和 chat 道完全一样,只是线格式有别——工具定义要扁平,tool_choice
一个都不能带。
定位方法值得记一下:光靠猜是猜不出来的。把
XDG_CONFIG_HOME指向临时目录、 在配置里把 opencode provider 的baseURL改到本地,就能让真的 OpenCode CLI 把它的原始请求发过来(rust/tests/capture-opencode-request.mjs), 再拿抓到的请求对着上游逐项减字段回放(replay-gate-probe.mjs), 二分出到底是哪个字段在起作用。用户一句「我桌面版没登录也能用」直接推翻结论。
活例:fledge-alpha-free —— OpenCode 桌面版已经不再列它,但它在本工具里照常可用
(实测 2026-10-02 返回正常回复)。
原因是两边数据源不同:
| 数据源 | |
|---|---|
| OpenCode 桌面版 | OpenCode 服务端按账号下发的清单(需要登录令牌,我们拿不到) |
| 本工具 | Zen 实时 /v1/models ∩ models.dev(第三方目录) |
桌面版撤下某个模型的动作发生在它的服务端清单上,models.dev 不一定同步。于是会出现 两种分歧,都属正常:
- 桌面版没有、我们有(fledge 就是这样——两个数据源此刻都还挂着它,且实测能用)
- 桌面版有、我们没有(上游上架但 models.dev 还没收录的新模型,要等目录更新)
判定规则本身仍与桌面版的 isFree 逐位一致(见上),差异只来自"清单来源"而非"判定口径"。
一个佐证:fledge-alpha-free 在 models.dev 里 cost=0、status 未标废弃,Zen 实时清单
也仍有它——两个我们能拿到的权威源都说它该在。
取舍:能用就留着。 多一个能用的免费模型,代价只是它不在桌面版的名单里。
数据拿得到,但显示不了——卡在 ZCode 那边,不是卡在本工具。
- 拿得到:评分要用的六项(上下文窗口、最大输出、推理档位、工具调用、多模态、 结构化输出)全都来自 models.dev,而本工具本来就在拉 models.dev。想算的话 在这里加十行就够了,不需要额外数据源。
- 写不进:ZCode 的模型配置 schema 是
.strict()白名单,只认这些叶子——contextWindow、inputFormat(supportsImage/supportsVideo/supportsPdf)、outputFormat、supportsToolCall、supportsJsonSchemaOutput、supportsNativeWebSearch、supportsMidConversationSystem, 以及optionSpecs下的reasoningLevel和maxOutputTokens。 没有description、没有评分、没有显示名/别名,多写的键会被直接丢弃。 模型 ID 也不能拿来改名——它要原样发给上游 API。
所以本工具不硬塞评分。界面上真实可见的是:上下文窗口、最大输出、思考档位阶梯
(这三项能间接反映模型的思考深度),外加模型 ID 本身携带的名字信息。
需要横向对比评分,用同作者的 provider-fetcher 查那张表。
万一将来 ZCode 加了这个字段,这里补上就是改
buildModelRule一个函数的事。
原项目写于 2026-09,实测下来今天有两处已经不成立,这里是改过的:
① reasoning_effort 不再「一律归一成 none」。
原项目的策略是 off / 缺失 / 不认识的一律写成 "none",注释说这是关掉 always-think 的唯一写法。
现在这样会直接把请求打成 400。 逐模型实测(2026-10):
| 模型 | off |
none |
minimal…max |
省略该字段 |
|---|---|---|---|---|
big-pickle |
400 | 400 | 200 | 200 |
space-bunny-free |
400 | 400 | 200 | 200 |
nemotron-3.5-lightning-free |
400 | 200 | 200 | 200 |
mimo-v2.5-free |
500 | 200 | 时好时坏 | 200 |
档位逐模型不一致,所以改成:只透传上游确实认的主动档位(minimal→max);
off / none / 缺失 / 未知一律省略该字段,退回厂商默认;UI 档位阶梯也不再提供
off(提供一个上游必然拒绝的档位是骗人的)。另外 handleUpstream 里加了一次兜底:
只在「确实带了 reasoning_effort」且上游回 400/422/500 时,摘掉该字段重试一次。
② 模型目录用 smart 车道而不是 manual。
ZCode 个人配置有两条模型元数据车道:providerModelRules(smart,接受部分稀疏配置)
和 manualProviderModelRules(manual,要求把 contextWindow / inputFormat /
optionSpecs 等叶子填满,且同一 Provider+Model 不能两条道并存)。
本工具走 smart —— 这也是 ZCode 内置模板和你本机已有自定义 Provider 实际在用的那条。
已验证可用:
GET /healthz -> 200,目录 source=live,84 个模型,暴露 9 个
GET /v1/models -> chat 道全部模型(随上游增减)
POST /v1/chat/completions big-pickle -> 200,SSE 流式正常返回,模型回了 "OK"
同上,带 reasoning_effort:"none" -> 200(档位被摘掉,不再 400)
同上,带 reasoning_effort:"high" -> 200(原样透传)
POST /v1/responses muse-spark-1.3-... -> 200,SSE 正常,模型回了 "OK"
POST 不存在的模型 -> 上游 ModelError + 401,原样透传
非回环 Host 访问 /healthz -> 403 loopback requests only
POST /v1/responses 带 tool_choice:"none" -> 代理摘掉后 200(带上去上游会 400)
cargo test 25/25
node rust/tests/cli-parity.mjs 18/18 Node 版 vs Rust 版,配置逐字节一致
node rust/tests/live-mock.mjs 21/21 端到端打本地 mock
node selftest.mjs 23/23 Node 版回归
node rust/tests/fresh-machine-dryrun.mjs 29/29 新机器演练
配置里的模型数随上游自动增减,不做上限:opencode-free(chat 道)+
opencode-free-responses(responses 道,目前只有 muse-spark)。判定规则与
OpenCode 桌面版逐位一致,桌面版列多少这里就写多少。/healthz 里的 exposed
比分道统计略多——jev-* 系列两条道都不走(只答 /systemone),本来就不该进配置。
已知限制,都不是本工具的 bug:
- 匿名通道按 IP 限速。 429 会原样呈现,等一会儿就好。
- 本质是客户端伪装,上游随时可能收紧。 免费通道只允许在 OpenCode 内部使用, 本工具靠伪装请求头 + 体门禁混过去。没盗用任何账号或密钥,但确实是在冒充身份 过一道对方明确设了限制的关卡。原项目自己也写明「不保证可用性」。
responses道调不通(见上一节),所以默认不暴露 muse-spark。
| 现象 | 处理 |
|---|---|
| 设置里看不到「OpenCode 免费模型」 | 先查配置写对地方没有:跑 node zen-proxy.mjs --detect 看它打算写到哪。正常路径下窗口里会打印 [zen] 配置目标(…):<路径> 和 [zen] 已同步 ZCode 配置:<路径>,路径不对就用 --config <完整路径> 指定。ZCode 每 1 秒轮询配置文件,写完稍等即可,不用重启 |
| 同上,但路径是对的 | 确认代理在跑:curl http://127.0.0.1:41930/healthz。没响应就是钩子没拉起来,先手动 node zen-proxy.mjs 看报错 |
| 模型列表比预期少 | 看 /healthz 的 catalog.status 与 lastError。网络中断时用最近一次成功解析的结果顶上(来源是磁盘缓存,7 天有效);缓存也没有就如实空着,等网络恢复自动补齐。目录里判定为「已下架」的模型会被有意剔掉 |
对话报 403 FreeTierError |
伪装头或体门禁没过,上游收紧了。x-opencode-client / user-agent 的版本号可能要跟着官方 CLI 更新 |
| 对话报 429 | 匿名通道按 IP 限速,等一会儿 |
| 模型数和文档里写的不一样 | 正常——清单随上游自动增减,以 curl http://127.0.0.1:41930/v1/models 实时结果为准 |
| WebSearch 报 "does not support native WebSearch" | ZCode 的「原生联网搜索」只在 Anthropic API 管道上有实现(映射到 Anthropic 服务端的 web_search 工具),OpenAI 兼容管道——包括本工具——没有编码,声明了能力位也会在请求时被拒。这是 ZCode 的功能边界,本工具无法绕过。读具体网址用 WebFetch(走的是另一条路,本工具已适配) |
对话报 400 invalid request |
看代理 stderr 有没有「摘掉该字段重试」的日志;若重试也 400,多半是这个模型当前不可用 |
| 想撤销 | 双击 uninstall.cmd(钩子和 Provider 一起清干净)。只想清 Provider 就 node zen-proxy.mjs --remove-provider;只想停自启就 node zen-proxy.mjs --uninstall |
代理的日志都走 stderr,--print-config 的 JSON 走 stdout,方便重定向。
--print-config会把所有 provider 的apiKey替换成***已隐藏***再打印, 不会把你已有的 GPT / Grok / DeepSeek 密钥打到终端上。- 本工具自身不落任何密钥:匿名通道的 Bearer 是写死的
public,不是密钥。 - 写配置前一定留备份;只替换自己那两个 providerId,不动你的其它 Provider。
- 出站网络只有两处:
opencode.ai(对话与目录)和models.dev(模型元数据)。 无遥测、无第三方服务器。
bin/opencode2zcode.exe 唯一要分发的文件(2.0 MB,自包含,对方零依赖)
README.md 本文件,给人看的说明
AI-SETUP.md 给 AI 助手看的安装说明(拿到包的是 AI 时先读那份)
rust/ Rust 源码(cargo build --release 产出上面那个 exe)
src/zen.rs 伪装头、会话 id、免费通道门禁、思考档位归一
src/catalog.rs 模型目录与「免费」判定
src/zcode.rs ZCode 个人 Provider 配置的合并/写入/摘除
src/detect.rs ZCode 数据目录探测
src/autostart.rs 自启钩子的装/卸 + 静默拉起
src/proxy.rs 回环服务与 SSE 透传
tests/cli-parity.mjs Node 版 vs Rust 版对拍
tests/live-mock.mjs 端到端(本地 mock 上游)
zen-proxy.mjs 等价的 Node 实现,留作行为基准与对照(需 Node 18+)
selftest.mjs Node 版自测,22 项
zcode-data-dir.txt 运行后自动生成,记录探测到的数据根;不要拷到别的电脑
只需要 opencode2zcode.exe 一个文件(2.0 MB)。 微信、邮件、U 盘随手发都行。
对方把它放哪都行,然后跑一条命令:
opencode2zcode.exe --install
完事。不用装 Node.js、不用装任何运行时、不用配环境变量。
包里不含任何机器专属路径——ZCode 的数据目录靠自动探测(扫各固定盘的
{数据根}/.zcode/v2/runtime 指纹),所以跨机器直接可用。
让那个 AI 先读 AI-SETUP.md,里面有逐步命令、每步的期望输出、以及各种失败分支。
第 6 节就是安装自启那一步。
ZCode 的钩子必须写绝对路径,所以安装命令会把当时那个位置的可执行文件路径 记进 ZCode 的配置。这就带来一条规则:
必须在「最终摆放位置」上执行安装。 对方把文件解压到哪、就在哪运行——不要先在 A 处运行、再挪到 B 处。
如果确实挪过了,到新位置重跑一次安装命令就行:它会认出自己的旧钩子并替换掉, 指向已不存在的旧路径那条会被自动清掉,不会越堆越多。
(为什么非得是绝对路径:ZCode 的钩子变量 ${ZCODE_PROJECT_DIR} 是工作区相对
路径,指向的是用户打开的项目文件夹,不是本工具的安装位置,用不上。)
--install 往 ZCode 的 CLI 配置({用户目录}/.zcode/cli/config.json)里加一条:
{ "hooks": { "enabled": true, "events": { "SessionStart": [
{ "hooks": [ { "type": "process",
"command": "<本程序的绝对路径>",
"args": ["--ensure"],
"timeoutMs": 12000 } ] } ] } } }源码形态下 command 是 node 的绝对路径、args 是 [zen-proxy.mjs 的路径, "--ensure"];
exe 形态下 command 直接是那个 exe。
几点说明:
process类型而不是command,直接给可执行文件 + 参数数组,不过 shell,Windows 上最稳。- 路径用绝对路径写死,免得目标机 PATH 里什么都没有时钩子静默失效; exe 形态还会把 8.3 短名还原成长路径,那种短名在目录被重建后会失效。
- 钩子是同步执行的,
async字段不生效。所以--ensure自己负责快速判断: 代理在就直接退出(毫秒级),不在才拉起来并最多等 6 秒,无论成败退出码恒为 0, 绝不影响 ZCode 开会话。 - 子进程用
detached+windowsHide拉起,完全脱离且不弹窗——这就是「看不见黑窗口」的来源。 - 有单实例保护(排他锁文件 + 端口健康检查)。否则同时开两个 ZCode 会话时,
第二个实例会绑到 41931 并把配置里的
baseUrl改掉,用户会突然发现模型不能用。 - 认领自己的钩子靠文件名而不是完整路径,并且把已经废弃的
ensure-proxy.mjs、install.mjs也列进识别名单——不然早几版装过的人,那些指向已删文件的死钩子 会被当成「别人的钩子」永远留着,每次开 ZCode 都白跑一次。
边界:装的时候必须手动执行一次安装命令(或让 AI 做),因为往 ZCode 配置里
写东西这件事本身没法自动完成。装完之后就再也不用管了。--uninstall 能干净还原。
cd rust
cargo build --release
# 产物:rust/target/release/opencode2zcode.exe构建期只需要 Rust 工具链。对方拿到 exe 完全用不到任何构建工具。
cd rust && cargo test # 25 项单元测试
node rust/tests/cli-parity.mjs # 16 项:Node 版 vs Rust 版对拍
node rust/tests/live-mock.mjs # 21 项:端到端(本地 mock 上游)
node rust/tests/fresh-machine-dryrun.mjs # 29 项:模拟另一台电脑,从 zip 装一遍
node rust/tests/capture-opencode-request.mjs # 抓 OpenCode CLI 的真实请求
node rust/tests/replay-gate-probe.mjs # 对着真实上游逐项回放,定位门禁
node selftest.mjs # 22 项:Node 版自测对拍测试是这个项目的安全网。 重写最容易出的错是「一边改了另一边没改」——
比如 models.dev 的返回形状(供应商有时裹在 providers 下、有时直接摊在根上),
只认一种会让整个元数据判定静默失效,然后所有名字带 free 的模型都被放行,
下架的也混进来。对拍把两个实现放在同一组断言下跑,任何一边跑偏都会被抓到。
- 本项目与 OpenCode、ZCode 官方没有任何关系,也不是官方插件。
- 它通过模拟 OpenCode CLI 的请求特征,使用 OpenCode Zen 的匿名免费通道。
这条通道官方本意只开放给自家客户端使用——没有盗用任何账号或密钥
(凭据是公开的占位符
public),但确实是在对方设了限制的地方走了侧门。 - 上游随时可能改规则让它失效(2026-09-16 就收紧过一次会话 id 校验)。 用的时候请节制,不要拿它跑大批量任务把免费通道打爆。
- 本项目仅用于学习与个人使用,风险自负。如果你不接受这些,请直接注册 OpenCode 账号或使用其它正规渠道。
skills/opencode-probe/ 里带了配套的 ZCode 技能与命令:装到
~/.zcode/skills/opencode-probe/SKILL.md 和 ~/.zcode/commands/opencode-probe.md
之后,在 ZCode 里输入 /opencode-probe 就能一键体检所有免费渠道,
输出 S/A/B/C 档位、上下文窗口与 OpenRouter 社区热度,辅助选模型。
这个项目站在好几层前人的肩膀上,每一层都实名感谢:
- JasonYu0822/opencode2pi-desktop(MIT)—
本工具的直接移植来源 🫡。伪装头、体门禁、目录 S1∩S2、SSE 透传都出自它的
lib/zen.js,没有它就没有这个项目。本项目的 Responses 线修复也已回馈为它的 Issue 与 PR。 - FishBottle7/opencode2dsh(MIT,© FishBottle7)— 上一层的来源:伪装头与规范会话 id 形状的出处,架构文档写得极好。
- eric8bit/pi-commandcode-desktop(MIT)— 「回环端点 + 静态 provider 声明」这套架构模式的样板。
- jasonxu114514/opencode2api(GPL-3.0)—
整个伪装思路的祖师爷。本项目没有复制它的代码,其开创的协议参数
(头名称、会话 id 形状、门禁要求)是功能事实,已由本项目自己的抓包
独立复验(见
rust/tests/capture-opencode-request.mjs)。 - OpenCode — 匿名 Zen 免费通道的提供方,没有这份慷慨 就什么都不会有。