@kapeta/local-cluster-service 0.55.3 → 0.56.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 CHANGED
@@ -1,3 +1,16 @@
1
+ # [0.56.0](https://github.com/kapetacom/local-cluster-service/compare/v0.55.3...v0.56.0) (2024-07-15)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * better abort handling on reqs ([412c2f9](https://github.com/kapetacom/local-cluster-service/commit/412c2f9ab7e49f5e5d35d6b1ad4d683dafb170b8))
7
+ * guard against \\n in output ([5ae55ed](https://github.com/kapetacom/local-cluster-service/commit/5ae55ed2d6cacedb6867256a4245b413b14e061c))
8
+
9
+
10
+ ### Features
11
+
12
+ * add /block/codegen endpoint to (re)run codegen on a block ([0fec94b](https://github.com/kapetacom/local-cluster-service/commit/0fec94b563fd1bbc340c478b937f2848c9a4e644))
13
+
1
14
  ## [0.55.3](https://github.com/kapetacom/local-cluster-service/compare/v0.55.2...v0.55.3) (2024-07-12)
2
15
 
3
16
 
@@ -10,9 +10,10 @@ export declare class StormCodegen {
10
10
  private readonly blocks;
11
11
  private readonly out;
12
12
  private readonly events;
13
- private readonly tmpDir;
13
+ private tmpDir;
14
14
  private readonly conversationId;
15
15
  constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
16
+ setTmpDir(tmpDir: string): void;
16
17
  process(): Promise<void>;
17
18
  isAborted(): boolean;
18
19
  getStream(): StormStream;
@@ -107,6 +107,9 @@ class StormCodegen {
107
107
  this.tmpDir = path_2.default.join(node_os_1.default.tmpdir(), conversationId);
108
108
  this.conversationId = conversationId;
109
109
  }
110
+ setTmpDir(tmpDir) {
111
+ this.tmpDir = tmpDir;
112
+ }
110
113
  async process() {
111
114
  const promises = this.blocks.map((block) => {
112
115
  if (block.archetype) {
@@ -241,7 +244,7 @@ class StormCodegen {
241
244
  payload: {
242
245
  filename: data.payload.filename,
243
246
  path: (0, path_2.join)(this.getBasePath(blockUri.fullName), data.payload.filename),
244
- content: data.payload.content,
247
+ content: data.payload.content.replace(/\\n/g, '\n'),
245
248
  blockRef: ref,
246
249
  instanceId: event_parser_1.StormEventParser.toInstanceIdFromRef(ref),
247
250
  },
@@ -9,6 +9,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  const express_promise_router_1 = __importDefault(require("express-promise-router"));
11
11
  const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const lodash_1 = __importDefault(require("lodash"));
12
14
  const cors_1 = require("../middleware/cors");
13
15
  const stringBody_1 = require("../middleware/stringBody");
14
16
  const stormClient_1 = require("./stormClient");
@@ -16,8 +18,6 @@ const events_1 = require("./events");
16
18
  const event_parser_1 = require("./event-parser");
17
19
  const codegen_1 = require("./codegen");
18
20
  const assetManager_1 = require("../assetManager");
19
- const path_1 = __importDefault(require("path"));
20
- const lodash_1 = __importDefault(require("lodash"));
21
21
  const router = (0, express_promise_router_1.default)();
22
22
  router.use('/', cors_1.corsHandler);
23
23
  router.use('/', stringBody_1.stringBody);
@@ -86,6 +86,8 @@ router.post('/:handle/all', async (req, res) => {
86
86
  if (metaStream.isAborted()) {
87
87
  return;
88
88
  }
89
+ // Cancel debounce, we don't need to send the plan again
90
+ sendUpdatedPlan.cancel();
89
91
  sendDefinitions(res, result);
90
92
  if (!req.query.skipCodegen) {
91
93
  try {
@@ -134,6 +136,26 @@ router.post('/block/create', async (req, res) => {
134
136
  res.status(500).send({ error: err.message });
135
137
  }
136
138
  });
139
+ router.post('/block/codegen', async (req, res) => {
140
+ const body = JSON.parse(req.stringBody ?? '{}');
141
+ console.log('Codegen request', body);
142
+ const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
143
+ try {
144
+ const stormCodegen = new codegen_1.StormCodegen(conversationId ?? '', body.prompt, [body.block], body.events || []);
145
+ stormCodegen.setTmpDir(body.outDir);
146
+ onRequestAborted(req, res, () => {
147
+ stormCodegen.abort();
148
+ });
149
+ const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
150
+ await stormCodegen.process();
151
+ await codegenPromise;
152
+ sendDone(res);
153
+ }
154
+ catch (err) {
155
+ console.error('Failed to generate code', err);
156
+ res.status(500).send({ error: err.message });
157
+ }
158
+ });
137
159
  function sendDefinitions(res, result) {
138
160
  sendEvent(res, {
139
161
  type: 'DEFINITION_CHANGE',
@@ -187,10 +209,7 @@ function sendEvent(res, evt) {
187
209
  res.write(JSON.stringify(evt) + '\n');
188
210
  }
189
211
  function onRequestAborted(req, res, onAborted) {
190
- req.on('close', () => {
191
- onAborted();
192
- });
193
- res.on('close', () => {
212
+ req.socket.on('close', () => {
194
213
  onAborted();
195
214
  });
196
215
  }
@@ -7,6 +7,7 @@ import { EventEmitter } from 'node:events';
7
7
  import { StormEvent } from './events';
8
8
  import { AIFileTypes, GeneratedFile } from '@kapeta/codegen';
9
9
  import { BlockDefinition } from '@kapeta/schemas';
10
+ import { BlockDefinitionInfo } from './event-parser';
10
11
  export declare class StormStream extends EventEmitter {
11
12
  private conversationId;
12
13
  private lines;
@@ -41,6 +42,12 @@ export interface StormCreateBlockRequest {
41
42
  tmpPath: string;
42
43
  newPath: string;
43
44
  }
45
+ export interface StormCodegenRequest {
46
+ block: BlockDefinitionInfo;
47
+ prompt: string;
48
+ events: StormEvent[];
49
+ outDir: string;
50
+ }
44
51
  export interface StormFileInfo extends GeneratedFile {
45
52
  type: AIFileTypes;
46
53
  }
@@ -10,9 +10,10 @@ export declare class StormCodegen {
10
10
  private readonly blocks;
11
11
  private readonly out;
12
12
  private readonly events;
13
- private readonly tmpDir;
13
+ private tmpDir;
14
14
  private readonly conversationId;
15
15
  constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
16
+ setTmpDir(tmpDir: string): void;
16
17
  process(): Promise<void>;
17
18
  isAborted(): boolean;
18
19
  getStream(): StormStream;
@@ -107,6 +107,9 @@ class StormCodegen {
107
107
  this.tmpDir = path_2.default.join(node_os_1.default.tmpdir(), conversationId);
108
108
  this.conversationId = conversationId;
109
109
  }
110
+ setTmpDir(tmpDir) {
111
+ this.tmpDir = tmpDir;
112
+ }
110
113
  async process() {
111
114
  const promises = this.blocks.map((block) => {
112
115
  if (block.archetype) {
@@ -241,7 +244,7 @@ class StormCodegen {
241
244
  payload: {
242
245
  filename: data.payload.filename,
243
246
  path: (0, path_2.join)(this.getBasePath(blockUri.fullName), data.payload.filename),
244
- content: data.payload.content,
247
+ content: data.payload.content.replace(/\\n/g, '\n'),
245
248
  blockRef: ref,
246
249
  instanceId: event_parser_1.StormEventParser.toInstanceIdFromRef(ref),
247
250
  },
@@ -9,6 +9,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  const express_promise_router_1 = __importDefault(require("express-promise-router"));
11
11
  const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const lodash_1 = __importDefault(require("lodash"));
12
14
  const cors_1 = require("../middleware/cors");
13
15
  const stringBody_1 = require("../middleware/stringBody");
14
16
  const stormClient_1 = require("./stormClient");
@@ -16,8 +18,6 @@ const events_1 = require("./events");
16
18
  const event_parser_1 = require("./event-parser");
17
19
  const codegen_1 = require("./codegen");
18
20
  const assetManager_1 = require("../assetManager");
19
- const path_1 = __importDefault(require("path"));
20
- const lodash_1 = __importDefault(require("lodash"));
21
21
  const router = (0, express_promise_router_1.default)();
22
22
  router.use('/', cors_1.corsHandler);
23
23
  router.use('/', stringBody_1.stringBody);
@@ -86,6 +86,8 @@ router.post('/:handle/all', async (req, res) => {
86
86
  if (metaStream.isAborted()) {
87
87
  return;
88
88
  }
89
+ // Cancel debounce, we don't need to send the plan again
90
+ sendUpdatedPlan.cancel();
89
91
  sendDefinitions(res, result);
90
92
  if (!req.query.skipCodegen) {
91
93
  try {
@@ -134,6 +136,26 @@ router.post('/block/create', async (req, res) => {
134
136
  res.status(500).send({ error: err.message });
135
137
  }
136
138
  });
139
+ router.post('/block/codegen', async (req, res) => {
140
+ const body = JSON.parse(req.stringBody ?? '{}');
141
+ console.log('Codegen request', body);
142
+ const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
143
+ try {
144
+ const stormCodegen = new codegen_1.StormCodegen(conversationId ?? '', body.prompt, [body.block], body.events || []);
145
+ stormCodegen.setTmpDir(body.outDir);
146
+ onRequestAborted(req, res, () => {
147
+ stormCodegen.abort();
148
+ });
149
+ const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
150
+ await stormCodegen.process();
151
+ await codegenPromise;
152
+ sendDone(res);
153
+ }
154
+ catch (err) {
155
+ console.error('Failed to generate code', err);
156
+ res.status(500).send({ error: err.message });
157
+ }
158
+ });
137
159
  function sendDefinitions(res, result) {
138
160
  sendEvent(res, {
139
161
  type: 'DEFINITION_CHANGE',
@@ -187,10 +209,7 @@ function sendEvent(res, evt) {
187
209
  res.write(JSON.stringify(evt) + '\n');
188
210
  }
189
211
  function onRequestAborted(req, res, onAborted) {
190
- req.on('close', () => {
191
- onAborted();
192
- });
193
- res.on('close', () => {
212
+ req.socket.on('close', () => {
194
213
  onAborted();
195
214
  });
196
215
  }
@@ -7,6 +7,7 @@ import { EventEmitter } from 'node:events';
7
7
  import { StormEvent } from './events';
8
8
  import { AIFileTypes, GeneratedFile } from '@kapeta/codegen';
9
9
  import { BlockDefinition } from '@kapeta/schemas';
10
+ import { BlockDefinitionInfo } from './event-parser';
10
11
  export declare class StormStream extends EventEmitter {
11
12
  private conversationId;
12
13
  private lines;
@@ -41,6 +42,12 @@ export interface StormCreateBlockRequest {
41
42
  tmpPath: string;
42
43
  newPath: string;
43
44
  }
45
+ export interface StormCodegenRequest {
46
+ block: BlockDefinitionInfo;
47
+ prompt: string;
48
+ events: StormEvent[];
49
+ outDir: string;
50
+ }
44
51
  export interface StormFileInfo extends GeneratedFile {
45
52
  type: AIFileTypes;
46
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.55.3",
3
+ "version": "0.56.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -106,7 +106,7 @@ export class StormCodegen {
106
106
  private readonly blocks: BlockDefinitionInfo[];
107
107
  private readonly out = new StormStream();
108
108
  private readonly events: StormEvent[];
109
- private readonly tmpDir: string;
109
+ private tmpDir: string;
110
110
  private readonly conversationId: string;
111
111
 
112
112
  constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]) {
@@ -117,6 +117,10 @@ export class StormCodegen {
117
117
  this.conversationId = conversationId;
118
118
  }
119
119
 
120
+ public setTmpDir(tmpDir: string) {
121
+ this.tmpDir = tmpDir;
122
+ }
123
+
120
124
  public async process() {
121
125
  const promises = this.blocks.map((block) => {
122
126
  if (block.archetype) {
@@ -273,7 +277,7 @@ export class StormCodegen {
273
277
  payload: {
274
278
  filename: data.payload.filename,
275
279
  path: join(this.getBasePath(blockUri.fullName), data.payload.filename),
276
- content: data.payload.content,
280
+ content: data.payload.content.replace(/\\n/g, '\n'),
277
281
  blockRef: ref,
278
282
  instanceId: StormEventParser.toInstanceIdFromRef(ref),
279
283
  },
@@ -320,6 +324,7 @@ export class StormCodegen {
320
324
  file.type !== AIFileTypes.WEB_ROUTER
321
325
  );
322
326
  const uiTemplates = allFiles.filter((file) => file.type === AIFileTypes.WEB_SCREEN);
327
+
323
328
  const screenFiles: StormEventFileDone[] = [];
324
329
 
325
330
  let filteredEvents = [] as StormEvent[];
@@ -6,10 +6,12 @@
6
6
  import Router from 'express-promise-router';
7
7
  import FS from 'fs-extra';
8
8
  import { Response } from 'express';
9
+ import Path from 'path';
10
+ import _ from 'lodash';
9
11
  import { corsHandler } from '../middleware/cors';
10
12
  import { stringBody } from '../middleware/stringBody';
11
13
  import { KapetaBodyRequest } from '../types';
12
- import { StormContextRequest, StormCreateBlockRequest, StormStream } from './stream';
14
+ import { StormCodegenRequest, StormContextRequest, StormCreateBlockRequest, StormStream } from './stream';
13
15
  import { ConversationIdHeader, stormClient } from './stormClient';
14
16
  import { StormEvent, StormEventPhaseType } from './events';
15
17
  import {
@@ -21,8 +23,6 @@ import {
21
23
  } from './event-parser';
22
24
  import { StormCodegen } from './codegen';
23
25
  import { assetManager } from '../assetManager';
24
- import Path from 'path';
25
- import _ from 'lodash';
26
26
 
27
27
  const router = Router();
28
28
 
@@ -108,7 +108,8 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
108
108
  if (metaStream.isAborted()) {
109
109
  return;
110
110
  }
111
-
111
+ // Cancel debounce, we don't need to send the plan again
112
+ sendUpdatedPlan.cancel();
112
113
  sendDefinitions(res, result);
113
114
 
114
115
  if (!req.query.skipCodegen) {
@@ -169,6 +170,31 @@ router.post('/block/create', async (req: KapetaBodyRequest, res: Response) => {
169
170
  }
170
171
  });
171
172
 
173
+ router.post('/block/codegen', async (req: KapetaBodyRequest, res: Response) => {
174
+ const body: StormCodegenRequest = JSON.parse(req.stringBody ?? '{}');
175
+ console.log('Codegen request', body);
176
+ const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
177
+ try {
178
+ const stormCodegen = new StormCodegen(conversationId ?? '', body.prompt, [body.block], body.events || []);
179
+ stormCodegen.setTmpDir(body.outDir);
180
+
181
+ onRequestAborted(req, res, () => {
182
+ stormCodegen.abort();
183
+ });
184
+
185
+ const codegenPromise = streamStormPartialResponse(stormCodegen.getStream(), res);
186
+
187
+ await stormCodegen.process();
188
+
189
+ await codegenPromise;
190
+
191
+ sendDone(res);
192
+ } catch (err: any) {
193
+ console.error('Failed to generate code', err);
194
+ res.status(500).send({ error: err.message });
195
+ }
196
+ });
197
+
172
198
  function sendDefinitions(res: Response, result: StormDefinitions) {
173
199
  sendEvent(res, {
174
200
  type: 'DEFINITION_CHANGE',
@@ -228,10 +254,7 @@ function sendEvent(res: Response, evt: StormEvent) {
228
254
  }
229
255
 
230
256
  function onRequestAborted(req: KapetaBodyRequest, res: Response, onAborted: () => void) {
231
- req.on('close', () => {
232
- onAborted();
233
- });
234
- res.on('close', () => {
257
+ req.socket.on('close', () => {
235
258
  onAborted();
236
259
  });
237
260
  }
@@ -6,6 +6,7 @@ import { EventEmitter } from 'node:events';
6
6
  import { StormEvent } from './events';
7
7
  import { AIFileTypes, GeneratedFile } from '@kapeta/codegen';
8
8
  import { BlockDefinition } from '@kapeta/schemas';
9
+ import { BlockDefinitionInfo } from './event-parser';
9
10
 
10
11
  export class StormStream extends EventEmitter {
11
12
  private conversationId: string = '';
@@ -106,6 +107,13 @@ export interface StormCreateBlockRequest {
106
107
  newPath: string;
107
108
  }
108
109
 
110
+ export interface StormCodegenRequest {
111
+ block: BlockDefinitionInfo;
112
+ prompt: string;
113
+ events: StormEvent[];
114
+ outDir: string;
115
+ }
116
+
109
117
  export interface StormFileInfo extends GeneratedFile {
110
118
  type: AIFileTypes;
111
119
  }