当前位置: 首页 > news >正文

38、【Agent】【OpenCode】本地代理分析(二)

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除

背景

上篇 blog
【Agent】【OpenCode】本地代理分析(一)
开始分析本地代理的实现,该本地 HTTP 代理服务器可以接收来自 OpenCode(或其他兼容 OpenAI API 的客户端)的请求,并将请求转发给 DashScope 的 API 兼容接口,其整体结构使用 Node.js 内置模块 http 创建一个本地 HTTP 服务器,该服务器监听本地的2048端口,所有发往http://127.0.0.1:2048的请求都会进入这个回调函数,该代理只处理 OpenAI 风格的聊天请求,其中条件/v1/chat/completions是 OpenCode 默认发送的 endpoint,然后详细分析了 endpoint 和 OpenAI 风格的概念,下面继续分析

OpenCode

上篇 blog 分析了 endpoint 概念,并简单提了下 OpenAI 风格,下面继续分析 OpenAI 风格的核心特征:

  • 首先是固定的 endpoint 路径/v1/chat/completions,所有的聊天请求都会发到这里
  • 其次是请求格式统一
{"model":"qwen-plus","messages":[{"role":"user","content":"你好"}],"stream":true,"temperature":0.7}
  • 统一的认证方式Authorization: Bearer sk-xxxxxxxxxxxx
  • 统一的流式响应格式(SSE)

非流式

{"choices":[{"message":{"content":"你好!"}}]}

流式(每块一个 JSON)

{"choices":[{"delta":{"content":"你"}}]}{"choices":[{"delta":{"content":"好"}}]}{"choices":[{"delta":{"content":"!"}}]}

OpenCode 只发送 OpenAI 风格的 endpoint 和格式,所以本地代理这里必须监听/v1/chat/completions(OpenAI 风格 endpoint),然后接收 OpenAI 格式的 JSON,再转发给 DashScope 的兼容接口/compatible-mode/v1/chat/completions

OK,继续分析本地代理的内容

// dashscope-proxy.jsconsthttp=require('http');consthttps=require('https');constserver=http.createServer((req,res)=>{console.log(`📥 Received${req.method}${req.url}`);if(req.method==='POST'&&req.url==='/v1/chat/completions'){letbody='';req.on('data',chunk=>body+=chunk);req.on('end',()=>{// 从原始请求中获取 Authorization 头constauthHeader=req.headers['authorization']||'';constoptions={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)}};constproxyReq=https.request(options,(proxyRes)=>{res.writeHead(proxyRes.statusCode,proxyRes.headers);proxyRes.pipe(res);// 自动处理流式/非流式 + 自动 end()});proxyReq.on('error',(e)=>{console.error('Proxy error:',e.message);res.writeHead(502);res.end('Bad Gateway');});proxyReq.write(body);proxyReq.end();});return;}res.writeHead(404);res.end('Not Found');});server.listen(2048,'127.0.0.1',()=>{console.log('✅ DashScope proxy with full OpenCode & Ollama compatibility running on http://127.0.0.1:2048');});

当收到的是指定请求/v1/chat/completions时,开始拼接 request body

letbody='';req.on('data',chunk=>body+=chunk);req.on('end',()=>{...});

这里的 HTTP request body 可能是分块(chunked)传输的,通过监听data事件把所有数据块拼成完整的字符串 body,然后当end事件触发时,说明整个 request body 已经接收完毕,可以开始处理,注意,此时的 body 仍是个 JSON 字符串,而不是 JS 对象,除非像之前 blog 【Agent】【OpenCode】本地代理(JavaScript 脚本) 介绍的,要用JSON.parse解析,并修改 request body 的内容,此时的 body 内容可能是这样的

{"model":"qwen-plus","messages":[{"role":"user","content":"你好"}]}

这里涉及比较多的点,下面详细分析下

首先是分块传输的概念,分块传输(Chunked Transfer Encoding)是 HTTP/1.1 中一种动态传输数据的方式,适合服务器在发送响应(或客户端发送请求)时,还不知道整个内容的场景,在分块传输中,数据被分成多个块(chunks)进行发送,每个块前面标明该数据块的大小,最后一个大小为 0 的块表示结束,举个原始 HTTP 响应片断的例子

