@kapeta/local-cluster-service 0.76.1 → 0.76.3
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/instanceManager.js +12 -0
- package/dist/cjs/src/instances/routes.js +4 -1
- package/dist/cjs/src/serviceManager.js +7 -2
- package/dist/cjs/src/storm/routes.js +2 -2
- package/dist/cjs/src/storm/stormClient.js +20 -10
- package/dist/esm/src/instanceManager.js +12 -0
- package/dist/esm/src/instances/routes.js +4 -1
- package/dist/esm/src/serviceManager.js +7 -2
- package/dist/esm/src/storm/routes.js +2 -2
- package/dist/esm/src/storm/stormClient.js +20 -10
- package/package.json +1 -1
- package/src/instanceManager.ts +16 -0
- package/src/instances/routes.ts +4 -1
- package/src/serviceManager.ts +6 -2
- package/src/storm/routes.ts +3 -3
- package/src/storm/stormClient.ts +23 -12
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## [0.76.3](https://github.com/kapetacom/local-cluster-service/compare/v0.76.2...v0.76.3) (2024-10-01)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Catch errors and return empty HTMPPage array ([01f780e](https://github.com/kapetacom/local-cluster-service/commit/01f780e0e808c9d2e26c44f9330df75dda09a6f5))
|
7
|
+
|
8
|
+
## [0.76.2](https://github.com/kapetacom/local-cluster-service/compare/v0.76.1...v0.76.2) (2024-09-30)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* gracefully handle missing docker ([973c2d3](https://github.com/kapetacom/local-cluster-service/commit/973c2d383b0315e7fd6aa376b248417f76bd8f62))
|
14
|
+
* port conflict cant prevent server start ([0aac354](https://github.com/kapetacom/local-cluster-service/commit/0aac354742c3ccadd9da769a8802bea5b38df736))
|
15
|
+
|
1
16
|
## [0.76.1](https://github.com/kapetacom/local-cluster-service/compare/v0.76.0...v0.76.1) (2024-09-30)
|
2
17
|
|
3
18
|
|
@@ -427,6 +427,10 @@ class InstanceManager {
|
|
427
427
|
if (!blockAsset) {
|
428
428
|
throw new Error('Block not found: ' + blockRef);
|
429
429
|
}
|
430
|
+
const isAlive = containerManager_1.containerManager.isAlive();
|
431
|
+
if (!isAlive) {
|
432
|
+
throw new Error('Docker is not running or is not responding');
|
433
|
+
}
|
430
434
|
if (checkForSingleton && (await this.isSingletonOperator(blockAsset))) {
|
431
435
|
const instances = await this.getAllInstancesForKind(systemId, blockAsset.data.kind);
|
432
436
|
if (instances.length > 1) {
|
@@ -584,6 +588,10 @@ class InstanceManager {
|
|
584
588
|
//console.log('\n## Checking instances:');
|
585
589
|
let changed = false;
|
586
590
|
const all = [...this._instances];
|
591
|
+
if (!containerManager_1.containerManager.isAlive()) {
|
592
|
+
// No need to check anything if docker is not running
|
593
|
+
return;
|
594
|
+
}
|
587
595
|
while (all.length > 0) {
|
588
596
|
// Check a few instances at a time - docker doesn't like too many concurrent requests
|
589
597
|
const chunk = all.splice(0, 30);
|
@@ -712,6 +720,10 @@ class InstanceManager {
|
|
712
720
|
}
|
713
721
|
async getExternalStatus(instance) {
|
714
722
|
if (instance.type === types_1.InstanceType.DOCKER) {
|
723
|
+
if (!containerManager_1.containerManager.isAlive()) {
|
724
|
+
// Consider making this "unknown"
|
725
|
+
return types_1.InstanceStatus.STOPPED;
|
726
|
+
}
|
715
727
|
const containerName = await (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
716
728
|
const container = await containerManager_1.containerManager.getContainerByName(containerName);
|
717
729
|
if (!container) {
|
@@ -73,13 +73,16 @@ router.post('/:systemId/:instanceId/start', async (req, res) => {
|
|
73
73
|
taskId: result.id,
|
74
74
|
});
|
75
75
|
}
|
76
|
-
else {
|
76
|
+
else if (result) {
|
77
77
|
res.status(202).send({
|
78
78
|
ok: true,
|
79
79
|
pid: result.pid,
|
80
80
|
type: result.type,
|
81
81
|
});
|
82
82
|
}
|
83
|
+
else {
|
84
|
+
res.status(500).send({ ok: false, error: 'Failed to start instance' });
|
85
|
+
}
|
83
86
|
}
|
84
87
|
catch (e) {
|
85
88
|
res.status(500).send({ ok: false, error: e.message });
|
@@ -24,10 +24,15 @@ class ServiceManager {
|
|
24
24
|
if (!this._systems) {
|
25
25
|
this._systems = {};
|
26
26
|
}
|
27
|
-
lodash_1.default.forEach(this._systems, (system) => {
|
27
|
+
lodash_1.default.forEach(this._systems, (system, systemId) => {
|
28
28
|
lodash_1.default.forEach(system, (services) => {
|
29
29
|
lodash_1.default.forEach(services, (portInfo) => {
|
30
|
-
|
30
|
+
try {
|
31
|
+
clusterService_1.clusterService.reservePort(portInfo.port);
|
32
|
+
}
|
33
|
+
catch (e) {
|
34
|
+
console.warn('Failed to reserve port', systemId, portInfo.port, e);
|
35
|
+
}
|
31
36
|
});
|
32
37
|
});
|
33
38
|
});
|
@@ -607,7 +607,7 @@ router.post('/ui/vote', async (req, res) => {
|
|
607
607
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
608
608
|
const { topic, vote, mainConversationId } = aiRequest;
|
609
609
|
try {
|
610
|
-
const stormClient = new stormClient_1.StormClient(
|
610
|
+
const stormClient = new stormClient_1.StormClient('', mainConversationId);
|
611
611
|
await stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
|
612
612
|
}
|
613
613
|
catch (e) {
|
@@ -619,7 +619,7 @@ router.post('/ui/get-vote', async (req, res) => {
|
|
619
619
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
620
620
|
const { topic, mainConversationId } = aiRequest;
|
621
621
|
try {
|
622
|
-
const stormClient = new stormClient_1.StormClient(
|
622
|
+
const stormClient = new stormClient_1.StormClient('', mainConversationId);
|
623
623
|
const vote = await stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
|
624
624
|
res.send({ vote });
|
625
625
|
}
|
@@ -25,7 +25,7 @@ class StormClient {
|
|
25
25
|
_handle;
|
26
26
|
constructor(handle, systemId) {
|
27
27
|
this._baseUrl = (0, utils_1.getRemoteUrl)('ai-service', 'https://ai.kapeta.com');
|
28
|
-
this._systemId = systemId ||
|
28
|
+
this._systemId = systemId || '';
|
29
29
|
this._handle = handle;
|
30
30
|
}
|
31
31
|
async createOptions(path, method, body) {
|
@@ -148,15 +148,25 @@ class StormClient {
|
|
148
148
|
}
|
149
149
|
async replaceMockWithAPICall(prompt) {
|
150
150
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
151
|
+
try {
|
152
|
+
const response = await fetch(u, {
|
153
|
+
method: 'POST',
|
154
|
+
body: JSON.stringify(prompt.pages),
|
155
|
+
headers: {
|
156
|
+
systemId: prompt.systemId,
|
157
|
+
conversationId: prompt.systemId,
|
158
|
+
},
|
159
|
+
});
|
160
|
+
if (!response.ok) {
|
161
|
+
console.error('Failed to implement api clients', response.status, await response.text());
|
162
|
+
return [];
|
163
|
+
}
|
164
|
+
return (await response.json());
|
165
|
+
}
|
166
|
+
catch (error) {
|
167
|
+
console.error('Failed to implement api clients', error);
|
168
|
+
return [];
|
169
|
+
}
|
160
170
|
}
|
161
171
|
async generatePrompt(pages) {
|
162
172
|
const u = `${this._baseUrl}/v2/ui/prompt`;
|
@@ -427,6 +427,10 @@ class InstanceManager {
|
|
427
427
|
if (!blockAsset) {
|
428
428
|
throw new Error('Block not found: ' + blockRef);
|
429
429
|
}
|
430
|
+
const isAlive = containerManager_1.containerManager.isAlive();
|
431
|
+
if (!isAlive) {
|
432
|
+
throw new Error('Docker is not running or is not responding');
|
433
|
+
}
|
430
434
|
if (checkForSingleton && (await this.isSingletonOperator(blockAsset))) {
|
431
435
|
const instances = await this.getAllInstancesForKind(systemId, blockAsset.data.kind);
|
432
436
|
if (instances.length > 1) {
|
@@ -584,6 +588,10 @@ class InstanceManager {
|
|
584
588
|
//console.log('\n## Checking instances:');
|
585
589
|
let changed = false;
|
586
590
|
const all = [...this._instances];
|
591
|
+
if (!containerManager_1.containerManager.isAlive()) {
|
592
|
+
// No need to check anything if docker is not running
|
593
|
+
return;
|
594
|
+
}
|
587
595
|
while (all.length > 0) {
|
588
596
|
// Check a few instances at a time - docker doesn't like too many concurrent requests
|
589
597
|
const chunk = all.splice(0, 30);
|
@@ -712,6 +720,10 @@ class InstanceManager {
|
|
712
720
|
}
|
713
721
|
async getExternalStatus(instance) {
|
714
722
|
if (instance.type === types_1.InstanceType.DOCKER) {
|
723
|
+
if (!containerManager_1.containerManager.isAlive()) {
|
724
|
+
// Consider making this "unknown"
|
725
|
+
return types_1.InstanceStatus.STOPPED;
|
726
|
+
}
|
715
727
|
const containerName = await (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
716
728
|
const container = await containerManager_1.containerManager.getContainerByName(containerName);
|
717
729
|
if (!container) {
|
@@ -73,13 +73,16 @@ router.post('/:systemId/:instanceId/start', async (req, res) => {
|
|
73
73
|
taskId: result.id,
|
74
74
|
});
|
75
75
|
}
|
76
|
-
else {
|
76
|
+
else if (result) {
|
77
77
|
res.status(202).send({
|
78
78
|
ok: true,
|
79
79
|
pid: result.pid,
|
80
80
|
type: result.type,
|
81
81
|
});
|
82
82
|
}
|
83
|
+
else {
|
84
|
+
res.status(500).send({ ok: false, error: 'Failed to start instance' });
|
85
|
+
}
|
83
86
|
}
|
84
87
|
catch (e) {
|
85
88
|
res.status(500).send({ ok: false, error: e.message });
|
@@ -24,10 +24,15 @@ class ServiceManager {
|
|
24
24
|
if (!this._systems) {
|
25
25
|
this._systems = {};
|
26
26
|
}
|
27
|
-
lodash_1.default.forEach(this._systems, (system) => {
|
27
|
+
lodash_1.default.forEach(this._systems, (system, systemId) => {
|
28
28
|
lodash_1.default.forEach(system, (services) => {
|
29
29
|
lodash_1.default.forEach(services, (portInfo) => {
|
30
|
-
|
30
|
+
try {
|
31
|
+
clusterService_1.clusterService.reservePort(portInfo.port);
|
32
|
+
}
|
33
|
+
catch (e) {
|
34
|
+
console.warn('Failed to reserve port', systemId, portInfo.port, e);
|
35
|
+
}
|
31
36
|
});
|
32
37
|
});
|
33
38
|
});
|
@@ -607,7 +607,7 @@ router.post('/ui/vote', async (req, res) => {
|
|
607
607
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
608
608
|
const { topic, vote, mainConversationId } = aiRequest;
|
609
609
|
try {
|
610
|
-
const stormClient = new stormClient_1.StormClient(
|
610
|
+
const stormClient = new stormClient_1.StormClient('', mainConversationId);
|
611
611
|
await stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
|
612
612
|
}
|
613
613
|
catch (e) {
|
@@ -619,7 +619,7 @@ router.post('/ui/get-vote', async (req, res) => {
|
|
619
619
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
620
620
|
const { topic, mainConversationId } = aiRequest;
|
621
621
|
try {
|
622
|
-
const stormClient = new stormClient_1.StormClient(
|
622
|
+
const stormClient = new stormClient_1.StormClient('', mainConversationId);
|
623
623
|
const vote = await stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
|
624
624
|
res.send({ vote });
|
625
625
|
}
|
@@ -25,7 +25,7 @@ class StormClient {
|
|
25
25
|
_handle;
|
26
26
|
constructor(handle, systemId) {
|
27
27
|
this._baseUrl = (0, utils_1.getRemoteUrl)('ai-service', 'https://ai.kapeta.com');
|
28
|
-
this._systemId = systemId ||
|
28
|
+
this._systemId = systemId || '';
|
29
29
|
this._handle = handle;
|
30
30
|
}
|
31
31
|
async createOptions(path, method, body) {
|
@@ -148,15 +148,25 @@ class StormClient {
|
|
148
148
|
}
|
149
149
|
async replaceMockWithAPICall(prompt) {
|
150
150
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
151
|
+
try {
|
152
|
+
const response = await fetch(u, {
|
153
|
+
method: 'POST',
|
154
|
+
body: JSON.stringify(prompt.pages),
|
155
|
+
headers: {
|
156
|
+
systemId: prompt.systemId,
|
157
|
+
conversationId: prompt.systemId,
|
158
|
+
},
|
159
|
+
});
|
160
|
+
if (!response.ok) {
|
161
|
+
console.error('Failed to implement api clients', response.status, await response.text());
|
162
|
+
return [];
|
163
|
+
}
|
164
|
+
return (await response.json());
|
165
|
+
}
|
166
|
+
catch (error) {
|
167
|
+
console.error('Failed to implement api clients', error);
|
168
|
+
return [];
|
169
|
+
}
|
160
170
|
}
|
161
171
|
async generatePrompt(pages) {
|
162
172
|
const u = `${this._baseUrl}/v2/ui/prompt`;
|
package/package.json
CHANGED
package/src/instanceManager.ts
CHANGED
@@ -575,6 +575,11 @@ export class InstanceManager {
|
|
575
575
|
throw new Error('Block not found: ' + blockRef);
|
576
576
|
}
|
577
577
|
|
578
|
+
const isAlive = containerManager.isAlive();
|
579
|
+
if (!isAlive) {
|
580
|
+
throw new Error('Docker is not running or is not responding');
|
581
|
+
}
|
582
|
+
|
578
583
|
if (checkForSingleton && (await this.isSingletonOperator(blockAsset))) {
|
579
584
|
const instances = await this.getAllInstancesForKind(systemId, blockAsset.data.kind);
|
580
585
|
if (instances.length > 1) {
|
@@ -767,6 +772,12 @@ export class InstanceManager {
|
|
767
772
|
//console.log('\n## Checking instances:');
|
768
773
|
let changed = false;
|
769
774
|
const all = [...this._instances];
|
775
|
+
|
776
|
+
if (!containerManager.isAlive()) {
|
777
|
+
// No need to check anything if docker is not running
|
778
|
+
return;
|
779
|
+
}
|
780
|
+
|
770
781
|
while (all.length > 0) {
|
771
782
|
// Check a few instances at a time - docker doesn't like too many concurrent requests
|
772
783
|
const chunk = all.splice(0, 30);
|
@@ -932,6 +943,11 @@ export class InstanceManager {
|
|
932
943
|
|
933
944
|
private async getExternalStatus(instance: InstanceInfo): Promise<InstanceStatus> {
|
934
945
|
if (instance.type === InstanceType.DOCKER) {
|
946
|
+
if (!containerManager.isAlive()) {
|
947
|
+
// Consider making this "unknown"
|
948
|
+
return InstanceStatus.STOPPED;
|
949
|
+
}
|
950
|
+
|
935
951
|
const containerName = await getBlockInstanceContainerName(instance.systemId, instance.instanceId);
|
936
952
|
const container = await containerManager.getContainerByName(containerName);
|
937
953
|
if (!container) {
|
package/src/instances/routes.ts
CHANGED
@@ -12,6 +12,7 @@ import { kapetaHeaders, KapetaRequest } from '../middleware/kapeta';
|
|
12
12
|
import { stringBody } from '../middleware/stringBody';
|
13
13
|
import { DesiredInstanceStatus, InstanceInfo, InstanceOwner, InstanceType, KapetaBodyRequest } from '../types';
|
14
14
|
import { Task } from '../taskManager';
|
15
|
+
import { containerManager } from '../containerManager';
|
15
16
|
|
16
17
|
const router = Router();
|
17
18
|
router.use('/', corsHandler);
|
@@ -76,12 +77,14 @@ router.post('/:systemId/:instanceId/start', async (req: Request, res: Response)
|
|
76
77
|
ok: true,
|
77
78
|
taskId: result.id,
|
78
79
|
});
|
79
|
-
} else {
|
80
|
+
} else if (result) {
|
80
81
|
res.status(202).send({
|
81
82
|
ok: true,
|
82
83
|
pid: result.pid,
|
83
84
|
type: result.type,
|
84
85
|
});
|
86
|
+
} else {
|
87
|
+
res.status(500).send({ ok: false, error: 'Failed to start instance' });
|
85
88
|
}
|
86
89
|
} catch (e: any) {
|
87
90
|
res.status(500).send({ ok: false, error: e.message });
|
package/src/serviceManager.ts
CHANGED
@@ -26,10 +26,14 @@ class ServiceManager {
|
|
26
26
|
this._systems = {};
|
27
27
|
}
|
28
28
|
|
29
|
-
_.forEach(this._systems, (system) => {
|
29
|
+
_.forEach(this._systems, (system, systemId) => {
|
30
30
|
_.forEach(system, (services) => {
|
31
31
|
_.forEach(services, (portInfo) => {
|
32
|
-
|
32
|
+
try {
|
33
|
+
clusterService.reservePort(portInfo.port);
|
34
|
+
} catch (e) {
|
35
|
+
console.warn('Failed to reserve port', systemId, portInfo.port, e);
|
36
|
+
}
|
33
37
|
});
|
34
38
|
});
|
35
39
|
});
|
package/src/storm/routes.ts
CHANGED
@@ -163,7 +163,7 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
|
|
163
163
|
sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
|
164
164
|
|
165
165
|
const pagesFromDisk = readFilesAndContent(srcDir);
|
166
|
-
const client = new StormClient(handle, systemId)
|
166
|
+
const client = new StormClient(handle, systemId);
|
167
167
|
const pagesWithImplementation = await client.replaceMockWithAPICall({
|
168
168
|
pages: pagesFromDisk,
|
169
169
|
systemId: systemId,
|
@@ -756,7 +756,7 @@ router.post('/ui/vote', async (req: KapetaBodyRequest, res: Response) => {
|
|
756
756
|
const aiRequest: UIPageVoteRequest = JSON.parse(req.stringBody ?? '{}');
|
757
757
|
const { topic, vote, mainConversationId } = aiRequest;
|
758
758
|
try {
|
759
|
-
const stormClient = new StormClient(
|
759
|
+
const stormClient = new StormClient('', mainConversationId);
|
760
760
|
await stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
|
761
761
|
} catch (e: any) {
|
762
762
|
res.status(500).send({ error: e.message });
|
@@ -768,7 +768,7 @@ router.post('/ui/get-vote', async (req: KapetaBodyRequest, res: Response) => {
|
|
768
768
|
const aiRequest: UIPageGetVoteRequest = JSON.parse(req.stringBody ?? '{}');
|
769
769
|
const { topic, mainConversationId } = aiRequest;
|
770
770
|
try {
|
771
|
-
const stormClient = new StormClient(
|
771
|
+
const stormClient = new StormClient('', mainConversationId);
|
772
772
|
const vote = await stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
|
773
773
|
res.send({ vote });
|
774
774
|
} catch (e: any) {
|
package/src/storm/stormClient.ts
CHANGED
@@ -93,7 +93,7 @@ export class StormClient {
|
|
93
93
|
private readonly _handle: string;
|
94
94
|
constructor(handle: string, systemId?: string) {
|
95
95
|
this._baseUrl = getRemoteUrl('ai-service', 'https://ai.kapeta.com');
|
96
|
-
this._systemId = systemId ||
|
96
|
+
this._systemId = systemId || '';
|
97
97
|
this._handle = handle;
|
98
98
|
}
|
99
99
|
|
@@ -116,10 +116,10 @@ export class StormClient {
|
|
116
116
|
headers[ConversationIdHeader] = body.conversationId;
|
117
117
|
}
|
118
118
|
if (this._systemId) {
|
119
|
-
headers[SystemIdHeader] = this._systemId
|
119
|
+
headers[SystemIdHeader] = this._systemId;
|
120
120
|
}
|
121
121
|
if (this._handle) {
|
122
|
-
headers[HandleHeader] = this._handle
|
122
|
+
headers[HandleHeader] = this._handle;
|
123
123
|
}
|
124
124
|
return {
|
125
125
|
url,
|
@@ -252,15 +252,26 @@ export class StormClient {
|
|
252
252
|
|
253
253
|
public async replaceMockWithAPICall(prompt: ImplementAPIClients): Promise<HTMLPage[]> {
|
254
254
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
255
|
+
try {
|
256
|
+
const response = await fetch(u, {
|
257
|
+
method: 'POST',
|
258
|
+
body: JSON.stringify(prompt.pages),
|
259
|
+
headers: {
|
260
|
+
systemId: prompt.systemId,
|
261
|
+
conversationId: prompt.systemId,
|
262
|
+
},
|
263
|
+
});
|
264
|
+
|
265
|
+
if (!response.ok) {
|
266
|
+
console.error('Failed to implement api clients', response.status, await response.text());
|
267
|
+
return [];
|
268
|
+
}
|
269
|
+
|
270
|
+
return (await response.json()) as HTMLPage[];
|
271
|
+
} catch (error) {
|
272
|
+
console.error('Failed to implement api clients', error);
|
273
|
+
return [];
|
274
|
+
}
|
264
275
|
}
|
265
276
|
|
266
277
|
public async generatePrompt(pages: string[]): Promise<string> {
|