Skip to content

Commit 33049dd

Browse files
authored
feat(httpapi): bridge worktree read endpoint (anomalyco#24366)
1 parent fff2009 commit 33049dd

4 files changed

Lines changed: 29 additions & 2 deletions

File tree

packages/opencode/specs/effect/http-api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
164164
| `mcp` | `bridged` partial | status only |
165165
| `workspace` | `bridged` | list, get, enter |
166166
| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter |
167-
| experimental JSON routes | `bridged` partial | console reads, tool ids, resource list; worktree and global session list remain later |
167+
| experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list, resource list; global session list remains later |
168168
| `session` | `later/special` | large stateful surface plus streaming |
169169
| `sync` | `later` | process/control side effects |
170170
| `event` | `special` | SSE |

packages/opencode/src/server/routes/instance/httpapi/experimental.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Account } from "@/account/account"
22
import { Config } from "@/config"
3+
import { InstanceState } from "@/effect"
34
import { MCP } from "@/mcp"
5+
import { Project } from "@/project"
46
import { ToolRegistry } from "@/tool"
57
import { Effect, Layer, Option, Schema } from "effect"
68
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
@@ -27,10 +29,13 @@ const ConsoleOrgList = Schema.Struct({
2729

2830
const ToolIDs = Schema.Array(Schema.String).annotate({ identifier: "ToolIDs" })
2931

32+
const WorktreeList = Schema.Array(Schema.String).annotate({ identifier: "WorktreeList" })
33+
3034
export const ExperimentalPaths = {
3135
console: "/experimental/console",
3236
consoleOrgs: "/experimental/console/orgs",
3337
toolIDs: "/experimental/tool/ids",
38+
worktree: "/experimental/worktree",
3439
resource: "/experimental/resource",
3540
} as const
3641

@@ -66,6 +71,15 @@ export const ExperimentalApi = HttpApi.make("experimental")
6671
"Get a list of all available tool IDs, including both built-in tools and dynamically registered tools.",
6772
}),
6873
),
74+
HttpApiEndpoint.get("worktree", ExperimentalPaths.worktree, {
75+
success: WorktreeList,
76+
}).annotateMerge(
77+
OpenApi.annotations({
78+
identifier: "worktree.list",
79+
summary: "List worktrees",
80+
description: "List all sandbox worktrees for the current project.",
81+
}),
82+
),
6983
HttpApiEndpoint.get("resource", ExperimentalPaths.resource, {
7084
success: Schema.Record(Schema.String, MCP.Resource),
7185
}).annotateMerge(
@@ -97,6 +111,7 @@ export const experimentalHandlers = Layer.unwrap(
97111
const account = yield* Account.Service
98112
const config = yield* Config.Service
99113
const mcp = yield* MCP.Service
114+
const project = yield* Project.Service
100115
const registry = yield* ToolRegistry.Service
101116

102117
const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () {
@@ -139,6 +154,11 @@ export const experimentalHandlers = Layer.unwrap(
139154
return yield* registry.ids()
140155
})
141156

157+
const worktree = Effect.fn("ExperimentalHttpApi.worktree")(function* () {
158+
const ctx = yield* InstanceState.context
159+
return yield* project.sandboxes(ctx.project.id)
160+
})
161+
142162
const resource = Effect.fn("ExperimentalHttpApi.resource")(function* () {
143163
return yield* mcp.resources()
144164
})
@@ -148,12 +168,14 @@ export const experimentalHandlers = Layer.unwrap(
148168
.handle("console", getConsole)
149169
.handle("consoleOrgs", listConsoleOrgs)
150170
.handle("toolIDs", toolIDs)
171+
.handle("worktree", worktree)
151172
.handle("resource", resource),
152173
)
153174
}),
154175
).pipe(
155176
Layer.provide(Account.defaultLayer),
156177
Layer.provide(Config.defaultLayer),
157178
Layer.provide(MCP.defaultLayer),
179+
Layer.provide(Project.defaultLayer),
158180
Layer.provide(ToolRegistry.defaultLayer),
159181
)

packages/opencode/src/server/routes/instance/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
4949
app.get(ExperimentalPaths.console, (c) => handler(c.req.raw, context))
5050
app.get(ExperimentalPaths.consoleOrgs, (c) => handler(c.req.raw, context))
5151
app.get(ExperimentalPaths.toolIDs, (c) => handler(c.req.raw, context))
52+
app.get(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context))
5253
app.get(ExperimentalPaths.resource, (c) => handler(c.req.raw, context))
5354
app.get("/provider", (c) => handler(c.req.raw, context))
5455
app.get("/provider/auth", (c) => handler(c.req.raw, context))

packages/opencode/test/server/httpapi-experimental.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,11 @@ describe("experimental HttpApi", () => {
4141
})
4242

4343
const headers = { "x-opencode-directory": tmp.path }
44-
const [consoleState, consoleOrgs, toolIDs, resources] = await Promise.all([
44+
const [consoleState, consoleOrgs, toolIDs, worktrees, resources] = await Promise.all([
4545
app().request(ExperimentalPaths.console, { headers }),
4646
app().request(ExperimentalPaths.consoleOrgs, { headers }),
4747
app().request(ExperimentalPaths.toolIDs, { headers }),
48+
app().request(ExperimentalPaths.worktree, { headers }),
4849
app().request(ExperimentalPaths.resource, { headers }),
4950
])
5051

@@ -60,6 +61,9 @@ describe("experimental HttpApi", () => {
6061
expect(toolIDs.status).toBe(200)
6162
expect(await toolIDs.json()).toContain("bash")
6263

64+
expect(worktrees.status).toBe(200)
65+
expect(await worktrees.json()).toEqual([])
66+
6367
expect(resources.status).toBe(200)
6468
expect(await resources.json()).toEqual({})
6569
})

0 commit comments

Comments
 (0)