UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

266 lines (265 loc) • 12.9 kB
import { c as normalizeOptionalLowercaseString, g as readStringValue, l as normalizeOptionalString, u as normalizeOptionalStringifiedId } from "./string-coerce-1VuiLaaR.mjs"; import { l as asNonNegativeFiniteNumber } from "./number-coercion-CkhMiRw8.mjs"; import { a as asOptionalRecord } from "./record-coerce-TIsquoVR.mjs"; import { t as normalizeAnyChannelId } from "./registry-normalize-DKVgg-js.mjs"; import { d as normalizeLegacyInteractiveReply, f as normalizeMessagePresentation } from "./payload-DD64v4IB.mjs"; import { t as getChannelPlugin } from "./registry-Bcrk1FVy.mjs"; import "./plugins-BXkKReMp.mjs"; import { i as normalizeTargetForProvider } from "./target-normalization-C-ukmQYe.mjs"; import { a as readToolResultDetails } from "./tool-result-error-CN2enaAJ.mjs"; import { o as isMessagingToolTargetEvidenceAction } from "./embedded-agent-messaging-NL2tVtK9.mjs"; import { r as isDeliveredCurrentSourceReply } from "./source-reply-mirror-tuxv32_Y.mjs"; //#region src/agents/embedded-agent-messaging-extraction.ts /** Extracts message delivery evidence from embedded-agent tool calls and results. */ function extractMessagingToolSourceReplyPayload(result) { const details = readToolResultDetails(result); if (!details || details.sourceReplySink !== "internal-ui") return; const status = normalizeOptionalLowercaseString(details.deliveryStatus); if (status && status !== "sent") return; return readSourceReplyPayload(details, asOptionalRecord(details.sourceReply) ?? details); } /** * Reads the final reply a `canDeliverSourceReply` tool authored in `details.sourceReply`. * Unlike internal-ui mirrors, nothing has been sent yet: the host delivers the payload * to the current source and records it in the transcript after delivery. A reply needs * text or media; `final: false` is not a deliverable reply, so the model continues as * usual. Callers must already have verified the tool's capability and invocation scope. */ function extractToolAuthoredSourceReplyPayload(result) { const details = readToolResultDetails(result); const sourceReply = details ? asOptionalRecord(details.sourceReply) : void 0; if (!details || !sourceReply || sourceReply.final === false) return; const payload = readSourceReplyPayload(details, sourceReply); if (!payload) return; return Boolean(payload.text?.trim()) || resolveSourceReplyMediaUrls(payload).length > 0 ? payload : void 0; } /** * The media a source reply delivers: `mediaUrls` when present, else `mediaUrl`, * without blank entries. Delivery and tool-authored admission share it. */ function resolveSourceReplyMediaUrls(payload) { return (payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : []).filter((value) => value.trim().length > 0); } function readSourceReplyPayload(details, sourceReply) { const payload = {}; const copyText = (key, fallback = key) => { const value = readStringValue(sourceReply[key]) ?? readStringValue(details[fallback]); if (value) payload[key] = value; }; copyText("text", "message"); copyText("mediaUrl"); const mediaUrls = (Array.isArray(sourceReply.mediaUrls) ? sourceReply.mediaUrls : Array.isArray(details.mediaUrls) ? details.mediaUrls : []).filter((value) => typeof value === "string"); if (mediaUrls.length > 0) payload.mediaUrls = mediaUrls; if (Array.isArray(sourceReply.attachments)) { const attachments = sourceReply.attachments.flatMap((value) => { const attachment = asOptionalRecord(value); if (!attachment) return []; const projected = {}; for (const key of [ "path", "url", "mediaUrl", "filePath", "mimeType", "name" ]) { const fieldText = readStringValue(attachment[key]); if (fieldText) projected[key] = fieldText; } if (typeof attachment.trustedLocalMedia === "boolean") projected.trustedLocalMedia = attachment.trustedLocalMedia; for (const key of [ "durationMs", "width", "height" ]) { const number = asNonNegativeFiniteNumber(attachment[key]); if (number !== void 0) projected[key] = number; } return [projected]; }); if (attachments.length > 0) payload.attachments = attachments; } if (typeof sourceReply.trustedLocalMedia === "boolean") payload.trustedLocalMedia = sourceReply.trustedLocalMedia; if (sourceReply.audioAsVoice === true || details.audioAsVoice === true) payload.audioAsVoice = true; const presentation = normalizeMessagePresentation(sourceReply.presentation); if (presentation) payload.presentation = presentation; const interactive = normalizeLegacyInteractiveReply(sourceReply.interactive); if (interactive) payload.interactive = interactive; const channelData = asOptionalRecord(sourceReply.channelData); if (channelData) payload.channelData = { ...channelData }; copyText("idempotencyKey"); if (details.sourceReplyTranscriptOwner === true) payload.transcriptOwner = true; return Object.keys(payload).length > 0 ? payload : void 0; } function resolveMessagingToolThreadEvidence(params) { const threading = getChannelPlugin(params.providerId)?.threading; const autoThreadResolver = params.allowImplicitThread ? threading?.resolveAutoThreadId : void 0; const replyTransport = params.replyToId ? threading?.resolveReplyTransport?.({ cfg: params.options?.config ?? {}, accountId: params.accountId, threadId: params.threadId, replyToId: params.replyToId }) : void 0; const transportThreadId = normalizeOptionalStringifiedId(replyTransport?.threadId); const replyToThreadId = replyTransport?.threadId === null ? normalizeOptionalString(replyTransport.replyToId) : void 0; const explicitThreadId = transportThreadId ?? replyToThreadId ?? params.threadId; const currentChannelId = normalizeOptionalString(params.options?.currentChannelId); const currentMessagingTarget = normalizeOptionalString(params.options?.currentMessagingTarget); const currentThreadId = normalizeOptionalString(params.options?.currentThreadId); const replyToMode = params.options?.replyToMode ?? (currentThreadId ? "all" : void 0); const canResolveCurrentThread = Boolean((currentChannelId || currentMessagingTarget) && currentThreadId); const resolvedCurrentThreadId = !explicitThreadId && !params.threadSuppressed && autoThreadResolver && canResolveCurrentThread ? autoThreadResolver({ cfg: params.options?.config ?? {}, accountId: params.accountId, to: params.to, replyToId: params.replyToId, toolContext: { currentChannelId, currentMessagingTarget, currentThreadTs: currentThreadId, currentMessageId: params.options?.currentMessageId, replyToMode, hasRepliedRef: params.options?.hasRepliedRef } }) : void 0; const threadImplicit = !explicitThreadId && !params.threadSuppressed && Boolean(autoThreadResolver) && (!canResolveCurrentThread || Boolean(resolvedCurrentThreadId)); return { ...explicitThreadId ?? resolvedCurrentThreadId ? { threadId: explicitThreadId ?? resolvedCurrentThreadId } : {}, ...threadImplicit ? { threadImplicit: true } : {}, ...params.threadSuppressed ? { threadSuppressed: true } : {} }; } function extractMessagingToolSend(toolName, args, options) { const action = normalizeOptionalString(args.action) ?? ""; const accountId = normalizeOptionalString(args.accountId); if (toolName === "conversations_send" || toolName === "conversations_turn") { const conversationRef = normalizeOptionalString(args.conversationRef); return conversationRef ? { tool: toolName, provider: "conversation", to: conversationRef } : void 0; } let providerId; let provider; let to; let resolvedAccountId; let threadId; let outboundReplyToId; let threadSuppressed; let allowImplicitThread; if (toolName === "message") { if (!isMessagingToolTargetEvidenceAction(toolName, args)) return; const providerRaw = normalizeOptionalString(args.provider) ?? ""; const channelRaw = normalizeOptionalString(args.channel) ?? ""; const providerHint = providerRaw || channelRaw; providerId = providerHint ? normalizeAnyChannelId(providerHint) : null; const currentChannelId = options?.currentChannelId; const currentMessagingTarget = options?.currentMessagingTarget; let toRaw = normalizeOptionalString(args.target) ?? normalizeOptionalString(args.to) ?? normalizeOptionalString(args.channelId); if (!toRaw) { const aliases = providerId ? getChannelPlugin(providerId)?.actions?.messageActionTargetAliases?.[action]?.deliveryTargetAliases : void 0; for (const alias of aliases ?? []) { toRaw = normalizeOptionalStringifiedId(args[alias]); if (toRaw) break; } toRaw ??= currentMessagingTarget ?? currentChannelId; } if (!toRaw) return; provider = providerId ?? normalizeOptionalLowercaseString(providerHint) ?? "message"; const pluginExtractionArgs = { ...args, to: toRaw }; const pluginExtracted = providerId ? getChannelPlugin(providerId)?.actions?.extractToolSend?.({ args: pluginExtractionArgs }) : null; to = normalizeTargetForProvider(provider, pluginExtracted?.to ?? toRaw); resolvedAccountId = normalizeOptionalString(pluginExtracted?.accountId) ?? accountId; threadId = normalizeOptionalString(pluginExtracted?.threadId) ?? normalizeOptionalString(args.threadId); const replyToId = normalizeOptionalString(args.replyTo); outboundReplyToId = action === "send" ? replyToId : void 0; threadSuppressed = pluginExtracted?.threadSuppressed === true || args.topLevel === true || args.threadId === null; allowImplicitThread = Boolean(to && providerId) && (!pluginExtracted || pluginExtracted.threadImplicit === true); } else { providerId = normalizeAnyChannelId(toolName); if (!providerId) return; provider = providerId; const extracted = getChannelPlugin(providerId)?.actions?.extractToolSend?.({ args }); if (!extracted?.to) return; to = normalizeTargetForProvider(providerId, extracted.to); threadId = normalizeOptionalString(extracted.threadId); threadSuppressed = extracted.threadSuppressed === true; resolvedAccountId = normalizeOptionalString(extracted.accountId) ?? accountId; const nativeReplyToMode = options?.replyToMode; const nativeSingleUseMode = nativeReplyToMode === "first" || nativeReplyToMode === "batched"; allowImplicitThread = extracted.threadImplicit === true && nativeReplyToMode !== void 0 && (!nativeSingleUseMode || options?.hasRepliedRef !== void 0); } return to ? { tool: toolName, provider, accountId: resolvedAccountId, to, ...providerId ? resolveMessagingToolThreadEvidence({ providerId, to, accountId: resolvedAccountId, threadId, replyToId: outboundReplyToId, allowImplicitThread, threadSuppressed, options }) : { ...threadId ? { threadId } : {}, ...threadSuppressed ? { threadSuppressed: true } : {} } } : void 0; } /** Reconciles pending send evidence with the provider's successful action result. */ function extractMessagingToolSendResult(pending, result) { const providerId = normalizeAnyChannelId(pending.provider); const extracted = providerId ? getChannelPlugin(providerId)?.actions?.extractToolSendResult?.({ result, send: { to: pending.to ?? "", accountId: pending.accountId, threadId: pending.threadId, threadImplicit: pending.threadImplicit, threadSuppressed: pending.threadSuppressed } }) : null; if (!extracted?.to) return pending; const threadEvidence = normalizeOptionalString(extracted.threadId) != null || extracted.threadImplicit === true || extracted.threadSuppressed === true ? extracted : pending; return { ...pending, ...extracted, accountId: normalizeOptionalString(extracted.accountId) ?? pending.accountId, to: normalizeTargetForProvider(providerId ?? pending.provider, extracted.to), threadId: normalizeOptionalString(threadEvidence.threadId), threadImplicit: threadEvidence.threadImplicit === true ? true : void 0, threadSuppressed: threadEvidence.threadSuppressed === true ? true : void 0 }; } function isDeliveredMessagingToolSendToCurrentSource(params) { const send = params.send; if (!send?.to) return false; return isDeliveredCurrentSourceReply({ action: "send", channel: send.provider, accountId: send.accountId, currentAccountId: params.currentAccountId, actionParams: { target: send.to, ...send.threadSuppressed ? { topLevel: true } : send.threadId ? { threadId: send.threadId } : {} }, cfg: params.config ?? {}, sessionKey: params.sessionKey, toolContext: { currentChannelProvider: params.currentProvider, currentChannelId: params.currentChannelId, currentMessagingTarget: params.currentMessagingTarget, currentThreadTs: params.currentThreadId }, deliveredPayload: params.deliveredPayload }); } //#endregion export { isDeliveredMessagingToolSendToCurrentSource as a, extractToolAuthoredSourceReplyPayload as i, extractMessagingToolSendResult as n, resolveSourceReplyMediaUrls as o, extractMessagingToolSourceReplyPayload as r, extractMessagingToolSend as t };