Skip to main content
Glama
agent.tsβ€’18.2 kB
import { MCPManager } from '@core/mcp/manager.js'; import { AgentServices } from '../../utils/service-initializer.js'; import { createAgentServices } from '../../utils/service-initializer.js'; import { createEnhancedAgentServices, shouldEnableLazyLoading, LazyAgentServices, } from '../memory/enhanced-service-initializer.js'; import { EnhancedPromptManager } from '../systemPrompt/enhanced-manager.js'; import { MemAgentStateManager } from './state-manager.js'; import { SessionManager } from '../../session/session-manager.js'; import { ConversationSession } from '../../session/coversation-session.js'; import { AgentConfig } from './config.js'; import { logger } from '../../logger/index.js'; import { LLMConfig } from '../llm/config.js'; import { IMCPClient, McpServerConfig } from '../../mcp/types.js'; const requiredServices: (keyof AgentServices)[] = [ 'mcpManager', 'promptManager', 'stateManager', 'sessionManager', 'internalToolManager', 'unifiedToolManager', ]; export class MemAgent { public readonly mcpManager!: MCPManager; public readonly promptManager!: EnhancedPromptManager; public readonly stateManager!: MemAgentStateManager; public readonly sessionManager!: SessionManager; public readonly internalToolManager!: any; // Will be properly typed later public readonly unifiedToolManager!: any; // Will be properly typed later public readonly services!: AgentServices; private defaultSession: ConversationSession | null = null; private currentDefaultSessionId: string = 'default'; private currentActiveSessionId: string = 'default'; // Will be set properly in constructor private isStarted: boolean = false; private isStopped: boolean = false; private config: AgentConfig; private appMode: 'cli' | 'mcp' | 'api' | null = null; constructor(config: AgentConfig, appMode?: 'cli' | 'mcp' | 'api') { this.config = config; this.appMode = appMode || null; // Set session ID based on mode if (appMode === 'cli') { // For CLI, use default session to enable persistence this.currentActiveSessionId = this.currentDefaultSessionId; } else { // For API/MCP, generate unique session IDs this.currentActiveSessionId = this.generateUniqueSessionId(); } if (appMode !== 'cli') { logger.debug('MemAgent created'); } } /** * Generate a unique session ID for API/MCP modes * CLI mode uses the default session for persistence */ private generateUniqueSessionId(): string { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 8); return `session-${timestamp}-${random}`; } /** * Start the MemAgent */ public async start(): Promise<void> { if (this.isStarted) { throw new Error('MemAgent is already started'); } try { if (this.appMode !== 'cli') { logger.debug('Starting MemAgent...'); } // 1. Initialize services with optional lazy loading const useLazyLoading = shouldEnableLazyLoading({ appMode: this.appMode || undefined, }); let services: AgentServices | LazyAgentServices; // console.log('this.config',this.config) // console.log('useLazyLoading',useLazyLoading) if (useLazyLoading) { logger.debug('MemAgent: Using enhanced services with lazy loading'); services = await createEnhancedAgentServices(this.config, { enableLazyLoading: true, ...(this.appMode && { appMode: this.appMode }), }); } else { logger.debug('MemAgent: Using standard services'); services = await createAgentServices(this.config, this.appMode || undefined); } for (const service of requiredServices) { if (!services[service]) { throw new Error(`Required service ${service} is missing during agent start`); } } Object.assign(this, { mcpManager: services.mcpManager, promptManager: services.promptManager, stateManager: services.stateManager, sessionManager: services.sessionManager, internalToolManager: services.internalToolManager, unifiedToolManager: services.unifiedToolManager, services: services, }); // Sessions are already loaded during SessionManager initialization // No need to load them again here this.isStarted = true; if (this.appMode !== 'cli') { logger.debug('MemAgent started successfully'); } } catch (error) { logger.error('Failed to start MemAgent:', error); throw error; } } /** * Stop the MemAgent */ public async stop(): Promise<void> { if (this.isStopped) { logger.warn('MemAgent is already stopped'); return; } if (!this.isStarted) { throw new Error('MemAgent must be started before stopping'); } try { logger.info('Stopping MemAgent...'); const shutdownErrors: Error[] = []; // Save all sessions before shutdown try { if (this.sessionManager) { logger.info('Saving all sessions before shutdown...'); await this.sessionManager.shutdown(); // This will call saveAllSessions internally logger.debug('SessionManager shutdown completed'); } } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); shutdownErrors.push(new Error(`SessionManager shutdown failed: ${err.message}`)); } try { if (this.mcpManager) { await this.mcpManager.disconnectAll(); logger.debug('MCPManager disconnected all clients successfully'); } } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); shutdownErrors.push(new Error(`MCPManager disconnect failed: ${err.message}`)); } this.isStopped = true; this.isStarted = false; if (shutdownErrors.length > 0) { const errorMessages = shutdownErrors.map(e => e.message).join('; '); logger.warn(`MemAgent stopped with some errors: ${errorMessages}`); // Still consider it stopped, but log the errors } else { logger.info('MemAgent stopped successfully.'); } } catch (error) { logger.error('Failed to stop MemAgent:', error); throw error; } } /** * Get the status of the MemAgent */ public getIsStarted(): boolean { return this.isStarted; } /** * Get the status of the MemAgent */ public getIsStopped(): boolean { return this.isStopped; } private ensureStarted(): void { if (this.isStopped) { throw new Error('MemAgent has been stopped and cannot be used'); } if (!this.isStarted) { throw new Error('MemAgent must be started before use. Call agent.start() first.'); } } /** * Run the MemAgent */ public async run( userInput: string, imageDataInput?: { image: string; mimeType: string }, sessionId?: string, stream: boolean = false, options?: { memoryMetadata?: Record<string, any>; sessionOptions?: Record<string, any>; } ): Promise<{ response: string | null; backgroundOperations: Promise<void> }> { this.ensureStarted(); try { let session: ConversationSession; if (sessionId) { session = (await this.sessionManager.getSession(sessionId)) ?? (await this.sessionManager.createSession(sessionId)); this.currentActiveSessionId = sessionId; } else { // Use current active session or fall back to default session = (await this.sessionManager.getSession(this.currentActiveSessionId)) ?? (await this.sessionManager.createSession(this.currentActiveSessionId)); } logger.debug(`MemAgent.run: using session ${session.id}`); const { response, backgroundOperations } = await session.run( userInput, imageDataInput, stream, { ...(options?.memoryMetadata !== undefined && { memoryMetadata: options.memoryMetadata }), ...(options?.sessionOptions !== undefined && { contextOverrides: options.sessionOptions, }), } ); const finalResponse = response && response.trim() !== '' ? response : null; return { response: finalResponse, backgroundOperations }; } catch (error) { logger.error('MemAgent.run: error', error); throw error; } } public async createSession(sessionId?: string): Promise<ConversationSession> { this.ensureStarted(); logger.debug(`MemAgent: Creating session with ID: ${sessionId || 'auto-generated'}`); const session = await this.sessionManager.createSession(sessionId); logger.info(`MemAgent: Created session: ${session.id}`); return session; } public async getSession(sessionId: string): Promise<ConversationSession | null> { this.ensureStarted(); let session = await this.sessionManager.getSession(sessionId); // For CLI mode, automatically create the default session if it doesn't exist if (!session && this.appMode === 'cli' && sessionId === 'default') { logger.debug( 'MemAgent: Default session not found, creating new default session for CLI mode' ); try { session = await this.sessionManager.createSession('default'); logger.info('MemAgent: Created default session for CLI mode'); } catch (error) { logger.error('MemAgent: Failed to create default session:', error); return null; } } return session; } /** * Get the current active session ID */ public getCurrentSessionId(): string { this.ensureStarted(); return this.currentActiveSessionId; } /** * Load conversation history for a specific session */ public async loadSessionHistory(sessionId: string): Promise<void> { this.ensureStarted(); const session = await this.sessionManager.getSession(sessionId); if (!session) { throw new Error(`Session not found: ${sessionId}`); } try { await session.refreshConversationHistory(); } catch (error) { logger.warn(`MemAgent: Failed to load conversation history for session ${sessionId}:`, error); throw error; } } /** * Load (switch to) a specific session */ public async loadSession(sessionId: string): Promise<ConversationSession> { this.ensureStarted(); let session = await this.sessionManager.getSession(sessionId); if (!session) { throw new Error(`Session not found: ${sessionId}`); } // Initialize services and load conversation history try { logger.debug(`MemAgent: Loading session ${sessionId}...`); await session.init(); // Initialize services first // Load conversation history so AI can see previous messages await session.refreshConversationHistory(); logger.info(`MemAgent: Successfully loaded session ${sessionId} with conversation history`); } catch (error) { logger.warn(`MemAgent: Failed to initialize session ${sessionId}:`, error); // Continue even if initialization fails } this.currentActiveSessionId = sessionId; logger.debug(`MemAgent: Switched to session ${sessionId}`); return session; } /** * Get all active session IDs */ public async listSessions(): Promise<string[]> { this.ensureStarted(); return await this.sessionManager.getActiveSessionIds(); } /** * Remove a session */ public async removeSession(sessionId: string): Promise<boolean> { this.ensureStarted(); // Prevent removing the currently active session if (sessionId === this.currentActiveSessionId) { throw new Error( 'Cannot remove the currently active session. Switch to another session first.' ); } return await this.sessionManager.removeSession(sessionId); } /** * Get session metadata including message count */ public async getSessionMetadata(sessionId: string): Promise<{ id: string; createdAt?: number; lastActivity?: number; messageCount?: number; } | null> { this.ensureStarted(); // Check if session exists const session = await this.sessionManager.getSession(sessionId); if (!session) { return null; } // Get actual message count from the session let messageCount = 0; try { // Ensure the session is properly initialized if (!session.getContextManager) { await session.init(); } const history = await session.getConversationHistory(); messageCount = history.length; } catch (error) { logger.warn(`Failed to get message count for session ${sessionId}:`, error); // Try alternative method to get message count try { const contextManager = session.getContextManager(); if (contextManager) { const rawMessages = contextManager.getRawMessages(); messageCount = rawMessages.length; } } catch (fallbackError) { logger.warn( `Failed to get message count via fallback for session ${sessionId}:`, fallbackError ); } } // For now, return basic metadata since SessionManager doesn't expose internal metadata // This could be enhanced later to track more detailed session statistics return { id: sessionId, createdAt: Date.now(), // Placeholder - actual creation time would need to be tracked lastActivity: Date.now(), // Placeholder - actual last activity would need to be tracked messageCount, }; } /** * Get conversation history for the current session */ public async getCurrentSessionHistory(): Promise<any[]> { this.ensureStarted(); const session = await this.sessionManager.getSession(this.currentActiveSessionId); if (!session) { return []; } return await session.getConversationHistory(); } /** * Get conversation history for a specific session */ public async getSessionHistory(sessionId: string): Promise<any[]> { this.ensureStarted(); // Get the session const session = await this.sessionManager.getSession(sessionId); if (!session) { throw new Error(`Session not found: ${sessionId}`); } // Access the context manager to get raw messages // Note: We need to access the private contextManager property // This is a temporary solution until we add a proper public method to ConversationSession const contextManager = (session as any).contextManager; if (!contextManager) { // Session might not be initialized yet return []; } // Get raw messages and convert them to a format suitable for API response const rawMessages = contextManager.getRawMessages(); // Transform the messages to a more user-friendly format return rawMessages.map((msg: any, index: number) => ({ id: index + 1, role: msg.role, content: msg.content, timestamp: new Date().toISOString(), // Placeholder - actual timestamps would need to be tracked ...(msg.toolCalls && { toolCalls: msg.toolCalls }), ...(msg.toolCallId && { toolCallId: msg.toolCallId }), ...(msg.name && { name: msg.name }), })); } public getCurrentLLMConfig(): LLMConfig { this.ensureStarted(); return structuredClone(this.stateManager.getLLMConfig()); } public async connectMcpServer(name: string, config: McpServerConfig): Promise<void> { this.ensureStarted(); try { // Add to runtime state first with validation const validation = this.stateManager.addMcpServer(name, config); if (!validation.isValid) { const errorMessages = validation.errors.map(e => e.message).join(', '); throw new Error(`Invalid MCP server configuration: ${errorMessages}`); } // Then connect the server await this.mcpManager.connectServer(name, config); logger.info(`MemAgent: Successfully added and connected to MCP server '${name}'.`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`MemAgent: Failed to add MCP server '${name}': ${errorMessage}`); // Clean up state if connection failed this.stateManager.removeMcpServer(name); throw error; } } public async removeMcpServer(name: string): Promise<void> { this.ensureStarted(); // Disconnect the client first await this.mcpManager.removeClient(name); // Then remove from runtime state this.stateManager.removeMcpServer(name); } public async executeMcpTool(toolName: string, args: any): Promise<any> { this.ensureStarted(); return await this.mcpManager.executeTool(toolName, args); } public async getAllMcpTools(): Promise<any> { this.ensureStarted(); return await this.mcpManager.getAllTools(); } public getMcpClients(): Map<string, IMCPClient> { this.ensureStarted(); return this.mcpManager.getClients(); } public getMcpFailedConnections(): Record<string, string> { this.ensureStarted(); return this.mcpManager.getFailedConnections(); } public getAllMcpServers(): Array<{ id: string; name: string; status: string; error?: string }> { this.ensureStarted(); const clients = this.mcpManager.getClients(); const failedConnections = this.mcpManager.getFailedConnections(); const servers: Array<{ id: string; name: string; status: string; error?: string }> = []; // Add connected servers for (const [name, client] of clients.entries()) { try { client.getServerInfo(); // Get server info but don't use it servers.push({ id: name, // Use client name as id for API compatibility name, status: 'connected', }); } catch (error) { servers.push({ id: name, // Use client name as id for API compatibility name, status: 'error', error: error instanceof Error ? error.message : String(error), }); } } // Add failed connections for (const [name, error] of Object.entries(failedConnections)) { servers.push({ id: name, // Use client name as id for API compatibility name, status: 'error', error, }); } return servers; } public getEffectiveConfig(sessionId?: string): Readonly<AgentConfig> { this.ensureStarted(); return sessionId ? this.stateManager.getRuntimeConfig(sessionId) : this.stateManager.getRuntimeConfig(); } public getCurrentActiveSessionId() { return this.currentActiveSessionId; } /** * Manually save all sessions to persistent storage */ public async saveAllSessions(): Promise<{ saved: number; failed: number; total: number }> { this.ensureStarted(); const stats = await this.sessionManager.saveAllSessions(); return { saved: stats.savedSessions, failed: stats.failedSessions, total: stats.totalSessions, }; } /** * Manually load all sessions from persistent storage */ public async loadAllSessions(): Promise<{ restored: number; failed: number; total: number }> { this.ensureStarted(); const stats = await this.sessionManager.loadAllSessions(); return { restored: stats.restoredSessions, failed: stats.failedSessions, total: stats.totalSessions, }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/campfirein/cipher'

If you have feedback or need assistance with the MCP directory API, please join our Discord server