autotel 7.0.1 → 7.2.0

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 (62) hide show
  1. package/README.md +25 -0
  2. package/dist/analysis.cjs +17 -0
  3. package/dist/analysis.d.cts +9 -1
  4. package/dist/analysis.d.ts +9 -1
  5. package/dist/analysis.js +17 -1
  6. package/dist/auto.cjs +2 -3
  7. package/dist/auto.js +1 -2
  8. package/dist/correlation-id.cjs +1 -1
  9. package/dist/correlation-id.js +1 -1
  10. package/dist/decorators.cjs +1 -1
  11. package/dist/decorators.d.cts +1 -1
  12. package/dist/decorators.d.ts +1 -1
  13. package/dist/decorators.js +1 -1
  14. package/dist/event.cjs +1 -1
  15. package/dist/event.js +1 -1
  16. package/dist/evidence.cjs +57 -0
  17. package/dist/evidence.d.cts +61 -0
  18. package/dist/evidence.d.ts +61 -0
  19. package/dist/evidence.js +50 -0
  20. package/dist/{functional-C2ONG9PK.cjs → functional-C925G95W.cjs} +68 -17
  21. package/dist/{functional-CJn2zY4o.d.cts → functional-CgWcRXGX.d.cts} +12 -5
  22. package/dist/{functional-nS5U4KCQ.js → functional-Doi0hFm3.js} +64 -13
  23. package/dist/{functional-DXy1cHFF.d.ts → functional-RFFpQzAV.d.ts} +12 -5
  24. package/dist/functional.cjs +1 -1
  25. package/dist/functional.d.cts +1 -1
  26. package/dist/functional.d.ts +1 -1
  27. package/dist/functional.js +1 -1
  28. package/dist/http.cjs +1 -1
  29. package/dist/http.js +1 -1
  30. package/dist/index.cjs +53 -15
  31. package/dist/index.d.cts +24 -4
  32. package/dist/index.d.ts +24 -4
  33. package/dist/index.js +40 -5
  34. package/dist/{init-DrlL1eis.d.cts → init-8GSy2bza.d.cts} +1 -1
  35. package/dist/{init-DDyaLzeg.js → init-DzAdgcFM.js} +2181 -1303
  36. package/dist/{init-BgnYi2sm.d.ts → init-UnKrXhpq.d.ts} +1 -1
  37. package/dist/{init-C5aHRqi2.cjs → init-aTZKsAwy.cjs} +2324 -1294
  38. package/dist/instrumentation.cjs +2 -3
  39. package/dist/instrumentation.js +1 -2
  40. package/dist/messaging.cjs +5 -5
  41. package/dist/messaging.js +2 -2
  42. package/dist/{sampling-CM5S2Lp3.d.ts → sampling-DBng4Foa.d.ts} +20 -1
  43. package/dist/{sampling-CmRdnXUi.d.cts → sampling-Er217Mge.d.cts} +20 -1
  44. package/dist/sampling.cjs +22 -601
  45. package/dist/sampling.d.cts +2 -2
  46. package/dist/sampling.d.ts +2 -2
  47. package/dist/sampling.js +2 -585
  48. package/dist/semantic-helpers.cjs +1 -1
  49. package/dist/semantic-helpers.js +1 -1
  50. package/dist/tail-sampling-processor.cjs +2 -26
  51. package/dist/tail-sampling-processor.js +1 -25
  52. package/dist/webhook.cjs +1 -1
  53. package/dist/webhook.js +1 -1
  54. package/dist/workflow-distributed.cjs +1 -1
  55. package/dist/workflow-distributed.js +1 -1
  56. package/dist/workflow.cjs +1 -1
  57. package/dist/workflow.js +1 -1
  58. package/dist/yaml-config.cjs +4 -248
  59. package/dist/yaml-config.d.cts +2 -2
  60. package/dist/yaml-config.d.ts +2 -2
  61. package/dist/yaml-config.js +1 -242
  62. package/package.json +6 -1
