openkbs 0.0.36 → 0.0.38
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/package.json +2 -2
- package/scripts/deploy-templates.js +8 -28
- package/src/actions.js +30 -1
- package/src/utils.js +0 -1
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/app/icon.png +0 -0
- package/templates/.openkbs/knowledge/examples/{ai-copywriter → ai-copywriter-agent}/app/instructions.txt +1 -1
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/app/settings.json +6 -0
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/scripts/run_job.js +26 -0
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/scripts/utils/agent_client.js +169 -0
- package/templates/.openkbs/knowledge/examples/{ai-copywriter → ai-copywriter-agent}/src/Events/actions.js +10 -21
- package/templates/.openkbs/knowledge/examples/{ai-copywriter/src/Events/onResponse.js → ai-copywriter-agent/src/Events/handler.js} +6 -10
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/onRequest.js +3 -0
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/onRequest.json +5 -0
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/onResponse.js +3 -0
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/onResponse.json +5 -0
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Frontend/contentRender.js +64 -0
- package/templates/.openkbs/knowledge/examples/{ai-copywriter → ai-copywriter-agent}/src/Frontend/contentRender.json +2 -1
- package/templates/.openkbs/knowledge/metadata.json +1 -1
- package/templates/CLAUDE.md +107 -13
- package/templates/app/instructions.txt +1 -10
- package/templates/app/settings.json +2 -2
- package/templates/related-agents/README.md +6 -0
- package/templates/scripts/run_job.js +26 -0
- package/templates/scripts/utils/agent_client.js +169 -0
- package/templates/src/Events/actions.js +8 -10
- package/templates/src/Events/handler.js +55 -0
- package/templates/src/Events/onRequest.js +2 -13
- package/templates/src/Events/onRequest.json +5 -0
- package/templates/src/Events/onResponse.js +2 -12
- package/templates/{.openkbs/knowledge/examples/ai-copywriter/src → src}/Events/onResponse.json +1 -0
- package/templates/.openkbs/knowledge/README.md +0 -34
- package/templates/.openkbs/knowledge/examples/ai-copywriter/app/icon.png +0 -0
- package/templates/.openkbs/knowledge/examples/ai-copywriter/app/settings.json +0 -13
- package/templates/.openkbs/knowledge/examples/ai-copywriter/run_agent.js +0 -120
- package/templates/.openkbs/knowledge/examples/ai-copywriter/src/Frontend/contentRender.js +0 -26
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openkbs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.38",
|
|
4
4
|
"description": "OpenKBS - Command Line Interface",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "pkg .",
|
|
8
|
-
"deploy": "npm run build && npm run deploy:linux && npm run deploy:macos && npm run deploy:win",
|
|
8
|
+
"deploy": "npm run build && npm run deploy:linux && npm run deploy:macos && npm run deploy:win && npm run deploy:templates",
|
|
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",
|
|
@@ -1,41 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
|
|
6
5
|
|
|
7
|
-
const s3Client = new S3Client({ region: 'us-east-1' });
|
|
8
6
|
const BUCKET = 'openkbs-downloads';
|
|
9
7
|
const TEMPLATES_DIR = path.join(__dirname, '../templates');
|
|
10
8
|
|
|
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
9
|
async function deployTemplates() {
|
|
33
10
|
try {
|
|
34
|
-
console.log('
|
|
35
|
-
|
|
36
|
-
|
|
11
|
+
console.log('Syncing templates to S3...');
|
|
12
|
+
|
|
13
|
+
const syncCommand = `aws s3 sync "${TEMPLATES_DIR}" s3://${BUCKET}/templates --delete`;
|
|
14
|
+
execSync(syncCommand, { stdio: 'inherit' });
|
|
15
|
+
|
|
16
|
+
console.log('Templates synced successfully!');
|
|
37
17
|
} catch (error) {
|
|
38
|
-
console.error('Error
|
|
18
|
+
console.error('Error syncing templates:', error);
|
|
39
19
|
process.exit(1);
|
|
40
20
|
}
|
|
41
21
|
}
|
package/src/actions.js
CHANGED
|
@@ -542,6 +542,7 @@ async function updateKnowledgeAction() {
|
|
|
542
542
|
try {
|
|
543
543
|
const knowledgeDir = path.join(process.cwd(), '.openkbs', 'knowledge');
|
|
544
544
|
const metadataPath = path.join(knowledgeDir, 'metadata.json');
|
|
545
|
+
const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
|
|
545
546
|
|
|
546
547
|
// Check if .openkbs/knowledge directory exists
|
|
547
548
|
if (!fs.existsSync(knowledgeDir)) {
|
|
@@ -593,7 +594,10 @@ async function updateKnowledgeAction() {
|
|
|
593
594
|
// Download updated knowledge files from S3
|
|
594
595
|
await downloadKnowledgeFromS3(knowledgeDir);
|
|
595
596
|
|
|
596
|
-
|
|
597
|
+
// Download CLAUDE.md file from S3
|
|
598
|
+
await downloadClaudeMdFromS3(claudeMdPath, s3Client, bucket);
|
|
599
|
+
|
|
600
|
+
console.green('Knowledge base and CLAUDE.md updated successfully!');
|
|
597
601
|
|
|
598
602
|
} catch (error) {
|
|
599
603
|
console.red('Error updating knowledge base:', error.message);
|
|
@@ -651,6 +655,31 @@ async function downloadKnowledgeFromS3(targetDir) {
|
|
|
651
655
|
}
|
|
652
656
|
}
|
|
653
657
|
|
|
658
|
+
async function downloadClaudeMdFromS3(claudeMdPath, s3Client, bucket) {
|
|
659
|
+
const claudeMdKey = 'templates/CLAUDE.md';
|
|
660
|
+
|
|
661
|
+
try {
|
|
662
|
+
// Download CLAUDE.md file from S3
|
|
663
|
+
const response = await s3Client.send(new GetObjectCommand({
|
|
664
|
+
Bucket: bucket,
|
|
665
|
+
Key: claudeMdKey
|
|
666
|
+
}));
|
|
667
|
+
|
|
668
|
+
const fileContent = await response.Body.transformToByteArray();
|
|
669
|
+
await fs.writeFile(claudeMdPath, fileContent);
|
|
670
|
+
|
|
671
|
+
// console.log('Downloaded: CLAUDE.md');
|
|
672
|
+
|
|
673
|
+
} catch (error) {
|
|
674
|
+
if (error.name === 'NoSuchKey') {
|
|
675
|
+
console.yellow('CLAUDE.md not found in remote repository, skipping...');
|
|
676
|
+
} else {
|
|
677
|
+
console.red('Error downloading CLAUDE.md from S3:', error.message);
|
|
678
|
+
throw error;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
654
683
|
module.exports = {
|
|
655
684
|
signAction,
|
|
656
685
|
loginAction,
|
package/src/utils.js
CHANGED
|
@@ -1080,7 +1080,6 @@ async function downloadTemplatesFromS3(targetDir) {
|
|
|
1080
1080
|
|
|
1081
1081
|
const fileContent = await streamToBuffer(response.Body);
|
|
1082
1082
|
await fs.writeFile(localPath, fileContent);
|
|
1083
|
-
console.log(relativePath);
|
|
1084
1083
|
});
|
|
1085
1084
|
|
|
1086
1085
|
// Wait for all downloads to complete
|
|
@@ -7,7 +7,7 @@ Job:
|
|
|
7
7
|
- If you see additional instructions in PROCESS_PRODUCT, execute them.
|
|
8
8
|
- When you have obtained all the necessary information, output JOB_COMPLETED response.
|
|
9
9
|
|
|
10
|
-
Guidelines:
|
|
10
|
+
Important Guidelines:
|
|
11
11
|
- use webpageToText command to read websites content in details
|
|
12
12
|
- Always output one command per response and wait for the response before continuing
|
|
13
13
|
- If an API call fails, so that you can't extract a required data, retry with different website or search query
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// This boilerplate code is a starting point for development.
|
|
2
|
+
|
|
3
|
+
const OpenKBSAgentClient = require('./utils/agent_client');
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const client = new OpenKBSAgentClient();
|
|
7
|
+
|
|
8
|
+
const message = `Today's Date: ${new Date().toLocaleDateString()}
|
|
9
|
+
|
|
10
|
+
PROCESS_PRODUCT:
|
|
11
|
+
Product Name: iPhone 14 Pro Max
|
|
12
|
+
Product Code: MQ9X3RX/A
|
|
13
|
+
ID: 97649
|
|
14
|
+
|
|
15
|
+
find at least 2 images and 2 videos
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = await client.runJob(message);
|
|
20
|
+
console.log('Job completed:', result);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Job failed:', error.message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
main();
|
package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/scripts/utils/agent_client.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
class OpenKBSAgentClient {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.settings = this.findSettings();
|
|
9
|
+
this.secretsPath = this.findSecretsPath();
|
|
10
|
+
this.apiKey = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
findSettings() {
|
|
14
|
+
let currentDir = __dirname;
|
|
15
|
+
|
|
16
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
17
|
+
const settingsPath = path.join(currentDir, 'app', 'settings.json');
|
|
18
|
+
if (fs.existsSync(settingsPath)) {
|
|
19
|
+
return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
currentDir = path.dirname(currentDir);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throw new Error('Could not find app/settings.json in parent directories');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
findSecretsPath() {
|
|
28
|
+
let currentDir = __dirname;
|
|
29
|
+
|
|
30
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
31
|
+
const settingsPath = path.join(currentDir, 'app', 'settings.json');
|
|
32
|
+
if (fs.existsSync(settingsPath)) {
|
|
33
|
+
const secretsPath = path.join(currentDir, '.openkbs', 'secrets.json');
|
|
34
|
+
return secretsPath;
|
|
35
|
+
}
|
|
36
|
+
currentDir = path.dirname(currentDir);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new Error('Could not find agent directory with app/settings.json');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getApiKey() {
|
|
43
|
+
if (this.apiKey) return this.apiKey;
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(this.secretsPath)) {
|
|
46
|
+
const secrets = JSON.parse(fs.readFileSync(this.secretsPath, 'utf8'));
|
|
47
|
+
this.apiKey = secrets.apiKey;
|
|
48
|
+
return this.apiKey;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.apiKey = await this.promptForApiKey();
|
|
52
|
+
this.saveApiKey(this.apiKey);
|
|
53
|
+
return this.apiKey;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async promptForApiKey() {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const rl = readline.createInterface({
|
|
59
|
+
input: process.stdin,
|
|
60
|
+
output: process.stdout
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
console.log(`Please generate an API key from: https://${this.settings.kbId}.apps.openkbs.com/?tab=access&createAPIKey=api-${+new Date()}`);
|
|
64
|
+
|
|
65
|
+
rl.question('Enter your API key: ', (key) => {
|
|
66
|
+
rl.close();
|
|
67
|
+
resolve(key);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
rl._writeToOutput = (str) => rl.output.write(rl.line ? "*" : str);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
saveApiKey(key) {
|
|
75
|
+
const secretsDir = path.dirname(this.secretsPath);
|
|
76
|
+
if (!fs.existsSync(secretsDir)) {
|
|
77
|
+
fs.mkdirSync(secretsDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
fs.writeFileSync(this.secretsPath, JSON.stringify({ apiKey: key }, null, 2));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async runJob(message, options = {}) {
|
|
83
|
+
const apiKey = await this.getApiKey();
|
|
84
|
+
|
|
85
|
+
if (!this.settings.kbId) {
|
|
86
|
+
throw new Error('First use: "openkbs push" to create the agent');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const chatTitle = options.chatTitle || `Task ${new Date().getTime()}`;
|
|
90
|
+
const chatId = await this.startJob(chatTitle, message, { kbId: this.settings.kbId, apiKey });
|
|
91
|
+
|
|
92
|
+
console.log(`Job ${chatId} created.\nWorking ...`);
|
|
93
|
+
|
|
94
|
+
if (options.poll !== false) {
|
|
95
|
+
return this.pollForMessages(chatId, { kbId: this.settings.kbId, apiKey });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return chatId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async startJob(chatTitle, data, app) {
|
|
102
|
+
const response = await this.makeRequest('https://chat.openkbs.com/', {
|
|
103
|
+
...app,
|
|
104
|
+
chatTitle,
|
|
105
|
+
message: data
|
|
106
|
+
});
|
|
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
|
+
}
|
|
117
|
+
|
|
118
|
+
makeRequest(url, payload) {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const { hostname, pathname } = new URL(url);
|
|
121
|
+
const req = https.request({
|
|
122
|
+
hostname,
|
|
123
|
+
path: pathname,
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'Content-Type': 'application/json' }
|
|
126
|
+
}, res => {
|
|
127
|
+
let data = '';
|
|
128
|
+
res.on('data', chunk => data += chunk);
|
|
129
|
+
res.on('end', () => resolve(data));
|
|
130
|
+
}).on('error', reject);
|
|
131
|
+
|
|
132
|
+
req.write(JSON.stringify(payload));
|
|
133
|
+
req.end();
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async pollForMessages(chatId, app) {
|
|
138
|
+
const payload = {
|
|
139
|
+
...app,
|
|
140
|
+
action: 'getChatMessages',
|
|
141
|
+
chatId,
|
|
142
|
+
decryptContent: true
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
const interval = setInterval(() => {
|
|
147
|
+
this.makeRequest('https://chat.openkbs.com/', payload)
|
|
148
|
+
.then(jsonString => {
|
|
149
|
+
const messages = JSON.parse(jsonString)[0].data.messages;
|
|
150
|
+
for (const message of messages) {
|
|
151
|
+
if (message.role === 'system' &&
|
|
152
|
+
/{"type"\s*:\s*"(JOB_COMPLETED|JOB_FAILED)".*?}/s.test(message.content)) {
|
|
153
|
+
|
|
154
|
+
const result = JSON.parse(message.content)?.data?.find(item =>
|
|
155
|
+
item.type === 'JOB_COMPLETED' || item.type === 'JOB_FAILED'
|
|
156
|
+
);
|
|
157
|
+
clearInterval(interval);
|
|
158
|
+
resolve(result);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.catch(console.error);
|
|
164
|
+
}, 1000);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = OpenKBSAgentClient;
|
|
@@ -32,37 +32,33 @@ export const getActions = () => [
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
[/[\s\S]*"type"\s*:\s*"JOB_FAILED"[\s\S]*/, async (match, event) => {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
35
|
+
const parsedData = extractJSONFromText(match[0]);
|
|
36
|
+
if (parsedData && parsedData.type === "JOB_FAILED") {
|
|
37
37
|
await openkbs.chats({
|
|
38
38
|
action: "updateChat",
|
|
39
|
-
title: await openkbs.encrypt(
|
|
39
|
+
title: await openkbs.encrypt(parsedData.reason),
|
|
40
40
|
chatIcon: '🔴',
|
|
41
41
|
chatId: event?.payload?.chatId
|
|
42
42
|
})
|
|
43
|
+
return parsedData;
|
|
43
44
|
}
|
|
44
45
|
}],
|
|
45
46
|
|
|
46
|
-
[/\/?googleSearch\("(.*?)"
|
|
47
|
+
[/\/?googleSearch\("(.*?)"\)/, async (match) => {
|
|
47
48
|
const q = match[1];
|
|
48
|
-
const limit = match[2] ? parseInt(match[2]) : 5;
|
|
49
|
-
|
|
50
49
|
try {
|
|
51
50
|
const response = await openkbs.googleSearch(q, {});
|
|
52
51
|
const data = response?.map(({ title, link, snippet, pagemap }) => ({
|
|
53
52
|
title, link, snippet, image: pagemap?.metatags?.[0]?.["og:image"]
|
|
54
|
-
}))
|
|
55
|
-
|
|
56
|
-
if(!data?.length) return { data: { error: "No results found" } };
|
|
53
|
+
}));
|
|
57
54
|
return { data };
|
|
58
55
|
} catch (e) {
|
|
59
56
|
return { error: e.message };
|
|
60
57
|
}
|
|
61
58
|
}],
|
|
62
59
|
|
|
63
|
-
[/\/?youtubeSearch\("(.*?)"
|
|
60
|
+
[/\/?youtubeSearch\("(.*?)"\)/, async (match) => {
|
|
64
61
|
const q = match[1];
|
|
65
|
-
const limit = match[2] ? parseInt(match[2]) : 5;
|
|
66
62
|
try {
|
|
67
63
|
const response = await openkbs.googleSearch(q + ' site:youtube.com', { videoOnly: true });
|
|
68
64
|
const data = response?.map(({ title, link, snippet, pagemap }) => ({
|
|
@@ -72,19 +68,15 @@ export const getActions = () => [
|
|
|
72
68
|
thumbnail: pagemap?.videoobject?.[0]?.thumbnailurl || pagemap?.metatags?.[0]?.["og:image"],
|
|
73
69
|
duration: pagemap?.videoobject?.[0]?.duration,
|
|
74
70
|
channel: pagemap?.metatags?.[0]?.["og:site_name"],
|
|
75
|
-
})).filter(item => item.link.includes('youtu'))
|
|
76
|
-
|
|
77
|
-
if(!data?.length) return { data: { error: "No YouTube videos found" } };
|
|
71
|
+
})).filter(item => item.link.includes('youtu'));
|
|
78
72
|
return { data };
|
|
79
73
|
} catch (e) {
|
|
80
74
|
return { error: e.message };
|
|
81
75
|
}
|
|
82
76
|
}],
|
|
83
77
|
|
|
84
|
-
[/\/?googleImageSearch\("(.*?)"
|
|
78
|
+
[/\/?googleImageSearch\("(.*?)"\)/, async (match) => {
|
|
85
79
|
const q = match[1];
|
|
86
|
-
const limit = match[2] ? parseInt(match[2]) : 10;
|
|
87
|
-
|
|
88
80
|
try {
|
|
89
81
|
const response = await openkbs.googleSearch(q, { searchType: 'image' });
|
|
90
82
|
const data = response?.map(({ title, link, snippet, pagemap }) => {
|
|
@@ -96,11 +88,8 @@ export const getActions = () => [
|
|
|
96
88
|
snippet,
|
|
97
89
|
image: thumbnail
|
|
98
90
|
};
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
if (!data?.length) return { data: { error: "No image results found" } };
|
|
91
|
+
});
|
|
102
92
|
return { data };
|
|
103
|
-
|
|
104
93
|
} catch (e) {
|
|
105
94
|
return { error: e.message };
|
|
106
95
|
}
|
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
import {getActions} from './actions.js';
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const backendHandler = async (event) => {
|
|
4
4
|
const maxSelfInvokeMessagesCount = 60;
|
|
5
|
-
const lastMessage = event.payload.messages[event.payload.messages.length - 1]
|
|
6
|
-
|
|
7
|
-
// Check if this is a JOB_COMPLETED or JOB_FAILED event
|
|
8
|
-
const isJobCompleted = /"type"\s*:\s*"JOB_COMPLETED"/.test(lastMessage);
|
|
9
|
-
const isJobFailed = /"type"\s*:\s*"JOB_FAILED"/.test(lastMessage);
|
|
10
|
-
|
|
5
|
+
const lastMessage = event.payload.messages[event.payload.messages.length - 1];
|
|
11
6
|
const actions = getActions();
|
|
12
7
|
|
|
13
8
|
const matchingActions = actions.reduce((acc, [regex, action]) => {
|
|
14
|
-
const matches = [...lastMessage.matchAll(new RegExp(regex, 'g'))];
|
|
9
|
+
const matches = [...lastMessage.content.matchAll(new RegExp(regex, 'g'))];
|
|
15
10
|
matches.forEach(match => {
|
|
16
11
|
acc.push(action(match, event));
|
|
17
12
|
});
|
|
18
13
|
return acc;
|
|
19
14
|
}, []);
|
|
20
15
|
|
|
21
|
-
|
|
16
|
+
// For Coding Agents: avoid unnecessary LLM reactions after job is finished
|
|
17
|
+
const isJobFinished = /"JOB_COMPLETED"|"JOB_FAILED"/.test(lastMessage.content);
|
|
22
18
|
|
|
23
19
|
const meta = {
|
|
24
20
|
_meta_actions:
|
|
25
21
|
(
|
|
26
22
|
event?.payload?.messages?.length > maxSelfInvokeMessagesCount ||
|
|
27
|
-
isJobFinished &&
|
|
23
|
+
isJobFinished && lastMessage.role === 'system'
|
|
28
24
|
)
|
|
29
25
|
? []
|
|
30
26
|
: ["REQUEST_CHAT_MODEL"]
|
package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Frontend/contentRender.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import JsonView from '@uiw/react-json-view';
|
|
3
|
+
import { Chip } from '@mui/material';
|
|
4
|
+
|
|
5
|
+
const extractJSONFromText = (text) => {
|
|
6
|
+
let braceCount = 0, startIndex = text.indexOf('{');
|
|
7
|
+
if (startIndex === -1) return null;
|
|
8
|
+
|
|
9
|
+
for (let i = startIndex; i < text.length; i++) {
|
|
10
|
+
if (text[i] === '{') braceCount++;
|
|
11
|
+
if (text[i] === '}' && --braceCount === 0) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(text.slice(startIndex, i + 1));
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const parseCommands = (text) => {
|
|
23
|
+
return text.match(/\/\w+\("([^"]+)"\)/g) || [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// do NOT useState() directly in this function, it is not a React component
|
|
27
|
+
const onRenderChatMessage = async (params) => {
|
|
28
|
+
const { content } = params.messages[params.msgIndex];
|
|
29
|
+
const JSONData = extractJSONFromText(content);
|
|
30
|
+
const commands = parseCommands(content);
|
|
31
|
+
|
|
32
|
+
if (JSONData?.type === 'JOB_COMPLETED') {
|
|
33
|
+
return <JsonView value={JSONData} />
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (commands.length > 0) {
|
|
37
|
+
return commands.map((cmd, i) => (
|
|
38
|
+
<Chip key={i} label={cmd} size="small" />
|
|
39
|
+
));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// return undefined to use default message render
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const Header = ({ setRenderSettings }) => {
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setRenderSettings({
|
|
48
|
+
inputLabelsQuickSend: true,
|
|
49
|
+
disableBalanceView: false,
|
|
50
|
+
disableSentLabel: false,
|
|
51
|
+
disableChatAvatar: false,
|
|
52
|
+
disableChatModelsSelect: false,
|
|
53
|
+
disableContextItems: false,
|
|
54
|
+
disableCopyButton: false,
|
|
55
|
+
disableEmojiButton: false,
|
|
56
|
+
disableTextToSpeechButton: false,
|
|
57
|
+
disableMobileLeftButton: false,
|
|
58
|
+
});
|
|
59
|
+
}, [setRenderSettings]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const exports = { onRenderChatMessage, Header };
|
|
63
|
+
window.contentRender = exports;
|
|
64
|
+
export default exports;
|
package/templates/CLAUDE.md
CHANGED
|
@@ -1,15 +1,109 @@
|
|
|
1
1
|
# Claude Code Instructions
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
# MANDATORY FIRST STEPS
|
|
4
|
+
**CRITICAL**: Before taking ANY action, implementing ANY feature, planing the implementation or answering ANY question about this project, you must perform the following steps:
|
|
5
|
+
|
|
6
|
+
**FIRST**: Update the knowledge base:
|
|
7
|
+
```bash
|
|
8
|
+
openkbs update knowledge
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**SECOND**: Read every file in the examples folder using the Read tool:
|
|
12
|
+
- First discover all files in the `.openkbs/knowledge/examples/` directory and ALL subdirectories.
|
|
13
|
+
- Then, use the Read tool to examine the content of EACH file individually (skip only icon.png files).
|
|
14
|
+
- You must read ALL files directly
|
|
15
|
+
|
|
16
|
+
**THIRD**: Read existing agent code using the Read tool:
|
|
17
|
+
- First discover all files in the `./app/`, `./src/`, and `./scripts/` folders.
|
|
18
|
+
- Then, use the Read tool to examine each file individually
|
|
19
|
+
|
|
20
|
+
# OpenKBS Agent Development Guidelines
|
|
21
|
+
|
|
22
|
+
## **Critical** Rules (**IMPORTANT**)
|
|
23
|
+
- Never skip reading examples
|
|
24
|
+
- Study the complete working examples to understand OpenKBS patterns
|
|
25
|
+
- Never guess framework methods, settings or variables — always reference the examples.
|
|
26
|
+
|
|
27
|
+
## Development Guidelines
|
|
28
|
+
- To add npm dependency to backend handlers, add it to onRequest.json and onResponse.json
|
|
29
|
+
- In src/Events and src/Frontend always use Imports (not Require)
|
|
30
|
+
- To add npm dependency to the frontend, add it to contentRender.json
|
|
31
|
+
- Valid values for the _meta_actions key are [] or ["REQUEST_CHAT_MODEL"]
|
|
32
|
+
- Add and use npm dependencies only if necessary, some of those shown in the examples are purely demonstrative
|
|
33
|
+
- If developing new agent, generate it's own ./scripts/run_job.js
|
|
34
|
+
- Before using third-party services in onRequest and onResponse handlers, ask the user for permission
|
|
35
|
+
|
|
36
|
+
## Architecture Overview
|
|
37
|
+
OpenKBS agents have **two execution environments**:
|
|
38
|
+
|
|
39
|
+
### 1. Cloud Environment (`./src/`)
|
|
40
|
+
- **Event handlers** (`onRequest.js`, `onResponse.js`) run on OpenKBS cloud platform
|
|
41
|
+
- **Purpose**: Process user messages, execute AI actions, return responses
|
|
42
|
+
- **Deployment**: Code is deployed via `openkbs push`
|
|
43
|
+
|
|
44
|
+
### 2. Local Environment (`./scripts/`)
|
|
45
|
+
- **User-run scripts** execute locally on user's machine
|
|
46
|
+
- **Purpose**: Call cloud agents via API, orchestrate multi-agent workflows, integrate with external systems
|
|
47
|
+
- **Execution**: Run directly with `node scripts/script-name.js`
|
|
48
|
+
|
|
49
|
+
### Backend
|
|
50
|
+
The OpenKBS backend framework is for developing AI agents with custom tools, using Node.js.
|
|
51
|
+
It integrates with openkbs chat service via `onRequest` and `onResponse` handlers for custom actions and service integration.
|
|
52
|
+
|
|
53
|
+
#### Backend Handlers
|
|
54
|
+
The OpenKBS framework's core uses `onRequest` and `onResponse` handlers as middleware for message tool call parsing and execution.
|
|
55
|
+
All these event handlers are executed on-demand (upon API request) by the OpenKBS cloud platform, where user production agents are deployed.
|
|
56
|
+
- **`onResponse` Handler:** Activated after the LLM generates a message, enabling command extraction, and action execution.
|
|
57
|
+
- **`onRequest` Handler:** Triggered on user message to allow the user to execute action
|
|
58
|
+
|
|
59
|
+
#### NPM Dependencies for onRequest.js or onResponse.js Backend Handlers
|
|
60
|
+
1. If a file imports an NPM dependency and is then imported by onRequest.js or onResponse.js, this dependency must be defined in the handler's corresponding json file
|
|
61
|
+
Example: If actions.js imports mysql2 and onResponse.js imports actions.js, then mysql2 must be in onResponse.json:
|
|
62
|
+
{
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"mysql2": "^3.14.2"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Similarly, we need to create onRequest.json for onRequest.js as each handler have separate Node.js build with separate dependencies
|
|
69
|
+
|
|
70
|
+
#### Managing Secrets
|
|
71
|
+
To securely manage sensitive information like API keys or database passwords within your backend event handlers (onRequest, onResponse, etc.), use the {{secrets.your_secret_name}} syntax.
|
|
72
|
+
|
|
73
|
+
#### User-Run Scripts
|
|
74
|
+
**User-run scripts** are located in `./scripts/` folder and communicate with cloud agents via API calls.
|
|
75
|
+
|
|
76
|
+
**Key Components:**
|
|
77
|
+
- `scripts/run_job.js` - Main job runner for calling the cloud agent
|
|
78
|
+
- `scripts/utils/agent_client.js` - Shared utility using `OpenKBSAgentClient` class
|
|
79
|
+
- Custom workflow scripts for multi-agent orchestration
|
|
80
|
+
|
|
81
|
+
**Architecture:**
|
|
82
|
+
- Scripts use `OpenKBSAgentClient` to communicate with deployed cloud agents
|
|
83
|
+
- **Path Resolution**: Automatically finds `app/settings.json` and `.openkbs/secrets.json` by walking up directories
|
|
84
|
+
- **Usage**: `const client = new OpenKBSAgentClient(); await client.runJob(message);`
|
|
85
|
+
- **Multi-agent support**: Each agent (base or related) finds its own settings and secrets in its directory structure
|
|
86
|
+
|
|
87
|
+
#### NPM Dependencies for User-Run Scripts
|
|
88
|
+
Add needed NPM dependencies to `package.json`.
|
|
89
|
+
|
|
90
|
+
### Frontend Overview
|
|
91
|
+
The OpenKBS frontend framework, built with React and MUI, offers a flexible platform for custom chat interfaces. Developers can customize chat appearance and behavior via the `contentRender` module.
|
|
92
|
+
|
|
93
|
+
#### contentRender
|
|
94
|
+
The `contentRender.js` file is central to frontend customization, exporting key functions for interface adjustments.
|
|
95
|
+
- **`onRenderChatMessage(params)`:** function called every time a chat message is rendered.
|
|
96
|
+
|
|
97
|
+
#### OpenKBS commands
|
|
98
|
+
`openkbs push` - deploy the agent to openkbs cloud
|
|
99
|
+
`openkbs create my-agent` - creates a directory structure for a new agent
|
|
100
|
+
|
|
101
|
+
### Creating Related Agents
|
|
102
|
+
To create related agents that work alongside the main agent:
|
|
103
|
+
|
|
104
|
+
1. **Create in related-agents/ folder**: `cd related-agents && openkbs create agent-name`
|
|
105
|
+
2. **Each related agent gets**: Own `app/settings.json`, `src/` folder, and `.openkbs/secrets.json`
|
|
106
|
+
3. **Script usage**: Related agents use same `OpenKBSAgentClient` - it automatically finds their settings and secrets
|
|
107
|
+
4. **Multi-agent workflows**: Scripts can orchestrate multiple agents by creating separate client instances
|
|
108
|
+
|
|
109
|
+
Related agents are independent but can share the base agent's script utilities.
|
|
@@ -1,10 +1 @@
|
|
|
1
|
-
You are an AI assistant, assist users with their inquiries and tasks efficiently.
|
|
2
|
-
|
|
3
|
-
Here are some APIs you can use to assist users:
|
|
4
|
-
|
|
5
|
-
/googleSearch("query")
|
|
6
|
-
Description: """
|
|
7
|
-
Get results from Google Search API.
|
|
8
|
-
"""
|
|
9
|
-
$InputLabel = """Let me Search in Google!"""
|
|
10
|
-
$InputValue = """Search in google for the latest news"""
|
|
1
|
+
You are an AI assistant, assist users with their inquiries and tasks efficiently.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// This boilerplate code is a starting point for development.
|
|
2
|
+
|
|
3
|
+
const OpenKBSAgentClient = require('./utils/agent_client');
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const client = new OpenKBSAgentClient();
|
|
7
|
+
|
|
8
|
+
const message = `Today's Date: ${new Date().toLocaleDateString()}
|
|
9
|
+
|
|
10
|
+
PROCESS_PRODUCT:
|
|
11
|
+
Product Name: iPhone 14 Pro Max
|
|
12
|
+
Product Code: MQ9X3RX/A
|
|
13
|
+
ID: 97649
|
|
14
|
+
|
|
15
|
+
find at least 2 images and 2 videos
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = await client.runJob(message);
|
|
20
|
+
console.log('Job completed:', result);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Job failed:', error.message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
main();
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
class OpenKBSAgentClient {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.settings = this.findSettings();
|
|
9
|
+
this.secretsPath = this.findSecretsPath();
|
|
10
|
+
this.apiKey = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
findSettings() {
|
|
14
|
+
let currentDir = __dirname;
|
|
15
|
+
|
|
16
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
17
|
+
const settingsPath = path.join(currentDir, 'app', 'settings.json');
|
|
18
|
+
if (fs.existsSync(settingsPath)) {
|
|
19
|
+
return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
currentDir = path.dirname(currentDir);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throw new Error('Could not find app/settings.json in parent directories');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
findSecretsPath() {
|
|
28
|
+
let currentDir = __dirname;
|
|
29
|
+
|
|
30
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
31
|
+
const settingsPath = path.join(currentDir, 'app', 'settings.json');
|
|
32
|
+
if (fs.existsSync(settingsPath)) {
|
|
33
|
+
const secretsPath = path.join(currentDir, '.openkbs', 'secrets.json');
|
|
34
|
+
return secretsPath;
|
|
35
|
+
}
|
|
36
|
+
currentDir = path.dirname(currentDir);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new Error('Could not find agent directory with app/settings.json');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getApiKey() {
|
|
43
|
+
if (this.apiKey) return this.apiKey;
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(this.secretsPath)) {
|
|
46
|
+
const secrets = JSON.parse(fs.readFileSync(this.secretsPath, 'utf8'));
|
|
47
|
+
this.apiKey = secrets.apiKey;
|
|
48
|
+
return this.apiKey;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.apiKey = await this.promptForApiKey();
|
|
52
|
+
this.saveApiKey(this.apiKey);
|
|
53
|
+
return this.apiKey;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async promptForApiKey() {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const rl = readline.createInterface({
|
|
59
|
+
input: process.stdin,
|
|
60
|
+
output: process.stdout
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
console.log(`Please generate an API key from: https://${this.settings.kbId}.apps.openkbs.com/?tab=access&createAPIKey=api-${+new Date()}`);
|
|
64
|
+
|
|
65
|
+
rl.question('Enter your API key: ', (key) => {
|
|
66
|
+
rl.close();
|
|
67
|
+
resolve(key);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
rl._writeToOutput = (str) => rl.output.write(rl.line ? "*" : str);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
saveApiKey(key) {
|
|
75
|
+
const secretsDir = path.dirname(this.secretsPath);
|
|
76
|
+
if (!fs.existsSync(secretsDir)) {
|
|
77
|
+
fs.mkdirSync(secretsDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
fs.writeFileSync(this.secretsPath, JSON.stringify({ apiKey: key }, null, 2));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async runJob(message, options = {}) {
|
|
83
|
+
const apiKey = await this.getApiKey();
|
|
84
|
+
|
|
85
|
+
if (!this.settings.kbId) {
|
|
86
|
+
throw new Error('First use: "openkbs push" to create the agent');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const chatTitle = options.chatTitle || `Task ${new Date().getTime()}`;
|
|
90
|
+
const chatId = await this.startJob(chatTitle, message, { kbId: this.settings.kbId, apiKey });
|
|
91
|
+
|
|
92
|
+
console.log(`Job ${chatId} created.\nWorking ...`);
|
|
93
|
+
|
|
94
|
+
if (options.poll !== false) {
|
|
95
|
+
return this.pollForMessages(chatId, { kbId: this.settings.kbId, apiKey });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return chatId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async startJob(chatTitle, data, app) {
|
|
102
|
+
const response = await this.makeRequest('https://chat.openkbs.com/', {
|
|
103
|
+
...app,
|
|
104
|
+
chatTitle,
|
|
105
|
+
message: data
|
|
106
|
+
});
|
|
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
|
+
}
|
|
117
|
+
|
|
118
|
+
makeRequest(url, payload) {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const { hostname, pathname } = new URL(url);
|
|
121
|
+
const req = https.request({
|
|
122
|
+
hostname,
|
|
123
|
+
path: pathname,
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'Content-Type': 'application/json' }
|
|
126
|
+
}, res => {
|
|
127
|
+
let data = '';
|
|
128
|
+
res.on('data', chunk => data += chunk);
|
|
129
|
+
res.on('end', () => resolve(data));
|
|
130
|
+
}).on('error', reject);
|
|
131
|
+
|
|
132
|
+
req.write(JSON.stringify(payload));
|
|
133
|
+
req.end();
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async pollForMessages(chatId, app) {
|
|
138
|
+
const payload = {
|
|
139
|
+
...app,
|
|
140
|
+
action: 'getChatMessages',
|
|
141
|
+
chatId,
|
|
142
|
+
decryptContent: true
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
const interval = setInterval(() => {
|
|
147
|
+
this.makeRequest('https://chat.openkbs.com/', payload)
|
|
148
|
+
.then(jsonString => {
|
|
149
|
+
const messages = JSON.parse(jsonString)[0].data.messages;
|
|
150
|
+
for (const message of messages) {
|
|
151
|
+
if (message.role === 'system' &&
|
|
152
|
+
/{"type"\s*:\s*"(JOB_COMPLETED|JOB_FAILED)".*?}/s.test(message.content)) {
|
|
153
|
+
|
|
154
|
+
const result = JSON.parse(message.content)?.data?.find(item =>
|
|
155
|
+
item.type === 'JOB_COMPLETED' || item.type === 'JOB_FAILED'
|
|
156
|
+
);
|
|
157
|
+
clearInterval(interval);
|
|
158
|
+
resolve(result);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.catch(console.error);
|
|
164
|
+
}, 1000);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = OpenKBSAgentClient;
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// This boilerplate code is a starting point for development.
|
|
2
|
+
export const getActions = () => [
|
|
3
|
+
[/\/?googleSearch\("(.*?)"\)/, async (match) => {
|
|
3
4
|
const q = match[1];
|
|
4
5
|
try {
|
|
5
|
-
const
|
|
6
|
-
const params = { q, ...(noSecrets ? {} : { key: '{{secrets.googlesearch_api_key}}', cx: '{{secrets.googlesearch_engine_id}}' }) };
|
|
7
|
-
const response = noSecrets
|
|
8
|
-
? await openkbs.googleSearch(params.q, params)
|
|
9
|
-
: (await axios.get('https://www.googleapis.com/customsearch/v1', { params }))?.data?.items;
|
|
6
|
+
const response = await openkbs.googleSearch(q, {});
|
|
10
7
|
const data = response?.map(({ title, link, snippet, pagemap }) => ({
|
|
11
8
|
title, link, snippet, image: pagemap?.metatags?.[0]?.["og:image"]
|
|
12
9
|
}));
|
|
13
|
-
return { data
|
|
10
|
+
return { data };
|
|
14
11
|
} catch (e) {
|
|
15
|
-
return { error: e.
|
|
12
|
+
return { error: e.message };
|
|
16
13
|
}
|
|
17
|
-
}]
|
|
14
|
+
}],
|
|
15
|
+
// add more actions here
|
|
18
16
|
];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {getActions} from './actions.js';
|
|
2
|
+
|
|
3
|
+
export const backendHandler = async (event) => {
|
|
4
|
+
const maxSelfInvokeMessagesCount = 60;
|
|
5
|
+
const lastMessage = event.payload.messages[event.payload.messages.length - 1];
|
|
6
|
+
const actions = getActions();
|
|
7
|
+
|
|
8
|
+
const matchingActions = actions.reduce((acc, [regex, action]) => {
|
|
9
|
+
const matches = [...lastMessage.content.matchAll(new RegExp(regex, 'g'))];
|
|
10
|
+
matches.forEach(match => {
|
|
11
|
+
acc.push(action(match, event));
|
|
12
|
+
});
|
|
13
|
+
return acc;
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
// Avoid unnecessary LLM reactions if job is finished
|
|
17
|
+
const isJobFinished = /"JOB_COMPLETED"|"JOB_FAILED"/.test(lastMessage.content);
|
|
18
|
+
|
|
19
|
+
const meta = {
|
|
20
|
+
_meta_actions:
|
|
21
|
+
(
|
|
22
|
+
event?.payload?.messages?.length > maxSelfInvokeMessagesCount ||
|
|
23
|
+
isJobFinished && lastMessage.role === 'system'
|
|
24
|
+
)
|
|
25
|
+
? []
|
|
26
|
+
: ["REQUEST_CHAT_MODEL"]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (matchingActions.length > 0) {
|
|
30
|
+
try {
|
|
31
|
+
const results = await Promise.all(matchingActions);
|
|
32
|
+
|
|
33
|
+
if (results?.[0]?.data?.some?.(o => o?.type === 'image_url')) {
|
|
34
|
+
return {
|
|
35
|
+
...results[0],
|
|
36
|
+
...meta
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
type: 'RESPONSE',
|
|
42
|
+
data: results,
|
|
43
|
+
...meta
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return {
|
|
47
|
+
type: 'ERROR',
|
|
48
|
+
error: error.message,
|
|
49
|
+
...meta
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { type: 'CONTINUE' };
|
|
55
|
+
};
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {backendHandler} from './handler';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
export const handler = async (event) => {
|
|
5
|
-
const actions = getActions({});
|
|
6
|
-
|
|
7
|
-
for (let [regex, action] of actions) {
|
|
8
|
-
const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
|
|
9
|
-
const match = lastMessage?.match(regex);
|
|
10
|
-
if (match) return await action(match);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return { type: 'CONTINUE' }
|
|
14
|
-
};
|
|
3
|
+
export const handler = async (event) => backendHandler(event)
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {backendHandler} from './handler';
|
|
2
2
|
|
|
3
|
-
export const handler = async (event) =>
|
|
4
|
-
const actions = getActions({_meta_actions: ["REQUEST_CHAT_MODEL"]});
|
|
5
|
-
|
|
6
|
-
for (let [regex, action] of actions) {
|
|
7
|
-
const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
|
|
8
|
-
const match = lastMessage?.match(regex);
|
|
9
|
-
if (match) return await action(match);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return { type: 'CONTINUE' }
|
|
13
|
-
};
|
|
3
|
+
export const handler = async (event) => backendHandler(event)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# OpenKBS Development Guidelines
|
|
2
|
-
|
|
3
|
-
## Development Flow
|
|
4
|
-
|
|
5
|
-
1. **Read existing code first:**
|
|
6
|
-
- `./app/` folder (settings, instructions, etc.)
|
|
7
|
-
- `./src/` folder (all Events and Frontend files)
|
|
8
|
-
|
|
9
|
-
2. **Assess implementation requirements:**
|
|
10
|
-
- **Minor changes only**: Simple modifications to existing code that don't require framework knowledge
|
|
11
|
-
- **Framework implementation needed**: New features, new event handlers, new frontend components, or placeholder app development
|
|
12
|
-
|
|
13
|
-
3. **For framework implementation:**
|
|
14
|
-
- Read all source code in `.openkbs/knowledge/examples/`
|
|
15
|
-
- Study complete working applications to understand OpenKBS patterns
|
|
16
|
-
- Never guess framework methods or variables - always reference examples
|
|
17
|
-
|
|
18
|
-
## When Examples Are Required
|
|
19
|
-
|
|
20
|
-
- New OpenKBS application development (placeholder app)
|
|
21
|
-
- Adding new event handlers (onRequest, onResponse, etc.)
|
|
22
|
-
- Creating new frontend components
|
|
23
|
-
- Implementing OpenKBS-specific functionality
|
|
24
|
-
- Any framework-related implementation
|
|
25
|
-
|
|
26
|
-
## When Examples May Not Be Needed
|
|
27
|
-
|
|
28
|
-
- Simple text changes to existing files
|
|
29
|
-
- Minor bug fixes in existing code
|
|
30
|
-
- Configuration adjustments that don't involve framework methods
|
|
31
|
-
|
|
32
|
-
## Critical Rule
|
|
33
|
-
|
|
34
|
-
**When in doubt, read the examples.** They contain all necessary framework knowledge for proper OpenKBS implementation.
|
|
Binary file
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"chatVendor": "anthropic",
|
|
3
|
-
"kbDescription": "An autonomous AI powerhouse for content generation",
|
|
4
|
-
"kbTitle": "Copywriter",
|
|
5
|
-
"model": "claude-3-5-haiku-20241022",
|
|
6
|
-
"inputTools": [
|
|
7
|
-
"imageToText"
|
|
8
|
-
],
|
|
9
|
-
"embeddingModel": "none",
|
|
10
|
-
"embeddingDimension": "none",
|
|
11
|
-
"searchEngine": "IndexedDB",
|
|
12
|
-
"itemTypes": {}
|
|
13
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
const https = require('https');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const readline = require('readline');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
const settings = JSON.parse(fs.readFileSync('app/settings.json', 'utf8'));
|
|
7
|
-
const secretsPath = path.join('.openkbs', 'secrets.json');
|
|
8
|
-
let apiKey;
|
|
9
|
-
|
|
10
|
-
const message = `PROCESS_PRODUCT:
|
|
11
|
-
Product Name: iPhone 14 Pro Max
|
|
12
|
-
Product Code: MQ9X3RX/A
|
|
13
|
-
ID: 97649
|
|
14
|
-
|
|
15
|
-
find at least 2 images and 2 videos
|
|
16
|
-
`;
|
|
17
|
-
|
|
18
|
-
if (!settings.kbId) {
|
|
19
|
-
console.log('First use: "openkbs push" To create the agent')
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Function to read the API key from secrets.json
|
|
24
|
-
function getApiKey() {
|
|
25
|
-
if (fs.existsSync(secretsPath)) {
|
|
26
|
-
const secrets = JSON.parse(fs.readFileSync(secretsPath, 'utf8'));
|
|
27
|
-
return secrets.apiKey;
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function promptForApiKey() {
|
|
33
|
-
return new Promise((resolve) => {
|
|
34
|
-
const rl = readline.createInterface({
|
|
35
|
-
input: process.stdin,
|
|
36
|
-
output: process.stdout
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
console.log(`Please generate an API key from: https://${settings.kbId}.apps.openkbs.com/?tab=access&createAPIKey=api-${+new Date()}`);
|
|
40
|
-
|
|
41
|
-
rl.question('Enter your API key: ', (key) => {
|
|
42
|
-
rl.close();
|
|
43
|
-
resolve(key);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
rl._writeToOutput = (str) => rl.output.write(rl.line ? "*" : str);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function saveApiKey(key) {
|
|
51
|
-
if (!fs.existsSync('.openkbs')) {
|
|
52
|
-
fs.mkdirSync('.openkbs');
|
|
53
|
-
}
|
|
54
|
-
fs.writeFileSync(secretsPath, JSON.stringify({ apiKey: key }, null, 2));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function main() {
|
|
58
|
-
apiKey = getApiKey();
|
|
59
|
-
|
|
60
|
-
if (!apiKey) {
|
|
61
|
-
apiKey = await promptForApiKey();
|
|
62
|
-
console.log('\n')
|
|
63
|
-
saveApiKey(apiKey);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const app = { kbId: settings.kbId, apiKey };
|
|
67
|
-
|
|
68
|
-
const chatTitle = "Task " + new Date().getTime();
|
|
69
|
-
startJob(chatTitle, message, app).then(chatId => {
|
|
70
|
-
console.log(`Job ${chatId} created.\nWorking ...`);
|
|
71
|
-
pollForMessages(chatId, app);
|
|
72
|
-
}).catch(console.error);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function startJob(chatTitle, data, app) {
|
|
76
|
-
return makeRequest('https://chat.openkbs.com/', { ...app, chatTitle, message: data })
|
|
77
|
-
.then(res => {
|
|
78
|
-
try {
|
|
79
|
-
return JSON.parse(res)[0].createdChatId;
|
|
80
|
-
} catch (error) {
|
|
81
|
-
fs.unlinkSync(secretsPath);
|
|
82
|
-
throw 'Authentication failed.';
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function makeRequest(url, payload) {
|
|
88
|
-
return new Promise((resolve, reject) => {
|
|
89
|
-
const { hostname, pathname } = new URL(url);
|
|
90
|
-
const req = https.request({
|
|
91
|
-
hostname, path: pathname, method: 'POST',
|
|
92
|
-
headers: { 'Content-Type': 'application/json' }
|
|
93
|
-
}, res => {
|
|
94
|
-
let data = '';
|
|
95
|
-
res.on('data', chunk => data += chunk);
|
|
96
|
-
res.on('end', () => resolve(data));
|
|
97
|
-
}).on('error', reject);
|
|
98
|
-
|
|
99
|
-
req.write(JSON.stringify(payload));
|
|
100
|
-
req.end();
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function pollForMessages(chatId, app) {
|
|
105
|
-
const payload = { ...app, action: 'getChatMessages', chatId, decryptContent: true };
|
|
106
|
-
const interval = setInterval(() => {
|
|
107
|
-
makeRequest('https://chat.openkbs.com/', payload).then(jsonString => {
|
|
108
|
-
const messages = JSON.parse(jsonString)[0].data.messages;
|
|
109
|
-
for (const message of messages) {
|
|
110
|
-
if (message.role === 'system' && /{"type"\s*:\s*"(JOB_COMPLETED|JOB_FAILED)".*?}/s.test(message.content)) {
|
|
111
|
-
console.log(JSON.parse(message.content).data[0]);
|
|
112
|
-
clearInterval(interval);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}).catch(console.error);
|
|
117
|
-
}, 1000);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
main();
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import React, { useEffect } from "react";
|
|
2
|
-
|
|
3
|
-
const onRenderChatMessage = async (params) => {
|
|
4
|
-
return;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
const Header = ({ setRenderSettings }) => {
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
setRenderSettings({
|
|
10
|
-
inputLabelsQuickSend: true,
|
|
11
|
-
disableBalanceView: false,
|
|
12
|
-
disableSentLabel: false,
|
|
13
|
-
disableChatAvatar: false,
|
|
14
|
-
disableChatModelsSelect: false,
|
|
15
|
-
disableContextItems: false,
|
|
16
|
-
disableCopyButton: false,
|
|
17
|
-
disableEmojiButton: false,
|
|
18
|
-
disableTextToSpeechButton: false,
|
|
19
|
-
disableMobileLeftButton: false,
|
|
20
|
-
});
|
|
21
|
-
}, [setRenderSettings]);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const exports = { onRenderChatMessage, Header };
|
|
25
|
-
window.contentRender = exports;
|
|
26
|
-
export default exports;
|