@postxl/generators 1.12.2 → 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.
- package/dist/backend-ai/ai.generator.d.ts +18 -0
- package/dist/backend-ai/ai.generator.js +174 -0
- package/dist/backend-ai/ai.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-agent-service.generator.d.ts +4 -0
- package/dist/backend-ai/generators/ai-agent-service.generator.js +264 -0
- package/dist/backend-ai/generators/ai-agent-service.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-cache-service.generator.d.ts +1 -0
- package/dist/backend-ai/generators/ai-cache-service.generator.js +110 -0
- package/dist/backend-ai/generators/ai-cache-service.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-config.generator.d.ts +1 -0
- package/dist/backend-ai/generators/ai-config.generator.js +27 -0
- package/dist/backend-ai/generators/ai-config.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-module.generator.d.ts +2 -0
- package/dist/backend-ai/generators/ai-module.generator.js +89 -0
- package/dist/backend-ai/generators/ai-module.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-route.generator.d.ts +1 -0
- package/dist/backend-ai/generators/ai-route.generator.js +29 -0
- package/dist/backend-ai/generators/ai-route.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-tools-service.generator.d.ts +4 -0
- package/dist/backend-ai/generators/ai-tools-service.generator.js +222 -0
- package/dist/backend-ai/generators/ai-tools-service.generator.js.map +1 -0
- package/dist/backend-ai/generators/model-provider-interface.generator.d.ts +1 -0
- package/dist/backend-ai/generators/model-provider-interface.generator.js +48 -0
- package/dist/backend-ai/generators/model-provider-interface.generator.js.map +1 -0
- package/dist/backend-ai/generators/openai-model-provider-service.generator.d.ts +1 -0
- package/dist/backend-ai/generators/openai-model-provider-service.generator.js +128 -0
- package/dist/backend-ai/generators/openai-model-provider-service.generator.js.map +1 -0
- package/dist/backend-ai/index.d.ts +4 -0
- package/dist/backend-ai/index.js +40 -0
- package/dist/backend-ai/index.js.map +1 -0
- package/dist/backend-core/generators/main.generator.js +4 -3
- package/dist/backend-core/generators/main.generator.js.map +1 -1
- package/dist/backend-e2e/backend-e2e.generator.js +4 -4
- package/dist/backend-e2e/backend-e2e.generator.js.map +1 -1
- package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js +1 -1
- package/dist/backend-router-trpc/generators/app-routes.generator.js +3 -1
- package/dist/backend-router-trpc/generators/app-routes.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js +3 -0
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-router-module.generator.js +2 -1
- package/dist/backend-router-trpc/generators/trpc-router-module.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js +7 -1
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js.map +1 -1
- package/dist/backend-router-trpc/router-trpc.generator.d.ts +2 -1
- package/dist/backend-router-trpc/router-trpc.generator.js +2 -0
- package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
- package/dist/backend-seed/seed.generator.js +10 -1
- package/dist/backend-seed/seed.generator.js.map +1 -1
- package/dist/base/template/scripts/setup.sh +9 -4
- package/dist/base/template/sonar-project.properties +9 -1
- package/dist/devops/generators/bitbucket-pipelines-yml.generator.js +1 -0
- package/dist/devops/generators/bitbucket-pipelines-yml.generator.js.map +1 -1
- package/dist/devops/generators/e2e-yml.generator.js +35 -10
- package/dist/devops/generators/e2e-yml.generator.js.map +1 -1
- package/dist/devops/generators/jenkinsfile.generator.js +25 -1
- package/dist/devops/generators/jenkinsfile.generator.js.map +1 -1
- package/dist/e2e/template/e2e/specs/example.spec.ts-snapshots/Navigate-to-homepage-and-take-snapshot-1-chromium-linux.png +0 -0
- package/dist/frontend-actions/actions.generator.d.ts +9 -0
- package/dist/frontend-actions/actions.generator.js +111 -0
- package/dist/frontend-actions/actions.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-action-text.utils.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-action-text.utils.generator.js +52 -0
- package/dist/frontend-actions/generators/ai-action-text.utils.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-assistant-store.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-assistant-store.generator.js +230 -0
- package/dist/frontend-actions/generators/ai-assistant-store.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-sidebar-content.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-sidebar-content.generator.js +139 -0
- package/dist/frontend-actions/generators/ai-sidebar-content.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-sidepane.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-sidepane.generator.js +98 -0
- package/dist/frontend-actions/generators/ai-sidepane.generator.js.map +1 -0
- package/dist/frontend-actions/generators/base-global-actions.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/base-global-actions.generator.js +405 -0
- package/dist/frontend-actions/generators/base-global-actions.generator.js.map +1 -0
- package/dist/frontend-actions/generators/command-palette-action.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/command-palette-action.generator.js +87 -0
- package/dist/frontend-actions/generators/command-palette-action.generator.js.map +1 -0
- package/dist/frontend-actions/generators/command-palette-store.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/command-palette-store.generator.js +288 -0
- package/dist/frontend-actions/generators/command-palette-store.generator.js.map +1 -0
- package/dist/frontend-actions/generators/command-palette.generator.d.ts +5 -0
- package/dist/frontend-actions/generators/command-palette.generator.js +332 -0
- package/dist/frontend-actions/generators/command-palette.generator.js.map +1 -0
- package/dist/frontend-actions/generators/filter-utils.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/filter-utils.generator.js +50 -0
- package/dist/frontend-actions/generators/filter-utils.generator.js.map +1 -0
- package/dist/frontend-actions/generators/sidepanel-toggle.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/sidepanel-toggle.generator.js +37 -0
- package/dist/frontend-actions/generators/sidepanel-toggle.generator.js.map +1 -0
- package/dist/frontend-actions/index.d.ts +4 -0
- package/dist/frontend-actions/index.js +40 -0
- package/dist/frontend-actions/index.js.map +1 -0
- package/dist/frontend-admin/admin.generator.d.ts +3 -1
- package/dist/frontend-admin/admin.generator.js +8 -1
- package/dist/frontend-admin/admin.generator.js.map +1 -1
- package/dist/frontend-admin/generators/admin-global-actions.generator.d.ts +4 -0
- package/dist/frontend-admin/generators/admin-global-actions.generator.js +152 -0
- package/dist/frontend-admin/generators/admin-global-actions.generator.js.map +1 -0
- package/dist/frontend-admin/generators/comment-sidebar.generator.js +5 -3
- package/dist/frontend-admin/generators/comment-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/detail-sidebar.generator.js +40 -24
- package/dist/frontend-admin/generators/detail-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/model-admin-page.generator.js +172 -11
- package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
- package/dist/frontend-admin/utils.d.ts +1 -0
- package/dist/frontend-admin/utils.js +1 -0
- package/dist/frontend-admin/utils.js.map +1 -1
- package/dist/frontend-core/generators/tsconfig.generator.js +1 -0
- package/dist/frontend-core/generators/tsconfig.generator.js.map +1 -1
- package/dist/frontend-core/template/.env.example +3 -0
- package/dist/frontend-core/template/src/components/admin/table-view-panel.tsx +22 -4
- package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +1 -1
- package/dist/frontend-core/template/src/lib/color.ts +6 -3
- package/dist/frontend-core/template/src/lib/config.ts +3 -1
- package/dist/frontend-tables/generators/model-table.generator.js +13 -0
- package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
- package/dist/generators.js +4 -0
- package/dist/generators.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/types/template/ai.types.ts +34 -0
- package/dist/types/types.generator.js +1 -0
- package/dist/types/types.generator.js.map +1 -1
- 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,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"}
|