Explorar el Código

feat(httpapi): bridge project update endpoint (#24398)

Kit Langton hace 17 horas
padre
commit
58c65874ba

+ 19 - 5
packages/opencode/specs/effect/http-api.md

@@ -176,7 +176,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
 | `permission`              | `bridged`         | list and reply                                                                                     |
 | `provider`                | `bridged`         | list, auth, OAuth authorize/callback                                                               |
 | `config`                  | `bridged`         | read, providers, update                                                                            |
-| `project`                 | `bridged`         | list, current, git init                                                                            |
+| `project`                 | `bridged`         | list, current, git init, update                                                                    |
 | `file`                    | `bridged` partial | find text/file/symbol, list/content/status                                                         |
 | `mcp`                     | `bridged` partial | status only                                                                                        |
 | `workspace`               | `bridged`         | list, get, enter                                                                                   |
@@ -188,10 +188,24 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
 | `pty`                     | `special`         | websocket                                                                                          |
 | `tui`                     | `special`         | UI bridge                                                                                          |
 
-## Next PRs
-
-1. Produce a generated route inventory from Hono registrations and update `Current Route Status` with exact paths.
-2. Start the Effect OpenAPI/SDK generation path for already-bridged routes.
+## Remaining PR Plan
+
+Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays reviewable.
+
+1. Bridge `PATCH /project/:projectID`.
+2. Bridge MCP add/connect/disconnect routes.
+3. Bridge MCP OAuth routes: start, callback, authenticate, remove.
+4. Bridge experimental console switch and tool list routes.
+5. Bridge experimental global session list.
+6. Bridge sync start/replay/history routes.
+7. Bridge session read routes: list, status, get, children, todo, diff, messages.
+8. Bridge session lifecycle mutation routes: create, delete, update, fork, abort.
+9. Bridge session share/summary/message/part mutation routes.
+10. Replace event SSE with non-Hono Effect HTTP.
+11. Replace pty websocket/control routes with non-Hono Effect HTTP.
+12. Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer.
+13. Switch OpenAPI/SDK generation to Effect routes and compare SDK output.
+14. Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files.
 
 ## Checklist
 

+ 9 - 0
packages/opencode/src/project/project.ts

@@ -91,6 +91,15 @@ export const UpdateInput = z.object({
 })
 export type UpdateInput = z.infer<typeof UpdateInput>
 
+export const UpdatePayload = Schema.Struct({
+  name: Schema.optional(Schema.String),
+  icon: Schema.optional(ProjectIcon),
+  commands: Schema.optional(ProjectCommands),
+})
+  .annotate({ identifier: "ProjectUpdateInput" })
+  .pipe(withStatics((s) => ({ zod: zod(s) })))
+export type UpdatePayload = Types.DeepMutable<Schema.Schema.Type<typeof UpdatePayload>>
+
 // ---------------------------------------------------------------------------
 // Effect service
 // ---------------------------------------------------------------------------

+ 20 - 1
packages/opencode/src/server/routes/instance/httpapi/project.ts

@@ -2,6 +2,7 @@ import * as InstanceState from "@/effect/instance-state"
 import { AppRuntime } from "@/effect/app-runtime"
 import { Project } from "@/project"
 import { InstanceBootstrap } from "@/project/bootstrap"
+import { ProjectID } from "@/project/schema"
 import { Effect, Layer, Schema } from "effect"
 import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
 import { Authorization } from "./auth"
@@ -40,6 +41,17 @@ export const ProjectApi = HttpApi.make("project")
             description: "Create a git repository for the current project and return the refreshed project info.",
           }),
         ),
+        HttpApiEndpoint.patch("update", `${root}/:projectID`, {
+          params: { projectID: ProjectID },
+          payload: Project.UpdatePayload,
+          success: Project.Info,
+        }).annotateMerge(
+          OpenApi.annotations({
+            identifier: "project.update",
+            summary: "Update project",
+            description: "Update project properties such as name, icon, and commands.",
+          }),
+        ),
       )
       .annotateMerge(
         OpenApi.annotations({
@@ -83,8 +95,15 @@ export const projectHandlers = Layer.unwrap(
       return next
     })
 
+    const update = Effect.fn("ProjectHttpApi.update")(function* (ctx: {
+      params: { projectID: ProjectID }
+      payload: Project.UpdatePayload
+    }) {
+      return yield* svc.update({ ...Project.UpdatePayload.zod.parse(ctx.payload), projectID: ctx.params.projectID })
+    })
+
     return HttpApiBuilder.group(ProjectApi, "project", (handlers) =>
-      handlers.handle("list", list).handle("current", current).handle("initGit", initGit),
+      handlers.handle("list", list).handle("current", current).handle("initGit", initGit).handle("update", update),
     )
   }),
 ).pipe(Layer.provide(Project.defaultLayer))

+ 1 - 0
packages/opencode/src/server/routes/instance/index.ts

@@ -62,6 +62,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
     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))

+ 27 - 0
packages/opencode/test/server/httpapi-instance.test.ts

@@ -115,6 +115,33 @@ describe("instance HttpApi", () => {
     expect(await current.json()).toMatchObject({ vcs: "git", worktree: tmp.path })
   })
 
+  test("serves project update through Hono bridge", async () => {
+    await using tmp = await tmpdir({ config: { formatter: false, lsp: false } })
+
+    const current = await app().request("/project/current", { headers: { "x-opencode-directory": tmp.path } })
+    expect(current.status).toBe(200)
+    const project = (await current.json()) as { id: string }
+
+    const response = await app().request(`/project/${project.id}`, {
+      method: "PATCH",
+      headers: { "x-opencode-directory": tmp.path, "content-type": "application/json" },
+      body: JSON.stringify({ name: "patched-project", commands: { start: "bun dev" } }),
+    })
+
+    expect(response.status).toBe(200)
+    expect(await response.json()).toMatchObject({
+      id: project.id,
+      name: "patched-project",
+      commands: { start: "bun dev" },
+    })
+
+    const list = await app().request("/project", { headers: { "x-opencode-directory": tmp.path } })
+    expect(list.status).toBe(200)
+    expect(await list.json()).toContainEqual(
+      expect.objectContaining({ id: project.id, name: "patched-project", commands: { start: "bun dev" } }),
+    )
+  })
+
   test("serves instance dispose through Hono bridge", async () => {
     await using tmp = await tmpdir()