@aj-archipelago/cortex 1.3.58 → 1.3.60

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.
Files changed (57) hide show
  1. package/config/default.example.json +15 -1
  2. package/config.js +42 -0
  3. package/helper-apps/cortex-file-handler/INTERFACE.md +20 -9
  4. package/helper-apps/cortex-file-handler/package-lock.json +2 -2
  5. package/helper-apps/cortex-file-handler/package.json +1 -1
  6. package/helper-apps/cortex-file-handler/scripts/setup-azure-container.js +17 -17
  7. package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +35 -35
  8. package/helper-apps/cortex-file-handler/src/blobHandler.js +1010 -909
  9. package/helper-apps/cortex-file-handler/src/constants.js +98 -98
  10. package/helper-apps/cortex-file-handler/src/docHelper.js +27 -27
  11. package/helper-apps/cortex-file-handler/src/fileChunker.js +224 -214
  12. package/helper-apps/cortex-file-handler/src/helper.js +93 -93
  13. package/helper-apps/cortex-file-handler/src/index.js +584 -550
  14. package/helper-apps/cortex-file-handler/src/localFileHandler.js +86 -86
  15. package/helper-apps/cortex-file-handler/src/redis.js +186 -90
  16. package/helper-apps/cortex-file-handler/src/services/ConversionService.js +301 -273
  17. package/helper-apps/cortex-file-handler/src/services/FileConversionService.js +55 -55
  18. package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +174 -154
  19. package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +239 -223
  20. package/helper-apps/cortex-file-handler/src/services/storage/LocalStorageProvider.js +161 -159
  21. package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +73 -71
  22. package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +46 -45
  23. package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +256 -213
  24. package/helper-apps/cortex-file-handler/src/start.js +4 -1
  25. package/helper-apps/cortex-file-handler/src/utils/filenameUtils.js +59 -25
  26. package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +119 -116
  27. package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +257 -257
  28. package/helper-apps/cortex-file-handler/tests/cleanup.test.js +676 -0
  29. package/helper-apps/cortex-file-handler/tests/conversionResilience.test.js +124 -124
  30. package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +249 -208
  31. package/helper-apps/cortex-file-handler/tests/fileUpload.test.js +439 -380
  32. package/helper-apps/cortex-file-handler/tests/getOperations.test.js +299 -263
  33. package/helper-apps/cortex-file-handler/tests/postOperations.test.js +265 -239
  34. package/helper-apps/cortex-file-handler/tests/start.test.js +1230 -1201
  35. package/helper-apps/cortex-file-handler/tests/storage/AzureStorageProvider.test.js +110 -105
  36. package/helper-apps/cortex-file-handler/tests/storage/GCSStorageProvider.test.js +201 -175
  37. package/helper-apps/cortex-file-handler/tests/storage/LocalStorageProvider.test.js +128 -125
  38. package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +78 -73
  39. package/helper-apps/cortex-file-handler/tests/storage/StorageService.test.js +99 -99
  40. package/helper-apps/cortex-file-handler/tests/testUtils.helper.js +74 -70
  41. package/lib/azureAuthTokenHelper.js +78 -0
  42. package/lib/entityConstants.js +5 -4
  43. package/package.json +1 -1
  44. package/pathways/bing_afagent.js +13 -0
  45. package/pathways/gemini_15_vision.js +4 -0
  46. package/pathways/system/entity/tools/sys_tool_bing_search.js +1 -1
  47. package/pathways/system/entity/tools/sys_tool_bing_search_afagent.js +141 -0
  48. package/pathways/system/entity/tools/sys_tool_browser_jina.js +1 -1
  49. package/pathways/system/entity/tools/sys_tool_readfile.js +4 -0
  50. package/pathways/system/workspaces/workspace_applet_edit.js +4 -0
  51. package/pathways/transcribe_gemini.js +4 -0
  52. package/pathways/translate_subtitle.js +15 -8
  53. package/server/modelExecutor.js +4 -0
  54. package/server/plugins/azureFoundryAgentsPlugin.js +372 -0
  55. package/server/plugins/gemini15ChatPlugin.js +3 -3
  56. package/tests/azureAuthTokenHelper.test.js +150 -0
  57. package/tests/azureFoundryAgents.test.js +212 -0
