2026-04-11 20:55:17 +00:00
import { generateSpecs } from "hono-openapi"
2025-05-31 18:41:00 +00:00
import { Hono } from "hono"
2026-04-17 06:06:20 +00:00
import type { MiddlewareHandler } from "hono"
2026-04-11 20:55:17 +00:00
import { adapter } from "#hono"
2026-03-09 21:13:42 +00:00
import { lazy } from "@/util/lazy"
2026-04-17 06:06:20 +00:00
import { Log } from "@/util"
import { Flag } from "@/flag/flag"
import { Instance } from "@/project/instance"
import { InstanceBootstrap } from "@/project/bootstrap"
import { AppRuntime } from "@/effect/app-runtime"
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { WorkspaceID } from "@/control-plane/schema"
import { WorkspaceContext } from "@/control-plane/workspace-context"
import { MDNS } from "./mdns"
2026-04-11 20:55:17 +00:00
import { AuthMiddleware , CompressionMiddleware , CorsMiddleware , ErrorMiddleware , LoggerMiddleware } from "./middleware"
2026-04-16 01:04:37 +00:00
import { FenceMiddleware } from "./fence"
2026-03-25 14:47:40 +00:00
import { initProjectors } from "./projectors"
2026-04-17 06:06:20 +00:00
import { InstanceRoutes } from "./routes/instance"
import { ControlPlaneRoutes } from "./routes/control"
import { UIRoutes } from "./routes/ui"
import { GlobalRoutes } from "./routes/global"
import { WorkspaceRouterMiddleware } from "./workspace"
2025-06-09 18:01:11 +00:00
2025-11-20 14:42:24 +00:00
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
globalThis . AI_SDK_LOG_WARNINGS = false
2026-03-25 14:47:40 +00:00
initProjectors ( )
2026-04-16 23:51:01 +00:00
const log = Log . create ( { service : "server" } )
2026-04-11 20:55:17 +00:00
2026-04-16 23:51:01 +00:00
export type Listener = {
hostname : string
port : number
url : URL
stop : ( close? : boolean ) = > Promise < void >
}
2025-12-30 03:05:08 +00:00
2026-04-16 23:51:01 +00:00
export const Default = lazy ( ( ) = > create ( { } ) )
2026-04-16 01:04:37 +00:00
2026-04-16 23:51:01 +00:00
function create ( opts : { cors? : string [ ] } ) {
const app = new Hono ( )
2026-04-17 06:06:20 +00:00
. onError ( ErrorMiddleware )
. use ( AuthMiddleware )
. use ( LoggerMiddleware )
. use ( CompressionMiddleware )
. use ( CorsMiddleware ( opts ) )
. route ( "/global" , GlobalRoutes ( ) )
2026-04-16 23:51:01 +00:00
const runtime = adapter . create ( app )
2026-04-16 01:04:37 +00:00
2026-04-17 06:06:20 +00:00
function InstanceMiddleware ( workspaceID? : WorkspaceID ) : MiddlewareHandler {
return async ( c , next ) = > {
const raw = c . req . query ( "directory" ) || c . req . header ( "x-opencode-directory" ) || process . cwd ( )
const directory = AppFileSystem . resolve (
( ( ) = > {
try {
return decodeURIComponent ( raw )
} catch {
return raw
}
} ) ( ) ,
)
return WorkspaceContext . provide ( {
workspaceID ,
async fn() {
return Instance . provide ( {
directory ,
init : ( ) = > AppRuntime . runPromise ( InstanceBootstrap ) ,
async fn() {
return next ( )
} ,
} )
} ,
} )
}
}
2026-04-16 23:51:01 +00:00
if ( Flag . OPENCODE_WORKSPACE_ID ) {
2026-04-06 17:24:55 +00:00
return {
2026-04-11 20:55:17 +00:00
app : app
2026-04-17 06:06:20 +00:00
. use ( InstanceMiddleware ( Flag . OPENCODE_WORKSPACE_ID ? WorkspaceID . make ( Flag . OPENCODE_WORKSPACE_ID ) : undefined ) )
2026-04-16 23:51:01 +00:00
. use ( FenceMiddleware )
. route ( "/" , InstanceRoutes ( runtime . upgradeWebSocket ) ) ,
2026-04-11 20:55:17 +00:00
runtime ,
2026-04-06 17:24:55 +00:00
}
2026-03-27 15:51:21 +00:00
}
2026-03-09 21:13:42 +00:00
2026-04-16 23:51:01 +00:00
return {
app : app
2026-04-17 06:06:20 +00:00
. use ( InstanceMiddleware ( ) )
2026-04-16 23:51:01 +00:00
. route ( "/" , ControlPlaneRoutes ( ) )
2026-04-17 06:06:20 +00:00
. use ( WorkspaceRouterMiddleware ( runtime . upgradeWebSocket ) )
2026-04-16 23:51:01 +00:00
. route ( "/" , InstanceRoutes ( runtime . upgradeWebSocket ) )
. route ( "/" , UIRoutes ( ) ) ,
runtime ,
2025-05-18 18:28:08 +00:00
}
2026-04-16 23:51:01 +00:00
}
2025-05-18 18:13:04 +00:00
2026-04-16 23:51:01 +00:00
export async function openapi() {
// Build a fresh app with all routes registered directly so
// hono-openapi can see describeRoute metadata (`.route()` wraps
// handlers when the sub-app has a custom errorHandler, which
// strips the metadata symbol).
const { app } = create ( { } )
const result = await generateSpecs ( app , {
documentation : {
info : {
title : "opencode" ,
version : "1.0.0" ,
description : "opencode api" ,
} ,
openapi : "3.1.1" ,
} ,
} )
return result
}
2026-03-11 21:41:49 +00:00
2026-04-16 23:51:01 +00:00
export let url : URL
2025-12-26 20:24:44 +00:00
2026-04-16 23:51:01 +00:00
export async function listen ( opts : {
port : number
hostname : string
mdns? : boolean
mdnsDomain? : string
cors? : string [ ]
} ) : Promise < Listener > {
const built = create ( opts )
const server = await built . runtime . listen ( opts )
2026-04-06 17:24:55 +00:00
2026-04-16 23:51:01 +00:00
const next = new URL ( "http://localhost" )
next . hostname = opts . hostname
next . port = String ( server . port )
url = next
2025-12-26 20:24:44 +00:00
2026-04-16 23:51:01 +00:00
const mdns =
opts . mdns &&
server . port &&
opts . hostname !== "127.0.0.1" &&
opts . hostname !== "localhost" &&
opts . hostname !== "::1"
if ( mdns ) {
MDNS . publish ( server . port , opts . mdnsDomain )
} else if ( opts . mdns ) {
log . warn ( "mDNS enabled but hostname is loopback; skipping mDNS publish" )
}
let closing : Promise < void > | undefined
return {
hostname : opts.hostname ,
port : server.port ,
url : next ,
stop ( close? : boolean ) {
closing ? ? = ( async ( ) = > {
if ( mdns ) MDNS . unpublish ( )
await server . stop ( close )
} ) ( )
return closing
} ,
2025-05-18 01:31:42 +00:00
}
}
2026-04-16 23:51:01 +00:00
export * as Server from "./server"