@kapeta/local-cluster-service 0.49.0 → 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 +7 -0
- package/dist/cjs/src/storm/codegen.d.ts +2 -0
- package/dist/cjs/src/storm/codegen.js +35 -2
- package/dist/cjs/src/storm/event-parser.d.ts +4 -1
- package/dist/cjs/src/storm/event-parser.js +21 -3
- package/dist/cjs/src/storm/events.d.ts +13 -1
- package/dist/cjs/src/storm/events.js +7 -0
- package/dist/cjs/src/storm/routes.js +66 -9
- package/dist/cjs/src/storm/stormClient.js +5 -1
- package/dist/cjs/src/storm/stream.d.ts +5 -0
- package/dist/cjs/src/storm/stream.js +11 -0
- package/dist/esm/src/storm/codegen.d.ts +2 -0
- package/dist/esm/src/storm/codegen.js +35 -2
- package/dist/esm/src/storm/event-parser.d.ts +4 -1
- package/dist/esm/src/storm/event-parser.js +21 -3
- package/dist/esm/src/storm/events.d.ts +13 -1
- package/dist/esm/src/storm/events.js +7 -0
- package/dist/esm/src/storm/routes.js +66 -9
- package/dist/esm/src/storm/stormClient.js +5 -1
- package/dist/esm/src/storm/stream.d.ts +5 -0
- package/dist/esm/src/storm/stream.js +11 -0
- package/package.json +1 -1
- package/src/storm/codegen.ts +43 -2
- package/src/storm/event-parser.ts +29 -3
- package/src/storm/events.ts +16 -1
- package/src/storm/routes.ts +87 -23
- package/src/storm/stormClient.ts +8 -1
- package/src/storm/stream.ts +15 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
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
|
+
|
1
8
|
# [0.49.0](https://github.com/kapetacom/local-cluster-service/compare/v0.48.5...v0.49.0) (2024-06-05)
|
2
9
|
|
3
10
|
|
@@ -14,6 +14,7 @@ export declare class StormCodegen {
|
|
14
14
|
private readonly conversationId;
|
15
15
|
constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
|
16
16
|
process(): Promise<void>;
|
17
|
+
isAborted(): boolean;
|
17
18
|
getStream(): StormStream;
|
18
19
|
private handleTemplateFileOutput;
|
19
20
|
private handleUiOutput;
|
@@ -47,4 +48,5 @@ export declare class StormCodegen {
|
|
47
48
|
* Generates the code using codegen for a given block.
|
48
49
|
*/
|
49
50
|
private generateBlock;
|
51
|
+
abort(): void;
|
50
52
|
}
|
@@ -63,6 +63,9 @@ class StormCodegen {
|
|
63
63
|
await Promise.all(promises);
|
64
64
|
this.out.end();
|
65
65
|
}
|
66
|
+
isAborted() {
|
67
|
+
return this.out.isAborted();
|
68
|
+
}
|
66
69
|
getStream() {
|
67
70
|
return this.out;
|
68
71
|
}
|
@@ -117,6 +120,9 @@ class StormCodegen {
|
|
117
120
|
* Generates the code for a block and sends it to the AI
|
118
121
|
*/
|
119
122
|
async processBlockCode(block) {
|
123
|
+
if (this.isAborted()) {
|
124
|
+
return;
|
125
|
+
}
|
120
126
|
// Generate the code for the block using the standard codegen templates
|
121
127
|
const generatedResult = await this.generateBlock(block.content);
|
122
128
|
if (!generatedResult) {
|
@@ -125,6 +131,9 @@ class StormCodegen {
|
|
125
131
|
const allFiles = this.toStormFiles(generatedResult);
|
126
132
|
// Send all the non-ai files to the stream
|
127
133
|
this.emitFiles((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, allFiles);
|
134
|
+
if (this.isAborted()) {
|
135
|
+
return;
|
136
|
+
}
|
128
137
|
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && file.type !== codegen_1.AIFileTypes.WEB_SCREEN);
|
129
138
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
130
139
|
if (uiTemplates.length > 0) {
|
@@ -138,8 +147,14 @@ class StormCodegen {
|
|
138
147
|
uiStream.on('data', (evt) => {
|
139
148
|
this.handleUiOutput((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, evt);
|
140
149
|
});
|
150
|
+
this.out.on('aborted', () => {
|
151
|
+
uiStream.abort();
|
152
|
+
});
|
141
153
|
await uiStream.waitForDone();
|
142
154
|
}
|
155
|
+
if (this.isAborted()) {
|
156
|
+
return;
|
157
|
+
}
|
143
158
|
// Gather the context files for implementation. These will be all be passed to the AI
|
144
159
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type));
|
145
160
|
// Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
|
@@ -148,6 +163,9 @@ class StormCodegen {
|
|
148
163
|
await this.processTemplates((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
149
164
|
}
|
150
165
|
const basePath = this.getBasePath(block.content.metadata.name);
|
166
|
+
if (this.isAborted()) {
|
167
|
+
return;
|
168
|
+
}
|
151
169
|
for (const serviceFile of serviceFiles) {
|
152
170
|
const filePath = (0, path_1.join)(basePath, serviceFile.filename);
|
153
171
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -192,6 +210,9 @@ class StormCodegen {
|
|
192
210
|
console.debug('Validation error:', result);
|
193
211
|
const errorStream = await stormClient_1.stormClient.createErrorClassification(result.error, []);
|
194
212
|
const fixes = new Map();
|
213
|
+
this.out.on('aborted', () => {
|
214
|
+
errorStream.abort();
|
215
|
+
});
|
195
216
|
errorStream.on('data', (evt) => {
|
196
217
|
if (evt.type === 'ERROR_CLASSIFIER') {
|
197
218
|
// find the file that caused the error
|
@@ -206,8 +227,8 @@ class StormCodegen {
|
|
206
227
|
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
207
228
|
.map((e) => e.filename)
|
208
229
|
.join('\n')}\n---\n${content}`;
|
209
|
-
console.log(`trying to fix the code in ${eventFileName}`);
|
210
|
-
console.debug(`with the fix:\n${fix}`);
|
230
|
+
//console.log(`trying to fix the code in ${eventFileName}`);
|
231
|
+
//console.debug(`with the fix:\n${fix}`);
|
211
232
|
const code = this.codeFix(fix);
|
212
233
|
fixes.set((0, path_1.join)(basePath, eventFileName), code);
|
213
234
|
}
|
@@ -252,6 +273,9 @@ class StormCodegen {
|
|
252
273
|
resolve(evt.payload.content);
|
253
274
|
}
|
254
275
|
});
|
276
|
+
this.out.on('aborted', () => {
|
277
|
+
fixStream.abort();
|
278
|
+
});
|
255
279
|
fixStream.on('error', (err) => {
|
256
280
|
reject(err);
|
257
281
|
});
|
@@ -307,6 +331,9 @@ class StormCodegen {
|
|
307
331
|
prompt: this.userPrompt,
|
308
332
|
});
|
309
333
|
const files = [];
|
334
|
+
this.out.on('aborted', () => {
|
335
|
+
stream.abort();
|
336
|
+
});
|
310
337
|
stream.on('data', (evt) => {
|
311
338
|
const file = this.handleTemplateFileOutput(blockUri, aiName, templateFile, evt);
|
312
339
|
if (file) {
|
@@ -357,6 +384,9 @@ class StormCodegen {
|
|
357
384
|
* Generates the code using codegen for a given block.
|
358
385
|
*/
|
359
386
|
async generateBlock(yamlContent) {
|
387
|
+
if (this.isAborted()) {
|
388
|
+
return;
|
389
|
+
}
|
360
390
|
if (!yamlContent.spec.target?.kind) {
|
361
391
|
//Not all block types have targets
|
362
392
|
return;
|
@@ -371,5 +401,8 @@ class StormCodegen {
|
|
371
401
|
new codegen_1.CodeWriter(basePath).write(generatedResult);
|
372
402
|
return generatedResult;
|
373
403
|
}
|
404
|
+
abort() {
|
405
|
+
this.out.abort();
|
406
|
+
}
|
374
407
|
}
|
375
408
|
exports.StormCodegen = StormCodegen;
|
@@ -2,7 +2,7 @@
|
|
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 {
|
@@ -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,
|
@@ -184,4 +184,16 @@ export interface StormEventDefinitionChange {
|
|
184
184
|
created: number;
|
185
185
|
payload: StormDefinitions;
|
186
186
|
}
|
187
|
-
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 = {}));
|
@@ -12,6 +12,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
12
|
const cors_1 = require("../middleware/cors");
|
13
13
|
const stringBody_1 = require("../middleware/stringBody");
|
14
14
|
const stormClient_1 = require("./stormClient");
|
15
|
+
const events_1 = require("./events");
|
15
16
|
const event_parser_1 = require("./event-parser");
|
16
17
|
const codegen_1 = require("./codegen");
|
17
18
|
const assetManager_1 = require("../assetManager");
|
@@ -27,15 +28,41 @@ router.post('/:handle/all', async (req, res) => {
|
|
27
28
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
28
29
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
29
30
|
const metaStream = await stormClient_1.stormClient.createMetadata(aiRequest.prompt, conversationId);
|
31
|
+
onRequestAborted(req, res, () => {
|
32
|
+
metaStream.abort();
|
33
|
+
});
|
30
34
|
res.set('Content-Type', 'application/x-ndjson');
|
31
35
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
32
36
|
res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
|
37
|
+
let currentPhase = events_1.StormEventPhaseType.META;
|
33
38
|
metaStream.on('data', (data) => {
|
34
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
|
+
}
|
35
51
|
sendEvent(res, data);
|
36
52
|
sendDefinitions(res, result);
|
37
53
|
});
|
38
|
-
|
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
|
+
}
|
39
66
|
if (!eventParser.isValid()) {
|
40
67
|
// We can't continue if the meta stream is invalid
|
41
68
|
sendEvent(res, {
|
@@ -48,32 +75,45 @@ router.post('/:handle/all', async (req, res) => {
|
|
48
75
|
return;
|
49
76
|
}
|
50
77
|
const result = eventParser.toResult(handle);
|
78
|
+
if (metaStream.isAborted()) {
|
79
|
+
return;
|
80
|
+
}
|
51
81
|
sendDefinitions(res, result);
|
52
82
|
if (!req.query.skipCodegen) {
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
+
}
|
57
98
|
}
|
58
99
|
sendDone(res);
|
59
100
|
}
|
60
101
|
catch (err) {
|
61
102
|
sendError(err, res);
|
62
|
-
res.
|
103
|
+
if (!res.closed) {
|
104
|
+
res.end();
|
105
|
+
}
|
63
106
|
}
|
64
107
|
});
|
65
108
|
router.post('/block/create', async (req, res) => {
|
66
109
|
const createRequest = JSON.parse(req.stringBody ?? '{}');
|
67
110
|
try {
|
68
111
|
const ymlPath = path_1.default.join(createRequest.newPath, 'kapeta.yml');
|
69
|
-
console.log('Creating block at', ymlPath);
|
70
112
|
const [asset] = await assetManager_1.assetManager.createAsset(ymlPath, createRequest.definition);
|
71
113
|
if (await fs_extra_1.default.pathExists(createRequest.tmpPath)) {
|
72
|
-
console.log('Moving block from', createRequest.tmpPath, 'to', createRequest.newPath);
|
73
114
|
await fs_extra_1.default.move(createRequest.tmpPath, createRequest.newPath, {
|
74
115
|
overwrite: true,
|
75
116
|
});
|
76
|
-
console.log('Updating asset', asset.ref);
|
77
117
|
res.send(await assetManager_1.assetManager.updateAsset(asset.ref, createRequest.definition));
|
78
118
|
}
|
79
119
|
else {
|
@@ -93,6 +133,9 @@ function sendDefinitions(res, result) {
|
|
93
133
|
});
|
94
134
|
}
|
95
135
|
function sendDone(res) {
|
136
|
+
if (res.closed) {
|
137
|
+
return;
|
138
|
+
}
|
96
139
|
sendEvent(res, {
|
97
140
|
type: 'DONE',
|
98
141
|
created: Date.now(),
|
@@ -100,6 +143,9 @@ function sendDone(res) {
|
|
100
143
|
res.end();
|
101
144
|
}
|
102
145
|
function sendError(err, res) {
|
146
|
+
if (res.closed) {
|
147
|
+
return;
|
148
|
+
}
|
103
149
|
console.error('Failed to send prompt', err);
|
104
150
|
if (res.headersSent) {
|
105
151
|
sendEvent(res, {
|
@@ -137,6 +183,17 @@ function streamStormPartialResponse(result, res) {
|
|
137
183
|
});
|
138
184
|
}
|
139
185
|
function sendEvent(res, evt) {
|
186
|
+
if (res.closed) {
|
187
|
+
return;
|
188
|
+
}
|
140
189
|
res.write(JSON.stringify(evt) + '\n');
|
141
190
|
}
|
191
|
+
function onRequestAborted(req, res, onAborted) {
|
192
|
+
req.on('close', () => {
|
193
|
+
onAborted();
|
194
|
+
});
|
195
|
+
res.on('close', () => {
|
196
|
+
onAborted();
|
197
|
+
});
|
198
|
+
}
|
142
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) {
|
@@ -10,17 +10,22 @@ import { BlockDefinition } from '@kapeta/schemas';
|
|
10
10
|
export declare class StormStream extends EventEmitter {
|
11
11
|
private conversationId;
|
12
12
|
private lines;
|
13
|
+
private aborted;
|
13
14
|
constructor(prompt?: string, conversationId?: string | null);
|
14
15
|
getConversationId(): string;
|
16
|
+
isAborted(): boolean;
|
15
17
|
addJSONLine(line: string): void;
|
16
18
|
end(): void;
|
17
19
|
on(event: 'end', listener: () => void): this;
|
20
|
+
on(event: 'aborted', listener: () => void): this;
|
18
21
|
on(event: 'error', listener: (e: Error) => void): this;
|
19
22
|
on(event: 'data', listener: (data: StormEvent) => void): this;
|
20
23
|
emit(event: 'end'): boolean;
|
24
|
+
emit(event: 'aborted'): void;
|
21
25
|
emit(event: 'error', e: Error): boolean;
|
22
26
|
emit(event: 'data', data: StormEvent): boolean;
|
23
27
|
waitForDone(): Promise<void>;
|
28
|
+
abort(): void;
|
24
29
|
}
|
25
30
|
export interface ConversationItem {
|
26
31
|
role: 'user' | 'model';
|
@@ -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;
|
@@ -14,6 +14,7 @@ export declare class StormCodegen {
|
|
14
14
|
private readonly conversationId;
|
15
15
|
constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
|
16
16
|
process(): Promise<void>;
|
17
|
+
isAborted(): boolean;
|
17
18
|
getStream(): StormStream;
|
18
19
|
private handleTemplateFileOutput;
|
19
20
|
private handleUiOutput;
|
@@ -47,4 +48,5 @@ export declare class StormCodegen {
|
|
47
48
|
* Generates the code using codegen for a given block.
|
48
49
|
*/
|
49
50
|
private generateBlock;
|
51
|
+
abort(): void;
|
50
52
|
}
|
@@ -63,6 +63,9 @@ class StormCodegen {
|
|
63
63
|
await Promise.all(promises);
|
64
64
|
this.out.end();
|
65
65
|
}
|
66
|
+
isAborted() {
|
67
|
+
return this.out.isAborted();
|
68
|
+
}
|
66
69
|
getStream() {
|
67
70
|
return this.out;
|
68
71
|
}
|
@@ -117,6 +120,9 @@ class StormCodegen {
|
|
117
120
|
* Generates the code for a block and sends it to the AI
|
118
121
|
*/
|
119
122
|
async processBlockCode(block) {
|
123
|
+
if (this.isAborted()) {
|
124
|
+
return;
|
125
|
+
}
|
120
126
|
// Generate the code for the block using the standard codegen templates
|
121
127
|
const generatedResult = await this.generateBlock(block.content);
|
122
128
|
if (!generatedResult) {
|
@@ -125,6 +131,9 @@ class StormCodegen {
|
|
125
131
|
const allFiles = this.toStormFiles(generatedResult);
|
126
132
|
// Send all the non-ai files to the stream
|
127
133
|
this.emitFiles((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, allFiles);
|
134
|
+
if (this.isAborted()) {
|
135
|
+
return;
|
136
|
+
}
|
128
137
|
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && file.type !== codegen_1.AIFileTypes.WEB_SCREEN);
|
129
138
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
130
139
|
if (uiTemplates.length > 0) {
|
@@ -138,8 +147,14 @@ class StormCodegen {
|
|
138
147
|
uiStream.on('data', (evt) => {
|
139
148
|
this.handleUiOutput((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, evt);
|
140
149
|
});
|
150
|
+
this.out.on('aborted', () => {
|
151
|
+
uiStream.abort();
|
152
|
+
});
|
141
153
|
await uiStream.waitForDone();
|
142
154
|
}
|
155
|
+
if (this.isAborted()) {
|
156
|
+
return;
|
157
|
+
}
|
143
158
|
// Gather the context files for implementation. These will be all be passed to the AI
|
144
159
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type));
|
145
160
|
// Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
|
@@ -148,6 +163,9 @@ class StormCodegen {
|
|
148
163
|
await this.processTemplates((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
149
164
|
}
|
150
165
|
const basePath = this.getBasePath(block.content.metadata.name);
|
166
|
+
if (this.isAborted()) {
|
167
|
+
return;
|
168
|
+
}
|
151
169
|
for (const serviceFile of serviceFiles) {
|
152
170
|
const filePath = (0, path_1.join)(basePath, serviceFile.filename);
|
153
171
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -192,6 +210,9 @@ class StormCodegen {
|
|
192
210
|
console.debug('Validation error:', result);
|
193
211
|
const errorStream = await stormClient_1.stormClient.createErrorClassification(result.error, []);
|
194
212
|
const fixes = new Map();
|
213
|
+
this.out.on('aborted', () => {
|
214
|
+
errorStream.abort();
|
215
|
+
});
|
195
216
|
errorStream.on('data', (evt) => {
|
196
217
|
if (evt.type === 'ERROR_CLASSIFIER') {
|
197
218
|
// find the file that caused the error
|
@@ -206,8 +227,8 @@ class StormCodegen {
|
|
206
227
|
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
207
228
|
.map((e) => e.filename)
|
208
229
|
.join('\n')}\n---\n${content}`;
|
209
|
-
console.log(`trying to fix the code in ${eventFileName}`);
|
210
|
-
console.debug(`with the fix:\n${fix}`);
|
230
|
+
//console.log(`trying to fix the code in ${eventFileName}`);
|
231
|
+
//console.debug(`with the fix:\n${fix}`);
|
211
232
|
const code = this.codeFix(fix);
|
212
233
|
fixes.set((0, path_1.join)(basePath, eventFileName), code);
|
213
234
|
}
|
@@ -252,6 +273,9 @@ class StormCodegen {
|
|
252
273
|
resolve(evt.payload.content);
|
253
274
|
}
|
254
275
|
});
|
276
|
+
this.out.on('aborted', () => {
|
277
|
+
fixStream.abort();
|
278
|
+
});
|
255
279
|
fixStream.on('error', (err) => {
|
256
280
|
reject(err);
|
257
281
|
});
|
@@ -307,6 +331,9 @@ class StormCodegen {
|
|
307
331
|
prompt: this.userPrompt,
|
308
332
|
});
|
309
333
|
const files = [];
|
334
|
+
this.out.on('aborted', () => {
|
335
|
+
stream.abort();
|
336
|
+
});
|
310
337
|
stream.on('data', (evt) => {
|
311
338
|
const file = this.handleTemplateFileOutput(blockUri, aiName, templateFile, evt);
|
312
339
|
if (file) {
|
@@ -357,6 +384,9 @@ class StormCodegen {
|
|
357
384
|
* Generates the code using codegen for a given block.
|
358
385
|
*/
|
359
386
|
async generateBlock(yamlContent) {
|
387
|
+
if (this.isAborted()) {
|
388
|
+
return;
|
389
|
+
}
|
360
390
|
if (!yamlContent.spec.target?.kind) {
|
361
391
|
//Not all block types have targets
|
362
392
|
return;
|
@@ -371,5 +401,8 @@ class StormCodegen {
|
|
371
401
|
new codegen_1.CodeWriter(basePath).write(generatedResult);
|
372
402
|
return generatedResult;
|
373
403
|
}
|
404
|
+
abort() {
|
405
|
+
this.out.abort();
|
406
|
+
}
|
374
407
|
}
|
375
408
|
exports.StormCodegen = StormCodegen;
|
@@ -2,7 +2,7 @@
|
|
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 {
|
@@ -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,
|