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

1,211 lines 86.4 kB
/** * NeuroLink - Unified AI Interface with Real MCP Tool Integration * * REDESIGNED FALLBACK CHAIN - NO CIRCULAR DEPENDENCIES * Enhanced AI provider system with natural MCP tool access. * Uses real MCP infrastructure for tool discovery and execution. */ import type { CompactionConfig, CompactionResult, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, AuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult, StreamGenerationEndContext } from "./types/index.js"; import { ConversationMemoryManager } from "./core/conversationMemoryManager.js"; import type { RedisConversationMemoryManager } from "./core/redisConversationMemoryManager.js"; import { ExternalServerManager } from "./mcp/externalServerManager.js"; import { MCPToolRegistry } from "./mcp/toolRegistry.js"; import type { DynamicOptions } from "./types/index.js"; import { TaskManager } from "./tasks/taskManager.js"; /** * Curator P2-4 dedup (concurrency-safe): native providers emit * `generation:end` on the shared SDK emitter. We attach a fresh * mutable `dedupContext` object directly to the per-call * `StreamOptions` (under `_streamDedupContext`) so each stream gets * its own instance — concurrent streams have different option objects * and therefore different contexts, so they cannot interfere. * * Native provider emit sites read `options._streamDedupContext` and * flip `.providerEmitted = true` before emitting; the orchestration's * finally block reads the same closed-over reference and skips its * own emit when the flag is set. * * This avoids the AsyncLocalStorage approach which doesn't reliably * propagate through async-generator yield boundaries when iteration * happens from outside the original `run()` scope (e.g. when the * consumer drives `for await of result.stream` after `sdk.stream(...)` * returns). */ export declare const STREAM_DEDUP_CONTEXT_KEY: "_streamDedupContext"; /** * Native providers call this from their `generation:end` emit sites, * passing the same `options` object they received. Safe no-op when * the field isn't set. */ export declare function markStreamProviderEmittedGenerationEnd(options: { _streamDedupContext?: StreamGenerationEndContext; } | undefined): void; /** * Symbol-based brand for cross-module identification without circular imports. * * Provider constructors receive `sdk?: unknown` (the factory layer's * contract). Rather than duck-typing via `"getInMemoryServers" in sdk`, * use `isNeuroLink(value)` from this module to do a brand check — * survives minification AND doesn't rely on method-name stability. */ export declare const NEUROLINK_BRAND: unique symbol; /** * Type-guard for opaque values that should be a {@link NeuroLink} instance. * * Designed for the provider-factory boundary where TS can't carry the type * through `UnknownRecord` without forcing every caller into a circular * dependency. Cheap to call and unaffected by minification. */ export declare function isNeuroLink(value: unknown): value is NeuroLink; export declare class NeuroLink { /** @internal Brand for cross-module identification — see {@link isNeuroLink}. */ readonly [NEUROLINK_BRAND]: true; private mcpInitialized; private mcpSkipped; private mcpInitPromise; private emitter; private _taskManager?; private _taskManagerConfig?; private toolRegistry; private autoDiscoveredServerInfos; private externalServerManager; private toolCache; private readonly toolCacheDuration; private modelAliasConfig?; private lastCompactionMessageCount; /** Extract sessionId from options context for compaction watermark keying */ private getCompactionSessionId; private mcpToolResultCache?; private mcpToolRouter?; private mcpToolBatcher?; private mcpEnhancedDiscovery?; private mcpToolMiddlewares; /** Artifact store for externalized MCP tool outputs (set when strategy=externalize). */ private mcpArtifactStore?; private _disableToolCacheForCurrentRequest; private mcpEnhancementsConfig?; private toolCircuitBreakers; private toolExecutionMetrics; private currentStreamToolExecutions; private toolExecutionHistory; private activeToolExecutions; /** * Helper method to emit tool end event in a consistent way * Used by executeTool in both success and error paths * @param toolName - Name of the tool * @param startTime - Timestamp when tool execution started * @param success - Whether the tool execution was successful * @param result - The result of the tool execution (optional) * @param error - The error if execution failed (optional) */ private emitToolEndEvent; conversationMemory?: ConversationMemoryManager | RedisConversationMemoryManager | null; private conversationMemoryNeedsInit; private conversationMemoryConfig?; private enableOrchestration; private authProvider?; private pendingAuthConfig?; private authInitPromise?; private credentials?; private readonly fallbackConfig; /** * Merge instance-level credentials with per-call credentials. * * Semantics: **deep merge at the provider level.** For each provider key * present in both `this.credentials` and `callCredentials`, the per-call * fields are merged ON TOP of the instance-level fields, so fields not * mentioned in the per-call slice are preserved. * * Example: * ``` * instance: { openai: { apiKey: "key1", baseURL: "url1" } } * per-call: { openai: { apiKey: "key2" } } * merged: { openai: { apiKey: "key2", baseURL: "url1" } } // baseURL preserved * ``` * * Providers present only in one source are carried through unchanged. * Unrelated providers (not overridden in callCredentials) are carried through * from instance credentials unchanged. */ private resolveCredentials; private hitlManager?; private _sessionCostUsd; private fileRegistry; private cachedFileTools; private memoryInstance?; private memorySDKConfig?; /** * Extract and set Langfuse context from options with proper async scoping */ private setLangfuseContextFromOptions; private createMetricsTraceContext; private enforceSessionBudget; private assertInputText; private applyAuthenticatedRequestContext; private applyGenerateLifecycleMiddleware; private applyStreamLifecycleMiddleware; private initializeMemoryConfig; /** * Lazy initialization for memory — called during generate/stream. */ private ensureMemoryReady; /** * Context storage for tool execution * This context will be merged with any runtime context passed by the AI model */ private toolExecutionContext?; /** * Creates a new NeuroLink instance for AI text generation with MCP tool integration. * * @param config - Optional configuration object * @param config.conversationMemory - Configuration for conversation memory features * @param config.conversationMemory.enabled - Whether to enable conversation memory (default: false) * @param config.conversationMemory.maxSessions - Maximum number of concurrent sessions (default: 100) * @param config.conversationMemory.maxTurnsPerSession - Maximum conversation turns per session (default: 50) * @param config.enableOrchestration - Whether to enable smart model orchestration (default: false) * @param config.hitl - Configuration for Human-in-the-Loop safety features * @param config.hitl.enabled - Whether to enable HITL tool confirmation (default: false) * @param config.hitl.dangerousActions - Keywords that trigger confirmation (default: ['delete', 'remove', 'drop']) * @param config.hitl.timeout - Confirmation timeout in milliseconds (default: 30000) * @param config.hitl.allowArgumentModification - Allow users to modify tool parameters (default: true) * @param config.toolRegistry - Optional tool registry instance for advanced use cases (default: new MCPToolRegistry()) * * @example * ```typescript * // Basic usage * const neurolink = new NeuroLink(); * * // With conversation memory * const neurolink = new NeuroLink({ * conversationMemory: { * enabled: true, * maxSessions: 50, * maxTurnsPerSession: 20 * } * }); * * // With orchestration enabled * const neurolink = new NeuroLink({ * enableOrchestration: true * }); * * // With HITL safety features * const neurolink = new NeuroLink({ * hitl: { * enabled: true, * dangerousActions: ['delete', 'remove', 'drop', 'truncate'], * timeout: 30000, * allowArgumentModification: true * } * }); * ``` * * @throws {Error} When provider registry setup fails * @throws {Error} When conversation memory initialization fails (if enabled) * @throws {Error} When external server manager initialization fails * @throws {Error} When HITL configuration is invalid (if enabled) */ private observabilityConfig?; private metricsAggregator; /** * Per-request metrics trace context backed by AsyncLocalStorage. * Safe for concurrent requests on the same SDK instance. * Context is set via metricsTraceContextStorage.run() in generate/stream. */ private get _metricsTraceContext(); constructor(config?: NeurolinkConstructorConfig); /** * TaskManager — scheduled and self-running tasks. * Lazy-initialized on first access. Configurable via constructor `tasks` option. * The actual async initialization (Redis connect, backend start) happens * lazily inside TaskManager on first operation. */ get tasks(): TaskManager; /** * Initialize provider registry with security settings */ private initializeProviderRegistry; /** * Initialize conversation memory if enabled */ private initializeConversationMemory; /** * Initialize HITL (Human-in-the-Loop) if enabled */ private initializeHITL; /** * Initialize MCP enhancement modules (cache, router, batcher, discovery). * Wires standalone MCP modules into the core SDK execution path. */ private initializeMCPEnhancements; /** * Register file reference tools with the MCP tool registry. * * Creates file access tools (list_attached_files, read_file_section, * search_in_file, get_file_preview) bound to the FileReferenceRegistry * and registers them as direct tools so they're available to LLMs. */ private registerFileTools; /** * Register task management tools bound to a TaskManager instance. * Follows the same factory + registry pattern as registerFileTools(). * Called when TaskManager is created (eagerly or lazily via the `tasks` getter). */ private registerTaskTools; /** * Register memory retrieval tools that allow the AI to access * conversation history, including full tool outputs. * Only registered when Redis conversation memory is active. */ private registerMemoryRetrievalTools; /** Format memory context for prompt inclusion */ private formatMemoryContext; /** * Format memory context from multiple users into a labeled block. */ private formatMultiUserMemoryContext; /** * Determine whether memory should be read (retrieved) for this call. * Respects both the global memory SDK config and per-call overrides. */ private shouldReadMemory; /** * Determine whether memory should be written (stored) for this call. * Respects both the global memory SDK config and per-call overrides. */ private shouldWriteMemory; /** * Retrieve condensed memory for a user (and optionally additional users). * Returns the input text enhanced with memory context, or unchanged if no memory. */ private retrieveMemory; /** * Store a conversation turn in memory (non-blocking). * Calls add(userId, content) which internally condenses old + new via LLM. * Supports additional users with per-user prompt and maxWords overrides. */ private storeMemoryInBackground; /** * Set up HITL event forwarding to main emitter */ private setupHITLEventForwarding; /** * Initialize external server manager with event handlers */ private initializeExternalServerManager; /** * Setup event handlers for external server manager */ private setupExternalServerEventHandlers; /** * Initialize Langfuse observability for AI operations tracking */ private initializeLangfuse; /** * Log constructor completion with final state summary */ private logConstructorComplete; /** * Initialize MCP registry with enhanced error handling and resource cleanup * Uses isolated async context to prevent hanging */ private initializeMCP; /** * Actual one-shot MCP initialization logic. Called at most once per * NeuroLink instance lifetime (unless cleanup() resets the flag). */ private performMCPInitializationOnce; /** * Import performance manager with error handling */ private importPerformanceManager; /** * Perform main MCP initialization logic */ private performMCPInitialization; /** * Initialize tool registry with timeout protection */ private initializeToolRegistryInternal; /** * Initialize provider registry */ private initializeProviderRegistryInternal; /** * Register direct tools server */ private registerDirectToolsServerInternal; /** * Load MCP configuration from .mcp-config.json with parallel loading for improved performance */ private loadMCPConfigurationInternal; /** * Log MCP initialization completion */ private logMCPInitComplete; /** * Apply orchestration to determine optimal provider and model * @param options - Original GenerateOptions * @returns Modified options with orchestrated provider marked in context, or empty object if validation fails */ private applyOrchestration; /** * Apply orchestration to determine optimal provider and model for streaming * @param options - Original StreamOptions * @returns Modified options with orchestrated provider marked in context, or empty object if validation fails */ private applyStreamOrchestration; /** * MAIN ENTRY POINT: Enhanced generate method with new function signature * Replaces both generateText and legacy methods */ /** * Extracts the original prompt text from the provided input. * If a string is provided, it returns the string directly. * If a GenerateOptions object is provided, it returns the input text from the object. * @param optionsOrPrompt The prompt input, either as a string or a GenerateOptions object. * @returns The original prompt text as a string. */ private _extractOriginalPrompt; /** * Generate AI content using the best available provider with MCP tool integration. * This is the primary method for text generation with full feature support. * * @param optionsOrPrompt - Either a string prompt or a comprehensive GenerateOptions object * @param optionsOrPrompt.input - Input configuration object * @param optionsOrPrompt.input.text - The text prompt to send to the AI (required) * @param optionsOrPrompt.provider - AI provider to use ('auto', 'openai', 'anthropic', etc.) * @param optionsOrPrompt.model - Specific model to use (e.g., 'gpt-4', 'claude-3-opus') * @param optionsOrPrompt.temperature - Randomness in response (0.0 = deterministic, 2.0 = very random) * @param optionsOrPrompt.maxTokens - Maximum tokens in response * @param optionsOrPrompt.systemPrompt - System message to set AI behavior * @param optionsOrPrompt.disableTools - Whether to disable MCP tool usage * @param optionsOrPrompt.enableAnalytics - Whether to include usage analytics * @param optionsOrPrompt.enableEvaluation - Whether to include response quality evaluation * @param optionsOrPrompt.context - Additional context for the request * @param optionsOrPrompt.evaluationDomain - Domain for specialized evaluation * @param optionsOrPrompt.toolUsageContext - Context for tool usage decisions * * @returns Promise resolving to GenerateResult with content, usage data, and optional analytics * * @example * ```typescript * // Simple usage with string prompt * const result = await neurolink.generate("What is artificial intelligence?"); * console.log(result.content); * * // Advanced usage with options * const result = await neurolink.generate({ * input: { text: "Explain quantum computing" }, * provider: "openai", * model: "gpt-4", * temperature: 0.7, * maxTokens: 500, * enableAnalytics: true, * enableEvaluation: true, * context: { domain: "science", level: "intermediate" } * }); * * // Access analytics and evaluation data * console.log(result.analytics?.usage); * console.log(result.evaluation?.relevance); * ``` * * @throws {Error} When input text is missing or invalid * @throws {Error} When all providers fail to generate content * @throws {Error} When conversation memory operations fail (if enabled) */ /** * Get observability configuration */ getObservabilityConfig(): ObservabilityConfig | undefined; /** * Check if Langfuse telemetry is enabled * Centralized utility to avoid duplication across providers */ isTelemetryEnabled(): boolean; /** * Get comprehensive telemetry status including Langfuse, OTel, and exporter health */ getTelemetryStatus(): { enabled: boolean; langfuse?: { enabled: boolean; baseUrl?: string; environment?: string; }; openTelemetry?: { enabled: boolean; endpoint?: string; serviceName?: string; }; exporters?: Array<{ name: string; enabled: boolean; healthy: boolean; pendingSpans: number; lastExportTime?: string; latencyMs?: number; errors?: string[]; }>; }; /** * Get aggregated observability metrics (latency, tokens, cost, success rate) */ getMetrics(): MetricsSummary; /** * Get all recorded spans */ getSpans(): SpanData[]; /** * Get traces (spans grouped by traceId with parent-child hierarchy) */ getTraces(): TraceView[]; /** * Reset all collected metrics and spans */ resetMetrics(): void; /** * Record a span for metrics tracking */ recordMetricsSpan(span: SpanData): void; /** * Record a memory operation span to both instance and global metrics aggregators. * This ensures memory spans are visible via sdk.getSpans() and getMetricsAggregator().getSpans(). */ private recordMemorySpan; /** * Public method to initialize Langfuse observability * This method can be called externally to ensure Langfuse is properly initialized */ initializeLangfuseObservability(): Promise<void>; /** * Gracefully shutdown NeuroLink and all MCP connections */ shutdown(): Promise<void>; /** * Initialize event listeners that feed span data to MetricsAggregator. * Listens to generation:end, stream:complete, and tool:end events. */ private initializeMetricsListeners; /** * Generate AI response with comprehensive feature support. * * Primary method for AI generation with support for all NeuroLink features: * - Multi-provider support (14+ providers) * - MCP tool integration * - Structured JSON output with Zod schemas * - Conversation memory (Redis or in-memory) * - HITL security workflows * - Middleware execution * - Multimodal inputs (images, PDFs, CSV) * * @category Generation * * @param optionsOrPrompt - Generation options or simple text prompt * @param optionsOrPrompt.input - Input text and optional files * @param optionsOrPrompt.provider - AI provider name (e.g., 'vertex', 'openai', 'anthropic') * @param optionsOrPrompt.model - Model name to use * @param optionsOrPrompt.tools - MCP tools to enable for this generation * @param optionsOrPrompt.schema - Zod schema for structured output validation * @param optionsOrPrompt.temperature - Sampling temperature (0-2, default: 1.0) * @param optionsOrPrompt.maxTokens - Maximum tokens to generate * @param optionsOrPrompt.thinkingConfig - Extended thinking configuration (thinkingLevel: 'minimal'|'low'|'medium'|'high') * @param optionsOrPrompt.context - Context with conversationId and userId for memory * @returns Promise resolving to generation result with content and metadata * * @example Basic text generation * ```typescript * const result = await neurolink.generate({ * input: { text: 'Explain quantum computing' } * }); * console.log(result.content); * ``` * * @example With specific provider * ```typescript * const result = await neurolink.generate({ * input: { text: 'Write a poem' }, * provider: 'anthropic', * model: 'claude-3-opus' * }); * ``` * * @example With MCP tools * ```typescript * const result = await neurolink.generate({ * input: { text: 'Read README.md and summarize it' }, * tools: ['readFile'] * }); * ``` * * @example With structured output * ```typescript * import { z } from 'zod'; * * const schema = z.object({ * name: z.string(), * age: z.number(), * city: z.string() * }); * * const result = await neurolink.generate({ * input: { text: 'Extract person info: John is 30 years old from NYC' }, * schema: schema * }); * // result.structuredData is type-safe! * ``` * * @example With conversation memory * ```typescript * const result = await neurolink.generate({ * input: { text: 'What did we discuss earlier?' }, * context: { * conversationId: 'conv-123', * userId: 'user-456' * } * }); * ``` * * @example With multimodal input * ```typescript * const result = await neurolink.generate({ * input: { * text: 'Describe this image', * images: ['/path/to/image.jpg'] * }, * provider: 'vertex' * }); * ``` * * @throws {Error} When input text is missing or invalid * @throws {Error} When all providers fail to generate content * @throws {Error} When structured output validation fails * @throws {Error} When HITL approval is denied * * @see {@link GenerateOptions} for all available options * @see {@link GenerateResult} for result structure * @see {@link stream} for streaming generation * @since 1.0.0 */ generate(optionsOrPrompt: GenerateOptions | DynamicOptions | string): Promise<GenerateResult>; private fireConsumerOnErrorIfNotFired; /** * Curator P2-3: wraps a generate/stream call with the fallback * orchestration (`providerFallback` callback + `modelChain` walker). * * On a model-access-denied error from the inner call: * 1. Resolve the effective callback (per-call > instance > synthesised * from modelChain) and the effective chain (per-call > instance). * 2. Walk attempts: invoke callback (or pop next chain entry) → emit * `model.fallback` event → re-call inner with the new {provider, * model}. * 3. Stop on first success, on a callback returning null, or after * exhausting the chain (throw the most recent error). */ private runWithFallbackOrchestration; private attemptInner; private executeGenerateWithMetricsContext; private executeGenerateRequest; private prepareGenerateRequest; private maybeHandleEarlyGenerateResult; private runStandardGenerateRequest; private maybeApplyGenerateOrchestration; private prepareGenerateAugmentations; private buildGenerateTextOptions; private finalizeGenerateRequestResult; private emitGenerateErrorEvent; /** * Schedule non-blocking memory storage after generate completes. */ private scheduleGenerateMemoryStorage; /** * Handle PPT generation mode */ private generateWithPPT; /** * Dispatch a music-generation request to the registered music handler * for the provider named in `options.output.music.provider`. */ private generateWithMusic; /** * Dispatch an avatar (lip-sync) request to the registered avatar handler * for the provider named in `options.output.avatar.provider`. */ private generateWithAvatar; /** * Generate with workflow engine integration * Returns both original and processed responses for AB testing */ private generateWithWorkflow; /** * Stream with workflow engine integration * Progressive streaming: yields preliminary response (first model) then final synthesis */ private streamWithWorkflow; /** * BACKWARD COMPATIBILITY: Legacy generateText method * Internally calls generate() and converts result format */ generateText(options: TextGenerationOptions): Promise<TextGenerationResult>; /** * REDESIGNED INTERNAL GENERATION - NO CIRCULAR DEPENDENCIES * * This method implements a clean fallback chain: * 1. Initialize conversation memory if enabled * 2. Inject conversation history into prompt * 3. Try MCP-enhanced generation if available * 4. Fall back to direct provider generation * 5. Store conversation turn for future context */ private generateTextInternal; private executeGenerateTextInternalWithSpan; private initializeGenerateTextInternalContext; private runGenerateTextInternalFlow; private captureOriginalConversationMessagesForRecovery; private finalizeGenerateTextInternalResult; private handleGenerateTextInternalFailure; private tryRecoverGenerateTextOverflow; /** * Log generateTextInternal start with comprehensive analysis */ private logGenerateTextInternalStart; /** * Emit generation start events */ private emitGenerationStartEvents; /** * Initialize conversation memory for generation * Lazily initializes memory if needed from constructor flags */ private initializeConversationMemoryForGeneration; /** * Attempt MCP generation with retry logic */ private attemptMCPGeneration; /** * Perform MCP generation with retry logic */ private performMCPGenerationRetries; /** * Try MCP-enhanced generation (no fallback recursion) */ private tryMCPGeneration; private prepareMCPGenerationContext; private logMCPConversationSummary; private ensureMCPGenerationBudget; private compactMCPConversationForBudget; private generateWithMCPProvider; /** * Direct provider generation (no MCP, no recursion) */ private directProviderGeneration; /** * Create tool-aware system prompt that informs AI about available tools */ /** * Apply per-call tool filtering (whitelist/blacklist) to a ToolInfo array. * Used to filter the tool list before building the system prompt. */ private applyToolInfoFiltering; private createToolAwareSystemPrompt; /** * Execute tools if available through centralized registry * Simplified approach without domain detection - relies on tool registry */ private detectAndExecuteTools; /** * BACKWARD COMPATIBILITY: Legacy streamText method * Internally calls stream() and converts result format */ streamText(prompt: string, options?: Partial<StreamOptions>): Promise<AsyncIterable<string>>; /** * Stream AI-generated content in real-time using the best available provider. * This method provides real-time streaming of AI responses with full MCP tool integration. * * @param options - Stream configuration options * @param options.input - Input configuration object * @param options.input.text - The text prompt to send to the AI (required) * @param options.provider - AI provider to use ('auto', 'openai', 'anthropic', etc.) * @param options.model - Specific model to use (e.g., 'gpt-4', 'claude-3-opus') * @param options.temperature - Randomness in response (0.0 = deterministic, 2.0 = very random) * @param options.maxTokens - Maximum tokens in response * @param options.systemPrompt - System message to set AI behavior * @param options.disableTools - Whether to disable MCP tool usage * @param options.enableAnalytics - Whether to include usage analytics * @param options.enableEvaluation - Whether to include response quality evaluation * @param options.context - Additional context for the request * @param options.evaluationDomain - Domain for specialized evaluation * * @returns Promise resolving to StreamResult with an async iterable stream * * @example * ```typescript * // Basic streaming usage * const result = await neurolink.stream({ * input: { text: "Tell me a story about space exploration" } * }); * * // Consume the stream * for await (const chunk of result.stream) { * process.stdout.write(chunk.content); * } * * // Advanced streaming with options * const result = await neurolink.stream({ * input: { text: "Explain machine learning" }, * provider: "openai", * model: "gpt-4", * temperature: 0.7, * enableAnalytics: true, * context: { domain: "education", audience: "beginners" } * }); * * // Access metadata and analytics * console.log(result.provider); * console.log(result.analytics?.usage); * ``` * * @throws {Error} When input text is missing or invalid * @throws {Error} When all providers fail to generate content * @throws {Error} When conversation memory operations fail (if enabled) */ stream(options: StreamOptions | DynamicOptions): Promise<StreamResult>; /** * Curator P2-3 / Reviewer Finding #2: stream-fallback that also covers * errors thrown during async iteration (e.g. LiteLLM throwing inside * `createLiteLLMTransformedStream`). The standard * `runWithFallbackOrchestration` only catches errors thrown while the * `StreamResult` is being created — once we hand the iterator back to * the caller, errors raised during consumption used to bypass * `providerFallback` / `modelChain`. * * This wrapper runs the orchestration to get an initial StreamResult, * then wraps `result.stream` so that: * - chunks are forwarded transparently while consumption succeeds * - if iteration throws a model-access-denied error AND no chunks * have been yielded yet, we resolve the next fallback target, * emit `model.fallback`, and recurse * - if chunks were already yielded, the error propagates (mid-stream * recovery isn't safe — the consumer has half a response) */ private streamWithIterationFallback; private executeStreamRequest; private validateStreamRequestOptions; private maybeHandleWorkflowStreamRequest; private runStandardStreamRequest; /** * TTS Mode 2 synthesis helper for the stream() pipeline. * * m5 — extracted from runStandardStreamRequest so the surrounding generator * stays under the max-lines-per-function lint budget. Behaviour preserved * exactly: * - When Mode 2 is enabled (`tts.enabled && tts.useAiResponse`) AND the * model produced non-empty content: synthesises one final audio buffer * and returns it as an `audioChunk` for the caller to `yield`. Resolves * `ttsResolver` with the `TTSResult`. * - When Mode 2 is enabled but synthesis fails: logs a warning and resolves * `ttsResolver` with `undefined`. * - When Mode 2 is requested but skipped (empty content / wrong mode): * resolves `ttsResolver` with `undefined` early so callers awaiting * `result.audio` unblock before the surrounding `finally` cleanup * completes (Issue 7 latency micro-opt — the finally block also resolves * defensively, so this is a redundant early signal, not a coverage fix). */ private synthesizeStreamModeTwo; /** * Prepare stream options: initialize memory, MCP, retrieval, orchestration, * Ollama tool auto-disable, factory processing, and tool detection. */ private prepareStreamOptions; /** * Auto-disable tools for Ollama models that don't support them (stream mode). * Prevents overwhelming smaller models with massive tool descriptions in the system message. */ private autoDisableOllamaStreamTools; /** * Set up event listeners for stream event capture (tool calls, HITL, UI components). * Returns the shared event sequence array and a cleanup function to remove all listeners. */ private setupStreamEventListeners; /** * Handle fallback when the primary stream returns 0 chunks. * Yields chunks from a fallback provider and updates metadata accordingly. */ private handleStreamFallback; /** * Store conversation memory after stream consumption is complete (called from finally block). * Handles conversation memory storage in the background. */ private storeStreamConversationMemory; /** * Validate stream input with comprehensive error reporting */ private validateStreamInput; /** * Emit stream start events */ private emitStreamStartEvents; /** * Create MCP stream */ private createMCPStream; /** * Process stream result */ private processStreamResult; /** * Emit stream end events */ private emitStreamEndEvents; /** * Create stream response */ private createStreamResponse; /** * Handle stream error with fallback */ private handleStreamError; /** * Get the EventEmitter instance to listen to NeuroLink events for real-time monitoring and debugging. * This method provides access to the internal event system that emits events during AI generation, * tool execution, streaming, and other operations for comprehensive observability. * * @returns EventEmitter instance that emits various NeuroLink operation events * * @example * ```typescript * // Basic event listening setup * const neurolink = new NeuroLink(); * const emitter = neurolink.getEventEmitter(); * * // Listen to generation events * emitter.on('generation:start', (event) => { * console.log(`Generation started with provider: ${event.provider}`); * console.log(`Started at: ${new Date(event.timestamp)}`); * }); * * emitter.on('generation:end', (event) => { * console.log(`Generation completed in ${event.responseTime}ms`); * console.log(`Tools used: ${event.toolsUsed?.length || 0}`); * }); * * // Listen to streaming events * emitter.on('stream:start', (event) => { * console.log(`Streaming started with provider: ${event.provider}`); * }); * * emitter.on('stream:end', (event) => { * console.log(`Streaming completed in ${event.responseTime}ms`); * if (event.fallback) console.log('Used fallback streaming'); * }); * * // Listen to tool execution events * emitter.on('tool:start', (event) => { * console.log(`Tool execution started: ${event.toolName}`); * }); * * emitter.on('tool:end', (event) => { * console.log(`Tool ${event.toolName} ${event.success ? 'succeeded' : 'failed'}`); * console.log(`Execution time: ${event.responseTime}ms`); * }); * * // Listen to tool registration events * emitter.on('tools-register:start', (event) => { * console.log(`Registering tool: ${event.toolName}`); * }); * * emitter.on('tools-register:end', (event) => { * console.log(`Tool registration ${event.success ? 'succeeded' : 'failed'}: ${event.toolName}`); * }); * * // Listen to external MCP server events * emitter.on('externalMCP:serverConnected', (event) => { * console.log(`External MCP server connected: ${event.serverId}`); * console.log(`Tools available: ${event.toolCount || 0}`); * }); * * emitter.on('externalMCP:serverDisconnected', (event) => { * console.log(`External MCP server disconnected: ${event.serverId}`); * console.log(`Reason: ${event.reason || 'Unknown'}`); * }); * * emitter.on('externalMCP:toolDiscovered', (event) => { * console.log(`New tool discovered: ${event.toolName} from ${event.serverId}`); * }); * * // Advanced usage with error handling * emitter.on('error', (error) => { * console.error('NeuroLink error:', error); * }); * * // Clean up event listeners when done * function cleanup() { * emitter.removeAllListeners(); * } * * process.on('SIGINT', cleanup); * process.on('SIGTERM', cleanup); * ``` * * @example * ```typescript * // Advanced monitoring with metrics collection * const neurolink = new NeuroLink(); * const emitter = neurolink.getEventEmitter(); * const metrics = { * generations: 0, * totalResponseTime: 0, * toolExecutions: 0, * failures: 0 * }; * * // Collect performance metrics * emitter.on('generation:end', (event) => { * metrics.generations++; * metrics.totalResponseTime += event.responseTime; * metrics.toolExecutions += event.toolsUsed?.length || 0; * }); * * emitter.on('tool:end', (event) => { * if (!event.success) { * metrics.failures++; * } * }); * * // Log metrics every 10 seconds * setInterval(() => { * const avgResponseTime = metrics.generations > 0 * ? metrics.totalResponseTime / metrics.generations * : 0; * * console.log('NeuroLink Metrics:', { * totalGenerations: metrics.generations, * averageResponseTime: `${avgResponseTime.toFixed(2)}ms`, * totalToolExecutions: metrics.toolExecutions, * failureRate: `${((metrics.failures / (metrics.toolExecutions || 1)) * 100).toFixed(2)}%` * }); * }, 10000); * ``` * * **Available Events:** * * **Generation Events:** * - `generation:start` - Fired when text generation begins * - `{ provider: string, timestamp: number }` * - `generation:end` - Fired when text generation completes (or fails / is aborted) * - `{ provider: string, responseTime: number, toolsUsed?: string[], timestamp: number, success?: boolean, aborted?: boolean, error?: string }` * - `success` is `false` for both failures and client aborts; `aborted: true` * distinguishes the latter so consumers can route cancellations * differently from real errors. Pipeline B's metrics span maps * `aborted: true` events to `SpanStatus.WARNING` (not ERROR). * * **Streaming Events:** * - `stream:start` - Fired when streaming begins * - `{ provider: string, timestamp: number }` * - `stream:end` - Fired when streaming completes * - `{ provider: string, responseTime: number, fallback?: boolean }` * * **Tool Events:** * - `tool:start` - Fired when tool execution begins * - `{ toolName: string, timestamp: number }` * - `tool:end` - Fired when tool execution completes * - `{ toolName: string, responseTime: number, success: boolean, timestamp: number }` * - `tools-register:start` - Fired when tool registration begins * - `{ toolName: string, timestamp: number }` * - `tools-register:end` - Fired when tool registration completes * - `{ toolName: string, success: boolean, timestamp: number }` * * **External MCP Events:** * - `externalMCP:serverConnected` - Fired when external MCP server connects * - `{ serverId: string, toolCount?: number, timestamp: number }` * - `externalMCP:serverDisconnected` - Fired when external MCP server disconnects * - `{ serverId: string, reason?: string, timestamp: number }` * - `externalMCP:serverFailed` - Fired when external MCP server fails * - `{ serverId: string, error: string, timestamp: number }` * - `externalMCP:toolDiscovered` - Fired when external MCP tool is discovered * - `{ toolName: string, serverId: string, timestamp: number }` * - `externalMCP:toolRemoved` - Fired when external MCP tool is removed * - `{ toolName: string, serverId: string, timestamp: number }` * - `externalMCP:serverAdded` - Fired when external MCP server is added * - `{ serverId: string, config: MCPServerInfo, toolCount: number, timestamp: number }` * - `externalMCP:serverRemoved` - Fired when external MCP server is removed * - `{ serverId: string, timestamp: number }` * * **Error Events:** * - `error` - Fired when an error occurs * - `{ error: Error, context?: object }` * * @throws {Error} This method does not throw errors as it returns the internal EventEmitter * * @since 1.0.0 * @see {@link https://nodejs.org/api/events.html} Node.js EventEmitter documentation * @see {@link NeuroLink.generate} for events related to text generation * @see {@link NeuroLink.stream} for events related to streaming * @see {@link NeuroLink.executeTool} for events related to tool execution */ getEventEmitter(): TypedEventEmitter<NeuroLinkEvents>; /** * Curator P1-1: synchronous credential health check for a single provider. * * Drives a tiny real call against the provider (1-token completion or * `/models` listing depending on provider) to confirm the configured * credentials are valid. Useful at startup so a service can refuse to * boot if its primary provider's credentials are broken instead of * discovering the problem on first user request. * * @example * ```ts * const health = await neurolink.checkCredentials({ provider: "litellm" }); * if (health.status !== "ok") { * throw new Error(`provider not ready: ${health.detail}`); * } * ``` * * @param input - the provider to check * @returns `{ provider, status, detail }`. Possible status values: * - `"ok"` — credentials valid and provider reachable * - `"missing"` — required env / credentials not configured * - `"expired"` — credentials present but rejected (401/403) * - `"denied"` — credentials valid but team not whitelisted for any model * - `"network"` — provider unreachable (timeout, ECONNREFUSED, DNS) * - `"unknown"` — other error; consult `detail` */ checkCredentials(input: { provider: string; model?: string; }): Promise<{ provider: string; status: "ok" | "missing" | "expired" | "denied" | "network" | "unknown"; detail: string; }>; /** * Emit tool start event with execution tracking * @param toolName - Name of the tool being executed * @param input - Input parameters for the tool * @param startTime - Timestamp when execution started * @returns executionId for tracking this specific execution */ emitToolStart(toolName: string, input: unknown, startTime?: number): string; /** * Emit tool end event with execution summary * @param toolName - Name of the tool that finished * @param result - Result from the tool execution * @param error - Error message if execution failed * @param startTime - When execution started * @param endTime - When execution finished * @param executionId - Optional execution ID for tracking */ emitToolEnd(toolName: string, result?: unknown, error?: string, startTime?: number, endTime?: number, executionId?: string): void; /** * Get current tool execution contexts for stream metadata */ getCurrentToolExecutions(): ToolExecutionContext[]; /** * Get tool execution history */ getToolExecutionHistory(): ToolExecutionSummary[]; /** * Clear current stream tool executions (called at stream start) */ clearCurrentStreamExecutions(): void; /** * Register a custom tool that will be available to all AI providers * @param name - Unique name for the tool * @param tool - Tool in MCPExecutableTool format (unified MCP protocol type) */ registerTool(name: string, tool: MCPExecutableTool, options?: ToolRegistrationOptions): void; /** * Set the context that will be passed to tools during execution * This context will be merged with any runtime context passed by the AI model * @param context - Context object containing session info, tokens, shop data, etc. */ setToolContext(context: Record<string, unknown>): void; /** * Get the current tool execution context * @returns Current context or undefined if not set */ getToolContext(): Record<string, unknown> | undefined; /** * Clear the tool execution context */ clearToolContext(): void; /** * Register multiple tools at once - Supports both object and array formats * @param tools - Object mapping tool names to MCPExecutableTool format OR Array of tools with names * * Object format (existing): { toolName: MCPExecutableTool, ... } * Array format (Lighthouse compatible): [{ name: string, tool: MCPExecutableTool }, ...] */ registerTools(tools: Record<string, MCPExecutableTool> | Array<{ name: string; tool: MCPExecutableTool; }>): void; /** * Unregister a custom tool * @param name - Name of the tool to remove * @returns true if the tool was removed, false if it didn't exist */ unregisterTool(name: string): boolean; /** * Register a global tool middleware that runs on every tool execution. * Middleware receives the tool, params, context, and a next() function. * @param middleware - The middleware function to register * @returns this (for chaining) */ useToolMiddleware(middleware: import("./types/index.js").ToolMiddleware): this; /** * Get all registered tool middlewares */ getToolMiddlewares(): import("./types/index.js").ToolMiddleware[]; /** * Flush any pending batched tool calls immediately */ flushToolBatch(): Promise<void>; /** * Get the current MCP enhancements configuration */ getMCPEnhancementsConfig(): MCPEnhancementsConfig | undefined; /** * Update agentic loop report metadata for a conversation session. * Upserts a report entry by reportId — updates existing or adds new. * Only supported when using Redis conversation memory. * * @param sessionId The session identifier * @param report The agentic loop report metadata to upsert * @param userId Optional user identifier * @throws Error if conversation memory is not initialized or is not Redis-backed * * @example * ```typescript * await neurolink.updateAgenticLoopReport("session-123", { * reportId: "report-abc", * reportType: "META", * reportStatus: "INPROGRESS", * }); * ``` */ updateAgenticLoopReport(sessionId: string, report: import("./types/index.js").AgenticLoopReportMetadata, userId?: string): Promise<void>; /** * Get all registered custom tools * @returns Map of tool names to MCPExecutableTool format */ getCustomTools(): Map<string, MCPExecutableTool>; /** * Add an in-memory MCP server (from git diff) * Allows registration of pre-instantiated server objects * @param serverId - Unique identifier for the server * @param serverInfo - Server configuration */ addInMemoryMCPServer(serverId: string, serverInfo: MCPServerInfo): Promise<void>; /** * Get all registered in-memory servers as a Map for ID-based lookup. * * This method is primarily used when you need O(1) lookup by server ID, * such as in `testMCPServer()` for checking if a specific server exists. * * @returns Map of server IDs to MCPServerInfo * @see {@link getInMemoryServerInfos} for array-based access (useful for iteration/spreading) */ getInMemoryServers(): Map<string, MCPServerInfo>; /