package/README.md CHANGED
@@ -775,6 +775,31 @@ app.use((req, res, next) => {
775
775
  - Baggage values are strings (convert numbers/objects before setting)
776
776
  - Never put PII in baggage - it propagates in HTTP headers across services!
777
777
 
778
+ ### Getting the trace id, and a link to it
779
+
780
+ `getActiveTraceContext()` reads the trace currently in scope, and
781
+ `resolveTraceUrl()` turns its id into a link to wherever you view traces. Printing
782
+ that link beside a slow request is the shortest path from "this was slow" to
783
+ looking at why:
784
+
785
+ ```typescript
786
+ import { getActiveTraceContext, resolveTraceUrl, span } from 'autotel';
787
+
788
+ await span('GET /feed', async () => {
789
+ const traceId = getActiveTraceContext()?.traceId;
790
+ const url = resolveTraceUrl(
791
+ 'http://localhost:4318/#trace={traceId}',
792
+ traceId,
793
+ );
794
+ console.log(`feed request ${url ?? traceId}`);
795
+ });
796
+ ```
797
+
798
+ `resolveTraceUrl(template, traceId)` substitutes `{traceId}`. Pass `undefined`
799
+ as the template and it falls back to `OTEL_TRACE_URL_TEMPLATE`, so the same code
800
+ points at devtools locally and at your vendor in production without a branch.
801
+ It returns `undefined` when neither is set.
802
+
778
803
  ### Reusable Middleware Helpers
779
804
 
780
805
  - `withTracing(options)` : create a preconfigured wrapper (service name, default attributes, skip rules).
package/dist/analysis.cjs CHANGED
@@ -81,6 +81,23 @@ function compareCohorts(options) {
81
81
  results.sort((a, b) => Math.abs(b.difference) - Math.abs(a.difference) || b.difference - a.difference || a.field.localeCompare(b.field) || a.value.localeCompare(b.value));
82
82
  return results.slice(0, limit);
83
83
  }
84
+ /**
85
+ * Label a numeric value with the range it falls in.
86
+ *
87
+ * `compareCohorts` skips a field whose values never repeat, which is what a
88
+ * raw duration or byte count does. Bucketing at instrumentation time turns
89
+ * such a field into one that can describe a cohort.
90
+ */
91
+ function bucket(value, boundaries) {
92
+ if (!Number.isFinite(value) || boundaries.length === 0) return "unknown";
93
+ const ordered = [...boundaries].sort((a, b) => a - b);
94
+ for (const [index, boundary] of ordered.entries()) if (value < boundary) {
95
+ const lower = ordered[index - 1];
96
+ return lower === void 0 ? `<${boundary}` : `${lower}-${boundary}`;
97
+ }
98
+ return `>=${ordered.at(-1)}`;
99
+ }
84
100
 
85
101
  //#endregion
102
+ exports.bucket = bucket;
86
103
  exports.compareCohorts = compareCohorts;
@@ -82,5 +82,13 @@ interface CompareCohortsOptions {
82
82
  * @returns Differences sorted by absolute strength, strongest first
83
83
  */
84
84
  declare function compareCohorts(options: CompareCohortsOptions): CohortDifference[];
85
+ /**
86
+ * Label a numeric value with the range it falls in.
87
+ *
88
+ * `compareCohorts` skips a field whose values never repeat, which is what a
89
+ * raw duration or byte count does. Bucketing at instrumentation time turns
90
+ * such a field into one that can describe a cohort.
91
+ */
92
+ declare function bucket(value: number, boundaries: readonly number[]): string;
85
93
  //#endregion
86
- export { AnalysisEvent, CohortDifference, CompareCohortsOptions, compareCohorts };
94
+ export { AnalysisEvent, CohortDifference, CompareCohortsOptions, bucket, compareCohorts };
@@ -82,5 +82,13 @@ interface CompareCohortsOptions {
82
82
  * @returns Differences sorted by absolute strength, strongest first
83
83
  */
84
84
  declare function compareCohorts(options: CompareCohortsOptions): CohortDifference[];
85
+ /**
86
+ * Label a numeric value with the range it falls in.
87
+ *
88
+ * `compareCohorts` skips a field whose values never repeat, which is what a
89
+ * raw duration or byte count does. Bucketing at instrumentation time turns
90
+ * such a field into one that can describe a cohort.
91
+ */
92
+ declare function bucket(value: number, boundaries: readonly number[]): string;
85
93
  //#endregion
86
- export { AnalysisEvent, CohortDifference, CompareCohortsOptions, compareCohorts };
94
+ export { AnalysisEvent, CohortDifference, CompareCohortsOptions, bucket, compareCohorts };
package/dist/analysis.js CHANGED
@@ -79,6 +79,22 @@ function compareCohorts(options) {
79
79
  results.sort((a, b) => Math.abs(b.difference) - Math.abs(a.difference) || b.difference - a.difference || a.field.localeCompare(b.field) || a.value.localeCompare(b.value));
80
80
  return results.slice(0, limit);
81
81
  }
82
+ /**
83
+ * Label a numeric value with the range it falls in.
84
+ *
85
+ * `compareCohorts` skips a field whose values never repeat, which is what a
86
+ * raw duration or byte count does. Bucketing at instrumentation time turns
87
+ * such a field into one that can describe a cohort.
88
+ */
89
+ function bucket(value, boundaries) {
90
+ if (!Number.isFinite(value) || boundaries.length === 0) return "unknown";
91
+ const ordered = [...boundaries].sort((a, b) => a - b);
92
+ for (const [index, boundary] of ordered.entries()) if (value < boundary) {
93
+ const lower = ordered[index - 1];
94
+ return lower === void 0 ? `<${boundary}` : `${lower}-${boundary}`;
95
+ }
96
+ return `>=${ordered.at(-1)}`;
97
+ }
82
98
 
83
99
  //#endregion
84
- export { compareCohorts };
100
+ export { bucket, compareCohorts };
package/dist/auto.cjs CHANGED
@@ -1,6 +1,5 @@
1
1
  const require_rolldown_runtime = require('./rolldown-runtime-C_NdSu1c.cjs');
2
- const require_init = require('./init-C5aHRqi2.cjs');
3
- const require_yaml_config = require('./yaml-config.cjs');
2
+ const require_init = require('./init-aTZKsAwy.cjs');
4
3
  let node_module = require("node:module");
5
4
  node_module = require_rolldown_runtime.__toESM(node_module, 1);
6
5
  let import_in_the_middle = require("import-in-the-middle");
@@ -42,7 +41,7 @@ let import_in_the_middle = require("import-in-the-middle");
42
41
  */
43
42
  const { registerOptions } = (0, import_in_the_middle.createAddHookMessageChannel)();
44
43
  node_module.register("import-in-the-middle/hook.mjs", require("url").pathToFileURL(__filename).href, registerOptions);
45
- const yamlConfig = require_yaml_config.loadYamlConfig();
44
+ const yamlConfig = require_init.loadYamlConfig();
46
45
  const autoInstrumentationsEnv = process.env.AUTOTEL_INTEGRATIONS;
47
46
  const autoInstrumentations = autoInstrumentationsEnv === "true" ? true : autoInstrumentationsEnv ? autoInstrumentationsEnv.split(",").map((s) => s.trim()) : yamlConfig?.autoInstrumentations ?? ["http", "express"];
48
47
  require_init.init({
package/dist/auto.js CHANGED
@@ -1,5 +1,4 @@
1
- import { i as init } from "./init-DDyaLzeg.js";
2
- import { loadYamlConfig } from "./yaml-config.js";
1
+ import { g as loadYamlConfig, i as init } from "./init-DzAdgcFM.js";
3
2
  import * as nodeModule from "node:module";
4
3
  import { createAddHookMessageChannel } from "import-in-the-middle";
5
4
 
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_init = require('./init-C5aHRqi2.cjs');
2
+ const require_init = require('./init-aTZKsAwy.cjs');
3
3
 
4
4
  exports.CORRELATION_ID_BAGGAGE_KEY = require_init.CORRELATION_ID_BAGGAGE_KEY;
5
5
  exports.generateCorrelationId = require_init.generateCorrelationId;
@@ -1,3 +1,3 @@
1
- import { C as setCorrelationId, S as runWithCorrelationId, _ as CORRELATION_ID_BAGGAGE_KEY, b as getCorrelationStorage, v as generateCorrelationId, w as setCorrelationIdInBaggage, x as getOrCreateCorrelationId, y as getCorrelationId } from "./init-DDyaLzeg.js";
1
+ import { $ as getOrCreateCorrelationId, Q as getCorrelationStorage, X as generateCorrelationId, Y as CORRELATION_ID_BAGGAGE_KEY, Z as getCorrelationId, et as runWithCorrelationId, nt as setCorrelationIdInBaggage, tt as setCorrelationId } from "./init-DzAdgcFM.js";
2
2
 
3
3
  export { CORRELATION_ID_BAGGAGE_KEY, generateCorrelationId, getCorrelationId, getCorrelationStorage, getOrCreateCorrelationId, runWithCorrelationId, setCorrelationId, setCorrelationIdInBaggage };
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_values = require('./values-DApyq53y.cjs');
3
3
  const require_config = require('./config.cjs');
4
- const require_init = require('./init-C5aHRqi2.cjs');
4
+ const require_init = require('./init-aTZKsAwy.cjs');
5
5
  let _opentelemetry_api = require("@opentelemetry/api");
6
6
 
7
7
  //#region src/decorators.ts
@@ -1,5 +1,5 @@
1
1
  import { n as TraceContext } from "./trace-context-C1Cn1q0y.cjs";
2
- import { m as TracingOptions } from "./functional-CJn2zY4o.cjs";
2
+ import { m as TracingOptions } from "./functional-CgWcRXGX.cjs";
3
3
  //#region src/decorators.d.ts
4
4
  /**
5
5
  * Options for @Trace method decorator
@@ -1,5 +1,5 @@
1
1
  import { n as TraceContext } from "./trace-context-f89xPJbK.js";
2
- import { m as TracingOptions } from "./functional-DXy1cHFF.js";
2
+ import { m as TracingOptions } from "./functional-RFFpQzAV.js";
3
3
  //#region src/decorators.d.ts
4
4
  /**
5
5
  * Options for @Trace method decorator
@@ -1,6 +1,6 @@
1
1
  import { y as splitNameOrOptions } from "./values-BenyvWKz.js";
2
2
  import { getConfig } from "./config.js";
3
- import { T as createTraceContext } from "./init-DDyaLzeg.js";
3
+ import { H as createTraceContext } from "./init-DzAdgcFM.js";
4
4
  import { SpanStatusCode } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/decorators.ts
package/dist/event.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_init = require('./init-C5aHRqi2.cjs');
2
+ const require_init = require('./init-aTZKsAwy.cjs');
3
3
 
4
4
  exports.Event = require_init.Event;
5
5
  exports.getEvents = require_init.getEvents;
package/dist/event.js CHANGED
@@ -1,3 +1,3 @@
1
- import { d as getEvents, f as resetEvents, u as Event } from "./init-DDyaLzeg.js";
1
+ import { d as getEvents, f as resetEvents, u as Event } from "./init-DzAdgcFM.js";
2
2
 
3
3
  export { Event, getEvents, resetEvents };
@@ -0,0 +1,57 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ //#region src/evidence.ts
4
+ /** Prefix for per-field evidence labels. */
5
+ const EVIDENCE_ATTR_PREFIX = "autotel.evidence.";
6
+ /** The span attribute key carrying `field`'s evidence label. */
7
+ function evidenceAttribute(field) {
8
+ return `${EVIDENCE_ATTR_PREFIX}${field}`;
9
+ }
10
+ /** Label how `field` on this span came to be. */
11
+ function recordEvidence(ctx, field, quality) {
12
+ ctx.setAttribute(evidenceAttribute(field), quality);
13
+ }
14
+ /**
15
+ * The capture surfaces a deployment can be asked about. Closed set, because the
16
+ * point is that a backend can query `unobserved` across services and get
17
+ * comparable answers.
18
+ */
19
+ const CAPTURE_SURFACES = [
20
+ "llm_calls",
21
+ "tool_calls",
22
+ "user_prompts",
23
+ "file_io",
24
+ "subprocess",
25
+ "network",
26
+ "ide_context"
27
+ ];
28
+ const CAPTURE_COVERAGE_ATTR = {
29
+ observed: "autotel.coverage.observed",
30
+ unobserved: "autotel.coverage.unobserved"
31
+ };
32
+ /**
33
+ * Resource attributes declaring {@link CaptureCoverage}.
34
+ *
35
+ * An empty list is omitted rather than emitted: `unobserved: []` would read as
36
+ * "this process has no blind spots", which is a stronger claim than declaring
37
+ * nothing. A surface claimed as both is dropped from both — a contradiction
38
+ * that left `observed` intact would let a reader conclude a gap does not exist.
39
+ */
40
+ function captureCoverageAttributes(coverage) {
41
+ const claimedBoth = new Set(coverage.observed.filter((s) => coverage.unobserved.includes(s)));
42
+ const keep = (surfaces) => [...new Set(surfaces.filter((s) => !claimedBoth.has(s)))];
43
+ const observed = keep(coverage.observed);
44
+ const unobserved = keep(coverage.unobserved);
45
+ return {
46
+ ...observed.length > 0 && { [CAPTURE_COVERAGE_ATTR.observed]: observed },
47
+ ...unobserved.length > 0 && { [CAPTURE_COVERAGE_ATTR.unobserved]: unobserved }
48
+ };
49
+ }
50
+
51
+ //#endregion
52
+ exports.CAPTURE_COVERAGE_ATTR = CAPTURE_COVERAGE_ATTR;
53
+ exports.CAPTURE_SURFACES = CAPTURE_SURFACES;
54
+ exports.EVIDENCE_ATTR_PREFIX = EVIDENCE_ATTR_PREFIX;
55
+ exports.captureCoverageAttributes = captureCoverageAttributes;
56
+ exports.evidenceAttribute = evidenceAttribute;
57
+ exports.recordEvidence = recordEvidence;
@@ -0,0 +1,61 @@
1
+ import { Attributes } from "@opentelemetry/api";
2
+ //#region src/evidence.d.ts
3
+ /**
4
+ * Why a recorded fact is the way it is.
5
+ *
6
+ * There is deliberately no `unknown` member: a field carrying no evidence label
7
+ * is already unknown, and encoding that as a value invites code that treats an
8
+ * unlabelled field as *verified* unknown rather than simply unexamined.
9
+ */
10
+ type EvidenceQuality =
11
+ /** The instrumentation saw the value directly. */
12
+ 'observed' |
13
+ /** Derived from other observed facts, not seen. */
14
+ 'inferred' |
15
+ /** Computed from a price table, heuristic, or model — not reported upstream. */
16
+ 'estimated' |
17
+ /** Seen, then cut short. What is here is real; what is missing is not. */
18
+ 'truncated' |
19
+ /** Seen, then removed before storage. */
20
+ 'redacted' |
21
+ /** Expected here and not present — a real gap in an observable place. */
22
+ 'absent' |
23
+ /** This process cannot see it at all, so its absence proves nothing. */
24
+ 'unobservable';
25
+ /** Prefix for per-field evidence labels. */
26
+ declare const EVIDENCE_ATTR_PREFIX = "autotel.evidence.";
27
+ /** The span attribute key carrying `field`'s evidence label. */
28
+ declare function evidenceAttribute(field: string): string;
29
+ /** The one thing {@link recordEvidence} needs — any `TraceContext` or `Span`. */
30
+ interface EvidenceTarget {
31
+ setAttribute(key: string, value: string): void;
32
+ }
33
+ /** Label how `field` on this span came to be. */
34
+ declare function recordEvidence(ctx: EvidenceTarget, field: string, quality: EvidenceQuality): void;
35
+ /**
36
+ * The capture surfaces a deployment can be asked about. Closed set, because the
37
+ * point is that a backend can query `unobserved` across services and get
38
+ * comparable answers.
39
+ */
40
+ declare const CAPTURE_SURFACES: readonly ["llm_calls", "tool_calls", "user_prompts", "file_io", "subprocess", "network", "ide_context"];
41
+ type CaptureSurface = (typeof CAPTURE_SURFACES)[number];
42
+ /** What this process can and cannot see. Anything in neither list is unknown. */
43
+ interface CaptureCoverage {
44
+ observed: readonly CaptureSurface[];
45
+ unobserved: readonly CaptureSurface[];
46
+ }
47
+ declare const CAPTURE_COVERAGE_ATTR: {
48
+ readonly observed: "autotel.coverage.observed";
49
+ readonly unobserved: "autotel.coverage.unobserved";
50
+ };
51
+ /**
52
+ * Resource attributes declaring {@link CaptureCoverage}.
53
+ *
54
+ * An empty list is omitted rather than emitted: `unobserved: []` would read as
55
+ * "this process has no blind spots", which is a stronger claim than declaring
56
+ * nothing. A surface claimed as both is dropped from both — a contradiction
57
+ * that left `observed` intact would let a reader conclude a gap does not exist.
58
+ */
59
+ declare function captureCoverageAttributes(coverage: CaptureCoverage): Attributes;
60
+ //#endregion
61
+ export { CAPTURE_COVERAGE_ATTR, CAPTURE_SURFACES, CaptureCoverage, CaptureSurface, EVIDENCE_ATTR_PREFIX, EvidenceQuality, EvidenceTarget, captureCoverageAttributes, evidenceAttribute, recordEvidence };
@@ -0,0 +1,61 @@
1
+ import { Attributes } from "@opentelemetry/api";
2
+ //#region src/evidence.d.ts
3
+ /**
4
+ * Why a recorded fact is the way it is.
5
+ *
6
+ * There is deliberately no `unknown` member: a field carrying no evidence label
7
+ * is already unknown, and encoding that as a value invites code that treats an
8
+ * unlabelled field as *verified* unknown rather than simply unexamined.
9
+ */
10
+ type EvidenceQuality =
11
+ /** The instrumentation saw the value directly. */
12
+ 'observed' |
13
+ /** Derived from other observed facts, not seen. */
14
+ 'inferred' |
15
+ /** Computed from a price table, heuristic, or model — not reported upstream. */
16
+ 'estimated' |
17
+ /** Seen, then cut short. What is here is real; what is missing is not. */
18
+ 'truncated' |
19
+ /** Seen, then removed before storage. */
20
+ 'redacted' |
21
+ /** Expected here and not present — a real gap in an observable place. */
22
+ 'absent' |
23
+ /** This process cannot see it at all, so its absence proves nothing. */
24
+ 'unobservable';
25
+ /** Prefix for per-field evidence labels. */
26
+ declare const EVIDENCE_ATTR_PREFIX = "autotel.evidence.";
27
+ /** The span attribute key carrying `field`'s evidence label. */
28
+ declare function evidenceAttribute(field: string): string;
29
+ /** The one thing {@link recordEvidence} needs — any `TraceContext` or `Span`. */
30
+ interface EvidenceTarget {
31
+ setAttribute(key: string, value: string): void;
32
+ }
33
+ /** Label how `field` on this span came to be. */
34
+ declare function recordEvidence(ctx: EvidenceTarget, field: string, quality: EvidenceQuality): void;
35
+ /**
36
+ * The capture surfaces a deployment can be asked about. Closed set, because the
37
+ * point is that a backend can query `unobserved` across services and get
38
+ * comparable answers.
39
+ */
40
+ declare const CAPTURE_SURFACES: readonly ["llm_calls", "tool_calls", "user_prompts", "file_io", "subprocess", "network", "ide_context"];
41
+ type CaptureSurface = (typeof CAPTURE_SURFACES)[number];
42
+ /** What this process can and cannot see. Anything in neither list is unknown. */
43
+ interface CaptureCoverage {
44
+ observed: readonly CaptureSurface[];
45
+ unobserved: readonly CaptureSurface[];
46
+ }
47
+ declare const CAPTURE_COVERAGE_ATTR: {
48
+ readonly observed: "autotel.coverage.observed";
49
+ readonly unobserved: "autotel.coverage.unobserved";
50
+ };
51
+ /**
52
+ * Resource attributes declaring {@link CaptureCoverage}.
53
+ *
54
+ * An empty list is omitted rather than emitted: `unobserved: []` would read as
55
+ * "this process has no blind spots", which is a stronger claim than declaring
56
+ * nothing. A surface claimed as both is dropped from both — a contradiction
57
+ * that left `observed` intact would let a reader conclude a gap does not exist.
58
+ */
59
+ declare function captureCoverageAttributes(coverage: CaptureCoverage): Attributes;
60
+ //#endregion
61
+ export { CAPTURE_COVERAGE_ATTR, CAPTURE_SURFACES, CaptureCoverage, CaptureSurface, EVIDENCE_ATTR_PREFIX, EvidenceQuality, EvidenceTarget, captureCoverageAttributes, evidenceAttribute, recordEvidence };
@@ -0,0 +1,50 @@
1
+ //#region src/evidence.ts
2
+ /** Prefix for per-field evidence labels. */
3
+ const EVIDENCE_ATTR_PREFIX = "autotel.evidence.";
4
+ /** The span attribute key carrying `field`'s evidence label. */
5
+ function evidenceAttribute(field) {
6
+ return `${EVIDENCE_ATTR_PREFIX}${field}`;
7
+ }
8
+ /** Label how `field` on this span came to be. */
9
+ function recordEvidence(ctx, field, quality) {
10
+ ctx.setAttribute(evidenceAttribute(field), quality);
11
+ }
12
+ /**
13
+ * The capture surfaces a deployment can be asked about. Closed set, because the
14
+ * point is that a backend can query `unobserved` across services and get
15
+ * comparable answers.
16
+ */
17
+ const CAPTURE_SURFACES = [
18
+ "llm_calls",
19
+ "tool_calls",
20
+ "user_prompts",
21
+ "file_io",
22
+ "subprocess",
23
+ "network",
24
+ "ide_context"
25
+ ];
26
+ const CAPTURE_COVERAGE_ATTR = {
27
+ observed: "autotel.coverage.observed",
28
+ unobserved: "autotel.coverage.unobserved"
29
+ };
30
+ /**
31
+ * Resource attributes declaring {@link CaptureCoverage}.
32
+ *
33
+ * An empty list is omitted rather than emitted: `unobserved: []` would read as
34
+ * "this process has no blind spots", which is a stronger claim than declaring
35
+ * nothing. A surface claimed as both is dropped from both — a contradiction
36
+ * that left `observed` intact would let a reader conclude a gap does not exist.
37
+ */
38
+ function captureCoverageAttributes(coverage) {
39
+ const claimedBoth = new Set(coverage.observed.filter((s) => coverage.unobserved.includes(s)));
40
+ const keep = (surfaces) => [...new Set(surfaces.filter((s) => !claimedBoth.has(s)))];
41
+ const observed = keep(coverage.observed);
42
+ const unobserved = keep(coverage.unobserved);
43
+ return {
44
+ ...observed.length > 0 && { [CAPTURE_COVERAGE_ATTR.observed]: observed },
45
+ ...unobserved.length > 0 && { [CAPTURE_COVERAGE_ATTR.unobserved]: unobserved }
46
+ };
47
+ }
48
+
49
+ //#endregion
50
+ export { CAPTURE_COVERAGE_ATTR, CAPTURE_SURFACES, EVIDENCE_ATTR_PREFIX, captureCoverageAttributes, evidenceAttribute, recordEvidence };
@@ -2,8 +2,8 @@ const require_rolldown_runtime = require('./rolldown-runtime-C_NdSu1c.cjs');
2
2
  const require_values = require('./values-DApyq53y.cjs');
3
3
  const require_tracer_provider = require('./tracer-provider.cjs');
4
4
  const require_config = require('./config.cjs');
5
- const require_sampling = require('./sampling.cjs');
6
- const require_init = require('./init-C5aHRqi2.cjs');
5
+ const require_init = require('./init-aTZKsAwy.cjs');
6
+ const require_evidence = require('./evidence.cjs');
7
7
  const require_trace_helpers = require('./trace-helpers.cjs');
8
8
  let _opentelemetry_api = require("@opentelemetry/api");
9
9
  let node_fs = require("node:fs");
@@ -11,6 +11,36 @@ node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
11
11
  let node_url = require("node:url");
12
12
  node_url = require_rolldown_runtime.__toESM(node_url, 1);
13
13
 
14
+ //#region src/is-thenable.ts
15
+ /**
16
+ * Convert anything `await` would wait on into a native Promise, or return
17
+ * `undefined` for a synchronous value.
18
+ *
19
+ * `instanceof Promise` is not enough. ORM query builders (drizzle, knex,
20
+ * Prisma, Sequelize) are lazy thenables that are not native Promises, and a
21
+ * promise created in another realm fails the check too. Treating those as
22
+ * synchronous ends the span before the work runs, which produces a short
23
+ * parent span with its real children orphaned outside it.
24
+ */
25
+ function promiseFromThenable(value) {
26
+ if (typeof value !== "object" && typeof value !== "function" || value === null) return;
27
+ let then;
28
+ try {
29
+ then = value.then;
30
+ } catch (error) {
31
+ return Promise.reject(error);
32
+ }
33
+ if (typeof then !== "function") return void 0;
34
+ return new Promise((resolve, reject) => {
35
+ try {
36
+ then.call(value, resolve, reject);
37
+ } catch (error) {
38
+ reject(error);
39
+ }
40
+ });
41
+ }
42
+
43
+ //#endregion
14
44
  //#region src/variable-name-inference.ts
15
45
  /**
16
46
  * Variable Name Inference Utility
@@ -205,25 +235,41 @@ function createDummyCtx() {
205
235
  getAllBaggage: () => /* @__PURE__ */ new Map()
206
236
  };
207
237
  }
238
+ /**
239
+ * Serialize a captured value, and say whether the ceiling cut it. The flag is
240
+ * separate from the text because `…[truncated]` inside the value is only
241
+ * legible to a human reading one span — a backend cannot filter on it.
242
+ */
208
243
  function serializeCapture(value) {
209
244
  if (value === void 0) return void 0;
210
245
  try {
211
246
  const json = require_values.asString(value) ?? JSON.stringify(value);
212
247
  if (json === void 0) return void 0;
213
- return json.length > CAPTURE_MAX_CHARS ? `${json.slice(0, CAPTURE_MAX_CHARS)}…[truncated]` : json;
248
+ return json.length > CAPTURE_MAX_CHARS ? {
249
+ text: `${json.slice(0, CAPTURE_MAX_CHARS)}…[truncated]`,
250
+ truncated: true
251
+ } : {
252
+ text: json,
253
+ truncated: false
254
+ };
214
255
  } catch {
215
256
  return;
216
257
  }
217
258
  }
218
- function captureInputAttrs(args, enabled) {
259
+ function captureAttrs(value, valueAttr, field, enabled) {
219
260
  if (!enabled) return {};
220
- const serialized = serializeCapture(args.length === 1 ? args[0] : args);
221
- return serialized === void 0 ? {} : { [AUTOTEL_INPUT_ATTR]: serialized };
261
+ const serialized = serializeCapture(value);
262
+ if (serialized === void 0) return {};
263
+ return {
264
+ [valueAttr]: serialized.text,
265
+ ...serialized.truncated && { [require_evidence.evidenceAttribute(field)]: "truncated" }
266
+ };
267
+ }
268
+ function captureInputAttrs(args, enabled) {
269
+ return captureAttrs(args.length === 1 ? args[0] : args, AUTOTEL_INPUT_ATTR, "input", enabled);
222
270
  }
223
271
  function captureOutputAttrs(result, enabled) {
224
- if (!enabled) return {};
225
- const serialized = serializeCapture(result);
226
- return serialized === void 0 ? {} : { [AUTOTEL_OUTPUT_ATTR]: serialized };
272
+ return captureAttrs(result, AUTOTEL_OUTPUT_ATTR, "output", enabled);
227
273
  }
228
274
  function truncateErrorMessage(message) {
229
275
  return message.length <= 500 ? message : `${message.slice(0, 500)}... (truncated)`;
@@ -302,7 +348,7 @@ function runWithTraceContextStorage(fn) {
302
348
  }
303
349
  function wrapWithTracingSync(fnFactory, options, variableName) {
304
350
  const { tracer, meter } = require_config.getConfig();
305
- const sampler = options.sampler || new require_sampling.AlwaysSampler();
351
+ const sampler = options.sampler || new require_init.AlwaysSampler();
306
352
  const spanName = getSpanName(options, fnFactory, variableName);
307
353
  const callCounter = options.withMetrics ? meter.createCounter(`${spanName}.calls`, {
308
354
  description: `Call count for ${spanName}`,
@@ -321,7 +367,10 @@ function wrapWithTracingSync(fnFactory, options, variableName) {
321
367
  const shouldSample = sampler.shouldSample(samplingContext);
322
368
  const sampleRate = sampler.sampleRate?.(samplingContext);
323
369
  const needsTailSampling = sampler.needsTailSampling?.() ?? false;
324
- if (!shouldSample && !needsTailSampling) return fnFactory(createDummyCtx()).call(this, ...args);
370
+ if (!shouldSample && !needsTailSampling) {
371
+ const unsampled = fnFactory(createDummyCtx()).call(this, ...args);
372
+ return promiseFromThenable(unsampled) ?? unsampled;
373
+ }
325
374
  const startTime = performance.now();
326
375
  const flushRootTelemetry = createRootTelemetryFlusher(options, options.startNewRoot || _opentelemetry_api.trace.getActiveSpan() === void 0);
327
376
  const spanOptions = {};
@@ -330,21 +379,21 @@ function wrapWithTracingSync(fnFactory, options, variableName) {
330
379
  return tracer.startActiveSpan(spanName, spanOptions, require_init.getActiveContextWithBaggage(), (span) => require_init.runInOperationContext(spanName, () => runWithTraceContextStorage(() => {
331
380
  let shouldKeepSpan = true;
332
381
  require_trace_helpers.setSpanName(span, spanName);
333
- if (sampleRate !== void 0 && sampleRate > 1) span.setAttribute(require_sampling.AUTOTEL_SAMPLING_RATE, sampleRate);
382
+ if (sampleRate !== void 0 && sampleRate > 1) span.setAttribute(require_init.AUTOTEL_SAMPLING_RATE, sampleRate);
334
383
  const fn = fnFactory(require_init.createTraceContext(span));
335
384
  const argsAttributes = {
336
385
  ...captureInputAttrs(args, options.captureInput),
337
386
  ...options.attributesFromArgs?.(args)
338
387
  };
339
388
  const handleTailSampling = (success, duration, error) => {
340
- if (needsTailSampling && sampler.shouldKeepTrace) {
389
+ if (needsTailSampling && sampler.shouldKeepTrace && !require_init.isForceKept(span) && !require_init.debugCaptureRequested()) {
341
390
  shouldKeepSpan = sampler.shouldKeepTrace(samplingContext, {
342
391
  success,
343
392
  duration,
344
393
  error
345
394
  });
346
- span.setAttribute(require_sampling.AUTOTEL_SAMPLING_TAIL_KEEP, shouldKeepSpan);
347
- span.setAttribute(require_sampling.AUTOTEL_SAMPLING_TAIL_EVALUATED, true);
395
+ span.setAttribute(require_init.AUTOTEL_SAMPLING_TAIL_KEEP, shouldKeepSpan);
396
+ span.setAttribute(require_init.AUTOTEL_SAMPLING_TAIL_EVALUATED, true);
348
397
  }
349
398
  };
350
399
  const onSuccess = (result) => {
@@ -392,7 +441,8 @@ function wrapWithTracingSync(fnFactory, options, variableName) {
392
441
  status: "started"
393
442
  });
394
443
  const result = _opentelemetry_api.context.with(_opentelemetry_api.trace.setSpan(require_init.getActiveContextWithBaggage(), span), () => fn.call(this, ...args));
395
- if (result instanceof Promise) return result.then(async (value) => {
444
+ const resultPromise = promiseFromThenable(result);
445
+ if (resultPromise) return resultPromise.then(async (value) => {
396
446
  const completed = onSuccess(value);
397
447
  await flushRootTelemetry();
398
448
  return completed;
@@ -642,7 +692,8 @@ function span(nameOrOptions, fn) {
642
692
  try {
643
693
  if (attributes) for (const [key, value] of Object.entries(attributes)) span.setAttribute(key, value);
644
694
  const result = fn(span);
645
- if (result instanceof Promise) return result.then((resolved) => {
695
+ const resultPromise = promiseFromThenable(result);
696
+ if (resultPromise) return resultPromise.then((resolved) => {
646
697
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
647
698
  span.end();
648
699
  return resolved;
@@ -1,5 +1,5 @@
1
1
  import { n as TraceContext } from "./trace-context-C1Cn1q0y.cjs";
2
- import { p as Sampler } from "./sampling-CmRdnXUi.cjs";
2
+ import { m as Sampler } from "./sampling-Er217Mge.cjs";
3
3
  import { Attributes, Span, SpanKind } from "@opentelemetry/api";
4
4
  //#region src/functional-wrapper.d.ts
5
5
  type WrappedFunction<TArgs extends unknown[], TReturn> = (...args: TArgs) => TReturn | Promise<TReturn>;
@@ -192,7 +192,9 @@ declare const ctx: TraceContext;
192
192
  * })
193
193
  * ```
194
194
  */
195
+ declare function traceImpl<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => PromiseLike<TReturn>): (...args: TArgs) => Promise<TReturn>;
195
196
  declare function traceImpl<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
197
+ declare function traceImpl<TArgs extends unknown[], TReturn>(name: string, fn: (...args: TArgs) => PromiseLike<TReturn>): (...args: TArgs) => Promise<TReturn>;
196
198
  declare function traceImpl<TArgs extends unknown[], TReturn>(name: string, fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
197
199
  declare function traceImpl<TArgs extends unknown[], TReturn>(options: TracingOptions<TArgs, TReturn>, fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
198
200
  declare function traceImpl(name: string): <TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn) => (...args: TArgs) => TReturn;
@@ -258,7 +260,7 @@ declare const trace$1: typeof traceImpl & {
258
260
  * )(ctx => async (data) => { })
259
261
  * ```
260
262
  */
261
- declare function withTracing<TCfgArgs extends unknown[] = unknown[], TCfgReturn = unknown>(options?: TracingOptions<TCfgArgs, TCfgReturn>): <TArgs extends TCfgArgs, TReturn extends TCfgReturn>(fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn | Promise<TReturn>) => WrappedFunction<TArgs, TReturn>;
263
+ declare function withTracing<TCfgArgs extends unknown[] = unknown[], TCfgReturn = unknown>(options?: TracingOptions<TCfgArgs, TCfgReturn>): <TArgs extends TCfgArgs, TReturn extends TCfgReturn>(fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn | PromiseLike<TReturn>) => WrappedFunction<TArgs, TReturn>;
262
264
  /**
263
265
  * Approach 3: instrument() - Batch auto-instrumentation
264
266
  *
@@ -309,8 +311,13 @@ declare function withTracing<TCfgArgs extends unknown[] = unknown[], TCfgReturn
309
311
  * })
310
312
  * ```
311
313
  */
314
+ type InstrumentedFunction<TFunction extends AnyInstrumentable> = TFunction extends ((...args: infer TArgs) => infer TReturn) ? (...args: TArgs) => TReturn extends PromiseLike<infer TValue> ? Promise<TValue> : TReturn : never;
315
+ type InstrumentedFunctions<T extends Record<string, AnyInstrumentable>> = { [TKey in keyof T]: InstrumentedFunction<T[TKey]>; };
316
+ declare function instrument<TFunction extends AnyInstrumentable>(options: SingleInstrumentOptions<TFunction> & {
317
+ fn: TFunction & ((...args: Parameters<TFunction>) => PromiseLike<unknown>);
318
+ }): (...args: Parameters<TFunction>) => Promise<Awaited<ReturnType<TFunction>>>;
312
319
  declare function instrument<TFunction extends AnyInstrumentable>(options: SingleInstrumentOptions<TFunction>): TFunction;
313
- declare function instrument<T extends Record<string, AnyInstrumentable>>(options: InstrumentOptions<T>): T;
320
+ declare function instrument<T extends Record<string, AnyInstrumentable>>(options: InstrumentOptions<T>): InstrumentedFunctions<T>;
314
321
  /**
315
322
  * Options for span() function
316
323
  */
@@ -352,10 +359,10 @@ interface SpanOptions {
352
359
  * })
353
360
  * ```
354
361
  */
362
+ declare function span<T = unknown>(name: string, fn: (span: Span) => PromiseLike<T>): Promise<T>;
363
+ declare function span<T = unknown>(options: SpanOptions, fn: (span: Span) => PromiseLike<T>): Promise<T>;
355
364
  declare function span<T = unknown>(name: string, fn: (span: Span) => T): T;
356
- declare function span<T = unknown>(name: string, fn: (span: Span) => Promise<T>): Promise<T>;
357
365
  declare function span<T = unknown>(options: SpanOptions, fn: (span: Span) => T): T;
358
- declare function span<T = unknown>(options: SpanOptions, fn: (span: Span) => Promise<T>): Promise<T>;
359
366
  /**
360
367
  * Options for withNewContext() function
361
368
  */