@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,329 @@
1
+ import { BadGatewayException, Logger, ServiceUnavailableException } from '@nestjs/common';
2
+
3
+ import { ConfigService } from '../../../common/services/config.service';
4
+ import { ErrorCode } from '../../error-code';
5
+ import {
6
+ ILlmProvider,
7
+ LlmCapabilities,
8
+ LlmCompletionOptions,
9
+ LlmMessage,
10
+ LlmResponse,
11
+ LlmToolSchema,
12
+ } from '../interfaces/llm-provider.interface';
13
+ import { ResolvedAiConnection } from '../interfaces/resolved-ai-connection.interface';
14
+
15
+ /**
16
+ * Provider for OpenAI-compatible chat-completions endpoints.
17
+ *
18
+ * Works with any backend that speaks the OpenAI chat-completions shape — local
19
+ * runtimes or hosted endpoints alike. Implemented with the native `fetch` API so
20
+ * the framework core stays dependency-free and vendor-neutral.
21
+ *
22
+ * Capabilities are taken from the connection config (the admin knows what the
23
+ * endpoint supports). The orchestrator compensates: when `nativeTools` is false it
24
+ * emulates tool calling via the system prompt; when `jsonResponse` is false it
25
+ * relies on prompt-instructed JSON. Both are off by default (the safe, lowest
26
+ * common denominator) and can be enabled per connection.
27
+ */
28
+ export class OpenAiCompatibleProvider implements ILlmProvider {
29
+ readonly capabilities: LlmCapabilities;
30
+ readonly name = 'openai-compatible';
31
+
32
+ private readonly logger = new Logger(OpenAiCompatibleProvider.name);
33
+ private readonly defaultTimeoutMs: number;
34
+
35
+ constructor(private readonly connection: ResolvedAiConnection) {
36
+ this.capabilities = {
37
+ jsonResponse: connection.supportsJsonResponse ?? false,
38
+ nativeTools: connection.supportsNativeTools ?? false,
39
+ systemPrompt: true,
40
+ };
41
+ // Default to 2 minutes for interactive prompts; overridable per connection.
42
+ this.defaultTimeoutMs = connection.timeoutMs ?? 120_000;
43
+ }
44
+
45
+ /**
46
+ * Send a chat completion request to the OpenAI-compatible `/chat/completions`
47
+ * endpoint and map the response to {@link LlmResponse}. Sends native `tools` and
48
+ * `response_format` only when the connection's capabilities allow it; native tool
49
+ * calls are mapped via {@link mapNativeToolCalls}. Aborts after the connection's
50
+ * timeout and maps HTTP/transport errors to a {@link ServiceUnavailableException}.
51
+ */
52
+ async chat(messages: LlmMessage[], tools: LlmToolSchema[], options?: LlmCompletionOptions): Promise<LlmResponse> {
53
+ const url = `${this.connection.baseUrl.replace(/\/$/, '')}/chat/completions`;
54
+ if (!url.startsWith('http')) {
55
+ this.logger.warn(`AI connection "${this.connection.name}" has no valid baseUrl: ${this.connection.baseUrl}`);
56
+ throw new ServiceUnavailableException(ErrorCode.AI_CONNECTION_INVALID_URL);
57
+ }
58
+ this.assertBaseUrlAllowed(url);
59
+
60
+ const body: Record<string, any> = {
61
+ max_tokens: options?.maxTokens ?? this.connection.defaultMaxTokens ?? 2048,
62
+ messages: messages.map((m) => ({
63
+ // Map the emulated 'tool' role to 'user' for backends that only know
64
+ // system/user/assistant.
65
+ content: this.mapMessageContent(m),
66
+ role: m.role === 'tool' ? 'user' : m.role,
67
+ })),
68
+ model: options?.model ?? this.connection.model,
69
+ stream: false,
70
+ temperature: options?.temperature ?? this.connection.defaultTemperature ?? 0.1,
71
+ };
72
+
73
+ // Attach native parameters only when the backend supports them.
74
+ if (this.capabilities.nativeTools && tools.length) {
75
+ body.tools = tools.map((t) => ({
76
+ function: { description: t.description, name: t.name, parameters: t.parameters },
77
+ type: 'function',
78
+ }));
79
+ }
80
+ if (this.capabilities.jsonResponse) {
81
+ body.response_format = { type: 'json_object' };
82
+ }
83
+
84
+ const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
85
+ let response: Response;
86
+ try {
87
+ response = await fetch(url, {
88
+ body: JSON.stringify(body),
89
+ headers: {
90
+ Authorization: `Bearer ${this.connection.apiKey}`,
91
+ 'Content-Type': 'application/json',
92
+ },
93
+ method: 'POST',
94
+ signal: AbortSignal.timeout(timeoutMs),
95
+ });
96
+ } catch (err) {
97
+ const message = (err as Error)?.name === 'TimeoutError' ? `timeout after ${timeoutMs}ms` : (err as Error).message;
98
+ this.logger.warn(`AI request to "${this.connection.name}" failed: ${message}`);
99
+ throw new ServiceUnavailableException(ErrorCode.AI_PROVIDER_ERROR);
100
+ }
101
+
102
+ if (!response.ok) {
103
+ const text = await response.text().catch(() => '');
104
+ this.logger.warn(`AI completion failed (${response.status}) at ${url}: ${text.slice(0, 300)}`);
105
+ throw new BadGatewayException(ErrorCode.AI_PROVIDER_ERROR);
106
+ }
107
+
108
+ const result = (await response.json()) as {
109
+ choices?: { message?: { content?: string; tool_calls?: any[] } }[];
110
+ usage?: { completion_tokens?: number; prompt_tokens?: number; total_tokens?: number };
111
+ };
112
+
113
+ const choice = result.choices?.[0]?.message;
114
+ const text = choice?.content ?? '';
115
+ const nativeToolCalls = this.capabilities.nativeTools ? this.mapNativeToolCalls(choice?.tool_calls) : undefined;
116
+
117
+ return {
118
+ raw: result,
119
+ text,
120
+ toolCalls: nativeToolCalls,
121
+ usage: {
122
+ completionTokens: result.usage?.completion_tokens,
123
+ promptTokens: result.usage?.prompt_tokens,
124
+ totalTokens: result.usage?.total_tokens,
125
+ },
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Optional SSRF hardening: when `ai.allowedBaseUrlHosts` is configured (non-empty),
131
+ * only allow requests to those hosts (matched by `host` incl. port, or bare
132
+ * `hostname`). Unset → permissive (so local providers like Ollama on localhost work
133
+ * out of the box). `baseUrl` is admin-only, so this guards a compromised/misconfigured
134
+ * admin, not an end-user input.
135
+ */
136
+ protected assertBaseUrlAllowed(url: string): void {
137
+ const allowedHosts = ConfigService.get<string[]>('ai.allowedBaseUrlHosts');
138
+ if (!Array.isArray(allowedHosts) || !allowedHosts.length) {
139
+ return;
140
+ }
141
+ let parsed: URL;
142
+ try {
143
+ parsed = new URL(url);
144
+ } catch {
145
+ throw new ServiceUnavailableException(ErrorCode.AI_CONNECTION_INVALID_URL);
146
+ }
147
+ if (!allowedHosts.includes(parsed.host) && !allowedHosts.includes(parsed.hostname)) {
148
+ this.logger.warn(
149
+ `AI connection "${this.connection.name}" host "${parsed.host}" is not in ai.allowedBaseUrlHosts`,
150
+ );
151
+ throw new ServiceUnavailableException(ErrorCode.AI_CONNECTION_NOT_AVAILABLE);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Probe the backend to auto-detect capabilities for flags the connection left
157
+ * undefined. Explicit flags are authoritative and are NOT probed. Best effort:
158
+ * - JSON: send `response_format: json_object`; 2xx → true, 4xx → false.
159
+ * - Native tools: send a trivial tool with `tool_choice: 'required'`; 2xx WITH a
160
+ * `tool_calls` result → true, otherwise → false (a backend that silently ignores
161
+ * tools returns no tool_calls and is treated as unsupported).
162
+ *
163
+ * Throws on a transport error so callers can treat the connection as undetected
164
+ * (and retry later) rather than persisting a wrong value.
165
+ */
166
+ async detectCapabilities(): Promise<{ jsonResponse?: boolean; nativeTools?: boolean }> {
167
+ const result: { jsonResponse?: boolean; nativeTools?: boolean } = {};
168
+ if (this.connection.supportsJsonResponse === undefined) {
169
+ const res = await this.probe({
170
+ max_tokens: 8,
171
+ messages: [{ content: 'Reply with the JSON object {"ok":true}.', role: 'user' }],
172
+ response_format: { type: 'json_object' },
173
+ });
174
+ result.jsonResponse = res.ok;
175
+ }
176
+ if (this.connection.supportsNativeTools === undefined) {
177
+ const res = await this.probe({
178
+ max_tokens: 8,
179
+ messages: [{ content: 'Call the ping tool.', role: 'user' }],
180
+ tool_choice: 'required',
181
+ tools: [
182
+ {
183
+ function: {
184
+ description: 'A no-op capability probe.',
185
+ name: 'ping',
186
+ parameters: { properties: {}, type: 'object' },
187
+ },
188
+ type: 'function',
189
+ },
190
+ ],
191
+ });
192
+ result.nativeTools = res.ok && !!res.json?.choices?.[0]?.message?.tool_calls?.length;
193
+ }
194
+ return result;
195
+ }
196
+
197
+ /**
198
+ * Send a minimal probe request and report whether the endpoint accepted it.
199
+ * `ok` is true only for a 2xx response; a 4xx (feature unsupported) yields false.
200
+ */
201
+ protected async probe(extra: Record<string, any>): Promise<{ json?: any; ok: boolean }> {
202
+ const url = `${this.connection.baseUrl.replace(/\/$/, '')}/chat/completions`;
203
+ if (!url.startsWith('http')) {
204
+ throw new ServiceUnavailableException(ErrorCode.AI_CONNECTION_INVALID_URL);
205
+ }
206
+ this.assertBaseUrlAllowed(url);
207
+ const response = await fetch(url, {
208
+ body: JSON.stringify({ model: this.connection.model, ...extra }),
209
+ headers: { Authorization: `Bearer ${this.connection.apiKey}`, 'Content-Type': 'application/json' },
210
+ method: 'POST',
211
+ signal: AbortSignal.timeout(this.connection.timeoutMs ?? 30_000),
212
+ });
213
+ if (!response.ok) {
214
+ return { ok: false };
215
+ }
216
+ const json = await response.json().catch(() => undefined);
217
+ return { json, ok: true };
218
+ }
219
+
220
+ /**
221
+ * Determine the model's total context window automatically: first an Ollama
222
+ * `/api/show` probe (local runtimes expose `<arch>.context_length`), then a
223
+ * known-model heuristic table. Best effort — returns undefined when unknown.
224
+ */
225
+ async detectContextWindow(): Promise<number | undefined> {
226
+ const fromBackend = await this.probeContextWindow().catch(() => undefined);
227
+ return fromBackend ?? this.knownContextWindow(this.connection.model);
228
+ }
229
+
230
+ /**
231
+ * Heuristic context window (tokens) for well-known model families, matched by
232
+ * case-insensitive substrings of the model id. Override/extend for custom models.
233
+ */
234
+ protected knownContextWindow(model: string | undefined): number | undefined {
235
+ const m = (model || '').toLowerCase();
236
+ const table: [number, string[]][] = [
237
+ [1_000_000, ['gemini-1.5', 'gemini-2']],
238
+ [200_000, ['claude']],
239
+ [
240
+ 128_000,
241
+ ['gpt-4o', 'gpt-4.1', 'gpt-4-turbo', 'o1', 'o3', 'gpt-oss', 'mistral-large', 'mistral-small3', 'command-r'],
242
+ ],
243
+ [131_072, ['qwen2.5', 'qwen3', 'llama-3.1', 'llama3.1', 'llama-3.3', 'llama3.3']],
244
+ [65_536, ['mixtral']],
245
+ [32_768, ['qwen2', 'mistral', 'gemma2', 'gemma-2']],
246
+ [16_385, ['gpt-3.5']],
247
+ [8_192, ['llama3', 'llama-3', 'gemma']],
248
+ ];
249
+ for (const [window, keys] of table) {
250
+ if (keys.some((k) => m.includes(k))) {
251
+ return window;
252
+ }
253
+ }
254
+ return undefined;
255
+ }
256
+
257
+ /**
258
+ * Probe a local Ollama backend for the model's context length via `/api/show`.
259
+ * Returns undefined for non-Ollama endpoints or on any error.
260
+ */
261
+ protected async probeContextWindow(): Promise<number | undefined> {
262
+ const base = this.connection.baseUrl.replace(/\/v1\/?$/, '').replace(/\/$/, '');
263
+ if (!base.startsWith('http')) {
264
+ return undefined;
265
+ }
266
+ const response = await fetch(`${base}/api/show`, {
267
+ body: JSON.stringify({ name: this.connection.model }),
268
+ headers: { 'Content-Type': 'application/json' },
269
+ method: 'POST',
270
+ signal: AbortSignal.timeout(this.connection.timeoutMs ?? 15_000),
271
+ }).catch(() => undefined);
272
+ if (!response?.ok) {
273
+ return undefined;
274
+ }
275
+ const json = (await response.json().catch(() => undefined)) as { model_info?: Record<string, unknown> } | undefined;
276
+ const info = json?.model_info;
277
+ if (!info) {
278
+ return undefined;
279
+ }
280
+ const key = Object.keys(info).find((k) => k.endsWith('.context_length'));
281
+ const value = key ? info[key] : undefined;
282
+ return typeof value === 'number' && value > 0 ? value : undefined;
283
+ }
284
+
285
+ /**
286
+ * Map native `tool_calls` to the normalized {@link LlmResponse.toolCalls}.
287
+ */
288
+ /**
289
+ * Map a (possibly multi-modal) LlmMessage to the OpenAI content shape:
290
+ * - Plain text → `content: string` (unchanged).
291
+ * - Text + attachments → `content: [{type:'text',text}, {type:'image_url',image_url:{...}}, ...]`
292
+ * Files/PDFs with `dataUrl` are passed as `image_url` too — backends that don't
293
+ * support vision will warn or ignore.
294
+ */
295
+ protected mapMessageContent(m: { attachments?: any[]; content: string }): any {
296
+ if (!Array.isArray(m.attachments) || !m.attachments.length) {
297
+ return m.content;
298
+ }
299
+ const parts: any[] = [];
300
+ if (m.content) {
301
+ parts.push({ text: m.content, type: 'text' });
302
+ }
303
+ for (const a of m.attachments) {
304
+ const url = a?.url || a?.dataUrl;
305
+ if (!url) {
306
+ continue;
307
+ }
308
+ parts.push({ image_url: { url }, type: 'image_url' });
309
+ }
310
+ return parts.length ? parts : m.content;
311
+ }
312
+
313
+ protected mapNativeToolCalls(toolCalls: any[] | undefined) {
314
+ if (!toolCalls?.length) {
315
+ return undefined;
316
+ }
317
+ return toolCalls
318
+ .filter((c) => c?.function?.name)
319
+ .map((c) => {
320
+ let parsedArgs: Record<string, any> = {};
321
+ try {
322
+ parsedArgs = c.function.arguments ? JSON.parse(c.function.arguments) : {};
323
+ } catch {
324
+ parsedArgs = {};
325
+ }
326
+ return { arguments: parsedArgs, id: c.id, name: c.function.name };
327
+ });
328
+ }
329
+ }
@@ -0,0 +1,120 @@
1
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
2
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
3
+
4
+ import { isProductionLikeEnv } from '../../../common/helpers/cookies.helper';
5
+ import { ConfigService } from '../../../common/services/config.service';
6
+
7
+ /**
8
+ * AES-256-GCM wrapper for AI connection secrets (API keys).
9
+ *
10
+ * Stored shape: `"<iv_b64>.<authTag_b64>.<ciphertext_b64>"` — a dot-separated
11
+ * triplet that carries everything {@link decrypt} needs. The 256-bit key is
12
+ * derived via SHA-256 from a configurable pass-phrase, so any length works.
13
+ *
14
+ * Secret resolution order:
15
+ * 1. `ai.encryptionSecret` (config / `NSC__AI__ENCRYPTION_SECRET`)
16
+ * 2. `SECRETS_ENCRYPTION_KEY` (env)
17
+ * 3. an insecure development default (logs a warning on first use)
18
+ *
19
+ * Rotating the secret invalidates every value currently in the database;
20
+ * operators must decrypt-then-re-encrypt on rotation (out of scope here).
21
+ */
22
+ @Injectable()
23
+ export class AiCryptoService implements OnModuleInit {
24
+ private readonly logger = new Logger(AiCryptoService.name);
25
+ private warned = false;
26
+
27
+ /**
28
+ * Fail loud at boot in production/staging when AI is active but no real encryption
29
+ * secret is configured — otherwise stored API keys would be "encrypted" with a
30
+ * public, insecure development default. Mirrors the email/cookies production guards.
31
+ */
32
+ onModuleInit(): void {
33
+ this.assertProductionSafe();
34
+ }
35
+
36
+ /**
37
+ * Throw when running in a production-like environment without a configured secret.
38
+ * No-op in local/dev/ci/e2e (there the insecure default + a warning are acceptable).
39
+ */
40
+ assertProductionSafe(): void {
41
+ if (isProductionLikeEnv(ConfigService.get<string>('env')) && !this.resolveSecret()) {
42
+ throw new Error(
43
+ 'AI encryption secret is required in production/staging but none is set ' +
44
+ '(ai.encryptionSecret / NSC__AI__ENCRYPTION_SECRET / SECRETS_ENCRYPTION_KEY). ' +
45
+ 'Without it, stored AI API keys are encrypted with a public, insecure development ' +
46
+ 'default. Set a random 32+ char value.',
47
+ );
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Decrypt a stored cipher text. Values without the expected triplet shape are
53
+ * returned unchanged (tolerates legacy/plain values without crashing).
54
+ */
55
+ decrypt(cipherText: string): string {
56
+ if (!cipherText) {
57
+ return '';
58
+ }
59
+ const parts = cipherText.split('.');
60
+ if (parts.length !== 3) {
61
+ return cipherText;
62
+ }
63
+ const [ivB64, tagB64, dataB64] = parts;
64
+ try {
65
+ const iv = Buffer.from(ivB64, 'base64');
66
+ const tag = Buffer.from(tagB64, 'base64');
67
+ const data = Buffer.from(dataB64, 'base64');
68
+ const decipher = createDecipheriv('aes-256-gcm', this.getKey(), iv);
69
+ decipher.setAuthTag(tag);
70
+ const plain = Buffer.concat([decipher.update(data), decipher.final()]);
71
+ return plain.toString('utf8');
72
+ } catch (err) {
73
+ this.logger.error(`Decrypt failed: ${(err as Error).message}`);
74
+ throw new Error('Secret decryption failed — key mismatch or corrupted value', { cause: err });
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Encrypt a plaintext secret. Empty/undefined input is preserved as an empty
80
+ * string so "set and cleared" stays distinguishable from "never set".
81
+ */
82
+ encrypt(plainText: string): string {
83
+ if (plainText === '' || plainText === undefined || plainText === null) {
84
+ return '';
85
+ }
86
+ const iv = randomBytes(12); // GCM standard: 96-bit IV
87
+ const cipher = createCipheriv('aes-256-gcm', this.getKey(), iv);
88
+ const encrypted = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]);
89
+ const tag = cipher.getAuthTag();
90
+ return [iv.toString('base64'), tag.toString('base64'), encrypted.toString('base64')].join('.');
91
+ }
92
+
93
+ /**
94
+ * Derive the 32-byte AES key from the configured pass-phrase. `protected` so a
95
+ * project can override key derivation (e.g. to source the key from a KMS).
96
+ */
97
+ /** Resolve the configured pass-phrase from config/env (no dev fallback). */
98
+ protected resolveSecret(): string | undefined {
99
+ return (
100
+ ConfigService.get<string>('ai.encryptionSecret') ||
101
+ process.env.NSC__AI__ENCRYPTION_SECRET ||
102
+ process.env.SECRETS_ENCRYPTION_KEY
103
+ );
104
+ }
105
+
106
+ protected getKey(): Buffer {
107
+ const raw = this.resolveSecret();
108
+ if (!raw) {
109
+ if (!this.warned) {
110
+ this.logger.warn(
111
+ 'No AI encryption secret set (ai.encryptionSecret / NSC__AI__ENCRYPTION_SECRET / SECRETS_ENCRYPTION_KEY) — ' +
112
+ 'using an insecure development default. DO NOT run this in production; set a random 32+ char value.',
113
+ );
114
+ this.warned = true;
115
+ }
116
+ return createHash('sha256').update('lt-nest-server-ai-dev-only-insecure-default').digest();
117
+ }
118
+ return createHash('sha256').update(raw, 'utf8').digest();
119
+ }
120
+ }