""" Email Agent Module. Handles email-related tasks using Microsoft Graph API. """ import logging import json from typing import Dict, Any, List, Optional, Tuple import uuid import os from modules.workflow.agentBase import AgentBase from modules.interfaces.serviceChatModel import Task, ChatDocument, ChatContent logger = logging.getLogger(__name__) class AgentEmail(AgentBase): """Agent for handling email-related tasks.""" def __init__(self): """Initialize the email agent.""" super().__init__() self.name = "email" self.label = "Email Agent" self.description = "Handles email composition and sending using Microsoft Graph API" self.capabilities = [ "email_composition", "email_draft_creation", "email_template_generation" ] self.serviceBase = None def setDependencies(self, serviceBase=None): """Set external dependencies for the agent.""" self.serviceBase = serviceBase async def processTask(self, task: Task) -> Dict[str, Any]: """ Process an email-related task. Args: task: Task object containing: - prompt: Instructions for the agent - inputDocuments: List of documents to process - outputSpecifications: List of required output documents - context: Additional context including workflow info Returns: Dictionary containing: - feedback: Text response explaining what was done - documents: List of created documents """ try: # Extract task information prompt = task.prompt inputDocuments = task.filesInput outputSpecs = task.filesOutput # Check AI service if not self.service.base: return { "feedback": "The Email agent requires an AI service to function.", "documents": [] } # Check if Microsoft connector is available if not hasattr(self.service, 'msft'): return { "feedback": "Microsoft connector not available. Please ensure Microsoft integration is properly configured.", "documents": [] } # Get Microsoft token token_data = self.service.msft.getMsftToken() if not token_data: # Create authentication trigger document auth_doc = self._createFrontendAuthTriggerDocument() return { "feedback": "Microsoft authentication required. Please authenticate to continue.", "documents": [auth_doc] } # Extract document data from input documentContents, attachments = self._processInputDocuments(inputDocuments) # Generate email subject and body using AI emailTemplate = await self._generateEmailTemplate(prompt, documentContents) # Create HTML preview of the email htmlPreview = self._createHtmlPreview(emailTemplate) # Attempt to create a draft email using Microsoft Graph API draft_result = self.service.msft.createDraftEmail( emailTemplate["recipient"], emailTemplate["subject"], emailTemplate["htmlBody"], attachments ) # Prepare output documents documents = [] # Process output specifications for spec in outputSpecs: label = spec.get("label", "") description = spec.get("description", "") if label.endswith(".html"): # Create the HTML template file templateDoc = self.formatAgentDocumentOutput( label, emailTemplate["htmlBody"], # Use the actual HTML body, not the preview "text/html" ) documents.append(templateDoc) elif label.endswith(".json"): # Create JSON template if requested templateJson = json.dumps(emailTemplate, indent=2) templateDoc = self.formatAgentDocumentOutput( label, templateJson, "application/json" ) documents.append(templateDoc) else: # Default to preview for other cases previewDoc = self.formatAgentDocumentOutput( label, htmlPreview, "text/html" ) documents.append(previewDoc) # Prepare feedback message if draft_result: feedback = f"Email draft created successfully for {emailTemplate.get('recipient')}. The subject is: '{emailTemplate['subject']}'" if attachments: feedback += f" with {len(attachments)} attachment(s)" feedback += ". You can open and edit it in your Outlook draft folder." else: feedback = "Email template created but could not save as draft. HTML preview and template are available as documents." return { "feedback": feedback, "documents": documents } except Exception as e: logger.error(f"Error in email agent: {str(e)}") return { "feedback": f"Error processing email task: {str(e)}", "documents": [] } def _createFrontendAuthTriggerDocument(self) -> ChatDocument: """Create a document that triggers Microsoft authentication in the frontend.""" return ChatDocument( id=str(uuid.uuid4()), name="microsoft_auth", ext="html", data="""

Microsoft Authentication Required

Please click the button below to authenticate with Microsoft:

""", contents=[ ChatContent( name="main", data="""

Microsoft Authentication Required

