Files
mnote/design/old/07-ai/done/7-45-chatonly-api-provider-runtime-v1.md
T
Agent Board b798f628ee chore: land tree view-state, vault, Pi module split, and repo hygiene
Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
2026-07-21 05:13:05 +08:00

14 KiB
Raw Blame History

[recycle] 7-45 ChatOnly API provider runtime v1

创建时间:2026-06-02

状态:done

OwnerPage AI ChatOnly / mnote-web / SQLite control-plane / OmniRoute API provider

上位依据:

  • design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md
  • design/07-ai/done/7-25-acp-session-runtime-enhancement-plan-v1.md
  • design/07-ai/done/7-30-acp-session-load-resume-checklist-v1.md
  • design/07-ai/done/7-39-page-ai-agent-selector-context-authorization-settings-v1.md
  • design/07-ai/done/7-44-chatonly-doubao-session-binding-v1.md

1. 背景

当前 Page AI Chat-only 已接入豆包、DeepSeek、Gemini 等网页 provider。网页 provider 的价值是复用网页账号、网页历史和远端会话删除,但它也带来浏览器、登录态、验证码、前台窗口、OpenClaw provider prompt 和 provider-specific 会话绑定复杂度。

现在 OmniRoute 已能提供 OpenAI-compatible API endpoint

http://127.0.0.1:20128/v1/chat/completions

并已验证以下模型可用:

  • aisz-chat/grok-4.3
  • aisz-chat/gemini-3.1-pro
  • aisz-chat/gpt-5.5-extra-high-fast
  • aisz-chat/kimi-k2.5
  • DeepSeek pro / flash 组合模型

这些模型不需要浏览器,也不需要 OpenClaw。对于 MNote 当前需求,它们只是最简 ChatOnly provider:用户选一个 provider,发送消息,MNote 本地保存历史,删除 MNote 会话时删除本地历史。

2. 第一结论

API ChatOnly 不走 OpenClaw。

原因:

  • OpenClaw 是 agent runtime,包含 agent prompt、skills、workspace、compaction、浏览器 profile 等能力;最简聊天窗口不需要这些能力。
  • API provider 没有网页远端历史对齐语义,不应复用 CHATONLY_PROVIDER_CONFIGS 的网页 conversation binding / remote delete 链路。
  • MNote 已有 ChatOnly UI、SSE 消费、SQLite runtime session、删除入口和 reqwest streaming 依赖;新增一个薄的 OpenAI-compatible adapter 比引入完整第三方 chat app 更可控。

最终分层:

Provider 类别 默认状态 运行方式 历史删除语义
豆包网页 保留默认可用 OpenClaw doubao-web MNote 本地删除 + 豆包远端 best effort 删除
DeepSeek 网页 fallback OpenClaw deepseek-web 网页远端 best effort 删除
Gemini 网页 fallback OpenClaw gemini-web 网页远端 best effort 删除
Grok 网页 fallback OpenClaw grok-web 暂不作为默认
OmniRoute API Chat 新默认候选 mnote-web 直连 /v1/chat/completions 仅 MNote 本地删除

3. 目标

  • 新增 api-chat provider kind,直接调用 OpenAI-compatible /v1/chat/completions
  • 支持 DeepSeek、Gemini、Grok、GPT、Kimi API ChatOnly profiles。
  • 复用现有 Page AI ChatOnly UI、session list、session detail、rename、delete、search 和 SSE 消费模型。
  • API provider 不启动 ACP runtime,不启动 OpenClaw,不注入网页 provider metadata prompt。
  • API provider 的会话和消息以 SQLite/control-plane 为准,按用户隔离。
  • 网页 DeepSeek/Gemini/Grok 保留为 fallback,后续稳定后再决定是否从默认 UI 中隐藏。

非目标:

  • 不实现完整 ChatGPT / Open WebUI / LibreChat 类产品。
  • 不导入第三方 chat app 的数据库、用户系统或前端壳。
  • 不同步 Gemini/Grok/DeepSeek 网页历史。
  • 不把 API ChatOnly 暴露为可写文件 agent。
  • 不支持 tool calling、function calling、vision、文件上传、多模态附件。

4. Provider 配置合同

新增一个小型静态 registry,建议先放在 mnote-web 后端,前端只消费 profile 列表:

api_chat_profiles
- profile_id
- label
- provider_kind = api-chat
- base_url
- model
- api_key_env
- fallback_profile_id
- status = active | fallback | hidden

第一批 profiles

profile_id label model 默认状态
shared_api_deepseek_flash_chat DeepSeek Flash DeepSeek flash 组合模型 active
shared_api_deepseek_pro_chat DeepSeek Pro DeepSeek pro 组合模型 active
shared_api_gpt_chat GPT aisz-chat/gpt-5.5-extra-high-fast active
shared_api_kimi_chat Kimi aisz-chat/kimi-k2.5 active
shared_api_gemini_chat Gemini API aisz-chat/gemini-3.1-pro active
shared_api_grok_chat Grok API aisz-chat/grok-4.3 active 或 hidden,按限额策略决定

