decouple sessions even further

i think this is a better idea?
This commit is contained in:
perf3ct 2025-04-16 21:28:17 +00:00
parent 06924aad59
commit cdc84f1cef
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
2 changed files with 315 additions and 287 deletions

View File

@ -45,10 +45,10 @@ interface SessionOptions {
/** /**
* @swagger * @swagger
* /api/llm/sessions: * /api/llm/chat:
* post: * post:
* summary: Create a new LLM chat session * summary: Create a new LLM chat
* operationId: llm-create-session * operationId: llm-create-chat
* requestBody: * requestBody:
* required: true * required: true
* content: * content:
@ -58,7 +58,7 @@ interface SessionOptions {
* properties: * properties:
* title: * title:
* type: string * type: string
* description: Title for the chat session * description: Title for the chat
* systemPrompt: * systemPrompt:
* type: string * type: string
* description: System message to set the behavior of the assistant * 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') * description: LLM provider to use (e.g., 'openai', 'anthropic', 'ollama')
* contextNoteId: * contextNoteId:
* type: string * type: string
* description: Note ID to use as context for the session * description: Note ID to use as context for the chat
* responses: * responses:
* '200': * '200':
* description: Successfully created session * description: Successfully created chat
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: object * type: object
* properties: * properties:
* sessionId: * chatNoteId:
* type: string * type: string
* title: * title:
* type: string * type: string
@ -96,25 +96,25 @@ interface SessionOptions {
* - session: [] * - session: []
* tags: ["llm"] * tags: ["llm"]
*/ */
async function createSession(req: Request, res: Response) { async function createChat(req: Request, res: Response) {
return restChatService.createSession(req, res); return restChatService.createSession(req, res);
} }
/** /**
* @swagger * @swagger
* /api/llm/sessions/{sessionId}: * /api/llm/chat/{chatNoteId}:
* get: * get:
* summary: Retrieve a specific chat session by ID * summary: Retrieve a specific chat by ID
* operationId: llm-get-session * operationId: llm-get-chat
* parameters: * parameters:
* - name: sessionId * - name: chatNoteId
* in: path * in: path
* required: true * required: true
* schema: * schema:
* type: string * type: string
* responses: * responses:
* '200': * '200':
* description: Chat session details * description: Chat details
* content: * content:
* application/json: * application/json:
* schema: * schema:
@ -144,23 +144,23 @@ async function createSession(req: Request, res: Response) {
* type: string * type: string
* format: date-time * format: date-time
* '404': * '404':
* description: Session not found * description: Chat not found
* security: * security:
* - session: [] * - session: []
* tags: ["llm"] * tags: ["llm"]
*/ */
async function getSession(req: Request, res: Response) { async function getChat(req: Request, res: Response) {
return restChatService.getSession(req, res); return restChatService.getSession(req, res);
} }
/** /**
* @swagger * @swagger
* /api/llm/sessions/{sessionId}: * /api/llm/chat/{chatNoteId}:
* patch: * patch:
* summary: Update a chat session's settings * summary: Update a chat's settings
* operationId: llm-update-session * operationId: llm-update-chat
* parameters: * parameters:
* - name: sessionId * - name: chatNoteId
* in: path * in: path
* required: true * required: true
* schema: * schema:
@ -174,7 +174,7 @@ async function getSession(req: Request, res: Response) {
* properties: * properties:
* title: * title:
* type: string * type: string
* description: Updated title for the session * description: Updated title for the chat
* systemPrompt: * systemPrompt:
* type: string * type: string
* description: Updated system prompt * description: Updated system prompt
@ -195,7 +195,7 @@ async function getSession(req: Request, res: Response) {
* description: Updated note ID for context * description: Updated note ID for context
* responses: * responses:
* '200': * '200':
* description: Session successfully updated * description: Chat successfully updated
* content: * content:
* application/json: * application/json:
* schema: * schema:
@ -209,46 +209,46 @@ async function getSession(req: Request, res: Response) {
* type: string * type: string
* format: date-time * format: date-time
* '404': * '404':
* description: Session not found * description: Chat not found
* security: * security:
* - session: [] * - session: []
* tags: ["llm"] * tags: ["llm"]
*/ */
async function updateSession(req: Request, res: Response) { async function updateChat(req: Request, res: Response) {
// Get the session using ChatService // Get the chat using ChatService
const sessionId = req.params.sessionId; const chatNoteId = req.params.chatNoteId;
const updates = req.body; const updates = req.body;
try { try {
// Get the session // Get the chat
const session = await chatService.getOrCreateSession(sessionId); const session = await chatService.getOrCreateSession(chatNoteId);
// Update title if provided // Update title if provided
if (updates.title) { 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 { return {
id: sessionId, id: chatNoteId,
title: updates.title || session.title, title: updates.title || session.title,
updatedAt: new Date() updatedAt: new Date()
}; };
} catch (error) { } catch (error) {
log.error(`Error updating session: ${error}`); log.error(`Error updating chat: ${error}`);
throw new Error(`Failed to update session: ${error}`); throw new Error(`Failed to update chat: ${error}`);
} }
} }
/** /**
* @swagger * @swagger
* /api/llm/sessions: * /api/llm/chat:
* get: * get:
* summary: List all chat sessions * summary: List all chats
* operationId: llm-list-sessions * operationId: llm-list-chats
* responses: * responses:
* '200': * '200':
* description: List of chat sessions * description: List of chats
* content: * content:
* application/json: * application/json:
* schema: * schema:
@ -272,14 +272,14 @@ async function updateSession(req: Request, res: Response) {
* - session: [] * - session: []
* tags: ["llm"] * tags: ["llm"]
*/ */
async function listSessions(req: Request, res: Response) { async function listChats(req: Request, res: Response) {
// Get all sessions using ChatService // Get all chats using ChatService
try { try {
const sessions = await chatService.getAllSessions(); const sessions = await chatService.getAllSessions();
// Format the response // Format the response
return { return {
sessions: sessions.map(session => ({ chats: sessions.map(session => ({
id: session.id, id: session.id,
title: session.title, title: session.title,
createdAt: new Date(), // Since we don't have this in chat sessions 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) { } catch (error) {
log.error(`Error listing sessions: ${error}`); log.error(`Error listing chats: ${error}`);
throw new Error(`Failed to list sessions: ${error}`); throw new Error(`Failed to list chats: ${error}`);
} }
} }
/** /**
* @swagger * @swagger
* /api/llm/sessions/{sessionId}: * /api/llm/chat/{chatNoteId}:
* delete: * delete:
* summary: Delete a chat session * summary: Delete a chat
* operationId: llm-delete-session * operationId: llm-delete-chat
* parameters: * parameters:
* - name: sessionId * - name: chatNoteId
* in: path * in: path
* required: true * required: true
* schema: * schema:
* type: string * type: string
* responses: * responses:
* '200': * '200':
* description: Session successfully deleted * description: Chat successfully deleted
* '404': * '404':
* description: Session not found * description: Chat not found
* security: * security:
* - session: [] * - session: []
* tags: ["llm"] * tags: ["llm"]
*/ */
async function deleteSession(req: Request, res: Response) { async function deleteChat(req: Request, res: Response) {
return restChatService.deleteSession(req, res); return restChatService.deleteSession(req, res);
} }
/** /**
* @swagger * @swagger
* /api/llm/sessions/{sessionId}/messages: * /api/llm/chat/{chatNoteId}/messages:
* post: * post:
* summary: Send a message to an LLM and get a response * summary: Send a message to an LLM and get a response
* operationId: llm-send-message * operationId: llm-send-message
* parameters: * parameters:
* - name: sessionId * - name: chatNoteId
* in: path * in: path
* required: true * required: true
* schema: * schema:
@ -357,7 +357,7 @@ async function deleteSession(req: Request, res: Response) {
* description: Whether to include relevant notes as context * description: Whether to include relevant notes as context
* useNoteContext: * useNoteContext:
* type: boolean * type: boolean
* description: Whether to use the session's context note * description: Whether to use the chat's context note
* responses: * responses:
* '200': * '200':
* description: LLM response * description: LLM response
@ -379,10 +379,10 @@ async function deleteSession(req: Request, res: Response) {
* type: string * type: string
* similarity: * similarity:
* type: number * type: number
* sessionId: * chatNoteId:
* type: string * type: string
* '404': * '404':
* description: Session not found * description: Chat not found
* '500': * '500':
* description: Error processing request * description: Error processing request
* security: * security:
@ -393,6 +393,175 @@ async function sendMessage(req: Request, res: Response) {
return restChatService.handleSendMessage(req, res); 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 * @swagger
* /api/llm/indexes/stats: * /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 { export default {
// Chat session management // Chat management
createSession, createChat,
getSession, getChat,
updateSession, updateChat,
listSessions, listChats,
deleteSession, deleteChat,
sendMessage, sendMessage,
streamMessage, streamMessage,

View File

@ -84,7 +84,7 @@ class RestChatService {
log.info("=== Starting handleSendMessage ==="); log.info("=== Starting handleSendMessage ===");
try { try {
// Extract parameters differently based on the request method // Extract parameters differently based on the request method
let content, useAdvancedContext, showThinking, sessionId; let content, useAdvancedContext, showThinking, chatNoteId;
if (req.method === 'POST') { if (req.method === 'POST') {
// For POST requests, get content from the request body // For POST requests, get content from the request body
@ -94,7 +94,7 @@ class RestChatService {
showThinking = requestBody.showThinking || false; showThinking = requestBody.showThinking || false;
// Add logging for POST requests // 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') { } else if (req.method === 'GET') {
// For GET (streaming) requests, get parameters from query params and body // For GET (streaming) requests, get parameters from query params and body
// For streaming requests, we need the content from the 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 : ''; content = req.body && req.body.content ? req.body.content : '';
// Add detailed logging for GET requests // 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 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'}`); 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 // Get chatNoteId from URL params since it's part of the route
sessionId = req.params.sessionId; chatNoteId = req.params.chatNoteId || req.params.sessionId; // Support both names for backward compatibility
// For GET requests, ensure we have the stream parameter // For GET requests, ensure we have the stream parameter
if (req.method === 'GET' && req.query.stream !== 'true') { if (req.method === 'GET' && req.query.stream !== 'true') {
@ -121,46 +121,48 @@ class RestChatService {
throw new Error('Content cannot be empty'); throw new Error('Content cannot be empty');
} }
// Check if session exists, create one if not // Get or create session from Chat Note
let session = SessionsStore.getSession(sessionId); 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 (!session) {
if (req.method === 'GET') { // This should never happen due to our logic above
// For GET requests, we should try to restore from Chat Note throw new Error('Failed to create or retrieve session');
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;
}
}
} }
// Update session last active timestamp // Update session last active timestamp
SessionsStore.touchSession(session.id); SessionsStore.touchSession(session.id);
// For POST requests, store the user message // For POST requests, store the user message
if (req.method === 'POST' && content) { if (req.method === 'POST' && content && session) {
// Add message to session // Add message to session
session.messages.push({ session.messages.push({
role: 'user', role: 'user',
@ -261,15 +263,15 @@ class RestChatService {
const pipelineOptions = { const pipelineOptions = {
// Force useAdvancedContext to be a boolean, no matter what // Force useAdvancedContext to be a boolean, no matter what
useAdvancedContext: useAdvancedContext === true, useAdvancedContext: useAdvancedContext === true,
systemPrompt: session.messages.find(m => m.role === 'system')?.content, systemPrompt: session?.messages.find(m => m.role === 'system')?.content,
temperature: session.metadata.temperature, temperature: session?.metadata.temperature,
maxTokens: session.metadata.maxTokens, maxTokens: session?.metadata.maxTokens,
model: session.metadata.model, model: session?.metadata.model,
// Set stream based on request type, but ensure it's explicitly a boolean value // 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 // GET requests or format=stream parameter indicates streaming should be used
stream: !!(req.method === 'GET' || req.query.format === 'stream' || req.query.stream === 'true'), stream: !!(req.method === 'GET' || req.query.format === 'stream' || req.query.stream === 'true'),
// Include sessionId for tracking tool executions // Include chatNoteId for tracking tool executions
sessionId: sessionId sessionId: chatNoteId // Use sessionId property for backward compatibility
}; };
// Log the options to verify what's being sent to the pipeline // Log the options to verify what's being sent to the pipeline
@ -300,7 +302,7 @@ class RestChatService {
// Use WebSocket service to send messages // Use WebSocket service to send messages
this.handleStreamCallback( this.handleStreamCallback(
data, done, rawChunk, data, done, rawChunk,
wsService.default, sessionId, wsService.default, chatNoteId,
messageContent, session, res messageContent, session, res
); );
} catch (error) { } catch (error) {
@ -310,7 +312,7 @@ class RestChatService {
try { try {
wsService.default.sendMessageToAllClients({ wsService.default.sendMessageToAllClients({
type: 'llm-stream', type: 'llm-stream',
sessionId, sessionId: chatNoteId, // Use sessionId property for backward compatibility
error: `Stream error: ${error instanceof Error ? error.message : 'Unknown error'}`, error: `Stream error: ${error instanceof Error ? error.message : 'Unknown error'}`,
done: true done: true
}); });
@ -379,7 +381,7 @@ class RestChatService {
done: boolean, done: boolean,
rawChunk: any, rawChunk: any,
wsService: any, wsService: any,
sessionId: string, chatNoteId: string,
messageContent: string, messageContent: string,
session: any, session: any,
res: Response res: Response
@ -392,7 +394,7 @@ class RestChatService {
// Create a message object with all necessary fields // Create a message object with all necessary fields
const message: LLMStreamMessage = { const message: LLMStreamMessage = {
type: 'llm-stream', 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 // 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) // Log what was sent (first message and completion)
if (message.thinking || done) { if (message.thinking || done) {
log.info( 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 * Create an in-memory session from a Chat Note
* This is used when a session doesn't exist in memory but there's a corresponding Chat Note * This treats the Chat Note as the source of truth, using its ID as the session ID
*/ */
async restoreSessionFromChatNote(sessionId: string): Promise<ChatSession | null> { async createSessionFromChatNote(noteId: string): Promise<ChatSession | null> {
try { 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 // Import chat storage service
const chatStorageService = (await import('../../llm/chat_storage_service.js')).default; const chatStorageService = (await import('../../llm/chat_storage_service.js')).default;
// Try to get the Chat Note data // Try to get the Chat Note data
const chatNote = await chatStorageService.getChat(sessionId); const chatNote = await chatStorageService.getChat(noteId);
if (!chatNote) { if (!chatNote) {
log.error(`Chat Note ${sessionId} not found, cannot restore session`); log.error(`Chat Note ${noteId} not found, cannot create session`);
return null; 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 // Convert Message[] to ChatMessage[] by ensuring the role is compatible
const chatMessages: ChatMessage[] = chatNote.messages.map(msg => ({ 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 // Create a new session with the same ID as the Chat Note
const session: ChatSession = { const session: ChatSession = {
id: chatNote.id, id: chatNote.id, // Use Chat Note ID as the session ID
title: chatNote.title, title: chatNote.title,
messages: chatMessages, messages: chatMessages,
createdAt: chatNote.createdAt || new Date(), createdAt: chatNote.createdAt || new Date(),
@ -615,15 +617,41 @@ class RestChatService {
}; };
// Add the session to the in-memory store // 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; return session;
} catch (error) { } catch (error) {
log.error(`Failed to restore session from Chat Note: ${error}`); log.error(`Failed to create session from Chat Note: ${error}`);
return null; 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<ChatSession | null> {
// 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 // Create singleton instance