openkbs 0.0.37 → 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 CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "openkbs",
3
- "version": "0.0.37",
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",
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
- console.green('Knowledge base updated successfully!');
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,
@@ -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;
@@ -13,12 +13,12 @@ export const backendHandler = async (event) => {
13
13
  return acc;
14
14
  }, []);
15
15
 
16
+ // For Coding Agents: avoid unnecessary LLM reactions after job is finished
16
17
  const isJobFinished = /"JOB_COMPLETED"|"JOB_FAILED"/.test(lastMessage.content);
17
18
 
18
19
  const meta = {
19
20
  _meta_actions:
20
21
  (
21
- // disable post-response interactions if limit is exceeded or final job response
22
22
  event?.payload?.messages?.length > maxSelfInvokeMessagesCount ||
23
23
  isJobFinished && lastMessage.role === 'system'
24
24
  )
@@ -1,19 +1,109 @@
1
1
  # Claude Code Instructions
2
2
 
3
- ## MANDATORY FIRST STEPS
4
- **CRITICAL**: Before taking ANY action, implementing ANY feature, or answering ANY question about this project:
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
5
 
6
- 1. **FIRST**: Update the knowledge base:
7
- ```bash
8
- openkbs update knowledge
9
- ```
6
+ **FIRST**: Update the knowledge base:
7
+ ```bash
8
+ openkbs update knowledge
9
+ ```
10
10
 
11
- 2. **IMMEDIATELY AFTER**: Read the knowledge base README:
12
- ```bash
13
- # THIS MUST BE YOUR SECOND ACTION - READ THIS FILE FOR MORE INSTRUCTIONS
14
- .openkbs/knowledge/README.md
15
- ```
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
16
15
 
17
- **DO NOT PROCEED WITHOUT COMPLETING BOTH STEPS IN ORDER**
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
18
19
 
19
- `.openkbs/knowledge/README.md` contains comprehensive instructions on how to implement openkbs agents
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.
@@ -0,0 +1,6 @@
1
+ # Related Agents
2
+
3
+ To add a related agent, use:
4
+ ```
5
+ openkbs create "my-agent"
6
+ ```
@@ -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;
@@ -13,11 +13,17 @@ export const backendHandler = async (event) => {
13
13
  return acc;
14
14
  }, []);
15
15
 
16
+ // Avoid unnecessary LLM reactions if job is finished
17
+ const isJobFinished = /"JOB_COMPLETED"|"JOB_FAILED"/.test(lastMessage.content);
18
+
16
19
  const meta = {
17
20
  _meta_actions:
18
- event?.payload?.messages?.length > maxSelfInvokeMessagesCount
19
- ? [] // Ends the chat
20
- : ["REQUEST_CHAT_MODEL"] // Triggers the LLM to react after the response
21
+ (
22
+ event?.payload?.messages?.length > maxSelfInvokeMessagesCount ||
23
+ isJobFinished && lastMessage.role === 'system'
24
+ )
25
+ ? []
26
+ : ["REQUEST_CHAT_MODEL"]
21
27
  }
22
28
 
