Surface 覆盖层
Surface 是一种路由覆盖层,仅在某个运行时条件成立时激活,并且会遮蔽同一路径的内置路由。
普通路由注册在唯一的 (method, path) 上,且内置路由始终优先;而 surface 由中间件在路由匹配之前进行求值,因此只要其条件为真,它就可以替换另一个模块已经占用的路径——例如 site 模块的 /。
最典型的场景是首次运行引导:当尚未配置任何 LLM 提供商时,Agent OS 会在 / 提供设置向导,而不是营销站点。一旦配置了提供商,该覆盖层就会消失,无需重建模块图,也无需重启(除了向导自身触发的那次重启)。
配置
Surface 在 config/surfaces/*.yaml 中声明:
id: onboarding
when: no_llm # always | no_llm | llm
priority: 100 # higher wins between matching surfaces
paths: ["/", "/index.html", "/site/"]
page: onboarding.html # HTML file, relative to this YAML …
# … or, instead of `page`:
# redirect: /platform
| 字段 | 含义 |
|---|---|
id |
标识;同 id 的文件会替换内置默认值。 |
when |
条件:always、no_llm 或 llm(已配置 LLM)。 |
priority |
当多个 surface 匹配同一路径时的优先级裁决(数值高者优先)。 |
paths |
路径模式;支持 :param 和 *rest 通配符,否则为精确匹配。 |
page |
surface 匹配时原样提供的 HTML 文件。 |
redirect |
302 目标(与 page 互斥)。 |
内置的 onboarding surface 随二进制文件一起编译了其页面,因此全新分发版本无需任何配置即可工作。添加 config/surfaces/<id>.yaml(以及 page: 文件)即可自定义它,或添加你自己的覆盖层——例如维护页面:
id: maintenance
when: always
priority: 50
paths: ["/", "/:page"]
page: maintenance.html
条件
条件是一套小型、具名的词汇表,针对运行时状态快照进行求值(并非通用表达式语言),因此配置保持可审计:
no_llm— 当且仅当没有任何推理提供商拥有可解析的 API 密钥时激活(provider.api_key、具名的models.*条目,或环境中的AGENT_OS_API_KEY/DEEPSEEK_API_KEY)。llm— 其补集。always— 无条件。
优先级与安全性
- 覆盖层中间件在路由之前运行:匹配且激活的 surface 优先于内置路由。
- 在多个匹配的 surface 之间,
priority最高者胜出(优先级相同:先注册的 surface)。 - 仅覆盖
GET/HEAD。 - 控制路径永远不会被覆盖:
/v1/*、/login*、/logout、/health、/metrics、/proxy*、/ui/*、/widget*、/dist*、/favicon.ico以及站点样式表/favicon。因此覆盖层永远不会将操作员锁在服务器配置之外。 - 没有任何 surface 认领的请求会原样透传,并照常由访问日志记录。
首次运行引导
当服务器在没有 LLM 的情况下启动时,内置的 onboarding surface 会在 /、/index.html 和 /site/ 提供向导。该向导会向以下端点发送请求:
GET /v1/onboarding— 状态:{ llm_configured, base_url, model }(无密钥)。POST /v1/onboarding/connect—{ base_url, model, api_key };持久化config/llm.yaml(基础 URL + 模型,非机密)和AGENT_OS_API_KEY到.env(已被 git 忽略),然后重启服务器以使设置生效。
这两个端点按设计是公开的——其全部意义就在于让全新的、未配置的服务器能够被设置。一旦存在提供商,连接端点就会以 409 拒绝,因此它无法被用来悄悄重新配置正在运行的服务器(而且它永远不会被覆盖层遮蔽)。
config/llm.yaml 在加载时覆盖 server.yaml 的 provider: 块;server.yaml 本身永远不会被重写,因此其注释得以保留。
管理界面
管理外壳(菜单 Platform)暴露了由宿主拥有的两个页面:
/provider— (重新)配置 LLM 连接(GET/POST /v1/provider,需管理员权限)。保存config/llm.yaml+AGENT_OS_API_KEY并重启。用于在首次运行后更改提供商;与公开的引导向导不同,它始终可用。/surfaces— 强制开启/关闭任何 surface,或恢复为自动(GET /v1/surfaces、POST /v1/surfaces/:id,使用{ "mode": "on" | "off" | "auto" },需管理员权限)。强制切换会实时生效并持久化到config/surfaces.enabled(每行一个id表示强制开启;-id表示强制关闭;缺省 = 遵循条件)。启动时也会读取同一文件。
这两个 API 都需要管理员权限(未配置认证时该门禁会打开),且这些页面永远不会被 surface 遮蔽。
禁用
删除 config/surfaces/<id>.yaml(或更改其 when)并重启。只要未配置 LLM,内置的引导 surface 就会处于激活状态。若想在该状态下仍保留正常站点,可用 when: llm 覆盖它,该 surface 仅在存在 LLM 时激活(即首次运行期间永不激活):
id: onboarding
when: llm
或者直接配置一个提供商。