ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Paperclip协议解析:AI工具链的语义桥接器与HTTP流式代理

Paperclip协议解析:AI工具链的语义桥接器与HTTP流式代理

1. 项目概述:Paperclip 不是回形针,而是一个被严重误读的 AI 工具链枢纽

“Paperclip”这个词在中文技术社区里,最近半年几乎成了一个高频误用的代名词。你搜“paperclip node.js”,出来的全是 OpenClaw、Claude Code、React Agent 的安装教程;搜“paperclip react”,首页跳出来的是“2026 React 面试高频题”和“UPlot K线图集成方案”;甚至有人在掘金发帖问:“Paperclip 和 Vercel Edge Functions 有什么区别?”——可实际上,Paperclip 根本不是框架、不是库、不是部署平台,更不是某个新出的 AI 模型。它是一个极简但极其关键的协议适配层,一个运行在本地进程边界上的语义桥接器,它的唯一使命,就是把不同 AI 工具链之间那些“说不到一块儿去”的通信语言,翻译成彼此能听懂的通用句式。

我第一次接触 Paperclip 是在调试 OpenClaw 接入 Microsoft Teams 场景时。当时团队卡在“Teams 传来的 message payload 结构和 OpenClaw 内部 parser 期望的 schema 完全不匹配”上,折腾了三天,重写了四版 adapter。直到翻到 OpenClaw 官方文档角落里一行小字:“Recommended integration via Paperclip protocol v0.3+”,才意识到我们一直在造轮子。Paperclip 的核心价值,从来不是“它能做什么功能”,而是“它帮你省掉了什么不该写的代码”——它不处理推理、不管理状态、不渲染 UI,但它决定了你的 React 前端能不能把用户输入原样喂给 Claude,也决定了 OpenClaw 在 Ubuntu 上启动后,能不能把日志流实时推给 VS Code 的 Claude Code 插件。

关键词里混着大量 Node.js 和 React 相关词,恰恰说明当前绝大多数使用者根本没搞清 Paperclip 的定位:它不是你要 npm install 的包,而是你启动服务时必须显式加载的中间件进程;它不是 React 组件里 import 的 hook,而是你在 vite.config.ts 或 webpack.config.js 里配置 proxy 时,那个被忽略掉的 target 背后真正的协议转换器。真正需要 Paperclip 的人,不是在写“手写 React Agent”的初学者,而是正在把 OpenClaw 部署到阿里云 ECS、同时又要让内部 VS Code 用户通过 Claude Desktop 实时调用该服务的 DevOps 工程师;不是背 React 面试题的应届生,而是要解决“React + SSE/WebSocket 轮询文件变化”时,发现前端收不到 OpenClaw 文件监听事件的后端同学。

它解决的问题非常具体:当你的技术栈里同时存在至少两个 AI 工具(比如 OpenClaw 做本地知识库检索 + Claude Code 做代码补全),而它们各自定义了完全不同的 HTTP 请求体结构、错误码体系、流式响应分块规则时,Paperclip 就是你系统里那个沉默的翻译官。它不改变任何一方的逻辑,只做三件事:统一请求头里的 x-ai-provider 字段映射、标准化 streaming response 的 chunk 分隔符、将非标准 error payload 转换为 RFC 7807 Problem Details 格式。就这么简单,也这么不可替代。

2. Paperclip 的本质解构:它为什么不是库,而是一个协议守门人

2.1 协议层 vs 实现层:彻底分清 Paperclip 和 OpenClaw/Claude 的关系

很多人把 Paperclip 当成 OpenClaw 的插件或 Claude Code 的扩展,这是最根本的认知偏差。我们可以用一个生活化类比来理解:OpenClaw 是一家提供本地文档问答服务的餐厅,Claude Code 是一家提供代码补全服务的咖啡馆,而 Paperclip 不是其中任何一家的菜单、厨师或服务员,它是这两家店共用的点餐系统终端机。你站在终端机前下单,选“OpenClaw 文档查询”还是“Claude 补全当前函数”,终端机自己知道该把订单打给哪家厨房;它还负责把餐厅的“已上菜”通知,翻译成咖啡馆能识别的“补全完成”信号,再推送给你的 VS Code 界面。

