Skip to main content
Glama
service-initializer.ts33.8 kB
import { EnhancedPromptManager } from '../brain/systemPrompt/enhanced-manager.js'; import { ContextManager } from '../brain/llm/index.js'; import { MemAgentStateManager } from '../brain/memAgent/state-manager.js'; import { MCPManager } from '../mcp/manager.js'; import { SessionManager } from '../session/session-manager.js'; import { InternalToolManager } from '../brain/tools/manager.js'; import { UnifiedToolManager } from '../brain/tools/unified-tool-manager.js'; import { registerAllTools } from '../brain/tools/definitions/index.js'; import { logger } from '../logger/index.js'; import { AgentConfig } from '../brain/memAgent/config.js'; import { ServerConfigsSchema } from '../mcp/config.js'; import { ServerConfigs } from '../mcp/types.js'; import { EmbeddingManager } from '../brain/embedding/index.js'; import { VectorStoreManager, DualCollectionVectorManager } from '../vector_storage/index.js'; import { createLLMService } from '../brain/llm/services/factory.js'; import { createContextManager } from '../brain/llm/messages/factory.js'; import { ILLMService } from '../brain/llm/index.js'; import { getServiceCache, createServiceKey } from '../brain/memory/service-cache.js'; import { createVectorStoreFromEnv, createDualCollectionVectorStoreFromEnv, createMultiCollectionVectorStoreFromEnv, } from '../vector_storage/factory.js'; import { KnowledgeGraphManager } from '../knowledge_graph/manager.js'; import { createKnowledgeGraphFromEnv } from '../knowledge_graph/factory.js'; import { EventManager } from '../events/event-manager.js'; import { EventPersistenceConfig } from '../events/persistence.js'; import { env } from '../env.js'; import { ProviderType } from '../brain/systemPrompt/interfaces.js'; import fs from 'fs'; import path from 'path'; import yaml from 'yaml'; /** * Create embedding configuration from LLM provider settings */ async function createEmbeddingFromLLMProvider( embeddingManager: EmbeddingManager, llmConfig: any ): Promise<{ embedder: any; info: any } | null> { const provider = llmConfig.provider?.toLowerCase(); try { switch (provider) { case 'openai': { const apiKey = llmConfig.apiKey || process.env.OPENAI_API_KEY; if (!apiKey || apiKey.trim() === '') { logger.debug( 'No OpenAI API key available for embedding fallback - switching to chat-only mode' ); return null; } const embeddingConfig = { type: 'openai' as const, apiKey, model: 'text-embedding-3-small' as const, baseUrl: llmConfig.baseUrl, organization: llmConfig.organization, timeout: 30000, maxRetries: 3, }; logger.debug('Using OpenAI embedding fallback: text-embedding-3-small'); return await embeddingManager.createEmbedderFromConfig(embeddingConfig, 'default'); } case 'ollama': { let baseUrl = llmConfig.baseUrl || process.env.OLLAMA_BASE_URL || 'http://localhost:11434'; // For embedding service, remove /v1 suffix if present and use base URL // Ollama embeddings use /api/embeddings endpoint, not /v1/embeddings if (baseUrl.endsWith('/v1')) { baseUrl = baseUrl.replace(/\/v1$/, ''); } // Ollama doesn't require API key, so proceed with embedding config const embeddingConfig = { type: 'ollama' as const, baseUrl, model: 'nomic-embed-text' as const, timeout: 30000, maxRetries: 3, }; logger.debug('Using Ollama embedding fallback: nomic-embed-text'); return await embeddingManager.createEmbedderFromConfig(embeddingConfig, 'default'); } case 'lmstudio': { const baseUrl = llmConfig.baseUrl || process.env.LMSTUDIO_BASE_URL || 'http://localhost:1234/v1'; // LM Studio doesn't require API key, so proceed with embedding config const embeddingConfig = { type: 'lmstudio' as const, baseUrl, model: 'nomic-embed-text-v1.5' as const, timeout: 30000, maxRetries: 3, }; logger.debug('Using LM Studio embedding fallback: nomic-embed-text-v1.5'); return await embeddingManager.createEmbedderFromConfig(embeddingConfig, 'default'); } case 'gemini': { const apiKey = llmConfig.apiKey || process.env.GEMINI_API_KEY; if (!apiKey || apiKey.trim() === '') { logger.debug( 'No Gemini API key available for embedding fallback - switching to chat-only mode' ); // API key not available - will skip embedding return null; } const embeddingConfig = { type: 'gemini' as const, apiKey, model: 'gemini-embedding-001' as const, timeout: 30000, maxRetries: 3, }; logger.debug('Using Gemini embedding fallback: gemini-embedding-001'); return await embeddingManager.createEmbedderFromConfig(embeddingConfig, 'default'); } case 'anthropic': { // Anthropic doesn't have native embeddings, use Voyage as recommended fallback const apiKey = llmConfig.apiKey || process.env.VOYAGE_API_KEY; if (!apiKey || apiKey.trim() === '') { logger.debug( 'No Voyage API key available for Anthropic - switching to chat-only mode (set VOYAGE_API_KEY)' ); // Voyage API key not available return null; } const embeddingConfig = { type: 'voyage' as const, apiKey, model: 'voyage-3-large' as const, timeout: 30000, maxRetries: 3, dimensions: 1024, }; logger.debug('Using Voyage embedding for Anthropic LLM', { voyageModel: 'voyage-3-large', voyageDimensions: 1024, provider: 'voyage', }); return await embeddingManager.createEmbedderFromConfig(embeddingConfig, 'default'); } case 'aws': { // AWS Bedrock has native embeddings via Amazon Titan and Cohere // Here we are const accessKeyId = llmConfig.accessKeyId || process.env.AWS_ACCESS_KEY_ID; const secretAccessKey = llmConfig.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY; if ( !accessKeyId || accessKeyId.trim() === '' || !secretAccessKey || secretAccessKey.trim() === '' ) { logger.debug( 'No AWS credentials available for AWS Bedrock embedding - switching to chat-only mode (need AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY)' ); // AWS credentials not available return null; } const embeddingConfig = { type: 'aws-bedrock' as const, region: llmConfig.region || process.env.AWS_DEFAULT_REGION || 'us-east-1', accessKeyId, secretAccessKey, sessionToken: llmConfig.sessionToken || process.env.AWS_SESSION_TOKEN, model: 'amazon.titan-embed-text-v2:0' as const, timeout: 30000, maxRetries: 3, dimensions: 1024, }; logger.debug('Using AWS Bedrock native embedding: amazon.titan-embed-text-v2:0'); return await embeddingManager.createEmbedderFromConfig(embeddingConfig, 'default'); } case 'azure': { // Azure OpenAI - try to use same Azure setup for embeddings const azureApiKey = llmConfig.apiKey || process.env.AZURE_OPENAI_API_KEY; if (!azureApiKey || azureApiKey.trim() === '') { logger.debug('No Azure OpenAI API key available for embedding fallback'); // Fallback to regular OpenAI if Azure not available const openaiApiKey = process.env.OPENAI_API_KEY; if (openaiApiKey && openaiApiKey.trim() !== '') { const embeddingConfig = { type: 'openai' as const, apiKey: openaiApiKey, model: 'text-embedding-3-small' as const, timeout: 30000, maxRetries: 3, }; logger.debug('Using OpenAI embedding fallback for Azure LLM: text-embedding-3-small'); return await embeddingManager.createEmbedderFromConfig(embeddingConfig, 'default'); } logger.debug('No OpenAI API key available either - switching to chat-only mode'); // Neither Azure nor OpenAI API key provided return null; } const embeddingConfig = { type: 'openai' as const, apiKey: azureApiKey, model: 'text-embedding-3-small' as const, baseUrl: llmConfig.azure?.endpoint || process.env.AZURE_OPENAI_ENDPOINT, timeout: 30000, maxRetries: 3, }; logger.debug('Using Azure OpenAI embedding fallback: text-embedding-3-small'); return await embeddingManager.createEmbedderFromConfig(embeddingConfig, 'default'); } case 'qwen': { // Qwen has native embeddings via DashScope API const apiKey = llmConfig.apiKey || process.env.QWEN_API_KEY || process.env.DASHSCOPE_API_KEY; if (!apiKey || apiKey.trim() === '') { logger.debug( 'No Qwen API key available for native embedding - switching to chat-only mode (need QWEN_API_KEY or DASHSCOPE_API_KEY)' ); // Removed global embedding state -('Qwen API key not provided'); return null; } const embeddingConfig = { type: 'qwen' as const, apiKey, model: 'text-embedding-v3' as const, baseUrl: llmConfig.baseUrl, timeout: 30000, maxRetries: 3, dimensions: 1024, }; logger.debug('Using Qwen native embedding: text-embedding-v3'); return await embeddingManager.createEmbedderFromConfig(embeddingConfig, 'default'); } case 'openrouter': { // OpenRouter doesn't support embeddings and no fallback should be used logger.debug( 'OpenRouter does not support embedding models - embeddings disabled for this provider' ); return null; } default: { logger.debug(`No embedding fallback available for LLM provider: ${provider}`); return null; } } } catch (error) { logger.warn(`Failed to create embedding from LLM provider ${provider}`, { error: error instanceof Error ? error.message : String(error), }); return null; } } export type AgentServices = { [key: string]: any; mcpManager: MCPManager; promptManager: EnhancedPromptManager; stateManager: MemAgentStateManager; sessionManager: SessionManager; internalToolManager: InternalToolManager; unifiedToolManager: UnifiedToolManager; embeddingManager?: EmbeddingManager; vectorStoreManager: VectorStoreManager | DualCollectionVectorManager; eventManager: EventManager; llmService?: ILLMService; contextManager?: any; knowledgeGraphManager?: KnowledgeGraphManager; }; export async function createAgentServices( agentConfig: AgentConfig, appMode?: 'cli' | 'mcp' | 'api' ): Promise<AgentServices> { let contextManager: ContextManager | undefined = undefined; // 1. Initialize agent config const config = agentConfig; // 1.1. Initialize event manager first (other services will use it) logger.debug('Initializing event manager...'); // Use eventPersistence config if present, with environment variable overrides const eventPersistenceConfig = { ...config.eventPersistence, // Support EVENT_PERSISTENCE_ENABLED env variable enabled: process.env.EVENT_PERSISTENCE_ENABLED === 'true' || (config.eventPersistence?.enabled ?? false), // Support EVENT_PERSISTENCE_PATH env variable filePath: process.env.EVENT_PERSISTENCE_PATH || config.eventPersistence?.filePath, }; // Support EVENT_FILTERING_ENABLED env variable const enableFiltering = process.env.EVENT_FILTERING_ENABLED === 'true'; // Support EVENT_FILTERED_TYPES env variable (comma-separated) const filteredTypes = (process.env.EVENT_FILTERED_TYPES || '') .split(',') .map(s => s.trim()) .filter(Boolean); const eventManager = new EventManager({ enableLogging: true, enablePersistence: eventPersistenceConfig.enabled, enableFiltering, maxServiceListeners: 300, maxSessionListeners: 150, maxSessionHistorySize: 1000, sessionCleanupInterval: 300000, // 5 minutes // Pass through eventPersistenceConfig for use by persistence provider eventPersistenceConfig: eventPersistenceConfig as Partial<EventPersistenceConfig>, }); // Register filter for filtered event types if (enableFiltering && filteredTypes.length > 0) { eventManager.registerFilter({ name: 'env-filtered-types', description: 'Block event types from EVENT_FILTERED_TYPES', enabled: true, filter: event => !filteredTypes.includes(event.type), }); } // Log event persistence configuration if (eventPersistenceConfig.enabled) { logger.info('Event persistence enabled', { storageType: eventPersistenceConfig.storageType || 'file', filePath: eventPersistenceConfig.filePath || './data/events', enabled: eventPersistenceConfig.enabled, }); } // Emit cipher startup event eventManager.emitServiceEvent('cipher:started', { timestamp: Date.now(), version: process.env.npm_package_version || '1.0.0', }); const mcpManager = new MCPManager(); // Set event manager for connection lifecycle events mcpManager.setEventManager(eventManager); // Set quiet mode for CLI to reduce MCP logging noise if (appMode === 'cli') { mcpManager.setQuietMode(true); } // Parse and validate the MCP server configurations to ensure required fields are present // The ServerConfigsSchema.parse() will transform input types to output types with required fields const parsedMcpServers = ServerConfigsSchema.parse(config.mcpServers) as ServerConfigs; await mcpManager.initializeFromConfig(parsedMcpServers); const mcpServerCount = Object.keys(config.mcpServers || {}).length; if (mcpServerCount === 0) { if (appMode !== 'cli') { logger.debug('Agent initialized without MCP servers - only built-in capabilities available'); } } else { if (appMode !== 'cli') { logger.debug(`Client manager initialized with ${mcpServerCount} MCP server(s)`); } } // Emit MCP manager initialization event eventManager.emitServiceEvent('cipher:serviceStarted', { serviceType: 'MCPManager', timestamp: Date.now(), }); // 2. Initialize embedding manager with new fallback mechanism if (appMode !== 'cli') { logger.debug('Initializing embedding manager...'); } const embeddingManager = new EmbeddingManager(); let embeddingEnabled = false; try { let embeddingResult: { embedder: any; info: any } | null = null; // Check if embeddings are explicitly disabled const explicitlyDisabled = (config.embedding && typeof config.embedding === 'object' && 'disabled' in config.embedding && config.embedding.disabled === true) || config.embedding === null || config.embedding === false || process.env.DISABLE_EMBEDDINGS === 'true' || process.env.EMBEDDING_DISABLED === 'true'; if (explicitlyDisabled) { logger.warn( 'Embeddings are explicitly disabled - all embedding-dependent tools will be unavailable (chat-only mode)' ); // Removed global embedding state -('Explicitly disabled in configuration'); embeddingEnabled = false; } else { // Priority 1: Try explicit YAML embedding configuration if available if ( config.embedding && typeof config.embedding === 'object' && !('disabled' in config.embedding) ) { logger.debug('Found explicit embedding configuration in YAML, using it'); // Validate API key for explicit embedding config const embeddingConfig = config.embedding as any; const needsApiKey = ['openai', 'gemini', 'anthropic', 'voyage', 'qwen'].includes( embeddingConfig.type ); const needsAwsCredentials = embeddingConfig.type === 'aws-bedrock'; if (needsApiKey) { const apiKey = embeddingConfig.apiKey || process.env[`${embeddingConfig.type.toUpperCase()}_API_KEY`]; if (!apiKey || apiKey.trim() === '') { logger.debug( `No API key available for explicit ${embeddingConfig.type} embedding config - switching to chat-only mode` ); // API key not provided for explicit embedding config embeddingResult = null; } else { // Create a clean config object similar to the fallback logic const cleanEmbeddingConfig = { type: embeddingConfig.type, apiKey, model: embeddingConfig.model || 'text-embedding-3-small', baseUrl: embeddingConfig.baseUrl, organization: embeddingConfig.organization, timeout: embeddingConfig.timeout || 30000, maxRetries: embeddingConfig.maxRetries || 3, dimensions: embeddingConfig.dimensions, }; embeddingResult = await embeddingManager.createEmbedderFromConfig( cleanEmbeddingConfig, 'default' ); } } else if (needsAwsCredentials) { const accessKeyId = embeddingConfig.accessKeyId || process.env.AWS_ACCESS_KEY_ID; const secretAccessKey = embeddingConfig.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY; console.log('accessKeyId', accessKeyId); console.log('secretAccessKey', secretAccessKey); if ( !accessKeyId || accessKeyId.trim() === '' || !secretAccessKey || secretAccessKey.trim() === '' ) { logger.debug( 'No AWS credentials available for explicit aws-bedrock embedding config - switching to chat-only mode' ); // AWS credentials not available embeddingResult = null; } else { embeddingResult = await embeddingManager.createEmbedderFromConfig( embeddingConfig, 'default' ); } } else { // Ollama, LM Studio - no API key needed embeddingResult = await embeddingManager.createEmbedderFromConfig( embeddingConfig, 'default' ); } } // Priority 2: If no explicit embedding config (undefined) or disabled:false, fallback to LLM provider's embedding if (!embeddingResult) { if (config.llm?.provider) { logger.debug( 'No explicit embedding config found, falling back to LLM provider embedding' ); embeddingResult = await createEmbeddingFromLLMProvider(embeddingManager, config.llm); // Update agent config with the embedding info so vector store can use correct dimension if (embeddingResult && embeddingResult.info) { // Create embedding config object for the agent config const embeddingConfig = { type: embeddingResult.info.provider, model: embeddingResult.info.model, dimensions: embeddingResult.info.dimension, }; // Update the agent config with the embedding configuration (config as any).embedding = embeddingConfig; logger.debug('Updated agent config with embedding fallback configuration', { provider: embeddingResult.info.provider, model: embeddingResult.info.model, dimension: embeddingResult.info.dimension, }); } } else { logger.debug( 'No LLM provider available for embedding fallback, trying environment auto-detection' ); embeddingResult = await embeddingManager.createEmbedderFromEnv('default'); } } if (embeddingResult) { if (appMode !== 'cli') { logger.info('Embedding manager initialized successfully', { provider: embeddingResult.info.provider, model: embeddingResult.info.model, dimension: embeddingResult.info.dimension, }); } // Emit embedding manager initialization event eventManager.emitServiceEvent('cipher:serviceStarted', { serviceType: 'EmbeddingManager', timestamp: Date.now(), }); embeddingEnabled = true; } else { logger.warn( 'No embedding configuration available - embedding-dependent tools will be disabled (chat-only mode)' ); embeddingEnabled = false; } } } catch (error) { logger.error('Failed to initialize embedding manager - activating fallback mode', { error: error instanceof Error ? error.message : String(error), fallbackMode: 'chat-only', }); embeddingEnabled = false; // Log detailed fallback information logger.warn('🔄 Embedding system in fallback mode:', { mode: 'chat-only', availableFeatures: ['LLM conversation', 'MCP tools', 'System prompts'], unavailableFeatures: [ 'Memory search', 'Knowledge storage', 'Reasoning patterns', 'Vector operations', ], recoveryAction: 'Check embedding configuration and credentials, then restart the service', }); } // 3. Initialize vector storage manager with configuration // Use dual collection manager if reflection memory is enabled, otherwise use regular manager if (appMode !== 'cli') { logger.debug('Initializing vector storage manager...'); } let vectorStoreManager: VectorStoreManager | DualCollectionVectorManager | any; // MultiCollectionVectorManager try { // Check workspace memory first, then reflection memory to determine which manager to use const workspaceEnabled = !!env.USE_WORKSPACE_MEMORY; const reflectionEnabled = !env.DISABLE_REFLECTION_MEMORY && env.REFLECTION_VECTOR_STORE_COLLECTION && env.REFLECTION_VECTOR_STORE_COLLECTION.trim() !== ''; if (workspaceEnabled) { logger.debug('Workspace memory enabled, using multi collection vector manager'); const { manager } = await createMultiCollectionVectorStoreFromEnv(config); vectorStoreManager = manager; // Set event manager for memory operation events (vectorStoreManager as any).setEventManager(eventManager); const info = (vectorStoreManager as any).getInfo(); logger.debug('Multi collection vector storage manager initialized successfully', { backend: info.knowledge.manager.getInfo().backend.type, knowledgeCollection: info.knowledge.collectionName, reflectionCollection: info.reflection.enabled ? info.reflection.collectionName : 'disabled', workspaceCollection: info.workspace.enabled ? info.workspace.collectionName : 'disabled', dimension: info.knowledge.manager.getInfo().backend.dimension, knowledgeConnected: info.knowledge.connected, reflectionConnected: info.reflection.connected, workspaceConnected: info.workspace.connected, reflectionEnabled: info.reflection.enabled, workspaceEnabled: info.workspace.enabled, }); } else if (reflectionEnabled) { logger.debug('Reflection memory enabled, using dual collection vector manager'); const { manager } = await createDualCollectionVectorStoreFromEnv(config); vectorStoreManager = manager; // Set event manager for memory operation events (vectorStoreManager as DualCollectionVectorManager).setEventManager(eventManager); const info = (vectorStoreManager as DualCollectionVectorManager).getInfo(); logger.debug('Dual collection vector storage manager initialized successfully', { backend: info.knowledge.manager.getInfo().backend.type, knowledgeCollection: info.knowledge.collectionName, reflectionCollection: info.reflection.collectionName, dimension: info.knowledge.manager.getInfo().backend.dimension, knowledgeConnected: info.knowledge.connected, reflectionConnected: info.reflection.connected, reflectionEnabled: info.reflection.enabled, }); } else { logger.debug('Reflection memory disabled, using single collection vector manager'); const { manager } = await createVectorStoreFromEnv(config); vectorStoreManager = manager; // Set event manager for memory operation events (vectorStoreManager as VectorStoreManager).setEventManager(eventManager); logger.debug('Vector storage manager initialized successfully', { backend: vectorStoreManager.getInfo().backend.type, collection: vectorStoreManager.getInfo().backend.collectionName, dimension: vectorStoreManager.getInfo().backend.dimension, fallback: vectorStoreManager.getInfo().backend.fallback || false, }); } } catch (error) { logger.warn('Failed to initialize vector storage manager', { error: error instanceof Error ? error.message : String(error), }); logger.warn(error instanceof Error ? error.message : String(error)); // Fallback to regular manager in case of error const { manager } = await createVectorStoreFromEnv(config); vectorStoreManager = manager; } // 4. Initialize knowledge graph manager with configuration if (appMode !== 'cli') { logger.debug('Initializing knowledge graph manager...'); } let knowledgeGraphManager: KnowledgeGraphManager | undefined = undefined; try { const kgFactory = await createKnowledgeGraphFromEnv(); if (kgFactory) { knowledgeGraphManager = kgFactory.manager; logger.debug('Knowledge graph manager initialized successfully', { backend: knowledgeGraphManager.getInfo().backend.type, connected: knowledgeGraphManager.isConnected(), fallback: knowledgeGraphManager.getInfo().backend.fallback || false, }); } else { logger.debug('Knowledge graph is disabled in environment configuration'); } } catch (error) { logger.warn('Failed to initialize knowledge graph manager', { error: error instanceof Error ? error.message : String(error), }); } // 5. Initialize prompt manager // --- BEGIN MERGE ADVANCED PROMPT CONFIG --- const promptManager = new EnhancedPromptManager(); // Load static provider from cipher.yml let staticProvider: any = null; if (config.systemPrompt) { let enabled = true; let content = ''; if (typeof config.systemPrompt === 'string') { content = config.systemPrompt; } else if (typeof config.systemPrompt === 'object' && config.systemPrompt !== null) { const promptObj = config.systemPrompt as any; enabled = promptObj.enabled !== false && promptObj.enabled !== undefined; content = promptObj.content || ''; } staticProvider = { name: 'user-instruction', type: ProviderType.STATIC, priority: 100, enabled, config: { content }, }; } // Load providers from cipher-advanced-prompt.yml let advancedProviders: any[] = []; let advancedSettings: any = {}; const advancedPromptPath = path.resolve(process.cwd(), 'memAgent/cipher-advanced-prompt.yml'); if (fs.existsSync(advancedPromptPath)) { const fileContent = fs.readFileSync(advancedPromptPath, 'utf8'); const parsed = yaml.parse(fileContent); if (Array.isArray(parsed.providers)) { advancedProviders = parsed.providers; } if (parsed.settings) { advancedSettings = parsed.settings; } } // Merge providers: staticProvider (from cipher.yml) + advancedProviders (from cipher-advanced-prompt.yml) const mergedProviders = [ ...(staticProvider ? [staticProvider] : []), ...advancedProviders.filter(p => !staticProvider || p.name !== staticProvider.name), ]; // DEBUG: Print merged provider list (skip in MCP mode to avoid stdout contamination) // Removed verbose logging for cleaner output // Merge settings: advancedSettings takes precedence, fallback to default const mergedSettings = { maxGenerationTime: 10000, failOnProviderError: false, contentSeparator: '\n\n', ...advancedSettings, }; const mergedPromptConfig = { providers: mergedProviders, settings: mergedSettings, }; await promptManager.initialize(mergedPromptConfig); // --- END MERGE ADVANCED PROMPT CONFIG --- // 6. Initialize state manager for runtime state tracking const stateManager = new MemAgentStateManager(config); if (appMode !== 'cli') { logger.debug('Agent state manager initialized'); } // 7. Initialize LLM service let llmService: ILLMService | undefined = undefined; try { if (appMode !== 'cli') { logger.debug('Initializing LLM service...'); } const llmConfig = stateManager.getLLMConfig(); // Use ServiceCache for ContextManager to prevent duplicate creation const serviceCache = getServiceCache(); const contextManagerKey = createServiceKey('contextManager', { provider: llmConfig.provider, model: llmConfig.model, // Include additional config for proper cache key differentiation apiKey: llmConfig.apiKey ? 'present' : 'missing', baseURL: llmConfig.baseURL || 'default', }); contextManager = await serviceCache.getOrCreate(contextManagerKey, async () => { return createContextManager(llmConfig, promptManager, undefined, undefined); }); llmService = createLLMService(llmConfig, mcpManager, contextManager); if (appMode !== 'cli') { logger.info('LLM service initialized successfully', { provider: llmConfig.provider, model: llmConfig.model, }); } // Inject llmService into promptManager for dynamic providers promptManager.setLLMService(llmService); } catch (error) { logger.warn('Failed to initialize LLM service', { error: error instanceof Error ? error.message : String(error), }); } // 8. Prepare session manager configuration const sessionConfig: { maxSessions?: number; sessionTTL?: number } = {}; if (config.sessions?.maxSessions !== undefined) { sessionConfig.maxSessions = config.sessions.maxSessions; } if (config.sessions?.sessionTTL !== undefined) { sessionConfig.sessionTTL = config.sessions.sessionTTL; } // 9. Initialize internal tool manager const internalToolManager = new InternalToolManager({ enabled: true, timeout: 30000, enableCache: true, cacheTimeout: 300000, }); await internalToolManager.initialize(); // Set event manager for internal tool execution events internalToolManager.setEventManager(eventManager); // Register all internal tools const toolRegistrationResult = await registerAllTools(internalToolManager, { embeddingEnabled }); // Only log tool registration results if there are failures or in non-CLI mode if (appMode !== 'cli' || toolRegistrationResult.failed.length > 0) { logger.info('Internal tools registration completed', { totalTools: toolRegistrationResult.total, registered: toolRegistrationResult.registered.length, failed: toolRegistrationResult.failed.length, }); } if (toolRegistrationResult.failed.length > 0) { logger.warn('Some internal tools failed to register', { failedTools: toolRegistrationResult.failed, }); } // Configure the internal tool manager with services for advanced tools // Only include embeddingManager if embeddings are enabled const services: any = { vectorStoreManager, llmService, knowledgeGraphManager, }; if (embeddingEnabled) { services.embeddingManager = embeddingManager; } internalToolManager.setServices(services); // 10. Initialize unified tool manager with proper mode handling let unifiedToolManagerConfig: any; if (appMode === 'cli') { // CLI Mode: Only search tools accessible to Cipher's LLM, background tools executed separately unifiedToolManagerConfig = { enableInternalTools: true, enableMcpTools: true, conflictResolution: 'prefix-internal', mode: 'cli', // Special CLI mode }; } else if (appMode === 'mcp') { // MCP Mode: Configure based on MCP_SERVER_MODE const mcpServerMode = process.env.MCP_SERVER_MODE || 'default'; if (mcpServerMode === 'aggregator') { // Aggregator mode: Use aggregator mode for unified tool manager to expose all tools unifiedToolManagerConfig = { enableInternalTools: true, enableMcpTools: true, conflictResolution: 'prefix-internal', mode: 'aggregator', // Aggregator mode exposes all tools without filtering }; } else { // Default MCP mode: Use cli mode internally for agent access to all tools // External MCP exposure is controlled separately in mcp_handler.ts unifiedToolManagerConfig = { enableInternalTools: true, enableMcpTools: true, conflictResolution: 'prefix-internal', mode: 'cli', // Internal agent needs access to all tools in default mode }; } } else { // API Mode: Respect MCP_SERVER_MODE like MCP mode does const mcpServerMode = process.env.MCP_SERVER_MODE || 'default'; if (mcpServerMode === 'aggregator') { // Aggregator mode: Use aggregator mode for unified tool manager to expose all tools unifiedToolManagerConfig = { enableInternalTools: true, enableMcpTools: true, conflictResolution: 'prefix-internal', mode: 'aggregator', // Aggregator mode exposes all tools without filtering }; } else { // Default API mode: Similar to CLI unifiedToolManagerConfig = { enableInternalTools: true, enableMcpTools: true, conflictResolution: 'prefix-internal', mode: 'api', }; } } const unifiedToolManager = new UnifiedToolManager( mcpManager, internalToolManager, unifiedToolManagerConfig ); // Set event manager for tool execution events unifiedToolManager.setEventManager(eventManager); // Set embedding manager for embedding status checking if (embeddingManager) { unifiedToolManager.setEmbeddingManager(embeddingManager); } if (appMode !== 'cli') { logger.debug('Unified tool manager initialized'); } // 11. Create session manager with unified tool manager const sessionManager = new SessionManager( { stateManager, promptManager, contextManager, mcpManager, unifiedToolManager, eventManager, ...(embeddingManager && { embeddingManager }), // Only include if available }, sessionConfig ); // Initialize the session manager with persistent storage await sessionManager.init(); if (appMode !== 'cli') { logger.debug('Session manager with unified tools initialized'); } // Emit session manager initialization event eventManager.emitServiceEvent('cipher:serviceStarted', { serviceType: 'SessionManager', timestamp: Date.now(), }); // 12. Return the core services const agentServices: AgentServices = { mcpManager, promptManager, stateManager, sessionManager, internalToolManager, unifiedToolManager, vectorStoreManager, eventManager, contextManager, llmService: llmService || { generate: async () => '', directGenerate: async () => '', getAllTools: async () => ({}), getConfig: () => ({ provider: 'unknown', model: 'unknown' }), }, }; // Only include embeddingManager if embeddings are enabled if (embeddingEnabled) { agentServices.embeddingManager = embeddingManager; } // Only include knowledgeGraphManager when it's defined if (knowledgeGraphManager) { agentServices.knowledgeGraphManager = knowledgeGraphManager; } // Emit all services ready event const serviceTypes = Object.keys(agentServices).filter( key => agentServices[key as keyof AgentServices] ); eventManager.emitServiceEvent('cipher:allServicesReady', { timestamp: Date.now(), services: serviceTypes, }); return agentServices; }

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