@juspay/neurolink 9.29.1 → 9.30.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
+ ## [9.30.0](https://github.com/juspay/neurolink/compare/v9.29.1...v9.30.0) (2026-03-21)
2
+
3
+ ### Features
4
+
5
+ - **(middleware):** add lifecycle middleware with onFinish, onError, onChunk callbacks ([2d23087](https://github.com/juspay/neurolink/commit/2d230879272a8e67fd936ff087b232bb25f612c0))
6
+
1
7
  ## [9.29.1](https://github.com/juspay/neurolink/compare/v9.29.0...v9.29.1) (2026-03-20)
2
8
 
3
9
  ### Bug Fixes
package/dist/index.d.ts CHANGED
@@ -53,6 +53,7 @@ import { createContextEnricher, flushOpenTelemetry, getLangfuseContext, getLangf
53
53
  export type { LangfuseContext } from "./services/server/ai/observability/instrumentation.js";
54
54
  export { initializeOpenTelemetry, shutdownOpenTelemetry, flushOpenTelemetry, getLangfuseHealthStatus, setLangfuseContext, getLangfuseSpanProcessor, getTracerProvider, isOpenTelemetryInitialized, getSpanProcessors, createContextEnricher, isUsingExternalTracerProvider, getLangfuseContext, getTracer, runWithCurrentLangfuseContext, };
55
55
  export { clearAnalyticsMetrics, createAnalyticsMiddleware, getAnalyticsMetrics, } from "./middleware/builtin/analytics.js";
56
+ export { createLifecycleMiddleware } from "./middleware/builtin/lifecycle.js";
56
57
  export { MiddlewareFactory } from "./middleware/factory.js";
57
58
  export { ExporterRegistry } from "./observability/exporterRegistry.js";
58
59
  export { NoOpExporter } from "./observability/exporters/baseExporter.js";
package/dist/index.js CHANGED
@@ -71,6 +71,7 @@ getLangfuseContext, getTracer,
71
71
  runWithCurrentLangfuseContext, };
72
72
  // Analytics Middleware exports
73
73
  export { clearAnalyticsMetrics, createAnalyticsMiddleware, getAnalyticsMetrics, } from "./middleware/builtin/analytics.js";
74
+ export { createLifecycleMiddleware } from "./middleware/builtin/lifecycle.js";
74
75
  export { MiddlewareFactory } from "./middleware/factory.js";
75
76
  export { ExporterRegistry } from "./observability/exporterRegistry.js";
76
77
  export { NoOpExporter } from "./observability/exporters/baseExporter.js";
@@ -53,6 +53,7 @@ import { createContextEnricher, flushOpenTelemetry, getLangfuseContext, getLangf
53
53
  export type { LangfuseContext } from "./services/server/ai/observability/instrumentation.js";
54
54
  export { initializeOpenTelemetry, shutdownOpenTelemetry, flushOpenTelemetry, getLangfuseHealthStatus, setLangfuseContext, getLangfuseSpanProcessor, getTracerProvider, isOpenTelemetryInitialized, getSpanProcessors, createContextEnricher, isUsingExternalTracerProvider, getLangfuseContext, getTracer, runWithCurrentLangfuseContext, };
55
55
  export { clearAnalyticsMetrics, createAnalyticsMiddleware, getAnalyticsMetrics, } from "./middleware/builtin/analytics.js";
56
+ export { createLifecycleMiddleware } from "./middleware/builtin/lifecycle.js";
56
57
  export { MiddlewareFactory } from "./middleware/factory.js";
57
58
  export { ExporterRegistry } from "./observability/exporterRegistry.js";
58
59
  export { NoOpExporter } from "./observability/exporters/baseExporter.js";
package/dist/lib/index.js CHANGED
@@ -71,6 +71,7 @@ getLangfuseContext, getTracer,
71
71
  runWithCurrentLangfuseContext, };
72
72
  // Analytics Middleware exports
73
73
  export { clearAnalyticsMetrics, createAnalyticsMiddleware, getAnalyticsMetrics, } from "./middleware/builtin/analytics.js";
74
+ export { createLifecycleMiddleware } from "./middleware/builtin/lifecycle.js";
74
75
  export { MiddlewareFactory } from "./middleware/factory.js";
75
76
  export { ExporterRegistry } from "./observability/exporterRegistry.js";
76
77
  export { NoOpExporter } from "./observability/exporters/baseExporter.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Lifecycle Middleware
3
+ *
4
+ * Provides onFinish, onError, and onChunk callbacks for observing
5
+ * generation and streaming lifecycle events.
6
+ *
7
+ * This middleware is automatically enabled when lifecycle callbacks
8
+ * (onFinish, onError, onChunk) are passed in GenerateOptions or StreamOptions.
9
+ */
10
+ import type { NeuroLinkMiddleware, LifecycleMiddlewareConfig } from "../../types/middlewareTypes.js";
11
+ export declare function createLifecycleMiddleware(config?: LifecycleMiddlewareConfig): NeuroLinkMiddleware;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Lifecycle Middleware
3
+ *
4
+ * Provides onFinish, onError, and onChunk callbacks for observing
5
+ * generation and streaming lifecycle events.
6
+ *
7
+ * This middleware is automatically enabled when lifecycle callbacks
8
+ * (onFinish, onError, onChunk) are passed in GenerateOptions or StreamOptions.
9
+ */
10
+ import { logger } from "../../utils/logger.js";
11
+ import { isRecoverableError } from "../../utils/errorHandling.js";
12
+ export function createLifecycleMiddleware(config = {}) {
13
+ const metadata = {
14
+ id: "lifecycle",
15
+ name: "Lifecycle Callbacks",
16
+ description: "Provides onFinish, onError, and onChunk callbacks for generation and streaming lifecycle events",
17
+ priority: 110,
18
+ defaultEnabled: false,
19
+ };
20
+ const middleware = {
21
+ wrapGenerate: async ({ doGenerate }) => {
22
+ const startTime = Date.now();
23
+ try {
24
+ const result = await doGenerate();
25
+ if (config.onFinish) {
26
+ try {
27
+ const callbackResult = config.onFinish({
28
+ text: result.text ?? "",
29
+ usage: result.usage
30
+ ? {
31
+ promptTokens: result.usage.promptTokens ?? 0,
32
+ completionTokens: result.usage.completionTokens ?? 0,
33
+ }
34
+ : undefined,
35
+ duration: Date.now() - startTime,
36
+ finishReason: result.finishReason,
37
+ });
38
+ Promise.resolve(callbackResult).catch((e) => {
39
+ logger.warn("[LifecycleMiddleware] onFinish callback error:", e);
40
+ });
41
+ }
42
+ catch (e) {
43
+ logger.warn("[LifecycleMiddleware] onFinish callback error:", e);
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ catch (error) {
49
+ if (config.onError) {
50
+ const err = error instanceof Error ? error : new Error(String(error));
51
+ try {
52
+ const callbackResult = config.onError({
53
+ error: err,
54
+ duration: Date.now() - startTime,
55
+ recoverable: isRecoverableError(err),
56
+ });
57
+ Promise.resolve(callbackResult).catch((e) => {
58
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
59
+ });
60
+ }
61
+ catch (e) {
62
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
63
+ }
64
+ }
65
+ throw error;
66
+ }
67
+ },
68
+ wrapStream: async ({ doStream }) => {
69
+ const startTime = Date.now();
70
+ try {
71
+ const result = await doStream();
72
+ if (!config.onChunk && !config.onFinish && !config.onError) {
73
+ return result;
74
+ }
75
+ let sequenceNumber = 0;
76
+ let accumulatedText = "";
77
+ const transformStream = new TransformStream({
78
+ transform(chunk, controller) {
79
+ try {
80
+ if (chunk.type === "text-delta") {
81
+ accumulatedText += chunk.textDelta;
82
+ }
83
+ if (config.onChunk && chunk.type) {
84
+ try {
85
+ const callbackResult = config.onChunk({
86
+ type: chunk.type,
87
+ textDelta: chunk.type === "text-delta" ? chunk.textDelta : undefined,
88
+ sequenceNumber: sequenceNumber++,
89
+ });
90
+ Promise.resolve(callbackResult).catch((e) => {
91
+ logger.warn("[LifecycleMiddleware] onChunk callback error:", e);
92
+ });
93
+ }
94
+ catch (e) {
95
+ logger.warn("[LifecycleMiddleware] onChunk callback error:", e);
96
+ }
97
+ }
98
+ controller.enqueue(chunk);
99
+ }
100
+ catch (error) {
101
+ if (config.onError) {
102
+ const err = error instanceof Error ? error : new Error(String(error));
103
+ try {
104
+ const callbackResult = config.onError({
105
+ error: err,
106
+ duration: Date.now() - startTime,
107
+ recoverable: isRecoverableError(err),
108
+ });
109
+ Promise.resolve(callbackResult).catch((e) => {
110
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
111
+ });
112
+ }
113
+ catch (e) {
114
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
115
+ }
116
+ }
117
+ throw error;
118
+ }
119
+ },
120
+ flush() {
121
+ if (config.onFinish) {
122
+ try {
123
+ const callbackResult = config.onFinish({
124
+ text: accumulatedText,
125
+ duration: Date.now() - startTime,
126
+ });
127
+ Promise.resolve(callbackResult).catch((e) => {
128
+ logger.warn("[LifecycleMiddleware] onFinish callback error:", e);
129
+ });
130
+ }
131
+ catch (e) {
132
+ logger.warn("[LifecycleMiddleware] onFinish callback error:", e);
133
+ }
134
+ }
135
+ },
136
+ });
137
+ return {
138
+ ...result,
139
+ stream: result.stream.pipeThrough(transformStream),
140
+ };
141
+ }
142
+ catch (error) {
143
+ if (config.onError) {
144
+ const err = error instanceof Error ? error : new Error(String(error));
145
+ try {
146
+ const callbackResult = config.onError({
147
+ error: err,
148
+ duration: Date.now() - startTime,
149
+ recoverable: isRecoverableError(err),
150
+ });
151
+ Promise.resolve(callbackResult).catch((e) => {
152
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
153
+ });
154
+ }
155
+ catch (e) {
156
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
157
+ }
158
+ }
159
+ throw error;
160
+ }
161
+ },
162
+ };
163
+ return { ...middleware, metadata };
164
+ }
165
+ //# sourceMappingURL=lifecycle.js.map
@@ -3,6 +3,7 @@ import { MiddlewareRegistry } from "./registry.js";
3
3
  import { createAnalyticsMiddleware } from "./builtin/analytics.js";
4
4
  import { createGuardrailsMiddleware } from "./builtin/guardrails.js";
5
5
  import { createAutoEvaluationMiddleware } from "./builtin/autoEvaluation.js";
6
+ import { createLifecycleMiddleware } from "./builtin/lifecycle.js";
6
7
  import { logger } from "../utils/logger.js";
7
8
  /**
8
9
  * Middleware factory for creating and applying middleware chains.
@@ -26,6 +27,7 @@ export class MiddlewareFactory {
26
27
  analytics: createAnalyticsMiddleware,
27
28
  guardrails: createGuardrailsMiddleware,
28
29
  autoEvaluation: createAutoEvaluationMiddleware,
30
+ lifecycle: createLifecycleMiddleware,
29
31
  };
30
32
  // Register built-in presets
31
33
  this.registerPreset({
@@ -142,6 +144,7 @@ export class MiddlewareFactory {
142
144
  analytics: createAnalyticsMiddleware,
143
145
  guardrails: createGuardrailsMiddleware,
144
146
  autoEvaluation: createAutoEvaluationMiddleware,
147
+ lifecycle: createLifecycleMiddleware,
145
148
  };
146
149
  logger.debug("Getting creator for middleware ID:", id);
147
150
  return builtInMiddlewareCreators[id];
@@ -9,4 +9,5 @@ import { MiddlewareFactory } from "./factory.js";
9
9
  export type { NeuroLinkMiddleware, MiddlewareConfig, MiddlewareContext, MiddlewareConditions, MiddlewareRegistrationOptions, MiddlewareExecutionResult, MiddlewareChainStats, MiddlewarePreset, MiddlewareFactoryOptions, BuiltInMiddlewareType, } from "../types/middlewareTypes.js";
10
10
  export type { LanguageModelV1Middleware } from "ai";
11
11
  export { MiddlewareFactory };
12
+ export { createLifecycleMiddleware } from "./builtin/lifecycle.js";
12
13
  export default MiddlewareFactory;
@@ -9,6 +9,8 @@
9
9
  import { MiddlewareFactory } from "./factory.js";
10
10
  // Factory for creating and applying middleware chains
11
11
  export { MiddlewareFactory };
12
+ // Built-in middleware creators
13
+ export { createLifecycleMiddleware } from "./builtin/lifecycle.js";
12
14
  // Export the factory as the default export for clean, direct usage
13
15
  export default MiddlewareFactory;
14
16
  //# sourceMappingURL=index.js.map
@@ -2209,6 +2209,26 @@ Current user's request: ${currentInput}`;
2209
2209
  },
2210
2210
  });
2211
2211
  }
2212
+ // Auto-inject lifecycle middleware when callbacks are provided
2213
+ // (must happen before workflow/PPT early returns so those paths get middleware too)
2214
+ if (options.onFinish || options.onError) {
2215
+ options.middleware = {
2216
+ ...options.middleware,
2217
+ middlewareConfig: {
2218
+ ...options.middleware?.middlewareConfig,
2219
+ lifecycle: {
2220
+ ...options.middleware?.middlewareConfig?.lifecycle,
2221
+ enabled: true,
2222
+ config: {
2223
+ ...options.middleware?.middlewareConfig?.lifecycle
2224
+ ?.config,
2225
+ onFinish: options.onFinish,
2226
+ onError: options.onError,
2227
+ },
2228
+ },
2229
+ },
2230
+ };
2231
+ }
2212
2232
  // Check if workflow is requested
2213
2233
  if (options.workflow || options.workflowConfig) {
2214
2234
  return await this.generateWithWorkflow(options);
@@ -2364,6 +2384,7 @@ Current user's request: ${currentInput}`;
2364
2384
  fileRegistry: this.fileRegistry,
2365
2385
  abortSignal: options.abortSignal,
2366
2386
  skipToolPromptInjection: options.skipToolPromptInjection,
2387
+ middleware: options.middleware,
2367
2388
  };
2368
2389
  // Auto-map top-level sessionId/userId to context for convenience
2369
2390
  // Tests and users may pass sessionId/userId as top-level options
@@ -2400,7 +2421,6 @@ Current user's request: ${currentInput}`;
2400
2421
  toolResults: toolResults.length,
2401
2422
  });
2402
2423
  }
2403
- // Use redesigned generation logic
2404
2424
  const textResult = await this.generateTextInternal(textOptions);
2405
2425
  // Emit generation completion event (NeuroLink format - enhanced with content)
2406
2426
  this.emitter.emit("generation:end", {
@@ -4024,6 +4044,26 @@ Current user's request: ${currentInput}`;
4024
4044
  });
4025
4045
  }
4026
4046
  this.emitStreamStartEvents(options, startTime);
4047
+ // Auto-inject lifecycle middleware when callbacks are provided
4048
+ // (must happen before workflow early return so that path gets middleware too)
4049
+ if (options.onFinish || options.onError || options.onChunk) {
4050
+ options.middleware = {
4051
+ ...options.middleware,
4052
+ middlewareConfig: {
4053
+ ...options.middleware?.middlewareConfig,
4054
+ lifecycle: {
4055
+ ...options.middleware?.middlewareConfig?.lifecycle,
4056
+ enabled: true,
4057
+ config: {
4058
+ ...options.middleware?.middlewareConfig?.lifecycle?.config,
4059
+ onFinish: options.onFinish,
4060
+ onError: options.onError,
4061
+ onChunk: options.onChunk,
4062
+ },
4063
+ },
4064
+ },
4065
+ };
4066
+ }
4027
4067
  // Check if workflow is requested
4028
4068
  if (options.workflow || options.workflowConfig) {
4029
4069
  const result = await this.streamWithWorkflow(options, startTime);
@@ -6,7 +6,7 @@ import type { JsonValue } from "./common.js";
6
6
  import type { Content, ImageWithAltText } from "./content.js";
7
7
  import type { ChatMessage, ConversationMemoryConfig } from "./conversation.js";
8
8
  import type { EvaluationData } from "./evaluation.js";
9
- import type { MiddlewareFactoryOptions } from "./middlewareTypes.js";
9
+ import type { MiddlewareFactoryOptions, OnFinishCallback, OnErrorCallback } from "./middlewareTypes.js";
10
10
  import type { DirectorModeOptions, DirectorSegment, VideoGenerationResult, VideoOutputOptions } from "./multimodal.js";
11
11
  import type { PPTGenerationResult, PPTOutputOptions } from "./pptTypes.js";
12
12
  import type { TTSOptions, TTSResult } from "./ttsTypes.js";
@@ -399,6 +399,12 @@ export type GenerateOptions = {
399
399
  * @internal Set by NeuroLink SDK — not typically used directly by consumers.
400
400
  */
401
401
  fileRegistry?: unknown;
402
+ /** Per-call middleware configuration. */
403
+ middleware?: import("./middlewareTypes.js").MiddlewareFactoryOptions;
404
+ /** Callback invoked when generation completes successfully. */
405
+ onFinish?: OnFinishCallback;
406
+ /** Callback invoked when generation encounters an error. */
407
+ onError?: OnErrorCallback;
402
408
  };
403
409
  /**
404
410
  * Generate function result type - Primary output format
@@ -110,7 +110,7 @@ export type MiddlewareChainStats = {
110
110
  /**
111
111
  * Built-in middleware types
112
112
  */
113
- export type BuiltInMiddlewareType = "analytics" | "guardrails" | "logging" | "caching" | "rateLimit" | "retry" | "timeout" | "autoEvaluation";
113
+ export type BuiltInMiddlewareType = "analytics" | "guardrails" | "logging" | "caching" | "rateLimit" | "retry" | "timeout" | "autoEvaluation" | "lifecycle";
114
114
  /**
115
115
  * Middleware preset configurations
116
116
  */
@@ -233,3 +233,56 @@ export type MiddlewareChainConfig = {
233
233
  timeout?: number;
234
234
  retries?: number;
235
235
  };
236
+ /**
237
+ * Payload delivered to onFinish callbacks after generation or streaming completes.
238
+ */
239
+ export type LifecycleFinishPayload = {
240
+ /** The generated text content */
241
+ text: string;
242
+ /** Token usage from the provider */
243
+ usage?: {
244
+ promptTokens: number;
245
+ completionTokens: number;
246
+ };
247
+ /** Wall-clock duration in milliseconds */
248
+ duration: number;
249
+ /** Why generation stopped */
250
+ finishReason?: string;
251
+ };
252
+ /**
253
+ * Payload delivered to onError callbacks when generation or streaming fails.
254
+ */
255
+ export type LifecycleErrorPayload = {
256
+ /** The error that occurred */
257
+ error: Error;
258
+ /** Wall-clock duration until failure in milliseconds */
259
+ duration: number;
260
+ /** Whether the error is likely recoverable (rate limit, timeout, network) */
261
+ recoverable: boolean;
262
+ };
263
+ /**
264
+ * Payload delivered to onChunk callbacks for each streaming chunk.
265
+ */
266
+ export type LifecycleChunkPayload = {
267
+ /** Chunk type from the AI SDK stream */
268
+ type: string;
269
+ /** Text content for text-delta chunks */
270
+ textDelta?: string;
271
+ /** Zero-based chunk sequence number */
272
+ sequenceNumber: number;
273
+ };
274
+ /** Callback invoked when generation or streaming finishes successfully. */
275
+ export type OnFinishCallback = (payload: LifecycleFinishPayload) => void | Promise<void>;
276
+ /** Callback invoked when generation or streaming encounters an error. */
277
+ export type OnErrorCallback = (payload: LifecycleErrorPayload) => void | Promise<void>;
278
+ /** Callback invoked for each chunk during streaming. */
279
+ export type OnChunkCallback = (payload: LifecycleChunkPayload) => void | Promise<void>;
280
+ /**
281
+ * Configuration for the lifecycle middleware.
282
+ * Pass callbacks to observe generation/streaming lifecycle events.
283
+ */
284
+ export type LifecycleMiddlewareConfig = {
285
+ onFinish?: OnFinishCallback;
286
+ onError?: OnErrorCallback;
287
+ onChunk?: OnChunkCallback;
288
+ };
@@ -3,7 +3,7 @@ import type { AIProviderName } from "../constants/enums.js";
3
3
  import type { EvaluationData } from "../index.js";
4
4
  import type { RAGConfig } from "../rag/types.js";
5
5
  import type { AnalyticsData, ToolExecutionEvent, ToolExecutionSummary } from "../types/index.js";
6
- import type { MiddlewareFactoryOptions } from "../types/middlewareTypes.js";
6
+ import type { MiddlewareFactoryOptions, OnFinishCallback, OnErrorCallback, OnChunkCallback } from "../types/middlewareTypes.js";
7
7
  import type { TokenUsage } from "./analytics.js";
8
8
  import type { JsonValue, UnknownRecord } from "./common.js";
9
9
  import type { Content, ImageWithAltText } from "./content.js";
@@ -409,6 +409,12 @@ export type StreamOptions = {
409
409
  * @internal Set by NeuroLink SDK — not typically used directly by consumers.
410
410
  */
411
411
  fileRegistry?: unknown;
412
+ /** Callback invoked when streaming completes successfully. */
413
+ onFinish?: OnFinishCallback;
414
+ /** Callback invoked when streaming encounters an error. */
415
+ onError?: OnErrorCallback;
416
+ /** Callback invoked for each streaming chunk. */
417
+ onChunk?: OnChunkCallback;
412
418
  };
413
419
  /**
414
420
  * Stream function result type - Primary output format for streaming
@@ -258,6 +258,11 @@ export declare function isAbortError(error: unknown): boolean;
258
258
  * Error handler that decides whether to retry based on error type
259
259
  */
260
260
  export declare function isRetriableError(error: Error): boolean;
261
+ /**
262
+ * Determines if an error is likely recoverable (rate limit, timeout, network issues).
263
+ * Useful for deciding whether to retry or fail fast.
264
+ */
265
+ export declare function isRecoverableError(error: Error): boolean;
261
266
  /**
262
267
  * Enhanced error logger that provides structured logging
263
268
  */
@@ -865,6 +865,44 @@ export function isRetriableError(error) {
865
865
  ];
866
866
  return retriablePatterns.some((pattern) => pattern.test(error.message));
867
867
  }
868
+ /**
869
+ * Determines if an error is likely recoverable (rate limit, timeout, network issues).
870
+ * Useful for deciding whether to retry or fail fast.
871
+ */
872
+ export function isRecoverableError(error) {
873
+ // Check NeuroLinkError.retriable first
874
+ const errorWithRetriable = error;
875
+ if ("retriable" in error &&
876
+ typeof errorWithRetriable.retriable === "boolean") {
877
+ return errorWithRetriable.retriable;
878
+ }
879
+ const message = error.message?.toLowerCase() || "";
880
+ // Rate limit errors
881
+ if (message.includes("rate limit") || message.includes("too many requests")) {
882
+ return true;
883
+ }
884
+ if (/\b429\b/.test(message)) {
885
+ return true;
886
+ }
887
+ // Timeout errors
888
+ if (message.includes("timeout") ||
889
+ message.includes("etimedout") ||
890
+ message.includes("timed out")) {
891
+ return true;
892
+ }
893
+ // Network errors
894
+ if (message.includes("econnreset") ||
895
+ message.includes("econnrefused") ||
896
+ message.includes("network") ||
897
+ message.includes("socket")) {
898
+ return true;
899
+ }
900
+ // Server errors (use word boundaries to avoid false matches)
901
+ if (/\b50[0234]\b/.test(message)) {
902
+ return true;
903
+ }
904
+ return false;
905
+ }
868
906
  /**
869
907
  * Enhanced error logger that provides structured logging
870
908
  */
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Lifecycle Middleware
3
+ *
4
+ * Provides onFinish, onError, and onChunk callbacks for observing
5
+ * generation and streaming lifecycle events.
6
+ *
7
+ * This middleware is automatically enabled when lifecycle callbacks
8
+ * (onFinish, onError, onChunk) are passed in GenerateOptions or StreamOptions.
9
+ */
10
+ import type { NeuroLinkMiddleware, LifecycleMiddlewareConfig } from "../../types/middlewareTypes.js";
11
+ export declare function createLifecycleMiddleware(config?: LifecycleMiddlewareConfig): NeuroLinkMiddleware;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Lifecycle Middleware
3
+ *
4
+ * Provides onFinish, onError, and onChunk callbacks for observing
5
+ * generation and streaming lifecycle events.
6
+ *
7
+ * This middleware is automatically enabled when lifecycle callbacks
8
+ * (onFinish, onError, onChunk) are passed in GenerateOptions or StreamOptions.
9
+ */
10
+ import { logger } from "../../utils/logger.js";
11
+ import { isRecoverableError } from "../../utils/errorHandling.js";
12
+ export function createLifecycleMiddleware(config = {}) {
13
+ const metadata = {
14
+ id: "lifecycle",
15
+ name: "Lifecycle Callbacks",
16
+ description: "Provides onFinish, onError, and onChunk callbacks for generation and streaming lifecycle events",
17
+ priority: 110,
18
+ defaultEnabled: false,
19
+ };
20
+ const middleware = {
21
+ wrapGenerate: async ({ doGenerate }) => {
22
+ const startTime = Date.now();
23
+ try {
24
+ const result = await doGenerate();
25
+ if (config.onFinish) {
26
+ try {
27
+ const callbackResult = config.onFinish({
28
+ text: result.text ?? "",
29
+ usage: result.usage
30
+ ? {
31
+ promptTokens: result.usage.promptTokens ?? 0,
32
+ completionTokens: result.usage.completionTokens ?? 0,
33
+ }
34
+ : undefined,
35
+ duration: Date.now() - startTime,
36
+ finishReason: result.finishReason,
37
+ });
38
+ Promise.resolve(callbackResult).catch((e) => {
39
+ logger.warn("[LifecycleMiddleware] onFinish callback error:", e);
40
+ });
41
+ }
42
+ catch (e) {
43
+ logger.warn("[LifecycleMiddleware] onFinish callback error:", e);
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ catch (error) {
49
+ if (config.onError) {
50
+ const err = error instanceof Error ? error : new Error(String(error));
51
+ try {
52
+ const callbackResult = config.onError({
53
+ error: err,
54
+ duration: Date.now() - startTime,
55
+ recoverable: isRecoverableError(err),
56
+ });
57
+ Promise.resolve(callbackResult).catch((e) => {
58
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
59
+ });
60
+ }
61
+ catch (e) {
62
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
63
+ }
64
+ }
65
+ throw error;
66
+ }
67
+ },
68
+ wrapStream: async ({ doStream }) => {
69
+ const startTime = Date.now();
70
+ try {
71
+ const result = await doStream();
72
+ if (!config.onChunk && !config.onFinish && !config.onError) {
73
+ return result;
74
+ }
75
+ let sequenceNumber = 0;
76
+ let accumulatedText = "";
77
+ const transformStream = new TransformStream({
78
+ transform(chunk, controller) {
79
+ try {
80
+ if (chunk.type === "text-delta") {
81
+ accumulatedText += chunk.textDelta;
82
+ }
83
+ if (config.onChunk && chunk.type) {
84
+ try {
85
+ const callbackResult = config.onChunk({
86
+ type: chunk.type,
87
+ textDelta: chunk.type === "text-delta" ? chunk.textDelta : undefined,
88
+ sequenceNumber: sequenceNumber++,
89
+ });
90
+ Promise.resolve(callbackResult).catch((e) => {
91
+ logger.warn("[LifecycleMiddleware] onChunk callback error:", e);
92
+ });
93
+ }
94
+ catch (e) {
95
+ logger.warn("[LifecycleMiddleware] onChunk callback error:", e);
96
+ }
97
+ }
98
+ controller.enqueue(chunk);
99
+ }
100
+ catch (error) {
101
+ if (config.onError) {
102
+ const err = error instanceof Error ? error : new Error(String(error));
103
+ try {
104
+ const callbackResult = config.onError({
105
+ error: err,
106
+ duration: Date.now() - startTime,
107
+ recoverable: isRecoverableError(err),
108
+ });
109
+ Promise.resolve(callbackResult).catch((e) => {
110
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
111
+ });
112
+ }
113
+ catch (e) {
114
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
115
+ }
116
+ }
117
+ throw error;
118
+ }
119
+ },
120
+ flush() {
121
+ if (config.onFinish) {
122
+ try {
123
+ const callbackResult = config.onFinish({
124
+ text: accumulatedText,
125
+ duration: Date.now() - startTime,
126
+ });
127
+ Promise.resolve(callbackResult).catch((e) => {
128
+ logger.warn("[LifecycleMiddleware] onFinish callback error:", e);
129
+ });
130
+ }
131
+ catch (e) {
132
+ logger.warn("[LifecycleMiddleware] onFinish callback error:", e);
133
+ }
134
+ }
135
+ },
136
+ });
137
+ return {
138
+ ...result,
139
+ stream: result.stream.pipeThrough(transformStream),
140
+ };
141
+ }
142
+ catch (error) {
143
+ if (config.onError) {
144
+ const err = error instanceof Error ? error : new Error(String(error));
145
+ try {
146
+ const callbackResult = config.onError({
147
+ error: err,
148
+ duration: Date.now() - startTime,
149
+ recoverable: isRecoverableError(err),
150
+ });
151
+ Promise.resolve(callbackResult).catch((e) => {
152
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
153
+ });
154
+ }
155
+ catch (e) {
156
+ logger.warn("[LifecycleMiddleware] onError callback error:", e);
157
+ }
158
+ }
159
+ throw error;
160
+ }
161
+ },
162
+ };
163
+ return { ...middleware, metadata };
164
+ }
@@ -3,6 +3,7 @@ import { MiddlewareRegistry } from "./registry.js";
3
3
  import { createAnalyticsMiddleware } from "./builtin/analytics.js";
4
4
  import { createGuardrailsMiddleware } from "./builtin/guardrails.js";
5
5
  import { createAutoEvaluationMiddleware } from "./builtin/autoEvaluation.js";
6
+ import { createLifecycleMiddleware } from "./builtin/lifecycle.js";
6
7
  import { logger } from "../utils/logger.js";
7
8
  /**
8
9
  * Middleware factory for creating and applying middleware chains.
@@ -26,6 +27,7 @@ export class MiddlewareFactory {
26
27
  analytics: createAnalyticsMiddleware,
27
28
  guardrails: createGuardrailsMiddleware,
28
29
  autoEvaluation: createAutoEvaluationMiddleware,
30
+ lifecycle: createLifecycleMiddleware,
29
31
  };
30
32
  // Register built-in presets
31
33
  this.registerPreset({
@@ -142,6 +144,7 @@ export class MiddlewareFactory {
142
144
  analytics: createAnalyticsMiddleware,
143
145
  guardrails: createGuardrailsMiddleware,
144
146
  autoEvaluation: createAutoEvaluationMiddleware,
147
+ lifecycle: createLifecycleMiddleware,
145
148
  };
146
149
  logger.debug("Getting creator for middleware ID:", id);
147
150
  return builtInMiddlewareCreators[id];
@@ -9,4 +9,5 @@ import { MiddlewareFactory } from "./factory.js";
9
9
  export type { NeuroLinkMiddleware, MiddlewareConfig, MiddlewareContext, MiddlewareConditions, MiddlewareRegistrationOptions, MiddlewareExecutionResult, MiddlewareChainStats, MiddlewarePreset, MiddlewareFactoryOptions, BuiltInMiddlewareType, } from "../types/middlewareTypes.js";
10
10
  export type { LanguageModelV1Middleware } from "ai";
11
11
  export { MiddlewareFactory };
12
+ export { createLifecycleMiddleware } from "./builtin/lifecycle.js";
12
13
  export default MiddlewareFactory;
@@ -9,5 +9,7 @@
9
9
  import { MiddlewareFactory } from "./factory.js";
10
10
  // Factory for creating and applying middleware chains
11
11
  export { MiddlewareFactory };
12
+ // Built-in middleware creators
13
+ export { createLifecycleMiddleware } from "./builtin/lifecycle.js";
12
14
  // Export the factory as the default export for clean, direct usage
13
15
  export default MiddlewareFactory;
package/dist/neurolink.js CHANGED
@@ -2209,6 +2209,26 @@ Current user's request: ${currentInput}`;
2209
2209
  },
2210
2210
  });
2211
2211
  }
2212
+ // Auto-inject lifecycle middleware when callbacks are provided
2213
+ // (must happen before workflow/PPT early returns so those paths get middleware too)
2214
+ if (options.onFinish || options.onError) {
2215
+ options.middleware = {
2216
+ ...options.middleware,
2217
+ middlewareConfig: {
2218
+ ...options.middleware?.middlewareConfig,
2219
+ lifecycle: {
2220
+ ...options.middleware?.middlewareConfig?.lifecycle,
2221
+ enabled: true,
2222
+ config: {
2223
+ ...options.middleware?.middlewareConfig?.lifecycle
2224
+ ?.config,
2225
+ onFinish: options.onFinish,
2226
+ onError: options.onError,
2227
+ },
2228
+ },
2229
+ },
2230
+ };
2231
+ }
2212
2232
  // Check if workflow is requested
2213
2233
  if (options.workflow || options.workflowConfig) {
2214
2234
  return await this.generateWithWorkflow(options);
@@ -2364,6 +2384,7 @@ Current user's request: ${currentInput}`;
2364
2384
  fileRegistry: this.fileRegistry,
2365
2385
  abortSignal: options.abortSignal,
2366
2386
  skipToolPromptInjection: options.skipToolPromptInjection,
2387
+ middleware: options.middleware,
2367
2388
  };
2368
2389
  // Auto-map top-level sessionId/userId to context for convenience
2369
2390
  // Tests and users may pass sessionId/userId as top-level options
@@ -2400,7 +2421,6 @@ Current user's request: ${currentInput}`;
2400
2421
  toolResults: toolResults.length,
2401
2422
  });
2402
2423
  }
2403
- // Use redesigned generation logic
2404
2424
  const textResult = await this.generateTextInternal(textOptions);
2405
2425
  // Emit generation completion event (NeuroLink format - enhanced with content)
2406
2426
  this.emitter.emit("generation:end", {
@@ -4024,6 +4044,26 @@ Current user's request: ${currentInput}`;
4024
4044
  });
4025
4045
  }
4026
4046
  this.emitStreamStartEvents(options, startTime);
4047
+ // Auto-inject lifecycle middleware when callbacks are provided
4048
+ // (must happen before workflow early return so that path gets middleware too)
4049
+ if (options.onFinish || options.onError || options.onChunk) {
4050
+ options.middleware = {
4051
+ ...options.middleware,
4052
+ middlewareConfig: {
4053
+ ...options.middleware?.middlewareConfig,
4054
+ lifecycle: {
4055
+ ...options.middleware?.middlewareConfig?.lifecycle,
4056
+ enabled: true,
4057
+ config: {
4058
+ ...options.middleware?.middlewareConfig?.lifecycle?.config,
4059
+ onFinish: options.onFinish,
4060
+ onError: options.onError,
4061
+ onChunk: options.onChunk,
4062
+ },
4063
+ },
4064
+ },
4065
+ };
4066
+ }
4027
4067
  // Check if workflow is requested
4028
4068
  if (options.workflow || options.workflowConfig) {
4029
4069
  const result = await this.streamWithWorkflow(options, startTime);
@@ -6,7 +6,7 @@ import type { JsonValue } from "./common.js";
6
6
  import type { Content, ImageWithAltText } from "./content.js";
7
7
  import type { ChatMessage, ConversationMemoryConfig } from "./conversation.js";
8
8
  import type { EvaluationData } from "./evaluation.js";
9
- import type { MiddlewareFactoryOptions } from "./middlewareTypes.js";
9
+ import type { MiddlewareFactoryOptions, OnFinishCallback, OnErrorCallback } from "./middlewareTypes.js";
10
10
  import type { DirectorModeOptions, DirectorSegment, VideoGenerationResult, VideoOutputOptions } from "./multimodal.js";
11
11
  import type { PPTGenerationResult, PPTOutputOptions } from "./pptTypes.js";
12
12
  import type { TTSOptions, TTSResult } from "./ttsTypes.js";
@@ -399,6 +399,12 @@ export type GenerateOptions = {
399
399
  * @internal Set by NeuroLink SDK — not typically used directly by consumers.
400
400
  */
401
401
  fileRegistry?: unknown;
402
+ /** Per-call middleware configuration. */
403
+ middleware?: import("./middlewareTypes.js").MiddlewareFactoryOptions;
404
+ /** Callback invoked when generation completes successfully. */
405
+ onFinish?: OnFinishCallback;
406
+ /** Callback invoked when generation encounters an error. */
407
+ onError?: OnErrorCallback;
402
408
  };
403
409
  /**
404
410
  * Generate function result type - Primary output format
@@ -110,7 +110,7 @@ export type MiddlewareChainStats = {
110
110
  /**
111
111
  * Built-in middleware types
112
112
  */
113
- export type BuiltInMiddlewareType = "analytics" | "guardrails" | "logging" | "caching" | "rateLimit" | "retry" | "timeout" | "autoEvaluation";
113
+ export type BuiltInMiddlewareType = "analytics" | "guardrails" | "logging" | "caching" | "rateLimit" | "retry" | "timeout" | "autoEvaluation" | "lifecycle";
114
114
  /**
115
115
  * Middleware preset configurations
116
116
  */
@@ -233,3 +233,56 @@ export type MiddlewareChainConfig = {
233
233
  timeout?: number;
234
234
  retries?: number;
235
235
  };
236
+ /**
237
+ * Payload delivered to onFinish callbacks after generation or streaming completes.
238
+ */
239
+ export type LifecycleFinishPayload = {
240
+ /** The generated text content */
241
+ text: string;
242
+ /** Token usage from the provider */
243
+ usage?: {
244
+ promptTokens: number;
245
+ completionTokens: number;
246
+ };
247
+ /** Wall-clock duration in milliseconds */
248
+ duration: number;
249
+ /** Why generation stopped */
250
+ finishReason?: string;
251
+ };
252
+ /**
253
+ * Payload delivered to onError callbacks when generation or streaming fails.
254
+ */
255
+ export type LifecycleErrorPayload = {
256
+ /** The error that occurred */
257
+ error: Error;
258
+ /** Wall-clock duration until failure in milliseconds */
259
+ duration: number;
260
+ /** Whether the error is likely recoverable (rate limit, timeout, network) */
261
+ recoverable: boolean;
262
+ };
263
+ /**
264
+ * Payload delivered to onChunk callbacks for each streaming chunk.
265
+ */
266
+ export type LifecycleChunkPayload = {
267
+ /** Chunk type from the AI SDK stream */
268
+ type: string;
269
+ /** Text content for text-delta chunks */
270
+ textDelta?: string;
271
+ /** Zero-based chunk sequence number */
272
+ sequenceNumber: number;
273
+ };
274
+ /** Callback invoked when generation or streaming finishes successfully. */
275
+ export type OnFinishCallback = (payload: LifecycleFinishPayload) => void | Promise<void>;
276
+ /** Callback invoked when generation or streaming encounters an error. */
277
+ export type OnErrorCallback = (payload: LifecycleErrorPayload) => void | Promise<void>;
278
+ /** Callback invoked for each chunk during streaming. */
279
+ export type OnChunkCallback = (payload: LifecycleChunkPayload) => void | Promise<void>;
280
+ /**
281
+ * Configuration for the lifecycle middleware.
282
+ * Pass callbacks to observe generation/streaming lifecycle events.
283
+ */
284
+ export type LifecycleMiddlewareConfig = {
285
+ onFinish?: OnFinishCallback;
286
+ onError?: OnErrorCallback;
287
+ onChunk?: OnChunkCallback;
288
+ };
@@ -3,7 +3,7 @@ import type { AIProviderName } from "../constants/enums.js";
3
3
  import type { EvaluationData } from "../index.js";
4
4
  import type { RAGConfig } from "../rag/types.js";
5
5
  import type { AnalyticsData, ToolExecutionEvent, ToolExecutionSummary } from "../types/index.js";
6
- import type { MiddlewareFactoryOptions } from "../types/middlewareTypes.js";
6
+ import type { MiddlewareFactoryOptions, OnFinishCallback, OnErrorCallback, OnChunkCallback } from "../types/middlewareTypes.js";
7
7
  import type { TokenUsage } from "./analytics.js";
8
8
  import type { JsonValue, UnknownRecord } from "./common.js";
9
9
  import type { Content, ImageWithAltText } from "./content.js";
@@ -409,6 +409,12 @@ export type StreamOptions = {
409
409
  * @internal Set by NeuroLink SDK — not typically used directly by consumers.
410
410
  */
411
411
  fileRegistry?: unknown;
412
+ /** Callback invoked when streaming completes successfully. */
413
+ onFinish?: OnFinishCallback;
414
+ /** Callback invoked when streaming encounters an error. */
415
+ onError?: OnErrorCallback;
416
+ /** Callback invoked for each streaming chunk. */
417
+ onChunk?: OnChunkCallback;
412
418
  };
413
419
  /**
414
420
  * Stream function result type - Primary output format for streaming
@@ -258,6 +258,11 @@ export declare function isAbortError(error: unknown): boolean;
258
258
  * Error handler that decides whether to retry based on error type
259
259
  */
260
260
  export declare function isRetriableError(error: Error): boolean;
261
+ /**
262
+ * Determines if an error is likely recoverable (rate limit, timeout, network issues).
263
+ * Useful for deciding whether to retry or fail fast.
264
+ */
265
+ export declare function isRecoverableError(error: Error): boolean;
261
266
  /**
262
267
  * Enhanced error logger that provides structured logging
263
268
  */
@@ -865,6 +865,44 @@ export function isRetriableError(error) {
865
865
  ];
866
866
  return retriablePatterns.some((pattern) => pattern.test(error.message));
867
867
  }
868
+ /**
869
+ * Determines if an error is likely recoverable (rate limit, timeout, network issues).
870
+ * Useful for deciding whether to retry or fail fast.
871
+ */
872
+ export function isRecoverableError(error) {
873
+ // Check NeuroLinkError.retriable first
874
+ const errorWithRetriable = error;
875
+ if ("retriable" in error &&
876
+ typeof errorWithRetriable.retriable === "boolean") {
877
+ return errorWithRetriable.retriable;
878
+ }
879
+ const message = error.message?.toLowerCase() || "";
880
+ // Rate limit errors
881
+ if (message.includes("rate limit") || message.includes("too many requests")) {
882
+ return true;
883
+ }
884
+ if (/\b429\b/.test(message)) {
885
+ return true;
886
+ }
887
+ // Timeout errors
888
+ if (message.includes("timeout") ||
889
+ message.includes("etimedout") ||
890
+ message.includes("timed out")) {
891
+ return true;
892
+ }
893
+ // Network errors
894
+ if (message.includes("econnreset") ||
895
+ message.includes("econnrefused") ||
896
+ message.includes("network") ||
897
+ message.includes("socket")) {
898
+ return true;
899
+ }
900
+ // Server errors (use word boundaries to avoid false matches)
901
+ if (/\b50[0234]\b/.test(message)) {
902
+ return true;
903
+ }
904
+ return false;
905
+ }
868
906
  /**
869
907
  * Enhanced error logger that provides structured logging
870
908
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.29.1",
3
+ "version": "9.30.0",
4
4
  "description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 13 providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
5
5
  "author": {
6
6
  "name": "Juspay Technologies",
@@ -66,6 +66,7 @@
66
66
  "test:mcp": "npx tsx test/continuous-test-suite-mcp-http.ts",
67
67
  "test:media": "npx tsx test/continuous-test-suite-media-gen.ts",
68
68
  "test:memory": "npx tsx test/continuous-test-suite-memory.ts",
69
+ "test:middleware": "npx tsx test/continuous-test-suite-middleware.ts",
69
70
  "test:observability": "npx tsx test/continuous-test-suite-observability.ts",
70
71
  "test:ppt": "npx tsx test/continuous-test-suite-ppt.ts",
71
72
  "test:providers": "npx tsx test/continuous-test-suite-providers.ts",