226 lines
14 KiB
Markdown
226 lines
14 KiB
Markdown
# 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.ts` only composes groups, instance lookup, observability, and the web handler bridge.
|
|
- Hono route implementations are deleted once their `HttpApi` replacements 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 `HttpApi` if `HttpApi` is the wrong abstraction.
|
|
|
|
## Current State
|
|
|
|
- `OPENCODE_EXPERIMENTAL_HTTPAPI` gates the bridge. Default behavior still uses Hono.
|
|
- The bridge mounts selected paths in `server/routes/instance/index.ts` before legacy Hono routes.
|
|
- Legacy Hono routes remain for default behavior and for `hono-openapi` SDK generation.
|
|
- `HttpApi` auth is independent of Hono auth.
|
|
- `Authorization` is attached in each route module, not centrally wrapped in `server.ts`.
|
|
- Auth supports Basic auth and the legacy `auth_token` query parameter through `HttpApiSecurity.apiKey`.
|
|
- Instance context is provided by `httpapi/server.ts` using `directory`, `workspace`, and `x-opencode-directory`.
|
|
- `Observability.layer` is provided in the Effect route layer and deduplicated through the shared `memoMap`.
|
|
|
|
## 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 `.zod` only 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 `describeRoute` entry.
|
|
|
|
## Route Slice Checklist
|
|
|
|
Use this checklist for each small HttpApi migration PR:
|
|
|
|
1. Read the legacy Hono route and copy behavior exactly, including default values, headers, operation IDs, response schemas, and status codes.
|
|
2. Put the new `HttpApiGroup`, route paths, DTO schemas, and handlers in `src/server/routes/instance/httpapi/*`.
|
|
3. Mount the new paths in `src/server/routes/instance/index.ts` only inside the `OPENCODE_EXPERIMENTAL_HTTPAPI` block.
|
|
4. Use `InstanceState.context` / `InstanceState.directory` inside HttpApi handlers instead of `Instance.directory`, `Instance.worktree`, or `Instance.project` ALS globals.
|
|
5. Reuse existing services directly. If a service returns plain objects, use `Schema.Struct`; use `Schema.Class` only when handlers return actual class instances.
|
|
6. Keep legacy Hono routes and `.zod` compatibility in place for SDK/OpenAPI generation.
|
|
7. Add tests that hit the Hono-mounted bridge via `InstanceRoutes`, not only the raw `HttpApi` web handler, when the route depends on auth or instance context.
|
|
8. Run `bun typecheck` from `packages/opencode`, relevant `bun run test:ci ...` tests from `packages/opencode`, and `./packages/sdk/js/script/build.ts` from the repo root.
|
|
|
|
## Hono Deletion Checklist
|
|
|
|
Use this checklist before deleting any Hono route implementation. A route being `bridged` is not enough.
|
|
|
|
1. `HttpApi` parity is complete for the route path, method, auth behavior, query parameters, request body, response status, response headers, and error status.
|
|
2. The route is mounted by default, not only behind `OPENCODE_EXPERIMENTAL_HTTPAPI`.
|
|
3. If a fallback flag exists, tests cover both the default `HttpApi` path and the fallback Hono path until the fallback is removed.
|
|
4. OpenAPI generation uses the Effect `HttpApi` route as the source for that path.
|
|
5. Generated SDK output is unchanged from the Hono-generated contract, or the SDK diff is intentionally reviewed and accepted.
|
|
6. The legacy Hono `describeRoute`, validator, and handler for that path are removed.
|
|
7. Any duplicate Zod-only DTOs are deleted or kept only as `.zod` compatibility on the canonical Effect Schema.
|
|
8. Bridge tests exist for auth, instance selection, success response, and route-specific side effects.
|
|
9. 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.
|
|
10. Streaming, SSE, websocket, and UI bridge routes have a specific non-Hono replacement plan. Do not force them through `HttpApi` if 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 /worktree` only if the handler uses `InstanceState.context` instead of `Instance.project`.
|
|
- Defer `POST /console/switch`, worktree create/remove/reset, and `GET /session` to 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.Class` only when the handler returns real class instances or the constructor requirement is intentional.
|
|
- Keep nested anonymous shapes as `Schema.Struct` unless 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 `HttpApi` bridge, not just `HttpApiBuilder.layer` directly.
|
|
- Cover auth disabled, Basic auth success, `auth_token` success, missing credentials, and bad credentials.
|
|
- Cover `directory` and `x-opencode-directory` instance selection.
|
|
- Verify generated SDK output remains unchanged for non-SDK work.
|
|
- Fix or remove any implemented-but-unmounted `HttpApi` groups.
|
|
|
|
### 2. Complete The Inventory
|
|
|
|
Create a route inventory from the actual Hono registrations and classify each route.
|
|
|
|
Statuses:
|
|
|
|
- `bridged`: served through the `HttpApi` bridge when the flag is on.
|
|
- `implemented`: `HttpApi` group 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 than `HttpApi`.
|
|
|
|
### 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:
|
|
|
|
- `session`
|
|
- `sync`
|
|
- 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 `HttpApi` for ported routes.
|
|
- Keep operation IDs, schemas, status codes, and SDK type names stable unless the change is intentional.
|
|
- Compare generated SDK output against `dev` for 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:
|
|
|
|
- `HttpApi` route 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 from `instance/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`: SSE
|
|
- `pty`: websocket
|
|
- `tui`: UI/control bridge behavior
|
|
- streaming `session` endpoints
|
|
|
|
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` partial | status only |
|
|
| `workspace` | `bridged` | list, get, enter |
|
|
| 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 |
|
|
|
|
## Remaining PR Plan
|
|
|
|
Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays reviewable.
|
|
|
|
1. Bridge `PATCH /project/:projectID`.
|
|
2. Bridge MCP add/connect/disconnect routes.
|
|
3. Bridge MCP OAuth routes: start, callback, authenticate, remove.
|
|
4. Bridge experimental console switch and tool list routes.
|
|
5. Bridge experimental global session list.
|
|
6. Bridge sync start/replay/history routes.
|
|
7. Bridge session read routes: list, status, get, children, todo, diff, messages.
|
|
8. Bridge session lifecycle mutation routes: create, delete, update, fork, abort.
|
|
9. Bridge session share/summary/message/part mutation routes.
|
|
10. Replace event SSE with non-Hono Effect HTTP.
|
|
11. Replace pty websocket/control routes with non-Hono Effect HTTP.
|
|
12. Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer.
|
|
13. Switch OpenAPI/SDK generation to Effect routes and compare SDK output.
|
|
14. Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files.
|
|
|
|
## Checklist
|
|
|
|
- [x] Add first `HttpApi` JSON route slices.
|
|
- [x] Bridge selected `HttpApi` routes into Hono behind `OPENCODE_EXPERIMENTAL_HTTPAPI`.
|
|
- [x] Reuse existing Effect services in handlers.
|
|
- [x] Provide auth, instance lookup, and observability in the Effect route layer.
|
|
- [x] Attach auth middleware in route modules.
|
|
- [x] Support `auth_token` as a query security scheme.
|
|
- [x] Add bridge-level auth and instance tests.
|
|
- [ ] Complete exact Hono route inventory.
|
|
- [x] Resolve implemented-but-unmounted route groups.
|
|
- [x] 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.
|