@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
228 lines (227 loc) • 9.16 kB
TypeScript
/**
* OpenTelemetry Instrumentation for Langfuse v4
*
* Configures OpenTelemetry TracerProvider with LangfuseSpanProcessor to capture
* traces from Vercel AI SDK's experimental_telemetry feature.
*
* Flow: Vercel AI SDK → OpenTelemetry Spans → LangfuseSpanProcessor → Langfuse Platform
*/
import { trace, type Span as ApiSpan } from "@opentelemetry/api";
import type { LoggerProvider } from "@opentelemetry/sdk-logs";
import type { SpanProcessor } from "@opentelemetry/sdk-trace-base";
import type { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import type { LangfuseConfig, LangfuseContext } from "../../../../types/index.js";
/**
* True when a span is an internal NeuroLink wrapper that should NOT be sent to
* Langfuse. Internal wrappers carry the `langfuse.internal: true` attribute.
*
* Exposed so host apps that bring their own `LangfuseSpanProcessor` (e.g.
* `skipLangfuseSpanProcessor: true`, or manual registration on an existing
* TracerProvider) can apply the same filter and avoid duplicate observations.
*/
export declare function isLangfuseInternalSpan(span: {
attributes?: Record<string, unknown>;
}): boolean;
/**
* Drop-in `shouldExportSpan` predicate for a `LangfuseSpanProcessor` that
* filters out NeuroLink internal wrapper spans.
*
* Usage in host apps:
* ```ts
* import { langfuseShouldExportSpan } from "@juspay/neurolink";
* new LangfuseSpanProcessor({ ..., shouldExportSpan: langfuseShouldExportSpan });
* ```
*/
export declare function langfuseShouldExportSpan({ otelSpan, }: {
otelSpan: {
attributes?: Record<string, unknown>;
};
}): boolean;
/**
* Initialize OpenTelemetry once and let concurrent callers share the work.
*
* NeuroLink constructors intentionally start this asynchronously. Keeping the
* in-flight promise here lets another constructor join it and lets an immediate
* flush or shutdown wait for the same initialization before touching providers.
*/
export declare function initializeOpenTelemetry(config: LangfuseConfig): Promise<void>;
/**
* Flush all pending spans to Langfuse
*/
export declare function flushOpenTelemetry(): Promise<void>;
/**
* Shutdown OpenTelemetry and Langfuse span processor
*
* Concurrent callers share one in-flight teardown, the same way concurrent
* initializers share one in-flight `initializeOpenTelemetry`: a second
* shutdown call joins the first instead of tearing down the same provider
* instances a second time.
*/
export declare function shutdownOpenTelemetry(): Promise<void>;
/**
* Get the Langfuse span processor
*/
export declare function getLangfuseSpanProcessor(): SpanProcessor | null;
/**
* Get the tracer provider
*/
export declare function getTracerProvider(): NodeTracerProvider | null;
/**
* Get the logger provider for emitting OTLP log records.
* Returns null if OTLP is not configured or LoggerProvider was not created.
*/
export declare function getLoggerProvider(): LoggerProvider | null;
/**
* Check if OpenTelemetry is initialized
*/
export declare function isOpenTelemetryInitialized(): boolean;
/**
* Get health status for Langfuse observability
*
* @returns Health status object with initialization and configuration details
*/
export declare function getLangfuseHealthStatus(): {
isHealthy: boolean;
initialized: boolean;
credentialsValid: boolean;
enabled: boolean;
hasProcessor: boolean;
usingExternalProvider: boolean;
config?: {
baseUrl: string;
environment: string;
release: string;
};
};
/**
* Set user and session context for Langfuse spans in the current async context
*
* Merges the provided context with existing AsyncLocalStorage context. If a callback is provided,
* the context is scoped to that callback execution and returns the callback's result.
* Without a callback, the context applies to the current execution context and its children.
*
* Uses AsyncLocalStorage to properly scope context per request, avoiding race conditions
* in concurrent scenarios.
*
* @param context - Object containing context fields to merge with existing context
* @param callback - Optional callback to run within the context scope. If omitted, context applies to current execution
* @returns The callback's return value if provided, otherwise void
*
* @example
* // With callback - returns the result
* const result = await setLangfuseContext({ userId: "user123" }, async () => {
* return await generateText({ model: "gpt-4", prompt: "Hello" });
* });
*
* @example
* // Without callback - sets context for current execution
* await setLangfuseContext({ sessionId: "session456", traceName: "chat-completion" });
*/
export declare function setLangfuseContext<T = void>(context: {
userId?: string | null;
sessionId?: string | null;
conversationId?: string | null;
requestId?: string | null;
traceName?: string | null;
metadata?: Record<string, unknown> | null;
/** Explicit operation name (overrides auto-detection) */
operationName?: string | null;
/** Override global autoDetectOperationName for this context */
autoDetectOperationName?: boolean;
/** Custom attributes to set on all spans within this context */
customAttributes?: Record<string, string | number | boolean>;
}, callback?: () => T | Promise<T>): Promise<T | void>;
/**
* Get the current Langfuse context from AsyncLocalStorage
*
* Returns the current context including userId, sessionId, conversationId,
* requestId, traceName, and metadata. Returns undefined if no context is set.
*
* @returns The current LangfuseContext or undefined
*
* @example
* const context = getLangfuseContext();
* console.log(context?.userId, context?.sessionId);
*/
export declare function getLangfuseContext(): LangfuseContext | undefined;
/**
* Fill a span's Langfuse identity when the caller's context would otherwise
* fall back to "guest". Identity (user.id / session.id) is set additively —
* only fields ambient context didn't already provide, so a host's own context
* is never overridden. trace.name (the title) is rescued only when no ambient
* name source exists AND this span is the trace root, mirroring
* ContextEnricher.onStart so a host wrapper span isn't relabelled.
*/
export declare function stampGuestRescueIdentity(span: ApiSpan, callContext: unknown, isRootSpan: boolean): void;
/**
* Capture the current Langfuse AsyncLocalStorage context and return a wrapper
* that re-enters that context when executing the provided callback.
*
* This is essential for preserving trace context across async boundaries that
* break the automatic ALS propagation chain, such as `setImmediate()`,
* `setTimeout()`, or event-emitter callbacks. Without this, spans created
* inside those callbacks become orphaned traces in Langfuse.
*
* **How it works:**
* 1. Captures the current ALS store at call time (synchronously).
* 2. Returns an async function that, when invoked, re-enters the captured
* context via `contextStorage.run()` before executing the callback.
* 3. If no context exists at capture time, the callback runs without
* ALS wrapping (no-op passthrough).
*
* @param fn - The async function to execute within the captured context
* @returns A new async function that preserves the Langfuse ALS context
*
* @example
* // Before (broken — setImmediate loses ALS context):
* setImmediate(async () => {
* await this.checkAndSummarize(session, threshold);
* });
*
* // After (fixed — context is captured and re-entered):
* const wrappedFn = runWithCurrentLangfuseContext(async () => {
* await this.checkAndSummarize(session, threshold);
* });
* setImmediate(wrappedFn);
*/
export declare function runWithCurrentLangfuseContext<T>(fn: () => Promise<T>): () => Promise<T>;
/**
* Get an OpenTelemetry Tracer for creating custom spans
*
* This allows applications to create their own spans that will be
* processed by the same span processors (ContextEnricher + LangfuseSpanProcessor).
*
* @param name - Tracer name, defaults to "neurolink"
* @param version - Tracer version, optional
* @returns OpenTelemetry Tracer instance
*
* @example
* const tracer = getTracer("my-app");
* const span = tracer.startSpan("custom-operation");
* try {
* // ... do work
* } finally {
* span.end();
* }
*/
export declare function getTracer(name?: string, version?: string): ReturnType<typeof trace.getTracer>;
/**
* Create a new ContextEnricher span processor
* Use this when useExternalTracerProvider is true to add to your own TracerProvider
*
* @returns A new ContextEnricher instance
*/
export declare function createContextEnricher(): SpanProcessor;
/**
* Get all span processors that NeuroLink would use
* Convenience function that returns [ContextEnricher, LangfuseSpanProcessor]
*
* @returns Array of span processors, or empty array if not initialized
*/
export declare function getSpanProcessors(): SpanProcessor[];
/**
* Check if using external TracerProvider mode
*
* @returns true if operating in external TracerProvider mode
*/
export declare function isUsingExternalTracerProvider(): boolean;