feat(httpapi): bridge instance read endpoints (#24258)

This commit is contained in:
Kit Langton
2026-04-25 10:42:31 -04:00
committed by GitHub
parent bad732c26a
commit d5bfaef53d
6 changed files with 193 additions and 36 deletions
@@ -0,0 +1,103 @@
import { Global } from "@/global"
import { Vcs } from "@/project"
import * as InstanceState from "@/effect/instance-state"
import { Effect, Layer, Schema } from "effect"
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "./auth"
const PathInfo = Schema.Struct({
home: Schema.String,
state: Schema.String,
config: Schema.String,
worktree: Schema.String,
directory: Schema.String,
}).annotate({ identifier: "Path" })
const VcsDiffQuery = Schema.Struct({
mode: Vcs.Mode,
})
export const InstancePaths = {
path: "/path",
vcs: "/vcs",
vcsDiff: "/vcs/diff",
} as const
export const InstanceApi = HttpApi.make("instance")
.add(
HttpApiGroup.make("instance")
.add(
HttpApiEndpoint.get("path", InstancePaths.path, {
success: PathInfo,
}).annotateMerge(
OpenApi.annotations({
identifier: "path.get",
summary: "Get paths",
description: "Retrieve the current working directory and related path information for the OpenCode instance.",
}),
),
HttpApiEndpoint.get("vcs", InstancePaths.vcs, {
success: Vcs.Info,
}).annotateMerge(
OpenApi.annotations({
identifier: "vcs.get",
summary: "Get VCS info",
description: "Retrieve version control system (VCS) information for the current project, such as git branch.",
}),
),
HttpApiEndpoint.get("vcsDiff", InstancePaths.vcsDiff, {
query: VcsDiffQuery,
success: Schema.Array(Vcs.FileDiff),
}).annotateMerge(
OpenApi.annotations({
identifier: "vcs.diff",
summary: "Get VCS diff",
description: "Retrieve the current git diff for the working tree or against the default branch.",
}),
),
)
.annotateMerge(
OpenApi.annotations({
title: "instance",
description: "Experimental HttpApi instance read routes.",
}),
)
.middleware(Authorization),
)
.annotateMerge(
OpenApi.annotations({
title: "opencode experimental HttpApi",
version: "0.0.1",
description: "Experimental HttpApi surface for selected instance routes.",
}),
)
export const instanceHandlers = Layer.unwrap(
Effect.gen(function* () {
const vcs = yield* Vcs.Service
const getPath = Effect.fn("InstanceHttpApi.path")(function* () {
const ctx = yield* InstanceState.context
return {
home: Global.Path.home,
state: Global.Path.state,
config: Global.Path.config,
worktree: ctx.worktree,
directory: ctx.directory,
}
})
const getVcs = Effect.fn("InstanceHttpApi.vcs")(function* () {
const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2 })
return { branch, default_branch }
})
const getVcsDiff = Effect.fn("InstanceHttpApi.vcsDiff")(function* (ctx: { query: { mode: Vcs.Mode } }) {
return yield* vcs.diff(ctx.query.mode)
})
return HttpApiBuilder.group(InstanceApi, "instance", (handlers) =>
handlers.handle("path", getPath).handle("vcs", getVcs).handle("vcsDiff", getVcsDiff),
)
}),
).pipe(Layer.provide(Vcs.defaultLayer))
@@ -11,6 +11,7 @@ import { Filesystem } from "@/util"
import { authorizationLayer } from "./auth"
import { ConfigApi, configHandlers } from "./config"
import { FileApi, fileHandlers } from "./file"
import { InstanceApi, instanceHandlers } from "./instance"
import { McpApi, mcpHandlers } from "./mcp"
import { PermissionApi, permissionHandlers } from "./permission"
import { ProjectApi, projectHandlers } from "./project"
@@ -63,6 +64,7 @@ const instance = HttpRouter.middleware()(
export const routes = Layer.mergeAll(
HttpApiBuilder.layer(ConfigApi).pipe(Layer.provide(configHandlers)),
HttpApiBuilder.layer(FileApi).pipe(Layer.provide(fileHandlers)),
HttpApiBuilder.layer(InstanceApi).pipe(Layer.provide(instanceHandlers)),
HttpApiBuilder.layer(McpApi).pipe(Layer.provide(mcpHandlers)),
HttpApiBuilder.layer(ProjectApi).pipe(Layer.provide(projectHandlers)),
HttpApiBuilder.layer(QuestionApi).pipe(Layer.provide(questionHandlers)),
@@ -17,6 +17,7 @@ import { PermissionRoutes } from "./permission"
import { Flag } from "@/flag/flag"
import { ExperimentalHttpApiServer } from "./httpapi/server"
import { FilePaths } from "./httpapi/file"
import { InstancePaths } from "./httpapi/instance"
import { McpPaths } from "./httpapi/mcp"
import { ProjectRoutes } from "./project"
import { SessionRoutes } from "./session"
@@ -53,6 +54,9 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
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.get(InstancePaths.vcs, (c) => handler(c.req.raw, context))
app.get(InstancePaths.vcsDiff, (c) => handler(c.req.raw, context))
app.get(McpPaths.status, (c) => handler(c.req.raw, context))
}
@@ -142,7 +146,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
description: "VCS info",
content: {
"application/json": {
schema: resolver(Vcs.Info),
schema: resolver(Vcs.Info.zod),
},
},
},
@@ -168,7 +172,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
description: "VCS diff",
content: {
"application/json": {
schema: resolver(Vcs.FileDiff.array()),
schema: resolver(Vcs.FileDiff.zod.array()),
},
},
},
@@ -177,7 +181,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
validator(
"query",
z.object({
mode: Vcs.Mode,
mode: Vcs.Mode.zod,
}),
),
async (c) =>