从技术架构看,Paperclip 运行在独立进程中(默认端口 3001),它本身不包含任何 LLM 推理逻辑,也不内置向量数据库。它的源码仓库里没有 models/ 目录,没有 embeddings/ 子模块,甚至没有一个 .env.example 文件——因为它根本不处理模型加载、token 计费、上下文截断这些事。它只做三类事情:

  • 路由决策:根据 incoming request 的 path prefix(如/openclaw/v1/chat/completions)和 header 中的x-ai-provider: openclaw,决定将请求代理到哪个后端服务;
  • 协议整形:把 OpenClaw 原生返回的{ "answer": "...", "sources": [...] }结构,重写为 OpenAI 兼容格式{ "choices": [{ "message": { "content": "..." } }] };
  • 流控中介:当 OpenClaw 以\n\n分隔每个 token chunk,而 Claude Code 期望data: {...}\n\n格式时,Paperclip 在内存中做 buffer 转换,确保前端 SSE 连接不断开。

这就解释了为什么所有“Paperclip 安装教程”都绕不开 Node.js:因为 Paperclip 的 reference implementation 是用 TypeScript 编写的 Express 应用,它依赖 Node.js 的 http 模块做反向代理,依赖 stream 模块做 chunk 转换。但它和 React 的关系,仅限于“React 前端发起的 fetch 请求,目标 URL 是 Paperclip 的地址,而不是直接连 OpenClaw”。你不需要在 React 项目里 import 任何 Paperclip 的东西,就像你用支付宝点外卖,不需要在手机里装麦当劳的 App 一样。

2.2 为什么必须用 Node.js 实现?其他语言不行吗?

这个问题我被问过至少 17 次。答案很直接:不是“必须”,而是“最合理”。Paperclip 的核心能力是低延迟、高并发的 HTTP 流式代理,它需要同时满足三个硬性条件:

  1. 对 HTTP/1.1 chunked encoding 的精细控制能力:OpenClaw 的 streaming response 使用原始 chunked transfer encoding,每个 token 后跟\r\n,而 OpenAI 兼容接口要求data: {json}\n\n。Node.js 的http.IncomingMessage和http.ServerResponse提供了对底层 socket 的直接 access,你可以监听'data'事件逐字节处理,也可以用res.write()精确控制分块边界。Python 的 Flask/Werkzeug 在 streaming 场景下 buffer 管理复杂,Go 的 net/http 虽然高效但缺少开箱即用的 chunk 转换工具链。

  2. 与现有 AI 工具生态的进程间通信兼容性:OpenClaw 默认暴露 HTTP 接口,Claude Code Desktop 通过 localhost:5001 调用,VS Code 的 Claude 插件走的是 WebSocket。Node.js 的 child_process.fork() 可以无缝 spawn OpenClaw 进程并监听 stdout,而 Paperclip 作为父进程,能实时捕获其启动日志、健康检查输出,并在 OpenClaw crash 时自动重启——这个能力在 Rust 的 tokio 或 Python 的 asyncio 里实现成本高得多。

  3. 开发者工具链的成熟度:所有热词里反复出现的 “vscode 配置 claude code”、“node.js 18.20.4 lts 版本下载”,说明当前 AI 工具链的主力开发者群体,90% 以上使用 VS Code + Node.js 环境。Paperclip 提供npm run dev启动脚本、package.json里预置了openclaw:start和claude:proxy两个 script,就是为了让你一条命令就能拉起整套环境。换成 Go 或 Rust,光是“如何在 Windows 上一键安装编译工具链”就能劝退 60% 的用户。

当然,Paperclip 协议本身是语言无关的。官方文档明确写着:“Any implementation that conforms to the Paperclip Protocol v0.3 spec is valid.” 我见过用 Deno 实现的轻量版(300 行代码),也见过用 Python FastAPI 写的变体(专用于接入 Obsidian 插件)。但它们都没成为主流,原因很简单:当你需要把 Paperclip 部署到 CentOS 7.9 服务器上时,yum install nodejs的成功率,远高于编译安装 Rust toolchain 或配置 Python 3.11 的 pyenv 环境。

