11 KiB
HttpApi migration
Plan for replacing instance Hono route implementations with Effect HttpApi while preserving behavior, OpenAPI, and SDK output during the transition.
End State
- JSON route contracts and handlers live in
src/server/routes/instance/httpapi/*. - Route modules own their
HttpApiGroup, schemas, handlers, and route-level middleware. httpapi/server.tsonly composes groups, instance lookup, observability, and the web handler bridge.- Hono route implementations are deleted once their
HttpApireplacements are default, tested, and represented in the SDK/OpenAPI pipeline. - Streaming, SSE, and websocket routes move later through Effect HTTP primitives or another explicit replacement plan; they do not need to fit
HttpApiifHttpApiis the wrong abstraction.
Current State
OPENCODE_EXPERIMENTAL_HTTPAPIgates the bridge. Default behavior still uses Hono.- The bridge mounts selected paths in
server/routes/instance/index.tsbefore legacy Hono routes. - Legacy Hono routes remain for default behavior and for
hono-openapiSDK generation. HttpApiauth is independent of Hono auth.Authorizationis attached in each route module, not centrally wrapped inserver.ts.- Auth supports Basic auth and the legacy
auth_tokenquery parameter throughHttpApiSecurity.apiKey. - Instance context is provided by
httpapi/server.tsusingdirectory,workspace, andx-opencode-directory. Observability.layeris provided in the Effect route layer and deduplicated through the sharedmemoMap.
Migration Rules
- Preserve runtime behavior first. Semantic changes, new error behavior, or route shape changes need separate PRs.
- Migrate one route group, or one coherent subset of a route group, at a time.
- Reuse existing services. Do not re-architect service logic during HTTP boundary migration.
- Effect Schema owns route DTOs. Keep
.zodonly as compatibility for remaining Hono/OpenAPI surfaces. - Regenerate the SDK after schema or OpenAPI-affecting changes and verify the diff is expected.
- Do not delete a Hono route until the SDK/OpenAPI pipeline no longer depends on its Hono
describeRouteentry.
Route Slice Checklist
Use this checklist for each small HttpApi migration PR:
- Read the legacy Hono route and copy behavior exactly, including default values, headers, operation IDs, response schemas, and status codes.
- Put the new
HttpApiGroup, route paths, DTO schemas, and handlers insrc/server/routes/instance/httpapi/*. - Mount the new paths in
src/server/routes/instance/index.tsonly inside theOPENCODE_EXPERIMENTAL_HTTPAPIblock. - Use
InstanceState.context/InstanceState.directoryinside HttpApi handlers instead ofInstance.directory,Instance.worktree, orInstance.projectALS globals. - Reuse existing services directly. If a service returns plain objects, use
Schema.Struct; useSchema.Classonly when handlers return actual class instances. - Keep legacy Hono routes and
.zodcompatibility in place for SDK/OpenAPI generation. - Add tests that hit the Hono-mounted bridge via
InstanceRoutes, not only the rawHttpApiweb handler, when the route depends on auth or instance context. - Run
bun typecheckfrompackages/opencode, relevantbun run test:ci ...tests frompackages/opencode, and./packages/sdk/js/script/build.tsfrom the repo root.
Experimental Read Slice Guidance
For the experimental route group, port read-only JSON routes before mutations:
- Good first batch:
GET /console,GET /console/orgs,GET /tool/ids,GET /resource. - Consider
GET /worktreeonly if the handler usesInstanceState.contextinstead ofInstance.project. - Defer
POST /console/switch, worktree create/remove/reset, andGET /sessionto separate PRs because they mutate state or have broader pagination/session behavior. - Preserve response headers such as pagination cursors if a route is ported.
- If SDK generation changes, explain whether it is a semantic contract change or a generator-equivalent type normalization.
Schema Notes
- Use
Schema.Struct(...).annotate({ identifier })for named OpenAPI refs when handlers return plain objects. - Use
Schema.Classonly when the handler returns real class instances or the constructor requirement is intentional. - Keep nested anonymous shapes as
Schema.Structunless a named SDK type is useful. - Avoid parallel hand-written Zod and Effect definitions for the same route boundary.
Phases
1. Stabilize The Bridge
Before porting more routes, cover the bridge behavior that every route depends on.
- Add tests that hit the Hono-mounted
HttpApibridge, not justHttpApiBuilder.layerdirectly. - Cover auth disabled, Basic auth success,
auth_tokensuccess, missing credentials, and bad credentials. - Cover
directoryandx-opencode-directoryinstance selection. - Verify generated SDK output remains unchanged for non-SDK work.
- Fix or remove any implemented-but-unmounted
HttpApigroups.
2. Complete The Inventory
Create a route inventory from the actual Hono registrations and classify each route.
Statuses:
bridged: served through theHttpApibridge when the flag is on.implemented:HttpApigroup exists but is not mounted through Hono.next: good JSON candidate for near-term porting.later: portable, but needs schema/service cleanup first.special: SSE, websocket, streaming, or UI bridge behavior that likely needs raw Effect HTTP rather thanHttpApi.
3. Finish JSON Route Parity
Port remaining JSON routes in small batches.
Good near-term candidates:
- top-level reads:
GET /path,GET /vcs,GET /vcs/diff,GET /command,GET /agent,GET /skill,GET /lsp,GET /formatter - simple mutations:
POST /instance/dispose - experimental JSON reads: console, tool, worktree list, resource list
- deferred JSON mutations:
PATCH /config, project git init, workspace/worktree create/remove/reset, file search, MCP auth flows
Keep large or stateful groups for later:
sessionsync- process-level experimental routes
4. Move OpenAPI And SDK Generation
Hono routes cannot be deleted while hono-openapi is the source of SDK generation.
Required before route deletion:
- Generate the public OpenAPI surface from Effect
HttpApifor ported routes. - Keep operation IDs, schemas, status codes, and SDK type names stable unless the change is intentional.
- Compare generated SDK output against
devfor every route group deletion. - Remove Hono OpenAPI stubs only after Effect OpenAPI is the SDK source for those paths.
5. Make HttpApi Default For JSON Routes
After JSON parity and SDK generation are covered:
- Flip the bridge default for ported JSON routes.
- Keep a short-lived fallback flag for the old Hono implementation.
- Run the same tests against both the default and fallback path during rollout.
- Stop adding new Hono handlers for JSON routes once the default flips.
6. Delete Hono Route Implementations
Delete Hono routes group-by-group after each group meets the deletion criteria.
Deletion criteria:
HttpApiroute is mounted by default.- Behavior is covered by bridge-level tests.
- OpenAPI/SDK generation comes from Effect for that path.
- SDK diff is zero or explicitly accepted.
- Legacy Hono route is no longer needed as a fallback.
After deleting a group:
- Remove its Hono route file or dead endpoints.
- Remove its
.route(...)registration frominstance/index.ts. - Remove duplicate Zod-only route DTOs if Effect Schema now owns the type.
- Regenerate SDK and verify output.
7. Replace Special Routes
Special routes need explicit designs before Hono can disappear completely.
event: SSEpty: websockettui: UI/control bridge behavior- streaming
sessionendpoints
Use raw Effect HTTP routes where HttpApi does not fit. The goal is deleting Hono implementations, not forcing every transport shape through HttpApi.
Current Route Status
| Area | Status | Notes |
|---|---|---|
question |
bridged |
GET /question, reply, reject |
permission |
bridged |
list and reply |
provider |
bridged |
list, auth, OAuth authorize/callback |
config |
bridged partial |
reads only; mutation remains Hono |
project |
bridged partial |
reads only; git-init remains Hono |
file |
bridged partial |
find text/file/symbol, list/content/status |
mcp |
bridged partial |
status only |
workspace |
bridged |
list, get, enter |
| top-level instance reads | bridged |
path, vcs, command, agent, skill, lsp, formatter |
| experimental JSON routes | bridged partial |
console reads, tool ids, resource list; worktree and global session list remain later |
session |
later/special |
large stateful surface plus streaming |
sync |
later |
process/control side effects |
event |
special |
SSE |
pty |
special |
websocket |
tui |
special |
UI bridge |
Next PRs
- Produce a generated route inventory from Hono registrations and update
Current Route Statuswith exact paths. - Start the Effect OpenAPI/SDK generation path for already-bridged routes.
Checklist
- Add first
HttpApiJSON route slices. - Bridge selected
HttpApiroutes into Hono behindOPENCODE_EXPERIMENTAL_HTTPAPI. - Reuse existing Effect services in handlers.
- Provide auth, instance lookup, and observability in the Effect route layer.
- Attach auth middleware in route modules.
- Support
auth_tokenas a query security scheme. - Add bridge-level auth and instance tests.
- Complete exact Hono route inventory.
- Resolve implemented-but-unmounted route groups.
- Port remaining top-level JSON reads.
- Generate SDK/OpenAPI from Effect routes.
- Flip ported JSON routes to default-on with fallback.
- Delete replaced Hono route implementations.
- Replace SSE/websocket/streaming Hono routes with non-Hono implementations.