@juspay/neurolink 9.6.0 → 9.8.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 (100) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/adapters/video/vertexVideoHandler.js +3 -3
  3. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  4. package/dist/cli/loop/optionsSchema.js +4 -0
  5. package/dist/core/analytics.js +11 -4
  6. package/dist/core/baseProvider.d.ts +6 -0
  7. package/dist/core/baseProvider.js +83 -14
  8. package/dist/core/conversationMemoryManager.d.ts +13 -0
  9. package/dist/core/conversationMemoryManager.js +28 -0
  10. package/dist/core/dynamicModels.js +3 -2
  11. package/dist/core/modules/GenerationHandler.js +2 -0
  12. package/dist/core/redisConversationMemoryManager.d.ts +11 -0
  13. package/dist/core/redisConversationMemoryManager.js +26 -9
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.js +5 -0
  16. package/dist/lib/adapters/video/vertexVideoHandler.js +3 -3
  17. package/dist/lib/core/analytics.js +11 -4
  18. package/dist/lib/core/baseProvider.d.ts +6 -0
  19. package/dist/lib/core/baseProvider.js +83 -14
  20. package/dist/lib/core/conversationMemoryManager.d.ts +13 -0
  21. package/dist/lib/core/conversationMemoryManager.js +28 -0
  22. package/dist/lib/core/dynamicModels.js +3 -2
  23. package/dist/lib/core/modules/GenerationHandler.js +2 -0
  24. package/dist/lib/core/redisConversationMemoryManager.d.ts +11 -0
  25. package/dist/lib/core/redisConversationMemoryManager.js +26 -9
  26. package/dist/lib/index.d.ts +4 -0
  27. package/dist/lib/index.js +5 -0
  28. package/dist/lib/mcp/httpRetryHandler.js +6 -2
  29. package/dist/lib/neurolink.d.ts +5 -0
  30. package/dist/lib/neurolink.js +160 -10
  31. package/dist/lib/processors/base/BaseFileProcessor.js +2 -1
  32. package/dist/lib/processors/errors/errorHelpers.js +12 -4
  33. package/dist/lib/providers/amazonBedrock.js +2 -1
  34. package/dist/lib/providers/anthropic.js +2 -2
  35. package/dist/lib/providers/anthropicBaseProvider.js +10 -4
  36. package/dist/lib/providers/azureOpenai.js +14 -25
  37. package/dist/lib/providers/googleAiStudio.d.ts +0 -34
  38. package/dist/lib/providers/googleAiStudio.js +124 -315
  39. package/dist/lib/providers/googleNativeGemini3.d.ts +119 -0
  40. package/dist/lib/providers/googleNativeGemini3.js +264 -0
  41. package/dist/lib/providers/googleVertex.d.ts +0 -40
  42. package/dist/lib/providers/googleVertex.js +150 -317
  43. package/dist/lib/providers/huggingFace.js +20 -5
  44. package/dist/lib/providers/litellm.js +6 -4
  45. package/dist/lib/providers/mistral.js +3 -2
  46. package/dist/lib/providers/openAI.js +2 -2
  47. package/dist/lib/providers/openRouter.js +8 -7
  48. package/dist/lib/providers/openaiCompatible.js +10 -4
  49. package/dist/lib/rag/resilience/RetryHandler.js +6 -2
  50. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +24 -2
  51. package/dist/lib/services/server/ai/observability/instrumentation.js +12 -1
  52. package/dist/lib/types/generateTypes.d.ts +28 -0
  53. package/dist/lib/types/ragTypes.d.ts +9 -1
  54. package/dist/lib/types/streamTypes.d.ts +13 -0
  55. package/dist/lib/utils/conversationMemory.js +15 -0
  56. package/dist/lib/utils/errorHandling.d.ts +5 -0
  57. package/dist/lib/utils/errorHandling.js +19 -0
  58. package/dist/lib/utils/pricing.d.ts +12 -0
  59. package/dist/lib/utils/pricing.js +134 -0
  60. package/dist/lib/utils/redis.d.ts +17 -0
  61. package/dist/lib/utils/redis.js +105 -0
  62. package/dist/lib/utils/timeout.d.ts +10 -0
  63. package/dist/lib/utils/timeout.js +15 -0
  64. package/dist/mcp/httpRetryHandler.js +6 -2
  65. package/dist/neurolink.d.ts +5 -0
  66. package/dist/neurolink.js +160 -10
  67. package/dist/processors/base/BaseFileProcessor.js +2 -1
  68. package/dist/processors/errors/errorHelpers.js +12 -4
  69. package/dist/providers/amazonBedrock.js +2 -1
  70. package/dist/providers/anthropic.js +2 -2
  71. package/dist/providers/anthropicBaseProvider.js +10 -4
  72. package/dist/providers/azureOpenai.js +14 -25
  73. package/dist/providers/googleAiStudio.d.ts +0 -34
  74. package/dist/providers/googleAiStudio.js +124 -315
  75. package/dist/providers/googleNativeGemini3.d.ts +119 -0
  76. package/dist/providers/googleNativeGemini3.js +263 -0
  77. package/dist/providers/googleVertex.d.ts +0 -40
  78. package/dist/providers/googleVertex.js +150 -317
  79. package/dist/providers/huggingFace.js +20 -5
  80. package/dist/providers/litellm.js +6 -4
  81. package/dist/providers/mistral.js +3 -2
  82. package/dist/providers/openAI.js +2 -2
  83. package/dist/providers/openRouter.js +8 -7
  84. package/dist/providers/openaiCompatible.js +10 -4
  85. package/dist/rag/resilience/RetryHandler.js +6 -2
  86. package/dist/services/server/ai/observability/instrumentation.d.ts +24 -2
  87. package/dist/services/server/ai/observability/instrumentation.js +12 -1
  88. package/dist/types/generateTypes.d.ts +28 -0
  89. package/dist/types/ragTypes.d.ts +9 -1
  90. package/dist/types/streamTypes.d.ts +13 -0
  91. package/dist/utils/conversationMemory.js +15 -0
  92. package/dist/utils/errorHandling.d.ts +5 -0
  93. package/dist/utils/errorHandling.js +19 -0
  94. package/dist/utils/pricing.d.ts +12 -0
  95. package/dist/utils/pricing.js +133 -0
  96. package/dist/utils/redis.d.ts +17 -0
  97. package/dist/utils/redis.js +105 -0
  98. package/dist/utils/timeout.d.ts +10 -0
  99. package/dist/utils/timeout.js +15 -0
  100. package/package.json +26 -25
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [9.8.0](https://github.com/juspay/neurolink/compare/v9.7.0...v9.8.0) (2026-02-17)
2
+
3
+ ### Features
4
+
5
+ - **(scripts):** migrate peripheral JS/CJS/MJS files to TypeScript with tsx runner ([25f17e8](https://github.com/juspay/neurolink/commit/25f17e81c09d6184a4bf2ff15e4427c302fde55e))
6
+
7
+ ## [9.7.0](https://github.com/juspay/neurolink/compare/v9.6.0...v9.7.0) (2026-02-16)
8
+
9
+ ### Features
10
+
11
+ - **(core):** add abort signal composition, tool filtering, provider hardening, and shared utilities ([805e8fe](https://github.com/juspay/neurolink/commit/805e8fe98184d3b57360ce751d61806450fe0ab8))
12
+
1
13
  ## [9.6.0](https://github.com/juspay/neurolink/compare/v9.5.3...v9.6.0) (2026-02-14)
2
14
 
3
15
  ### Features
@@ -12,7 +12,7 @@
12
12
  import { readFile } from "node:fs/promises";
13
13
  import { ErrorCategory, ErrorSeverity } from "../../constants/enums.js";
14
14
  import { TIMEOUTS } from "../../constants/timeouts.js";
15
- import { NeuroLinkError, withTimeout } from "../../utils/errorHandling.js";
15
+ import { isAbortError, NeuroLinkError, withTimeout, } from "../../utils/errorHandling.js";
16
16
  import { logger } from "../../utils/logger.js";
17
17
  // ============================================================================
18
18
  // VIDEO ERROR CODES
@@ -371,7 +371,7 @@ export async function generateVideoWithVertex(image, prompt, options = {}, regio
371
371
  }
372
372
  catch (error) {
373
373
  clearTimeout(requestTimeout);
374
- if (error instanceof Error && error.name === "AbortError") {
374
+ if (isAbortError(error)) {
375
375
  throw new VideoError({
376
376
  code: VIDEO_ERROR_CODES.GENERATION_FAILED,
377
377
  message: "Video generation request timed out after 30 seconds",
@@ -542,7 +542,7 @@ async function makePollRequest(pollEndpoint, operationName, accessToken, timeout
542
542
  }
543
543
  catch (error) {
544
544
  clearTimeout(requestTimeout);
545
- if (error instanceof Error && error.name === "AbortError") {
545
+ if (isAbortError(error)) {
546
546
  throw new VideoError({
547
547
  code: VIDEO_ERROR_CODES.GENERATION_FAILED,
548
548
  message: `Poll request timed out after ${timeoutMs}ms`,
@@ -5,4 +5,4 @@ import type { TextGenerationOptions } from "../../lib/types/generateTypes.js";
5
5
  * This object provides metadata for validation and help text in the CLI loop.
6
6
  * It is derived from the main TextGenerationOptions interface to ensure consistency.
7
7
  */
8
- export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "tts" | "thinkingConfig" | "fileRegistry">, OptionSchema>;
8
+ export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "tts" | "thinkingConfig" | "fileRegistry" | "abortSignal" | "toolFilter" | "excludeTools">, OptionSchema>;
@@ -78,5 +78,9 @@ export const textGenerationOptionsSchema = {
78
78
  description: "Thinking level for Gemini 3 models: minimal, low, medium, high.",
79
79
  allowedValues: ["minimal", "low", "medium", "high"],
80
80
  },
81
+ skipToolPromptInjection: {
82
+ type: "boolean",
83
+ description: "Skip injecting tool descriptions into the system prompt. Useful when tool info is already provided.",
84
+ },
81
85
  };
82
86
  //# sourceMappingURL=optionsSchema.js.map
@@ -7,6 +7,7 @@
7
7
  import { logger } from "../utils/logger.js";
8
8
  import { modelConfig } from "./modelConfiguration.js";
9
9
  import { extractTokenUsage as extractTokenUsageUtil } from "../utils/tokenUtils.js";
10
+ import { calculateCost, hasPricing } from "../utils/pricing.js";
10
11
  /**
11
12
  * Create analytics data structure from AI response
12
13
  */
@@ -60,19 +61,25 @@ function extractTokenUsage(result) {
60
61
  return extractTokenUsageUtil(result.usage);
61
62
  }
62
63
  /**
63
- * Estimate cost based on provider, model, and token usage
64
+ * Estimate cost based on provider, model, and token usage.
65
+ * Uses the per-model pricing table first (which includes cache token rates),
66
+ * then falls back to the provider-level configuration system.
64
67
  */
65
68
  function estimateCost(provider, model, tokens) {
66
69
  try {
67
- // Use the new configuration system instead of hardcoded costs
70
+ // Try the per-model pricing table first (includes cache token rates)
71
+ if (hasPricing(provider, model)) {
72
+ return calculateCost(provider, model, tokens);
73
+ }
74
+ // Fall back to the configuration system for providers/models not in the pricing table
68
75
  const costInfo = modelConfig.getCostInfo(provider.toLowerCase(), model);
69
76
  if (!costInfo) {
70
77
  return undefined;
71
78
  }
72
- // Calculate cost using the configuration system
79
+ // Calculate cost using the configuration system (per-1K-token rates)
73
80
  const inputCost = (tokens.input / 1000) * costInfo.input;
74
81
  const outputCost = (tokens.output / 1000) * costInfo.output;
75
- return Math.round((inputCost + outputCost) * 100000) / 100000; // Round to 5 decimal places
82
+ return Math.round((inputCost + outputCost) * 1_000_000) / 1_000_000; // Round to 6 decimal places
76
83
  }
77
84
  catch (error) {
78
85
  logger.debug("Cost estimation failed", { provider, model, error });
@@ -46,6 +46,12 @@ export declare abstract class BaseProvider implements AIProvider {
46
46
  * Execute fake streaming - extracted method for reusability
47
47
  */
48
48
  private executeFakeStreaming;
49
+ /**
50
+ * Apply per-call tool filtering (whitelist/blacklist) to a tools record.
51
+ * If toolFilter is set, only tools whose names are in the list are kept.
52
+ * If excludeTools is set, matching tools are removed. excludeTools is applied after toolFilter.
53
+ */
54
+ private applyToolFiltering;
49
55
  /**
50
56
  * Prepare generation context including tools and model
51
57
  */
@@ -1,8 +1,9 @@
1
1
  import { directAgentTools } from "../agent/directTools.js";
2
2
  import { IMAGE_GENERATION_MODELS } from "../core/constants.js";
3
3
  import { MiddlewareFactory } from "../middleware/factory.js";
4
+ import { isAbortError } from "../utils/errorHandling.js";
4
5
  import { logger } from "../utils/logger.js";
5
- import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
6
+ import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
6
7
  import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
7
8
  import { getKeyCount, getKeysAsString } from "../utils/transformationUtils.js";
8
9
  import { TTSProcessor } from "../utils/ttsProcessor.js";
@@ -101,16 +102,11 @@ export class BaseProvider {
101
102
  // executeStream() can simply use options.tools (or getAllTools() + options.tools)
102
103
  // and get the complete tool set without needing per-provider merge logic.
103
104
  if (!options.disableTools && this.supportsTools()) {
104
- const baseTools = await this.getAllTools();
105
- const externalTools = (options.tools || {});
106
- const mergedTools = { ...baseTools, ...externalTools };
105
+ const mergedTools = await this.getToolsForStream(options);
107
106
  options = { ...options, tools: mergedTools };
108
- logger.debug(`Central tool merge for stream`, {
109
- provider: this.providerName,
110
- baseToolCount: Object.keys(baseTools).length,
111
- externalToolCount: Object.keys(externalTools).length,
112
- totalToolCount: Object.keys(mergedTools).length,
113
- });
107
+ }
108
+ else {
109
+ options = { ...options, tools: {} };
114
110
  }
115
111
  // CRITICAL FIX: Always prefer real streaming over fake streaming
116
112
  // Try real streaming first, use fake streaming only as fallback
@@ -175,6 +171,13 @@ export class BaseProvider {
175
171
  toolUsageContext: options.toolUsageContext,
176
172
  context: options.context,
177
173
  csvOptions: options.csvOptions,
174
+ // Forward abort, tool filtering, and timeout options to prevent
175
+ // silent bypass when falling back from real streaming to fake streaming
176
+ abortSignal: options.abortSignal,
177
+ toolFilter: options.toolFilter,
178
+ excludeTools: options.excludeTools,
179
+ skipToolPromptInjection: options.skipToolPromptInjection,
180
+ timeout: options.timeout,
178
181
  };
179
182
  logger.debug(`Calling generate for fake streaming`, {
180
183
  provider: this.providerName,
@@ -254,18 +257,62 @@ export class BaseProvider {
254
257
  throw this.handleProviderError(error);
255
258
  }
256
259
  }
260
+ /**
261
+ * Apply per-call tool filtering (whitelist/blacklist) to a tools record.
262
+ * If toolFilter is set, only tools whose names are in the list are kept.
263
+ * If excludeTools is set, matching tools are removed. excludeTools is applied after toolFilter.
264
+ */
265
+ applyToolFiltering(tools, options) {
266
+ if ((!options.toolFilter || options.toolFilter.length === 0) &&
267
+ (!options.excludeTools || options.excludeTools.length === 0)) {
268
+ return tools;
269
+ }
270
+ const beforeCount = Object.keys(tools).length;
271
+ let filtered = { ...tools };
272
+ if (options.toolFilter && options.toolFilter.length > 0) {
273
+ const allowSet = new Set(options.toolFilter);
274
+ const result = {};
275
+ for (const [name, tool] of Object.entries(filtered)) {
276
+ if (allowSet.has(name)) {
277
+ result[name] = tool;
278
+ }
279
+ }
280
+ filtered = result;
281
+ }
282
+ if (options.excludeTools && options.excludeTools.length > 0) {
283
+ const denySet = new Set(options.excludeTools);
284
+ for (const name of Object.keys(filtered)) {
285
+ if (denySet.has(name)) {
286
+ delete filtered[name];
287
+ }
288
+ }
289
+ }
290
+ const afterCount = Object.keys(filtered).length;
291
+ if (beforeCount !== afterCount) {
292
+ logger.debug(`Tool filtering applied`, {
293
+ provider: this.providerName,
294
+ beforeCount,
295
+ afterCount,
296
+ toolFilter: options.toolFilter,
297
+ excludeTools: options.excludeTools,
298
+ });
299
+ }
300
+ return filtered;
301
+ }
257
302
  /**
258
303
  * Prepare generation context including tools and model
259
304
  */
260
305
  async prepareGenerationContext(options) {
261
306
  const shouldUseTools = !options.disableTools && this.supportsTools();
262
307
  const baseTools = shouldUseTools ? await this.getAllTools() : {};
263
- const tools = shouldUseTools
308
+ let tools = shouldUseTools
264
309
  ? {
265
310
  ...baseTools,
266
311
  ...(options.tools || {}),
267
312
  }
268
313
  : {};
314
+ // Apply per-call tool filtering (whitelist/blacklist)
315
+ tools = this.applyToolFiltering(tools, options);
269
316
  logger.debug(`Final tools prepared for AI`, {
270
317
  provider: this.providerName,
271
318
  directTools: getKeyCount(baseTools),
@@ -294,7 +341,9 @@ export class BaseProvider {
294
341
  }
295
342
  const baseTools = await this.getAllTools();
296
343
  const externalTools = (options.tools || {});
297
- const merged = { ...baseTools, ...externalTools };
344
+ let merged = { ...baseTools, ...externalTools };
345
+ // Apply per-call tool filtering (whitelist/blacklist)
346
+ merged = this.applyToolFiltering(merged, options);
298
347
  logger.debug(`Tools prepared for streaming`, {
299
348
  provider: this.providerName,
300
349
  baseToolCount: Object.keys(baseTools).length,
@@ -424,7 +473,19 @@ export class BaseProvider {
424
473
  // ===== Normal AI Generation Flow =====
425
474
  const { tools, model } = await this.prepareGenerationContext(options);
426
475
  const messages = await this.buildMessages(options);
427
- const generateResult = await this.executeGeneration(model, messages, tools, options);
476
+ // Compose timeout signal with user-provided abort signal (mirrors stream path)
477
+ const timeoutController = createTimeoutController(options.timeout, this.providerName, "generate");
478
+ const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
479
+ const composedOptions = composedSignal
480
+ ? { ...options, abortSignal: composedSignal }
481
+ : options;
482
+ let generateResult;
483
+ try {
484
+ generateResult = await this.executeGeneration(model, messages, tools, composedOptions);
485
+ }
486
+ finally {
487
+ timeoutController?.cleanup();
488
+ }
428
489
  this.analyzeAIResponse(generateResult);
429
490
  this.logGenerationComplete(generateResult);
430
491
  const responseTime = Date.now() - startTime;
@@ -471,7 +532,15 @@ export class BaseProvider {
471
532
  return await this.enhanceResult(enhancedResult, options, startTime);
472
533
  }
473
534
  catch (error) {
474
- logger.error(`Generate failed for ${this.providerName}:`, error);
535
+ // Abort errors are expected when a generation is cancelled — log at info, not error
536
+ if (isAbortError(error)) {
537
+ logger.info(`Generate aborted for ${this.providerName}`, {
538
+ error: error instanceof Error ? error.message : String(error),
539
+ });
540
+ }
541
+ else {
542
+ logger.error(`Generate failed for ${this.providerName}:`, error);
543
+ }
475
544
  throw this.handleProviderError(error);
476
545
  }
477
546
  }
@@ -18,6 +18,15 @@ export declare class ConversationMemoryManager implements IConversationMemoryMan
18
18
  * Initialize the memory manager
19
19
  */
20
20
  initialize(): Promise<void>;
21
+ /** Whether this memory manager can persist data (always true for in-memory within process) */
22
+ get canPersist(): boolean;
23
+ /** Whether Redis client is configured (always false for in-memory) */
24
+ get isRedisConfigured(): boolean;
25
+ /** Get health status for monitoring */
26
+ getHealthStatus(): {
27
+ initialized: boolean;
28
+ connected: boolean;
29
+ };
21
30
  /**
22
31
  * Store a conversation turn for a session
23
32
  * TOKEN-BASED: Validates message size and triggers summarization based on tokens
@@ -32,6 +41,10 @@ export declare class ConversationMemoryManager implements IConversationMemoryMan
32
41
  * Check if summarization is needed based on token count
33
42
  */
34
43
  private checkAndSummarize;
44
+ /**
45
+ * Estimate total tokens for a list of messages
46
+ */
47
+ private estimateTokens;
35
48
  /**
36
49
  * Build context messages for AI prompt injection (TOKEN-BASED)
37
50
  * Returns messages from pointer onwards (or all if no pointer)
@@ -42,6 +42,21 @@ export class ConversationMemoryManager {
42
42
  });
43
43
  }
44
44
  }
45
+ /** Whether this memory manager can persist data (always true for in-memory within process) */
46
+ get canPersist() {
47
+ return true;
48
+ }
49
+ /** Whether Redis client is configured (always false for in-memory) */
50
+ get isRedisConfigured() {
51
+ return false;
52
+ }
53
+ /** Get health status for monitoring */
54
+ getHealthStatus() {
55
+ return {
56
+ initialized: this.isInitialized,
57
+ connected: false,
58
+ };
59
+ }
45
60
  /**
46
61
  * Store a conversation turn for a session
47
62
  * TOKEN-BASED: Validates message size and triggers summarization based on tokens
@@ -162,6 +177,19 @@ export class ConversationMemoryManager {
162
177
  this.summarizationInProgress.delete(session.sessionId);
163
178
  }
164
179
  }
180
+ /**
181
+ * Estimate total tokens for a list of messages
182
+ */
183
+ estimateTokens(messages) {
184
+ return messages.reduce((total, msg) => {
185
+ let msgTokens = TokenUtils.estimateTokenCount(msg.content);
186
+ if (msg.events && Array.isArray(msg.events) && msg.events.length > 0) {
187
+ const eventsJson = JSON.stringify(msg.events);
188
+ msgTokens += TokenUtils.estimateTokenCount(eventsJson);
189
+ }
190
+ return total + msgTokens;
191
+ }, 0);
192
+ }
165
193
  /**
166
194
  * Build context messages for AI prompt injection (TOKEN-BASED)
167
195
  * Returns messages from pointer onwards (or all if no pointer)
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { isAbortError } from "../utils/errorHandling.js";
2
3
  import { logger } from "../utils/logger.js";
3
4
  /**
4
5
  * Model configuration schema for validation
@@ -122,7 +123,7 @@ export class DynamicModelProvider {
122
123
  }
123
124
  catch (error) {
124
125
  clearTimeout(timeoutId);
125
- if (error instanceof Error && error.name === "AbortError") {
126
+ if (isAbortError(error)) {
126
127
  throw new Error(`Request timeout after ${timeoutMs}ms`);
127
128
  }
128
129
  throw error;
@@ -177,7 +178,7 @@ export class DynamicModelProvider {
177
178
  }
178
179
  catch (error) {
179
180
  clearTimeout(timeoutId);
180
- if (error instanceof Error && error.name === "AbortError") {
181
+ if (isAbortError(error)) {
181
182
  throw new Error(`Localhost health check timeout - server may not be running`);
182
183
  }
183
184
  // For connection refused, throw a more specific error
@@ -53,6 +53,7 @@ export class GenerationHandler {
53
53
  ...(shouldUseTools && { toolChoice: "auto" }),
54
54
  temperature: options.temperature,
55
55
  maxTokens: options.maxTokens,
56
+ abortSignal: options.abortSignal,
56
57
  ...(useStructuredOutput &&
57
58
  options.schema && {
58
59
  experimental_output: Output.object({ schema: options.schema }),
@@ -272,6 +273,7 @@ export class GenerationHandler {
272
273
  return {
273
274
  content,
274
275
  usage,
276
+ finishReason: generateResult.finishReason,
275
277
  provider: this.providerName,
276
278
  model: this.modelName,
277
279
  toolCalls: generateResult.toolCalls
@@ -34,6 +34,17 @@ export declare class RedisConversationMemoryManager implements IConversationMemo
34
34
  * Initialize the memory manager with Redis connection
35
35
  */
36
36
  initialize(): Promise<void>;
37
+ /** Whether this memory manager can persist data (Redis connected and initialized) */
38
+ get canPersist(): boolean;
39
+ /** Whether Redis client is configured and connected */
40
+ get isRedisConfigured(): boolean;
41
+ /** Get health status for monitoring */
42
+ getHealthStatus(): {
43
+ initialized: boolean;
44
+ connected: boolean;
45
+ host: string;
46
+ keyPrefix: string;
47
+ };
37
48
  /**
38
49
  * Get session by ID, reconstructing a SessionMemory from Redis storage.
39
50
  */
@@ -9,7 +9,7 @@ import { NeuroLink } from "../neurolink.js";
9
9
  import { ConversationMemoryError } from "../types/conversation.js";
10
10
  import { buildContextFromPointer, getEffectiveTokenThreshold, } from "../utils/conversationMemory.js";
11
11
  import { logger } from "../utils/logger.js";
12
- import { createRedisClient, deserializeConversation, getNormalizedConfig, getSessionKey, getUserSessionsKey, scanKeys, serializeConversation, } from "../utils/redis.js";
12
+ import { createRedisClient, deserializeConversation, getNormalizedConfig, getPooledRedisClient, getSessionKey, getUserSessionsKey, releasePooledRedisClient, scanKeys, serializeConversation, } from "../utils/redis.js";
13
13
  /**
14
14
  * Redis-based implementation of the ConversationMemoryManager
15
15
  * Uses the same interface but stores data in Redis
@@ -54,7 +54,7 @@ export class RedisConversationMemoryManager {
54
54
  keyPrefix: this.redisConfig.keyPrefix,
55
55
  ttl: this.redisConfig.ttl,
56
56
  });
57
- this.redisClient = await createRedisClient(this.redisConfig);
57
+ this.redisClient = await getPooledRedisClient(this.redisConfig);
58
58
  this.isInitialized = true;
59
59
  logger.info("RedisConversationMemoryManager initialized", {
60
60
  storage: "redis",
@@ -82,6 +82,23 @@ export class RedisConversationMemoryManager {
82
82
  });
83
83
  }
84
84
  }
85
+ /** Whether this memory manager can persist data (Redis connected and initialized) */
86
+ get canPersist() {
87
+ return (this.isInitialized && this.redisClient !== null && this.redisClient.isOpen);
88
+ }
89
+ /** Whether Redis client is configured and connected */
90
+ get isRedisConfigured() {
91
+ return this.redisClient !== null && this.redisClient.isOpen;
92
+ }
93
+ /** Get health status for monitoring */
94
+ getHealthStatus() {
95
+ return {
96
+ initialized: this.isInitialized,
97
+ connected: this.redisClient?.isOpen ?? false,
98
+ host: this.redisConfig.host,
99
+ keyPrefix: this.redisConfig.keyPrefix,
100
+ };
101
+ }
85
102
  /**
86
103
  * Get session by ID, reconstructing a SessionMemory from Redis storage.
87
104
  */
@@ -671,7 +688,7 @@ export class RedisConversationMemoryManager {
671
688
  * Uses AI to create a concise, descriptive title (5-8 words)
672
689
  */
673
690
  async generateConversationTitle(userMessage) {
674
- logger.debug("[RedisConversationMemoryManager] Generating conversation title", {
691
+ logger.info("[RedisConversationMemoryManager] Generating conversation title", {
675
692
  userMessageLength: userMessage.length,
676
693
  userMessagePreview: userMessage.substring(0, 100),
677
694
  });
@@ -680,12 +697,12 @@ export class RedisConversationMemoryManager {
680
697
  const titleGenerator = new NeuroLink({
681
698
  conversationMemory: { enabled: false },
682
699
  });
683
- const titlePrompt = `Generate a clear, concise, and descriptive title (5–8 words maximum) for a conversation based on the following user message.
684
- The title must meaningfully reflect the topic or intent of the message.
685
- Do not output anything unrelated, vague, or generic.
700
+ const titlePrompt = `Generate a clear, concise, and descriptive title (5–8 words maximum) for a conversation based on the following user message.
701
+ The title must meaningfully reflect the topic or intent of the message.
702
+ Do not output anything unrelated, vague, or generic.
686
703
  Do not say you cannot create a title. Always return a valid title.
687
704
 
688
- User message: "${userMessage}`;
705
+ User message: "${userMessage}"`;
689
706
  const result = await titleGenerator.generate({
690
707
  input: { text: titlePrompt },
691
708
  provider: this.config.summarizationProvider || "vertex",
@@ -704,7 +721,7 @@ User message: "${userMessage}`;
704
721
  if (title.length < 3) {
705
722
  title = "New Conversation";
706
723
  }
707
- logger.debug("[RedisConversationMemoryManager] Generated conversation title", {
724
+ logger.info("[RedisConversationMemoryManager] Generated conversation title", {
708
725
  originalLength: result.content?.length || 0,
709
726
  cleanedTitle: title,
710
727
  titleLength: title.length,
@@ -744,7 +761,7 @@ User message: "${userMessage}`;
744
761
  */
745
762
  async close() {
746
763
  if (this.redisClient) {
747
- await this.redisClient.quit();
764
+ await releasePooledRedisClient(this.redisConfig);
748
765
  this.redisClient = null;
749
766
  this.isInitialized = false;
750
767
  logger.info("Redis connection closed");
package/dist/index.d.ts CHANGED
@@ -40,12 +40,15 @@ export { validateTool } from "./sdk/toolRegistration.js";
40
40
  export * from "./types/index.js";
41
41
  export type { DynamicModelConfig, ModelRegistry } from "./types/modelTypes.js";
42
42
  export { getAvailableProviders, getBestProvider, isValidProvider, } from "./utils/providerUtils.js";
43
+ export { calculateCost, hasPricing } from "./utils/pricing.js";
44
+ export { isAbortError } from "./utils/errorHandling.js";
43
45
  import { NeuroLink } from "./neurolink.js";
44
46
  export { NeuroLink };
45
47
  export type { MCPServerInfo } from "./types/mcpTypes.js";
46
48
  export type { LangfuseConfig, LangfuseSpanAttributes, ObservabilityConfig, OpenTelemetryConfig, TraceNameFormat, } from "./types/observability.js";
47
49
  export { buildObservabilityConfigFromEnv } from "./utils/observabilityHelpers.js";
48
50
  import { createContextEnricher, flushOpenTelemetry, getLangfuseContext, getLangfuseHealthStatus, getLangfuseSpanProcessor, getSpanProcessors, getTracer, getTracerProvider, initializeOpenTelemetry, isOpenTelemetryInitialized, isUsingExternalTracerProvider, setLangfuseContext, shutdownOpenTelemetry } from "./services/server/ai/observability/instrumentation.js";
51
+ export type { LangfuseContext } from "./services/server/ai/observability/instrumentation.js";
49
52
  export { initializeOpenTelemetry, shutdownOpenTelemetry, flushOpenTelemetry, getLangfuseHealthStatus, setLangfuseContext, getLangfuseSpanProcessor, getTracerProvider, isOpenTelemetryInitialized, getSpanProcessors, createContextEnricher, isUsingExternalTracerProvider, getLangfuseContext, getTracer, };
50
53
  export { clearAnalyticsMetrics, createAnalyticsMiddleware, getAnalyticsMetrics, } from "./middleware/builtin/analytics.js";
51
54
  export { MiddlewareFactory } from "./middleware/factory.js";
@@ -191,6 +194,7 @@ export type { AuthorizationUrlResult, DiscoveredMcp, HTTPRetryConfig, MCPOAuthCo
191
194
  export type { ExecutionContext, ToolExecutionResult, ToolInfo, } from "./types/tools.js";
192
195
  export type { LogLevel } from "./types/utilities.js";
193
196
  export { logger } from "./utils/logger.js";
197
+ export { getPoolStats } from "./utils/redis.js";
194
198
  export declare function initializeTelemetry(): Promise<boolean>;
195
199
  export declare function getTelemetryStatus(): Promise<{
196
200
  enabled: boolean;
package/dist/index.js CHANGED
@@ -45,6 +45,10 @@ export { validateTool } from "./sdk/toolRegistration.js";
45
45
  export * from "./types/index.js";
46
46
  // Utility exports
47
47
  export { getAvailableProviders, getBestProvider, isValidProvider, } from "./utils/providerUtils.js";
48
+ // Pricing utilities
49
+ export { calculateCost, hasPricing } from "./utils/pricing.js";
50
+ // Error utilities
51
+ export { isAbortError } from "./utils/errorHandling.js";
48
52
  // Main NeuroLink wrapper class and diagnostic types
49
53
  import { NeuroLink } from "./neurolink.js";
50
54
  export { NeuroLink };
@@ -222,6 +226,7 @@ initializeMCPEcosystem, isRetryableHTTPError, isRetryableStatusCode, isTokenExpi
222
226
  // Circuit Breaker
223
227
  MCPCircuitBreaker, mcpLogger, NeuroLinkOAuthProvider, RateLimiterManager, validateServerTools, validateTool as validateMCPTool, withHTTPRetry, } from "./mcp/index.js";
224
228
  export { logger } from "./utils/logger.js";
229
+ export { getPoolStats } from "./utils/redis.js";
225
230
  // ============================================================================
226
231
  // REAL-TIME SERVICES & TELEMETRY - Enterprise Platform Features
227
232
  // ============================================================================
@@ -12,7 +12,7 @@
12
12
  import { readFile } from "node:fs/promises";
13
13
  import { ErrorCategory, ErrorSeverity } from "../../constants/enums.js";
14
14
  import { TIMEOUTS } from "../../constants/timeouts.js";
15
- import { NeuroLinkError, withTimeout } from "../../utils/errorHandling.js";
15
+ import { isAbortError, NeuroLinkError, withTimeout, } from "../../utils/errorHandling.js";
16
16
  import { logger } from "../../utils/logger.js";
17
17
  // ============================================================================
18
18
  // VIDEO ERROR CODES
@@ -371,7 +371,7 @@ export async function generateVideoWithVertex(image, prompt, options = {}, regio
371
371
  }
372
372
  catch (error) {
373
373
  clearTimeout(requestTimeout);
374
- if (error instanceof Error && error.name === "AbortError") {
374
+ if (isAbortError(error)) {
375
375
  throw new VideoError({
376
376
  code: VIDEO_ERROR_CODES.GENERATION_FAILED,
377
377
  message: "Video generation request timed out after 30 seconds",
@@ -542,7 +542,7 @@ async function makePollRequest(pollEndpoint, operationName, accessToken, timeout
542
542
  }
543
543
  catch (error) {
544
544
  clearTimeout(requestTimeout);
545
- if (error instanceof Error && error.name === "AbortError") {
545
+ if (isAbortError(error)) {
546
546
  throw new VideoError({
547
547
  code: VIDEO_ERROR_CODES.GENERATION_FAILED,
548
548
  message: `Poll request timed out after ${timeoutMs}ms`,
@@ -7,6 +7,7 @@
7
7
  import { logger } from "../utils/logger.js";
8
8
  import { modelConfig } from "./modelConfiguration.js";
9
9
  import { extractTokenUsage as extractTokenUsageUtil } from "../utils/tokenUtils.js";
10
+ import { calculateCost, hasPricing } from "../utils/pricing.js";
10
11
  /**
11
12
  * Create analytics data structure from AI response
12
13
  */
@@ -60,19 +61,25 @@ function extractTokenUsage(result) {
60
61
  return extractTokenUsageUtil(result.usage);
61
62
  }
62
63
  /**
63
- * Estimate cost based on provider, model, and token usage
64
+ * Estimate cost based on provider, model, and token usage.
65
+ * Uses the per-model pricing table first (which includes cache token rates),
66
+ * then falls back to the provider-level configuration system.
64
67
  */
65
68
  function estimateCost(provider, model, tokens) {
66
69
  try {
67
- // Use the new configuration system instead of hardcoded costs
70
+ // Try the per-model pricing table first (includes cache token rates)
71
+ if (hasPricing(provider, model)) {
72
+ return calculateCost(provider, model, tokens);
73
+ }
74
+ // Fall back to the configuration system for providers/models not in the pricing table
68
75
  const costInfo = modelConfig.getCostInfo(provider.toLowerCase(), model);
69
76
  if (!costInfo) {
70
77
  return undefined;
71
78
  }
72
- // Calculate cost using the configuration system
79
+ // Calculate cost using the configuration system (per-1K-token rates)
73
80
  const inputCost = (tokens.input / 1000) * costInfo.input;
74
81
  const outputCost = (tokens.output / 1000) * costInfo.output;
75
- return Math.round((inputCost + outputCost) * 100000) / 100000; // Round to 5 decimal places
82
+ return Math.round((inputCost + outputCost) * 1_000_000) / 1_000_000; // Round to 6 decimal places
76
83
  }
77
84
  catch (error) {
78
85
  logger.debug("Cost estimation failed", { provider, model, error });
@@ -46,6 +46,12 @@ export declare abstract class BaseProvider implements AIProvider {
46
46
  * Execute fake streaming - extracted method for reusability
47
47
  */
48
48
  private executeFakeStreaming;
49
+ /**
50
+ * Apply per-call tool filtering (whitelist/blacklist) to a tools record.
51
+ * If toolFilter is set, only tools whose names are in the list are kept.
52
+ * If excludeTools is set, matching tools are removed. excludeTools is applied after toolFilter.
53
+ */
54
+ private applyToolFiltering;
49
55
  /**
50
56
  * Prepare generation context including tools and model
51
57
  */