diff --git a/src/services/llm/ai_service_manager.ts b/src/services/llm/ai_service_manager.ts index fbaf7a0b0..c9c0581f3 100644 --- a/src/services/llm/ai_service_manager.ts +++ b/src/services/llm/ai_service_manager.ts @@ -18,6 +18,17 @@ import type { } from './interfaces/ai_service_interfaces.js'; import type { NoteSearchResult } from './interfaces/context_interfaces.js'; +/** + * Interface representing relevant note context + */ +interface NoteContext { + title: string; + content?: string; + noteId?: string; + summary?: string; + score?: number; +} + export class AIServiceManager implements IAIServiceManager { private services: Record = { openai: new OpenAIService(), @@ -31,30 +42,30 @@ export class AIServiceManager implements IAIServiceManager { constructor() { // Initialize provider order immediately this.updateProviderOrder(); - + // Initialize tools immediately this.initializeTools().catch(error => { log.error(`Error initializing LLM tools during AIServiceManager construction: ${error.message || String(error)}`); }); } - + /** * Initialize all LLM tools in one place */ private async initializeTools(): Promise { try { log.info('Initializing LLM tools during AIServiceManager construction...'); - + // Initialize agent tools - await agentTools.initialize(true); + await this.initializeAgentTools(); log.info("Agent tools initialized successfully"); - + // Initialize LLM tools const toolInitializer = await import('./tools/tool_initializer.js'); await toolInitializer.default.initializeTools(); log.info("LLM tools initialized successfully"); - } catch (error: any) { - log.error(`Error initializing tools: ${error.message || String(error)}`); + } catch (error: unknown) { + log.error(`Error initializing tools: ${this.handleError(error)}`); // Don't throw, just log the error to prevent breaking construction } } @@ -463,10 +474,8 @@ export class AIServiceManager implements IAIServiceManager { } /** - * Get agent tools context for a specific note - * This context enriches LLM prompts with tools that can interact with Trilium - * - * @param noteId - The note ID + * Get enhanced context with available agent tools + * @param noteId - The ID of the note * @param query - The user's query * @param showThinking - Whether to show LLM's thinking process * @param relevantNotes - Optional notes already found to be relevant @@ -476,12 +485,12 @@ export class AIServiceManager implements IAIServiceManager { noteId: string, query: string, showThinking: boolean = false, - relevantNotes: Array = [] + relevantNotes: NoteSearchResult[] = [] ): Promise { try { // Create agent tools message const toolsMessage = await this.getAgentToolsDescription(); - + // Agent tools are already initialized in the constructor // No need to initialize them again @@ -508,7 +517,7 @@ export class AIServiceManager implements IAIServiceManager { log.info(`Found ${contextNotes.length} relevant notes for context`); } catch (error) { - log.error(`Failed to find relevant notes: ${error}`); + log.error(`Failed to find relevant notes: ${this.handleError(error)}`); // Continue without context notes contextNotes = []; } @@ -526,7 +535,7 @@ export class AIServiceManager implements IAIServiceManager { // Combine tool message with context return toolsMessage + contextStr; } catch (error) { - log.error(`Error getting agent tools context: ${error}`); + log.error(`Error getting agent tools context: ${this.handleError(error)}`); return ""; } } @@ -600,6 +609,16 @@ export class AIServiceManager implements IAIServiceManager { defaultModel: 'default' }; } + + /** + * Error handler that properly types the error object + */ + private handleError(error: unknown): string { + if (error instanceof Error) { + return error.message || String(error); + } + return String(error); + } } // Don't create singleton immediately, use a lazy-loading pattern @@ -662,7 +681,7 @@ export default { noteId: string, query: string, showThinking: boolean = false, - relevantNotes: Array = [] + relevantNotes: NoteSearchResult[] = [] ): Promise { return getInstance().getAgentToolsContext( noteId, diff --git a/src/services/llm/chat_service.ts b/src/services/llm/chat_service.ts index 2d809a4d9..649fac92a 100644 --- a/src/services/llm/chat_service.ts +++ b/src/services/llm/chat_service.ts @@ -6,17 +6,26 @@ import { ChatPipeline } from './pipeline/chat_pipeline.js'; import type { ChatPipelineConfig, StreamCallback } from './pipeline/interfaces.js'; import aiServiceManager from './ai_service_manager.js'; import type { ChatPipelineInput } from './pipeline/interfaces.js'; +import type { NoteSearchResult } from './interfaces/context_interfaces.js'; // Update the ChatCompletionOptions interface to include the missing properties -// TODO fix declare module './ai_interface.js' { interface ChatCompletionOptions { pipeline?: string; noteId?: string; useAdvancedContext?: boolean; + showThinking?: boolean; + enableTools?: boolean; } } +// Add a type for context extraction result +interface ContextExtractionResult { + context: string; + sources?: NoteSearchResult[]; + thinking?: string; +} + export interface ChatSession { id: string; title: string; @@ -144,7 +153,7 @@ export class ChatService { ...(options || session.options || {}), sessionId: session.id }; - + // Execute the pipeline const response = await pipeline.execute({ messages: session.messages, @@ -156,7 +165,8 @@ export class ChatService { // Add assistant message const assistantMessage: Message = { role: 'assistant', - content: response.text + content: response.text, + tool_calls: response.tool_calls }; session.messages.push(assistantMessage); @@ -168,13 +178,13 @@ export class ChatService { provider: response.provider, usage: response.usage }; - + // If there are tool calls, make sure they're stored in metadata if (response.tool_calls && response.tool_calls.length > 0) { // Let the storage service extract and save tool executions // The tool results are already in the messages } - + // Save the complete conversation with metadata await chatStorageService.updateChat(session.id, session.messages, undefined, metadata); @@ -187,9 +197,9 @@ export class ChatService { return session; - } catch (error: any) { + } catch (error: unknown) { session.isStreaming = false; - console.error('Error in AI chat:', error); + console.error('Error in AI chat:', this.handleError(error)); // Add error message const errorMessage: Message = { @@ -266,7 +276,8 @@ export class ChatService { // Add assistant message const assistantMessage: Message = { role: 'assistant', - content: response.text + content: response.text, + tool_calls: response.tool_calls }; session.messages.push(assistantMessage); @@ -279,13 +290,13 @@ export class ChatService { usage: response.usage, contextNoteId: noteId // Store the note ID used for context }; - + // If there are tool calls, make sure they're stored in metadata if (response.tool_calls && response.tool_calls.length > 0) { // Let the storage service extract and save tool executions // The tool results are already in the messages } - + // Save the complete conversation with metadata await chatStorageService.updateChat(session.id, session.messages, undefined, metadata); @@ -298,9 +309,9 @@ export class ChatService { return session; - } catch (error: any) { + } catch (error: unknown) { session.isStreaming = false; - console.error('Error in context-aware chat:', error); + console.error('Error in context-aware chat:', this.handleError(error)); // Add error message const errorMessage: Message = { @@ -343,7 +354,7 @@ export class ChatService { noteId, query: lastUserMessage, useSmartContext - }); + }) as ContextExtractionResult; const contextMessage: Message = { role: 'user', @@ -351,28 +362,27 @@ export class ChatService { }; session.messages.push(contextMessage); - + // Store the context note id in metadata const metadata = { contextNoteId: noteId }; - + // Check if the context extraction result has sources - // Note: We're adding a defensive check since TypeScript doesn't know about this property - const contextSources = (contextResult as any).sources || []; - if (contextSources && contextSources.length > 0) { - // Convert the sources to the format expected by recordSources - const sources = contextSources.map((source: any) => ({ + if (contextResult.sources && contextResult.sources.length > 0) { + // Convert the sources to match expected format (handling null vs undefined) + const sources = contextResult.sources.map(source => ({ noteId: source.noteId, title: source.title, similarity: source.similarity, - content: source.content + // Replace null with undefined for content + content: source.content === null ? undefined : source.content })); - + // Store these sources in metadata await chatStorageService.recordSources(session.id, sources); } - + await chatStorageService.updateChat(session.id, session.messages, undefined, metadata); return session; @@ -380,11 +390,6 @@ export class ChatService { /** * Add semantically relevant context from a note based on a specific query - * - * @param sessionId - The ID of the chat session - * @param noteId - The ID of the note to add context from - * @param query - The specific query to find relevant information for - * @returns The updated chat session */ async addSemanticNoteContext(sessionId: string, noteId: string, query: string): Promise { const session = await this.getOrCreateSession(sessionId); @@ -404,28 +409,27 @@ export class ChatService { }; session.messages.push(contextMessage); - + // Store the context note id and query in metadata const metadata = { contextNoteId: noteId }; - + // Check if the semantic context extraction result has sources - // Note: We're adding a defensive check since TypeScript doesn't know about this property - const contextSources = (contextResult as any).sources || []; + const contextSources = (contextResult as ContextExtractionResult).sources || []; if (contextSources && contextSources.length > 0) { // Convert the sources to the format expected by recordSources - const sources = contextSources.map((source: any) => ({ + const sources = contextSources.map((source) => ({ noteId: source.noteId, title: source.title, similarity: source.similarity, - content: source.content + content: source.content === null ? undefined : source.content })); - + // Store these sources in metadata await chatStorageService.recordSources(session.id, sources); } - + await chatStorageService.updateChat(session.id, session.messages, undefined, metadata); return session; @@ -456,7 +460,7 @@ export class ChatService { /** * Get pipeline performance metrics */ - getPipelineMetrics(pipelineType: string = 'default'): any { + getPipelineMetrics(pipelineType: string = 'default'): unknown { const pipeline = this.getPipeline(pipelineType); return pipeline.getMetrics(); } @@ -542,11 +546,21 @@ export class ChatService { // If not using advanced context, use direct service call return await service.generateChatCompletion(messages, options); - } catch (error: any) { + } catch (error: unknown) { console.error('Error in generateChatCompletion:', error); throw error; } } + + /** + * Error handler utility + */ + private handleError(error: unknown): string { + if (error instanceof Error) { + return error.message || String(error); + } + return String(error); + } } // Singleton instance diff --git a/src/services/llm/context_extractors/note_navigator_tool.ts b/src/services/llm/context_extractors/note_navigator_tool.ts index 27ec47659..65d980624 100644 --- a/src/services/llm/context_extractors/note_navigator_tool.ts +++ b/src/services/llm/context_extractors/note_navigator_tool.ts @@ -57,6 +57,16 @@ export class NoteNavigatorTool { private maxBreadth: number = SEARCH_CONSTANTS.HIERARCHY.MAX_BREADTH; private maxDepth: number = SEARCH_CONSTANTS.HIERARCHY.MAX_DEPTH; + /** + * Error handler that properly types the error object + */ + private handleError(error: unknown): string { + if (error instanceof Error) { + return error.message || String(error); + } + return String(error); + } + /** * Get detailed information about a note */ @@ -84,8 +94,8 @@ export class NoteNavigatorTool { attributeNames, hasChildren: note.children.length > 0 }; - } catch (error: any) { - log.error(`Error getting note info: ${error.message}`); + } catch (error: unknown) { + log.error(`Error getting note info: ${this.handleError(error)}`); return null; } } @@ -118,8 +128,8 @@ export class NoteNavigatorTool { notePathTitles: titles }; }).sort((a, b) => a.notePath.length - b.notePath.length); // Sort by path length, shortest first - } catch (error: any) { - log.error(`Error getting note paths: ${error.message}`); + } catch (error: unknown) { + log.error(`Error getting note paths: ${this.handleError(error)}`); return []; } } @@ -154,8 +164,8 @@ export class NoteNavigatorTool { } return result; - } catch (error: any) { - log.error(`Error getting note hierarchy: ${error.message}`); + } catch (error: unknown) { + log.error(`Error getting note hierarchy: ${this.handleError(error)}`); return null; } } @@ -185,7 +195,8 @@ export class NoteNavigatorTool { } return result; - } catch (error) { + } catch (error: unknown) { + log.error(`Error in _getHierarchyLevel: ${this.handleError(error)}`); return null; } } @@ -201,8 +212,8 @@ export class NoteNavigatorTool { } return note.ownedAttributes; - } catch (error: any) { - log.error(`Error getting note attributes: ${error.message}`); + } catch (error: unknown) { + log.error(`Error getting note attributes: ${this.handleError(error)}`); return []; } } @@ -280,8 +291,8 @@ export class NoteNavigatorTool { // No path found return null; - } catch (error: any) { - log.error(`Error finding path between notes: ${error.message}`); + } catch (error: unknown) { + log.error(`Error finding path between notes: ${this.handleError(error)}`); return null; } } @@ -312,8 +323,8 @@ export class NoteNavigatorTool { } return results; - } catch (error: any) { - log.error(`Error searching notes by title: ${error.message}`); + } catch (error: unknown) { + log.error(`Error searching notes by title: ${this.handleError(error)}`); return []; } } @@ -338,8 +349,8 @@ export class NoteNavigatorTool { return parents .map(parent => this.getNoteInfo(parent.noteId)) .filter((info): info is NoteInfo => info !== null); - } catch (error: any) { - log.error(`Error getting note clones: ${error.message}`); + } catch (error: unknown) { + log.error(`Error getting note clones: ${this.handleError(error)}`); return []; } } @@ -429,8 +440,8 @@ export class NoteNavigatorTool { } return result; - } catch (error: any) { - log.error(`Error getting note context: ${error.message}`); + } catch (error: unknown) { + log.error(`Error getting note context: ${this.handleError(error)}`); return "Error generating note context description."; } } @@ -503,8 +514,8 @@ export class NoteNavigatorTool { attributes, parentPath }; - } catch (error) { - log.error(`Error getting note structure: ${error}`); + } catch (error: unknown) { + log.error(`Error getting note structure: ${this.handleError(error)}`); // Return a minimal structure with empty arrays to avoid null errors return { noteId, @@ -534,8 +545,8 @@ export class NoteNavigatorTool { noteId: child.noteId, title: child.title })); - } catch (error) { - log.error(`Error getting child notes: ${error}`); + } catch (error: unknown) { + log.error(`Error getting child notes: ${this.handleError(error)}`); return []; } } @@ -555,8 +566,8 @@ export class NoteNavigatorTool { noteId: parent.noteId, title: parent.title })); - } catch (error) { - log.error(`Error getting parent notes: ${error}`); + } catch (error: unknown) { + log.error(`Error getting parent notes: ${this.handleError(error)}`); return []; } } @@ -613,8 +624,8 @@ export class NoteNavigatorTool { } return [...outboundLinks, ...inboundLinks.slice(0, Math.floor(limit / 2))]; - } catch (error) { - log.error(`Error getting linked notes: ${error}`); + } catch (error: unknown) { + log.error(`Error getting linked notes: ${this.handleError(error)}`); return []; } } @@ -629,8 +640,8 @@ export class NoteNavigatorTool { path.push(structure.title); return path.join(' > '); - } catch (error) { - log.error(`Error getting note path: ${error}`); + } catch (error: unknown) { + log.error(`Error getting note path: ${this.handleError(error)}`); return 'Unknown path'; } } diff --git a/src/services/llm/tools/tool_interfaces.ts b/src/services/llm/tools/tool_interfaces.ts index 9cff91f46..12c08a7b0 100644 --- a/src/services/llm/tools/tool_interfaces.ts +++ b/src/services/llm/tools/tool_interfaces.ts @@ -42,7 +42,7 @@ export interface ToolCall { type?: string; function: { name: string; - arguments: Record | string; + arguments: Record | string; }; } @@ -58,5 +58,5 @@ export interface ToolHandler { /** * Execute the tool with the given arguments */ - execute(args: Record): Promise; + execute(args: Record): Promise; }