import { describeRoute, resolver, validator } from "hono-openapi" import { Hono } from "hono" import type { UpgradeWebSocket } from "hono/ws" import { Context, Effect } from "effect" import z from "zod" import { Format } from "@/format" import { TuiRoutes } from "./tui" import { Instance } from "@/project/instance" import { Vcs } from "@/project" import { Agent } from "@/agent/agent" import { Skill } from "@/skill" import { Global } from "@opencode-ai/core/global" import { LSP } from "@/lsp" import { Command } from "@/command" import { QuestionRoutes } from "./question" import { PermissionRoutes } from "./permission" import { Flag } from "@opencode-ai/core/flag/flag" import { ExperimentalHttpApiServer } from "./httpapi/server" import { ExperimentalPaths } from "./httpapi/experimental" import { FilePaths } from "./httpapi/file" import { InstancePaths } from "./httpapi/instance" import { McpPaths } from "./httpapi/mcp" import { SessionPaths } from "./httpapi/session" import { SyncPaths } from "./httpapi/sync" import { ProjectRoutes } from "./project" import { SessionRoutes } from "./session" import { PtyRoutes } from "./pty" import { McpRoutes } from "./mcp" import { FileRoutes } from "./file" import { ConfigRoutes } from "./config" import { ExperimentalRoutes } from "./experimental" import { ProviderRoutes } from "./provider" import { EventRoutes } from "./event" import { SyncRoutes } from "./sync" import { InstanceMiddleware } from "./middleware" import { jsonRequest } from "./trace" export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { const app = new Hono() if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) { const handler = ExperimentalHttpApiServer.webHandler().handler const context = Context.empty() as Context.Context app.get("/question", (c) => handler(c.req.raw, context)) app.post("/question/:requestID/reply", (c) => handler(c.req.raw, context)) app.post("/question/:requestID/reject", (c) => handler(c.req.raw, context)) app.get("/permission", (c) => handler(c.req.raw, context)) app.post("/permission/:requestID/reply", (c) => handler(c.req.raw, context)) app.get("/config", (c) => handler(c.req.raw, context)) app.patch("/config", (c) => handler(c.req.raw, context)) app.get("/config/providers", (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.console, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.consoleOrgs, (c) => handler(c.req.raw, context)) app.post(ExperimentalPaths.consoleSwitch, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.tool, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.toolIDs, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) app.post(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) app.delete(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) app.post(ExperimentalPaths.worktreeReset, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.session, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.resource, (c) => handler(c.req.raw, context)) app.get("/provider", (c) => handler(c.req.raw, context)) app.get("/provider/auth", (c) => handler(c.req.raw, context)) app.post("/provider/:providerID/oauth/authorize", (c) => handler(c.req.raw, context)) app.post("/provider/:providerID/oauth/callback", (c) => handler(c.req.raw, context)) app.get("/project", (c) => handler(c.req.raw, context)) app.get("/project/current", (c) => handler(c.req.raw, context)) app.post("/project/git/init", (c) => handler(c.req.raw, context)) app.patch("/project/:projectID", (c) => handler(c.req.raw, context)) app.get(FilePaths.findText, (c) => handler(c.req.raw, context)) app.get(FilePaths.findFile, (c) => handler(c.req.raw, context)) app.get(FilePaths.findSymbol, (c) => handler(c.req.raw, context)) app.get(FilePaths.list, (c) => handler(c.req.raw, context)) app.get(FilePaths.content, (c) => handler(c.req.raw, context)) app.get(FilePaths.status, (c) => handler(c.req.raw, context)) app.get(InstancePaths.path, (c) => handler(c.req.raw, context)) app.post(InstancePaths.dispose, (c) => handler(c.req.raw, context)) app.get(InstancePaths.vcs, (c) => handler(c.req.raw, context)) app.get(InstancePaths.vcsDiff, (c) => handler(c.req.raw, context)) app.get(InstancePaths.command, (c) => handler(c.req.raw, context)) app.get(InstancePaths.agent, (c) => handler(c.req.raw, context)) app.get(InstancePaths.skill, (c) => handler(c.req.raw, context)) app.get(InstancePaths.lsp, (c) => handler(c.req.raw, context)) app.get(InstancePaths.formatter, (c) => handler(c.req.raw, context)) app.get(McpPaths.status, (c) => handler(c.req.raw, context)) app.post(McpPaths.status, (c) => handler(c.req.raw, context)) app.post(McpPaths.auth, (c) => handler(c.req.raw, context)) app.post(McpPaths.authCallback, (c) => handler(c.req.raw, context)) app.post(McpPaths.authAuthenticate, (c) => handler(c.req.raw, context)) app.delete(McpPaths.auth, (c) => handler(c.req.raw, context)) app.post(McpPaths.connect, (c) => handler(c.req.raw, context)) app.post(McpPaths.disconnect, (c) => handler(c.req.raw, context)) app.post(SyncPaths.start, (c) => handler(c.req.raw, context)) app.post(SyncPaths.replay, (c) => handler(c.req.raw, context)) app.post(SyncPaths.history, (c) => handler(c.req.raw, context)) app.get(SessionPaths.list, (c) => handler(c.req.raw, context)) app.get(SessionPaths.status, (c) => handler(c.req.raw, context)) app.get(SessionPaths.get, (c) => handler(c.req.raw, context)) app.get(SessionPaths.children, (c) => handler(c.req.raw, context)) app.get(SessionPaths.todo, (c) => handler(c.req.raw, context)) app.get(SessionPaths.diff, (c) => handler(c.req.raw, context)) app.get(SessionPaths.messages, (c) => handler(c.req.raw, context)) app.get(SessionPaths.message, (c) => handler(c.req.raw, context)) app.post(SessionPaths.create, (c) => handler(c.req.raw, context)) app.delete(SessionPaths.remove, (c) => handler(c.req.raw, context)) app.patch(SessionPaths.update, (c) => handler(c.req.raw, context)) app.post(SessionPaths.fork, (c) => handler(c.req.raw, context)) app.post(SessionPaths.abort, (c) => handler(c.req.raw, context)) } return app .route("/project", ProjectRoutes()) .route("/pty", PtyRoutes(upgrade)) .route("/config", ConfigRoutes()) .route("/experimental", ExperimentalRoutes()) .route("/session", SessionRoutes()) .route("/permission", PermissionRoutes()) .route("/question", QuestionRoutes()) .route("/provider", ProviderRoutes()) .route("/sync", SyncRoutes()) .route("/", FileRoutes()) .route("/", EventRoutes()) .route("/mcp", McpRoutes()) .route("/tui", TuiRoutes()) .post( "/instance/dispose", describeRoute({ summary: "Dispose instance", description: "Clean up and dispose the current OpenCode instance, releasing all resources.", operationId: "instance.dispose", responses: { 200: { description: "Instance disposed", content: { "application/json": { schema: resolver(z.boolean()), }, }, }, }, }), async (c) => { await Instance.dispose() return c.json(true) }, ) .get( "/path", describeRoute({ summary: "Get paths", description: "Retrieve the current working directory and related path information for the OpenCode instance.", operationId: "path.get", responses: { 200: { description: "Path", content: { "application/json": { schema: resolver( z .object({ home: z.string(), state: z.string(), config: z.string(), worktree: z.string(), directory: z.string(), }) .meta({ ref: "Path", }), ), }, }, }, }, }), async (c) => { return c.json({ home: Global.Path.home, state: Global.Path.state, config: Global.Path.config, worktree: Instance.worktree, directory: Instance.directory, }) }, ) .get( "/vcs", describeRoute({ summary: "Get VCS info", description: "Retrieve version control system (VCS) information for the current project, such as git branch.", operationId: "vcs.get", responses: { 200: { description: "VCS info", content: { "application/json": { schema: resolver(Vcs.Info.zod), }, }, }, }, }), async (c) => jsonRequest("InstanceRoutes.vcs.get", c, function* () { const vcs = yield* Vcs.Service const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2, }) return { branch, default_branch } }), ) .get( "/vcs/diff", describeRoute({ summary: "Get VCS diff", description: "Retrieve the current git diff for the working tree or against the default branch.", operationId: "vcs.diff", responses: { 200: { description: "VCS diff", content: { "application/json": { schema: resolver(Vcs.FileDiff.zod.array()), }, }, }, }, }), validator( "query", z.object({ mode: Vcs.Mode.zod, }), ), async (c) => jsonRequest("InstanceRoutes.vcs.diff", c, function* () { const vcs = yield* Vcs.Service return yield* vcs.diff(c.req.valid("query").mode) }), ) .get( "/command", describeRoute({ summary: "List commands", description: "Get a list of all available commands in the OpenCode system.", operationId: "command.list", responses: { 200: { description: "List of commands", content: { "application/json": { schema: resolver(Command.Info.zod.array()), }, }, }, }, }), async (c) => jsonRequest("InstanceRoutes.command.list", c, function* () { const svc = yield* Command.Service return yield* svc.list() }), ) .get( "/agent", describeRoute({ summary: "List agents", description: "Get a list of all available AI agents in the OpenCode system.", operationId: "app.agents", responses: { 200: { description: "List of agents", content: { "application/json": { schema: resolver(Agent.Info.zod.array()), }, }, }, }, }), async (c) => jsonRequest("InstanceRoutes.agent.list", c, function* () { const svc = yield* Agent.Service return yield* svc.list() }), ) .get( "/skill", describeRoute({ summary: "List skills", description: "Get a list of all available skills in the OpenCode system.", operationId: "app.skills", responses: { 200: { description: "List of skills", content: { "application/json": { schema: resolver(Skill.Info.zod.array()), }, }, }, }, }), async (c) => jsonRequest("InstanceRoutes.skill.list", c, function* () { const skill = yield* Skill.Service return yield* skill.all() }), ) .get( "/lsp", describeRoute({ summary: "Get LSP status", description: "Get LSP server status", operationId: "lsp.status", responses: { 200: { description: "LSP server status", content: { "application/json": { schema: resolver(LSP.Status.zod.array()), }, }, }, }, }), async (c) => jsonRequest("InstanceRoutes.lsp.status", c, function* () { const lsp = yield* LSP.Service return yield* lsp.status() }), ) .get( "/formatter", describeRoute({ summary: "Get formatter status", description: "Get formatter status", operationId: "formatter.status", responses: { 200: { description: "Formatter status", content: { "application/json": { schema: resolver(Format.Status.zod.array()), }, }, }, }, }), async (c) => jsonRequest("InstanceRoutes.formatter.status", c, function* () { const svc = yield* Format.Service return yield* svc.status() }), ) }