HTTP/1.1 200 OK Transfer-Encoding: chunked 5\r\n ← 第一块长度是 5 字节 Hello\r\n ← 实际数据 "Hello" 6\r\n ← 第二块长度是 6 字节 World\r\n ← 实际数据 " World" 0\r\n ← 长度为 0,表示结束 \r\n

最终数据流拼接起来就是Hello World,另外注意,Transfer-Encoding: chunked出现时,不会同时有Content-Length字段,在这段代码中

letbody='';req.on('data',chunk=>body+=chunk);req.on('end',()=>{// 完整 body 内容});

Node.js 底层已经自动解码了分块传输,所以这里收到的每个 chunk 都是原始数据的一部分(Buffer 或 string),不需要手动解析十六进制长度,所以无论是客户端是用Content-Length一次性发完数据,还是用 chunked 分多次发送,本地代理都能正确拼出完整 body

OK,下面再介绍下分块传输的适用场景

场景说明
流式响应如大模型聊天(stream=true),一边生成 token,一边发送
动态生成内容服务器实时读取日志,执行命令并输出
上传大文件客户端可能分块上传(虽然较少见,但 HTTP 允许)
代理转发接收 OpenCode 请求时,可能会收到 chunked body,比如上面第一个流式响应

所以,本地代理这里监听dataend是处理 HTTP 请求的标准做法,能兼容所有传输方式


OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog

http://www.jsqmd.com/news/592719/

相关文章:

  • 实测梦幻动漫魔法工坊:用LoRA调整画风,轻松打造不同风格的动漫作品
  • 浪琴官方售后服务中心新址实地考察报告(2026年4月最新地址电话) - 亨得利官方服务中心
  • 避坑指南:淘晶驰串口屏文件浏览器控件中文乱码?用UTF8字库5分钟搞定!
  • 亨得利官方售后服务中心新址实地考察报告(2026年4月最新地址电话) - 亨得利官方服务中心
  • MogFace-large部署案例:嵌入式AI盒子(Jetson Orin)上轻量运行方案
  • ODrive自定义控制算法开发:从架构思维到高级应用
  • MyKeymap应用专属键盘映射配置指南
  • 面向人形机器人的弹热冷却技术:高效热管理解决方案
  • VoxCPM-1.5-WEBUI作品集:高音质语音合成效果大赏
  • 企业级应用权限架构设计与实践指南
  • AMD Ryzen处理器终极调优:SMU Debug Tool完全指南揭秘硬件性能潜力
  • StructBERT-Large镜像部署教程:GPU加速推理环境搭建指南
  • S7-200组态王在火电厂脱硫应用中的后处理产品:梯形图原理图及IO分配解析
  • 零基础入门:星图平台私有化部署Qwen3-VL:30B,Clawdbot飞书接入完整指南
  • 费城“敬畏部”:AI 与艺术融合的新奇沉浸式体验
  • 如何彻底解决Windows多显示器DPI缩放难题?SetDPI的技术实现与实战指南
  • 教师必备:用CosyVoice快速制作带语音讲解的理工科课件
  • python 文件管理库 Path 解析(详细基础)
  • 效率提升:用快马一键生成优化版快速排序模块,轻松应对大数据排序
  • (支援发出,转发需官方授权)某个名师大家可能还是一个女的自称“廉者不受嗟来之食”对自己对自己的学生和想要招(找)的学生都一样。
  • 2026年宠物口腔医生口碑推荐,狗狗洗牙/猫咪口腔/宠物牙科/狗狗拔牙/狗口腔溃疡诊疗/狗狗牙科,宠物口腔医生推荐 - 品牌推荐师
  • 如何用Depressurizer拯救混乱的Steam游戏库?3个高效管理技巧
  • OpenClaw与Python:构建高效自动化脚本,赋能复杂工作场景落地
  • 电动夹爪Modbus通讯控制实战:从硬件接线到编程控制的全流程解析
  • 告别Idea!用VSCode从零搭建Fabric 1.21.5模组开发环境(附Gradle加速配置)
  • BiliTools:2026年最强大的免费哔哩哔哩资源管理工具终极指南
  • python Array
  • 3步实现微信聊天记录永久管理,让珍贵对话不再流失
  • 告别C++硬编码!用QML+QtSql写一个可复用的SQLite数据库组件(附完整源码)
  • 2025届最火的六大降AI率方案推荐