Bläddra i källkod

fix: ensure effect server middleware properly parses errors (#25717)

Aiden Cline 3 månader sedan
förälder
incheckning
d431a0e4b4

+ 58 - 0
packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts

@@ -0,0 +1,58 @@
+import { Provider } from "@/provider/provider"
+import { Session } from "@/session/session"
+import { NotFoundError } from "@/storage/storage"
+import { iife } from "@/util/iife"
+import { NamedError } from "@opencode-ai/core/util/error"
+import * as Log from "@opencode-ai/core/util/log"
+import { Cause, Effect } from "effect"
+import { HttpRouter, HttpServerError, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"
+
+const log = Log.create({ service: "server" })
+
+// Keep typed HttpApi failures on their declared error path; this boundary only replaces defect-only empty 500s.
+export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) =>
+  effect.pipe(
+    Effect.catchCause((cause) => {
+      const defect = cause.reasons.filter(Cause.isDieReason).find((reason) => {
+        if (HttpServerResponse.isHttpServerResponse(reason.defect)) return false
+        if (HttpServerError.isHttpServerError(reason.defect)) return false
+        if (HttpServerRespondable.isRespondable(reason.defect)) return false
+        return true
+      })
+      if (!defect) return Effect.failCause(cause)
+
+      const error = defect.defect
+      log.error("failed", { error, cause: Cause.pretty(cause) })
+
+      if (error instanceof NamedError) {
+        return Effect.succeed(
+          HttpServerResponse.jsonUnsafe(error.toObject(), {
+            status: iife(() => {
+              if (error instanceof NotFoundError) return 404
+              if (error instanceof Provider.ModelNotFoundError) return 400
+              if (error.name === "ProviderAuthValidationFailed") return 400
+              if (error.name.startsWith("Worktree")) return 400
+              return 500
+            }),
+          }),
+        )
+      }
+      if (error instanceof Session.BusyError) {
+        return Effect.succeed(
+          HttpServerResponse.jsonUnsafe(new NamedError.Unknown({ message: error.message }).toObject(), {
+            status: 400,
+          }),
+        )
+      }
+
+      return Effect.succeed(
+        HttpServerResponse.jsonUnsafe(
+          new NamedError.Unknown({
+            message: error instanceof Error && error.stack ? error.stack : String(error),
+          }).toObject(),
+          { status: 500 },
+        ),
+      )
+    }),
+  ),
+).layer

+ 2 - 0
packages/opencode/src/server/routes/instance/httpapi/server.ts

@@ -73,6 +73,7 @@ import { workspaceRouterMiddleware, workspaceRoutingLayer } from "./middleware/w
 import { disposeMiddleware } from "./lifecycle"
 import { memoMap } from "@opencode-ai/core/effect/memo-map"
 import * as ServerBackend from "@/server/backend"
+import { errorLayer } from "./middleware/error"
 
 export const context = Context.makeUnsafe<unknown>(new Map())
 
@@ -144,6 +145,7 @@ const uiRoute = HttpRouter.use((router) =>
 export function createRoutes(corsOptions?: CorsOptions) {
   return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
     Layer.provide([
+      errorLayer,
       cors(corsOptions),
       runtime,
       Account.defaultLayer,