Skip to content

Commit 58c6587

Browse files
authored
feat(httpapi): bridge project update endpoint (#24398)
1 parent 27b0877 commit 58c6587

5 files changed

Lines changed: 76 additions & 6 deletions

File tree

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
176176
| `permission` | `bridged` | list and reply |
177177
| `provider` | `bridged` | list, auth, OAuth authorize/callback |
178178
| `config` | `bridged` | read, providers, update |
179-
| `project` | `bridged` | list, current, git init |
179+
| `project` | `bridged` | list, current, git init, update |
180180
| `file` | `bridged` partial | find text/file/symbol, list/content/status |
181181
| `mcp` | `bridged` partial | status only |
182182
| `workspace` | `bridged` | list, get, enter |
@@ -188,10 +188,24 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
188188
| `pty` | `special` | websocket |
189189
| `tui` | `special` | UI bridge |
190190

191-
## Next PRs
192-
193-
1. Produce a generated route inventory from Hono registrations and update `Current Route Status` with exact paths.
194-
2. Start the Effect OpenAPI/SDK generation path for already-bridged routes.
191+
## Remaining PR Plan
192+
193+
Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays reviewable.
194+
195+
1. Bridge `PATCH /project/:projectID`.
196+
2. Bridge MCP add/connect/disconnect routes.
197+
3. Bridge MCP OAuth routes: start, callback, authenticate, remove.
198+
4. Bridge experimental console switch and tool list routes.
199+
5. Bridge experimental global session list.
200+
6. Bridge sync start/replay/history routes.
201+
7. Bridge session read routes: list, status, get, children, todo, diff, messages.
202+
8. Bridge session lifecycle mutation routes: create, delete, update, fork, abort.
203+
9. Bridge session share/summary/message/part mutation routes.
204+
10. Replace event SSE with non-Hono Effect HTTP.
205+
11. Replace pty websocket/control routes with non-Hono Effect HTTP.
206+
12. Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer.
207+
13. Switch OpenAPI/SDK generation to Effect routes and compare SDK output.
208+
14. Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files.
195209

196210
## Checklist
197211

packages/opencode/src/project/project.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ export const UpdateInput = z.object({
9191
})
9292
export type UpdateInput = z.infer<typeof UpdateInput>
9393

94+
export const UpdatePayload = Schema.Struct({
95+
name: Schema.optional(Schema.String),
96+
icon: Schema.optional(ProjectIcon),
97+
commands: Schema.optional(ProjectCommands),
98+
})
99+
.annotate({ identifier: "ProjectUpdateInput" })
100+
.pipe(withStatics((s) => ({ zod: zod(s) })))
101+
export type UpdatePayload = Types.DeepMutable<Schema.Schema.Type<typeof UpdatePayload>>
102+
94103
// ---------------------------------------------------------------------------
95104
// Effect service
96105
// ---------------------------------------------------------------------------

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as InstanceState from "@/effect/instance-state"
22
import { AppRuntime } from "@/effect/app-runtime"
33
import { Project } from "@/project"
44
import { InstanceBootstrap } from "@/project/bootstrap"
5+
import { ProjectID } from "@/project/schema"
56
import { Effect, Layer, Schema } from "effect"
67
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
78
import { Authorization } from "./auth"
@@ -40,6 +41,17 @@ export const ProjectApi = HttpApi.make("project")
4041
description: "Create a git repository for the current project and return the refreshed project info.",
4142
}),
4243
),
44+
HttpApiEndpoint.patch("update", `${root}/:projectID`, {
45+
params: { projectID: ProjectID },
46+
payload: Project.UpdatePayload,
47+
success: Project.Info,
48+
}).annotateMerge(
49+
OpenApi.annotations({
50+
identifier: "project.update",
51+
summary: "Update project",
52+
description: "Update project properties such as name, icon, and commands.",
53+
}),
54+
),
4355
)
4456
.annotateMerge(
4557
OpenApi.annotations({
@@ -83,8 +95,15 @@ export const projectHandlers = Layer.unwrap(
8395
return next
8496
})
8597

98+
const update = Effect.fn("ProjectHttpApi.update")(function* (ctx: {
99+
params: { projectID: ProjectID }
100+
payload: Project.UpdatePayload
101+
}) {
102+
return yield* svc.update({ ...Project.UpdatePayload.zod.parse(ctx.payload), projectID: ctx.params.projectID })
103+
})
104+
86105
return HttpApiBuilder.group(ProjectApi, "project", (handlers) =>
87-
handlers.handle("list", list).handle("current", current).handle("initGit", initGit),
106+
handlers.handle("list", list).handle("current", current).handle("initGit", initGit).handle("update", update),
88107
)
89108
}),
90109
).pipe(Layer.provide(Project.defaultLayer))

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
6262
app.get("/project", (c) => handler(c.req.raw, context))
6363
app.get("/project/current", (c) => handler(c.req.raw, context))
6464
app.post("/project/git/init", (c) => handler(c.req.raw, context))
65+
app.patch("/project/:projectID", (c) => handler(c.req.raw, context))
6566
app.get(FilePaths.findText, (c) => handler(c.req.raw, context))
6667
app.get(FilePaths.findFile, (c) => handler(c.req.raw, context))
6768
app.get(FilePaths.findSymbol, (c) => handler(c.req.raw, context))

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,33 @@ describe("instance HttpApi", () => {
115115
expect(await current.json()).toMatchObject({ vcs: "git", worktree: tmp.path })
116116
})
117117

118+
test("serves project update through Hono bridge", async () => {
119+
await using tmp = await tmpdir({ config: { formatter: false, lsp: false } })
120+
121+
const current = await app().request("/project/current", { headers: { "x-opencode-directory": tmp.path } })
122+
expect(current.status).toBe(200)
123+
const project = (await current.json()) as { id: string }
124+
125+
const response = await app().request(`/project/${project.id}`, {
126+
method: "PATCH",
127+
headers: { "x-opencode-directory": tmp.path, "content-type": "application/json" },
128+
body: JSON.stringify({ name: "patched-project", commands: { start: "bun dev" } }),
129+
})
130+
131+
expect(response.status).toBe(200)
132+
expect(await response.json()).toMatchObject({
133+
id: project.id,
134+
name: "patched-project",
135+
commands: { start: "bun dev" },
136+
})
137+
138+
const list = await app().request("/project", { headers: { "x-opencode-directory": tmp.path } })
139+
expect(list.status).toBe(200)
140+
expect(await list.json()).toContainEqual(
141+
expect.objectContaining({ id: project.id, name: "patched-project", commands: { start: "bun dev" } }),
142+
)
143+
})
144+
118145
test("serves instance dispose through Hono bridge", async () => {
119146
await using tmp = await tmpdir()
120147

0 commit comments

Comments
 (0)