普通服务模式和 launch 子命令按以下顺序解析配置文件:
- 环境变量
OPENCODE2API_CONFIG - 显式传入的
-config/--config - 当前目录已存在的
config.json - 用户配置目录下的
opencode2api/config.json
第 4 项的完整位置由 os.UserConfigDir() 决定(Linux 常见为 ~/.config/opencode2api/config.json,macOS 为 ~/Library/Application Support/opencode2api/config.json)。选择该项且服务模式需要保存配置时,目录会自动创建;launch 模式不会写回。
统计路径按以下优先级解析:
- 环境变量
OPENCODE2API_STATS - 环境变量
OPENCODE2API_STATS_FILE - 显式传入的
-stats-file/--stats-file - 下面的默认规则
日志路径按以下优先级解析:
- 环境变量
OPENCODE2API_LOG_FILE - 显式传入的
-log-file/--log-file - 下面的默认规则
默认规则区分 config 是否显式提供:
- 显式
-config /path/config.json时,未单独配置的默认路径是/path/stats.json和/path/opencode2api.log;当前目录已有旧文件不会覆盖该规则。 - 未显式指定 config 时,已有当前目录旧文件继续兼容使用;否则使用 config 回退目录,通常是
<UserConfigDir>/opencode2api/stats.json和<UserConfigDir>/opencode2api/opencode2api.log。 - 空环境值会被忽略。现有容器 entrypoint 显式传入
-config /data/config.json,并保持/data/config.json与/data/opencode2api.log行为不变。
仓库开发流程仍可先从示例复制当前目录文件:
cp config.example.json config.json模型别名映射。键是客户端请求的模型名,值是实际传给上游的模型名。
显式使用 go: 或 zen: 认证时,如果客户端模型名本身存在于对应目录,且别名仅将它映射到同名的 -free 版本,则优先使用对应目录中的原模型。其他别名仍照常解析。
{
"model_alias": {
"deepseek-v4-flash": "deepseek-v4-flash-free",
"mimo-v2.5": "mimo-v2.5-free",
"ling-3.0-flash": "ling-3.0-flash-free",
"nemotron-3-ultra": "nemotron-3-ultra-free",
"north-mini-code": "north-mini-code-free",
"laguna-s-2.1": "laguna-s-2.1-free"
}
}把客户端传入的 reasoning_effort 映射到上游可接受的值。
{
"reasoning_effort_map": {
"minimal": "low",
"medium": "medium",
"high": "high"
}
}设为 true 时,服务会尽量禁用 thinking/reasoning,并从返回中移除 reasoning 内容。
全局默认 max_tokens 上限。客户端传入的 max_tokens 超过此值时,会被截断到此值。设为 0 或不填则不限制。
{
"max_tokens_cap": 131072
}按模型覆盖全局上限。键是上游模型名,值是该模型的上限。值为 0 表示对该模型不限制。
{
"max_tokens_cap_per_model": {
"deepseek-v4-flash-free": 131072,
"laguna-s-2.1-free": 262144,
"mimo-v2.5-free": 1048576
}
}上游对不同模型的 max_tokens 限制不同,实测值如下:
| 模型 | 限制类型 | 上限 |
|---|---|---|
deepseek-v4-flash-free |
completion tokens | 131,072 |
laguna-s-2.1-free |
context length | 262,144 |
mimo-v2.5-free |
context length | 1,048,576 |
nemotron-3-ultra-free |
context length | 1,000,000 |
nemotron-3.5-lightning-free |
context length | 1,000,000 |
上游协议路由规则:按模型模式把请求分流到 OpenCode Zen 的三种原生协议端点,而不是固定走 Chat Completions 翻译。
| 协议 | 上游端点 | 说明 |
|---|---|---|
chat_completions |
/zen/v1/chat/completions(或 /zen/go/v1/...) |
默认兜底,未命中规则时使用 |
anthropic |
/zen/v1/messages(或 /zen/go/v1/messages) |
上游原生 Anthropic Messages |
responses |
/zen/v1/responses |
上游原生 OpenAI Responses |
规则语法:
pattern:精确模型 ID 或单个尾部*通配(如claude-*、*),大小写不敏感,匹配前剥离[1m]等上下文后缀(claude-sonnet-4.6规则同时命中claude-sonnet-4.6[1m])。protocol:chat_completions/anthropic/responses三选一。- 规则按声明顺序匹配,首个命中生效;最多 64 条,pattern ≤128 字符且不含空白。
优先级:显式规则 > 运行时 native-responses 探测记忆 > 默认 Chat Completions。未命中任何规则时行为与旧版本完全一致。/v1/chat/completions 入站仅应用显式规则(探测记忆仍走"翻译失败→探测→透传"回退,保证默认行为不变);/v1/messages 与 /v1/responses 入站应用完整优先级。
三种入站协议(Chat / Responses / Claude Messages)都可以路由到任意上游协议,请求与响应在网关内自动转换(流式 SSE、工具调用、推理内容、usage 统计均支持)。/v1/messages/count_tokens 命中 anthropic 规则时直连上游 /zen/v1/messages/count_tokens 取精确计数,未命中或上游失败回落本地启发式。
本批次行为补充(仅本版起):
max_tokens(Anthropic/Chat)/max_output_tokens(Responses)全局与按模型上限取max_tokens_cap/max_tokens_cap_per_model。配置了 cap 时,所有发给上游的请求(Responses/Chat/Anthropic 的直通与翻译路径)缺省该字段都会自动注入 = cap;已设置的收敛到[128, cap]。未配置 cap 时 Chat 直通保持缺省,Anthropic / Chat→Anthropic / Chat→Responses 因 max_tokens 必填兜底 8192。count_tokens 直通只降不补不注入。例外:Chat 直通路径客户端已带max_completion_tokens时不补max_tokens(OpenAI 要求二者互斥,部分上游对并存严格 400,issue #35)。- chat 入站的
max_completion_tokens优先于max_tokens指导预算(按 Worker A/B 的 OpenAI 现代字段语义),响应走的store:false且上游为 reasoning 时带include:["reasoning.encrypted_content"]由 A/B 补齐。 - thinking 模式与
temperature/top_p/top_k互斥:开启 thinking 时剥离这些采样参数(避免上游 400),同时保留output_config.effort→reasoning_effort映射。 - tool_use/tool_result 配对归一:Claude 历史的 orphan tool_use(无 matching tool_result)在翻译为 chat/responses 前补占位 tool_result 或 drop,保证上游不再因序列非法 400。
{
"protocol_rules": [
{"pattern": "claude-*", "protocol": "anthropic"},
{"pattern": "gpt-*", "protocol": "responses"},
{"pattern": "glm-5.3", "protocol": "chat_completions"}
]
}规则同样可在管理面板「模型与路由 → 上游协议路由规则」中配置(支持快捷预设与排序),面板保存为严格校验:任一条非法整体拒绝(HTTP 400),配置文件加载为宽松校验(非法条目剔除并告警)。错误处理按入站协议写回对应形状(如 claude 入站收到 Anthropic 错误原样透传;chat 入站收到 Anthropic 错误转为 Chat 错误格式),上游状态码保真。
上游模型 ID 列表:这些模型已知只支持原生 Responses 端点,请求会跳过 Chat 翻译,直接透传到上游 /responses。除配置外,代理还内置了一份静态预置列表(muse-spark-1.2/1.3-contributor 及 -free 变体,因上游对其 chat/completions 通道整档 500、只剩 /responses 可用),并会在运行时探测确认后动态记忆更多模型;配置值与静态预置只增不减地合并,不会清掉运行时学到的模型;静态预置与配置下发的模型不会因连续失败被剔除,运行时学到的模型连续失败 5 次后会自动剔除并回落 Chat 翻译路径。
注意:受免费层指纹门限制,
muse-spark-*-contributor-free的 tools 必须是 Responses 形状({"type":"function","name",...}),注入的缺失四件 bash/glob/grep/read 已自动按此形状补齐;Anthropic 形状的input_schema会被上游按did not match any supported type拒绝。
{
"native_responses_models": ["muse-spark-1.3-contributor"]
}models.dev 目录数据之外,额外强制按纯文本处理的模型前缀列表。纯文本判定本身是数据驱动的:models.dev 报告的 modalities.input 只含 text 的已知模型(如 deepseek-v4-flash、glm-5.2、qwen3-coder*)会自动把消息里的图片/文档内容静默降级为 ["text"] 标注后继续转发,而不是交给上游报错;text_only_models 用于覆盖 catalog 未收录或数据滞后的场景。
匹配是大小写不敏感的前缀匹配:一个前缀覆盖该模型的所有变体。例如 "deepseek" 同时匹配 deepseek-v4-flash 和 deepseek-v4-flash-free。catalog 未收录且前缀未命中的模型不会降级(fail-open,上游如实报错)。显式设置(即使是空数组)会替换默认值(默认为空)。此字段同样作用于 Chat、Responses、Claude 三条协议面。
{
"text_only_models": []
}SOCKS5 代理列表。
{
"socks5_proxies": [
{
"name": "local",
"addr": "127.0.0.1:1080",
"username": "",
"password": ""
}
]
}启用的代理。
- 空字符串:直连
- 某个
addr:固定使用该代理 __round_robin__:在多个代理之间轮询
控制带 key / 付费上游请求是否绕过 SOCKS5。
- 不填或
false(默认):只要配置了active_socks5,public 与带 key 请求都走代理 true:带 key 请求直连;仅 public / 免费层走代理(旧行为)
{
"active_socks5": "127.0.0.1:1080",
"socks5_paid_direct": false
}轮询模式(active_socks5: "__round_robin__")下的会话粘性出口。
实测(Claude Code 真实会话):上游免费层 prompt 缓存按出口 IP 隔离,随机轮换出口时相同请求两次都全 miss;固定出口时相同请求命中 99.8%。socks5_sticky 让同一会话(付费按账号 token,public 按 Claude metadata 的 session_id,缺省按公共兜底)固定走同一出口代理,缓存持续累积;不同会话之间仍然轮询分散。
- 缺省或
true:轮询时按会话固定出口(推荐) false:恢复纯轮询(每次请求随机换出口)
以下情况会自动切断当前会话的 sticky 绑定,重试/下次请求换到下一个出口:
- 传输层连接错误(代理不可达)
- 上游 HTTP 429(免费层按出口 IP 限流,换出口可绕过)与 5xx
{
"active_socks5": "__round_robin__",
"socks5_sticky": true
}opencode zen 上游的 base URL 列表。默认(未设置或为空数组)为 ["https://opencode.ai"]。典型用途:你自己反代的多个域名,配合 socks5_proxies 实现多入口负载均衡、提高可用性。
{
"upstream_base_urls": [
"https://opencode.ai",
"https://zen1.example.com",
"https://zen2.example.com"
]
}- 规范化:自动去除尾部
/、空项与重复项;全空回落默认https://opencode.ai - 会话 sticky:同一会话(付费按账号 token,public 按 session_id,缺省按公共兜底)会固定到某个 (域名, 代理) 组合——多域名下请求不会在多域名间漂移,反代侧的缓存、限流状态持续累积;不同会话仍分散到不同组合实现负载均衡
- 与
socks5_sticky=false配合:仅代理维恢复轮询,域名维仍按会话固定 - 与
socks5_paid_direct=true配合:付费请求 client 直连,但域名维仍按会话固定 - 服务端全局请求(模型目录拉取
/v1/models等)不做 sticky,多域名间轮询
域名列表变化(增删改)时自动清空全部 sticky 绑定,避免指向已不存在的目标。
缓存命中率与三个开关相关,默认对接受的模型都会启用(详见 internal/app/cache_debug.go 的 OPENCODE2API_CACHE_DEBUG=1 运行时日志可以在各入口看到生效后的上游 body 摘要):
prompt_cache_retention:向上游 zen 网关显式声明 prompt 前缀缓存的保留时长。上游默认约 5 分钟(in_memory),agent 任务间歇过长时缓存容易过期导致命中率低。- 不填或
"24h":注入prompt_cache_retention: "24h",缓存保留一天 "in_memory":显式维持上游默认(约 5 分钟)"off":完全不注入该字段
- 不填或
prompt_cache_key:按客户端x-opencode-session/x-session-id自动派生稳定 key(oc2api:<session>),跨轮对话/同 session 的 prefix 更容易命中;显式传入时优先。cache_control_breakpoints:是否向上游请求附加 Anthropic 风格缓存断点cache_control: {"type":"ephemeral","ttl":"1h"}。- 缺省或
true:注入(对支持的上游提升缓存命中;GLM/Zhipu 模型会拒绝该字段,自动跳过) false:不注入
- 缺省或
{
"prompt_cache_retention": "24h",
"cache_control_breakpoints": true
}真实运行验证:
opencode2api launch claude --model mimo-v2.6-flash第二轮prompt_cached_tokens从 ~28.7k 提升到 ~32.4k(≈99.9% 的 prompt 命中),big-pickle31.5k/31.6k;codex --model mimo-v2.6-flashprompt_cached_tokens=9.92k(≈98%),都已通过OPENCODE2API_CACHE_DEBUG=1中的cache_debug_usage观察。先前行为是只在buildUpstreamBody时注入顶层prompt_cache_retention;现在 chat/claude/responses 直通(remembered)与 chat→responses 桥都统一补齐,并保持 IDEMPOTENT(上游已有字段时不覆盖)。
claude→responses 流式链路的「空流兜底 + 首 token 前重试」。覆盖两类常见上游故障:
- prefill 阶段被宰:上游代理(CF / nginx)在首个 token 前杀 tunnel,网关只收到一个干净的 EOF——按旧实现客户端会看到
stream ended without completion,agent 中断。 - 挂死:上游接受了连接但既不发数据也不关,客户端永久等待。
开启后(默认开启):上游 200 收到、但还没向客户端 WriteHeader 之前的窗口里,遇到 空流 EOF / 超时未发数据 / 上游只发 response.failed 错误帧,静默重发同一份请求最多 stream_empty_retry_max 次,客户端完全无感;重试经 key_pool 自动落到下一个可用 key,不会重复同一根 pipe。
{
"stream_empty_retry_max": 1,
"stream_first_byte_timeout_ms": 30000
}stream_empty_retry_max:重试次数,默认1,0关闭。每个 attempt 都用同一份请求体重发(prompt_cache_key 稳定,input tokens 在缓存命中时接近零成本)。stream_first_byte_timeout_ms:peek 窗口毫秒数,默认30000(30s)。覆盖大多数上游 prefill 时间;<=0关闭看门狗,仅 EOF/error 触发。
不重试的情况(不改的承诺):
- 已向客户端写过任何字节后 EOF/杀流——按 ParalonCloud Rule 2 合成正常 stop 收尾(已有产出交付给 agent)。
- 仅有 thinking 没有 text 的 EOF——
reasoningFallback兜底把思考内容提升为 text,agent 拿到思考、不发 error。 - 非流式请求(
stream: false)走的是另一条路径,与本机制无关。
真实运行验证:故意用反代在 5s 时杀 upstream tunnel,agent 端原本会
stream ended without completion中断;开启本机制后第一次空流透明重试到下一个 key,整轮圆满完成。
打开 http://127.0.0.1:8000/ 可进入管理面板。面板可以修改配置、刷新模型和查看 token 统计。管理面板现已可设置 prompt_cache_retention、cache_control_breakpoints、socks5_sticky、text_only_models(「模型与路由」/「网络与代理」Tab),保存时这些字段随其余配置一并持久化到 config.json,不再被静默回擦。
默认管理密码是 123456,生产部署必须修改:
./opencode2api -password "your-strong-password"GET/POST /api/config 额外返回/接受运行时日志字段(不写入 config.json):
log_level:debug/info/warn/errorlog_bodies:是否在 Debug 下记录 body 形状摘要
非容器模式默认路径由上文“统计与日志路径解析”决定,不再使用 Windows 的 %LOCALAPPDATA%/opencode2api/logs 旧 launch 缓存路径;容器内默认是 /data/opencode2api.log。日志由 lumberjack 按大小轮换;同时写 stdout。
关键字段:
| 事件 | 用途 |
|---|---|
request_plan |
协议决策:模型、auth_mode、thinking、reasoning_effort、stream |
upstream_attempt / upstream_result |
上游重试与回退链 |
stream_result |
流式结果摘要;empty_reply=true 时为 Warn |
request_result |
非流式结果摘要 |
密钥字段(authorization / token / sk-…)会被脱敏,永不落完整密钥。