openkbs 0.0.31 → 0.0.33
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/MODIFY.md +84 -35
- package/README.md +4 -0
- package/package.json +2 -1
- package/scripts/deploy-templates.js +43 -0
- package/src/actions.js +34 -3
- package/src/utils.js +65 -2
package/MODIFY.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
####
|
|
1
|
+
#### USER-DEFINED REQUIREMENTS
|
|
2
2
|
|
|
3
3
|
Define any additional features, integrations, or behavior modifications
|
|
4
4
|
|
|
5
|
-
####
|
|
6
|
-
|
|
5
|
+
#### USER-DEFINED REQUIREMENTS END
|
|
7
6
|
|
|
8
7
|
# OpenKBS Framework Technical Guide (For AI Coding Agents)
|
|
9
8
|
|
|
9
|
+
OpenKBS is claud AI platform designed to build, deploy and integrate AI agents and applications.
|
|
10
10
|
This guide provides a brief overview of developing backend and frontend logic for your OpenKBS app/agent.
|
|
11
11
|
|
|
12
12
|
## Project Structure Overview
|
|
@@ -14,8 +14,8 @@ This guide provides a brief overview of developing backend and frontend logic fo
|
|
|
14
14
|
A typical OpenKBS project has the following key directories:
|
|
15
15
|
|
|
16
16
|
* **`src/`**: Contains your custom source code.
|
|
17
|
-
* **`Events/`**: Houses backend event handlers.
|
|
18
|
-
* `actions.js`:
|
|
17
|
+
* **`Events/`**: Houses backend LLM event handlers.
|
|
18
|
+
* `actions.js`: Example file commonly used for shared backend logic/tools definitions.
|
|
19
19
|
* `onRequest.js`: Handles incoming user messages before LLM processing.
|
|
20
20
|
* `onRequest.json`: NPM dependencies for `onRequest.js`.
|
|
21
21
|
* `onResponse.js`: Handles LLM responses before sending to the user.
|
|
@@ -32,52 +32,101 @@ A typical OpenKBS project has the following key directories:
|
|
|
32
32
|
* `instructions.txt`: Instructions for the LLM.
|
|
33
33
|
* `icon.png`: Application icon.
|
|
34
34
|
|
|
35
|
+
#### Backend Handlers
|
|
36
|
+
- Files with names starting with "on" (like onRequest.js) are called handler
|
|
37
|
+
- Each handler functions as NodeJS execution entry point executed by the platform upon certain events.
|
|
38
|
+
- use ES6, user import, do not use require()
|
|
39
|
+
|
|
35
40
|
#### onRequest and onResponse Handlers
|
|
36
41
|
|
|
37
|
-
The core of the OpenKBS backend framework revolves around the `onRequest` and `onResponse` event handlers.
|
|
38
|
-
These handlers act as middleware, intercepting messages
|
|
42
|
+
The core of the OpenKBS backend framework revolves around the `onRequest` and `onResponse` Node.js backend event handlers.
|
|
43
|
+
These handlers act as middleware, intercepting user messages and messages generated by the LLM.
|
|
44
|
+
|
|
45
|
+
- onRequest handler is invoked every time a user sends a message to the chat. It provides an opportunity to process the user's input, extract commands and perform actions based on the user's message.
|
|
46
|
+
- onResponse handler is invoked after the LLM generates a response. It allows processing of the LLM's output, execution of commands based on the LLM's message.
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
#### NPM Dependencies for Backend Handlers
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
1. If a file imports an NPM dependency and is then imported by a handler, this dependency must be defined in the handler's corresponding json file
|
|
51
|
+
2. Example: If actions.js imports got and onRequest.js imports actions.js, then got must be in onRequest.json
|
|
43
52
|
|
|
44
53
|
|
|
45
54
|
## Backend Dependencies
|
|
46
55
|
|
|
47
56
|
To use external NPM packages in your backend event handlers, you must declare them in the corresponding `.json` file.
|
|
48
|
-
OpenKBS also provides a secure way to handle sensitive information using the `{{secrets.your_secret_name}}` syntax.
|
|
49
57
|
|
|
50
|
-
**Example: Using
|
|
58
|
+
**Example: Using https module with an API key**
|
|
51
59
|
|
|
52
|
-
1. **Declare dependencies** in both `src/Events/onRequest.json` and `src/Events/onResponse.json` (as each handler have separate build):
|
|
60
|
+
1. **Declare dependencies** in both `src/Events/onRequest.json` and `src/Events/onResponse.json` (as each handler have separate Node.js build):
|
|
53
61
|
```json
|
|
54
62
|
{
|
|
55
63
|
"dependencies": {
|
|
56
|
-
"
|
|
64
|
+
"got": "^11.8.5"
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
67
|
```
|
|
60
68
|
|
|
61
|
-
2. **Implement
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
];
|
|
79
|
-
};
|
|
80
|
-
```
|
|
69
|
+
2. **Implement in any JavaScript file** (actions.js is just an example name):
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
import got from 'got';
|
|
73
|
+
export const getActions = (meta) => {
|
|
74
|
+
return [
|
|
75
|
+
[/\/?getNews\("(.*)"\)/, async (match) => {
|
|
76
|
+
const { body } = await got(`https://newsapi.org/v2/everything?q=${match[1]}&apiKey={{secrets.news_api_key}}`,
|
|
77
|
+
{ responseType: 'json' });
|
|
78
|
+
|
|
79
|
+
return { result: body.articles, ...meta };
|
|
80
|
+
}],
|
|
81
|
+
// ... other actions
|
|
82
|
+
];
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
81
86
|
## Secrets Management
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
OpenKBS provides a secure way to handle sensitive information using the `{{secrets.your_secret_name}}` syntax.
|
|
88
|
+
Never hardcode secrets in the code, if any secrets are provided by the user replace them with placeholders syntax above.
|
|
89
|
+
The user will later insert the secrets using the secrets manager
|
|
90
|
+
|
|
91
|
+
#### LLM Instructions
|
|
92
|
+
`app/instructions.txt`
|
|
93
|
+
This file contains the instructions for the agent
|
|
94
|
+
|
|
95
|
+
**Example Instructions:**
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
You are an AI assistant.
|
|
99
|
+
|
|
100
|
+
You can execute the following commands:
|
|
101
|
+
|
|
102
|
+
/getNews("query")
|
|
103
|
+
Description: """
|
|
104
|
+
Get the latest news
|
|
105
|
+
"""
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Important Coding guidelines
|
|
109
|
+
- Organize new features across multiple files (like Events/utils.js) rather than adding everything to actions.js
|
|
110
|
+
- Similar for Frontend: keep newly implemented React components outside contentRender.js for maintainability.
|
|
111
|
+
- Keep in mind onRenderChatMessage is not a React component but simple JS function
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
#### onRenderChatMessage injected functions
|
|
115
|
+
These functions are automatically injected by the framework to onRenderChatMessage, providing access to system features
|
|
116
|
+
```
|
|
117
|
+
// Where functions are injected
|
|
118
|
+
const onRenderChatMessage = async (params) => {
|
|
119
|
+
const { APIResponseComponent, theme, setBlockingLoading, setSystemAlert, RequestChatAPI,
|
|
120
|
+
kbUserData, generateMsgId, messages, msgIndex } = params;
|
|
121
|
+
...
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
// Shows toast notifications in top-right corner
|
|
127
|
+
setSystemAlert({
|
|
128
|
+
msg: 'Payment successful',
|
|
129
|
+
type: 'success', //'error'|'success'|'info'
|
|
130
|
+
duration: 5000
|
|
131
|
+
});
|
|
132
|
+
```
|
package/README.md
CHANGED
|
@@ -597,6 +597,8 @@ The `openkbs` object provides a set of utility functions and services to interac
|
|
|
597
597
|
|
|
598
598
|
* **`openkbs.googleSearch(q, params)`:** Performs a Google search using the provided query and parameters.
|
|
599
599
|
|
|
600
|
+
* **`openkbs.sendMail(email, subject, content)`:** Sends an email to the specified recipient
|
|
601
|
+
|
|
600
602
|
* **`openkbs.documentToText(documentURL, params)`:** Extracts text from various document formats.
|
|
601
603
|
|
|
602
604
|
* **`openkbs.imageToText(imageUrl, params)`:** Extracts text from an image.
|
|
@@ -619,6 +621,8 @@ The `openkbs` object provides a set of utility functions and services to interac
|
|
|
619
621
|
|
|
620
622
|
* **`openkbs.clientHeaders`:** Exposes client headers for accessing information like IP address, location, etc. (e.g., `openkbs.clientHeaders['x-forwarded-for']`).
|
|
621
623
|
|
|
624
|
+
* **`openkbs.createEmbeddings(input, model)`:** Create embeddings from input text
|
|
625
|
+
|
|
622
626
|
**Example SDK Usage:**
|
|
623
627
|
|
|
624
628
|
```javascript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openkbs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.33",
|
|
4
4
|
"description": "OpenKBS - Command Line Interface",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"deploy:linux": "aws s3 cp build/openkbs-linux-x64 s3://openkbs-downloads/cli/linux/openkbs && aws s3 cp build/openkbs-linux-arm64 s3://openkbs-downloads/cli/linux/openkbs-arm64",
|
|
10
10
|
"deploy:macos": "aws s3 cp build/openkbs-macos-arm64 s3://openkbs-downloads/cli/macos/openkbs && aws s3 cp build/openkbs-macos-x64 s3://openkbs-downloads/cli/macos/openkbs-x64",
|
|
11
11
|
"deploy:win": "aws s3 cp build/openkbs-win-x64.exe s3://openkbs-downloads/cli/windows/openkbs.exe && aws s3 cp build/openkbs-win-arm64.exe s3://openkbs-downloads/cli/windows/openkbs-arm64.exe",
|
|
12
|
+
"deploy:templates": "node scripts/deploy-templates.js",
|
|
12
13
|
"postinstall": "node src/install.js"
|
|
13
14
|
},
|
|
14
15
|
"bin": {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
|
|
6
|
+
|
|
7
|
+
const s3Client = new S3Client({ region: 'us-east-1' });
|
|
8
|
+
const BUCKET = 'openkbs-downloads';
|
|
9
|
+
const TEMPLATES_DIR = path.join(__dirname, '../templates');
|
|
10
|
+
|
|
11
|
+
async function uploadDirectory(localPath, s3Prefix) {
|
|
12
|
+
const items = await fs.readdir(localPath, { withFileTypes: true });
|
|
13
|
+
|
|
14
|
+
for (const item of items) {
|
|
15
|
+
const itemPath = path.join(localPath, item.name);
|
|
16
|
+
const s3Key = `${s3Prefix}/${item.name}`;
|
|
17
|
+
|
|
18
|
+
if (item.isDirectory()) {
|
|
19
|
+
await uploadDirectory(itemPath, s3Key);
|
|
20
|
+
} else {
|
|
21
|
+
const fileContent = await fs.readFile(itemPath);
|
|
22
|
+
await s3Client.send(new PutObjectCommand({
|
|
23
|
+
Bucket: BUCKET,
|
|
24
|
+
Key: s3Key,
|
|
25
|
+
Body: fileContent
|
|
26
|
+
}));
|
|
27
|
+
console.log(`Uploaded: ${s3Key}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function deployTemplates() {
|
|
33
|
+
try {
|
|
34
|
+
console.log('Deploying templates to S3...');
|
|
35
|
+
await uploadDirectory(TEMPLATES_DIR, 'templates');
|
|
36
|
+
console.log('Templates deployed successfully!');
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Error deploying templates:', error);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
deployTemplates();
|
package/src/actions.js
CHANGED
|
@@ -10,10 +10,10 @@ const {
|
|
|
10
10
|
fetchAndSaveSettings, downloadFiles, downloadIcon, updateKB, uploadFiles, generateKey, generateMnemonic,
|
|
11
11
|
reset, bold, red, yellow, green, createKB, saveLocalKBData, listKBs, deleteKBFile,
|
|
12
12
|
deleteKB, buildPackage, replacePlaceholderInFiles, buildNodePackage, initByTemplateAction, modifyKB,
|
|
13
|
-
listKBsSharedWithMe
|
|
13
|
+
listKBsSharedWithMe, downloadTemplates
|
|
14
14
|
} = require("./utils");
|
|
15
15
|
|
|
16
|
-
const TEMPLATE_DIR = path.join(
|
|
16
|
+
const TEMPLATE_DIR = path.join(os.homedir(), '.openkbs', 'templates');
|
|
17
17
|
const jwtPath = path.join(os.homedir(), '.openkbs', 'clientJWT');
|
|
18
18
|
const generateTransactionId = () => `${+new Date()}-${Math.floor(100000 + Math.random() * 900000)}`;
|
|
19
19
|
|
|
@@ -247,12 +247,31 @@ async function pushAction(location = 'origin', targetFile, options) {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
async function cloneAction(kbId) {
|
|
250
|
+
// Store the original directory so we can restore it if needed
|
|
251
|
+
const originalDir = process.cwd();
|
|
252
|
+
|
|
250
253
|
try {
|
|
254
|
+
// Create a subdirectory with the name of the kbId
|
|
255
|
+
const targetDir = path.join(originalDir, kbId);
|
|
256
|
+
|
|
257
|
+
// Check if directory already exists
|
|
258
|
+
if (fs.existsSync(targetDir)) {
|
|
259
|
+
console.red(`Directory ${kbId} already exists.`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Create the subdirectory
|
|
264
|
+
fs.mkdirSync(targetDir);
|
|
265
|
+
|
|
266
|
+
// Change to the new directory
|
|
267
|
+
process.chdir(targetDir);
|
|
268
|
+
|
|
251
269
|
const localKBData = await fetchLocalKBData({forceInit: true});
|
|
252
270
|
|
|
253
271
|
if (localKBData?.kbId) {
|
|
254
272
|
console.red(`KB ${localKBData?.kbId} already saved in settings.json.`);
|
|
255
273
|
console.yellow(`To pull the changes from OpenKBS remote use "openkbs pull"`);
|
|
274
|
+
process.chdir(originalDir); // Change back to original directory
|
|
256
275
|
return;
|
|
257
276
|
}
|
|
258
277
|
|
|
@@ -262,14 +281,26 @@ async function cloneAction(kbId) {
|
|
|
262
281
|
await fetchAndSaveSettings({ kbId }, kbId, kbToken);
|
|
263
282
|
await downloadIcon(kbId);
|
|
264
283
|
await downloadFiles(['functions', 'frontend'], kbId, kbToken);
|
|
265
|
-
console.green(
|
|
284
|
+
console.green(`Cloning complete! Files created in directory: ${kbId}`);
|
|
266
285
|
} catch (error) {
|
|
267
286
|
console.error('Error during clone operation:', error.message);
|
|
287
|
+
// Make sure we return to the original directory in case of error
|
|
288
|
+
if (process.cwd() !== originalDir) {
|
|
289
|
+
process.chdir(originalDir);
|
|
290
|
+
}
|
|
291
|
+
} finally {
|
|
292
|
+
// Always ensure we return to the original directory
|
|
293
|
+
if (process.cwd() !== originalDir) {
|
|
294
|
+
process.chdir(originalDir);
|
|
295
|
+
}
|
|
268
296
|
}
|
|
269
297
|
}
|
|
270
298
|
|
|
271
299
|
async function createByTemplateAction(name) {
|
|
272
300
|
try {
|
|
301
|
+
// Download templates from S3 first
|
|
302
|
+
await downloadTemplates();
|
|
303
|
+
|
|
273
304
|
const targetDir = path.join(process.cwd(), name);
|
|
274
305
|
|
|
275
306
|
if (fs.existsSync(targetDir)) {
|
package/src/utils.js
CHANGED
|
@@ -10,7 +10,7 @@ const { exec } = require('child_process');
|
|
|
10
10
|
const readline = require('readline');
|
|
11
11
|
const Spinner = require('cli-spinner').Spinner;
|
|
12
12
|
|
|
13
|
-
const TEMPLATE_DIR = path.join(
|
|
13
|
+
const TEMPLATE_DIR = path.join(os.homedir(), '.openkbs', 'templates');
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Encrypts the given text using AES encryption with a passphrase.
|
|
@@ -1021,6 +1021,65 @@ async function buildLocalFilesMap(localDir, namespaces) {
|
|
|
1021
1021
|
return filesMap;
|
|
1022
1022
|
}
|
|
1023
1023
|
|
|
1024
|
+
async function downloadTemplates() {
|
|
1025
|
+
try {
|
|
1026
|
+
const templatesDir = TEMPLATE_DIR;
|
|
1027
|
+
await fs.ensureDir(templatesDir);
|
|
1028
|
+
|
|
1029
|
+
// Clear existing templates
|
|
1030
|
+
await fs.emptyDir(templatesDir);
|
|
1031
|
+
|
|
1032
|
+
// Download templates from S3
|
|
1033
|
+
await downloadTemplatesFromS3(templatesDir);
|
|
1034
|
+
|
|
1035
|
+
console.log('Templates downloaded successfully');
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
console.error('Error downloading templates:', error);
|
|
1038
|
+
throw error;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
async function downloadTemplatesFromS3(targetDir) {
|
|
1043
|
+
const s3Client = new S3Client({ region: 'us-east-1' });
|
|
1044
|
+
const { ListObjectsV2Command, GetObjectCommand } = require('@aws-sdk/client-s3');
|
|
1045
|
+
|
|
1046
|
+
const bucket = 'openkbs-downloads';
|
|
1047
|
+
const prefix = 'templates/';
|
|
1048
|
+
|
|
1049
|
+
// List all objects in templates folder
|
|
1050
|
+
const listResponse = await s3Client.send(new ListObjectsV2Command({
|
|
1051
|
+
Bucket: bucket,
|
|
1052
|
+
Prefix: prefix
|
|
1053
|
+
}));
|
|
1054
|
+
|
|
1055
|
+
// Download all files in parallel
|
|
1056
|
+
const downloadPromises = (listResponse.Contents || []).map(async (obj) => {
|
|
1057
|
+
const key = obj.Key;
|
|
1058
|
+
const relativePath = key.substring(prefix.length);
|
|
1059
|
+
|
|
1060
|
+
// Skip if it's a directory marker
|
|
1061
|
+
if (relativePath.endsWith('/')) return;
|
|
1062
|
+
|
|
1063
|
+
const localPath = path.join(targetDir, relativePath);
|
|
1064
|
+
|
|
1065
|
+
// Ensure directory exists
|
|
1066
|
+
await fs.ensureDir(path.dirname(localPath));
|
|
1067
|
+
|
|
1068
|
+
// Download file
|
|
1069
|
+
const response = await s3Client.send(new GetObjectCommand({
|
|
1070
|
+
Bucket: bucket,
|
|
1071
|
+
Key: key
|
|
1072
|
+
}));
|
|
1073
|
+
|
|
1074
|
+
const fileContent = await streamToBuffer(response.Body);
|
|
1075
|
+
await fs.writeFile(localPath, fileContent);
|
|
1076
|
+
console.log(`Downloaded: ${relativePath}`);
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
// Wait for all downloads to complete
|
|
1080
|
+
await Promise.all(downloadPromises);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1024
1083
|
const generateKey = (passphrase) => {
|
|
1025
1084
|
const salt = 'salt';
|
|
1026
1085
|
const iterations = 1000;
|
|
@@ -1054,6 +1113,9 @@ const replacePlaceholderInFiles = (dir, name) => {
|
|
|
1054
1113
|
|
|
1055
1114
|
async function initByTemplateAction(params) {
|
|
1056
1115
|
try {
|
|
1116
|
+
// Download templates from S3 first
|
|
1117
|
+
await downloadTemplates();
|
|
1118
|
+
|
|
1057
1119
|
const targetDir = process.cwd();
|
|
1058
1120
|
|
|
1059
1121
|
// Copy all files and folders, skipping existing ones
|
|
@@ -1077,5 +1139,6 @@ module.exports = {
|
|
|
1077
1139
|
KB_API_URL, AUTH_API_URL, decryptKBFields, fetchLocalKBData, fetchKBJWT, createAccountIdFromPublicKey, signPayload,
|
|
1078
1140
|
listFiles, getUserProfile, getKB, fetchAndSaveSettings, downloadIcon, downloadFiles, updateKB, uploadFiles, generateKey,
|
|
1079
1141
|
generateMnemonic, reset, bold, red, yellow, green, cyan, createKB, getClientJWT, saveLocalKBData, listKBs, deleteKBFile,
|
|
1080
|
-
deleteKB, buildPackage, replacePlaceholderInFiles, buildNodePackage, initByTemplateAction, modifyKB, listKBsSharedWithMe
|
|
1142
|
+
deleteKB, buildPackage, replacePlaceholderInFiles, buildNodePackage, initByTemplateAction, modifyKB, listKBsSharedWithMe,
|
|
1143
|
+
downloadTemplates
|
|
1081
1144
|
}
|