summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-25 14:55:29 -0400
committerGitHub <[email protected]>2026-04-25 14:55:29 -0400
commitb749866f0b21328919e0c525109c5b2f3e4e0e24 (patch)
tree1419903b24b586b2dead6e27dc187652afdab4ce /packages
parent60fa708f0b4490c49cddd518f9dd7b7471181379 (diff)
downloadopencode-b749866f0b21328919e0c525109c5b2f3e4e0e24.tar.gz
opencode-b749866f0b21328919e0c525109c5b2f3e4e0e24.zip
feat(httpapi): bridge worktree read endpoint (#24366)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/specs/effect/http-api.md2
-rw-r--r--packages/opencode/src/server/routes/instance/httpapi/experimental.ts22
-rw-r--r--packages/opencode/src/server/routes/instance/index.ts1
-rw-r--r--packages/opencode/test/server/httpapi-experimental.test.ts6
4 files changed, 29 insertions, 2 deletions
diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md
index e32a1dd7e..3e4f30d02 100644
--- a/packages/opencode/specs/effect/http-api.md
+++ b/packages/opencode/specs/effect/http-api.md
@@ -164,7 +164,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
| `mcp` | `bridged` partial | status only |
| `workspace` | `bridged` | list, get, enter |
| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter |
-| experimental JSON routes | `bridged` partial | console reads, tool ids, resource list; worktree and global session list remain later |
+| experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list, resource list; global session list remains later |
| `session` | `later/special` | large stateful surface plus streaming |
| `sync` | `later` | process/control side effects |
| `event` | `special` | SSE |
diff --git a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts
index 993971202..4bd9ba30a 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts
@@ -1,6 +1,8 @@
import { Account } from "@/account/account"
import { Config } from "@/config"
+import { InstanceState } from "@/effect"
import { MCP } from "@/mcp"
+import { Project } from "@/project"
import { ToolRegistry } from "@/tool"
import { Effect, Layer, Option, Schema } from "effect"
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
@@ -27,10 +29,13 @@ const ConsoleOrgList = Schema.Struct({
const ToolIDs = Schema.Array(Schema.String).annotate({ identifier: "ToolIDs" })
+const WorktreeList = Schema.Array(Schema.String).annotate({ identifier: "WorktreeList" })
+
export const ExperimentalPaths = {
console: "/experimental/console",
consoleOrgs: "/experimental/console/orgs",
toolIDs: "/experimental/tool/ids",
+ worktree: "/experimental/worktree",
resource: "/experimental/resource",
} as const
@@ -66,6 +71,15 @@ export const ExperimentalApi = HttpApi.make("experimental")
"Get a list of all available tool IDs, including both built-in tools and dynamically registered tools.",
}),
),
+ HttpApiEndpoint.get("worktree", ExperimentalPaths.worktree, {
+ success: WorktreeList,
+ }).annotateMerge(
+ OpenApi.annotations({
+ identifier: "worktree.list",
+ summary: "List worktrees",
+ description: "List all sandbox worktrees for the current project.",
+ }),
+ ),
HttpApiEndpoint.get("resource", ExperimentalPaths.resource, {
success: Schema.Record(Schema.String, MCP.Resource),
}).annotateMerge(
@@ -97,6 +111,7 @@ export const experimentalHandlers = Layer.unwrap(
const account = yield* Account.Service
const config = yield* Config.Service
const mcp = yield* MCP.Service
+ const project = yield* Project.Service
const registry = yield* ToolRegistry.Service
const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () {
@@ -139,6 +154,11 @@ export const experimentalHandlers = Layer.unwrap(
return yield* registry.ids()
})
+ const worktree = Effect.fn("ExperimentalHttpApi.worktree")(function* () {
+ const ctx = yield* InstanceState.context
+ return yield* project.sandboxes(ctx.project.id)
+ })
+
const resource = Effect.fn("ExperimentalHttpApi.resource")(function* () {
return yield* mcp.resources()
})
@@ -148,6 +168,7 @@ export const experimentalHandlers = Layer.unwrap(
.handle("console", getConsole)
.handle("consoleOrgs", listConsoleOrgs)
.handle("toolIDs", toolIDs)
+ .handle("worktree", worktree)
.handle("resource", resource),
)
}),
@@ -155,5 +176,6 @@ export const experimentalHandlers = Layer.unwrap(
Layer.provide(Account.defaultLayer),
Layer.provide(Config.defaultLayer),
Layer.provide(MCP.defaultLayer),
+ Layer.provide(Project.defaultLayer),
Layer.provide(ToolRegistry.defaultLayer),
)
diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts
index 65dd41705..8e4c497bd 100644
--- a/packages/opencode/src/server/routes/instance/index.ts
+++ b/packages/opencode/src/server/routes/instance/index.ts
@@ -49,6 +49,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
app.get(ExperimentalPaths.console, (c) => handler(c.req.raw, context))
app.get(ExperimentalPaths.consoleOrgs, (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.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))
diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts
index 00d1fefb8..38f43e68f 100644
--- a/packages/opencode/test/server/httpapi-experimental.test.ts
+++ b/packages/opencode/test/server/httpapi-experimental.test.ts
@@ -41,10 +41,11 @@ describe("experimental HttpApi", () => {
})
const headers = { "x-opencode-directory": tmp.path }
- const [consoleState, consoleOrgs, toolIDs, resources] = await Promise.all([
+ const [consoleState, consoleOrgs, toolIDs, worktrees, resources] = await Promise.all([
app().request(ExperimentalPaths.console, { headers }),
app().request(ExperimentalPaths.consoleOrgs, { headers }),
app().request(ExperimentalPaths.toolIDs, { headers }),
+ app().request(ExperimentalPaths.worktree, { headers }),
app().request(ExperimentalPaths.resource, { headers }),
])
@@ -60,6 +61,9 @@ describe("experimental HttpApi", () => {
expect(toolIDs.status).toBe(200)
expect(await toolIDs.json()).toContain("bash")
+ expect(worktrees.status).toBe(200)
+ expect(await worktrees.json()).toEqual([])
+
expect(resources.status).toBe(200)
expect(await resources.json()).toEqual({})
})