@juspay/neurolink 9.87.4 → 9.88.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/dist/neurolink.js CHANGED
@@ -10504,6 +10504,139 @@ Current user's request: ${currentInput}`;
10504
10504
  this.lastCompactionMessageCount.clear();
10505
10505
  await this.conversationMemory.clearAllSessions();
10506
10506
  }
10507
+ /**
10508
+ * List all conversation sessions with metadata (public API)
10509
+ * @param userId - Optional user ID to filter sessions (required for Redis storage)
10510
+ * @returns Array of session list items with metadata
10511
+ */
10512
+ async listSessions(userId) {
10513
+ // First ensure memory is initialized
10514
+ const initId = `list-sessions-init-${Date.now()}`;
10515
+ await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
10516
+ if (!this.conversationMemory) {
10517
+ throw new Error("Conversation memory is not enabled");
10518
+ }
10519
+ // Check if listSessions is available on the memory manager
10520
+ if (!this.conversationMemory.listSessions) {
10521
+ logger.warn("listSessions not available on current memory manager");
10522
+ return [];
10523
+ }
10524
+ const MEMORY_OPERATION_TIMEOUT = 30000; // 30 seconds
10525
+ try {
10526
+ const sessions = await withTimeout(this.conversationMemory.listSessions(userId), MEMORY_OPERATION_TIMEOUT, new Error("listSessions operation timed out after 30s"));
10527
+ logger.debug("Listed conversation sessions", {
10528
+ userId,
10529
+ sessionCount: sessions.length,
10530
+ });
10531
+ return sessions;
10532
+ }
10533
+ catch (error) {
10534
+ logger.error("Failed to list conversation sessions", {
10535
+ userId,
10536
+ error: error instanceof Error ? error.message : String(error),
10537
+ });
10538
+ return [];
10539
+ }
10540
+ }
10541
+ /**
10542
+ * Export a single session with full history and metadata (public API)
10543
+ * @param sessionId - The session ID to export
10544
+ * @param options - Export options
10545
+ * @returns Session export object with full history
10546
+ */
10547
+ async exportSession(sessionId, options = {}) {
10548
+ // First ensure memory is initialized
10549
+ const initId = `export-session-init-${Date.now()}`;
10550
+ await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
10551
+ if (!this.conversationMemory) {
10552
+ throw new Error("Conversation memory is not enabled");
10553
+ }
10554
+ if (!sessionId || typeof sessionId !== "string") {
10555
+ throw new Error("Session ID must be a non-empty string");
10556
+ }
10557
+ const MEMORY_OPERATION_TIMEOUT = 30000; // 30 seconds
10558
+ try {
10559
+ const messages = await withTimeout(this.conversationMemory.buildContextMessages(sessionId), MEMORY_OPERATION_TIMEOUT, new Error("buildContextMessages operation timed out after 30s"));
10560
+ if (messages.length === 0) {
10561
+ logger.debug("No messages found for session export", { sessionId });
10562
+ return null;
10563
+ }
10564
+ const sessionResult = this.conversationMemory.getSession(sessionId);
10565
+ const session = await withTimeout(sessionResult instanceof Promise
10566
+ ? sessionResult
10567
+ : Promise.resolve(sessionResult), MEMORY_OPERATION_TIMEOUT, new Error("getSession operation timed out after 30s"));
10568
+ const now = new Date().toISOString();
10569
+ const exportData = {
10570
+ sessionId,
10571
+ title: sessionId, // Use sessionId as title if not available
10572
+ userId: session?.userId,
10573
+ createdAt: session?.createdAt
10574
+ ? new Date(session.createdAt).toISOString()
10575
+ : now,
10576
+ updatedAt: session?.lastActivity
10577
+ ? new Date(session.lastActivity).toISOString()
10578
+ : now,
10579
+ messages,
10580
+ };
10581
+ if (options.includeMetadata) {
10582
+ exportData.exportMetadata = {
10583
+ exportedAt: now,
10584
+ exportFormat: options.format || "json",
10585
+ };
10586
+ }
10587
+ logger.debug("Exported conversation session", {
10588
+ sessionId,
10589
+ messageCount: messages.length,
10590
+ });
10591
+ return exportData;
10592
+ }
10593
+ catch (error) {
10594
+ logger.error("Failed to export conversation session", {
10595
+ sessionId,
10596
+ error: error instanceof Error ? error.message : String(error),
10597
+ });
10598
+ return null;
10599
+ }
10600
+ }
10601
+ /**
10602
+ * Export all sessions for a user (public API)
10603
+ * @param userId - Optional user ID (required for Redis storage)
10604
+ * @param options - Export options
10605
+ * @returns Array of session exports
10606
+ */
10607
+ async exportAllSessions(userId, options = {}) {
10608
+ // First ensure memory is initialized
10609
+ const initId = `export-all-init-${Date.now()}`;
10610
+ await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
10611
+ if (!this.conversationMemory) {
10612
+ throw new Error("Conversation memory is not enabled");
10613
+ }
10614
+ const MEMORY_OPERATION_TIMEOUT = 30000; // 30 seconds
10615
+ const EXPORT_SESSION_TIMEOUT = 60000; // 60 seconds for full export
10616
+ try {
10617
+ // Get all session IDs
10618
+ const sessions = await withTimeout(this.listSessions(userId), MEMORY_OPERATION_TIMEOUT, new Error("listSessions operation timed out after 30s"));
10619
+ const exports = [];
10620
+ for (const session of sessions) {
10621
+ const exportData = await withTimeout(this.exportSession(session.id, options), EXPORT_SESSION_TIMEOUT, new Error(`exportSession operation timed out after 60s for session ${session.id}`));
10622
+ if (exportData) {
10623
+ exports.push(exportData);
10624
+ }
10625
+ }
10626
+ logger.debug("Exported all conversation sessions", {
10627
+ userId,
10628
+ sessionCount: exports.length,
10629
+ });
10630
+ return exports;
10631
+ }
10632
+ catch (error) {
10633
+ logger.error("Failed to export all conversation sessions", {
10634
+ userId,
10635
+ error: error instanceof Error ? error.message : String(error),
10636
+ });
10637
+ return [];
10638
+ }
10639
+ }
10507
10640
  /**
10508
10641
  * Store tool executions in conversation memory if enabled and Redis is configured
10509
10642
  * @param sessionId - Session identifier
@@ -451,6 +451,42 @@ export type AgenticLoopReportMetadata = {
451
451
  endDate: string;
452
452
  };
453
453
  };
454
+ /**
455
+ * Session list item for CLI/API listing
456
+ * Extends SessionMetadata with additional display information
457
+ */
458
+ export type SessionListItem = SessionMetadata & {
459
+ /** User identifier associated with this session */
460
+ userId?: string;
461
+ /** Total number of messages in this session */
462
+ messageCount: number;
463
+ /** Human-readable time since last activity (e.g., "2 hours ago") */
464
+ lastActive?: string;
465
+ };
466
+ /**
467
+ * Complete session export format for backup/analytics
468
+ * Contains full session data including all messages
469
+ */
470
+ export type SessionExport = {
471
+ /** Session identifier */
472
+ sessionId: string;
473
+ /** Session title/description */
474
+ title?: string;
475
+ /** User identifier */
476
+ userId?: string;
477
+ /** When session was created (ISO 8601) */
478
+ createdAt: string;
479
+ /** When session was last updated (ISO 8601) */
480
+ updatedAt: string;
481
+ /** Complete message history */
482
+ messages: ChatMessage[];
483
+ /** Export metadata */
484
+ exportMetadata?: {
485
+ exportedAt: string;
486
+ exportFormat: "json" | "csv";
487
+ neuroLinkVersion?: string;
488
+ };
489
+ };
454
490
  /**
455
491
  * Base conversation metadata (shared fields across all conversation types)
456
492
  * Contains essential conversation information without heavy data arrays
@@ -3,7 +3,7 @@
3
3
  * Both ConversationMemoryManager and RedisConversationMemoryManager
4
4
  * should implement this type.
5
5
  */
