HTTP 路由(http_route / static_route)
🔵 代理和全局配置可以在服务器上添加自己的
HTTP 端点,无需重新构建——采用与 script_tool /
script_hook 相同的模型。两种路由:
http_route—— 由 Rhai/JavaScript 编写的处理器,负责响应请求。static_route—— 从文件夹提供静态资源(HTML/CSS/JS/资源文件)。
这是对固定接口的补充,而非替代:POST /v1/inbound/:template、
POST /v1/workflows/<name>/webhook 以及 telegram/email 通道。
21.1 布局
config/
├── routes/ # 全局路由——自动加载
│ ├── weather.yaml + weather.rhai
│ └── assets.yaml + assets/ # static_route 的静态资源文件夹
└── agents/
└── felix/
├── agent.yaml # route_files: [routes/card.yaml]
└── routes/
├── card.yaml + card.rhai
└── static/ # 代理添加的静态资源
全局路由从 config/routes/*.yaml 自动读取。
代理路由从 agent.yaml 显式挂载:
# config/agents/felix/agent.yaml (felix — условное имя агента для примера)
route_files:
- routes/card.yaml
代理路由注册到与全局路由同一个路由器中。按照
约定(这是警告,不是错误),代理路由的路径以
/ext/<agent>/ 开头。
21.2 http_route(脚本)
kind: http_route
name: weather # id маршрута (по умолчанию — имя файла); пишется в access-лог
method: GET # GET|POST|PUT|PATCH|DELETE|HEAD|* (по умолчанию GET)
path: /ext/weather/:city # сегменты :param и *rest; путь должен быть уникальным
script: weather.rhai # .rhai или .js, относительно этого YAML
auth: public # public | bearer | token_env | user
token_env: "" # env с bearer-токеном (при auth: token_env)
max_operations: 10000000 # бюджет инструкций скрипта
timeout_ms: 30000 # потолок времени на обработчик
max_body_bytes: 1048576 # лимит тела запроса (по умолчанию 1 МиБ)
# + любые свои поля → доступны в скрипте как request.config.<field>
脚本契约
fn handler(request) {
// request = #{
// method: "GET",
// path: "/ext/weather/moscow",
// params: #{ city: "moscow" }, // захваты :param / *rest
// query: #{ unit: "c" },
// headers: #{ "content-type": "application/json" },
// body: "...", // сырое тело строкой ("" если нет)
// json: #{ ... }, // распарсенное JSON-тело, или ()
// user_id: "...", // разрешённая идентичность, или ()
// user: "...", // канонический id пользователя, или ()
// config: #{ ... } // сырой YAML, включая свои поля
// }
if request.user_id == () {
return #{ status: 401, body: #{ error: "unauthorized" } };
}
let r = http_get(`https://api.weather.example/${request.params.city}`);
if r.status != 200 {
return #{ status: 502, body: #{ error: r.error } };
}
#{ status: 200, body: parse_json(r.body) }
}
返回 #{ status, headers, body }(所有字段均可选):
status—— 默认为200。headers—— 映射;默认根据body的类型选择Content-Type。body—— 映射 → 序列化为 JSON(application/json);字符串 → 按原样返回(text/plain)。- 抛出的异常 →
500,错误会被记录日志,响应体为空。
辅助函数
与 script_tool 相同的引擎和辅助函数集:http_get/post/put/patch/delete、
http(method, …)、env_var、now、sleep_ms、urlencode/urldecode、
parse_json/to_json、json_get、extract_script_json、print/debug,以及
db_query/db_schema/db_execute 用于读写 DuckDB(见 §7.3)和
shell/exec/fs_read(见 §7.3)。
路由中 llm(...) 不可用(没有代理进程可以计入
预算)——调用会抛出错误。无法访问内核;调用之间的状态不会
保留(请使用 HTTP 或外部存储/db_*)。
21.3 static_route(文件)
kind: static_route
name: felix_assets
path: /ext/felix/*rest # должно заканчиваться на *rest
dir: static/ # папка относительно этого YAML
index: index.html # отдаётся при обращении к "/" (опционально)
auth: public
cache_ttl_secs: 300 # Cache-Control (опционально)
mime: # переопределение content-type по расширению (опционально)
.wasm: application/wasm
行为(仅限 GET/HEAD):
*rest解析为dir下的路径;路径穿越会被阻止,检查 最终文件是否仍位于dir内。- Dotfiles 和
.env永远不会被提供(拒绝列表)。 - Content-type 根据扩展名确定(html/css/js/json/svg/png/jpg/…)。
- 文件不存在 →
404;请求文件夹 →index(如果已设置),否则404。
这样代理就能提供静态资源:static_route 指向其
routes/ 内的 static/ 文件夹。
21.4 认证
| 模式 | 行为 |
|---|---|
public |
开放(开发环境默认) |
bearer |
要求 Authorization: Bearer <token> 与 auth.admin_tokens 匹配(日志中记录名称,而非值) |
token_env |
要求 bearer 令牌等于 token_env 中的环境变量值(未设置/为空 = 拒绝一切,fail-closed) |
user |
要求有效的最终用户身份:当设置了 auth.web_user_secret_env 时使用 HMAC X-User-Token,否则信任 X-User-Id(开发环境)。返回 request.user_id / request.user |
auth: user 从第一个版本起就可用。
21.5 路由与生命周期
- 所有路由注册到同一个路由器中。
path+method与 内置路由(/、/health、/v1/*、/login、/proxy*、/widget*、 …)或与其他自定义路由的冲突会在加载时被拒绝(该路由 被跳过,记录错误)。 - 内置路由始终优先;自定义路由不得遮蔽它们。
- 自定义路由之间的匹配优先级:精确字面量 >
:param>*rest。/ext/git/api/dashboard上的精确http_route始终优先于/ext/git/*rest上的通配符static_route——可以为静态资源和 API 保持统一的 命名空间。特异性相同时,先注册的优先。 - 推荐的命名空间:全局 →
/ext/...,代理 →/ext/<agent>/...。 - 热重载(
kernel.live_reload,默认开启):config/routes/**和 代理route_files在变更时(添加/移除/更新)重新注册, 与工具和钩子一样。 - 处理器是同步的 → 在阻塞线程池上执行(而非 async-runtime),
受
timeout_ms和max_operations限制。
21.6 访问日志
对自定义路由的每个请求都会写入 logs/access.jsonl /
logs/access.db,与内置路由一样,但使用路由名称及其作用域
(global / <agent>)代替自由路径。请求体、请求头和令牌值
不会被记录。
21.7 测试(无需服务器)
agent-os route config/routes/weather.yaml '{"method":"GET","path":"/ext/weather/moscow"}'
agent-os route config/agents/felix/routes/card.yaml --method POST --body '{"user_id":"x"}'
agent-os route config/routes/dash.yaml --args-file req.json # request JSON из файла ('-' = stdin)
agent-os route 打印模拟请求、脚本结果(status + headers + body)
和脚本错误——相当于路由的 agent-os hook / agent-os tool。对于
static_route,显示在 dir 下解析出哪些文件。带有
?/& 的查询参数通过 --args-file 传递更方便,以免被 shell 破坏。