1. OpenCode 本地代理到底在解决什么问题
如果你最近在折腾 OpenCode 这类终端里的 Agent 工具,大概率会遇到一个很具体的场景:工具本身支持自定义模型通道,但你想让它走一个统一的 Key 和 API 入口,而不是把各家厂商的 Key 散落在配置文件里。OpenCode 的本地代理(local proxy)就是干这个的——它在你的机器上起一个轻量 HTTP 服务,把 OpenCode 发出来的请求接住,再按你定义的options转发到真正的上游。
这篇聚焦的是options配置项。很多人第一次看代理代码,会被hostname、port、path、method、headers这几个字段绕晕,尤其是path为什么不能带域名、Content-Length为什么要手动算。我试过把这几个字段拆开逐个改,观察请求到底发去了哪里,才真正理解它们的分工。下面我会先讲清楚options每个字段的作用,再给出一份可复制的config.toml骨架和settings.json片段,最后用一次本地代理请求验证配置是否生效。
适合谁看:已经在用 OpenCode、想接入统一 Key/API 通道的开发者;或者你只是想搞明白 Node.jshttps.request的options对象到底怎么填。读完你能自己改代理的目标地址、路径和鉴权头,并且知道改错了会报什么错。
2. 接入前的准备:TaoToken 的 Key 与通道
在动options之前,先把上游通道确定下来。TaoToken 提供统一的 API 入口,你只需要一个 Key,就能在 OpenCode 里通过本地代理转发请求,不用在每个工具里分别配不同厂商的地址。
你需要做两件事:拿到 API Key,以及确认接入文档里的 Base URL 和路径规则。Key 在控制台的 API Keys 页面创建,创建后复制保存,后面会写进settings.json或环境变量。接入文档里会说明兼容模式的路径前缀,这个前缀直接决定你options.path怎么写。
注意:Key 不要硬编码进代理源码里。代理的设计意图是透传客户端带来的
Authorization头,所以 Key 应该放在 OpenCode 侧的配置或环境变量中,代理只负责转发。
相关入口我放在这里,按需取用:
- 创建和管理 Key:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
- 接入文档(路径与兼容模式说明):https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
- 想先在网页里验证模型是否通:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
如果你打算长期在 OpenCode 里跑编码任务或 Agent 流程,可以看下 Coding Plan,额度模型更适合高频调用:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
3. options 配置项逐个拆解
options是传给 Node.jshttps.request的配置对象。它回答四个问题:发给谁、发到哪个路径、用什么方法、带什么头。下面逐个说。
3.1 hostname 与 port:目标服务器定位
hostname是目标服务器的主机名,只写域名,不能带协议。比如写dashscope.aliyuncs.com是对的,写https://dashscope.aliyuncs.com会直接报错,因为https.request本身已经隐含了 HTTPS,协议部分由模块处理。
port是目标端口,HTTPS 默认 443。省略port时https.request也会用 443,但显式写出来更清晰,尤其在你要切换到非标准端口做本地联调时,写出来不容易漏。
const options = { hostname: 'dashscope.aliyuncs.com', port: 443, path: '/compatible-mode/v1/chat/completions', method: 'POST', headers: { 'Authorization': authHeader, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } };3.2 path:请求路径,不含域名和协议
path是最容易写错的一个。它只包含 URL 里域名之后的部分,比如/compatible-mode/v1/chat/completions。OpenCode 插件发出来的是POST /v1/chat/completions,代理要做的事情之一就是把路径改写成上游兼容模式的路径。这就是代理存在的意义:客户端不用知道上游的真实路径,代理帮你映射。
如果你把path写成完整 URL,https.request不会报语法错,但请求会发到错误的位置,通常表现为 404 或连接异常。排查时先打印options.path确认。
3.3 method 与 headers:方法与鉴权透传
method用POST,因为聊天请求要把消息内容放在 body 里,GET 无法携带 body。行业惯例也是 POST。
headers里三个字段各有分工。Authorization从客户端请求头透传过来,代理不做硬编码,这样 Key 的归属清晰,换 Key 只改客户端配置。Content-Type固定application/json,告诉上游 body 是 JSON。Content-Length用Buffer.byteLength(body)计算,因为 body 是字符串,字符数和字节数在含中文时不一致,必须按字节算,否则上游可能截断或报长度不匹配。
const authHeader = req.headers['authorization'] || '';这行是防御性写法。如果客户端没带Authorization,req.headers['authorization']是undefined,加|| ''兜底成空字符串,避免后面拼头时出现undefined字面量。Bearer sk-xxxxxx里的Bearer表示持有者令牌,是标准的鉴权方案前缀。
4. 可复制的 config.toml 与 settings.json
下面给一份能直接改的骨架。config.toml放在 OpenCode 的配置目录,settings.json片段用于声明模型通道。
# config.toml [proxy] enabled = true host = "127.0.0.1" port = 8787 upstream_hostname = "dashscope.aliyuncs.com" upstream_port = 443 upstream_path = "/compatible-mode/v1/chat/completions" timeout_ms = 60000 [model] provider = "openai-compatible" base_url = "http://127.0.0.1:8787/v1"{ "models": { "taotoken-agent": { "provider": "openai-compatible", "baseUrl": "http://127.0.0.1:8787/v1", "apiKeyEnv": "TAOTOKEN_API_KEY", "options": { "hostname": "dashscope.aliyuncs.com", "port": 443, "path": "/compatible-mode/v1/chat/completions", "method": "POST" } } } }把 Key 放进环境变量,避免写进文件:
export TAOTOKEN_API_KEY="sk-你的Key"代理启动后监听127.0.0.1:8787,OpenCode 把请求发到本地,代理按options转发到上游。base_url指向本地代理,options里的hostname和path指向真实上游,两者分工明确。
5. 验证请求:一次本地代理调用
配置改完别急着跑完整 Agent,先用一条 curl 验证代理链路通不通。
curl -sS http://127.0.0.1:8787/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus", "messages": [{"role": "user", "content": "只回复 ok"}] }'预期返回里能看到choices数组,message.content是ok。如果返回 401,说明Authorization没透传成功,检查代理里authHeader的读取和headers拼装。如果返回 404,多半是path写错,对照接入文档确认兼容模式前缀。如果返回 400 且提示长度问题,检查Content-Length是否用了Buffer.byteLength。
代理侧加一行日志,把实际发出的options打出来,联调时非常有用:
console.log('proxy ->', options.hostname + options.path, options.method);看到日志里路径和上游一致,且 curl 返回正常,就说明options配置生效了。这时候再回到 OpenCode 里跑一次真实对话,确认端到端可用。
6. 本篇常见错排查
报错getaddrinfo ENOTFOUND:hostname写错或带了协议。只写域名,不带https://。
报错Cannot set headers after they are sent:代理里对同一个响应重复调用了写头或写 body,检查end事件里是否只处理一次。
返回 401 Unauthorized:Authorization头没透传,或 Key 失效。先确认客户端请求头里有Bearer,再确认 Key 在控制台有效。
返回 404 Not Found:path与上游不匹配。对照接入文档的兼容模式路径,注意前缀。
中文内容被截断:Content-Length用了body.length而不是Buffer.byteLength(body),中文字符字节数大于字符数。
代理启动但 OpenCode 连不上:base_url端口与代理监听端口不一致,或代理只监听了127.0.0.1而 OpenCode 在容器里跑。确认网络可达。
排障时优先看代理日志里打印的options,再对照 curl 的返回码,基本能定位到是路径、鉴权还是长度问题。接入相关的 Key 和文档入口:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 和 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
7. 把通道固定下来,后续只改配置
options这几个字段一旦调通,后面换模型、换上游,基本只改hostname和path,代理逻辑不用动。我的习惯是把options抽成一个函数,入参是上游配置,返回拼好的对象,这样联调时改一处就够。
如果你还在选长期方案,OpenCode 这类 Agent 工具调用频率高,Coding Plan 的额度模型更合适:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
想先在网页里确认模型通不通,再回来配代理,用模型对话页最快:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
控制台里可以统一管理 Key 和用量:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=