@kapeta/local-cluster-service 0.52.3 → 0.53.1

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.
@@ -3,7 +3,7 @@
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
5
 
6
- import {Definition} from '@kapeta/local-cluster-config';
6
+ import { Definition } from '@kapeta/local-cluster-config';
7
7
  import {
8
8
  AIFileTypes,
9
9
  BlockCodeGenerator,
@@ -13,27 +13,27 @@ import {
13
13
  GeneratedResult,
14
14
  MODE_CREATE_ONLY,
15
15
  } from '@kapeta/codegen';
16
- import {BlockDefinition} from '@kapeta/schemas';
17
- import {codeGeneratorManager} from '../codeGeneratorManager';
18
- import {STORM_ID, stormClient} from './stormClient';
16
+ import { BlockDefinition } from '@kapeta/schemas';
17
+ import { codeGeneratorManager } from '../codeGeneratorManager';
18
+ import { STORM_ID, stormClient } from './stormClient';
19
19
  import {
20
20
  StormEvent,
21
+ StormEventBlockStatusType,
21
22
  StormEventErrorDetailsFile,
22
23
  StormEventFileChunk,
23
24
  StormEventFileDone,
24
- StormEventFileLogical
25
+ StormEventFileLogical,
25
26
  } from './events';
26
- import {BlockDefinitionInfo, StormEventParser} from './event-parser';
27
- import {ConversationItem, StormFileImplementationPrompt, StormFileInfo, StormStream} from './stream';
28
- import {KapetaURI, parseKapetaUri} from '@kapeta/nodejs-utils';
29
- import {writeFile} from 'fs/promises';
27
+ import { BlockDefinitionInfo, StormEventParser } from './event-parser';
28
+ import { ConversationItem, StormFileImplementationPrompt, StormFileInfo, StormStream } from './stream';
29
+ import { KapetaURI, parseKapetaUri } from '@kapeta/nodejs-utils';
30
+ import { writeFile } from 'fs/promises';
30
31
  import path from 'path';
31
- import Path, {join} from 'path';
32
+ import Path, { join } from 'path';
32
33
  import os from 'node:os';
33
- import {readFileSync, writeFileSync} from 'fs';
34
- import YAML from "yaml";
35
- import * as fs from "node:fs";
36
- import {v4 as uuidv4} from 'uuid';
34
+ import { readFileSync, writeFileSync } from 'fs';
35
+ import YAML from 'yaml';
36
+ import * as fs from 'node:fs';
37
37
 
38
38
  type ImplementationGenerator = (prompt: StormFileImplementationPrompt, conversationId?: string) => Promise<StormStream>;
39
39
 
@@ -44,7 +44,7 @@ interface ErrorClassification {
44
44
  }
45
45
 
46
46
  const SIMULATED_DELAY = 1000;
47
-
47
+ const ENABLE_SIMULATED_DELAY = false;
48
48
  class SimulatedFileDelay {
49
49
  private readonly file: StormEventFileDone;
50
50
  public readonly stream;
@@ -166,43 +166,8 @@ export class StormCodegen {
166
166
  },
167
167
  });
168
168
  break;
169
- case 'FILE_START':
170
- case 'FILE_CHUNK_RESET':
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_CHUNK':
182
- this.out.emit('data', {
183
- ...data,
184
- payload: {
185
- ...data.payload,
186
- blockName,
187
- blockRef,
188
- instanceId,
189
- },
190
- });
191
- break;
192
- case 'FILE_STATE':
193
- this.out.emit('data', {
194
- ...data,
195
- payload: {
196
- ...data.payload,
197
- blockName,
198
- blockRef,
199
- instanceId,
200
- },
201
- });
202
- break;
203
169
  case 'FILE_DONE':
204
170
  return this.handleFileDoneOutput(blockUri, blockName, data);
205
- break;
206
171
  }
207
172
  }
208
173
 
@@ -298,6 +263,8 @@ export class StormCodegen {
298
263
  return;
299
264
  }
300
265
 
