docs(effect): add generated http route inventory

This commit is contained in:
Kit Langton 2026-04-24 18:14:53 -04:00
parent 5cd178ba70
commit 2b028287e2
3 changed files with 286 additions and 23 deletions

View file

@ -0,0 +1,138 @@
#!/usr/bin/env bun
import type { Hono } from "hono"
import type { UpgradeWebSocket } from "hono/ws"
import { WorkspacePaths } from "../src/server/routes/instance/httpapi/workspace"
import { FilePaths } from "../src/server/routes/instance/httpapi/file"
import { McpPaths } from "../src/server/routes/instance/httpapi/mcp"
import { Flag } from "../src/flag/flag"
import { ControlPlaneRoutes } from "../src/server/routes/control"
import { WorkspaceRoutes } from "../src/server/routes/control/workspace"
import { InstanceRoutes } from "../src/server/routes/instance"
import { UIRoutes } from "../src/server/routes/ui"
type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "ALL"
interface Route {
surface: string
method: Method
path: string
status: string
}
const methodOrder = new Map<Method, number>([
["GET", 0],
["POST", 1],
["PUT", 2],
["PATCH", 3],
["DELETE", 4],
["ALL", 5],
])
const bridged = new Set([
key("GET", "/question"),
key("POST", "/question/:requestID/reply"),
key("POST", "/question/:requestID/reject"),
key("GET", "/permission"),
key("POST", "/permission/:requestID/reply"),
key("GET", "/config"),
key("GET", "/config/providers"),
key("GET", "/provider"),
key("GET", "/provider/auth"),
key("POST", "/provider/:providerID/oauth/authorize"),
key("POST", "/provider/:providerID/oauth/callback"),
key("GET", "/project"),
key("GET", "/project/current"),
key("GET", FilePaths.list),
key("GET", FilePaths.content),
key("GET", FilePaths.status),
key("GET", McpPaths.status),
...Object.values(WorkspacePaths).map((path) => key("GET", path)),
])
const topLevelNext = new Set([
key("GET", "/path"),
key("GET", "/vcs"),
key("GET", "/vcs/diff"),
key("GET", "/command"),
key("GET", "/agent"),
key("GET", "/skill"),
key("GET", "/lsp"),
key("GET", "/formatter"),
])
function key(method: string, path: string) {
return `${method} ${path}`
}
function normalize(prefix: string, route: string) {
if (!prefix) return route
if (route === "/") return prefix
return `${prefix}${route}`.replaceAll(/\/+/g, "/")
}
function routes(surface: string, app: Hono, prefix = "") {
const seen = new Map<string, Route>()
for (const route of app.routes as Array<{ method: Method; path: string }>) {
if (surface !== "ui" && route.method === "ALL" && route.path === "/*") continue
const path = normalize(prefix, route.path)
seen.set(key(route.method, path), {
surface,
method: route.method,
path,
status: classify(route.method, path, surface),
})
}
return [...seen.values()].toSorted(compare)
}
function compare(a: Route, b: Route) {
return (
a.surface.localeCompare(b.surface) ||
a.path.localeCompare(b.path) ||
(methodOrder.get(a.method) ?? 99) - (methodOrder.get(b.method) ?? 99)
)
}
function classify(method: Method, path: string, surface: string) {
if (bridged.has(key(method, path))) return "bridged"
if (topLevelNext.has(key(method, path))) return "next"
if (surface === "ui") return "special"
if (path === "/event") return "special"
if (path.startsWith("/pty") || path.startsWith("/tui")) return "special"
if (path.startsWith("/session") || path.startsWith("/sync")) return "later"
if (path.startsWith("/experimental")) return method === "GET" ? "next" : "later"
if (path.startsWith("/mcp")) return "later"
if (path === "/instance/dispose") return "next"
return "later"
}
function table(items: Route[]) {
return [
"| Surface | Method | Path | Status |",
"| --- | --- | --- | --- |",
...items.map((item) => `| ${item.surface} | \`${item.method}\` | \`${item.path}\` | \`${item.status}\` |`),
].join("\n")
}
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = false
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
const inventory = [
...routes("control", ControlPlaneRoutes()),
...routes("workspace", WorkspaceRoutes(), "/experimental/workspace"),
...routes("instance", InstanceRoutes(websocket)),
...routes("ui", UIRoutes()),
].toSorted(compare)
await Bun.write(
new URL("../specs/effect/http-route-inventory.md", import.meta.url),
`# Http Route Inventory
Generated from Hono route registrations by \`packages/opencode/script/http-route-inventory.ts\`.
Status meanings are defined in \`specs/effect/http-api.md\`.
${table(inventory)}
`,
)

View file

