@hed-hog/core 0.0.328 → 0.0.330

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.
Files changed (206) hide show
  1. package/dist/ai/ai.service.d.ts +1 -1
  2. package/dist/auth/auth.controller.d.ts +23 -23
  3. package/dist/auth/auth.service.d.ts +41 -41
  4. package/dist/challenge/challenge.service.d.ts +14 -14
  5. package/dist/core/system.controller.d.ts +1 -1
  6. package/dist/core/system.service.d.ts +1 -1
  7. package/dist/core.module.d.ts.map +1 -1
  8. package/dist/core.module.js +9 -2
  9. package/dist/core.module.js.map +1 -1
  10. package/dist/dashboard/dashboard/dashboard.controller.d.ts +84 -84
  11. package/dist/dashboard/dashboard/dashboard.service.d.ts +84 -84
  12. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +29 -29
  13. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +29 -29
  14. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +42 -42
  15. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +42 -42
  16. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +11 -11
  17. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +11 -11
  18. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +6 -6
  19. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +6 -6
  20. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +33 -33
  21. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +33 -33
  22. package/dist/dashboard/dashboard-user/dashboard-user.controller.d.ts +3 -3
  23. package/dist/dashboard/dashboard-user/dashboard-user.service.d.ts +3 -3
  24. package/dist/file/file.controller.d.ts +10 -10
  25. package/dist/file/file.service.d.ts +22 -22
  26. package/dist/index.d.ts +11 -4
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +12 -4
  29. package/dist/index.js.map +1 -1
  30. package/dist/mail/mail.controller.d.ts +15 -15
  31. package/dist/mail/mail.service.d.ts +15 -15
  32. package/dist/mail-profile/mail-profile.controller.d.ts +9 -9
  33. package/dist/mail-profile/mail-profile.service.d.ts +9 -9
  34. package/dist/mail-sent/mail-sent.controller.d.ts +17 -17
  35. package/dist/mail-sent/mail-sent.service.d.ts +17 -17
  36. package/dist/mcp/decorators/mcp-tool.decorator.d.ts +4 -0
  37. package/dist/mcp/decorators/mcp-tool.decorator.d.ts.map +1 -0
  38. package/dist/mcp/decorators/mcp-tool.decorator.js +8 -0
  39. package/dist/mcp/decorators/mcp-tool.decorator.js.map +1 -0
  40. package/dist/mcp/guards/mcp-auth.guard.d.ts +9 -0
  41. package/dist/mcp/guards/mcp-auth.guard.d.ts.map +1 -0
  42. package/dist/mcp/guards/mcp-auth.guard.js +41 -0
  43. package/dist/mcp/guards/mcp-auth.guard.js.map +1 -0
  44. package/dist/mcp/guards/mcp-permission.guard.d.ts +8 -0
  45. package/dist/mcp/guards/mcp-permission.guard.d.ts.map +1 -0
  46. package/dist/mcp/guards/mcp-permission.guard.js +57 -0
  47. package/dist/mcp/guards/mcp-permission.guard.js.map +1 -0
  48. package/dist/mcp/interfaces/mcp-tool-definition.interface.d.ts +7 -0
  49. package/dist/mcp/interfaces/mcp-tool-definition.interface.d.ts.map +1 -0
  50. package/dist/mcp/interfaces/mcp-tool-definition.interface.js +3 -0
  51. package/dist/mcp/interfaces/mcp-tool-definition.interface.js.map +1 -0
  52. package/dist/mcp/mcp-audit.service.d.ts +17 -0
  53. package/dist/mcp/mcp-audit.service.d.ts.map +1 -0
  54. package/dist/mcp/mcp-audit.service.js +46 -0
  55. package/dist/mcp/mcp-audit.service.js.map +1 -0
  56. package/dist/mcp/mcp-registry.service.d.ts +21 -0
  57. package/dist/mcp/mcp-registry.service.d.ts.map +1 -0
  58. package/dist/mcp/mcp-registry.service.js +69 -0
  59. package/dist/mcp/mcp-registry.service.js.map +1 -0
  60. package/dist/mcp/mcp-server.service.d.ts +12 -0
  61. package/dist/mcp/mcp-server.service.d.ts.map +1 -0
  62. package/dist/mcp/mcp-server.service.js +94 -0
  63. package/dist/mcp/mcp-server.service.js.map +1 -0
  64. package/dist/mcp/mcp.controller.d.ts +8 -0
  65. package/dist/mcp/mcp.controller.d.ts.map +1 -0
  66. package/dist/mcp/mcp.controller.js +54 -0
  67. package/dist/mcp/mcp.controller.js.map +1 -0
  68. package/dist/mcp/mcp.module.d.ts +3 -0
  69. package/dist/mcp/mcp.module.d.ts.map +1 -0
  70. package/dist/mcp/mcp.module.js +49 -0
  71. package/dist/mcp/mcp.module.js.map +1 -0
  72. package/dist/mcp/tools/core-settings.mcp-tools.d.ts +12 -0
  73. package/dist/mcp/tools/core-settings.mcp-tools.d.ts.map +1 -0
  74. package/dist/mcp/tools/core-settings.mcp-tools.js +66 -0
  75. package/dist/mcp/tools/core-settings.mcp-tools.js.map +1 -0
  76. package/dist/mcp/tools/core-users.mcp-tools.d.ts +55 -0
  77. package/dist/mcp/tools/core-users.mcp-tools.d.ts.map +1 -0
  78. package/dist/mcp/tools/core-users.mcp-tools.js +295 -0
  79. package/dist/mcp/tools/core-users.mcp-tools.js.map +1 -0
  80. package/dist/mcp/types/mcp-context.type.d.ts +7 -0
  81. package/dist/mcp/types/mcp-context.type.d.ts.map +1 -0
  82. package/dist/mcp/types/mcp-context.type.js +3 -0
  83. package/dist/mcp/types/mcp-context.type.js.map +1 -0
  84. package/dist/mcp-chat/dto/create-conversation.dto.d.ts +4 -0
  85. package/dist/mcp-chat/dto/create-conversation.dto.d.ts.map +1 -0
  86. package/dist/mcp-chat/dto/create-conversation.dto.js +22 -0
  87. package/dist/mcp-chat/dto/create-conversation.dto.js.map +1 -0
  88. package/dist/mcp-chat/dto/send-message.dto.d.ts +4 -0
  89. package/dist/mcp-chat/dto/send-message.dto.d.ts.map +1 -0
  90. package/dist/mcp-chat/dto/send-message.dto.js +22 -0
  91. package/dist/mcp-chat/dto/send-message.dto.js.map +1 -0
  92. package/dist/mcp-chat/mcp-chat.controller.d.ts +55 -0
  93. package/dist/mcp-chat/mcp-chat.controller.d.ts.map +1 -0
  94. package/dist/mcp-chat/mcp-chat.controller.js +90 -0
  95. package/dist/mcp-chat/mcp-chat.controller.js.map +1 -0
  96. package/dist/mcp-chat/mcp-chat.module.d.ts +3 -0
  97. package/dist/mcp-chat/mcp-chat.module.d.ts.map +1 -0
  98. package/dist/mcp-chat/mcp-chat.module.js +30 -0
  99. package/dist/mcp-chat/mcp-chat.module.js.map +1 -0
  100. package/dist/mcp-chat/mcp-chat.service.d.ts +50 -0
  101. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -0
  102. package/dist/mcp-chat/mcp-chat.service.js +290 -0
  103. package/dist/mcp-chat/mcp-chat.service.js.map +1 -0
  104. package/dist/menu/menu.controller.d.ts +8 -8
  105. package/dist/menu/menu.service.d.ts +8 -8
  106. package/dist/oauth/oauth.service.js +1 -1
  107. package/dist/oauth/oauth.service.js.map +1 -1
  108. package/dist/pdf/components/index.d.ts +57 -0
  109. package/dist/pdf/components/index.d.ts.map +1 -0
  110. package/dist/pdf/components/index.js +258 -0
  111. package/dist/pdf/components/index.js.map +1 -0
  112. package/dist/pdf/dto/generate-pdf.dto.d.ts +5 -0
  113. package/dist/pdf/dto/generate-pdf.dto.d.ts.map +1 -0
  114. package/dist/pdf/dto/generate-pdf.dto.js +27 -0
  115. package/dist/pdf/dto/generate-pdf.dto.js.map +1 -0
  116. package/dist/pdf/interfaces/pdf-theme-config.interface.d.ts +10 -0
  117. package/dist/pdf/interfaces/pdf-theme-config.interface.d.ts.map +1 -0
  118. package/dist/pdf/interfaces/pdf-theme-config.interface.js +3 -0
  119. package/dist/pdf/interfaces/pdf-theme-config.interface.js.map +1 -0
  120. package/dist/pdf/pdf.controller.d.ts +16 -0
  121. package/dist/pdf/pdf.controller.d.ts.map +1 -0
  122. package/dist/pdf/pdf.controller.js +100 -0
  123. package/dist/pdf/pdf.controller.js.map +1 -0
  124. package/dist/pdf/pdf.module.d.ts +3 -0
  125. package/dist/pdf/pdf.module.d.ts.map +1 -0
  126. package/dist/pdf/pdf.module.js +29 -0
  127. package/dist/pdf/pdf.module.js.map +1 -0
  128. package/dist/pdf/pdf.service.d.ts +44 -0
  129. package/dist/pdf/pdf.service.d.ts.map +1 -0
  130. package/dist/pdf/pdf.service.js +185 -0
  131. package/dist/pdf/pdf.service.js.map +1 -0
  132. package/dist/pdf/templates/hcode-institutional.template.d.ts +36 -0
  133. package/dist/pdf/templates/hcode-institutional.template.d.ts.map +1 -0
  134. package/dist/pdf/templates/hcode-institutional.template.js +67 -0
  135. package/dist/pdf/templates/hcode-institutional.template.js.map +1 -0
  136. package/dist/pdf/themes/hcode.theme.d.ts +25 -0
  137. package/dist/pdf/themes/hcode.theme.d.ts.map +1 -0
  138. package/dist/pdf/themes/hcode.theme.js +504 -0
  139. package/dist/pdf/themes/hcode.theme.js.map +1 -0
  140. package/dist/profile/profile.controller.d.ts +16 -16
  141. package/dist/profile/profile.service.d.ts +16 -16
  142. package/dist/role/role.controller.d.ts +12 -12
  143. package/dist/role/role.service.d.ts +12 -12
  144. package/dist/route/dto/create.dto.d.ts +6 -2
  145. package/dist/route/dto/create.dto.d.ts.map +1 -1
  146. package/dist/route/dto/create.dto.js +19 -1
  147. package/dist/route/dto/create.dto.js.map +1 -1
  148. package/dist/route/dto/update.dto.d.ts +4 -0
  149. package/dist/route/dto/update.dto.d.ts.map +1 -1
  150. package/dist/route/dto/update.dto.js +19 -3
  151. package/dist/route/dto/update.dto.js.map +1 -1
  152. package/dist/route/route.service.d.ts +1 -1
  153. package/dist/route/route.service.d.ts.map +1 -1
  154. package/dist/route/route.service.js +18 -11
  155. package/dist/route/route.service.js.map +1 -1
  156. package/dist/screen/screen.controller.d.ts +3 -3
  157. package/dist/screen/screen.service.d.ts +3 -3
  158. package/dist/session/session.controller.d.ts +4 -4
  159. package/dist/session/session.service.d.ts +12 -12
  160. package/dist/setting/setting.controller.d.ts +13 -13
  161. package/dist/setting/setting.service.d.ts +13 -13
  162. package/dist/user/user.controller.d.ts +24 -24
  163. package/dist/user/user.service.d.ts +55 -55
  164. package/hedhog/data/menu.yaml +40 -26
  165. package/hedhog/data/route.yaml +583 -152
  166. package/hedhog/data/setting_group.yaml +60 -0
  167. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +574 -0
  168. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +5 -9
  169. package/hedhog/frontend/messages/en.json +19 -0
  170. package/hedhog/frontend/messages/pt.json +19 -0
  171. package/hedhog/table/mcp_conversation.yaml +12 -0
  172. package/hedhog/table/mcp_conversation_message.yaml +23 -0
  173. package/hedhog/table/mcp_tool_call_log.yaml +22 -0
  174. package/hedhog/table/route.yaml +33 -22
  175. package/package.json +7 -6
  176. package/src/core.module.ts +18 -11
  177. package/src/index.ts +22 -12
  178. package/src/mcp/decorators/mcp-tool.decorator.ts +7 -0
  179. package/src/mcp/guards/mcp-auth.guard.ts +32 -0
  180. package/src/mcp/guards/mcp-permission.guard.ts +51 -0
  181. package/src/mcp/interfaces/mcp-tool-definition.interface.ts +6 -0
  182. package/src/mcp/mcp-audit.service.ts +36 -0
  183. package/src/mcp/mcp-registry.service.ts +69 -0
  184. package/src/mcp/mcp-server.service.ts +87 -0
  185. package/src/mcp/mcp.controller.ts +30 -0
  186. package/src/mcp/mcp.module.ts +36 -0
  187. package/src/mcp/tools/core-settings.mcp-tools.ts +53 -0
  188. package/src/mcp/tools/core-users.mcp-tools.ts +267 -0
  189. package/src/mcp/types/mcp-context.type.ts +6 -0
  190. package/src/mcp-chat/dto/create-conversation.dto.ts +7 -0
  191. package/src/mcp-chat/dto/send-message.dto.ts +7 -0
  192. package/src/mcp-chat/mcp-chat.controller.ts +60 -0
  193. package/src/mcp-chat/mcp-chat.module.ts +17 -0
  194. package/src/mcp-chat/mcp-chat.service.ts +332 -0
  195. package/src/oauth/oauth.service.ts +12 -12
  196. package/src/pdf/components/index.ts +316 -0
  197. package/src/pdf/dto/generate-pdf.dto.ts +11 -0
  198. package/src/pdf/interfaces/pdf-theme-config.interface.ts +10 -0
  199. package/src/pdf/pdf.controller.ts +86 -0
  200. package/src/pdf/pdf.module.ts +17 -0
  201. package/src/pdf/pdf.service.ts +229 -0
  202. package/src/pdf/templates/hcode-institutional.template.ts +122 -0
  203. package/src/pdf/themes/hcode.theme.ts +515 -0
  204. package/src/route/dto/create.dto.ts +32 -14
  205. package/src/route/dto/update.dto.ts +31 -16
  206. package/src/route/route.service.ts +23 -12
