@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.
- package/CHANGELOG.md +12 -0
- package/dist/cli/commands/config.d.ts +18 -18
- package/dist/cli/factories/commandFactory.d.ts +24 -0
- package/dist/cli/factories/commandFactory.js +297 -245
- package/dist/core/baseProvider.d.ts +44 -3
- package/dist/core/baseProvider.js +729 -352
- package/dist/core/constants.d.ts +2 -30
- package/dist/core/constants.js +15 -43
- package/dist/core/redisConversationMemoryManager.d.ts +98 -15
- package/dist/core/redisConversationMemoryManager.js +665 -203
- package/dist/factories/providerFactory.js +23 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -3
- package/dist/lib/core/baseProvider.d.ts +44 -3
- package/dist/lib/core/baseProvider.js +729 -352
- package/dist/lib/core/constants.d.ts +2 -30
- package/dist/lib/core/constants.js +15 -43
- package/dist/lib/core/redisConversationMemoryManager.d.ts +98 -15
- package/dist/lib/core/redisConversationMemoryManager.js +665 -203
- package/dist/lib/factories/providerFactory.js +23 -6
- package/dist/lib/index.d.ts +3 -2
- package/dist/lib/index.js +4 -3
- package/dist/lib/mcp/externalServerManager.js +2 -2
- package/dist/lib/mcp/registry.js +2 -2
- package/dist/lib/mcp/servers/agent/directToolsServer.js +19 -10
- package/dist/lib/mcp/toolRegistry.js +4 -8
- package/dist/lib/neurolink.d.ts +95 -28
- package/dist/lib/neurolink.js +479 -719
- package/dist/lib/providers/amazonBedrock.js +2 -2
- 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.d.ts +3 -23
- package/dist/lib/providers/googleVertex.js +24 -342
- 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.d.ts +23 -0
- package/dist/lib/providers/openAI.js +323 -6
- package/dist/lib/providers/openaiCompatible.js +8 -0
- package/dist/lib/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/lib/sdk/toolRegistration.js +18 -1
- package/dist/lib/types/common.d.ts +98 -0
- package/dist/lib/types/conversation.d.ts +52 -2
- package/dist/lib/types/streamTypes.d.ts +13 -6
- package/dist/lib/types/typeAliases.d.ts +3 -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/parameterValidation.js +6 -25
- package/dist/lib/utils/promptRedaction.js +4 -4
- package/dist/lib/utils/redis.d.ts +10 -6
- package/dist/lib/utils/redis.js +71 -70
- package/dist/lib/utils/schemaConversion.d.ts +14 -0
- package/dist/lib/utils/schemaConversion.js +140 -0
- package/dist/lib/utils/transformationUtils.js +143 -5
- package/dist/mcp/externalServerManager.js +2 -2
- package/dist/mcp/registry.js +2 -2
- package/dist/mcp/servers/agent/directToolsServer.js +19 -10
- package/dist/mcp/toolRegistry.js +4 -8
- package/dist/neurolink.d.ts +95 -28
- package/dist/neurolink.js +479 -719
- package/dist/providers/amazonBedrock.js +2 -2
- 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.d.ts +3 -23
- package/dist/providers/googleVertex.js +24 -342
- 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.d.ts +23 -0
- package/dist/providers/openAI.js +323 -6
- package/dist/providers/openaiCompatible.js +8 -0
- package/dist/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/sdk/toolRegistration.js +18 -1
- package/dist/types/common.d.ts +98 -0
- package/dist/types/conversation.d.ts +52 -2
- package/dist/types/streamTypes.d.ts +13 -6
- package/dist/types/typeAliases.d.ts +3 -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/parameterValidation.js +6 -25
- package/dist/utils/promptRedaction.js +4 -4
- package/dist/utils/redis.d.ts +10 -6
- package/dist/utils/redis.js +71 -70
- package/dist/utils/schemaConversion.d.ts +14 -0
- package/dist/utils/schemaConversion.js +140 -0
- package/dist/utils/transformationUtils.js +143 -5
- 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 {
|
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 {
|
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 {
|
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 =
|
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 {
|
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 {};
|
@@ -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
|
-
|
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
|
-
//
|
196
|
-
if (mcpTool.execute) {
|
197
|
-
|
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,
|
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,
|
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,
|
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 !==
|
20
|
-
return
|
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 !==
|
39
|
-
return
|
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 {
|
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
|
-
*
|
17
|
+
* Generates a Redis key for user sessions mapping
|
18
18
|
*/
|
19
|
-
export declare function
|
19
|
+
export declare function getUserSessionsKey(config: Required<RedisStorageConfig>, userId: string): string;
|
20
20
|
/**
|
21
|
-
*
|
21
|
+
* Serializes conversation object for Redis storage
|
22
22
|
*/
|
23
|
-
export declare function
|
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
|
*/
|
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,
|
@@ -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
|
+
}
|