diff --git a/Docs/api-client.md b/Docs/api-client.md index 826c4d6..5e6ac8b 100644 --- a/Docs/api-client.md +++ b/Docs/api-client.md @@ -1,6 +1,6 @@ # Cyrene v2 客户端 API 契约 -> **契约版本:`v0.85`**(上一版 `v0.84`,其条目见 §6;`v0.83` / `v0.82` / `v0.81` / `v0.80` / `v0.79` / `v0.78` / `v0.77` / `v0.76` / `v0.75` / `v0.74` / `v0.73` / `v0.72` / `v0.71` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。 +> **契约版本:`v0.86`**(上一版 `v0.85`,其条目见 §6;`v0.84` / `v0.83` / `v0.82` / `v0.81` / `v0.80` / `v0.79` / `v0.78` / `v0.77` / `v0.76` / `v0.75` / `v0.74` / `v0.73` / `v0.72` / `v0.71` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。 > **读者**:Web 前端 / 移动端原生客户端(含 Android)/ IM 桥接等**跨平台客户端开发者**。 > **唯一真源 = 当前代码**(`internal/services/gateway/*` 等)。本文档由代码逐条核对得出;与代码不符时**以代码为准**,并回改本文档。 > **旧的超详细版已归档、只读**:`Docs/archive/api-client.v0.65.md`(412 KB,含 v0.28→v0.65 全部历史条目)与 `Docs/archive/api-reference.v0.65.md`(170 KB,后端全量字段级文档)。归档的原因是**结构性臃肿 + 历史条目并列 + 长期漂移**;本文档**不复制**那段历史,需要考古时去归档。 @@ -22,6 +22,8 @@ > **v0.77 是链路图按路分桶 + 记忆整理护栏/异步落地版(已实现;纯增量)**:① **`graph_update` 按路分桶**(切片 FV2)—— 帧 `data` 新增可选 `turnId`(该图的**路**的权威回合标识)与 `round`(会话轮次号);真并行(`parallel:true`)时同一会话**每一路各发自己的帧**(`data.id` / `data.turnId` 各不相同),两路不再互相顶掉;`GET /api/v1/admin/flow/graph` 响应新增 `lanes[]`(**最近一轮每一路**的图,恒为非 null 数组),`graph` 语义不变(最新一路 / 指定轮 / `null` 诚实空态)⇒ **老的读取方零改动**。**无破坏性变更**(两个新键可选;单路时 `turnId` 缺席 ⇒ 客户端行为与 v0.75 逐字相同)。详见 **§2.2 B13 / §3.4.5 / §4.11 / §7.4-19**。② **记忆整理护栏抬高 + 诚实截断 + 试算异步落地**(切片 MT-A)—— 每分区候选上限 `MEMORY_TIDY_CANDIDATE_LIMIT` 默认值 `20 → 200`、两两比对上限改为 env 键新键 `MEMORY_TIDY_COMPARE_LIMIT`(默认 `2000`,原先**写死** `400`);配置事实面 **12 → 13 条**;`truncated` 新增第 4 种标签 **`(budget)`**(墙钟时间预算截断);**v0.76 ③ 的加性异步已落地**(`?async=1` + B15 轮询可用;新错误码 **`tidy_run_not_found`**)。详见 **§2.2 B14/B15 / §4.10(a) / §4.12 / §4.12.1 / §5**。③ **「入库前审查」的 4 个 env 键登记**(切片 MT-B,纯增量)—— `MEMORY_REVIEW_ENABLED`(`true`)/ `MEMORY_REVIEW_QUEUE_SIZE`(`256`)/ `MEMORY_REVIEW_SCAN_LIMIT`(`200`)/ `MEMORY_REVIEW_TIMEOUT_SEC`(`20`);⚠️ 该切片的**面板读侧 / 回滚端点目前没有 HTTP 入口**(见 §7.2)。详见 **§5**。 +> **v0.86 是「搜索结果条数上限可配」版(已实现并推库 `bff57bd` + `c68387a`;**纯增量:面板多一个 number 字段 + 两个动作各多一个键;无破坏性变更**)**:`web-search` 的「结果条数上限」从**改不了**变成**面板上真能改**(此前三处断点:字段表里没这个字段 / `saveProvider` 只并四个键 / `mergeSelectionPatch` 只处理 `primary`+`fallback` ⇒ **手工塞进 `values` 也会被静默丢弃**,「我改了它却没变」成立)。① **面板声明**新增字段:`pluginId=web-search` / `panel=providers` / form `add` 多一个 **`maxResults`**(`type:"number"`, `min:1`, 标签「结果条数上限(默认 10)」)—— 前端本就渲染 `number` ⇒ **客户端零改动**。② **`plugin.saveProvider` 的 `values` 新增可写键 `maxResults`**:**三态** —— 缺位/null ⇒ 不改动;**正整数**(含整数字符串)⇒ 采用;`0` / 负数 / 小数 / 空串 / 非数 / 布尔 / 数组 ⇒ **`400 invalid_payload`**,且**盘上一字节不动**(**绝不静默忽略**)。③ 响应新增回显:`plugin.saveProvider` 的 `data.result.maxResults`(**0 = 文件里未声明**)、`plugin.reauthProvider` 的 `values.maxResults`(盘上有值才出现 ⇒ 用于「重配凭据」回填,否则一保存就把上限清掉)。④ **语义**:面板文件的 `maxResults` **压过** `.env` 的 `WEB_SEARCH_MAX_RESULTS`,是**运行期真上限**(模型的 `count` 只能更小),**保存即生效**(走既有热重载,响应里的 `hotReload`);代码默认值 **5 → 10**(三处口径已一致:env 默认 / io 包 `defaultMaxResults` / 面板文件)。⑤ **计数不变**(无新端点 ⇒ §2.0 维持 `14 + 25 + 40 = 79`)。⚠️ **已知取舍(如实)**:**清空输入框 = `400`**(后端按「非数 ⇒ 报错」拒)⇒ 面板上**没有「去掉上限」的手势**(要回到 `.env` 兜底得改文件);**小数/负数一律拒**;文件里没写该键时 `reauth` **不回填**(避免回填 0 让保存整笔进不去),因此那个框是空的、看不出当前生效值。 + > **v0.85 是「整理不再只产建议」版(已实现并推库 `6d92536`;**新增 1 条 writeAuth 端点 + 一处行为变更(整理默认自动落地)+ `link` 候选停产;无破坏性变更**)**:① **新增端点 `POST /api/v1/admin/memories/candidates/reject?action=&limit=`(B25)** —— 按**动作**批量把 `pending` 候选标成 `rejected`(**幂等、只改候选状态、绝不碰记忆行**)。`action` **必填**且是**闭集**(`merge|link|archive|reclassify|keep`,未知值 → `400 bad_request`);`limit` 缺省 **1000**、硬顶 **10000**、非整数 → `400`。`200 {data:{action,limit,total,rejected,truncated}}`(`total` 是命中筛选的**真**条数,`rejected` 只覆盖 `limit` 之内 ⇒ 不等时 `truncated:true`)。审计 `memory.tidy.reject`(只带计数)。语义见 **§4.15(e)**。② 🔴 **行为变更(默认生效):整理在轮末会「自动落地」两类高置信动作**,不再只产建议(主人 2026-10-07 口径「该删就删」)——**可自动的只有**:`merge`(**字面重复** ∨「事实集合等价 ∧ 相似度 ≥ 0.95」)、`archive`(理由 = `one_off_event` **且不像习惯**)。**不自动**:`reclassify`、低置信 merge、跨分区/跨角色、任何改正文、**受限来源(群聊/陌生人)**。六条护栏:软动作(正文不改、行不删)/ **每轮上限 50**(硬顶 500,超限如实 `truncated`)/ 审计 `memory.tidy.apply`(`auto=true`)/ **可 revert**(回滚路径与人工应用同一条)/ 幂等 / **一键关回只产建议**(`$DATA_DIR/config/memory-tidy.json` 的 `{"autoApply": false}`,**零新 env 键**)。⚠️ **面板侧的候选列表仍可能显示历史 `link` 行**,直到用 B25 清掉。③ **`link` 候选停产**:`link` 在 `design/23 §14` 里被列为**未细化**(既无落点也无数据模型),且 `applyable()` 只认 merge/archive/reclassify ⇒ **应用 link 恒 `skipped`,产出它只是往待办里灌噪音**。现在规则层不再产出(判 `keep`);`TidyLink` 常量保留**只为读历史行**。存量用 B25 清(主会话已执行)。④ **计数**:`writeAuth` 段 24 → **25**、管理路由总数 78 → **79**(`14 + 25 + 40`;`routes.go` 核:经 `writeAuth` 挂载的 admin 路由实有 **27**,按历史口径减 §2.4(a) 的 `channels/events` 与 `.../counters` ⇒ **25**)。⑤ ⚠️ **诚实边界**:**未部署**(要一次 `build` + `restart`);**自动档的真机行为未验**(不能重启后端);`reclassify` 仍全部人工;`TidyRuleVersion` **刻意未升版**(本次不改变任何判定结论,升版会让既有候选按幂等键复制出重复行)。 > **v0.84 是「显示名 + 段落标签硬约束」版(已实现并推库:`c46053e` 显示名 / `8051906` 段落修复 / `bc1dee3` provider 配置面去 env 化;**一处形状变更(插件动作响应)、一处新增硬约束;无破坏性变更**)**:① **`plugin.providers` 候选响应形状:裸字符串数组 → 对象数组 `{"options":[{"value":,"label":<中文显示名>},…]}`**(§4.14(c))。修的是主人报障「中文搜索接口在管理后台下拉菜单里全给的英文名,你甚至不愿意带个括号把中文名写上去」—— 根因不是漏写文案,而是**整条链路没有「显示名」这个位置**(字段声明只有 `optionsFrom`,动作回的就是纯 id 数组)。🔴 **两条硬口径**:**(i) `label` 仅供显示,`value` 才是落盘值**(把 `label` 提交进 `provider` 会写坏配置文件);**(ii) 前端解码器兼容三种形状**(裸字符串 / `{value,label}` / `{id}`)⇒ 仍返回裸字符串的其他插件与老声明**零影响**。表格单元格按 `value→label` 索引显示、查不到回落原值;多选(降级链)改为「候选开关(显示 label、写 value)+ 落盘值输入框」。② **产出侧新硬约束(§4.16)**:助手文本(落库 / 对外 / IM 镜像)**不得出现未配对的段落标签**(落单 `

