@kapeta/local-cluster-service 0.48.0 → 0.48.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/src/storm/codegen.d.ts +2 -0
- package/dist/cjs/src/storm/codegen.js +9 -2
- package/dist/cjs/src/storm/event-parser.d.ts +6 -1
- package/dist/cjs/src/storm/event-parser.js +34 -7
- package/dist/cjs/src/storm/events.d.ts +21 -2
- package/dist/cjs/src/storm/routes.js +13 -3
- package/dist/cjs/test/storm/event-parser.test.js +1 -1
- package/dist/esm/src/storm/codegen.d.ts +2 -0
- package/dist/esm/src/storm/codegen.js +9 -2
- package/dist/esm/src/storm/event-parser.d.ts +6 -1
- package/dist/esm/src/storm/event-parser.js +34 -7
- package/dist/esm/src/storm/events.d.ts +21 -2
- package/dist/esm/src/storm/routes.js +13 -3
- package/dist/esm/test/storm/event-parser.test.js +1 -1
- package/package.json +1 -1
- package/src/storm/codegen.ts +13 -4
- package/src/storm/event-parser.ts +39 -7
- package/src/storm/events.ts +22 -1
- package/src/storm/routes.ts +14 -3
- package/test/storm/event-parser.test.ts +1 -1
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.48.2](https://github.com/kapetacom/local-cluster-service/compare/v0.48.1...v0.48.2) (2024-06-03)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Include resource name for api and model ([#157](https://github.com/kapetacom/local-cluster-service/issues/157)) ([b79a704](https://github.com/kapetacom/local-cluster-service/commit/b79a704b74d11cb0ec0fc8346e29ed754b39fb12))
|
7
|
+
|
8
|
+
## [0.48.1](https://github.com/kapetacom/local-cluster-service/compare/v0.48.0...v0.48.1) (2024-06-03)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* Enrich events with block refs and instance ids ([#156](https://github.com/kapetacom/local-cluster-service/issues/156)) ([c45d5d7](https://github.com/kapetacom/local-cluster-service/commit/c45d5d776760a51fb786582d74cb4d049e27f0db))
|
14
|
+
|
1
15
|
# [0.48.0](https://github.com/kapetacom/local-cluster-service/compare/v0.47.4...v0.48.0) (2024-06-03)
|
2
16
|
|
3
17
|
|
@@ -10,12 +10,14 @@ export declare class StormCodegen {
|
|
10
10
|
private readonly blocks;
|
11
11
|
private readonly out;
|
12
12
|
private readonly events;
|
13
|
+
private readonly tmpDir;
|
13
14
|
constructor(userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
|
14
15
|
process(): Promise<void>;
|
15
16
|
getStream(): StormStream;
|
16
17
|
private handleTemplateFileOutput;
|
17
18
|
private handleUiOutput;
|
18
19
|
private handleFileOutput;
|
20
|
+
private getBasePath;
|
19
21
|
/**
|
20
22
|
* Generates the code for a block and sends it to the AI
|
21
23
|
*/
|
@@ -43,10 +43,12 @@ class StormCodegen {
|
|
43
43
|
blocks;
|
44
44
|
out = new stream_1.StormStream();
|
45
45
|
events;
|
46
|
+
tmpDir;
|
46
47
|
constructor(userPrompt, blocks, events) {
|
47
48
|
this.userPrompt = userPrompt;
|
48
49
|
this.blocks = blocks;
|
49
50
|
this.events = events;
|
51
|
+
this.tmpDir = node_os_1.default.tmpdir();
|
50
52
|
}
|
51
53
|
async process() {
|
52
54
|
for (const block of this.blocks) {
|
@@ -95,6 +97,9 @@ class StormCodegen {
|
|
95
97
|
};
|
96
98
|
}
|
97
99
|
}
|
100
|
+
getBasePath(blockName) {
|
101
|
+
return path_1.default.join(this.tmpDir, blockName);
|
102
|
+
}
|
98
103
|
/**
|
99
104
|
* Generates the code for a block and sends it to the AI
|
100
105
|
*/
|
@@ -129,7 +134,7 @@ class StormCodegen {
|
|
129
134
|
if (serviceFiles.length > 0) {
|
130
135
|
await this.processTemplates(block.uri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
131
136
|
}
|
132
|
-
const basePath =
|
137
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
133
138
|
for (const serviceFile of serviceFiles) {
|
134
139
|
const filePath = (0, path_1.join)(basePath, serviceFile.filename);
|
135
140
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -165,12 +170,14 @@ class StormCodegen {
|
|
165
170
|
});
|
166
171
|
}
|
167
172
|
emitFile(uri, blockName, filename, content, reason = 'File generated') {
|
173
|
+
const basePath = this.getBasePath(uri.fullName);
|
168
174
|
this.out.emit('data', {
|
169
175
|
type: 'FILE',
|
170
176
|
reason,
|
171
177
|
created: Date.now(),
|
172
178
|
payload: {
|
173
179
|
filename: filename,
|
180
|
+
path: (0, path_1.join)(basePath, filename),
|
174
181
|
content: content,
|
175
182
|
blockName,
|
176
183
|
blockRef: uri.toNormalizedString(),
|
@@ -245,7 +252,7 @@ class StormCodegen {
|
|
245
252
|
if (!(await codeGeneratorManager_1.codeGeneratorManager.ensureTarget(yamlContent.spec.target?.kind))) {
|
246
253
|
return;
|
247
254
|
}
|
248
|
-
const basePath =
|
255
|
+
const basePath = this.getBasePath(yamlContent.metadata.name);
|
249
256
|
const codeGenerator = new codegen_1.BlockCodeGenerator(yamlContent);
|
250
257
|
codeGenerator.withOption('AIContext', stormClient_1.STORM_ID);
|
251
258
|
const generatedResult = await codeGenerator.generate();
|
@@ -51,10 +51,15 @@ export declare class StormEventParser {
|
|
51
51
|
private options;
|
52
52
|
constructor(options: StormOptions);
|
53
53
|
private reset;
|
54
|
-
|
54
|
+
/**
|
55
|
+
* Builds plan and block definitions - and enriches events with relevant refs and ids
|
56
|
+
*/
|
57
|
+
processEvent(handle: string, evt: StormEvent): StormDefinitions;
|
55
58
|
getEvents(): StormEvent[];
|
56
59
|
isValid(): boolean;
|
57
60
|
getError(): string;
|
61
|
+
private toInstanceId;
|
62
|
+
private toInstanceIdFromRef;
|
58
63
|
toResult(handle: string): StormDefinitions;
|
59
64
|
private toSafeName;
|
60
65
|
private toRef;
|
@@ -122,9 +122,13 @@ class StormEventParser {
|
|
122
122
|
this.blocks = {};
|
123
123
|
this.connections = [];
|
124
124
|
}
|
125
|
-
|
125
|
+
/**
|
126
|
+
* Builds plan and block definitions - and enriches events with relevant refs and ids
|
127
|
+
*/
|
128
|
+
processEvent(handle, evt) {
|
129
|
+
let blockInfo;
|
126
130
|
this.events.push(evt);
|
127
|
-
console.log('
|
131
|
+
console.log('Processing event: %s', evt.type);
|
128
132
|
switch (evt.type) {
|
129
133
|
case 'CREATE_PLAN_PROPERTIES':
|
130
134
|
this.planName = evt.payload.name;
|
@@ -137,6 +141,8 @@ class StormEventParser {
|
|
137
141
|
models: [],
|
138
142
|
types: [],
|
139
143
|
};
|
144
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.name).toNormalizedString();
|
145
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
140
146
|
break;
|
141
147
|
case 'PLAN_RETRY':
|
142
148
|
this.reset();
|
@@ -146,15 +152,29 @@ class StormEventParser {
|
|
146
152
|
this.error = evt.payload.error;
|
147
153
|
break;
|
148
154
|
case 'CREATE_API':
|
149
|
-
this.blocks[evt.payload.blockName]
|
155
|
+
blockInfo = this.blocks[evt.payload.blockName];
|
156
|
+
blockInfo.apis.push(prettifyKaplang(evt.payload.content));
|
157
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
|
158
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
159
|
+
const api = blockInfo.resources.find((r) => r.type == 'API');
|
160
|
+
evt.payload.resourceName = api?.name;
|
161
|
+
break;
|
162
|
+
case 'CREATE_MODEL':
|
163
|
+
blockInfo = this.blocks[evt.payload.blockName];
|
164
|
+
blockInfo.models.push(prettifyKaplang(evt.payload.content));
|
165
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
|
166
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
167
|
+
const database = blockInfo.resources.find((r) => r.type == 'DATABASE');
|
168
|
+
evt.payload.resourceName = database?.name;
|
150
169
|
break;
|
151
170
|
case 'CREATE_TYPE':
|
152
171
|
this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
|
153
|
-
|
154
|
-
|
155
|
-
this.blocks[evt.payload.blockName].models.push(prettifyKaplang(evt.payload.content));
|
172
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
|
173
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
156
174
|
break;
|
157
175
|
case 'CREATE_CONNECTION':
|
176
|
+
evt.payload.fromBlockId = this.toInstanceId(handle, evt.payload.fromComponent);
|
177
|
+
evt.payload.toBlockId = this.toInstanceId(handle, evt.payload.toComponent);
|
158
178
|
this.connections.push(evt.payload);
|
159
179
|
break;
|
160
180
|
default:
|
@@ -176,13 +196,20 @@ class StormEventParser {
|
|
176
196
|
getError() {
|
177
197
|
return this.error;
|
178
198
|
}
|
199
|
+
toInstanceId(handle, blockName) {
|
200
|
+
const ref = this.toRef(handle, blockName);
|
201
|
+
return this.toInstanceIdFromRef(ref.toNormalizedString());
|
202
|
+
}
|
203
|
+
toInstanceIdFromRef(ref) {
|
204
|
+
return (0, uuid_1.v5)((0, nodejs_utils_1.normalizeKapetaUri)(ref), uuid_1.v5.URL);
|
205
|
+
}
|
179
206
|
toResult(handle) {
|
180
207
|
const planRef = this.toRef(handle, this.planName ?? 'undefined');
|
181
208
|
const blockDefinitions = this.toBlockDefinitions(handle);
|
182
209
|
const refIdMap = {};
|
183
210
|
const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
|
184
211
|
// Create a deterministic uuid
|
185
|
-
const id =
|
212
|
+
const id = this.toInstanceIdFromRef(ref);
|
186
213
|
refIdMap[ref] = id;
|
187
214
|
return {
|
188
215
|
id,
|
@@ -14,6 +14,8 @@ export interface StormBlockInfo {
|
|
14
14
|
name: string;
|
15
15
|
description: string;
|
16
16
|
}[];
|
17
|
+
blockRef?: string;
|
18
|
+
instanceId?: string;
|
17
19
|
}
|
18
20
|
export interface StormBlockInfoFilled extends StormBlockInfo {
|
19
21
|
apis: string[];
|
@@ -28,9 +30,11 @@ export interface StormEventCreateBlock {
|
|
28
30
|
}
|
29
31
|
export interface StormConnection {
|
30
32
|
fromComponent: string;
|
33
|
+
fromBlockId?: string;
|
31
34
|
fromResource: string;
|
32
35
|
fromResourceType: StormResourceType;
|
33
36
|
toComponent: string;
|
37
|
+
toBlockId?: string;
|
34
38
|
toResource: string;
|
35
39
|
toResourceType: StormResourceType;
|
36
40
|
}
|
@@ -66,12 +70,26 @@ export interface StormEventPlanRetry {
|
|
66
70
|
};
|
67
71
|
}
|
68
72
|
export interface StormEventCreateDSL {
|
69
|
-
type: '
|
73
|
+
type: 'CREATE_TYPE';
|
70
74
|
reason: string;
|
71
75
|
created: number;
|
72
76
|
payload: {
|
73
77
|
blockName: string;
|
74
78
|
content: string;
|
79
|
+
blockRef?: string;
|
80
|
+
instanceId?: string;
|
81
|
+
};
|
82
|
+
}
|
83
|
+
export interface StormEventCreateDSLResource extends Omit<StormEventCreateDSL, 'type'> {
|
84
|
+
type: 'CREATE_API' | 'CREATE_MODEL';
|
85
|
+
reason: string;
|
86
|
+
created: number;
|
87
|
+
payload: {
|
88
|
+
blockName: string;
|
89
|
+
content: string;
|
90
|
+
blockRef?: string;
|
91
|
+
instanceId?: string;
|
92
|
+
resourceName?: string;
|
75
93
|
};
|
76
94
|
}
|
77
95
|
export interface StormEventError {
|
@@ -116,6 +134,7 @@ export interface StormEventFile {
|
|
116
134
|
created: number;
|
117
135
|
payload: {
|
118
136
|
filename: string;
|
137
|
+
path: string;
|
119
138
|
content: string;
|
120
139
|
blockName: string;
|
121
140
|
blockRef: string;
|
@@ -131,4 +150,4 @@ export interface StormEventDefinitionChange {
|
|
131
150
|
created: number;
|
132
151
|
payload: StormDefinitions;
|
133
152
|
}
|
134
|
-
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
|
153
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
|
@@ -27,12 +27,12 @@ router.post('/:handle/all', async (req, res) => {
|
|
27
27
|
res.set('Content-Type', 'application/x-ndjson');
|
28
28
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
29
29
|
res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
|
30
|
-
console.log('metaStream.getConversationId()', metaStream.getConversationId());
|
31
30
|
metaStream.on('data', (data) => {
|
32
|
-
const result = eventParser.
|
31
|
+
const result = eventParser.processEvent(req.params.handle, data);
|
32
|
+
sendEvent(res, data);
|
33
33
|
sendDefinitions(res, result);
|
34
34
|
});
|
35
|
-
await
|
35
|
+
await waitForStormStream(metaStream);
|
36
36
|
if (!eventParser.isValid()) {
|
37
37
|
// We can't continue if the meta stream is invalid
|
38
38
|
sendEvent(res, {
|
@@ -88,6 +88,16 @@ function sendError(err, res) {
|
|
88
88
|
res.status(400).send({ error: err.message });
|
89
89
|
}
|
90
90
|
}
|
91
|
+
function waitForStormStream(result) {
|
92
|
+
return new Promise((resolve, reject) => {
|
93
|
+
result.on('error', (err) => {
|
94
|
+
reject(err);
|
95
|
+
});
|
96
|
+
result.on('end', () => {
|
97
|
+
resolve();
|
98
|
+
});
|
99
|
+
});
|
100
|
+
}
|
91
101
|
function streamStormPartialResponse(result, res) {
|
92
102
|
return new Promise((resolve, reject) => {
|
93
103
|
result.on('data', (data) => {
|
@@ -127,7 +127,7 @@ const events = [
|
|
127
127
|
describe('event-parser', () => {
|
128
128
|
it('it can parse events into a plan and blocks with proper layout', () => {
|
129
129
|
const parser = new event_parser_1.StormEventParser(parserOptions);
|
130
|
-
events.forEach((event) => parser.
|
130
|
+
events.forEach((event) => parser.processEvent('kapeta', event));
|
131
131
|
const result = parser.toResult('kapeta');
|
132
132
|
expect(result.plan.metadata.name).toBe('kapeta/my-plan');
|
133
133
|
expect(result.plan.metadata.description).toBe('my plan description');
|
@@ -10,12 +10,14 @@ export declare class StormCodegen {
|
|
10
10
|
private readonly blocks;
|
11
11
|
private readonly out;
|
12
12
|
private readonly events;
|
13
|
+
private readonly tmpDir;
|
13
14
|
constructor(userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
|
14
15
|
process(): Promise<void>;
|
15
16
|
getStream(): StormStream;
|
16
17
|
private handleTemplateFileOutput;
|
17
18
|
private handleUiOutput;
|
18
19
|
private handleFileOutput;
|
20
|
+
private getBasePath;
|
19
21
|
/**
|
20
22
|
* Generates the code for a block and sends it to the AI
|
21
23
|
*/
|
@@ -43,10 +43,12 @@ class StormCodegen {
|
|
43
43
|
blocks;
|
44
44
|
out = new stream_1.StormStream();
|
45
45
|
events;
|
46
|
+
tmpDir;
|
46
47
|
constructor(userPrompt, blocks, events) {
|
47
48
|
this.userPrompt = userPrompt;
|
48
49
|
this.blocks = blocks;
|
49
50
|
this.events = events;
|
51
|
+
this.tmpDir = node_os_1.default.tmpdir();
|
50
52
|
}
|
51
53
|
async process() {
|
52
54
|
for (const block of this.blocks) {
|
@@ -95,6 +97,9 @@ class StormCodegen {
|
|
95
97
|
};
|
96
98
|
}
|
97
99
|
}
|
100
|
+
getBasePath(blockName) {
|
101
|
+
return path_1.default.join(this.tmpDir, blockName);
|
102
|
+
}
|
98
103
|
/**
|
99
104
|
* Generates the code for a block and sends it to the AI
|
100
105
|
*/
|
@@ -129,7 +134,7 @@ class StormCodegen {
|
|
129
134
|
if (serviceFiles.length > 0) {
|
130
135
|
await this.processTemplates(block.uri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
131
136
|
}
|
132
|
-
const basePath =
|
137
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
133
138
|
for (const serviceFile of serviceFiles) {
|
134
139
|
const filePath = (0, path_1.join)(basePath, serviceFile.filename);
|
135
140
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -165,12 +170,14 @@ class StormCodegen {
|
|
165
170
|
});
|
166
171
|
}
|
167
172
|
emitFile(uri, blockName, filename, content, reason = 'File generated') {
|
173
|
+
const basePath = this.getBasePath(uri.fullName);
|
168
174
|
this.out.emit('data', {
|
169
175
|
type: 'FILE',
|
170
176
|
reason,
|
171
177
|
created: Date.now(),
|
172
178
|
payload: {
|
173
179
|
filename: filename,
|
180
|
+
path: (0, path_1.join)(basePath, filename),
|
174
181
|
content: content,
|
175
182
|
blockName,
|
176
183
|
blockRef: uri.toNormalizedString(),
|
@@ -245,7 +252,7 @@ class StormCodegen {
|
|
245
252
|
if (!(await codeGeneratorManager_1.codeGeneratorManager.ensureTarget(yamlContent.spec.target?.kind))) {
|
246
253
|
return;
|
247
254
|
}
|
248
|
-
const basePath =
|
255
|
+
const basePath = this.getBasePath(yamlContent.metadata.name);
|
249
256
|
const codeGenerator = new codegen_1.BlockCodeGenerator(yamlContent);
|
250
257
|
codeGenerator.withOption('AIContext', stormClient_1.STORM_ID);
|
251
258
|
const generatedResult = await codeGenerator.generate();
|
@@ -51,10 +51,15 @@ export declare class StormEventParser {
|
|
51
51
|
private options;
|
52
52
|
constructor(options: StormOptions);
|
53
53
|
private reset;
|
54
|
-
|
54
|
+
/**
|
55
|
+
* Builds plan and block definitions - and enriches events with relevant refs and ids
|
56
|
+
*/
|
57
|
+
processEvent(handle: string, evt: StormEvent): StormDefinitions;
|
55
58
|
getEvents(): StormEvent[];
|
56
59
|
isValid(): boolean;
|
57
60
|
getError(): string;
|
61
|
+
private toInstanceId;
|
62
|
+
private toInstanceIdFromRef;
|
58
63
|
toResult(handle: string): StormDefinitions;
|
59
64
|
private toSafeName;
|
60
65
|
private toRef;
|
@@ -122,9 +122,13 @@ class StormEventParser {
|
|
122
122
|
this.blocks = {};
|
123
123
|
this.connections = [];
|
124
124
|
}
|
125
|
-
|
125
|
+
/**
|
126
|
+
* Builds plan and block definitions - and enriches events with relevant refs and ids
|
127
|
+
*/
|
128
|
+
processEvent(handle, evt) {
|
129
|
+
let blockInfo;
|
126
130
|
this.events.push(evt);
|
127
|
-
console.log('
|
131
|
+
console.log('Processing event: %s', evt.type);
|
128
132
|
switch (evt.type) {
|
129
133
|
case 'CREATE_PLAN_PROPERTIES':
|
130
134
|
this.planName = evt.payload.name;
|
@@ -137,6 +141,8 @@ class StormEventParser {
|
|
137
141
|
models: [],
|
138
142
|
types: [],
|
139
143
|
};
|
144
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.name).toNormalizedString();
|
145
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
140
146
|
break;
|
141
147
|
case 'PLAN_RETRY':
|
142
148
|
this.reset();
|
@@ -146,15 +152,29 @@ class StormEventParser {
|
|
146
152
|
this.error = evt.payload.error;
|
147
153
|
break;
|
148
154
|
case 'CREATE_API':
|
149
|
-
this.blocks[evt.payload.blockName]
|
155
|
+
blockInfo = this.blocks[evt.payload.blockName];
|
156
|
+
blockInfo.apis.push(prettifyKaplang(evt.payload.content));
|
157
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
|
158
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
159
|
+
const api = blockInfo.resources.find((r) => r.type == 'API');
|
160
|
+
evt.payload.resourceName = api?.name;
|
161
|
+
break;
|
162
|
+
case 'CREATE_MODEL':
|
163
|
+
blockInfo = this.blocks[evt.payload.blockName];
|
164
|
+
blockInfo.models.push(prettifyKaplang(evt.payload.content));
|
165
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
|
166
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
167
|
+
const database = blockInfo.resources.find((r) => r.type == 'DATABASE');
|
168
|
+
evt.payload.resourceName = database?.name;
|
150
169
|
break;
|
151
170
|
case 'CREATE_TYPE':
|
152
171
|
this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
|
153
|
-
|
154
|
-
|
155
|
-
this.blocks[evt.payload.blockName].models.push(prettifyKaplang(evt.payload.content));
|
172
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
|
173
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
156
174
|
break;
|
157
175
|
case 'CREATE_CONNECTION':
|
176
|
+
evt.payload.fromBlockId = this.toInstanceId(handle, evt.payload.fromComponent);
|
177
|
+
evt.payload.toBlockId = this.toInstanceId(handle, evt.payload.toComponent);
|
158
178
|
this.connections.push(evt.payload);
|
159
179
|
break;
|
160
180
|
default:
|
@@ -176,13 +196,20 @@ class StormEventParser {
|
|
176
196
|
getError() {
|
177
197
|
return this.error;
|
178
198
|
}
|
199
|
+
toInstanceId(handle, blockName) {
|
200
|
+
const ref = this.toRef(handle, blockName);
|
201
|
+
return this.toInstanceIdFromRef(ref.toNormalizedString());
|
202
|
+
}
|
203
|
+
toInstanceIdFromRef(ref) {
|
204
|
+
return (0, uuid_1.v5)((0, nodejs_utils_1.normalizeKapetaUri)(ref), uuid_1.v5.URL);
|
205
|
+
}
|
179
206
|
toResult(handle) {
|
180
207
|
const planRef = this.toRef(handle, this.planName ?? 'undefined');
|
181
208
|
const blockDefinitions = this.toBlockDefinitions(handle);
|
182
209
|
const refIdMap = {};
|
183
210
|
const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
|
184
211
|
// Create a deterministic uuid
|
185
|
-
const id =
|
212
|
+
const id = this.toInstanceIdFromRef(ref);
|
186
213
|
refIdMap[ref] = id;
|
187
214
|
return {
|
188
215
|
id,
|
@@ -14,6 +14,8 @@ export interface StormBlockInfo {
|
|
14
14
|
name: string;
|
15
15
|
description: string;
|
16
16
|
}[];
|
17
|
+
blockRef?: string;
|
18
|
+
instanceId?: string;
|
17
19
|
}
|
18
20
|
export interface StormBlockInfoFilled extends StormBlockInfo {
|
19
21
|
apis: string[];
|
@@ -28,9 +30,11 @@ export interface StormEventCreateBlock {
|
|
28
30
|
}
|
29
31
|
export interface StormConnection {
|
30
32
|
fromComponent: string;
|
33
|
+
fromBlockId?: string;
|
31
34
|
fromResource: string;
|
32
35
|
fromResourceType: StormResourceType;
|
33
36
|
toComponent: string;
|
37
|
+
toBlockId?: string;
|
34
38
|
toResource: string;
|
35
39
|
toResourceType: StormResourceType;
|
36
40
|
}
|
@@ -66,12 +70,26 @@ export interface StormEventPlanRetry {
|
|
66
70
|
};
|
67
71
|
}
|
68
72
|
export interface StormEventCreateDSL {
|
69
|
-
type: '
|
73
|
+
type: 'CREATE_TYPE';
|
70
74
|
reason: string;
|
71
75
|
created: number;
|
72
76
|
payload: {
|
73
77
|
blockName: string;
|
74
78
|
content: string;
|
79
|
+
blockRef?: string;
|
80
|
+
instanceId?: string;
|
81
|
+
};
|
82
|
+
}
|
83
|
+
export interface StormEventCreateDSLResource extends Omit<StormEventCreateDSL, 'type'> {
|
84
|
+
type: 'CREATE_API' | 'CREATE_MODEL';
|
85
|
+
reason: string;
|
86
|
+
created: number;
|
87
|
+
payload: {
|
88
|
+
blockName: string;
|
89
|
+
content: string;
|
90
|
+
blockRef?: string;
|
91
|
+
instanceId?: string;
|
92
|
+
resourceName?: string;
|
75
93
|
};
|
76
94
|
}
|
77
95
|
export interface StormEventError {
|
@@ -116,6 +134,7 @@ export interface StormEventFile {
|
|
116
134
|
created: number;
|
117
135
|
payload: {
|
118
136
|
filename: string;
|
137
|
+
path: string;
|
119
138
|
content: string;
|
120
139
|
blockName: string;
|
121
140
|
blockRef: string;
|
@@ -131,4 +150,4 @@ export interface StormEventDefinitionChange {
|
|
131
150
|
created: number;
|
132
151
|
payload: StormDefinitions;
|
133
152
|
}
|
134
|
-
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
|
153
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
|
@@ -27,12 +27,12 @@ router.post('/:handle/all', async (req, res) => {
|
|
27
27
|
res.set('Content-Type', 'application/x-ndjson');
|
28
28
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
29
29
|
res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
|
30
|
-
console.log('metaStream.getConversationId()', metaStream.getConversationId());
|
31
30
|
metaStream.on('data', (data) => {
|
32
|
-
const result = eventParser.
|
31
|
+
const result = eventParser.processEvent(req.params.handle, data);
|
32
|
+
sendEvent(res, data);
|
33
33
|
sendDefinitions(res, result);
|
34
34
|
});
|
35
|
-
await
|
35
|
+
await waitForStormStream(metaStream);
|
36
36
|
if (!eventParser.isValid()) {
|
37
37
|
// We can't continue if the meta stream is invalid
|
38
38
|
sendEvent(res, {
|
@@ -88,6 +88,16 @@ function sendError(err, res) {
|
|
88
88
|
res.status(400).send({ error: err.message });
|
89
89
|
}
|
90
90
|
}
|
91
|
+
function waitForStormStream(result) {
|
92
|
+
return new Promise((resolve, reject) => {
|
93
|
+
result.on('error', (err) => {
|
94
|
+
reject(err);
|
95
|
+
});
|
96
|
+
result.on('end', () => {
|
97
|
+
resolve();
|
98
|
+
});
|
99
|
+
});
|
100
|
+
}
|
91
101
|
function streamStormPartialResponse(result, res) {
|
92
102
|
return new Promise((resolve, reject) => {
|
93
103
|
result.on('data', (data) => {
|
@@ -127,7 +127,7 @@ const events = [
|
|
127
127
|
describe('event-parser', () => {
|
128
128
|
it('it can parse events into a plan and blocks with proper layout', () => {
|
129
129
|
const parser = new event_parser_1.StormEventParser(parserOptions);
|
130
|
-
events.forEach((event) => parser.
|
130
|
+
events.forEach((event) => parser.processEvent('kapeta', event));
|
131
131
|
const result = parser.toResult('kapeta');
|
132
132
|
expect(result.plan.metadata.name).toBe('kapeta/my-plan');
|
133
133
|
expect(result.plan.metadata.description).toBe('my plan description');
|
package/package.json
CHANGED
package/src/storm/codegen.ts
CHANGED
@@ -23,11 +23,13 @@ export class StormCodegen {
|
|
23
23
|
private readonly blocks: BlockDefinitionInfo[];
|
24
24
|
private readonly out = new StormStream();
|
25
25
|
private readonly events: StormEvent[];
|
26
|
+
private readonly tmpDir: string;
|
26
27
|
|
27
28
|
constructor(userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]) {
|
28
29
|
this.userPrompt = userPrompt;
|
29
30
|
this.blocks = blocks;
|
30
31
|
this.events = events;
|
32
|
+
this.tmpDir = os.tmpdir();
|
31
33
|
}
|
32
34
|
|
33
35
|
public async process() {
|
@@ -83,6 +85,10 @@ export class StormCodegen {
|
|
83
85
|
}
|
84
86
|
}
|
85
87
|
|
88
|
+
private getBasePath(blockName: string) {
|
89
|
+
return path.join(this.tmpDir, blockName);
|
90
|
+
}
|
91
|
+
|
86
92
|
/**
|
87
93
|
* Generates the code for a block and sends it to the AI
|
88
94
|
*/
|
@@ -135,8 +141,8 @@ export class StormCodegen {
|
|
135
141
|
);
|
136
142
|
}
|
137
143
|
|
138
|
-
const basePath =
|
139
|
-
|
144
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
145
|
+
|
140
146
|
for (const serviceFile of serviceFiles) {
|
141
147
|
const filePath = join(basePath, serviceFile.filename);
|
142
148
|
await writeFile(filePath, serviceFile.content);
|
@@ -147,7 +153,7 @@ export class StormCodegen {
|
|
147
153
|
await writeFile(filePath, serviceFile.content);
|
148
154
|
}
|
149
155
|
|
150
|
-
for(const uiFile of uiTemplates){
|
156
|
+
for (const uiFile of uiTemplates) {
|
151
157
|
const filePath = join(basePath, uiFile.filename);
|
152
158
|
await writeFile(filePath, uiFile.content);
|
153
159
|
}
|
@@ -185,12 +191,15 @@ export class StormCodegen {
|
|
185
191
|
content: string,
|
186
192
|
reason: string = 'File generated'
|
187
193
|
) {
|
194
|
+
const basePath = this.getBasePath(uri.fullName);
|
195
|
+
|
188
196
|
this.out.emit('data', {
|
189
197
|
type: 'FILE',
|
190
198
|
reason,
|
191
199
|
created: Date.now(),
|
192
200
|
payload: {
|
193
201
|
filename: filename,
|
202
|
+
path: join(basePath, filename),
|
194
203
|
content: content,
|
195
204
|
blockName,
|
196
205
|
blockRef: uri.toNormalizedString(),
|
@@ -283,7 +292,7 @@ export class StormCodegen {
|
|
283
292
|
if (!(await codeGeneratorManager.ensureTarget(yamlContent.spec.target?.kind))) {
|
284
293
|
return;
|
285
294
|
}
|
286
|
-
const basePath =
|
295
|
+
const basePath = this.getBasePath(yamlContent.metadata.name);
|
287
296
|
|
288
297
|
const codeGenerator = new BlockCodeGenerator(yamlContent as BlockDefinition);
|
289
298
|
codeGenerator.withOption('AIContext', STORM_ID);
|
@@ -221,9 +221,13 @@ export class StormEventParser {
|
|
221
221
|
this.connections = [];
|
222
222
|
}
|
223
223
|
|
224
|
-
|
224
|
+
/**
|
225
|
+
* Builds plan and block definitions - and enriches events with relevant refs and ids
|
226
|
+
*/
|
227
|
+
public processEvent(handle: string, evt: StormEvent): StormDefinitions {
|
228
|
+
let blockInfo;
|
225
229
|
this.events.push(evt);
|
226
|
-
console.log('
|
230
|
+
console.log('Processing event: %s', evt.type);
|
227
231
|
switch (evt.type) {
|
228
232
|
case 'CREATE_PLAN_PROPERTIES':
|
229
233
|
this.planName = evt.payload.name;
|
@@ -236,6 +240,8 @@ export class StormEventParser {
|
|
236
240
|
models: [],
|
237
241
|
types: [],
|
238
242
|
};
|
243
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.name).toNormalizedString();
|
244
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
239
245
|
break;
|
240
246
|
case 'PLAN_RETRY':
|
241
247
|
this.reset();
|
@@ -245,15 +251,32 @@ export class StormEventParser {
|
|
245
251
|
this.error = evt.payload.error;
|
246
252
|
break;
|
247
253
|
case 'CREATE_API':
|
248
|
-
this.blocks[evt.payload.blockName]
|
254
|
+
blockInfo = this.blocks[evt.payload.blockName];
|
255
|
+
blockInfo.apis.push(prettifyKaplang(evt.payload.content));
|
256
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
|
257
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
258
|
+
|
259
|
+
const api = blockInfo.resources.find((r) => r.type == 'API');
|
260
|
+
evt.payload.resourceName = api?.name;
|
261
|
+
break;
|
262
|
+
case 'CREATE_MODEL':
|
263
|
+
blockInfo = this.blocks[evt.payload.blockName];
|
264
|
+
blockInfo.models.push(prettifyKaplang(evt.payload.content));
|
265
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
|
266
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
267
|
+
|
268
|
+
const database = blockInfo.resources.find((r) => r.type == 'DATABASE');
|
269
|
+
evt.payload.resourceName = database?.name;
|
249
270
|
break;
|
271
|
+
|
250
272
|
case 'CREATE_TYPE':
|
251
273
|
this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
|
252
|
-
|
253
|
-
|
254
|
-
this.blocks[evt.payload.blockName].models.push(prettifyKaplang(evt.payload.content));
|
274
|
+
evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
|
275
|
+
evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
|
255
276
|
break;
|
256
277
|
case 'CREATE_CONNECTION':
|
278
|
+
evt.payload.fromBlockId = this.toInstanceId(handle, evt.payload.fromComponent);
|
279
|
+
evt.payload.toBlockId = this.toInstanceId(handle, evt.payload.toComponent);
|
257
280
|
this.connections.push(evt.payload);
|
258
281
|
break;
|
259
282
|
|
@@ -281,13 +304,22 @@ export class StormEventParser {
|
|
281
304
|
return this.error;
|
282
305
|
}
|
283
306
|
|
307
|
+
private toInstanceId(handle: string, blockName: string) {
|
308
|
+
const ref = this.toRef(handle, blockName);
|
309
|
+
return this.toInstanceIdFromRef(ref.toNormalizedString());
|
310
|
+
}
|
311
|
+
|
312
|
+
private toInstanceIdFromRef(ref: string) {
|
313
|
+
return uuid(normalizeKapetaUri(ref), uuid.URL);
|
314
|
+
}
|
315
|
+
|
284
316
|
public toResult(handle: string): StormDefinitions {
|
285
317
|
const planRef = this.toRef(handle, this.planName ?? 'undefined');
|
286
318
|
const blockDefinitions = this.toBlockDefinitions(handle);
|
287
319
|
const refIdMap: { [key: string]: string } = {};
|
288
320
|
const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
|
289
321
|
// Create a deterministic uuid
|
290
|
-
const id =
|
322
|
+
const id = this.toInstanceIdFromRef(ref);
|
291
323
|
refIdMap[ref] = id;
|
292
324
|
return {
|
293
325
|
id,
|
package/src/storm/events.ts
CHANGED
@@ -30,6 +30,8 @@ export interface StormBlockInfo {
|
|
30
30
|
name: string;
|
31
31
|
description: string;
|
32
32
|
}[];
|
33
|
+
blockRef?: string;
|
34
|
+
instanceId?: string;
|
33
35
|
}
|
34
36
|
|
35
37
|
export interface StormBlockInfoFilled extends StormBlockInfo {
|
@@ -47,9 +49,11 @@ export interface StormEventCreateBlock {
|
|
47
49
|
|
48
50
|
export interface StormConnection {
|
49
51
|
fromComponent: string;
|
52
|
+
fromBlockId?: string;
|
50
53
|
fromResource: string;
|
51
54
|
fromResourceType: StormResourceType;
|
52
55
|
toComponent: string;
|
56
|
+
toBlockId?: string;
|
53
57
|
toResource: string;
|
54
58
|
toResourceType: StormResourceType;
|
55
59
|
}
|
@@ -90,12 +94,27 @@ export interface StormEventPlanRetry {
|
|
90
94
|
}
|
91
95
|
|
92
96
|
export interface StormEventCreateDSL {
|
93
|
-
type: '
|
97
|
+
type: 'CREATE_TYPE';
|
94
98
|
reason: string;
|
95
99
|
created: number;
|
96
100
|
payload: {
|
97
101
|
blockName: string;
|
98
102
|
content: string;
|
103
|
+
blockRef?: string;
|
104
|
+
instanceId?: string;
|
105
|
+
};
|
106
|
+
}
|
107
|
+
|
108
|
+
export interface StormEventCreateDSLResource extends Omit<StormEventCreateDSL, 'type'> {
|
109
|
+
type: 'CREATE_API' | 'CREATE_MODEL';
|
110
|
+
reason: string;
|
111
|
+
created: number;
|
112
|
+
payload: {
|
113
|
+
blockName: string;
|
114
|
+
content: string;
|
115
|
+
blockRef?: string;
|
116
|
+
instanceId?: string;
|
117
|
+
resourceName?: string;
|
99
118
|
};
|
100
119
|
}
|
101
120
|
|
@@ -145,6 +164,7 @@ export interface StormEventFile {
|
|
145
164
|
created: number;
|
146
165
|
payload: {
|
147
166
|
filename: string;
|
167
|
+
path: string;
|
148
168
|
content: string;
|
149
169
|
blockName: string;
|
150
170
|
blockRef: string;
|
@@ -170,6 +190,7 @@ export type StormEvent =
|
|
170
190
|
| StormEventInvalidResponse
|
171
191
|
| StormEventPlanRetry
|
172
192
|
| StormEventCreateDSL
|
193
|
+
| StormEventCreateDSLResource
|
173
194
|
| StormEventError
|
174
195
|
| StormEventScreen
|
175
196
|
| StormEventScreenCandidate
|
package/src/storm/routes.ts
CHANGED
@@ -35,15 +35,15 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
|
|
35
35
|
res.set('Content-Type', 'application/x-ndjson');
|
36
36
|
res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
37
37
|
res.set(ConversationIdHeader, metaStream.getConversationId());
|
38
|
-
console.log('metaStream.getConversationId()', metaStream.getConversationId());
|
39
38
|
|
40
39
|
metaStream.on('data', (data: StormEvent) => {
|
41
|
-
const result = eventParser.
|
40
|
+
const result = eventParser.processEvent(req.params.handle, data);
|
42
41
|
|
42
|
+
sendEvent(res, data);
|
43
43
|
sendDefinitions(res, result);
|
44
44
|
});
|
45
45
|
|
46
|
-
await
|
46
|
+
await waitForStormStream(metaStream);
|
47
47
|
|
48
48
|
if (!eventParser.isValid()) {
|
49
49
|
// We can't continue if the meta stream is invalid
|
@@ -109,6 +109,17 @@ function sendError(err: Error, res: Response) {
|
|
109
109
|
res.status(400).send({ error: err.message });
|
110
110
|
}
|
111
111
|
}
|
112
|
+
function waitForStormStream(result: StormStream) {
|
113
|
+
return new Promise<void>((resolve, reject) => {
|
114
|
+
result.on('error', (err) => {
|
115
|
+
reject(err);
|
116
|
+
});
|
117
|
+
|
118
|
+
result.on('end', () => {
|
119
|
+
resolve();
|
120
|
+
});
|
121
|
+
});
|
122
|
+
}
|
112
123
|
|
113
124
|
function streamStormPartialResponse(result: StormStream, res: Response) {
|
114
125
|
return new Promise<void>((resolve, reject) => {
|
@@ -139,7 +139,7 @@ const events: StormEvent[] = [
|
|
139
139
|
describe('event-parser', () => {
|
140
140
|
it('it can parse events into a plan and blocks with proper layout', () => {
|
141
141
|
const parser = new StormEventParser(parserOptions);
|
142
|
-
events.forEach((event) => parser.
|
142
|
+
events.forEach((event) => parser.processEvent('kapeta', event));
|
143
143
|
|
144
144
|
const result = parser.toResult('kapeta');
|
145
145
|
|