@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 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 ?? 'undefined');
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 ?? block.content.metadata.name,
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 type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventBlockReady;
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
- await waitForStormStream(metaStream);
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
- const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents());
54
- const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
55
- await stormCodegen.process();
56
- await codegenPromise;
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.end();
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 ?? 'undefined');
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 ?? block.content.metadata.name,
246
+ name: block.content.metadata.title || block.content.metadata.name,
229
247
  dimensions: {
230
248
  left: 0,
231
249
  top: 0,