@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.
- package/CHANGELOG.md +13 -0
- package/README.md +215 -16
- package/dist/agent/directTools.d.ts +55 -0
- package/dist/agent/directTools.js +266 -0
- package/dist/cli/factories/commandFactory.d.ts +2 -0
- package/dist/cli/factories/commandFactory.js +130 -16
- package/dist/cli/index.js +0 -0
- package/dist/cli/loop/conversationSelector.d.ts +45 -0
- package/dist/cli/loop/conversationSelector.js +222 -0
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/loop/session.d.ts +36 -8
- package/dist/cli/loop/session.js +257 -61
- package/dist/core/baseProvider.js +9 -2
- package/dist/core/evaluation.js +5 -2
- package/dist/factories/providerRegistry.js +2 -2
- package/dist/lib/agent/directTools.d.ts +55 -0
- package/dist/lib/agent/directTools.js +266 -0
- package/dist/lib/core/baseProvider.js +9 -2
- package/dist/lib/core/evaluation.js +5 -2
- package/dist/lib/factories/providerRegistry.js +2 -2
- package/dist/lib/mcp/factory.d.ts +2 -157
- package/dist/lib/mcp/flexibleToolValidator.d.ts +1 -5
- package/dist/lib/mcp/index.d.ts +3 -2
- package/dist/lib/mcp/mcpCircuitBreaker.d.ts +1 -75
- package/dist/lib/mcp/mcpClientFactory.d.ts +1 -20
- package/dist/lib/mcp/mcpClientFactory.js +1 -0
- package/dist/lib/mcp/registry.d.ts +3 -10
- package/dist/lib/mcp/servers/agent/directToolsServer.d.ts +1 -1
- package/dist/lib/mcp/servers/aiProviders/aiCoreServer.d.ts +1 -1
- package/dist/lib/mcp/servers/utilities/utilityServer.d.ts +1 -1
- package/dist/lib/mcp/toolDiscoveryService.d.ts +3 -84
- package/dist/lib/mcp/toolRegistry.d.ts +2 -24
- package/dist/lib/middleware/builtin/guardrails.d.ts +5 -16
- package/dist/lib/middleware/builtin/guardrails.js +44 -39
- package/dist/lib/middleware/utils/guardrailsUtils.d.ts +64 -0
- package/dist/lib/middleware/utils/guardrailsUtils.js +387 -0
- package/dist/lib/neurolink.d.ts +1 -1
- package/dist/lib/providers/anthropic.js +46 -3
- package/dist/lib/providers/azureOpenai.js +8 -2
- package/dist/lib/providers/googleAiStudio.js +8 -2
- package/dist/lib/providers/googleVertex.js +11 -2
- package/dist/lib/providers/huggingFace.js +1 -1
- package/dist/lib/providers/litellm.js +1 -1
- package/dist/lib/providers/mistral.js +1 -1
- package/dist/lib/providers/openAI.js +46 -3
- package/dist/lib/session/globalSessionState.d.ts +26 -0
- package/dist/lib/session/globalSessionState.js +49 -0
- package/dist/lib/types/cli.d.ts +28 -0
- package/dist/lib/types/content.d.ts +18 -5
- package/dist/lib/types/contextTypes.d.ts +1 -1
- package/dist/lib/types/conversation.d.ts +55 -4
- package/dist/lib/types/fileTypes.d.ts +65 -0
- package/dist/lib/types/fileTypes.js +4 -0
- package/dist/lib/types/generateTypes.d.ts +12 -0
- package/dist/lib/types/guardrails.d.ts +103 -0
- package/dist/lib/types/guardrails.js +1 -0
- package/dist/lib/types/index.d.ts +4 -2
- package/dist/lib/types/index.js +4 -0
- package/dist/lib/types/mcpTypes.d.ts +407 -14
- package/dist/lib/types/streamTypes.d.ts +7 -0
- package/dist/lib/types/tools.d.ts +132 -35
- package/dist/lib/utils/csvProcessor.d.ts +68 -0
- package/dist/lib/utils/csvProcessor.js +277 -0
- package/dist/lib/utils/fileDetector.d.ts +57 -0
- package/dist/lib/utils/fileDetector.js +457 -0
- package/dist/lib/utils/imageProcessor.d.ts +10 -0
- package/dist/lib/utils/imageProcessor.js +22 -0
- package/dist/lib/utils/loopUtils.d.ts +71 -0
- package/dist/lib/utils/loopUtils.js +262 -0
- package/dist/lib/utils/messageBuilder.d.ts +2 -1
- package/dist/lib/utils/messageBuilder.js +197 -2
- package/dist/lib/utils/optionsUtils.d.ts +1 -1
- package/dist/mcp/factory.d.ts +2 -157
- package/dist/mcp/flexibleToolValidator.d.ts +1 -5
- package/dist/mcp/index.d.ts +3 -2
- package/dist/mcp/mcpCircuitBreaker.d.ts +1 -75
- package/dist/mcp/mcpClientFactory.d.ts +1 -20
- package/dist/mcp/mcpClientFactory.js +1 -0
- package/dist/mcp/registry.d.ts +3 -10
- package/dist/mcp/servers/agent/directToolsServer.d.ts +1 -1
- package/dist/mcp/servers/aiProviders/aiCoreServer.d.ts +1 -1
- package/dist/mcp/servers/utilities/utilityServer.d.ts +1 -1
- package/dist/mcp/toolDiscoveryService.d.ts +3 -84
- package/dist/mcp/toolRegistry.d.ts +2 -24
- package/dist/middleware/builtin/guardrails.d.ts +5 -16
- package/dist/middleware/builtin/guardrails.js +44 -39
- package/dist/middleware/utils/guardrailsUtils.d.ts +64 -0
- package/dist/middleware/utils/guardrailsUtils.js +387 -0
- package/dist/neurolink.d.ts +1 -1
- package/dist/providers/anthropic.js +46 -3
- package/dist/providers/azureOpenai.js +8 -2
- package/dist/providers/googleAiStudio.js +8 -2
- package/dist/providers/googleVertex.js +11 -2
- package/dist/providers/huggingFace.js +1 -1
- package/dist/providers/litellm.js +1 -1
- package/dist/providers/mistral.js +1 -1
- package/dist/providers/openAI.js +46 -3
- package/dist/session/globalSessionState.d.ts +26 -0
- package/dist/session/globalSessionState.js +49 -0
- package/dist/types/cli.d.ts +28 -0
- package/dist/types/content.d.ts +18 -5
- package/dist/types/contextTypes.d.ts +1 -1
- package/dist/types/conversation.d.ts +55 -4
- package/dist/types/fileTypes.d.ts +65 -0
- package/dist/types/fileTypes.js +4 -0
- package/dist/types/generateTypes.d.ts +12 -0
- package/dist/types/guardrails.d.ts +103 -0
- package/dist/types/guardrails.js +1 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.js +4 -0
- package/dist/types/mcpTypes.d.ts +407 -14
- package/dist/types/modelTypes.d.ts +6 -6
- package/dist/types/streamTypes.d.ts +7 -0
- package/dist/types/tools.d.ts +132 -35
- package/dist/utils/csvProcessor.d.ts +68 -0
- package/dist/utils/csvProcessor.js +277 -0
- package/dist/utils/fileDetector.d.ts +57 -0
- package/dist/utils/fileDetector.js +457 -0
- package/dist/utils/imageProcessor.d.ts +10 -0
- package/dist/utils/imageProcessor.js +22 -0
- package/dist/utils/loopUtils.d.ts +71 -0
- package/dist/utils/loopUtils.js +262 -0
- package/dist/utils/messageBuilder.d.ts +2 -1
- package/dist/utils/messageBuilder.js +197 -2
- package/dist/utils/optionsUtils.d.ts +1 -1
- package/package.json +9 -3
- package/dist/lib/mcp/contracts/mcpContract.d.ts +0 -106
- package/dist/lib/mcp/contracts/mcpContract.js +0 -5
- package/dist/mcp/contracts/mcpContract.d.ts +0 -106
- 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
|
-
|
|
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 "../
|
|
8
|
+
import type { ExecutionContext } from "../types/tools.js";
|
|
9
9
|
/**
|
|
10
10
|
* Enhancement types for different optimization strategies
|
|
11
11
|
*/
|