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-11 20:55:17 +00:00
import { adapter } from "#hono"
2025-12-26 20:24:44 +00:00
import { MDNS } from "./mdns"
2026-03-09 21:13:42 +00:00
import { lazy } from "@/util/lazy"
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-27 15:51:21 +00:00
import { InstanceRoutes } from "./instance"
2026-03-25 14:47:40 +00:00
import { initProjectors } from "./projectors"
2026-04-16 03:15:58 +00:00
import { Log } from "@/util"
2026-04-16 01:04:37 +00:00
import { Flag } from "@/flag/flag"
2026-04-11 20:55:17 +00:00
import { ControlPlaneRoutes } from "./control"
import { UIRoutes } from "./ui"
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 ( )
const runtime = adapter . create ( app )
2026-04-16 01:04:37 +00:00
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
. onError ( ErrorMiddleware )
. use ( AuthMiddleware )
. use ( LoggerMiddleware )
. use ( CompressionMiddleware )
. use ( CorsMiddleware ( opts ) )
2026-04-16 23:51:01 +00:00
. use ( FenceMiddleware )
2026-04-11 20:55:17 +00:00
. route ( "/" , ControlPlaneRoutes ( ) )
2026-04-16 23:51:01 +00:00
. 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
. onError ( ErrorMiddleware )
. use ( AuthMiddleware )
. use ( LoggerMiddleware )
. use ( CompressionMiddleware )
. use ( CorsMiddleware ( opts ) )
. route ( "/" , ControlPlaneRoutes ( ) )
. 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"