2025-03-02 19:39:10 -08:00
|
|
|
import options from '../options.js';
|
2025-03-19 18:49:14 +00:00
|
|
|
import type { AIService, ChatCompletionOptions, ChatResponse, Message, SemanticContextService } from './ai_interface.js';
|
2025-03-11 17:30:50 +00:00
|
|
|
import { OpenAIService } from './providers/openai_service.js';
|
|
|
|
import { AnthropicService } from './providers/anthropic_service.js';
|
|
|
|
import { OllamaService } from './providers/ollama_service.js';
|
2025-03-09 02:19:26 +00:00
|
|
|
import log from '../log.js';
|
2025-03-11 18:39:59 +00:00
|
|
|
import { ContextExtractor } from './context/index.js';
|
2025-03-19 19:28:02 +00:00
|
|
|
import contextService from './context_service.js';
|
2025-03-11 23:26:47 +00:00
|
|
|
import indexService from './index_service.js';
|
2025-03-26 19:10:16 +00:00
|
|
|
import { getEmbeddingProvider, getEnabledEmbeddingProviders } from './providers/providers.js';
|
2025-03-19 16:19:48 +00:00
|
|
|
import agentTools from './agent_tools/index.js';
|
2025-03-02 19:39:10 -08:00
|
|
|
|
2025-03-28 21:04:12 +00:00
|
|
|
// Import interfaces
|
|
|
|
import type {
|
|
|
|
ServiceProviders,
|
|
|
|
IAIServiceManager,
|
|
|
|
ProviderMetadata
|
|
|
|
} from './interfaces/ai_service_interfaces.js';
|
|
|
|
import type { NoteSearchResult } from './interfaces/context_interfaces.js';
|
|
|
|
|
|
|
|
export class AIServiceManager implements IAIServiceManager {
|
2025-03-02 19:39:10 -08:00
|
|
|
private services: Record<ServiceProviders, AIService> = {
|
|
|
|
openai: new OpenAIService(),
|
|
|
|
anthropic: new AnthropicService(),
|
|
|
|
ollama: new OllamaService()
|
|
|
|
};
|
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
private providerOrder: ServiceProviders[] = ['openai', 'anthropic', 'ollama']; // Default order
|
|
|
|
private initialized = false;
|
2025-03-02 19:39:10 -08:00
|
|
|
|
|
|
|
constructor() {
|
2025-03-09 02:19:26 +00:00
|
|
|
// Don't call updateProviderOrder here
|
|
|
|
// Wait until a method is called to initialize
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the provider precedence order from saved options
|
2025-03-09 02:19:26 +00:00
|
|
|
* Returns true if successful, false if options not available yet
|
2025-03-02 19:39:10 -08:00
|
|
|
*/
|
2025-03-09 02:19:26 +00:00
|
|
|
updateProviderOrder(): boolean {
|
|
|
|
if (this.initialized) {
|
|
|
|
return true;
|
|
|
|
}
|
2025-03-02 19:39:10 -08:00
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
try {
|
|
|
|
// Default precedence: openai, anthropic, ollama
|
|
|
|
const defaultOrder: ServiceProviders[] = ['openai', 'anthropic', 'ollama'];
|
2025-03-02 19:39:10 -08:00
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
// Get custom order from options
|
|
|
|
const customOrder = options.getOption('aiProviderPrecedence');
|
|
|
|
|
|
|
|
if (customOrder) {
|
|
|
|
try {
|
2025-03-10 04:28:56 +00:00
|
|
|
// Try to parse as JSON first
|
|
|
|
let parsed;
|
|
|
|
|
|
|
|
// Handle both array in JSON format and simple string format
|
|
|
|
if (customOrder.startsWith('[') && customOrder.endsWith(']')) {
|
|
|
|
parsed = JSON.parse(customOrder);
|
|
|
|
} else if (typeof customOrder === 'string') {
|
2025-03-17 16:23:58 +00:00
|
|
|
// If it's a string with commas, split it
|
|
|
|
if (customOrder.includes(',')) {
|
|
|
|
parsed = customOrder.split(',').map(p => p.trim());
|
|
|
|
} else {
|
|
|
|
// If it's a simple string (like "ollama"), convert to single-item array
|
|
|
|
parsed = [customOrder];
|
|
|
|
}
|
2025-03-10 04:28:56 +00:00
|
|
|
} else {
|
|
|
|
// Fallback to default
|
|
|
|
parsed = defaultOrder;
|
|
|
|
}
|
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
// Validate that all providers are valid
|
|
|
|
if (Array.isArray(parsed) &&
|
|
|
|
parsed.every(p => Object.keys(this.services).includes(p))) {
|
|
|
|
this.providerOrder = parsed as ServiceProviders[];
|
|
|
|
} else {
|
|
|
|
log.info('Invalid AI provider precedence format, using defaults');
|
|
|
|
this.providerOrder = defaultOrder;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
log.error(`Failed to parse AI provider precedence: ${e}`);
|
2025-03-02 19:39:10 -08:00
|
|
|
this.providerOrder = defaultOrder;
|
|
|
|
}
|
2025-03-09 02:19:26 +00:00
|
|
|
} else {
|
2025-03-02 19:39:10 -08:00
|
|
|
this.providerOrder = defaultOrder;
|
|
|
|
}
|
2025-03-09 02:19:26 +00:00
|
|
|
|
|
|
|
this.initialized = true;
|
2025-03-17 16:23:58 +00:00
|
|
|
|
|
|
|
// Remove the validateEmbeddingProviders call since we now do validation on the client
|
|
|
|
// this.validateEmbeddingProviders();
|
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
return true;
|
|
|
|
} catch (error) {
|
|
|
|
// If options table doesn't exist yet, use defaults
|
|
|
|
// This happens during initial database creation
|
|
|
|
this.providerOrder = ['openai', 'anthropic', 'ollama'];
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-17 16:23:58 +00:00
|
|
|
/**
|
|
|
|
* Validate embedding providers configuration
|
|
|
|
* - Check if embedding default provider is in provider precedence list
|
|
|
|
* - Check if all providers in precedence list and default provider are enabled
|
|
|
|
*
|
|
|
|
* @returns A warning message if there are issues, or null if everything is fine
|
|
|
|
*/
|
|
|
|
async validateEmbeddingProviders(): Promise<string | null> {
|
|
|
|
try {
|
|
|
|
// Check if AI is enabled, if not, skip validation
|
|
|
|
const aiEnabled = await options.getOptionBool('aiEnabled');
|
|
|
|
if (!aiEnabled) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse provider precedence list (similar to updateProviderOrder)
|
|
|
|
let precedenceList: string[] = [];
|
|
|
|
const precedenceOption = await options.getOption('aiProviderPrecedence');
|
|
|
|
|
|
|
|
if (precedenceOption) {
|
|
|
|
if (precedenceOption.startsWith('[') && precedenceOption.endsWith(']')) {
|
|
|
|
precedenceList = JSON.parse(precedenceOption);
|
|
|
|
} else if (typeof precedenceOption === 'string') {
|
|
|
|
if (precedenceOption.includes(',')) {
|
|
|
|
precedenceList = precedenceOption.split(',').map(p => p.trim());
|
|
|
|
} else {
|
|
|
|
precedenceList = [precedenceOption];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get enabled providers
|
|
|
|
const enabledProviders = await getEnabledEmbeddingProviders();
|
|
|
|
const enabledProviderNames = enabledProviders.map(p => p.name);
|
|
|
|
|
|
|
|
// Check if all providers in precedence list are enabled
|
|
|
|
const allPrecedenceEnabled = precedenceList.every(p =>
|
|
|
|
enabledProviderNames.includes(p) || p === 'local');
|
|
|
|
|
|
|
|
// Return warning message if there are issues
|
2025-04-01 20:38:03 +00:00
|
|
|
if (!allPrecedenceEnabled) {
|
2025-03-17 16:23:58 +00:00
|
|
|
let message = 'There are issues with your AI provider configuration:';
|
|
|
|
|
|
|
|
if (!allPrecedenceEnabled) {
|
|
|
|
const disabledProviders = precedenceList.filter(p =>
|
|
|
|
!enabledProviderNames.includes(p) && p !== 'local');
|
|
|
|
message += `\n• The following providers in your precedence list are not enabled: ${disabledProviders.join(', ')}.`;
|
|
|
|
}
|
|
|
|
|
|
|
|
message += '\n\nPlease check your AI settings.';
|
|
|
|
|
|
|
|
// Log warning to console
|
|
|
|
log.error('AI Provider Configuration Warning: ' + message);
|
|
|
|
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
} catch (error) {
|
|
|
|
log.error(`Error validating embedding providers: ${error}`);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
/**
|
|
|
|
* Ensure manager is initialized before using
|
|
|
|
*/
|
|
|
|
private ensureInitialized() {
|
|
|
|
if (!this.initialized) {
|
|
|
|
this.updateProviderOrder();
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if any AI service is available
|
|
|
|
*/
|
|
|
|
isAnyServiceAvailable(): boolean {
|
|
|
|
return Object.values(this.services).some(service => service.isAvailable());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get list of available providers
|
|
|
|
*/
|
|
|
|
getAvailableProviders(): ServiceProviders[] {
|
2025-03-09 02:19:26 +00:00
|
|
|
this.ensureInitialized();
|
2025-03-02 19:39:10 -08:00
|
|
|
return Object.entries(this.services)
|
|
|
|
.filter(([_, service]) => service.isAvailable())
|
|
|
|
.map(([key, _]) => key as ServiceProviders);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a chat completion response using the first available AI service
|
|
|
|
* based on the configured precedence order
|
|
|
|
*/
|
|
|
|
async generateChatCompletion(messages: Message[], options: ChatCompletionOptions = {}): Promise<ChatResponse> {
|
2025-03-09 02:19:26 +00:00
|
|
|
this.ensureInitialized();
|
|
|
|
|
2025-03-02 19:39:10 -08:00
|
|
|
if (!messages || messages.length === 0) {
|
|
|
|
throw new Error('No messages provided for chat completion');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try providers in order of preference
|
|
|
|
const availableProviders = this.getAvailableProviders();
|
|
|
|
|
|
|
|
if (availableProviders.length === 0) {
|
|
|
|
throw new Error('No AI providers are available. Please check your AI settings.');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort available providers by precedence
|
|
|
|
const sortedProviders = this.providerOrder
|
|
|
|
.filter(provider => availableProviders.includes(provider));
|
|
|
|
|
|
|
|
// If a specific provider is requested and available, use it
|
|
|
|
if (options.model && options.model.includes(':')) {
|
|
|
|
const [providerName, modelName] = options.model.split(':');
|
|
|
|
|
|
|
|
if (availableProviders.includes(providerName as ServiceProviders)) {
|
|
|
|
try {
|
|
|
|
const modifiedOptions = { ...options, model: modelName };
|
|
|
|
return await this.services[providerName as ServiceProviders].generateChatCompletion(messages, modifiedOptions);
|
|
|
|
} catch (error) {
|
2025-03-09 02:19:26 +00:00
|
|
|
log.error(`Error with specified provider ${providerName}: ${error}`);
|
2025-03-02 19:39:10 -08:00
|
|
|
// If the specified provider fails, continue with the fallback providers
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try each provider in order until one succeeds
|
|
|
|
let lastError: Error | null = null;
|
|
|
|
|
|
|
|
for (const provider of sortedProviders) {
|
|
|
|
try {
|
|
|
|
return await this.services[provider].generateChatCompletion(messages, options);
|
|
|
|
} catch (error) {
|
2025-03-09 02:19:26 +00:00
|
|
|
log.error(`Error with provider ${provider}: ${error}`);
|
2025-03-02 19:39:10 -08:00
|
|
|
lastError = error as Error;
|
|
|
|
// Continue to the next provider
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we get here, all providers failed
|
|
|
|
throw new Error(`All AI providers failed: ${lastError?.message || 'Unknown error'}`);
|
|
|
|
}
|
2025-03-11 18:07:28 +00:00
|
|
|
|
|
|
|
setupEventListeners() {
|
|
|
|
// Setup event listeners for AI services
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the context extractor service
|
|
|
|
* @returns The context extractor instance
|
|
|
|
*/
|
|
|
|
getContextExtractor() {
|
|
|
|
return contextExtractor;
|
|
|
|
}
|
|
|
|
|
2025-03-19 19:28:02 +00:00
|
|
|
/**
|
|
|
|
* Get the context service for advanced context management
|
|
|
|
* @returns The context service instance
|
|
|
|
*/
|
|
|
|
getContextService() {
|
|
|
|
return contextService;
|
2025-03-11 18:07:28 +00:00
|
|
|
}
|
2025-03-17 16:23:58 +00:00
|
|
|
|
2025-03-11 23:26:47 +00:00
|
|
|
/**
|
|
|
|
* Get the index service for managing knowledge base indexing
|
|
|
|
* @returns The index service instance
|
|
|
|
*/
|
|
|
|
getIndexService() {
|
|
|
|
return indexService;
|
|
|
|
}
|
2025-03-19 16:19:48 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize agent tools for enhanced LLM features
|
|
|
|
*/
|
|
|
|
async initializeAgentTools(): Promise<void> {
|
|
|
|
try {
|
|
|
|
await agentTools.initialize(this);
|
|
|
|
log.info("Agent tools initialized successfully");
|
|
|
|
} catch (error: any) {
|
|
|
|
log.error(`Error initializing agent tools: ${error.message}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the agent tools manager
|
|
|
|
* This provides access to all agent tools
|
|
|
|
*/
|
|
|
|
getAgentTools() {
|
|
|
|
return agentTools;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the vector search tool for semantic similarity search
|
|
|
|
*/
|
|
|
|
getVectorSearchTool() {
|
|
|
|
return agentTools.getVectorSearchTool();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the note navigator tool for hierarchical exploration
|
|
|
|
*/
|
|
|
|
getNoteNavigatorTool() {
|
|
|
|
return agentTools.getNoteNavigatorTool();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the query decomposition tool for complex queries
|
|
|
|
*/
|
|
|
|
getQueryDecompositionTool() {
|
|
|
|
return agentTools.getQueryDecompositionTool();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the contextual thinking tool for transparent reasoning
|
|
|
|
*/
|
|
|
|
getContextualThinkingTool() {
|
|
|
|
return agentTools.getContextualThinkingTool();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get whether AI features are enabled from options
|
|
|
|
*/
|
|
|
|
getAIEnabled(): boolean {
|
|
|
|
return options.getOptionBool('aiEnabled');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set up embeddings provider for AI features
|
|
|
|
*/
|
|
|
|
async setupEmbeddingsProvider(): Promise<void> {
|
|
|
|
try {
|
|
|
|
if (!this.getAIEnabled()) {
|
|
|
|
log.info('AI features are disabled');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-04-01 20:38:03 +00:00
|
|
|
// Get provider precedence list
|
|
|
|
const precedenceOption = await options.getOption('embeddingProviderPrecedence');
|
|
|
|
let precedenceList: string[] = [];
|
|
|
|
|
|
|
|
if (precedenceOption) {
|
|
|
|
if (precedenceOption.startsWith('[') && precedenceOption.endsWith(']')) {
|
|
|
|
precedenceList = JSON.parse(precedenceOption);
|
|
|
|
} else if (typeof precedenceOption === 'string') {
|
|
|
|
if (precedenceOption.includes(',')) {
|
|
|
|
precedenceList = precedenceOption.split(',').map(p => p.trim());
|
|
|
|
} else {
|
|
|
|
precedenceList = [precedenceOption];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-03-19 16:19:48 +00:00
|
|
|
|
|
|
|
// Check if we have enabled providers
|
|
|
|
const enabledProviders = await getEnabledEmbeddingProviders();
|
|
|
|
|
|
|
|
if (enabledProviders.length === 0) {
|
|
|
|
log.info('No embedding providers are enabled');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize embedding providers
|
|
|
|
log.info('Embedding providers initialized successfully');
|
|
|
|
} catch (error: any) {
|
|
|
|
log.error(`Error setting up embedding providers: ${error.message}`);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the AI Service
|
|
|
|
*/
|
|
|
|
async initialize(): Promise<void> {
|
|
|
|
try {
|
|
|
|
log.info("Initializing AI service...");
|
|
|
|
|
|
|
|
// Check if AI is enabled in options
|
|
|
|
const isAIEnabled = this.getAIEnabled();
|
|
|
|
|
|
|
|
if (!isAIEnabled) {
|
|
|
|
log.info("AI features are disabled in options");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up embeddings provider if AI is enabled
|
|
|
|
await this.setupEmbeddingsProvider();
|
|
|
|
|
|
|
|
// Initialize index service
|
|
|
|
await this.getIndexService().initialize();
|
|
|
|
|
|
|
|
// Initialize agent tools with this service manager instance
|
|
|
|
await agentTools.initialize(this);
|
2025-04-06 20:50:08 +00:00
|
|
|
|
|
|
|
// Initialize LLM tools - this is the single place where tools are initialized
|
|
|
|
const toolInitializer = await import('./tools/tool_initializer.js');
|
|
|
|
await toolInitializer.default.initializeTools();
|
|
|
|
log.info("LLM tools initialized successfully");
|
2025-03-19 16:19:48 +00:00
|
|
|
|
|
|
|
this.initialized = true;
|
|
|
|
log.info("AI service initialized successfully");
|
|
|
|
} catch (error: any) {
|
|
|
|
log.error(`Error initializing AI service: ${error.message}`);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
2025-03-19 19:28:02 +00:00
|
|
|
|
|
|
|
/**
|
2025-03-28 21:04:12 +00:00
|
|
|
* Get context from agent tools
|
2025-03-19 19:28:02 +00:00
|
|
|
*/
|
|
|
|
async getAgentToolsContext(
|
|
|
|
noteId: string,
|
|
|
|
query: string,
|
|
|
|
showThinking: boolean = false,
|
2025-03-28 21:04:12 +00:00
|
|
|
relevantNotes: NoteSearchResult[] = []
|
2025-03-19 19:28:02 +00:00
|
|
|
): Promise<string> {
|
2025-03-19 20:09:18 +00:00
|
|
|
try {
|
2025-03-28 21:04:12 +00:00
|
|
|
if (!this.getAIEnabled()) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.initializeAgentTools();
|
|
|
|
return await contextService.getAgentToolsContext(noteId, query, showThinking);
|
2025-03-19 20:09:18 +00:00
|
|
|
} catch (error) {
|
2025-03-28 21:04:12 +00:00
|
|
|
log.error(`Error getting agent tools context: ${error}`);
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get AI service for the given provider
|
|
|
|
*/
|
|
|
|
getService(provider?: string): AIService {
|
|
|
|
this.ensureInitialized();
|
|
|
|
|
|
|
|
// If provider is specified, try to use it
|
|
|
|
if (provider && this.services[provider as ServiceProviders]?.isAvailable()) {
|
|
|
|
return this.services[provider as ServiceProviders];
|
2025-03-19 20:09:18 +00:00
|
|
|
}
|
2025-03-28 21:04:12 +00:00
|
|
|
|
|
|
|
// Otherwise, use the first available provider in the configured order
|
|
|
|
for (const providerName of this.providerOrder) {
|
|
|
|
const service = this.services[providerName];
|
|
|
|
if (service.isAvailable()) {
|
|
|
|
return service;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no provider is available, use first one anyway (it will throw an error)
|
|
|
|
// This allows us to show a proper error message rather than "provider not found"
|
|
|
|
return this.services[this.providerOrder[0]];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the preferred provider based on configuration
|
|
|
|
*/
|
|
|
|
getPreferredProvider(): string {
|
|
|
|
this.ensureInitialized();
|
|
|
|
|
|
|
|
// Return the first available provider in the order
|
|
|
|
for (const providerName of this.providerOrder) {
|
|
|
|
if (this.services[providerName].isAvailable()) {
|
|
|
|
return providerName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the first provider as fallback
|
|
|
|
return this.providerOrder[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a specific provider is available
|
|
|
|
*/
|
|
|
|
isProviderAvailable(provider: string): boolean {
|
|
|
|
return this.services[provider as ServiceProviders]?.isAvailable() ?? false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get metadata about a provider
|
|
|
|
*/
|
|
|
|
getProviderMetadata(provider: string): ProviderMetadata | null {
|
|
|
|
const service = this.services[provider as ServiceProviders];
|
|
|
|
if (!service) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
name: provider,
|
|
|
|
capabilities: {
|
|
|
|
chat: true,
|
|
|
|
embeddings: provider !== 'anthropic', // Anthropic doesn't have embeddings
|
|
|
|
streaming: true,
|
|
|
|
functionCalling: provider === 'openai' // Only OpenAI has function calling
|
|
|
|
},
|
|
|
|
models: ['default'], // Placeholder, could be populated from the service
|
|
|
|
defaultModel: 'default'
|
|
|
|
};
|
2025-03-19 19:28:02 +00:00
|
|
|
}
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
// Don't create singleton immediately, use a lazy-loading pattern
|
|
|
|
let instance: AIServiceManager | null = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the AIServiceManager instance (creates it if not already created)
|
|
|
|
*/
|
|
|
|
function getInstance(): AIServiceManager {
|
|
|
|
if (!instance) {
|
|
|
|
instance = new AIServiceManager();
|
|
|
|
}
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default {
|
|
|
|
getInstance,
|
|
|
|
// Also export methods directly for convenience
|
|
|
|
isAnyServiceAvailable(): boolean {
|
|
|
|
return getInstance().isAnyServiceAvailable();
|
|
|
|
},
|
|
|
|
getAvailableProviders() {
|
|
|
|
return getInstance().getAvailableProviders();
|
|
|
|
},
|
|
|
|
async generateChatCompletion(messages: Message[], options: ChatCompletionOptions = {}): Promise<ChatResponse> {
|
|
|
|
return getInstance().generateChatCompletion(messages, options);
|
2025-03-11 18:07:28 +00:00
|
|
|
},
|
2025-03-17 16:23:58 +00:00
|
|
|
// Add validateEmbeddingProviders method
|
|
|
|
async validateEmbeddingProviders(): Promise<string | null> {
|
|
|
|
return getInstance().validateEmbeddingProviders();
|
|
|
|
},
|
2025-03-11 23:26:47 +00:00
|
|
|
// Context and index related methods
|
2025-03-11 18:07:28 +00:00
|
|
|
getContextExtractor() {
|
|
|
|
return getInstance().getContextExtractor();
|
|
|
|
},
|
2025-03-19 19:28:02 +00:00
|
|
|
getContextService() {
|
|
|
|
return getInstance().getContextService();
|
|
|
|
},
|
2025-03-11 23:26:47 +00:00
|
|
|
getIndexService() {
|
|
|
|
return getInstance().getIndexService();
|
2025-03-19 16:19:48 +00:00
|
|
|
},
|
|
|
|
// Agent tools related methods
|
|
|
|
async initializeAgentTools(): Promise<void> {
|
|
|
|
const manager = getInstance();
|
|
|
|
return manager.initializeAgentTools();
|
|
|
|
},
|
|
|
|
getAgentTools() {
|
|
|
|
return getInstance().getAgentTools();
|
|
|
|
},
|
|
|
|
getVectorSearchTool() {
|
|
|
|
return getInstance().getVectorSearchTool();
|
|
|
|
},
|
|
|
|
getNoteNavigatorTool() {
|
|
|
|
return getInstance().getNoteNavigatorTool();
|
|
|
|
},
|
|
|
|
getQueryDecompositionTool() {
|
|
|
|
return getInstance().getQueryDecompositionTool();
|
|
|
|
},
|
|
|
|
getContextualThinkingTool() {
|
|
|
|
return getInstance().getContextualThinkingTool();
|
2025-03-19 19:28:02 +00:00
|
|
|
},
|
|
|
|
async getAgentToolsContext(
|
|
|
|
noteId: string,
|
|
|
|
query: string,
|
|
|
|
showThinking: boolean = false,
|
2025-03-28 21:04:12 +00:00
|
|
|
relevantNotes: NoteSearchResult[] = []
|
2025-03-19 19:28:02 +00:00
|
|
|
): Promise<string> {
|
|
|
|
return getInstance().getAgentToolsContext(
|
|
|
|
noteId,
|
|
|
|
query,
|
|
|
|
showThinking,
|
|
|
|
relevantNotes
|
|
|
|
);
|
2025-03-28 21:04:12 +00:00
|
|
|
},
|
|
|
|
// New methods
|
|
|
|
getService(provider?: string): AIService {
|
|
|
|
return getInstance().getService(provider);
|
|
|
|
},
|
|
|
|
getPreferredProvider(): string {
|
|
|
|
return getInstance().getPreferredProvider();
|
|
|
|
},
|
|
|
|
isProviderAvailable(provider: string): boolean {
|
|
|
|
return getInstance().isProviderAvailable(provider);
|
|
|
|
},
|
|
|
|
getProviderMetadata(provider: string): ProviderMetadata | null {
|
|
|
|
return getInstance().getProviderMetadata(provider);
|
2025-03-09 02:19:26 +00:00
|
|
|
}
|
|
|
|
};
|
2025-03-11 18:39:59 +00:00
|
|
|
|
|
|
|
// Create an instance of ContextExtractor for backward compatibility
|
|
|
|
const contextExtractor = new ContextExtractor();
|