news 2026/9/27 11:53:26

39、【Agent】【OpenCode】本地代理分析(body拼接):用 TaoToken 统一 Key 打通分块传输调试链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
39、【Agent】【OpenCode】本地代理分析(body拼接):用 TaoToken 统一 Key 打通分块传输调试链路

1. OpenCode 本地代理为什么总在 body 拼接上翻车

如果你正在用 OpenCode 这类 Agent 工具,并且想让它走自己的统一 API 通道,大概率会碰到一个很具体的场景:OpenCode 只认 OpenAI 风格的/v1/chat/completions,而你的上游通道需要统一 Key、统一入口。于是你在本地起了一个代理,负责把 OpenCode 发来的请求接住、拼完整、再转发出去。

问题就出在“拼完整”这一步。OpenCode 发出的 HTTP 请求 body 不一定是带Content-Length的一次性写入,很多时候是分块传输(chunked)。Node.js 的req.on('data')每次只给你一个数据片断,如果你直接把每个 chunk 当成完整 JSON 去JSON.parse,就会看到类似Unexpected end of JSON input或者Unexpected token的报错。更隐蔽的情况是:前几个 chunk 恰好拼成了合法 JSON 的前半段,解析不报错但字段缺失,转发出去后上游返回 400,你回头查日志却看不出哪里断了。

这篇就聚焦 OpenCode Agent 本地代理下 body 拼接与分块传输的调试链路,用 TaoToken 统一 Key 和 API 通道接入本地代理配置。你会拿到可复制的config.toml骨架、settings.json片段,以及分块传输的验证动作和报错排查清单。适合已经在跑 OpenCode、想自己写一层本地代理做请求分析或通道统一的开发者。

2. TaoToken 前置:统一 Key 与 API 通道准备

本地代理要转发,就得有一个稳定的上游入口。TaoToken 在这里的角色是提供统一的 API 通道和 Key 管理,让 OpenCode 的本地代理只需要认一个 base URL 和一把 Key,不用在代理里硬编码多个上游。

先拿到 Key。打开控制台创建 API Key:

https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite

创建后你会得到类似sk-xxxx的字符串。这个 Key 就是本地代理转发时放在Authorization: Bearer里的凭证。注意不要在客户端代码里明文提交,放到本地代理的环境变量或配置文件里。

TaoToken 的 API 入口是:

https://taotoken.net/api

本地代理的上游 base URL 就填这个,路径保持 OpenAI 风格,即/v1/chat/completions。这样 OpenCode 发到本地代理的请求,代理拼完 body 后原样转发到https://taotoken.net/api/v1/chat/completions,认证头换成你刚创建的 Key。

如果你还没确认模型通道是否通,可以先用模型对话页面做一次最小验证:

https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite

这一步的意义是:在写代理之前,先确认 Key 和通道本身没问题。否则代理报错时你分不清是拼接逻辑错了还是上游认证失败。

3. 可复制配置:config.toml 骨架与 settings.json 片段

OpenCode 的配置一般分两层:一层是 OpenCode 自己的config.toml,声明 provider 和 base URL;另一层是本地代理的settings.json,声明监听端口和上游地址。下面给的是骨架,字段名按你实际版本微调。

先看 OpenCode 侧的config.toml:

# ~/.config/opencode/config.toml [provider.local_proxy] name = "local-proxy" base_url = "http://127.0.0.1:8787/v1" api_key = "sk-local-placeholder" model = "your-model-name" [agent] provider = "local_proxy"

这里的关键是base_url指向本地代理的/v1,而不是直接指向 TaoToken。OpenCode 会把/v1/chat/completions拼到这个 base_url 后面,所以本地代理必须监听这个路径。

再看本地代理的settings.json:

{ "listen": { "host": "127.0.0.1", "port": 8787, "path": "/v1/chat/completions" }, "upstream": { "base_url": "https://taotoken.net/api", "path": "/v1/chat/completions", "api_key_env": "TAOTOKEN_API_KEY" }, "body": { "max_bytes": 10485760, "join_chunks": true, "parse_after_end": true }, "log": { "level": "debug", "dump_body": false } }

