@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
478 lines (477 loc) • 16.4 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 { JsonValue, RawUsageObject, Unknown } from "./common.js";
import type { SpanData } from "./span.js";
/**
* 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;
};
/**
* @description Langfuse exporter options. Every field falls back to its `LANGFUSE_*` environment variable when omitted.
*/
export type NeurolinkLangfuseConfig = {
/** Project public key; defaults to `LANGFUSE_PUBLIC_KEY`. */
publicKey?: string;
/** Project secret key; defaults to `LANGFUSE_SECRET_KEY`. */
secretKey?: string;
/** Langfuse host; defaults to `LANGFUSE_BASE_URL`, then Langfuse Cloud. */
baseUrl?: string;
/** Environment name; defaults to `LANGFUSE_TRACING_ENVIRONMENT`. */
environment?: string;
/** Release identifier; defaults to `LANGFUSE_RELEASE`. */
release?: string;
/** Applied to input, output and metadata before they leave the process. */
mask?: (params: {
data: JsonValue;
}) => JsonValue;
/** `"immediate"` sends every span as it ends; `"batched"` (default) queues them. */
exportMode?: "batched" | "immediate";
/** Spans queued before a batch is sent; defaults to 15. */
flushAt?: number;
/** Seconds between batch sends; defaults to 5. */
flushInterval?: number;
};
/**
* @description Observability configuration for the NeuroLink SDK. Langfuse turns on automatically when `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` are set; `false` turns it off.
*/
export type ObservabilityConfig = {
/** `false` disables Langfuse, `true` or an options object enables it. */
langfuse?: boolean | NeurolinkLangfuseConfig;
};
/**
* @description Per-call trace attributes propagated to every span created inside the call.
*/
export type NeurolinkTraceContext = {
/** End user the trace belongs to. */
userId?: string | null;
/** Session that groups related traces. */
sessionId?: string | null;
/** Conversation identifier, recorded as trace metadata. */
conversationId?: string | null;
/** Request identifier, recorded as trace metadata. */
requestId?: string | null;
/** Trace name shown in Langfuse. */
traceName?: string | null;
/** Trace tags. */
tags?: string[];
/** Trace metadata; values up to 200 characters once stringified are on every observation, longer values and arrays only on the trace. */
metadata?: Record<string, SpanContent | undefined>;
/** Name of the root observation of this `generate` or `stream` call; not propagated to child observations and not the trace name. */
observationName?: string | null;
};
/**
* 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";
};
/** A model request serialized onto a span: the system prompt plus messages, or a bare prompt. */
export type SpanModelRequest = {
system?: unknown;
messages?: readonly unknown[];
prompt?: string;
};
export type GenerationOperation = "generate" | "stream";
/** @description Any value a span attribute can carry: JSON, or an object serialized on the way out. */
export type SpanContent = JsonValue | object;
/** @description Observation kinds Langfuse renders a span as. */
export type ObservationType = "span" | "generation" | "event" | "embedding" | "agent" | "tool" | "chain" | "retriever" | "evaluator" | "guardrail";
/** @description Severity of an observation. */
export type ObservationLevel = "DEBUG" | "DEFAULT" | "WARNING" | "ERROR";
/** @description Attributes stamped on an observation span by `observe`. */
export type ObservationAttributes = {
input?: Unknown;
output?: Unknown;
metadata?: Record<string, SpanContent | undefined>;
level?: ObservationLevel;
statusMessage?: string;
completionStartTime?: Date;
model?: string;
modelParameters?: Record<string, string | number>;
usageDetails?: Record<string, number>;
costDetails?: Record<string, number>;
};
/** @description Trace-level input, output and per-key metadata recorded by `observeTrace`. */
export type TraceContent = Pick<ObservationAttributes, "input" | "output"> & {
metadata?: Record<string, SpanContent | undefined>;
};
/** @description A tool call as a provider, the agentic loop or a tool execution names its fields. */
export type AssistantToolCall = {
id?: string;
toolCallId?: string;
name?: string;
toolName?: string;
args?: SpanContent;
input?: SpanContent;
arguments?: SpanContent;
function?: {
name?: string;
arguments?: SpanContent;
};
};
/** @description A model response split into text, reasoning and tool calls. */
export type AssistantReply = {
text?: string;
reasoning?: string;
toolCalls?: AssistantToolCall[];
};
/** @description A model response as plain text, a structured reply, or message or content-part arrays. */
export type AssistantOutput = string | AssistantReply | object[];
/** @description A tool call in OpenAI chat shape, as Langfuse renders it. */
export type ObservationToolCall = {
id?: string;
type: "function";
function: {
name?: string;
arguments: string;
};
};
/**
* @description One model request as a Langfuse generation. Every field is optional so the request half and the response half can be recorded separately.
*/
export type GenerationRecord = {
/** Public API the call serves; inferred from the enclosing span when unset. */
operation?: GenerationOperation;
/** Provider name, used with the model to price the call. */
provider?: string;
/** Model the request asked for. */
model?: string;
/** Model the response says served it, when the API reports one. */
responseModel?: string;
/** Sampling and thinking parameters sent; undefined entries are dropped. */
modelParameters?: Record<string, string | number | undefined>;
/** System prompt and messages, or the bare prompt, sent. */
input?: SpanModelRequest;
/** Text and tool calls returned. */
output?: AssistantOutput;
/** Token usage in any naming convention `extractTokenUsage` reads. */
usage?: RawUsageObject | null;
/** Provider finish reason. */
finishReason?: string;
/** Whether the caller aborted the call before it finished. */
aborted?: boolean;
/** When the first chunk of a streamed response arrived. */
completionStartTime?: Date;
};
/**
* 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>;
/** Marks a thrown error as an expected skip whose Langfuse level the span set itself, so `withSpan` keeps it instead of raising ERROR. */
expectedFailure?: (error: unknown) => boolean;
/** Keeps the span out of the active context, so spans started inside `fn` parent to the nearest ancestor that is exported. */
detached?: boolean;
};
/** 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;
};