用 opencode-llm-proxy 把模型接到本地 OpenAI 接口
摘要
- 插件仓库:KochC/opencode-llm-proxy,npm 包名
opencode-llm-proxy。本文按插件1.11.2、OpenCode2.1.2来写。 - 它不是单独启动的服务。OpenCode 2.x 的后台服务加载插件后,在本机
127.0.0.1:4010提供 OpenAI、Anthropic、Gemini 兼容接口,模型用的是 OpenCode 里已经登录或配置好的那些。 - OpenCode 2.x 本身的安装、后台服务和插件命令见 opencode 2.x 的安装和使用。
外部工具通常只认一种接口,例如 OpenAI 的 POST /v1/chat/completions。OpenCode 里已经接好的模型,并不能直接给这些工具用。opencode-llm-proxy 把这件事接起来:请求打到本机端口,插件在 OpenCode 里开一个临时会话去调模型,再把回复翻成调用方原来的格式。
支持的入口:
| 格式 | 路径 |
|---|---|
| OpenAI Chat Completions | POST /v1/chat/completions |
| OpenAI Responses | POST /v1/responses |
| Anthropic Messages | POST /v1/messages |
| Gemini | POST /v1beta/models/<模型>:generateContent |
四条都支持流式输出,也支持把 tools 交回调用方执行。插件跑在 OpenCode 进程里,用的是 Bun.serve,本机不需要再起一个 Node 服务。
安装
下面两种方式互相独立,按需要任选其一。两种一起用时,后台服务会加载两份插件。
插件包
opencode plugin add 把包名写进 ~/.config/opencode/opencode.json 的 plugins,并从 npm 仓库下载到 OpenCode 的缓存目录:
1 | opencode plugin add opencode-llm-proxy |
缓存路径是:
1 | ~/.cache/opencode/npm/opencode-llm-proxy@latest/<安装时间戳>/node_modules/opencode-llm-proxy |
也可以自己改配置、写上同样的包名,效果相同。
1 | { |
opencode plugin remove 只从配置里去掉包名。这份缓存会留着,除非手动删除,或执行 opencode uninstall。插件仍在配置里时,opencode plugin update 会再装一版;它只保留最新的两代,更老的一代还要超过 7 天才删。
本地文件
把仓库里已经打包好的 dist/llm-proxy.js 放到 OpenCode 会自动加载的插件目录。文件留在原处,不进入上面的 npm 缓存。
全局生效:
1 | mkdir -p ~/.config/opencode/plugins |
只在当前项目生效时,放到该项目的 .opencode/plugins/llm-proxy.js。和 opencode.json 平级的 plugins/ 不会自动加载,需要写进 plugins 配置,或挪到 .opencode/plugins/。
重启并确认
两种方式装完都要重启。2.x 的后台服务会一直留着,已经在跑的进程不会因为刚装好就打开端口:
1 | opencode service restart |
确认端口和模型列表:
1 | curl -s http://127.0.0.1:4010/health |
健康检查返回:
1 | { "healthy": true, "service": "opencode-openai-proxy" } |
/v1/models 把 OpenCode 当前能看到的模型列成 provider/model。这一步只读配置,不会真正请求模型,所以列得出来不代表这个模型现在能调用成功。
服务日志里对应的一行是 OpenAI proxy server started,路径一般是 ~/.local/share/opencode/log/opencode.log。端口被占用时,OpenCode 本身仍会起来,插件只记一条 OpenAI proxy server failed to start,然后这次不监听。
调用
模型 id 用上面列出来的 provider/model。本地回环、又没有设置令牌时,Authorization 可以不传,SDK 里的 apiKey 填任意非空字符串即可,常见写法是 unused。
1 | curl http://127.0.0.1:4010/v1/chat/completions \ |
流式请求加上 "stream": true,响应是 text/event-stream,以 data: [DONE] 结束。
Anthropic SDK 的 base URL 是代理根地址,不要带 /v1,SDK 自己会拼 /v1/messages:
1 | import anthropic |
OpenAI SDK 则要带到 /v1:
1 | from openai import OpenAI |
Open WebUI 的 OpenAI API Base URL 填 http://127.0.0.1:4010/v1。Chatbox 的 API Host 填 http://127.0.0.1:4010。n8n、Continue、Zed 的具体字段见仓库里的 recipes。
同名模型落在多个提供商上时,加请求头 x-opencode-provider 指定提供商。变体用 x-opencode-variant。
每次请求都会在 OpenCode 里建一个临时会话,用完就删。插件不记住上一轮,多轮对话要把完整 messages 再发一遍。想留下这些会话时,给后台服务加上 OPENCODE_LLM_PROXY_KEEP_SESSIONS=true。
带 tools 时,插件把模型决定调用的函数翻译成 OpenAI 的 tool_calls、Anthropic 的 tool_use 或 Gemini 的 functionCall,然后结束这一轮。函数本身在调用方执行,结果按各自协议放进下一轮请求。OpenCode 自带的读文件、跑命令等工具不会在这条请求里执行。
配置
配置全是环境变量,由后台服务进程读取。在当前终端里 export 之后再执行 opencode,改不到已经在跑的那个服务。写进服务配置:
1 | opencode service set env OPENCODE_LLM_PROXY_PORT 4020 |
service set 和 service unset 会停掉正在跑的服务,新值在下一次启动时生效。改完用 opencode service status 看一下,停着就再 start。
常用项:
| 变量 | 默认 | 作用 |
|---|---|---|
OPENCODE_LLM_PROXY_HOST |
127.0.0.1 |
监听地址。局域网用 0.0.0.0 |
OPENCODE_LLM_PROXY_PORT |
4010 |
端口 |
OPENCODE_LLM_PROXY_TOKEN |
空 | Bearer 令牌。监听非回环地址时必须设置 |
OPENCODE_LLM_PROXY_TOKENS |
[] |
额外接受的令牌,JSON 数组 |
OPENCODE_LLM_PROXY_REQUEST_TIMEOUT_MS |
120000 |
单次请求超时 |
OPENCODE_LLM_PROXY_MAX_REQUEST_BYTES |
1048576 |
请求体上限,最大 100 MiB |
OPENCODE_LLM_PROXY_KEEP_SESSIONS |
false |
true 时保留临时会话 |
OPENCODE_LLM_PROXY_MODEL_ALIASES |
{} |
别名到模型 id,值可以是字符串或按顺序回退的数组 |
OPENCODE_LLM_PROXY_METRICS_ENABLED |
false |
true 时开放 GET /metrics,同样要带令牌 |
别名示例:
1 | opencode service set env OPENCODE_LLM_PROXY_MODEL_ALIASES '{"fast":"opencode/deepseek-v4.1-flash-free"}' |
请求里的 model 就可以写 fast。
暴露到局域网时同时改地址和令牌,否则插件拒绝启动:
1 | opencode service set env OPENCODE_LLM_PROXY_HOST 0.0.0.0 |
调用时带 Authorization: Bearer <令牌>。Docker 里的 Open WebUI 访问宿主机时,Base URL 用 http://host.docker.internal:4010/v1,并确认系统防火墙放行这个端口。
temperature、top_p、topK 会传给模型。max_tokens 一类字段会收下,避免客户端因缺字段报错,但当前 OpenCode SDK 不会按这个上限截断。stop、seed、frequency_penalty、presence_penalty、logprobs、n 会直接返回 400,不会悄悄忽略。
远程图片 URL 默认拒绝。确有需要再打开 OPENCODE_LLM_PROXY_REMOTE_MEDIA_ENABLED,并读仓库的 Security。完整变量表以仓库 README 为准。
排错
curl 连不上 127.0.0.1:4010 时,先看服务在不在、插件有没有写进配置:
1 | opencode service status |
服务是 stopped 就 opencode service start。plugin list 里没有 opencode-llm-proxy,就重新 plugin add,然后 opencode service restart。
模型列表正常,聊天却返回:
1 | {"error":{"message":"Upstream request failed.","type":"invalid_request_error"}} |
这是插件把上游错误收成了一句通用文案。具体原因在 OpenCode 日志里,例如:
1 | grep -E "Failed to drain Session|Proxy completion failed|Free usage limit" \ |
看到 Free usage limit reached 时,OpenCode Zen 的免费额度已经用完。/v1/models 仍会列出 opencode/*-free,因为列表不检查额度。换成自己在 providers 里配置的模型,例如 my-deepseek/deepseek-v4-flash,或者等额度重置。额度由 Zen 账号计算,在 OpenCode 终端里直接用同一个免费模型也会失败。近期有人反馈重置时间到了仍然被拦,这种情况写信给 help@anoma.ly,不要在公开场合贴 key。
api_key_expired 是另一件事,按 opencode 2.x 的安装和使用 里「免费模型报 API key 过期」换 Console 的 oc_sk_ key,然后 opencode service restart。
卸载
从全局配置去掉插件:
1 | opencode plugin remove opencode-llm-proxy |
如果还把 llm-proxy.js 拷进了插件目录,删掉这些文件,否则服务重启后仍会加载:
1 | ~/.config/opencode/plugins/llm-proxy.js |
改过监听地址、端口或令牌时,把对应变量清掉:
1 | opencode service unset env OPENCODE_LLM_PROXY_HOST |
正在运行的服务里,插件已经加载,端口还会继续听。卸完后重启,再确认端口已经关掉:
1 | opencode service restart |
连接被拒绝就说明代理已经停了。~/.cache/opencode/npm/ 里可能还留着安装缓存,不影响端口;下次再 plugin add 时会重新用到它。要换新版本用 opencode plugin update,然后同样重启服务。
后台服务本身可以继续留着。只有不再使用 OpenCode 时才需要 opencode service stop,那是服务的生命周期,不是这个插件的卸载步骤。