@kapeta/local-cluster-service 0.48.3 → 0.48.5

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.
@@ -6,7 +6,7 @@
6
6
  "request": "launch",
7
7
  "name": "Debug Node.js (npm run dev)",
8
8
  "runtimeExecutable": "npm",
9
- "runtimeArgs": ["run-script", "dev"],
9
+ "runtimeArgs": ["run", "start:dev"],
10
10
  "restart": true,
11
11
  "console": "integratedTerminal",
12
12
  "internalConsoleOptions": "neverOpen",
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.48.5](https://github.com/kapetacom/local-cluster-service/compare/v0.48.4...v0.48.5) (2024-06-04)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * we should read the file previously written ([fe0b78e](https://github.com/kapetacom/local-cluster-service/commit/fe0b78efd5018be28308170a0c76b2619425a158))
7
+
8
+ ## [0.48.4](https://github.com/kapetacom/local-cluster-service/compare/v0.48.3...v0.48.4) (2024-06-03)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Also prettify the kaplang on the event itself ([#158](https://github.com/kapetacom/local-cluster-service/issues/158)) ([63c27d2](https://github.com/kapetacom/local-cluster-service/commit/63c27d2e026b3e8ad4fbe1c181c402c42f7dec95))
14
+
1
15
  ## [0.48.3](https://github.com/kapetacom/local-cluster-service/compare/v0.48.2...v0.48.3) (2024-06-03)
2
16
 
3
17
 
@@ -22,6 +22,13 @@ export declare class StormCodegen {
22
22
  * Generates the code for a block and sends it to the AI
23
23
  */
24
24
  private processBlockCode;
25
+ private verifyAndFixCode;
26
+ removePrefix(prefix: string, str: string): string;
27
+ writeToFile(fileName: string, code: Promise<string>): Promise<void>;
28
+ /**
29
+ * Sends the code to the AI for a fix
30
+ */
31
+ private codeFix;
25
32
  /**
26
33
  * Emits the text-based files to the stream
27
34
  */
@@ -39,6 +39,7 @@ const stream_1 = require("./stream");
39
39
  const promises_1 = require("fs/promises");
40
40
  const path_1 = __importStar(require("path"));
41
41
  const node_os_1 = __importDefault(require("node:os"));
42
+ const fs_1 = require("fs");
42
43
  class StormCodegen {
43
44
  userPrompt;
44
45
  blocks;
@@ -154,6 +155,89 @@ class StormCodegen {
154
155
  const filePath = (0, path_1.join)(basePath, uiFile.filename);
155
156
  await (0, promises_1.writeFile)(filePath, uiFile.content);
156
157
  }
158
+ const filesToBeFixed = serviceFiles.concat(contextFiles);
159
+ const codeGenerator = new codegen_1.BlockCodeGenerator(block.content);
160
+ await this.verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles);
161
+ }
162
+ async verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, knownFiles) {
163
+ let attempts = 0;
164
+ let validCode = false;
165
+ for (let i = 0; i <= 3; i++) {
166
+ attempts++;
167
+ try {
168
+ console.log(`Validating the code in ${basePath} attempt #${attempts}`);
169
+ const result = await codeGenerator.validateForTarget(basePath);
170
+ if (result && result.valid) {
171
+ validCode = true;
172
+ break;
173
+ }
174
+ if (result && !result.valid) {
175
+ console.debug('Validation error:', result);
176
+ const errorStream = await stormClient_1.stormClient.createErrorClassification(result.error, []);
177
+ const fixes = new Map();
178
+ errorStream.on('data', (evt) => {
179
+ if (evt.type === 'ERROR_CLASSIFIER') {
180
+ // find the file that caused the error
181
+ // strip base path from event file name, if it exists sometimes the AI sends the full path
182
+ const eventFileName = this.removePrefix(basePath + '/', evt.payload.filename);
183
+ const file = filesToBeFixed.find((f) => f.filename === eventFileName);
184
+ if (!file) {
185
+ console.log(`Could not find the file ${eventFileName} in the list of files to be fixed, Henrik might wanna create a new file for this fix`);
186
+ }
187
+ // read the content of the file
188
+ const content = (0, fs_1.readFileSync)((0, path_1.join)(basePath, eventFileName), 'utf8');
189
+ const fix = `${evt.payload.potentialFix}\n---\n${knownFiles.map(e => e.filename).join("\n")}\n---\n${content}`;
190
+ console.log(`trying to fix the code in ${eventFileName}`);
191
+ console.debug(`with the fix:\n${fix}`);
192
+ const code = this.codeFix(fix);
193
+ fixes.set((0, path_1.join)(basePath, eventFileName), code);
194
+ }
195
+ });
196
+ await errorStream.waitForDone();
197
+ for (const [filename, codePromise] of fixes) {
198
+ const code = await codePromise;
199
+ (0, fs_1.writeFileSync)(filename, code);
200
+ }
201
+ }
202
+ }
203
+ catch (e) {
204
+ console.error('Error:', e);
205
+ }
206
+ }
207
+ if (validCode) {
208
+ console.log(`Validation successful after ${attempts} attempts`);
209
+ }
210
+ else {
211
+ console.error(`Validation failed for ${basePath} after ${attempts} attempts`);
212
+ }
213
+ }
214
+ removePrefix(prefix, str) {
215
+ if (str.startsWith(prefix)) {
216
+ return str.slice(prefix.length);
217
+ }
218
+ return str;
219
+ }
220
+ async writeToFile(fileName, code) {
221
+ console.log(`writing the fixed code to ${fileName}`);
222
+ const resolvedCode = await code;
223
+ (0, fs_1.writeFileSync)(fileName, resolvedCode);
224
+ }
225
+ /**
226
+ * Sends the code to the AI for a fix
227
+ */
228
+ async codeFix(fix) {
229
+ return new Promise(async (resolve, reject) => {
230
+ const fixStream = await stormClient_1.stormClient.createCodeFix(fix, []);
231
+ fixStream.on('data', (evt) => {
232
+ if (evt.type === 'CODE_FIX') {
233
+ resolve(evt.payload.content);
234
+ }
235
+ });
236
+ fixStream.on('error', (err) => {
237
+ reject(err);
238
+ });
239
+ await fixStream.waitForDone();
240
+ });
157
241
  }
