@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
TypeScript
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[];
}