feat(server): wire Server.listen() through native HttpApi listener (kill-switch)
When the effect-httpapi backend is selected, Server.listen() now delegates to HttpApiListener.listen() — a native Bun.serve listener with inline WebSocket upgrade handling — instead of routing through the Hono runtime adapter (Hono.fetch + createBunWebSocket). The Hono backend path is unchanged, and a kill-switch env var (OPENCODE_HTTPAPI_LEGACY_LISTENER) forces the effect-httpapi backend back through the Hono adapter as an escape hatch if the native listener regresses for a user. This unblocks the Hono deletion arc by giving the native listener real production traffic on dev/beta/local channels (where OPENCODE_EXPERIMENTAL_HTTPAPI defaults on) while leaving prod/latest channels on the Hono path.
This commit is contained in:
parent
39288a6953
commit
adc5f528af
2 changed files with 35 additions and 10 deletions
|
|
@ -94,6 +94,12 @@ export const Flag = {
|
||||||
OPENCODE_EXPERIMENTAL_HTTPAPI:
|
OPENCODE_EXPERIMENTAL_HTTPAPI:
|
||||||
truthy("OPENCODE_EXPERIMENTAL_HTTPAPI") ||
|
truthy("OPENCODE_EXPERIMENTAL_HTTPAPI") ||
|
||||||
(!falsy("OPENCODE_EXPERIMENTAL_HTTPAPI") && HTTPAPI_DEFAULT_ON_CHANNELS.has(InstallationChannel)),
|
(!falsy("OPENCODE_EXPERIMENTAL_HTTPAPI") && HTTPAPI_DEFAULT_ON_CHANNELS.has(InstallationChannel)),
|
||||||
|
// Kill-switch that forces the effect-httpapi backend back through the legacy
|
||||||
|
// hono runtime adapter (Hono.fetch + createBunWebSocket) instead of the
|
||||||
|
// native Bun.serve listener. Defaults to false; set to "true"/"1" to revert
|
||||||
|
// if the native listener regresses for a user. Has no effect when the hono
|
||||||
|
// backend is selected.
|
||||||
|
OPENCODE_HTTPAPI_LEGACY_LISTENER: truthy("OPENCODE_HTTPAPI_LEGACY_LISTENER"),
|
||||||
OPENCODE_EXPERIMENTAL_WORKSPACES: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES"),
|
OPENCODE_EXPERIMENTAL_WORKSPACES: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES"),
|
||||||
OPENCODE_EXPERIMENTAL_EVENT_SYSTEM: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"),
|
OPENCODE_EXPERIMENTAL_EVENT_SYSTEM: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { InstanceMiddleware } from "./routes/instance/middleware"
|
||||||
import { WorkspaceRoutes } from "./routes/control/workspace"
|
import { WorkspaceRoutes } from "./routes/control/workspace"
|
||||||
import { ExperimentalHttpApiServer } from "./routes/instance/httpapi/server"
|
import { ExperimentalHttpApiServer } from "./routes/instance/httpapi/server"
|
||||||
import { PublicApi } from "./routes/instance/httpapi/public"
|
import { PublicApi } from "./routes/instance/httpapi/public"
|
||||||
|
import { HttpApiListener } from "./httpapi-listener"
|
||||||
import * as ServerBackend from "./backend"
|
import * as ServerBackend from "./backend"
|
||||||
import type { CorsOptions } from "./cors"
|
import type { CorsOptions } from "./cors"
|
||||||
|
|
||||||
|
|
@ -182,35 +183,53 @@ export async function openapiHono() {
|
||||||
export let url: URL
|
export let url: URL
|
||||||
|
|
||||||
export async function listen(opts: ListenOptions): Promise<Listener> {
|
export async function listen(opts: ListenOptions): Promise<Listener> {
|
||||||
const built = create(opts)
|
const selected = select()
|
||||||
const server = await built.runtime.listen(opts)
|
const native = selected.backend === "effect-httpapi" && !Flag.OPENCODE_HTTPAPI_LEGACY_LISTENER
|
||||||
|
let inner: Listener
|
||||||
|
if (native) {
|
||||||
|
log.info("server backend selected", {
|
||||||
|
...ServerBackend.attributes(selected),
|
||||||
|
"opencode.server.listener": "bun-native",
|
||||||
|
})
|
||||||
|
inner = await HttpApiListener.listen(opts)
|
||||||
|
} else {
|
||||||
|
const built = create(opts)
|
||||||
|
const server = await built.runtime.listen(opts)
|
||||||
|
const innerUrl = new URL("http://localhost")
|
||||||
|
innerUrl.hostname = opts.hostname
|
||||||
|
innerUrl.port = String(server.port)
|
||||||
|
inner = {
|
||||||
|
hostname: opts.hostname,
|
||||||
|
port: server.port,
|
||||||
|
url: innerUrl,
|
||||||
|
stop: (close?: boolean) => server.stop(close),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const next = new URL("http://localhost")
|
const next = new URL(inner.url)
|
||||||
next.hostname = opts.hostname
|
|
||||||
next.port = String(server.port)
|
|
||||||
url = next
|
url = next
|
||||||
|
|
||||||
const mdns =
|
const mdns =
|
||||||
opts.mdns &&
|
opts.mdns &&
|
||||||
server.port &&
|
inner.port &&
|
||||||
opts.hostname !== "127.0.0.1" &&
|
opts.hostname !== "127.0.0.1" &&
|
||||||
opts.hostname !== "localhost" &&
|
opts.hostname !== "localhost" &&
|
||||||
opts.hostname !== "::1"
|
opts.hostname !== "::1"
|
||||||
if (mdns) {
|
if (mdns) {
|
||||||
MDNS.publish(server.port, opts.mdnsDomain)
|
MDNS.publish(inner.port, opts.mdnsDomain)
|
||||||
} else if (opts.mdns) {
|
} else if (opts.mdns) {
|
||||||
log.warn("mDNS enabled but hostname is loopback; skipping mDNS publish")
|
log.warn("mDNS enabled but hostname is loopback; skipping mDNS publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
let closing: Promise<void> | undefined
|
let closing: Promise<void> | undefined
|
||||||
return {
|
return {
|
||||||
hostname: opts.hostname,
|
hostname: inner.hostname,
|
||||||
port: server.port,
|
port: inner.port,
|
||||||
url: next,
|
url: next,
|
||||||
stop(close?: boolean) {
|
stop(close?: boolean) {
|
||||||
closing ??= (async () => {
|
closing ??= (async () => {
|
||||||
if (mdns) MDNS.unpublish()
|
if (mdns) MDNS.unpublish()
|
||||||
await server.stop(close)
|
await inner.stop(close)
|
||||||
})()
|
})()
|
||||||
return closing
|
return closing
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue