Просмотр исходного кода

fix(server): serve HttpApi OpenAPI document (#26438)

Kit Langton 4 месяцев назад
Родитель
Сommit
9c05d4e2fd

+ 15 - 3
packages/opencode/src/server/routes/instance/httpapi/server.ts

@@ -1,6 +1,6 @@
 import { Context, Effect, Layer } from "effect"
-import { HttpApiBuilder } from "effect/unstable/httpapi"
-import { FetchHttpClient, HttpClient, HttpMiddleware, HttpRouter, HttpServer } from "effect/unstable/http"
+import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi"
+import { FetchHttpClient, HttpClient, HttpMiddleware, HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
 import * as Socket from "effect/unstable/socket/Socket"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { Account } from "@/account/account"
@@ -49,6 +49,7 @@ import { CorsConfig, isAllowedCorsOrigin, type CorsOptions } from "@/server/cors
 import { serveUIEffect } from "@/server/shared/ui"
 import { ServerAuth } from "@/server/auth"
 import { InstanceHttpApi, RootHttpApi } from "./api"
+import { PublicApi } from "./public"
 import { authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
 import { EventApi, eventHandlers } from "./event"
 import { configHandlers } from "./handlers/config"
@@ -144,6 +145,17 @@ const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe
   ]),
 )
 
+const openApiDocument = OpenApi.fromApi(PublicApi)
+const openApiDocumentJson = JSON.stringify(openApiDocument)
+
+const docRoute = HttpRouter.use((router) =>
+  router.add(
+    "GET",
+    "/doc",
+    () => Effect.succeed(HttpServerResponse.text(openApiDocumentJson, { headers: { "content-type": "application/json" } })),
+  ),
+).pipe(Layer.provide(authOnlyRouterLayer))
+
 const uiRoute = HttpRouter.use((router) =>
   Effect.gen(function* () {
     const fs = yield* AppFileSystem.Service
@@ -153,7 +165,7 @@ const uiRoute = HttpRouter.use((router) =>
 ).pipe(Layer.provide(authOnlyRouterLayer))
 
 export function createRoutes(corsOptions?: CorsOptions) {
-  return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
+  return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, docRoute, uiRoute).pipe(
     Layer.provide([
       errorLayer,
       cors(corsOptions),

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

@@ -48,6 +48,23 @@ const it = testEffect(Layer.mergeAll(testStateLayer, httpApiServerLayer))
 const directoryHeader = (dir: string) => HttpClientRequest.setHeader("x-opencode-directory", dir)
 
 describe("instance HttpApi", () => {
+  it.live("serves the OpenAPI document", () =>
+    Effect.gen(function* () {
+      const response = yield* HttpClient.get("/doc")
+
+      expect(response.status).toBe(200)
+      expect(response.headers["content-type"]).toContain("application/json")
+      expect(yield* response.json).toMatchObject({
+        openapi: expect.any(String),
+        info: expect.any(Object),
+        paths: expect.objectContaining({
+          "/global/health": expect.any(Object),
+          "/session": expect.any(Object),
+        }),
+      })
+    }),
+  )
+
   it.live("serves path and VCS read endpoints", () =>
     Effect.gen(function* () {
       const dir = yield* tmpdirScoped({ git: true })