Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import { loadConfig, resolveGatewayPort } from "../../config/config.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { callGateway } from "../../gateway/call.js";
import { resolveGatewayCredentialsFromConfig, trimToUndefined } from "../../gateway/credentials.js";
import {
resolveLeastPrivilegeOperatorScopesForMethod,
type OperatorScope,
} from "../../gateway/method-scopes.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../gateway/protocol/client-info.js";
import { formatErrorMessage } from "../../infra/errors.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../../shared/string-coerce.js";
import { readStringParam } from "./common.js";
export const DEFAULT_GATEWAY_URL = "ws://127.0.0.1:18789";
export type GatewayCallOptions = {
gatewayUrl?: string;
gatewayToken?: string;
timeoutMs?: number;
};
type GatewayOverrideTarget = "local" | "remote";
export function readGatewayCallOptions(params: Record<string, unknown>): GatewayCallOptions {
return {
gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : undefined,
};
}
function canonicalizeToolGatewayWsUrl(raw: string): { origin: string; key: string } {
const input = raw.trim();
let url: 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");
}
// Agents/tools expect the gateway websocket on the origin, not arbitrary paths.
if (url.pathname && url.pathname !== "/") {
throw new Error("invalid gatewayUrl: path not allowed");
}
const origin = url.origin;
// Key: protocol + host only, lowercased. (host includes IPv6 brackets + port when present)
const key = `${url.protocol}//${normalizeLowercaseStringOrEmpty(url.host)}`;
return { origin, key };
}
function validateGatewayUrlOverrideForAgentTools(params: {
cfg: OpenClawConfig;
urlOverride: string;
}): { url: string; target: GatewayOverrideTarget } {
const { cfg } = params;
const port = resolveGatewayPort(cfg);
const localAllowed = new Set<string>([
`ws://127.0.0.1:${port}`,
`wss://127.0.0.1:${port}`,
`ws://localhost:${port}`,
`wss://localhost:${port}`,
`ws://[::1]:${port}`,
`wss://[::1]:${port}`,
]);
let remoteKey: string | undefined;
const remoteUrl = normalizeOptionalString(cfg.gateway?.remote?.url) ?? "";
if (remoteUrl) {
try {
const remote = canonicalizeToolGatewayWsUrl(remoteUrl);
remoteKey = remote.key;
} catch {
// ignore: misconfigured remote url; tools should fall back to default resolution.
}
}
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" };
}
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: {
cfg: OpenClawConfig;
target: GatewayOverrideTarget;
explicitToken?: string;
}): string | undefined {
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;
}
export function resolveGatewayOptions(opts?: GatewayCallOptions) {
const cfg = loadConfig();
const validatedOverride =
trimToUndefined(opts?.gatewayUrl) !== undefined
? validateGatewayUrlOverrideForAgentTools({
cfg,
urlOverride: String(opts?.gatewayUrl),
})
: undefined;
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))
: 30_000;
return { url: validatedOverride?.url, token, timeoutMs };
}
export async function callGatewayTool<T = Record<string, unknown>>(
method: string,
opts: GatewayCallOptions,
params?: unknown,
extra?: { expectFinal?: boolean; scopes?: OperatorScope[] },
) {
const gateway = resolveGatewayOptions(opts);
const scopes = Array.isArray(extra?.scopes)
? extra.scopes
: resolveLeastPrivilegeOperatorScopesForMethod(method);
return await callGateway<T>({
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,
scopes,
});
}
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|