` 或 `

`)—— `一个

= 一段`,**段落不嵌套**。修的是安卓端登记的 `

正文

` 畸形(他们按非贪婪契约必然把 `

` 显示给用户);**实测两半都是产出侧造成**:规整器**原样放过**该形状,且会在「末段漏 `

`」时**亲手套出**它(并顺带修掉一个幂等漏洞:落单 `

` 会越跑越多)。🔴 **历史行不回填**,消费方**仍应保留**剥落单标签的容错。③ **模型面登记(非客户端契约面,供跨端对照)**:`web-fetch` 的**浏览器渲染降级档已接线**(`design/28 §1 M5 / §8 B5`),其返回体用 **`fetchedBy`**(`direct` / `browser`)**如实标注正文实际来自哪条路**,两条档的失败各自留痕(`browserError` / `directError`),**绝不静默替换**;被 SSRF 拒绝的目标**不换路**。④ **计数**:本版**无新端点** ⇒ §2.0 权限矩阵维持 **14 + 24 + 40 = 78**。⑤ ⚠️ **诚实边界**:**`web-search` 的真机工具路径在登记时仍有一处未部署的修复**(智谱对「热搜/资讯」类查询返回的条目 `link` 整批为空,旧判据把「无链接」当「不是结果」整批丢光 ⇒ 工具报 `no results` 而面板探针报 `ok` —— 因为探针**不过 `Finalize`**,对这一类缺陷**结构性失明**)。修复已推库(`2c56bc9`:无链接条目照原样保留、只有 link/title/content/snippet 全空的空壳行才不算结果),**要等下次 `build` + `restart` 才真机生效**。**教训登记**:**别拿面板探针通不通来推断工具路径通不通**。 @@ -1484,7 +1486,17 @@ curl -s -X POST -H "X-API-Key: $KEY" \ > 只保留最近若干版(**v0.60 起**);更早的全部历史见归档 `Docs/archive/api-client.v0.65.md` §5(v0.28 → v0.59)。 > 每版一句话 + 三档标注。**三档定义见 [§0.4](#04-三档标注惯例)。** -### v0.85(当前契约版本) +### v0.86(当前契约版本) + +**「搜索结果条数上限可配」版 —— 面板多一个 number 字段 + 两个动作各多一个键**(切片 `bff57bd` + `c68387a`;依据:主人 2026-10-07 口径「返回五条结果会不会太少了?」+ 上一片登记的「`maxResults` 在面板上根本改不了」那条真缺陷。) + +- **上限真源与语义**:运行期真源是**面板文件** `$DATA_DIR/config/plugins/web-search.json` 的 `maxResults`(**压过** `.env` 的 `WEB_SEARCH_MAX_RESULTS`),是**真上限** —— 模型的 `count` 只能更小(`count:50` ⇒ 只给上限那么多;`count:3` ⇒ 给 3;缺省/非法 ⇒ 上限)。代码默认值 **5 → 10**,三处口径已一致。 +- **契约增量(三项,纯加性)**:① 面板声明新增字段 `maxResults`(`type:"number"`, `min:1`,前端无需改动);② `plugin.saveProvider` 的 `values` 新可写键(**三态**:缺位不改动 / 正整数采用 / 其余 **`400 invalid_payload`** 且**盘上不动**);③ 响应回显 —— `saveProvider.data.result.maxResults`(**0 = 未声明**)与 `reauthProvider.values.maxResults`(盘上有值才出现)。 +- **计数**:**无新端点** ⇒ §2.0 维持 **`14 + 25 + 40 = 79`**。 +- **验证证据**:整树 `go build ./...` 通过;三包 vet/测试绿;端到端「面板存 20 ⇒ 模型要 50 也只给 20、要 3 给 3」(真 action hub → 落盘 → 热重载 → `WithSearchMaxResults` → 真 dispatcher,**未重启后端**);主会话用 `-overlay` 摘掉落盘那一行 ⇒ 两条用例变红。 +- ⚠️ **已知取舍(如实)**:**清空输入框 = `400`**(没有「去掉上限」的手势;要回 `.env` 兜底得改文件);**小数/负数一律拒**;文件未声明该键时 `reauth` **不回填**(该框为空,看不出当前生效值)—— 与「面板显示的是该条、不是生效值」的既有口径一致。 + +### v0.85(上一版) **「整理不再只产建议」版 —— 新增 1 条端点(B25)+ 一处行为变更(轮末自动落地)+ `link` 候选停产**(切片 TIDY-AUTO `6d92536`;依据:主人 2026-10-07 口径「该删就删,怎么这个记忆整理机制这么保守呢」;`design/23 §7/§13/§14`。)