neatlogs 1.1.1 → 1.1.2

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 (45) hide show
  1. package/README.md +2 -2
  2. package/dist/anthropic.cjs +68 -2
  3. package/dist/anthropic.cjs.map +1 -1
  4. package/dist/anthropic.mjs +68 -2
  5. package/dist/anthropic.mjs.map +1 -1
  6. package/dist/azure-openai.cjs +68 -2
  7. package/dist/azure-openai.cjs.map +1 -1
  8. package/dist/azure-openai.mjs +68 -2
  9. package/dist/azure-openai.mjs.map +1 -1
  10. package/dist/bedrock.cjs +68 -2
  11. package/dist/bedrock.cjs.map +1 -1
  12. package/dist/bedrock.mjs +68 -2
  13. package/dist/bedrock.mjs.map +1 -1
  14. package/dist/browser.cjs +19 -9
  15. package/dist/browser.cjs.map +1 -1
  16. package/dist/browser.d.ts +23 -5
  17. package/dist/browser.mjs +19 -9
  18. package/dist/browser.mjs.map +1 -1
  19. package/dist/index.cjs +225 -381
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.ts +38 -19
  22. package/dist/index.mjs +224 -381
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/langchain.cjs +68 -2
  25. package/dist/langchain.cjs.map +1 -1
  26. package/dist/langchain.mjs +68 -2
  27. package/dist/langchain.mjs.map +1 -1
  28. package/dist/openai.cjs +68 -2
  29. package/dist/openai.cjs.map +1 -1
  30. package/dist/openai.mjs +68 -2
  31. package/dist/openai.mjs.map +1 -1
  32. package/dist/opencode-plugin.cjs +2 -2
  33. package/dist/opencode-plugin.cjs.map +1 -1
  34. package/dist/opencode-plugin.d.ts +1 -1
  35. package/dist/opencode-plugin.mjs +2 -2
  36. package/dist/opencode-plugin.mjs.map +1 -1
  37. package/dist/openrouter-agent.cjs +68 -2
  38. package/dist/openrouter-agent.cjs.map +1 -1
  39. package/dist/openrouter-agent.mjs +68 -2
  40. package/dist/openrouter-agent.mjs.map +1 -1
  41. package/dist/vertex-ai.cjs +68 -2
  42. package/dist/vertex-ai.cjs.map +1 -1
  43. package/dist/vertex-ai.mjs +68 -2
  44. package/dist/vertex-ai.mjs.map +1 -1
  45. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bedrock.ts","../src/core/auto-root.ts","../src/core/context.ts","../src/prompt/template.ts","../src/core/logger.ts","../src/core/mask.ts","../src/core/end-user.ts","../src/decorators/base.ts"],"sourcesContent":["/**\n * Neatlogs AWS Bedrock wrapper.\n *\n * The AWS SDK v3 `BedrockRuntimeClient` dispatches every API through\n * `client.send(new XCommand({...}))`. This wrapper intercepts `send`, inspects\n * the command, and traces the Converse and InvokeModel APIs. Token extraction\n * handles Claude, Titan, and Llama response formats.\n *\n * Usage:\n * import { wrapBedrock } from 'neatlogs/bedrock';\n * import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';\n * const client = wrapBedrock(new BedrockRuntimeClient({ region: 'us-east-1' }));\n * await client.send(new ConverseCommand({ modelId, messages }));\n *\n * `neatlogs.llm.provider` is always `bedrock`; `neatlogs.llm.system` is the\n * underlying model vendor (anthropic / amazon / meta / ...), inferred from modelId.\n */\n\nimport { trace, context as otelContext, SpanStatusCode, type Span } from '@opentelemetry/api';\nimport { getProviderTracer } from './core/auto-root.js';\n\nconst TRACER_NAME = 'neatlogs.bedrock';\nconst PROVIDER = 'bedrock';\n\n/**\n * Wrap a tool/function implementation to emit TOOL spans when executed.\n *\n * Usage:\n * const getWeather = traceTool('get_weather', async (args: { city: string }) => {\n * return `Weather in ${args.city}: sunny`;\n * });\n */\nexport function traceTool<TArgs = any, TResult = any>(\n name: string,\n fn: (args: TArgs) => TResult | Promise<TResult>,\n): (args: TArgs) => Promise<TResult> {\n return async function tracedTool(args: TArgs): Promise<TResult> {\n const tracer = getProviderTracer(TRACER_NAME);\n return tracer.startActiveSpan(\n `tool.${name}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n 'neatlogs.tool.name': name,\n 'input.value': safeStringify(args),\n },\n },\n otelContext.active(),\n async (span) => {\n try {\n const result = await fn(args);\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\nexport function wrapBedrock<T extends object>(client: T): T {\n const c = client as any;\n if (c._neatlogsBedrockPatched) return client;\n if (typeof c.send !== 'function') return client;\n\n const originalSend = c.send.bind(c);\n\n c.send = function (command: any, ...rest: any[]): any {\n const name = command?.constructor?.name ?? '';\n const input = command?.input ?? {};\n\n if (name === 'ConverseCommand') {\n return tracedConverse(originalSend, command, input, rest, false);\n }\n if (name === 'ConverseStreamCommand') {\n return tracedConverse(originalSend, command, input, rest, true);\n }\n if (name === 'InvokeModelCommand') {\n return tracedInvokeModel(originalSend, command, input, rest, false);\n }\n if (name === 'InvokeModelWithResponseStreamCommand') {\n return tracedInvokeModel(originalSend, command, input, rest, true);\n }\n\n return originalSend(command, ...rest);\n };\n\n c._neatlogsBedrockPatched = true;\n return client;\n}\n\n// ---------------------------------------------------------------------------\n// Vendor / span helpers\n// ---------------------------------------------------------------------------\n\nfunction vendorFromModel(modelId: any): string {\n let tail = String(modelId ?? '').split('/').pop() ?? '';\n for (const prefix of ['us.', 'eu.', 'apac.', 'us-gov.']) {\n if (tail.startsWith(prefix)) tail = tail.slice(prefix.length);\n }\n const vendor = tail.includes('.') ? tail.split('.')[0] : '';\n return vendor || 'bedrock';\n}\n\nfunction startSpan(name: string, modelId: any, isStream: boolean): Span {\n return getProviderTracer(TRACER_NAME).startSpan(name, {\n attributes: {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': PROVIDER,\n 'neatlogs.llm.system': vendorFromModel(modelId),\n 'neatlogs.llm.model_name': String(modelId ?? ''),\n 'neatlogs.llm.is_streaming': isStream,\n },\n }, otelContext.active());\n}\n\n// ---------------------------------------------------------------------------\n// Converse API\n// ---------------------------------------------------------------------------\n\nfunction tracedConverse(\n originalSend: (...a: any[]) => any,\n command: any,\n input: any,\n rest: any[],\n isStream: boolean,\n): any {\n const span = startSpan(\n isStream ? 'bedrock.converse_stream' : 'bedrock.converse',\n input?.modelId,\n isStream,\n );\n setConverseInput(span, input);\n\n const ctx = trace.setSpan(otelContext.active(), span);\n const promise = otelContext.with(ctx, () => originalSend(command, ...rest));\n\n return promise.then(\n (response: any) => {\n if (isStream) {\n return wrapConverseStream(response, span);\n }\n finalizeConverse(span, response);\n return response;\n },\n (err: any) => {\n recordError(span, err);\n throw err;\n },\n );\n}\n\nfunction setConverseInput(span: Span, input: any): void {\n let idx = 0;\n\n if (Array.isArray(input?.system)) {\n const text = input.system.map((b: any) => b?.text ?? '').join(' ').trim();\n if (text) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'system');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, text);\n idx++;\n }\n }\n\n for (const msg of input?.messages ?? []) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, msg?.role ?? 'user');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, converseBlocksToText(msg?.content));\n idx++;\n }\n\n const cfg = input?.inferenceConfig ?? {};\n if (cfg.temperature != null) span.setAttribute('neatlogs.llm.temperature', cfg.temperature);\n if (cfg.topP != null) span.setAttribute('neatlogs.llm.top_p', cfg.topP);\n if (cfg.maxTokens != null) span.setAttribute('neatlogs.llm.max_tokens', cfg.maxTokens);\n\n const tools = input?.toolConfig?.tools ?? [];\n for (let i = 0; i < tools.length; i++) {\n const spec = tools[i]?.toolSpec ?? {};\n if (spec.name) span.setAttribute(`neatlogs.llm.tools.${i}.name`, spec.name);\n if (spec.description) span.setAttribute(`neatlogs.llm.tools.${i}.description`, spec.description);\n if (spec.inputSchema) span.setAttribute(`neatlogs.llm.tools.${i}.input_schema`, safeStringify(spec.inputSchema));\n }\n}\n\nfunction converseBlocksToText(content: any): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return safeStringify(content);\n const parts: string[] = [];\n for (const block of content) {\n if (block && typeof block === 'object') {\n if ('text' in block) parts.push(String(block.text));\n else if ('toolResult' in block) parts.push(safeStringify(block.toolResult));\n else if ('toolUse' in block) parts.push(safeStringify(block.toolUse));\n else parts.push(safeStringify(block));\n } else {\n parts.push(String(block));\n }\n }\n return parts.join('\\n');\n}\n\nfunction finalizeConverse(span: Span, response: any): void {\n const content = response?.output?.message?.content ?? [];\n const textParts: string[] = [];\n let toolIdx = 0;\n\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if ('text' in block) {\n textParts.push(String(block.text));\n } else if ('toolUse' in block) {\n const tu = block.toolUse;\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.id`, String(tu?.toolUseId ?? ''));\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.name`, String(tu?.name ?? ''));\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.arguments`, safeStringify(tu?.input ?? {}));\n toolIdx++;\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 (response?.stopReason) span.setAttribute('neatlogs.llm.finish_reason', String(response.stopReason));\n setConverseUsage(span, response?.usage);\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\nfunction setConverseUsage(span: Span, usage: any): void {\n if (!usage) return;\n if (usage.inputTokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', usage.inputTokens);\n if (usage.outputTokens != null) span.setAttribute('neatlogs.llm.token_count.completion', usage.outputTokens);\n if (usage.totalTokens != null) span.setAttribute('neatlogs.llm.token_count.total', usage.totalTokens);\n if (usage.cacheReadInputTokens != null) span.setAttribute('neatlogs.llm.token_count.cache_read', usage.cacheReadInputTokens);\n if (usage.cacheWriteInputTokens != null) span.setAttribute('neatlogs.llm.token_count.cache_write', usage.cacheWriteInputTokens);\n}\n\nfunction wrapConverseStream(response: any, span: Span): any {\n const stream = response?.stream;\n if (!stream || typeof stream[Symbol.asyncIterator] !== 'function') {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return response;\n }\n\n const originalIterator = stream[Symbol.asyncIterator].bind(stream);\n const textParts: string[] = [];\n const toolCalls: Record<number, { id: string; name: string; arguments: string }> = {};\n let finishReason = '';\n let usage: any = null;\n\n const wrappedStream = {\n [Symbol.asyncIterator]() {\n const iterator = originalIterator();\n return {\n async next(): Promise<IteratorResult<any>> {\n try {\n const result = await iterator.next();\n if (result.done) {\n finalizeConverseStream(span, textParts, toolCalls, finishReason, usage);\n return result;\n }\n const ev = result.value;\n const delta = ev?.contentBlockDelta?.delta;\n if (delta?.text) textParts.push(delta.text);\n if (delta?.toolUse?.input) {\n const idx = ev.contentBlockDelta.contentBlockIndex ?? 0;\n toolCalls[idx] = toolCalls[idx] ?? { id: '', name: '', arguments: '' };\n toolCalls[idx].arguments += delta.toolUse.input;\n }\n const startBlk = ev?.contentBlockStart?.start?.toolUse;\n if (startBlk) {\n const idx = ev.contentBlockStart.contentBlockIndex ?? 0;\n toolCalls[idx] = toolCalls[idx] ?? { id: '', name: '', arguments: '' };\n toolCalls[idx].name = startBlk.name ?? '';\n toolCalls[idx].id = startBlk.toolUseId ?? '';\n }\n if (ev?.messageStop?.stopReason) finishReason = ev.messageStop.stopReason;\n if (ev?.metadata?.usage) usage = ev.metadata.usage;\n return result;\n } catch (err) {\n recordError(span, err);\n throw err;\n }\n },\n async return(value?: any): Promise<IteratorResult<any>> {\n finalizeConverseStream(span, textParts, toolCalls, finishReason, usage);\n return iterator.return?.(value) ?? { done: true, value: undefined };\n },\n };\n },\n };\n\n response.stream = wrappedStream;\n return response;\n}\n\nfunction finalizeConverseStream(\n span: Span,\n textParts: string[],\n toolCalls: Record<number, { id: string; name: string; arguments: string }>,\n finishReason: string,\n usage: any,\n): void {\n const full = textParts.join('');\n if (full) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', full);\n }\n let j = 0;\n for (const tc of Object.values(toolCalls)) {\n if (tc.id) span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, tc.id);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc.name);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, tc.arguments);\n j++;\n }\n if (finishReason) span.setAttribute('neatlogs.llm.finish_reason', String(finishReason));\n setConverseUsage(span, usage);\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// InvokeModel API (vendor-specific body formats)\n// ---------------------------------------------------------------------------\n\nfunction isEmbeddingModel(modelId: any): boolean {\n return String(modelId ?? '').toLowerCase().includes('embed');\n}\n\nfunction tracedInvokeModel(\n originalSend: (...a: any[]) => any,\n command: any,\n input: any,\n rest: any[],\n isStream: boolean,\n): any {\n const vendor = vendorFromModel(input?.modelId);\n const isEmbedding = !isStream && isEmbeddingModel(input?.modelId);\n const bodyIn = decodeBody(input?.body);\n\n let span: Span;\n if (isEmbedding) {\n span = getProviderTracer(TRACER_NAME).startSpan('bedrock.invoke_model', {\n attributes: {\n 'neatlogs.span.kind': 'EMBEDDING',\n 'neatlogs.llm.provider': PROVIDER,\n 'neatlogs.embedding.model_name': String(input?.modelId ?? ''),\n },\n }, otelContext.active());\n const text = bodyIn?.inputText ?? bodyIn?.texts ?? bodyIn?.input_text;\n if (text) {\n span.setAttribute('neatlogs.embedding.text', (typeof text === 'string' ? text : safeStringify(text)));\n }\n } else {\n span = startSpan(\n isStream ? 'bedrock.invoke_model_with_response_stream' : 'bedrock.invoke_model',\n input?.modelId,\n isStream,\n );\n setInvokeInput(span, vendor, bodyIn);\n }\n\n const ctx = trace.setSpan(otelContext.active(), span);\n const promise = otelContext.with(ctx, () => originalSend(command, ...rest));\n\n return promise.then(\n (response: any) => {\n if (isStream) {\n return wrapInvokeStream(response, span, vendor);\n }\n try {\n if (isEmbedding) {\n finalizeInvokeEmbedding(span, decodeBody(response?.body));\n } else {\n finalizeInvoke(span, vendor, decodeBody(response?.body));\n }\n } catch {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n }\n return response;\n },\n (err: any) => {\n recordError(span, err);\n throw err;\n },\n );\n}\n\nfunction finalizeInvokeEmbedding(span: Span, body: any): void {\n const emb = body?.embedding;\n const embs = body?.embeddings;\n if (Array.isArray(emb)) {\n span.setAttribute('neatlogs.embedding.count', 1);\n span.setAttribute('neatlogs.embedding.dimensions', emb.length);\n } else if (Array.isArray(embs) && embs.length) {\n span.setAttribute('neatlogs.embedding.count', embs.length);\n if (Array.isArray(embs[0])) span.setAttribute('neatlogs.embedding.dimensions', embs[0].length);\n }\n if (body?.inputTextTokenCount != null) {\n span.setAttribute('neatlogs.llm.token_count.prompt', body.inputTextTokenCount);\n span.setAttribute('neatlogs.embedding.token_count', body.inputTextTokenCount);\n }\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\nfunction decodeBody(body: any): any {\n try {\n if (body == null) return {};\n if (typeof body === 'string') return JSON.parse(body);\n // Uint8Array (AWS SDK v3 returns the response body as bytes)\n if (body instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(body));\n if (typeof body === 'object' && typeof body.byteLength === 'number') {\n return JSON.parse(new TextDecoder().decode(body));\n }\n if (typeof body === 'object') return body;\n } catch {\n /* fall through */\n }\n return {};\n}\n\nfunction setInvokeInput(span: Span, vendor: string, body: any): void {\n let idx = 0;\n if (body?.system) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'system');\n span.setAttribute(\n `neatlogs.llm.input_messages.${idx}.content`,\n typeof body.system === 'string' ? body.system : safeStringify(body.system),\n );\n idx++;\n }\n if (Array.isArray(body?.messages)) {\n for (const msg of body.messages) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, msg?.role ?? 'user');\n span.setAttribute(\n `neatlogs.llm.input_messages.${idx}.content`,\n typeof msg?.content === 'string' ? msg.content : safeStringify(msg?.content),\n );\n idx++;\n }\n } else if (body?.prompt) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'user');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, String(body.prompt));\n } else if (body?.inputText) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'user');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, String(body.inputText));\n }\n\n if (body?.temperature != null) span.setAttribute('neatlogs.llm.temperature', body.temperature);\n if (body?.top_p != null) span.setAttribute('neatlogs.llm.top_p', body.top_p);\n const maxTokens = body?.max_tokens ?? body?.maxTokens ?? body?.max_tokens_to_sample;\n if (maxTokens != null) span.setAttribute('neatlogs.llm.max_tokens', maxTokens);\n const cfg = body?.textGenerationConfig;\n if (cfg) {\n if (cfg.temperature != null) span.setAttribute('neatlogs.llm.temperature', cfg.temperature);\n if (cfg.topP != null) span.setAttribute('neatlogs.llm.top_p', cfg.topP);\n if (cfg.maxTokenCount != null) span.setAttribute('neatlogs.llm.max_tokens', cfg.maxTokenCount);\n }\n}\n\nfunction finalizeInvoke(span: Span, vendor: string, body: any): void {\n let text: string | undefined;\n let promptTokens: number | undefined;\n let completionTokens: number | undefined;\n let finishReason: string | undefined;\n\n if (vendor === 'anthropic') {\n if (Array.isArray(body?.content)) {\n text = body.content.filter((b: any) => b?.type === 'text').map((b: any) => b.text).join('');\n let toolIdx = 0;\n for (const b of body.content) {\n if (b?.type === 'tool_use') {\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.id`, String(b.id ?? ''));\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.name`, String(b.name ?? ''));\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.arguments`, safeStringify(b.input ?? {}));\n toolIdx++;\n }\n }\n } else if (body?.completion != null) {\n text = body.completion;\n }\n promptTokens = body?.usage?.input_tokens;\n completionTokens = body?.usage?.output_tokens;\n finishReason = body?.stop_reason;\n } else if (vendor === 'amazon') {\n const r = Array.isArray(body?.results) ? body.results[0] : undefined;\n if (r) {\n text = r.outputText;\n completionTokens = r.tokenCount;\n finishReason = r.completionReason;\n }\n promptTokens = body?.inputTextTokenCount;\n } else if (vendor === 'meta') {\n text = body?.generation;\n promptTokens = body?.prompt_token_count;\n completionTokens = body?.generation_token_count;\n finishReason = body?.stop_reason;\n } else if (vendor === 'cohere') {\n const g = Array.isArray(body?.generations) ? body.generations[0] : undefined;\n if (g) {\n text = g.text;\n finishReason = g.finish_reason;\n }\n } else {\n text = body?.generation ?? body?.completion ?? body?.outputText;\n }\n\n if (text) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', String(text));\n }\n if (promptTokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', promptTokens);\n if (completionTokens != null) span.setAttribute('neatlogs.llm.token_count.completion', completionTokens);\n if (promptTokens != null && completionTokens != null) {\n span.setAttribute('neatlogs.llm.token_count.total', promptTokens + completionTokens);\n }\n if (finishReason) span.setAttribute('neatlogs.llm.finish_reason', String(finishReason));\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\nfunction wrapInvokeStream(response: any, span: Span, vendor: string): any {\n const body = response?.body;\n if (!body || typeof body[Symbol.asyncIterator] !== 'function') {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return response;\n }\n\n const originalIterator = body[Symbol.asyncIterator].bind(body);\n const textParts: string[] = [];\n let promptTokens: number | undefined;\n let completionTokens: number | undefined;\n let finishReason: string | undefined;\n\n const finalize = () => {\n const full = textParts.join('');\n if (full) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', full);\n }\n if (promptTokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', promptTokens);\n if (completionTokens != null) span.setAttribute('neatlogs.llm.token_count.completion', completionTokens);\n if (finishReason) span.setAttribute('neatlogs.llm.finish_reason', String(finishReason));\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n };\n\n const wrappedBody = {\n [Symbol.asyncIterator]() {\n const iterator = originalIterator();\n return {\n async next(): Promise<IteratorResult<any>> {\n try {\n const result = await iterator.next();\n if (result.done) {\n finalize();\n return result;\n }\n const data = decodeBody(result.value?.chunk?.bytes);\n if (data?.type === 'content_block_delta') {\n textParts.push(data?.delta?.text ?? '');\n } else if (data?.type === 'message_delta') {\n if (data?.delta?.stop_reason) finishReason = data.delta.stop_reason;\n if (data?.usage?.output_tokens != null) completionTokens = data.usage.output_tokens;\n }\n if (data?.outputText) textParts.push(data.outputText);\n if (data?.generation) textParts.push(data.generation);\n const metrics = data?.['amazon-bedrock-invocationMetrics'];\n if (metrics) {\n promptTokens = metrics.inputTokenCount ?? promptTokens;\n completionTokens = metrics.outputTokenCount ?? completionTokens;\n }\n return result;\n } catch (err) {\n recordError(span, err);\n throw err;\n }\n },\n async return(value?: any): Promise<IteratorResult<any>> {\n finalize();\n return iterator.return?.(value) ?? { done: true, value: undefined };\n },\n };\n },\n };\n\n response.body = wrappedBody;\n return response;\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\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 context as otelContext,\n type Tracer,\n type Span,\n type Context,\n type SpanOptions,\n} from '@opentelemetry/api';\n\nimport { getSessionConfig } from './context.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 * 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 const existing = otelTrace.getSpan(baseCtx);\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 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 const ctx = context ?? otelContext.active();\n const parent = otelTrace.getSpan(ctx);\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, context);\n }\n\n const root = this._tracer.startSpan(\n resolveRootWorkflowName(),\n { attributes: { 'neatlogs.span.kind': 'workflow', 'neatlogs.auto_root': true } },\n ctx,\n );\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 return new AutoRootTracer(otelTrace.getTracer(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';\n\nimport { getLogger } from './logger.js';\nimport { safeJsonDumps, serializeObj } from '../decorators/base.js';\nimport type { TraceOptions, MaskFunction } from '../types.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 '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 `session_id` is set in init() AND no active parent span exists,\n * this creates a NEW 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 endUserId,\n endUserMetadata,\n attributes: explicitAttributes,\n ...extraOptions\n } = options;\n\n const sessionConfig = getSessionConfig();\n const sessionId = sessionConfig.sessionId;\n\n // Determine whether we are inside an existing active trace\n const currentSpan = otelTrace.getSpan(otelContext.active());\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 let ctx = otelContext.active();\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 = otelTrace.getTracer('neatlogs.trace');\n\n const spanCallback = async (span: Span): Promise<T> => {\n _setSpanAttributes(span, kind, extraAttributes);\n\n // End-user belongs to the trace root only; skipped on a non-root span.\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 if (shouldCreateRootTrace) {\n logger.debug(`[trace] Creating NEW root trace '${name}' (sessionId=${sessionId})`);\n return tracer.startActiveSpan(name, {}, ROOT_CONTEXT, spanCallback);\n } else {\n logger.debug(`[trace] Creating child span '${name}'`);\n return tracer.startActiveSpan(name, {}, ctx, spanCallback);\n }\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 * 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 { trace as otelTrace, context as otelContext, type Span } from '@opentelemetry/api';\nimport { getLogger } from './logger.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 const current = otelTrace.getSpan(otelContext.active());\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 if (!endUserId && !endUserMetadata) 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 init().',\n );\n return;\n }\n if (endUserId) {\n span.setAttribute(END_USER_ID_KEY, String(endUserId));\n }\n const metaJson = normalizeEndUserMetadata(endUserMetadata);\n if (metaJson) {\n span.setAttribute(END_USER_METADATA_KEY, metaJson);\n }\n}\n","/**\n * Core span decoration utilities.\n * Provides the low-level wrapping logic used by span() and Span().\n */\n\nimport { trace, 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 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 tracer = trace.getTracer(TRACER_NAME);\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 // 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 return tracer.startActiveSpan(spanName, (span: OtelSpan) => {\n try {\n // Set common attributes\n setCommonSpanAttrs(span, opts);\n\n // End-user identity (root span only; skipped on a non-root child).\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":["trace","otelContext","SpanStatusCode","otelTrace","otelContext","otelTrace","otelContext","SpanStatusCode","logger","logger","logger","otelContext","otelTrace","otelContext","SpanStatusCode","trace"]}
