@aj-archipelago/cortex 1.1.20 → 1.1.22

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 (101) hide show
  1. package/config/default.example.json +84 -0
  2. package/config.js +17 -4
  3. package/helper-apps/cortex-file-handler/blobHandler.js +144 -100
  4. package/helper-apps/cortex-file-handler/fileChunker.js +13 -8
  5. package/helper-apps/cortex-file-handler/index.js +56 -9
  6. package/lib/pathwayTools.js +7 -1
  7. package/lib/requestExecutor.js +4 -4
  8. package/lib/util.js +163 -1
  9. package/package.json +2 -1
  10. package/pathways/categorize.js +23 -0
  11. package/pathways/chat.js +1 -1
  12. package/pathways/chat_code.js +19 -0
  13. package/pathways/chat_context.js +19 -0
  14. package/pathways/chat_jarvis.js +19 -0
  15. package/pathways/chat_persist.js +23 -0
  16. package/pathways/code_review.js +17 -0
  17. package/pathways/cognitive_delete.js +2 -1
  18. package/pathways/cognitive_insert.js +1 -0
  19. package/pathways/cognitive_search.js +1 -0
  20. package/pathways/embeddings.js +1 -1
  21. package/pathways/expand_story.js +12 -0
  22. package/pathways/format_paragraph_turbo.js +16 -0
  23. package/pathways/format_summarization.js +21 -0
  24. package/pathways/gemini_15_vision.js +20 -0
  25. package/pathways/gemini_vision.js +20 -0
  26. package/pathways/grammar.js +30 -0
  27. package/pathways/hashtags.js +19 -0
  28. package/pathways/headline.js +43 -0
  29. package/pathways/headline_custom.js +169 -0
  30. package/pathways/highlights.js +22 -0
  31. package/pathways/image.js +2 -1
  32. package/pathways/index.js +109 -17
  33. package/pathways/jira_story.js +18 -0
  34. package/pathways/keywords.js +4 -0
  35. package/pathways/language.js +17 -6
  36. package/pathways/locations.js +93 -0
  37. package/pathways/quotes.js +19 -0
  38. package/pathways/rag.js +207 -0
  39. package/pathways/rag_jarvis.js +254 -0
  40. package/pathways/rag_search_helper.js +21 -0
  41. package/pathways/readme.js +18 -0
  42. package/pathways/release_notes.js +16 -0
  43. package/pathways/remove_content.js +31 -0
  44. package/pathways/retrieval.js +23 -0
  45. package/pathways/run_claude35_sonnet.js +21 -0
  46. package/pathways/run_claude3_haiku.js +20 -0
  47. package/pathways/run_gpt35turbo.js +20 -0
  48. package/pathways/run_gpt4.js +20 -0
  49. package/pathways/run_gpt4_32.js +20 -0
  50. package/pathways/select_extension.js +6 -0
  51. package/pathways/select_services.js +10 -0
  52. package/pathways/spelling.js +3 -0
  53. package/pathways/story_angles.js +13 -0
  54. package/pathways/styleguide/styleguide.js +221 -0
  55. package/pathways/styleguidemulti.js +127 -0
  56. package/pathways/subhead.js +48 -0
  57. package/pathways/summarize_turbo.js +98 -0
  58. package/pathways/summary.js +31 -12
  59. package/pathways/sys_claude_35_sonnet.js +19 -0
  60. package/pathways/sys_claude_3_haiku.js +19 -0
  61. package/pathways/sys_google_chat.js +19 -0
  62. package/pathways/sys_google_code_chat.js +19 -0
  63. package/pathways/sys_google_gemini_chat.js +23 -0
  64. package/pathways/sys_openai_chat.js +2 -2
  65. package/pathways/sys_openai_chat_16.js +19 -0
  66. package/pathways/sys_openai_chat_gpt4.js +19 -0
  67. package/pathways/sys_openai_chat_gpt4_32.js +19 -0
  68. package/pathways/sys_openai_chat_gpt4_turbo.js +19 -0
  69. package/pathways/tags.js +25 -0
  70. package/pathways/taxonomy.js +135 -0
  71. package/pathways/timeline.js +51 -0
  72. package/pathways/topics.js +25 -0
  73. package/pathways/topics_sentiment.js +20 -0
  74. package/pathways/transcribe.js +2 -4
  75. package/pathways/transcribe_neuralspace.js +18 -0
  76. package/pathways/translate.js +10 -12
  77. package/pathways/translate_azure.js +13 -0
  78. package/pathways/translate_context.js +21 -0
  79. package/pathways/translate_gpt4.js +19 -0
  80. package/pathways/translate_gpt4_turbo.js +19 -0
  81. package/pathways/translate_turbo.js +19 -0
  82. package/pathways/vision.js +9 -7
  83. package/server/modelExecutor.js +4 -0
  84. package/server/pathwayResolver.js +19 -1
  85. package/server/plugins/azureCognitivePlugin.js +10 -1
  86. package/server/plugins/claude3VertexPlugin.js +2 -1
  87. package/server/plugins/gemini15ChatPlugin.js +8 -3
  88. package/server/plugins/gemini15VisionPlugin.js +19 -3
  89. package/server/plugins/geminiChatPlugin.js +1 -1
  90. package/server/plugins/geminiVisionPlugin.js +2 -3
  91. package/server/plugins/neuralSpacePlugin.js +252 -0
  92. package/server/plugins/openAiVisionPlugin.js +32 -13
  93. package/server/plugins/openAiWhisperPlugin.js +5 -152
  94. package/server/plugins/palmChatPlugin.js +1 -1
  95. package/server/resolver.js +3 -4
  96. package/server/typeDef.js +1 -0
  97. package/tests/claude3VertexPlugin.test.js +214 -0
  98. package/tests/main.test.js +2 -2
  99. package/tests/mocks.js +2 -0
  100. package/tests/openAiChatPlugin.test.js +4 -0
  101. package/tests/vision.test.js +0 -34
