@lenne.tech/nest-server 11.25.5 → 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 (341) 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/better-auth/core-better-auth-user.mapper.js +7 -1
  222. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
  223. package/dist/core/modules/error-code/error-codes.d.ts +81 -0
  224. package/dist/core/modules/error-code/error-codes.js +72 -0
  225. package/dist/core/modules/error-code/error-codes.js.map +1 -1
  226. package/dist/core/modules/tenant/core-tenant.service.js +3 -3
  227. package/dist/core/modules/tenant/core-tenant.service.js.map +1 -1
  228. package/dist/core.module.js +8 -0
  229. package/dist/core.module.js.map +1 -1
  230. package/dist/index.d.ts +1 -0
  231. package/dist/index.js +1 -0
  232. package/dist/index.js.map +1 -1
  233. package/dist/server/modules/ai/ai-tools.module.d.ts +5 -0
  234. package/dist/server/modules/ai/ai-tools.module.js +36 -0
  235. package/dist/server/modules/ai/ai-tools.module.js.map +1 -0
  236. package/dist/server/modules/ai/tools/delete-user.tool.d.ts +30 -0
  237. package/dist/server/modules/ai/tools/delete-user.tool.js +49 -0
  238. package/dist/server/modules/ai/tools/delete-user.tool.js.map +1 -0
  239. package/dist/server/modules/ai/tools/find-users.tool.d.ts +30 -0
  240. package/dist/server/modules/ai/tools/find-users.tool.js +52 -0
  241. package/dist/server/modules/ai/tools/find-users.tool.js.map +1 -0
  242. package/dist/server/modules/ai/tools/get-user.tool.d.ts +26 -0
  243. package/dist/server/modules/ai/tools/get-user.tool.js +48 -0
  244. package/dist/server/modules/ai/tools/get-user.tool.js.map +1 -0
  245. package/dist/server/modules/ai/tools/update-user-job-title.tool.d.ts +32 -0
  246. package/dist/server/modules/ai/tools/update-user-job-title.tool.js +54 -0
  247. package/dist/server/modules/ai/tools/update-user-job-title.tool.js.map +1 -0
  248. package/dist/server/modules/error-code/error-codes.d.ts +9 -0
  249. package/dist/server/server.module.js +2 -0
  250. package/dist/server/server.module.js.map +1 -1
  251. package/dist/tsconfig.build.tsbuildinfo +1 -1
  252. package/docs/REQUEST-LIFECYCLE.md +10 -0
  253. package/migration-guides/11.25.x-to-11.26.0.md +278 -0
  254. package/package.json +44 -26
  255. package/src/config.env.ts +48 -1
  256. package/src/core/common/helpers/config.helper.ts +5 -2
  257. package/src/core/common/interceptors/check-security.interceptor.ts +13 -3
  258. package/src/core/common/interfaces/server-options.interface.ts +312 -0
  259. package/src/core/modules/ai/INTEGRATION-CHECKLIST.md +220 -0
  260. package/src/core/modules/ai/README.md +526 -0
  261. package/src/core/modules/ai/core-ai-mcp.controller.ts +223 -0
  262. package/src/core/modules/ai/core-ai.controller.ts +547 -0
  263. package/src/core/modules/ai/core-ai.module.ts +269 -0
  264. package/src/core/modules/ai/core-ai.resolver.ts +526 -0
  265. package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +35 -0
  266. package/src/core/modules/ai/hooks/ai-hook.base.ts +23 -0
  267. package/src/core/modules/ai/hooks/ai-hook.registry.ts +119 -0
  268. package/src/core/modules/ai/index.ts +70 -0
  269. package/src/core/modules/ai/inputs/core-ai-budget-limit-create.input.ts +27 -0
  270. package/src/core/modules/ai/inputs/core-ai-budget-limit.input.ts +52 -0
  271. package/src/core/modules/ai/inputs/core-ai-connection-create.input.ts +32 -0
  272. package/src/core/modules/ai/inputs/core-ai-connection-preference.input.ts +42 -0
  273. package/src/core/modules/ai/inputs/core-ai-connection.input.ts +235 -0
  274. package/src/core/modules/ai/inputs/core-ai-conversation-create.input.ts +13 -0
  275. package/src/core/modules/ai/inputs/core-ai-conversation.input.ts +33 -0
  276. package/src/core/modules/ai/inputs/core-ai-prompt-create.input.ts +50 -0
  277. package/src/core/modules/ai/inputs/core-ai-prompt-hint-create.input.ts +26 -0
  278. package/src/core/modules/ai/inputs/core-ai-prompt-hint.input.ts +43 -0
  279. package/src/core/modules/ai/inputs/core-ai-prompt-update.input.ts +42 -0
  280. package/src/core/modules/ai/inputs/core-ai-prompt.input.ts +144 -0
  281. package/src/core/modules/ai/inputs/core-ai-slot-create.input.ts +26 -0
  282. package/src/core/modules/ai/inputs/core-ai-slot-update.input.ts +65 -0
  283. package/src/core/modules/ai/interfaces/ai-hook.interface.ts +74 -0
  284. package/src/core/modules/ai/interfaces/ai-placeholder.interface.ts +49 -0
  285. package/src/core/modules/ai/interfaces/ai-tool.interface.ts +112 -0
  286. package/src/core/modules/ai/interfaces/llm-provider.interface.ts +187 -0
  287. package/src/core/modules/ai/interfaces/resolved-ai-connection.interface.ts +55 -0
  288. package/src/core/modules/ai/models/core-ai-action.model.ts +37 -0
  289. package/src/core/modules/ai/models/core-ai-available-connection.model.ts +42 -0
  290. package/src/core/modules/ai/models/core-ai-budget-limit.model.ts +82 -0
  291. package/src/core/modules/ai/models/core-ai-connection-preference.model.ts +72 -0
  292. package/src/core/modules/ai/models/core-ai-connection.model.ts +330 -0
  293. package/src/core/modules/ai/models/core-ai-conversation.model.ts +87 -0
  294. package/src/core/modules/ai/models/core-ai-interaction.model.ts +145 -0
  295. package/src/core/modules/ai/models/core-ai-message.model.ts +29 -0
  296. package/src/core/modules/ai/models/core-ai-mode.model.ts +93 -0
  297. package/src/core/modules/ai/models/core-ai-prompt-hint.model.ts +98 -0
  298. package/src/core/modules/ai/models/core-ai-prompt.model.ts +120 -0
  299. package/src/core/modules/ai/models/core-ai-response.model.ts +119 -0
  300. package/src/core/modules/ai/models/core-ai-slot.model.ts +146 -0
  301. package/src/core/modules/ai/models/core-ai-tool-grant.model.ts +87 -0
  302. package/src/core/modules/ai/models/core-ai-tool-policy.model.ts +115 -0
  303. package/src/core/modules/ai/models/core-ai-usage-info.model.ts +107 -0
  304. package/src/core/modules/ai/models/core-ai-usage.model.ts +29 -0
  305. package/src/core/modules/ai/providers/claude-cli.provider.ts +240 -0
  306. package/src/core/modules/ai/providers/llm-provider.factory.ts +63 -0
  307. package/src/core/modules/ai/providers/openai-compatible.provider.ts +329 -0
  308. package/src/core/modules/ai/services/ai-crypto.service.ts +120 -0
  309. package/src/core/modules/ai/services/core-ai-budget.service.ts +297 -0
  310. package/src/core/modules/ai/services/core-ai-connection-preference.service.ts +74 -0
  311. package/src/core/modules/ai/services/core-ai-connection-resolver.service.ts +308 -0
  312. package/src/core/modules/ai/services/core-ai-connection.service.ts +387 -0
  313. package/src/core/modules/ai/services/core-ai-conversation.service.ts +105 -0
  314. package/src/core/modules/ai/services/core-ai-interaction.service.ts +56 -0
  315. package/src/core/modules/ai/services/core-ai-mcp-client.service.ts +169 -0
  316. package/src/core/modules/ai/services/core-ai-mcp-oauth.service.ts +445 -0
  317. package/src/core/modules/ai/services/core-ai-mcp.service.ts +94 -0
  318. package/src/core/modules/ai/services/core-ai-mode.service.ts +43 -0
  319. package/src/core/modules/ai/services/core-ai-placeholder.registry.ts +123 -0
  320. package/src/core/modules/ai/services/core-ai-prompt-builder.service.ts +228 -0
  321. package/src/core/modules/ai/services/core-ai-prompt-hint.service.ts +133 -0
  322. package/src/core/modules/ai/services/core-ai-prompt.service.ts +164 -0
  323. package/src/core/modules/ai/services/core-ai-slot.service.ts +526 -0
  324. package/src/core/modules/ai/services/core-ai-tool-grant.service.ts +107 -0
  325. package/src/core/modules/ai/services/core-ai-tool-policy.service.ts +124 -0
  326. package/src/core/modules/ai/services/core-ai.service.ts +1396 -0
  327. package/src/core/modules/ai/tools/ai-tool.base.ts +46 -0
  328. package/src/core/modules/ai/tools/ai-tool.registry.ts +124 -0
  329. package/src/core/modules/ai/tools/ask-user-question.tool.ts +76 -0
  330. package/src/core/modules/ai/tools/search-tools.tool.ts +70 -0
  331. package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +15 -2
  332. package/src/core/modules/error-code/error-codes.ts +86 -0
  333. package/src/core/modules/tenant/core-tenant.service.ts +3 -3
  334. package/src/core.module.ts +12 -0
  335. package/src/index.ts +6 -0
  336. package/src/server/modules/ai/ai-tools.module.ts +33 -0
  337. package/src/server/modules/ai/tools/delete-user.tool.ts +44 -0
  338. package/src/server/modules/ai/tools/find-users.tool.ts +49 -0
  339. package/src/server/modules/ai/tools/get-user.tool.ts +43 -0
  340. package/src/server/modules/ai/tools/update-user-job-title.tool.ts +62 -0
  341. package/src/server/server.module.ts +5 -0