默认 base URL

http://127.0.0.1:20128/v1

API key 优先级:

  1. MNOTE_API_CHAT_<PROFILE>_API_KEY
  2. MNOTE_API_CHAT_API_KEY
  3. OPENAI_API_KEY

不建议直接读取 /home/lix/.codex/auth.json 作为长期方案。开发期可以从环境注入,避免 MNote 代码绑定 Codex 配置文件。

5. 数据模型

继续复用现有 ai_runtime_runs / ai_runtime_events 作为会话索引和事件审计。API ChatOnly 的 run payload 增加:

{
  "agentId": "chat_only",
  "providerKind": "api-chat",
  "profileId": "shared_api_gpt_chat",
  "model": "aisz-chat/gpt-5.5-extra-high-fast",
  "sessionId": "mnote_...",
  "message": "..."
}

消息恢复来源:

  • 用户消息:从 run payload 的 message 恢复。
  • 助手消息:从 ai_runtime_events 中的 message.delta 聚合,或从 run.completed 的 final text 恢复。
  • 错误:写入 run.failed,用于 session detail 展示。

API ChatOnly 不写 ai_external_conversation_bindings。该表只属于网页 provider 远端会话绑定。

6. 后端数据流

6.1 创建会话

沿用现有:

POST /api/hermes/client/sessions

agentId=chat_only 且 profile 是 api-chat 时:

  1. 写入本地 session index。
  2. persistence=local_ai_session_jsonl 或当前 SQLite runtime persistence 口径。
  3. 不创建远端会话。
  4. 不创建 ACP session。

6.2 发送消息

沿用现有:

POST /api/hermes/client/runs

分流规则:

agentId == chat_only
  且 profile/profileId 命中 api-chat registry
    -> api_chat_runtime
否则
    -> 当前 ACP/OpenClaw/Web provider runtime

api_chat_runtime 执行:

  1. 按当前 user_id + workspace_id + session_id + profile_id 读取最近消息。
  2. 构造 OpenAI-compatible messages
[
  { "role": "system", "content": "你是 MNote 的简洁聊天助手。不要声称能编辑文件。" },
  { "role": "user", "content": "..." },
  { "role": "assistant", "content": "..." },
  { "role": "user", "content": "当前问题" }
]
  1. 调 OmniRoute
POST {base_url}/chat/completions
Authorization: Bearer ...
Content-Type: application/json

请求体最小字段:

{
  "model": "aisz-chat/kimi-k2.5",
  "messages": [],
  "stream": true
}
  1. 解析 upstream SSE
data: {"choices":[{"delta":{"content":"..."}}]}
data: [DONE]
  1. 转成 MNote 现有 SSE event
event: message.delta
data: {"delta":"..."}

event: run.completed
data: {"output":"...","model":"..."}
  1. 同步写入 ai_runtime_events,用于历史恢复和审计。

6.3 删除会话

沿用现有 session delete endpoint。

当 session 属于 api-chat

  1. 软删除本地 runs/events 或标记 session deleted。
  2. 不调用 provider conversation delete。
  3. 返回:
{
  "ok": true,
  "remoteDelete": {
    "attempted": false,
    "reason": "api_chat_has_no_remote_conversation"
  }
}

UI 文案必须避免暗示已删除网页历史。

7. 代码边界

建议新增模块:

rust/crates/mnote-web/src/api_chat.rs

职责:

  • profile registry。
  • provider config resolution。
  • OpenAI-compatible request construction。
  • streaming parser。
  • upstream error normalization。
  • 将 upstream chunk 映射为 message.delta / run.completed / run.failed

hermes_client.rs 只做分流和现有 session/run 持久化衔接,不继续塞大量 provider 细节。

前端改动控制在:

rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js
rust/crates/mnote-web/browser/sidebar-page-ai-session-runtime.js

前端只需要:

  • 展示新增 profiles。
  • 对 API ChatOnly 不显示“网页问答”描述,改为“API 聊天”。
  • 删除成功提示区分本地删除和网页同步删除。

SQLite/control-plane 改动:

rust/crates/control-plane/src/sqlite.rs

新增 shared profiles provisioning。第一版可以不新增表,只复用 profile metadata;如后续要 UI 动态配置模型,再引入 ai_chat_provider_profiles 表。

8. 第三方参考取舍

已考虑的参考:

  • async-openai:适合参考 OpenAI-compatible 配置、SSE streaming、请求类型;如果后续字段变多,可引入依赖。
  • HuggingFace chat-ui、Open WebUI、LibreChat:功能完整,但带独立前端、用户、会话和数据库模型,不适合嵌入 MNote 当前 Page AI sidebar。
  • Vercel AI SDK:前端/Next 生态友好,但 MNote 当前主链是 Rust SSR + Rust Axum,不应为最小 adapter 引入 JS server 侧依赖。

第一版采用 reqwest + serde_json 的最小 adapter。理由:

  • 当前仓库已有 reqwest streaming。
  • 需要支持的协议子集很小。
  • 不引入大型依赖和类型迁移成本。
  • bug 面集中在一个小模块,可用 mock upstream 和真实 OmniRoute smoke 覆盖。

