├── .idea ├── .gitignore └── vcs.xml ├── output ├── request.bin ├── request.json └── request_with_length.bin ├── package-lock.json ├── package.json ├── request.proto ├── request_body_build.js └── response_stream.mjs /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /output/request.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekohy/Cursor/25bb261bd8ed5b0452fb9fdfce33d4e28147af62/output/request.bin -------------------------------------------------------------------------------- /output/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": [ 3 | { 4 | "message": "你好,你是谁", 5 | "roles": "1", 6 | "uuid": "756eae9a-f933-4ab8-a2b5-44a4a760142c" 7 | }, 8 | { 9 | "message": "你好!我是 Claude,一个由 Anthropic 开发的 AI 助手。我可以用中文和你交流,也可以帮你解决编程相关的问题。有什么我可以帮你的吗?\n", 10 | "roles": "2", 11 | "uuid": "e9e297c3-7a0e-4d5f-bb96-917ed38d949b" 12 | }, 13 | { 14 | "message": "你好,你是谁", 15 | "roles": "1", 16 | "uuid": "756eae9a-f933-4ab8-a2b5-44a4a760142c" 17 | } 18 | ], 19 | "unknown": { 20 | "type": "Buffer", 21 | "data": [] 22 | }, 23 | "paths": "/c:/Users/Test/.cursor-tutor", 24 | "model": { 25 | "model": "claude-3.5-sonnet", 26 | "unknown": "" 27 | }, 28 | "traceid": "e2501298-9221-4eb1-971c-ad65797060e4", 29 | "uknown1": 0, 30 | "uknown2": 0, 31 | "uknown3": "c929448d-6425-49aa-b00b-6aed1ea76a82", 32 | "uknown4": 1, 33 | "uknown5": 0, 34 | "uknown6": 0, 35 | "uknown7": 0, 36 | "uknown8": 0 37 | } -------------------------------------------------------------------------------- /output/request_with_length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekohy/Cursor/25bb261bd8ed5b0452fb9fdfce33d4e28147af62/output/request_with_length.bin -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cursor-Test", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "Cursor-Test", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "node-fetch": "^3.3.2", 12 | "protobufjs": "^7.4.0", 13 | "undici": "^6.21.0", 14 | "uuid": "^11.0.3" 15 | } 16 | }, 17 | "node_modules/@protobufjs/aspromise": { 18 | "version": "1.1.2", 19 | "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", 20 | "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", 21 | "license": "BSD-3-Clause" 22 | }, 23 | "node_modules/@protobufjs/base64": { 24 | "version": "1.1.2", 25 | "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", 26 | "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", 27 | "license": "BSD-3-Clause" 28 | }, 29 | "node_modules/@protobufjs/codegen": { 30 | "version": "2.0.4", 31 | "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", 32 | "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", 33 | "license": "BSD-3-Clause" 34 | }, 35 | "node_modules/@protobufjs/eventemitter": { 36 | "version": "1.1.0", 37 | "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", 38 | "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", 39 | "license": "BSD-3-Clause" 40 | }, 41 | "node_modules/@protobufjs/fetch": { 42 | "version": "1.1.0", 43 | "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", 44 | "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", 45 | "license": "BSD-3-Clause", 46 | "dependencies": { 47 | "@protobufjs/aspromise": "^1.1.1", 48 | "@protobufjs/inquire": "^1.1.0" 49 | } 50 | }, 51 | "node_modules/@protobufjs/float": { 52 | "version": "1.0.2", 53 | "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", 54 | "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", 55 | "license": "BSD-3-Clause" 56 | }, 57 | "node_modules/@protobufjs/inquire": { 58 | "version": "1.1.0", 59 | "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", 60 | "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", 61 | "license": "BSD-3-Clause" 62 | }, 63 | "node_modules/@protobufjs/path": { 64 | "version": "1.1.2", 65 | "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", 66 | "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", 67 | "license": "BSD-3-Clause" 68 | }, 69 | "node_modules/@protobufjs/pool": { 70 | "version": "1.1.0", 71 | "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", 72 | "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", 73 | "license": "BSD-3-Clause" 74 | }, 75 | "node_modules/@protobufjs/utf8": { 76 | "version": "1.1.0", 77 | "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", 78 | "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", 79 | "license": "BSD-3-Clause" 80 | }, 81 | "node_modules/@types/node": { 82 | "version": "22.9.0", 83 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", 84 | "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", 85 | "license": "MIT", 86 | "dependencies": { 87 | "undici-types": "~6.19.8" 88 | } 89 | }, 90 | "node_modules/data-uri-to-buffer": { 91 | "version": "4.0.1", 92 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", 93 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", 94 | "license": "MIT", 95 | "engines": { 96 | "node": ">= 12" 97 | } 98 | }, 99 | "node_modules/fetch-blob": { 100 | "version": "3.2.0", 101 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", 102 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", 103 | "funding": [ 104 | { 105 | "type": "github", 106 | "url": "https://github.com/sponsors/jimmywarting" 107 | }, 108 | { 109 | "type": "paypal", 110 | "url": "https://paypal.me/jimmywarting" 111 | } 112 | ], 113 | "license": "MIT", 114 | "dependencies": { 115 | "node-domexception": "^1.0.0", 116 | "web-streams-polyfill": "^3.0.3" 117 | }, 118 | "engines": { 119 | "node": "^12.20 || >= 14.13" 120 | } 121 | }, 122 | "node_modules/formdata-polyfill": { 123 | "version": "4.0.10", 124 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 125 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 126 | "license": "MIT", 127 | "dependencies": { 128 | "fetch-blob": "^3.1.2" 129 | }, 130 | "engines": { 131 | "node": ">=12.20.0" 132 | } 133 | }, 134 | "node_modules/long": { 135 | "version": "5.2.3", 136 | "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", 137 | "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", 138 | "license": "Apache-2.0" 139 | }, 140 | "node_modules/node-domexception": { 141 | "version": "1.0.0", 142 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 143 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 144 | "funding": [ 145 | { 146 | "type": "github", 147 | "url": "https://github.com/sponsors/jimmywarting" 148 | }, 149 | { 150 | "type": "github", 151 | "url": "https://paypal.me/jimmywarting" 152 | } 153 | ], 154 | "license": "MIT", 155 | "engines": { 156 | "node": ">=10.5.0" 157 | } 158 | }, 159 | "node_modules/node-fetch": { 160 | "version": "3.3.2", 161 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", 162 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", 163 | "license": "MIT", 164 | "dependencies": { 165 | "data-uri-to-buffer": "^4.0.0", 166 | "fetch-blob": "^3.1.4", 167 | "formdata-polyfill": "^4.0.10" 168 | }, 169 | "engines": { 170 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 171 | }, 172 | "funding": { 173 | "type": "opencollective", 174 | "url": "https://opencollective.com/node-fetch" 175 | } 176 | }, 177 | "node_modules/protobufjs": { 178 | "version": "7.4.0", 179 | "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", 180 | "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", 181 | "hasInstallScript": true, 182 | "license": "BSD-3-Clause", 183 | "dependencies": { 184 | "@protobufjs/aspromise": "^1.1.2", 185 | "@protobufjs/base64": "^1.1.2", 186 | "@protobufjs/codegen": "^2.0.4", 187 | "@protobufjs/eventemitter": "^1.1.0", 188 | "@protobufjs/fetch": "^1.1.0", 189 | "@protobufjs/float": "^1.0.2", 190 | "@protobufjs/inquire": "^1.1.0", 191 | "@protobufjs/path": "^1.1.2", 192 | "@protobufjs/pool": "^1.1.0", 193 | "@protobufjs/utf8": "^1.1.0", 194 | "@types/node": ">=13.7.0", 195 | "long": "^5.0.0" 196 | }, 197 | "engines": { 198 | "node": ">=12.0.0" 199 | } 200 | }, 201 | "node_modules/undici": { 202 | "version": "6.21.0", 203 | "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", 204 | "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", 205 | "license": "MIT", 206 | "engines": { 207 | "node": ">=18.17" 208 | } 209 | }, 210 | "node_modules/undici-types": { 211 | "version": "6.19.8", 212 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 213 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 214 | "license": "MIT" 215 | }, 216 | "node_modules/uuid": { 217 | "version": "11.0.3", 218 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", 219 | "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", 220 | "funding": [ 221 | "https://github.com/sponsors/broofa", 222 | "https://github.com/sponsors/ctavan" 223 | ], 224 | "license": "MIT", 225 | "bin": { 226 | "uuid": "dist/esm/bin/uuid" 227 | } 228 | }, 229 | "node_modules/web-streams-polyfill": { 230 | "version": "3.3.3", 231 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 232 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", 233 | "license": "MIT", 234 | "engines": { 235 | "node": ">= 8" 236 | } 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cursor-Test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "request_body_build.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "private": true, 10 | "dependencies": { 11 | "node-fetch": "^3.3.2", 12 | "protobufjs": "^7.4.0", 13 | "undici": "^6.21.0", 14 | "uuid": "^11.0.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /request.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; // 使用Protobuf版本3 2 | 3 | package aiserver.v1; 4 | 5 | service AiService { 6 | rpc StreamChat (Request) returns (Response); 7 | } 8 | 9 | message Request { 10 | repeated Message message = 2; // 系统和用户的message 11 | bytes unknown = 4; // 也许也为string,没内容,未知用处 12 | string paths = 5; // 也许是本地运行的路径 examples /c:/Users/Test/.cursor-tutor 13 | Model model = 7; // 模型 14 | string traceid = 9; // 追踪id,x-amzn-trace-id:Root=这玩意 和 x-request-id 15 | uint64 uknown1 = 13; // 也许常量0 16 | uint64 uknown2 = 14; // 也许常量0 17 | string uknown3 = 15; // 也许是常量,用来表示对话?9acff59a-35cc-4a1a-befb-7bcb43e69fac 18 | uint64 uknown4 = 16; // 也许常量1 19 | uint64 uknown5 = 22; // 也许常量0 20 | uint64 uknown6 = 24; // 也许常量0 21 | uint64 uknown7 = 28; // 也许常量0 22 | uint64 uknown8 = 29; // 也许常量0 23 | } 24 | 25 | message Model { 26 | string model = 1; // 模型 ep. claude-3.5-sonnet 27 | bytes unknown = 4; // 也许也为string,没内容,未知用处 28 | } 29 | 30 | message Message { 31 | string message = 1; // 发送的消息 32 | uint64 roles = 2; // 返回1为user,2为system 33 | string uuid = 13; // 也许是随机生成的一个uuid用于追踪?记得每一次的同一个对话的消息的uuid相同 34 | } 35 | 36 | message Response { 37 | bytes Unknown = 1; 38 | } -------------------------------------------------------------------------------- /request_body_build.js: -------------------------------------------------------------------------------- 1 | const protobuf = require('protobufjs'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const { v4: uuidv4 } = require('uuid'); 5 | 6 | const protoPath = path.join(__dirname, 'request.proto'); 7 | 8 | protobuf.load(protoPath) 9 | .then(root => { 10 | // 获取指定包下的消息类型 11 | const Request = root.lookupType('aiserver.v1.Request'); 12 | const Model = root.lookupType('aiserver.v1.Model'); 13 | const Message = root.lookupType('aiserver.v1.Message'); 14 | 15 | const userMessage = Message.create({ 16 | message: '你好,你是谁', 17 | roles: 1, // 1表示user 18 | uuid: uuidv4() 19 | }); 20 | 21 | const systemMessage = Message.create({ 22 | message: '你好!我是 Claude,一个由 Anthropic 开发的 AI 助手。我可以用中文和你交流,也可以帮你解决编程相关的问题。有什么我可以帮你的吗?\n', 23 | roles: 2, // 2表示system 24 | uuid: uuidv4() 25 | }); 26 | 27 | // 创建Model对象 28 | const model = Model.create({ 29 | model: 'claude-3.5-sonnet', 30 | unknown: Buffer.from([]) 31 | }); 32 | 33 | // 创建Request对象 34 | const payload = { 35 | message: [userMessage,systemMessage,userMessage], 36 | unknown: Buffer.from([]), 37 | paths: '/c:/Users/Test/.cursor-tutor', 38 | model: model, 39 | traceid: 'e2501298-9221-4eb1-971c-ad65797060e4', 40 | uknown1: 0, 41 | uknown2: 0, 42 | uknown3: uuidv4(), 43 | uknown4: 1, 44 | uknown5: 0, 45 | uknown6: 0, 46 | uknown7: 0, 47 | uknown8: 0 48 | }; 49 | 50 | const errMsg = Request.verify(payload); 51 | if (errMsg) throw Error(errMsg); 52 | 53 | const message = Request.create(payload); 54 | const buffer = Request.encode(message).finish(); 55 | 56 | const length = buffer.length; 57 | const lengthBuffer = Buffer.alloc(5); 58 | 59 | // 使用 writeUIntBE 以大端字节序写入长度 60 | lengthBuffer.writeUIntBE(length, 0, 5); 61 | 62 | const envelope = Buffer.concat([lengthBuffer, buffer]); 63 | 64 | const outputDir = path.join(__dirname, 'output'); 65 | const binaryFilePath = path.join(outputDir, 'request_with_length.bin'); 66 | const jsonFilePath = path.join(outputDir, 'request.json'); 67 | 68 | fs.mkdirSync(outputDir, { recursive: true }); 69 | 70 | // 保存包含长度前缀的二进制文件 71 | fs.writeFileSync(binaryFilePath, envelope); 72 | console.log(`带长度前缀的二进制请求体已保存到 ${binaryFilePath}`); 73 | 74 | // 保存原始二进制文件 75 | fs.writeFileSync(path.join(outputDir, 'request.bin'), buffer); 76 | console.log(`原始二进制请求体已保存到 ${path.join(outputDir, 'request.bin')}`); 77 | 78 | // 保存 JSON 格式 79 | fs.writeFileSync(jsonFilePath, JSON.stringify(payload, null, 2)); 80 | console.log(`JSON 请求体已保存到 ${jsonFilePath}`); 81 | }) 82 | .catch(err => { 83 | console.error('加载 .proto 文件时出错:', err); 84 | }); 85 | -------------------------------------------------------------------------------- /response_stream.mjs: -------------------------------------------------------------------------------- 1 | import path, { dirname } from 'path'; 2 | import { fileURLToPath } from 'url'; 3 | import { promises as fsPromises } from 'node:fs'; 4 | import { Buffer } from 'buffer'; 5 | // 不需要额外安装 node-fetch,Node.js 18+ 已内置 fetch 6 | 7 | const __dirname = dirname(fileURLToPath(import.meta.url)); 8 | 9 | // 构建文件路径 10 | const filePath = path.join(__dirname, 'output', 'request_with_length.bin'); 11 | 12 | async function sendRequest() { 13 | try { 14 | // 读取整个文件到 Buffer 15 | const data = await fsPromises.readFile(filePath); 16 | const fileSize = data.length; 17 | console.log(`文件大小: ${fileSize} 字节`); 18 | 19 | // 定义分隔符模式:'00000000' 20 | const delimiterPattern = Buffer.from('00000000', 'hex'); // 4 bytes: 00 00 00 00 21 | const delimiterLength = delimiterPattern.length; 22 | 23 | // 发起 POST 请求 24 | const options = { 25 | method: 'POST', 26 | headers: { 27 | 'User-Agent': 'connect-es/1.6.1', 28 | 'authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhdXRoMHx1c2VyXzAxSkJZUkFOUUZSMURHMEVXNTkzVFROVzNLIiwidGltZSI6IjE3MzA4MzAyNTgiLCJyYW5kb21uZXNzIjoiNzU4OTIyNzktMTkxZC00NzVlIiwiZXhwIjo0MzIyODMwMjU4LCJpc3MiOiJodHRwczovL2F1dGhlbnRpY2F0aW9uLmN1cnNvci5zaCIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwgb2ZmbGluZV9hY2Nlc3MiLCJhdWQiOiJodHRwczovL2N1cnNvci5jb20ifQ.CVya5bek2xsh-lgU5DaZUiEA0bBUsQMcb-rJGd6nkMI', 29 | 'connect-accept-encoding': 'gzip,br', 30 | 'connect-protocol-version': '1', 31 | 'content-type': 'application/grpc-web+proto', 32 | 'x-amzn-trace-id': 'Root=e2501298-9221-4eb1-971c-ad65797060e4', 33 | 'x-client-key': 'd49b1266343b31dd26a53b5fc9afdd5eb31936c12b0e1ee105a106b591011262', 34 | 'x-cursor-checksum': 'zMPF4o-Gd2ea6e8d761ab6249652f546fd6d34c81fbbb28e5c42164515f1033b288287a9/f43041a7a83639c58e00fb8665c54eb771ab2bc48686f050ed72f360f771e8e4', 35 | 'x-cursor-client-version': '0.42.4', 36 | 'x-cursor-timezone': 'Asia/Shanghai', 37 | 'x-ghost-mode': 'true', 38 | 'x-request-id': 'e2501298-9221-4eb1-971c-ad65797060e4', 39 | 'Cookie': '' 40 | }, 41 | body: data, // 直接使用 Buffer 作为请求体 42 | // 不需要 duplex 选项 43 | }; 44 | 45 | console.log('正在发送请求...'); 46 | const response = await fetch('https://api2.cursor.sh/aiserver.v1.AiService/StreamChat', options); 47 | 48 | if (!response.body) { 49 | throw new Error('响应没有 body'); 50 | } 51 | 52 | const reader = response.body; // Node.js Readable 流 53 | 54 | let buffer = Buffer.alloc(0); 55 | let totalChunks = 0; 56 | let fullbody = ""; 57 | 58 | // 使用异步迭代器读取数据 59 | for await (const chunk of reader) { 60 | // 将新读取的数据追加到缓冲区 61 | buffer = Buffer.concat([buffer, Buffer.from(chunk)]); 62 | // console.log(`缓冲区大小: ${buffer.length} 字节`); 63 | 64 | let delimiterIndex; 65 | while ((delimiterIndex = buffer.indexOf(delimiterPattern)) !== -1) { 66 | // 确保分隔符后至少有3个字节以进行检查 67 | if (buffer.length < delimiterIndex + delimiterLength + 3) { 68 | // 数据不足,等待更多数据 69 | break; 70 | } 71 | 72 | // 读取分隔符后的字节 73 | const byte1 = buffer[delimiterIndex + delimiterLength]; 74 | const byte2 = buffer[delimiterIndex + delimiterLength + 1]; 75 | const byte3 = buffer[delimiterIndex + delimiterLength + 2]; 76 | 77 | // 检查第2个字节是否为0A 78 | if (byte2 !== 0x0A) { 79 | // 条件不满足,继续搜索下一个分隔符 80 | console.warn(`找到分隔符后,第二个字节不是0A: byte2=${byte2.toString(16)}`); 81 | // 移除当前找到的分隔符,继续查找下一个 82 | buffer = buffer.slice(delimiterIndex + 1); 83 | continue; 84 | } 85 | 86 | // 检查第1个字节减去2是否等于第3个字节 87 | if ((byte1 - 2) !== byte3) { 88 | // 条件不满足,继续搜索下一个分隔符 89 | console.warn(`byte1 - 2 不等于 byte3: byte1=${byte1.toString(16)}, byte3=${byte3.toString(16)}`); 90 | // 移除当前找到的分隔符,继续查找下一个 91 | buffer = buffer.slice(delimiterIndex + 1); 92 | continue; 93 | } 94 | 95 | // 第3个字节代表的长度 96 | const length = byte3; 97 | console.log(`找到有效分隔符: byte1=${byte1.toString(16)}, byte2=0a, byte3=${byte3.toString(16)} (长度=${length})`); 98 | 99 | // 计算数据块的起始和结束位置 100 | const chunkStart = delimiterIndex + delimiterLength + 3; // 分隔符 + byte1 + byte2 + byte3 101 | const chunkEnd = chunkStart + length; 102 | 103 | if (buffer.length < chunkEnd) { 104 | // 数据块尚未完全接收,等待更多数据 105 | console.log('数据块尚未完全接收,等待更多数据...'); 106 | break; 107 | } 108 | 109 | // 提取数据块,不包含分隔符的3个字节 110 | const chunkData = buffer.slice(chunkStart, chunkEnd); 111 | if (chunkData.length > 0) { 112 | totalChunks += 1; 113 | console.log(`接收到的数据块 ${totalChunks}:`, chunkData.toString('utf-8')); // 根据需要调整编码 114 | fullbody += chunkData.toString('utf-8') 115 | // 例如,若数据为二进制,可以使用 chunkData.toString('hex') 或其他合适的编码 116 | } 117 | 118 | // 从缓冲区中移除已处理的数据和分隔符及相关字节 119 | buffer = buffer.slice(chunkEnd); 120 | } 121 | 122 | // 防止缓冲区无限增长,可以根据需要调整 123 | if (buffer.length > 10 * 1024 * 1024) { // 例如,10 MB 124 | console.warn('缓冲区过大,清空缓冲区以防内存泄漏'); 125 | buffer = Buffer.alloc(0); 126 | } 127 | } 128 | 129 | // 处理剩余缓冲区 130 | if (buffer.length > 0) { 131 | console.log('最后一块数据 (可能不完整):', buffer.toString('hex')); 132 | } 133 | 134 | console.log(`总共接收到 ${totalChunks} 个数据块。`); 135 | console.log(fullbody) 136 | } catch (error) { 137 | console.error('错误:', error); 138 | } 139 | } 140 | 141 | sendRequest(); 142 | --------------------------------------------------------------------------------