neatlogs 1.0.8 → 1.0.10

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.
package/dist/index.cjs CHANGED
@@ -5899,7 +5899,7 @@ async function removeTag(name, tag) {
5899
5899
  }
5900
5900
 
5901
5901
  // src/version.ts
5902
- var __version__ = "1.0.8";
5902
+ var __version__ = "1.0.10";
5903
5903
 
5904
5904
  // src/init.ts
5905
5905
  var logger13 = getLogger();
@@ -6532,14 +6532,108 @@ function recordSpanError(span2, err) {
6532
6532
  }
6533
6533
 
6534
6534
  // src/openai.ts
6535
+ var import_api10 = require("@opentelemetry/api");
6536
+
6537
+ // src/core/auto-root.ts
6535
6538
  var import_api9 = require("@opentelemetry/api");
6539
+ var ROOT_KINDS = /* @__PURE__ */ new Set(["workflow", "chain", "agent", "mcp_tool"]);
6540
+ function autoRootEnabled() {
6541
+ const val = (process.env.NEATLOGS_AUTO_ROOT ?? "").trim().toLowerCase();
6542
+ return !["false", "0", "no", "off"].includes(val);
6543
+ }
6544
+ function resolveRootWorkflowName() {
6545
+ try {
6546
+ const name = getSessionConfig()?.workflowName;
6547
+ if (typeof name === "string" && name.trim()) return name;
6548
+ } catch {
6549
+ }
6550
+ return "workflow";
6551
+ }
6552
+ function wrapSpanWithRoot(child, root) {
6553
+ let ended = false;
6554
+ return new Proxy(child, {
6555
+ get(target, prop, _receiver) {
6556
+ if (prop === "end") {
6557
+ return (...args) => {
6558
+ if (ended) return;
6559
+ ended = true;
6560
+ try {
6561
+ target.end(...args);
6562
+ } finally {
6563
+ try {
6564
+ root.end();
6565
+ } catch {
6566
+ }
6567
+ }
6568
+ };
6569
+ }
6570
+ const value = Reflect.get(target, prop, target);
6571
+ return typeof value === "function" ? value.bind(target) : value;
6572
+ }
6573
+ });
6574
+ }
6575
+ function maybeOpenAutoRoot(tracer, kind, baseCtx) {
6576
+ const k = String(kind ?? "").toLowerCase();
6577
+ const existing = import_api9.trace.getSpan(baseCtx);
6578
+ if (!autoRootEnabled() || ROOT_KINDS.has(k) || existing !== void 0 && existing.isRecording()) {
6579
+ return { root: void 0, ctx: baseCtx };
6580
+ }
6581
+ const root = tracer.startSpan(
6582
+ resolveRootWorkflowName(),
6583
+ { attributes: { "neatlogs.span.kind": "workflow", "neatlogs.auto_root": true } },
6584
+ baseCtx
6585
+ );
6586
+ return { root, ctx: import_api9.trace.setSpan(baseCtx, root) };
6587
+ }
6588
+ function endAutoRoot(root) {
6589
+ if (!root) return;
6590
+ try {
6591
+ root.end();
6592
+ } catch {
6593
+ }
6594
+ }
6595
+ var AutoRootTracer = class {
6596
+ constructor(_tracer) {
6597
+ this._tracer = _tracer;
6598
+ }
6599
+ _tracer;
6600
+ startSpan(name, options, context3) {
6601
+ const kind = String(
6602
+ options?.attributes?.["neatlogs.span.kind"] ?? ""
6603
+ ).toLowerCase();
6604
+ const ctx = context3 ?? import_api9.context.active();
6605
+ const parent = import_api9.trace.getSpan(ctx);
6606
+ const needsRoot = autoRootEnabled() && !ROOT_KINDS.has(kind) && !(parent !== void 0 && parent.isRecording());
6607
+ if (!needsRoot) {
6608
+ return this._tracer.startSpan(name, options, context3);
6609
+ }
6610
+ const root = this._tracer.startSpan(
6611
+ resolveRootWorkflowName(),
6612
+ { attributes: { "neatlogs.span.kind": "workflow", "neatlogs.auto_root": true } },
6613
+ ctx
6614
+ );
6615
+ const childCtx = import_api9.trace.setSpan(ctx, root);
6616
+ const child = this._tracer.startSpan(name, options, childCtx);
6617
+ return wrapSpanWithRoot(child, root);
6618
+ }
6619
+ // startActiveSpan is used by traceTool (callback-scoped, always under an
6620
+ // active span in practice) — pass straight through, no auto-root.
6621
+ startActiveSpan = ((...args) => this._tracer.startActiveSpan(
6622
+ ...args
6623
+ ));
6624
+ };
6625
+ function getProviderTracer(name) {
6626
+ return new AutoRootTracer(import_api9.trace.getTracer(name));
6627
+ }
6628
+
6629
+ // src/openai.ts
6536
6630
  var TRACER_NAME3 = "neatlogs.openai";
6537
6631
  function wrapOpenAI(client) {
6538
6632
  return wrapNamespace(client, []);
6539
6633
  }