9. 错误处理

必须明确区分:

  • api_chat_profile_unknownprofile 未注册。
  • api_chat_base_url_missingbase URL 缺失。
  • api_chat_api_key_missingAPI key 缺失。
  • api_chat_upstream_unavailableOmniRoute 不可达。
  • api_chat_upstream_errorOmniRoute 返回非 2xx。
  • api_chat_stream_parse_errorSSE chunk 解析失败。
  • api_chat_empty_response:流结束但没有助手文本。

错误事件写入:

event: run.failed
data: {"code":"api_chat_upstream_error","message":"..."}

前端展示为普通 ChatOnly 失败消息,不进入文件权限或 tool call UI。

10. 验收

10.1 单元测试

  • profile registry 能解析 DeepSeek/Gemini/Grok/GPT/Kimi。
  • API key 优先级正确。
  • OpenAI-compatible SSE chunk 能解析 delta.content
  • [DONE] 正确结束。
  • 非 2xx upstream 映射为 run.failed
  • API ChatOnly 不命中 CHATONLY_PROVIDER_CONFIGS
  • API ChatOnly 删除不调用网页 provider delete。

10.2 集成测试

Mock upstream

  • /v1/chat/completions 返回 SSE delta。
  • MNote /runs 返回 runId。
  • MNote /events/{runId} 输出 message.deltarun.completed
  • session detail 可恢复 user/assistant messages。
  • 删除 session 后列表不再出现。

真实 OmniRoute smoke

  • shared_api_deepseek_flash_chat 发一条短消息成功。
  • shared_api_gpt_chat 发一条短消息成功。
  • 确认没有启动或调用 OpenClaw gateway/proxy。

浏览器 smoke

  • 打开 Page AI ChatOnly。
  • 选择 GPT API,发消息,只出现一条助手回复。
  • 新建会话、切换会话、刷新页面后历史仍存在。
  • 删除会话后本地列表消失。
  • 豆包网页会话链路不回归,仍能远端删除。

11. 分阶段实施

Phase 1:最小 API runtime

  • 新增 api_chat.rs
  • 接入 shared_api_deepseek_flash_chatshared_api_gpt_chat
  • 复用现有 /runs/events
  • 跑 mock upstream 和真实 OmniRoute smoke。

Phase 2:扩展 provider 列表

  • 增加 deepseek_progemini_apikimigrok_api
  • Grok 默认可设为 hidden 或 fallback,避免限额被普通默认选择消耗。
  • 前端区分 API网页 fallback 标签。

Phase 3:收口网页 fallback

  • 观察 API provider 稳定性。
  • DeepSeek/Gemini/Grok 网页降级为 fallback 或隐藏。
  • 保留豆包网页主链,直到豆包也有可替代的稳定 API provider 且不再要求网页历史对齐。

12. 风险与约束

  • OmniRoute 模型名可能变动,profile registry 要允许环境覆盖 model。
  • 某些 upstream 可能不完全遵守 OpenAI streaming chunk 格式,parser 要容忍 content / text / delta 变体,但不要吞掉错误。
  • API ChatOnly 没有网页远端删除,UI 和返回值必须说清楚。
  • 不要让 API ChatOnly 继承 Page AI 文件写权限;它只能读已显式加入 prompt 的上下文。
  • 不要为第一版引入 tool calling,否则会重新变成 agent runtime。

13. 退出条件

本设计可归档到 done/ 的条件:

  • API ChatOnly 至少两个 provider 完成真实浏览器 smoke。
  • 本地历史创建、恢复、删除通过。
  • API ChatOnly 确认不启动 OpenClaw。
  • 豆包网页删除同步回归通过。
  • DeepSeek/Gemini/Grok 网页 fallback 没有被误删或误改。

14. 完成验证

完成时间:2026-06-02

已完成:

  • api-chat runtime 已接入 MNote ChatOnly,直接调用 OmniRoute OpenAI-compatible /v1/chat/completions,不启动 ACP/OpenClaw。
  • SQLite profile provisioning 已包含 DeepSeek Flash / DeepSeek Pro / GPT / Kimi / Gemini API / Grok API 六个 shared API ChatOnly profiles。
  • 后端 SSE 已将 OpenAI-compatible streaming 映射为 message.delta / run.completed / run.failed,并写入 ai_runtime_events 用于历史恢复。
  • API ChatOnly 删除只删除 MNote 本地会话,返回 api_chat_has_no_remote_conversation,不进入网页 provider 远端删除链路。
  • 真实 OmniRoute smoke 已验证六个模型均返回 200。
  • 真实浏览器 smoke 已验证 GPT API 与 DeepSeek Flash API:发送后只有一条助手回复,刷新后历史恢复,删除后本地会话消失,且没有写入 ai_external_conversation_bindings
  • 豆包网页主链回归已验证:豆包端 user/assistant marker 各一条,未重复发送助手回复,删除走侧栏三点菜单并返回 remote_deleted