@juspay/neurolink 7.37.0 → 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 (93) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/commands/config.d.ts +18 -18
  3. package/dist/cli/factories/commandFactory.d.ts +24 -0
  4. package/dist/cli/factories/commandFactory.js +297 -245
  5. package/dist/core/baseProvider.d.ts +44 -3
  6. package/dist/core/baseProvider.js +729 -352
  7. package/dist/core/constants.d.ts +2 -30
  8. package/dist/core/constants.js +15 -43
  9. package/dist/core/redisConversationMemoryManager.d.ts +98 -15
  10. package/dist/core/redisConversationMemoryManager.js +665 -203
  11. package/dist/factories/providerFactory.js +23 -6
  12. package/dist/index.d.ts +3 -2
  13. package/dist/index.js +4 -3
  14. package/dist/lib/core/baseProvider.d.ts +44 -3
  15. package/dist/lib/core/baseProvider.js +729 -352
  16. package/dist/lib/core/constants.d.ts +2 -30
  17. package/dist/lib/core/constants.js +15 -43
  18. package/dist/lib/core/redisConversationMemoryManager.d.ts +98 -15
  19. package/dist/lib/core/redisConversationMemoryManager.js +665 -203
  20. package/dist/lib/factories/providerFactory.js +23 -6
  21. package/dist/lib/index.d.ts +3 -2
  22. package/dist/lib/index.js +4 -3
  23. package/dist/lib/mcp/externalServerManager.js +2 -2
  24. package/dist/lib/mcp/registry.js +2 -2
  25. package/dist/lib/mcp/servers/agent/directToolsServer.js +19 -10
  26. package/dist/lib/mcp/toolRegistry.js +4 -8
  27. package/dist/lib/neurolink.d.ts +95 -28
  28. package/dist/lib/neurolink.js +479 -719
  29. package/dist/lib/providers/amazonBedrock.js +2 -2
  30. package/dist/lib/providers/anthropic.js +8 -0
  31. package/dist/lib/providers/anthropicBaseProvider.js +8 -0
  32. package/dist/lib/providers/azureOpenai.js +8 -0
  33. package/dist/lib/providers/googleAiStudio.js +8 -0
  34. package/dist/lib/providers/googleVertex.d.ts +3 -23
  35. package/dist/lib/providers/googleVertex.js +24 -342
  36. package/dist/lib/providers/huggingFace.js +8 -0
  37. package/dist/lib/providers/litellm.js +8 -0
  38. package/dist/lib/providers/mistral.js +8 -0
  39. package/dist/lib/providers/openAI.d.ts +23 -0
  40. package/dist/lib/providers/openAI.js +323 -6
  41. package/dist/lib/providers/openaiCompatible.js +8 -0
  42. package/dist/lib/providers/sagemaker/language-model.d.ts +2 -2
  43. package/dist/lib/sdk/toolRegistration.js +18 -1
  44. package/dist/lib/types/common.d.ts +98 -0
  45. package/dist/lib/types/conversation.d.ts +52 -2
  46. package/dist/lib/types/streamTypes.d.ts +13 -6
  47. package/dist/lib/types/typeAliases.d.ts +3 -2
  48. package/dist/lib/utils/conversationMemory.js +3 -1
  49. package/dist/lib/utils/messageBuilder.d.ts +10 -2
  50. package/dist/lib/utils/messageBuilder.js +22 -1
  51. package/dist/lib/utils/parameterValidation.js +6 -25
  52. package/dist/lib/utils/promptRedaction.js +4 -4
  53. package/dist/lib/utils/redis.d.ts +10 -6
  54. package/dist/lib/utils/redis.js +71 -70
  55. package/dist/lib/utils/schemaConversion.d.ts +14 -0
  56. package/dist/lib/utils/schemaConversion.js +140 -0
  57. package/dist/lib/utils/transformationUtils.js +143 -5
  58. package/dist/mcp/externalServerManager.js +2 -2
  59. package/dist/mcp/registry.js +2 -2
  60. package/dist/mcp/servers/agent/directToolsServer.js +19 -10
  61. package/dist/mcp/toolRegistry.js +4 -8
  62. package/dist/neurolink.d.ts +95 -28
  63. package/dist/neurolink.js +479 -719
  64. package/dist/providers/amazonBedrock.js +2 -2
  65. package/dist/providers/anthropic.js +8 -0
  66. package/dist/providers/anthropicBaseProvider.js +8 -0
  67. package/dist/providers/azureOpenai.js +8 -0
  68. package/dist/providers/googleAiStudio.js +8 -0
  69. package/dist/providers/googleVertex.d.ts +3 -23
  70. package/dist/providers/googleVertex.js +24 -342
  71. package/dist/providers/huggingFace.js +8 -0
  72. package/dist/providers/litellm.js +8 -0
  73. package/dist/providers/mistral.js +8 -0
  74. package/dist/providers/openAI.d.ts +23 -0
  75. package/dist/providers/openAI.js +323 -6
  76. package/dist/providers/openaiCompatible.js +8 -0
  77. package/dist/providers/sagemaker/language-model.d.ts +2 -2
  78. package/dist/sdk/toolRegistration.js +18 -1
  79. package/dist/types/common.d.ts +98 -0
  80. package/dist/types/conversation.d.ts +52 -2
  81. package/dist/types/streamTypes.d.ts +13 -6
  82. package/dist/types/typeAliases.d.ts +3 -2
  83. package/dist/utils/conversationMemory.js +3 -1
  84. package/dist/utils/messageBuilder.d.ts +10 -2
  85. package/dist/utils/messageBuilder.js +22 -1
  86. package/dist/utils/parameterValidation.js +6 -25
  87. package/dist/utils/promptRedaction.js +4 -4
  88. package/dist/utils/redis.d.ts +10 -6
  89. package/dist/utils/redis.js +71 -70
  90. package/dist/utils/schemaConversion.d.ts +14 -0
  91. package/dist/utils/schemaConversion.js +140 -0
  92. package/dist/utils/transformationUtils.js +143 -5
  93. package/package.json +3 -2
