UNPKG

msteams-mcp-server

Version:

Microsoft Teams MCP Server - Complete Teams integration for Claude Desktop and MCP clients with secure OAuth2 authentication and comprehensive team management

1,448 lines 64.6 kB
import { logger } from './api.js';
import { teamsAuth } from './teams-auth.js';
/**
 * Microsoft Teams operations using Microsoft Graph API
 */
export class TeamsOperations {
    graphClient = null;
    constructor() {
        // Initialize will be called when needed
    }
    /**
     * Initialize Graph client with authentication
     */
    async ensureGraphClient() {
        if (!this.graphClient) {
            logger.log('Initializing Teams Graph client...');
            this.graphClient = await teamsAuth.getGraphClient();
            logger.log('Teams Graph client initialized successfully');
        }
        else {
            logger.log('Using existing Teams Graph client');
        }
        return this.graphClient;
    }
    /**
     * Get all teams the user is a member of
     */
    async getUserTeams(maxResults) {
        try {
            logger.log('getUserTeams() method called');
            // Build API path with select minimal fields
            // Note: $top is not supported by /me/joinedTeams endpoint
            let apiPath = '/me/joinedTeams';
            const params = [];
            // Select only essential fields to reduce payload size
            params.push('$select=id,displayName,description,visibility,webUrl,createdDateTime');
            if (params.length > 0) {
                apiPath += '?' + params.join('&');
            }
            logger.log(`Making API call to: ${apiPath}`);
            // Use direct HTTP request instead of Graph SDK to avoid hanging
            const token = await this.getAccessToken();
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Successfully retrieved ${response.value.length} teams`);
            // Apply client-side limiting if maxResults is specified
            let teams = response.value;
            if (maxResults && teams.length > maxResults) {
                teams = teams.slice(0, maxResults);
                logger.log(`Limited results to ${maxResults} teams`);
            }
            return teams.map((team) => ({
                id: team.id,
                displayName: team.displayName,
                description: team.description,
                visibility: team.visibility,
                webUrl: team.webUrl,
                createdDateTime: team.createdDateTime,
                memberSettings: team.memberSettings,
                guestSettings: team.guestSettings,
                messagingSettings: team.messagingSettings,
                funSettings: team.funSettings
            }));
        }
        catch (error) {
            logger.error('Error getting user teams:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get teams: ${errorMessage}`);
        }
    }
    /**
     * Search for teams
     */
    async searchTeams(options = {}) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // Build query
            let apiPath = '/me/joinedTeams';
            const params = [];
            if (options.maxResults) {
                params.push(`$top=${options.maxResults}`);
            }
            if (params.length > 0) {
                apiPath += '?' + params.join('&');
            }
            const response = await this.makeDirectGraphRequest(apiPath, token);
            let teams = response.value;
            // Client-side filtering for now (Graph API has limited search capabilities for teams)
            if (options.query) {
                const query = options.query.toLowerCase();
                teams = teams.filter((team) => team.displayName.toLowerCase().includes(query) ||
                    (team.description && team.description.toLowerCase().includes(query)));
            }
            if (options.visibility) {
                teams = teams.filter((team) => team.visibility === options.visibility);
            }
            logger.log(`Found ${teams.length} teams matching search criteria`);
            return teams.map((team) => ({
                id: team.id,
                displayName: team.displayName,
                description: team.description,
                visibility: team.visibility,
                webUrl: team.webUrl,
                createdDateTime: team.createdDateTime
            }));
        }
        catch (error) {
            logger.error('Error searching teams:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to search teams: ${errorMessage}`);
        }
    }
    /**
     * Get a specific team by ID
     */
    async getTeam(teamId) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const response = await this.makeDirectGraphRequest(`/teams/${teamId}`, token);
            logger.log(`Retrieved team: ${response.displayName}`);
            return {
                id: response.id,
                displayName: response.displayName,
                description: response.description,
                visibility: response.visibility,
                webUrl: response.webUrl,
                createdDateTime: response.createdDateTime,
                memberSettings: response.memberSettings,
                guestSettings: response.guestSettings,
                messagingSettings: response.messagingSettings,
                funSettings: response.funSettings
            };
        }
        catch (error) {
            logger.error('Error getting team:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get team: ${errorMessage}`);
        }
    }
    /**
     * Create a new team
     */
    async createTeam(request) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const teamData = {
                'template@odata.bind': "https://graph.microsoft.com/v1.0/teamsTemplates('standard')",
                displayName: request.displayName,
                description: request.description || '',
                visibility: request.visibility
            };
            const response = await this.makeDirectGraphPostRequest('/teams', token, teamData);
            logger.log(`Created team: ${request.displayName}`);
            // Add members and owners if specified
            if (request.owners && request.owners.length > 0) {
                for (const ownerId of request.owners) {
                    await this.addTeamMember(response.id, ownerId, ['owner']);
                }
            }
            if (request.members && request.members.length > 0) {
                for (const memberId of request.members) {
                    await this.addTeamMember(response.id, memberId, ['member']);
                }
            }
            return {
                id: response.id,
                displayName: response.displayName,
                description: response.description,
                visibility: response.visibility,
                webUrl: response.webUrl,
                createdDateTime: response.createdDateTime
            };
        }
        catch (error) {
            logger.error('Error creating team:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to create team: ${errorMessage}`);
        }
    }
    /**
     * Get channels for a team
     */
    async getTeamChannels(teamId) {
        try {
            // Use direct HTTP request to avoid Graph SDK issues
            const token = await this.getAccessToken();
            const apiPath = `/teams/${teamId}/channels`;
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} channels for team ${teamId}`);
            return response.value.map((channel) => ({
                id: channel.id,
                displayName: channel.displayName,
                description: channel.description,
                webUrl: channel.webUrl,
                membershipType: channel.membershipType,
                createdDateTime: channel.createdDateTime
            }));
        }
        catch (error) {
            logger.error('Error getting team channels:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get channels: ${errorMessage}`);
        }
    }
    /**
     * Search channels in a team
     */
    async searchChannels(options) {
        try {
            let channels = await this.getTeamChannels(options.teamId);
            // Client-side filtering
            if (options.query) {
                const query = options.query.toLowerCase();
                channels = channels.filter(channel => channel.displayName.toLowerCase().includes(query) ||
                    (channel.description && channel.description.toLowerCase().includes(query)));
            }
            if (options.membershipType) {
                channels = channels.filter(channel => channel.membershipType === options.membershipType);
            }
            if (options.maxResults) {
                channels = channels.slice(0, options.maxResults);
            }
            logger.log(`Found ${channels.length} channels matching search criteria`);
            return channels;
        }
        catch (error) {
            logger.error('Error searching channels:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to search channels: ${errorMessage}`);
        }
    }
    /**
     * Create a new channel in a team
     */
    async createChannel(teamId, request) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const channelData = {
                displayName: request.displayName,
                description: request.description || '',
                membershipType: request.membershipType || 'standard'
            };
            const response = await this.makeDirectGraphPostRequest(`/teams/${teamId}/channels`, token, channelData);
            logger.log(`Created channel: ${request.displayName} in team ${teamId}`);
            return {
                id: response.id,
                displayName: response.displayName,
                description: response.description,
                webUrl: response.webUrl,
                membershipType: response.membershipType,
                createdDateTime: response.createdDateTime
            };
        }
        catch (error) {
            logger.error('Error creating channel:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to create channel: ${errorMessage}`);
        }
    }
    /**
     * Send a message to a channel
     */
    async sendChannelMessage(teamId, channelId, request) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // Format mentions for Microsoft Graph API
            const mentions = request.mentions ? await Promise.all(request.mentions.map(async (mention, index) => {
                // Try to find the user by display name if ID is not provided
                let userId = mention.id;
                if (!userId && mention.displayName) {
                    // Search for user by display name using direct HTTP
                    try {
                        const response = await this.makeDirectGraphRequest(`/users?$filter=displayName eq '${mention.displayName}'`, token);
                        if (response.value && response.value.length > 0) {
                            userId = response.value[0].id;
                        }
                    }
                    catch (error) {
                        logger.warn(`Failed to find user with display name '${mention.displayName}': ${error instanceof Error ? error.message : String(error)}`);
                    }
                }
                if (!userId) {
                    throw new Error(`User ID is required for mention. Could not find user with display name: ${mention.displayName}`);
                }
                return {
                    id: index,
                    mentionText: mention.displayName || userId,
                    mentioned: {
                        user: {
                            id: userId,
                            displayName: mention.displayName || userId
                        }
                    }
                };
            })) : [];
            const messageData = {
                body: {
                    content: request.content,
                    contentType: request.contentType || 'text'
                },
                attachments: request.attachments || [],
                mentions: mentions
            };
            // Use direct HTTP POST request
            const response = await this.makeDirectGraphPostRequest(`/teams/${teamId}/channels/${channelId}/messages`, token, messageData);
            logger.log(`Sent message to channel ${channelId} in team ${teamId}`);
            return {
                id: response.id,
                messageType: response.messageType,
                createdDateTime: response.createdDateTime,
                lastModifiedDateTime: response.lastModifiedDateTime,
                body: response.body,
                from: response.from,
                attachments: response.attachments,
                mentions: response.mentions,
                reactions: response.reactions
            };
        }
        catch (error) {
            logger.error('Error sending channel message:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to send message: ${errorMessage}`);
        }
    }
    /**
     * Get messages from a channel
     */
    async getChannelMessages(teamId, channelId, options = {}) {
        try {
            // Use direct HTTP request to avoid Graph SDK hanging issues
            const token = await this.getAccessToken();
            let apiPath = `/teams/${teamId}/channels/${channelId}/messages`;
            if (options.maxResults) {
                apiPath += `?$top=${options.maxResults}`;
            }
            logger.log(`Making direct HTTP request to: ${apiPath}`);
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} messages from channel ${channelId}`);
            return response.value.map((message) => ({
                id: message.id,
                messageType: message.messageType,
                createdDateTime: message.createdDateTime,
                lastModifiedDateTime: message.lastModifiedDateTime,
                body: message.body,
                from: message.from,
                attachments: message.attachments,
                mentions: message.mentions,
                reactions: message.reactions
            }));
        }
        catch (error) {
            logger.error('Error getting channel messages:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get messages: ${errorMessage}`);
        }
    }
    /**
     * Search messages in a channel
     */
    async searchChannelMessages(options) {
        try {
            let messages = await this.getChannelMessages(options.teamId, options.channelId, {
                maxResults: options.maxResults || 100
            });
            // Client-side filtering
            if (options.query) {
                const query = options.query.toLowerCase();
                messages = messages.filter(message => message.body.content.toLowerCase().includes(query));
            }
            if (options.fromDate) {
                const fromDate = new Date(options.fromDate);
                messages = messages.filter(message => new Date(message.createdDateTime) >= fromDate);
            }
            if (options.toDate) {
                const toDate = new Date(options.toDate);
                messages = messages.filter(message => new Date(message.createdDateTime) <= toDate);
            }
            logger.log(`Found ${messages.length} messages matching search criteria`);
            return messages;
        }
        catch (error) {
            logger.error('Error searching channel messages:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to search messages: ${errorMessage}`);
        }
    }
    /**
     * Get team members
     */
    async getTeamMembers(teamId) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const apiPath = `/teams/${teamId}/members`;
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} members for team ${teamId}`);
            return response.value.map((member) => ({
                id: member.id,
                displayName: member.displayName,
                email: member.email,
                roles: member.roles,
                visibleHistoryStartDateTime: member.visibleHistoryStartDateTime
            }));
        }
        catch (error) {
            logger.error('Error getting team members:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get team members: ${errorMessage}`);
        }
    }
    /**
     * Add a member to a team
     */
    async addTeamMember(teamId, userId, roles = ['member']) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const memberData = {
                '@odata.type': '#microsoft.graph.aadUserConversationMember',
                'user@odata.bind': `https://graph.microsoft.com/v1.0/users('${userId}')`,
                roles: roles
            };
            await this.makeDirectGraphPostRequest(`/teams/${teamId}/members`, token, memberData);
            logger.log(`Added member ${userId} to team ${teamId} with roles: ${roles.join(', ')}`);
        }
        catch (error) {
            logger.error('Error adding team member:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to add team member: ${errorMessage}`);
        }
    }
    /**
     * Remove a member from a team
     */
    async removeTeamMember(teamId, memberId) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            await this.makeDirectGraphDeleteRequest(`/teams/${teamId}/members/${memberId}`, token);
            logger.log(`Removed member ${memberId} from team ${teamId}`);
        }
        catch (error) {
            logger.error('Error removing team member:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to remove team member: ${errorMessage}`);
        }
    }
    /**
     * Get current user information
     */
    async getCurrentUser() {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const response = await this.makeDirectGraphRequest('/me', token);
            return {
                id: response.id,
                displayName: response.displayName,
                email: response.userPrincipalName || response.mail
            };
        }
        catch (error) {
            logger.error('Error getting current user:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get current user: ${errorMessage}`);
        }
    }
    /**
     * Search for users in the organization
     */
    async searchUsers(query, maxResults = 10) {
        try {
            logger.log(`Searching for users with query: ${query}`);
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const searchQuery = `$search="displayName:${query}" OR "mail:${query}"&$top=${maxResults}`;
            const apiPath = `/users?${searchQuery}`;
            logger.log(`Making direct HTTP request to: ${apiPath}`);
            const response = await this.makeDirectGraphRequest(apiPath, token, {
                'ConsistencyLevel': 'eventual'
            });
            logger.log(`Found ${response.value.length} users matching query: ${query}`);
            return response.value.map((user) => ({
                id: user.id,
                displayName: user.displayName,
                email: user.userPrincipalName || user.mail
            }));
        }
        catch (error) {
            logger.error('Error searching users:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to search users: ${errorMessage}`);
        }
    }
    /**
     * Get or create a chat with a specific user
     */
    async getOrCreateChatWithUser(userId) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // First try to find existing chat with this user
            logger.log(`Looking for existing chat with user ${userId}`);
            const chatsResponse = await this.makeDirectGraphRequest('/me/chats', token);
            for (const chat of chatsResponse.value) {
                if (chat.chatType === 'oneOnOne') {
                    // Get members of this chat
                    const membersResponse = await this.makeDirectGraphRequest(`/chats/${chat.id}/members`, token);
                    const members = membersResponse.value;
                    // Check if this chat includes the target user
                    if (members.some((member) => member.userId === userId)) {
                        logger.log(`Found existing chat ${chat.id} with user ${userId}`);
                        return {
                            id: chat.id,
                            topic: chat.topic,
                            createdDateTime: chat.createdDateTime,
                            lastUpdatedDateTime: chat.lastUpdatedDateTime,
                            chatType: chat.chatType,
                            members: members.map((member) => ({
                                id: member.userId,
                                displayName: member.displayName,
                                email: member.email,
                                roles: member.roles || []
                            }))
                        };
                    }
                }
            }
            // If no existing chat found, create a new one using POST request
            logger.log(`No existing chat found, creating new chat with user ${userId}`);
            const chatData = {
                chatType: 'oneOnOne',
                members: [
                    {
                        '@odata.type': '#microsoft.graph.aadUserConversationMember',
                        'user@odata.bind': `https://graph.microsoft.com/v1.0/users('${userId}')`,
                        roles: ['owner']
                    }
                ]
            };
            const newChatResponse = await this.makeDirectGraphPostRequest('/chats', token, chatData);
            logger.log(`Created new chat ${newChatResponse.id} with user ${userId}`);
            return {
                id: newChatResponse.id,
                topic: newChatResponse.topic,
                createdDateTime: newChatResponse.createdDateTime,
                lastUpdatedDateTime: newChatResponse.lastUpdatedDateTime,
                chatType: newChatResponse.chatType,
                members: [] // Will be populated when needed
            };
        }
        catch (error) {
            logger.error('Error getting or creating chat:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get or create chat: ${errorMessage}`);
        }
    }
    /**
     * Send a direct message to a user
     */
    async sendDirectMessage(userId, request) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // Get or create chat with the user
            const chat = await this.getOrCreateChatWithUser(userId);
            // Format mentions for Microsoft Graph API (same as channel messages)
            const mentions = request.mentions ? await Promise.all(request.mentions.map(async (mention, index) => {
                let mentionUserId = mention.id;
                if (!mentionUserId && mention.displayName) {
                    try {
                        // Use direct HTTP request for user search
                        const response = await this.makeDirectGraphRequest(`/users?$filter=displayName eq '${mention.displayName}'`, token);
                        if (response.value && response.value.length > 0) {
                            mentionUserId = response.value[0].id;
                        }
                    }
                    catch (error) {
                        logger.warn(`Failed to find user with display name '${mention.displayName}': ${error instanceof Error ? error.message : String(error)}`);
                    }
                }
                if (!mentionUserId) {
                    throw new Error(`User ID is required for mention. Could not find user with display name: ${mention.displayName}`);
                }
                return {
                    id: index,
                    mentionText: mention.displayName || mentionUserId,
                    mentioned: {
                        user: {
                            id: mentionUserId,
                            displayName: mention.displayName || mentionUserId
                        }
                    }
                };
            })) : [];
            const messageData = {
                body: {
                    content: request.content,
                    contentType: request.contentType || 'text'
                },
                mentions: mentions
            };
            // Use direct HTTP POST request
            const response = await this.makeDirectGraphPostRequest(`/chats/${chat.id}/messages`, token, messageData);
            logger.log(`Sent direct message to user ${userId} in chat ${chat.id}`);
            return {
                id: response.id,
                messageType: response.messageType,
                createdDateTime: response.createdDateTime,
                lastModifiedDateTime: response.lastModifiedDateTime,
                body: response.body,
                from: response.from,
                attachments: response.attachments,
                mentions: response.mentions,
                reactions: response.reactions
            };
        }
        catch (error) {
            logger.error('Error sending direct message:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to send direct message: ${errorMessage}`);
        }
    }
    /**
     * Get direct messages from a chat
     */
    async getDirectMessages(userId, options = {}) {
        try {
            // Get chat with the user
            const chat = await this.getOrCreateChatWithUser(userId);
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const query = options.maxResults ? `?$top=${options.maxResults}` : '';
            const apiPath = `/chats/${chat.id}/messages${query}`;
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} messages from chat ${chat.id}`);
            return response.value.map((message) => ({
                id: message.id,
                messageType: message.messageType,
                createdDateTime: message.createdDateTime,
                lastModifiedDateTime: message.lastModifiedDateTime,
                body: message.body,
                from: message.from,
                attachments: message.attachments,
                mentions: message.mentions,
                reactions: message.reactions
            }));
        }
        catch (error) {
            logger.error('Error getting direct messages:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get direct messages: ${errorMessage}`);
        }
    }
    /**
     * Add a reaction to a channel message
     */
    async addMessageReaction(teamId, channelId, messageId, reactionType) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // Microsoft Graph uses specific reaction types
            const validReactions = ['like', 'heart', 'laugh', 'surprised', 'sad', 'angry'];
            if (!validReactions.includes(reactionType.toLowerCase())) {
                throw new Error(`Invalid reaction type. Must be one of: ${validReactions.join(', ')}`);
            }
            const reactionData = {
                reactionType: reactionType.toLowerCase()
            };
            await this.makeDirectGraphPostRequest(`/teams/${teamId}/channels/${channelId}/messages/${messageId}/reactions`, token, reactionData);
            logger.log(`Added ${reactionType} reaction to message ${messageId}`);
        }
        catch (error) {
            logger.error('Error adding message reaction:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to add reaction: ${errorMessage}`);
        }
    }
    /**
     * Remove a reaction from a channel message
     */
    async removeMessageReaction(teamId, channelId, messageId, reactionType) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // Get current user to identify which reaction to remove
            const currentUser = await this.getCurrentUser();
            await this.makeDirectGraphDeleteRequest(`/teams/${teamId}/channels/${channelId}/messages/${messageId}/reactions/${reactionType.toLowerCase()}/${currentUser.id}`, token);
            logger.log(`Removed ${reactionType} reaction from message ${messageId}`);
        }
        catch (error) {
            logger.error('Error removing message reaction:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to remove reaction: ${errorMessage}`);
        }
    }
    /**
     * Add a reaction to a chat message (direct message)
     */
    async addChatMessageReaction(chatId, messageId, reactionType) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const validReactions = ['like', 'heart', 'laugh', 'surprised', 'sad', 'angry'];
            if (!validReactions.includes(reactionType.toLowerCase())) {
                throw new Error(`Invalid reaction type. Must be one of: ${validReactions.join(', ')}`);
            }
            const reactionData = {
                reactionType: reactionType.toLowerCase()
            };
            await this.makeDirectGraphPostRequest(`/chats/${chatId}/messages/${messageId}/reactions`, token, reactionData);
            logger.log(`Added ${reactionType} reaction to chat message ${messageId}`);
        }
        catch (error) {
            logger.error('Error adding chat message reaction:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to add chat reaction: ${errorMessage}`);
        }
    }
    /**
     * Remove a reaction from a chat message
     */
    async removeChatMessageReaction(chatId, messageId, reactionType) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const currentUser = await this.getCurrentUser();
            await this.makeDirectGraphDeleteRequest(`/chats/${chatId}/messages/${messageId}/reactions/${reactionType.toLowerCase()}/${currentUser.id}`, token);
            logger.log(`Removed ${reactionType} reaction from chat message ${messageId}`);
        }
        catch (error) {
            logger.error('Error removing chat message reaction:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to remove chat reaction: ${errorMessage}`);
        }
    }
    /**
     * Upload a file to a team's SharePoint drive
     */
    async uploadFileToTeam(teamId, request) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // Get the team's drive
            const driveResponse = await this.makeDirectGraphRequest(`/groups/${teamId}/drive`, token);
            const driveId = driveResponse.id;
            // Upload the file to the drive's root folder
            let fileContent;
            if (Buffer.isBuffer(request.fileContent)) {
                fileContent = request.fileContent;
            }
            else {
                fileContent = Buffer.from(request.fileContent);
            }
            const uploadUrl = `/drives/${driveId}/root:/${encodeURIComponent(request.fileName)}:/content`;
            const response = await this.makeDirectGraphPutRequest(uploadUrl, token, fileContent, {
                'Content-Type': 'application/octet-stream'
            });
            logger.log(`Uploaded file ${request.fileName} to team ${teamId}`);
            return {
                id: response.id,
                name: response.name,
                size: response.size,
                createdDateTime: response.createdDateTime,
                lastModifiedDateTime: response.lastModifiedDateTime,
                downloadUrl: response['@microsoft.graph.downloadUrl'],
                webUrl: response.webUrl,
                folder: response.folder,
                file: response.file,
                createdBy: response.createdBy
            };
        }
        catch (error) {
            logger.error('Error uploading file to team:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to upload file: ${errorMessage}`);
        }
    }
    /**
     * Upload a file to a channel's files tab
     */
    async uploadFileToChannel(teamId, channelId, request) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // Get the channel's drive folder
            const channelResponse = await this.makeDirectGraphRequest(`/teams/${teamId}/channels/${channelId}/filesFolder`, token);
            const driveId = channelResponse.parentReference.driveId;
            const folderId = channelResponse.id;
            // Upload the file to the channel folder
            let fileContent;
            if (Buffer.isBuffer(request.fileContent)) {
                fileContent = request.fileContent;
            }
            else {
                fileContent = Buffer.from(request.fileContent);
            }
            const uploadUrl = `/drives/${driveId}/items/${folderId}:/${encodeURIComponent(request.fileName)}:/content`;
            const response = await this.makeDirectGraphPutRequest(uploadUrl, token, fileContent, {
                'Content-Type': 'application/octet-stream'
            });
            logger.log(`Uploaded file ${request.fileName} to channel ${channelId} in team ${teamId}`);
            return {
                id: response.id,
                name: response.name,
                size: response.size,
                createdDateTime: response.createdDateTime,
                lastModifiedDateTime: response.lastModifiedDateTime,
                downloadUrl: response['@microsoft.graph.downloadUrl'],
                webUrl: response.webUrl,
                folder: response.folder,
                file: response.file,
                createdBy: response.createdBy
            };
        }
        catch (error) {
            logger.error('Error uploading file to channel:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to upload file to channel: ${errorMessage}`);
        }
    }
    /**
     * List files in a team's drive
     */
    async getTeamFiles(teamId, maxResults = 20) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const queryParams = maxResults ? `?$top=${maxResults}` : '';
            const apiPath = `/groups/${teamId}/drive/root/children${queryParams}`;
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} files from team ${teamId}`);
            return response.value.map((item) => ({
                id: item.id,
                name: item.name,
                size: item.size || 0,
                createdDateTime: item.createdDateTime,
                lastModifiedDateTime: item.lastModifiedDateTime,
                downloadUrl: item['@microsoft.graph.downloadUrl'],
                webUrl: item.webUrl,
                folder: item.folder,
                file: item.file,
                createdBy: item.createdBy
            }));
        }
        catch (error) {
            logger.error('Error getting team files:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get team files: ${errorMessage}`);
        }
    }
    /**
     * List files in a channel's files tab
     */
    async getChannelFiles(teamId, channelId, maxResults = 20) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // Get the channel's drive folder
            const channelResponse = await this.makeDirectGraphRequest(`/teams/${teamId}/channels/${channelId}/filesFolder`, token);
            const driveId = channelResponse.parentReference.driveId;
            const folderId = channelResponse.id;
            const queryParams = maxResults ? `?$top=${maxResults}` : '';
            const response = await this.makeDirectGraphRequest(`/drives/${driveId}/items/${folderId}/children${queryParams}`, token);
            logger.log(`Retrieved ${response.value.length} files from channel ${channelId}`);
            return response.value.map((item) => ({
                id: item.id,
                name: item.name,
                size: item.size || 0,
                createdDateTime: item.createdDateTime,
                lastModifiedDateTime: item.lastModifiedDateTime,
                downloadUrl: item['@microsoft.graph.downloadUrl'],
                webUrl: item.webUrl,
                folder: item.folder,
                file: item.file,
                createdBy: item.createdBy
            }));
        }
        catch (error) {
            logger.error('Error getting channel files:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get channel files: ${errorMessage}`);
        }
    }
    /**
     * Download a file from a team's drive
     */
    async downloadFile(driveId, itemId) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            // Get file metadata
            const fileResponse = await this.makeDirectGraphRequest(`/drives/${driveId}/items/${itemId}`, token);
            // Download file content
            const url = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/content`;
            const response = await fetch(url, {
                method: 'GET',
                headers: {
                    'Authorization': `Bearer ${token}`
                }
            });
            if (!response.ok) {
                const errorText = await response.text();
                throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
            }
            const content = Buffer.from(await response.arrayBuffer());
            logger.log(`Downloaded file ${fileResponse.name} (${content.length} bytes)`);
            return {
                fileName: fileResponse.name,
                content: content,
                mimeType: fileResponse.file?.mimeType || 'application/octet-stream'
            };
        }
        catch (error) {
            logger.error('Error downloading file:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to download file: ${errorMessage}`);
        }
    }
    /**
     * Reply to a message in a channel
     */
    async replyToChannelMessage(teamId, channelId, messageId, content, contentType = 'text') {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const replyData = {
                body: {
                    content: content,
                    contentType: contentType
                }
            };
            // Use direct HTTP POST request
            const response = await this.makeDirectGraphPostRequest(`/teams/${teamId}/channels/${channelId}/messages/${messageId}/replies`, token, replyData);
            logger.log(`Replied to message ${messageId} in channel ${channelId}`);
            return {
                id: response.id,
                messageType: response.messageType,
                createdDateTime: response.createdDateTime,
                lastModifiedDateTime: response.lastModifiedDateTime,
                body: response.body,
                from: response.from,
                replyToId: messageId
            };
        }
        catch (error) {
            logger.error('Error replying to channel message:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to reply to message: ${errorMessage}`);
        }
    }
    /**
     * Get replies to a message in a channel
     */
    async getChannelMessageReplies(teamId, channelId, messageId, maxResults = 20) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const queryParams = maxResults ? `?$top=${maxResults}` : '';
            const apiPath = `/teams/${teamId}/channels/${channelId}/messages/${messageId}/replies${queryParams}`;
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} replies to message ${messageId}`);
            return response.value.map((reply) => ({
                id: reply.id,
                messageType: reply.messageType,
                createdDateTime: reply.createdDateTime,
                lastModifiedDateTime: reply.lastModifiedDateTime,
                body: reply.body,
                from: reply.from,
                replyToId: messageId
            }));
        }
        catch (error) {
            logger.error('Error getting message replies:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get message replies: ${errorMessage}`);
        }
    }
    /**
     * Reply to a message in a chat (direct message)
     */
    async replyToChatMessage(chatId, messageId, content, contentType = 'text') {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const replyData = {
                body: {
                    content: content,
                    contentType: contentType
                }
            };
            // Use direct HTTP POST request
            const response = await this.makeDirectGraphPostRequest(`/chats/${chatId}/messages/${messageId}/replies`, token, replyData);
            logger.log(`Replied to chat message ${messageId} in chat ${chatId}`);
            return {
                id: response.id,
                messageType: response.messageType,
                createdDateTime: response.createdDateTime,
                lastModifiedDateTime: response.lastModifiedDateTime,
                body: response.body,
                from: response.from,
                replyToId: messageId
            };
        }
        catch (error) {
            logger.error('Error replying to chat message:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to reply to chat message: ${errorMessage}`);
        }
    }
    /**
     * Get replies to a message in a chat
     */
    async getChatMessageReplies(chatId, messageId, maxResults = 20) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const queryParams = maxResults ? `?$top=${maxResults}` : '';
            const apiPath = `/chats/${chatId}/messages/${messageId}/replies${queryParams}`;
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} replies to chat message ${messageId}`);
            return response.value.map((reply) => ({
                id: reply.id,
                messageType: reply.messageType,
                createdDateTime: reply.createdDateTime,
                lastModifiedDateTime: reply.lastModifiedDateTime,
                body: reply.body,
                from: reply.from,
                replyToId: messageId
            }));
        }
        catch (error) {
            logger.error('Error getting chat message replies:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get chat message replies: ${errorMessage}`);
        }
    }
    /**
     * Create a group chat
     */
    async createGroupChat(request) {
        try {
            // Format members for Microsoft Graph API
            const members = await Promise.all(request.members.map(async (userId) => ({
                '@odata.type': '#microsoft.graph.aadUserConversationMember',
                'user@odata.bind': `https://graph.microsoft.com/v1.0/users('${userId}')`,
                roles: ['owner']
            })));
            const chatData = {
                chatType: 'group',
                topic: request.topic,
                members: members
            };
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const response = await this.makeDirectGraphPostRequest('/chats', token, chatData);
            logger.log(`Created group chat with ${request.members.length} members`);
            return {
                id: response.id,
                topic: response.topic,
                createdDateTime: response.createdDateTime,
                lastUpdatedDateTime: response.lastUpdatedDateTime,
                chatType: response.chatType,
                members: [], // Will be populated when needed
                webUrl: response.webUrl
            };
        }
        catch (error) {
            logger.error('Error creating group chat:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to create group chat: ${errorMessage}`);
        }
    }
    /**
     * Get all group chats for the current user
     */
    async getGroupChats(maxResults = 20) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const queryParams = `?$filter=chatType eq 'group'&$top=${maxResults}`;
            const apiPath = `/me/chats${queryParams}`;
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} group chats`);
            return response.value.map((chat) => ({
                id: chat.id,
                topic: chat.topic,
                createdDateTime: chat.createdDateTime,
                lastUpdatedDateTime: chat.lastUpdatedDateTime,
                chatType: chat.chatType,
                members: [], // Members can be fetched separately if needed
                webUrl: chat.webUrl
            }));
        }
        catch (error) {
            logger.error('Error getting group chats:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get group chats: ${errorMessage}`);
        }
    }
    /**
     * Get members of a group chat
     */
    async getGroupChatMembers(chatId) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const apiPath = `/chats/${chatId}/members`;
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} members from group chat ${chatId}`);
            return response.value.map((member) => ({
                id: member.userId,
                displayName: member.displayName,
                email: member.email,
                roles: member.roles || []
            }));
        }
        catch (error) {
            logger.error('Error getting group chat members:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get group chat members: ${errorMessage}`);
        }
    }
    /**
     * Add members to a group chat
     */
    async addGroupChatMembers(chatId, userIds) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            for (const userId of userIds) {
                const memberData = {
                    '@odata.type': '#microsoft.graph.aadUserConversationMember',
                    'user@odata.bind': `https://graph.microsoft.com/v1.0/users('${userId}')`,
                    roles: ['owner']
                };
                await this.makeDirectGraphPostRequest(`/chats/${chatId}/members`, token, memberData);
                logger.log(`Added user ${userId} to group chat ${chatId}`);
            }
        }
        catch (error) {
            logger.error('Error adding group chat members:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to add group chat members: ${errorMessage}`);
        }
    }
    /**
     * Remove a member from a group chat
     */
    async removeGroupChatMember(chatId, membershipId) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            await this.makeDirectGraphDeleteRequest(`/chats/${chatId}/members/${membershipId}`, token);
            logger.log(`Removed member ${membershipId} from group chat ${chatId}`);
        }
        catch (error) {
            logger.error('Error removing group chat member:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to remove group chat member: ${errorMessage}`);
        }
    }
    /**
     * Create an online meeting
     */
    async createOnlineMeeting(request) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const meetingData = {
                subject: request.subject,
                startDateTime: request.startDateTime,
                endDateTime: request.endDateTime,
                participants: request.attendees ? {
                    attendees: request.attendees.map(email => ({
                        identity: {
                            user: {
                                email: email
                            }
                        }
                    }))
                } : undefined,
                allowMeetingChat: true,
                allowTeamworkReactions: true,
                isRecordingEnabled: request.isRecordingEnabled || false
            };
            const response = await this.makeDirectGraphPostRequest('/me/onlineMeetings', token, meetingData);
            logger.log(`Created online meeting: ${request.subject}`);
            return {
                id: response.id,
                subject: response.subject,
                startDateTime: response.startDateTime,
                endDateTime: response.endDateTime,
                joinWebUrl: response.joinWebUrl,
                organizer: response.organizer,
                participants: response.participants,
                isRecordingEnabled: response.isRecordingEnabled,
                allowMeetingChat: response.allowMeetingChat,
                allowTeamworkReactions: response.allowTeamworkReactions
            };
        }
        catch (error) {
            logger.error('Error creating online meeting:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to create online meeting: ${errorMessage}`);
        }
    }
    /**
     * Get user's online meetings
     */
    async getOnlineMeetings(maxResults = 20) {
        try {
            // Use direct HTTP request to avoid Graph SDK issues
            const token = await this.getAccessToken();
            const apiPath = '/me/onlineMeetings';
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} online meetings`);
            // Apply client-side limiting if maxResults is specified
            let meetings = response.value;
            if (maxResults && meetings.length > maxResults) {
                meetings = meetings.slice(0, maxResults);
                logger.log(`Limited results to ${maxResults} meetings`);
            }
            return meetings.map((meeting) => ({
                id: meeting.id,
                subject: meeting.subject,
                startDateTime: meeting.startDateTime,
                endDateTime: meeting.endDateTime,
                joinWebUrl: meeting.joinWebUrl,
                organizer: meeting.organizer,
                participants: meeting.participants,
                isRecordingEnabled: meeting.isRecordingEnabled,
                allowMeetingChat: meeting.allowMeetingChat,
                allowTeamworkReactions: meeting.allowTeamworkReactions
            }));
        }
        catch (error) {
            logger.error('Error getting online meetings:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get online meetings: ${errorMessage}`);
        }
    }
    /**
     * Get calendar events
     */
    async getCalendarEvents(startTime, endTime, maxResults = 20) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            let queryParams = `?$top=${maxResults}&$orderby=start/dateTime`;
            if (startTime && endTime) {
                queryParams += `&$filter=start/dateTime ge '${startTime}' and end/dateTime le '${endTime}'`;
            }
            const apiPath = `/me/calendar/events${queryParams}`;
            const response = await this.makeDirectGraphRequest(apiPath, token);
            logger.log(`Retrieved ${response.value.length} calendar events`);
            return response.value.map((event) => ({
                id: event.id,
                subject: event.subject,
                start: event.start,
                end: event.end,
                location: event.location,
                organizer: event.organizer,
                attendees: event.attendees,
                isOnlineMeeting: event.isOnlineMeeting,
                onlineMeeting: event.onlineMeeting
            }));
        }
        catch (error) {
            logger.error('Error getting calendar events:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get calendar events: ${errorMessage}`);
        }
    }
    /**
     * Get user presence
     */
    async getUserPresence(userId) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const endpoint = userId ? `/users/${userId}/presence` : '/me/presence';
            const response = await this.makeDirectGraphRequest(endpoint, token);
            logger.log(`Retrieved presence for user ${userId || 'current user'}`);
            return {
                id: response.id,
                availability: response.availability,
                activity: response.activity,
                statusMessage: response.statusMessage
            };
        }
        catch (error) {
            logger.error('Error getting user presence:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get user presence: ${errorMessage}`);
        }
    }
    /**
     * Set user presence
     */
    async setUserPresence(request) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const presenceData = {
                availability: request.availability,
                activity: request.activity,
                expirationDuration: request.expirationDuration || 'PT1H' // Default to 1 hour
            };
            await this.makeDirectGraphPostRequest('/me/presence/setPresence', token, presenceData);
            logger.log(`Set presence to ${request.availability}/${request.activity}`);
        }
        catch (error) {
            logger.error('Error setting user presence:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to set user presence: ${errorMessage}`);
        }
    }
    /**
     * Get presence for multiple users
     */
    async getBulkPresence(userIds) {
        try {
            // Use direct HTTP request to avoid Graph SDK timeout issues
            const token = await this.getAccessToken();
            const requestBody = {
                ids: userIds
            };
            const response = await this.makeDirectGraphPostRequest('/communications/getPresencesByUserId', token, requestBody);
            logger.log(`Retrieved presence for ${response.value.length} users`);
            return response.value.map((presence) => ({
                id: presence.id,
                availability: presence.availability,
                activity: presence.activity,
                statusMessage: presence.statusMessage
            }));
        }
        catch (error) {
            logger.error('Error getting bulk user presence:', error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to get bulk user presence: ${errorMessage}`);
        }
    }
    /**
     * Get access token directly
     */
    async getAccessToken() {
        const token = await teamsAuth.authenticate();
        return token.accessToken;
    }
    /**
     * Make direct HTTP request to Graph API
     */
    async makeDirectGraphRequest(path, token, additionalHeaders) {
        const url = `https://graph.microsoft.com/v1.0${path}`;
        logger.log(`Making direct HTTP request to: ${url}`);
        const headers = {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
            ...additionalHeaders
        };
        const response = await fetch(url, {
            method: 'GET',
            headers
        });
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        return await response.json();
    }
    /**
     * Make direct HTTP POST request to Graph API
     */
    async makeDirectGraphPostRequest(path, token, body, additionalHeaders) {
        const url = `https://graph.microsoft.com/v1.0${path}`;
        logger.log(`Making direct HTTP POST request to: ${url}`);
        const headers = {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
            ...additionalHeaders
        };
        const response = await fetch(url, {
            method: 'POST',
            headers,
            body: JSON.stringify(body)
        });
        if (!response.ok) {
            const errorText = await response.text();
            throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
        }
        return await response.json();
    }
    /**
     * Make direct HTTP DELETE request to Graph API
     */
    async makeDirectGraphDeleteRequest(path, token, additionalHeaders) {
        const url = `https://graph.microsoft.com/v1.0${path}`;
        logger.log(`Making direct HTTP DELETE request to: ${url}`);
        const headers = {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
            ...additionalHeaders
        };
        const response = await fetch(url, {
            method: 'DELETE',
            headers
        });
        if (!response.ok) {
            const errorText = await response.text();
            throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
        }
    }
    /**
     * Make direct HTTP PUT request to Graph API
     */
    async makeDirectGraphPutRequest(path, token, body, additionalHeaders) {
        const url = `https://graph.microsoft.com/v1.0${path}`;
        logger.log(`Making direct HTTP PUT request to: ${url}`);
        const headers = {
            'Authorization': `Bearer ${token}`,
            ...additionalHeaders
        };
        const response = await fetch(url, {
            method: 'PUT',
            headers,
            body: body
        });
        if (!response.ok) {
            const errorText = await response.text();
            throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
        }
        return await response.json();
    }
    /**
     * Reset the Graph client (useful for re-authentication)
     */
    resetClient() {
        this.graphClient = null;
        logger.log('Teams Graph client reset');
    }
}
// Export singleton instance
export const teamsOperations = new TeamsOperations();