neatlogs 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/ai-sdk.cjs +5 -1
  2. package/dist/ai-sdk.cjs.map +1 -1
  3. package/dist/ai-sdk.mjs +6 -1
  4. package/dist/ai-sdk.mjs.map +1 -1
  5. package/dist/anthropic.cjs +5 -1
  6. package/dist/anthropic.cjs.map +1 -1
  7. package/dist/anthropic.mjs +12 -7
  8. package/dist/anthropic.mjs.map +1 -1
  9. package/dist/azure-openai.cjs +5 -1
  10. package/dist/azure-openai.cjs.map +1 -1
  11. package/dist/azure-openai.mjs +12 -7
  12. package/dist/azure-openai.mjs.map +1 -1
  13. package/dist/bedrock.cjs +5 -1
  14. package/dist/bedrock.cjs.map +1 -1
  15. package/dist/bedrock.mjs +12 -7
  16. package/dist/bedrock.mjs.map +1 -1
  17. package/dist/claude-agent-sdk.cjs +5 -1
  18. package/dist/claude-agent-sdk.cjs.map +1 -1
  19. package/dist/claude-agent-sdk.mjs +6 -1
  20. package/dist/claude-agent-sdk.mjs.map +1 -1
  21. package/dist/google-genai.cjs +5 -1
  22. package/dist/google-genai.cjs.map +1 -1
  23. package/dist/google-genai.mjs +12 -7
  24. package/dist/google-genai.mjs.map +1 -1
  25. package/dist/index.cjs +48 -9
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.ts +25 -2
  28. package/dist/index.mjs +56 -17
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/langchain.cjs +1 -0
  31. package/dist/langchain.cjs.map +1 -1
  32. package/dist/langchain.mjs +8 -6
  33. package/dist/langchain.mjs.map +1 -1
  34. package/dist/mastra-wrap.cjs +17 -1
  35. package/dist/mastra-wrap.cjs.map +1 -1
  36. package/dist/mastra-wrap.mjs +18 -1
  37. package/dist/mastra-wrap.mjs.map +1 -1
  38. package/dist/openai-agents.cjs +1 -0
  39. package/dist/openai-agents.cjs.map +1 -1
  40. package/dist/openai-agents.mjs +2 -0
  41. package/dist/openai-agents.mjs.map +1 -1
  42. package/dist/openai.cjs +5 -1
  43. package/dist/openai.cjs.map +1 -1
  44. package/dist/openai.mjs +12 -7
  45. package/dist/openai.mjs.map +1 -1
  46. package/dist/opencode-plugin.cjs +17 -8
  47. package/dist/opencode-plugin.cjs.map +1 -1
  48. package/dist/opencode-plugin.d.ts +10 -6
  49. package/dist/opencode-plugin.mjs +17 -8
  50. package/dist/opencode-plugin.mjs.map +1 -1
  51. package/dist/openrouter-agent.cjs +5 -1
  52. package/dist/openrouter-agent.cjs.map +1 -1
  53. package/dist/openrouter-agent.mjs +12 -7
  54. package/dist/openrouter-agent.mjs.map +1 -1
  55. package/dist/pi-agent.cjs +1 -0
  56. package/dist/pi-agent.cjs.map +1 -1
  57. package/dist/pi-agent.mjs +2 -0
  58. package/dist/pi-agent.mjs.map +1 -1
  59. package/dist/vertex-ai.cjs +5 -1
  60. package/dist/vertex-ai.cjs.map +1 -1
  61. package/dist/vertex-ai.mjs +12 -7
  62. package/dist/vertex-ai.mjs.map +1 -1
  63. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/anthropic.ts","../src/core/auto-root.ts","../src/core/context.ts","../src/prompt/template.ts","../src/core/logger.ts","../src/core/mask.ts","../src/core/identity.ts","../src/core/provider.ts","../src/core/end-user.ts","../src/core/session.ts","../src/decorators/base.ts"],"sourcesContent":["/**\n * Neatlogs Anthropic wrapper — ES6 Proxy-based.\n *\n * Usage:\n * import { wrapAnthropic, traceTool } from 'neatlogs/anthropic';\n * import Anthropic from '@anthropic-ai/sdk';\n * const client = wrapAnthropic(new Anthropic());\n *\n * Intercepts:\n * - client.messages.create() — non-streaming and streaming (stream: true)\n * - client.messages.stream() — Anthropic's streaming helper (MessageStream)\n *\n * Also exports traceTool() to wrap user-defined tool functions with TOOL spans.\n * Handles thinking blocks, tool_use blocks, and cache tokens.\n */\n\nimport { SpanStatusCode, type Span } from '@opentelemetry/api';\nimport { getProviderTracer } from './core/auto-root.js';\nimport {\n getNeatlogsTracer,\n getNeatlogsParentContext,\n withNeatlogsSpan,\n} from './core/provider.js';\n\nconst TRACER_NAME = 'neatlogs.anthropic';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport function wrapAnthropic<T extends object>(client: T): T {\n return wrapNamespace(client, []) as T;\n}\n\n/**\n * Wrap a tool/function implementation to emit TOOL spans when executed.\n *\n * Usage:\n * const getWeather = traceTool('get_weather', async (input: { city: string }) => {\n * return `Weather in ${input.city}: sunny`;\n * });\n */\nexport function traceTool<TInput = any, TResult = any>(\n name: string,\n fn: (input: TInput) => TResult | Promise<TResult>,\n): (input: TInput) => Promise<TResult> {\n return async function tracedTool(input: TInput): Promise<TResult> {\n const tracer = getNeatlogsTracer(TRACER_NAME);\n const span = tracer.startSpan(\n `tool.${name}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n 'neatlogs.tool.name': name,\n 'input.value': safeStringify(input),\n },\n },\n getNeatlogsParentContext(),\n );\n return withNeatlogsSpan(span, async () => {\n try {\n const result = await fn(input);\n span.setAttribute('output.value', safeStringify(result));\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n recordError(span, err);\n throw err;\n } finally {\n span.end();\n }\n });\n };\n}\n\n// ---------------------------------------------------------------------------\n// Proxy wrapping\n// ---------------------------------------------------------------------------\n\nfunction wrapNamespace(target: any, path: string[]): any {\n return new Proxy(target, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n if (typeof prop === 'symbol' || String(prop).startsWith('_')) return value;\n\n const currentPath = [...path, String(prop)];\n const pathStr = currentPath.join('.');\n\n if (pathStr === 'messages.create' && typeof value === 'function') {\n return tracedMessagesCreate(value.bind(obj));\n }\n if (pathStr === 'messages.stream' && typeof value === 'function') {\n return tracedMessagesStream(value.bind(obj));\n }\n\n if (value && typeof value === 'object' && !Array.isArray(value) && isNamespace(currentPath)) {\n return wrapNamespace(value, currentPath);\n }\n\n return value;\n },\n });\n}\n\nfunction isNamespace(path: string[]): boolean {\n if (path.length > 3) return false;\n const key = path[path.length - 1];\n return ['messages', 'beta'].includes(key);\n}\n\n// ---------------------------------------------------------------------------\n// messages.create — non-streaming + streaming (stream: true)\n// ---------------------------------------------------------------------------\n\nfunction tracedMessagesCreate(original: (...args: any[]) => any) {\n return function (opts: any, ...rest: any[]): any {\n const tracer = getProviderTracer(TRACER_NAME);\n const model = opts?.model ?? '';\n const messages: any[] = opts?.messages ?? [];\n const isStream = opts?.stream === true;\n\n const span = tracer.startSpan('anthropic.messages.create', {\n attributes: {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': 'anthropic',\n 'neatlogs.llm.system': 'anthropic',\n 'neatlogs.llm.model_name': model,\n 'neatlogs.llm.is_streaming': isStream,\n },\n }, getNeatlogsParentContext());\n\n setInputMessages(span, opts?.system, messages);\n setTools(span, opts?.tools);\n setInvocationParams(span, opts);\n\n const promise = withNeatlogsSpan(span, () => original(opts, ...rest));\n\n return promise.then(\n (response: any) => {\n if (isStream) {\n return wrapStreamIterable(response, span);\n }\n finalizeMessageResponse(span, response);\n return response;\n },\n (err: any) => {\n recordError(span, err);\n throw err;\n },\n );\n };\n}\n\n// ---------------------------------------------------------------------------\n// messages.stream() — returns a MessageStream object\n// ---------------------------------------------------------------------------\n\nfunction tracedMessagesStream(original: (...args: any[]) => any) {\n return function (opts: any, ...rest: any[]): any {\n const tracer = getProviderTracer(TRACER_NAME);\n const model = opts?.model ?? '';\n const messages: any[] = opts?.messages ?? [];\n\n const span = tracer.startSpan('anthropic.messages.stream', {\n attributes: {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': 'anthropic',\n 'neatlogs.llm.system': 'anthropic',\n 'neatlogs.llm.model_name': model,\n 'neatlogs.llm.is_streaming': true,\n },\n }, getNeatlogsParentContext());\n\n setInputMessages(span, opts?.system, messages);\n setTools(span, opts?.tools);\n setInvocationParams(span, opts);\n\n const messageStream = withNeatlogsSpan(span, () => original(opts, ...rest));\n\n return wrapMessageStream(messageStream, span);\n };\n}\n\n// ---------------------------------------------------------------------------\n// Streaming: async iterable (stream: true returns events)\n// ---------------------------------------------------------------------------\n\nfunction wrapStreamIterable(stream: any, span: Span): any {\n const events: any[] = [];\n const originalAsyncIterator = stream[Symbol.asyncIterator]?.bind(stream);\n\n if (!originalAsyncIterator) {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return stream;\n }\n\n const wrapped = Object.create(Object.getPrototypeOf(stream));\n Object.assign(wrapped, stream);\n\n wrapped[Symbol.asyncIterator] = function () {\n const iterator = originalAsyncIterator();\n return {\n async next(): Promise<IteratorResult<any>> {\n try {\n const result = await iterator.next();\n if (result.done) {\n finalizeStreamEvents(span, events);\n return result;\n }\n events.push(result.value);\n return result;\n } catch (err) {\n recordError(span, err);\n throw err;\n }\n },\n async return(value?: any): Promise<IteratorResult<any>> {\n finalizeStreamEvents(span, events);\n return iterator.return?.(value) ?? { done: true, value: undefined };\n },\n async throw(err?: any): Promise<IteratorResult<any>> {\n recordError(span, err);\n return iterator.throw?.(err) ?? { done: true, value: undefined };\n },\n };\n };\n\n if (typeof stream.finalMessage === 'function') {\n const origFinal = stream.finalMessage.bind(stream);\n wrapped.finalMessage = async function () {\n return origFinal();\n };\n }\n\n return wrapped;\n}\n\n// ---------------------------------------------------------------------------\n// Streaming: MessageStream (.on('end'), .finalMessage())\n// ---------------------------------------------------------------------------\n\nfunction wrapMessageStream(messageStream: any, span: Span): any {\n const origOn = messageStream.on?.bind(messageStream);\n if (origOn) {\n origOn('end', () => {\n const finalMsg = messageStream._finalMessage ?? messageStream.currentMessage;\n if (finalMsg) {\n finalizeMessageResponse(span, finalMsg);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n }\n });\n origOn('error', (err: any) => {\n if (span.isRecording()) recordError(span, err);\n });\n }\n\n if (typeof messageStream.finalMessage === 'function') {\n const origFinal = messageStream.finalMessage.bind(messageStream);\n messageStream.finalMessage = async function () {\n const result = await origFinal();\n if (span.isRecording()) {\n finalizeMessageResponse(span, result);\n }\n return result;\n };\n }\n\n return messageStream;\n}\n\n// ---------------------------------------------------------------------------\n// Stream event finalization\n// ---------------------------------------------------------------------------\n\nfunction finalizeStreamEvents(span: Span, events: any[]): void {\n const textParts: string[] = [];\n const thinkingParts: string[] = [];\n const toolCalls: Array<{ id: string; name: string; input: string }> = [];\n let currentToolInput = '';\n let currentToolId = '';\n let currentToolName = '';\n let usage: any = null;\n let model = '';\n let stopReason = '';\n\n for (const event of events) {\n const type = event?.type ?? '';\n\n if (type === 'content_block_delta') {\n const delta = event?.delta;\n if (delta?.type === 'text_delta' && delta.text) textParts.push(delta.text);\n else if (delta?.type === 'thinking_delta' && delta.thinking) thinkingParts.push(delta.thinking);\n else if (delta?.type === 'input_json_delta' && delta.partial_json) currentToolInput += delta.partial_json;\n } else if (type === 'content_block_start') {\n const block = event?.content_block;\n if (block?.type === 'tool_use') {\n currentToolId = block.id ?? '';\n currentToolName = block.name ?? '';\n currentToolInput = '';\n }\n } else if (type === 'content_block_stop') {\n if (currentToolName) {\n toolCalls.push({ id: currentToolId, name: currentToolName, input: currentToolInput });\n currentToolId = '';\n currentToolName = '';\n currentToolInput = '';\n }\n } else if (type === 'message_start') {\n const msg = event?.message;\n if (msg?.model) model = msg.model;\n if (msg?.usage) usage = msg.usage;\n } else if (type === 'message_delta') {\n const delta = event?.delta;\n if (delta?.stop_reason) stopReason = delta.stop_reason;\n if (event?.usage) usage = { ...(usage ?? {}), ...event.usage };\n }\n }\n\n const fullText = textParts.join('');\n if (fullText) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', fullText);\n }\n\n const thinking = thinkingParts.join('');\n if (thinking) {\n span.setAttribute('neatlogs.llm.output_messages.0.thinking', thinking);\n }\n\n for (let j = 0; j < toolCalls.length; j++) {\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, toolCalls[j].id);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, toolCalls[j].name);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, toolCalls[j].input);\n }\n\n if (model) span.setAttribute('neatlogs.llm.model_name', model);\n if (stopReason) span.setAttribute('neatlogs.llm.stop_reason', stopReason);\n setUsageAttrs(span, usage);\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Non-streaming response finalization\n// ---------------------------------------------------------------------------\n\nfunction finalizeMessageResponse(span: Span, response: any): void {\n const content: any[] = response?.content ?? [];\n const textParts: string[] = [];\n const thinkingParts: string[] = [];\n const toolCalls: Array<{ id: string; name: string; input: string }> = [];\n\n for (const block of content) {\n if (block.type === 'text' && block.text) textParts.push(block.text);\n else if (block.type === 'thinking' && block.thinking) thinkingParts.push(block.thinking);\n else if (block.type === 'tool_use') {\n toolCalls.push({\n id: block.id ?? '',\n name: block.name ?? '',\n input: safeStringify(block.input ?? {}),\n });\n }\n }\n\n if (textParts.length) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', textParts.join(''));\n }\n if (thinkingParts.length) {\n span.setAttribute('neatlogs.llm.output_messages.0.thinking', thinkingParts.join(''));\n }\n\n for (let j = 0; j < toolCalls.length; j++) {\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, toolCalls[j].id);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, toolCalls[j].name);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, toolCalls[j].input);\n }\n\n setUsageAttrs(span, response?.usage);\n if (response?.model) span.setAttribute('neatlogs.llm.model_name', response.model);\n if (response?.stop_reason) span.setAttribute('neatlogs.llm.stop_reason', response.stop_reason);\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\nfunction setInputMessages(span: Span, system: any, messages: any[]): void {\n let idx = 0;\n if (system) {\n const content = typeof system === 'string' ? system : safeStringify(system);\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'system');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, content);\n idx++;\n }\n for (const msg of messages) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, msg.role ?? '');\n if (typeof msg.content === 'string') {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, msg.content);\n } else if (msg.content) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, safeStringify(msg.content));\n }\n idx++;\n }\n}\n\nfunction setTools(span: Span, tools: any[] | undefined): void {\n if (!tools) return;\n for (let i = 0; i < tools.length; i++) {\n const tool = tools[i];\n span.setAttribute(`neatlogs.llm.tools.${i}.name`, tool.name ?? '');\n if (tool.description) span.setAttribute(`neatlogs.llm.tools.${i}.description`, tool.description);\n if (tool.input_schema) span.setAttribute(`neatlogs.llm.tools.${i}.input_schema`, safeStringify(tool.input_schema));\n }\n}\n\nfunction setInvocationParams(span: Span, opts: any): void {\n if (opts?.temperature != null) span.setAttribute('neatlogs.llm.temperature', opts.temperature);\n if (opts?.top_p != null) span.setAttribute('neatlogs.llm.top_p', opts.top_p);\n if (opts?.top_k != null) span.setAttribute('neatlogs.llm.top_k', opts.top_k);\n if (opts?.max_tokens != null) span.setAttribute('neatlogs.llm.max_tokens', opts.max_tokens);\n if (opts?.stop_sequences) span.setAttribute('neatlogs.llm.stop_sequences', safeStringify(opts.stop_sequences));\n}\n\nfunction setUsageAttrs(span: Span, usage: any): void {\n if (!usage) return;\n if (usage.input_tokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', usage.input_tokens);\n if (usage.output_tokens != null) span.setAttribute('neatlogs.llm.token_count.completion', usage.output_tokens);\n if (usage.cache_read_input_tokens != null) span.setAttribute('neatlogs.llm.token_count.cache_read', usage.cache_read_input_tokens);\n if (usage.cache_creation_input_tokens != null) span.setAttribute('neatlogs.llm.token_count.cache_write', usage.cache_creation_input_tokens);\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return typeof value === 'string' ? value : JSON.stringify(value);\n } catch {\n return '';\n }\n}\n\nfunction recordError(span: Span, err: unknown): void {\n if (err instanceof Error) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });\n span.recordException(err);\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });\n }\n span.end();\n}\n","/**\n * Auto-root for direct-provider wrappers.\n *\n * The backend only renders a trace once it contains a *parentless* span of a\n * root-eligible kind (WORKFLOW / CHAIN / AGENT / MCP_TOOL). Direct-provider\n * wrappers (wrapOpenAI, wrapAnthropic, wrapBedrock, ...) only ever emit\n * non-root spans (LLM / EMBEDDING / TOOL). So a bare\n * `const client = wrapOpenAI(new OpenAI())` with no surrounding `span()` /\n * `trace()` produces an orphan span and the trace never renders.\n *\n * `getProviderTracer(name)` returns a Tracer facade used *only* by those\n * direct-provider wrappers: when `startSpan` would otherwise produce a\n * parentless, non-root span, it transparently opens a WORKFLOW root (named\n * after the configured `workflowName`) and ends it when the provider span\n * ends. Every wrapper path — non-streaming, streaming finalize, error —\n * funnels through `span.end()`, so wrapping `end()` covers them all with no\n * changes to the wrappers' streaming code.\n *\n * Framework wrappers (langchain, mastra, openai-agents, ai-sdk, ...) keep\n * using `trace.getTracer(...)` directly — they already emit their own root and\n * thread context explicitly, so auto-root must never fire for them.\n */\n\nimport {\n trace as otelTrace,\n type Tracer,\n type Span,\n type Context,\n type SpanOptions,\n} from '@opentelemetry/api';\n\nimport { getSessionConfig } from './context.js';\nimport { applySessionAttributes } from './session.js';\nimport { applyEndUserAttributes } from './end-user.js';\nimport {\n getNeatlogsTracer,\n getNeatlogsParentContext,\n getActiveNeatlogsSpan,\n} from './provider.js';\n\n// A parentless span of one of these kinds already satisfies the backend's\n// root requirement, so it must NOT be wrapped in another root.\nconst ROOT_KINDS = new Set(['workflow', 'chain', 'agent', 'mcp_tool']);\n\nfunction autoRootEnabled(): boolean {\n const val = (process.env.NEATLOGS_AUTO_ROOT ?? '').trim().toLowerCase();\n return !['false', '0', 'no', 'off'].includes(val);\n}\n\nfunction resolveRootWorkflowName(): string {\n try {\n const name = getSessionConfig()?.workflowName;\n if (typeof name === 'string' && name.trim()) return name;\n } catch {\n /* ignore */\n }\n return 'workflow';\n}\n\n/**\n * Stamp request-scoped identify() identity onto a freshly-created auto-root.\n * The auto-root IS the trace root, and wrapper-only code has no user-controlled\n * root to put trace()/span() args on, so identity comes purely from the\n * identify() context (passed undefined per-call values fall back to it).\n */\nfunction stampAutoRootIdentity(root: Span): void {\n applySessionAttributes(root, undefined, true);\n applyEndUserAttributes(root, undefined, undefined, true);\n}\n\n/**\n * Wrap a provider span so that ending it also ends the auto-created WORKFLOW\n * root. A Proxy keeps this transparent across OTel versions: every property /\n * method except `end` passes straight through to the child span.\n */\nfunction wrapSpanWithRoot(child: Span, root: Span): Span {\n let ended = false;\n return new Proxy(child, {\n get(target, prop, _receiver) {\n if (prop === 'end') {\n return (...args: any[]): void => {\n if (ended) return;\n ended = true;\n try {\n (target.end as (...a: any[]) => void)(...args);\n } finally {\n try {\n root.end();\n } catch {\n /* ignore */\n }\n }\n };\n }\n const value = Reflect.get(target, prop, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n\n/**\n * Self-root helper for callback/event-driven handlers (LangChain, etc.).\n *\n * A callback handler builds its own parent context per run-id rather than going\n * through {@link AutoRootTracer}. When a run BEGINS with a non-root kind (a bare\n * `llm.invoke()` fires the LLM callback with no chain above it; a standalone\n * tool/retriever similarly), the span it creates is parentless and non-root, so\n * the backend can't anchor the trace and it never renders. Call this when there\n * is no LangChain parent: if the kind is non-root and nothing is actively\n * recording in `baseCtx`, it opens a WORKFLOW root and returns it plus the\n * child context to create the span in. The caller MUST end the returned\n * `root` after the child span ends (see {@link endAutoRoot}).\n *\n * Returns `{ root, ctx }` when a root was opened, or `{ root: undefined, ctx: baseCtx }`\n * when no auto-root is needed (root-eligible kind, already-recording parent, or\n * disabled).\n */\nexport function maybeOpenAutoRoot(\n tracer: Tracer,\n kind: string,\n baseCtx: Context,\n): { root: Span | undefined; ctx: Context } {\n const k = String(kind ?? '').toLowerCase();\n // A foreign provider's span must never count as our parent.\n const existing = getActiveNeatlogsSpan();\n if (\n !autoRootEnabled() ||\n ROOT_KINDS.has(k) ||\n (existing !== undefined && existing.isRecording())\n ) {\n return { root: undefined, ctx: baseCtx };\n }\n const root = tracer.startSpan(\n resolveRootWorkflowName(),\n { attributes: { 'neatlogs.span.kind': 'workflow', 'neatlogs.auto_root': true } },\n baseCtx,\n );\n stampAutoRootIdentity(root);\n return { root, ctx: otelTrace.setSpan(baseCtx, root) };\n}\n\n/** End an auto-root opened by {@link maybeOpenAutoRoot}. Safe on undefined. */\nexport function endAutoRoot(root: Span | undefined): void {\n if (!root) return;\n try {\n root.end();\n } catch {\n /* ignore */\n }\n}\n\nclass AutoRootTracer implements Tracer {\n constructor(private readonly _tracer: Tracer) {}\n\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const kind = String(\n (options?.attributes as Record<string, unknown> | undefined)?.[\n 'neatlogs.span.kind'\n ] ?? '',\n ).toLowerCase();\n\n // Parent from our private context, never the foreign global.\n const ctx = context ?? getNeatlogsParentContext();\n const parent = getActiveNeatlogsSpan();\n const needsRoot =\n autoRootEnabled() &&\n !ROOT_KINDS.has(kind) &&\n !(parent !== undefined && parent.isRecording());\n\n if (!needsRoot) {\n return this._tracer.startSpan(name, options, ctx);\n }\n\n const root = this._tracer.startSpan(\n resolveRootWorkflowName(),\n { attributes: { 'neatlogs.span.kind': 'workflow', 'neatlogs.auto_root': true } },\n ctx,\n );\n stampAutoRootIdentity(root);\n const childCtx = otelTrace.setSpan(ctx, root);\n const child = this._tracer.startSpan(name, options, childCtx);\n return wrapSpanWithRoot(child, root);\n }\n\n // startActiveSpan is used by traceTool (callback-scoped, always under an\n // active span in practice) — pass straight through, no auto-root.\n startActiveSpan = ((...args: any[]) =>\n (this._tracer.startActiveSpan as (...a: any[]) => any)(\n ...args,\n )) as Tracer['startActiveSpan'];\n}\n\n/**\n * Tracer for direct-provider wrappers (wrapOpenAI, wrapAnthropic, ...).\n * Identical to `trace.getTracer(name)` but adds transparent auto-root so a\n * bare `wrapOpenAI(new OpenAI())` renders a trace without a manual `span()` /\n * `trace()` wrapper. Do NOT use for framework wrappers.\n */\nexport function getProviderTracer(name: string): Tracer {\n // Resolve from the private provider.\n return new AutoRootTracer(getNeatlogsTracer(name));\n}\n","/**\n * Context wrapper for manual span creation.\n *\n * Provides the `trace()` function — the TypeScript equivalent of the Python\n * `trace()` context manager. Creates an OTel span, sets prompt template/variable\n * context, and executes the user callback within the span.\n *\n * @example\n * ```typescript\n * await trace({ name: 'my-trace' }, async (span) => {\n * // user code runs here with the span active\n * });\n * ```\n */\n\nimport {\n trace as otelTrace,\n context as otelContext,\n createContextKey,\n ROOT_CONTEXT,\n SpanStatusCode,\n type Span,\n} from '@opentelemetry/api';\nimport { PromptContext, UserPromptContext, PromptTemplate, UserPromptTemplate } from '../prompt/template.js';\nimport { registerMask } from './mask.js';\nimport { applyEndUserAttributes } from './end-user.js';\nimport { applySessionAttributes } from './session.js';\nimport { currentSessionId } from './identity.js';\n\nimport { getLogger } from './logger.js';\nimport { safeJsonDumps, serializeObj } from '../decorators/base.js';\nimport type { TraceOptions, MaskFunction } from '../types.js';\nimport {\n getActiveNeatlogsSpan,\n getNeatlogsActiveContext,\n getNeatlogsParentContext,\n getNeatlogsTracer,\n withNeatlogsSpan,\n} from './provider.js';\n\nconst logger = getLogger();\n\n// ---------------------------------------------------------------------------\n// Module-level session config (set by init.ts via _setSessionConfig)\n// ---------------------------------------------------------------------------\n\nlet _sessionConfig: Record<string, any> = {};\n\n/**\n * Set the session configuration. Called by init() during SDK setup.\n * @internal\n */\nexport function _setSessionConfig(config: Record<string, any>): void {\n _sessionConfig = config;\n}\n\n/**\n * Get a copy of the current session configuration.\n */\nexport function getSessionConfig(): Record<string, any> {\n return { ..._sessionConfig };\n}\n\n// ---------------------------------------------------------------------------\n// OTel context keys for prompt data propagation\n// ---------------------------------------------------------------------------\n\nexport const PROMPT_VARIABLES_KEY = createContextKey('neatlogs.prompt_variables');\nexport const PROMPT_TEMPLATE_KEY = createContextKey('neatlogs.prompt_template');\nexport const PROMPT_VERSION_KEY = createContextKey('neatlogs.prompt_version');\nexport const USER_PROMPT_TEMPLATE_KEY = createContextKey('neatlogs.user_prompt_template');\nexport const USER_PROMPT_VARIABLES_KEY = createContextKey('neatlogs.user_prompt_variables');\n\n// ---------------------------------------------------------------------------\n// Known TraceOptions keys (not forwarded as extra span attributes)\n// ---------------------------------------------------------------------------\n\nconst KNOWN_OPTION_KEYS = new Set([\n 'name',\n 'kind',\n 'input',\n 'promptTemplate',\n 'promptVariables',\n 'userPromptTemplate',\n 'userPromptVariables',\n 'version',\n 'mask',\n 'attributes',\n 'sessionId',\n 'endUserId',\n 'endUserMetadata',\n]);\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Set standard and extra attributes on a span.\n * @internal\n */\nexport function _setSpanAttributes(\n span: Span,\n kind: string | undefined,\n attributes: Record<string, any>,\n): void {\n span.setAttribute('neatlogs.internal', true);\n span.setAttribute('openinference.span.kind', kind ?? 'CHAIN');\n\n for (const [key, value] of Object.entries(attributes)) {\n span.setAttribute(key, value);\n }\n}\n\n/**\n * After the user callback completes, capture prompt variables that were\n * set by PromptTemplate.compile() / UserPromptTemplate.compile() and\n * record them as span attributes.\n * @internal\n */\nexport function _finalizePromptCapture(\n span: Span,\n isPromptTemplateObj: boolean,\n isUserPromptTemplateObj: boolean,\n): void {\n if (isPromptTemplateObj) {\n const capturedVars = PromptContext.getVariables();\n if (capturedVars) {\n span.setAttribute(\n 'llm.prompt_template_variables',\n JSON.stringify(capturedVars),\n );\n logger.debug(\n `[trace] Auto-captured variables from PromptContext: ${Object.keys(capturedVars).join(', ')}`,\n );\n }\n }\n\n if (isUserPromptTemplateObj) {\n const capturedUserVars = UserPromptContext.getVariables();\n if (capturedUserVars) {\n span.setAttribute(\n 'llm.user_prompt_template_variables',\n JSON.stringify(capturedUserVars),\n );\n logger.debug(\n `[trace] Auto-captured variables from UserPromptContext: ${Object.keys(capturedUserVars).join(', ')}`,\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main trace() function\n// ---------------------------------------------------------------------------\n\n/**\n * Async callback wrapper for manual span creation with prompt tracking.\n *\n * Creates an OTel span, optionally sets prompt template/variable context,\n * and executes the provided callback within the span.\n *\n * **Session-Aware Trace Creation:**\n * - If a session id is resolved (per-call `sessionId` or the request-scoped\n * `identify()` context) AND no active parent span exists, this creates a NEW\n * root trace (for multi-turn conversations).\n * - Otherwise, creates a normal child span within the existing trace.\n *\n * @param options - Trace configuration options\n * @param fn - Callback that receives the active span\n * @returns The return value of the callback\n *\n * @example\n * ```typescript\n * // Basic usage\n * await trace({ name: 'my-pipeline' }, async (span) => {\n * await step1();\n * await step2();\n * });\n *\n * // With prompt template\n * const template = new PromptTemplate('Hello {{name}}');\n * await trace({ name: 'prompt', promptTemplate: template }, async (span) => {\n * const rendered = template.compile({ name: 'world' });\n * // ...\n * });\n * ```\n */\nexport async function trace<T>(\n options: TraceOptions,\n fn: (span: Span) => T | Promise<T>,\n): Promise<T> {\n const {\n name,\n kind,\n input,\n promptTemplate,\n promptVariables,\n userPromptTemplate,\n userPromptVariables,\n version,\n mask,\n sessionId: sessionIdOption,\n endUserId,\n endUserMetadata,\n attributes: explicitAttributes,\n ...extraOptions\n } = options;\n\n // Per-call sessionId wins, else the request-scoped identify() context.\n // Session identity is NOT process-global — init() never sets it.\n const sessionId = sessionIdOption ?? currentSessionId();\n\n // Determine whether we are inside an existing active trace\n const currentSpan = getActiveNeatlogsSpan();\n const isInActiveTrace = currentSpan !== undefined && currentSpan.isRecording();\n const shouldCreateRootTrace = !!sessionId && !isInActiveTrace;\n\n // ---------------------------------------------------------------------------\n // Process prompt templates\n // ---------------------------------------------------------------------------\n\n let templateString: string | undefined;\n let isPromptTemplateObj = false;\n\n if (promptTemplate !== undefined) {\n if (promptTemplate instanceof PromptTemplate) {\n isPromptTemplateObj = true;\n const t = promptTemplate.template;\n templateString = typeof t === 'string' ? t : JSON.stringify(t);\n logger.debug(\n `[trace] Using PromptTemplate object with variables: ${promptTemplate.variables.join(', ')}`,\n );\n } else if (typeof promptTemplate === 'string') {\n templateString = promptTemplate;\n } else {\n const t = promptTemplate.template;\n templateString = typeof t === 'string' ? t : JSON.stringify(t);\n }\n }\n\n let userTemplateString: string | undefined;\n let isUserPromptTemplateObj = false;\n\n if (userPromptTemplate !== undefined) {\n if (userPromptTemplate instanceof UserPromptTemplate) {\n isUserPromptTemplateObj = true;\n const t = userPromptTemplate.template;\n userTemplateString = typeof t === 'string' ? t : JSON.stringify(t);\n logger.debug(\n `[trace] Using UserPromptTemplate object with variables: ${userPromptTemplate.variables.join(', ')}`,\n );\n } else if (typeof userPromptTemplate === 'string') {\n userTemplateString = userPromptTemplate;\n } else {\n const t = userPromptTemplate.template;\n userTemplateString = typeof t === 'string' ? t : JSON.stringify(t);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Build OTel context with prompt values\n // ---------------------------------------------------------------------------\n\n // Start from the Neatlogs active context (private store in isolated mode), NOT\n // otelContext.active() — the latter is the foreign co-tenant's context, whose\n // active span must never become our parent. Prompt values threaded here then\n // propagate to descendant LLM spans through the private store.\n let ctx = getNeatlogsActiveContext();\n\n const variablesJson = promptVariables ? JSON.stringify(promptVariables) : undefined;\n const userVariablesJson = userPromptVariables ? JSON.stringify(userPromptVariables) : undefined;\n\n if (variablesJson) {\n ctx = ctx.setValue(PROMPT_VARIABLES_KEY, variablesJson);\n logger.debug(`[trace] Set neatlogs.prompt_variables in context: ${variablesJson}`);\n }\n if (templateString) {\n ctx = ctx.setValue(PROMPT_TEMPLATE_KEY, templateString);\n logger.debug(`[trace] Set neatlogs.prompt_template in context: ${templateString}`);\n }\n if (userVariablesJson) {\n ctx = ctx.setValue(USER_PROMPT_VARIABLES_KEY, userVariablesJson);\n logger.debug(`[trace] Set neatlogs.user_prompt_variables in context: ${userVariablesJson}`);\n }\n if (userTemplateString) {\n ctx = ctx.setValue(USER_PROMPT_TEMPLATE_KEY, userTemplateString);\n logger.debug(`[trace] Set neatlogs.user_prompt_template in context: ${userTemplateString}`);\n }\n if (version) {\n ctx = ctx.setValue(PROMPT_VERSION_KEY, version);\n logger.debug(`[trace] Set neatlogs.prompt_version in context: ${version}`);\n }\n\n // ---------------------------------------------------------------------------\n // Collect extra attributes (non-standard option keys)\n // ---------------------------------------------------------------------------\n\n const extraAttributes: Record<string, any> = { ...(explicitAttributes ?? {}) };\n for (const [key, value] of Object.entries(extraOptions)) {\n if (!KNOWN_OPTION_KEYS.has(key)) {\n extraAttributes[key] = value;\n }\n }\n\n // End-user belongs to the trace root only. This trace() call is a root when it\n // creates a new root trace, or when it isn't nested in an already-active trace.\n const isRootTrace = shouldCreateRootTrace || !isInActiveTrace;\n\n // ---------------------------------------------------------------------------\n // Create span and execute callback\n // ---------------------------------------------------------------------------\n\n const tracer = getNeatlogsTracer('neatlogs.trace');\n\n const spanCallback = async (span: Span): Promise<T> => {\n _setSpanAttributes(span, kind, extraAttributes);\n\n // Session/end-user belong to the trace root only; skipped on a non-root span.\n applySessionAttributes(span, sessionId, isRootTrace);\n applyEndUserAttributes(span, endUserId, endUserMetadata, isRootTrace);\n\n if (input !== undefined && input !== null) {\n span.setAttribute('input.value', safeJsonDumps(serializeObj(input)));\n }\n\n if (mask) {\n const maskId = registerMask(mask);\n span.setAttribute('neatlogs.mask_id', maskId);\n }\n\n try {\n const result: T = await fn(span);\n\n _finalizePromptCapture(span, isPromptTemplateObj, isUserPromptTemplateObj);\n\n if (result !== undefined && result !== null) {\n const spanAttrs = (span as any).attributes ?? (span as any)._attributes;\n const hasOutput = spanAttrs && (\n 'output.value' in spanAttrs || 'neatlogs.output.value' in spanAttrs\n );\n if (!hasOutput) {\n span.setAttribute('output.value', safeJsonDumps(serializeObj(result)));\n }\n }\n\n return result;\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n } finally {\n if (isPromptTemplateObj) {\n PromptContext.clear();\n }\n if (isUserPromptTemplateObj) {\n UserPromptContext.clear();\n }\n span.end();\n }\n };\n\n const parentContext = shouldCreateRootTrace\n ? ROOT_CONTEXT\n : getNeatlogsParentContext(ctx);\n // Seed the span kind at CREATION, not just in the callback: the span processor\n // classifies LLM spans in onStart() (which fires from startSpan, before the\n // callback runs). Without this, a trace({ kind: 'LLM', promptTemplate }) span\n // isn't recognized as LLM in time and its prompt-template attributes are dropped.\n const startAttributes = kind ? { 'openinference.span.kind': kind } : undefined;\n const span = tracer.startSpan(name, startAttributes ? { attributes: startAttributes } : {}, parentContext);\n\n if (shouldCreateRootTrace) {\n logger.debug(`[trace] Creating NEW root trace '${name}' (sessionId=${sessionId})`);\n } else {\n logger.debug(`[trace] Creating child span '${name}'`);\n }\n\n return withNeatlogsSpan(span, () => spanCallback(span), ctx);\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { PromptMessage } from '../types.js';\n\n// ---------------------------------------------------------------------------\n// Async-local context for system prompts\n// ---------------------------------------------------------------------------\n\ninterface PromptContextData {\n template: string;\n variables: Record<string, any>;\n}\n\nconst _promptStorage = new AsyncLocalStorage<PromptContextData>();\n\n/**\n * Manages system prompt metadata in async-local context for automatic tracing.\n */\nexport class PromptContext {\n /**\n * Store system prompt template and variables in context.\n */\n static set(template: string, variables: Record<string, any>): void {\n // enterWith mutates the current async context in-place\n _promptStorage.enterWith({ template, variables });\n }\n\n /**\n * Retrieve system prompt template from context.\n */\n static getTemplate(): string | undefined {\n return _promptStorage.getStore()?.template;\n }\n\n /**\n * Retrieve system prompt variables from context.\n */\n static getVariables(): Record<string, any> | undefined {\n return _promptStorage.getStore()?.variables;\n }\n\n /**\n * Clear system prompt context.\n */\n static clear(): void {\n _promptStorage.enterWith(undefined as unknown as PromptContextData);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Async-local context for user prompts\n// ---------------------------------------------------------------------------\n\nconst _userPromptStorage = new AsyncLocalStorage<PromptContextData>();\n\n/**\n * Manages user/human prompt metadata in async-local context for automatic tracing.\n */\nexport class UserPromptContext {\n /**\n * Store user prompt template and variables in context.\n */\n static set(template: string, variables: Record<string, any>): void {\n _userPromptStorage.enterWith({ template, variables });\n }\n\n /**\n * Retrieve user prompt template from context.\n */\n static getTemplate(): string | undefined {\n return _userPromptStorage.getStore()?.template;\n }\n\n /**\n * Retrieve user prompt variables from context.\n */\n static getVariables(): Record<string, any> | undefined {\n return _userPromptStorage.getStore()?.variables;\n }\n\n /**\n * Clear user prompt context.\n */\n static clear(): void {\n _userPromptStorage.enterWith(undefined as unknown as PromptContextData);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared context setter type for the base template class\n// ---------------------------------------------------------------------------\n\ninterface ContextSetter {\n set(template: string, variables: Record<string, any>): void;\n}\n\n// ---------------------------------------------------------------------------\n// BasePromptTemplate — shared logic for system and user prompts\n// ---------------------------------------------------------------------------\n\nconst VARIABLE_PATTERN = /\\{\\{(\\w+)\\}\\}/g;\n\n/**\n * Base class for prompt templates with `{{variable}}` placeholders.\n *\n * Extracts variables, compiles templates, and renders strings.\n * Subclasses only need to specify the context class and display name.\n */\nabstract class BasePromptTemplate {\n protected readonly _template: string | PromptMessage[];\n protected readonly _variables: string[];\n\n /**\n * @param template - Either a string with `{{variable}}` placeholders or\n * an array of `PromptMessage` objects whose `content` fields contain placeholders.\n */\n constructor(template: string | PromptMessage[]) {\n this._template = template;\n this._variables = this._extractVariables();\n }\n\n /** List of unique variable names found in this template. */\n get variables(): string[] {\n return this._variables;\n }\n\n /** The raw template (string or message array). */\n get template(): string | PromptMessage[] {\n return this._template;\n }\n\n /** The context class to store template/variables for automatic tracing. */\n protected abstract get _contextSetter(): ContextSetter;\n\n /** Display name for toString(). */\n protected abstract get _displayName(): string;\n\n /**\n * Compile the prompt template with the given variables.\n *\n * @param variables - Key/value pairs to substitute for `{{key}}` placeholders.\n * @returns The rendered string or rendered message array.\n * @throws {Error} If any required variables are missing.\n */\n compile(variables?: Record<string, any>): string | PromptMessage[] {\n const vars = variables ?? {};\n\n const missing = this._variables.filter((v) => !(v in vars));\n if (missing.length > 0) {\n throw new Error(\n `Missing required variables: ${missing.join(', ')}. Template requires: ${this._variables.join(', ')}`,\n );\n }\n\n this._contextSetter.set(\n typeof this._template === 'string'\n ? this._template\n : this._template.map((msg) => `${msg.role}: ${msg.content}`).join('\\n'),\n vars,\n );\n\n if (typeof this._template === 'string') {\n return this._renderString(this._template, vars);\n }\n\n return this._template.map((msg) => ({\n role: msg.role,\n content: this._renderString(msg.content, vars),\n }));\n }\n\n /**\n * Replace `{{key}}` placeholders in a string with the corresponding values.\n */\n _renderString(text: string, variables: Record<string, any>): string {\n let result = text;\n for (const [key, value] of Object.entries(variables)) {\n result = result.replaceAll(`{{${key}}}`, String(value));\n }\n return result;\n }\n\n toString(): string {\n if (typeof this._template === 'string') {\n return this._template.length > 50\n ? `${this._displayName}('${this._template.slice(0, 50)}...')`\n : `${this._displayName}('${this._template}')`;\n }\n return `${this._displayName}(${this._template.length} messages, variables=${JSON.stringify(this._variables)})`;\n }\n\n // ---- private ----\n\n private _extractVariables(): string[] {\n if (typeof this._template === 'string') {\n return [...new Set(Array.from(this._template.matchAll(VARIABLE_PATTERN), (m) => m[1]))];\n }\n\n const found: string[] = [];\n for (const msg of this._template) {\n if (msg.content) {\n for (const match of msg.content.matchAll(VARIABLE_PATTERN)) {\n found.push(match[1]);\n }\n }\n }\n return [...new Set(found)];\n }\n}\n\n// ---------------------------------------------------------------------------\n// PromptTemplate — system/AI instruction prompt\n// ---------------------------------------------------------------------------\n\n/**\n * Template for the system/AI instruction prompt with `{{variable}}` placeholders.\n */\nexport class PromptTemplate extends BasePromptTemplate {\n protected get _contextSetter(): ContextSetter {\n return PromptContext;\n }\n\n protected get _displayName(): string {\n return 'PromptTemplate';\n }\n}\n\n// ---------------------------------------------------------------------------\n// UserPromptTemplate — user/human turn prompt\n// ---------------------------------------------------------------------------\n\n/**\n * Template for the user/human turn prompt with `{{variable}}` placeholders.\n *\n * Identical to {@link PromptTemplate} but stores context in {@link UserPromptContext}.\n */\nexport class UserPromptTemplate extends BasePromptTemplate {\n protected get _contextSetter(): ContextSetter {\n return UserPromptContext;\n }\n\n protected get _displayName(): string {\n return 'UserPromptTemplate';\n }\n}\n","/**\n * Centralized logging configuration for Neatlogs SDK.\n * Provides structured logging with configurable levels.\n */\n\ntype LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'NONE';\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n DEBUG: 0,\n INFO: 1,\n WARN: 2,\n ERROR: 3,\n NONE: 4,\n};\n\nlet _logLevel: LogLevel = 'INFO';\nlet _disabled = false;\n\nfunction _getConfiguredLevel(): LogLevel {\n const envLevel = process.env.NEATLOGS_LOG_LEVEL?.toUpperCase();\n if (envLevel && envLevel in LOG_LEVELS) {\n return envLevel as LogLevel;\n }\n return 'INFO';\n}\n\n// Initialize from env on first import\n_logLevel = _getConfiguredLevel();\n\nexport function getLogger() {\n return {\n debug(message: string, ...args: any[]) {\n if (!_disabled && LOG_LEVELS[_logLevel] <= LOG_LEVELS.DEBUG) {\n console.debug(`[neatlogs] ${message}`, ...args);\n }\n },\n info(message: string, ...args: any[]) {\n if (!_disabled && LOG_LEVELS[_logLevel] <= LOG_LEVELS.INFO) {\n console.info(`[neatlogs] ${message}`, ...args);\n }\n },\n warn(message: string, ...args: any[]) {\n if (!_disabled && LOG_LEVELS[_logLevel] <= LOG_LEVELS.WARN) {\n console.warn(`[neatlogs] ${message}`, ...args);\n }\n },\n error(message: string, ...args: any[]) {\n if (!_disabled && LOG_LEVELS[_logLevel] <= LOG_LEVELS.ERROR) {\n console.error(`[neatlogs] ${message}`, ...args);\n }\n },\n };\n}\n\nexport function setLogLevel(level: LogLevel): void {\n _logLevel = level;\n}\n\nexport function enableDebugLogging(): void {\n _logLevel = 'DEBUG';\n}\n\nexport function disableLogging(): void {\n _disabled = true;\n}\n\nexport function enableLogging(): void {\n _disabled = false;\n}\n","/**\n * PII masking support for Neatlogs spans.\n *\n * Users supply a callable that receives the full span dict and returns\n * the (possibly modified) span dict. Return null to drop the span entirely.\n */\n\nimport type { MaskFunction } from '../types.js';\nimport { getLogger } from './logger.js';\n\nconst logger = getLogger();\n\n/** Module-level registry: key -> mask function */\nconst _MASK_REGISTRY = new Map<string, MaskFunction>();\n\nlet _nextId = 0;\n\n/**\n * Register a mask function and return its lookup key.\n */\nexport function registerMask(fn: MaskFunction): string {\n const key = String(++_nextId);\n _MASK_REGISTRY.set(key, fn);\n return key;\n}\n\n/**\n * Apply the effective mask function to span data.\n *\n * Per-span mask (stored in attributes[\"neatlogs.mask_id\"]) takes\n * precedence over the global mask. Returns the (possibly modified) dict.\n */\nexport function applyMask(\n spanData: Record<string, any>,\n globalMask: MaskFunction | null | undefined,\n): Record<string, any> | null {\n const maskId = spanData?.attributes?.['neatlogs.mask_id'];\n let maskFn: MaskFunction | undefined | null = null;\n\n if (maskId) {\n maskFn = _MASK_REGISTRY.get(String(maskId)) ?? null;\n }\n\n if (!maskFn) {\n maskFn = globalMask ?? null;\n }\n\n if (!maskFn) {\n return spanData;\n }\n\n try {\n const result = maskFn(spanData);\n // null means \"drop this span entirely\"; undefined means \"keep original\"\n if (result === null) return null;\n return result !== undefined ? result : spanData;\n } catch (exc) {\n logger.warn(\n `mask callable raised an exception for span '${spanData?.name}': ${exc} — original span data will be exported unchanged.`,\n );\n return spanData;\n }\n}\n\n/**\n * Clear all registered masks. Used for testing.\n * @internal\n */\nexport function _clearMaskRegistry(): void {\n _MASK_REGISTRY.clear();\n _nextId = 0;\n}\n","/**\n * Request-scoped session & end-user identity.\n *\n * Session and end-user identity are PER-REQUEST, not process-global, so they are\n * never set on `init()`. The primary way to set them is at the trace root\n * (`trace({ sessionId })` / `span({ sessionId })`). For code that only uses\n * `wrap()` — where the trace root is created internally by the wrapper and the\n * caller has no root to put arguments on — use the `identify()` context:\n *\n * await neatlogs.identify(\n * { sessionId: 'chat_123', endUserId: 'user_456', endUserMetadata: { plan: 'pro' } },\n * async () => {\n * await wrappedClient.chat.completions.create(...); // auto-root reads this\n * },\n * );\n *\n * Resolution at the trace root (per field): explicit per-call argument wins, then\n * the `identify()` context, then nothing. Identity is stamped on the ROOT span\n * only; the backend rolls it up to the trace and its session.\n *\n * Backed by `AsyncLocalStorage` so it propagates across `await` boundaries (and\n * nested async tasks), exactly like OpenTelemetry's own context propagation.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nexport interface IdentityStore {\n sessionId?: string;\n endUserId?: string;\n endUserMetadata?: Record<string, any>;\n}\n\nexport interface IdentifyOptions {\n sessionId?: string;\n endUserId?: string;\n endUserMetadata?: Record<string, any>;\n}\n\n// tsup bundles each entry point (index, openai, azure-openai, ...) separately,\n// so a plain module-level `new AsyncLocalStorage()` would be DUPLICATED per\n// bundle — identify() (in index) would write to a different store than a\n// provider wrapper's auto-root (in e.g. azure-openai) reads, silently losing\n// identity on the wrapper-only path. Pin a single instance on globalThis so all\n// bundled copies (ESM + CJS) resolve to the same store.\nconst _GLOBAL_KEY = Symbol.for('neatlogs.identity.storage');\ntype _GlobalWithStore = typeof globalThis & {\n [_GLOBAL_KEY]?: AsyncLocalStorage<IdentityStore>;\n};\nconst _g = globalThis as _GlobalWithStore;\nconst _storage: AsyncLocalStorage<IdentityStore> =\n _g[_GLOBAL_KEY] ?? (_g[_GLOBAL_KEY] = new AsyncLocalStorage<IdentityStore>());\n\nexport function currentSessionId(): string | undefined {\n return _storage.getStore()?.sessionId;\n}\n\nexport function currentEndUserId(): string | undefined {\n return _storage.getStore()?.endUserId;\n}\n\nexport function currentEndUserMetadata(): Record<string, any> | undefined {\n return _storage.getStore()?.endUserMetadata;\n}\n\n/**\n * Bind session / end-user identity for the duration of `fn`.\n *\n * Only defined fields are set, so a nested `identify()` overrides one field\n * without clearing the others (it merges onto the enclosing store). The previous\n * store is automatically restored when `fn` returns / rejects. Returns whatever\n * `fn` returns (sync value or Promise).\n *\n * @example\n * ```typescript\n * await neatlogs.identify({ sessionId: 'chat_123' }, async () => {\n * await wrappedClient.chat.completions.create(...);\n * });\n * ```\n */\nexport function identify<T>(opts: IdentifyOptions, fn: () => T): T {\n const prev = _storage.getStore();\n const next: IdentityStore = { ...(prev ?? {}) };\n if (opts.sessionId !== undefined) next.sessionId = opts.sessionId;\n if (opts.endUserId !== undefined) next.endUserId = opts.endUserId;\n if (opts.endUserMetadata !== undefined) next.endUserMetadata = opts.endUserMetadata;\n return _storage.run(next, fn);\n}\n","/**\n * Neatlogs-owned tracing state.\n *\n * Spans are created by the private Neatlogs provider and their parent is carried\n * in a private context key. The process-global OpenTelemetry span is\n * deliberately left untouched so other observability SDKs cannot export or\n * become parents of Neatlogs spans.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n ROOT_CONTEXT,\n INVALID_SPAN_CONTEXT,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n type TracerProvider,\n} from '@opentelemetry/api';\n\n// Entry points are bundled independently (`neatlogs`, `neatlogs/openai`,\n// `neatlogs/ai`, `neatlogs/mastra`, … each in both CJS and ESM), so every piece\n// of shared tracing state — the private span store AND the resolved provider /\n// provider — must live on `globalThis` behind a `Symbol.for` key.\n// Otherwise `init()` (run from the `neatlogs` bundle) sets `_provider` in ITS\n// module copy while a wrapper imported from `neatlogs/openai` reads a different,\n// still-null copy and silently falls back to the foreign global provider.\nconst PRIVATE_SPAN_STORAGE_KEY = Symbol.for(\n 'neatlogs.private_span_async_local_storage',\n);\nconst PRIVATE_PROVIDER_STATE_KEY = Symbol.for(\n 'neatlogs.private_provider_state',\n);\ninterface PrivateProviderState {\n provider: TracerProvider | null;\n}\ntype NeatlogsGlobal = typeof globalThis & {\n [PRIVATE_SPAN_STORAGE_KEY]?: AsyncLocalStorage<Context>;\n [PRIVATE_PROVIDER_STATE_KEY]?: PrivateProviderState;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\n// Stores the full Neatlogs Context (parent span PLUS any threaded values such as\n// trace()'s prompt-template keys), not just the span. We never\n// activate the OTel global context, so this private store is the ONLY channel\n// through which those values propagate down to descendant spans.\nconst privateContextStorage =\n neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ??\n (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new AsyncLocalStorage<Context>());\nconst providerState: PrivateProviderState =\n neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ??\n (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {\n provider: null,\n });\n// Wrappers may be constructed or accidentally invoked before init(). They must\n// never fall back to a foreign process-global provider, so pre-init calls use a\n// local no-op tracer and safely emit no exported spans.\nconst preInitTracer: Tracer = {\n startSpan(): Span {\n return otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n _name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n const fn =\n typeof arg2 === 'function'\n ? arg2\n : typeof arg3 === 'function'\n ? arg3\n : arg4;\n return fn!(otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT)) as ReturnType<F>;\n },\n};\n\n/** @internal Configure the provider used by Neatlogs-created spans. */\nexport function _setNeatlogsProvider(provider: TracerProvider | null): void {\n providerState.provider = provider;\n}\n\n/** Resolve a tracer from the private provider when one is configured. */\nexport function getNeatlogsTracer(name: string): Tracer {\n return providerState.provider?.getTracer(name) ?? preInitTracer;\n}\n\n/**\n * @internal The private Neatlogs provider, or null before\n * init(). Used by integrations that must repoint a self-instrumenting library's\n * captured provider onto ours.\n */\nexport function getNeatlogsProvider(): TracerProvider | null {\n return providerState.provider;\n}\n\n/**\n * Wrap a tracer so that spans it creates parent from — and, for\n * `startActiveSpan`, activate on — the PRIVATE Neatlogs context instead of the\n * global one.\n *\n * We hand this to libraries that create their own spans off a tracer we give\n * them (the Vercel AI SDK's `experimental_telemetry.tracer`, which calls\n * `tracer.startActiveSpan()` internally). Without the facade the AI SDK's native\n * spans would parent from `context.active()` — the foreign co-tenant's context —\n * and `startActiveSpan` would push them onto the GLOBAL context, so a foreign\n * tracer's next span reads our native span as its parent. Both directions leak.\n *\n */\nexport function isolateTracer(tracer: Tracer): Tracer {\n const facade: Tracer = {\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const parent = context ?? getNeatlogsActiveContext();\n return tracer.startSpan(name, options, parent);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n // Normalize the 2/3/4-arg overloads of startActiveSpan.\n let options: SpanOptions | undefined;\n let context: Context | undefined;\n let fn: F;\n if (typeof arg2 === 'function') {\n fn = arg2 as F;\n } else if (typeof arg3 === 'function') {\n options = arg2 as SpanOptions;\n fn = arg3 as F;\n } else {\n options = arg2 as SpanOptions;\n context = arg3 as Context;\n fn = arg4 as F;\n }\n const parent = context ?? getNeatlogsActiveContext();\n const span = tracer.startSpan(name, options, parent);\n return withNeatlogsSpan(span, () => fn(span), parent) as ReturnType<F>;\n },\n };\n return facade;\n}\n\n/**\n * The base context to build new Neatlogs spans and values on. NEVER contains a\n * foreign provider's span: it reads our private store, or ROOT_CONTEXT when\n * nothing is active.\n */\nexport function getNeatlogsActiveContext(): Context {\n return privateContextStorage.getStore() ?? ROOT_CONTEXT;\n}\n\n/** Return only the active Neatlogs span (never a foreign provider's span). */\nexport function getActiveNeatlogsSpan(): Span | undefined {\n return otelTrace.getSpan(getNeatlogsActiveContext());\n}\n\n/**\n * Build a parent context that cannot contain a foreign provider's span.\n *\n * The active Neatlogs context already carries the parent span AND any values\n * the caller threaded in upstream (e.g. `trace()`'s prompt-template values), so\n * those values reach the span processor via `onStart(parentContext)`. An\n * explicit `baseContext` (a caller's own value-carrying context) is honored as-is.\n */\nexport function getNeatlogsParentContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * A base context for callers that thread parent linkage themselves\n * (callback/event handlers that keep their own run-id → span map: LangChain,\n * OpenAI-Agents, Claude Agent SDK).\n *\n * This is the ACTIVE Neatlogs context — the private store's\n * context if a Neatlogs `trace()`/`span()` encloses this call, else\n * ROOT_CONTEXT. So a handler's own root/entry span nests under an enclosing\n * Neatlogs trace (preserving its session + end-user id) when one exists, and\n * auto-roots cleanly when one doesn't. A foreign provider's active span can\n * never leak in as an ancestor because the global context is never read.\n */\nexport function getNeatlogsBaseContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * Build an execution context for a Neatlogs span while preserving the active\n * foreign context. The span rides our private context store.\n */\nexport function getNeatlogsExecutionContext(\n span: Span,\n baseContext: Context = ROOT_CONTEXT,\n): Context {\n return otelTrace.setSpan(baseContext, span);\n}\n\n/**\n * Run a callback with a Neatlogs span active under the appropriate policy.\n *\n * The stored context is `setSpan(base, span)` — carrying the span PLUS whatever\n * values `base` holds — so threaded values (prompt templates)\n * propagate to descendant spans through our private store instead of the global\n * OTel context we deliberately never touch.\n */\nexport function withNeatlogsSpan<T>(\n span: Span,\n fn: () => T,\n baseContext?: Context,\n): T {\n const base = baseContext ?? getNeatlogsActiveContext();\n const ctx = otelTrace.setSpan(base, span);\n return privateContextStorage.run(ctx, fn);\n}\n","/**\n * End-user identity.\n *\n * The *end user* is the user of OUR CUSTOMER'S application — the person\n * interacting with the AI product our customer built. This is deliberately\n * distinct from the operator-level `userId` set in {@link init}, which identifies\n * whoever is running the SDK (a developer, a service account, a CI job).\n *\n * Model: **one end-user per trace.** Identity is declared once at a trace\n * boundary — `init()` (process-global default), `trace()` (per-request), or\n * `span()` (per root) — via new options on those existing constructs. There is\n * no separate `identify()` call and no nested per-span override: a trace belongs\n * to a single end-user.\n *\n * End-user identity belongs to the trace as a whole, so the SDK only stamps it\n * on the trace's ROOT span (any span kind, created via `trace()` / `span()`). A\n * non-root child span never carries it. The backend reads end-user from the root\n * span and rolls the value up to the trace (and its session) so\n * filtering/analytics are trace- and session-level, not per-span.\n *\n * Canonical span attributes:\n * `neatlogs.end_user.id` — the end-user identifier (string)\n * `neatlogs.end_user.metadata` — JSON object of arbitrary end-user fields\n */\n\nimport { type Span } from '@opentelemetry/api';\nimport { getLogger } from './logger.js';\nimport { currentEndUserId, currentEndUserMetadata } from './identity.js';\nimport { getActiveNeatlogsSpan } from './provider.js';\n\nconst logger = getLogger();\n\nexport const END_USER_ID_KEY = 'neatlogs.end_user.id';\nexport const END_USER_METADATA_KEY = 'neatlogs.end_user.metadata';\n\n/** Coerce end-user metadata to a JSON string. Returns undefined when empty. */\nexport function normalizeEndUserMetadata(\n metadata: string | Record<string, any> | undefined | null,\n): string | undefined {\n if (metadata === undefined || metadata === null) return undefined;\n if (typeof metadata === 'string') return metadata || undefined;\n try {\n return JSON.stringify(metadata);\n } catch {\n return JSON.stringify(String(metadata));\n }\n}\n\n/**\n * True when no recording span is currently active — i.e. a span created now\n * would be the trace root.\n */\nexport function isRootSpan(): boolean {\n // Resolve from the private span store in isolated mode (a foreign provider's\n // active span must not count as our parent), else the active OTel span.\n const current = getActiveNeatlogsSpan();\n return !(current && current.isRecording());\n}\n\n/**\n * Stamp end-user id/metadata onto a ROOT span (best-effort). When `isRoot` is\n * false the span is a child and the value is ignored — the backend reads\n * end-user from the root span and rolls it up to the trace and session.\n */\nexport function applyEndUserAttributes(\n span: Span,\n endUserId?: string,\n endUserMetadata?: string | Record<string, any> | null,\n isRoot = true,\n): void {\n // Resolution at the root (per field): explicit per-call arg wins, else the\n // identify() context (request-scoped). Non-root spans never carry identity.\n const resolvedId = isRoot ? (endUserId ?? currentEndUserId()) : endUserId;\n const resolvedMeta = isRoot\n ? (endUserMetadata ?? currentEndUserMetadata())\n : endUserMetadata;\n if (!resolvedId && !resolvedMeta) return;\n if (!isRoot) {\n logger.debug(\n '[end_user] Ignoring endUserId/endUserMetadata on a non-root span — ' +\n 'declare it on the trace root (top-level trace()/span()) or identify().',\n );\n return;\n }\n if (resolvedId) {\n span.setAttribute(END_USER_ID_KEY, String(resolvedId));\n }\n const metaJson = normalizeEndUserMetadata(resolvedMeta);\n if (metaJson) {\n span.setAttribute(END_USER_METADATA_KEY, metaJson);\n }\n}\n","/**\n * Session identity.\n *\n * A *session* is one conversation / thread / journey — it groups the multiple\n * traces of a multi-turn interaction (one turn = one trace). Identity is\n * declared once at a trace boundary — `init()` (process-global default),\n * `trace()` (per-turn), or `span()` (per root) — via the `sessionId` option on\n * those existing constructs. The SDK stamps only the trace's ROOT span; the\n * backend rolls the value up to group the traces of a session together.\n *\n * For single-turn workflows a session maps 1:1 to a trace: when no session is\n * declared the backend backfills `session_id = trace_id`.\n *\n * Canonical span attribute:\n * `neatlogs.session.id` — the session identifier (string)\n */\n\nimport { type Span } from '@opentelemetry/api';\nimport { getLogger } from './logger.js';\nimport { currentSessionId } from './identity.js';\n\nconst logger = getLogger();\n\nexport const SESSION_ID_KEY = 'neatlogs.session.id';\n\n/**\n * Stamp the session id onto a ROOT span (best-effort). When `isRoot` is false\n * the span is a child and the value is ignored — the backend reads session from\n * the root span and groups traces by it.\n */\nexport function applySessionAttributes(\n span: Span,\n sessionId?: string,\n isRoot = true,\n): void {\n // Resolution at the root: explicit per-call arg wins, else the identify()\n // context (request-scoped). Non-root spans never carry session identity.\n const resolved = isRoot ? (sessionId ?? currentSessionId()) : sessionId;\n if (!resolved) return;\n if (!isRoot) {\n logger.debug(\n '[session] Ignoring sessionId on a non-root span — declare it on the ' +\n 'trace root (top-level trace()/span()) or identify().',\n );\n return;\n }\n span.setAttribute(SESSION_ID_KEY, String(resolved));\n}\n","/**\n * Core span decoration utilities.\n * Provides the low-level wrapping logic used by span() and Span().\n */\n\nimport { ROOT_CONTEXT, SpanStatusCode, type Span as OtelSpan } from '@opentelemetry/api';\nimport { getLogger } from '../core/logger.js';\nimport { registerMask } from '../core/mask.js';\nimport { applyEndUserAttributes, isRootSpan } from '../core/end-user.js';\nimport { applySessionAttributes } from '../core/session.js';\nimport {\n getNeatlogsTracer,\n getNeatlogsParentContext,\n withNeatlogsSpan,\n} from '../core/provider.js';\nimport type { SpanOptions, MaskFunction } from '../types.js';\n\nconst logger = getLogger();\nconst TRACER_NAME = 'neatlogs';\n\n/** Safely serialize any value to JSON string. */\nexport function safeJsonDumps(value: any): string {\n try {\n return JSON.stringify(value, (_key, val) => {\n if (typeof val === 'bigint') return val.toString();\n if (val instanceof Error) return { message: val.message, name: val.name, stack: val.stack };\n if (typeof val === 'function') return `[Function: ${val.name || 'anonymous'}]`;\n return val;\n });\n } catch {\n return String(value);\n }\n}\n\n/** Serialize an object for span attributes. Handles toJSON(), plain objects, primitives. */\nexport function serializeObj(obj: any): any {\n if (obj === null || obj === undefined) return obj;\n if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') return obj;\n if (typeof obj.toJSON === 'function') return obj.toJSON();\n if (Array.isArray(obj)) return obj.map(serializeObj);\n if (typeof obj === 'object') {\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = serializeObj(value);\n }\n return result;\n }\n return String(obj);\n}\n\n/** Check if content capture is enabled via env var. */\nexport function shouldCaptureContent(): boolean {\n const envVal = process.env.NEATLOGS_TRACE_CONTENT;\n if (envVal === undefined || envVal === '') return true;\n return envVal.toLowerCase() !== 'false' && envVal !== '0';\n}\n\n/** Set common span attributes from options. */\nexport function setCommonSpanAttrs(span: OtelSpan, opts: SpanOptions): void {\n if (opts.kind) {\n span.setAttribute('openinference.span.kind', opts.kind);\n }\n if (opts.internal) {\n span.setAttribute('neatlogs.internal', true);\n }\n if (opts.description) {\n span.setAttribute('neatlogs.description', opts.description);\n }\n if (opts.mask) {\n const maskId = registerMask(opts.mask);\n span.setAttribute('neatlogs.mask_id', maskId);\n }\n}\n\nexport interface DecorateSpanOptions extends SpanOptions {\n /** Override the span name (defaults to function name). */\n spanName?: string;\n /** Post-process callback after the function returns. */\n postprocessResult?: (span: OtelSpan, result: any, boundInputs: Record<string, any>) => void;\n}\n\n/**\n * Core wrapper factory that creates an instrumented version of a function.\n * This is the low-level building block used by span() and Span().\n */\nexport function decorateSpan<TArgs extends any[], TReturn>(\n opts: DecorateSpanOptions,\n fn: (...args: TArgs) => TReturn,\n): (...args: TArgs) => TReturn extends Promise<any> ? TReturn : Promise<Awaited<TReturn>> {\n const spanName = opts.spanName ?? opts.name ?? (fn.name || 'anonymous');\n const captureInput = opts.captureInput !== false;\n const captureOutput = opts.captureOutput !== false;\n const doCapture = shouldCaptureContent();\n\n const wrapped = (...args: TArgs): any => {\n // Resolve the tracer at CALL time, not definition time: a decorator applied\n // before init() would otherwise capture the no-op global provider forever\n // and emit zero spans once the private provider is configured.\n const tracer = getNeatlogsTracer(TRACER_NAME);\n // End-user belongs to the trace root only. Capture root status BEFORE the\n // span is created (once it's active, it would be the \"current\" span).\n const isRoot = isRootSpan();\n // Parent from our private span store; a root decorated function anchors a\n // fresh trace under ROOT_CONTEXT.\n const parentContext = isRoot ? ROOT_CONTEXT : getNeatlogsParentContext();\n const span = tracer.startSpan(spanName, {}, parentContext);\n return withNeatlogsSpan(span, () => {\n try {\n // Set common attributes\n setCommonSpanAttrs(span, opts);\n\n // Session/end-user identity (root span only; skipped on a non-root child).\n applySessionAttributes(span, opts.sessionId, isRoot);\n applyEndUserAttributes(span, opts.endUserId, opts.endUserMetadata, isRoot);\n\n // Capture input\n if (captureInput && doCapture && args.length > 0) {\n try {\n const inputValue = args.length === 1 ? serializeObj(args[0]) : args.map(serializeObj);\n span.setAttribute('input.value', safeJsonDumps(inputValue));\n } catch (err) {\n logger.debug(`Failed to capture input: ${err}`);\n }\n }\n\n // Execute the function\n const result = fn(...args);\n\n // Handle async results\n if (result instanceof Promise) {\n return result\n .then((resolved: any) => {\n // Capture output\n if (captureOutput && doCapture) {\n try {\n span.setAttribute('output.value', safeJsonDumps(serializeObj(resolved)));\n } catch (err) {\n logger.debug(`Failed to capture output: ${err}`);\n }\n }\n\n // Post-process\n if (opts.postprocessResult) {\n try {\n const boundInputs = _extractBoundInputs(fn, args);\n opts.postprocessResult(span, resolved, boundInputs);\n } catch (err) {\n logger.debug(`Postprocess failed: ${err}`);\n }\n }\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return resolved;\n })\n .catch((error: any) => {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error?.message ?? String(error),\n });\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n span.end();\n throw error;\n });\n }\n\n // Sync result\n if (captureOutput && doCapture) {\n try {\n span.setAttribute('output.value', safeJsonDumps(serializeObj(result)));\n } catch (err) {\n logger.debug(`Failed to capture output: ${err}`);\n }\n }\n\n if (opts.postprocessResult) {\n try {\n const boundInputs = _extractBoundInputs(fn, args);\n opts.postprocessResult(span, result, boundInputs);\n } catch (err) {\n logger.debug(`Postprocess failed: ${err}`);\n }\n }\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return result;\n } catch (error: any) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error?.message ?? String(error),\n });\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n span.end();\n throw error;\n }\n });\n };\n\n // Preserve function name for debugging\n Object.defineProperty(wrapped, 'name', { value: spanName, configurable: true });\n\n return wrapped as any;\n}\n\n/** Extract named arguments from function call (best-effort). */\nfunction _extractBoundInputs(fn: Function, args: any[]): Record<string, any> {\n const result: Record<string, any> = {};\n // Parse function parameter names from toString()\n const fnStr = fn.toString();\n const match = fnStr.match(/\\(([^)]*)\\)/);\n if (match) {\n const paramNames = match[1]\n .split(',')\n .map((p) => p.trim().replace(/\\s*[:=].*$/, '').replace(/^\\.\\.\\./, ''))\n .filter(Boolean);\n for (let i = 0; i < Math.min(paramNames.length, args.length); i++) {\n result[paramNames[i]] = args[i];\n }\n }\n return result;\n}\n"],"mappings":"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","names":["SpanStatusCode","otelTrace","ROOT_CONTEXT","SpanStatusCode","AsyncLocalStorage","AsyncLocalStorage","logger","logger","logger","ROOT_CONTEXT","logger","logger","otelTrace","SpanStatusCode"]}
1
+ {"version":3,"sources":["../src/anthropic.ts","../src/core/auto-root.ts","../src/core/context.ts","../src/prompt/template.ts","../src/core/logger.ts","../src/core/mask.ts","../src/core/identity.ts","../src/core/provider.ts","../src/core/end-user.ts","../src/core/session.ts","../src/decorators/base.ts"],"sourcesContent":["/**\n * Neatlogs Anthropic wrapper — ES6 Proxy-based.\n *\n * Usage:\n * import { wrapAnthropic, traceTool } from 'neatlogs/anthropic';\n * import Anthropic from '@anthropic-ai/sdk';\n * const client = wrapAnthropic(new Anthropic());\n *\n * Intercepts:\n * - client.messages.create() — non-streaming and streaming (stream: true)\n * - client.messages.stream() — Anthropic's streaming helper (MessageStream)\n *\n * Also exports traceTool() to wrap user-defined tool functions with TOOL spans.\n * Handles thinking blocks, tool_use blocks, and cache tokens.\n */\n\nimport { SpanStatusCode, type Span } from '@opentelemetry/api';\nimport { getProviderTracer } from './core/auto-root.js';\nimport {\n getNeatlogsTracer,\n getNeatlogsParentContext,\n withNeatlogsSpan,\n} from './core/provider.js';\n\nconst TRACER_NAME = 'neatlogs.anthropic';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport function wrapAnthropic<T extends object>(client: T): T {\n return wrapNamespace(client, []) as T;\n}\n\n/**\n * Wrap a tool/function implementation to emit TOOL spans when executed.\n *\n * Usage:\n * const getWeather = traceTool('get_weather', async (input: { city: string }) => {\n * return `Weather in ${input.city}: sunny`;\n * });\n */\nexport function traceTool<TInput = any, TResult = any>(\n name: string,\n fn: (input: TInput) => TResult | Promise<TResult>,\n): (input: TInput) => Promise<TResult> {\n return async function tracedTool(input: TInput): Promise<TResult> {\n const tracer = getNeatlogsTracer(TRACER_NAME);\n const span = tracer.startSpan(\n `tool.${name}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n 'neatlogs.tool.name': name,\n 'input.value': safeStringify(input),\n },\n },\n getNeatlogsParentContext(),\n );\n return withNeatlogsSpan(span, async () => {\n try {\n const result = await fn(input);\n span.setAttribute('output.value', safeStringify(result));\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n recordError(span, err);\n throw err;\n } finally {\n span.end();\n }\n });\n };\n}\n\n// ---------------------------------------------------------------------------\n// Proxy wrapping\n// ---------------------------------------------------------------------------\n\nfunction wrapNamespace(target: any, path: string[]): any {\n return new Proxy(target, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n if (typeof prop === 'symbol' || String(prop).startsWith('_')) return value;\n\n const currentPath = [...path, String(prop)];\n const pathStr = currentPath.join('.');\n\n if (pathStr === 'messages.create' && typeof value === 'function') {\n return tracedMessagesCreate(value.bind(obj));\n }\n if (pathStr === 'messages.stream' && typeof value === 'function') {\n return tracedMessagesStream(value.bind(obj));\n }\n\n if (value && typeof value === 'object' && !Array.isArray(value) && isNamespace(currentPath)) {\n return wrapNamespace(value, currentPath);\n }\n\n return value;\n },\n });\n}\n\nfunction isNamespace(path: string[]): boolean {\n if (path.length > 3) return false;\n const key = path[path.length - 1];\n return ['messages', 'beta'].includes(key);\n}\n\n// ---------------------------------------------------------------------------\n// messages.create — non-streaming + streaming (stream: true)\n// ---------------------------------------------------------------------------\n\nfunction tracedMessagesCreate(original: (...args: any[]) => any) {\n return function (opts: any, ...rest: any[]): any {\n const tracer = getProviderTracer(TRACER_NAME);\n const model = opts?.model ?? '';\n const messages: any[] = opts?.messages ?? [];\n const isStream = opts?.stream === true;\n\n const span = tracer.startSpan('anthropic.messages.create', {\n attributes: {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': 'anthropic',\n 'neatlogs.llm.system': 'anthropic',\n 'neatlogs.llm.model_name': model,\n 'neatlogs.llm.is_streaming': isStream,\n },\n }, getNeatlogsParentContext());\n\n setInputMessages(span, opts?.system, messages);\n setTools(span, opts?.tools);\n setInvocationParams(span, opts);\n\n const promise = withNeatlogsSpan(span, () => original(opts, ...rest));\n\n return promise.then(\n (response: any) => {\n if (isStream) {\n return wrapStreamIterable(response, span);\n }\n finalizeMessageResponse(span, response);\n return response;\n },\n (err: any) => {\n recordError(span, err);\n throw err;\n },\n );\n };\n}\n\n// ---------------------------------------------------------------------------\n// messages.stream() — returns a MessageStream object\n// ---------------------------------------------------------------------------\n\nfunction tracedMessagesStream(original: (...args: any[]) => any) {\n return function (opts: any, ...rest: any[]): any {\n const tracer = getProviderTracer(TRACER_NAME);\n const model = opts?.model ?? '';\n const messages: any[] = opts?.messages ?? [];\n\n const span = tracer.startSpan('anthropic.messages.stream', {\n attributes: {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': 'anthropic',\n 'neatlogs.llm.system': 'anthropic',\n 'neatlogs.llm.model_name': model,\n 'neatlogs.llm.is_streaming': true,\n },\n }, getNeatlogsParentContext());\n\n setInputMessages(span, opts?.system, messages);\n setTools(span, opts?.tools);\n setInvocationParams(span, opts);\n\n const messageStream = withNeatlogsSpan(span, () => original(opts, ...rest));\n\n return wrapMessageStream(messageStream, span);\n };\n}\n\n// ---------------------------------------------------------------------------\n// Streaming: async iterable (stream: true returns events)\n// ---------------------------------------------------------------------------\n\nfunction wrapStreamIterable(stream: any, span: Span): any {\n const events: any[] = [];\n const originalAsyncIterator = stream[Symbol.asyncIterator]?.bind(stream);\n\n if (!originalAsyncIterator) {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return stream;\n }\n\n const wrapped = Object.create(Object.getPrototypeOf(stream));\n Object.assign(wrapped, stream);\n\n wrapped[Symbol.asyncIterator] = function () {\n const iterator = originalAsyncIterator();\n return {\n async next(): Promise<IteratorResult<any>> {\n try {\n const result = await iterator.next();\n if (result.done) {\n finalizeStreamEvents(span, events);\n return result;\n }\n events.push(result.value);\n return result;\n } catch (err) {\n recordError(span, err);\n throw err;\n }\n },\n async return(value?: any): Promise<IteratorResult<any>> {\n finalizeStreamEvents(span, events);\n return iterator.return?.(value) ?? { done: true, value: undefined };\n },\n async throw(err?: any): Promise<IteratorResult<any>> {\n recordError(span, err);\n return iterator.throw?.(err) ?? { done: true, value: undefined };\n },\n };\n };\n\n if (typeof stream.finalMessage === 'function') {\n const origFinal = stream.finalMessage.bind(stream);\n wrapped.finalMessage = async function () {\n return origFinal();\n };\n }\n\n return wrapped;\n}\n\n// ---------------------------------------------------------------------------\n// Streaming: MessageStream (.on('end'), .finalMessage())\n// ---------------------------------------------------------------------------\n\nfunction wrapMessageStream(messageStream: any, span: Span): any {\n const origOn = messageStream.on?.bind(messageStream);\n if (origOn) {\n origOn('end', () => {\n const finalMsg = messageStream._finalMessage ?? messageStream.currentMessage;\n if (finalMsg) {\n finalizeMessageResponse(span, finalMsg);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n }\n });\n origOn('error', (err: any) => {\n if (span.isRecording()) recordError(span, err);\n });\n }\n\n if (typeof messageStream.finalMessage === 'function') {\n const origFinal = messageStream.finalMessage.bind(messageStream);\n messageStream.finalMessage = async function () {\n const result = await origFinal();\n if (span.isRecording()) {\n finalizeMessageResponse(span, result);\n }\n return result;\n };\n }\n\n return messageStream;\n}\n\n// ---------------------------------------------------------------------------\n// Stream event finalization\n// ---------------------------------------------------------------------------\n\nfunction finalizeStreamEvents(span: Span, events: any[]): void {\n const textParts: string[] = [];\n const thinkingParts: string[] = [];\n const toolCalls: Array<{ id: string; name: string; input: string }> = [];\n let currentToolInput = '';\n let currentToolId = '';\n let currentToolName = '';\n let usage: any = null;\n let model = '';\n let stopReason = '';\n\n for (const event of events) {\n const type = event?.type ?? '';\n\n if (type === 'content_block_delta') {\n const delta = event?.delta;\n if (delta?.type === 'text_delta' && delta.text) textParts.push(delta.text);\n else if (delta?.type === 'thinking_delta' && delta.thinking) thinkingParts.push(delta.thinking);\n else if (delta?.type === 'input_json_delta' && delta.partial_json) currentToolInput += delta.partial_json;\n } else if (type === 'content_block_start') {\n const block = event?.content_block;\n if (block?.type === 'tool_use') {\n currentToolId = block.id ?? '';\n currentToolName = block.name ?? '';\n currentToolInput = '';\n }\n } else if (type === 'content_block_stop') {\n if (currentToolName) {\n toolCalls.push({ id: currentToolId, name: currentToolName, input: currentToolInput });\n currentToolId = '';\n currentToolName = '';\n currentToolInput = '';\n }\n } else if (type === 'message_start') {\n const msg = event?.message;\n if (msg?.model) model = msg.model;\n if (msg?.usage) usage = msg.usage;\n } else if (type === 'message_delta') {\n const delta = event?.delta;\n if (delta?.stop_reason) stopReason = delta.stop_reason;\n if (event?.usage) usage = { ...(usage ?? {}), ...event.usage };\n }\n }\n\n const fullText = textParts.join('');\n if (fullText) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', fullText);\n }\n\n const thinking = thinkingParts.join('');\n if (thinking) {\n span.setAttribute('neatlogs.llm.output_messages.0.thinking', thinking);\n }\n\n for (let j = 0; j < toolCalls.length; j++) {\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, toolCalls[j].id);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, toolCalls[j].name);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, toolCalls[j].input);\n }\n\n if (model) span.setAttribute('neatlogs.llm.model_name', model);\n if (stopReason) span.setAttribute('neatlogs.llm.stop_reason', stopReason);\n setUsageAttrs(span, usage);\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Non-streaming response finalization\n// ---------------------------------------------------------------------------\n\nfunction finalizeMessageResponse(span: Span, response: any): void {\n const content: any[] = response?.content ?? [];\n const textParts: string[] = [];\n const thinkingParts: string[] = [];\n const toolCalls: Array<{ id: string; name: string; input: string }> = [];\n\n for (const block of content) {\n if (block.type === 'text' && block.text) textParts.push(block.text);\n else if (block.type === 'thinking' && block.thinking) thinkingParts.push(block.thinking);\n else if (block.type === 'tool_use') {\n toolCalls.push({\n id: block.id ?? '',\n name: block.name ?? '',\n input: safeStringify(block.input ?? {}),\n });\n }\n }\n\n if (textParts.length) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', textParts.join(''));\n }\n if (thinkingParts.length) {\n span.setAttribute('neatlogs.llm.output_messages.0.thinking', thinkingParts.join(''));\n }\n\n for (let j = 0; j < toolCalls.length; j++) {\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, toolCalls[j].id);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, toolCalls[j].name);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, toolCalls[j].input);\n }\n\n setUsageAttrs(span, response?.usage);\n if (response?.model) span.setAttribute('neatlogs.llm.model_name', response.model);\n if (response?.stop_reason) span.setAttribute('neatlogs.llm.stop_reason', response.stop_reason);\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\nfunction setInputMessages(span: Span, system: any, messages: any[]): void {\n let idx = 0;\n if (system) {\n const content = typeof system === 'string' ? system : safeStringify(system);\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'system');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, content);\n idx++;\n }\n for (const msg of messages) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, msg.role ?? '');\n if (typeof msg.content === 'string') {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, msg.content);\n } else if (msg.content) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, safeStringify(msg.content));\n }\n idx++;\n }\n}\n\nfunction setTools(span: Span, tools: any[] | undefined): void {\n if (!tools) return;\n for (let i = 0; i < tools.length; i++) {\n const tool = tools[i];\n span.setAttribute(`neatlogs.llm.tools.${i}.name`, tool.name ?? '');\n if (tool.description) span.setAttribute(`neatlogs.llm.tools.${i}.description`, tool.description);\n if (tool.input_schema) span.setAttribute(`neatlogs.llm.tools.${i}.input_schema`, safeStringify(tool.input_schema));\n }\n}\n\nfunction setInvocationParams(span: Span, opts: any): void {\n if (opts?.temperature != null) span.setAttribute('neatlogs.llm.temperature', opts.temperature);\n if (opts?.top_p != null) span.setAttribute('neatlogs.llm.top_p', opts.top_p);\n if (opts?.top_k != null) span.setAttribute('neatlogs.llm.top_k', opts.top_k);\n if (opts?.max_tokens != null) span.setAttribute('neatlogs.llm.max_tokens', opts.max_tokens);\n if (opts?.stop_sequences) span.setAttribute('neatlogs.llm.stop_sequences', safeStringify(opts.stop_sequences));\n}\n\nfunction setUsageAttrs(span: Span, usage: any): void {\n if (!usage) return;\n if (usage.input_tokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', usage.input_tokens);\n if (usage.output_tokens != null) span.setAttribute('neatlogs.llm.token_count.completion', usage.output_tokens);\n if (usage.cache_read_input_tokens != null) span.setAttribute('neatlogs.llm.token_count.cache_read', usage.cache_read_input_tokens);\n if (usage.cache_creation_input_tokens != null) span.setAttribute('neatlogs.llm.token_count.cache_write', usage.cache_creation_input_tokens);\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return typeof value === 'string' ? value : JSON.stringify(value);\n } catch {\n return '';\n }\n}\n\nfunction recordError(span: Span, err: unknown): void {\n if (err instanceof Error) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });\n span.recordException(err);\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });\n }\n span.end();\n}\n","/**\n * Auto-root for direct-provider wrappers.\n *\n * The backend only renders a trace once it contains a *parentless* span of a\n * root-eligible kind (WORKFLOW / CHAIN / AGENT / MCP_TOOL). Direct-provider\n * wrappers (wrapOpenAI, wrapAnthropic, wrapBedrock, ...) only ever emit\n * non-root spans (LLM / EMBEDDING / TOOL). So a bare\n * `const client = wrapOpenAI(new OpenAI())` with no surrounding `span()` /\n * `trace()` produces an orphan span and the trace never renders.\n *\n * `getProviderTracer(name)` returns a Tracer facade used *only* by those\n * direct-provider wrappers: when `startSpan` would otherwise produce a\n * parentless, non-root span, it transparently opens a WORKFLOW root (named\n * after the configured `workflowName`) and ends it when the provider span\n * ends. Every wrapper path — non-streaming, streaming finalize, error —\n * funnels through `span.end()`, so wrapping `end()` covers them all with no\n * changes to the wrappers' streaming code.\n *\n * Framework wrappers (langchain, mastra, openai-agents, ai-sdk, ...) keep\n * using `trace.getTracer(...)` directly — they already emit their own root and\n * thread context explicitly, so auto-root must never fire for them.\n */\n\nimport {\n trace as otelTrace,\n type Tracer,\n type Span,\n type Context,\n type SpanOptions,\n} from '@opentelemetry/api';\n\nimport { getSessionConfig } from './context.js';\nimport { applySessionAttributes } from './session.js';\nimport { applyEndUserAttributes } from './end-user.js';\nimport {\n getNeatlogsTracer,\n getNeatlogsParentContext,\n getActiveNeatlogsSpan,\n} from './provider.js';\n\n// A parentless span of one of these kinds already satisfies the backend's\n// root requirement, so it must NOT be wrapped in another root.\nconst ROOT_KINDS = new Set(['workflow', 'chain', 'agent', 'mcp_tool']);\n\nfunction autoRootEnabled(): boolean {\n const val = (process.env.NEATLOGS_AUTO_ROOT ?? '').trim().toLowerCase();\n return !['false', '0', 'no', 'off'].includes(val);\n}\n\nfunction resolveRootWorkflowName(): string {\n try {\n const name = getSessionConfig()?.workflowName;\n if (typeof name === 'string' && name.trim()) return name;\n } catch {\n /* ignore */\n }\n return 'workflow';\n}\n\n/**\n * Stamp request-scoped identify() identity onto a freshly-created auto-root.\n * The auto-root IS the trace root, and wrapper-only code has no user-controlled\n * root to put trace()/span() args on, so identity comes purely from the\n * identify() context (passed undefined per-call values fall back to it).\n */\nfunction stampAutoRootIdentity(root: Span): void {\n applySessionAttributes(root, undefined, true);\n applyEndUserAttributes(root, undefined, undefined, true);\n}\n\n/**\n * Wrap a provider span so that ending it also ends the auto-created WORKFLOW\n * root. A Proxy keeps this transparent across OTel versions: every property /\n * method except `end` passes straight through to the child span.\n */\nfunction wrapSpanWithRoot(child: Span, root: Span): Span {\n let ended = false;\n return new Proxy(child, {\n get(target, prop, _receiver) {\n if (prop === 'end') {\n return (...args: any[]): void => {\n if (ended) return;\n ended = true;\n try {\n (target.end as (...a: any[]) => void)(...args);\n } finally {\n try {\n root.end();\n } catch {\n /* ignore */\n }\n }\n };\n }\n const value = Reflect.get(target, prop, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n\n/**\n * Self-root helper for callback/event-driven handlers (LangChain, etc.).\n *\n * A callback handler builds its own parent context per run-id rather than going\n * through {@link AutoRootTracer}. When a run BEGINS with a non-root kind (a bare\n * `llm.invoke()` fires the LLM callback with no chain above it; a standalone\n * tool/retriever similarly), the span it creates is parentless and non-root, so\n * the backend can't anchor the trace and it never renders. Call this when there\n * is no LangChain parent: if the kind is non-root and nothing is actively\n * recording in `baseCtx`, it opens a WORKFLOW root and returns it plus the\n * child context to create the span in. The caller MUST end the returned\n * `root` after the child span ends (see {@link endAutoRoot}).\n *\n * Returns `{ root, ctx }` when a root was opened, or `{ root: undefined, ctx: baseCtx }`\n * when no auto-root is needed (root-eligible kind, already-recording parent, or\n * disabled).\n */\nexport function maybeOpenAutoRoot(\n tracer: Tracer,\n kind: string,\n baseCtx: Context,\n): { root: Span | undefined; ctx: Context } {\n const k = String(kind ?? '').toLowerCase();\n // A foreign provider's span must never count as our parent.\n const existing = getActiveNeatlogsSpan();\n if (\n !autoRootEnabled() ||\n ROOT_KINDS.has(k) ||\n (existing !== undefined && existing.isRecording())\n ) {\n return { root: undefined, ctx: baseCtx };\n }\n const root = tracer.startSpan(\n resolveRootWorkflowName(),\n { attributes: { 'neatlogs.span.kind': 'workflow', 'neatlogs.auto_root': true } },\n baseCtx,\n );\n stampAutoRootIdentity(root);\n return { root, ctx: otelTrace.setSpan(baseCtx, root) };\n}\n\n/** End an auto-root opened by {@link maybeOpenAutoRoot}. Safe on undefined. */\nexport function endAutoRoot(root: Span | undefined): void {\n if (!root) return;\n try {\n root.end();\n } catch {\n /* ignore */\n }\n}\n\nclass AutoRootTracer implements Tracer {\n constructor(private readonly _tracer: Tracer) {}\n\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const kind = String(\n (options?.attributes as Record<string, unknown> | undefined)?.[\n 'neatlogs.span.kind'\n ] ?? '',\n ).toLowerCase();\n\n // Parent from our private context, never the foreign global.\n const ctx = context ?? getNeatlogsParentContext();\n const parent = getActiveNeatlogsSpan();\n const needsRoot =\n autoRootEnabled() &&\n !ROOT_KINDS.has(kind) &&\n !(parent !== undefined && parent.isRecording());\n\n if (!needsRoot) {\n return this._tracer.startSpan(name, options, ctx);\n }\n\n const root = this._tracer.startSpan(\n resolveRootWorkflowName(),\n { attributes: { 'neatlogs.span.kind': 'workflow', 'neatlogs.auto_root': true } },\n ctx,\n );\n stampAutoRootIdentity(root);\n const childCtx = otelTrace.setSpan(ctx, root);\n const child = this._tracer.startSpan(name, options, childCtx);\n return wrapSpanWithRoot(child, root);\n }\n\n // startActiveSpan is used by traceTool (callback-scoped, always under an\n // active span in practice) — pass straight through, no auto-root.\n startActiveSpan = ((...args: any[]) =>\n (this._tracer.startActiveSpan as (...a: any[]) => any)(\n ...args,\n )) as Tracer['startActiveSpan'];\n}\n\n/**\n * Tracer for direct-provider wrappers (wrapOpenAI, wrapAnthropic, ...).\n * Identical to `trace.getTracer(name)` but adds transparent auto-root so a\n * bare `wrapOpenAI(new OpenAI())` renders a trace without a manual `span()` /\n * `trace()` wrapper. Do NOT use for framework wrappers.\n */\nexport function getProviderTracer(name: string): Tracer {\n // Resolve from the private provider.\n return new AutoRootTracer(getNeatlogsTracer(name));\n}\n","/**\n * Context wrapper for manual span creation.\n *\n * Provides the `trace()` function — the TypeScript equivalent of the Python\n * `trace()` context manager. Creates an OTel span, sets prompt template/variable\n * context, and executes the user callback within the span.\n *\n * @example\n * ```typescript\n * await trace({ name: 'my-trace' }, async (span) => {\n * // user code runs here with the span active\n * });\n * ```\n */\n\nimport {\n trace as otelTrace,\n context as otelContext,\n createContextKey,\n ROOT_CONTEXT,\n SpanStatusCode,\n type Span,\n} from '@opentelemetry/api';\nimport { PromptContext, UserPromptContext, PromptTemplate, UserPromptTemplate } from '../prompt/template.js';\nimport { registerMask } from './mask.js';\nimport { applyEndUserAttributes } from './end-user.js';\nimport { applySessionAttributes } from './session.js';\nimport { currentSessionId } from './identity.js';\n\nimport { getLogger } from './logger.js';\nimport { safeJsonDumps, serializeObj } from '../decorators/base.js';\nimport type { TraceOptions, MaskFunction } from '../types.js';\nimport {\n getActiveNeatlogsSpan,\n getNeatlogsActiveContext,\n getNeatlogsParentContext,\n getNeatlogsRootSpan,\n getNeatlogsTracer,\n withNeatlogsSpan,\n} from './provider.js';\n\nconst logger = getLogger();\n\n// ---------------------------------------------------------------------------\n// Module-level session config (set by init.ts via _setSessionConfig)\n// ---------------------------------------------------------------------------\n\nlet _sessionConfig: Record<string, any> = {};\n\n/**\n * Set the session configuration. Called by init() during SDK setup.\n * @internal\n */\nexport function _setSessionConfig(config: Record<string, any>): void {\n _sessionConfig = config;\n}\n\n/**\n * Get a copy of the current session configuration.\n */\nexport function getSessionConfig(): Record<string, any> {\n return { ..._sessionConfig };\n}\n\n// ---------------------------------------------------------------------------\n// OTel context keys for prompt data propagation\n// ---------------------------------------------------------------------------\n\nexport const PROMPT_VARIABLES_KEY = createContextKey('neatlogs.prompt_variables');\nexport const PROMPT_TEMPLATE_KEY = createContextKey('neatlogs.prompt_template');\nexport const PROMPT_VERSION_KEY = createContextKey('neatlogs.prompt_version');\nexport const USER_PROMPT_TEMPLATE_KEY = createContextKey('neatlogs.user_prompt_template');\nexport const USER_PROMPT_VARIABLES_KEY = createContextKey('neatlogs.user_prompt_variables');\n\n// ---------------------------------------------------------------------------\n// Known TraceOptions keys (not forwarded as extra span attributes)\n// ---------------------------------------------------------------------------\n\nconst KNOWN_OPTION_KEYS = new Set([\n 'name',\n 'kind',\n 'input',\n 'promptTemplate',\n 'promptVariables',\n 'userPromptTemplate',\n 'userPromptVariables',\n 'version',\n 'mask',\n 'attributes',\n 'sessionId',\n 'endUserId',\n 'endUserMetadata',\n]);\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Set standard and extra attributes on a span.\n * @internal\n */\nexport function _setSpanAttributes(\n span: Span,\n kind: string | undefined,\n attributes: Record<string, any>,\n): void {\n span.setAttribute('neatlogs.internal', true);\n span.setAttribute('openinference.span.kind', kind ?? 'CHAIN');\n\n for (const [key, value] of Object.entries(attributes)) {\n span.setAttribute(key, value);\n }\n}\n\n/**\n * After the user callback completes, capture prompt variables that were\n * set by PromptTemplate.compile() / UserPromptTemplate.compile() and\n * record them as span attributes.\n * @internal\n */\nexport function _finalizePromptCapture(\n span: Span,\n isPromptTemplateObj: boolean,\n isUserPromptTemplateObj: boolean,\n): void {\n if (isPromptTemplateObj) {\n const capturedVars = PromptContext.getVariables();\n if (capturedVars) {\n span.setAttribute(\n 'llm.prompt_template_variables',\n JSON.stringify(capturedVars),\n );\n logger.debug(\n `[trace] Auto-captured variables from PromptContext: ${Object.keys(capturedVars).join(', ')}`,\n );\n }\n }\n\n if (isUserPromptTemplateObj) {\n const capturedUserVars = UserPromptContext.getVariables();\n if (capturedUserVars) {\n span.setAttribute(\n 'llm.user_prompt_template_variables',\n JSON.stringify(capturedUserVars),\n );\n logger.debug(\n `[trace] Auto-captured variables from UserPromptContext: ${Object.keys(capturedUserVars).join(', ')}`,\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Explicit trace-level output\n// ---------------------------------------------------------------------------\n\n/**\n * Explicitly set the trace-level \"final output\" shown for the current trace.\n *\n * By default the trace's displayed output is derived from the root span's\n * captured output. Call this to declare it instead — useful when the turn's\n * meaningful result lives in a tool call or is a status object you don't want\n * shown (e.g. an agent that suspends awaiting user input). The value is stamped\n * as `neatlogs.trace.output` on the trace ROOT span, which the backend prefers\n * over the derived output.\n *\n * No-op when called outside an active trace. Never throws.\n *\n * @param value - The value to show as the trace's final output.\n *\n * @example\n * ```typescript\n * await trace({ name: 'turn', sessionId }, async () => {\n * const plan = await proposePlan();\n * setTraceOutput(plan.title); // show the plan, not the raw status object\n * });\n * ```\n */\nexport function setTraceOutput(value: unknown): void {\n try {\n const span = getNeatlogsRootSpan() ?? getActiveNeatlogsSpan();\n if (!span) return;\n span.setAttribute('neatlogs.trace.output', safeJsonDumps(serializeObj(value)));\n } catch (err) {\n logger.warn(`[setTraceOutput] failed to set trace output: ${err}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main trace() function\n// ---------------------------------------------------------------------------\n\n/**\n * Async callback wrapper for manual span creation with prompt tracking.\n *\n * Creates an OTel span, optionally sets prompt template/variable context,\n * and executes the provided callback within the span.\n *\n * **Session-Aware Trace Creation:**\n * - If a session id is resolved (per-call `sessionId` or the request-scoped\n * `identify()` context) AND no active parent span exists, this creates a NEW\n * root trace (for multi-turn conversations).\n * - Otherwise, creates a normal child span within the existing trace.\n *\n * @param options - Trace configuration options\n * @param fn - Callback that receives the active span\n * @returns The return value of the callback\n *\n * @example\n * ```typescript\n * // Basic usage\n * await trace({ name: 'my-pipeline' }, async (span) => {\n * await step1();\n * await step2();\n * });\n *\n * // With prompt template\n * const template = new PromptTemplate('Hello {{name}}');\n * await trace({ name: 'prompt', promptTemplate: template }, async (span) => {\n * const rendered = template.compile({ name: 'world' });\n * // ...\n * });\n * ```\n */\nexport async function trace<T>(\n options: TraceOptions,\n fn: (span: Span) => T | Promise<T>,\n): Promise<T> {\n const {\n name,\n kind,\n input,\n promptTemplate,\n promptVariables,\n userPromptTemplate,\n userPromptVariables,\n version,\n mask,\n sessionId: sessionIdOption,\n endUserId,\n endUserMetadata,\n attributes: explicitAttributes,\n ...extraOptions\n } = options;\n\n // Per-call sessionId wins, else the request-scoped identify() context.\n // Session identity is NOT process-global — init() never sets it.\n const sessionId = sessionIdOption ?? currentSessionId();\n\n // Determine whether we are inside an existing active trace\n const currentSpan = getActiveNeatlogsSpan();\n const isInActiveTrace = currentSpan !== undefined && currentSpan.isRecording();\n const shouldCreateRootTrace = !!sessionId && !isInActiveTrace;\n\n // ---------------------------------------------------------------------------\n // Process prompt templates\n // ---------------------------------------------------------------------------\n\n let templateString: string | undefined;\n let isPromptTemplateObj = false;\n\n if (promptTemplate !== undefined) {\n if (promptTemplate instanceof PromptTemplate) {\n isPromptTemplateObj = true;\n const t = promptTemplate.template;\n templateString = typeof t === 'string' ? t : JSON.stringify(t);\n logger.debug(\n `[trace] Using PromptTemplate object with variables: ${promptTemplate.variables.join(', ')}`,\n );\n } else if (typeof promptTemplate === 'string') {\n templateString = promptTemplate;\n } else {\n const t = promptTemplate.template;\n templateString = typeof t === 'string' ? t : JSON.stringify(t);\n }\n }\n\n let userTemplateString: string | undefined;\n let isUserPromptTemplateObj = false;\n\n if (userPromptTemplate !== undefined) {\n if (userPromptTemplate instanceof UserPromptTemplate) {\n isUserPromptTemplateObj = true;\n const t = userPromptTemplate.template;\n userTemplateString = typeof t === 'string' ? t : JSON.stringify(t);\n logger.debug(\n `[trace] Using UserPromptTemplate object with variables: ${userPromptTemplate.variables.join(', ')}`,\n );\n } else if (typeof userPromptTemplate === 'string') {\n userTemplateString = userPromptTemplate;\n } else {\n const t = userPromptTemplate.template;\n userTemplateString = typeof t === 'string' ? t : JSON.stringify(t);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Build OTel context with prompt values\n // ---------------------------------------------------------------------------\n\n // Start from the Neatlogs active context (private store in isolated mode), NOT\n // otelContext.active() — the latter is the foreign co-tenant's context, whose\n // active span must never become our parent. Prompt values threaded here then\n // propagate to descendant LLM spans through the private store.\n let ctx = getNeatlogsActiveContext();\n\n const variablesJson = promptVariables ? JSON.stringify(promptVariables) : undefined;\n const userVariablesJson = userPromptVariables ? JSON.stringify(userPromptVariables) : undefined;\n\n if (variablesJson) {\n ctx = ctx.setValue(PROMPT_VARIABLES_KEY, variablesJson);\n logger.debug(`[trace] Set neatlogs.prompt_variables in context: ${variablesJson}`);\n }\n if (templateString) {\n ctx = ctx.setValue(PROMPT_TEMPLATE_KEY, templateString);\n logger.debug(`[trace] Set neatlogs.prompt_template in context: ${templateString}`);\n }\n if (userVariablesJson) {\n ctx = ctx.setValue(USER_PROMPT_VARIABLES_KEY, userVariablesJson);\n logger.debug(`[trace] Set neatlogs.user_prompt_variables in context: ${userVariablesJson}`);\n }\n if (userTemplateString) {\n ctx = ctx.setValue(USER_PROMPT_TEMPLATE_KEY, userTemplateString);\n logger.debug(`[trace] Set neatlogs.user_prompt_template in context: ${userTemplateString}`);\n }\n if (version) {\n ctx = ctx.setValue(PROMPT_VERSION_KEY, version);\n logger.debug(`[trace] Set neatlogs.prompt_version in context: ${version}`);\n }\n\n // ---------------------------------------------------------------------------\n // Collect extra attributes (non-standard option keys)\n // ---------------------------------------------------------------------------\n\n const extraAttributes: Record<string, any> = { ...(explicitAttributes ?? {}) };\n for (const [key, value] of Object.entries(extraOptions)) {\n if (!KNOWN_OPTION_KEYS.has(key)) {\n extraAttributes[key] = value;\n }\n }\n\n // End-user belongs to the trace root only. This trace() call is a root when it\n // creates a new root trace, or when it isn't nested in an already-active trace.\n const isRootTrace = shouldCreateRootTrace || !isInActiveTrace;\n\n // ---------------------------------------------------------------------------\n // Create span and execute callback\n // ---------------------------------------------------------------------------\n\n const tracer = getNeatlogsTracer('neatlogs.trace');\n\n const spanCallback = async (span: Span): Promise<T> => {\n _setSpanAttributes(span, kind, extraAttributes);\n\n // Session/end-user belong to the trace root only; skipped on a non-root span.\n applySessionAttributes(span, sessionId, isRootTrace);\n applyEndUserAttributes(span, endUserId, endUserMetadata, isRootTrace);\n\n if (input !== undefined && input !== null) {\n span.setAttribute('input.value', safeJsonDumps(serializeObj(input)));\n }\n\n if (mask) {\n const maskId = registerMask(mask);\n span.setAttribute('neatlogs.mask_id', maskId);\n }\n\n try {\n const result: T = await fn(span);\n\n _finalizePromptCapture(span, isPromptTemplateObj, isUserPromptTemplateObj);\n\n if (result !== undefined && result !== null) {\n const spanAttrs = (span as any).attributes ?? (span as any)._attributes;\n const hasOutput = spanAttrs && (\n 'output.value' in spanAttrs || 'neatlogs.output.value' in spanAttrs\n );\n if (!hasOutput) {\n span.setAttribute('output.value', safeJsonDumps(serializeObj(result)));\n }\n }\n\n return result;\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n } finally {\n if (isPromptTemplateObj) {\n PromptContext.clear();\n }\n if (isUserPromptTemplateObj) {\n UserPromptContext.clear();\n }\n span.end();\n }\n };\n\n const parentContext = shouldCreateRootTrace\n ? ROOT_CONTEXT\n : getNeatlogsParentContext(ctx);\n // Seed the span kind at CREATION, not just in the callback: the span processor\n // classifies LLM spans in onStart() (which fires from startSpan, before the\n // callback runs). Without this, a trace({ kind: 'LLM', promptTemplate }) span\n // isn't recognized as LLM in time and its prompt-template attributes are dropped.\n const startAttributes = kind ? { 'openinference.span.kind': kind } : undefined;\n const span = tracer.startSpan(name, startAttributes ? { attributes: startAttributes } : {}, parentContext);\n\n if (shouldCreateRootTrace) {\n logger.debug(`[trace] Creating NEW root trace '${name}' (sessionId=${sessionId})`);\n } else {\n logger.debug(`[trace] Creating child span '${name}'`);\n }\n\n return withNeatlogsSpan(span, () => spanCallback(span), ctx);\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { PromptMessage } from '../types.js';\n\n// ---------------------------------------------------------------------------\n// Async-local context for system prompts\n// ---------------------------------------------------------------------------\n\ninterface PromptContextData {\n template: string;\n variables: Record<string, any>;\n}\n\nconst _promptStorage = new AsyncLocalStorage<PromptContextData>();\n\n/**\n * Manages system prompt metadata in async-local context for automatic tracing.\n */\nexport class PromptContext {\n /**\n * Store system prompt template and variables in context.\n */\n static set(template: string, variables: Record<string, any>): void {\n // enterWith mutates the current async context in-place\n _promptStorage.enterWith({ template, variables });\n }\n\n /**\n * Retrieve system prompt template from context.\n */\n static getTemplate(): string | undefined {\n return _promptStorage.getStore()?.template;\n }\n\n /**\n * Retrieve system prompt variables from context.\n */\n static getVariables(): Record<string, any> | undefined {\n return _promptStorage.getStore()?.variables;\n }\n\n /**\n * Clear system prompt context.\n */\n static clear(): void {\n _promptStorage.enterWith(undefined as unknown as PromptContextData);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Async-local context for user prompts\n// ---------------------------------------------------------------------------\n\nconst _userPromptStorage = new AsyncLocalStorage<PromptContextData>();\n\n/**\n * Manages user/human prompt metadata in async-local context for automatic tracing.\n */\nexport class UserPromptContext {\n /**\n * Store user prompt template and variables in context.\n */\n static set(template: string, variables: Record<string, any>): void {\n _userPromptStorage.enterWith({ template, variables });\n }\n\n /**\n * Retrieve user prompt template from context.\n */\n static getTemplate(): string | undefined {\n return _userPromptStorage.getStore()?.template;\n }\n\n /**\n * Retrieve user prompt variables from context.\n */\n static getVariables(): Record<string, any> | undefined {\n return _userPromptStorage.getStore()?.variables;\n }\n\n /**\n * Clear user prompt context.\n */\n static clear(): void {\n _userPromptStorage.enterWith(undefined as unknown as PromptContextData);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared context setter type for the base template class\n// ---------------------------------------------------------------------------\n\ninterface ContextSetter {\n set(template: string, variables: Record<string, any>): void;\n}\n\n// ---------------------------------------------------------------------------\n// BasePromptTemplate — shared logic for system and user prompts\n// ---------------------------------------------------------------------------\n\nconst VARIABLE_PATTERN = /\\{\\{(\\w+)\\}\\}/g;\n\n/**\n * Base class for prompt templates with `{{variable}}` placeholders.\n *\n * Extracts variables, compiles templates, and renders strings.\n * Subclasses only need to specify the context class and display name.\n */\nabstract class BasePromptTemplate {\n protected readonly _template: string | PromptMessage[];\n protected readonly _variables: string[];\n\n /**\n * @param template - Either a string with `{{variable}}` placeholders or\n * an array of `PromptMessage` objects whose `content` fields contain placeholders.\n */\n constructor(template: string | PromptMessage[]) {\n this._template = template;\n this._variables = this._extractVariables();\n }\n\n /** List of unique variable names found in this template. */\n get variables(): string[] {\n return this._variables;\n }\n\n /** The raw template (string or message array). */\n get template(): string | PromptMessage[] {\n return this._template;\n }\n\n /** The context class to store template/variables for automatic tracing. */\n protected abstract get _contextSetter(): ContextSetter;\n\n /** Display name for toString(). */\n protected abstract get _displayName(): string;\n\n /**\n * Compile the prompt template with the given variables.\n *\n * @param variables - Key/value pairs to substitute for `{{key}}` placeholders.\n * @returns The rendered string or rendered message array.\n * @throws {Error} If any required variables are missing.\n */\n compile(variables?: Record<string, any>): string | PromptMessage[] {\n const vars = variables ?? {};\n\n const missing = this._variables.filter((v) => !(v in vars));\n if (missing.length > 0) {\n throw new Error(\n `Missing required variables: ${missing.join(', ')}. Template requires: ${this._variables.join(', ')}`,\n );\n }\n\n this._contextSetter.set(\n typeof this._template === 'string'\n ? this._template\n : this._template.map((msg) => `${msg.role}: ${msg.content}`).join('\\n'),\n vars,\n );\n\n if (typeof this._template === 'string') {\n return this._renderString(this._template, vars);\n }\n\n return this._template.map((msg) => ({\n role: msg.role,\n content: this._renderString(msg.content, vars),\n }));\n }\n\n /**\n * Replace `{{key}}` placeholders in a string with the corresponding values.\n */\n _renderString(text: string, variables: Record<string, any>): string {\n let result = text;\n for (const [key, value] of Object.entries(variables)) {\n result = result.replaceAll(`{{${key}}}`, String(value));\n }\n return result;\n }\n\n toString(): string {\n if (typeof this._template === 'string') {\n return this._template.length > 50\n ? `${this._displayName}('${this._template.slice(0, 50)}...')`\n : `${this._displayName}('${this._template}')`;\n }\n return `${this._displayName}(${this._template.length} messages, variables=${JSON.stringify(this._variables)})`;\n }\n\n // ---- private ----\n\n private _extractVariables(): string[] {\n if (typeof this._template === 'string') {\n return [...new Set(Array.from(this._template.matchAll(VARIABLE_PATTERN), (m) => m[1]))];\n }\n\n const found: string[] = [];\n for (const msg of this._template) {\n if (msg.content) {\n for (const match of msg.content.matchAll(VARIABLE_PATTERN)) {\n found.push(match[1]);\n }\n }\n }\n return [...new Set(found)];\n }\n}\n\n// ---------------------------------------------------------------------------\n// PromptTemplate — system/AI instruction prompt\n// ---------------------------------------------------------------------------\n\n/**\n * Template for the system/AI instruction prompt with `{{variable}}` placeholders.\n */\nexport class PromptTemplate extends BasePromptTemplate {\n protected get _contextSetter(): ContextSetter {\n return PromptContext;\n }\n\n protected get _displayName(): string {\n return 'PromptTemplate';\n }\n}\n\n// ---------------------------------------------------------------------------\n// UserPromptTemplate — user/human turn prompt\n// ---------------------------------------------------------------------------\n\n/**\n * Template for the user/human turn prompt with `{{variable}}` placeholders.\n *\n * Identical to {@link PromptTemplate} but stores context in {@link UserPromptContext}.\n */\nexport class UserPromptTemplate extends BasePromptTemplate {\n protected get _contextSetter(): ContextSetter {\n return UserPromptContext;\n }\n\n protected get _displayName(): string {\n return 'UserPromptTemplate';\n }\n}\n","/**\n * Centralized logging configuration for Neatlogs SDK.\n * Provides structured logging with configurable levels.\n */\n\ntype LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'NONE';\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n DEBUG: 0,\n INFO: 1,\n WARN: 2,\n ERROR: 3,\n NONE: 4,\n};\n\nlet _logLevel: LogLevel = 'INFO';\nlet _disabled = false;\n\nfunction _getConfiguredLevel(): LogLevel {\n const envLevel = process.env.NEATLOGS_LOG_LEVEL?.toUpperCase();\n if (envLevel && envLevel in LOG_LEVELS) {\n return envLevel as LogLevel;\n }\n return 'INFO';\n}\n\n// Initialize from env on first import\n_logLevel = _getConfiguredLevel();\n\nexport function getLogger() {\n return {\n debug(message: string, ...args: any[]) {\n if (!_disabled && LOG_LEVELS[_logLevel] <= LOG_LEVELS.DEBUG) {\n console.debug(`[neatlogs] ${message}`, ...args);\n }\n },\n info(message: string, ...args: any[]) {\n if (!_disabled && LOG_LEVELS[_logLevel] <= LOG_LEVELS.INFO) {\n console.info(`[neatlogs] ${message}`, ...args);\n }\n },\n warn(message: string, ...args: any[]) {\n if (!_disabled && LOG_LEVELS[_logLevel] <= LOG_LEVELS.WARN) {\n console.warn(`[neatlogs] ${message}`, ...args);\n }\n },\n error(message: string, ...args: any[]) {\n if (!_disabled && LOG_LEVELS[_logLevel] <= LOG_LEVELS.ERROR) {\n console.error(`[neatlogs] ${message}`, ...args);\n }\n },\n };\n}\n\nexport function setLogLevel(level: LogLevel): void {\n _logLevel = level;\n}\n\nexport function enableDebugLogging(): void {\n _logLevel = 'DEBUG';\n}\n\nexport function disableLogging(): void {\n _disabled = true;\n}\n\nexport function enableLogging(): void {\n _disabled = false;\n}\n","/**\n * PII masking support for Neatlogs spans.\n *\n * Users supply a callable that receives the full span dict and returns\n * the (possibly modified) span dict. Return null to drop the span entirely.\n */\n\nimport type { MaskFunction } from '../types.js';\nimport { getLogger } from './logger.js';\n\nconst logger = getLogger();\n\n/** Module-level registry: key -> mask function */\nconst _MASK_REGISTRY = new Map<string, MaskFunction>();\n\nlet _nextId = 0;\n\n/**\n * Register a mask function and return its lookup key.\n */\nexport function registerMask(fn: MaskFunction): string {\n const key = String(++_nextId);\n _MASK_REGISTRY.set(key, fn);\n return key;\n}\n\n/**\n * Apply the effective mask function to span data.\n *\n * Per-span mask (stored in attributes[\"neatlogs.mask_id\"]) takes\n * precedence over the global mask. Returns the (possibly modified) dict.\n */\nexport function applyMask(\n spanData: Record<string, any>,\n globalMask: MaskFunction | null | undefined,\n): Record<string, any> | null {\n const maskId = spanData?.attributes?.['neatlogs.mask_id'];\n let maskFn: MaskFunction | undefined | null = null;\n\n if (maskId) {\n maskFn = _MASK_REGISTRY.get(String(maskId)) ?? null;\n }\n\n if (!maskFn) {\n maskFn = globalMask ?? null;\n }\n\n if (!maskFn) {\n return spanData;\n }\n\n try {\n const result = maskFn(spanData);\n // null means \"drop this span entirely\"; undefined means \"keep original\"\n if (result === null) return null;\n return result !== undefined ? result : spanData;\n } catch (exc) {\n logger.warn(\n `mask callable raised an exception for span '${spanData?.name}': ${exc} — original span data will be exported unchanged.`,\n );\n return spanData;\n }\n}\n\n/**\n * Clear all registered masks. Used for testing.\n * @internal\n */\nexport function _clearMaskRegistry(): void {\n _MASK_REGISTRY.clear();\n _nextId = 0;\n}\n","/**\n * Request-scoped session & end-user identity.\n *\n * Session and end-user identity are PER-REQUEST, not process-global, so they are\n * never set on `init()`. The primary way to set them is at the trace root\n * (`trace({ sessionId })` / `span({ sessionId })`). For code that only uses\n * `wrap()` — where the trace root is created internally by the wrapper and the\n * caller has no root to put arguments on — use the `identify()` context:\n *\n * await neatlogs.identify(\n * { sessionId: 'chat_123', endUserId: 'user_456', endUserMetadata: { plan: 'pro' } },\n * async () => {\n * await wrappedClient.chat.completions.create(...); // auto-root reads this\n * },\n * );\n *\n * Resolution at the trace root (per field): explicit per-call argument wins, then\n * the `identify()` context, then nothing. Identity is stamped on the ROOT span\n * only; the backend rolls it up to the trace and its session.\n *\n * Backed by `AsyncLocalStorage` so it propagates across `await` boundaries (and\n * nested async tasks), exactly like OpenTelemetry's own context propagation.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nexport interface IdentityStore {\n sessionId?: string;\n endUserId?: string;\n endUserMetadata?: Record<string, any>;\n}\n\nexport interface IdentifyOptions {\n sessionId?: string;\n endUserId?: string;\n endUserMetadata?: Record<string, any>;\n}\n\n// tsup bundles each entry point (index, openai, azure-openai, ...) separately,\n// so a plain module-level `new AsyncLocalStorage()` would be DUPLICATED per\n// bundle — identify() (in index) would write to a different store than a\n// provider wrapper's auto-root (in e.g. azure-openai) reads, silently losing\n// identity on the wrapper-only path. Pin a single instance on globalThis so all\n// bundled copies (ESM + CJS) resolve to the same store.\nconst _GLOBAL_KEY = Symbol.for('neatlogs.identity.storage');\ntype _GlobalWithStore = typeof globalThis & {\n [_GLOBAL_KEY]?: AsyncLocalStorage<IdentityStore>;\n};\nconst _g = globalThis as _GlobalWithStore;\nconst _storage: AsyncLocalStorage<IdentityStore> =\n _g[_GLOBAL_KEY] ?? (_g[_GLOBAL_KEY] = new AsyncLocalStorage<IdentityStore>());\n\nexport function currentSessionId(): string | undefined {\n return _storage.getStore()?.sessionId;\n}\n\nexport function currentEndUserId(): string | undefined {\n return _storage.getStore()?.endUserId;\n}\n\nexport function currentEndUserMetadata(): Record<string, any> | undefined {\n return _storage.getStore()?.endUserMetadata;\n}\n\n/**\n * Bind session / end-user identity for the duration of `fn`.\n *\n * Only defined fields are set, so a nested `identify()` overrides one field\n * without clearing the others (it merges onto the enclosing store). The previous\n * store is automatically restored when `fn` returns / rejects. Returns whatever\n * `fn` returns (sync value or Promise).\n *\n * @example\n * ```typescript\n * await neatlogs.identify({ sessionId: 'chat_123' }, async () => {\n * await wrappedClient.chat.completions.create(...);\n * });\n * ```\n */\nexport function identify<T>(opts: IdentifyOptions, fn: () => T): T {\n const prev = _storage.getStore();\n const next: IdentityStore = { ...(prev ?? {}) };\n if (opts.sessionId !== undefined) next.sessionId = opts.sessionId;\n if (opts.endUserId !== undefined) next.endUserId = opts.endUserId;\n if (opts.endUserMetadata !== undefined) next.endUserMetadata = opts.endUserMetadata;\n return _storage.run(next, fn);\n}\n","/**\n * Neatlogs-owned tracing state.\n *\n * Spans are created by the private Neatlogs provider and their parent is carried\n * in a private context key. The process-global OpenTelemetry span is\n * deliberately left untouched so other observability SDKs cannot export or\n * become parents of Neatlogs spans.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n ROOT_CONTEXT,\n INVALID_SPAN_CONTEXT,\n createContextKey,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n type TracerProvider,\n} from '@opentelemetry/api';\n\n// Carries the trace-ROOT span down the private context so descendants can target\n// it (e.g. setTraceOutput). getActiveNeatlogsSpan() returns the innermost span,\n// which is not the root once nested; this key preserves the root reference.\nconst NEATLOGS_ROOT_SPAN_KEY = createContextKey('neatlogs.root_span');\n\n// Entry points are bundled independently (`neatlogs`, `neatlogs/openai`,\n// `neatlogs/ai`, `neatlogs/mastra`, … each in both CJS and ESM), so every piece\n// of shared tracing state — the private span store AND the resolved provider /\n// provider — must live on `globalThis` behind a `Symbol.for` key.\n// Otherwise `init()` (run from the `neatlogs` bundle) sets `_provider` in ITS\n// module copy while a wrapper imported from `neatlogs/openai` reads a different,\n// still-null copy and silently falls back to the foreign global provider.\nconst PRIVATE_SPAN_STORAGE_KEY = Symbol.for(\n 'neatlogs.private_span_async_local_storage',\n);\nconst PRIVATE_PROVIDER_STATE_KEY = Symbol.for(\n 'neatlogs.private_provider_state',\n);\ninterface PrivateProviderState {\n provider: TracerProvider | null;\n}\ntype NeatlogsGlobal = typeof globalThis & {\n [PRIVATE_SPAN_STORAGE_KEY]?: AsyncLocalStorage<Context>;\n [PRIVATE_PROVIDER_STATE_KEY]?: PrivateProviderState;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\n// Stores the full Neatlogs Context (parent span PLUS any threaded values such as\n// trace()'s prompt-template keys), not just the span. We never\n// activate the OTel global context, so this private store is the ONLY channel\n// through which those values propagate down to descendant spans.\nconst privateContextStorage =\n neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ??\n (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new AsyncLocalStorage<Context>());\nconst providerState: PrivateProviderState =\n neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ??\n (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {\n provider: null,\n });\n// Wrappers may be constructed or accidentally invoked before init(). They must\n// never fall back to a foreign process-global provider, so pre-init calls use a\n// local no-op tracer and safely emit no exported spans.\nconst preInitTracer: Tracer = {\n startSpan(): Span {\n return otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n _name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n const fn =\n typeof arg2 === 'function'\n ? arg2\n : typeof arg3 === 'function'\n ? arg3\n : arg4;\n return fn!(otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT)) as ReturnType<F>;\n },\n};\n\n/** @internal Configure the provider used by Neatlogs-created spans. */\nexport function _setNeatlogsProvider(provider: TracerProvider | null): void {\n providerState.provider = provider;\n}\n\n/** Resolve a tracer from the private provider when one is configured. */\nexport function getNeatlogsTracer(name: string): Tracer {\n return providerState.provider?.getTracer(name) ?? preInitTracer;\n}\n\n/**\n * @internal The private Neatlogs provider, or null before\n * init(). Used by integrations that must repoint a self-instrumenting library's\n * captured provider onto ours.\n */\nexport function getNeatlogsProvider(): TracerProvider | null {\n return providerState.provider;\n}\n\n/**\n * Wrap a tracer so that spans it creates parent from — and, for\n * `startActiveSpan`, activate on — the PRIVATE Neatlogs context instead of the\n * global one.\n *\n * We hand this to libraries that create their own spans off a tracer we give\n * them (the Vercel AI SDK's `experimental_telemetry.tracer`, which calls\n * `tracer.startActiveSpan()` internally). Without the facade the AI SDK's native\n * spans would parent from `context.active()` — the foreign co-tenant's context —\n * and `startActiveSpan` would push them onto the GLOBAL context, so a foreign\n * tracer's next span reads our native span as its parent. Both directions leak.\n *\n */\nexport function isolateTracer(tracer: Tracer): Tracer {\n const facade: Tracer = {\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const parent = context ?? getNeatlogsActiveContext();\n return tracer.startSpan(name, options, parent);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n // Normalize the 2/3/4-arg overloads of startActiveSpan.\n let options: SpanOptions | undefined;\n let context: Context | undefined;\n let fn: F;\n if (typeof arg2 === 'function') {\n fn = arg2 as F;\n } else if (typeof arg3 === 'function') {\n options = arg2 as SpanOptions;\n fn = arg3 as F;\n } else {\n options = arg2 as SpanOptions;\n context = arg3 as Context;\n fn = arg4 as F;\n }\n const parent = context ?? getNeatlogsActiveContext();\n const span = tracer.startSpan(name, options, parent);\n return withNeatlogsSpan(span, () => fn(span), parent) as ReturnType<F>;\n },\n };\n return facade;\n}\n\n/**\n * The base context to build new Neatlogs spans and values on. NEVER contains a\n * foreign provider's span: it reads our private store, or ROOT_CONTEXT when\n * nothing is active.\n */\nexport function getNeatlogsActiveContext(): Context {\n return privateContextStorage.getStore() ?? ROOT_CONTEXT;\n}\n\n/** Return only the active Neatlogs span (never a foreign provider's span). */\nexport function getActiveNeatlogsSpan(): Span | undefined {\n return otelTrace.getSpan(getNeatlogsActiveContext());\n}\n\n/**\n * Return the trace-ROOT Neatlogs span, or undefined when no trace is active.\n *\n * Unlike {@link getActiveNeatlogsSpan} (innermost), this is the outermost span\n * of the current trace — the one the backend derives trace-level output from\n * (`parent_span_id=''`). It is stashed on the private context the first time a\n * span becomes active, so nested calls still resolve to the root.\n */\nexport function getNeatlogsRootSpan(): Span | undefined {\n return getNeatlogsActiveContext().getValue(NEATLOGS_ROOT_SPAN_KEY) as\n | Span\n | undefined;\n}\n\n/**\n * Build a parent context that cannot contain a foreign provider's span.\n *\n * The active Neatlogs context already carries the parent span AND any values\n * the caller threaded in upstream (e.g. `trace()`'s prompt-template values), so\n * those values reach the span processor via `onStart(parentContext)`. An\n * explicit `baseContext` (a caller's own value-carrying context) is honored as-is.\n */\nexport function getNeatlogsParentContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * A base context for callers that thread parent linkage themselves\n * (callback/event handlers that keep their own run-id → span map: LangChain,\n * OpenAI-Agents, Claude Agent SDK).\n *\n * This is the ACTIVE Neatlogs context — the private store's\n * context if a Neatlogs `trace()`/`span()` encloses this call, else\n * ROOT_CONTEXT. So a handler's own root/entry span nests under an enclosing\n * Neatlogs trace (preserving its session + end-user id) when one exists, and\n * auto-roots cleanly when one doesn't. A foreign provider's active span can\n * never leak in as an ancestor because the global context is never read.\n */\nexport function getNeatlogsBaseContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * Build an execution context for a Neatlogs span while preserving the active\n * foreign context. The span rides our private context store.\n */\nexport function getNeatlogsExecutionContext(\n span: Span,\n baseContext: Context = ROOT_CONTEXT,\n): Context {\n return otelTrace.setSpan(baseContext, span);\n}\n\n/**\n * Run a callback with a Neatlogs span active under the appropriate policy.\n *\n * The stored context is `setSpan(base, span)` — carrying the span PLUS whatever\n * values `base` holds — so threaded values (prompt templates)\n * propagate to descendant spans through our private store instead of the global\n * OTel context we deliberately never touch.\n */\nexport function withNeatlogsSpan<T>(\n span: Span,\n fn: () => T,\n baseContext?: Context,\n): T {\n const base = baseContext ?? getNeatlogsActiveContext();\n let ctx = otelTrace.setSpan(base, span);\n // The first span activated in a context with no root recorded IS the root of\n // this trace; remember it so descendants (setTraceOutput) can target it.\n if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === undefined) {\n ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span);\n }\n return privateContextStorage.run(ctx, fn);\n}\n","/**\n * End-user identity.\n *\n * The *end user* is the user of OUR CUSTOMER'S application — the person\n * interacting with the AI product our customer built. This is deliberately\n * distinct from the operator-level `userId` set in {@link init}, which identifies\n * whoever is running the SDK (a developer, a service account, a CI job).\n *\n * Model: **one end-user per trace.** Identity is declared once at a trace\n * boundary — `init()` (process-global default), `trace()` (per-request), or\n * `span()` (per root) — via new options on those existing constructs. There is\n * no separate `identify()` call and no nested per-span override: a trace belongs\n * to a single end-user.\n *\n * End-user identity belongs to the trace as a whole, so the SDK only stamps it\n * on the trace's ROOT span (any span kind, created via `trace()` / `span()`). A\n * non-root child span never carries it. The backend reads end-user from the root\n * span and rolls the value up to the trace (and its session) so\n * filtering/analytics are trace- and session-level, not per-span.\n *\n * Canonical span attributes:\n * `neatlogs.end_user.id` — the end-user identifier (string)\n * `neatlogs.end_user.metadata` — JSON object of arbitrary end-user fields\n */\n\nimport { type Span } from '@opentelemetry/api';\nimport { getLogger } from './logger.js';\nimport { currentEndUserId, currentEndUserMetadata } from './identity.js';\nimport { getActiveNeatlogsSpan } from './provider.js';\n\nconst logger = getLogger();\n\nexport const END_USER_ID_KEY = 'neatlogs.end_user.id';\nexport const END_USER_METADATA_KEY = 'neatlogs.end_user.metadata';\n\n/** Coerce end-user metadata to a JSON string. Returns undefined when empty. */\nexport function normalizeEndUserMetadata(\n metadata: string | Record<string, any> | undefined | null,\n): string | undefined {\n if (metadata === undefined || metadata === null) return undefined;\n if (typeof metadata === 'string') return metadata || undefined;\n try {\n return JSON.stringify(metadata);\n } catch {\n return JSON.stringify(String(metadata));\n }\n}\n\n/**\n * True when no recording span is currently active — i.e. a span created now\n * would be the trace root.\n */\nexport function isRootSpan(): boolean {\n // Resolve from the private span store in isolated mode (a foreign provider's\n // active span must not count as our parent), else the active OTel span.\n const current = getActiveNeatlogsSpan();\n return !(current && current.isRecording());\n}\n\n/**\n * Stamp end-user id/metadata onto a ROOT span (best-effort). When `isRoot` is\n * false the span is a child and the value is ignored — the backend reads\n * end-user from the root span and rolls it up to the trace and session.\n */\nexport function applyEndUserAttributes(\n span: Span,\n endUserId?: string,\n endUserMetadata?: string | Record<string, any> | null,\n isRoot = true,\n): void {\n // Resolution at the root (per field): explicit per-call arg wins, else the\n // identify() context (request-scoped). Non-root spans never carry identity.\n const resolvedId = isRoot ? (endUserId ?? currentEndUserId()) : endUserId;\n const resolvedMeta = isRoot\n ? (endUserMetadata ?? currentEndUserMetadata())\n : endUserMetadata;\n if (!resolvedId && !resolvedMeta) return;\n if (!isRoot) {\n logger.debug(\n '[end_user] Ignoring endUserId/endUserMetadata on a non-root span — ' +\n 'declare it on the trace root (top-level trace()/span()) or identify().',\n );\n return;\n }\n if (resolvedId) {\n span.setAttribute(END_USER_ID_KEY, String(resolvedId));\n }\n const metaJson = normalizeEndUserMetadata(resolvedMeta);\n if (metaJson) {\n span.setAttribute(END_USER_METADATA_KEY, metaJson);\n }\n}\n","/**\n * Session identity.\n *\n * A *session* is one conversation / thread / journey — it groups the multiple\n * traces of a multi-turn interaction (one turn = one trace). Identity is\n * declared once at a trace boundary — `init()` (process-global default),\n * `trace()` (per-turn), or `span()` (per root) — via the `sessionId` option on\n * those existing constructs. The SDK stamps only the trace's ROOT span; the\n * backend rolls the value up to group the traces of a session together.\n *\n * For single-turn workflows a session maps 1:1 to a trace: when no session is\n * declared the backend backfills `session_id = trace_id`.\n *\n * Canonical span attribute:\n * `neatlogs.session.id` — the session identifier (string)\n */\n\nimport { type Span } from '@opentelemetry/api';\nimport { getLogger } from './logger.js';\nimport { currentSessionId } from './identity.js';\n\nconst logger = getLogger();\n\nexport const SESSION_ID_KEY = 'neatlogs.session.id';\n\n/**\n * Stamp the session id onto a ROOT span (best-effort). When `isRoot` is false\n * the span is a child and the value is ignored — the backend reads session from\n * the root span and groups traces by it.\n */\nexport function applySessionAttributes(\n span: Span,\n sessionId?: string,\n isRoot = true,\n): void {\n // Resolution at the root: explicit per-call arg wins, else the identify()\n // context (request-scoped). Non-root spans never carry session identity.\n const resolved = isRoot ? (sessionId ?? currentSessionId()) : sessionId;\n if (!resolved) return;\n if (!isRoot) {\n logger.debug(\n '[session] Ignoring sessionId on a non-root span — declare it on the ' +\n 'trace root (top-level trace()/span()) or identify().',\n );\n return;\n }\n span.setAttribute(SESSION_ID_KEY, String(resolved));\n}\n","/**\n * Core span decoration utilities.\n * Provides the low-level wrapping logic used by span() and Span().\n */\n\nimport { ROOT_CONTEXT, SpanStatusCode, type Span as OtelSpan } from '@opentelemetry/api';\nimport { getLogger } from '../core/logger.js';\nimport { registerMask } from '../core/mask.js';\nimport { applyEndUserAttributes, isRootSpan } from '../core/end-user.js';\nimport { applySessionAttributes } from '../core/session.js';\nimport {\n getNeatlogsTracer,\n getNeatlogsParentContext,\n withNeatlogsSpan,\n} from '../core/provider.js';\nimport type { SpanOptions, MaskFunction } from '../types.js';\n\nconst logger = getLogger();\nconst TRACER_NAME = 'neatlogs';\n\n/** Safely serialize any value to JSON string. */\nexport function safeJsonDumps(value: any): string {\n try {\n return JSON.stringify(value, (_key, val) => {\n if (typeof val === 'bigint') return val.toString();\n if (val instanceof Error) return { message: val.message, name: val.name, stack: val.stack };\n if (typeof val === 'function') return `[Function: ${val.name || 'anonymous'}]`;\n return val;\n });\n } catch {\n return String(value);\n }\n}\n\n/** Serialize an object for span attributes. Handles toJSON(), plain objects, primitives. */\nexport function serializeObj(obj: any): any {\n if (obj === null || obj === undefined) return obj;\n if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') return obj;\n if (typeof obj.toJSON === 'function') return obj.toJSON();\n if (Array.isArray(obj)) return obj.map(serializeObj);\n if (typeof obj === 'object') {\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = serializeObj(value);\n }\n return result;\n }\n return String(obj);\n}\n\n/** Check if content capture is enabled via env var. */\nexport function shouldCaptureContent(): boolean {\n const envVal = process.env.NEATLOGS_TRACE_CONTENT;\n if (envVal === undefined || envVal === '') return true;\n return envVal.toLowerCase() !== 'false' && envVal !== '0';\n}\n\n/** Set common span attributes from options. */\nexport function setCommonSpanAttrs(span: OtelSpan, opts: SpanOptions): void {\n if (opts.kind) {\n span.setAttribute('openinference.span.kind', opts.kind);\n }\n if (opts.internal) {\n span.setAttribute('neatlogs.internal', true);\n }\n if (opts.description) {\n span.setAttribute('neatlogs.description', opts.description);\n }\n if (opts.mask) {\n const maskId = registerMask(opts.mask);\n span.setAttribute('neatlogs.mask_id', maskId);\n }\n}\n\nexport interface DecorateSpanOptions extends SpanOptions {\n /** Override the span name (defaults to function name). */\n spanName?: string;\n /** Post-process callback after the function returns. */\n postprocessResult?: (span: OtelSpan, result: any, boundInputs: Record<string, any>) => void;\n}\n\n/**\n * Core wrapper factory that creates an instrumented version of a function.\n * This is the low-level building block used by span() and Span().\n */\nexport function decorateSpan<TArgs extends any[], TReturn>(\n opts: DecorateSpanOptions,\n fn: (...args: TArgs) => TReturn,\n): (...args: TArgs) => TReturn extends Promise<any> ? TReturn : Promise<Awaited<TReturn>> {\n const spanName = opts.spanName ?? opts.name ?? (fn.name || 'anonymous');\n const captureInput = opts.captureInput !== false;\n const captureOutput = opts.captureOutput !== false;\n const doCapture = shouldCaptureContent();\n\n const wrapped = (...args: TArgs): any => {\n // Resolve the tracer at CALL time, not definition time: a decorator applied\n // before init() would otherwise capture the no-op global provider forever\n // and emit zero spans once the private provider is configured.\n const tracer = getNeatlogsTracer(TRACER_NAME);\n // End-user belongs to the trace root only. Capture root status BEFORE the\n // span is created (once it's active, it would be the \"current\" span).\n const isRoot = isRootSpan();\n // Parent from our private span store; a root decorated function anchors a\n // fresh trace under ROOT_CONTEXT.\n const parentContext = isRoot ? ROOT_CONTEXT : getNeatlogsParentContext();\n const span = tracer.startSpan(spanName, {}, parentContext);\n return withNeatlogsSpan(span, () => {\n try {\n // Set common attributes\n setCommonSpanAttrs(span, opts);\n\n // Session/end-user identity (root span only; skipped on a non-root child).\n applySessionAttributes(span, opts.sessionId, isRoot);\n applyEndUserAttributes(span, opts.endUserId, opts.endUserMetadata, isRoot);\n\n // Capture input\n if (captureInput && doCapture && args.length > 0) {\n try {\n const inputValue = args.length === 1 ? serializeObj(args[0]) : args.map(serializeObj);\n span.setAttribute('input.value', safeJsonDumps(inputValue));\n } catch (err) {\n logger.debug(`Failed to capture input: ${err}`);\n }\n }\n\n // Execute the function\n const result = fn(...args);\n\n // Handle async results\n if (result instanceof Promise) {\n return result\n .then((resolved: any) => {\n // Capture output\n if (captureOutput && doCapture) {\n try {\n span.setAttribute('output.value', safeJsonDumps(serializeObj(resolved)));\n } catch (err) {\n logger.debug(`Failed to capture output: ${err}`);\n }\n }\n\n // Post-process\n if (opts.postprocessResult) {\n try {\n const boundInputs = _extractBoundInputs(fn, args);\n opts.postprocessResult(span, resolved, boundInputs);\n } catch (err) {\n logger.debug(`Postprocess failed: ${err}`);\n }\n }\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return resolved;\n })\n .catch((error: any) => {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error?.message ?? String(error),\n });\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n span.end();\n throw error;\n });\n }\n\n // Sync result\n if (captureOutput && doCapture) {\n try {\n span.setAttribute('output.value', safeJsonDumps(serializeObj(result)));\n } catch (err) {\n logger.debug(`Failed to capture output: ${err}`);\n }\n }\n\n if (opts.postprocessResult) {\n try {\n const boundInputs = _extractBoundInputs(fn, args);\n opts.postprocessResult(span, result, boundInputs);\n } catch (err) {\n logger.debug(`Postprocess failed: ${err}`);\n }\n }\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return result;\n } catch (error: any) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error?.message ?? String(error),\n });\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n span.end();\n throw error;\n }\n });\n };\n\n // Preserve function name for debugging\n Object.defineProperty(wrapped, 'name', { value: spanName, configurable: true });\n\n return wrapped as any;\n}\n\n/** Extract named arguments from function call (best-effort). */\nfunction _extractBoundInputs(fn: Function, args: any[]): Record<string, any> {\n const result: Record<string, any> = {};\n // Parse function parameter names from toString()\n const fnStr = fn.toString();\n const match = fnStr.match(/\\(([^)]*)\\)/);\n if (match) {\n const paramNames = match[1]\n .split(',')\n .map((p) => p.trim().replace(/\\s*[:=].*$/, '').replace(/^\\.\\.\\./, ''))\n .filter(Boolean);\n for (let i = 0; i < Math.min(paramNames.length, args.length); i++) {\n result[paramNames[i]] = args[i];\n }\n }\n return result;\n}\n"],"mappings":"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","names":["SpanStatusCode","otelTrace","createContextKey","ROOT_CONTEXT","SpanStatusCode","AsyncLocalStorage","AsyncLocalStorage","logger","logger","logger","ROOT_CONTEXT","logger","logger","createContextKey","otelTrace","SpanStatusCode"]}
@@ -101,6 +101,7 @@ function currentEndUserMetadata() {
101
101
  // src/core/provider.ts
102
102
  var import_node_async_hooks3 = require("async_hooks");
103
103
  var import_api = require("@opentelemetry/api");
104
+ var NEATLOGS_ROOT_SPAN_KEY = (0, import_api.createContextKey)("neatlogs.root_span");
104
105
  var PRIVATE_SPAN_STORAGE_KEY = /* @__PURE__ */ Symbol.for(
105
106
  "neatlogs.private_span_async_local_storage"
106
107
  );
@@ -135,7 +136,10 @@ function getNeatlogsParentContext(baseContext) {
135
136
  }
136
137
  function withNeatlogsSpan(span, fn, baseContext) {
137
138
  const base = baseContext ?? getNeatlogsActiveContext();
138
- const ctx = import_api.trace.setSpan(base, span);
139
+ let ctx = import_api.trace.setSpan(base, span);
140
+ if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === void 0) {
141
+ ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span);
142
+ }
139
143
  return privateContextStorage.run(ctx, fn);
140
144
  }
141
145