Skip to content

Latest commit

 

History

History
334 lines (238 loc) · 17 KB

File metadata and controls

334 lines (238 loc) · 17 KB

配置说明

普通服务模式和 launch 子命令按以下顺序解析配置文件:

  1. 环境变量 OPENCODE2API_CONFIG
  2. 显式传入的 -config / --config
  3. 当前目录已存在的 config.json
  4. 用户配置目录下的 opencode2api/config.json

第 4 项的完整位置由 os.UserConfigDir() 决定(Linux 常见为 ~/.config/opencode2api/config.json,macOS 为 ~/Library/Application Support/opencode2api/config.json)。选择该项且服务模式需要保存配置时,目录会自动创建;launch 模式不会写回。

统计与日志路径解析

统计路径按以下优先级解析:

  1. 环境变量 OPENCODE2API_STATS
  2. 环境变量 OPENCODE2API_STATS_FILE
  3. 显式传入的 -stats-file / --stats-file
  4. 下面的默认规则

日志路径按以下优先级解析:

  1. 环境变量 OPENCODE2API_LOG_FILE
  2. 显式传入的 -log-file / --log-file
  3. 下面的默认规则

默认规则区分 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

字段

model_alias

模型别名映射。键是客户端请求的模型名,值是实际传给上游的模型名。

显式使用 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_map

把客户端传入的 reasoning_effort 映射到上游可接受的值。

{
  "reasoning_effort_map": {
    "minimal": "low",
    "medium": "medium",
    "high": "high"
  }
}

force_disable_thinking

设为 true 时,服务会尽量禁用 thinking/reasoning,并从返回中移除 reasoning 内容。

max_tokens_cap

全局默认 max_tokens 上限。客户端传入的 max_tokens 超过此值时,会被截断到此值。设为 0 或不填则不限制。

{
  "max_tokens_cap": 131072
}

max_tokens_cap_per_model

按模型覆盖全局上限。键是上游模型名,值是该模型的上限。值为 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

protocol_rules

上游协议路由规则:按模型模式把请求分流到 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 错误格式),上游状态码保真。

native_responses_models

上游模型 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"]
}

text_only_models

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_proxies

SOCKS5 代理列表。

{
  "socks5_proxies": [
    {
      "name": "local",
      "addr": "127.0.0.1:1080",
      "username": "",
      "password": ""
    }
  ]
}

active_socks5

启用的代理。

  • 空字符串:直连
  • 某个 addr:固定使用该代理
  • __round_robin__:在多个代理之间轮询

socks5_paid_direct

控制带 key / 付费上游请求是否绕过 SOCKS5。

  • 不填或 false(默认):只要配置了 active_socks5,public 与带 key 请求都走代理
  • true:带 key 请求直连;仅 public / 免费层走代理(旧行为)
{
  "active_socks5": "127.0.0.1:1080",
  "socks5_paid_direct": false
}

socks5_sticky

轮询模式(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
}

upstream_base_urls

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 绑定,避免指向已不存在的目标。

prompt_cache_key / prompt_cache_retention / cache_control_breakpoints

缓存命中率与三个开关相关,默认对接受的模型都会启用(详见 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-pickle 31.5k/31.6k;codex --model mimo-v2.6-flash prompt_cached_tokens=9.92k(≈98%),都已通过 OPENCODE2API_CACHE_DEBUG=1 中的 cache_debug_usage 观察。先前行为是只在 buildUpstreamBody 时注入顶层 prompt_cache_retention;现在 chat/claude/responses 直通(remembered)与 chat→responses 桥都统一补齐,并保持 IDEMPOTENT(上游已有字段时不覆盖)。

stream_empty_retry_max / stream_first_byte_timeout_ms

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 / error
  • log_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-…)会被脱敏,永不落完整密钥。