@kapeta/local-cluster-service 0.31.0 → 0.32.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 +7 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/src/ai/aiClient.d.ts +20 -0
- package/dist/cjs/src/ai/aiClient.js +74 -0
- package/dist/cjs/src/ai/routes.d.ts +7 -0
- package/dist/cjs/src/ai/routes.js +37 -0
- package/dist/cjs/src/ai/transform.d.ts +11 -0
- package/dist/cjs/src/ai/transform.js +239 -0
- package/dist/cjs/src/ai/types.d.ts +40 -0
- package/dist/cjs/src/ai/types.js +2 -0
- package/dist/cjs/src/codeGeneratorManager.js +4 -3
- package/dist/cjs/src/containerManager.js +7 -1
- package/dist/cjs/src/utils/utils.d.ts +1 -0
- package/dist/cjs/src/utils/utils.js +7 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/src/ai/aiClient.d.ts +20 -0
- package/dist/esm/src/ai/aiClient.js +74 -0
- package/dist/esm/src/ai/routes.d.ts +7 -0
- package/dist/esm/src/ai/routes.js +37 -0
- package/dist/esm/src/ai/transform.d.ts +11 -0
- package/dist/esm/src/ai/transform.js +239 -0
- package/dist/esm/src/ai/types.d.ts +40 -0
- package/dist/esm/src/ai/types.js +2 -0
- package/dist/esm/src/codeGeneratorManager.js +4 -3
- package/dist/esm/src/containerManager.js +7 -1
- package/dist/esm/src/utils/utils.d.ts +1 -0
- package/dist/esm/src/utils/utils.js +7 -1
- package/index.ts +2 -0
- package/package.json +1 -1
- package/src/ai/aiClient.ts +93 -0
- package/src/ai/routes.ts +39 -0
- package/src/ai/transform.ts +275 -0
- package/src/ai/types.ts +45 -0
- package/src/codeGeneratorManager.ts +6 -4
- package/src/containerManager.ts +7 -1
- package/src/utils/utils.ts +6 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
"use strict";
|
2
|
+
/**
|
3
|
+
* Copyright 2023 Kapeta Inc.
|
4
|
+
* SPDX-License-Identifier: BUSL-1.1
|
5
|
+
*/
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
8
|
+
};
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
10
|
+
const express_promise_router_1 = __importDefault(require("express-promise-router"));
|
11
|
+
const cors_1 = require("../middleware/cors");
|
12
|
+
const stringBody_1 = require("../middleware/stringBody");
|
13
|
+
const aiClient_1 = require("./aiClient");
|
14
|
+
const yaml_1 = __importDefault(require("yaml"));
|
15
|
+
const router = (0, express_promise_router_1.default)();
|
16
|
+
router.use('/', cors_1.corsHandler);
|
17
|
+
router.use('/', stringBody_1.stringBody);
|
18
|
+
router.post('/prompt/:handle', async (req, res) => {
|
19
|
+
const handle = req.params.handle;
|
20
|
+
try {
|
21
|
+
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
22
|
+
const result = await aiClient_1.aiClient.sendPrompt(handle, aiRequest);
|
23
|
+
if (req.accepts('application/yaml')) {
|
24
|
+
res.set('Content-Type', 'application/yaml');
|
25
|
+
res.send(yaml_1.default.stringify(result));
|
26
|
+
}
|
27
|
+
else {
|
28
|
+
res.json(result);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
catch (err) {
|
32
|
+
console.error('Failed to send prompt', err);
|
33
|
+
res.status(400).send({ error: err.message });
|
34
|
+
return;
|
35
|
+
}
|
36
|
+
});
|
37
|
+
exports.default = router;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
import { BlockDefinition, Plan } from '@kapeta/schemas';
|
6
|
+
import { Application } from './types';
|
7
|
+
export type PlanContext = {
|
8
|
+
plan: Plan;
|
9
|
+
blocks: BlockDefinition[];
|
10
|
+
};
|
11
|
+
export declare const transformToPlan: (handle: string, application: Application) => Promise<PlanContext>;
|
@@ -0,0 +1,239 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.transformToPlan = void 0;
|
7
|
+
const definitionsManager_1 = require("../definitionsManager");
|
8
|
+
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
9
|
+
const node_uuid_1 = __importDefault(require("node-uuid"));
|
10
|
+
async function getFreeName(name) {
|
11
|
+
let currentName = name;
|
12
|
+
let iteration = 1;
|
13
|
+
do {
|
14
|
+
const found = await definitionsManager_1.definitionsManager.getLatestDefinition(currentName);
|
15
|
+
if (!found) {
|
16
|
+
return currentName;
|
17
|
+
}
|
18
|
+
currentName = name + '_' + iteration++;
|
19
|
+
} while (true);
|
20
|
+
}
|
21
|
+
const transformToPlan = async (handle, application) => {
|
22
|
+
const blockTypeService = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/block-type-service');
|
23
|
+
const blockTypeFrontend = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/block-type-frontend');
|
24
|
+
const mongoDbResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-mongodb');
|
25
|
+
const postgresResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-postgresql');
|
26
|
+
const webPageResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-web-page');
|
27
|
+
const webFragmentResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-web-fragment');
|
28
|
+
const restApiResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-rest-api');
|
29
|
+
const restClientResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-rest-client');
|
30
|
+
const javaLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-java-spring-boot');
|
31
|
+
const nodejsLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-nodejs');
|
32
|
+
const reactLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-react-ts');
|
33
|
+
if (!blockTypeService ||
|
34
|
+
!blockTypeFrontend ||
|
35
|
+
!mongoDbResource ||
|
36
|
+
!postgresResource ||
|
37
|
+
!javaLanguage ||
|
38
|
+
!nodejsLanguage ||
|
39
|
+
!reactLanguage ||
|
40
|
+
!webPageResource ||
|
41
|
+
!restApiResource ||
|
42
|
+
!restClientResource ||
|
43
|
+
!webFragmentResource) {
|
44
|
+
throw new Error('Missing definitions');
|
45
|
+
}
|
46
|
+
const plan = {
|
47
|
+
kind: 'core/plan',
|
48
|
+
metadata: {
|
49
|
+
name: await getFreeName(`${handle}/${application.name}`),
|
50
|
+
title: application.title,
|
51
|
+
description: application.description,
|
52
|
+
visibility: 'private',
|
53
|
+
},
|
54
|
+
spec: {
|
55
|
+
blocks: [],
|
56
|
+
connections: [],
|
57
|
+
},
|
58
|
+
};
|
59
|
+
const blocks = [];
|
60
|
+
const addToPlan = (ref, name) => {
|
61
|
+
const top = 100 + Math.floor(plan.spec.blocks.length / 3) * 300;
|
62
|
+
const left = 200 + (plan.spec.blocks.length % 3) * 450;
|
63
|
+
plan.spec.blocks.push({
|
64
|
+
block: {
|
65
|
+
ref,
|
66
|
+
},
|
67
|
+
name,
|
68
|
+
id: node_uuid_1.default.v4(),
|
69
|
+
dimensions: {
|
70
|
+
top,
|
71
|
+
left,
|
72
|
+
width: -1,
|
73
|
+
height: -1,
|
74
|
+
},
|
75
|
+
});
|
76
|
+
};
|
77
|
+
const nameMapper = new Map();
|
78
|
+
for (const backend of application.backends) {
|
79
|
+
const blockName = `${handle}/${backend.name}`;
|
80
|
+
const blockRealName = await getFreeName(blockName);
|
81
|
+
nameMapper.set(blockName, blockRealName);
|
82
|
+
const language = backend.targetLanguage === 'java' ? javaLanguage : nodejsLanguage;
|
83
|
+
const databaseInfo = backend.databases?.[0];
|
84
|
+
const database = databaseInfo?.type === 'mongodb' ? mongoDbResource : postgresResource;
|
85
|
+
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockRealName + ':local');
|
86
|
+
let targetOptions = {};
|
87
|
+
if (backend.targetLanguage === 'java') {
|
88
|
+
targetOptions = {
|
89
|
+
basePackage: `${handle}.${application.name}`.toLowerCase().replace(/-/g, '_'),
|
90
|
+
groupId: `${handle}.${application.name}`.toLowerCase().replace(/-/g, '_'),
|
91
|
+
artifactId: backend.name.toLowerCase().replace(/-/g, '_'),
|
92
|
+
};
|
93
|
+
}
|
94
|
+
blocks.push({
|
95
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeService.definition.metadata.name}:${blockTypeService.version}`),
|
96
|
+
metadata: {
|
97
|
+
name: blockRealName,
|
98
|
+
title: backend.title,
|
99
|
+
description: backend.description,
|
100
|
+
visibility: 'private',
|
101
|
+
},
|
102
|
+
spec: {
|
103
|
+
target: {
|
104
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${language.definition.metadata.name}:${language.version}`),
|
105
|
+
options: targetOptions,
|
106
|
+
},
|
107
|
+
consumers: [
|
108
|
+
{
|
109
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${database.definition.metadata.name}:${database.version}`),
|
110
|
+
metadata: {
|
111
|
+
name: (databaseInfo?.name ?? 'main').replace(/-/g, ''),
|
112
|
+
},
|
113
|
+
spec: {
|
114
|
+
port: {
|
115
|
+
type: database.definition.spec.ports[0].type,
|
116
|
+
},
|
117
|
+
},
|
118
|
+
},
|
119
|
+
],
|
120
|
+
providers: [
|
121
|
+
{
|
122
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${restApiResource.definition.metadata.name}:${restApiResource.version}`),
|
123
|
+
metadata: {
|
124
|
+
name: 'main',
|
125
|
+
},
|
126
|
+
spec: {
|
127
|
+
port: {
|
128
|
+
type: restApiResource.definition.spec.ports[0].type,
|
129
|
+
},
|
130
|
+
},
|
131
|
+
},
|
132
|
+
],
|
133
|
+
},
|
134
|
+
});
|
135
|
+
addToPlan(blockRef, backend.name);
|
136
|
+
}
|
137
|
+
for (const frontend of application.frontends) {
|
138
|
+
const blockName = `${handle}/${frontend.name}`;
|
139
|
+
const blockRealName = await getFreeName(blockName);
|
140
|
+
nameMapper.set(blockName, blockRealName);
|
141
|
+
const language = reactLanguage;
|
142
|
+
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockRealName + ':local');
|
143
|
+
blocks.push({
|
144
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeFrontend.definition.metadata.name}:${blockTypeFrontend.version}`),
|
145
|
+
metadata: {
|
146
|
+
name: blockRealName,
|
147
|
+
title: frontend.title,
|
148
|
+
description: frontend.description,
|
149
|
+
visibility: 'private',
|
150
|
+
},
|
151
|
+
spec: {
|
152
|
+
target: {
|
153
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${language.definition.metadata.name}:${language.version}`),
|
154
|
+
options: {},
|
155
|
+
},
|
156
|
+
consumers: [],
|
157
|
+
providers: [
|
158
|
+
{
|
159
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${webPageResource.definition.metadata.name}:${webPageResource.version}`),
|
160
|
+
metadata: {
|
161
|
+
name: 'main',
|
162
|
+
},
|
163
|
+
spec: {
|
164
|
+
port: {
|
165
|
+
type: webPageResource.definition.spec.ports[0].type,
|
166
|
+
},
|
167
|
+
},
|
168
|
+
},
|
169
|
+
],
|
170
|
+
},
|
171
|
+
});
|
172
|
+
addToPlan(blockRef, frontend.name);
|
173
|
+
}
|
174
|
+
application.connections?.forEach((connection) => {
|
175
|
+
const fullProviderName = nameMapper.get(`${handle}/${connection.provider.name}`);
|
176
|
+
const fullConsumerName = nameMapper.get(`${handle}/${connection.consumer.name}`);
|
177
|
+
const consumerResourceName = connection.provider.name;
|
178
|
+
const providerRef = (0, nodejs_utils_1.normalizeKapetaUri)(`${fullProviderName}:local`);
|
179
|
+
const consumerRef = (0, nodejs_utils_1.normalizeKapetaUri)(`${fullConsumerName}:local`);
|
180
|
+
const instanceProvider = plan.spec.blocks.find((b) => b.block.ref === providerRef);
|
181
|
+
const instanceConsumer = plan.spec.blocks.find((b) => b.block.ref === consumerRef);
|
182
|
+
const consumerBlock = blocks.find((block) => block.metadata.name === fullConsumerName);
|
183
|
+
const providerBlock = blocks.find((block) => block.metadata.name === fullProviderName);
|
184
|
+
if (!consumerBlock) {
|
185
|
+
throw new Error('Missing consumer block: ' + fullConsumerName);
|
186
|
+
}
|
187
|
+
if (!providerBlock) {
|
188
|
+
throw new Error('Missing provider block: ' + fullProviderName);
|
189
|
+
}
|
190
|
+
const portType = (0, nodejs_utils_1.parseKapetaUri)(providerBlock.kind).fullName === 'kapeta/block-type-service' ? 'rest' : 'web';
|
191
|
+
if (portType === 'rest') {
|
192
|
+
consumerBlock.spec.consumers.push({
|
193
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${restClientResource.definition.metadata.name}:${restClientResource.version}`),
|
194
|
+
metadata: {
|
195
|
+
name: consumerResourceName,
|
196
|
+
},
|
197
|
+
spec: {
|
198
|
+
port: {
|
199
|
+
type: 'rest',
|
200
|
+
},
|
201
|
+
},
|
202
|
+
});
|
203
|
+
}
|
204
|
+
else {
|
205
|
+
consumerBlock.spec.consumers.push({
|
206
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${webFragmentResource.definition.metadata.name}:${webFragmentResource.version}`),
|
207
|
+
metadata: {
|
208
|
+
name: consumerResourceName,
|
209
|
+
},
|
210
|
+
spec: {
|
211
|
+
port: {
|
212
|
+
type: 'web',
|
213
|
+
},
|
214
|
+
},
|
215
|
+
});
|
216
|
+
}
|
217
|
+
plan.spec.connections.push({
|
218
|
+
provider: {
|
219
|
+
blockId: instanceProvider.id,
|
220
|
+
resourceName: 'main',
|
221
|
+
port: {
|
222
|
+
type: portType,
|
223
|
+
},
|
224
|
+
},
|
225
|
+
consumer: {
|
226
|
+
blockId: instanceConsumer.id,
|
227
|
+
resourceName: consumerResourceName,
|
228
|
+
port: {
|
229
|
+
type: portType,
|
230
|
+
},
|
231
|
+
},
|
232
|
+
});
|
233
|
+
});
|
234
|
+
return {
|
235
|
+
plan,
|
236
|
+
blocks,
|
237
|
+
};
|
238
|
+
};
|
239
|
+
exports.transformToPlan = transformToPlan;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
export interface Database {
|
6
|
+
name: string;
|
7
|
+
type: 'mongodb' | 'postgres';
|
8
|
+
}
|
9
|
+
export interface BackendService {
|
10
|
+
name: string;
|
11
|
+
title: string;
|
12
|
+
description: string;
|
13
|
+
targetLanguage: 'java' | 'node';
|
14
|
+
databases: Database[] | null;
|
15
|
+
}
|
16
|
+
export interface FrontendService {
|
17
|
+
name: string;
|
18
|
+
title: string;
|
19
|
+
description: string;
|
20
|
+
targetLanguage: 'react';
|
21
|
+
}
|
22
|
+
export interface Endpoint {
|
23
|
+
type: 'backend' | 'frontend';
|
24
|
+
name: string;
|
25
|
+
}
|
26
|
+
export interface Connection {
|
27
|
+
provider: Endpoint;
|
28
|
+
consumer: Endpoint;
|
29
|
+
}
|
30
|
+
export interface Application {
|
31
|
+
kind: 'core/plan';
|
32
|
+
name: string;
|
33
|
+
title: string;
|
34
|
+
description: string;
|
35
|
+
backends: BackendService[];
|
36
|
+
frontends: FrontendService[];
|
37
|
+
connections: Connection[];
|
38
|
+
explanation: string;
|
39
|
+
response: string;
|
40
|
+
}
|
@@ -58,13 +58,14 @@ class CodeGeneratorManager {
|
|
58
58
|
});
|
59
59
|
}
|
60
60
|
async canGenerateCode(yamlContent) {
|
61
|
-
if (!yamlContent.spec
|
61
|
+
if (!yamlContent.spec?.target?.kind || !yamlContent.kind) {
|
62
62
|
//Not all block types have targets
|
63
63
|
return false;
|
64
64
|
}
|
65
|
+
const kindUri = (0, nodejs_utils_1.parseKapetaUri)(yamlContent.kind);
|
65
66
|
const blockTypes = await definitionsManager_1.definitionsManager.getDefinitions(BLOCK_TYPE_KIND);
|
66
|
-
const blockTypeKinds = blockTypes.map((blockType) => blockType.definition.metadata.name
|
67
|
-
return
|
67
|
+
const blockTypeKinds = blockTypes.map((blockType) => (0, nodejs_utils_1.parseKapetaUri)(blockType.definition.metadata.name).fullName);
|
68
|
+
return blockTypeKinds.includes(kindUri.fullName);
|
68
69
|
}
|
69
70
|
async generate(yamlFile, yamlContent) {
|
70
71
|
if (!yamlContent.spec.target?.kind) {
|
@@ -98,11 +98,17 @@ class ContainerManager {
|
|
98
98
|
];
|
99
99
|
for (const opts of connectOptions) {
|
100
100
|
try {
|
101
|
+
const testClient = new dockerode_1.default({
|
102
|
+
...opts,
|
103
|
+
timeout: 1000, // 1 secs should be enough for a ping
|
104
|
+
});
|
105
|
+
await testClient.ping();
|
106
|
+
// If we get here - we have a working connection
|
107
|
+
// Now create a client with a longer timeout for all other operations
|
101
108
|
const client = new dockerode_1.default({
|
102
109
|
...opts,
|
103
110
|
timeout: 15 * 60 * 1000, //15 minutes should be enough for any operation
|
104
111
|
});
|
105
|
-
await client.ping();
|
106
112
|
this._docker = client;
|
107
113
|
const versionInfo = await client.version();
|
108
114
|
this._version = versionInfo.Server?.Version ?? versionInfo.Version;
|
@@ -5,6 +5,7 @@
|
|
5
5
|
import { EntityList } from '@kapeta/schemas';
|
6
6
|
import { AnyMap } from '../types';
|
7
7
|
export declare function getBlockInstanceContainerName(systemId: string, instanceId: string): string;
|
8
|
+
export declare function getRemoteUrl(id: string, defautValue: string): any;
|
8
9
|
export declare function readYML(path: string): any;
|
9
10
|
export declare function isWindows(): boolean;
|
10
11
|
export declare function isMac(): boolean;
|
@@ -7,15 +7,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
8
8
|
};
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
10
|
-
exports.getResolvedConfiguration = exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.getBlockInstanceContainerName = void 0;
|
10
|
+
exports.getResolvedConfiguration = exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.getRemoteUrl = exports.getBlockInstanceContainerName = void 0;
|
11
11
|
const node_fs_1 = __importDefault(require("node:fs"));
|
12
12
|
const yaml_1 = __importDefault(require("yaml"));
|
13
13
|
const md5_1 = __importDefault(require("md5"));
|
14
14
|
const lodash_1 = __importDefault(require("lodash"));
|
15
|
+
const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
|
15
16
|
function getBlockInstanceContainerName(systemId, instanceId) {
|
16
17
|
return `kapeta-block-instance-${(0, md5_1.default)(systemId + instanceId)}`;
|
17
18
|
}
|
18
19
|
exports.getBlockInstanceContainerName = getBlockInstanceContainerName;
|
20
|
+
function getRemoteUrl(id, defautValue) {
|
21
|
+
const remoteConfig = local_cluster_config_1.default.getClusterConfig().remote;
|
22
|
+
return remoteConfig?.[id] ?? defautValue;
|
23
|
+
}
|
24
|
+
exports.getRemoteUrl = getRemoteUrl;
|
19
25
|
function readYML(path) {
|
20
26
|
const rawYaml = node_fs_1.default.readFileSync(path);
|
21
27
|
try {
|
package/index.ts
CHANGED
@@ -23,6 +23,7 @@ import ProviderRoutes from './src/providers/routes';
|
|
23
23
|
import AttachmentRoutes from './src/attachments/routes';
|
24
24
|
import TaskRoutes from './src/tasks/routes';
|
25
25
|
import APIRoutes from './src/api';
|
26
|
+
import AIRoutes from './src/ai/routes';
|
26
27
|
import { getBindHost } from './src/utils/utils';
|
27
28
|
import request from 'request';
|
28
29
|
import { repositoryManager } from './src/repositoryManager';
|
@@ -72,6 +73,7 @@ function createServer() {
|
|
72
73
|
app.use('/attachments', AttachmentRoutes);
|
73
74
|
app.use('/tasks', TaskRoutes);
|
74
75
|
app.use('/api', APIRoutes);
|
76
|
+
app.use('/ai', AIRoutes);
|
75
77
|
|
76
78
|
app.get('/status', async (req, res) => {
|
77
79
|
res.send({
|
package/package.json
CHANGED
@@ -0,0 +1,93 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
import request from 'request';
|
6
|
+
import { PlanContext, transformToPlan } from './transform';
|
7
|
+
import { Application } from './types';
|
8
|
+
import { KapetaAPI } from '@kapeta/nodejs-api-client';
|
9
|
+
import ClusterConfiguration from '@kapeta/local-cluster-config';
|
10
|
+
import { getRemoteUrl } from '../utils/utils';
|
11
|
+
|
12
|
+
export type PromptResult = {
|
13
|
+
explanation: string;
|
14
|
+
response: string;
|
15
|
+
context?: PlanContext;
|
16
|
+
};
|
17
|
+
|
18
|
+
export interface AIMessage {
|
19
|
+
content: string;
|
20
|
+
role: 'user' | 'assistant';
|
21
|
+
}
|
22
|
+
|
23
|
+
export interface AIRequest {
|
24
|
+
messages: AIMessage[];
|
25
|
+
}
|
26
|
+
|
27
|
+
class AIClient {
|
28
|
+
private readonly _baseUrl: string;
|
29
|
+
|
30
|
+
constructor() {
|
31
|
+
this._baseUrl = getRemoteUrl('ai-service', 'https://ai.kapeta.com');
|
32
|
+
}
|
33
|
+
|
34
|
+
public async sendPrompt(handle: string, body: AIRequest): Promise<PromptResult> {
|
35
|
+
const url = `${this._baseUrl}/v1/plan?type=chat`;
|
36
|
+
|
37
|
+
const headers: { [k: string]: string } = {};
|
38
|
+
const api = new KapetaAPI();
|
39
|
+
if (api.hasToken()) {
|
40
|
+
headers['Authorization'] = `Bearer ${await api.getAccessToken()}`;
|
41
|
+
}
|
42
|
+
|
43
|
+
const options = {
|
44
|
+
url,
|
45
|
+
method: 'POST',
|
46
|
+
json: true,
|
47
|
+
body,
|
48
|
+
headers,
|
49
|
+
};
|
50
|
+
|
51
|
+
return new Promise((resolve, reject) => {
|
52
|
+
request(options, async (error, response, application: Application) => {
|
53
|
+
if (error) {
|
54
|
+
console.error(error);
|
55
|
+
reject(error);
|
56
|
+
}
|
57
|
+
|
58
|
+
if (response.statusCode !== 200) {
|
59
|
+
console.log('Prompt failed', response.statusCode, response.body);
|
60
|
+
reject(new Error(`Invalid response code: ${response.statusCode}`));
|
61
|
+
return;
|
62
|
+
}
|
63
|
+
|
64
|
+
try {
|
65
|
+
if (application?.name) {
|
66
|
+
const planContext = await transformToPlan(handle, application);
|
67
|
+
resolve({
|
68
|
+
explanation: application.explanation,
|
69
|
+
response: application.response ?? application.explanation ?? 'Plan was generated',
|
70
|
+
context: planContext,
|
71
|
+
});
|
72
|
+
} else {
|
73
|
+
resolve({
|
74
|
+
explanation: application.explanation,
|
75
|
+
response:
|
76
|
+
application.response ??
|
77
|
+
application.explanation ??
|
78
|
+
'I did not understand your request. Please rephrase.',
|
79
|
+
});
|
80
|
+
}
|
81
|
+
} catch (err: any) {
|
82
|
+
console.error(err);
|
83
|
+
resolve({
|
84
|
+
explanation: '',
|
85
|
+
response: 'I did not understand your request. Please rephrase.',
|
86
|
+
});
|
87
|
+
}
|
88
|
+
});
|
89
|
+
});
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
export const aiClient = new AIClient();
|
package/src/ai/routes.ts
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
|
6
|
+
import Router from 'express-promise-router';
|
7
|
+
import { Response } from 'express';
|
8
|
+
|
9
|
+
import { corsHandler } from '../middleware/cors';
|
10
|
+
|
11
|
+
import { stringBody, StringBodyRequest } from '../middleware/stringBody';
|
12
|
+
import { aiClient, AIRequest } from './aiClient';
|
13
|
+
import { KapetaBodyRequest } from '../types';
|
14
|
+
import YAML from 'yaml';
|
15
|
+
|
16
|
+
const router = Router();
|
17
|
+
|
18
|
+
router.use('/', corsHandler);
|
19
|
+
router.use('/', stringBody);
|
20
|
+
|
21
|
+
router.post('/prompt/:handle', async (req: KapetaBodyRequest, res: Response) => {
|
22
|
+
const handle = req.params.handle;
|
23
|
+
try {
|
24
|
+
const aiRequest: AIRequest = JSON.parse(req.stringBody ?? '{}');
|
25
|
+
const result = await aiClient.sendPrompt(handle, aiRequest);
|
26
|
+
if (req.accepts('application/yaml')) {
|
27
|
+
res.set('Content-Type', 'application/yaml');
|
28
|
+
res.send(YAML.stringify(result));
|
29
|
+
} else {
|
30
|
+
res.json(result);
|
31
|
+
}
|
32
|
+
} catch (err: any) {
|
33
|
+
console.error('Failed to send prompt', err);
|
34
|
+
res.status(400).send({ error: err.message });
|
35
|
+
return;
|
36
|
+
}
|
37
|
+
});
|
38
|
+
|
39
|
+
export default router;
|