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

refactor(server): reorganize route structure and middleware

- Extract instance-specific routes into separate InstanceRoutes Hono router
- Reorganize middleware chain for better separation of concerns
- Migrate embedded UI file serving from Bun.file() to Filesystem.readArrayBuffer()
- Clean up type casting in route handler
Dax Raad 3 недель назад
Родитель
Сommit
f3fd3488ee
1 измененных файлов с 145 добавлено и 140 удалено
  1. 145 140
      packages/opencode/src/server/server.ts

+ 145 - 140
packages/opencode/src/server/server.ts

@@ -67,141 +67,8 @@ export namespace Server {
 
   export const createApp = (opts: { cors?: string[] }): Hono => {
     const app = new Hono()
-    return app
-      .onError((err, c) => {
-        log.error("failed", {
-          error: err,
-        })
-        if (err instanceof NamedError) {
-          let status: ContentfulStatusCode
-          if (err instanceof NotFoundError) status = 404
-          else if (err instanceof Provider.ModelNotFoundError) status = 400
-          else if (err.name === "ProviderAuthValidationFailed") status = 400
-          else if (err.name.startsWith("Worktree")) status = 400
-          else status = 500
-          return c.json(err.toObject(), { status })
-        }
-        if (err instanceof HTTPException) return err.getResponse()
-        const message = err instanceof Error && err.stack ? err.stack : err.toString()
-        return c.json(new NamedError.Unknown({ message }).toObject(), {
-          status: 500,
-        })
-      })
-      .use((c, next) => {
-        // Allow CORS preflight requests to succeed without auth.
-        // Browser clients sending Authorization headers will preflight with OPTIONS.
-        if (c.req.method === "OPTIONS") return next()
-        const password = Flag.OPENCODE_SERVER_PASSWORD
-        if (!password) return next()
-        const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode"
-        return basicAuth({ username, password })(c, next)
-      })
-      .use(async (c, next) => {
-        const skipLogging = c.req.path === "/log"
-        if (!skipLogging) {
-          log.info("request", {
-            method: c.req.method,
-            path: c.req.path,
-          })
-        }
-        const timer = log.time("request", {
-          method: c.req.method,
-          path: c.req.path,
-        })
-        await next()
-        if (!skipLogging) {
-          timer.stop()
-        }
-      })
-      .use(
-        cors({
-          origin(input) {
-            if (!input) return
-
-            if (input.startsWith("http://localhost:")) return input
-            if (input.startsWith("http://127.0.0.1:")) return input
-            if (
-              input === "tauri://localhost" ||
-              input === "http://tauri.localhost" ||
-              input === "https://tauri.localhost"
-            )
-              return input
 
-            // *.opencode.ai (https only, adjust if needed)
-            if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) {
-              return input
-            }
-            if (opts?.cors?.includes(input)) {
-              return input
-            }
-
-            return
-          },
-        }),
-      )
-      .route("/global", GlobalRoutes())
-      .put(
-        "/auth/:providerID",
-        describeRoute({
-          summary: "Set auth credentials",
-          description: "Set authentication credentials",
-          operationId: "auth.set",
-          responses: {
-            200: {
-              description: "Successfully set authentication credentials",
-              content: {
-                "application/json": {
-                  schema: resolver(z.boolean()),
-                },
-              },
-            },
-            ...errors(400),
-          },
-        }),
-        validator(
-          "param",
-          z.object({
-            providerID: ProviderID.zod,
-          }),
-        ),
-        validator("json", Auth.Info.zod),
-        async (c) => {
-          const providerID = c.req.valid("param").providerID
-          const info = c.req.valid("json")
-          await Auth.set(providerID, info)
-          return c.json(true)
-        },
-      )
-      .delete(
-        "/auth/:providerID",
-        describeRoute({
-          summary: "Remove auth credentials",
-          description: "Remove authentication credentials",
-          operationId: "auth.remove",
-          responses: {
-            200: {
-              description: "Successfully removed authentication credentials",
-              content: {
-                "application/json": {
-                  schema: resolver(z.boolean()),
-                },
-              },
-            },
-            ...errors(400),
-          },
-        }),
-        validator(
-          "param",
-          z.object({
-            providerID: ProviderID.zod,
-          }),
-        ),
-        async (c) => {
-          const providerID = c.req.valid("param").providerID
-          await Auth.remove(providerID)
-          return c.json(true)
-        },
-      )
+    const InstanceRoutes: Hono = new Hono()
       .use(async (c, next) => {
         if (c.req.path === "/log") return next()
         const rawWorkspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace")
@@ -509,6 +376,143 @@ export namespace Server {
           return c.json(await Format.status())
         },
       )
+
+    return app
+      .onError((err, c) => {
+        log.error("failed", {
+          error: err,
+        })
+        if (err instanceof NamedError) {
+          let status: ContentfulStatusCode
+          if (err instanceof NotFoundError) status = 404
+          else if (err instanceof Provider.ModelNotFoundError) status = 400
+          else if (err.name === "ProviderAuthValidationFailed") status = 400
+          else if (err.name.startsWith("Worktree")) status = 400
+          else status = 500
+          return c.json(err.toObject(), { status })
+        }
+        if (err instanceof HTTPException) return err.getResponse()
+        const message = err instanceof Error && err.stack ? err.stack : err.toString()
+        return c.json(new NamedError.Unknown({ message }).toObject(), {
+          status: 500,
+        })
+      })
+      .use((c, next) => {
+        // Allow CORS preflight requests to succeed without auth.
+        // Browser clients sending Authorization headers will preflight with OPTIONS.
+        if (c.req.method === "OPTIONS") return next()
+        const password = Flag.OPENCODE_SERVER_PASSWORD
+        if (!password) return next()
+        const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode"
+        return basicAuth({ username, password })(c, next)
+      })
+      .use(async (c, next) => {
+        const skipLogging = c.req.path === "/log"
+        if (!skipLogging) {
+          log.info("request", {
+            method: c.req.method,
+            path: c.req.path,
+          })
+        }
+        const timer = log.time("request", {
+          method: c.req.method,
+          path: c.req.path,
+        })
+        await next()
+        if (!skipLogging) {
+          timer.stop()
+        }
+      })
+      .use(
+        cors({
+          origin(input) {
+            if (!input) return
+
+            if (input.startsWith("http://localhost:")) return input
+            if (input.startsWith("http://127.0.0.1:")) return input
+            if (
+              input === "tauri://localhost" ||
+              input === "http://tauri.localhost" ||
+              input === "https://tauri.localhost"
+            )
+              return input
+
+            // *.opencode.ai (https only, adjust if needed)
+            if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) {
+              return input
+            }
+            if (opts?.cors?.includes(input)) {
+              return input
+            }
+
+            return
+          },
+        }),
+      )
+      .route("/global", GlobalRoutes())
+      .put(
+        "/auth/:providerID",
+        describeRoute({
+          summary: "Set auth credentials",
+          description: "Set authentication credentials",
+          operationId: "auth.set",
+          responses: {
+            200: {
+              description: "Successfully set authentication credentials",
+              content: {
+                "application/json": {
+                  schema: resolver(z.boolean()),
+                },
+              },
+            },
+            ...errors(400),
+          },
+        }),
+        validator(
+          "param",
+          z.object({
+            providerID: ProviderID.zod,
+          }),
+        ),
+        validator("json", Auth.Info.zod),
+        async (c) => {
+          const providerID = c.req.valid("param").providerID
+          const info = c.req.valid("json")
+          await Auth.set(providerID, info)
+          return c.json(true)
+        },
+      )
+      .delete(
+        "/auth/:providerID",
+        describeRoute({
+          summary: "Remove auth credentials",
+          description: "Remove authentication credentials",
+          operationId: "auth.remove",
+          responses: {
+            200: {
+              description: "Successfully removed authentication credentials",
+              content: {
+                "application/json": {
+                  schema: resolver(z.boolean()),
+                },
+              },
+            },
+            ...errors(400),
+          },
+        }),
+        validator(
+          "param",
+          z.object({
+            providerID: ProviderID.zod,
+          }),
+        ),
+        async (c) => {
+          const providerID = c.req.valid("param").providerID
+          await Auth.remove(providerID)
+          return c.json(true)
+        },
+      )
+      .route("/", InstanceRoutes)
       .all("/*", async (c) => {
         const embeddedWebUI = await embeddedUIPromise
         const path = c.req.path
@@ -516,13 +520,14 @@ export namespace Server {
         if (embeddedWebUI) {
           const match = embeddedWebUI[path.replace(/^\//, "")] ?? embeddedWebUI["index.html"] ?? null
           if (!match) return c.json({ error: "Not Found" }, 404)
-          const file = Bun.file(match)
-          if (await file.exists()) {
-            c.header("Content-Type", file.type)
-            if (file.type.startsWith("text/html")) {
+          const file = await Filesystem.readArrayBuffer(match).catch(() => null)
+          if (file) {
+            const mime = Filesystem.mimeType(match)
+            c.header("Content-Type", mime)
+            if (mime.startsWith("text/html")) {
               c.header("Content-Security-Policy", DEFAULT_CSP)
             }
-            return c.body(await file.arrayBuffer())
+            return c.body(file)
           } else {
             return c.json({ error: "Not Found" }, 404)
           }
@@ -543,7 +548,7 @@ export namespace Server {
           response.headers.set("Content-Security-Policy", csp(hash))
           return response
         }
-      }) as unknown as Hono
+      }) as Hono
   }
 
   export async function openapi() {