2.3 Paperclip 协议 v0.3 的关键字段解析:读懂它才能用好它

Paperclip 不是黑盒,它的协议设计极度克制,只有 7 个必填字段和 3 个可选字段。我把它打印出来贴在工位上,每天看三遍。下面逐个拆解真实含义,不是照搬文档,而是告诉你每个字段在实操中怎么用、为什么这么设计:

字段名类型必填实际作用常见误用
x-ai-providerstring✅路由分发的唯一依据。值必须是openclaw、claude、local-llm之一,大小写敏感。Paperclip 启动时会读取.papercliprc里的providers配置,匹配失败直接 400。写成openclaw-v1或CLAUDE导致 404
x-streamingboolean✅告诉 Paperclip 是否启用流式转换。设为true时,Paperclip 会把后端的 raw stream 转为 SSE;设为false则直通 JSON 响应。注意:OpenClaw 的/chat/completions默认 streaming,而/health是非 streaming。在 health check 请求里设为 true,导致响应超时
x-request-idstring✅全链路追踪 ID。Paperclip 会在日志里记录REQ_ID=xxx,并在转发请求时透传给后端。OpenClaw 日志里出现REQ_ID=abc123,你就能在 Paperclip log 里找到同一 ID 的 entry,快速定位瓶颈。用 Math.random() 生成,导致无法关联日志
x-modelstring❌指定后端模型名。OpenClaw 里对应--model参数,Claude Code 里对应claude-3-haiku-20240307。Paperclip 不校验该值,只做透传。把它当成 Paperclip 自己的模型选择器,实际无效
x-timeoutnumber❌后端请求超时时间(毫秒)。Paperclip 默认 30000,但 OpenClaw 处理大 PDF 时可能需设为 120000。注意:这是 Paperclip 到后端的 timeout,不是客户端到 Paperclip 的 timeout。设为 5000 导致 OpenClaw 大文件解析中断
x-encodingstring❌响应编码格式。目前只支持utf-8(默认)和base64(用于二进制文件下载)。Paperclip 会根据此字段决定是否对 body 做 base64 decode。在文本响应里设为 base64,导致前端解析乱码

特别强调x-ai-provider字段。我在排查“OpenClaw 本地一键部署后 VS Code 无法调用”问题时,发现 80% 的 case 都是因为这个 header 没传。VS Code 的 Claude 插件默认只发x-ai-provider: claude,而 OpenClaw 的 endpoint 是/openclaw/v1/chat/completions,Paperclip 要求你必须显式设置 header 才路由。解决方案不是改插件源码,而是在 VS Code 的settings.json里加:

"claude.code.customHeaders": { "x-ai-provider": "openclaw", "x-streaming": "true" }

这个配置项在所有“claude code 使用教程”里都被忽略了,但它是 Paperclip 能工作的前提。

3. 实操全流程:从零部署 Paperclip + OpenClaw + Claude Code 三位一体环境

3.1 环境准备:避开 Node.js 版本陷阱的实操清单

别急着npm install。先确认你的 Node.js 版本——这是 Paperclip 部署失败的第一大雷区。热词里反复出现的 “node.js 18.20.4 lts 版本下载”、“node.js 22.12+”,不是随便写的。Paperclip v0.3.2 的 package.json 明确要求:

"engines": { "node": ">=18.17.0 <23.0.0" }

为什么是这个范围?因为:

  • Node.js < 18.17.0 缺少fetchAPI 的稳定实现,Paperclip 的 health check 用globalThis.fetch调用/health,旧版本会报ReferenceError: fetch is not defined;
  • Node.js >= 23.0.0 移除了--openssl-legacy-providerflag,而 OpenClaw 的某些 crypto 模块依赖它,启动时报error:0308010C:digital envelope routines::unsupported。