23
29
  if (matchingActions.length > 0) {
@@ -1,57 +0,0 @@
1
- # OpenKBS Agent Development Guidelines
2
-
3
- ## MANDATORY FIRST STEP
4
- **READ EVERY SINGLE FILE IN THE EXAMPLES FOLDER**
5
-
6
- Read the content of ALL files in `.openkbs/knowledge/examples/` directory and ALL subdirectories (without the icon.png)
7
-
8
- ## Development Flow
9
-
10
- 1. **Read ALL example files first** to get familiar with OpenKBS framework for building AI agents
11
- 2. **Read existing agent code:**
12
- - `./app/` folder (settings, instructions, etc.)
13
- - `./src/` folder (all Events and Frontend files)
14
- - `./run_job.js` any files starting with "run"
15
- 3. **Implement requested features using knowledge from examples**
16
-
17
-
18
- ## **Critical** Rules (**IMPORTANT**)
19
- - Never skip reading examples
20
- - Study the complete working examples to understand OpenKBS patterns
21
- - Never guess framework methods, settings or variables — always reference the examples.
22
- - Think hard before the implementation
23
-
24
- ## Development Guidelines
25
- - If develop new agent from scratch, implement the run_job.js script as start point to backend-to-backend integrations
26
- - To add npm dependency to backend handlers, add it to onRequest.json and onResponse.json
27
- - To add npm dependency to the frontend, add it to contentRender.json
28
- - Valid values for the _meta_actions key are [] or ["REQUEST_CHAT_MODEL"].
29
- - Add and use npm dependencies only if necessary, some of those shown in the examples are purely demonstrative
30
-
31
- ### Backend
32
- The OpenKBS backend framework is for developing AI agents with custom tools, using Node.js. It integrates with chat services via `onRequest` and `onResponse` handlers for custom actions and service integration.
33
-
34
- #### Backend Handlers
35
- The framework's core uses `onRequest` and `onResponse` handlers as middleware for message tool call parsing and execution.
36
- - **`onResponse` Handler:** Activated after the LLM generates a message, enabling command extraction, and action execution.
37
- - **`onRequest` Handler:** Triggered on user message to allow the user to execute action
38
-
39
- #### NPM Dependencies for onRequest.js or onResponse.js Backend Handlers
40
- 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
41
- Example: If actions.js imports mysql2 and onResponse.js imports actions.js, then mysql2 must be in onResponse.json:
42
- {
43
- "dependencies": {
44
- "mysql2": "^3.14.2"
45
- }
46
- }
47
-
48
- Similarly, we need to create onRequest.json for onRequest.js as each handler have separate Node.js build with separate dependencies
49
-
50
-
51
- ### Frontend Overview
52
- 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.
53
-
54
- #### contentRender
55
- The `contentRender.js` file is central to frontend customization, exporting key functions for interface adjustments.
56
- - **`onRenderChatMessage(params)`:** function called every time a chat message is rendered.
57
-
@@ -1,124 +0,0 @@
1
- // Start new agent job via API call to openkbs cloud service
2
-
3
- const https = require('https');
4
- const fs = require('fs');
5
- const readline = require('readline');
6
- const path = require('path');
7
-
8
- const settings = JSON.parse(fs.readFileSync('app/settings.json', 'utf8'));
9
- const secretsPath = path.join('.openkbs', 'secrets.json');
10
- let apiKey;
11
-
12
- const message = `Today's Date: ${new Date().toLocaleDateString()}
13
-
14
- PROCESS_PRODUCT:
15
- Product Name: iPhone 14 Pro Max
16
- Product Code: MQ9X3RX/A
17
- ID: 97649
18
-
19
- find at least 2 images and 2 videos
20
- `;
21
-
22
- if (!settings.kbId) {
23
- console.log('First use: "openkbs push" To create the agent')
24
- return;
25
- }
26
-
27
- function getApiKey() {
28
- if (fs.existsSync(secretsPath)) {
29
- const secrets = JSON.parse(fs.readFileSync(secretsPath, 'utf8'));
30
- return secrets.apiKey;
31
- }
32
- return null;
33
- }
34
-
35
- function promptForApiKey() {
36
- return new Promise((resolve) => {
37
- const rl = readline.createInterface({
38
- input: process.stdin,
39
- output: process.stdout
40
- });
41
-
42
- console.log(`Please generate an API key from: https://${settings.kbId}.apps.openkbs.com/?tab=access&createAPIKey=api-${+new Date()}`);
43
-
44
- rl.question('Enter your API key: ', (key) => {
45
- rl.close();
46
- resolve(key);
47
- });
48
-
49
- rl._writeToOutput = (str) => rl.output.write(rl.line ? "*" : str);
50
- });
51
- }
52
-
53
- function saveApiKey(key) {
54
- if (!fs.existsSync('.openkbs')) {
55
- fs.mkdirSync('.openkbs');
56
- }
57
- fs.writeFileSync(secretsPath, JSON.stringify({ apiKey: key }, null, 2));
58
- }
59
-
60
- async function main() {
61
- apiKey = getApiKey();
62
-
63
- if (!apiKey) {
64
- apiKey = await promptForApiKey();
65
- console.log('\n')
66
- saveApiKey(apiKey);
67
- }
68
-
69
- const app = { kbId: settings.kbId, apiKey };
70
-
71
- const chatTitle = "Task " + new Date().getTime();
72
- startJob(chatTitle, message, app).then(chatId => {
73
- console.log(`Job ${chatId} created.\nWorking ...`);
74
- pollForMessages(chatId, app);
75
- }).catch(console.error);
76
- }
77
-
78
- function startJob(chatTitle, data, app) {
79
- return makeRequest('https://chat.openkbs.com/', { ...app, chatTitle, message: data })
80
- .then(res => {
81
- try {
82
- return JSON.parse(res)[0].createdChatId;
83
- } catch (error) {
84
- fs.unlinkSync(secretsPath);
85
- throw 'Authentication failed.';
86
- }
87
- });
88
- }
89
-
90
- function makeRequest(url, payload) {
91
- return new Promise((resolve, reject) => {
92
- const { hostname, pathname } = new URL(url);
93
- const req = https.request({
94
- hostname, path: pathname, method: 'POST',
95
- headers: { 'Content-Type': 'application/json' }
96
- }, res => {
97
- let data = '';
98
- res.on('data', chunk => data += chunk);
99
- res.on('end', () => resolve(data));
100
- }).on('error', reject);
101
-
102
- req.write(JSON.stringify(payload));
103
- req.end();
104
- });
105
- }
106
-
107
- function pollForMessages(chatId, app) {
108
- const payload = { ...app, action: 'getChatMessages', chatId, decryptContent: true };
109
- const interval = setInterval(() => {
110
- makeRequest('https://chat.openkbs.com/', payload).then(jsonString => {
111
- const messages = JSON.parse(jsonString)[0].data.messages;
112
- for (const message of messages) {
113
- if (message.role === 'system' && /{"type"\s*:\s*"(JOB_COMPLETED|JOB_FAILED)".*?}/s.test(message.content)) {
114
- // return the final response found in the data results array generated by the backendHandler
115
- console.log(JSON.parse(message.content)?.data?.find(item => item.type === 'JOB_COMPLETED' || item.type === 'JOB_FAILED'));
116
- clearInterval(interval);
117
- return;
118
- }
119
- }
120
- }).catch(console.error);
121
- }, 1000);
122
- }
123
-
124
- main();