@juspay/neurolink 7.24.1 → 7.25.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 CHANGED
@@ -1,3 +1,9 @@
1
+ ## [7.25.0](https://github.com/juspay/neurolink/compare/v7.24.1...v7.25.0) (2025-08-21)
2
+
3
+ ### Features
4
+
5
+ - **(middleware):** add custom middleware development guide ([ffd0343](https://github.com/juspay/neurolink/commit/ffd0343a589b267a5b8349a06cdfe2664a942e4c))
6
+
1
7
  ## [7.24.1](https://github.com/juspay/neurolink/compare/v7.24.0...v7.24.1) (2025-08-21)
2
8
 
3
9
  ## [7.24.0](https://github.com/juspay/neurolink/compare/v7.23.0...v7.24.0) (2025-08-20)
@@ -63,6 +63,19 @@ export declare abstract class BaseProvider implements AIProvider {
63
63
  * Returns the Vercel AI SDK model instance for this provider
64
64
  */
65
65
  protected abstract getAISDKModel(): LanguageModelV1 | Promise<LanguageModelV1>;
66
+ /**
67
+ * Get AI SDK model with middleware applied
68
+ * This method wraps the base model with any configured middleware
69
+ */
70
+ protected getAISDKModelWithMiddleware(options?: TextGenerationOptions | StreamOptions): Promise<LanguageModelV1>;
71
+ /**
72
+ * Extract middleware options from generation options
73
+ */
74
+ private extractMiddlewareOptions;
75
+ /**
76
+ * Determine if middleware should be skipped for this request
77
+ */
78
+ private shouldSkipMiddleware;
66
79
  /**
67
80
  * Get all available tools - direct tools are ALWAYS available
68
81
  * MCP tools are added when available (without blocking)
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { MiddlewareFactory } from "../middleware/factory.js";
2
3
  import { logger } from "../utils/logger.js";
3
4
  import { DEFAULT_MAX_STEPS, STEP_LIMITS } from "../core/constants.js";
4
5
  import { directAgentTools } from "../agent/directTools.js";
@@ -357,6 +358,83 @@ export class BaseProvider {
357
358
  evaluation: result.evaluation,
358
359
  };
359
360
  }
361
+ /**
362
+ * Get AI SDK model with middleware applied
363
+ * This method wraps the base model with any configured middleware
364
+ */
365
+ async getAISDKModelWithMiddleware(options = {}) {
366
+ // Get the base model
367
+ const baseModel = await this.getAISDKModel();
368
+ // Check if middleware should be applied
369
+ const middlewareOptions = this.extractMiddlewareOptions(options);
370
+ if (!middlewareOptions || this.shouldSkipMiddleware(options)) {
371
+ return baseModel;
372
+ }
373
+ try {
374
+ // Create middleware context
375
+ const context = MiddlewareFactory.createContext(this.providerName, this.modelName, options, {
376
+ sessionId: this.sessionId,
377
+ userId: this.userId,
378
+ });
379
+ // Apply middleware to the model
380
+ const wrappedModel = MiddlewareFactory.applyMiddleware(baseModel, context, middlewareOptions);
381
+ logger.debug(`Applied middleware to ${this.providerName} model`, {
382
+ provider: this.providerName,
383
+ model: this.modelName,
384
+ hasMiddleware: true,
385
+ });
386
+ return wrappedModel;
387
+ }
388
+ catch (error) {
389
+ logger.warn(`Failed to apply middleware to ${this.providerName}, using base model`, {
390
+ error: error instanceof Error ? error.message : String(error),
391
+ });
392
+ // Return base model on middleware failure to maintain functionality
393
+ return baseModel;
394
+ }
395
+ }
396
+ /**
397
+ * Extract middleware options from generation options
398
+ */
399
+ extractMiddlewareOptions(options) {
400
+ // Check for middleware configuration in options
401
+ const optionsRecord = options;
402
+ const middlewareConfig = optionsRecord.middlewareConfig;
403
+ const enabledMiddleware = optionsRecord.enabledMiddleware;
404
+ const disabledMiddleware = optionsRecord.disabledMiddleware;
405
+ const preset = optionsRecord.middlewarePreset;
406
+ // If no middleware configuration is present, return null
407
+ if (!middlewareConfig &&
408
+ !enabledMiddleware &&
409
+ !disabledMiddleware &&
410
+ !preset) {
411
+ return null;
412
+ }
413
+ return {
414
+ middlewareConfig,
415
+ enabledMiddleware,
416
+ disabledMiddleware,
417
+ preset,
418
+ global: {
419
+ collectStats: true,
420
+ continueOnError: true,
421
+ },
422
+ };
423
+ }
424
+ /**
425
+ * Determine if middleware should be skipped for this request
426
+ */
427
+ shouldSkipMiddleware(options) {
428
+ // Skip middleware if explicitly disabled
429
+ if (options.disableMiddleware === true) {
430
+ return true;
431
+ }
432
+ // Skip middleware for tool-disabled requests to avoid conflicts
433
+ if (options.disableTools === true) {
434
+ return true;
435
+ }
436
+ return false;
437
+ }
360
438
  // ===================
361
439
  // TOOL MANAGEMENT
362
440
  // ===================
@@ -428,7 +506,7 @@ export class BaseProvider {
428
506
  timestamp: new Date().toISOString(),
429
507
  params: params,
430
508
  // Keep it simple - just indicate an error occurred
431
- message: `Error calling ${toolName}: ${error instanceof Error ? error.message : String(error)}`
509
+ message: `Error calling ${toolName}: ${error instanceof Error ? error.message : String(error)}`,
432
510
  };
433
511
  }
434
512
  },
package/dist/index.d.ts CHANGED
@@ -18,6 +18,9 @@ export { getBestProvider, getAvailableProviders, isValidProvider, } from "./util
18
18
  export { NeuroLink } from "./neurolink.js";
19
19
  export type { ProviderStatus, MCPStatus } from "./neurolink.js";
20
20
  export type { MCPServerInfo } from "./types/mcpTypes.js";
21
+ export type { NeuroLinkMiddleware, MiddlewareContext, MiddlewareFactoryOptions, } from "./middleware/types.js";
22
+ export { MiddlewareRegistry, MiddlewareFactory } from "./middleware/index.js";
23
+ export { createAnalyticsMiddleware } from "./middleware/builtin/analytics.js";
21
24
  export declare const VERSION = "1.0.0";
22
25
  /**
23
26
  * Quick start factory function
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ export { BedrockModels, OpenAIModels, VertexModels, DEFAULT_PROVIDER_CONFIGS, }
16
16
  export { getBestProvider, getAvailableProviders, isValidProvider, } from "./utils/providerUtils.js";
17
17
  // Main NeuroLink wrapper class and diagnostic types
18
18
  export { NeuroLink } from "./neurolink.js";
19
+ export { MiddlewareRegistry, MiddlewareFactory } from "./middleware/index.js";
20
+ export { createAnalyticsMiddleware } from "./middleware/builtin/analytics.js";
19
21
  // Version
20
22
  export const VERSION = "1.0.0";
21
23
  /**
@@ -63,6 +63,19 @@ export declare abstract class BaseProvider implements AIProvider {
63
63
  * Returns the Vercel AI SDK model instance for this provider
64
64
  */
65
65
  protected abstract getAISDKModel(): LanguageModelV1 | Promise<LanguageModelV1>;
66
+ /**
67
+ * Get AI SDK model with middleware applied
68
+ * This method wraps the base model with any configured middleware
69
+ */
70
+ protected getAISDKModelWithMiddleware(options?: TextGenerationOptions | StreamOptions): Promise<LanguageModelV1>;
71
+ /**
72
+ * Extract middleware options from generation options
73
+ */
74
+ private extractMiddlewareOptions;
75
+ /**
76
+ * Determine if middleware should be skipped for this request
77
+ */
78
+ private shouldSkipMiddleware;
66
79
  /**
67
80
  * Get all available tools - direct tools are ALWAYS available
68
81
  * MCP tools are added when available (without blocking)
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { MiddlewareFactory } from "../middleware/factory.js";
2
3
  import { logger } from "../utils/logger.js";
3
4
  import { DEFAULT_MAX_STEPS, STEP_LIMITS } from "../core/constants.js";
4
5
  import { directAgentTools } from "../agent/directTools.js";
@@ -357,6 +358,83 @@ export class BaseProvider {
357
358
  evaluation: result.evaluation,
358
359
  };
359
360
  }
361
+ /**
362
+ * Get AI SDK model with middleware applied
363
+ * This method wraps the base model with any configured middleware
364
+ */
365
+ async getAISDKModelWithMiddleware(options = {}) {
366
+ // Get the base model
367
+ const baseModel = await this.getAISDKModel();
368
+ // Check if middleware should be applied
369
+ const middlewareOptions = this.extractMiddlewareOptions(options);
370
+ if (!middlewareOptions || this.shouldSkipMiddleware(options)) {
371
+ return baseModel;
372
+ }
373
+ try {
374
+ // Create middleware context
375
+ const context = MiddlewareFactory.createContext(this.providerName, this.modelName, options, {
376
+ sessionId: this.sessionId,
377
+ userId: this.userId,
378
+ });
379
+ // Apply middleware to the model
380
+ const wrappedModel = MiddlewareFactory.applyMiddleware(baseModel, context, middlewareOptions);
381
+ logger.debug(`Applied middleware to ${this.providerName} model`, {
382
+ provider: this.providerName,
383
+ model: this.modelName,
384
+ hasMiddleware: true,
385
+ });
386
+ return wrappedModel;
387
+ }
388
+ catch (error) {
389
+ logger.warn(`Failed to apply middleware to ${this.providerName}, using base model`, {
390
+ error: error instanceof Error ? error.message : String(error),
391
+ });
392
+ // Return base model on middleware failure to maintain functionality
393
+ return baseModel;
394
+ }
395
+ }
396
+ /**
397
+ * Extract middleware options from generation options
398
+ */
399
+ extractMiddlewareOptions(options) {
400
+ // Check for middleware configuration in options
401
+ const optionsRecord = options;
402
+ const middlewareConfig = optionsRecord.middlewareConfig;
403
+ const enabledMiddleware = optionsRecord.enabledMiddleware;
404
+ const disabledMiddleware = optionsRecord.disabledMiddleware;
405
+ const preset = optionsRecord.middlewarePreset;
406
+ // If no middleware configuration is present, return null
407
+ if (!middlewareConfig &&
408
+ !enabledMiddleware &&
409
+ !disabledMiddleware &&
410
+ !preset) {
411
+ return null;
412
+ }
413
+ return {
414
+ middlewareConfig,
415
+ enabledMiddleware,
416
+ disabledMiddleware,
417
+ preset,
418
+ global: {
419
+ collectStats: true,
420
+ continueOnError: true,
421
+ },
422
+ };
423
+ }
424
+ /**
425
+ * Determine if middleware should be skipped for this request
426
+ */
427
+ shouldSkipMiddleware(options) {
428
+ // Skip middleware if explicitly disabled
429
+ if (options.disableMiddleware === true) {
430
+ return true;
431
+ }
432
+ // Skip middleware for tool-disabled requests to avoid conflicts
433
+ if (options.disableTools === true) {
434
+ return true;
435
+ }
436
+ return false;
437
+ }
360
438
  // ===================
361
439
  // TOOL MANAGEMENT
362
440
  // ===================
@@ -428,7 +506,7 @@ export class BaseProvider {
428
506
  timestamp: new Date().toISOString(),
429
507
  params: params,
430
508
  // Keep it simple - just indicate an error occurred
431
- message: `Error calling ${toolName}: ${error instanceof Error ? error.message : String(error)}`
509
+ message: `Error calling ${toolName}: ${error instanceof Error ? error.message : String(error)}`,
432
510
  };
433
511
  }