158
242
  /**
159
243
  * Emits the text-based files to the stream
@@ -166,7 +166,8 @@ class StormEventParser {
166
166
  break;
167
167
  case 'CREATE_API':
168
168
  blockInfo = this.blocks[evt.payload.blockName];
169
- blockInfo.apis.push(prettifyKaplang(evt.payload.content));
169
+ evt.payload.content = prettifyKaplang(evt.payload.content);
170
+ blockInfo.apis.push(evt.payload.content);
170
171
  evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
171
172
  evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
172
173
  const api = blockInfo.resources.find((r) => r.type == 'API');
@@ -174,14 +175,16 @@ class StormEventParser {
174
175
  break;
175
176
  case 'CREATE_MODEL':
176
177
  blockInfo = this.blocks[evt.payload.blockName];
177
- blockInfo.models.push(prettifyKaplang(evt.payload.content));
178
+ evt.payload.content = prettifyKaplang(evt.payload.content);
179
+ blockInfo.models.push(evt.payload.content);
178
180
  evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
179
181
  evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
180
182
  const database = blockInfo.resources.find((r) => r.type == 'DATABASE');
181
183
  evt.payload.resourceName = database?.name;
182
184
  break;
183
185
  case 'CREATE_TYPE':
184
- this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
186
+ evt.payload.content = prettifyKaplang(evt.payload.content);
187
+ this.blocks[evt.payload.blockName].types.push(evt.payload.content);
185
188
  evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
186
189
  evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
187
190
  break;
@@ -100,6 +100,26 @@ export interface StormEventError {
100
100
  error: string;
101
101
  };
102
102
  }
103
+ export interface StormEventErrorClassifier {
104
+ type: 'ERROR_CLASSIFIER';
105
+ reason: string;
106
+ created: number;
107
+ payload: StormEventErrorClassifierInfo;
108
+ }
109
+ export interface StormEventCodeFix {
110
+ type: 'CODE_FIX';
111
+ reason: string;
112
+ created: number;
113
+ payload: {
114
+ filename: string;
115
+ content: string;
116
+ };
117
+ }
118
+ export interface StormEventErrorClassifierInfo {
119
+ error: string;
120
+ filename: string;
121
+ potentialFix: string;
122
+ }
103
123
  export interface ScreenTemplate {
104
124
  name: string;
105
125
  template: string;
@@ -153,4 +173,4 @@ export interface StormEventDefinitionChange {
153
173
  created: number;
154
174
  payload: StormDefinitions;
155
175
  }
156
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
176
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix;
@@ -1,4 +1,4 @@
1
- import { StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt } from './stream';
1
+ import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt } from './stream';
2
2
  export declare const STORM_ID = "storm";
3
3
  export declare const ConversationIdHeader = "Conversation-Id";
4
4
  declare class StormClient {
@@ -9,6 +9,8 @@ declare class StormClient {
9
9
  createMetadata(prompt: string, conversationId?: string): Promise<StormStream>;
10
10
  createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string): Promise<StormStream>;
11
11
  createServiceImplementation(prompt: StormFileImplementationPrompt, conversationId?: string): Promise<StormStream>;
12
+ createErrorClassification(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
13
+ createCodeFix(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
12
14
  }
13
15
  export declare const stormClient: StormClient;
14
16
  export {};
@@ -83,5 +83,17 @@ class StormClient {
83
83
  conversationId,
84
84
  });
85
85
  }
86
+ createErrorClassification(prompt, history, conversationId) {
87
+ return this.send('/v2/code/errorclassifier', {
88
+ conversationId: conversationId,
89
+ prompt,
90
+ });
91
+ }
92
+ createCodeFix(prompt, history, conversationId) {
93
+ return this.send('/v2/code/fix', {
94
+ conversationId: conversationId,
95
+ prompt,
96
+ });
97
+ }
86
98
  }
87
99
  exports.stormClient = new StormClient();
@@ -22,6 +22,13 @@ export declare class StormCodegen {
22
22
  * Generates the code for a block and sends it to the AI
23
23
  */
