Cuong2004 commited on
Commit
3e12ae4
·
1 Parent(s): 9a8eaac
src/agent/agent-executor.ts CHANGED
@@ -38,7 +38,7 @@ export class MedagenAgent {
38
  this.initialized = true;
39
  logger.info('Medagen Agent initialized successfully');
40
  } catch (error) {
41
- logger.error(`Failed to initialize agent: ${error}`);
42
  throw error;
43
  }
44
  }
@@ -98,7 +98,7 @@ export class MedagenAgent {
98
  }
99
  }
100
  } catch (error) {
101
- logger.error(`Error processing query: ${error}`);
102
 
103
  // Return safe default
104
  return this.getSafeDefaultResponse(userText);
@@ -254,7 +254,7 @@ Tạo response JSON (ONLY JSON, no markdown):
254
 
255
  throw new Error('Failed to parse LLM response');
256
  } catch (error) {
257
- logger.error(`Error processing disease info query: ${error}`);
258
  return this.getSafeDefaultResponse(userText);
259
  }
260
  }
@@ -286,7 +286,7 @@ Tạo response JSON (ONLY JSON, no markdown):
286
  ${conversationContext ? `Context: ${conversationContext}` : ''}
287
 
288
  Thông tin từ hướng dẫn:
289
- ${guidelines.map((g, i) => `${i + 1}. ${g.content || g.snippet || g}`).join('\n')}
290
 
291
  Trả lời một cách hữu ích, giáo dục, an toàn. Nhấn mạnh không thay thế bác sĩ.
292
 
@@ -381,7 +381,7 @@ JSON response (ONLY JSON):
381
 
382
  return finalResult;
383
  } catch (error) {
384
- logger.error(`Error in custom agent workflow: ${error}`);
385
  throw error;
386
  }
387
  }
@@ -424,7 +424,7 @@ JSON response (ONLY JSON):
424
  conversationContext
425
  );
426
  } catch (error) {
427
- logger.error(`Error in text-only triage: ${error}`);
428
  throw error;
429
  }
430
  }
 
38
  this.initialized = true;
39
  logger.info('Medagen Agent initialized successfully');
40
  } catch (error) {
41
+ logger.error({ error }, 'Failed to initialize agent');
42
  throw error;
43
  }
44
  }
 
98
  }
99
  }
100
  } catch (error) {
101
+ logger.error({ error }, 'Error processing query');
102
 
103
  // Return safe default
104
  return this.getSafeDefaultResponse(userText);
 
254
 
255
  throw new Error('Failed to parse LLM response');
256
  } catch (error) {
257
+ logger.error({ error }, 'Error processing disease info query');
258
  return this.getSafeDefaultResponse(userText);
259
  }
260
  }
 
286
  ${conversationContext ? `Context: ${conversationContext}` : ''}
287
 
288
  Thông tin từ hướng dẫn:
289
+ ${guidelines.map((g, i) => `${i + 1}. ${g}`).join('\n')}
290
 
291
  Trả lời một cách hữu ích, giáo dục, an toàn. Nhấn mạnh không thay thế bác sĩ.
292
 
 
381
 
382
  return finalResult;
383
  } catch (error) {
384
+ logger.error({ error }, 'Error in custom agent workflow');
385
  throw error;
386
  }
387
  }
 
424
  conversationContext
425
  );
426
  } catch (error) {
427
+ logger.error({ error }, 'Error in text-only triage');
428
  throw error;
429
  }
430
  }
src/agent/gemini-embedding.ts CHANGED
@@ -26,8 +26,8 @@ export class GeminiEmbedding extends Embeddings {
26
 
27
  return embeddings;
28
  } catch (error) {
29
- logger.error('Error generating embeddings:', error);
30
- throw new Error(`Embedding error: ${error}`);
31
  }
32
  }
33
 
@@ -37,8 +37,8 @@ export class GeminiEmbedding extends Embeddings {
37
  const result = await model.embedContent(text);
38
  return result.embedding.values;
39
  } catch (error) {
40
- logger.error('Error generating query embedding:', error);
41
- throw new Error(`Query embedding error: ${error}`);
42
  }
43
  }
44
  }
 
26
 
27
  return embeddings;
28
  } catch (error) {
29
+ logger.error({ error }, 'Error generating embeddings');
30
+ throw new Error(`Embedding error: ${error instanceof Error ? error.message : String(error)}`);
31
  }
32
  }
33
 
 
37
  const result = await model.embedContent(text);
38
  return result.embedding.values;
39
  } catch (error) {
40
+ logger.error({ error }, 'Error generating query embedding');
41
+ throw new Error(`Query embedding error: ${error instanceof Error ? error.message : String(error)}`);
42
  }
43
  }
44
  }
src/agent/gemini-llm.ts CHANGED
@@ -4,7 +4,6 @@ import { config } from '../utils/config.js';
4
  import { logger } from '../utils/logger.js';
5
  import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
6
  import type { LLMResult } from '@langchain/core/outputs';
7
-
8
  export class GeminiLLM extends LLM {
9
  private genAI: GoogleGenerativeAI;
10
  private modelName: string;
@@ -19,10 +18,19 @@ export class GeminiLLM extends LLM {
19
  return 'gemini';
20
  }
21
 
 
 
 
 
 
 
 
 
 
22
  async _generate(
23
  prompts: string[],
24
- options?: this['ParsedCallOptions'],
25
- runManager?: CallbackManagerForLLMRun
26
  ): Promise<LLMResult> {
27
  try {
28
  const model = this.genAI.getGenerativeModel({ model: this.modelName });
@@ -48,8 +56,8 @@ export class GeminiLLM extends LLM {
48
  generations: [generations]
49
  };
50
  } catch (error) {
51
- logger.error('Error calling Gemini API:', error);
52
- throw new Error(`Gemini API error: ${error}`);
53
  }
54
  }
55
  }
 
4
  import { logger } from '../utils/logger.js';
5
  import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
6
  import type { LLMResult } from '@langchain/core/outputs';
 
7
  export class GeminiLLM extends LLM {
8
  private genAI: GoogleGenerativeAI;
9
  private modelName: string;
 
18
  return 'gemini';
19
  }
20
 
21
+ async _call(
22
+ prompt: string,
23
+ _options?: this['ParsedCallOptions'],
24
+ _runManager?: CallbackManagerForLLMRun
25
+ ): Promise<string> {
26
+ const result = await this._generate([prompt], _options, _runManager);
27
+ return result.generations[0][0].text;
28
+ }
29
+
30
  async _generate(
31
  prompts: string[],
32
+ _options?: this['ParsedCallOptions'],
33
+ _runManager?: CallbackManagerForLLMRun
34
  ): Promise<LLMResult> {
35
  try {
36
  const model = this.genAI.getGenerativeModel({ model: this.modelName });
 
56
  generations: [generations]
57
  };
58
  } catch (error) {
59
+ logger.error({ error }, 'Error calling Gemini API');
60
+ throw new Error(`Gemini API error: ${error instanceof Error ? error.message : String(error)}`);
61
  }
