mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 20:08:26 +00:00
refactor: centralize delivery/path/media/version lifecycle
This commit is contained in:
@@ -14,11 +14,18 @@ export const msteamsOutbound: ChannelOutboundAdapter = {
|
||||
const result = await send(to, text);
|
||||
return { channel: "msteams", ...result };
|
||||
},
|
||||
sendMedia: async ({ cfg, to, text, mediaUrl, deps }) => {
|
||||
sendMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, deps }) => {
|
||||
const send =
|
||||
deps?.sendMSTeams ??
|
||||
((to, text, opts) => sendMessageMSTeams({ cfg, to, text, mediaUrl: opts?.mediaUrl }));
|
||||
const result = await send(to, text, { mediaUrl });
|
||||
((to, text, opts) =>
|
||||
sendMessageMSTeams({
|
||||
cfg,
|
||||
to,
|
||||
text,
|
||||
mediaUrl: opts?.mediaUrl,
|
||||
mediaLocalRoots: opts?.mediaLocalRoots,
|
||||
}));
|
||||
const result = await send(to, text, { mediaUrl, mediaLocalRoots });
|
||||
return { channel: "msteams", ...result };
|
||||
},
|
||||
sendPoll: async ({ cfg, to, poll }) => {
|
||||
|
||||
109
extensions/msteams/src/send.test.ts
Normal file
109
extensions/msteams/src/send.test.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { sendMessageMSTeams } from "./send.js";
|
||||
|
||||
const mockState = vi.hoisted(() => ({
|
||||
loadOutboundMediaFromUrl: vi.fn(),
|
||||
resolveMSTeamsSendContext: vi.fn(),
|
||||
requiresFileConsent: vi.fn(),
|
||||
prepareFileConsentActivity: vi.fn(),
|
||||
extractFilename: vi.fn(async () => "fallback.bin"),
|
||||
sendMSTeamsMessages: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk", () => ({
|
||||
loadOutboundMediaFromUrl: mockState.loadOutboundMediaFromUrl,
|
||||
}));
|
||||
|
||||
vi.mock("./send-context.js", () => ({
|
||||
resolveMSTeamsSendContext: mockState.resolveMSTeamsSendContext,
|
||||
}));
|
||||
|
||||
vi.mock("./file-consent-helpers.js", () => ({
|
||||
requiresFileConsent: mockState.requiresFileConsent,
|
||||
prepareFileConsentActivity: mockState.prepareFileConsentActivity,
|
||||
}));
|
||||
|
||||
vi.mock("./media-helpers.js", () => ({
|
||||
extractFilename: mockState.extractFilename,
|
||||
extractMessageId: () => "message-1",
|
||||
}));
|
||||
|
||||
vi.mock("./messenger.js", () => ({
|
||||
sendMSTeamsMessages: mockState.sendMSTeamsMessages,
|
||||
buildConversationReference: () => ({}),
|
||||
}));
|
||||
|
||||
vi.mock("./runtime.js", () => ({
|
||||
getMSTeamsRuntime: () => ({
|
||||
channel: {
|
||||
text: {
|
||||
resolveMarkdownTableMode: () => "off",
|
||||
convertMarkdownTables: (text: string) => text,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("sendMessageMSTeams", () => {
|
||||
beforeEach(() => {
|
||||
mockState.loadOutboundMediaFromUrl.mockReset();
|
||||
mockState.resolveMSTeamsSendContext.mockReset();
|
||||
mockState.requiresFileConsent.mockReset();
|
||||
mockState.prepareFileConsentActivity.mockReset();
|
||||
mockState.extractFilename.mockReset();
|
||||
mockState.sendMSTeamsMessages.mockReset();
|
||||
|
||||
mockState.extractFilename.mockResolvedValue("fallback.bin");
|
||||
mockState.requiresFileConsent.mockReturnValue(false);
|
||||
mockState.resolveMSTeamsSendContext.mockResolvedValue({
|
||||
adapter: {},
|
||||
appId: "app-id",
|
||||
conversationId: "19:conversation@thread.tacv2",
|
||||
ref: {},
|
||||
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
conversationType: "personal",
|
||||
tokenProvider: { getAccessToken: vi.fn(async () => "token") },
|
||||
mediaMaxBytes: 8 * 1024,
|
||||
sharePointSiteId: undefined,
|
||||
});
|
||||
mockState.sendMSTeamsMessages.mockResolvedValue(["message-1"]);
|
||||
});
|
||||
|
||||
it("loads media through shared helper and forwards mediaLocalRoots", async () => {
|
||||
const mediaBuffer = Buffer.from("tiny-image");
|
||||
mockState.loadOutboundMediaFromUrl.mockResolvedValueOnce({
|
||||
buffer: mediaBuffer,
|
||||
contentType: "image/png",
|
||||
fileName: "inline.png",
|
||||
kind: "image",
|
||||
});
|
||||
|
||||
await sendMessageMSTeams({
|
||||
cfg: {} as OpenClawConfig,
|
||||
to: "conversation:19:conversation@thread.tacv2",
|
||||
text: "hello",
|
||||
mediaUrl: "file:///tmp/agent-workspace/inline.png",
|
||||
mediaLocalRoots: ["/tmp/agent-workspace"],
|
||||
});
|
||||
|
||||
expect(mockState.loadOutboundMediaFromUrl).toHaveBeenCalledWith(
|
||||
"file:///tmp/agent-workspace/inline.png",
|
||||
{
|
||||
maxBytes: 8 * 1024,
|
||||
mediaLocalRoots: ["/tmp/agent-workspace"],
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockState.sendMSTeamsMessages).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: [
|
||||
expect.objectContaining({
|
||||
text: "hello",
|
||||
mediaUrl: `data:image/png;base64,${mediaBuffer.toString("base64")}`,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
import { loadWebMedia, resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk";
|
||||
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk";
|
||||
import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js";
|
||||
import {
|
||||
classifyMSTeamsSendError,
|
||||
@@ -28,6 +28,7 @@ export type SendMSTeamsMessageParams = {
|
||||
text: string;
|
||||
/** Optional media URL */
|
||||
mediaUrl?: string;
|
||||
mediaLocalRoots?: readonly string[];
|
||||
};
|
||||
|
||||
export type SendMSTeamsMessageResult = {
|
||||
@@ -93,7 +94,7 @@ export type SendMSTeamsCardResult = {
|
||||
export async function sendMessageMSTeams(
|
||||
params: SendMSTeamsMessageParams,
|
||||
): Promise<SendMSTeamsMessageResult> {
|
||||
const { cfg, to, text, mediaUrl } = params;
|
||||
const { cfg, to, text, mediaUrl, mediaLocalRoots } = params;
|
||||
const tableMode = getMSTeamsRuntime().channel.text.resolveMarkdownTableMode({
|
||||
cfg,
|
||||
channel: "msteams",
|
||||
@@ -120,12 +121,11 @@ export async function sendMessageMSTeams(
|
||||
|
||||
// Handle media if present
|
||||
if (mediaUrl) {
|
||||
const mediaMaxBytes =
|
||||
resolveChannelMediaMaxBytes({
|
||||
cfg,
|
||||
resolveChannelLimitMb: ({ cfg }) => cfg.channels?.msteams?.mediaMaxMb,
|
||||
}) ?? MSTEAMS_MAX_MEDIA_BYTES;
|
||||
const media = await loadWebMedia(mediaUrl, mediaMaxBytes);
|
||||
const mediaMaxBytes = ctx.mediaMaxBytes ?? MSTEAMS_MAX_MEDIA_BYTES;
|
||||
const media = await loadOutboundMediaFromUrl(mediaUrl, {
|
||||
maxBytes: mediaMaxBytes,
|
||||
mediaLocalRoots,
|
||||
});
|
||||
const isLargeFile = media.buffer.length >= FILE_CONSENT_THRESHOLD_BYTES;
|
||||
const isImage = media.contentType?.startsWith("image/") ?? false;
|
||||
const fallbackFileName = await extractFilename(mediaUrl);
|
||||
|
||||
Reference in New Issue
Block a user