import { Provider } from "@/provider/provider" import { NamedError } from "@opencode-ai/core/util/error" import { NotFoundError } from "@/storage/storage" import { Session } from "@/session/session" import type { ContentfulStatusCode } from "hono/utils/http-status" import type { ErrorHandler, MiddlewareHandler } from "hono" import { HTTPException } from "hono/http-exception" import * as Log from "@opencode-ai/core/util/log" import { Flag } from "@opencode-ai/core/flag/flag" import { basicAuth } from "hono/basic-auth" import { cors } from "hono/cors" import { compress } from "hono/compress" import * as ServerBackend from "./backend" import { isAllowedCorsOrigin, type CorsOptions } from "./cors" const log = Log.create({ service: "server" }) export const ErrorMiddleware: ErrorHandler = (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 Session.BusyError) { return c.json(new NamedError.Unknown({ message: err.message }).toObject(), { status: 400 }) } 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, }) } export const AuthMiddleware: MiddlewareHandler = (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" if (c.req.query("auth_token")) c.req.raw.headers.set("authorization", `Basic ${c.req.query("auth_token")}`) return basicAuth({ username, password })(c, next) } export function LoggerMiddleware(backendAttributes: ServerBackend.Attributes): MiddlewareHandler { return async (c, next) => { const skip = c.req.path === "/log" if (skip) return next() const attributes = { method: c.req.method, path: c.req.path, ...backendAttributes, } log.info("request", attributes) const timer = log.time("request", attributes) await next() timer.stop() } } export function CorsMiddleware(opts?: CorsOptions): MiddlewareHandler { return cors({ maxAge: 86_400, origin(input) { if (isAllowedCorsOrigin(input, opts)) return input }, }) } const zipped = compress() export const CompressionMiddleware: MiddlewareHandler = (c, next) => { const path = c.req.path const method = c.req.method if (path === "/event" || path === "/global/event") return next() if (method === "POST" && /\/session\/[^/]+\/(message|prompt_async)$/.test(path)) return next() return zipped(c, next) }