From 8ec194a6048c1dd68d65c2aaa039ed9d2232998e Mon Sep 17 00:00:00 2001 From: Adam Gough <77861281+aadamgough@users.noreply.github.com> Date: Sat, 24 May 2025 22:28:31 -0700 Subject: [PATCH] feat(tools): added support for ms teams attachments (#412) * added support for attachments in teams * ran linter * fixed small error * run lint * removed comments --------- Co-authored-by: Adam Gough --- .../components/teams-message-selector.tsx | 4 +- .../tools/microsoft_teams/attachment-utils.ts | 29 +++++ .../sim/tools/microsoft_teams/read_channel.ts | 100 ++++++++++++++---- apps/sim/tools/microsoft_teams/read_chat.ts | 70 +++++++++--- apps/sim/tools/microsoft_teams/types.ts | 18 +++- .../tools/microsoft_teams/write_channel.ts | 2 - 6 files changed, 182 insertions(+), 41 deletions(-) create mode 100644 apps/sim/tools/microsoft_teams/attachment-utils.ts diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx index cf0a6f700c..fc457c49ac 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx @@ -381,8 +381,8 @@ export function TeamsMessageSelector({ setSelectedChannelId(channel.channelId || '') setSelectedChatId('') setSelectedMessage(channel) - setSelectedMessageId(channel.id) - onChange(channel.id, channel) + setSelectedMessageId(channel.channelId || '') + onChange(channel.channelId || '', channel) onMessageInfoChange?.(channel) setOpen(false) } diff --git a/apps/sim/tools/microsoft_teams/attachment-utils.ts b/apps/sim/tools/microsoft_teams/attachment-utils.ts new file mode 100644 index 0000000000..b8f259a7e3 --- /dev/null +++ b/apps/sim/tools/microsoft_teams/attachment-utils.ts @@ -0,0 +1,29 @@ +import type { MicrosoftTeamsAttachment } from './types' + +/** + * Transform raw attachment data from Microsoft Graph API + */ +function transformAttachment(rawAttachment: any): MicrosoftTeamsAttachment { + return { + id: rawAttachment.id, + contentType: rawAttachment.contentType, + contentUrl: rawAttachment.contentUrl, + content: rawAttachment.content, + name: rawAttachment.name, + thumbnailUrl: rawAttachment.thumbnailUrl, + size: rawAttachment.size, + sourceUrl: rawAttachment.sourceUrl, + providerType: rawAttachment.providerType, + item: rawAttachment.item, + } +} + +/** + * Extract attachments from message data + * Returns all attachments without any content processing + */ +export function extractMessageAttachments(message: any): MicrosoftTeamsAttachment[] { + const attachments = (message.attachments || []).map(transformAttachment) + + return attachments +} diff --git a/apps/sim/tools/microsoft_teams/read_channel.ts b/apps/sim/tools/microsoft_teams/read_channel.ts index e08244b93a..3ff2a4cad1 100644 --- a/apps/sim/tools/microsoft_teams/read_channel.ts +++ b/apps/sim/tools/microsoft_teams/read_channel.ts @@ -1,11 +1,15 @@ +import { createLogger } from '@/lib/logs/console-logger' import type { ToolConfig } from '../types' +import { extractMessageAttachments } from './attachment-utils' import type { MicrosoftTeamsReadResponse, MicrosoftTeamsToolParams } from './types' +const logger = createLogger('MicrosoftTeamsReadChannel') + export const readChannelTool: ToolConfig = { id: 'microsoft_teams_read_channel', name: 'Read Microsoft Teams Channel', description: 'Read content from a Microsoft Teams channel', - version: '1.0', + version: '1.1', oauth: { required: true, provider: 'microsoft-teams', @@ -60,10 +64,16 @@ export const readChannelTool: ToolConfig { + transformResponse: async (response: Response, params?: MicrosoftTeamsToolParams) => { if (!response.ok) { const errorText = await response.text() - throw new Error(`Failed to read Microsoft Teams channel: ${errorText}`) + logger.error( + `Microsoft Teams channel API error: ${response.status} ${response.statusText}`, + errorText + ) + throw new Error( + `Failed to read Microsoft Teams channel: ${response.status} ${response.statusText} - ${errorText}` + ) } const data = await response.json() @@ -81,36 +91,88 @@ export const readChannelTool: ToolConfig { + if (!params?.teamId || !params?.channelId) { + throw new Error('Missing required parameters: teamId and channelId') + } + // Process messages with attachments + const processedMessages = messages.map((message: any, index: number) => { + try { const content = message.body?.content || 'No content' - const sender = message.from?.user?.displayName || 'Unknown sender' - const timestamp = message.createdDateTime - ? new Date(message.createdDateTime).toLocaleString() + const messageId = message.id + + const attachments = extractMessageAttachments(message) + + let sender = 'Unknown' + if (message.from?.user?.displayName) { + sender = message.from.user.displayName + } else if (message.messageType === 'systemEventMessage') { + sender = 'System' + } + + return { + id: messageId, + content: content, + sender, + timestamp: message.createdDateTime, + messageType: message.messageType || 'message', + attachments, + } + } catch (error) { + logger.error(`Error processing message at index ${index}:`, error) + return { + id: message.id || `unknown-${index}`, + content: 'Error processing message', + sender: 'Unknown', + timestamp: message.createdDateTime || new Date().toISOString(), + messageType: 'error', + attachments: [], + } + } + }) + + // Format the messages into a readable text (no attachment info in content) + const formattedMessages = processedMessages + .map((message: any) => { + const sender = message.sender + const timestamp = message.timestamp + ? new Date(message.timestamp).toLocaleString() : 'Unknown time' - return `[${timestamp}] ${sender}: ${content}` + return `[${timestamp}] ${sender}: ${message.content}` }) .join('\n\n') + // Calculate attachment statistics + const allAttachments = processedMessages.flatMap((msg: any) => msg.attachments || []) + const attachmentTypes: string[] = [] + const seenTypes = new Set() + + allAttachments.forEach((att: any) => { + if ( + att.contentType && + typeof att.contentType === 'string' && + !seenTypes.has(att.contentType) + ) { + attachmentTypes.push(att.contentType) + seenTypes.add(att.contentType) + } + }) + // Create document metadata const metadata = { - teamId: messages[0]?.channelIdentity?.teamId || '', - channelId: messages[0]?.channelIdentity?.channelId || '', + teamId: messages[0]?.channelIdentity?.teamId || params.teamId || '', + channelId: messages[0]?.channelIdentity?.channelId || params.channelId || '', messageCount: messages.length, - messages: messages.map((msg: any) => ({ - id: msg.id, - content: msg.body?.content || '', - sender: msg.from?.user?.displayName || 'Unknown', - timestamp: msg.createdDateTime, - messageType: msg.messageType || 'message', - })), + totalAttachments: allAttachments.length, + attachmentTypes, + messages: processedMessages, } return { diff --git a/apps/sim/tools/microsoft_teams/read_chat.ts b/apps/sim/tools/microsoft_teams/read_chat.ts index e9a6fabb57..451fdb527f 100644 --- a/apps/sim/tools/microsoft_teams/read_chat.ts +++ b/apps/sim/tools/microsoft_teams/read_chat.ts @@ -1,11 +1,12 @@ import type { ToolConfig } from '../types' +import { extractMessageAttachments } from './attachment-utils' import type { MicrosoftTeamsReadResponse, MicrosoftTeamsToolParams } from './types' export const readChatTool: ToolConfig = { id: 'microsoft_teams_read_chat', name: 'Read Microsoft Teams Chat', description: 'Read content from a Microsoft Teams chat', - version: '1.0', + version: '1.1', oauth: { required: true, provider: 'microsoft-teams', @@ -44,7 +45,7 @@ export const readChatTool: ToolConfig { + transformResponse: async (response: Response, params?: MicrosoftTeamsToolParams) => { if (!response.ok) { const errorText = await response.text() throw new Error(`Failed to read Microsoft Teams chat: ${errorText}`) @@ -64,35 +65,70 @@ export const readChatTool: ToolConfig { + const content = message.body?.content || 'No content' + const messageId = message.id + + // Extract attachments without any content processing + const attachments = extractMessageAttachments(message) + + return { + id: messageId, + content: content, // Keep original content without modification + sender: message.from?.user?.displayName || 'Unknown', + timestamp: message.createdDateTime, + messageType: message.messageType || 'message', + attachments, // Attachments only stored here + } + }) + + // Format the messages into a readable text (no attachment info in content) + const formattedMessages = processedMessages .map((message: any) => { - const content = message.body?.content || 'No content' - const sender = message.from?.user?.displayName || 'Unknown sender' - const timestamp = message.createdDateTime - ? new Date(message.createdDateTime).toLocaleString() + const sender = message.sender + const timestamp = message.timestamp + ? new Date(message.timestamp).toLocaleString() : 'Unknown time' - return `[${timestamp}] ${sender}: ${content}` + return `[${timestamp}] ${sender}: ${message.content}` }) .join('\n\n') + // Calculate attachment statistics + const allAttachments = processedMessages.flatMap((msg: any) => msg.attachments || []) + const attachmentTypes: string[] = [] + const seenTypes = new Set() + + allAttachments.forEach((att: any) => { + if ( + att.contentType && + typeof att.contentType === 'string' && + !seenTypes.has(att.contentType) + ) { + attachmentTypes.push(att.contentType) + seenTypes.add(att.contentType) + } + }) + // Create document metadata const metadata = { - chatId: messages[0]?.chatId || '', + chatId: messages[0]?.chatId || params.chatId || '', messageCount: messages.length, - messages: messages.map((msg: any) => ({ - id: msg.id, - content: msg.body?.content || '', - sender: msg.from?.user?.displayName || 'Unknown', - timestamp: msg.createdDateTime, - messageType: msg.messageType || 'message', - })), + totalAttachments: allAttachments.length, + attachmentTypes, + messages: processedMessages, } return { diff --git a/apps/sim/tools/microsoft_teams/types.ts b/apps/sim/tools/microsoft_teams/types.ts index 0378889e7b..e0408c554f 100644 --- a/apps/sim/tools/microsoft_teams/types.ts +++ b/apps/sim/tools/microsoft_teams/types.ts @@ -1,5 +1,18 @@ import type { ToolResponse } from '../types' +export interface MicrosoftTeamsAttachment { + id: string + contentType: string + contentUrl?: string + content?: string + name?: string + thumbnailUrl?: string + size?: number + sourceUrl?: string + providerType?: string + item?: any +} + export interface MicrosoftTeamsMetadata { messageId?: string channelId?: string @@ -7,7 +20,6 @@ export interface MicrosoftTeamsMetadata { chatId?: string content?: string createdTime?: string - modifiedTime?: string url?: string messageCount?: number messages?: Array<{ @@ -16,7 +28,11 @@ export interface MicrosoftTeamsMetadata { sender: string timestamp: string messageType: string + attachments?: MicrosoftTeamsAttachment[] }> + // Global attachments summary + totalAttachments?: number + attachmentTypes?: string[] } export interface MicrosoftTeamsReadResponse extends ToolResponse { diff --git a/apps/sim/tools/microsoft_teams/write_channel.ts b/apps/sim/tools/microsoft_teams/write_channel.ts index d6074de23d..78243bc7dc 100644 --- a/apps/sim/tools/microsoft_teams/write_channel.ts +++ b/apps/sim/tools/microsoft_teams/write_channel.ts @@ -44,11 +44,9 @@ export const writeChannelTool: ToolConfig