openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
289 lines (288 loc) • 13.3 kB
JavaScript
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { c as normalizeOptionalLowercaseString, g as readStringValue, l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { a as redactModelVisibleSecrets, h as redactToolPayloadText, o as redactModelVisibleSensitiveFieldValueWithConfig, s as redactModelVisibleToolPayloadText, u as redactSensitiveFieldValue } from "./redact-BtvPPfTi.js";
import "./utils-P__uGsPB.js";
import { n as estimateBase64DecodedBytes } from "./base64-Vw7DZYSc.js";
import { o as readToolResultDetails, r as isToolResultError, s as readToolResultStatus } from "./tool-result-error-CbDLJzG-.js";
//#region src/agents/content-blocks.ts
/** Collects text block payloads from provider-style structured content arrays. */
function collectTextContentBlocks(content) {
if (!Array.isArray(content)) return [];
const parts = [];
for (const block of content) {
if (!block || typeof block !== "object") continue;
const rec = block;
if (rec.type === "text" && typeof rec.text === "string") parts.push(rec.text);
}
return parts;
}
//#endregion
//#region src/agents/embedded-agent-tool-results.ts
/** Sanitizes, extracts, and classifies embedded-agent tool execution results. */
const TOOL_RESULT_MAX_CHARS = 8e3;
const TOOL_ERROR_MAX_CHARS = 400;
const LIVE_EXEC_OUTPUT_MAX_CHARS = 8e3;
const TOOL_DENIAL_ERROR_CODES = ["SYSTEM_RUN_DENIED", "INVALID_REQUEST"];
const OPAQUE_STRUCTURED_RESULT_FIELDS = /* @__PURE__ */ new Set(["encrypted_content", "encrypted_stdout"]);
const SENSITIVE_STRUCTURED_HEADER_FIELDS = /* @__PURE__ */ new Set([
"authorization",
"proxy-authorization",
"cookie",
"set-cookie",
"x-api-key",
"x-auth-token"
]);
function truncateToolText(text) {
if (text.length <= TOOL_RESULT_MAX_CHARS) return text;
return `${truncateUtf16Safe(text, TOOL_RESULT_MAX_CHARS)}\n…(truncated)…`;
}
function truncateLiveExecOutput(text) {
if (text.length <= LIVE_EXEC_OUTPUT_MAX_CHARS) return text;
return `${truncateUtf16Safe(text, LIVE_EXEC_OUTPUT_MAX_CHARS)}\n...(live output truncated)...`;
}
function capLiveExecResult(result) {
const details = readToolResultDetails(result);
if (!details || typeof details.status !== "string" || typeof details.aggregated !== "string") return result;
const aggregated = truncateLiveExecOutput(details.aggregated);
if (aggregated === details.aggregated) return result;
if (!result || typeof result !== "object" || Array.isArray(result)) return result;
return {
...result,
details: {
...details,
aggregated
}
};
}
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 rawDetails = asOptionalRecord(errorRecord?.details);
const nodeError = asOptionalRecord(rawDetails?.nodeError);
const gatewayCode = readErrorCodeField(errorRecord?.gatewayCode) ?? readErrorCodeField(errorRecord?.code);
const message = error instanceof Error ? error.message : String(error);
return {
content: [{
type: "text",
text: message
}],
details: {
status: "error",
error: message,
...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 entries = Object.entries(value);
for (const entry of entries) entry[1] = typeof entry[1] === "string" ? redactSensitiveFieldValue(entry[0], entry[1]) : redactStringsDeep(entry[1], seen);
return Object.fromEntries(entries);
}
return value;
}
function sanitizeToolArgs(args) {
return redactStringsDeep(args);
}
function sanitizeToolResult(result) {
if (typeof result === "string") return redactModelVisibleToolPayloadText(result);
if (Array.isArray(result)) return redactModelVisibleSecrets(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 existingBytes = typeof entry.bytes === "number" ? entry.bytes : void 0;
const bytes = data === void 0 ? existingBytes : estimateBase64DecodedBytes(data);
const cleaned = { ...entry };
delete cleaned.data;
return Object.assign(cleaned, {
bytes,
omitted: true
});
}
return entry;
});
const out = redactModelVisibleSecrets(preCleaned);
const content = Array.isArray(out.content) ? out.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") {
const text = truncateToolText(entry.text);
return Object.assign({ ...entry }, { text });
}
return entry;
});
return out;
}
const INLINE_DATA_URI_VALUE_PATTERN = /^data:(?:[a-z][a-z0-9.+-]*\/[a-z0-9.+-]+)?(?:;[a-z0-9.+-]+(?:=[^,;"'\s]+)?)*,/i;
function redactInlineDataUriValue(value) {
const trimmed = value.trimStart();
if (!INLINE_DATA_URI_VALUE_PATTERN.test(trimmed)) return value;
return `[inline data URI: ${value.length} chars]`;
}
function carriesBinaryData(record) {
const type = normalizeOptionalLowercaseString(record.type);
if (type === "audio" || type === "image" || type === "base64") return true;
const mediaType = normalizeOptionalLowercaseString(record.media_type ?? record.mimeType);
return mediaType?.startsWith("image/") === true || mediaType?.startsWith("audio/") === true || mediaType?.startsWith("video/") === true || mediaType === "application/pdf";
}
function sanitizeStructuredToolResultValue(value, key = "", parentCarriesBinaryData = false, seen = /* @__PURE__ */ new WeakSet()) {
if (typeof value === "string") {
if (SENSITIVE_STRUCTURED_HEADER_FIELDS.has(key.toLowerCase())) return "***";
if (key === "blob" || key === "data" && parentCarriesBinaryData) return `[binary omitted: ${value.length} chars]`;
if (OPAQUE_STRUCTURED_RESULT_FIELDS.has(key)) return `[opaque data omitted: ${value.length} chars]`;
return truncateToolText(redactInlineDataUriValue(redactModelVisibleSensitiveFieldValueWithConfig(key, value)));
}
if (typeof value === "bigint") return value.toString();
if (!value || typeof value !== "object") return value;
if (seen.has(value)) return "[Circular]";
seen.add(value);
if (Array.isArray(value)) return value.map((item) => sanitizeStructuredToolResultValue(item, key, parentCarriesBinaryData, seen));
const record = value;
const hasBinaryData = carriesBinaryData(record);
return Object.fromEntries(Object.entries(record).map(([childKey, child]) => [childKey, sanitizeStructuredToolResultValue(child, childKey, hasBinaryData, seen)]));
}
function stringifyStructuredToolResultContent(block) {
if (!block || typeof block !== "object") return;
const record = block;
const type = readStringValue(record.type);
if (type === "text" || type === "image" || type === "image_url" || type === "audio") return;
try {
const serialized = JSON.stringify(sanitizeStructuredToolResultValue(record));
const redacted = serialized ? redactModelVisibleToolPayloadText(serialized) : serialized;
return redacted && redacted !== "{}" ? redacted : void 0;
} catch {
return;
}
}
function resolveToolResultContentBlocks(result) {
if (Array.isArray(result)) return result;
const record = result;
if (readStringValue(record.type)) return [record];
if (Array.isArray(record.content)) return record.content;
if (record.content && typeof record.content === "object") return [record.content];
return [record];
}
function extractToolResultText(result) {
if (typeof result === "string") {
const trimmed = redactModelVisibleToolPayloadText(redactInlineDataUriValue(result)).trim();
return trimmed ? truncateToolText(trimmed) : void 0;
}
if (!result || typeof result !== "object") return;
const content = resolveToolResultContentBlocks(result);
const texts = collectTextContentBlocks(content).map((item) => {
const trimmed = item.trim();
return trimmed ? trimmed : void 0;
}).filter((value) => Boolean(value));
if (texts.length > 0) return truncateToolText(texts.join("\n"));
const structuredTexts = [];
for (const item of content) {
const structured = stringifyStructuredToolResultContent(item);
if (structured) structuredTexts.push(structured);
}
if (structuredTexts.length === 0) return;
return truncateToolText(structuredTexts.join("\n"));
}
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;
if (readToolResultStatus(result) && !isToolResultError(result)) return;
return text ? normalizeToolErrorText(text) : void 0;
}
//#endregion
export { extractToolResultText as a, sanitizeToolResult as c, extractToolErrorMessage as i, truncateLiveExecOutput as l, capLiveExecResult as n, isToolResultTimedOut as o, extractToolErrorCode as r, sanitizeToolArgs as s, buildToolLifecycleErrorResult as t, collectTextContentBlocks as u };