☰
【Agent】【OpenCode】本地代理分析(分块传输):TaoToken 统一 Key 接入与 config.toml 配置骨架
2026/9/26 12:30:36 网站建设 项目流程

1. OpenCode 本地代理为什么会卡在分块传输

OpenCode 这类 Agent 客户端在跑长任务时,最典型的动作就是持续向模型要流式输出:一边生成 token,一边把增量内容吐回终端。它默认走 OpenAI 风格的/v1/chat/completions,请求体里带"stream": true,响应侧就是 SSE 加 HTTP chunked。问题往往不出在模型本身,而是出在中间那层本地代理——请求体是分块传进来的,响应体也是分块吐出去的,任何一头没接住,表现就是终端一直转圈、内容半截断掉、或者干脆报socket hang up。

我试过把 OpenCode 直接指向远端兼容接口,短对话没问题,一旦让它连续改十几个文件就开始飘。后来把链路拆开看,才发现本地代理在转发时对 chunked 的处理有坑:请求侧用Content-Length一次性发完没问题,但 OpenCode 某些版本会用 chunked 上传 body;响应侧如果代理手动设了Content-Length,流式就被压成一次性返回,Agent 的增量渲染直接失效。这篇就聚焦这条链路,把 TaoToken 统一 Key 接进来,给一份能直接抄的config.toml骨架,再配一套抓包验证动作,帮你定位到底是哪一段把流掐断了。

适合谁看:已经在用 OpenCode 或类似 OpenAI 兼容客户端、想接统一 API 通道、并且被流式响应问题折腾过的人。下面所有配置都以本地代理监听127.0.0.1:2048为例,你可以按自己端口改。

2. TaoToken 统一 Key 与接入前置

TaoToken 在这里的角色是统一 API 通道:你不需要在 OpenCode 里分别填各家模型的地址和 Key,而是拿一个统一 Key,通过它的 API 入口转发到具体模型。对本地代理来说,这带来两个直接好处——第一,代理只需要认一个上游 host 和一套鉴权头,转发逻辑大幅简化;第二,模型切换在服务端完成,OpenCode 侧配置不用动。

接入前你需要准备三样东西。第一是 TaoToken 的 API Key,在控制台的 API Keys 页面创建,格式通常是sk-开头的一串。第二是确认 API 基地址,TaoToken 的 API 入口是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 base URL 使用。第三是确认你要调的模型名,比如qwen-plus、claude-sonnet这类,填在请求体的model字段里。

有一点要提前说清楚:本地代理只负责转发,不负责鉴权转换。OpenCode 发出来的Authorization: Bearer sk-xxx会被代理原样透传给 TaoToken,所以你在 OpenCode 里填的 Key 就应该是 TaoToken 的统一 Key,而不是某个模型厂商的 Key。这样链路是:OpenCode → 本地代理127.0.0.1:2048→ TaoToken API → 目标模型。代理层不做 Key 替换,只做 host 和 path 的改写,排障时链路更干净。

如果你还没建 Key,可以先到控制台把 Key 建好,顺手在模型对话页面发一条测试消息确认通道可用,再回来配代理。这一步能省掉后面很多“到底是代理错还是 Key 错”的扯皮。

3. 可复制的 config.toml 与 settings.json 骨架

OpenCode 的配置分两层:一层是config.toml,管模型 provider 和默认行为;一层是settings.json,管运行时和代理相关字段。下面这份骨架你可以直接改端口和模型名使用。

先看config.toml:

# ~/.config/opencode/config.toml # 本地代理模式:所有请求先打到 127.0.0.1:2048 [provider.local_proxy] name = "taotoken-proxy" base_url = "http://127.0.0.1:2048/v1" api_key = "sk-你的TaoToken统一Key" wire_api = "chat" # 走 OpenAI 风格 /v1/chat/completions [model] provider = "local_proxy" name = "qwen-plus" # 换成你要用的模型名 stream = true # Agent 场景必须开流式 temperature = 0.7 max_tokens = 8192 [agent] auto_compact = true max_turns = 50

关键点有三个。base_url指向本地代理而不是 TaoToken,因为代理会帮你改写 path;wire_api = "chat"告诉 OpenCode 用 chat completions 协议;stream = true是 Agent 增量输出的前提,关掉它分块传输就无从谈起。

再看settings.json,这里放代理和超时相关字段:

{ "proxy": { "enabled": true, "url": "http://127.0.0.1:2048", "no_proxy": "localhost,127.0.0.1" }, "request": { "timeout_ms": 120000, "stream_idle_timeout_ms": 60000, "max_retries": 2 }, "logging": { "level": "debug", "log_chunks": true } }

stream_idle_timeout_ms这个字段值得单独说:它管的是流式响应里两个 chunk 之间的最大间隔。Agent 跑长任务时,模型思考阶段可能十几秒不吐 token,如果这个值设太小,客户端会误判超时然后断开,表现就是“内容生成到一半突然停”。设成 60000 比较稳。log_chunks: true打开后,代理会把每个 chunk 的到达时间打进日志,后面排障全靠它。

代理脚本本身用 Node.js 内置http/https就够,核心是请求侧拼 body、响应侧 pipe:

