@kapeta/local-cluster-service 0.70.4 → 0.70.6
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 +16 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/src/storm/PageGenerator.d.ts +2 -0
- package/dist/cjs/src/storm/PageGenerator.js +21 -6
- package/dist/cjs/src/storm/page-utils.js +4 -3
- package/dist/cjs/src/storm/routes.js +8 -0
- package/dist/cjs/src/storm/stormClient.d.ts +1 -2
- package/dist/cjs/src/storm/stormClient.js +22 -14
- package/dist/esm/index.js +1 -1
- package/dist/esm/src/storm/PageGenerator.d.ts +2 -0
- package/dist/esm/src/storm/PageGenerator.js +21 -6
- package/dist/esm/src/storm/page-utils.js +4 -3
- package/dist/esm/src/storm/routes.js +8 -0
- package/dist/esm/src/storm/stormClient.d.ts +1 -2
- package/dist/esm/src/storm/stormClient.js +22 -14
- package/index.ts +1 -1
- package/package.json +2 -1
- package/src/storm/PageGenerator.ts +24 -10
- package/src/storm/page-utils.ts +4 -3
- package/src/storm/routes.ts +10 -0
- package/src/storm/stormClient.ts +21 -12
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
## [0.70.6](https://github.com/kapetacom/local-cluster-service/compare/v0.70.5...v0.70.6) (2024-09-11)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* disable sentry in dev mode ([9a7793f](https://github.com/kapetacom/local-cluster-service/commit/9a7793feaf81239d600d2b6650f126780f0ffdd5))
|
7
|
+
* emit errors to res instead of into void ([d1302c1](https://github.com/kapetacom/local-cluster-service/commit/d1302c1b523a72de6596b619a44a2cde073f032f))
|
8
|
+
* promise error handling and fetch retries ([d746e14](https://github.com/kapetacom/local-cluster-service/commit/d746e14c1edaaddd1a3aa5cb7bbf644dfde334b2))
|
9
|
+
|
10
|
+
## [0.70.5](https://github.com/kapetacom/local-cluster-service/compare/v0.70.4...v0.70.5) (2024-09-11)
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* strip hash of URLs for refs ([#242](https://github.com/kapetacom/local-cluster-service/issues/242)) ([681a3fd](https://github.com/kapetacom/local-cluster-service/commit/681a3fdcfd837eb4d0d3411ee719e2dc68334522))
|
16
|
+
|
1
17
|
## [0.70.4](https://github.com/kapetacom/local-cluster-service/compare/v0.70.3...v0.70.4) (2024-09-10)
|
2
18
|
|
3
19
|
|
package/dist/cjs/index.js
CHANGED
@@ -64,7 +64,7 @@ const assetManager_1 = require("./src/assetManager");
|
|
64
64
|
const instanceManager_1 = require("./src/instanceManager");
|
65
65
|
Sentry.init({
|
66
66
|
dsn: 'https://0b7cc946d82c591473d6f95fff5e210b@o4505820837249024.ingest.sentry.io/4506212692000768',
|
67
|
-
enabled: process.env.NODE_ENV !== 'development',
|
67
|
+
enabled: !!process.env.NODE_ENV && process.env.NODE_ENV !== 'development',
|
68
68
|
// Performance Monitoring on every ~20th request
|
69
69
|
tracesSampleRate: 0.05,
|
70
70
|
// Set sampling rate for profiling - this is relative to tracesSampleRate
|
@@ -29,9 +29,11 @@ export declare class PageQueue extends EventEmitter {
|
|
29
29
|
private uiShells;
|
30
30
|
private theme;
|
31
31
|
constructor(systemId: string, systemPrompt: string, concurrency?: number);
|
32
|
+
on(event: 'error', listener: (error: unknown) => void): this;
|
32
33
|
on(event: 'event', listener: (data: StormEvent) => void | Promise<void>): this;
|
33
34
|
on(event: 'page', listener: (data: StormEventPage) => void | Promise<void>): this;
|
34
35
|
on(event: 'image', listener: (data: StormImage, source: ImagePrompt) => void | Promise<void>): this;
|
36
|
+
emit(type: 'error', error: unknown): boolean;
|
35
37
|
emit(type: 'event', event: StormEvent): boolean;
|
36
38
|
emit(type: 'page', event: StormEventPage): boolean;
|
37
39
|
emit(type: 'image', event: StormImage, source: ImagePrompt): boolean;
|
@@ -32,7 +32,12 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
32
32
|
}
|
33
33
|
on(event, listener) {
|
34
34
|
return super.on(event, (...args) => {
|
35
|
-
|
35
|
+
this.eventQueue
|
36
|
+
.add(async () => listener(...args))
|
37
|
+
// If the event queue fails, we want to emit an error
|
38
|
+
.catch((err) => {
|
39
|
+
this.emit('error', err);
|
40
|
+
});
|
36
41
|
});
|
37
42
|
}
|
38
43
|
emit(eventName, ...args) {
|
@@ -121,6 +126,9 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
121
126
|
await this.addImagePrompt({
|
122
127
|
...reference,
|
123
128
|
content: event.payload.content,
|
129
|
+
}).catch((err) => {
|
130
|
+
console.error('Failed to generate image for reference', reference.name, err);
|
131
|
+
this.emit('error', err);
|
124
132
|
});
|
125
133
|
break;
|
126
134
|
case 'css':
|
@@ -154,7 +162,7 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
154
162
|
await Promise.allSettled(resourcePromises);
|
155
163
|
this.emit('page', event);
|
156
164
|
// Emit any new pages after the current page to increase responsiveness
|
157
|
-
|
165
|
+
initialPrompts.forEach((prompt) => {
|
158
166
|
if (!this.hasPrompt(prompt.path)) {
|
159
167
|
this.emit('page', {
|
160
168
|
type: 'PAGE',
|
@@ -173,7 +181,11 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
173
181
|
},
|
174
182
|
});
|
175
183
|
}
|
176
|
-
|
184
|
+
// Trigger but don't wait for the "bonus" pages
|
185
|
+
this.addPrompt(prompt).catch((err) => {
|
186
|
+
console.error('Failed to generate page reference', prompt.name, err);
|
187
|
+
this.emit('error', err);
|
188
|
+
});
|
177
189
|
});
|
178
190
|
}
|
179
191
|
catch (e) {
|
@@ -208,18 +220,21 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
208
220
|
await result.waitForDone();
|
209
221
|
}
|
210
222
|
async generate(prompt, conversationId) {
|
211
|
-
const promises = [];
|
212
223
|
const screenStream = await stormClient_1.stormClient.createUIPage(prompt, conversationId);
|
224
|
+
let pageEvent = null;
|
213
225
|
screenStream.on('data', (event) => {
|
214
226
|
if (event.type === 'PAGE') {
|
215
227
|
event.payload.conversationId = conversationId;
|
216
|
-
|
228
|
+
pageEvent = event;
|
217
229
|
return;
|
218
230
|
}
|
219
231
|
this.emit('event', event);
|
220
232
|
});
|
221
233
|
await screenStream.waitForDone();
|
222
|
-
|
234
|
+
if (!pageEvent) {
|
235
|
+
throw new Error('No page was generated');
|
236
|
+
}
|
237
|
+
await this.processPageEventWithReferences(pageEvent);
|
223
238
|
}
|
224
239
|
async resolveReferences(content) {
|
225
240
|
const referenceStream = await stormClient_1.stormClient.classifyUIReferences(content);
|
@@ -10,9 +10,10 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
10
|
exports.SystemIdHeader = 'System-Id';
|
11
11
|
function normalizePath(path) {
|
12
12
|
return path
|
13
|
-
.replace(
|
14
|
-
.replace(
|
15
|
-
.replace(
|
13
|
+
.replace(/#.*$/g, '') // Remove hash
|
14
|
+
.replace(/\?.*$/gi, '') // Remove query params
|
15
|
+
.replace(/:[a-z][a-z_]*\b/gi, '*') // Replace all params with *
|
16
|
+
.replace(/\{[a-z-.]+}/gi, '*'); // Replace all variables with *
|
16
17
|
}
|
17
18
|
exports.normalizePath = normalizePath;
|
18
19
|
async function writePageToDisk(systemId, event) {
|
@@ -124,6 +124,10 @@ router.post('/ui/screen', async (req, res) => {
|
|
124
124
|
throw e;
|
125
125
|
}
|
126
126
|
});
|
127
|
+
queue.on('error', (err) => {
|
128
|
+
console.error('Failed to process page', err);
|
129
|
+
sendError(err, res);
|
130
|
+
});
|
127
131
|
await queue.addPrompt(aiRequest, conversationId, true);
|
128
132
|
await queue.wait();
|
129
133
|
await Promise.allSettled(promises);
|
@@ -214,6 +218,10 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
214
218
|
pageQueue.on('event', (screenData) => {
|
215
219
|
sendEvent(res, screenData);
|
216
220
|
});
|
221
|
+
pageQueue.on('error', (err) => {
|
222
|
+
console.error('Failed to process page', err);
|
223
|
+
sendError(err, res);
|
224
|
+
});
|
217
225
|
await waitForStormStream(landingPagesStream);
|
218
226
|
await pageQueue.wait();
|
219
227
|
await Promise.allSettled(pageEventPromises);
|
@@ -1,4 +1,3 @@
|
|
1
|
-
/// <reference types="node" />
|
2
1
|
import { ConversationItem, ImplementAPIClientsRequest, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
|
3
2
|
import { Page, StormEventPageUrl } from './events';
|
4
3
|
export declare const STORM_ID = "storm";
|
@@ -66,7 +65,7 @@ declare class StormClient {
|
|
66
65
|
createUIShells(prompt: UIShellsPrompt, conversationId?: string): Promise<StormStream>;
|
67
66
|
createUILandingPages(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
68
67
|
createUIPage(prompt: UIPagePrompt, conversationId?: string, history?: ConversationItem[]): Promise<StormStream>;
|
69
|
-
voteUIPage(topic: string, conversationId: string, vote: -1 | 0 | 1, mainConversationId?: string): Promise<Response>;
|
68
|
+
voteUIPage(topic: string, conversationId: string, vote: -1 | 0 | 1, mainConversationId?: string): Promise<import("undici").Response>;
|
70
69
|
getVoteUIPage(topic: string, conversationId: string, mainConversationId?: string): Promise<{
|
71
70
|
vote: -1 | 0 | 1;
|
72
71
|
}>;
|
@@ -13,6 +13,22 @@ 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 undici_1 = require("undici");
|
17
|
+
// Will only retry on error codes and GET requests by default
|
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
32
|
exports.STORM_ID = 'storm';
|
17
33
|
exports.ConversationIdHeader = 'Conversation-Id';
|
18
34
|
class StormClient {
|
@@ -38,6 +54,7 @@ class StormClient {
|
|
38
54
|
method: method,
|
39
55
|
body: JSON.stringify(body),
|
40
56
|
headers,
|
57
|
+
dispatcher: retryAgent,
|
41
58
|
};
|
42
59
|
}
|
43
60
|
async send(path, body, method = 'POST') {
|
@@ -48,7 +65,7 @@ class StormClient {
|
|
48
65
|
});
|
49
66
|
const abort = new AbortController();
|
50
67
|
options.signal = abort.signal;
|
51
|
-
const response = await fetch(options.url, options);
|
68
|
+
const response = await (0, undici_1.fetch)(options.url, options);
|
52
69
|
if (response.status !== 200) {
|
53
70
|
throw new Error(`Got error response from ${options.url}: ${response.status}\nContent: ${await response.text()}`);
|
54
71
|
}
|
@@ -121,25 +138,19 @@ class StormClient {
|
|
121
138
|
prompt: JSON.stringify({ topic, vote, mainConversationId }),
|
122
139
|
conversationId,
|
123
140
|
});
|
124
|
-
return fetch(options.url,
|
125
|
-
method: options.method,
|
126
|
-
headers: options.headers,
|
127
|
-
});
|
141
|
+
return (0, undici_1.fetch)(options.url, options);
|
128
142
|
}
|
129
143
|
async getVoteUIPage(topic, conversationId, mainConversationId) {
|
130
144
|
const options = await this.createOptions('/v2/ui/get-vote', 'POST', {
|
131
145
|
prompt: JSON.stringify({ topic, mainConversationId }),
|
132
146
|
conversationId,
|
133
147
|
});
|
134
|
-
const response = await fetch(options.url,
|
135
|
-
method: options.method,
|
136
|
-
headers: options.headers,
|
137
|
-
});
|
148
|
+
const response = await (0, undici_1.fetch)(options.url, options);
|
138
149
|
return response.json();
|
139
150
|
}
|
140
151
|
async implementAPIClients(prompt) {
|
141
152
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients`;
|
142
|
-
const response = await fetch(u, {
|
153
|
+
const response = await (0, undici_1.fetch)(u, {
|
143
154
|
method: 'POST',
|
144
155
|
body: JSON.stringify({
|
145
156
|
fileName: prompt.fileName,
|
@@ -214,10 +225,7 @@ class StormClient {
|
|
214
225
|
prompt: '',
|
215
226
|
conversationId: conversationId,
|
216
227
|
});
|
217
|
-
const response = await fetch(options.url,
|
218
|
-
method: options.method,
|
219
|
-
headers: options.headers,
|
220
|
-
});
|
228
|
+
const response = await (0, undici_1.fetch)(options.url, options);
|
221
229
|
return response.text();
|
222
230
|
}
|
223
231
|
}
|
package/dist/esm/index.js
CHANGED
@@ -64,7 +64,7 @@ const assetManager_1 = require("./src/assetManager");
|
|
64
64
|
const instanceManager_1 = require("./src/instanceManager");
|
65
65
|
Sentry.init({
|
66
66
|
dsn: 'https://0b7cc946d82c591473d6f95fff5e210b@o4505820837249024.ingest.sentry.io/4506212692000768',
|
67
|
-
enabled: process.env.NODE_ENV !== 'development',
|
67
|
+
enabled: !!process.env.NODE_ENV && process.env.NODE_ENV !== 'development',
|
68
68
|
// Performance Monitoring on every ~20th request
|
69
69
|
tracesSampleRate: 0.05,
|
70
70
|
// Set sampling rate for profiling - this is relative to tracesSampleRate
|
@@ -29,9 +29,11 @@ export declare class PageQueue extends EventEmitter {
|
|
29
29
|
private uiShells;
|
30
30
|
private theme;
|
31
31
|
constructor(systemId: string, systemPrompt: string, concurrency?: number);
|
32
|
+
on(event: 'error', listener: (error: unknown) => void): this;
|
32
33
|
on(event: 'event', listener: (data: StormEvent) => void | Promise<void>): this;
|
33
34
|
on(event: 'page', listener: (data: StormEventPage) => void | Promise<void>): this;
|
34
35
|
on(event: 'image', listener: (data: StormImage, source: ImagePrompt) => void | Promise<void>): this;
|
36
|
+
emit(type: 'error', error: unknown): boolean;
|
35
37
|
emit(type: 'event', event: StormEvent): boolean;
|
36
38
|
emit(type: 'page', event: StormEventPage): boolean;
|
37
39
|
emit(type: 'image', event: StormImage, source: ImagePrompt): boolean;
|
@@ -32,7 +32,12 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
32
32
|
}
|
33
33
|
on(event, listener) {
|
34
34
|
return super.on(event, (...args) => {
|
35
|
-
|
35
|
+
this.eventQueue
|
36
|
+
.add(async () => listener(...args))
|
37
|
+
// If the event queue fails, we want to emit an error
|
38
|
+
.catch((err) => {
|
39
|
+
this.emit('error', err);
|
40
|
+
});
|
36
41
|
});
|
37
42
|
}
|
38
43
|
emit(eventName, ...args) {
|
@@ -121,6 +126,9 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
121
126
|
await this.addImagePrompt({
|
122
127
|
...reference,
|
123
128
|
content: event.payload.content,
|
129
|
+
}).catch((err) => {
|
130
|
+
console.error('Failed to generate image for reference', reference.name, err);
|
131
|
+
this.emit('error', err);
|
124
132
|
});
|
125
133
|
break;
|
126
134
|
case 'css':
|
@@ -154,7 +162,7 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
154
162
|
await Promise.allSettled(resourcePromises);
|
155
163
|
this.emit('page', event);
|
156
164
|
// Emit any new pages after the current page to increase responsiveness
|
157
|
-
|
165
|
+
initialPrompts.forEach((prompt) => {
|
158
166
|
if (!this.hasPrompt(prompt.path)) {
|
159
167
|
this.emit('page', {
|
160
168
|
type: 'PAGE',
|
@@ -173,7 +181,11 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
173
181
|
},
|
174
182
|
});
|
175
183
|
}
|
176
|
-
|
184
|
+
// Trigger but don't wait for the "bonus" pages
|
185
|
+
this.addPrompt(prompt).catch((err) => {
|
186
|
+
console.error('Failed to generate page reference', prompt.name, err);
|
187
|
+
this.emit('error', err);
|
188
|
+
});
|
177
189
|
});
|
178
190
|
}
|
179
191
|
catch (e) {
|
@@ -208,18 +220,21 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
208
220
|
await result.waitForDone();
|
209
221
|
}
|
210
222
|
async generate(prompt, conversationId) {
|
211
|
-
const promises = [];
|
212
223
|
const screenStream = await stormClient_1.stormClient.createUIPage(prompt, conversationId);
|
224
|
+
let pageEvent = null;
|
213
225
|
screenStream.on('data', (event) => {
|
214
226
|
if (event.type === 'PAGE') {
|
215
227
|
event.payload.conversationId = conversationId;
|
216
|
-
|
228
|
+
pageEvent = event;
|
217
229
|
return;
|
218
230
|
}
|
219
231
|
this.emit('event', event);
|
220
232
|
});
|
221
233
|
await screenStream.waitForDone();
|
222
|
-
|
234
|
+
if (!pageEvent) {
|
235
|
+
throw new Error('No page was generated');
|
236
|
+
}
|
237
|
+
await this.processPageEventWithReferences(pageEvent);
|
223
238
|
}
|
224
239
|
async resolveReferences(content) {
|
225
240
|
const referenceStream = await stormClient_1.stormClient.classifyUIReferences(content);
|
@@ -10,9 +10,10 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
10
|
exports.SystemIdHeader = 'System-Id';
|
11
11
|
function normalizePath(path) {
|
12
12
|
return path
|
13
|
-
.replace(
|
14
|
-
.replace(
|
15
|
-
.replace(
|
13
|
+
.replace(/#.*$/g, '') // Remove hash
|
14
|
+
.replace(/\?.*$/gi, '') // Remove query params
|
15
|
+
.replace(/:[a-z][a-z_]*\b/gi, '*') // Replace all params with *
|
16
|
+
.replace(/\{[a-z-.]+}/gi, '*'); // Replace all variables with *
|
16
17
|
}
|
17
18
|
exports.normalizePath = normalizePath;
|
18
19
|
async function writePageToDisk(systemId, event) {
|
@@ -124,6 +124,10 @@ router.post('/ui/screen', async (req, res) => {
|
|
124
124
|
throw e;
|
125
125
|
}
|
126
126
|
});
|
127
|
+
queue.on('error', (err) => {
|
128
|
+
console.error('Failed to process page', err);
|
129
|
+
sendError(err, res);
|
130
|
+
});
|
127
131
|
await queue.addPrompt(aiRequest, conversationId, true);
|
128
132
|
await queue.wait();
|
129
133
|
await Promise.allSettled(promises);
|
@@ -214,6 +218,10 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
214
218
|
pageQueue.on('event', (screenData) => {
|
215
219
|
sendEvent(res, screenData);
|
216
220
|
});
|
221
|
+
pageQueue.on('error', (err) => {
|
222
|
+
console.error('Failed to process page', err);
|
223
|
+
sendError(err, res);
|
224
|
+
});
|
217
225
|
await waitForStormStream(landingPagesStream);
|
218
226
|
await pageQueue.wait();
|
219
227
|
await Promise.allSettled(pageEventPromises);
|
@@ -1,4 +1,3 @@
|
|
1
|
-
/// <reference types="node" />
|
2
1
|
import { ConversationItem, ImplementAPIClientsRequest, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
|
3
2
|
import { Page, StormEventPageUrl } from './events';
|
4
3
|
export declare const STORM_ID = "storm";
|
@@ -66,7 +65,7 @@ declare class StormClient {
|
|
66
65
|
createUIShells(prompt: UIShellsPrompt, conversationId?: string): Promise<StormStream>;
|
67
66
|
createUILandingPages(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
68
67
|
createUIPage(prompt: UIPagePrompt, conversationId?: string, history?: ConversationItem[]): Promise<StormStream>;
|
69
|
-
voteUIPage(topic: string, conversationId: string, vote: -1 | 0 | 1, mainConversationId?: string): Promise<Response>;
|
68
|
+
voteUIPage(topic: string, conversationId: string, vote: -1 | 0 | 1, mainConversationId?: string): Promise<import("undici").Response>;
|
70
69
|
getVoteUIPage(topic: string, conversationId: string, mainConversationId?: string): Promise<{
|
71
70
|
vote: -1 | 0 | 1;
|
72
71
|
}>;
|
@@ -13,6 +13,22 @@ 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 undici_1 = require("undici");
|
17
|
+
// Will only retry on error codes and GET requests by default
|
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
32
|
exports.STORM_ID = 'storm';
|
17
33
|
exports.ConversationIdHeader = 'Conversation-Id';
|
18
34
|
class StormClient {
|
@@ -38,6 +54,7 @@ class StormClient {
|
|
38
54
|
method: method,
|
39
55
|
body: JSON.stringify(body),
|
40
56
|
headers,
|
57
|
+
dispatcher: retryAgent,
|
41
58
|
};
|
42
59
|
}
|
43
60
|
async send(path, body, method = 'POST') {
|
@@ -48,7 +65,7 @@ class StormClient {
|
|
48
65
|
});
|
49
66
|
const abort = new AbortController();
|
50
67
|
options.signal = abort.signal;
|
51
|
-
const response = await fetch(options.url, options);
|
68
|
+
const response = await (0, undici_1.fetch)(options.url, options);
|
52
69
|
if (response.status !== 200) {
|
53
70
|
throw new Error(`Got error response from ${options.url}: ${response.status}\nContent: ${await response.text()}`);
|
54
71
|
}
|
@@ -121,25 +138,19 @@ class StormClient {
|
|
121
138
|
prompt: JSON.stringify({ topic, vote, mainConversationId }),
|
122
139
|
conversationId,
|
123
140
|
});
|
124
|
-
return fetch(options.url,
|
125
|
-
method: options.method,
|
126
|
-
headers: options.headers,
|
127
|
-
});
|
141
|
+
return (0, undici_1.fetch)(options.url, options);
|
128
142
|
}
|
129
143
|
async getVoteUIPage(topic, conversationId, mainConversationId) {
|
130
144
|
const options = await this.createOptions('/v2/ui/get-vote', 'POST', {
|
131
145
|
prompt: JSON.stringify({ topic, mainConversationId }),
|
132
146
|
conversationId,
|
133
147
|
});
|
134
|
-
const response = await fetch(options.url,
|
135
|
-
method: options.method,
|
136
|
-
headers: options.headers,
|
137
|
-
});
|
148
|
+
const response = await (0, undici_1.fetch)(options.url, options);
|
138
149
|
return response.json();
|
139
150
|
}
|
140
151
|
async implementAPIClients(prompt) {
|
141
152
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients`;
|
142
|
-
const response = await fetch(u, {
|
153
|
+
const response = await (0, undici_1.fetch)(u, {
|
143
154
|
method: 'POST',
|
144
155
|
body: JSON.stringify({
|
145
156
|
fileName: prompt.fileName,
|
@@ -214,10 +225,7 @@ class StormClient {
|
|
214
225
|
prompt: '',
|
215
226
|
conversationId: conversationId,
|
216
227
|
});
|
217
|
-
const response = await fetch(options.url,
|
218
|
-
method: options.method,
|
219
|
-
headers: options.headers,
|
220
|
-
});
|
228
|
+
const response = await (0, undici_1.fetch)(options.url, options);
|
221
229
|
return response.text();
|
222
230
|
}
|
223
231
|
}
|
package/index.ts
CHANGED
@@ -39,7 +39,7 @@ import { instanceManager } from './src/instanceManager';
|
|
39
39
|
|
40
40
|
Sentry.init({
|
41
41
|
dsn: 'https://0b7cc946d82c591473d6f95fff5e210b@o4505820837249024.ingest.sentry.io/4506212692000768',
|
42
|
-
enabled: process.env.NODE_ENV !== 'development',
|
42
|
+
enabled: !!process.env.NODE_ENV && process.env.NODE_ENV !== 'development',
|
43
43
|
// Performance Monitoring on every ~20th request
|
44
44
|
tracesSampleRate: 0.05,
|
45
45
|
// Set sampling rate for profiling - this is relative to tracesSampleRate
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.70.
|
3
|
+
"version": "0.70.6",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -87,6 +87,7 @@
|
|
87
87
|
"stream-json": "^1.8.0",
|
88
88
|
"tar-stream": "^3.1.6",
|
89
89
|
"typescript": "^5.1.6",
|
90
|
+
"undici": "^6.19.8",
|
90
91
|
"uuid": "^9.0.1",
|
91
92
|
"yaml": "^1.6.0"
|
92
93
|
},
|
@@ -42,16 +42,23 @@ export class PageQueue extends EventEmitter {
|
|
42
42
|
this.eventQueue = new PQueue({ concurrency: Number.MAX_VALUE });
|
43
43
|
}
|
44
44
|
|
45
|
+
on(event: 'error', listener: (error: unknown) => void): this;
|
45
46
|
on(event: 'event', listener: (data: StormEvent) => void | Promise<void>): this;
|
46
47
|
on(event: 'page', listener: (data: StormEventPage) => void | Promise<void>): this;
|
47
48
|
on(event: 'image', listener: (data: StormImage, source: ImagePrompt) => void | Promise<void>): this;
|
48
49
|
|
49
50
|
on(event: string, listener: (...args: any[]) => void | Promise<void>): this {
|
50
51
|
return super.on(event, (...args) => {
|
51
|
-
|
52
|
+
this.eventQueue
|
53
|
+
.add(async () => listener(...args))
|
54
|
+
// If the event queue fails, we want to emit an error
|
55
|
+
.catch((err) => {
|
56
|
+
this.emit('error', err);
|
57
|
+
});
|
52
58
|
});
|
53
59
|
}
|
54
60
|
|
61
|
+
emit(type: 'error', error: unknown): boolean;
|
55
62
|
emit(type: 'event', event: StormEvent): boolean;
|
56
63
|
emit(type: 'page', event: StormEventPage): boolean;
|
57
64
|
emit(type: 'image', event: StormImage, source: ImagePrompt): boolean;
|
@@ -160,6 +167,9 @@ export class PageQueue extends EventEmitter {
|
|
160
167
|
await this.addImagePrompt({
|
161
168
|
...reference,
|
162
169
|
content: event.payload.content,
|
170
|
+
}).catch((err) => {
|
171
|
+
console.error('Failed to generate image for reference', reference.name, err);
|
172
|
+
this.emit('error', err);
|
163
173
|
});
|
164
174
|
break;
|
165
175
|
case 'css':
|
@@ -197,7 +207,7 @@ export class PageQueue extends EventEmitter {
|
|
197
207
|
this.emit('page', event);
|
198
208
|
|
199
209
|
// Emit any new pages after the current page to increase responsiveness
|
200
|
-
|
210
|
+
initialPrompts.forEach((prompt) => {
|
201
211
|
if (!this.hasPrompt(prompt.path)) {
|
202
212
|
this.emit('page', {
|
203
213
|
type: 'PAGE',
|
@@ -216,7 +226,11 @@ export class PageQueue extends EventEmitter {
|
|
216
226
|
},
|
217
227
|
});
|
218
228
|
}
|
219
|
-
|
229
|
+
// Trigger but don't wait for the "bonus" pages
|
230
|
+
this.addPrompt(prompt).catch((err) => {
|
231
|
+
console.error('Failed to generate page reference', prompt.name, err);
|
232
|
+
this.emit('error', err);
|
233
|
+
});
|
220
234
|
});
|
221
235
|
} catch (e) {
|
222
236
|
console.error('Failed to process event', e);
|
@@ -258,25 +272,25 @@ export class PageQueue extends EventEmitter {
|
|
258
272
|
}
|
259
273
|
|
260
274
|
public async generate(prompt: UIPagePrompt, conversationId: string) {
|
261
|
-
const promises: Promise<void>[] = [];
|
262
275
|
const screenStream = await stormClient.createUIPage(prompt, conversationId);
|
263
|
-
|
276
|
+
let pageEvent: StormEventPage | null = null;
|
264
277
|
screenStream.on('data', (event: StormEvent) => {
|
265
278
|
if (event.type === 'PAGE') {
|
266
279
|
event.payload.conversationId = conversationId;
|
267
|
-
|
268
|
-
promises.push(this.processPageEventWithReferences(event));
|
280
|
+
pageEvent = event;
|
269
281
|
return;
|
270
282
|
}
|
271
|
-
|
272
283
|
this.emit('event', event);
|
273
284
|
});
|
274
285
|
|
275
286
|
await screenStream.waitForDone();
|
276
|
-
|
287
|
+
if (!pageEvent) {
|
288
|
+
throw new Error('No page was generated');
|
289
|
+
}
|
290
|
+
await this.processPageEventWithReferences(pageEvent);
|
277
291
|
}
|
278
292
|
|
279
|
-
private async resolveReferences(content: string) {
|
293
|
+
private async resolveReferences(content: string): Promise<ReferenceClassification[]> {
|
280
294
|
const referenceStream = await stormClient.classifyUIReferences(content);
|
281
295
|
|
282
296
|
const references: ReferenceClassification[] = [];
|
package/src/storm/page-utils.ts
CHANGED
@@ -17,9 +17,10 @@ export const SystemIdHeader = 'System-Id';
|
|
17
17
|
|
18
18
|
export function normalizePath(path: string) {
|
19
19
|
return path
|
20
|
-
.replace(
|
21
|
-
.replace(
|
22
|
-
.replace(
|
20
|
+
.replace(/#.*$/g, '') // Remove hash
|
21
|
+
.replace(/\?.*$/gi, '') // Remove query params
|
22
|
+
.replace(/:[a-z][a-z_]*\b/gi, '*') // Replace all params with *
|
23
|
+
.replace(/\{[a-z-.]+}/gi, '*'); // Replace all variables with *
|
23
24
|
}
|
24
25
|
|
25
26
|
export async function writePageToDisk(systemId: string, event: StormEventPage) {
|
package/src/storm/routes.ts
CHANGED
@@ -168,6 +168,11 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
|
168
168
|
}
|
169
169
|
});
|
170
170
|
|
171
|
+
queue.on('error', (err) => {
|
172
|
+
console.error('Failed to process page', err);
|
173
|
+
sendError(err as any, res);
|
174
|
+
});
|
175
|
+
|
171
176
|
await queue.addPrompt(aiRequest, conversationId, true);
|
172
177
|
|
173
178
|
await queue.wait();
|
@@ -274,6 +279,11 @@ router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Respons
|
|
274
279
|
sendEvent(res, screenData);
|
275
280
|
});
|
276
281
|
|
282
|
+
pageQueue.on('error', (err) => {
|
283
|
+
console.error('Failed to process page', err);
|
284
|
+
sendError(err as any, res);
|
285
|
+
});
|
286
|
+
|
277
287
|
await waitForStormStream(landingPagesStream);
|
278
288
|
await pageQueue.wait();
|
279
289
|
await Promise.allSettled(pageEventPromises);
|
package/src/storm/stormClient.ts
CHANGED
@@ -16,6 +16,23 @@ import {
|
|
16
16
|
StormUIListPrompt,
|
17
17
|
} from './stream';
|
18
18
|
import { Page, StormEventPageUrl } from './events';
|
19
|
+
import { fetch, RequestInit, Agent, RetryAgent } from 'undici';
|
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
36
|
|
20
37
|
export const STORM_ID = 'storm';
|
21
38
|
|
@@ -112,6 +129,7 @@ class StormClient {
|
|
112
129
|
method: method,
|
113
130
|
body: JSON.stringify(body),
|
114
131
|
headers,
|
132
|
+
dispatcher: retryAgent,
|
115
133
|
};
|
116
134
|
}
|
117
135
|
|
@@ -222,10 +240,7 @@ class StormClient {
|
|
222
240
|
conversationId,
|
223
241
|
});
|
224
242
|
|
225
|
-
return fetch(options.url,
|
226
|
-
method: options.method,
|
227
|
-
headers: options.headers,
|
228
|
-
});
|
243
|
+
return fetch(options.url, options);
|
229
244
|
}
|
230
245
|
|
231
246
|
public async getVoteUIPage(topic: string, conversationId: string, mainConversationId?: string) {
|
@@ -234,10 +249,7 @@ class StormClient {
|
|
234
249
|
conversationId,
|
235
250
|
});
|
236
251
|
|
237
|
-
const response = await fetch(options.url,
|
238
|
-
method: options.method,
|
239
|
-
headers: options.headers,
|
240
|
-
});
|
252
|
+
const response = await fetch(options.url, options);
|
241
253
|
|
242
254
|
return response.json() as Promise<{ vote: -1 | 0 | 1 }>;
|
243
255
|
}
|
@@ -331,10 +343,7 @@ class StormClient {
|
|
331
343
|
conversationId: conversationId,
|
332
344
|
});
|
333
345
|
|
334
|
-
const response = await fetch(options.url,
|
335
|
-
method: options.method,
|
336
|
-
headers: options.headers,
|
337
|
-
});
|
346
|
+
const response = await fetch(options.url, options);
|
338
347
|
|
339
348
|
return response.text();
|
340
349
|
}
|