2026-01-19 11:32:15 +00:00
import type {
GatewayAuthConfig ,
GatewayBindMode ,
GatewayTailscaleConfig ,
loadConfig ,
} from "../config/config.js" ;
2026-01-14 09:11:21 +00:00
import {
assertGatewayAuthConfigured ,
type ResolvedGatewayAuth ,
resolveGatewayAuth ,
} from "./auth.js" ;
2026-01-22 23:41:28 +00:00
import { normalizeControlUiBasePath } from "./control-ui-shared.js" ;
2026-01-14 09:11:21 +00:00
import { resolveHooksConfig } from "./hooks.js" ;
import { isLoopbackHost , resolveGatewayBindHost } from "./net.js" ;
export type GatewayRuntimeConfig = {
bindHost : string ;
controlUiEnabled : boolean ;
openAiChatCompletionsEnabled : boolean ;
2026-01-19 10:44:48 +01:00
openResponsesEnabled : boolean ;
2026-01-20 07:35:29 +00:00
openResponsesConfig? : import ( "../config/types.gateway.js" ) . GatewayHttpResponsesConfig ;
2026-01-14 09:11:21 +00:00
controlUiBasePath : string ;
2026-02-03 13:56:20 -05:00
controlUiRoot? : string ;
2026-01-14 09:11:21 +00:00
resolvedAuth : ResolvedGatewayAuth ;
authMode : ResolvedGatewayAuth [ "mode" ] ;
tailscaleConfig : GatewayTailscaleConfig ;
tailscaleMode : "off" | "serve" | "funnel" ;
hooksConfig : ReturnType < typeof resolveHooksConfig > ;
canvasHostEnabled : boolean ;
} ;
export async function resolveGatewayRuntimeConfig ( params : {
cfg : ReturnType < typeof loadConfig > ;
port : number ;
2026-01-19 04:50:07 +00:00
bind? : GatewayBindMode ;
2026-01-14 09:11:21 +00:00
host? : string ;
controlUiEnabled? : boolean ;
openAiChatCompletionsEnabled? : boolean ;
2026-01-19 10:44:48 +01:00
openResponsesEnabled? : boolean ;
2026-01-14 09:11:21 +00:00
auth? : GatewayAuthConfig ;
tailscale? : GatewayTailscaleConfig ;
} ) : Promise < GatewayRuntimeConfig > {
const bindMode = params . bind ? ? params . cfg . gateway ? . bind ? ? "loopback" ;
const customBindHost = params . cfg . gateway ? . customBindHost ;
2026-01-14 14:31:43 +00:00
const bindHost = params . host ? ? ( await resolveGatewayBindHost ( bindMode , customBindHost ) ) ;
2026-01-14 09:11:21 +00:00
const controlUiEnabled =
params . controlUiEnabled ? ? params . cfg . gateway ? . controlUi ? . enabled ? ? true ;
const openAiChatCompletionsEnabled =
params . openAiChatCompletionsEnabled ? ?
params . cfg . gateway ? . http ? . endpoints ? . chatCompletions ? . enabled ? ?
false ;
2026-01-20 07:35:29 +00:00
const openResponsesConfig = params . cfg . gateway ? . http ? . endpoints ? . responses ;
const openResponsesEnabled = params . openResponsesEnabled ? ? openResponsesConfig ? . enabled ? ? false ;
2026-01-14 14:31:43 +00:00
const controlUiBasePath = normalizeControlUiBasePath ( params . cfg . gateway ? . controlUi ? . basePath ) ;
2026-02-03 13:56:20 -05:00
const controlUiRootRaw = params . cfg . gateway ? . controlUi ? . root ;
const controlUiRoot =
typeof controlUiRootRaw === "string" && controlUiRootRaw . trim ( ) . length > 0
? controlUiRootRaw . trim ( )
: undefined ;
2026-01-14 09:11:21 +00:00
const authBase = params . cfg . gateway ? . auth ? ? { } ;
const authOverrides = params . auth ? ? { } ;
const authConfig = {
. . . authBase ,
. . . authOverrides ,
} ;
const tailscaleBase = params . cfg . gateway ? . tailscale ? ? { } ;
const tailscaleOverrides = params . tailscale ? ? { } ;
const tailscaleConfig = {
. . . tailscaleBase ,
. . . tailscaleOverrides ,
} ;
const tailscaleMode = tailscaleConfig . mode ? ? "off" ;
const resolvedAuth = resolveGatewayAuth ( {
authConfig ,
env : process.env ,
tailscaleMode ,
} ) ;
const authMode : ResolvedGatewayAuth [ "mode" ] = resolvedAuth . mode ;
2026-01-26 12:56:33 +00:00
const hasToken = typeof resolvedAuth . token === "string" && resolvedAuth . token . trim ( ) . length > 0 ;
const hasPassword =
typeof resolvedAuth . password === "string" && resolvedAuth . password . trim ( ) . length > 0 ;
const hasSharedSecret =
( authMode === "token" && hasToken ) || ( authMode === "password" && hasPassword ) ;
2026-01-14 09:11:21 +00:00
const hooksConfig = resolveHooksConfig ( params . cfg ) ;
const canvasHostEnabled =
2026-01-30 03:15:10 +01:00
process . env . OPENCLAW_SKIP_CANVAS_HOST !== "1" && params . cfg . canvasHost ? . enabled !== false ;
2026-01-14 09:11:21 +00:00
assertGatewayAuthConfigured ( resolvedAuth ) ;
if ( tailscaleMode === "funnel" && authMode !== "password" ) {
throw new Error (
2026-01-30 03:15:10 +01:00
"tailscale funnel requires gateway auth mode=password (set gateway.auth.password or OPENCLAW_GATEWAY_PASSWORD)" ,
2026-01-14 09:11:21 +00:00
) ;
}
if ( tailscaleMode !== "off" && ! isLoopbackHost ( bindHost ) ) {
2026-01-14 14:31:43 +00:00
throw new Error ( "tailscale serve/funnel requires gateway bind=loopback (127.0.0.1)" ) ;
2026-01-14 09:11:21 +00:00
}
2026-01-26 12:56:33 +00:00
if ( ! isLoopbackHost ( bindHost ) && ! hasSharedSecret ) {
2026-01-14 09:11:21 +00:00
throw new Error (
2026-01-30 03:15:10 +01:00
` refusing to bind gateway to ${ bindHost } : ${ params . port } without auth (set gateway.auth.token/password, or set OPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD) ` ,
2026-01-14 09:11:21 +00:00
) ;
}
return {
bindHost ,
controlUiEnabled ,
openAiChatCompletionsEnabled ,
2026-01-19 10:44:48 +01:00
openResponsesEnabled ,
2026-01-20 07:35:29 +00:00
openResponsesConfig : openResponsesConfig
? { . . . openResponsesConfig , enabled : openResponsesEnabled }
: undefined ,
2026-01-14 09:11:21 +00:00
controlUiBasePath ,
2026-02-03 13:56:20 -05:00
controlUiRoot ,
2026-01-14 09:11:21 +00:00
resolvedAuth ,
authMode ,
tailscaleConfig ,
tailscaleMode ,
hooksConfig ,
canvasHostEnabled ,
} ;
}