命令、API、SDK 与 WebUI 扩展边界
对应源码
| 扩展表面 | 源码 |
|---|---|
| slash command | nanobot/command/router.py::CommandRouter、nanobot/command/builtin.py |
| HTTP API | nanobot/api/server.py |
| Python SDK 与 stream | nanobot/sdk/clients.py、runtime.py、streaming.py、types.py |
| WebSocket 和 WebUI backend | nanobot/channels/websocket.py、nanobot/webui/*_api.py、*_routes.py、ws_http.py |
| Runtime Event 与 hook | nanobot/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。
前置知识
- 理解 五个入口与运行边界;
- 理解 HTTP API 与 Python SDK;
- 理解 WebSocket 与 WebUI。
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.pynanobot/command/builtin.py
CommandRouter 有三层:
| 层 | 行为 | 适合 |
|---|---|---|
| priority | 在 Session dispatch lock 前 exact match | 必须中断/观察正在运行 Turn 的控制 |
| exact | lock 内完整匹配 | 无参数命令 |
| prefix | 最长前缀优先 | 带参数命令 |
新增 built-in command 通常需要:
- 写
cmd_xxx(ctx); - 在
register_builtin_commands()注册; - 在
BUILTIN_COMMAND_SPECS加入 palette/help metadata; - 补命令 handler 与 router tests;
- 核对 WebUI
/api/commands和其他 Channel help。
priority 是并发语义,不是性能优化
它能绕过当前 Session lock。只有确实需要在 Turn 运行中生效、且能安全读写状态的命令才适合 priority。
3. Public HTTP API
nanobot/api/server.py 是 aiohttp 应用,当前公开:
POST /v1/chat/completionsGET /v1/modelsGET /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__.pynanobot/sdk/clients.pynanobot/sdk/runtime.pynanobot/sdk/streaming.pynanobot/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.pynanobot/webui/ws_http.pynanobot/webui/*_routes.pynanobot/webui/*_api.pynanobot/session/webui_turns.pynanobot/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.tswebui/src/lib/api.tswebui/src/lib/types.tswebui/src/hooks/webui/src/components/webui/src/i18n/locales/webui/src/tests/
一项可见功能通常要同步:
- wire/HTTP 类型;
- client method;
- hook/state;
- component;
- loading、empty、error 状态;
- i18n;
- frontend test;
- Python contract test;
- Vite build output。
7. 先判断扩展 Runtime Event 还是 hook
若多个运行表面都需要一个新的高层运行状态:
- 在
nanobot/bus/runtime_events.py定义通用语义; - 在 core 的真实状态变化点发布;
- WebSocket coordinator 等订阅者转换为自己的协议;
- 分层测试发布顺序和失败隔离。
事件名应描述运行事实,如“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 command | handler、router、spec、tests | docs/i18n |
/v1 API | handler、schema、contract tests、docs | SDK 若承诺对等 |
| SDK method | client/runtime、types、exports、tests、docs | 示例 |
| WebUI HTTP route | route、API helper、auth、Python tests | frontend client |
| WebSocket event | event type、coordinator、TS type、hook、tests | transcript replay |
| 可见设置 | config schema、settings API、UI、i18n、tests | migration/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.pytests/command/test_model_command.pytests/command/test_skill_command.py
API:
tests/test_openai_api.pytests/test_api_stream.pytests/test_api_attachment.py
SDK:
tests/test_nanobot_facade.py
WebUI/WebSocket:
tests/channels/test_websocket_http_routes.pytests/channels/test_websocket_channel.pytests/webui/webui/src/tests/
自检
- 哪类命令才应注册为 priority?
- SDK 为什么不应直接返回内部可变 Session?
- WebUI 私有 route 为什么不应随意进入
/v1? - 一个新 UI 状态何时需要 Runtime Event,何时只需前端本地 state?
下一部分:源码实践导航