@juspay/neurolink 7.48.1 → 7.49.0

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 (130) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +215 -16
  3. package/dist/agent/directTools.d.ts +55 -0
  4. package/dist/agent/directTools.js +266 -0
  5. package/dist/cli/factories/commandFactory.d.ts +2 -0
  6. package/dist/cli/factories/commandFactory.js +130 -16
  7. package/dist/cli/index.js +0 -0
  8. package/dist/cli/loop/conversationSelector.d.ts +45 -0
  9. package/dist/cli/loop/conversationSelector.js +222 -0
  10. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  11. package/dist/cli/loop/session.d.ts +36 -8
  12. package/dist/cli/loop/session.js +257 -61
  13. package/dist/core/baseProvider.js +9 -2
  14. package/dist/core/evaluation.js +5 -2
  15. package/dist/factories/providerRegistry.js +2 -2
  16. package/dist/lib/agent/directTools.d.ts +55 -0
  17. package/dist/lib/agent/directTools.js +266 -0
  18. package/dist/lib/core/baseProvider.js +9 -2
  19. package/dist/lib/core/evaluation.js +5 -2
  20. package/dist/lib/factories/providerRegistry.js +2 -2
  21. package/dist/lib/mcp/factory.d.ts +2 -157
  22. package/dist/lib/mcp/flexibleToolValidator.d.ts +1 -5
  23. package/dist/lib/mcp/index.d.ts +3 -2
  24. package/dist/lib/mcp/mcpCircuitBreaker.d.ts +1 -75
  25. package/dist/lib/mcp/mcpClientFactory.d.ts +1 -20
  26. package/dist/lib/mcp/mcpClientFactory.js +1 -0
  27. package/dist/lib/mcp/registry.d.ts +3 -10
  28. package/dist/lib/mcp/servers/agent/directToolsServer.d.ts +1 -1
  29. package/dist/lib/mcp/servers/aiProviders/aiCoreServer.d.ts +1 -1
  30. package/dist/lib/mcp/servers/utilities/utilityServer.d.ts +1 -1
  31. package/dist/lib/mcp/toolDiscoveryService.d.ts +3 -84
  32. package/dist/lib/mcp/toolRegistry.d.ts +2 -24
  33. package/dist/lib/middleware/builtin/guardrails.d.ts +5 -16
  34. package/dist/lib/middleware/builtin/guardrails.js +44 -39
  35. package/dist/lib/middleware/utils/guardrailsUtils.d.ts +64 -0
  36. package/dist/lib/middleware/utils/guardrailsUtils.js +387 -0
  37. package/dist/lib/neurolink.d.ts +1 -1
  38. package/dist/lib/providers/anthropic.js +46 -3
  39. package/dist/lib/providers/azureOpenai.js +8 -2
  40. package/dist/lib/providers/googleAiStudio.js +8 -2
  41. package/dist/lib/providers/googleVertex.js +11 -2
  42. package/dist/lib/providers/huggingFace.js +1 -1
  43. package/dist/lib/providers/litellm.js +1 -1
  44. package/dist/lib/providers/mistral.js +1 -1
  45. package/dist/lib/providers/openAI.js +46 -3
  46. package/dist/lib/session/globalSessionState.d.ts +26 -0
  47. package/dist/lib/session/globalSessionState.js +49 -0
  48. package/dist/lib/types/cli.d.ts +28 -0
  49. package/dist/lib/types/content.d.ts +18 -5
  50. package/dist/lib/types/contextTypes.d.ts +1 -1
  51. package/dist/lib/types/conversation.d.ts +55 -4
  52. package/dist/lib/types/fileTypes.d.ts +65 -0
  53. package/dist/lib/types/fileTypes.js +4 -0
  54. package/dist/lib/types/generateTypes.d.ts +12 -0
  55. package/dist/lib/types/guardrails.d.ts +103 -0
  56. package/dist/lib/types/guardrails.js +1 -0
  57. package/dist/lib/types/index.d.ts +4 -2
  58. package/dist/lib/types/index.js +4 -0
  59. package/dist/lib/types/mcpTypes.d.ts +407 -14
  60. package/dist/lib/types/streamTypes.d.ts +7 -0
  61. package/dist/lib/types/tools.d.ts +132 -35
  62. package/dist/lib/utils/csvProcessor.d.ts +68 -0
  63. package/dist/lib/utils/csvProcessor.js +277 -0
  64. package/dist/lib/utils/fileDetector.d.ts +57 -0
  65. package/dist/lib/utils/fileDetector.js +457 -0
  66. package/dist/lib/utils/imageProcessor.d.ts +10 -0
  67. package/dist/lib/utils/imageProcessor.js +22 -0
  68. package/dist/lib/utils/loopUtils.d.ts +71 -0
  69. package/dist/lib/utils/loopUtils.js +262 -0
  70. package/dist/lib/utils/messageBuilder.d.ts +2 -1
  71. package/dist/lib/utils/messageBuilder.js +197 -2
  72. package/dist/lib/utils/optionsUtils.d.ts +1 -1
  73. package/dist/mcp/factory.d.ts +2 -157
  74. package/dist/mcp/flexibleToolValidator.d.ts +1 -5
  75. package/dist/mcp/index.d.ts +3 -2
  76. package/dist/mcp/mcpCircuitBreaker.d.ts +1 -75
  77. package/dist/mcp/mcpClientFactory.d.ts +1 -20
  78. package/dist/mcp/mcpClientFactory.js +1 -0
  79. package/dist/mcp/registry.d.ts +3 -10
  80. package/dist/mcp/servers/agent/directToolsServer.d.ts +1 -1
  81. package/dist/mcp/servers/aiProviders/aiCoreServer.d.ts +1 -1
  82. package/dist/mcp/servers/utilities/utilityServer.d.ts +1 -1
  83. package/dist/mcp/toolDiscoveryService.d.ts +3 -84
  84. package/dist/mcp/toolRegistry.d.ts +2 -24
  85. package/dist/middleware/builtin/guardrails.d.ts +5 -16
  86. package/dist/middleware/builtin/guardrails.js +44 -39
  87. package/dist/middleware/utils/guardrailsUtils.d.ts +64 -0
  88. package/dist/middleware/utils/guardrailsUtils.js +387 -0
  89. package/dist/neurolink.d.ts +1 -1
  90. package/dist/providers/anthropic.js +46 -3
  91. package/dist/providers/azureOpenai.js +8 -2
  92. package/dist/providers/googleAiStudio.js +8 -2
  93. package/dist/providers/googleVertex.js +11 -2
  94. package/dist/providers/huggingFace.js +1 -1
  95. package/dist/providers/litellm.js +1 -1
  96. package/dist/providers/mistral.js +1 -1
  97. package/dist/providers/openAI.js +46 -3
  98. package/dist/session/globalSessionState.d.ts +26 -0
  99. package/dist/session/globalSessionState.js +49 -0
  100. package/dist/types/cli.d.ts +28 -0
  101. package/dist/types/content.d.ts +18 -5
  102. package/dist/types/contextTypes.d.ts +1 -1
  103. package/dist/types/conversation.d.ts +55 -4
  104. package/dist/types/fileTypes.d.ts +65 -0
  105. package/dist/types/fileTypes.js +4 -0
  106. package/dist/types/generateTypes.d.ts +12 -0
  107. package/dist/types/guardrails.d.ts +103 -0
  108. package/dist/types/guardrails.js +1 -0
  109. package/dist/types/index.d.ts +4 -2
  110. package/dist/types/index.js +4 -0
  111. package/dist/types/mcpTypes.d.ts +407 -14
  112. package/dist/types/modelTypes.d.ts +6 -6
  113. package/dist/types/streamTypes.d.ts +7 -0
  114. package/dist/types/tools.d.ts +132 -35
  115. package/dist/utils/csvProcessor.d.ts +68 -0
  116. package/dist/utils/csvProcessor.js +277 -0
  117. package/dist/utils/fileDetector.d.ts +57 -0
  118. package/dist/utils/fileDetector.js +457 -0
  119. package/dist/utils/imageProcessor.d.ts +10 -0
  120. package/dist/utils/imageProcessor.js +22 -0
  121. package/dist/utils/loopUtils.d.ts +71 -0
  122. package/dist/utils/loopUtils.js +262 -0
  123. package/dist/utils/messageBuilder.d.ts +2 -1
  124. package/dist/utils/messageBuilder.js +197 -2
  125. package/dist/utils/optionsUtils.d.ts +1 -1
  126. package/package.json +9 -3
  127. package/dist/lib/mcp/contracts/mcpContract.d.ts +0 -106
  128. package/dist/lib/mcp/contracts/mcpContract.js +0 -5
  129. package/dist/mcp/contracts/mcpContract.d.ts +0 -106
  130. package/dist/mcp/contracts/mcpContract.js +0 -5
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Loop Mode Utilities
3
+ * Utilities specific to CLI loop mode session management and restoration
4
+ */
5
+ import chalk from "chalk";
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import os from "os";
9
+ import { globalSession } from "../session/globalSessionState.js";
10
+ import { logger } from "./logger.js";
11
+ /**
12
+ * Verify that conversation context is accessible and properly loaded
13
+ * Uses the global session to access the NeuroLink instance
14
+ */
15
+ export async function verifyConversationContext(sessionId) {
16
+ const session = globalSession.getLoopSession();
17
+ if (!session) {
18
+ logger.warn("No active session found for conversation verification");
19
+ return;
20
+ }
21
+ try {
22
+ const messages = await session.neurolinkInstance.getConversationHistory(sessionId);
23
+ logger.debug(`Successfully loaded ${messages.length} messages from conversation ${sessionId}`);
24
+ if (messages.length === 0) {
25
+ logger.warn(`No messages found for session ${sessionId} after restoration`);
26
+ logger.warn("The conversation exists in Redis but may not be accessible through the API");
27
+ }
28
+ else {
29
+ logger.info(`Conversation context restored successfully: ${messages.length} messages loaded`);
30
+ }
31
+ }
32
+ catch (error) {
33
+ logger.warn("Could not access conversation history after restoration:", error);
34
+ logger.warn("The conversation may still be accessible when generating new responses");
35
+ }
36
+ }
37
+ /**
38
+ * Get conversation context for display (first few and last few messages)
39
+ * Uses the global session to access the NeuroLink instance
40
+ */
41
+ export async function getConversationPreview(sessionId, previewCount = 2) {
42
+ const session = globalSession.getLoopSession();
43
+ if (!session) {
44
+ logger.debug("No active session found for conversation preview");
45
+ return [];
46
+ }
47
+ try {
48
+ const allMessages = await session.neurolinkInstance.getConversationHistory(sessionId);
49
+ if (allMessages.length <= previewCount * 2) {
50
+ return allMessages;
51
+ }
52
+ const firstMessages = allMessages.slice(0, previewCount);
53
+ const lastMessages = allMessages.slice(-previewCount);
54
+ return [...firstMessages, ...lastMessages];
55
+ }
56
+ catch (error) {
57
+ logger.debug("Failed to get conversation preview:", error);
58
+ return [];
59
+ }
60
+ }
61
+ /**
62
+ * Generate a title from content by truncating to appropriate length
63
+ */
64
+ export function generateConversationTitle(content) {
65
+ const truncated = content.slice(0, LOOP_DISPLAY_LIMITS.TITLE_LENGTH);
66
+ return truncated.length < content.length ? `${truncated}...` : truncated;
67
+ }
68
+ /**
69
+ * Truncate text content to specified length with ellipsis
70
+ */
71
+ export function truncateText(content, maxLength) {
72
+ if (content.length <= maxLength) {
73
+ return content;
74
+ }
75
+ return `${content.slice(0, maxLength)}...`;
76
+ }
77
+ /**
78
+ * Format timestamp as human-readable relative time
79
+ * Uses Intl.RelativeTimeFormat for natural language output
80
+ */
81
+ export function formatTimeAgo(timestamp) {
82
+ const now = Date.now();
83
+ const date = new Date(timestamp);
84
+ if (isNaN(date.getTime())) {
85
+ logger.warn(`Invalid timestamp provided: ${timestamp}`);
86
+ return "Unknown time";
87
+ }
88
+ const diffSeconds = Math.floor((date.getTime() - now) / 1000);
89
+ for (const { unit, seconds } of TIME_UNITS) {
90
+ if (Math.abs(diffSeconds) >= seconds || unit === "minute") {
91
+ return rtf.format(Math.round(diffSeconds / seconds), unit);
92
+ }
93
+ }
94
+ return rtf.format(0, "second");
95
+ }
96
+ /**
97
+ * Get appropriate icon for content based on regex patterns
98
+ */
99
+ export function getContentIcon(content) {
100
+ const match = CONTENT_ICON_PATTERNS.find((pattern) => pattern.pattern.test(content));
101
+ return match?.icon ?? "📝";
102
+ }
103
+ /**
104
+ * Display session restoration status message
105
+ */
106
+ export function displaySessionMessage(result) {
107
+ if (result.success) {
108
+ logger.always(chalk.green(`✅ Resumed conversation: ${result.sessionId.slice(0, LOOP_DISPLAY_LIMITS.SESSION_ID_DISPLAY)}...`));
109
+ logger.always(chalk.gray(` ${result.messageCount} messages | Last activity: ${result.lastActivity
110
+ ? new Date(result.lastActivity).toLocaleString()
111
+ : "Unknown"}`));
112
+ }
113
+ else {
114
+ logger.always(chalk.red(`❌ Failed to restore conversation: ${result.error}`));
115
+ logger.always(chalk.gray(" Starting new conversation instead..."));
116
+ }
117
+ }
118
+ /**
119
+ * Load command history from the global history file
120
+ */
121
+ export async function loadCommandHistory() {
122
+ try {
123
+ const content = await fs.readFile(HISTORY_FILE, "utf8");
124
+ return content.split("\n").filter((line) => line.trim());
125
+ }
126
+ catch {
127
+ return [];
128
+ }
129
+ }
130
+ /**
131
+ * Save a command to the global history file
132
+ */
133
+ export async function saveCommandToHistory(command) {
134
+ try {
135
+ const sensitivePattern = /\b(api[-_]?key|token|password|secret|authorization)\b/i;
136
+ if (sensitivePattern.test(command)) {
137
+ return;
138
+ }
139
+ await fs.writeFile(HISTORY_FILE, command + "\n", {
140
+ flag: "a",
141
+ mode: 0o600,
142
+ });
143
+ await fs.chmod(HISTORY_FILE, 0o600);
144
+ }
145
+ catch (error) {
146
+ logger.warn("Warning: Could not save command to history:", error);
147
+ }
148
+ }
149
+ /**
150
+ * Display conversation preview with formatted messages
151
+ */
152
+ export function displayConversationPreview(preview, maxPreview = 2) {
153
+ if (preview.length === 0) {
154
+ return;
155
+ }
156
+ logger.always(chalk.gray("\n--- Conversation Preview ---"));
157
+ preview.slice(0, maxPreview).forEach((msg) => {
158
+ const role = msg.role === "user" ? chalk.cyan("You") : chalk.green("AI");
159
+ const content = msg.content.length > 100
160
+ ? msg.content.slice(0, 100) + "..."
161
+ : msg.content;
162
+ logger.always(`${role}: ${content}`);
163
+ });
164
+ if (preview.length > maxPreview) {
165
+ logger.always(chalk.gray("... (conversation continues)"));
166
+ }
167
+ logger.always(chalk.gray("--- End Preview ---\n"));
168
+ }
169
+ /**
170
+ * Parse a string value to its appropriate type (string, number, or boolean)
171
+ * Useful for parsing user input from CLI commands
172
+ */
173
+ export function parseValue(value) {
174
+ // Try to parse as number
175
+ if (!isNaN(Number(value))) {
176
+ return Number(value);
177
+ }
178
+ // Try to parse as boolean
179
+ if (value.toLowerCase() === "true") {
180
+ return true;
181
+ }
182
+ if (value.toLowerCase() === "false") {
183
+ return false;
184
+ }
185
+ // Return as string
186
+ return value;
187
+ }
188
+ /**
189
+ * Restore session variables from conversation metadata
190
+ * Extracts and sets session variables stored in conversation metadata
191
+ */
192
+ export async function restoreSessionVariables(conversationData) {
193
+ try {
194
+ // Check if conversation has stored session variables
195
+ const metadata = conversationData.metadata;
196
+ if (metadata && metadata.sessionVariables) {
197
+ logger.debug("Restoring session variables from conversation metadata");
198
+ const sessionVariables = metadata.sessionVariables;
199
+ for (const [key, value] of Object.entries(sessionVariables)) {
200
+ if (typeof value === "string" ||
201
+ typeof value === "number" ||
202
+ typeof value === "boolean") {
203
+ globalSession.setSessionVariable(key, value);
204
+ logger.debug(`Restored session variable: ${key} = ${value}`);
205
+ }
206
+ }
207
+ }
208
+ else {
209
+ logger.debug("No session variables found in conversation metadata");
210
+ }
211
+ }
212
+ catch (error) {
213
+ logger.warn("Failed to restore session variables:", error);
214
+ // Don't fail the restoration for this
215
+ }
216
+ }
217
+ // ============================================================================
218
+ // CONSTANTS
219
+ // ============================================================================
220
+ export const HISTORY_FILE = path.join(os.homedir(), ".neurolink_history");
221
+ export const LOOP_CACHE_CONFIG = {
222
+ TTL_MS: 5 * 60 * 1000,
223
+ };
224
+ export const LOOP_DISPLAY_LIMITS = {
225
+ MAX_CONVERSATIONS: 20,
226
+ CONTENT_LENGTH: 50,
227
+ TITLE_LENGTH: 40,
228
+ SESSION_ID_DISPLAY: 12,
229
+ SESSION_ID_SHORT: 8,
230
+ PAGE_SIZE: 15,
231
+ };
232
+ const TIME_UNITS = [
233
+ { unit: "year", seconds: 31536000 },
234
+ { unit: "month", seconds: 2592000 },
235
+ { unit: "week", seconds: 604800 },
236
+ { unit: "day", seconds: 86400 },
237
+ { unit: "hour", seconds: 3600 },
238
+ { unit: "minute", seconds: 60 },
239
+ ];
240
+ const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
241
+ const CONTENT_ICON_PATTERNS = [
242
+ {
243
+ icon: "💻",
244
+ pattern: /\b(code|debug|programming|function|class|bug|script|syntax|compile|error)\b/i,
245
+ },
246
+ {
247
+ icon: "💡",
248
+ pattern: /\b(explain|what|how|why|understand|clarify|learn|teach)\b/i,
249
+ },
250
+ {
251
+ icon: "📊",
252
+ pattern: /\b(analyz[e|ing]|data|report|metrics?|statistics?|chart|graph|visualiz[e|ation])\b/i,
253
+ },
254
+ {
255
+ icon: "✍️",
256
+ pattern: /\b(write|create|generat[e|ing]|compose|draft|build|author)\b/i,
257
+ },
258
+ {
259
+ icon: "🤖",
260
+ pattern: /\b(help|assist|support|guide|tutorial|show me)\b/i,
261
+ },
262
+ ];
@@ -17,8 +17,9 @@ export declare function convertToCoreMessages(messages: MultimodalChatMessage[])
17
17
  * Build a properly formatted message array for AI providers
