@lenne.tech/nest-server 12.1.1 → 12.3.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/architecture.md +1 -0
- package/.claude/rules/better-auth.md +31 -4
- package/.claude/rules/configurable-features.md +8 -6
- package/.claude/rules/package-management.md +29 -2
- package/.claude/rules/persistence.md +27 -1
- package/.claude/rules/testing.md +80 -15
- package/.claude/rules/versioning.md +13 -2
- package/CLAUDE.md +7 -1
- package/FRAMEWORK-API.md +20 -4
- package/README.md +20 -2
- package/dist/config.env.js +2 -2
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/helpers/graphql-ws-connection.helper.d.ts +4 -0
- package/dist/core/common/helpers/graphql-ws-connection.helper.js +17 -3
- package/dist/core/common/helpers/graphql-ws-connection.helper.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +49 -0
- package/dist/core/common/interfaces/service-options.interface.d.ts +3 -0
- package/dist/core/common/persistence/core-persistence.module.js +12 -2
- package/dist/core/common/persistence/core-persistence.module.js.map +1 -1
- package/dist/core/common/persistence/data-source.runtime.js +4 -2
- package/dist/core/common/persistence/data-source.runtime.js.map +1 -1
- package/dist/core/common/persistence/data-store.interface.d.ts +24 -9
- package/dist/core/common/persistence/id-format.d.ts +2 -0
- package/dist/core/common/persistence/id-format.js +14 -0
- package/dist/core/common/persistence/id-format.js.map +1 -1
- package/dist/core/common/persistence/model-descriptor.interface.d.ts +6 -0
- package/dist/core/common/persistence/model-metadata.registry.d.ts +2 -1
- package/dist/core/common/persistence/model-metadata.registry.js +20 -2
- package/dist/core/common/persistence/model-metadata.registry.js.map +1 -1
- package/dist/core/common/persistence/mongo/sync-schema.plugin.d.ts +18 -0
- package/dist/core/common/persistence/mongo/sync-schema.plugin.js +368 -0
- package/dist/core/common/persistence/mongo/sync-schema.plugin.js.map +1 -0
- package/dist/core/common/persistence/mongoose-data-store.d.ts +19 -9
- package/dist/core/common/persistence/mongoose-data-store.js +107 -31
- package/dist/core/common/persistence/mongoose-data-store.js.map +1 -1
- package/dist/core/common/persistence/persisted-model.decorator.d.ts +3 -0
- package/dist/core/common/persistence/persisted-model.decorator.js +9 -0
- package/dist/core/common/persistence/persisted-model.decorator.js.map +1 -1
- package/dist/core/common/persistence/policies/sync.policy.d.ts +17 -0
- package/dist/core/common/persistence/policies/sync.policy.js +77 -0
- package/dist/core/common/persistence/policies/sync.policy.js.map +1 -0
- package/dist/core/common/persistence/sql/mysql/mysql-data-store.d.ts +12 -8
- package/dist/core/common/persistence/sql/mysql/mysql-data-store.js +82 -29
- package/dist/core/common/persistence/sql/mysql/mysql-data-store.js.map +1 -1
- package/dist/core/common/persistence/sql/mysql/mysql-schema.compiler.d.ts +10 -0
- package/dist/core/common/persistence/sql/mysql/mysql-schema.compiler.js +61 -4
- package/dist/core/common/persistence/sql/mysql/mysql-schema.compiler.js.map +1 -1
- package/dist/core/common/persistence/sql/mysql/mysql-schema.migrator.js +101 -4
- package/dist/core/common/persistence/sql/mysql/mysql-schema.migrator.js.map +1 -1
- package/dist/core/common/persistence/sql/postgres/postgres-schema.compiler.d.ts +10 -0
- package/dist/core/common/persistence/sql/postgres/postgres-schema.compiler.js +41 -2
- package/dist/core/common/persistence/sql/postgres/postgres-schema.compiler.js.map +1 -1
- package/dist/core/common/persistence/sql/postgres/postgres-schema.migrator.js +98 -10
- package/dist/core/common/persistence/sql/postgres/postgres-schema.migrator.js.map +1 -1
- package/dist/core/common/persistence/sql/sql-data-store.d.ts +38 -14
- package/dist/core/common/persistence/sql/sql-data-store.js +212 -38
- package/dist/core/common/persistence/sql/sql-data-store.js.map +1 -1
- package/dist/core/common/persistence/sql/sql-errors.d.ts +2 -1
- package/dist/core/common/persistence/sql/sql-errors.js +3 -1
- package/dist/core/common/persistence/sql/sql-errors.js.map +1 -1
- package/dist/core/common/persistence/sync-access.registry.d.ts +17 -0
- package/dist/core/common/persistence/sync-access.registry.js +8 -0
- package/dist/core/common/persistence/sync-access.registry.js.map +1 -0
- package/dist/core/common/persistence/sync-model.d.ts +29 -0
- package/dist/core/common/persistence/sync-model.js +138 -0
- package/dist/core/common/persistence/sync-model.js.map +1 -0
- package/dist/core/common/services/crud.service.d.ts +13 -1
- package/dist/core/common/services/crud.service.js +174 -16
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/core/modules/ai/core-ai-mcp.controller.d.ts +3 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js +29 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
- package/dist/core/modules/ai/core-ai.constants.d.ts +2 -0
- package/dist/core/modules/ai/core-ai.constants.js +2 -0
- package/dist/core/modules/ai/core-ai.constants.js.map +1 -1
- package/dist/core/modules/ai/core-ai.controller.d.ts +6 -0
- package/dist/core/modules/ai/core-ai.controller.js +31 -1
- package/dist/core/modules/ai/core-ai.controller.js.map +1 -1
- package/dist/core/modules/ai/core-ai.module.d.ts +2 -0
- package/dist/core/modules/ai/core-ai.module.js +8 -1
- package/dist/core/modules/ai/core-ai.module.js.map +1 -1
- package/dist/core/modules/ai/core-ai.resolver.d.ts +6 -0
- package/dist/core/modules/ai/core-ai.resolver.js +32 -0
- package/dist/core/modules/ai/core-ai.resolver.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.d.ts +65 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.js +6 -3
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js +48 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-tool-authorization.helper.d.ts +9 -0
- package/dist/core/modules/ai/helpers/ai-tool-authorization.helper.js +54 -0
- package/dist/core/modules/ai/helpers/ai-tool-authorization.helper.js.map +1 -0
- package/dist/core/modules/ai/index.d.ts +7 -0
- package/dist/core/modules/ai/index.js +6 -0
- package/dist/core/modules/ai/index.js.map +1 -1
- package/dist/core/modules/ai/inputs/core-ai-settings.input.d.ts +3 -0
- package/dist/core/modules/ai/inputs/core-ai-settings.input.js +31 -0
- package/dist/core/modules/ai/inputs/core-ai-settings.input.js.map +1 -0
- package/dist/core/modules/ai/interfaces/ai-tool.interface.d.ts +2 -0
- package/dist/core/modules/ai/models/core-ai-settings.model.d.ts +4 -0
- package/dist/core/modules/ai/models/core-ai-settings.model.js +30 -0
- package/dist/core/modules/ai/models/core-ai-settings.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-user-setting.model.d.ts +8 -0
- package/dist/core/modules/ai/models/core-ai-user-setting.model.js +45 -0
- package/dist/core/modules/ai/models/core-ai-user-setting.model.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-conversation.service.d.ts +4 -1
- package/dist/core/modules/ai/services/core-ai-conversation.service.js +16 -4
- package/dist/core/modules/ai/services/core-ai-conversation.service.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js +6 -6
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-mcp.service.d.ts +27 -2
- package/dist/core/modules/ai/services/core-ai-mcp.service.js +118 -11
- package/dist/core/modules/ai/services/core-ai-mcp.service.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-prompt-builder.service.js +9 -6
- package/dist/core/modules/ai/services/core-ai-prompt-builder.service.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-user-setting.service.d.ts +17 -0
- package/dist/core/modules/ai/services/core-ai-user-setting.service.js +72 -0
- package/dist/core/modules/ai/services/core-ai-user-setting.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai.service.d.ts +8 -3
- package/dist/core/modules/ai/services/core-ai.service.js +68 -29
- package/dist/core/modules/ai/services/core-ai.service.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.config.js +3 -1
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +2 -2
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-path-guard.middleware.d.ts +8 -0
- package/dist/core/modules/better-auth/core-better-auth-path-guard.middleware.js +64 -0
- package/dist/core/modules/better-auth/core-better-auth-path-guard.middleware.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js +2 -1
- package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.d.ts +1 -1
- package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js +5 -3
- package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.module.js +2 -0
- package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
- package/dist/core/modules/better-auth/index.d.ts +1 -0
- package/dist/core/modules/better-auth/index.js +1 -0
- package/dist/core/modules/better-auth/index.js.map +1 -1
- package/dist/core/modules/error-code/error-codes.d.ts +72 -0
- package/dist/core/modules/error-code/error-codes.js +64 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -1
- package/dist/core/modules/migrate/sql-migration.helper.d.ts +15 -1
- package/dist/core/modules/migrate/sql-migration.helper.js +90 -5
- package/dist/core/modules/migrate/sql-migration.helper.js.map +1 -1
- package/dist/core/modules/sync/core-sync-base.controller.d.ts +7 -0
- package/dist/core/modules/sync/core-sync-base.controller.js +44 -0
- package/dist/core/modules/sync/core-sync-base.controller.js.map +1 -0
- package/dist/core/modules/sync/core-sync-cursor.d.ts +19 -0
- package/dist/core/modules/sync/core-sync-cursor.js +77 -0
- package/dist/core/modules/sync/core-sync-cursor.js.map +1 -0
- package/dist/core/modules/sync/core-sync-hints.controller.d.ts +4 -0
- package/dist/core/modules/sync/core-sync-hints.controller.js +55 -0
- package/dist/core/modules/sync/core-sync-hints.controller.js.map +1 -0
- package/dist/core/modules/sync/core-sync-hints.service.d.ts +28 -0
- package/dist/core/modules/sync/core-sync-hints.service.js +155 -0
- package/dist/core/modules/sync/core-sync-hints.service.js.map +1 -0
- package/dist/core/modules/sync/core-sync-infrastructure.models.d.ts +2 -0
- package/dist/core/modules/sync/core-sync-infrastructure.models.js +17 -0
- package/dist/core/modules/sync/core-sync-infrastructure.models.js.map +1 -0
- package/dist/core/modules/sync/core-sync-mutation.store.d.ts +43 -0
- package/dist/core/modules/sync/core-sync-mutation.store.js +88 -0
- package/dist/core/modules/sync/core-sync-mutation.store.js.map +1 -0
- package/dist/core/modules/sync/core-sync-state.store.d.ts +18 -0
- package/dist/core/modules/sync/core-sync-state.store.js +64 -0
- package/dist/core/modules/sync/core-sync-state.store.js.map +1 -0
- package/dist/core/modules/sync/core-sync.constants.d.ts +28 -0
- package/dist/core/modules/sync/core-sync.constants.js +23 -0
- package/dist/core/modules/sync/core-sync.constants.js.map +1 -0
- package/dist/core/modules/sync/core-sync.controller.d.ts +8 -0
- package/dist/core/modules/sync/core-sync.controller.js +79 -0
- package/dist/core/modules/sync/core-sync.controller.js.map +1 -0
- package/dist/core/modules/sync/core-sync.decorators.d.ts +12 -0
- package/dist/core/modules/sync/core-sync.decorators.js +28 -0
- package/dist/core/modules/sync/core-sync.decorators.js.map +1 -0
- package/dist/core/modules/sync/core-sync.module.d.ts +16 -0
- package/dist/core/modules/sync/core-sync.module.js +50 -0
- package/dist/core/modules/sync/core-sync.module.js.map +1 -0
- package/dist/core/modules/sync/core-sync.resolver.d.ts +9 -0
- package/dist/core/modules/sync/core-sync.resolver.js +46 -0
- package/dist/core/modules/sync/core-sync.resolver.js.map +1 -0
- package/dist/core/modules/sync/core-sync.service.d.ts +143 -0
- package/dist/core/modules/sync/core-sync.service.js +1237 -0
- package/dist/core/modules/sync/core-sync.service.js.map +1 -0
- package/dist/core/modules/sync/interfaces/core-sync.interfaces.d.ts +56 -0
- package/dist/core/modules/sync/interfaces/core-sync.interfaces.js +2 -0
- package/dist/core/modules/sync/interfaces/core-sync.interfaces.js.map +1 -0
- package/dist/core/modules/sync/models/core-sync-hint.model.d.ts +4 -0
- package/dist/core/modules/sync/models/core-sync-hint.model.js +30 -0
- package/dist/core/modules/sync/models/core-sync-hint.model.js.map +1 -0
- package/dist/core/modules/tenant/core-tenant.guard.js +14 -0
- package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.service.d.ts +21 -1
- package/dist/core/modules/tenant/core-tenant.service.js +153 -1
- package/dist/core/modules/tenant/core-tenant.service.js.map +1 -1
- package/dist/core.module.js +14 -0
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/error-code/error-codes.d.ts +8 -0
- package/dist/server/modules/sync-note/inputs/sync-note-create.input.d.ts +4 -0
- package/dist/server/modules/sync-note/inputs/sync-note-create.input.js +27 -0
- package/dist/server/modules/sync-note/inputs/sync-note-create.input.js.map +1 -0
- package/dist/server/modules/sync-note/inputs/sync-note.input.d.ts +6 -0
- package/dist/server/modules/sync-note/inputs/sync-note.input.js +37 -0
- package/dist/server/modules/sync-note/inputs/sync-note.input.js.map +1 -0
- package/dist/server/modules/sync-note/sync-note.model.d.ts +10 -0
- package/dist/server/modules/sync-note/sync-note.model.js +73 -0
- package/dist/server/modules/sync-note/sync-note.model.js.map +1 -0
- package/dist/server/modules/sync-note/sync-note.module.d.ts +2 -0
- package/dist/server/modules/sync-note/sync-note.module.js +21 -0
- package/dist/server/modules/sync-note/sync-note.module.js.map +1 -0
- package/dist/server/modules/sync-note/sync-note.service.d.ts +10 -0
- package/dist/server/modules/sync-note/sync-note.service.js +35 -0
- package/dist/server/modules/sync-note/sync-note.service.js.map +1 -0
- package/dist/test/test.helper.d.ts +1 -0
- package/dist/test/test.helper.js +9 -2
- package/dist/test/test.helper.js.map +1 -1
- package/docs/REQUEST-LIFECYCLE.md +4 -2
- package/docs/error-codes.md +14 -0
- package/docs/offline-sync-protocol.md +111 -68
- package/docs/security-overrides.md +14 -0
- package/migration-guides/12.0.x-to-12.1.x.md +0 -1
- package/migration-guides/12.1.x-to-12.2.x.md +154 -0
- package/migration-guides/12.2.x-to-12.3.x.md +440 -0
- package/package.json +23 -21
- package/src/config.env.ts +6 -2
- package/src/core/common/helpers/graphql-ws-connection.helper.ts +32 -4
- package/src/core/common/interfaces/server-options.interface.ts +229 -8
- package/src/core/common/interfaces/service-options.interface.ts +15 -0
- package/src/core/common/persistence/core-persistence.module.ts +21 -2
- package/src/core/common/persistence/data-source.runtime.ts +5 -2
- package/src/core/common/persistence/data-store.interface.ts +65 -12
- package/src/core/common/persistence/id-format.ts +26 -0
- package/src/core/common/persistence/model-descriptor.interface.ts +12 -0
- package/src/core/common/persistence/model-metadata.registry.ts +31 -2
- package/src/core/common/persistence/mongo/sync-schema.plugin.ts +508 -0
- package/src/core/common/persistence/mongoose-data-store.ts +171 -35
- package/src/core/common/persistence/persisted-model.decorator.ts +26 -0
- package/src/core/common/persistence/policies/sync.policy.ts +121 -0
- package/src/core/common/persistence/sql/mysql/mysql-data-store.ts +108 -30
- package/src/core/common/persistence/sql/mysql/mysql-schema.compiler.ts +107 -4
- package/src/core/common/persistence/sql/mysql/mysql-schema.migrator.ts +129 -3
- package/src/core/common/persistence/sql/postgres/postgres-schema.compiler.ts +74 -2
- package/src/core/common/persistence/sql/postgres/postgres-schema.migrator.ts +139 -7
- package/src/core/common/persistence/sql/sql-data-store.ts +325 -44
- package/src/core/common/persistence/sql/sql-errors.ts +5 -1
- package/src/core/common/persistence/sync-access.registry.ts +50 -0
- package/src/core/common/persistence/sync-model.ts +270 -0
- package/src/core/common/services/crud.service.ts +257 -19
- package/src/core/modules/ai/INTEGRATION-CHECKLIST.md +36 -15
- package/src/core/modules/ai/README.md +163 -25
- package/src/core/modules/ai/core-ai-mcp.controller.ts +48 -1
- package/src/core/modules/ai/core-ai.constants.ts +6 -0
- package/src/core/modules/ai/core-ai.controller.ts +36 -1
- package/src/core/modules/ai/core-ai.module.ts +12 -0
- package/src/core/modules/ai/core-ai.resolver.ts +36 -0
- package/src/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.ts +35 -4
- package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +68 -1
- package/src/core/modules/ai/helpers/ai-tool-authorization.helper.ts +99 -0
- package/src/core/modules/ai/index.ts +18 -0
- package/src/core/modules/ai/inputs/core-ai-settings.input.ts +24 -0
- package/src/core/modules/ai/interfaces/ai-hook.interface.ts +1 -1
- package/src/core/modules/ai/interfaces/ai-tool.interface.ts +38 -26
- package/src/core/modules/ai/models/core-ai-mode.model.ts +1 -1
- package/src/core/modules/ai/models/core-ai-settings.model.ts +23 -0
- package/src/core/modules/ai/models/core-ai-tool-policy.model.ts +1 -1
- package/src/core/modules/ai/models/core-ai-user-setting.model.ts +46 -0
- package/src/core/modules/ai/services/core-ai-conversation.service.ts +31 -6
- package/src/core/modules/ai/services/core-ai-mcp-oauth.service.ts +12 -9
- package/src/core/modules/ai/services/core-ai-mcp.service.ts +206 -13
- package/src/core/modules/ai/services/core-ai-prompt-builder.service.ts +13 -6
- package/src/core/modules/ai/services/core-ai-user-setting.service.ts +85 -0
- package/src/core/modules/ai/services/core-ai.service.ts +134 -35
- package/src/core/modules/api-token/README.md +4 -2
- package/src/core/modules/better-auth/CUSTOMIZATION.md +1 -1
- package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +7 -0
- package/src/core/modules/better-auth/README.md +32 -1
- package/src/core/modules/better-auth/better-auth.config.ts +12 -1
- package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +5 -2
- package/src/core/modules/better-auth/core-better-auth-path-guard.middleware.ts +85 -0
- package/src/core/modules/better-auth/core-better-auth-rate-limit.middleware.ts +5 -2
- package/src/core/modules/better-auth/core-better-auth-rate-limiter.service.ts +14 -7
- package/src/core/modules/better-auth/core-better-auth.module.ts +12 -2
- package/src/core/modules/better-auth/index.ts +1 -0
- package/src/core/modules/error-code/error-codes.ts +77 -0
- package/src/core/modules/migrate/sql-migration.helper.ts +141 -7
- package/src/core/modules/sync/INTEGRATION-CHECKLIST.md +95 -0
- package/src/core/modules/sync/README.md +198 -0
- package/src/core/modules/sync/SECURITY.md +65 -0
- package/src/core/modules/sync/core-sync-base.controller.ts +43 -0
- package/src/core/modules/sync/core-sync-cursor.ts +117 -0
- package/src/core/modules/sync/core-sync-hints.controller.ts +52 -0
- package/src/core/modules/sync/core-sync-hints.service.ts +178 -0
- package/src/core/modules/sync/core-sync-infrastructure.models.ts +40 -0
- package/src/core/modules/sync/core-sync-mutation.store.ts +146 -0
- package/src/core/modules/sync/core-sync-state.store.ts +89 -0
- package/src/core/modules/sync/core-sync.constants.ts +51 -0
- package/src/core/modules/sync/core-sync.controller.ts +68 -0
- package/src/core/modules/sync/core-sync.decorators.ts +84 -0
- package/src/core/modules/sync/core-sync.module.ts +75 -0
- package/src/core/modules/sync/core-sync.resolver.ts +35 -0
- package/src/core/modules/sync/core-sync.service.ts +1773 -0
- package/src/core/modules/sync/interfaces/core-sync.interfaces.ts +85 -0
- package/src/core/modules/sync/models/core-sync-hint.model.ts +18 -0
- package/src/core/modules/tenant/INTEGRATION-CHECKLIST.md +12 -1
- package/src/core/modules/tenant/README.md +49 -3
- package/src/core/modules/tenant/core-tenant.guard.ts +26 -2
- package/src/core/modules/tenant/core-tenant.service.ts +287 -2
- package/src/core.module.ts +25 -0
- package/src/index.ts +21 -0
- package/src/server/modules/sync-note/inputs/sync-note-create.input.ts +14 -0
- package/src/server/modules/sync-note/inputs/sync-note.input.ts +23 -0
- package/src/server/modules/sync-note/sync-note.model.ts +77 -0
- package/src/server/modules/sync-note/sync-note.module.ts +16 -0
- package/src/server/modules/sync-note/sync-note.service.ts +25 -0
- package/src/test/test.helper.ts +17 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Inject } from '@nestjs/common';
|
|
1
2
|
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|
2
3
|
|
|
3
4
|
import { FilterArgs } from '../../common/args/filter.args.js';
|
|
@@ -19,6 +20,7 @@ import { CoreAiPromptUpdateInput } from './inputs/core-ai-prompt-update.input.js
|
|
|
19
20
|
import { CoreAiSlotCreateInput } from './inputs/core-ai-slot-create.input.js';
|
|
20
21
|
import { CoreAiSlotUpdateInput } from './inputs/core-ai-slot-update.input.js';
|
|
21
22
|
import { CoreAiPromptInput } from './inputs/core-ai-prompt.input.js';
|
|
23
|
+
import { CoreAiSettingsInput } from './inputs/core-ai-settings.input.js';
|
|
22
24
|
import { CoreAiAvailableConnection } from './models/core-ai-available-connection.model.js';
|
|
23
25
|
import { CoreAiBudgetLimit } from './models/core-ai-budget-limit.model.js';
|
|
24
26
|
import { CoreAiConnectionPreference } from './models/core-ai-connection-preference.model.js';
|
|
@@ -29,6 +31,7 @@ import { CoreAiPromptHint } from './models/core-ai-prompt-hint.model.js';
|
|
|
29
31
|
import { CoreAiPrompt } from './models/core-ai-prompt.model.js';
|
|
30
32
|
import { CoreAiSlot } from './models/core-ai-slot.model.js';
|
|
31
33
|
import { CoreAiResponse } from './models/core-ai-response.model.js';
|
|
34
|
+
import { CoreAiSettings } from './models/core-ai-settings.model.js';
|
|
32
35
|
import { CoreAiUsageInfo } from './models/core-ai-usage-info.model.js';
|
|
33
36
|
import { CoreAiBudgetService } from './services/core-ai-budget.service.js';
|
|
34
37
|
import { CoreAiConnectionPreferenceService } from './services/core-ai-connection-preference.service.js';
|
|
@@ -40,6 +43,7 @@ import { CoreAiPromptHintService } from './services/core-ai-prompt-hint.service.
|
|
|
40
43
|
import { CoreAiPromptService } from './services/core-ai-prompt.service.js';
|
|
41
44
|
import { CoreAiSlotService } from './services/core-ai-slot.service.js';
|
|
42
45
|
import { CoreAiService } from './services/core-ai.service.js';
|
|
46
|
+
import { CoreAiUserSettingService } from './services/core-ai-user-setting.service.js';
|
|
43
47
|
|
|
44
48
|
/**
|
|
45
49
|
* GraphQL resolver for AI prompts and connection management.
|
|
@@ -67,6 +71,13 @@ export class CoreAiResolver {
|
|
|
67
71
|
protected readonly promptService: CoreAiPromptService,
|
|
68
72
|
) {}
|
|
69
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Personal AI settings (12.3.0+). Injected as a property, not through the constructor, so a project subclass
|
|
76
|
+
* written before it keeps compiling and still gets the settings endpoints.
|
|
77
|
+
*/
|
|
78
|
+
@Inject(CoreAiUserSettingService)
|
|
79
|
+
protected readonly userSettingService: CoreAiUserSettingService;
|
|
80
|
+
|
|
70
81
|
// ===================================================================================================================
|
|
71
82
|
// Prompt
|
|
72
83
|
// ===================================================================================================================
|
|
@@ -348,6 +359,31 @@ export class CoreAiResolver {
|
|
|
348
359
|
return this.budgetService.getUsageInfo(serviceOptions?.currentUser?.id, RequestContext.getTenantId());
|
|
349
360
|
}
|
|
350
361
|
|
|
362
|
+
// ===================================================================================================================
|
|
363
|
+
// Personal settings
|
|
364
|
+
// ===================================================================================================================
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The current user's AI settings as they take effect.
|
|
368
|
+
*/
|
|
369
|
+
@Query(() => CoreAiSettings, { description: "The current user's effective AI settings" })
|
|
370
|
+
@Roles(RoleEnum.S_USER)
|
|
371
|
+
async aiSettings(@GraphQLServiceOptions() serviceOptions: ServiceOptions): Promise<CoreAiSettings> {
|
|
372
|
+
return this.userSettingService.getSettings(serviceOptions?.currentUser?.id);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Change the current user's own AI settings (403 for a setting the server enforces).
|
|
377
|
+
*/
|
|
378
|
+
@Mutation(() => CoreAiSettings, { description: "Change the current user's own AI settings" })
|
|
379
|
+
@Roles(RoleEnum.S_USER)
|
|
380
|
+
async updateAiSettings(
|
|
381
|
+
@GraphQLServiceOptions() serviceOptions: ServiceOptions,
|
|
382
|
+
@Args('input') input: CoreAiSettingsInput,
|
|
383
|
+
): Promise<CoreAiSettings> {
|
|
384
|
+
return this.userSettingService.updateSettings(serviceOptions?.currentUser?.id, input);
|
|
385
|
+
}
|
|
386
|
+
|
|
351
387
|
/**
|
|
352
388
|
* Create an AI budget limit (admin).
|
|
353
389
|
*/
|
|
@@ -125,9 +125,31 @@ export function mcpOAuthConsentHeaders(nonce: string): Record<string, string> {
|
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/** Every text of the page in one locale. */
|
|
129
|
+
export type McpOAuthConsentTexts = (typeof TEXTS)[McpOAuthConsentLocale];
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Project adjustments for {@link renderMcpOAuthConsentPage}: replacement texts per locale (merged over
|
|
133
|
+
* the built-in ones, so a project changes only what it needs, e.g. its form of address) and CSS
|
|
134
|
+
* appended after the built-in styles (its colours and font). Neither touches the markup, the scripts or
|
|
135
|
+
* the security headers — what makes the page safe stays the framework's.
|
|
136
|
+
*
|
|
137
|
+
* Fill both from code or configuration, never from user or tenant input. Texts are escaped like the
|
|
138
|
+
* built-in ones; styles cannot leave their `<style>` element (every `<` becomes the CSS escape `\3c `), and
|
|
139
|
+
* the page's CSP blocks what CSS could load from elsewhere (`img-src 'self' data:`, no font source, no
|
|
140
|
+
* `@import` without the nonce) — but CSS still decides what the page shows.
|
|
141
|
+
*/
|
|
142
|
+
export interface IMcpOAuthConsentPageOptions {
|
|
143
|
+
styles?: string;
|
|
144
|
+
texts?: Partial<Record<McpOAuthConsentLocale, Partial<McpOAuthConsentTexts>>>;
|
|
145
|
+
}
|
|
146
|
+
|
|
128
147
|
/** Renders the page. Every dynamic value goes through {@link escapeHtml}. */
|
|
129
|
-
export function renderMcpOAuthConsentPage(
|
|
130
|
-
|
|
148
|
+
export function renderMcpOAuthConsentPage(
|
|
149
|
+
view: IMcpOAuthConsentView,
|
|
150
|
+
options: IMcpOAuthConsentPageOptions = {},
|
|
151
|
+
): string {
|
|
152
|
+
const t: Texts = { ...TEXTS[view.locale], ...options.texts?.[view.locale] };
|
|
131
153
|
const clientName = view.client.name?.trim() || t.unnamedClient;
|
|
132
154
|
// One pass: a client name is chosen by whoever registered the client, so a name containing
|
|
133
155
|
// `{app}` must not be expanded by a second replace.
|
|
@@ -146,7 +168,7 @@ export function renderMcpOAuthConsentPage(view: IMcpOAuthConsentView): string {
|
|
|
146
168
|
<meta name="robots" content="noindex">
|
|
147
169
|
<link rel="icon" href="data:,">
|
|
148
170
|
<title>${escapeHtml(t.title)} – ${escapeHtml(view.appHost)}</title>
|
|
149
|
-
<style nonce="${escapeHtml(view.nonce)}">${STYLES}</style>
|
|
171
|
+
<style nonce="${escapeHtml(view.nonce)}">${STYLES}${confinedStyles(options.styles)}</style>
|
|
150
172
|
</head>
|
|
151
173
|
<body>
|
|
152
174
|
<main class="card">
|
|
@@ -158,7 +180,16 @@ ${pageScript(view)}
|
|
|
158
180
|
`;
|
|
159
181
|
}
|
|
160
182
|
|
|
161
|
-
type Texts =
|
|
183
|
+
type Texts = McpOAuthConsentTexts;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Project CSS that cannot end the `<style>` element: a `</style>` in it would continue as markup — an injected
|
|
187
|
+
* form, for one, since the page sets no `form-action`. `<` has no meaning in CSS outside strings and comments, and
|
|
188
|
+
* the CSS escape `\3c ` keeps it where it does.
|
|
189
|
+
*/
|
|
190
|
+
function confinedStyles(styles: string | undefined): string {
|
|
191
|
+
return typeof styles === 'string' ? styles.replace(/</g, '\\3c ') : '';
|
|
192
|
+
}
|
|
162
193
|
|
|
163
194
|
function consentView(view: IMcpOAuthConsentView, t: Texts, fill: (text: string) => string): string {
|
|
164
195
|
const target = view.redirect.loopback
|
|
@@ -122,6 +122,7 @@ async function mountRouter(
|
|
|
122
122
|
const { getOAuthProtectedResourceMetadataUrl, mcpAuthRouter } =
|
|
123
123
|
await import('@modelcontextprotocol/sdk/server/auth/router.js');
|
|
124
124
|
const provider = oauthService.buildOAuthProvider() as any;
|
|
125
|
+
markAuthorizeReached(provider);
|
|
125
126
|
|
|
126
127
|
let router: unknown;
|
|
127
128
|
try {
|
|
@@ -136,7 +137,7 @@ async function mountRouter(
|
|
|
136
137
|
});
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
use(router);
|
|
140
|
+
use(guardErrorRedirects(router as (req: any, res: any, next: (error?: unknown) => void) => void));
|
|
140
141
|
const mounted: IMountedMcpOAuth = {
|
|
141
142
|
authBasePath: options.authBasePath,
|
|
142
143
|
baseUrl: baseUrl.replace(/\/$/, ''),
|
|
@@ -153,6 +154,72 @@ async function mountRouter(
|
|
|
153
154
|
return mounted;
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
/** `res.locals` key: the SDK's authorize handler handed this request to the provider */
|
|
158
|
+
const AUTHORIZE_REACHED = Symbol('ltMcpOAuthAuthorizeReached');
|
|
159
|
+
|
|
160
|
+
/** Marks every request the provider's `authorize` receives, so {@link refuseUnvouchedErrorRedirects} lets its redirects pass */
|
|
161
|
+
function markAuthorizeReached(provider: { authorize?: (...args: any[]) => Promise<void> }): void {
|
|
162
|
+
const authorize = provider.authorize;
|
|
163
|
+
if (typeof authorize !== 'function') {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
provider.authorize = (client: unknown, params: unknown, res: any) => {
|
|
167
|
+
if (res) {
|
|
168
|
+
res.locals = res.locals ?? {};
|
|
169
|
+
res.locals[AUTHORIZE_REACHED] = true;
|
|
170
|
+
}
|
|
171
|
+
return authorize.call(provider, client, params, res);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The SDK router behind a guard on `/authorize`: an error the SDK finds after the client check (a missing
|
|
177
|
+
* `code_challenge`, a wrong `response_type`) is answered here, never sent to the client's redirect URI. Dynamic client
|
|
178
|
+
* registration takes any redirect URI — it must, for claude.ai, ChatGPT, Cursor and Claude Code — so that 302 made
|
|
179
|
+
* `/authorize` an open redirect on this domain for whoever registered a client (RFC 9700 §4.11.2). The answer has the
|
|
180
|
+
* shape of the SDK's own errors before the client check. What the provider does keeps its redirects: the signed-in
|
|
181
|
+
* user's Allow or Deny, which carry the consent form's token, and the provider's own refusals. One handler, so the
|
|
182
|
+
* router is still mounted with a single `use()`.
|
|
183
|
+
*/
|
|
184
|
+
function guardErrorRedirects(
|
|
185
|
+
router: (req: any, res: any, next: (error?: unknown) => void) => void,
|
|
186
|
+
): (req: any, res: any, next: (error?: unknown) => void) => void {
|
|
187
|
+
return (req, res, next) => {
|
|
188
|
+
if (
|
|
189
|
+
String(req.path ?? '')
|
|
190
|
+
.toLowerCase()
|
|
191
|
+
.replace(/\/+$/, '') === '/authorize'
|
|
192
|
+
) {
|
|
193
|
+
refuseUnvouchedErrorRedirects(res);
|
|
194
|
+
}
|
|
195
|
+
router(req, res, next);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Answers an error redirect of the SDK directly unless the request reached the provider's `authorize` */
|
|
200
|
+
function refuseUnvouchedErrorRedirects(res: any): void {
|
|
201
|
+
const redirect = res.redirect.bind(res);
|
|
202
|
+
res.redirect = (...args: unknown[]) => {
|
|
203
|
+
const target = typeof args[0] === 'number' ? args[1] : args[0];
|
|
204
|
+
let error: null | string = null;
|
|
205
|
+
let description: null | string = null;
|
|
206
|
+
try {
|
|
207
|
+
const url = new URL(String(target));
|
|
208
|
+
error = url.searchParams.get('error');
|
|
209
|
+
description = url.searchParams.get('error_description');
|
|
210
|
+
} catch {
|
|
211
|
+
// Not an absolute URL: nothing the SDK's error redirect would produce
|
|
212
|
+
}
|
|
213
|
+
if (error && !res.locals?.[AUTHORIZE_REACHED]) {
|
|
214
|
+
res
|
|
215
|
+
.status(error === 'server_error' ? 500 : 400)
|
|
216
|
+
.json({ error, ...(description ? { error_description: description } : {}) });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
return redirect(...args);
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
156
223
|
/** The Express instance behind a Nest application, or the object itself for any other shape. */
|
|
157
224
|
function mountTargetOf(app: IAiMcpOAuthMountTarget): object {
|
|
158
225
|
const instance = app.getHttpAdapter?.()?.getInstance?.();
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Logger } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { ConfigService } from '../../../common/services/config.service.js';
|
|
4
|
+
import type { AiToolAuthorization, AiToolContext, IAiTool } from '../interfaces/ai-tool.interface.js';
|
|
5
|
+
|
|
6
|
+
const logger = new Logger('AiToolAuthorization');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Runs a tool's `authorize()` for one call. Shared by the chat orchestrator (auto and plan mode) and the MCP
|
|
10
|
+
* endpoint, so a data-level check holds on every path a tool is executed on (12.3.0+).
|
|
11
|
+
*
|
|
12
|
+
* Fails closed: a thrown `authorize()`, a missing answer or an `allowed` that is not exactly `true` is a refusal. The
|
|
13
|
+
* error itself is logged, never handed to the model or the MCP client, because it may name records or internals.
|
|
14
|
+
* A tool without `authorize()` is allowed — the registry role filter has already decided that the user may see it.
|
|
15
|
+
*/
|
|
16
|
+
export async function authorizeToolCall(
|
|
17
|
+
tool: IAiTool,
|
|
18
|
+
args: Record<string, any>,
|
|
19
|
+
context: AiToolContext,
|
|
20
|
+
): Promise<AiToolAuthorization> {
|
|
21
|
+
if (!tool.authorize) {
|
|
22
|
+
return { allowed: true };
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const result = await tool.authorize(args, context);
|
|
26
|
+
if (typeof result === 'boolean') {
|
|
27
|
+
return { allowed: result };
|
|
28
|
+
}
|
|
29
|
+
if (!result || typeof result !== 'object') {
|
|
30
|
+
return { allowed: false };
|
|
31
|
+
}
|
|
32
|
+
return { ...result, allowed: result.allowed === true };
|
|
33
|
+
} catch (err) {
|
|
34
|
+
logger.warn(`authorize() of AI tool "${tool.name}" threw, call refused: ${(err as Error).message}`);
|
|
35
|
+
return { allowed: false, reason: 'The authorization check for this action failed.' };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether a call of this tool needs the user's confirmation before it runs: a `destructive` tool always, a
|
|
41
|
+
* `mutating` tool as the `ai.confirmation.mutating` policy says (`enforced`, else the caller's `requireConfirmation`,
|
|
42
|
+
* else `default`), any other tool never.
|
|
43
|
+
*
|
|
44
|
+
* `requireConfirmation` is the chat client's per-prompt wish; the MCP endpoint passes none, so there the policy's
|
|
45
|
+
* `default` decides.
|
|
46
|
+
*/
|
|
47
|
+
export function toolRequiresConfirmation(tool: IAiTool, requireConfirmation?: boolean): boolean {
|
|
48
|
+
if (tool.destructive) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (!tool.mutating) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const cfg = ConfigService.get<{ default?: boolean; enabled?: boolean; enforced?: boolean }>(
|
|
55
|
+
'ai.confirmation.mutating',
|
|
56
|
+
);
|
|
57
|
+
if (cfg?.enforced === true) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return requireConfirmation ?? cfg?.default === true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The server's MCP confirmation policy, from `ai.mcp.confirmation` (12.3.0+): `default` is whether MCP asks a user
|
|
65
|
+
* who has not chosen for themselves, `enforced` whether users may choose at all.
|
|
66
|
+
*/
|
|
67
|
+
export interface AiMcpConfirmationPolicy {
|
|
68
|
+
default: boolean;
|
|
69
|
+
enforced: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reads `ai.mcp.confirmation`: `false` → asks nobody unless a user switches it on; `true`, `{}`, unset or any other
|
|
74
|
+
* value → asks everybody unless a user switches it off; `{ default, enforced }` as written. Only `false` switches the
|
|
75
|
+
* default off and only `true` enforces, so a misspelt value never relaxes the gate.
|
|
76
|
+
*/
|
|
77
|
+
export function mcpConfirmationPolicy(): AiMcpConfirmationPolicy {
|
|
78
|
+
const mcp = ConfigService.get<unknown>('ai.mcp');
|
|
79
|
+
const setting = mcp && typeof mcp === 'object' ? (mcp as { confirmation?: unknown }).confirmation : undefined;
|
|
80
|
+
if (setting === false) {
|
|
81
|
+
return { default: false, enforced: false };
|
|
82
|
+
}
|
|
83
|
+
if (setting && typeof setting === 'object') {
|
|
84
|
+
const { default: defaultValue, enforced } = setting as { default?: unknown; enforced?: unknown };
|
|
85
|
+
return { default: defaultValue !== false, enforced: enforced === true };
|
|
86
|
+
}
|
|
87
|
+
return { default: true, enforced: false };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Whether MCP asks a user before a tool that needs confirmation: the user's own choice, unless the policy is
|
|
92
|
+
* enforced or the user has not chosen.
|
|
93
|
+
*/
|
|
94
|
+
export function effectiveMcpConfirmation(policy: AiMcpConfirmationPolicy, userChoice?: boolean | null): boolean {
|
|
95
|
+
if (policy.enforced || typeof userChoice !== 'boolean') {
|
|
96
|
+
return policy.default;
|
|
97
|
+
}
|
|
98
|
+
return userChoice;
|
|
99
|
+
}
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
export * from './core-ai-mcp-oauth.initializer.js';
|
|
3
3
|
export * from './core-ai-mcp.controller.js';
|
|
4
4
|
export * from './helpers/ai-mcp-oauth.helper.js';
|
|
5
|
+
// Named on purpose: the page's scripts and security headers stay internal, so a project cannot pin them
|
|
6
|
+
export {
|
|
7
|
+
MCP_OAUTH_CONSENT_DECISION_FIELD,
|
|
8
|
+
MCP_OAUTH_CONSENT_TOKEN_FIELD,
|
|
9
|
+
mcpOAuthConsentLocale,
|
|
10
|
+
renderMcpOAuthConsentPage,
|
|
11
|
+
} from './helpers/ai-mcp-oauth-consent.helper.js';
|
|
12
|
+
export type {
|
|
13
|
+
IMcpOAuthConsentPageOptions,
|
|
14
|
+
IMcpOAuthConsentView,
|
|
15
|
+
McpOAuthConsentLocale,
|
|
16
|
+
McpOAuthConsentTexts,
|
|
17
|
+
} from './helpers/ai-mcp-oauth-consent.helper.js';
|
|
18
|
+
export * from './helpers/ai-tool-authorization.helper.js';
|
|
5
19
|
export * from './core-ai.controller.js';
|
|
6
20
|
export * from './core-ai.module.js';
|
|
7
21
|
export * from './core-ai.resolver.js';
|
|
@@ -19,6 +33,7 @@ export * from './inputs/core-ai-prompt-update.input.js';
|
|
|
19
33
|
export * from './inputs/core-ai-slot-create.input.js';
|
|
20
34
|
export * from './inputs/core-ai-slot-update.input.js';
|
|
21
35
|
export * from './inputs/core-ai-prompt.input.js';
|
|
36
|
+
export * from './inputs/core-ai-settings.input.js';
|
|
22
37
|
export * from './hooks/ai-hook.base.js';
|
|
23
38
|
export * from './hooks/ai-hook.registry.js';
|
|
24
39
|
export * from './interfaces/ai-interaction-record.interface.js';
|
|
@@ -41,6 +56,8 @@ export * from './models/core-ai-slot.model.js';
|
|
|
41
56
|
export * from './models/core-ai-tool-grant.model.js';
|
|
42
57
|
export * from './models/core-ai-mode.model.js';
|
|
43
58
|
export * from './models/core-ai-tool-policy.model.js';
|
|
59
|
+
export * from './models/core-ai-settings.model.js';
|
|
60
|
+
export * from './models/core-ai-user-setting.model.js';
|
|
44
61
|
export * from './models/core-ai-response.model.js';
|
|
45
62
|
export * from './models/core-ai-usage-info.model.js';
|
|
46
63
|
export * from './models/core-ai-usage.model.js';
|
|
@@ -65,6 +82,7 @@ export * from './services/core-ai-tool-grant.service.js';
|
|
|
65
82
|
export * from './services/core-ai-mcp-client.service.js';
|
|
66
83
|
export * from './services/core-ai-mode.service.js';
|
|
67
84
|
export * from './services/core-ai-tool-policy.service.js';
|
|
85
|
+
export * from './services/core-ai-user-setting.service.js';
|
|
68
86
|
export * from './services/core-ai.service.js';
|
|
69
87
|
export * from './tools/ai-tool.base.js';
|
|
70
88
|
export * from './tools/ai-tool.registry.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { InputType } from '@nestjs/graphql';
|
|
2
|
+
|
|
3
|
+
import { Restricted } from '../../../common/decorators/restricted.decorator.js';
|
|
4
|
+
import { UnifiedField } from '../../../common/decorators/unified-field.decorator.js';
|
|
5
|
+
import { RoleEnum } from '../../../common/enums/role.enum.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Changes to the current user's own AI settings (12.3.0+). A field left out stays as it is.
|
|
9
|
+
*/
|
|
10
|
+
@InputType({ description: "Changes to the current user's AI settings" })
|
|
11
|
+
@Restricted(RoleEnum.S_USER)
|
|
12
|
+
export class CoreAiSettingsInput {
|
|
13
|
+
/**
|
|
14
|
+
* Whether MCP asks before a tool that needs confirmation. Refused with 403 while the server enforces the setting
|
|
15
|
+
* (`ai.mcp.confirmation.enforced`).
|
|
16
|
+
*/
|
|
17
|
+
@UnifiedField({
|
|
18
|
+
description: 'Whether MCP asks before a tool that needs confirmation',
|
|
19
|
+
isOptional: true,
|
|
20
|
+
roles: RoleEnum.S_USER,
|
|
21
|
+
type: () => Boolean,
|
|
22
|
+
})
|
|
23
|
+
mcpConfirmation?: boolean = undefined;
|
|
24
|
+
}
|
|
@@ -39,7 +39,7 @@ export interface AiHookEvent {
|
|
|
39
39
|
* **Security:** hooks can only ADD restrictions (block calls, redact args) — they
|
|
40
40
|
* cannot relax the permission system. A hook returning no block does not bypass
|
|
41
41
|
* `@Restricted`/`@Roles`/`securityCheck()`; those still apply on every path
|
|
42
|
-
* (`authorize()`
|
|
42
|
+
* (`authorize()` runs before every execution since 12.3.0, on the arguments after the PreToolUse hooks).
|
|
43
43
|
*/
|
|
44
44
|
export interface IAiHook {
|
|
45
45
|
/** Unique hook name (for diagnostics and deterministic ordering). */
|
|
@@ -12,7 +12,13 @@ export interface AiToolContext {
|
|
|
12
12
|
/** Authenticated user (shortcut for `serviceOptions.currentUser`). */
|
|
13
13
|
currentUser?: { [key: string]: any; id: string; roles?: string[] };
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* The decision {@link IAiTool.authorize} returned for THIS call, handed to `execute()` (12.3.0+). Lets a tool reuse
|
|
17
|
+
* what its check loaded (`data`) instead of loading it twice. Absent for a tool without `authorize()`.
|
|
18
|
+
*/
|
|
19
|
+
authorization?: AiToolAuthorization;
|
|
20
|
+
|
|
21
|
+
/** Accept-Language of the original request. Absent over MCP, which carries no request language. */
|
|
16
22
|
language?: string;
|
|
17
23
|
|
|
18
24
|
/**
|
|
@@ -26,10 +32,19 @@ export interface AiToolContext {
|
|
|
26
32
|
* Result of a tool's pre-flight authorization check ({@link IAiTool.authorize}).
|
|
27
33
|
*/
|
|
28
34
|
export interface AiToolAuthorization {
|
|
29
|
-
/** Whether the user may run the tool with these arguments. */
|
|
35
|
+
/** Whether the user may run the tool with these arguments. Only `true` allows; anything else refuses. */
|
|
30
36
|
allowed: boolean;
|
|
31
37
|
|
|
32
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Whatever the check loaded and `execute()` may reuse (e.g. the record whose ownership it verified). Reaches
|
|
40
|
+
* `execute()` as `context.authorization.data`; never sent to the model or the client.
|
|
41
|
+
*/
|
|
42
|
+
data?: unknown;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Optional reason when not allowed. It reaches the model (chat) or the MCP client, so it must not name anything
|
|
46
|
+
* the user may not know.
|
|
47
|
+
*/
|
|
33
48
|
reason?: string;
|
|
34
49
|
}
|
|
35
50
|
|
|
@@ -69,21 +84,15 @@ export interface IAiTool {
|
|
|
69
84
|
|
|
70
85
|
/**
|
|
71
86
|
* Whether the tool performs a destructive/irreversible action (delete, bulk
|
|
72
|
-
* update, payment, …).
|
|
73
|
-
* confirmation: they are NOT executed until the prompt is re-sent with
|
|
74
|
-
* `confirm: true`; the first response lists them as `pendingActions` with
|
|
75
|
-
* `requiresConfirmation: true`.
|
|
87
|
+
* update, payment, …). Such a tool always needs the user's confirmation:
|
|
76
88
|
*
|
|
77
|
-
* **
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* unreachable by lesser-privileged MCP clients; MCP only skips the extra confirmation
|
|
85
|
-
* step for clients that may already see the tool. Expose MCP only to clients you trust
|
|
86
|
-
* to obtain user consent themselves.
|
|
89
|
+
* - **Chat:** it is NOT executed until the prompt is re-sent with `confirm: true`;
|
|
90
|
+
* the first response lists it as `pendingActions` with `requiresConfirmation: true`.
|
|
91
|
+
* - **MCP (12.3.0+):** `tools/call` asks the user through MCP elicitation and runs the
|
|
92
|
+
* tool only after they accepted. A client that cannot ask (no `elicitation`
|
|
93
|
+
* capability) gets a refusal. Whether MCP asks is `ai.mcp.confirmation` (on by
|
|
94
|
+
* default), which each user may switch for themselves unless it is enforced;
|
|
95
|
+
* `tools/list` marks the tool with `destructiveHint: true`.
|
|
87
96
|
*/
|
|
88
97
|
readonly destructive?: boolean;
|
|
89
98
|
|
|
@@ -93,22 +102,25 @@ export interface IAiTool {
|
|
|
93
102
|
* optionally client-overridable, optionally enforced). `destructive` is the
|
|
94
103
|
* stronger flag and always requires confirmation regardless of policy.
|
|
95
104
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
105
|
+
* Over MCP (12.3.0+) the same policy applies through elicitation, with the
|
|
106
|
+
* policy's `default` in place of the chat client's `requireConfirmation`.
|
|
98
107
|
*/
|
|
99
108
|
readonly mutating?: boolean;
|
|
100
109
|
|
|
101
110
|
/**
|
|
102
|
-
* Optional
|
|
111
|
+
* Optional authorization check for one call. MUST NOT mutate anything — it only
|
|
103
112
|
* decides whether the user may run the tool with these arguments (e.g. load the
|
|
104
113
|
* target record and verify ownership).
|
|
105
114
|
*
|
|
106
|
-
* **Runs
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
115
|
+
* **Runs before every execution (12.3.0+)**: in auto mode, in plan mode (once for
|
|
116
|
+
* the whole plan before any step runs, and again before each step) and over MCP. It
|
|
117
|
+
* sees the arguments that will be executed, i.e. after the PreToolUse hooks. A refusal,
|
|
118
|
+
* a thrown error or an `allowed` that is not `true` skips the call. Return `data` to
|
|
119
|
+
* hand what the check loaded to `execute()` (`context.authorization.data`).
|
|
120
|
+
*
|
|
121
|
+
* Still route the data access in `execute()` through `CrudService` with
|
|
122
|
+
* `context.serviceOptions`: `authorize()` decides whether to try, the service decides
|
|
123
|
+
* what the user may read and write.
|
|
112
124
|
*
|
|
113
125
|
* When omitted, only the registry role filter applies.
|
|
114
126
|
*/
|
|
@@ -19,7 +19,7 @@ export type AiModeDocument = CoreAiMode & Document;
|
|
|
19
19
|
* domain-specialized assistants without forking the orchestrator. Like every
|
|
20
20
|
* other ai layer, modes can only ADD restrictions; they cannot relax the
|
|
21
21
|
* permission model (`@Restricted` / `@Roles` / `securityCheck()` still apply;
|
|
22
|
-
* `authorize()`
|
|
22
|
+
* `authorize()` before every execution).
|
|
23
23
|
*/
|
|
24
24
|
@MongooseSchema({ collection: 'aiModes', timestamps: true })
|
|
25
25
|
@ObjectType({ description: 'Named agent mode' })
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Field, ObjectType } from '@nestjs/graphql';
|
|
2
|
+
|
|
3
|
+
import { Restricted } from '../../../common/decorators/restricted.decorator.js';
|
|
4
|
+
import { RoleEnum } from '../../../common/enums/role.enum.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The current user's AI settings as they take effect (12.3.0+): the user's own choice where the server allows one,
|
|
8
|
+
* else the server configuration. Answer of `GET/PUT /ai/settings`, `aiSettings` and `updateAiSettings`.
|
|
9
|
+
*/
|
|
10
|
+
@ObjectType({ description: "The current user's effective AI settings" })
|
|
11
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
12
|
+
export class CoreAiSettings {
|
|
13
|
+
/**
|
|
14
|
+
* Whether MCP asks this user before a tool that needs confirmation (`destructive`, or `mutating` under
|
|
15
|
+
* `ai.confirmation.mutating`). `false` works like a "bypass permissions" mode for this user's MCP clients.
|
|
16
|
+
*/
|
|
17
|
+
@Field(() => Boolean, { description: 'Whether MCP asks before a tool that needs confirmation' })
|
|
18
|
+
mcpConfirmation: boolean;
|
|
19
|
+
|
|
20
|
+
/** Whether the server fixed {@link mcpConfirmation} (`ai.mcp.confirmation.enforced`), so the user cannot change it. */
|
|
21
|
+
@Field(() => Boolean, { description: 'Whether the server fixed mcpConfirmation, so it cannot be changed' })
|
|
22
|
+
mcpConfirmationEnforced: boolean;
|
|
23
|
+
}
|
|
@@ -51,7 +51,7 @@ export class CoreAiToolPolicyRule {
|
|
|
51
51
|
* `user`. A more specific scope wins over a more generic one. Hints only ever
|
|
52
52
|
* tighten or relax the confirmation gate; the underlying permission model
|
|
53
53
|
* (`@Restricted`, `@Roles`, `securityCheck()`) is enforced regardless;
|
|
54
|
-
* `authorize()` adds to that in
|
|
54
|
+
* `authorize()` adds to that before every execution. Rules apply in the chat and over MCP (12.3.0+).
|
|
55
55
|
*/
|
|
56
56
|
@MongooseSchema({ collection: 'aiToolPolicies', timestamps: true })
|
|
57
57
|
@ObjectType({ description: 'Admin-editable scoped policy for a tool call' })
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ObjectType } from '@nestjs/graphql';
|
|
2
|
+
import { Schema as MongooseSchema, SchemaFactory } from '../../../common/persistence/mongo/mongoose-compat.js';
|
|
3
|
+
import type { Document } from 'mongoose';
|
|
4
|
+
|
|
5
|
+
import { Restricted } from '../../../common/decorators/restricted.decorator.js';
|
|
6
|
+
import { UnifiedField } from '../../../common/decorators/unified-field.decorator.js';
|
|
7
|
+
import { RoleEnum } from '../../../common/enums/role.enum.js';
|
|
8
|
+
import { CorePersistenceModel } from '../../../common/models/core-persistence.model.js';
|
|
9
|
+
|
|
10
|
+
export type AiUserSettingDocument = CoreAiUserSetting & Document;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Personal AI settings of one user (12.3.0+), one record per user. Only what the user chose is stored; an unset
|
|
14
|
+
* field follows the server configuration.
|
|
15
|
+
*
|
|
16
|
+
* Users read and change their own settings through `GET/PUT /ai/settings` and the `aiSettings` /
|
|
17
|
+
* `updateAiSettings` operations, which answer with {@link CoreAiSettings} (the effective values), never with this
|
|
18
|
+
* record — hence admin-only here.
|
|
19
|
+
*/
|
|
20
|
+
@MongooseSchema({ collection: 'aiUserSettings', timestamps: true })
|
|
21
|
+
@ObjectType({ description: 'Personal AI settings of one user' })
|
|
22
|
+
@Restricted(RoleEnum.ADMIN)
|
|
23
|
+
export class CoreAiUserSetting extends CorePersistenceModel {
|
|
24
|
+
/**
|
|
25
|
+
* Whether MCP asks this user before a tool that needs confirmation. Unset follows `ai.mcp.confirmation`; ignored
|
|
26
|
+
* while that is `enforced`.
|
|
27
|
+
*/
|
|
28
|
+
@UnifiedField({
|
|
29
|
+
description: 'Whether MCP asks this user before a tool that needs confirmation (unset = server default)',
|
|
30
|
+
isOptional: true,
|
|
31
|
+
mongoose: true,
|
|
32
|
+
roles: RoleEnum.ADMIN,
|
|
33
|
+
type: () => Boolean,
|
|
34
|
+
})
|
|
35
|
+
mcpConfirmation?: boolean = undefined;
|
|
36
|
+
|
|
37
|
+
/** The user the settings belong to. */
|
|
38
|
+
@UnifiedField({
|
|
39
|
+
description: 'The user the settings belong to',
|
|
40
|
+
mongoose: { unique: true },
|
|
41
|
+
roles: RoleEnum.ADMIN,
|
|
42
|
+
})
|
|
43
|
+
userId: string = undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const AiUserSettingSchema = SchemaFactory.createForClass(CoreAiUserSetting);
|