@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
|
@@ -49,6 +49,26 @@ export class CLICommandFactory {
|
|
|
49
49
|
description: "Add image file for multimodal analysis (can be used multiple times)",
|
|
50
50
|
alias: "i",
|
|
51
51
|
},
|
|
52
|
+
csv: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Add CSV file for data analysis (can be used multiple times)",
|
|
55
|
+
alias: "c",
|
|
56
|
+
},
|
|
57
|
+
file: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Add file with auto-detection (CSV, image, etc. - can be used multiple times)",
|
|
60
|
+
},
|
|
61
|
+
csvMaxRows: {
|
|
62
|
+
type: "number",
|
|
63
|
+
default: 1000,
|
|
64
|
+
description: "Maximum number of CSV rows to process",
|
|
65
|
+
},
|
|
66
|
+
csvFormat: {
|
|
67
|
+
type: "string",
|
|
68
|
+
choices: ["raw", "markdown", "json"],
|
|
69
|
+
default: "raw",
|
|
70
|
+
description: "CSV output format (raw recommended for large files)",
|
|
71
|
+
},
|
|
52
72
|
model: {
|
|
53
73
|
type: "string",
|
|
54
74
|
description: "Specific model to use (e.g. gemini-2.5-pro, gemini-2.5-flash)",
|
|
@@ -188,6 +208,20 @@ export class CLICommandFactory {
|
|
|
188
208
|
// File paths will be converted to base64 by the message builder
|
|
189
209
|
return imagePaths;
|
|
190
210
|
}
|
|
211
|
+
// Helper method to process CLI CSV files
|
|
212
|
+
static processCliCSVFiles(csvFiles) {
|
|
213
|
+
if (!csvFiles) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
return Array.isArray(csvFiles) ? csvFiles : [csvFiles];
|
|
217
|
+
}
|
|
218
|
+
// Helper method to process CLI files with auto-detection
|
|
219
|
+
static processCliFiles(files) {
|
|
220
|
+
if (!files) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
return Array.isArray(files) ? files : [files];
|
|
224
|
+
}
|
|
191
225
|
// Helper method to process common options
|
|
192
226
|
static processOptions(argv) {
|
|
193
227
|
// Handle noColor option by disabling chalk
|
|
@@ -674,7 +708,7 @@ export class CLICommandFactory {
|
|
|
674
708
|
static createLoopCommand() {
|
|
675
709
|
return {
|
|
676
710
|
command: "loop",
|
|
677
|
-
describe: "Start an interactive loop session",
|
|
711
|
+
describe: "Start an interactive loop session with conversation management",
|
|
678
712
|
builder: (yargs) => this.buildOptions(yargs, {
|
|
679
713
|
"enable-conversation-memory": {
|
|
680
714
|
type: "boolean",
|
|
@@ -696,9 +730,27 @@ export class CLICommandFactory {
|
|
|
696
730
|
description: "Automatically use Redis if available",
|
|
697
731
|
default: true,
|
|
698
732
|
},
|
|
733
|
+
resume: {
|
|
734
|
+
type: "string",
|
|
735
|
+
description: "Directly resume a specific conversation by session ID",
|
|
736
|
+
alias: "r",
|
|
737
|
+
},
|
|
738
|
+
new: {
|
|
739
|
+
type: "boolean",
|
|
740
|
+
description: "Force start a new conversation (skip selection menu)",
|
|
741
|
+
alias: "n",
|
|
742
|
+
},
|
|
743
|
+
"list-conversations": {
|
|
744
|
+
type: "boolean",
|
|
745
|
+
description: "List available conversations and exit",
|
|
746
|
+
alias: "l",
|
|
747
|
+
},
|
|
699
748
|
})
|
|
700
|
-
.example("$0 loop
|
|
701
|
-
.example("$0 loop", "
|
|
749
|
+
.example("$0 loop", "Start interactive session with conversation selection")
|
|
750
|
+
.example("$0 loop --new", "Force start new conversation")
|
|
751
|
+
.example("$0 loop --resume abc123", "Resume specific conversation")
|
|
752
|
+
.example("$0 loop --list-conversations", "List available conversations")
|
|
753
|
+
.example("$0 loop --no-auto-redis", "Use in-memory storage only")
|
|
702
754
|
.example("$0 loop --enable-conversation-memory", "Start loop with memory"),
|
|
703
755
|
handler: async (argv) => {
|
|
704
756
|
if (globalSession.getCurrentSessionId()) {
|
|
@@ -706,7 +758,7 @@ export class CLICommandFactory {
|
|
|
706
758
|
return;
|
|
707
759
|
}
|
|
708
760
|
let conversationMemoryConfig;
|
|
709
|
-
const { enableConversationMemory, maxSessions, maxTurnsPerSession, autoRedis, } = argv;
|
|
761
|
+
const { enableConversationMemory, maxSessions, maxTurnsPerSession, autoRedis, listConversations, } = argv;
|
|
710
762
|
if (enableConversationMemory) {
|
|
711
763
|
let storageType = "memory";
|
|
712
764
|
if (autoRedis) {
|
|
@@ -731,7 +783,48 @@ export class CLICommandFactory {
|
|
|
731
783
|
maxTurnsPerSession: maxTurnsPerSession,
|
|
732
784
|
};
|
|
733
785
|
}
|
|
734
|
-
|
|
786
|
+
// Handle --list-conversations option
|
|
787
|
+
if (listConversations) {
|
|
788
|
+
const { ConversationSelector } = await import("../loop/conversationSelector.js");
|
|
789
|
+
const conversationSelector = new ConversationSelector();
|
|
790
|
+
try {
|
|
791
|
+
const hasConversations = await conversationSelector.hasStoredConversations();
|
|
792
|
+
if (!hasConversations) {
|
|
793
|
+
logger.always(chalk.yellow("📝 No stored conversations found"));
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const conversations = await conversationSelector.getAvailableConversations();
|
|
797
|
+
logger.always(chalk.blue("📋 Available Conversations:"));
|
|
798
|
+
conversations.forEach((conv, index) => {
|
|
799
|
+
const sessionId = conv.sessionId.slice(0, 12) + "...";
|
|
800
|
+
const title = conv.title || "Untitled Conversation";
|
|
801
|
+
const messageCount = conv.messageCount || 0;
|
|
802
|
+
const lastActivity = conv.updatedAt
|
|
803
|
+
? new Date(conv.updatedAt).toLocaleDateString()
|
|
804
|
+
: "Unknown";
|
|
805
|
+
logger.always(`${index + 1}. ${chalk.cyan(sessionId)} - ${title}`);
|
|
806
|
+
logger.always(` ${chalk.gray(`${messageCount} messages | Last: ${lastActivity}`)}`);
|
|
807
|
+
});
|
|
808
|
+
logger.always(chalk.gray(`\nUse: neurolink loop --resume <session-id> to resume a conversation`));
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
logger.error("Failed to list conversations:", error);
|
|
812
|
+
}
|
|
813
|
+
finally {
|
|
814
|
+
await conversationSelector.close();
|
|
815
|
+
}
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
// Create enhanced session with direct session management options
|
|
819
|
+
const sessionOptions = {};
|
|
820
|
+
// Pass CLI options to session for direct session management
|
|
821
|
+
if (argv.resume && typeof argv.resume === "string") {
|
|
822
|
+
sessionOptions.directResumeSessionId = argv.resume;
|
|
823
|
+
}
|
|
824
|
+
if (argv.new) {
|
|
825
|
+
sessionOptions.forceNewSession = true;
|
|
826
|
+
}
|
|
827
|
+
const session = new LoopSession(initializeCliParser, conversationMemoryConfig, sessionOptions);
|
|
735
828
|
await session.start();
|
|
736
829
|
},
|
|
737
830
|
};
|
|
@@ -967,12 +1060,21 @@ export class CLICommandFactory {
|
|
|
967
1060
|
toolsEnabled: !options.disableTools,
|
|
968
1061
|
});
|
|
969
1062
|
}
|
|
970
|
-
// Process CLI
|
|
1063
|
+
// Process CLI multimodal inputs
|
|
971
1064
|
const imageBuffers = CLICommandFactory.processCliImages(argv.image);
|
|
1065
|
+
const csvFiles = CLICommandFactory.processCliCSVFiles(argv.csv);
|
|
1066
|
+
const files = CLICommandFactory.processCliFiles(argv.file);
|
|
972
1067
|
const result = await sdk.generate({
|
|
973
|
-
input:
|
|
974
|
-
|
|
975
|
-
|
|
1068
|
+
input: {
|
|
1069
|
+
text: inputText,
|
|
1070
|
+
...(imageBuffers && { images: imageBuffers }),
|
|
1071
|
+
...(csvFiles && { csvFiles }),
|
|
1072
|
+
...(files && { files }),
|
|
1073
|
+
},
|
|
1074
|
+
csvOptions: {
|
|
1075
|
+
maxRows: argv.csvMaxRows,
|
|
1076
|
+
formatStyle: argv.csvFormat,
|
|
1077
|
+
},
|
|
976
1078
|
provider: enhancedOptions.provider,
|
|
977
1079
|
model: enhancedOptions.model,
|
|
978
1080
|
temperature: enhancedOptions.temperature,
|
|
@@ -1140,12 +1242,21 @@ export class CLICommandFactory {
|
|
|
1140
1242
|
const context = sessionId
|
|
1141
1243
|
? { ...contextMetadata, sessionId }
|
|
1142
1244
|
: contextMetadata;
|
|
1143
|
-
// Process CLI
|
|
1245
|
+
// Process CLI multimodal inputs
|
|
1144
1246
|
const imageBuffers = CLICommandFactory.processCliImages(argv.image);
|
|
1247
|
+
const csvFiles = CLICommandFactory.processCliCSVFiles(argv.csv);
|
|
1248
|
+
const files = CLICommandFactory.processCliFiles(argv.file);
|
|
1145
1249
|
const stream = await sdk.stream({
|
|
1146
|
-
input:
|
|
1147
|
-
|
|
1148
|
-
|
|
1250
|
+
input: {
|
|
1251
|
+
text: inputText,
|
|
1252
|
+
...(imageBuffers && { images: imageBuffers }),
|
|
1253
|
+
...(csvFiles && { csvFiles }),
|
|
1254
|
+
...(files && { files }),
|
|
1255
|
+
},
|
|
1256
|
+
csvOptions: {
|
|
1257
|
+
maxRows: argv.csvMaxRows,
|
|
1258
|
+
formatStyle: argv.csvFormat,
|
|
1259
|
+
},
|
|
1149
1260
|
provider: enhancedOptions.provider,
|
|
1150
1261
|
model: enhancedOptions.model,
|
|
1151
1262
|
temperature: enhancedOptions.temperature,
|
|
@@ -1179,11 +1290,14 @@ export class CLICommandFactory {
|
|
|
1179
1290
|
let fullContent = "";
|
|
1180
1291
|
let contentReceived = false;
|
|
1181
1292
|
const abortController = new AbortController();
|
|
1182
|
-
// Create timeout promise for stream consumption (30 seconds)
|
|
1293
|
+
// Create timeout promise for stream consumption (default: 30 seconds, respects user-provided timeout)
|
|
1294
|
+
const streamTimeout = options.timeout && typeof options.timeout === "number"
|
|
1295
|
+
? options.timeout * 1000
|
|
1296
|
+
: 30000;
|
|
1183
1297
|
const timeoutPromise = new Promise((_, reject) => {
|
|
1184
1298
|
const timeoutId = setTimeout(() => {
|
|
1185
1299
|
if (!contentReceived) {
|
|
1186
|
-
const timeoutError = new Error(
|
|
1300
|
+
const timeoutError = new Error(`\n❌ Stream timeout - no content received within ${streamTimeout / 1000} seconds\n` +
|
|
1187
1301
|
"This usually indicates authentication or network issues\n\n" +
|
|
1188
1302
|
"🔧 Try these steps:\n" +
|
|
1189
1303
|
"1. Check your provider credentials are configured correctly\n" +
|
|
@@ -1191,7 +1305,7 @@ export class CLICommandFactory {
|
|
|
1191
1305
|
`3. Use debug mode: neurolink stream "test" --provider ${options.provider} --debug`);
|
|
1192
1306
|
reject(timeoutError);
|
|
1193
1307
|
}
|
|
1194
|
-
},
|
|
1308
|
+
}, streamTimeout);
|
|
1195
1309
|
// Clean up timeout when aborted
|
|
1196
1310
|
abortController.signal.addEventListener("abort", () => {
|
|
1197
1311
|
clearTimeout(timeoutId);
|
package/dist/cli/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Selector for Loop Mode
|
|
3
|
+
* Handles discovery and selection of stored conversations from Redis
|
|
4
|
+
*/
|
|
5
|
+
import type { ConversationSummary } from "../../lib/types/conversation.js";
|
|
6
|
+
import type { RedisStorageConfig } from "../../lib/types/conversation.js";
|
|
7
|
+
export declare class ConversationSelector {
|
|
8
|
+
private redisClient;
|
|
9
|
+
private redisConfig;
|
|
10
|
+
private conversationCache;
|
|
11
|
+
private cacheTimestamp;
|
|
12
|
+
constructor(redisConfig?: RedisStorageConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Initialize Redis connection
|
|
15
|
+
*/
|
|
16
|
+
private initializeRedis;
|
|
17
|
+
/**
|
|
18
|
+
* Get available conversations for a user
|
|
19
|
+
*/
|
|
20
|
+
getAvailableConversations(userId?: string): Promise<ConversationSummary[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Display conversation menu and get user selection
|
|
23
|
+
*/
|
|
24
|
+
displayConversationMenu(userId?: string): Promise<string | "NEW_CONVERSATION">;
|
|
25
|
+
/**
|
|
26
|
+
* Check if there are any stored conversations
|
|
27
|
+
*/
|
|
28
|
+
hasStoredConversations(userId?: string): Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* Close Redis connection
|
|
31
|
+
*/
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
private scanConversationKeys;
|
|
34
|
+
private processConversationKeys;
|
|
35
|
+
private processSingleConversationKey;
|
|
36
|
+
private sortConversationsByDate;
|
|
37
|
+
private updateCache;
|
|
38
|
+
private filterConversationsByUser;
|
|
39
|
+
private createMenuChoices;
|
|
40
|
+
private showSelectionPrompt;
|
|
41
|
+
private handleRetrievalError;
|
|
42
|
+
private handleMenuError;
|
|
43
|
+
private createConversationSummary;
|
|
44
|
+
private formatConversationChoice;
|
|
45
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Selector for Loop Mode
|
|
3
|
+
* Handles discovery and selection of stored conversations from Redis
|
|
4
|
+
*/
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { createRedisClient, scanKeys, deserializeConversation, getNormalizedConfig, } from "../../lib/utils/redis.js";
|
|
8
|
+
import { logger } from "../../lib/utils/logger.js";
|
|
9
|
+
import { LOOP_CACHE_CONFIG, LOOP_DISPLAY_LIMITS, generateConversationTitle, truncateText, formatTimeAgo, getContentIcon, } from "../../lib/utils/loopUtils.js";
|
|
10
|
+
export class ConversationSelector {
|
|
11
|
+
redisClient = null;
|
|
12
|
+
redisConfig;
|
|
13
|
+
conversationCache = null;
|
|
14
|
+
cacheTimestamp = 0;
|
|
15
|
+
constructor(redisConfig = {}) {
|
|
16
|
+
this.redisConfig = getNormalizedConfig(redisConfig);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Initialize Redis connection
|
|
20
|
+
*/
|
|
21
|
+
async initializeRedis() {
|
|
22
|
+
if (!this.redisClient) {
|
|
23
|
+
this.redisClient = await createRedisClient(this.redisConfig);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get available conversations for a user
|
|
28
|
+
*/
|
|
29
|
+
async getAvailableConversations(userId) {
|
|
30
|
+
// Check if cached conversations are still valid (within TTL)
|
|
31
|
+
if (this.conversationCache &&
|
|
32
|
+
Date.now() - this.cacheTimestamp < LOOP_CACHE_CONFIG.TTL_MS) {
|
|
33
|
+
logger.debug("Using cached conversation list");
|
|
34
|
+
return this.filterConversationsByUser(this.conversationCache, userId);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
await this.initializeRedis();
|
|
38
|
+
if (!this.redisClient) {
|
|
39
|
+
throw new Error("Redis client not available");
|
|
40
|
+
}
|
|
41
|
+
const keys = await this.scanConversationKeys();
|
|
42
|
+
if (keys.length === 0) {
|
|
43
|
+
logger.debug("No conversations found in Redis");
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const summaries = await this.processConversationKeys(keys);
|
|
47
|
+
const sortedSummaries = this.sortConversationsByDate(summaries);
|
|
48
|
+
this.updateCache(sortedSummaries);
|
|
49
|
+
return this.filterConversationsByUser(sortedSummaries, userId);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return this.handleRetrievalError(error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Display conversation menu and get user selection
|
|
57
|
+
*/
|
|
58
|
+
async displayConversationMenu(userId) {
|
|
59
|
+
try {
|
|
60
|
+
const conversations = await this.getAvailableConversations(userId);
|
|
61
|
+
if (conversations.length === 0) {
|
|
62
|
+
logger.debug("No conversations available for selection");
|
|
63
|
+
return "NEW_CONVERSATION";
|
|
64
|
+
}
|
|
65
|
+
const choices = this.createMenuChoices(conversations);
|
|
66
|
+
return await this.showSelectionPrompt(choices);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return this.handleMenuError(error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Check if there are any stored conversations
|
|
74
|
+
*/
|
|
75
|
+
async hasStoredConversations(userId) {
|
|
76
|
+
try {
|
|
77
|
+
const conversations = await this.getAvailableConversations(userId);
|
|
78
|
+
return conversations.length > 0;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
logger.debug("Failed to check for stored conversations:", error);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Close Redis connection
|
|
87
|
+
*/
|
|
88
|
+
async close() {
|
|
89
|
+
if (this.redisClient) {
|
|
90
|
+
await this.redisClient.quit();
|
|
91
|
+
this.redisClient = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async scanConversationKeys() {
|
|
95
|
+
if (!this.redisClient) {
|
|
96
|
+
throw new Error("Redis client not initialized");
|
|
97
|
+
}
|
|
98
|
+
const pattern = `${this.redisConfig.keyPrefix}*`;
|
|
99
|
+
const keys = await scanKeys(this.redisClient, pattern);
|
|
100
|
+
logger.debug(`Found ${keys.length} conversation keys in Redis`);
|
|
101
|
+
return keys;
|
|
102
|
+
}
|
|
103
|
+
async processConversationKeys(keys) {
|
|
104
|
+
const summaries = [];
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
const summary = await this.processSingleConversationKey(key);
|
|
107
|
+
if (summary) {
|
|
108
|
+
summaries.push(summary);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return summaries;
|
|
112
|
+
}
|
|
113
|
+
async processSingleConversationKey(key) {
|
|
114
|
+
if (!this.redisClient) {
|
|
115
|
+
logger.warn(`Redis client not available for key ${key}`);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const conversationData = await this.redisClient.get(key);
|
|
120
|
+
const conversation = deserializeConversation(conversationData);
|
|
121
|
+
if (conversation &&
|
|
122
|
+
conversation.messages &&
|
|
123
|
+
conversation.messages.length > 0) {
|
|
124
|
+
return this.createConversationSummary(conversation);
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
logger.warn(`Failed to process conversation key ${key}:`, error);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
sortConversationsByDate(summaries) {
|
|
134
|
+
return summaries.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
135
|
+
}
|
|
136
|
+
updateCache(summaries) {
|
|
137
|
+
this.conversationCache = summaries;
|
|
138
|
+
this.cacheTimestamp = Date.now();
|
|
139
|
+
logger.debug(`Retrieved ${summaries.length} valid conversations`);
|
|
140
|
+
}
|
|
141
|
+
filterConversationsByUser(summaries, userId) {
|
|
142
|
+
if (!userId) {
|
|
143
|
+
return summaries;
|
|
144
|
+
}
|
|
145
|
+
return summaries.filter((summary) => summary.userId === userId);
|
|
146
|
+
}
|
|
147
|
+
/*
|
|
148
|
+
* Create menu choices for inquirer prompt
|
|
149
|
+
*/
|
|
150
|
+
createMenuChoices(conversations) {
|
|
151
|
+
const choices = [
|
|
152
|
+
{
|
|
153
|
+
name: chalk.green("🆕 Start New Conversation"),
|
|
154
|
+
value: "NEW_CONVERSATION",
|
|
155
|
+
short: "New Conversation",
|
|
156
|
+
},
|
|
157
|
+
new inquirer.Separator(),
|
|
158
|
+
];
|
|
159
|
+
for (const conversation of conversations.slice(0, LOOP_DISPLAY_LIMITS.MAX_CONVERSATIONS)) {
|
|
160
|
+
const choice = this.formatConversationChoice(conversation);
|
|
161
|
+
choices.push(choice);
|
|
162
|
+
}
|
|
163
|
+
return choices;
|
|
164
|
+
}
|
|
165
|
+
async showSelectionPrompt(choices) {
|
|
166
|
+
const answer = await inquirer.prompt([
|
|
167
|
+
{
|
|
168
|
+
type: "list",
|
|
169
|
+
name: "selectedConversation",
|
|
170
|
+
message: "Select a conversation to continue:",
|
|
171
|
+
choices,
|
|
172
|
+
pageSize: LOOP_DISPLAY_LIMITS.PAGE_SIZE,
|
|
173
|
+
},
|
|
174
|
+
]);
|
|
175
|
+
return answer.selectedConversation;
|
|
176
|
+
}
|
|
177
|
+
handleRetrievalError(error) {
|
|
178
|
+
logger.error("Failed to retrieve conversations:", error);
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
handleMenuError(error) {
|
|
182
|
+
logger.error("Failed to display conversation menu:", error);
|
|
183
|
+
return "NEW_CONVERSATION";
|
|
184
|
+
}
|
|
185
|
+
createConversationSummary(conversation) {
|
|
186
|
+
const messages = conversation.messages;
|
|
187
|
+
const firstMessage = messages[0];
|
|
188
|
+
const lastMessage = messages[messages.length - 1];
|
|
189
|
+
return {
|
|
190
|
+
sessionId: conversation.sessionId,
|
|
191
|
+
id: conversation.id,
|
|
192
|
+
title: conversation.title || generateConversationTitle(firstMessage.content),
|
|
193
|
+
firstMessage: {
|
|
194
|
+
content: truncateText(firstMessage.content, LOOP_DISPLAY_LIMITS.CONTENT_LENGTH),
|
|
195
|
+
timestamp: firstMessage.timestamp || conversation.createdAt,
|
|
196
|
+
},
|
|
197
|
+
lastMessage: {
|
|
198
|
+
content: truncateText(lastMessage.content, LOOP_DISPLAY_LIMITS.CONTENT_LENGTH),
|
|
199
|
+
timestamp: lastMessage.timestamp || conversation.updatedAt,
|
|
200
|
+
},
|
|
201
|
+
messageCount: messages.length,
|
|
202
|
+
userId: conversation.userId,
|
|
203
|
+
duration: formatTimeAgo(conversation.updatedAt),
|
|
204
|
+
createdAt: conversation.createdAt,
|
|
205
|
+
updatedAt: conversation.updatedAt,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
formatConversationChoice(summary) {
|
|
209
|
+
const icon = getContentIcon(summary.firstMessage.content);
|
|
210
|
+
const title = chalk.white(summary.title || "Untitled Conversation");
|
|
211
|
+
const duration = chalk.gray(`(${summary.duration})`);
|
|
212
|
+
const details = chalk.gray(` └ ${summary.messageCount} message${summary.messageCount !== 1 ? "s" : ""} | ` +
|
|
213
|
+
`Session: ${summary.sessionId.slice(0, LOOP_DISPLAY_LIMITS.SESSION_ID_DISPLAY)}... | ` +
|
|
214
|
+
`Updated: ${new Date(summary.updatedAt).toLocaleString()}`);
|
|
215
|
+
const name = `${icon} ${title} ${duration}\n${details}`;
|
|
216
|
+
return {
|
|
217
|
+
name,
|
|
218
|
+
value: summary.sessionId,
|
|
219
|
+
short: `${summary.title} (${summary.sessionId.slice(0, LOOP_DISPLAY_LIMITS.SESSION_ID_SHORT)}...)`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -12,4 +12,4 @@ export interface OptionSchema {
|
|
|
12
12
|
* This object provides metadata for validation and help text in the CLI loop.
|
|
13
13
|
* It is derived from the main TextGenerationOptions interface to ensure consistency.
|
|
14
14
|
*/
|
|
15
|
-
export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region">, OptionSchema>;
|
|
15
|
+
export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions">, OptionSchema>;
|
|
@@ -2,27 +2,55 @@ import type { Argv } from "yargs";
|
|
|
2
2
|
import type { ConversationMemoryConfig } from "../../lib/types/conversation.js";
|
|
3
3
|
export declare class LoopSession {
|
|
4
4
|
private conversationMemoryConfig?;
|
|
5
|
+
private options?;
|
|
5
6
|
private initializeCliParser;
|
|
6
7
|
private isRunning;
|
|
7
8
|
private sessionId?;
|
|
8
9
|
private commandHistory;
|
|
9
10
|
private sessionVariablesSchema;
|
|
10
|
-
constructor(initializeCliParser: () => Argv, conversationMemoryConfig?: ConversationMemoryConfig | undefined
|
|
11
|
+
constructor(initializeCliParser: () => Argv, conversationMemoryConfig?: ConversationMemoryConfig | undefined, options?: {
|
|
12
|
+
directResumeSessionId?: string;
|
|
13
|
+
forceNewSession?: boolean;
|
|
14
|
+
} | undefined);
|
|
11
15
|
start(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Handle direct session resume from CLI option
|
|
18
|
+
*/
|
|
19
|
+
private handleDirectSessionResume;
|
|
20
|
+
/**
|
|
21
|
+
* Handle conversation selection logic when no direct resume is specified
|
|
22
|
+
*/
|
|
23
|
+
private handleConversationSelection;
|
|
24
|
+
/**
|
|
25
|
+
* Clean up session resources and connections
|
|
26
|
+
*/
|
|
27
|
+
private cleanup;
|
|
12
28
|
private handleSessionCommands;
|
|
13
|
-
private parseValue;
|
|
14
29
|
private showHelp;
|
|
15
30
|
private showSetHelp;
|
|
16
31
|
/**
|
|
17
|
-
*
|
|
32
|
+
* Get command input with history support using readline
|
|
33
|
+
*/
|
|
34
|
+
private getCommandWithHistory;
|
|
35
|
+
/**
|
|
36
|
+
* Restore a conversation session and set up the global session state
|
|
18
37
|
*/
|
|
19
|
-
private
|
|
38
|
+
private restoreSession;
|
|
20
39
|
/**
|
|
21
|
-
*
|
|
40
|
+
* Create NeuroLink instance and validate conversation in one step
|
|
41
|
+
* Eliminates redundant instance creation and initialization
|
|
22
42
|
*/
|
|
23
|
-
private
|
|
43
|
+
private createAndValidateNeurolinkInstance;
|
|
24
44
|
/**
|
|
25
|
-
*
|
|
45
|
+
* Configure tool execution context for the restored session
|
|
26
46
|
*/
|
|
27
|
-
private
|
|
47
|
+
private configureToolContext;
|
|
48
|
+
/**
|
|
49
|
+
* Verify that tools are available and working in the restored session
|
|
50
|
+
*/
|
|
51
|
+
private verifyToolAvailability;
|
|
52
|
+
/**
|
|
53
|
+
* Restore global session state and session variables
|
|
54
|
+
*/
|
|
55
|
+
private restoreGlobalSessionState;
|
|
28
56
|
}
|