18
18
  * Combines system prompt, conversation history, and current user prompt
19
19
  * Supports both TextGenerationOptions and StreamOptions
20
+ * Enhanced with CSV file processing support
20
21
  */
21
- export declare function buildMessagesArray(options: TextGenerationOptions | StreamOptions): CoreMessage[];
22
+ export declare function buildMessagesArray(options: TextGenerationOptions | StreamOptions): Promise<CoreMessage[]>;
22
23
  /**
23
24
  * Build multimodal message array with image support
24
25
  * Detects when images are present and routes through provider adapter
@@ -6,6 +6,7 @@
6
6
  import { CONVERSATION_INSTRUCTIONS } from "../config/conversationMemory.js";
7
7
  import { ProviderImageAdapter, MultimodalLogger, } from "../adapters/providerImageAdapter.js";
8
8
  import { logger } from "./logger.js";
9
+ import { FileDetector } from "./fileDetector.js";
9
10
  import { request } from "undici";
10
11
  import { readFileSync, existsSync } from "fs";
11
12
  /**
@@ -164,12 +165,33 @@ function toCoreMessage(message) {
164
165
  }
165
166
  return null; // Filter out tool_call and tool_result messages
166
167
  }
168
+ /**
169
+ * Format CSV metadata for LLM consumption
170
+ */
171
+ function formatCSVMetadata(metadata) {
172
+ const parts = [];
173
+ if (metadata.rowCount !== undefined) {
174
+ parts.push(`${metadata.rowCount} data rows`);
175
+ }
176
+ if (metadata.columnCount !== undefined) {
177
+ parts.push(`${metadata.columnCount} columns`);
178
+ }
179
+ if (metadata.columnNames && metadata.columnNames.length > 0) {
180
+ const columns = metadata.columnNames.join(", ");
181
+ parts.push(`Columns: [${columns}]`);
182
+ }
183
+ if (metadata.hasEmptyColumns) {
184
+ parts.push(`⚠️ Contains empty column names`);
185
+ }
186
+ return parts.length > 0 ? `**Metadata**: ${parts.join(" | ")}` : "";
187
+ }
167
188
  /**
168
189
  * Build a properly formatted message array for AI providers
169
190
  * Combines system prompt, conversation history, and current user prompt
170
191
  * Supports both TextGenerationOptions and StreamOptions
192
+ * Enhanced with CSV file processing support
171
193
  */
