diff --git a/src/routes/api/llm.ts b/src/routes/api/llm.ts index 4f4a226e5..ed7108bff 100644 --- a/src/routes/api/llm.ts +++ b/src/routes/api/llm.ts @@ -45,10 +45,10 @@ interface SessionOptions { /** * @swagger - * /api/llm/sessions: + * /api/llm/chat: * post: - * summary: Create a new LLM chat session - * operationId: llm-create-session + * summary: Create a new LLM chat + * operationId: llm-create-chat * requestBody: * required: true * content: @@ -58,7 +58,7 @@ interface SessionOptions { * properties: * title: * type: string - * description: Title for the chat session + * description: Title for the chat * systemPrompt: * type: string * description: System message to set the behavior of the assistant @@ -76,16 +76,16 @@ interface SessionOptions { * description: LLM provider to use (e.g., 'openai', 'anthropic', 'ollama') * contextNoteId: * type: string - * description: Note ID to use as context for the session + * description: Note ID to use as context for the chat * responses: * '200': - * description: Successfully created session + * description: Successfully created chat * content: * application/json: * schema: * type: object * properties: - * sessionId: + * chatNoteId: * type: string * title: * type: string @@ -96,25 +96,25 @@ interface SessionOptions { * - session: [] * tags: ["llm"] */ -async function createSession(req: Request, res: Response) { +async function createChat(req: Request, res: Response) { return restChatService.createSession(req, res); } /** * @swagger - * /api/llm/sessions/{sessionId}: + * /api/llm/chat/{chatNoteId}: * get: - * summary: Retrieve a specific chat session by ID - * operationId: llm-get-session + * summary: Retrieve a specific chat by ID + * operationId: llm-get-chat * parameters: - * - name: sessionId + * - name: chatNoteId * in: path * required: true * schema: * type: string * responses: * '200': - * description: Chat session details + * description: Chat details * content: * application/json: * schema: @@ -144,23 +144,23 @@ async function createSession(req: Request, res: Response) { * type: string * format: date-time * '404': - * description: Session not found + * description: Chat not found * security: * - session: [] * tags: ["llm"] */ -async function getSession(req: Request, res: Response) { +async function getChat(req: Request, res: Response) { return restChatService.getSession(req, res); } /** * @swagger - * /api/llm/sessions/{sessionId}: + * /api/llm/chat/{chatNoteId}: * patch: - * summary: Update a chat session's settings - * operationId: llm-update-session + * summary: Update a chat's settings + * operationId: llm-update-chat * parameters: - * - name: sessionId + * - name: chatNoteId * in: path * required: true * schema: @@ -174,7 +174,7 @@ async function getSession(req: Request, res: Response) { * properties: * title: * type: string - * description: Updated title for the session + * description: Updated title for the chat * systemPrompt: * type: string * description: Updated system prompt @@ -195,7 +195,7 @@ async function getSession(req: Request, res: Response) { * description: Updated note ID for context * responses: * '200': - * description: Session successfully updated + * description: Chat successfully updated * content: * application/json: * schema: @@ -209,46 +209,46 @@ async function getSession(req: Request, res: Response) { * type: string * format: date-time * '404': - * description: Session not found + * description: Chat not found * security: * - session: [] * tags: ["llm"] */ -async function updateSession(req: Request, res: Response) { - // Get the session using ChatService - const sessionId = req.params.sessionId; +async function updateChat(req: Request, res: Response) { + // Get the chat using ChatService + const chatNoteId = req.params.chatNoteId; const updates = req.body; try { - // Get the session - const session = await chatService.getOrCreateSession(sessionId); + // Get the chat + const session = await chatService.getOrCreateSession(chatNoteId); // Update title if provided if (updates.title) { - await chatStorageService.updateChat(sessionId, session.messages, updates.title); + await chatStorageService.updateChat(chatNoteId, session.messages, updates.title); } - // Return the updated session + // Return the updated chat return { - id: sessionId, + id: chatNoteId, title: updates.title || session.title, updatedAt: new Date() }; } catch (error) { - log.error(`Error updating session: ${error}`); - throw new Error(`Failed to update session: ${error}`); + log.error(`Error updating chat: ${error}`); + throw new Error(`Failed to update chat: ${error}`); } } /** * @swagger - * /api/llm/sessions: + * /api/llm/chat: * get: - * summary: List all chat sessions - * operationId: llm-list-sessions + * summary: List all chats + * operationId: llm-list-chats * responses: * '200': - * description: List of chat sessions + * description: List of chats * content: * application/json: * schema: @@ -272,14 +272,14 @@ async function updateSession(req: Request, res: Response) { * - session: [] * tags: ["llm"] */ -async function listSessions(req: Request, res: Response) { - // Get all sessions using ChatService +async function listChats(req: Request, res: Response) { + // Get all chats using ChatService try { const sessions = await chatService.getAllSessions(); // Format the response return { - sessions: sessions.map(session => ({ + chats: sessions.map(session => ({ id: session.id, title: session.title, createdAt: new Date(), // Since we don't have this in chat sessions @@ -288,44 +288,44 @@ async function listSessions(req: Request, res: Response) { })) }; } catch (error) { - log.error(`Error listing sessions: ${error}`); - throw new Error(`Failed to list sessions: ${error}`); + log.error(`Error listing chats: ${error}`); + throw new Error(`Failed to list chats: ${error}`); } } /** * @swagger - * /api/llm/sessions/{sessionId}: + * /api/llm/chat/{chatNoteId}: * delete: - * summary: Delete a chat session - * operationId: llm-delete-session + * summary: Delete a chat + * operationId: llm-delete-chat * parameters: - * - name: sessionId + * - name: chatNoteId * in: path * required: true * schema: * type: string * responses: * '200': - * description: Session successfully deleted + * description: Chat successfully deleted * '404': - * description: Session not found + * description: Chat not found * security: * - session: [] * tags: ["llm"] */ -async function deleteSession(req: Request, res: Response) { +async function deleteChat(req: Request, res: Response) { return restChatService.deleteSession(req, res); } /** * @swagger - * /api/llm/sessions/{sessionId}/messages: + * /api/llm/chat/{chatNoteId}/messages: * post: * summary: Send a message to an LLM and get a response * operationId: llm-send-message * parameters: - * - name: sessionId + * - name: chatNoteId * in: path * required: true * schema: @@ -357,7 +357,7 @@ async function deleteSession(req: Request, res: Response) { * description: Whether to include relevant notes as context * useNoteContext: * type: boolean - * description: Whether to use the session's context note + * description: Whether to use the chat's context note * responses: * '200': * description: LLM response @@ -379,10 +379,10 @@ async function deleteSession(req: Request, res: Response) { * type: string * similarity: * type: number - * sessionId: + * chatNoteId: * type: string * '404': - * description: Session not found + * description: Chat not found * '500': * description: Error processing request * security: @@ -393,6 +393,175 @@ async function sendMessage(req: Request, res: Response) { return restChatService.handleSendMessage(req, res); } +/** + * @swagger + * /api/llm/chat/{chatNoteId}/messages/stream: + * post: + * summary: Start a streaming response via WebSockets + * operationId: llm-stream-message + * parameters: + * - name: chatNoteId + * in: path + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * content: + * type: string + * description: The user message to send to the LLM + * useAdvancedContext: + * type: boolean + * description: Whether to use advanced context extraction + * showThinking: + * type: boolean + * description: Whether to show thinking process in the response + * responses: + * '200': + * description: Streaming started successfully + * '404': + * description: Chat not found + * '500': + * description: Error processing request + * security: + * - session: [] + * tags: ["llm"] + */ +async function streamMessage(req: Request, res: Response) { + log.info("=== Starting streamMessage ==="); + try { + const chatNoteId = req.params.chatNoteId; + const { content, useAdvancedContext, showThinking } = req.body; + + if (!content || typeof content !== 'string' || content.trim().length === 0) { + throw new Error('Content cannot be empty'); + } + + // Check if session exists in memory + let session = restChatService.getSessions().get(chatNoteId); + + // If session doesn't exist in memory, try to create it from the Chat Note + if (!session) { + log.info(`Session not found in memory for Chat Note ${chatNoteId}, attempting to create from Chat Note`); + + const restoredSession = await restChatService.createSessionFromChatNote(chatNoteId); + + if (!restoredSession) { + // If we can't find the Chat Note, then it's truly not found + log.error(`Chat Note ${chatNoteId} not found, cannot create session`); + throw new Error('Chat Note not found, cannot create session for streaming'); + } + + session = restoredSession; + } + + // Update last active timestamp + session.lastActive = new Date(); + + // Add user message to the session + session.messages.push({ + role: 'user', + content, + timestamp: new Date() + }); + + // Create request parameters for the pipeline + const requestParams = { + chatNoteId, + content, + useAdvancedContext: useAdvancedContext === true, + showThinking: showThinking === true, + stream: true // Always stream for this endpoint + }; + + // Create a fake request/response pair to pass to the handler + const fakeReq = { + ...req, + method: 'GET', // Set to GET to indicate streaming + query: { + stream: 'true', // Set stream param - don't use format: 'stream' to avoid confusion + useAdvancedContext: String(useAdvancedContext === true), + showThinking: String(showThinking === true) + }, + params: { + chatNoteId + }, + // Make sure the original content is available to the handler + body: { + content, + useAdvancedContext: useAdvancedContext === true, + showThinking: showThinking === true + } + } as unknown as Request; + + // Log to verify correct parameters + log.info(`WebSocket stream settings - useAdvancedContext=${useAdvancedContext === true}, in query=${fakeReq.query.useAdvancedContext}, in body=${fakeReq.body.useAdvancedContext}`); + // Extra safety to ensure the parameters are passed correctly + if (useAdvancedContext === true) { + log.info(`Enhanced context IS enabled for this request`); + } else { + log.info(`Enhanced context is NOT enabled for this request`); + } + + // Process the request in the background + Promise.resolve().then(async () => { + try { + await restChatService.handleSendMessage(fakeReq, res); + } catch (error) { + log.error(`Background message processing error: ${error}`); + + // Import the WebSocket service + const wsService = (await import('../../services/ws.js')).default; + + // Define LLMStreamMessage interface + interface LLMStreamMessage { + type: 'llm-stream'; + sessionId: string; // Keep this as sessionId for WebSocket compatibility + content?: string; + thinking?: string; + toolExecution?: any; + done?: boolean; + error?: string; + raw?: unknown; + } + + // Send error to client via WebSocket + wsService.sendMessageToAllClients({ + type: 'llm-stream', + sessionId: chatNoteId, // Use sessionId property, but pass the chatNoteId + error: `Error processing message: ${error}`, + done: true + } as LLMStreamMessage); + } + }); + + // Import the WebSocket service + const wsService = (await import('../../services/ws.js')).default; + + // Let the client know streaming has started via WebSocket (helps client confirm connection is working) + wsService.sendMessageToAllClients({ + type: 'llm-stream', + sessionId: chatNoteId, + thinking: 'Initializing streaming LLM response...' + }); + + // Let the client know streaming has started via HTTP response + return { + success: true, + message: 'Streaming started', + sessionId: chatNoteId // Keep using sessionId for API response compatibility + }; + } catch (error: any) { + log.error(`Error starting message stream: ${error.message}`); + throw error; + } +} + /** * @swagger * /api/llm/indexes/stats: @@ -788,182 +957,13 @@ async function indexNote(req: Request, res: Response) { } } -/** - * @swagger - * /api/llm/sessions/{sessionId}/messages/stream: - * post: - * summary: Start a streaming response session via WebSockets - * operationId: llm-stream-message - * parameters: - * - name: sessionId - * in: path - * required: true - * schema: - * type: string - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * content: - * type: string - * description: The user message to send to the LLM - * useAdvancedContext: - * type: boolean - * description: Whether to use advanced context extraction - * showThinking: - * type: boolean - * description: Whether to show thinking process in the response - * responses: - * '200': - * description: Streaming started successfully - * '404': - * description: Session not found - * '500': - * description: Error processing request - * security: - * - session: [] - * tags: ["llm"] - */ -async function streamMessage(req: Request, res: Response) { - log.info("=== Starting streamMessage ==="); - try { - const sessionId = req.params.sessionId; - const { content, useAdvancedContext, showThinking } = req.body; - - if (!content || typeof content !== 'string' || content.trim().length === 0) { - throw new Error('Content cannot be empty'); - } - - // Check if session exists in memory - let session = restChatService.getSessions().get(sessionId); - - // If session doesn't exist in memory, try to recreate it from the Chat Note - if (!session) { - log.info(`Session ${sessionId} not found in memory, attempting to restore from Chat Note`); - - const restoredSession = await restChatService.restoreSessionFromChatNote(sessionId); - - if (!restoredSession) { - // If we can't find the Chat Note either, then it's truly not found - log.error(`Chat Note ${sessionId} not found, cannot restore session`); - throw new Error('Session not found and no corresponding Chat Note exists'); - } - - session = restoredSession; - } - - // Update last active timestamp - session.lastActive = new Date(); - - // Add user message to the session - session.messages.push({ - role: 'user', - content, - timestamp: new Date() - }); - - // Create request parameters for the pipeline - const requestParams = { - sessionId, - content, - useAdvancedContext: useAdvancedContext === true, - showThinking: showThinking === true, - stream: true // Always stream for this endpoint - }; - - // Create a fake request/response pair to pass to the handler - const fakeReq = { - ...req, - method: 'GET', // Set to GET to indicate streaming - query: { - stream: 'true', // Set stream param - don't use format: 'stream' to avoid confusion - useAdvancedContext: String(useAdvancedContext === true), - showThinking: String(showThinking === true) - }, - params: { - sessionId - }, - // Make sure the original content is available to the handler - body: { - content, - useAdvancedContext: useAdvancedContext === true, - showThinking: showThinking === true - } - } as unknown as Request; - - // Log to verify correct parameters - log.info(`WebSocket stream settings - useAdvancedContext=${useAdvancedContext === true}, in query=${fakeReq.query.useAdvancedContext}, in body=${fakeReq.body.useAdvancedContext}`); - // Extra safety to ensure the parameters are passed correctly - if (useAdvancedContext === true) { - log.info(`Enhanced context IS enabled for this request`); - } else { - log.info(`Enhanced context is NOT enabled for this request`); - } - - // Process the request in the background - Promise.resolve().then(async () => { - try { - await restChatService.handleSendMessage(fakeReq, res); - } catch (error) { - log.error(`Background message processing error: ${error}`); - - // Import the WebSocket service - const wsService = (await import('../../services/ws.js')).default; - - // Define LLMStreamMessage interface - interface LLMStreamMessage { - type: 'llm-stream'; - sessionId: string; - content?: string; - thinking?: string; - toolExecution?: any; - done?: boolean; - error?: string; - raw?: unknown; - } - - // Send error to client via WebSocket - wsService.sendMessageToAllClients({ - type: 'llm-stream', - sessionId, - error: `Error processing message: ${error}`, - done: true - } as LLMStreamMessage); - } - }); - - // Import the WebSocket service - const wsService = (await import('../../services/ws.js')).default; - - // Let the client know streaming has started via WebSocket (helps client confirm connection is working) - wsService.sendMessageToAllClients({ - type: 'llm-stream', - sessionId, - thinking: 'Initializing streaming LLM response...' - }); - - // Let the client know streaming has started via HTTP response - return { - success: true, - message: 'Streaming started', - sessionId - }; - } catch (error: any) { - log.error(`Error starting message stream: ${error.message}`); - throw error; - } -} - export default { - // Chat session management - createSession, - getSession, - updateSession, - listSessions, - deleteSession, + // Chat management + createChat, + getChat, + updateChat, + listChats, + deleteChat, sendMessage, streamMessage, diff --git a/src/services/llm/chat/rest_chat_service.ts b/src/services/llm/chat/rest_chat_service.ts index 7a2b476b7..ac5fabc0e 100644 --- a/src/services/llm/chat/rest_chat_service.ts +++ b/src/services/llm/chat/rest_chat_service.ts @@ -84,7 +84,7 @@ class RestChatService { log.info("=== Starting handleSendMessage ==="); try { // Extract parameters differently based on the request method - let content, useAdvancedContext, showThinking, sessionId; + let content, useAdvancedContext, showThinking, chatNoteId; if (req.method === 'POST') { // For POST requests, get content from the request body @@ -94,7 +94,7 @@ class RestChatService { showThinking = requestBody.showThinking || false; // Add logging for POST requests - log.info(`LLM POST message: sessionId=${req.params.sessionId}, useAdvancedContext=${useAdvancedContext}, showThinking=${showThinking}, contentLength=${content ? content.length : 0}`); + log.info(`LLM POST message: chatNoteId=${req.params.chatNoteId}, useAdvancedContext=${useAdvancedContext}, showThinking=${showThinking}, contentLength=${content ? content.length : 0}`); } else if (req.method === 'GET') { // For GET (streaming) requests, get parameters from query params and body // For streaming requests, we need the content from the body @@ -103,13 +103,13 @@ class RestChatService { content = req.body && req.body.content ? req.body.content : ''; // Add detailed logging for GET requests - log.info(`LLM GET stream: sessionId=${req.params.sessionId}, useAdvancedContext=${useAdvancedContext}, showThinking=${showThinking}`); + log.info(`LLM GET stream: chatNoteId=${req.params.chatNoteId}, useAdvancedContext=${useAdvancedContext}, showThinking=${showThinking}`); log.info(`Parameters from query: useAdvancedContext=${req.query.useAdvancedContext}, showThinking=${req.query.showThinking}`); log.info(`Parameters from body: useAdvancedContext=${req.body?.useAdvancedContext}, showThinking=${req.body?.showThinking}, content=${content ? `${content.substring(0, 20)}...` : 'none'}`); } - // Get sessionId from URL params since it's part of the route - sessionId = req.params.sessionId; + // Get chatNoteId from URL params since it's part of the route + chatNoteId = req.params.chatNoteId || req.params.sessionId; // Support both names for backward compatibility // For GET requests, ensure we have the stream parameter if (req.method === 'GET' && req.query.stream !== 'true') { @@ -121,46 +121,48 @@ class RestChatService { throw new Error('Content cannot be empty'); } - // Check if session exists, create one if not - let session = SessionsStore.getSession(sessionId); + // Get or create session from Chat Note + let session = await this.getOrCreateSessionFromChatNote(chatNoteId, req.method === 'POST'); + // If no session found and we're not allowed to create one (GET request) + if (!session && req.method === 'GET') { + throw new Error('Chat Note not found, cannot create session for streaming'); + } + + // For POST requests, if no Chat Note exists, create a new one + if (!session && req.method === 'POST') { + log.info(`No Chat Note found for ${chatNoteId}, creating a new Chat Note and session`); + + // Create a new Chat Note via the storage service + const chatStorageService = (await import('../../llm/chat_storage_service.js')).default; + const newChat = await chatStorageService.createChat('New Chat'); + + // Use the new Chat Note's ID for the session + session = SessionsStore.createSession({ + title: newChat.title + }); + + // Update the session ID to match the Chat Note ID + session.id = newChat.id; + + log.info(`Created new Chat Note and session with ID: ${session.id}`); + + // Update the parameter to use the new ID + chatNoteId = session.id; + } + + // At this point, session should never be null + // TypeScript doesn't know this, so we'll add a check if (!session) { - if (req.method === 'GET') { - // For GET requests, we should try to restore from Chat Note - log.info(`Session ${sessionId} not found in memory for GET request, attempting to restore from Chat Note`); - - const restoredSession = await this.restoreSessionFromChatNote(sessionId); - - if (!restoredSession) { - // If we still can't find the Chat Note, throw error - throw new Error('Session not found and no corresponding Chat Note exists'); - } - - session = restoredSession; - } else { - // For POST requests, we can create a new session, or try to restore from Chat Note - log.info(`Session ${sessionId} not found for POST request, checking if Chat Note exists`); - - const restoredSession = await this.restoreSessionFromChatNote(sessionId); - - if (!restoredSession) { - // If we can't find a Chat Note, create a new session - log.info(`No Chat Note found for ${sessionId}, creating a new session`); - session = SessionsStore.createSession({ - title: 'Auto-created Session' - }); - log.info(`Created new session with ID: ${session.id}`); - } else { - session = restoredSession; - } - } + // This should never happen due to our logic above + throw new Error('Failed to create or retrieve session'); } // Update session last active timestamp SessionsStore.touchSession(session.id); // For POST requests, store the user message - if (req.method === 'POST' && content) { + if (req.method === 'POST' && content && session) { // Add message to session session.messages.push({ role: 'user', @@ -261,15 +263,15 @@ class RestChatService { const pipelineOptions = { // Force useAdvancedContext to be a boolean, no matter what useAdvancedContext: useAdvancedContext === true, - systemPrompt: session.messages.find(m => m.role === 'system')?.content, - temperature: session.metadata.temperature, - maxTokens: session.metadata.maxTokens, - model: session.metadata.model, + systemPrompt: session?.messages.find(m => m.role === 'system')?.content, + temperature: session?.metadata.temperature, + maxTokens: session?.metadata.maxTokens, + model: session?.metadata.model, // Set stream based on request type, but ensure it's explicitly a boolean value // GET requests or format=stream parameter indicates streaming should be used stream: !!(req.method === 'GET' || req.query.format === 'stream' || req.query.stream === 'true'), - // Include sessionId for tracking tool executions - sessionId: sessionId + // Include chatNoteId for tracking tool executions + sessionId: chatNoteId // Use sessionId property for backward compatibility }; // Log the options to verify what's being sent to the pipeline @@ -300,7 +302,7 @@ class RestChatService { // Use WebSocket service to send messages this.handleStreamCallback( data, done, rawChunk, - wsService.default, sessionId, + wsService.default, chatNoteId, messageContent, session, res ); } catch (error) { @@ -310,7 +312,7 @@ class RestChatService { try { wsService.default.sendMessageToAllClients({ type: 'llm-stream', - sessionId, + sessionId: chatNoteId, // Use sessionId property for backward compatibility error: `Stream error: ${error instanceof Error ? error.message : 'Unknown error'}`, done: true }); @@ -379,7 +381,7 @@ class RestChatService { done: boolean, rawChunk: any, wsService: any, - sessionId: string, + chatNoteId: string, messageContent: string, session: any, res: Response @@ -392,7 +394,7 @@ class RestChatService { // Create a message object with all necessary fields const message: LLMStreamMessage = { type: 'llm-stream', - sessionId + sessionId: chatNoteId // Use sessionId property for backward compatibility }; // Add content if available - either the new chunk or full content on completion @@ -445,7 +447,7 @@ class RestChatService { // Log what was sent (first message and completion) if (message.thinking || done) { log.info( - `[WS-SERVER] Sending LLM stream message: sessionId=${sessionId}, content=${!!message.content}, contentLength=${message.content?.length || 0}, thinking=${!!message.thinking}, toolExecution=${!!message.toolExecution}, done=${done}` + `[WS-SERVER] Sending LLM stream message: chatNoteId=${chatNoteId}, content=${!!message.content}, contentLength=${message.content?.length || 0}, thinking=${!!message.thinking}, toolExecution=${!!message.toolExecution}, done=${done}` ); } @@ -577,25 +579,25 @@ class RestChatService { } /** - * Restore a session from a Chat Note - * This is used when a session doesn't exist in memory but there's a corresponding Chat Note + * Create an in-memory session from a Chat Note + * This treats the Chat Note as the source of truth, using its ID as the session ID */ - async restoreSessionFromChatNote(sessionId: string): Promise { + async createSessionFromChatNote(noteId: string): Promise { try { - log.info(`Attempting to restore session ${sessionId} from Chat Note`); + log.info(`Creating in-memory session for Chat Note ID ${noteId}`); // Import chat storage service const chatStorageService = (await import('../../llm/chat_storage_service.js')).default; // Try to get the Chat Note data - const chatNote = await chatStorageService.getChat(sessionId); + const chatNote = await chatStorageService.getChat(noteId); if (!chatNote) { - log.error(`Chat Note ${sessionId} not found, cannot restore session`); + log.error(`Chat Note ${noteId} not found, cannot create session`); return null; } - log.info(`Found Chat Note ${sessionId}, recreating session from it`); + log.info(`Found Chat Note ${noteId}, creating in-memory session`); // Convert Message[] to ChatMessage[] by ensuring the role is compatible const chatMessages: ChatMessage[] = chatNote.messages.map(msg => ({ @@ -606,7 +608,7 @@ class RestChatService { // Create a new session with the same ID as the Chat Note const session: ChatSession = { - id: chatNote.id, + id: chatNote.id, // Use Chat Note ID as the session ID title: chatNote.title, messages: chatMessages, createdAt: chatNote.createdAt || new Date(), @@ -615,15 +617,41 @@ class RestChatService { }; // Add the session to the in-memory store - SessionsStore.getAllSessions().set(sessionId, session); + SessionsStore.getAllSessions().set(noteId, session); - log.info(`Successfully restored session ${sessionId} from Chat Note`); + log.info(`Successfully created in-memory session for Chat Note ${noteId}`); return session; } catch (error) { - log.error(`Failed to restore session from Chat Note: ${error}`); + log.error(`Failed to create session from Chat Note: ${error}`); return null; } } + + /** + * Get an existing session or create a new one from a Chat Note + * This treats the Chat Note as the source of truth, using its ID as the session ID + */ + async getOrCreateSessionFromChatNote(noteId: string, createIfNotFound: boolean = true): Promise { + // First check if we already have this session in memory + let session = SessionsStore.getSession(noteId); + + if (session) { + log.info(`Found existing in-memory session for Chat Note ${noteId}`); + return session; + } + + // If not in memory, try to create from Chat Note + log.info(`Session not found in memory for Chat Note ${noteId}, attempting to create it`); + + // Only try to create if allowed + if (!createIfNotFound) { + log.info(`Not creating new session for ${noteId} as createIfNotFound=false`); + return null; + } + + // Create from Chat Note + return await this.createSessionFromChatNote(noteId); + } } // Create singleton instance