@kapeta/local-cluster-service 0.53.4 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +1 -0
- package/CHANGELOG.md +17 -0
- package/dist/cjs/src/storm/codegen.js +68 -22
- package/dist/cjs/src/storm/event-parser.js +26 -11
- package/dist/cjs/src/storm/events.d.ts +1 -0
- package/dist/cjs/src/storm/stormClient.d.ts +2 -1
- package/dist/cjs/src/storm/stormClient.js +6 -0
- package/dist/cjs/src/storm/stream.d.ts +8 -0
- package/dist/cjs/test/storm/event-parser.test.js +18 -0
- package/dist/cjs/test/storm/simple-blog-events.json +470 -0
- package/dist/esm/src/storm/codegen.js +68 -22
- package/dist/esm/src/storm/event-parser.js +26 -11
- package/dist/esm/src/storm/events.d.ts +1 -0
- package/dist/esm/src/storm/stormClient.d.ts +2 -1
- package/dist/esm/src/storm/stormClient.js +6 -0
- package/dist/esm/src/storm/stream.d.ts +8 -0
- package/dist/esm/test/storm/event-parser.test.js +18 -0
- package/dist/esm/test/storm/simple-blog-events.json +470 -0
- package/package.json +2 -2
- package/src/storm/codegen.ts +101 -39
- package/src/storm/event-parser.ts +30 -13
- package/src/storm/events.ts +1 -0
- package/src/storm/stormClient.ts +9 -1
- package/src/storm/stream.ts +9 -0
- package/test/storm/event-parser.test.ts +19 -0
- package/test/storm/simple-blog-events.json +470 -0
- package/tsconfig.json +2 -1
package/.eslintrc.cjs
CHANGED
@@ -6,6 +6,7 @@ module.exports = {
|
|
6
6
|
rules: {
|
7
7
|
'@typescript-eslint/no-explicit-any': 'off',
|
8
8
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
9
|
+
'@typescript-eslint/no-use-before-define': 'off',
|
9
10
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
10
11
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
11
12
|
'@typescript-eslint/no-unsafe-return': 'off',
|
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
# [0.54.0](https://github.com/kapetacom/local-cluster-service/compare/v0.53.5...v0.54.0) (2024-06-17)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* add support for split UI routes + screen context in AI service ([#178](https://github.com/kapetacom/local-cluster-service/issues/178)) ([4330a72](https://github.com/kapetacom/local-cluster-service/commit/4330a72eddb5fe82f9e7a9c2af412d47091cf9b0))
|
7
|
+
|
8
|
+
## [0.53.5](https://github.com/kapetacom/local-cluster-service/compare/v0.53.4...v0.53.5) (2024-06-17)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* actually map to correct api ([dc88af0](https://github.com/kapetacom/local-cluster-service/commit/dc88af0f7a76867ed9f96e5653a1ceb7f9002f6d))
|
14
|
+
* pretty ([9efc6ed](https://github.com/kapetacom/local-cluster-service/commit/9efc6edd9bb7717889d37a2d1c0c227b10e57d47))
|
15
|
+
* split apis into multiple named resources ([da3b59f](https://github.com/kapetacom/local-cluster-service/commit/da3b59fb238f2035235b452491f3b170768e49d9))
|
16
|
+
* use type to determine controller or not ([bb88e9f](https://github.com/kapetacom/local-cluster-service/commit/bb88e9f0d349bc23fc7973e8525578e6dcb1d663))
|
17
|
+
|
1
18
|
## [0.53.4](https://github.com/kapetacom/local-cluster-service/compare/v0.53.3...v0.53.4) (2024-06-13)
|
2
19
|
|
3
20
|
|
@@ -44,7 +44,6 @@ const path_2 = __importStar(require("path"));
|
|
44
44
|
const node_os_1 = __importDefault(require("node:os"));
|
45
45
|
const fs_1 = require("fs");
|
46
46
|
const yaml_1 = __importDefault(require("yaml"));
|
47
|
-
const fs = __importStar(require("node:fs"));
|
48
47
|
const SIMULATED_DELAY = 1000;
|
49
48
|
const ENABLE_SIMULATED_DELAY = false;
|
50
49
|
class SimulatedFileDelay {
|
@@ -239,8 +238,11 @@ class StormCodegen {
|
|
239
238
|
return;
|
240
239
|
}
|
241
240
|
const blockUri = (0, nodejs_utils_1.parseKapetaUri)(block.uri);
|
242
|
-
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE &&
|
241
|
+
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE &&
|
242
|
+
file.type !== codegen_1.AIFileTypes.WEB_SCREEN &&
|
243
|
+
file.type !== codegen_1.AIFileTypes.WEB_ROUTER);
|
243
244
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
245
|
+
const webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
244
246
|
const screenFiles = [];
|
245
247
|
let filteredEvents = [];
|
246
248
|
for (const event of this.events) {
|
@@ -249,14 +251,55 @@ class StormCodegen {
|
|
249
251
|
filteredEvents = [];
|
250
252
|
}
|
251
253
|
}
|
252
|
-
|
253
|
-
|
254
|
+
const screenEvents = [];
|
255
|
+
// generate screens
|
256
|
+
const screenStream = await stormClient_1.stormClient.listScreens({
|
257
|
+
events: filteredEvents,
|
258
|
+
templates: uiTemplates,
|
259
|
+
context: relevantFiles,
|
260
|
+
blockName: block.aiName,
|
261
|
+
prompt: this.userPrompt,
|
262
|
+
});
|
263
|
+
screenStream.on('data', (evt) => {
|
264
|
+
if (evt.type === 'SCREEN') {
|
265
|
+
screenEvents.push(evt);
|
266
|
+
}
|
267
|
+
this.handleUiOutput(blockUri, block.aiName, evt);
|
268
|
+
});
|
269
|
+
this.out.on('aborted', () => {
|
270
|
+
screenStream.abort();
|
271
|
+
});
|
272
|
+
await screenStream.waitForDone();
|
273
|
+
// screenfiles
|
274
|
+
const screenTemplates = screenEvents
|
275
|
+
.map((screenEvent) => ({
|
276
|
+
...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
|
277
|
+
filename: screenEvent.payload.filename,
|
278
|
+
}))
|
279
|
+
.filter((tpl) => !!tpl.content);
|
280
|
+
await Promise.all(screenTemplates.concat(webRouters).map(async (template) => {
|
281
|
+
const payload = {
|
254
282
|
events: filteredEvents,
|
255
|
-
templates: uiTemplates,
|
256
|
-
context: relevantFiles,
|
257
283
|
blockName: block.aiName,
|
284
|
+
filename: template.filename,
|
285
|
+
template: template,
|
286
|
+
context: relevantFiles.concat([
|
287
|
+
{
|
288
|
+
type: codegen_1.AIFileTypes.INSTRUCTIONS,
|
289
|
+
mode: codegen_1.MODE_CREATE_ONLY,
|
290
|
+
permissions: '0644',
|
291
|
+
filename: '<screens>.md',
|
292
|
+
content: `
|
293
|
+
# Generated screens
|
294
|
+
|
295
|
+
${JSON.stringify({ screenEvents })}
|
296
|
+
|
297
|
+
`,
|
298
|
+
},
|
299
|
+
]),
|
258
300
|
prompt: this.userPrompt,
|
259
|
-
}
|
301
|
+
};
|
302
|
+
const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
|
260
303
|
uiStream.on('data', (evt) => {
|
261
304
|
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
262
305
|
if (uiFile != undefined) {
|
@@ -267,21 +310,34 @@ class StormCodegen {
|
|
267
310
|
uiStream.abort();
|
268
311
|
});
|
269
312
|
await uiStream.waitForDone();
|
270
|
-
}
|
313
|
+
}));
|
271
314
|
if (this.isAborted()) {
|
272
315
|
return;
|
273
316
|
}
|
317
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
318
|
+
const screenFilesConverted = screenFiles.map((screenFile) => {
|
319
|
+
return {
|
320
|
+
filename: screenFile.payload.filename,
|
321
|
+
content: screenFile.payload.content,
|
322
|
+
mode: codegen_1.MODE_CREATE_ONLY,
|
323
|
+
permissions: '0644',
|
324
|
+
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
325
|
+
};
|
326
|
+
});
|
274
327
|
// Gather the context files for implementation. These will be all be passed to the AI
|
275
|
-
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type));
|
328
|
+
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
276
329
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
277
330
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
278
331
|
if (serviceFiles.length > 0) {
|
279
332
|
await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
280
333
|
}
|
281
|
-
const basePath = this.getBasePath(block.content.metadata.name);
|
282
334
|
if (this.isAborted()) {
|
283
335
|
return;
|
284
336
|
}
|
337
|
+
for (const screenFile of screenFilesConverted) {
|
338
|
+
const filePath = (0, path_2.join)(basePath, screenFile.filename);
|
339
|
+
await (0, promises_1.writeFile)(filePath, screenFile.content);
|
340
|
+
}
|
285
341
|
for (const serviceFile of serviceFiles) {
|
286
342
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
287
343
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -296,16 +352,6 @@ class StormCodegen {
|
|
296
352
|
const filePath = (0, path_2.join)(basePath, screenFile.payload.filename);
|
297
353
|
await (0, promises_1.writeFile)(filePath, screenFile.payload.content);
|
298
354
|
}
|
299
|
-
const screenFilesConverted = screenFiles.map((screenFile) => {
|
300
|
-
return {
|
301
|
-
filename: screenFile.payload.filename,
|
302
|
-
content: screenFile.payload.content,
|
303
|
-
mode: codegen_1.MODE_CREATE_ONLY,
|
304
|
-
permissions: '0644',
|
305
|
-
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
306
|
-
};
|
307
|
-
});
|
308
|
-
allFiles.push(...screenFilesConverted);
|
309
355
|
const blockRef = block.uri;
|
310
356
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
|
311
357
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
@@ -466,7 +512,7 @@ class StormCodegen {
|
|
466
512
|
const files = new Set(filesForContext);
|
467
513
|
files.add(filename);
|
468
514
|
const requestedFiles = Array.from(files).flatMap((file) => {
|
469
|
-
if (
|
515
|
+
if ((0, fs_1.existsSync)(file)) {
|
470
516
|
return file;
|
471
517
|
}
|
472
518
|
// file does not exist - look for similar
|
@@ -551,7 +597,7 @@ class StormCodegen {
|
|
551
597
|
// They will need to be implemented by the AI
|
552
598
|
return;
|
553
599
|
}
|
554
|
-
if (
|
600
|
+
if ([codegen_1.AIFileTypes.WEB_ROUTER, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type)) {
|
555
601
|
// Don't send the web screen files to the stream yet
|
556
602
|
// They will need to be implemented by the AI
|
557
603
|
return;
|
@@ -211,13 +211,13 @@ class StormEventParser {
|
|
211
211
|
this.connections.push(evt.payload);
|
212
212
|
break;
|
213
213
|
case 'API_RETRY':
|
214
|
-
Object.values(this.blocks).forEach(block => {
|
214
|
+
Object.values(this.blocks).forEach((block) => {
|
215
215
|
block.types = [];
|
216
216
|
block.apis = [];
|
217
217
|
});
|
218
218
|
break;
|
219
219
|
case 'MODEL_RETRY':
|
220
|
-
Object.values(this.blocks).forEach(block => {
|
220
|
+
Object.values(this.blocks).forEach((block) => {
|
221
221
|
block.models = [];
|
222
222
|
});
|
223
223
|
break;
|
@@ -384,7 +384,7 @@ class StormEventParser {
|
|
384
384
|
},
|
385
385
|
};
|
386
386
|
const blockSpec = blockDefinitionInfo.content.spec;
|
387
|
-
|
387
|
+
const apiResources = {};
|
388
388
|
let dbResource = undefined;
|
389
389
|
blockInfo.resources.forEach((resource) => {
|
390
390
|
const port = {
|
@@ -392,10 +392,7 @@ class StormEventParser {
|
|
392
392
|
};
|
393
393
|
switch (resource.type) {
|
394
394
|
case 'API':
|
395
|
-
|
396
|
-
break;
|
397
|
-
}
|
398
|
-
apiResource = {
|
395
|
+
const apiResource = {
|
399
396
|
kind: this.toResourceKind(resource.type),
|
400
397
|
metadata: {
|
401
398
|
name: resource.name,
|
@@ -411,6 +408,7 @@ class StormEventParser {
|
|
411
408
|
},
|
412
409
|
},
|
413
410
|
};
|
411
|
+
apiResources[resource.name] = apiResource;
|
414
412
|
blockSpec.providers.push(apiResource);
|
415
413
|
break;
|
416
414
|
case 'CLIENT':
|
@@ -491,11 +489,28 @@ class StormEventParser {
|
|
491
489
|
});
|
492
490
|
}
|
493
491
|
});
|
494
|
-
|
495
|
-
|
496
|
-
|
492
|
+
blockInfo.apis.forEach((api) => {
|
493
|
+
const dslApi = kaplang_core_1.DSLAPIParser.parse(api, {
|
494
|
+
ignoreSemantics: true,
|
497
495
|
});
|
498
|
-
|
496
|
+
let exactMatch = false;
|
497
|
+
if (dslApi[0] && dslApi[0].type == kaplang_core_1.DSLEntityType.CONTROLLER) {
|
498
|
+
const name = dslApi[0].name.toLowerCase();
|
499
|
+
const apiResourceName = Object.keys(apiResources).find((key) => key.indexOf(name) > -1);
|
500
|
+
if (apiResourceName) {
|
501
|
+
const exactResource = apiResources[apiResourceName];
|
502
|
+
exactResource.spec.source.value += api + '\n\n';
|
503
|
+
exactMatch = true;
|
504
|
+
}
|
505
|
+
}
|
506
|
+
if (!exactMatch) {
|
507
|
+
// if we couldn't place the given api on the exact resource we just park it on the first
|
508
|
+
// available rest resource
|
509
|
+
const firstKey = Object.keys(apiResources)[0];
|
510
|
+
const firstEntry = apiResources[firstKey];
|
511
|
+
firstEntry.spec.source.value += api + '\n\n';
|
512
|
+
}
|
513
|
+
});
|
499
514
|
blockInfo.types.forEach((type) => {
|
500
515
|
blockSpec.entities.source.value += type + '\n';
|
501
516
|
});
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt } from './stream';
|
1
|
+
import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
|
2
2
|
export declare const STORM_ID = "storm";
|
3
3
|
export declare const ConversationIdHeader = "Conversation-Id";
|
4
4
|
declare class StormClient {
|
@@ -7,6 +7,7 @@ declare class StormClient {
|
|
7
7
|
private createOptions;
|
8
8
|
private send;
|
9
9
|
createMetadata(prompt: string, conversationId?: string): Promise<StormStream>;
|
10
|
+
listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
|
10
11
|
createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string): Promise<StormStream>;
|
11
12
|
createServiceImplementation(prompt: StormFileImplementationPrompt, conversationId?: string): Promise<StormStream>;
|
12
13
|
createErrorClassification(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
@@ -80,6 +80,12 @@ class StormClient {
|
|
80
80
|
conversationId,
|
81
81
|
});
|
82
82
|
}
|
83
|
+
listScreens(prompt, conversationId) {
|
84
|
+
return this.send('/v2/ui/list', {
|
85
|
+
prompt,
|
86
|
+
conversationId,
|
87
|
+
});
|
88
|
+
}
|
83
89
|
createUIImplementation(prompt, conversationId) {
|
84
90
|
return this.send('/v2/ui/merge', {
|
85
91
|
prompt,
|
@@ -50,6 +50,14 @@ export interface StormFileImplementationPrompt {
|
|
50
50
|
prompt: string;
|
51
51
|
}
|
52
52
|
export interface StormUIImplementationPrompt {
|
53
|
+
events: StormEvent[];
|
54
|
+
template: StormFileInfo;
|
55
|
+
filename: string;
|
56
|
+
context: StormFileInfo[];
|
57
|
+
blockName: string;
|
58
|
+
prompt: string;
|
59
|
+
}
|
60
|
+
export interface StormUIListPrompt {
|
53
61
|
events: StormEvent[];
|
54
62
|
templates: StormFileInfo[];
|
55
63
|
context: StormFileInfo[];
|
@@ -3,8 +3,12 @@
|
|
3
3
|
* Copyright 2023 Kapeta Inc.
|
4
4
|
* SPDX-License-Identifier: BUSL-1.1
|
5
5
|
*/
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
8
|
+
};
|
6
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
7
10
|
const event_parser_1 = require("../../src/storm/event-parser");
|
11
|
+
const simple_blog_events_json_1 = __importDefault(require("./simple-blog-events.json"));
|
8
12
|
const parserOptions = {
|
9
13
|
serviceKind: 'kapeta/block-service:local',
|
10
14
|
serviceLanguage: 'kapeta/language-target-nodejs-ts:local',
|
@@ -158,4 +162,18 @@ describe('event-parser', () => {
|
|
158
162
|
expect(result.plan.spec.connections[0].provider.blockId).toBe(serviceBlockInstance.id);
|
159
163
|
expect(result.plan.spec.connections[0].provider.resourceName).toBe(apiResource?.metadata.name);
|
160
164
|
});
|
165
|
+
it('it will split api into correct provider', () => {
|
166
|
+
const events = simple_blog_events_json_1.default;
|
167
|
+
const parser = new event_parser_1.StormEventParser(parserOptions);
|
168
|
+
events.forEach((event) => parser.processEvent('kapeta', event));
|
169
|
+
const result = parser.toResult('kapeta');
|
170
|
+
const blogService = result.blocks.find((block) => block.aiName === 'blog-service');
|
171
|
+
expect(blogService).toBeDefined();
|
172
|
+
expect(blogService?.content).toBeDefined();
|
173
|
+
const apiProviders = blogService?.content?.spec?.providers?.filter((provider) => provider.kind === 'kapeta/block-type-api:local');
|
174
|
+
expect(apiProviders).toBeDefined();
|
175
|
+
expect(apiProviders.length).toBe(2);
|
176
|
+
expect(apiProviders["0"].spec.source.value).not.toBe('');
|
177
|
+
expect(apiProviders["1"].spec.source.value).not.toBe('');
|
178
|
+
});
|
161
179
|
});
|