@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.
Files changed (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/core/baseProvider.d.ts +4 -0
  3. package/dist/core/baseProvider.js +40 -0
  4. package/dist/core/redisConversationMemoryManager.d.ts +98 -15
  5. package/dist/core/redisConversationMemoryManager.js +665 -203
  6. package/dist/lib/core/baseProvider.d.ts +4 -0
  7. package/dist/lib/core/baseProvider.js +40 -0
  8. package/dist/lib/core/redisConversationMemoryManager.d.ts +98 -15
  9. package/dist/lib/core/redisConversationMemoryManager.js +665 -203
  10. package/dist/lib/neurolink.d.ts +33 -1
  11. package/dist/lib/neurolink.js +64 -0
  12. package/dist/lib/providers/anthropic.js +8 -0
  13. package/dist/lib/providers/anthropicBaseProvider.js +8 -0
  14. package/dist/lib/providers/azureOpenai.js +8 -0
  15. package/dist/lib/providers/googleAiStudio.js +8 -0
  16. package/dist/lib/providers/googleVertex.js +10 -0
  17. package/dist/lib/providers/huggingFace.js +8 -0
  18. package/dist/lib/providers/litellm.js +8 -0
  19. package/dist/lib/providers/mistral.js +8 -0
  20. package/dist/lib/providers/openAI.js +10 -0
  21. package/dist/lib/providers/openaiCompatible.js +8 -0
  22. package/dist/lib/types/conversation.d.ts +52 -2
  23. package/dist/lib/utils/conversationMemory.js +3 -1
  24. package/dist/lib/utils/messageBuilder.d.ts +10 -2
  25. package/dist/lib/utils/messageBuilder.js +22 -1
  26. package/dist/lib/utils/redis.d.ts +10 -6
  27. package/dist/lib/utils/redis.js +71 -70
  28. package/dist/neurolink.d.ts +33 -1
  29. package/dist/neurolink.js +64 -0
  30. package/dist/providers/anthropic.js +8 -0
  31. package/dist/providers/anthropicBaseProvider.js +8 -0
  32. package/dist/providers/azureOpenai.js +8 -0
  33. package/dist/providers/googleAiStudio.js +8 -0
  34. package/dist/providers/googleVertex.js +10 -0
  35. package/dist/providers/huggingFace.js +8 -0
  36. package/dist/providers/litellm.js +8 -0
  37. package/dist/providers/mistral.js +8 -0
  38. package/dist/providers/openAI.js +10 -0
  39. package/dist/providers/openaiCompatible.js +8 -0
  40. package/dist/types/conversation.d.ts +52 -2
  41. package/dist/utils/conversationMemory.js +3 -1
  42. package/dist/utils/messageBuilder.d.ts +10 -2
  43. package/dist/utils/messageBuilder.js +22 -1
  44. package/dist/utils/redis.d.ts +10 -6
  45. package/dist/utils/redis.js +71 -70
  46. package/package.json +1 -1
@@ -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
- * Serializes messages for Redis storage
67
+ * Generates a Redis key for user sessions mapping
67
68
  */
68
- export function serializeMessages(messages) {
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
- logger.debug("[redisUtils] Serializing messages", {
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 messages", {
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
- messageCount: messages.length,
84
+ sessionId: conversation?.sessionId,
85
+ userId: conversation?.userId,
100
86
  });
101
87
  throw error;
102
88
  }
103
89
  }
104
90
  /**
105
- * Deserializes messages from Redis storage
91
+ * Deserializes conversation object from Redis storage
106
92
  */
107
- export function deserializeMessages(data) {
93
+ export function deserializeConversation(data) {
108
94
  if (!data) {
109
- logger.debug("[redisUtils] No data to deserialize, returning empty array");
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 messages", {
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 array
120
- if (!Array.isArray(parsedData)) {
121
- logger.warn("[redisUtils] Deserialized data is not an array", {
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 item in the array has the correct ChatMessage structure
128
- const isValid = parsedData.every((m) => typeof m === "object" &&
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" || m.role === "assistant" || m.role === "system"));
135
- if (!isValid) {
136
- logger.warn("[redisUtils] Deserialized data has unexpected structure", {
137
- isArray: true,
138
- firstItem: parsedData.length > 0 ? JSON.stringify(parsedData[0]) : null,
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
- // Now that we've validated, we can safely cast
143
- const messages = parsedData;
144
- logger.debug("[redisUtils] Messages deserialized successfully", {
145
- messageCount: messages.length,
146
- messageTypes: messages.map((m) => m.role),
147
- firstMessage: messages.length > 0
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
- logger.debug("[deserializeMessages] completed");
163
- return messages;
160
+ return conversation;
164
161
  }
165
162
  catch (error) {
166
- logger.error("[redisUtils] Failed to deserialize messages", {
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: config.keyPrefix || "neurolink:conversation:",
253
+ keyPrefix,
254
+ userSessionsKeyPrefix: config.userSessionsKeyPrefix || defaultUserSessionsPrefix,
254
255
  ttl: config.ttl || 86400,
255
256
  connectionOptions: {
256
257
  connectTimeout: 30000,
@@ -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
- private conversationMemory?;
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
@@ -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 sender */
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 { ChatMessage, MultimodalChatMessage } from "../types/conversation.js";
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): ChatMessage[];
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 {};