
opencode HTTP 路由规范HttpApiBuilder 处理器分层、错误边界与 OpenAPI 兼容策略【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode本文基于 packages/opencode/specs/effect/routes.md 这份路由编写规范展开系统讲解 opencode 服务进程中packages/opencode/src/server/routes/instance/httpapi目录下的 HTTP 路由模式何时使用HttpApiBuilder.group(...)、何时退回裸HttpRouter域错误如何在处理器边界翻译成公开的 HTTP 错误契约以及 OpenAPI/SDK 兼容层public.ts的演进策略。读完后你能按仓库既有规范编写新的路由组、声明公开错误 schema并在改动会影响生成的 SDK 时执行正确的自检流程。路由代码在哪里规范解决什么问题opencode 的服务端 HTTP 表面集中在 packages/opencode/src/server/routes/instance/httpapi 目录按职责分为三层groups/用HttpApi/HttpApiGroup/HttpApiEndpoint声明每条端点的路径、query、payload、成功与错误 schema并挂载 OpenAPI 注解handlers/用HttpApiBuilder.group(...)编写各端点的具体实现middleware/server.ts授权、实例上下文、工作区路由等横切关注点以及最终的路由树组装。routes.md这份文档的定位是给路由代码的当前编写指南原文标题为 HTTP Route Patterns它约束的核心问题是处理器层如何取用服务、错误在哪里翻译、OpenAPI 兼容层如何逐步收缩。同一目录下的 AGENTS.md 提供了更细的模式说明含 SSE 与handleRaw的写法与本文互为补充。处理器形态稳定服务只 yield 一次规范第一条规则是普通的 JSON 与流式 HTTP 端点一律使用HttpApiBuilder.group(...)。构建处理器层时在Effect.gen里一次性 yield 所有稳定服务然后让各端点实现闭包捕获这些服务而不是在每个请求内重新解析依赖export const sessionHandlers HttpApiBuilder.group(InstanceHttpApi, session, (handlers) Effect.gen(function* () { const session yield* Session.Service return handlers.handle(list, () session.list()) }), )仓库中 session 路由组是这一模式的完整示范。handlers/session.ts 中sessionHandlers在构建期集中 yield 了十多个稳定服务Session.Service、SessionShare.Service、SessionPrompt.Service、SessionRevert.Service、SessionCompaction.Service、Permission.Service、EventV2Bridge.Service以及Scope.Scope随后通过一条链式调用把约 25 个端点逐一绑定return handlers .handle(list, list) .handle(get, get) .handleRaw(create, createRaw) .handle(prompt, prompt) .handle(revert, revert) // ...从源码结构看这带来几个可验证的实践细节请求级上下文才允许在请求内解析。例如messages处理器在返回分页链接时需要真实请求对象来构造Link头才在请求内yield* HttpServerRequest.HttpServerRequesthandlers/session.ts#L112-L144。规范强调“不要在请求处理器里重建稳定层”正是为了让这类请求级解析成为唯一的例外场景。流式响应仍留在HttpApiBuilder.group内。prompt端点通过HttpServerResponse.stream(...)返回流而不是普通 JSON 响应体handlers/session.ts#L295-L309说明streaming HTTP API endpoint同样适用本模式不必降级到裸路由。需要原始请求体时改用handleRaw。create与fork端点声明为可接受空 body[HttpApiSchema.NoContent, Session.CreateInput]处理器实现createRaw先取原始request.text手动JSON.parseSchema.decodeUnknownEffect解码handlers/session.ts#L159-L176。这保留了端点的中间件、路由上下文与 OpenAPI 元数据。什么时候才允许用裸 HttpRouter规范明确裸HttpRouter只用于不契合请求/响应 HttpApi 模型的路线典型场景是 WebSocket 升级和 catch-all 回退。server.ts 顶部的路由树注释精确对应了这一划分// Route tree: // - rootApiRoutes: typed /global/* and control routes; auth is declared by RootHttpApi. // - eventApiRoutes: typed SSE route ... // - ptyConnectApiRoutes: typed WebSocket upgrade route ... // - instanceApiRoutes: remaining typed instance routes. // - uiRoute: raw catch-all fallback; auth is router middleware so public static assets can bypass it.其中uiRoute是唯一的裸HttpRouter.use(...)catch-allserver.ts#L194-L203负责把未匹配路径落到内嵌 Web UI。与之形成对照的是PTY 的 WebSocket 升级端点其实也优先收编进了 HttpApiPtyConnectApi声明为 typed 端点经ptyConnectApiRoutes挂载体现了能进类型化路由树就进路由树的取舍。另外两条与分层相关的约束值得注意避免在请求处理器或裸路由回调里写Effect.provide(SomeLayer)——稳定层应在应用/层边界只提供一次HttpRouter.provideRequest(...)仅用于刻意请求级的依赖稳定服务应走HttpRouter.use(...)。错误边界域错误在处理器层翻译中间件只做兜底这是规范中最有约束力的一部分其目标可以概括为一句话预期的服务错误expected service errors应在处理器边界映射为端点声明的公开 HTTP 错误而不是在通用中间件里做域错误分发。公开错误必须是显式 schema 契约规范要求公开 JSON 错误应是在每个端点或分组上声明的显式 schema 契约内置HttpApiError.*只有在它生成的 body 恰好就是期望的公开线型wire shape时才可以使用。仓库中 errors.ts 就是这个契约的集中定义处每个公开错误都用Schema.TaggedErrorClass声明并附带httpApiStatusexport class SessionBusyError extends Schema.TaggedErrorClassSessionBusyError()( SessionBusyError, { sessionID: Schema.String, message: Schema.String, }, { httpApiStatus: 409 }, ) {}端点声明侧与之对应。以 groups/session.ts 的shell端点为例错误被显式声明为[HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError]——哪些状态码可能出现在 OpenAPI 契约里一目了然。而get端点则只声明[HttpApiError.BadRequest, ApiNotFoundError]。保留 { name, data } 线型直到明确的破坏性变更规范特别指出既有{ name, data }错误体结构要一直保留直到一次有意的破坏性 API 变更。errors.ts中的ApiNotFoundError正是一个兼容案例——它用Schema.ErrorClass显式持有name: NotFoundError与data: { message }两个字段errors.ts#L178-L193并导出notFound(message)工厂函数使处理器可以用与旧 SDK 完全一致的 body 返回 404。这直接服务于 SDK 兼容目标客户端TUI、桌面端、外部 SDK解析错误体时不感知服务端内部错误类型。处理器边界的错误映射长什么样结合 handlers/session.ts 可以看到一次性映射内联、重复映射提小助手的具体执行方式存储不存在类错误统一经SessionError.mapStorageNotFound(...)在处理器边界转成ApiNotFoundError例如get、remove、fork等端点见requireSession与fork实现会话忙busy类错误经SessionError.mapBusy(...)映射为SessionBusyError409用于shell、revert、unrevert、deleteMessage等会改动会话状态的端点handlers/session.ts#L349-L355权限响应端点用Effect.catchTag(Permission.NotFoundError, ...)把域错误Permission.NotFoundError精确转成公开的PermissionNotFoundErrorhandlers/session.ts#L362-L378——注意这是catchTag式的类型化捕获而不是宽泛的mapError一把抓对确实不可控的失败share/unshare映射为类型化的 500 而不是笼统 400源码注释解释了原因SessionShare的存储与网络失败不是客户端诱因handlers/session.ts#L254-L271。同目录下的 specs/effect/error-boundaries-plan.md 进一步给出了分层蓝图域/服务错误Schema.TaggedErrorClass不带 HTTP 状态、不带toObject()、HTTP 公开错误带httpApiStatus、CLI 渲染、会话/消息可见错误自有{ name, data }线型四者各归其位每个缝隙seam把自己拥有的形状适配出去。该计划的迁移清单也确认了 routes.md 所述规则并非空谈Storage not found、Worktree、Provider auth、Provider model not found 等域错误已移出 HTTP 中间件的特判而Session.BusyError的路由边界映射与旧NamedError中间件分支的删除仍在队列中。中间件只做横切关注与最终兜底通用中间件不应成为域错误映射器这条规则在 middleware/error.ts 中有清晰的落地errorLayer只在存在**缺陷defect**时介入——先过滤掉已经能自行响应的HttpServerResponse/HttpServerError再对真正的未预期缺陷做两件事配置类错误ConfigErrorV1.*返回 400 及其原始 body其余未知缺陷记录Cause.pretty(cause)日志后返回带ref引用的安全 500 bodymiddleware/error.ts#L7-L43。文件头注释直说了边界typed HttpApi failures on their declared error path; this boundary only replaces defect-only empty 500s。换言之类型化错误走端点声明的路中间件只擦最后的地板。OpenAPI 兼容public.ts 拥有 SDK 转换逐条收缩规范第三部分承认了一个现实public.ts 目前独自承担 SDK/OpenAPI 兼容性转换策略是收紧源 schema 一条 workaround 一条地消灭它们shrink those transforms by tightening source schemas one workaround at a time。从源码看这个兼容层当前处理的问题都很具体可选字段的 null 形态Effect 的Schema.optional在 OpenAPI 中会产出anyOf: [T, {type:null}]而旧 SDK 期望的是纯T因此matchLegacyOpenApi会对所有组件 schema 做stripOptionalNullpublic.ts#L92-L97query 参数类型对齐Effect query schema 描述的是解码后的值如数字而生成 SDK 需要公开调用形态QueryParameterSchemas按GET /session limit这样的路径 参数键逐一覆写为number/integer等public.ts#L55-L74;自引用组件修复、组件名归一、去重、遗留 schema 覆写、遗留错误 schema 注入等全部集中在同一函数内顺序执行public.ts#L82-L104。正因为存在这些后处理规则规范给出了改动 OpenAPI 可见源 schema 时的三条纪律验证生成的 SDK diff 是有意为之的除非 PR 明确要改否则保留遗留兼容优先修源 schema而不是新增后处理规则。配套的/doc文档路由也值得了解server.ts#L183-L192 中OpenApi.fromApi(PublicApi)被lazy化只有真正命中/doc才付出生成成本且由于HttpServerResponse.jsonUnsafe会立即序列化缓存的响应体让后续请求复用同一份序列化结果。/doc本身是裸HttpRouter.use路由走的是仅鉴权的 router 中间件。路由 PR 自检清单规范末尾的五条 checklist 是路由改动合入前的验收标准逐条都有对应落点稳定服务在处理器层构建期被 yield对照handlers/session.ts开头的服务声明区预期域错误在路由边界完成翻译对照SessionError.mapStorageNotFound/mapBusy、catchTag捕获;端点/分组的错误 schema 准确描述公开 body 与状态码对照 groups/session.ts 各端点的error:声明中间件没有新增任何域特定的 name 判断对照 middleware/error.ts 只处理 defect 与配置错误的现状裸路由仅在 HttpApi 是错误抽象时才使用对照server.ts路由树注释中唯一保留的uiRoute。关键文件速查文件作用specs/effect/routes.md本文的主体路由编写规范与 PR 清单src/server/routes/instance/httpapi/api.ts组合RootHttpApi/InstanceHttpApi/OpenCodeHttpApi挂载SchemaErrorMiddleware、Authorizationsrc/server/routes/instance/httpapi/groups/session.tssession 端点契约声明、SessionPaths路径表、OpenAPI 注解与分组中间件src/server/routes/instance/httpapi/handlers/session.tssession 处理器实现服务 yield、handleRaw、流式响应、错误边界映射src/server/routes/instance/httpapi/errors.ts公开 HTTP 错误 schema 契约含httpApiStatus与遗留{ name, data }形态src/server/routes/instance/httpapi/middleware/error.ts最终未知缺陷兜底中间件src/server/routes/instance/httpapi/public.tsSDK/OpenAPI 兼容性后处理逐条待收缩的 workaround 集合src/server/routes/instance/httpapi/server.ts路由树组装、/doc与 catch-all UI 路由、层组装顺序specs/effect/error-boundaries-plan.md错误边界分层蓝图与NamedError移除迁移队列适用前提说明以上模式与文件路径均以当前仓库packages/opencode的服务进程路由实现为准groups/中标题含 Experimental HttpApi 的分组如 session 组的 OpenAPI 描述表明部分实例路由仍处于向 HttpApi 迁移过程中的实验表面具体端点状态以groups/内各文件的最新声明为准。【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考