@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.
- package/.vscode/launch.json +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/cjs/src/storm/codegen.d.ts +7 -0
- package/dist/cjs/src/storm/codegen.js +84 -0
- package/dist/cjs/src/storm/event-parser.js +6 -3
- package/dist/cjs/src/storm/events.d.ts +21 -1
- package/dist/cjs/src/storm/stormClient.d.ts +3 -1
- package/dist/cjs/src/storm/stormClient.js +12 -0
- package/dist/esm/src/storm/codegen.d.ts +7 -0
- package/dist/esm/src/storm/codegen.js +84 -0
- package/dist/esm/src/storm/event-parser.js +6 -3
- package/dist/esm/src/storm/events.d.ts +21 -1
- package/dist/esm/src/storm/stormClient.d.ts +3 -1
- package/dist/esm/src/storm/stormClient.js +12 -0
- package/package.json +1 -1
- package/src/storm/codegen.ts +89 -1
- package/src/storm/event-parser.ts +6 -3
- package/src/storm/events.ts +25 -1
- package/src/storm/stormClient.ts +13 -1
package/.vscode/launch.json
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
"request": "launch",
|
7
7
|
"name": "Debug Node.js (npm run dev)",
|
8
8
|
"runtimeExecutable": "npm",
|
9
|
-
"runtimeArgs": ["run
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
package/src/storm/codegen.ts
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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;
|
package/src/storm/events.ts
CHANGED
@@ -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;
|
package/src/storm/stormClient.ts
CHANGED
@@ -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();
|