import type { Server as HttpServer } from "node:http"; import { WebSocketServer } from "ws"; import { CANVAS_HOST_PATH } from "../canvas-host/a2ui.js"; import { type CanvasHostHandler, createCanvasHostHandler } from "../canvas-host/server.js"; import type { CliDeps } from "../cli/deps.js"; import type { createSubsystemLogger } from "../logging/subsystem.js"; import type { PluginRegistry } from "../plugins/registry.js"; import type { RuntimeEnv } from "../runtime.js"; import type { AuthRateLimiter } from "./auth-rate-limit.js"; import type { ResolvedGatewayAuth } from "./auth.js"; import type { ChatAbortControllerEntry } from "./chat-abort.js"; import type { ControlUiRootState } from "./control-ui.js"; import type { HooksConfigResolved } from "./hooks.js"; import { resolveGatewayListenHosts } from "./net.js"; import { createGatewayBroadcaster, type GatewayBroadcastFn, type GatewayBroadcastToConnIdsFn, } from "./server-broadcast.js"; import { type ChatRunEntry, createChatRunState, createToolEventRecipientRegistry, } from "./server-chat.js"; import { MAX_PAYLOAD_BYTES } from "./server-constants.js"; import { attachGatewayUpgradeHandler, createGatewayHttpServer } from "./server-http.js"; import type { DedupeEntry } from "./server-shared.js"; import { createGatewayHooksRequestHandler } from "./server/hooks.js"; import { listenGatewayHttpServer } from "./server/http-listen.js"; import { createGatewayPluginRequestHandler } from "./server/plugins-http.js"; import type { GatewayTlsRuntime } from "./server/tls.js"; import type { GatewayWsClient } from "./server/ws-types.js"; export async function createGatewayRuntimeState(params: { cfg: import("../config/config.js").OpenClawConfig; bindHost: string; port: number; controlUiEnabled: boolean; controlUiBasePath: string; controlUiRoot?: ControlUiRootState; openAiChatCompletionsEnabled: boolean; openResponsesEnabled: boolean; openResponsesConfig?: import("../config/types.gateway.js").GatewayHttpResponsesConfig; resolvedAuth: ResolvedGatewayAuth; /** Optional rate limiter for auth brute-force protection. */ rateLimiter?: AuthRateLimiter; gatewayTls?: GatewayTlsRuntime; hooksConfig: () => HooksConfigResolved | null; pluginRegistry: PluginRegistry; deps: CliDeps; canvasRuntime: RuntimeEnv; canvasHostEnabled: boolean; allowCanvasHostInTests?: boolean; logCanvas: { info: (msg: string) => void; warn: (msg: string) => void }; log: { info: (msg: string) => void; warn: (msg: string) => void }; logHooks: ReturnType; logPlugins: ReturnType; }): Promise<{ canvasHost: CanvasHostHandler | null; httpServer: HttpServer; httpServers: HttpServer[]; httpBindHosts: string[]; wss: WebSocketServer; clients: Set; broadcast: GatewayBroadcastFn; broadcastToConnIds: GatewayBroadcastToConnIdsFn; agentRunSeq: Map; dedupe: Map; chatRunState: ReturnType; chatRunBuffers: Map; chatDeltaSentAt: Map; addChatRun: (sessionId: string, entry: ChatRunEntry) => void; removeChatRun: ( sessionId: string, clientRunId: string, sessionKey?: string, ) => ChatRunEntry | undefined; chatAbortControllers: Map; toolEventRecipients: ReturnType; }> { let canvasHost: CanvasHostHandler | null = null; if (params.canvasHostEnabled) { try { const handler = await createCanvasHostHandler({ runtime: params.canvasRuntime, rootDir: params.cfg.canvasHost?.root, basePath: CANVAS_HOST_PATH, allowInTests: params.allowCanvasHostInTests, liveReload: params.cfg.canvasHost?.liveReload, }); if (handler.rootDir) { canvasHost = handler; params.logCanvas.info( `canvas host mounted at http://${params.bindHost}:${params.port}${CANVAS_HOST_PATH}/ (root ${handler.rootDir})`, ); } } catch (err) { params.logCanvas.warn(`canvas host failed to start: ${String(err)}`); } } const clients = new Set(); const { broadcast, broadcastToConnIds } = createGatewayBroadcaster({ clients }); const handleHooksRequest = createGatewayHooksRequestHandler({ deps: params.deps, getHooksConfig: params.hooksConfig, bindHost: params.bindHost, port: params.port, logHooks: params.logHooks, }); const handlePluginRequest = createGatewayPluginRequestHandler({ registry: params.pluginRegistry, log: params.logPlugins, }); const bindHosts = await resolveGatewayListenHosts(params.bindHost); const httpServers: HttpServer[] = []; const httpBindHosts: string[] = []; for (const host of bindHosts) { const httpServer = createGatewayHttpServer({ canvasHost, clients, controlUiEnabled: params.controlUiEnabled, controlUiBasePath: params.controlUiBasePath, controlUiRoot: params.controlUiRoot, openAiChatCompletionsEnabled: params.openAiChatCompletionsEnabled, openResponsesEnabled: params.openResponsesEnabled, openResponsesConfig: params.openResponsesConfig, handleHooksRequest, handlePluginRequest, resolvedAuth: params.resolvedAuth, rateLimiter: params.rateLimiter, tlsOptions: params.gatewayTls?.enabled ? params.gatewayTls.tlsOptions : undefined, }); try { await listenGatewayHttpServer({ httpServer, bindHost: host, port: params.port, }); httpServers.push(httpServer); httpBindHosts.push(host); } catch (err) { if (host === bindHosts[0]) { throw err; } params.log.warn( `gateway: failed to bind loopback alias ${host}:${params.port} (${String(err)})`, ); } } const httpServer = httpServers[0]; if (!httpServer) { throw new Error("Gateway HTTP server failed to start"); } const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD_BYTES, }); for (const server of httpServers) { attachGatewayUpgradeHandler({ httpServer: server, wss, canvasHost, clients, resolvedAuth: params.resolvedAuth, rateLimiter: params.rateLimiter, }); } const agentRunSeq = new Map(); const dedupe = new Map(); const chatRunState = createChatRunState(); const chatRunRegistry = chatRunState.registry; const chatRunBuffers = chatRunState.buffers; const chatDeltaSentAt = chatRunState.deltaSentAt; const addChatRun = chatRunRegistry.add; const removeChatRun = chatRunRegistry.remove; const chatAbortControllers = new Map(); const toolEventRecipients = createToolEventRecipientRegistry(); return { canvasHost, httpServer, httpServers, httpBindHosts, wss, clients, broadcast, broadcastToConnIds, agentRunSeq, dedupe, chatRunState, chatRunBuffers, chatDeltaSentAt, addChatRun, removeChatRun, chatAbortControllers, toolEventRecipients, }; }