命令、API、SDK 与 WebUI 扩展边界

对应源码

扩展表面源码
slash commandnanobot/command/router.py::CommandRouter、nanobot/command/builtin.py
HTTP APInanobot/api/server.py
Python SDK 与 streamnanobot/sdk/clients.py、runtime.py、streaming.py、types.py
WebSocket 和 WebUI backendnanobot/channels/websocket.py、nanobot/webui/*_api.py、*_routes.py、ws_http.py
Runtime Event 与 hooknanobot/bus/runtime_events.py、nanobot/agent/hook.py
前端实现webui/src/lib/、webui/src/hooks/、webui/src/components/

学习目标

读完后,你应当能够:

  • 选择 slash command、Tool、SDK、HTTP API 或 WebUI;
  • 新增命令时同步 handler、router 和 palette;
  • 区分 public OpenAI-compatible API 与 WebUI 私有 HTTP 路由;
  • 让 UI 订阅通用 Runtime Event,而不污染 core。

前置知识

1. 按消费者选表面

消费者首选
聊天用户的确定性控制slash command
模型自主决定的动作Tool
同一 Python 进程SDK
OpenAI client/外部 HTTP 程序/v1 API
nanobot 浏览器界面WebUI HTTP + WebSocket
所有运行表面的通用进度Runtime Event

如果一个能力只在 WebUI 中展示,不要因此给模型新增 Tool;如果一个动作必须由用户明确触发,不要强迫模型推断何时调用。

2. 新增 slash command

命令系统位于:

  • nanobot/command/router.py
  • nanobot/command/builtin.py

CommandRouter 有三层:

层行为适合
priority在 Session dispatch lock 前 exact match必须中断/观察正在运行 Turn 的控制
exactlock 内完整匹配无参数命令
prefix最长前缀优先带参数命令

新增 built-in command 通常需要:

  1. 写 cmd_xxx(ctx);
  2. 在 register_builtin_commands() 注册;
  3. 在 BUILTIN_COMMAND_SPECS 加入 palette/help metadata;
  4. 补命令 handler 与 router tests;
  5. 核对 WebUI /api/commands 和其他 Channel help。

priority 是并发语义,不是性能优化

它能绕过当前 Session lock。只有确实需要在 Turn 运行中生效、且能安全读写状态的命令才适合 priority。

3. Public HTTP API

nanobot/api/server.py 是 aiohttp 应用,当前公开:

  • POST /v1/chat/completions
  • GET /v1/models
  • GET /health

其中 /v1 路由的目标是 OpenAI-compatible 交互。新增字段或 endpoint 时,要判断:

  • 是否仍符合 OpenAI 兼容语义;
  • streaming SSE 是否兼容;
  • multipart/base64 media 限制;
  • timeout/cancellation;
  • session key 与并发;
  • 错误状态码;
  • 文档与兼容测试。

仅供 WebUI 使用的设置、workspace 或 Session 管理 endpoint 不应随意放到 /v1。

4. Python SDK

SDK 位于:

  • nanobot/sdk/__init__.py
  • nanobot/sdk/clients.py
  • nanobot/sdk/runtime.py
  • nanobot/sdk/streaming.py
  • nanobot/sdk/types.py

它是 AgentLoop 的进程内 facade,不是 HTTP API client。SessionClient、MemoryClient 和 RuntimeClient 直接操作 loop service。

新增 SDK 能力时:

  • 优先调用已有 service,而不是复制业务逻辑;
  • 返回 snapshot/dataclass,避免暴露可变内部对象;
  • 更新 public export;
  • 同步 async context manager 的资源语义;
  • 考虑 model override 的 reader/writer gate;
  • 写 SDK 专项测试。

不要把 _cache、_pending_queues 或 Provider credential 等内部状态直接变成公共 SDK 契约。

5. WebUI backend

WebUI 的 Python 边界分布在:

  • nanobot/channels/websocket.py
  • nanobot/webui/ws_http.py
  • nanobot/webui/*_routes.py
  • nanobot/webui/*_api.py
  • nanobot/session/webui_turns.py
  • nanobot/bus/runtime_events.py

职责分离:

  • Agent core:发布通用 runtime events;
  • WebSocket coordinator:sequence、replay、订阅、wire envelope;
  • HTTP route:认证、解析、状态码;
  • *_api.py:配置/Session/usage 等业务;
  • 前端:获取、缓存、展示和交互。

新 endpoint 应先找是否已有 route dispatcher 和 API helper,不在 ws_http.py 中堆全部业务。

6. WebUI frontend

主要入口:

  • webui/src/lib/nanobot-client.ts
  • webui/src/lib/api.ts
  • webui/src/lib/types.ts
  • webui/src/hooks/
  • webui/src/components/
  • webui/src/i18n/locales/
  • webui/src/tests/

一项可见功能通常要同步:

  1. wire/HTTP 类型;
  2. client method;
  3. hook/state;
  4. component;
  5. loading、empty、error 状态;
  6. i18n;
  7. frontend test;
  8. Python contract test;
  9. Vite build output。

7. 先判断扩展 Runtime Event 还是 hook

若多个运行表面都需要一个新的高层运行状态:

  1. 在 nanobot/bus/runtime_events.py 定义通用语义;
  2. 在 core 的真实状态变化点发布;
  3. WebSocket coordinator 等订阅者转换为自己的协议;
  4. 分层测试发布顺序和失败隔离。

事件名应描述运行事实,如“session turn started”或“goal state changed”,而不是组件动作,如“打开右侧抽屉”。

若要增加文本、reasoning、Tool 或文件编辑等细粒度进度,应先检查 nanobot/agent/hook.py、progress_hook.py 和 SDK streaming adapter。当前 SDK 的 text.delta、tool.started 等不是从 RuntimeEventBus dataclass 直接转换而来。

flowchart TD
    A["高层 Turn / Goal 状态"] --> B["RuntimeEventBus"]
    B --> C["WebSocket coordinator / 其他 subscriber"]
    C --> D["运行表面协议"]

    E["文本 / reasoning / Tool 进度"] --> F["Agent hooks / callbacks"]
    F --> G["SDK / Channel / WebSocket adapter"]
    G --> H["各自的流式事件"]

8. 同步修改矩阵

变更常见必须同步视需求同步
built-in commandhandler、router、spec、testsdocs/i18n
/v1 APIhandler、schema、contract tests、docsSDK 若承诺对等
SDK methodclient/runtime、types、exports、tests、docs示例
WebUI HTTP routeroute、API helper、auth、Python testsfrontend client
WebSocket eventevent type、coordinator、TS type、hook、teststranscript replay
可见设置config schema、settings API、UI、i18n、testsmigration/docs

这个表是检查清单,不是要求所有扩展同时改所有层。

9. 先复用,再新增

新增 surface 前检查:

  • Session service 是否已有方法;
  • RuntimeEventBus 是否已有接近事件;
  • WebUI 是否已有 API client 和 route dispatcher;
  • SDK 是否能直接复用 core service;
  • command 是否只是已有 Tool 的确定性入口;
  • public API 是否真的需要新协议,而不是现有 metadata。

10. 测试参考

Command:

  • tests/command/test_router_dispatchable.py
  • tests/command/test_model_command.py
  • tests/command/test_skill_command.py

API:

  • tests/test_openai_api.py
  • tests/test_api_stream.py
  • tests/test_api_attachment.py

SDK:

  • tests/test_nanobot_facade.py

WebUI/WebSocket:

  • tests/channels/test_websocket_http_routes.py
  • tests/channels/test_websocket_channel.py
  • tests/webui/
  • webui/src/tests/

自检

  1. 哪类命令才应注册为 priority?
  2. SDK 为什么不应直接返回内部可变 Session?
  3. WebUI 私有 route 为什么不应随意进入 /v1?
  4. 一个新 UI 状态何时需要 Runtime Event,何时只需前端本地 state?

下一部分:源码实践导航