Please click the button below to authenticate with Microsoft:

""", summary="Microsoft authentication trigger page", metadata={ "contentType": "text/html", "isText": True } ) ] ) def _processInputDocuments(self, input_docs: List[ChatDocument]) -> Tuple[str, List[Dict[str, Any]]]: """ Process input documents to extract content and prepare attachments. Args: input_docs: List of input documents Returns: Tuple of (document content text, list of attachments) """ documentContents = [] attachments = [] for doc in input_docs: docName = doc.name if doc.ext: docName = f"{docName}.{doc.ext}" # Add document name to contents documentContents.append(f"\n\n--- {docName} ---\n") # Process document data directly if doc.data: # Add to attachments with proper metadata attachments.append({ "name": docName, "document": { "data": doc.data, "mimeType": doc.contents[0].metadata.get("contentType", "application/octet-stream") if doc.contents else "application/octet-stream", "base64Encoded": doc.contents[0].metadata.get("base64Encoded", False) if doc.contents else False } }) documentContents.append(f"Document attached: {docName}") else: documentContents.append(f"Document referenced: {docName}") return "\n".join(documentContents), attachments def formatAgentDocumentOutput(self, filename: str, content: str, contentType: str) -> ChatDocument: """ Format a document for agent output. Args: filename: Output filename content: Document content contentType: MIME type of the content Returns: ChatDocument object """ # Split filename into name and extension name, ext = os.path.splitext(filename) if ext.startswith('.'): ext = ext[1:] # Create document object return ChatDocument( id=str(uuid.uuid4()), name=name, ext=ext, data=content, contents=[ ChatContent( name="main", data=content, summary=f"Generated {filename}", metadata={"contentType": contentType} ) ] ) async def _generateEmailTemplate(self, prompt: str, documentContents: str) -> Dict[str, Any]: """ Generate email template using AI. Args: prompt: The task prompt documentContents: Extracted document content Returns: Email template dictionary with recipient, subject, body """ emailPrompt = f""" Create an email based on the following request: REQUEST: {prompt} DOCUMENT CONTENTS: {documentContents[:2000]}... (truncated if longer) Generate an email template with: 1. A relevant recipient (use placeholder or derive from content if possible) 2. A concise but descriptive subject line 3. A professional HTML-formatted email body 4. Appropriate greeting and closing Format your response as JSON with these fields: - recipient: email address - subject: subject line - plainBody: plain text version - htmlBody: HTML formatted version Only return valid JSON. No preamble or explanations. """ try: response = await self.service.base.callAi([ {"role": "system", "content": "You are an email template specialist. Create professional emails. Respond with valid JSON only."}, {"role": "user", "content": emailPrompt} ]) # Extract JSON from response jsonStart = response.find('{') jsonEnd = response.rfind('}') + 1 if jsonStart >= 0 and jsonEnd > jsonStart: template = json.loads(response[jsonStart:jsonEnd]) return template else: # Fallback plan logger.warning(f"Not able creating email template, generating fallback plan") return { "recipient": "recipient@example.com", "subject": "Information Regarding Your Request", "plainBody": f"This email is regarding your request: {prompt}", "htmlBody": f"

This email is regarding your request: {prompt}

" } except Exception as e: logger.warning(f"Error generating email template: {str(e)}") return { "recipient": "recipient@example.com", "subject": "Information Regarding Your Request", "plainBody": f"This email is regarding your request: {prompt}", "htmlBody": f"

This email is regarding your request: {prompt}

" } def _createHtmlPreview(self, emailTemplate: Dict[str, Any]) -> str: """ Create an HTML preview of the email template. Args: emailTemplate: Email template dictionary Returns: HTML string for preview """ html = f""" Email Preview: {emailTemplate.get('subject', 'Email Template')}

Email Template Preview

To:
{emailTemplate.get('recipient', 'recipient@example.com')}
Subject:
{emailTemplate.get('subject', 'No Subject')}
""" return html def getAgentEmail() -> AgentEmail: """Factory function to create and return an EmailAgent instance.""" return AgentEmail()