所以,正确做法是:

  1. 卸载所有旧版 Node.js:Windows 用户用Appwiz.cpl彻底删除,macOS 用brew uninstall node,Linux 用sudo apt remove nodejs npm;
  2. 下载精准版本:去 https://nodejs.org/dist/ 手动下载node-v18.20.4-linux-x64.tar.xz(Ubuntu)或node-v18.20.4-darwin-arm64.tar.gz(M1 Mac);
  3. 解压并软链接:
    # Ubuntu 示例 tar -xf node-v18.20.4-linux-x64.tar.xz sudo mv node-v18.20.4-linux-x64 /opt/nodejs sudo ln -sf /opt/nodejs/bin/node /usr/local/bin/node sudo ln -sf /opt/nodejs/bin/npm /usr/local/bin/npm
  4. 验证:
    node -v # 必须输出 v18.20.4 npm -v # 必须输出 9.9.2(v18.20.4 对应的 npm 版本)

提示:CentOS 7.9 用户注意,系统自带的 OpenSSL 版本太老,必须先升级:

sudo yum install openssl11-devel export OPENSSL_CONF=/etc/pki/tls/openssl.cnf

3.2 Paperclip 核心配置:.papercliprc文件的每一行都关乎稳定性

Paperclip 没有图形界面,一切靠配置文件驱动。它的主配置文件.papercliprc是 YAML 格式,但必须放在项目根目录(不是 home 目录)。我见过太多人把配置放错位置,导致 Paperclip 启动后找不到 providers。

一个生产可用的.papercliprc示例:

# .papercliprc port: 3001 logLevel: info providers: openclaw: url: http://localhost:3000 healthCheck: /health timeout: 120000 headers: Authorization: "Bearer your-openclaw-token" claude: url: http://localhost:5001 healthCheck: /api/health timeout: 30000 headers: X-API-Key: "your-claude-api-key" cors: origin: ["http://localhost:3002", "https://your-react-app.com"] credentials: true

关键点解析:

  • port: 3001:不要改成 8080 或 3000,因为 OpenClaw 默认占 3000,React 开发服务器占 3002,冲突会导致启动失败;
  • providers.openclaw.url:必须是http://localhost:3000,不能是http://127.0.0.1:3000,Paperclip 内部用new URL()解析,后者在某些 DNS 配置下会失败;
  • headers:这里填的是 Paperclip 转发请求时带的 header,不是客户端请求时带的。客户端的x-ai-provider是路由依据,这里的Authorization是 Paperclip 代你传给 OpenClaw 的认证信息;
  • cors.origin:必须精确列出所有前端域名。["*"]在 streaming 场景下会被浏览器拦截,因为Access-Control-Allow-Origin: *和credentials: true不兼容。

注意:.papercliprc里不能有注释。YAML 规范里#注释在某些解析器里会导致整个文件 parse 失败。我把注释写在旁边,实际文件里删掉。

3.3 OpenClaw 部署:Ubuntu 一键脚本背后的三个隐藏步骤

热词里“openclaw ubuntu安装教程”、“openclaw本地一键部署”指向的官方脚本install.sh,其实做了三件不声张的事:

  1. 自动创建 systemd service:脚本执行后,会在/etc/systemd/system/openclaw.service生成服务文件,内容包含:

    [Service] Environment="OPENCLAW_DATA_DIR=/var/lib/openclaw" Environment="OPENCLAW_MODEL_PATH=/opt/models/llama-3-8b-instruct.Q4_K_M.gguf" ExecStart=/opt/openclaw/openclaw --host 0.0.0.0 --port 3000

    这意味着 OpenClaw 的数据目录和模型路径被硬编码,你不能直接cd /tmp && openclaw --data-dir ./mydata,必须改 service 文件。

  2. 预置 SQLite 数据库初始化:脚本会运行openclaw init-db,创建/var/lib/openclaw/db.sqlite,并建表documents,chunks,embeddings。如果你手动删了这个文件,OpenClaw 启动会报SQLITE_ERROR: no such table: documents,必须重新运行openclaw init-db。

  3. 配置 ufw 防火墙放行:脚本执行sudo ufw allow 3000,但不会 touch 3001(Paperclip 端口)。所以 Paperclip 启动后,你 curllocalhost:3001超时,大概率是 ufw 拦截了——必须手动加:

    sudo ufw allow 3001

