openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
370 lines (369 loc) • 15.4 kB
JavaScript
import { c as normalizeOptionalString, p as readStringValue, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js";
import { a as redactSensitiveFieldValue, u as redactToolPayloadText } from "./redact-DduLliKq.js";
import { i as asOptionalRecord } from "./record-coerce-DHZ4bFlT.js";
import { _ as uniqueStrings } from "./string-normalization-WNUDCpXX.js";
import { y as truncateUtf16Safe } from "./utils-CCC-BEJH.js";
import { a as normalizeChannelId, t as getChannelPlugin } from "./registry-MkxNn1Ue.js";
import "./plugins-t2ejWcVy.js";
import { h as normalizeToolName } from "./tool-policy-CpBMaMTY.js";
import { a as normalizeTargetForProvider } from "./target-normalization-BUpmwo0F.js";
import { t as isMessageToolSendActionName } from "./embedded-agent-messaging-CprZBAme.js";
import { t as collectTextContentBlocks } from "./content-blocks-DRK0dze4.js";
//#region src/agents/embedded-agent-subscribe.tools.ts
/**
* Sanitizes, extracts, and classifies embedded-agent tool execution results.
*/
const TOOL_RESULT_MAX_CHARS = 8e3;
const TOOL_ERROR_MAX_CHARS = 400;
const TOOL_DENIAL_ERROR_CODES = ["SYSTEM_RUN_DENIED", "INVALID_REQUEST"];
function truncateToolText(text) {
if (text.length <= TOOL_RESULT_MAX_CHARS) return text;
return `${truncateUtf16Safe(text, TOOL_RESULT_MAX_CHARS)}\n…(truncated)…`;
}
function normalizeToolErrorText(text) {
const trimmed = text.trim();
if (!trimmed) return;
const firstLine = trimmed.split(/\r?\n/)[0]?.trim() ?? "";
if (!firstLine) return;
return firstLine.length > TOOL_ERROR_MAX_CHARS ? `${truncateUtf16Safe(firstLine, TOOL_ERROR_MAX_CHARS)}…` : firstLine;
}
function isErrorLikeStatus(status) {
const normalized = normalizeOptionalLowercaseString(status);
if (!normalized) return false;
if (normalized === "0" || normalized === "ok" || normalized === "success" || normalized === "completed" || normalized === "running") return false;
return /error|fail|timeout|timed[_\s-]?out|denied|cancel|invalid|forbidden/.test(normalized);
}
function readErrorCandidate(value) {
if (typeof value === "string") return normalizeToolErrorText(value);
if (!value || typeof value !== "object") return;
const record = value;
if (typeof record.message === "string") return normalizeToolErrorText(record.message);
if (typeof record.error === "string") return normalizeToolErrorText(record.error);
}
function extractErrorField(value) {
if (!value || typeof value !== "object") return;
const record = value;
const direct = extractDirectErrorField(record);
if (direct) return direct;
const status = normalizeOptionalString(record.status) ?? "";
if (!status || !isErrorLikeStatus(status)) return;
return normalizeToolErrorText(status);
}
function extractDirectErrorField(value) {
if (!value || typeof value !== "object") return;
const record = value;
return readErrorCandidate(record.error) ?? readErrorCandidate(record.message) ?? readErrorCandidate(record.reason);
}
function readErrorCodeField(value) {
return typeof value === "string" ? normalizeOptionalString(value) : void 0;
}
function readDenialErrorCodeFromMessage(value) {
const message = typeof value === "string" ? normalizeOptionalString(value) : void 0;
if (!message) return;
for (const code of TOOL_DENIAL_ERROR_CODES) if (message === code || message.startsWith(`${code}:`)) return code;
}
function readNestedErrorCodeField(value) {
if (!value || typeof value !== "object") return;
const record = value;
return readDenialErrorCodeFromMessage(record.message) ?? readDenialErrorCodeFromMessage(record.error) ?? readErrorCodeField(record.code) ?? readErrorCodeField(record.gatewayCode);
}
function extractDirectErrorCodeField(value) {
if (!value || typeof value !== "object") return;
const record = value;
return readNestedErrorCodeField(record.error) ?? readNestedErrorCodeField(record.nodeError) ?? readErrorCodeField(record.code) ?? readErrorCodeField(record.gatewayCode);
}
function buildToolLifecycleErrorResult(error) {
const errorRecord = asOptionalRecord(error);
const nodeError = asOptionalRecord(asOptionalRecord(errorRecord?.details)?.nodeError);
const gatewayCode = readErrorCodeField(errorRecord?.gatewayCode) ?? readErrorCodeField(errorRecord?.code);
return { details: {
status: "error",
error: error instanceof Error ? error.message : String(error),
...gatewayCode ? { gatewayCode } : {},
...nodeError ? { nodeError } : {}
} };
}
function extractAggregatedErrorField(value) {
if (!value || typeof value !== "object") return;
return readErrorCandidate(value.aggregated);
}
function redactStringsDeep(value, seen = /* @__PURE__ */ new WeakSet()) {
if (typeof value === "string") return redactToolPayloadText(value);
if (Array.isArray(value)) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
return value.map((item) => redactStringsDeep(item, seen));
}
if (value && typeof value === "object") {
if (seen.has(value)) return "[Circular]";
seen.add(value);
const out = {};
for (const [key, child] of Object.entries(value)) out[key] = typeof child === "string" ? redactSensitiveFieldValue(key, child) : redactStringsDeep(child, seen);
return out;
}
return value;
}
function sanitizeToolArgs(args) {
return redactStringsDeep(args);
}
function sanitizeToolResult(result) {
if (typeof result === "string") return redactToolPayloadText(result);
if (Array.isArray(result)) return redactStringsDeep(result);
if (!result || typeof result !== "object") return result;
const record = result;
const preCleaned = { ...record };
const originalContent = Array.isArray(record.content) ? record.content : null;
if (originalContent) preCleaned.content = originalContent.map((item) => {
if (!item || typeof item !== "object") return item;
const entry = item;
if (readStringValue(entry.type) === "image") {
const data = readStringValue(entry.data);
const bytes = data ? data.length : void 0;
const cleaned = { ...entry };
delete cleaned.data;
return Object.assign({}, cleaned, {
bytes,
omitted: true
});
}
return entry;
});
const baseline = redactStringsDeep(preCleaned);
const out = { ...baseline };
const content = Array.isArray(baseline.content) ? baseline.content : null;
if (content) out.content = content.map((item) => {
if (!item || typeof item !== "object") return item;
const entry = item;
if (readStringValue(entry.type) === "text" && typeof entry.text === "string") return Object.assign({}, entry, { text: truncateToolText(entry.text) });
return entry;
});
return out;
}
function extractToolResultText(result) {
if (!result || typeof result !== "object") return;
const texts = collectTextContentBlocks(result.content).map((item) => {
const trimmed = item.trim();
return trimmed ? trimmed : void 0;
}).filter((value) => Boolean(value));
if (texts.length === 0) return;
return texts.join("\n");
}
const TRUSTED_TOOL_RESULT_MEDIA = new Set([
"agents_list",
"apply_patch",
"browser",
"canvas",
"cron",
"edit",
"exec",
"gateway",
"image",
"image_generate",
"memory_get",
"memory_search",
"message",
"music_generate",
"nodes",
"process",
"read",
"session_status",
"sessions_history",
"sessions_list",
"sessions_send",
"sessions_spawn",
"subagents",
"tts",
"video_generate",
"web_fetch",
"web_search",
"x_search",
"write"
]);
const HTTP_URL_RE = /^https?:\/\//i;
function isCoreToolResultMediaTrustedName(toolName) {
if (!toolName) return false;
return TRUSTED_TOOL_RESULT_MEDIA.has(normalizeToolName(toolName));
}
function readToolResultDetails(result) {
if (!result || typeof result !== "object") return;
const record = result;
return record.details && typeof record.details === "object" && !Array.isArray(record.details) ? record.details : void 0;
}
function readToolResultStatus(result) {
const status = readToolResultDetails(result)?.status;
return normalizeOptionalLowercaseString(status);
}
function isExternalToolResult(result) {
const details = readToolResultDetails(result);
if (!details) return false;
return typeof details.mcpServer === "string" || typeof details.mcpTool === "string";
}
function isToolResultMediaTrusted(toolName, result, trustedLocalMediaToolNames) {
if (!toolName || isExternalToolResult(result)) return false;
const registeredName = toolName.trim();
if (registeredName && trustedLocalMediaToolNames?.has(registeredName) === true) return true;
return isCoreToolResultMediaTrustedName(toolName);
}
function isTrustedOwnedTtsLocalMedia(toolName, result, trustedLocalMediaToolNames) {
if (!toolName || !isToolResultMediaTrusted(toolName, result, trustedLocalMediaToolNames) || normalizeToolName(toolName) !== "tts") return false;
const media = readToolResultDetails(result)?.media;
if (!media || typeof media !== "object" || Array.isArray(media)) return false;
return media.trustedLocalMedia === true;
}
function filterToolResultMediaUrls(toolName, mediaUrls, result, trustedLocalMediaToolNames) {
if (mediaUrls.length === 0) return mediaUrls;
const trustedOwnedTtsLocalMedia = isTrustedOwnedTtsLocalMedia(toolName, result, trustedLocalMediaToolNames);
if (isToolResultMediaTrusted(toolName, result, trustedLocalMediaToolNames)) {
if (trustedLocalMediaToolNames !== void 0) {
if (!trustedOwnedTtsLocalMedia) {
const registeredName = toolName?.trim();
if (!registeredName || !trustedLocalMediaToolNames.has(registeredName)) return mediaUrls.filter((url) => HTTP_URL_RE.test(url.trim()));
}
}
return mediaUrls;
}
return mediaUrls.filter((url) => HTTP_URL_RE.test(url.trim()));
}
function readToolResultDetailsMedia(result) {
const details = readToolResultDetails(result);
return details?.media && typeof details.media === "object" && !Array.isArray(details.media) ? details.media : void 0;
}
function collectStructuredMediaUrls(media) {
const urls = [];
const pushString = (value) => {
if (typeof value !== "string") return;
const normalized = value.trim();
if (normalized) urls.push(normalized);
};
const pushAttachment = (value) => {
if (!value || typeof value !== "object" || Array.isArray(value)) return;
const attachment = value;
pushString(attachment.media);
pushString(attachment.path);
pushString(attachment.url);
pushString(attachment.mediaUrl);
pushString(attachment.filePath);
pushString(attachment.fileUrl);
};
pushString(media.media);
pushString(media.path);
pushString(media.url);
pushString(media.mediaUrl);
pushString(media.filePath);
pushString(media.fileUrl);
if (Array.isArray(media.mediaUrls)) for (const value of media.mediaUrls) pushString(value);
if (Array.isArray(media.attachments)) for (const attachment of media.attachments) pushAttachment(attachment);
return uniqueStrings(urls);
}
function isNonOutboundToolResultMedia(media) {
return media.outbound === false;
}
function hasImageContentBlock(content) {
for (const item of content) {
if (!item || typeof item !== "object") continue;
if (item.type === "image") return true;
}
return false;
}
function extractToolResultMediaArtifact(result) {
if (!result || typeof result !== "object") return;
const record = result;
const detailsMedia = readToolResultDetailsMedia(record);
if (detailsMedia) {
if (isNonOutboundToolResultMedia(detailsMedia)) return;
const mediaUrls = collectStructuredMediaUrls(detailsMedia);
if (mediaUrls.length > 0) return {
mediaUrls,
...detailsMedia.audioAsVoice === true ? { audioAsVoice: true } : {},
...detailsMedia.trustedLocalMedia === true ? { trustedLocalMedia: true } : {}
};
}
const content = Array.isArray(record.content) ? record.content : null;
if (!content) return;
if (hasImageContentBlock(content)) {
const details = record.details;
const p = normalizeOptionalString(details?.path) ?? "";
if (p) return { mediaUrls: [p] };
}
}
function isToolResultError(result) {
const normalized = readToolResultStatus(result);
if (!normalized) return false;
return normalized === "error" || normalized === "timeout";
}
function extractToolErrorCode(result) {
if (!result || typeof result !== "object") return;
const record = result;
return extractDirectErrorCodeField(record.details) ?? extractDirectErrorCodeField(record);
}
function isToolResultTimedOut(result) {
if (readToolResultStatus(result) === "timeout") return true;
return readToolResultDetails(result)?.timedOut === true;
}
function extractToolErrorMessage(result) {
if (!result || typeof result !== "object") return;
const record = result;
const fromDetails = extractDirectErrorField(record.details);
if (fromDetails) return fromDetails;
const fromDetailsAggregated = extractAggregatedErrorField(record.details);
if (fromDetailsAggregated) return fromDetailsAggregated;
const fromRoot = extractDirectErrorField(record);
if (fromRoot) return fromRoot;
const text = extractToolResultText(result);
if (text) try {
const fromJson = extractErrorField(JSON.parse(text));
if (fromJson) return fromJson;
} catch {}
const fromDetailsStatus = extractErrorField(record.details);
if (fromDetailsStatus) return fromDetailsStatus;
const fromRootStatus = extractErrorField(record);
if (fromRootStatus) return fromRootStatus;
return text ? normalizeToolErrorText(text) : void 0;
}
function resolveMessageToolTarget(args) {
const toRaw = readStringValue(args.to);
if (toRaw) return toRaw;
return readStringValue(args.target);
}
function extractMessagingToolSend(toolName, args) {
const action = normalizeOptionalString(args.action) ?? "";
const accountId = normalizeOptionalString(args.accountId);
if (toolName === "message") {
if (!isMessageToolSendActionName(action)) return;
const toRaw = resolveMessageToolTarget(args);
if (!toRaw) return;
const providerRaw = normalizeOptionalString(args.provider) ?? "";
const channelRaw = normalizeOptionalString(args.channel) ?? "";
const providerHint = providerRaw || channelRaw;
const providerId = providerHint ? normalizeChannelId(providerHint) : null;
const provider = providerId ?? normalizeOptionalLowercaseString(providerHint) ?? "message";
const to = normalizeTargetForProvider(provider, toRaw);
const threadId = normalizeOptionalString(args.threadId);
const threadSuppressed = args.topLevel === true || args.threadId === null;
const threadImplicit = !threadId && !threadSuppressed && Boolean(providerId && getChannelPlugin(providerId)?.threading?.resolveAutoThreadId);
return to ? {
tool: toolName,
provider,
accountId,
to,
...threadId ? { threadId } : {},
...threadImplicit ? { threadImplicit: true } : {},
...threadSuppressed ? { threadSuppressed: true } : {}
} : void 0;
}
const providerId = normalizeChannelId(toolName);
if (!providerId) return;
const extracted = getChannelPlugin(providerId)?.actions?.extractToolSend?.({ args });
if (!extracted?.to) return;
const to = normalizeTargetForProvider(providerId, extracted.to);
const threadId = normalizeOptionalString(extracted.threadId);
return to ? {
tool: toolName,
provider: providerId,
accountId: extracted.accountId ?? accountId,
to,
...threadId ? { threadId } : {}
} : void 0;
}
//#endregion
export { extractToolResultMediaArtifact as a, isToolResultError as c, sanitizeToolResult as d, extractToolErrorMessage as i, isToolResultTimedOut as l, extractMessagingToolSend as n, extractToolResultText as o, extractToolErrorCode as r, filterToolResultMediaUrls as s, buildToolLifecycleErrorResult as t, sanitizeToolArgs as u };