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

412 lines (411 loc) 18.5 kB
import type { LanguageModel, ModelMessage, Tool, ToolCallRepairFunction, ToolSet } from "ai"; import type { AIProviderName } from "../constants/enums.js"; import type { EvaluationData } from "../index.js"; import type { NeuroLink } from "../neurolink.js"; import type { UnknownRecord, MiddlewareFactoryOptions, StreamOptions, StreamResult, AIProvider, AnalyticsData, EnhancedGenerateResult, TextGenerationOptions, TextGenerationResult, ValidationSchema } from "../types/index.js"; import { TelemetryHandler } from "./modules/TelemetryHandler.js"; /** * Abstract base class for all AI providers * Tools are integrated as first-class citizens - always available by default */ export declare abstract class BaseProvider implements AIProvider { protected modelName: string; protected readonly providerName: AIProviderName; protected readonly defaultTimeout: number; protected middlewareOptions?: MiddlewareFactoryOptions; protected readonly directTools: {}; protected mcpTools?: Record<string, Tool>; protected customTools?: Map<string, unknown>; protected toolExecutor?: (toolName: string, params: unknown) => Promise<unknown>; protected sessionId?: string; protected userId?: string; protected neurolink?: NeuroLink; /** @internal Trace context propagated from NeuroLink SDK for span hierarchy */ protected _traceContext: { traceId: string; parentSpanId: string; } | null; setTraceContext(ctx: { traceId: string; parentSpanId: string; } | null): void; private messageBuilder; private streamHandler; private generationHandler; protected telemetryHandler: TelemetryHandler; private utilities; private readonly toolsManager; constructor(modelName?: string, providerName?: AIProviderName, neurolink?: NeuroLink, middleware?: MiddlewareFactoryOptions); /** * Update modelName and rebuild composition handlers with the new value. * * Auto-discovery providers (lm-studio, llamacpp) call this once they have * resolved the loaded model from `/v1/models`. Without this, handlers * (TelemetryHandler, MessageBuilder, ...) keep the pre-discovery name and * pricing / span / log metadata reports the stale value. */ protected refreshHandlersForModel(model: string): void; /** * Check if this provider supports tool/function calling * Override in subclasses to disable tools for specific providers or models * @returns true by default, providers can override to return false */ supportsTools(): boolean; /** * Primary streaming method - implements AIProvider interface * When tools are involved, falls back to generate() with synthetic streaming */ stream(optionsOrPrompt: StreamOptions | string, analysisSchema?: ValidationSchema): Promise<StreamResult>; /** * Wrap a StreamResult with consumer-facing lifecycle callbacks. * * `options.onChunk`, `options.onFinish`, `options.onError` are translated * by NeuroLink.applyStreamLifecycleMiddleware() into * `options.middleware.middlewareConfig.lifecycle.config`. The AI SDK's * lifecycle middleware only sees these via the wrapped LanguageModel — * which is bypassed by providers that stream via raw HTTP fetch (Ollama * over /api/chat, custom OpenAI-compatible servers, etc). Wrapping the * user-facing stream here ensures the callbacks fire regardless of the * underlying transport. */ private wrapStreamWithLifecycleCallbacks; /** * Fire the consumer-supplied onError callback before throwing. Used in * error branches inside stream() that re-throw without emitting any * stream chunks (which would otherwise hide the failure from a caller * that supplied `onError`). */ private fireLifecycleErrorCallback; /** * Execute fake streaming - extracted method for reusability */ private executeFakeStreaming; /** * Apply per-call tool filtering (whitelist/blacklist) to a tools record. * If toolFilter is set, only tools whose names are in the list are kept. * If excludeTools is set, matching tools are removed. excludeTools is applied after toolFilter. */ private applyToolFiltering; /** * Prepare generation context including tools and model */ private prepareGenerationContext; /** * Get merged tools for streaming: combines base tools (MCP/built-in) with * user-provided tools (e.g., RAG tools passed via options.tools). * * This is the canonical tool-merge pattern for executeStream() implementations. * All providers should call this instead of getAllTools() directly. */ protected getToolsForStream(options: StreamOptions | TextGenerationOptions): Promise<Record<string, Tool>>; /** * Build messages array for generation - delegated to MessageBuilder */ private buildMessages; /** * Build messages array for streaming operations - delegated to MessageBuilder * This is a protected helper method that providers can use to build messages * with automatic multimodal detection, eliminating code duplication * * @param options - Stream options or text generation options * @returns Promise resolving to ModelMessage array ready for AI SDK */ protected buildMessagesForStream(options: StreamOptions | TextGenerationOptions): Promise<ModelMessage[]>; /** * Execute the generation with AI SDK - delegated to GenerationHandler */ private executeGeneration; /** * Log generation completion information - delegated to GenerationHandler */ private logGenerationComplete; /** * Record performance metrics - delegated to TelemetryHandler */ private recordPerformanceMetrics; /** * Extract tool information from generation result - delegated to GenerationHandler */ private extractToolInformation; /** * Format the enhanced result - delegated to GenerationHandler */ private formatEnhancedResult; /** * Analyze AI response structure and log detailed debugging information - delegated to GenerationHandler */ private analyzeAIResponse; /** * Text generation method - implements AIProvider interface * Tools are always available unless explicitly disabled * * Supports Text-to-Speech (TTS) audio generation in two modes: * 1. Direct synthesis (default): TTS synthesizes the input text without AI generation * 2. AI response synthesis: TTS synthesizes the AI-generated response after generation * * When TTS is enabled with useAiResponse=false (default), the method returns early with * only the audio result, skipping AI generation entirely for optimal performance. * * When TTS is enabled with useAiResponse=true, the method performs full AI generation * and then synthesizes the AI response to audio. * * @param optionsOrPrompt - Generation options or prompt string * @param _analysisSchema - Optional analysis schema (not used) * @returns Enhanced result with optional audio field containing TTSResult * * IMPLEMENTATION NOTE: Uses streamText() under the hood and accumulates results * for consistency and better performance */ generate(optionsOrPrompt: TextGenerationOptions | string, _analysisSchema?: ValidationSchema): Promise<EnhancedGenerateResult | null>; /** * Alias for generate method - implements AIProvider interface */ gen(optionsOrPrompt: TextGenerationOptions | string, analysisSchema?: ValidationSchema): Promise<EnhancedGenerateResult | null>; private runGenerateInActiveContext; protected handleDirectTTSSynthesis(options: TextGenerationOptions, startTime: number): Promise<EnhancedGenerateResult>; private handleVideoFrameGeneration; private executeStandardGenerateFlow; protected synthesizeAIResponseIfNeeded(enhancedResult: EnhancedGenerateResult, options: TextGenerationOptions): Promise<EnhancedGenerateResult>; /** * BACKWARD COMPATIBILITY: Legacy generateText method * Converts EnhancedGenerateResult to TextGenerationResult format * Ensures existing scripts using createAIProvider().generateText() continue to work */ generateText(options: TextGenerationOptions): Promise<TextGenerationResult>; /** * Generate embeddings for text * * This is a default implementation that throws an error. * Providers that support embeddings (OpenAI, Google Vertex, Amazon Bedrock) * should override this method with their specific implementation. * * @param text - The text to embed * @param _modelName - Optional embedding model name (provider-specific) * @returns Promise resolving to the embedding vector (array of numbers) * @throws Error if the provider does not support embeddings * * @example * ```typescript * const provider = await ProviderFactory.createProvider('openai', 'text-embedding-3-small'); * const embedding = await provider.embed('Hello world'); * console.log(embedding); // [0.123, -0.456, ...] * ``` */ embed(text: string, _modelName?: string): Promise<number[]>; /** * Generate embeddings for multiple texts in a single batch * * This is a default implementation that throws an error. * Providers that support embeddings should override this method. * The AI SDK's embedMany automatically handles chunking for models with batch limits. * * @param texts - The texts to embed * @param _modelName - Optional embedding model name (provider-specific) * @returns Promise resolving to an array of embedding vectors * @throws Error if the provider does not support embeddings */ embedMany(texts: string[], _modelName?: string): Promise<number[][]>; /** * Get the default embedding model for this provider * * Override in subclasses to provide provider-specific defaults. * Returns undefined for providers that don't support embeddings. * * @returns The default embedding model name, or undefined if not supported */ protected getDefaultEmbeddingModel(): string | undefined; /** * Create an `experimental_repairToolCall` handler for streamText/generateText. * Dynamically reads the tool's JSON schema to repair wrong names and params. * Returns undefined when repair is disabled via options. */ protected getToolCallRepairFn(options?: StreamOptions | TextGenerationOptions): ToolCallRepairFunction<ToolSet> | undefined; /** * Provider-specific streaming implementation (only used when tools are disabled) */ protected abstract executeStream(options: StreamOptions, analysisSchema?: ValidationSchema): Promise<StreamResult>; /** * Get the provider name */ protected abstract getProviderName(): AIProviderName; /** * Get the default model for this provider */ protected abstract getDefaultModel(): string; /** * REQUIRED: Every provider MUST implement this method * Returns the Vercel AI SDK model instance for this provider */ protected abstract getAISDKModel(): LanguageModel | Promise<LanguageModel>; /** * Get AI SDK model with middleware applied * This method wraps the base model with any configured middleware * TODO: Implement global level middlewares that can be used */ protected getAISDKModelWithMiddleware(options?: TextGenerationOptions | StreamOptions): Promise<LanguageModel>; /** * Extract middleware options - delegated to Utilities */ private extractMiddlewareOptions; /** * Check if a schema is a Zod schema - delegated to Utilities */ private isZodSchema; /** * Convert tool execution result - delegated to Utilities */ private convertToolResult; /** * Fix JSON Schema for OpenAI strict mode - delegated to Utilities */ private fixSchemaForOpenAIStrictMode; /** * Get all available tools - delegated to ToolsManager */ protected getAllTools(): Promise<Record<string, Tool>>; /** * Calculate actual cost - delegated to TelemetryHandler */ private calculateActualCost; /** * Create a permissive Zod schema - delegated to Utilities */ private createPermissiveZodSchema; /** * Set session context for MCP tools - delegated to ToolsManager */ setSessionContext(sessionId?: string, userId?: string): void; /** * Provider-specific error formatting. * Subclasses implement this to produce human-readable error messages * (e.g., "❌ Google Vertex AI Provider Error\n\n..."). */ protected abstract formatProviderError(error: unknown): Error; /** * Handle provider errors with abort passthrough. * AbortErrors are never wrapped — they must propagate with their * original identity so that isAbortError() can detect them in * retry/fallback loops (directProviderGeneration, performMCPGenerationRetries). */ protected handleProviderError(error: unknown): Error; /** * Image generation method. Providers that support it should override this. * By default, it throws an error indicating that the functionality is not supported. * @param _options The generation options. * @returns A promise that resolves to the generation result. */ protected executeImageGeneration(_options: TextGenerationOptions): Promise<EnhancedGenerateResult>; /** * Execute operation with timeout and proper cleanup * Consolidates identical timeout handling from 8/10 providers */ protected executeWithTimeout<T>(operation: () => Promise<T>, options: { timeout?: number | string; operationType?: string; }): Promise<T>; /** * Validate stream options - delegated to StreamHandler */ protected validateStreamOptions(options: StreamOptions): void; /** * Create text stream transformation - delegated to StreamHandler. * Reviewer follow-up: forwards the optional `getUnderlyingError` * callback so providers can capture upstream errors via * `streamText`'s `onError` and have them flow into the * NoOutputGeneratedError sentinel's `providerError` / * `modelResponseRaw`. */ protected createTextStream(result: { textStream: AsyncIterable<string>; finishReason?: Promise<unknown> | unknown; totalUsage?: Promise<unknown> | unknown; }, getUnderlyingError?: () => unknown): AsyncGenerator<{ content: string; } | import("../types/index.js").StreamNoOutputSentinel>; /** * Create standardized stream result - delegated to StreamHandler */ protected createStreamResult(stream: AsyncGenerator<{ content: string; }>, additionalProps?: Partial<StreamResult>): StreamResult; /** * Create stream analytics - delegated to StreamHandler */ protected createStreamAnalytics(result: UnknownRecord, startTime: number, options: StreamOptions): Promise<UnknownRecord | undefined>; /** * Handle common error patterns - delegated to Utilities */ protected handleCommonErrors(error: unknown): Error | null; /** * Set up tool executor - delegated to ToolsManager * @param sdk - The NeuroLinkSDK instance for tool execution * @param functionTag - Function name for logging */ setupToolExecutor(sdk: { customTools: Map<string, unknown>; executeTool: (toolName: string, params: unknown) => Promise<unknown>; }, functionTag: string): void; /** * Normalize text generation options - delegated to Utilities */ protected normalizeTextOptions(optionsOrPrompt: TextGenerationOptions | string): TextGenerationOptions; /** * Normalize stream options - delegated to Utilities */ protected normalizeStreamOptions(optionsOrPrompt: StreamOptions | string): StreamOptions; protected enhanceResult(result: EnhancedGenerateResult, options: TextGenerationOptions, startTime: number): Promise<EnhancedGenerateResult>; /** * Handle video generation mode * * Generates video from input image + text prompt using Vertex AI Veo 3.1. * * @param options - Text generation options with video configuration * @param startTime - Generation start timestamp for metrics * @returns Enhanced result with video data * * @example * ```typescript * const result = await provider.generate({ * input: { text: "Product showcase", images: [imageBuffer] }, * output: { mode: "video", video: { resolution: "1080p" } } * }); * // result.video contains the generated video * ``` */ protected handleVideoGeneration(options: TextGenerationOptions, startTime: number): Promise<EnhancedGenerateResult>; /** * Create analytics - delegated to TelemetryHandler */ protected createAnalytics(result: EnhancedGenerateResult, responseTime: number, options: TextGenerationOptions): Promise<AnalyticsData>; /** * Create evaluation - delegated to TelemetryHandler */ protected createEvaluation(result: EnhancedGenerateResult, options: TextGenerationOptions): Promise<EvaluationData>; /** * Validate text generation options - delegated to Utilities */ protected validateOptions(options: TextGenerationOptions): void; /** * Get provider information - delegated to Utilities */ protected getProviderInfo(): { provider: string; model: string; }; /** * Get timeout value in milliseconds - delegated to Utilities */ getTimeout(options: TextGenerationOptions | StreamOptions): number; /** * Check if tool executions should be stored and handle storage */ protected handleToolExecutionStorage(toolCalls: unknown[], toolResults: unknown[], options: TextGenerationOptions | StreamOptions, currentTime: Date): Promise<void>; /** * Utility method to chunk large prompts into smaller pieces * @param prompt The prompt to chunk * @param maxChunkSize Maximum size per chunk (default: 900,000 characters) * @param overlap Overlap between chunks to maintain context (default: 100 characters) * @returns Array of prompt chunks */ static chunkPrompt(prompt: string, maxChunkSize?: number, overlap?: number): string[]; }