@kapeta/local-cluster-service 0.74.1 → 0.74.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 CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.74.2](https://github.com/kapetacom/local-cluster-service/compare/v0.74.1...v0.74.2) (2024-09-27)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add more bail checkpoints for aborted request ([1f4823c](https://github.com/kapetacom/local-cluster-service/commit/1f4823c648d21f3ea8c8f8f2ead156d1e43c7252))
7
+ * define conversationIds for pages as early as possible ([7b020a5](https://github.com/kapetacom/local-cluster-service/commit/7b020a52ddeba60329989dafda62abc8d3565039))
8
+
1
9
  ## [0.74.1](https://github.com/kapetacom/local-cluster-service/compare/v0.74.0...v0.74.1) (2024-09-27)
2
10
 
3
11
 
@@ -37,6 +37,7 @@ const node_events_1 = require("node:events");
37
37
  const p_queue_1 = __importDefault(require("p-queue"));
38
38
  const page_utils_1 = require("./page-utils");
39
39
  const mimetypes = __importStar(require("mime-types"));
40
+ const node_crypto_1 = require("node:crypto");
40
41
  class PageQueue extends node_events_1.EventEmitter {
41
42
  queue;
42
43
  eventQueue;
@@ -166,6 +167,8 @@ class PageQueue extends node_events_1.EventEmitter {
166
167
  }
167
168
  this.pages.set(normalizedPath, reference.description);
168
169
  initialPrompts.push({
170
+ conversationId: (0, node_crypto_1.randomUUID)(),
171
+ id: (0, node_crypto_1.randomUUID)(),
169
172
  name: reference.name,
170
173
  title: reference.title,
171
174
  path: normalizedPath,
@@ -193,20 +196,21 @@ class PageQueue extends node_events_1.EventEmitter {
193
196
  reason: 'reference',
194
197
  created: Date.now(),
195
198
  payload: {
199
+ id: prompt.id,
200
+ conversationId: prompt.conversationId,
196
201
  name: prompt.name,
197
202
  title: prompt.title,
198
203
  filename: prompt.filename,
199
204
  method: 'GET',
200
205
  path: prompt.path,
201
206
  prompt: prompt.description,
202
- conversationId: '',
203
207
  content: '',
204
208
  description: prompt.description,
205
209
  },
206
210
  });
207
211
  }
208
212
  // Trigger but don't wait for the "bonus" pages
209
- this.addPrompt(prompt).catch((err) => {
213
+ this.addPrompt(prompt, prompt.conversationId).catch((err) => {
210
214
  console.error('Failed to generate page reference', prompt.name, err);
211
215
  this.emit('error', err);
212
216
  });
@@ -287,6 +287,7 @@ export interface StormEventPhases {
287
287
  };
288
288
  }
289
289
  export interface Page {
290
+ id: string;
290
291
  name: string;
291
292
  filename: string;
292
293
  title: string;
@@ -329,6 +330,7 @@ export interface UserJourneyScreen {
329
330
  path: string;
330
331
  method: string;
331
332
  nextScreens: string[];
333
+ conversationId?: string;
332
334
  }
333
335
  export interface UserJourney {
334
336
  title: string;
@@ -357,11 +357,11 @@ router.post('/:handle/ui', async (req, res) => {
357
357
  let systemPrompt = aiRequest.prompt;
358
358
  userJourneysStream.on('data', (data) => {
359
359
  try {
360
- sendEvent(res, data);
361
360
  if (data.type === 'PROMPT_IMPROVE') {
362
361
  systemPrompt = data.payload.prompt;
363
362
  }
364
363
  if (data.type !== 'USER_JOURNEY') {
364
+ sendEvent(res, data);
365
365
  return;
366
366
  }
367
367
  if (userJourneysStream.isAborted()) {
@@ -369,9 +369,11 @@ router.post('/:handle/ui', async (req, res) => {
369
369
  }
370
370
  data.payload.screens.forEach((screen) => {
371
371
  if (!uniqueUserJourneyScreens[screen.name]) {
372
+ screen.conversationId = (0, crypto_1.randomUUID)();
372
373
  uniqueUserJourneyScreens[screen.name] = screen;
373
374
  }
374
375
  });
376
+ sendEvent(res, data);
375
377
  }
376
378
  catch (e) {
377
379
  console.error('Failed to process event', e);
@@ -422,6 +424,9 @@ router.post('/:handle/ui', async (req, res) => {
422
424
  });
423
425
  }
424
426
  await waitForStormStream(userJourneysStream);
427
+ if (req.socket.closed) {
428
+ return;
429
+ }
425
430
  // Get the UI shells
426
431
  const shellsStream = await stormClient.createUIShells({
427
432
  theme: theme || undefined,
@@ -456,6 +461,9 @@ router.post('/:handle/ui', async (req, res) => {
456
461
  sendError(error, res);
457
462
  });
458
463
  await waitForStormStream(shellsStream);
464
+ if (req.socket.closed) {
465
+ return;
466
+ }
459
467
  UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
460
468
  await UI_SERVERS[outerConversationId].start();
461
469
  sendEvent(res, {
@@ -470,7 +478,7 @@ router.post('/:handle/ui', async (req, res) => {
470
478
  onRequestAborted(req, res, () => {
471
479
  queue.cancel();
472
480
  });
473
- queue.on('page', (screenData) => sendPageEvent(outerConversationId, screenData, res));
481
+ queue.on('page', (pageEvent) => sendPageEvent(outerConversationId, pageEvent, res));
474
482
  queue.on('event', (event) => {
475
483
  if (event.type === 'FILE_CHUNK') {
476
484
  return;
@@ -493,7 +501,7 @@ router.post('/:handle/ui', async (req, res) => {
493
501
  filename: screen.filename,
494
502
  storage_prefix: outerConversationId + '_',
495
503
  theme,
496
- })
504
+ }, screen.conversationId)
497
505
  .catch((e) => {
498
506
  console.error('Failed to generate page for screen %s', screen.name, e);
499
507
  sendError(e, res);
@@ -37,6 +37,7 @@ const node_events_1 = require("node:events");
37
37
  const p_queue_1 = __importDefault(require("p-queue"));
38
38
  const page_utils_1 = require("./page-utils");
39
39
  const mimetypes = __importStar(require("mime-types"));
40
+ const node_crypto_1 = require("node:crypto");
40
41
  class PageQueue extends node_events_1.EventEmitter {
41
42
  queue;
42
43
  eventQueue;
@@ -166,6 +167,8 @@ class PageQueue extends node_events_1.EventEmitter {
166
167
  }
167
168
  this.pages.set(normalizedPath, reference.description);
168
169
  initialPrompts.push({
170
+ conversationId: (0, node_crypto_1.randomUUID)(),
171
+ id: (0, node_crypto_1.randomUUID)(),
169
172
  name: reference.name,
170
173
  title: reference.title,
171
174
  path: normalizedPath,
@@ -193,20 +196,21 @@ class PageQueue extends node_events_1.EventEmitter {
193
196
  reason: 'reference',
194
197
  created: Date.now(),
195
198
  payload: {
199
+ id: prompt.id,
200
+ conversationId: prompt.conversationId,
196
201
  name: prompt.name,
197
202
  title: prompt.title,
198
203
  filename: prompt.filename,
199
204
  method: 'GET',
200
205
  path: prompt.path,
201
206
  prompt: prompt.description,
202
- conversationId: '',
203
207
  content: '',
204
208
  description: prompt.description,
205
209
  },
206
210
  });
207
211
  }
208
212
  // Trigger but don't wait for the "bonus" pages
209
- this.addPrompt(prompt).catch((err) => {
213
+ this.addPrompt(prompt, prompt.conversationId).catch((err) => {
210
214
  console.error('Failed to generate page reference', prompt.name, err);
211
215
  this.emit('error', err);
212
216
  });
@@ -287,6 +287,7 @@ export interface StormEventPhases {
287
287
  };
288
288
  }
289
289
  export interface Page {
290
+ id: string;
290
291
  name: string;
291
292
  filename: string;
292
293
  title: string;
@@ -329,6 +330,7 @@ export interface UserJourneyScreen {
329
330
  path: string;
330
331
  method: string;
331
332
  nextScreens: string[];
333
+ conversationId?: string;
332
334
  }
333
335
  export interface UserJourney {
334
336
  title: string;
@@ -357,11 +357,11 @@ router.post('/:handle/ui', async (req, res) => {
357
357
  let systemPrompt = aiRequest.prompt;
358
358
  userJourneysStream.on('data', (data) => {
359
359
  try {
360
- sendEvent(res, data);
361
360
  if (data.type === 'PROMPT_IMPROVE') {
362
361
  systemPrompt = data.payload.prompt;
363
362
  }
364
363
  if (data.type !== 'USER_JOURNEY') {
364
+ sendEvent(res, data);
365
365
  return;
366
366
  }
367
367
  if (userJourneysStream.isAborted()) {
@@ -369,9 +369,11 @@ router.post('/:handle/ui', async (req, res) => {
369
369
  }
370
370
  data.payload.screens.forEach((screen) => {
371
371
  if (!uniqueUserJourneyScreens[screen.name]) {
372
+ screen.conversationId = (0, crypto_1.randomUUID)();
372
373
  uniqueUserJourneyScreens[screen.name] = screen;
373
374
  }
374
375
  });
376
+ sendEvent(res, data);
375
377
  }
376
378
  catch (e) {
377
379
  console.error('Failed to process event', e);
@@ -422,6 +424,9 @@ router.post('/:handle/ui', async (req, res) => {
422
424
  });
423
425
  }
424
426
  await waitForStormStream(userJourneysStream);
427
+ if (req.socket.closed) {
428
+ return;
429
+ }
425
430
  // Get the UI shells
426
431
  const shellsStream = await stormClient.createUIShells({
427
432
  theme: theme || undefined,
@@ -456,6 +461,9 @@ router.post('/:handle/ui', async (req, res) => {
456
461
  sendError(error, res);
457
462
  });
458
463
  await waitForStormStream(shellsStream);
464
+ if (req.socket.closed) {
465
+ return;
466
+ }
459
467
  UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
460
468
  await UI_SERVERS[outerConversationId].start();
461
469
  sendEvent(res, {
@@ -470,7 +478,7 @@ router.post('/:handle/ui', async (req, res) => {
470
478
  onRequestAborted(req, res, () => {
471
479
  queue.cancel();
472
480
  });
473
- queue.on('page', (screenData) => sendPageEvent(outerConversationId, screenData, res));
481
+ queue.on('page', (pageEvent) => sendPageEvent(outerConversationId, pageEvent, res));
474
482
  queue.on('event', (event) => {
475
483
  if (event.type === 'FILE_CHUNK') {
476
484
  return;
@@ -493,7 +501,7 @@ router.post('/:handle/ui', async (req, res) => {
493
501
  filename: screen.filename,
494
502
  storage_prefix: outerConversationId + '_',
495
503
  theme,
496
- })
504
+ }, screen.conversationId)
497
505
  .catch((e) => {
498
506
  console.error('Failed to generate page for screen %s', screen.name, e);
499
507
  sendError(e, res);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.74.1",
3
+ "version": "0.74.2",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -11,6 +11,7 @@ import PQueue from 'p-queue';
11
11
 
12
12
  import { hasPageOnDisk, normalizePath, writeImageToDisk } from './page-utils';
13
13
  import * as mimetypes from 'mime-types';
14
+ import { randomUUID } from 'node:crypto';
14
15
 
15
16
  export interface ImagePrompt {
16
17
  name: string;
@@ -22,7 +23,10 @@ export interface ImagePrompt {
22
23
  content: string;
23
24
  }
24
25
 
25
- type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & { shellType?: 'public' | 'admin' | 'user' };
26
+ type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & {
27
+ shellType?: 'public' | 'admin' | 'user';
28
+ };
29
+ type PagePrompt = InitialPrompt & { conversationId: string; id: string };
26
30
 
27
31
  export class PageQueue extends EventEmitter {
28
32
  private readonly queue: PQueue;
@@ -149,7 +153,7 @@ export class PageQueue extends EventEmitter {
149
153
  new RegExp(`^${path.replaceAll('/*', '/[^/]+')}$`).test(url)
150
154
  );
151
155
  };
152
- const initialPrompts: InitialPrompt[] = [];
156
+ const initialPrompts: PagePrompt[] = [];
153
157
  const resourcePromises = references.map(async (reference) => {
154
158
  if (
155
159
  reference.url.startsWith('#') ||
@@ -185,6 +189,8 @@ export class PageQueue extends EventEmitter {
185
189
  this.pages.set(normalizedPath, reference.description);
186
190
 
187
191
  initialPrompts.push({
192
+ conversationId: randomUUID(),
193
+ id: randomUUID(),
188
194
  name: reference.name,
189
195
  title: reference.title,
190
196
  path: normalizedPath,
@@ -215,20 +221,21 @@ export class PageQueue extends EventEmitter {
215
221
  reason: 'reference',
216
222
  created: Date.now(),
217
223
  payload: {
224
+ id: prompt.id,
225
+ conversationId: prompt.conversationId,
218
226
  name: prompt.name,
219
227
  title: prompt.title,
220
228
  filename: prompt.filename,
221
229
  method: 'GET',
222
230
  path: prompt.path,
223
231
  prompt: prompt.description,
224
- conversationId: '',
225
232
  content: '',
226
233
  description: prompt.description,
227
234
  },
228
235
  });
229
236
  }
230
237
  // Trigger but don't wait for the "bonus" pages
231
- this.addPrompt(prompt).catch((err) => {
238
+ this.addPrompt(prompt, prompt.conversationId).catch((err) => {
232
239
  console.error('Failed to generate page reference', prompt.name, err);
233
240
  this.emit('error', err);
234
241
  });
@@ -347,6 +347,7 @@ export interface StormEventPhases {
347
347
  }
348
348
 
349
349
  export interface Page {
350
+ id: string;
350
351
  name: string;
351
352
  filename: string;
352
353
  title: string;
@@ -394,6 +395,7 @@ export interface UserJourneyScreen {
394
395
  path: string;
395
396
  method: string;
396
397
  nextScreens: string[];
398
+ conversationId?: string;
397
399
  }
398
400
 
399
401
  export interface UserJourney {
@@ -458,11 +458,11 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
458
458
 
459
459
  userJourneysStream.on('data', (data: StormEvent) => {
460
460
  try {
461
- sendEvent(res, data);
462
461
  if (data.type === 'PROMPT_IMPROVE') {
463
462
  systemPrompt = data.payload.prompt;
464
463
  }
465
464
  if (data.type !== 'USER_JOURNEY') {
465
+ sendEvent(res, data);
466
466
  return;
467
467
  }
468
468
 
@@ -472,9 +472,12 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
472
472
 
473
473
  data.payload.screens.forEach((screen) => {
474
474
  if (!uniqueUserJourneyScreens[screen.name]) {
475
+ screen.conversationId = randomUUID();
475
476
  uniqueUserJourneyScreens[screen.name] = screen;
476
477
  }
477
478
  });
479
+
480
+ sendEvent(res, data);
478
481
  } catch (e) {
479
482
  console.error('Failed to process event', e);
480
483
  }
@@ -528,6 +531,10 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
528
531
 
529
532
  await waitForStormStream(userJourneysStream);
530
533
 
534
+ if (req.socket.closed) {
535
+ return;
536
+ }
537
+
531
538
  // Get the UI shells
532
539
  const shellsStream = await stormClient.createUIShells(
533
540
  {
@@ -574,6 +581,10 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
574
581
 
575
582
  await waitForStormStream(shellsStream);
576
583
 
584
+ if (req.socket.closed) {
585
+ return;
586
+ }
587
+
577
588
  UI_SERVERS[outerConversationId] = new UIServer(outerConversationId);
578
589
  await UI_SERVERS[outerConversationId].start();
579
590
 
@@ -591,7 +602,7 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
591
602
  queue.cancel();
592
603
  });
593
604
 
594
- queue.on('page', (screenData: StormEventPage) => sendPageEvent(outerConversationId, screenData, res));
605
+ queue.on('page', (pageEvent: StormEventPage) => sendPageEvent(outerConversationId, pageEvent, res));
595
606
 
596
607
  queue.on('event', (event: StormEvent) => {
597
608
  if (event.type === 'FILE_CHUNK') {
@@ -607,17 +618,20 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
607
618
 
608
619
  for (const screen of Object.values(uniqueUserJourneyScreens)) {
609
620
  queue
610
- .addPrompt({
611
- prompt: screen.requirements,
612
- method: screen.method,
613
- path: screen.path,
614
- description: screen.requirements,
615
- name: screen.name,
616
- title: screen.title,
617
- filename: screen.filename,
618
- storage_prefix: outerConversationId + '_',
619
- theme,
620
- })
621
+ .addPrompt(
622
+ {
623
+ prompt: screen.requirements,
624
+ method: screen.method,
625
+ path: screen.path,
626
+ description: screen.requirements,
627
+ name: screen.name,
628
+ title: screen.title,
629
+ filename: screen.filename,
630
+ storage_prefix: outerConversationId + '_',
631
+ theme,
632
+ },
633
+ screen.conversationId
634
+ )
621
635
  .catch((e) => {
622
636
  console.error('Failed to generate page for screen %s', screen.name, e);
623
637
  sendError(e as any, res);