@@ -12,6 +12,62 @@
12
12
  "requestsPerSecond": 10,
13
13
  "maxTokenLength": 2000
14
14
  },
15
+
16
+ "gemini-pro-chat": {
17
+ "type": "GEMINI-CHAT",
18
+ "url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/gemini-pro:streamGenerateContent",
19
+ "headers": {
20
+ "Content-Type": "application/json"
21
+ },
22
+ "requestsPerSecond": 10,
23
+ "maxTokenLength": 32768,
24
+ "maxReturnTokens": 8192,
25
+ "supportsStreaming": true
26
+ },
27
+ "gemini-pro-vision": {
28
+ "type": "GEMINI-VISION",
29
+ "url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/gemini-pro-vision:streamGenerateContent",
30
+ "headers": {
31
+ "Content-Type": "application/json"
32
+ },
33
+ "requestsPerSecond": 10,
34
+ "maxTokenLength": 32768,
35
+ "maxReturnTokens": 2048,
36
+ "supportsStreaming": true
37
+ },
38
+ "gemini-pro-15-vision": {
39
+ "type": "GEMINI-VISION",
40
+ "url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/gemini-1.5-pro-preview-0215:streamGenerateContent",
41
+ "headers": {
42
+ "Content-Type": "application/json"
43
+ },
44
+ "requestsPerSecond": 10,
45
+ "maxTokenLength": 1048576,
46
+ "maxReturnTokens": 2048,
47
+ "supportsStreaming": true
48
+ },
49
+ "claude-3-haiku-vertex": {
50
+ "type": "CLAUDE-3-VERTEX",
51
+ "url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/anthropic/models/claude-3-haiku@20240307",
52
+ "headers": {
53
+ "Content-Type": "application/json"
54
+ },
55
+ "requestsPerSecond": 10,
56
+ "maxTokenLength": 200000,
57
+ "maxReturnTokens": 2048,
58
+ "supportsStreaming": true
59
+ },
60
+ "claude-35-sonnet-vertex": {
61
+ "type": "CLAUDE-3-VERTEX",
62
+ "url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/anthropic/models/claude-3-5-sonnet@20240229",
63
+ "headers": {
64
+ "Content-Type": "application/json"
65
+ },
66
+ "requestsPerSecond": 10,
67
+ "maxTokenLength": 200000,
68
+ "maxReturnTokens": 2048,
69
+ "supportsStreaming": true
70
+ },
15
71
  "oai-gpturbo": {
16
72
  "type": "OPENAI-CHAT",
17
73
  "url": "https://api.openai.com/v1/chat/completions",
@@ -38,6 +94,34 @@
38
94
  "requestsPerSecond": 10,
39
95
  "maxTokenLength": 8192
40
96
  },
