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 流式代理,它需要同时满足三个硬性条件:
对 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 转换工具链。与现有 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 里实现成本高得多。
开发者工具链的成熟度:所有热词里反复出现的 “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-provider | string | ✅ | 路由分发的唯一依据。值必须是openclaw、claude、local-llm之一,大小写敏感。Paperclip 启动时会读取.papercliprc里的providers配置,匹配失败直接 400。 | 写成openclaw-v1或CLAUDE导致 404 |
x-streaming | boolean | ✅ | 告诉 Paperclip 是否启用流式转换。设为true时,Paperclip 会把后端的 raw stream 转为 SSE;设为false则直通 JSON 响应。注意:OpenClaw 的/chat/completions默认 streaming,而/health是非 streaming。 | 在 health check 请求里设为 true,导致响应超时 |
x-request-id | string | ✅ | 全链路追踪 ID。Paperclip 会在日志里记录REQ_ID=xxx,并在转发请求时透传给后端。OpenClaw 日志里出现REQ_ID=abc123,你就能在 Paperclip log 里找到同一 ID 的 entry,快速定位瓶颈。 | 用 Math.random() 生成,导致无法关联日志 |
x-model | string | ❌ | 指定后端模型名。OpenClaw 里对应--model参数,Claude Code 里对应claude-3-haiku-20240307。Paperclip 不校验该值,只做透传。 | 把它当成 Paperclip 自己的模型选择器,实际无效 |
x-timeout | number | ❌ | 后端请求超时时间(毫秒)。Paperclip 默认 30000,但 OpenClaw 处理大 PDF 时可能需设为 120000。注意:这是 Paperclip 到后端的 timeout,不是客户端到 Paperclip 的 timeout。 | 设为 5000 导致 OpenClaw 大文件解析中断 |
x-encoding | string | ❌ | 响应编码格式。目前只支持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。
所以,正确做法是:
- 卸载所有旧版 Node.js:Windows 用户用
Appwiz.cpl彻底删除,macOS 用brew uninstall node,Linux 用sudo apt remove nodejs npm; - 下载精准版本:去 https://nodejs.org/dist/ 手动下载
node-v18.20.4-linux-x64.tar.xz(Ubuntu)或node-v18.20.4-darwin-arm64.tar.gz(M1 Mac); - 解压并软链接:
# 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 - 验证:
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,其实做了三件不声张的事:
自动创建 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 文件。预置 SQLite 数据库初始化:脚本会运行
openclaw init-db,创建/var/lib/openclaw/db.sqlite,并建表documents,chunks,embeddings。如果你手动删了这个文件,OpenClaw 启动会报SQLITE_ERROR: no such table: documents,必须重新运行openclaw init-db。配置 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,必须修改两个地方:
VS Code 设置:打开
settings.json,添加:"claude.code.apiBase": "http://localhost:3001", "claude.code.customHeaders": { "x-ai-provider": "claude", "x-streaming": "true" }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,其实是它的安全设计——防止后端错误信息泄露(比如文件路径、数据库密码)。
要看到真实错误,必须:
- 在 Paperclip 启动时加环境变量:
PAPERCLIP_DEBUG=true npm start - 查看 Paperclip 日志,里面会有
UPSTREAM_ERROR: ...前缀的行; - 或者临时修改
.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 30004.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