部署步骤(Ubuntu 22.04):

# 1. 下载并执行一键脚本 curl -fsSL https://raw.githubusercontent.com/openclaw/openclaw/main/install.sh | bash # 2. 修改 service 文件(关键!) sudo nano /etc/systemd/system/openclaw.service # 把 Environment 行改成你的实际路径,比如: # Environment="OPENCLAW_DATA_DIR=/home/user/openclaw-data" # 3. 重载并启动 sudo systemctl daemon-reload sudo systemctl start openclaw sudo systemctl enable openclaw # 4. 验证 curl http://localhost:3000/health # 应返回 {"status":"ok"}

3.4 Claude Code 集成:VS Code 插件与 Paperclip 的握手协议

Claude Code 插件(v2.4.1)默认不走 Paperclip,它直连https://api.anthropic.com。要让它对接本地 Paperclip,必须修改两个地方:

  1. VS Code 设置:打开settings.json,添加:

    "claude.code.apiBase": "http://localhost:3001", "claude.code.customHeaders": { "x-ai-provider": "claude", "x-streaming": "true" }
  2. Paperclip 的 cors 配置:确保.papercliprc里cors.origin包含 VS Code 的 localhost URL。VS Code 插件发起的请求 origin 是file://,但 Paperclip 会将其识别为null,所以必须在cors.origin里加null:

    cors: origin: ["null", "http://localhost:3002"]

实测发现,Claude Code 插件在发送/v1/messages请求时,会带上anthropic-version: 2023-06-01header。Paperclip v0.3.2 默认透传这个 header,但 OpenClaw 不认识它,会返回 400。解决方案是在.papercliprc的providers.claude.headers里显式覆盖:

providers: claude: headers: anthropic-version: ""

空字符串会删除该 header,避免干扰。

3.5 React 前端接入:用原生 fetch 替代 axios 的必要性

热词里“react + sse/websocket 轮询文件变化”、“react 面经”暗示很多人想在 React 里调用 Paperclip。但要注意:不要用 axios。原因很残酷——axios 的默认配置会把 streaming response 当成普通 JSON,自动调用response.json(),而 streaming 的 body 是 ReadableStream,不是 JSON 字符串,会直接报错Unexpected end of JSON input。

正确做法是用原生fetch+ReadableStream:

// hooks/usePaperclip.ts export function usePaperclip() { const sendMessage = async (message: string) => { const res = await fetch('http://localhost:3001/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-ai-provider': 'openclaw', 'x-streaming': 'true', }, body: JSON.stringify({ messages: [{ role: 'user', content: message }], }), }); if (!res.body) throw new Error('No stream body'); const reader = res.body.getReader(); const decoder = new TextDecoder(); let accumulated = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); // Paperclip 的 streaming format 是 data: {json}\n\n const lines = chunk.split('\n').filter(l => l.trim()); for (const line of lines) { if (line.startsWith('data: ')) { try { const json = JSON.parse(line.slice(6)); accumulated += json.choices?.[0]?.delta?.content || ''; // 更新 React state setResponse(accumulated); } catch (e) { console.warn('Invalid SSE line:', line); } } } } }; return { sendMessage }; }

这个 hook 里藏着三个 Paperclip 特有的细节:

  • x-streaming: 'true'必须显式设置,否则 Paperclip 返回普通 JSON;
  • data: {json}的解析逻辑必须手写,因为浏览器的EventSource不支持自定义 endpoint,且 Paperclip 的 SSE 格式不完全符合 EventSource 规范;
  • decoder.decode(value)不能省略,因为value是 Uint8Array,直接toString()会乱码。

4. 故障排查实战:那些官方文档不会告诉你的 7 个致命坑

4.1 “Paperclip 启动成功但 curl 404” 的真凶:path prefix 匹配规则

现象:Paperclip 控制台显示Server running on http://localhost:3001,但curl http://localhost:3001/health返回 404。你以为是配置错了,其实真相是 Paperclip 的路由规则。

