fix: ensure effect server middleware properly parses errors (#25717)
This commit is contained in:
@@ -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
|
||||||
@@ -73,6 +73,7 @@ import { workspaceRouterMiddleware, workspaceRoutingLayer } from "./middleware/w
|
|||||||
import { disposeMiddleware } from "./lifecycle"
|
import { disposeMiddleware } from "./lifecycle"
|
||||||
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
||||||
import * as ServerBackend from "@/server/backend"
|
import * as ServerBackend from "@/server/backend"
|
||||||
|
import { errorLayer } from "./middleware/error"
|
||||||
|
|
||||||
export const context = Context.makeUnsafe<unknown>(new Map())
|
export const context = Context.makeUnsafe<unknown>(new Map())
|
||||||
|
|
||||||
@@ -144,6 +145,7 @@ const uiRoute = HttpRouter.use((router) =>
|
|||||||
export function createRoutes(corsOptions?: CorsOptions) {
|
export function createRoutes(corsOptions?: CorsOptions) {
|
||||||
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
|
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
|
||||||
Layer.provide([
|
Layer.provide([
|
||||||
|
errorLayer,
|
||||||
cors(corsOptions),
|
cors(corsOptions),
|
||||||
runtime,
|
runtime,
|
||||||
Account.defaultLayer,
|
Account.defaultLayer,
|
||||||
|
|||||||
Reference in New Issue
Block a user