434
512
  },
@@ -18,6 +18,9 @@ export { getBestProvider, getAvailableProviders, isValidProvider, } from "./util
18
18
  export { NeuroLink } from "./neurolink.js";
19
19
  export type { ProviderStatus, MCPStatus } from "./neurolink.js";
20
20
  export type { MCPServerInfo } from "./types/mcpTypes.js";
21
+ export type { NeuroLinkMiddleware, MiddlewareContext, MiddlewareFactoryOptions, } from "./middleware/types.js";
22
+ export { MiddlewareRegistry, MiddlewareFactory } from "./middleware/index.js";
23
+ export { createAnalyticsMiddleware } from "./middleware/builtin/analytics.js";
21
24
  export declare const VERSION = "1.0.0";
22
25
  /**
23
26
  * Quick start factory function
package/dist/lib/index.js CHANGED
@@ -16,6 +16,8 @@ export { BedrockModels, OpenAIModels, VertexModels, DEFAULT_PROVIDER_CONFIGS, }
16
16
  export { getBestProvider, getAvailableProviders, isValidProvider, } from "./utils/providerUtils.js";
17
17
  // Main NeuroLink wrapper class and diagnostic types
18
18
  export { NeuroLink } from "./neurolink.js";
19
+ export { MiddlewareRegistry, MiddlewareFactory } from "./middleware/index.js";
20
+ export { createAnalyticsMiddleware } from "./middleware/builtin/analytics.js";
19
21
  // Version
20
22
  export const VERSION = "1.0.0";
21
23
  /**
@@ -0,0 +1,16 @@
1
+ import type { NeuroLinkMiddleware } from "../types.js";
2
+ /**
3
+ * Create analytics middleware for tracking AI model usage
4
+ * Collects metrics on token usage, response times, and model performance
5
+ */
6
+ export declare function createAnalyticsMiddleware(): NeuroLinkMiddleware;
7
+ /**
8
+ * Get collected metrics from analytics middleware
9
+ * Note: This is a utility function for accessing metrics
10
+ */
11
+ export declare function getAnalyticsMetrics(): Map<string, Record<string, unknown>>;
12
+ /**
13
+ * Clear collected metrics from analytics middleware
14
+ * Note: This is a utility function for clearing metrics
15
+ */
16
+ export declare function clearAnalyticsMetrics(): void;
@@ -0,0 +1,130 @@
1
+ import { logger } from "../../utils/logger.js";
2
+ /**
3
+ * Create analytics middleware for tracking AI model usage
4
+ * Collects metrics on token usage, response times, and model performance
5
+ */
6
+ export function createAnalyticsMiddleware() {
7
+ const requestMetrics = new Map();
8
+ const metadata = {
9
+ id: "analytics",
10
+ name: "Analytics Tracking",
11
+ description: "Tracks token usage, response times, and model performance metrics",
12
+ priority: 100, // High priority to ensure analytics are captured
13
+ defaultEnabled: true,
14
+ };
15
+ const middleware = {
16
+ wrapGenerate: async ({ doGenerate, params }) => {
17
+ const requestId = `analytics-${Date.now()}`;
18
+ const startTime = Date.now();
19
+ logger.debug(`[AnalyticsMiddleware] Starting request tracking`, {
20
+ requestId,
21
+ hasPrompt: !!params.prompt,
22
+ });
23
+ try {
24
+ // Execute the generation
25
+ const result = await doGenerate();
26
+ // Calculate metrics
27
+ const responseTime = Date.now() - startTime;
28
+ const analytics = {
29
+ requestId,
30
+ responseTime,
31
+ timestamp: new Date().toISOString(),
32
+ usage: {
33
+ inputTokens: result.usage?.promptTokens || 0,
34
+ outputTokens: result.usage?.completionTokens || 0,
35
+ totalTokens: (result.usage?.promptTokens || 0) +
36
+ (result.usage?.completionTokens || 0),
37
+ },
38
+ };
39
+ // Store metrics for potential retrieval
40
+ requestMetrics.set(requestId, analytics);
41
+ logger.debug(`[AnalyticsMiddleware] Request completed`, analytics);
42
+ // Add analytics to the result
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ const updatedResult = { ...result };
45
+ if (!updatedResult.experimental_providerMetadata) {
46
+ updatedResult.experimental_providerMetadata = {};
47
+ }
48
+ if (!updatedResult.experimental_providerMetadata.neurolink) {
49
+ updatedResult.experimental_providerMetadata.neurolink = {};
50
+ }
51
+ updatedResult.experimental_providerMetadata.neurolink.analytics =
52
+ analytics;
53
+ return updatedResult;
54
+ }
55
+ catch (error) {
56
+ const responseTime = Date.now() - startTime;
57
+ logger.error(`[AnalyticsMiddleware] Request failed`, {
58
+ requestId,
59
+ responseTime,
60
+ error: error instanceof Error ? error.message : String(error),
61
+ });
62
+ throw error;
63
+ }
64
+ },
65
+ wrapStream: async ({ doStream, params }) => {
66
+ const requestId = `analytics-stream-${Date.now()}`;
67
+ const startTime = Date.now();
68
+ logger.debug(`[AnalyticsMiddleware] Starting stream tracking`, {
69
+ requestId,
70
+ hasPrompt: !!params.prompt,
71
+ });
72
+ try {
73
+ const result = await doStream();
74
+ const streamAnalytics = {
75
+ requestId,
76
+ startTime,
77
+ timestamp: new Date().toISOString(),
78
+ streamingMode: true,
79
+ };
80
+ requestMetrics.set(requestId, streamAnalytics);
81
+ // The 'result' is a stream, so we can't directly modify it.
82
+ // Analytics for streams are typically handled after the stream is consumed.
83
+ // For this middleware, we'll log the start and rely on other mechanisms
84
+ // to capture the end-to-end stream metrics if needed.
85
+ // We will pass a new property in the `rawResponse`
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ const updatedResult = { ...result };
88
+ if (!updatedResult.rawResponse) {
89
+ updatedResult.rawResponse = {};
90
+ }
91
+ if (!updatedResult.rawResponse.neurolink) {
92
+ updatedResult.rawResponse.neurolink = {};
93
+ }
94
+ updatedResult.rawResponse.neurolink.analytics = streamAnalytics;
95
+ return updatedResult;
96
+ }
97
+ catch (error) {
98
+ const responseTime = Date.now() - startTime;
99
+ logger.error(`[AnalyticsMiddleware] Stream failed`, {
100
+ requestId,
101
+ responseTime,
102
+ error: error instanceof Error ? error.message : String(error),
103
+ });
104
+ throw error;
105
+ }
106
+ },
107
+ };
108
+ // Return the NeuroLinkMiddleware with metadata
109
+ return {
110
+ ...middleware,
111
+ metadata,
112
+ };
113
+ }
114
+ /**
115
+ * Get collected metrics from analytics middleware
116
+ * Note: This is a utility function for accessing metrics
117
+ */
118
+ export function getAnalyticsMetrics() {
119
+ // This would need to be implemented with a global registry
120
+ // For now, return empty map
121
+ return new Map();
122
+ }
123
+ /**
124
+ * Clear collected metrics from analytics middleware
125
+ * Note: This is a utility function for clearing metrics
126
+ */
127
+ export function clearAnalyticsMetrics() {
128
+ // This would need to be implemented with a global registry
129
+ // For now, do nothing
130
+ }
@@ -0,0 +1,54 @@
1
+ import type { LanguageModelV1 } from "ai";
2
+ import type { MiddlewareContext, MiddlewareConfig, MiddlewareFactoryOptions, MiddlewareChainStats } from "./types.js";
3
+ /**
4
+ * Middleware factory for creating and applying middleware chains
5
+ */
6
+ export declare class MiddlewareFactory {
7
+ /**
8
+ * Apply middleware to a language model
9
+ */
10
+ static applyMiddleware(model: LanguageModelV1, context: MiddlewareContext, options?: MiddlewareFactoryOptions): LanguageModelV1;
11
+ /**
12
+ * Build middleware configuration from factory options
13
+ */
14
+ private static buildMiddlewareConfig;
15
+ /**
16
+ * Get preset configuration
17
+ */
18
+ private static getPresetConfig;
19
+ /**
20
+ * Get built-in preset configurations
21
+ */
22
+ private static getBuiltInPresets;
23
+ /**
24
+ * Create middleware context from provider and options
25
+ */
26
+ static createContext(provider: string, model: string, options?: Record<string, unknown>, session?: {
27
+ sessionId?: string;
28
+ userId?: string;
29
+ }): MiddlewareContext;
30
+ /**
31
+ * Validate middleware configuration
32
+ */
33
+ static validateConfig(config: Record<string, MiddlewareConfig>): {
34
+ isValid: boolean;
35
+ errors: string[];
36
+ warnings: string[];
37
+ };
38
+ /**
39
+ * Get available presets
40
+ */
41
+ static getAvailablePresets(): Array<{
42
+ name: string;
43
+ description: string;
44
+ middleware: string[];
45
+ }>;
46
+ /**
47
+ * Get middleware chain statistics
48
+ */
49
+ static getChainStats(context: MiddlewareContext, config: Record<string, MiddlewareConfig>): MiddlewareChainStats;
50
+ /**
51
+ * Create a middleware-enabled model factory function
52
+ */
53
+ static createModelFactory(baseModelFactory: () => Promise<LanguageModelV1>, defaultOptions?: MiddlewareFactoryOptions): (context: MiddlewareContext, options?: MiddlewareFactoryOptions) => Promise<LanguageModelV1>;
54
+ }