62
  }
63
  }
src/agent/websocket-callback.handler.ts CHANGED
@@ -63,14 +63,14 @@ export class WebSocketStreamHandler extends BaseCallbackHandler {
63
  await wsConnectionManager.sendToSession(this.sessionId, actionStartMsg);
64
  logger.debug(`Sent action_start for tool ${toolName}, session ${this.sessionId}`);
65
  } catch (error) {
66
- logger.error('Error in onAgentAction:', error);
67
  }
68
  }
69
 
70
  /**
71
  * Called when tool execution completes
72
  */
73
- async onToolEnd(output: string, runId: string, parentRunId?: string, tags?: string[]): Promise<void> {
74
  try {
75
  // Try to parse tool output as JSON (most tools return JSON)
76
  let results: any;
@@ -81,7 +81,7 @@ export class WebSocketStreamHandler extends BaseCallbackHandler {
81
  }
82
 
83
  // Get tool name from tags or use fallback
84
- const toolName = tags?.find(tag => tag in TOOL_DISPLAY_NAMES) || 'unknown';
85
 
86
  // Calculate duration
87
  const startTime = this.actionStartTimes.get(toolName);
@@ -114,16 +114,16 @@ export class WebSocketStreamHandler extends BaseCallbackHandler {
114
  logger.debug(`Sent observation for tool ${toolName}, session ${this.sessionId}`);
115
  }
116
  } catch (error) {
117
- logger.error('Error in onToolEnd:', error);
118
  }
119
  }
120
 
121
  /**
122
  * Called when tool execution fails
123
  */
124
- async onToolError(error: Error, runId: string, parentRunId?: string, tags?: string[]): Promise<void> {
125
  try {
126
- const toolName = tags?.find(tag => tag in TOOL_DISPLAY_NAMES) || 'unknown';
127
 
128
  // Calculate duration
129
  const startTime = this.actionStartTimes.get(toolName);
@@ -142,7 +142,7 @@ export class WebSocketStreamHandler extends BaseCallbackHandler {
142
  await wsConnectionManager.sendToSession(this.sessionId, actionErrorMsg);
143
  logger.debug(`Sent action_error for tool ${toolName}, session ${this.sessionId}`);
144
  } catch (err) {
145
- logger.error('Error in onToolError:', err);
146
  }
147
  }
148
 
@@ -173,7 +173,7 @@ export class WebSocketStreamHandler extends BaseCallbackHandler {
173
  await wsConnectionManager.sendToSession(this.sessionId, finalAnswerMsg);
174
  logger.debug(`Sent final_answer for session ${this.sessionId}`);
175
  } catch (error) {
176
- logger.error('Error in onAgentFinish:', error);
177
  }
178
  }
179
 
@@ -197,9 +197,9 @@ export class WebSocketStreamHandler extends BaseCallbackHandler {
197
  async onChainError(error: Error): Promise<void> {
198
  try {
199
  wsConnectionManager.sendError(this.sessionId, 'CHAIN_ERROR', error.message);
200
- logger.error(`Chain error for session ${this.sessionId}:`, error);
201
  } catch (err) {
202
- logger.error('Error in onChainError:', err);
203
  }
204
  }
205
  }
 
63
  await wsConnectionManager.sendToSession(this.sessionId, actionStartMsg);
64
  logger.debug(`Sent action_start for tool ${toolName}, session ${this.sessionId}`);
65
  } catch (error) {
66
+ logger.error({ error }, 'Error in onAgentAction');
67
  }
68
  }
69
 
70
  /**
71
  * Called when tool execution completes
72
  */
73
+ async onToolEnd(output: string, _runId: string, _parentRunId?: string, _tags?: string[]): Promise<void> {
74
  try {
75
  // Try to parse tool output as JSON (most tools return JSON)
76
  let results: any;
 
81
  }
82
 
83
  // Get tool name from tags or use fallback
84
+ const toolName = _tags?.find((tag: string) => tag in TOOL_DISPLAY_NAMES) || 'unknown';
85
 
86
  // Calculate duration
87
  const startTime = this.actionStartTimes.get(toolName);
 
114
  logger.debug(`Sent observation for tool ${toolName}, session ${this.sessionId}`);
115
  }
116
  } catch (error) {
117
+ logger.error({ error }, 'Error in onToolEnd');
118
  }
119
  }
120
 
121
  /**
122
  * Called when tool execution fails
123
  */
124
+ async onToolError(error: Error, _runId: string, _parentRunId?: string, _tags?: string[]): Promise<void> {
125
  try {
126
+ const toolName = _tags?.find((tag: string) => tag in TOOL_DISPLAY_NAMES) || 'unknown';
127
 
128
  // Calculate duration
129
  const startTime = this.actionStartTimes.get(toolName);
 
142
  await wsConnectionManager.sendToSession(this.sessionId, actionErrorMsg);
143
  logger.debug(`Sent action_error for tool ${toolName}, session ${this.sessionId}`);
144
  } catch (err) {
145
+ logger.error({ error: err }, 'Error in onToolError');
146
  }
147
  }
148
 
 
173
  await wsConnectionManager.sendToSession(this.sessionId, finalAnswerMsg);
174
  logger.debug(`Sent final_answer for session ${this.sessionId}`);
175
  } catch (error) {
176
+ logger.error({ error }, 'Error in onAgentFinish');
177
  }
178
  }
179
 
 
197
  async onChainError(error: Error): Promise<void> {
198
  try {
199
  wsConnectionManager.sendError(this.sessionId, 'CHAIN_ERROR', error.message);
200
+ logger.error({ error, sessionId: this.sessionId }, 'Chain error');
201
  } catch (err) {
202
+ logger.error({ error: err }, 'Error in onChainError');
203
  }
204
  }
205
  }
