cloud-functions/ · 区域机房运行 · 可选区域
把 OpenCode Zen 包成标准 OpenAI 与 Anthropic 接口。免费模型自动识别与过滤、客户端身份伪装、
非流式请求自动折叠,每个响应都带 x-zen-* 诊断头。改 base_url 即可接入,无需改业务代码。
所有路径都映射到上游 opencode.ai/zen。客户端路径会剥掉自定义前缀后转发,
因此 /api/zen/v1/models 与 /v1/models 落到同一个上游地址。
| 路径 | 协议 | 方法 | 说明 |
|---|---|---|---|
/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 |
本地端点,返回免费模型判定规则与样例命中情况 |
/test.html),平台会优先返回静态资源,函数不会被执行。
根路径 / 因此返回本落地页。
同一套网关服务两套协议。网关按路径识别协议族,工具注入与 SSE 折叠都使用对应协议的原生格式。
| 项目 | OpenAI Chat | Anthropic 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 |
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(默认)时,模型列表只返回免费档。
判定使用规则集而非固定名单,上游新增免费档通常能被自动识别。
按优先级依次匹配,任一命中即视为免费档:
命中免费档时,网关会自动做三件事,目的是绕过上游对非官方客户端的限制:
付费模型不注入工具,响应头 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 设为 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 |
调试日志 |
context.env 与 process.env 读取。
Cloud Functions 运行在区域机房,需要显式选区域。opencode.ai 在中国大陆不可直连, 因此境外访问场景必须把函数区域配成境外,否则会持续返回 502。
{
"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 |
按出现频率排序。
函数区域在中国大陆,而 opencode.ai 在境内不可直连。
把 overseasRegions 改成境外区域(如 ap-tokyo),并确认加速区域不含中国大陆。响应的 hint 字段里也会提示这一点。
配置了 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 查日志。
上游对非官方客户端有限制。确认 ZEN_SPOOF_ALL 与 ZEN_SPOOF_CLIENT 均为 true(默认已开)。
若已开启仍报错,看 x-zen-auth-source:显示 anonymous(spoofed) 说明伪装已生效,问题在上游侧。
平台按执行时长切断连接。网关已加空闲超时(默认 90s),上游持续吐数据时连接会保持。
若仍断开,把平台的最大执行时长调大到 120s,并相应调大 UPSTREAM_TIMEOUT。