@kapeta/local-cluster-service 0.74.0 → 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,18 @@
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
+
9
+ ## [0.74.1](https://github.com/kapetacom/local-cluster-service/compare/v0.74.0...v0.74.1) (2024-09-27)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * adding system id to all calls ([b3e978f](https://github.com/kapetacom/local-cluster-service/commit/b3e978ffb74fe0ab1a46006412e52c63fe49eff8))
15
+
1
16
  # [0.74.0](https://github.com/kapetacom/local-cluster-service/compare/v0.73.0...v0.74.0) (2024-09-26)
2
17
 
3
18
 
@@ -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
  });
@@ -240,8 +244,9 @@ class PageQueue extends node_events_1.EventEmitter {
240
244
  console.warn('Skipping image reference of type %s for url %s', mimeType, prompt.url);
241
245
  return;
242
246
  }
247
+ const client = new stormClient_1.StormClient(this.systemId);
243
248
  this.images.set(prompt.url, prompt.description);
244
- const result = await stormClient_1.stormClient.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
249
+ const result = await client.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
245
250
  let imageEvent = null;
246
251
  result.on('data', (event) => {
247
252
  if (event.type === 'IMAGE') {
@@ -256,7 +261,8 @@ class PageQueue extends node_events_1.EventEmitter {
256
261
  this.emit('image', imageEvent, prompt);
257
262
  }
258
263
  async generate(prompt, conversationId) {
259
- const screenStream = await stormClient_1.stormClient.createUIPage(prompt, conversationId);
264
+ const client = new stormClient_1.StormClient(this.systemId);
265
+ const screenStream = await client.createUIPage(prompt, conversationId);
260
266
  let pageEvent = null;
261
267
  screenStream.on('data', (event) => {
262
268
  if (event.type === 'PAGE') {
@@ -273,7 +279,8 @@ class PageQueue extends node_events_1.EventEmitter {
273
279
  await this.processPageEventWithReferences(pageEvent);
274
280
  }
275
281
  async resolveReferences(content) {
276
- const referenceStream = await stormClient_1.stormClient.classifyUIReferences(content);
282
+ const client = new stormClient_1.StormClient(this.systemId);
283
+ const referenceStream = await client.classifyUIReferences(content);
277
284
  const references = [];
278
285
  referenceStream.on('data', (referenceData) => {
279
286
  if (referenceData.type !== 'REF_CLASSIFICATION') {
@@ -311,9 +311,10 @@ class StormCodegen {
311
311
  permissions: '0644',
312
312
  });
313
313
  const uiEvents = [];
314
+ const stormClient = new stormClient_1.StormClient(this.uiSystemId);
314
315
  // generate screens
315
316
  if (uiTemplates.length) {
316
- const screenStream = await stormClient_1.stormClient.listScreens({
317
+ const screenStream = await stormClient.listScreens({
317
318
  events: filteredEvents,
318
319
  templates: uiTemplates,
319
320
  context: relevantFiles,
@@ -346,7 +347,7 @@ class StormCodegen {
346
347
  context: relevantFiles.concat([getScreenEventsFile()]),
347
348
  prompt: this.userPrompt,
348
349
  };
349
- const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
350
+ const uiStream = await stormClient.createUIImplementation(payload);
350
351
  uiStream.on('data', (evt) => {
351
352
  const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
352
353
  if (uiFile != undefined) {
@@ -374,13 +375,13 @@ class StormCodegen {
374
375
  });
375
376
  allFiles.push(...screenFilesConverted);
376
377
  let webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
377
- webRouters = await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.generateCode.bind(stormClient_1.stormClient), webRouters, screenFilesConverted.concat([getScreenEventsFile()]));
378
+ webRouters = await this.processTemplates(blockUri, block.aiName, stormClient.generateCode.bind(stormClient), webRouters, screenFilesConverted.concat([getScreenEventsFile()]));
378
379
  // Gather the context files for implementation. These will be all be passed to the AI
379
380
  const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
380
381
  // Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
381
382
  let serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
382
383
  if (serviceFiles.length > 0) {
383
- serviceFiles = await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
384
+ serviceFiles = await this.processTemplates(blockUri, block.aiName, stormClient.createServiceImplementation.bind(stormClient), serviceFiles, contextFiles);
384
385
  }
385
386
  if (this.isAborted()) {
386
387
  return;
@@ -510,7 +511,8 @@ class StormCodegen {
510
511
  }
511
512
  }
512
513
  async classifyErrors(errors, basePath) {
513
- const errorStream = await stormClient_1.stormClient.createErrorClassification(errors, []);
514
+ const stormClient = new stormClient_1.StormClient(this.uiSystemId);
515
+ const errorStream = await stormClient.createErrorClassification(errors, []);
514
516
  const fixes = new Map();
515
517
  this.out.on('aborted', () => {
516
518
  errorStream.abort();
@@ -547,7 +549,8 @@ class StormCodegen {
547
549
  error: error,
548
550
  projectFiles: allFiles.map((f) => f.filename),
549
551
  };
550
- const detailsStream = await stormClient_1.stormClient.createErrorDetails(JSON.stringify(request), []);
552
+ const stormClient = new stormClient_1.StormClient(this.uiSystemId);
553
+ const detailsStream = await stormClient.createErrorDetails(JSON.stringify(request), []);
551
554
  detailsStream.on('data', (evt) => {
552
555
  if (evt.type === 'ERROR_DETAILS') {
553
556
  resolve(evt.payload.files);
@@ -611,7 +614,8 @@ class StormCodegen {
611
614
  async codeFix(blockUri, blockName, fix, history) {
612
615
  return new Promise(async (resolve, reject) => {
613
616
  try {
614
- const fixStream = await stormClient_1.stormClient.createCodeFix(fix, history, this.conversationId);
617
+ const stormClient = new stormClient_1.StormClient(this.uiSystemId);
618
+ const fixStream = await stormClient.createCodeFix(fix, history, this.conversationId);
615
619
  let resolved = false;
616
620
  fixStream.on('data', (evt) => {
617
621
  if (this.handleFileEvents(blockUri, blockName, evt)) {
@@ -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;
@@ -117,8 +117,10 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
117
117
  res.set(stormClient_1.ConversationIdHeader, systemId);
118
118
  sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
119
119
  const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
120
- const pagesWithImplementation = await stormClient_1.stormClient.replaceMockWithAPICall({
120
+ const client = new stormClient_1.StormClient(systemId);
121
+ const pagesWithImplementation = await client.replaceMockWithAPICall({
121
122
  pages: pagesFromDisk,
123
+ systemId: systemId,
122
124
  });
123
125
  await (0, utils_1.copyDirectory)(srcDir, destDir, (fileName, content) => {
124
126
  // find the page from result1 and write the content to the file
@@ -131,7 +133,7 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
131
133
  const pageContents = pagesWithImplementation.map((page) => {
132
134
  return page.content;
133
135
  });
134
- const prompt = await stormClient_1.stormClient.generatePrompt(pageContents);
136
+ const prompt = await client.generatePrompt(pageContents);
135
137
  sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
136
138
  req.query.systemId = systemId;
137
139
  const promptRequest = {
@@ -149,10 +151,12 @@ router.post('/ui/create-system-simple/:handle/:systemId', async (req, res) => {
149
151
  //res.set('Access-Control-Expose-Headers', ConversationIdHeader);
150
152
  //res.set(ConversationIdHeader, systemId);
151
153
  //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
154
+ const client = new stormClient_1.StormClient(systemId);
152
155
  try {
153
156
  const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
154
- const pagesWithImplementation = await stormClient_1.stormClient.replaceMockWithAPICall({
157
+ const pagesWithImplementation = await client.replaceMockWithAPICall({
155
158
  pages: pagesFromDisk,
159
+ systemId: systemId,
156
160
  });
157
161
  //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
158
162
  //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM));
@@ -165,7 +169,7 @@ router.post('/ui/create-system-simple/:handle/:systemId', async (req, res) => {
165
169
  }
166
170
  return page;
167
171
  });
168
- const systemUrl = await stormClient_1.stormClient.createSimpleBackend(handle, systemId, { pages: allFiles });
172
+ const systemUrl = await client.createSimpleBackend(handle, systemId, { pages: allFiles });
169
173
  //sendEvent(res, {type: 'SYSTEM_READY', created: Math.floor(Date.now() / 1000), reason: 'System Ready', payload: { systemUrl: systemUrl }});
170
174
  //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM));
171
175
  //sendDone(res);
@@ -250,7 +254,8 @@ router.post('/:handle/ui/iterative', async (req, res) => {
250
254
  try {
251
255
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
252
256
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
253
- const landingPagesStream = await stormClient_1.stormClient.createUILandingPages(aiRequest, conversationId);
257
+ const client = new stormClient_1.StormClient(conversationId); //todo is this correct we are using the landing page getConversationId down below as well
258
+ const landingPagesStream = await client.createUILandingPages(aiRequest, conversationId);
254
259
  onRequestAborted(req, res, () => {
255
260
  landingPagesStream.abort();
256
261
  });
@@ -339,8 +344,9 @@ router.post('/:handle/ui', async (req, res) => {
339
344
  try {
340
345
  const outerConversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()] || (0, crypto_1.randomUUID)();
341
346
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
347
+ const stormClient = new stormClient_1.StormClient(outerConversationId);
342
348
  // Get user journeys
343
- const userJourneysStream = await stormClient_1.stormClient.createUIUserJourneys(aiRequest, outerConversationId);
349
+ const userJourneysStream = await stormClient.createUIUserJourneys(aiRequest, outerConversationId);
344
350
  onRequestAborted(req, res, () => {
345
351
  userJourneysStream.abort();
346
352
  });
@@ -351,11 +357,11 @@ router.post('/:handle/ui', async (req, res) => {
351
357
  let systemPrompt = aiRequest.prompt;
352
358
  userJourneysStream.on('data', (data) => {
353
359
  try {
354
- sendEvent(res, data);
355
360
  if (data.type === 'PROMPT_IMPROVE') {
356
361
  systemPrompt = data.payload.prompt;
357
362
  }
358
363
  if (data.type !== 'USER_JOURNEY') {
364
+ sendEvent(res, data);
359
365
  return;
360
366
  }
361
367
  if (userJourneysStream.isAborted()) {
@@ -363,9 +369,11 @@ router.post('/:handle/ui', async (req, res) => {
363
369
  }
364
370
  data.payload.screens.forEach((screen) => {
365
371
  if (!uniqueUserJourneyScreens[screen.name]) {
372
+ screen.conversationId = (0, crypto_1.randomUUID)();
366
373
  uniqueUserJourneyScreens[screen.name] = screen;
367
374
  }
368
375
  });
376
+ sendEvent(res, data);
369
377
  }
370
378
  catch (e) {
371
379
  console.error('Failed to process event', e);
@@ -378,7 +386,7 @@ router.post('/:handle/ui', async (req, res) => {
378
386
  });
379
387
  let theme = '';
380
388
  try {
381
- const themeStream = await stormClient_1.stormClient.createTheme(aiRequest, outerConversationId);
389
+ const themeStream = await stormClient.createTheme(aiRequest, outerConversationId);
382
390
  onRequestAborted(req, res, () => {
383
391
  themeStream.abort();
384
392
  });
@@ -416,8 +424,11 @@ router.post('/:handle/ui', async (req, res) => {
416
424
  });
417
425
  }
418
426
  await waitForStormStream(userJourneysStream);
427
+ if (req.socket.closed) {
428
+ return;
429
+ }
419
430
  // Get the UI shells
420
- const shellsStream = await stormClient_1.stormClient.createUIShells({
431
+ const shellsStream = await stormClient.createUIShells({
421
432
  theme: theme || undefined,
422
433
  pages: Object.values(uniqueUserJourneyScreens).map((screen) => ({
423
434
  name: screen.name,
@@ -450,6 +461,9 @@ router.post('/:handle/ui', async (req, res) => {
450
461
  sendError(error, res);
451
462
  });
452
463
  await waitForStormStream(shellsStream);
464
+ if (req.socket.closed) {
465
+ return;
466
+ }
453
467
  UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
454
468
  await UI_SERVERS[outerConversationId].start();
455
469
  sendEvent(res, {
@@ -464,7 +478,7 @@ router.post('/:handle/ui', async (req, res) => {
464
478
  onRequestAborted(req, res, () => {
465
479
  queue.cancel();
466
480
  });
467
- queue.on('page', (screenData) => sendPageEvent(outerConversationId, screenData, res));
481
+ queue.on('page', (pageEvent) => sendPageEvent(outerConversationId, pageEvent, res));
468
482
  queue.on('event', (event) => {
469
483
  if (event.type === 'FILE_CHUNK') {
470
484
  return;
@@ -487,7 +501,7 @@ router.post('/:handle/ui', async (req, res) => {
487
501
  filename: screen.filename,
488
502
  storage_prefix: outerConversationId + '_',
489
503
  theme,
490
- })
504
+ }, screen.conversationId)
491
505
  .catch((e) => {
492
506
  console.error('Failed to generate page for screen %s', screen.name, e);
493
507
  sendError(e, res);
@@ -574,7 +588,8 @@ router.post('/ui/vote', async (req, res) => {
574
588
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
575
589
  const { topic, vote, mainConversationId } = aiRequest;
576
590
  try {
577
- await stormClient_1.stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
591
+ const stormClient = new stormClient_1.StormClient(mainConversationId);
592
+ await stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
578
593
  }
579
594
  catch (e) {
580
595
  res.status(500).send({ error: e.message });
@@ -585,7 +600,8 @@ router.post('/ui/get-vote', async (req, res) => {
585
600
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
586
601
  const { topic, mainConversationId } = aiRequest;
587
602
  try {
588
- const vote = await stormClient_1.stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
603
+ const stormClient = new stormClient_1.StormClient(mainConversationId);
604
+ const vote = await stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
589
605
  res.send({ vote });
590
606
  }
591
607
  catch (e) {
@@ -609,7 +625,8 @@ async function handleAll(req, res) {
609
625
  const eventParser = new event_parser_1.StormEventParser(stormOptions);
610
626
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
611
627
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
612
- const metaStream = await stormClient_1.stormClient.createMetadata(aiRequest, conversationId);
628
+ const stormClient = new stormClient_1.StormClient(systemId);
629
+ const metaStream = await stormClient.createMetadata(aiRequest, conversationId);
613
630
  onRequestAborted(req, res, () => {
614
631
  metaStream.abort();
615
632
  });
@@ -4,6 +4,7 @@ import { ConversationItem, CreateSimpleBackendRequest, HTMLPage, ImplementAPICli
4
4
  import { Page, StormEventPageUrl } from './events';
5
5
  export declare const STORM_ID = "storm";
6
6
  export declare const ConversationIdHeader = "Conversation-Id";
7
+ export declare const SystemIdHeader = "System-Id";
7
8
  export interface UIShellsPrompt {
8
9
  theme?: string;
9
10
  pages: {
@@ -55,9 +56,10 @@ export interface BasePromptRequest {
55
56
  prompt: string;
56
57
  skipImprovement?: boolean;
57
58
  }
58
- declare class StormClient {
59
+ export declare class StormClient {
59
60
  private readonly _baseUrl;
60
- constructor();
61
+ private readonly _systemId;
62
+ constructor(systemId?: string);
61
63
  private createOptions;
62
64
  private send;
63
65
  createMetadata(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
@@ -88,5 +90,3 @@ declare class StormClient {
88
90
  downloadSystem(handle: string, conversationId: string): Promise<Buffer>;
89
91
  uploadSystem(handle: string, conversationId: string, buffer: Buffer): Promise<Response>;
90
92
  }
91
- export declare const stormClient: StormClient;
92
- export {};
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.stormClient = exports.ConversationIdHeader = exports.STORM_ID = void 0;
6
+ exports.StormClient = exports.SystemIdHeader = exports.ConversationIdHeader = exports.STORM_ID = void 0;
7
7
  /**
8
8
  * Copyright 2023 Kapeta Inc.
9
9
  * SPDX-License-Identifier: BUSL-1.1
@@ -17,10 +17,13 @@ const fetch_retry_1 = __importDefault(require("fetch-retry"));
17
17
  const fetchWithRetries = (0, fetch_retry_1.default)(global.fetch, { retries: 5, retryDelay: 10 });
18
18
  exports.STORM_ID = 'storm';
19
19
  exports.ConversationIdHeader = 'Conversation-Id';
20
+ exports.SystemIdHeader = 'System-Id';
20
21
  class StormClient {
21
22
  _baseUrl;
22
- constructor() {
23
+ _systemId;
24
+ constructor(systemId) {
23
25
  this._baseUrl = (0, utils_1.getRemoteUrl)('ai-service', 'https://ai.kapeta.com');
26
+ this._systemId = systemId || "";
24
27
  }
25
28
  async createOptions(path, method, body) {
26
29
  const url = `${this._baseUrl}${path}`;
@@ -35,6 +38,9 @@ class StormClient {
35
38
  if (body.conversationId) {
36
39
  headers[exports.ConversationIdHeader] = body.conversationId;
37
40
  }
41
+ if (this._systemId) {
42
+ headers[exports.SystemIdHeader] = this._systemId;
43
+ }
38
44
  return {
39
45
  url,
40
46
  method: method,
@@ -103,6 +109,7 @@ class StormClient {
103
109
  createUIShells(prompt, conversationId) {
104
110
  return this.send('/v2/ui/shells', {
105
111
  prompt: JSON.stringify(prompt),
112
+ conversationId: conversationId,
106
113
  });
107
114
  }
108
115
  createUILandingPages(prompt, conversationId) {
@@ -138,6 +145,9 @@ class StormClient {
138
145
  const response = await fetch(u, {
139
146
  method: 'POST',
140
147
  body: JSON.stringify(prompt.pages),
148
+ headers: {
149
+ 'systemId': prompt.systemId,
150
+ },
141
151
  });
142
152
  return (await response.json());
143
153
  }
@@ -252,4 +262,4 @@ class StormClient {
252
262
  return response;
253
263
  }
254
264
  }
255
- exports.stormClient = new StormClient();
265
+ exports.StormClient = StormClient;
@@ -35,6 +35,7 @@ export interface ConversationItem {
35
35
  }
36
36
  export interface StormContextRequest<T = string> {
37
37
  conversationId?: string;
38
+ systemId?: string;
38
39
  history?: ConversationItem[];
39
40
  prompt: T;
40
41
  }
@@ -78,6 +79,7 @@ export declare enum HTMLPageEncoding {
78
79
  }
79
80
  export interface ImplementAPIClients {
80
81
  pages: HTMLPage[];
82
+ systemId: string;
81
83
  }
82
84
  export interface HTMLPage {
83
85
  fileName: string;
@@ -13,8 +13,8 @@ export declare class StormService {
13
13
  saveConversation(conversationId: string, events: StormEvent[]): Promise<void>;
14
14
  appendConversation(conversationId: string, events: StormEvent[]): Promise<void>;
15
15
  deleteConversation(conversationId: string): Promise<void>;
16
- uploadConversation(handle: string, conversationId: string): Promise<void>;
17
- installProjectById(handle: string, conversationId: string): Promise<void>;
16
+ uploadConversation(handle: string, systemId: string): Promise<void>;
17
+ installProjectById(handle: string, systemId: string): Promise<void>;
18
18
  }
19
19
  declare const _default: StormService;
20
20
  export default _default;
@@ -119,8 +119,8 @@ class StormService {
119
119
  await promises_1.default.rm(conversationDir, { recursive: true, force: true });
120
120
  }
121
121
  }
122
- async uploadConversation(handle, conversationId) {
123
- const tarballFile = this.getConversationTarball(conversationId);
122
+ async uploadConversation(handle, systemId) {
123
+ const tarballFile = this.getConversationTarball(systemId);
124
124
  const destDir = path_1.default.dirname(tarballFile);
125
125
  const tarballName = path_1.default.basename(tarballFile);
126
126
  await tar.create({
@@ -129,12 +129,14 @@ class StormService {
129
129
  gzip: true,
130
130
  filter: (entry) => !entry.includes(tarballName),
131
131
  }, ['.']);
132
- await stormClient_1.stormClient.uploadSystem(handle, conversationId, await promises_1.default.readFile(tarballFile));
132
+ const stormClient = new stormClient_1.StormClient(systemId);
133
+ await stormClient.uploadSystem(handle, systemId, await promises_1.default.readFile(tarballFile));
133
134
  }
134
- async installProjectById(handle, conversationId) {
135
- const tarballFile = this.getConversationTarball(conversationId);
135
+ async installProjectById(handle, systemId) {
136
+ const tarballFile = this.getConversationTarball(systemId);
136
137
  const destDir = path_1.default.dirname(tarballFile);
137
- const buffer = await stormClient_1.stormClient.downloadSystem(handle, conversationId);
138
+ const stormClient = new stormClient_1.StormClient(systemId);
139
+ const buffer = await stormClient.downloadSystem(handle, systemId);
138
140
  await promises_1.default.mkdir(destDir, { recursive: true });
139
141
  await promises_1.default.writeFile(tarballFile, buffer);
140
142
  await tar.extract({
@@ -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
  });
@@ -240,8 +244,9 @@ class PageQueue extends node_events_1.EventEmitter {
240
244
  console.warn('Skipping image reference of type %s for url %s', mimeType, prompt.url);
241
245
  return;
242
246
  }
247
+ const client = new stormClient_1.StormClient(this.systemId);
243
248
  this.images.set(prompt.url, prompt.description);
244
- const result = await stormClient_1.stormClient.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
249
+ const result = await client.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
245
250
  let imageEvent = null;
246
251
  result.on('data', (event) => {
247
252
  if (event.type === 'IMAGE') {
@@ -256,7 +261,8 @@ class PageQueue extends node_events_1.EventEmitter {
256
261
  this.emit('image', imageEvent, prompt);
257
262
  }
258
263
  async generate(prompt, conversationId) {
259
- const screenStream = await stormClient_1.stormClient.createUIPage(prompt, conversationId);
264
+ const client = new stormClient_1.StormClient(this.systemId);
265
+ const screenStream = await client.createUIPage(prompt, conversationId);
260
266
  let pageEvent = null;
261
267
  screenStream.on('data', (event) => {
262
268
  if (event.type === 'PAGE') {
@@ -273,7 +279,8 @@ class PageQueue extends node_events_1.EventEmitter {
273
279
  await this.processPageEventWithReferences(pageEvent);
274
280
  }
275
281
  async resolveReferences(content) {
276
- const referenceStream = await stormClient_1.stormClient.classifyUIReferences(content);
282
+ const client = new stormClient_1.StormClient(this.systemId);
283
+ const referenceStream = await client.classifyUIReferences(content);
277
284
  const references = [];
278
285
  referenceStream.on('data', (referenceData) => {
279
286
  if (referenceData.type !== 'REF_CLASSIFICATION') {
@@ -311,9 +311,10 @@ class StormCodegen {
311
311
  permissions: '0644',
312
312
  });
313
313
  const uiEvents = [];
314
+ const stormClient = new stormClient_1.StormClient(this.uiSystemId);
314
315
  // generate screens
315
316
  if (uiTemplates.length) {
316
- const screenStream = await stormClient_1.stormClient.listScreens({
317
+ const screenStream = await stormClient.listScreens({
317
318
  events: filteredEvents,
318
319
  templates: uiTemplates,
319
320
  context: relevantFiles,
@@ -346,7 +347,7 @@ class StormCodegen {
346
347
  context: relevantFiles.concat([getScreenEventsFile()]),
347
348
  prompt: this.userPrompt,
348
349
  };
349
- const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
350
+ const uiStream = await stormClient.createUIImplementation(payload);
350
351
  uiStream.on('data', (evt) => {
351
352
  const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
352
353
  if (uiFile != undefined) {
@@ -374,13 +375,13 @@ class StormCodegen {
374
375
  });
375
376
  allFiles.push(...screenFilesConverted);
376
377
  let webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
377
- webRouters = await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.generateCode.bind(stormClient_1.stormClient), webRouters, screenFilesConverted.concat([getScreenEventsFile()]));
378
+ webRouters = await this.processTemplates(blockUri, block.aiName, stormClient.generateCode.bind(stormClient), webRouters, screenFilesConverted.concat([getScreenEventsFile()]));
378
379
  // Gather the context files for implementation. These will be all be passed to the AI
379
380
  const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
380
381
  // Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
381
382
  let serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
382
383
  if (serviceFiles.length > 0) {
383
- serviceFiles = await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
384
+ serviceFiles = await this.processTemplates(blockUri, block.aiName, stormClient.createServiceImplementation.bind(stormClient), serviceFiles, contextFiles);
384
385
  }
385
386
  if (this.isAborted()) {
386
387
  return;
@@ -510,7 +511,8 @@ class StormCodegen {
510
511
  }
511
512
  }
512
513
  async classifyErrors(errors, basePath) {
513
- const errorStream = await stormClient_1.stormClient.createErrorClassification(errors, []);
514
+ const stormClient = new stormClient_1.StormClient(this.uiSystemId);
515
+ const errorStream = await stormClient.createErrorClassification(errors, []);
514
516
  const fixes = new Map();
515
517
  this.out.on('aborted', () => {
516
518
  errorStream.abort();
@@ -547,7 +549,8 @@ class StormCodegen {
547
549
  error: error,
548
550
  projectFiles: allFiles.map((f) => f.filename),
549
551
  };
550
- const detailsStream = await stormClient_1.stormClient.createErrorDetails(JSON.stringify(request), []);
552
+ const stormClient = new stormClient_1.StormClient(this.uiSystemId);
553
+ const detailsStream = await stormClient.createErrorDetails(JSON.stringify(request), []);
551
554
  detailsStream.on('data', (evt) => {
552
555
  if (evt.type === 'ERROR_DETAILS') {
553
556
  resolve(evt.payload.files);
@@ -611,7 +614,8 @@ class StormCodegen {
611
614
  async codeFix(blockUri, blockName, fix, history) {
612
615
  return new Promise(async (resolve, reject) => {
613
616
  try {
614
- const fixStream = await stormClient_1.stormClient.createCodeFix(fix, history, this.conversationId);
617
+ const stormClient = new stormClient_1.StormClient(this.uiSystemId);
618
+ const fixStream = await stormClient.createCodeFix(fix, history, this.conversationId);
615
619
  let resolved = false;
616
620
  fixStream.on('data', (evt) => {
617
621
  if (this.handleFileEvents(blockUri, blockName, evt)) {
@@ -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;