@kapeta/local-cluster-service 0.70.12 → 0.71.1
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 +15 -0
- package/dist/cjs/src/storm/events.d.ts +2 -0
- package/dist/cjs/src/storm/events.js +2 -0
- package/dist/cjs/src/storm/routes.js +34 -10
- package/dist/cjs/src/storm/stormClient.d.ts +2 -1
- package/dist/cjs/src/storm/stormClient.js +8 -23
- package/dist/esm/src/storm/events.d.ts +2 -0
- package/dist/esm/src/storm/events.js +2 -0
- package/dist/esm/src/storm/routes.js +34 -10
- package/dist/esm/src/storm/stormClient.d.ts +2 -1
- package/dist/esm/src/storm/stormClient.js +8 -23
- package/package.json +2 -2
- package/src/storm/PageGenerator.ts +1 -1
- package/src/storm/events.ts +2 -0
- package/src/storm/routes.ts +40 -10
- package/src/storm/stormClient.ts +3 -19
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## [0.71.1](https://github.com/kapetacom/local-cluster-service/compare/v0.71.0...v0.71.1) (2024-09-17)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* emit all events except chunks from screen endpoints ([8e67b58](https://github.com/kapetacom/local-cluster-service/commit/8e67b58b6cc70c359556edfc61dd904254f9d3bc))
|
7
|
+
* replace fetch retry handler library to avoid fetch failed ([59a4342](https://github.com/kapetacom/local-cluster-service/commit/59a4342203cd8efb706beba254284ce321fe313a))
|
8
|
+
|
9
|
+
# [0.71.0](https://github.com/kapetacom/local-cluster-service/compare/v0.70.12...v0.71.0) (2024-09-16)
|
10
|
+
|
11
|
+
|
12
|
+
### Features
|
13
|
+
|
14
|
+
* Add two new phases ([d2805a8](https://github.com/kapetacom/local-cluster-service/commit/d2805a89d7e21b6f30674ae852ddc356954e7d79))
|
15
|
+
|
1
16
|
## [0.70.12](https://github.com/kapetacom/local-cluster-service/compare/v0.70.11...v0.70.12) (2024-09-13)
|
2
17
|
|
3
18
|
|
@@ -271,6 +271,8 @@ export interface StormEventDefinitionChange {
|
|
271
271
|
payload: StormDefinitions;
|
272
272
|
}
|
273
273
|
export declare enum StormEventPhaseType {
|
274
|
+
IMPLEMENT_APIS = "IMPLEMENT_APIS",// Implement APIs in the html pages
|
275
|
+
COMPOSE_SYSTEM_PROMPT = "COMPOSE_SYSTEM_PROMPT",// Compose system prompt for bottom-up approach
|
274
276
|
META = "META",
|
275
277
|
DEFINITIONS = "DEFINITIONS",
|
276
278
|
IMPLEMENTATION = "IMPLEMENTATION",
|
@@ -11,6 +11,8 @@ var StormEventBlockStatusType;
|
|
11
11
|
})(StormEventBlockStatusType || (exports.StormEventBlockStatusType = StormEventBlockStatusType = {}));
|
12
12
|
var StormEventPhaseType;
|
13
13
|
(function (StormEventPhaseType) {
|
14
|
+
StormEventPhaseType["IMPLEMENT_APIS"] = "IMPLEMENT_APIS";
|
15
|
+
StormEventPhaseType["COMPOSE_SYSTEM_PROMPT"] = "COMPOSE_SYSTEM_PROMPT";
|
14
16
|
StormEventPhaseType["META"] = "META";
|
15
17
|
StormEventPhaseType["DEFINITIONS"] = "DEFINITIONS";
|
16
18
|
StormEventPhaseType["IMPLEMENTATION"] = "IMPLEMENTATION";
|
@@ -72,6 +72,10 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
|
|
72
72
|
const systemId = req.params.systemId;
|
73
73
|
const srcDir = (0, page_utils_1.getSystemBaseDir)(systemId);
|
74
74
|
const destDir = (0, page_utils_1.getSystemBaseImplDir)(systemId);
|
75
|
+
res.set('Content-Type', 'application/x-ndjson');
|
76
|
+
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
77
|
+
res.set(stormClient_1.ConversationIdHeader, systemId);
|
78
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
|
75
79
|
await (0, utils_1.copyDirectory)(srcDir, destDir, async (fileName, content) => {
|
76
80
|
const result = await stormClient_1.stormClient.implementAPIClients({
|
77
81
|
content: content,
|
@@ -79,8 +83,11 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
|
|
79
83
|
});
|
80
84
|
return result;
|
81
85
|
});
|
86
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
|
87
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
|
82
88
|
const pages = (0, utils_1.readPages)(destDir);
|
83
89
|
const prompt = await stormClient_1.stormClient.generatePrompt(pages);
|
90
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
|
84
91
|
req.query.systemId = systemId;
|
85
92
|
const promptRequest = {
|
86
93
|
prompt: prompt,
|
@@ -122,6 +129,12 @@ router.post('/ui/screen', async (req, res) => {
|
|
122
129
|
console.error('Failed to process page', err);
|
123
130
|
sendError(err, res);
|
124
131
|
});
|
132
|
+
queue.on('event', (event) => {
|
133
|
+
if (event.type === 'FILE_CHUNK') {
|
134
|
+
return;
|
135
|
+
}
|
136
|
+
sendEvent(res, event);
|
137
|
+
});
|
125
138
|
await queue.addPrompt(aiRequest, conversationId, true);
|
126
139
|
await queue.wait();
|
127
140
|
await Promise.allSettled(promises);
|
@@ -200,8 +213,11 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
200
213
|
pageQueue.cancel();
|
201
214
|
});
|
202
215
|
pageQueue.on('page', (screenData) => sendPageEvent(landingPagesStream.getConversationId(), screenData, res));
|
203
|
-
pageQueue.on('event', (
|
204
|
-
|
216
|
+
pageQueue.on('event', (event) => {
|
217
|
+
if (event.type === 'FILE_CHUNK') {
|
218
|
+
return;
|
219
|
+
}
|
220
|
+
sendEvent(res, event);
|
205
221
|
});
|
206
222
|
pageQueue.on('error', (err) => {
|
207
223
|
console.error('Failed to process page', err);
|
@@ -353,8 +369,11 @@ router.post('/:handle/ui', async (req, res) => {
|
|
353
369
|
queue.cancel();
|
354
370
|
});
|
355
371
|
queue.on('page', (screenData) => sendPageEvent(outerConversationId, screenData, res));
|
356
|
-
queue.on('event', (
|
357
|
-
|
372
|
+
queue.on('event', (event) => {
|
373
|
+
if (event.type === 'FILE_CHUNK') {
|
374
|
+
return;
|
375
|
+
}
|
376
|
+
sendEvent(res, event);
|
358
377
|
});
|
359
378
|
queue.on('error', (err) => {
|
360
379
|
console.error('Failed to process page', err);
|
@@ -410,10 +429,11 @@ router.post('/ui/edit', async (req, res) => {
|
|
410
429
|
return promise;
|
411
430
|
}
|
412
431
|
});
|
413
|
-
queue.on('event', (
|
414
|
-
if (
|
415
|
-
|
432
|
+
queue.on('event', (event) => {
|
433
|
+
if (event.type === 'FILE_CHUNK') {
|
434
|
+
return;
|
416
435
|
}
|
436
|
+
sendEvent(res, event);
|
417
437
|
});
|
418
438
|
queue.on('error', (err) => {
|
419
439
|
console.error('Failed to process page', err);
|
@@ -490,9 +510,13 @@ async function handleAll(req, res) {
|
|
490
510
|
onRequestAborted(req, res, () => {
|
491
511
|
metaStream.abort();
|
492
512
|
});
|
493
|
-
|
494
|
-
|
495
|
-
res.
|
513
|
+
// We check if the headers have been sent, because we might have already sent some data
|
514
|
+
// before this function is called
|
515
|
+
if (!res.headersSent) {
|
516
|
+
res.set('Content-Type', 'application/x-ndjson');
|
517
|
+
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
518
|
+
res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
|
519
|
+
}
|
496
520
|
let currentPhase = events_1.StormEventPhaseType.META;
|
497
521
|
// Helper to avoid sending the plan multiple times in a row
|
498
522
|
const sendUpdatedPlan = lodash_1.default.debounce(sendDefinitions, 50, { maxWait: 200 });
|
@@ -1,3 +1,4 @@
|
|
1
|
+
/// <reference types="node" />
|
1
2
|
import { ConversationItem, ImplementAPIClientsRequest, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
|
2
3
|
import { Page, StormEventPageUrl } from './events';
|
3
4
|
export declare const STORM_ID = "storm";
|
@@ -65,7 +66,7 @@ declare class StormClient {
|
|
65
66
|
createUIShells(prompt: UIShellsPrompt, conversationId?: string): Promise<StormStream>;
|
66
67
|
createUILandingPages(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
67
68
|
createUIPage(prompt: UIPagePrompt, conversationId?: string, history?: ConversationItem[]): Promise<StormStream>;
|
68
|
-
voteUIPage(topic: string, conversationId: string, vote: -1 | 0 | 1, mainConversationId?: string): Promise<
|
69
|
+
voteUIPage(topic: string, conversationId: string, vote: -1 | 0 | 1, mainConversationId?: string): Promise<Response>;
|
69
70
|
getVoteUIPage(topic: string, conversationId: string, mainConversationId?: string): Promise<{
|
70
71
|
vote: -1 | 0 | 1;
|
71
72
|
}>;
|
@@ -13,22 +13,8 @@ const utils_1 = require("../utils/utils");
|
|
13
13
|
const promises_1 = __importDefault(require("node:readline/promises"));
|
14
14
|
const node_stream_1 = require("node:stream");
|
15
15
|
const stream_1 = require("./stream");
|
16
|
-
const
|
17
|
-
|
18
|
-
// See https://github.com/nodejs/undici/blob/990df2c7e37cbe5bb44fe2f576dddeaeb5916590/docs/docs/api/RetryAgent.md
|
19
|
-
const retryAgent = new undici_1.RetryAgent(new undici_1.Agent(), {
|
20
|
-
methods: [
|
21
|
-
// Added methods ↓ (not idempotent), but we want to retry on POST:
|
22
|
-
'POST',
|
23
|
-
// defaults below
|
24
|
-
'GET',
|
25
|
-
'HEAD',
|
26
|
-
'OPTIONS',
|
27
|
-
'PUT',
|
28
|
-
'DELETE',
|
29
|
-
'TRACE',
|
30
|
-
],
|
31
|
-
});
|
16
|
+
const fetch_retry_1 = __importDefault(require("fetch-retry"));
|
17
|
+
const fetchWithRetries = (0, fetch_retry_1.default)(global.fetch, { retries: 5, retryDelay: 10 });
|
32
18
|
exports.STORM_ID = 'storm';
|
33
19
|
exports.ConversationIdHeader = 'Conversation-Id';
|
34
20
|
class StormClient {
|
@@ -54,7 +40,6 @@ class StormClient {
|
|
54
40
|
method: method,
|
55
41
|
body: JSON.stringify(body),
|
56
42
|
headers,
|
57
|
-
dispatcher: retryAgent,
|
58
43
|
};
|
59
44
|
}
|
60
45
|
async send(path, body, method = 'POST') {
|
@@ -65,7 +50,7 @@ class StormClient {
|
|
65
50
|
});
|
66
51
|
const abort = new AbortController();
|
67
52
|
options.signal = abort.signal;
|
68
|
-
const response = await (
|
53
|
+
const response = await fetchWithRetries(options.url, options);
|
69
54
|
if (response.status !== 200) {
|
70
55
|
throw new Error(`Got error response from ${options.url}: ${response.status}\nContent: ${await response.text()}`);
|
71
56
|
}
|
@@ -138,19 +123,19 @@ class StormClient {
|
|
138
123
|
prompt: JSON.stringify({ topic, vote, mainConversationId }),
|
139
124
|
conversationId,
|
140
125
|
});
|
141
|
-
return
|
126
|
+
return fetch(options.url, options);
|
142
127
|
}
|
143
128
|
async getVoteUIPage(topic, conversationId, mainConversationId) {
|
144
129
|
const options = await this.createOptions('/v2/ui/get-vote', 'POST', {
|
145
130
|
prompt: JSON.stringify({ topic, mainConversationId }),
|
146
131
|
conversationId,
|
147
132
|
});
|
148
|
-
const response = await
|
133
|
+
const response = await fetch(options.url, options);
|
149
134
|
return response.json();
|
150
135
|
}
|
151
136
|
async implementAPIClients(prompt) {
|
152
137
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients`;
|
153
|
-
const response = await
|
138
|
+
const response = await fetch(u, {
|
154
139
|
method: 'POST',
|
155
140
|
body: JSON.stringify({
|
156
141
|
fileName: prompt.fileName,
|
@@ -162,7 +147,7 @@ class StormClient {
|
|
162
147
|
}
|
163
148
|
async generatePrompt(pages) {
|
164
149
|
const u = `${this._baseUrl}/v2/ui/prompt`;
|
165
|
-
const response = await
|
150
|
+
const response = await fetch(u, {
|
166
151
|
method: 'POST',
|
167
152
|
body: JSON.stringify({
|
168
153
|
pages: pages,
|
@@ -235,7 +220,7 @@ class StormClient {
|
|
235
220
|
prompt: '',
|
236
221
|
conversationId: conversationId,
|
237
222
|
});
|
238
|
-
const response = await
|
223
|
+
const response = await fetch(options.url, options);
|
239
224
|
return response.text();
|
240
225
|
}
|
241
226
|
}
|
@@ -271,6 +271,8 @@ export interface StormEventDefinitionChange {
|
|
271
271
|
payload: StormDefinitions;
|
272
272
|
}
|
273
273
|
export declare enum StormEventPhaseType {
|
274
|
+
IMPLEMENT_APIS = "IMPLEMENT_APIS",// Implement APIs in the html pages
|
275
|
+
COMPOSE_SYSTEM_PROMPT = "COMPOSE_SYSTEM_PROMPT",// Compose system prompt for bottom-up approach
|
274
276
|
META = "META",
|
275
277
|
DEFINITIONS = "DEFINITIONS",
|
276
278
|
IMPLEMENTATION = "IMPLEMENTATION",
|
@@ -11,6 +11,8 @@ var StormEventBlockStatusType;
|
|
11
11
|
})(StormEventBlockStatusType || (exports.StormEventBlockStatusType = StormEventBlockStatusType = {}));
|
12
12
|
var StormEventPhaseType;
|
13
13
|
(function (StormEventPhaseType) {
|
14
|
+
StormEventPhaseType["IMPLEMENT_APIS"] = "IMPLEMENT_APIS";
|
15
|
+
StormEventPhaseType["COMPOSE_SYSTEM_PROMPT"] = "COMPOSE_SYSTEM_PROMPT";
|
14
16
|
StormEventPhaseType["META"] = "META";
|
15
17
|
StormEventPhaseType["DEFINITIONS"] = "DEFINITIONS";
|
16
18
|
StormEventPhaseType["IMPLEMENTATION"] = "IMPLEMENTATION";
|
@@ -72,6 +72,10 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
|
|
72
72
|
const systemId = req.params.systemId;
|
73
73
|
const srcDir = (0, page_utils_1.getSystemBaseDir)(systemId);
|
74
74
|
const destDir = (0, page_utils_1.getSystemBaseImplDir)(systemId);
|
75
|
+
res.set('Content-Type', 'application/x-ndjson');
|
76
|
+
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
77
|
+
res.set(stormClient_1.ConversationIdHeader, systemId);
|
78
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
|
75
79
|
await (0, utils_1.copyDirectory)(srcDir, destDir, async (fileName, content) => {
|
76
80
|
const result = await stormClient_1.stormClient.implementAPIClients({
|
77
81
|
content: content,
|
@@ -79,8 +83,11 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
|
|
79
83
|
});
|
80
84
|
return result;
|
81
85
|
});
|
86
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
|
87
|
+
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
|
82
88
|
const pages = (0, utils_1.readPages)(destDir);
|
83
89
|
const prompt = await stormClient_1.stormClient.generatePrompt(pages);
|
90
|
+
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
|
84
91
|
req.query.systemId = systemId;
|
85
92
|
const promptRequest = {
|
86
93
|
prompt: prompt,
|
@@ -122,6 +129,12 @@ router.post('/ui/screen', async (req, res) => {
|
|
122
129
|
console.error('Failed to process page', err);
|
123
130
|
sendError(err, res);
|
124
131
|
});
|
132
|
+
queue.on('event', (event) => {
|
133
|
+
if (event.type === 'FILE_CHUNK') {
|
134
|
+
return;
|
135
|
+
}
|
136
|
+
sendEvent(res, event);
|
137
|
+
});
|
125
138
|
await queue.addPrompt(aiRequest, conversationId, true);
|
126
139
|
await queue.wait();
|
127
140
|
await Promise.allSettled(promises);
|
@@ -200,8 +213,11 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
200
213
|
pageQueue.cancel();
|
201
214
|
});
|
202
215
|
pageQueue.on('page', (screenData) => sendPageEvent(landingPagesStream.getConversationId(), screenData, res));
|
203
|
-
pageQueue.on('event', (
|
204
|
-
|
216
|
+
pageQueue.on('event', (event) => {
|
217
|
+
if (event.type === 'FILE_CHUNK') {
|
218
|
+
return;
|
219
|
+
}
|
220
|
+
sendEvent(res, event);
|
205
221
|
});
|
206
222
|
pageQueue.on('error', (err) => {
|
207
223
|
console.error('Failed to process page', err);
|
@@ -353,8 +369,11 @@ router.post('/:handle/ui', async (req, res) => {
|
|
353
369
|
queue.cancel();
|
354
370
|
});
|
355
371
|
queue.on('page', (screenData) => sendPageEvent(outerConversationId, screenData, res));
|
356
|
-
queue.on('event', (
|
357
|
-
|
372
|
+
queue.on('event', (event) => {
|
373
|
+
if (event.type === 'FILE_CHUNK') {
|
374
|
+
return;
|
375
|
+
}
|
376
|
+
sendEvent(res, event);
|
358
377
|
});
|
359
378
|
queue.on('error', (err) => {
|
360
379
|
console.error('Failed to process page', err);
|
@@ -410,10 +429,11 @@ router.post('/ui/edit', async (req, res) => {
|
|
410
429
|
return promise;
|
411
430
|
}
|
412
431
|
});
|
413
|
-
queue.on('event', (
|
414
|
-
if (
|
415
|
-
|
432
|
+
queue.on('event', (event) => {
|
433
|
+
if (event.type === 'FILE_CHUNK') {
|
434
|
+
return;
|
416
435
|
}
|
436
|
+
sendEvent(res, event);
|
417
437
|
});
|
418
438
|
queue.on('error', (err) => {
|
419
439
|
console.error('Failed to process page', err);
|
@@ -490,9 +510,13 @@ async function handleAll(req, res) {
|
|
490
510
|
onRequestAborted(req, res, () => {
|
491
511
|
metaStream.abort();
|
492
512
|
});
|
493
|
-
|
494
|
-
|
495
|
-
res.
|
513
|
+
// We check if the headers have been sent, because we might have already sent some data
|
514
|
+
// before this function is called
|
515
|
+
if (!res.headersSent) {
|
516
|
+
res.set('Content-Type', 'application/x-ndjson');
|
517
|
+
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
518
|
+
res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
|
519
|
+
}
|
496
520
|
let currentPhase = events_1.StormEventPhaseType.META;
|
497
521
|
// Helper to avoid sending the plan multiple times in a row
|
498
522
|
const sendUpdatedPlan = lodash_1.default.debounce(sendDefinitions, 50, { maxWait: 200 });
|
@@ -1,3 +1,4 @@
|
|
1
|
+
/// <reference types="node" />
|
1
2
|
import { ConversationItem, ImplementAPIClientsRequest, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
|
2
3
|
import { Page, StormEventPageUrl } from './events';
|
3
4
|
export declare const STORM_ID = "storm";
|
@@ -65,7 +66,7 @@ declare class StormClient {
|
|
65
66
|
createUIShells(prompt: UIShellsPrompt, conversationId?: string): Promise<StormStream>;
|
66
67
|
createUILandingPages(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
67
68
|
createUIPage(prompt: UIPagePrompt, conversationId?: string, history?: ConversationItem[]): Promise<StormStream>;
|
68
|
-
voteUIPage(topic: string, conversationId: string, vote: -1 | 0 | 1, mainConversationId?: string): Promise<
|
69
|
+
voteUIPage(topic: string, conversationId: string, vote: -1 | 0 | 1, mainConversationId?: string): Promise<Response>;
|
69
70
|
getVoteUIPage(topic: string, conversationId: string, mainConversationId?: string): Promise<{
|
70
71
|
vote: -1 | 0 | 1;
|
71
72
|
}>;
|
@@ -13,22 +13,8 @@ const utils_1 = require("../utils/utils");
|
|
13
13
|
const promises_1 = __importDefault(require("node:readline/promises"));
|
14
14
|
const node_stream_1 = require("node:stream");
|
15
15
|
const stream_1 = require("./stream");
|
16
|
-
const
|
17
|
-
|
18
|
-
// See https://github.com/nodejs/undici/blob/990df2c7e37cbe5bb44fe2f576dddeaeb5916590/docs/docs/api/RetryAgent.md
|
19
|
-
const retryAgent = new undici_1.RetryAgent(new undici_1.Agent(), {
|
20
|
-
methods: [
|
21
|
-
// Added methods ↓ (not idempotent), but we want to retry on POST:
|
22
|
-
'POST',
|
23
|
-
// defaults below
|
24
|
-
'GET',
|
25
|
-
'HEAD',
|
26
|
-
'OPTIONS',
|
27
|
-
'PUT',
|
28
|
-
'DELETE',
|
29
|
-
'TRACE',
|
30
|
-
],
|
31
|
-
});
|
16
|
+
const fetch_retry_1 = __importDefault(require("fetch-retry"));
|
17
|
+
const fetchWithRetries = (0, fetch_retry_1.default)(global.fetch, { retries: 5, retryDelay: 10 });
|
32
18
|
exports.STORM_ID = 'storm';
|
33
19
|
exports.ConversationIdHeader = 'Conversation-Id';
|
34
20
|
class StormClient {
|
@@ -54,7 +40,6 @@ class StormClient {
|
|
54
40
|
method: method,
|
55
41
|
body: JSON.stringify(body),
|
56
42
|
headers,
|
57
|
-
dispatcher: retryAgent,
|
58
43
|
};
|
59
44
|
}
|
60
45
|
async send(path, body, method = 'POST') {
|
@@ -65,7 +50,7 @@ class StormClient {
|
|
65
50
|
});
|
66
51
|
const abort = new AbortController();
|
67
52
|
options.signal = abort.signal;
|
68
|
-
const response = await (
|
53
|
+
const response = await fetchWithRetries(options.url, options);
|
69
54
|
if (response.status !== 200) {
|
70
55
|
throw new Error(`Got error response from ${options.url}: ${response.status}\nContent: ${await response.text()}`);
|
71
56
|
}
|
@@ -138,19 +123,19 @@ class StormClient {
|
|
138
123
|
prompt: JSON.stringify({ topic, vote, mainConversationId }),
|
139
124
|
conversationId,
|
140
125
|
});
|
141
|
-
return
|
126
|
+
return fetch(options.url, options);
|
142
127
|
}
|
143
128
|
async getVoteUIPage(topic, conversationId, mainConversationId) {
|
144
129
|
const options = await this.createOptions('/v2/ui/get-vote', 'POST', {
|
145
130
|
prompt: JSON.stringify({ topic, mainConversationId }),
|
146
131
|
conversationId,
|
147
132
|
});
|
148
|
-
const response = await
|
133
|
+
const response = await fetch(options.url, options);
|
149
134
|
return response.json();
|
150
135
|
}
|
151
136
|
async implementAPIClients(prompt) {
|
152
137
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients`;
|
153
|
-
const response = await
|
138
|
+
const response = await fetch(u, {
|
154
139
|
method: 'POST',
|
155
140
|
body: JSON.stringify({
|
156
141
|
fileName: prompt.fileName,
|
@@ -162,7 +147,7 @@ class StormClient {
|
|
162
147
|
}
|
163
148
|
async generatePrompt(pages) {
|
164
149
|
const u = `${this._baseUrl}/v2/ui/prompt`;
|
165
|
-
const response = await
|
150
|
+
const response = await fetch(u, {
|
166
151
|
method: 'POST',
|
167
152
|
body: JSON.stringify({
|
168
153
|
pages: pages,
|
@@ -235,7 +220,7 @@ class StormClient {
|
|
235
220
|
prompt: '',
|
236
221
|
conversationId: conversationId,
|
237
222
|
});
|
238
|
-
const response = await
|
223
|
+
const response = await fetch(options.url, options);
|
239
224
|
return response.text();
|
240
225
|
}
|
241
226
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.71.1",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -70,6 +70,7 @@
|
|
70
70
|
"download-git-repo": "^3.0.2",
|
71
71
|
"express": "4.17.1",
|
72
72
|
"express-promise-router": "^4.1.1",
|
73
|
+
"fetch-retry": "^6.0.0",
|
73
74
|
"fs-extra": "^11.1.0",
|
74
75
|
"glob": "^7.1.6",
|
75
76
|
"gunzip-maybe": "^1.4.2",
|
@@ -88,7 +89,6 @@
|
|
88
89
|
"stream-json": "^1.8.0",
|
89
90
|
"tar-stream": "^3.1.6",
|
90
91
|
"typescript": "^5.1.6",
|
91
|
-
"undici": "^6.19.8",
|
92
92
|
"uuid": "^9.0.1",
|
93
93
|
"yaml": "^1.6.0"
|
94
94
|
},
|
@@ -259,7 +259,7 @@ export class PageQueue extends EventEmitter {
|
|
259
259
|
// Add safeguard to avoid generating images for nonsense URLs
|
260
260
|
// Sometimes we get entries for Base URLs that will then cause issues on the filesystem
|
261
261
|
// Example: https://www.kapeta.com/images/
|
262
|
-
const mimeType = mimetypes.lookup(prompt.url)
|
262
|
+
const mimeType = mimetypes.lookup(prompt.url);
|
263
263
|
if (!mimeType || !mimeType.startsWith('image/')) {
|
264
264
|
console.warn('Skipping image reference of type %s for url %s', mimeType, prompt.url);
|
265
265
|
return;
|
package/src/storm/events.ts
CHANGED
@@ -329,6 +329,8 @@ export interface StormEventDefinitionChange {
|
|
329
329
|
}
|
330
330
|
|
331
331
|
export enum StormEventPhaseType {
|
332
|
+
IMPLEMENT_APIS = 'IMPLEMENT_APIS', // Implement APIs in the html pages
|
333
|
+
COMPOSE_SYSTEM_PROMPT = 'COMPOSE_SYSTEM_PROMPT', // Compose system prompt for bottom-up approach
|
332
334
|
META = 'META',
|
333
335
|
DEFINITIONS = 'DEFINITIONS',
|
334
336
|
IMPLEMENTATION = 'IMPLEMENTATION',
|
package/src/storm/routes.ts
CHANGED
@@ -105,6 +105,12 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
|
|
105
105
|
const srcDir = getSystemBaseDir(systemId);
|
106
106
|
const destDir = getSystemBaseImplDir(systemId);
|
107
107
|
|
108
|
+
res.set('Content-Type', 'application/x-ndjson');
|
109
|
+
res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
110
|
+
res.set(ConversationIdHeader, systemId);
|
111
|
+
|
112
|
+
sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
|
113
|
+
|
108
114
|
await copyDirectory(srcDir, destDir, async (fileName, content) => {
|
109
115
|
const result = await stormClient.implementAPIClients({
|
110
116
|
content: content,
|
@@ -113,9 +119,15 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
|
|
113
119
|
return result;
|
114
120
|
});
|
115
121
|
|
122
|
+
sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
|
123
|
+
|
124
|
+
sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
|
125
|
+
|
116
126
|
const pages = readPages(destDir);
|
117
127
|
const prompt = await stormClient.generatePrompt(pages);
|
118
128
|
|
129
|
+
sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
|
130
|
+
|
119
131
|
req.query.systemId = systemId;
|
120
132
|
const promptRequest: BasePromptRequest = {
|
121
133
|
prompt: prompt,
|
@@ -169,6 +181,13 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
|
169
181
|
sendError(err as any, res);
|
170
182
|
});
|
171
183
|
|
184
|
+
queue.on('event', (event: StormEvent) => {
|
185
|
+
if (event.type === 'FILE_CHUNK') {
|
186
|
+
return;
|
187
|
+
}
|
188
|
+
sendEvent(res, event);
|
189
|
+
});
|
190
|
+
|
172
191
|
await queue.addPrompt(aiRequest, conversationId, true);
|
173
192
|
|
174
193
|
await queue.wait();
|
@@ -262,8 +281,11 @@ router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Respons
|
|
262
281
|
sendPageEvent(landingPagesStream.getConversationId(), screenData, res)
|
263
282
|
);
|
264
283
|
|
265
|
-
pageQueue.on('event', (
|
266
|
-
|
284
|
+
pageQueue.on('event', (event: StormEvent) => {
|
285
|
+
if (event.type === 'FILE_CHUNK') {
|
286
|
+
return;
|
287
|
+
}
|
288
|
+
sendEvent(res, event);
|
267
289
|
});
|
268
290
|
|
269
291
|
pageQueue.on('error', (err) => {
|
@@ -448,8 +470,11 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
448
470
|
|
449
471
|
queue.on('page', (screenData: StormEventPage) => sendPageEvent(outerConversationId, screenData, res));
|
450
472
|
|
451
|
-
queue.on('event', (
|
452
|
-
|
473
|
+
queue.on('event', (event: StormEvent) => {
|
474
|
+
if (event.type === 'FILE_CHUNK') {
|
475
|
+
return;
|
476
|
+
}
|
477
|
+
sendEvent(res, event);
|
453
478
|
});
|
454
479
|
|
455
480
|
queue.on('error', (err) => {
|
@@ -515,10 +540,11 @@ router.post('/ui/edit', async (req: KapetaBodyRequest, res: Response) => {
|
|
515
540
|
}
|
516
541
|
});
|
517
542
|
|
518
|
-
queue.on('event', (
|
519
|
-
if (
|
520
|
-
|
543
|
+
queue.on('event', (event) => {
|
544
|
+
if (event.type === 'FILE_CHUNK') {
|
545
|
+
return;
|
521
546
|
}
|
547
|
+
sendEvent(res, event);
|
522
548
|
});
|
523
549
|
|
524
550
|
queue.on('error', (err) => {
|
@@ -612,9 +638,13 @@ async function handleAll(req: KapetaBodyRequest, res: Response) {
|
|
612
638
|
metaStream.abort();
|
613
639
|
});
|
614
640
|
|
615
|
-
|
616
|
-
|
617
|
-
res.
|
641
|
+
// We check if the headers have been sent, because we might have already sent some data
|
642
|
+
// before this function is called
|
643
|
+
if (!res.headersSent) {
|
644
|
+
res.set('Content-Type', 'application/x-ndjson');
|
645
|
+
res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
646
|
+
res.set(ConversationIdHeader, metaStream.getConversationId());
|
647
|
+
}
|
618
648
|
|
619
649
|
let currentPhase = StormEventPhaseType.META;
|
620
650
|
|
package/src/storm/stormClient.ts
CHANGED
@@ -16,23 +16,8 @@ import {
|
|
16
16
|
StormUIListPrompt,
|
17
17
|
} from './stream';
|
18
18
|
import { Page, StormEventPageUrl } from './events';
|
19
|
-
import
|
20
|
-
|
21
|
-
// Will only retry on error codes and GET requests by default
|
22
|
-
// See https://github.com/nodejs/undici/blob/990df2c7e37cbe5bb44fe2f576dddeaeb5916590/docs/docs/api/RetryAgent.md
|
23
|
-
const retryAgent = new RetryAgent(new Agent(), {
|
24
|
-
methods: [
|
25
|
-
// Added methods ↓ (not idempotent), but we want to retry on POST:
|
26
|
-
'POST',
|
27
|
-
// defaults below
|
28
|
-
'GET',
|
29
|
-
'HEAD',
|
30
|
-
'OPTIONS',
|
31
|
-
'PUT',
|
32
|
-
'DELETE',
|
33
|
-
'TRACE',
|
34
|
-
],
|
35
|
-
});
|
19
|
+
import createFetch from 'fetch-retry';
|
20
|
+
const fetchWithRetries = createFetch(global.fetch, { retries: 5, retryDelay: 10 });
|
36
21
|
|
37
22
|
export const STORM_ID = 'storm';
|
38
23
|
|
@@ -129,7 +114,6 @@ class StormClient {
|
|
129
114
|
method: method,
|
130
115
|
body: JSON.stringify(body),
|
131
116
|
headers,
|
132
|
-
dispatcher: retryAgent,
|
133
117
|
};
|
134
118
|
}
|
135
119
|
|
@@ -148,7 +132,7 @@ class StormClient {
|
|
148
132
|
const abort = new AbortController();
|
149
133
|
options.signal = abort.signal;
|
150
134
|
|
151
|
-
const response = await
|
135
|
+
const response = await fetchWithRetries(options.url, options);
|
152
136
|
|
153
137
|
if (response.status !== 200) {
|
154
138
|
throw new Error(
|