@hed-hog/core 0.0.350 → 0.0.351
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-log/access-log.controller.d.ts +16 -0
- package/dist/access-log/access-log.controller.d.ts.map +1 -0
- package/dist/access-log/access-log.controller.js +44 -0
- package/dist/access-log/access-log.controller.js.map +1 -0
- package/dist/access-log/access-log.interceptor.d.ts +9 -0
- package/dist/access-log/access-log.interceptor.d.ts.map +1 -0
- package/dist/access-log/access-log.interceptor.js +46 -0
- package/dist/access-log/access-log.interceptor.js.map +1 -0
- package/dist/access-log/access-log.module.d.ts +3 -0
- package/dist/access-log/access-log.module.d.ts.map +1 -0
- package/dist/access-log/access-log.module.js +29 -0
- package/dist/access-log/access-log.module.js.map +1 -0
- package/dist/access-log/access-log.service.d.ts +47 -0
- package/dist/access-log/access-log.service.d.ts.map +1 -0
- package/dist/access-log/access-log.service.js +113 -0
- package/dist/access-log/access-log.service.js.map +1 -0
- package/dist/access-log/dto/query-access-log.dto.d.ts +8 -0
- package/dist/access-log/dto/query-access-log.dto.d.ts.map +1 -0
- package/dist/access-log/dto/query-access-log.dto.js +42 -0
- package/dist/access-log/dto/query-access-log.dto.js.map +1 -0
- package/dist/auth/auth.controller.d.ts +9 -9
- package/dist/auth/auth.service.d.ts +19 -19
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +0 -4
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +2 -2
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +0 -1
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/core/system.module.d.ts.map +1 -1
- package/dist/core/system.module.js +1 -0
- package/dist/core/system.module.js.map +1 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +12 -0
- package/dist/core.module.js.map +1 -1
- package/dist/dashboard/dashboard-user/dashboard-user.controller.d.ts +3 -3
- package/dist/dashboard/dashboard-user/dashboard-user.service.d.ts +3 -3
- package/dist/dashboard/dashboard.module.d.ts.map +1 -1
- package/dist/dashboard/dashboard.module.js +4 -1
- package/dist/dashboard/dashboard.module.js.map +1 -1
- package/dist/file/file.controller.js +0 -1
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +65 -0
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +151 -3
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/provider/abstract.provider.d.ts +8 -0
- package/dist/file/provider/abstract.provider.d.ts.map +1 -1
- package/dist/file/provider/abstract.provider.js.map +1 -1
- package/dist/file/provider/azure.provider.d.ts +4 -0
- package/dist/file/provider/azure.provider.d.ts.map +1 -1
- package/dist/file/provider/azure.provider.js +14 -0
- package/dist/file/provider/azure.provider.js.map +1 -1
- package/dist/file/provider/gcs.provider.d.ts +4 -0
- package/dist/file/provider/gcs.provider.d.ts.map +1 -1
- package/dist/file/provider/gcs.provider.js +14 -0
- package/dist/file/provider/gcs.provider.js.map +1 -1
- package/dist/file/provider/local.provider.d.ts +4 -0
- package/dist/file/provider/local.provider.d.ts.map +1 -1
- package/dist/file/provider/local.provider.js +19 -0
- package/dist/file/provider/local.provider.js.map +1 -1
- package/dist/file/provider/provider.factory.d.ts.map +1 -1
- package/dist/file/provider/provider.factory.js +3 -0
- package/dist/file/provider/provider.factory.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts +4 -0
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +17 -0
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/mcp/mcp-audit.service.d.ts +3 -1
- package/dist/mcp/mcp-audit.service.d.ts.map +1 -1
- package/dist/mcp/mcp-audit.service.js +14 -2
- package/dist/mcp/mcp-audit.service.js.map +1 -1
- package/dist/mcp/mcp.controller.d.ts +3 -1
- package/dist/mcp/mcp.controller.d.ts.map +1 -1
- package/dist/mcp/mcp.controller.js +10 -2
- package/dist/mcp/mcp.controller.js.map +1 -1
- package/dist/mcp/mcp.module.d.ts.map +1 -1
- package/dist/mcp/mcp.module.js +72 -0
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/mcp/tools/core-ai.mcp-tools.d.ts +45 -0
- package/dist/mcp/tools/core-ai.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-ai.mcp-tools.js +214 -0
- package/dist/mcp/tools/core-ai.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts +21 -0
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.js +89 -0
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts +29 -0
- package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-dashboard.mcp-tools.js +149 -0
- package/dist/mcp/tools/core-dashboard.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts +30 -0
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.js +185 -0
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-files.mcp-tools.d.ts +26 -0
- package/dist/mcp/tools/core-files.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-files.mcp-tools.js +140 -0
- package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts +26 -0
- package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-integration-events.mcp-tools.js +168 -0
- package/dist/mcp/tools/core-integration-events.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +32 -0
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js +178 -0
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts +14 -0
- package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-mail-sent.mcp-tools.js +106 -0
- package/dist/mcp/tools/core-mail-sent.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-mail.mcp-tools.d.ts +36 -0
- package/dist/mcp/tools/core-mail.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-mail.mcp-tools.js +184 -0
- package/dist/mcp/tools/core-mail.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts +21 -0
- package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-mcp-chat.mcp-tools.js +118 -0
- package/dist/mcp/tools/core-mcp-chat.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-menus.mcp-tools.d.ts +40 -0
- package/dist/mcp/tools/core-menus.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-menus.mcp-tools.js +268 -0
- package/dist/mcp/tools/core-menus.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-notifications.mcp-tools.d.ts +31 -0
- package/dist/mcp/tools/core-notifications.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-notifications.mcp-tools.js +178 -0
- package/dist/mcp/tools/core-notifications.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-profile.mcp-tools.d.ts +20 -0
- package/dist/mcp/tools/core-profile.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-profile.mcp-tools.js +111 -0
- package/dist/mcp/tools/core-profile.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-roles.mcp-tools.d.ts +70 -0
- package/dist/mcp/tools/core-roles.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-roles.mcp-tools.js +355 -0
- package/dist/mcp/tools/core-roles.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-routes.mcp-tools.d.ts +52 -0
- package/dist/mcp/tools/core-routes.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-routes.mcp-tools.js +254 -0
- package/dist/mcp/tools/core-routes.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-screens.mcp-tools.d.ts +47 -0
- package/dist/mcp/tools/core-screens.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-screens.mcp-tools.js +250 -0
- package/dist/mcp/tools/core-screens.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-sessions.mcp-tools.d.ts +20 -0
- package/dist/mcp/tools/core-sessions.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-sessions.mcp-tools.js +128 -0
- package/dist/mcp/tools/core-sessions.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-settings.mcp-tools.d.ts +25 -0
- package/dist/mcp/tools/core-settings.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-settings.mcp-tools.js +138 -0
- package/dist/mcp/tools/core-settings.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-system.mcp-tools.d.ts +8 -0
- package/dist/mcp/tools/core-system.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-system.mcp-tools.js +40 -0
- package/dist/mcp/tools/core-system.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts +46 -0
- package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-webhooks.mcp-tools.js +266 -0
- package/dist/mcp/tools/core-webhooks.mcp-tools.js.map +1 -0
- package/dist/mcp-chat/mcp-chat.controller.d.ts +1 -1
- package/dist/mcp-chat/mcp-chat.service.d.ts +25 -2
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +666 -36
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/notification/notification.controller.d.ts +8 -4
- package/dist/notification/notification.controller.d.ts.map +1 -1
- package/dist/notification/notification.controller.js +12 -0
- package/dist/notification/notification.controller.js.map +1 -1
- package/dist/notification/notification.service.d.ts +19 -5
- package/dist/notification/notification.service.d.ts.map +1 -1
- package/dist/notification/notification.service.js +155 -9
- package/dist/notification/notification.service.js.map +1 -1
- package/dist/pdf/pdf.service.js +1 -1
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/profile/profile.controller.d.ts +3 -3
- package/dist/profile/profile.controller.js +0 -1
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/profile/profile.module.d.ts.map +1 -1
- package/dist/profile/profile.module.js +1 -0
- package/dist/profile/profile.module.js.map +1 -1
- package/dist/profile/profile.service.d.ts +3 -3
- package/dist/session/session.controller.d.ts +2 -2
- package/dist/session/session.service.d.ts +6 -6
- package/dist/setting/setting.controller.d.ts +1 -1
- package/dist/setting/setting.service.d.ts +1 -1
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +1 -4
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/task/task.service.d.ts +1 -0
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +25 -0
- package/dist/task/task.service.js.map +1 -1
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +0 -1
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +9 -9
- package/dist/user/user.service.d.ts +21 -21
- package/dist/webhook-integration/dto/webhook-action.dto.d.ts +22 -0
- package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -0
- package/dist/webhook-integration/dto/webhook-action.dto.js +120 -0
- package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts +7 -5
- package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.js +18 -5
- package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts +14 -5
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +174 -41
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/hedhog/data/menu.yaml +15 -0
- package/hedhog/data/route.yaml +1304 -0
- package/hedhog/data/setting_group.yaml +67 -0
- package/hedhog/data/setting_subgroup.yaml +16 -0
- package/hedhog/frontend/app/access-log/page.tsx.ejs +772 -0
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +10 -3
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +33 -13
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +12 -5
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +12 -5
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +1817 -356
- package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +43 -5
- package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +35 -7
- package/hedhog/frontend/app/menu/page.tsx.ejs +9 -3
- package/hedhog/frontend/app/roles/page.tsx.ejs +18 -6
- package/hedhog/frontend/app/routes/page.tsx.ejs +9 -3
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +344 -171
- package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +310 -304
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +25 -7
- package/hedhog/frontend/messages/en.json +104 -3
- package/hedhog/frontend/messages/pt.json +104 -3
- package/hedhog/table/access_log.yaml +31 -0
- package/package.json +5 -4
- package/src/access-log/access-log.controller.ts +29 -0
- package/src/access-log/access-log.interceptor.ts +38 -0
- package/src/access-log/access-log.module.ts +16 -0
- package/src/access-log/access-log.service.ts +129 -0
- package/src/access-log/dto/query-access-log.dto.ts +23 -0
- package/src/auth/auth.service.ts +0 -7
- package/src/challenge/challenge.service.ts +0 -2
- package/src/core/system.module.ts +1 -0
- package/src/core.module.ts +12 -0
- package/src/dashboard/dashboard.module.ts +4 -1
- package/src/file/file.controller.ts +14 -14
- package/src/file/file.service.ts +198 -4
- package/src/file/provider/abstract.provider.ts +10 -0
- package/src/file/provider/azure.provider.ts +24 -0
- package/src/file/provider/gcs.provider.ts +20 -0
- package/src/file/provider/local.provider.ts +30 -2
- package/src/file/provider/provider.factory.ts +3 -0
- package/src/file/provider/s3.provider.ts +22 -0
- package/src/mcp/mcp-audit.service.ts +47 -36
- package/src/mcp/mcp.controller.ts +40 -30
- package/src/mcp/mcp.module.ts +72 -0
- package/src/mcp/tools/core-ai.mcp-tools.ts +156 -0
- package/src/mcp/tools/core-dashboard-items.mcp-tools.ts +63 -0
- package/src/mcp/tools/core-dashboard.mcp-tools.ts +103 -0
- package/src/mcp/tools/core-event-webhooks.mcp-tools.ts +134 -0
- package/src/mcp/tools/core-files.mcp-tools.ts +105 -0
- package/src/mcp/tools/core-integration-events.mcp-tools.ts +118 -0
- package/src/mcp/tools/core-mail-profile.mcp-tools.ts +128 -0
- package/src/mcp/tools/core-mail-sent.mcp-tools.ts +76 -0
- package/src/mcp/tools/core-mail.mcp-tools.ts +130 -0
- package/src/mcp/tools/core-mcp-chat.mcp-tools.ts +81 -0
- package/src/mcp/tools/core-menus.mcp-tools.ts +197 -0
- package/src/mcp/tools/core-notifications.mcp-tools.ts +128 -0
- package/src/mcp/tools/core-profile.mcp-tools.ts +74 -0
- package/src/mcp/tools/core-roles.mcp-tools.ts +288 -0
- package/src/mcp/tools/core-routes.mcp-tools.ts +196 -0
- package/src/mcp/tools/core-screens.mcp-tools.ts +186 -0
- package/src/mcp/tools/core-sessions.mcp-tools.ts +97 -0
- package/src/mcp/tools/core-settings.mcp-tools.ts +171 -53
- package/src/mcp/tools/core-system.mcp-tools.ts +19 -0
- package/src/mcp/tools/core-webhooks.mcp-tools.ts +199 -0
- package/src/mcp-chat/mcp-chat.service.ts +887 -38
- package/src/notification/notification.controller.ts +20 -8
- package/src/notification/notification.service.ts +209 -9
- package/src/pdf/pdf.service.ts +1 -1
- package/src/profile/profile.controller.ts +1 -1
- package/src/profile/profile.module.ts +1 -0
- package/src/setting/setting.service.ts +8 -11
- package/src/task/task.service.ts +31 -0
- package/src/token/token.service.ts +0 -2
- package/src/webhook-integration/dto/webhook-action.dto.ts +96 -0
- package/src/webhook-integration/webhook-integration.controller.ts +124 -103
- package/src/webhook-integration/webhook-integration.service.ts +267 -64
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
NotFoundException,
|
|
12
12
|
} from '@nestjs/common';
|
|
13
13
|
import axios from 'axios';
|
|
14
|
-
import { McpRegistryService } from '../mcp/mcp-registry.service';
|
|
14
|
+
import { McpRegistryService, McpToolEntry } from '../mcp/mcp-registry.service';
|
|
15
15
|
import { McpContext } from '../mcp/types/mcp-context.type';
|
|
16
16
|
import { SettingService } from '../setting/setting.service';
|
|
17
17
|
import { CreateConversationDTO } from './dto/create-conversation.dto';
|
|
@@ -37,16 +37,52 @@ interface OpenAiToolDefinition {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
interface GeminiFunctionDeclaration {
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
parameters: Record<string, any>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface GeminiPart {
|
|
47
|
+
text?: string;
|
|
48
|
+
functionCall?: { name: string; args: Record<string, any> };
|
|
49
|
+
functionResponse?: { name: string; response: { output: string } };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface GeminiContent {
|
|
53
|
+
role: 'user' | 'model';
|
|
54
|
+
parts: GeminiPart[];
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
interface ScoredTool {
|
|
41
58
|
name: string;
|
|
59
|
+
domain: string;
|
|
42
60
|
readOnly: boolean;
|
|
43
61
|
priorityBucket: number;
|
|
44
62
|
keywordBoost: number;
|
|
63
|
+
matchScore: number;
|
|
45
64
|
index: number;
|
|
46
65
|
tool: OpenAiToolDefinition;
|
|
47
66
|
}
|
|
48
67
|
|
|
68
|
+
interface ToolSelectionResult<TTool> {
|
|
69
|
+
tools: TTool[];
|
|
70
|
+
clarification?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type ConversationHistoryItem = {
|
|
74
|
+
role: string;
|
|
75
|
+
content: string;
|
|
76
|
+
tool_name: string | null;
|
|
77
|
+
tool_call_id: string | null;
|
|
78
|
+
};
|
|
79
|
+
|
|
49
80
|
const MAX_OPENAI_TOOLS = 128;
|
|
81
|
+
const TARGET_SELECTED_TOOLS = 112;
|
|
82
|
+
const MAX_ITERATIONS = 20;
|
|
83
|
+
const FORCE_FINISH_THRESHOLD = 2;
|
|
84
|
+
const MIN_DOMAIN_CONFIDENCE = 2;
|
|
85
|
+
const AMBIGUOUS_DOMAIN_DELTA = 1;
|
|
50
86
|
|
|
51
87
|
const SYSTEM_PROMPT =
|
|
52
88
|
'You are a helpful assistant with access to tools from the HedHog system. ' +
|
|
@@ -56,6 +92,144 @@ const SYSTEM_PROMPT =
|
|
|
56
92
|
const sanitize = (n: string) => n.replace(/\./g, '__');
|
|
57
93
|
const restore = (n: string) => n.replace(/__/g, '.');
|
|
58
94
|
|
|
95
|
+
const DOMAIN_KEYWORDS: Record<string, string[]> = {
|
|
96
|
+
finance: [
|
|
97
|
+
'finance',
|
|
98
|
+
'financeiro',
|
|
99
|
+
'financeira',
|
|
100
|
+
'financas',
|
|
101
|
+
'finanças',
|
|
102
|
+
'banco',
|
|
103
|
+
'bancos',
|
|
104
|
+
'bank',
|
|
105
|
+
'banks',
|
|
106
|
+
'conta',
|
|
107
|
+
'contas',
|
|
108
|
+
'account',
|
|
109
|
+
'accounts',
|
|
110
|
+
'conta bancaria',
|
|
111
|
+
'contas bancarias',
|
|
112
|
+
'conta bancária',
|
|
113
|
+
'contas bancárias',
|
|
114
|
+
'extrato',
|
|
115
|
+
'extratos',
|
|
116
|
+
'statement',
|
|
117
|
+
'statements',
|
|
118
|
+
'pagamento',
|
|
119
|
+
'pagamentos',
|
|
120
|
+
'payment',
|
|
121
|
+
'payments',
|
|
122
|
+
'recebimento',
|
|
123
|
+
'recebimentos',
|
|
124
|
+
'receivable',
|
|
125
|
+
'payable',
|
|
126
|
+
'currency',
|
|
127
|
+
'moeda',
|
|
128
|
+
'transferencia',
|
|
129
|
+
'transferência',
|
|
130
|
+
],
|
|
131
|
+
operations: [
|
|
132
|
+
'operations',
|
|
133
|
+
'operacoes',
|
|
134
|
+
'operações',
|
|
135
|
+
'tarefa',
|
|
136
|
+
'tarefas',
|
|
137
|
+
'task',
|
|
138
|
+
'tasks',
|
|
139
|
+
'projeto',
|
|
140
|
+
'projetos',
|
|
141
|
+
'project',
|
|
142
|
+
'projects',
|
|
143
|
+
'timesheet',
|
|
144
|
+
'apontamento',
|
|
145
|
+
'aprovacao',
|
|
146
|
+
'aprovação',
|
|
147
|
+
'approval',
|
|
148
|
+
'colaborador',
|
|
149
|
+
'collaborator',
|
|
150
|
+
],
|
|
151
|
+
lms: [
|
|
152
|
+
'lms',
|
|
153
|
+
'curso',
|
|
154
|
+
'cursos',
|
|
155
|
+
'course',
|
|
156
|
+
'courses',
|
|
157
|
+
'aula',
|
|
158
|
+
'aulas',
|
|
159
|
+
'lesson',
|
|
160
|
+
'lessons',
|
|
161
|
+
'treinamento',
|
|
162
|
+
'training',
|
|
163
|
+
'turma',
|
|
164
|
+
'class',
|
|
165
|
+
'aluno',
|
|
166
|
+
'student',
|
|
167
|
+
'prova',
|
|
168
|
+
'exam',
|
|
169
|
+
],
|
|
170
|
+
core: [
|
|
171
|
+
'core',
|
|
172
|
+
'usuario',
|
|
173
|
+
'usuário',
|
|
174
|
+
'usuarios',
|
|
175
|
+
'usuários',
|
|
176
|
+
'user',
|
|
177
|
+
'users',
|
|
178
|
+
'perfil',
|
|
179
|
+
'profile',
|
|
180
|
+
'role',
|
|
181
|
+
'roles',
|
|
182
|
+
'menu',
|
|
183
|
+
'menus',
|
|
184
|
+
'rota',
|
|
185
|
+
'rotas',
|
|
186
|
+
'route',
|
|
187
|
+
'routes',
|
|
188
|
+
'configuracao',
|
|
189
|
+
'configuração',
|
|
190
|
+
'setting',
|
|
191
|
+
'settings',
|
|
192
|
+
],
|
|
193
|
+
contact: [
|
|
194
|
+
'contato',
|
|
195
|
+
'contatos',
|
|
196
|
+
'contact',
|
|
197
|
+
'contacts',
|
|
198
|
+
'pessoa',
|
|
199
|
+
'pessoas',
|
|
200
|
+
'person',
|
|
201
|
+
'proposal',
|
|
202
|
+
'proposta',
|
|
203
|
+
],
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const ACTION_KEYWORDS = [
|
|
207
|
+
'listar',
|
|
208
|
+
'lista',
|
|
209
|
+
'buscar',
|
|
210
|
+
'procurar',
|
|
211
|
+
'consultar',
|
|
212
|
+
'mostrar',
|
|
213
|
+
'ver',
|
|
214
|
+
'criar',
|
|
215
|
+
'adicionar',
|
|
216
|
+
'atualizar',
|
|
217
|
+
'alterar',
|
|
218
|
+
'remover',
|
|
219
|
+
'deletar',
|
|
220
|
+
'excluir',
|
|
221
|
+
'list',
|
|
222
|
+
'search',
|
|
223
|
+
'find',
|
|
224
|
+
'show',
|
|
225
|
+
'get',
|
|
226
|
+
'create',
|
|
227
|
+
'add',
|
|
228
|
+
'update',
|
|
229
|
+
'delete',
|
|
230
|
+
'remove',
|
|
231
|
+
];
|
|
232
|
+
|
|
59
233
|
@Injectable()
|
|
60
234
|
export class McpChatService {
|
|
61
235
|
private readonly logger = new Logger(McpChatService.name);
|
|
@@ -107,9 +281,14 @@ export class McpChatService {
|
|
|
107
281
|
locale: string,
|
|
108
282
|
message: string,
|
|
109
283
|
) {
|
|
284
|
+
const cfg = await this.setting.getSettingValues(['mcp-enabled']);
|
|
285
|
+
if (cfg['mcp-enabled'] === false) {
|
|
286
|
+
throw new BadRequestException('MCP server is disabled.');
|
|
287
|
+
}
|
|
288
|
+
|
|
110
289
|
await this.assertOwnership(conversationId, userId);
|
|
111
290
|
|
|
112
|
-
const apiKey = await this.
|
|
291
|
+
const { provider, apiKey } = await this.getProviderKey();
|
|
113
292
|
|
|
114
293
|
// Save user message
|
|
115
294
|
await this.prisma.mcp_conversation_message.create({
|
|
@@ -131,7 +310,21 @@ export class McpChatService {
|
|
|
131
310
|
});
|
|
132
311
|
}
|
|
133
312
|
|
|
134
|
-
|
|
313
|
+
if (provider === 'gemini') {
|
|
314
|
+
return this.runGeminiLoop(conversationId, userId, locale, message, apiKey);
|
|
315
|
+
}
|
|
316
|
+
return this.runOpenAiLoop(conversationId, userId, locale, message, apiKey);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── OpenAI loop ──────────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
private async runOpenAiLoop(
|
|
322
|
+
conversationId: number,
|
|
323
|
+
userId: number,
|
|
324
|
+
locale: string,
|
|
325
|
+
message: string,
|
|
326
|
+
apiKey: string,
|
|
327
|
+
) {
|
|
135
328
|
const history = await this.prisma.mcp_conversation_message.findMany({
|
|
136
329
|
where: { conversation_id: conversationId },
|
|
137
330
|
orderBy: { created_at: 'asc' },
|
|
@@ -142,12 +335,24 @@ export class McpChatService {
|
|
|
142
335
|
...this.toOpenAiMessages(history),
|
|
143
336
|
];
|
|
144
337
|
|
|
145
|
-
|
|
146
|
-
|
|
338
|
+
const toolSelection = await this.buildOpenAiTools(
|
|
339
|
+
message,
|
|
340
|
+
conversationId,
|
|
341
|
+
userId,
|
|
342
|
+
history,
|
|
343
|
+
);
|
|
344
|
+
if (toolSelection.clarification) {
|
|
345
|
+
return this.recordAssistantAndReturnHistory(
|
|
346
|
+
conversationId,
|
|
347
|
+
toolSelection.clarification,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
const tools = toolSelection.tools;
|
|
147
351
|
|
|
148
|
-
|
|
149
|
-
let maxIterations =
|
|
352
|
+
const callSignatures = new Set<string>();
|
|
353
|
+
let maxIterations = MAX_ITERATIONS;
|
|
150
354
|
while (maxIterations-- > 0) {
|
|
355
|
+
const forceFinish = maxIterations < FORCE_FINISH_THRESHOLD;
|
|
151
356
|
let response: any;
|
|
152
357
|
try {
|
|
153
358
|
response = await axios.post(
|
|
@@ -155,8 +360,8 @@ export class McpChatService {
|
|
|
155
360
|
{
|
|
156
361
|
model: 'gpt-4o-mini',
|
|
157
362
|
messages: openAiMessages,
|
|
158
|
-
tools: tools.length > 0 ? tools : undefined,
|
|
159
|
-
tool_choice: tools.length > 0 ? 'auto' : undefined,
|
|
363
|
+
tools: !forceFinish && tools.length > 0 ? tools : undefined,
|
|
364
|
+
tool_choice: !forceFinish && tools.length > 0 ? 'auto' : undefined,
|
|
160
365
|
},
|
|
161
366
|
{
|
|
162
367
|
headers: {
|
|
@@ -186,15 +391,13 @@ export class McpChatService {
|
|
|
186
391
|
const choice = response.data?.choices?.[0];
|
|
187
392
|
const assistantMsg = choice?.message;
|
|
188
393
|
|
|
189
|
-
if (assistantMsg?.tool_calls?.length) {
|
|
190
|
-
// Add assistant tool-call message to context
|
|
394
|
+
if (assistantMsg?.tool_calls?.length && !forceFinish) {
|
|
191
395
|
openAiMessages.push({
|
|
192
396
|
role: 'assistant',
|
|
193
397
|
content: null,
|
|
194
398
|
tool_calls: assistantMsg.tool_calls,
|
|
195
399
|
});
|
|
196
400
|
|
|
197
|
-
// Execute each tool call
|
|
198
401
|
for (const toolCall of assistantMsg.tool_calls as OpenAiToolCall[]) {
|
|
199
402
|
const originalName = restore(toolCall.function.name);
|
|
200
403
|
const argsJson = toolCall.function.arguments;
|
|
@@ -205,7 +408,6 @@ export class McpChatService {
|
|
|
205
408
|
args = {};
|
|
206
409
|
}
|
|
207
410
|
|
|
208
|
-
// Persist tool_call message
|
|
209
411
|
await this.prisma.mcp_conversation_message.create({
|
|
210
412
|
data: {
|
|
211
413
|
conversation_id: conversationId,
|
|
@@ -216,7 +418,31 @@ export class McpChatService {
|
|
|
216
418
|
},
|
|
217
419
|
});
|
|
218
420
|
|
|
219
|
-
|
|
421
|
+
const sig = `${toolCall.function.name}:${argsJson}`;
|
|
422
|
+
if (callSignatures.has(sig)) {
|
|
423
|
+
this.logger.warn(
|
|
424
|
+
`MCP chat duplicate tool call detected — aborting re-execution. ` +
|
|
425
|
+
`conversationId=${conversationId} tool=${originalName}`,
|
|
426
|
+
);
|
|
427
|
+
const dupResult = JSON.stringify({ error: 'Duplicate tool call detected, aborting loop.' });
|
|
428
|
+
await this.prisma.mcp_conversation_message.create({
|
|
429
|
+
data: {
|
|
430
|
+
conversation_id: conversationId,
|
|
431
|
+
role: 'tool_result',
|
|
432
|
+
content: dupResult,
|
|
433
|
+
tool_name: originalName,
|
|
434
|
+
tool_call_id: toolCall.id,
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
openAiMessages.push({
|
|
438
|
+
role: 'tool',
|
|
439
|
+
tool_call_id: toolCall.id,
|
|
440
|
+
content: dupResult,
|
|
441
|
+
});
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
callSignatures.add(sig);
|
|
445
|
+
|
|
220
446
|
let resultContent: string;
|
|
221
447
|
const entry = this.registry.get(originalName);
|
|
222
448
|
if (entry) {
|
|
@@ -236,7 +462,6 @@ export class McpChatService {
|
|
|
236
462
|
resultContent = JSON.stringify({ error: `Tool '${originalName}' not found` });
|
|
237
463
|
}
|
|
238
464
|
|
|
239
|
-
// Persist tool_result message
|
|
240
465
|
await this.prisma.mcp_conversation_message.create({
|
|
241
466
|
data: {
|
|
242
467
|
conversation_id: conversationId,
|
|
@@ -247,7 +472,6 @@ export class McpChatService {
|
|
|
247
472
|
},
|
|
248
473
|
});
|
|
249
474
|
|
|
250
|
-
// Add tool result to context for next iteration
|
|
251
475
|
openAiMessages.push({
|
|
252
476
|
role: 'tool',
|
|
253
477
|
tool_call_id: toolCall.id,
|
|
@@ -255,10 +479,9 @@ export class McpChatService {
|
|
|
255
479
|
});
|
|
256
480
|
}
|
|
257
481
|
|
|
258
|
-
continue;
|
|
482
|
+
continue;
|
|
259
483
|
}
|
|
260
484
|
|
|
261
|
-
// Final assistant response
|
|
262
485
|
const finalContent: string = assistantMsg?.content || '';
|
|
263
486
|
await this.prisma.mcp_conversation_message.create({
|
|
264
487
|
data: {
|
|
@@ -277,6 +500,165 @@ export class McpChatService {
|
|
|
277
500
|
throw new BadRequestException('Maximum tool call iterations exceeded.');
|
|
278
501
|
}
|
|
279
502
|
|
|
503
|
+
// ── Gemini loop ──────────────────────────────────────────────────────────
|
|
504
|
+
|
|
505
|
+
private async runGeminiLoop(
|
|
506
|
+
conversationId: number,
|
|
507
|
+
userId: number,
|
|
508
|
+
locale: string,
|
|
509
|
+
message: string,
|
|
510
|
+
apiKey: string,
|
|
511
|
+
) {
|
|
512
|
+
const history = await this.prisma.mcp_conversation_message.findMany({
|
|
513
|
+
where: { conversation_id: conversationId },
|
|
514
|
+
orderBy: { created_at: 'asc' },
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const geminiContents: GeminiContent[] = this.toGeminiContents(history);
|
|
518
|
+
const toolSelection = await this.buildGeminiTools(
|
|
519
|
+
message,
|
|
520
|
+
conversationId,
|
|
521
|
+
userId,
|
|
522
|
+
history,
|
|
523
|
+
);
|
|
524
|
+
if (toolSelection.clarification) {
|
|
525
|
+
return this.recordAssistantAndReturnHistory(
|
|
526
|
+
conversationId,
|
|
527
|
+
toolSelection.clarification,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
const functionDeclarations = toolSelection.tools;
|
|
531
|
+
|
|
532
|
+
let maxIterations = MAX_ITERATIONS;
|
|
533
|
+
let callIndex = 0;
|
|
534
|
+
|
|
535
|
+
while (maxIterations-- > 0) {
|
|
536
|
+
const forceFinish = maxIterations < FORCE_FINISH_THRESHOLD;
|
|
537
|
+
let response: any;
|
|
538
|
+
try {
|
|
539
|
+
response = await axios.post(
|
|
540
|
+
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`,
|
|
541
|
+
{
|
|
542
|
+
systemInstruction: { parts: [{ text: SYSTEM_PROMPT }] },
|
|
543
|
+
contents: geminiContents,
|
|
544
|
+
tools:
|
|
545
|
+
!forceFinish && functionDeclarations.length > 0
|
|
546
|
+
? [{ functionDeclarations }]
|
|
547
|
+
: undefined,
|
|
548
|
+
},
|
|
549
|
+
{ headers: { 'Content-Type': 'application/json' } },
|
|
550
|
+
);
|
|
551
|
+
} catch (err: any) {
|
|
552
|
+
const status = err?.response?.status;
|
|
553
|
+
const errData = err?.response?.data;
|
|
554
|
+
if (status === 429) {
|
|
555
|
+
throw new HttpException(
|
|
556
|
+
errData?.error?.message ?? 'Gemini rate limit exceeded. Please try again later.',
|
|
557
|
+
HttpStatus.TOO_MANY_REQUESTS,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
if (status === 400) {
|
|
561
|
+
throw new BadRequestException(
|
|
562
|
+
errData?.error?.message ?? 'Invalid Gemini API request.',
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
if (status === 401 || status === 403) {
|
|
566
|
+
throw new BadRequestException('Invalid Gemini API key.');
|
|
567
|
+
}
|
|
568
|
+
throw new HttpException(
|
|
569
|
+
errData?.error?.message ?? 'Gemini API error.',
|
|
570
|
+
status ?? HttpStatus.BAD_GATEWAY,
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const candidate = response.data?.candidates?.[0];
|
|
575
|
+
const parts: GeminiPart[] = candidate?.content?.parts ?? [];
|
|
576
|
+
|
|
577
|
+
const functionCallParts = parts.filter((p) => p.functionCall);
|
|
578
|
+
const textParts = parts.filter((p) => p.text);
|
|
579
|
+
|
|
580
|
+
if (functionCallParts.length > 0 && !forceFinish) {
|
|
581
|
+
geminiContents.push({ role: 'model', parts: functionCallParts });
|
|
582
|
+
|
|
583
|
+
const functionResponseParts: GeminiPart[] = [];
|
|
584
|
+
|
|
585
|
+
for (const part of functionCallParts) {
|
|
586
|
+
const { name, args } = part.functionCall!;
|
|
587
|
+
const originalName = restore(name);
|
|
588
|
+
const argsJson = JSON.stringify(args ?? {});
|
|
589
|
+
const toolCallId = `gemini-${callIndex++}`;
|
|
590
|
+
|
|
591
|
+
await this.prisma.mcp_conversation_message.create({
|
|
592
|
+
data: {
|
|
593
|
+
conversation_id: conversationId,
|
|
594
|
+
role: 'tool_call',
|
|
595
|
+
content: argsJson,
|
|
596
|
+
tool_name: originalName,
|
|
597
|
+
tool_call_id: toolCallId,
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
let resultContent: string;
|
|
602
|
+
const entry = this.registry.get(originalName);
|
|
603
|
+
if (entry) {
|
|
604
|
+
const context: McpContext = {
|
|
605
|
+
userId,
|
|
606
|
+
sessionId: '',
|
|
607
|
+
locale,
|
|
608
|
+
toolName: originalName,
|
|
609
|
+
};
|
|
610
|
+
try {
|
|
611
|
+
const result = await entry.handler(args ?? {}, context);
|
|
612
|
+
resultContent = JSON.stringify(result);
|
|
613
|
+
} catch (e: any) {
|
|
614
|
+
resultContent = JSON.stringify({ error: e.message });
|
|
615
|
+
}
|
|
616
|
+
} else {
|
|
617
|
+
resultContent = JSON.stringify({ error: `Tool '${originalName}' not found` });
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
await this.prisma.mcp_conversation_message.create({
|
|
621
|
+
data: {
|
|
622
|
+
conversation_id: conversationId,
|
|
623
|
+
role: 'tool_result',
|
|
624
|
+
content: resultContent,
|
|
625
|
+
tool_name: originalName,
|
|
626
|
+
tool_call_id: toolCallId,
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
functionResponseParts.push({
|
|
631
|
+
functionResponse: {
|
|
632
|
+
name,
|
|
633
|
+
response: { output: resultContent },
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
geminiContents.push({ role: 'user', parts: functionResponseParts });
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const finalContent = textParts.map((p) => p.text).join('');
|
|
643
|
+
await this.prisma.mcp_conversation_message.create({
|
|
644
|
+
data: {
|
|
645
|
+
conversation_id: conversationId,
|
|
646
|
+
role: 'assistant',
|
|
647
|
+
content: finalContent,
|
|
648
|
+
},
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
return this.prisma.mcp_conversation_message.findMany({
|
|
652
|
+
where: { conversation_id: conversationId },
|
|
653
|
+
orderBy: { created_at: 'asc' },
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
throw new BadRequestException('Maximum tool call iterations exceeded.');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
661
|
+
|
|
280
662
|
private toOpenAiMessages(
|
|
281
663
|
history: Array<{
|
|
282
664
|
role: string;
|
|
@@ -319,6 +701,63 @@ export class McpChatService {
|
|
|
319
701
|
return messages;
|
|
320
702
|
}
|
|
321
703
|
|
|
704
|
+
private toGeminiContents(
|
|
705
|
+
history: Array<{
|
|
706
|
+
role: string;
|
|
707
|
+
content: string;
|
|
708
|
+
tool_name: string | null;
|
|
709
|
+
tool_call_id: string | null;
|
|
710
|
+
}>,
|
|
711
|
+
): GeminiContent[] {
|
|
712
|
+
const contents: GeminiContent[] = [];
|
|
713
|
+
let i = 0;
|
|
714
|
+
|
|
715
|
+
while (i < history.length) {
|
|
716
|
+
const msg = history[i];
|
|
717
|
+
|
|
718
|
+
if (msg.role === 'user') {
|
|
719
|
+
contents.push({ role: 'user', parts: [{ text: msg.content }] });
|
|
720
|
+
i++;
|
|
721
|
+
} else if (msg.role === 'assistant') {
|
|
722
|
+
contents.push({ role: 'model', parts: [{ text: msg.content }] });
|
|
723
|
+
i++;
|
|
724
|
+
} else if (msg.role === 'tool_call') {
|
|
725
|
+
const functionCallParts: GeminiPart[] = [];
|
|
726
|
+
while (i < history.length && history[i].role === 'tool_call') {
|
|
727
|
+
const m = history[i];
|
|
728
|
+
let args: Record<string, any> = {};
|
|
729
|
+
try {
|
|
730
|
+
args = JSON.parse(m.content);
|
|
731
|
+
} catch {
|
|
732
|
+
args = {};
|
|
733
|
+
}
|
|
734
|
+
functionCallParts.push({
|
|
735
|
+
functionCall: { name: sanitize(m.tool_name ?? ''), args },
|
|
736
|
+
});
|
|
737
|
+
i++;
|
|
738
|
+
}
|
|
739
|
+
contents.push({ role: 'model', parts: functionCallParts });
|
|
740
|
+
} else if (msg.role === 'tool_result') {
|
|
741
|
+
const functionResponseParts: GeminiPart[] = [];
|
|
742
|
+
while (i < history.length && history[i].role === 'tool_result') {
|
|
743
|
+
const m = history[i];
|
|
744
|
+
functionResponseParts.push({
|
|
745
|
+
functionResponse: {
|
|
746
|
+
name: sanitize(m.tool_name ?? ''),
|
|
747
|
+
response: { output: m.content },
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
i++;
|
|
751
|
+
}
|
|
752
|
+
contents.push({ role: 'user', parts: functionResponseParts });
|
|
753
|
+
} else {
|
|
754
|
+
i++;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return contents;
|
|
759
|
+
}
|
|
760
|
+
|
|
322
761
|
private async assertOwnership(conversationId: number, userId: number) {
|
|
323
762
|
const conv = await this.prisma.mcp_conversation.findUnique({
|
|
324
763
|
where: { id: conversationId },
|
|
@@ -332,32 +771,115 @@ export class McpChatService {
|
|
|
332
771
|
}
|
|
333
772
|
}
|
|
334
773
|
|
|
335
|
-
private async
|
|
336
|
-
const settings = await this.setting.getSettingValues([
|
|
337
|
-
|
|
774
|
+
private async getProviderKey(): Promise<{ provider: 'openai' | 'gemini'; apiKey: string }> {
|
|
775
|
+
const settings = await this.setting.getSettingValues([
|
|
776
|
+
'mcp-ai-provider',
|
|
777
|
+
'ai-openai-api-key',
|
|
778
|
+
'ai-gemini-api-key',
|
|
779
|
+
]);
|
|
780
|
+
const provider = ((settings['mcp-ai-provider'] as string) || 'openai') as 'openai' | 'gemini';
|
|
781
|
+
|
|
782
|
+
if (provider === 'gemini') {
|
|
783
|
+
const key = settings['ai-gemini-api-key'] as string;
|
|
784
|
+
if (!key) {
|
|
785
|
+
throw new BadRequestException(
|
|
786
|
+
'Gemini API key is not configured. Please set it in Settings → AI → Gemini API Key.',
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
return { provider: 'gemini', apiKey: key };
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const key = settings['ai-openai-api-key'] as string;
|
|
338
793
|
if (!key) {
|
|
339
794
|
throw new BadRequestException(
|
|
340
|
-
'OpenAI API key is not configured
|
|
795
|
+
'OpenAI API key is not configured. Please set it in Settings → AI → OpenAI API Key.',
|
|
341
796
|
);
|
|
342
797
|
}
|
|
343
|
-
return key;
|
|
798
|
+
return { provider: 'openai', apiKey: key };
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private async buildOpenAiTools(
|
|
802
|
+
message: string,
|
|
803
|
+
conversationId: number,
|
|
804
|
+
userId: number,
|
|
805
|
+
history: ConversationHistoryItem[],
|
|
806
|
+
): Promise<ToolSelectionResult<OpenAiToolDefinition>> {
|
|
807
|
+
return this.selectOpenAiTools(message, conversationId, userId, history);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
private async buildGeminiTools(
|
|
811
|
+
message: string,
|
|
812
|
+
conversationId: number,
|
|
813
|
+
userId: number,
|
|
814
|
+
history: ConversationHistoryItem[],
|
|
815
|
+
): Promise<ToolSelectionResult<GeminiFunctionDeclaration>> {
|
|
816
|
+
const selection = await this.selectOpenAiTools(
|
|
817
|
+
message,
|
|
818
|
+
conversationId,
|
|
819
|
+
userId,
|
|
820
|
+
history,
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
return {
|
|
824
|
+
clarification: selection.clarification,
|
|
825
|
+
tools: selection.tools.map((t) => t.function),
|
|
826
|
+
};
|
|
344
827
|
}
|
|
345
828
|
|
|
346
|
-
private
|
|
829
|
+
private async selectOpenAiTools(
|
|
347
830
|
message: string,
|
|
348
831
|
conversationId: number,
|
|
349
832
|
userId: number,
|
|
350
|
-
|
|
833
|
+
history: ConversationHistoryItem[],
|
|
834
|
+
): Promise<ToolSelectionResult<OpenAiToolDefinition>> {
|
|
351
835
|
const allEntries = this.registry.getAll();
|
|
836
|
+
const allowedToolNames = await this.getAllowedMcpToolNames(userId);
|
|
837
|
+
const allowedEntries = allEntries.filter((entry) =>
|
|
838
|
+
allowedToolNames.has(entry.definition.name),
|
|
839
|
+
);
|
|
352
840
|
|
|
353
841
|
const dedupedBySanitized = new Map<string, ScoredTool>();
|
|
354
842
|
const excludedDuplicates: string[] = [];
|
|
355
|
-
const
|
|
843
|
+
const routerText = this.buildToolRouterText(message, history);
|
|
844
|
+
const normalizedMessage = this.normalizeText(routerText);
|
|
845
|
+
const messageTerms = this.getMessageKeywordSet(normalizedMessage);
|
|
846
|
+
const domainScores = this.scoreToolDomains(normalizedMessage, allowedEntries);
|
|
847
|
+
const candidateDomains = this.getCandidateDomains(domainScores);
|
|
848
|
+
const clarification = this.getToolSelectionClarification(
|
|
849
|
+
message,
|
|
850
|
+
normalizedMessage,
|
|
851
|
+
domainScores,
|
|
852
|
+
candidateDomains,
|
|
853
|
+
allowedEntries,
|
|
854
|
+
);
|
|
356
855
|
|
|
357
|
-
|
|
856
|
+
if (clarification) {
|
|
857
|
+
this.logger.log(
|
|
858
|
+
`MCP tools need clarification: conversationId=${conversationId} userId=${userId} ` +
|
|
859
|
+
`registered=${allEntries.length} authorized=${allowedEntries.length} ` +
|
|
860
|
+
`domains=${this.formatDomainScores(domainScores)} selected=0`,
|
|
861
|
+
);
|
|
862
|
+
return { tools: [], clarification };
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const entriesForRanking =
|
|
866
|
+
candidateDomains.length > 0
|
|
867
|
+
? allowedEntries.filter((entry) =>
|
|
868
|
+
candidateDomains.includes(this.getToolDomain(entry.definition.name)),
|
|
869
|
+
)
|
|
870
|
+
: allowedEntries;
|
|
871
|
+
|
|
872
|
+
for (const [index, entry] of entriesForRanking.entries()) {
|
|
358
873
|
const originalName = entry.definition.name;
|
|
359
874
|
const sanitizedName = sanitize(originalName);
|
|
360
875
|
const readOnly = Boolean(entry.definition.readOnly);
|
|
876
|
+
const domain = this.getToolDomain(originalName);
|
|
877
|
+
const matchScore = this.scoreToolMatch(
|
|
878
|
+
normalizedMessage,
|
|
879
|
+
messageTerms,
|
|
880
|
+
entry,
|
|
881
|
+
candidateDomains,
|
|
882
|
+
);
|
|
361
883
|
|
|
362
884
|
if (dedupedBySanitized.has(sanitizedName)) {
|
|
363
885
|
excludedDuplicates.push(originalName);
|
|
@@ -366,9 +888,11 @@ export class McpChatService {
|
|
|
366
888
|
|
|
367
889
|
const scored: ScoredTool = {
|
|
368
890
|
name: originalName,
|
|
891
|
+
domain,
|
|
369
892
|
readOnly,
|
|
370
|
-
priorityBucket: this.getDomainPriorityBucket(originalName),
|
|
893
|
+
priorityBucket: this.getDomainPriorityBucket(originalName, candidateDomains),
|
|
371
894
|
keywordBoost: this.getKeywordBoost(normalizedMessage, originalName),
|
|
895
|
+
matchScore,
|
|
372
896
|
index,
|
|
373
897
|
tool: {
|
|
374
898
|
type: 'function',
|
|
@@ -388,6 +912,9 @@ export class McpChatService {
|
|
|
388
912
|
|
|
389
913
|
const scoredTools = Array.from(dedupedBySanitized.values());
|
|
390
914
|
scoredTools.sort((a, b) => {
|
|
915
|
+
if (a.matchScore !== b.matchScore) {
|
|
916
|
+
return b.matchScore - a.matchScore;
|
|
917
|
+
}
|
|
391
918
|
if (a.priorityBucket !== b.priorityBucket) {
|
|
392
919
|
return a.priorityBucket - b.priorityBucket;
|
|
393
920
|
}
|
|
@@ -400,9 +927,9 @@ export class McpChatService {
|
|
|
400
927
|
return a.index - b.index;
|
|
401
928
|
});
|
|
402
929
|
|
|
403
|
-
const selected = scoredTools.slice(0,
|
|
930
|
+
const selected = scoredTools.slice(0, TARGET_SELECTED_TOOLS);
|
|
404
931
|
const excludedByLimit = scoredTools
|
|
405
|
-
.slice(
|
|
932
|
+
.slice(TARGET_SELECTED_TOOLS)
|
|
406
933
|
.map((tool) => tool.name);
|
|
407
934
|
|
|
408
935
|
if (excludedDuplicates.length > 0) {
|
|
@@ -414,7 +941,7 @@ export class McpChatService {
|
|
|
414
941
|
|
|
415
942
|
if (excludedByLimit.length > 0) {
|
|
416
943
|
this.logger.warn(
|
|
417
|
-
`MCP tools truncated for
|
|
944
|
+
`MCP tools truncated for provider budget (${TARGET_SELECTED_TOOLS}/${MAX_OPENAI_TOOLS}). ` +
|
|
418
945
|
`conversationId=${conversationId} userId=${userId} ` +
|
|
419
946
|
`available=${scoredTools.length} selected=${selected.length} excluded=${excludedByLimit.length}. ` +
|
|
420
947
|
`Top excluded: ${excludedByLimit.slice(0, 12).join(', ')}`,
|
|
@@ -423,17 +950,50 @@ export class McpChatService {
|
|
|
423
950
|
|
|
424
951
|
this.logger.log(
|
|
425
952
|
`MCP tools prepared: conversationId=${conversationId} userId=${userId} ` +
|
|
426
|
-
`
|
|
953
|
+
`registered=${allEntries.length} authorized=${allowedEntries.length} ` +
|
|
954
|
+
`domains=${this.formatDomainScores(domainScores)} ranked=${scoredTools.length} ` +
|
|
955
|
+
`selected=${selected.length}`,
|
|
427
956
|
);
|
|
428
957
|
|
|
429
|
-
return selected.map((entry) => entry.tool);
|
|
958
|
+
return { tools: selected.map((entry) => entry.tool) };
|
|
430
959
|
}
|
|
431
960
|
|
|
432
|
-
private
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
961
|
+
private async getAllowedMcpToolNames(userId: number): Promise<Set<string>> {
|
|
962
|
+
const routes = await this.prisma.route.findMany({
|
|
963
|
+
where: {
|
|
964
|
+
type: 'MCP' as any,
|
|
965
|
+
tool_name: { not: null },
|
|
966
|
+
role_route: {
|
|
967
|
+
some: {
|
|
968
|
+
role: {
|
|
969
|
+
role_user: {
|
|
970
|
+
some: { user_id: userId },
|
|
971
|
+
},
|
|
972
|
+
},
|
|
973
|
+
},
|
|
974
|
+
},
|
|
975
|
+
},
|
|
976
|
+
select: { tool_name: true },
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
return new Set(
|
|
980
|
+
routes
|
|
981
|
+
.map((route) => route.tool_name)
|
|
982
|
+
.filter((toolName): toolName is string => typeof toolName === 'string'),
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
private getDomainPriorityBucket(toolName: string, candidateDomains: string[]): number {
|
|
987
|
+
const domain = this.getToolDomain(toolName);
|
|
988
|
+
const candidateIndex = candidateDomains.indexOf(domain);
|
|
989
|
+
if (candidateIndex >= 0) {
|
|
990
|
+
return candidateIndex;
|
|
991
|
+
}
|
|
992
|
+
if (toolName.startsWith('operations.')) return 10;
|
|
993
|
+
if (toolName.startsWith('finance.')) return 11;
|
|
994
|
+
if (toolName.startsWith('lms.')) return 12;
|
|
995
|
+
if (toolName.startsWith('core.')) return 13;
|
|
996
|
+
return 99;
|
|
437
997
|
}
|
|
438
998
|
|
|
439
999
|
private getKeywordBoost(message: string, toolName: string): number {
|
|
@@ -475,6 +1035,289 @@ export class McpChatService {
|
|
|
475
1035
|
return boost;
|
|
476
1036
|
}
|
|
477
1037
|
|
|
1038
|
+
private buildToolRouterText(
|
|
1039
|
+
message: string,
|
|
1040
|
+
history: ConversationHistoryItem[],
|
|
1041
|
+
): string {
|
|
1042
|
+
const recentContext = history
|
|
1043
|
+
.slice(-8)
|
|
1044
|
+
.map((item) =>
|
|
1045
|
+
[item.role === 'user' ? item.content : undefined, item.tool_name]
|
|
1046
|
+
.filter(Boolean)
|
|
1047
|
+
.join(' '),
|
|
1048
|
+
)
|
|
1049
|
+
.join(' ');
|
|
1050
|
+
|
|
1051
|
+
return `${recentContext} ${message}`;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
private normalizeText(value: string): string {
|
|
1055
|
+
return value
|
|
1056
|
+
.toLowerCase()
|
|
1057
|
+
.normalize('NFD')
|
|
1058
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
1059
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
1060
|
+
.replace(/\s+/g, ' ')
|
|
1061
|
+
.trim();
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
private getToolDomain(toolName: string): string {
|
|
1065
|
+
return toolName.split('.')[0] || 'unknown';
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
private scoreToolDomains(
|
|
1069
|
+
normalizedMessage: string,
|
|
1070
|
+
entries: McpToolEntry[],
|
|
1071
|
+
): Map<string, number> {
|
|
1072
|
+
const scores = new Map<string, number>();
|
|
1073
|
+
|
|
1074
|
+
for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORDS)) {
|
|
1075
|
+
let score = 0;
|
|
1076
|
+
if (this.hasTerm(normalizedMessage, domain)) {
|
|
1077
|
+
score += 3;
|
|
1078
|
+
}
|
|
1079
|
+
for (const keyword of keywords) {
|
|
1080
|
+
const normalizedKeyword = this.normalizeText(keyword);
|
|
1081
|
+
if (normalizedKeyword && normalizedMessage.includes(normalizedKeyword)) {
|
|
1082
|
+
score += normalizedKeyword.includes(' ') ? 3 : 2;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (score > 0 && entries.some((entry) => this.getToolDomain(entry.definition.name) === domain)) {
|
|
1086
|
+
scores.set(domain, score);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
for (const entry of entries) {
|
|
1091
|
+
const domain = this.getToolDomain(entry.definition.name);
|
|
1092
|
+
const toolText = this.normalizeText(
|
|
1093
|
+
`${entry.definition.name} ${entry.definition.description ?? ''}`,
|
|
1094
|
+
);
|
|
1095
|
+
let score = scores.get(domain) ?? 0;
|
|
1096
|
+
for (const term of normalizedMessage.split(' ')) {
|
|
1097
|
+
if (term.length >= 4 && toolText.includes(term)) {
|
|
1098
|
+
score += 1;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (score > 0) {
|
|
1102
|
+
scores.set(domain, score);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
return scores;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
private getCandidateDomains(domainScores: Map<string, number>): string[] {
|
|
1110
|
+
const ranked = Array.from(domainScores.entries())
|
|
1111
|
+
.filter(([, score]) => score >= MIN_DOMAIN_CONFIDENCE)
|
|
1112
|
+
.sort((a, b) => b[1] - a[1]);
|
|
1113
|
+
|
|
1114
|
+
if (ranked.length === 0) {
|
|
1115
|
+
return [];
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const topScore = ranked[0][1];
|
|
1119
|
+
return ranked
|
|
1120
|
+
.filter(([, score]) => topScore - score <= AMBIGUOUS_DOMAIN_DELTA)
|
|
1121
|
+
.map(([domain]) => domain);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private getToolSelectionClarification(
|
|
1125
|
+
originalMessage: string,
|
|
1126
|
+
normalizedMessage: string,
|
|
1127
|
+
domainScores: Map<string, number>,
|
|
1128
|
+
candidateDomains: string[],
|
|
1129
|
+
entries: McpToolEntry[],
|
|
1130
|
+
): string | undefined {
|
|
1131
|
+
if (!this.messageLooksLikeToolRequest(normalizedMessage)) {
|
|
1132
|
+
return undefined;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
const rankedDomains = Array.from(domainScores.entries()).sort((a, b) => b[1] - a[1]);
|
|
1136
|
+
if (rankedDomains.length === 0 || rankedDomains[0][1] < MIN_DOMAIN_CONFIDENCE) {
|
|
1137
|
+
return this.isLikelyPortuguese(originalMessage)
|
|
1138
|
+
? 'Você quer consultar qual área do sistema? Por exemplo: financeiro, operações, LMS ou configurações.'
|
|
1139
|
+
: 'Which system area do you want to query? For example: finance, operations, LMS, or settings.';
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (candidateDomains.length > 1) {
|
|
1143
|
+
const options = candidateDomains
|
|
1144
|
+
.slice(0, 3)
|
|
1145
|
+
.map((domain) => this.getDomainLabel(domain, originalMessage))
|
|
1146
|
+
.join(', ');
|
|
1147
|
+
return this.isLikelyPortuguese(originalMessage)
|
|
1148
|
+
? `Você quer consultar qual área: ${options}?`
|
|
1149
|
+
: `Which area do you want to query: ${options}?`;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if (
|
|
1153
|
+
candidateDomains[0] === 'finance' &&
|
|
1154
|
+
this.isAmbiguousFinanceAccountsRequest(normalizedMessage, entries)
|
|
1155
|
+
) {
|
|
1156
|
+
return this.isLikelyPortuguese(originalMessage)
|
|
1157
|
+
? 'Você quer consultar contas bancárias, contas a pagar ou contas a receber?'
|
|
1158
|
+
: 'Do you want bank accounts, accounts payable, or accounts receivable?';
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return undefined;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
private scoreToolMatch(
|
|
1165
|
+
normalizedMessage: string,
|
|
1166
|
+
terms: Set<string>,
|
|
1167
|
+
entry: McpToolEntry,
|
|
1168
|
+
candidateDomains: string[],
|
|
1169
|
+
): number {
|
|
1170
|
+
const toolName = entry.definition.name;
|
|
1171
|
+
const toolText = this.normalizeText(
|
|
1172
|
+
`${toolName} ${entry.definition.description ?? ''} ${this.getSchemaText(entry.definition.inputSchema)}`,
|
|
1173
|
+
);
|
|
1174
|
+
const domain = this.getToolDomain(toolName);
|
|
1175
|
+
let score = candidateDomains.includes(domain) ? 8 : 0;
|
|
1176
|
+
|
|
1177
|
+
for (const term of normalizedMessage.split(' ')) {
|
|
1178
|
+
if (term.length < 3) continue;
|
|
1179
|
+
if (toolText.includes(term)) {
|
|
1180
|
+
score += term.length >= 5 ? 2 : 1;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (terms.has('list') && entry.definition.readOnly) score += 4;
|
|
1185
|
+
if (terms.has('write') && !entry.definition.readOnly) score += 4;
|
|
1186
|
+
|
|
1187
|
+
score += this.getFinanceSpecificBoost(normalizedMessage, toolName);
|
|
1188
|
+
score += this.getKeywordBoost(normalizedMessage, toolName);
|
|
1189
|
+
|
|
1190
|
+
return score;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
private getSchemaText(schema: Record<string, any> | undefined): string {
|
|
1194
|
+
if (!schema || typeof schema !== 'object') {
|
|
1195
|
+
return '';
|
|
1196
|
+
}
|
|
1197
|
+
const properties = schema.properties;
|
|
1198
|
+
if (!properties || typeof properties !== 'object') {
|
|
1199
|
+
return '';
|
|
1200
|
+
}
|
|
1201
|
+
return Object.keys(properties).join(' ');
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
private getFinanceSpecificBoost(message: string, toolName: string): number {
|
|
1205
|
+
if (!toolName.startsWith('finance.')) {
|
|
1206
|
+
return 0;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
let boost = 0;
|
|
1210
|
+
if (
|
|
1211
|
+
/banco|bancaria|bancarias|bank/.test(message) &&
|
|
1212
|
+
toolName.includes('bank-accounts')
|
|
1213
|
+
) {
|
|
1214
|
+
boost += 10;
|
|
1215
|
+
}
|
|
1216
|
+
if (/extrato|statement/.test(message) && toolName.includes('statements')) {
|
|
1217
|
+
boost += 10;
|
|
1218
|
+
}
|
|
1219
|
+
if (/pagar|payable/.test(message) && toolName.includes('accounts-payable')) {
|
|
1220
|
+
boost += 10;
|
|
1221
|
+
}
|
|
1222
|
+
if (/receber|receivable/.test(message) && toolName.includes('accounts-receivable')) {
|
|
1223
|
+
boost += 10;
|
|
1224
|
+
}
|
|
1225
|
+
if (/moeda|currency/.test(message) && toolName.includes('currencies')) {
|
|
1226
|
+
boost += 8;
|
|
1227
|
+
}
|
|
1228
|
+
if (/transferencia|transfer|transferencias/.test(message) && toolName.includes('transfers')) {
|
|
1229
|
+
boost += 8;
|
|
1230
|
+
}
|
|
1231
|
+
return boost;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
private messageLooksLikeToolRequest(normalizedMessage: string): boolean {
|
|
1235
|
+
return ACTION_KEYWORDS.some((keyword) =>
|
|
1236
|
+
this.hasTerm(normalizedMessage, this.normalizeText(keyword)),
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
private isAmbiguousFinanceAccountsRequest(
|
|
1241
|
+
normalizedMessage: string,
|
|
1242
|
+
entries: McpToolEntry[],
|
|
1243
|
+
): boolean {
|
|
1244
|
+
const mentionsGenericAccounts = /\b(conta|contas|account|accounts)\b/.test(normalizedMessage);
|
|
1245
|
+
const hasQualifier =
|
|
1246
|
+
/banco|bancaria|bancarias|bank|extrato|statement|pagar|payable|receber|receivable/.test(
|
|
1247
|
+
normalizedMessage,
|
|
1248
|
+
);
|
|
1249
|
+
if (!mentionsGenericAccounts || hasQualifier) {
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
const financeToolNames = entries
|
|
1254
|
+
.map((entry) => entry.definition.name)
|
|
1255
|
+
.filter((name) => name.startsWith('finance.'));
|
|
1256
|
+
|
|
1257
|
+
return (
|
|
1258
|
+
financeToolNames.some((name) => name.includes('bank-accounts')) &&
|
|
1259
|
+
(financeToolNames.some((name) => name.includes('accounts-payable')) ||
|
|
1260
|
+
financeToolNames.some((name) => name.includes('accounts-receivable')))
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
private hasTerm(text: string, term: string): boolean {
|
|
1265
|
+
return new RegExp(`(^|\\s)${this.escapeRegex(term)}($|\\s)`).test(text);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
private escapeRegex(value: string): string {
|
|
1269
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
private getDomainLabel(domain: string, message: string): string {
|
|
1273
|
+
const labels: Record<string, { pt: string; en: string }> = {
|
|
1274
|
+
finance: { pt: 'financeiro', en: 'finance' },
|
|
1275
|
+
operations: { pt: 'operações', en: 'operations' },
|
|
1276
|
+
lms: { pt: 'LMS/cursos', en: 'LMS/courses' },
|
|
1277
|
+
core: { pt: 'configurações/core', en: 'settings/core' },
|
|
1278
|
+
contact: { pt: 'contatos', en: 'contacts' },
|
|
1279
|
+
};
|
|
1280
|
+
const label = labels[domain];
|
|
1281
|
+
if (!label) {
|
|
1282
|
+
return domain;
|
|
1283
|
+
}
|
|
1284
|
+
return this.isLikelyPortuguese(message) ? label.pt : label.en;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
private isLikelyPortuguese(message: string): boolean {
|
|
1288
|
+
return /(voce|você|qual|consultar|listar|conta|contas|bancaria|bancária|extrato|tarefas|curso|cursos)/i.test(
|
|
1289
|
+
message,
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
private formatDomainScores(domainScores: Map<string, number>): string {
|
|
1294
|
+
const formatted = Array.from(domainScores.entries())
|
|
1295
|
+
.sort((a, b) => b[1] - a[1])
|
|
1296
|
+
.slice(0, 5)
|
|
1297
|
+
.map(([domain, score]) => `${domain}:${score}`)
|
|
1298
|
+
.join(',');
|
|
1299
|
+
|
|
1300
|
+
return formatted || 'none';
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
private async recordAssistantAndReturnHistory(
|
|
1304
|
+
conversationId: number,
|
|
1305
|
+
content: string,
|
|
1306
|
+
) {
|
|
1307
|
+
await this.prisma.mcp_conversation_message.create({
|
|
1308
|
+
data: {
|
|
1309
|
+
conversation_id: conversationId,
|
|
1310
|
+
role: 'assistant',
|
|
1311
|
+
content,
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
return this.prisma.mcp_conversation_message.findMany({
|
|
1316
|
+
where: { conversation_id: conversationId },
|
|
1317
|
+
orderBy: { created_at: 'asc' },
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
|
|
478
1321
|
private getMessageKeywordSet(message: string): Set<string> {
|
|
479
1322
|
const keywords = new Set<string>();
|
|
480
1323
|
if (/(tarefa|tarefas|task|tasks)/i.test(message)) {
|
|
@@ -489,6 +1332,12 @@ export class McpChatService {
|
|
|
489
1332
|
if (/(pendente|pendentes|pending|open|aberta|abertas)/i.test(message)) {
|
|
490
1333
|
keywords.add('pending');
|
|
491
1334
|
}
|
|
1335
|
+
if (/(listar|lista|buscar|procurar|consultar|mostrar|ver|list|search|find|show|get)/i.test(message)) {
|
|
1336
|
+
keywords.add('list');
|
|
1337
|
+
}
|
|
1338
|
+
if (/(criar|adicionar|atualizar|alterar|remover|deletar|excluir|create|add|update|delete|remove)/i.test(message)) {
|
|
1339
|
+
keywords.add('write');
|
|
1340
|
+
}
|
|
492
1341
|
return keywords;
|
|
493
1342
|
}
|
|
494
1343
|
}
|