// proxy.js const http = require('http'); const https = require('https'); const UPSTREAM_HOST = 'taotoken.net'; const UPSTREAM_PATH = '/api/v1/chat/completions'; const server = http.createServer((req, res) => { if (req.method === 'POST' && req.url === '/v1/chat/completions') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { const auth = req.headers['authorization'] || ''; const options = { hostname: UPSTREAM_HOST, port: 443, path: UPSTREAM_PATH, method: 'POST', headers: { 'Authorization': auth, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }; const proxyReq = https.request(options, proxyRes => { // 关键:透传上游响应头,保留 Transfer-Encoding: chunked res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); }); proxyReq.on('error', e => { res.writeHead(502); res.end('Bad Gateway: ' + e.message); }); proxyReq.write(body); proxyReq.end(); }); return; } res.writeHead(404); res.end('Not Found'); }); server.listen(2048, '127.0.0.1', () => { console.log('proxy on http://127.0.0.1:2048'); });

这里最容易写错的一行是res.writeHead(proxyRes.statusCode, proxyRes.headers)。如果你手动构造响应头、只写Content-Type而漏掉Transfer-Encoding,Node 会默认用Content-Length或直接缓冲整个响应,流式就没了。透传上游头是保住 chunked 的最省事做法。

4. 验证请求与分块传输抓包

配好之后别急着跑 Agent,先用 curl 打一发,确认代理和上游都通:

curl -N -X POST http://127.0.0.1:2048/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoToken统一Key" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus", "messages": [{"role": "user", "content": "数到五"}], "stream": true }'

-N关掉 curl 的缓冲,这样你能肉眼看到 token 一个个蹦出来。正常输出长这样:

data: {"choices":[{"delta":{"content":"1"}}]} data: {"choices":[{"delta":{"content":"、"}}]} data: {"choices":[{"delta":{"content":"2"}}]} ... data: [DONE]

如果所有内容一次性刷出来,说明流式在某一环被缓冲了。接下来用抓包确认分块传输到底有没有发生。推荐用tcpdump抓本地回环,或者更直观地用mitmproxy看明文:

# 抓 2048 端口的回环流量,写到文件 sudo tcpdump -i lo0 -A -s 0 'tcp port 2048' -w proxy.pcap

跑完一次请求后,用 Wireshark 打开proxy.pcap,过滤http,看响应头里有没有Transfer-Encoding: chunked。有,说明分块传输成立;没有而是Content-Length,说明代理把流压平了。再看请求侧,如果 OpenCode 用 chunked 上传 body,你会在请求头看到Transfer-Encoding: chunked且没有Content-Length,此时代理的req.on('data')会分多次触发,body += chunk把它们拼起来,end时才是完整 JSON。

一个更轻量的验证方式是在代理里打时间戳:

req.on('data', chunk => { console.log('chunk at', Date.now(), 'len', chunk.length); body += chunk; });

如果日志里同一请求出现多行chunk at,说明请求体确实是分块来的;如果只有一行,说明客户端用了Content-Length一次性发。两种都正常,代理都要能处理。

5. 本篇常见错排查

错误一:Content-Length和Transfer-Encoding同时出现。这是代理手动设头时最常见的冲突。HTTP 规范里两者不能共存,Node 会直接报ERR_HTTP_CONTENT_LENGTH_MISMATCH或让客户端解析失败。解法就是上面说的,响应侧透传proxyRes.headers,别自己拼。

错误二:流式响应被缓冲,终端一次性出结果。检查三处:代理有没有透传Transfer-Encoding;中间有没有别的反向代理(比如 nginx)默认开了proxy_buffering on;OpenCode 的stream是不是被某层配置覆盖成了 false。nginx 场景加一行proxy_buffering off;即可。

错误三:socket hang up或流到一半断。多半是stream_idle_timeout_ms太小,或者上游在长思考时超过了代理的 socket 超时。把代理的server.timeout和客户端 idle 超时都调大,同时确认max_retries不会在流已经开始后重试——流式请求重试会导致重复内容。

错误四:请求体 JSON 解析失败。如果代理里对 body 做了JSON.parse再改写,注意 chunked 场景下end之前 body 不完整,必须等end再 parse。另外body += chunk在 chunk 是 Buffer 时默认按 utf8 转,中文多字节字符跨 chunk 边界可能被截断,稳妥做法是先收集 Buffer 数组再Buffer.concat后toString('utf8')。

错误五:404 Not Found。代理只监听了/v1/chat/completions,OpenCode 如果请求/v1/models或别的路径就会 404。要么在代理里补上这些路由,要么确认 OpenCode 的wire_api配置没让它走别的 endpoint。

6. 继续接入与验证

链路跑通之后,建议按这个顺序往下走:先在模型对话页面确认统一 Key 能正常出流式结果,排除 Key 和通道问题;再回到本地代理,用上面的 curl 和抓包确认 chunked 透传;最后才让 OpenCode 跑真实 Agent 任务。这样出问题时你能快速判断是通道、代理还是客户端。

如果你打算长期用 OpenCode 跑编码和 Agent 任务,可以看下 Coding Plan 的额度方案,比按次调用更适合高频场景。接入文档里有完整的 base URL 和鉴权说明,配置字段对不上时以文档为准。API Keys 页面可以随时新建和吊销 Key,建议给本地代理单独建一个,方便轮换。

最后留一个实用习惯:把代理日志按天切分,log_chunks打开后日志会涨得很快,但排障时它就是你的时间线。等链路稳定了再关掉,能省不少磁盘。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询