@@ -1,258 +1,274 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { v4 as uuidv4 } from 'uuid';
4
- import { Storage } from '@google-cloud/storage';
5
- import axios from 'axios';
6
- import { sanitizeFilename } from '../../utils/filenameUtils.js';
1
+ import { Storage } from "@google-cloud/storage";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import {
5
+ generateShortId,
6
+ generateBlobName,
7
+ } from "../../utils/filenameUtils.js";
8
+ import axios from "axios";
7
9
 
8
- import { StorageProvider } from './StorageProvider.js';
10
+ import { StorageProvider } from "./StorageProvider.js";
9
11
 
10
12
  export class GCSStorageProvider extends StorageProvider {
11
- constructor(credentials, bucketName) {
12
- super();
13
- if (!credentials || !bucketName) {
14
- throw new Error('Missing GCS credentials or bucket name');
15
- }
16
-
17
- this.bucketName = bucketName;
18
- this.storage = new Storage({
19
- projectId: credentials.project_id,
20
- credentials: credentials
21
- });
13
+ constructor(credentials, bucketName) {
14
+ super();
15
+ if (!credentials || !bucketName) {
16
+ throw new Error("Missing GCS credentials or bucket name");
22
17
  }
23
18
 
24
- isConfigured() {
25
- return !!this.storage && !!this.bucketName;
26
- }
19
+ this.bucketName = bucketName;
20
+ this.storage = new Storage({
21
+ projectId: credentials.project_id,
22
+ credentials: credentials,
23
+ });
24
+ }
27
25
 
28
- ensureUnencodedGcsUrl(url) {
29
- if (!url || !url.startsWith('gs://')) {
30
- return url;
31
- }
32
- // Split into bucket and path parts
33
- const [bucket, ...pathParts] = url.replace('gs://', '').split('/');
34
- // Reconstruct URL with decoded path parts
35
- return `gs://${bucket}/${pathParts.map(part => decodeURIComponent(part)).join('/')}`;
26
+ isConfigured() {
27
+ return !!this.storage && !!this.bucketName;
28
+ }
29
+
30
+ ensureUnencodedGcsUrl(url) {
31
+ if (!url || !url.startsWith("gs://")) {
32
+ return url;
36
33
  }
34
+ // Split into bucket and path parts
35
+ const [bucket, ...pathParts] = url.replace("gs://", "").split("/");
36
+ // Reconstruct URL with decoded path parts
37
+ return `gs://${bucket}/${pathParts.map((part) => decodeURIComponent(part)).join("/")}`;
38
+ }
37
39
 
38
- async uploadFile(context, filePath, requestId, hash = null) {
39
- const bucket = this.storage.bucket(this.bucketName);
40
-
41
- // Use the filename with a UUID as the blob name
42
- let baseName = typeof filePath === 'string' ? sanitizeFilename(path.basename(filePath)) : requestId;
43
- // Remove any query parameters from the filename
44
- baseName = baseName.split('?')[0];
45
- const blobName = `${requestId}/${uuidv4()}_${baseName}`;
46
-
47
- if (typeof filePath === 'string') {
48
- // Use bucket.upload for file-path uploads
49
- await bucket.upload(filePath, {
50
- destination: blobName,
51
- metadata: { contentType: this.getContentType(filePath) },
52
- resumable: false,
53
- });
54
- } else {
55
- // Handle buffer uploads
56
- const file = bucket.file(blobName);
57
- await file.save(filePath, {
58
- metadata: { contentType: 'application/octet-stream' },
59
- resumable: false,
60
- });
61
- }
40
+ async uploadFile(context, filePath, requestId, hash = null, filename = null) {
41
+ const bucket = this.storage.bucket(this.bucketName);
62
42
 
63
- return {
64
- url: `gs://${this.bucketName}/${blobName}`,
65
- blobName,
66
- };
43
+ // Use provided filename or generate LLM-friendly naming
44
+ let blobName;
45
+ if (filename) {
46
+ blobName = generateBlobName(requestId, filename);
47
+ } else {
48
+ const fileExtension = path.extname(filePath);
49
+ const shortId = generateShortId();
50
+ blobName = generateBlobName(requestId, `${shortId}${fileExtension}`);
67
51
  }
68
52
 
69
- async deleteFiles(requestId) {
70
- if (!requestId) throw new Error('Missing requestId parameter');
53
+ if (typeof filePath === "string") {
54
+ // Use bucket.upload for file-path uploads
55
+ await bucket.upload(filePath, {
56
+ destination: blobName,
57
+ metadata: { contentType: this.getContentType(filePath) },
58
+ resumable: false,
59
+ });
60
+ } else {
61
+ // Handle buffer uploads
62
+ const file = bucket.file(blobName);
63
+ await file.save(filePath, {
64
+ metadata: { contentType: "application/octet-stream" },
65
+ resumable: false,
66
+ });
67
+ }
71
68
 
72
- try {
73
- let filesToDelete = [];
69
+ return {
70
+ url: `gs://${this.bucketName}/${blobName}`,
71
+ blobName,
72
+ };
73
+ }
74
74
 
75
- if (process.env.STORAGE_EMULATOR_HOST) {
76
- // When using the emulator, list objects via raw REST because client lib list may 404
77
- try {
78
- const listResp = await axios.get(
79
- `${process.env.STORAGE_EMULATOR_HOST}/storage/v1/b/${this.bucketName}/o`,
80
- {
81
- params: { prefix: requestId },
82
- validateStatus: (s) => s === 200 || s === 404,
83
- },
84
- );
75
+ async deleteFiles(requestId) {
76
+ if (!requestId) throw new Error("Missing requestId parameter");
85
77
 
86
- if (listResp.status === 200 && Array.isArray(listResp.data.items)) {
87
- filesToDelete = listResp.data.items.map((item) => ({ name: item.name }));
88
- } else if (listResp.status === 404) {
89
- // Bucket or objects not found; treat as nothing to delete
90
- filesToDelete = [];
91
- }
92
- } catch (listErr) {
93
- console.error('Error listing objects from emulator:', listErr.message || listErr);
94
- // Fallback to empty list to avoid throwing
95
- filesToDelete = [];
96
- }
97
- } else {
98
- // Real GCS – use client library
99
- const bucket = this.storage.bucket(this.bucketName);
100
- try {
101
- const [files] = await bucket.getFiles({ prefix: requestId });
102
- filesToDelete = files;
103
- } catch (libErr) {
104
- console.error('Error listing objects from GCS:', libErr.message || libErr);
105
- filesToDelete = [];
106
- }
107
- }
78
+ try {
79
+ let filesToDelete = [];
108
80
 
109
- const result = [];
110
- for (const file of filesToDelete) {
111
- const fileName = file.name || file; // for emulator list we constructed objects with name member
112
- try {
113
- if (process.env.STORAGE_EMULATOR_HOST) {
114
- await axios.delete(
115
- `${process.env.STORAGE_EMULATOR_HOST}/storage/v1/b/${this.bucketName}/o/${encodeURIComponent(fileName)}`,
116
- { validateStatus: (s) => s === 200 || s === 204 || s === 404 },
117
- );
118
- } else {
119
- // file is a File object from @google-cloud/storage
120
- if (file.delete) {
121
- await file.delete({ ignoreNotFound: true });
122
- }
123
- }
124
- result.push(fileName);
125
- } catch (error) {
126
- const code = error.code || error.response?.status;
127
- if (code === 404 || code === 412) {
128
- console.warn(`GCS file already missing during delete: ${fileName}`);
129
- } else {
130
- console.error(`Error deleting GCS file ${fileName}:`, error);
131
- }
132
- }
133
- }
81
+ if (process.env.STORAGE_EMULATOR_HOST) {
82
+ // When using the emulator, list objects via raw REST because client lib list may 404
83
+ try {
84
+ const listResp = await axios.get(
85
+ `${process.env.STORAGE_EMULATOR_HOST}/storage/v1/b/${this.bucketName}/o`,
86
+ {
87
+ params: { prefix: requestId },
88
+ validateStatus: (s) => s === 200 || s === 404,
89
+ },
90
+ );
134
91
 
135
- return result;
136
- } catch (error) {
137
- console.error('Error during GCS deleteFiles:', error);
138
- return [];
92
+ if (listResp.status === 200 && Array.isArray(listResp.data.items)) {
93
+ filesToDelete = listResp.data.items.map((item) => ({
94
+ name: item.name,
95
+ }));
96
+ } else if (listResp.status === 404) {
97
+ // Bucket or objects not found; treat as nothing to delete
98
+ filesToDelete = [];
99
+ }
100
+ } catch (listErr) {
101
+ console.error(
102
+ "Error listing objects from emulator:",
103
+ listErr.message || listErr,
104
+ );
105
+ // Fallback to empty list to avoid throwing
106
+ filesToDelete = [];
139
107
  }
140
- }
108
+ } else {
109
+ // Real GCS – use client library
110
+ const bucket = this.storage.bucket(this.bucketName);
111
+ try {
112
+ const [files] = await bucket.getFiles({ prefix: requestId });
113
+ filesToDelete = files;
114
+ } catch (libErr) {
115
+ console.error(
116
+ "Error listing objects from GCS:",
117
+ libErr.message || libErr,
118
+ );
119
+ filesToDelete = [];
120
+ }
121
+ }
141
122
 
142
- async fileExists(url) {
123
+ const result = [];
124
+ for (const file of filesToDelete) {
125
+ const fileName = file.name || file; // for emulator list we constructed objects with name member
143
126
  try {
144
- if (!url || !url.startsWith('gs://')) {
145
- return false;
127
+ if (process.env.STORAGE_EMULATOR_HOST) {
128
+ await axios.delete(
129
+ `${process.env.STORAGE_EMULATOR_HOST}/storage/v1/b/${this.bucketName}/o/${encodeURIComponent(fileName)}`,
130
+ { validateStatus: (s) => s === 200 || s === 204 || s === 404 },
131
+ );
132
+ } else {
133
+ // file is a File object from @google-cloud/storage
134
+ if (file.delete) {
135
+ await file.delete({ ignoreNotFound: true });
146
136
  }
137
+ }
138
+ result.push(fileName);
139
+ } catch (error) {
140
+ const code = error.code || error.response?.status;
141
+ if (code === 404 || code === 412) {
142
+ console.warn(`GCS file already missing during delete: ${fileName}`);
143
+ } else {
144
+ console.error(`Error deleting GCS file ${fileName}:`, error);
145
+ }
146
+ }
147
+ }
148
+
149
+ return result;
150
+ } catch (error) {
151
+ console.error("Error during GCS deleteFiles:", error);
152
+ return [];
153
+ }
154
+ }
147
155
 
148
- const unencodedUrl = this.ensureUnencodedGcsUrl(url);
149
- const urlParts = unencodedUrl.replace('gs://', '').split('/');
150
- const bucketName = urlParts[0];
151
- const fileName = urlParts.slice(1).join('/');
156
+ async fileExists(url) {
157
+ try {
158
+ if (!url || !url.startsWith("gs://")) {
159
+ return false;
160
+ }
152
161
 
153
- if (process.env.STORAGE_EMULATOR_HOST) {
154
- try {
155
- const response = await axios.get(
156
- `${process.env.STORAGE_EMULATOR_HOST}/storage/v1/b/${bucketName}/o/${encodeURIComponent(fileName)}`,
157
- { validateStatus: (status) => status === 200 || status === 404 }
158
- );
159
- return response.status === 200;
160
- } catch (error) {
161
- console.error('Error checking emulator file:', error);
162
- return false;
163
- }
164
- }
162
+ const unencodedUrl = this.ensureUnencodedGcsUrl(url);
163
+ const urlParts = unencodedUrl.replace("gs://", "").split("/");
164
+ const bucketName = urlParts[0];
165
+ const fileName = urlParts.slice(1).join("/");
165
166
 
166
- const bucket = this.storage.bucket(bucketName);
167
- const file = bucket.file(fileName);
168
- const [exists] = await file.exists();
169
- return exists;
167
+ if (process.env.STORAGE_EMULATOR_HOST) {
168
+ try {
169
+ const response = await axios.get(
170
+ `${process.env.STORAGE_EMULATOR_HOST}/storage/v1/b/${bucketName}/o/${encodeURIComponent(fileName)}`,
171
+ { validateStatus: (status) => status === 200 || status === 404 },
172
+ );
173
+ return response.status === 200;
170
174
  } catch (error) {
171
- console.error('Error checking if GCS URL exists:', error);
172
- return false;
175
+ console.error("Error checking emulator file:", error);
176
+ return false;
173
177
  }
178
+ }
179
+
180
+ const bucket = this.storage.bucket(bucketName);
181
+ const file = bucket.file(fileName);
182
+ const [exists] = await file.exists();
183
+ return exists;
184
+ } catch (error) {
185
+ console.error("Error checking if GCS URL exists:", error);
186
+ return false;
174
187
  }
188
+ }
175
189
 
176
- async downloadFile(url, destinationPath) {
177
- if (!url || !url.startsWith('gs://')) {
178
- throw new Error('Invalid GCS URL');
179
- }
190
+ async downloadFile(url, destinationPath) {
191
+ if (!url || !url.startsWith("gs://")) {
192
+ throw new Error("Invalid GCS URL");
193
+ }
180
194
 
181
- const urlParts = url.replace('gs://', '').split('/');
182
- const bucketName = urlParts[0];
183
- const fileName = urlParts.slice(1).join('/');
195
+ const urlParts = url.replace("gs://", "").split("/");
196
+ const bucketName = urlParts[0];
197
+ const fileName = urlParts.slice(1).join("/");
184
198
 
185
- if (process.env.STORAGE_EMULATOR_HOST) {
186
- // Use axios to download from emulator
187
- const response = await axios({
188
- method: 'GET',
189
- url: `${process.env.STORAGE_EMULATOR_HOST}/storage/v1/b/${bucketName}/o/${encodeURIComponent(fileName)}?alt=media`,
190
- responseType: 'stream'
191
- });
192
-
193
- // Write the response to file
194
- const writer = fs.createWriteStream(destinationPath);
195
- return new Promise((resolve, reject) => {
196
- response.data.pipe(writer);
197
- writer.on('finish', resolve);
198
- writer.on('error', reject);
199
- response.data.on('error', reject);
200
- });
201
- } else {
202
- // Use GCS client for real GCS
203
- const bucket = this.storage.bucket(bucketName);
204
- const file = bucket.file(fileName);
205
- await file.download({ destination: destinationPath });
206
- }
199
+ if (process.env.STORAGE_EMULATOR_HOST) {
200
+ // Use axios to download from emulator
201
+ const response = await axios({
202
+ method: "GET",
203
+ url: `${process.env.STORAGE_EMULATOR_HOST}/storage/v1/b/${bucketName}/o/${encodeURIComponent(fileName)}?alt=media`,
204
+ responseType: "stream",
205
+ });
206
+
207
+ // Write the response to file
208
+ const writer = fs.createWriteStream(destinationPath);
209
+ return new Promise((resolve, reject) => {
210
+ response.data.pipe(writer);
211
+ writer.on("finish", resolve);
212
+ writer.on("error", reject);
213
+ response.data.on("error", reject);
214
+ });
215
+ } else {
216
+ // Use GCS client for real GCS
217
+ const bucket = this.storage.bucket(bucketName);
218
+ const file = bucket.file(fileName);
219
+ await file.download({ destination: destinationPath });
207
220
  }
221
+ }
208
222
 
209
- async cleanup(urls) {
210
- if (!urls || !urls.length) return;
211
-
212
- const bucket = this.storage.bucket(this.bucketName);
213
- const result = [];
214
-
215
- for (const url of urls) {
216
- try {
217
- if (!url.startsWith('gs://')) continue;
218
-
219
- const urlParts = url.replace('gs://', '').split('/');
220
- const bucketName = urlParts[0];
221
- const fileName = urlParts.slice(1).join('/');
222
-
223
- if (bucketName === this.bucketName) {
224
- const file = bucket.file(fileName);
225
- await file.delete();
226
- result.push(fileName);
227
- }
228
- } catch (error) {
229
- console.error(`Error cleaning up GCS file ${url}:`, error);
230
- }
223
+ async cleanup(urls) {
224
+ if (!urls || !urls.length) return;
225
+
226
+ const bucket = this.storage.bucket(this.bucketName);
227
+ const result = [];
228
+
229
+ for (const url of urls) {
230
+ try {
231
+ if (!url.startsWith("gs://")) continue;
232
+
233
+ const urlParts = url.replace("gs://", "").split("/");
234
+ const bucketName = urlParts[0];
235
+ const fileName = urlParts.slice(1).join("/");
236
+
237
+ if (bucketName === this.bucketName) {
238
+ const file = bucket.file(fileName);
239
+ await file.delete();
240
+ result.push(fileName);
231
241
  }
232
-
233
- return result;
242
+ } catch (error) {
243
+ console.error(`Error cleaning up GCS file ${url}:`, error);
244
+ }
234
245
  }
235
246
 
236
- isEncoded(str) {
237
- return /%[0-9A-Fa-f]{2}/.test(str);
238
- }
247
+ return result;
248
+ }
239
249
 
240
- getContentType(filePath) {
241
- const ext = path.extname(filePath).toLowerCase();
242
- const mimeTypes = {
243
- '.txt': 'text/plain',
244
- '.pdf': 'application/pdf',
245
- '.doc': 'application/msword',
246
- '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
247
- '.xls': 'application/vnd.ms-excel',
248
- '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
249
- '.jpg': 'image/jpeg',
250
- '.jpeg': 'image/jpeg',
251
- '.png': 'image/png',
252
- '.gif': 'image/gif',
253
- '.mp4': 'video/mp4',
254
- '.mp3': 'audio/mpeg'
255
- };
256
- return mimeTypes[ext] || 'application/octet-stream';
257
- }
258
- }
250
+ isEncoded(str) {
251
+ return /%[0-9A-Fa-f]{2}/.test(str);
252
+ }
253
+
254
+ getContentType(filePath) {
255
+ const ext = path.extname(filePath).toLowerCase();
256
+ const mimeTypes = {
257
+ ".txt": "text/plain",
258
+ ".pdf": "application/pdf",
259
+ ".doc": "application/msword",
260
+ ".docx":
261
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
262
+ ".xls": "application/vnd.ms-excel",
263
+ ".xlsx":
264
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
265
+ ".jpg": "image/jpeg",
266
+ ".jpeg": "image/jpeg",
267
+ ".png": "image/png",
268
+ ".gif": "image/gif",
269
+ ".mp4": "video/mp4",
270
+ ".mp3": "audio/mpeg",
271
+ };
272
+ return mimeTypes[ext] || "application/octet-stream";
273
+ }
274
+ }