@@ -0,0 +1,332 @@
1
+ import { PrismaService } from '@hed-hog/api-prisma';
2
+ import {
3
+ BadRequestException,
4
+ ForbiddenException,
5
+ forwardRef,
6
+ HttpException,
7
+ HttpStatus,
8
+ Inject,
9
+ Injectable,
10
+ NotFoundException,
11
+ } from '@nestjs/common';
12
+ import axios from 'axios';
13
+ import { SettingService } from '../setting/setting.service';
14
+ import { McpContext } from '../mcp/types/mcp-context.type';
15
+ import { McpRegistryService } from '../mcp/mcp-registry.service';
16
+ import { CreateConversationDTO } from './dto/create-conversation.dto';
17
+
18
+ type OpenAiMessage =
19
+ | { role: 'system'; content: string }
20
+ | { role: 'user'; content: string }
21
+ | { role: 'assistant'; content: string | null; tool_calls?: OpenAiToolCall[] }
22
+ | { role: 'tool'; tool_call_id: string; content: string };
23
+
24
+ interface OpenAiToolCall {
25
+ id: string;
26
+ type: 'function';
27
+ function: { name: string; arguments: string };
28
+ }
29
+
30
+ const SYSTEM_PROMPT =
31
+ 'You are a helpful assistant with access to tools from the HedHog system. ' +
32
+ 'Use the available tools to answer the user\'s questions accurately. ' +
33
+ 'Always respond in the same language the user writes in.';
34
+
35
+ const sanitize = (n: string) => n.replace(/\./g, '__');
36
+ const restore = (n: string) => n.replace(/__/g, '.');
37
+
38
+ @Injectable()
39
+ export class McpChatService {
40
+ constructor(
41
+ @Inject(forwardRef(() => PrismaService))
42
+ private readonly prisma: PrismaService,
43
+ @Inject(forwardRef(() => SettingService))
44
+ private readonly setting: SettingService,
45
+ @Inject(forwardRef(() => McpRegistryService))
46
+ private readonly registry: McpRegistryService,
47
+ ) {}
48
+
49
+ async listConversations(userId: number) {
50
+ return this.prisma.mcp_conversation.findMany({
51
+ where: { user_id: userId },
52
+ orderBy: { created_at: 'desc' },
53
+ select: { id: true, title: true, created_at: true, updated_at: true },
54
+ });
55
+ }
56
+
57
+ async createConversation(userId: number, dto: CreateConversationDTO) {
58
+ return this.prisma.mcp_conversation.create({
59
+ data: {
60
+ user_id: userId,
61
+ title: dto.title ?? null,
62
+ },
63
+ });
64
+ }
65
+
66
+ async deleteConversations(userId: number, ids: number[]) {
67
+ await this.prisma.mcp_conversation.deleteMany({
68
+ where: { id: { in: ids }, user_id: userId },
69
+ });
70
+ return { count: ids.length };
71
+ }
72
+
73
+ async getMessages(conversationId: number, userId: number) {
74
+ await this.assertOwnership(conversationId, userId);
75
+ return this.prisma.mcp_conversation_message.findMany({
76
+ where: { conversation_id: conversationId },
77
+ orderBy: { created_at: 'asc' },
78
+ });
79
+ }
80
+
81
+ async sendMessage(
82
+ conversationId: number,
83
+ userId: number,
84
+ locale: string,
85
+ message: string,
86
+ ) {
87
+ await this.assertOwnership(conversationId, userId);
88
+
89
+ const apiKey = await this.getOpenAiKey();
90
+
91
+ // Save user message
92
+ await this.prisma.mcp_conversation_message.create({
93
+ data: {
94
+ conversation_id: conversationId,
95
+ role: 'user',
96
+ content: message,
97
+ },
98
+ });
99
+
100
+ // Auto-set title on first message
101
+ const conv = await this.prisma.mcp_conversation.findUnique({
102
+ where: { id: conversationId },
103
+ });
104
+ if (!conv!.title) {
105
+ await this.prisma.mcp_conversation.update({
106
+ where: { id: conversationId },
107
+ data: { title: message.slice(0, 60) },
108
+ });
109
+ }
110
+
111
+ // Build OpenAI messages from history
112
+ const history = await this.prisma.mcp_conversation_message.findMany({
113
+ where: { conversation_id: conversationId },
114
+ orderBy: { created_at: 'asc' },
115
+ });
116
+
117
+ const openAiMessages: OpenAiMessage[] = [
118
+ { role: 'system', content: SYSTEM_PROMPT },
119
+ ...this.toOpenAiMessages(history),
120
+ ];
121
+
122
+ // Build tool definitions
123
+ const tools = this.registry.getAll().map((entry) => ({
124
+ type: 'function' as const,
125
+ function: {
126
+ name: sanitize(entry.definition.name),
127
+ description: entry.definition.description,
128
+ parameters: entry.definition.inputSchema ?? {
129
+ type: 'object',
130
+ properties: {},
131
+ },
132
+ },
133
+ }));
134
+
135
+ // Tool-calling loop
136
+ let maxIterations = 10;
137
+ while (maxIterations-- > 0) {
138
+ let response: any;
139
+ try {
140
+ response = await axios.post(
141
+ 'https://api.openai.com/v1/chat/completions',
142
+ {
143
+ model: 'gpt-4o-mini',
144
+ messages: openAiMessages,
145
+ tools: tools.length > 0 ? tools : undefined,
146
+ tool_choice: tools.length > 0 ? 'auto' : undefined,
147
+ },
148
+ {
149
+ headers: {
150
+ Authorization: `Bearer ${apiKey}`,
151
+ 'Content-Type': 'application/json',
152
+ },
153
+ },
154
+ );
155
+ } catch (err: any) {
156
+ const status = err?.response?.status;
157
+ const errBody = err?.response?.data?.error;
158
+ if (status === 429) {
159
+ throw new HttpException(
160
+ errBody?.message ?? 'OpenAI rate limit exceeded. Please try again later.',
161
+ HttpStatus.TOO_MANY_REQUESTS,
162
+ );
163
+ }
164
+ if (status === 401) {
165
+ throw new BadRequestException('Invalid OpenAI API key.');
166
+ }
167
+ throw new HttpException(
168
+ errBody?.message ?? 'OpenAI API error.',
169
+ status ?? HttpStatus.BAD_GATEWAY,
170
+ );
171
+ }
172
+
173
+ const choice = response.data?.choices?.[0];
174
+ const assistantMsg = choice?.message;
175
+
176
+ if (assistantMsg?.tool_calls?.length) {
177
+ // Add assistant tool-call message to context
178
+ openAiMessages.push({
179
+ role: 'assistant',
180
+ content: null,
181
+ tool_calls: assistantMsg.tool_calls,
182
+ });
183
+
184
+ // Execute each tool call
185
+ for (const toolCall of assistantMsg.tool_calls as OpenAiToolCall[]) {
186
+ const originalName = restore(toolCall.function.name);
187
+ const argsJson = toolCall.function.arguments;
188
+ let args: Record<string, any> = {};
189
+ try {
190
+ args = JSON.parse(argsJson);
191
+ } catch {
192
+ args = {};
193
+ }
194
+
195
+ // Persist tool_call message
196
+ await this.prisma.mcp_conversation_message.create({
197
+ data: {
198
+ conversation_id: conversationId,
199
+ role: 'tool_call',
200
+ content: argsJson,
201
+ tool_name: originalName,
202
+ tool_call_id: toolCall.id,
203
+ },
204
+ });
205
+
206
+ // Execute the tool
207
+ let resultContent: string;
208
+ const entry = this.registry.get(originalName);
209
+ if (entry) {
210
+ const context: McpContext = {
211
+ userId,
212
+ sessionId: '',
213
+ locale,
214
+ toolName: originalName,
215
+ };
216
+ try {
217
+ const result = await entry.handler(args, context);
218
+ resultContent = JSON.stringify(result);
219
+ } catch (e: any) {
220
+ resultContent = JSON.stringify({ error: e.message });
221
+ }
222
+ } else {
223
+ resultContent = JSON.stringify({ error: `Tool '${originalName}' not found` });
224
+ }
225
+
226
+ // Persist tool_result message
227
+ await this.prisma.mcp_conversation_message.create({
228
+ data: {
229
+ conversation_id: conversationId,
230
+ role: 'tool_result',
231
+ content: resultContent,
232
+ tool_name: originalName,
233
+ tool_call_id: toolCall.id,
234
+ },
235
+ });
236
+
237
+ // Add tool result to context for next iteration
238
+ openAiMessages.push({
239
+ role: 'tool',
240
+ tool_call_id: toolCall.id,
241
+ content: resultContent,
242
+ });
243
+ }
244
+
245
+ continue; // next iteration with tool results
246
+ }
247
+
248
+ // Final assistant response
249
+ const finalContent: string = assistantMsg?.content || '';
250
+ await this.prisma.mcp_conversation_message.create({
251
+ data: {
252
+ conversation_id: conversationId,
253
+ role: 'assistant',
254
+ content: finalContent,
255
+ },
256
+ });
257
+
258
+ return this.prisma.mcp_conversation_message.findMany({
259
+ where: { conversation_id: conversationId },
260
+ orderBy: { created_at: 'asc' },
261
+ });
262
+ }
263
+
264
+ throw new BadRequestException('Maximum tool call iterations exceeded.');
265
+ }
266
+
267
+ private toOpenAiMessages(
268
+ history: Array<{
269
+ role: string;
270
+ content: string;
271
+ tool_name: string | null;
272
+ tool_call_id: string | null;
273
+ }>,
274
+ ): OpenAiMessage[] {
275
+ const messages: OpenAiMessage[] = [];
276
+ const toolCallMap = new Map<string, string>(); // tool_call_id → sanitized name
277
+
278
+ for (const msg of history) {
279
+ if (msg.role === 'user') {
280
+ messages.push({ role: 'user', content: msg.content });
281
+ } else if (msg.role === 'assistant') {
282
+ messages.push({ role: 'assistant', content: msg.content });
283
+ } else if (msg.role === 'tool_call' && msg.tool_call_id && msg.tool_name) {
284
+ const sanitizedName = sanitize(msg.tool_name);
285
+ toolCallMap.set(msg.tool_call_id, sanitizedName);
286
+ messages.push({
287
+ role: 'assistant',
288
+ content: null,
289
+ tool_calls: [
290
+ {
291
+ id: msg.tool_call_id,
292
+ type: 'function',
293
+ function: { name: sanitizedName, arguments: msg.content },
294
+ },
295
+ ],
296
+ });
297
+ } else if (msg.role === 'tool_result' && msg.tool_call_id) {
298
+ messages.push({
299
+ role: 'tool',
300
+ tool_call_id: msg.tool_call_id,
301
+ content: msg.content,
302
+ });
303
+ }
304
+ }
305
+
306
+ return messages;
307
+ }
308
+
309
+ private async assertOwnership(conversationId: number, userId: number) {
310
+ const conv = await this.prisma.mcp_conversation.findUnique({
311
+ where: { id: conversationId },
312
+ select: { user_id: true },
313
+ });
314
+ if (!conv) {
315
+ throw new NotFoundException(`Conversation ${conversationId} not found.`);
316
+ }
317
+ if (conv.user_id !== userId) {
318
+ throw new ForbiddenException('Access denied to this conversation.');
319
+ }
320
+ }
321
+
322
+ private async getOpenAiKey(): Promise<string> {
323
+ const settings = await this.setting.getSettingValues(['ai-openai-api-key']);
324
+ const key = settings['ai-openai-api-key'];
325
+ if (!key) {
326
+ throw new BadRequestException(
327
+ 'OpenAI API key is not configured (setting "ai-openai-api-key").',
328
+ );
329
+ }
330
+ return key;
331
+ }
332
+ }
@@ -1,14 +1,14 @@
1
1
  import { getLocaleText } from '@hed-hog/api-locale';
