@kapeta/local-cluster-service 0.49.0 → 0.51.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/storm/codegen.d.ts +5 -2
- package/dist/cjs/src/storm/codegen.js +203 -27
- package/dist/cjs/src/storm/event-parser.d.ts +4 -1
- package/dist/cjs/src/storm/event-parser.js +21 -7
- package/dist/cjs/src/storm/events.d.ts +37 -9
- 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 +5 -2
- package/dist/esm/src/storm/codegen.js +203 -27
- package/dist/esm/src/storm/event-parser.d.ts +4 -1
- package/dist/esm/src/storm/event-parser.js +21 -7
- package/dist/esm/src/storm/events.d.ts +37 -9
- 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 +231 -33
- package/src/storm/event-parser.ts +29 -8
- package/src/storm/events.ts +48 -10
- package/src/storm/routes.ts +87 -23
- package/src/storm/stormClient.ts +8 -1
- package/src/storm/stream.ts +15 -0
@@ -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,10 +14,12 @@ 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;
|
20
|
-
private
|
21
|
+
private handleFileEvents;
|
22
|
+
private handleFileDoneOutput;
|
21
23
|
private getBasePath;
|
22
24
|
/**
|
23
25
|
* Generates the code for a block and sends it to the AI
|
@@ -33,7 +35,7 @@ export declare class StormCodegen {
|
|
33
35
|
/**
|
34
36
|
* Emits the text-based files to the stream
|
35
37
|
*/
|
36
|
-
private
|
38
|
+
private emitStaticFiles;
|
37
39
|
private emitFile;
|
38
40
|
/**
|
39
41
|
* Sends the template to the AI and processes the response
|
@@ -47,4 +49,5 @@ export declare class StormCodegen {
|
|
47
49
|
* Generates the code using codegen for a given block.
|
48
50
|
*/
|
49
51
|
private generateBlock;
|
52
|
+
abort(): void;
|
50
53
|
}
|
@@ -42,6 +42,49 @@ const path_1 = __importStar(require("path"));
|
|
42
42
|
const node_os_1 = __importDefault(require("node:os"));
|
43
43
|
const fs_1 = require("fs");
|
44
44
|
const path_2 = __importDefault(require("path"));
|
45
|
+
const SIMULATED_DELAY = 1000;
|
46
|
+
class SimulatedFileDelay {
|
47
|
+
file;
|
48
|
+
stream;
|
49
|
+
constructor(file, stream) {
|
50
|
+
this.file = file;
|
51
|
+
this.stream = stream;
|
52
|
+
}
|
53
|
+
async start() {
|
54
|
+
const commonPayload = {
|
55
|
+
filename: this.file.payload.filename,
|
56
|
+
path: this.file.payload.path,
|
57
|
+
blockName: this.file.payload.blockName,
|
58
|
+
blockRef: this.file.payload.blockRef,
|
59
|
+
instanceId: this.file.payload.instanceId,
|
60
|
+
};
|
61
|
+
this.stream.emit('data', {
|
62
|
+
type: 'FILE_START',
|
63
|
+
created: Date.now(),
|
64
|
+
reason: 'File start',
|
65
|
+
payload: commonPayload,
|
66
|
+
});
|
67
|
+
const lines = this.file.payload.content.split('\n');
|
68
|
+
const delayPerLine = SIMULATED_DELAY / lines.length;
|
69
|
+
for (const line of lines) {
|
70
|
+
await new Promise((resolve) => {
|
71
|
+
setTimeout(() => {
|
72
|
+
this.stream.emit('data', {
|
73
|
+
type: 'FILE_CHUNK',
|
74
|
+
created: Date.now(),
|
75
|
+
reason: 'File chunk',
|
76
|
+
payload: {
|
77
|
+
...commonPayload,
|
78
|
+
content: line,
|
79
|
+
},
|
80
|
+
});
|
81
|
+
resolve();
|
82
|
+
}, delayPerLine);
|
83
|
+
});
|
84
|
+
}
|
85
|
+
this.stream.emit('data', this.file);
|
86
|
+
}
|
87
|
+
}
|
45
88
|
class StormCodegen {
|
46
89
|
userPrompt;
|
47
90
|
blocks;
|
@@ -63,46 +106,139 @@ class StormCodegen {
|
|
63
106
|
await Promise.all(promises);
|
64
107
|
this.out.end();
|
65
108
|
}
|
109
|
+
isAborted() {
|
110
|
+
return this.out.isAborted();
|
111
|
+
}
|
66
112
|
getStream() {
|
67
113
|
return this.out;
|
68
114
|
}
|
69
115
|
handleTemplateFileOutput(blockUri, aiName, template, data) {
|
116
|
+
if (this.handleFileEvents(blockUri, aiName, data)) {
|
117
|
+
return;
|
118
|
+
}
|
70
119
|
switch (data.type) {
|
71
|
-
case '
|
120
|
+
case 'FILE_DONE':
|
72
121
|
template.filename = data.payload.filename;
|
73
122
|
template.content = data.payload.content;
|
74
|
-
|
123
|
+
this.handleFileDoneOutput(blockUri, aiName, data);
|
124
|
+
break;
|
75
125
|
}
|
76
126
|
}
|
77
|
-
handleUiOutput(blockUri,
|
127
|
+
handleUiOutput(blockUri, blockName, data) {
|
128
|
+
const blockRef = blockUri.toNormalizedString();
|
129
|
+
const instanceId = event_parser_1.StormEventParser.toInstanceIdFromRef(blockRef);
|
130
|
+
if (this.handleFileEvents(blockUri, blockName, data)) {
|
131
|
+
return;
|
132
|
+
}
|
78
133
|
switch (data.type) {
|
79
134
|
case 'SCREEN':
|
80
|
-
const ref = blockUri.toNormalizedString();
|
81
135
|
this.out.emit('data', {
|
82
136
|
type: 'SCREEN',
|
83
137
|
reason: data.reason,
|
84
138
|
created: Date.now(),
|
85
139
|
payload: {
|
86
140
|
...data.payload,
|
87
|
-
blockName
|
88
|
-
blockRef
|
89
|
-
instanceId
|
141
|
+
blockName,
|
142
|
+
blockRef,
|
143
|
+
instanceId,
|
144
|
+
},
|
145
|
+
});
|
146
|
+
break;
|
147
|
+
case 'FILE_START':
|
148
|
+
case 'FILE_CHUNK_RESET':
|
149
|
+
this.out.emit('data', {
|
150
|
+
...data,
|
151
|
+
payload: {
|
152
|
+
...data.payload,
|
153
|
+
blockName,
|
154
|
+
blockRef,
|
155
|
+
instanceId,
|
156
|
+
},
|
157
|
+
});
|
158
|
+
break;
|
159
|
+
case 'FILE_CHUNK':
|
160
|
+
this.out.emit('data', {
|
161
|
+
...data,
|
162
|
+
payload: {
|
163
|
+
...data.payload,
|
164
|
+
blockName,
|
165
|
+
blockRef,
|
166
|
+
instanceId,
|
90
167
|
},
|
91
168
|
});
|
92
|
-
|
93
|
-
|
169
|
+
break;
|
170
|
+
case 'FILE_STATE':
|
171
|
+
this.out.emit('data', {
|
172
|
+
...data,
|
173
|
+
payload: {
|
174
|
+
...data.payload,
|
175
|
+
blockName,
|
176
|
+
blockRef,
|
177
|
+
instanceId,
|
178
|
+
},
|
179
|
+
});
|
180
|
+
break;
|
181
|
+
case 'FILE_DONE':
|
182
|
+
this.handleFileDoneOutput(blockUri, blockName, data);
|
183
|
+
break;
|
94
184
|
}
|
95
185
|
}
|
96
|
-
|
186
|
+
handleFileEvents(blockUri, blockName, data) {
|
187
|
+
const blockRef = blockUri.toNormalizedString();
|
188
|
+
const instanceId = event_parser_1.StormEventParser.toInstanceIdFromRef(blockRef);
|
189
|
+
const basePath = this.getBasePath(blockUri.fullName);
|
97
190
|
switch (data.type) {
|
98
|
-
case '
|
191
|
+
case 'FILE_START':
|
192
|
+
case 'FILE_CHUNK_RESET':
|
193
|
+
this.out.emit('data', {
|
194
|
+
...data,
|
195
|
+
payload: {
|
196
|
+
...data.payload,
|
197
|
+
path: (0, path_1.join)(basePath, data.payload.filename),
|
198
|
+
blockName,
|
199
|
+
blockRef,
|
200
|
+
instanceId,
|
201
|
+
},
|
202
|
+
});
|
203
|
+
return true;
|
204
|
+
case 'FILE_CHUNK':
|
205
|
+
this.out.emit('data', {
|
206
|
+
...data,
|
207
|
+
payload: {
|
208
|
+
...data.payload,
|
209
|
+
path: (0, path_1.join)(basePath, data.payload.filename),
|
210
|
+
blockName,
|
211
|
+
blockRef,
|
212
|
+
instanceId,
|
213
|
+
},
|
214
|
+
});
|
215
|
+
return true;
|
216
|
+
case 'FILE_STATE':
|
217
|
+
this.out.emit('data', {
|
218
|
+
...data,
|
219
|
+
payload: {
|
220
|
+
...data.payload,
|
221
|
+
path: (0, path_1.join)(basePath, data.payload.filename),
|
222
|
+
blockName,
|
223
|
+
blockRef,
|
224
|
+
instanceId,
|
225
|
+
},
|
226
|
+
});
|
227
|
+
return true;
|
228
|
+
}
|
229
|
+
return false;
|
230
|
+
}
|
231
|
+
handleFileDoneOutput(blockUri, aiName, data) {
|
232
|
+
switch (data.type) {
|
233
|
+
case 'FILE_DONE':
|
99
234
|
const ref = blockUri.toNormalizedString();
|
100
235
|
this.emitFile(blockUri, aiName, data.payload.filename, data.payload.content, data.reason);
|
101
236
|
return {
|
102
|
-
type: '
|
237
|
+
type: 'FILE_DONE',
|
103
238
|
created: Date.now(),
|
104
239
|
payload: {
|
105
240
|
filename: data.payload.filename,
|
241
|
+
path: (0, path_1.join)(this.getBasePath(blockUri.fullName), data.payload.filename),
|
106
242
|
content: data.payload.content,
|
107
243
|
blockRef: ref,
|
108
244
|
instanceId: event_parser_1.StormEventParser.toInstanceIdFromRef(ref),
|
@@ -117,6 +253,9 @@ class StormCodegen {
|
|
117
253
|
* Generates the code for a block and sends it to the AI
|
118
254
|
*/
|
119
255
|
async processBlockCode(block) {
|
256
|
+
if (this.isAborted()) {
|
257
|
+
return;
|
258
|
+
}
|
120
259
|
// Generate the code for the block using the standard codegen templates
|
121
260
|
const generatedResult = await this.generateBlock(block.content);
|
122
261
|
if (!generatedResult) {
|
@@ -124,7 +263,10 @@ class StormCodegen {
|
|
124
263
|
}
|
125
264
|
const allFiles = this.toStormFiles(generatedResult);
|
126
265
|
// Send all the non-ai files to the stream
|
127
|
-
this.
|
266
|
+
await this.emitStaticFiles((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, allFiles);
|
267
|
+
if (this.isAborted()) {
|
268
|
+
return;
|
269
|
+
}
|
128
270
|
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && file.type !== codegen_1.AIFileTypes.WEB_SCREEN);
|
129
271
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
130
272
|
if (uiTemplates.length > 0) {
|
@@ -138,8 +280,14 @@ class StormCodegen {
|
|
138
280
|
uiStream.on('data', (evt) => {
|
139
281
|
this.handleUiOutput((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, evt);
|
140
282
|
});
|
283
|
+
this.out.on('aborted', () => {
|
284
|
+
uiStream.abort();
|
285
|
+
});
|
141
286
|
await uiStream.waitForDone();
|
142
287
|
}
|
288
|
+
if (this.isAborted()) {
|
289
|
+
return;
|
290
|
+
}
|
143
291
|
// Gather the context files for implementation. These will be all be passed to the AI
|
144
292
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type));
|
145
293
|
// Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
|
@@ -148,6 +296,9 @@ class StormCodegen {
|
|
148
296
|
await this.processTemplates((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
149
297
|
}
|
150
298
|
const basePath = this.getBasePath(block.content.metadata.name);
|
299
|
+
if (this.isAborted()) {
|
300
|
+
return;
|
301
|
+
}
|
151
302
|
for (const serviceFile of serviceFiles) {
|
152
303
|
const filePath = (0, path_1.join)(basePath, serviceFile.filename);
|
153
304
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -192,6 +343,9 @@ class StormCodegen {
|
|
192
343
|
console.debug('Validation error:', result);
|
193
344
|
const errorStream = await stormClient_1.stormClient.createErrorClassification(result.error, []);
|
194
345
|
const fixes = new Map();
|
346
|
+
this.out.on('aborted', () => {
|
347
|
+
errorStream.abort();
|
348
|
+
});
|
195
349
|
errorStream.on('data', (evt) => {
|
196
350
|
if (evt.type === 'ERROR_CLASSIFIER') {
|
197
351
|
// find the file that caused the error
|
@@ -206,8 +360,8 @@ class StormCodegen {
|
|
206
360
|
const fix = `${evt.payload.potentialFix}\n---\n${knownFiles
|
207
361
|
.map((e) => e.filename)
|
208
362
|
.join('\n')}\n---\n${content}`;
|
209
|
-
console.log(`trying to fix the code in ${eventFileName}`);
|
210
|
-
console.debug(`with the fix:\n${fix}`);
|
363
|
+
//console.log(`trying to fix the code in ${eventFileName}`);
|
364
|
+
//console.debug(`with the fix:\n${fix}`);
|
211
365
|
const code = this.codeFix(fix);
|
212
366
|
fixes.set((0, path_1.join)(basePath, eventFileName), code);
|
213
367
|
}
|
@@ -252,6 +406,9 @@ class StormCodegen {
|
|
252
406
|
resolve(evt.payload.content);
|
253
407
|
}
|
254
408
|
});
|
409
|
+
this.out.on('aborted', () => {
|
410
|
+
fixStream.abort();
|
411
|
+
});
|
255
412
|
fixStream.on('error', (err) => {
|
256
413
|
reject(err);
|
257
414
|
});
|
@@ -261,8 +418,8 @@ class StormCodegen {
|
|
261
418
|
/**
|
262
419
|
* Emits the text-based files to the stream
|
263
420
|
*/
|
264
|
-
|
265
|
-
files.
|
421
|
+
async emitStaticFiles(uri, aiName, files) {
|
422
|
+
const promises = files.map((file) => {
|
266
423
|
if (!file.content || typeof file.content !== 'string') {
|
267
424
|
return;
|
268
425
|
}
|
@@ -276,14 +433,30 @@ class StormCodegen {
|
|
276
433
|
// They will need to be implemented by the AI
|
277
434
|
return;
|
278
435
|
}
|
279
|
-
this.
|
436
|
+
const basePath = this.getBasePath(uri.fullName);
|
437
|
+
const ref = uri.toNormalizedString();
|
438
|
+
const fileEvent = {
|
439
|
+
type: 'FILE_DONE',
|
440
|
+
reason: 'File generated',
|
441
|
+
created: Date.now(),
|
442
|
+
payload: {
|
443
|
+
filename: file.filename,
|
444
|
+
path: (0, path_1.join)(basePath, file.filename),
|
445
|
+
content: file.content,
|
446
|
+
blockName: aiName,
|
447
|
+
blockRef: ref,
|
448
|
+
instanceId: event_parser_1.StormEventParser.toInstanceIdFromRef(ref),
|
449
|
+
},
|
450
|
+
};
|
451
|
+
return new SimulatedFileDelay(fileEvent, this.out).start();
|
280
452
|
});
|
453
|
+
return Promise.all(promises);
|
281
454
|
}
|
282
455
|
emitFile(uri, blockName, filename, content, reason = 'File generated') {
|
283
456
|
const basePath = this.getBasePath(uri.fullName);
|
284
457
|
const ref = uri.toNormalizedString();
|
285
458
|
this.out.emit('data', {
|
286
|
-
type: '
|
459
|
+
type: 'FILE_DONE',
|
287
460
|
reason,
|
288
461
|
created: Date.now(),
|
289
462
|
payload: {
|
@@ -306,18 +479,15 @@ class StormCodegen {
|
|
306
479
|
template: templateFile,
|
307
480
|
prompt: this.userPrompt,
|
308
481
|
});
|
309
|
-
|
482
|
+
this.out.on('aborted', () => {
|
483
|
+
stream.abort();
|
484
|
+
});
|
310
485
|
stream.on('data', (evt) => {
|
311
|
-
|
312
|
-
if (file) {
|
313
|
-
files.push(file);
|
314
|
-
}
|
486
|
+
this.handleTemplateFileOutput(blockUri, aiName, templateFile, evt);
|
315
487
|
});
|
316
488
|
await stream.waitForDone();
|
317
|
-
return files;
|
318
489
|
});
|
319
|
-
|
320
|
-
return fileChunks.flat();
|
490
|
+
await Promise.all(promises);
|
321
491
|
}
|
322
492
|
/**
|
323
493
|
* Converts the generated files to a format that can be sent to the AI
|
@@ -357,6 +527,9 @@ class StormCodegen {
|
|
357
527
|
* Generates the code using codegen for a given block.
|
358
528
|
*/
|
359
529
|
async generateBlock(yamlContent) {
|
530
|
+
if (this.isAborted()) {
|
531
|
+
return;
|
532
|
+
}
|
360
533
|
if (!yamlContent.spec.target?.kind) {
|
361
534
|
//Not all block types have targets
|
362
535
|
return;
|
@@ -371,5 +544,8 @@ class StormCodegen {
|
|
371
544
|
new codegen_1.CodeWriter(basePath).write(generatedResult);
|
372
545
|
return generatedResult;
|
373
546
|
}
|
547
|
+
abort() {
|
548
|
+
this.out.abort();
|
549
|
+
}
|
374
550
|
}
|
375
551
|
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;
|