24
24
  private processBlockCode;
25
+ private verifyAndFixCode;
26
+ removePrefix(prefix: string, str: string): string;
27
+ writeToFile(fileName: string, code: Promise<string>): Promise<void>;
28
+ /**
29
+ * Sends the code to the AI for a fix
30
+ */
31
+ private codeFix;
25
32
  /**
26
33
  * Emits the text-based files to the stream
27
34
  */
@@ -39,6 +39,7 @@ const stream_1 = require("./stream");
39
39
  const promises_1 = require("fs/promises");
40
40
  const path_1 = __importStar(require("path"));
41
41
  const node_os_1 = __importDefault(require("node:os"));
42
+ const fs_1 = require("fs");
42
43
  class StormCodegen {
43
44
  userPrompt;
44
45
  blocks;
@@ -154,6 +155,89 @@ class StormCodegen {
154
155
  const filePath = (0, path_1.join)(basePath, uiFile.filename);
155
156
  await (0, promises_1.writeFile)(filePath, uiFile.content);
156
157
  }
158
+ const filesToBeFixed = serviceFiles.concat(contextFiles);
159
+ const codeGenerator = new codegen_1.BlockCodeGenerator(block.content);
160
+ await this.verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles);
161
+ }
162
+ async verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, knownFiles) {
163
+ let attempts = 0;
164
+ let validCode = false;
165
+ for (let i = 0; i <= 3; i++) {
166
+ attempts++;
167
+ try {
168
+ console.log(`Validating the code in ${basePath} attempt #${attempts}`);
169
+ const result = await codeGenerator.validateForTarget(basePath);
170
+ if (result && result.valid) {
171
+ validCode = true;
172
+ break;
173
+ }
174
+ if (result && !result.valid) {
175
+ console.debug('Validation error:', result);
176
+ const errorStream = await stormClient_1.stormClient.createErrorClassification(result.error, []);
177
+ const fixes = new Map();
178
+ errorStream.on('data', (evt) => {
179
+ if (evt.type === 'ERROR_CLASSIFIER') {
180
+ // find the file that caused the error
181
+ // strip base path from event file name, if it exists sometimes the AI sends the full path
182
+ const eventFileName = this.removePrefix(basePath + '/', evt.payload.filename);
183
+ const file = filesToBeFixed.find((f) => f.filename === eventFileName);
184
+ if (!file) {
185
+ console.log(`Could not find the file ${eventFileName} in the list of files to be fixed, Henrik might wanna create a new file for this fix`);
186
+ }
187
+ // read the content of the file
188
+ const content = (0, fs_1.readFileSync)((0, path_1.join)(basePath, eventFileName), 'utf8');
189
+ const fix = `${evt.payload.potentialFix}\n---\n${knownFiles.map(e => e.filename).join("\n")}\n---\n${content}`;
190
+ console.log(`trying to fix the code in ${eventFileName}`);
191
+ console.debug(`with the fix:\n${fix}`);
192
+ const code = this.codeFix(fix);
193
+ fixes.set((0, path_1.join)(basePath, eventFileName), code);
194
+ }
195
+ });
196
+ await errorStream.waitForDone();
197
+ for (const [filename, codePromise] of fixes) {
198
+ const code = await codePromise;
199
+ (0, fs_1.writeFileSync)(filename, code);
200
+ }
201
+ }
202
+ }
203
+ catch (e) {
204
+ console.error('Error:', e);
205
+ }
206
+ }
207
+ if (validCode) {
208
+ console.log(`Validation successful after ${attempts} attempts`);
209
+ }
210
+ else {
211
+ console.error(`Validation failed for ${basePath} after ${attempts} attempts`);
212
+ }
213
+ }
214
+ removePrefix(prefix, str) {
215
+ if (str.startsWith(prefix)) {
216
+ return str.slice(prefix.length);
217
+ }
218
+ return str;
219
+ }
220
+ async writeToFile(fileName, code) {
221
+ console.log(`writing the fixed code to ${fileName}`);
222
+ const resolvedCode = await code;
223
+ (0, fs_1.writeFileSync)(fileName, resolvedCode);
224
+ }
225
+ /**
226
+ * Sends the code to the AI for a fix
227
+ */
228
+ async codeFix(fix) {
229
+ return new Promise(async (resolve, reject) => {
230
+ const fixStream = await stormClient_1.stormClient.createCodeFix(fix, []);
231
+ fixStream.on('data', (evt) => {
232
+ if (evt.type === 'CODE_FIX') {
233
+ resolve(evt.payload.content);
234
+ }
235
+ });
236
+ fixStream.on('error', (err) => {
237
+ reject(err);
238
+ });
239
+ await fixStream.waitForDone();
240
+ });
157
241
  }