2
2
  import { PrismaService, user_account_provider_52222e2ecb_enum } from '@hed-hog/api-prisma';
3
3
  import {
4
- BadRequestException,
5
- ConflictException,
6
- Inject,
7
- Injectable,
8
- Logger,
9
- NotFoundException,
10
- ServiceUnavailableException,
11
- forwardRef
4
+ BadRequestException,
5
+ ConflictException,
6
+ Inject,
7
+ Injectable,
8
+ Logger,
9
+ NotFoundException,
10
+ ServiceUnavailableException,
11
+ forwardRef
12
12
  } from '@nestjs/common';
13
13
  import { AuthService } from '../auth/auth.service';
14
14
  import { FileService } from '../file/file.service';
@@ -21,9 +21,9 @@ import { UserService } from '../user/user.service';
21
21
  import { OAuthProvider } from './interfaces/OAuthProvider';
22
22
  import { OAuthCallbackCoordinatorService } from './oauth-callback-coordinator.service';
23
23
  import {
24
- OAuthCallbackAlreadyProcessedError,
25
- OAuthCallbackInProgressError,
26
- OAuthProviderException,
24
+ OAuthCallbackAlreadyProcessedError,
25
+ OAuthCallbackInProgressError,
26
+ OAuthProviderException,
27
27
  } from './oauth.errors';
28
28
  import { FacebookProvider } from './providers/facebook.provider';
29
29
  import { GithubProvider } from './providers/github.provider';
@@ -362,7 +362,7 @@ export class OAuthService {
362
362
  data: {
363
363
  name: profile.name,
364
364
  role_user: {
365
- connect: (await roleIds).map((role) => ({ id: role.id })),
365
+ create: (await roleIds).map((role) => ({ role_id: role.id })),
366
366
  },
367
367
  user_identifier: {
368
368
  create: {