tools-backend 是 mai 用来跑实际工具调用的小服务,跑在 Cloudflare Worker 上。这一页讲:你发的请求进来后,先过哪几道关,怎么找到对的工具,工具是同步出结果还是先记一笔任务再轮询。读完你大概能知道每一段责任在哪、出问题去看谁。
下图把请求从进入到出门画在三条横线上:你那边、worker 里、外面的服务。前三道关谁都跑(贴标签、计时、拦跨域),第四道是 auth — 看你带没带 JWT。
前三道都在 app.ts 的 app.use('*', ...) 里挂着,顺序固定,每个请求都跑。它们不挡请求,只往请求上添附信息或给后续看的人留痕。
| 这道关 | 在干嘛 | 留下了什么 |
|---|---|---|
| request-id | 给请求生成或读取一个单号 | 放到 c.requestId,所有后续 log、错误响应都带上它,方便你串日志 |
| timing | 开始计秒 | 请求结束打一条 timing log,记录这个请求总耗时 |
| CORS | 看 origin 决定要不要发 CORS 头 | 没配 CORS 就跳过,直接 next();预检请求 (OPTIONS) 也在这里处理 |
auth 看请求带没带 ignisauth header,带就拿那段 JWT 去验;没带就退回找 Authorization: Bearer。两者都不通过,直接 401 退回客户端。完整流程见 → auth-flow 这一面。
带了 JWT 的情况
退回 bearer token 的情况
Authorization: Bearer xxx 或 X-Worker-Auth-Key: xxx/healthz 和 /deploy-check 完全不查 auth — 给探活和部署校验用。tobatsu-gateway 的回调路由 /tobatsu-gateway-callbacks/:kind 也绕过 ignisauth,它自己有 bearer,由 tobatsu-gateway 那边验。
tools-backend 一共注册了 17 个工具,全都挂在 POST /:toolName 这一个路由上。Hono 拿到 toolName 后去 registry.ts 找;找不到 404;找到先检查 Content-Type 是不是这个工具收的,不收就 415。
| 看到的请求 | 结果 |
|---|---|
GET / 或 GET /tools |
返回所有工具的元信息(名字、描述、accepts) |
GET /tools/extract-frame |
只返这一个工具的元信息 |
POST /extract-frame + JSON body |
把请求交给这个工具的 run(),等它返回结果 |
POST /未知工具 |
404 + code: TOOL_NOT_FOUND |
POST /extract-frame + 错的 Content-Type |
415 + code: UNSUPPORTED_MEDIA_TYPE,告诉你这个工具收什么 |
GET /tasks/abc123 |
异步任务结果,下一段说 |
有些工具拍一下立刻有结果(比如读视频帧时长),有些得跑很久(视频剪辑、Vertex 大模型)。tool.run() 返回的对象里有个 kind 字段告诉框架走哪条。
poll_interval_ms 间隔来问。tool 自己实现 poll(),框架负责存读 task 和把结果回填。同步工具 (kind: 'sync')
异步工具 (kind: 'async')
{task_id, poll_url, poll_interval_ms, timeout_ms}Hono 的 app.onError 是统一兜底。工具里抛 ApiError(带 status / code / message / details)就照样返回;抛任何其它的 Error,统一变成 500 + INTERNAL_ERROR,不把内部错误消息泄到响应里。但 console.error 会记完整对象,所以 worker 日志能看到。
| 情况 | HTTP | 响应 code | 什么时候出 |
|---|---|---|---|
| 路径不存在 | 404 | NOT_FOUND | 路由没匹配到任何 handler |
| 工具不存在 | 404 | TOOL_NOT_FOUND | POST 到没注册的 toolName |
| Content-Type 不收 | 415 | UNSUPPORTED_MEDIA_TYPE | 请求 Content-Type 不在工具的 accepts 列表里 |
| auth 失败 | 401 | UNAUTHORIZED | JWT 验不过 / bearer 不对 / 都没带 |
| 配置缺失 | 500 | CONFIG_ERROR | 比如 ignisauth 公钥环境变量没配 |
| 异步任务找不到 | 404 | TASK_NOT_FOUND | GET /tasks/:id 的 id 在 KV 里没有 |
| 其它没接住的 | 500 | INTERNAL_ERROR | onError 兜底,message 不外泄 |
TOOLS_BACKEND_EXPOSE_ERROR_DETAILS=1,ApiError 的 details 字段就会回到响应里(调试用,线上别开)。其它 Error 永远不会回 details。