xiaowei-system/skills/software-development/obsidian-plugin/references/plugin-api-contract-2026091...

3.7 KiB
Raw Blame History

zhiyi-memory 插件 ↔ zhiyid 后端 API 契约2026-09-10 实测)

场景Obsidian 织忆插件(~/.obsidian/plugins/zhiyi-memory/v1.1.0,只有编译产物 main.js 无源码) 问题:搜索 404、图谱结果异常庞大。逐个端点探测后定位 2 处失配。

1. 插件 HTTP 封装main.js 里的 z()

async function z(a, n={}) {
  let e = n.method ?? "GET";
  let t = {"X-API-Key": ee(), "Content-Type": "application/json"};
  let r = {method: e, headers: t};
  n.body !== void 0 && (r.body = JSON.stringify(n.body));
  let o = await fetch(`${J()}${a}`, r);      // J() = 硬编码 base实测 http://localhost:7821
  if (!o.ok) throw new Error(`API ${a} failed: ${o.status}`);
  let s = await o.text();
  return s ? JSON.parse(s) : {};
}

封装本身没有问题body 序列化、错误抛出都正确)。问题在调用点传的路径与参数名

2. 失配对照表

插件调用 服务端实际 结果
POST /api/v1/search/recall {query,namespace,top_k} POST /api/v1/recall 404 endpoint not found
GET /api/v1/graph/navigate?entity=X&depth=1 POST /api/v1/graph/navigate {entity, max_hops} ⚠️ 200depth 被忽略 → 返回全图
GET /api/v1/memories?...
GET /api/v1/stats
GET /api/v1/graph/pagerank
GET /api/v1/graph/export?namespace=&limit=
GET /api/v1/distill/status / quota

修复后

// 修 1
z("/api/v1/recall", {method:"POST", body:{query:a, namespace:n||v(), top_k:20}})
// 修 2
z("/api/v1/graph/navigate", {method:"POST", body:{entity:a, max_hops:1}})

3. 参数名忽略的验证方法(本次抓到 bug 的关键)

对同一端点传不同参数值,比较返回是否变化

POST /api/v1/graph/navigate  {"entity":"小唯"}                 → count=3097   (默认全图)
POST /api/v1/graph/navigate  {"entity":"小唯","depth":1}      → count=3097   ⚠️ 无变化 = depth 被忽略
POST /api/v1/graph/navigate  {"entity":"小唯","max_hops":1}   → count=277    ✅ 生效
POST /api/v1/graph/navigate  {"entity":"小唯","hops":1}       → count=3097   ⚠️ 无变化

通则:只测「成功/失败」抓不到这类 bug必须测「参数生效性」。

4. 改 minified main.js 的安全流程

import shutil, subprocess, os
p = '/home/muc/mc/.obsidian/plugins/zhiyi-memory/main.js'
c = orig = open(p, encoding='utf-8').read()

n1 = orig.count('\"/api/v1/search/recall\"')
c = c.replace('\"/api/v1/search/recall\"', '\"/api/v1/recall\"')

old_nav = 'z(`/api/v1/graph/navigate?entity=${encodeURIComponent(a)}&depth=1`)'
new_nav = 'z("/api/v1/graph/navigate",{method:"POST",body:{entity:a,max_hops:1}})'
n2 = orig.count(old_nav)
c = c.replace(old_nav, new_nav)

assert n1 or n2, '两处都没匹配 → 检查调用点原文(模板字符串引号是反引号)'
shutil.copy2(p, p + '.bak-api-fix')
open(p, 'w', encoding='utf-8').write(c)

r = subprocess.run(['node', '--check', p], capture_output=True, text=True)
print('OK' if r.returncode == 0 else r.stderr[:200])

⚠️ 常见坑navigate 调用用的是模板字符串(反引号),里面嵌 ${encodeURIComponent(a)} —— 复制调用点原文时引号类型必须一致,否则 replace 静默不匹配。

5. 验证清单

□ grep 出插件所有 api/v1 路径,逐个 curl 探活
□ 每个端点传「不同参数值」验证参数生效性
□ 改完 node --check
□ 备份 .bak-<date>.obsidian/ 不进 git
□ 同步到异地副本WebDAV / NAS
□ 提醒用户重启 Obsidian 或重载插件才生效