@postxl/generators 1.12.3 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/backend-ai/ai.generator.d.ts +18 -0
  2. package/dist/backend-ai/ai.generator.js +174 -0
  3. package/dist/backend-ai/ai.generator.js.map +1 -0
  4. package/dist/backend-ai/generators/ai-agent-service.generator.d.ts +4 -0
  5. package/dist/backend-ai/generators/ai-agent-service.generator.js +264 -0
  6. package/dist/backend-ai/generators/ai-agent-service.generator.js.map +1 -0
  7. package/dist/backend-ai/generators/ai-cache-service.generator.d.ts +1 -0
  8. package/dist/backend-ai/generators/ai-cache-service.generator.js +110 -0
  9. package/dist/backend-ai/generators/ai-cache-service.generator.js.map +1 -0
  10. package/dist/backend-ai/generators/ai-config.generator.d.ts +1 -0
  11. package/dist/backend-ai/generators/ai-config.generator.js +27 -0
  12. package/dist/backend-ai/generators/ai-config.generator.js.map +1 -0
  13. package/dist/backend-ai/generators/ai-module.generator.d.ts +2 -0
  14. package/dist/backend-ai/generators/ai-module.generator.js +89 -0
  15. package/dist/backend-ai/generators/ai-module.generator.js.map +1 -0
  16. package/dist/backend-ai/generators/ai-route.generator.d.ts +1 -0
  17. package/dist/backend-ai/generators/ai-route.generator.js +29 -0
  18. package/dist/backend-ai/generators/ai-route.generator.js.map +1 -0
  19. package/dist/backend-ai/generators/ai-tools-service.generator.d.ts +4 -0
  20. package/dist/backend-ai/generators/ai-tools-service.generator.js +222 -0
  21. package/dist/backend-ai/generators/ai-tools-service.generator.js.map +1 -0
  22. package/dist/backend-ai/generators/model-provider-interface.generator.d.ts +1 -0
  23. package/dist/backend-ai/generators/model-provider-interface.generator.js +48 -0
  24. package/dist/backend-ai/generators/model-provider-interface.generator.js.map +1 -0
  25. package/dist/backend-ai/generators/openai-model-provider-service.generator.d.ts +1 -0
  26. package/dist/backend-ai/generators/openai-model-provider-service.generator.js +128 -0
  27. package/dist/backend-ai/generators/openai-model-provider-service.generator.js.map +1 -0
  28. package/dist/backend-ai/index.d.ts +4 -0
  29. package/dist/backend-ai/index.js +40 -0
  30. package/dist/backend-ai/index.js.map +1 -0
  31. package/dist/backend-core/generators/main.generator.js +4 -3
  32. package/dist/backend-core/generators/main.generator.js.map +1 -1
  33. package/dist/backend-e2e/backend-e2e.generator.js +4 -4
  34. package/dist/backend-e2e/backend-e2e.generator.js.map +1 -1
  35. package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js +1 -1
  36. package/dist/backend-router-trpc/generators/app-routes.generator.js +3 -1
  37. package/dist/backend-router-trpc/generators/app-routes.generator.js.map +1 -1
  38. package/dist/backend-router-trpc/generators/trpc-plugin.generator.js +3 -0
  39. package/dist/backend-router-trpc/generators/trpc-plugin.generator.js.map +1 -1
  40. package/dist/backend-router-trpc/generators/trpc-router-module.generator.js +2 -1
  41. package/dist/backend-router-trpc/generators/trpc-router-module.generator.js.map +1 -1
  42. package/dist/backend-router-trpc/generators/trpc-shared.generator.js +7 -1
  43. package/dist/backend-router-trpc/generators/trpc-shared.generator.js.map +1 -1
  44. package/dist/backend-router-trpc/router-trpc.generator.d.ts +2 -1
  45. package/dist/backend-router-trpc/router-trpc.generator.js +2 -0
  46. package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
  47. package/dist/backend-seed/seed.generator.js +10 -1
  48. package/dist/backend-seed/seed.generator.js.map +1 -1
  49. package/dist/base/template/scripts/setup.sh +9 -4
  50. package/dist/base/template/sonar-project.properties +9 -1
  51. package/dist/devops/generators/bitbucket-pipelines-yml.generator.js +1 -0
  52. package/dist/devops/generators/bitbucket-pipelines-yml.generator.js.map +1 -1
  53. package/dist/devops/generators/e2e-yml.generator.js +35 -10
  54. package/dist/devops/generators/e2e-yml.generator.js.map +1 -1
  55. package/dist/devops/generators/jenkinsfile.generator.js +25 -1
  56. package/dist/devops/generators/jenkinsfile.generator.js.map +1 -1
  57. package/dist/e2e/template/e2e/specs/example.spec.ts-snapshots/Navigate-to-homepage-and-take-snapshot-1-chromium-linux.png +0 -0
  58. package/dist/frontend-actions/actions.generator.d.ts +9 -0
  59. package/dist/frontend-actions/actions.generator.js +111 -0
  60. package/dist/frontend-actions/actions.generator.js.map +1 -0
  61. package/dist/frontend-actions/generators/ai-action-text.utils.generator.d.ts +1 -0
  62. package/dist/frontend-actions/generators/ai-action-text.utils.generator.js +52 -0
  63. package/dist/frontend-actions/generators/ai-action-text.utils.generator.js.map +1 -0
  64. package/dist/frontend-actions/generators/ai-assistant-store.generator.d.ts +1 -0
  65. package/dist/frontend-actions/generators/ai-assistant-store.generator.js +230 -0
  66. package/dist/frontend-actions/generators/ai-assistant-store.generator.js.map +1 -0
  67. package/dist/frontend-actions/generators/ai-sidebar-content.generator.d.ts +1 -0
  68. package/dist/frontend-actions/generators/ai-sidebar-content.generator.js +139 -0
  69. package/dist/frontend-actions/generators/ai-sidebar-content.generator.js.map +1 -0
  70. package/dist/frontend-actions/generators/ai-sidepane.generator.d.ts +1 -0
  71. package/dist/frontend-actions/generators/ai-sidepane.generator.js +98 -0
  72. package/dist/frontend-actions/generators/ai-sidepane.generator.js.map +1 -0
  73. package/dist/frontend-actions/generators/base-global-actions.generator.d.ts +1 -0
  74. package/dist/frontend-actions/generators/base-global-actions.generator.js +405 -0
  75. package/dist/frontend-actions/generators/base-global-actions.generator.js.map +1 -0
  76. package/dist/frontend-actions/generators/command-palette-action.generator.d.ts +1 -0
  77. package/dist/frontend-actions/generators/command-palette-action.generator.js +87 -0
  78. package/dist/frontend-actions/generators/command-palette-action.generator.js.map +1 -0
  79. package/dist/frontend-actions/generators/command-palette-store.generator.d.ts +1 -0
  80. package/dist/frontend-actions/generators/command-palette-store.generator.js +288 -0
  81. package/dist/frontend-actions/generators/command-palette-store.generator.js.map +1 -0
  82. package/dist/frontend-actions/generators/command-palette.generator.d.ts +5 -0
  83. package/dist/frontend-actions/generators/command-palette.generator.js +332 -0
  84. package/dist/frontend-actions/generators/command-palette.generator.js.map +1 -0
  85. package/dist/frontend-actions/generators/filter-utils.generator.d.ts +1 -0
  86. package/dist/frontend-actions/generators/filter-utils.generator.js +50 -0
  87. package/dist/frontend-actions/generators/filter-utils.generator.js.map +1 -0
  88. package/dist/frontend-actions/generators/sidepanel-toggle.generator.d.ts +1 -0
  89. package/dist/frontend-actions/generators/sidepanel-toggle.generator.js +37 -0
  90. package/dist/frontend-actions/generators/sidepanel-toggle.generator.js.map +1 -0
  91. package/dist/frontend-actions/index.d.ts +4 -0
  92. package/dist/frontend-actions/index.js +40 -0
  93. package/dist/frontend-actions/index.js.map +1 -0
  94. package/dist/frontend-admin/admin.generator.d.ts +3 -1
  95. package/dist/frontend-admin/admin.generator.js +8 -1
  96. package/dist/frontend-admin/admin.generator.js.map +1 -1
  97. package/dist/frontend-admin/generators/admin-global-actions.generator.d.ts +4 -0
  98. package/dist/frontend-admin/generators/admin-global-actions.generator.js +152 -0
  99. package/dist/frontend-admin/generators/admin-global-actions.generator.js.map +1 -0
  100. package/dist/frontend-admin/generators/detail-sidebar.generator.js +44 -32
  101. package/dist/frontend-admin/generators/detail-sidebar.generator.js.map +1 -1
  102. package/dist/frontend-admin/generators/model-admin-page.generator.js +172 -11
  103. package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
  104. package/dist/frontend-admin/utils.d.ts +1 -0
  105. package/dist/frontend-admin/utils.js +1 -0
  106. package/dist/frontend-admin/utils.js.map +1 -1
  107. package/dist/frontend-core/generators/tsconfig.generator.js +1 -0
  108. package/dist/frontend-core/generators/tsconfig.generator.js.map +1 -1
  109. package/dist/frontend-core/template/.env.example +3 -0
  110. package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +1 -1
  111. package/dist/frontend-core/template/src/lib/color.ts +6 -3
  112. package/dist/frontend-core/template/src/lib/config.ts +3 -1
  113. package/dist/generators.js +4 -0
  114. package/dist/generators.js.map +1 -1
  115. package/dist/index.d.ts +2 -0
  116. package/dist/index.js +8 -2
  117. package/dist/index.js.map +1 -1
  118. package/dist/types/template/ai.types.ts +34 -0
  119. package/dist/types/types.generator.js +1 -0
  120. package/dist/types/types.generator.js.map +1 -1
  121. package/package.json +4 -4
