1. opencode 装 claude-mem 为什么一直报错
如果你正在用 opencode 搭配 claude-mem 做长期记忆,大概率会遇到一个很迷惑的现象:插件文件明明装好了,opencode 启动日志里也能看到加载记录,但你就是感觉它没生效——发消息、调工具,claude-mem 的 worker 一点反应都没有,数据库里sdk_sessions、user_prompts、observations全是 0。
我试过最典型的排查路径:先怀疑 worker 没起来,去查http://127.0.0.1:37701端口,发现服务是活的;再怀疑 viewer 展示有问题,结果直接查库,表里就是空的。到这一步基本能确定:不是展示层的问题,是插件根本没把事件送出去。
根因通常出在插件 API 的版本错配上。claude-mem 官方安装器生成的claude-mem.js,用的是旧版 opencode 的 hook 结构,大致长这样:
{ hooks: { tool: { execute: { after: ... } } }, event: (eventName, payload) => { ... } }但当前 opencode(比如 1.14.48)要求的是顶层 hook 名,形如"chat.message"、"tool.execute.after",事件总线也改成了event: async ({ event }) => {}这种签名。旧插件被加载了,但监听函数永远命中不了,于是/api/sessions/init和/api/sessions/observations这两个请求压根没发出去。
这篇就围绕这个报错场景,把 opencode + claude-mem 的插件 API 接入、TaoToken 统一 Key 配置、以及验证动作完整走一遍。适合已经在用统一 Key/API 通道、想让 claude-mem 真正跑起来的开发者。
2. 前置准备:TaoToken 统一 Key 与 opencode 环境
在动插件代码之前,先把 Key 和通道理顺,否则后面验证请求时会分不清是插件问题还是鉴权问题。
TaoToken 在这里的角色是统一 API 通道:你拿一个 Key,就能在 opencode、claude-mem 以及其它工具里复用同一套模型调用入口,不用每个工具单独配一遍。官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个不带 UTM,直接填进配置即可)。
你需要先拿到 Key。进控制台创建:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,然后在 API Keys 页面生成:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。生成后先复制保存,后面 settings.json 和插件环境变量都要用。
环境侧确认三件事:opencode 版本(opencode --version,确认是不是 1.14.x 这类新 hook 结构)、Node 版本(node -v,建议 18+)、以及 claude-mem worker 是否在跑。worker 默认监听127.0.0.1:37701,你可以先用 curl 探一下:
curl -s http://127.0.0.1:37701/api/health如果这个都连不上,先解决 worker 启动问题,别急着改插件。插件只是"送信人",worker 不在,送信人再对也没用。
3. 可复制配置:settings.json 与插件骨架
opencode 的配置分两层:一层是模型/通道配置(settings.json 或 config.toml),一层是插件本身(claude-mem.js)。两层都要对,缺一不可。
先看模型通道配置。opencode 支持 JSON 配置,把 TaoToken 作为 provider 填进去,Key 用环境变量注入,避免硬编码:
{ "provider": { "taotoken": { "type": "openai-compatible", "baseURL": "https://taotoken.net/api", "apiKey": "{env:TAOTOKEN_API_KEY}", "models": { "claude-sonnet": { "name": "claude-sonnet" } } } }, "model": "taotoken/claude-sonnet" }如果你更习惯 TOML,等价写法是:
[provider.taotoken] type = "openai-compatible" baseURL = "https://taotoken.net/api" apiKey = "{env:TAOTOKEN_API_KEY}" [provider.taotoken.models.claude-sonnet] name = "claude-sonnet" model = "taotoken/claude-sonnet"然后在 shell 里导出 Key:
export TAOTOKEN_API_KEY="你的Key"接着是插件骨架。把旧的claude-mem.js替换成兼容新 hook 的实现,核心是三个顶层 hook:
// claude-mem.js —— 兼容 opencode 1.14.x 的插件实现 const MEM_BASE = process.env.CLAUDE_MEM_BASE || "http://127.0.0.1:37701"; async function post(path, body) { const res = await fetch(`${MEM_BASE}${path}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); return res.json(); } export default { // 捕获用户输入,初始化会话 "chat.message": async (input, output) => { await post("/api/sessions/init", { sessionId: input.sessionId, message: input.message, }); }, // 捕获工具调用输出,写入 observations "tool.execute.after": async (input, output) => { await post("/api/sessions/observations", { sessionId: input.sessionId, tool: input.tool, result: output.result, }); }, // 兼容当前事件总线 event: async ({ event }) => { if (event.type === "session.compact") { await post("/api/sessions/observations", { sessionId: event.sessionId, kind: "compact", }); } }, // 保留搜索工具 tools: { claude_mem_search: { description: "搜索 claude-mem 历史记忆", parameters: { query: { type: "string" } }, execute: async ({ query }) => { const res = await fetch( `${MEM_BASE}/api/search?q=${encodeURIComponent(query)}` ); return res.json(); }, }, }, };关键点:"chat.message"负责把用户输入送到/api/sessions/init,"tool.execute.after"负责把工具输出送到/api/sessions/observations,event用新签名兜住会话压缩等事件。旧版那种hooks.tool.execute.after嵌套结构在新版里不会被触发,这是报错的直接来源。
4. 验证请求:确认插件真的发出去了
改完代码别急着开 TUI,先做静态和动态两层验证。
静态检查语法:
node --check claude-mem.js没输出就是通过。然后触发插件加载日志:
opencode mcp list这一步能看到插件被加载的记录。如果这里就报错,说明文件路径或导出格式有问题,先解决再往下。
动态验证是重点。你可以 mock 一次 hook 调用,确认它真的会请求那两个端点。写个临时脚本:
// verify-hook.mjs import plugin from "./claude-mem.js"; await plugin["chat.message"]( { sessionId: "test-1", message: "hello" }, {} ); await plugin["tool.execute.after"]( { sessionId: "test-1", tool: "read", result: "ok" }, { result: "ok" } ); console.log("hook 调用完成");跑之前先开一个终端监听 worker 日志,或者用 tcpdump 之类看请求。正常的话你会看到:
POST http://127.0.0.1:37701/api/sessions/init POST http://127.0.0.1:37701/api/sessions/observations两个请求都出现,说明插件 API 接入成功。这时候再去查数据库,sdk_sessions和user_prompts应该开始有记录了。
最后一步很关键:重启 opencode TUI。新插件只有在重启后才会被当前会话加载,热更新不生效。重启后随便发一条消息,再查库确认数据在涨。
5. 本篇常见错排查
报错一:插件加载了但数据库还是 0。九成是 hook 名不对。检查你的claude-mem.js是不是还在用hooks.tool.execute.after这种嵌套写法,改成顶层"tool.execute.after"。
报错二:fetch is not defined。Node 版本太低,18 以下没有全局 fetch。升级 Node,或者引入node-fetch并改 import。
报错三:请求 401/403。这是 TaoToken Key 没注入成功。确认TAOTOKEN_API_KEY在当前 shell 里echo得出来,且 settings.json 里写的是{env:TAOTOKEN_API_KEY}而不是明文占位。
报错四:连不上 37701。worker 没起来,或者端口被占。先curl http://127.0.0.1:37701/api/health,不通就重启 worker,别改插件。
报错五:改了代码没效果。忘了重启 TUI。opencode 不会热加载插件,必须退出重进。
报错六:event回调参数对不上。新版签名是event: async ({ event }) => {},解构出来的是对象,不是(eventName, payload)。旧写法拿不到数据。
排查顺序建议固定成:worker 健康 → Key 注入 → 语法检查 → hook 名 → 重启 TUI。按这个顺序走,基本不会绕弯。
6. 接入文档与后续动作
插件跑通之后,如果你还想把模型调用也统一到同一条通道上,可以对照接入文档确认参数细节:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。里面覆盖了 baseURL、鉴权头和模型名的对应关系,配 settings.json 时对着填就行。
想先在网页里验证模型通不通,用模型对话页面发一条测试消息最快:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。如果那边能正常回,说明 Key 和通道没问题,剩下的就纯粹是 opencode 插件层的事。
如果你打算长期用 opencode 做编码和 Agent 任务,Key 会频繁调用,可以考虑 Coding Plan 把额度固定下来:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。这样插件、对话、编码共用一套 Key,排查问题时变量更少。
最后提醒一句:claude-mem 的 worker 和 opencode 插件是两个独立进程,出问题先分清是哪一层。worker 挂了改插件没用,插件 hook 错了重启 worker 也没用。把这两层分开看,报错定位会快很多。