@aj-archipelago/cortex 1.4.6 → 1.4.7

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 (38) hide show
  1. package/helper-apps/cortex-file-handler/package-lock.json +2 -2
  2. package/helper-apps/cortex-file-handler/package.json +1 -1
  3. package/helper-apps/cortex-file-handler/src/index.js +27 -4
  4. package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +74 -10
  5. package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +23 -2
  6. package/helper-apps/cortex-file-handler/src/start.js +2 -0
  7. package/helper-apps/cortex-file-handler/tests/deleteOperations.test.js +287 -0
  8. package/helper-apps/cortex-file-handler/tests/start.test.js +1 -1
  9. package/lib/entityConstants.js +1 -1
  10. package/lib/fileUtils.js +1481 -0
  11. package/lib/pathwayTools.js +7 -1
  12. package/lib/util.js +2 -313
  13. package/package.json +4 -3
  14. package/pathways/image_qwen.js +1 -1
  15. package/pathways/system/entity/memory/sys_read_memory.js +17 -3
  16. package/pathways/system/entity/memory/sys_save_memory.js +22 -6
  17. package/pathways/system/entity/sys_entity_agent.js +21 -4
  18. package/pathways/system/entity/tools/sys_tool_analyzefile.js +171 -0
  19. package/pathways/system/entity/tools/sys_tool_codingagent.js +38 -4
  20. package/pathways/system/entity/tools/sys_tool_editfile.js +403 -0
  21. package/pathways/system/entity/tools/sys_tool_file_collection.js +433 -0
  22. package/pathways/system/entity/tools/sys_tool_image.js +172 -10
  23. package/pathways/system/entity/tools/sys_tool_image_gemini.js +123 -10
  24. package/pathways/system/entity/tools/sys_tool_readfile.js +217 -124
  25. package/pathways/system/entity/tools/sys_tool_validate_url.js +137 -0
  26. package/pathways/system/entity/tools/sys_tool_writefile.js +211 -0
  27. package/pathways/system/workspaces/run_workspace_prompt.js +4 -3
  28. package/pathways/transcribe_gemini.js +2 -1
  29. package/server/executeWorkspace.js +1 -1
  30. package/server/plugins/neuralSpacePlugin.js +2 -6
  31. package/server/plugins/openAiWhisperPlugin.js +2 -1
  32. package/server/plugins/replicateApiPlugin.js +4 -14
  33. package/server/typeDef.js +10 -1
  34. package/tests/integration/features/tools/fileCollection.test.js +858 -0
  35. package/tests/integration/features/tools/fileOperations.test.js +851 -0
  36. package/tests/integration/features/tools/writefile.test.js +350 -0
  37. package/tests/unit/core/fileCollection.test.js +259 -0
  38. package/tests/unit/core/util.test.js +320 -1
@@ -1,7 +1,7 @@
1
1
  // sys_tool_image_gemini.js
2
2
  // Entity tool that creates and modifies images for the entity to show to the user
3
3
  import { callPathway } from '../../../../lib/pathwayTools.js';
4
- import { uploadImageToCloud } from '../../../../lib/util.js';
4
+ import { uploadImageToCloud, addFileToCollection, resolveFileParameter } from '../../../../lib/fileUtils.js';
5
5
 