172
- export function buildMessagesArray(options) {
194
+ export async function buildMessagesArray(options) {
173
195
  const messages = [];
174
196
  // Check if conversation history exists
175
197
  const hasConversationHistory = options.conversationMessages && options.conversationMessages.length > 0;
@@ -205,6 +227,76 @@ export function buildMessagesArray(options) {
205
227
  else if ("input" in options && options.input?.text) {
206
228
  currentPrompt = options.input.text;
207
229
  }
230
+ // Process CSV files if present and inject into prompt using proper CSV parser
231
+ if ("input" in options && options.input) {
232
+ const input = options.input;
233
+ let csvContent = "";
234
+ const csvOptions = "csvOptions" in options ? options.csvOptions : undefined;
235
+ // Process explicit csvFiles array
236
+ if (input.csvFiles && input.csvFiles.length > 0) {
237
+ for (let i = 0; i < input.csvFiles.length; i++) {
238
+ const csvFile = input.csvFiles[i];
239
+ const filename = extractFilename(csvFile, i);
240
+ const filePath = typeof csvFile === "string" ? csvFile : filename;
241
+ try {
242
+ const result = await FileDetector.detectAndProcess(csvFile, {
243
+ allowedTypes: ["csv"],
244
+ csvOptions: csvOptions,
245
+ });
246
+ let csvSection = `\n\n## CSV Data from "${filename}":\n`;
247
+ // Add metadata from csv-parser library
248
+ if (result.metadata) {
249
+ const metadataText = formatCSVMetadata(result.metadata);
250
+ if (metadataText) {
251
+ csvSection += metadataText + `\n\n`;
252
+ }
253
+ }
254
+ csvSection += buildCSVToolInstructions(filePath);
255
+ csvSection += result.content;
256
+ csvContent += csvSection;
257
+ logger.info(`[CSV] ✅ Processed: ${filename}`, result.metadata);
258
+ }
259
+ catch (error) {
260
+ logger.error(`[CSV] ❌ Failed to process ${filename}:`, error);
261
+ csvContent += `\n\n## CSV Data Error: Failed to process "${filename}"\nReason: ${error instanceof Error ? error.message : "Unknown error"}`;
262
+ }
263
+ }
264
+ }
265
+ // Process unified files array (auto-detect CSV)
266
+ if (input.files && input.files.length > 0) {
267
+ for (const file of input.files) {
268
+ const filename = extractFilename(file);
269
+ try {
270
+ const result = await FileDetector.detectAndProcess(file, {
271
+ maxSize: 10 * 1024 * 1024,
272
+ allowedTypes: ["csv"],
273
+ csvOptions: csvOptions,
274
+ });
275
+ if (result.type === "csv") {
276
+ let csvSection = `\n\n## CSV Data from "${filename}":\n`;
277
+ // Add metadata from csv-parser library
278
+ if (result.metadata) {
279
+ const metadataText = formatCSVMetadata(result.metadata);
280
+ if (metadataText) {
281
+ csvSection += metadataText + `\n\n`;
282
+ }
283
+ }
284
+ csvSection += result.content;
285
+ csvContent += csvSection;
286
+ logger.info(`[FileDetector] ✅ CSV: ${filename}`, result.metadata);
287
+ }
288
+ }
289
+ catch (error) {
290
+ // Silently skip non-CSV files in auto-detect mode
291
+ logger.debug(`[FileDetector] Skipped ${filename}: ${error instanceof Error ? error.message : String(error)}`);
292
+ }
293
+ }
294
+ }
295
+ // Prepend CSV content to current prompt
296
+ if (csvContent) {
297
+ currentPrompt = csvContent + (currentPrompt || "");
298
+ }
299
+ }
208
300
  if (currentPrompt?.trim()) {
209
301
  messages.push({
210
302
  role: "user",
@@ -218,13 +310,95 @@ export function buildMessagesArray(options) {
218
310
  * Detects when images are present and routes through provider adapter
219
311
  */
220
312
  export async function buildMultimodalMessagesArray(options, provider, model) {
313
+ // Process unified files array (auto-detect)
314
+ if (options.input.files && options.input.files.length > 0) {
315
+ logger.info(`[FileDetector] Processing ${options.input.files.length} file(s) with auto-detection`);
316
+ options.input.text = options.input.text || "";
317
+ for (const file of options.input.files) {
318
+ try {
319
+ const result = await FileDetector.detectAndProcess(file, {
320
+ maxSize: 10 * 1024 * 1024,
321
+ allowedTypes: ["csv", "image"],
322
+ csvOptions: options.csvOptions,
323
+ });
324
+ if (result.type === "csv") {
325
+ const filename = extractFilename(file);
326
+ const filePath = typeof file === "string" ? file : filename;
327
+ let csvSection = `\n\n## CSV Data from "${filename}":\n`;
328
+ // Add metadata from csv-parser library
329
+ if (result.metadata) {
330
+ const metadataText = formatCSVMetadata(result.metadata);
331
+ if (metadataText) {
332
+ csvSection += metadataText + `\n\n`;
333
+ }
334
+ }
335
+ csvSection += buildCSVToolInstructions(filePath);
336
+ csvSection += result.content;
337
+ options.input.text += csvSection;
338
+ logger.info(`[FileDetector] ✅ CSV: ${filename}`);
339
+ }
340
+ else if (result.type === "image") {
341
+ options.input.images = [
342
+ ...(options.input.images || []),
343
+ result.content,
344
+ ];
345
+ logger.info(`[FileDetector] ✅ Image: ${result.mimeType}`);
346
+ }
347
+ }
348
+ catch (error) {
349
+ logger.error(`[FileDetector] ❌ Failed to process file:`, error);
350
+ }
351
+ }
352
+ }
353
+ // Process explicit CSV files array
354
+ if (options.input.csvFiles && options.input.csvFiles.length > 0) {
355
+ logger.info(`[CSV] Processing ${options.input.csvFiles.length} explicit CSV file(s)`);
356
+ options.input.text = options.input.text || "";
357
+ for (let i = 0; i < options.input.csvFiles.length; i++) {
358
+ const csvFile = options.input.csvFiles[i];
359
+ try {
360
+ const result = await FileDetector.detectAndProcess(csvFile, {
361
+ allowedTypes: ["csv"],
362
+ csvOptions: options.csvOptions,
363
+ });
364
+ const filename = extractFilename(csvFile, i);
365
+ const filePath = typeof csvFile === "string" ? csvFile : filename;
366
+ let csvSection = `\n\n## CSV Data from "${filename}":\n`;
367
+ // Add metadata from csv-parser library
368
+ if (result.metadata) {
369
+ const metadataText = formatCSVMetadata(result.metadata);
370
+ if (metadataText) {
371
+ csvSection += metadataText + `\n\n`;
372
+ }
373
+ }
374
+ csvSection += buildCSVToolInstructions(filePath);
375
+ csvSection += result.content;
376
+ options.input.text += csvSection;
377
+ logger.info(`[CSV] ✅ Processed: ${filename}`);
378
+ }
379
+ catch (error) {
380
+ logger.error(`[CSV] ❌ Failed:`, error);
381
+ const filename = extractFilename(csvFile, i);
382
+ options.input.text += `\n\n## CSV Data Error: Failed to process "${filename}"`;
383
+ options.input.text += `\nReason: ${error instanceof Error ? error.message : "Unknown error"}`;
384
+ }
385
+ }
386
+ }
221
387
  // Check if this is a multimodal request
222
388
  const hasImages = (options.input.images && options.input.images.length > 0) ||
223
389
  (options.input.content &&
224
390
  options.input.content.some((c) => c.type === "image"));
225
391
  // If no images, use standard message building and convert to MultimodalChatMessage[]
226
392
  if (!hasImages) {
227
- const standardMessages = buildMessagesArray(options);
393
+ // Clear csvFiles and files arrays to prevent duplication
394
+ // (already processed and added to options.input.text above)
395
+ if (options.input.csvFiles) {
396
+ options.input.csvFiles = [];
397
+ }
398
+ if (options.input.files) {
399
+ options.input.files = [];
400
+ }
401
+ const standardMessages = await buildMessagesArray(options);
228
402
  return standardMessages.map((msg) => ({
229
403
  role: msg.role,
230
404
  content: typeof msg.content === "string" ? msg.content : msg.content,
@@ -490,3 +664,24 @@ async function convertSimpleImagesToProviderFormat(text, images, provider, _mode
490
664
  });
491
665
  return content;
492
666
  }
667
+ /**
668
+ * Extract filename from file input
669
+ */
670
+ function extractFilename(file, index = 0) {
671
+ if (typeof file === "string") {
672
+ if (file.startsWith("http")) {
673
+ try {
674
+ const url = new URL(file);
675
+ return url.pathname.split("/").pop() || `file-${index + 1}`;
676
+ }
677
+ catch {
678
+ return `file-${index + 1}`;
679
+ }
680
+ }
681
+ return (file.split("/").pop() || file.split("\\").pop() || `file-${index + 1}`);
682
+ }
683
+ return `file-${index + 1}`;
684
+ }
685
+ function buildCSVToolInstructions(filePath) {
686
+ return `\n**IMPORTANT**: For counting, aggregation, or statistical operations, use the analyzeCSV tool with filePath="${filePath}". The tool reads the file directly - do NOT pass CSV content.\n\nExample: analyzeCSV(filePath="${filePath}", operation="count_by_column", column="merchant_id")\n\n`;
687
+ }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { GenerateOptions, UnifiedGenerationOptions } from "../types/generateTypes.js";
7
7
  import type { StreamOptions } from "../types/streamTypes.js";
8
- import type { ExecutionContext } from "../mcp/contracts/mcpContract.js";
8
+ import type { ExecutionContext } from "../types/tools.js";
9
9
  /**
10
10
  * Enhancement types for different optimization strategies
11
11
  */