@ -53,6 +53,12 @@ Before porting more routes, cover the bridge behavior that every route depends o
Create a route inventory from the actual Hono registrations and classify each route.
The generated inventory lives in `specs/effect/http-route-inventory.md` and is produced by:
```bash
bun run script/http-route-inventory.ts
```
Statuses:
- `bridged`: served through the `HttpApi` bridge when the flag is on.
@ -130,31 +136,31 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
## 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 | list/content/status only |
| `mcp` | `bridged` partial | status only |
| `workspace` | `implemented` | `HttpApi` group exists, but bridge mounting needs verification |
| top-level instance reads | `next` | path, vcs, command, agent, skill, lsp, formatter |
| experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list |
| `session` | `later/special` | large stateful surface plus streaming |
| `sync` | `later` | process/control side effects |
| `event` | `special` | SSE |
| `pty` | `special` | websocket |
| `tui` | `special` | UI bridge |
| 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; `PATCH /config` remains Hono |
| `project` | `bridged` partial | reads only; update and git-init remain Hono |
| `file` | `bridged` partial | `GET /file`, `GET /file/content`, `GET /file/status` |
| `mcp` | `bridged` partial | `GET /mcp` only |
| `workspace` | `bridged` partial | reads only; create/remove/session-restore remain Hono |
| top-level instance reads | `next` | `GET /path`, `/vcs`, `/vcs/diff`, `/command`, `/agent`, `/skill`, etc. |
| experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list |
| `session` | `later/special` | large stateful surface plus streaming |
| `sync` | `later` | process/control side effects |
| `event` | `special` | SSE |
| `pty` | `special` | websocket |
| `tui` | `special` | UI bridge |
See `specs/effect/http-route-inventory.md` for exact paths and per-route status.
## Next PRs
1. Add bridge-level auth and instance-context tests for the current `HttpApi` bridge.
2. Produce a generated route inventory from Hono registrations and update `Current Route Status` with exact paths.
3. Fix the `workspace` status: mount it if it should be reachable, or remove it from the composed `HttpApi` layer.
4. Port the top-level JSON reads.
5. Start the Effect OpenAPI/SDK generation path for already-bridged routes.
2. Port the top-level JSON reads.
3. Start the Effect OpenAPI/SDK generation path for already-bridged routes.
## Checklist
@ -165,8 +171,8 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
- [x] Attach auth middleware in route modules.
- [x] Support `auth_token` as a query security scheme.
- [ ] Add bridge-level auth and instance tests.
- [ ] Complete exact Hono route inventory.
- [ ] Resolve implemented-but-unmounted route groups.
- [x] Complete exact Hono route inventory.
- [x] Resolve implemented-but-unmounted route groups.
- [ ] Port remaining JSON routes.
- [ ] Generate SDK/OpenAPI from Effect routes.
- [ ] Flip ported JSON routes to default-on with fallback.

View file

