neatlogs 1.1.19 → 1.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +34 -2
  2. package/dist/ai-sdk.cjs +186 -11
  3. package/dist/ai-sdk.cjs.map +1 -1
  4. package/dist/ai-sdk.d.ts +17 -7
  5. package/dist/ai-sdk.mjs +189 -12
  6. package/dist/ai-sdk.mjs.map +1 -1
  7. package/dist/anthropic.cjs +31 -14
  8. package/dist/anthropic.cjs.map +1 -1
  9. package/dist/anthropic.mjs +31 -14
  10. package/dist/anthropic.mjs.map +1 -1
  11. package/dist/azure-openai.cjs +31 -14
  12. package/dist/azure-openai.cjs.map +1 -1
  13. package/dist/azure-openai.mjs +31 -14
  14. package/dist/azure-openai.mjs.map +1 -1
  15. package/dist/bedrock.cjs +31 -14
  16. package/dist/bedrock.cjs.map +1 -1
  17. package/dist/bedrock.mjs +31 -14
  18. package/dist/bedrock.mjs.map +1 -1
  19. package/dist/browser.cjs +26 -1
  20. package/dist/browser.cjs.map +1 -1
  21. package/dist/browser.d.ts +1 -1
  22. package/dist/browser.mjs +26 -1
  23. package/dist/browser.mjs.map +1 -1
  24. package/dist/claude-agent-sdk.cjs +2 -2
  25. package/dist/claude-agent-sdk.cjs.map +1 -1
  26. package/dist/claude-agent-sdk.mjs +2 -2
  27. package/dist/claude-agent-sdk.mjs.map +1 -1
  28. package/dist/cli.cjs +452 -200
  29. package/dist/cli.cjs.map +1 -1
  30. package/dist/cli.mjs +427 -173
  31. package/dist/cli.mjs.map +1 -1
  32. package/dist/google-genai.cjs +31 -14
  33. package/dist/google-genai.cjs.map +1 -1
  34. package/dist/google-genai.mjs +31 -14
  35. package/dist/google-genai.mjs.map +1 -1
  36. package/dist/index.cjs +652 -333
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.ts +15 -6
  39. package/dist/index.mjs +505 -184
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/langchain.cjs +3 -3
  42. package/dist/langchain.cjs.map +1 -1
  43. package/dist/langchain.mjs +3 -3
  44. package/dist/langchain.mjs.map +1 -1
  45. package/dist/mastra-wrap.cjs +2 -2
  46. package/dist/mastra-wrap.cjs.map +1 -1
  47. package/dist/mastra-wrap.mjs +2 -2
  48. package/dist/mastra-wrap.mjs.map +1 -1
  49. package/dist/openai-agents.cjs.map +1 -1
  50. package/dist/openai-agents.mjs.map +1 -1
  51. package/dist/openai.cjs +31 -14
  52. package/dist/openai.cjs.map +1 -1
  53. package/dist/openai.mjs +31 -14
  54. package/dist/openai.mjs.map +1 -1
  55. package/dist/opencode-plugin.cjs +1 -1
  56. package/dist/opencode-plugin.cjs.map +1 -1
  57. package/dist/opencode-plugin.mjs +1 -1
  58. package/dist/opencode-plugin.mjs.map +1 -1
  59. package/dist/openrouter-agent.cjs +31 -14
  60. package/dist/openrouter-agent.cjs.map +1 -1
  61. package/dist/openrouter-agent.mjs +31 -14
  62. package/dist/openrouter-agent.mjs.map +1 -1
  63. package/dist/pi-agent.cjs.map +1 -1
  64. package/dist/pi-agent.mjs.map +1 -1
  65. package/dist/vertex-ai.cjs +31 -14
  66. package/dist/vertex-ai.cjs.map +1 -1
  67. package/dist/vertex-ai.mjs +31 -14
  68. package/dist/vertex-ai.mjs.map +1 -1
  69. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -58,6 +58,110 @@ function enableLogging() {
58
58
  _disabled = false;
59
59
  }
60
60
 
61
+ // src/span-kinds/constants.ts
62
+ var VECTOR_DB_SYSTEMS = /* @__PURE__ */ new Set([
63
+ "chroma",
64
+ "chromadb",
65
+ "pinecone",
66
+ "qdrant",
67
+ "milvus",
68
+ "marqo",
69
+ "weaviate",
70
+ "lancedb",
71
+ "pgvector",
72
+ "elasticsearch"
73
+ ]);
74
+ var RETRIEVAL_OPS = [
75
+ "query",
76
+ "search",
77
+ "get",
78
+ "fetch",
79
+ "find",
80
+ "retrieve",
81
+ "scroll",
82
+ "peek",
83
+ "discover",
84
+ "recommend",
85
+ "aggregate",
86
+ "hybrid_search",
87
+ "select"
88
+ ];
89
+ var VECTOR_DB_NAMES = [
90
+ "chroma",
91
+ "pinecone",
92
+ "weaviate",
93
+ "qdrant",
94
+ "milvus",
95
+ "lancedb",
96
+ "marqo"
97
+ ];
98
+
99
+ // src/span-kinds/mapping.ts
100
+ var VALID_SPAN_KINDS = /* @__PURE__ */ new Set([
101
+ "WORKFLOW",
102
+ "AGENT",
103
+ "CHAIN",
104
+ "TOOL",
105
+ "RETRIEVER",
106
+ "EMBEDDING",
107
+ "MCP_TOOL",
108
+ "GUARDRAIL",
109
+ "EVALUATOR"
110
+ ]);
111
+ var ALL_SPAN_KINDS = /* @__PURE__ */ new Set([
112
+ ...VALID_SPAN_KINDS,
113
+ "LLM",
114
+ "RERANKER",
115
+ "VECTOR_STORE"
116
+ ]);
117
+ function resolveExplicitSpanKind(attributes) {
118
+ for (const key of [
119
+ "neatlogs.span.kind",
120
+ "openinference.span.kind",
121
+ "traceloop.span.kind"
122
+ ]) {
123
+ const value = String(attributes[key] ?? "").trim();
124
+ if (value) return value;
125
+ }
126
+ return "";
127
+ }
128
+ function inferSpanKindFromName(spanName) {
129
+ const nameLower = spanName.toLowerCase();
130
+ if (["openai", "anthropic", "cohere", "bedrock", "chat", "completion", "llm", "gemini", "google_genai"].some(
131
+ (kw) => nameLower.includes(kw)
132
+ )) {
133
+ return "LLM";
134
+ }
135
+ if (nameLower.includes("embed")) {
136
+ return "EMBEDDING";
137
+ }
138
+ if (VECTOR_DB_NAMES.some((db) => nameLower.includes(db))) {
139
+ if (RETRIEVAL_OPS.some((kw) => nameLower.includes(kw))) {
140
+ return "RETRIEVER";
141
+ }
142
+ return "VECTOR_STORE";
143
+ }
144
+ if (["retriev", "search", "query"].some((kw) => nameLower.includes(kw))) {
145
+ return "RETRIEVER";
146
+ }
147
+ if (nameLower.includes("rerank")) {
148
+ return "RERANKER";
149
+ }
150
+ if (nameLower.includes("agent")) {
151
+ return "AGENT";
152
+ }
153
+ if (["tool", "function"].some((kw) => nameLower.includes(kw))) {
154
+ return "TOOL";
155
+ }
156
+ if (["guardrail", "validate", "moderate", "safety"].some((kw) => nameLower.includes(kw))) {
157
+ return "GUARDRAIL";
158
+ }
159
+ if (["evaluat", "score", "metric"].some((kw) => nameLower.includes(kw))) {
160
+ return "EVALUATOR";
161
+ }
162
+ return "CHAIN";
163
+ }
164
+
61
165
  // src/config/attribute-mapping.json