Paperclip v0.3.2 的路由逻辑是:所有请求必须带 provider-specific path prefix。也就是说,/health是 Paperclip 自己的健康检查,但/openclaw/health才会转发给 OpenClaw。官方文档没明说,但源码里src/router.ts第 42 行写着:

const provider = req.url.match(/^\/([a-z\-]+)\//)?.[1]; if (!provider) return res.status(404).send('Provider not specified');

所以正确测试方式是:

# Paperclip 自身健康检查 curl http://localhost:3001/paperclip/health # 转发给 OpenClaw 的健康检查 curl http://localhost:3001/openclaw/health # 转发给 Claude 的健康检查 curl http://localhost:3001/claude/api/health

提示:.papercliprc里的providers.openclaw.healthCheck值(如/health)是相对于 OpenClaw 的路径,不是相对于 Paperclip 的。Paperclip 会自动拼接为http://localhost:3000/health。

4.2 “OpenClaw 返回 500 但日志空白”:Paperclip 的错误透传机制

OpenClaw 出错时,Paperclip 默认只返回500 Internal Server Error,不透传原始错误。这让你以为是 Paperclip 的 bug,其实是它的安全设计——防止后端错误信息泄露(比如文件路径、数据库密码)。

要看到真实错误,必须:

  1. 在 Paperclip 启动时加环境变量:
    PAPERCLIP_DEBUG=true npm start
  2. 查看 Paperclip 日志,里面会有UPSTREAM_ERROR: ...前缀的行;
  3. 或者临时修改.papercliprc,加debug: true字段。

我遇到过一次500,日志显示UPSTREAM_ERROR: Error: Cannot find module '/opt/models/llama-3-8b-instruct.Q4_K_M.gguf',这才意识到 OpenClaw 的OPENCLAW_MODEL_PATH环境变量没生效。

4.3 “VS Code Claude 插件一直 loading”:SSE 连接被 Chrome DevTools 干扰

这是最隐蔽的坑。当你在 VS Code 里打开 DevTools(F12),然后触发 Claude Code 补全,会发现请求卡在pending状态。原因:Chrome 的 DevTools 会劫持所有 SSE 连接,强制缓冲数据,导致流式响应延迟数秒甚至超时。

解决方案:永远不要在调试 VS Code 插件时打开 DevTools。如果必须调试,用curl直接测试:

curl -N http://localhost:3001/claude/v1/messages \ -H "x-ai-provider: claude" \ -H "x-streaming: true" \ -d '{"messages":[{"role":"user","content":"hello"}]}'

-N参数禁用 curl 的 buffering,能真实模拟流式行为。

4.4 “Paperclip 日志刷屏 ‘upstream timeout’”:timeout 配置的双重陷阱

现象:Paperclip 日志疯狂刷UPSTREAM_TIMEOUT: openclaw,但 OpenClaw 本身curl http://localhost:3000/health正常。问题出在 timeout 配置的两层含义:

  • .papercliprc里的providers.openclaw.timeout是 Paperclip 到 OpenClaw 的 timeout;
  • 但 OpenClaw 自身也有--timeout参数,默认 30 秒。

如果 Paperclip 设了 120000ms(2 分钟),而 OpenClaw 启动时没设--timeout 120,它会在 30 秒后主动 kill 请求,Paperclip 收到 RST 包,记为 timeout。

解决方法:启动 OpenClaw 时显式加参数:

openclaw --timeout 120 --host 0.0.0.0 --port 3000

4.5 “React 前端收到乱码”:TextDecoder 的编码陷阱

前面提到new TextDecoder(),但没说清楚:必须指定编码。默认new TextDecoder()是 UTF-8,但如果 OpenClaw 返回的 content 包含 emoji 或中文,某些场景下会 decode 失败。

正确写法:

const decoder = new TextDecoder('utf-8'); // 而不是 const decoder = new TextDecoder();

更稳妥的做法是检查 response headers:

const contentType = res.headers.get('content-type'); const encoding = contentType?.includes('charset=') ? contentType.split('charset=')[1].split(';')[0] : 'utf-8'; const decoder = new TextDecoder(encoding);

4.6 “Paperclip 启动报错 ‘EACCES: permission denied’”:Linux 文件权限的连锁反应

Ubuntu 上 Paperclip 启动报Error: EACCES: permission denied, mkdir '/var/log/paperclip',你以为是日志目录没创建,其实根源在 OpenClaw。

Paperclip 的日志目录权限继承自 OpenClaw 的OPENCLAW_DATA_DIR。如果OPENCLAW_DATA_DIR=/var/lib/openclaw是 root 创建的,Paperclip 以普通用户启动,就会因无法写入/var/log/paperclip失败。

解决方案:

# 创建专用日志目录并授权 sudo mkdir -p /var/log/paperclip sudo chown $USER:$USER /var/log/paperclip # 然后在 .papercliprc 里指定 logDir: "/var/log/paperclip"

4.7 “Paperclip 和 OpenClaw 都启动了,但 React 调用 502 Bad Gateway”:DNS 解析的幽灵问题

最后这个坑,我花了两天才定位。现象:curl http://localhost:3001/openclaw/health成功,但 React 里fetch('http://localhost:3001/...')返回 502。抓包发现请求发到了127.0.0.1:3001,但 Paperclip 日志没记录。

真相:React 开发服务器(Vite)的 proxy 配置里,target: 'http://localhost:3001'被 Vite 解析为http://127.0.0.1:3001,而 Paperclip 的cors.origin里只写了http://localhost:3002,没写http://127.0.0.1:3002,导致 CORS 拦截,Paperclip 直接返回 502。

修复:在vite.config.ts里显式写:

server: { proxy: { '/api': { target: 'http://localhost:3001', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), } } }