6
- import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, SessionMemory, StoreConversationTurnOptions } from "./conversation.js";
6
+ import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, SessionListItem, SessionMemory, StoreConversationTurnOptions } from "./conversation.js";
7
7
  /**
8
8
  * Common type for all conversation memory manager implementations.
9
9
  * Provides a consistent API for storing, retrieving, and managing conversation history.
@@ -24,6 +24,10 @@ export type IConversationMemoryManager = {
24
24
  clearAllSessions(): Promise<void> | void;
25
25
  /** Get memory statistics */
26
26
  getStats(): Promise<ConversationMemoryStats> | ConversationMemoryStats;
27
+ /** List all sessions with metadata (optional - for session management) */
28
+ listSessions?(userId?: string): Promise<SessionListItem[]>;
29
+ /** List all sessions with metadata (optional - for session management) */
30
+ listSessions?(userId?: string): Promise<SessionListItem[]>;
27
31
  /** Get raw messages array for a session (no context filtering or summarization) */
28
32
  getSessionMessages(sessionId: string, userId?: string): Promise<ChatMessage[]>;
29
33
  /** Replace the entire messages array for a session */
@@ -1823,23 +1823,47 @@ class ExtensionStrategy {
1823
1823
  };
1824
1824
  }
1825
1825
  getExtension(input) {
1826
- if (this.isURL(input)) {
1827
- const url = new URL(input);
1828
- const match = url.pathname.match(/\.([^.]+)$/);
1829
- return match ? match[1] : null;
1826
+ const normalizedInput = input.trim();
1827
+ let extensionSource = normalizedInput;
1828
+ if (this.isURL(normalizedInput)) {
1829
+ try {
1830
+ const url = new URL(normalizedInput);
1831
+ extensionSource = url.pathname;
1832
+ try {
1833
+ extensionSource = decodeURIComponent(extensionSource);
1834
+ }
1835
+ catch {
1836
+ // Keep the original pathname if the URL contains malformed escapes.
1837
+ }
1838
+ }
1839
+ catch {
1840
+ extensionSource = normalizedInput;
1841
+ }
1830
1842
  }
1831
- const match = input.match(/\.([^.]+)$/);
1832
- return match ? match[1] : null;
1843
+ const match = extensionSource.trim().match(/\.([^.]+)$/);
1844
+ if (!match) {
1845
+ return null;
1846
+ }
1847
+ const ext = match[1].split(/[?#]/)[0].toLowerCase();
1848
+ return /^[a-z0-9]+$/.test(ext) ? ext : null;
1833
1849
  }
1834
1850
  isURL(str) {
1835
- return str.startsWith("http://") || str.startsWith("https://");
1851
+ const normalized = str.trim();
1852
+ return (normalized.startsWith("http://") || normalized.startsWith("https://"));
1836
1853
  }
1837
1854
  detectSource(input) {
1838
- if (input.startsWith("data:")) {
1855
+ const normalized = input.trim();
1856
+ if (normalized.startsWith("data:")) {
1839
1857
  return "datauri";
1840
1858
  }
1841
- if (this.isURL(input)) {
1842
- return "url";
1859
+ if (this.isURL(normalized)) {
1860
+ try {
1861
+ new URL(normalized);
1862
+ return "url";
1863
+ }
1864
+ catch {
1865
+ return "path";
1866
+ }
1843
1867
  }
1844
1868
  return "path";
1845
1869
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.87.4",
3
+ "version": "9.88.0",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (58+ servers), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {
@@ -114,6 +114,7 @@
114
114
  "test:dynamic": "npx tsx test/continuous-test-suite-dynamic.ts",
115
115
  "test:proxy": "npx tsx test/continuous-test-suite-proxy.ts",
116
116
  "test:bugfixes": "npx tsx test/continuous-test-suite-bugfixes.ts",
117
+ "test:file-detector-extension": "npx tsx test/continuous-test-suite-file-detector-extension.ts",
117
118
  "test:json": "npx tsx test/continuous-test-suite-json.ts",
118
119
  "test:json-e2e": "npx tsx test/continuous-test-suite-json-e2e.ts",
119
120
  "test:workflow": "npx tsx test/continuous-test-suite-workflow.ts",
@@ -140,7 +141,7 @@
140
141
  "test:system-messages:vitest": "pnpm exec vitest run test/systemMessages.test.ts",
141
142
  "test:tool-routing-semantic:vitest": "pnpm exec vitest run test/toolRoutingSemantic.test.ts",
142
143
  "test:tool-routing-semantic": "pnpm run test:tool-routing-semantic:vitest && npx tsx test/continuous-test-suite-tool-routing-semantic.ts",
143
- "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:system-messages:vitest && pnpm run test:tool-routing-semantic:vitest && pnpm run test:anthropic-tools-policy && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop",
144
+ "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:system-messages:vitest && pnpm run test:tool-routing-semantic:vitest && pnpm run test:anthropic-tools-policy && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop",
144
145
  "// CI tier — live providers, runs only when API keys are present (test:credentials and test:dynamic make real provider calls when keys are set, so they live here, not in test:unit)": "",
145
146
  "test:live": "pnpm run test:providers && pnpm run test:mcp:http && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:observability && pnpm run test:context && pnpm run test:memory && pnpm run test:tool-reliability && pnpm run test:evaluation && pnpm run test:autoresearch && pnpm run test:credentials && pnpm run test:dynamic",
146
147
  "// CI tier — product output (image/video/TTS/PPT) — costs $$ per run": "",