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.cjs CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/doctor-cli.ts
27
- var import_api9 = require("@opentelemetry/api");
27
+ var import_api11 = require("@opentelemetry/api");
28
28
 
29
29
  // src/core/doctor-capture.ts
30
30
  var import_api = require("@opentelemetry/api");
@@ -81,6 +81,110 @@ function enableLogging() {
81
81
  _disabled = false;
82
82
  }
83
83
 
84
+ // src/span-kinds/constants.ts
85
+ var VECTOR_DB_SYSTEMS = /* @__PURE__ */ new Set([
86
+ "chroma",
87
+ "chromadb",
88
+ "pinecone",
89
+ "qdrant",
90
+ "milvus",
91
+ "marqo",
92
+ "weaviate",
93
+ "lancedb",
94
+ "pgvector",
95
+ "elasticsearch"
96
+ ]);
97
+ var RETRIEVAL_OPS = [
98
+ "query",
99
+ "search",
100
+ "get",
101
+ "fetch",
102
+ "find",
103
+ "retrieve",
104
+ "scroll",
105
+ "peek",
106
+ "discover",
107
+ "recommend",
108
+ "aggregate",
109
+ "hybrid_search",
110
+ "select"
111
+ ];
112
+ var VECTOR_DB_NAMES = [
113
+ "chroma",
114
+ "pinecone",
115
+ "weaviate",
116
+ "qdrant",
117
+ "milvus",
118
+ "lancedb",
119
+ "marqo"
120
+ ];
121
+
122
+ // src/span-kinds/mapping.ts
123
+ var VALID_SPAN_KINDS = /* @__PURE__ */ new Set([
124
+ "WORKFLOW",
125
+ "AGENT",
126
+ "CHAIN",
127
+ "TOOL",
128
+ "RETRIEVER",
129
+ "EMBEDDING",
130
+ "MCP_TOOL",
131
+ "GUARDRAIL",
132
+ "EVALUATOR"
133
+ ]);
134
+ var ALL_SPAN_KINDS = /* @__PURE__ */ new Set([
135
+ ...VALID_SPAN_KINDS,
136
+ "LLM",
137
+ "RERANKER",
138
+ "VECTOR_STORE"
139
+ ]);
140
+ function resolveExplicitSpanKind(attributes) {
141
+ for (const key of [
142
+ "neatlogs.span.kind",
143
+ "openinference.span.kind",
144
+ "traceloop.span.kind"
145
+ ]) {
146
+ const value = String(attributes[key] ?? "").trim();
147
+ if (value) return value;
148
+ }
149
+ return "";
150
+ }
151
+ function inferSpanKindFromName(spanName) {
152
+ const nameLower = spanName.toLowerCase();
153
+ if (["openai", "anthropic", "cohere", "bedrock", "chat", "completion", "llm", "gemini", "google_genai"].some(
154
+ (kw) => nameLower.includes(kw)
155
+ )) {
156
+ return "LLM";
157
+ }
158
+ if (nameLower.includes("embed")) {
159
+ return "EMBEDDING";
160
+ }
161
+ if (VECTOR_DB_NAMES.some((db) => nameLower.includes(db))) {
162
+ if (RETRIEVAL_OPS.some((kw) => nameLower.includes(kw))) {
163
+ return "RETRIEVER";
164
+ }
165
+ return "VECTOR_STORE";
166
+ }
167
+ if (["retriev", "search", "query"].some((kw) => nameLower.includes(kw))) {
168
+ return "RETRIEVER";
169
+ }
170
+ if (nameLower.includes("rerank")) {
171
+ return "RERANKER";
172
+ }
173
+ if (nameLower.includes("agent")) {
174
+ return "AGENT";
175
+ }
176
+ if (["tool", "function"].some((kw) => nameLower.includes(kw))) {
177
+ return "TOOL";
178
+ }
179
+ if (["guardrail", "validate", "moderate", "safety"].some((kw) => nameLower.includes(kw))) {
180
+ return "GUARDRAIL";
181
+ }
182
+ if (["evaluat", "score", "metric"].some((kw) => nameLower.includes(kw))) {
183
+ return "EVALUATOR";
184
+ }
185
+ return "CHAIN";
186
+ }
187
+
84
188
  // src/config/attribute-mapping.json
