@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.
- package/.claude/rules/configurable-features.md +2 -0
- package/CLAUDE.md +18 -0
- package/FRAMEWORK-API.md +48 -1
- package/dist/config.env.js +35 -1
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/helpers/config.helper.js +2 -2
- package/dist/core/common/helpers/config.helper.js.map +1 -1
- package/dist/core/common/interceptors/check-security.interceptor.js +9 -2
- package/dist/core/common/interceptors/check-security.interceptor.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +89 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.d.ts +21 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js +191 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -0
- package/dist/core/modules/ai/core-ai.controller.d.ts +90 -0
- package/dist/core/modules/ai/core-ai.controller.js +576 -0
- package/dist/core/modules/ai/core-ai.controller.js.map +1 -0
- package/dist/core/modules/ai/core-ai.module.d.ts +43 -0
- package/dist/core/modules/ai/core-ai.module.js +164 -0
- package/dist/core/modules/ai/core-ai.module.js.map +1 -0
- package/dist/core/modules/ai/core-ai.resolver.d.ts +84 -0
- package/dist/core/modules/ai/core-ai.resolver.js +530 -0
- package/dist/core/modules/ai/core-ai.resolver.js.map +1 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.d.ts +7 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js +16 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js.map +1 -0
- package/dist/core/modules/ai/hooks/ai-hook.base.d.ts +10 -0
- package/dist/core/modules/ai/hooks/ai-hook.base.js +30 -0
- package/dist/core/modules/ai/hooks/ai-hook.base.js.map +1 -0
- package/dist/core/modules/ai/hooks/ai-hook.registry.d.ts +22 -0
- package/dist/core/modules/ai/hooks/ai-hook.registry.js +98 -0
- package/dist/core/modules/ai/hooks/ai-hook.registry.js.map +1 -0
- package/dist/core/modules/ai/index.d.ts +69 -0
- package/dist/core/modules/ai/index.js +86 -0
- package/dist/core/modules/ai/index.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-budget-limit-create.input.d.ts +5 -0
- package/dist/core/modules/ai/inputs/core-ai-budget-limit-create.input.js +43 -0
- package/dist/core/modules/ai/inputs/core-ai-budget-limit-create.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-budget-limit.input.d.ts +7 -0
- package/dist/core/modules/ai/inputs/core-ai-budget-limit.input.js +74 -0
- package/dist/core/modules/ai/inputs/core-ai-budget-limit.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-connection-create.input.d.ts +6 -0
- package/dist/core/modules/ai/inputs/core-ai-connection-create.input.js +49 -0
- package/dist/core/modules/ai/inputs/core-ai-connection-create.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-connection-preference.input.d.ts +6 -0
- package/dist/core/modules/ai/inputs/core-ai-connection-preference.input.js +60 -0
- package/dist/core/modules/ai/inputs/core-ai-connection-preference.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-connection.input.d.ts +23 -0
- package/dist/core/modules/ai/inputs/core-ai-connection.input.js +229 -0
- package/dist/core/modules/ai/inputs/core-ai-connection.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-conversation-create.input.d.ts +3 -0
- package/dist/core/modules/ai/inputs/core-ai-conversation-create.input.js +21 -0
- package/dist/core/modules/ai/inputs/core-ai-conversation-create.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-conversation.input.d.ts +4 -0
- package/dist/core/modules/ai/inputs/core-ai-conversation.input.js +42 -0
- package/dist/core/modules/ai/inputs/core-ai-conversation.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-create.input.d.ts +10 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-create.input.js +74 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-create.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-hint-create.input.d.ts +5 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-hint-create.input.js +41 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-hint-create.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-hint.input.d.ts +6 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-hint.input.js +63 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-hint.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-update.input.d.ts +10 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-update.input.js +70 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt-update.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt.input.d.ts +18 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt.input.js +131 -0
- package/dist/core/modules/ai/inputs/core-ai-prompt.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-slot-create.input.d.ts +5 -0
- package/dist/core/modules/ai/inputs/core-ai-slot-create.input.js +41 -0
- package/dist/core/modules/ai/inputs/core-ai-slot-create.input.js.map +1 -0
- package/dist/core/modules/ai/inputs/core-ai-slot-update.input.d.ts +9 -0
- package/dist/core/modules/ai/inputs/core-ai-slot-update.input.js +91 -0
- package/dist/core/modules/ai/inputs/core-ai-slot-update.input.js.map +1 -0
- package/dist/core/modules/ai/interfaces/ai-hook.interface.d.ts +28 -0
- package/dist/core/modules/ai/interfaces/ai-hook.interface.js +3 -0
- package/dist/core/modules/ai/interfaces/ai-hook.interface.js.map +1 -0
- package/dist/core/modules/ai/interfaces/ai-placeholder.interface.d.ts +21 -0
- package/dist/core/modules/ai/interfaces/ai-placeholder.interface.js +3 -0
- package/dist/core/modules/ai/interfaces/ai-placeholder.interface.js.map +1 -0
- package/dist/core/modules/ai/interfaces/ai-tool.interface.d.ts +30 -0
- package/dist/core/modules/ai/interfaces/ai-tool.interface.js +3 -0
- package/dist/core/modules/ai/interfaces/ai-tool.interface.js.map +1 -0
- package/dist/core/modules/ai/interfaces/llm-provider.interface.d.ts +55 -0
- package/dist/core/modules/ai/interfaces/llm-provider.interface.js +3 -0
- package/dist/core/modules/ai/interfaces/llm-provider.interface.js.map +1 -0
- package/dist/core/modules/ai/interfaces/resolved-ai-connection.interface.d.ts +16 -0
- package/dist/core/modules/ai/interfaces/resolved-ai-connection.interface.js +3 -0
- package/dist/core/modules/ai/interfaces/resolved-ai-connection.interface.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-action.model.d.ts +6 -0
- package/dist/core/modules/ai/models/core-ai-action.model.js +44 -0
- package/dist/core/modules/ai/models/core-ai-action.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-available-connection.model.d.ts +8 -0
- package/dist/core/modules/ai/models/core-ai-available-connection.model.js +53 -0
- package/dist/core/modules/ai/models/core-ai-available-connection.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-budget-limit.model.d.ts +97 -0
- package/dist/core/modules/ai/models/core-ai-budget-limit.model.js +79 -0
- package/dist/core/modules/ai/models/core-ai-budget-limit.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-connection-preference.model.d.ts +91 -0
- package/dist/core/modules/ai/models/core-ai-connection-preference.model.js +67 -0
- package/dist/core/modules/ai/models/core-ai-connection-preference.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-connection.model.d.ts +200 -0
- package/dist/core/modules/ai/models/core-ai-connection.model.js +263 -0
- package/dist/core/modules/ai/models/core-ai-connection.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-conversation.model.d.ts +93 -0
- package/dist/core/modules/ai/models/core-ai-conversation.model.js +82 -0
- package/dist/core/modules/ai/models/core-ai-conversation.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-interaction.model.d.ts +133 -0
- package/dist/core/modules/ai/models/core-ai-interaction.model.js +137 -0
- package/dist/core/modules/ai/models/core-ai-interaction.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-message.model.d.ts +5 -0
- package/dist/core/modules/ai/models/core-ai-message.model.js +38 -0
- package/dist/core/modules/ai/models/core-ai-message.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-mode.model.d.ts +109 -0
- package/dist/core/modules/ai/models/core-ai-mode.model.js +100 -0
- package/dist/core/modules/ai/models/core-ai-mode.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-prompt-hint.model.d.ts +103 -0
- package/dist/core/modules/ai/models/core-ai-prompt-hint.model.js +89 -0
- package/dist/core/modules/ai/models/core-ai-prompt-hint.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-prompt.model.d.ts +122 -0
- package/dist/core/modules/ai/models/core-ai-prompt.model.js +117 -0
- package/dist/core/modules/ai/models/core-ai-prompt.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-response.model.d.ts +29 -0
- package/dist/core/modules/ai/models/core-ai-response.model.js +105 -0
- package/dist/core/modules/ai/models/core-ai-response.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-slot.model.d.ts +121 -0
- package/dist/core/modules/ai/models/core-ai-slot.model.js +119 -0
- package/dist/core/modules/ai/models/core-ai-slot.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-tool-grant.model.d.ts +103 -0
- package/dist/core/modules/ai/models/core-ai-tool-grant.model.js +88 -0
- package/dist/core/modules/ai/models/core-ai-tool-grant.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-tool-policy.model.d.ts +122 -0
- package/dist/core/modules/ai/models/core-ai-tool-policy.model.js +121 -0
- package/dist/core/modules/ai/models/core-ai-tool-policy.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-usage-info.model.d.ts +24 -0
- package/dist/core/modules/ai/models/core-ai-usage-info.model.js +127 -0
- package/dist/core/modules/ai/models/core-ai-usage-info.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-usage.model.d.ts +5 -0
- package/dist/core/modules/ai/models/core-ai-usage.model.js +38 -0
- package/dist/core/modules/ai/models/core-ai-usage.model.js.map +1 -0
- package/dist/core/modules/ai/providers/claude-cli.provider.d.ts +16 -0
- package/dist/core/modules/ai/providers/claude-cli.provider.js +151 -0
- package/dist/core/modules/ai/providers/claude-cli.provider.js.map +1 -0
- package/dist/core/modules/ai/providers/llm-provider.factory.d.ts +10 -0
- package/dist/core/modules/ai/providers/llm-provider.factory.js +45 -0
- package/dist/core/modules/ai/providers/llm-provider.factory.js.map +1 -0
- package/dist/core/modules/ai/providers/openai-compatible.provider.d.ts +32 -0
- package/dist/core/modules/ai/providers/openai-compatible.provider.js +236 -0
- package/dist/core/modules/ai/providers/openai-compatible.provider.js.map +1 -0
- package/dist/core/modules/ai/services/ai-crypto.service.d.ts +11 -0
- package/dist/core/modules/ai/services/ai-crypto.service.js +84 -0
- package/dist/core/modules/ai/services/ai-crypto.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-budget.service.d.ts +42 -0
- package/dist/core/modules/ai/services/core-ai-budget.service.js +211 -0
- package/dist/core/modules/ai/services/core-ai-budget.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-connection-preference.service.d.ts +18 -0
- package/dist/core/modules/ai/services/core-ai-connection-preference.service.js +53 -0
- package/dist/core/modules/ai/services/core-ai-connection-preference.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-connection-resolver.service.d.ts +55 -0
- package/dist/core/modules/ai/services/core-ai-connection-resolver.service.js +177 -0
- package/dist/core/modules/ai/services/core-ai-connection-resolver.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-connection.service.d.ts +43 -0
- package/dist/core/modules/ai/services/core-ai-connection.service.js +272 -0
- package/dist/core/modules/ai/services/core-ai-connection.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-conversation.service.d.ts +26 -0
- package/dist/core/modules/ai/services/core-ai-conversation.service.js +72 -0
- package/dist/core/modules/ai/services/core-ai-conversation.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-interaction.service.d.ts +13 -0
- package/dist/core/modules/ai/services/core-ai-interaction.service.js +52 -0
- package/dist/core/modules/ai/services/core-ai-interaction.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-mcp-client.service.d.ts +51 -0
- package/dist/core/modules/ai/services/core-ai-mcp-client.service.js +123 -0
- package/dist/core/modules/ai/services/core-ai-mcp-client.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.d.ts +68 -0
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js +279 -0
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-mcp.service.d.ts +22 -0
- package/dist/core/modules/ai/services/core-ai-mcp.service.js +59 -0
- package/dist/core/modules/ai/services/core-ai-mcp.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-mode.service.d.ts +14 -0
- package/dist/core/modules/ai/services/core-ai-mode.service.js +56 -0
- package/dist/core/modules/ai/services/core-ai-mode.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-placeholder.registry.d.ts +15 -0
- package/dist/core/modules/ai/services/core-ai-placeholder.registry.js +102 -0
- package/dist/core/modules/ai/services/core-ai-placeholder.registry.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-prompt-builder.service.d.ts +42 -0
- package/dist/core/modules/ai/services/core-ai-prompt-builder.service.js +136 -0
- package/dist/core/modules/ai/services/core-ai-prompt-builder.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-prompt-hint.service.d.ts +29 -0
- package/dist/core/modules/ai/services/core-ai-prompt-hint.service.js +98 -0
- package/dist/core/modules/ai/services/core-ai-prompt-hint.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-prompt.service.d.ts +21 -0
- package/dist/core/modules/ai/services/core-ai-prompt.service.js +143 -0
- package/dist/core/modules/ai/services/core-ai-prompt.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-slot.service.d.ts +51 -0
- package/dist/core/modules/ai/services/core-ai-slot.service.js +372 -0
- package/dist/core/modules/ai/services/core-ai-slot.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-tool-grant.service.d.ts +22 -0
- package/dist/core/modules/ai/services/core-ai-tool-grant.service.js +97 -0
- package/dist/core/modules/ai/services/core-ai-tool-grant.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-tool-policy.service.d.ts +23 -0
- package/dist/core/modules/ai/services/core-ai-tool-policy.service.js +102 -0
- package/dist/core/modules/ai/services/core-ai-tool-policy.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai.service.d.ts +144 -0
- package/dist/core/modules/ai/services/core-ai.service.js +991 -0
- package/dist/core/modules/ai/services/core-ai.service.js.map +1 -0
- package/dist/core/modules/ai/tools/ai-tool.base.d.ts +15 -0
- package/dist/core/modules/ai/tools/ai-tool.base.js +30 -0
- package/dist/core/modules/ai/tools/ai-tool.base.js.map +1 -0
- package/dist/core/modules/ai/tools/ai-tool.registry.d.ts +19 -0
- package/dist/core/modules/ai/tools/ai-tool.registry.js +71 -0
- package/dist/core/modules/ai/tools/ai-tool.registry.js.map +1 -0
- package/dist/core/modules/ai/tools/ask-user-question.tool.d.ts +38 -0
- package/dist/core/modules/ai/tools/ask-user-question.tool.js +65 -0
- package/dist/core/modules/ai/tools/ask-user-question.tool.js.map +1 -0
- package/dist/core/modules/ai/tools/search-tools.tool.d.ts +24 -0
- package/dist/core/modules/ai/tools/search-tools.tool.js +68 -0
- package/dist/core/modules/ai/tools/search-tools.tool.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +7 -1
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/error-code/error-codes.d.ts +81 -0
- package/dist/core/modules/error-code/error-codes.js +72 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.service.js +3 -3
- package/dist/core/modules/tenant/core-tenant.service.js.map +1 -1
- package/dist/core.module.js +8 -0
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/ai/ai-tools.module.d.ts +5 -0
- package/dist/server/modules/ai/ai-tools.module.js +36 -0
- package/dist/server/modules/ai/ai-tools.module.js.map +1 -0
- package/dist/server/modules/ai/tools/delete-user.tool.d.ts +30 -0
- package/dist/server/modules/ai/tools/delete-user.tool.js +49 -0
- package/dist/server/modules/ai/tools/delete-user.tool.js.map +1 -0
- package/dist/server/modules/ai/tools/find-users.tool.d.ts +30 -0
- package/dist/server/modules/ai/tools/find-users.tool.js +52 -0
- package/dist/server/modules/ai/tools/find-users.tool.js.map +1 -0
- package/dist/server/modules/ai/tools/get-user.tool.d.ts +26 -0
- package/dist/server/modules/ai/tools/get-user.tool.js +48 -0
- package/dist/server/modules/ai/tools/get-user.tool.js.map +1 -0
- package/dist/server/modules/ai/tools/update-user-job-title.tool.d.ts +32 -0
- package/dist/server/modules/ai/tools/update-user-job-title.tool.js +54 -0
- package/dist/server/modules/ai/tools/update-user-job-title.tool.js.map +1 -0
- package/dist/server/modules/error-code/error-codes.d.ts +9 -0
- package/dist/server/server.module.js +2 -0
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +10 -0
- package/migration-guides/11.25.x-to-11.26.0.md +278 -0
- package/package.json +44 -26
- package/src/config.env.ts +48 -1
- package/src/core/common/helpers/config.helper.ts +5 -2
- package/src/core/common/interceptors/check-security.interceptor.ts +13 -3
- package/src/core/common/interfaces/server-options.interface.ts +312 -0
- package/src/core/modules/ai/INTEGRATION-CHECKLIST.md +220 -0
- package/src/core/modules/ai/README.md +526 -0
- package/src/core/modules/ai/core-ai-mcp.controller.ts +223 -0
- package/src/core/modules/ai/core-ai.controller.ts +547 -0
- package/src/core/modules/ai/core-ai.module.ts +269 -0
- package/src/core/modules/ai/core-ai.resolver.ts +526 -0
- package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +35 -0
- package/src/core/modules/ai/hooks/ai-hook.base.ts +23 -0
- package/src/core/modules/ai/hooks/ai-hook.registry.ts +119 -0
- package/src/core/modules/ai/index.ts +70 -0
- package/src/core/modules/ai/inputs/core-ai-budget-limit-create.input.ts +27 -0
- package/src/core/modules/ai/inputs/core-ai-budget-limit.input.ts +52 -0
- package/src/core/modules/ai/inputs/core-ai-connection-create.input.ts +32 -0
- package/src/core/modules/ai/inputs/core-ai-connection-preference.input.ts +42 -0
- package/src/core/modules/ai/inputs/core-ai-connection.input.ts +235 -0
- package/src/core/modules/ai/inputs/core-ai-conversation-create.input.ts +13 -0
- package/src/core/modules/ai/inputs/core-ai-conversation.input.ts +33 -0
- package/src/core/modules/ai/inputs/core-ai-prompt-create.input.ts +50 -0
- package/src/core/modules/ai/inputs/core-ai-prompt-hint-create.input.ts +26 -0
- package/src/core/modules/ai/inputs/core-ai-prompt-hint.input.ts +43 -0
- package/src/core/modules/ai/inputs/core-ai-prompt-update.input.ts +42 -0
- package/src/core/modules/ai/inputs/core-ai-prompt.input.ts +144 -0
- package/src/core/modules/ai/inputs/core-ai-slot-create.input.ts +26 -0
- package/src/core/modules/ai/inputs/core-ai-slot-update.input.ts +65 -0
- package/src/core/modules/ai/interfaces/ai-hook.interface.ts +74 -0
- package/src/core/modules/ai/interfaces/ai-placeholder.interface.ts +49 -0
- package/src/core/modules/ai/interfaces/ai-tool.interface.ts +112 -0
- package/src/core/modules/ai/interfaces/llm-provider.interface.ts +187 -0
- package/src/core/modules/ai/interfaces/resolved-ai-connection.interface.ts +55 -0
- package/src/core/modules/ai/models/core-ai-action.model.ts +37 -0
- package/src/core/modules/ai/models/core-ai-available-connection.model.ts +42 -0
- package/src/core/modules/ai/models/core-ai-budget-limit.model.ts +82 -0
- package/src/core/modules/ai/models/core-ai-connection-preference.model.ts +72 -0
- package/src/core/modules/ai/models/core-ai-connection.model.ts +330 -0
- package/src/core/modules/ai/models/core-ai-conversation.model.ts +87 -0
- package/src/core/modules/ai/models/core-ai-interaction.model.ts +145 -0
- package/src/core/modules/ai/models/core-ai-message.model.ts +29 -0
- package/src/core/modules/ai/models/core-ai-mode.model.ts +93 -0
- package/src/core/modules/ai/models/core-ai-prompt-hint.model.ts +98 -0
- package/src/core/modules/ai/models/core-ai-prompt.model.ts +120 -0
- package/src/core/modules/ai/models/core-ai-response.model.ts +119 -0
- package/src/core/modules/ai/models/core-ai-slot.model.ts +146 -0
- package/src/core/modules/ai/models/core-ai-tool-grant.model.ts +87 -0
- package/src/core/modules/ai/models/core-ai-tool-policy.model.ts +115 -0
- package/src/core/modules/ai/models/core-ai-usage-info.model.ts +107 -0
- package/src/core/modules/ai/models/core-ai-usage.model.ts +29 -0
- package/src/core/modules/ai/providers/claude-cli.provider.ts +240 -0
- package/src/core/modules/ai/providers/llm-provider.factory.ts +63 -0
- package/src/core/modules/ai/providers/openai-compatible.provider.ts +329 -0
- package/src/core/modules/ai/services/ai-crypto.service.ts +120 -0
- package/src/core/modules/ai/services/core-ai-budget.service.ts +297 -0
- package/src/core/modules/ai/services/core-ai-connection-preference.service.ts +74 -0
- package/src/core/modules/ai/services/core-ai-connection-resolver.service.ts +308 -0
- package/src/core/modules/ai/services/core-ai-connection.service.ts +387 -0
- package/src/core/modules/ai/services/core-ai-conversation.service.ts +105 -0
- package/src/core/modules/ai/services/core-ai-interaction.service.ts +56 -0
- package/src/core/modules/ai/services/core-ai-mcp-client.service.ts +169 -0
- package/src/core/modules/ai/services/core-ai-mcp-oauth.service.ts +445 -0
- package/src/core/modules/ai/services/core-ai-mcp.service.ts +94 -0
- package/src/core/modules/ai/services/core-ai-mode.service.ts +43 -0
- package/src/core/modules/ai/services/core-ai-placeholder.registry.ts +123 -0
- package/src/core/modules/ai/services/core-ai-prompt-builder.service.ts +228 -0
- package/src/core/modules/ai/services/core-ai-prompt-hint.service.ts +133 -0
- package/src/core/modules/ai/services/core-ai-prompt.service.ts +164 -0
- package/src/core/modules/ai/services/core-ai-slot.service.ts +526 -0
- package/src/core/modules/ai/services/core-ai-tool-grant.service.ts +107 -0
- package/src/core/modules/ai/services/core-ai-tool-policy.service.ts +124 -0
- package/src/core/modules/ai/services/core-ai.service.ts +1396 -0
- package/src/core/modules/ai/tools/ai-tool.base.ts +46 -0
- package/src/core/modules/ai/tools/ai-tool.registry.ts +124 -0
- package/src/core/modules/ai/tools/ask-user-question.tool.ts +76 -0
- package/src/core/modules/ai/tools/search-tools.tool.ts +70 -0
- package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +15 -2
- package/src/core/modules/error-code/error-codes.ts +86 -0
- package/src/core/modules/tenant/core-tenant.service.ts +3 -3
- package/src/core.module.ts +12 -0
- package/src/index.ts +6 -0
- package/src/server/modules/ai/ai-tools.module.ts +33 -0
- package/src/server/modules/ai/tools/delete-user.tool.ts +44 -0
- package/src/server/modules/ai/tools/find-users.tool.ts +49 -0
- package/src/server/modules/ai/tools/get-user.tool.ts +43 -0
- package/src/server/modules/ai/tools/update-user-job-title.tool.ts +62 -0
- package/src/server/server.module.ts +5 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# AI Module
|
|
2
|
+
|
|
3
|
+
An extensible AI-assistant layer for `@lenne.tech/nest-server`. Users send prompts
|
|
4
|
+
from the frontend; the API optimizes/enriches the prompt, talks to a configurable
|
|
5
|
+
LLM (local or external), and lets the model call **backend tools** to read and
|
|
6
|
+
manipulate data — MCP-style — before returning a structured answer.
|
|
7
|
+
|
|
8
|
+
## Highlights
|
|
9
|
+
|
|
10
|
+
- **Database-backed LLM connections** (`CoreAiConnection`) — endpoints, models and
|
|
11
|
+
**encrypted API keys** are managed at runtime (admin CRUD; later a frontend
|
|
12
|
+
settings area), not in `config.env.ts`.
|
|
13
|
+
- **Vendor-agnostic provider abstraction** (`ILlmProvider`) — swap local runtimes
|
|
14
|
+
and hosted backends via config. Ships with a dependency-free `fetch`-based
|
|
15
|
+
provider for the OpenAI-compatible chat API (a de-facto standard, not a vendor).
|
|
16
|
+
- **Capability gradations** — each backend declares its `capabilities`
|
|
17
|
+
(`nativeTools`, `jsonResponse`, `systemPrompt`); the orchestrator compensates
|
|
18
|
+
across the whole spectrum (full native → none = emulated). No backend is special-cased.
|
|
19
|
+
- **Tool registry** (`AiToolRegistry`) — a global registry of backend capabilities
|
|
20
|
+
the LLM may call. Tools self-register from any module and are filtered by the
|
|
21
|
+
caller's roles.
|
|
22
|
+
- **Emulated tool calling** — for backends without native function calling, the
|
|
23
|
+
tool catalog is injected into the system prompt and tool calls are parsed from
|
|
24
|
+
the model's JSON output. Native tool calling is used transparently when supported.
|
|
25
|
+
- **Security first** — tools execute through `CrudService` with the caller's
|
|
26
|
+
permissions, so `@Restricted`, `securityCheck()` and tenant context all apply.
|
|
27
|
+
The LLM can never escalate beyond what the user is allowed to do.
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Frontend prompt
|
|
33
|
+
│ aiPrompt (GraphQL) / POST /ai/prompt (REST) @Roles(S_USER)
|
|
34
|
+
▼
|
|
35
|
+
CoreAiService (orchestrator / agent loop)
|
|
36
|
+
│ 1. rate-limit → resolve connection (DB) → build provider
|
|
37
|
+
│ 2. filter tools by the user's roles
|
|
38
|
+
│ 3. loop: LLM call → execute tool calls (CrudService, user perms) → feed back
|
|
39
|
+
▼
|
|
40
|
+
ILlmProvider ── OpenAiCompatibleProvider (any OpenAI-compatible endpoint) / custom providers
|
|
41
|
+
▼
|
|
42
|
+
CoreAiResponse { text, actions[], data, usage, iterations }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
Presence of the `ai` block enables the module (omit to disable, `{ enabled: false }`
|
|
48
|
+
to keep config but disable). See `IAi` in `server-options.interface.ts`.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// config.env.ts
|
|
52
|
+
ai: {
|
|
53
|
+
encryptionSecret: process.env.NSC__AI__ENCRYPTION_SECRET, // 32+ chars in prod
|
|
54
|
+
maxIterations: 5,
|
|
55
|
+
rateLimit: { max: 20, windowSeconds: 60 },
|
|
56
|
+
systemPrompt: 'You are a helpful assistant for …',
|
|
57
|
+
contextWindow: 8192, // fallback when a connection has no detected window
|
|
58
|
+
maxToolResultChars: 12000, // cap a tool-results payload fed back to the model
|
|
59
|
+
promptLearning: { autoApply: false }, // governed self-improvement (admin approves hints)
|
|
60
|
+
// Optional one-time seed of a default connection (DB is the source of truth):
|
|
61
|
+
defaultConnection: {
|
|
62
|
+
name: 'Default LLM',
|
|
63
|
+
baseUrl: process.env.AI_BASE_URL, // any OpenAI-compatible endpoint
|
|
64
|
+
model: process.env.AI_MODEL,
|
|
65
|
+
apiKeyEnv: 'AI_API_KEY', // prefer env over an inline apiKey
|
|
66
|
+
supportsNativeTools: false, // set per the backend's actual support
|
|
67
|
+
supportsJsonResponse: false,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Capabilities (vendor-agnostic)
|
|
73
|
+
|
|
74
|
+
The module makes no assumptions about a specific vendor. Each connection declares
|
|
75
|
+
what its backend supports via `supportsNativeTools` and `supportsJsonResponse`:
|
|
76
|
+
|
|
77
|
+
- Both **true** → native function calling + JSON mode are used directly.
|
|
78
|
+
- **false** → the module compensates: tool calling is emulated via the system
|
|
79
|
+
prompt and JSON is requested in-prompt and parsed defensively.
|
|
80
|
+
- **undefined** (omitted) → **auto-detected** by probing the endpoint (see below).
|
|
81
|
+
|
|
82
|
+
This covers the full spectrum — from feature-rich hosted models with native tools
|
|
83
|
+
to minimal local runtimes or gateways with no native tool/JSON support — without
|
|
84
|
+
naming or special-casing any provider. Add entirely different backends/protocols by
|
|
85
|
+
registering a builder on `LlmProviderFactory`.
|
|
86
|
+
|
|
87
|
+
> **Emulated-mode limitation (weak models):** In emulated tool calling the model
|
|
88
|
+
> is _asked_ (via the system prompt) to emit a `tool_calls` request before acting.
|
|
89
|
+
> Weaker models sometimes ignore this and reply with a natural-language "done"
|
|
90
|
+
> message **without** actually emitting the tool call. This is a _false positive_
|
|
91
|
+
> (the tool never ran — no `actions` are recorded and no confirmation gate is
|
|
92
|
+
> shown), **not** a security issue: a tool can only execute by going through the
|
|
93
|
+
> backend's `executeToolCall()` + confirmation gate, so the model can never trigger
|
|
94
|
+
> a real (or unconfirmed) side-effect by merely claiming success. For workflows
|
|
95
|
+
> that depend on reliable, action-heavy tool use, prefer a connection whose backend
|
|
96
|
+
> supports **native** tool calling (`supportsNativeTools: true`).
|
|
97
|
+
|
|
98
|
+
#### Capability auto-detection
|
|
99
|
+
|
|
100
|
+
Leave `supportsJsonResponse` / `supportsNativeTools` **undefined** to let the module
|
|
101
|
+
detect them automatically (explicit `true`/`false` is always authoritative and is
|
|
102
|
+
never probed). Detection runs in two complementary ways:
|
|
103
|
+
|
|
104
|
+
- **Eager (on create):** when a connection is created with an undefined flag, the
|
|
105
|
+
endpoint is probed once and the result is persisted. Best-effort — a probe failure
|
|
106
|
+
never blocks the create.
|
|
107
|
+
- **Lazy (on first prompt):** if a flag is still undefined at prompt time (e.g. the
|
|
108
|
+
eager probe was not possible, or the connection was seeded), the orchestrator probes
|
|
109
|
+
once, persists, and uses the result. Until then the safe emulated baseline applies.
|
|
110
|
+
- **On demand:** admins can re-probe via `detectAiConnectionCapabilities` /
|
|
111
|
+
`POST /ai/connections/:id/detect-capabilities` (e.g. after changing `baseUrl`/`model`).
|
|
112
|
+
|
|
113
|
+
The probe is provider-agnostic best effort: `response_format: json_object` is sent
|
|
114
|
+
(2xx → JSON supported); a trivial tool with `tool_choice: 'required'` is sent (2xx
|
|
115
|
+
returning a `tool_calls` result → native tools supported; a `4xx` or a silent ignore
|
|
116
|
+
→ unsupported). Override `OpenAiCompatibleProvider.detectCapabilities()` for custom
|
|
117
|
+
backends, or implement the optional `ILlmProvider.detectCapabilities()` in your own provider.
|
|
118
|
+
|
|
119
|
+
### Backend examples (external, local, CLI)
|
|
120
|
+
|
|
121
|
+
The same module connects to all of these — the only differences are the connection's
|
|
122
|
+
`providerType`, `baseUrl`/`model` and which provider builder is registered:
|
|
123
|
+
|
|
124
|
+
| Backend | How to connect | Tools |
|
|
125
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------- |
|
|
126
|
+
| **External** (hosted) | Built-in `openai-compatible` provider. `baseUrl` = the vendor's OpenAI-compatible endpoint, `apiKey` set. | native or emulated (auto-detected) |
|
|
127
|
+
| **Local** (e.g. Ollama) | Built-in `openai-compatible` provider. `baseUrl: 'http://localhost:11434/v1'`, any `apiKey` (Ollama ignores it). A tool-capable model (e.g. `qwen2.5`) supports native tools — set `supportsNativeTools: true` explicitly if the auto-probe is conservative. | native or emulated |
|
|
128
|
+
| **Claude Code CLI** | Opt-in `ClaudeCliProvider` (shells out to the local `claude` CLI). `providerType: 'claude-cli'`, `model` = a Claude alias (`opus`/`sonnet`/`haiku`). | emulated (the CLI is run **tool-free**) |
|
|
129
|
+
|
|
130
|
+
`openai-compatible` is registered out of the box. For the CLI, register the provider
|
|
131
|
+
once (e.g. in a consumer module's `onModuleInit`) — it is **not** auto-registered:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { ClaudeCliProvider, LlmProviderFactory } from '@lenne.tech/nest-server';
|
|
135
|
+
|
|
136
|
+
@Module({ providers: [] })
|
|
137
|
+
export class AiProvidersModule implements OnModuleInit {
|
|
138
|
+
constructor(private readonly factory: LlmProviderFactory) {}
|
|
139
|
+
onModuleInit() {
|
|
140
|
+
this.factory.registerBuilder('claude-cli', (conn) => new ClaudeCliProvider(conn));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then create a connection with `providerType: 'claude-cli'`. The provider invokes
|
|
146
|
+
`claude -p --output-format json --tools "" --system-prompt <orchestrator-prompt>`:
|
|
147
|
+
Claude Code runs **with all of its own tools disabled**, so it cannot read files or
|
|
148
|
+
run shell commands — it is a pure text generator and the orchestrator emulates tool
|
|
149
|
+
calling and executes tools itself through `CrudService` with the caller's permissions.
|
|
150
|
+
`spawn` uses an argument array (no shell), the conversation is piped via stdin, and
|
|
151
|
+
the child runs in a temp dir so no `CLAUDE.md`/settings leak into the context. See
|
|
152
|
+
`ClaudeCliProvider` for the full security model and the optional `ai.claudeCli` config.
|
|
153
|
+
|
|
154
|
+
## Connections (DB configuration)
|
|
155
|
+
|
|
156
|
+
Connections live in the `aiConnections` collection and are managed by admins via
|
|
157
|
+
GraphQL (`findAiConnections`, `getAiConnection`, `createAiConnection`,
|
|
158
|
+
`updateAiConnection`, `deleteAiConnection`) or REST (`/ai/connections*`).
|
|
159
|
+
|
|
160
|
+
- The API key is **AES-256-GCM encrypted at rest** (`AiCryptoService`) and **never
|
|
161
|
+
returned** — responses expose only `hasApiKey: boolean`.
|
|
162
|
+
- Patch semantics for `apiKey`: a value sets it, `''` clears it, omitting it leaves
|
|
163
|
+
it untouched.
|
|
164
|
+
- `resolve(id?)` returns a runtime `ResolvedAiConnection` (with the decrypted key)
|
|
165
|
+
for the orchestrator — system-internal, never sent to clients.
|
|
166
|
+
- One connection can be `isDefault`; the service keeps it unique.
|
|
167
|
+
- Each connection declares its capabilities (`supportsJsonResponse`,
|
|
168
|
+
`supportsNativeTools`) so the orchestrator compensates for backends without
|
|
169
|
+
native tool calling / JSON output (emulated tool calling via the system prompt).
|
|
170
|
+
|
|
171
|
+
## Connection selection (resolution chain)
|
|
172
|
+
|
|
173
|
+
When several connections exist the system picks one via a **prioritized, fully
|
|
174
|
+
overridable chain** (`CoreAiConnectionResolverService`). No connections at all →
|
|
175
|
+
AI handling is effectively disabled (the orchestrator returns a translated
|
|
176
|
+
"unavailable" response). Exactly one connection → it is the implicit default.
|
|
177
|
+
|
|
178
|
+
Availability can be **restricted per tenant** (`tenantIds` on the connection; empty
|
|
179
|
+
= available to all). A connection's availability is the gate for the "soft" layers;
|
|
180
|
+
the mandatory ("hard") layers win regardless.
|
|
181
|
+
|
|
182
|
+
Resolution order (ascending priority — a later layer overrides an earlier one):
|
|
183
|
+
|
|
184
|
+
| # | Layer | Source | Type |
|
|
185
|
+
| --- | ------------------------- | -------------------------------------------- | ---- |
|
|
186
|
+
| 1 | Global default | connection `isDefault` | soft |
|
|
187
|
+
| 2 | Tenant default | preference `scope:'tenant'`, not enforced | soft |
|
|
188
|
+
| 3 | User default | preference `scope:'user'` | soft |
|
|
189
|
+
| 4 | Client selection | `input.connectionId` | soft |
|
|
190
|
+
| 5 | Tenant-enforced | preference `scope:'tenant'`, `enforced:true` | hard |
|
|
191
|
+
| 6 | Admin-enforced global | connection `enforced` | hard |
|
|
192
|
+
| 7 | Admin-enforced per tenant | connection `enforcedTenantIds` | hard |
|
|
193
|
+
| 8 | Code override | `serviceOptions._aiConnectionId` | hard |
|
|
194
|
+
|
|
195
|
+
- **Tenant/user defaults & tenant-enforced** live in the `aiConnectionPreferences`
|
|
196
|
+
collection (`CoreAiConnectionPreferenceService`), keyed uniquely by `(scope, refId)`.
|
|
197
|
+
- **Self-service:** users set their own default via `aiSetUserConnection` /
|
|
198
|
+
`POST /ai/connections/select` (validated against availability). They list what they
|
|
199
|
+
may use via `aiAvailableConnections` / `GET /ai/connections/available` — a
|
|
200
|
+
non-sensitive list flagging the currently `selected` one and whether the choice is
|
|
201
|
+
`locked` by a mandatory layer.
|
|
202
|
+
- **Admins** manage tenant/user preferences via `setAiConnectionPreference` /
|
|
203
|
+
`findAiConnectionPreferences` / `deleteAiConnectionPreference` (REST under
|
|
204
|
+
`/ai/connections/preferences`).
|
|
205
|
+
- **Code override** (`serviceOptions._aiConnectionId`) is the deliberate, trusted
|
|
206
|
+
top layer. Projects that need admin/tenant mandates to be absolute can reorder the
|
|
207
|
+
chain — every layer is an overridable `protected` method and `resolutionLayers()`
|
|
208
|
+
can be replaced wholesale (pass a subclass via `ai: { connectionResolver }`).
|
|
209
|
+
- **Robustness:** a selection that points to a deleted/disabled connection (e.g. an
|
|
210
|
+
orphaned enforced preference or a stale code override) is dropped with a `warn` log
|
|
211
|
+
and the chain degrades to the fallback instead of throwing mid-prompt. Admin
|
|
212
|
+
`setAiConnectionPreference` validates that the connection exists before saving, and
|
|
213
|
+
deleting a connection automatically removes preferences pointing to it.
|
|
214
|
+
|
|
215
|
+
## Tools
|
|
216
|
+
|
|
217
|
+
> **⚠️ Read this before integrating the AI module into a project — also for AI agents
|
|
218
|
+
> assisting with that integration.**
|
|
219
|
+
>
|
|
220
|
+
> The AI module **does NOT have any automatic access to your domain models** (users,
|
|
221
|
+
> orders, files, …). This is intentional: the LLM is treated as untrusted, so it can
|
|
222
|
+
> only call tools that a developer has **explicitly registered**. A freshly added AI
|
|
223
|
+
> module on a new project will answer "I cannot find a tool to do that" for every
|
|
224
|
+
> domain question — that's correct behaviour, **not** a misconfiguration.
|
|
225
|
+
>
|
|
226
|
+
> **What a project must do** for the assistant to be useful:
|
|
227
|
+
>
|
|
228
|
+
> 1. For every domain operation that should be reachable from chat (read a list,
|
|
229
|
+
> fetch a record, create / update / delete something, kick off a workflow), write
|
|
230
|
+
> a small `AiTool` subclass that delegates to your existing `*Service` (NEVER
|
|
231
|
+
> directly to the Mongoose Model — see the security contract below).
|
|
232
|
+
> 2. Group them in a project module (e.g. `AiToolsModule`) and import it from
|
|
233
|
+
> `ServerModule.imports`.
|
|
234
|
+
> 3. Mark mutating tools `mutating: true` (confirmation prompt) and irreversible
|
|
235
|
+
> ones `destructive: true` (always confirmed regardless of admin override).
|
|
236
|
+
>
|
|
237
|
+
> The framework ships reference tools in
|
|
238
|
+
> `node_modules/@lenne.tech/nest-server/src/server/modules/ai/tools/` —
|
|
239
|
+
> **`find-users.tool.ts`**, **`get-user.tool.ts`**, **`delete-user.tool.ts`**,
|
|
240
|
+
> **`update-user-job-title.tool.ts`** — that demonstrate read / restricted-read /
|
|
241
|
+
> destructive-write / mutating-write patterns. They are **NOT auto-registered in
|
|
242
|
+
> consumer projects** (they live in the framework's own test fixtures and aren't
|
|
243
|
+
> exported); copy them to `<api>/src/server/modules/ai/tools/`, replace the relative
|
|
244
|
+
> `'../../../../core/...'` imports with `'@lenne.tech/nest-server'`, and register
|
|
245
|
+
> them via a project `AiToolsModule`.
|
|
246
|
+
>
|
|
247
|
+
> **Security contract:** every tool MUST go through a service that uses `CrudService`
|
|
248
|
+
>
|
|
249
|
+
> - `serviceOptions.currentUser`, so `@Restricted` field filtering, `securityCheck()`
|
|
250
|
+
> and tenant context still apply. Direct `Model.find()` etc. bypasses the framework's
|
|
251
|
+
> permission layer — see `.claude/rules/security-rules.md` in this repo for the
|
|
252
|
+
> complete rules.
|
|
253
|
+
|
|
254
|
+
A tool implements `IAiTool` (or extends the `AiTool` base class) and self-registers
|
|
255
|
+
in the global `AiToolRegistry`:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
@Injectable()
|
|
259
|
+
export class FindUsersAiTool extends AiTool {
|
|
260
|
+
readonly name = 'find_users';
|
|
261
|
+
readonly description = 'Search users by email or username. Admin only.';
|
|
262
|
+
readonly parameters = { properties: { search: { type: 'string' } }, type: 'object' };
|
|
263
|
+
readonly roles = [RoleEnum.ADMIN];
|
|
264
|
+
|
|
265
|
+
constructor(
|
|
266
|
+
registry: AiToolRegistry,
|
|
267
|
+
private readonly userService: UserService,
|
|
268
|
+
) {
|
|
269
|
+
super(registry);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async execute(args, context) {
|
|
273
|
+
// Routes through CrudService with the caller's serviceOptions → permissions apply.
|
|
274
|
+
const users = await this.userService.find(
|
|
275
|
+
{
|
|
276
|
+
filterQuery: {
|
|
277
|
+
/* … */
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
context.serviceOptions,
|
|
281
|
+
);
|
|
282
|
+
return { data: users, success: true };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Declare the tool as a provider in any module (see `src/server/modules/ai/` for the
|
|
288
|
+
reference implementation). Registering a tool with an existing name overrides it —
|
|
289
|
+
the supported way to customize a core tool.
|
|
290
|
+
|
|
291
|
+
**Security rule:** always route data access through `CrudService` using
|
|
292
|
+
`context.serviceOptions`. Returning raw `.lean()`/aggregate data bypasses
|
|
293
|
+
`@Restricted` field filtering and may leak data into the LLM context.
|
|
294
|
+
|
|
295
|
+
## Extending / overriding
|
|
296
|
+
|
|
297
|
+
Every collaborator can be replaced with a project subclass via
|
|
298
|
+
`CoreModule.forRoot(envConfig, { ai: { … } })`:
|
|
299
|
+
|
|
300
|
+
| Override | Base class |
|
|
301
|
+
| --------------------- | ----------------------------------------------------------------- |
|
|
302
|
+
| `service` | `CoreAiService` (orchestrator, prompt loop, rate-limit, audit) |
|
|
303
|
+
| `promptBuilder` | `CoreAiPromptBuilderService` (system prompt, RAG) |
|
|
304
|
+
| `connectionService` | `CoreAiConnectionService` |
|
|
305
|
+
| `connectionResolver` | `CoreAiConnectionResolverService` (resolution chain) |
|
|
306
|
+
| `preferenceService` | `CoreAiConnectionPreferenceService` (tenant/user preferences) |
|
|
307
|
+
| `budgetService` | `CoreAiBudgetService` (token/prompt budgets + usage) |
|
|
308
|
+
| `conversationService` | `CoreAiConversationService` (multi-turn history) |
|
|
309
|
+
| `interactionService` | `CoreAiInteractionService` (audit records) |
|
|
310
|
+
| `slotService` | `CoreAiSlotService` (admin slots — system-prompt building blocks) |
|
|
311
|
+
| `promptService` | `CoreAiPromptService` (user re-usable prompts / "Vorlagen") |
|
|
312
|
+
| `promptHintService` | `CoreAiPromptHintService` (governed learning loop) |
|
|
313
|
+
| `placeholderRegistry` | `CoreAiPlaceholderRegistry` (runtime `{{placeholder}}` registry) |
|
|
314
|
+
| `resolver` | `CoreAiResolver` (re-declare GraphQL decorators when overriding) |
|
|
315
|
+
| `controller` | `CoreAiController` |
|
|
316
|
+
|
|
317
|
+
Add a new LLM backend by registering a builder on `LlmProviderFactory`:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
factory.registerBuilder('my-provider', (conn) => new MyProvider(conn));
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Conversations, audit & streaming
|
|
324
|
+
|
|
325
|
+
- **Multi-turn conversations** (`aiConversations`, owner-scoped): pass
|
|
326
|
+
`conversationId` to `aiPrompt` to load prior turns and append the new ones.
|
|
327
|
+
Manage via `createAiConversation` / `findAiConversations` / `getAiConversation`
|
|
328
|
+
/ `deleteAiConversation`.
|
|
329
|
+
- **Audit** (`ai.audit: true`): every run is persisted to `aiInteractions`
|
|
330
|
+
(admin-readable via `findAiInteractions` / `getAiInteraction`).
|
|
331
|
+
- **Streaming**: `POST /ai/stream` returns Server-Sent Events (`action`, `token`,
|
|
332
|
+
`final`, `error`). `CoreAiService.promptStream()` powers it.
|
|
333
|
+
|
|
334
|
+
## Plan mode (pre-flight permission validation, all-or-nothing)
|
|
335
|
+
|
|
336
|
+
Send `input.mode: 'plan'` (or set `ai.defaultMode: 'plan'`). The model first
|
|
337
|
+
produces a complete plan; the backend then **authorizes every step up front**
|
|
338
|
+
(registry role filter + each tool's optional `authorize()` data-level check). If
|
|
339
|
+
ANY step is not permitted, **nothing executes** and a translated (de/en) error with
|
|
340
|
+
`deniedActions` is returned. Otherwise all steps run in order, feeding results
|
|
341
|
+
forward. This is the strongest fit for "do several connected actions automatically,
|
|
342
|
+
but only if the user may perform all of them".
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
@Injectable()
|
|
346
|
+
export class TransferFundsTool extends AiTool {
|
|
347
|
+
readonly name = 'transfer_funds';
|
|
348
|
+
readonly mutating = true; // governed by the confirmation policy
|
|
349
|
+
readonly destructive = true; // always requires confirmation
|
|
350
|
+
// Pre-flight check WITHOUT mutating — decides if the user may run this:
|
|
351
|
+
async authorize(args, context) {
|
|
352
|
+
const account = await this.accountService.get(args.fromId, context.serviceOptions).catch(() => null);
|
|
353
|
+
return { allowed: !!account, reason: account ? undefined : 'No access to source account' };
|
|
354
|
+
}
|
|
355
|
+
async execute(args, context) {
|
|
356
|
+
/* … */
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Confirmation for changes
|
|
362
|
+
|
|
363
|
+
- `destructive` tools always require confirmation.
|
|
364
|
+
- `mutating` tools follow `ai.confirmation.mutating: { default, enforced }`: the admin
|
|
365
|
+
default can be overridden per request via `input.requireConfirmation` — unless the
|
|
366
|
+
admin set `enforced: true`.
|
|
367
|
+
- When confirmation is needed the response has `requiresConfirmation: true` +
|
|
368
|
+
`pendingActions`; re-send the prompt with `confirm: true` to proceed.
|
|
369
|
+
|
|
370
|
+
## Client metadata & prompt enrichment
|
|
371
|
+
|
|
372
|
+
- `input.metadata` (current URL, navigation steps, console logs, …) is injected as a
|
|
373
|
+
clearly-delimited, size-capped, **untrusted** context block.
|
|
374
|
+
- The system prompt is enriched with the user's roles + available tools and optional
|
|
375
|
+
`ai.documentation` (override `CoreAiPromptBuilderService.getDocumentation()` for RAG).
|
|
376
|
+
|
|
377
|
+
## Self-optimizing prompts (editable templates + governed learning)
|
|
378
|
+
|
|
379
|
+
The system prompt is assembled from **keyed fragments** by `CoreAiPromptBuilderService`,
|
|
380
|
+
so non-technical users only enter their domain prompt while the backend keeps the model
|
|
381
|
+
optimally informed (capabilities, available tools + catalog, the user's roles, an
|
|
382
|
+
anti-hallucination + output contract, and the emulated tool protocol when needed). Every
|
|
383
|
+
prompt text is **transparent and editable** — there are no hard-coded, inaccessible
|
|
384
|
+
prompt strings.
|
|
385
|
+
|
|
386
|
+
**Editable slots (`aiSlots`, admin CRUD, tenant-scoped).** The builder ships sensible
|
|
387
|
+
built-in defaults for every slot (`getSystemDefaultSlots()`), so the module works with
|
|
388
|
+
zero rows. A row here **overrides** the default for its `key` (logical slot, e.g. `base`,
|
|
389
|
+
`permissions`, `anti_hallucination`, `output_contract`, `tool_protocol_emulated`),
|
|
390
|
+
optionally scoped by `locale` and `capability` (`all` / `native` / `emulated`). When
|
|
391
|
+
multi-tenancy is active, overrides apply only to the admin's tenant; without multi-tenancy
|
|
392
|
+
they are effectively system-wide. `create()` is idempotent on `(tenantId, key)` — a second
|
|
393
|
+
"override" of the same system slot UPDATES the existing row instead of inserting a
|
|
394
|
+
duplicate. Content may use `{{placeholders}}` (resolved via the placeholder registry —
|
|
395
|
+
see below) rendered at build time. Manage via `createAiSlot` / `findAiSlots` / … (GraphQL)
|
|
396
|
+
or `/ai/slots` (REST), with admin-friendly extras: `GET /ai/slots/effective` returns the
|
|
397
|
+
combined view (framework defaults + tenant overrides + custom slots with `isSystem` /
|
|
398
|
+
`isOverride` flags) and `POST /ai/slots/:id/reset` deletes an override → the framework
|
|
399
|
+
default applies again.
|
|
400
|
+
|
|
401
|
+
**Placeholder registry (`CoreAiPlaceholderRegistry`).** Tokens like `{{userId}}`,
|
|
402
|
+
`{{roles}}`, `{{tools}}`, `{{toolCatalog}}`, `{{documentation}}`, `{{learnedHints}}` are
|
|
403
|
+
resolved at run time by a registry. The framework registers six defaults at boot; projects
|
|
404
|
+
add their own via `register({ name, description, resolve })` from any provider. The
|
|
405
|
+
current list is served via `GET /ai/placeholders` (S*USER), so any admin/editor UI can
|
|
406
|
+
render an up-to-date sidebar without hard-coded names in the frontend. **User-prompt
|
|
407
|
+
placeholders** (in user prompts coming from `aiPrompt` / SSE) are ALSO resolved before
|
|
408
|
+
the LLM sees them — a stored user prompt template like *"Erkläre dem Nutzer mit ID
|
|
409
|
+
`{{userId}}` …"\_ gets the real value substituted at run time. Unknown tokens are left
|
|
410
|
+
untouched so plain text with curly braces survives.
|
|
411
|
+
|
|
412
|
+
**User-facing prompts (`aiPrompts`).** Re-usable short user prompts ("Vorlagen") that
|
|
413
|
+
each user authors for themselves (`scope: 'user'` — private) or for the whole tenant
|
|
414
|
+
(`scope: 'tenant'` — public). Inserted into the chat input by a picker. Mutations are
|
|
415
|
+
owner-only.
|
|
416
|
+
|
|
417
|
+
**Governed learning loop (`aiPromptHints`, admin CRUD).** When the orchestrator hits a
|
|
418
|
+
recurring failure (tool not available, tool error/exception), it records a learned
|
|
419
|
+
**hint** scoped to the tool. Hints are `suggested` by default and only reach the prompt
|
|
420
|
+
once an admin **approves** them; set `ai.promptLearning.autoApply: true` to auto-approve.
|
|
421
|
+
Review/approve/reject via `updateAiPromptHint` (GraphQL) or `/ai/prompt-hints` (REST).
|
|
422
|
+
The model also receives **structured tool errors** (`{ error: { code, message, hint } }`)
|
|
423
|
+
so it can recover within the run.
|
|
424
|
+
|
|
425
|
+
> **Security:** learned hints and template overrides only ever **add textual guidance** —
|
|
426
|
+
> they can never relax the permission model. Tool role-filtering, `authorize()`,
|
|
427
|
+
> `CrudService`/`@Restricted` and `secretFields` are enforced backend-side regardless of
|
|
428
|
+
> the prompt.
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
ai: {
|
|
432
|
+
promptLearning: {
|
|
433
|
+
enabled: true, // record + apply learned hints (default true)
|
|
434
|
+
autoApply: false, // false = admin approves each hint (governed); true = auto-approve
|
|
435
|
+
},
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
All stores are fully overridable per project via `CoreModule.forRoot(env, { ai: {
|
|
440
|
+
slotService, promptService, promptHintService, promptBuilder, placeholderRegistry } })`.
|
|
441
|
+
|
|
442
|
+
## Context window (per user/session, auto-detected)
|
|
443
|
+
|
|
444
|
+
The assembled session conversation is budgeted against the model's **context window**.
|
|
445
|
+
The window is determined automatically per connection (`ILlmProvider.detectContextWindow()`
|
|
446
|
+
— e.g. a local Ollama `/api/show` probe, a known-model table, or the Claude alias) and
|
|
447
|
+
persisted on the connection alongside the capability flags; set `connection.contextWindow`
|
|
448
|
+
explicitly to override, or `ai.contextWindow` (default `8192`) as the global fallback.
|
|
449
|
+
When a user's session history would overflow, the **oldest non-system turns are dropped**
|
|
450
|
+
(the latest turn is truncated if still too large) and oversized tool-results are capped to
|
|
451
|
+
`ai.maxToolResultChars` (default `12000`) — so long-running conversations never exceed the
|
|
452
|
+
model's limit.
|
|
453
|
+
|
|
454
|
+
## Token budgets & usage
|
|
455
|
+
|
|
456
|
+
Token/prompt limits are **per user AND per tenant**, with **config defaults** so you
|
|
457
|
+
need not configure each one individually. Limits are **optional** — a missing or `0`
|
|
458
|
+
limit means unlimited (only the LLM's own limit then applies). Requires `audit`.
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
ai: {
|
|
462
|
+
audit: true,
|
|
463
|
+
budget: {
|
|
464
|
+
period: 'day', // 'day' | 'month' | 'none'
|
|
465
|
+
user: { maxTokens: 50000 }, // default per user
|
|
466
|
+
tenant: { maxTokens: 2000000 }, // default per whole tenant
|
|
467
|
+
},
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
- Admins override per user/tenant at runtime (`aiBudgetLimits`, admin CRUD via
|
|
472
|
+
`createAiBudgetLimit` / REST `/ai/budget-limits`): `{ scope: 'user'|'tenant', refId, maxTokens?, maxPrompts?, period? }`.
|
|
473
|
+
- Resolution per scope: persisted override → config default → unlimited.
|
|
474
|
+
- Enforced before the run (user OR tenant limit) → HTTP 429 + translated message.
|
|
475
|
+
|
|
476
|
+
**Usage reported to the client:** every prompt response carries a compact
|
|
477
|
+
`budget` summary — `promptTokens` (this prompt), `usedTokens`, `remainingTokens`
|
|
478
|
+
(null = unlimited) and `resetAt`. The full breakdown (user + tenant scopes, prompts
|
|
479
|
+
|
|
480
|
+
- tokens, limits, reset) is available via the `aiUsage` query / `GET /ai/usage`.
|
|
481
|
+
|
|
482
|
+
## MCP server (`ai.mcp: true`)
|
|
483
|
+
|
|
484
|
+
The `AiToolRegistry` also feeds a real **MCP server** at `POST/GET/DELETE /ai/mcp`
|
|
485
|
+
(Streamable HTTP), so external MCP clients use the same backend tools with the
|
|
486
|
+
same role gating. Enable with `ai: { mcp: true }`.
|
|
487
|
+
|
|
488
|
+
**Install the SDK** in your project (it is a peer-style optional dependency that
|
|
489
|
+
the controller lazy-imports only when an MCP request arrives — projects that
|
|
490
|
+
don't enable MCP pay no install cost):
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
pnpm add @modelcontextprotocol/sdk
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
When `ai.mcp` is set but the SDK is missing, `/ai/mcp` returns **503 Service
|
|
497
|
+
Unavailable** with an actionable install-hint message instead of a 500 stack
|
|
498
|
+
trace.
|
|
499
|
+
|
|
500
|
+
- Auth: the request must carry a valid Bearer token/session (resolved by the
|
|
501
|
+
framework's existing auth) — the MCP session is bound to that user, and
|
|
502
|
+
`tools/list` / `tools/call` are filtered to and executed with their permissions.
|
|
503
|
+
- Unauthenticated requests get `401` with a `WWW-Authenticate` header.
|
|
504
|
+
|
|
505
|
+
### OAuth 2.1 (`ai.mcp.oauth: true`)
|
|
506
|
+
|
|
507
|
+
For generic MCP clients that auto-discover + register:
|
|
508
|
+
`CoreAiMcpOAuthService` provides HMAC-signed access tokens (constant-time verify),
|
|
509
|
+
PKCE S256, and MongoDB-backed client/code/refresh stores; the MCP controller then
|
|
510
|
+
also accepts OAuth access tokens. Mount the discovery/token endpoints in `main.ts`:
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
// main.ts, after app.init()
|
|
514
|
+
import { mountAiMcpOAuth } from '@lenne.tech/nest-server';
|
|
515
|
+
await mountAiMcpOAuth(app, { baseUrl: process.env.BASE_URL });
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Override `CoreAiMcpOAuthService.authorizeConsent()` to wire your login/consent UI.
|
|
519
|
+
Set `ai.mcp.oauthSecret` (or reuse `ai.encryptionSecret`) to a random 32+ char value.
|
|
520
|
+
|
|
521
|
+
## Tests
|
|
522
|
+
|
|
523
|
+
- `tests/unit/ai.spec.ts` — crypto, registry role filtering, orchestrator loop,
|
|
524
|
+
streaming, destructive confirmation, MCP role gating (fakes)
|
|
525
|
+
- `tests/ai.e2e-spec.ts` — full DI graph: connection CRUD + encryption, prompt
|
|
526
|
+
flow, multi-turn conversation, audit persistence, MCP 401
|