@juspay/neurolink 9.6.0 → 9.7.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 +6 -0
- package/dist/adapters/video/vertexVideoHandler.js +3 -3
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/core/analytics.js +11 -4
- package/dist/core/baseProvider.d.ts +6 -0
- package/dist/core/baseProvider.js +83 -14
- package/dist/core/conversationMemoryManager.d.ts +13 -0
- package/dist/core/conversationMemoryManager.js +28 -0
- package/dist/core/dynamicModels.js +3 -2
- package/dist/core/modules/GenerationHandler.js +2 -0
- package/dist/core/redisConversationMemoryManager.d.ts +11 -0
- package/dist/core/redisConversationMemoryManager.js +26 -9
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/lib/adapters/video/vertexVideoHandler.js +3 -3
- package/dist/lib/core/analytics.js +11 -4
- package/dist/lib/core/baseProvider.d.ts +6 -0
- package/dist/lib/core/baseProvider.js +83 -14
- package/dist/lib/core/conversationMemoryManager.d.ts +13 -0
- package/dist/lib/core/conversationMemoryManager.js +28 -0
- package/dist/lib/core/dynamicModels.js +3 -2
- package/dist/lib/core/modules/GenerationHandler.js +2 -0
- package/dist/lib/core/redisConversationMemoryManager.d.ts +11 -0
- package/dist/lib/core/redisConversationMemoryManager.js +26 -9
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.js +5 -0
- package/dist/lib/mcp/httpRetryHandler.js +6 -2
- package/dist/lib/neurolink.d.ts +5 -0
- package/dist/lib/neurolink.js +160 -10
- package/dist/lib/processors/base/BaseFileProcessor.js +2 -1
- package/dist/lib/processors/errors/errorHelpers.js +12 -4
- package/dist/lib/providers/amazonBedrock.js +2 -1
- package/dist/lib/providers/anthropic.js +2 -2
- package/dist/lib/providers/anthropicBaseProvider.js +10 -4
- package/dist/lib/providers/azureOpenai.js +14 -25
- package/dist/lib/providers/googleAiStudio.d.ts +0 -34
- package/dist/lib/providers/googleAiStudio.js +124 -315
- package/dist/lib/providers/googleNativeGemini3.d.ts +119 -0
- package/dist/lib/providers/googleNativeGemini3.js +264 -0
- package/dist/lib/providers/googleVertex.d.ts +0 -40
- package/dist/lib/providers/googleVertex.js +150 -317
- package/dist/lib/providers/huggingFace.js +20 -5
- package/dist/lib/providers/litellm.js +6 -4
- package/dist/lib/providers/mistral.js +3 -2
- package/dist/lib/providers/openAI.js +2 -2
- package/dist/lib/providers/openRouter.js +8 -7
- package/dist/lib/providers/openaiCompatible.js +10 -4
- package/dist/lib/rag/resilience/RetryHandler.js +6 -2
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +24 -2
- package/dist/lib/services/server/ai/observability/instrumentation.js +12 -1
- package/dist/lib/types/generateTypes.d.ts +28 -0
- package/dist/lib/types/ragTypes.d.ts +9 -1
- package/dist/lib/types/streamTypes.d.ts +13 -0
- package/dist/lib/utils/conversationMemory.js +15 -0
- package/dist/lib/utils/errorHandling.d.ts +5 -0
- package/dist/lib/utils/errorHandling.js +19 -0
- package/dist/lib/utils/pricing.d.ts +12 -0
- package/dist/lib/utils/pricing.js +134 -0
- package/dist/lib/utils/redis.d.ts +17 -0
- package/dist/lib/utils/redis.js +105 -0
- package/dist/lib/utils/timeout.d.ts +10 -0
- package/dist/lib/utils/timeout.js +15 -0
- package/dist/mcp/httpRetryHandler.js +6 -2
- package/dist/neurolink.d.ts +5 -0
- package/dist/neurolink.js +160 -10
- package/dist/processors/base/BaseFileProcessor.js +2 -1
- package/dist/processors/errors/errorHelpers.js +12 -4
- package/dist/providers/amazonBedrock.js +2 -1
- package/dist/providers/anthropic.js +2 -2
- package/dist/providers/anthropicBaseProvider.js +10 -4
- package/dist/providers/azureOpenai.js +14 -25
- package/dist/providers/googleAiStudio.d.ts +0 -34
- package/dist/providers/googleAiStudio.js +124 -315
- package/dist/providers/googleNativeGemini3.d.ts +119 -0
- package/dist/providers/googleNativeGemini3.js +263 -0
- package/dist/providers/googleVertex.d.ts +0 -40
- package/dist/providers/googleVertex.js +150 -317
- package/dist/providers/huggingFace.js +20 -5
- package/dist/providers/litellm.js +6 -4
- package/dist/providers/mistral.js +3 -2
- package/dist/providers/openAI.js +2 -2
- package/dist/providers/openRouter.js +8 -7
- package/dist/providers/openaiCompatible.js +10 -4
- package/dist/rag/resilience/RetryHandler.js +6 -2
- package/dist/services/server/ai/observability/instrumentation.d.ts +24 -2
- package/dist/services/server/ai/observability/instrumentation.js +12 -1
- package/dist/types/generateTypes.d.ts +28 -0
- package/dist/types/ragTypes.d.ts +9 -1
- package/dist/types/streamTypes.d.ts +13 -0
- package/dist/utils/conversationMemory.js +15 -0
- package/dist/utils/errorHandling.d.ts +5 -0
- package/dist/utils/errorHandling.js +19 -0
- package/dist/utils/pricing.d.ts +12 -0
- package/dist/utils/pricing.js +133 -0
- package/dist/utils/redis.d.ts +17 -0
- package/dist/utils/redis.js +105 -0
- package/dist/utils/timeout.d.ts +10 -0
- package/dist/utils/timeout.js +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [9.7.0](https://github.com/juspay/neurolink/compare/v9.6.0...v9.7.0) (2026-02-16)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **(core):** add abort signal composition, tool filtering, provider hardening, and shared utilities ([805e8fe](https://github.com/juspay/neurolink/commit/805e8fe98184d3b57360ce751d61806450fe0ab8))
|
|
6
|
+
|
|
1
7
|
## [9.6.0](https://github.com/juspay/neurolink/compare/v9.5.3...v9.6.0) (2026-02-14)
|
|
2
8
|
|
|
3
9
|
### 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
|
|
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
|
|
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
|
package/dist/core/analytics.js
CHANGED
|
@@ -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
|
-
//
|
|
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) *
|
|
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
|
|
105
|
-
const externalTools = (options.tools || {});
|
|
106
|
-
const mergedTools = { ...baseTools, ...externalTools };
|
|
105
|
+
const mergedTools = await this.getToolsForStream(options);
|
|
107
106
|
options = { ...options, tools: mergedTools };
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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) *
|
|
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
|
*/
|