85
189
  var attribute_mapping_default = {
86
190
  $schema: "http://json-schema.org/draft-07/schema#",
@@ -1258,27 +1362,11 @@ var AttributeMapper = class {
1258
1362
  /** Extract and normalize span kind from multiple possible sources. */
1259
1363
  mapSpanKind(attributes) {
1260
1364
  const spanKindConfig = this.mappings.span_kind ?? {};
1261
- const sources = spanKindConfig.sources ?? [];
1262
1365
  const valuesMap = spanKindConfig.values ?? {};
1263
- const priority = spanKindConfig.priority ?? "openinference";
1264
- let spanKindValue;
1265
- if (priority === "openinference") {
1266
- if ("openinference.span.kind" in attributes) {
1267
- spanKindValue = attributes["openinference.span.kind"];
1268
- }
1269
- } else {
1270
- for (const source of sources) {
1271
- if (source in attributes) {
1272
- spanKindValue = attributes[source];
1273
- break;
1274
- }
1275
- }
1276
- }
1277
- if (!spanKindValue && "neatlogs.span.kind" in attributes) {
1278
- spanKindValue = attributes["neatlogs.span.kind"];
1279
- }
1280
- if (spanKindValue && spanKindValue in valuesMap) {
1281
- return valuesMap[spanKindValue];
1366
+ const spanKindValue = resolveExplicitSpanKind(attributes);
1367
+ if (spanKindValue) {
1368
+ const mapped = valuesMap[spanKindValue] ?? valuesMap[spanKindValue.toUpperCase()];
1369
+ if (mapped) return mapped;
1282
1370
  }
1283
1371
  if (spanKindValue && Object.values(valuesMap).includes(spanKindValue)) {
1284
1372
  return spanKindValue;
@@ -1851,7 +1939,7 @@ function clearDoctorCapture() {
1851
1939
  }
1852
1940
 
1853
1941
  // src/core/context.ts
1854
- var import_api4 = require("@opentelemetry/api");
1942
+ var import_api5 = require("@opentelemetry/api");
1855
1943
 
1856
1944
  // src/prompt/template.ts
1857
1945
  var import_node_async_hooks = require("async_hooks");
@@ -2190,11 +2278,11 @@ function getNeatlogsRootSpan() {
2190
2278
  function getNeatlogsParentContext(baseContext) {
2191
2279
  return baseContext ?? getNeatlogsActiveContext();
2192
2280
  }
2193
- function withNeatlogsSpan(span2, fn, baseContext) {
2281
+ function withNeatlogsSpan(span2, fn, baseContext, rootSpan) {
2194
2282
  const base = baseContext ?? getNeatlogsActiveContext();
2195
2283
  let ctx = import_api2.trace.setSpan(base, span2);
2196
2284
  if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === void 0) {
2197
- ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span2);
2285
+ ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, rootSpan ?? span2);
2198
2286
  }
2199
2287
  return privateContextStorage.run(ctx, fn);
2200
2288
  }
@@ -3926,8 +4014,84 @@ function discardPendingMediaOwner(owner) {
3926
4014
  pendingMediaOwners.delete(owner ?? defaultPendingMediaOwner);
3927
4015
  }
3928
4016
 
3929
- // src/decorators/base.ts
4017
+ // src/core/auto-root.ts
3930
4018
  var import_api3 = require("@opentelemetry/api");
4019
+ var boundAutoRoots = /* @__PURE__ */ new WeakMap();
4020
+ var ROOT_KINDS = /* @__PURE__ */ new Set(["workflow", "chain", "agent", "mcp_tool", "llm"]);
4021
+ function autoRootEnabled() {
4022
+ const val = (process.env.NEATLOGS_AUTO_ROOT ?? "").trim().toLowerCase();
4023
+ return !["false", "0", "no", "off"].includes(val);
4024
+ }
4025
+ function resolveRootWorkflowName() {
4026
+ const clientName = getActiveClient()?.workflowName;
4027
+ if (typeof clientName === "string" && clientName.trim()) return clientName;
4028
+ try {
4029
+ const name = getSessionConfig()?.workflowName;
4030
+ if (typeof name === "string" && name.trim()) return name;
4031
+ } catch {
4032
+ }
4033
+ return "workflow";
4034
+ }
4035
+ function stampRootIdentity(root) {
4036
+ applySessionAttributes(root, void 0, true);
4037
+ applyEndUserAttributes(root, void 0, void 0, true);
4038
+ }
4039
+ function bindSpanToAutoRoot(child, root) {
4040
+ if (!root) return child;
4041
+ let ended = false;
4042
+ const proxy = new Proxy(child, {
4043
+ get(target, prop, _receiver) {
4044
+ if (prop === "end") {
4045
+ return (...args) => {
4046
+ if (ended) return;
4047
+ ended = true;
4048
+ try {
4049
+ target.end(...args);
4050
+ } finally {
4051
+ try {
4052
+ root.end();
4053
+ } catch {
4054
+ }
4055
+ }
4056
+ };
4057
+ }
4058
+ const value = Reflect.get(target, prop, target);
4059
+ return typeof value === "function" ? value.bind(target) : value;
4060
+ }
4061
+ });
4062
+ boundAutoRoots.set(proxy, root);
4063
+ aliasMediaCaptureSpan(proxy, child);
4064
+ return proxy;
4065
+ }
4066
+ function maybeOpenAutoRoot(tracer, kind, baseCtx) {
4067
+ const k = String(kind ?? "").toLowerCase();
4068
+ const existing = getActiveNeatlogsSpan();
4069
+ if (!autoRootEnabled() || ROOT_KINDS.has(k) || existing !== void 0 && existing.isRecording()) {
4070
+ return { root: void 0, ctx: baseCtx };
4071
+ }
4072
+ const root = tracer.startSpan(
4073
+ resolveRootWorkflowName(),
4074
+ { attributes: { "neatlogs.span.kind": "workflow", "neatlogs.auto_root": true } },
4075
+ baseCtx
4076
+ );
4077
+ stampRootIdentity(root);
4078
+ return { root, ctx: import_api3.trace.setSpan(baseCtx, root) };
4079
+ }
4080
+
4081
+ // src/errors.ts
4082
+ var NeatlogsConfigurationError = class extends TypeError {
4083
+ code;
4084
+ option;
4085
+ constructor(code, option, message) {
4086
+ super(message);
4087
+ this.name = "NeatlogsConfigurationError";
4088
+ this.code = code;
4089
+ this.option = option;
4090
+ }
4091
+ };
4092
+
4093
+ // src/decorators/base.ts
4094
+ var import_api4 = require("@opentelemetry/api");
3931
4095
  var logger5 = getLogger();
3932
4096
  var TRACER_NAME = "neatlogs";
3933
4097
  var STREAM_CAPTURE_TRUNCATED = "[TRUNCATED_STREAM_OUTPUT]";
@@ -3999,8 +4163,29 @@ function decorateSpan(opts, fn) {
3999
4163
  const wrapped = (...args) => {
4000
4164
  const tracer = getNeatlogsTracer(TRACER_NAME);
4001
4165
  const isRoot = isRootSpan();
4002
- const parentContext = isRoot ? import_api3.ROOT_CONTEXT : getNeatlogsParentContext();
4003
- const span2 = tracer.startSpan(spanName, {}, parentContext);
4166
+ const parentContext = isRoot ? import_api4.ROOT_CONTEXT : getNeatlogsParentContext();
4167
+ const autoRoot = maybeOpenAutoRoot(
4168
+ tracer,
4169
+ String(opts.kind ?? ""),
4170
+ parentContext
4171
+ );
4172
+ if (autoRoot.root) {
4173
+ applySessionAttributes(autoRoot.root, opts.sessionId, true, {
4174
+ parentSessionId: opts.parentSessionId,
4175
+ sessionFeatureName: opts.sessionFeatureName,
4176
+ sessionEntryPoint: opts.sessionEntryPoint
4177
+ });
4178
+ applyEndUserAttributes(
4179
+ autoRoot.root,
4180
+ opts.endUserId,
4181
+ opts.endUserMetadata,
4182
+ true
4183
+ );
4184
+ }
4185
+ const span2 = bindSpanToAutoRoot(
4186
+ tracer.startSpan(spanName, {}, autoRoot.ctx),
4187
+ autoRoot.root
4188
+ );
4004
4189
  return withNeatlogsSpan(span2, () => {
4005
4190
  let finished = false;
4006
4191
  let streamInterrupted = false;
@@ -4040,7 +4225,7 @@ function decorateSpan(opts, fn) {
4040
4225
  if (finished) return;
4041
4226
  finished = true;
4042
4227
  captureResult(result);
4043
- span2.setStatus({ code: import_api3.SpanStatusCode.OK });
4228
+ span2.setStatus({ code: import_api4.SpanStatusCode.OK });
4044
4229
  span2.end();
4045
4230
  };
4046
4231
  const setStreamCounts = () => {
@@ -4079,12 +4264,12 @@ function decorateSpan(opts, fn) {
4079
4264
  setStreamCounts();
4080
4265
  captureResult(streamCaptureValue());
4081
4266
  }
4082
- span2.setStatus({ code: import_api3.SpanStatusCode.UNSET });
4267
+ span2.setStatus({ code: import_api4.SpanStatusCode.UNSET });
4083
4268
  span2.end();
4084
4269
  return;
4085
4270
  }
4086
4271
  span2.setStatus({
4087
- code: import_api3.SpanStatusCode.ERROR,
4272
+ code: import_api4.SpanStatusCode.ERROR,
4088
4273
  message: error?.message ?? String(error)
4089
4274
  });
4090
4275
  span2.recordException(
@@ -4149,7 +4334,7 @@ function decorateSpan(opts, fn) {
4149
4334
  finished = true;
4150
4335
  span2.setAttribute("neatlogs.stream.cancelled", true);
4151
4336
  captureResult(streamCaptureValue());
4152
- span2.setStatus({ code: import_api3.SpanStatusCode.UNSET });
4337
+ span2.setStatus({ code: import_api4.SpanStatusCode.UNSET });
4153
4338
  span2.end();
4154
4339
  };
4155
4340
  const wrapAsyncIterator = (iterator) => {
@@ -4369,7 +4554,8 @@ function decorateSpan(opts, fn) {
4369
4554
  });
4370
4555
  try {
4371
4556
  setCommonSpanAttrs(span2, opts);
4372
- applySessionAttributes(span2, opts.sessionId, isRoot, {
4557
+ const spanIsRoot = isRoot && !autoRoot.root;
4558
+ applySessionAttributes(span2, opts.sessionId, spanIsRoot, {
4373
4559
  parentSessionId: opts.parentSessionId,
4374
4560
  sessionFeatureName: opts.sessionFeatureName,
4375
4561
  sessionEntryPoint: opts.sessionEntryPoint
@@ -4378,7 +4564,7 @@ function decorateSpan(opts, fn) {
4378
4564
  span2,
4379
4565
  opts.endUserId,
4380
4566
  opts.endUserMetadata,
4381
- isRoot
4567
+ spanIsRoot
4382
4568
  );
4383
4569
  if (captureInput && args.length > 0) {
4384
4570
  try {
@@ -4418,7 +4604,7 @@ function decorateSpan(opts, fn) {
4418
4604
  finishError(error);
4419
4605
  throw error;
4420
4606
  }
4421
- });
4607
+ }, autoRoot.ctx, autoRoot.root);
4422
4608
  };
4423
4609
  Object.defineProperty(wrapped, "name", {
4424
4610
  value: spanName,
@@ -4447,11 +4633,14 @@ var _sessionConfig = {};
4447
4633
  function _setSessionConfig(config) {
4448
4634
  _sessionConfig = config;
4449
4635
  }
4450
- var PROMPT_VARIABLES_KEY = (0, import_api4.createContextKey)("neatlogs.prompt_variables");
4451
- var PROMPT_TEMPLATE_KEY = (0, import_api4.createContextKey)("neatlogs.prompt_template");
4452
- var PROMPT_VERSION_KEY = (0, import_api4.createContextKey)("neatlogs.prompt_version");
4453
- var USER_PROMPT_TEMPLATE_KEY = (0, import_api4.createContextKey)("neatlogs.user_prompt_template");
4454
- var USER_PROMPT_VARIABLES_KEY = (0, import_api4.createContextKey)("neatlogs.user_prompt_variables");
4636
+ function getSessionConfig() {
4637
+ return { ..._sessionConfig };
4638
+ }
4639
+ var PROMPT_VARIABLES_KEY = (0, import_api5.createContextKey)("neatlogs.prompt_variables");
4640
+ var PROMPT_TEMPLATE_KEY = (0, import_api5.createContextKey)("neatlogs.prompt_template");
4641
+ var PROMPT_VERSION_KEY = (0, import_api5.createContextKey)("neatlogs.prompt_version");
4642
+ var USER_PROMPT_TEMPLATE_KEY = (0, import_api5.createContextKey)("neatlogs.user_prompt_template");
4643
+ var USER_PROMPT_VARIABLES_KEY = (0, import_api5.createContextKey)("neatlogs.user_prompt_variables");
4455
4644
  var KNOWN_OPTION_KEYS = /* @__PURE__ */ new Set([
4456
4645
  "name",
4457
4646
  "kind",
@@ -4470,6 +4659,20 @@ var KNOWN_OPTION_KEYS = /* @__PURE__ */ new Set([
4470
4659
  "endUserId",
4471
4660
  "endUserMetadata"
4472
4661
  ]);
4662
+ function assertNoHttpSpanKind(kind, attributes) {
4663
+ const candidates = [
4664
+ kind,
4665
+ attributes?.["neatlogs.span.kind"],
4666
+ attributes?.["openinference.span.kind"]
4667
+ ];
4668
+ if (candidates.some((value) => String(value ?? "").trim().toUpperCase() === "HTTP")) {
4669
+ throw new NeatlogsConfigurationError(
4670
+ "UNSUPPORTED_SPAN_KIND",
4671
+ "kind",
4672
+ "HTTP spans are not supported. Trace the semantic LLM, TOOL, RETRIEVER, or other AI operation instead."
4673
+ );
4674
+ }
4675
+ }
4473
4676
  function _setSpanAttributes(span2, kind, attributes) {
4474
4677
  if (kind?.toUpperCase() === "LLM") {
4475
4678
  span2.setAttribute("neatlogs.internal", true);
@@ -4540,6 +4743,7 @@ async function trace(options, fn) {
4540
4743
  attributes: explicitAttributes,
4541
4744
  ...extraOptions
4542
4745
  } = options;
4746
+ assertNoHttpSpanKind(kind, explicitAttributes);
4543
4747
  const sessionId = sessionIdOption ?? currentSessionId();
4544
4748
  const currentSpan = getActiveNeatlogsSpan();
4545
4749
  const rootSpan = getNeatlogsRootSpan();
@@ -4584,7 +4788,7 @@ async function trace(options, fn) {
4584
4788
  }
4585
4789
  let ctx = getNeatlogsActiveContext();
4586
4790
  if (shouldFallbackToRoot) {
4587
- ctx = import_api4.trace.setSpan(ctx, rootSpan);
4791
+ ctx = import_api5.trace.setSpan(ctx, rootSpan);
4588
4792
  }
4589
4793
  const variablesJson = promptVariables ? JSON.stringify(promptVariables) : void 0;
4590
4794
  const userVariablesJson = userPromptVariables ? JSON.stringify(userPromptVariables) : void 0;
@@ -4618,12 +4822,13 @@ async function trace(options, fn) {
4618
4822
  const tracer = getNeatlogsTracer("neatlogs.trace");
4619
4823
  const spanCallback = async (span3) => {
4620
4824
  _setSpanAttributes(span3, kind, extraAttributes);
4621
- applySessionAttributes(span3, sessionId, isRootTrace, {
4825
+ const spanIsRoot = isRootTrace && !autoRoot.root;
4826
+ applySessionAttributes(span3, sessionId, spanIsRoot, {
4622
4827
  parentSessionId,
4623
4828
  sessionFeatureName,
4624
4829
  sessionEntryPoint
4625
4830
  });
4626
- applyEndUserAttributes(span3, endUserId, endUserMetadata, isRootTrace);
4831
+ applyEndUserAttributes(span3, endUserId, endUserMetadata, spanIsRoot);
4627
4832
  if (input !== void 0 && input !== null) {
4628
4833
  const capturedInput = captureMedia(
4629
4834
  span3,
@@ -4656,7 +4861,7 @@ async function trace(options, fn) {
4656
4861
  return result;
4657
4862
  } catch (error) {
4658
4863
  span3.setStatus({
4659
- code: import_api4.SpanStatusCode.ERROR,
4864
+ code: import_api5.SpanStatusCode.ERROR,
4660
4865
  message: error instanceof Error ? error.message : String(error)
4661
4866
  });
4662
4867
  span3.recordException(
@@ -4673,108 +4878,36 @@ async function trace(options, fn) {
4673
4878
  span3.end();
4674
4879
  }
4675
4880
  };
4676
- const parentContext = shouldCreateRootTrace ? import_api4.ROOT_CONTEXT : getNeatlogsParentContext(ctx);
4881
+ const parentContext = shouldCreateRootTrace ? import_api5.ROOT_CONTEXT : getNeatlogsParentContext(ctx);
4677
4882
  const startAttributes = kind ? { "openinference.span.kind": kind } : void 0;
4678
- const span2 = tracer.startSpan(name, startAttributes ? { attributes: startAttributes } : {}, parentContext);
4883
+ const autoRoot = isRootTrace ? maybeOpenAutoRoot(tracer, String(kind ?? "CHAIN"), parentContext) : { root: void 0, ctx: parentContext };
4884
+ if (autoRoot.root) {
4885
+ applySessionAttributes(autoRoot.root, sessionId, true, {
4886
+ parentSessionId,
4887
+ sessionFeatureName,
4888
+ sessionEntryPoint
4889
+ });
4890
+ applyEndUserAttributes(autoRoot.root, endUserId, endUserMetadata, true);
4891
+ }
4892
+ const span2 = bindSpanToAutoRoot(
4893
+ tracer.startSpan(
4894
+ name,
4895
+ startAttributes ? { attributes: startAttributes } : {},
4896
+ autoRoot.ctx
4897
+ ),
4898
+ autoRoot.root
4899
+ );
4679
4900
  if (shouldCreateRootTrace) {
4680
4901
  logger6.debug(`[trace] Creating NEW root trace '${name}' (sessionId=${sessionId})`);
4681
4902
  } else {
4682
4903
  logger6.debug(`[trace] Creating child span '${name}'`);
4683
4904
  }
4684
- return withNeatlogsSpan(span2, () => spanCallback(span2), ctx);
4685
- }
4686
-
4687
- // src/span-kinds/constants.ts
4688
- var VECTOR_DB_SYSTEMS = /* @__PURE__ */ new Set([
4689
- "chroma",
4690
- "chromadb",
4691
- "pinecone",
4692
- "qdrant",
4693
- "milvus",
4694
- "marqo",
4695
- "weaviate",
4696
- "lancedb",
4697
- "pgvector",
4698
- "elasticsearch"
4699
- ]);
4700
- var RETRIEVAL_OPS = [
4701
- "query",
4702
- "search",
4703
- "get",
4704
- "fetch",
4705
- "find",
4706
- "retrieve",
4707
- "scroll",
4708
- "peek",
4709
- "discover",
4710
- "recommend",
4711
- "aggregate",
4712
- "hybrid_search",
4713
- "select"
4714
- ];
4715
- var VECTOR_DB_NAMES = [
4716
- "chroma",
4717
- "pinecone",
4718
- "weaviate",
4719
- "qdrant",
4720
- "milvus",
4721
- "lancedb",
4722
- "marqo"
4723
- ];
4724
-
4725
- // src/span-kinds/mapping.ts
4726
- var VALID_SPAN_KINDS = /* @__PURE__ */ new Set([
4727
- "WORKFLOW",
4728
- "AGENT",
4729
- "CHAIN",
4730
- "TOOL",
4731
- "RETRIEVER",
4732
- "EMBEDDING",
4733
- "MCP_TOOL",
4734
- "GUARDRAIL"
4735
- ]);
4736
- var ALL_SPAN_KINDS = /* @__PURE__ */ new Set([
4737
- ...VALID_SPAN_KINDS,
4738
- "LLM",
4739
- "RERANKER",
4740
- "EVALUATOR",
4741
- "VECTOR_STORE"
4742
- ]);
4743
- function inferSpanKindFromName(spanName) {
4744
- const nameLower = spanName.toLowerCase();
4745
- if (["openai", "anthropic", "cohere", "bedrock", "chat", "completion", "llm", "gemini", "google_genai"].some(
4746
- (kw) => nameLower.includes(kw)
4747
- )) {
4748
- return "LLM";
4749
- }
4750
- if (nameLower.includes("embed")) {
4751
- return "EMBEDDING";
4752
- }
4753
- if (VECTOR_DB_NAMES.some((db) => nameLower.includes(db))) {
4754
- if (RETRIEVAL_OPS.some((kw) => nameLower.includes(kw))) {
4755
- return "RETRIEVER";
4756
- }
4757
- return "VECTOR_STORE";
4758
- }
4759
- if (["retriev", "search", "query"].some((kw) => nameLower.includes(kw))) {
4760
- return "RETRIEVER";
4761
- }
4762
- if (nameLower.includes("rerank")) {
4763
- return "RERANKER";
4764
- }
4765
- if (nameLower.includes("agent")) {
4766
- return "AGENT";
4767
- }
4768
- if (["tool", "function"].some((kw) => nameLower.includes(kw))) {
4769
- return "TOOL";
4770
- }
4771
- if (["guardrail", "validate", "moderate", "safety"].some((kw) => nameLower.includes(kw))) {
4772
- return "GUARDRAIL";
4773
- }
4774
- if (["evaluat", "score", "metric"].some((kw) => nameLower.includes(kw))) {
4775
- return "EVALUATOR";
4776
- }
4777
- return "CHAIN";
4905
+ return withNeatlogsSpan(
4906
+ span2,
4907
+ () => spanCallback(span2),
4908
+ autoRoot.ctx,
4909
+ autoRoot.root
4910
+ );
4778
4911
  }
4779
4912
 
4780
4913
  // src/decorators/orchestration.ts
@@ -5294,6 +5427,7 @@ var neatlogs_telemetry_schema_default = {
5294
5427
  "VECTOR_STORE",
5295
5428
  "MEMORY",
5296
5429
  "GUARDRAIL",
5430
+ "EVALUATOR",
5297
5431
  "LOG",
5298
5432
  "HTTP",
5299
5433
  "UNKNOWN"
@@ -6722,6 +6856,7 @@ var neatlogs_telemetry_schema_default = {
6722
6856
  "AGENT",
6723
6857
  "CHAIN",
6724
6858
  "TASK",
6859
+ "EVALUATOR",
6725
6860
  "HTTP",
6726
6861
  "UNKNOWN"
6727
6862
  ]
@@ -6885,6 +7020,7 @@ var neatlogs_telemetry_schema_default = {
6885
7020
  "neatlogs.vector_store.*",
6886
7021
  "neatlogs.memory.*",
6887
7022
  "neatlogs.guardrail.*",
7023
+ "neatlogs.evaluator.*",
6888
7024
  "neatlogs.mcp.*",
6889
7025
  "neatlogs.code.*"
6890
7026
  ],
@@ -6968,7 +7104,7 @@ var TELEMETRY_CONFLICT_PRECEDENCE = Object.freeze(
6968
7104
  );
6969
7105
 
6970
7106
  // src/version.ts
6971
- var __version__ = "1.1.19";
7107
+ var __version__ = "1.1.21";
6972
7108
 
6973
7109
  // src/init.ts
6974
7110
  var path2 = __toESM(require("path"));
@@ -6987,16 +7123,16 @@ var import_exporter_logs_otlp_proto = require("@opentelemetry/exporter-logs-otlp
6987
7123
  var fs = __toESM(require("fs"));
6988
7124
  var path = __toESM(require("path"));
6989
7125
  var import_node_perf_hooks = require("perf_hooks");
6990
- var import_api5 = require("@opentelemetry/api");
7126
+ var import_api7 = require("@opentelemetry/api");
6991
7127
 
6992
7128
  // src/core/instrumentation-scope-parser.ts
6993
7129
  var SCOPE_PATTERNS = {
6994
7130
  // Neatlogs custom instrumentations
6995
7131
  "@neatlogs/instrumentation-google-genai": { provider: "google", framework: "google_genai" },
6996
7132
  "@neatlogs/instrumentation-mastra": { framework: "mastra" },
6997
- "@neatlogs/instrumentation-ai-sdk": { framework: "ai_sdk" },
7133
+ "@neatlogs/instrumentation-ai-sdk": { framework: "vercel_ai_sdk" },
6998
7134
  // Vercel AI SDK native scope (the `ai` package emits this directly)
6999
- ai: { framework: "ai_sdk" },
7135
+ ai: { framework: "vercel_ai_sdk" },
7000
7136
  // OpenInference instrumentations (npm @arizeai scope names)
7001
7137
  "@arizeai/openinference-instrumentation-openai": { provider: "openai", framework: "openai" },
7002
7138
  "@arizeai/openinference-instrumentation-anthropic": { provider: "anthropic", framework: "anthropic" },
@@ -7085,7 +7221,7 @@ function parseInstrumentationScope(scopeName) {
7085
7221
  } else if (scopeLower.includes("dspy")) {
7086
7222
  result.framework = "dspy";
7087
7223
  } else if (scopeLower.includes("vercel-ai") || scopeLower.includes("ai-sdk")) {
7088
- result.framework = "ai_sdk";
7224
+ result.framework = "vercel_ai_sdk";
7089
7225
  }
7090
7226
  if (scopeLower.includes("openai")) {
7091
7227
  result.provider = "openai";
@@ -8974,6 +9110,46 @@ function addVerificationMarkerResourceAttribute(attributes) {
8974
9110
  if (marker) attributes[VERIFICATION_MARKER_KEY] = marker;
8975
9111
  }
8976
9112
 
9113
+ // src/core/http-span.ts
9114
+ var import_api6 = require("@opentelemetry/api");
9115
+ var HTTP_SCOPE_PREFIXES = [
9116
+ "@opentelemetry/instrumentation-http",
9117
+ "@opentelemetry/instrumentation-fetch",
9118
+ "@opentelemetry/instrumentation-undici"
9119
+ ];
9120
+ var HTTP_ATTRIBUTE_KEYS = [
9121
+ "http.method",
9122
+ "http.request.method",
9123
+ "http.url",
9124
+ "http.route",
9125
+ "url.full"
9126
+ ];
9127
+ var SEMANTIC_AI_KINDS = /* @__PURE__ */ new Set([
9128
+ "WORKFLOW",
9129
+ "AGENT",
9130
+ "CHAIN",
9131
+ "TOOL",
9132
+ "RETRIEVER",
9133
+ "EMBEDDING",
9134
+ "GUARDRAIL",
9135
+ "LLM",
9136
+ "RERANKER",
9137
+ "VECTOR_STORE",
9138
+ "TASK",
9139
+ "EVALUATOR",
9140
+ "LOG",
9141
+ "MEMORY",
9142
+ "MCP_TOOL"
9143
+ ]);
9144
+ function isHttpSpan(span2, attributes = span2.attributes ?? {}) {
9145
+ const resolvedKind = resolveExplicitSpanKind(attributes).toUpperCase();
9146
+ if (resolvedKind === "HTTP") return true;
9147
+ if (SEMANTIC_AI_KINDS.has(resolvedKind)) return false;
9148
+ const scopeName = span2.instrumentationLibrary.name ?? "";
9149
+ if (HTTP_SCOPE_PREFIXES.some((prefix) => scopeName.startsWith(prefix))) return true;
9150
+ return span2.kind === import_api6.SpanKind.CLIENT && HTTP_ATTRIBUTE_KEYS.some((key) => key in attributes);
9151
+ }
9152
+
8977
9153
  // src/core/span-processor.ts
8978
9154
  var logger11 = getLogger();
8979
9155
  function resolveLogFilePath(configuredPath) {
@@ -9245,6 +9421,7 @@ var NeatlogsSpanProcessor = class {
9245
9421
  onEnd(span2) {
9246
9422
  this._activeSpans.delete(span2.spanContext().spanId);
9247
9423
  if (this._closed) return;
9424
+ if (isHttpSpan(span2)) return;
9248
9425
  if (span2.name === "neatlogs.trace.complete") {
9249
9426
  const markerData = spanToDict(span2, { includeScope: true });
9250
9427
  markerData.resource = { attributes: markerData.resource };
@@ -9361,7 +9538,7 @@ var NeatlogsSpanProcessor = class {
9361
9538
  attributes: unifiedAttrs,
9362
9539
  resource: { attributes: resourceAttrs },
9363
9540
  status: {
9364
- code: import_api5.SpanStatusCode[span2.status.code] ?? String(span2.status.code),
9541
+ code: import_api7.SpanStatusCode[span2.status.code] ?? String(span2.status.code),
9365
9542
  description: span2.status.message ?? null
9366
9543
  },
9367
9544
  links: span2.links.map((link) => ({
@@ -9404,6 +9581,7 @@ var NeatlogsSpanProcessor = class {
9404
9581
  const maskResult = scheduleMask(span2, spanData, this.mask ?? null);
9405
9582
  const pending = maskResult.then((maskedSpanData) => {
9406
9583
  if (maskedSpanData === null) return;
9584
+ if (isHttpSpan(span2, maskedSpanData.attributes ?? {})) return;
9407
9585
  if (this._processedLogStream && !this._processedLogStream.destroyed) {
9408
9586
  try {
9409
9587
  this._processedLogStream.write(JSON.stringify(maskedSpanData) + "\n");
@@ -9483,9 +9661,9 @@ var NeatlogsSpanProcessor = class {
9483
9661
  _markInterrupted(span2, cleanReason) {
9484
9662
  span2.setAttribute("neatlogs.trace.interrupted", true);
9485
9663
  span2.setAttribute("neatlogs.trace.termination.reason", cleanReason);
9486
- if (span2.status.code === import_api5.SpanStatusCode.UNSET) {
9664
+ if (span2.status.code === import_api7.SpanStatusCode.UNSET) {
9487
9665
  span2.setStatus({
9488
- code: import_api5.SpanStatusCode.ERROR,
9666
+ code: import_api7.SpanStatusCode.ERROR,
9489
9667
  message: `Interrupted during ${cleanReason}`
9490
9668
  });
9491
9669
  }
@@ -9500,10 +9678,10 @@ var NeatlogsSpanProcessor = class {
9500
9678
  traceId: rootSpan.spanContext().traceId,
9501
9679
  spanId: rootSpan.spanContext().spanId,
9502
9680
  isRemote: false,
9503
- traceFlags: import_api5.TraceFlags.SAMPLED
9681
+ traceFlags: import_api7.TraceFlags.SAMPLED
9504
9682
  };
9505
- const wrappedSpan = import_api5.trace.wrapSpanContext(spanCtx);
9506
- const ctx = import_api5.trace.setSpan(import_api5.ROOT_CONTEXT, wrappedSpan);
9683
+ const wrappedSpan = import_api7.trace.wrapSpanContext(spanCtx);
9684
+ const ctx = import_api7.trace.setSpan(import_api7.ROOT_CONTEXT, wrappedSpan);
9507
9685
  const marker = markerTracer.startSpan("neatlogs.trace.complete", void 0, ctx);
9508
9686
  marker.setAttribute("neatlogs.trace.complete", true);
9509
9687
  marker.setAttribute("neatlogs.internal", true);
@@ -9672,7 +9850,8 @@ var CompletionMarkerSpanProcessor = class {
9672
9850
  onStart(_span, _parentContext) {
9673
9851
  }
9674
9852
  onEnd(span2) {
9675
- if (this.closed || span2.name === "neatlogs.trace.complete" || span2.parentSpanId) return;
9853
+ if (this.closed || span2.name === "neatlogs.trace.complete" || span2.parentSpanId || isHttpSpan(span2))
9854
+ return;
9676
9855
  if (!this.source.ownsSpan(span2)) return;
9677
9856
  const task = this.source.whenMaskComplete(span2).then((eligible) => {
9678
9857
  if (!eligible || this.closed) return;
@@ -9709,7 +9888,7 @@ var CompletionMarkerSpanProcessor = class {
9709
9888
  };
9710
9889
 
9711
9890
  // src/core/filtering-exporter.ts
9712
- var import_api6 = require("@opentelemetry/api");
9891
+ var import_api8 = require("@opentelemetry/api");
9713
9892
  var import_core = require("@opentelemetry/core");
9714
9893
  var import_resources = require("@opentelemetry/resources");
9715
9894
  var MEDIA_RESOLUTION_CONCURRENCY = 4;
@@ -9748,7 +9927,7 @@ function toAttributes(value) {
9748
9927
  function statusCode(value, fallback) {
9749
9928
  if (typeof value === "number") return value;
9750
9929
  if (typeof value === "string") {
9751
- const resolved = import_api6.SpanStatusCode[value];
9930
+ const resolved = import_api8.SpanStatusCode[value];
9752
9931
  if (typeof resolved === "number") return resolved;
9753
9932
  }
9754
9933
  return fallback;
@@ -9812,6 +9991,10 @@ var FilteringExporter = class {
9812
9991
  _onPrepared;
9813
9992
  export(spans, resultCallback) {
9814
9993
  void mapWithConcurrency(spans, MEDIA_RESOLUTION_CONCURRENCY, async (span2) => {
9994
+ if (isHttpSpan(span2)) {
9995
+ discardPendingMedia(span2);
9996
+ return { span: null, mediaReady: true };
9997
+ }
9815
9998
  if (span2.instrumentationLibrary.name === "next.js") {
9816
9999
  discardPendingMedia(span2);
9817
10000
  this.diagnostics?.recordFrameworkSpanDrop();
@@ -9832,6 +10015,11 @@ var FilteringExporter = class {
9832
10015
  ...masked.attributes && typeof masked.attributes === "object" ? masked.attributes : {}
9833
10016
  };
9834
10017
  const maskedData = { ...masked, attributes };
10018
+ const maskedSpan = maskedReadableSpan(span2, maskedData);
10019
+ if (isHttpSpan(maskedSpan)) {
10020
+ discardPendingMedia(span2);
10021
+ return { span: null, mediaReady: true };
10022
+ }
9835
10023
  captureMediaInSnapshot(
9836
10024
  span2,
9837
10025
  maskedData,
@@ -10299,7 +10487,7 @@ var DeliveryDiagnostics = class {
10299
10487
  };
10300
10488
 
10301
10489
  // src/core/observable-batch-processors.ts
10302
- var import_api7 = require("@opentelemetry/api");
10490
+ var import_api9 = require("@opentelemetry/api");
10303
10491
  var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
10304
10492
  var import_sdk_logs = require("@opentelemetry/sdk-logs");
10305
10493
  var ObservableBatchSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcessor {
@@ -10310,7 +10498,7 @@ var ObservableBatchSpanProcessor = class extends import_sdk_trace_base.BatchSpan
10310
10498
  diagnostics;
10311
10499
  onEnd(span2) {
10312
10500
  const state = this;
10313
- if ((span2.spanContext().traceFlags & import_api7.TraceFlags.SAMPLED) !== 0 && state._finishedSpans.length >= state._maxQueueSize) {
10501
+ if ((span2.spanContext().traceFlags & import_api9.TraceFlags.SAMPLED) !== 0 && state._finishedSpans.length >= state._maxQueueSize) {
10314
10502
  discardPendingMedia(span2);
10315
10503
  this.diagnostics.recordQueueDrop("span");
10316
10504
  }
@@ -10344,7 +10532,7 @@ function _setOtelLogger(otelLogger, debug) {
10344
10532
  }
10345
10533
 
10346
10534
  // src/prompt/client.ts
10347
- var import_api8 = require("@opentelemetry/api");
10535
+ var import_api10 = require("@opentelemetry/api");
10348
10536
  var import_core4 = require("@opentelemetry/core");
10349
10537
  var PromptClientError = class extends Error {
10350
10538
  constructor(message) {
@@ -11057,8 +11245,8 @@ var PromptClient = class _PromptClient {
11057
11245
  let contextResult;
11058
11246
  try {
11059
11247
  try {
11060
- const suppressedContext = (0, import_core4.suppressTracing)(import_api8.context.active());
11061
- contextResult = import_api8.context.with(suppressedContext, startTransport);
11248
+ const suppressedContext = (0, import_core4.suppressTracing)(import_api10.context.active());
11249
+ contextResult = import_api10.context.with(suppressedContext, startTransport);
11062
11250
  } catch {
11063
11251
  }
11064
11252
  if (contextResult && contextResult !== transportPromise) {
@@ -11135,18 +11323,6 @@ function closeSharedClient() {
11135
11323
  function _resetMastraCache() {
11136
11324
  }
11137
11325
 
11138
- // src/errors.ts
11139
- var NeatlogsConfigurationError = class extends TypeError {
11140
- code;
11141
- option;
11142
- constructor(code, option, message) {
11143
- super(message);
11144
- this.name = "NeatlogsConfigurationError";
11145
- this.code = code;
11146
- this.option = option;
11147
- }
11148
- };
11149
-
11150
11326
  // src/core/deadline.ts
11151
11327
  async function runByDeadline(operation, deadlineMs) {
11152
11328
  const remaining = Math.max(0, deadlineMs - Date.now());
@@ -11247,6 +11423,24 @@ var INIT_OPTION_KEYS = /* @__PURE__ */ new Set([
11247
11423
  "piiSpanTypes",
11248
11424
  "uploadAuthority"
11249
11425
  ]);
11426
+ function resolveInitEndpoint(endpoint) {
11427
+ const explicit = (endpoint ?? "").trim();
11428
+ if (explicit) return explicit;
11429
+ const fromEnv = (process.env.NEATLOGS_ENDPOINT ?? "").trim();
11430
+ return fromEnv || DEFAULT_INGEST_ENDPOINT;
11431
+ }
11432
+ function resolveIngestBaseUrl(endpoint) {
11433
+ const parsed = new URL(endpoint.trim());
11434
+ const path3 = parsed.pathname.replace(/\/+$/, "");
11435
+ if (path3 !== "" && path3 !== "/v1/traces") {
11436
+ throw new NeatlogsConfigurationError(
11437
+ "INVALID_ENDPOINT",
11438
+ "endpoint",
11439
+ "endpoint must be a base URL or an OTLP traces URL ending in /v1/traces."
11440
+ );
11441
+ }
11442
+ return parsed.origin;
11443
+ }
11250
11444
  function validateInitOptions(options) {
11251
11445
  const raw = options;
11252
11446
  if (Object.prototype.hasOwnProperty.call(raw, "instrumentations")) {
@@ -11330,8 +11524,13 @@ function stableValue(value, ancestors = /* @__PURE__ */ new WeakSet(), location
11330
11524
  ancestors.delete(value);
11331
11525
  }
11332
11526
  }
11527
+ function resolveApiKey(apiKey) {
11528
+ const explicit = (apiKey ?? "").trim();
11529
+ if (explicit) return explicit;
11530
+ return (process.env.NEATLOGS_API_KEY ?? "").trim();
11531
+ }
11333
11532
  function initIdentity(options) {
11334
- const apiKey = (options.apiKey ?? process.env.NEATLOGS_API_KEY ?? "").trim();
11533
+ const apiKey = resolveApiKey(options.apiKey);
11335
11534
  const apiKeyDigest = (0, import_node_crypto4.createHash)("sha256").update(apiKey).digest("hex");
11336
11535
  const serialized = stableValue({
11337
11536
  apiKeyDigest,
@@ -11351,7 +11550,7 @@ function initIdentity(options) {
11351
11550
  captureLogs: options.captureLogs ?? false,
11352
11551
  pii: options.pii ?? null,
11353
11552
  version: options.version ?? null,
11354
- endpoint: options.endpoint ?? DEFAULT_INGEST_ENDPOINT,
11553
+ endpoint: resolveInitEndpoint(options.endpoint),
11355
11554
  batchSize: options.batchSize ?? 100,
11356
11555
  flushInterval: options.flushInterval ?? 5,
11357
11556
  piiEnabled: options.piiEnabled ?? null,
@@ -11431,12 +11630,7 @@ async function _performInit(options) {
11431
11630
  );
11432
11631
  }
11433
11632
  _deliveryDiagnostics = new DeliveryDiagnostics();
11434
- let resolvedKey;
11435
- if (options.apiKey && options.apiKey.trim()) {
11436
- resolvedKey = options.apiKey.trim();
11437
- } else {
11438
- resolvedKey = (process.env.NEATLOGS_API_KEY ?? "").trim();
11439
- }
11633
+ let resolvedKey = resolveApiKey(options.apiKey);
11440
11634
  let disableExportResolved = !!options.disableExport || ["true", "1", "yes"].includes(
11441
11635
  (process.env.NEATLOGS_DISABLE_EXPORT ?? "").toLowerCase()
11442
11636
  );
@@ -11457,8 +11651,8 @@ async function _performInit(options) {
11457
11651
  }
11458
11652
  _debugMode2 = options.debug ?? false;
11459
11653
  const resolvedWorkflowName = _resolveWorkflowName(options.workflowName);
11460
- const endpoint = options.endpoint ?? DEFAULT_INGEST_ENDPOINT;
11461
- const baseUrl = new URL(endpoint).origin;
11654
+ const endpoint = resolveInitEndpoint(options.endpoint);
11655
+ const baseUrl = resolveIngestBaseUrl(endpoint);
11462
11656
  const uploadAuthority = disableExportResolved ? new DisabledUploadAuthority("export_disabled") : resolveUploadAuthority(
11463
11657
  options.uploadAuthority,
11464
11658
  process.env.NEATLOGS_UPLOADS_ENABLED,
@@ -12178,6 +12372,36 @@ var INGESTION_STAGES = /* @__PURE__ */ new Set([
12178
12372
  ]);
12179
12373
  var INGESTION_STATES = /* @__PURE__ */ new Set(["processing", "failed", "succeeded"]);
12180
12374
  var SAFE_FAILURE_CODE = /^[A-Z0-9_]{1,64}$/;
12375
+ var PROBE_FAILURE = {
12376
+ AUTH_FAILED: {
12377
+ remediationCode: "CHECK_INGEST_CREDENTIAL",
12378
+ message: "The project key was rejected by the existing trace API"
12379
+ },
12380
+ BACKEND_PROBE_UNAVAILABLE: {
12381
+ remediationCode: "CHECK_TRACE_ENDPOINT",
12382
+ message: "The existing trace ingestion or read path is unavailable"
12383
+ },
12384
+ TRACE_READBACK_TIMEOUT: {
12385
+ remediationCode: "WAIT_FOR_TRACE",
12386
+ message: "Timed out waiting for the exact Doctor trace"
12387
+ },
12388
+ INGESTION_PIPELINE_FAILED: {
12389
+ remediationCode: "CONTACT_SUPPORT",
12390
+ message: "Trace ingestion reported a terminal pipeline failure"
12391
+ },
12392
+ BACKEND_HTTP_ERROR: {
12393
+ remediationCode: "CHECK_TRACE_ENDPOINT",
12394
+ message: "The existing trace read path returned an unexpected HTTP status"
12395
+ },
12396
+ BACKEND_CONNECTION_FAILED: {
12397
+ remediationCode: "CHECK_TRACE_ENDPOINT",
12398
+ message: "Could not connect to the existing trace read path"
12399
+ },
12400
+ TRACE_READBACK_INVALID: {
12401
+ remediationCode: "CONTACT_SUPPORT",
12402
+ message: "Trace read-back returned an invalid response"
12403
+ }
12404
+ };
12181
12405
  var ProbeReadError = class extends Error {
12182
12406
  constructor(reasonCode, message, details) {
12183
12407
  super(message);
@@ -12297,7 +12521,7 @@ function validateLocalFixture(traceId) {
12297
12521
  check("controlled_fixture_bounds", stats.droppedSpans === 0, "CONTROLLED_BOUNDS_VALID", "CONTROLLED_CAPTURE_TRUNCATED", "REDUCE_DIAGNOSTIC_CAPTURE", "Doctor stayed inside the bounded capture budget")
12298
12522
  ];
12299
12523
  }
12300
- async function withTimeout(operation, timeoutMs, onTimeout) {
12524
+ async function withTimeout(operation, timeoutMs, onTimeout, timeoutReason = "BACKEND_PROBE_UNAVAILABLE") {
12301
12525
  let timer;
12302
12526
  try {
12303
12527
  return await Promise.race([
@@ -12305,7 +12529,7 @@ async function withTimeout(operation, timeoutMs, onTimeout) {
12305
12529
  new Promise((_resolve, reject) => {
12306
12530
  timer = setTimeout(() => {
12307
12531
  onTimeout?.();
12308
- reject(new ProbeReadError("BACKEND_PROBE_UNAVAILABLE", "Doctor operation timed out"));
12532
+ reject(new ProbeReadError(timeoutReason, "Doctor operation timed out"));
12309
12533
  }, Math.max(1, timeoutMs));
12310
12534
  })
12311
12535
  ]);
@@ -12360,7 +12584,7 @@ async function localResult(env, options = {}) {
12360
12584
  markDoctorSpan("LLM");
12361
12585
  const output = { text: "generated diagnostic output" };
12362
12586
  llm.setAttribute("output.value", JSON.stringify(output));
12363
- llm.setStatus({ code: import_api9.SpanStatusCode.OK });
12587
+ llm.setStatus({ code: import_api11.SpanStatusCode.OK });
12364
12588
  llm.end();
12365
12589
  return output;
12366
12590
  }
@@ -12379,7 +12603,7 @@ async function localResult(env, options = {}) {
12379
12603
  const output = await diagnosticTool({ value: 1 });
12380
12604
  const result2 = { result: output };
12381
12605
  setTraceOutput(result2);
12382
- root.setStatus({ code: import_api9.SpanStatusCode.OK });
12606
+ root.setStatus({ code: import_api11.SpanStatusCode.OK });
12383
12607
  return result2;
12384
12608
  });
12385
12609
  let flushed = false;
@@ -12493,12 +12717,13 @@ function failedProbeResult(local, reasonCode, details) {
12493
12717
  };
12494
12718
  }
12495
12719
  function failedProbeTransportCheck(reasonCode, details) {
12720
+ const failure2 = PROBE_FAILURE[reasonCode];
12496
12721
  return {
12497
12722
  name: "probe_transport",
12498
12723
  status: "fail",
12499
12724
  reason_code: reasonCode,
12500
- remediation_code: reasonCode === "AUTH_FAILED" ? "CHECK_INGEST_CREDENTIAL" : "CHECK_TRACE_ENDPOINT",
12501
- message: reasonCode === "AUTH_FAILED" ? "The project key was rejected by the existing trace API" : "The existing trace ingestion or read path is unavailable",
12725
+ remediation_code: failure2.remediationCode,
12726
+ message: failure2.message,
12502
12727
  ...details ? { details } : {}
12503
12728
  };
12504
12729
  }
@@ -12576,16 +12801,23 @@ function human(result) {
12576
12801
  if (result.note) lines.push(result.note);
12577
12802
  return lines.join("\n");
12578
12803
  }
12804
+ function readbackTimeoutReason(remainingMs, requestTimeoutMs) {
12805
+ return remainingMs <= requestTimeoutMs ? "TRACE_READBACK_TIMEOUT" : "BACKEND_CONNECTION_FAILED";
12806
+ }
12807
+ function responseBodyFailureReason(status, remainingMs, requestTimeoutMs) {
12808
+ return status === 409 || status >= 200 && status < 300 && status !== 202 ? "TRACE_READBACK_INVALID" : readbackTimeoutReason(remainingMs, requestTimeoutMs);
12809
+ }
12579
12810
  async function boundedFetch(io, input, initOptions, deadline) {
12580
12811
  const remaining = deadline - Date.now();
12581
12812
  if (remaining <= 0) {
12582
- throw new ProbeReadError("BACKEND_PROBE_UNAVAILABLE", "Doctor probe deadline expired");
12813
+ throw new ProbeReadError("TRACE_READBACK_TIMEOUT", "Doctor probe deadline expired");
12583
12814
  }
12584
12815
  const controller = new AbortController();
12585
12816
  return withTimeout(
12586
12817
  () => io.fetch(input, { ...initOptions, signal: controller.signal }),
12587
12818
  Math.min(io.requestTimeoutMs, remaining),
12588
- () => controller.abort()
12819
+ () => controller.abort(),
12820
+ readbackTimeoutReason(remaining, io.requestTimeoutMs)
12589
12821
  );
12590
12822
  }
12591
12823
  async function discardResponseBody(response, reader) {
@@ -12607,7 +12839,7 @@ async function boundedJson2(response, io, deadline) {
12607
12839
  const remaining = deadline - Date.now();
12608
12840
  if (remaining <= 0) {
12609
12841
  await discardResponseBody(response);
12610
- throw new ProbeReadError("BACKEND_PROBE_UNAVAILABLE", "Doctor probe deadline expired");
12842
+ throw new ProbeReadError("TRACE_READBACK_TIMEOUT", "Doctor probe deadline expired");
12611
12843
  }
12612
12844
  const contentLength = Number(response.headers.get("content-length"));
12613
12845
  if (Number.isFinite(contentLength) && contentLength > MAX_READBACK_BYTES) {
@@ -12642,9 +12874,17 @@ async function boundedJson2(response, io, deadline) {
12642
12874
  }
12643
12875
  }, Math.min(io.requestTimeoutMs, remaining), () => {
12644
12876
  void discardResponseBody(response, reader);
12645
- }).catch(async () => {
12877
+ }, responseBodyFailureReason(
12878
+ response.status,
12879
+ remaining,
12880
+ io.requestTimeoutMs
12881
+ )).catch(async (error) => {
12646
12882
  await discardResponseBody(response, reader);
12647
- return null;
12883
+ if (error instanceof ProbeReadError) throw error;
12884
+ throw new ProbeReadError(
12885
+ "BACKEND_CONNECTION_FAILED",
12886
+ "Trace read-back response could not be read"
12887
+ );
12648
12888
  });
12649
12889
  }
12650
12890
  function usage() {
@@ -12749,10 +12989,11 @@ async function runDoctorCli(argv, overrides = {}) {
12749
12989
  "x-neatlogs-doctor": DOCTOR_MARKER_VERSION
12750
12990
  }
12751
12991
  }, deadline);
12752
- } catch {
12992
+ } catch (error) {
12993
+ if (error instanceof ProbeReadError) throw error;
12753
12994
  throw new ProbeReadError(
12754
- "BACKEND_PROBE_UNAVAILABLE",
12755
- "The existing trace read path is unavailable",
12995
+ "BACKEND_CONNECTION_FAILED",
12996
+ "Could not connect to the existing trace read path",
12756
12997
  lastDiagnostics
12757
12998
  );
12758
12999
  }
@@ -12760,7 +13001,7 @@ async function runDoctorCli(argv, overrides = {}) {
12760
13001
  const value = await boundedJson2(response, io, deadline);
12761
13002
  if (!value || typeof value !== "object") {
12762
13003
  throw new ProbeReadError(
12763
- "BACKEND_PROBE_UNAVAILABLE",
13004
+ "TRACE_READBACK_INVALID",
12764
13005
  "Trace read-back returned an invalid response"
12765
13006
  );
12766
13007
  }
@@ -12773,12 +13014,23 @@ async function runDoctorCli(argv, overrides = {}) {
12773
13014
  throw new ProbeReadError("AUTH_FAILED", "Trace read-back rejected the project key");
12774
13015
  }
12775
13016
  if ([202, 404, 409].includes(response.status)) {
12776
- const value = await boundedJson2(response, io, deadline).catch(() => null);
13017
+ const value = await boundedJson2(response, io, deadline);
12777
13018
  const currentDiagnostics = ingestionDiagnosticDetails(value);
12778
13019
  if (currentDiagnostics) lastDiagnostics = currentDiagnostics;
12779
13020
  if (response.status === 409) {
13021
+ const terminalValue = objectValue(value);
13022
+ const isDlq = terminalValue.finalizationStatus === "dlq";
13023
+ const hasDiagnostics = Object.hasOwn(terminalValue, "ingestionDiagnostics");
13024
+ const validDlq = typeof terminalValue.error === "string" && (terminalValue.message === void 0 || typeof terminalValue.message === "string") && (!hasDiagnostics || currentDiagnostics !== void 0);
13025
+ const validReceipt = currentDiagnostics?.ingestion_state === "failed";
13026
+ if (isDlq ? !validDlq : !validReceipt) {
13027
+ throw new ProbeReadError(
13028
+ "TRACE_READBACK_INVALID",
13029
+ "Trace read-back returned an invalid terminal receipt"
13030
+ );
13031
+ }
12780
13032
  throw new ProbeReadError(
12781
- "BACKEND_PROBE_UNAVAILABLE",
13033
+ "INGESTION_PIPELINE_FAILED",
12782
13034
  "Trace ingestion reported a terminal failure",
12783
13035
  currentDiagnostics
12784
13036
  );
@@ -12786,7 +13038,7 @@ async function runDoctorCli(argv, overrides = {}) {
12786
13038
  } else {
12787
13039
  await discardResponseBody(response);
12788
13040
  throw new ProbeReadError(
12789
- "BACKEND_PROBE_UNAVAILABLE",
13041
+ "BACKEND_HTTP_ERROR",
12790
13042
  `Trace read-back failed with HTTP ${response.status}`,
12791
13043
  response.status >= 500 ? lastDiagnostics : void 0
12792
13044
  );
@@ -12796,7 +13048,7 @@ async function runDoctorCli(argv, overrides = {}) {
12796
13048
  }
12797
13049
  if (!persisted) {
12798
13050
  throw new ProbeReadError(
12799
- "BACKEND_PROBE_UNAVAILABLE",
13051
+ "TRACE_READBACK_TIMEOUT",
12800
13052
  "Timed out waiting for the exact Doctor trace",
12801
13053
  lastDiagnostics
12802
13054
  );
@@ -12805,7 +13057,7 @@ async function runDoctorCli(argv, overrides = {}) {
12805
13057
  io.stdout(json ? JSON.stringify(result, null, 2) : human(result));
12806
13058
  return result.status === "pass" ? 0 : 3;
12807
13059
  } catch (error) {
12808
- const reason = error instanceof ProbeReadError && error.reasonCode === "AUTH_FAILED" ? "AUTH_FAILED" : "BACKEND_PROBE_UNAVAILABLE";
13060
+ const reason = error instanceof ProbeReadError ? error.reasonCode : "BACKEND_PROBE_UNAVAILABLE";
12809
13061
  const result = local ? failedProbeResult(
12810
13062
  local,
12811
13063
  reason,