20 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.
Hono Deletion Checklist
Use this checklist before deleting any Hono route implementation. A route being bridged is not enough.
HttpApiparity is complete for the route path, method, auth behavior, query parameters, request body, response status, response headers, and error status.- The route is mounted by default, not only behind
OPENCODE_EXPERIMENTAL_HTTPAPI. - If a fallback flag exists, tests cover both the default
HttpApipath and the fallback Hono path until the fallback is removed. - OpenAPI generation uses the Effect
HttpApiroute as the source for that path. - Generated SDK output is unchanged from the Hono-generated contract, or the SDK diff is intentionally reviewed and accepted.
- The legacy Hono
describeRoute, validator, and handler for that path are removed. - Any duplicate Zod-only DTOs are deleted or kept only as
.zodcompatibility on the canonical Effect Schema. - Bridge tests exist for auth, instance selection, success response, and route-specific side effects.
- Mutation routes prove persisted side effects and cleanup behavior in tests. If the mutation disposes/reloads the active instance, disposal happens through an explicit post-response lifecycle hook rather than inline handler teardown.
- Streaming, SSE, websocket, and UI bridge routes have a specific non-Hono replacement plan. Do not force them through
HttpApiif raw Effect HTTP is a better fit.
Hono can be removed from the instance server only after all mounted Hono route groups meet this checklist and server/routes/instance/index.ts no longer depends on Hono routing for default behavior.
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: 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 |
read, providers, update |
project |
bridged |
list, current, git init, update |
file |
bridged partial |
find text/file/symbol, list/content/status |
mcp |
bridged |
status, add, OAuth, connect/disconnect |
workspace |
bridged partial |
adaptor/list/status; create/remove/session-restore remain |
| top-level instance routes | bridged |
path, vcs, command, agent, skill, lsp, formatter, dispose |
| experimental JSON routes | bridged partial |
console reads, tool ids, worktree list/mutations, resource list; global session list remains 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 |
Full Route Checklist
This checklist tracks bridge parity only. Checked routes are available through the experimental HttpApi bridge; Hono deletion is tracked separately by the deletion checklist above.
Top-Level Instance Routes
POST /instance/dispose- dispose active instance after response.GET /path- current directory and worktree paths.GET /vcs- current VCS status.GET /vcs/diff- VCS diff summary.GET /command- command catalog.GET /agent- agent catalog.GET /skill- skill catalog.GET /lsp- LSP status.GET /formatter- formatter status.
Config Routes
GET /config- read config.PATCH /config- update config and dispose active instance after response.GET /config/providers- config provider summary.
Project Routes
GET /project- list projects.GET /project/current- current project.POST /project/git/init- initialize git and reload active instance after response.PATCH /project/:projectID- update project metadata.
Provider Routes
GET /provider- list providers.GET /provider/auth- list provider auth methods.POST /provider/:providerID/oauth/authorize- start provider OAuth.POST /provider/:providerID/oauth/callback- finish provider OAuth.
Question Routes
GET /question- list questions.POST /question/:requestID/reply- reply to question.POST /question/:requestID/reject- reject question.
Permission Routes
GET /permission- list permission requests.POST /permission/:requestID/reply- reply to permission request.
File Routes
GET /find- text search.GET /find/file- file search.GET /find/symbol- symbol search.GET /file- list directory entries.GET /file/content- read file content.GET /file/status- file status.
MCP Routes
GET /mcp- MCP status.POST /mcp- add MCP server at runtime.POST /mcp/:name/auth- start MCP OAuth.POST /mcp/:name/auth/callback- finish MCP OAuth callback.POST /mcp/:name/auth/authenticate- run MCP OAuth authenticate flow.DELETE /mcp/:name/auth- remove MCP OAuth credentials.POST /mcp/:name/connect- connect MCP server.POST /mcp/:name/disconnect- disconnect MCP server.
Experimental Routes
GET /experimental/console- active Console provider metadata.GET /experimental/console/orgs- switchable Console orgs.POST /experimental/console/switch- switch active Console org.GET /experimental/tool/ids- tool IDs.GET /experimental/tool- tools for provider/model.GET /experimental/worktree- list worktrees.POST /experimental/worktree- create worktree.DELETE /experimental/worktree- remove worktree.POST /experimental/worktree/reset- reset worktree.GET /experimental/session- global session list.GET /experimental/resource- MCP resources.
Workspace Routes
GET /experimental/workspace/adaptor- list workspace adaptors.POST /experimental/workspace- create workspace.GET /experimental/workspace- list workspaces.GET /experimental/workspace/status- workspace status.DELETE /experimental/workspace/:id- remove workspace.POST /experimental/workspace/:id/session-restore- restore session into workspace.
Sync Routes
POST /sync/start- start workspace sync.POST /sync/replay- replay sync events.POST /sync/history- list sync event history.
Session Routes
GET /session- list sessions.GET /session/status- session status map.GET /session/:sessionID- get session.GET /session/:sessionID/children- get child sessions.GET /session/:sessionID/todo- get session todos.POST /session- create session.DELETE /session/:sessionID- delete session.PATCH /session/:sessionID- update session metadata.POST /session/:sessionID/init- run project init command.POST /session/:sessionID/fork- fork session.POST /session/:sessionID/abort- abort session.POST /session/:sessionID/share- share session.GET /session/:sessionID/diff- session diff.DELETE /session/:sessionID/share- unshare session.POST /session/:sessionID/summarize- summarize session.GET /session/:sessionID/message- list session messages.GET /session/:sessionID/message/:messageID- get message.DELETE /session/:sessionID/message/:messageID- delete message.DELETE /session/:sessionID/message/:messageID/part/:partID- delete part.PATCH /session/:sessionID/message/:messageID/part/:partID- update part.POST /session/:sessionID/message- prompt with streaming response.POST /session/:sessionID/prompt_async- async prompt.POST /session/:sessionID/command- run command.POST /session/:sessionID/shell- run shell command.POST /session/:sessionID/revert- revert message.POST /session/:sessionID/unrevert- restore reverted messages.POST /session/:sessionID/permissions/:permissionID- deprecated permission response route.
Event Routes
GET /event- SSE event stream; replace with raw Effect HTTP, notHttpApi.
PTY Routes
GET /pty- list PTY sessions.POST /pty- create PTY session.GET /pty/:ptyID- get PTY session.PUT /pty/:ptyID- update PTY session.DELETE /pty/:ptyID- remove PTY session.GET /pty/:ptyID/connect- PTY websocket; replace with raw Effect HTTP/websocket support.
TUI Routes
POST /tui/append-prompt- append prompt.POST /tui/open-help- open help.POST /tui/open-sessions- open sessions.POST /tui/open-themes- open themes.POST /tui/open-models- open models.POST /tui/submit-prompt- submit prompt.POST /tui/clear-prompt- clear prompt.POST /tui/execute-command- execute command.POST /tui/show-toast- show toast.POST /tui/publish- publish TUI event.POST /tui/select-session- select session.GET /tui/control/next- get next TUI request.POST /tui/control/response- submit TUI control response.
Remaining PR Plan
Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays reviewable.
- Bridge
PATCH /project/:projectID. - Bridge MCP add/connect/disconnect routes.
- Bridge MCP OAuth routes: start, callback, authenticate, remove.
- Bridge experimental console switch and tool list routes.
- Bridge experimental global session list.
- Bridge workspace create/remove/session-restore routes.
- Bridge sync start/replay/history routes.
- Bridge session read routes: list, status, get, children, todo, diff, messages.
- Bridge session lifecycle mutation routes: create, delete, update, fork, abort.
- Bridge session share/summary/message/part mutation routes.
- Replace event SSE with non-Hono Effect HTTP.
- Replace pty websocket/control routes with non-Hono Effect HTTP.
- Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer.
- Switch OpenAPI/SDK generation to Effect routes and compare SDK output.
- Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files.
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.