158
242
  /**
159
243
  * Emits the text-based files to the stream
@@ -166,7 +166,8 @@ class StormEventParser {
166
166
  break;
167
167
  case 'CREATE_API':
168
168
  blockInfo = this.blocks[evt.payload.blockName];
169
- blockInfo.apis.push(prettifyKaplang(evt.payload.content));
169
+ evt.payload.content = prettifyKaplang(evt.payload.content);
170
+ blockInfo.apis.push(evt.payload.content);
170
171
  evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
171
172
  evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
172
173
  const api = blockInfo.resources.find((r) => r.type == 'API');
@@ -174,14 +175,16 @@ class StormEventParser {
174
175
  break;
175
176
  case 'CREATE_MODEL':
176
177
  blockInfo = this.blocks[evt.payload.blockName];
177
- blockInfo.models.push(prettifyKaplang(evt.payload.content));
178
+ evt.payload.content = prettifyKaplang(evt.payload.content);
179
+ blockInfo.models.push(evt.payload.content);
178
180
  evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
179
181
  evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
180
182
  const database = blockInfo.resources.find((r) => r.type == 'DATABASE');
181
183
  evt.payload.resourceName = database?.name;
182
184
  break;
183
185
  case 'CREATE_TYPE':
184
- this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
186
+ evt.payload.content = prettifyKaplang(evt.payload.content);
187
+ this.blocks[evt.payload.blockName].types.push(evt.payload.content);
185
188
  evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
186
189
  evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
187
190
  break;
@@ -100,6 +100,26 @@ export interface StormEventError {
100
100
  error: string;
101
101
  };
102
102
  }
103
+ export interface StormEventErrorClassifier {
104
+ type: 'ERROR_CLASSIFIER';
105
+ reason: string;
106
+ created: number;
107
+ payload: StormEventErrorClassifierInfo;
108
+ }
109
+ export interface StormEventCodeFix {
110
+ type: 'CODE_FIX';
111
+ reason: string;
112
+ created: number;
113
+ payload: {
114
+ filename: string;
115
+ content: string;
116
+ };
117
+ }
118
+ export interface StormEventErrorClassifierInfo {
119
+ error: string;
120
+ filename: string;
121
+ potentialFix: string;
122
+ }
103
123
  export interface ScreenTemplate {
104
124
  name: string;
105
125
  template: string;
@@ -153,4 +173,4 @@ export interface StormEventDefinitionChange {
153
173
  created: number;
154
174
  payload: StormDefinitions;
155
175
  }
156
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
176
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix;
@@ -1,4 +1,4 @@
1
- import { StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt } from './stream';
1
+ import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt } from './stream';
2
2
  export declare const STORM_ID = "storm";
3
3
  export declare const ConversationIdHeader = "Conversation-Id";
4
4
  declare class StormClient {
@@ -9,6 +9,8 @@ declare class StormClient {
9
9
  createMetadata(prompt: string, conversationId?: string): Promise<StormStream>;
10
10
  createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string): Promise<StormStream>;
11
11
  createServiceImplementation(prompt: StormFileImplementationPrompt, conversationId?: string): Promise<StormStream>;
12
+ createErrorClassification(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
13
+ createCodeFix(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
12
14
  }
13
15
  export declare const stormClient: StormClient;
14
16
  export {};
@@ -83,5 +83,17 @@ class StormClient {
83
83
  conversationId,
84
84
  });
85
85
  }
86
+ createErrorClassification(prompt, history, conversationId) {
87
+ return this.send('/v2/code/errorclassifier', {
88
+ conversationId: conversationId,
89
+ prompt,
90
+ });
91
+ }
92
+ createCodeFix(prompt, history, conversationId) {
93
+ return this.send('/v2/code/fix', {
94
+ conversationId: conversationId,
95
+ prompt,
96
+ });
97
+ }
86
98
  }
87
99
  exports.stormClient = new StormClient();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.48.3",
3
+ "version": "0.48.5",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Definition } from '@kapeta/local-cluster-config';
7
- import { AIFileTypes, BlockCodeGenerator, CodeWriter, GeneratedFile, GeneratedResult } from '@kapeta/codegen';
7
+ import { AIFileTypes, BlockCodeGenerator, CodeGenerator, CodeWriter, GeneratedFile, GeneratedResult } from '@kapeta/codegen';
8
8
  import { BlockDefinition } from '@kapeta/schemas';
9
9
  import { codeGeneratorManager } from '../codeGeneratorManager';
10
10
  import { STORM_ID, stormClient } from './stormClient';
@@ -15,6 +15,7 @@ import { KapetaURI } from '@kapeta/nodejs-utils';
15
15
  import { writeFile } from 'fs/promises';
16
16
  import path, { join } from 'path';
17
17
  import os from 'node:os';
18
+ import { readFile, readFileSync, writeFileSync } from 'fs';
18
19
 
19
20
  type ImplementationGenerator = (prompt: StormFileImplementationPrompt, conversationId?: string) => Promise<StormStream>;
20
21
 
@@ -163,6 +164,93 @@ export class StormCodegen {
163
164
  const filePath = join(basePath, uiFile.filename);
164
165
  await writeFile(filePath, uiFile.content);
165
166
  }
167
+
168
+ const filesToBeFixed = serviceFiles.concat(contextFiles);
169
+ const codeGenerator = new BlockCodeGenerator(block.content as BlockDefinition);
170
+ await this.verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles);
171
+ }
172
+
173
+ private async verifyAndFixCode(codeGenerator: CodeGenerator, basePath: string, filesToBeFixed: StormFileInfo[], knownFiles: StormFileInfo[]) {
174
+ let attempts = 0;
175
+ let validCode = false;
176
+ for (let i = 0; i <= 3; i++) {
177
+ attempts++;
178
+ try {
179
+ console.log(`Validating the code in ${basePath} attempt #${attempts}`)
180
+ const result = await codeGenerator.validateForTarget(basePath);
181
+ if (result && result.valid) {
182
+ validCode = true;
183
+ break;
184
+ }
185
+
186
+ if (result && !result.valid) {
187
+ console.debug('Validation error:', result);
188
+ const errorStream = await stormClient.createErrorClassification(result.error, []);
189
+ const fixes = new Map<string, Promise<string>>();
190
+
191
+ errorStream.on('data', (evt) => {
192
+ if (evt.type === 'ERROR_CLASSIFIER') {
193
+ // find the file that caused the error
194
+ // strip base path from event file name, if it exists sometimes the AI sends the full path
195
+ const eventFileName = this.removePrefix(basePath+'/', evt.payload.filename);
196
+ const file = filesToBeFixed.find((f) => f.filename === eventFileName);
197
+ if(!file) {
198
+ console.log(`Could not find the file ${eventFileName} in the list of files to be fixed, Henrik might wanna create a new file for this fix`);
199
+ }
200
+ // read the content of the file
201
+ const content = readFileSync(join(basePath, eventFileName), 'utf8');
202
+ const fix = `${evt.payload.potentialFix}\n---\n${knownFiles.map(e => e.filename).join("\n")}\n---\n${content}`;
203
+ console.log(`trying to fix the code in ${eventFileName}`);
204
+ console.debug(`with the fix:\n${fix}`)
205
+ const code = this.codeFix(fix);
206
+ fixes.set(join(basePath, eventFileName), code);
207
+ }
208
+ });
209
+
210
+ await errorStream.waitForDone();
211
+ for (const [filename, codePromise] of fixes) {
212
+ const code = await codePromise;
213
+ writeFileSync(filename, code);
214
+ }
215
+ }
216
+ } catch (e) {
217
+ console.error('Error:', e);
218
+ }
219
+ }
220
+ if (validCode) {
221
+ console.log(`Validation successful after ${attempts} attempts`);
222
+ } else {
223
+ console.error(`Validation failed for ${basePath} after ${attempts} attempts`);
224
+ }
225
+ }
226
+
227
+ removePrefix(prefix: string, str: string): string {
228
+ if (str.startsWith(prefix)) {
229
+ return str.slice(prefix.length);
230
+ }
231
+ return str;
232
+ }
233
+ async writeToFile(fileName: string, code: Promise<string>) {
234
+ console.log(`writing the fixed code to ${fileName}`);
235
+ const resolvedCode = await code;
236
+ writeFileSync(fileName, resolvedCode);
237
+ }
238
+ /**
239
+ * Sends the code to the AI for a fix
240
+ */
241
+ private async codeFix(fix: string): Promise<string> {
242
+ return new Promise<string>(async (resolve, reject) => {
243
+ const fixStream = await stormClient.createCodeFix(fix, []);
244
+ fixStream.on('data', (evt) => {
245
+ if (evt.type === 'CODE_FIX') {
246
+ resolve(evt.payload.content);
247
+ }
248
+ });
249
+ fixStream.on('error', (err) => {
250
+ reject(err);
251
+ });
252
+ await fixStream.waitForDone();
253
+ });
166
254
  }