@@ -0,0 +1,94 @@
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+
3
+ import { AiToolContext } from '../interfaces/ai-tool.interface';
4
+ import { AiToolRegistry, AiToolUser } from '../tools/ai-tool.registry';
5
+
6
+ /**
7
+ * MCP tool descriptor (name + description + JSON-schema input).
8
+ */
9
+ export interface McpToolDescriptor {
10
+ description: string;
11
+ inputSchema: Record<string, any>;
12
+ name: string;
13
+ }
14
+
15
+ /**
16
+ * MCP tool-call result in the SDK content shape.
17
+ */
18
+ export interface McpCallResult {
19
+ content: { text: string; type: 'text' }[];
20
+ isError?: boolean;
21
+ }
22
+
23
+ /**
24
+ * Exposes the {@link AiToolRegistry} as a Model Context Protocol (MCP) server so
25
+ * external MCP clients (e.g. desktop MCP clients) can use the same backend tools with
26
+ * the same role gating as the internal orchestrator.
27
+ *
28
+ * The MCP server is created per session and bound to the authenticating user, so
29
+ * `tools/list` and `tools/call` are filtered to that user's permitted tools and
30
+ * executed with their permissions (via each tool's `execute()` → CrudService).
31
+ *
32
+ * The `@modelcontextprotocol/sdk` is imported lazily in {@link createServer} so it
33
+ * is only loaded when MCP is actually enabled — keeping the core lean.
34
+ */
35
+ @Injectable()
36
+ export class CoreAiMcpService {
37
+ protected readonly logger = new Logger(CoreAiMcpService.name);
38
+
39
+ constructor(protected readonly toolRegistry: AiToolRegistry) {}
40
+
41
+ /**
42
+ * List the MCP tool descriptors the user is allowed to use.
43
+ */
44
+ mcpListTools(user: AiToolUser | null | undefined): McpToolDescriptor[] {
45
+ return this.toolRegistry
46
+ .forUser(user)
47
+ .map((tool) => ({ description: tool.description, inputSchema: tool.parameters, name: tool.name }));
48
+ }
49
+
50
+ /**
51
+ * Execute an MCP tool call with the user's permissions. Returns an MCP content
52
+ * result; unknown/forbidden tools and execution errors yield `isError: true`.
53
+ */
54
+ async mcpCallTool(
55
+ user: AiToolUser | null | undefined,
56
+ name: string,
57
+ args: Record<string, any>,
58
+ ): Promise<McpCallResult> {
59
+ const tool = this.toolRegistry.forUser(user).find((t) => t.name === name);
60
+ if (!tool) {
61
+ return { content: [{ text: `Unknown or not permitted tool: ${name}`, type: 'text' }], isError: true };
62
+ }
63
+ const context: AiToolContext = {
64
+ currentUser: (user ?? undefined) as AiToolContext['currentUser'],
65
+ serviceOptions: { currentUser: (user ?? undefined) as AiToolContext['currentUser'] },
66
+ };
67
+ try {
68
+ const result = await tool.execute(args ?? {}, context);
69
+ return { content: [{ text: JSON.stringify(result), type: 'text' }] };
70
+ } catch (err) {
71
+ this.logger.warn(`MCP tool "${name}" failed: ${(err as Error).message}`);
72
+ return { content: [{ text: (err as Error).message, type: 'text' }], isError: true };
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Create a low-level MCP server bound to a user. Uses the JSON-schema tool
78
+ * definitions directly (no zod conversion needed). The SDK is imported lazily.
79
+ */
80
+ async createServer(user: AiToolUser): Promise<any> {
81
+ const { Server } = await import('@modelcontextprotocol/sdk/server/index.js');
82
+ const { CallToolRequestSchema, ListToolsRequestSchema } = await import('@modelcontextprotocol/sdk/types.js');
83
+
84
+ // Loosely typed to decouple from the SDK's strict request/result types.
85
+ const server: any = new Server({ name: 'lt-nest-server-ai', version: '1.0.0' }, { capabilities: { tools: {} } });
86
+
87
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: this.mcpListTools(user) }));
88
+ server.setRequestHandler(CallToolRequestSchema, async (request: any) =>
89
+ this.mcpCallTool(user, request.params.name, request.params.arguments ?? {}),
90
+ );
91
+
92
+ return server;
93
+ }
94
+ }
@@ -0,0 +1,43 @@
1
+ import { Inject, Injectable, Logger } 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 { AiModeDocument, CoreAiMode } from '../models/core-ai-mode.model';
8
+
9
+ export const AI_MODE_MODEL = 'AiMode';
10
+ export const AI_MODE_CLASS = 'AI_MODE_CLASS';
11
+
12
+ /**
13
+ * Named-mode store. See {@link CoreAiMode}. Override via
14
+ * `CoreModule.forRoot(env, { ai: { modeService } })`.
15
+ */
16
+ @Injectable()
17
+ export class CoreAiModeService extends CrudService<CoreAiMode, CoreAiMode, CoreAiMode> {
18
+ protected readonly logger = new Logger(CoreAiModeService.name);
19
+
20
+ constructor(
21
+ @InjectModel(AI_MODE_MODEL) protected override readonly mainDbModel: Model<AiModeDocument>,
22
+ @Inject(AI_MODE_CLASS) protected override readonly mainModelConstructor: CoreModelConstructor<CoreAiMode>,
23
+ ) {
24
+ super();
25
+ }
26
+
27
+ /** Lookup a mode by `name`. Returns `null` if missing or disabled. */
28
+ async getByName(name: string): Promise<CoreAiMode | null> {
29
+ if (!name) {
30
+ return null;
31
+ }
32
+ try {
33
+ const row = await this.mainDbModel
34
+ .findOne({ enabled: { $ne: false }, name })
35
+ .lean<CoreAiMode>()
36
+ .exec();
37
+ return row || null;
38
+ } catch (err) {
39
+ this.logger.warn(`Failed to load mode "${name}": ${(err as Error).message}`);
40
+ return null;
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,123 @@
1
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
2
+
3
+ import { ConfigService } from '../../../common/services/config.service';
4
+ import { AiPlaceholderContext, AiPlaceholderInfo, IAiPlaceholder } from '../interfaces/ai-placeholder.interface';
5
+ import { CoreAiPromptHintService } from './core-ai-prompt-hint.service';
6
+
7
+ /**
8
+ * Runtime registry of `{{placeholder}}` resolvers used by slots and user
9
+ * prompts. The framework registers a small set of system placeholders on boot
10
+ * (`roles`, `tools`, `toolCatalog`, `documentation`, `learnedHints`, `userId`);
11
+ * projects can add their own via {@link register} from any provider.
12
+ *
13
+ * Why dynamic and not hard-coded in the frontend: the placeholder list is
14
+ * served via `GET /ai/placeholders`, so admins see EVERY currently-supported
15
+ * placeholder (incl. project-specific ones) — without requiring a frontend
16
+ * change when the backend gets new ones. Resolvers stay in TypeScript (no
17
+ * eval, no DB-stored function bodies), which keeps the surface secure and
18
+ * straightforward.
19
+ *
20
+ * Override / extend via `CoreModule.forRoot(env, { ai: { placeholderRegistry } })`
21
+ * or by injecting and calling `register()` from a project provider.
22
+ */
23
+ @Injectable()
24
+ export class CoreAiPlaceholderRegistry implements OnModuleInit {
25
+ protected readonly logger = new Logger(CoreAiPlaceholderRegistry.name);
26
+ protected readonly placeholders = new Map<string, IAiPlaceholder>();
27
+
28
+ constructor(protected readonly hintService?: CoreAiPromptHintService) {}
29
+
30
+ onModuleInit(): void {
31
+ this.registerDefaults();
32
+ }
33
+
34
+ /**
35
+ * Register a placeholder. Repeated calls with the same `name` overwrite the
36
+ * previous resolver — projects can replace system placeholders if they need
37
+ * different semantics.
38
+ */
39
+ register(placeholder: IAiPlaceholder): void {
40
+ if (!placeholder?.name) {
41
+ this.logger.warn('Refusing to register a placeholder with no name.');
42
+ return;
43
+ }
44
+ this.placeholders.set(placeholder.name, placeholder);
45
+ }
46
+
47
+ /** Remove a placeholder by name. Returns true if it was present. */
48
+ unregister(name: string): boolean {
49
+ return this.placeholders.delete(name);
50
+ }
51
+
52
+ /** Public metadata of every registered placeholder, sorted by name. */
53
+ list(): AiPlaceholderInfo[] {
54
+ return [...this.placeholders.values()]
55
+ .map((p) => ({ description: p.description, example: p.example, name: p.name }))
56
+ .sort((a, b) => a.name.localeCompare(b.name));
57
+ }
58
+
59
+ /**
60
+ * Resolve every registered placeholder for `ctx` and return a flat record
61
+ * `{ name: value }` suitable for `{{token}}` substitution.
62
+ */
63
+ async resolveAll(ctx: AiPlaceholderContext): Promise<Record<string, string>> {
64
+ const out: Record<string, string> = {};
65
+ for (const p of this.placeholders.values()) {
66
+ try {
67
+ const value = await p.resolve(ctx);
68
+ out[p.name] = value === null || value === undefined ? '' : String(value);
69
+ } catch (err) {
70
+ this.logger.warn(`Placeholder ${p.name} failed to resolve: ${(err as Error).message}`);
71
+ out[p.name] = '';
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+
77
+ /**
78
+ * Built-in system placeholders. Override this method in a subclass to swap
79
+ * defaults; project-specific additions should use {@link register} from a
80
+ * provider's `onModuleInit` for clearer separation.
81
+ */
82
+ protected registerDefaults(): void {
83
+ this.register({
84
+ description: 'Comma-separated list of the current user roles (e.g. "admin, editor"). "none" when empty.',
85
+ example: 'admin, editor',
86
+ name: 'roles',
87
+ resolve: (ctx) => (ctx.user?.roles?.length ? ctx.user.roles.join(', ') : 'none'),
88
+ });
89
+ this.register({
90
+ description: 'Comma-separated list of the tools the current user may use this turn.',
91
+ example: 'get_user, find_users',
92
+ name: 'tools',
93
+ resolve: (ctx) => (ctx.tools?.length ? ctx.tools.map((t) => t.name).join(', ') : 'none'),
94
+ });
95
+ this.register({
96
+ description: 'Full tool catalog with names, descriptions and parameter schemas (LLM-readable).',
97
+ name: 'toolCatalog',
98
+ resolve: (ctx) => ctx.toolCatalog ?? '',
99
+ });
100
+ this.register({
101
+ description:
102
+ 'System documentation injected into the prompt (from the `ai.documentation` config or a project override).',
103
+ name: 'documentation',
104
+ resolve: () => ConfigService.get<string>('ai.documentation') || '',
105
+ });
106
+ this.register({
107
+ description:
108
+ 'Admin-approved hints from the governed learning loop, scoped to the tools in use this turn. Empty when nothing applies.',
109
+ name: 'learnedHints',
110
+ resolve: async (ctx) => {
111
+ if (!this.hintService) return '';
112
+ const learned = await this.hintService.approvedHints((ctx.tools || []).map((t) => t.name));
113
+ return learned.length ? learned.map((h) => `- ${h}`).join('\n') : '';
114
+ },
115
+ });
116
+ this.register({
117
+ description: 'Current user id (empty when no user is in context).',
118
+ example: '6a16c51530d808eb40a93d66',
119
+ name: 'userId',
120
+ resolve: (ctx) => ctx.user?.id || '',
121
+ });
122
+ }
123
+ }
@@ -0,0 +1,228 @@
1
+ import { Injectable, Optional } from '@nestjs/common';
2
+
3
+ import { ConfigService } from '../../../common/services/config.service';
4
+ import { IAiTool } from '../interfaces/ai-tool.interface';
5
+ import { LlmToolSchema } from '../interfaces/llm-provider.interface';
6
+ import { CoreAiPlaceholderRegistry } from './core-ai-placeholder.registry';
7
+ import { CoreAiPromptHintService } from './core-ai-prompt-hint.service';
8
+ import { CoreAiSlotService, getSystemDefaultSlots, ResolvedPromptFragment } from './core-ai-slot.service';
9
+
10
+ /** Options for a prompt build (locale for template/hint selection). */
11
+ export interface BuildPromptOptions {
12
+ language?: string;
13
+ /** Named agent mode (e.g. 'support', 'audit') — exposes as a `mode:<name>` scope to filter fragments. */
14
+ mode?: string;
15
+ }
16
+
17
+ /**
18
+ * Builds the system prompt and tool catalog for a prompt run.
19
+ *
20
+ * The prompt is assembled from keyed fragments: built-in {@link defaultFragments}
21
+ * overlaid by admin-editable rows from {@link CoreAiSlotService}, plus
22
+ * governed learned hints from {@link CoreAiPromptHintService}. Nothing is hard-coded
23
+ * and unreachable — admins and the learning loop can adjust every fragment. Override
24
+ * this class via `CoreModule.forRoot(env, { ai: { promptBuilder } })` for custom
25
+ * composition (domain context, RAG, tone).
26
+ *
27
+ * Placeholders rendered at build time: `{{documentation}}`, `{{roles}}`, `{{tools}}`,
28
+ * `{{toolCatalog}}`, `{{learnedHints}}`, `{{userId}}`.
29
+ */
30
+ @Injectable()
31
+ export class CoreAiPromptBuilderService {
32
+ /** Keys that belong only to the auto (step-by-step) execution mode. */
33
+ protected readonly autoOnlyKeys = ['output_contract', 'plan_protocol', 'tool_catalog', 'tool_protocol_emulated'];
34
+
35
+ /** Default base system prompt (used when `ai.systemPrompt` is not configured). */
36
+ protected readonly defaultSystemPrompt =
37
+ 'You are a helpful assistant integrated into a business application. ' +
38
+ 'Answer concisely and only use information you can obtain through the provided tools. ' +
39
+ 'Never invent data. If a request cannot be fulfilled with the available tools, say so.';
40
+
41
+ /** Keys that describe tool calling (skipped when the user has no tools). */
42
+ protected readonly toolKeys = ['output_contract', 'plan_protocol', 'tool_catalog', 'tool_protocol_emulated'];
43
+
44
+ constructor(
45
+ @Optional() protected readonly templateService?: CoreAiSlotService,
46
+ @Optional() protected readonly hintService?: CoreAiPromptHintService,
47
+ @Optional() protected readonly placeholderRegistry?: CoreAiPlaceholderRegistry,
48
+ ) {}
49
+
50
+ /**
51
+ * Build the full system prompt for an auto-mode run, assembled from the effective
52
+ * prompt fragments (defaults + admin overrides) with placeholders rendered and
53
+ * governed learned hints injected.
54
+ */
55
+ async buildSystemPrompt(
56
+ tools: IAiTool[],
57
+ supportsNativeTools: boolean,
58
+ user?: { id?: string; roles?: string[] },
59
+ options?: BuildPromptOptions,
60
+ ): Promise<string> {
61
+ const capability = supportsNativeTools ? 'native' : 'emulated';
62
+ const fragments = await this.resolveFragments(
63
+ capability,
64
+ options?.language,
65
+ this.computeScopes(tools, user, options),
66
+ );
67
+ const context = await this.renderContext(tools, user);
68
+ return this.assemble(
69
+ fragments.filter((f) => f.key !== 'plan_protocol'),
70
+ context,
71
+ tools.length,
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Build the system prompt for plan mode: the model must return a COMPLETE ordered
77
+ * plan of tool calls as JSON instead of executing step by step. Shares the base /
78
+ * documentation / permissions / anti-hallucination / error / learned-hints fragments
79
+ * and uses the `plan_protocol` fragment instead of the auto-mode tool protocol.
80
+ */
81
+ async buildPlanSystemPrompt(
82
+ tools: IAiTool[],
83
+ user?: { id?: string; roles?: string[] },
84
+ options?: BuildPromptOptions,
85
+ ): Promise<string> {
86
+ const fragments = await this.resolveFragments(
87
+ 'emulated',
88
+ options?.language,
89
+ this.computeScopes(tools, user, options),
90
+ );
91
+ const context = await this.renderContext(tools, user);
92
+ const planFragments = fragments.filter((f) => f.key === 'plan_protocol' || !this.autoOnlyKeys.includes(f.key));
93
+ return this.assemble(planFragments, context, tools.length);
94
+ }
95
+
96
+ /**
97
+ * Map tools to native tool schemas (used only when the provider supports it).
98
+ */
99
+ buildToolSchemas(tools: IAiTool[]): LlmToolSchema[] {
100
+ return tools.map((t) => ({ description: t.description, name: t.name, parameters: t.parameters }));
101
+ }
102
+
103
+ /**
104
+ * Built-in default prompt fragments. `content` may contain `{{placeholders}}`.
105
+ * Override to ship different defaults (admins can also override per-key in the DB
106
+ * via {@link CoreAiSlotService}).
107
+ */
108
+ protected defaultFragments(): ResolvedPromptFragment[] {
109
+ return getSystemDefaultSlots(ConfigService.get<string>('ai.systemPrompt') || this.defaultSystemPrompt);
110
+ }
111
+
112
+ /**
113
+ * Optional system documentation injected into the prompt. Reads `ai.documentation`
114
+ * by default; override to supply RAG results / API docs.
115
+ */
116
+ protected getDocumentation(): string | undefined {
117
+ return ConfigService.get<string>('ai.documentation') || undefined;
118
+ }
119
+
120
+ /**
121
+ * Resolve the effective fragments: built-in defaults overlaid by admin DB rows
122
+ * (when the template service is wired). Falls back to defaults only otherwise.
123
+ */
124
+ protected async resolveFragments(
125
+ capability: string,
126
+ locale?: string,
127
+ scopes?: string[],
128
+ ): Promise<ResolvedPromptFragment[]> {
129
+ const defaults = this.defaultFragments();
130
+ const activeScopes = scopes ?? [];
131
+ if (!this.templateService) {
132
+ return defaults
133
+ .filter((f) => !f.capability || f.capability === 'all' || f.capability === capability)
134
+ .filter((f) => !f.scope || activeScopes.includes(f.scope))
135
+ .sort((a, b) => a.order - b.order);
136
+ }
137
+ return this.templateService.resolveFragments(defaults, { capability, locale, scopes: activeScopes });
138
+ }
139
+
140
+ /**
141
+ * Compute the active scope tags for a run: `tool:<name>` for each tool in scope,
142
+ * `role:<name>` for each user role, and `mode:<name>` if a named mode is active.
143
+ * Used to filter scoped prompt fragments via {@link CoreAiSlotService.resolveFragments}.
144
+ */
145
+ protected computeScopes(
146
+ tools: IAiTool[],
147
+ user?: { id?: string; roles?: string[] },
148
+ options?: BuildPromptOptions & { mode?: string },
149
+ ): string[] {
150
+ const scopes: string[] = [];
151
+ for (const tool of tools || []) {
152
+ if (tool?.name) {
153
+ scopes.push(`tool:${tool.name}`);
154
+ }
155
+ }
156
+ for (const role of user?.roles || []) {
157
+ scopes.push(`role:${role}`);
158
+ }
159
+ if (options?.mode) {
160
+ scopes.push(`mode:${options.mode}`);
161
+ }
162
+ return scopes;
163
+ }
164
+
165
+ /**
166
+ * Compute placeholder values for the run. Resolves via {@link CoreAiPlaceholderRegistry}
167
+ * when wired, so project-registered placeholders are honored automatically; falls
168
+ * back to a hard-coded record when no registry is available.
169
+ */
170
+ protected async renderContext(
171
+ tools: IAiTool[],
172
+ user?: { id?: string; roles?: string[] },
173
+ ): Promise<Record<string, string>> {
174
+ // Deferred tool-schemas (#13): with many tools the full JSON-Schema catalog can
175
+ // dominate the system prompt. When `ai.deferToolSchemas` is on, the catalog
176
+ // emits ONLY the tool names + short descriptions; the LLM uses the built-in
177
+ // `search_tools` meta-tool to fetch the parameter schema for a tool on demand.
178
+ const defer = ConfigService.get<boolean>('ai.deferToolSchemas') === true;
179
+ const toolCatalog = defer
180
+ ? (tools.map((t) => `- ${t.name}: ${t.description}`).join('\n') || '(none)') +
181
+ '\n\n[Schemas deferred. Call `search_tools` with the tool name to fetch its parameter schema BEFORE you call it.]'
182
+ : tools
183
+ .map((t) => `- ${t.name}: ${t.description}\n parameters (JSON schema): ${JSON.stringify(t.parameters)}`)
184
+ .join('\n') || '(none)';
185
+
186
+ if (this.placeholderRegistry) {
187
+ return this.placeholderRegistry.resolveAll({ toolCatalog, tools, user });
188
+ }
189
+
190
+ // Fallback when the registry isn't wired (legacy / unit-test paths).
191
+ const learned = this.hintService ? await this.hintService.approvedHints(tools.map((t) => t.name)) : [];
192
+ return {
193
+ documentation: this.getDocumentation() || '',
194
+ learnedHints: learned.length ? learned.map((h) => `- ${h}`).join('\n') : '',
195
+ roles: user?.roles?.length ? user.roles.join(', ') : 'none',
196
+ toolCatalog,
197
+ tools: tools.map((t) => t.name).join(', ') || 'none',
198
+ userId: user?.id || '',
199
+ };
200
+ }
201
+
202
+ /** Render placeholders, drop empty/irrelevant fragments, and join. */
203
+ protected assemble(fragments: ResolvedPromptFragment[], context: Record<string, string>, toolCount: number): string {
204
+ const parts: string[] = [];
205
+ for (const fragment of fragments) {
206
+ // Skip fragments whose driving placeholder is empty or that need tools.
207
+ if (fragment.key === 'documentation' && !context.documentation) {
208
+ continue;
209
+ }
210
+ if (fragment.key === 'learned_hints' && !context.learnedHints) {
211
+ continue;
212
+ }
213
+ if (this.toolKeys.includes(fragment.key) && toolCount === 0) {
214
+ continue;
215
+ }
216
+ const rendered = this.render(fragment.content, context).trim();
217
+ if (rendered) {
218
+ parts.push(rendered);
219
+ }
220
+ }
221
+ return parts.join('\n\n');
222
+ }
223
+
224
+ /** Replace `{{placeholder}}` tokens with their values (unknown tokens become ''). */
225
+ protected render(template: string, context: Record<string, string>): string {
226
+ return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (_match, key: string) => context[key] ?? '');
227
+ }
228
+ }
@@ -0,0 +1,133 @@
1
+ import { Inject, Injectable, Logger } from '@nestjs/common';
2
+ import { InjectModel } from '@nestjs/mongoose';
3
+ import { Model } from 'mongoose';
4
+
5
+ import { ConfigService } from '../../../common/services/config.service';
6
+ import { CrudService } from '../../../common/services/crud.service';
7
+ import { CoreModelConstructor } from '../../../common/types/core-model-constructor.type';
8
+ import { CoreAiPromptHintCreateInput } from '../inputs/core-ai-prompt-hint-create.input';
9
+ import { CoreAiPromptHintInput } from '../inputs/core-ai-prompt-hint.input';
10
+ import { AiPromptHintDocument, CoreAiPromptHint } from '../models/core-ai-prompt-hint.model';
11
+
12
+ /** Mongoose injection token for the prompt-hint model. */
13
+ export const AI_PROMPT_HINT_MODEL = 'AiPromptHint';
14
+
15
+ /** DI token for the prompt-hint model constructor. */
16
+ export const AI_PROMPT_HINT_CLASS = 'AI_PROMPT_HINT_CLASS';
17
+
18
+ /** A failure signal recorded by the orchestrator for the learning loop. */
19
+ export interface AiPromptFeedbackSignal {
20
+ /** Generated guidance to add to the prompt to avoid the failure next time. */
21
+ content: string;
22
+ /** Optional scope (e.g. a tool name); empty = global. */
23
+ scope?: string;
24
+ /** Failure-pattern id (e.g. 'tool_error', 'hallucinated_execution', 'parse_failure', 'denied'). */
25
+ trigger: string;
26
+ }
27
+
28
+ /** Resolved `ai.promptLearning` config. */
29
+ interface PromptLearningConfig {
30
+ autoApply: boolean;
31
+ enabled: boolean;
32
+ minOccurrences: number;
33
+ }
34
+
35
+ /**
36
+ * CRUD + governed self-improvement loop for learned prompt hints.
37
+ *
38
+ * The orchestrator reports failures via {@link recordSignal}; recurring patterns are
39
+ * aggregated into {@link CoreAiPromptHint} rows. By default new hints are `suggested`
40
+ * and only affect the prompt once an admin approves them; with
41
+ * `ai.promptLearning.autoApply` they are created `approved`. {@link approvedHints}
42
+ * feeds the active guidance into the prompt builder.
43
+ *
44
+ * Learning can never relax security — hints only ADD textual guidance; permissions and
45
+ * tool gating are always enforced backend-side regardless of the prompt.
46
+ */
47
+ @Injectable()
48
+ export class CoreAiPromptHintService extends CrudService<
49
+ CoreAiPromptHint,
50
+ CoreAiPromptHintCreateInput,
51
+ CoreAiPromptHintInput
52
+ > {
53
+ protected readonly logger = new Logger(CoreAiPromptHintService.name);
54
+
55
+ constructor(
56
+ @InjectModel(AI_PROMPT_HINT_MODEL) protected override readonly mainDbModel: Model<AiPromptHintDocument>,
57
+ @Inject(AI_PROMPT_HINT_CLASS)
58
+ protected override readonly mainModelConstructor: CoreModelConstructor<CoreAiPromptHint>,
59
+ ) {
60
+ super();
61
+ }
62
+
63
+ /**
64
+ * Return the active learned guidance for a run: enabled, `approved` hints whose
65
+ * scope is global or matches one of the run's tool names.
66
+ */
67
+ async approvedHints(scopes: string[] = []): Promise<string[]> {
68
+ if (!this.config().enabled) {
69
+ return [];
70
+ }
71
+ try {
72
+ const rows = await this.mainDbModel
73
+ .find({ enabled: { $ne: false }, scope: { $in: [null, '', ...scopes] }, status: 'approved' })
74
+ .sort({ occurrences: -1 })
75
+ .lean<CoreAiPromptHint[]>()
76
+ .exec();
77
+ return rows.map((r) => r.content).filter((c): c is string => !!c?.trim());
78
+ } catch {
79
+ return [];
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Record a failure signal. Best-effort: aggregates by (trigger, scope), increments
85
+ * the occurrence counter and creates/updates the hint. Never throws — feedback must
86
+ * not break a prompt run.
87
+ */
88
+ async recordSignal(signal: AiPromptFeedbackSignal): Promise<void> {
89
+ const config = this.config();
90
+ if (!config.enabled || !signal?.trigger || !signal?.content) {
91
+ return;
92
+ }
93
+ const scope = signal.scope || '';
94
+ try {
95
+ const existing = await this.mainDbModel
96
+ .findOne({ scope, trigger: signal.trigger })
97
+ .lean<CoreAiPromptHint>()
98
+ .exec();
99
+ if (existing) {
100
+ const occurrences = (existing.occurrences ?? 1) + 1;
101
+ const update: Record<string, unknown> = { occurrences };
102
+ // Auto-approve once the threshold is reached (config) and not already decided.
103
+ if (config.autoApply && existing.status === 'suggested' && occurrences >= config.minOccurrences) {
104
+ update.status = 'approved';
105
+ }
106
+ await this.mainDbModel.updateOne({ _id: (existing as any)._id }, { $set: update }).exec();
107
+ return;
108
+ }
109
+ const status = config.autoApply && config.minOccurrences <= 1 ? 'approved' : 'suggested';
110
+ await this.mainDbModel.create({
111
+ content: signal.content,
112
+ occurrences: 1,
113
+ scope,
114
+ status,
115
+ trigger: signal.trigger,
116
+ });
117
+ } catch (err) {
118
+ this.logger.debug(`recordSignal skipped: ${(err as Error).message}`);
119
+ }
120
+ }
121
+
122
+ /** Resolve the `ai.promptLearning` config with safe defaults. */
123
+ protected config(): PromptLearningConfig {
124
+ const raw = ConfigService.get<{ autoApply?: boolean; enabled?: boolean; minOccurrences?: number }>(
125
+ 'ai.promptLearning',
126
+ );
127
+ return {
128
+ autoApply: raw?.autoApply === true,
129
+ enabled: raw?.enabled !== false,
130
+ minOccurrences: typeof raw?.minOccurrences === 'number' && raw.minOccurrences > 0 ? raw.minOccurrences : 1,
131
+ };
132
+ }
133
+ }