openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
374 lines (373 loc) • 15 kB
JavaScript
import { F as resolveTimerTimeoutMs } from "./number-coercion-CLj0HTDM.js";
import { r as createLazyRuntimeModule } from "./lazy-runtime-CgCh8H_K.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { i as GATEWAY_CLIENT_NAMES, r as GATEWAY_CLIENT_MODES } from "./client-info-B1bPgeKr.js";
import "./message-channel-BQrhwUEA.js";
import { i as resolveOutboundDurableFinalDeliverySupport } from "./deliver-prepare-6uVAVf8W.js";
import { C as normalizeOutboundReplyFacts } from "./reply-payload-Bfsi665c.js";
import { i as projectOutboundPayloadPlanForDelivery, o as projectOutboundPayloadPlanForMirror, t as createOutboundPayloadPlan } from "./payloads-DC1mf-yF.js";
import { t as buildOutboundSessionContext } from "./session-context-CpdGMrv6.js";
import { n as deriveDurableFinalDeliveryRequirementsForBatch } from "./capabilities-B0Za3iuj.js";
import "./deliver-BngawiqN.js";
import { n as sendDurableMessageBatchCore, r as serializeDurableMessagePayloadOutcomes } from "./send-Dk4bo3m5.js";
import "./runtime-zD0iWK04.js";
import { n as normalizePollInput } from "./polls-C-v11_tu.js";
import { r as resolveMessageChannelSelection } from "./channel-selection-DGYiyd5S.js";
import { a as resolveOutboundTarget } from "./targets-CP7C4mlG.js";
//#region src/infra/outbound/message-gateway-options.ts
/** Normalizes outbound gateway options and fills CLI defaults. */
function resolveOutboundMessageGatewayOptions(gateway) {
const clientName = gateway?.clientName ?? GATEWAY_CLIENT_NAMES.CLI;
const mode = gateway?.mode ?? GATEWAY_CLIENT_MODES.CLI;
return {
url: mode === GATEWAY_CLIENT_MODES.BACKEND || clientName === GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT ? void 0 : gateway?.url,
token: gateway?.token,
timeoutMs: resolveTimerTimeoutMs(gateway?.timeoutMs, 1e4),
clientName,
clientDisplayName: gateway?.clientDisplayName,
mode
};
}
//#endregion
//#region src/infra/outbound/message.ts
const SEND_BUFFER_MEDIA_URL = "buffer://message-send/attachment";
const loadMessageConfigRuntime = createLazyRuntimeModule(() => import("./message.config.runtime.js"));
const loadMessageGatewayRuntime = createLazyRuntimeModule(() => import("./message.gateway.runtime.js"));
function normalizeMessagePollDeliveryResult(result) {
const { channelId, conversationId, ...delivery } = result;
return {
...delivery,
...channelId ? { target: {
kind: "channel",
id: channelId
} } : conversationId ? { target: {
kind: "conversation",
id: conversationId
} } : {}
};
}
function buildMessagePollResult(params) {
return {
channel: params.channel,
to: params.to,
question: params.normalized.question,
options: params.normalized.options,
maxSelections: params.normalized.maxSelections,
durationSeconds: params.normalized.durationSeconds ?? null,
durationHours: params.normalized.durationHours ?? null,
via: params.via,
...params.dryRun ? { dryRun: true } : { result: params.result }
};
}
function assertPollOptionSupport(params) {
if (typeof params.durationSeconds === "number" && params.outbound.supportsPollDurationSeconds !== true) throw new Error(`durationSeconds is not supported for ${params.channel} polls`);
if (typeof params.isAnonymous === "boolean" && params.outbound.supportsAnonymousPolls !== true) throw new Error(`isAnonymous is not supported for ${params.channel} polls`);
}
async function resolveRequiredChannel(params) {
return await resolveMessageChannelSelection({
cfg: params.cfg,
channel: params.channel
});
}
function deriveRequiredMessageSendCapabilities(params) {
return deriveDurableFinalDeliveryRequirementsForBatch({
...params,
reconcileUnknownSend: true
});
}
async function assertRequiredMessageSendDurability(params) {
const support = await resolveOutboundDurableFinalDeliverySupport({
cfg: params.cfg,
agentId: params.agentId,
channel: params.channel,
requirements: deriveRequiredMessageSendCapabilities(params)
});
if (support.ok) return;
const suffix = support.reason === "capability_mismatch" && support.capability ? `missing ${support.capability}` : support.reason;
throw new Error(`Required durable message send is unsupported for ${params.channel}: ${suffix}. Use queuePolicy:"best_effort" for best-effort delivery, omit bestEffort:false in message-tool calls, or use a channel with required durable delivery support.`);
}
function resolveGatewayOptions(opts) {
return resolveOutboundMessageGatewayOptions(opts);
}
async function callMessageGateway(params) {
const { callGatewayLeastPrivilege } = await loadMessageGatewayRuntime();
const gateway = resolveGatewayOptions(params.gateway);
const agentRuntimeIdentityToken = await params.gateway?.resolveAgentRuntimeIdentityToken?.();
await params.onPlatformSendDispatch?.();
return await callGatewayLeastPrivilege({
url: gateway.url,
token: gateway.token,
method: params.method,
params: params.params,
timeoutMs: gateway.timeoutMs,
clientName: gateway.clientName,
clientDisplayName: gateway.clientDisplayName,
mode: gateway.mode,
agentRuntimeIdentityToken
});
}
async function resolveMessageConfig(cfg) {
if (cfg) return cfg;
const { getRuntimeConfig } = await loadMessageConfigRuntime();
return getRuntimeConfig();
}
async function resolveGatewayIdempotencyKey(idempotencyKey) {
if (idempotencyKey) return idempotencyKey;
const { randomIdempotencyKey } = await loadMessageGatewayRuntime();
return randomIdempotencyKey();
}
async function sendMessage(params) {
const cfg = await resolveMessageConfig(params.cfg);
const reply = normalizeOutboundReplyFacts({
reply: params.reply,
replyToId: params.replyToId
});
const { channel, plugin } = params.preparedPlugin ? {
channel: params.preparedPlugin.id,
plugin: params.preparedPlugin
} : await resolveRequiredChannel({
cfg,
channel: params.channel
});
const deliveryMode = plugin.outbound?.deliveryMode ?? "direct";
const hasRealMediaSource = [params.mediaUrl, ...params.mediaUrls ?? []].filter((source) => Boolean(source)).some((source) => source !== SEND_BUFFER_MEDIA_URL);
const shouldForwardBuffer = deliveryMode === "gateway" && Boolean(params.buffer) && !hasRealMediaSource;
const mediaUrl = params.mediaUrl ?? (shouldForwardBuffer ? SEND_BUFFER_MEDIA_URL : void 0);
const mediaUrls = params.mediaUrls ?? (shouldForwardBuffer ? [SEND_BUFFER_MEDIA_URL] : void 0);
const outboundPayloads = params.payloads && params.payloads.length > 0 ? params.payloads : [{
text: params.content,
mediaUrl,
mediaUrls,
audioAsVoice: params.asVoice === true
}];
const outboundPlan = createOutboundPayloadPlan(outboundPayloads);
const normalizedPayloads = projectOutboundPayloadPlanForDelivery(outboundPlan);
const mirrorProjection = projectOutboundPayloadPlanForMirror(outboundPlan);
const mirrorText = mirrorProjection.text;
const mirrorMediaUrls = mirrorProjection.mediaUrls;
const primaryMediaUrl = mirrorMediaUrls[0] ?? mediaUrl ?? null;
if (params.dryRun) return {
channel,
to: params.to,
via: deliveryMode === "gateway" ? "gateway" : "direct",
mediaUrl: primaryMediaUrl,
mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0,
dryRun: true
};
if (deliveryMode !== "gateway" || params.gatewayOwnedDelivery === true) {
const outboundChannel = channel;
const resolvedTarget = resolveOutboundTarget({
channel: outboundChannel,
plugin,
to: params.to,
cfg,
accountId: params.accountId,
mode: "explicit"
});
if (!resolvedTarget.ok) throw resolvedTarget.error;
const outboundSession = buildOutboundSessionContext({
cfg,
agentId: params.agentId,
sessionKey: params.requesterSessionKey ?? params.mirror?.sessionKey,
conversationType: params.conversationType,
requesterAccountId: params.requesterAccountId ?? params.accountId,
requesterSenderId: params.requesterSenderId,
requesterSenderName: params.requesterSenderName,
requesterSenderUsername: params.requesterSenderUsername,
requesterSenderE164: params.requesterSenderE164
});
const requireUnknownSendReconciliation = params.requireUnknownSendReconciliation ?? params.queuePolicy === "required";
if (requireUnknownSendReconciliation) await assertRequiredMessageSendDurability({
cfg,
agentId: params.agentId,
channel: outboundChannel,
payloads: normalizedPayloads,
replyToId: reply?.replyToId,
threadId: params.threadId,
silent: params.silent
});
const send = await sendDurableMessageBatchCore({
cfg,
channel: outboundChannel,
to: resolvedTarget.to,
session: outboundSession,
runId: params.runId,
executionIdentityToken: params.executionIdentityToken,
accountId: params.accountId,
conversationReadOrigin: params.conversationReadOrigin,
payloads: normalizedPayloads,
reply,
threadId: params.threadId,
gifPlayback: params.gifPlayback,
forceDocument: params.forceDocument,
deps: params.deps,
bestEffort: params.bestEffort,
...requireUnknownSendReconciliation ? { requireUnknownSendReconciliation: true } : {},
durability: params.bestEffort || params.queuePolicy === "best_effort" ? "best_effort" : "required",
signal: params.abortSignal,
silent: params.silent,
mediaAccess: params.mediaAccess,
formatting: params.parseMode ? { parseMode: params.parseMode } : void 0,
preparedMessageId: params.preparedMessageId,
deliveryIntentId: params.deliveryIntentId,
deliveryCompletion: params.deliveryCompletion,
reusePendingDeliveryIntent: params.reusePendingDeliveryIntent,
deliveryRetryOwner: params.deliveryRetryOwner,
completionRetention: params.completionRetention,
...params.onDeliveryIntent ? { onDeliveryIntent: params.onDeliveryIntent } : {},
...params.onDeliveryAttempt ? { onDeliveryAttempt: params.onDeliveryAttempt } : {},
...params.onDeliveryResult ? { onDeliveryResult: params.onDeliveryResult } : {},
...params.onPlatformSendDispatch ? { onPlatformSendDispatch: params.onPlatformSendDispatch } : {},
skipQueue: params.skipQueue,
...params.onDeliveredPayload ? { onDeliveredPayload: params.onDeliveredPayload } : {},
mirror: params.mirror ? {
...params.mirror,
text: mirrorText || params.content,
mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0,
idempotencyKey: params.mirror.idempotencyKey ?? params.idempotencyKey
} : void 0
});
if (!params.bestEffort && params.gateway?.clientName !== GATEWAY_CLIENT_NAMES.CLI && (send.status === "failed" || send.status === "partial_failed")) throw send.error;
const results = send.status === "sent" || send.status === "partial_failed" ? send.results : [];
const payloadOutcomes = serializeDurableMessagePayloadOutcomes(send.payloadOutcomes);
return {
channel,
to: params.to,
via: "direct",
mediaUrl: primaryMediaUrl,
mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0,
result: results.at(-1),
deliveryStatus: send.status,
...send.status === "suppressed" ? { suppressionReason: send.reason } : {},
...send.status === "failed" || send.status === "partial_failed" ? { error: formatErrorMessage(send.error) } : {},
...send.status === "partial_failed" ? { sentBeforeError: true } : {},
...payloadOutcomes ? { payloadOutcomes } : {}
};
}
const result = await callMessageGateway({
gateway: params.gateway,
method: "send",
onPlatformSendDispatch: params.onPlatformSendDispatch,
params: {
to: params.to,
message: params.content,
mediaUrl,
mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : mediaUrls,
buffer: shouldForwardBuffer ? params.buffer : void 0,
filename: shouldForwardBuffer ? params.filename : void 0,
contentType: shouldForwardBuffer ? params.contentType : void 0,
asVoice: params.asVoice,
gifPlayback: params.gifPlayback,
accountId: params.accountId,
agentId: params.agentId,
channel,
replyToId: reply?.replyToId,
threadId: params.threadId != null ? String(params.threadId) : void 0,
forceDocument: params.forceDocument,
silent: params.silent,
parseMode: params.parseMode,
sessionKey: params.mirror?.sessionKey,
idempotencyKey: await resolveGatewayIdempotencyKey(params.idempotencyKey)
}
});
return {
channel,
to: params.to,
via: "gateway",
mediaUrl: primaryMediaUrl,
mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0,
result
};
}
async function sendPoll(params) {
const cfg = await resolveMessageConfig(params.cfg);
const { channel, plugin } = params.preparedPlugin ? {
channel: params.preparedPlugin.id,
plugin: params.preparedPlugin
} : await resolveRequiredChannel({
cfg,
channel: params.channel
});
const pollInput = {
question: params.question,
options: params.options,
maxSelections: params.maxSelections,
durationSeconds: params.durationSeconds,
durationHours: params.durationHours
};
const outbound = plugin.outbound;
if (!outbound?.sendPoll) throw new Error(`Unsupported poll channel: ${channel}`);
const deliveryMode = outbound.deliveryMode ?? "direct";
const normalized = outbound.pollMaxOptions ? normalizePollInput(pollInput, { maxOptions: outbound.pollMaxOptions }) : normalizePollInput(pollInput);
if (params.dryRun) return buildMessagePollResult({
channel,
to: params.to,
normalized,
via: deliveryMode === "gateway" ? "gateway" : "direct",
dryRun: true
});
assertPollOptionSupport({
channel,
outbound,
durationSeconds: params.durationSeconds,
isAnonymous: params.isAnonymous
});
if (deliveryMode !== "gateway") {
const resolvedTarget = resolveOutboundTarget({
channel,
plugin,
to: params.to,
cfg,
accountId: params.accountId,
mode: "explicit"
});
if (!resolvedTarget.ok) throw resolvedTarget.error;
const result = await outbound.sendPoll({
cfg,
to: resolvedTarget.to,
poll: normalized,
content: params.content,
accountId: params.accountId,
threadId: params.threadId,
silent: params.silent,
isAnonymous: params.isAnonymous,
sessionKey: params.sessionKey,
inboundEventKind: params.inboundEventKind,
onPlatformSendDispatch: params.onPlatformSendDispatch
});
return buildMessagePollResult({
channel,
to: params.to,
normalized,
via: "direct",
result: normalizeMessagePollDeliveryResult(result)
});
}
const result = await callMessageGateway({
gateway: params.gateway,
method: "poll",
params: {
to: params.to,
question: normalized.question,
options: normalized.options,
maxSelections: normalized.maxSelections,
durationSeconds: normalized.durationSeconds,
durationHours: normalized.durationHours,
threadId: params.threadId,
silent: params.silent,
isAnonymous: params.isAnonymous,
channel,
accountId: params.accountId,
idempotencyKey: await resolveGatewayIdempotencyKey(params.idempotencyKey)
}
});
return buildMessagePollResult({
channel,
to: params.to,
normalized,
via: "gateway",
result: normalizeMessagePollDeliveryResult(result)
});
}
//#endregion
export { sendPoll as n, resolveOutboundMessageGatewayOptions as r, sendMessage as t };