167
255
 
168
256
  /**
@@ -269,7 +269,8 @@ export class StormEventParser {
269
269
  break;
270
270
  case 'CREATE_API':
271
271
  blockInfo = this.blocks[evt.payload.blockName];
272
- blockInfo.apis.push(prettifyKaplang(evt.payload.content));
272
+ evt.payload.content = prettifyKaplang(evt.payload.content);
273
+ blockInfo.apis.push(evt.payload.content);
273
274
  evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
274
275
  evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
275
276
 
@@ -278,7 +279,8 @@ export class StormEventParser {
278
279
  break;
279
280
  case 'CREATE_MODEL':
280
281
  blockInfo = this.blocks[evt.payload.blockName];
281
- blockInfo.models.push(prettifyKaplang(evt.payload.content));
282
+ evt.payload.content = prettifyKaplang(evt.payload.content);
283
+ blockInfo.models.push(evt.payload.content);
282
284
  evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
283
285
  evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
284
286
 
@@ -287,7 +289,8 @@ export class StormEventParser {
287
289
  break;
288
290
 
289
291
  case 'CREATE_TYPE':
290
- this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
292
+ evt.payload.content = prettifyKaplang(evt.payload.content);
293
+ this.blocks[evt.payload.blockName].types.push(evt.payload.content);
291
294
  evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
292
295
  evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
293
296
  break;
@@ -127,6 +127,28 @@ export interface StormEventError {
127
127
  };
128
128
  }
129
129
 
130
+ export interface StormEventErrorClassifier {
131
+ type: 'ERROR_CLASSIFIER';
132
+ reason: string;
133
+ created: number;
134
+ payload: StormEventErrorClassifierInfo;
135
+ }
136
+
137
+ export interface StormEventCodeFix {
138
+ type: 'CODE_FIX';
139
+ reason: string;
140
+ created: number;
141
+ payload: {
142
+ filename: string;
143
+ content: string;
144
+ };
145
+ }
146
+ export interface StormEventErrorClassifierInfo {
147
+ error: string,
148
+ filename: string,
149
+ potentialFix: string
150
+ }
151
+
130
152
  export interface ScreenTemplate {
131
153
  name: string;
132
154
  template: string;
@@ -199,4 +221,6 @@ export type StormEvent =
199
221
  | StormEventScreenCandidate
200
222
  | StormEventFile
201
223
  | StormEventDone
202
- | StormEventDefinitionChange;
224
+ | StormEventDefinitionChange
225
+ | StormEventErrorClassifier
226
+ | StormEventCodeFix;
@@ -10,7 +10,6 @@ import {
10
10
  ConversationItem,
11
11
  StormContextRequest,
12
12
  StormFileImplementationPrompt,
13
- StormFileInfo,
14
13
  StormStream,
15
14
  StormUIImplementationPrompt,
16
15
  } from './stream';
@@ -111,6 +110,19 @@ class StormClient {
111
110
  conversationId,
112
111
  });
113
112
  }
113
+
114
+ public createErrorClassification(prompt: string, history?: ConversationItem[], conversationId?: string) {
115
+ return this.send('/v2/code/errorclassifier', {
116
+ conversationId: conversationId,
117
+ prompt,
118
+ });
119
+ }
120
+ public createCodeFix(prompt: string, history?: ConversationItem[], conversationId?: string) {
121
+ return this.send('/v2/code/fix', {
122
+ conversationId: conversationId,
123
+ prompt,
124
+ });
125
+ }
114
126
  }
115
127
 
116
128
  export const stormClient = new StormClient();