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

141 lines (140 loc) 5.08 kB
/** * OpenTelemetry Bridge * Bidirectional context propagation between NeuroLink and OpenTelemetry */ import { context, propagation, trace, } from "@opentelemetry/api"; import { SpanStatus } from "../types/index.js"; import { SpanSerializer } from "./utils/spanSerializer.js"; /** * Bridge for bidirectional context propagation between * NeuroLink's observability system and OpenTelemetry */ export class OtelBridge { tracer = trace.getTracer("neurolink-bridge"); /** * Extract trace context from incoming request headers */ extractContext(headers) { // Normalize traceparent: OTel HTTP auto-instrumentation may inject a second traceparent // on top of the SDK's manually injected one, producing "val1, val2". The W3C spec rejects // multiple values, causing extraction to silently return null. Take the first value only. const normalizedHeaders = { ...headers }; if (typeof normalizedHeaders["traceparent"] === "string" && normalizedHeaders["traceparent"].includes(", ")) { normalizedHeaders["traceparent"] = normalizedHeaders["traceparent"].split(", ")[0]; } const extractedContext = propagation.extract(context.active(), normalizedHeaders); const spanContext = trace.getSpanContext(extractedContext); return spanContext ?? null; } /** * Inject trace context into outgoing request headers */ injectContext(headers, otelContext = context.active()) { propagation.inject(otelContext, headers); return headers; } /** * Create a NeuroLink span from OpenTelemetry context */ createSpanFromOtelContext(spanContext, type, name) { return SpanSerializer.createSpan(type, name, {}, spanContext.spanId, spanContext.traceId); } /** * Wrap a function with OpenTelemetry tracing that also creates NeuroLink spans */ async wrapWithTracing(name, type, fn, onSpanEnd) { const otelSpan = this.tracer.startSpan(name); const neuroLinkSpan = SpanSerializer.createSpan(type, name, {}, undefined, otelSpan.spanContext().traceId); try { const result = await context.with(trace.setSpan(context.active(), otelSpan), () => fn(neuroLinkSpan)); const endedSpan = SpanSerializer.endSpan(neuroLinkSpan, SpanStatus.OK); otelSpan.setStatus({ code: 1 }); // OK if (onSpanEnd) { onSpanEnd(endedSpan); } return result; } catch (error) { const endedSpan = SpanSerializer.endSpan(neuroLinkSpan, SpanStatus.ERROR, error instanceof Error ? error.message : String(error)); otelSpan.setStatus({ code: 2, // ERROR message: error instanceof Error ? error.message : String(error), }); otelSpan.recordException(error); if (onSpanEnd) { onSpanEnd(endedSpan); } throw error; } finally { otelSpan.end(); } } /** * Convert NeuroLink span to OpenTelemetry span and export */ exportToOtel(span) { const otelSpan = this.tracer.startSpan(span.name, { startTime: new Date(span.startTime), attributes: this.filterAttributes(span.attributes), }); // Add events for (const event of span.events) { otelSpan.addEvent(event.name, this.filterEventAttributes(event.attributes), new Date(event.timestamp)); } // Set status (map NeuroLink status to OTel status) const otelStatusCode = span.status === SpanStatus.ERROR ? 2 : 1; otelSpan.setStatus({ code: otelStatusCode, message: span.statusMessage, }); // End span if (span.endTime) { otelSpan.end(new Date(span.endTime)); } else { otelSpan.end(); } } /** * Get current trace context for correlation */ getCurrentTraceContext() { const spanContext = trace.getActiveSpan()?.spanContext(); if (!spanContext) { return null; } return { traceId: spanContext.traceId, spanId: spanContext.spanId, }; } /** * Filter attributes to only include OTel-compatible types */ filterAttributes(attrs) { const result = {}; for (const [key, value] of Object.entries(attrs)) { if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { result[key] = value; } else if (value !== undefined && value !== null) { result[key] = JSON.stringify(value); } } return result; } /** * Filter event attributes */ filterEventAttributes(attrs) { if (!attrs) { return undefined; } return this.filterAttributes(attrs); } }