@lenne.tech/nest-server 11.25.6 → 11.26.0

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 (335) hide show
  1. package/.claude/rules/configurable-features.md +2 -0
  2. package/CLAUDE.md +18 -0
  3. package/FRAMEWORK-API.md +48 -1
  4. package/dist/config.env.js +35 -1
  5. package/dist/config.env.js.map +1 -1
  6. package/dist/core/common/helpers/config.helper.js +2 -2
  7. package/dist/core/common/helpers/config.helper.js.map +1 -1
  8. package/dist/core/common/interceptors/check-security.interceptor.js +9 -2
  9. package/dist/core/common/interceptors/check-security.interceptor.js.map +1 -1
  10. package/dist/core/common/interfaces/server-options.interface.d.ts +89 -0
  11. package/dist/core/modules/ai/core-ai-mcp.controller.d.ts +21 -0
  12. package/dist/core/modules/ai/core-ai-mcp.controller.js +191 -0
  13. package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -0
  14. package/dist/core/modules/ai/core-ai.controller.d.ts +90 -0
  15. package/dist/core/modules/ai/core-ai.controller.js +576 -0
  16. package/dist/core/modules/ai/core-ai.controller.js.map +1 -0
  17. package/dist/core/modules/ai/core-ai.module.d.ts +43 -0
  18. package/dist/core/modules/ai/core-ai.module.js +164 -0
  19. package/dist/core/modules/ai/core-ai.module.js.map +1 -0
  20. package/dist/core/modules/ai/core-ai.resolver.d.ts +84 -0
  21. package/dist/core/modules/ai/core-ai.resolver.js +530 -0
  22. package/dist/core/modules/ai/core-ai.resolver.js.map +1 -0
  23. package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.d.ts +7 -0
  24. package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js +16 -0
  25. package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js.map +1 -0
  26. package/dist/core/modules/ai/hooks/ai-hook.base.d.ts +10 -0
  27. package/dist/core/modules/ai/hooks/ai-hook.base.js +30 -0
  28. package/dist/core/modules/ai/hooks/ai-hook.base.js.map +1 -0
  29. package/dist/core/modules/ai/hooks/ai-hook.registry.d.ts +22 -0
  30. package/dist/core/modules/ai/hooks/ai-hook.registry.js +98 -0
  31. package/dist/core/modules/ai/hooks/ai-hook.registry.js.map +1 -0
  32. package/dist/core/modules/ai/index.d.ts +69 -0
  33. package/dist/core/modules/ai/index.js +86 -0
  34. package/dist/core/modules/ai/index.js.map +1 -0
  35. package/dist/core/modules/ai/inputs/core-ai-budget-limit-create.input.d.ts +5 -0
  36. package/dist/core/modules/ai/inputs/core-ai-budget-limit-create.input.js +43 -0
  37. package/dist/core/modules/ai/inputs/core-ai-budget-limit-create.input.js.map +1 -0
  38. package/dist/core/modules/ai/inputs/core-ai-budget-limit.input.d.ts +7 -0
  39. package/dist/core/modules/ai/inputs/core-ai-budget-limit.input.js +74 -0
  40. package/dist/core/modules/ai/inputs/core-ai-budget-limit.input.js.map +1 -0
  41. package/dist/core/modules/ai/inputs/core-ai-connection-create.input.d.ts +6 -0
  42. package/dist/core/modules/ai/inputs/core-ai-connection-create.input.js +49 -0
  43. package/dist/core/modules/ai/inputs/core-ai-connection-create.input.js.map +1 -0
  44. package/dist/core/modules/ai/inputs/core-ai-connection-preference.input.d.ts +6 -0
  45. package/dist/core/modules/ai/inputs/core-ai-connection-preference.input.js +60 -0
  46. package/dist/core/modules/ai/inputs/core-ai-connection-preference.input.js.map +1 -0
  47. package/dist/core/modules/ai/inputs/core-ai-connection.input.d.ts +23 -0
  48. package/dist/core/modules/ai/inputs/core-ai-connection.input.js +229 -0
  49. package/dist/core/modules/ai/inputs/core-ai-connection.input.js.map +1 -0
  50. package/dist/core/modules/ai/inputs/core-ai-conversation-create.input.d.ts +3 -0
  51. package/dist/core/modules/ai/inputs/core-ai-conversation-create.input.js +21 -0
  52. package/dist/core/modules/ai/inputs/core-ai-conversation-create.input.js.map +1 -0
  53. package/dist/core/modules/ai/inputs/core-ai-conversation.input.d.ts +4 -0
  54. package/dist/core/modules/ai/inputs/core-ai-conversation.input.js +42 -0
  55. package/dist/core/modules/ai/inputs/core-ai-conversation.input.js.map +1 -0
  56. package/dist/core/modules/ai/inputs/core-ai-prompt-create.input.d.ts +10 -0
  57. package/dist/core/modules/ai/inputs/core-ai-prompt-create.input.js +74 -0
  58. package/dist/core/modules/ai/inputs/core-ai-prompt-create.input.js.map +1 -0
  59. package/dist/core/modules/ai/inputs/core-ai-prompt-hint-create.input.d.ts +5 -0
  60. package/dist/core/modules/ai/inputs/core-ai-prompt-hint-create.input.js +41 -0
  61. package/dist/core/modules/ai/inputs/core-ai-prompt-hint-create.input.js.map +1 -0
  62. package/dist/core/modules/ai/inputs/core-ai-prompt-hint.input.d.ts +6 -0
  63. package/dist/core/modules/ai/inputs/core-ai-prompt-hint.input.js +63 -0
  64. package/dist/core/modules/ai/inputs/core-ai-prompt-hint.input.js.map +1 -0
  65. package/dist/core/modules/ai/inputs/core-ai-prompt-update.input.d.ts +10 -0
  66. package/dist/core/modules/ai/inputs/core-ai-prompt-update.input.js +70 -0
  67. package/dist/core/modules/ai/inputs/core-ai-prompt-update.input.js.map +1 -0
  68. package/dist/core/modules/ai/inputs/core-ai-prompt.input.d.ts +18 -0
  69. package/dist/core/modules/ai/inputs/core-ai-prompt.input.js +131 -0
  70. package/dist/core/modules/ai/inputs/core-ai-prompt.input.js.map +1 -0
  71. package/dist/core/modules/ai/inputs/core-ai-slot-create.input.d.ts +5 -0
  72. package/dist/core/modules/ai/inputs/core-ai-slot-create.input.js +41 -0
  73. package/dist/core/modules/ai/inputs/core-ai-slot-create.input.js.map +1 -0
  74. package/dist/core/modules/ai/inputs/core-ai-slot-update.input.d.ts +9 -0
  75. package/dist/core/modules/ai/inputs/core-ai-slot-update.input.js +91 -0
  76. package/dist/core/modules/ai/inputs/core-ai-slot-update.input.js.map +1 -0
  77. package/dist/core/modules/ai/interfaces/ai-hook.interface.d.ts +28 -0
  78. package/dist/core/modules/ai/interfaces/ai-hook.interface.js +3 -0
  79. package/dist/core/modules/ai/interfaces/ai-hook.interface.js.map +1 -0
  80. package/dist/core/modules/ai/interfaces/ai-placeholder.interface.d.ts +21 -0
  81. package/dist/core/modules/ai/interfaces/ai-placeholder.interface.js +3 -0
  82. package/dist/core/modules/ai/interfaces/ai-placeholder.interface.js.map +1 -0
  83. package/dist/core/modules/ai/interfaces/ai-tool.interface.d.ts +30 -0
  84. package/dist/core/modules/ai/interfaces/ai-tool.interface.js +3 -0
  85. package/dist/core/modules/ai/interfaces/ai-tool.interface.js.map +1 -0
  86. package/dist/core/modules/ai/interfaces/llm-provider.interface.d.ts +55 -0
  87. package/dist/core/modules/ai/interfaces/llm-provider.interface.js +3 -0
  88. package/dist/core/modules/ai/interfaces/llm-provider.interface.js.map +1 -0
  89. package/dist/core/modules/ai/interfaces/resolved-ai-connection.interface.d.ts +16 -0
  90. package/dist/core/modules/ai/interfaces/resolved-ai-connection.interface.js +3 -0
  91. package/dist/core/modules/ai/interfaces/resolved-ai-connection.interface.js.map +1 -0
  92. package/dist/core/modules/ai/models/core-ai-action.model.d.ts +6 -0
  93. package/dist/core/modules/ai/models/core-ai-action.model.js +44 -0
  94. package/dist/core/modules/ai/models/core-ai-action.model.js.map +1 -0
  95. package/dist/core/modules/ai/models/core-ai-available-connection.model.d.ts +8 -0
  96. package/dist/core/modules/ai/models/core-ai-available-connection.model.js +53 -0
  97. package/dist/core/modules/ai/models/core-ai-available-connection.model.js.map +1 -0
  98. package/dist/core/modules/ai/models/core-ai-budget-limit.model.d.ts +97 -0
  99. package/dist/core/modules/ai/models/core-ai-budget-limit.model.js +79 -0
  100. package/dist/core/modules/ai/models/core-ai-budget-limit.model.js.map +1 -0
  101. package/dist/core/modules/ai/models/core-ai-connection-preference.model.d.ts +91 -0
  102. package/dist/core/modules/ai/models/core-ai-connection-preference.model.js +67 -0
  103. package/dist/core/modules/ai/models/core-ai-connection-preference.model.js.map +1 -0
  104. package/dist/core/modules/ai/models/core-ai-connection.model.d.ts +200 -0
  105. package/dist/core/modules/ai/models/core-ai-connection.model.js +263 -0
  106. package/dist/core/modules/ai/models/core-ai-connection.model.js.map +1 -0
  107. package/dist/core/modules/ai/models/core-ai-conversation.model.d.ts +93 -0
  108. package/dist/core/modules/ai/models/core-ai-conversation.model.js +82 -0
  109. package/dist/core/modules/ai/models/core-ai-conversation.model.js.map +1 -0
  110. package/dist/core/modules/ai/models/core-ai-interaction.model.d.ts +133 -0
  111. package/dist/core/modules/ai/models/core-ai-interaction.model.js +137 -0
  112. package/dist/core/modules/ai/models/core-ai-interaction.model.js.map +1 -0
  113. package/dist/core/modules/ai/models/core-ai-message.model.d.ts +5 -0
  114. package/dist/core/modules/ai/models/core-ai-message.model.js +38 -0
  115. package/dist/core/modules/ai/models/core-ai-message.model.js.map +1 -0
  116. package/dist/core/modules/ai/models/core-ai-mode.model.d.ts +109 -0
  117. package/dist/core/modules/ai/models/core-ai-mode.model.js +100 -0
  118. package/dist/core/modules/ai/models/core-ai-mode.model.js.map +1 -0
  119. package/dist/core/modules/ai/models/core-ai-prompt-hint.model.d.ts +103 -0
  120. package/dist/core/modules/ai/models/core-ai-prompt-hint.model.js +89 -0
  121. package/dist/core/modules/ai/models/core-ai-prompt-hint.model.js.map +1 -0
  122. package/dist/core/modules/ai/models/core-ai-prompt.model.d.ts +122 -0
  123. package/dist/core/modules/ai/models/core-ai-prompt.model.js +117 -0
  124. package/dist/core/modules/ai/models/core-ai-prompt.model.js.map +1 -0
  125. package/dist/core/modules/ai/models/core-ai-response.model.d.ts +29 -0
  126. package/dist/core/modules/ai/models/core-ai-response.model.js +105 -0
  127. package/dist/core/modules/ai/models/core-ai-response.model.js.map +1 -0
  128. package/dist/core/modules/ai/models/core-ai-slot.model.d.ts +121 -0
  129. package/dist/core/modules/ai/models/core-ai-slot.model.js +119 -0
  130. package/dist/core/modules/ai/models/core-ai-slot.model.js.map +1 -0
  131. package/dist/core/modules/ai/models/core-ai-tool-grant.model.d.ts +103 -0
  132. package/dist/core/modules/ai/models/core-ai-tool-grant.model.js +88 -0
  133. package/dist/core/modules/ai/models/core-ai-tool-grant.model.js.map +1 -0
  134. package/dist/core/modules/ai/models/core-ai-tool-policy.model.d.ts +122 -0
  135. package/dist/core/modules/ai/models/core-ai-tool-policy.model.js +121 -0
  136. package/dist/core/modules/ai/models/core-ai-tool-policy.model.js.map +1 -0
  137. package/dist/core/modules/ai/models/core-ai-usage-info.model.d.ts +24 -0
  138. package/dist/core/modules/ai/models/core-ai-usage-info.model.js +127 -0
  139. package/dist/core/modules/ai/models/core-ai-usage-info.model.js.map +1 -0
  140. package/dist/core/modules/ai/models/core-ai-usage.model.d.ts +5 -0
  141. package/dist/core/modules/ai/models/core-ai-usage.model.js +38 -0
  142. package/dist/core/modules/ai/models/core-ai-usage.model.js.map +1 -0
  143. package/dist/core/modules/ai/providers/claude-cli.provider.d.ts +16 -0
  144. package/dist/core/modules/ai/providers/claude-cli.provider.js +151 -0
  145. package/dist/core/modules/ai/providers/claude-cli.provider.js.map +1 -0
  146. package/dist/core/modules/ai/providers/llm-provider.factory.d.ts +10 -0
  147. package/dist/core/modules/ai/providers/llm-provider.factory.js +45 -0
  148. package/dist/core/modules/ai/providers/llm-provider.factory.js.map +1 -0
  149. package/dist/core/modules/ai/providers/openai-compatible.provider.d.ts +32 -0
  150. package/dist/core/modules/ai/providers/openai-compatible.provider.js +236 -0
  151. package/dist/core/modules/ai/providers/openai-compatible.provider.js.map +1 -0
  152. package/dist/core/modules/ai/services/ai-crypto.service.d.ts +11 -0
  153. package/dist/core/modules/ai/services/ai-crypto.service.js +84 -0
  154. package/dist/core/modules/ai/services/ai-crypto.service.js.map +1 -0
  155. package/dist/core/modules/ai/services/core-ai-budget.service.d.ts +42 -0
  156. package/dist/core/modules/ai/services/core-ai-budget.service.js +211 -0
  157. package/dist/core/modules/ai/services/core-ai-budget.service.js.map +1 -0
  158. package/dist/core/modules/ai/services/core-ai-connection-preference.service.d.ts +18 -0
  159. package/dist/core/modules/ai/services/core-ai-connection-preference.service.js +53 -0
  160. package/dist/core/modules/ai/services/core-ai-connection-preference.service.js.map +1 -0
  161. package/dist/core/modules/ai/services/core-ai-connection-resolver.service.d.ts +55 -0
  162. package/dist/core/modules/ai/services/core-ai-connection-resolver.service.js +177 -0
  163. package/dist/core/modules/ai/services/core-ai-connection-resolver.service.js.map +1 -0
  164. package/dist/core/modules/ai/services/core-ai-connection.service.d.ts +43 -0
  165. package/dist/core/modules/ai/services/core-ai-connection.service.js +272 -0
  166. package/dist/core/modules/ai/services/core-ai-connection.service.js.map +1 -0
  167. package/dist/core/modules/ai/services/core-ai-conversation.service.d.ts +26 -0
  168. package/dist/core/modules/ai/services/core-ai-conversation.service.js +72 -0
  169. package/dist/core/modules/ai/services/core-ai-conversation.service.js.map +1 -0
  170. package/dist/core/modules/ai/services/core-ai-interaction.service.d.ts +13 -0
  171. package/dist/core/modules/ai/services/core-ai-interaction.service.js +52 -0
  172. package/dist/core/modules/ai/services/core-ai-interaction.service.js.map +1 -0
  173. package/dist/core/modules/ai/services/core-ai-mcp-client.service.d.ts +51 -0
  174. package/dist/core/modules/ai/services/core-ai-mcp-client.service.js +123 -0
  175. package/dist/core/modules/ai/services/core-ai-mcp-client.service.js.map +1 -0
  176. package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.d.ts +68 -0
  177. package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js +279 -0
  178. package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js.map +1 -0
  179. package/dist/core/modules/ai/services/core-ai-mcp.service.d.ts +22 -0
  180. package/dist/core/modules/ai/services/core-ai-mcp.service.js +59 -0
  181. package/dist/core/modules/ai/services/core-ai-mcp.service.js.map +1 -0
  182. package/dist/core/modules/ai/services/core-ai-mode.service.d.ts +14 -0
  183. package/dist/core/modules/ai/services/core-ai-mode.service.js +56 -0
  184. package/dist/core/modules/ai/services/core-ai-mode.service.js.map +1 -0
  185. package/dist/core/modules/ai/services/core-ai-placeholder.registry.d.ts +15 -0
  186. package/dist/core/modules/ai/services/core-ai-placeholder.registry.js +102 -0
  187. package/dist/core/modules/ai/services/core-ai-placeholder.registry.js.map +1 -0
  188. package/dist/core/modules/ai/services/core-ai-prompt-builder.service.d.ts +42 -0
  189. package/dist/core/modules/ai/services/core-ai-prompt-builder.service.js +136 -0
  190. package/dist/core/modules/ai/services/core-ai-prompt-builder.service.js.map +1 -0
  191. package/dist/core/modules/ai/services/core-ai-prompt-hint.service.d.ts +29 -0
  192. package/dist/core/modules/ai/services/core-ai-prompt-hint.service.js +98 -0
  193. package/dist/core/modules/ai/services/core-ai-prompt-hint.service.js.map +1 -0
  194. package/dist/core/modules/ai/services/core-ai-prompt.service.d.ts +21 -0
  195. package/dist/core/modules/ai/services/core-ai-prompt.service.js +143 -0
  196. package/dist/core/modules/ai/services/core-ai-prompt.service.js.map +1 -0
  197. package/dist/core/modules/ai/services/core-ai-slot.service.d.ts +51 -0
  198. package/dist/core/modules/ai/services/core-ai-slot.service.js +372 -0
  199. package/dist/core/modules/ai/services/core-ai-slot.service.js.map +1 -0
  200. package/dist/core/modules/ai/services/core-ai-tool-grant.service.d.ts +22 -0
  201. package/dist/core/modules/ai/services/core-ai-tool-grant.service.js +97 -0
  202. package/dist/core/modules/ai/services/core-ai-tool-grant.service.js.map +1 -0
  203. package/dist/core/modules/ai/services/core-ai-tool-policy.service.d.ts +23 -0
  204. package/dist/core/modules/ai/services/core-ai-tool-policy.service.js +102 -0
  205. package/dist/core/modules/ai/services/core-ai-tool-policy.service.js.map +1 -0
  206. package/dist/core/modules/ai/services/core-ai.service.d.ts +144 -0
  207. package/dist/core/modules/ai/services/core-ai.service.js +991 -0
  208. package/dist/core/modules/ai/services/core-ai.service.js.map +1 -0
  209. package/dist/core/modules/ai/tools/ai-tool.base.d.ts +15 -0
  210. package/dist/core/modules/ai/tools/ai-tool.base.js +30 -0
  211. package/dist/core/modules/ai/tools/ai-tool.base.js.map +1 -0
  212. package/dist/core/modules/ai/tools/ai-tool.registry.d.ts +19 -0
  213. package/dist/core/modules/ai/tools/ai-tool.registry.js +71 -0
  214. package/dist/core/modules/ai/tools/ai-tool.registry.js.map +1 -0
  215. package/dist/core/modules/ai/tools/ask-user-question.tool.d.ts +38 -0
  216. package/dist/core/modules/ai/tools/ask-user-question.tool.js +65 -0
  217. package/dist/core/modules/ai/tools/ask-user-question.tool.js.map +1 -0
  218. package/dist/core/modules/ai/tools/search-tools.tool.d.ts +24 -0
  219. package/dist/core/modules/ai/tools/search-tools.tool.js +68 -0
  220. package/dist/core/modules/ai/tools/search-tools.tool.js.map +1 -0
  221. package/dist/core/modules/error-code/error-codes.d.ts +81 -0
  222. package/dist/core/modules/error-code/error-codes.js +72 -0
  223. package/dist/core/modules/error-code/error-codes.js.map +1 -1
  224. package/dist/core.module.js +8 -0
  225. package/dist/core.module.js.map +1 -1
  226. package/dist/index.d.ts +1 -0
  227. package/dist/index.js +1 -0
  228. package/dist/index.js.map +1 -1
  229. package/dist/server/modules/ai/ai-tools.module.d.ts +5 -0
  230. package/dist/server/modules/ai/ai-tools.module.js +36 -0
  231. package/dist/server/modules/ai/ai-tools.module.js.map +1 -0
  232. package/dist/server/modules/ai/tools/delete-user.tool.d.ts +30 -0
  233. package/dist/server/modules/ai/tools/delete-user.tool.js +49 -0
  234. package/dist/server/modules/ai/tools/delete-user.tool.js.map +1 -0
  235. package/dist/server/modules/ai/tools/find-users.tool.d.ts +30 -0
  236. package/dist/server/modules/ai/tools/find-users.tool.js +52 -0
  237. package/dist/server/modules/ai/tools/find-users.tool.js.map +1 -0
  238. package/dist/server/modules/ai/tools/get-user.tool.d.ts +26 -0
  239. package/dist/server/modules/ai/tools/get-user.tool.js +48 -0
  240. package/dist/server/modules/ai/tools/get-user.tool.js.map +1 -0
  241. package/dist/server/modules/ai/tools/update-user-job-title.tool.d.ts +32 -0
  242. package/dist/server/modules/ai/tools/update-user-job-title.tool.js +54 -0
  243. package/dist/server/modules/ai/tools/update-user-job-title.tool.js.map +1 -0
  244. package/dist/server/modules/error-code/error-codes.d.ts +9 -0
  245. package/dist/server/server.module.js +2 -0
  246. package/dist/server/server.module.js.map +1 -1
  247. package/dist/tsconfig.build.tsbuildinfo +1 -1
  248. package/docs/REQUEST-LIFECYCLE.md +10 -0
  249. package/migration-guides/11.25.x-to-11.26.0.md +278 -0
  250. package/package.json +2 -1
  251. package/src/config.env.ts +48 -1
  252. package/src/core/common/helpers/config.helper.ts +5 -2
  253. package/src/core/common/interceptors/check-security.interceptor.ts +13 -3
  254. package/src/core/common/interfaces/server-options.interface.ts +312 -0
  255. package/src/core/modules/ai/INTEGRATION-CHECKLIST.md +220 -0
  256. package/src/core/modules/ai/README.md +526 -0
  257. package/src/core/modules/ai/core-ai-mcp.controller.ts +223 -0
  258. package/src/core/modules/ai/core-ai.controller.ts +547 -0
  259. package/src/core/modules/ai/core-ai.module.ts +269 -0
  260. package/src/core/modules/ai/core-ai.resolver.ts +526 -0
  261. package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +35 -0
  262. package/src/core/modules/ai/hooks/ai-hook.base.ts +23 -0
  263. package/src/core/modules/ai/hooks/ai-hook.registry.ts +119 -0
  264. package/src/core/modules/ai/index.ts +70 -0
  265. package/src/core/modules/ai/inputs/core-ai-budget-limit-create.input.ts +27 -0
  266. package/src/core/modules/ai/inputs/core-ai-budget-limit.input.ts +52 -0
  267. package/src/core/modules/ai/inputs/core-ai-connection-create.input.ts +32 -0
  268. package/src/core/modules/ai/inputs/core-ai-connection-preference.input.ts +42 -0
  269. package/src/core/modules/ai/inputs/core-ai-connection.input.ts +235 -0
  270. package/src/core/modules/ai/inputs/core-ai-conversation-create.input.ts +13 -0
  271. package/src/core/modules/ai/inputs/core-ai-conversation.input.ts +33 -0
  272. package/src/core/modules/ai/inputs/core-ai-prompt-create.input.ts +50 -0
  273. package/src/core/modules/ai/inputs/core-ai-prompt-hint-create.input.ts +26 -0
  274. package/src/core/modules/ai/inputs/core-ai-prompt-hint.input.ts +43 -0
  275. package/src/core/modules/ai/inputs/core-ai-prompt-update.input.ts +42 -0
  276. package/src/core/modules/ai/inputs/core-ai-prompt.input.ts +144 -0
  277. package/src/core/modules/ai/inputs/core-ai-slot-create.input.ts +26 -0
  278. package/src/core/modules/ai/inputs/core-ai-slot-update.input.ts +65 -0
  279. package/src/core/modules/ai/interfaces/ai-hook.interface.ts +74 -0
  280. package/src/core/modules/ai/interfaces/ai-placeholder.interface.ts +49 -0
  281. package/src/core/modules/ai/interfaces/ai-tool.interface.ts +112 -0
  282. package/src/core/modules/ai/interfaces/llm-provider.interface.ts +187 -0
  283. package/src/core/modules/ai/interfaces/resolved-ai-connection.interface.ts +55 -0
  284. package/src/core/modules/ai/models/core-ai-action.model.ts +37 -0
  285. package/src/core/modules/ai/models/core-ai-available-connection.model.ts +42 -0
  286. package/src/core/modules/ai/models/core-ai-budget-limit.model.ts +82 -0
  287. package/src/core/modules/ai/models/core-ai-connection-preference.model.ts +72 -0
  288. package/src/core/modules/ai/models/core-ai-connection.model.ts +330 -0
  289. package/src/core/modules/ai/models/core-ai-conversation.model.ts +87 -0
  290. package/src/core/modules/ai/models/core-ai-interaction.model.ts +145 -0
  291. package/src/core/modules/ai/models/core-ai-message.model.ts +29 -0
  292. package/src/core/modules/ai/models/core-ai-mode.model.ts +93 -0
  293. package/src/core/modules/ai/models/core-ai-prompt-hint.model.ts +98 -0
  294. package/src/core/modules/ai/models/core-ai-prompt.model.ts +120 -0
  295. package/src/core/modules/ai/models/core-ai-response.model.ts +119 -0
  296. package/src/core/modules/ai/models/core-ai-slot.model.ts +146 -0
  297. package/src/core/modules/ai/models/core-ai-tool-grant.model.ts +87 -0
  298. package/src/core/modules/ai/models/core-ai-tool-policy.model.ts +115 -0
  299. package/src/core/modules/ai/models/core-ai-usage-info.model.ts +107 -0
  300. package/src/core/modules/ai/models/core-ai-usage.model.ts +29 -0
  301. package/src/core/modules/ai/providers/claude-cli.provider.ts +240 -0
  302. package/src/core/modules/ai/providers/llm-provider.factory.ts +63 -0
  303. package/src/core/modules/ai/providers/openai-compatible.provider.ts +329 -0
  304. package/src/core/modules/ai/services/ai-crypto.service.ts +120 -0
  305. package/src/core/modules/ai/services/core-ai-budget.service.ts +297 -0
  306. package/src/core/modules/ai/services/core-ai-connection-preference.service.ts +74 -0
  307. package/src/core/modules/ai/services/core-ai-connection-resolver.service.ts +308 -0
  308. package/src/core/modules/ai/services/core-ai-connection.service.ts +387 -0
  309. package/src/core/modules/ai/services/core-ai-conversation.service.ts +105 -0
  310. package/src/core/modules/ai/services/core-ai-interaction.service.ts +56 -0
  311. package/src/core/modules/ai/services/core-ai-mcp-client.service.ts +169 -0
  312. package/src/core/modules/ai/services/core-ai-mcp-oauth.service.ts +445 -0
  313. package/src/core/modules/ai/services/core-ai-mcp.service.ts +94 -0
  314. package/src/core/modules/ai/services/core-ai-mode.service.ts +43 -0
  315. package/src/core/modules/ai/services/core-ai-placeholder.registry.ts +123 -0
  316. package/src/core/modules/ai/services/core-ai-prompt-builder.service.ts +228 -0
  317. package/src/core/modules/ai/services/core-ai-prompt-hint.service.ts +133 -0
  318. package/src/core/modules/ai/services/core-ai-prompt.service.ts +164 -0
  319. package/src/core/modules/ai/services/core-ai-slot.service.ts +526 -0
  320. package/src/core/modules/ai/services/core-ai-tool-grant.service.ts +107 -0
  321. package/src/core/modules/ai/services/core-ai-tool-policy.service.ts +124 -0
  322. package/src/core/modules/ai/services/core-ai.service.ts +1396 -0
  323. package/src/core/modules/ai/tools/ai-tool.base.ts +46 -0
  324. package/src/core/modules/ai/tools/ai-tool.registry.ts +124 -0
  325. package/src/core/modules/ai/tools/ask-user-question.tool.ts +76 -0
  326. package/src/core/modules/ai/tools/search-tools.tool.ts +70 -0
  327. package/src/core/modules/error-code/error-codes.ts +86 -0
  328. package/src/core.module.ts +12 -0
  329. package/src/index.ts +6 -0
  330. package/src/server/modules/ai/ai-tools.module.ts +33 -0
  331. package/src/server/modules/ai/tools/delete-user.tool.ts +44 -0
  332. package/src/server/modules/ai/tools/find-users.tool.ts +49 -0
  333. package/src/server/modules/ai/tools/get-user.tool.ts +43 -0
  334. package/src/server/modules/ai/tools/update-user-job-title.tool.ts +62 -0
  335. package/src/server/server.module.ts +5 -0