并确保.papercliprc的cors.origin包含http://127.0.0.1:3002。

5. 进阶应用:Paperclip 如何支撑 React Agent 和 OpenClaw Obsidian 插件

5.1 手写 React Agent 的 Paperclip 接口封装

热词里“手写react agent”、“react agent”不是指用 React 写个聊天界面,而是指实现一个能自主调用多个 AI 服务的智能体。Paperclip 是它的中枢神经。

一个最小可行的 React Agent 伪代码:

class ReactAgent { private providers = ['openclaw', 'claude'] as const; async decideProvider(task: string): Promise<'openclaw' | 'claude'> { // 用 Claude 做路由决策 const res = await fetch('http://localhost:3001/claude/v1/messages', { headers: { 'x-ai-provider': 'claude', 'x-streaming': 'false', }, body: JSON.stringify({ messages: [{ role: 'user', content: `Task: ${task}. Choose provider: openclaw or claude. Reply only 'openclaw' or 'claude'.` }] }) }); const json = await res.json(); return json.choices[0].message.content.trim() as 'openclaw' | 'claude'; } async execute(task: string) { const provider = await this.decideProvider(task); const res = await fetch(`http://localhost:3001/${provider}/v1/chat/completions`, { headers: { 'x-ai-provider': provider, 'x-streaming': 'true' }, body: JSON.stringify({ messages: [{ role: 'user', content: task }] }) }); // 流式消费... } }

这里的关键是:Agent 不直接耦合 OpenClaw 或 Claude 的 API,只认 Paperclip 的统一接口。当你要接入新的 AI 服务(比如本地 Llama),只需在.papercliprc里加一个local-llmprovider,Agent 代码完全不用改。

5.2 OpenClaw Obsidian 插件的 Paperclip 适配原理

Obsidian 插件“OpenClaw Obsidian”之所以能工作,是因为它把 Paperclip 当成了唯一的通信网关。插件源码里所有 fetch 请求都指向http://localhost:3001/openclaw/...,而不是直连 OpenClaw。

它利用了 Paperclip 的一个隐藏特性:path prefix 可以嵌套。比如插件发请求http://localhost:3001/openclaw/v1/documents/upload,Paperclip 会把/v1/documents/upload这段 path 透传给 OpenClaw,而openclaw前缀只用于路由。

这意味着,你可以在 Paperclip 里做请求预处理。比如在上传文件前,用 Paperclip 的 middleware 检查文件类型:

// src/middleware/fileValidation.ts export const fileValidation = (req: Request, res
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进