import type { GatewayAuthConfig, GatewayBindMode, GatewayTailscaleConfig, loadConfig, } from "../config/config.js"; import { assertGatewayAuthConfigured, type ResolvedGatewayAuth, resolveGatewayAuth, } from "./auth.js"; import { normalizeControlUiBasePath } from "./control-ui.js"; import { resolveHooksConfig } from "./hooks.js"; import { isLoopbackHost, resolveGatewayBindHost } from "./net.js"; export type GatewayRuntimeConfig = { bindHost: string; controlUiEnabled: boolean; openAiChatCompletionsEnabled: boolean; openResponsesEnabled: boolean; controlUiBasePath: string; resolvedAuth: ResolvedGatewayAuth; authMode: ResolvedGatewayAuth["mode"]; tailscaleConfig: GatewayTailscaleConfig; tailscaleMode: "off" | "serve" | "funnel"; hooksConfig: ReturnType; canvasHostEnabled: boolean; }; export async function resolveGatewayRuntimeConfig(params: { cfg: ReturnType; port: number; bind?: GatewayBindMode; host?: string; controlUiEnabled?: boolean; openAiChatCompletionsEnabled?: boolean; openResponsesEnabled?: boolean; auth?: GatewayAuthConfig; tailscale?: GatewayTailscaleConfig; }): Promise { const bindMode = params.bind ?? params.cfg.gateway?.bind ?? "loopback"; const customBindHost = params.cfg.gateway?.customBindHost; const bindHost = params.host ?? (await resolveGatewayBindHost(bindMode, customBindHost)); const controlUiEnabled = params.controlUiEnabled ?? params.cfg.gateway?.controlUi?.enabled ?? true; const openAiChatCompletionsEnabled = params.openAiChatCompletionsEnabled ?? params.cfg.gateway?.http?.endpoints?.chatCompletions?.enabled ?? false; const openResponsesEnabled = params.openResponsesEnabled ?? params.cfg.gateway?.http?.endpoints?.responses?.enabled ?? false; const controlUiBasePath = normalizeControlUiBasePath(params.cfg.gateway?.controlUi?.basePath); 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; const hooksConfig = resolveHooksConfig(params.cfg); const canvasHostEnabled = process.env.CLAWDBOT_SKIP_CANVAS_HOST !== "1" && params.cfg.canvasHost?.enabled !== false; assertGatewayAuthConfigured(resolvedAuth); if (tailscaleMode === "funnel" && authMode !== "password") { throw new Error( "tailscale funnel requires gateway auth mode=password (set gateway.auth.password or CLAWDBOT_GATEWAY_PASSWORD)", ); } if (tailscaleMode !== "off" && !isLoopbackHost(bindHost)) { throw new Error("tailscale serve/funnel requires gateway bind=loopback (127.0.0.1)"); } if (!isLoopbackHost(bindHost) && authMode === "none") { throw new Error( `refusing to bind gateway to ${bindHost}:${params.port} without auth (set gateway.auth.token or CLAWDBOT_GATEWAY_TOKEN, or pass --token)`, ); } return { bindHost, controlUiEnabled, openAiChatCompletionsEnabled, openResponsesEnabled, controlUiBasePath, resolvedAuth, authMode, tailscaleConfig, tailscaleMode, hooksConfig, canvasHostEnabled, }; }