@juspay/neurolink 7.48.1 → 7.50.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 +19 -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 +6 -0
- package/dist/cli/factories/commandFactory.js +149 -16
- package/dist/cli/index.js +13 -2
- 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.d.ts +9 -0
- package/dist/core/baseProvider.js +45 -5
- package/dist/core/evaluation.js +5 -2
- package/dist/factories/providerRegistry.js +2 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +11 -10
- package/dist/lib/agent/directTools.d.ts +55 -0
- package/dist/lib/agent/directTools.js +266 -0
- package/dist/lib/core/baseProvider.d.ts +9 -0
- package/dist/lib/core/baseProvider.js +45 -5
- package/dist/lib/core/evaluation.js +5 -2
- package/dist/lib/factories/providerRegistry.js +2 -2
- package/dist/lib/index.d.ts +8 -2
- package/dist/lib/index.js +11 -10
- 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 +36 -7
- package/dist/lib/neurolink.js +141 -0
- package/dist/lib/providers/anthropic.js +47 -3
- package/dist/lib/providers/azureOpenai.js +9 -2
- package/dist/lib/providers/googleAiStudio.js +9 -2
- package/dist/lib/providers/googleVertex.js +12 -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 +47 -3
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +57 -0
- package/dist/lib/services/server/ai/observability/instrumentation.js +170 -0
- package/dist/lib/session/globalSessionState.d.ts +26 -0
- package/dist/lib/session/globalSessionState.js +86 -1
- package/dist/lib/telemetry/index.d.ts +1 -0
- package/dist/lib/telemetry/telemetryService.d.ts +2 -0
- package/dist/lib/telemetry/telemetryService.js +7 -7
- 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 +57 -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/modelTypes.d.ts +6 -6
- package/dist/lib/types/observability.d.ts +49 -0
- package/dist/lib/types/observability.js +6 -0
- 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 +36 -7
- package/dist/neurolink.js +141 -0
- package/dist/providers/anthropic.js +47 -3
- package/dist/providers/azureOpenai.js +9 -2
- package/dist/providers/googleAiStudio.js +9 -2
- package/dist/providers/googleVertex.js +12 -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 +47 -3
- package/dist/services/server/ai/observability/instrumentation.d.ts +57 -0
- package/dist/services/server/ai/observability/instrumentation.js +170 -0
- package/dist/session/globalSessionState.d.ts +26 -0
- package/dist/session/globalSessionState.js +86 -1
- package/dist/telemetry/index.d.ts +1 -0
- package/dist/telemetry/telemetryService.d.ts +2 -0
- package/dist/telemetry/telemetryService.js +7 -7
- 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 +57 -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/observability.d.ts +49 -0
- package/dist/types/observability.js +6 -0
- 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 +18 -16
- 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
|
};
|
|
@@ -951,6 +1044,7 @@ export class CLICommandFactory {
|
|
|
951
1044
|
logger.debug("Mode: DRY-RUN (no actual API calls made)");
|
|
952
1045
|
}
|
|
953
1046
|
if (!globalSession.getCurrentSessionId()) {
|
|
1047
|
+
await this.flushLangfuseTraces();
|
|
954
1048
|
process.exit(0);
|
|
955
1049
|
}
|
|
956
1050
|
}
|
|
@@ -967,12 +1061,21 @@ export class CLICommandFactory {
|
|
|
967
1061
|
toolsEnabled: !options.disableTools,
|
|
968
1062
|
});
|
|
969
1063
|
}
|
|
970
|
-
// Process CLI
|
|
1064
|
+
// Process CLI multimodal inputs
|
|
971
1065
|
const imageBuffers = CLICommandFactory.processCliImages(argv.image);
|
|
1066
|
+
const csvFiles = CLICommandFactory.processCliCSVFiles(argv.csv);
|
|
1067
|
+
const files = CLICommandFactory.processCliFiles(argv.file);
|
|
972
1068
|
const result = await sdk.generate({
|
|
973
|
-
input:
|
|
974
|
-
|
|
975
|
-
|
|
1069
|
+
input: {
|
|
1070
|
+
text: inputText,
|
|
1071
|
+
...(imageBuffers && { images: imageBuffers }),
|
|
1072
|
+
...(csvFiles && { csvFiles }),
|
|
1073
|
+
...(files && { files }),
|
|
1074
|
+
},
|
|
1075
|
+
csvOptions: {
|
|
1076
|
+
maxRows: argv.csvMaxRows,
|
|
1077
|
+
formatStyle: argv.csvFormat,
|
|
1078
|
+
},
|
|
976
1079
|
provider: enhancedOptions.provider,
|
|
977
1080
|
model: enhancedOptions.model,
|
|
978
1081
|
temperature: enhancedOptions.temperature,
|
|
@@ -1018,6 +1121,7 @@ export class CLICommandFactory {
|
|
|
1018
1121
|
}
|
|
1019
1122
|
}
|
|
1020
1123
|
if (!globalSession.getCurrentSessionId()) {
|
|
1124
|
+
await this.flushLangfuseTraces();
|
|
1021
1125
|
process.exit(0);
|
|
1022
1126
|
}
|
|
1023
1127
|
}
|
|
@@ -1126,6 +1230,7 @@ export class CLICommandFactory {
|
|
|
1126
1230
|
logger.debug("Mode: DRY-RUN (no actual API calls made)");
|
|
1127
1231
|
}
|
|
1128
1232
|
if (!globalSession.getCurrentSessionId()) {
|
|
1233
|
+
await this.flushLangfuseTraces();
|
|
1129
1234
|
process.exit(0);
|
|
1130
1235
|
}
|
|
1131
1236
|
}
|
|
@@ -1140,12 +1245,21 @@ export class CLICommandFactory {
|
|
|
1140
1245
|
const context = sessionId
|
|
1141
1246
|
? { ...contextMetadata, sessionId }
|
|
1142
1247
|
: contextMetadata;
|
|
1143
|
-
// Process CLI
|
|
1248
|
+
// Process CLI multimodal inputs
|
|
1144
1249
|
const imageBuffers = CLICommandFactory.processCliImages(argv.image);
|
|
1250
|
+
const csvFiles = CLICommandFactory.processCliCSVFiles(argv.csv);
|
|
1251
|
+
const files = CLICommandFactory.processCliFiles(argv.file);
|
|
1145
1252
|
const stream = await sdk.stream({
|
|
1146
|
-
input:
|
|
1147
|
-
|
|
1148
|
-
|
|
1253
|
+
input: {
|
|
1254
|
+
text: inputText,
|
|
1255
|
+
...(imageBuffers && { images: imageBuffers }),
|
|
1256
|
+
...(csvFiles && { csvFiles }),
|
|
1257
|
+
...(files && { files }),
|
|
1258
|
+
},
|
|
1259
|
+
csvOptions: {
|
|
1260
|
+
maxRows: argv.csvMaxRows,
|
|
1261
|
+
formatStyle: argv.csvFormat,
|
|
1262
|
+
},
|
|
1149
1263
|
provider: enhancedOptions.provider,
|
|
1150
1264
|
model: enhancedOptions.model,
|
|
1151
1265
|
temperature: enhancedOptions.temperature,
|
|
@@ -1179,11 +1293,14 @@ export class CLICommandFactory {
|
|
|
1179
1293
|
let fullContent = "";
|
|
1180
1294
|
let contentReceived = false;
|
|
1181
1295
|
const abortController = new AbortController();
|
|
1182
|
-
// Create timeout promise for stream consumption (30 seconds)
|
|
1296
|
+
// Create timeout promise for stream consumption (default: 30 seconds, respects user-provided timeout)
|
|
1297
|
+
const streamTimeout = options.timeout && typeof options.timeout === "number"
|
|
1298
|
+
? options.timeout * 1000
|
|
1299
|
+
: 30000;
|
|
1183
1300
|
const timeoutPromise = new Promise((_, reject) => {
|
|
1184
1301
|
const timeoutId = setTimeout(() => {
|
|
1185
1302
|
if (!contentReceived) {
|
|
1186
|
-
const timeoutError = new Error(
|
|
1303
|
+
const timeoutError = new Error(`\n❌ Stream timeout - no content received within ${streamTimeout / 1000} seconds\n` +
|
|
1187
1304
|
"This usually indicates authentication or network issues\n\n" +
|
|
1188
1305
|
"🔧 Try these steps:\n" +
|
|
1189
1306
|
"1. Check your provider credentials are configured correctly\n" +
|
|
@@ -1191,7 +1308,7 @@ export class CLICommandFactory {
|
|
|
1191
1308
|
`3. Use debug mode: neurolink stream "test" --provider ${options.provider} --debug`);
|
|
1192
1309
|
reject(timeoutError);
|
|
1193
1310
|
}
|
|
1194
|
-
},
|
|
1311
|
+
}, streamTimeout);
|
|
1195
1312
|
// Clean up timeout when aborted
|
|
1196
1313
|
abortController.signal.addEventListener("abort", () => {
|
|
1197
1314
|
clearTimeout(timeoutId);
|
|
@@ -1375,6 +1492,7 @@ export class CLICommandFactory {
|
|
|
1375
1492
|
const fullContent = await this.executeRealStream(argv, options, inputText, contextMetadata);
|
|
1376
1493
|
await this.handleStreamOutput(options, fullContent);
|
|
1377
1494
|
if (!globalSession.getCurrentSessionId()) {
|
|
1495
|
+
await this.flushLangfuseTraces();
|
|
1378
1496
|
process.exit(0);
|
|
1379
1497
|
}
|
|
1380
1498
|
}
|
|
@@ -1496,6 +1614,7 @@ export class CLICommandFactory {
|
|
|
1496
1614
|
// Handle output with universal formatting
|
|
1497
1615
|
this.handleOutput(results, options);
|
|
1498
1616
|
if (!globalSession.getCurrentSessionId()) {
|
|
1617
|
+
await this.flushLangfuseTraces();
|
|
1499
1618
|
process.exit(0);
|
|
1500
1619
|
}
|
|
1501
1620
|
}
|
|
@@ -1891,4 +2010,18 @@ export class CLICommandFactory {
|
|
|
1891
2010
|
handleError(error, "Completion generation");
|
|
1892
2011
|
}
|
|
1893
2012
|
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Flush Langfuse traces before exit
|
|
2015
|
+
*/
|
|
2016
|
+
static async flushLangfuseTraces() {
|
|
2017
|
+
try {
|
|
2018
|
+
logger.debug("[CLI] Flushing Langfuse traces before exit...");
|
|
2019
|
+
const { flushOpenTelemetry } = await import("../../lib/services/server/ai/observability/instrumentation.js");
|
|
2020
|
+
await flushOpenTelemetry();
|
|
2021
|
+
logger.debug("[CLI] Langfuse traces flushed successfully");
|
|
2022
|
+
}
|
|
2023
|
+
catch (error) {
|
|
2024
|
+
logger.error("[CLI] Error flushing Langfuse traces", { error });
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
1894
2027
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -32,17 +32,28 @@ const cli = initializeCliParser();
|
|
|
32
32
|
try {
|
|
33
33
|
// Parse and execute commands
|
|
34
34
|
await cli.parse();
|
|
35
|
+
await cleanup();
|
|
35
36
|
}
|
|
36
37
|
catch (error) {
|
|
37
38
|
// Global error handler - should not reach here due to fail() handler
|
|
38
39
|
process.stderr.write(chalk.red(`Unexpected CLI _error: ${error.message}\n`));
|
|
40
|
+
await cleanup();
|
|
39
41
|
process.exit(1);
|
|
40
42
|
}
|
|
41
43
|
})();
|
|
42
44
|
// Cleanup on exit
|
|
43
|
-
process.on("SIGINT", () => {
|
|
45
|
+
process.on("SIGINT", async () => {
|
|
46
|
+
await cleanup();
|
|
44
47
|
process.exit(0);
|
|
45
48
|
});
|
|
46
|
-
process.on("SIGTERM", () => {
|
|
49
|
+
process.on("SIGTERM", async () => {
|
|
50
|
+
await cleanup();
|
|
47
51
|
process.exit(0);
|
|
48
52
|
});
|
|
53
|
+
process.on("beforeExit", async () => {
|
|
54
|
+
await cleanup();
|
|
55
|
+
});
|
|
56
|
+
async function cleanup() {
|
|
57
|
+
const { flushOpenTelemetry } = await import("../lib/services/server/ai/observability/instrumentation.js");
|
|
58
|
+
await flushOpenTelemetry();
|
|
59
|
+
}
|
|
@@ -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
|
}
|