@@ -0,0 +1,18 @@
1
+ import * as Generator from '@postxl/generator';
2
+ import { WithBackend } from '../backend-core';
3
+ import { WithBase } from '../base';
4
+ import { WithTypes } from '../types';
5
+ type ContextRequirements = WithTypes<WithBackend<WithBase<Generator.Context>>>;
6
+ export type ContextResult = WithBackendAi<ContextRequirements>;
7
+ export type WithBackendAi<Context> = Generator.ExtendContext<Context, {
8
+ ai: {
9
+ module: Generator.ImportableClass;
10
+ agentService: Generator.ImportableClass;
11
+ route: Generator.ImportableFunction & {
12
+ _filePath: Generator.FilePath;
13
+ };
14
+ };
15
+ }>;
16
+ export declare const generatorId: string & import("zod").$brand<"PXL.GeneratorInterfaceId">;
17
+ export declare const generator: Generator.GeneratorInterface;
18
+ export {};
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generator = exports.generatorId = void 0;
37
+ const Generator = __importStar(require("@postxl/generator"));
38
+ const backend_core_1 = require("../backend-core");
39
+ const base_1 = require("../base");
40
+ const types_1 = require("../types");
41
+ const ai_agent_service_generator_1 = require("./generators/ai-agent-service.generator");
42
+ const ai_cache_service_generator_1 = require("./generators/ai-cache-service.generator");
43
+ const ai_config_generator_1 = require("./generators/ai-config.generator");
44
+ const ai_module_generator_1 = require("./generators/ai-module.generator");
45
+ const ai_route_generator_1 = require("./generators/ai-route.generator");
46
+ const ai_tools_service_generator_1 = require("./generators/ai-tools-service.generator");
47
+ const model_provider_interface_generator_1 = require("./generators/model-provider-interface.generator");
48
+ const openai_model_provider_service_generator_1 = require("./generators/openai-model-provider-service.generator");
49
+ const apiKeyDummyValue = 'sk-...';
50
+ exports.generatorId = Generator.toGeneratorInterfaceId('backend-ai');
51
+ exports.generator = {
52
+ id: exports.generatorId,
53
+ requires: [backend_core_1.backendGeneratorId, base_1.baseGeneratorId, types_1.typesGeneratorId],
54
+ register: (context) => {
55
+ const module = {
56
+ name: Generator.toClassName('AiModule'),
57
+ location: Generator.toBackendModuleLocation('@ai/ai.module'),
58
+ };
59
+ const agentService = {
60
+ name: Generator.toClassName('AiAgentService'),
61
+ location: Generator.toBackendModuleLocation('@ai/ai-agent.service'),
62
+ };
63
+ const aiModule = {
64
+ name: Generator.toBackendModuleName('ai'),
65
+ moduleClass: module,
66
+ apiModuleRegistration: {
67
+ code: Generator.ts('AiModule.forRoot(config.ai)'),
68
+ },
69
+ envConfig: {
70
+ decoder: Generator.ts(`
71
+ AI_ENABLED: zEnvBoolean.optional().default(true),
72
+ AI_OPENAI_KEY: z.string().optional(),
73
+ AI_OPENAI_MODEL: z.string().optional().default('gpt-4.1-mini'),
74
+ AI_OPENAI_KEYS: z.string().optional(),
75
+ AI_OPENAI_TIMEOUT_MS: z.coerce.number().optional().default(20000),
76
+ AI_RATE_LIMIT_WINDOW_MS: z.coerce.number().optional().default(60000),
77
+ AI_RATE_LIMIT_MAX_REQUESTS: z.coerce.number().optional().default(120),
78
+ AI_CONVERSATION_TTL_MS: z.coerce.number().optional().default(900000),
79
+ AI_CONVERSATION_CACHE_MAX_SIZE: z.coerce.number().optional().default(500),
80
+ AI_MAX_INTERNAL_TOOL_STEPS: z.coerce.number().optional().default(12),
81
+ `),
82
+ transformer: Generator.ts(`
83
+ ai: {
84
+ ...(() => {
85
+ const singleKey = typeof val.AI_OPENAI_KEY === 'string' ? val.AI_OPENAI_KEY.trim() : ''
86
+ const keyPool =
87
+ typeof val.AI_OPENAI_KEYS === 'string'
88
+ ? val.AI_OPENAI_KEYS
89
+ .split(',')
90
+ .map((key) => key.trim())
91
+ .filter((key) => key.length > 0)
92
+ : []
93
+
94
+ const hasSingleKey = singleKey.length > 0
95
+ const hasKeyPool = keyPool.length > 0
96
+ const hasDummySingleKey = singleKey === '${apiKeyDummyValue}'
97
+ const hasDummyKeyInPool = keyPool.includes('${apiKeyDummyValue}')
98
+
99
+ if (val.AI_ENABLED && !hasSingleKey && !hasKeyPool) {
100
+ throw new Error('AI_ENABLED=true requires AI_OPENAI_KEY or AI_OPENAI_KEYS')
101
+ }
102
+ if (val.AI_ENABLED && (hasDummySingleKey || hasDummyKeyInPool)) {
103
+ throw new Error('AI_ENABLED=true requires a real OpenAI key, not the placeholder "${apiKeyDummyValue}"')
104
+ }
105
+
106
+ return {
107
+ enabled: val.AI_ENABLED,
108
+ openAIApiKey: val.AI_OPENAI_KEY,
109
+ model: val.AI_OPENAI_MODEL,
110
+ openAIApiKeys: val.AI_OPENAI_KEYS,
111
+ timeoutMs: val.AI_OPENAI_TIMEOUT_MS,
112
+ rateLimitWindowMs: val.AI_RATE_LIMIT_WINDOW_MS,
113
+ rateLimitMaxRequests: val.AI_RATE_LIMIT_MAX_REQUESTS,
114
+ conversationTtlMs: val.AI_CONVERSATION_TTL_MS,
115
+ conversationCacheMaxSize: val.AI_CONVERSATION_CACHE_MAX_SIZE,
116
+ maxInternalToolSteps: val.AI_MAX_INTERNAL_TOOL_STEPS,
117
+ }
118
+ })(),
119
+ },
120
+ `),
121
+ dotEnvExample: Generator.ts(`
122
+ # Enable or disable AI assistant features on the backend
123
+ AI_ENABLED=true
124
+ # Required when AI is enabled unless AI_OPENAI_KEYS is set
125
+ AI_OPENAI_KEY="${apiKeyDummyValue}"
126
+ # Optional: key pool for rotation (comma-separated); used before AI_OPENAI_KEY when set
127
+ # AI_OPENAI_KEYS="sk-key-1,sk-key-2"
128
+ # Optional model override
129
+ # AI_OPENAI_MODEL="gpt-4.1-mini"
130
+ # Optional request timeout in milliseconds
131
+ # AI_OPENAI_TIMEOUT_MS=20000
132
+ # Optional global backend AI request rate limit (window + max requests)
133
+ # AI_RATE_LIMIT_WINDOW_MS=60000
134
+ # AI_RATE_LIMIT_MAX_REQUESTS=120
135
+ # Optional in-memory conversation cache controls
136
+ # AI_CONVERSATION_TTL_MS=900000
137
+ # AI_CONVERSATION_CACHE_MAX_SIZE=500
138
+ # Optional max number of backend tool executions in one AI turn
139
+ # AI_MAX_INTERNAL_TOOL_STEPS=12
140
+ `),
141
+ },
142
+ };
143
+ context.backend.modules.push(aiModule);
144
+ return {
145
+ ...context,
146
+ ai: {
147
+ module,
148
+ agentService,
149
+ route: {
150
+ name: Generator.toFunctionName('ai'),
151
+ location: Generator.toBackendModuleLocation('@ai/ai.route'),
152
+ _filePath: Generator.toFilePath('./ai.route.ts'),
153
+ },
154
+ },
155
+ };
156
+ },
157
+ generate: (context) => {
158
+ const vfsSrc = new Generator.VirtualFileSystem();
159
+ vfsSrc.write(Generator.toLocalFile(context.ai.module), (0, ai_module_generator_1.generateAiModule)(context));
160
+ vfsSrc.write(Generator.toLocalFile(context.ai.agentService), (0, ai_agent_service_generator_1.generateAiAgentService)({ context }));
161
+ vfsSrc.write(Generator.toFilePath('./ai.route.ts'), (0, ai_route_generator_1.generateAiRoute)());
162
+ vfsSrc.write(Generator.toFilePath('./ai-cache.service.ts'), (0, ai_cache_service_generator_1.generateAiCacheService)());
163
+ vfsSrc.write(Generator.toFilePath('./ai-config.ts'), (0, ai_config_generator_1.generateAiConfig)());
164
+ vfsSrc.write(Generator.toFilePath('./ai-tools.service.ts'), (0, ai_tools_service_generator_1.generateAiToolsService)({ context }));
165
+ vfsSrc.write(Generator.toFilePath('./model-provider/model-provider.interface.ts'), (0, model_provider_interface_generator_1.generateModelProviderInterface)());
166
+ vfsSrc.write(Generator.toFilePath('./model-provider/openai-model-provider.service.ts'), (0, openai_model_provider_service_generator_1.generateOpenAiModelProviderService)());
167
+ const vfs = new Generator.VirtualFileSystem();
168
+ vfs.insertFromVfs({ targetPath: 'src', vfs: vfsSrc });
169
+ vfs.write('/tsconfig.lib.json', Generator.generateTsConfig('ai'));
170
+ context.vfs.insertFromVfs({ targetPath: 'backend/libs/ai', vfs });
171
+ return Promise.resolve(context);
172
+ },
173
+ };
174
+ //# sourceMappingURL=ai.generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.generator.js","sourceRoot":"","sources":["../../src/backend-ai/ai.generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { ContextResult } from '../ai.generator';
2
+ export declare function generateAiAgentService({ context }: {
3
+ context: ContextResult;
4
+ }): string;
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generateAiAgentService = generateAiAgentService;
37
+ const Generator = __importStar(require("@postxl/generator"));
38
+ function generateAiAgentService({ context }) {
39
+ const imports = Generator.ImportGenerator.from(Generator.toFilePath('./ai-agent.service.ts')).addImport({
40
+ from: context.types.location,
41
+ items: [Generator.toTypeName('AskStepInput'), Generator.toTypeName('AskTaskInput')],
42
+ });
43
+ return /* ts */ `
44
+ import { Inject, Injectable } from '@nestjs/common'
45
+
46
+ ${imports.generate()}
47
+
48
+ import { AiCacheService } from './ai-cache.service'
49
+ import { AiConfig } from './ai-config'
50
+ import type {
51
+ AiModelProvider,
52
+ ChatMessage,
53
+ } from './model-provider/model-provider.interface'
54
+ import { AiToolsService, BackendToolExecutionLog } from './ai-tools.service'
55
+
56
+ export const AI_MODEL_PROVIDER = Symbol('AI_MODEL_PROVIDER')
57
+
58
+ type ResolvedToolCall = {
59
+ messages: ChatMessage[]
60
+ key: string
61
+ input: Record<string, unknown>
62
+ pendingToolCallId?: string
63
+ backendLogs: BackendToolExecutionLog[]
64
+ }
65
+
66
+ @Injectable()
67
+ export class AiAgentService {
68
+ private readonly systemPrompt = [
69
+ 'You are an AI assistant controlling tools to satisfy a user task.',
70
+ 'Choose one tool call at a time and stop once task is completed.',
71
+ 'Call done when task is complete or no further progress can be made.',
72
+ 'Do not repeat the same tool twice in a row.',
73
+ 'If required information is missing or ambiguous, call ask-user with a clear question and wait for the answer.',
74
+ ].join('\\n')
75
+
76
+ constructor(
77
+ private readonly cache: AiCacheService,
78
+ private readonly aiConfig: AiConfig,
79
+ private readonly tools: AiToolsService,
80
+ @Inject(AI_MODEL_PROVIDER) private readonly modelProvider: AiModelProvider,
81
+ ) {}
82
+
83
+ public async startExecution({ task, tools }: AskTaskInput, actorId: unknown) {
84
+ const actorKey = this.toActorKey(actorId)
85
+ if (!this.aiConfig.config.enabled) {
86
+ throw new Error('AI feature is disabled')
87
+ }
88
+
89
+ this.cache.pruneConversationCache()
90
+
91
+ const messages: ChatMessage[] = [
92
+ { role: 'system', content: this.systemPrompt },
93
+ { role: 'user', content: task },
94
+ ]
95
+
96
+ const resolved = await this.resolveNextToolCall({ messages, tools, actorKey })
97
+
98
+ const conversation = resolved.key !== 'done' && resolved.pendingToolCallId
99
+ ? this.cache.createConversation({
100
+ actorKey,
101
+ pendingToolCallId: resolved.pendingToolCallId,
102
+ messages: resolved.messages,
103
+ })
104
+ : (globalThis.crypto.randomUUID() as string)
105
+
106
+ return {
107
+ conversation,
108
+ key: resolved.key,
109
+ input: resolved.input,
110
+ backendLogs: resolved.backendLogs,
111
+ }
112
+ }
113
+
114
+ public async continueExecution({ conversation, output, tools }: AskStepInput, actorId: unknown) {
115
+ const actorKey = this.toActorKey(actorId)
116
+ const history = this.cache.getConversation(conversation, actorKey)
117
+
118
+ if (!history?.pendingToolCallId) {
119
+ throw new Error('Conversation not found')
120
+ }
121
+
122
+ const toolOutput = output ? JSON.stringify(output) : JSON.stringify({ ok: true })
123
+
124
+ const messages: ChatMessage[] = [
125
+ ...history.messages,
126
+ {
127
+ role: 'tool',
128
+ tool_call_id: history.pendingToolCallId,
129
+ content: toolOutput,
130
+ },
131
+ ]
132
+
133
+ const resolved = await this.resolveNextToolCall({ messages, tools, actorKey })
134
+
135
+ if (resolved.key === 'done') {
136
+ this.cache.deleteConversation(conversation, actorKey)
137
+ } else if (resolved.pendingToolCallId) {
138
+ this.cache.upsertConversation(conversation, {
139
+ actorKey,
140
+ pendingToolCallId: resolved.pendingToolCallId,
141
+ messages: resolved.messages,
142
+ })
143
+ }
144
+
145
+ return {
146
+ conversation,
147
+ key: resolved.key,
148
+ input: resolved.input,
149
+ backendLogs: resolved.backendLogs,
150
+ }
151
+ }
152
+
153
+ public cancelExecution(conversation: string, actorId: unknown) {
154
+ const actorKey = this.toActorKey(actorId)
155
+ return this.cache.deleteConversation(conversation, actorKey)
156
+ }
157
+
158
+ private async resolveNextToolCall({
159
+ messages,
160
+ tools,
161
+ actorKey,
162
+ }: {
163
+ messages: ChatMessage[]
164
+ tools: AskTaskInput['tools']
165
+ actorKey: string
166
+ }): Promise<ResolvedToolCall> {
167
+ const normalizedFrontendTools = this.tools.normalizeFrontendTools(tools)
168
+ this.tools.assertNoToolKeyCollision(normalizedFrontendTools)
169
+
170
+ const conversationMessages = [...messages]
171
+ const backendLogs: BackendToolExecutionLog[] = []
172
+
173
+ for (let step = 0; step < this.aiConfig.config.maxInternalToolSteps; step += 1) {
174
+ this.cache.assertWithinRateLimit(actorKey)
175
+
176
+ const providerResult = await this.modelProvider.requestSingleToolCall({
177
+ messages: conversationMessages,
178
+ tools: this.tools.buildTools(normalizedFrontendTools),
179
+ })
180
+
181
+ conversationMessages.push(providerResult.assistantMessage)
182
+
183
+ const parsedInput = this.parseArguments(providerResult.toolCall.function.arguments)
184
+
185
+ if (providerResult.toolCall.function.name === 'done') {
186
+ return {
187
+ messages: conversationMessages,
188
+ key: 'done',
189
+ input: parsedInput,
190
+ backendLogs,
191
+ }
192
+ }
193
+
194
+ const backendTool = this.tools.getBackendTool(providerResult.toolCall.function.name)
195
+ if (!backendTool) {
196
+ return {
197
+ messages: conversationMessages,
198
+ key: providerResult.toolCall.function.name,
199
+ input: parsedInput,
200
+ pendingToolCallId: providerResult.toolCall.id,
201
+ backendLogs,
202
+ }
203
+ }
204
+
205
+ const toolLabel = backendTool.definition.label
206
+
207
+ try {
208
+ const output = (await backendTool.execute(parsedInput, { actorKey })) ?? { ok: true }
209
+ backendLogs.push({
210
+ toolKey: backendTool.definition.key,
211
+ status: 'done',
212
+ text: 'Executed backend tool: ' + toolLabel,
213
+ })
214
+
215
+ conversationMessages.push({
216
+ role: 'tool',
217
+ tool_call_id: providerResult.toolCall.id,
218
+ content: JSON.stringify(output),
219
+ })
220
+ } catch (error) {
221
+ const message = error instanceof Error ? error.message : String(error)
222
+ backendLogs.push({
223
+ toolKey: backendTool.definition.key,
224
+ status: 'error',
225
+ text: 'Backend tool failed: ' + toolLabel + ' (' + message + ')',
226
+ })
227
+
228
+ conversationMessages.push({
229
+ role: 'tool',
230
+ tool_call_id: providerResult.toolCall.id,
231
+ content: JSON.stringify({ ok: false, error: message }),
232
+ })
233
+ }
234
+ }
235
+
236
+ throw new Error('AI exceeded backend tool iteration limit')
237
+ }
238
+
239
+ private parseArguments(argumentsJson: string): Record<string, unknown> {
240
+ try {
241
+ const parsed = JSON.parse(argumentsJson)
242
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
243
+ return parsed as Record<string, unknown>
244
+ }
245
+
246
+ console.warn('AI action arguments must be a JSON object. Received:', parsed)
247
+ return {}
248
+ } catch (error) {
249
+ console.warn('Failed to parse AI action arguments:', error)
250
+ return {}
251
+ }
252
+ }
253
+
254
+ private toActorKey(userId: unknown): string {
255
+ if (typeof userId === 'string' && userId.length > 0) {
256
+ return userId
257
+ }
258
+
259
+ return String(userId ?? 'unknown-user')
260
+ }
261
+ }
262
+ `;
263
+ }
264
+ //# sourceMappingURL=ai-agent-service.generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-agent-service.generator.js","sourceRoot":"","sources":["../../../src/backend-ai/generators/ai-agent-service.generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,wDAkOC;AAtOD,6DAA8C;AAI9C,SAAgB,sBAAsB,CAAC,EAAE,OAAO,EAA8B;IAC5E,MAAM,OAAO,GAAG,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC,CAAC,SAAS,CAAC;QACtG,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ;QAC5B,KAAK,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;KACpF,CAAC,CAAA;IAEF,OAAO,QAAQ,CAAC;;;EAGhB,OAAO,CAAC,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwNnB,CAAA;AACD,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function generateAiCacheService(): string;
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateAiCacheService = generateAiCacheService;
4
+ function generateAiCacheService() {
5
+ return /* ts */ `
6
+ import { Injectable } from '@nestjs/common'
7
+
8
+ import { AiConfig } from './ai-config'
9
+ import type { ChatMessage } from './model-provider/model-provider.interface'
10
+
11
+ export type ConversationState = {
12
+ actorKey: string
13
+ pendingToolCallId?: string
14
+ messages: ChatMessage[]
15
+ expiresAt: number
16
+ updatedAt: number
17
+ }
18
+
19
+ @Injectable()
20
+ export class AiCacheService {
21
+ private readonly conversationCache = new Map<string, ConversationState>()
22
+ private readonly actorRateLimit = new Map<string, { windowStart: number; requestCount: number }>()
23
+
24
+ constructor(private readonly aiConfig: AiConfig) {}
25
+
26
+ public createConversation(value: { actorKey: string; pendingToolCallId?: string; messages: ChatMessage[] }): string {
27
+ const conversation = globalThis.crypto.randomUUID() as string
28
+ this.upsertConversation(conversation, value)
29
+ return conversation
30
+ }
31
+
32
+ public upsertConversation(
33
+ conversation: string,
34
+ value: { actorKey: string; pendingToolCallId?: string; messages: ChatMessage[] },
35
+ ) {
36
+ const now = Date.now()
37
+ this.pruneConversationCache(now)
38
+
39
+ this.conversationCache.set(conversation, {
40
+ ...value,
41
+ updatedAt: now,
42
+ expiresAt: now + this.aiConfig.config.conversationTtlMs,
43
+ })
44
+ }
45
+
46
+ public getConversation(conversation: string, actorKey: string): ConversationState | undefined {
47
+ const now = Date.now()
48
+ const value = this.conversationCache.get(conversation)
49
+ if (!value) {
50
+ return undefined
51
+ }
52
+ if (value.actorKey !== actorKey) {
53
+ return undefined
54
+ }
55
+ if (value.expiresAt <= now) {
56
+ this.conversationCache.delete(conversation)
57
+ return undefined
58
+ }
59
+
60
+ value.updatedAt = now
61
+ value.expiresAt = now + this.aiConfig.config.conversationTtlMs
62
+ this.conversationCache.set(conversation, value)
63
+ return value
64
+ }
65
+
66
+ public deleteConversation(conversation: string, actorKey: string): boolean {
67
+ const history = this.conversationCache.get(conversation)
68
+ if (history?.actorKey !== actorKey) {
69
+ return false
70
+ }
71
+
72
+ return this.conversationCache.delete(conversation)
73
+ }
74
+
75
+ public assertWithinRateLimit(actorKey: string, now = Date.now()) {
76
+ const current = this.actorRateLimit.get(actorKey)
77
+ if (!current || now - current.windowStart >= this.aiConfig.config.rateLimitWindowMs) {
78
+ this.actorRateLimit.set(actorKey, { windowStart: now, requestCount: 1 })
79
+ return
80
+ }
81
+
82
+ current.requestCount += 1
83
+ if (current.requestCount > this.aiConfig.config.rateLimitMaxRequests) {
84
+ throw new Error('AI rate limit exceeded for current user. Please retry shortly.')
85
+ }
86
+
87
+ this.actorRateLimit.set(actorKey, current)
88
+ }
89
+
90
+ public pruneConversationCache(now = Date.now()) {
91
+ for (const [conversation, value] of this.conversationCache.entries()) {
92
+ if (value.expiresAt <= now) {
93
+ this.conversationCache.delete(conversation)
94
+ }
95
+ }
96
+
97
+ if (this.conversationCache.size <= this.aiConfig.config.conversationCacheMaxSize) {
98
+ return
99
+ }
100
+
101
+ const entries = [...this.conversationCache.entries()].sort((left, right) => left[1].updatedAt - right[1].updatedAt)
102
+ const excess = this.conversationCache.size - this.aiConfig.config.conversationCacheMaxSize
103
+ for (const [conversation] of entries.slice(0, excess)) {
104
+ this.conversationCache.delete(conversation)
105
+ }
106
+ }
107
+ }
108
+ `;
109
+ }
110
+ //# sourceMappingURL=ai-cache-service.generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-cache-service.generator.js","sourceRoot":"","sources":["../../../src/backend-ai/generators/ai-cache-service.generator.ts"],"names":[],"mappings":";;AAAA,wDAyGC;AAzGD,SAAgB,sBAAsB;IACpC,OAAO,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuGjB,CAAA;AACD,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function generateAiConfig(): string;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateAiConfig = generateAiConfig;
4
+ function generateAiConfig() {
5
+ return /* ts */ `
6
+ import { Injectable } from '@nestjs/common'
7
+
8
+ export type Config = Readonly<{
9
+ enabled: boolean
10
+ openAIApiKey?: string
11
+ openAIApiKeys?: string
12
+ model: string
13
+ timeoutMs: number
14
+ rateLimitWindowMs: number
15
+ rateLimitMaxRequests: number
16
+ conversationTtlMs: number
17
+ conversationCacheMaxSize: number
18
+ maxInternalToolSteps: number
19
+ }>
20
+
21
+ @Injectable()
22
+ export class AiConfig {
23
+ constructor(public readonly config: Config) {}
24
+ }
25
+ `;
26
+ }
27
+ //# sourceMappingURL=ai-config.generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-config.generator.js","sourceRoot":"","sources":["../../../src/backend-ai/generators/ai-config.generator.ts"],"names":[],"mappings":";;AAAA,4CAsBC;AAtBD,SAAgB,gBAAgB;IAC9B,OAAO,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;CAoBjB,CAAA;AACD,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { ContextResult } from '../ai.generator';
2
+ export declare function generateAiModule(context: ContextResult): string;