openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
189 lines (188 loc) • 7.53 kB
JavaScript
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { d as resolveGatewayPort } from "./paths-mvMm5bYV.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { a as trimToUndefined } from "./credential-planner-wESmL_7M.js";
import { r as resolveGatewayCredentialsFromConfig } from "./credentials-irvgw8Le.js";
import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js";
import "./config-C9RxTsn1.js";
import { i as GATEWAY_CLIENT_NAMES, r as GATEWAY_CLIENT_MODES } from "./client-info-CcqJJIan.js";
import { o as callGateway } from "./call-B5-GYOlf.js";
import { n as loadDeviceIdentityIfPresent, r as loadOrCreateDeviceIdentity } from "./device-identity-CEPJolq9.js";
import { u as resolveLeastPrivilegeOperatorScopesForMethod } from "./method-scopes-BtdN3y7s.js";
import { t as getOperatorApprovalRuntimeToken } from "./operator-approval-runtime-token-BPk6svmE.js";
import { b as readStringParam, g as readPositiveIntegerParam } from "./common-C4yy9V-D.js";
//#region src/agents/tools/gateway.ts
/**
* Gateway call helpers for built-in tools.
*
* Resolves gateway URL/token overrides, local credentials, and least-privilege operator scopes.
*/
/** Reads common gateway options from tool parameters while preserving explicit token whitespace. */
function readGatewayCallOptions(params) {
return {
gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
timeoutMs: readPositiveIntegerParam(params, "timeoutMs")
};
}
/**
* Canonicalizes websocket URLs for allowlist comparisons without retaining paths or credentials.
*/
function canonicalizeToolGatewayWsUrl(raw) {
const input = raw.trim();
let url;
try {
url = new URL(input);
} catch (error) {
const message = formatErrorMessage(error);
throw new Error(`invalid gatewayUrl: ${input} (${message})`, { cause: error });
}
if (url.protocol !== "ws:" && url.protocol !== "wss:") throw new Error(`invalid gatewayUrl protocol: ${url.protocol} (expected ws:// or wss://)`);
if (url.username || url.password) throw new Error("invalid gatewayUrl: credentials are not allowed");
if (url.search || url.hash) throw new Error("invalid gatewayUrl: query/hash not allowed");
if (url.pathname && url.pathname !== "/") throw new Error("invalid gatewayUrl: path not allowed");
return {
origin: url.origin,
key: `${url.protocol}//${normalizeLowercaseStringOrEmpty(url.host)}`
};
}
function resolveLocalGatewayUrlKeys(cfg) {
const port = resolveGatewayPort(cfg);
return new Set([
`ws://127.0.0.1:${port}`,
`wss://127.0.0.1:${port}`,
`ws://localhost:${port}`,
`wss://localhost:${port}`,
`ws://[::1]:${port}`,
`wss://[::1]:${port}`
]);
}
function resolveConfiguredRemoteGatewayKey(cfg) {
let remoteKey;
const remoteUrl = normalizeOptionalString(cfg.gateway?.remote?.url) ?? "";
if (remoteUrl) try {
remoteKey = canonicalizeToolGatewayWsUrl(remoteUrl).key;
} catch {}
return remoteKey;
}
function resolveDefaultGatewayTarget(params) {
if (params.envGatewayUrl) return "remote";
if (params.cfg.gateway?.mode === "remote" && normalizeOptionalString(params.cfg.gateway.remote?.url)) return "remote";
return "local";
}
function validateGatewayUrlOverrideForAgentTools(params) {
const { cfg } = params;
const localAllowed = resolveLocalGatewayUrlKeys(cfg);
const remoteKey = resolveConfiguredRemoteGatewayKey(cfg);
const parsed = canonicalizeToolGatewayWsUrl(params.urlOverride);
if (localAllowed.has(parsed.key)) return {
url: parsed.origin,
target: "local"
};
if (remoteKey && parsed.key === remoteKey) return {
url: parsed.origin,
target: "remote"
};
const port = resolveGatewayPort(cfg);
throw new Error([
"gatewayUrl override rejected.",
`Allowed: ws(s) loopback on port ${port} (127.0.0.1/localhost/[::1])`,
"Or: configure gateway.remote.url and omit gatewayUrl to use the configured remote gateway."
].join(" "));
}
function resolveGatewayOverrideToken(params) {
if (params.explicitToken) return params.explicitToken;
return resolveGatewayCredentialsFromConfig({
cfg: params.cfg,
env: process.env,
modeOverride: params.target,
remoteTokenFallback: params.target === "remote" ? "remote-only" : "remote-env-local",
remotePasswordFallback: params.target === "remote" ? "remote-only" : "remote-env-local"
}).token;
}
/**
* Resolves the gateway URL, token, and timeout for agent tool calls.
*/
function resolveGatewayOptions(opts) {
const cfg = getRuntimeConfig();
const validatedOverride = trimToUndefined(opts?.gatewayUrl) !== void 0 ? validateGatewayUrlOverrideForAgentTools({
cfg,
urlOverride: String(opts?.gatewayUrl)
}) : void 0;
const explicitToken = trimToUndefined(opts?.gatewayToken);
const token = validatedOverride ? resolveGatewayOverrideToken({
cfg,
target: validatedOverride.target,
explicitToken
}) : explicitToken;
const timeoutMs = typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? Math.max(1, Math.floor(opts.timeoutMs)) : 3e4;
const envGatewayUrl = trimToUndefined(process.env.OPENCLAW_GATEWAY_URL);
const target = validatedOverride?.target ?? resolveDefaultGatewayTarget({
cfg,
envGatewayUrl
});
return {
url: validatedOverride?.url,
token,
timeoutMs,
target
};
}
const APPROVAL_RUNTIME_METHODS = new Set([
"exec.approval.request",
"exec.approval.resolve",
"exec.approval.waitDecision",
"plugin.approval.request",
"plugin.approval.waitDecision"
]);
function resolveApprovalRuntimeTokenForGatewayTool(params) {
if (!APPROVAL_RUNTIME_METHODS.has(params.method)) return;
if (trimToUndefined(params.opts.gatewayUrl) !== void 0) return;
if (params.target !== "local") return;
return getOperatorApprovalRuntimeToken();
}
function resolveApprovalRequesterDeviceIdentityForGatewayTool(params) {
if (!APPROVAL_RUNTIME_METHODS.has(params.method)) return;
if (trimToUndefined(params.opts.gatewayUrl) !== void 0) return;
if (params.target !== "remote") return;
try {
const identity = loadOrCreateDeviceIdentity();
if (loadDeviceIdentityIfPresent()?.deviceId !== identity.deviceId) throw new Error("device identity is not persisted");
return identity;
} catch (error) {
throw new Error(["remote approval gateway calls require a stable device identity.", "Fix the OpenClaw state directory permissions or use the local approval-runtime gateway."].join(" "), { cause: error });
}
}
/**
* Calls a gateway method as the agent-tool backend client with least-privilege scopes.
*/
async function callGatewayTool(method, opts, params, extra) {
const gateway = resolveGatewayOptions(opts);
const scopes = Array.isArray(extra?.scopes) ? extra.scopes : resolveLeastPrivilegeOperatorScopesForMethod(method, params);
const approvalRuntimeToken = resolveApprovalRuntimeTokenForGatewayTool({
method,
opts,
target: gateway.target
});
const deviceIdentity = resolveApprovalRequesterDeviceIdentityForGatewayTool({
method,
opts,
target: gateway.target
});
return await callGateway({
url: gateway.url,
token: gateway.token,
method,
params,
timeoutMs: gateway.timeoutMs,
expectFinal: extra?.expectFinal,
clientName: GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
clientDisplayName: "agent",
mode: GATEWAY_CLIENT_MODES.BACKEND,
...approvalRuntimeToken ? { approvalRuntimeToken } : {},
...deviceIdentity ? { deviceIdentity } : {},
scopes
});
}
//#endregion
export { readGatewayCallOptions as n, resolveGatewayOptions as r, callGatewayTool as t };