neatlogs 1.1.19 → 1.1.21

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 (69) hide show
  1. package/README.md +34 -2
  2. package/dist/ai-sdk.cjs +186 -11
  3. package/dist/ai-sdk.cjs.map +1 -1
  4. package/dist/ai-sdk.d.ts +17 -7
  5. package/dist/ai-sdk.mjs +189 -12
  6. package/dist/ai-sdk.mjs.map +1 -1
  7. package/dist/anthropic.cjs +31 -14
  8. package/dist/anthropic.cjs.map +1 -1
  9. package/dist/anthropic.mjs +31 -14
  10. package/dist/anthropic.mjs.map +1 -1
  11. package/dist/azure-openai.cjs +31 -14
  12. package/dist/azure-openai.cjs.map +1 -1
  13. package/dist/azure-openai.mjs +31 -14
  14. package/dist/azure-openai.mjs.map +1 -1
  15. package/dist/bedrock.cjs +31 -14
  16. package/dist/bedrock.cjs.map +1 -1
  17. package/dist/bedrock.mjs +31 -14
  18. package/dist/bedrock.mjs.map +1 -1
  19. package/dist/browser.cjs +26 -1
  20. package/dist/browser.cjs.map +1 -1
  21. package/dist/browser.d.ts +1 -1
  22. package/dist/browser.mjs +26 -1
  23. package/dist/browser.mjs.map +1 -1
  24. package/dist/claude-agent-sdk.cjs +2 -2
  25. package/dist/claude-agent-sdk.cjs.map +1 -1
  26. package/dist/claude-agent-sdk.mjs +2 -2
  27. package/dist/claude-agent-sdk.mjs.map +1 -1
  28. package/dist/cli.cjs +452 -200
  29. package/dist/cli.cjs.map +1 -1
  30. package/dist/cli.mjs +427 -173
  31. package/dist/cli.mjs.map +1 -1
  32. package/dist/google-genai.cjs +31 -14
  33. package/dist/google-genai.cjs.map +1 -1
  34. package/dist/google-genai.mjs +31 -14
  35. package/dist/google-genai.mjs.map +1 -1
  36. package/dist/index.cjs +652 -333
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.ts +15 -6
  39. package/dist/index.mjs +505 -184
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/langchain.cjs +3 -3
  42. package/dist/langchain.cjs.map +1 -1
  43. package/dist/langchain.mjs +3 -3
  44. package/dist/langchain.mjs.map +1 -1
  45. package/dist/mastra-wrap.cjs +2 -2
  46. package/dist/mastra-wrap.cjs.map +1 -1
  47. package/dist/mastra-wrap.mjs +2 -2
  48. package/dist/mastra-wrap.mjs.map +1 -1
  49. package/dist/openai-agents.cjs.map +1 -1
  50. package/dist/openai-agents.mjs.map +1 -1
  51. package/dist/openai.cjs +31 -14
  52. package/dist/openai.cjs.map +1 -1
  53. package/dist/openai.mjs +31 -14
  54. package/dist/openai.mjs.map +1 -1
  55. package/dist/opencode-plugin.cjs +1 -1
  56. package/dist/opencode-plugin.cjs.map +1 -1
  57. package/dist/opencode-plugin.mjs +1 -1
  58. package/dist/opencode-plugin.mjs.map +1 -1
  59. package/dist/openrouter-agent.cjs +31 -14
  60. package/dist/openrouter-agent.cjs.map +1 -1
  61. package/dist/openrouter-agent.mjs +31 -14
  62. package/dist/openrouter-agent.mjs.map +1 -1
  63. package/dist/pi-agent.cjs.map +1 -1
  64. package/dist/pi-agent.mjs.map +1 -1
  65. package/dist/vertex-ai.cjs +31 -14
  66. package/dist/vertex-ai.cjs.map +1 -1
  67. package/dist/vertex-ai.mjs +31 -14
  68. package/dist/vertex-ai.mjs.map +1 -1
  69. package/package.json +1 -1
package/dist/ai-sdk.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  // src/ai-sdk.ts
2
- import { SpanStatusCode } from "@opentelemetry/api";
2
+ import {
3
+ SpanStatusCode
4
+ } from "@opentelemetry/api";
3
5
 
4
6
  // src/core/provider.ts
5
7
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
@@ -95,11 +97,11 @@ function getNeatlogsActiveContext() {
95
97
  function getNeatlogsParentContext(baseContext) {
96
98
  return baseContext ?? getNeatlogsActiveContext();
97
99
  }
98
- function withNeatlogsSpan(span, fn, baseContext) {
100
+ function withNeatlogsSpan(span, fn, baseContext, rootSpan) {
99
101
  const base = baseContext ?? getNeatlogsActiveContext();
100
102
  let ctx = otelTrace.setSpan(base, span);
101
103
  if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === void 0) {
102
- ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span);
104
+ ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, rootSpan ?? span);
103
105
  }
104
106
  return privateContextStorage.run(ctx, fn);
105
107
  }