1
+ {"version":3,"sources":["../src/bedrock.ts","../src/core/auto-root.ts","../src/core/context.ts","../src/prompt/template.ts","../src/core/logger.ts","../src/core/mask.ts","../src/core/end-user.ts","../src/core/identity.ts","../src/core/session.ts","../src/decorators/base.ts"],"sourcesContent":["/**\n * Neatlogs AWS Bedrock wrapper.\n *\n * The AWS SDK v3 `BedrockRuntimeClient` dispatches every API through\n * `client.send(new XCommand({...}))`. This wrapper intercepts `send`, inspects\n * the command, and traces the Converse and InvokeModel APIs. Token extraction\n * handles Claude, Titan, and Llama response formats.\n *\n * Usage:\n * import { wrapBedrock } from 'neatlogs/bedrock';\n * import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';\n * const client = wrapBedrock(new BedrockRuntimeClient({ region: 'us-east-1' }));\n * await client.send(new ConverseCommand({ modelId, messages }));\n *\n * `neatlogs.llm.provider` is always `bedrock`; `neatlogs.llm.system` is the\n * underlying model vendor (anthropic / amazon / meta / ...), inferred from modelId.\n */\n\nimport { trace, context as otelContext, SpanStatusCode, type Span } from '@opentelemetry/api';\nimport { getProviderTracer } from './core/auto-root.js';\n\nconst TRACER_NAME = 'neatlogs.bedrock';\nconst PROVIDER = 'bedrock';\n\n/**\n * Wrap a tool/function implementation to emit TOOL spans when executed.\n *\n * Usage:\n * const getWeather = traceTool('get_weather', async (args: { city: string }) => {\n * return `Weather in ${args.city}: sunny`;\n * });\n */\nexport function traceTool<TArgs = any, TResult = any>(\n name: string,\n fn: (args: TArgs) => TResult | Promise<TResult>,\n): (args: TArgs) => Promise<TResult> {\n return async function tracedTool(args: TArgs): Promise<TResult> {\n const tracer = getProviderTracer(TRACER_NAME);\n return tracer.startActiveSpan(\n `tool.${name}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n 'neatlogs.tool.name': name,\n 'input.value': safeStringify(args),\n },\n },\n otelContext.active(),\n async (span) => {\n try {\n const result = await fn(args);\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\nexport function wrapBedrock<T extends object>(client: T): T {\n const c = client as any;\n if (c._neatlogsBedrockPatched) return client;\n if (typeof c.send !== 'function') return client;\n\n const originalSend = c.send.bind(c);\n\n c.send = function (command: any, ...rest: any[]): any {\n const name = command?.constructor?.name ?? '';\n const input = command?.input ?? {};\n\n if (name === 'ConverseCommand') {\n return tracedConverse(originalSend, command, input, rest, false);\n }\n if (name === 'ConverseStreamCommand') {\n return tracedConverse(originalSend, command, input, rest, true);\n }\n if (name === 'InvokeModelCommand') {\n return tracedInvokeModel(originalSend, command, input, rest, false);\n }\n if (name === 'InvokeModelWithResponseStreamCommand') {\n return tracedInvokeModel(originalSend, command, input, rest, true);\n }\n\n return originalSend(command, ...rest);\n };\n\n c._neatlogsBedrockPatched = true;\n return client;\n}\n\n// ---------------------------------------------------------------------------\n// Vendor / span helpers\n// ---------------------------------------------------------------------------\n\nfunction vendorFromModel(modelId: any): string {\n let tail = String(modelId ?? '').split('/').pop() ?? '';\n for (const prefix of ['us.', 'eu.', 'apac.', 'us-gov.']) {\n if (tail.startsWith(prefix)) tail = tail.slice(prefix.length);\n }\n const vendor = tail.includes('.') ? tail.split('.')[0] : '';\n return vendor || 'bedrock';\n}\n\nfunction startSpan(name: string, modelId: any, isStream: boolean): Span {\n return getProviderTracer(TRACER_NAME).startSpan(name, {\n attributes: {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': PROVIDER,\n 'neatlogs.llm.system': vendorFromModel(modelId),\n 'neatlogs.llm.model_name': String(modelId ?? ''),\n 'neatlogs.llm.is_streaming': isStream,\n },\n }, otelContext.active());\n}\n\n// ---------------------------------------------------------------------------\n// Converse API\n// ---------------------------------------------------------------------------\n\nfunction tracedConverse(\n originalSend: (...a: any[]) => any,\n command: any,\n input: any,\n rest: any[],\n isStream: boolean,\n): any {\n const span = startSpan(\n isStream ? 'bedrock.converse_stream' : 'bedrock.converse',\n input?.modelId,\n isStream,\n );\n setConverseInput(span, input);\n\n const ctx = trace.setSpan(otelContext.active(), span);\n const promise = otelContext.with(ctx, () => originalSend(command, ...rest));\n\n return promise.then(\n (response: any) => {\n if (isStream) {\n return wrapConverseStream(response, span);\n }\n finalizeConverse(span, response);\n return response;\n },\n (err: any) => {\n recordError(span, err);\n throw err;\n },\n );\n}\n\nfunction setConverseInput(span: Span, input: any): void {\n let idx = 0;\n\n if (Array.isArray(input?.system)) {\n const text = input.system.map((b: any) => b?.text ?? '').join(' ').trim();\n if (text) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'system');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, text);\n idx++;\n }\n }\n\n for (const msg of input?.messages ?? []) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, msg?.role ?? 'user');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, converseBlocksToText(msg?.content));\n idx++;\n }\n\n const cfg = input?.inferenceConfig ?? {};\n if (cfg.temperature != null) span.setAttribute('neatlogs.llm.temperature', cfg.temperature);\n if (cfg.topP != null) span.setAttribute('neatlogs.llm.top_p', cfg.topP);\n if (cfg.maxTokens != null) span.setAttribute('neatlogs.llm.max_tokens', cfg.maxTokens);\n\n const tools = input?.toolConfig?.tools ?? [];\n for (let i = 0; i < tools.length; i++) {\n const spec = tools[i]?.toolSpec ?? {};\n if (spec.name) span.setAttribute(`neatlogs.llm.tools.${i}.name`, spec.name);\n if (spec.description) span.setAttribute(`neatlogs.llm.tools.${i}.description`, spec.description);\n if (spec.inputSchema) span.setAttribute(`neatlogs.llm.tools.${i}.input_schema`, safeStringify(spec.inputSchema));\n }\n}\n\nfunction converseBlocksToText(content: any): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return safeStringify(content);\n const parts: string[] = [];\n for (const block of content) {\n if (block && typeof block === 'object') {\n if ('text' in block) parts.push(String(block.text));\n else if ('toolResult' in block) parts.push(safeStringify(block.toolResult));\n else if ('toolUse' in block) parts.push(safeStringify(block.toolUse));\n else parts.push(safeStringify(block));\n } else {\n parts.push(String(block));\n }\n }\n return parts.join('\\n');\n}\n\nfunction finalizeConverse(span: Span, response: any): void {\n const content = response?.output?.message?.content ?? [];\n const textParts: string[] = [];\n let toolIdx = 0;\n\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if ('text' in block) {\n textParts.push(String(block.text));\n } else if ('toolUse' in block) {\n const tu = block.toolUse;\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.id`, String(tu?.toolUseId ?? ''));\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.name`, String(tu?.name ?? ''));\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.arguments`, safeStringify(tu?.input ?? {}));\n toolIdx++;\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 (response?.stopReason) span.setAttribute('neatlogs.llm.finish_reason', String(response.stopReason));\n setConverseUsage(span, response?.usage);\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\nfunction setConverseUsage(span: Span, usage: any): void {\n if (!usage) return;\n if (usage.inputTokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', usage.inputTokens);\n if (usage.outputTokens != null) span.setAttribute('neatlogs.llm.token_count.completion', usage.outputTokens);\n if (usage.totalTokens != null) span.setAttribute('neatlogs.llm.token_count.total', usage.totalTokens);\n if (usage.cacheReadInputTokens != null) span.setAttribute('neatlogs.llm.token_count.cache_read', usage.cacheReadInputTokens);\n if (usage.cacheWriteInputTokens != null) span.setAttribute('neatlogs.llm.token_count.cache_write', usage.cacheWriteInputTokens);\n}\n\nfunction wrapConverseStream(response: any, span: Span): any {\n const stream = response?.stream;\n if (!stream || typeof stream[Symbol.asyncIterator] !== 'function') {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return response;\n }\n\n const originalIterator = stream[Symbol.asyncIterator].bind(stream);\n const textParts: string[] = [];\n const toolCalls: Record<number, { id: string; name: string; arguments: string }> = {};\n let finishReason = '';\n let usage: any = null;\n\n const wrappedStream = {\n [Symbol.asyncIterator]() {\n const iterator = originalIterator();\n return {\n async next(): Promise<IteratorResult<any>> {\n try {\n const result = await iterator.next();\n if (result.done) {\n finalizeConverseStream(span, textParts, toolCalls, finishReason, usage);\n return result;\n }\n const ev = result.value;\n const delta = ev?.contentBlockDelta?.delta;\n if (delta?.text) textParts.push(delta.text);\n if (delta?.toolUse?.input) {\n const idx = ev.contentBlockDelta.contentBlockIndex ?? 0;\n toolCalls[idx] = toolCalls[idx] ?? { id: '', name: '', arguments: '' };\n toolCalls[idx].arguments += delta.toolUse.input;\n }\n const startBlk = ev?.contentBlockStart?.start?.toolUse;\n if (startBlk) {\n const idx = ev.contentBlockStart.contentBlockIndex ?? 0;\n toolCalls[idx] = toolCalls[idx] ?? { id: '', name: '', arguments: '' };\n toolCalls[idx].name = startBlk.name ?? '';\n toolCalls[idx].id = startBlk.toolUseId ?? '';\n }\n if (ev?.messageStop?.stopReason) finishReason = ev.messageStop.stopReason;\n if (ev?.metadata?.usage) usage = ev.metadata.usage;\n return result;\n } catch (err) {\n recordError(span, err);\n throw err;\n }\n },\n async return(value?: any): Promise<IteratorResult<any>> {\n finalizeConverseStream(span, textParts, toolCalls, finishReason, usage);\n return iterator.return?.(value) ?? { done: true, value: undefined };\n },\n };\n },\n };\n\n response.stream = wrappedStream;\n return response;\n}\n\nfunction finalizeConverseStream(\n span: Span,\n textParts: string[],\n toolCalls: Record<number, { id: string; name: string; arguments: string }>,\n finishReason: string,\n usage: any,\n): void {\n const full = textParts.join('');\n if (full) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', full);\n }\n let j = 0;\n for (const tc of Object.values(toolCalls)) {\n if (tc.id) span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, tc.id);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc.name);\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, tc.arguments);\n j++;\n }\n if (finishReason) span.setAttribute('neatlogs.llm.finish_reason', String(finishReason));\n setConverseUsage(span, usage);\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// InvokeModel API (vendor-specific body formats)\n// ---------------------------------------------------------------------------\n\nfunction isEmbeddingModel(modelId: any): boolean {\n return String(modelId ?? '').toLowerCase().includes('embed');\n}\n\nfunction tracedInvokeModel(\n originalSend: (...a: any[]) => any,\n command: any,\n input: any,\n rest: any[],\n isStream: boolean,\n): any {\n const vendor = vendorFromModel(input?.modelId);\n const isEmbedding = !isStream && isEmbeddingModel(input?.modelId);\n const bodyIn = decodeBody(input?.body);\n\n let span: Span;\n if (isEmbedding) {\n span = getProviderTracer(TRACER_NAME).startSpan('bedrock.invoke_model', {\n attributes: {\n 'neatlogs.span.kind': 'EMBEDDING',\n 'neatlogs.llm.provider': PROVIDER,\n 'neatlogs.embedding.model_name': String(input?.modelId ?? ''),\n },\n }, otelContext.active());\n const text = bodyIn?.inputText ?? bodyIn?.texts ?? bodyIn?.input_text;\n if (text) {\n span.setAttribute('neatlogs.embedding.text', (typeof text === 'string' ? text : safeStringify(text)));\n }\n } else {\n span = startSpan(\n isStream ? 'bedrock.invoke_model_with_response_stream' : 'bedrock.invoke_model',\n input?.modelId,\n isStream,\n );\n setInvokeInput(span, vendor, bodyIn);\n }\n\n const ctx = trace.setSpan(otelContext.active(), span);\n const promise = otelContext.with(ctx, () => originalSend(command, ...rest));\n\n return promise.then(\n (response: any) => {\n if (isStream) {\n return wrapInvokeStream(response, span, vendor);\n }\n try {\n if (isEmbedding) {\n finalizeInvokeEmbedding(span, decodeBody(response?.body));\n } else {\n finalizeInvoke(span, vendor, decodeBody(response?.body));\n }\n } catch {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n }\n return response;\n },\n (err: any) => {\n recordError(span, err);\n throw err;\n },\n );\n}\n\nfunction finalizeInvokeEmbedding(span: Span, body: any): void {\n const emb = body?.embedding;\n const embs = body?.embeddings;\n if (Array.isArray(emb)) {\n span.setAttribute('neatlogs.embedding.count', 1);\n span.setAttribute('neatlogs.embedding.dimensions', emb.length);\n } else if (Array.isArray(embs) && embs.length) {\n span.setAttribute('neatlogs.embedding.count', embs.length);\n if (Array.isArray(embs[0])) span.setAttribute('neatlogs.embedding.dimensions', embs[0].length);\n }\n if (body?.inputTextTokenCount != null) {\n span.setAttribute('neatlogs.llm.token_count.prompt', body.inputTextTokenCount);\n span.setAttribute('neatlogs.embedding.token_count', body.inputTextTokenCount);\n }\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\nfunction decodeBody(body: any): any {\n try {\n if (body == null) return {};\n if (typeof body === 'string') return JSON.parse(body);\n // Uint8Array (AWS SDK v3 returns the response body as bytes)\n if (body instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(body));\n if (typeof body === 'object' && typeof body.byteLength === 'number') {\n return JSON.parse(new TextDecoder().decode(body));\n }\n if (typeof body === 'object') return body;\n } catch {\n /* fall through */\n }\n return {};\n}\n\nfunction setInvokeInput(span: Span, vendor: string, body: any): void {\n let idx = 0;\n if (body?.system) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'system');\n span.setAttribute(\n `neatlogs.llm.input_messages.${idx}.content`,\n typeof body.system === 'string' ? body.system : safeStringify(body.system),\n );\n idx++;\n }\n if (Array.isArray(body?.messages)) {\n for (const msg of body.messages) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, msg?.role ?? 'user');\n span.setAttribute(\n `neatlogs.llm.input_messages.${idx}.content`,\n typeof msg?.content === 'string' ? msg.content : safeStringify(msg?.content),\n );\n idx++;\n }\n } else if (body?.prompt) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'user');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, String(body.prompt));\n } else if (body?.inputText) {\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, 'user');\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, String(body.inputText));\n }\n\n if (body?.temperature != null) span.setAttribute('neatlogs.llm.temperature', body.temperature);\n if (body?.top_p != null) span.setAttribute('neatlogs.llm.top_p', body.top_p);\n const maxTokens = body?.max_tokens ?? body?.maxTokens ?? body?.max_tokens_to_sample;\n if (maxTokens != null) span.setAttribute('neatlogs.llm.max_tokens', maxTokens);\n const cfg = body?.textGenerationConfig;\n if (cfg) {\n if (cfg.temperature != null) span.setAttribute('neatlogs.llm.temperature', cfg.temperature);\n if (cfg.topP != null) span.setAttribute('neatlogs.llm.top_p', cfg.topP);\n if (cfg.maxTokenCount != null) span.setAttribute('neatlogs.llm.max_tokens', cfg.maxTokenCount);\n }\n}\n\nfunction finalizeInvoke(span: Span, vendor: string, body: any): void {\n let text: string | undefined;\n let promptTokens: number | undefined;\n let completionTokens: number | undefined;\n let finishReason: string | undefined;\n\n if (vendor === 'anthropic') {\n if (Array.isArray(body?.content)) {\n text = body.content.filter((b: any) => b?.type === 'text').map((b: any) => b.text).join('');\n let toolIdx = 0;\n for (const b of body.content) {\n if (b?.type === 'tool_use') {\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.id`, String(b.id ?? ''));\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.name`, String(b.name ?? ''));\n span.setAttribute(`neatlogs.llm.tool_calls.${toolIdx}.arguments`, safeStringify(b.input ?? {}));\n toolIdx++;\n }\n }\n } else if (body?.completion != null) {\n text = body.completion;\n }\n promptTokens = body?.usage?.input_tokens;\n completionTokens = body?.usage?.output_tokens;\n finishReason = body?.stop_reason;\n } else if (vendor === 'amazon') {\n const r = Array.isArray(body?.results) ? body.results[0] : undefined;\n if (r) {\n text = r.outputText;\n completionTokens = r.tokenCount;\n finishReason = r.completionReason;\n }\n promptTokens = body?.inputTextTokenCount;\n } else if (vendor === 'meta') {\n text = body?.generation;\n promptTokens = body?.prompt_token_count;\n completionTokens = body?.generation_token_count;\n finishReason = body?.stop_reason;\n } else if (vendor === 'cohere') {\n const g = Array.isArray(body?.generations) ? body.generations[0] : undefined;\n if (g) {\n text = g.text;\n finishReason = g.finish_reason;\n }\n } else {\n text = body?.generation ?? body?.completion ?? body?.outputText;\n }\n\n if (text) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', String(text));\n }\n if (promptTokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', promptTokens);\n if (completionTokens != null) span.setAttribute('neatlogs.llm.token_count.completion', completionTokens);\n if (promptTokens != null && completionTokens != null) {\n span.setAttribute('neatlogs.llm.token_count.total', promptTokens + completionTokens);\n }\n if (finishReason) span.setAttribute('neatlogs.llm.finish_reason', String(finishReason));\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\nfunction wrapInvokeStream(response: any, span: Span, vendor: string): any {\n const body = response?.body;\n if (!body || typeof body[Symbol.asyncIterator] !== 'function') {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return response;\n }\n\n const originalIterator = body[Symbol.asyncIterator].bind(body);\n const textParts: string[] = [];\n let promptTokens: number | undefined;\n let completionTokens: number | undefined;\n let finishReason: string | undefined;\n\n const finalize = () => {\n const full = textParts.join('');\n if (full) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', full);\n }\n if (promptTokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', promptTokens);\n if (completionTokens != null) span.setAttribute('neatlogs.llm.token_count.completion', completionTokens);\n if (finishReason) span.setAttribute('neatlogs.llm.finish_reason', String(finishReason));\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n };\n\n const wrappedBody = {\n [Symbol.asyncIterator]() {\n const iterator = originalIterator();\n return {\n async next(): Promise<IteratorResult<any>> {\n try {\n const result = await iterator.next();\n if (result.done) {\n finalize();\n return result;\n }\n const data = decodeBody(result.value?.chunk?.bytes);\n if (data?.type === 'content_block_delta') {\n textParts.push(data?.delta?.text ?? '');\n } else if (data?.type === 'message_delta') {\n if (data?.delta?.stop_reason) finishReason = data.delta.stop_reason;\n if (data?.usage?.output_tokens != null) completionTokens = data.usage.output_tokens;\n }\n if (data?.outputText) textParts.push(data.outputText);\n if (data?.generation) textParts.push(data.generation);\n const metrics = data?.['amazon-bedrock-invocationMetrics'];\n if (metrics) {\n promptTokens = metrics.inputTokenCount ?? promptTokens;\n completionTokens = metrics.outputTokenCount ?? completionTokens;\n }\n return result;\n } catch (err) {\n recordError(span, err);\n throw err;\n }\n },\n async return(value?: any): Promise<IteratorResult<any>> {\n finalize();\n return iterator.return?.(value) ?? { done: true, value: undefined };\n },\n };\n },\n };\n\n response.body = wrappedBody;\n return response;\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\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 context as otelContext,\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';\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 const existing = otelTrace.getSpan(baseCtx);\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 const ctx = context ?? otelContext.active();\n const parent = otelTrace.getSpan(ctx);\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, context);\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 return new AutoRootTracer(otelTrace.getTracer(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';\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 = otelTrace.getSpan(otelContext.active());\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 let ctx = otelContext.active();\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 = otelTrace.getTracer('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 if (shouldCreateRootTrace) {\n logger.debug(`[trace] Creating NEW root trace '${name}' (sessionId=${sessionId})`);\n return tracer.startActiveSpan(name, {}, ROOT_CONTEXT, spanCallback);\n } else {\n logger.debug(`[trace] Creating child span '${name}'`);\n return tracer.startActiveSpan(name, {}, ctx, spanCallback);\n }\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 * 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 { trace as otelTrace, context as otelContext, type Span } from '@opentelemetry/api';\nimport { getLogger } from './logger.js';\nimport { currentEndUserId, currentEndUserMetadata } from './identity.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 const current = otelTrace.getSpan(otelContext.active());\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 * 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 * 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 { trace, 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 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 tracer = trace.getTracer(TRACER_NAME);\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 // 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 return tracer.startActiveSpan(spanName, (span: OtelSpan) => {\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":["trace","otelContext","SpanStatusCode","otelTrace","otelContext","otelTrace","otelContext","SpanStatusCode","AsyncLocalStorage","logger","logger","logger","logger","logger","otelContext","otelTrace","otelContext","SpanStatusCode","trace"]}
package/dist/browser.cjs CHANGED
@@ -24,9 +24,10 @@ __export(browser_exports, {
24
24
  default: () => browser_default
25
25
  });
26
26
  module.exports = __toCommonJS(browser_exports);
27
- var DEFAULT_ENDPOINT = "https://staging-cloud.neatlogs.com";
27
+ var DEFAULT_ENDPOINT = "https://ingest.neatlogs.com";
28
28
  var END_USER_ID_KEY = "neatlogs.end_user.id";
29
29
  var END_USER_METADATA_KEY = "neatlogs.end_user.metadata";
30
+ var SESSION_ID_KEY = "neatlogs.session.id";
30
31
  var Neatlogs = class {
31
32
  apiKey;
32
33
  project;
@@ -35,6 +36,7 @@ var Neatlogs = class {
35
36
  onError;
36
37
  endUser;
37
38
  endUserMetadata;
39
+ session;
38
40
  constructor(opts) {
39
41
  if (!opts || !opts.apiKey) {
40
42
  throw new Error("Neatlogs: apiKey is required");
@@ -46,6 +48,7 @@ var Neatlogs = class {
46
48
  this.enabled = opts.enabled !== false;
47
49
  this.endUser = opts.endUser;
48
50
  this.endUserMetadata = opts.endUserMetadata;
51
+ this.session = opts.session;
49
52
  this.onError = opts.onError || ((err) => {
50
53
  if (typeof console !== "undefined") console.warn("[neatlogs] send failed:", err);
51
54
  });
@@ -81,16 +84,20 @@ var Neatlogs = class {
81
84
  };
82
85
  }
83
86
  /**
84
- * Fold end-user identity onto the trace ROOT as canonical attributes, and
85
- * strip the convenience `endUser`/`endUserMetadata` fields so they aren't sent
86
- * as raw root fields. Per-call values win over the client defaults; explicit
87
- * `attributes` win over both. One end-user per trace — only the root carries it.
87
+ * Fold identity (end-user + session) onto the trace ROOT as canonical
88
+ * attributes, and strip the convenience `endUser`/`endUserMetadata`/`session`
89
+ * fields so they aren't sent as raw root fields. Per-call values win over the
90
+ * client defaults; explicit `attributes` win over both. Only the root carries
91
+ * identity — one end-user per trace, one session per trace.
88
92
  */
89
- applyEndUser(body) {
90
- const { endUser, endUserMetadata, ...rest } = body;
93
+ applyIdentity(body) {
94
+ const { endUser, endUserMetadata, session, ...rest } = body;
91
95
  const id = endUser ?? this.endUser;
92
96
  const meta = endUserMetadata ?? this.endUserMetadata;
93
- if (id === void 0 && meta === void 0) return rest;
97
+ const sessionId = session ?? this.session;
98
+ if (id === void 0 && meta === void 0 && sessionId === void 0) {
99
+ return rest;
100
+ }
94
101
  const attributes = { ...rest.attributes ?? {} };
95
102
  if (id !== void 0 && attributes[END_USER_ID_KEY] === void 0) {
96
103
  attributes[END_USER_ID_KEY] = String(id);
@@ -98,12 +105,15 @@ var Neatlogs = class {
98
105
  if (meta !== void 0 && attributes[END_USER_METADATA_KEY] === void 0) {
99
106
  attributes[END_USER_METADATA_KEY] = typeof meta === "string" ? meta : JSON.stringify(meta);
100
107
  }
108
+ if (sessionId !== void 0 && attributes[SESSION_ID_KEY] === void 0) {
109
+ attributes[SESSION_ID_KEY] = String(sessionId);
110
+ }
101
111
  return { ...rest, attributes };
102
112
  }
103
113
  /** POST the trace JSON to the backend's /v1/trace endpoint. */
104
114
  async post(body) {
105
115
  if (!this.enabled) return { ok: true };
106
- body = this.applyEndUser(body);
116
+ body = this.applyIdentity(body);
107
117
  const payload = this.project && body.project === void 0 ? { ...body, project: this.project } : body;
108
118
  try {
109
119
  const res = await fetch(`${this.baseUrl}/v1/trace`, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/browser.ts"],"sourcesContent":["/**\n * Neatlogs Browser SDK — `neatlogs/browser`\n *\n * A minimal, browser-safe client for sending traces to Neatlogs from web apps.\n * It has ZERO dependencies (no OpenTelemetry, no Node APIs) — only `fetch` — so\n * it bundles cleanly into front-end apps. It POSTs plain JSON to the backend's\n * simple trace endpoint (`/v1/trace`); the backend generates trace/span ids,\n * builds the hierarchy from nesting, infers cost from model+tokens, and pushes\n * through the normal pipeline. Nothing here streams OTLP.\n *\n * Usage:\n * import { Neatlogs } from 'neatlogs/browser';\n * const nl = new Neatlogs({ apiKey: 'nl_...' });\n *\n * // one-shot AI interaction\n * await nl.trackAI({ name: 'chat', model: 'gpt-4o', input, output,\n * tokens: { prompt: 10, completion: 5 } });\n *\n * // a full nested trace (same shape the backend's POST /v1/trace accepts)\n * await nl.trace({ name: 'support-chat', children: [\n * { name: 'retrieve', query, documents },\n * { name: 'answer', model: 'gpt-4o', input, output },\n * ]});\n *\n * // streaming: open, accumulate, finish\n * const t = nl.startTrace({ name: 'chat', model: 'gpt-4o', input });\n * t.finish({ output: full, tokens: { prompt, completion } });\n */\n\nconst DEFAULT_ENDPOINT = \"https://staging-cloud.neatlogs.com\";\n\n// Canonical end-user attribute keys (inlined — this file stays dependency-free).\nconst END_USER_ID_KEY = \"neatlogs.end_user.id\";\nconst END_USER_METADATA_KEY = \"neatlogs.end_user.metadata\";\n\n// --- the simple trace shape the backend (/v1/trace) accepts --------------------\n// Mirrors the server-side SimpleSpan; kept local so this file has no imports.\n\nexport interface NeatlogsLog {\n level?: string;\n message: string;\n timestamp?: string;\n}\n\n/**\n * Span kinds the backend accepts (the canonical set). `kind` is optional — when\n * omitted the backend infers it from the fields present.\n */\nexport type NeatlogsKind =\n | \"WORKFLOW\" | \"AGENT\" | \"CHAIN\" | \"TOOL\" | \"RETRIEVER\" | \"RERANKER\"\n | \"EMBEDDING\" | \"LLM\" | \"GUARDRAIL\" | \"MCP_TOOL\" | \"TASK\"\n | \"VECTOR_STORE\" | \"EVALUATOR\" | \"HTTP\";\n\nexport interface NeatlogsSpan {\n name: string;\n /** Optional — the backend infers the kind from fields when omitted. */\n kind?: NeatlogsKind | string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n query?: unknown;\n documents?: unknown;\n tool_name?: string;\n passed?: boolean;\n score?: number;\n metadata?: Record<string, unknown>;\n status?: string;\n error?: string;\n start?: string;\n end?: string;\n /** Simplest way to record latency — the backend derives end from start + this. */\n duration_ms?: number;\n /**\n * Full canonical-attribute escape hatch. Send ANY neatlogs.* attribute the SDK\n * supports — e.g. { \"neatlogs.llm.temperature\": 0.7, \"neatlogs.agent.role\":\n * \"researcher\", \"neatlogs.tool.parameters\": {...} }. Non-canonical keys are\n * dropped server-side. The fields above (model/tokens/query/...) are shortcuts\n * for the common ones; explicit `attributes` win on conflict.\n */\n attributes?: Record<string, unknown>;\n children?: NeatlogsSpan[];\n logs?: NeatlogsLog[];\n /**\n * END-USER this trace belongs to — only meaningful on the ROOT of a trace\n * (overrides the client default). One end-user per trace; the backend rolls it\n * up to the trace and its session. Ignored on child spans.\n */\n endUser?: string;\n /** Arbitrary end-user fields for the trace root (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n}\n\n/** The root of a trace = a span node (its `name` becomes the workflow name). */\nexport type NeatlogsTrace = NeatlogsSpan;\n\nexport interface NeatlogsOptions {\n /**\n * Your Neatlogs WRITE key (`nlw_…`) — an ingest-only credential safe to embed in\n * browser code. (A full project key also works but should not be exposed client-side,\n * since it can read data.)\n */\n apiKey: string;\n /**\n * Project NAME to ingest into. REQUIRED when using a write key (the key identifies\n * you, not a project). Sent as the root `project` field on every trace. Ignored for\n * a full project key (already project-scoped).\n */\n project?: string;\n /** Backend base URL. Defaults to the same host the SDKs use. */\n endpoint?: string;\n /** Set false to validate calls without sending (default true). */\n enabled?: boolean;\n /** Called on transport errors instead of throwing (default: console.warn). */\n onError?: (err: unknown) => void;\n /**\n * Default END-USER identity for every trace this client sends — the user of\n * your app, not the operator. One end-user per trace; the backend rolls it up\n * to the trace and its session. A per-call `endUser` (on trace()/trackAI())\n * overrides this. Set it once after login, e.g. `new Neatlogs({ apiKey, endUser })`.\n */\n endUser?: string;\n /** Default arbitrary end-user fields stored as JSON (e.g. { plan: 'pro' }). */\n endUserMetadata?: Record<string, unknown>;\n}\n\nexport interface TrackResult {\n ok: boolean;\n trace_id?: string;\n spans?: number;\n error?: string;\n}\n\n/** Shorthand for a single AI interaction → a one-span trace. */\nexport interface TrackAIInput {\n name: string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n metadata?: Record<string, unknown>;\n duration_ms?: number;\n /** Any canonical neatlogs.* attributes (see NeatlogsSpan.attributes). */\n attributes?: Record<string, unknown>;\n /** Override the inferred kind (defaults to LLM for trackAI). */\n kind?: NeatlogsKind | string;\n /** END-USER for this trace (overrides the client default). One per trace. */\n endUser?: string;\n /** Arbitrary end-user fields for this trace (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n}\n\nexport class Neatlogs {\n private readonly apiKey: string;\n private readonly project?: string;\n private readonly baseUrl: string;\n private readonly enabled: boolean;\n private readonly onError: (err: unknown) => void;\n private readonly endUser?: string;\n private readonly endUserMetadata?: Record<string, unknown>;\n\n constructor(opts: NeatlogsOptions) {\n if (!opts || !opts.apiKey) {\n throw new Error(\"Neatlogs: apiKey is required\");\n }\n this.apiKey = opts.apiKey;\n this.project = opts.project;\n // Use the origin of the configured endpoint (same convention as the Node SDK),\n // so passing a full /v1/traces URL or a bare host both work.\n const endpoint = opts.endpoint || DEFAULT_ENDPOINT;\n this.baseUrl = safeOrigin(endpoint) || DEFAULT_ENDPOINT;\n this.enabled = opts.enabled !== false;\n this.endUser = opts.endUser;\n this.endUserMetadata = opts.endUserMetadata;\n this.onError =\n opts.onError ||\n ((err) => {\n // eslint-disable-next-line no-console\n if (typeof console !== \"undefined\") console.warn(\"[neatlogs] send failed:\", err);\n });\n }\n\n /** Send a full (optionally nested) trace. Returns the backend's result. */\n async trace(trace: NeatlogsTrace): Promise<TrackResult> {\n return this.post(trace);\n }\n\n /** Send a single AI interaction as a one-span trace (kind defaults to LLM). */\n async trackAI(ai: TrackAIInput): Promise<TrackResult> {\n const { name, kind, ...rest } = ai;\n return this.post({ name, kind: kind ?? \"LLM\", ...rest });\n }\n\n /**\n * Begin a trace you'll complete later (e.g. streaming). Buffers the partial\n * input; call `.finish()` with the final output/tokens to send it. Nothing is\n * sent until `finish()`.\n */\n startTrace(initial: TrackAIInput): {\n finish: (final?: Partial<TrackAIInput>) => Promise<TrackResult>;\n } {\n const startedAt = nowIso();\n return {\n finish: (final?: Partial<TrackAIInput>) => {\n const merged: TrackAIInput = { ...initial, ...(final ?? {}) };\n const { name, kind, ...rest } = merged;\n return this.post({\n name,\n kind: kind ?? \"LLM\",\n start: startedAt,\n end: nowIso(),\n ...rest,\n });\n },\n };\n }\n\n /**\n * Fold end-user identity onto the trace ROOT as canonical attributes, and\n * strip the convenience `endUser`/`endUserMetadata` fields so they aren't sent\n * as raw root fields. Per-call values win over the client defaults; explicit\n * `attributes` win over both. One end-user per trace — only the root carries it.\n */\n private applyEndUser(body: NeatlogsTrace): NeatlogsTrace {\n const { endUser, endUserMetadata, ...rest } = body as NeatlogsTrace & {\n endUser?: string;\n endUserMetadata?: Record<string, unknown>;\n };\n const id = endUser ?? this.endUser;\n const meta = endUserMetadata ?? this.endUserMetadata;\n if (id === undefined && meta === undefined) return rest;\n\n const attributes: Record<string, unknown> = { ...(rest.attributes ?? {}) };\n if (id !== undefined && attributes[END_USER_ID_KEY] === undefined) {\n attributes[END_USER_ID_KEY] = String(id);\n }\n if (meta !== undefined && attributes[END_USER_METADATA_KEY] === undefined) {\n attributes[END_USER_METADATA_KEY] =\n typeof meta === \"string\" ? meta : JSON.stringify(meta);\n }\n return { ...rest, attributes };\n }\n\n /** POST the trace JSON to the backend's /v1/trace endpoint. */\n private async post(body: NeatlogsTrace): Promise<TrackResult> {\n if (!this.enabled) return { ok: true };\n // Fold end-user identity onto the root before anything else.\n body = this.applyEndUser(body);\n // Inject the configured project name into the root (required for write keys;\n // ignored server-side for full project keys). A `project` already on the body wins.\n const payload =\n this.project && (body as any).project === undefined\n ? { ...body, project: this.project }\n : body;\n try {\n const res = await fetch(`${this.baseUrl}/v1/trace`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n // keepalive lets the request survive a page unload (e.g. on navigation).\n keepalive: true,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n const result = { ok: false, error: `HTTP ${res.status}${text ? `: ${text}` : \"\"}` };\n this.onError(new Error(result.error));\n return result;\n }\n const data = (await res.json().catch(() => ({}))) as Partial<TrackResult>;\n return { ok: true, trace_id: data.trace_id, spans: data.spans };\n } catch (err) {\n // Never throw into the host app over telemetry.\n this.onError(err);\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\n// --- helpers (kept local, no imports) -----------------------------------------\n\nfunction safeOrigin(endpoint: string): string | null {\n try {\n return new URL(endpoint).origin;\n } catch {\n return null;\n }\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nexport default Neatlogs;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/browser.ts"],"sourcesContent":["/**\n * Neatlogs Browser SDK — `neatlogs/browser`\n *\n * A minimal, browser-safe client for sending traces to Neatlogs from web apps.\n * It has ZERO dependencies (no OpenTelemetry, no Node APIs) — only `fetch` — so\n * it bundles cleanly into front-end apps. It POSTs plain JSON to the backend's\n * simple trace endpoint (`/v1/trace`); the backend generates trace/span ids,\n * builds the hierarchy from nesting, infers cost from model+tokens, and pushes\n * through the normal pipeline. Nothing here streams OTLP.\n *\n * Usage:\n * import { Neatlogs } from 'neatlogs/browser';\n * const nl = new Neatlogs({ apiKey: 'nl_...' });\n *\n * // one-shot AI interaction\n * await nl.trackAI({ name: 'chat', model: 'gpt-4o', input, output,\n * tokens: { prompt: 10, completion: 5 } });\n *\n * // a full nested trace (same shape the backend's POST /v1/trace accepts)\n * await nl.trace({ name: 'support-chat', children: [\n * { name: 'retrieve', query, documents },\n * { name: 'answer', model: 'gpt-4o', input, output },\n * ]});\n *\n * // streaming: open, accumulate, finish\n * const t = nl.startTrace({ name: 'chat', model: 'gpt-4o', input });\n * t.finish({ output: full, tokens: { prompt, completion } });\n */\n\nconst DEFAULT_ENDPOINT = \"https://ingest.neatlogs.com\";\n\n// Canonical identity attribute keys (inlined — this file stays dependency-free).\nconst END_USER_ID_KEY = \"neatlogs.end_user.id\";\nconst END_USER_METADATA_KEY = \"neatlogs.end_user.metadata\";\nconst SESSION_ID_KEY = \"neatlogs.session.id\";\n\n// --- the simple trace shape the backend (/v1/trace) accepts --------------------\n// Mirrors the server-side SimpleSpan; kept local so this file has no imports.\n\nexport interface NeatlogsLog {\n level?: string;\n message: string;\n timestamp?: string;\n}\n\n/**\n * Span kinds the backend accepts (the canonical set). `kind` is optional — when\n * omitted the backend infers it from the fields present.\n */\nexport type NeatlogsKind =\n | \"WORKFLOW\" | \"AGENT\" | \"CHAIN\" | \"TOOL\" | \"RETRIEVER\" | \"RERANKER\"\n | \"EMBEDDING\" | \"LLM\" | \"GUARDRAIL\" | \"MCP_TOOL\" | \"TASK\"\n | \"VECTOR_STORE\" | \"EVALUATOR\" | \"HTTP\";\n\nexport interface NeatlogsSpan {\n name: string;\n /** Optional — the backend infers the kind from fields when omitted. */\n kind?: NeatlogsKind | string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n query?: unknown;\n documents?: unknown;\n tool_name?: string;\n passed?: boolean;\n score?: number;\n metadata?: Record<string, unknown>;\n status?: string;\n error?: string;\n start?: string;\n end?: string;\n /** Simplest way to record latency — the backend derives end from start + this. */\n duration_ms?: number;\n /**\n * Full canonical-attribute escape hatch. Send ANY neatlogs.* attribute the SDK\n * supports — e.g. { \"neatlogs.llm.temperature\": 0.7, \"neatlogs.agent.role\":\n * \"researcher\", \"neatlogs.tool.parameters\": {...} }. Non-canonical keys are\n * dropped server-side. The fields above (model/tokens/query/...) are shortcuts\n * for the common ones; explicit `attributes` win on conflict.\n */\n attributes?: Record<string, unknown>;\n children?: NeatlogsSpan[];\n logs?: NeatlogsLog[];\n /**\n * END-USER this trace belongs to — only meaningful on the ROOT of a trace\n * (overrides the client default). One end-user per trace; the backend rolls it\n * up to the trace and its session. Ignored on child spans.\n */\n endUser?: string;\n /** Arbitrary end-user fields for the trace root (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * SESSION this trace belongs to — the conversation/thread grouping many turns.\n * Only meaningful on the ROOT of a trace (overrides the client default). Every\n * trace sharing a `session` is grouped into one session in the dashboard; a new\n * trace per turn, all with the same `session`, forms a multi-turn conversation.\n * Ignored on child spans.\n */\n session?: string;\n}\n\n/** The root of a trace = a span node (its `name` becomes the workflow name). */\nexport type NeatlogsTrace = NeatlogsSpan;\n\nexport interface NeatlogsOptions {\n /**\n * Your Neatlogs WRITE key (`nlw_…`) — an ingest-only credential safe to embed in\n * browser code. (A full project key also works but should not be exposed client-side,\n * since it can read data.)\n */\n apiKey: string;\n /**\n * Project NAME to ingest into. REQUIRED when using a write key (the key identifies\n * you, not a project). Sent as the root `project` field on every trace. Ignored for\n * a full project key (already project-scoped).\n */\n project?: string;\n /** Backend base URL. Defaults to the same host the SDKs use. */\n endpoint?: string;\n /** Set false to validate calls without sending (default true). */\n enabled?: boolean;\n /** Called on transport errors instead of throwing (default: console.warn). */\n onError?: (err: unknown) => void;\n /**\n * Default END-USER identity for every trace this client sends — the user of\n * your app, not the operator. One end-user per trace; the backend rolls it up\n * to the trace and its session. A per-call `endUser` (on trace()/trackAI())\n * overrides this. Set it once after login, e.g. `new Neatlogs({ apiKey, endUser })`.\n */\n endUser?: string;\n /** Default arbitrary end-user fields stored as JSON (e.g. { plan: 'pro' }). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * Default SESSION for every trace this client sends — the conversation/thread\n * these traces belong to. A per-call `session` (on trace()/trackAI()) overrides\n * this. Set it once per conversation, e.g. `new Neatlogs({ apiKey, session: convId })`.\n */\n session?: string;\n}\n\nexport interface TrackResult {\n ok: boolean;\n trace_id?: string;\n spans?: number;\n error?: string;\n}\n\n/** Shorthand for a single AI interaction → a one-span trace. */\nexport interface TrackAIInput {\n name: string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n metadata?: Record<string, unknown>;\n duration_ms?: number;\n /** Any canonical neatlogs.* attributes (see NeatlogsSpan.attributes). */\n attributes?: Record<string, unknown>;\n /** Override the inferred kind (defaults to LLM for trackAI). */\n kind?: NeatlogsKind | string;\n /** END-USER for this trace (overrides the client default). One per trace. */\n endUser?: string;\n /** Arbitrary end-user fields for this trace (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /** SESSION this trace belongs to (overrides the client default). */\n session?: string;\n}\n\nexport class Neatlogs {\n private readonly apiKey: string;\n private readonly project?: string;\n private readonly baseUrl: string;\n private readonly enabled: boolean;\n private readonly onError: (err: unknown) => void;\n private readonly endUser?: string;\n private readonly endUserMetadata?: Record<string, unknown>;\n private readonly session?: string;\n\n constructor(opts: NeatlogsOptions) {\n if (!opts || !opts.apiKey) {\n throw new Error(\"Neatlogs: apiKey is required\");\n }\n this.apiKey = opts.apiKey;\n this.project = opts.project;\n // Use the origin of the configured endpoint (same convention as the Node SDK),\n // so passing a full /v1/traces URL or a bare host both work.\n const endpoint = opts.endpoint || DEFAULT_ENDPOINT;\n this.baseUrl = safeOrigin(endpoint) || DEFAULT_ENDPOINT;\n this.enabled = opts.enabled !== false;\n this.endUser = opts.endUser;\n this.endUserMetadata = opts.endUserMetadata;\n this.session = opts.session;\n this.onError =\n opts.onError ||\n ((err) => {\n // eslint-disable-next-line no-console\n if (typeof console !== \"undefined\") console.warn(\"[neatlogs] send failed:\", err);\n });\n }\n\n /** Send a full (optionally nested) trace. Returns the backend's result. */\n async trace(trace: NeatlogsTrace): Promise<TrackResult> {\n return this.post(trace);\n }\n\n /** Send a single AI interaction as a one-span trace (kind defaults to LLM). */\n async trackAI(ai: TrackAIInput): Promise<TrackResult> {\n const { name, kind, ...rest } = ai;\n return this.post({ name, kind: kind ?? \"LLM\", ...rest });\n }\n\n /**\n * Begin a trace you'll complete later (e.g. streaming). Buffers the partial\n * input; call `.finish()` with the final output/tokens to send it. Nothing is\n * sent until `finish()`.\n */\n startTrace(initial: TrackAIInput): {\n finish: (final?: Partial<TrackAIInput>) => Promise<TrackResult>;\n } {\n const startedAt = nowIso();\n return {\n finish: (final?: Partial<TrackAIInput>) => {\n const merged: TrackAIInput = { ...initial, ...(final ?? {}) };\n const { name, kind, ...rest } = merged;\n return this.post({\n name,\n kind: kind ?? \"LLM\",\n start: startedAt,\n end: nowIso(),\n ...rest,\n });\n },\n };\n }\n\n /**\n * Fold identity (end-user + session) onto the trace ROOT as canonical\n * attributes, and strip the convenience `endUser`/`endUserMetadata`/`session`\n * fields so they aren't sent as raw root fields. Per-call values win over the\n * client defaults; explicit `attributes` win over both. Only the root carries\n * identity — one end-user per trace, one session per trace.\n */\n private applyIdentity(body: NeatlogsTrace): NeatlogsTrace {\n const { endUser, endUserMetadata, session, ...rest } = body as NeatlogsTrace & {\n endUser?: string;\n endUserMetadata?: Record<string, unknown>;\n session?: string;\n };\n const id = endUser ?? this.endUser;\n const meta = endUserMetadata ?? this.endUserMetadata;\n const sessionId = session ?? this.session;\n if (id === undefined && meta === undefined && sessionId === undefined) {\n return rest;\n }\n\n const attributes: Record<string, unknown> = { ...(rest.attributes ?? {}) };\n if (id !== undefined && attributes[END_USER_ID_KEY] === undefined) {\n attributes[END_USER_ID_KEY] = String(id);\n }\n if (meta !== undefined && attributes[END_USER_METADATA_KEY] === undefined) {\n attributes[END_USER_METADATA_KEY] =\n typeof meta === \"string\" ? meta : JSON.stringify(meta);\n }\n if (sessionId !== undefined && attributes[SESSION_ID_KEY] === undefined) {\n attributes[SESSION_ID_KEY] = String(sessionId);\n }\n return { ...rest, attributes };\n }\n\n /** POST the trace JSON to the backend's /v1/trace endpoint. */\n private async post(body: NeatlogsTrace): Promise<TrackResult> {\n if (!this.enabled) return { ok: true };\n // Fold identity (end-user + session) onto the root before anything else.\n body = this.applyIdentity(body);\n // Inject the configured project name into the root (required for write keys;\n // ignored server-side for full project keys). A `project` already on the body wins.\n const payload =\n this.project && (body as any).project === undefined\n ? { ...body, project: this.project }\n : body;\n try {\n const res = await fetch(`${this.baseUrl}/v1/trace`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n // keepalive lets the request survive a page unload (e.g. on navigation).\n keepalive: true,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n const result = { ok: false, error: `HTTP ${res.status}${text ? `: ${text}` : \"\"}` };\n this.onError(new Error(result.error));\n return result;\n }\n const data = (await res.json().catch(() => ({}))) as Partial<TrackResult>;\n return { ok: true, trace_id: data.trace_id, spans: data.spans };\n } catch (err) {\n // Never throw into the host app over telemetry.\n this.onError(err);\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\n// --- helpers (kept local, no imports) -----------------------------------------\n\nfunction safeOrigin(endpoint: string): string | null {\n try {\n return new URL(endpoint).origin;\n } catch {\n return null;\n }\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nexport default Neatlogs;\n"],"mappings":"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","names":[]}