62
166
  var attribute_mapping_default = {
63
167
  $schema: "http://json-schema.org/draft-07/schema#",
@@ -1235,27 +1339,11 @@ var AttributeMapper = class {
1235
1339
  /** Extract and normalize span kind from multiple possible sources. */
1236
1340
  mapSpanKind(attributes) {
1237
1341
  const spanKindConfig = this.mappings.span_kind ?? {};
1238
- const sources = spanKindConfig.sources ?? [];
1239
1342
  const valuesMap = spanKindConfig.values ?? {};
1240
- const priority = spanKindConfig.priority ?? "openinference";
1241
- let spanKindValue;
1242
- if (priority === "openinference") {
1243
- if ("openinference.span.kind" in attributes) {
1244
- spanKindValue = attributes["openinference.span.kind"];
1245
- }
1246
- } else {
1247
- for (const source of sources) {
1248
- if (source in attributes) {
1249
- spanKindValue = attributes[source];
1250
- break;
1251
- }
1252
- }
1253
- }
1254
- if (!spanKindValue && "neatlogs.span.kind" in attributes) {
1255
- spanKindValue = attributes["neatlogs.span.kind"];
1256
- }
1257
- if (spanKindValue && spanKindValue in valuesMap) {
1258
- return valuesMap[spanKindValue];
1343
+ const spanKindValue = resolveExplicitSpanKind(attributes);
1344
+ if (spanKindValue) {
1345
+ const mapped = valuesMap[spanKindValue] ?? valuesMap[spanKindValue.toUpperCase()];
1346
+ if (mapped) return mapped;
1259
1347
  }
1260
1348
  if (spanKindValue && Object.values(valuesMap).includes(spanKindValue)) {
1261
1349
  return spanKindValue;
@@ -1829,7 +1917,7 @@ function clearDoctorCapture() {
1829
1917
 
1830
1918
  // src/core/context.ts
1831
1919
  import {
1832
- trace as otelTrace2,
1920
+ trace as otelTrace3,
1833
1921
  createContextKey as createContextKey2,
1834
1922
  ROOT_CONTEXT as ROOT_CONTEXT3,
1835
1923
  SpanStatusCode as SpanStatusCode3
@@ -2177,11 +2265,11 @@ function getNeatlogsRootSpan() {
2177
2265
  function getNeatlogsParentContext(baseContext) {
2178
2266
  return baseContext ?? getNeatlogsActiveContext();
2179
2267
  }
2180
- function withNeatlogsSpan(span2, fn, baseContext) {
2268
+ function withNeatlogsSpan(span2, fn, baseContext, rootSpan) {
2181
2269
  const base = baseContext ?? getNeatlogsActiveContext();
2182
2270
  let ctx = otelTrace.setSpan(base, span2);
2183
2271
  if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === void 0) {
2184
- ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span2);
2272
+ ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, rootSpan ?? span2);
2185
2273
  }
2186
2274
  return privateContextStorage.run(ctx, fn);
2187
2275
  }
@@ -3913,6 +4001,84 @@ function discardPendingMediaOwner(owner) {
3913
4001
  pendingMediaOwners.delete(owner ?? defaultPendingMediaOwner);
3914
4002
  }
3915
4003
 
4004
+ // src/core/auto-root.ts
4005
+ import {
4006
+ trace as otelTrace2
4007
+ } from "@opentelemetry/api";
4008
+ var boundAutoRoots = /* @__PURE__ */ new WeakMap();
4009
+ var ROOT_KINDS = /* @__PURE__ */ new Set(["workflow", "chain", "agent", "mcp_tool", "llm"]);
4010
+ function autoRootEnabled() {
4011
+ const val = (process.env.NEATLOGS_AUTO_ROOT ?? "").trim().toLowerCase();
4012
+ return !["false", "0", "no", "off"].includes(val);
4013
+ }
4014
+ function resolveRootWorkflowName() {
4015
+ const clientName = getActiveClient()?.workflowName;
4016
+ if (typeof clientName === "string" && clientName.trim()) return clientName;
4017
+ try {
4018
+ const name = getSessionConfig()?.workflowName;
4019
+ if (typeof name === "string" && name.trim()) return name;
4020
+ } catch {
4021
+ }
4022
+ return "workflow";
4023
+ }
4024
+ function stampRootIdentity(root) {
4025
+ applySessionAttributes(root, void 0, true);
4026
+ applyEndUserAttributes(root, void 0, void 0, true);
4027
+ }
4028
+ function bindSpanToAutoRoot(child, root) {
4029
+ if (!root) return child;
4030
+ let ended = false;
4031
+ const proxy = new Proxy(child, {
4032
+ get(target, prop, _receiver) {
4033
+ if (prop === "end") {
4034
+ return (...args) => {
4035
+ if (ended) return;
4036
+ ended = true;
4037
+ try {
4038
+ target.end(...args);
4039
+ } finally {
4040
+ try {
4041
+ root.end();
4042
+ } catch {
4043
+ }
4044
+ }
4045
+ };
4046
+ }
4047
+ const value = Reflect.get(target, prop, target);
4048
+ return typeof value === "function" ? value.bind(target) : value;
4049
+ }
4050
+ });
4051
+ boundAutoRoots.set(proxy, root);
4052
+ aliasMediaCaptureSpan(proxy, child);
4053
+ return proxy;
4054
+ }
4055
+ function maybeOpenAutoRoot(tracer, kind, baseCtx) {
4056
+ const k = String(kind ?? "").toLowerCase();
4057
+ const existing = getActiveNeatlogsSpan();
4058
+ if (!autoRootEnabled() || ROOT_KINDS.has(k) || existing !== void 0 && existing.isRecording()) {
4059
+ return { root: void 0, ctx: baseCtx };
4060
+ }
4061
+ const root = tracer.startSpan(
4062
+ resolveRootWorkflowName(),
4063
+ { attributes: { "neatlogs.span.kind": "workflow", "neatlogs.auto_root": true } },
4064
+ baseCtx
4065
+ );
4066
+ stampRootIdentity(root);
4067
+ return { root, ctx: otelTrace2.setSpan(baseCtx, root) };
4068
+ }
4069
+
4070
+ // src/errors.ts
4071
+ var NeatlogsConfigurationError = class extends TypeError {
4072
+ code;
4073
+ option;
4074
+ constructor(code, option, message) {
4075
+ super(message);
4076
+ this.name = "NeatlogsConfigurationError";
4077
+ this.code = code;
4078
+ this.option = option;
4079
+ }
4080
+ };
4081
+
3916
4082
  // src/decorators/base.ts
3917
4083
  import {
3918
4084
  ROOT_CONTEXT as ROOT_CONTEXT2,
@@ -3990,7 +4156,28 @@ function decorateSpan(opts, fn) {
3990
4156
  const tracer = getNeatlogsTracer(TRACER_NAME);
3991
4157
  const isRoot = isRootSpan();
3992
4158
  const parentContext = isRoot ? ROOT_CONTEXT2 : getNeatlogsParentContext();
3993
- const span2 = tracer.startSpan(spanName, {}, parentContext);
4159
+ const autoRoot = maybeOpenAutoRoot(
4160
+ tracer,
4161
+ String(opts.kind ?? ""),
4162
+ parentContext
4163
+ );
4164
+ if (autoRoot.root) {
4165
+ applySessionAttributes(autoRoot.root, opts.sessionId, true, {
4166
+ parentSessionId: opts.parentSessionId,
4167
+ sessionFeatureName: opts.sessionFeatureName,
4168
+ sessionEntryPoint: opts.sessionEntryPoint
4169
+ });
4170
+ applyEndUserAttributes(
4171
+ autoRoot.root,
4172
+ opts.endUserId,
4173
+ opts.endUserMetadata,
4174
+ true
4175
+ );
4176
+ }
4177
+ const span2 = bindSpanToAutoRoot(
4178
+ tracer.startSpan(spanName, {}, autoRoot.ctx),
4179
+ autoRoot.root
4180
+ );
3994
4181
  return withNeatlogsSpan(span2, () => {
3995
4182
  let finished = false;
3996
4183
  let streamInterrupted = false;
@@ -4359,7 +4546,8 @@ function decorateSpan(opts, fn) {
4359
4546
  });
4360
4547
  try {
4361
4548
  setCommonSpanAttrs(span2, opts);
4362
- applySessionAttributes(span2, opts.sessionId, isRoot, {
4549
+ const spanIsRoot = isRoot && !autoRoot.root;
4550
+ applySessionAttributes(span2, opts.sessionId, spanIsRoot, {
4363
4551
  parentSessionId: opts.parentSessionId,
4364
4552
  sessionFeatureName: opts.sessionFeatureName,
4365
4553
  sessionEntryPoint: opts.sessionEntryPoint
@@ -4368,7 +4556,7 @@ function decorateSpan(opts, fn) {
4368
4556
  span2,
4369
4557
  opts.endUserId,
4370
4558
  opts.endUserMetadata,
4371
- isRoot
4559
+ spanIsRoot
4372
4560
  );
4373
4561
  if (captureInput && args.length > 0) {
4374
4562
  try {
@@ -4408,7 +4596,7 @@ function decorateSpan(opts, fn) {
4408
4596
  finishError(error);
4409
4597
  throw error;
4410
4598
  }
4411
- });
4599
+ }, autoRoot.ctx, autoRoot.root);
4412
4600
  };
4413
4601
  Object.defineProperty(wrapped, "name", {
4414
4602
  value: spanName,
@@ -4437,6 +4625,9 @@ var _sessionConfig = {};
4437
4625
  function _setSessionConfig(config) {
4438
4626
  _sessionConfig = config;
4439
4627
  }
4628
+ function getSessionConfig() {
4629
+ return { ..._sessionConfig };
4630
+ }
4440
4631
  var PROMPT_VARIABLES_KEY = createContextKey2("neatlogs.prompt_variables");
4441
4632
  var PROMPT_TEMPLATE_KEY = createContextKey2("neatlogs.prompt_template");
4442
4633
  var PROMPT_VERSION_KEY = createContextKey2("neatlogs.prompt_version");
@@ -4460,6 +4651,20 @@ var KNOWN_OPTION_KEYS = /* @__PURE__ */ new Set([
4460
4651
  "endUserId",
4461
4652
  "endUserMetadata"
4462
4653
  ]);
4654
+ function assertNoHttpSpanKind(kind, attributes) {
4655
+ const candidates = [
4656
+ kind,
4657
+ attributes?.["neatlogs.span.kind"],
4658
+ attributes?.["openinference.span.kind"]
4659
+ ];
4660
+ if (candidates.some((value) => String(value ?? "").trim().toUpperCase() === "HTTP")) {
4661
+ throw new NeatlogsConfigurationError(
4662
+ "UNSUPPORTED_SPAN_KIND",
4663
+ "kind",
4664
+ "HTTP spans are not supported. Trace the semantic LLM, TOOL, RETRIEVER, or other AI operation instead."
4665
+ );
4666
+ }
4667
+ }
4463
4668
  function _setSpanAttributes(span2, kind, attributes) {
4464
4669
  if (kind?.toUpperCase() === "LLM") {
4465
4670
  span2.setAttribute("neatlogs.internal", true);
@@ -4530,6 +4735,7 @@ async function trace(options, fn) {
4530
4735
  attributes: explicitAttributes,
4531
4736
  ...extraOptions
4532
4737
  } = options;
4738
+ assertNoHttpSpanKind(kind, explicitAttributes);
4533
4739
  const sessionId = sessionIdOption ?? currentSessionId();
4534
4740
  const currentSpan = getActiveNeatlogsSpan();
4535
4741
  const rootSpan = getNeatlogsRootSpan();
@@ -4574,7 +4780,7 @@ async function trace(options, fn) {
4574
4780
  }
4575
4781
  let ctx = getNeatlogsActiveContext();
4576
4782
  if (shouldFallbackToRoot) {
4577
- ctx = otelTrace2.setSpan(ctx, rootSpan);
4783
+ ctx = otelTrace3.setSpan(ctx, rootSpan);
4578
4784
  }
4579
4785
  const variablesJson = promptVariables ? JSON.stringify(promptVariables) : void 0;
4580
4786
  const userVariablesJson = userPromptVariables ? JSON.stringify(userPromptVariables) : void 0;
@@ -4608,12 +4814,13 @@ async function trace(options, fn) {
4608
4814
  const tracer = getNeatlogsTracer("neatlogs.trace");
4609
4815
  const spanCallback = async (span3) => {
4610
4816
  _setSpanAttributes(span3, kind, extraAttributes);
4611
- applySessionAttributes(span3, sessionId, isRootTrace, {
4817
+ const spanIsRoot = isRootTrace && !autoRoot.root;
4818
+ applySessionAttributes(span3, sessionId, spanIsRoot, {
4612
4819
  parentSessionId,
4613
4820
  sessionFeatureName,
4614
4821
  sessionEntryPoint
4615
4822
  });
4616
- applyEndUserAttributes(span3, endUserId, endUserMetadata, isRootTrace);
4823
+ applyEndUserAttributes(span3, endUserId, endUserMetadata, spanIsRoot);
4617
4824
  if (input !== void 0 && input !== null) {
4618
4825
  const capturedInput = captureMedia(
4619
4826
  span3,
@@ -4665,106 +4872,34 @@ async function trace(options, fn) {
4665
4872
  };
4666
4873
  const parentContext = shouldCreateRootTrace ? ROOT_CONTEXT3 : getNeatlogsParentContext(ctx);
4667
4874
  const startAttributes = kind ? { "openinference.span.kind": kind } : void 0;
4668
- const span2 = tracer.startSpan(name, startAttributes ? { attributes: startAttributes } : {}, parentContext);
4875
+ const autoRoot = isRootTrace ? maybeOpenAutoRoot(tracer, String(kind ?? "CHAIN"), parentContext) : { root: void 0, ctx: parentContext };
4876
+ if (autoRoot.root) {
4877
+ applySessionAttributes(autoRoot.root, sessionId, true, {
4878
+ parentSessionId,
4879
+ sessionFeatureName,
4880
+ sessionEntryPoint
4881
+ });
4882
+ applyEndUserAttributes(autoRoot.root, endUserId, endUserMetadata, true);
4883
+ }
4884
+ const span2 = bindSpanToAutoRoot(
4885
+ tracer.startSpan(
4886
+ name,
4887
+ startAttributes ? { attributes: startAttributes } : {},
4888
+ autoRoot.ctx
4889
+ ),
4890
+ autoRoot.root
4891
+ );
4669
4892
  if (shouldCreateRootTrace) {
4670
4893
  logger6.debug(`[trace] Creating NEW root trace '${name}' (sessionId=${sessionId})`);
4671
4894
  } else {
4672
4895
  logger6.debug(`[trace] Creating child span '${name}'`);
4673
4896
  }
4674
- return withNeatlogsSpan(span2, () => spanCallback(span2), ctx);
4675
- }
4676
-
4677
- // src/span-kinds/constants.ts
4678
- var VECTOR_DB_SYSTEMS = /* @__PURE__ */ new Set([
4679
- "chroma",
4680
- "chromadb",
4681
- "pinecone",
4682
- "qdrant",
4683
- "milvus",
4684
- "marqo",
4685
- "weaviate",
4686
- "lancedb",
4687
- "pgvector",
4688
- "elasticsearch"
4689
- ]);
4690
- var RETRIEVAL_OPS = [
4691
- "query",
4692
- "search",
4693
- "get",
4694
- "fetch",
4695
- "find",
4696
- "retrieve",
4697
- "scroll",
4698
- "peek",
4699
- "discover",
4700
- "recommend",
4701
- "aggregate",
4702
- "hybrid_search",
4703
- "select"
4704
- ];
4705
- var VECTOR_DB_NAMES = [
4706
- "chroma",
4707
- "pinecone",
4708
- "weaviate",
4709
- "qdrant",
4710
- "milvus",
4711
- "lancedb",
4712
- "marqo"
4713
- ];
4714
-
4715
- // src/span-kinds/mapping.ts
4716
- var VALID_SPAN_KINDS = /* @__PURE__ */ new Set([
4717
- "WORKFLOW",
4718
- "AGENT",
4719
- "CHAIN",
4720
- "TOOL",
4721
- "RETRIEVER",
4722
- "EMBEDDING",
4723
- "MCP_TOOL",
4724
- "GUARDRAIL"
4725
- ]);
4726
- var ALL_SPAN_KINDS = /* @__PURE__ */ new Set([
4727
- ...VALID_SPAN_KINDS,
4728
- "LLM",
4729
- "RERANKER",
4730
- "EVALUATOR",
4731
- "VECTOR_STORE"
4732
- ]);
4733
- function inferSpanKindFromName(spanName) {
4734
- const nameLower = spanName.toLowerCase();
4735
- if (["openai", "anthropic", "cohere", "bedrock", "chat", "completion", "llm", "gemini", "google_genai"].some(
4736
- (kw) => nameLower.includes(kw)
4737
- )) {
4738
- return "LLM";
4739
- }
4740
- if (nameLower.includes("embed")) {
4741
- return "EMBEDDING";
4742
- }
4743
- if (VECTOR_DB_NAMES.some((db) => nameLower.includes(db))) {
4744
- if (RETRIEVAL_OPS.some((kw) => nameLower.includes(kw))) {
4745
- return "RETRIEVER";
4746
- }
4747
- return "VECTOR_STORE";
4748
- }
4749
- if (["retriev", "search", "query"].some((kw) => nameLower.includes(kw))) {
4750
- return "RETRIEVER";
4751
- }
4752
- if (nameLower.includes("rerank")) {
4753
- return "RERANKER";
4754
- }
4755
- if (nameLower.includes("agent")) {
4756
- return "AGENT";
4757
- }
4758
- if (["tool", "function"].some((kw) => nameLower.includes(kw))) {
4759
- return "TOOL";
4760
- }
4761
- if (["guardrail", "validate", "moderate", "safety"].some((kw) => nameLower.includes(kw))) {
4762
- return "GUARDRAIL";
4763
- }
4764
- if (["evaluat", "score", "metric"].some((kw) => nameLower.includes(kw))) {
4765
- return "EVALUATOR";
4766
- }
4767
- return "CHAIN";
4897
+ return withNeatlogsSpan(
4898
+ span2,
4899
+ () => spanCallback(span2),
4900
+ autoRoot.ctx,
4901
+ autoRoot.root
4902
+ );
4768
4903
  }
4769
4904
 
4770
4905
  // src/decorators/orchestration.ts
@@ -5284,6 +5419,7 @@ var neatlogs_telemetry_schema_default = {
5284
5419
  "VECTOR_STORE",
5285
5420
  "MEMORY",
5286
5421
  "GUARDRAIL",
5422
+ "EVALUATOR",
5287
5423
  "LOG",
5288
5424
  "HTTP",
5289
5425
  "UNKNOWN"
@@ -6712,6 +6848,7 @@ var neatlogs_telemetry_schema_default = {
6712
6848
  "AGENT",
6713
6849
  "CHAIN",
6714
6850
  "TASK",
6851
+ "EVALUATOR",
6715
6852
  "HTTP",
6716
6853
  "UNKNOWN"
6717
6854
  ]
@@ -6875,6 +7012,7 @@ var neatlogs_telemetry_schema_default = {
6875
7012
  "neatlogs.vector_store.*",
6876
7013
  "neatlogs.memory.*",
6877
7014
  "neatlogs.guardrail.*",
7015
+ "neatlogs.evaluator.*",
6878
7016
  "neatlogs.mcp.*",
6879
7017
  "neatlogs.code.*"
6880
7018
  ],
@@ -6958,7 +7096,7 @@ var TELEMETRY_CONFLICT_PRECEDENCE = Object.freeze(
6958
7096
  );
6959
7097
 
6960
7098
  // src/version.ts
6961
- var __version__ = "1.1.19";
7099
+ var __version__ = "1.1.21";
6962
7100
 
6963
7101
  // src/init.ts
6964
7102
  import * as path2 from "path";
@@ -6980,16 +7118,16 @@ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
6980
7118
  import * as fs from "fs";
6981
7119
  import * as path from "path";
6982
7120
  import { performance } from "perf_hooks";
6983
- import { ROOT_CONTEXT as ROOT_CONTEXT4, trace as otelTrace3, TraceFlags, SpanStatusCode as SpanStatusCode4 } from "@opentelemetry/api";
7121
+ import { ROOT_CONTEXT as ROOT_CONTEXT4, trace as otelTrace4, TraceFlags, SpanStatusCode as SpanStatusCode4 } from "@opentelemetry/api";
6984
7122
 
6985
7123
  // src/core/instrumentation-scope-parser.ts
6986
7124
  var SCOPE_PATTERNS = {
6987
7125
  // Neatlogs custom instrumentations
6988
7126
  "@neatlogs/instrumentation-google-genai": { provider: "google", framework: "google_genai" },
6989
7127
  "@neatlogs/instrumentation-mastra": { framework: "mastra" },
6990
- "@neatlogs/instrumentation-ai-sdk": { framework: "ai_sdk" },
7128
+ "@neatlogs/instrumentation-ai-sdk": { framework: "vercel_ai_sdk" },
6991
7129
  // Vercel AI SDK native scope (the `ai` package emits this directly)
6992
- ai: { framework: "ai_sdk" },
7130
+ ai: { framework: "vercel_ai_sdk" },
6993
7131
  // OpenInference instrumentations (npm @arizeai scope names)
6994
7132
  "@arizeai/openinference-instrumentation-openai": { provider: "openai", framework: "openai" },
6995
7133
  "@arizeai/openinference-instrumentation-anthropic": { provider: "anthropic", framework: "anthropic" },
@@ -7078,7 +7216,7 @@ function parseInstrumentationScope(scopeName) {
7078
7216
  } else if (scopeLower.includes("dspy")) {
7079
7217
  result.framework = "dspy";
7080
7218
  } else if (scopeLower.includes("vercel-ai") || scopeLower.includes("ai-sdk")) {
7081
- result.framework = "ai_sdk";
7219
+ result.framework = "vercel_ai_sdk";
7082
7220
  }
7083
7221
  if (scopeLower.includes("openai")) {
7084
7222
  result.provider = "openai";
@@ -8967,6 +9105,46 @@ function addVerificationMarkerResourceAttribute(attributes) {
8967
9105
  if (marker) attributes[VERIFICATION_MARKER_KEY] = marker;
8968
9106
  }
8969
9107
 
9108
+ // src/core/http-span.ts
9109
+ import { SpanKind } from "@opentelemetry/api";
9110
+ var HTTP_SCOPE_PREFIXES = [
9111
+ "@opentelemetry/instrumentation-http",
9112
+ "@opentelemetry/instrumentation-fetch",
9113
+ "@opentelemetry/instrumentation-undici"
9114
+ ];
9115
+ var HTTP_ATTRIBUTE_KEYS = [
9116
+ "http.method",
9117
+ "http.request.method",
9118
+ "http.url",
9119
+ "http.route",
9120
+ "url.full"
9121
+ ];
9122
+ var SEMANTIC_AI_KINDS = /* @__PURE__ */ new Set([
9123
+ "WORKFLOW",
9124
+ "AGENT",
9125
+ "CHAIN",
9126
+ "TOOL",
9127
+ "RETRIEVER",
9128
+ "EMBEDDING",
9129
+ "GUARDRAIL",
9130
+ "LLM",
9131
+ "RERANKER",
9132
+ "VECTOR_STORE",
9133
+ "TASK",
9134
+ "EVALUATOR",
9135
+ "LOG",
9136
+ "MEMORY",
9137
+ "MCP_TOOL"
9138
+ ]);
9139
+ function isHttpSpan(span2, attributes = span2.attributes ?? {}) {
9140
+ const resolvedKind = resolveExplicitSpanKind(attributes).toUpperCase();
9141
+ if (resolvedKind === "HTTP") return true;
9142
+ if (SEMANTIC_AI_KINDS.has(resolvedKind)) return false;
9143
+ const scopeName = span2.instrumentationLibrary.name ?? "";
9144
+ if (HTTP_SCOPE_PREFIXES.some((prefix) => scopeName.startsWith(prefix))) return true;
9145
+ return span2.kind === SpanKind.CLIENT && HTTP_ATTRIBUTE_KEYS.some((key) => key in attributes);
9146
+ }
9147
+
8970
9148
  // src/core/span-processor.ts
8971
9149
  var logger11 = getLogger();
8972
9150
  function resolveLogFilePath(configuredPath) {
@@ -9238,6 +9416,7 @@ var NeatlogsSpanProcessor = class {
9238
9416
  onEnd(span2) {
9239
9417
  this._activeSpans.delete(span2.spanContext().spanId);
9240
9418
  if (this._closed) return;
9419
+ if (isHttpSpan(span2)) return;
9241
9420
  if (span2.name === "neatlogs.trace.complete") {
9242
9421
  const markerData = spanToDict(span2, { includeScope: true });
9243
9422
  markerData.resource = { attributes: markerData.resource };
@@ -9397,6 +9576,7 @@ var NeatlogsSpanProcessor = class {
9397
9576
  const maskResult = scheduleMask(span2, spanData, this.mask ?? null);
9398
9577
  const pending = maskResult.then((maskedSpanData) => {
9399
9578
  if (maskedSpanData === null) return;
9579
+ if (isHttpSpan(span2, maskedSpanData.attributes ?? {})) return;
9400
9580
  if (this._processedLogStream && !this._processedLogStream.destroyed) {
9401
9581
  try {
9402
9582
  this._processedLogStream.write(JSON.stringify(maskedSpanData) + "\n");
@@ -9495,8 +9675,8 @@ var NeatlogsSpanProcessor = class {
9495
9675
  isRemote: false,
9496
9676
  traceFlags: TraceFlags.SAMPLED
9497
9677
  };
9498
- const wrappedSpan = otelTrace3.wrapSpanContext(spanCtx);
9499
- const ctx = otelTrace3.setSpan(ROOT_CONTEXT4, wrappedSpan);
9678
+ const wrappedSpan = otelTrace4.wrapSpanContext(spanCtx);
9679
+ const ctx = otelTrace4.setSpan(ROOT_CONTEXT4, wrappedSpan);
9500
9680
  const marker = markerTracer.startSpan("neatlogs.trace.complete", void 0, ctx);
9501
9681
  marker.setAttribute("neatlogs.trace.complete", true);
9502
9682
  marker.setAttribute("neatlogs.internal", true);
@@ -9665,7 +9845,8 @@ var CompletionMarkerSpanProcessor = class {
9665
9845
  onStart(_span, _parentContext) {
9666
9846
  }
9667
9847
  onEnd(span2) {
9668
- if (this.closed || span2.name === "neatlogs.trace.complete" || span2.parentSpanId) return;
9848
+ if (this.closed || span2.name === "neatlogs.trace.complete" || span2.parentSpanId || isHttpSpan(span2))
9849
+ return;
9669
9850
  if (!this.source.ownsSpan(span2)) return;
9670
9851
  const task = this.source.whenMaskComplete(span2).then((eligible) => {
9671
9852
  if (!eligible || this.closed) return;
@@ -9805,6 +9986,10 @@ var FilteringExporter = class {
9805
9986
  _onPrepared;
9806
9987
  export(spans, resultCallback) {
9807
9988
  void mapWithConcurrency(spans, MEDIA_RESOLUTION_CONCURRENCY, async (span2) => {
9989
+ if (isHttpSpan(span2)) {
9990
+ discardPendingMedia(span2);
9991
+ return { span: null, mediaReady: true };
9992
+ }
9808
9993
  if (span2.instrumentationLibrary.name === "next.js") {
9809
9994
  discardPendingMedia(span2);
9810
9995
  this.diagnostics?.recordFrameworkSpanDrop();
@@ -9825,6 +10010,11 @@ var FilteringExporter = class {
9825
10010
  ...masked.attributes && typeof masked.attributes === "object" ? masked.attributes : {}
9826
10011
  };
9827
10012
  const maskedData = { ...masked, attributes };
10013
+ const maskedSpan = maskedReadableSpan(span2, maskedData);
10014
+ if (isHttpSpan(maskedSpan)) {
10015
+ discardPendingMedia(span2);
10016
+ return { span: null, mediaReady: true };
10017
+ }
9828
10018
  captureMediaInSnapshot(
9829
10019
  span2,
9830
10020
  maskedData,
@@ -11132,18 +11322,6 @@ function closeSharedClient() {
11132
11322
  function _resetMastraCache() {
11133
11323
  }
11134
11324
 
11135
- // src/errors.ts
11136
- var NeatlogsConfigurationError = class extends TypeError {
11137
- code;
11138
- option;
11139
- constructor(code, option, message) {
11140
- super(message);
11141
- this.name = "NeatlogsConfigurationError";
11142
- this.code = code;
11143
- this.option = option;
11144
- }
11145
- };
11146
-
11147
11325
  // src/core/deadline.ts
11148
11326
  async function runByDeadline(operation, deadlineMs) {
11149
11327
  const remaining = Math.max(0, deadlineMs - Date.now());
@@ -11244,6 +11422,24 @@ var INIT_OPTION_KEYS = /* @__PURE__ */ new Set([
11244
11422
  "piiSpanTypes",
11245
11423
  "uploadAuthority"
11246
11424
  ]);
11425
+ function resolveInitEndpoint(endpoint) {
11426
+ const explicit = (endpoint ?? "").trim();
11427
+ if (explicit) return explicit;
11428
+ const fromEnv = (process.env.NEATLOGS_ENDPOINT ?? "").trim();
11429
+ return fromEnv || DEFAULT_INGEST_ENDPOINT;
11430
+ }
11431
+ function resolveIngestBaseUrl(endpoint) {
11432
+ const parsed = new URL(endpoint.trim());
11433
+ const path3 = parsed.pathname.replace(/\/+$/, "");
11434
+ if (path3 !== "" && path3 !== "/v1/traces") {
11435
+ throw new NeatlogsConfigurationError(
11436
+ "INVALID_ENDPOINT",
11437
+ "endpoint",
11438
+ "endpoint must be a base URL or an OTLP traces URL ending in /v1/traces."
11439
+ );
11440
+ }
11441
+ return parsed.origin;
11442
+ }
11247
11443
  function validateInitOptions(options) {
11248
11444
  const raw = options;
11249
11445
  if (Object.prototype.hasOwnProperty.call(raw, "instrumentations")) {
@@ -11327,8 +11523,13 @@ function stableValue(value, ancestors = /* @__PURE__ */ new WeakSet(), location
11327
11523
  ancestors.delete(value);
11328
11524
  }
11329
11525
  }
11526
+ function resolveApiKey(apiKey) {
11527
+ const explicit = (apiKey ?? "").trim();
11528
+ if (explicit) return explicit;
11529
+ return (process.env.NEATLOGS_API_KEY ?? "").trim();
11530
+ }
11330
11531
  function initIdentity(options) {
11331
- const apiKey = (options.apiKey ?? process.env.NEATLOGS_API_KEY ?? "").trim();
11532
+ const apiKey = resolveApiKey(options.apiKey);
11332
11533
  const apiKeyDigest = createHash4("sha256").update(apiKey).digest("hex");
11333
11534
  const serialized = stableValue({
11334
11535
  apiKeyDigest,
@@ -11348,7 +11549,7 @@ function initIdentity(options) {
11348
11549
  captureLogs: options.captureLogs ?? false,
11349
11550
  pii: options.pii ?? null,
11350
11551
  version: options.version ?? null,
11351
- endpoint: options.endpoint ?? DEFAULT_INGEST_ENDPOINT,
11552
+ endpoint: resolveInitEndpoint(options.endpoint),
11352
11553
  batchSize: options.batchSize ?? 100,
11353
11554
  flushInterval: options.flushInterval ?? 5,
11354
11555
  piiEnabled: options.piiEnabled ?? null,
@@ -11428,12 +11629,7 @@ async function _performInit(options) {
11428
11629
  );
11429
11630
  }
11430
11631
  _deliveryDiagnostics = new DeliveryDiagnostics();
11431
- let resolvedKey;
11432
- if (options.apiKey && options.apiKey.trim()) {
11433
- resolvedKey = options.apiKey.trim();
11434
- } else {
11435
- resolvedKey = (process.env.NEATLOGS_API_KEY ?? "").trim();
11436
- }
11632
+ let resolvedKey = resolveApiKey(options.apiKey);
11437
11633
  let disableExportResolved = !!options.disableExport || ["true", "1", "yes"].includes(
11438
11634
  (process.env.NEATLOGS_DISABLE_EXPORT ?? "").toLowerCase()
11439
11635
  );
@@ -11454,8 +11650,8 @@ async function _performInit(options) {
11454
11650
  }
11455
11651
  _debugMode2 = options.debug ?? false;
11456
11652
  const resolvedWorkflowName = _resolveWorkflowName(options.workflowName);
11457
- const endpoint = options.endpoint ?? DEFAULT_INGEST_ENDPOINT;
11458
- const baseUrl = new URL(endpoint).origin;
11653
+ const endpoint = resolveInitEndpoint(options.endpoint);
11654
+ const baseUrl = resolveIngestBaseUrl(endpoint);
11459
11655
  const uploadAuthority = disableExportResolved ? new DisabledUploadAuthority("export_disabled") : resolveUploadAuthority(
11460
11656
  options.uploadAuthority,
11461
11657
  process.env.NEATLOGS_UPLOADS_ENABLED,
@@ -12175,6 +12371,36 @@ var INGESTION_STAGES = /* @__PURE__ */ new Set([
12175
12371
  ]);
12176
12372
  var INGESTION_STATES = /* @__PURE__ */ new Set(["processing", "failed", "succeeded"]);
12177
12373
  var SAFE_FAILURE_CODE = /^[A-Z0-9_]{1,64}$/;
12374
+ var PROBE_FAILURE = {
12375
+ AUTH_FAILED: {
12376
+ remediationCode: "CHECK_INGEST_CREDENTIAL",
12377
+ message: "The project key was rejected by the existing trace API"
12378
+ },
12379
+ BACKEND_PROBE_UNAVAILABLE: {
12380
+ remediationCode: "CHECK_TRACE_ENDPOINT",
12381
+ message: "The existing trace ingestion or read path is unavailable"
12382
+ },
12383
+ TRACE_READBACK_TIMEOUT: {
12384
+ remediationCode: "WAIT_FOR_TRACE",
12385
+ message: "Timed out waiting for the exact Doctor trace"
12386
+ },
12387
+ INGESTION_PIPELINE_FAILED: {
12388
+ remediationCode: "CONTACT_SUPPORT",
12389
+ message: "Trace ingestion reported a terminal pipeline failure"
12390
+ },
12391
+ BACKEND_HTTP_ERROR: {
12392
+ remediationCode: "CHECK_TRACE_ENDPOINT",
12393
+ message: "The existing trace read path returned an unexpected HTTP status"
12394
+ },
12395
+ BACKEND_CONNECTION_FAILED: {
12396
+ remediationCode: "CHECK_TRACE_ENDPOINT",
12397
+ message: "Could not connect to the existing trace read path"
12398
+ },
12399
+ TRACE_READBACK_INVALID: {
12400
+ remediationCode: "CONTACT_SUPPORT",
12401
+ message: "Trace read-back returned an invalid response"
12402
+ }
12403
+ };
12178
12404
  var ProbeReadError = class extends Error {
12179
12405
  constructor(reasonCode, message, details) {
12180
12406
  super(message);
@@ -12294,7 +12520,7 @@ function validateLocalFixture(traceId) {
12294
12520
  check("controlled_fixture_bounds", stats.droppedSpans === 0, "CONTROLLED_BOUNDS_VALID", "CONTROLLED_CAPTURE_TRUNCATED", "REDUCE_DIAGNOSTIC_CAPTURE", "Doctor stayed inside the bounded capture budget")
12295
12521
  ];
12296
12522
  }
12297
- async function withTimeout(operation, timeoutMs, onTimeout) {
12523
+ async function withTimeout(operation, timeoutMs, onTimeout, timeoutReason = "BACKEND_PROBE_UNAVAILABLE") {
12298
12524
  let timer;
12299
12525
  try {
12300
12526
  return await Promise.race([
@@ -12302,7 +12528,7 @@ async function withTimeout(operation, timeoutMs, onTimeout) {
12302
12528
  new Promise((_resolve, reject) => {
12303
12529
  timer = setTimeout(() => {
12304
12530
  onTimeout?.();
12305
- reject(new ProbeReadError("BACKEND_PROBE_UNAVAILABLE", "Doctor operation timed out"));
12531
+ reject(new ProbeReadError(timeoutReason, "Doctor operation timed out"));
12306
12532
  }, Math.max(1, timeoutMs));
12307
12533
  })
12308
12534
  ]);
@@ -12490,12 +12716,13 @@ function failedProbeResult(local, reasonCode, details) {
12490
12716
  };
12491
12717
  }
12492
12718
  function failedProbeTransportCheck(reasonCode, details) {
12719
+ const failure2 = PROBE_FAILURE[reasonCode];
12493
12720
  return {
12494
12721
  name: "probe_transport",
12495
12722
  status: "fail",
12496
12723
  reason_code: reasonCode,
12497
- remediation_code: reasonCode === "AUTH_FAILED" ? "CHECK_INGEST_CREDENTIAL" : "CHECK_TRACE_ENDPOINT",
12498
- message: reasonCode === "AUTH_FAILED" ? "The project key was rejected by the existing trace API" : "The existing trace ingestion or read path is unavailable",
12724
+ remediation_code: failure2.remediationCode,
12725
+ message: failure2.message,
12499
12726
  ...details ? { details } : {}
12500
12727
  };
12501
12728
  }
@@ -12573,16 +12800,23 @@ function human(result) {
12573
12800
  if (result.note) lines.push(result.note);
12574
12801
  return lines.join("\n");
12575
12802
  }
12803
+ function readbackTimeoutReason(remainingMs, requestTimeoutMs) {
12804
+ return remainingMs <= requestTimeoutMs ? "TRACE_READBACK_TIMEOUT" : "BACKEND_CONNECTION_FAILED";
12805
+ }
12806
+ function responseBodyFailureReason(status, remainingMs, requestTimeoutMs) {
12807
+ return status === 409 || status >= 200 && status < 300 && status !== 202 ? "TRACE_READBACK_INVALID" : readbackTimeoutReason(remainingMs, requestTimeoutMs);
12808
+ }
12576
12809
  async function boundedFetch(io, input, initOptions, deadline) {
12577
12810
  const remaining = deadline - Date.now();
12578
12811
  if (remaining <= 0) {
12579
- throw new ProbeReadError("BACKEND_PROBE_UNAVAILABLE", "Doctor probe deadline expired");
12812
+ throw new ProbeReadError("TRACE_READBACK_TIMEOUT", "Doctor probe deadline expired");
12580
12813
  }
12581
12814
  const controller = new AbortController();
12582
12815
  return withTimeout(
12583
12816
  () => io.fetch(input, { ...initOptions, signal: controller.signal }),
12584
12817
  Math.min(io.requestTimeoutMs, remaining),
12585
- () => controller.abort()
12818
+ () => controller.abort(),
12819
+ readbackTimeoutReason(remaining, io.requestTimeoutMs)
12586
12820
  );
12587
12821
  }
12588
12822
  async function discardResponseBody(response, reader) {
@@ -12604,7 +12838,7 @@ async function boundedJson2(response, io, deadline) {
12604
12838
  const remaining = deadline - Date.now();
12605
12839
  if (remaining <= 0) {
12606
12840
  await discardResponseBody(response);
12607
- throw new ProbeReadError("BACKEND_PROBE_UNAVAILABLE", "Doctor probe deadline expired");
12841
+ throw new ProbeReadError("TRACE_READBACK_TIMEOUT", "Doctor probe deadline expired");
12608
12842
  }
12609
12843
  const contentLength = Number(response.headers.get("content-length"));
12610
12844
  if (Number.isFinite(contentLength) && contentLength > MAX_READBACK_BYTES) {
@@ -12639,9 +12873,17 @@ async function boundedJson2(response, io, deadline) {
12639
12873
  }
12640
12874
  }, Math.min(io.requestTimeoutMs, remaining), () => {
12641
12875
  void discardResponseBody(response, reader);
12642
- }).catch(async () => {
12876
+ }, responseBodyFailureReason(
12877
+ response.status,
12878
+ remaining,
12879
+ io.requestTimeoutMs
12880
+ )).catch(async (error) => {
12643
12881
  await discardResponseBody(response, reader);
12644
- return null;
12882
+ if (error instanceof ProbeReadError) throw error;
12883
+ throw new ProbeReadError(
12884
+ "BACKEND_CONNECTION_FAILED",
12885
+ "Trace read-back response could not be read"
12886
+ );
12645
12887
  });
12646
12888
  }
12647
12889
  function usage() {
@@ -12746,10 +12988,11 @@ async function runDoctorCli(argv, overrides = {}) {
12746
12988
  "x-neatlogs-doctor": DOCTOR_MARKER_VERSION
12747
12989
  }
12748
12990
  }, deadline);
12749
- } catch {
12991
+ } catch (error) {
12992
+ if (error instanceof ProbeReadError) throw error;
12750
12993
  throw new ProbeReadError(
12751
- "BACKEND_PROBE_UNAVAILABLE",
12752
- "The existing trace read path is unavailable",
12994
+ "BACKEND_CONNECTION_FAILED",
12995
+ "Could not connect to the existing trace read path",
12753
12996
  lastDiagnostics
12754
12997
  );
12755
12998
  }
@@ -12757,7 +13000,7 @@ async function runDoctorCli(argv, overrides = {}) {
12757
13000
  const value = await boundedJson2(response, io, deadline);
12758
13001
  if (!value || typeof value !== "object") {
12759
13002
  throw new ProbeReadError(
12760
- "BACKEND_PROBE_UNAVAILABLE",
13003
+ "TRACE_READBACK_INVALID",
12761
13004
  "Trace read-back returned an invalid response"
12762
13005
  );
12763
13006
  }
@@ -12770,12 +13013,23 @@ async function runDoctorCli(argv, overrides = {}) {
12770
13013
  throw new ProbeReadError("AUTH_FAILED", "Trace read-back rejected the project key");
12771
13014
  }
12772
13015
  if ([202, 404, 409].includes(response.status)) {
12773
- const value = await boundedJson2(response, io, deadline).catch(() => null);
13016
+ const value = await boundedJson2(response, io, deadline);
12774
13017
  const currentDiagnostics = ingestionDiagnosticDetails(value);
12775
13018
  if (currentDiagnostics) lastDiagnostics = currentDiagnostics;
12776
13019
  if (response.status === 409) {
13020
+ const terminalValue = objectValue(value);
13021
+ const isDlq = terminalValue.finalizationStatus === "dlq";
13022
+ const hasDiagnostics = Object.hasOwn(terminalValue, "ingestionDiagnostics");
13023
+ const validDlq = typeof terminalValue.error === "string" && (terminalValue.message === void 0 || typeof terminalValue.message === "string") && (!hasDiagnostics || currentDiagnostics !== void 0);
13024
+ const validReceipt = currentDiagnostics?.ingestion_state === "failed";
13025
+ if (isDlq ? !validDlq : !validReceipt) {
13026
+ throw new ProbeReadError(
13027
+ "TRACE_READBACK_INVALID",
13028
+ "Trace read-back returned an invalid terminal receipt"
13029
+ );
13030
+ }
12777
13031
  throw new ProbeReadError(
12778
- "BACKEND_PROBE_UNAVAILABLE",
13032
+ "INGESTION_PIPELINE_FAILED",
12779
13033
  "Trace ingestion reported a terminal failure",
12780
13034
  currentDiagnostics
12781
13035
  );
@@ -12783,7 +13037,7 @@ async function runDoctorCli(argv, overrides = {}) {
12783
13037
  } else {
12784
13038
  await discardResponseBody(response);
12785
13039
  throw new ProbeReadError(
12786
- "BACKEND_PROBE_UNAVAILABLE",
13040
+ "BACKEND_HTTP_ERROR",
12787
13041
  `Trace read-back failed with HTTP ${response.status}`,
12788
13042
  response.status >= 500 ? lastDiagnostics : void 0
12789
13043
  );
@@ -12793,7 +13047,7 @@ async function runDoctorCli(argv, overrides = {}) {
12793
13047
  }
12794
13048
  if (!persisted) {
12795
13049
  throw new ProbeReadError(
12796
- "BACKEND_PROBE_UNAVAILABLE",
13050
+ "TRACE_READBACK_TIMEOUT",
12797
13051
  "Timed out waiting for the exact Doctor trace",
12798
13052
  lastDiagnostics
12799
13053
  );
@@ -12802,7 +13056,7 @@ async function runDoctorCli(argv, overrides = {}) {
12802
13056
  io.stdout(json ? JSON.stringify(result, null, 2) : human(result));
12803
13057
  return result.status === "pass" ? 0 : 3;
12804
13058
  } catch (error) {
12805
- const reason = error instanceof ProbeReadError && error.reasonCode === "AUTH_FAILED" ? "AUTH_FAILED" : "BACKEND_PROBE_UNAVAILABLE";
13059
+ const reason = error instanceof ProbeReadError ? error.reasonCode : "BACKEND_PROBE_UNAVAILABLE";
12806
13060
  const result = local ? failedProbeResult(
12807
13061
  local,
12808
13062
  reason,