@@ -108,6 +110,8 @@ function withNeatlogsSpan(span, fn, baseContext) {
108
110
  var TRACER_NAME = "neatlogs.ai-sdk";
109
111
  function createAITelemetry(opts = {}) {
110
112
  const userMeta = opts.metadata ?? {};
113
+ const neatlogsTracer = getRoutingNeatlogsTracer(TRACER_NAME);
114
+ const callerTracer = opts.tracer;
111
115
  return {
112
116
  isEnabled: true,
113
117
  recordInputs: true,
@@ -116,8 +120,11 @@ function createAITelemetry(opts = {}) {
116
120
  // internally, which would otherwise parent its native spans from the foreign
117
121
  // global context AND push them onto it (so a co-tenant's next span inherits
118
122
  // ours). The facade routes both through the private Neatlogs context.
119
- tracer: getRoutingNeatlogsTracer(TRACER_NAME),
120
- metadata: { ...userMeta, neatlogsWrapped: true }
123
+ tracer: callerTracer ? createMirroredTracer(callerTracer, neatlogsTracer) : neatlogsTracer,
124
+ ...opts.functionId !== void 0 ? { functionId: opts.functionId } : {},
125
+ // The marker is an implementation detail used only when Neatlogs owns the
126
+ // telemetry stream. Do not leak it into caller-owned providers.
127
+ metadata: callerTracer ? { ...userMeta } : { ...userMeta, neatlogsWrapped: true }
121
128
  };
122
129
  }
123
130
  function safeStringify(value) {
@@ -191,21 +198,80 @@ var WRAPPED_FUNCTIONS = [
191
198
  "embedMany",
192
199
  "rerank"
193
200
  ];
201
+ var WRAPPED_AGENT_CONSTRUCTORS = [
202
+ "ToolLoopAgent",
203
+ "Experimental_Agent"
204
+ ];
205
+ var wrappedExports = /* @__PURE__ */ new WeakSet();
206
+ var wrapperByOriginal = /* @__PURE__ */ new WeakMap();
194
207
  function wrapAISDK(aiModule) {
195
208
  const wrapped = { ...aiModule };
196
209
  for (const name of WRAPPED_FUNCTIONS) {
197
210
  const original = aiModule[name];
198
211
  if (typeof original !== "function") continue;
212
+ const existing = getExistingWrapper(original);
213
+ if (existing) {
214
+ wrapped[name] = existing;
215
+ continue;
216
+ }
199
217
  if (name === "streamText" || name === "streamObject") {
200
- wrapped[name] = createStreamWrapper(name, original);
218
+ wrapped[name] = cacheWrapper(
219
+ original,
220
+ createStreamWrapper(name, original)
221
+ );
201
222
  } else {
202
- wrapped[name] = createAsyncWrapper(name, original);
223
+ wrapped[name] = cacheWrapper(
224
+ original,
225
+ createAsyncWrapper(name, original)
226
+ );
203
227
  }
204
228
  }
229
+ for (const name of WRAPPED_AGENT_CONSTRUCTORS) {
230
+ const original = aiModule[name];
231
+ if (typeof original !== "function") continue;
232
+ const existing = getExistingWrapper(original);
233
+ wrapped[name] = existing ?? cacheWrapper(
234
+ original,
235
+ createAgentConstructorWrapper(original)
236
+ );
237
+ }
205
238
  return wrapped;
206
239
  }
240
+ function getExistingWrapper(original) {
241
+ if (wrappedExports.has(original)) return original;
242
+ return wrapperByOriginal.get(original);
243
+ }
244
+ function cacheWrapper(original, wrapped) {
245
+ wrapperByOriginal.set(original, wrapped);
246
+ wrappedExports.add(wrapped);
247
+ return wrapped;
248
+ }
249
+ function createAgentConstructorWrapper(original) {
250
+ return new Proxy(original, {
251
+ construct(target, args, newTarget) {
252
+ if (args.length === 0 || typeof args[0] !== "object" || args[0] === null) {
253
+ return Reflect.construct(target, args, newTarget);
254
+ }
255
+ const settings = mergeAgentSettings(args[0]);
256
+ return Reflect.construct(target, [settings, ...args.slice(1)], newTarget);
257
+ }
258
+ });
259
+ }
260
+ function mergeAgentSettings(settings) {
261
+ const merged = mergeTelemetry(settings);
262
+ const userPrepareCall = settings.prepareCall;
263
+ if (typeof userPrepareCall !== "function") return merged;
264
+ return {
265
+ ...merged,
266
+ prepareCall: async function wrappedPrepareCall(...args) {
267
+ const prepared = await Reflect.apply(userPrepareCall, this, args);
268
+ return prepared == null ? prepared : mergeTelemetry(prepared, settings.experimental_telemetry);
269
+ }
270
+ };
271
+ }
207
272
  function rootSpanKind(name) {
208
- if (name === "embed" || name === "embedMany" || name === "rerank") return "CHAIN";
273
+ if (name === "embed" || name === "embedMany" || name === "rerank")
274
+ return "CHAIN";
209
275
  return "WORKFLOW";
210
276
  }
211
277
  function getParentContext() {
@@ -302,15 +368,126 @@ function createStreamWrapper(name, original) {
302
368
  );
303
369
  };
304
370
  }
305
- function mergeTelemetry(opts) {
371
+ function createMirroredSpan(primary, secondary) {
372
+ const mirrored = {
373
+ spanContext() {
374
+ return primary.spanContext();
375
+ },
376
+ setAttribute(key, value) {
377
+ primary.setAttribute(key, value);
378
+ secondary.setAttribute(key, value);
379
+ return mirrored;
380
+ },
381
+ setAttributes(attributes) {
382
+ primary.setAttributes(attributes);
383
+ secondary.setAttributes(attributes);
384
+ return mirrored;
385
+ },
386
+ addEvent(name, attributesOrStartTime, startTime) {
387
+ primary.addEvent(name, attributesOrStartTime, startTime);
388
+ secondary.addEvent(name, attributesOrStartTime, startTime);
389
+ return mirrored;
390
+ },
391
+ addLink(link) {
392
+ primary.addLink(link);
393
+ secondary.addLink(link);
394
+ return mirrored;
395
+ },
396
+ addLinks(links) {
397
+ primary.addLinks(links);
398
+ secondary.addLinks(links);
399
+ return mirrored;
400
+ },
401
+ setStatus(status) {
402
+ primary.setStatus(status);
403
+ secondary.setStatus(status);
404
+ return mirrored;
405
+ },
406
+ updateName(name) {
407
+ primary.updateName(name);
408
+ secondary.updateName(name);
409
+ return mirrored;
410
+ },
411
+ end(endTime) {
412
+ primary.end(endTime);
413
+ secondary.end(endTime);
414
+ },
415
+ isRecording() {
416
+ return primary.isRecording() || secondary.isRecording();
417
+ },
418
+ recordException(exception, time) {
419
+ primary.recordException(exception, time);
420
+ secondary.recordException(exception, time);
421
+ }
422
+ };
423
+ return mirrored;
424
+ }
425
+ function createMirroredTracer(primary, secondary) {
426
+ return {
427
+ startSpan(name, options, context) {
428
+ const primarySpan = primary.startSpan(name, options, context);
429
+ const secondarySpan = secondary.startSpan(name, options);
430
+ return createMirroredSpan(primarySpan, secondarySpan);
431
+ },
432
+ startActiveSpan: ((name, arg2, arg3, arg4) => {
433
+ let options;
434
+ let context;
435
+ let fn;
436
+ if (typeof arg2 === "function") {
437
+ fn = arg2;
438
+ } else if (typeof arg3 === "function") {
439
+ options = arg2;
440
+ fn = arg3;
441
+ } else {
442
+ options = arg2;
443
+ context = arg3;
444
+ fn = arg4;
445
+ }
446
+ const runSecondary = (primarySpan) => {
447
+ const onSecondarySpan = (secondarySpan) => fn(createMirroredSpan(primarySpan, secondarySpan));
448
+ return options === void 0 ? secondary.startActiveSpan(name, onSecondarySpan) : secondary.startActiveSpan(name, options, onSecondarySpan);
449
+ };
450
+ if (context !== void 0) {
451
+ return primary.startActiveSpan(
452
+ name,
453
+ options ?? {},
454
+ context,
455
+ runSecondary
456
+ );
457
+ }
458
+ return options === void 0 ? primary.startActiveSpan(name, runSecondary) : primary.startActiveSpan(name, options, runSecondary);
459
+ })
460
+ };
461
+ }
462
+ function mergeTelemetry(opts, fallbackTelemetry) {
463
+ const requestedTelemetry = {
464
+ ...fallbackTelemetry,
465
+ ...opts?.experimental_telemetry,
466
+ metadata: {
467
+ ...fallbackTelemetry?.metadata,
468
+ ...opts?.experimental_telemetry?.metadata
469
+ }
470
+ };
306
471
  const baseTelemetry = createAITelemetry({
307
- metadata: opts?.experimental_telemetry?.metadata
472
+ functionId: requestedTelemetry.functionId,
473
+ metadata: requestedTelemetry.metadata,
474
+ tracer: requestedTelemetry.tracer
308
475
  });
476
+ const callerTracer = requestedTelemetry.tracer;
477
+ const hasCallerTracer = callerTracer !== void 0;
309
478
  return {
310
479
  ...opts,
311
480
  experimental_telemetry: {
312
- ...opts?.experimental_telemetry,
313
- ...baseTelemetry
481
+ ...baseTelemetry,
482
+ ...requestedTelemetry,
483
+ isEnabled: true,
484
+ recordInputs: requestedTelemetry.recordInputs ?? true,
485
+ recordOutputs: requestedTelemetry.recordOutputs ?? true,
486
+ tracer: baseTelemetry.tracer,
487
+ // Do not add Neatlogs-only marker metadata to a caller-owned telemetry
488
+ // pipeline such as Laminar. Both providers receive the same AI SDK span
489
+ // data, while their providers, parent contexts, and exporters stay separate.
490
+ metadata: hasCallerTracer ? requestedTelemetry.metadata : baseTelemetry.metadata
314
491
  }
315
492
  };
316
493
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ai-sdk.ts","../src/core/provider.ts","../src/core/active-client.ts"],"sourcesContent":["/**\n * Vercel AI SDK wrapper — inline implementation.\n *\n * Wraps `generateText`, `streamText`, `generateObject`, `streamObject` from\n * the `ai` package with OTel parent spans + forced telemetry. Static export,\n * no dynamic imports — bundler-friendly (works with Turbopack, webpack, esbuild).\n *\n * Usage:\n * import { wrapAISDK } from 'neatlogs';\n * import * as ai from 'ai';\n * const { streamText, generateText } = wrapAISDK(ai);\n */\n\nimport { SpanStatusCode, type Span, type Tracer } from '@opentelemetry/api';\nimport { getNeatlogsTracer, getNeatlogsParentContext, getRoutingNeatlogsTracer, withNeatlogsSpan } from './core/provider.js';\n\nconst TRACER_NAME = 'neatlogs.ai-sdk';\n\n// -- Telemetry config --------------------------------------------------------\n\nexport interface CreateAITelemetryOptions {\n metadata?: Record<string, unknown>;\n}\n\nexport interface AITelemetryConfig {\n isEnabled: true;\n recordInputs: true;\n recordOutputs: true;\n tracer: Tracer;\n metadata: Record<string, unknown>;\n}\n\nexport function createAITelemetry(\n opts: CreateAITelemetryOptions = {},\n): AITelemetryConfig {\n const userMeta = opts.metadata ?? {};\n return {\n isEnabled: true,\n recordInputs: true,\n recordOutputs: true,\n // Hand the AI SDK an isolation-aware tracer: it calls startActiveSpan()\n // internally, which would otherwise parent its native spans from the foreign\n // global context AND push them onto it (so a co-tenant's next span inherits\n // ours). The facade routes both through the private Neatlogs context.\n tracer: getRoutingNeatlogsTracer(TRACER_NAME),\n metadata: { ...userMeta, neatlogsWrapped: true },\n };\n}\n\n// -- Span attributes ---------------------------------------------------------\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch {\n return '';\n }\n}\n\nfunction setInputValue(span: Span, opts: Record<string, unknown>): void {\n // For generateText/streamText — only capture prompt or messages, not full model config\n if (opts && ('prompt' in opts || 'messages' in opts)) {\n const input = opts.messages ?? opts.prompt;\n const stringified = safeStringify(input);\n if (stringified) {\n span.setAttribute('input.value', stringified);\n }\n return;\n }\n const stringified = safeStringify(opts);\n if (stringified) {\n span.setAttribute('input.value', stringified);\n }\n}\n\nfunction setOutputValue(span: Span, result: unknown): void {\n if (result && typeof result === 'object') {\n const r = result as Record<string, unknown>;\n // GenerateTextResult / StreamTextResult — extract meaningful fields\n if ('text' in r && 'finishReason' in r) {\n const text = String(r.text ?? '');\n if (text) {\n span.setAttribute('output.value', text);\n }\n if (r.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(r.finishReason));\n }\n return;\n }\n // GenerateObjectResult — the structured object is the output, not `text`.\n // Without this the envelope (object+usage+response+…) gets stringified whole.\n if ('object' in r && 'finishReason' in r) {\n if (r.object !== undefined) {\n span.setAttribute('output.value', safeStringify(r.object));\n }\n if (r.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(r.finishReason));\n }\n return;\n }\n }\n const stringified = safeStringify(result);\n if (stringified) {\n span.setAttribute('output.value', stringified);\n }\n}\n\n// Extract output from a streamText/streamObject `onFinish` event. The event\n// extends StepResult, so `text` (streamText) / `object` (streamObject) sit at\n// the top level alongside `finishReason` — but the event also carries `steps`,\n// `usage`, etc., so we pull only the meaningful fields instead of stringifying\n// the whole envelope.\nfunction setStreamOutputValue(span: Span, event: unknown): void {\n if (!event || typeof event !== 'object') return;\n const e = event as Record<string, unknown>;\n if (typeof e.text === 'string' && e.text) {\n span.setAttribute('output.value', e.text);\n } else if ('object' in e && e.object !== undefined) {\n const stringified = safeStringify(e.object);\n if (stringified) span.setAttribute('output.value', stringified);\n }\n if (e.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(e.finishReason));\n }\n}\n\n// -- Wrapping ----------------------------------------------------------------\n\ntype WrappedFunctionName = 'generateText' | 'streamText' | 'generateObject' | 'streamObject' | 'embed' | 'embedMany' | 'rerank';\n\nconst WRAPPED_FUNCTIONS: readonly WrappedFunctionName[] = [\n 'generateText',\n 'streamText',\n 'generateObject',\n 'streamObject',\n 'embed',\n 'embedMany',\n 'rerank',\n] as const;\n\n/**\n * Wrap the `ai` module namespace so that every `generateText` / `streamText` /\n * `generateObject` / `streamObject` call:\n *\n * 1. Opens a parent OTel span on the active TracerProvider.\n * 2. Forces `experimental_telemetry: { isEnabled: true }`, merging user metadata.\n * 3. Records input/output on the parent span and propagates errors.\n *\n * Other exports (types, helpers) pass through unchanged.\n */\nexport function wrapAISDK<T extends Record<string, unknown>>(aiModule: T): T {\n const wrapped: Record<string, unknown> = { ...aiModule };\n\n for (const name of WRAPPED_FUNCTIONS) {\n const original = aiModule[name];\n if (typeof original !== 'function') continue;\n\n if (name === 'streamText' || name === 'streamObject') {\n wrapped[name] = createStreamWrapper(name, original as (opts: any) => unknown);\n } else {\n wrapped[name] = createAsyncWrapper(name, original as (opts: any) => Promise<unknown>);\n }\n }\n\n return wrapped as T;\n}\n\nfunction rootSpanKind(name: WrappedFunctionName): string {\n if (name === 'embed' || name === 'embedMany' || name === 'rerank') return 'CHAIN';\n return 'WORKFLOW';\n}\n\n\nfunction getParentContext() {\n // Our parent comes solely from the private span store; a\n // foreign provider's active span must never become our ancestor.\n return getNeatlogsParentContext();\n}\n\nfunction createAsyncWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => Promise<unknown>,\n): (opts: any) => Promise<unknown> {\n return async function wrappedAsyncFn(opts: any): Promise<unknown> {\n const tracer = getNeatlogsTracer(TRACER_NAME);\n // startSpan (NOT startActiveSpan) + withNeatlogsSpan: startActiveSpan would\n // push our span onto the GLOBAL OTel context, so a foreign tracer's\n // startSpan() inside generateText() would read it as parent and inherit our\n // trace id. withNeatlogsSpan carries the parent in the private store in\n // the private context, leaving the global context untouched.\n const parentContext = getParentContext();\n const span = tracer.startSpan(\n `ai.${name}`,\n { attributes: { 'openinference.span.kind': rootSpanKind(name) } },\n parentContext,\n );\n return withNeatlogsSpan(\n span,\n async () => {\n try {\n const isEmbedOrRerank = name === 'embed' || name === 'embedMany' || name === 'rerank';\n if (!isEmbedOrRerank) {\n setInputValue(span, opts);\n }\n if (name === 'rerank' && opts?.query) {\n span.setAttribute('ai.rerank.query', String(opts.query));\n }\n const merged = mergeTelemetry(opts);\n const result = await original(merged);\n if (!isEmbedOrRerank) {\n setOutputValue(span, result);\n }\n return result;\n } catch (err) {\n recordSpanError(span, err);\n throw err;\n } finally {\n span.end();\n }\n },\n parentContext,\n );\n };\n}\n\n// streamText/streamObject return synchronously while the model keeps producing\n// tokens for seconds afterwards. Ending the span in a `finally` (as a plain sync\n// wrapper would) closes it in ~2ms with no output — the output only exists once\n// the stream finishes. Instead we keep the span open and end it from the AI SDK's\n// `onFinish` callback, where the final text/object is available. Any user-provided\n// `onFinish` is preserved and invoked first.\nfunction createStreamWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => unknown,\n): (opts: any) => unknown {\n return function wrappedStreamFn(opts: any): unknown {\n const tracer = getNeatlogsTracer(TRACER_NAME);\n // startSpan + withNeatlogsSpan (see createAsyncWrapper) so streamText's\n // internals never see our span on the global OTel context. The span stays\n // open past the run scope and is ended from onFinish/onError.\n const parentContext = getParentContext();\n const span = tracer.startSpan(\n `ai.${name}`,\n { attributes: { 'openinference.span.kind': rootSpanKind(name) } },\n parentContext,\n );\n return withNeatlogsSpan(\n span,\n () => {\n let spanEnded = false;\n const endOnce = () => {\n if (spanEnded) return;\n spanEnded = true;\n span.end();\n };\n try {\n setInputValue(span, opts);\n const merged = mergeTelemetry(opts);\n const userOnFinish = opts?.onFinish;\n const userOnError = opts?.onError;\n const wrappedOpts = {\n ...merged,\n onFinish: async (event: any) => {\n try {\n setStreamOutputValue(span, event);\n } finally {\n endOnce();\n }\n if (typeof userOnFinish === 'function') {\n return userOnFinish(event);\n }\n },\n onError: (event: any) => {\n recordSpanError(span, (event && event.error) ?? event);\n endOnce();\n if (typeof userOnError === 'function') {\n return userOnError(event);\n }\n },\n };\n return original(wrappedOpts);\n } catch (err) {\n // Synchronous throw (e.g. bad arguments) — the stream never started.\n recordSpanError(span, err);\n endOnce();\n throw err;\n }\n },\n parentContext,\n );\n };\n}\n\nfunction mergeTelemetry(opts: any): any {\n const baseTelemetry: AITelemetryConfig = createAITelemetry({\n metadata: opts?.experimental_telemetry?.metadata,\n });\n return {\n ...opts,\n experimental_telemetry: {\n ...opts?.experimental_telemetry,\n ...baseTelemetry,\n },\n };\n}\n\n\nfunction recordSpanError(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}\n","/**\n * Neatlogs-owned tracing state.\n *\n * Spans are created by the private Neatlogs provider and their parent is carried\n * in a private context key. The process-global OpenTelemetry span is\n * deliberately left untouched so other observability SDKs cannot export or\n * become parents of Neatlogs spans.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n ROOT_CONTEXT,\n INVALID_SPAN_CONTEXT,\n createContextKey,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n type TracerProvider,\n} from '@opentelemetry/api';\nimport { getActiveClient } from './active-client.js';\n\n// Carries the trace-ROOT span down the private context so descendants can target\n// it (e.g. setTraceOutput). getActiveNeatlogsSpan() returns the innermost span,\n// which is not the root once nested; this key preserves the root reference.\nconst NEATLOGS_ROOT_SPAN_KEY = createContextKey('neatlogs.root_span');\n\n// Entry points are bundled independently (`neatlogs`, `neatlogs/openai`,\n// `neatlogs/ai`, `neatlogs/mastra`, … each in both CJS and ESM), so every piece\n// of shared tracing state — the private span store AND the resolved provider /\n// provider — must live on `globalThis` behind a `Symbol.for` key.\n// Otherwise `init()` (run from the `neatlogs` bundle) sets `_provider` in ITS\n// module copy while a wrapper imported from `neatlogs/openai` reads a different,\n// still-null copy and silently falls back to the foreign global provider.\nconst PRIVATE_SPAN_STORAGE_KEY = Symbol.for(\n 'neatlogs.private_span_async_local_storage',\n);\nconst PRIVATE_PROVIDER_STATE_KEY = Symbol.for(\n 'neatlogs.private_provider_state',\n);\ninterface PrivateProviderState {\n provider: TracerProvider | null;\n}\ntype NeatlogsGlobal = typeof globalThis & {\n [PRIVATE_SPAN_STORAGE_KEY]?: AsyncLocalStorage<Context>;\n [PRIVATE_PROVIDER_STATE_KEY]?: PrivateProviderState;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\n// Stores the full Neatlogs Context (parent span PLUS any threaded values such as\n// trace()'s prompt-template keys), not just the span. We never\n// activate the OTel global context, so this private store is the ONLY channel\n// through which those values propagate down to descendant spans.\nconst privateContextStorage =\n neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ??\n (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new AsyncLocalStorage<Context>());\nconst providerState: PrivateProviderState =\n neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ??\n (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {\n provider: null,\n });\n// Wrappers may be constructed or accidentally invoked before init(). They must\n// never fall back to a foreign process-global provider, so pre-init calls use a\n// local no-op tracer and safely emit no exported spans.\nconst preInitTracer: Tracer = {\n startSpan(): Span {\n return otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n _name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n const fn =\n typeof arg2 === 'function'\n ? arg2\n : typeof arg3 === 'function'\n ? arg3\n : arg4;\n return fn!(otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT)) as ReturnType<F>;\n },\n};\n\n/** @internal Configure the provider used by Neatlogs-created spans. */\nexport function _setNeatlogsProvider(provider: TracerProvider | null): void {\n providerState.provider = provider;\n}\n\n/** Resolve a tracer from the private provider when one is configured. */\nexport function getNeatlogsTracer(name: string): Tracer {\n const client = getActiveClient();\n if (client) return client.getTracer(name);\n return providerState.provider?.getTracer(name) ?? preInitTracer;\n}\n\n/** A reusable tracer facade that resolves the active Client on every call. */\nexport function getRoutingNeatlogsTracer(name: string): Tracer {\n return {\n startSpan(spanName: string, options?: SpanOptions, context?: Context): Span {\n return isolateTracer(getNeatlogsTracer(name)).startSpan(\n spanName,\n options,\n context,\n );\n },\n startActiveSpan: ((...args: any[]) =>\n (isolateTracer(getNeatlogsTracer(name)).startActiveSpan as (...inner: any[]) => any)(\n ...args,\n )) as Tracer['startActiveSpan'],\n };\n}\n\n/**\n * @internal The private Neatlogs provider, or null before\n * init(). Used by integrations that must repoint a self-instrumenting library's\n * captured provider onto ours.\n */\nexport function getNeatlogsProvider(): TracerProvider | null {\n const client = getActiveClient();\n if (client) return client.tracerProvider;\n return providerState.provider;\n}\n\n/** Run a Client callback without inheriting another pipeline's private span. */\nexport function runWithFreshNeatlogsContext<T>(fn: () => T): T {\n return privateContextStorage.run(ROOT_CONTEXT, fn);\n}\n\n/**\n * Wrap a tracer so that spans it creates parent from — and, for\n * `startActiveSpan`, activate on — the PRIVATE Neatlogs context instead of the\n * global one.\n *\n * We hand this to libraries that create their own spans off a tracer we give\n * them (the Vercel AI SDK's `experimental_telemetry.tracer`, which calls\n * `tracer.startActiveSpan()` internally). Without the facade the AI SDK's native\n * spans would parent from `context.active()` — the foreign co-tenant's context —\n * and `startActiveSpan` would push them onto the GLOBAL context, so a foreign\n * tracer's next span reads our native span as its parent. Both directions leak.\n *\n */\nexport function isolateTracer(tracer: Tracer): Tracer {\n const facade: Tracer = {\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const parent = context ?? getNeatlogsActiveContext();\n return tracer.startSpan(name, options, parent);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n // Normalize the 2/3/4-arg overloads of startActiveSpan.\n let options: SpanOptions | undefined;\n let context: Context | undefined;\n let fn: F;\n if (typeof arg2 === 'function') {\n fn = arg2 as F;\n } else if (typeof arg3 === 'function') {\n options = arg2 as SpanOptions;\n fn = arg3 as F;\n } else {\n options = arg2 as SpanOptions;\n context = arg3 as Context;\n fn = arg4 as F;\n }\n const parent = context ?? getNeatlogsActiveContext();\n const span = tracer.startSpan(name, options, parent);\n return withNeatlogsSpan(span, () => fn(span), parent) as ReturnType<F>;\n },\n };\n return facade;\n}\n\n/**\n * The base context to build new Neatlogs spans and values on. NEVER contains a\n * foreign provider's span: it reads our private store, or ROOT_CONTEXT when\n * nothing is active.\n */\nexport function getNeatlogsActiveContext(): Context {\n return privateContextStorage.getStore() ?? ROOT_CONTEXT;\n}\n\n/** Return only the active Neatlogs span (never a foreign provider's span). */\nexport function getActiveNeatlogsSpan(): Span | undefined {\n return otelTrace.getSpan(getNeatlogsActiveContext());\n}\n\n/**\n * Return the trace-ROOT Neatlogs span, or undefined when no trace is active.\n *\n * Unlike {@link getActiveNeatlogsSpan} (innermost), this is the outermost span\n * of the current trace — the one the backend derives trace-level output from\n * (`parent_span_id=''`). It is stashed on the private context the first time a\n * span becomes active, so nested calls still resolve to the root.\n */\nexport function getNeatlogsRootSpan(): Span | undefined {\n return getNeatlogsActiveContext().getValue(NEATLOGS_ROOT_SPAN_KEY) as\n | Span\n | undefined;\n}\n\n/**\n * Build a parent context that cannot contain a foreign provider's span.\n *\n * The active Neatlogs context already carries the parent span AND any values\n * the caller threaded in upstream (e.g. `trace()`'s prompt-template values), so\n * those values reach the span processor via `onStart(parentContext)`. An\n * explicit `baseContext` (a caller's own value-carrying context) is honored as-is.\n */\nexport function getNeatlogsParentContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * A base context for callers that thread parent linkage themselves\n * (callback/event handlers that keep their own run-id → span map: LangChain,\n * OpenAI-Agents, Claude Agent SDK).\n *\n * This is the ACTIVE Neatlogs context — the private store's\n * context if a Neatlogs `trace()`/`span()` encloses this call, else\n * ROOT_CONTEXT. So a handler's own root/entry span nests under an enclosing\n * Neatlogs trace (preserving its session + end-user id) when one exists, and\n * auto-roots cleanly when one doesn't. A foreign provider's active span can\n * never leak in as an ancestor because the global context is never read.\n */\nexport function getNeatlogsBaseContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * Build an execution context for a Neatlogs span while preserving the active\n * foreign context. The span rides our private context store.\n */\nexport function getNeatlogsExecutionContext(\n span: Span,\n baseContext: Context = ROOT_CONTEXT,\n): Context {\n return otelTrace.setSpan(baseContext, span);\n}\n\n/**\n * Run a callback with a Neatlogs span active under the appropriate policy.\n *\n * The stored context is `setSpan(base, span)` — carrying the span PLUS whatever\n * values `base` holds — so threaded values (prompt templates)\n * propagate to descendant spans through our private store instead of the global\n * OTel context we deliberately never touch.\n */\nexport function withNeatlogsSpan<T>(\n span: Span,\n fn: () => T,\n baseContext?: Context,\n): T {\n const base = baseContext ?? getNeatlogsActiveContext();\n let ctx = otelTrace.setSpan(base, span);\n // The first span activated in a context with no root recorded IS the root of\n // this trace; remember it so descendants (setTraceOutput) can target it.\n if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === undefined) {\n ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span);\n }\n return privateContextStorage.run(ctx, fn);\n}\n\n/**\n * @internal Run with a private Neatlogs parent without treating that parent as\n * a locally-recording trace root. This is used for an extracted remote parent:\n * the first local recording span should remain the target for local trace-level\n * output, while still inheriting the remote trace/span IDs.\n */\nexport function withNeatlogsRemoteParent<T>(span: Span, fn: () => T): T {\n return privateContextStorage.run(otelTrace.setSpan(ROOT_CONTEXT, span), fn);\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Tracer, TracerProvider } from '@opentelemetry/api';\n\nexport interface ActiveNeatlogsClient {\n readonly workflowName: string;\n readonly tracerProvider: TracerProvider;\n getTracer(scope: string): Tracer;\n getLogger(): any | null;\n}\n\nconst ACTIVE_CLIENT_STORAGE_KEY = Symbol.for(\n 'neatlogs.active_client_async_local_storage',\n);\ntype NeatlogsGlobal = typeof globalThis & {\n [ACTIVE_CLIENT_STORAGE_KEY]?: AsyncLocalStorage<ActiveNeatlogsClient>;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\nconst storage =\n neatlogsGlobal[ACTIVE_CLIENT_STORAGE_KEY] ??\n (neatlogsGlobal[ACTIVE_CLIENT_STORAGE_KEY] =\n new AsyncLocalStorage<ActiveNeatlogsClient>());\n\nexport function getActiveClient(): ActiveNeatlogsClient | undefined {\n return storage.getStore();\n}\n\nexport function runWithClient<T>(\n client: ActiveNeatlogsClient,\n fn: () => T,\n): T {\n return storage.run(client, fn);\n}\n"],"mappings":"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","names":["AsyncLocalStorage","neatlogsGlobal","AsyncLocalStorage","stringified"]}
1
+ {"version":3,"sources":["../src/ai-sdk.ts","../src/core/provider.ts","../src/core/active-client.ts"],"sourcesContent":["/**\n * Vercel AI SDK wrapper — inline implementation.\n *\n * Wraps `generateText`, `streamText`, `generateObject`, `streamObject` from\n * the `ai` package with OTel parent spans + forced telemetry. Static export,\n * no dynamic imports — bundler-friendly (works with Turbopack, webpack, esbuild).\n *\n * Usage:\n * import { wrapAISDK } from 'neatlogs';\n * import * as ai from 'ai';\n * const { streamText, generateText, ToolLoopAgent } = wrapAISDK(ai);\n */\n\nimport {\n SpanStatusCode,\n type AttributeValue,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n} from '@opentelemetry/api';\nimport {\n getNeatlogsTracer,\n getNeatlogsParentContext,\n getRoutingNeatlogsTracer,\n withNeatlogsSpan,\n} from './core/provider.js';\n\nconst TRACER_NAME = 'neatlogs.ai-sdk';\n\n// -- Telemetry config --------------------------------------------------------\n\nexport interface CreateAITelemetryOptions {\n /** Identifier used by the AI SDK to group telemetry for this operation. */\n functionId?: string;\n metadata?: Record<string, AttributeValue>;\n /**\n * Existing telemetry tracer to preserve alongside Neatlogs (for example,\n * Laminar). Native AI SDK spans are mirrored to both isolated pipelines.\n */\n tracer?: Tracer;\n}\n\nexport interface AITelemetryConfig {\n isEnabled: true;\n recordInputs: true;\n recordOutputs: true;\n tracer: Tracer;\n functionId?: string;\n metadata: Record<string, AttributeValue>;\n}\n\nexport function createAITelemetry(\n opts: CreateAITelemetryOptions = {},\n): AITelemetryConfig {\n const userMeta = opts.metadata ?? {};\n const neatlogsTracer = getRoutingNeatlogsTracer(TRACER_NAME);\n const callerTracer = opts.tracer;\n return {\n isEnabled: true,\n recordInputs: true,\n recordOutputs: true,\n // Hand the AI SDK an isolation-aware tracer: it calls startActiveSpan()\n // internally, which would otherwise parent its native spans from the foreign\n // global context AND push them onto it (so a co-tenant's next span inherits\n // ours). The facade routes both through the private Neatlogs context.\n tracer: callerTracer\n ? createMirroredTracer(callerTracer, neatlogsTracer)\n : neatlogsTracer,\n ...(opts.functionId !== undefined ? { functionId: opts.functionId } : {}),\n // The marker is an implementation detail used only when Neatlogs owns the\n // telemetry stream. Do not leak it into caller-owned providers.\n metadata: callerTracer ? { ...userMeta } : { ...userMeta, neatlogsWrapped: true },\n };\n}\n\n// -- Span attributes ---------------------------------------------------------\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch {\n return '';\n }\n}\n\nfunction setInputValue(span: Span, opts: Record<string, unknown>): void {\n // For generateText/streamText — only capture prompt or messages, not full model config\n if (opts && ('prompt' in opts || 'messages' in opts)) {\n const input = opts.messages ?? opts.prompt;\n const stringified = safeStringify(input);\n if (stringified) {\n span.setAttribute('input.value', stringified);\n }\n return;\n }\n const stringified = safeStringify(opts);\n if (stringified) {\n span.setAttribute('input.value', stringified);\n }\n}\n\nfunction setOutputValue(span: Span, result: unknown): void {\n if (result && typeof result === 'object') {\n const r = result as Record<string, unknown>;\n // GenerateTextResult / StreamTextResult — extract meaningful fields\n if ('text' in r && 'finishReason' in r) {\n const text = String(r.text ?? '');\n if (text) {\n span.setAttribute('output.value', text);\n }\n if (r.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(r.finishReason));\n }\n return;\n }\n // GenerateObjectResult — the structured object is the output, not `text`.\n // Without this the envelope (object+usage+response+…) gets stringified whole.\n if ('object' in r && 'finishReason' in r) {\n if (r.object !== undefined) {\n span.setAttribute('output.value', safeStringify(r.object));\n }\n if (r.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(r.finishReason));\n }\n return;\n }\n }\n const stringified = safeStringify(result);\n if (stringified) {\n span.setAttribute('output.value', stringified);\n }\n}\n\n// Extract output from a streamText/streamObject `onFinish` event. The event\n// extends StepResult, so `text` (streamText) / `object` (streamObject) sit at\n// the top level alongside `finishReason` — but the event also carries `steps`,\n// `usage`, etc., so we pull only the meaningful fields instead of stringifying\n// the whole envelope.\nfunction setStreamOutputValue(span: Span, event: unknown): void {\n if (!event || typeof event !== 'object') return;\n const e = event as Record<string, unknown>;\n if (typeof e.text === 'string' && e.text) {\n span.setAttribute('output.value', e.text);\n } else if ('object' in e && e.object !== undefined) {\n const stringified = safeStringify(e.object);\n if (stringified) span.setAttribute('output.value', stringified);\n }\n if (e.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(e.finishReason));\n }\n}\n\n// -- Wrapping ----------------------------------------------------------------\n\ntype WrappedFunctionName =\n | 'generateText'\n | 'streamText'\n | 'generateObject'\n | 'streamObject'\n | 'embed'\n | 'embedMany'\n | 'rerank';\n\nconst WRAPPED_FUNCTIONS: readonly WrappedFunctionName[] = [\n 'generateText',\n 'streamText',\n 'generateObject',\n 'streamObject',\n 'embed',\n 'embedMany',\n 'rerank',\n] as const;\n\ntype WrappedAgentConstructorName = 'ToolLoopAgent' | 'Experimental_Agent';\n\nconst WRAPPED_AGENT_CONSTRUCTORS: readonly WrappedAgentConstructorName[] = [\n 'ToolLoopAgent',\n 'Experimental_Agent',\n] as const;\n\ntype AgentConstructor = new (...args: any[]) => unknown;\n\nconst wrappedExports = new WeakSet<Function>();\nconst wrapperByOriginal = new WeakMap<Function, Function>();\n\n/**\n * Wrap the `ai` module namespace so that every supported generation, embedding,\n * and reranking call:\n *\n * 1. Opens a parent OTel span on the active TracerProvider.\n * 2. Forces `experimental_telemetry: { isEnabled: true }`, merging user metadata.\n * 3. Records input/output on the parent span and propagates errors.\n *\n * AI SDK v6's `ToolLoopAgent` (and its `Experimental_Agent` alias) is wrapped at\n * construction time so its internal model and tool calls receive the same native\n * telemetry configuration. Other exports pass through unchanged.\n */\nexport function wrapAISDK<T extends Record<string, unknown>>(aiModule: T): T {\n const wrapped: Record<string, unknown> = { ...aiModule };\n\n for (const name of WRAPPED_FUNCTIONS) {\n const original = aiModule[name];\n if (typeof original !== 'function') continue;\n\n const existing = getExistingWrapper(original);\n if (existing) {\n wrapped[name] = existing;\n continue;\n }\n\n if (name === 'streamText' || name === 'streamObject') {\n wrapped[name] = cacheWrapper(\n original,\n createStreamWrapper(name, original as (opts: any) => unknown),\n );\n } else {\n wrapped[name] = cacheWrapper(\n original,\n createAsyncWrapper(name, original as (opts: any) => Promise<unknown>),\n );\n }\n }\n\n for (const name of WRAPPED_AGENT_CONSTRUCTORS) {\n const original = aiModule[name];\n if (typeof original !== 'function') continue;\n\n const existing = getExistingWrapper(original);\n wrapped[name] =\n existing ??\n cacheWrapper(\n original,\n createAgentConstructorWrapper(original as AgentConstructor),\n );\n }\n\n return wrapped as T;\n}\n\nfunction getExistingWrapper(original: Function): Function | undefined {\n if (wrappedExports.has(original)) return original;\n return wrapperByOriginal.get(original);\n}\n\nfunction cacheWrapper(original: Function, wrapped: Function): Function {\n wrapperByOriginal.set(original, wrapped);\n wrappedExports.add(wrapped);\n return wrapped;\n}\n\nfunction createAgentConstructorWrapper(\n original: AgentConstructor,\n): AgentConstructor {\n return new Proxy(original, {\n construct(target, args, newTarget) {\n if (\n args.length === 0 ||\n typeof args[0] !== 'object' ||\n args[0] === null\n ) {\n return Reflect.construct(target, args, newTarget);\n }\n\n const settings = mergeAgentSettings(args[0]);\n return Reflect.construct(target, [settings, ...args.slice(1)], newTarget);\n },\n });\n}\n\nfunction mergeAgentSettings(settings: any): any {\n const merged = mergeTelemetry(settings);\n const userPrepareCall = settings.prepareCall;\n if (typeof userPrepareCall !== 'function') return merged;\n\n return {\n ...merged,\n prepareCall: async function wrappedPrepareCall(\n this: unknown,\n ...args: any[]\n ) {\n const prepared = await Reflect.apply(userPrepareCall, this, args);\n return prepared == null\n ? prepared\n : mergeTelemetry(prepared, settings.experimental_telemetry);\n },\n };\n}\n\nfunction rootSpanKind(name: WrappedFunctionName): string {\n if (name === 'embed' || name === 'embedMany' || name === 'rerank')\n return 'CHAIN';\n return 'WORKFLOW';\n}\n\nfunction getParentContext() {\n // Our parent comes solely from the private span store; a\n // foreign provider's active span must never become our ancestor.\n return getNeatlogsParentContext();\n}\n\nfunction createAsyncWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => Promise<unknown>,\n): (opts: any) => Promise<unknown> {\n return async function wrappedAsyncFn(opts: any): Promise<unknown> {\n const tracer = getNeatlogsTracer(TRACER_NAME);\n // startSpan (NOT startActiveSpan) + withNeatlogsSpan: startActiveSpan would\n // push our span onto the GLOBAL OTel context, so a foreign tracer's\n // startSpan() inside generateText() would read it as parent and inherit our\n // trace id. withNeatlogsSpan carries the parent in the private store in\n // the private context, leaving the global context untouched.\n const parentContext = getParentContext();\n const span = tracer.startSpan(\n `ai.${name}`,\n { attributes: { 'openinference.span.kind': rootSpanKind(name) } },\n parentContext,\n );\n return withNeatlogsSpan(\n span,\n async () => {\n try {\n const isEmbedOrRerank =\n name === 'embed' || name === 'embedMany' || name === 'rerank';\n if (!isEmbedOrRerank) {\n setInputValue(span, opts);\n }\n if (name === 'rerank' && opts?.query) {\n span.setAttribute('ai.rerank.query', String(opts.query));\n }\n const merged = mergeTelemetry(opts);\n const result = await original(merged);\n if (!isEmbedOrRerank) {\n setOutputValue(span, result);\n }\n return result;\n } catch (err) {\n recordSpanError(span, err);\n throw err;\n } finally {\n span.end();\n }\n },\n parentContext,\n );\n };\n}\n\n// streamText/streamObject return synchronously while the model keeps producing\n// tokens for seconds afterwards. Ending the span in a `finally` (as a plain sync\n// wrapper would) closes it in ~2ms with no output — the output only exists once\n// the stream finishes. Instead we keep the span open and end it from the AI SDK's\n// `onFinish` callback, where the final text/object is available. Any user-provided\n// `onFinish` is preserved and invoked first.\nfunction createStreamWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => unknown,\n): (opts: any) => unknown {\n return function wrappedStreamFn(opts: any): unknown {\n const tracer = getNeatlogsTracer(TRACER_NAME);\n // startSpan + withNeatlogsSpan (see createAsyncWrapper) so streamText's\n // internals never see our span on the global OTel context. The span stays\n // open past the run scope and is ended from onFinish/onError.\n const parentContext = getParentContext();\n const span = tracer.startSpan(\n `ai.${name}`,\n { attributes: { 'openinference.span.kind': rootSpanKind(name) } },\n parentContext,\n );\n return withNeatlogsSpan(\n span,\n () => {\n let spanEnded = false;\n const endOnce = () => {\n if (spanEnded) return;\n spanEnded = true;\n span.end();\n };\n try {\n setInputValue(span, opts);\n const merged = mergeTelemetry(opts);\n const userOnFinish = opts?.onFinish;\n const userOnError = opts?.onError;\n const wrappedOpts = {\n ...merged,\n onFinish: async (event: any) => {\n try {\n setStreamOutputValue(span, event);\n } finally {\n endOnce();\n }\n if (typeof userOnFinish === 'function') {\n return userOnFinish(event);\n }\n },\n onError: (event: any) => {\n recordSpanError(span, (event && event.error) ?? event);\n endOnce();\n if (typeof userOnError === 'function') {\n return userOnError(event);\n }\n },\n };\n return original(wrappedOpts);\n } catch (err) {\n // Synchronous throw (e.g. bad arguments) — the stream never started.\n recordSpanError(span, err);\n endOnce();\n throw err;\n }\n },\n parentContext,\n );\n };\n}\n\nfunction createMirroredSpan(primary: Span, secondary: Span): Span {\n const mirrored: Span = {\n spanContext() {\n // The caller-owned tracer remains the process-global context owner. Its\n // context is therefore the one external instrumentation must observe.\n return primary.spanContext();\n },\n setAttribute(key, value) {\n primary.setAttribute(key, value);\n secondary.setAttribute(key, value);\n return mirrored;\n },\n setAttributes(attributes) {\n primary.setAttributes(attributes);\n secondary.setAttributes(attributes);\n return mirrored;\n },\n addEvent(name, attributesOrStartTime, startTime) {\n primary.addEvent(name, attributesOrStartTime, startTime);\n secondary.addEvent(name, attributesOrStartTime, startTime);\n return mirrored;\n },\n addLink(link) {\n primary.addLink(link);\n secondary.addLink(link);\n return mirrored;\n },\n addLinks(links) {\n primary.addLinks(links);\n secondary.addLinks(links);\n return mirrored;\n },\n setStatus(status) {\n primary.setStatus(status);\n secondary.setStatus(status);\n return mirrored;\n },\n updateName(name) {\n primary.updateName(name);\n secondary.updateName(name);\n return mirrored;\n },\n end(endTime) {\n primary.end(endTime);\n secondary.end(endTime);\n },\n isRecording() {\n return primary.isRecording() || secondary.isRecording();\n },\n recordException(exception, time) {\n primary.recordException(exception, time);\n secondary.recordException(exception, time);\n },\n };\n return mirrored;\n}\n\n/**\n * Mirror one AI SDK telemetry stream to two isolated tracer pipelines.\n *\n * The caller-owned tracer is deliberately outermost so its normal global OTel\n * activation and parentage stay unchanged. The Neatlogs routing tracer keeps\n * its span active only in Neatlogs' private AsyncLocalStorage context.\n */\nfunction createMirroredTracer(primary: Tracer, secondary: Tracer): Tracer {\n return {\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const primarySpan = primary.startSpan(name, options, context);\n const secondarySpan = secondary.startSpan(name, options);\n return createMirroredSpan(primarySpan, secondarySpan);\n },\n startActiveSpan: (<F extends (span: Span) => unknown>(\n name: string,\n arg2?: SpanOptions | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> => {\n let options: SpanOptions | undefined;\n let context: Context | undefined;\n let fn: F;\n\n if (typeof arg2 === 'function') {\n fn = arg2;\n } else if (typeof arg3 === 'function') {\n options = arg2;\n fn = arg3;\n } else {\n options = arg2;\n context = arg3 as Context;\n fn = arg4!;\n }\n\n const runSecondary = (primarySpan: Span): ReturnType<F> => {\n const onSecondarySpan = (secondarySpan: Span) =>\n fn(createMirroredSpan(primarySpan, secondarySpan)) as ReturnType<F>;\n return options === undefined\n ? secondary.startActiveSpan(name, onSecondarySpan)\n : secondary.startActiveSpan(name, options, onSecondarySpan);\n };\n\n if (context !== undefined) {\n return primary.startActiveSpan(\n name,\n options ?? {},\n context,\n runSecondary,\n ) as ReturnType<F>;\n }\n return options === undefined\n ? (primary.startActiveSpan(name, runSecondary) as ReturnType<F>)\n : (primary.startActiveSpan(name, options, runSecondary) as ReturnType<F>);\n }) as Tracer['startActiveSpan'],\n };\n}\n\nfunction mergeTelemetry(opts: any, fallbackTelemetry?: any): any {\n const requestedTelemetry = {\n ...fallbackTelemetry,\n ...opts?.experimental_telemetry,\n metadata: {\n ...fallbackTelemetry?.metadata,\n ...opts?.experimental_telemetry?.metadata,\n },\n };\n const baseTelemetry: AITelemetryConfig = createAITelemetry({\n functionId: requestedTelemetry.functionId,\n metadata: requestedTelemetry.metadata,\n tracer: requestedTelemetry.tracer as Tracer | undefined,\n });\n const callerTracer = requestedTelemetry.tracer as Tracer | undefined;\n const hasCallerTracer = callerTracer !== undefined;\n\n return {\n ...opts,\n experimental_telemetry: {\n ...baseTelemetry,\n ...requestedTelemetry,\n isEnabled: true,\n recordInputs: requestedTelemetry.recordInputs ?? true,\n recordOutputs: requestedTelemetry.recordOutputs ?? true,\n tracer: baseTelemetry.tracer,\n // Do not add Neatlogs-only marker metadata to a caller-owned telemetry\n // pipeline such as Laminar. Both providers receive the same AI SDK span\n // data, while their providers, parent contexts, and exporters stay separate.\n metadata: hasCallerTracer\n ? requestedTelemetry.metadata\n : baseTelemetry.metadata,\n },\n };\n}\n\nfunction recordSpanError(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}\n","/**\n * Neatlogs-owned tracing state.\n *\n * Spans are created by the private Neatlogs provider and their parent is carried\n * in a private context key. The process-global OpenTelemetry span is\n * deliberately left untouched so other observability SDKs cannot export or\n * become parents of Neatlogs spans.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n ROOT_CONTEXT,\n INVALID_SPAN_CONTEXT,\n createContextKey,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n type TracerProvider,\n} from '@opentelemetry/api';\nimport { getActiveClient } from './active-client.js';\n\n// Carries the trace-ROOT span down the private context so descendants can target\n// it (e.g. setTraceOutput). getActiveNeatlogsSpan() returns the innermost span,\n// which is not the root once nested; this key preserves the root reference.\nconst NEATLOGS_ROOT_SPAN_KEY = createContextKey('neatlogs.root_span');\n\n// Entry points are bundled independently (`neatlogs`, `neatlogs/openai`,\n// `neatlogs/ai`, `neatlogs/mastra`, … each in both CJS and ESM), so every piece\n// of shared tracing state — the private span store AND the resolved provider /\n// provider — must live on `globalThis` behind a `Symbol.for` key.\n// Otherwise `init()` (run from the `neatlogs` bundle) sets `_provider` in ITS\n// module copy while a wrapper imported from `neatlogs/openai` reads a different,\n// still-null copy and silently falls back to the foreign global provider.\nconst PRIVATE_SPAN_STORAGE_KEY = Symbol.for(\n 'neatlogs.private_span_async_local_storage',\n);\nconst PRIVATE_PROVIDER_STATE_KEY = Symbol.for(\n 'neatlogs.private_provider_state',\n);\ninterface PrivateProviderState {\n provider: TracerProvider | null;\n}\ntype NeatlogsGlobal = typeof globalThis & {\n [PRIVATE_SPAN_STORAGE_KEY]?: AsyncLocalStorage<Context>;\n [PRIVATE_PROVIDER_STATE_KEY]?: PrivateProviderState;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\n// Stores the full Neatlogs Context (parent span PLUS any threaded values such as\n// trace()'s prompt-template keys), not just the span. We never\n// activate the OTel global context, so this private store is the ONLY channel\n// through which those values propagate down to descendant spans.\nconst privateContextStorage =\n neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ??\n (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new AsyncLocalStorage<Context>());\nconst providerState: PrivateProviderState =\n neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ??\n (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {\n provider: null,\n });\n// Wrappers may be constructed or accidentally invoked before init(). They must\n// never fall back to a foreign process-global provider, so pre-init calls use a\n// local no-op tracer and safely emit no exported spans.\nconst preInitTracer: Tracer = {\n startSpan(): Span {\n return otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n _name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n const fn =\n typeof arg2 === 'function'\n ? arg2\n : typeof arg3 === 'function'\n ? arg3\n : arg4;\n return fn!(otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT)) as ReturnType<F>;\n },\n};\n\n/** @internal Configure the provider used by Neatlogs-created spans. */\nexport function _setNeatlogsProvider(provider: TracerProvider | null): void {\n providerState.provider = provider;\n}\n\n/** Resolve a tracer from the private provider when one is configured. */\nexport function getNeatlogsTracer(name: string): Tracer {\n const client = getActiveClient();\n if (client) return client.getTracer(name);\n return providerState.provider?.getTracer(name) ?? preInitTracer;\n}\n\n/** A reusable tracer facade that resolves the active Client on every call. */\nexport function getRoutingNeatlogsTracer(name: string): Tracer {\n return {\n startSpan(spanName: string, options?: SpanOptions, context?: Context): Span {\n return isolateTracer(getNeatlogsTracer(name)).startSpan(\n spanName,\n options,\n context,\n );\n },\n startActiveSpan: ((...args: any[]) =>\n (isolateTracer(getNeatlogsTracer(name)).startActiveSpan as (...inner: any[]) => any)(\n ...args,\n )) as Tracer['startActiveSpan'],\n };\n}\n\n/**\n * @internal The private Neatlogs provider, or null before\n * init(). Used by integrations that must repoint a self-instrumenting library's\n * captured provider onto ours.\n */\nexport function getNeatlogsProvider(): TracerProvider | null {\n const client = getActiveClient();\n if (client) return client.tracerProvider;\n return providerState.provider;\n}\n\n/** Run a Client callback without inheriting another pipeline's private span. */\nexport function runWithFreshNeatlogsContext<T>(fn: () => T): T {\n return privateContextStorage.run(ROOT_CONTEXT, fn);\n}\n\n/**\n * Wrap a tracer so that spans it creates parent from — and, for\n * `startActiveSpan`, activate on — the PRIVATE Neatlogs context instead of the\n * global one.\n *\n * We hand this to libraries that create their own spans off a tracer we give\n * them (the Vercel AI SDK's `experimental_telemetry.tracer`, which calls\n * `tracer.startActiveSpan()` internally). Without the facade the AI SDK's native\n * spans would parent from `context.active()` — the foreign co-tenant's context —\n * and `startActiveSpan` would push them onto the GLOBAL context, so a foreign\n * tracer's next span reads our native span as its parent. Both directions leak.\n *\n */\nexport function isolateTracer(tracer: Tracer): Tracer {\n const facade: Tracer = {\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const parent = context ?? getNeatlogsActiveContext();\n return tracer.startSpan(name, options, parent);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n // Normalize the 2/3/4-arg overloads of startActiveSpan.\n let options: SpanOptions | undefined;\n let context: Context | undefined;\n let fn: F;\n if (typeof arg2 === 'function') {\n fn = arg2 as F;\n } else if (typeof arg3 === 'function') {\n options = arg2 as SpanOptions;\n fn = arg3 as F;\n } else {\n options = arg2 as SpanOptions;\n context = arg3 as Context;\n fn = arg4 as F;\n }\n const parent = context ?? getNeatlogsActiveContext();\n const span = tracer.startSpan(name, options, parent);\n return withNeatlogsSpan(span, () => fn(span), parent) as ReturnType<F>;\n },\n };\n return facade;\n}\n\n/**\n * The base context to build new Neatlogs spans and values on. NEVER contains a\n * foreign provider's span: it reads our private store, or ROOT_CONTEXT when\n * nothing is active.\n */\nexport function getNeatlogsActiveContext(): Context {\n return privateContextStorage.getStore() ?? ROOT_CONTEXT;\n}\n\n/** Return only the active Neatlogs span (never a foreign provider's span). */\nexport function getActiveNeatlogsSpan(): Span | undefined {\n return otelTrace.getSpan(getNeatlogsActiveContext());\n}\n\n/**\n * Return the trace-ROOT Neatlogs span, or undefined when no trace is active.\n *\n * Unlike {@link getActiveNeatlogsSpan} (innermost), this is the outermost span\n * of the current trace — the one the backend derives trace-level output from\n * (`parent_span_id=''`). It is stashed on the private context the first time a\n * span becomes active, so nested calls still resolve to the root.\n */\nexport function getNeatlogsRootSpan(): Span | undefined {\n return getNeatlogsActiveContext().getValue(NEATLOGS_ROOT_SPAN_KEY) as\n | Span\n | undefined;\n}\n\n/**\n * Build a parent context that cannot contain a foreign provider's span.\n *\n * The active Neatlogs context already carries the parent span AND any values\n * the caller threaded in upstream (e.g. `trace()`'s prompt-template values), so\n * those values reach the span processor via `onStart(parentContext)`. An\n * explicit `baseContext` (a caller's own value-carrying context) is honored as-is.\n */\nexport function getNeatlogsParentContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * A base context for callers that thread parent linkage themselves\n * (callback/event handlers that keep their own run-id → span map: LangChain,\n * OpenAI-Agents, Claude Agent SDK).\n *\n * This is the ACTIVE Neatlogs context — the private store's\n * context if a Neatlogs `trace()`/`span()` encloses this call, else\n * ROOT_CONTEXT. So a handler's own root/entry span nests under an enclosing\n * Neatlogs trace (preserving its session + end-user id) when one exists, and\n * auto-roots cleanly when one doesn't. A foreign provider's active span can\n * never leak in as an ancestor because the global context is never read.\n */\nexport function getNeatlogsBaseContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * Build an execution context for a Neatlogs span while preserving the active\n * foreign context. The span rides our private context store.\n */\nexport function getNeatlogsExecutionContext(\n span: Span,\n baseContext: Context = ROOT_CONTEXT,\n): Context {\n return otelTrace.setSpan(baseContext, span);\n}\n\n/**\n * Run a callback with a Neatlogs span active under the appropriate policy.\n *\n * The stored context is `setSpan(base, span)` — carrying the span PLUS whatever\n * values `base` holds — so threaded values (prompt templates)\n * propagate to descendant spans through our private store instead of the global\n * OTel context we deliberately never touch.\n */\nexport function withNeatlogsSpan<T>(\n span: Span,\n fn: () => T,\n baseContext?: Context,\n rootSpan?: Span,\n): T {\n const base = baseContext ?? getNeatlogsActiveContext();\n let ctx = otelTrace.setSpan(base, span);\n // The first span activated in a context with no root recorded IS the root of\n // this trace; remember it so descendants (setTraceOutput) can target it.\n if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === undefined) {\n ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, rootSpan ?? span);\n }\n return privateContextStorage.run(ctx, fn);\n}\n\n/**\n * @internal Run with a private Neatlogs parent without treating that parent as\n * a locally-recording trace root. This is used for an extracted remote parent:\n * the first local recording span should remain the target for local trace-level\n * output, while still inheriting the remote trace/span IDs.\n */\nexport function withNeatlogsRemoteParent<T>(span: Span, fn: () => T): T {\n return privateContextStorage.run(otelTrace.setSpan(ROOT_CONTEXT, span), fn);\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Tracer, TracerProvider } from '@opentelemetry/api';\n\nexport interface ActiveNeatlogsClient {\n readonly workflowName: string;\n readonly tracerProvider: TracerProvider;\n getTracer(scope: string): Tracer;\n getLogger(): any | null;\n}\n\nconst ACTIVE_CLIENT_STORAGE_KEY = Symbol.for(\n 'neatlogs.active_client_async_local_storage',\n);\ntype NeatlogsGlobal = typeof globalThis & {\n [ACTIVE_CLIENT_STORAGE_KEY]?: AsyncLocalStorage<ActiveNeatlogsClient>;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\nconst storage =\n neatlogsGlobal[ACTIVE_CLIENT_STORAGE_KEY] ??\n (neatlogsGlobal[ACTIVE_CLIENT_STORAGE_KEY] =\n new AsyncLocalStorage<ActiveNeatlogsClient>());\n\nexport function getActiveClient(): ActiveNeatlogsClient | undefined {\n return storage.getStore();\n}\n\nexport function runWithClient<T>(\n client: ActiveNeatlogsClient,\n fn: () => T,\n): T {\n return storage.run(client, fn);\n}\n"],"mappings":"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","names":["AsyncLocalStorage","neatlogsGlobal","AsyncLocalStorage","stringified"]}
@@ -179,11 +179,11 @@ function getActiveNeatlogsSpan() {
179
179
  function getNeatlogsParentContext(baseContext) {
180
180
  return baseContext ?? getNeatlogsActiveContext();
181
181
  }
182
- function withNeatlogsSpan(span, fn, baseContext) {
182
+ function withNeatlogsSpan(span, fn, baseContext, rootSpan) {
183
183
  const base = baseContext ?? getNeatlogsActiveContext();
184
184
  let ctx = import_api.trace.setSpan(base, span);
185
185
  if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === void 0) {
186
- ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span);
186
+ ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, rootSpan ?? span);
187
187
  }
188
188
  return privateContextStorage.run(ctx, fn);
189
189
  }
@@ -917,7 +917,8 @@ var USER_PROMPT_TEMPLATE_KEY = (0, import_api3.createContextKey)("neatlogs.user_
917
917
  var USER_PROMPT_VARIABLES_KEY = (0, import_api3.createContextKey)("neatlogs.user_prompt_variables");
918
918
 
919
919
  // src/core/auto-root.ts
920
- var ROOT_KINDS = /* @__PURE__ */ new Set(["workflow", "chain", "agent", "mcp_tool"]);
920
+ var boundAutoRoots = /* @__PURE__ */ new WeakMap();
921
+ var ROOT_KINDS = /* @__PURE__ */ new Set(["workflow", "chain", "agent", "mcp_tool", "llm"]);
921
922
  function autoRootEnabled() {
922
923
  const val = (process.env.NEATLOGS_AUTO_ROOT ?? "").trim().toLowerCase();
923
924
  return !["false", "0", "no", "off"].includes(val);
@@ -932,11 +933,12 @@ function resolveRootWorkflowName() {
932
933
  }
933
934
  return "workflow";
934
935
  }
935
- function stampAutoRootIdentity(root) {
936
+ function stampRootIdentity(root) {
936
937
  applySessionAttributes(root, void 0, true);
937
938
  applyEndUserAttributes(root, void 0, void 0, true);
938
939
  }
939
- function wrapSpanWithRoot(child, root) {
940
+ function bindSpanToAutoRoot(child, root) {
941
+ if (!root) return child;
940
942
  let ended = false;
941
943
  const proxy = new Proxy(child, {
942
944
  get(target, prop, _receiver) {
@@ -958,6 +960,7 @@ function wrapSpanWithRoot(child, root) {
958
960
  return typeof value === "function" ? value.bind(target) : value;
959
961
  }
960
962
  });
963
+ boundAutoRoots.set(proxy, root);
961
964
  aliasMediaCaptureSpan(proxy, child);
962
965
  return proxy;
963
966
  }
@@ -970,27 +973,41 @@ var AutoRootTracer = class {
970
973
  const kind = String(
971
974
  options?.attributes?.["neatlogs.span.kind"] ?? ""
972
975
  ).toLowerCase();
976
+ const hasExplicitContext = context !== void 0;
973
977
  const ctx = context ?? getNeatlogsParentContext();
974
978
  const parent = getActiveNeatlogsSpan();
975
- const needsRoot = autoRootEnabled() && !ROOT_KINDS.has(kind) && !(parent !== void 0 && parent.isRecording());
979
+ const isParentless = !hasExplicitContext && !(parent !== void 0 && parent.isRecording());
980
+ const needsRoot = autoRootEnabled() && !ROOT_KINDS.has(kind) && isParentless;
976
981
  if (!needsRoot) {
977
- return this._tracer.startSpan(name, options, ctx);
982
+ const span = this._tracer.startSpan(name, options, ctx);
983
+ if (isParentless && ROOT_KINDS.has(kind)) stampRootIdentity(span);
984
+ return span;
978
985
  }
979
986
  const root = this._tracer.startSpan(
980
987
  resolveRootWorkflowName(),
981
988
  { attributes: { "neatlogs.span.kind": "workflow", "neatlogs.auto_root": true } },
982
989
  ctx
983
990
  );
984
- stampAutoRootIdentity(root);
991
+ stampRootIdentity(root);
985
992
  const childCtx = import_api4.trace.setSpan(ctx, root);
986
993
  const child = this._tracer.startSpan(name, options, childCtx);
987
- return wrapSpanWithRoot(child, root);
994
+ return bindSpanToAutoRoot(child, root);
988
995
  }
989
- // startActiveSpan is used by traceTool (callback-scoped, always under an
990
- // active span in practice) — pass straight through, no auto-root.
991
- startActiveSpan = ((...args) => this._tracer.startActiveSpan(
992
- ...args
993
- ));
996
+ startActiveSpan = ((name, ...args) => {
997
+ const callback = args.at(-1);
998
+ if (typeof callback !== "function") {
999
+ throw new TypeError("startActiveSpan requires a callback");
1000
+ }
1001
+ const options = args.length >= 2 ? args[0] : void 0;
1002
+ const parentContext = args.length >= 3 ? args[1] : getNeatlogsParentContext();
1003
+ const span = args.length >= 3 ? this.startSpan(name, options, parentContext) : this.startSpan(name, options);
1004
+ return withNeatlogsSpan(
1005
+ span,
1006
+ () => callback(span),
1007
+ parentContext,
1008
+ boundAutoRoots.get(span)
1009
+ );
1010
+ });
994
1011
  };
995
1012
  function getProviderTracer(name) {
996
1013
  return new AutoRootTracer(getNeatlogsTracer(name));