@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
503 lines (502 loc) • 16 kB
TypeScript
/**
* Observability Configuration Types
* These configs are passed from the parent application (e.g., Lighthouse)
* to enable telemetry and observability features in Neurolink SDK
*/
import type { AttributeValue } from "@opentelemetry/api";
import type { SpanData } from "./span.js";
/**
* Trace name format for Langfuse traces
*
* Controls how userId and operationName are combined to form the trace name.
* Can be a predefined format string or a custom function.
*
* @example
* // Predefined formats:
* "userId:operationName" → "user@email.com:ai.streamText"
* "operationName:userId" → "ai.streamText:user@email.com"
* "operationName" → "ai.streamText"
* "userId" → "user@email.com" (legacy)
*
* @example
* // Custom function:
* (ctx) => `[${ctx.operationName}] ${ctx.userId}`
* // → "[ai.streamText] user@email.com"
*/
export type TraceNameFormat = "userId:operationName" | "operationName:userId" | "operationName" | "userId" | ((context: {
userId?: string;
operationName?: string;
}) => string);
/**
* Standard GenAI semantic convention attributes from OpenTelemetry
* These are the attributes that Vercel AI SDK's experimental_telemetry creates
* @see https://opentelemetry.io/docs/specs/semconv/gen-ai/
*/
export type LangfuseSpanAttributes = {
"gen_ai.system"?: string;
"gen_ai.request.model"?: string;
"gen_ai.response.model"?: string;
"gen_ai.request.max_tokens"?: number;
"gen_ai.request.temperature"?: number;
"gen_ai.request.top_p"?: number;
"gen_ai.usage.input_tokens"?: number;
"gen_ai.usage.output_tokens"?: number;
"gen_ai.usage.total_tokens"?: number;
"gen_ai.response.finish_reasons"?: string[];
"gen_ai.prompt"?: string;
"gen_ai.completion"?: string;
"ai.model.id"?: string;
"ai.model.provider"?: string;
"ai.operationId"?: string;
"ai.telemetry.functionId"?: string;
"ai.finishReason"?: string;
"ai.usage.promptTokens"?: number;
"ai.usage.completionTokens"?: number;
[key: string]: AttributeValue | undefined;
};
/**
* Langfuse observability configuration
*/
export type LangfuseConfig = {
/** Whether Langfuse is enabled */
enabled: boolean;
/** Langfuse public key */
publicKey: string;
/**
* Langfuse secret key
* @sensitive
* WARNING: This is a sensitive credential. Handle securely.
* Do NOT log, expose, or share this key. Follow best practices for secret management.
*/
secretKey: string;
/** Langfuse base URL (default: https://cloud.langfuse.com) */
baseUrl?: string;
/** Environment name (e.g., dev, staging, prod) */
environment?: string;
/** Release/version identifier */
release?: string;
/** Optional default user id to attach to spans */
userId?: string;
/** Optional default session id to attach to spans */
sessionId?: string;
/**
* If true, NeuroLink will NOT create or register its own TracerProvider.
* Instead, it will only create the LangfuseSpanProcessor and ContextEnricher,
* which the parent application must add to its own TracerProvider.
*
* Use this when your application already has OpenTelemetry instrumentation.
*
* @default false
*/
useExternalTracerProvider?: boolean;
/**
* If true, NeuroLink will automatically detect if a TracerProvider is already
* registered globally and skip its own registration to avoid conflicts.
*
* This is a convenience option that combines well with useExternalTracerProvider.
*
* @default false
*/
autoDetectExternalProvider?: boolean;
/**
* If true, NeuroLink will NOT register its own LangfuseSpanProcessor with the
* global TracerProvider when using external provider mode. Only the ContextEnricher
* will be registered. Use this when the host application already registers a
* LangfuseSpanProcessor (e.g., via a DeferredSpanProcessor) to prevent duplicate
* trace exports to Langfuse.
*
* @default false
*/
skipLangfuseSpanProcessor?: boolean;
/**
* Enable auto-detection of operation names from span names.
*
* When true (default), AI operation spans (ai.streamText, ai.generateText, etc.)
* will have their operation name automatically extracted and included in the
* trace name.
*
* @default true
*
* @example
* // With auto-detection enabled (default):
* // Span "ai.streamText" + userId "user@email.com"
* // → Trace name: "user@email.com:ai.streamText"
*
* @example
* // With auto-detection disabled:
* // → Trace name: "user@email.com" (legacy behavior)
*/
autoDetectOperationName?: boolean;
/**
* Format for trace names in Langfuse.
*
* Controls how userId and operationName are combined to form the trace name.
* Can be a predefined format string or a custom function for full control.
*
* @default "userId:operationName"
*
* @example
* // Predefined formats:
* traceNameFormat: "userId:operationName" // "user@email.com:ai.streamText"
* traceNameFormat: "operationName:userId" // "ai.streamText:user@email.com"
* traceNameFormat: "operationName" // "ai.streamText"
* traceNameFormat: "userId" // "user@email.com" (legacy)
*
* @example
* // Custom function:
* traceNameFormat: (ctx) => `[${ctx.operationName || 'unknown'}] ${ctx.userId}`
* // → "[ai.streamText] user@email.com"
*/
traceNameFormat?: TraceNameFormat;
};
/**
* OpenTelemetry configuration
*/
export type OpenTelemetryConfig = {
/** Whether OpenTelemetry is enabled */
enabled: boolean;
/** OTLP endpoint URL */
endpoint?: string;
/** Service name for traces */
serviceName?: string;
/** Service version */
serviceVersion?: string;
};
/**
* Complete observability configuration for Neurolink SDK
*/
export type ObservabilityConfig = {
/** Langfuse configuration */
langfuse?: LangfuseConfig;
/** OpenTelemetry configuration */
openTelemetry?: OpenTelemetryConfig;
};
/**
* Retry policy type for observability exporters.
*/
export type RetryPolicy = {
/** Policy name for identification */
readonly name: string;
/** Decide whether to retry */
shouldRetry(context: RetryContext): RetryDecision;
/** Maximum attempts allowed */
readonly maxAttempts: number;
/** Maximum total time allowed for retries */
readonly maxTotalTimeMs: number;
};
/**
* Sampler type for controlling which spans are exported.
*/
export type Sampler = {
/** Sampler name for identification */
readonly name: string;
/** Determine if a span should be sampled */
shouldSample(span: SpanData): boolean;
/** Get sampling decision description */
getDescription(): string;
};
/**
* Span processor type for composable span processing pipelines.
*/
export type SpanProcessor = {
/** Processor name for identification */
readonly name: string;
/** Process a span before export, returns null to drop the span */
process(span: SpanData): SpanData | null;
/** Optional async processing (for external lookups, etc.) */
processAsync?(span: SpanData): Promise<SpanData | null>;
/** Shutdown the processor (cleanup resources) */
shutdown?(): Promise<void>;
};
/**
* Hierarchical trace view grouping related spans
*/
export type TraceView = {
/** Trace identifier shared by all spans in this trace */
traceId: string;
/** The root/parent span of this trace */
rootSpan: SpanData;
/** Child spans linked to the root */
childSpans: SpanData[];
/** Total duration from first to last span */
totalDurationMs: number;
/** Total number of spans in this trace */
spanCount: number;
/** Overall trace status */
status: "ok" | "error" | "partial";
};
/**
* Extended context for Langfuse spans.
* Supports all Langfuse trace attributes for rich observability.
*/
export type LangfuseContext = {
userId?: string | null;
sessionId?: string | null;
/** Conversation/thread identifier for grouping related traces */
conversationId?: string | null;
/** Request identifier for correlating with application logs */
requestId?: string | null;
/** Custom trace name for better organization in Langfuse UI */
traceName?: string | null;
/** Custom metadata to attach to spans */
metadata?: Record<string, unknown> | null;
/**
* Explicit operation name (e.g., "ai.streamText", "chat", "embeddings").
* If set, overrides auto-detection from the span name.
*/
operationName?: string | null;
/**
* Override global autoDetectOperationName setting for this context.
* When undefined, uses the global setting (defaults to true).
*/
autoDetectOperationName?: boolean;
/**
* Custom attributes to set on all spans within this context.
* These attributes are propagated to every span created within the
* AsyncLocalStorage context.
*/
customAttributes?: Record<string, string | number | boolean>;
};
/**
* Latency statistics with percentile calculations
*/
export type LatencyStats = {
/** Minimum latency in milliseconds */
min: number;
/** Maximum latency in milliseconds */
max: number;
/** Mean/average latency in milliseconds */
mean: number;
/** Median latency (p50) in milliseconds */
median: number;
/** 50th percentile latency in milliseconds */
p50: number;
/** 75th percentile latency in milliseconds */
p75: number;
/** 90th percentile latency in milliseconds */
p90: number;
/** 95th percentile latency in milliseconds */
p95: number;
/** 99th percentile latency in milliseconds */
p99: number;
/** Standard deviation in milliseconds */
stdDev: number;
/** Total number of samples */
count: number;
};
/**
* Cost breakdown by provider
*/
export type ProviderCostStats = {
provider: string;
totalCost: number;
requestCount: number;
avgCostPerRequest: number;
inputCost: number;
outputCost: number;
};
/**
* Cost breakdown by model
*/
export type ModelCostStats = {
model: string;
provider: string;
totalCost: number;
requestCount: number;
avgCostPerRequest: number;
inputTokens: number;
outputTokens: number;
inputCost: number;
outputCost: number;
};
/**
* Aggregated metrics summary
*/
export type MetricsSummary = {
/** Total number of spans tracked */
totalSpans: number;
/** Number of successful spans */
successfulSpans: number;
/** Number of failed spans */
failedSpans: number;
/** Overall success rate (0-1) */
successRate: number;
/** Latency statistics */
latency: LatencyStats;
/** Token usage statistics */
tokens: TokenUsageStats;
/** Cost by provider */
costByProvider: ProviderCostStats[];
/** Cost by model */
costByModel: ModelCostStats[];
/** Total cost across all providers */
totalCost: number;
/** Span count by type */
spansByType: Record<string, number>;
/** Timestamp of first span */
firstSpanTime?: Date;
/** Timestamp of last span */
lastSpanTime?: Date;
/** Tracking duration in milliseconds */
trackingDurationMs?: number;
};
/**
* Result of a retry decision
*/
export type RetryDecision = {
/** Whether to retry */
shouldRetry: boolean;
/** Delay before retry in milliseconds */
delayMs: number;
/** Reason for the decision */
reason: string;
};
/**
* Context for retry decision making
*/
export type RetryContext = {
/** Current attempt number (0-indexed) */
attempt: number;
/** The error that triggered the retry */
error: Error;
/** Total elapsed time since first attempt */
elapsedMs: number;
/** Operation name for logging */
operationName: string;
/** Additional metadata */
metadata?: Record<string, unknown>;
};
/**
* Token usage statistics by provider
*/
export type ProviderTokenStats = {
provider: string;
inputTokens: number;
outputTokens: number;
totalTokens: number;
cost: number;
requestCount: number;
};
/**
* Token usage statistics by model
*/
export type ModelTokenStats = {
model: string;
provider: string;
inputTokens: number;
outputTokens: number;
totalTokens: number;
cost: number;
requestCount: number;
avgTokensPerRequest: number;
};
/**
* Aggregated token usage statistics
*/
export type TokenUsageStats = {
totalInputTokens: number;
totalOutputTokens: number;
totalTokens: number;
cacheReadTokens: number;
cacheCreationTokens: number;
reasoningTokens: number;
totalCost: number;
byProvider: Map<string, ProviderTokenStats>;
byModel: Map<string, ModelTokenStats>;
bySpanType: Map<string, number>;
};
export type HealthMetrics = {
timestamp: number;
memoryUsage: NodeJS.MemoryUsage;
uptime: number;
activeConnections: number;
errorRate: number;
averageResponseTime: number;
};
export type SpanOptions = {
name: string;
tracer: import("@opentelemetry/api").Tracer;
kind?: import("@opentelemetry/api").SpanKind;
attributes?: Record<string, string | number | boolean | undefined>;
};
/** Trace + parent span IDs used to correlate metric records with spans. */
export type MetricsTraceContext = {
traceId: string;
parentSpanId: string;
};
/**
* Runtime state for the observability exporter circuit breaker.
* Prefixed to disambiguate from the richer MCP CircuitBreakerState in mcp.ts.
*/
export type ObservabilityCircuitBreakerState = {
failures: number;
lastFailure: number;
state: "closed" | "open" | "half-open";
};
/**
* Minimal config for the observability exporter circuit breaker.
* Prefixed to disambiguate from the richer MCP CircuitBreakerConfig in mcp.ts.
*/
export type ObservabilityCircuitBreakerConfig = {
failureThreshold: number;
resetTimeout: number;
};
/** Minimal view of the dynamically-imported @sentry/node module. */
export type SentryModule = {
init: (options: {
dsn: string;
tracesSampleRate: number;
release?: string;
environment: string;
}) => void;
withScope: (callback: (scope: SentryScope) => void) => void;
captureException: (error: Error) => void;
startInactiveSpan: (options: {
name: string;
op: string;
startTime: number;
attributes?: Record<string, unknown>;
}) => {
end: (timestamp?: number) => void;
};
flush: (timeout: number) => Promise<boolean>;
close: (timeout: number) => Promise<boolean>;
};
/** Sentry scope surface used by SentryExporter.withScope callbacks. */
export type SentryScope = {
setTags: (tags: Record<string, string>) => void;
setContext: (name: string, context: Record<string, unknown>) => void;
setUser: (user: {
id: string;
}) => void;
};
/** Aggregated metrics for a single time window. */
export type TimeWindowStats = {
windowStart: Date;
windowEnd: Date;
windowDurationMs: number;
requestCount: number;
errorCount: number;
successRate: number;
throughput: number;
latency: LatencyStats;
tokens: TokenUsageStats;
costByProvider: Map<string, ProviderCostStats>;
costByModel: Map<string, ModelCostStats>;
};
/** Configuration for MetricsAggregator. */
export type MetricsAggregatorConfig = {
maxSpansRetained?: number;
enableTimeWindows?: boolean;
timeWindowMs?: number;
maxTimeWindows?: number;
};
/**
* Per-million-token pricing used by the observability TokenTracker.
* Prefixed to disambiguate from the richer providers.ts ModelPricing.
*/
export type ObservabilityModelPricing = {
inputPricePerMillion: number;
outputPricePerMillion: number;
cachedInputPricePerMillion?: number;
};