@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.
- package/config/default.example.json +15 -1
- package/config.js +42 -0
- package/helper-apps/cortex-file-handler/INTERFACE.md +20 -9
- package/helper-apps/cortex-file-handler/package-lock.json +2 -2
- package/helper-apps/cortex-file-handler/package.json +1 -1
- package/helper-apps/cortex-file-handler/scripts/setup-azure-container.js +17 -17
- package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +35 -35
- package/helper-apps/cortex-file-handler/src/blobHandler.js +1010 -909
- package/helper-apps/cortex-file-handler/src/constants.js +98 -98
- package/helper-apps/cortex-file-handler/src/docHelper.js +27 -27
- package/helper-apps/cortex-file-handler/src/fileChunker.js +224 -214
- package/helper-apps/cortex-file-handler/src/helper.js +93 -93
- package/helper-apps/cortex-file-handler/src/index.js +584 -550
- package/helper-apps/cortex-file-handler/src/localFileHandler.js +86 -86
- package/helper-apps/cortex-file-handler/src/redis.js +186 -90
- package/helper-apps/cortex-file-handler/src/services/ConversionService.js +301 -273
- package/helper-apps/cortex-file-handler/src/services/FileConversionService.js +55 -55
- package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +174 -154
- package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +239 -223
- package/helper-apps/cortex-file-handler/src/services/storage/LocalStorageProvider.js +161 -159
- package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +73 -71
- package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +46 -45
- package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +256 -213
- package/helper-apps/cortex-file-handler/src/start.js +4 -1
- package/helper-apps/cortex-file-handler/src/utils/filenameUtils.js +59 -25
- package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +119 -116
- package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +257 -257
- package/helper-apps/cortex-file-handler/tests/cleanup.test.js +676 -0
- package/helper-apps/cortex-file-handler/tests/conversionResilience.test.js +124 -124
- package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +249 -208
- package/helper-apps/cortex-file-handler/tests/fileUpload.test.js +439 -380
- package/helper-apps/cortex-file-handler/tests/getOperations.test.js +299 -263
- package/helper-apps/cortex-file-handler/tests/postOperations.test.js +265 -239
- package/helper-apps/cortex-file-handler/tests/start.test.js +1230 -1201
- package/helper-apps/cortex-file-handler/tests/storage/AzureStorageProvider.test.js +110 -105
- package/helper-apps/cortex-file-handler/tests/storage/GCSStorageProvider.test.js +201 -175
- package/helper-apps/cortex-file-handler/tests/storage/LocalStorageProvider.test.js +128 -125
- package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +78 -73
- package/helper-apps/cortex-file-handler/tests/storage/StorageService.test.js +99 -99
- package/helper-apps/cortex-file-handler/tests/testUtils.helper.js +74 -70
- package/lib/azureAuthTokenHelper.js +78 -0
- package/lib/entityConstants.js +5 -4
- package/package.json +1 -1
- package/pathways/bing_afagent.js +13 -0
- package/pathways/gemini_15_vision.js +4 -0
- package/pathways/system/entity/tools/sys_tool_bing_search.js +1 -1
- package/pathways/system/entity/tools/sys_tool_bing_search_afagent.js +141 -0
- package/pathways/system/entity/tools/sys_tool_browser_jina.js +1 -1
- package/pathways/system/entity/tools/sys_tool_readfile.js +4 -0
- package/pathways/system/workspaces/workspace_applet_edit.js +4 -0
- package/pathways/transcribe_gemini.js +4 -0
- package/pathways/translate_subtitle.js +15 -8
- package/server/modelExecutor.js +4 -0
- package/server/plugins/azureFoundryAgentsPlugin.js +372 -0
- package/server/plugins/gemini15ChatPlugin.js +3 -3
- package/tests/azureAuthTokenHelper.test.js +150 -0
- package/tests/azureFoundryAgents.test.js +212 -0
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import { ConversionService } from
|
|
2
|
-
import { getFileStoreMap, setFileStoreMap } from
|
|
3
|
-
import { urlExists } from
|
|
4
|
-
import { gcsUrlExists, uploadChunkToGCS, gcs } from
|
|
5
|
-
import { downloadFile } from
|
|
6
|
-
import { saveFileToBlob } from
|
|
7
|
-
import { moveFileToPublicFolder } from
|
|
8
|
-
import { v4 as uuidv4 } from
|
|
1
|
+
import { ConversionService } from "./ConversionService.js";
|
|
2
|
+
import { getFileStoreMap, setFileStoreMap } from "../redis.js";
|
|
3
|
+
import { urlExists } from "../helper.js";
|
|
4
|
+
import { gcsUrlExists, uploadChunkToGCS, gcs } from "../blobHandler.js";
|
|
5
|
+
import { downloadFile } from "../fileChunker.js";
|
|
6
|
+
import { saveFileToBlob } from "../blobHandler.js";
|
|
7
|
+
import { moveFileToPublicFolder } from "../localFileHandler.js";
|
|
8
|
+
import { v4 as uuidv4 } from "uuid";
|
|
9
9
|
|
|
10
10
|
export class FileConversionService extends ConversionService {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
11
|
+
constructor(context, useAzure = true) {
|
|
12
|
+
super(context);
|
|
13
|
+
this.useAzure = useAzure;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async _getFileStoreMap(key) {
|
|
17
|
+
return getFileStoreMap(key);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async _setFileStoreMap(key, value) {
|
|
21
|
+
return setFileStoreMap(key, value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async _urlExists(url) {
|
|
25
|
+
return urlExists(url);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async _gcsUrlExists(url) {
|
|
29
|
+
return gcsUrlExists(url);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async _downloadFile(url, destination) {
|
|
33
|
+
return downloadFile(url, destination);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async _saveConvertedFile(filePath, requestId, filename = null) {
|
|
37
|
+
// Generate a fallback requestId if none supplied (e.g. during checkHash calls)
|
|
38
|
+
const reqId = requestId || uuidv4();
|
|
39
|
+
|
|
40
|
+
let fileUrl;
|
|
41
|
+
if (this.useAzure) {
|
|
42
|
+
const savedBlob = await saveFileToBlob(filePath, reqId, filename);
|
|
43
|
+
fileUrl = savedBlob.url;
|
|
44
|
+
} else {
|
|
45
|
+
fileUrl = await moveFileToPublicFolder(filePath, reqId);
|
|
46
|
+
}
|
|
47
|
+
return { url: fileUrl };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async _uploadChunkToGCS(filePath, requestId, filename = null) {
|
|
51
|
+
return uploadChunkToGCS(filePath, requestId, filename);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_isGCSConfigured() {
|
|
55
|
+
return !!gcs;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,177 +1,197 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
4
1
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
BlobServiceClient,
|
|
3
|
+
StorageSharedKeyCredential,
|
|
4
|
+
generateBlobSASQueryParameters,
|
|
5
|
+
} from "@azure/storage-blob";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
|
|
9
|
+
import { StorageProvider } from "./StorageProvider.js";
|
|
10
|
+
import {
|
|
11
|
+
generateShortId,
|
|
12
|
+
generateBlobName,
|
|
13
|
+
} from "../../utils/filenameUtils.js";
|
|
12
14
|
|
|
13
15
|
export class AzureStorageProvider extends StorageProvider {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.containerName = containerName;
|
|
21
|
-
this.sasTokenLifeDays = process.env.SAS_TOKEN_LIFE_DAYS || 30;
|
|
16
|
+
constructor(connectionString, containerName) {
|
|
17
|
+
super();
|
|
18
|
+
if (!connectionString || !containerName) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"Missing Azure Storage connection string or container name",
|
|
21
|
+
);
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
this.connectionString = connectionString;
|
|
24
|
+
this.containerName = containerName;
|
|
25
|
+
this.sasTokenLifeDays = process.env.SAS_TOKEN_LIFE_DAYS || 30;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async getBlobClient() {
|
|
29
|
+
const blobServiceClient = BlobServiceClient.fromConnectionString(
|
|
30
|
+
this.connectionString,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Ensure service version is set
|
|
34
|
+
const serviceProperties = await blobServiceClient.getProperties();
|
|
35
|
+
if (!serviceProperties.defaultServiceVersion) {
|
|
36
|
+
serviceProperties.defaultServiceVersion = "2020-02-10";
|
|
37
|
+
await blobServiceClient.setProperties(serviceProperties);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
const containerClient = blobServiceClient.getContainerClient(
|
|
41
|
+
this.containerName,
|
|
42
|
+
);
|
|
43
|
+
return { blobServiceClient, containerClient };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
generateSASToken(containerClient, blobName) {
|
|
47
|
+
const { accountName, accountKey } = containerClient.credential;
|
|
48
|
+
const sharedKeyCredential = new StorageSharedKeyCredential(
|
|
49
|
+
accountName,
|
|
50
|
+
accountKey,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const sasOptions = {
|
|
54
|
+
containerName: containerClient.containerName,
|
|
55
|
+
blobName: blobName,
|
|
56
|
+
permissions: "r",
|
|
57
|
+
startsOn: new Date(),
|
|
58
|
+
expiresOn: new Date(
|
|
59
|
+
new Date().valueOf() + this.sasTokenLifeDays * 24 * 60 * 60 * 1000,
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return generateBlobSASQueryParameters(
|
|
64
|
+
sasOptions,
|
|
65
|
+
sharedKeyCredential,
|
|
66
|
+
).toString();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async uploadFile(context, filePath, requestId, hash = null, filename = null) {
|
|
70
|
+
const { containerClient } = await this.getBlobClient();
|
|
71
|
+
|
|
72
|
+
// Use provided filename or generate LLM-friendly naming
|
|
73
|
+
let blobName;
|
|
74
|
+
if (filename) {
|
|
75
|
+
blobName = generateBlobName(requestId, filename);
|
|
76
|
+
} else {
|
|
77
|
+
const fileExtension = path.extname(filePath);
|
|
78
|
+
const shortId = generateShortId();
|
|
79
|
+
blobName = generateBlobName(requestId, `${shortId}${fileExtension}`);
|
|
54
80
|
}
|
|
55
81
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Create a consistent, sanitised blob name and encode once for Azure URL
|
|
60
|
-
let baseName = sanitizeFilename(path.basename(filePath));
|
|
61
|
-
baseName = encodeURIComponent(baseName);
|
|
62
|
-
const blobName = `${requestId}/${uuidv4()}_${baseName}`;
|
|
63
|
-
|
|
64
|
-
// Create a read stream for the file
|
|
65
|
-
const fileStream = fs.createReadStream(filePath);
|
|
66
|
-
|
|
67
|
-
// Upload the file to Azure Blob Storage using the stream
|
|
68
|
-
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
|
|
69
|
-
await blockBlobClient.uploadStream(fileStream);
|
|
70
|
-
|
|
71
|
-
// Generate SAS token after successful upload
|
|
72
|
-
const sasToken = this.generateSASToken(containerClient, blobName);
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
url: `${blockBlobClient.url}?${sasToken}`,
|
|
76
|
-
blobName: blobName
|
|
77
|
-
};
|
|
78
|
-
}
|
|
82
|
+
// Create a read stream for the file
|
|
83
|
+
const fileStream = fs.createReadStream(filePath);
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
result.push(blob.name);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
if (error.statusCode === 404) {
|
|
95
|
-
console.warn(`Azure blob already missing during delete: ${blob.name}`);
|
|
96
|
-
} else {
|
|
97
|
-
throw error;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return result;
|
|
104
|
-
}
|
|
85
|
+
// Upload the file to Azure Blob Storage using the stream
|
|
86
|
+
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
|
|
87
|
+
await blockBlobClient.uploadStream(fileStream);
|
|
88
|
+
|
|
89
|
+
// Generate SAS token after successful upload
|
|
90
|
+
const sasToken = this.generateSASToken(containerClient, blobName);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
url: `${blockBlobClient.url}?${sasToken}`,
|
|
94
|
+
blobName: blobName,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
105
97
|
|
|
106
|
-
|
|
98
|
+
async deleteFiles(requestId) {
|
|
99
|
+
if (!requestId) throw new Error("Missing requestId parameter");
|
|
100
|
+
const { containerClient } = await this.getBlobClient();
|
|
101
|
+
|
|
102
|
+
const result = [];
|
|
103
|
+
const blobs = containerClient.listBlobsFlat();
|
|
104
|
+
|
|
105
|
+
for await (const blob of blobs) {
|
|
106
|
+
if (blob.name.startsWith(requestId)) {
|
|
107
|
+
const blockBlobClient = containerClient.getBlockBlobClient(blob.name);
|
|
107
108
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (headResp.ok) return true;
|
|
111
|
-
|
|
112
|
-
// Some emulators (e.g. Azurite) may not properly support HEAD with SAS.
|
|
113
|
-
// Fall back to a ranged GET of a single byte.
|
|
114
|
-
const getResp = await fetch(url, {
|
|
115
|
-
method: 'GET',
|
|
116
|
-
headers: { Range: 'bytes=0-0' },
|
|
117
|
-
});
|
|
118
|
-
return getResp.ok || getResp.status === 206; // 206 Partial Content
|
|
109
|
+
await blockBlobClient.delete();
|
|
110
|
+
result.push(blob.name);
|
|
119
111
|
} catch (error) {
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
if (error.statusCode === 404) {
|
|
113
|
+
console.warn(
|
|
114
|
+
`Azure blob already missing during delete: ${blob.name}`,
|
|
115
|
+
);
|
|
116
|
+
} else {
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
122
119
|
}
|
|
120
|
+
}
|
|
123
121
|
}
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async fileExists(url) {
|
|
127
|
+
try {
|
|
128
|
+
// First attempt a lightweight HEAD request
|
|
129
|
+
const headResp = await fetch(url, { method: "HEAD" });
|
|
130
|
+
if (headResp.ok) return true;
|
|
131
|
+
|
|
132
|
+
// Some emulators (e.g. Azurite) may not properly support HEAD with SAS.
|
|
133
|
+
// Fall back to a ranged GET of a single byte.
|
|
134
|
+
const getResp = await fetch(url, {
|
|
135
|
+
method: "GET",
|
|
136
|
+
headers: { Range: "bytes=0-0" },
|
|
137
|
+
});
|
|
138
|
+
return getResp.ok || getResp.status === 206; // 206 Partial Content
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error("Error checking if file exists:", error);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
130
144
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
await fs.promises.writeFile(destinationPath, buffer);
|
|
145
|
+
async downloadFile(url, destinationPath) {
|
|
146
|
+
const response = await fetch(url);
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw new Error(`Failed to download file: ${response.statusText}`);
|
|
136
149
|
}
|
|
137
150
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
// In newer Node versions, response.body is a web-stream, not a Node stream.
|
|
152
|
+
// Easier + reliable: read into a Buffer then write to file.
|
|
153
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
154
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
155
|
+
await fs.promises.writeFile(destinationPath, buffer);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async cleanup(urls) {
|
|
159
|
+
if (!urls || !urls.length) return;
|
|
160
|
+
|
|
161
|
+
const { containerClient } = await this.getBlobClient();
|
|
162
|
+
const result = [];
|
|
163
|
+
|
|
164
|
+
for (const url of urls) {
|
|
165
|
+
try {
|
|
166
|
+
const blobName = this.extractBlobNameFromUrl(url);
|
|
167
|
+
if (blobName) {
|
|
168
|
+
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
|
|
169
|
+
await blockBlobClient.delete();
|
|
170
|
+
result.push(blobName);
|
|
155
171
|
}
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(`Error cleaning up blob ${url}:`, error);
|
|
174
|
+
}
|
|
158
175
|
}
|
|
159
176
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
163
179
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
isEncoded(str) {
|
|
181
|
+
return /%[0-9A-Fa-f]{2}/.test(str);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
extractBlobNameFromUrl(url) {
|
|
185
|
+
try {
|
|
186
|
+
const urlObj = new URL(url);
|
|
187
|
+
const pathParts = urlObj.pathname.split("/");
|
|
188
|
+
const containerIndex = pathParts.indexOf(this.containerName);
|
|
189
|
+
if (containerIndex === -1) return null;
|
|
190
|
+
|
|
191
|
+
return pathParts.slice(containerIndex + 1).join("/");
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error("Error extracting blob name from URL:", error);
|
|
194
|
+
return null;
|
|
176
195
|
}
|
|
177
|
-
}
|
|
196
|
+
}
|
|
197
|
+
}
|