forked from anomalyco/opencode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmiddleware.ts
More file actions
91 lines (83 loc) · 3.46 KB
/
middleware.ts
File metadata and controls
91 lines (83 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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"
import { isPtyConnectPath, PTY_CONNECT_TICKET_QUERY } from "./shared/pty-ticket"
import { isPublicUIPath } from "./shared/public-ui"
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()
if (isPublicUIPath(c.req.method, c.req.path)) return next()
if (isPtyConnectPath(c.req.path) && c.req.query(PTY_CONNECT_TICKET_QUERY)) 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,
// If this logger grows full-URL fields, redact auth_token and ticket query params.
...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)
}