@kapeta/local-cluster-service 0.48.5 → 0.50.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 +14 -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 +4 -1
- package/dist/cjs/src/storm/codegen.js +63 -11
- package/dist/cjs/src/storm/event-parser.d.ts +5 -2
- package/dist/cjs/src/storm/event-parser.js +24 -4
- package/dist/cjs/src/storm/events.d.ts +24 -1
- package/dist/cjs/src/storm/events.js +7 -0
- package/dist/cjs/src/storm/routes.js +88 -6
- package/dist/cjs/src/storm/stormClient.js +5 -1
- package/dist/cjs/src/storm/stream.d.ts +11 -0
- package/dist/cjs/src/storm/stream.js +11 -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 +4 -1
- package/dist/esm/src/storm/codegen.js +63 -11
- package/dist/esm/src/storm/event-parser.d.ts +5 -2
- package/dist/esm/src/storm/event-parser.js +24 -4
- package/dist/esm/src/storm/events.d.ts +24 -1
- package/dist/esm/src/storm/events.js +7 -0
- package/dist/esm/src/storm/routes.js +88 -6
- package/dist/esm/src/storm/stormClient.js +5 -1
- package/dist/esm/src/storm/stream.d.ts +11 -0
- package/dist/esm/src/storm/stream.js +11 -0
- package/package.json +1 -1
- package/src/assetManager.ts +6 -4
- package/src/filesystem/routes.ts +10 -0
- package/src/storm/codegen.ts +95 -22
- package/src/storm/event-parser.ts +33 -5
- package/src/storm/events.ts +32 -4
- package/src/storm/routes.ts +113 -12
- package/src/storm/stormClient.ts +8 -1
- package/src/storm/stream.ts +22 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.50.0](https://github.com/kapetacom/local-cluster-service/compare/v0.49.0...v0.50.0) (2024-06-05)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Handle aborted requests ([#162](https://github.com/kapetacom/local-cluster-service/issues/162)) ([a9323d4](https://github.com/kapetacom/local-cluster-service/commit/a9323d46423361c2de63e40b4b61927b9b4198b7))
|
7
|
+
|
8
|
+
# [0.49.0](https://github.com/kapetacom/local-cluster-service/compare/v0.48.5...v0.49.0) (2024-06-05)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* 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))
|
14
|
+
|
1
15
|
## [0.48.5](https://github.com/kapetacom/local-cluster-service/compare/v0.48.4...v0.48.5) (2024-06-04)
|
2
16
|
|
3
17
|
|
@@ -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,8 +11,10 @@ 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>;
|
17
|
+
isAborted(): boolean;
|
16
18
|
getStream(): StormStream;
|
17
19
|
private handleTemplateFileOutput;
|
18
20
|
private handleUiOutput;
|
@@ -46,4 +48,5 @@ export declare class StormCodegen {
|
|
46
48
|
* Generates the code using codegen for a given block.
|
47
49
|
*/
|
48
50
|
private generateBlock;
|
51
|
+
abort(): void;
|
49
52
|
}
|
@@ -36,28 +36,36 @@ 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
|
}
|
66
|
+
isAborted() {
|
67
|
+
return this.out.isAborted();
|
68
|
+
}
|
61
69
|
getStream() {
|
62
70
|
return this.out;
|
63
71
|
}
|
@@ -112,6 +120,9 @@ class StormCodegen {
|
|
112
120
|
* Generates the code for a block and sends it to the AI
|
113
121
|
*/
|
114
122
|
async processBlockCode(block) {
|
123
|
+
if (this.isAborted()) {
|
124
|
+
return;
|
125
|
+
}
|
115
126
|
// Generate the code for the block using the standard codegen templates
|
116
127
|
const generatedResult = await this.generateBlock(block.content);
|
117
128
|
if (!generatedResult) {
|
@@ -119,7 +130,10 @@ class StormCodegen {
|
|
119
130
|
}
|
120
131
|
const allFiles = this.toStormFiles(generatedResult);
|
121
132
|
// Send all the non-ai files to the stream
|
122
|
-
this.emitFiles(block.uri, block.aiName, allFiles);
|
133
|
+
this.emitFiles((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, allFiles);
|
134
|
+
if (this.isAborted()) {
|
135
|
+
return;
|
136
|
+
}
|
123
137
|
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && file.type !== codegen_1.AIFileTypes.WEB_SCREEN);
|
124
138
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
125
139
|
if (uiTemplates.length > 0) {
|
@@ -131,18 +145,27 @@ class StormCodegen {
|
|
131
145
|
prompt: this.userPrompt,
|
132
146
|
});
|
133
147
|
uiStream.on('data', (evt) => {
|
134
|
-
this.handleUiOutput(block.uri, block.aiName, evt);
|
148
|
+
this.handleUiOutput((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, evt);
|
149
|
+
});
|
150
|
+
this.out.on('aborted', () => {
|
151
|
+
uiStream.abort();
|
135
152
|
});
|
136
153
|
await uiStream.waitForDone();
|
137
154
|
}
|
155
|
+
if (this.isAborted()) {
|
156
|
+
return;
|
157
|
+
}
|
138
158
|
// Gather the context files for implementation. These will be all be passed to the AI
|
139
159
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type));
|
140
160
|
// Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
|
141
161
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
142
162
|
if (serviceFiles.length > 0) {
|
143
|
-
await this.processTemplates(block.uri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
163
|
+
await this.processTemplates((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
144
164
|
}
|
145
165
|
const basePath = this.getBasePath(block.content.metadata.name);
|
166
|
+
if (this.isAborted()) {
|
167
|
+
return;
|
168
|
+
}
|
146
169
|
for (const serviceFile of serviceFiles) {
|
147
170
|
const filePath = (0, path_1.join)(basePath, serviceFile.filename);
|
148
171
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -158,6 +181,18 @@ class StormCodegen {
|
|
158
181
|
const filesToBeFixed = serviceFiles.concat(contextFiles);
|
159
182
|
const codeGenerator = new codegen_1.BlockCodeGenerator(block.content);
|
160
183
|
await this.verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles);
|
184
|
+
const blockRef = block.uri;
|
185
|
+
this.out.emit('data', {
|
186
|
+
type: 'BLOCK_READY',
|
187
|
+
reason: 'Block ready',
|
188
|
+
created: Date.now(),
|
189
|
+
payload: {
|
190
|
+
path: basePath,
|
191
|
+
blockName: block.aiName,
|
192
|
+
blockRef,
|
193
|
+
instanceId: event_parser_1.StormEventParser.toInstanceIdFromRef(blockRef),
|
194
|
+
},
|
195
|
+
});
|
161
196
|
}
|
162
197
|
async verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, knownFiles) {
|
163
198
|
let attempts = 0;
|
@@ -175,6 +210,9 @@ class StormCodegen {
|
|
175
210
|
console.debug('Validation error:', result);
|
176
211
|
const errorStream = await stormClient_1.stormClient.createErrorClassification(result.error, []);
|
177
212
|
const fixes = new Map();
|
213
|
+
this.out.on('aborted', () => {
|
214
|
+
errorStream.abort();
|
215
|
+
});
|
178
216
|
errorStream.on('data', (evt) => {
|
179
217
|
if (evt.type === 'ERROR_CLASSIFIER') {
|
180
218
|
// find the file that caused the error
|
@@ -186,9 +224,11 @@ class StormCodegen {
|
|
186
224
|
}
|
187
225
|
// read the content of the file
|
188
226
|
const content = (0, fs_1.readFileSync)((0, path_1.join)(basePath, eventFileName), 'utf8');
|
189
|
-
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
190
|
-
|
191
|
-
|
227
|
+
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
228
|
+
.map((e) => e.filename)
|
229
|
+
.join('\n')}\n---\n${content}`;
|
230
|
+
//console.log(`trying to fix the code in ${eventFileName}`);
|
231
|
+
//console.debug(`with the fix:\n${fix}`);
|
192
232
|
const code = this.codeFix(fix);
|
193
233
|
fixes.set((0, path_1.join)(basePath, eventFileName), code);
|
194
234
|
}
|
@@ -233,6 +273,9 @@ class StormCodegen {
|
|
233
273
|
resolve(evt.payload.content);
|
234
274
|
}
|
235
275
|
});
|
276
|
+
this.out.on('aborted', () => {
|
277
|
+
fixStream.abort();
|
278
|
+
});
|
236
279
|
fixStream.on('error', (err) => {
|
237
280
|
reject(err);
|
238
281
|
});
|
@@ -288,6 +331,9 @@ class StormCodegen {
|
|
288
331
|
prompt: this.userPrompt,
|
289
332
|
});
|
290
333
|
const files = [];
|
334
|
+
this.out.on('aborted', () => {
|
335
|
+
stream.abort();
|
336
|
+
});
|
291
337
|
stream.on('data', (evt) => {
|
292
338
|
const file = this.handleTemplateFileOutput(blockUri, aiName, templateFile, evt);
|
293
339
|
if (file) {
|
@@ -338,6 +384,9 @@ class StormCodegen {
|
|
338
384
|
* Generates the code using codegen for a given block.
|
339
385
|
*/
|
340
386
|
async generateBlock(yamlContent) {
|
387
|
+
if (this.isAborted()) {
|
388
|
+
return;
|
389
|
+
}
|
341
390
|
if (!yamlContent.spec.target?.kind) {
|
342
391
|
//Not all block types have targets
|
343
392
|
return;
|
@@ -352,5 +401,8 @@ class StormCodegen {
|
|
352
401
|
new codegen_1.CodeWriter(basePath).write(generatedResult);
|
353
402
|
return generatedResult;
|
354
403
|
}
|
404
|
+
abort() {
|
405
|
+
this.out.abort();
|
406
|
+
}
|
355
407
|
}
|
356
408
|
exports.StormCodegen = StormCodegen;
|
@@ -2,11 +2,11 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import { StormEvent } from './events';
|
5
|
+
import { StormEvent, StormEventPhases, StormEventPhaseType } 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
|
}
|
@@ -39,6 +39,9 @@ export interface StormOptions {
|
|
39
39
|
desktopLanguage: string;
|
40
40
|
gatewayKind: string;
|
41
41
|
}
|
42
|
+
export declare function createPhaseStartEvent(type: StormEventPhaseType): StormEventPhases;
|
43
|
+
export declare function createPhaseEndEvent(type: StormEventPhaseType): StormEventPhases;
|
44
|
+
export declare function createPhaseEvent(start: boolean, type: StormEventPhaseType): StormEventPhases;
|
42
45
|
export declare function resolveOptions(): Promise<StormOptions>;
|
43
46
|
export declare class StormEventParser {
|
44
47
|
static toInstanceId(handle: string, blockName: string): string;
|
@@ -4,7 +4,7 @@
|
|
4
4
|
* SPDX-License-Identifier: BUSL-1.1
|
5
5
|
*/
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
7
|
-
exports.StormEventParser = exports.resolveOptions = void 0;
|
7
|
+
exports.StormEventParser = exports.resolveOptions = exports.createPhaseEvent = exports.createPhaseEndEvent = exports.createPhaseStartEvent = void 0;
|
8
8
|
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
9
9
|
const kaplang_core_1 = require("@kapeta/kaplang-core");
|
10
10
|
const uuid_1 = require("uuid");
|
@@ -29,6 +29,24 @@ function prettifyKaplang(source) {
|
|
29
29
|
return source;
|
30
30
|
}
|
31
31
|
}
|
32
|
+
function createPhaseStartEvent(type) {
|
33
|
+
return createPhaseEvent(true, type);
|
34
|
+
}
|
35
|
+
exports.createPhaseStartEvent = createPhaseStartEvent;
|
36
|
+
function createPhaseEndEvent(type) {
|
37
|
+
return createPhaseEvent(false, type);
|
38
|
+
}
|
39
|
+
exports.createPhaseEndEvent = createPhaseEndEvent;
|
40
|
+
function createPhaseEvent(start, type) {
|
41
|
+
return {
|
42
|
+
type: start ? 'PHASE_START' : 'PHASE_END',
|
43
|
+
created: Date.now(),
|
44
|
+
payload: {
|
45
|
+
phaseType: type,
|
46
|
+
},
|
47
|
+
};
|
48
|
+
}
|
49
|
+
exports.createPhaseEvent = createPhaseEvent;
|
32
50
|
async function resolveOptions() {
|
33
51
|
// Predefined types for now - TODO: Allow user to select / change
|
34
52
|
const blockTypeService = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/block-type-service');
|
@@ -213,7 +231,7 @@ class StormEventParser {
|
|
213
231
|
return this.error;
|
214
232
|
}
|
215
233
|
toResult(handle) {
|
216
|
-
const planRef = StormEventParser.toRef(handle, this.planName
|
234
|
+
const planRef = StormEventParser.toRef(handle, this.planName || 'undefined');
|
217
235
|
const blockDefinitions = this.toBlockDefinitions(handle);
|
218
236
|
const refIdMap = {};
|
219
237
|
const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
|
@@ -225,7 +243,7 @@ class StormEventParser {
|
|
225
243
|
block: {
|
226
244
|
ref,
|
227
245
|
},
|
228
|
-
name: block.content.metadata.title
|
246
|
+
name: block.content.metadata.title || block.content.metadata.name,
|
229
247
|
dimensions: {
|
230
248
|
left: 0,
|
231
249
|
top: 0,
|
@@ -315,6 +333,8 @@ class StormEventParser {
|
|
315
333
|
name: planRef.fullName,
|
316
334
|
title: this.planName,
|
317
335
|
description: this.planDescription,
|
336
|
+
structure: 'mono',
|
337
|
+
visibility: 'private',
|
318
338
|
},
|
319
339
|
spec: {
|
320
340
|
blocks,
|
@@ -331,7 +351,7 @@ class StormEventParser {
|
|
331
351
|
Object.entries(this.blocks).forEach(([, blockInfo]) => {
|
332
352
|
const blockRef = StormEventParser.toRef(handle, blockInfo.name);
|
333
353
|
const blockDefinitionInfo = {
|
334
|
-
uri: blockRef,
|
354
|
+
uri: blockRef.toNormalizedString(),
|
335
355
|
aiName: blockInfo.name,
|
336
356
|
content: {
|
337
357
|
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,16 @@ export interface StormEventDefinitionChange {
|
|
173
184
|
created: number;
|
174
185
|
payload: StormDefinitions;
|
175
186
|
}
|
176
|
-
export
|
187
|
+
export declare enum StormEventPhaseType {
|
188
|
+
META = "META",
|
189
|
+
DEFINITIONS = "DEFINITIONS",
|
190
|
+
IMPLEMENTATION = "IMPLEMENTATION"
|
191
|
+
}
|
192
|
+
export interface StormEventPhases {
|
193
|
+
type: 'PHASE_START' | 'PHASE_END';
|
194
|
+
created: number;
|
195
|
+
payload: {
|
196
|
+
phaseType: StormEventPhaseType;
|
197
|
+
};
|
198
|
+
}
|
199
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventBlockReady | StormEventPhases;
|
@@ -1,2 +1,9 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.StormEventPhaseType = void 0;
|
4
|
+
var StormEventPhaseType;
|
5
|
+
(function (StormEventPhaseType) {
|
6
|
+
StormEventPhaseType["META"] = "META";
|
7
|
+
StormEventPhaseType["DEFINITIONS"] = "DEFINITIONS";
|
8
|
+
StormEventPhaseType["IMPLEMENTATION"] = "IMPLEMENTATION";
|
9
|
+
})(StormEventPhaseType || (exports.StormEventPhaseType = StormEventPhaseType = {}));
|
@@ -8,11 +8,15 @@ 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");
|
15
|
+
const events_1 = require("./events");
|
14
16
|
const event_parser_1 = require("./event-parser");
|
15
17
|
const codegen_1 = require("./codegen");
|
18
|
+
const assetManager_1 = require("../assetManager");
|
19
|
+
const path_1 = __importDefault(require("path"));
|
16
20
|
const router = (0, express_promise_router_1.default)();
|
17
21
|
router.use('/', cors_1.corsHandler);
|
18
22
|
router.use('/', stringBody_1.stringBody);
|
@@ -24,15 +28,41 @@ router.post('/:handle/all', async (req, res) => {
|
|
24
28
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
25
29
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
26
30
|
const metaStream = await stormClient_1.stormClient.createMetadata(aiRequest.prompt, conversationId);
|
31
|
+
onRequestAborted(req, res, () => {
|
32
|
+
metaStream.abort();
|
33
|
+
});
|
27
34
|
res.set('Content-Type', 'application/x-ndjson');
|
28
35
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
29
36
|
res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
|
37
|
+
let currentPhase = events_1.StormEventPhaseType.META;
|
30
38
|
metaStream.on('data', (data) => {
|
31
39
|
const result = eventParser.processEvent(req.params.handle, data);
|
40
|
+
switch (data.type) {
|
41
|
+
case 'CREATE_API':
|
42
|
+
case 'CREATE_MODEL':
|
43
|
+
case 'CREATE_TYPE':
|
44
|
+
if (currentPhase !== events_1.StormEventPhaseType.DEFINITIONS) {
|
45
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.META));
|
46
|
+
currentPhase = events_1.StormEventPhaseType.DEFINITIONS;
|
47
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.DEFINITIONS));
|
48
|
+
}
|
49
|
+
break;
|
50
|
+
}
|
32
51
|
sendEvent(res, data);
|
33
52
|
sendDefinitions(res, result);
|
34
53
|
});
|
35
|
-
|
54
|
+
try {
|
55
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.META));
|
56
|
+
await waitForStormStream(metaStream);
|
57
|
+
}
|
58
|
+
finally {
|
59
|
+
if (!metaStream.isAborted()) {
|
60
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(currentPhase));
|
61
|
+
}
|
62
|
+
}
|
63
|
+
if (metaStream.isAborted()) {
|
64
|
+
return;
|
65
|
+
}
|
36
66
|
if (!eventParser.isValid()) {
|
37
67
|
// We can't continue if the meta stream is invalid
|
38
68
|
sendEvent(res, {
|
@@ -45,18 +75,53 @@ router.post('/:handle/all', async (req, res) => {
|
|
45
75
|
return;
|
46
76
|
}
|
47
77
|
const result = eventParser.toResult(handle);
|
78
|
+
if (metaStream.isAborted()) {
|
79
|
+
return;
|
80
|
+
}
|
48
81
|
sendDefinitions(res, result);
|
49
82
|
if (!req.query.skipCodegen) {
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
83
|
+
try {
|
84
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENTATION));
|
85
|
+
const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents());
|
86
|
+
onRequestAborted(req, res, () => {
|
87
|
+
stormCodegen.abort();
|
88
|
+
});
|
89
|
+
const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
|
90
|
+
await stormCodegen.process();
|
91
|
+
await codegenPromise;
|
92
|
+
}
|
93
|
+
finally {
|
94
|
+
if (!metaStream.isAborted()) {
|
95
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENTATION));
|
96
|
+
}
|
97
|
+
}
|
54
98
|
}
|
55
99
|
sendDone(res);
|
56
100
|
}
|
57
101
|
catch (err) {
|
58
102
|
sendError(err, res);
|
59
|
-
res.
|
103
|
+
if (!res.closed) {
|
104
|
+
res.end();
|
105
|
+
}
|
106
|
+
}
|
107
|
+
});
|
108
|
+
router.post('/block/create', async (req, res) => {
|
109
|
+
const createRequest = JSON.parse(req.stringBody ?? '{}');
|
110
|
+
try {
|
111
|
+
const ymlPath = path_1.default.join(createRequest.newPath, 'kapeta.yml');
|
112
|
+
const [asset] = await assetManager_1.assetManager.createAsset(ymlPath, createRequest.definition);
|
113
|
+
if (await fs_extra_1.default.pathExists(createRequest.tmpPath)) {
|
114
|
+
await fs_extra_1.default.move(createRequest.tmpPath, createRequest.newPath, {
|
115
|
+
overwrite: true,
|
116
|
+
});
|
117
|
+
res.send(await assetManager_1.assetManager.updateAsset(asset.ref, createRequest.definition));
|
118
|
+
}
|
119
|
+
else {
|
120
|
+
res.send(asset);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
catch (err) {
|
124
|
+
res.status(500).send({ error: err.message });
|
60
125
|
}
|
61
126
|
});
|
62
127
|
function sendDefinitions(res, result) {
|
@@ -68,6 +133,9 @@ function sendDefinitions(res, result) {
|
|
68
133
|
});
|
69
134
|
}
|
70
135
|
function sendDone(res) {
|
136
|
+
if (res.closed) {
|
137
|
+
return;
|
138
|
+
}
|
71
139
|
sendEvent(res, {
|
72
140
|
type: 'DONE',
|
73
141
|
created: Date.now(),
|
@@ -75,6 +143,9 @@ function sendDone(res) {
|
|
75
143
|
res.end();
|
76
144
|
}
|
77
145
|
function sendError(err, res) {
|
146
|
+
if (res.closed) {
|
147
|
+
return;
|
148
|
+
}
|
78
149
|
console.error('Failed to send prompt', err);
|
79
150
|
if (res.headersSent) {
|
80
151
|
sendEvent(res, {
|
@@ -112,6 +183,17 @@ function streamStormPartialResponse(result, res) {
|
|
112
183
|
});
|
113
184
|
}
|
114
185
|
function sendEvent(res, evt) {
|
186
|
+
if (res.closed) {
|
187
|
+
return;
|
188
|
+
}
|
115
189
|
res.write(JSON.stringify(evt) + '\n');
|
116
190
|
}
|
191
|
+
function onRequestAborted(req, res, onAborted) {
|
192
|
+
req.on('close', () => {
|
193
|
+
onAborted();
|
194
|
+
});
|
195
|
+
res.on('close', () => {
|
196
|
+
onAborted();
|
197
|
+
});
|
198
|
+
}
|
117
199
|
exports.default = router;
|
@@ -46,12 +46,13 @@ class StormClient {
|
|
46
46
|
prompt: stringPrompt,
|
47
47
|
conversationId: body.conversationId,
|
48
48
|
});
|
49
|
+
const abort = new AbortController();
|
50
|
+
options.signal = abort.signal;
|
49
51
|
const response = await fetch(options.url, options);
|
50
52
|
if (response.status !== 200) {
|
51
53
|
throw new Error(`Got error response from ${options.url}: ${response.status}\nContent: ${await response.text()}`);
|
52
54
|
}
|
53
55
|
const conversationId = response.headers.get(exports.ConversationIdHeader);
|
54
|
-
console.log('Received conversationId', conversationId);
|
55
56
|
const out = new stream_1.StormStream(stringPrompt, conversationId);
|
56
57
|
const jsonLStream = promises_1.default.createInterface(node_stream_1.Readable.fromWeb(response.body));
|
57
58
|
jsonLStream.on('line', (line) => {
|
@@ -63,6 +64,9 @@ class StormClient {
|
|
63
64
|
jsonLStream.on('close', () => {
|
64
65
|
out.end();
|
65
66
|
});
|
67
|
+
out.on('aborted', () => {
|
68
|
+
abort.abort();
|
69
|
+
});
|
66
70
|
return out;
|
67
71
|
}
|
68
72
|
createMetadata(prompt, conversationId) {
|
@@ -6,20 +6,26 @@
|
|
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;
|
13
|
+
private aborted;
|
12
14
|
constructor(prompt?: string, conversationId?: string | null);
|
13
15
|
getConversationId(): string;
|
16
|
+
isAborted(): boolean;
|
14
17
|
addJSONLine(line: string): void;
|
15
18
|
end(): void;
|
16
19
|
on(event: 'end', listener: () => void): this;
|
20
|
+
on(event: 'aborted', listener: () => void): this;
|
17
21
|
on(event: 'error', listener: (e: Error) => void): this;
|
18
22
|
on(event: 'data', listener: (data: StormEvent) => void): this;
|
19
23
|
emit(event: 'end'): boolean;
|
24
|
+
emit(event: 'aborted'): void;
|
20
25
|
emit(event: 'error', e: Error): boolean;
|
21
26
|
emit(event: 'data', data: StormEvent): boolean;
|
22
27
|
waitForDone(): Promise<void>;
|
28
|
+
abort(): void;
|
23
29
|
}
|
24
30
|
export interface ConversationItem {
|
25
31
|
role: 'user' | 'model';
|
@@ -29,6 +35,11 @@ export interface StormContextRequest<T = string> {
|
|
29
35
|
conversationId?: string;
|
30
36
|
prompt: T;
|
31
37
|
}
|
38
|
+
export interface StormCreateBlockRequest {
|
39
|
+
definition: BlockDefinition;
|
40
|
+
tmpPath: string;
|
41
|
+
newPath: string;
|
42
|
+
}
|
32
43
|
export interface StormFileInfo extends GeneratedFile {
|
33
44
|
type: AIFileTypes;
|
34
45
|
}
|
@@ -9,6 +9,7 @@ const node_events_1 = require("node:events");
|
|
9
9
|
class StormStream extends node_events_1.EventEmitter {
|
10
10
|
conversationId = '';
|
11
11
|
lines = [];
|
12
|
+
aborted = false;
|
12
13
|
constructor(prompt = '', conversationId) {
|
13
14
|
super();
|
14
15
|
this.conversationId = conversationId || '';
|
@@ -16,6 +17,9 @@ class StormStream extends node_events_1.EventEmitter {
|
|
16
17
|
getConversationId() {
|
17
18
|
return this.conversationId;
|
18
19
|
}
|
20
|
+
isAborted() {
|
21
|
+
return this.aborted;
|
22
|
+
}
|
19
23
|
addJSONLine(line) {
|
20
24
|
try {
|
21
25
|
this.lines.push(line);
|
@@ -48,5 +52,12 @@ class StormStream extends node_events_1.EventEmitter {
|
|
48
52
|
});
|
49
53
|
});
|
50
54
|
}
|
55
|
+
abort() {
|
56
|
+
if (this.aborted) {
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
this.aborted = true;
|
60
|
+
this.emit('aborted');
|
61
|
+
}
|
51
62
|
}
|
52
63
|
exports.StormStream = StormStream;
|
@@ -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>;
|