@ -0,0 +1,119 @@
# Http Route Inventory
Generated from Hono route registrations by `packages/opencode/script/http-route-inventory.ts`.
Status meanings are defined in `specs/effect/http-api.md`.
| Surface | Method | Path | Status |
| --- | --- | --- | --- |
| control | `PUT` | `/auth/:providerID` | `later` |
| control | `DELETE` | `/auth/:providerID` | `later` |
| control | `GET` | `/doc` | `later` |
| control | `POST` | `/log` | `later` |
| instance | `GET` | `/agent` | `next` |
| instance | `GET` | `/command` | `next` |
| instance | `GET` | `/config` | `bridged` |
| instance | `PATCH` | `/config` | `later` |
| instance | `GET` | `/config/providers` | `bridged` |
| instance | `GET` | `/event` | `special` |
| instance | `GET` | `/experimental/console` | `next` |
| instance | `GET` | `/experimental/console/orgs` | `next` |
| instance | `POST` | `/experimental/console/switch` | `later` |
| instance | `GET` | `/experimental/resource` | `next` |
| instance | `GET` | `/experimental/session` | `next` |
| instance | `GET` | `/experimental/tool` | `next` |
| instance | `GET` | `/experimental/tool/ids` | `next` |
| instance | `GET` | `/experimental/worktree` | `next` |
| instance | `POST` | `/experimental/worktree` | `later` |
| instance | `DELETE` | `/experimental/worktree` | `later` |
| instance | `POST` | `/experimental/worktree/reset` | `later` |
| instance | `GET` | `/file` | `bridged` |
| instance | `GET` | `/file/content` | `bridged` |
| instance | `GET` | `/file/status` | `bridged` |
| instance | `GET` | `/find` | `later` |
| instance | `GET` | `/find/file` | `later` |
| instance | `GET` | `/find/symbol` | `later` |
| instance | `GET` | `/formatter` | `next` |
| instance | `POST` | `/instance/dispose` | `next` |
| instance | `GET` | `/lsp` | `next` |
| instance | `GET` | `/mcp` | `bridged` |
| instance | `POST` | `/mcp` | `later` |
| instance | `POST` | `/mcp/:name/auth` | `later` |
| instance | `DELETE` | `/mcp/:name/auth` | `later` |
| instance | `POST` | `/mcp/:name/auth/authenticate` | `later` |
| instance | `POST` | `/mcp/:name/auth/callback` | `later` |
| instance | `POST` | `/mcp/:name/connect` | `later` |
| instance | `POST` | `/mcp/:name/disconnect` | `later` |
| instance | `GET` | `/path` | `next` |
| instance | `GET` | `/permission` | `bridged` |
| instance | `POST` | `/permission/:requestID/reply` | `bridged` |
| instance | `GET` | `/project` | `bridged` |
| instance | `PATCH` | `/project/:projectID` | `later` |
| instance | `GET` | `/project/current` | `bridged` |
| instance | `POST` | `/project/git/init` | `later` |
| instance | `GET` | `/provider` | `bridged` |
| instance | `POST` | `/provider/:providerID/oauth/authorize` | `bridged` |
| instance | `POST` | `/provider/:providerID/oauth/callback` | `bridged` |
| instance | `GET` | `/provider/auth` | `bridged` |
| instance | `GET` | `/pty` | `special` |
| instance | `POST` | `/pty` | `special` |
| instance | `GET` | `/pty/:ptyID` | `special` |
| instance | `PUT` | `/pty/:ptyID` | `special` |
| instance | `DELETE` | `/pty/:ptyID` | `special` |
| instance | `GET` | `/pty/:ptyID/connect` | `special` |
| instance | `GET` | `/question` | `bridged` |
| instance | `POST` | `/question/:requestID/reject` | `bridged` |
| instance | `POST` | `/question/:requestID/reply` | `bridged` |
| instance | `GET` | `/session` | `later` |
| instance | `POST` | `/session` | `later` |
| instance | `GET` | `/session/:sessionID` | `later` |
| instance | `PATCH` | `/session/:sessionID` | `later` |
| instance | `DELETE` | `/session/:sessionID` | `later` |
| instance | `POST` | `/session/:sessionID/abort` | `later` |
| instance | `GET` | `/session/:sessionID/children` | `later` |
| instance | `POST` | `/session/:sessionID/command` | `later` |
| instance | `GET` | `/session/:sessionID/diff` | `later` |
| instance | `POST` | `/session/:sessionID/fork` | `later` |
| instance | `POST` | `/session/:sessionID/init` | `later` |
| instance | `GET` | `/session/:sessionID/message` | `later` |
| instance | `POST` | `/session/:sessionID/message` | `later` |
| instance | `GET` | `/session/:sessionID/message/:messageID` | `later` |
| instance | `DELETE` | `/session/:sessionID/message/:messageID` | `later` |
| instance | `PATCH` | `/session/:sessionID/message/:messageID/part/:partID` | `later` |
| instance | `DELETE` | `/session/:sessionID/message/:messageID/part/:partID` | `later` |
| instance | `POST` | `/session/:sessionID/permissions/:permissionID` | `later` |
| instance | `POST` | `/session/:sessionID/prompt_async` | `later` |
| instance | `POST` | `/session/:sessionID/revert` | `later` |
| instance | `POST` | `/session/:sessionID/share` | `later` |
| instance | `DELETE` | `/session/:sessionID/share` | `later` |
| instance | `POST` | `/session/:sessionID/shell` | `later` |
| instance | `POST` | `/session/:sessionID/summarize` | `later` |
| instance | `GET` | `/session/:sessionID/todo` | `later` |
| instance | `POST` | `/session/:sessionID/unrevert` | `later` |
| instance | `GET` | `/session/status` | `later` |
| instance | `GET` | `/skill` | `next` |
| instance | `POST` | `/sync/history` | `later` |
| instance | `POST` | `/sync/replay` | `later` |
| instance | `POST` | `/sync/start` | `later` |
| instance | `POST` | `/tui/append-prompt` | `special` |
| instance | `POST` | `/tui/clear-prompt` | `special` |
| instance | `GET` | `/tui/control/next` | `special` |
| instance | `POST` | `/tui/control/response` | `special` |
| instance | `POST` | `/tui/execute-command` | `special` |
| instance | `POST` | `/tui/open-help` | `special` |
| instance | `POST` | `/tui/open-models` | `special` |
| instance | `POST` | `/tui/open-sessions` | `special` |
| instance | `POST` | `/tui/open-themes` | `special` |
| instance | `POST` | `/tui/publish` | `special` |
| instance | `POST` | `/tui/select-session` | `special` |
| instance | `POST` | `/tui/show-toast` | `special` |
| instance | `POST` | `/tui/submit-prompt` | `special` |
| instance | `GET` | `/vcs` | `next` |
| instance | `GET` | `/vcs/diff` | `next` |
| ui | `ALL` | `/*` | `special` |
| workspace | `GET` | `/experimental/workspace` | `bridged` |
| workspace | `POST` | `/experimental/workspace` | `later` |
| workspace | `DELETE` | `/experimental/workspace/:id` | `later` |
| workspace | `POST` | `/experimental/workspace/:id/session-restore` | `later` |
| workspace | `GET` | `/experimental/workspace/adaptor` | `bridged` |
| workspace | `GET` | `/experimental/workspace/status` | `bridged` |