src/index.ts CHANGED
@@ -36,7 +36,7 @@ async function startServer() {
36
  await fastify.register(websocket, {
37
  options: {
38
  maxPayload: 10 * 1024, // 10KB max message size
39
- verifyClient: (info, callback) => {
40
  // Optional: Add custom verification logic here
41
  callback(true);
42
  }
@@ -44,7 +44,7 @@ async function startServer() {
44
  });
45
 
46
  // Register Swagger
47
- await fastify.register(swagger, swaggerOptions);
48
  await fastify.register(swaggerUi, swaggerUiOptions);
49
 
50
  // Initialize services
@@ -62,7 +62,7 @@ async function startServer() {
62
  await registerRoutes(fastify, agent, supabaseService, mapsService);
63
 
64
  // Add request logging
65
- fastify.addHook('onRequest', async (request, reply) => {
66
  logger.info({
67
  method: request.method,
68
  url: request.url,
@@ -75,29 +75,30 @@ async function startServer() {
75
  logger.info({
76
  method: request.method,
77
  url: request.url,
78
- statusCode: reply.statusCode,
79
- responseTime: reply.getResponseTime()
80
  }, 'Request completed');
81
  });
82
 
83
  // Error handler
84
- fastify.setErrorHandler((error, request, reply) => {
85
  // Handle validation errors (Fastify schema validation)
86
- if (error.validation) {
 
87
  logger.warn({ validation: error.validation }, 'Validation error');
88
  return reply.status(400).send({
89
  error: 'Validation Error',
90
- message: error.message,
91
  details: error.validation
92
  });
93
  }
94
 
95
  // Handle other errors
96
- logger.error(error, 'Unhandled error');
 
97
 
98
- reply.status(500).send({
99
  error: 'Internal Server Error',
100
- message: process.env.NODE_ENV === 'development' ? error.message : 'An error occurred'
101
  });
102
  });
103
 
@@ -113,7 +114,7 @@ async function startServer() {
113
  logger.info(`🔌 WebSocket endpoint: ws://${host}:${port}/ws/chat`);
114
  logger.info(`📚 Swagger docs: http://${host}:${port}/docs`);
115
  } catch (error) {
116
- logger.error(error, 'Failed to start server');
117
  process.exit(1);
118
  }
119
  }
 
36
  await fastify.register(websocket, {
37
  options: {
38
  maxPayload: 10 * 1024, // 10KB max message size
39
+ verifyClient: (_info, callback) => {
40
  // Optional: Add custom verification logic here
41
  callback(true);
42
  }
 
44
  });
45
 
46
  // Register Swagger
47
+ await fastify.register(swagger as any, swaggerOptions);
48
  await fastify.register(swaggerUi, swaggerUiOptions);
49
 
50
  // Initialize services
 
62
  await registerRoutes(fastify, agent, supabaseService, mapsService);
63
 
64
  // Add request logging
65
+ fastify.addHook('onRequest', async (request) => {
66
  logger.info({
67
  method: request.method,
68
  url: request.url,
 
75
  logger.info({
76
  method: request.method,
77
  url: request.url,
78
+ statusCode: reply.statusCode
 
79
  }, 'Request completed');
80
  });
81
 
82
  // Error handler
83
+ fastify.setErrorHandler((error, _request, reply) => {
84
  // Handle validation errors (Fastify schema validation)
85
+ if (error && typeof error === 'object' && 'validation' in error && error.validation) {
86
+ const validationError = error as { validation?: unknown; message?: string };
87
  logger.warn({ validation: error.validation }, 'Validation error');
88
  return reply.status(400).send({
89
  error: 'Validation Error',
90
+ message: validationError.message || 'Validation failed',
91
  details: error.validation
92
  });
93
  }
94
 
95
  // Handle other errors
96
+ const errorMessage = error instanceof Error ? error.message : String(error);
97
+ logger.error({ error }, 'Unhandled error');
98
 
99
+ return reply.status(500).send({
100
  error: 'Internal Server Error',
101
+ message: process.env.NODE_ENV === 'development' ? errorMessage : 'An error occurred'
102
  });
103
  });
104
 
 
114
  logger.info(`🔌 WebSocket endpoint: ws://${host}:${port}/ws/chat`);
115
  logger.info(`📚 Swagger docs: http://${host}:${port}/docs`);
116
  } catch (error) {
117
+ logger.error({ error }, 'Failed to start server');
118
  process.exit(1);
119
  }
120
  }
src/mcp_tools/cv-tools.ts CHANGED
@@ -27,7 +27,7 @@ export function createDermCVTool(cvService: CVService): DynamicTool {
27
 
28
  return JSON.stringify({ top_predictions });
29
  } catch (error) {
30
- logger.error('derm_cv tool error:', error);
31
  return JSON.stringify({ error: 'Failed to analyze dermatology image', top_predictions: [] });
32
  }
33
  }
@@ -58,7 +58,7 @@ export function createEyeCVTool(cvService: CVService): DynamicTool {
58
 
59
  return JSON.stringify({ top_predictions });
60
  } catch (error) {
61
- logger.error('eye_cv tool error:', error);
62
  return JSON.stringify({ error: 'Failed to analyze eye image', top_predictions: [] });
63
  }
64
  }
@@ -89,7 +89,7 @@ export function createWoundCVTool(cvService: CVService): DynamicTool {
89
 
90
  return JSON.stringify({ top_predictions });
91
  } catch (error) {
92
- logger.error('wound_cv tool error:', error);
93
  return JSON.stringify({ error: 'Failed to analyze wound image', top_predictions: [] });
94
  }
95
  }
 
27
 
28
  return JSON.stringify({ top_predictions });
29
  } catch (error) {
30
+ logger.error({ error }, 'derm_cv tool error');
31
  return JSON.stringify({ error: 'Failed to analyze dermatology image', top_predictions: [] });
32
  }
33
  }
 
58
 
59
  return JSON.stringify({ top_predictions });
60
  } catch (error) {
61
+ logger.error({ error }, 'eye_cv tool error');
62
  return JSON.stringify({ error: 'Failed to analyze eye image', top_predictions: [] });
63
  }
64
  }
 
89
 
90
  return JSON.stringify({ top_predictions });
91
  } catch (error) {
92
+ logger.error({ error }, 'wound_cv tool error');
93
  return JSON.stringify({ error: 'Failed to analyze wound image', top_predictions: [] });
94
  }
95
  }
src/mcp_tools/rag-tool.ts CHANGED
@@ -29,7 +29,7 @@ export function createGuidelineRAGTool(ragService: RAGService): DynamicTool {
29
  guidelines: guidelines
30
  });
