@kapeta/local-cluster-service 0.48.5 → 0.49.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/CHANGELOG.md +7 -0
- package/dist/cjs/src/assetManager.d.ts +1 -1
- package/dist/cjs/src/assetManager.js +5 -3
- package/dist/cjs/src/filesystem/routes.js +10 -0
- package/dist/cjs/src/storm/codegen.d.ts +2 -1
- package/dist/cjs/src/storm/codegen.js +28 -9
- package/dist/cjs/src/storm/event-parser.d.ts +1 -1
- package/dist/cjs/src/storm/event-parser.js +3 -1
- package/dist/cjs/src/storm/events.d.ts +12 -1
- package/dist/cjs/src/storm/routes.js +26 -1
- package/dist/cjs/src/storm/stream.d.ts +6 -0
- package/dist/esm/src/assetManager.d.ts +1 -1
- package/dist/esm/src/assetManager.js +5 -3
- package/dist/esm/src/filesystem/routes.js +10 -0
- package/dist/esm/src/storm/codegen.d.ts +2 -1
- package/dist/esm/src/storm/codegen.js +28 -9
- package/dist/esm/src/storm/event-parser.d.ts +1 -1
- package/dist/esm/src/storm/event-parser.js +3 -1
- package/dist/esm/src/storm/events.d.ts +12 -1
- package/dist/esm/src/storm/routes.js +26 -1
- package/dist/esm/src/storm/stream.d.ts +6 -0
- package/package.json +1 -1
- package/src/assetManager.ts +6 -4
- package/src/filesystem/routes.ts +10 -0
- package/src/storm/codegen.ts +53 -21
- package/src/storm/event-parser.ts +4 -2
- package/src/storm/events.ts +17 -4
- package/src/storm/routes.ts +39 -2
- package/src/storm/stream.ts +7 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# [0.49.0](https://github.com/kapetacom/local-cluster-service/compare/v0.48.5...v0.49.0) (2024-06-05)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Implement ability to save ai generated plan ([#161](https://github.com/kapetacom/local-cluster-service/issues/161)) ([8223757](https://github.com/kapetacom/local-cluster-service/commit/8223757ff90ee6b5e1b7f171fc724d9877051df2))
|
7
|
+
|
1
8
|
## [0.48.5](https://github.com/kapetacom/local-cluster-service/compare/v0.48.4...v0.48.5) (2024-06-04)
|
2
9
|
|
3
10
|
|
@@ -28,7 +28,7 @@ declare class AssetManager {
|
|
28
28
|
getPlan(ref: string, noCache?: boolean): Promise<Plan>;
|
29
29
|
getBlockInstance(systemId: string, instanceId: string): Promise<BlockInstance>;
|
30
30
|
getAsset(ref: string, noCache?: boolean, autoFetch?: boolean): Promise<EnrichedAsset | undefined>;
|
31
|
-
createAsset(path: string, yaml: BlockDefinition, sourceOfChange?: SourceOfChange): Promise<EnrichedAsset[]>;
|
31
|
+
createAsset(path: string, yaml: BlockDefinition, sourceOfChange?: SourceOfChange, codegen?: boolean): Promise<EnrichedAsset[]>;
|
32
32
|
updateAsset(ref: string, yaml: Definition, sourceOfChange?: SourceOfChange): Promise<void>;
|
33
33
|
importFile(filePath: string): Promise<EnrichedAsset[]>;
|
34
34
|
unregisterAsset(ref: string): Promise<void>;
|
@@ -142,7 +142,7 @@ class AssetManager {
|
|
142
142
|
}
|
143
143
|
return undefined;
|
144
144
|
}
|
145
|
-
async createAsset(path, yaml, sourceOfChange = 'filesystem') {
|
145
|
+
async createAsset(path, yaml, sourceOfChange = 'filesystem', codegen = true) {
|
146
146
|
if (await fs_extra_1.default.pathExists(path)) {
|
147
147
|
throw new Error('File already exists: ' + path);
|
148
148
|
}
|
@@ -159,9 +159,11 @@ class AssetManager {
|
|
159
159
|
cacheManager_1.cacheManager.set(key, a, CACHE_TTL);
|
160
160
|
});
|
161
161
|
definitionsManager_1.definitionsManager.clearCache();
|
162
|
-
console.log(`Created asset at: ${path}`);
|
163
162
|
const ref = `kapeta://${yaml.metadata.name}:local`;
|
164
|
-
|
163
|
+
console.log(`Created asset ${ref} at: ${path}`);
|
164
|
+
if (codegen) {
|
165
|
+
await this.maybeGenerateCode(ref, path, yaml);
|
166
|
+
}
|
165
167
|
return asset;
|
166
168
|
}
|
167
169
|
async updateAsset(ref, yaml, sourceOfChange = 'filesystem') {
|
@@ -11,6 +11,7 @@ const express_promise_router_1 = __importDefault(require("express-promise-router
|
|
11
11
|
const stringBody_1 = require("../middleware/stringBody");
|
12
12
|
const filesystemManager_1 = require("../filesystemManager");
|
13
13
|
const cors_1 = require("../middleware/cors");
|
14
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
14
15
|
let router = (0, express_promise_router_1.default)();
|
15
16
|
router.use('/', cors_1.corsHandler);
|
16
17
|
router.get('/root', (req, res) => {
|
@@ -81,6 +82,15 @@ router.get('/readfile', async (req, res) => {
|
|
81
82
|
res.status(400).send({ error: '' + err });
|
82
83
|
}
|
83
84
|
});
|
85
|
+
router.get('/exists', async (req, res) => {
|
86
|
+
let pathArg = req.query.path;
|
87
|
+
try {
|
88
|
+
res.send(await fs_extra_1.default.pathExists(pathArg));
|
89
|
+
}
|
90
|
+
catch (err) {
|
91
|
+
res.status(400).send({ error: '' + err });
|
92
|
+
}
|
93
|
+
});
|
84
94
|
router.put('/mkdir', async (req, res) => {
|
85
95
|
let pathArg = req.query.path;
|
86
96
|
try {
|
@@ -11,7 +11,8 @@ export declare class StormCodegen {
|
|
11
11
|
private readonly out;
|
12
12
|
private readonly events;
|
13
13
|
private readonly tmpDir;
|
14
|
-
|
14
|
+
private readonly conversationId;
|
15
|
+
constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
|
15
16
|
process(): Promise<void>;
|
16
17
|
getStream(): StormStream;
|
17
18
|
private handleTemplateFileOutput;
|
@@ -36,26 +36,31 @@ const codeGeneratorManager_1 = require("../codeGeneratorManager");
|
|
36
36
|
const stormClient_1 = require("./stormClient");
|
37
37
|
const event_parser_1 = require("./event-parser");
|
38
38
|
const stream_1 = require("./stream");
|
39
|
+
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
39
40
|
const promises_1 = require("fs/promises");
|
40
41
|
const path_1 = __importStar(require("path"));
|
41
42
|
const node_os_1 = __importDefault(require("node:os"));
|
42
43
|
const fs_1 = require("fs");
|
44
|
+
const path_2 = __importDefault(require("path"));
|
43
45
|
class StormCodegen {
|
44
46
|
userPrompt;
|
45
47
|
blocks;
|
46
48
|
out = new stream_1.StormStream();
|
47
49
|
events;
|
48
50
|
tmpDir;
|
49
|
-
|
51
|
+
conversationId;
|
52
|
+
constructor(conversationId, userPrompt, blocks, events) {
|
50
53
|
this.userPrompt = userPrompt;
|
51
54
|
this.blocks = blocks;
|
52
55
|
this.events = events;
|
53
|
-
this.tmpDir = node_os_1.default.tmpdir();
|
56
|
+
this.tmpDir = path_2.default.join(node_os_1.default.tmpdir(), conversationId);
|
57
|
+
this.conversationId = conversationId;
|
54
58
|
}
|
55
59
|
async process() {
|
56
|
-
|
57
|
-
|
58
|
-
}
|
60
|
+
const promises = this.blocks.map((block) => {
|
61
|
+
return this.processBlockCode(block);
|
62
|
+
});
|
63
|
+
await Promise.all(promises);
|
59
64
|
this.out.end();
|
60
65
|
}
|
61
66
|
getStream() {
|
@@ -119,7 +124,7 @@ class StormCodegen {
|
|
119
124
|
}
|
120
125
|
const allFiles = this.toStormFiles(generatedResult);
|
121
126
|
// Send all the non-ai files to the stream
|
122
|
-
this.emitFiles(block.uri, block.aiName, allFiles);
|
127
|
+
this.emitFiles((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, allFiles);
|
123
128
|
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && file.type !== codegen_1.AIFileTypes.WEB_SCREEN);
|
124
129
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
125
130
|
if (uiTemplates.length > 0) {
|
@@ -131,7 +136,7 @@ class StormCodegen {
|
|
131
136
|
prompt: this.userPrompt,
|
132
137
|
});
|
133
138
|
uiStream.on('data', (evt) => {
|
134
|
-
this.handleUiOutput(block.uri, block.aiName, evt);
|
139
|
+
this.handleUiOutput((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, evt);
|
135
140
|
});
|
136
141
|
await uiStream.waitForDone();
|
137
142
|
}
|
@@ -140,7 +145,7 @@ class StormCodegen {
|
|
140
145
|
// Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
|
141
146
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
142
147
|
if (serviceFiles.length > 0) {
|
143
|
-
await this.processTemplates(block.uri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
148
|
+
await this.processTemplates((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
144
149
|
}
|
145
150
|
const basePath = this.getBasePath(block.content.metadata.name);
|
146
151
|
for (const serviceFile of serviceFiles) {
|
@@ -158,6 +163,18 @@ class StormCodegen {
|
|
158
163
|
const filesToBeFixed = serviceFiles.concat(contextFiles);
|
159
164
|
const codeGenerator = new codegen_1.BlockCodeGenerator(block.content);
|
160
165
|
await this.verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles);
|
166
|
+
const blockRef = block.uri;
|
167
|
+
this.out.emit('data', {
|
168
|
+
type: 'BLOCK_READY',
|
169
|
+
reason: 'Block ready',
|
170
|
+
created: Date.now(),
|
171
|
+
payload: {
|
172
|
+
path: basePath,
|
173
|
+
blockName: block.aiName,
|
174
|
+
blockRef,
|
175
|
+
instanceId: event_parser_1.StormEventParser.toInstanceIdFromRef(blockRef),
|
176
|
+
},
|
177
|
+
});
|
161
178
|
}
|
162
179
|
async verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, knownFiles) {
|
163
180
|
let attempts = 0;
|
@@ -186,7 +203,9 @@ class StormCodegen {
|
|
186
203
|
}
|
187
204
|
// read the content of the file
|
188
205
|
const content = (0, fs_1.readFileSync)((0, path_1.join)(basePath, eventFileName), 'utf8');
|
189
|
-
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
206
|
+
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
207
|
+
.map((e) => e.filename)
|
208
|
+
.join('\n')}\n---\n${content}`;
|
190
209
|
console.log(`trying to fix the code in ${eventFileName}`);
|
191
210
|
console.debug(`with the fix:\n${fix}`);
|
192
211
|
const code = this.codeFix(fix);
|
@@ -6,7 +6,7 @@ import { StormEvent } from './events';
|
|
6
6
|
import { BlockDefinition, Plan } from '@kapeta/schemas';
|
7
7
|
import { KapetaURI } from '@kapeta/nodejs-utils';
|
8
8
|
export interface BlockDefinitionInfo {
|
9
|
-
uri:
|
9
|
+
uri: string;
|
10
10
|
content: BlockDefinition;
|
11
11
|
aiName: string;
|
12
12
|
}
|
@@ -315,6 +315,8 @@ class StormEventParser {
|
|
315
315
|
name: planRef.fullName,
|
316
316
|
title: this.planName,
|
317
317
|
description: this.planDescription,
|
318
|
+
structure: 'mono',
|
319
|
+
visibility: 'private',
|
318
320
|
},
|
319
321
|
spec: {
|
320
322
|
blocks,
|
@@ -331,7 +333,7 @@ class StormEventParser {
|
|
331
333
|
Object.entries(this.blocks).forEach(([, blockInfo]) => {
|
332
334
|
const blockRef = StormEventParser.toRef(handle, blockInfo.name);
|
333
335
|
const blockDefinitionInfo = {
|
334
|
-
uri: blockRef,
|
336
|
+
uri: blockRef.toNormalizedString(),
|
335
337
|
aiName: blockInfo.name,
|
336
338
|
content: {
|
337
339
|
kind: this.toBlockKind(blockInfo.type),
|
@@ -163,6 +163,17 @@ export interface StormEventFile {
|
|
163
163
|
instanceId: string;
|
164
164
|
};
|
165
165
|
}
|
166
|
+
export interface StormEventBlockReady {
|
167
|
+
type: 'BLOCK_READY';
|
168
|
+
reason: string;
|
169
|
+
created: number;
|
170
|
+
payload: {
|
171
|
+
path: string;
|
172
|
+
blockName: string;
|
173
|
+
blockRef: string;
|
174
|
+
instanceId: string;
|
175
|
+
};
|
176
|
+
}
|
166
177
|
export interface StormEventDone {
|
167
178
|
type: 'DONE';
|
168
179
|
created: number;
|
@@ -173,4 +184,4 @@ export interface StormEventDefinitionChange {
|
|
173
184
|
created: number;
|
174
185
|
payload: StormDefinitions;
|
175
186
|
}
|
176
|
-
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix;
|
187
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventBlockReady;
|
@@ -8,11 +8,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
8
|
};
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
10
10
|
const express_promise_router_1 = __importDefault(require("express-promise-router"));
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
11
12
|
const cors_1 = require("../middleware/cors");
|
12
13
|
const stringBody_1 = require("../middleware/stringBody");
|
13
14
|
const stormClient_1 = require("./stormClient");
|
14
15
|
const event_parser_1 = require("./event-parser");
|
15
16
|
const codegen_1 = require("./codegen");
|
17
|
+
const assetManager_1 = require("../assetManager");
|
18
|
+
const path_1 = __importDefault(require("path"));
|
16
19
|
const router = (0, express_promise_router_1.default)();
|
17
20
|
router.use('/', cors_1.corsHandler);
|
18
21
|
router.use('/', stringBody_1.stringBody);
|
@@ -47,7 +50,7 @@ router.post('/:handle/all', async (req, res) => {
|
|
47
50
|
const result = eventParser.toResult(handle);
|
48
51
|
sendDefinitions(res, result);
|
49
52
|
if (!req.query.skipCodegen) {
|
50
|
-
const stormCodegen = new codegen_1.StormCodegen(aiRequest.prompt, result.blocks, eventParser.getEvents());
|
53
|
+
const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents());
|
51
54
|
const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
|
52
55
|
await stormCodegen.process();
|
53
56
|
await codegenPromise;
|
@@ -59,6 +62,28 @@ router.post('/:handle/all', async (req, res) => {
|
|
59
62
|
res.end();
|
60
63
|
}
|
61
64
|
});
|
65
|
+
router.post('/block/create', async (req, res) => {
|
66
|
+
const createRequest = JSON.parse(req.stringBody ?? '{}');
|
67
|
+
try {
|
68
|
+
const ymlPath = path_1.default.join(createRequest.newPath, 'kapeta.yml');
|
69
|
+
console.log('Creating block at', ymlPath);
|
70
|
+
const [asset] = await assetManager_1.assetManager.createAsset(ymlPath, createRequest.definition);
|
71
|
+
if (await fs_extra_1.default.pathExists(createRequest.tmpPath)) {
|
72
|
+
console.log('Moving block from', createRequest.tmpPath, 'to', createRequest.newPath);
|
73
|
+
await fs_extra_1.default.move(createRequest.tmpPath, createRequest.newPath, {
|
74
|
+
overwrite: true,
|
75
|
+
});
|
76
|
+
console.log('Updating asset', asset.ref);
|
77
|
+
res.send(await assetManager_1.assetManager.updateAsset(asset.ref, createRequest.definition));
|
78
|
+
}
|
79
|
+
else {
|
80
|
+
res.send(asset);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
catch (err) {
|
84
|
+
res.status(500).send({ error: err.message });
|
85
|
+
}
|
86
|
+
});
|
62
87
|
function sendDefinitions(res, result) {
|
63
88
|
sendEvent(res, {
|
64
89
|
type: 'DEFINITION_CHANGE',
|
@@ -6,6 +6,7 @@
|
|
6
6
|
import { EventEmitter } from 'node:events';
|
7
7
|
import { StormEvent } from './events';
|
8
8
|
import { AIFileTypes, GeneratedFile } from '@kapeta/codegen';
|
9
|
+
import { BlockDefinition } from '@kapeta/schemas';
|
9
10
|
export declare class StormStream extends EventEmitter {
|
10
11
|
private conversationId;
|
11
12
|
private lines;
|
@@ -29,6 +30,11 @@ export interface StormContextRequest<T = string> {
|
|
29
30
|
conversationId?: string;
|
30
31
|
prompt: T;
|
31
32
|
}
|
33
|
+
export interface StormCreateBlockRequest {
|
34
|
+
definition: BlockDefinition;
|
35
|
+
tmpPath: string;
|
36
|
+
newPath: string;
|
37
|
+
}
|
32
38
|
export interface StormFileInfo extends GeneratedFile {
|
33
39
|
type: AIFileTypes;
|
34
40
|
}
|
@@ -28,7 +28,7 @@ declare class AssetManager {
|
|
28
28
|
getPlan(ref: string, noCache?: boolean): Promise<Plan>;
|
29
29
|
getBlockInstance(systemId: string, instanceId: string): Promise<BlockInstance>;
|
30
30
|
getAsset(ref: string, noCache?: boolean, autoFetch?: boolean): Promise<EnrichedAsset | undefined>;
|
31
|
-
createAsset(path: string, yaml: BlockDefinition, sourceOfChange?: SourceOfChange): Promise<EnrichedAsset[]>;
|
31
|
+
createAsset(path: string, yaml: BlockDefinition, sourceOfChange?: SourceOfChange, codegen?: boolean): Promise<EnrichedAsset[]>;
|
32
32
|
updateAsset(ref: string, yaml: Definition, sourceOfChange?: SourceOfChange): Promise<void>;
|
33
33
|
importFile(filePath: string): Promise<EnrichedAsset[]>;
|
34
34
|
unregisterAsset(ref: string): Promise<void>;
|
@@ -142,7 +142,7 @@ class AssetManager {
|
|
142
142
|
}
|
143
143
|
return undefined;
|
144
144
|
}
|
145
|
-
async createAsset(path, yaml, sourceOfChange = 'filesystem') {
|
145
|
+
async createAsset(path, yaml, sourceOfChange = 'filesystem', codegen = true) {
|
146
146
|
if (await fs_extra_1.default.pathExists(path)) {
|
147
147
|
throw new Error('File already exists: ' + path);
|
148
148
|
}
|
@@ -159,9 +159,11 @@ class AssetManager {
|
|
159
159
|
cacheManager_1.cacheManager.set(key, a, CACHE_TTL);
|
160
160
|
});
|
161
161
|
definitionsManager_1.definitionsManager.clearCache();
|
162
|
-
console.log(`Created asset at: ${path}`);
|
163
162
|
const ref = `kapeta://${yaml.metadata.name}:local`;
|
164
|
-
|
163
|
+
console.log(`Created asset ${ref} at: ${path}`);
|
164
|
+
if (codegen) {
|
165
|
+
await this.maybeGenerateCode(ref, path, yaml);
|
166
|
+
}
|
165
167
|
return asset;
|
166
168
|
}
|
167
169
|
async updateAsset(ref, yaml, sourceOfChange = 'filesystem') {
|
@@ -11,6 +11,7 @@ const express_promise_router_1 = __importDefault(require("express-promise-router
|
|
11
11
|
const stringBody_1 = require("../middleware/stringBody");
|
12
12
|
const filesystemManager_1 = require("../filesystemManager");
|
13
13
|
const cors_1 = require("../middleware/cors");
|
14
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
14
15
|
let router = (0, express_promise_router_1.default)();
|
15
16
|
router.use('/', cors_1.corsHandler);
|
16
17
|
router.get('/root', (req, res) => {
|
@@ -81,6 +82,15 @@ router.get('/readfile', async (req, res) => {
|
|
81
82
|
res.status(400).send({ error: '' + err });
|
82
83
|
}
|
83
84
|
});
|
85
|
+
router.get('/exists', async (req, res) => {
|
86
|
+
let pathArg = req.query.path;
|
87
|
+
try {
|
88
|
+
res.send(await fs_extra_1.default.pathExists(pathArg));
|
89
|
+
}
|
90
|
+
catch (err) {
|
91
|
+
res.status(400).send({ error: '' + err });
|
92
|
+
}
|
93
|
+
});
|
84
94
|
router.put('/mkdir', async (req, res) => {
|
85
95
|
let pathArg = req.query.path;
|
86
96
|
try {
|
@@ -11,7 +11,8 @@ export declare class StormCodegen {
|
|
11
11
|
private readonly out;
|
12
12
|
private readonly events;
|
13
13
|
private readonly tmpDir;
|
14
|
-
|
14
|
+
private readonly conversationId;
|
15
|
+
constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
|
15
16
|
process(): Promise<void>;
|
16
17
|
getStream(): StormStream;
|
17
18
|
private handleTemplateFileOutput;
|
@@ -36,26 +36,31 @@ const codeGeneratorManager_1 = require("../codeGeneratorManager");
|
|
36
36
|
const stormClient_1 = require("./stormClient");
|
37
37
|
const event_parser_1 = require("./event-parser");
|
38
38
|
const stream_1 = require("./stream");
|
39
|
+
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
39
40
|
const promises_1 = require("fs/promises");
|
40
41
|
const path_1 = __importStar(require("path"));
|
41
42
|
const node_os_1 = __importDefault(require("node:os"));
|
42
43
|
const fs_1 = require("fs");
|
44
|
+
const path_2 = __importDefault(require("path"));
|
43
45
|
class StormCodegen {
|
44
46
|
userPrompt;
|
45
47
|
blocks;
|
46
48
|
out = new stream_1.StormStream();
|
47
49
|
events;
|
48
50
|
tmpDir;
|
49
|
-
|
51
|
+
conversationId;
|
52
|
+
constructor(conversationId, userPrompt, blocks, events) {
|
50
53
|
this.userPrompt = userPrompt;
|
51
54
|
this.blocks = blocks;
|
52
55
|
this.events = events;
|
53
|
-
this.tmpDir = node_os_1.default.tmpdir();
|
56
|
+
this.tmpDir = path_2.default.join(node_os_1.default.tmpdir(), conversationId);
|
57
|
+
this.conversationId = conversationId;
|
54
58
|
}
|
55
59
|
async process() {
|
56
|
-
|
57
|
-
|
58
|
-
}
|
60
|
+
const promises = this.blocks.map((block) => {
|
61
|
+
return this.processBlockCode(block);
|
62
|
+
});
|
63
|
+
await Promise.all(promises);
|
59
64
|
this.out.end();
|
60
65
|
}
|
61
66
|
getStream() {
|
@@ -119,7 +124,7 @@ class StormCodegen {
|
|
119
124
|
}
|
120
125
|
const allFiles = this.toStormFiles(generatedResult);
|
121
126
|
// Send all the non-ai files to the stream
|
122
|
-
this.emitFiles(block.uri, block.aiName, allFiles);
|
127
|
+
this.emitFiles((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, allFiles);
|
123
128
|
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && file.type !== codegen_1.AIFileTypes.WEB_SCREEN);
|
124
129
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
125
130
|
if (uiTemplates.length > 0) {
|
@@ -131,7 +136,7 @@ class StormCodegen {
|
|
131
136
|
prompt: this.userPrompt,
|
132
137
|
});
|
133
138
|
uiStream.on('data', (evt) => {
|
134
|
-
this.handleUiOutput(block.uri, block.aiName, evt);
|
139
|
+
this.handleUiOutput((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, evt);
|
135
140
|
});
|
136
141
|
await uiStream.waitForDone();
|
137
142
|
}
|
@@ -140,7 +145,7 @@ class StormCodegen {
|
|
140
145
|
// Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
|
141
146
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
142
147
|
if (serviceFiles.length > 0) {
|
143
|
-
await this.processTemplates(block.uri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
148
|
+
await this.processTemplates((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
144
149
|
}
|
145
150
|
const basePath = this.getBasePath(block.content.metadata.name);
|
146
151
|
for (const serviceFile of serviceFiles) {
|
@@ -158,6 +163,18 @@ class StormCodegen {
|
|
158
163
|
const filesToBeFixed = serviceFiles.concat(contextFiles);
|
159
164
|
const codeGenerator = new codegen_1.BlockCodeGenerator(block.content);
|
160
165
|
await this.verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles);
|
166
|
+
const blockRef = block.uri;
|
167
|
+
this.out.emit('data', {
|
168
|
+
type: 'BLOCK_READY',
|
169
|
+
reason: 'Block ready',
|
170
|
+
created: Date.now(),
|
171
|
+
payload: {
|
172
|
+
path: basePath,
|
173
|
+
blockName: block.aiName,
|
174
|
+
blockRef,
|
175
|
+
instanceId: event_parser_1.StormEventParser.toInstanceIdFromRef(blockRef),
|
176
|
+
},
|
177
|
+
});
|
161
178
|
}
|
162
179
|
async verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, knownFiles) {
|
163
180
|
let attempts = 0;
|
@@ -186,7 +203,9 @@ class StormCodegen {
|
|
186
203
|
}
|
187
204
|
// read the content of the file
|
188
205
|
const content = (0, fs_1.readFileSync)((0, path_1.join)(basePath, eventFileName), 'utf8');
|
189
|
-
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
206
|
+
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
207
|
+
.map((e) => e.filename)
|
208
|
+
.join('\n')}\n---\n${content}`;
|
190
209
|
console.log(`trying to fix the code in ${eventFileName}`);
|
191
210
|
console.debug(`with the fix:\n${fix}`);
|
192
211
|
const code = this.codeFix(fix);
|
@@ -6,7 +6,7 @@ import { StormEvent } from './events';
|
|
6
6
|
import { BlockDefinition, Plan } from '@kapeta/schemas';
|
7
7
|
import { KapetaURI } from '@kapeta/nodejs-utils';
|
8
8
|
export interface BlockDefinitionInfo {
|
9
|
-
uri:
|
9
|
+
uri: string;
|
10
10
|
content: BlockDefinition;
|
11
11
|
aiName: string;
|
12
12
|
}
|
@@ -315,6 +315,8 @@ class StormEventParser {
|
|
315
315
|
name: planRef.fullName,
|
316
316
|
title: this.planName,
|
317
317
|
description: this.planDescription,
|
318
|
+
structure: 'mono',
|
319
|
+
visibility: 'private',
|
318
320
|
},
|
319
321
|
spec: {
|
320
322
|
blocks,
|
@@ -331,7 +333,7 @@ class StormEventParser {
|
|
331
333
|
Object.entries(this.blocks).forEach(([, blockInfo]) => {
|
332
334
|
const blockRef = StormEventParser.toRef(handle, blockInfo.name);
|
333
335
|
const blockDefinitionInfo = {
|
334
|
-
uri: blockRef,
|
336
|
+
uri: blockRef.toNormalizedString(),
|
335
337
|
aiName: blockInfo.name,
|
336
338
|
content: {
|
337
339
|
kind: this.toBlockKind(blockInfo.type),
|
@@ -163,6 +163,17 @@ export interface StormEventFile {
|
|
163
163
|
instanceId: string;
|
164
164
|
};
|
165
165
|
}
|
166
|
+
export interface StormEventBlockReady {
|
167
|
+
type: 'BLOCK_READY';
|
168
|
+
reason: string;
|
169
|
+
created: number;
|
170
|
+
payload: {
|
171
|
+
path: string;
|
172
|
+
blockName: string;
|
173
|
+
blockRef: string;
|
174
|
+
instanceId: string;
|
175
|
+
};
|
176
|
+
}
|
166
177
|
export interface StormEventDone {
|
167
178
|
type: 'DONE';
|
168
179
|
created: number;
|
@@ -173,4 +184,4 @@ export interface StormEventDefinitionChange {
|
|
173
184
|
created: number;
|
174
185
|
payload: StormDefinitions;
|
175
186
|
}
|
176
|
-
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix;
|
187
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventBlockReady;
|
@@ -8,11 +8,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
8
|
};
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
10
10
|
const express_promise_router_1 = __importDefault(require("express-promise-router"));
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
11
12
|
const cors_1 = require("../middleware/cors");
|
12
13
|
const stringBody_1 = require("../middleware/stringBody");
|
13
14
|
const stormClient_1 = require("./stormClient");
|
14
15
|
const event_parser_1 = require("./event-parser");
|
15
16
|
const codegen_1 = require("./codegen");
|
17
|
+
const assetManager_1 = require("../assetManager");
|
18
|
+
const path_1 = __importDefault(require("path"));
|
16
19
|
const router = (0, express_promise_router_1.default)();
|
17
20
|
router.use('/', cors_1.corsHandler);
|
18
21
|
router.use('/', stringBody_1.stringBody);
|
@@ -47,7 +50,7 @@ router.post('/:handle/all', async (req, res) => {
|
|
47
50
|
const result = eventParser.toResult(handle);
|
48
51
|
sendDefinitions(res, result);
|
49
52
|
if (!req.query.skipCodegen) {
|
50
|
-
const stormCodegen = new codegen_1.StormCodegen(aiRequest.prompt, result.blocks, eventParser.getEvents());
|
53
|
+
const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents());
|
51
54
|
const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
|
52
55
|
await stormCodegen.process();
|
53
56
|
await codegenPromise;
|
@@ -59,6 +62,28 @@ router.post('/:handle/all', async (req, res) => {
|
|
59
62
|
res.end();
|
60
63
|
}
|
61
64
|
});
|
65
|
+
router.post('/block/create', async (req, res) => {
|
66
|
+
const createRequest = JSON.parse(req.stringBody ?? '{}');
|
67
|
+
try {
|
68
|
+
const ymlPath = path_1.default.join(createRequest.newPath, 'kapeta.yml');
|
69
|
+
console.log('Creating block at', ymlPath);
|
70
|
+
const [asset] = await assetManager_1.assetManager.createAsset(ymlPath, createRequest.definition);
|
71
|
+
if (await fs_extra_1.default.pathExists(createRequest.tmpPath)) {
|
72
|
+
console.log('Moving block from', createRequest.tmpPath, 'to', createRequest.newPath);
|
73
|
+
await fs_extra_1.default.move(createRequest.tmpPath, createRequest.newPath, {
|
74
|
+
overwrite: true,
|
75
|
+
});
|
76
|
+
console.log('Updating asset', asset.ref);
|
77
|
+
res.send(await assetManager_1.assetManager.updateAsset(asset.ref, createRequest.definition));
|
78
|
+
}
|
79
|
+
else {
|
80
|
+
res.send(asset);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
catch (err) {
|
84
|
+
res.status(500).send({ error: err.message });
|
85
|
+
}
|
86
|
+
});
|
62
87
|
function sendDefinitions(res, result) {
|
63
88
|
sendEvent(res, {
|
64
89
|
type: 'DEFINITION_CHANGE',
|
@@ -6,6 +6,7 @@
|
|
6
6
|
import { EventEmitter } from 'node:events';
|
7
7
|
import { StormEvent } from './events';
|
8
8
|
import { AIFileTypes, GeneratedFile } from '@kapeta/codegen';
|
9
|
+
import { BlockDefinition } from '@kapeta/schemas';
|
9
10
|
export declare class StormStream extends EventEmitter {
|
10
11
|
private conversationId;
|
11
12
|
private lines;
|
@@ -29,6 +30,11 @@ export interface StormContextRequest<T = string> {
|
|
29
30
|
conversationId?: string;
|
30
31
|
prompt: T;
|
31
32
|
}
|
33
|
+
export interface StormCreateBlockRequest {
|
34
|
+
definition: BlockDefinition;
|
35
|
+
tmpPath: string;
|
36
|
+
newPath: string;
|
37
|
+
}
|
32
38
|
export interface StormFileInfo extends GeneratedFile {
|
33
39
|
type: AIFileTypes;
|
34
40
|
}
|
package/package.json
CHANGED
package/src/assetManager.ts
CHANGED
@@ -179,7 +179,8 @@ class AssetManager {
|
|
179
179
|
async createAsset(
|
180
180
|
path: string,
|
181
181
|
yaml: BlockDefinition,
|
182
|
-
sourceOfChange: SourceOfChange = 'filesystem'
|
182
|
+
sourceOfChange: SourceOfChange = 'filesystem',
|
183
|
+
codegen: boolean = true
|
183
184
|
): Promise<EnrichedAsset[]> {
|
184
185
|
if (await FS.pathExists(path)) {
|
185
186
|
throw new Error('File already exists: ' + path);
|
@@ -199,11 +200,12 @@ class AssetManager {
|
|
199
200
|
});
|
200
201
|
|
201
202
|
definitionsManager.clearCache();
|
202
|
-
console.log(`Created asset at: ${path}`);
|
203
|
-
|
204
203
|
const ref = `kapeta://${yaml.metadata.name}:local`;
|
204
|
+
console.log(`Created asset ${ref} at: ${path}`);
|
205
205
|
|
206
|
-
|
206
|
+
if (codegen) {
|
207
|
+
await this.maybeGenerateCode(ref, path, yaml);
|
208
|
+
}
|
207
209
|
|
208
210
|
return asset;
|
209
211
|
}
|
package/src/filesystem/routes.ts
CHANGED
@@ -8,6 +8,7 @@ import { stringBody, StringBodyRequest } from '../middleware/stringBody';
|
|
8
8
|
import { filesystemManager } from '../filesystemManager';
|
9
9
|
import { corsHandler } from '../middleware/cors';
|
10
10
|
import { NextFunction, Request, Response } from 'express';
|
11
|
+
import FS from 'fs-extra';
|
11
12
|
|
12
13
|
let router = Router();
|
13
14
|
|
@@ -99,6 +100,15 @@ router.get('/readfile', async (req: Request, res: Response) => {
|
|
99
100
|
}
|
100
101
|
});
|
101
102
|
|
103
|
+
router.get('/exists', async (req: Request, res: Response) => {
|
104
|
+
let pathArg = req.query.path as string;
|
105
|
+
try {
|
106
|
+
res.send(await FS.pathExists(pathArg));
|
107
|
+
} catch (err) {
|
108
|
+
res.status(400).send({ error: '' + err });
|
109
|
+
}
|
110
|
+
});
|
111
|
+
|
102
112
|
router.put('/mkdir', async (req: Request, res: Response) => {
|
103
113
|
let pathArg = req.query.path as string;
|
104
114
|
try {
|
package/src/storm/codegen.ts
CHANGED
@@ -4,18 +4,26 @@
|
|
4
4
|
*/
|
5
5
|
|
6
6
|
import { Definition } from '@kapeta/local-cluster-config';
|
7
|
-
import {
|
7
|
+
import {
|
8
|
+
AIFileTypes,
|
9
|
+
BlockCodeGenerator,
|
10
|
+
CodeGenerator,
|
11
|
+
CodeWriter,
|
12
|
+
GeneratedFile,
|
13
|
+
GeneratedResult,
|
14
|
+
} from '@kapeta/codegen';
|
8
15
|
import { BlockDefinition } from '@kapeta/schemas';
|
9
16
|
import { codeGeneratorManager } from '../codeGeneratorManager';
|
10
17
|
import { STORM_ID, stormClient } from './stormClient';
|
11
18
|
import { StormEvent, StormEventFile } from './events';
|
12
19
|
import { BlockDefinitionInfo, StormEventParser } from './event-parser';
|
13
|
-
import {
|
14
|
-
import { KapetaURI } from '@kapeta/nodejs-utils';
|
20
|
+
import { StormFileImplementationPrompt, StormFileInfo, StormStream } from './stream';
|
21
|
+
import { KapetaURI, parseKapetaUri } from '@kapeta/nodejs-utils';
|
15
22
|
import { writeFile } from 'fs/promises';
|
16
23
|
import path, { join } from 'path';
|
17
24
|
import os from 'node:os';
|
18
25
|
import { readFile, readFileSync, writeFileSync } from 'fs';
|
26
|
+
import Path from 'path';
|
19
27
|
|
20
28
|
type ImplementationGenerator = (prompt: StormFileImplementationPrompt, conversationId?: string) => Promise<StormStream>;
|
21
29
|
|
@@ -25,19 +33,21 @@ export class StormCodegen {
|
|
25
33
|
private readonly out = new StormStream();
|
26
34
|
private readonly events: StormEvent[];
|
27
35
|
private readonly tmpDir: string;
|
36
|
+
private readonly conversationId: string;
|
28
37
|
|
29
|
-
constructor(userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]) {
|
38
|
+
constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]) {
|
30
39
|
this.userPrompt = userPrompt;
|
31
40
|
this.blocks = blocks;
|
32
41
|
this.events = events;
|
33
|
-
this.tmpDir = os.tmpdir();
|
42
|
+
this.tmpDir = Path.join(os.tmpdir(), conversationId);
|
43
|
+
this.conversationId = conversationId;
|
34
44
|
}
|
35
45
|
|
36
46
|
public async process() {
|
37
|
-
|
38
|
-
|
39
|
-
}
|
40
|
-
|
47
|
+
const promises = this.blocks.map((block) => {
|
48
|
+
return this.processBlockCode(block);
|
49
|
+
});
|
50
|
+
await Promise.all(promises);
|
41
51
|
this.out.end();
|
42
52
|
}
|
43
53
|
|
@@ -109,7 +119,7 @@ export class StormCodegen {
|
|
109
119
|
const allFiles = this.toStormFiles(generatedResult);
|
110
120
|
|
111
121
|
// Send all the non-ai files to the stream
|
112
|
-
this.emitFiles(block.uri, block.aiName, allFiles);
|
122
|
+
this.emitFiles(parseKapetaUri(block.uri), block.aiName, allFiles);
|
113
123
|
|
114
124
|
const relevantFiles: StormFileInfo[] = allFiles.filter(
|
115
125
|
(file) => file.type !== AIFileTypes.IGNORE && file.type !== AIFileTypes.WEB_SCREEN
|
@@ -125,7 +135,7 @@ export class StormCodegen {
|
|
125
135
|
});
|
126
136
|
|
127
137
|
uiStream.on('data', (evt) => {
|
128
|
-
this.handleUiOutput(block.uri, block.aiName, evt);
|
138
|
+
this.handleUiOutput(parseKapetaUri(block.uri), block.aiName, evt);
|
129
139
|
});
|
130
140
|
|
131
141
|
await uiStream.waitForDone();
|
@@ -140,7 +150,7 @@ export class StormCodegen {
|
|
140
150
|
const serviceFiles: StormFileInfo[] = allFiles.filter((file) => file.type === AIFileTypes.SERVICE);
|
141
151
|
if (serviceFiles.length > 0) {
|
142
152
|
await this.processTemplates(
|
143
|
-
block.uri,
|
153
|
+
parseKapetaUri(block.uri),
|
144
154
|
block.aiName,
|
145
155
|
stormClient.createServiceImplementation.bind(stormClient),
|
146
156
|
serviceFiles,
|
@@ -168,18 +178,36 @@ export class StormCodegen {
|
|
168
178
|
const filesToBeFixed = serviceFiles.concat(contextFiles);
|
169
179
|
const codeGenerator = new BlockCodeGenerator(block.content as BlockDefinition);
|
170
180
|
await this.verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles);
|
181
|
+
|
182
|
+
const blockRef = block.uri;
|
183
|
+
this.out.emit('data', {
|
184
|
+
type: 'BLOCK_READY',
|
185
|
+
reason: 'Block ready',
|
186
|
+
created: Date.now(),
|
187
|
+
payload: {
|
188
|
+
path: basePath,
|
189
|
+
blockName: block.aiName,
|
190
|
+
blockRef,
|
191
|
+
instanceId: StormEventParser.toInstanceIdFromRef(blockRef),
|
192
|
+
},
|
193
|
+
} satisfies StormEvent);
|
171
194
|
}
|
172
195
|
|
173
|
-
private async verifyAndFixCode(
|
196
|
+
private async verifyAndFixCode(
|
197
|
+
codeGenerator: CodeGenerator,
|
198
|
+
basePath: string,
|
199
|
+
filesToBeFixed: StormFileInfo[],
|
200
|
+
knownFiles: StormFileInfo[]
|
201
|
+
) {
|
174
202
|
let attempts = 0;
|
175
203
|
let validCode = false;
|
176
204
|
for (let i = 0; i <= 3; i++) {
|
177
205
|
attempts++;
|
178
206
|
try {
|
179
|
-
console.log(`Validating the code in ${basePath} attempt #${attempts}`)
|
207
|
+
console.log(`Validating the code in ${basePath} attempt #${attempts}`);
|
180
208
|
const result = await codeGenerator.validateForTarget(basePath);
|
181
209
|
if (result && result.valid) {
|
182
|
-
|
210
|
+
validCode = true;
|
183
211
|
break;
|
184
212
|
}
|
185
213
|
|
@@ -192,16 +220,20 @@ export class StormCodegen {
|
|
192
220
|
if (evt.type === 'ERROR_CLASSIFIER') {
|
193
221
|
// find the file that caused the error
|
194
222
|
// strip base path from event file name, if it exists sometimes the AI sends the full path
|
195
|
-
const eventFileName = this.removePrefix(basePath+'/', evt.payload.filename);
|
223
|
+
const eventFileName = this.removePrefix(basePath + '/', evt.payload.filename);
|
196
224
|
const file = filesToBeFixed.find((f) => f.filename === eventFileName);
|
197
|
-
if(!file) {
|
198
|
-
console.log(
|
225
|
+
if (!file) {
|
226
|
+
console.log(
|
227
|
+
`Could not find the file ${eventFileName} in the list of files to be fixed, Henrik might wanna create a new file for this fix`
|
228
|
+
);
|
199
229
|
}
|
200
230
|
// read the content of the file
|
201
231
|
const content = readFileSync(join(basePath, eventFileName), 'utf8');
|
202
|
-
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
232
|
+
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
233
|
+
.map((e) => e.filename)
|
234
|
+
.join('\n')}\n---\n${content}`;
|
203
235
|
console.log(`trying to fix the code in ${eventFileName}`);
|
204
|
-
console.debug(`with the fix:\n${fix}`)
|
236
|
+
console.debug(`with the fix:\n${fix}`);
|
205
237
|
const code = this.codeFix(fix);
|
206
238
|
fixes.set(join(basePath, eventFileName), code);
|
207
239
|
}
|
@@ -226,7 +258,7 @@ export class StormCodegen {
|
|
226
258
|
|
227
259
|
removePrefix(prefix: string, str: string): string {
|
228
260
|
if (str.startsWith(prefix)) {
|
229
|
-
|
261
|
+
return str.slice(prefix.length);
|
230
262
|
}
|
231
263
|
return str;
|
232
264
|
}
|
@@ -29,7 +29,7 @@ import { v5 as uuid } from 'uuid';
|
|
29
29
|
import { definitionsManager } from '../definitionsManager';
|
30
30
|
|
31
31
|
export interface BlockDefinitionInfo {
|
32
|
-
uri:
|
32
|
+
uri: string;
|
33
33
|
content: BlockDefinition;
|
34
34
|
aiName: string;
|
35
35
|
}
|
@@ -454,6 +454,8 @@ export class StormEventParser {
|
|
454
454
|
name: planRef.fullName,
|
455
455
|
title: this.planName,
|
456
456
|
description: this.planDescription,
|
457
|
+
structure: 'mono',
|
458
|
+
visibility: 'private',
|
457
459
|
},
|
458
460
|
spec: {
|
459
461
|
blocks,
|
@@ -474,7 +476,7 @@ export class StormEventParser {
|
|
474
476
|
const blockRef = StormEventParser.toRef(handle, blockInfo.name);
|
475
477
|
|
476
478
|
const blockDefinitionInfo: BlockDefinitionInfo = {
|
477
|
-
uri: blockRef,
|
479
|
+
uri: blockRef.toNormalizedString(),
|
478
480
|
aiName: blockInfo.name,
|
479
481
|
content: {
|
480
482
|
kind: this.toBlockKind(blockInfo.type),
|
package/src/storm/events.ts
CHANGED
@@ -144,9 +144,9 @@ export interface StormEventCodeFix {
|
|
144
144
|
};
|
145
145
|
}
|
146
146
|
export interface StormEventErrorClassifierInfo {
|
147
|
-
error: string
|
148
|
-
filename: string
|
149
|
-
potentialFix: string
|
147
|
+
error: string;
|
148
|
+
filename: string;
|
149
|
+
potentialFix: string;
|
150
150
|
}
|
151
151
|
|
152
152
|
export interface ScreenTemplate {
|
@@ -196,6 +196,18 @@ export interface StormEventFile {
|
|
196
196
|
};
|
197
197
|
}
|
198
198
|
|
199
|
+
export interface StormEventBlockReady {
|
200
|
+
type: 'BLOCK_READY';
|
201
|
+
reason: string;
|
202
|
+
created: number;
|
203
|
+
payload: {
|
204
|
+
path: string;
|
205
|
+
blockName: string;
|
206
|
+
blockRef: string;
|
207
|
+
instanceId: string;
|
208
|
+
};
|
209
|
+
}
|
210
|
+
|
199
211
|
export interface StormEventDone {
|
200
212
|
type: 'DONE';
|
201
213
|
created: number;
|
@@ -223,4 +235,5 @@ export type StormEvent =
|
|
223
235
|
| StormEventDone
|
224
236
|
| StormEventDefinitionChange
|
225
237
|
| StormEventErrorClassifier
|
226
|
-
| StormEventCodeFix
|
238
|
+
| StormEventCodeFix
|
239
|
+
| StormEventBlockReady;
|
package/src/storm/routes.ts
CHANGED
@@ -4,15 +4,19 @@
|
|
4
4
|
*/
|
5
5
|
|
6
6
|
import Router from 'express-promise-router';
|
7
|
+
import FS from 'fs-extra';
|
7
8
|
import { Response } from 'express';
|
8
9
|
import { corsHandler } from '../middleware/cors';
|
9
10
|
import { stringBody } from '../middleware/stringBody';
|
10
11
|
import { KapetaBodyRequest } from '../types';
|
11
|
-
import { StormContextRequest,
|
12
|
+
import { StormContextRequest, StormCreateBlockRequest, StormStream } from './stream';
|
12
13
|
import { ConversationIdHeader, stormClient } from './stormClient';
|
13
14
|
import { StormEvent } from './events';
|
14
15
|
import { resolveOptions, StormDefinitions, StormEventParser } from './event-parser';
|
15
16
|
import { StormCodegen } from './codegen';
|
17
|
+
import { assetManager } from '../assetManager';
|
18
|
+
import Path from 'path';
|
19
|
+
import { normalizeKapetaUri } from '@kapeta/nodejs-utils';
|
16
20
|
|
17
21
|
const router = Router();
|
18
22
|
|
@@ -62,7 +66,12 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
|
|
62
66
|
sendDefinitions(res, result);
|
63
67
|
|
64
68
|
if (!req.query.skipCodegen) {
|
65
|
-
const stormCodegen = new StormCodegen(
|
69
|
+
const stormCodegen = new StormCodegen(
|
70
|
+
metaStream.getConversationId(),
|
71
|
+
aiRequest.prompt,
|
72
|
+
result.blocks,
|
73
|
+
eventParser.getEvents()
|
74
|
+
);
|
66
75
|
|
67
76
|
const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
|
68
77
|
|
@@ -78,6 +87,34 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
|
|
78
87
|
}
|
79
88
|
});
|
80
89
|
|
90
|
+
router.post('/block/create', async (req: KapetaBodyRequest, res: Response) => {
|
91
|
+
const createRequest: StormCreateBlockRequest = JSON.parse(req.stringBody ?? '{}');
|
92
|
+
|
93
|
+
try {
|
94
|
+
const ymlPath = Path.join(createRequest.newPath, 'kapeta.yml');
|
95
|
+
|
96
|
+
console.log('Creating block at', ymlPath);
|
97
|
+
|
98
|
+
const [asset] = await assetManager.createAsset(ymlPath, createRequest.definition);
|
99
|
+
|
100
|
+
if (await FS.pathExists(createRequest.tmpPath)) {
|
101
|
+
console.log('Moving block from', createRequest.tmpPath, 'to', createRequest.newPath);
|
102
|
+
|
103
|
+
await FS.move(createRequest.tmpPath, createRequest.newPath, {
|
104
|
+
overwrite: true,
|
105
|
+
});
|
106
|
+
|
107
|
+
console.log('Updating asset', asset.ref);
|
108
|
+
|
109
|
+
res.send(await assetManager.updateAsset(asset.ref, createRequest.definition));
|
110
|
+
} else {
|
111
|
+
res.send(asset);
|
112
|
+
}
|
113
|
+
} catch (err: any) {
|
114
|
+
res.status(500).send({ error: err.message });
|
115
|
+
}
|
116
|
+
});
|
117
|
+
|
81
118
|
function sendDefinitions(res: Response, result: StormDefinitions) {
|
82
119
|
sendEvent(res, {
|
83
120
|
type: 'DEFINITION_CHANGE',
|
package/src/storm/stream.ts
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
import { EventEmitter } from 'node:events';
|
6
6
|
import { StormEvent } from './events';
|
7
7
|
import { AIFileTypes, GeneratedFile } from '@kapeta/codegen';
|
8
|
+
import { BlockDefinition } from '@kapeta/schemas';
|
8
9
|
|
9
10
|
export class StormStream extends EventEmitter {
|
10
11
|
private conversationId: string = '';
|
@@ -73,6 +74,12 @@ export interface StormContextRequest<T = string> {
|
|
73
74
|
prompt: T;
|
74
75
|
}
|
75
76
|
|
77
|
+
export interface StormCreateBlockRequest {
|
78
|
+
definition: BlockDefinition;
|
79
|
+
tmpPath: string;
|
80
|
+
newPath: string;
|
81
|
+
}
|
82
|
+
|
76
83
|
export interface StormFileInfo extends GeneratedFile {
|
77
84
|
type: AIFileTypes;
|
78
85
|
}
|