@kapeta/local-cluster-service 0.49.0 → 0.51.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/CHANGELOG.md +14 -0
- package/dist/cjs/src/storm/codegen.d.ts +5 -2
- package/dist/cjs/src/storm/codegen.js +203 -27
- package/dist/cjs/src/storm/event-parser.d.ts +4 -1
- package/dist/cjs/src/storm/event-parser.js +21 -7
- package/dist/cjs/src/storm/events.d.ts +37 -9
- package/dist/cjs/src/storm/events.js +7 -0
- package/dist/cjs/src/storm/routes.js +66 -9
- package/dist/cjs/src/storm/stormClient.js +5 -1
- package/dist/cjs/src/storm/stream.d.ts +5 -0
- package/dist/cjs/src/storm/stream.js +11 -0
- package/dist/esm/src/storm/codegen.d.ts +5 -2
- package/dist/esm/src/storm/codegen.js +203 -27
- package/dist/esm/src/storm/event-parser.d.ts +4 -1
- package/dist/esm/src/storm/event-parser.js +21 -7
- package/dist/esm/src/storm/events.d.ts +37 -9
- package/dist/esm/src/storm/events.js +7 -0
- package/dist/esm/src/storm/routes.js +66 -9
- package/dist/esm/src/storm/stormClient.js +5 -1
- package/dist/esm/src/storm/stream.d.ts +5 -0
- package/dist/esm/src/storm/stream.js +11 -0
- package/package.json +1 -1
- package/src/storm/codegen.ts +231 -33
- package/src/storm/event-parser.ts +29 -8
- package/src/storm/events.ts +48 -10
- package/src/storm/routes.ts +87 -23
- package/src/storm/stormClient.ts +8 -1
- package/src/storm/stream.ts +15 -0
@@ -4,7 +4,7 @@
|
|
4
4
|
* SPDX-License-Identifier: BUSL-1.1
|
5
5
|
*/
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
7
|
-
exports.StormEventParser = exports.resolveOptions = void 0;
|
7
|
+
exports.StormEventParser = exports.resolveOptions = exports.createPhaseEvent = exports.createPhaseEndEvent = exports.createPhaseStartEvent = void 0;
|
8
8
|
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
9
9
|
const kaplang_core_1 = require("@kapeta/kaplang-core");
|
10
10
|
const uuid_1 = require("uuid");
|
@@ -29,6 +29,24 @@ function prettifyKaplang(source) {
|
|
29
29
|
return source;
|
30
30
|
}
|
31
31
|
}
|
32
|
+
function createPhaseStartEvent(type) {
|
33
|
+
return createPhaseEvent(true, type);
|
34
|
+
}
|
35
|
+
exports.createPhaseStartEvent = createPhaseStartEvent;
|
36
|
+
function createPhaseEndEvent(type) {
|
37
|
+
return createPhaseEvent(false, type);
|
38
|
+
}
|
39
|
+
exports.createPhaseEndEvent = createPhaseEndEvent;
|
40
|
+
function createPhaseEvent(start, type) {
|
41
|
+
return {
|
42
|
+
type: start ? 'PHASE_START' : 'PHASE_END',
|
43
|
+
created: Date.now(),
|
44
|
+
payload: {
|
45
|
+
phaseType: type,
|
46
|
+
},
|
47
|
+
};
|
48
|
+
}
|
49
|
+
exports.createPhaseEvent = createPhaseEvent;
|
32
50
|
async function resolveOptions() {
|
33
51
|
// Predefined types for now - TODO: Allow user to select / change
|
34
52
|
const blockTypeService = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/block-type-service');
|
@@ -193,10 +211,6 @@ class StormEventParser {
|
|
193
211
|
evt.payload.toBlockId = StormEventParser.toInstanceId(handle, evt.payload.toComponent);
|
194
212
|
this.connections.push(evt.payload);
|
195
213
|
break;
|
196
|
-
default:
|
197
|
-
case 'SCREEN_CANDIDATE':
|
198
|
-
case 'FILE':
|
199
|
-
break;
|
200
214
|
}
|
201
215
|
return this.toResult(handle);
|
202
216
|
}
|
@@ -213,7 +227,7 @@ class StormEventParser {
|
|
213
227
|
return this.error;
|
214
228
|
}
|
215
229
|
toResult(handle) {
|
216
|
-
const planRef = StormEventParser.toRef(handle, this.planName
|
230
|
+
const planRef = StormEventParser.toRef(handle, this.planName || 'undefined');
|
217
231
|
const blockDefinitions = this.toBlockDefinitions(handle);
|
218
232
|
const refIdMap = {};
|
219
233
|
const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
|
@@ -225,7 +239,7 @@ class StormEventParser {
|
|
225
239
|
block: {
|
226
240
|
ref,
|
227
241
|
},
|
228
|
-
name: block.content.metadata.title
|
242
|
+
name: block.content.metadata.title || block.content.metadata.name,
|
229
243
|
dimensions: {
|
230
244
|
left: 0,
|
231
245
|
top: 0,
|
@@ -150,19 +150,35 @@ export interface StormEventScreenCandidate {
|
|
150
150
|
url: string;
|
151
151
|
};
|
152
152
|
}
|
153
|
-
export interface
|
154
|
-
|
153
|
+
export interface StormEventFileBasePayload {
|
154
|
+
filename: string;
|
155
|
+
path: string;
|
156
|
+
blockName: string;
|
157
|
+
blockRef: string;
|
158
|
+
instanceId: string;
|
159
|
+
}
|
160
|
+
export interface StormEventFileBase {
|
161
|
+
type: string;
|
155
162
|
reason: string;
|
156
163
|
created: number;
|
157
|
-
payload:
|
158
|
-
|
159
|
-
|
164
|
+
payload: StormEventFileBasePayload;
|
165
|
+
}
|
166
|
+
export interface StormEventFileLogical extends StormEventFileBase {
|
167
|
+
type: 'FILE_START' | 'FILE_CHUNK_RESET';
|
168
|
+
}
|
169
|
+
export interface StormEventFileState extends StormEventFileBase {
|
170
|
+
type: 'FILE_STATE';
|
171
|
+
payload: StormEventFileBasePayload & {
|
172
|
+
state: string;
|
173
|
+
};
|
174
|
+
}
|
175
|
+
export interface StormEventFileContent extends StormEventFileBase {
|
176
|
+
type: 'FILE_DONE' | 'FILE_CHUNK';
|
177
|
+
payload: StormEventFileBasePayload & {
|
160
178
|
content: string;
|
161
|
-
blockName: string;
|
162
|
-
blockRef: string;
|
163
|
-
instanceId: string;
|
164
179
|
};
|
165
180
|
}
|
181
|
+
export type StormEventFile = StormEventFileLogical | StormEventFileState | StormEventFileContent;
|
166
182
|
export interface StormEventBlockReady {
|
167
183
|
type: 'BLOCK_READY';
|
168
184
|
reason: string;
|
@@ -184,4 +200,16 @@ export interface StormEventDefinitionChange {
|
|
184
200
|
created: number;
|
185
201
|
payload: StormDefinitions;
|
186
202
|
}
|
187
|
-
export
|
203
|
+
export declare enum StormEventPhaseType {
|
204
|
+
META = "META",
|
205
|
+
DEFINITIONS = "DEFINITIONS",
|
206
|
+
IMPLEMENTATION = "IMPLEMENTATION"
|
207
|
+
}
|
208
|
+
export interface StormEventPhases {
|
209
|
+
type: 'PHASE_START' | 'PHASE_END';
|
210
|
+
created: number;
|
211
|
+
payload: {
|
212
|
+
phaseType: StormEventPhaseType;
|
213
|
+
};
|
214
|
+
}
|
215
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileContent | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventBlockReady | StormEventPhases;
|
@@ -1,2 +1,9 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.StormEventPhaseType = void 0;
|
4
|
+
var StormEventPhaseType;
|
5
|
+
(function (StormEventPhaseType) {
|
6
|
+
StormEventPhaseType["META"] = "META";
|
7
|
+
StormEventPhaseType["DEFINITIONS"] = "DEFINITIONS";
|
8
|
+
StormEventPhaseType["IMPLEMENTATION"] = "IMPLEMENTATION";
|
9
|
+
})(StormEventPhaseType || (exports.StormEventPhaseType = StormEventPhaseType = {}));
|
@@ -12,6 +12,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
12
|
const cors_1 = require("../middleware/cors");
|
13
13
|
const stringBody_1 = require("../middleware/stringBody");
|
14
14
|
const stormClient_1 = require("./stormClient");
|
15
|
+
const events_1 = require("./events");
|
15
16
|
const event_parser_1 = require("./event-parser");
|
16
17
|
const codegen_1 = require("./codegen");
|
17
18
|
const assetManager_1 = require("../assetManager");
|
@@ -27,15 +28,41 @@ router.post('/:handle/all', async (req, res) => {
|
|
27
28
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
28
29
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
29
30
|
const metaStream = await stormClient_1.stormClient.createMetadata(aiRequest.prompt, conversationId);
|
31
|
+
onRequestAborted(req, res, () => {
|
32
|
+
metaStream.abort();
|
33
|
+
});
|
30
34
|
res.set('Content-Type', 'application/x-ndjson');
|
31
35
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
32
36
|
res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
|
37
|
+
let currentPhase = events_1.StormEventPhaseType.META;
|
33
38
|
metaStream.on('data', (data) => {
|
34
39
|
const result = eventParser.processEvent(req.params.handle, data);
|
40
|
+
switch (data.type) {
|
41
|
+
case 'CREATE_API':
|
42
|
+
case 'CREATE_MODEL':
|
43
|
+
case 'CREATE_TYPE':
|
44
|
+
if (currentPhase !== events_1.StormEventPhaseType.DEFINITIONS) {
|
45
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.META));
|
46
|
+
currentPhase = events_1.StormEventPhaseType.DEFINITIONS;
|
47
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.DEFINITIONS));
|
48
|
+
}
|
49
|
+
break;
|
50
|
+
}
|
35
51
|
sendEvent(res, data);
|
36
52
|
sendDefinitions(res, result);
|
37
53
|
});
|
38
|
-
|
54
|
+
try {
|
55
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.META));
|
56
|
+
await waitForStormStream(metaStream);
|
57
|
+
}
|
58
|
+
finally {
|
59
|
+
if (!metaStream.isAborted()) {
|
60
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(currentPhase));
|
61
|
+
}
|
62
|
+
}
|
63
|
+
if (metaStream.isAborted()) {
|
64
|
+
return;
|
65
|
+
}
|
39
66
|
if (!eventParser.isValid()) {
|
40
67
|
// We can't continue if the meta stream is invalid
|
41
68
|
sendEvent(res, {
|
@@ -48,32 +75,45 @@ router.post('/:handle/all', async (req, res) => {
|
|
48
75
|
return;
|
49
76
|
}
|
50
77
|
const result = eventParser.toResult(handle);
|
78
|
+
if (metaStream.isAborted()) {
|
79
|
+
return;
|
80
|
+
}
|
51
81
|
sendDefinitions(res, result);
|
52
82
|
if (!req.query.skipCodegen) {
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
83
|
+
try {
|
84
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENTATION));
|
85
|
+
const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents());
|
86
|
+
onRequestAborted(req, res, () => {
|
87
|
+
stormCodegen.abort();
|
88
|
+
});
|
89
|
+
const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
|
90
|
+
await stormCodegen.process();
|
91
|
+
await codegenPromise;
|
92
|
+
}
|
93
|
+
finally {
|
94
|
+
if (!metaStream.isAborted()) {
|
95
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENTATION));
|
96
|
+
}
|
97
|
+
}
|
57
98
|
}
|
58
99
|
sendDone(res);
|
59
100
|
}
|
60
101
|
catch (err) {
|
61
102
|
sendError(err, res);
|
62
|
-
res.
|
103
|
+
if (!res.closed) {
|
104
|
+
res.end();
|
105
|
+
}
|
63
106
|
}
|
64
107
|
});
|
65
108
|
router.post('/block/create', async (req, res) => {
|
66
109
|
const createRequest = JSON.parse(req.stringBody ?? '{}');
|
67
110
|
try {
|
68
111
|
const ymlPath = path_1.default.join(createRequest.newPath, 'kapeta.yml');
|
69
|
-
console.log('Creating block at', ymlPath);
|
70
112
|
const [asset] = await assetManager_1.assetManager.createAsset(ymlPath, createRequest.definition);
|
71
113
|
if (await fs_extra_1.default.pathExists(createRequest.tmpPath)) {
|
72
|
-
console.log('Moving block from', createRequest.tmpPath, 'to', createRequest.newPath);
|
73
114
|
await fs_extra_1.default.move(createRequest.tmpPath, createRequest.newPath, {
|
74
115
|
overwrite: true,
|
75
116
|
});
|
76
|
-
console.log('Updating asset', asset.ref);
|
77
117
|
res.send(await assetManager_1.assetManager.updateAsset(asset.ref, createRequest.definition));
|
78
118
|
}
|
79
119
|
else {
|
@@ -93,6 +133,9 @@ function sendDefinitions(res, result) {
|
|
93
133
|
});
|
94
134
|
}
|
95
135
|
function sendDone(res) {
|
136
|
+
if (res.closed) {
|
137
|
+
return;
|
138
|
+
}
|
96
139
|
sendEvent(res, {
|
97
140
|
type: 'DONE',
|
98
141
|
created: Date.now(),
|
@@ -100,6 +143,9 @@ function sendDone(res) {
|
|
100
143
|
res.end();
|
101
144
|
}
|
102
145
|
function sendError(err, res) {
|
146
|
+
if (res.closed) {
|
147
|
+
return;
|
148
|
+
}
|
103
149
|
console.error('Failed to send prompt', err);
|
104
150
|
if (res.headersSent) {
|
105
151
|
sendEvent(res, {
|
@@ -137,6 +183,17 @@ function streamStormPartialResponse(result, res) {
|
|
137
183
|
});
|
138
184
|
}
|
139
185
|
function sendEvent(res, evt) {
|
186
|
+
if (res.closed) {
|
187
|
+
return;
|
188
|
+
}
|
140
189
|
res.write(JSON.stringify(evt) + '\n');
|
141
190
|
}
|
191
|
+
function onRequestAborted(req, res, onAborted) {
|
192
|
+
req.on('close', () => {
|
193
|
+
onAborted();
|
194
|
+
});
|
195
|
+
res.on('close', () => {
|
196
|
+
onAborted();
|
197
|
+
});
|
198
|
+
}
|
142
199
|
exports.default = router;
|
@@ -46,12 +46,13 @@ class StormClient {
|
|
46
46
|
prompt: stringPrompt,
|
47
47
|
conversationId: body.conversationId,
|
48
48
|
});
|
49
|
+
const abort = new AbortController();
|
50
|
+
options.signal = abort.signal;
|
49
51
|
const response = await fetch(options.url, options);
|
50
52
|
if (response.status !== 200) {
|
51
53
|
throw new Error(`Got error response from ${options.url}: ${response.status}\nContent: ${await response.text()}`);
|
52
54
|
}
|
53
55
|
const conversationId = response.headers.get(exports.ConversationIdHeader);
|
54
|
-
console.log('Received conversationId', conversationId);
|
55
56
|
const out = new stream_1.StormStream(stringPrompt, conversationId);
|
56
57
|
const jsonLStream = promises_1.default.createInterface(node_stream_1.Readable.fromWeb(response.body));
|
57
58
|
jsonLStream.on('line', (line) => {
|
@@ -63,6 +64,9 @@ class StormClient {
|
|
63
64
|
jsonLStream.on('close', () => {
|
64
65
|
out.end();
|
65
66
|
});
|
67
|
+
out.on('aborted', () => {
|
68
|
+
abort.abort();
|
69
|
+
});
|
66
70
|
return out;
|
67
71
|
}
|
68
72
|
createMetadata(prompt, conversationId) {
|
@@ -10,17 +10,22 @@ import { BlockDefinition } from '@kapeta/schemas';
|
|
10
10
|
export declare class StormStream extends EventEmitter {
|
11
11
|
private conversationId;
|
12
12
|
private lines;
|
13
|
+
private aborted;
|
13
14
|
constructor(prompt?: string, conversationId?: string | null);
|
14
15
|
getConversationId(): string;
|
16
|
+
isAborted(): boolean;
|
15
17
|
addJSONLine(line: string): void;
|
16
18
|
end(): void;
|
17
19
|
on(event: 'end', listener: () => void): this;
|
20
|
+
on(event: 'aborted', listener: () => void): this;
|
18
21
|
on(event: 'error', listener: (e: Error) => void): this;
|
19
22
|
on(event: 'data', listener: (data: StormEvent) => void): this;
|
20
23
|
emit(event: 'end'): boolean;
|
24
|
+
emit(event: 'aborted'): void;
|
21
25
|
emit(event: 'error', e: Error): boolean;
|
22
26
|
emit(event: 'data', data: StormEvent): boolean;
|
23
27
|
waitForDone(): Promise<void>;
|
28
|
+
abort(): void;
|
24
29
|
}
|
25
30
|
export interface ConversationItem {
|
26
31
|
role: 'user' | 'model';
|
@@ -9,6 +9,7 @@ const node_events_1 = require("node:events");
|
|
9
9
|
class StormStream extends node_events_1.EventEmitter {
|
10
10
|
conversationId = '';
|
11
11
|
lines = [];
|
12
|
+
aborted = false;
|
12
13
|
constructor(prompt = '', conversationId) {
|
13
14
|
super();
|
14
15
|
this.conversationId = conversationId || '';
|
@@ -16,6 +17,9 @@ class StormStream extends node_events_1.EventEmitter {
|
|
16
17
|
getConversationId() {
|
17
18
|
return this.conversationId;
|
18
19
|
}
|
20
|
+
isAborted() {
|
21
|
+
return this.aborted;
|
22
|
+
}
|
19
23
|
addJSONLine(line) {
|
20
24
|
try {
|
21
25
|
this.lines.push(line);
|
@@ -48,5 +52,12 @@ class StormStream extends node_events_1.EventEmitter {
|
|
48
52
|
});
|
49
53
|
});
|
50
54
|
}
|
55
|
+
abort() {
|
56
|
+
if (this.aborted) {
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
this.aborted = true;
|
60
|
+
this.emit('aborted');
|
61
|
+
}
|
51
62
|
}
|
52
63
|
exports.StormStream = StormStream;
|