31
  } catch (error) {
32
- logger.error('guideline_rag tool error:', error);
33
  return JSON.stringify({
34
  error: 'Failed to retrieve guidelines',
35
  guidelines: []
 
29
  guidelines: guidelines
30
  });
31
  } catch (error) {
32
+ logger.error({ error }, 'guideline_rag tool error');
33
  return JSON.stringify({
34
  error: 'Failed to retrieve guidelines',
35
  guidelines: []
src/mcp_tools/triage-tool.ts CHANGED
@@ -46,7 +46,7 @@ export function createTriageRulesTool(triageService: TriageRulesService): Dynami
46
 
47
  return JSON.stringify(result);
48
  } catch (error) {
49
- logger.error('triage_rules tool error:', error);
50
  return JSON.stringify({
51
  error: 'Failed to evaluate triage rules',
52
  triage: 'urgent',
 
46
 
47
  return JSON.stringify(result);
48
  } catch (error) {
49
+ logger.error({ error }, 'triage_rules tool error');
50
  return JSON.stringify({
51
  error: 'Failed to evaluate triage rules',
52
  triage: 'urgent',
src/routes/conversation.route.ts CHANGED
@@ -95,7 +95,7 @@ export async function conversationRoutes(
95
  count: messages.length
96
  });
97
  } catch (error) {
98
- logger.error('Get conversation history error:', error);
99
  return reply.status(500).send({
100
  error: 'Internal server error',
101
  message: 'Failed to get conversation history'
@@ -192,7 +192,7 @@ export async function conversationRoutes(
192
  count: sessions.length
193
  });
194
  } catch (error) {
195
- logger.error('Get user sessions error:', error);
196
  return reply.status(500).send({
197
  error: 'Internal server error',
198
  message: 'Failed to get user sessions'
 
95
  count: messages.length
96
  });
97
  } catch (error) {
98
+ logger.error({ error }, 'Get conversation history error');
99
  return reply.status(500).send({
100
  error: 'Internal server error',
101
  message: 'Failed to get conversation history'
 
192
  count: sessions.length
193
  });
194
  } catch (error) {
195
+ logger.error({ error }, 'Get user sessions error');
196
  return reply.status(500).send({
197
  error: 'Internal server error',
198
  message: 'Failed to get user sessions'
src/routes/cv.route.ts CHANGED
@@ -1,11 +1,12 @@
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
- import { z } from 'zod';
3
  import { CVService } from '../services/cv.service.js';
4
  import { logger } from '../utils/logger.js';
5
 
6
- const imageUrlSchema = z.object({
7
- image_url: z.string().url('Image URL must be a valid URL')
8
- });
 
9
 
10
  export async function cvRoutes(fastify: FastifyInstance) {
11
  const cvService = new CVService();
@@ -58,7 +59,7 @@ export async function cvRoutes(fastify: FastifyInstance) {
58
  const result = await cvService.callDermCV(request.body.image_url);
59
  return reply.status(200).send(result);
60
  } catch (error) {
61
- logger.error('Derm CV endpoint error:', error);
62
  return reply.status(500).send({
63
  error: 'Internal server error',
64
  message: 'Failed to analyze dermatology image'
@@ -113,7 +114,7 @@ export async function cvRoutes(fastify: FastifyInstance) {
113
  const result = await cvService.callEyeCV(request.body.image_url);
114
  return reply.status(200).send(result);
115
  } catch (error) {
116
- logger.error('Eye CV endpoint error:', error);
117
  return reply.status(500).send({
118
  error: 'Internal server error',
119
  message: 'Failed to analyze eye image'
@@ -168,7 +169,7 @@ export async function cvRoutes(fastify: FastifyInstance) {
168
  const result = await cvService.callWoundCV(request.body.image_url);
169
  return reply.status(200).send(result);
170
  } catch (error) {
171
- logger.error('Wound CV endpoint error:', error);
172
  return reply.status(500).send({
173
  error: 'Internal server error',
174
  message: 'Failed to analyze wound image'
 
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
+ // import { z } from 'zod'; // Unused for now, can be used for future validation
3
  import { CVService } from '../services/cv.service.js';
4
  import { logger } from '../utils/logger.js';
5
 
6
+ // Schema for image URL validation (can be used for future validation)
7
+ // const imageUrlSchema = z.object({
8
+ // image_url: z.string().url('Image URL must be a valid URL')
9
+ // });
10
 
11
  export async function cvRoutes(fastify: FastifyInstance) {
12
  const cvService = new CVService();
 
59
  const result = await cvService.callDermCV(request.body.image_url);
60
  return reply.status(200).send(result);
61
  } catch (error) {
62
+ logger.error({ error }, 'Derm CV endpoint error');
63
  return reply.status(500).send({
64
  error: 'Internal server error',
65
  message: 'Failed to analyze dermatology image'
 
114
  const result = await cvService.callEyeCV(request.body.image_url);
115
  return reply.status(200).send(result);
116
  } catch (error) {
117
+ logger.error({ error }, 'Eye CV endpoint error');
118
  return reply.status(500).send({
119
  error: 'Internal server error',
120
  message: 'Failed to analyze eye image'
 
169
  const result = await cvService.callWoundCV(request.body.image_url);
170
  return reply.status(200).send(result);
171
  } catch (error) {
172
+ logger.error({ error }, 'Wound CV endpoint error');
173
  return reply.status(500).send({
174
  error: 'Internal server error',
175
  message: 'Failed to analyze wound image'
src/routes/health.route.ts CHANGED
@@ -1,10 +1,11 @@
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
- import { CVService } from '../services/cv.service.js';
3
  import { config } from '../utils/config.js';
4
  import { logger } from '../utils/logger.js';
5
 
6
  export async function healthRoutes(fastify: FastifyInstance) {
7
- const cvService = new CVService();
 
8
 
9
  fastify.get('/health', {
10
  schema: {
@@ -38,7 +39,7 @@ export async function healthRoutes(fastify: FastifyInstance) {
38
  }
39
  }
40
  }
41
- }, async (request: FastifyRequest, reply: FastifyReply) => {
42
  try {
43
  logger.info('Health check requested');
44
 
@@ -75,7 +76,7 @@ export async function healthRoutes(fastify: FastifyInstance) {
75
 
76
  return reply.status(200).send(health);
77
  } catch (error) {
78
- logger.error('Health check error:', error);
79
  return reply.status(500).send({
80
  status: 'error',
81
  message: 'Health check failed'
 
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
+ // import { CVService } from '../services/cv.service.js'; // Unused for now, can be used for future health checks
3
  import { config } from '../utils/config.js';
4
  import { logger } from '../utils/logger.js';
5
 
6
  export async function healthRoutes(fastify: FastifyInstance) {
7
+ // CV service can be used for health checks in the future
8
+ // const cvService = new CVService();
9
 
10
  fastify.get('/health', {
11
  schema: {
 
39
  }
40
  }
41
  }
42
+ }, async (_request: FastifyRequest, reply: FastifyReply) => {
43
  try {
44
  logger.info('Health check requested');
45
 
 
76
 
77
  return reply.status(200).send(health);
78
  } catch (error) {
79
+ logger.error({ error }, 'Health check error');
80
  return reply.status(500).send({
81
  status: 'error',
82
  message: 'Health check failed'
src/routes/maps.route.ts CHANGED
@@ -1,14 +1,15 @@
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
- import { z } from 'zod';
3
  import { MapsService } from '../services/maps.service.js';
4
  import { logger } from '../utils/logger.js';
5
  import type { Location } from '../types/index.js';
6
 
7
- const locationSchema = z.object({
8
- lat: z.number().min(-90).max(90),
9
- lng: z.number().min(-180).max(180),
10
- keyword: z.string().optional()
11
- });
 
12
 
13
  export async function mapsRoutes(fastify: FastifyInstance) {
14
  const mapsService = new MapsService();
@@ -89,7 +90,7 @@ export async function mapsRoutes(fastify: FastifyInstance) {
89
 
90
  return reply.status(200).send(clinic);
91
  } catch (error) {
92
- logger.error('Maps endpoint error:', error);
93
  return reply.status(500).send({
94
  error: 'Internal server error',
95
  message: 'Failed to find nearest clinic'
 
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
+ // import { z } from 'zod'; // Unused for now, can be used for future validation
3
  import { MapsService } from '../services/maps.service.js';
4
  import { logger } from '../utils/logger.js';
5
  import type { Location } from '../types/index.js';
6
 
7
+ // Schema for location validation (can be used for future validation)
8
+ // const locationSchema = z.object({
9
+ // lat: z.number().min(-90).max(90),
10
+ // lng: z.number().min(-180).max(180),
11
+ // keyword: z.string().optional()
12
+ // });
13
 
14
  export async function mapsRoutes(fastify: FastifyInstance) {
15
  const mapsService = new MapsService();
 
90
 
91
  return reply.status(200).send(clinic);
92
  } catch (error) {
93
+ logger.error({ error }, 'Maps endpoint error');
94
  return reply.status(500).send({
95
  error: 'Internal server error',
96
  message: 'Failed to find nearest clinic'
src/routes/rag.route.ts CHANGED
@@ -1,15 +1,16 @@
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
- import { z } from 'zod';
3
  import { RAGService } from '../services/rag.service.js';
4
  import { SupabaseService } from '../services/supabase.service.js';
5
  import { logger } from '../utils/logger.js';
6
  import type { GuidelineQuery } from '../types/index.js';
7
 
8
- const guidelineQuerySchema = z.object({
9
- symptoms: z.string().min(1),
10
- suspected_conditions: z.array(z.string()).optional(),
11
- triage_level: z.string().optional()
12
- });
 
13
 
14
  export async function ragRoutes(
15
  fastify: FastifyInstance,
@@ -82,7 +83,7 @@ export async function ragRoutes(
82
  count: guidelines.length
83
  });
84
  } catch (error) {
85
- logger.error('RAG search endpoint error:', error);
86
  return reply.status(500).send({
87
  error: 'Internal server error',
88
  message: 'Failed to search guidelines'
 
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
+ // import { z } from 'zod'; // Unused for now, can be used for future validation
3
  import { RAGService } from '../services/rag.service.js';
4
  import { SupabaseService } from '../services/supabase.service.js';
5
  import { logger } from '../utils/logger.js';
6
  import type { GuidelineQuery } from '../types/index.js';
7
 
8
+ // Schema for guideline query validation (can be used for future validation)
9
+ // const guidelineQuerySchema = z.object({
10
+ // symptoms: z.string().min(1),
11
+ // suspected_conditions: z.array(z.string()).optional(),
12
+ // triage_level: z.string().optional()
13
+ // });
14
 
15
  export async function ragRoutes(
16
  fastify: FastifyInstance,
 
83
  count: guidelines.length
84
  });
85
  } catch (error) {
86
+ logger.error({ error }, 'RAG search endpoint error');
87
  return reply.status(500).send({
88
  error: 'Internal server error',
89
  message: 'Failed to search guidelines'
src/routes/sessions.route.ts CHANGED
@@ -63,7 +63,7 @@ export async function sessionsRoutes(
63
 
64
  return reply.status(200).send(session);
65
  } catch (error) {
66
- logger.error('Get session endpoint error:', error);
67
  return reply.status(500).send({
68
  error: 'Internal server error',
69
  message: 'Failed to get session'
 
63
 
64
  return reply.status(200).send(session);
65
  } catch (error) {
66
+ logger.error({ error }, 'Get session endpoint error');
67
  return reply.status(500).send({
68
  error: 'Internal server error',
69
  message: 'Failed to get session'
src/routes/triage-rules.route.ts CHANGED
@@ -1,24 +1,25 @@
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
- import { z } from 'zod';
3
  import { TriageRulesService } from '../services/triage-rules.service.js';
4
  import { logger } from '../utils/logger.js';
5
  import type { TriageInput } from '../types/index.js';
6
 
7
- const triageInputSchema = z.object({
8
- symptoms: z.object({
9
- main_complaint: z.string().min(1),
10
- duration: z.string().optional(),
11
- pain_severity: z.enum(['nhẹ', 'vừa', 'nặng']).optional(),
12
- fever: z.boolean().optional(),
13
- vision_changes: z.boolean().optional(),
14
- bleeding: z.boolean().optional(),
15
- breathing_difficulty: z.boolean().optional(),
16
- chest_pain: z.boolean().optional(),
17
- severe_headache: z.boolean().optional(),
18
- confusion: z.boolean().optional()
19
- }),
20
- cv_results: z.any().optional()
21
- });
 
22
 
23
  export async function triageRulesRoutes(fastify: FastifyInstance) {
24
  const triageService = new TriageRulesService();
@@ -83,7 +84,7 @@ export async function triageRulesRoutes(fastify: FastifyInstance) {
83
  const result = triageService.evaluateSymptoms(request.body as TriageInput);
84
  return reply.status(200).send(result);
85
  } catch (error) {
86
- logger.error('Triage rules endpoint error:', error);
87
  return reply.status(500).send({
88
  error: 'Internal server error',
89
  message: 'Failed to evaluate triage rules'
 
1
  import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
+ // import { z } from 'zod'; // Unused for now, can be used for future validation
3
  import { TriageRulesService } from '../services/triage-rules.service.js';
4
  import { logger } from '../utils/logger.js';
5
  import type { TriageInput } from '../types/index.js';
6
 
7
+ // Schema for triage input validation (can be used for future validation)
8
+ // const triageInputSchema = z.object({
9
+ // symptoms: z.object({
10
+ // main_complaint: z.string().min(1),
11
+ // duration: z.string().optional(),
12
+ // pain_severity: z.enum(['nhẹ', 'vừa', 'nặng']).optional(),
13
+ // fever: z.boolean().optional(),
14
+ // vision_changes: z.boolean().optional(),
15
+ // bleeding: z.boolean().optional(),
16
+ // breathing_difficulty: z.boolean().optional(),
17
+ // chest_pain: z.boolean().optional(),
18
+ // severe_headache: z.boolean().optional(),
19
+ // confusion: z.boolean().optional()
20
+ // }),
21
+ // cv_results: z.any().optional()
22
+ // });
23
 
24
  export async function triageRulesRoutes(fastify: FastifyInstance) {
25
  const triageService = new TriageRulesService();
 
84
  const result = triageService.evaluateSymptoms(request.body as TriageInput);
85
  return reply.status(200).send(result);
86
  } catch (error) {
87
+ logger.error({ error }, 'Triage rules endpoint error');
88
  return reply.status(500).send({
89
  error: 'Internal server error',
90
  message: 'Failed to evaluate triage rules'
src/routes/triage.route.ts CHANGED
@@ -168,7 +168,7 @@ export async function triageRoutes(
168
  const validationResult = healthCheckSchema.safeParse(request.body);
169
 
170
  if (!validationResult.success) {
171
- logger.warn('Invalid request body:', validationResult.error);
172
  return reply.status(400).send({
173
  error: 'Invalid request',
174
  details: validationResult.error.errors
@@ -223,13 +223,12 @@ export async function triageRoutes(
223
  // Add user message to history
224
  await conversationService.addUserMessage(activeSessionId, user_id, normalizedText, normalizedImageUrl);
225
 
226
- // Process triage with agent (pass conversation context and session ID for WebSocket streaming)
227
  const triageResult = await agent.processTriage(
228
  normalizedText || 'Da tôi bị gì thế này',
229
  normalizedImageUrl,
230
  user_id,
231
- conversationContext, // Pass context separately for better agent handling
232
- activeSessionId // Pass session ID for WebSocket streaming
233
  );
234
 
235
  // Add assistant response to conversation history
@@ -242,7 +241,7 @@ export async function triageRoutes(
242
  triageResult
243
  );
244
  } catch (error) {
245
- logger.error('Failed to save conversation history:', error);
246
  // Continue even if saving fails
247
  }
248
 
@@ -256,7 +255,7 @@ export async function triageRoutes(
256
  location
257
  });
258
  } catch (error) {
259
- logger.error('Failed to save session:', error);
260
  // Continue even if saving fails
261
  }
262
 
@@ -266,7 +265,7 @@ export async function triageRoutes(
266
  try {
267
  nearestClinic = await mapsService.findNearestClinic(location);
268
  } catch (error) {
269
- logger.error('Failed to find nearest clinic:', error);
270
  // Continue without clinic info
271
  }
272
  }
@@ -282,7 +281,7 @@ export async function triageRoutes(
282
 
283
  return reply.status(200).send(response);
284
  } catch (error) {
285
- logger.error('Health check error:', error);
286
 
287
  return reply.status(500).send({
288
  error: 'Internal server error',
 
168
  const validationResult = healthCheckSchema.safeParse(request.body);
169
 
170
  if (!validationResult.success) {
171
+ logger.warn({ error: validationResult.error }, 'Invalid request body');
172
  return reply.status(400).send({
173
  error: 'Invalid request',
174
  details: validationResult.error.errors
 
223
  // Add user message to history
224
  await conversationService.addUserMessage(activeSessionId, user_id, normalizedText, normalizedImageUrl);
225
 
226
+ // Process triage with agent (pass conversation context)
227
  const triageResult = await agent.processTriage(
228
  normalizedText || 'Da tôi bị gì thế này',
229
  normalizedImageUrl,
230
  user_id,
231
+ conversationContext // Pass context separately for better agent handling
 
232
  );
233
 
234
  // Add assistant response to conversation history
 
241
  triageResult
242
  );
243
  } catch (error) {
244
+ logger.error({ error }, 'Failed to save conversation history');
245
  // Continue even if saving fails
246
  }
247
 
 
255
  location
256
  });
257
  } catch (error) {
258
+ logger.error({ error }, 'Failed to save session');
259
  // Continue even if saving fails
260
  }
261
 
 
265
  try {
266
  nearestClinic = await mapsService.findNearestClinic(location);
267
  } catch (error) {
268
+ logger.error({ error }, 'Failed to find nearest clinic');
269
  // Continue without clinic info
270
  }
271
  }
 
281
 
282
  return reply.status(200).send(response);
283
  } catch (error) {
284
+ logger.error({ error }, 'Health check error');
285
 
286
  return reply.status(500).send({
287
  error: 'Internal server error',
src/routes/websocket.route.ts CHANGED
@@ -4,10 +4,8 @@
4
  */
5
 
6
  import { FastifyInstance, FastifyRequest } from 'fastify';
7
- import { SocketStream } from '@fastify/websocket';
8
  import { wsConnectionManager } from '../services/websocket.service.js';
9
  import { logger } from '../utils/logger.js';
10
-
11
  interface WebSocketQueryString {
12
  session: string;
13
  token?: string;
@@ -18,13 +16,13 @@ export async function websocketRoutes(fastify: FastifyInstance) {
18
  fastify.get(
19
  '/ws/chat',
20
  { websocket: true },
21
- async (connection: SocketStream, req: FastifyRequest) => {
22
- const { session, token } = req.query as WebSocketQueryString;
23
 
24
  // Validate session parameter
25
  if (!session || typeof session !== 'string') {
26
  logger.warn('WebSocket connection rejected: missing session parameter');
27
- connection.socket.close(1008, 'Session ID required');
28
  return;
29
  }
30
 
@@ -43,11 +41,11 @@ export async function websocketRoutes(fastify: FastifyInstance) {
43
  logger.info(`WebSocket connection request for session: ${session}`);
44
 
45
  // Add connection to manager
46
- wsConnectionManager.addConnection(session, connection.socket);
47
 
48
  // Send welcome message
49
  try {
50
- connection.socket.send(
51
  JSON.stringify({
52
  type: 'connected',
53
  message: 'WebSocket connected successfully',
@@ -56,17 +54,17 @@ export async function websocketRoutes(fastify: FastifyInstance) {
56
  })
57
  );
58
  } catch (error) {
59
- logger.error('Error sending welcome message:', error);
60
  }
61
 
62
  // Handle incoming messages (optional - mainly for keep-alive pings)
63
- connection.socket.on('message', (data: Buffer) => {
64
  try {
65
  const message = JSON.parse(data.toString());
66
 
67
  // Handle ping/pong
68
  if (message.type === 'ping') {
69
- connection.socket.send(
70
  JSON.stringify({
71
  type: 'pong',
72
  timestamp: new Date().toISOString(),
@@ -80,7 +78,7 @@ export async function websocketRoutes(fastify: FastifyInstance) {
80
  logger.info(`Auth message received for session ${session}`);
81
  }
82
  } catch (error) {
83
- logger.error(`Error processing WebSocket message for session ${session}:`, error);
84
  }
85
  });
86
 
 
4
  */
5
 
6
  import { FastifyInstance, FastifyRequest } from 'fastify';
 
7
  import { wsConnectionManager } from '../services/websocket.service.js';
8
  import { logger } from '../utils/logger.js';
 
9
  interface WebSocketQueryString {
10
  session: string;
11
  token?: string;
 
16
  fastify.get(
17
  '/ws/chat',
18
  { websocket: true },
19
+ async (connection, req: FastifyRequest) => {
20
+ const { session } = req.query as WebSocketQueryString;
21
 
22
  // Validate session parameter
23
  if (!session || typeof session !== 'string') {
24
  logger.warn('WebSocket connection rejected: missing session parameter');
25
+ connection.close(1008, 'Session ID required');
26
  return;
27
  }
28
 
 
41
  logger.info(`WebSocket connection request for session: ${session}`);
42
 
43
  // Add connection to manager
44
+ wsConnectionManager.addConnection(session, connection);
45
 
46
  // Send welcome message
47
  try {
48
+ connection.send(
49
  JSON.stringify({
50
  type: 'connected',
51
  message: 'WebSocket connected successfully',
 
54
  })
55
  );
56
  } catch (error) {
57
+ logger.error({ error }, 'Error sending welcome message');
58
  }
59
 
60
  // Handle incoming messages (optional - mainly for keep-alive pings)
61
+ connection.on('message', (data: Buffer) => {
62
  try {
63
  const message = JSON.parse(data.toString());
64
 
65
  // Handle ping/pong
66
  if (message.type === 'ping') {
67
+ connection.send(
68
  JSON.stringify({
69
  type: 'pong',
70
  timestamp: new Date().toISOString(),
 
78
  logger.info(`Auth message received for session ${session}`);
79
  }
80
  } catch (error) {
81
+ logger.error({ error, session }, 'Error processing WebSocket message');
82
  }
83
  });
84
 
src/services/cv.service.ts CHANGED
@@ -99,7 +99,7 @@ export class CVService {
99
  logger.info('[MCP CV] Calling Dermatology CV model...');
100
  return await this.callCVAPI(imageUrl, 'dermnet', 3);
101
  } catch (error) {
102
- logger.error('[MCP CV] Derm CV error:', error);
103
  return { top_conditions: [] };
104
  }
105
  }
@@ -110,7 +110,7 @@ export class CVService {
110
  // Eye conditions are also analyzed by dermnet model
111
  return await this.callCVAPI(imageUrl, 'dermnet', 3);
112
  } catch (error) {
113
- logger.error('[MCP CV] Eye CV error:', error);
114
  return { top_conditions: [] };
115
  }
116
  }
@@ -121,7 +121,7 @@ export class CVService {
121
  // Wound conditions are also analyzed by dermnet model
122
  return await this.callCVAPI(imageUrl, 'dermnet', 3);
123
  } catch (error) {
124
- logger.error('[MCP CV] Wound CV error:', error);
125
  return { top_conditions: [] };
126
  }
127
  }
 
99
  logger.info('[MCP CV] Calling Dermatology CV model...');
100
  return await this.callCVAPI(imageUrl, 'dermnet', 3);
101
  } catch (error) {
102
+ logger.error({ error }, '[MCP CV] Derm CV error');
103
  return { top_conditions: [] };
104
  }
105
  }
 
110
  // Eye conditions are also analyzed by dermnet model
111
  return await this.callCVAPI(imageUrl, 'dermnet', 3);
112
  } catch (error) {
113
+ logger.error({ error }, '[MCP CV] Eye CV error');
114
  return { top_conditions: [] };
115
  }
116
  }
 
121
  // Wound conditions are also analyzed by dermnet model
122
  return await this.callCVAPI(imageUrl, 'dermnet', 3);
123
  } catch (error) {
124
+ logger.error({ error }, '[MCP CV] Wound CV error');
125
  return { top_conditions: [] };
126
  }
127
  }
src/services/intent-classifier.service.ts CHANGED
@@ -32,10 +32,11 @@ export class IntentClassifierService {
32
  'thông tin về', 'tìm hiểu về', 'định nghĩa', 'nguyên nhân'
33
  ];
34
 
35
- private readonly TREATMENT_KEYWORDS = [
36
- 'điều trị', 'chữa', 'phòng ngừa', 'phòng bệnh', 'cách chữa',
37
- 'cách điều trị', 'thuốc', 'liệu pháp'
38
- ];
 
39
 
40
  private readonly OUT_OF_SCOPE_KEYWORDS = [
41
  'bảo hiểm', 'bhyt', 'chi phí', 'giá', 'thủ tục',
 
32
  'thông tin về', 'tìm hiểu về', 'định nghĩa', 'nguyên nhân'
33
  ];
34
 
35
+ // Treatment keywords can be used for future treatment-related intent classification
36
+ // private readonly TREATMENT_KEYWORDS = [
37
+ // 'điều trị', 'chữa', 'phòng ngừa', 'phòng bệnh', 'cách chữa',
38
+ // 'cách điều trị', 'thuốc', 'liệu pháp'
39
+ // ];
40
 
41
  private readonly OUT_OF_SCOPE_KEYWORDS = [
42
  'bảo hiểm', 'bhyt', 'chi phí', 'giá', 'thủ tục',
src/services/maps.service.ts CHANGED
@@ -54,7 +54,7 @@ export class MapsService {
54
  logger.warn('No clinics found nearby');
55
  return null;
56
  } catch (error) {
57
- logger.error('Google Maps API error:', error);
58
  return null;
59
  }
60
  }
 
54
  logger.warn('No clinics found nearby');
55
  return null;
56
  } catch (error) {
57
+ logger.error({ error }, 'Google Maps API error');
58
  return null;
59
  }
60
  }
src/services/supabase.service.ts CHANGED
@@ -38,13 +38,13 @@ export class SupabaseService {
38
  });
39
 
40
  if (error) {
41
- logger.error('Error saving session:', error);
42
  throw error;
43
  }
44
 
45
  logger.info('Session saved successfully');
46
  } catch (error) {
47
- logger.error('Failed to save session:', error);
48
  throw error;
49
  }
50
  }
@@ -58,13 +58,13 @@ export class SupabaseService {
58
  .single();
59
 
60
  if (error) {
61
- logger.error('Error fetching session:', error);
62
  throw error;
63
  }
64
 
65
  return data;
66
  } catch (error) {
67
- logger.error('Failed to fetch session:', error);
68
  throw error;
69
  }
70
  }
@@ -80,7 +80,7 @@ export class SupabaseService {
80
 
81
  return { user_id: data.user.id };
82
  } catch (error) {
83
- logger.error('Token verification failed:', error);
84
  return null;
85
  }
86
  }
 
38
  });
39
 
40
  if (error) {
41
+ logger.error({ error }, 'Error saving session');
42
  throw error;
43
  }
44
 
45
  logger.info('Session saved successfully');
46
  } catch (error) {
47
+ logger.error({ error }, 'Failed to save session');
48
  throw error;
49
  }
50
  }
 
58
  .single();
59
 
60
  if (error) {
61
+ logger.error({ error }, 'Error fetching session');
62
  throw error;
63
  }
64
 
65
  return data;
66
  } catch (error) {
67
+ logger.error({ error }, 'Failed to fetch session');
68
  throw error;
69
  }
70
  }
 
80
 
81
  return { user_id: data.user.id };
82
  } catch (error) {
83
+ logger.error({ error }, 'Token verification failed');
84
  return null;
85
  }
86
  }
src/services/websocket.service.ts CHANGED
@@ -23,7 +23,8 @@ export class WebSocketConnectionManager {
23
  // Configuration
24
  private readonly INACTIVITY_TIMEOUT = 30 * 60 * 1000; // 30 minutes
25
  private readonly RATE_LIMIT = 100; // messages per minute
26
- private readonly RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
 
27
 
28
  constructor() {
29
  // Start cleanup task
@@ -56,7 +57,7 @@ export class WebSocketConnectionManager {
56
  });
57
 
58
  ws.on('error', (error) => {
59
- logger.error(`WebSocket error for session ${sessionId}:`, error);
60
  this.removeConnection(sessionId);
61
  });
62
  }
@@ -72,7 +73,7 @@ export class WebSocketConnectionManager {
72
  ws.close();
73
  }
74
  } catch (error) {
75
- logger.error(`Error closing WebSocket for session ${sessionId}:`, error);
76
  }
77
  }
78
 
@@ -116,11 +117,11 @@ export class WebSocketConnectionManager {
116
  const count = this.messageCount.get(sessionId) || 0;
117
  this.messageCount.set(sessionId, count + 1);
118
 
119
- logger.debug(`Message sent to session ${sessionId}:`, { type: message.type });
120
 
121
  return true;
122
  } catch (error) {
123
- logger.error(`Error sending message to session ${sessionId}:`, error);
124
  return false;
125
  }
126
  }
 
23
  // Configuration
24
  private readonly INACTIVITY_TIMEOUT = 30 * 60 * 1000; // 30 minutes
25
  private readonly RATE_LIMIT = 100; // messages per minute
26
+ // Rate limit window can be used for future rate limiting implementation
27
+ // private readonly RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
28
 
29
  constructor() {
30
  // Start cleanup task
 
57
  });
58
 
59
  ws.on('error', (error) => {
60
+ logger.error({ error, sessionId }, 'WebSocket error');
61
  this.removeConnection(sessionId);
62
  });
63
  }
 
73
  ws.close();
74
  }
75
  } catch (error) {
76
+ logger.error({ error, sessionId }, 'Error closing WebSocket');
77
  }
78
  }
79
 
 
117
  const count = this.messageCount.get(sessionId) || 0;
118
  this.messageCount.set(sessionId, count + 1);
119
 
120
+ logger.debug({ sessionId, messageType: message.type }, 'Message sent to session');
121
 
122
  return true;
123
  } catch (error) {
124
+ logger.error({ error, sessionId }, 'Error sending message');
125
  return false;
126
  }
127
  }
src/utils/swagger.ts CHANGED
@@ -1,24 +1,22 @@
1
- import { FastifySwaggerOptions } from '@fastify/swagger';
2
- import { FastifySwaggerUiOptions } from '@fastify/swagger-ui';
3
 
4
- export const swaggerOptions: FastifySwaggerOptions = {
5
- openapi: {
6
- openapi: '3.1.0',
7
- info: {
8
- title: 'MEDAGEN Backend API',
9
- description: 'AI Triage Assistant API với ReAct Agent và Gemini 2.5Flash',
10
- version: '2.0.0',
11
- contact: {
12
- name: 'MEDAGEN Team'
13
- }
14
- },
15
- servers: [
16
  {
17
  url: 'http://localhost:7860',
18
  description: 'Development server (Port 7860)'
19
  }
20
- ],
21
- tags: [
22
  {
23
  name: 'health',
24
  description: 'Health check endpoints'
@@ -47,8 +45,8 @@ export const swaggerOptions: FastifySwaggerOptions = {
47
  name: 'conversations',
48
  description: 'Conversation history endpoints'
49
  }
50
- ],
51
- components: {
52
  schemas: {
53
  HealthCheckRequest: {
54
  type: 'object',
@@ -327,7 +325,6 @@ export const swaggerOptions: FastifySwaggerOptions = {
327
  }
328
  }
329
  }
330
- }
331
  };
332
 
333
  export const swaggerUiOptions: FastifySwaggerUiOptions = {
@@ -339,7 +336,7 @@ export const swaggerUiOptions: FastifySwaggerUiOptions = {
339
  persistAuthorization: true
340
  },
341
  staticCSP: false, // Disable strict CSP to allow Swagger UI to fetch JSON
342
- transformSpecification: (swaggerObject, request, reply) => {
343
  // Ensure servers URL matches current request
344
  if (swaggerObject.servers && swaggerObject.servers.length > 0) {
345
  const protocol = request.headers['x-forwarded-proto'] || (request.protocol || 'http');
 
1
+ import type { FastifySwaggerUiOptions } from '@fastify/swagger-ui';
 
2
 
3
+ export const swaggerOptions = {
4
+ openapi: '3.1.0',
5
+ info: {
6
+ title: 'MEDAGEN Backend API',
7
+ description: 'AI Triage Assistant API với ReAct Agent và Gemini 2.5Flash',
8
+ version: '2.0.0',
9
+ contact: {
10
+ name: 'MEDAGEN Team'
11
+ }
12
+ },
13
+ servers: [
 
14
  {
15
  url: 'http://localhost:7860',
16
  description: 'Development server (Port 7860)'
17
  }
18
+ ],
19
+ tags: [
20
  {
21
  name: 'health',
22
  description: 'Health check endpoints'
 
45
  name: 'conversations',
46
  description: 'Conversation history endpoints'
47
  }
48
+ ],
49
+ components: {
50
  schemas: {
51
  HealthCheckRequest: {
52
  type: 'object',
 
325
  }
326
  }
327
  }
 
328
  };
329
 
330
  export const swaggerUiOptions: FastifySwaggerUiOptions = {
 
336
  persistAuthorization: true
337
  },
338
  staticCSP: false, // Disable strict CSP to allow Swagger UI to fetch JSON
339
+ transformSpecification: (swaggerObject, request) => {
340
  // Ensure servers URL matches current request
341
  if (swaggerObject.servers && swaggerObject.servers.length > 0) {
342
  const protocol = request.headers['x-forwarded-proto'] || (request.protocol || 'http');