ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【Agent】【OpenCode】本地代理分析(响应透传):TaoToken 统一 Key 接入与 settings.json 配置骨架

【Agent】【OpenCode】本地代理分析(响应透传):TaoToken 统一 Key 接入与 settings.json 配置骨架 1. OpenCode 本地代理为什么要做响应透传OpenCode 这类 Agent 工具在本地跑起来之后请求链路通常是这样的OpenCode 客户端把对话请求发给本地代理本地代理再转发给真正的模型服务拿到响应后原样回传给客户端。这条链路里最容易出问题的不是请求发出去而是响应能不能完整、实时地传回来。我试过在本地代理里手动拼响应体结果流式输出直接变成一次性返回前端聊天框卡半天才刷出全部内容。后来才明白问题出在响应透传没做对。所谓响应透传就是把上游返回的状态码、响应头、响应体包括 SSE 流式分块原封不动地交给下游客户端中间不做缓冲、不做改写。OpenCode 的本地代理场景里响应透传要处理两种形态。普通响应是一次性返回的 JSON比如模型列表、非流式对话结果流式响应是 SSE服务端会持续推送data: {...}分块客户端要能边收边渲染。如果代理层用res.send()或者手动拼接字符串流式就废了。另一个现实问题是多模型 Key 管理。Agent 开发者手里往往有好几家的 KeyOpenCode 配置里散落着不同 baseURL 和 apiKey换模型就要改配置、重启。TaoToken 提供统一 Key 和统一 API 通道把多模型入口收敛到一个地址本地代理只需要认一个上游响应透传链路也跟着简化。这篇就围绕 OpenCode 本地代理的响应透传给出 TaoToken 统一 Key 接入的settings.json配置骨架再演示一次请求透传验证确认响应完整回传。适合正在搭 Agent 本地代理、需要统一管理多模型 Key 的开发者。2. TaoToken 统一 Key 与 API 通道前置准备TaoToken 在这里扮演的角色是统一入口你拿到一个 Key通过一个 API 地址访问多家模型OpenCode 本地代理不用再为每个模型维护一套鉴权逻辑。对响应透传来说上游只有一个透传链路更干净。先做两件事。第一去官网了解接入方式地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 页面里有接入文档入口。第二进控制台创建 API Key控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。API 基础地址用 https://taotoken.net/api 注意这个地址不带 UTM 参数配置里直接写它。Key 拿到后先别急着塞进 OpenCode建议用 curl 单独验证一次确认 Key 和通道是通的再往代理里接。这样排障时能快速区分是 Key 问题还是代理透传问题。模型对话调试可以用 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你后面要做长期编码或 Agent 任务可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。注意Key 只放在本地环境变量或本地配置文件里不要提交到 Git 仓库也不要在日志里打印完整 Key。3. settings.json 配置骨架与本地代理透传实现OpenCode 的配置一般落在settings.json本地代理的监听地址、上游地址、鉴权头都在这里定义。下面给一份配置骨架你可以按自己的目录结构调整。{ agent: { proxy: { listen: http://127.0.0.1:8787, upstream: https://taotoken.net/api, authHeader: Authorization, authPrefix: Bearer , apiKeyEnv: TAOTOKEN_API_KEY, timeoutMs: 120000, stream: true }, models: { default: claude-sonnet, fallback: gpt-4o-mini } } }字段含义对照一下字段作用建议值listen本地代理监听地址127.0.0.1:8787upstream上游 API 基础地址https://taotoken.net/apiauthHeader鉴权头名称AuthorizationauthPrefix鉴权头前缀Bearer 加空格apiKeyEnv读取 Key 的环境变量名TAOTOKEN_API_KEYtimeoutMs上游超时时间120000stream是否开启流式透传true配置里不写死 Key而是通过环境变量注入。启动代理前先导出export TAOTOKEN_API_KEY你的Key接下来是本地代理的核心透传逻辑用 Node.js 写一个最小实现。关键点有三个请求头透传、响应头透传、响应体 pipe。const http require(http); const https require(https); const { URL } require(url); const UPSTREAM https://taotoken.net/api; const API_KEY process.env.TAOTOKEN_API_KEY; const server http.createServer((req, res) { let body ; req.on(data, (chunk) (body chunk)); req.on(end, () { const target new URL(UPSTREAM req.url); const headers { Content-Type: req.headers[content-type] || application/json, Authorization: Bearer API_KEY, Content-Length: Buffer.byteLength(body), }; const proxyReq https.request( { hostname: target.hostname, port: 443, path: target.pathname target.search, method: req.method, headers, }, (proxyRes) { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); } ); proxyReq.on(error, (err) { console.error(proxy error:, err.message); res.writeHead(502, { Content-Type: application/json }); res.end(JSON.stringify({ error: upstream unreachable })); }); proxyReq.write(body); proxyReq.end(); }); }); server.listen(8787, 127.0.0.1, () { console.log(local proxy on http://127.0.0.1:8787); });这段代码里res.writeHead(proxyRes.statusCode, proxyRes.headers)把上游状态码和响应头原样写回proxyRes.pipe(res)把响应流直接管道给客户端。流式场景下SSE 的每个 chunk 会实时转发pipe 在流结束时自动关闭下游响应不需要手动res.end()。Content-Length用Buffer.byteLength(body)计算不要用body.length中文等多字节字符会导致长度算错上游可能直接拒绝或截断请求体。4. 验证请求透传与响应完整回传代理跑起来后先验证普通响应再验证流式响应。普通响应验证用 curl 打本地代理curl -s http://127.0.0.1:8787/v1/chat/completions \ -H Content-Type: application/json \ -d { model: claude-sonnet, messages: [{role: user, content: 用一句话说明什么是响应透传}], stream: false }预期结果是返回一段完整 JSON包含choices字段和模型回复内容。如果返回 401说明 Key 或鉴权头有问题返回 502说明上游地址或网络层有问题。流式响应验证把stream改成truecurl -N http://127.0.0.1:8787/v1/chat/completions \ -H Content-Type: application/json \ -d { model: claude-sonnet, messages: [{role: user, content: 数到五}], stream: true }-N关闭 curl 缓冲你应该能看到data: {...}一行行实时刷出来而不是等几秒后一次性出现。这就是响应透传生效的直接证据。如果全部内容一次性出现检查代理里是不是用了缓冲写法或者stream配置没开。验证响应头是否透传可以加-i看返回头curl -i -s http://127.0.0.1:8787/v1/chat/completions \ -H Content-Type: application/json \ -d {model:claude-sonnet,messages:[{role:user,content:hi}],stream:true}重点看content-type是不是text/event-streamtransfer-encoding是不是chunked。这两个头能透传说明响应头链路是通的。5. 本篇常见错误排查502 Bad Gateway 反复出现。先确认upstream地址写的是https://taotoken.net/api不要带多余路径。再确认本机能正常访问该地址可以用 curl 直接打上游验证。如果上游通、代理不通检查proxyReq.on(error)有没有打印具体错误信息。流式输出变成一次性返回。最常见原因是代理层做了缓冲比如先把proxyRes收集完再res.end()。正确做法是proxyRes.pipe(res)让数据边收边转发。另一个原因是客户端侧缓冲curl 要加-N前端 fetch 要确认没开额外缓冲。401 Unauthorized。检查环境变量TAOTOKEN_API_KEY是否导出成功echo $TAOTOKEN_API_KEY看有没有值。再检查鉴权头拼接Bearer后面有一个空格少了空格会鉴权失败。Key 本身如果失效去 API Keys 页面重新生成。请求体被截断或中文乱码。检查Content-Length是不是用Buffer.byteLength(body)算的。用body.length在纯 ASCII 下没问题一旦有中文就会偏小上游按错误的长度读取请求体就被截断。请求一直挂起直到超时。检查有没有调用proxyReq.end()。只write不end请求永远不会发出。可以合并成proxyReq.end(body)效果一样。响应头丢失自定义字段。res.writeHead传入的是proxyRes.headers如果上游返回了自定义头理论上会一起透传。如果发现丢了检查中间有没有手动改写 headers 对象。6. 统一 Key 接入后的下一步本地代理透传链路跑通之后OpenCode 侧只需要把 baseURL 指向http://127.0.0.1:8787模型名按 TaoToken 支持的名称填Key 交给代理层统一注入。这样换模型不用改 OpenCode 配置只改代理里的默认模型字段。如果你在排障或接入阶段卡住优先看 API Keys 和接入文档https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 与 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。想先验证模型通不通用模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。长期跑编码或 Agent 任务看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。透传这块我踩过的坑基本都在响应侧状态码透传了但响应头没透传客户端拿不到text/event-stream就不按流式解析pipe 用对了但上游超时没设长任务直接断。把timeoutMs调大、把proxyRes.pipe(res)写对这两步能解决大部分响应不完整的问题。
返回列表