EdgeOne Cloud Functions · Node.js 20

OpenCode Zen 网关
OpenAI / Anthropic 双兼容

部署于 cloud-functions/ · 区域机房运行 · 可选区域

把 OpenCode Zen 包成标准 OpenAI 与 Anthropic 接口。免费模型自动识别与过滤、客户端身份伪装、 非流式请求自动折叠,每个响应都带 x-zen-* 诊断头。改 base_url 即可接入,无需改业务代码。

查看端点 打开测试台 /health
服务状态
检测中…
版本
—
免费模型数
—
当前域名
—

端点总览

所有路径都映射到上游 opencode.ai/zen。客户端路径会剥掉自定义前缀后转发, 因此 /api/zen/v1/models 与 /v1/models 落到同一个上游地址。

/api/zen/v1/chat/completions
/api/zen/v1/responses
/api/zen/v1/models
/api/anthropic/v1/messages
路径协议方法说明
/api/zen/v1/chat/completions openai-chat POST OpenAI Chat Completions,SSE 流式与 tool_calls 均支持
/api/zen/v1/responses openai-responses POST OpenAI Responses API
/api/zen/v1/models mixed GET HEAD 模型列表,按 FREE_MODELS_ONLY 过滤为免费档
/api/anthropic/v1/messages anthropic POST Anthropic Messages,tool_use 块完整还原
/v1/* /zen/v1/* /anthropic/* auto 按路径自动识别 兼容路径,方便 SDK 直连不带前缀
/health local GET 本地端点,返回 JSON 配置与路由清单,不走上游
/health/free-models local GET 本地端点,返回免费模型判定规则与样例命中情况
ℹ️ 静态资源优先。Cloud Functions 与 Pages 静态资源共存在同一项目里, 若请求命中已存在的静态文件(如 /test.html),平台会优先返回静态资源,函数不会被执行。 根路径 / 因此返回本落地页。

协议对照

同一套网关服务两套协议。网关按路径识别协议族,工具注入与 SSE 折叠都使用对应协议的原生格式。

项目OpenAI ChatAnthropic Messages
base_url BASE/api/zen/v1 BASE/api/anthropic
鉴权头 Authorization: Bearer <key> Authorization: Bearer <key> 或 x-api-key
工具格式 {type:"function", function:{name, parameters}} {name, description, input_schema}
流式标记 stream: true stream: true
工具调用还原 message.tool_calls[] content[].type === "tool_use"
非流式折叠目标 chat.completion message
✅ 非流式请求的折叠是协议正确的。免费档在客户端未指定流式时,网关会强制改为流式请求上游, 再把 SSE 折回客户端协议的原生结构 —— Anthropic 客户端收到的是 type: "message" 与 tool_use 块,而不是 OpenAI 的 chat.completion。

接入示例

示例统一使用免费档模型 big-pickle。免费档推荐流式调用。

// baseURL 指到 /api/zen/v1 即可,其余写法与 OpenAI 官方 SDK 完全一致
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "BASE/api/zen/v1",
  apiKey: "你的 Zen Key",   // 或服务端配置的 ACCESS_TOKEN
  dangerouslyAllowBrowser: true,
});

const r = await client.chat.completions.create({
  model: "big-pickle",              // 免费档
  messages: [{ role: "user", content: "hi" }],
  stream: true,                        // 免费档推荐流式
});

for await (const c of r) {
  process.stdout.write(c.choices[0].delta.content || "");
}
from openai import OpenAI

client = OpenAI(
    base_url="BASE/api/zen/v1",
    api_key="你的 Zen Key",
)

r = client.chat.completions.create(
    model="big-pickle",
    messages=[{"role": "user", "content": "hi"}],
    stream=True,
)
for chunk in r:
    print(chunk.choices[0].delta.content or "", end="")
curl BASE/api/zen/v1/chat/completions \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "big-pickle",
    "messages": [{"role":"user","content":"hi"}],
    "stream": true
  }'
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  baseURL: "BASE/api/anthropic",
  apiKey: "你的 Zen Key",
});

const r = await client.messages.create({
  model: "claude-free",
  max_tokens: 1024,
  messages: [{ role: "user", content: "hi" }],
});

// r.type === "message",r.content[] 可能是 text 或 tool_use 块
for (const b of r.content) {
  if (b.type === "text") process.stdout.write(b.text);
}

免费模型识别与过滤

FREE_MODELS_ONLY=true(默认)时,模型列表只返回免费档。 判定使用规则集而非固定名单,上游新增免费档通常能被自动识别。

🧩

判定规则

按优先级依次匹配,任一命中即视为免费档:

1. 已知免费名单(精确匹配)
2. FREE_MODEL_EXTRA 白名单(环境变量)
3. 词元级:free 前后为分隔符或边界
4. OpenRouter 风格 :free 后缀
5. -free / _free 变体
🎛️

免费档带来的行为变化

命中免费档时,网关会自动做三件事,目的是绕过上游对非官方客户端的限制:

· 伪装官方 opencode CLI 身份(UA + 多个 x-opencode-* 头)
· 注入 bash / read 两个工具
· 未指定流式时强制改为流式,再折叠回原生协议

付费模型不注入工具,响应头 x-zen-tools 会标注 skipped(paid)。

模型 ID判定为免费档

如需放行更多模型,把模型 ID 填入环境变量 FREE_MODEL_EXTRA(逗号分隔)。

诊断响应头

每个响应都带 x-zen-* 头。排查问题时先看这几个头,比翻日志快。 浏览器里按 F12 → Network → 任一条请求的 Response Headers 即可看到。

响应头取值用途
x-zen-runtime cloud-functions 固定值,确认跑在 Cloud Functions 而非 Edge Functions
x-zen-protocol openai-chat / openai-responses / anthropic / unknown 网关识别到的协议族,决定工具注入与折叠格式
x-zen-auth-source server-key / client-authorization / anonymous / anonymous(spoofed) 本次实际使用的上游凭据来源
x-zen-free yes / no 该请求是否命中免费档
x-zen-tools injected / merged(+read) / had-tools(ok) / skipped(paid) 工具注入结果
x-zen-stream forced / as-requested 是否被网关强制改成流式
x-zen-folded sse->anthropic / sse->openai-chat 发生过 SSE 折叠及目标协议
x-zen-model-filter free-only 3/17 / passthrough(unparsable) 模型列表过滤结果,分母是上游返回总数
x-zen-attempts anon:401 -> key:200 请求尝试轨迹,含匿名/带 Key 两种模式的状态码
x-upstream opencode-zen 上游标识
🔍 DEBUG 日志。把环境变量 DEBUG 设为 true 后,函数日志会输出 [zen] 前缀的请求详情,可在控制台「日志分析」按此关键字检索。生产环境不建议常开。

环境变量

在 Pages 项目设置 → 环境变量中配置。带默认值的项不填也能正常运行。

变量默认说明
OPENCODE_API_KEY — 服务端固定 Zen Key。填了客户端就无需自带 Key,网关用它请求上游
ZEN_API_KEY — 同上,别名。两个都填时取 OPENCODE_API_KEY
ACCESS_TOKEN — 网关口令。填了则客户端必须带 Authorization: Bearer <口令>;该值不会被转发给上游
PUBLIC_MODELS 自动 模型列表是否公开。默认:配了服务端 Key 则不公开
FREE_MODELS_ONLY true 模型列表只返回免费档
FREE_MODEL_EXTRA — 额外视为免费的模型 ID,逗号分隔
ZEN_SPOOF_CLIENT true 客户端伪装总开关
ZEN_SPOOF_ALL true 是否连付费模型也伪装。设为 false 则只伪装免费档
ZEN_FORCE_TOOLS true 免费档注入 bash / read 工具
FREE_TIER_ANONYMOUS true 免费档优先匿名请求;被拒(401/403)时自动回退到带 Key 重试
UPSTREAM_TIMEOUT 25000 上游超时毫秒,上限 115000(需同时调大平台执行时长)
STREAM_IDLE_TIMEOUT 90000 流式空闲超时。上游持续吐数据则连接保持,卡死则补发终止事件
MAX_BODY_SIZE 5242880 请求体上限字节。平台硬限 6MB,默认拦截在 5MB
DEBUG false 调试日志
⚠️ 不要把真实 Key 提交进代码。Key 应配在 Pages 项目设置的环境变量里。 本项目代码不读取任何硬编码凭据,只从 context.env 与 process.env 读取。

部署与区域选择

Cloud Functions 运行在区域机房,需要显式选区域。opencode.ai 在中国大陆不可直连, 因此境外访问场景必须把函数区域配成境外,否则会持续返回 502。

🌏 加速区域决定可选范围。加速区域选「全球(含中国大陆)」时可分别配置大陆与境外区域; 选「全球(不含中国大陆)」或「中国大陆」时,只能配置对应一侧。 本项目建议加速区域选「全球(不含中国大陆)」。

境外区域

ap-singapore(默认) · ap-tokyo · ap-hongkong
ap-seoul · ap-bangkok · ap-jakarta
eu-frankfurt · na-ashburn · na-siliconvalley

中国大陆区域

ap-guangzhou(默认) · ap-shanghai
ap-nanjing · ap-beijing · ap-chengdu
edgeone.json
{
  "name": "zen-api-edgeone",
  "outputDirectory": "./",
  "overseasRegions": ["ap-tokyo"]
}
📌 edgeone.json 中的区域配置优先级高于控制台,每次每侧只能配一个区域。 上传 zip 时文件必须平铺在根目录 —— 多包一层文件夹会导致 cloud-functions/ 不在项目根, 平台会报 No server-handler detected 并把项目识别为纯静态站点。

平台限制

这些是 Cloud Functions 的硬限制,网关已做相应适配。

项目限制网关适配
请求体大小 6 MB 默认在 5MB 提前返回 413,不把超限请求读进来
单次执行时长 默认 30s,可配至 120s 默认上游超时 25s,留出响应返回余量
Node 版本 v20.x 本地开发建议同样用 Node 20
流式长响应 受执行时长约束 空闲超时保护:上游持续吐数据则保持连接;卡死时补发终止事件而非静默断流
代码包大小 128 MB 本项目零第三方依赖,部署包约 45KB

常见问题

按出现频率排序。

返回 502 Upstream unreachable

函数区域在中国大陆,而 opencode.ai 在境内不可直连。

把 overseasRegions 改成境外区域(如 ap-tokyo),并确认加速区域不含中国大陆。响应的 hint 字段里也会提示这一点。

返回 401 Unauthorized

配置了 ACCESS_TOKEN 时,客户端必须带 Authorization: Bearer <ACCESS_TOKEN>。

模型列表是否需要鉴权由 PUBLIC_MODELS 控制;配了服务端 Key 且未显式设 PUBLIC_MODELS=true 时,模型列表默认也要口令。

模型列表是空的

FREE_MODELS_ONLY=true 会过滤掉所有付费模型。上游免费档命名会变,可用 FREE_MODEL_EXTRA 手动放行。

访问 /health/free-models 可以看到当前规则对一组样例模型的命中情况,确认是否符合预期。

回复内容为空,或者少了工具调用

这是 v2.x 的已知问题(tool_calls 在 SSE 折叠时被丢弃、流内 error 事件被吞掉)。v3.0.0 已修复。

如果仍然出现,看响应头 x-zen-folded 与 x-zen-tools 确认折叠与注入是否正常,并打开 DEBUG 查日志。

报 FreeTierError

上游对非官方客户端有限制。确认 ZEN_SPOOF_ALL 与 ZEN_SPOOF_CLIENT 均为 true(默认已开)。

若已开启仍报错,看 x-zen-auth-source:显示 anonymous(spoofed) 说明伪装已生效,问题在上游侧。

流式输出中途断开

平台按执行时长切断连接。网关已加空闲超时(默认 90s),上游持续吐数据时连接会保持。

若仍断开,把平台的最大执行时长调大到 120s,并相应调大 UPSTREAM_TIMEOUT。