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