@@ -0,0 +1,387 @@
1
+ import {
2
+ Inject,
3
+ Injectable,
4
+ Logger,
5
+ NotFoundException,
6
+ OnModuleInit,
7
+ Optional,
8
+ ServiceUnavailableException,
9
+ } from '@nestjs/common';
10
+ import { InjectModel } from '@nestjs/mongoose';
11
+ import { Model } from 'mongoose';
12
+
13
+ import { ServiceOptions } from '../../../common/interfaces/service-options.interface';
14
+ import { CrudService } from '../../../common/services/crud.service';
15
+ import { ConfigService } from '../../../common/services/config.service';
16
+ import { CoreModelConstructor } from '../../../common/types/core-model-constructor.type';
17
+ import { ErrorCode } from '../../error-code';
18
+ import { CoreAiConnectionCreateInput } from '../inputs/core-ai-connection-create.input';
19
+ import { CoreAiConnectionInput } from '../inputs/core-ai-connection.input';
20
+ import { ResolvedAiConnection } from '../interfaces/resolved-ai-connection.interface';
21
+ import { AiConnectionDocument, CoreAiConnection } from '../models/core-ai-connection.model';
22
+ import { LlmProviderFactory } from '../providers/llm-provider.factory';
23
+ import { AiCryptoService } from './ai-crypto.service';
24
+ import { CoreAiConnectionPreferenceService } from './core-ai-connection-preference.service';
25
+
26
+ /**
27
+ * Mongoose injection token for the AI connection model.
28
+ */
29
+ export const AI_CONNECTION_MODEL = 'AiConnection';
30
+
31
+ /**
32
+ * DI token for the AI connection model constructor (used by CrudService mapping).
33
+ */
34
+ export const AI_CONNECTION_CLASS = 'AI_CONNECTION_CLASS';
35
+
36
+ /**
37
+ * CRUD service for {@link CoreAiConnection} — the database-backed LLM
38
+ * configuration. Admin-only (enforced by the model's `@Restricted(ADMIN)` plus
39
+ * `@Roles(ADMIN)` on the resolver/controller).
40
+ *
41
+ * Responsibilities beyond plain CRUD:
42
+ * - encrypts the plaintext `apiKey` on create/update and never returns it
43
+ * - keeps the single `isDefault` connection unique
44
+ * - {@link resolve} produces a runtime {@link ResolvedAiConnection} (with the
45
+ * decrypted key) for the orchestrator/provider factory
46
+ * - optional one-time seeding of a default connection from config/env
47
+ *
48
+ * Extend this class in a project (and pass it via `CoreAiModule.forRoot({ connectionService })`)
49
+ * to customize behaviour.
50
+ */
51
+ @Injectable()
52
+ export class CoreAiConnectionService
53
+ extends CrudService<CoreAiConnection, CoreAiConnectionCreateInput, CoreAiConnectionInput>
54
+ implements OnModuleInit
55
+ {
56
+ protected readonly logger = new Logger(CoreAiConnectionService.name);
57
+
58
+ constructor(
59
+ protected readonly aiCryptoService: AiCryptoService,
60
+ @InjectModel(AI_CONNECTION_MODEL) protected override readonly mainDbModel: Model<AiConnectionDocument>,
61
+ @Inject(AI_CONNECTION_CLASS)
62
+ protected override readonly mainModelConstructor: CoreModelConstructor<CoreAiConnection>,
63
+ // Optional: used only to clean up dangling preferences when a connection is deleted.
64
+ @Optional() protected readonly preferenceService?: CoreAiConnectionPreferenceService,
65
+ // Optional: used to auto-detect capabilities (JSON / native tools) by probing the endpoint.
66
+ @Optional() protected readonly providerFactory?: LlmProviderFactory,
67
+ ) {
68
+ super();
69
+ }
70
+
71
+ /**
72
+ * One-time seeding: if no connections exist and `ai.defaultConnection` is
73
+ * configured, create it as the default. Keeps the DB the source of truth while
74
+ * offering a smooth bootstrap from config/env.
75
+ */
76
+ async onModuleInit(): Promise<void> {
77
+ await this.seedDefaultConnection();
78
+ await this.assertStoredKeysDecryptable();
79
+ }
80
+
81
+ /**
82
+ * One-time seeding of `ai.defaultConnection`. Skips silently when no default
83
+ * connection is configured, but warns when it is configured incompletely (so a
84
+ * missing `AI_*` env var doesn't fail silently). No-op once any connection exists.
85
+ */
86
+ protected async seedDefaultConnection(): Promise<void> {
87
+ const seed = ConfigService.get<Record<string, any>>('ai.defaultConnection');
88
+ if (!seed) {
89
+ return; // no default connection configured — normal
90
+ }
91
+ if (!seed.baseUrl || !seed.model || !seed.name) {
92
+ this.logger.warn(
93
+ `ai.defaultConnection seed skipped: incomplete config — need baseUrl, model and name ` +
94
+ `(got baseUrl=${!!seed.baseUrl}, model=${!!seed.model}, name=${!!seed.name}). ` +
95
+ `Set the missing AI_* env vars or create the connection via the admin UI.`,
96
+ );
97
+ return;
98
+ }
99
+ const count = await this.mainDbModel.countDocuments().exec();
100
+ if (count > 0) {
101
+ return;
102
+ }
103
+ const { apiKey, ...rest } = seed;
104
+ // Direct model access: system-internal seeding without a user context, and we
105
+ // must set the internal `apiKeyEncrypted` field which CrudService does not expose.
106
+ await this.mainDbModel.create({
107
+ ...rest,
108
+ apiKeyEncrypted: apiKey ? this.aiCryptoService.encrypt(apiKey) : undefined,
109
+ isDefault: true,
110
+ providerType: rest.providerType || 'openai-compatible',
111
+ });
112
+ this.logger.log(`Seeded default AI connection "${seed.name}"`);
113
+ }
114
+
115
+ /**
116
+ * Best-effort boot self-check: detect connections whose stored API key can no
117
+ * longer be decrypted (typically after the encryption secret was changed/rotated).
118
+ * Logs a clear, actionable error per connection instead of letting prompts fail
119
+ * deep inside the request later. Never throws — a check failure must not block boot.
120
+ */
121
+ protected async assertStoredKeysDecryptable(): Promise<void> {
122
+ try {
123
+ const docs = await this.mainDbModel
124
+ .find({ apiKeyEncrypted: { $exists: true, $ne: '' } }, { apiKeyEncrypted: 1, name: 1 })
125
+ .lean()
126
+ .exec();
127
+ const broken: string[] = [];
128
+ for (const doc of docs as { _id: unknown; apiKeyEncrypted?: string; name?: string }[]) {
129
+ try {
130
+ this.aiCryptoService.decrypt(doc.apiKeyEncrypted ?? '');
131
+ } catch {
132
+ broken.push(doc.name || String(doc._id));
133
+ }
134
+ }
135
+ if (broken.length) {
136
+ this.logger.error(
137
+ `AI connection API key(s) could not be decrypted (encryption secret changed or value corrupted): ` +
138
+ `${broken.join(', ')}. Re-enter the API key for these connections — prompts using them will fail until fixed.`,
139
+ );
140
+ }
141
+ } catch (err) {
142
+ this.logger.warn(`AI key decryptability self-check skipped: ${(err as Error).message}`);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Create a connection. Encrypts the optional plaintext `apiKey` and keeps the
148
+ * default connection unique.
149
+ */
150
+ override async create(
151
+ input: CoreAiConnectionCreateInput,
152
+ serviceOptions?: ServiceOptions,
153
+ ): Promise<CoreAiConnection> {
154
+ const { apiKey, ...rest } = input;
155
+ let created = await super.create(rest as CoreAiConnectionCreateInput, serviceOptions);
156
+ if (input.isDefault) {
157
+ await this.unsetOtherDefaults(created.id);
158
+ }
159
+ if (apiKey !== undefined) {
160
+ await this.applyApiKey(created.id, apiKey);
161
+ created = await this.get(created.id, serviceOptions);
162
+ }
163
+ // Eager capability auto-detection (A): probe the endpoint when a capability flag
164
+ // was left undefined. Best-effort — never fails the create (the lazy runtime path
165
+ // re-tries on first prompt).
166
+ if (this.providerFactory && (input.supportsJsonResponse === undefined || input.supportsNativeTools === undefined)) {
167
+ await this.detectAndPersistCapabilities(created.id).catch(() => undefined);
168
+ created = await this.get(created.id, serviceOptions);
169
+ }
170
+ return created;
171
+ }
172
+
173
+ /**
174
+ * Update a connection. Patch semantics for `apiKey` (set/clear/leave) and keeps
175
+ * the default connection unique.
176
+ */
177
+ override async update(
178
+ id: string,
179
+ input: CoreAiConnectionInput,
180
+ serviceOptions?: ServiceOptions,
181
+ ): Promise<CoreAiConnection> {
182
+ const { apiKey, ...rest } = input;
183
+ let updated = await super.update(id, rest as CoreAiConnectionInput, serviceOptions);
184
+ if (input.isDefault) {
185
+ await this.unsetOtherDefaults(id);
186
+ }
187
+ if (apiKey !== undefined) {
188
+ await this.applyApiKey(id, apiKey);
189
+ updated = await this.get(id, serviceOptions);
190
+ }
191
+ return updated;
192
+ }
193
+
194
+ /**
195
+ * Delete a connection and clean up any tenant/user preferences that pointed to it
196
+ * (avoids dangling preferences). The cleanup is best-effort and never fails the delete.
197
+ */
198
+ override async delete(id: string, serviceOptions?: ServiceOptions): Promise<CoreAiConnection> {
199
+ const deleted = await super.delete(id, serviceOptions);
200
+ if (this.preferenceService) {
201
+ try {
202
+ await this.preferenceService.deleteByConnectionId(id);
203
+ } catch (err) {
204
+ this.logger.warn(`Failed to clean up AI connection preferences for "${id}": ${(err as Error).message}`);
205
+ }
206
+ }
207
+ return deleted;
208
+ }
209
+
210
+ /**
211
+ * Auto-detect capabilities (JSON / native tools) for flags the connection left
212
+ * undefined, persist the detected booleans, and return the resolved connection
213
+ * (with the decrypted key) reflecting them. Explicit flags are authoritative and
214
+ * are never probed. Best effort: on a probe/transport failure nothing is persisted
215
+ * (so it is retried later) and the connection is returned unchanged — undefined
216
+ * flags then fall back to the safe emulated baseline for the current run.
217
+ *
218
+ * Used eagerly on create (A) and lazily by the orchestrator on first prompt (B);
219
+ * also exposed for an explicit admin "detect" endpoint.
220
+ */
221
+ async detectAndPersistCapabilities(connectionId: string): Promise<ResolvedAiConnection> {
222
+ const resolved = await this.resolve(connectionId);
223
+ const needsCapabilities = resolved.supportsJsonResponse === undefined || resolved.supportsNativeTools === undefined;
224
+ const needsContextWindow = resolved.contextWindow === undefined;
225
+ if (!this.providerFactory || (!needsCapabilities && !needsContextWindow)) {
226
+ return resolved;
227
+ }
228
+
229
+ let provider: {
230
+ detectCapabilities?: () => Promise<{ jsonResponse?: boolean; nativeTools?: boolean }>;
231
+ detectContextWindow?: () => Promise<number | undefined>;
232
+ };
233
+ try {
234
+ provider = this.providerFactory.create(resolved);
235
+ } catch (err) {
236
+ this.logger.warn(`AI capability detection skipped for "${resolved.name}": ${(err as Error).message}`);
237
+ return resolved;
238
+ }
239
+
240
+ const update: Record<string, boolean | number> = {};
241
+ if (needsCapabilities && typeof provider.detectCapabilities === 'function') {
242
+ try {
243
+ const detected = await provider.detectCapabilities();
244
+ if (resolved.supportsJsonResponse === undefined && typeof detected.jsonResponse === 'boolean') {
245
+ update.supportsJsonResponse = detected.jsonResponse;
246
+ resolved.supportsJsonResponse = detected.jsonResponse;
247
+ }
248
+ if (resolved.supportsNativeTools === undefined && typeof detected.nativeTools === 'boolean') {
249
+ update.supportsNativeTools = detected.nativeTools;
250
+ resolved.supportsNativeTools = detected.nativeTools;
251
+ }
252
+ } catch (err) {
253
+ this.logger.warn(`AI capability detection failed for "${resolved.name}": ${(err as Error).message}`);
254
+ }
255
+ }
256
+ if (needsContextWindow && typeof provider.detectContextWindow === 'function') {
257
+ try {
258
+ const window = await provider.detectContextWindow();
259
+ if (typeof window === 'number' && window > 0) {
260
+ update.contextWindow = window;
261
+ resolved.contextWindow = window;
262
+ }
263
+ } catch (err) {
264
+ this.logger.warn(`AI context-window detection failed for "${resolved.name}": ${(err as Error).message}`);
265
+ }
266
+ }
267
+ if (Object.keys(update).length) {
268
+ await this.mainDbModel.findByIdAndUpdate(connectionId, { $set: update }).exec();
269
+ this.logger.log(`Auto-detected AI connection metadata for "${resolved.name}": ${JSON.stringify(update)}`);
270
+ }
271
+ return resolved;
272
+ }
273
+
274
+ /**
275
+ * List all enabled connections with the fields the resolver needs (system-internal
276
+ * lean read; never exposes the API key). Used for availability + default resolution.
277
+ */
278
+ async listUsable(): Promise<
279
+ {
280
+ enforced?: boolean;
281
+ enforcedTenantIds?: string[];
282
+ id: string;
283
+ isDefault?: boolean;
284
+ model?: string;
285
+ name?: string;
286
+ tenantIds?: string[];
287
+ }[]
288
+ > {
289
+ const docs = await this.mainDbModel
290
+ .find({ enabled: { $ne: false } })
291
+ .select('enforced enforcedTenantIds isDefault model name tenantIds')
292
+ .sort({ createdAt: 1 })
293
+ .lean()
294
+ .exec();
295
+ return docs.map((doc) => ({
296
+ enforced: doc.enforced,
297
+ enforcedTenantIds: doc.enforcedTenantIds,
298
+ id: String(doc._id),
299
+ isDefault: doc.isDefault,
300
+ model: doc.model,
301
+ name: doc.name,
302
+ tenantIds: doc.tenantIds,
303
+ }));
304
+ }
305
+
306
+ /**
307
+ * Resolve a connection to a runtime {@link ResolvedAiConnection} with the
308
+ * decrypted API key. System-internal — never expose the result to clients.
309
+ *
310
+ * @param connectionId Specific connection, or undefined for the default / first enabled one.
311
+ */
312
+ async resolve(connectionId?: string): Promise<ResolvedAiConnection> {
313
+ // Direct lean reads: system-internal resolution that must access the
314
+ // encrypted key field, which CrudService strips from outputs.
315
+ let doc = connectionId
316
+ ? await this.mainDbModel.findById(connectionId).lean().exec()
317
+ : await this.mainDbModel
318
+ .findOne({ enabled: { $ne: false }, isDefault: true })
319
+ .lean()
320
+ .exec();
321
+
322
+ if (!doc && !connectionId) {
323
+ doc = await this.mainDbModel
324
+ .findOne({ enabled: { $ne: false } })
325
+ .sort({ createdAt: 1 })
326
+ .lean()
327
+ .exec();
328
+ }
329
+
330
+ if (!doc) {
331
+ throw new NotFoundException(ErrorCode.AI_NO_CONNECTION);
332
+ }
333
+ if (doc.enabled === false) {
334
+ throw new ServiceUnavailableException(ErrorCode.AI_CONNECTION_DISABLED);
335
+ }
336
+
337
+ return {
338
+ apiKey: this.resolveApiKeyFromDoc(doc) ?? '',
339
+ baseUrl: doc.baseUrl,
340
+ contextWindow: doc.contextWindow,
341
+ defaultMaxTokens: doc.defaultMaxTokens,
342
+ defaultTemperature: doc.defaultTemperature,
343
+ defaultUserMaxPeriod: doc.defaultUserMaxPeriod,
344
+ defaultUserMaxTokens: doc.defaultUserMaxTokens,
345
+ id: String(doc._id),
346
+ model: doc.model,
347
+ name: doc.name,
348
+ providerType: doc.providerType || 'openai-compatible',
349
+ supportsJsonResponse: doc.supportsJsonResponse,
350
+ supportsNativeTools: doc.supportsNativeTools,
351
+ };
352
+ }
353
+
354
+ /**
355
+ * Encrypt + store, clear, or leave the API key based on patch semantics.
356
+ */
357
+ protected async applyApiKey(id: string, apiKey: string): Promise<void> {
358
+ if (apiKey === '') {
359
+ await this.mainDbModel.findByIdAndUpdate(id, { $unset: { apiKeyEncrypted: 1 } }).exec();
360
+ } else {
361
+ await this.mainDbModel
362
+ .findByIdAndUpdate(id, { $set: { apiKeyEncrypted: this.aiCryptoService.encrypt(apiKey) } })
363
+ .exec();
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Read the plaintext API key from an encrypted DB value, falling back to the
369
+ * configured environment variable.
370
+ */
371
+ protected resolveApiKeyFromDoc(doc: Pick<CoreAiConnection, 'apiKeyEncrypted' | 'apiKeyEnv'>): string | undefined {
372
+ if (doc?.apiKeyEncrypted) {
373
+ return this.aiCryptoService.decrypt(doc.apiKeyEncrypted);
374
+ }
375
+ if (doc?.apiKeyEnv) {
376
+ return process.env[doc.apiKeyEnv];
377
+ }
378
+ return undefined;
379
+ }
380
+
381
+ /**
382
+ * Ensure only one connection is marked as default.
383
+ */
384
+ protected async unsetOtherDefaults(id: string): Promise<void> {
385
+ await this.mainDbModel.updateMany({ _id: { $ne: id } }, { $set: { isDefault: false } }).exec();
386
+ }
387
+ }
@@ -0,0 +1,105 @@
1
+ import { Inject, Injectable } from '@nestjs/common';
2
+ import { InjectModel } from '@nestjs/mongoose';
3
+ import { Model, Types } from 'mongoose';
4
+
5
+ import { CrudService } from '../../../common/services/crud.service';
6
+ import { CoreModelConstructor } from '../../../common/types/core-model-constructor.type';
7
+ import { CoreAiConversationCreateInput } from '../inputs/core-ai-conversation-create.input';
8
+ import { CoreAiConversationInput } from '../inputs/core-ai-conversation.input';
9
+ import { AiConversationDocument, CoreAiConversation } from '../models/core-ai-conversation.model';
10
+
11
+ /**
12
+ * Mongoose injection token for the AI conversation model.
13
+ */
14
+ export const AI_CONVERSATION_MODEL = 'AiConversation';
15
+
16
+ /**
17
+ * DI token for the AI conversation model constructor.
18
+ */
19
+ export const AI_CONVERSATION_CLASS = 'AI_CONVERSATION_CLASS';
20
+
21
+ /**
22
+ * CRUD service for multi-turn {@link CoreAiConversation}s.
23
+ *
24
+ * `appendMessage()` adds a turn via `$push` (never round-trips the subdocument
25
+ * array through `update()`). Ownership is enforced by the model's `securityCheck`
26
+ * and by the resolver/controller passing `S_CREATOR`/`S_SELF` roles.
27
+ */
28
+ @Injectable()
29
+ export class CoreAiConversationService extends CrudService<
30
+ CoreAiConversation,
31
+ CoreAiConversationCreateInput,
32
+ CoreAiConversationInput
33
+ > {
34
+ /** Maximum number of messages retained per conversation (capped on `$push`). */
35
+ protected readonly maxRetainedMessages = 500;
36
+
37
+ constructor(
38
+ @InjectModel(AI_CONVERSATION_MODEL) protected override readonly mainDbModel: Model<AiConversationDocument>,
39
+ @Inject(AI_CONVERSATION_CLASS)
40
+ protected override readonly mainModelConstructor: CoreModelConstructor<CoreAiConversation>,
41
+ ) {
42
+ super();
43
+ }
44
+
45
+ /**
46
+ * Append a message to a conversation via `$push` (system-internal; the user
47
+ * already authorized the prompt that produced it). The array is capped at
48
+ * {@link maxRetainedMessages} via `$slice` so a long-lived conversation cannot
49
+ * grow unbounded.
50
+ */
51
+ async appendMessage(id: string, message: { content: string; createdAt?: Date; role: string }): Promise<void> {
52
+ await this.mainDbModel
53
+ .findByIdAndUpdate(id, {
54
+ $push: {
55
+ messages: {
56
+ $each: [{ ...message, createdAt: message.createdAt ?? new Date() }],
57
+ $slice: -this.maxRetainedMessages,
58
+ },
59
+ },
60
+ })
61
+ .exec();
62
+ }
63
+
64
+ /**
65
+ * Load the most recent turns of a conversation as a lean, projected read for the
66
+ * orchestrator's LLM context — avoids the full `process()` pipeline over the whole
67
+ * subdocument array on every turn. Performs an explicit ownership check (creator or
68
+ * admin) because the lean read bypasses the model's `securityCheck`.
69
+ *
70
+ * The `id` parameter is forwarded from `aiPrompt` input — a hostile or
71
+ * misconfigured caller may pass an empty string, the literal strings `"null"`
72
+ * / `"undefined"`, or anything that does not parse as a valid ObjectId. We
73
+ * fail-soft to `[]` instead of letting Mongoose throw a BSON cast error; the
74
+ * orchestrator then proceeds without prior history, which is the same
75
+ * outcome as no conversationId at all. We deliberately do NOT 404 here
76
+ * because this method is called from inside the prompt pipeline.
77
+ *
78
+ * @returns the last `limit` turns ({ content, role }), or `[]` if not found / not owned / invalid id.
79
+ */
80
+ async loadRecentMessages(
81
+ id: string,
82
+ currentUser: { id?: string; roles?: string[] } | undefined,
83
+ limit = 20,
84
+ ): Promise<{ content: string; role: string }[]> {
85
+ if (!id || typeof id !== 'string' || !Types.ObjectId.isValid(id)) {
86
+ return [];
87
+ }
88
+ const doc = await this.mainDbModel
89
+ .findById(id, { createdBy: 1, messages: { $slice: -limit } })
90
+ .lean()
91
+ .exec();
92
+ if (!doc) {
93
+ return [];
94
+ }
95
+ const isOwner = currentUser?.id && String((doc as { createdBy?: unknown }).createdBy) === String(currentUser.id);
96
+ const isAdmin = currentUser?.roles?.includes('admin');
97
+ if (!isOwner && !isAdmin) {
98
+ return [];
99
+ }
100
+ return ((doc as { messages?: { content: string; role: string }[] }).messages ?? []).map((m) => ({
101
+ content: m.content,
102
+ role: m.role,
103
+ }));
104
+ }
105
+ }
@@ -0,0 +1,56 @@
1
+ import { Inject, Injectable } from '@nestjs/common';
2
+ import { InjectModel } from '@nestjs/mongoose';
3
+ import { Model } from 'mongoose';
4
+
5
+ import { CrudService } from '../../../common/services/crud.service';
6
+ import { CoreModelConstructor } from '../../../common/types/core-model-constructor.type';
7
+ import { AiInteractionDocument, CoreAiInteraction } from '../models/core-ai-interaction.model';
8
+ import type { AiInteractionRecord } from './core-ai.service';
9
+
10
+ /**
11
+ * Mongoose injection token for the AI interaction model.
12
+ */
13
+ export const AI_INTERACTION_MODEL = 'AiInteraction';
14
+
15
+ /**
16
+ * DI token for the AI interaction model constructor.
17
+ */
18
+ export const AI_INTERACTION_CLASS = 'AI_INTERACTION_CLASS';
19
+
20
+ /**
21
+ * CRUD + write service for {@link CoreAiInteraction} audit records.
22
+ *
23
+ * `record()` persists a run system-internally (no user context). The standard
24
+ * CrudService methods power admin read endpoints. Extend and pass via
25
+ * `CoreAiModule.forRoot({ interactionService })` to customize.
26
+ */
27
+ @Injectable()
28
+ export class CoreAiInteractionService extends CrudService<CoreAiInteraction> {
29
+ constructor(
30
+ @InjectModel(AI_INTERACTION_MODEL) protected override readonly mainDbModel: Model<AiInteractionDocument>,
31
+ @Inject(AI_INTERACTION_CLASS)
32
+ protected override readonly mainModelConstructor: CoreModelConstructor<CoreAiInteraction>,
33
+ ) {
34
+ super();
35
+ }
36
+
37
+ /**
38
+ * Persist an audit record. System-internal (direct model access, no user).
39
+ * `tenantId` is auto-set by the tenant plugin from the request context, but is
40
+ * passed through explicitly when known for robustness.
41
+ */
42
+ async record(rec: AiInteractionRecord): Promise<void> {
43
+ await this.mainDbModel.create({
44
+ actions: rec.actions,
45
+ completionTokens: rec.usage?.completionTokens,
46
+ connectionId: rec.connectionId,
47
+ iterations: rec.iterations,
48
+ prompt: rec.prompt,
49
+ promptTokens: rec.usage?.promptTokens,
50
+ responseText: rec.responseText,
51
+ tenantId: rec.tenantId,
52
+ totalTokens: rec.usage?.totalTokens,
53
+ userId: rec.userId,
54
+ });
55
+ }
56
+ }