UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

687 lines (686 loc) 29.9 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { d as resolveGatewayPort, f as resolveIncludeRoots, s as resolveConfigPath, y as resolveStateDir } from "./paths-mvMm5bYV.js"; import { r as resolveConfigEnvVars } from "./env-substitution-BWBLYD_t.js"; import { t as applyConfigEnvVars } from "./config-env-vars-DeC7to2c.js"; import { n as VERSION } from "./version-Crcn9X9T.js"; import { t as parseJsonWithJson5Fallback } from "./parse-json-compat-DvZKmwhP.js"; import { a as trimToUndefined } from "./credential-planner-wESmL_7M.js"; import "./credentials-irvgw8Le.js"; import { f as isLoopbackIpAddress } from "./ip-0oQXo6_w.js"; import { n as resolveGatewayAuth } from "./auth-resolve-BSKl_nHz.js"; import { a as resolveConfigIncludes, i as readConfigIncludeFileWithGuards } from "./includes-Cf57krG9.js"; import { o as redactSensitiveUrlLikeString } from "./redact-sensitive-url-Cf2Fdzd5.js"; import { i as GATEWAY_CLIENT_NAMES, r as GATEWAY_CLIENT_MODES } from "./client-info-CcqJJIan.js"; import "./version-51ymduTn.js"; import { a as isGatewayConnectAssemblyError, n as GatewayClient, u as loadDeviceAuthToken } from "./client-C2g2lFC5.js"; import { r as loadOrCreateDeviceIdentity } from "./device-identity-CEPJolq9.js"; import { t as loadGatewayTlsRuntime } from "./gateway-cyLi5C4L.js"; import { a as resolvePreauthHandshakeTimeoutMs, o as resolveSafeTimeoutDelayMs } from "./timeouts-DdTImbzl.js"; import { t as startGatewayClientWhenEventLoopReady } from "./client-start-readiness-BIoVQZze.js"; import { t as buildGatewayConnectionDetailsWithResolvers } from "./connection-details-ClaW_IO-.js"; import { n as resolveGatewayCredentialsWithSecretInputs } from "./credentials-secret-inputs-B4AcFF94.js"; import { t as canSkipGatewayConfigLoad } from "./explicit-connection-policy-pRPGrlMn.js"; import { a as isGatewayMethodClassified, t as CLI_DEFAULT_OPERATOR_SCOPES, u as resolveLeastPrivilegeOperatorScopesForMethod } from "./method-scopes-BtdN3y7s.js"; import fs from "node:fs"; import path from "node:path"; import { randomUUID } from "node:crypto"; //#region src/config/gateway-dispatch-config.ts const GATEWAY_DISPATCH_SHELL_ENV_EXPECTED_KEYS = ["OPENCLAW_GATEWAY_TOKEN", "OPENCLAW_GATEWAY_PASSWORD"]; const GATEWAY_DISPATCH_TOP_LEVEL_KEYS = [ "agents", "env", "gateway", "plugins", "secrets", "session" ]; function isPlainRecord(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function cloneConfigValue(value) { if (Array.isArray(value)) return value.map((entry) => cloneConfigValue(entry)); if (!isPlainRecord(value)) return value; const out = {}; for (const [key, child] of Object.entries(value)) out[key] = cloneConfigValue(child); return out; } function projectGatewayDispatchConfig(value) { if (!isPlainRecord(value)) return {}; const projected = {}; for (const key of GATEWAY_DISPATCH_TOP_LEVEL_KEYS) if (Object.hasOwn(value, key)) projected[key] = cloneConfigValue(value[key]); return projected; } function applyGatewayDispatchSessionDefaults(config) { if (config.session?.mainKey === void 0) return config; return { ...config, session: { ...config.session, mainKey: "main" } }; } function resolveIncludesForGatewayDispatch(parsed, configPath, env) { return resolveConfigIncludes(parsed, configPath, { readFile: (candidate) => fs.readFileSync(candidate, "utf-8"), readFileWithGuards: ({ includePath, resolvedPath, rootRealDir }) => readConfigIncludeFileWithGuards({ includePath, resolvedPath, rootRealDir, ioFs: fs }), parseJson: parseJsonWithJson5Fallback }, { allowedRoots: resolveIncludeRoots(env) }); } function resolveGatewayDispatchEnvVars(config, env) { if (isPlainRecord(config) && Object.hasOwn(config, "env")) applyConfigEnvVars(config, env); return resolveConfigEnvVars(config, env, { onMissing: () => void 0 }); } function readRawGatewayDispatchConfig(options = {}) { const env = options.env ?? process.env; const configPath = options.configPath ?? resolveConfigPath(env); if (!fs.existsSync(configPath)) return { config: {}, configPath }; return { config: applyGatewayDispatchSessionDefaults(projectGatewayDispatchConfig(resolveGatewayDispatchEnvVars(resolveIncludesForGatewayDispatch(parseJsonWithJson5Fallback(fs.readFileSync(configPath, "utf-8")), configPath, env), env))), configPath }; } function readGatewayDispatchConfig(options = {}) { return readRawGatewayDispatchConfig(options).config; } async function readGatewayDispatchConfigWithShellEnvFallback(options = {}) { const env = options.env ?? process.env; const firstRead = readRawGatewayDispatchConfig(options); const { loadShellEnvFallback, resolveShellEnvFallbackTimeoutMs, shouldDeferShellEnvFallback, shouldEnableShellEnvFallback } = await import("./shell-env-CB1dlcF1.js"); if ((shouldEnableShellEnvFallback(env) || firstRead.config.env?.shellEnv?.enabled === true) && !shouldDeferShellEnvFallback(env)) loadShellEnvFallback({ enabled: true, env, expectedKeys: [...GATEWAY_DISPATCH_SHELL_ENV_EXPECTED_KEYS], logger: options.logger ?? console, timeoutMs: firstRead.config.env?.shellEnv?.timeoutMs ?? resolveShellEnvFallbackTimeoutMs(env) }); return readGatewayDispatchConfig({ ...options, configPath: path.resolve(firstRead.configPath) }); } //#endregion //#region src/gateway/call.ts var GatewayTransportError = class extends Error { constructor(params) { super(params.message); this.name = "GatewayTransportError"; this.kind = params.kind; this.connectionDetails = params.connectionDetails; if (params.code !== void 0) this.code = params.code; if (params.reason !== void 0) this.reason = params.reason; if (params.timeoutMs !== void 0) this.timeoutMs = params.timeoutMs; } }; var GatewayCredentialsRequiredError = class extends Error { constructor(params) { super([ `gateway ${params.method} requires credentials before opening a websocket`, "Fix: configure gateway.auth token/password, pair this device, or pass --token/--password.", `Config: ${params.configPath}` ].join("\n")); this.name = "GatewayCredentialsRequiredError"; this.method = params.method; this.configPath = params.configPath; } }; var GatewayExplicitAuthRequiredError = class extends Error { constructor(message) { super(message); this.name = "GatewayExplicitAuthRequiredError"; } }; function firstGatewayErrorLine(message) { return message.split("\n", 1)[0]?.trim() || message; } function formatGatewayTransportErrorJson(value) { if (!isGatewayTransportError(value)) return null; return { ok: false, error: { type: "gateway_transport_error", kind: value.kind, message: firstGatewayErrorLine(value.message), ...value.code !== void 0 ? { code: value.code } : {}, ...value.reason !== void 0 ? { reason: value.reason } : {}, ...value.timeoutMs !== void 0 ? { timeoutMs: value.timeoutMs } : {} }, gateway: { url: redactSensitiveUrlLikeString(value.connectionDetails.url), urlSource: value.connectionDetails.urlSource, ...value.connectionDetails.bindDetail ? { bindDetail: value.connectionDetails.bindDetail } : {}, ...value.connectionDetails.remoteFallbackNote ? { remoteFallbackNote: value.connectionDetails.remoteFallbackNote } : {} } }; } function isGatewayTransportError(value) { if (value instanceof GatewayTransportError) return true; if (!(value instanceof Error) || value.name !== "GatewayTransportError") return false; const candidate = value; return (candidate.kind === "closed" || candidate.kind === "timeout") && typeof candidate.connectionDetails === "object" && candidate.connectionDetails !== null; } function isGatewayCredentialsRequiredError(value) { if (value instanceof GatewayCredentialsRequiredError) return true; if (!(value instanceof Error) || value.name !== "GatewayCredentialsRequiredError") return false; const candidate = value; return typeof candidate.method === "string" && typeof candidate.configPath === "string"; } function isGatewayExplicitAuthRequiredError(value) { return value instanceof Error && value.name === "GatewayExplicitAuthRequiredError"; } const defaultCreateGatewayClient = (opts) => new GatewayClient(opts); const defaultGetRuntimeConfig = async () => (await import("./io-BYWoyJ4I.js")).getRuntimeConfig(); const defaultGatewayCallDeps = { createGatewayClient: defaultCreateGatewayClient, getRuntimeConfig: defaultGetRuntimeConfig, loadOrCreateDeviceIdentity, resolveGatewayPort, resolveConfigPath, resolveStateDir, loadGatewayTlsRuntime, loadDeviceAuthToken }; const gatewayCallDeps = { ...defaultGatewayCallDeps }; async function stopGatewayClient(client) { try { await client.stopAndWait({ timeoutMs: 1e3 }); } catch { client.stop(); } } function resolveGatewayClientDisplayName(opts) { if (opts.clientDisplayName) return opts.clientDisplayName; const clientName = opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI; if ((opts.mode ?? GATEWAY_CLIENT_MODES.CLI) !== GATEWAY_CLIENT_MODES.BACKEND && clientName !== GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT) return; const method = opts.method.trim(); return method ? `gateway:${method}` : "gateway:request"; } async function loadGatewayConfig() { return await (typeof gatewayCallDeps.getRuntimeConfig === "function" ? gatewayCallDeps.getRuntimeConfig : typeof defaultGatewayCallDeps.getRuntimeConfig === "function" ? defaultGatewayCallDeps.getRuntimeConfig : defaultGetRuntimeConfig)(); } function loadGatewayConfigForConnectionDetails() { if (gatewayCallDeps.getRuntimeConfig !== defaultGetRuntimeConfig && typeof gatewayCallDeps.getRuntimeConfig === "function") { const config = gatewayCallDeps.getRuntimeConfig(); if (config && typeof config.then === "function") throw new Error("async gateway config loader is not supported for connection details"); return config; } return readGatewayDispatchConfig(); } function resolveGatewayStateDir(env) { return (typeof gatewayCallDeps.resolveStateDir === "function" ? gatewayCallDeps.resolveStateDir : resolveStateDir)(env); } function resolveGatewayConfigPath(env) { return (typeof gatewayCallDeps.resolveConfigPath === "function" ? gatewayCallDeps.resolveConfigPath : resolveConfigPath)(env, resolveGatewayStateDir(env)); } function resolveGatewayPortValue(config, env) { return (typeof gatewayCallDeps.resolveGatewayPort === "function" ? gatewayCallDeps.resolveGatewayPort : resolveGatewayPort)(config, env); } function buildGatewayConnectionDetails(options = {}) { return buildGatewayConnectionDetailsWithResolvers(options, { getRuntimeConfig: () => loadGatewayConfigForConnectionDetails(), resolveConfigPath: (env) => resolveGatewayConfigPath(env), resolveGatewayPort: (config, env) => resolveGatewayPortValue(config, env) }); } const testing = { setDepsForTests(deps) { gatewayCallDeps.createGatewayClient = deps?.createGatewayClient ?? defaultGatewayCallDeps.createGatewayClient; gatewayCallDeps.getRuntimeConfig = deps?.getRuntimeConfig ?? defaultGatewayCallDeps.getRuntimeConfig; gatewayCallDeps.loadOrCreateDeviceIdentity = deps?.loadOrCreateDeviceIdentity ?? defaultGatewayCallDeps.loadOrCreateDeviceIdentity; gatewayCallDeps.resolveGatewayPort = deps?.resolveGatewayPort ?? defaultGatewayCallDeps.resolveGatewayPort; gatewayCallDeps.resolveConfigPath = deps?.resolveConfigPath ?? defaultGatewayCallDeps.resolveConfigPath; gatewayCallDeps.resolveStateDir = deps?.resolveStateDir ?? defaultGatewayCallDeps.resolveStateDir; gatewayCallDeps.loadGatewayTlsRuntime = deps?.loadGatewayTlsRuntime ?? defaultGatewayCallDeps.loadGatewayTlsRuntime; gatewayCallDeps.loadDeviceAuthToken = deps?.loadDeviceAuthToken ?? defaultGatewayCallDeps.loadDeviceAuthToken; }, setCreateGatewayClientForTests(createGatewayClient) { gatewayCallDeps.createGatewayClient = createGatewayClient ?? defaultGatewayCallDeps.createGatewayClient; }, resetDepsForTests() { gatewayCallDeps.createGatewayClient = defaultGatewayCallDeps.createGatewayClient; gatewayCallDeps.getRuntimeConfig = defaultGatewayCallDeps.getRuntimeConfig; gatewayCallDeps.loadOrCreateDeviceIdentity = defaultGatewayCallDeps.loadOrCreateDeviceIdentity; gatewayCallDeps.resolveGatewayPort = defaultGatewayCallDeps.resolveGatewayPort; gatewayCallDeps.resolveConfigPath = defaultGatewayCallDeps.resolveConfigPath; gatewayCallDeps.resolveStateDir = defaultGatewayCallDeps.resolveStateDir; gatewayCallDeps.loadGatewayTlsRuntime = defaultGatewayCallDeps.loadGatewayTlsRuntime; gatewayCallDeps.loadDeviceAuthToken = defaultGatewayCallDeps.loadDeviceAuthToken; } }; function isLoopbackGatewayUrl(rawUrl) { try { const hostname = new URL(rawUrl).hostname.toLowerCase(); const unbracketed = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname; return unbracketed === "localhost" || isLoopbackIpAddress(unbracketed); } catch { return false; } } function shouldOmitDeviceIdentityForGatewayCall(params) { const mode = params.opts.mode ?? GATEWAY_CLIENT_MODES.CLI; const clientName = params.opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI; const hasSharedAuth = Boolean(params.token || params.password); return mode === GATEWAY_CLIENT_MODES.BACKEND && clientName === GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT && hasSharedAuth && isLoopbackGatewayUrl(params.url); } function resolveDeviceIdentityForGatewayCall(params) { if (shouldOmitDeviceIdentityForGatewayCall(params)) return null; try { return gatewayCallDeps.loadOrCreateDeviceIdentity(); } catch { return null; } } function hasStoredOperatorDeviceAuthToken(deviceIdentity) { if (!deviceIdentity) return false; try { return Boolean(gatewayCallDeps.loadDeviceAuthToken({ deviceId: deviceIdentity.deviceId, role: "operator", env: process.env })?.token); } catch { return false; } } function resolveGatewayCallAuth(config) { return resolveGatewayAuth({ authConfig: config.gateway?.auth, env: process.env, tailscaleMode: config.gateway?.tailscale?.mode }); } function ensureGatewayCallCanAuthenticate(params) { const resolvedAuth = resolveGatewayCallAuth(params.context.config); const authMode = resolvedAuth.mode; if (authMode !== "token" && authMode !== "password") return; if (params.token || params.password || params.opts.approvalRuntimeToken) return; if (resolvedAuth.allowTailscale) return; if (hasStoredOperatorDeviceAuthToken(params.deviceIdentity)) return; throw new GatewayCredentialsRequiredError({ method: params.opts.method, configPath: params.context.configPath }); } function resolveExplicitGatewayAuth(opts) { return { token: typeof opts?.token === "string" && opts.token.trim().length > 0 ? opts.token.trim() : void 0, password: typeof opts?.password === "string" && opts.password.trim().length > 0 ? opts.password.trim() : void 0 }; } function ensureExplicitGatewayAuth(params) { if (!params.urlOverride) return; const explicitToken = params.explicitAuth?.token; const explicitPassword = params.explicitAuth?.password; if (params.urlOverrideSource === "cli" && (explicitToken || explicitPassword)) return; const hasResolvedAuth = params.resolvedAuth?.token || params.resolvedAuth?.password || explicitToken || explicitPassword; if (params.urlOverrideSource === "env" && hasResolvedAuth) return; throw new GatewayExplicitAuthRequiredError([ "gateway url override requires explicit credentials", params.errorHint, params.configPath ? `Config: ${params.configPath}` : void 0 ].filter(Boolean).join("\n")); } function resolveGatewayCallTimeout(timeoutValue, configuredHandshakeTimeoutMs) { const hasConfiguredHandshakeTimeout = typeof configuredHandshakeTimeoutMs === "number" && Number.isFinite(configuredHandshakeTimeoutMs) && configuredHandshakeTimeoutMs > 0; const hasEnvHandshakeTimeout = Boolean(process.env.OPENCLAW_HANDSHAKE_TIMEOUT_MS) || Boolean(process.env.VITEST && process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS); const resolvedHandshakeTimeoutMs = hasConfiguredHandshakeTimeout || hasEnvHandshakeTimeout ? resolvePreauthHandshakeTimeoutMs({ configuredTimeoutMs: configuredHandshakeTimeoutMs }) : void 0; const timeoutMs = typeof timeoutValue === "number" && Number.isFinite(timeoutValue) ? timeoutValue : typeof resolvedHandshakeTimeoutMs === "number" && resolvedHandshakeTimeoutMs > 1e4 ? resolvedHandshakeTimeoutMs : 1e4; return { timeoutMs, safeTimerTimeoutMs: resolveSafeTimeoutDelayMs(timeoutMs) }; } async function resolveGatewayCallContext(opts) { const cliUrlOverride = trimToUndefined(opts.url); const explicitAuth = resolveExplicitGatewayAuth({ token: opts.token, password: opts.password }); const envUrlOverride = cliUrlOverride ? void 0 : trimToUndefined(process.env.OPENCLAW_GATEWAY_URL); const urlOverride = cliUrlOverride ?? envUrlOverride; const urlOverrideSource = cliUrlOverride ? "cli" : envUrlOverride ? "env" : void 0; const canSkipConfigLoad = canSkipGatewayConfigLoad({ config: opts.config, urlOverride, explicitAuth }); const config = opts.config ?? (canSkipConfigLoad ? {} : await loadGatewayConfig()); const configPath = opts.configPath ?? resolveGatewayConfigPath(process.env); const isRemoteMode = config.gateway?.mode === "remote"; const remote = isRemoteMode ? config.gateway?.remote : void 0; return { config, configPath, isRemoteMode, remote, urlOverride, urlOverrideSource, remoteUrl: trimToUndefined(remote?.url), explicitAuth }; } function ensureRemoteModeUrlConfigured(context) { if (!context.isRemoteMode || context.urlOverride || context.remoteUrl) return; throw new Error([ "gateway remote mode misconfigured: gateway.remote.url missing", `Config: ${context.configPath}`, "Fix: set gateway.remote.url, or set gateway.mode=local." ].join("\n")); } async function resolveGatewayCredentials(context) { return resolveGatewayCredentialsWithEnv(context, process.env); } async function resolveGatewayCredentialsWithEnv(context, env) { if (context.explicitAuth.token || context.explicitAuth.password) return { token: context.explicitAuth.token, password: context.explicitAuth.password }; return resolveGatewayCredentialsWithSecretInputs({ config: context.config, explicitAuth: context.explicitAuth, urlOverride: context.urlOverride, urlOverrideSource: context.urlOverrideSource, env, modeOverride: context.modeOverride, localTokenPrecedence: context.localTokenPrecedence, localPasswordPrecedence: context.localPasswordPrecedence, remoteTokenPrecedence: context.remoteTokenPrecedence, remotePasswordPrecedence: context.remotePasswordPrecedence, remoteTokenFallback: context.remoteTokenFallback, remotePasswordFallback: context.remotePasswordFallback }); } async function resolveGatewayTlsFingerprint(params) { const { opts, context, url } = params; const tlsRuntime = context.config.gateway?.tls?.enabled === true && !context.urlOverrideSource && !context.remoteUrl && url.startsWith("wss://") ? await gatewayCallDeps.loadGatewayTlsRuntime(context.config.gateway?.tls) : void 0; const overrideTlsFingerprint = trimToUndefined(opts.tlsFingerprint); const remoteTlsFingerprint = context.isRemoteMode && context.urlOverrideSource !== "cli" ? trimToUndefined(context.remote?.tlsFingerprint) : void 0; return overrideTlsFingerprint || remoteTlsFingerprint || (tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 : void 0); } function formatGatewayCloseError(code, reason, connectionDetails) { const reasonText = normalizeOptionalString(reason) || "no close reason"; const hint = code === 1006 ? "abnormal closure (no close frame)" : code === 1e3 ? "normal closure" : ""; let message = `gateway closed (${code}${hint ? ` ${hint}` : ""}): ${reasonText}\n${connectionDetails.message}`; if (code === 1006) message += "\n\nPossible causes:\n- Gateway not yet ready to accept connections (retry after a moment)\n- TLS mismatch (connecting with ws:// to a wss:// gateway, or vice versa)\n- Gateway crashed or was terminated unexpectedly\nRun `openclaw doctor` for diagnostics."; return message; } function formatGatewayTimeoutError(timeoutMs, connectionDetails) { return `gateway timeout after ${timeoutMs}ms\n${connectionDetails.message}`; } function createGatewayCloseTransportError(params) { const reasonText = normalizeOptionalString(params.reason) || "no close reason"; return new GatewayTransportError({ kind: "closed", code: params.code, reason: reasonText, connectionDetails: params.connectionDetails, message: formatGatewayCloseError(params.code, params.reason, params.connectionDetails) }); } function createGatewayTimeoutTransportError(params) { return new GatewayTransportError({ kind: "timeout", timeoutMs: params.timeoutMs, connectionDetails: params.connectionDetails, message: formatGatewayTimeoutError(params.timeoutMs, params.connectionDetails) }); } function createGatewayRequestAbortError(method) { const err = /* @__PURE__ */ new Error(`gateway request aborted for ${method}`); err.name = "AbortError"; return err; } function ensureGatewaySupportsRequiredMethods(params) { const requiredMethods = Array.isArray(params.requiredMethods) ? params.requiredMethods.map((entry) => entry.trim()).filter((entry) => entry.length > 0) : []; if (requiredMethods.length === 0) return; const supportedMethods = new Set((Array.isArray(params.methods) ? params.methods : []).map((entry) => entry.trim()).filter((entry) => entry.length > 0)); for (const method of requiredMethods) { if (supportedMethods.has(method)) continue; throw new Error([`active gateway does not support required method "${method}" for "${params.attemptedMethod}".`, "Update the gateway or run without SecretRefs."].join(" ")); } } async function executeGatewayRequestWithScopes(params) { const { opts, scopes, url, token, password, tlsFingerprint, preauthHandshakeTimeoutMs, timeoutMs, safeTimerTimeoutMs, deviceIdentity } = params; return await new Promise((resolve, reject) => { if (opts.signal?.aborted) { reject(createGatewayRequestAbortError(opts.method)); return; } let settled = false; let ignoreClose = false; const startAbort = new AbortController(); let primaryRequestStarted = false; const cleanup = () => { startAbort.abort(); if (abortHandler) opts.signal?.removeEventListener("abort", abortHandler); if (timer) clearTimeout(timer); }; const stopClientThenSettle = (activeClient, err, value) => { const complete = () => { if (err) reject(err); else resolve(value); }; if (!activeClient) { complete(); return; } stopGatewayClient(activeClient).finally(complete); }; const stop = (err, value) => { if (settled) return; settled = true; cleanup(); stopClientThenSettle(client, err, value); }; const abortHandler = () => { if (settled) return; ignoreClose = true; settled = true; cleanup(); const err = createGatewayRequestAbortError(opts.method); const activeClient = client; const stopAfterAbortHook = () => stopClientThenSettle(activeClient, err); if (!activeClient || !opts.onSignalAbort || !primaryRequestStarted) { stopAfterAbortHook(); return; } const request = activeClient.request.bind(activeClient); Promise.resolve().then(() => opts.onSignalAbort?.(request)).catch(() => {}).finally(stopAfterAbortHook); }; opts.signal?.addEventListener("abort", abortHandler, { once: true }); const client = gatewayCallDeps.createGatewayClient({ url, token, password, tlsFingerprint, preauthHandshakeTimeoutMs, instanceId: opts.instanceId ?? randomUUID(), clientName: opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI, clientDisplayName: resolveGatewayClientDisplayName(opts), clientVersion: opts.clientVersion ?? VERSION, platform: opts.platform, mode: opts.mode ?? GATEWAY_CLIENT_MODES.CLI, ...opts.approvalRuntimeToken ? { approvalRuntimeToken: opts.approvalRuntimeToken } : {}, role: "operator", scopes, deviceIdentity, minProtocol: opts.minProtocol ?? 4, maxProtocol: opts.maxProtocol ?? 4, onHelloOk: (hello) => { (async () => { try { ensureGatewaySupportsRequiredMethods({ requiredMethods: opts.requiredMethods, methods: hello.features?.methods, attemptedMethod: opts.method }); const activeClient = client; if (!activeClient) throw new Error("gateway client not initialized"); primaryRequestStarted = true; const result = await activeClient.request(opts.method, opts.params, { expectFinal: opts.expectFinal, timeoutMs: opts.timeoutMs, signal: opts.signal, onAccepted: opts.onAccepted }); ignoreClose = true; stop(void 0, result); } catch (err) { ignoreClose = true; stop(err); } })(); }, onClose: (code, reason) => { if (settled || ignoreClose) return; ignoreClose = true; stop(createGatewayCloseTransportError({ code, reason, connectionDetails: params.connectionDetails })); }, onConnectError: (err) => { if (settled || !isGatewayConnectAssemblyError(err)) return; ignoreClose = true; stop(err); } }); const timer = setTimeout(() => { ignoreClose = true; stop(createGatewayTimeoutTransportError({ timeoutMs, connectionDetails: params.connectionDetails })); }, safeTimerTimeoutMs); startGatewayClientWhenEventLoopReady(client, { timeoutMs: safeTimerTimeoutMs, signal: startAbort.signal }).then((readiness) => { if (settled || readiness.ready || readiness.aborted) return; ignoreClose = true; stop(createGatewayTimeoutTransportError({ timeoutMs, connectionDetails: params.connectionDetails })); }).catch((err) => { if (settled) return; ignoreClose = true; stop(err instanceof Error ? err : new Error(String(err))); }); }); } async function callGatewayWithScopes(opts, scopes) { const context = await resolveGatewayCallContext(opts); const { timeoutMs, safeTimerTimeoutMs } = resolveGatewayCallTimeout(opts.timeoutMs, context.config.gateway?.handshakeTimeoutMs); const resolvedCredentials = await resolveGatewayCredentials(context); ensureExplicitGatewayAuth({ urlOverride: context.urlOverride, urlOverrideSource: context.urlOverrideSource, explicitAuth: context.explicitAuth, resolvedAuth: resolvedCredentials, errorHint: "Fix: pass --token or --password (or gatewayToken in tools).", configPath: context.configPath }); ensureRemoteModeUrlConfigured(context); const connectionDetails = buildGatewayConnectionDetails({ config: context.config, url: context.urlOverride, urlSource: context.urlOverrideSource, ...opts.configPath ? { configPath: opts.configPath } : {} }); const url = connectionDetails.url; const tlsFingerprint = await resolveGatewayTlsFingerprint({ opts, context, url }); const { token, password } = resolvedCredentials; const deviceIdentity = opts.deviceIdentity === void 0 ? resolveDeviceIdentityForGatewayCall({ opts, url, token, password }) : opts.deviceIdentity; ensureGatewayCallCanAuthenticate({ opts, context, token, password, deviceIdentity }); return await executeGatewayRequestWithScopes({ opts, scopes, url, token, password, tlsFingerprint, preauthHandshakeTimeoutMs: context.config.gateway?.handshakeTimeoutMs, timeoutMs, safeTimerTimeoutMs, connectionDetails, deviceIdentity }); } async function buildGatewayProbeConnectionDetails(opts = {}) { const callOpts = { ...opts, method: "status" }; const context = await resolveGatewayCallContext(callOpts); ensureRemoteModeUrlConfigured(context); const connectionDetails = buildGatewayConnectionDetails({ config: context.config, url: context.urlOverride, urlSource: context.urlOverrideSource, ...opts.configPath ? { configPath: opts.configPath } : {} }); const tlsFingerprint = await resolveGatewayTlsFingerprint({ opts: callOpts, context, url: connectionDetails.url }); return { ...connectionDetails, ...tlsFingerprint ? { tlsFingerprint } : {}, ...context.config.gateway?.handshakeTimeoutMs ? { preauthHandshakeTimeoutMs: context.config.gateway.handshakeTimeoutMs } : {} }; } async function callGatewayScoped(opts) { return await callGatewayWithScopes(opts, opts.scopes); } async function callGatewayCli(opts) { return await callGatewayWithScopes(opts, Array.isArray(opts.scopes) ? opts.scopes : isGatewayMethodClassified(opts.method) ? resolveLeastPrivilegeOperatorScopesForMethod(opts.method, opts.params) : CLI_DEFAULT_OPERATOR_SCOPES); } async function callGatewayLeastPrivilege(opts) { return await callGatewayWithScopes(opts, resolveLeastPrivilegeOperatorScopesForMethod(opts.method, opts.params)); } async function callGateway(opts) { const callerMode = opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND; const callerName = opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT; if (callerMode === GATEWAY_CLIENT_MODES.CLI || callerName === GATEWAY_CLIENT_NAMES.CLI) return await callGatewayCli(opts); if (Array.isArray(opts.scopes)) return await callGatewayWithScopes({ ...opts, mode: callerMode, clientName: callerName }, opts.scopes); return await callGatewayLeastPrivilege({ ...opts, mode: callerMode, clientName: callerName }); } function randomIdempotencyKey() { return randomUUID(); } //#endregion export { testing as _, buildGatewayProbeConnectionDetails as a, callGatewayLeastPrivilege as c, formatGatewayTransportErrorJson as d, isGatewayCredentialsRequiredError as f, resolveExplicitGatewayAuth as g, randomIdempotencyKey as h, buildGatewayConnectionDetails as i, callGatewayScoped as l, isGatewayTransportError as m, GatewayExplicitAuthRequiredError as n, callGateway as o, isGatewayExplicitAuthRequiredError as p, GatewayTransportError as r, callGatewayCli as s, GatewayCredentialsRequiredError as t, ensureExplicitGatewayAuth as u, readGatewayDispatchConfig as v, readGatewayDispatchConfigWithShellEnvFallback as y };