mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 08:57:40 +00:00
refactor: dedupe channel outbound and monitor tests
This commit is contained in:
@@ -20,6 +20,51 @@ type DirectSendFn<TOpts extends Record<string, unknown>, TResult extends DirectS
|
||||
opts: TOpts,
|
||||
) => Promise<TResult>;
|
||||
|
||||
type SendPayloadContext = Parameters<NonNullable<ChannelOutboundAdapter["sendPayload"]>>[0];
|
||||
type SendPayloadResult = Awaited<ReturnType<NonNullable<ChannelOutboundAdapter["sendPayload"]>>>;
|
||||
type SendPayloadAdapter = Pick<
|
||||
ChannelOutboundAdapter,
|
||||
"sendMedia" | "sendText" | "chunker" | "textChunkLimit"
|
||||
>;
|
||||
|
||||
export async function sendTextMediaPayload(params: {
|
||||
channel: string;
|
||||
ctx: SendPayloadContext;
|
||||
adapter: SendPayloadAdapter;
|
||||
}): Promise<SendPayloadResult> {
|
||||
const text = params.ctx.payload.text ?? "";
|
||||
const urls = params.ctx.payload.mediaUrls?.length
|
||||
? params.ctx.payload.mediaUrls
|
||||
: params.ctx.payload.mediaUrl
|
||||
? [params.ctx.payload.mediaUrl]
|
||||
: [];
|
||||
if (!text && urls.length === 0) {
|
||||
return { channel: params.channel, messageId: "" };
|
||||
}
|
||||
if (urls.length > 0) {
|
||||
let lastResult = await params.adapter.sendMedia!({
|
||||
...params.ctx,
|
||||
text,
|
||||
mediaUrl: urls[0],
|
||||
});
|
||||
for (let i = 1; i < urls.length; i++) {
|
||||
lastResult = await params.adapter.sendMedia!({
|
||||
...params.ctx,
|
||||
text: "",
|
||||
mediaUrl: urls[i],
|
||||
});
|
||||
}
|
||||
return lastResult;
|
||||
}
|
||||
const limit = params.adapter.textChunkLimit;
|
||||
const chunks = limit && params.adapter.chunker ? params.adapter.chunker(text, limit) : [text];
|
||||
let lastResult: Awaited<ReturnType<NonNullable<typeof params.adapter.sendText>>>;
|
||||
for (const chunk of chunks) {
|
||||
lastResult = await params.adapter.sendText!({ ...params.ctx, text: chunk });
|
||||
}
|
||||
return lastResult!;
|
||||
}
|
||||
|
||||
export function resolveScopedChannelMediaMaxBytes(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
@@ -91,39 +136,8 @@ export function createDirectTextMediaOutbound<
|
||||
chunker: chunkText,
|
||||
chunkerMode: "text",
|
||||
textChunkLimit: 4000,
|
||||
sendPayload: async (ctx) => {
|
||||
const text = ctx.payload.text ?? "";
|
||||
const urls = ctx.payload.mediaUrls?.length
|
||||
? ctx.payload.mediaUrls
|
||||
: ctx.payload.mediaUrl
|
||||
? [ctx.payload.mediaUrl]
|
||||
: [];
|
||||
if (!text && urls.length === 0) {
|
||||
return { channel: params.channel, messageId: "" };
|
||||
}
|
||||
if (urls.length > 0) {
|
||||
let lastResult = await outbound.sendMedia!({
|
||||
...ctx,
|
||||
text,
|
||||
mediaUrl: urls[0],
|
||||
});
|
||||
for (let i = 1; i < urls.length; i++) {
|
||||
lastResult = await outbound.sendMedia!({
|
||||
...ctx,
|
||||
text: "",
|
||||
mediaUrl: urls[i],
|
||||
});
|
||||
}
|
||||
return lastResult;
|
||||
}
|
||||
const limit = outbound.textChunkLimit;
|
||||
const chunks = limit && outbound.chunker ? outbound.chunker(text, limit) : [text];
|
||||
let lastResult: Awaited<ReturnType<NonNullable<typeof outbound.sendText>>>;
|
||||
for (const chunk of chunks) {
|
||||
lastResult = await outbound.sendText!({ ...ctx, text: chunk });
|
||||
}
|
||||
return lastResult!;
|
||||
},
|
||||
sendPayload: async (ctx) =>
|
||||
await sendTextMediaPayload({ channel: params.channel, ctx, adapter: outbound }),
|
||||
sendText: async ({ cfg, to, text, accountId, deps, replyToId }) => {
|
||||
return await sendDirect({
|
||||
cfg,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import type { OutboundIdentity } from "../../../infra/outbound/identity.js";
|
||||
import { normalizeDiscordOutboundTarget } from "../normalize/discord.js";
|
||||
import type { ChannelOutboundAdapter } from "../types.js";
|
||||
import { sendTextMediaPayload } from "./direct-text-media.js";
|
||||
|
||||
function resolveDiscordOutboundTarget(params: {
|
||||
to: string;
|
||||
@@ -80,39 +81,8 @@ export const discordOutbound: ChannelOutboundAdapter = {
|
||||
textChunkLimit: 2000,
|
||||
pollMaxOptions: 10,
|
||||
resolveTarget: ({ to }) => normalizeDiscordOutboundTarget(to),
|
||||
sendPayload: async (ctx) => {
|
||||
const text = ctx.payload.text ?? "";
|
||||
const urls = ctx.payload.mediaUrls?.length
|
||||
? ctx.payload.mediaUrls
|
||||
: ctx.payload.mediaUrl
|
||||
? [ctx.payload.mediaUrl]
|
||||
: [];
|
||||
if (!text && urls.length === 0) {
|
||||
return { channel: "discord", messageId: "" };
|
||||
}
|
||||
if (urls.length > 0) {
|
||||
let lastResult = await discordOutbound.sendMedia!({
|
||||
...ctx,
|
||||
text,
|
||||
mediaUrl: urls[0],
|
||||
});
|
||||
for (let i = 1; i < urls.length; i++) {
|
||||
lastResult = await discordOutbound.sendMedia!({
|
||||
...ctx,
|
||||
text: "",
|
||||
mediaUrl: urls[i],
|
||||
});
|
||||
}
|
||||
return lastResult;
|
||||
}
|
||||
const limit = discordOutbound.textChunkLimit;
|
||||
const chunks = limit && discordOutbound.chunker ? discordOutbound.chunker(text, limit) : [text];
|
||||
let lastResult: Awaited<ReturnType<NonNullable<typeof discordOutbound.sendText>>>;
|
||||
for (const chunk of chunks) {
|
||||
lastResult = await discordOutbound.sendText!({ ...ctx, text: chunk });
|
||||
}
|
||||
return lastResult!;
|
||||
},
|
||||
sendPayload: async (ctx) =>
|
||||
await sendTextMediaPayload({ channel: "discord", ctx, adapter: discordOutbound }),
|
||||
sendText: async ({ to, text, accountId, deps, replyToId, threadId, identity, silent }) => {
|
||||
if (!silent) {
|
||||
const webhookResult = await maybeSendDiscordWebhookText({
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { OutboundIdentity } from "../../../infra/outbound/identity.js";
|
||||
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
|
||||
import { sendMessageSlack, type SlackSendIdentity } from "../../../slack/send.js";
|
||||
import type { ChannelOutboundAdapter } from "../types.js";
|
||||
import { sendTextMediaPayload } from "./direct-text-media.js";
|
||||
|
||||
function resolveSlackSendIdentity(identity?: OutboundIdentity): SlackSendIdentity | undefined {
|
||||
if (!identity) {
|
||||
@@ -93,39 +94,8 @@ export const slackOutbound: ChannelOutboundAdapter = {
|
||||
deliveryMode: "direct",
|
||||
chunker: null,
|
||||
textChunkLimit: 4000,
|
||||
sendPayload: async (ctx) => {
|
||||
const text = ctx.payload.text ?? "";
|
||||
const urls = ctx.payload.mediaUrls?.length
|
||||
? ctx.payload.mediaUrls
|
||||
: ctx.payload.mediaUrl
|
||||
? [ctx.payload.mediaUrl]
|
||||
: [];
|
||||
if (!text && urls.length === 0) {
|
||||
return { channel: "slack", messageId: "" };
|
||||
}
|
||||
if (urls.length > 0) {
|
||||
let lastResult = await slackOutbound.sendMedia!({
|
||||
...ctx,
|
||||
text,
|
||||
mediaUrl: urls[0],
|
||||
});
|
||||
for (let i = 1; i < urls.length; i++) {
|
||||
lastResult = await slackOutbound.sendMedia!({
|
||||
...ctx,
|
||||
text: "",
|
||||
mediaUrl: urls[i],
|
||||
});
|
||||
}
|
||||
return lastResult;
|
||||
}
|
||||
const limit = slackOutbound.textChunkLimit;
|
||||
const chunks = limit && slackOutbound.chunker ? slackOutbound.chunker(text, limit) : [text];
|
||||
let lastResult: Awaited<ReturnType<NonNullable<typeof slackOutbound.sendText>>>;
|
||||
for (const chunk of chunks) {
|
||||
lastResult = await slackOutbound.sendText!({ ...ctx, text: chunk });
|
||||
}
|
||||
return lastResult!;
|
||||
},
|
||||
sendPayload: async (ctx) =>
|
||||
await sendTextMediaPayload({ channel: "slack", ctx, adapter: slackOutbound }),
|
||||
sendText: async ({ to, text, accountId, deps, replyToId, threadId, identity }) => {
|
||||
return await sendSlackOutboundMessage({
|
||||
to,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { shouldLogVerbose } from "../../../globals.js";
|
||||
import { sendPollWhatsApp } from "../../../web/outbound.js";
|
||||
import { resolveWhatsAppOutboundTarget } from "../../../whatsapp/resolve-outbound-target.js";
|
||||
import type { ChannelOutboundAdapter } from "../types.js";
|
||||
import { sendTextMediaPayload } from "./direct-text-media.js";
|
||||
|
||||
export const whatsappOutbound: ChannelOutboundAdapter = {
|
||||
deliveryMode: "gateway",
|
||||
@@ -12,40 +13,8 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
|
||||
pollMaxOptions: 12,
|
||||
resolveTarget: ({ to, allowFrom, mode }) =>
|
||||
resolveWhatsAppOutboundTarget({ to, allowFrom, mode }),
|
||||
sendPayload: async (ctx) => {
|
||||
const text = ctx.payload.text ?? "";
|
||||
const urls = ctx.payload.mediaUrls?.length
|
||||
? ctx.payload.mediaUrls
|
||||
: ctx.payload.mediaUrl
|
||||
? [ctx.payload.mediaUrl]
|
||||
: [];
|
||||
if (!text && urls.length === 0) {
|
||||
return { channel: "whatsapp", messageId: "" };
|
||||
}
|
||||
if (urls.length > 0) {
|
||||
let lastResult = await whatsappOutbound.sendMedia!({
|
||||
...ctx,
|
||||
text,
|
||||
mediaUrl: urls[0],
|
||||
});
|
||||
for (let i = 1; i < urls.length; i++) {
|
||||
lastResult = await whatsappOutbound.sendMedia!({
|
||||
...ctx,
|
||||
text: "",
|
||||
mediaUrl: urls[i],
|
||||
});
|
||||
}
|
||||
return lastResult;
|
||||
}
|
||||
const limit = whatsappOutbound.textChunkLimit;
|
||||
const chunks =
|
||||
limit && whatsappOutbound.chunker ? whatsappOutbound.chunker(text, limit) : [text];
|
||||
let lastResult: Awaited<ReturnType<NonNullable<typeof whatsappOutbound.sendText>>>;
|
||||
for (const chunk of chunks) {
|
||||
lastResult = await whatsappOutbound.sendText!({ ...ctx, text: chunk });
|
||||
}
|
||||
return lastResult!;
|
||||
},
|
||||
sendPayload: async (ctx) =>
|
||||
await sendTextMediaPayload({ channel: "whatsapp", ctx, adapter: whatsappOutbound }),
|
||||
sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
|
||||
const send =
|
||||
deps?.sendWhatsApp ?? (await import("../../../web/outbound.js")).sendMessageWhatsApp;
|
||||
|
||||
Reference in New Issue
Block a user