openkbs 0.0.51 → 0.0.53
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/README.md +28 -11
- package/package.json +1 -1
- package/src/actions.js +66 -16
- package/src/index.js +22 -2
- package/src/utils.js +1 -1
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/app/settings.json +1 -1
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/scripts/utils/agent_client.js +167 -71
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/actions.js +1 -11
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/handler.js +10 -14
- package/templates/.openkbs/knowledge/metadata.json +2 -2
- package/templates/CLAUDE.md +200 -25
- package/templates/README.md +4 -74
- package/templates/package.json +2 -4
- package/templates/scripts/run_job.js +3 -1
- package/templates/scripts/utils/agent_client.js +156 -70
- package/templates/src/Events/handler.js +1 -1
- package/version.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# OpenKBS · [](https://github.com/open-kbs/openkbs-chat/blob/main/LICENSE) [](https://www.npmjs.com/package/openkbs)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
OpenKBS is an extendable AI service designed to build,
|
|
4
5
|
deploy and integrate AI agents and applications.
|
|
5
6
|
|
|
6
7
|
## Table of Contents
|
|
@@ -9,10 +10,10 @@ deploy and integrate AI agents and applications.
|
|
|
9
10
|
- [Create App](#create-app)
|
|
10
11
|
- [Deploy](#deploy)
|
|
11
12
|
- [Extend Frontend](#extend-frontend)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
- [Chat Render](#chat-render)
|
|
14
|
+
- [Setup Local Development](#setup-local-development)
|
|
15
|
+
- [Use Built-in MUI Components](#use-built-in-mui-components)
|
|
16
|
+
- [AI-Powered Generation](#ai-powered-frontend-generation)
|
|
16
17
|
- [Extend Backend](#extend-backend)
|
|
17
18
|
- [Mobile & Desktop App](#mobile--desktop-app)
|
|
18
19
|
- [Framework Documentation](#framework-documentation)
|
|
@@ -373,6 +374,18 @@ export const getActions = (meta) => [
|
|
|
373
374
|
});
|
|
374
375
|
return { data: response, ...meta };
|
|
375
376
|
}],
|
|
377
|
+
|
|
378
|
+
// example: checkVAT("BG123456789")
|
|
379
|
+
[/\/?checkVAT\("(.*)"\)/, async (match) => {
|
|
380
|
+
let response = await openkbs.checkVAT(match[1]);
|
|
381
|
+
return { data: response, ...meta };
|
|
382
|
+
}],
|
|
383
|
+
|
|
384
|
+
// example: getExchangeRates("USD", "EUR,GBP")
|
|
385
|
+
[/\/?getExchangeRates\("(.*)"\s*,\s*"(.*)"\)/, async (match) => {
|
|
386
|
+
let response = await openkbs.getExchangeRates(match[1], match[2]);
|
|
387
|
+
return { data: response, ...meta };
|
|
388
|
+
}],
|
|
376
389
|
];
|
|
377
390
|
```
|
|
378
391
|
|
|
@@ -417,7 +430,7 @@ The `onPublicAPIRequest` handler serves as a bridge between publicly accessible
|
|
|
417
430
|
|
|
418
431
|
**How it works:**
|
|
419
432
|
|
|
420
|
-
1. **Public API Endpoint:** The `onPublicAPIRequest` handler is associated with a dedicated public API endpoint (e.g., `/publicAPIRequest`)
|
|
433
|
+
1. **Public API Endpoint:** The `onPublicAPIRequest` handler is associated with a dedicated public API endpoint (e.g., `/publicAPIRequest`)
|
|
421
434
|
2. **Payload** could be any JSON object
|
|
422
435
|
3. **Handler Logic:** Inside the `onPublicAPIRequest` handler, you receive this payload as an argument. Your code then processes the payload and performs the necessary actions using the OpenKBS SDK for example.
|
|
423
436
|
4. **Data Storage:** The `openkbs.items` function is typically used within this handler to create, update, or delete items in the OpenKBS NoSQL Items service. You can use encryption for sensitive data within this handler.
|
|
@@ -483,8 +496,8 @@ Remember to carefully consider security implications and implement necessary pre
|
|
|
483
496
|
|
|
484
497
|
#### onAddMessages Event Handler
|
|
485
498
|
|
|
486
|
-
The `onAddMessages` handler allows you to intercept and process messages *as they are added to the chat*.
|
|
487
|
-
This handler is triggered *after* the `onRequest` handler but *before* the message is sent to the LLM.
|
|
499
|
+
The `onAddMessages` handler allows you to intercept and process messages *as they are added to the chat*.
|
|
500
|
+
This handler is triggered *after* the `onRequest` handler but *before* the message is sent to the LLM.
|
|
488
501
|
It's particularly useful for scenarios where a third-party system or service sends messages directly to your OpenKBS app to perform an action.
|
|
489
502
|
Unlike `onPublicAPIRequest`, this handler requires an `apiKey`, which can be created in the 'Access' section of your OpenKBS app.
|
|
490
503
|
|
|
@@ -599,6 +612,10 @@ The `openkbs` object provides a set of utility functions and services to interac
|
|
|
599
612
|
|
|
600
613
|
* **`openkbs.sendMail(email, subject, content)`:** Sends an email to the specified recipient
|
|
601
614
|
|
|
615
|
+
* **`openkbs.checkVAT(vatNumber)`:** Validates a VAT number against official databases
|
|
616
|
+
|
|
617
|
+
* **`openkbs.getExchangeRates(base, symbols)`:** Retrieves current exchange rates
|
|
618
|
+
|
|
602
619
|
* **`openkbs.documentToText(documentURL, params)`:** Extracts text from various document formats.
|
|
603
620
|
|
|
604
621
|
* **`openkbs.imageToText(imageUrl, params)`:** Extracts text from an image.
|
|
@@ -671,7 +688,7 @@ This file contains essential configuration settings for the AI agent.
|
|
|
671
688
|
|
|
672
689
|
#### LLM Instructions
|
|
673
690
|
`app/instructions.txt`
|
|
674
|
-
This file contains the instructions for the LLM, guiding its behavior and interaction with custom functionalities.
|
|
691
|
+
This file contains the instructions for the LLM, guiding its behavior and interaction with custom functionalities.
|
|
675
692
|
Clear and specific instructions ensure the LLM effectively utilizes provided actions and commands.
|
|
676
693
|
|
|
677
694
|
**Example Instructions:**
|
|
@@ -1247,7 +1264,7 @@ This endpoint allows adding messages to a specified chat, which is useful for in
|
|
|
1247
1264
|
#### createPublicChatToken
|
|
1248
1265
|
|
|
1249
1266
|
This endpoint allows third-party systems (like WordPress, e-commerce platforms, etc.) to generate signed JWT tokens for their users to interact with the OpenKBS agent. This enables secure, limited access to the agent while maintaining user context and authorization.
|
|
1250
|
-
|
|
1267
|
+
|
|
1251
1268
|
- **Endpoint:** `https://chat.openkbs.com/`
|
|
1252
1269
|
- **Method:** `POST`
|
|
1253
1270
|
- **Request Body (JSON):**
|
|
@@ -1370,4 +1387,4 @@ We welcome contributions from the community! Please feel free to submit issues,
|
|
|
1370
1387
|
|
|
1371
1388
|
## Contact
|
|
1372
1389
|
|
|
1373
|
-
For more information, visit our [official website](https://openkbs.com) or join our community discussions on [GitHub](https://github.com/open-kbs/openkbs/discussions).
|
|
1390
|
+
For more information, visit our [official website](https://openkbs.com) or join our community discussions on [GitHub](https://github.com/open-kbs/openkbs/discussions).
|
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -10,7 +10,7 @@ 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, downloadTemplates
|
|
13
|
+
listKBsSharedWithMe, downloadTemplates, KB_API_URL, makePostRequest
|
|
14
14
|
} = require("./utils");
|
|
15
15
|
|
|
16
16
|
const TEMPLATE_DIR = path.join(os.homedir(), '.openkbs', 'templates');
|
|
@@ -138,10 +138,10 @@ function isModulePresent(moduleName) {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
async function deployAction(moduleName) {
|
|
141
|
-
const validModules = ['contentRender', 'onRequest', 'onResponse', 'onAddMessages', 'onPublicAPIRequest'];
|
|
141
|
+
const validModules = ['contentRender', 'onRequest', 'onResponse', 'onAddMessages', 'onPublicAPIRequest', 'onCronjob'];
|
|
142
142
|
|
|
143
143
|
if (moduleName && !validModules.includes(moduleName)) {
|
|
144
|
-
return console.error(`Invalid module name ${moduleName} (valid options: 'contentRender', 'onRequest', 'onResponse', 'onAddMessages', 'onPublicAPIRequest')`);
|
|
144
|
+
return console.error(`Invalid module name ${moduleName} (valid options: 'contentRender', 'onRequest', 'onResponse', 'onAddMessages', 'onPublicAPIRequest', 'onCronjob')`);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
const modulesToDeploy = moduleName ? [moduleName] : validModules.filter(isModulePresent);
|
|
@@ -217,7 +217,7 @@ async function pushAction(location = 'origin', targetFile, options) {
|
|
|
217
217
|
await deployAction();
|
|
218
218
|
console.green(`KB update complete: All changes have been successfully uploaded to https://${kbId}.apps.openkbs.com`);
|
|
219
219
|
} else if (location === 'localstack') {
|
|
220
|
-
const modulesToDeploy = ['onRequest', 'onResponse', 'onAddMessages', 'onPublicAPIRequest'].filter(isModulePresent);
|
|
220
|
+
const modulesToDeploy = ['onRequest', 'onResponse', 'onAddMessages', 'onPublicAPIRequest', 'onCronjob'].filter(isModulePresent);
|
|
221
221
|
|
|
222
222
|
const originalDir = process.cwd();
|
|
223
223
|
for (const module of modulesToDeploy) {
|
|
@@ -618,18 +618,18 @@ async function updateCliAction() {
|
|
|
618
618
|
try {
|
|
619
619
|
const packageJson = require('../package.json');
|
|
620
620
|
const currentVersion = packageJson.version;
|
|
621
|
-
|
|
621
|
+
|
|
622
622
|
console.log(`Current OpenKBS CLI version: ${currentVersion}`);
|
|
623
623
|
console.log('Checking for updates...');
|
|
624
|
-
|
|
624
|
+
|
|
625
625
|
// Check remote version from S3
|
|
626
626
|
const https = require('https');
|
|
627
627
|
const bucket = 'openkbs-downloads';
|
|
628
628
|
const versionMetadataKey = 'cli/version.json';
|
|
629
|
-
|
|
629
|
+
|
|
630
630
|
let remoteVersionData = null;
|
|
631
631
|
let cliUpdateAvailable = false;
|
|
632
|
-
|
|
632
|
+
|
|
633
633
|
try {
|
|
634
634
|
const fileUrl = `https://${bucket}.s3.amazonaws.com/${versionMetadataKey}`;
|
|
635
635
|
const remoteVersionContent = await new Promise((resolve, reject) => {
|
|
@@ -643,25 +643,25 @@ async function updateCliAction() {
|
|
|
643
643
|
res.on('end', () => resolve(data));
|
|
644
644
|
}).on('error', reject);
|
|
645
645
|
});
|
|
646
|
-
|
|
646
|
+
|
|
647
647
|
remoteVersionData = JSON.parse(remoteVersionContent);
|
|
648
648
|
const remoteVersion = remoteVersionData.version;
|
|
649
|
-
|
|
649
|
+
|
|
650
650
|
// Compare versions using semantic versioning
|
|
651
651
|
if (compareVersions(currentVersion, remoteVersion) < 0) {
|
|
652
652
|
cliUpdateAvailable = true;
|
|
653
653
|
console.log(`New CLI version available: ${remoteVersion}`);
|
|
654
654
|
console.log('Updating automatically...');
|
|
655
|
-
|
|
655
|
+
|
|
656
656
|
// Spawn npm update as detached child process
|
|
657
657
|
const { spawn } = require('child_process');
|
|
658
658
|
const updateProcess = spawn('npm', ['update', '-g', 'openkbs'], {
|
|
659
659
|
detached: true,
|
|
660
660
|
stdio: 'inherit'
|
|
661
661
|
});
|
|
662
|
-
|
|
662
|
+
|
|
663
663
|
updateProcess.unref(); // Allow parent to exit
|
|
664
|
-
|
|
664
|
+
|
|
665
665
|
console.green(`Update started! OpenKBS CLI will be updated to version ${remoteVersion}.`);
|
|
666
666
|
console.log('The update will complete in the background.');
|
|
667
667
|
} else {
|
|
@@ -670,15 +670,63 @@ async function updateCliAction() {
|
|
|
670
670
|
} catch (error) {
|
|
671
671
|
console.red('Error fetching CLI version metadata:', error.message);
|
|
672
672
|
}
|
|
673
|
-
|
|
673
|
+
|
|
674
674
|
// Also update knowledge base silently if it exists
|
|
675
675
|
await updateKnowledgeAction(true);
|
|
676
|
-
|
|
676
|
+
|
|
677
677
|
} catch (error) {
|
|
678
678
|
console.red('Error updating CLI:', error.message);
|
|
679
679
|
}
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
+
async function publishAction(domain) {
|
|
683
|
+
try {
|
|
684
|
+
const localKBData = await fetchLocalKBData();
|
|
685
|
+
const { kbId } = localKBData;
|
|
686
|
+
if (!kbId) return console.red('No KB found. Please push the KB first using the command "openkbs push".');
|
|
687
|
+
|
|
688
|
+
console.log(`Publishing KB ${kbId} to domain ${domain}...`);
|
|
689
|
+
const res = await fetchKBJWT(kbId);
|
|
690
|
+
|
|
691
|
+
if (!res?.kbToken) return console.red(`KB ${kbId} does not exist on the remote service`);
|
|
692
|
+
|
|
693
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
694
|
+
token: res.kbToken,
|
|
695
|
+
action: 'publish',
|
|
696
|
+
domain: domain
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
console.green(`KB ${kbId} successfully published to ${domain}`);
|
|
700
|
+
return response;
|
|
701
|
+
} catch (error) {
|
|
702
|
+
console.red('Error during publish operation:', error.message);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async function unpublishAction(domain) {
|
|
707
|
+
try {
|
|
708
|
+
const localKBData = await fetchLocalKBData();
|
|
709
|
+
const { kbId } = localKBData;
|
|
710
|
+
if (!kbId) return console.red('No KB found. Please push the KB first using the command "openkbs push".');
|
|
711
|
+
|
|
712
|
+
console.log(`Unpublishing KB ${kbId} from domain ${domain}...`);
|
|
713
|
+
const res = await fetchKBJWT(kbId);
|
|
714
|
+
|
|
715
|
+
if (!res?.kbToken) return console.red(`KB ${kbId} does not exist on the remote service`);
|
|
716
|
+
|
|
717
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
718
|
+
token: res.kbToken,
|
|
719
|
+
action: 'unpublish',
|
|
720
|
+
domain: domain
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
console.green(`KB ${kbId} successfully unpublished from ${domain}`);
|
|
724
|
+
return response;
|
|
725
|
+
} catch (error) {
|
|
726
|
+
console.red('Error during unpublish operation:', error.message);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
682
730
|
function compareVersions(version1, version2) {
|
|
683
731
|
const parts1 = version1.split('.').map(Number);
|
|
684
732
|
const parts2 = version2.split('.').map(Number);
|
|
@@ -807,5 +855,7 @@ module.exports = {
|
|
|
807
855
|
modifyAction,
|
|
808
856
|
downloadModifyAction,
|
|
809
857
|
updateKnowledgeAction,
|
|
810
|
-
updateCliAction
|
|
858
|
+
updateCliAction,
|
|
859
|
+
publishAction,
|
|
860
|
+
unpublishAction
|
|
811
861
|
};
|
package/src/index.js
CHANGED
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
deleteFileAction,
|
|
14
14
|
describeAction, deployAction, createByTemplateAction, initByTemplateAction,
|
|
15
15
|
logoutAction, installFrontendPackageAction, modifyAction, downloadModifyAction,
|
|
16
|
-
updateKnowledgeAction, updateCliAction
|
|
16
|
+
updateKnowledgeAction, updateCliAction, publishAction, unpublishAction
|
|
17
17
|
} = require('./actions');
|
|
18
18
|
|
|
19
19
|
|
|
@@ -171,10 +171,30 @@ program
|
|
|
171
171
|
Examples:
|
|
172
172
|
$ openkbs update
|
|
173
173
|
This will check for CLI updates and install them if available.
|
|
174
|
-
|
|
174
|
+
|
|
175
175
|
$ openkbs update knowledge
|
|
176
176
|
This will check if your local .openkbs/knowledge directory is up to date with the remote repository
|
|
177
177
|
and update it if necessary.
|
|
178
178
|
`);
|
|
179
179
|
|
|
180
|
+
program
|
|
181
|
+
.command('publish <domain>')
|
|
182
|
+
.description('Publish KB to a custom domain')
|
|
183
|
+
.action(publishAction)
|
|
184
|
+
.addHelpText('after', `
|
|
185
|
+
Examples:
|
|
186
|
+
$ openkbs publish example.com
|
|
187
|
+
This will publish your KB to the domain example.com
|
|
188
|
+
`);
|
|
189
|
+
|
|
190
|
+
program
|
|
191
|
+
.command('unpublish <domain>')
|
|
192
|
+
.description('Unpublish KB from a custom domain')
|
|
193
|
+
.action(unpublishAction)
|
|
194
|
+
.addHelpText('after', `
|
|
195
|
+
Examples:
|
|
196
|
+
$ openkbs unpublish example.com
|
|
197
|
+
This will unpublish your KB from the domain example.com
|
|
198
|
+
`);
|
|
199
|
+
|
|
180
200
|
program.parse(process.argv);
|
package/src/utils.js
CHANGED
|
@@ -1167,5 +1167,5 @@ module.exports = {
|
|
|
1167
1167
|
listFiles, getUserProfile, getKB, fetchAndSaveSettings, downloadIcon, downloadFiles, updateKB, uploadFiles, generateKey,
|
|
1168
1168
|
generateMnemonic, reset, bold, red, yellow, green, cyan, createKB, getClientJWT, saveLocalKBData, listKBs, deleteKBFile,
|
|
1169
1169
|
deleteKB, buildPackage, replacePlaceholderInFiles, buildNodePackage, initByTemplateAction, modifyKB, listKBsSharedWithMe,
|
|
1170
|
-
downloadTemplates
|
|
1170
|
+
downloadTemplates, makePostRequest
|
|
1171
1171
|
}
|
package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/scripts/utils/agent_client.js
CHANGED
|
@@ -2,6 +2,7 @@ const https = require('https');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const readline = require('readline');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { URL } = require('url');
|
|
5
6
|
|
|
6
7
|
class OpenKBSAgentClient {
|
|
7
8
|
constructor() {
|
|
@@ -12,7 +13,7 @@ class OpenKBSAgentClient {
|
|
|
12
13
|
|
|
13
14
|
findSettings() {
|
|
14
15
|
let currentDir = __dirname;
|
|
15
|
-
|
|
16
|
+
|
|
16
17
|
while (currentDir !== path.parse(currentDir).root) {
|
|
17
18
|
const settingsPath = path.join(currentDir, 'app', 'settings.json');
|
|
18
19
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -20,13 +21,13 @@ class OpenKBSAgentClient {
|
|
|
20
21
|
}
|
|
21
22
|
currentDir = path.dirname(currentDir);
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
|
|
24
25
|
throw new Error('Could not find app/settings.json in parent directories');
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
findSecretsPath() {
|
|
28
29
|
let currentDir = __dirname;
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
while (currentDir !== path.parse(currentDir).root) {
|
|
31
32
|
const settingsPath = path.join(currentDir, 'app', 'settings.json');
|
|
32
33
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -35,24 +36,28 @@ class OpenKBSAgentClient {
|
|
|
35
36
|
}
|
|
36
37
|
currentDir = path.dirname(currentDir);
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
throw new Error('Could not find agent directory with app/settings.json');
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
async getApiKey() {
|
|
43
44
|
if (this.apiKey) return this.apiKey;
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
if (fs.existsSync(this.secretsPath)) {
|
|
46
47
|
const secrets = JSON.parse(fs.readFileSync(this.secretsPath, 'utf8'));
|
|
47
48
|
this.apiKey = secrets.apiKey;
|
|
48
49
|
return this.apiKey;
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
+
|
|
51
52
|
this.apiKey = await this.promptForApiKey();
|
|
52
53
|
this.saveApiKey(this.apiKey);
|
|
53
54
|
return this.apiKey;
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
async init() {
|
|
58
|
+
await this.getApiKey();
|
|
59
|
+
}
|
|
60
|
+
|
|
56
61
|
async promptForApiKey() {
|
|
57
62
|
return new Promise((resolve) => {
|
|
58
63
|
const rl = readline.createInterface({
|
|
@@ -67,7 +72,17 @@ class OpenKBSAgentClient {
|
|
|
67
72
|
resolve(key);
|
|
68
73
|
});
|
|
69
74
|
|
|
70
|
-
rl._writeToOutput = (str) =>
|
|
75
|
+
rl._writeToOutput = (str) => {
|
|
76
|
+
if (str === '\n' || str === '\r\n') {
|
|
77
|
+
rl.output.write(str);
|
|
78
|
+
} else if (str.match(/[\x08\x7f]/)) {
|
|
79
|
+
rl.output.write(str);
|
|
80
|
+
} else if (rl.line && str.length === 1) {
|
|
81
|
+
rl.output.write('*');
|
|
82
|
+
} else {
|
|
83
|
+
rl.output.write(str);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
71
86
|
});
|
|
72
87
|
}
|
|
73
88
|
|
|
@@ -79,89 +94,170 @@ class OpenKBSAgentClient {
|
|
|
79
94
|
fs.writeFileSync(this.secretsPath, JSON.stringify({ apiKey: key }, null, 2));
|
|
80
95
|
}
|
|
81
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Run a job and get response from the agent
|
|
99
|
+
*
|
|
100
|
+
* @param {string|Array} message - Either:
|
|
101
|
+
* - String: "Do something ..."
|
|
102
|
+
* - Array: [
|
|
103
|
+
* {type:"text", text:"Process this invoice"},
|
|
104
|
+
* {type:"image_url", image_url:{url:"https://files.openkbs.com/invoice.png"}}
|
|
105
|
+
* ]
|
|
106
|
+
*
|
|
107
|
+
* @returns {Promise<Object>} Response structure:
|
|
108
|
+
* {
|
|
109
|
+
* data: {
|
|
110
|
+
* type: 'TEXT' | 'CUSTOM_TYPE',
|
|
111
|
+
* content: '...' | data: {...}
|
|
112
|
+
* },
|
|
113
|
+
* chatId: 'xxx-xxx',
|
|
114
|
+
* msgId: 'msg_xxx'
|
|
115
|
+
* }
|
|
116
|
+
*/
|
|
82
117
|
async runJob(message, options = {}) {
|
|
83
118
|
const apiKey = await this.getApiKey();
|
|
84
|
-
|
|
85
|
-
if (!this.settings.kbId) {
|
|
86
|
-
throw new Error('First use: "openkbs push" to create the agent');
|
|
87
|
-
}
|
|
119
|
+
if (!this.settings.kbId) throw new Error('First use: "openkbs push" to create the agent');
|
|
88
120
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
121
|
+
const payload = { message };
|
|
122
|
+
Object.keys(options).forEach(key => {
|
|
123
|
+
if (key === 'historyLimit') {
|
|
124
|
+
payload[key] = Math.min(Math.max(1, options[key]), 100);
|
|
125
|
+
} else if (options[key] !== undefined) {
|
|
126
|
+
payload[key] = options[key];
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const response = await this.request(
|
|
131
|
+
`https://${this.settings.kbId}.apps.openkbs.com/api`,
|
|
132
|
+
payload,
|
|
133
|
+
{ Authorization: `Bearer ${apiKey}` }
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (response.chatId) this.lastChatId = response.chatId;
|
|
137
|
+
return response;
|
|
99
138
|
}
|
|
100
139
|
|
|
101
|
-
async
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
140
|
+
async continueChat(message, chatId = null, options = {}) {
|
|
141
|
+
const targetChatId = chatId || this.lastChatId;
|
|
142
|
+
if (!targetChatId) throw new Error('No chatId provided and no previous chat to continue');
|
|
143
|
+
|
|
144
|
+
return this.runJob(message, {
|
|
145
|
+
...options,
|
|
146
|
+
chatId: targetChatId,
|
|
147
|
+
includeHistory: options.includeHistory !== false
|
|
106
148
|
});
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
return JSON.parse(response)[0].createdChatId;
|
|
110
|
-
} catch (error) {
|
|
111
|
-
if (fs.existsSync(this.secretsPath)) {
|
|
112
|
-
fs.unlinkSync(this.secretsPath);
|
|
113
|
-
}
|
|
114
|
-
throw new Error('Authentication failed.');
|
|
115
|
-
}
|
|
116
149
|
}
|
|
117
150
|
|
|
118
|
-
|
|
151
|
+
async uploadFile(filePath, options = {}) {
|
|
152
|
+
const apiKey = await this.getApiKey();
|
|
153
|
+
if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
154
|
+
|
|
155
|
+
const fileContent = fs.readFileSync(filePath);
|
|
156
|
+
const ext = path.extname(filePath);
|
|
157
|
+
const fileName = options.fileName || `file-${Date.now()}-${Math.random().toString(36).substring(7)}${ext}`;
|
|
158
|
+
const fileType = options.fileType || this.getMimeType(filePath);
|
|
159
|
+
|
|
160
|
+
const presignedResponse = await this.request('https://kb.openkbs.com/', {
|
|
161
|
+
apiKey,
|
|
162
|
+
kbId: this.settings.kbId,
|
|
163
|
+
namespace: 'files',
|
|
164
|
+
presignedOperation: 'putObject',
|
|
165
|
+
action: 'createPresignedURL',
|
|
166
|
+
fileName,
|
|
167
|
+
fileType
|
|
168
|
+
}, { Origin: `https://${this.settings.kbId}.apps.openkbs.com` });
|
|
169
|
+
|
|
170
|
+
const presignedUrl = presignedResponse.presignedUrl || presignedResponse;
|
|
171
|
+
|
|
172
|
+
await this.requestRaw(presignedUrl, fileContent, {
|
|
173
|
+
'Content-Type': fileType,
|
|
174
|
+
'Content-Length': fileContent.length
|
|
175
|
+
}, 'PUT');
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
fileName,
|
|
179
|
+
uploaded: true,
|
|
180
|
+
url: `https://file.openkbs.com/files/${this.settings.kbId}/${fileName}`
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getMimeType(filePath) {
|
|
185
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
186
|
+
const mimeTypes = {
|
|
187
|
+
'.pdf': 'application/pdf',
|
|
188
|
+
'.txt': 'text/plain',
|
|
189
|
+
'.json': 'application/json',
|
|
190
|
+
'.jpg': 'image/jpeg',
|
|
191
|
+
'.jpeg': 'image/jpeg',
|
|
192
|
+
'.png': 'image/png',
|
|
193
|
+
'.gif': 'image/gif',
|
|
194
|
+
'.doc': 'application/msword',
|
|
195
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
196
|
+
'.xls': 'application/vnd.ms-excel',
|
|
197
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
198
|
+
'.csv': 'text/csv',
|
|
199
|
+
'.html': 'text/html',
|
|
200
|
+
'.xml': 'application/xml',
|
|
201
|
+
'.zip': 'application/zip'
|
|
202
|
+
};
|
|
203
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Unified HTTP request helper
|
|
207
|
+
async request(url, payload, headers = {}, method = 'POST') {
|
|
119
208
|
return new Promise((resolve, reject) => {
|
|
120
|
-
const { hostname, pathname } = new URL(url);
|
|
209
|
+
const { hostname, pathname, search } = new URL(url);
|
|
121
210
|
const req = https.request({
|
|
122
|
-
hostname,
|
|
123
|
-
path: pathname,
|
|
124
|
-
method
|
|
125
|
-
headers: { 'Content-Type': 'application/json' }
|
|
211
|
+
hostname,
|
|
212
|
+
path: pathname + (search || ''),
|
|
213
|
+
method,
|
|
214
|
+
headers: { 'Content-Type': 'application/json', ...headers }
|
|
126
215
|
}, res => {
|
|
127
216
|
let data = '';
|
|
128
217
|
res.on('data', chunk => data += chunk);
|
|
129
|
-
res.on('end', () =>
|
|
218
|
+
res.on('end', () => {
|
|
219
|
+
try {
|
|
220
|
+
const result = JSON.parse(data);
|
|
221
|
+
if (res.statusCode === 401) {
|
|
222
|
+
if (fs.existsSync(this.secretsPath)) fs.unlinkSync(this.secretsPath);
|
|
223
|
+
reject(new Error('Authentication failed. Please run "node scripts/run_job.js init" to reconfigure.'));
|
|
224
|
+
} else if (res.statusCode !== 200) {
|
|
225
|
+
reject(new Error(`Request failed with status ${res.statusCode}: ${data}`));
|
|
226
|
+
} else {
|
|
227
|
+
resolve(result);
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
reject(new Error(`Failed to parse response: ${error.message}`));
|
|
231
|
+
}
|
|
232
|
+
});
|
|
130
233
|
}).on('error', reject);
|
|
131
234
|
|
|
132
|
-
req.write(JSON.stringify(payload));
|
|
235
|
+
req.write(typeof payload === 'string' ? payload : JSON.stringify(payload));
|
|
133
236
|
req.end();
|
|
134
237
|
});
|
|
135
238
|
}
|
|
136
239
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
resolve(result);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
})
|
|
163
|
-
.catch(console.error);
|
|
164
|
-
}, 1000);
|
|
240
|
+
// Raw request for binary data
|
|
241
|
+
async requestRaw(url, data, headers = {}, method = 'PUT') {
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
const { hostname, pathname, search } = new URL(url);
|
|
244
|
+
const req = https.request({
|
|
245
|
+
hostname,
|
|
246
|
+
path: pathname + (search || ''),
|
|
247
|
+
method,
|
|
248
|
+
headers
|
|
249
|
+
}, res => {
|
|
250
|
+
if (res.statusCode === 200 || res.statusCode === 204) {
|
|
251
|
+
resolve();
|
|
252
|
+
} else {
|
|
253
|
+
let responseData = '';
|
|
254
|
+
res.on('data', chunk => responseData += chunk);
|
|
255
|
+
res.on('end', () => reject(new Error(`Request failed with status ${res.statusCode}: ${responseData}`)));
|
|
256
|
+
}
|
|
257
|
+
}).on('error', reject);
|
|
258
|
+
|
|
259
|
+
req.write(data);
|
|
260
|
+
req.end();
|
|
165
261
|
});
|
|
166
262
|
}
|
|
167
263
|
}
|
|
@@ -16,6 +16,7 @@ const extractJSONFromText = (text) => {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export const getActions = () => [
|
|
19
|
+
// IMPORTANT: Actions returning JOB_COMPLETED or JOB_FAILED stop agent execution and return final result
|
|
19
20
|
[/[\s\S]*"type"\s*:\s*"JOB_COMPLETED"[\s\S]*/, async (match, event) => {
|
|
20
21
|
const parsedData = extractJSONFromText(match[0]);
|
|
21
22
|
if (parsedData && parsedData.type === "JOB_COMPLETED") {
|
|
@@ -98,11 +99,6 @@ export const getActions = () => [
|
|
|
98
99
|
[/\/?webpageToText\("(.*)"\)/, async (match) => {
|
|
99
100
|
try {
|
|
100
101
|
let response = await openkbs.webpageToText(match[1]);
|
|
101
|
-
|
|
102
|
-
// limit output length
|
|
103
|
-
if (response?.content?.length > 5000) {
|
|
104
|
-
response.content = response.content.substring(0, 5000);
|
|
105
|
-
}
|
|
106
102
|
if(!response?.url) return { data: { error: "Unable to read website" } };
|
|
107
103
|
return { data: response };
|
|
108
104
|
} catch (e) {
|
|
@@ -113,12 +109,6 @@ export const getActions = () => [
|
|
|
113
109
|
[/\/?documentToText\("(.*)"\)/, async (match) => {
|
|
114
110
|
try {
|
|
115
111
|
let response = await openkbs.documentToText(match[1]);
|
|
116
|
-
|
|
117
|
-
// limit output length
|
|
118
|
-
if (response?.text?.length > 5000) {
|
|
119
|
-
response.text = response.text.substring(0, 5000);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
112
|
return { data: response };
|
|
123
113
|
} catch (e) {
|
|
124
114
|
return { error: e.response.data };
|