@@ -1,12 +1,13 @@
1
1
  import type { Tool } from "ai";
2
2
  import type { ValidationSchema, StandardRecord } from "./typeAliases.js";
3
- import type { AIProviderName, ProviderConfig } from "./providers.js";
4
- import type { AnalyticsData, TokenUsage } from "./analytics.js";
5
- import type { EvaluationData } from "./evaluation.js";
6
- import type { UnknownRecord, JsonValue } from "./common.js";
7
- import type { ChatMessage } from "./conversation.js";
3
+ import type { ProviderConfig } from "./providers.js";
8
4
  import type { TextContent, ImageContent } from "./content.js";
9
- import type { MiddlewareFactoryOptions } from "./middlewareTypes.js";
5
+ import type { AIProviderName, AnalyticsData } from "../core/types.js";
6
+ import type { TokenUsage } from "./analytics.js";
7
+ import type { EvaluationData } from "../index.js";
8
+ import type { UnknownRecord, JsonValue, ToolExecutionEvent, ToolExecutionSummary } from "./common.js";
9
+ import type { MiddlewareFactoryOptions } from "../types/middlewareTypes.js";
10
+ import type { ChatMessage } from "./conversation.js";
10
11
  /**
11
12
  * Progress tracking and metadata for streaming operations
12
13
  */