join_chunks和parse_after_end是这篇的核心开关。前者表示把所有data事件拼成一个完整字符串,后者表示只在end事件触发后才做JSON.parse。max_bytes是保护阈值,防止异常大 body 把内存打满。

代理的核心拼接逻辑用 Node.js 写出来大概是这样:

const http = require('http'); const server = http.createServer((req, res) => { if (req.method !== 'POST' || req.url !== '/v1/chat/completions') { res.writeHead(404); return res.end('not found'); } let body = ''; let size = 0; req.on('data', chunk => { size += chunk.length; if (size > 10 * 1024 * 1024) { req.destroy(); return; } body += chunk; }); req.on('end', () => { let payload; try { payload = JSON.parse(body); } catch (e) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'invalid json body' })); } // 转发到 TaoToken forward(payload, res); }); }); server.listen(8787, '127.0.0.1');

注意body += chunk这一行。它看起来简单,但前提是chunk是 Buffer 或字符串,Node.js 默认给的是 Buffer,+=会隐式转成字符串。如果 body 里有非 UTF-8 字节,隐式转换可能出问题,稳妥写法是显式chunk.toString('utf8')或者用数组收集后Buffer.concat。

4. 验证请求:分块传输下 body 拼接是否成功

配置写完后,不要直接上 OpenCode 跑,先用 curl 模拟分块传输,确认代理的拼接逻辑是对的。

第一种验证:带Content-Length的一次性请求。

curl -v http://127.0.0.1:8787/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-local-placeholder" \ -d '{"model":"your-model-name","messages":[{"role":"user","content":"ping"}]}'

这种请求 Node.js 可能一次就收到完整 body,data事件只触发一次。如果代理返回正常,说明基础转发链路通了。

第二种验证:强制分块传输。用Transfer-Encoding: chunked,并且手动分两次写:

curl -v http://127.0.0.1:8787/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Transfer-Encoding: chunked" \ -H "Authorization: Bearer sk-local-placeholder" \ --data-binary @- <<'EOF' {"model":"your-model-name","messages":[{"role":"user","content":"ping EOF

上面这个写法不完整,更可靠的方式是用 Node.js 脚本模拟分块:

const http = require('http'); const req = http.request({ host: '127.0.0.1', port: 8787, path: '/v1/chat/completions', method: 'POST', headers: { 'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked' } }, res => { let data = ''; res.on('data', c => data += c); res.on('end', () => console.log('status:', res.statusCode, 'body:', data)); }); req.write('{"model":"your-model-name",'); setTimeout(() => req.write('"messages":[{"role":"user","content":"ping"}]}'), 200); req.end();

这个脚本故意把 JSON 切成两段,中间隔 200ms。如果代理的join_chunks生效,上游会收到完整 JSON 并正常返回;如果代理在第一个 chunk 就解析,会直接 400。

成功的结果是:代理日志里能看到data事件触发两次,end事件触发一次,JSON.parse成功,转发后上游返回 200,响应体里有正常的choices字段。

5. 本篇常见错排查清单

报错一:Unexpected end of JSON input

原因几乎都是JSON.parse写在了data事件里,而不是end事件里。检查你的代码,parse必须等所有 chunk 到齐。另一个可能是max_bytes太小,body 被截断,但这种情况通常会先触发req.destroy()。

报错二:Unexpected token < in JSON at position 0

