import { DataSourceAdapter, type IngestedEntity } from '../ingestion/DataIngestionEngine.js'; import { readFile } from 'fs/promises'; import { existsSync } from 'fs'; interface OutlookEmail { id: string; subject: string; sender: { name: string; address: string; }; receivedDateTime: string; bodyPreview: string; body: { contentType: string; content: string; }; importance: 'low' | 'normal' | 'high'; isRead: boolean; } export class OutlookJsonAdapter implements DataSourceAdapter { name = 'Outlook'; type: 'outlook_mail' = 'outlook_mail'; private filePath: string; private emails: OutlookEmail[] = []; private lastLoaded: number = 0; constructor(filePath: string) { this.filePath = filePath; } /** * Loads emails from the local JSON file */ private async loadEmails(): Promise { if (!existsSync(this.filePath)) { console.warn(`[OutlookJsonAdapter] File not found: ${this.filePath}`); this.emails = []; return; } try { const data = await readFile(this.filePath, 'utf-8'); // Handle potential wrapping structures (e.g. { value: [...] } or just [...]) const json = JSON.parse(data); this.emails = Array.isArray(json) ? json : (json.value || []); this.lastLoaded = Date.now(); console.log(`[OutlookJsonAdapter] Loaded ${this.emails.length} emails from ${this.filePath}`); } catch (error) { console.error(`[OutlookJsonAdapter] Failed to parse JSON:`, error); this.emails = []; } } /** * Fetch emails, reloading if cache is stale (> 5 minutes) */ async fetch(): Promise { const STALE_THRESHOLD = 5 * 60 * 1000; // 5 minutes if (this.emails.length === 0 || Date.now() - this.lastLoaded > STALE_THRESHOLD) { await this.loadEmails(); } return this.emails; } /** * Transform Outlook emails to Unified Memory entities */ async transform(raw: any[]): Promise { return raw.map((email: OutlookEmail) => ({ id: email.id, type: 'email', source: 'Outlook', title: email.subject, content: `${email.subject}\n\nFrom: ${email.sender.name} <${email.sender.address}>\nDate: ${email.receivedDateTime}\n\n${email.bodyPreview}`, metadata: { subject: email.subject, sender: email.sender.address, senderName: email.sender.name, receivedAt: email.receivedDateTime, importance: email.importance, isRead: email.isRead, source: 'outlook_json', }, timestamp: new Date(email.receivedDateTime) })); } /** * Check if the adapter is available (file exists) */ async isAvailable(): Promise { return existsSync(this.filePath); } /** * Support direct queries */ async query(operation: string, params: any): Promise { await this.fetch(); // Ensure data is loaded switch (operation) { case 'search': const term = params.query?.toLowerCase(); if (!term) return this.emails.slice(0, params.limit || 10); return this.emails.filter(e => e.subject.toLowerCase().includes(term) || e.sender.name.toLowerCase().includes(term) || e.bodyPreview.toLowerCase().includes(term) ).slice(0, params.limit || 10); case 'get_latest': return this.emails .sort((a, b) => new Date(b.receivedDateTime).getTime() - new Date(a.receivedDateTime).getTime()) .slice(0, params.limit || 10); case 'get_by_sender': const sender = params.sender?.toLowerCase(); if (!sender) return []; return this.emails.filter(e => e.sender.address.toLowerCase().includes(sender) || e.sender.name.toLowerCase().includes(sender) ).slice(0, params.limit || 10); default: throw new Error(`Unsupported operation: ${operation}`); } } }