@@ -187,6 +188,9 @@ export type StreamResult = {
187
188
  finishReason?: string;
188
189
  toolCalls?: ToolCall[];
189
190
  toolResults?: ToolResult[];
191
+ toolEvents?: AsyncIterable<ToolExecutionEvent>;
192
+ toolExecutions?: ToolExecutionSummary[];
193
+ toolsUsed?: string[];
190
194
  metadata?: {
191
195
  streamId?: string;
192
196
  startTime?: number;
@@ -194,6 +198,9 @@ export type StreamResult = {
194
198
  estimatedDuration?: number;
195
199
  responseTime?: number;
196
200
  fallback?: boolean;
201
+ totalToolExecutions?: number;
202
+ toolExecutionTime?: number;
203
+ hasToolErrors?: boolean;
197
204
  };
198
205
  analytics?: AnalyticsData | Promise<AnalyticsData>;
199
206
  evaluation?: EvaluationData | Promise<EvaluationData>;
@@ -2,14 +2,15 @@
2
2
  * Comprehensive Type Alias Library
3
3
  * Centralizes commonly used complex types to improve readability and maintainability
4
4
  */
5
- import type { ZodType, ZodTypeDef } from "zod";
5
+ import type { ZodTypeAny } from "zod";
6
6
  import type { Schema } from "ai";
7
7
  import type { JsonValue, JsonObject } from "./common.js";
8
8
  /**
9
9
  * Type alias for complex Zod schema type to improve readability
10
10
  * Used across providers and validation systems
11
+ * Using ZodTypeAny to prevent infinite type recursion in zod-to-json-schema
11
12
  */
12
- export type ZodUnknownSchema = ZodType<unknown, ZodTypeDef, unknown>;
13
+ export type ZodUnknownSchema = ZodTypeAny;
13
14
  /**
14
15
  * Union type for schema validation (Zod or AI SDK schema)
15
16
  * Commonly used in provider interfaces and validation functions
@@ -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 {};
@@ -8,6 +8,21 @@ import { ProviderImageAdapter, MultimodalLogger, } from "../adapters/providerIma
8
8
  import { logger } from "./logger.js";
9
9
  import { request } from "undici";
10
10
  import { readFileSync, existsSync } from "fs";
11
+ /**
12
+ * Convert ChatMessage to CoreMessage for AI SDK compatibility
13
+ */
14
+ function toCoreMessage(message) {
15
+ // Only include messages with roles supported by AI SDK
16
+ if (message.role === "user" ||
17
+ message.role === "assistant" ||
18
+ message.role === "system") {
19
+ return {
20
+ role: message.role,
21
+ content: message.content,
22
+ };
23
+ }
24
+ return null; // Filter out tool_call and tool_result messages
25
+ }
11
26
  /**
12
27
  * Build a properly formatted message array for AI providers
13
28
  * Combines system prompt, conversation history, and current user prompt
@@ -31,8 +46,14 @@ export function buildMessagesArray(options) {
31
46
  });
32
47
  }
33
48
  // Add conversation history if available
49
+ // Convert ChatMessages to CoreMessages and filter out tool messages
34
50
  if (hasConversationHistory && options.conversationMessages) {
35
- messages.push(...options.conversationMessages);
51
+ for (const chatMessage of options.conversationMessages) {
52
+ const coreMessage = toCoreMessage(chatMessage);
53
+ if (coreMessage) {
54
+ messages.push(coreMessage);
55
+ }
56
+ }
36
57
  }
37
58
  // Add current user prompt (required)
38
59
  // Handle both TextGenerationOptions (prompt field) and StreamOptions (input.text field)
@@ -192,28 +192,9 @@ export function validateMCPTool(tool) {
192
192
  if (execError) {
193
193
  errors.push(execError);
194
194
  }
195
- // Additional MCP-specific validation
196
- if (mcpTool.execute) {
197
- try {
198
- // Test execute function with mock data
199
- const mockParams = {};
200
- const mockContext = {
201
- sessionId: "validation-test",
202
- userId: "validation-user",
203
- };
204
- const result = mcpTool.execute(mockParams, mockContext);
205
- const returnsPromise = result && typeof result === "object" && "then" in result;
206
- if (!returnsPromise) {
207
- errors.push(new ValidationError("Execute function must return a Promise", "execute", "NOT_PROMISE", [
208
- "Ensure function returns a Promise<ToolResult>",
209
- "Use async/await pattern",
210
- "Return a result object with success property",
211
- ]));
212
- }
213
- }
214
- catch (error) {
215
- warnings.push(`Execute function validation failed: ${error instanceof Error ? error.message : String(error)}`);
216
- }
195
+ // Simplified validation - just check if execute is a function
196
+ if (mcpTool.execute && typeof mcpTool.execute !== "function") {
197
+ errors.push(new ValidationError("Execute must be a function", "execute", "INVALID_TYPE", ["Provide a function for the execute property"]));
217
198
  }
218
199
  // Check optional properties
219
200
  if (mcpTool.inputSchema && !isNonNullObject(mcpTool.inputSchema)) {
@@ -264,7 +245,7 @@ export function validateTextGenerationOptions(options) {
264
245
  errors.push(tempError);
265
246
  }
266
247
  // Validate maxTokens
267
- const tokensError = validateNumberRange(opts.maxTokens, "maxTokens", 1, 200000);
248
+ const tokensError = validateNumberRange(opts.maxTokens, "maxTokens", 1, 128000);
268
249
  if (tokensError) {
269
250
  errors.push(tokensError);
270
251
  }
@@ -316,7 +297,7 @@ export function validateStreamOptions(options) {
316
297
  errors.push(tempError);
317
298
  }
318
299
  // Validate maxTokens
319
- const tokensError = validateNumberRange(opts.maxTokens, "maxTokens", 1, 200000);
300
+ const tokensError = validateNumberRange(opts.maxTokens, "maxTokens", 1, 128000);
320
301
  if (tokensError) {
321
302
  errors.push(tokensError);
322
303
  }
@@ -349,7 +330,7 @@ export function validateGenerateOptions(options) {
349
330
  if (tempError) {
350
331
  errors.push(tempError);
351
332
  }
352
- const tokensError = validateNumberRange(opts.maxTokens, "maxTokens", 1, 200000);
333
+ const tokensError = validateNumberRange(opts.maxTokens, "maxTokens", 1, 128000);
353
334
  if (tokensError) {
354
335
  errors.push(tokensError);
355
336
  }
@@ -16,8 +16,8 @@ const DEFAULT_REDACTION_OPTIONS = {
16
16
  */
17
17
  export function redactPrompt(prompt, options = {}) {
18
18
  const opts = { ...DEFAULT_REDACTION_OPTIONS, ...options };
19
- if (!prompt || typeof prompt !== 'string') {
20
- return '[INVALID_PROMPT]';
19
+ if (!prompt || typeof prompt !== "string") {
20
+ return "[INVALID_PROMPT]";
21
21
  }
22
22
  const wordCount = prompt.trim().split(/\s+/).length;
23
23
  let redacted = prompt.substring(0, opts.maxLength);
@@ -35,8 +35,8 @@ export function redactPrompt(prompt, options = {}) {
35
35
  * Create a short safe mask for highly sensitive contexts
36
36
  */
37
37
  export function createSafeMask(prompt, _maskLength = 20) {
38
- if (!prompt || typeof prompt !== 'string') {
39
- return '[INVALID_PROMPT]';
38
+ if (!prompt || typeof prompt !== "string") {
39
+ return "[INVALID_PROMPT]";
40
40
  }
41
41
  const wordCount = prompt.trim().split(/\s+/).length;
42
42
  const charCount = prompt.length;
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { createClient } from "redis";
6
6
  type RedisClient = ReturnType<typeof createClient>;
7
- import type { ChatMessage, RedisStorageConfig } from "../types/conversation.js";
7
+ import type { RedisStorageConfig, RedisConversationObject } from "../types/conversation.js";
8
8
  /**
9
9
  * Creates a Redis client with the provided configuration
10
10
  */
@@ -12,15 +12,19 @@ export declare function createRedisClient(config: Required<RedisStorageConfig>):
12
12
  /**
13
13
  * Generates a Redis key for session messages
14
14
  */
15
- export declare function getSessionKey(config: Required<RedisStorageConfig>, sessionId: string): string;
15
+ export declare function getSessionKey(config: Required<RedisStorageConfig>, sessionId: string, userId?: string): string;
16
16
  /**
17
- * Serializes messages for Redis storage
17
+ * Generates a Redis key for user sessions mapping
18
18
  */
19
- export declare function serializeMessages(messages: ChatMessage[]): string;
19
+ export declare function getUserSessionsKey(config: Required<RedisStorageConfig>, userId: string): string;
20
20
  /**
21
- * Deserializes messages from Redis storage
21
+ * Serializes conversation object for Redis storage
22
22
  */
23
- export declare function deserializeMessages(data: string | null): ChatMessage[];
23
+ export declare function serializeConversation(conversation: RedisConversationObject): string;
24
+ /**
25
+ * Deserializes conversation object from Redis storage
26
+ */
27
+ export declare function deserializeConversation(data: string | null): RedisConversationObject | null;
24
28
  /**
25
29
  * Checks if Redis client is healthy
26
30
  */
@@ -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,
@@ -0,0 +1,14 @@
1
+ import type { ZodUnknownSchema } from "../types/tools.js";
2
+ /**
3
+ * Convert Zod schema to JSON Schema format for Claude AI
4
+ */
5
+ export declare function convertZodToJsonSchema(zodSchema: ZodUnknownSchema): object;
6
+ /**
7
+ * Check if a value is a Zod schema
8
+ */
9
+ export declare function isZodSchema(value: unknown): boolean;
10
+ /**
11
+ * Convert JSON Schema to Zod schema format using official json-schema-to-zod library
12
+ * This ensures complete preservation of all schema structure and validation rules
13
+ */
14
+ export declare function convertJsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodUnknownSchema;
@@ -0,0 +1,140 @@
1
+ import { zodToJsonSchema } from "zod-to-json-schema";
2
+ import { jsonSchemaToZod } from "json-schema-to-zod";
3
+ import { z } from "zod";
4
+ import { logger } from "./logger.js";
5
+ /**
6
+ * Convert Zod schema to JSON Schema format for Claude AI
7
+ */
8
+ export function convertZodToJsonSchema(zodSchema) {
9
+ try {
10
+ // Use a type assertion that bypasses the infinite recursion check
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ const jsonSchema = zodToJsonSchema(zodSchema, {
13
+ name: "ToolParameters",
14
+ target: "jsonSchema7",
15
+ errorMessages: true,
16
+ });
17
+ // CRITICAL FIX: Ensure schema always has a proper type field for Google Vertex AI
18
+ if (!jsonSchema.type) {
19
+ // Default to "object" type if not specified, as most tool schemas are objects
20
+ jsonSchema.type = "object";
21
+ // If no properties, ensure it's at least an empty object schema
22
+ if (!jsonSchema.properties) {
23
+ jsonSchema.properties = {};
24
+ }
25
+ logger.info(`[SCHEMA-TYPE-FIX] Added missing type field to JSON Schema`, {
26
+ originalType: undefined,
27
+ fixedType: jsonSchema.type,
28
+ hasProperties: !!jsonSchema.properties,
29
+ addedEmptyProperties: !jsonSchema.properties,
30
+ });
31
+ }
32
+ logger.debug("Converted Zod schema to JSON Schema", {
33
+ hasProperties: !!jsonSchema.properties,
34
+ propertiesCount: Object.keys(jsonSchema.properties || {}).length,
35
+ schemaType: jsonSchema.type,
36
+ hasTypeField: !!jsonSchema.type,
37
+ });
38
+ return jsonSchema;
39
+ }
40
+ catch (error) {
41
+ logger.warn("Failed to convert Zod schema to JSON Schema", {
42
+ error: error instanceof Error ? error.message : String(error),
43
+ });
44
+ // Return a valid empty object schema instead of empty object
45
+ return {
46
+ type: "object",
47
+ properties: {},
48
+ };
49
+ }
50
+ }
51
+ /**
52
+ * Check if a value is a Zod schema
53
+ */
54
+ export function isZodSchema(value) {
55
+ return !!(value &&
56
+ typeof value === "object" &&
57
+ "_def" in value &&
58
+ typeof value.parse === "function");
59
+ }
60
+ /**
61
+ * Convert JSON Schema to Zod schema format using official json-schema-to-zod library
62
+ * This ensures complete preservation of all schema structure and validation rules
63
+ */
64
+ export function convertJsonSchemaToZod(jsonSchema) {
65
+ const startTime = Date.now();
66
+ try {
67
+ // Handle empty or invalid schemas
68
+ if (!jsonSchema || typeof jsonSchema !== "object") {
69
+ logger.debug("🔍 [SCHEMA-CONVERSION] Invalid or empty JSON schema, using fallback");
70
+ return z.object({}).passthrough();
71
+ }
72
+ // Log detailed input schema for debugging
73
+ logger.debug("🔍 [SCHEMA-CONVERSION] ===== STARTING OFFICIAL LIBRARY CONVERSION =====");
74
+ logger.debug("🔍 [SCHEMA-CONVERSION] Input JSON Schema:", {
75
+ type: jsonSchema.type,
76
+ hasProperties: !!jsonSchema.properties,
77
+ propertiesCount: jsonSchema.properties
78
+ ? Object.keys(jsonSchema.properties).length
79
+ : 0,
80
+ requiredCount: Array.isArray(jsonSchema.required)
81
+ ? jsonSchema.required.length
82
+ : 0,
83
+ required: jsonSchema.required,
84
+ sampleProperties: jsonSchema.properties
85
+ ? Object.keys(jsonSchema.properties).slice(0, 5)
86
+ : [],
87
+ });
88
+ // Use official library to convert JSON Schema to Zod code
89
+ const zodCodeResult = jsonSchemaToZod(jsonSchema, {
90
+ module: "esm",
91
+ name: "schema",
92
+ });
93
+ logger.debug("🔍 [SCHEMA-CONVERSION] Generated Zod code:", {
94
+ codeLength: zodCodeResult.length,
95
+ codePreview: zodCodeResult.substring(0, 200) + "...",
96
+ });
97
+ // Extract the actual Zod schema expression from the generated code
98
+ // Generated code looks like: "import { z } from "zod"\n\nexport const schema = z.object({...})\n"
99
+ const schemaMatch = zodCodeResult.match(/export const schema = (z\..+?)(?:\n|$)/s);
100
+ if (!schemaMatch) {
101
+ throw new Error("Could not extract Zod schema from generated code");
102
+ }
103
+ const schemaExpression = schemaMatch[1].trim();
104
+ logger.debug("🔍 [SCHEMA-CONVERSION] Extracted schema expression:", {
105
+ expression: schemaExpression.substring(0, 300) + "...",
106
+ });
107
+ // Use Function constructor instead of eval for better scope control
108
+ const createZodSchema = new Function("z", `return ${schemaExpression}`);
109
+ const zodSchema = createZodSchema(z);
110
+ const conversionTime = Date.now() - startTime;
111
+ logger.debug("🔍 [SCHEMA-CONVERSION] ===== CONVERSION SUCCESSFUL =====", {
112
+ inputType: jsonSchema.type,
113
+ propertiesCount: jsonSchema.properties
114
+ ? Object.keys(jsonSchema.properties).length
115
+ : 0,
116
+ requiredCount: Array.isArray(jsonSchema.required)
117
+ ? jsonSchema.required.length
118
+ : 0,
119
+ conversionSuccess: true,
120
+ conversionTimeMs: conversionTime,
121
+ libraryUsed: "json-schema-to-zod-official",
122
+ zodSchemaType: zodSchema?.constructor?.name || "unknown",
123
+ });
124
+ return zodSchema;
125
+ }
126
+ catch (error) {
127
+ const conversionTime = Date.now() - startTime;
128
+ logger.warn("🚨 [SCHEMA-CONVERSION] Official library conversion failed, using passthrough fallback", {
129
+ error: error instanceof Error ? error.message : String(error),
130
+ errorType: error instanceof Error ? error.constructor.name : typeof error,
131
+ inputSchemaType: jsonSchema?.type,
132
+ inputSchemaKeys: jsonSchema && typeof jsonSchema === "object"
133
+ ? Object.keys(jsonSchema)
134
+ : [],
135
+ conversionTimeMs: conversionTime,
136
+ libraryUsed: "json-schema-to-zod-official-FAILED",
137
+ });
138
+ return z.object({}).passthrough();
139
+ }
140
+ }