说明 body 开头不是{,可能是上游返回了 HTML 错误页,或者代理把响应体当成了请求体。检查转发逻辑里Content-Type是否被正确设置,以及是否误把上游响应写回了请求解析流程。

报错三:上游返回 401 或 403

本地代理转发时没有带上正确的Authorization头,或者 Key 从环境变量读取失败。检查TAOTOKEN_API_KEY是否在启动代理的 shell 里 export 了。可以用printenv TAOTOKEN_API_KEY确认。

报错四:OpenCode 侧一直转圈,代理日志没有请求

说明 OpenCode 的base_url没指向本地代理,或者端口不对。检查config.toml里的base_url是否是http://127.0.0.1:8787/v1,以及代理是否真的在 8787 监听。用curl http://127.0.0.1:8787/v1/chat/completions发个 GET 看是否返回 404(说明服务活着)。

报错五:分块传输时 body 拼接后多了换行或空格

某些客户端在 chunk 之间会插入\r\n,如果你手动处理了 chunk 边界,可能把分隔符也拼进去了。Node.js 的data事件已经去掉了 chunked 编码的元数据,正常情况下不会有多余字符。如果确实有,检查是否在chunk.toString()之后又做了trim()或replace。

报错六:大 body 导致内存飙升

body += chunk在超大请求下会频繁创建新字符串。如果 OpenCode 发送的上下文很长,建议改成数组收集:

const chunks = []; req.on('data', c => chunks.push(c)); req.on('end', () => { const body = Buffer.concat(chunks).toString('utf8'); // parse... });

这样内存占用更可控,也避免了隐式编码转换的问题。

6. 接入文档与后续调试入口

本地代理跑通后,如果你要把它接到更完整的编码工作流里,比如让 OpenCode 长时间跑 Agent 任务,建议看一下 Coding Plan 的配置方式,它涉及更细的通道和额度管理:

https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite

如果你在接入过程中遇到认证或路径问题,接入文档里有完整的 endpoint 说明和示例:

https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

需要重新生成或管理 Key 时,回到 API Keys 页面:

https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite

最后提醒一个实操细节:本地代理的日志级别在调试阶段开到debug,但dump_body保持false。因为 OpenCode 的请求 body 里可能包含你的代码片段和上下文,打到日志里既占空间又有泄露风险。确认拼接逻辑没问题后,把日志级别调回info,只保留状态码和耗时。这样你的本地代理既能稳定拼接分块 body,又不会在长期运行中留下敏感数据。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 11:53:26

5个免费工具搞定手机网站模板psd,拒绝丑到爆

5个免费工具搞定手机网站模板psd,拒绝丑到爆 别再信什么“一键生成”了,那些套出来的手机网站模板psd,丑得让人想砸电脑。 甲方盯着屏幕直摇头,你说“再改改”,心里在滴血。 用对免费工具,把psd变成真能用的代码,才是正经事。 项目背景与需求:培训机构选错,项目就废了一半…

作者头像 李华
网站建设 2026/9/27 11:53:18

短视频公司网站建设方案多少钱?安全落地避坑指南

短视频公司网站建设方案多少钱?安全落地避坑指南 自己不会代码想做网站,最怕的不是贵,而是花了几千块做个“裸奔”站,上线三天就被挂马。很多短视频公司老板觉得,做个展示型官网或者接单落地页,找外包搞定就行,至于多少钱,市场上从3000到5万都有报价。但今天我要泼盆冷水:对于短视频这种内容密集、交互频繁的…

作者头像 李华
网站建设 2026/9/27 11:52:19

嵌入式C++实战:STM32裸机开发中的final、std::array与constexpr

1. 这个标题不是吐槽&#xff0c;是嵌入式C转型者的真实心电图“看了三篇了&#xff0c;一行都没让我写呢”——这句话我第一次在STM32技术群看到时&#xff0c;手里的开发板差点没拿稳。不是因为夸张&#xff0c;而是太真实。它精准戳中了当前嵌入式工程师学C时最普遍、最隐蔽…

作者头像 李华
网站建设 2026/9/27 11:51:59

做网站需要会编程吗?2026建站速查手册:3步搞定不拖沓

做网站需要会编程吗?2026建站速查手册:3步搞定不拖沓 改个需求建站公司拖一周,后台改个颜色要排期半个月,这种日子你还要过多久?很多老板一上来就问“做网站需要会编程吗”,其实这问反了。真正的痛点不是你会不会写代码,而是你手里有没有一套能随时调动资源的“速查手册”。…

作者头像 李华
网站建设 2026/9/27 11:51:48

wordpress主题yusi实战速查手册:域名服务器避坑与SEO全解

wordpress主题yusi实战速查手册:域名服务器避坑与SEO全解 域名服务器搞不懂,是不是让你对着后台那一堆参数头大?别慌,这不仅是新手,很多干了五年六年的老手在部署 wordpress主题yusi 时也会踩坑。很多设计师转做前端或独立站运营,技术底子薄,一碰到 Nginx 配置或 DNS…

作者头像 李华