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 CHANGED
@@ -1,6 +1,7 @@
1
1
  # OpenKBS · [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/open-kbs/openkbs-chat/blob/main/LICENSE) [![npm version](https://img.shields.io/badge/npm-v0.0.20-orange.svg)](https://www.npmjs.com/package/openkbs)
2
2
 
3
- OpenKBS is an extendable AI service designed to build,
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
- - [Chat Render](#chat-render)
13
- - [Setup Local Development](#setup-local-development)
14
- - [Use Built-in MUI Components](#use-built-in-mui-components)
15
- - [AI-Powered Generation](#ai-powered-frontend-generation)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openkbs",
3
- "version": "0.0.51",
3
+ "version": "0.0.53",
4
4
  "description": "OpenKBS - Command Line Interface",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
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
  }
@@ -2,5 +2,5 @@
2
2
  "chatVendor": "anthropic",
3
3
  "kbDescription": "An autonomous AI powerhouse for content generation",
4
4
  "kbTitle": "Copywriter",
5
- "model": "claude-3-5-haiku-20241022"
5
+ "model": "claude-sonnet-4-20250514"
6
6
  }
@@ -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) => rl.output.write(rl.line ? "*" : 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 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;
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 startJob(chatTitle, data, app) {
102
- const response = await this.makeRequest('https://chat.openkbs.com/', {
103
- ...app,
104
- chatTitle,
105
- message: data
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
- makeRequest(url, payload) {
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: 'POST',
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', () => resolve(data));
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
- 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);
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 };