@juspay/neurolink 8.19.0 → 8.20.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 (57) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/adapters/providerImageAdapter.d.ts +12 -0
  3. package/dist/adapters/providerImageAdapter.js +30 -3
  4. package/dist/cli/loop/optionsSchema.js +4 -0
  5. package/dist/config/conversationMemory.d.ts +17 -1
  6. package/dist/config/conversationMemory.js +37 -10
  7. package/dist/core/baseProvider.js +23 -13
  8. package/dist/core/conversationMemoryFactory.js +0 -3
  9. package/dist/core/conversationMemoryInitializer.js +1 -9
  10. package/dist/core/conversationMemoryManager.d.ts +31 -8
  11. package/dist/core/conversationMemoryManager.js +174 -80
  12. package/dist/core/modules/GenerationHandler.d.ts +5 -0
  13. package/dist/core/modules/GenerationHandler.js +56 -9
  14. package/dist/core/redisConversationMemoryManager.d.ts +28 -13
  15. package/dist/core/redisConversationMemoryManager.js +211 -121
  16. package/dist/lib/adapters/providerImageAdapter.d.ts +12 -0
  17. package/dist/lib/adapters/providerImageAdapter.js +30 -3
  18. package/dist/lib/config/conversationMemory.d.ts +17 -1
  19. package/dist/lib/config/conversationMemory.js +37 -10
  20. package/dist/lib/core/baseProvider.js +23 -13
  21. package/dist/lib/core/conversationMemoryFactory.js +0 -3
  22. package/dist/lib/core/conversationMemoryInitializer.js +1 -9
  23. package/dist/lib/core/conversationMemoryManager.d.ts +31 -8
  24. package/dist/lib/core/conversationMemoryManager.js +174 -80
  25. package/dist/lib/core/modules/GenerationHandler.d.ts +5 -0
  26. package/dist/lib/core/modules/GenerationHandler.js +56 -9
  27. package/dist/lib/core/redisConversationMemoryManager.d.ts +28 -13
  28. package/dist/lib/core/redisConversationMemoryManager.js +211 -121
  29. package/dist/lib/mcp/servers/agent/directToolsServer.js +5 -0
  30. package/dist/lib/mcp/toolRegistry.js +5 -0
  31. package/dist/lib/neurolink.js +29 -22
  32. package/dist/lib/types/conversation.d.ts +58 -9
  33. package/dist/lib/types/generateTypes.d.ts +1 -0
  34. package/dist/lib/types/sdkTypes.d.ts +1 -1
  35. package/dist/lib/types/streamTypes.d.ts +1 -0
  36. package/dist/lib/utils/conversationMemory.d.ts +43 -1
  37. package/dist/lib/utils/conversationMemory.js +181 -5
  38. package/dist/lib/utils/conversationMemoryUtils.js +16 -1
  39. package/dist/lib/utils/fileDetector.d.ts +25 -0
  40. package/dist/lib/utils/fileDetector.js +433 -10
  41. package/dist/lib/utils/messageBuilder.js +6 -2
  42. package/dist/lib/utils/redis.js +0 -5
  43. package/dist/mcp/servers/agent/directToolsServer.js +5 -0
  44. package/dist/mcp/toolRegistry.js +5 -0
  45. package/dist/neurolink.js +29 -22
  46. package/dist/types/conversation.d.ts +58 -9
  47. package/dist/types/generateTypes.d.ts +1 -0
  48. package/dist/types/sdkTypes.d.ts +1 -1
  49. package/dist/types/streamTypes.d.ts +1 -0
  50. package/dist/utils/conversationMemory.d.ts +43 -1
  51. package/dist/utils/conversationMemory.js +181 -5
  52. package/dist/utils/conversationMemoryUtils.js +16 -1
  53. package/dist/utils/fileDetector.d.ts +25 -0
  54. package/dist/utils/fileDetector.js +433 -10
  55. package/dist/utils/messageBuilder.js +6 -2
  56. package/dist/utils/redis.js +0 -5
  57. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [8.20.0](https://github.com/juspay/neurolink/compare/v8.19.1...v8.20.0) (2025-12-22)
2
+
3
+ ### Features
4
+
5
+ - **(memory):** Implement token based summarization ([ffdc902](https://github.com/juspay/neurolink/commit/ffdc902f534c97a5aff38d7de419021fcabcd791))
6
+
7
+ ## [8.19.1](https://github.com/juspay/neurolink/compare/v8.19.0...v8.19.1) (2025-12-20)
8
+
9
+ ### Bug Fixes
10
+
11
+ - **(files):** comprehensive extension-less file detection with fallback parsing (FD-018) ([7e9dbc7](https://github.com/juspay/neurolink/commit/7e9dbc78df48f6df051c7845824977f360f8feee))
12
+
1
13
  ## [8.19.0](https://github.com/juspay/neurolink/compare/v8.18.0...v8.19.0) (2025-12-18)
2
14
 
3
15
  ### Features
@@ -60,4 +60,16 @@ export declare class ProviderImageAdapter {
60
60
  * Get all vision-capable providers
61
61
  */
62
62
  static getVisionProviders(): string[];
63
+ /**
64
+ * Count total "images" in a message (actual images + PDF pages)
65
+ * PDF pages count toward image limits for providers
66
+ */
67
+ static countImagesInMessage(images: Array<Buffer | string>, pdfPages?: number | null): number;
68
+ /**
69
+ * Extract page count from PDF metadata array
70
+ * Returns total pages across all PDFs
71
+ */
72
+ static countImagesInPages(pdfMetadataArray: Array<{
73
+ pageCount?: number | null;
74
+ }> | undefined): number;
63
75
  }
@@ -416,13 +416,19 @@ export class ProviderImageAdapter {
416
416
  adaptedPayload = this.formatForOpenAI(text, images);
417
417
  break;
418
418
  case "litellm":
419
- adaptedPayload = this.formatForOpenAI(text, images);
419
+ // LiteLLM uses same format as OpenAI but validate with litellm provider name
420
+ this.validateImageCount(images.length, "litellm");
421
+ adaptedPayload = this.formatForOpenAI(text, images, true);
420
422
  break;
421
423
  case "mistral":
422
- adaptedPayload = this.formatForOpenAI(text, images);
424
+ // Mistral uses same format as OpenAI but validate with mistral provider name
425
+ this.validateImageCount(images.length, "mistral");
426
+ adaptedPayload = this.formatForOpenAI(text, images, true);
423
427
  break;
424
428
  case "bedrock":
425
- adaptedPayload = this.formatForAnthropic(text, images);
429
+ // Bedrock uses same format as Anthropic but validate with bedrock provider name
430
+ this.validateImageCount(images.length, "bedrock");
431
+ adaptedPayload = this.formatForAnthropic(text, images, true);
426
432
  break;
427
433
  default:
428
434
  throw new Error(`Vision not supported for provider: ${provider}`);
@@ -666,4 +672,25 @@ export class ProviderImageAdapter {
666
672
  static getVisionProviders() {
667
673
  return Object.keys(VISION_CAPABILITIES);
668
674
  }
675
+ /**
676
+ * Count total "images" in a message (actual images + PDF pages)
677
+ * PDF pages count toward image limits for providers
678
+ */
679
+ static countImagesInMessage(images, pdfPages) {
680
+ const imageCount = images?.length || 0;
681
+ const pageCount = pdfPages ?? 0;
682
+ return imageCount + pageCount;
683
+ }
684
+ /**
685
+ * Extract page count from PDF metadata array
686
+ * Returns total pages across all PDFs
687
+ */
688
+ static countImagesInPages(pdfMetadataArray) {
689
+ if (!pdfMetadataArray || pdfMetadataArray.length === 0) {
690
+ return 0;
691
+ }
692
+ return pdfMetadataArray.reduce((total, pdf) => {
693
+ return total + (pdf.pageCount ?? 0);
694
+ }, 0);
695
+ }
669
696
  }
@@ -61,5 +61,9 @@ export const textGenerationOptionsSchema = {
61
61
  type: "string",
62
62
  description: "Context about tools/MCPs used in the interaction.",
63
63
  },
64
+ enableSummarization: {
65
+ type: "boolean",
66
+ description: "Enable or disable automatic conversation summarization for this request.",
67
+ },
64
68
  };
65
69
  //# sourceMappingURL=optionsSchema.js.map
@@ -20,12 +20,28 @@ export declare const MESSAGES_PER_TURN = 2;
20
20
  * Used to enhance system prompts when conversation history exists
21
21
  */
22
22
  export declare const CONVERSATION_INSTRUCTIONS = "\n\nIMPORTANT: You are continuing an ongoing conversation. The previous messages in this conversation contain important context including:\n- Names, personal information, and preferences shared by the user\n- Projects, tasks, and topics discussed previously \n- Any decisions, agreements, or conclusions reached\n\nAlways reference and build upon this conversation history when relevant. If the user asks about information mentioned earlier in the conversation, refer to those previous messages to provide accurate, contextual responses.";
23
+ /**
24
+ * Percentage of model context window to use for conversation memory threshold
25
+ * Default: 80% of model's context window
26
+ */
27
+ export declare const MEMORY_THRESHOLD_PERCENTAGE = 0.8;
28
+ /**
29
+ * Fallback token threshold if model context unknown
30
+ */
31
+ export declare const DEFAULT_FALLBACK_THRESHOLD = 50000;
32
+ /**
33
+ * Ratio of threshold to keep as recent unsummarized messages
34
+ * When summarization triggers, this percentage of tokens from the end
35
+ * are preserved as detailed messages, while older content gets summarized.
36
+ */
37
+ export declare const RECENT_MESSAGES_RATIO = 0.3;
23
38
  /**
24
39
  * Structured output instructions for JSON/structured output mode
25
40
  * Used to ensure AI providers output only valid JSON without conversational filler
26
41
  * This addresses the issue where models add text like "Excellent!" before JSON output
42
+ * and the case where tools are used but final output must still be pure JSON
27
43
  */
28
- export declare const STRUCTURED_OUTPUT_INSTRUCTIONS = "\n\nSTRUCTURED OUTPUT REQUIREMENT:\nYou MUST respond with ONLY a valid JSON object that matches the provided schema.\n- Do NOT include any text before the JSON (no greetings, acknowledgments, or preamble like \"Excellent!\", \"Sure!\", \"Here is the result:\", etc.)\n- Do NOT include any text after the JSON (no explanations, summaries, or follow-up comments)\n- Do NOT wrap the JSON in markdown code blocks\n- Output ONLY the raw JSON object, starting with { and ending with }\n- Ensure the JSON is valid and parseable";
44
+ export declare const STRUCTURED_OUTPUT_INSTRUCTIONS = "\nOutput ONLY valid JSON. No markdown, text, or decorations\u2014ever.\n\nFORBIDDEN: markdown code blocks, text before/after JSON, explanations, preambles, summaries, conversational text about tools.\n\nREQUIRED: response starts with { and ends with }, valid JSON only, no additional characters.\n\nIF YOU CALLED TOOLS: Incorporate data directly into the JSON structure. Do NOT explain what you did.\n\nWRONG: ```json\n{\"field\": \"value\"}\n```\nWRONG: Based on the data, here's the result: {\"field\": \"value\"}\nCORRECT: {\"field\": \"value\"}\n\nYour entire response = raw JSON object. Nothing else.";
29
45
  /**
30
46
  * Get default configuration values for conversation memory
31
47
  * Reads environment variables when called (not at module load time)
@@ -26,20 +26,43 @@ IMPORTANT: You are continuing an ongoing conversation. The previous messages in
26
26
  - Any decisions, agreements, or conclusions reached
27
27
 
28
28
  Always reference and build upon this conversation history when relevant. If the user asks about information mentioned earlier in the conversation, refer to those previous messages to provide accurate, contextual responses.`;
29
+ /**
30
+ * Percentage of model context window to use for conversation memory threshold
31
+ * Default: 80% of model's context window
32
+ */
33
+ export const MEMORY_THRESHOLD_PERCENTAGE = 0.8;
34
+ /**
35
+ * Fallback token threshold if model context unknown
36
+ */
37
+ export const DEFAULT_FALLBACK_THRESHOLD = 50000;
38
+ /**
39
+ * Ratio of threshold to keep as recent unsummarized messages
40
+ * When summarization triggers, this percentage of tokens from the end
41
+ * are preserved as detailed messages, while older content gets summarized.
42
+ */
43
+ export const RECENT_MESSAGES_RATIO = 0.3;
29
44
  /**
30
45
  * Structured output instructions for JSON/structured output mode
31
46
  * Used to ensure AI providers output only valid JSON without conversational filler
32
47
  * This addresses the issue where models add text like "Excellent!" before JSON output
48
+ * and the case where tools are used but final output must still be pure JSON
33
49
  */
34
50
  export const STRUCTURED_OUTPUT_INSTRUCTIONS = `
51
+ Output ONLY valid JSON. No markdown, text, or decorations—ever.
52
+
53
+ FORBIDDEN: markdown code blocks, text before/after JSON, explanations, preambles, summaries, conversational text about tools.
35
54
 
36
- STRUCTURED OUTPUT REQUIREMENT:
37
- You MUST respond with ONLY a valid JSON object that matches the provided schema.
38
- - Do NOT include any text before the JSON (no greetings, acknowledgments, or preamble like "Excellent!", "Sure!", "Here is the result:", etc.)
39
- - Do NOT include any text after the JSON (no explanations, summaries, or follow-up comments)
40
- - Do NOT wrap the JSON in markdown code blocks
41
- - Output ONLY the raw JSON object, starting with { and ending with }
42
- - Ensure the JSON is valid and parseable`;
55
+ REQUIRED: response starts with { and ends with }, valid JSON only, no additional characters.
56
+
57
+ IF YOU CALLED TOOLS: Incorporate data directly into the JSON structure. Do NOT explain what you did.
58
+
59
+ WRONG: \`\`\`json
60
+ {"field": "value"}
61
+ \`\`\`
62
+ WRONG: Based on the data, here's the result: {"field": "value"}
63
+ CORRECT: {"field": "value"}
64
+
65
+ Your entire response = raw JSON object. Nothing else.`;
43
66
  /**
44
67
  * Get default configuration values for conversation memory
45
68
  * Reads environment variables when called (not at module load time)
@@ -48,12 +71,16 @@ export function getConversationMemoryDefaults() {
48
71
  return {
49
72
  enabled: process.env.NEUROLINK_MEMORY_ENABLED === "true",
50
73
  maxSessions: Number(process.env.NEUROLINK_MEMORY_MAX_SESSIONS) || DEFAULT_MAX_SESSIONS,
74
+ enableSummarization: process.env.NEUROLINK_SUMMARIZATION_ENABLED !== "false",
75
+ tokenThreshold: process.env.NEUROLINK_TOKEN_THRESHOLD
76
+ ? Number(process.env.NEUROLINK_TOKEN_THRESHOLD)
77
+ : undefined,
78
+ summarizationProvider: process.env.NEUROLINK_SUMMARIZATION_PROVIDER || "vertex",
79
+ summarizationModel: process.env.NEUROLINK_SUMMARIZATION_MODEL || "gemini-2.5-flash",
80
+ // Deprecated (for backward compatibility)
51
81
  maxTurnsPerSession: Number(process.env.NEUROLINK_MEMORY_MAX_TURNS_PER_SESSION) ||
52
82
  DEFAULT_MAX_TURNS_PER_SESSION,
53
- enableSummarization: process.env.NEUROLINK_SUMMARIZATION_ENABLED === "true",
54
83
  summarizationThresholdTurns: Number(process.env.NEUROLINK_SUMMARIZATION_THRESHOLD_TURNS) || 20,
55
84
  summarizationTargetTurns: Number(process.env.NEUROLINK_SUMMARIZATION_TARGET_TURNS) || 10,
56
- summarizationProvider: process.env.NEUROLINK_SUMMARIZATION_PROVIDER || "vertex",
57
- summarizationModel: process.env.NEUROLINK_SUMMARIZATION_MODEL || "gemini-2.5-flash",
58
85
  };
59
86
  }
@@ -327,18 +327,21 @@ export class BaseProvider {
327
327
  // This is optimal for simple read-aloud scenarios
328
328
  if (options.tts?.enabled && !options.tts?.useAiResponse) {
329
329
  const textToSynthesize = options.prompt ?? options.input?.text ?? "";
330
- const ttsResult = await TTSProcessor.synthesize(textToSynthesize, options.provider ?? this.providerName, options.tts);
330
+ // Build base result structure - common to both paths
331
331
  const baseResult = {
332
332
  content: textToSynthesize,
333
- audio: ttsResult,
334
333
  provider: options.provider ?? this.providerName,
335
334
  model: this.modelName,
336
- usage: {
337
- input: 0,
338
- output: 0,
339
- total: 0,
340
- },
335
+ usage: { input: 0, output: 0, total: 0 },
341
336
  };
337
+ try {
338
+ const ttsResult = await TTSProcessor.synthesize(textToSynthesize, options.provider ?? this.providerName, options.tts);
339
+ baseResult.audio = ttsResult;
340
+ }
341
+ catch (ttsError) {
342
+ logger.error(`TTS synthesis failed in Mode 1 (direct input synthesis):`, ttsError);
343
+ // baseResult remains without audio - graceful degradation
344
+ }
342
345
  // Call enhanceResult for consistency - enables analytics/evaluation for TTS-only requests
343
346
  return await this.enhanceResult(baseResult, options, startTime);
344
347
  }
@@ -359,12 +362,19 @@ export class BaseProvider {
359
362
  const provider = options.provider ?? this.providerName;
360
363
  // Validate AI response and provider before synthesis
361
364
  if (aiResponse && provider) {
362
- const ttsResult = await TTSProcessor.synthesize(aiResponse, provider, options.tts);
363
- // Add audio to enhanced result (TTSProcessor already includes latency in metadata)
364
- enhancedResult = {
365
- ...enhancedResult,
366
- audio: ttsResult,
367
- };
365
+ try {
366
+ const ttsResult = await TTSProcessor.synthesize(aiResponse, provider, options.tts);
367
+ // Add audio to enhanced result (TTSProcessor already includes latency in metadata)
368
+ enhancedResult = {
369
+ ...enhancedResult,
370
+ audio: ttsResult,
371
+ };
372
+ }
373
+ catch (ttsError) {
374
+ // Log TTS error but continue with text-only result
375
+ logger.error(`TTS synthesis failed in Mode 2 (AI response synthesis):`, ttsError);
376
+ // enhancedResult remains unchanged (no audio field added)
377
+ }
368
378
  }
369
379
  else {
370
380
  logger.warn(`TTS synthesis skipped despite being enabled`, {
@@ -14,10 +14,7 @@ export function createConversationMemoryManager(config, storageType = "memory",
14
14
  config: {
15
15
  enabled: config.enabled,
16
16
  maxSessions: config.maxSessions,
17
- maxTurnsPerSession: config.maxTurnsPerSession,
18
17
  enableSummarization: config.enableSummarization,
19
- summarizationThresholdTurns: config.summarizationThresholdTurns,
20
- summarizationTargetTurns: config.summarizationTargetTurns,
21
18
  summarizationProvider: config.summarizationProvider,
22
19
  summarizationModel: config.summarizationModel,
23
20
  },
@@ -62,15 +62,7 @@ export async function initializeConversationMemory(config) {
62
62
  "RedisConversationMemoryManager",
63
63
  hasConfig: !!redisMemoryManager?.config,
64
64
  });
65
- logger.info("[conversationMemoryInitializer] Redis conversation memory manager created successfully", {
66
- configSource,
67
- host: redisConfig.host || "localhost",
68
- port: redisConfig.port || 6379,
69
- keyPrefix: redisConfig.keyPrefix || "neurolink:conversation:",
70
- maxSessions: memoryConfig.maxSessions,
71
- maxTurnsPerSession: memoryConfig.maxTurnsPerSession,
72
- managerType: redisMemoryManager?.constructor?.name,
73
- });
65
+ logger.info("[conversationMemoryInitializer] Redis conversation memory manager created successfully");
74
66
  // Perform basic validation
75
67
  if (redisMemoryManager?.constructor?.name !==
76
68
  "RedisConversationMemoryManager") {
@@ -2,11 +2,15 @@
2
2
  * Conversation Memory Manager for NeuroLink
3
3
  * Handles in-memory conversation storage, session management, and context injection
4
4
  */
5
- import type { ConversationMemoryConfig, SessionMemory, ConversationMemoryStats, ChatMessage } from "../types/conversation.js";
5
+ import type { ConversationMemoryConfig, SessionMemory, ConversationMemoryStats, ChatMessage, StoreConversationTurnOptions } from "../types/conversation.js";
6
6
  export declare class ConversationMemoryManager {
7
7
  private sessions;
8
8
  config: ConversationMemoryConfig;
9
9
  private isInitialized;
10
+ /**
11
+ * Track sessions currently being summarized to prevent race conditions
12
+ */
13
+ private summarizationInProgress;
10
14
  constructor(config: ConversationMemoryConfig);
11
15
  /**
12
16
  * Initialize the memory manager
@@ -14,19 +18,38 @@ export declare class ConversationMemoryManager {
14
18
  initialize(): Promise<void>;
15
19
  /**
16
20
  * Store a conversation turn for a session
17
- * ULTRA-OPTIMIZED: Direct ChatMessage[] storage with zero conversion overhead
21
+ * TOKEN-BASED: Validates message size and triggers summarization based on tokens
22
+ */
23
+ storeConversationTurn(options: StoreConversationTurnOptions): Promise<void>;
24
+ /**
25
+ * Validate and prepare a message before adding to session
26
+ * Truncates if message exceeds token limit
27
+ */
28
+ private validateAndPrepareMessage;
29
+ /**
30
+ * Check if summarization is needed based on token count
18
31
  */
19
- storeConversationTurn(sessionId: string, userId: string | undefined, userMessage: string, aiResponse: string, _startTimeStamp: Date | undefined): Promise<void>;
32
+ private checkAndSummarize;
20
33
  /**
21
- * Build context messages for AI prompt injection (ULTRA-OPTIMIZED)
22
- * Returns pre-stored message array with zero conversion overhead
34
+ * Estimate total tokens for a list of messages
35
+ */
36
+ private estimateTokens;
37
+ /**
38
+ * Build context messages for AI prompt injection (TOKEN-BASED)
39
+ * Returns messages from pointer onwards (or all if no pointer)
23
40
  * Now consistently async to match Redis implementation
24
41
  */
25
42
  buildContextMessages(sessionId: string): Promise<ChatMessage[]>;
26
43
  getSession(sessionId: string): SessionMemory | undefined;
27
- createSummarySystemMessage(content: string): ChatMessage;
28
- private _summarizeSession;
29
- private _createSummarizationPrompt;
44
+ createSummarySystemMessage(content: string, summarizesFrom?: string, summarizesTo?: string): ChatMessage;
45
+ /**
46
+ * Token-based summarization (pointer-based, non-destructive)
47
+ */
48
+ private summarizeSessionTokenBased;
49
+ /**
50
+ * Find split index to keep recent messages within target token count
51
+ */
52
+ private findSplitIndexByTokens;
30
53
  private ensureInitialized;
31
54
  private createNewSession;
32
55
  private enforceSessionLimit;