97
+ "oai-gpt4-32": {
98
+ "type": "OPENAI-CHAT",
99
+ "url": "https://api.openai.com/v1/chat/completions",
100
+ "headers": {
101
+ "Authorization": "Bearer {{OPENAI_API_KEY}}",
102
+ "Content-Type": "application/json"
103
+ },
104
+ "params": {
105
+ "model": "gpt-4-32"
106
+ },
107
+ "requestsPerSecond": 10,
108
+ "maxTokenLength": 32768
109
+ },
110
+ "oai-gpt4o": {
111
+ "type": "OPENAI-VISION",
112
+ "url": "https://api.openai.com/v1/chat/completions",
113
+ "headers": {
114
+ "Authorization": "Bearer {{OPENAI_API_KEY}}",
115
+ "Content-Type": "application/json"
116
+ },
117
+ "params": {
118
+ "model": "gpt-4o"
119
+ },
120
+ "requestsPerSecond": 10,
121
+ "maxTokenLength": 131072,
122
+ "maxReturnTokens": 4096,
123
+ "supportsStreaming": true
124
+ },
41
125
  "palm-text": {
42
126
  "type": "PALM-COMPLETION",
43
127
  "url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/text-bison@001:predict",
package/config.js CHANGED
@@ -111,6 +111,13 @@ var config = convict({
111
111
  "model": "whisper-1"
112
112
  },
113
113
  },
114
+ "neuralspace": {
115
+ "type": "NEURALSPACE",
116
+ "url": "https://voice.neuralspace.ai/api/v2/jobs",
117
+ "headers": {
118
+ "Authorization": "{{NEURALSPACE_API_KEY}}",
119
+ },
120
+ },
114
121
  "azure-cognitive": {
115
122
  "type": "AZURE-COGNITIVE",
116
123
  "url": "{{{AZURE_COGNITIVE_API_URL}}}",
@@ -132,7 +139,7 @@ var config = convict({
132
139
  },
133
140
  "maxTokenLength": 8192,
134
141
  },
135
- "oai-gpt4-vision": {
142
+ "oai-gpt4o": {
136
143
  "type": "OPENAI-VISION",
137
144
  "url": "https://api.openai.com/v1/chat/completions",
138
145
  "headers": {
@@ -140,10 +147,11 @@ var config = convict({
140
147
  "Content-Type": "application/json"
141
148
  },
142
149
  "params": {
143
- "model": "gpt-4-vision-preview"
150
+ "model": "gpt-4o"
144
151
  },
145
- "requestsPerSecond": 1,
146
- "maxTokenLength": 128000,
152
+ "requestsPerSecond": 50,
153
+ "maxTokenLength": 131072,
154
+ "maxReturnTokens": 4096,
147
155
  "supportsStreaming": true
148
156
  },
149
157
  "azure-bing": {
@@ -222,6 +230,11 @@ var config = convict({
222
230
  default: 0,
223
231
  env: 'SUBSCRIPTION_KEEP_ALIVE'
224
232
  },
233
+ neuralSpaceApiKey: {
234
+ format: String,
235
+ default: null,
236
+ env: 'NEURALSPACE_API_KEY'
237
+ },
225
238
  });
226
239
 
227
240
  // Read in environment variables and set up service configuration
@@ -36,6 +36,15 @@ const VIDEO_EXTENSIONS = [
36
36
  ".mkv",
37
37
  ];
38
38
 
39
+ const AUDIO_EXTENSIONS = [
40
+ ".mp3",
41
+ ".wav",
42
+ ".ogg",
43
+ ".flac",
44
+ ".aac",
45
+ ".aiff",
46
+ ];
47
+
39
48
  function isBase64(str) {
40
49
  try {
41
50
  return btoa(atob(str)) == str;
@@ -76,6 +85,33 @@ if (!GCP_PROJECT_ID || !GCP_SERVICE_ACCOUNT) {
76
85
 
77
86
  const GCS_BUCKETNAME = process.env.GCS_BUCKETNAME || "cortextempfiles";
78
87
 
88
+
89
+ async function gcsUrlExists(url, defaultReturn = true) {
90
+ try {
91
+ if(!url) {
92
+ return defaultReturn; // Cannot check return
93
+ }
94
+ if (!gcs) {
95
+ console.warn('GCS environment variables are not set. Unable to check if URL exists in GCS.');
96
+ return defaultReturn; // Cannot check return
97
+ }
98
+
99
+ const urlParts = url.replace('gs://', '').split('/');
100
+ const bucketName = urlParts[0];
101
+ const fileName = urlParts.slice(1).join('/');
102
+
103
+ const bucket = gcs.bucket(bucketName);
104
+ const file = bucket.file(fileName);
105
+
106
+ const [exists] = await file.exists();
107
+
108
+ return exists;
109
+ } catch (error) {
110
+ console.error('Error checking if GCS URL exists:', error);
111
+ return false;
112
+ }
113
+ }
114
+
79
115
  const getBlobClient = async () => {
80
116
  const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
81
117
  const containerName = process.env.AZURE_STORAGE_CONTAINER_NAME;
@@ -101,7 +137,7 @@ const getBlobClient = async () => {
101
137
  async function saveFileToBlob(chunkPath, requestId) {
102
138
  const { containerClient } = await getBlobClient();
103
139
  // Use the filename with a UUID as the blob name
104
- const blobName = `${requestId}/${uuidv4()}_${path.basename(chunkPath)}`;
140
+ const blobName = `${requestId}/${uuidv4()}_${encodeURIComponent(path.basename(chunkPath))}`;
105
141
 
106
142
  // Create a read stream for the chunk file
107
143
  const fileStream = fs.createReadStream(chunkPath);
@@ -135,131 +171,139 @@ async function deleteBlob(requestId) {
135
171
  return result;
136
172
  }
137
173
 
138
- async function uploadBlob(
139
- context,
140
- req,
141
- saveToLocal = false,
142
- useGoogle = false
143
- ) {
174
+ async function uploadBlob(context, req, saveToLocal = false, useGoogle = false, filePath=null) {
144
175
  return new Promise((resolve, reject) => {
145
176
  try {
146
- const busboy = Busboy({ headers: req.headers });
147
177
  let requestId = uuidv4();
148
178
  let body = {};
149
179
 
150
- busboy.on("field", (fieldname, value) => {
151
- if (fieldname === "requestId") {
152
- requestId = value;
153
- } else if (fieldname === "useGoogle") {
154
- useGoogle = value;
155
- }
156
- });
157
-
158
- busboy.on("file", async (fieldname, file, info) => {
159
- //do not use google if file is not image or video
160
- const ext = path.extname(info.filename).toLowerCase();
161
- const canUseGoogle = IMAGE_EXTENSIONS.includes(ext) || VIDEO_EXTENSIONS.includes(ext);
162
- if(!canUseGoogle) {
163
- useGoogle = false;
164
- }
180
+ // If filePath is given, we are dealing with local file and not form-data
181
+ if (filePath) {
182
+ const file = fs.createReadStream(filePath);
183
+ const filename = path.basename(filePath);
184
+ uploadFile(context, requestId, body, saveToLocal, useGoogle, file, filename, resolve)
185
+ } else {
186
+ // Otherwise, continue working with form-data
187
+ const busboy = Busboy({ headers: req.headers });
188
+
189
+ busboy.on("field", (fieldname, value) => {
190
+ if (fieldname === "requestId") {
191
+ requestId = value;
192
+ } else if (fieldname === "useGoogle") {
193
+ useGoogle = value;
194
+ }
195
+ });
196
+
197
+ busboy.on("file", async (fieldname, file, filename) => {
198
+ uploadFile(context, requestId, body, saveToLocal, useGoogle, file, filename?.filename || filename, resolve)
199
+ });
200
+
201
+ busboy.on("error", (error) => {
202
+ context.log.error("Error processing file upload:", error);
203
+ context.res = {
204
+ status: 500,
205
+ body: "Error processing file upload.",
206
+ };
207
+ reject(error); // Reject the promise
208
+ });
209
+
210
+ req.pipe(busboy);
211
+ }
212
+ } catch (error) {
213
+ context.log.error("Error processing file upload:", error);
214
+ context.res = {
215
+ status: 500,
216
+ body: "Error processing file upload.",
217
+ };
218
+ reject(error); // Reject the promise
219
+ }
220
+ });
221
+ }
165
222
 
166
- //check if useGoogle is set but no gcs and warn
167
- if(useGoogle && useGoogle !== "false" && !gcs) {
168
- context.log.warn("Google Cloud Storage is not initialized reverting google upload ");
169
- useGoogle = false;
170
- }
223
+ async function uploadFile(context, requestId, body, saveToLocal, useGoogle, file, filename, resolve) {
224
+ // do not use Google if the file is not an image or video
225
+ const ext = path.extname(filename).toLowerCase();
226
+ const canUseGoogle = IMAGE_EXTENSIONS.includes(ext) || VIDEO_EXTENSIONS.includes(ext) || AUDIO_EXTENSIONS.includes(ext);
227
+ if (!canUseGoogle) {
228
+ useGoogle = false;
229
+ }
171
230
 
172
- if (saveToLocal) {
173
- // Create the target folder if it doesn't exist
174
- const localPath = join(publicFolder, requestId);
175
- fs.mkdirSync(localPath, { recursive: true });
231
+ // check if useGoogle is set but no gcs and warn
232
+ if (useGoogle && useGoogle !== "false" && !gcs) {
233
+ context.log.warn("Google Cloud Storage is not initialized reverting google upload ");
234
+ useGoogle = false;
235
+ }
176
236
 
177
- const filename = `${uuidv4()}_${info.filename}`;
178
- const destinationPath = `${localPath}/${filename}`;
237
+ const encodedFilename = encodeURIComponent(`${requestId || uuidv4()}_${filename}`);
179
238
 
180
- await pipeline(file, fs.createWriteStream(destinationPath));
181
239
 
182
- const message = `File '${filename}' saved to folder successfully.`;
183
- context.log(message);
240
+ if (saveToLocal) {
241
+ // create the target folder if it doesn't exist
242
+ const localPath = join(publicFolder, requestId);
243
+ fs.mkdirSync(localPath, { recursive: true });
184
244
 
185
- const url = `http://${ipAddress}:${port}/files/${requestId}/${filename}`;
245
+ const destinationPath = `${localPath}/${encodedFilename}`;
186
246
 
187
- body = { message, url };
247
+ await pipeline(file, fs.createWriteStream(destinationPath));
188
248
 
189
- resolve(body); // Resolve the promise
190
- } else {
191
- const filename = `${requestId}/${uuidv4()}_${info.filename}`;
192
- const { containerClient } = await getBlobClient();
249
+ const message = `File '${encodedFilename}' saved to folder successfully.`;
250
+ context.log(message);
193
251
 
194
- const contentType = mime.lookup(filename); // content type based on file extension
195
- const options = {};
196
- if (contentType) {
197
- options.blobHTTPHeaders = { blobContentType: contentType };
198
- }
252
+ const url = `http://${ipAddress}:${port}/files/${requestId}/${encodedFilename}`;
199
253
 
200
- const blockBlobClient = containerClient.getBlockBlobClient(filename);
254
+ body = { message, url };
201
255
 
202
- const passThroughStream = new PassThrough();
203
- file.pipe(passThroughStream);
256
+ resolve(body); // Resolve the promise
257
+ } else {
258
+ const { containerClient } = await getBlobClient();
204
259
 
205
- await blockBlobClient.uploadStream(passThroughStream, undefined, undefined, options);
260
+ const contentType = mime.lookup(encodedFilename); // content type based on file extension
261
+ const options = {};
262
+ if (contentType) {
263
+ options.blobHTTPHeaders = { blobContentType: contentType };
264
+ }
206
265
 
207
- const message = `File '${filename}' uploaded successfully.`;
208
- const url = blockBlobClient.url;
209
- context.log(message);
210
- body = { message, url };
211
- }
266
+ const blockBlobClient = containerClient.getBlockBlobClient(encodedFilename);
212
267
 
213
- context.res = {
214
- status: 200,
215
- body,
216
- };
268
+ const passThroughStream = new PassThrough();
269
+ file.pipe(passThroughStream);
217
270
 
218
- if (useGoogle && useGoogle !== "false") {
219
- const { url } = body;
220
- const filename = `${requestId}/${uuidv4()}_${info.filename}`;
221
- const gcsFile = gcs.bucket(GCS_BUCKETNAME).file(filename);
222
- const writeStream = gcsFile.createWriteStream();
271
+ await blockBlobClient.uploadStream(passThroughStream, undefined, undefined, options);
223
272
 
224
- const response = await axios({
225
- method: "get",
226
- url: url,
227
- responseType: "stream",
228
- });
273
+ const message = `File '${encodedFilename}' uploaded successfully.`;
274
+ const url = blockBlobClient.url;
275
+ context.log(message);
276
+ body = { message, url };
277
+ }
229
278
 
230
- // Pipe the Axios response stream directly into the GCS Write Stream
231
- response.data.pipe(writeStream);
279
+ context.res = {
280
+ status: 200,
281
+ body,
282
+ };
232
283
 
233
- await new Promise((resolve, reject) => {
234
- writeStream.on("finish", resolve);
235
- writeStream.on("error", reject);
236
- });
284
+ if (useGoogle && useGoogle !== "false") {
285
+ const { url } = body;
286
+ const gcsFile = gcs.bucket(GCS_BUCKETNAME).file(encodedFilename);
287
+ const writeStream = gcsFile.createWriteStream();
237
288
 
238
- body.gcs = `gs://${GCS_BUCKETNAME}/${filename}`;
239
- }
289
+ const response = await axios({
290
+ method: "get",
291
+ url: url,
292
+ responseType: "stream",
293
+ });
240
294
 
241
- resolve(body); // Resolve the promise
242
- });
295
+ // pipe the Axios response stream directly into the GCS Write Stream
296
+ response.data.pipe(writeStream);
243
297
 
244
- busboy.on("error", (error) => {
245
- context.log.error("Error processing file upload:", error);
246
- context.res = {
247
- status: 500,
248
- body: "Error processing file upload.",
249
- };
250
- reject(error); // Reject the promise
251
- });
298
+ await new Promise((resolve, reject) => {
299
+ writeStream.on("finish", resolve);
300
+ writeStream.on("error", reject);
301
+ });
252
302
 
253
- req.pipe(busboy);
254
- } catch (error) {
255
- context.log.error("Error processing file upload:", error);
256
- context.res = {
257
- status: 500,
258
- body: "Error processing file upload.",
259
- };
260
- reject(error); // Reject the promise
261
- }
262
- });
303
+ body.gcs = `gs://${GCS_BUCKETNAME}/${encodedFilename}`;
304
+ }
305
+
306
+ resolve(body); // Resolve the promise
263
307
  }
264
308
 
265
309
  // Function to delete files that haven't been used in more than a month
@@ -348,4 +392,4 @@ async function cleanupGCS(urls=null) {
348
392
  return cleanedURLs;
349
393
  }
350
394
 
351
- export { saveFileToBlob, deleteBlob, uploadBlob, cleanup, cleanupGCS };
395
+ export { saveFileToBlob, deleteBlob, uploadBlob, cleanup, cleanupGCS, gcsUrlExists };
@@ -112,16 +112,20 @@ async function splitMediaFile(inputPath, chunkDurationInSeconds = 500) {
112
112
  }
113
113
  }
114
114
 
115
- const ytdlDownload = async (url, filename) => {
115
+ const ytdlDownload = async (url, filename, video = false) => {
116
116
  return new Promise((resolve, reject) => {
117
- const video = ytdl(url, { quality: 'highestaudio' });
117
+ const videoOptions = video
118
+ ? { filter: 'audioandvideo' } // audio and video
119
+ : { quality: 'highestaudio' }; // audio only
120
+
121
+ const videoStream = ytdl(url, videoOptions);
118
122
  let lastLoggedTime = Date.now();
119
123
 
120
- video.on('error', (error) => {
124
+ videoStream.on('error', (error) => {
121
125
  reject(error);
122
126
  });
123
127
 
124
- video.on('progress', (chunkLength, downloaded, total) => {
128
+ videoStream.on('progress', (chunkLength, downloaded, total) => {
125
129
  const currentTime = Date.now();
126
130
  if (currentTime - lastLoggedTime >= 2000) { // Log every 2 seconds
127
131
  const percent = downloaded / total;
@@ -130,7 +134,7 @@ const ytdlDownload = async (url, filename) => {
130
134
  }
131
135
  });
132
136
 
133
- video.pipe(fs.createWriteStream(filename))
137
+ videoStream.pipe(fs.createWriteStream(filename))
134
138
  .on('finish', () => {
135
139
  resolve();
136
140
  })
@@ -140,10 +144,11 @@ const ytdlDownload = async (url, filename) => {
140
144
  });
141
145
  };
142
146
 
143
- const processYoutubeUrl = async (url) => {
147
+ async function processYoutubeUrl(url, video=false) {
144
148
  try {
145
- const outputFileName = path.join(os.tmpdir(), `${uuidv4()}.mp3`);
146
- await ytdlDownload(url, outputFileName);
149
+ const outputFormat = video ? '.mp4' : '.mp3';
150
+ const outputFileName = path.join(os.tmpdir(), `${uuidv4()}${outputFormat}`);
151
+ await ytdlDownload(url, outputFileName, video);
147
152
  return outputFileName;
148
153
  } catch (e) {
149
154
  console.log(e);
@@ -1,5 +1,5 @@
1
1
  import { downloadFile, processYoutubeUrl, splitMediaFile } from './fileChunker.js';
2
- import { saveFileToBlob, deleteBlob, uploadBlob, cleanup, cleanupGCS } from './blobHandler.js';
2
+ import { saveFileToBlob, deleteBlob, uploadBlob, cleanup, cleanupGCS, gcsUrlExists } from './blobHandler.js';
3
3
  import { cleanupRedisFileStoreMap, getFileStoreMap, publishRequestProgress, removeFromFileStoreMap, setFileStoreMap } from './redis.js';
4
4
  import { deleteTempPath, ensureEncoded, isValidYoutubeUrl } from './helper.js';
5
5
  import { moveFileToPublicFolder, deleteFolder, cleanupLocal } from './localFileHandler.js';
@@ -10,6 +10,10 @@ import { v4 as uuidv4 } from 'uuid';
10
10
  import fs from 'fs';
11
11
  import http from 'http';
12
12
  import https from 'https';
13
+ import axios from "axios";
14
+ import { pipeline } from "stream";
15
+ import { promisify } from "util";
16
+ const pipelineUtility = promisify(pipeline); // To pipe streams using async/await
13
17
 
14
18
  const DOC_EXTENSIONS = [".txt", ".json", ".csv", ".md", ".xml", ".js", ".html", ".css", '.pdf', '.docx', '.xlsx', '.csv'];
15
19
 
@@ -116,20 +120,62 @@ async function main(context, req) {
116
120
  return;
117
121
  }
118
122
 
119
- const { uri, requestId, save, hash, checkHash } = req.body?.params || req.query;
123
+ const { uri, requestId, save, hash, checkHash, fetch, load, restore } = req.body?.params || req.query;
124
+
125
+ const filepond = fetch || restore || load;
126
+ if (req.method.toLowerCase() === `get` && filepond) {
127
+ context.log(`Remote file: ${filepond}`);
128
+ // Check if file already exists (using hash as the key)
129
+ const exists = await getFileStoreMap(filepond);
130
+ if(exists){
131
+ context.res = {
132
+ status: 200,
133
+ body: exists // existing file URL
134
+ };
135
+ return;
136
+ }
137
+
138
+ // Check if it's a youtube url
139
+ let youtubeDownloadedFile = null;
140
+ if(isValidYoutubeUrl(filepond)){
141
+ youtubeDownloadedFile = await processYoutubeUrl(filepond, true);
142
+ }
143
+ const filename = path.join(os.tmpdir(), path.basename(youtubeDownloadedFile || filepond));
144
+ // Download the remote file to a local/temporary location keep name & ext
145
+ if(!youtubeDownloadedFile){
146
+ const response = await axios.get(filepond, { responseType: "stream" });
147
+ await pipelineUtility(response.data, fs.createWriteStream(filename));
148
+ }
149
+
150
+
151
+ const res = await uploadBlob(context, null, !useAzure, true, filename);
152
+ context.log(`File uploaded: ${JSON.stringify(res)}`);
153
+
154
+ //Update Redis (using hash as the key)
155
+ await setFileStoreMap(filepond, res);
156
+
157
+ // Return the file URL
158
+ context.res = {
159
+ status: 200,
160
+ body: res,
161
+ };
162
+
163
+ return;
164
+ }
120
165
 
121
166
  if(hash && checkHash){ //check if hash exists
122
167
  context.log(`Checking hash: ${hash}`);
123
168
  const result = await getFileStoreMap(hash);
124
169
 
125
- const exists = await urlExists(result?.url);
170
+ if(result){
171
+ const exists = await urlExists(result?.url);
172
+ const gcsExists = await gcsUrlExists(result?.gcs);
126
173
 
127
- if(!exists){
128
- await removeFromFileStoreMap(hash);
129
- return;
130
- }
174
+ if(!exists || !gcsExists){
175
+ await removeFromFileStoreMap(hash);
176
+ return;
177
+ }
131
178
 
132
- if(result){
133
179
  context.log(`Hash exists: ${hash}`);
134
180
  //update redis timestamp with current time
135
181
  await setFileStoreMap(hash, result);
@@ -228,7 +274,8 @@ async function main(context, req) {
228
274
 
229
275
  if (isYoutubeUrl) {
230
276
  // totalCount += 1; // extra 1 step for youtube download
231
- file = await processYoutubeUrl(file);
277
+ const processAsVideo = req.body?.params?.processAsVideo || req.query?.processAsVideo;
278
+ file = await processYoutubeUrl(file, processAsVideo);
232
279
  }
233
280
 
234
281
  const { chunkPromises, chunkOffsets, uniqueOutputPath } = await splitMediaFile(file);
@@ -3,7 +3,7 @@ import { encode, decode } from '../lib/encodeCache.js';
3
3
  import { config } from '../config.js';
4
4
 
5
5
  // callPathway - call a pathway from another pathway
6
- const callPathway = async (pathwayName, inArgs) => {
6
+ const callPathway = async (pathwayName, inArgs, pathwayResolver) => {
7
7
 
8
8
  // Clone the args object to avoid modifying the original
9
9
  const args = JSON.parse(JSON.stringify(inArgs));
@@ -15,6 +15,12 @@ const callPathway = async (pathwayName, inArgs) => {
15
15
  const requestState = {};
16
16
  const parent = {};
17
17
  const data = await pathway.rootResolver(parent, args, { config, pathway, requestState } );
18
+
19
+ // Merge the results into the pathwayResolver if it was provided
20
+ if (pathwayResolver) {
21
+ pathwayResolver.mergeResults(data);
22
+ }
23
+
18
24
  return data?.result;
19
25
  };
20
26
 
@@ -184,6 +184,7 @@ setInterval(() => {
184
184
  }, 30000); // Log rates every 30 seconds
185
185
 
186
186
  const requestWithMonitor = async (endpoint, url, data, axiosConfigObj) => {
187
+ //logger.warn(`Requesting ${url} with data: ${JSON.stringify(data)}`);
187
188
  const callId = endpoint?.monitor?.startCall();
188
189
  let response;
189
190
  try {
@@ -356,14 +357,13 @@ const executeRequest = async (cortexRequest) => {
356
357
  if (cached) {
357
358
  logger.info(`<<< [${requestId}] served with cached response.`);
358
359
  }
359
- if (error && error.length > 0) {
360
- const lastError = error[error.length - 1];
361
- return { error: lastError.toJSON() ?? lastError ?? error };
360
+ if (error) {
361
+ throw { error: error.toJSON() ?? error };
362
362
  }
363
363
  return { data, duration };
364
364
  } catch (error) {
365
365
  logger.error(`Error in request: ${error.message || error}`);
366
- return { error: error };
366
+ throw error;
367
367
  }
368
368
  }
369
369