@juspay/neurolink 7.37.1 → 7.38.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 +6 -0
- package/dist/core/baseProvider.d.ts +4 -0
- package/dist/core/baseProvider.js +40 -0
- package/dist/core/redisConversationMemoryManager.d.ts +98 -15
- package/dist/core/redisConversationMemoryManager.js +665 -203
- package/dist/lib/core/baseProvider.d.ts +4 -0
- package/dist/lib/core/baseProvider.js +40 -0
- package/dist/lib/core/redisConversationMemoryManager.d.ts +98 -15
- package/dist/lib/core/redisConversationMemoryManager.js +665 -203
- package/dist/lib/neurolink.d.ts +33 -1
- package/dist/lib/neurolink.js +64 -0
- package/dist/lib/providers/anthropic.js +8 -0
- package/dist/lib/providers/anthropicBaseProvider.js +8 -0
- package/dist/lib/providers/azureOpenai.js +8 -0
- package/dist/lib/providers/googleAiStudio.js +8 -0
- package/dist/lib/providers/googleVertex.js +10 -0
- package/dist/lib/providers/huggingFace.js +8 -0
- package/dist/lib/providers/litellm.js +8 -0
- package/dist/lib/providers/mistral.js +8 -0
- package/dist/lib/providers/openAI.js +10 -0
- package/dist/lib/providers/openaiCompatible.js +8 -0
- package/dist/lib/types/conversation.d.ts +52 -2
- package/dist/lib/utils/conversationMemory.js +3 -1
- package/dist/lib/utils/messageBuilder.d.ts +10 -2
- package/dist/lib/utils/messageBuilder.js +22 -1
- package/dist/lib/utils/redis.d.ts +10 -6
- package/dist/lib/utils/redis.js +71 -70
- package/dist/neurolink.d.ts +33 -1
- package/dist/neurolink.js +64 -0
- package/dist/providers/anthropic.js +8 -0
- package/dist/providers/anthropicBaseProvider.js +8 -0
- package/dist/providers/azureOpenai.js +8 -0
- package/dist/providers/googleAiStudio.js +8 -0
- package/dist/providers/googleVertex.js +10 -0
- package/dist/providers/huggingFace.js +8 -0
- package/dist/providers/litellm.js +8 -0
- package/dist/providers/mistral.js +8 -0
- package/dist/providers/openAI.js +10 -0
- package/dist/providers/openaiCompatible.js +8 -0
- package/dist/types/conversation.d.ts +52 -2
- package/dist/utils/conversationMemory.js +3 -1
- package/dist/utils/messageBuilder.d.ts +10 -2
- package/dist/utils/messageBuilder.js +22 -1
- package/dist/utils/redis.d.ts +10 -6
- package/dist/utils/redis.js +71 -70
- package/package.json +1 -1
package/dist/lib/utils/redis.js
CHANGED
@@ -53,123 +53,120 @@ export async function createRedisClient(config) {
|
|
53
53
|
/**
|
54
54
|
* Generates a Redis key for session messages
|
55
55
|
*/
|
56
|
-
export function getSessionKey(config, sessionId) {
|
57
|
-
const key = `${config.keyPrefix}${sessionId}`;
|
56
|
+
export function getSessionKey(config, sessionId, userId) {
|
57
|
+
const key = `${config.keyPrefix}${userId || "randomUser"}:${sessionId}`;
|
58
58
|
logger.debug("[redisUtils] Generated session key", {
|
59
59
|
sessionId,
|
60
|
+
userId,
|
60
61
|
keyPrefix: config.keyPrefix,
|
61
62
|
fullKey: key,
|
62
63
|
});
|
63
64
|
return key;
|
64
65
|
}
|
65
66
|
/**
|
66
|
-
*
|
67
|
+
* Generates a Redis key for user sessions mapping
|
67
68
|
*/
|
68
|
-
export function
|
69
|
+
export function getUserSessionsKey(config, userId) {
|
70
|
+
return `${config.userSessionsKeyPrefix}${userId}`;
|
71
|
+
}
|
72
|
+
/**
|
73
|
+
* Serializes conversation object for Redis storage
|
74
|
+
*/
|
75
|
+
export function serializeConversation(conversation) {
|
69
76
|
try {
|
70
|
-
|
71
|
-
messageCount: messages.length,
|
72
|
-
messageTypes: messages.map((m) => m.role),
|
73
|
-
firstMessage: messages.length > 0
|
74
|
-
? {
|
75
|
-
role: messages[0].role,
|
76
|
-
contentLength: messages[0].content.length,
|
77
|
-
contentPreview: messages[0].content.substring(0, 50),
|
78
|
-
}
|
79
|
-
: null,
|
80
|
-
lastMessage: messages.length > 0
|
81
|
-
? {
|
82
|
-
role: messages[messages.length - 1].role,
|
83
|
-
contentLength: messages[messages.length - 1].content.length,
|
84
|
-
contentPreview: messages[messages.length - 1].content.substring(0, 50),
|
85
|
-
}
|
86
|
-
: null,
|
87
|
-
});
|
88
|
-
const serialized = JSON.stringify(messages);
|
89
|
-
logger.debug("[redisUtils] Messages serialized successfully", {
|
90
|
-
serializedLength: serialized.length,
|
91
|
-
messageCount: messages.length,
|
92
|
-
});
|
77
|
+
const serialized = JSON.stringify(conversation);
|
93
78
|
return serialized;
|
94
79
|
}
|
95
80
|
catch (error) {
|
96
|
-
logger.error("[redisUtils] Failed to serialize
|
81
|
+
logger.error("[redisUtils] Failed to serialize conversation", {
|
97
82
|
error: error instanceof Error ? error.message : String(error),
|
98
83
|
stack: error instanceof Error ? error.stack : undefined,
|
99
|
-
|
84
|
+
sessionId: conversation?.sessionId,
|
85
|
+
userId: conversation?.userId,
|
100
86
|
});
|
101
87
|
throw error;
|
102
88
|
}
|
103
89
|
}
|
104
90
|
/**
|
105
|
-
* Deserializes
|
91
|
+
* Deserializes conversation object from Redis storage
|
106
92
|
*/
|
107
|
-
export function
|
93
|
+
export function deserializeConversation(data) {
|
108
94
|
if (!data) {
|
109
|
-
logger.debug("[redisUtils] No data to deserialize, returning
|
110
|
-
return
|
95
|
+
logger.debug("[redisUtils] No conversation data to deserialize, returning null");
|
96
|
+
return null;
|
111
97
|
}
|
112
98
|
try {
|
113
|
-
logger.debug("[redisUtils] Deserializing
|
99
|
+
logger.debug("[redisUtils] Deserializing conversation", {
|
114
100
|
dataLength: data.length,
|
115
101
|
dataPreview: data.substring(0, 100) + (data.length > 100 ? "..." : ""),
|
116
102
|
});
|
117
103
|
// Parse as unknown first, then validate before casting
|
118
104
|
const parsedData = JSON.parse(data);
|
119
|
-
// Check if the parsed data is an
|
120
|
-
if (
|
121
|
-
|
105
|
+
// Check if the parsed data is an object with required properties
|
106
|
+
if (typeof parsedData !== "object" ||
|
107
|
+
parsedData === null ||
|
108
|
+
!("title" in parsedData) ||
|
109
|
+
!("sessionId" in parsedData) ||
|
110
|
+
!("userId" in parsedData) ||
|
111
|
+
!("createdAt" in parsedData) ||
|
112
|
+
!("updatedAt" in parsedData) ||
|
113
|
+
!("messages" in parsedData)) {
|
114
|
+
logger.warn("[redisUtils] Deserialized data is not a valid conversation object", {
|
122
115
|
type: typeof parsedData,
|
116
|
+
hasRequiredFields: parsedData && typeof parsedData === "object"
|
117
|
+
? Object.keys(parsedData).join(", ")
|
118
|
+
: "none",
|
123
119
|
preview: JSON.stringify(parsedData).substring(0, 100),
|
124
120
|
});
|
125
|
-
return
|
121
|
+
return null;
|
122
|
+
}
|
123
|
+
const conversation = parsedData;
|
124
|
+
// Validate messages is an array
|
125
|
+
if (!Array.isArray(conversation.messages)) {
|
126
|
+
logger.warn("[redisUtils] messages is not an array", {
|
127
|
+
type: typeof conversation.messages,
|
128
|
+
});
|
129
|
+
return null;
|
126
130
|
}
|
127
|
-
// Validate each
|
128
|
-
const
|
131
|
+
// Validate each message in the messages array
|
132
|
+
const isValidHistory = conversation.messages.every((m) => typeof m === "object" &&
|
129
133
|
m !== null &&
|
130
134
|
"role" in m &&
|
131
135
|
"content" in m &&
|
132
136
|
typeof m.role === "string" &&
|
133
137
|
typeof m.content === "string" &&
|
134
|
-
(m.role === "user" ||
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
138
|
+
(m.role === "user" ||
|
139
|
+
m.role === "assistant" ||
|
140
|
+
m.role === "system" ||
|
141
|
+
m.role === "tool_call" ||
|
142
|
+
m.role === "tool_result"));
|
143
|
+
if (!isValidHistory) {
|
144
|
+
logger.warn("[redisUtils] Invalid messages structure", {
|
145
|
+
messageCount: conversation.messages.length,
|
146
|
+
firstMessage: conversation.messages.length > 0
|
147
|
+
? JSON.stringify(conversation.messages[0])
|
148
|
+
: null,
|
139
149
|
});
|
140
|
-
return
|
150
|
+
return null;
|
141
151
|
}
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
role: messages[0].role,
|
150
|
-
contentLength: messages[0].content.length,
|
151
|
-
contentPreview: messages[0].content.substring(0, 50),
|
152
|
-
}
|
153
|
-
: null,
|
154
|
-
lastMessage: messages.length > 0
|
155
|
-
? {
|
156
|
-
role: messages[messages.length - 1].role,
|
157
|
-
contentLength: messages[messages.length - 1].content.length,
|
158
|
-
contentPreview: messages[messages.length - 1].content.substring(0, 50),
|
159
|
-
}
|
160
|
-
: null,
|
152
|
+
logger.debug("[redisUtils] Conversation deserialized successfully", {
|
153
|
+
sessionId: conversation.sessionId,
|
154
|
+
userId: conversation.userId,
|
155
|
+
title: conversation.title,
|
156
|
+
messageCount: conversation.messages.length,
|
157
|
+
createdAt: conversation.createdAt,
|
158
|
+
updatedAt: conversation.updatedAt,
|
161
159
|
});
|
162
|
-
|
163
|
-
return messages;
|
160
|
+
return conversation;
|
164
161
|
}
|
165
162
|
catch (error) {
|
166
|
-
logger.error("[redisUtils] Failed to deserialize
|
163
|
+
logger.error("[redisUtils] Failed to deserialize conversation", {
|
167
164
|
error: error instanceof Error ? error.message : String(error),
|
168
165
|
stack: error instanceof Error ? error.stack : undefined,
|
169
166
|
dataLength: data.length,
|
170
167
|
dataPreview: "[REDACTED]", // Prevent exposure of potentially sensitive data
|
171
168
|
});
|
172
|
-
return
|
169
|
+
return null;
|
173
170
|
}
|
174
171
|
}
|
175
172
|
/**
|
@@ -245,12 +242,16 @@ export async function scanKeys(client, pattern, batchSize = 100) {
|
|
245
242
|
* Get normalized Redis configuration with defaults
|
246
243
|
*/
|
247
244
|
export function getNormalizedConfig(config) {
|
245
|
+
const keyPrefix = config.keyPrefix || "neurolink:conversation:";
|
246
|
+
// Intelligent default: derive user sessions prefix from conversation prefix
|
247
|
+
const defaultUserSessionsPrefix = keyPrefix.replace(/conversation:?$/, "user:sessions:");
|
248
248
|
return {
|
249
249
|
host: config.host || "localhost",
|
250
250
|
port: config.port || 6379,
|
251
251
|
password: config.password || "",
|
252
252
|
db: config.db || 0,
|
253
|
-
keyPrefix
|
253
|
+
keyPrefix,
|
254
|
+
userSessionsKeyPrefix: config.userSessionsKeyPrefix || defaultUserSessionsPrefix,
|
254
255
|
ttl: config.ttl || 86400,
|
255
256
|
connectionOptions: {
|
256
257
|
connectTimeout: 30000,
|
package/dist/neurolink.d.ts
CHANGED
@@ -14,6 +14,8 @@ import type { NeuroLinkEvents, TypedEventEmitter, ToolExecutionContext, ToolExec
|
|
14
14
|
import type { JsonObject } from "./types/common.js";
|
15
15
|
import type { BatchOperationResult } from "./types/typeAliases.js";
|
16
16
|
import type { ConversationMemoryConfig, ChatMessage } from "./types/conversation.js";
|
17
|
+
import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
|
18
|
+
import { RedisConversationMemoryManager } from "./core/redisConversationMemoryManager.js";
|
17
19
|
import type { ExternalMCPServerInstance, ExternalMCPOperationResult, ExternalMCPToolInfo } from "./types/externalMcp.js";
|
18
20
|
export interface ProviderStatus {
|
19
21
|
provider: string;
|
@@ -62,7 +64,7 @@ export declare class NeuroLink {
|
|
62
64
|
* @param error - The error if execution failed (optional)
|
63
65
|
*/
|
64
66
|
private emitToolEndEvent;
|
65
|
-
|
67
|
+
conversationMemory?: ConversationMemoryManager | RedisConversationMemoryManager | null;
|
66
68
|
private conversationMemoryNeedsInit;
|
67
69
|
private conversationMemoryConfig?;
|
68
70
|
private enableOrchestration;
|
@@ -848,6 +850,12 @@ export declare class NeuroLink {
|
|
848
850
|
recommendations: string[];
|
849
851
|
}>;
|
850
852
|
}>;
|
853
|
+
/**
|
854
|
+
* Initialize conversation memory if enabled (public method for explicit initialization)
|
855
|
+
* This is useful for testing or when you want to ensure conversation memory is ready
|
856
|
+
* @returns Promise resolving to true if initialization was successful, false otherwise
|
857
|
+
*/
|
858
|
+
ensureConversationMemoryInitialized(): Promise<boolean>;
|
851
859
|
/**
|
852
860
|
* Get conversation memory statistics (public API)
|
853
861
|
*/
|
@@ -866,6 +874,30 @@ export declare class NeuroLink {
|
|
866
874
|
* Clear all conversation history (public API)
|
867
875
|
*/
|
868
876
|
clearAllConversations(): Promise<void>;
|
877
|
+
/**
|
878
|
+
* Store tool executions in conversation memory if enabled and Redis is configured
|
879
|
+
* @param sessionId - Session identifier
|
880
|
+
* @param userId - User identifier (optional)
|
881
|
+
* @param toolCalls - Array of tool calls
|
882
|
+
* @param toolResults - Array of tool results
|
883
|
+
* @returns Promise resolving when storage is complete
|
884
|
+
*/
|
885
|
+
storeToolExecutions(sessionId: string, userId: string | undefined, toolCalls: Array<{
|
886
|
+
toolCallId?: string;
|
887
|
+
toolName?: string;
|
888
|
+
args?: Record<string, unknown>;
|
889
|
+
[key: string]: unknown;
|
890
|
+
}>, toolResults: Array<{
|
891
|
+
toolCallId?: string;
|
892
|
+
result?: unknown;
|
893
|
+
error?: string;
|
894
|
+
[key: string]: unknown;
|
895
|
+
}>): Promise<void>;
|
896
|
+
/**
|
897
|
+
* Check if tool execution storage is available
|
898
|
+
* @returns boolean indicating if Redis storage is configured and available
|
899
|
+
*/
|
900
|
+
isToolExecutionStorageAvailable(): boolean;
|
869
901
|
/**
|
870
902
|
* Add an external MCP server
|
871
903
|
* Automatically discovers and registers tools from the server
|
package/dist/neurolink.js
CHANGED
@@ -3232,6 +3232,24 @@ export class NeuroLink {
|
|
3232
3232
|
// ============================================================================
|
3233
3233
|
// CONVERSATION MEMORY PUBLIC API
|
3234
3234
|
// ============================================================================
|
3235
|
+
/**
|
3236
|
+
* Initialize conversation memory if enabled (public method for explicit initialization)
|
3237
|
+
* This is useful for testing or when you want to ensure conversation memory is ready
|
3238
|
+
* @returns Promise resolving to true if initialization was successful, false otherwise
|
3239
|
+
*/
|
3240
|
+
async ensureConversationMemoryInitialized() {
|
3241
|
+
try {
|
3242
|
+
const initId = `manual-init-${Date.now()}`;
|
3243
|
+
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
3244
|
+
return !!this.conversationMemory;
|
3245
|
+
}
|
3246
|
+
catch (error) {
|
3247
|
+
logger.error("Failed to initialize conversation memory", {
|
3248
|
+
error: error instanceof Error ? error.message : String(error),
|
3249
|
+
});
|
3250
|
+
return false;
|
3251
|
+
}
|
3252
|
+
}
|
3235
3253
|
/**
|
3236
3254
|
* Get conversation memory statistics (public API)
|
3237
3255
|
*/
|
@@ -3302,6 +3320,52 @@ export class NeuroLink {
|
|
3302
3320
|
}
|
3303
3321
|
await this.conversationMemory.clearAllSessions();
|
3304
3322
|
}
|
3323
|
+
/**
|
3324
|
+
* Store tool executions in conversation memory if enabled and Redis is configured
|
3325
|
+
* @param sessionId - Session identifier
|
3326
|
+
* @param userId - User identifier (optional)
|
3327
|
+
* @param toolCalls - Array of tool calls
|
3328
|
+
* @param toolResults - Array of tool results
|
3329
|
+
* @returns Promise resolving when storage is complete
|
3330
|
+
*/
|
3331
|
+
async storeToolExecutions(sessionId, userId, toolCalls, toolResults) {
|
3332
|
+
// Check if tools are not empty
|
3333
|
+
const hasToolData = (toolCalls && toolCalls.length > 0) ||
|
3334
|
+
(toolResults && toolResults.length > 0);
|
3335
|
+
if (!hasToolData) {
|
3336
|
+
logger.debug("Tool execution storage skipped", {
|
3337
|
+
hasToolData,
|
3338
|
+
toolCallsCount: toolCalls?.length || 0,
|
3339
|
+
toolResultsCount: toolResults?.length || 0,
|
3340
|
+
});
|
3341
|
+
return;
|
3342
|
+
}
|
3343
|
+
// Type guard to ensure it's Redis conversation memory manager
|
3344
|
+
const redisMemory = this
|
3345
|
+
.conversationMemory;
|
3346
|
+
try {
|
3347
|
+
await redisMemory.storeToolExecution(sessionId, userId, toolCalls, toolResults);
|
3348
|
+
}
|
3349
|
+
catch (error) {
|
3350
|
+
logger.warn("Failed to store tool executions", {
|
3351
|
+
sessionId,
|
3352
|
+
userId,
|
3353
|
+
error: error instanceof Error ? error.message : String(error),
|
3354
|
+
});
|
3355
|
+
// Don't throw - tool storage failures shouldn't break generation
|
3356
|
+
}
|
3357
|
+
}
|
3358
|
+
/**
|
3359
|
+
* Check if tool execution storage is available
|
3360
|
+
* @returns boolean indicating if Redis storage is configured and available
|
3361
|
+
*/
|
3362
|
+
isToolExecutionStorageAvailable() {
|
3363
|
+
const isRedisStorage = process.env.STORAGE_TYPE === "redis";
|
3364
|
+
const hasRedisConversationMemory = this.conversationMemory &&
|
3365
|
+
this.conversationMemory.constructor.name ===
|
3366
|
+
"RedisConversationMemoryManager";
|
3367
|
+
return !!(isRedisStorage && hasRedisConversationMemory);
|
3368
|
+
}
|
3305
3369
|
// ===== EXTERNAL MCP SERVER METHODS =====
|
3306
3370
|
/**
|
3307
3371
|
* Add an external MCP server
|
@@ -103,6 +103,14 @@ export class AnthropicProvider extends BaseProvider {
|
|
103
103
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
104
104
|
toolChoice: shouldUseTools ? "auto" : "none",
|
105
105
|
abortSignal: timeoutController?.controller.signal,
|
106
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
107
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
108
|
+
logger.warn("[AnthropicProvider] Failed to store tool executions", {
|
109
|
+
provider: this.providerName,
|
110
|
+
error: error instanceof Error ? error.message : String(error),
|
111
|
+
});
|
112
|
+
});
|
113
|
+
},
|
106
114
|
});
|
107
115
|
timeoutController?.cleanup();
|
108
116
|
const transformedStream = this.createTextStream(result);
|
@@ -73,6 +73,14 @@ export class AnthropicProviderV2 extends BaseProvider {
|
|
73
73
|
tools: options.tools,
|
74
74
|
toolChoice: "auto",
|
75
75
|
abortSignal: timeoutController?.controller.signal,
|
76
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
77
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
78
|
+
logger.warn("[AnthropicBaseProvider] Failed to store tool executions", {
|
79
|
+
provider: this.providerName,
|
80
|
+
error: error instanceof Error ? error.message : String(error),
|
81
|
+
});
|
82
|
+
});
|
83
|
+
},
|
76
84
|
});
|
77
85
|
timeoutController?.cleanup();
|
78
86
|
// Transform string stream to content object stream (match Google AI pattern)
|
@@ -122,6 +122,14 @@ export class AzureOpenAIProvider extends BaseProvider {
|
|
122
122
|
: {}),
|
123
123
|
tools,
|
124
124
|
toolChoice: shouldUseTools ? "auto" : "none",
|
125
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
126
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
127
|
+
logger.warn("[AzureOpenaiProvider] Failed to store tool executions", {
|
128
|
+
provider: this.providerName,
|
129
|
+
error: error instanceof Error ? error.message : String(error),
|
130
|
+
});
|
131
|
+
});
|
132
|
+
},
|
125
133
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
126
134
|
});
|
127
135
|
// Transform string stream to content object stream using BaseProvider method
|
@@ -101,6 +101,14 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
101
101
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
102
102
|
toolChoice: shouldUseTools ? "auto" : "none",
|
103
103
|
abortSignal: timeoutController?.controller.signal,
|
104
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
105
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
106
|
+
logger.warn("[GoogleAiStudioProvider] Failed to store tool executions", {
|
107
|
+
provider: this.providerName,
|
108
|
+
error: error instanceof Error ? error.message : String(error),
|
109
|
+
});
|
110
|
+
});
|
111
|
+
},
|
104
112
|
});
|
105
113
|
timeoutController?.cleanup();
|
106
114
|
// Transform string stream to content object stream using BaseProvider method
|
@@ -646,6 +646,16 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
646
646
|
onChunk: () => {
|
647
647
|
chunkCount++;
|
648
648
|
},
|
649
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
650
|
+
logger.info("Tool execution completed", { toolResults, toolCalls });
|
651
|
+
// Handle tool execution storage
|
652
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
653
|
+
logger.warn("[GoogleVertexProvider] Failed to store tool executions", {
|
654
|
+
provider: this.providerName,
|
655
|
+
error: error instanceof Error ? error.message : String(error),
|
656
|
+
});
|
657
|
+
});
|
658
|
+
},
|
649
659
|
};
|
650
660
|
if (analysisSchema) {
|
651
661
|
try {
|
@@ -123,6 +123,14 @@ export class HuggingFaceProvider extends BaseProvider {
|
|
123
123
|
tools: streamOptions.tools, // Tools format conversion handled by prepareStreamOptions
|
124
124
|
toolChoice: streamOptions.toolChoice, // Tool choice handled by prepareStreamOptions
|
125
125
|
abortSignal: timeoutController?.controller.signal,
|
126
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
127
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
128
|
+
logger.warn("[HuggingFaceProvider] Failed to store tool executions", {
|
129
|
+
provider: this.providerName,
|
130
|
+
error: error instanceof Error ? error.message : String(error),
|
131
|
+
});
|
132
|
+
});
|
133
|
+
},
|
126
134
|
});
|
127
135
|
timeoutController?.cleanup();
|
128
136
|
// Transform stream to match StreamResult interface with enhanced tool call parsing
|
@@ -130,6 +130,14 @@ export class LiteLLMProvider extends BaseProvider {
|
|
130
130
|
tools: options.tools,
|
131
131
|
toolChoice: "auto",
|
132
132
|
abortSignal: timeoutController?.controller.signal,
|
133
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
134
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
135
|
+
logger.warn("LiteLLMProvider] Failed to store tool executions", {
|
136
|
+
provider: this.providerName,
|
137
|
+
error: error instanceof Error ? error.message : String(error),
|
138
|
+
});
|
139
|
+
});
|
140
|
+
},
|
133
141
|
});
|
134
142
|
timeoutController?.cleanup();
|
135
143
|
// Transform stream to match StreamResult interface
|
@@ -59,6 +59,14 @@ export class MistralProvider extends BaseProvider {
|
|
59
59
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
60
60
|
toolChoice: shouldUseTools ? "auto" : "none",
|
61
61
|
abortSignal: timeoutController?.controller.signal,
|
62
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
63
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
64
|
+
logger.warn("[MistralProvider] Failed to store tool executions", {
|
65
|
+
provider: this.providerName,
|
66
|
+
error: error instanceof Error ? error.message : String(error),
|
67
|
+
});
|
68
|
+
});
|
69
|
+
},
|
62
70
|
});
|
63
71
|
timeoutController?.cleanup();
|
64
72
|
// Transform string stream to content object stream using BaseProvider method
|
package/dist/providers/openAI.js
CHANGED
@@ -272,6 +272,16 @@ export class OpenAIProvider extends BaseProvider {
|
|
272
272
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
273
273
|
toolChoice: shouldUseTools && Object.keys(tools).length > 0 ? "auto" : "none",
|
274
274
|
abortSignal: timeoutController?.controller.signal,
|
275
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
276
|
+
logger.info("Tool execution completed", { toolResults, toolCalls });
|
277
|
+
// Handle tool execution storage
|
278
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
279
|
+
logger.warn("[OpenAIProvider] Failed to store tool executions", {
|
280
|
+
provider: this.providerName,
|
281
|
+
error: error instanceof Error ? error.message : String(error),
|
282
|
+
});
|
283
|
+
});
|
284
|
+
},
|
275
285
|
});
|
276
286
|
timeoutController?.cleanup();
|
277
287
|
// Debug the actual result structure
|
@@ -166,6 +166,14 @@ export class OpenAICompatibleProvider extends BaseProvider {
|
|
166
166
|
tools: options.tools,
|
167
167
|
toolChoice: "auto",
|
168
168
|
abortSignal: timeoutController?.controller.signal,
|
169
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
170
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
171
|
+
logger.warn("[OpenAiCompatibleProvider] Failed to store tool executions", {
|
172
|
+
provider: this.providerName,
|
173
|
+
error: error instanceof Error ? error.message : String(error),
|
174
|
+
});
|
175
|
+
});
|
176
|
+
},
|
169
177
|
});
|
170
178
|
timeoutController?.cleanup();
|
171
179
|
// Transform stream to match StreamResult interface
|
@@ -32,6 +32,8 @@ export interface SessionMemory {
|
|
32
32
|
sessionId: string;
|
33
33
|
/** User identifier (optional) */
|
34
34
|
userId?: string;
|
35
|
+
/** Auto-generated conversation title (created on first user message) */
|
36
|
+
title?: string;
|
35
37
|
/** Direct message storage - ready for immediate AI consumption */
|
36
38
|
messages: ChatMessage[];
|
37
39
|
/** When this session was created */
|
@@ -61,10 +63,26 @@ export interface ConversationMemoryStats {
|
|
61
63
|
* Chat message format for conversation history
|
62
64
|
*/
|
63
65
|
export interface ChatMessage {
|
64
|
-
/** Role of the message
|
65
|
-
role: "user" | "assistant" | "system";
|
66
|
+
/** Role/type of the message */
|
67
|
+
role: "user" | "assistant" | "system" | "tool_call" | "tool_result";
|
66
68
|
/** Content of the message */
|
67
69
|
content: string;
|
70
|
+
/** Message ID (optional) - for new format */
|
71
|
+
id?: string;
|
72
|
+
/** Timestamp (optional) - for new format */
|
73
|
+
timestamp?: string;
|
74
|
+
/** Tool name (optional) - for tool_call/tool_result messages */
|
75
|
+
tool?: string;
|
76
|
+
/** Tool arguments (optional) - for tool_call messages */
|
77
|
+
args?: Record<string, unknown>;
|
78
|
+
/** Tool result (optional) - for tool_result messages */
|
79
|
+
result?: {
|
80
|
+
success?: boolean;
|
81
|
+
expression?: string;
|
82
|
+
result?: unknown;
|
83
|
+
type?: string;
|
84
|
+
error?: string;
|
85
|
+
};
|
68
86
|
}
|
69
87
|
/**
|
70
88
|
* Content format for multimodal messages (used internally)
|
@@ -129,6 +147,36 @@ export type SessionIdentifier = {
|
|
129
147
|
sessionId: string;
|
130
148
|
userId?: string;
|
131
149
|
};
|
150
|
+
/**
|
151
|
+
* Lightweight session metadata for efficient session listing
|
152
|
+
* Contains only essential information without heavy message arrays
|
153
|
+
*/
|
154
|
+
export interface SessionMetadata {
|
155
|
+
id: string;
|
156
|
+
title: string;
|
157
|
+
createdAt: string;
|
158
|
+
updatedAt: string;
|
159
|
+
}
|
160
|
+
/**
|
161
|
+
* New Redis conversation storage object format
|
162
|
+
* Contains conversation metadata and history in a single object
|
163
|
+
*/
|
164
|
+
export type RedisConversationObject = {
|
165
|
+
/** Unique conversation identifier (UUID v4) */
|
166
|
+
id: string;
|
167
|
+
/** Auto-generated conversation title */
|
168
|
+
title: string;
|
169
|
+
/** Session identifier */
|
170
|
+
sessionId: string;
|
171
|
+
/** User identifier */
|
172
|
+
userId: string;
|
173
|
+
/** When this conversation was first created */
|
174
|
+
createdAt: string;
|
175
|
+
/** When this conversation was last updated */
|
176
|
+
updatedAt: string;
|
177
|
+
/** Array of conversation messages */
|
178
|
+
messages: ChatMessage[];
|
179
|
+
};
|
132
180
|
/**
|
133
181
|
* Redis storage configuration
|
134
182
|
*/
|
@@ -143,6 +191,8 @@ export type RedisStorageConfig = {
|
|
143
191
|
db?: number;
|
144
192
|
/** Key prefix for Redis keys (default: 'neurolink:conversation:') */
|
145
193
|
keyPrefix?: string;
|
194
|
+
/** Key prefix for user sessions mapping (default: derived from keyPrefix) */
|
195
|
+
userSessionsKeyPrefix?: string;
|
146
196
|
/** Time-to-live in seconds (default: 86400, 24 hours) */
|
147
197
|
ttl?: number;
|
148
198
|
/** Additional Redis connection options */
|
@@ -32,8 +32,10 @@ export async function getConversationMessages(conversationMemory, options) {
|
|
32
32
|
return [];
|
33
33
|
}
|
34
34
|
try {
|
35
|
+
// Extract userId from context
|
36
|
+
const userId = options.context?.userId;
|
35
37
|
// Remove duplicate summarization logic - it should be handled in ConversationMemoryManager
|
36
|
-
const messages = await conversationMemory.buildContextMessages(sessionId);
|
38
|
+
const messages = await conversationMemory.buildContextMessages(sessionId, userId);
|
37
39
|
logger.debug("[conversationMemoryUtils] Conversation messages retrieved successfully", {
|
38
40
|
sessionId,
|
39
41
|
messageCount: messages.length,
|
@@ -3,18 +3,26 @@
|
|
3
3
|
* Centralized logic for building message arrays from TextGenerationOptions
|
4
4
|
* Enhanced with multimodal support for images
|
5
5
|
*/
|
6
|
-
import type {
|
6
|
+
import type { MultimodalChatMessage } from "../types/conversation.js";
|
7
7
|
import type { TextGenerationOptions } from "../types/index.js";
|
8
8
|
import type { StreamOptions } from "../types/streamTypes.js";
|
9
9
|
import type { GenerateOptions } from "../types/generateTypes.js";
|
10
|
+
/**
|
11
|
+
* Core message type compatible with AI SDK
|
12
|
+
*/
|
13
|
+
type CoreMessage = {
|
14
|
+
role: "user" | "assistant" | "system";
|
15
|
+
content: string;
|
16
|
+
};
|
10
17
|
/**
|
11
18
|
* Build a properly formatted message array for AI providers
|
12
19
|
* Combines system prompt, conversation history, and current user prompt
|
13
20
|
* Supports both TextGenerationOptions and StreamOptions
|
14
21
|
*/
|
15
|
-
export declare function buildMessagesArray(options: TextGenerationOptions | StreamOptions):
|
22
|
+
export declare function buildMessagesArray(options: TextGenerationOptions | StreamOptions): CoreMessage[];
|
16
23
|
/**
|
17
24
|
* Build multimodal message array with image support
|
18
25
|
* Detects when images are present and routes through provider adapter
|
19
26
|
*/
|
20
27
|
export declare function buildMultimodalMessagesArray(options: GenerateOptions, provider: string, model: string): Promise<MultimodalChatMessage[]>;
|
28
|
+
export {};
|