266
+ const blockUri = parseKapetaUri(block.uri);
267
+
301
268
  const relevantFiles: StormFileInfo[] = allFiles.filter(
302
269
  (file) => file.type !== AIFileTypes.IGNORE && file.type !== AIFileTypes.WEB_SCREEN
303
270
  );
@@ -313,7 +280,7 @@ export class StormCodegen {
313
280
  });
314
281
 
315
282
  uiStream.on('data', (evt) => {
316
- const uiFile = this.handleUiOutput(parseKapetaUri(block.uri), block.aiName, evt);
283
+ const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
317
284
  if (uiFile != undefined) {
318
285
  screenFiles.push(uiFile);
319
286
  }
@@ -339,7 +306,7 @@ export class StormCodegen {
339
306
  const serviceFiles: StormFileInfo[] = allFiles.filter((file) => file.type === AIFileTypes.SERVICE);
340
307
  if (serviceFiles.length > 0) {
341
308
  await this.processTemplates(
342
- parseKapetaUri(block.uri),
309
+ blockUri,
343
310
  block.aiName,
344
311
  stormClient.createServiceImplementation.bind(stormClient),
345
312
  serviceFiles,
@@ -371,7 +338,7 @@ export class StormCodegen {
371
338
  await writeFile(filePath, screenFile.payload.content);
372
339
  }
373
340
 
374
- const screenFilesConverted = screenFiles.map(screenFile => {
341
+ const screenFilesConverted = screenFiles.map((screenFile) => {
375
342
  return {
376
343
  filename: screenFile.payload.filename,
377
344
  content: screenFile.payload.content,
@@ -381,12 +348,16 @@ export class StormCodegen {
381
348
  };
382
349
  });
383
350
  allFiles.push(...screenFilesConverted);
351
+ const blockRef = block.uri;
352
+
353
+ this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.QA);
384
354
 
385
355
  const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
386
356
  const codeGenerator = new BlockCodeGenerator(block.content as BlockDefinition);
387
- await this.verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles);
388
357
 
389
- const blockRef = block.uri;
358
+ this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.BUILDING);
359
+ await this.verifyAndFixCode(blockUri, block.aiName, codeGenerator, basePath, filesToBeFixed, allFiles);
360
+
390
361
  this.out.emit('data', {
391
362
  type: 'BLOCK_READY',
392
363
  reason: 'Block ready',
@@ -400,7 +371,23 @@ export class StormCodegen {
400
371
  } satisfies StormEvent);
401
372
  }
402
373
 
374
+ private emitBlockStatus(blockUri: KapetaURI, blockName: string, status: StormEventBlockStatusType) {
375
+ this.out.emit('data', {
376
+ type: 'BLOCK_STATUS',
377
+ reason: status,
378
+ created: Date.now(),
379
+ payload: {
380
+ status,
381
+ blockName,
382
+ blockRef: blockUri.toNormalizedString(),
383
+ instanceId: StormEventParser.toInstanceIdFromRef(blockUri.toNormalizedString()),
384
+ },
385
+ } satisfies StormEvent);
386
+ }
387
+
403
388
  private async verifyAndFixCode(
389
+ blockUri: KapetaURI,
390
+ blockName: string,
404
391
  codeGenerator: CodeGenerator,
405
392
  basePath: string,
406
393
  filesToBeFixed: StormFileInfo[],
@@ -421,11 +408,30 @@ export class StormCodegen {
421
408
  if (result && !result.valid) {
422
409
  console.debug('Validation error:', result);
423
410
 
411
+ this.emitBlockStatus(blockUri, blockName, StormEventBlockStatusType.PLANNING_FIX);
412
+
424
413
  const errors = await this.classifyErrors(result.error, basePath);
425
- for (const [filename, fileErrors] of errors.entries()) {
426
- // todo: only try to fix file if it is part of filesToBeFixed
427
- await this.tryToFixFile(basePath, filename, fileErrors, allFiles, codeGenerator);
414
+
415
+ if (errors.size > 0) {
416
+ this.emitBlockStatus(blockUri, blockName, StormEventBlockStatusType.FIXING);
417
+
418
+ const promises = Array.from(errors.entries()).map(([filename, fileErrors]) => {
419
+ // todo: only try to fix file if it is part of filesToBeFixed
420
+ return this.tryToFixFile(
421
+ blockUri,
422
+ blockName,
423
+ basePath,
424
+ filename,
425
+ fileErrors,
426
+ allFiles,
427
+ codeGenerator
428
+ );
429
+ });
430
+
431
+ await Promise.all(promises);
428
432
  }
433
+
434
+ this.emitBlockStatus(blockUri, blockName, StormEventBlockStatusType.FIX_DONE);
429
435
  }
430
436
  } catch (e) {
431
437
  console.error('Error:', e);
@@ -439,33 +445,57 @@ export class StormCodegen {
439
445
  }
440
446
  }
441
447
 
442
- private async tryToFixFile(basePath: string, filename: string, fileErrors: ErrorClassification[], allFiles: StormFileInfo[], codeGenerator: CodeGenerator) {
448
+ private async tryToFixFile(
449
+ blockUri: KapetaURI,
450
+ blockName: string,
451
+ basePath: string,
452
+ filename: string,
453
+ fileErrors: ErrorClassification[],
454
+ allFiles: StormFileInfo[],
455
+ codeGenerator: CodeGenerator
456
+ ) {
443
457
  console.log(`Processing ${filename}`);
444
458
  const language = await codeGenerator.language();
445
- const relevantFiles = allFiles.filter(file => file.type != AIFileTypes.IGNORE);
459
+ const relevantFiles = allFiles.filter((file) => file.type != AIFileTypes.IGNORE);
446
460
 
447
- for (let attempts = 1; attempts <= 5; attempts++) {
461
+ for (let attempts = 1; attempts <= 5; attempts++) {
448
462
  if (fileErrors.length == 0) {
449
463
  console.log(`No more errors for ${filename}`);
450
464
  return;
451
465
  }
452
466
 
453
467
  console.log(`Errors in ${filename} - requesting error details`);
454
- const filesForContext = await this.getErrorDetailsForFile(basePath, filename, fileErrors[0], relevantFiles, language);
468
+ const filesForContext = await this.getErrorDetailsForFile(
469
+ basePath,
470
+ filename,
471
+ fileErrors[0],
472
+ relevantFiles,
473
+ language
474
+ );
455
475
  console.log(`Get error details for ${filename} requesting code fixes`);
456
476
 
457
- const fix = this.createFixRequestForFile(basePath, filename, fileErrors[0], filesForContext, relevantFiles, language);
458
- const codeFixFiles = await this.codeFix(fix);
477
+ const fix = this.createFixRequestForFile(
478
+ basePath,
479
+ filename,
480
+ fileErrors[0],
481
+ filesForContext,
482
+ relevantFiles,
483
+ language
484
+ );
485
+ const codeFixFile = await this.codeFix(blockUri, blockName, fix);
459
486
  console.log(`Got fixed code for ${filename}`);
460
- for (const codeFixFile of codeFixFiles) {
461
- const filePath = codeFixFile.filename.indexOf(basePath) > -1 ? codeFixFile.filename : join(basePath, codeFixFile.filename);
462
- const existing = readFileSync(filePath);
463
- if (existing.toString().replace(/(\r\n|\r|\n)+$/, '') == codeFixFile.content.replace(/(\r\n|\r|\n)+$/, '')) {
464
- console.log(`${filename} not changed by gemini`);
465
- continue;
466
- }
467
- writeFileSync(filePath, codeFixFile.content);
487
+ const filePath =
488
+ codeFixFile.filename.indexOf(basePath) > -1
489
+ ? codeFixFile.filename
490
+ : join(basePath, codeFixFile.filename);
491
+ const existing = readFileSync(filePath);
492
+ if (
493
+ existing.toString().replace(/(\r\n|\r|\n)+$/, '') == codeFixFile.content.replace(/(\r\n|\r|\n)+$/, '')
494
+ ) {
495
+ console.log(`${filename} not changed by gemini`);
496
+ continue;
468
497
  }
498
+ writeFileSync(filePath, codeFixFile.content);
469
499
 
470
500
  const result = await codeGenerator.validateForTarget(basePath);
471
501
  if (result && result.valid) {
@@ -488,7 +518,11 @@ export class StormCodegen {
488
518
  errorStream.on('data', (evt) => {
489
519
  if (evt.type === 'ERROR_CLASSIFIER') {
490
520
  const eventFileName = this.removePrefix(basePath + '/', evt.payload.filename);
491
- const fix = { error: evt.payload.error, lineNumber: evt.payload.lineNumber, column: evt.payload.column };
521
+ const fix = {
522
+ error: evt.payload.error,
523
+ lineNumber: evt.payload.lineNumber,
524
+ column: evt.payload.column,
525
+ };
492
526
 
493
527
  let existingFixes = fixes.get(eventFileName);
494
528
  if (existingFixes) {
@@ -504,17 +538,23 @@ export class StormCodegen {
504
538
  return fixes;
505
539
  }
506
540
 
507
- private async getErrorDetailsForFile(basePath: string, filename: string, error: ErrorClassification, allFiles: StormFileInfo[], language: string): Promise<string[]> {
541
+ private async getErrorDetailsForFile(
542
+ basePath: string,
543
+ filename: string,
544
+ error: ErrorClassification,
545
+ allFiles: StormFileInfo[],
546
+ language: string
547
+ ): Promise<string[]> {
508
548
  const filePath = filename.indexOf(basePath) > -1 ? filename : join(basePath, filename); // to compensate when compiler returns absolute path
509
549
  return new Promise<string[]>(async (resolve, reject) => {
510
550
  const request = {
511
- "language": language,
512
- "sourceFile": {
513
- "filename": filename,
514
- "content": readFileSync(filePath, 'utf8')
551
+ language: language,
552
+ sourceFile: {
553
+ filename: filename,
554
+ content: readFileSync(filePath, 'utf8'),
515
555
  },
516
- "error": error,
517
- "projectFiles": allFiles.map(f => f.filename)
556
+ error: error,
557
+ projectFiles: allFiles.map((f) => f.filename),
518
558
  };
519
559
 
520
560
  const detailsStream = await stormClient.createErrorDetails(JSON.stringify(request), []);
@@ -522,11 +562,11 @@ export class StormCodegen {
522
562
  if (evt.type === 'ERROR_DETAILS') {
523
563
  resolve(evt.payload.files);
524
564
  }
525
- reject(new Error("Error details: Unexpected event [" + evt.type + "]"));
565
+ reject(new Error('Error details: Unexpected event [' + evt.type + ']'));
526
566
  });
527
567
  this.out.on('aborted', () => {
528
568
  detailsStream.abort();
529
- reject("aborted");
569
+ reject(new Error('aborted'));
530
570
  });
531
571
  detailsStream.on('error', (err) => {
532
572
  reject(err);
@@ -535,20 +575,25 @@ export class StormCodegen {
535
575
  });
536
576
  }
537
577
 
538
- private createFixRequestForFile(basePath: string, filename: string, error: ErrorClassification, filesForContext: string[], allFiles: StormFileInfo[], language: string): string {
578
+ private createFixRequestForFile(
579
+ basePath: string,
580
+ filename: string,
581
+ error: ErrorClassification,
582
+ filesForContext: string[],
583
+ allFiles: StormFileInfo[],
584
+ language: string
585
+ ): string {
539
586
  const files = new Set(filesForContext);
540
587
  files.add(filename);
541
588
 
542
- const requestedFiles = Array.from(files).flatMap(file => {
589
+ const requestedFiles = Array.from(files).flatMap((file) => {
543
590
  if (fs.existsSync(file)) {
544
591
  return file;
545
592
  }
546
593
 
547
594
  // file does not exist - look for similar
548
595
  const candidateName = file.split('/').pop();
549
- return allFiles
550
- .filter(file => file.filename.split('/').pop() === candidateName)
551
- .map(f => f.filename);
596
+ return allFiles.filter((file) => file.filename.split('/').pop() === candidateName).map((f) => f.filename);
552
597
  });
553
598
 
554
599
  const filePath = filename.indexOf(basePath) > -1 ? filename : join(basePath, filename);
@@ -556,31 +601,26 @@ export class StormCodegen {
556
601
  const affectedLine = this.getErrorLine(error, content);
557
602
 
558
603
  const fixRequest = {
559
- "language": language,
560
- "filename": filename,
561
- "errors": error.error,
562
- "affectedLine": affectedLine,
563
- "projectFiles": requestedFiles.map(filename => {
604
+ language: language,
605
+ filename: filename,
606
+ error: error.error,
607
+ affectedLine: affectedLine,
608
+ projectFiles: requestedFiles.map((filename) => {
564
609
  const filePath = filename.indexOf(basePath) > -1 ? filename : join(basePath, filename);
565
610
  const content = readFileSync(filePath, 'utf8');
566
611
  return { filename: filename, content: content };
567
612
  }),
568
- "conversationId": this.conversationId,
569
- "sessionId": uuidv4()
570
613
  };
571
614
 
572
615
  return JSON.stringify(fixRequest);
573
616
  }
574
617
 
575
- private getErrorLine(
576
- errorDetails: ErrorClassification,
577
- sourceCode: string
578
- ): string {
618
+ private getErrorLine(errorDetails: ErrorClassification, sourceCode: string): string {
579
619
  const lines = sourceCode.split('\n');
580
620
  const errorLine = lines[errorDetails.lineNumber - 1];
581
621
 
582
622
  if (!errorLine) {
583
- return "Error: Line number out of range.";
623
+ return 'Error: Line number out of range.';
584
624
  }
585
625
 
586
626
  return errorLine;
@@ -596,24 +636,41 @@ export class StormCodegen {
596
636
  /**
597
637
  * Sends the code to the AI for a fix
598
638
  */
599
- private async codeFix(fix: string, history?: ConversationItem[]): Promise<StormEventErrorDetailsFile[]> {
600
- return new Promise<StormEventErrorDetailsFile[]>(async (resolve, reject) => {
601
- const fixStream = await stormClient.createCodeFix(fix, history);
602
- fixStream.on('data', (evt) => {
603
- if (evt.type === 'CODE_FIX') {
604
- resolve(evt.payload.content.files);
605
- }
606
- reject(new Error("Error details: Unexpected event [" + evt.type + "]"));
607
- });
608
- this.out.on('aborted', () => {
609
- fixStream.abort();
610
- reject("aborted");
611
- });
612
- fixStream.on('error', (err) => {
613
- console.log("error", err);
614
- reject(err);
615
- });
616
- await fixStream.waitForDone();
639
+ private async codeFix(
640
+ blockUri: KapetaURI,
641
+ blockName: string,
642
+ fix: string,
643
+ history?: ConversationItem[]
644
+ ): Promise<StormEventErrorDetailsFile> {
645
+ return new Promise<StormEventErrorDetailsFile>(async (resolve, reject) => {
646
+ try {
647
+ const fixStream = await stormClient.createCodeFix(fix, history, this.conversationId);
648
+ let resolved = false;
649
+ fixStream.on('data', (evt) => {
650
+ if (this.handleFileEvents(blockUri, blockName, evt)) {
651
+ return;
652
+ }
653
+
654
+ if (evt.type === 'CODE_FIX') {
655
+ resolved = true;
656
+ resolve(evt.payload);
657
+ }
658
+ });
659
+ this.out.on('aborted', () => {
660
+ fixStream.abort();
661
+ reject(new Error('aborted'));
662
+ });
663
+ fixStream.on('error', (err) => {
664
+ reject(err);
665
+ });
666
+ fixStream.on('end', () => {
667
+ if (!resolved) {
668
+ reject(new Error('Code fix never returned a valid event'));
669
+ }
670
+ });
671
+ } catch (e) {
672
+ reject(e);
673
+ }
617
674
  });
618
675
  }
619
676
 
@@ -653,7 +710,13 @@ export class StormCodegen {
653
710
  instanceId: StormEventParser.toInstanceIdFromRef(ref),
654
711
  },
655
712
  };
656
- return new SimulatedFileDelay(fileEvent, this.out).start();
713
+
714
+ if (ENABLE_SIMULATED_DELAY) {
715
+ // Simulate a delay when sending the file
716
+ return new SimulatedFileDelay(fileEvent, this.out).start();
717
+ } else {
718
+ this.out.emit('data', fileEvent);
719
+ }
657
720
  });
658
721
 
659
722
  return Promise.all(promises);
@@ -181,10 +181,10 @@ export async function resolveOptions(): Promise<StormOptions> {
181
181
 
182
182
  return {
183
183
  serviceKind: normalizeKapetaUri(`${blockTypeService.definition.metadata.name}:${blockTypeService.version}`),
184
- serviceLanguage: normalizeKapetaUri('kapeta/language-target-java-spring-boot:local'), //normalizeKapetaUri(`${javaLanguage.definition.metadata.name}:${javaLanguage.version}`),
184
+ serviceLanguage: normalizeKapetaUri(`${javaLanguage.definition.metadata.name}:${javaLanguage.version}`),
185
185
 
186
186
  frontendKind: normalizeKapetaUri(`${blockTypeFrontend.definition.metadata.name}:${blockTypeFrontend.version}`),
187
- frontendLanguage: normalizeKapetaUri('kapeta/language-target-react-ts:local'), // normalizeKapetaUri(`${reactLanguage.definition.metadata.name}:${reactLanguage.version}`),
187
+ frontendLanguage: normalizeKapetaUri(`${reactLanguage.definition.metadata.name}:${reactLanguage.version}`),
188
188
 
189
189
  cliKind: normalizeKapetaUri(`${blockTypeCli.definition.metadata.name}:${blockTypeCli.version}`),
190
190
  cliLanguage: normalizeKapetaUri(`${nodejsLanguage.definition.metadata.name}:${nodejsLanguage.version}`),
@@ -270,7 +270,6 @@ export class StormEventParser {
270
270
  public processEvent(handle: string, evt: StormEvent): StormDefinitions {
271
271
  let blockInfo;
272
272
  this.events.push(evt);
273
- console.log('Processing event: %s', evt.type);
274
273
  switch (evt.type) {
275
274
  case 'CREATE_PLAN_PROPERTIES':
276
275
  this.planName = evt.payload.name;
@@ -431,9 +430,12 @@ export class StormEventParser {
431
430
  }
432
431
 
433
432
  const clientTypes = DSLDataTypeParser.parse(
434
- clientConsumerBlock.content.spec.entities.source!.value, {ignoreSemantics: true}
433
+ clientConsumerBlock.content.spec.entities.source!.value,
434
+ { ignoreSemantics: true }
435
435
  );
436
- const apiTypes = DSLDataTypeParser.parse(apiProviderBlock.content.spec.entities?.source?.value, {ignoreSemantics: true});
436
+ const apiTypes = DSLDataTypeParser.parse(apiProviderBlock.content.spec.entities?.source?.value, {
437
+ ignoreSemantics: true,
438
+ });
437
439
 
438
440
  apiTypes.forEach((apiType) => {
439
441
  if (clientTypes.some((clientType) => clientType.name === apiType.name)) {
@@ -139,9 +139,8 @@ export interface StormEventCodeFix {
139
139
  reason: string;
140
140
  created: number;
141
141
  payload: {
142
- content: {
143
- files: StormEventErrorDetailsFile[]
144
- }
142
+ filename: string;
143
+ content: string;
145
144
  };
146
145
  }
147
146
 
@@ -162,7 +161,7 @@ export interface StormEventErrorDetails {
162
161
  reason: string;
163
162
  created: number;
164
163
  payload: {
165
- files: string[]
164
+ files: string[];
166
165
  };
167
166
  }
168
167
 
@@ -252,6 +251,26 @@ export interface StormEventBlockReady {
252
251
  };
253
252
  }
254
253
 
254
+ export enum StormEventBlockStatusType {
255
+ QA = 'QA',
256
+ FIXING = 'FIXING',
257
+ PLANNING_FIX = 'PLANNING_FIX',
258
+ FIX_DONE = 'FIX_DONE',
259
+ BUILDING = 'BUILDING',
260
+ }
261
+
262
+ export interface StormEventBlockStatus {
263
+ type: 'BLOCK_STATUS';
264
+ reason: string;
265
+ created: number;
266
+ payload: {
267
+ status: StormEventBlockStatusType;
268
+ blockName: string;
269
+ blockRef: string;
270
+ instanceId: string;
271
+ };
272
+ }
273
+
255
274
  export interface StormEventDone {
256
275
  type: 'DONE';
257
276
  created: number;
@@ -268,6 +287,7 @@ export enum StormEventPhaseType {
268
287
  META = 'META',
269
288
  DEFINITIONS = 'DEFINITIONS',
270
289
  IMPLEMENTATION = 'IMPLEMENTATION',
290
+ QA = 'QA',
271
291
  }
272
292
 
273
293
  export interface StormEventPhases {
@@ -299,4 +319,5 @@ export type StormEvent =
299
319
  | StormEventCodeFix
300
320
  | StormEventErrorDetails
301
321
  | StormEventBlockReady
302
- | StormEventPhases;
322
+ | StormEventPhases
323
+ | StormEventBlockStatus;
@@ -26,7 +26,11 @@ class StormClient {
26
26
  this._baseUrl = getRemoteUrl('ai-service', 'https://ai.kapeta.com');
27
27
  }
28
28
 
29
- private async createOptions(path: string, method: string, body: StormContextRequest): Promise<RequestInit & { url: string }> {
29
+ private async createOptions(
30
+ path: string,
31
+ method: string,
32
+ body: StormContextRequest
33
+ ): Promise<RequestInit & { url: string }> {
30
34
  const url = `${this._baseUrl}${path}`;
31
35
  const headers: { [k: string]: string } = {
32
36
  'Content-Type': 'application/json',
@@ -39,7 +43,6 @@ class StormClient {
39
43
 
40
44
  if (body.conversationId) {
41
45
  headers[ConversationIdHeader] = body.conversationId;
42
- console.log('Setting ConversationIdHeader', headers[ConversationIdHeader]);
43
46
  }
44
47
 
45
48
  return {
@@ -92,7 +95,11 @@ class StormClient {
92
95
  });
93
96
 
94
97
  out.on('aborted', () => {
95
- abort.abort();
98
+ try {
99
+ abort.abort();
100
+ } catch (e) {
101
+ console.warn('Error aborting stream', e);
102
+ }
96
103
  });
97
104
 
98
105
  return out;
@@ -11,6 +11,7 @@ export class StormStream extends EventEmitter {
11
11
  private conversationId: string = '';
12
12
  private lines: string[] = [];
13
13
  private aborted: boolean = false;
14
+ private done: boolean = false;
14
15
 
15
16
  constructor(prompt: string = '', conversationId?: string | null) {
16
17
  super();
@@ -39,6 +40,7 @@ export class StormStream extends EventEmitter {
39
40
  }
40
41
 
41
42
  end() {
43
+ this.done = true;
42
44
  this.emit('end');
43
45
  }
44
46
 
@@ -59,6 +61,9 @@ export class StormStream extends EventEmitter {
59
61
  }
60
62
 
61
63
  waitForDone() {
64
+ if (this.done) {
65
+ return Promise.resolve();
66
+ }
62
67
  return new Promise<void>((resolve, reject) => {
63
68
  const errorHandler = (err: any) => {
64
69
  this.removeListener('error', errorHandler);
@@ -70,8 +75,8 @@ export class StormStream extends EventEmitter {
70
75
  this.removeListener('end', endHandler);
71
76
  resolve();
72
77
  };
73
- this.once('error', errorHandler);
74
- this.once('end', endHandler);
78
+ this.on('error', errorHandler);
79
+ this.on('end', endHandler);
75
80
  });
76
81
  }
77
82
 
@@ -80,6 +85,7 @@ export class StormStream extends EventEmitter {
80
85
  return;
81
86
  }
82
87
  this.aborted = true;
88
+ this.done = true;
83
89
  this.emit('aborted');
84
90
  }
85
91
  }