@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.
@@ -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 ?? 'undefined');
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 ?? block.content.metadata.name,
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 StormEventFile {
154
- type: 'FILE';
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
- filename: string;
159
- path: string;
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 type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventBlockReady;
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
- await waitForStormStream(metaStream);
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
- const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents());
54
- const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
55
- await stormCodegen.process();
56
- await codegenPromise;
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.end();
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.49.0",
3
+ "version": "0.51.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {