UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

170 lines (169 loc) 7.7 kB
/** * Lifecycle Middleware * * Provides onFinish, onError, and onChunk callbacks for observing * generation and streaming lifecycle events. * * This middleware is automatically enabled when lifecycle callbacks * (onFinish, onError, onChunk) are passed in GenerateOptions or StreamOptions. */ import { logger } from "../../utils/logger.js"; import { isRecoverableError } from "../../utils/errorHandling.js"; import { fireOnErrorOnce } from "../../utils/lifecycleCallbacks.js"; /** * Normalize a thrown value to an `Error` while preserving structured * fields (`code`, `status`, `statusCode`, `retryAfter`, `details`, * `cause`, etc.) that downstream retry logic and SDK callers depend on. * * Previously the lifecycle middleware did * `error instanceof Error ? error : new Error(String(error))`, which * silently dropped every custom property on non-Error throws — so a * provider's `{ code: "RATE_LIMITED", retryAfter: 30 }` rejection * surfaced to consumers as a bare `Error("[object Object]")`. */ function normalizeToError(error) { if (error instanceof Error) { return error; } if (typeof error === "object" && error !== null) { const obj = error; const msg = typeof obj.message === "string" ? obj.message : String(error); // Object.assign keeps Error's prototype chain (including the // captured stack from this synthetic Error's construction) while // copying enumerable fields from the original throw. return Object.assign(new Error(msg), obj); } return new Error(String(error)); } export function createLifecycleMiddleware(config = {}) { const metadata = { id: "lifecycle", name: "Lifecycle Callbacks", description: "Provides onFinish, onError, and onChunk callbacks for generation and streaming lifecycle events", priority: 110, defaultEnabled: false, }; const middleware = { specificationVersion: "v3", wrapGenerate: async ({ doGenerate, }) => { const startTime = Date.now(); try { const result = await doGenerate(); if (config.onFinish) { try { const content = result.content ?.map((c) => c.type === "text" ? c.text : "") .join("") ?? ""; const callbackResult = config.onFinish({ text: content, usage: result.usage ? { promptTokens: result.usage.inputTokens?.total ?? 0, completionTokens: result.usage.outputTokens?.total ?? 0, } : undefined, duration: Date.now() - startTime, finishReason: String(result.finishReason ?? ""), }); Promise.resolve(callbackResult).catch((e) => { logger.warn("[LifecycleMiddleware] onFinish callback error:", e); }); } catch (e) { logger.warn("[LifecycleMiddleware] onFinish callback error:", e); } } return result; } catch (error) { const err = normalizeToError(error); // fireOnErrorOnce stamps a Symbol on `err` so the SDK-level catch // in neurolink.ts (and baseProvider.handleProviderError) skips // its own onError fire for the same logical failure. fireOnErrorOnce(config.onError, err, { error: err, duration: Date.now() - startTime, recoverable: isRecoverableError(err), }); // Rethrow the normalized err (not the raw `error`) so the // fired-mark and any preserved structured fields propagate. throw err; } }, wrapStream: async ({ doStream, }) => { const startTime = Date.now(); try { const result = await doStream(); if (!config.onChunk && !config.onFinish && !config.onError) { return result; } let sequenceNumber = 0; let accumulatedText = ""; const transformStream = new TransformStream({ transform(chunk, controller) { try { if (chunk.type === "text-delta") { accumulatedText += chunk.textDelta; } if (config.onChunk && chunk.type) { try { const callbackResult = config.onChunk({ type: chunk.type, textDelta: chunk.type === "text-delta" ? chunk.textDelta : undefined, sequenceNumber: sequenceNumber++, }); Promise.resolve(callbackResult).catch((e) => { logger.warn("[LifecycleMiddleware] onChunk callback error:", e); }); } catch (e) { logger.warn("[LifecycleMiddleware] onChunk callback error:", e); } } controller.enqueue(chunk); } catch (error) { const err = normalizeToError(error); fireOnErrorOnce(config.onError, err, { error: err, duration: Date.now() - startTime, recoverable: isRecoverableError(err), }); throw err; } }, flush() { if (config.onFinish) { try { const callbackResult = config.onFinish({ text: accumulatedText, duration: Date.now() - startTime, }); Promise.resolve(callbackResult).catch((e) => { logger.warn("[LifecycleMiddleware] onFinish callback error:", e); }); } catch (e) { logger.warn("[LifecycleMiddleware] onFinish callback error:", e); } } }, }); return { ...result, stream: result.stream.pipeThrough(transformStream), }; } catch (error) { const err = normalizeToError(error); fireOnErrorOnce(config.onError, err, { error: err, duration: Date.now() - startTime, recoverable: isRecoverableError(err), }); throw err; } }, }; return { ...middleware, metadata }; }