6
6
  export default {
7
7
  prompt: [],
@@ -9,6 +9,8 @@ export default {
9
9
  enableDuplicateRequests: false,
10
10
  inputParameters: {
11
11
  model: 'oai-gpt4o',
12
+ contextId: '',
13
+ contextKey: '',
12
14
  },
13
15
  timeout: 300,
14
16
  toolDefinition: [{
@@ -25,6 +27,17 @@ export default {
25
27
  type: "string",
26
28
  description: "A very detailed prompt describing the image you want to create. You should be very specific - explaining subject matter, style, and details about the image including things like camera angle, lens types, lighting, photographic techniques, etc. Any details you can provide to the image creation engine will help it create the most accurate and useful images. The more detailed and descriptive the prompt, the better the result."
27
29
  },
30
+ filenamePrefix: {
31
+ type: "string",
32
+ description: "Optional: A descriptive prefix to use for the generated image filename (e.g., 'portrait', 'landscape', 'logo'). If not provided, defaults to 'generated-image'."
33
+ },
34
+ tags: {
35
+ type: "array",
36
+ items: {
37
+ type: "string"
38
+ },
39
+ description: "Optional: Array of tags to categorize the image (e.g., ['portrait', 'art', 'photography']). Will be merged with default tags ['image', 'generated']."
40
+ },
28
41
  userMessage: {
29
42
  type: "string",
30
43
  description: "A user-friendly message that describes what you're doing with this tool"
@@ -46,20 +59,31 @@ export default {
46
59
  properties: {
47
60
  inputImage: {
48
61
  type: "string",
49
- description: "The first image URL copied exactly from an image_url field in your chat context."
62
+ description: "An image from your available files (from Available Files section or ListFileCollection or SearchFileCollection) to use as a reference for the image modification."
50
63
  },
51
64
  inputImage2: {
52
65
  type: "string",
53
- description: "The second input image URL copied exactly from an image_url field in your chat context if there is one."
66
+ description: "A second image from your available files (from Available Files section or ListFileCollection or SearchFileCollection) to use as a reference for the image modification if there is one."
54
67
  },
55
68
  inputImage3: {
56
69
  type: "string",
57
- description: "The third input image URL copied exactly from an image_url field in your chat context if there is one."
70
+ description: "A third image from your available files (from Available Files section or ListFileCollection or SearchFileCollection) to use as a reference for the image modification if there is one."
58
71
  },
59
72
  detailedInstructions: {
60
73
  type: "string",
61
74
  description: "A very detailed prompt describing how you want to modify the image. Be specific about the changes you want to make, including style changes, artistic effects, or specific modifications. The more detailed and descriptive the prompt, the better the result."
62
75
  },
76
+ filenamePrefix: {
77
+ type: "string",
78
+ description: "Optional: A prefix to use for the modified image filename (e.g., 'edited', 'stylized', 'enhanced'). If not provided, defaults to 'modified-image'."
79
+ },
80
+ tags: {
81
+ type: "array",
82
+ items: {
83
+ type: "string"
84
+ },
85
+ description: "Optional: Array of tags to categorize the image (e.g., ['edited', 'art', 'stylized']). Will be merged with default tags ['image', 'modified']."
86
+ },
63
87
  userMessage: {
64
88
  type: "string",
65
89
  description: "A user-friendly message that describes what you're doing with this tool"
@@ -76,15 +100,55 @@ export default {
76
100
  let model = "gemini-25-flash-image";
77
101
  let prompt = args.detailedInstructions || "";
78
102
 
103
+ // Resolve input images to URLs using the common utility
104
+ // For Gemini, prefer GCS URLs over Azure URLs
105
+ // Fail early if any provided image parameter cannot be resolved
106
+ if (args.inputImage) {
107
+ if (!args.contextId) {
108
+ throw new Error("contextId is required when using the 'inputImage' parameter. Use ListFileCollection or SearchFileCollection to find available files.");
109
+ }
110
+ const resolved = await resolveFileParameter(args.inputImage, args.contextId, args.contextKey, { preferGcs: true });
111
+ if (!resolved) {
112
+ throw new Error(`File not found: "${args.inputImage}". Use ListFileCollection or SearchFileCollection to find available files.`);
113
+ }
114
+ args.inputImage = resolved;
115
+ }
116
+
117
+ if (args.inputImage2) {
118
+ if (!args.contextId) {
119
+ throw new Error("contextId is required when using the 'inputImage2' parameter. Use ListFileCollection or SearchFileCollection to find available files.");
120
+ }
121
+ const resolved = await resolveFileParameter(args.inputImage2, args.contextId, args.contextKey, { preferGcs: true });
122
+ if (!resolved) {
123
+ throw new Error(`File not found: "${args.inputImage2}". Use ListFileCollection or SearchFileCollection to find available files.`);
124
+ }
125
+ args.inputImage2 = resolved;
126
+ }
127
+
128
+ if (args.inputImage3) {
129
+ if (!args.contextId) {
130
+ throw new Error("contextId is required when using the 'inputImage3' parameter. Use ListFileCollection or SearchFileCollection to find available files.");
131
+ }
132
+ const resolved = await resolveFileParameter(args.inputImage3, args.contextId, args.contextKey, { preferGcs: true });
133
+ if (!resolved) {
134
+ throw new Error(`File not found: "${args.inputImage3}". Use ListFileCollection or SearchFileCollection to find available files.`);
135
+ }
136
+ args.inputImage3 = resolved;
137
+ }
138
+
139
+ const resolvedInputImage = args.inputImage;
140
+ const resolvedInputImage2 = args.inputImage2;
141
+ const resolvedInputImage3 = args.inputImage3;
142
+
79
143
  // Call the image generation pathway
80
144
  let result = await callPathway('image_gemini_25', {
81
145
  ...args,
82
146
  text: prompt,
83
147
  model,
84
148
  stream: false,
85
- input_image: args.inputImage,
86
- input_image_2: args.inputImage2,
87
- input_image_3: args.inputImage3,
149
+ input_image: resolvedInputImage,
150
+ input_image_2: resolvedInputImage2,
151
+ input_image_3: resolvedInputImage3,
88
152
  optimizePrompt: true,
89
153
  }, pathwayResolver);
90
154
 
@@ -98,13 +162,62 @@ export default {
98
162
  for (const artifact of pathwayResolver.pathwayResultData.artifacts) {
99
163
  if (artifact.type === 'image' && artifact.data && artifact.mimeType) {
100
164
  try {
101
- // Upload image to cloud storage
102
- const imageUrl = await uploadImageToCloud(artifact.data, artifact.mimeType, pathwayResolver);
165
+ // Upload image to cloud storage (returns {url, gcs, hash})
166
+ const uploadResult = await uploadImageToCloud(artifact.data, artifact.mimeType, pathwayResolver);
167
+
168
+ const imageUrl = uploadResult.url || uploadResult;
169
+ const imageGcs = uploadResult.gcs || null;
170
+ const imageHash = uploadResult.hash || null;
171
+
103
172
  uploadedImages.push({
104
173
  type: 'image',
105
174
  url: imageUrl,
175
+ gcs: imageGcs,
176
+ hash: imageHash,
106
177
  mimeType: artifact.mimeType
107
178
  });
179
+
180
+ // Add uploaded image to file collection if contextId is available
181
+ if (args.contextId && imageUrl) {
182
+ try {
183
+ // Generate filename from mimeType (e.g., "image/png" -> "png")
184
+ const extension = artifact.mimeType.split('/')[1] || 'png';
185
+ // Use hash for uniqueness if available, otherwise use timestamp and index
186
+ const uniqueId = imageHash ? imageHash.substring(0, 8) : `${Date.now()}-${uploadedImages.length}`;
187
+
188
+ // Determine filename prefix based on whether this is a modification or generation
189
+ // If inputImage exists, it's a modification; otherwise it's a generation
190
+ const isModification = args.inputImage || args.inputImage2 || args.inputImage3;
191
+ const defaultPrefix = isModification ? 'modified-image' : 'generated-image';
192
+ const filenamePrefix = args.filenamePrefix || defaultPrefix;
193
+
194
+ // Sanitize the prefix to ensure it's a valid filename component
195
+ const sanitizedPrefix = filenamePrefix.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
196
+ const filename = `${sanitizedPrefix}-${uniqueId}.${extension}`;
197
+
198
+ // Merge provided tags with default tags
199
+ const defaultTags = ['image', isModification ? 'modified' : 'generated'];
200
+ const providedTags = Array.isArray(args.tags) ? args.tags : [];
201
+ const allTags = [...defaultTags, ...providedTags.filter(tag => !defaultTags.includes(tag))];
202
+
203
+ // Use the centralized utility function to add to collection
204
+ await addFileToCollection(
205
+ args.contextId,
206
+ args.contextKey || '',
207
+ imageUrl,
208
+ imageGcs,
209
+ filename,
210
+ allTags,
211
+ isModification
212
+ ? `Modified image from prompt: ${args.detailedInstructions || 'image modification'}`
213
+ : `Generated image from prompt: ${args.detailedInstructions || 'image generation'}`,
214
+ imageHash
215
+ );
216
+ } catch (collectionError) {
217
+ // Log but don't fail - file collection is optional
218
+ pathwayResolver.logWarning(`Failed to add image to file collection: ${collectionError.message}`);
219
+ }
220
+ }
108
221
  } catch (uploadError) {
109
222
  pathwayResolver.logError(`Failed to upload artifact: ${uploadError.message}`);
110
223
  // Keep original artifact as fallback
@@ -117,7 +230,7 @@ export default {
117
230
  }
118
231
 
119
232
  // Return the urls of the uploaded images as text in the result
120
- result = result + '\n' + uploadedImages.map(image => image.url).join('\n');
233
+ result = result ? result + '\n' + uploadedImages.map(image => image.url || image).join('\n') : uploadedImages.map(image => image.url || image).join('\n');
121
234
  }
122
235
  } else {
123
236
  // If result is not a CortexResponse, log a warning but return as-is
@@ -1,148 +1,241 @@
1
1
  // sys_tool_readfile.js
2
- // Entity tool that reads one or more files and answers questions about them
3
-
4
- import { Prompt } from '../../../../server/prompt.js';
2
+ // Tool pathway that reads text files with line number support
3
+ import logger from '../../../../lib/logger.js';
4
+ import { axios } from '../../../../lib/requestExecutor.js';
5
+ import { resolveFileParameter } from '../../../../lib/fileUtils.js';
5
6
 
6
7
  export default {
7
- prompt:
8
- [
9
- new Prompt({ messages: [
10
- {"role": "system", "content": `You are the part of an AI entity named {{aiName}} that can view, hear, and understand files of all sorts (images, videos, audio, pdfs, text, etc.) - you provide the capability to view and analyze files that the user provides.\nThe user has provided you with one or more files in this conversation - you should consider them for context when you respond.\nIf you don't see any files, something has gone wrong in the upload and you should inform the user and have them try again.\n{{renderTemplate AI_DATETIME}}`},
11
- "{{chatHistory}}",
12
- ]}),
13
- ],
14
- inputParameters: {
15
- chatHistory: [{role: '', content: []}],
16
- contextId: ``,
17
- aiName: "Jarvis",
18
- language: "English",
19
- },
20
- max_tokens: 8192,
21
- model: 'gemini-flash-20-vision',
22
- useInputChunking: false,
23
- enableDuplicateRequests: false,
24
- timeout: 600,
25
- geminiSafetySettings: [{category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_ONLY_HIGH'},
26
- {category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_ONLY_HIGH'},
27
- {category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_ONLY_HIGH'},
28
- {category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_ONLY_HIGH'}],
29
- toolDefinition: [{
8
+ prompt: [],
9
+ timeout: 60,
10
+ toolDefinition: {
30
11
  type: "function",
31
- icon: "📄",
12
+ icon: "📖",
32
13
  function: {
33
- name: "AnalyzePDF",
34
- description: "Use specifically for reading, analyzing, and answering questions about PDF file content. Do not use this tool for analyzing and answering questions about other file types.",
14
+ name: "ReadFile",
15
+ description: "Read text content from a file. Can read the entire file or specific line ranges. Use this to access and analyze text files from your file collection. Supports text files, markdown files, html, csv, and other document formats that can be converted to text, but not images, videos, or audio files or pdfs.",
35
16
  parameters: {
36
17
  type: "object",
37
18
  properties: {
38
- detailedInstructions: {
19
+ file: {
39
20
  type: "string",
40
- description: "Detailed instructions about what you need the tool to do - questions you need answered about the files, etc."
21
+ description: "The file to read: can be the file ID, filename, URL, or hash from your file collection. You can find available files in the Available Files section or ListFileCollection or SearchFileCollection."
22
+ },
23
+ startLine: {
24
+ type: "number",
25
+ description: "Optional: Starting line number (1-indexed). If not provided, reads from the beginning."
26
+ },
27
+ endLine: {
28
+ type: "number",
29
+ description: "Optional: Ending line number (1-indexed). If not provided, reads to the end. Must be >= startLine if startLine is provided."
30
+ },
31
+ maxLines: {
32
+ type: "number",
33
+ description: "Optional: Maximum number of lines to read (default: 1000). Use this to limit the size of the response."
41
34
  },
42
35
  userMessage: {
43
36
  type: "string",
44
37
  description: "A user-friendly message that describes what you're doing with this tool"
45
38
  }
46
39
  },
47
- required: ["detailedInstructions", "userMessage"]
40
+ required: ["userMessage"]
48
41
  }
49
42
  }
50
43
  },
51
- {
52
- type: "function",
53
- icon: "📝",
54
- function: {
55
- name: "AnalyzeText",
56
- description: "Use specifically for reading, analyzing, and answering questions about text files (including csv, json, html, etc.).",
57
- parameters: {
58
- type: "object",
59
- properties: {
60
- detailedInstructions: {
61
- type: "string",
62
- description: "Detailed instructions about what you need the tool to do - questions you need answered about the files, etc."
63
- },
64
- userMessage: {
65
- type: "string",
66
- description: "A user-friendly message that describes what you're doing with this tool"
67
- }
68
- },
69
- required: ["detailedInstructions", "userMessage"]
44
+
45
+ executePathway: async ({args, runAllPrompts, resolver}) => {
46
+ try {
47
+ let { cloudUrl, file, startLine, endLine, maxLines = 1000, contextId, contextKey } = args;
48
+
49
+ // If file parameter is provided, resolve it to a URL using the common utility
50
+ if (file) {
51
+ if (!contextId) {
52
+ const errorResult = {
53
+ success: false,
54
+ error: "contextId is required when using the 'file' parameter. Use ListFileCollection or SearchFileCollection to find available files."
55
+ };
56
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
57
+ return JSON.stringify(errorResult);
58
+ }
59
+ const resolvedUrl = await resolveFileParameter(file, contextId, contextKey);
60
+ if (!resolvedUrl) {
61
+ const errorResult = {
62
+ success: false,
63
+ error: `File not found: "${file}". Use ListFileCollection or SearchFileCollection to find available files.`
64
+ };
65
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
66
+ return JSON.stringify(errorResult);
67
+ }
68
+ cloudUrl = resolvedUrl;
70
69
  }
71
- }
72
- },
73
- {
74
- type: "function",
75
- icon: "📝",
76
- function: {
77
- name: "AnalyzeMarkdown",
78
- description: "Use specifically for reading, analyzing, and answering questions about markdown files.",
79
- parameters: {
80
- type: "object",
81
- properties: {
82
- detailedInstructions: {
83
- type: "string",
84
- description: "Detailed instructions about what you need the tool to do - questions you need answered about the files, etc."
85
- },
86
- userMessage: {
87
- type: "string",
88
- description: "A user-friendly message that describes what you're doing with this tool"
89
- }
90
- },
91
- required: ["detailedInstructions", "userMessage"]
70
+
71
+ if (!cloudUrl || typeof cloudUrl !== 'string') {
72
+ const errorResult = {
73
+ success: false,
74
+ error: "Either cloudUrl or file parameter is required and must be a string"
75
+ };
76
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
77
+ return JSON.stringify(errorResult);
92
78
  }
93
- }
94
- },
95
- {
96
- type: "function",
97
- icon: "🖼️",
98
- function: {
99
- name: "AnalyzeImage",
100
- description: "Use specifically for reading, analyzing, and answering questions about image files (jpg, gif, bmp, png, etc). This cannot be used for creating or transforming images.",
101
- parameters: {
102
- type: "object",
103
- properties: {
104
- detailedInstructions: {
105
- type: "string",
106
- description: "Detailed instructions about what you need the tool to do - questions you need answered about the files, etc."
107
- },
108
- userMessage: {
109
- type: "string",
110
- description: "A user-friendly message that describes what you're doing with this tool"
111
- }
112
- },
113
- required: ["detailedInstructions", "userMessage"]
79
+
80
+ if (startLine !== undefined) {
81
+ if (typeof startLine !== 'number' || !Number.isInteger(startLine) || startLine < 1) {
82
+ const errorResult = {
83
+ success: false,
84
+ error: "startLine must be a positive integer (1-indexed)"
85
+ };
86
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
87
+ return JSON.stringify(errorResult);
88
+ }
114
89
  }
115
- }
116
- },
117
- {
118
- type: "function",
119
- icon: "🎥",
120
- function: {
121
- name: "AnalyzeVideo",
122
- description: "Use specifically for reading, analyzing, and answering questions about video or audio file content. You MUST use this tool to look at video or audio files.",
123
- parameters: {
124
- type: "object",
125
- properties: {
126
- detailedInstructions: {
127
- type: "string",
128
- description: "Detailed instructions about what you need the tool to do - questions you need answered about the files, etc."
129
- },
130
- userMessage: {
131
- type: "string",
132
- description: "A user-friendly message that describes what you're doing with this tool"
133
- }
134
- },
135
- required: ["detailedInstructions", "userMessage"]
90
+
91
+ if (endLine !== undefined) {
92
+ if (typeof endLine !== 'number' || !Number.isInteger(endLine) || endLine < 1) {
93
+ const errorResult = {
94
+ success: false,
95
+ error: "endLine must be a positive integer (1-indexed)"
96
+ };
97
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
98
+ return JSON.stringify(errorResult);
99
+ }
136
100
  }
101
+
102
+ if (startLine !== undefined && endLine !== undefined && endLine < startLine) {
103
+ const errorResult = {
104
+ success: false,
105
+ error: "endLine must be >= startLine"
106
+ };
107
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
108
+ return JSON.stringify(errorResult);
109
+ }
110
+
111
+ if (maxLines !== undefined) {
112
+ if (typeof maxLines !== 'number' || !Number.isInteger(maxLines) || maxLines < 1) {
113
+ const errorResult = {
114
+ success: false,
115
+ error: "maxLines must be a positive integer"
116
+ };
117
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
118
+ return JSON.stringify(errorResult);
119
+ }
120
+ }
121
+ // Download file content directly from the URL (don't use file handler for content)
122
+ // Use arraybuffer and explicitly decode as UTF-8 to avoid encoding issues
123
+ const response = await axios.get(cloudUrl, {
124
+ responseType: 'arraybuffer',
125
+ timeout: 30000,
126
+ validateStatus: (status) => status >= 200 && status < 400
127
+ });
128
+
129
+ if (response.status !== 200 || !response.data) {
130
+ throw new Error(`Failed to download file content: ${response.status}`);
131
+ }
132
+
133
+ // Explicitly decode as UTF-8 to prevent mojibake (encoding corruption)
134
+ const textContent = Buffer.from(response.data).toString('utf8');
135
+ const allLines = textContent.split(/\r?\n/);
136
+ const totalLines = allLines.length;
137
+
138
+ // Handle empty file
139
+ if (totalLines === 0 || (totalLines === 1 && allLines[0] === '')) {
140
+ const result = {
141
+ success: true,
142
+ cloudUrl: cloudUrl,
143
+ totalLines: 0,
144
+ returnedLines: 0,
145
+ startLine: 1,
146
+ endLine: 0,
147
+ content: '',
148
+ truncated: false,
149
+ isEmpty: true
150
+ };
151
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
152
+ return JSON.stringify(result);
153
+ }
154
+
155
+ // Apply line range filtering
156
+ let selectedLines = allLines;
157
+ let actualStartLine = 1;
158
+ let actualEndLine = totalLines;
159
+ let wasTruncatedByRange = false;
160
+
161
+ if (startLine !== undefined || endLine !== undefined) {
162
+ const start = startLine !== undefined ? Math.max(1, Math.min(startLine, totalLines)) - 1 : 0; // Convert to 0-indexed, clamp to valid range
163
+ const end = endLine !== undefined ? Math.min(totalLines, Math.max(1, endLine)) : totalLines; // Clamp to valid range
164
+
165
+ if (startLine !== undefined && startLine > totalLines) {
166
+ const errorResult = {
167
+ success: false,
168
+ error: `startLine (${startLine}) exceeds file length (${totalLines} lines)`
169
+ };
170
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
171
+ return JSON.stringify(errorResult);
172
+ }
173
+
174
+ selectedLines = allLines.slice(start, end);
175
+ actualStartLine = start + 1; // Convert back to 1-indexed
176
+ actualEndLine = end;
177
+ wasTruncatedByRange = (endLine !== undefined && endLine < totalLines) || (startLine !== undefined && startLine > 1);
178
+ }
179
+
180
+ // Apply maxLines limit
181
+ let wasTruncatedByMaxLines = false;
182
+ if (selectedLines.length > maxLines) {
183
+ selectedLines = selectedLines.slice(0, maxLines);
184
+ wasTruncatedByMaxLines = true;
185
+ }
186
+
187
+ const result = {
188
+ success: true,
189
+ cloudUrl: cloudUrl,
190
+ totalLines: totalLines,
191
+ returnedLines: selectedLines.length,
192
+ startLine: actualStartLine,
193
+ endLine: actualEndLine,
194
+ content: selectedLines.join('\n'),
195
+ truncated: wasTruncatedByRange || wasTruncatedByMaxLines,
196
+ truncatedByRange: wasTruncatedByRange,
197
+ truncatedByMaxLines: wasTruncatedByMaxLines
198
+ };
199
+
200
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
201
+ return JSON.stringify(result);
202
+
203
+ } catch (e) {
204
+ let errorMsg;
205
+ if (e?.message) {
206
+ errorMsg = e.message;
207
+ } else if (e?.response) {
208
+ // Handle HTTP errors
209
+ const status = e.response.status;
210
+ const statusText = e.response.statusText || '';
211
+ errorMsg = `HTTP ${status}${statusText ? ` ${statusText}` : ''}: Failed to download file`;
212
+ } else if (e?.code === 'ECONNABORTED' || e?.code === 'ETIMEDOUT') {
213
+ errorMsg = 'Request timeout: File download took too long';
214
+ } else if (e?.code === 'ENOTFOUND' || e?.code === 'ECONNREFUSED') {
215
+ errorMsg = `Connection error: ${e.message || 'Unable to reach file server'}`;
216
+ } else if (typeof e === 'string') {
217
+ errorMsg = e;
218
+ } else if (e?.errors && Array.isArray(e.errors)) {
219
+ // Handle AggregateError
220
+ errorMsg = e.errors.map(err => err?.message || String(err)).join('; ');
221
+ } else if (e) {
222
+ errorMsg = String(e);
223
+ } else {
224
+ errorMsg = 'Unknown error occurred while reading file';
225
+ }
226
+
227
+ logger.error(`Error reading cloud file ${cloudUrl || file || 'unknown'}: ${errorMsg}`);
228
+
229
+ const errorResult = {
230
+ success: false,
231
+ cloudUrl: cloudUrl || null,
232
+ file: file || null,
233
+ error: errorMsg
234
+ };
235
+
236
+ resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
237
+ return JSON.stringify(errorResult);
137
238
  }
138
- }],
139
-
140
- executePathway: async ({args, runAllPrompts, resolver}) => {
141
- if (args.detailedInstructions) {
142
- args.chatHistory.push({role: "user", content: args.detailedInstructions});
143
- }
144
- const result = await runAllPrompts({ ...args });
145
- resolver.tool = JSON.stringify({ toolUsed: "vision" });
146
- return result;
147
239
  }
148
- }
240
+ };
241
+