6540
6634
  function traceTool(name, fn) {
6541
6635
  return async function tracedTool(args) {
6542
- const tracer = import_api9.trace.getTracer(TRACER_NAME3);
6636
+ const tracer = import_api10.trace.getTracer(TRACER_NAME3);
6543
6637
  return tracer.startActiveSpan(
6544
6638
  `tool.${name}`,
6545
6639
  {
@@ -6549,12 +6643,12 @@ function traceTool(name, fn) {
6549
6643
  "input.value": safeStringify3(args)
6550
6644
  }
6551
6645
  },
6552
- import_api9.context.active(),
6646
+ import_api10.context.active(),
6553
6647
  async (span2) => {
6554
6648
  try {
6555
6649
  const result = await fn(args);
6556
6650
  span2.setAttribute("output.value", safeStringify3(result));
6557
- span2.setStatus({ code: import_api9.SpanStatusCode.OK });
6651
+ span2.setStatus({ code: import_api10.SpanStatusCode.OK });
6558
6652
  return result;
6559
6653
  } catch (err) {
6560
6654
  recordError(span2, err);
@@ -6593,7 +6687,7 @@ function isNamespace(path4) {
6593
6687
  }
6594
6688
  function tracedChatCompletionsCreate(original) {
6595
6689
  return function(opts, ...rest) {
6596
- const tracer = import_api9.trace.getTracer(TRACER_NAME3);
6690
+ const tracer = getProviderTracer(TRACER_NAME3);
6597
6691
  const model = opts?.model ?? "";
6598
6692
  const messages = opts?.messages ?? [];
6599
6693
  const isStream = opts?.stream === true;
@@ -6605,7 +6699,7 @@ function tracedChatCompletionsCreate(original) {
6605
6699
  "neatlogs.llm.model_name": model,
6606
6700
  "neatlogs.llm.is_streaming": isStream
6607
6701
  }
6608
- }, import_api9.context.active());
6702
+ }, import_api10.context.active());
6609
6703
  for (let i = 0; i < messages.length; i++) {
6610
6704
  const msg = messages[i];
6611
6705
  span2.setAttribute(`neatlogs.llm.input_messages.${i}.role`, msg.role ?? "");
@@ -6634,8 +6728,8 @@ function tracedChatCompletionsCreate(original) {
6634
6728
  opts.stream_options = { ...streamOpts, include_usage: true };
6635
6729
  }
6636
6730
  }
6637
- const ctx = import_api9.trace.setSpan(import_api9.context.active(), span2);
6638
- const promise = import_api9.context.with(ctx, () => original(opts, ...rest));
6731
+ const ctx = import_api10.trace.setSpan(import_api10.context.active(), span2);
6732
+ const promise = import_api10.context.with(ctx, () => original(opts, ...rest));
6639
6733
  return promise.then(
6640
6734
  (response) => {
6641
6735
  if (isStream) {
@@ -6653,7 +6747,7 @@ function tracedChatCompletionsCreate(original) {
6653
6747
  }
6654
6748
  function tracedResponsesCreate(original) {
6655
6749
  return function(opts, ...rest) {
6656
- const tracer = import_api9.trace.getTracer(TRACER_NAME3);
6750
+ const tracer = getProviderTracer(TRACER_NAME3);
6657
6751
  const model = opts?.model ?? "";
6658
6752
  const span2 = tracer.startSpan("openai.responses.create", {
6659
6753
  attributes: {
@@ -6663,9 +6757,9 @@ function tracedResponsesCreate(original) {
6663
6757
  "neatlogs.llm.model_name": model,
6664
6758
  "input.value": safeStringify3(opts?.input ?? "")
6665
6759
  }
6666
- }, import_api9.context.active());
6667
- const ctx = import_api9.trace.setSpan(import_api9.context.active(), span2);
6668
- const promise = import_api9.context.with(ctx, () => original(opts, ...rest));
6760
+ }, import_api10.context.active());
6761
+ const ctx = import_api10.trace.setSpan(import_api10.context.active(), span2);
6762
+ const promise = import_api10.context.with(ctx, () => original(opts, ...rest));
6669
6763
  return promise.then(
6670
6764
  (response) => {
6671
6765
  if (response?.output_text) {
@@ -6677,7 +6771,7 @@ function tracedResponsesCreate(original) {
6677
6771
  if (response.usage.input_tokens != null) span2.setAttribute("neatlogs.llm.token_count.prompt", response.usage.input_tokens);
6678
6772
  if (response.usage.output_tokens != null) span2.setAttribute("neatlogs.llm.token_count.completion", response.usage.output_tokens);
6679
6773
  }
6680
- span2.setStatus({ code: import_api9.SpanStatusCode.OK });
6774
+ span2.setStatus({ code: import_api10.SpanStatusCode.OK });
6681
6775
  span2.end();
6682
6776
  return response;
6683
6777
  },
@@ -6692,7 +6786,7 @@ function wrapAsyncIterableStream(stream, span2) {
6692
6786
  const chunks = [];
6693
6787
  const originalAsyncIterator = stream[Symbol.asyncIterator]?.bind(stream);
6694
6788
  if (!originalAsyncIterator) {
6695
- span2.setStatus({ code: import_api9.SpanStatusCode.OK });
6789
+ span2.setStatus({ code: import_api10.SpanStatusCode.OK });
6696
6790
  span2.end();
6697
6791
  return stream;
6698
6792
  }
@@ -6778,7 +6872,7 @@ function finalizeStreamChunks(span2, chunks) {
6778
6872
  span2.setAttribute("neatlogs.llm.token_count.reasoning", usage.completion_tokens_details.reasoning_tokens);
6779
6873
  }
6780
6874
  }
6781
- span2.setStatus({ code: import_api9.SpanStatusCode.OK });
6875
+ span2.setStatus({ code: import_api10.SpanStatusCode.OK });
6782
6876
  span2.end();
6783
6877
  }
6784
6878
  function finalizeChatResponse(span2, response) {
@@ -6815,7 +6909,7 @@ function finalizeChatResponse(span2, response) {
6815
6909
  }
6816
6910
  }
6817
6911
  if (response?.model) span2.setAttribute("neatlogs.llm.model_name", response.model);
6818
- span2.setStatus({ code: import_api9.SpanStatusCode.OK });
6912
+ span2.setStatus({ code: import_api10.SpanStatusCode.OK });
6819
6913
  span2.end();
6820
6914
  }
6821
6915
  function setInvocationParams(span2, opts) {
@@ -6835,23 +6929,23 @@ function safeStringify3(value) {
6835
6929
  }
6836
6930
  function recordError(span2, err) {
6837
6931
  if (err instanceof Error) {
6838
- span2.setStatus({ code: import_api9.SpanStatusCode.ERROR, message: err.message });
6932
+ span2.setStatus({ code: import_api10.SpanStatusCode.ERROR, message: err.message });
6839
6933
  span2.recordException(err);
6840
6934
  } else {
6841
- span2.setStatus({ code: import_api9.SpanStatusCode.ERROR, message: String(err) });
6935
+ span2.setStatus({ code: import_api10.SpanStatusCode.ERROR, message: String(err) });
6842
6936
  }
6843
6937
  span2.end();
6844
6938
  }
6845
6939
 
6846
6940
  // src/anthropic.ts
6847
- var import_api10 = require("@opentelemetry/api");
6941
+ var import_api11 = require("@opentelemetry/api");
6848
6942
  var TRACER_NAME4 = "neatlogs.anthropic";
6849
6943
  function wrapAnthropic(client) {
6850
6944
  return wrapNamespace2(client, []);
6851
6945
  }
6852
6946
  function traceTool2(name, fn) {
6853
6947
  return async function tracedTool(input) {
6854
- const tracer = import_api10.trace.getTracer(TRACER_NAME4);
6948
+ const tracer = getProviderTracer(TRACER_NAME4);
6855
6949
  return tracer.startActiveSpan(
6856
6950
  `tool.${name}`,
6857
6951
  {
@@ -6861,12 +6955,12 @@ function traceTool2(name, fn) {
6861
6955
  "input.value": safeStringify4(input)
6862
6956
  }
6863
6957
  },
6864
- import_api10.context.active(),
6958
+ import_api11.context.active(),
6865
6959
  async (span2) => {
6866
6960
  try {
6867
6961
  const result = await fn(input);
6868
6962
  span2.setAttribute("output.value", safeStringify4(result));
6869
- span2.setStatus({ code: import_api10.SpanStatusCode.OK });
6963
+ span2.setStatus({ code: import_api11.SpanStatusCode.OK });
6870
6964
  return result;
6871
6965
  } catch (err) {
6872
6966
  recordError2(span2, err);
@@ -6905,7 +6999,7 @@ function isNamespace2(path4) {
6905
6999
  }
6906
7000
  function tracedMessagesCreate(original) {
6907
7001
  return function(opts, ...rest) {
6908
- const tracer = import_api10.trace.getTracer(TRACER_NAME4);
7002
+ const tracer = getProviderTracer(TRACER_NAME4);
6909
7003
  const model = opts?.model ?? "";
6910
7004
  const messages = opts?.messages ?? [];
6911
7005
  const isStream = opts?.stream === true;
@@ -6917,12 +7011,12 @@ function tracedMessagesCreate(original) {
6917
7011
  "neatlogs.llm.model_name": model,
6918
7012
  "neatlogs.llm.is_streaming": isStream
6919
7013
  }
6920
- }, import_api10.context.active());
7014
+ }, import_api11.context.active());
6921
7015
  setInputMessages(span2, opts?.system, messages);
6922
7016
  setTools(span2, opts?.tools);
6923
7017
  setInvocationParams2(span2, opts);
6924
- const ctx = import_api10.trace.setSpan(import_api10.context.active(), span2);
6925
- const promise = import_api10.context.with(ctx, () => original(opts, ...rest));
7018
+ const ctx = import_api11.trace.setSpan(import_api11.context.active(), span2);
7019
+ const promise = import_api11.context.with(ctx, () => original(opts, ...rest));
6926
7020
  return promise.then(
6927
7021
  (response) => {
6928
7022
  if (isStream) {
@@ -6940,7 +7034,7 @@ function tracedMessagesCreate(original) {
6940
7034
  }
6941
7035
  function tracedMessagesStream(original) {
6942
7036
  return function(opts, ...rest) {
6943
- const tracer = import_api10.trace.getTracer(TRACER_NAME4);
7037
+ const tracer = getProviderTracer(TRACER_NAME4);
6944
7038
  const model = opts?.model ?? "";
6945
7039
  const messages = opts?.messages ?? [];
6946
7040
  const span2 = tracer.startSpan("anthropic.messages.stream", {
@@ -6951,12 +7045,12 @@ function tracedMessagesStream(original) {
6951
7045
  "neatlogs.llm.model_name": model,
6952
7046
  "neatlogs.llm.is_streaming": true
6953
7047
  }
6954
- }, import_api10.context.active());
7048
+ }, import_api11.context.active());
6955
7049
  setInputMessages(span2, opts?.system, messages);
6956
7050
  setTools(span2, opts?.tools);
6957
7051
  setInvocationParams2(span2, opts);
6958
- const ctx = import_api10.trace.setSpan(import_api10.context.active(), span2);
6959
- const messageStream = import_api10.context.with(ctx, () => original(opts, ...rest));
7052
+ const ctx = import_api11.trace.setSpan(import_api11.context.active(), span2);
7053
+ const messageStream = import_api11.context.with(ctx, () => original(opts, ...rest));
6960
7054
  return wrapMessageStream(messageStream, span2);
6961
7055
  };
6962
7056
  }
@@ -6964,7 +7058,7 @@ function wrapStreamIterable(stream, span2) {
6964
7058
  const events = [];
6965
7059
  const originalAsyncIterator = stream[Symbol.asyncIterator]?.bind(stream);
6966
7060
  if (!originalAsyncIterator) {
6967
- span2.setStatus({ code: import_api10.SpanStatusCode.OK });
7061
+ span2.setStatus({ code: import_api11.SpanStatusCode.OK });
6968
7062
  span2.end();
6969
7063
  return stream;
6970
7064
  }
@@ -7013,7 +7107,7 @@ function wrapMessageStream(messageStream, span2) {
7013
7107
  if (finalMsg) {
7014
7108
  finalizeMessageResponse(span2, finalMsg);
7015
7109
  } else {
7016
- span2.setStatus({ code: import_api10.SpanStatusCode.OK });
7110
+ span2.setStatus({ code: import_api11.SpanStatusCode.OK });
7017
7111
  span2.end();
7018
7112
  }
7019
7113
  });
@@ -7091,7 +7185,7 @@ function finalizeStreamEvents(span2, events) {
7091
7185
  if (model) span2.setAttribute("neatlogs.llm.model_name", model);
7092
7186
  if (stopReason) span2.setAttribute("neatlogs.llm.stop_reason", stopReason);
7093
7187
  setUsageAttrs(span2, usage);
7094
- span2.setStatus({ code: import_api10.SpanStatusCode.OK });
7188
+ span2.setStatus({ code: import_api11.SpanStatusCode.OK });
7095
7189
  span2.end();
7096
7190
  }
7097
7191
  function finalizeMessageResponse(span2, response) {
@@ -7125,7 +7219,7 @@ function finalizeMessageResponse(span2, response) {
7125
7219
  setUsageAttrs(span2, response?.usage);
7126
7220
  if (response?.model) span2.setAttribute("neatlogs.llm.model_name", response.model);
7127
7221
  if (response?.stop_reason) span2.setAttribute("neatlogs.llm.stop_reason", response.stop_reason);
7128
- span2.setStatus({ code: import_api10.SpanStatusCode.OK });
7222
+ span2.setStatus({ code: import_api11.SpanStatusCode.OK });
7129
7223
  span2.end();
7130
7224
  }
7131
7225
  function setInputMessages(span2, system, messages) {
@@ -7178,16 +7272,16 @@ function safeStringify4(value) {
7178
7272
  }
7179
7273
  function recordError2(span2, err) {
7180
7274
  if (err instanceof Error) {
7181
- span2.setStatus({ code: import_api10.SpanStatusCode.ERROR, message: err.message });
7275
+ span2.setStatus({ code: import_api11.SpanStatusCode.ERROR, message: err.message });
7182
7276
  span2.recordException(err);
7183
7277
  } else {
7184
- span2.setStatus({ code: import_api10.SpanStatusCode.ERROR, message: String(err) });
7278
+ span2.setStatus({ code: import_api11.SpanStatusCode.ERROR, message: String(err) });
7185
7279
  }
7186
7280
  span2.end();
7187
7281
  }
7188
7282
 
7189
7283
  // src/azure-openai.ts
7190
- var import_api11 = require("@opentelemetry/api");
7284
+ var import_api12 = require("@opentelemetry/api");
7191
7285
  var TRACER_NAME5 = "neatlogs.azure_openai";
7192
7286
  var PROVIDER = "azure";
7193
7287
  var SYSTEM = "azure";
@@ -7196,7 +7290,7 @@ function wrapAzureOpenAI(client) {
7196
7290
  }
7197
7291
  function traceTool3(name, fn) {
7198
7292
  return async function tracedTool(args) {
7199
- const tracer = import_api11.trace.getTracer(TRACER_NAME5);
7293
+ const tracer = getProviderTracer(TRACER_NAME5);
7200
7294
  return tracer.startActiveSpan(
7201
7295
  `tool.${name}`,
7202
7296
  {
@@ -7206,12 +7300,12 @@ function traceTool3(name, fn) {
7206
7300
  "input.value": safeStringify5(args)
7207
7301
  }
7208
7302
  },
7209
- import_api11.context.active(),
7303
+ import_api12.context.active(),
7210
7304
  async (span2) => {
7211
7305
  try {
7212
7306
  const result = await fn(args);
7213
7307
  span2.setAttribute("output.value", safeStringify5(result));
7214
- span2.setStatus({ code: import_api11.SpanStatusCode.OK });
7308
+ span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7215
7309
  return result;
7216
7310
  } catch (err) {
7217
7311
  recordError3(span2, err);
@@ -7250,7 +7344,7 @@ function isNamespace3(path4) {
7250
7344
  }
7251
7345
  function tracedChatCompletionsCreate2(original) {
7252
7346
  return function(opts, ...rest) {
7253
- const tracer = import_api11.trace.getTracer(TRACER_NAME5);
7347
+ const tracer = getProviderTracer(TRACER_NAME5);
7254
7348
  const model = opts?.model ?? "";
7255
7349
  const messages = opts?.messages ?? [];
7256
7350
  const isStream = opts?.stream === true;
@@ -7262,7 +7356,7 @@ function tracedChatCompletionsCreate2(original) {
7262
7356
  "neatlogs.llm.model_name": model,
7263
7357
  "neatlogs.llm.is_streaming": isStream
7264
7358
  }
7265
- }, import_api11.context.active());
7359
+ }, import_api12.context.active());
7266
7360
  for (let i = 0; i < messages.length; i++) {
7267
7361
  const msg = messages[i];
7268
7362
  span2.setAttribute(`neatlogs.llm.input_messages.${i}.role`, msg.role ?? "");
@@ -7291,8 +7385,8 @@ function tracedChatCompletionsCreate2(original) {
7291
7385
  opts.stream_options = { ...streamOpts, include_usage: true };
7292
7386
  }
7293
7387
  }
7294
- const ctx = import_api11.trace.setSpan(import_api11.context.active(), span2);
7295
- const promise = import_api11.context.with(ctx, () => original(opts, ...rest));
7388
+ const ctx = import_api12.trace.setSpan(import_api12.context.active(), span2);
7389
+ const promise = import_api12.context.with(ctx, () => original(opts, ...rest));
7296
7390
  return promise.then(
7297
7391
  (response) => {
7298
7392
  if (isStream) {
@@ -7310,7 +7404,7 @@ function tracedChatCompletionsCreate2(original) {
7310
7404
  }
7311
7405
  function tracedResponsesCreate2(original) {
7312
7406
  return function(opts, ...rest) {
7313
- const tracer = import_api11.trace.getTracer(TRACER_NAME5);
7407
+ const tracer = getProviderTracer(TRACER_NAME5);
7314
7408
  const model = opts?.model ?? "";
7315
7409
  const span2 = tracer.startSpan("azure_openai.responses.create", {
7316
7410
  attributes: {
@@ -7320,9 +7414,9 @@ function tracedResponsesCreate2(original) {
7320
7414
  "neatlogs.llm.model_name": model,
7321
7415
  "input.value": safeStringify5(opts?.input ?? "")
7322
7416
  }
7323
- }, import_api11.context.active());
7324
- const ctx = import_api11.trace.setSpan(import_api11.context.active(), span2);
7325
- const promise = import_api11.context.with(ctx, () => original(opts, ...rest));
7417
+ }, import_api12.context.active());
7418
+ const ctx = import_api12.trace.setSpan(import_api12.context.active(), span2);
7419
+ const promise = import_api12.context.with(ctx, () => original(opts, ...rest));
7326
7420
  return promise.then(
7327
7421
  (response) => {
7328
7422
  if (response?.output_text) {
@@ -7334,7 +7428,7 @@ function tracedResponsesCreate2(original) {
7334
7428
  if (response.usage.input_tokens != null) span2.setAttribute("neatlogs.llm.token_count.prompt", response.usage.input_tokens);
7335
7429
  if (response.usage.output_tokens != null) span2.setAttribute("neatlogs.llm.token_count.completion", response.usage.output_tokens);
7336
7430
  }
7337
- span2.setStatus({ code: import_api11.SpanStatusCode.OK });
7431
+ span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7338
7432
  span2.end();
7339
7433
  return response;
7340
7434
  },
@@ -7349,7 +7443,7 @@ function wrapAsyncIterableStream2(stream, span2) {
7349
7443
  const chunks = [];
7350
7444
  const originalAsyncIterator = stream?.[Symbol.asyncIterator]?.bind(stream);
7351
7445
  if (!originalAsyncIterator) {
7352
- span2.setStatus({ code: import_api11.SpanStatusCode.OK });
7446
+ span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7353
7447
  span2.end();
7354
7448
  return stream;
7355
7449
  }
@@ -7435,7 +7529,7 @@ function finalizeStreamChunks2(span2, chunks) {
7435
7529
  span2.setAttribute("neatlogs.llm.token_count.reasoning", usage.completion_tokens_details.reasoning_tokens);
7436
7530
  }
7437
7531
  }
7438
- span2.setStatus({ code: import_api11.SpanStatusCode.OK });
7532
+ span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7439
7533
  span2.end();
7440
7534
  }
7441
7535
  function finalizeChatResponse2(span2, response) {
@@ -7472,7 +7566,7 @@ function finalizeChatResponse2(span2, response) {
7472
7566
  }
7473
7567
  }
7474
7568
  if (response?.model) span2.setAttribute("neatlogs.llm.model_name", response.model);
7475
- span2.setStatus({ code: import_api11.SpanStatusCode.OK });
7569
+ span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7476
7570
  span2.end();
7477
7571
  }
7478
7572
  function setInvocationParams3(span2, opts) {
@@ -7492,16 +7586,16 @@ function safeStringify5(value) {
7492
7586
  }
7493
7587
  function recordError3(span2, err) {
7494
7588
  if (err instanceof Error) {
7495
- span2.setStatus({ code: import_api11.SpanStatusCode.ERROR, message: err.message });
7589
+ span2.setStatus({ code: import_api12.SpanStatusCode.ERROR, message: err.message });
7496
7590
  span2.recordException(err);
7497
7591
  } else {
7498
- span2.setStatus({ code: import_api11.SpanStatusCode.ERROR, message: String(err) });
7592
+ span2.setStatus({ code: import_api12.SpanStatusCode.ERROR, message: String(err) });
7499
7593
  }
7500
7594
  span2.end();
7501
7595
  }
7502
7596
 
7503
7597
  // src/vertex-ai.ts
7504
- var import_api12 = require("@opentelemetry/api");
7598
+ var import_api13 = require("@opentelemetry/api");
7505
7599
  var TRACER_NAME6 = "neatlogs.vertex_ai";
7506
7600
  var PROVIDER2 = "vertex_ai";
7507
7601
  var SYSTEM2 = "vertexai";
@@ -7510,7 +7604,7 @@ function wrapVertexAI(client) {
7510
7604
  }
7511
7605
  function traceTool4(name, fn) {
7512
7606
  return async function tracedTool(args) {
7513
- const tracer = import_api12.trace.getTracer(TRACER_NAME6);
7607
+ const tracer = getProviderTracer(TRACER_NAME6);
7514
7608
  return tracer.startActiveSpan(
7515
7609
  `tool.${name}`,
7516
7610
  {
@@ -7520,12 +7614,12 @@ function traceTool4(name, fn) {
7520
7614
  "input.value": safeStringify6(args)
7521
7615
  }
7522
7616
  },
7523
- import_api12.context.active(),
7617
+ import_api13.context.active(),
7524
7618
  async (span2) => {
7525
7619
  try {
7526
7620
  const result = await fn(args);
7527
7621
  span2.setAttribute("output.value", safeStringify6(result));
7528
- span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7622
+ span2.setStatus({ code: import_api13.SpanStatusCode.OK });
7529
7623
  return result;
7530
7624
  } catch (err) {
7531
7625
  recordError4(span2, err);
@@ -7586,7 +7680,7 @@ function wrapVertexAIChat(chat) {
7586
7680
  return chat;
7587
7681
  }
7588
7682
  function tracedChatSend(original, chat, params, rest, isStream) {
7589
- const tracer = import_api12.trace.getTracer(TRACER_NAME6);
7683
+ const tracer = getProviderTracer(TRACER_NAME6);
7590
7684
  const model = chat?.model ?? chat?.modelVersion ?? "";
7591
7685
  const span2 = tracer.startSpan("vertex_ai.chat.send_message", {
7592
7686
  attributes: {
@@ -7596,15 +7690,15 @@ function tracedChatSend(original, chat, params, rest, isStream) {
7596
7690
  "neatlogs.llm.model_name": model,
7597
7691
  "neatlogs.llm.is_streaming": isStream
7598
7692
  }
7599
- }, import_api12.context.active());
7693
+ }, import_api13.context.active());
7600
7694
  const message = params?.message ?? params;
7601
7695
  span2.setAttribute("neatlogs.llm.input_messages.0.role", "user");
7602
7696
  span2.setAttribute(
7603
7697
  "neatlogs.llm.input_messages.0.content",
7604
7698
  typeof message === "string" ? message : safeStringify6(message)
7605
7699
  );
7606
- const ctx = import_api12.trace.setSpan(import_api12.context.active(), span2);
7607
- const result = import_api12.context.with(ctx, () => original(params, ...rest));
7700
+ const ctx = import_api13.trace.setSpan(import_api13.context.active(), span2);
7701
+ const result = import_api13.context.with(ctx, () => original(params, ...rest));
7608
7702
  return Promise.resolve(result).then(
7609
7703
  (response) => {
7610
7704
  if (isStream) return wrapStream(response, span2);
@@ -7619,7 +7713,7 @@ function tracedChatSend(original, chat, params, rest, isStream) {
7619
7713
  }
7620
7714
  function tracedEmbedContent(original) {
7621
7715
  return function(opts, ...rest) {
7622
- const tracer = import_api12.trace.getTracer(TRACER_NAME6);
7716
+ const tracer = getProviderTracer(TRACER_NAME6);
7623
7717
  const span2 = tracer.startSpan("vertex_ai.models.embed_content", {
7624
7718
  attributes: {
7625
7719
  "neatlogs.span.kind": "EMBEDDING",
@@ -7627,9 +7721,9 @@ function tracedEmbedContent(original) {
7627
7721
  "neatlogs.embedding.model_name": opts?.model ?? "",
7628
7722
  "neatlogs.embedding.text": safeStringify6(opts?.contents ?? "")
7629
7723
  }
7630
- }, import_api12.context.active());
7631
- const ctx = import_api12.trace.setSpan(import_api12.context.active(), span2);
7632
- const result = import_api12.context.with(ctx, () => original(opts, ...rest));
7724
+ }, import_api13.context.active());
7725
+ const ctx = import_api13.trace.setSpan(import_api13.context.active(), span2);
7726
+ const result = import_api13.context.with(ctx, () => original(opts, ...rest));
7633
7727
  return Promise.resolve(result).then(
7634
7728
  (response) => {
7635
7729
  const embeddings = response?.embeddings;
@@ -7638,7 +7732,7 @@ function tracedEmbedContent(original) {
7638
7732
  const vals = embeddings[0]?.values;
7639
7733
  if (Array.isArray(vals)) span2.setAttribute("neatlogs.embedding.dimensions", vals.length);
7640
7734
  }
7641
- span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7735
+ span2.setStatus({ code: import_api13.SpanStatusCode.OK });
7642
7736
  span2.end();
7643
7737
  return response;
7644
7738
  },
@@ -7651,7 +7745,7 @@ function tracedEmbedContent(original) {
7651
7745
  }
7652
7746
  function tracedCountTokens(original) {
7653
7747
  return function(opts, ...rest) {
7654
- const tracer = import_api12.trace.getTracer(TRACER_NAME6);
7748
+ const tracer = getProviderTracer(TRACER_NAME6);
7655
7749
  const span2 = tracer.startSpan("vertex_ai.models.count_tokens", {
7656
7750
  attributes: {
7657
7751
  "neatlogs.span.kind": "LLM",
@@ -7659,13 +7753,13 @@ function tracedCountTokens(original) {
7659
7753
  "neatlogs.llm.task": "count_tokens",
7660
7754
  "neatlogs.llm.model_name": opts?.model ?? ""
7661
7755
  }
7662
- }, import_api12.context.active());
7663
- const ctx = import_api12.trace.setSpan(import_api12.context.active(), span2);
7664
- const result = import_api12.context.with(ctx, () => original(opts, ...rest));
7756
+ }, import_api13.context.active());
7757
+ const ctx = import_api13.trace.setSpan(import_api13.context.active(), span2);
7758
+ const result = import_api13.context.with(ctx, () => original(opts, ...rest));
7665
7759
  return Promise.resolve(result).then(
7666
7760
  (response) => {
7667
7761
  if (response?.totalTokens != null) span2.setAttribute("neatlogs.llm.token_count.prompt", response.totalTokens);
7668
- span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7762
+ span2.setStatus({ code: import_api13.SpanStatusCode.OK });
7669
7763
  span2.end();
7670
7764
  return response;
7671
7765
  },
@@ -7678,7 +7772,7 @@ function tracedCountTokens(original) {
7678
7772
  }
7679
7773
  function tracedGenerateContent(original, isStream) {
7680
7774
  return function(opts, ...rest) {
7681
- const tracer = import_api12.trace.getTracer(TRACER_NAME6);
7775
+ const tracer = getProviderTracer(TRACER_NAME6);
7682
7776
  const model = opts?.model ?? "";
7683
7777
  const span2 = tracer.startSpan("vertex_ai.models.generate_content", {
7684
7778
  attributes: {
@@ -7688,10 +7782,10 @@ function tracedGenerateContent(original, isStream) {
7688
7782
  "neatlogs.llm.model_name": model,
7689
7783
  "neatlogs.llm.is_streaming": isStream
7690
7784
  }
7691
- }, import_api12.context.active());
7785
+ }, import_api13.context.active());
7692
7786
  setInputAttributes(span2, opts);
7693
- const ctx = import_api12.trace.setSpan(import_api12.context.active(), span2);
7694
- const result = import_api12.context.with(ctx, () => original(opts, ...rest));
7787
+ const ctx = import_api13.trace.setSpan(import_api13.context.active(), span2);
7788
+ const result = import_api13.context.with(ctx, () => original(opts, ...rest));
7695
7789
  return Promise.resolve(result).then(
7696
7790
  (response) => {
7697
7791
  if (isStream) {
@@ -7838,7 +7932,7 @@ function finalizeStreamChunks3(span2, chunks) {
7838
7932
  }
7839
7933
  if (finishReason) span2.setAttribute("neatlogs.llm.finish_reason", String(finishReason));
7840
7934
  setUsage(span2, usage);
7841
- span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7935
+ span2.setStatus({ code: import_api13.SpanStatusCode.OK });
7842
7936
  span2.end();
7843
7937
  }
7844
7938
  function finalizeResponse(span2, response) {
@@ -7864,7 +7958,7 @@ function finalizeResponse(span2, response) {
7864
7958
  span2.setAttribute("neatlogs.llm.output_messages.0.content", textParts.join(""));
7865
7959
  }
7866
7960
  setUsage(span2, response?.usageMetadata);
7867
- span2.setStatus({ code: import_api12.SpanStatusCode.OK });
7961
+ span2.setStatus({ code: import_api13.SpanStatusCode.OK });
7868
7962
  span2.end();
7869
7963
  }
7870
7964
  function setUsage(span2, usage) {
@@ -7884,21 +7978,21 @@ function safeStringify6(value) {
7884
7978
  }
7885
7979
  function recordError4(span2, err) {
7886
7980
  if (err instanceof Error) {
7887
- span2.setStatus({ code: import_api12.SpanStatusCode.ERROR, message: err.message });
7981
+ span2.setStatus({ code: import_api13.SpanStatusCode.ERROR, message: err.message });
7888
7982
  span2.recordException(err);
7889
7983
  } else {
7890
- span2.setStatus({ code: import_api12.SpanStatusCode.ERROR, message: String(err) });
7984
+ span2.setStatus({ code: import_api13.SpanStatusCode.ERROR, message: String(err) });
7891
7985
  }
7892
7986
  span2.end();
7893
7987
  }
7894
7988
 
7895
7989
  // src/bedrock.ts
7896
- var import_api13 = require("@opentelemetry/api");
7990
+ var import_api14 = require("@opentelemetry/api");
7897
7991
  var TRACER_NAME7 = "neatlogs.bedrock";
7898
7992
  var PROVIDER3 = "bedrock";
7899
7993
  function traceTool5(name, fn) {
7900
7994
  return async function tracedTool(args) {
7901
- const tracer = import_api13.trace.getTracer(TRACER_NAME7);
7995
+ const tracer = getProviderTracer(TRACER_NAME7);
7902
7996
  return tracer.startActiveSpan(
7903
7997
  `tool.${name}`,
7904
7998
  {
@@ -7908,12 +8002,12 @@ function traceTool5(name, fn) {
7908
8002
  "input.value": safeStringify7(args)
7909
8003
  }
7910
8004
  },
7911
- import_api13.context.active(),
8005
+ import_api14.context.active(),
7912
8006
  async (span2) => {
7913
8007
  try {
7914
8008
  const result = await fn(args);
7915
8009
  span2.setAttribute("output.value", safeStringify7(result));
7916
- span2.setStatus({ code: import_api13.SpanStatusCode.OK });
8010
+ span2.setStatus({ code: import_api14.SpanStatusCode.OK });
7917
8011
  return result;
7918
8012
  } catch (err) {
7919
8013
  recordError5(span2, err);
@@ -7959,7 +8053,7 @@ function vendorFromModel(modelId) {
7959
8053
  return vendor || "bedrock";
7960
8054
  }
7961
8055
  function startSpan(name, modelId, isStream) {
7962
- return import_api13.trace.getTracer(TRACER_NAME7).startSpan(name, {
8056
+ return getProviderTracer(TRACER_NAME7).startSpan(name, {
7963
8057
  attributes: {
7964
8058
  "neatlogs.span.kind": "LLM",
7965
8059
  "neatlogs.llm.provider": PROVIDER3,
@@ -7967,7 +8061,7 @@ function startSpan(name, modelId, isStream) {
7967
8061
  "neatlogs.llm.model_name": String(modelId ?? ""),
7968
8062
  "neatlogs.llm.is_streaming": isStream
7969
8063
  }
7970
- }, import_api13.context.active());
8064
+ }, import_api14.context.active());
7971
8065
  }
7972
8066
  function tracedConverse(originalSend, command, input, rest, isStream) {
7973
8067
  const span2 = startSpan(
@@ -7976,8 +8070,8 @@ function tracedConverse(originalSend, command, input, rest, isStream) {
7976
8070
  isStream
7977
8071
  );
7978
8072
  setConverseInput(span2, input);
7979
- const ctx = import_api13.trace.setSpan(import_api13.context.active(), span2);
7980
- const promise = import_api13.context.with(ctx, () => originalSend(command, ...rest));
8073
+ const ctx = import_api14.trace.setSpan(import_api14.context.active(), span2);
8074
+ const promise = import_api14.context.with(ctx, () => originalSend(command, ...rest));
7981
8075
  return promise.then(
7982
8076
  (response) => {
7983
8077
  if (isStream) {
@@ -8057,7 +8151,7 @@ function finalizeConverse(span2, response) {
8057
8151
  }
8058
8152
  if (response?.stopReason) span2.setAttribute("neatlogs.llm.finish_reason", String(response.stopReason));
8059
8153
  setConverseUsage(span2, response?.usage);
8060
- span2.setStatus({ code: import_api13.SpanStatusCode.OK });
8154
+ span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8061
8155
  span2.end();
8062
8156
  }
8063
8157
  function setConverseUsage(span2, usage) {
@@ -8071,7 +8165,7 @@ function setConverseUsage(span2, usage) {
8071
8165
  function wrapConverseStream(response, span2) {
8072
8166
  const stream = response?.stream;
8073
8167
  if (!stream || typeof stream[Symbol.asyncIterator] !== "function") {
8074
- span2.setStatus({ code: import_api13.SpanStatusCode.OK });
8168
+ span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8075
8169
  span2.end();
8076
8170
  return response;
8077
8171
  }
@@ -8139,7 +8233,7 @@ function finalizeConverseStream(span2, textParts, toolCalls, finishReason, usage
8139
8233
  }
8140
8234
  if (finishReason) span2.setAttribute("neatlogs.llm.finish_reason", String(finishReason));
8141
8235
  setConverseUsage(span2, usage);
8142
- span2.setStatus({ code: import_api13.SpanStatusCode.OK });
8236
+ span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8143
8237
  span2.end();
8144
8238
  }
8145
8239
  function isEmbeddingModel(modelId) {
@@ -8151,13 +8245,13 @@ function tracedInvokeModel(originalSend, command, input, rest, isStream) {
8151
8245
  const bodyIn = decodeBody(input?.body);
8152
8246
  let span2;
8153
8247
  if (isEmbedding) {
8154
- span2 = import_api13.trace.getTracer(TRACER_NAME7).startSpan("bedrock.invoke_model", {
8248
+ span2 = getProviderTracer(TRACER_NAME7).startSpan("bedrock.invoke_model", {
8155
8249
  attributes: {
8156
8250
  "neatlogs.span.kind": "EMBEDDING",
8157
8251
  "neatlogs.llm.provider": PROVIDER3,
8158
8252
  "neatlogs.embedding.model_name": String(input?.modelId ?? "")
8159
8253
  }
8160
- }, import_api13.context.active());
8254
+ }, import_api14.context.active());
8161
8255
  const text = bodyIn?.inputText ?? bodyIn?.texts ?? bodyIn?.input_text;
8162
8256
  if (text) {
8163
8257
  span2.setAttribute("neatlogs.embedding.text", typeof text === "string" ? text : safeStringify7(text));
@@ -8170,8 +8264,8 @@ function tracedInvokeModel(originalSend, command, input, rest, isStream) {
8170
8264
  );
8171
8265
  setInvokeInput(span2, vendor, bodyIn);
8172
8266
  }
8173
- const ctx = import_api13.trace.setSpan(import_api13.context.active(), span2);
8174
- const promise = import_api13.context.with(ctx, () => originalSend(command, ...rest));
8267
+ const ctx = import_api14.trace.setSpan(import_api14.context.active(), span2);
8268
+ const promise = import_api14.context.with(ctx, () => originalSend(command, ...rest));
8175
8269
  return promise.then(
8176
8270
  (response) => {
8177
8271
  if (isStream) {
@@ -8184,7 +8278,7 @@ function tracedInvokeModel(originalSend, command, input, rest, isStream) {
8184
8278
  finalizeInvoke(span2, vendor, decodeBody(response?.body));
8185
8279
  }
8186
8280
  } catch {
8187
- span2.setStatus({ code: import_api13.SpanStatusCode.OK });
8281
+ span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8188
8282
  span2.end();
8189
8283
  }
8190
8284
  return response;
@@ -8209,7 +8303,7 @@ function finalizeInvokeEmbedding(span2, body) {
8209
8303
  span2.setAttribute("neatlogs.llm.token_count.prompt", body.inputTextTokenCount);
8210
8304
  span2.setAttribute("neatlogs.embedding.token_count", body.inputTextTokenCount);
8211
8305
  }
8212
- span2.setStatus({ code: import_api13.SpanStatusCode.OK });
8306
+ span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8213
8307
  span2.end();
8214
8308
  }
8215
8309
  function decodeBody(body) {
@@ -8317,13 +8411,13 @@ function finalizeInvoke(span2, vendor, body) {
8317
8411
  span2.setAttribute("neatlogs.llm.token_count.total", promptTokens + completionTokens);
8318
8412
  }
8319
8413
  if (finishReason) span2.setAttribute("neatlogs.llm.finish_reason", String(finishReason));
8320
- span2.setStatus({ code: import_api13.SpanStatusCode.OK });
8414
+ span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8321
8415
  span2.end();
8322
8416
  }
8323
8417
  function wrapInvokeStream(response, span2, vendor) {
8324
8418
  const body = response?.body;
8325
8419
  if (!body || typeof body[Symbol.asyncIterator] !== "function") {
8326
- span2.setStatus({ code: import_api13.SpanStatusCode.OK });
8420
+ span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8327
8421
  span2.end();
8328
8422
  return response;
8329
8423
  }
@@ -8341,7 +8435,7 @@ function wrapInvokeStream(response, span2, vendor) {
8341
8435
  if (promptTokens != null) span2.setAttribute("neatlogs.llm.token_count.prompt", promptTokens);
8342
8436
  if (completionTokens != null) span2.setAttribute("neatlogs.llm.token_count.completion", completionTokens);
8343
8437
  if (finishReason) span2.setAttribute("neatlogs.llm.finish_reason", String(finishReason));
8344
- span2.setStatus({ code: import_api13.SpanStatusCode.OK });
8438
+ span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8345
8439
  span2.end();
8346
8440
  };
8347
8441
  const wrappedBody = {
@@ -8394,16 +8488,16 @@ function safeStringify7(value) {
8394
8488
  }
8395
8489
  function recordError5(span2, err) {
8396
8490
  if (err instanceof Error) {
8397
- span2.setStatus({ code: import_api13.SpanStatusCode.ERROR, message: err.message });
8491
+ span2.setStatus({ code: import_api14.SpanStatusCode.ERROR, message: err.message });
8398
8492
  span2.recordException(err);
8399
8493
  } else {
8400
- span2.setStatus({ code: import_api13.SpanStatusCode.ERROR, message: String(err) });
8494
+ span2.setStatus({ code: import_api14.SpanStatusCode.ERROR, message: String(err) });
8401
8495
  }
8402
8496
  span2.end();
8403
8497
  }
8404
8498
 
8405
8499
  // src/langchain.ts
8406
- var import_api14 = require("@opentelemetry/api");
8500
+ var import_api15 = require("@opentelemetry/api");
8407
8501
  var TRACER_NAME8 = "neatlogs.langchain";
8408
8502
  function langchainHandler(opts) {
8409
8503
  return new NeatlogsCallbackHandler(opts);
@@ -8411,16 +8505,42 @@ function langchainHandler(opts) {
8411
8505
  var NeatlogsCallbackHandler = class {
8412
8506
  name = "neatlogs";
8413
8507
  _spans = /* @__PURE__ */ new Map();
8508
+ // Auto-root spans keyed by the run-id of the child that triggered them.
8509
+ _autoRoots = /* @__PURE__ */ new Map();
8414
8510
  _workflowName;
8415
8511
  constructor(opts) {
8416
8512
  this._workflowName = opts?.workflowName;
8417
8513
  }
8514
+ // --- Self-rooting ----------------------------------------------------------
8515
+ //
8516
+ // A LangChain run can begin with ANY callback. When it begins with a non-root
8517
+ // kind (a bare `llm.invoke()` fires handleChatModelStart/handleLLMStart with no
8518
+ // chain above it; a standalone tool/retriever similarly), the span we create is
8519
+ // parentless and non-root -> the backend can't anchor the trace and it never
8520
+ // renders. Open a WORKFLOW root transparently in that case so just adding the
8521
+ // handler yields a complete trace. A top-level CHAIN is already root-eligible.
8522
+ _startCtx(parentRunId, runId, kind) {
8523
+ const parentSpan = parentRunId ? this._spans.get(parentRunId) : void 0;
8524
+ if (parentSpan) {
8525
+ return import_api15.trace.setSpan(import_api15.context.active(), parentSpan);
8526
+ }
8527
+ const tracer = import_api15.trace.getTracer(TRACER_NAME8);
8528
+ const { root, ctx } = maybeOpenAutoRoot(tracer, kind, import_api15.context.active());
8529
+ if (root) this._autoRoots.set(runId, root);
8530
+ return ctx;
8531
+ }
8532
+ _endAutoRoot(runId) {
8533
+ const root = this._autoRoots.get(runId);
8534
+ if (root) {
8535
+ endAutoRoot(root);
8536
+ this._autoRoots.delete(runId);
8537
+ }
8538
+ }
8418
8539
  // --- Chain/Graph callbacks ---
8419
8540
  async handleChainStart(serialized, inputs, runId, parentRunId, tags, metadata) {
8420
- const tracer = import_api14.trace.getTracer(TRACER_NAME8);
8541
+ const tracer = import_api15.trace.getTracer(TRACER_NAME8);
8421
8542
  const name = serialized?.name ?? serialized?.id?.at(-1) ?? "chain";
8422
- const parentSpan = parentRunId ? this._spans.get(parentRunId) : void 0;
8423
- const parentCtx = parentSpan ? import_api14.trace.setSpan(import_api14.context.active(), parentSpan) : import_api14.context.active();
8543
+ const parentCtx = this._startCtx(parentRunId, runId, "chain");
8424
8544
  const attrs = {
8425
8545
  "neatlogs.span.kind": "CHAIN",
8426
8546
  "neatlogs.chain.name": name
@@ -8435,24 +8555,25 @@ var NeatlogsCallbackHandler = class {
8435
8555
  const span2 = this._spans.get(runId);
8436
8556
  if (!span2) return;
8437
8557
  if (outputs) span2.setAttribute("output.value", safeStringify8(outputs));
8438
- span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8558
+ span2.setStatus({ code: import_api15.SpanStatusCode.OK });
8439
8559
  span2.end();
8440
8560
  this._spans.delete(runId);
8561
+ this._endAutoRoot(runId);
8441
8562
  }
8442
8563
  async handleChainError(error, runId) {
8443
8564
  const span2 = this._spans.get(runId);
8444
8565
  if (!span2) return;
8445
- span2.setStatus({ code: import_api14.SpanStatusCode.ERROR, message: error.message });
8566
+ span2.setStatus({ code: import_api15.SpanStatusCode.ERROR, message: error.message });
8446
8567
  span2.recordException(error);
8447
8568
  span2.end();
8448
8569
  this._spans.delete(runId);
8570
+ this._endAutoRoot(runId);
8449
8571
  }
8450
8572
  // --- LLM callbacks ---
8451
8573
  async handleLLMStart(serialized, prompts, runId, parentRunId, extraParams) {
8452
- const tracer = import_api14.trace.getTracer(TRACER_NAME8);
8574
+ const tracer = import_api15.trace.getTracer(TRACER_NAME8);
8453
8575
  const model = serialized?.kwargs?.model_name ?? serialized?.kwargs?.model ?? serialized?.name ?? "";
8454
- const parentSpan = parentRunId ? this._spans.get(parentRunId) : void 0;
8455
- const parentCtx = parentSpan ? import_api14.trace.setSpan(import_api14.context.active(), parentSpan) : import_api14.context.active();
8576
+ const parentCtx = this._startCtx(parentRunId, runId, "llm");
8456
8577
  const attrs = {
8457
8578
  "neatlogs.span.kind": "LLM",
8458
8579
  "neatlogs.llm.provider": detectProvider(model),
@@ -8472,10 +8593,9 @@ var NeatlogsCallbackHandler = class {
8472
8593
  this._spans.set(runId, span2);
8473
8594
  }
8474
8595
  async handleChatModelStart(serialized, messages, runId, parentRunId, extraParams) {
8475
- const tracer = import_api14.trace.getTracer(TRACER_NAME8);
8596
+ const tracer = import_api15.trace.getTracer(TRACER_NAME8);
8476
8597
  const model = serialized?.kwargs?.model_name ?? serialized?.kwargs?.model ?? serialized?.name ?? "";
8477
- const parentSpan = parentRunId ? this._spans.get(parentRunId) : void 0;
8478
- const parentCtx = parentSpan ? import_api14.trace.setSpan(import_api14.context.active(), parentSpan) : import_api14.context.active();
8598
+ const parentCtx = this._startCtx(parentRunId, runId, "llm");
8479
8599
  const attrs = {
8480
8600
  "neatlogs.span.kind": "LLM",
8481
8601
  "neatlogs.llm.provider": detectProvider(model),
@@ -8533,26 +8653,27 @@ var NeatlogsCallbackHandler = class {
8533
8653
  span2.setAttribute("neatlogs.llm.token_count.total", usage.totalTokens ?? usage.total_tokens);
8534
8654
  }
8535
8655
  }
8536
- span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8656
+ span2.setStatus({ code: import_api15.SpanStatusCode.OK });
8537
8657
  span2.end();
8538
8658
  this._spans.delete(runId);
8659
+ this._endAutoRoot(runId);
8539
8660
  }
8540
8661
  async handleLLMNewToken(_token, _idx, _runId) {
8541
8662
  }
8542
8663
  async handleLLMError(error, runId) {
8543
8664
  const span2 = this._spans.get(runId);
8544
8665
  if (!span2) return;
8545
- span2.setStatus({ code: import_api14.SpanStatusCode.ERROR, message: error.message });
8666
+ span2.setStatus({ code: import_api15.SpanStatusCode.ERROR, message: error.message });
8546
8667
  span2.recordException(error);
8547
8668
  span2.end();
8548
8669
  this._spans.delete(runId);
8670
+ this._endAutoRoot(runId);
8549
8671
  }
8550
8672
  // --- Tool callbacks ---
8551
8673
  async handleToolStart(serialized, input, runId, parentRunId, _tags, _metadata, runName, toolCallId) {
8552
- const tracer = import_api14.trace.getTracer(TRACER_NAME8);
8674
+ const tracer = import_api15.trace.getTracer(TRACER_NAME8);
8553
8675
  const name = serialized?.name || runName || (Array.isArray(serialized?.id) ? serialized.id[serialized.id.length - 1] : void 0) || "tool";
8554
- const parentSpan = parentRunId ? this._spans.get(parentRunId) : void 0;
8555
- const parentCtx = parentSpan ? import_api14.trace.setSpan(import_api14.context.active(), parentSpan) : import_api14.context.active();
8676
+ const parentCtx = this._startCtx(parentRunId, runId, "tool");
8556
8677
  const attrs = {
8557
8678
  "neatlogs.span.kind": "TOOL",
8558
8679
  "neatlogs.tool.name": name,
@@ -8566,24 +8687,25 @@ var NeatlogsCallbackHandler = class {
8566
8687
  const span2 = this._spans.get(runId);
8567
8688
  if (!span2) return;
8568
8689
  span2.setAttribute("output.value", String(output));
8569
- span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8690
+ span2.setStatus({ code: import_api15.SpanStatusCode.OK });
8570
8691
  span2.end();
8571
8692
  this._spans.delete(runId);
8693
+ this._endAutoRoot(runId);
8572
8694
  }
8573
8695
  async handleToolError(error, runId) {
8574
8696
  const span2 = this._spans.get(runId);
8575
8697
  if (!span2) return;
8576
- span2.setStatus({ code: import_api14.SpanStatusCode.ERROR, message: error.message });
8698
+ span2.setStatus({ code: import_api15.SpanStatusCode.ERROR, message: error.message });
8577
8699
  span2.recordException(error);
8578
8700
  span2.end();
8579
8701
  this._spans.delete(runId);
8702
+ this._endAutoRoot(runId);
8580
8703
  }
8581
8704
  // --- Retriever callbacks ---
8582
8705
  async handleRetrieverStart(serialized, query, runId, parentRunId) {
8583
- const tracer = import_api14.trace.getTracer(TRACER_NAME8);
8706
+ const tracer = import_api15.trace.getTracer(TRACER_NAME8);
8584
8707
  const name = serialized?.name ?? "retriever";
8585
- const parentSpan = parentRunId ? this._spans.get(parentRunId) : void 0;
8586
- const parentCtx = parentSpan ? import_api14.trace.setSpan(import_api14.context.active(), parentSpan) : import_api14.context.active();
8708
+ const parentCtx = this._startCtx(parentRunId, runId, "retriever");
8587
8709
  const attrs = {
8588
8710
  "neatlogs.span.kind": "RETRIEVER",
8589
8711
  "neatlogs.retriever.name": name,
@@ -8604,17 +8726,19 @@ var NeatlogsCallbackHandler = class {
8604
8726
  }
8605
8727
  }
8606
8728
  }
8607
- span2.setStatus({ code: import_api14.SpanStatusCode.OK });
8729
+ span2.setStatus({ code: import_api15.SpanStatusCode.OK });
8608
8730
  span2.end();
8609
8731
  this._spans.delete(runId);
8732
+ this._endAutoRoot(runId);
8610
8733
  }
8611
8734
  async handleRetrieverError(error, runId) {
8612
8735
  const span2 = this._spans.get(runId);
8613
8736
  if (!span2) return;
8614
- span2.setStatus({ code: import_api14.SpanStatusCode.ERROR, message: error.message });
8737
+ span2.setStatus({ code: import_api15.SpanStatusCode.ERROR, message: error.message });
8615
8738
  span2.recordException(error);
8616
8739
  span2.end();
8617
8740
  this._spans.delete(runId);
8741
+ this._endAutoRoot(runId);
8618
8742
  }
8619
8743
  };
8620
8744
  function detectProvider(model) {
@@ -8783,7 +8907,7 @@ function safeStringify9(value) {
8783
8907
  }
8784
8908
 
8785
8909
  // src/openai-agents.ts
8786
- var import_api15 = require("@opentelemetry/api");
8910
+ var import_api16 = require("@opentelemetry/api");
8787
8911
  var TRACER_NAME9 = "neatlogs.openai_agents";
8788
8912
  function openaiAgentsProcessor() {
8789
8913
  return new NeatlogsTraceProcessor();
@@ -8793,13 +8917,13 @@ var NeatlogsTraceProcessor = class {
8793
8917
  _startTimes = /* @__PURE__ */ new Map();
8794
8918
  // The @openai/agents SDK passes a Trace object: { traceId, name, groupId, metadata }.
8795
8919
  onTraceStart(traceData) {
8796
- const tracer = import_api15.trace.getTracer(TRACER_NAME9);
8920
+ const tracer = import_api16.trace.getTracer(TRACER_NAME9);
8797
8921
  const attrs = { "neatlogs.span.kind": "WORKFLOW" };
8798
8922
  const workflowName = traceData?.name ?? traceData?.workflow_name;
8799
8923
  if (workflowName) attrs["neatlogs.workflow.name"] = workflowName;
8800
8924
  const traceId = traceData?.traceId ?? traceData?.trace_id;
8801
8925
  if (traceId) attrs["neatlogs.agent.trace_id"] = String(traceId);
8802
- const span2 = tracer.startSpan("openai_agents.trace", { attributes: attrs }, import_api15.context.active());
8926
+ const span2 = tracer.startSpan("openai_agents.trace", { attributes: attrs }, import_api16.context.active());
8803
8927
  const key = String(traceId ?? `trace_${Date.now()}`);
8804
8928
  this._spans.set(key, span2);
8805
8929
  this._startTimes.set(key, Date.now());
@@ -8812,7 +8936,7 @@ var NeatlogsTraceProcessor = class {
8812
8936
  if (startTime) {
8813
8937
  span2.setAttribute("neatlogs.metrics.duration_ms", Date.now() - startTime);
8814
8938
  }
8815
- span2.setStatus({ code: import_api15.SpanStatusCode.OK });
8939
+ span2.setStatus({ code: import_api16.SpanStatusCode.OK });
8816
8940
  span2.end();
8817
8941
  this._spans.delete(key);
8818
8942
  this._startTimes.delete(key);
@@ -8820,13 +8944,13 @@ var NeatlogsTraceProcessor = class {
8820
8944
  // The SDK passes a Span object: { type, spanId, traceId, parentId?, spanData: {...} }.
8821
8945
  // The meaningful payload (type, name, input, output, usage, ...) lives in `spanData`.
8822
8946
  onSpanStart(span2) {
8823
- const tracer = import_api15.trace.getTracer(TRACER_NAME9);
8947
+ const tracer = import_api16.trace.getTracer(TRACER_NAME9);
8824
8948
  const data = span2?.spanData ?? span2 ?? {};
8825
8949
  const spanType = data?.type ?? data?.span_type ?? span2?.type ?? "";
8826
8950
  const spanId = String(span2?.spanId ?? span2?.span_id ?? data?.span_id ?? `span_${Date.now()}`);
8827
8951
  const parentKey = String(span2?.parentId ?? span2?.traceId ?? span2?.trace_id ?? "");
8828
8952
  const parentSpan = this._spans.get(parentKey);
8829
- const parentCtx = parentSpan ? import_api15.trace.setSpan(import_api15.context.active(), parentSpan) : import_api15.context.active();
8953
+ const parentCtx = parentSpan ? import_api16.trace.setSpan(import_api16.context.active(), parentSpan) : import_api16.context.active();
8830
8954
  let otelSpan;
8831
8955
  if (spanType === "agent" || spanType === "agent_run") {
8832
8956
  const agentName = data?.name ?? data?.agent_name ?? "agent";
@@ -8935,13 +9059,13 @@ var NeatlogsTraceProcessor = class {
8935
9059
  const error = data?.error ?? span2?.error;
8936
9060
  if (error) {
8937
9061
  if (error instanceof Error) {
8938
- otelSpan.setStatus({ code: import_api15.SpanStatusCode.ERROR, message: error.message });
9062
+ otelSpan.setStatus({ code: import_api16.SpanStatusCode.ERROR, message: error.message });
8939
9063
  otelSpan.recordException(error);
8940
9064
  } else {
8941
- otelSpan.setStatus({ code: import_api15.SpanStatusCode.ERROR, message: String(error) });
9065
+ otelSpan.setStatus({ code: import_api16.SpanStatusCode.ERROR, message: String(error) });
8942
9066
  }
8943
9067
  } else {
8944
- otelSpan.setStatus({ code: import_api15.SpanStatusCode.OK });
9068
+ otelSpan.setStatus({ code: import_api16.SpanStatusCode.OK });
8945
9069
  }
8946
9070
  if (startTime) {
8947
9071
  otelSpan.setAttribute("neatlogs.llm.metrics.duration_ms", Date.now() - startTime);
@@ -8952,7 +9076,7 @@ var NeatlogsTraceProcessor = class {
8952
9076
  }
8953
9077
  shutdown() {
8954
9078
  for (const [, span2] of this._spans) {
8955
- span2.setStatus({ code: import_api15.SpanStatusCode.ERROR, message: "Processor shutdown before span completed" });
9079
+ span2.setStatus({ code: import_api16.SpanStatusCode.ERROR, message: "Processor shutdown before span completed" });
8956
9080
  span2.end();
8957
9081
  }
8958
9082
  this._spans.clear();
@@ -8970,7 +9094,7 @@ function safeStringify10(value) {
8970
9094
  }
8971
9095
 
8972
9096
  // src/mastra-wrap.ts
8973
- var import_api16 = require("@opentelemetry/api");
9097
+ var import_api17 = require("@opentelemetry/api");
8974
9098
  var TRACER_NAME10 = "neatlogs.mastra";
8975
9099
  var PATCH_FLAG2 = "_neatlogs_patched";
8976
9100
  function wrapMastra(entity) {
@@ -9037,11 +9161,11 @@ function patchAgentMethod(agent, method, streaming) {
9037
9161
  }
9038
9162
  if (streaming) attrs["neatlogs.llm.is_streaming"] = true;
9039
9163
  if (streaming) {
9040
- const tracer = import_api16.trace.getTracer(TRACER_NAME10);
9041
- const span2 = tracer.startSpan(`mastra.agent.${agentName}`, { attributes: attrs }, import_api16.context.active());
9042
- const ctx = import_api16.trace.setSpan(import_api16.context.active(), span2);
9164
+ const tracer = import_api17.trace.getTracer(TRACER_NAME10);
9165
+ const span2 = tracer.startSpan(`mastra.agent.${agentName}`, { attributes: attrs }, import_api17.context.active());
9166
+ const ctx = import_api17.trace.setSpan(import_api17.context.active(), span2);
9043
9167
  try {
9044
- const result = await import_api16.context.with(ctx, () => orig(input, opts));
9168
+ const result = await import_api17.context.with(ctx, () => orig(input, opts));
9045
9169
  return wrapStreamingOutput(result, span2, ctx);
9046
9170
  } catch (err) {
9047
9171
  recordError6(span2, err);
@@ -9245,7 +9369,7 @@ function wrapRootMastra(mastra) {
9245
9369
  }
9246
9370
  function wrapStreamingOutput(output, span2, ctx) {
9247
9371
  if (!output) {
9248
- span2.setStatus({ code: import_api16.SpanStatusCode.OK });
9372
+ span2.setStatus({ code: import_api17.SpanStatusCode.OK });
9249
9373
  span2.end();
9250
9374
  return output;
9251
9375
  }
@@ -9254,7 +9378,7 @@ function wrapStreamingOutput(output, span2, ctx) {
9254
9378
  if (ended) return;
9255
9379
  ended = true;
9256
9380
  if (err) recordError6(span2, err);
9257
- else span2.setStatus({ code: import_api16.SpanStatusCode.OK });
9381
+ else span2.setStatus({ code: import_api17.SpanStatusCode.OK });
9258
9382
  span2.end();
9259
9383
  };
9260
9384
  if (output.text && typeof output.text.then === "function") {
@@ -9345,7 +9469,7 @@ function rebindStreamContext(output, ctx) {
9345
9469
  const origIterator = value[Symbol.asyncIterator].bind(value);
9346
9470
  return {
9347
9471
  [Symbol.asyncIterator]() {
9348
- return import_api16.context.with(ctx, () => origIterator());
9472
+ return import_api17.context.with(ctx, () => origIterator());
9349
9473
  }
9350
9474
  };
9351
9475
  }
@@ -9455,13 +9579,13 @@ function recordUsage(span2, usage) {
9455
9579
  }
9456
9580
  }
9457
9581
  function withActiveSpan(name, attrs, fn) {
9458
- const tracer = import_api16.trace.getTracer(TRACER_NAME10);
9459
- const span2 = tracer.startSpan(name, { attributes: attrs }, import_api16.context.active());
9460
- const ctx = import_api16.trace.setSpan(import_api16.context.active(), span2);
9461
- return import_api16.context.with(ctx, async () => {
9582
+ const tracer = import_api17.trace.getTracer(TRACER_NAME10);
9583
+ const span2 = tracer.startSpan(name, { attributes: attrs }, import_api17.context.active());
9584
+ const ctx = import_api17.trace.setSpan(import_api17.context.active(), span2);
9585
+ return import_api17.context.with(ctx, async () => {
9462
9586
  try {
9463
9587
  const result = await fn(span2);
9464
- span2.setStatus({ code: import_api16.SpanStatusCode.OK });
9588
+ span2.setStatus({ code: import_api17.SpanStatusCode.OK });
9465
9589
  return result;
9466
9590
  } catch (err) {
9467
9591
  recordError6(span2, err);
@@ -9496,15 +9620,15 @@ function safeStringify11(value) {
9496
9620
  }
9497
9621
  function recordError6(span2, err) {
9498
9622
  if (err instanceof Error) {
9499
- span2.setStatus({ code: import_api16.SpanStatusCode.ERROR, message: err.message });
9623
+ span2.setStatus({ code: import_api17.SpanStatusCode.ERROR, message: err.message });
9500
9624
  span2.recordException(err);
9501
9625
  } else {
9502
- span2.setStatus({ code: import_api16.SpanStatusCode.ERROR, message: String(err) });
9626
+ span2.setStatus({ code: import_api17.SpanStatusCode.ERROR, message: String(err) });
9503
9627
  }
9504
9628
  }
9505
9629
 
9506
9630
  // src/pi-agent.ts
9507
- var import_api17 = require("@opentelemetry/api");
9631
+ var import_api18 = require("@opentelemetry/api");
9508
9632
  var TRACER_NAME11 = "neatlogs.pi-agent";
9509
9633
  var PATCH_FLAG3 = "_neatlogs_patched";
9510
9634
  function piAgentHooks(agent) {
@@ -9512,7 +9636,7 @@ function piAgentHooks(agent) {
9512
9636
  const a = agent;
9513
9637
  if (typeof a.subscribe !== "function") return agent;
9514
9638
  const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [] };
9515
- const tracer = import_api17.trace.getTracer(TRACER_NAME11);
9639
+ const tracer = import_api18.trace.getTracer(TRACER_NAME11);
9516
9640
  a.subscribe((event) => {
9517
9641
  try {
9518
9642
  handleEvent(tracer, state, event);
@@ -9528,10 +9652,10 @@ function handleEvent(tracer, state, event) {
9528
9652
  const span2 = tracer.startSpan(
9529
9653
  "pi_agent.run",
9530
9654
  { attributes: { "neatlogs.span.kind": "AGENT" } },
9531
- import_api17.context.active()
9655
+ import_api18.context.active()
9532
9656
  );
9533
9657
  state.agentSpan = span2;
9534
- state.agentCtx = import_api17.trace.setSpan(import_api17.context.active(), span2);
9658
+ state.agentCtx = import_api18.trace.setSpan(import_api18.context.active(), span2);
9535
9659
  state.inputMessages = [];
9536
9660
  break;
9537
9661
  }
@@ -9550,7 +9674,7 @@ function handleEvent(tracer, state, event) {
9550
9674
  break;
9551
9675
  }
9552
9676
  case "tool_execution_start": {
9553
- const parent = state.agentCtx ?? import_api17.context.active();
9677
+ const parent = state.agentCtx ?? import_api18.context.active();
9554
9678
  const span2 = tracer.startSpan(
9555
9679
  `pi_agent.tool.${event.toolName ?? "tool"}`,
9556
9680
  {
@@ -9572,10 +9696,10 @@ function handleEvent(tracer, state, event) {
9572
9696
  span2.setAttribute("output.value", safeStringify12(event.result));
9573
9697
  }
9574
9698
  if (event.isError) {
9575
- span2.setStatus({ code: import_api17.SpanStatusCode.ERROR });
9699
+ span2.setStatus({ code: import_api18.SpanStatusCode.ERROR });
9576
9700
  span2.setAttribute("neatlogs.tool.is_error", true);
9577
9701
  } else {
9578
- span2.setStatus({ code: import_api17.SpanStatusCode.OK });
9702
+ span2.setStatus({ code: import_api18.SpanStatusCode.OK });
9579
9703
  }
9580
9704
  span2.end();
9581
9705
  if (event.toolCallId) state.toolSpans.delete(event.toolCallId);
@@ -9594,7 +9718,7 @@ function handleEvent(tracer, state, event) {
9594
9718
  if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
9595
9719
  const finalText = lastAssistantText(event.messages);
9596
9720
  if (finalText) state.agentSpan.setAttribute("output.value", finalText);
9597
- state.agentSpan.setStatus({ code: import_api17.SpanStatusCode.OK });
9721
+ state.agentSpan.setStatus({ code: import_api18.SpanStatusCode.OK });
9598
9722
  state.agentSpan.end();
9599
9723
  state.agentSpan = void 0;
9600
9724
  state.agentCtx = void 0;
@@ -9646,13 +9770,13 @@ function emitLlmSpan(tracer, state, msg) {
9646
9770
  if (usage.cacheRead) attrs["neatlogs.llm.token_count.cache_read"] = usage.cacheRead;
9647
9771
  if (usage.cacheWrite) attrs["neatlogs.llm.token_count.cache_write"] = usage.cacheWrite;
9648
9772
  }
9649
- const parent = state.agentCtx ?? import_api17.context.active();
9773
+ const parent = state.agentCtx ?? import_api18.context.active();
9650
9774
  const span2 = tracer.startSpan(
9651
9775
  `pi_agent.llm.${msg.model || "model"}`,
9652
9776
  { attributes: attrs },
9653
9777
  parent
9654
9778
  );
9655
- span2.setStatus({ code: import_api17.SpanStatusCode.OK });
9779
+ span2.setStatus({ code: import_api18.SpanStatusCode.OK });
9656
9780
  span2.end();
9657
9781
  }
9658
9782
  function splitAssistantContent(content) {
@@ -9713,7 +9837,7 @@ function safeStringify12(value) {
9713
9837
  }
9714
9838
 
9715
9839
  // src/claude-agent-sdk.ts
9716
- var import_api18 = require("@opentelemetry/api");
9840
+ var import_api19 = require("@opentelemetry/api");
9717
9841
  var TRACER_NAME12 = "neatlogs.claude_agent_sdk";
9718
9842
  var ROOT_SCOPE = "__root__";
9719
9843
  function wrapClaudeAgentSDK(sdk, options = {}) {
@@ -9734,13 +9858,13 @@ function wrapClaudeAgentSDK(sdk, options = {}) {
9734
9858
  }
9735
9859
  function wrapQuery(original, options) {
9736
9860
  return function(params, ...rest) {
9737
- const tracer = import_api18.trace.getTracer(TRACER_NAME12);
9861
+ const tracer = import_api19.trace.getTracer(TRACER_NAME12);
9738
9862
  const workflowName = options.workflowName ?? "claude_agent.query";
9739
9863
  const promptRef = { text: "" };
9740
9864
  const agentSpan = tracer.startSpan(
9741
9865
  "claude_agent.query",
9742
9866
  { attributes: { "neatlogs.span.kind": "AGENT", "neatlogs.workflow.name": workflowName } },
9743
- import_api18.context.active()
9867
+ import_api19.context.active()
9744
9868
  );
9745
9869
  const promptText = extractPromptText(params?.prompt);
9746
9870
  if (promptText) {
@@ -9749,8 +9873,8 @@ function wrapQuery(original, options) {
9749
9873
  } else if (params && isAsyncIterable(params.prompt)) {
9750
9874
  params = { ...params, prompt: tapPromptStream(params.prompt, promptRef) };
9751
9875
  }
9752
- const agentCtx = import_api18.trace.setSpan(import_api18.context.active(), agentSpan);
9753
- const queryObj = import_api18.context.with(agentCtx, () => original(params, ...rest));
9876
+ const agentCtx = import_api19.trace.setSpan(import_api19.context.active(), agentSpan);
9877
+ const queryObj = import_api19.context.with(agentCtx, () => original(params, ...rest));
9754
9878
  return instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRef);
9755
9879
  };
9756
9880
  }
@@ -9769,7 +9893,7 @@ async function* tapPromptStream(prompt, ref) {
9769
9893
  function instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRef) {
9770
9894
  const originalAsyncIterator = queryObj?.[Symbol.asyncIterator]?.bind(queryObj);
9771
9895
  if (!originalAsyncIterator) {
9772
- agentSpan.setStatus({ code: import_api18.SpanStatusCode.OK });
9896
+ agentSpan.setStatus({ code: import_api19.SpanStatusCode.OK });
9773
9897
  agentSpan.end();
9774
9898
  return queryObj;
9775
9899
  }
@@ -9806,7 +9930,7 @@ function instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRe
9806
9930
  if (status === "error") {
9807
9931
  recordError7(agentSpan, err);
9808
9932
  } else {
9809
- agentSpan.setStatus({ code: import_api18.SpanStatusCode.OK });
9933
+ agentSpan.setStatus({ code: import_api19.SpanStatusCode.OK });
9810
9934
  agentSpan.end();
9811
9935
  }
9812
9936
  };
@@ -9817,7 +9941,7 @@ function instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRe
9817
9941
  return {
9818
9942
  async next() {
9819
9943
  try {
9820
- const result = await import_api18.context.with(agentCtx, () => iterator.next());
9944
+ const result = await import_api19.context.with(agentCtx, () => iterator.next());
9821
9945
  if (result.done) {
9822
9946
  finalizeAgent("ok");
9823
9947
  return result;
@@ -9848,7 +9972,7 @@ function instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRe
9848
9972
  function closeScope(scope, status) {
9849
9973
  try {
9850
9974
  if (scope.finalText) scope.span.setAttribute("output.value", scope.finalText);
9851
- scope.span.setStatus({ code: status === "ok" ? import_api18.SpanStatusCode.OK : import_api18.SpanStatusCode.ERROR });
9975
+ scope.span.setStatus({ code: status === "ok" ? import_api19.SpanStatusCode.OK : import_api19.SpanStatusCode.ERROR });
9852
9976
  scope.span.end();
9853
9977
  } catch {
9854
9978
  }
@@ -9863,7 +9987,7 @@ function getScope(tracer, state, msg) {
9863
9987
  flushAssistantTurn(tracer, root, state);
9864
9988
  }
9865
9989
  const parentToolSpan = state.toolSpans.get(parentId);
9866
- const parentCtx = parentToolSpan ? import_api18.trace.setSpan(import_api18.context.active(), parentToolSpan) : state.scopes.get(ROOT_SCOPE).ctx;
9990
+ const parentCtx = parentToolSpan ? import_api19.trace.setSpan(import_api19.context.active(), parentToolSpan) : state.scopes.get(ROOT_SCOPE).ctx;
9867
9991
  const subType = msg?.subagent_type ? String(msg.subagent_type) : "subagent";
9868
9992
  const attrs = {
9869
9993
  "neatlogs.span.kind": "AGENT",
@@ -9873,7 +9997,7 @@ function getScope(tracer, state, msg) {
9873
9997
  const span2 = tracer.startSpan(`claude_agent.subagent.${subType}`, { attributes: attrs }, parentCtx);
9874
9998
  const scope = {
9875
9999
  span: span2,
9876
- ctx: import_api18.trace.setSpan(import_api18.context.active(), span2),
10000
+ ctx: import_api19.trace.setSpan(import_api19.context.active(), span2),
9877
10001
  inputMessages: msg?.task_description ? [{ role: "user", content: String(msg.task_description) }] : [],
9878
10002
  assistantBuffer: null,
9879
10003
  finalText: "",
@@ -9997,7 +10121,7 @@ function flushAssistantTurn(tracer, scope, state) {
9997
10121
  if (buf.stopReason) attrs["neatlogs.llm.finish_reason"] = String(buf.stopReason);
9998
10122
  const span2 = tracer.startSpan(`claude_agent.llm.${model || "model"}`, { attributes: attrs }, scope.ctx);
9999
10123
  if (buf.usage) setUsage2(span2, buf.usage);
10000
- span2.setStatus({ code: import_api18.SpanStatusCode.OK });
10124
+ span2.setStatus({ code: import_api19.SpanStatusCode.OK });
10001
10125
  span2.end();
10002
10126
  if (outText) scope.finalText = outText;
10003
10127
  const turnParts = [];
@@ -10032,10 +10156,10 @@ function closeToolSpansFromUser(state, scope, msg) {
10032
10156
  if (!span2) continue;
10033
10157
  span2.setAttribute("output.value", outText);
10034
10158
  if (block.is_error) {
10035
- span2.setStatus({ code: import_api18.SpanStatusCode.ERROR });
10159
+ span2.setStatus({ code: import_api19.SpanStatusCode.ERROR });
10036
10160
  span2.setAttribute("neatlogs.tool.is_error", true);
10037
10161
  } else {
10038
- span2.setStatus({ code: import_api18.SpanStatusCode.OK });
10162
+ span2.setStatus({ code: import_api19.SpanStatusCode.OK });
10039
10163
  }
10040
10164
  span2.end();
10041
10165
  state.toolSpans.delete(id);
@@ -10082,16 +10206,16 @@ function safeStringify13(value) {
10082
10206
  }
10083
10207
  function recordError7(span2, err) {
10084
10208
  if (err instanceof Error) {
10085
- span2.setStatus({ code: import_api18.SpanStatusCode.ERROR, message: err.message });
10209
+ span2.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: err.message });
10086
10210
  span2.recordException(err);
10087
10211
  } else {
10088
- span2.setStatus({ code: import_api18.SpanStatusCode.ERROR, message: String(err) });
10212
+ span2.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: String(err) });
10089
10213
  }
10090
10214
  span2.end();
10091
10215
  }
10092
10216
 
10093
10217
  // src/openrouter-agent.ts
10094
- var import_api19 = require("@opentelemetry/api");
10218
+ var import_api20 = require("@opentelemetry/api");
10095
10219
  var TRACER_NAME13 = "neatlogs.openrouter_agent";
10096
10220
  var PROVIDER4 = "openrouter";
10097
10221
  function wrapOpenRouterAgent(client) {
@@ -10110,21 +10234,21 @@ function wrapOpenRouterAgent(client) {
10110
10234
  function wrapCallModel(callModel) {
10111
10235
  return function(clientArg, opts, ...rest) {
10112
10236
  const span2 = startLlmSpan(opts);
10113
- const ctx = import_api19.trace.setSpan(import_api19.context.active(), span2);
10114
- const result = import_api19.context.with(ctx, () => callModel.call(this, clientArg, opts, ...rest));
10237
+ const ctx = import_api20.trace.setSpan(import_api20.context.active(), span2);
10238
+ const result = import_api20.context.with(ctx, () => callModel.call(this, clientArg, opts, ...rest));
10115
10239
  return instrumentModelResult(result, span2);
10116
10240
  };
10117
10241
  }
10118
10242
  function tracedCallModel(original) {
10119
10243
  return function(opts, ...rest) {
10120
10244
  const span2 = startLlmSpan(opts);
10121
- const ctx = import_api19.trace.setSpan(import_api19.context.active(), span2);
10122
- const result = import_api19.context.with(ctx, () => original(opts, ...rest));
10245
+ const ctx = import_api20.trace.setSpan(import_api20.context.active(), span2);
10246
+ const result = import_api20.context.with(ctx, () => original(opts, ...rest));
10123
10247
  return instrumentModelResult(result, span2);
10124
10248
  };
10125
10249
  }
10126
10250
  function startLlmSpan(opts) {
10127
- const tracer = import_api19.trace.getTracer(TRACER_NAME13);
10251
+ const tracer = getProviderTracer(TRACER_NAME13);
10128
10252
  const model = opts?.model ?? "";
10129
10253
  const span2 = tracer.startSpan("openrouter.call_model", {
10130
10254
  attributes: {
@@ -10133,7 +10257,7 @@ function startLlmSpan(opts) {
10133
10257
  "neatlogs.llm.system": PROVIDER4,
10134
10258
  "neatlogs.llm.model_name": model
10135
10259
  }
10136
- }, import_api19.context.active());
10260
+ }, import_api20.context.active());
10137
10261
  const messages = Array.isArray(opts?.messages) ? opts.messages : Array.isArray(opts?.input) ? opts.input : [];
10138
10262
  if (messages.length) {
10139
10263
  messages.forEach((msg, i) => {
@@ -10185,7 +10309,7 @@ function startLlmSpan(opts) {
10185
10309
  }
10186
10310
  function instrumentModelResult(result, span2) {
10187
10311
  if (!result || typeof result !== "object" && typeof result !== "function") {
10188
- span2.setStatus({ code: import_api19.SpanStatusCode.OK });
10312
+ span2.setStatus({ code: import_api20.SpanStatusCode.OK });
10189
10313
  span2.end();
10190
10314
  return result;
10191
10315
  }
@@ -10196,7 +10320,7 @@ function instrumentModelResult(result, span2) {
10196
10320
  try {
10197
10321
  finalizeLlm(span2, resolved);
10198
10322
  } catch {
10199
- span2.setStatus({ code: import_api19.SpanStatusCode.OK });
10323
+ span2.setStatus({ code: import_api20.SpanStatusCode.OK });
10200
10324
  span2.end();
10201
10325
  }
10202
10326
  };
@@ -10299,7 +10423,7 @@ function finalizeLlm(span2, result) {
10299
10423
  const finishReason = result?.finishReason ?? result?.finish_reason ?? result?.choices?.[0]?.finish_reason;
10300
10424
  if (finishReason) span2.setAttribute("neatlogs.llm.finish_reason", String(finishReason));
10301
10425
  setOpenResponsesUsage(span2, result);
10302
- span2.setStatus({ code: import_api19.SpanStatusCode.OK });
10426
+ span2.setStatus({ code: import_api20.SpanStatusCode.OK });
10303
10427
  span2.end();
10304
10428
  }
10305
10429
  function extractOpenResponsesText(result) {
@@ -10325,197 +10449,623 @@ function safeStringify14(value) {
10325
10449
  }
10326
10450
  function recordError8(span2, err) {
10327
10451
  if (err instanceof Error) {
10328
- span2.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: err.message });
10452
+ span2.setStatus({ code: import_api20.SpanStatusCode.ERROR, message: err.message });
10329
10453
  span2.recordException(err);
10330
10454
  } else {
10331
- span2.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: String(err) });
10455
+ span2.setStatus({ code: import_api20.SpanStatusCode.ERROR, message: String(err) });
10332
10456
  }
10333
10457
  span2.end();
10334
10458
  }
10335
10459
 
10336
- // src/opencode-plugin.ts
10337
- var import_api20 = require("@opentelemetry/api");
10338
- var TRACER_NAME14 = "neatlogs.opencode";
10339
- var _initialized2 = false;
10340
- async function ensureInitialized() {
10341
- if (_initialized2) return;
10342
- _initialized2 = true;
10343
- try {
10344
- await init({
10345
- apiKey: process.env.NEATLOGS_API_KEY,
10346
- // The plugin bootstraps itself inside opencode's process — there is no user
10347
- // init() to pass an endpoint, so honor NEATLOGS_ENDPOINT from the env (e.g.
10348
- // a local backend). init() otherwise defaults to the hosted cloud.
10349
- ...process.env.NEATLOGS_ENDPOINT ? { endpoint: process.env.NEATLOGS_ENDPOINT } : {},
10350
- workflowName: process.env.NEATLOGS_WORKFLOW_NAME || "opencode",
10351
- // opencode is long-running and event-driven; keep auto session off so each
10352
- // span's conversation id is the opencode session id we set explicitly.
10353
- autoSession: false
10354
- });
10355
- } catch {
10460
+ // src/opencode-trace-shipper.ts
10461
+ var import_protobufjs = __toESM(require("protobufjs"));
10462
+ var PACKAGE_NAME = "neatlogs.opencode";
10463
+ var OTLP_PROTO_JSON = {
10464
+ nested: {
10465
+ opentelemetry: {
10466
+ nested: {
10467
+ proto: {
10468
+ nested: {
10469
+ common: {
10470
+ nested: {
10471
+ v1: {
10472
+ nested: {
10473
+ AnyValue: {
10474
+ oneofs: {
10475
+ value: {
10476
+ oneof: [
10477
+ "stringValue",
10478
+ "boolValue",
10479
+ "intValue",
10480
+ "doubleValue",
10481
+ "arrayValue",
10482
+ "kvlistValue",
10483
+ "bytesValue"
10484
+ ]
10485
+ }
10486
+ },
10487
+ fields: {
10488
+ stringValue: { type: "string", id: 1 },
10489
+ boolValue: { type: "bool", id: 2 },
10490
+ intValue: { type: "int64", id: 3 },
10491
+ doubleValue: { type: "double", id: 4 },
10492
+ arrayValue: { type: "ArrayValue", id: 5 },
10493
+ kvlistValue: { type: "KeyValueList", id: 6 },
10494
+ bytesValue: { type: "bytes", id: 7 }
10495
+ }
10496
+ },
10497
+ ArrayValue: { fields: { values: { rule: "repeated", type: "AnyValue", id: 1 } } },
10498
+ KeyValueList: { fields: { values: { rule: "repeated", type: "KeyValue", id: 1 } } },
10499
+ KeyValue: {
10500
+ fields: { key: { type: "string", id: 1 }, value: { type: "AnyValue", id: 2 } }
10501
+ },
10502
+ InstrumentationScope: {
10503
+ fields: { name: { type: "string", id: 1 }, version: { type: "string", id: 2 } }
10504
+ }
10505
+ }
10506
+ }
10507
+ }
10508
+ },
10509
+ resource: {
10510
+ nested: {
10511
+ v1: {
10512
+ nested: {
10513
+ Resource: {
10514
+ fields: {
10515
+ attributes: {
10516
+ rule: "repeated",
10517
+ type: "opentelemetry.proto.common.v1.KeyValue",
10518
+ id: 1
10519
+ }
10520
+ }
10521
+ }
10522
+ }
10523
+ }
10524
+ }
10525
+ },
10526
+ trace: {
10527
+ nested: {
10528
+ v1: {
10529
+ nested: {
10530
+ ResourceSpans: {
10531
+ fields: {
10532
+ resource: { type: "opentelemetry.proto.resource.v1.Resource", id: 1 },
10533
+ scopeSpans: { rule: "repeated", type: "ScopeSpans", id: 2 }
10534
+ }
10535
+ },
10536
+ ScopeSpans: {
10537
+ fields: {
10538
+ scope: { type: "opentelemetry.proto.common.v1.InstrumentationScope", id: 1 },
10539
+ spans: { rule: "repeated", type: "Span", id: 2 }
10540
+ }
10541
+ },
10542
+ Span: {
10543
+ fields: {
10544
+ traceId: { type: "bytes", id: 1 },
10545
+ spanId: { type: "bytes", id: 2 },
10546
+ traceState: { type: "string", id: 3 },
10547
+ parentSpanId: { type: "bytes", id: 4 },
10548
+ name: { type: "string", id: 5 },
10549
+ kind: { type: "SpanKind", id: 6 },
10550
+ startTimeUnixNano: { type: "fixed64", id: 7 },
10551
+ endTimeUnixNano: { type: "fixed64", id: 8 },
10552
+ attributes: {
10553
+ rule: "repeated",
10554
+ type: "opentelemetry.proto.common.v1.KeyValue",
10555
+ id: 9
10556
+ },
10557
+ droppedAttributesCount: { type: "uint32", id: 10 },
10558
+ status: { type: "Status", id: 15 }
10559
+ }
10560
+ },
10561
+ Status: {
10562
+ fields: { message: { type: "string", id: 2 }, code: { type: "StatusCode", id: 3 } }
10563
+ },
10564
+ StatusCode: {
10565
+ values: { STATUS_CODE_UNSET: 0, STATUS_CODE_OK: 1, STATUS_CODE_ERROR: 2 }
10566
+ },
10567
+ SpanKind: {
10568
+ values: {
10569
+ SPAN_KIND_UNSPECIFIED: 0,
10570
+ SPAN_KIND_INTERNAL: 1,
10571
+ SPAN_KIND_SERVER: 2,
10572
+ SPAN_KIND_CLIENT: 3,
10573
+ SPAN_KIND_PRODUCER: 4,
10574
+ SPAN_KIND_CONSUMER: 5
10575
+ }
10576
+ }
10577
+ }
10578
+ }
10579
+ }
10580
+ },
10581
+ collector: {
10582
+ nested: {
10583
+ trace: {
10584
+ nested: {
10585
+ v1: {
10586
+ nested: {
10587
+ ExportTraceServiceRequest: {
10588
+ fields: {
10589
+ resourceSpans: {
10590
+ rule: "repeated",
10591
+ type: "opentelemetry.proto.trace.v1.ResourceSpans",
10592
+ id: 1
10593
+ }
10594
+ }
10595
+ }
10596
+ }
10597
+ }
10598
+ }
10599
+ }
10600
+ }
10601
+ }
10602
+ }
10603
+ }
10604
+ }
10605
+ }
10606
+ }
10607
+ };
10608
+ var protoRoot = import_protobufjs.default.Root.fromJSON(OTLP_PROTO_JSON);
10609
+ var ExportTraceServiceRequest = protoRoot.lookupType(
10610
+ "opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest"
10611
+ );
10612
+ var SpanStatusCode16 = { UNSET: 0, OK: 1, ERROR: 2 };
10613
+ function randomBytes3(length) {
10614
+ const out = new Uint8Array(length);
10615
+ const crypto2 = globalThis.crypto;
10616
+ if (crypto2 && typeof crypto2.getRandomValues === "function") {
10617
+ crypto2.getRandomValues(out);
10618
+ return out;
10619
+ }
10620
+ for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
10621
+ return out;
10622
+ }
10623
+ function generateTraceId() {
10624
+ return randomBytes3(16);
10625
+ }
10626
+ function generateSpanId() {
10627
+ return randomBytes3(8);
10628
+ }
10629
+ function nowNanoString() {
10630
+ return (BigInt(Date.now()) * 1000000n).toString();
10631
+ }
10632
+ function msToNanoString(ms) {
10633
+ return (BigInt(Math.floor(ms)) * 1000000n).toString();
10634
+ }
10635
+ function attrStr(key, value) {
10636
+ if (value === void 0 || value === null) return void 0;
10637
+ return { key, value: { stringValue: String(value) } };
10638
+ }
10639
+ function attrInt(key, value) {
10640
+ if (value === void 0 || value === null || !Number.isFinite(value)) return void 0;
10641
+ return { key, value: { intValue: String(Math.trunc(value)) } };
10642
+ }
10643
+ function attrDouble(key, value) {
10644
+ if (value === void 0 || value === null || !Number.isFinite(value)) return void 0;
10645
+ return { key, value: { doubleValue: value } };
10646
+ }
10647
+ var TraceShipper = class {
10648
+ apiKey;
10649
+ endpoint;
10650
+ debug;
10651
+ maxRetries;
10652
+ workflowName;
10653
+ queue = [];
10654
+ prefix = "[neatlogs/opencode]";
10655
+ constructor(opts) {
10656
+ this.apiKey = opts.apiKey;
10657
+ this.endpoint = opts.endpoint.endsWith("/") ? opts.endpoint.slice(0, -1) : opts.endpoint;
10658
+ this.debug = !!opts.debug;
10659
+ this.maxRetries = opts.maxRetries ?? 3;
10660
+ this.workflowName = opts.workflowName || "";
10661
+ }
10662
+ enqueue(span2) {
10663
+ this.queue.push(span2);
10664
+ }
10665
+ get pending() {
10666
+ return this.queue.length;
10356
10667
  }
10668
+ /**
10669
+ * Ship all queued spans in a single awaited POST. Resolves only once the HTTP
10670
+ * response is received (or all retries are exhausted) — so a short-lived host
10671
+ * can safely exit immediately after awaiting this.
10672
+ */
10673
+ async flush() {
10674
+ if (this.queue.length === 0) return;
10675
+ if (!this.apiKey) {
10676
+ this.queue = [];
10677
+ return;
10678
+ }
10679
+ const spans = this.queue.splice(0);
10680
+ const payload = this.buildProtobuf(spans);
10681
+ const url = `${this.endpoint}/v1/traces`;
10682
+ if (this.debug) console.log(`${this.prefix} Shipping ${spans.length} spans to ${url}`);
10683
+ for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
10684
+ try {
10685
+ const resp = await fetch(url, {
10686
+ method: "POST",
10687
+ headers: {
10688
+ "Content-Type": "application/x-protobuf",
10689
+ "x-api-key": this.apiKey
10690
+ },
10691
+ body: payload
10692
+ });
10693
+ if (resp.ok) {
10694
+ if (this.debug) console.log(`${this.prefix} Shipped ${spans.length} spans`);
10695
+ return;
10696
+ }
10697
+ if (resp.status === 401) {
10698
+ console.warn(`${this.prefix} Invalid API key (401) \u2014 dropping ${spans.length} spans`);
10699
+ return;
10700
+ }
10701
+ if (resp.status < 500 && resp.status !== 429) {
10702
+ if (this.debug) console.warn(`${this.prefix} HTTP ${resp.status} \u2014 dropping spans`);
10703
+ return;
10704
+ }
10705
+ } catch (err) {
10706
+ if (this.debug) {
10707
+ console.warn(
10708
+ `${this.prefix} Attempt ${attempt}/${this.maxRetries}: ${err.message}`
10709
+ );
10710
+ }
10711
+ }
10712
+ if (attempt < this.maxRetries) {
10713
+ await new Promise((r) => setTimeout(r, 500 * 2 ** (attempt - 1)));
10714
+ }
10715
+ }
10716
+ console.warn(`${this.prefix} Failed to ship ${spans.length} spans after ${this.maxRetries} attempts`);
10717
+ }
10718
+ buildProtobuf(spans) {
10719
+ const protoSpans = spans.map((span2) => ({
10720
+ traceId: span2.traceId,
10721
+ spanId: span2.spanId,
10722
+ parentSpanId: span2.parentSpanId || void 0,
10723
+ name: span2.name,
10724
+ kind: span2.kind,
10725
+ startTimeUnixNano: nanoToLong(span2.startTimeUnixNano),
10726
+ endTimeUnixNano: nanoToLong(span2.endTimeUnixNano),
10727
+ attributes: span2.attributes.map((a) => ({
10728
+ key: a.key,
10729
+ value: a.value.intValue !== void 0 ? { intValue: nanoToLong(a.value.intValue) } : a.value
10730
+ })),
10731
+ status: span2.status ? { code: span2.status.code, message: span2.status.message } : void 0
10732
+ }));
10733
+ const resourceAttributes = [
10734
+ { key: "service.name", value: { stringValue: "neatlogs.opencode" } },
10735
+ { key: "service.version", value: { stringValue: __version__ } }
10736
+ ];
10737
+ if (this.workflowName) {
10738
+ resourceAttributes.push({
10739
+ key: "neatlogs.workflow_name",
10740
+ value: { stringValue: this.workflowName }
10741
+ });
10742
+ }
10743
+ const message = {
10744
+ resourceSpans: [
10745
+ {
10746
+ resource: { attributes: resourceAttributes },
10747
+ scopeSpans: [
10748
+ {
10749
+ scope: { name: PACKAGE_NAME, version: __version__ },
10750
+ spans: protoSpans
10751
+ }
10752
+ ]
10753
+ }
10754
+ ]
10755
+ };
10756
+ const errMsg = ExportTraceServiceRequest.verify(message);
10757
+ if (errMsg && this.debug) console.warn(`${this.prefix} Proto verify: ${errMsg}`);
10758
+ return ExportTraceServiceRequest.encode(ExportTraceServiceRequest.fromObject(message)).finish();
10759
+ }
10760
+ };
10761
+ function nanoToLong(nanoStr) {
10762
+ const big = BigInt(nanoStr);
10763
+ const low = Number(big & 0xffffffffn);
10764
+ const high = Number(big >> 32n & 0xffffffffn);
10765
+ return { low, high, unsigned: true };
10357
10766
  }
10767
+
10768
+ // src/opencode-plugin.ts
10769
+ var DEFAULT_ENDPOINT = "https://staging-cloud.neatlogs.com";
10358
10770
  var NeatlogsOpencodePlugin = async (_ctx) => {
10359
- await ensureInitialized();
10360
- const sessions = /* @__PURE__ */ new Map();
10361
- const tracer = import_api20.trace.getTracer(TRACER_NAME14);
10771
+ const apiKey = (process.env.NEATLOGS_API_KEY ?? "").trim();
10772
+ const endpoint = (process.env.NEATLOGS_ENDPOINT ?? DEFAULT_ENDPOINT).trim();
10773
+ const workflowName = process.env.NEATLOGS_WORKFLOW_NAME || "opencode";
10362
10774
  const captureSystemPrompt = String(process.env.NEATLOGS_CAPTURE_SYSTEM_PROMPT || "").toLowerCase() === "true";
10775
+ const debug = String(process.env.NEATLOGS_DEBUG || "").toLowerCase() === "true";
10776
+ const shipper = new TraceShipper({ apiKey, endpoint, workflowName, debug });
10777
+ const sessions = /* @__PURE__ */ new Map();
10363
10778
  function stateFor(sessionID) {
10364
10779
  let s = sessions.get(sessionID);
10365
10780
  if (!s) {
10366
- s = { toolSpans: /* @__PURE__ */ new Map(), messageText: /* @__PURE__ */ new Map(), emitted: /* @__PURE__ */ new Set(), lastUserText: "" };
10781
+ s = {
10782
+ traceId: generateTraceId(),
10783
+ toolStarts: /* @__PURE__ */ new Map(),
10784
+ outputParts: /* @__PURE__ */ new Map(),
10785
+ toolCalls: /* @__PURE__ */ new Map(),
10786
+ processed: /* @__PURE__ */ new Set(),
10787
+ currentInput: "",
10788
+ lastAssistantText: ""
10789
+ };
10367
10790
  sessions.set(sessionID, s);
10368
10791
  }
10369
10792
  return s;
10370
10793
  }
10794
+ function startRoot(st, sessionID) {
10795
+ if (st.rootSpan) return;
10796
+ st.rootSpan = {
10797
+ spanId: generateSpanId(),
10798
+ name: "opencode.session",
10799
+ startNano: nowNanoString(),
10800
+ attributes: [
10801
+ { key: "neatlogs.span.kind", value: { stringValue: "AGENT" } },
10802
+ { key: "neatlogs.agent.framework", value: { stringValue: "opencode" } },
10803
+ { key: "neatlogs.conversation.id", value: { stringValue: sessionID } }
10804
+ ]
10805
+ };
10806
+ }
10807
+ async function closeAndFlush(st, sessionID) {
10808
+ if (st.rootSpan) {
10809
+ const attrs = st.rootSpan.attributes.slice();
10810
+ setIO(attrs, "AGENT", st.currentInput || void 0, st.lastAssistantText || void 0);
10811
+ shipper.enqueue({
10812
+ traceId: st.traceId,
10813
+ spanId: st.rootSpan.spanId,
10814
+ name: st.rootSpan.name,
10815
+ kind: 1,
10816
+ startTimeUnixNano: st.rootSpan.startNano,
10817
+ endTimeUnixNano: nowNanoString(),
10818
+ attributes: attrs,
10819
+ status: { code: SpanStatusCode16.OK }
10820
+ });
10821
+ const m = nowNanoString();
10822
+ shipper.enqueue({
10823
+ traceId: st.traceId,
10824
+ spanId: generateSpanId(),
10825
+ parentSpanId: st.rootSpan.spanId,
10826
+ name: "neatlogs.trace.complete",
10827
+ kind: 1,
10828
+ startTimeUnixNano: m,
10829
+ endTimeUnixNano: m,
10830
+ attributes: [
10831
+ { key: "neatlogs.trace.complete", value: { boolValue: true } },
10832
+ { key: "neatlogs.internal", value: { boolValue: true } },
10833
+ { key: "neatlogs.span.kind", value: { stringValue: "Neatlogs.INTERNAL" } }
10834
+ ]
10835
+ });
10836
+ st.rootSpan = void 0;
10837
+ }
10838
+ await shipper.flush();
10839
+ void sessionID;
10840
+ }
10371
10841
  return {
10372
- /**
10373
- * Global event bus. opencode emits message + session lifecycle events here.
10374
- * We use it to emit an LLM span when an assistant message completes, and to
10375
- * track user-message text + assistant text from part updates.
10376
- */
10842
+ /** Fired when the user submits a prompt — open the turn's AGENT root. */
10843
+ "chat.message": async (_input, output) => {
10844
+ try {
10845
+ const sessionID = String(_input?.sessionID ?? output?.sessionID ?? "");
10846
+ if (!sessionID) return;
10847
+ const st = stateFor(sessionID);
10848
+ const parts = output?.parts ?? [];
10849
+ const text = Array.isArray(parts) ? parts.filter((p) => p?.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n") : "";
10850
+ if (text) st.currentInput = text;
10851
+ startRoot(st, sessionID);
10852
+ } catch {
10853
+ }
10854
+ },
10855
+ /** Capture the system prompt (opt-in). */
10856
+ "experimental.chat.system.transform": async (_input, output) => {
10857
+ try {
10858
+ if (!captureSystemPrompt) return;
10859
+ const sessionID = String(_input?.sessionID ?? "");
10860
+ const parts = output?.system ?? output?.parts ?? output;
10861
+ const joined = Array.isArray(parts) ? parts.map((p) => typeof p === "string" ? p : p?.text ?? "").join("\n") : String(parts ?? "");
10862
+ if (sessionID && joined) stateFor(sessionID).systemPrompt = joined;
10863
+ } catch {
10864
+ }
10865
+ },
10866
+ /** Global event bus — message parts, assistant completions, session idle. */
10377
10867
  event: async ({ event }) => {
10378
10868
  try {
10379
- await handleEvent2(tracer, sessions, stateFor, captureSystemPrompt, event);
10869
+ await handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, event);
10380
10870
  } catch {
10381
10871
  }
10382
10872
  },
10383
- /** Fired before a tool runs: open a TOOL span keyed by callID. */
10873
+ /** Tool start record start time + args (span built atomically in `after`). */
10384
10874
  "tool.execute.before": async (input, output) => {
10385
10875
  try {
10386
- const sessionID = input?.sessionID ?? "";
10387
- const callID = input?.callID ?? input?.tool ?? "";
10876
+ const sessionID = String(input?.sessionID ?? "");
10877
+ if (!sessionID) return;
10388
10878
  const st = stateFor(sessionID);
10389
- const span2 = tracer.startSpan(
10390
- `opencode.tool.${input?.tool ?? "tool"}`,
10391
- {
10392
- attributes: {
10393
- "neatlogs.span.kind": "TOOL",
10394
- "neatlogs.tool.name": String(input?.tool ?? ""),
10395
- ...sessionID ? { "neatlogs.conversation.id": String(sessionID) } : {},
10396
- ...output?.args !== void 0 ? { "input.value": safeStringify15(output.args) } : {}
10397
- }
10398
- },
10399
- import_api20.context.active()
10400
- );
10401
- if (callID) st.toolSpans.set(String(callID), span2);
10879
+ startRoot(st, sessionID);
10880
+ const callID = String(input?.callID ?? input?.tool ?? "");
10881
+ st.toolStarts.set(callID, {
10882
+ spanId: generateSpanId(),
10883
+ startNano: nowNanoString(),
10884
+ tool: String(input?.tool ?? "tool"),
10885
+ args: output?.args
10886
+ });
10402
10887
  } catch {
10403
10888
  }
10404
10889
  },
10405
- /** Fired after a tool runs: close the matching TOOL span. */
10406
- "tool.execute.after": async (input, output) => {
10890
+ /** Tool end enqueue the completed TOOL span (parented to the turn root). */
10891
+ "tool.execute.after": async (input, result) => {
10407
10892
  try {
10408
- const sessionID = input?.sessionID ?? "";
10409
- const callID = String(input?.callID ?? input?.tool ?? "");
10893
+ const sessionID = String(input?.sessionID ?? "");
10894
+ if (!sessionID) return;
10410
10895
  const st = stateFor(sessionID);
10411
- const span2 = st.toolSpans.get(callID);
10412
- if (!span2) return;
10413
- if (output?.title) span2.setAttribute("neatlogs.tool.title", String(output.title));
10414
- const out = output?.output ?? output?.result;
10896
+ const callID = String(input?.callID ?? input?.tool ?? "");
10897
+ const start = st.toolStarts.get(callID);
10898
+ if (!start) return;
10899
+ st.toolStarts.delete(callID);
10900
+ const attrs = [
10901
+ { key: "neatlogs.span.kind", value: { stringValue: "TOOL" } },
10902
+ { key: "neatlogs.tool.name", value: { stringValue: start.tool } },
10903
+ { key: "neatlogs.conversation.id", value: { stringValue: sessionID } }
10904
+ ];
10905
+ if (start.args !== void 0) {
10906
+ setIO(attrs, "TOOL", safeStringify15(start.args), void 0);
10907
+ push(attrs, attrStr("neatlogs.tool.input", safeStringify15(start.args)));
10908
+ }
10909
+ if (result?.title) push(attrs, attrStr("neatlogs.tool.title", String(result.title)));
10910
+ const out = result?.output ?? result?.result;
10415
10911
  if (out !== void 0) {
10416
- span2.setAttribute("output.value", typeof out === "string" ? out : safeStringify15(out));
10417
- }
10418
- if (output?.metadata !== void 0) {
10419
- span2.setAttribute("neatlogs.tool.metadata", safeStringify15(output.metadata));
10420
- }
10421
- span2.setStatus({ code: import_api20.SpanStatusCode.OK });
10422
- span2.end();
10423
- st.toolSpans.delete(callID);
10912
+ const o = typeof out === "string" ? out : safeStringify15(out);
10913
+ setIO(attrs, "TOOL", void 0, o);
10914
+ push(attrs, attrStr("neatlogs.tool.output", o));
10915
+ }
10916
+ if (result?.metadata !== void 0) {
10917
+ push(attrs, attrStr("neatlogs.tool.metadata", safeStringify15(result.metadata)));
10918
+ }
10919
+ shipper.enqueue({
10920
+ traceId: st.traceId,
10921
+ spanId: start.spanId,
10922
+ parentSpanId: st.rootSpan?.spanId,
10923
+ name: `opencode.tool.${start.tool}`,
10924
+ kind: 1,
10925
+ startTimeUnixNano: start.startNano,
10926
+ endTimeUnixNano: nowNanoString(),
10927
+ attributes: attrs,
10928
+ status: { code: SpanStatusCode16.OK }
10929
+ });
10424
10930
  } catch {
10425
10931
  }
10426
10932
  }
10427
10933
  };
10428
10934
  };
10429
- function handleEvent2(tracer, sessions, stateFor, captureSystemPrompt, event) {
10935
+ function handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, event) {
10430
10936
  const type = event?.type;
10431
10937
  const props = event?.properties ?? {};
10432
10938
  if (type === "message.part.updated" || type === "message.part.completed") {
10433
10939
  const part = props.part ?? props;
10434
10940
  const messageID = part?.messageID ?? part?.message_id;
10435
10941
  const sessionID = part?.sessionID ?? props.sessionID;
10436
- if (part?.type === "text" && typeof part?.text === "string" && messageID && sessionID) {
10437
- const st = stateFor(String(sessionID));
10438
- st.messageText.set(String(messageID), part.text);
10942
+ if (!messageID || !sessionID) return void 0;
10943
+ const st = stateFor(String(sessionID));
10944
+ if (part?.type === "text" && typeof part?.text === "string") {
10945
+ st.outputParts.set(String(messageID), part.text);
10946
+ } else if (part?.type === "tool" && part?.tool) {
10947
+ const list = st.toolCalls.get(String(messageID)) ?? [];
10948
+ const callID = String(part.callID ?? part.tool);
10949
+ const input = part?.state?.input ?? {};
10950
+ const existing = list.find((t) => t.callID === callID);
10951
+ if (existing) existing.input = input;
10952
+ else list.push({ name: String(part.tool), input, callID });
10953
+ st.toolCalls.set(String(messageID), list);
10439
10954
  }
10440
- return;
10955
+ return void 0;
10441
10956
  }
10442
10957
  if (type === "message.updated" || type === "message.completed") {
10443
10958
  const info = props.info ?? props.message ?? props;
10444
10959
  const sessionID = info?.sessionID ?? info?.session_id;
10445
- if (!sessionID) return;
10960
+ if (!sessionID) return void 0;
10961
+ if (info?.role !== "assistant") return void 0;
10962
+ const completed = info?.time?.completed ?? info?.completed;
10963
+ const id = String(info?.id ?? "");
10964
+ if (!completed || !id) return void 0;
10446
10965
  const st = stateFor(String(sessionID));
10447
- if (info?.role === "user") {
10448
- const text = messageText2(info) || st.messageText.get(String(info?.id)) || "";
10449
- if (text) st.lastUserText = text;
10450
- return;
10451
- }
10452
- if (info?.role === "assistant") {
10453
- const completed = info?.time?.completed ?? info?.completed;
10454
- const id = String(info?.id ?? "");
10455
- if (!completed || st.emitted.has(id)) return;
10456
- st.emitted.add(id);
10457
- emitLlmSpan2(tracer, st, captureSystemPrompt, info, String(sessionID));
10458
- }
10459
- return;
10966
+ if (st.processed.has(id)) return void 0;
10967
+ st.processed.add(id);
10968
+ startRoot(st, String(sessionID));
10969
+ emitLlmSpan2(shipper, st, info, String(sessionID));
10970
+ return shipper.flush().catch(() => void 0);
10460
10971
  }
10461
10972
  if (type === "session.idle" || type === "session.deleted") {
10462
10973
  const sessionID = props.sessionID ?? props.info?.id;
10463
10974
  if (!sessionID) return void 0;
10464
10975
  const st = sessions.get(String(sessionID));
10465
10976
  if (!st) return void 0;
10466
- for (const ts of st.toolSpans.values()) {
10467
- try {
10468
- ts.end();
10469
- } catch {
10470
- }
10471
- }
10472
- st.toolSpans.clear();
10977
+ st.toolStarts.clear();
10978
+ const p = closeAndFlush(st, String(sessionID));
10473
10979
  if (type === "session.deleted") sessions.delete(String(sessionID));
10474
- return flush().catch(() => false);
10980
+ return p;
10475
10981
  }
10476
10982
  return void 0;
10477
10983
  }
10478
- function emitLlmSpan2(tracer, st, captureSystemPrompt, info, sessionID) {
10984
+ function emitLlmSpan2(shipper, st, info, sessionID) {
10479
10985
  const model = info?.modelID ?? info?.model ?? "";
10480
10986
  const provider = info?.providerID ?? info?.provider ?? "";
10481
- const attrs = {
10482
- "neatlogs.span.kind": "LLM",
10483
- "neatlogs.conversation.id": sessionID
10484
- };
10485
- if (model) attrs["neatlogs.llm.model_name"] = String(model);
10486
- if (provider) attrs["neatlogs.llm.provider"] = String(provider);
10487
- if (captureSystemPrompt && info?.system) {
10488
- const sys = Array.isArray(info.system) ? info.system.join("\n") : String(info.system);
10489
- attrs["neatlogs.llm.input_messages.0.role"] = "system";
10490
- attrs["neatlogs.llm.input_messages.0.content"] = sys;
10491
- }
10492
- let inIdx = captureSystemPrompt && info?.system ? 1 : 0;
10493
- if (st.lastUserText) {
10494
- attrs[`neatlogs.llm.input_messages.${inIdx}.role`] = "user";
10495
- attrs[`neatlogs.llm.input_messages.${inIdx}.content`] = st.lastUserText;
10987
+ const attrs = [
10988
+ { key: "neatlogs.span.kind", value: { stringValue: "LLM" } },
10989
+ { key: "neatlogs.conversation.id", value: { stringValue: sessionID } }
10990
+ ];
10991
+ push(attrs, attrStr("neatlogs.llm.model_name", model ? String(model) : void 0));
10992
+ push(attrs, attrStr("neatlogs.llm.provider", provider ? String(provider) : void 0));
10993
+ let inIdx = 0;
10994
+ if (st.systemPrompt) {
10995
+ push(attrs, attrStr(`neatlogs.llm.input_messages.${inIdx}.role`, "system"));
10996
+ push(attrs, attrStr(`neatlogs.llm.input_messages.${inIdx}.content`, st.systemPrompt));
10496
10997
  inIdx++;
10497
10998
  }
10498
- const outText = messageText2(info) || st.messageText.get(String(info?.id)) || "";
10999
+ if (st.currentInput) {
11000
+ push(attrs, attrStr(`neatlogs.llm.input_messages.${inIdx}.role`, "user"));
11001
+ push(attrs, attrStr(`neatlogs.llm.input_messages.${inIdx}.content`, st.currentInput));
11002
+ setIO(attrs, "LLM", st.currentInput, void 0);
11003
+ }
11004
+ const outText = st.outputParts.get(String(info?.id)) || messageText2(info) || "";
11005
+ const toolCalls = st.toolCalls.get(String(info?.id)) ?? [];
10499
11006
  if (outText) {
10500
- attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
10501
- attrs["neatlogs.llm.output_messages.0.content"] = outText;
11007
+ push(attrs, attrStr("neatlogs.llm.output_messages.0.role", "assistant"));
11008
+ push(attrs, attrStr("neatlogs.llm.output_messages.0.content", outText));
11009
+ setIO(attrs, "LLM", void 0, outText);
11010
+ st.lastAssistantText = outText;
11011
+ }
11012
+ if (toolCalls.length) {
11013
+ toolCalls.forEach((tc, j) => {
11014
+ push(attrs, attrStr(`neatlogs.llm.tool_calls.${j}.name`, tc.name));
11015
+ push(attrs, attrStr(`neatlogs.llm.tool_calls.${j}.arguments`, safeStringify15(tc.input ?? {})));
11016
+ });
11017
+ if (!outText) {
11018
+ const rendered = toolCalls.map((tc) => `\u2192 ${tc.name}(${safeStringify15(tc.input ?? {})})`).join("\n");
11019
+ push(attrs, attrStr("neatlogs.llm.output_messages.0.role", "assistant"));
11020
+ push(attrs, attrStr("neatlogs.llm.output_messages.0.content", rendered));
11021
+ setIO(attrs, "LLM", void 0, rendered);
11022
+ }
10502
11023
  }
10503
11024
  const tokens = info?.tokens;
10504
11025
  if (tokens) {
10505
- if (tokens.input != null) attrs["neatlogs.llm.token_count.prompt"] = tokens.input;
10506
- if (tokens.output != null) attrs["neatlogs.llm.token_count.completion"] = tokens.output;
11026
+ push(attrs, attrInt("neatlogs.llm.token_count.prompt", tokens.input));
11027
+ push(attrs, attrInt("neatlogs.llm.token_count.completion", tokens.output));
10507
11028
  if (tokens.input != null && tokens.output != null) {
10508
- attrs["neatlogs.llm.token_count.total"] = tokens.input + tokens.output;
10509
- }
10510
- if (tokens.reasoning != null) attrs["neatlogs.llm.token_count.reasoning"] = tokens.reasoning;
10511
- if (tokens.cache?.read != null) attrs["neatlogs.llm.token_count.cache_read"] = tokens.cache.read;
10512
- if (tokens.cache?.write != null) attrs["neatlogs.llm.token_count.cache_write"] = tokens.cache.write;
11029
+ push(attrs, attrInt("neatlogs.llm.token_count.total", tokens.input + tokens.output));
11030
+ }
11031
+ push(attrs, attrInt("neatlogs.llm.token_count.reasoning", tokens.reasoning));
11032
+ push(attrs, attrInt("neatlogs.llm.token_count.cache_read", tokens.cache?.read));
11033
+ push(attrs, attrInt("neatlogs.llm.token_count.cache_write", tokens.cache?.write));
11034
+ }
11035
+ push(attrs, attrDouble("neatlogs.llm.cost_usd", info?.cost));
11036
+ const createdMs = info?.time?.created;
11037
+ const startNano = typeof createdMs === "number" ? msToNanoString(Math.floor(createdMs)) : nowNanoString();
11038
+ shipper.enqueue({
11039
+ traceId: st.traceId,
11040
+ spanId: generateSpanId(),
11041
+ parentSpanId: st.rootSpan?.spanId,
11042
+ name: `opencode.llm.${model || "model"}`,
11043
+ kind: 1,
11044
+ startTimeUnixNano: startNano,
11045
+ endTimeUnixNano: nowNanoString(),
11046
+ attributes: attrs,
11047
+ status: { code: SpanStatusCode16.OK }
11048
+ });
11049
+ if (info?.id) {
11050
+ st.outputParts.delete(String(info.id));
11051
+ st.toolCalls.delete(String(info.id));
11052
+ }
11053
+ }
11054
+ function push(arr, kv) {
11055
+ if (kv) arr.push(kv);
11056
+ }
11057
+ function setIO(arr, kind, input, output) {
11058
+ const k = kind.toLowerCase();
11059
+ if (input !== void 0) {
11060
+ push(arr, attrStr("input.value", input));
11061
+ push(arr, attrStr("neatlogs.input.value", input));
11062
+ push(arr, attrStr(`neatlogs.${k}.input`, input));
11063
+ }
11064
+ if (output !== void 0) {
11065
+ push(arr, attrStr("output.value", output));
11066
+ push(arr, attrStr("neatlogs.output.value", output));
11067
+ push(arr, attrStr(`neatlogs.${k}.output`, output));
10513
11068
  }
10514
- if (info?.cost != null) attrs["neatlogs.llm.cost_usd"] = info.cost;
10515
- const span2 = tracer.startSpan(`opencode.llm.${model || "model"}`, { attributes: attrs }, import_api20.context.active());
10516
- span2.setStatus({ code: import_api20.SpanStatusCode.OK });
10517
- span2.end();
10518
- if (info?.id) st.messageText.delete(String(info.id));
10519
11069
  }
10520
11070
  function messageText2(info) {
10521
11071
  if (!info) return "";
@@ -10535,7 +11085,7 @@ function safeStringify15(value) {
10535
11085
  try {
10536
11086
  return JSON.stringify(value) ?? "";
10537
11087
  } catch {
10538
- return "";
11088
+ return String(value);
10539
11089
  }
10540
11090
  }
10541
11091