From 419d216de1bac653e0597cddbb360d46eb040cf3 Mon Sep 17 00:00:00 2001 From: idityage Date: Thu, 20 Aug 2026 17:45:00 +0530 Subject: [PATCH] fix: wire tracer provider and parse user messages --- plugins/tracing/dist/index.mjs | 1388 +++++++++--------- plugins/tracing/src/instrumentation.ts | 3 + plugins/tracing/src/parse.ts | 29 +- plugins/tracing/test/instrumentation.test.ts | 11 + plugins/tracing/test/parse.test.ts | 53 + 5 files changed, 793 insertions(+), 691 deletions(-) create mode 100644 plugins/tracing/test/instrumentation.test.ts diff --git a/plugins/tracing/dist/index.mjs b/plugins/tracing/dist/index.mjs index 6c20f5b..b9268c4 100644 --- a/plugins/tracing/dist/index.mjs +++ b/plugins/tracing/dist/index.mjs @@ -45627,604 +45627,138 @@ ${JSON.stringify({ }; //#endregion -//#region ../../node_modules/.pnpm/@opentelemetry+context-async-hooks@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/context-async-hooks/build/src/AbstractAsyncHooksContextManager.js -var require_AbstractAsyncHooksContextManager = /* @__PURE__ */ __commonJSMin(((exports) => { - Object.defineProperty(exports, "__esModule", { value: true }); - exports.AbstractAsyncHooksContextManager = void 0; - const events_1 = __require("events"); - const ADD_LISTENER_METHODS = [ - "addListener", - "on", - "once", - "prependListener", - "prependOnceListener" - ]; - var AbstractAsyncHooksContextManager = class { - /** - * Binds a the certain context or the active one to the target function and then returns the target - * @param context A context (span) to be bind to target - * @param target a function or event emitter. When target or one of its callbacks is called, - * the provided context will be used as the active context for the duration of the call. - */ - bind(context$1, target) { - if (target instanceof events_1.EventEmitter) return this._bindEventEmitter(context$1, target); - if (typeof target === "function") return this._bindFunction(context$1, target); - return target; - } - _bindFunction(context$1, target) { - const manager = this; - const contextWrapper = function(...args) { - return manager.with(context$1, () => target.apply(this, args)); - }; - Object.defineProperty(contextWrapper, "length", { - enumerable: false, - configurable: true, - writable: false, - value: target.length - }); - /** - * It isn't possible to tell Typescript that contextWrapper is the same as T - * so we forced to cast as any here. - */ - return contextWrapper; - } - /** - * By default, EventEmitter call their callback with their context, which we do - * not want, instead we will bind a specific context to all callbacks that - * go through it. - * @param context the context we want to bind - * @param ee EventEmitter an instance of EventEmitter to patch - */ - _bindEventEmitter(context$1, ee) { - if (this._getPatchMap(ee) !== void 0) return ee; - this._createPatchMap(ee); - ADD_LISTENER_METHODS.forEach((methodName) => { - if (ee[methodName] === void 0) return; - ee[methodName] = this._patchAddListener(ee, ee[methodName], context$1); - }); - if (typeof ee.removeListener === "function") ee.removeListener = this._patchRemoveListener(ee, ee.removeListener); - if (typeof ee.off === "function") ee.off = this._patchRemoveListener(ee, ee.off); - if (typeof ee.removeAllListeners === "function") ee.removeAllListeners = this._patchRemoveAllListeners(ee, ee.removeAllListeners); - return ee; - } - /** - * Patch methods that remove a given listener so that we match the "patched" - * version of that listener (the one that propagate context). - * @param ee EventEmitter instance - * @param original reference to the patched method - */ - _patchRemoveListener(ee, original) { - const contextManager = this; - return function(event, listener) { - const events = contextManager._getPatchMap(ee)?.[event]; - if (events === void 0) return original.call(this, event, listener); - const patchedListener = events.get(listener); - return original.call(this, event, patchedListener || listener); - }; - } - /** - * Patch methods that remove all listeners so we remove our - * internal references for a given event. - * @param ee EventEmitter instance - * @param original reference to the patched method - */ - _patchRemoveAllListeners(ee, original) { - const contextManager = this; - return function(event) { - const map = contextManager._getPatchMap(ee); - if (map !== void 0) { - if (arguments.length === 0) contextManager._createPatchMap(ee); - else if (map[event] !== void 0) delete map[event]; - } - return original.apply(this, arguments); - }; - } - /** - * Patch methods on an event emitter instance that can add listeners so we - * can force them to propagate a given context. - * @param ee EventEmitter instance - * @param original reference to the patched method - * @param [context] context to propagate when calling listeners - */ - _patchAddListener(ee, original, context$1) { - const contextManager = this; - return function(event, listener) { - /** - * This check is required to prevent double-wrapping the listener. - * The implementation for ee.once wraps the listener and calls ee.on. - * Without this check, we would wrap that wrapped listener. - * This causes an issue because ee.removeListener depends on the onceWrapper - * to properly remove the listener. If we wrap their wrapper, we break - * that detection. - */ - if (contextManager._wrapped) return original.call(this, event, listener); - let map = contextManager._getPatchMap(ee); - if (map === void 0) map = contextManager._createPatchMap(ee); - let listeners = map[event]; - if (listeners === void 0) { - listeners = /* @__PURE__ */ new WeakMap(); - map[event] = listeners; - } - const patchedListener = contextManager.bind(context$1, listener); - listeners.set(listener, patchedListener); - /** - * See comment at the start of this function for the explanation of this property. - */ - contextManager._wrapped = true; - try { - return original.call(this, event, patchedListener); - } finally { - contextManager._wrapped = false; - } - }; - } - _createPatchMap(ee) { - const map = Object.create(null); - ee[this._kOtListeners] = map; - return map; - } - _getPatchMap(ee) { - return ee[this._kOtListeners]; - } - _kOtListeners = Symbol("OtListeners"); - _wrapped = false; +//#region ../../node_modules/.pnpm/@langfuse+tracing@5.4.1_@opentelemetry+api@1.9.1/node_modules/@langfuse/tracing/dist/index.mjs +init_esm$2(); +function createTraceAttributes({ input, output } = {}) { + const attributes = { + [LangfuseOtelSpanAttributes.TRACE_INPUT]: _serialize(input), + [LangfuseOtelSpanAttributes.TRACE_OUTPUT]: _serialize(output) }; - exports.AbstractAsyncHooksContextManager = AbstractAsyncHooksContextManager; -})); - -//#endregion -//#region ../../node_modules/.pnpm/@opentelemetry+context-async-hooks@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/context-async-hooks/build/src/AsyncHooksContextManager.js -var require_AsyncHooksContextManager = /* @__PURE__ */ __commonJSMin(((exports) => { - Object.defineProperty(exports, "__esModule", { value: true }); - exports.AsyncHooksContextManager = void 0; - const api_1 = (init_esm$2(), __toCommonJS(esm_exports$2)); - const asyncHooks = __require("async_hooks"); - const AbstractAsyncHooksContextManager_1 = require_AbstractAsyncHooksContextManager(); + return Object.fromEntries(Object.entries(attributes).filter(([_, v]) => v != null)); +} +function createObservationAttributes(type, attributes) { + const { metadata, input, output, level, statusMessage, version: version$1, completionStartTime, model, modelParameters, usageDetails, costDetails, prompt } = attributes; + let otelAttributes = { + [LangfuseOtelSpanAttributes.OBSERVATION_TYPE]: type, + [LangfuseOtelSpanAttributes.OBSERVATION_LEVEL]: level, + [LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE]: statusMessage, + [LangfuseOtelSpanAttributes.VERSION]: version$1, + [LangfuseOtelSpanAttributes.OBSERVATION_INPUT]: _serialize(input), + [LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT]: _serialize(output), + [LangfuseOtelSpanAttributes.OBSERVATION_MODEL]: model, + [LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS]: _serialize(usageDetails), + [LangfuseOtelSpanAttributes.OBSERVATION_COST_DETAILS]: _serialize(costDetails), + [LangfuseOtelSpanAttributes.OBSERVATION_COMPLETION_START_TIME]: _serialize(completionStartTime), + [LangfuseOtelSpanAttributes.OBSERVATION_MODEL_PARAMETERS]: _serialize(modelParameters), + ...prompt && !prompt.isFallback ? { + [LangfuseOtelSpanAttributes.OBSERVATION_PROMPT_NAME]: prompt.name, + [LangfuseOtelSpanAttributes.OBSERVATION_PROMPT_VERSION]: prompt.version + } : {}, + ..._flattenAndSerializeMetadata(metadata, "observation") + }; + return Object.fromEntries(Object.entries(otelAttributes).filter(([_, v]) => v != null)); +} +function _serialize(obj) { + try { + if (typeof obj === "string") return obj; + return obj != null ? JSON.stringify(obj) : void 0; + } catch { + return ""; + } +} +function _flattenAndSerializeMetadata(metadata, type) { + const prefix = type === "observation" ? LangfuseOtelSpanAttributes.OBSERVATION_METADATA : LangfuseOtelSpanAttributes.TRACE_METADATA; + const metadataAttributes = {}; + if (metadata === void 0 || metadata === null) return metadataAttributes; + if (typeof metadata !== "object" || Array.isArray(metadata)) { + const serialized = _serialize(metadata); + if (serialized) metadataAttributes[prefix] = serialized; + } else for (const [key, value] of Object.entries(metadata)) { + const serialized = typeof value === "string" ? value : _serialize(value); + if (serialized) metadataAttributes[`${prefix}.${key}`] = serialized; + } + return metadataAttributes; +} +var LANGFUSE_GLOBAL_SYMBOL = Symbol.for("langfuse"); +function createState() { + return { isolatedTracerProvider: null }; +} +function getGlobalState() { + const initialState = createState(); + try { + const g = globalThis; + if (typeof g !== "object" || g === null) { + getGlobalLogger().warn("globalThis is not available, using fallback state"); + return initialState; + } + if (!g[LANGFUSE_GLOBAL_SYMBOL]) Object.defineProperty(g, LANGFUSE_GLOBAL_SYMBOL, { + value: initialState, + writable: false, + configurable: false, + enumerable: false + }); + return g[LANGFUSE_GLOBAL_SYMBOL]; + } catch (err) { + if (err instanceof Error) getGlobalLogger().error(`Failed to access global state: ${err.message}`); + else getGlobalLogger().error(`Failed to access global state: ${String(err)}`); + return initialState; + } +} +function setLangfuseTracerProvider(provider) { + getGlobalState().isolatedTracerProvider = provider; +} +function getLangfuseTracerProvider() { + const { isolatedTracerProvider } = getGlobalState(); + if (isolatedTracerProvider) return isolatedTracerProvider; + return trace.getTracerProvider(); +} +function getLangfuseTracer() { + return getLangfuseTracerProvider().getTracer(LANGFUSE_TRACER_NAME, LANGFUSE_SDK_VERSION); +} +var LangfuseBaseObservation = class { + constructor(params) { + this.otelSpan = params.otelSpan; + this.id = params.otelSpan.spanContext().spanId; + this.traceId = params.otelSpan.spanContext().traceId; + this.type = params.type; + if (params.attributes) this.otelSpan.setAttributes(createObservationAttributes(params.type, params.attributes)); + } + /** Gets the Langfuse OpenTelemetry tracer instance */ + get tracer() { + return getLangfuseTracer(); + } /** - * @deprecated Use AsyncLocalStorageContextManager instead. + * Ends the observation, marking it as complete. + * + * @param endTime - Optional end time, defaults to current time */ - var AsyncHooksContextManager = class extends AbstractAsyncHooksContextManager_1.AbstractAsyncHooksContextManager { - _asyncHook; - _contexts = /* @__PURE__ */ new Map(); - _stack = []; - constructor() { - super(); - this._asyncHook = asyncHooks.createHook({ - init: this._init.bind(this), - before: this._before.bind(this), - after: this._after.bind(this), - destroy: this._destroy.bind(this), - promiseResolve: this._destroy.bind(this) - }); - } - active() { - return this._stack[this._stack.length - 1] ?? api_1.ROOT_CONTEXT; - } - with(context$1, fn, thisArg, ...args) { - this._enterContext(context$1); - try { - return fn.call(thisArg, ...args); - } finally { - this._exitContext(); - } - } - enable() { - this._asyncHook.enable(); - return this; - } - disable() { - this._asyncHook.disable(); - this._contexts.clear(); - this._stack = []; - return this; - } - /** - * Init hook will be called when userland create a async context, setting the - * context as the current one if it exist. - * @param uid id of the async context - * @param type the resource type - */ - _init(uid, type) { - if (type === "TIMERWRAP") return; - const context$1 = this._stack[this._stack.length - 1]; - if (context$1 !== void 0) this._contexts.set(uid, context$1); - } - /** - * Destroy hook will be called when a given context is no longer used so we can - * remove its attached context. - * @param uid uid of the async context - */ - _destroy(uid) { - this._contexts.delete(uid); - } - /** - * Before hook is called just before executing a async context. - * @param uid uid of the async context - */ - _before(uid) { - const context$1 = this._contexts.get(uid); - if (context$1 !== void 0) this._enterContext(context$1); - } - /** - * After hook is called just after completing the execution of a async context. - */ - _after() { - this._exitContext(); - } - /** - * Set the given context as active - */ - _enterContext(context$1) { - this._stack.push(context$1); - } - /** - * Remove the context at the root of the stack - */ - _exitContext() { - this._stack.pop(); - } - }; - exports.AsyncHooksContextManager = AsyncHooksContextManager; -})); - -//#endregion -//#region ../../node_modules/.pnpm/@opentelemetry+context-async-hooks@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/context-async-hooks/build/src/AsyncLocalStorageContextManager.js -var require_AsyncLocalStorageContextManager = /* @__PURE__ */ __commonJSMin(((exports) => { - Object.defineProperty(exports, "__esModule", { value: true }); - exports.AsyncLocalStorageContextManager = void 0; - const api_1 = (init_esm$2(), __toCommonJS(esm_exports$2)); - const async_hooks_1 = __require("async_hooks"); - const AbstractAsyncHooksContextManager_1 = require_AbstractAsyncHooksContextManager(); - var AsyncLocalStorageContextManager = class extends AbstractAsyncHooksContextManager_1.AbstractAsyncHooksContextManager { - _asyncLocalStorage; - constructor() { - super(); - this._asyncLocalStorage = new async_hooks_1.AsyncLocalStorage(); - } - active() { - return this._asyncLocalStorage.getStore() ?? api_1.ROOT_CONTEXT; - } - with(context$1, fn, thisArg, ...args) { - const cb = thisArg == null ? fn : fn.bind(thisArg); - return this._asyncLocalStorage.run(context$1, cb, ...args); - } - enable() { - return this; - } - disable() { - this._asyncLocalStorage.disable(); - return this; - } - }; - exports.AsyncLocalStorageContextManager = AsyncLocalStorageContextManager; -})); - -//#endregion -//#region ../../node_modules/.pnpm/@opentelemetry+context-async-hooks@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/context-async-hooks/build/src/index.js -var require_src$1 = /* @__PURE__ */ __commonJSMin(((exports) => { - Object.defineProperty(exports, "__esModule", { value: true }); - exports.AsyncLocalStorageContextManager = exports.AsyncHooksContextManager = void 0; - var AsyncHooksContextManager_1 = require_AsyncHooksContextManager(); - Object.defineProperty(exports, "AsyncHooksContextManager", { - enumerable: true, - get: function() { - return AsyncHooksContextManager_1.AsyncHooksContextManager; - } - }); - var AsyncLocalStorageContextManager_1 = require_AsyncLocalStorageContextManager(); - Object.defineProperty(exports, "AsyncLocalStorageContextManager", { - enumerable: true, - get: function() { - return AsyncLocalStorageContextManager_1.AsyncLocalStorageContextManager; - } - }); -})); - -//#endregion -//#region ../../node_modules/.pnpm/@opentelemetry+sdk-trace-node@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/sdk-trace-node/build/src/NodeTracerProvider.js -var require_NodeTracerProvider = /* @__PURE__ */ __commonJSMin(((exports) => { - Object.defineProperty(exports, "__esModule", { value: true }); - exports.NodeTracerProvider = void 0; - const context_async_hooks_1 = require_src$1(); - const sdk_trace_base_1 = require_src$2(); - const api_1 = (init_esm$2(), __toCommonJS(esm_exports$2)); - const core_1 = require_src$9(); - function setupContextManager(contextManager) { - if (contextManager === null) return; - if (contextManager === void 0) { - const defaultContextManager = new context_async_hooks_1.AsyncLocalStorageContextManager(); - defaultContextManager.enable(); - api_1.context.setGlobalContextManager(defaultContextManager); - return; - } - contextManager.enable(); - api_1.context.setGlobalContextManager(contextManager); + end(endTime) { + this.otelSpan.end(endTime); } - function setupPropagator(propagator) { - if (propagator === null) return; - if (propagator === void 0) { - api_1.propagation.setGlobalPropagator(new core_1.CompositePropagator({ propagators: [new core_1.W3CTraceContextPropagator(), new core_1.W3CBaggagePropagator()] })); - return; - } - api_1.propagation.setGlobalPropagator(propagator); + updateOtelSpanAttributes(attributes) { + this.otelSpan.setAttributes(createObservationAttributes(this.type, attributes)); } /** - * Register this TracerProvider for use with the OpenTelemetry API. - * Undefined values may be replaced with defaults, and - * null values will be skipped. + * Set trace-level input and output for the trace this observation belongs to. * - * @param config Configuration object for SDK registration - */ - var NodeTracerProvider = class extends sdk_trace_base_1.BasicTracerProvider { - constructor(config$1 = {}) { - super(config$1); - } - /** - * Register this TracerProvider for use with the OpenTelemetry API. - * Undefined values may be replaced with defaults, and - * null values will be skipped. - * - * @param config Configuration object for SDK registration - */ - register(config$1 = {}) { - api_1.trace.setGlobalTracerProvider(this); - setupContextManager(config$1.contextManager); - setupPropagator(config$1.propagator); - } - }; - exports.NodeTracerProvider = NodeTracerProvider; -})); - -//#endregion -//#region ../../node_modules/.pnpm/@opentelemetry+sdk-trace-node@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/sdk-trace-node/build/src/index.js -var require_src = /* @__PURE__ */ __commonJSMin(((exports) => { - Object.defineProperty(exports, "__esModule", { value: true }); - exports.TraceIdRatioBasedSampler = exports.SimpleSpanProcessor = exports.SamplingDecision = exports.RandomIdGenerator = exports.ParentBasedSampler = exports.NoopSpanProcessor = exports.InMemorySpanExporter = exports.ConsoleSpanExporter = exports.BatchSpanProcessor = exports.BasicTracerProvider = exports.AlwaysOnSampler = exports.AlwaysOffSampler = exports.NodeTracerProvider = void 0; - var NodeTracerProvider_1 = require_NodeTracerProvider(); - Object.defineProperty(exports, "NodeTracerProvider", { - enumerable: true, - get: function() { - return NodeTracerProvider_1.NodeTracerProvider; - } - }); - var sdk_trace_base_1 = require_src$2(); - Object.defineProperty(exports, "AlwaysOffSampler", { - enumerable: true, - get: function() { - return sdk_trace_base_1.AlwaysOffSampler; - } - }); - Object.defineProperty(exports, "AlwaysOnSampler", { - enumerable: true, - get: function() { - return sdk_trace_base_1.AlwaysOnSampler; - } - }); - Object.defineProperty(exports, "BasicTracerProvider", { - enumerable: true, - get: function() { - return sdk_trace_base_1.BasicTracerProvider; - } - }); - Object.defineProperty(exports, "BatchSpanProcessor", { - enumerable: true, - get: function() { - return sdk_trace_base_1.BatchSpanProcessor; - } - }); - Object.defineProperty(exports, "ConsoleSpanExporter", { - enumerable: true, - get: function() { - return sdk_trace_base_1.ConsoleSpanExporter; - } - }); - Object.defineProperty(exports, "InMemorySpanExporter", { - enumerable: true, - get: function() { - return sdk_trace_base_1.InMemorySpanExporter; - } - }); - Object.defineProperty(exports, "NoopSpanProcessor", { - enumerable: true, - get: function() { - return sdk_trace_base_1.NoopSpanProcessor; - } - }); - Object.defineProperty(exports, "ParentBasedSampler", { - enumerable: true, - get: function() { - return sdk_trace_base_1.ParentBasedSampler; - } - }); - Object.defineProperty(exports, "RandomIdGenerator", { - enumerable: true, - get: function() { - return sdk_trace_base_1.RandomIdGenerator; - } - }); - Object.defineProperty(exports, "SamplingDecision", { - enumerable: true, - get: function() { - return sdk_trace_base_1.SamplingDecision; - } - }); - Object.defineProperty(exports, "SimpleSpanProcessor", { - enumerable: true, - get: function() { - return sdk_trace_base_1.SimpleSpanProcessor; - } - }); - Object.defineProperty(exports, "TraceIdRatioBasedSampler", { - enumerable: true, - get: function() { - return sdk_trace_base_1.TraceIdRatioBasedSampler; - } - }); -})); - -//#endregion -//#region src/instrumentation.ts -var import_src = require_src(); -/** -* Configure an isolated OpenTelemetry tracer provider wired to Langfuse. -* -* We register a dedicated `NodeTracerProvider` (rather than the full auto- -* instrumenting `NodeSDK`) so the bundle stays small and free of dynamic -* instrumentation loading. Registering the provider also installs the -* AsyncLocalStorage context manager that `propagateAttributes` relies on. -* -* We use `exportMode: "batched"` and flush once at the end: the whole rollout -* is converted in-process, so batching every span into one (or a few) requests -* is far faster than one request per span — important for the hook's timeout -* budget. `shutdown()` below calls `forceFlush()` before the process exits. -*/ -function setupInstrumentation(config$1) { - const spanProcessor = new LangfuseSpanProcessor({ - publicKey: config$1.public_key, - secretKey: config$1.secret_key, - baseUrl: config$1.base_url, - environment: config$1.environment, - exportMode: "batched", - shouldExportSpan: () => true - }); - const provider = new import_src.NodeTracerProvider({ spanProcessors: [spanProcessor] }); - provider.register(); - return { shutdown: async () => { - await spanProcessor.forceFlush(); - await provider.shutdown(); - } }; -} - -//#endregion -//#region ../../node_modules/.pnpm/@langfuse+tracing@5.4.1_@opentelemetry+api@1.9.1/node_modules/@langfuse/tracing/dist/index.mjs -init_esm$2(); -function createTraceAttributes({ input, output } = {}) { - const attributes = { - [LangfuseOtelSpanAttributes.TRACE_INPUT]: _serialize(input), - [LangfuseOtelSpanAttributes.TRACE_OUTPUT]: _serialize(output) - }; - return Object.fromEntries(Object.entries(attributes).filter(([_, v]) => v != null)); -} -function createObservationAttributes(type, attributes) { - const { metadata, input, output, level, statusMessage, version: version$1, completionStartTime, model, modelParameters, usageDetails, costDetails, prompt } = attributes; - let otelAttributes = { - [LangfuseOtelSpanAttributes.OBSERVATION_TYPE]: type, - [LangfuseOtelSpanAttributes.OBSERVATION_LEVEL]: level, - [LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE]: statusMessage, - [LangfuseOtelSpanAttributes.VERSION]: version$1, - [LangfuseOtelSpanAttributes.OBSERVATION_INPUT]: _serialize(input), - [LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT]: _serialize(output), - [LangfuseOtelSpanAttributes.OBSERVATION_MODEL]: model, - [LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS]: _serialize(usageDetails), - [LangfuseOtelSpanAttributes.OBSERVATION_COST_DETAILS]: _serialize(costDetails), - [LangfuseOtelSpanAttributes.OBSERVATION_COMPLETION_START_TIME]: _serialize(completionStartTime), - [LangfuseOtelSpanAttributes.OBSERVATION_MODEL_PARAMETERS]: _serialize(modelParameters), - ...prompt && !prompt.isFallback ? { - [LangfuseOtelSpanAttributes.OBSERVATION_PROMPT_NAME]: prompt.name, - [LangfuseOtelSpanAttributes.OBSERVATION_PROMPT_VERSION]: prompt.version - } : {}, - ..._flattenAndSerializeMetadata(metadata, "observation") - }; - return Object.fromEntries(Object.entries(otelAttributes).filter(([_, v]) => v != null)); -} -function _serialize(obj) { - try { - if (typeof obj === "string") return obj; - return obj != null ? JSON.stringify(obj) : void 0; - } catch { - return ""; - } -} -function _flattenAndSerializeMetadata(metadata, type) { - const prefix = type === "observation" ? LangfuseOtelSpanAttributes.OBSERVATION_METADATA : LangfuseOtelSpanAttributes.TRACE_METADATA; - const metadataAttributes = {}; - if (metadata === void 0 || metadata === null) return metadataAttributes; - if (typeof metadata !== "object" || Array.isArray(metadata)) { - const serialized = _serialize(metadata); - if (serialized) metadataAttributes[prefix] = serialized; - } else for (const [key, value] of Object.entries(metadata)) { - const serialized = typeof value === "string" ? value : _serialize(value); - if (serialized) metadataAttributes[`${prefix}.${key}`] = serialized; - } - return metadataAttributes; -} -var LANGFUSE_GLOBAL_SYMBOL = Symbol.for("langfuse"); -function createState() { - return { isolatedTracerProvider: null }; -} -function getGlobalState() { - const initialState = createState(); - try { - const g = globalThis; - if (typeof g !== "object" || g === null) { - getGlobalLogger().warn("globalThis is not available, using fallback state"); - return initialState; - } - if (!g[LANGFUSE_GLOBAL_SYMBOL]) Object.defineProperty(g, LANGFUSE_GLOBAL_SYMBOL, { - value: initialState, - writable: false, - configurable: false, - enumerable: false - }); - return g[LANGFUSE_GLOBAL_SYMBOL]; - } catch (err) { - if (err instanceof Error) getGlobalLogger().error(`Failed to access global state: ${err.message}`); - else getGlobalLogger().error(`Failed to access global state: ${String(err)}`); - return initialState; - } -} -function getLangfuseTracerProvider() { - const { isolatedTracerProvider } = getGlobalState(); - if (isolatedTracerProvider) return isolatedTracerProvider; - return trace.getTracerProvider(); -} -function getLangfuseTracer() { - return getLangfuseTracerProvider().getTracer(LANGFUSE_TRACER_NAME, LANGFUSE_SDK_VERSION); -} -var LangfuseBaseObservation = class { - constructor(params) { - this.otelSpan = params.otelSpan; - this.id = params.otelSpan.spanContext().spanId; - this.traceId = params.otelSpan.spanContext().traceId; - this.type = params.type; - if (params.attributes) this.otelSpan.setAttributes(createObservationAttributes(params.type, params.attributes)); - } - /** Gets the Langfuse OpenTelemetry tracer instance */ - get tracer() { - return getLangfuseTracer(); - } - /** - * Ends the observation, marking it as complete. - * - * @param endTime - Optional end time, defaults to current time - */ - end(endTime) { - this.otelSpan.end(endTime); - } - updateOtelSpanAttributes(attributes) { - this.otelSpan.setAttributes(createObservationAttributes(this.type, attributes)); - } - /** - * Set trace-level input and output for the trace this observation belongs to. - * - * @deprecated This is a legacy method for backward compatibility with Langfuse platform - * features that still rely on trace-level input/output (e.g., legacy LLM-as-a-judge - * evaluators). It will be removed in a future major version. - * - * For setting other trace attributes (userId, sessionId, metadata, tags, version), - * use {@link propagateAttributes} instead. - * - * @param attributes - Input and output data to associate with the trace - * @returns The observation instance for method chaining - * - * @example - * ```typescript - * const span = startObservation('my-operation'); - * span.setTraceIO({ - * input: { query: 'user question' }, - * output: { response: 'assistant answer' } - * }); - * ``` + * @deprecated This is a legacy method for backward compatibility with Langfuse platform + * features that still rely on trace-level input/output (e.g., legacy LLM-as-a-judge + * evaluators). It will be removed in a future major version. + * + * For setting other trace attributes (userId, sessionId, metadata, tags, version), + * use {@link propagateAttributes} instead. + * + * @param attributes - Input and output data to associate with the trace + * @returns The observation instance for method chaining + * + * @example + * ```typescript + * const span = startObservation('my-operation'); + * span.setTraceIO({ + * input: { query: 'user question' }, + * output: { response: 'assistant answer' } + * }); + * ``` */ setTraceIO(attributes) { this.otelSpan.setAttributes(createTraceAttributes(attributes)); @@ -46455,103 +45989,574 @@ var LangfuseGeneration = class extends LangfuseBaseObservation { this.updateOtelSpanAttributes(attributes); return this; } -}; -var LangfuseEmbedding = class extends LangfuseBaseObservation { - constructor(params) { - super({ - ...params, - type: "embedding" - }); +}; +var LangfuseEmbedding = class extends LangfuseBaseObservation { + constructor(params) { + super({ + ...params, + type: "embedding" + }); + } + /** + * Updates this embedding observation with new attributes. + * + * @param attributes - Embedding attributes to set + * @returns This embedding for method chaining + */ + update(attributes) { + this.updateOtelSpanAttributes(attributes); + return this; + } +}; +var LangfuseEvent = class extends LangfuseBaseObservation { + constructor(params) { + super({ + ...params, + type: "event" + }); + this.otelSpan.end(params.timestamp); + } +}; +function createOtelSpan(params) { + return getLangfuseTracer().startSpan(params.name, { startTime: params.startTime }, createParentContext(params.parentSpanContext)); +} +function createParentContext(parentSpanContext) { + if (!parentSpanContext) return; + return trace.setSpanContext(context.active(), parentSpanContext); +} +function startObservation(name, attributes, options) { + var _a$3; + const { asType = "span", ...observationOptions } = options || {}; + const otelSpan = createOtelSpan({ + name, + ...observationOptions + }); + switch (asType) { + case "generation": return new LangfuseGeneration({ + otelSpan, + attributes + }); + case "embedding": return new LangfuseEmbedding({ + otelSpan, + attributes + }); + case "agent": return new LangfuseAgent({ + otelSpan, + attributes + }); + case "tool": return new LangfuseTool({ + otelSpan, + attributes + }); + case "chain": return new LangfuseChain({ + otelSpan, + attributes + }); + case "retriever": return new LangfuseRetriever({ + otelSpan, + attributes + }); + case "evaluator": return new LangfuseEvaluator({ + otelSpan, + attributes + }); + case "guardrail": return new LangfuseGuardrail({ + otelSpan, + attributes + }); + case "event": return new LangfuseEvent({ + otelSpan, + attributes, + timestamp: (_a$3 = observationOptions == null ? void 0 : observationOptions.startTime) != null ? _a$3 : /* @__PURE__ */ new Date() + }); + case "span": + default: return new LangfuseSpan({ + otelSpan, + attributes + }); + } +} +async function createTraceId(seed) { + if (seed) { + const data = new TextEncoder().encode(seed); + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + return uint8ArrayToHex(new Uint8Array(hashBuffer)).slice(0, 32); + } + return uint8ArrayToHex(crypto.getRandomValues(new Uint8Array(16))); +} +function uint8ArrayToHex(array$1) { + return Array.from(array$1).map((b) => b.toString(16).padStart(2, "0")).join(""); +} + +//#endregion +//#region ../../node_modules/.pnpm/@opentelemetry+context-async-hooks@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/context-async-hooks/build/src/AbstractAsyncHooksContextManager.js +var require_AbstractAsyncHooksContextManager = /* @__PURE__ */ __commonJSMin(((exports) => { + Object.defineProperty(exports, "__esModule", { value: true }); + exports.AbstractAsyncHooksContextManager = void 0; + const events_1 = __require("events"); + const ADD_LISTENER_METHODS = [ + "addListener", + "on", + "once", + "prependListener", + "prependOnceListener" + ]; + var AbstractAsyncHooksContextManager = class { + /** + * Binds a the certain context or the active one to the target function and then returns the target + * @param context A context (span) to be bind to target + * @param target a function or event emitter. When target or one of its callbacks is called, + * the provided context will be used as the active context for the duration of the call. + */ + bind(context$1, target) { + if (target instanceof events_1.EventEmitter) return this._bindEventEmitter(context$1, target); + if (typeof target === "function") return this._bindFunction(context$1, target); + return target; + } + _bindFunction(context$1, target) { + const manager = this; + const contextWrapper = function(...args) { + return manager.with(context$1, () => target.apply(this, args)); + }; + Object.defineProperty(contextWrapper, "length", { + enumerable: false, + configurable: true, + writable: false, + value: target.length + }); + /** + * It isn't possible to tell Typescript that contextWrapper is the same as T + * so we forced to cast as any here. + */ + return contextWrapper; + } + /** + * By default, EventEmitter call their callback with their context, which we do + * not want, instead we will bind a specific context to all callbacks that + * go through it. + * @param context the context we want to bind + * @param ee EventEmitter an instance of EventEmitter to patch + */ + _bindEventEmitter(context$1, ee) { + if (this._getPatchMap(ee) !== void 0) return ee; + this._createPatchMap(ee); + ADD_LISTENER_METHODS.forEach((methodName) => { + if (ee[methodName] === void 0) return; + ee[methodName] = this._patchAddListener(ee, ee[methodName], context$1); + }); + if (typeof ee.removeListener === "function") ee.removeListener = this._patchRemoveListener(ee, ee.removeListener); + if (typeof ee.off === "function") ee.off = this._patchRemoveListener(ee, ee.off); + if (typeof ee.removeAllListeners === "function") ee.removeAllListeners = this._patchRemoveAllListeners(ee, ee.removeAllListeners); + return ee; + } + /** + * Patch methods that remove a given listener so that we match the "patched" + * version of that listener (the one that propagate context). + * @param ee EventEmitter instance + * @param original reference to the patched method + */ + _patchRemoveListener(ee, original) { + const contextManager = this; + return function(event, listener) { + const events = contextManager._getPatchMap(ee)?.[event]; + if (events === void 0) return original.call(this, event, listener); + const patchedListener = events.get(listener); + return original.call(this, event, patchedListener || listener); + }; + } + /** + * Patch methods that remove all listeners so we remove our + * internal references for a given event. + * @param ee EventEmitter instance + * @param original reference to the patched method + */ + _patchRemoveAllListeners(ee, original) { + const contextManager = this; + return function(event) { + const map = contextManager._getPatchMap(ee); + if (map !== void 0) { + if (arguments.length === 0) contextManager._createPatchMap(ee); + else if (map[event] !== void 0) delete map[event]; + } + return original.apply(this, arguments); + }; + } + /** + * Patch methods on an event emitter instance that can add listeners so we + * can force them to propagate a given context. + * @param ee EventEmitter instance + * @param original reference to the patched method + * @param [context] context to propagate when calling listeners + */ + _patchAddListener(ee, original, context$1) { + const contextManager = this; + return function(event, listener) { + /** + * This check is required to prevent double-wrapping the listener. + * The implementation for ee.once wraps the listener and calls ee.on. + * Without this check, we would wrap that wrapped listener. + * This causes an issue because ee.removeListener depends on the onceWrapper + * to properly remove the listener. If we wrap their wrapper, we break + * that detection. + */ + if (contextManager._wrapped) return original.call(this, event, listener); + let map = contextManager._getPatchMap(ee); + if (map === void 0) map = contextManager._createPatchMap(ee); + let listeners = map[event]; + if (listeners === void 0) { + listeners = /* @__PURE__ */ new WeakMap(); + map[event] = listeners; + } + const patchedListener = contextManager.bind(context$1, listener); + listeners.set(listener, patchedListener); + /** + * See comment at the start of this function for the explanation of this property. + */ + contextManager._wrapped = true; + try { + return original.call(this, event, patchedListener); + } finally { + contextManager._wrapped = false; + } + }; + } + _createPatchMap(ee) { + const map = Object.create(null); + ee[this._kOtListeners] = map; + return map; + } + _getPatchMap(ee) { + return ee[this._kOtListeners]; + } + _kOtListeners = Symbol("OtListeners"); + _wrapped = false; + }; + exports.AbstractAsyncHooksContextManager = AbstractAsyncHooksContextManager; +})); + +//#endregion +//#region ../../node_modules/.pnpm/@opentelemetry+context-async-hooks@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/context-async-hooks/build/src/AsyncHooksContextManager.js +var require_AsyncHooksContextManager = /* @__PURE__ */ __commonJSMin(((exports) => { + Object.defineProperty(exports, "__esModule", { value: true }); + exports.AsyncHooksContextManager = void 0; + const api_1 = (init_esm$2(), __toCommonJS(esm_exports$2)); + const asyncHooks = __require("async_hooks"); + const AbstractAsyncHooksContextManager_1 = require_AbstractAsyncHooksContextManager(); + /** + * @deprecated Use AsyncLocalStorageContextManager instead. + */ + var AsyncHooksContextManager = class extends AbstractAsyncHooksContextManager_1.AbstractAsyncHooksContextManager { + _asyncHook; + _contexts = /* @__PURE__ */ new Map(); + _stack = []; + constructor() { + super(); + this._asyncHook = asyncHooks.createHook({ + init: this._init.bind(this), + before: this._before.bind(this), + after: this._after.bind(this), + destroy: this._destroy.bind(this), + promiseResolve: this._destroy.bind(this) + }); + } + active() { + return this._stack[this._stack.length - 1] ?? api_1.ROOT_CONTEXT; + } + with(context$1, fn, thisArg, ...args) { + this._enterContext(context$1); + try { + return fn.call(thisArg, ...args); + } finally { + this._exitContext(); + } + } + enable() { + this._asyncHook.enable(); + return this; + } + disable() { + this._asyncHook.disable(); + this._contexts.clear(); + this._stack = []; + return this; + } + /** + * Init hook will be called when userland create a async context, setting the + * context as the current one if it exist. + * @param uid id of the async context + * @param type the resource type + */ + _init(uid, type) { + if (type === "TIMERWRAP") return; + const context$1 = this._stack[this._stack.length - 1]; + if (context$1 !== void 0) this._contexts.set(uid, context$1); + } + /** + * Destroy hook will be called when a given context is no longer used so we can + * remove its attached context. + * @param uid uid of the async context + */ + _destroy(uid) { + this._contexts.delete(uid); + } + /** + * Before hook is called just before executing a async context. + * @param uid uid of the async context + */ + _before(uid) { + const context$1 = this._contexts.get(uid); + if (context$1 !== void 0) this._enterContext(context$1); + } + /** + * After hook is called just after completing the execution of a async context. + */ + _after() { + this._exitContext(); + } + /** + * Set the given context as active + */ + _enterContext(context$1) { + this._stack.push(context$1); + } + /** + * Remove the context at the root of the stack + */ + _exitContext() { + this._stack.pop(); + } + }; + exports.AsyncHooksContextManager = AsyncHooksContextManager; +})); + +//#endregion +//#region ../../node_modules/.pnpm/@opentelemetry+context-async-hooks@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/context-async-hooks/build/src/AsyncLocalStorageContextManager.js +var require_AsyncLocalStorageContextManager = /* @__PURE__ */ __commonJSMin(((exports) => { + Object.defineProperty(exports, "__esModule", { value: true }); + exports.AsyncLocalStorageContextManager = void 0; + const api_1 = (init_esm$2(), __toCommonJS(esm_exports$2)); + const async_hooks_1 = __require("async_hooks"); + const AbstractAsyncHooksContextManager_1 = require_AbstractAsyncHooksContextManager(); + var AsyncLocalStorageContextManager = class extends AbstractAsyncHooksContextManager_1.AbstractAsyncHooksContextManager { + _asyncLocalStorage; + constructor() { + super(); + this._asyncLocalStorage = new async_hooks_1.AsyncLocalStorage(); + } + active() { + return this._asyncLocalStorage.getStore() ?? api_1.ROOT_CONTEXT; + } + with(context$1, fn, thisArg, ...args) { + const cb = thisArg == null ? fn : fn.bind(thisArg); + return this._asyncLocalStorage.run(context$1, cb, ...args); + } + enable() { + return this; + } + disable() { + this._asyncLocalStorage.disable(); + return this; + } + }; + exports.AsyncLocalStorageContextManager = AsyncLocalStorageContextManager; +})); + +//#endregion +//#region ../../node_modules/.pnpm/@opentelemetry+context-async-hooks@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/context-async-hooks/build/src/index.js +var require_src$1 = /* @__PURE__ */ __commonJSMin(((exports) => { + Object.defineProperty(exports, "__esModule", { value: true }); + exports.AsyncLocalStorageContextManager = exports.AsyncHooksContextManager = void 0; + var AsyncHooksContextManager_1 = require_AsyncHooksContextManager(); + Object.defineProperty(exports, "AsyncHooksContextManager", { + enumerable: true, + get: function() { + return AsyncHooksContextManager_1.AsyncHooksContextManager; + } + }); + var AsyncLocalStorageContextManager_1 = require_AsyncLocalStorageContextManager(); + Object.defineProperty(exports, "AsyncLocalStorageContextManager", { + enumerable: true, + get: function() { + return AsyncLocalStorageContextManager_1.AsyncLocalStorageContextManager; + } + }); +})); + +//#endregion +//#region ../../node_modules/.pnpm/@opentelemetry+sdk-trace-node@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/sdk-trace-node/build/src/NodeTracerProvider.js +var require_NodeTracerProvider = /* @__PURE__ */ __commonJSMin(((exports) => { + Object.defineProperty(exports, "__esModule", { value: true }); + exports.NodeTracerProvider = void 0; + const context_async_hooks_1 = require_src$1(); + const sdk_trace_base_1 = require_src$2(); + const api_1 = (init_esm$2(), __toCommonJS(esm_exports$2)); + const core_1 = require_src$9(); + function setupContextManager(contextManager) { + if (contextManager === null) return; + if (contextManager === void 0) { + const defaultContextManager = new context_async_hooks_1.AsyncLocalStorageContextManager(); + defaultContextManager.enable(); + api_1.context.setGlobalContextManager(defaultContextManager); + return; + } + contextManager.enable(); + api_1.context.setGlobalContextManager(contextManager); + } + function setupPropagator(propagator) { + if (propagator === null) return; + if (propagator === void 0) { + api_1.propagation.setGlobalPropagator(new core_1.CompositePropagator({ propagators: [new core_1.W3CTraceContextPropagator(), new core_1.W3CBaggagePropagator()] })); + return; + } + api_1.propagation.setGlobalPropagator(propagator); } /** - * Updates this embedding observation with new attributes. + * Register this TracerProvider for use with the OpenTelemetry API. + * Undefined values may be replaced with defaults, and + * null values will be skipped. * - * @param attributes - Embedding attributes to set - * @returns This embedding for method chaining + * @param config Configuration object for SDK registration */ - update(attributes) { - this.updateOtelSpanAttributes(attributes); - return this; - } -}; -var LangfuseEvent = class extends LangfuseBaseObservation { - constructor(params) { - super({ - ...params, - type: "event" - }); - this.otelSpan.end(params.timestamp); - } -}; -function createOtelSpan(params) { - return getLangfuseTracer().startSpan(params.name, { startTime: params.startTime }, createParentContext(params.parentSpanContext)); -} -function createParentContext(parentSpanContext) { - if (!parentSpanContext) return; - return trace.setSpanContext(context.active(), parentSpanContext); -} -function startObservation(name, attributes, options) { - var _a$3; - const { asType = "span", ...observationOptions } = options || {}; - const otelSpan = createOtelSpan({ - name, - ...observationOptions + var NodeTracerProvider = class extends sdk_trace_base_1.BasicTracerProvider { + constructor(config$1 = {}) { + super(config$1); + } + /** + * Register this TracerProvider for use with the OpenTelemetry API. + * Undefined values may be replaced with defaults, and + * null values will be skipped. + * + * @param config Configuration object for SDK registration + */ + register(config$1 = {}) { + api_1.trace.setGlobalTracerProvider(this); + setupContextManager(config$1.contextManager); + setupPropagator(config$1.propagator); + } + }; + exports.NodeTracerProvider = NodeTracerProvider; +})); + +//#endregion +//#region ../../node_modules/.pnpm/@opentelemetry+sdk-trace-node@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/sdk-trace-node/build/src/index.js +var require_src = /* @__PURE__ */ __commonJSMin(((exports) => { + Object.defineProperty(exports, "__esModule", { value: true }); + exports.TraceIdRatioBasedSampler = exports.SimpleSpanProcessor = exports.SamplingDecision = exports.RandomIdGenerator = exports.ParentBasedSampler = exports.NoopSpanProcessor = exports.InMemorySpanExporter = exports.ConsoleSpanExporter = exports.BatchSpanProcessor = exports.BasicTracerProvider = exports.AlwaysOnSampler = exports.AlwaysOffSampler = exports.NodeTracerProvider = void 0; + var NodeTracerProvider_1 = require_NodeTracerProvider(); + Object.defineProperty(exports, "NodeTracerProvider", { + enumerable: true, + get: function() { + return NodeTracerProvider_1.NodeTracerProvider; + } }); - switch (asType) { - case "generation": return new LangfuseGeneration({ - otelSpan, - attributes - }); - case "embedding": return new LangfuseEmbedding({ - otelSpan, - attributes - }); - case "agent": return new LangfuseAgent({ - otelSpan, - attributes - }); - case "tool": return new LangfuseTool({ - otelSpan, - attributes - }); - case "chain": return new LangfuseChain({ - otelSpan, - attributes - }); - case "retriever": return new LangfuseRetriever({ - otelSpan, - attributes - }); - case "evaluator": return new LangfuseEvaluator({ - otelSpan, - attributes - }); - case "guardrail": return new LangfuseGuardrail({ - otelSpan, - attributes - }); - case "event": return new LangfuseEvent({ - otelSpan, - attributes, - timestamp: (_a$3 = observationOptions == null ? void 0 : observationOptions.startTime) != null ? _a$3 : /* @__PURE__ */ new Date() - }); - case "span": - default: return new LangfuseSpan({ - otelSpan, - attributes - }); - } -} -async function createTraceId(seed) { - if (seed) { - const data = new TextEncoder().encode(seed); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - return uint8ArrayToHex(new Uint8Array(hashBuffer)).slice(0, 32); - } - return uint8ArrayToHex(crypto.getRandomValues(new Uint8Array(16))); -} -function uint8ArrayToHex(array$1) { - return Array.from(array$1).map((b) => b.toString(16).padStart(2, "0")).join(""); + var sdk_trace_base_1 = require_src$2(); + Object.defineProperty(exports, "AlwaysOffSampler", { + enumerable: true, + get: function() { + return sdk_trace_base_1.AlwaysOffSampler; + } + }); + Object.defineProperty(exports, "AlwaysOnSampler", { + enumerable: true, + get: function() { + return sdk_trace_base_1.AlwaysOnSampler; + } + }); + Object.defineProperty(exports, "BasicTracerProvider", { + enumerable: true, + get: function() { + return sdk_trace_base_1.BasicTracerProvider; + } + }); + Object.defineProperty(exports, "BatchSpanProcessor", { + enumerable: true, + get: function() { + return sdk_trace_base_1.BatchSpanProcessor; + } + }); + Object.defineProperty(exports, "ConsoleSpanExporter", { + enumerable: true, + get: function() { + return sdk_trace_base_1.ConsoleSpanExporter; + } + }); + Object.defineProperty(exports, "InMemorySpanExporter", { + enumerable: true, + get: function() { + return sdk_trace_base_1.InMemorySpanExporter; + } + }); + Object.defineProperty(exports, "NoopSpanProcessor", { + enumerable: true, + get: function() { + return sdk_trace_base_1.NoopSpanProcessor; + } + }); + Object.defineProperty(exports, "ParentBasedSampler", { + enumerable: true, + get: function() { + return sdk_trace_base_1.ParentBasedSampler; + } + }); + Object.defineProperty(exports, "RandomIdGenerator", { + enumerable: true, + get: function() { + return sdk_trace_base_1.RandomIdGenerator; + } + }); + Object.defineProperty(exports, "SamplingDecision", { + enumerable: true, + get: function() { + return sdk_trace_base_1.SamplingDecision; + } + }); + Object.defineProperty(exports, "SimpleSpanProcessor", { + enumerable: true, + get: function() { + return sdk_trace_base_1.SimpleSpanProcessor; + } + }); + Object.defineProperty(exports, "TraceIdRatioBasedSampler", { + enumerable: true, + get: function() { + return sdk_trace_base_1.TraceIdRatioBasedSampler; + } + }); +})); + +//#endregion +//#region src/instrumentation.ts +var import_src = require_src(); +/** +* Configure an isolated OpenTelemetry tracer provider wired to Langfuse. +* +* We register a dedicated `NodeTracerProvider` (rather than the full auto- +* instrumenting `NodeSDK`) so the bundle stays small and free of dynamic +* instrumentation loading. Registering the provider also installs the +* AsyncLocalStorage context manager that `propagateAttributes` relies on. +* +* We use `exportMode: "batched"` and flush once at the end: the whole rollout +* is converted in-process, so batching every span into one (or a few) requests +* is far faster than one request per span — important for the hook's timeout +* budget. `shutdown()` below calls `forceFlush()` before the process exits. +*/ +function setupInstrumentation(config$1) { + const spanProcessor = new LangfuseSpanProcessor({ + publicKey: config$1.public_key, + secretKey: config$1.secret_key, + baseUrl: config$1.base_url, + environment: config$1.environment, + exportMode: "batched", + shouldExportSpan: () => true + }); + const provider = new import_src.NodeTracerProvider({ spanProcessors: [spanProcessor] }); + provider.register(); + setLangfuseTracerProvider(provider); + return { shutdown: async () => { + await spanProcessor.forceFlush(); + await provider.shutdown(); + setLangfuseTracerProvider(null); + } }; } //#endregion @@ -46627,6 +46632,16 @@ function extractMessageText(content) { return ""; }).filter(Boolean).join("\n"); } +function extractCompletedUserMessage(payload) { + const item = payload.item; + if (!item || typeof item !== "object") return ""; + if (!("type" in item) || item.type !== "UserMessage") return ""; + return extractMessageText(item.content); +} +function isInstructionWrapperMessage(text) { + const trimmed = text.trim(); + return /<\/?(environment_context|user_instructions)\b/.test(trimmed) || /^# AGENTS\.md instructions for\b/.test(trimmed); +} /** Extract reasoning text, skipping encrypted-only reasoning items. */ function extractReasoning(item) { if (typeof item.content === "string") return item.content; @@ -46741,7 +46756,7 @@ function parseSession(lines) { const s = ensureStep(ts); if (text) s.text = s.text ? `${s.text}\n${text}` : text; } else if (msg.role === "user" && text) { - if (!turn.userInputFallback && !/^<(environment_context|user_instructions)/.test(text.trim())) turn.userInputFallback = text; + if (!turn.userInputFallback && !isInstructionWrapperMessage(text)) turn.userInputFallback = text; } } else if (p.type === "function_call") { const call = p; @@ -46823,7 +46838,10 @@ function parseSession(lines) { continue; } ensureTurn(ts); - if (et === "user_message" && typeof p.message === "string") { + if (et === "item_completed") { + const text = extractCompletedUserMessage(p); + if (text && !isInstructionWrapperMessage(text) && !turn.userInput) turn.userInput = text; + } else if (et === "user_message" && typeof p.message === "string") { if (!turn.userInput) turn.userInput = p.message; } else if (et === "agent_message" && typeof p.message === "string") turn.lastAgentMessage = p.message; else if (et === "token_count") { diff --git a/plugins/tracing/src/instrumentation.ts b/plugins/tracing/src/instrumentation.ts index 7ad72dd..41392c5 100644 --- a/plugins/tracing/src/instrumentation.ts +++ b/plugins/tracing/src/instrumentation.ts @@ -1,4 +1,5 @@ import { LangfuseSpanProcessor } from "@langfuse/otel"; +import { setLangfuseTracerProvider } from "@langfuse/tracing"; import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"; import type { Config } from "./config.js"; @@ -36,11 +37,13 @@ export function setupInstrumentation(config: Config): Instrumentation { spanProcessors: [spanProcessor], }); provider.register(); + setLangfuseTracerProvider(provider); return { shutdown: async () => { await spanProcessor.forceFlush(); await provider.shutdown(); + setLangfuseTracerProvider(null); }, }; } diff --git a/plugins/tracing/src/parse.ts b/plugins/tracing/src/parse.ts index c9b3d5f..d192c03 100644 --- a/plugins/tracing/src/parse.ts +++ b/plugins/tracing/src/parse.ts @@ -31,6 +31,21 @@ function extractMessageText(content: MessageContentPart[] | undefined): string { .join("\n"); } +function extractCompletedUserMessage(payload: EventMsgPayload): string { + const item = payload.item; + if (!item || typeof item !== "object") return ""; + if (!("type" in item) || item.type !== "UserMessage") return ""; + return extractMessageText((item as { content?: MessageContentPart[] }).content); +} + +function isInstructionWrapperMessage(text: string): boolean { + const trimmed = text.trim(); + return ( + /<\/?(environment_context|user_instructions)\b/.test(trimmed) || + /^# AGENTS\.md instructions for\b/.test(trimmed) + ); +} + /** Extract reasoning text, skipping encrypted-only reasoning items. */ function extractReasoning(item: { content?: unknown[] | string | null; @@ -204,11 +219,8 @@ export function parseSession(lines: RolloutLine[]): { if (text) s.text = s.text ? `${s.text}\n${text}` : text; } else if (msg.role === "user" && text) { // Codex injects / as user - // messages; keep only the first that does not look like wrapper XML. - if ( - !turn!.userInputFallback && - !/^<(environment_context|user_instructions)/.test(text.trim()) - ) { + // messages; keep only the first that does not look like wrapper content. + if (!turn!.userInputFallback && !isInstructionWrapperMessage(text)) { turn!.userInputFallback = text; } } @@ -301,7 +313,12 @@ export function parseSession(lines: RolloutLine[]): { ensureTurn(ts); - if (et === "user_message" && typeof p.message === "string") { + if (et === "item_completed") { + const text = extractCompletedUserMessage(p); + if (text && !isInstructionWrapperMessage(text) && !turn!.userInput) { + turn!.userInput = text; + } + } else if (et === "user_message" && typeof p.message === "string") { if (!turn!.userInput) turn!.userInput = p.message; } else if (et === "agent_message" && typeof p.message === "string") { turn!.lastAgentMessage = p.message; diff --git a/plugins/tracing/test/instrumentation.test.ts b/plugins/tracing/test/instrumentation.test.ts new file mode 100644 index 0000000..9c164d5 --- /dev/null +++ b/plugins/tracing/test/instrumentation.test.ts @@ -0,0 +1,11 @@ +import * as fs from "node:fs"; + +import { describe, expect, it } from "vitest"; + +describe("setupInstrumentation", () => { + it("wires @langfuse/tracing to the registered NodeTracerProvider", () => { + const source = fs.readFileSync(new URL("../src/instrumentation.ts", import.meta.url), "utf-8"); + + expect(source).toContain("setLangfuseTracerProvider(provider)"); + }); +}); diff --git a/plugins/tracing/test/parse.test.ts b/plugins/tracing/test/parse.test.ts index b01e4ef..c428c68 100644 --- a/plugins/tracing/test/parse.test.ts +++ b/plugins/tracing/test/parse.test.ts @@ -202,6 +202,59 @@ describe("parseSession", () => { expect(turns[0].userInput).toBe("real question"); }); + it("uses item_completed UserMessage events before instruction-wrapper fallbacks", () => { + const lines: RolloutLine[] = [ + { timestamp: "2026-06-03T12:00:00.000Z", type: "session_meta", payload: { id: "s" } }, + { + timestamp: "2026-06-03T12:00:01.000Z", + type: "event_msg", + payload: { type: "task_started", turn_id: "t" }, + }, + { + timestamp: "2026-06-03T12:00:01.100Z", + type: "response_item", + payload: { + type: "message", + role: "user", + content: [ + { + type: "input_text", + text: "# AGENTS.md instructions for /repo\n\nFollow these.\n", + }, + ], + }, + }, + { + timestamp: "2026-06-03T12:00:01.200Z", + type: "response_item", + payload: { + type: "message", + role: "user", + content: [{ type: "input_text", text: 'hi from codex. say "Hi"' }], + }, + }, + { + timestamp: "2026-06-03T12:00:01.300Z", + type: "event_msg", + payload: { + type: "item_completed", + item: { + type: "UserMessage", + content: [{ type: "input_text", text: 'hi from codex. say "Hi"' }], + }, + }, + }, + { + timestamp: "2026-06-03T12:00:02.000Z", + type: "event_msg", + payload: { type: "task_complete", turn_id: "t" }, + }, + ]; + + const { turns } = parseSession(lines); + expect(turns[0].userInput).toBe('hi from codex. say "Hi"'); + }); + it("captures web search, local shell, and MCP tool calls", () => { const { turns } = parseSession(loadFixture("rollout-tools-main.jsonl"));