UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

462 lines (461 loc) • 22.2 kB
import { m as readNonBlankString } from "./string-coerce-1VuiLaaR.mjs"; import { s as asFiniteNumber } from "./number-coercion-CkhMiRw8.mjs"; import "./src-BirUqOZp.mjs"; import { a as asOptionalRecord, i as asOptionalObjectRecord, r as asNullableRecord } from "./record-coerce-TIsquoVR.mjs"; import { n as safeParseJsonRecord, t as safeParseJson } from "./json-coercion-pfKbZnCJ.mjs"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import { c as isMeaningfulMediaFact, f as readPersistedMediaFacts } from "./media-facts-BIPREXi-.mjs"; import { h as normalizeInputProvenance } from "./input-provenance-B5YZS2Do.mjs"; import "./code-regions-Brr79Na1.mjs"; import { n as splitMediaOutput, t as isRelativeAssistantMediaReference } from "./parse-output-BEoO0_ns.mjs"; import { i as isToolResultContentType, n as isToolCallContentType } from "./tool-content-SH_V-fgH.mjs"; import { r as isSuppressedControlReplyText } from "./control-reply-text-CdlY-Sik.mjs"; //#region src/chat/canvas-render.ts function coerceMcpAppDescriptor(record) { const viewId = readNonBlankString(record?.viewId); if (!viewId || viewId.length > 128) return; const serverName = readNonBlankString(record?.serverName); const toolName = readNonBlankString(record?.toolName); const uiResourceUri = readNonBlankString(record?.uiResourceUri); const toolCallId = readNonBlankString(record?.toolCallId); const originSessionKey = readNonBlankString(record?.originSessionKey); const resultMetaState = record?.resultMetaState === "unavailable" ? "unavailable" : void 0; return Boolean(serverName && serverName.length <= 256 && toolName && toolName.length <= 256 && uiResourceUri?.startsWith("ui://") && uiResourceUri.length <= 2048 && toolCallId && toolCallId.length <= 512) ? { viewId, serverName, toolName, uiResourceUri, toolCallId, ...originSessionKey && originSessionKey.length <= 512 ? { originSessionKey } : {}, ...resultMetaState ? { resultMetaState } : {} } : { viewId }; } function normalizePreferredHeight(value) { return typeof value === "number" && Number.isFinite(value) && value >= 160 ? Math.min(Math.trunc(value), 1200) : void 0; } function isCanvasBoardWidgetName(value) { return typeof value === "string" && /^[a-z0-9][a-z0-9._-]{0,63}$/u.test(value); } function coerceCanvasPreview(record) { if (!record) return; if (readNonBlankString(record?.kind)?.trim().toLowerCase() !== "canvas") return; const presentation = asOptionalRecord(record.presentation); const view = asOptionalRecord(record.view); const source = asOptionalRecord(record.source); const mcpApp = coerceMcpAppDescriptor(asOptionalRecord(record.mcpApp)); const mcpAppViewId = mcpApp?.viewId; const surface = readNonBlankString(presentation?.target) ?? readNonBlankString(record?.target) ?? "assistant_message"; if (surface !== "assistant_message" && surface !== "node_panel") return; const title = readNonBlankString(presentation?.title) ?? readNonBlankString(view?.title); const preferredHeight = normalizePreferredHeight(asFiniteNumber(presentation?.preferred_height) ?? asFiniteNumber(presentation?.preferredHeight) ?? asFiniteNumber(view?.preferred_height) ?? asFiniteNumber(view?.preferredHeight)); const className = readNonBlankString(presentation?.class_name) ?? readNonBlankString(presentation?.className); const style = readNonBlankString(presentation?.style); const sandbox = readNonBlankString(presentation?.sandbox); const viewUrl = readNonBlankString(view?.url) ?? readNonBlankString(view?.entryUrl); const viewId = readNonBlankString(view?.id) ?? readNonBlankString(view?.docId); const requestedBoardWidgetName = readNonBlankString(view?.boardWidgetName); const boardWidgetName = isCanvasBoardWidgetName(requestedBoardWidgetName) ? requestedBoardWidgetName : void 0; const preview = { kind: "canvas", surface, render: "url", ...title ? { title } : {}, ...preferredHeight ? { preferredHeight } : {}, ...sandbox === "strict" || sandbox === "scripts" ? { sandbox } : {}, ...mcpApp ? { mcpApp } : {} }; if (mcpAppViewId && viewId === mcpAppViewId) return { ...preview, viewId }; const url = viewUrl ?? (readNonBlankString(source?.type)?.trim().toLowerCase() === "url" ? readNonBlankString(source?.url) : void 0); if (!url) return; return { ...preview, url, ...viewUrl && viewId ? { viewId } : {}, ...className ? { className } : {}, ...style ? { style } : {}, ...viewUrl && boardWidgetName ? { boardWidgetName } : {} }; } /** Extracts an MCP App Canvas preview from sanitized tool-result details. */ function extractCanvasFromDetails(value) { const details = asOptionalRecord(value); return coerceCanvasPreview(asOptionalRecord(details?.mcpAppPreview)); } /** Extracts a canvas preview from a JSON-shaped tool or assistant payload. */ function extractCanvasFromText(outputText) { return coerceCanvasPreview(outputText ? safeParseJsonRecord(outputText) : void 0); } //#endregion //#region src/shared/tool-approval-reviews.ts const REVIEW_STATUSES = /* @__PURE__ */ new Set([ "in_progress", "approved", "denied", "timed_out", "aborted" ]); function boundedString(value, maxChars) { const text = typeof value === "string" ? value.trim() : ""; return text ? truncateUtf16Safe(text, maxChars) : void 0; } function isReviewStatus(value) { return value !== void 0 && REVIEW_STATUSES.has(value); } function normalizeToolApprovalReview(value) { const review = asNullableRecord(value); const id = boundedString(review?.id, 256); const label = boundedString(review?.label, 80); const status = boundedString(review?.status, 32); if (!id || !label || !isReviewStatus(status)) return null; const riskLevel = boundedString(review?.riskLevel, 40); const userAuthorization = boundedString(review?.userAuthorization, 40); const rationale = boundedString(review?.rationale, 2e3); return { id, label, status, ...riskLevel ? { riskLevel } : {}, ...userAuthorization ? { userAuthorization } : {}, ...rationale ? { rationale } : {} }; } //#endregion //#region src/gateway/chat-display-projection.helpers.ts const DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS = 8e3; /** Removes private yield inputs from a display copy without changing the source argument objects. */ function stripPrivateToolCallContextForDisplay(entry) { if (entry.type !== "toolCall" || entry.name !== "sessions_yield") return false; let changed = false; for (const field of ["arguments", "input"]) { const toolInput = asOptionalRecord(entry[field]); if (!toolInput || !("message" in toolInput)) continue; const { message: _, ...publicInput } = toolInput; entry[field] = publicInput; changed = true; } return changed; } function takeAssistantManagedMediaUrlsForDisplay(entry, role) { const delivery = role === "assistant" ? asOptionalRecord(entry.openclawDelivery) : void 0; const urls = Array.isArray(delivery?.mediaUrls) ? delivery.mediaUrls.filter((url) => typeof url === "string") : []; if (!delivery || !Object.hasOwn(delivery, "mediaUrls")) return { changed: false, urls }; const projectedDelivery = { ...delivery }; delete projectedDelivery.mediaUrls; if (Object.keys(projectedDelivery).length > 0) entry.openclawDelivery = projectedDelivery; else delete entry.openclawDelivery; return { changed: true, urls }; } /** Keeps managed attachment commands in the raw transcript while removing them from display text. */ function stripAssistantMediaDirectivesForDisplay(text, managedMediaUrls) { if (managedMediaUrls.length === 0 || !/(?:^|[\r\n])\s*MEDIA:/iu.test(text)) return text; const managed = new Set(managedMediaUrls.map((url) => url.trim()).filter(Boolean)); const parsed = splitMediaOutput(text, { extractAudioDirectives: false }); if (!parsed.mediaUrls?.some((url) => isRelativeAssistantMediaReference(url) && managed.has(url.trim()))) return text; return (parsed.segments ?? []).filter((segment) => segment.type !== "media" || !isRelativeAssistantMediaReference(segment.url) || !managed.has(segment.url.trim())).map((segment) => segment.type === "media" ? `MEDIA:${segment.url}` : segment.text).join("\n"); } /** Resolve the text cap used when projecting chat history for display. */ function resolveEffectiveChatHistoryMaxChars(maxChars) { if (typeof maxChars === "number") return maxChars; return DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS; } function truncateChatHistoryText(text, maxChars = DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS, preserveExactPrefix = false, toolResultMaxChars) { let limit = maxChars; if (toolResultMaxChars !== void 0 && toolResultMaxChars < Math.min(text.length, maxChars)) { const preview = truncateUtf16Safe(text, maxChars); if (!/^\s*[[{]/u.test(preview) || safeParseJson(preview) === void 0) limit = toolResultMaxChars; } if (text.length <= limit) return { text, truncated: false }; const prefix = truncateUtf16Safe(text, limit); return { text: preserveExactPrefix ? prefix : `${prefix}\n...(truncated)...`, truncated: true }; } function extractAssistantTextForSilentCheck(message) { const entry = asOptionalObjectRecord(message); if (entry?.role !== "assistant") return; if (typeof entry.text === "string") return entry.text; if (typeof entry.content === "string") return entry.content; if (!Array.isArray(entry.content) || entry.content.length === 0) return; const texts = []; for (const block of entry.content) { const typed = asOptionalObjectRecord(block); if (!typed) return; if (isAssistantInternalReasoningContentType(typed.type)) continue; if (!isAssistantTextContentType(typed.type) || typeof typed.text !== "string") return; texts.push(typed.text); } return texts.length > 0 ? texts.join("\n") : void 0; } function isAssistantTextContentType(type) { return type === "text" || type === "input_text" || type === "output_text"; } function isAssistantInternalReasoningContentType(type) { return type === "thinking" || type === "reasoning" || type === "redacted_thinking"; } function hasAssistantNonTextContent(message, includeReasoning = true) { const content = asOptionalObjectRecord(message)?.content; return Array.isArray(content) && content.some((block) => { const entry = asOptionalObjectRecord(block); return entry && !isAssistantTextContentType(entry.type) && (includeReasoning || !isAssistantInternalReasoningContentType(entry.type)); }); } function hasAssistantDisplayableNonTextContent(message) { return hasAssistantNonTextContent(message, false); } function shouldPreserveAssistantControlReplyText(message) { if (isProjectedForwardedMessage(message)) return true; if (!hasAssistantDisplayableNonTextContent(message)) return false; const content = message.text ?? message.content; const texts = typeof content === "string" ? [content] : Array.isArray(content) ? content.flatMap((block) => { const typed = asOptionalRecord(block); return isAssistantTextContentType(typed?.type) && typeof typed?.text === "string" ? [typed.text] : []; }) : []; return texts.length > 0 && texts.every((text) => isSuppressedControlReplyText(text)); } function asRoleContentMessage(message) { const role = typeof message.role === "string" ? message.role.toLowerCase() : ""; if (!role) return null; return { role, ...message.content !== void 0 ? { content: message.content } : message.text !== void 0 ? { content: message.text } : {} }; } function isEmptyTextOnlyContent(content) { if (typeof content === "string") return content.trim().length === 0; if (!Array.isArray(content)) return false; for (const block of content) { const entry = asOptionalObjectRecord(block); if (entry?.type !== "text" || typeof entry.text !== "string" || entry.text.trim().length > 0) return false; } return true; } function hasTranscriptMediaFacts(message) { return (readPersistedMediaFacts(message) ?? []).some(isMeaningfulMediaFact); } function extractProjectedText(content) { if (typeof content === "string") return content; if (!Array.isArray(content)) return ""; const parts = []; for (const block of content) { const text = asOptionalObjectRecord(block)?.text; if (typeof text === "string") parts.push(text); } return parts.join("\n"); } function isCronRunMessage(message) { const provenance = normalizeInputProvenance(message.provenance); return provenance?.kind === "internal_system" && provenance.sourceTool === "cron" && Boolean(provenance.jobId && provenance.runId && provenance.sourceSessionKey); } function isForwardedUserMessage(message) { return message.role === "user" && hasForwardedMessageProvenance(message); } function isProjectedForwardedMessage(message) { return message.role === "assistant" && hasForwardedMessageProvenance(message); } function hasForwardedMessageProvenance(message) { const provenance = normalizeInputProvenance(message.provenance); return provenance?.kind === "inter_session" && provenance.sourceTool === "sessions_send" || isCronRunMessage(message); } //#endregion //#region src/gateway/chat-display-projection.canvas.ts function isBrowserRouteIdentifier(value, maxChars) { return typeof value === "string" && value.length > 0 && value.length <= maxChars && value.trim() === value; } /** Return true for known tool-call/tool-result block type spellings in transcripts. */ function isToolHistoryBlockType(type) { if (typeof type !== "string") return false; const normalized = type.trim(); return isToolCallContentType(normalized) || isToolResultContentType(normalized); } function isToolResultHistoryBlockType(type) { return typeof type === "string" && isToolResultContentType(type.trim()); } function projectToolResultDetails(details, maxChars) { const record = asOptionalRecord(details); if (!record) return { details: void 0, truncated: false }; const projected = {}; const browserTab = asOptionalRecord(record.browserTab); if (isBrowserRouteIdentifier(browserTab?.targetId, 128) && isBrowserRouteIdentifier(browserTab?.profile, 128) && (browserTab?.target === "host" && browserTab.node === void 0 || browserTab?.target === "node" && isBrowserRouteIdentifier(browserTab.node, 256))) projected.browserTab = { targetId: browserTab.targetId, profile: browserTab.profile, target: browserTab.target, ...browserTab.target === "node" ? { node: browserTab.node } : {}, ...typeof browserTab.url === "string" ? { url: truncateUtf16Safe(browserTab.url, 2048) } : {}, ...typeof browserTab.title === "string" ? { title: truncateUtf16Safe(browserTab.title, 512) } : {} }; let truncated = false; for (const key of ["exitCode", "durationMs"]) { const value = record[key]; if (typeof value === "number" && Number.isFinite(value)) projected[key] = value; } if (typeof record.cwd === "string") { const cwd = truncateChatHistoryText(record.cwd, maxChars); projected.cwd = cwd.text; truncated ||= cwd.truncated; } if (typeof record.sessionKey === "string" && record.sessionKey.length > 0 && record.sessionKey.length <= maxChars && record.sessionKey.trim() === record.sessionKey) projected.sessionKey = record.sessionKey; for (const key of [ "ok", "changed", "created" ]) if (typeof record[key] === "boolean") projected[key] = record[key]; if (typeof record.diff === "string" && record.diff.trim()) { const diff = truncateChatHistoryText(record.diff, maxChars); projected.diff = diff.text; truncated ||= diff.truncated; } if (Array.isArray(record.approvalReviews)) { const reviews = record.approvalReviews.slice(-16).flatMap((review) => normalizeToolApprovalReview(review) ?? []); if (reviews.length > 0) projected.approvalReviews = reviews; } const reviewOutcome = record.approvalReviewOutcome; if (reviewOutcome === "approved" || reviewOutcome === "denied" || reviewOutcome === "reviewing") projected.approvalReviewOutcome = reviewOutcome; const preview = extractCanvasFromDetails(record); if (preview?.mcpApp && preview.viewId) projected.mcpAppPreview = { kind: "canvas", view: { id: preview.viewId, ...preview.url ? { url: preview.url } : {}, ...preview.title ? { title: preview.title } : {} }, presentation: { target: "assistant_message", ...preview.title ? { title: preview.title } : {}, ...preview.preferredHeight ? { preferred_height: preview.preferredHeight } : {}, ...preview.sandbox ? { sandbox: preview.sandbox } : {} }, mcpApp: preview.mcpApp }; return { details: Object.keys(projected).length > 0 ? projected : void 0, truncated }; } function messageHasToolResultShape(message) { const role = typeof message.role === "string" ? message.role.toLowerCase() : ""; if (role === "toolresult" || role === "tool_result" || role === "tool" || role === "function") return true; const content = Array.isArray(message.content) ? message.content : []; if (content.some((block) => isToolResultHistoryBlockType(asOptionalObjectRecord(block)?.type))) return true; const hasToolCallBlock = content.some((block) => { const type = asOptionalObjectRecord(block)?.type; return isToolHistoryBlockType(type) && !isToolResultHistoryBlockType(type); }); const hasToolId = typeof message.toolCallId === "string" || typeof message.tool_call_id === "string" || typeof message.toolUseId === "string" || typeof message.tool_use_id === "string"; const hasToolName = typeof message.toolName === "string" || typeof message.tool_name === "string"; return hasToolId && hasToolName && !hasToolCallBlock; } function extractChatHistoryBlockText(message) { const entry = asOptionalObjectRecord(message); if (!entry) return; if (typeof entry.content === "string") return entry.content; if (typeof entry.text === "string") return entry.text; if (!Array.isArray(entry.content)) return; const textParts = entry.content.map((block) => asOptionalObjectRecord(block)?.text).filter((value) => typeof value === "string"); return textParts.length > 0 ? textParts.join("\n") : void 0; } function extractChatHistoryCanvasPreview(message) { const direct = extractCanvasFromDetails(message.details); if (direct) return direct; if (!Array.isArray(message.content)) return; for (const block of message.content) { const preview = extractCanvasFromDetails(asOptionalRecord(block)?.details); if (preview) return preview; } } function extractChatToolResultCanvasPreview(message) { const entry = asOptionalRecord(message); if (!entry) return; const detailsPreview = extractChatHistoryCanvasPreview(entry); const text = detailsPreview ? void 0 : extractChatHistoryBlockText(entry); const preview = detailsPreview ?? extractCanvasFromText(text); return preview ? { preview, rawText: detailsPreview ? null : text ?? null } : void 0; } function appendChatCanvasBlocks(content, previews) { const baseContent = [...content]; for (const { preview, rawText } of previews) if (!baseContent.some((block) => { const typed = asOptionalObjectRecord(block); const existing = typed?.type === "canvas" ? asOptionalObjectRecord(typed.preview) : void 0; return Boolean(existing && (existing.viewId && existing.viewId === preview.viewId || existing.url && existing.url === preview.url)); })) baseContent.push({ type: "canvas", preview, rawText }); return baseContent; } function appendChatCanvasBlocksToMessage(message, previews) { if (!message || previews.length === 0) return message; const content = Array.isArray(message.content) ? message.content : typeof message.content === "string" ? [{ type: "text", text: message.content }] : typeof message.text === "string" ? [{ type: "text", text: message.text }] : []; return { ...message, content: appendChatCanvasBlocks(content, previews) }; } function messageContainsToolHistoryContent(entry) { if (typeof entry.toolCallId === "string" || typeof entry.tool_call_id === "string" || typeof entry.toolName === "string" || typeof entry.tool_name === "string") return true; if (!Array.isArray(entry.content)) return false; return entry.content.some((block) => isToolHistoryBlockType(asOptionalObjectRecord(block)?.type)); } function augmentChatHistoryWithCanvasBlocks(messages) { if (messages.length === 0) return messages; const next = [...messages]; let changed = false; let lastAssistantIndex = -1; let lastRenderableAssistantIndex = -1; const pending = []; for (let index = 0; index < next.length; index++) { const entry = asOptionalObjectRecord(next[index]); if (!entry) continue; if ((typeof entry.role === "string" ? entry.role.toLowerCase() : "") === "assistant") { lastAssistantIndex = index; if (!messageContainsToolHistoryContent(entry)) { lastRenderableAssistantIndex = index; if (pending.length > 0) { next[index] = appendChatCanvasBlocksToMessage(entry, pending); pending.length = 0; changed = true; } } continue; } if (!messageContainsToolHistoryContent(entry)) continue; const preview = extractChatToolResultCanvasPreview(entry); if (!preview) continue; pending.push(preview); } if (pending.length > 0) { const targetIndex = lastRenderableAssistantIndex >= 0 ? lastRenderableAssistantIndex : lastAssistantIndex; if (targetIndex >= 0) { next[targetIndex] = appendChatCanvasBlocksToMessage(asOptionalRecord(next[targetIndex]), pending); changed = true; } } return changed ? next : messages; } //#endregion export { resolveEffectiveChatHistoryMaxChars as C, takeAssistantManagedMediaUrlsForDisplay as D, stripPrivateToolCallContextForDisplay as E, truncateChatHistoryText as O, isProjectedForwardedMessage as S, stripAssistantMediaDirectivesForDisplay as T, isAssistantInternalReasoningContentType as _, extractChatToolResultCanvasPreview as a, isEmptyTextOnlyContent as b, messageHasToolResultShape as c, asRoleContentMessage as d, extractAssistantTextForSilentCheck as f, hasTranscriptMediaFacts as g, hasAssistantNonTextContent as h, extractChatHistoryBlockText as i, projectToolResultDetails as l, hasAssistantDisplayableNonTextContent as m, appendChatCanvasBlocksToMessage as n, isToolHistoryBlockType as o, extractProjectedText as p, augmentChatHistoryWithCanvasBlocks as r, isToolResultHistoryBlockType as s, appendChatCanvasBlocks as t, DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS as u, isAssistantTextContentType as v, shouldPreserveAssistantControlReplyText as w, isForwardedUserMessage as x, isCronRunMessage as y };