@hue-run/sdk 0.12.1 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/privacy.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { SpanStatusCode } from "@opentelemetry/api";
2
2
  import { resourceFromAttributes } from "@opentelemetry/resources";
3
- import { MAX_BODY_BYTES, MAX_CONTENT_BYTES } from "./config.js";
3
+ import { MAX_CONTENT_BYTES } from "./config.js";
4
4
  import { scrubToolCredentials, withToolCatalogSummary } from "./tool-definitions.js";
5
5
  import { hashInlineFiles } from "./inline-files.js";
6
+ import { truncateUtf8 } from "./safety.js";
7
+ export { truncateUtf8 };
6
8
  /** Attribute keys (and their dotted children) removed in metadata-only mode. */
7
9
  export const contentPrefixes = [
8
10
  "gen_ai.input.messages",
@@ -48,56 +50,120 @@ export const contentPrefixes = [
48
50
  "ai.embeddings",
49
51
  "traceloop.entity.input",
50
52
  "traceloop.entity.output",
53
+ // Langfuse content; its model name, usage, cost, type, session, user and metadata keys stay.
54
+ "langfuse.observation.input",
55
+ "langfuse.observation.output",
56
+ "langfuse.observation.status_message",
57
+ "langfuse.observation.model.parameters",
58
+ "langfuse.trace.input",
59
+ "langfuse.trace.output",
51
60
  "tool.parameters",
52
61
  "exception.message",
53
62
  "exception.stacktrace",
54
63
  ];
55
64
  // Kept as a function so metadata-only filtering is applied consistently to every exporter location.
56
- function isContentKey(key) {
65
+ export function isContentKey(key) {
57
66
  return contentPrefixes.some((prefix) => key === prefix || key.startsWith(`${prefix}.`));
58
67
  }
59
- function redactValue(value, path, options, budget, depth = 0) {
68
+ /** The record attribute listing the keys whose values were cut to fit Hue's 256 KiB value cap:
69
+ * Hue's receiver writes it for the values it cuts, and the SDK writes it for the values it cuts
70
+ * before export, under the same name, so a reader learns of a cut value from one place. */
71
+ export const TRUNCATED_KEY = "hue.truncated";
72
+ /** The size of a structured value the marker replaced. */
73
+ export const TRUNCATED_BYTES_KEY = "hue.truncated_bytes";
74
+ /** The root span attribute counting this trace's records the SDK could not export at all (a
75
+ * record over the request limit even with every content value shed, or one its queue had no
76
+ * room for): what Hue stores of the trace is incomplete by that many records. */
77
+ export const DROPPED_RECORDS_KEY = "hue.sdk.dropped_records";
78
+ /** The serialized size of a structured value as recorded, 0 for one JSON cannot serialize. */
79
+ function structuredBytes(value) {
80
+ try {
81
+ return Buffer.byteLength(JSON.stringify(value) ?? "");
82
+ }
83
+ catch {
84
+ return 0;
85
+ }
86
+ }
87
+ /** Hue's receiver's own marker for a structured value it replaced: nothing of the value, its
88
+ * size, and the flag a reader tests. */
89
+ export function truncatedMarker(bytes) {
90
+ return { [TRUNCATED_KEY]: true, [TRUNCATED_BYTES_KEY]: bytes };
91
+ }
92
+ /** `listed` with `keys` added, each once, in order. */
93
+ export function withTruncatedKeys(listed, keys) {
94
+ const result = Array.isArray(listed)
95
+ ? listed.filter((item) => typeof item === "string")
96
+ : [];
97
+ for (const key of keys)
98
+ if (!result.includes(key))
99
+ result.push(key);
100
+ return result;
101
+ }
102
+ /**
103
+ * One value through the redactor and Hue's value cap. Text over 256 KiB is cut to a UTF-8 prefix
104
+ * and bytes over it are replaced by the receiver's marker, as the receiver itself does to a
105
+ * value over its cap, and the value's key (`listAs`) is listed under `hue.truncated`: the span
106
+ * is exported whole with the rest of its evidence, and a reader knows the one value is partial.
107
+ * Dropping the record for a large value lost every call, result and reply it recorded.
108
+ */
109
+ function redactValue(value, path, options, budget, depth = 0, listAs = path) {
60
110
  if (++budget.nodes > 16384 || depth > 32)
61
111
  throw new Error("Telemetry value exceeds the supported nesting limit");
62
112
  if (typeof value === "string") {
113
+ // The redactor sees the whole recorded text, so a secret that crosses the cap is still its
114
+ // to recognize; what it returns is cut to the cap afterwards.
63
115
  const result = options.redact ? options.redact(value, path) : value;
64
116
  // JavaScript can supply an async redactor despite the synchronous contract.
65
117
  // Observe its rejection before dropping the invalid record.
66
118
  if (result && typeof result === "object")
67
119
  void Promise.resolve(result).catch(() => { });
120
+ // A redactor may shorten or keep its text, never grow it past the cap: an answer longer than
121
+ // both the cap and the text it was given is the redactor's own, refused by its length before
122
+ // anything scans it, since scanning would materialize text the record never held.
68
123
  if (typeof result !== "string" ||
69
- result.length > MAX_CONTENT_BYTES ||
124
+ result.length > Math.max(MAX_CONTENT_BYTES, value.length) ||
70
125
  !result.isWellFormed() ||
71
126
  result.includes("\u0000"))
72
127
  throw new Error("Redaction produced unsupported text");
73
- if (Buffer.byteLength(result) > MAX_CONTENT_BYTES)
74
- throw new Error("Telemetry text exceeds 256 KiB");
75
- budget.bytes += Buffer.byteLength(result);
76
- if (budget.bytes > MAX_BODY_BYTES)
77
- throw new Error("Redacted record exceeds the content budget");
78
- return result;
128
+ const bytes = Buffer.byteLength(result);
129
+ if (bytes <= MAX_CONTENT_BYTES) {
130
+ budget.bytes += bytes;
131
+ return result;
132
+ }
133
+ // Within the cap's code units but over its bytes (multibyte text): cut to the bytes.
134
+ if (!budget.truncated.includes(listAs))
135
+ budget.truncated.push(listAs);
136
+ budget.bytes += MAX_CONTENT_BYTES;
137
+ return truncateUtf8(result, MAX_CONTENT_BYTES);
79
138
  }
80
139
  if (Array.isArray(value)) {
81
140
  if (value.length > 16384)
82
141
  throw new Error("Telemetry array exceeds the complexity limit");
83
- return value.map((item, index) => redactValue(item, `${path}.${index}`, options, budget, depth + 1));
142
+ return value.map((item, index) => redactValue(item, `${path}.${index}`, options, budget, depth + 1, listAs));
84
143
  }
85
144
  if (value instanceof Uint8Array) {
86
- if (value.byteLength > MAX_CONTENT_BYTES)
87
- throw new Error("Telemetry bytes exceed 256 KiB");
88
- budget.bytes += value.byteLength;
89
- if (budget.bytes > MAX_BODY_BYTES)
90
- throw new Error("Redacted record exceeds the content budget");
91
- return value;
145
+ if (value.byteLength <= MAX_CONTENT_BYTES) {
146
+ budget.bytes += value.byteLength;
147
+ return value;
148
+ }
149
+ // Bytes are never cut: a shortened encoding is a different value.
150
+ if (!budget.truncated.includes(listAs))
151
+ budget.truncated.push(listAs);
152
+ return truncatedMarker(value.byteLength);
92
153
  }
93
154
  if (value !== null && typeof value === "object")
94
155
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [
95
156
  key,
96
- redactValue(item, `${path}.${key}`, options, budget, depth + 1),
157
+ redactValue(item, `${path}.${key}`, options, budget, depth + 1, listAs),
97
158
  ]));
98
159
  return value;
99
160
  }
100
- function attributes(source, options, path, budget) {
161
+ /**
162
+ * A record's attributes through the redactor. `listPrefix` is how a cut value of these
163
+ * attributes is listed under the record's `hue.truncated`: the key alone for the record's own
164
+ * attributes, `event:<name>:<key>` for an event's, as Hue's receiver lists the values it cuts.
165
+ */
166
+ function attributes(source, options, path, budget, listPrefix = "") {
101
167
  // Metadata-only export summarizes the tool definitions it removes by name and digest.
102
168
  const summarized = options.captureContent ? source : withToolCatalogSummary(source);
103
169
  return Object.fromEntries(Object.entries(summarized).flatMap(([key, value]) => !options.captureContent && isContentKey(key)
@@ -105,15 +171,25 @@ function attributes(source, options, path, budget) {
105
171
  : [
106
172
  [
107
173
  key,
108
- redactValue(scrubToolCredentials(key, hashInlineFiles(key, value)), `${path}.${key}`, options, budget),
174
+ redactValue(scrubToolCredentials(key, hashInlineFiles(key, value)), `${path}.${key}`, options, budget, 0, `${listPrefix}${key}`),
109
175
  ],
110
176
  ]));
111
177
  }
178
+ /** The record's attributes with the keys the redaction cut listed under `hue.truncated`, merged
179
+ * with any the application listed itself; unchanged when nothing was cut. */
180
+ function withTruncated(source, budget) {
181
+ if (!budget.truncated.length)
182
+ return source;
183
+ return { ...source, [TRUNCATED_KEY]: withTruncatedKeys(source[TRUNCATED_KEY], budget.truncated) };
184
+ }
112
185
  function redactResource(resource, options, cache, budget) {
113
186
  let result = cache.get(resource);
114
187
  if (!result) {
115
- result = resourceFromAttributes(attributes(resource.attributes, options, "resource.attributes", budget), { schemaUrl: resource.schemaUrl });
116
- cache.set(resource, result);
188
+ const cutsBefore = budget.truncated.length;
189
+ result = resourceFromAttributes(attributes(resource.attributes, options, "resource.attributes", budget, "resource."), { schemaUrl: resource.schemaUrl });
190
+ // A resource whose value was cut is redacted for every record, so each lists the cut.
191
+ if (budget.truncated.length === cutsBefore)
192
+ cache.set(resource, result);
117
193
  }
118
194
  return result;
119
195
  }
@@ -146,7 +222,7 @@ function hostedMcpCall(attributes) {
146
222
  return { ...(valid ? { serverName: serverLabel } : {}), failed: error != null };
147
223
  }
148
224
  export function redactSpan(span, options, cache) {
149
- const budget = { bytes: 0, nodes: 0 };
225
+ const budget = { bytes: 0, nodes: 0, truncated: [] };
150
226
  // Derived before metadata-only stripping so the identity survives without the result itself.
151
227
  const hosted = hostedMcpCall(span.attributes);
152
228
  const source = {
@@ -154,6 +230,30 @@ export function redactSpan(span, options, cache) {
154
230
  ...(hosted.failed ? { "error.type": "mcp_error" } : {}),
155
231
  ...span.attributes,
156
232
  };
233
+ const status = {
234
+ code: hosted.failed && span.status.code === SpanStatusCode.UNSET
235
+ ? SpanStatusCode.ERROR
236
+ : span.status.code,
237
+ ...(options.captureContent &&
238
+ span.status.message !== undefined &&
239
+ !(hosted.failed && span.status.code === SpanStatusCode.UNSET)
240
+ ? { message: String(redactValue(span.status.message, "status.message", options, budget)) }
241
+ : {}),
242
+ };
243
+ const events = span.events
244
+ .filter((event) => options.captureContent ||
245
+ !/^gen_ai\.(?:system|user|assistant|tool|choice)/.test(event.name))
246
+ .map((event) => ({
247
+ ...event,
248
+ attributes: attributes(event.attributes ?? {}, options, `events.${event.name}`, budget, `event:${event.name}:`),
249
+ }));
250
+ const links = span.links.map((link) => ({
251
+ ...link,
252
+ attributes: attributes(link.attributes ?? {}, options, "links.attributes", budget, "link:"),
253
+ }));
254
+ const resource = redactResource(span.resource, options, cache, budget);
255
+ // Last, so every cut of the record, its events', links' and resource's included, is listed.
256
+ const redactedAttributes = withTruncated(attributes(source, options, "attributes", budget), budget);
157
257
  return {
158
258
  name: span.name,
159
259
  kind: span.kind,
@@ -163,29 +263,11 @@ export function redactSpan(span, options, cache) {
163
263
  endTime: span.endTime,
164
264
  duration: span.duration,
165
265
  ended: span.ended,
166
- status: {
167
- code: hosted.failed && span.status.code === SpanStatusCode.UNSET
168
- ? SpanStatusCode.ERROR
169
- : span.status.code,
170
- ...(options.captureContent &&
171
- span.status.message !== undefined &&
172
- !(hosted.failed && span.status.code === SpanStatusCode.UNSET)
173
- ? { message: String(redactValue(span.status.message, "status.message", options, budget)) }
174
- : {}),
175
- },
176
- attributes: attributes(source, options, "attributes", budget),
177
- events: span.events
178
- .filter((event) => options.captureContent ||
179
- !/^gen_ai\.(?:system|user|assistant|tool|choice)/.test(event.name))
180
- .map((event) => ({
181
- ...event,
182
- attributes: attributes(event.attributes ?? {}, options, `events.${event.name}`, budget),
183
- })),
184
- links: span.links.map((link) => ({
185
- ...link,
186
- attributes: attributes(link.attributes ?? {}, options, "links.attributes", budget),
187
- })),
188
- resource: redactResource(span.resource, options, cache, budget),
266
+ status,
267
+ attributes: redactedAttributes,
268
+ events,
269
+ links,
270
+ resource,
189
271
  instrumentationScope: span.instrumentationScope,
190
272
  droppedAttributesCount: span.droppedAttributesCount,
191
273
  droppedEventsCount: span.droppedEventsCount,
@@ -193,10 +275,22 @@ export function redactSpan(span, options, cache) {
193
275
  };
194
276
  }
195
277
  export function redactLog(log, options, cache) {
196
- const budget = { bytes: 0, nodes: 0 };
197
- const body = options.captureContent ? redactValue(log.body, "body", options, budget) : undefined;
198
- if (body !== undefined && Buffer.byteLength(JSON.stringify(body)) > MAX_CONTENT_BYTES)
199
- throw new Error("Telemetry log body exceeds 256 KiB");
278
+ const budget = { bytes: 0, nodes: 0, truncated: [] };
279
+ let body = options.captureContent ? redactValue(log.body, "body", options, budget) : undefined;
280
+ if (body !== undefined && typeof body !== "string") {
281
+ // A structured body over the cap is replaced by the receiver's marker, as the receiver would
282
+ // replace it; the marker names the body's size as recorded, before any text part was cut.
283
+ if (Buffer.byteLength(JSON.stringify(body)) > MAX_CONTENT_BYTES) {
284
+ body = truncatedMarker(structuredBytes(log.body));
285
+ if (!budget.truncated.includes("body"))
286
+ budget.truncated.push("body");
287
+ }
288
+ }
289
+ const redactedAttributes = attributes(log.attributes, options, "attributes", budget);
290
+ const resource = redactResource(log.resource, options, cache, budget);
291
+ const scopeAttributes = log.instrumentationScope.attributes
292
+ ? attributes(log.instrumentationScope.attributes, options, "scope.attributes", budget, "scope.")
293
+ : undefined;
200
294
  return {
201
295
  hrTime: log.hrTime,
202
296
  hrTimeObserved: log.hrTimeObserved,
@@ -205,15 +299,11 @@ export function redactLog(log, options, cache) {
205
299
  severityNumber: log.severityNumber,
206
300
  eventName: log.eventName,
207
301
  body: body,
208
- attributes: attributes(log.attributes, options, "attributes", budget),
209
- resource: redactResource(log.resource, options, cache, budget),
302
+ attributes: withTruncated(redactedAttributes, budget),
303
+ resource,
210
304
  instrumentationScope: {
211
305
  ...log.instrumentationScope,
212
- ...(log.instrumentationScope.attributes
213
- ? {
214
- attributes: attributes(log.instrumentationScope.attributes, options, "scope.attributes", budget),
215
- }
216
- : {}),
306
+ ...(scopeAttributes ? { attributes: scopeAttributes } : {}),
217
307
  },
218
308
  droppedAttributesCount: log.droppedAttributesCount,
219
309
  };
package/dist/safety.d.ts CHANGED
@@ -9,5 +9,23 @@ export interface EncodeLimits {
9
9
  }
10
10
  /** Validate a bounded data tree without invoking toJSON or property getters. */
11
11
  export declare function encodeContent(value: unknown, limits?: EncodeLimits): string;
12
+ /**
13
+ * The value as JSON text within `limits.bytes`, and whether it was cut to fit. A value over the
14
+ * cap is cut to a UTF-8 prefix of its JSON text at the cap, as Hue's receiver cuts a value over
15
+ * its own cap and as it recognizes a cut (a text at one of its limits), so a span still carries
16
+ * the call, the recorded part of its result and the rest of its evidence; the caller lists the
17
+ * key under `hue.truncated`. The text is written as the value is walked and the walk stops once
18
+ * it is past the budget, so no more than the budget and one value is ever held, and a value cut
19
+ * among its members is never a shorter document that reads whole: the prefix ends at the cap,
20
+ * inside whatever member the cap fell in.
21
+ */
22
+ export declare function encodeBoundedContent(value: unknown, limits?: EncodeLimits, cut?: boolean): {
23
+ text: string;
24
+ truncated: boolean;
25
+ };
26
+ /** `value` cut to at most `max` UTF-8 bytes, ending on a character boundary. The copy is bounded
27
+ * before the cut: `max` code units hold at least `max` bytes, so a gigabyte of text is never
28
+ * encoded whole to be cut to a quarter megabyte. */
29
+ export declare function truncateUtf8(value: string, max: number): string;
12
30
  /** Bounded conservative accounting for the record data retained by our queue, not total process RSS. */
13
31
  export declare function estimateRecordBytes(value: unknown, limit: number): number;
package/dist/safety.js CHANGED
@@ -12,6 +12,16 @@ class SafeSpan {
12
12
  this.source = source;
13
13
  this.failed = failed;
14
14
  }
15
+ /** The SDK span's attributes as they stand (the API alone shows none); undefined for a span
16
+ * that carries none, such as a non-recording span. */
17
+ get attributes() {
18
+ try {
19
+ return this.source.attributes;
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
15
25
  write(work) {
16
26
  try {
17
27
  const result = work();
@@ -93,28 +103,56 @@ export function safeSpan(source, failed) {
93
103
  const contentLimits = { bytes: MAX_CONTENT_BYTES, nodes: 16384, depth: 32 };
94
104
  /** Validate a bounded data tree without invoking toJSON or property getters. */
95
105
  export function encodeContent(value, limits = contentLimits) {
106
+ const { text, truncated } = encodeBoundedContent(value, limits, false);
107
+ if (truncated)
108
+ throw new RangeError("Content limit exceeded");
109
+ return text;
110
+ }
111
+ /**
112
+ * The value as JSON text within `limits.bytes`, and whether it was cut to fit. A value over the
113
+ * cap is cut to a UTF-8 prefix of its JSON text at the cap, as Hue's receiver cuts a value over
114
+ * its own cap and as it recognizes a cut (a text at one of its limits), so a span still carries
115
+ * the call, the recorded part of its result and the rest of its evidence; the caller lists the
116
+ * key under `hue.truncated`. The text is written as the value is walked and the walk stops once
117
+ * it is past the budget, so no more than the budget and one value is ever held, and a value cut
118
+ * among its members is never a shorter document that reads whole: the prefix ends at the cap,
119
+ * inside whatever member the cap fell in.
120
+ */
121
+ export function encodeBoundedContent(value, limits = contentLimits, cut = true) {
96
122
  let nodes = 0;
97
123
  let bytes = 0;
124
+ let truncated = false;
125
+ const parts = [];
98
126
  const ancestors = new Set();
99
- const charge = (amount) => {
100
- bytes += amount;
101
- if (bytes > limits.bytes)
102
- throw new RangeError("Content limit exceeded");
127
+ const write = (text) => {
128
+ parts.push(text);
129
+ bytes += Buffer.byteLength(text);
130
+ if (bytes > limits.bytes) {
131
+ if (!cut)
132
+ throw new RangeError("Content limit exceeded");
133
+ truncated = true;
134
+ }
103
135
  };
104
136
  const visit = (item, depth) => {
105
137
  if (++nodes > limits.nodes || depth > limits.depth)
106
138
  throw new RangeError("Content complexity limit exceeded");
107
139
  if (typeof item === "string") {
108
- if (item.length > limits.bytes)
109
- throw new RangeError("Content limit exceeded");
110
- charge(Buffer.byteLength(JSON.stringify(item)));
111
- return item;
140
+ let text = item;
141
+ if (text.length > limits.bytes || Buffer.byteLength(text) > limits.bytes) {
142
+ if (!cut)
143
+ throw new RangeError("Content limit exceeded");
144
+ // The copy itself is bounded in bytes: a longer string is cut before it is escaped.
145
+ truncated = true;
146
+ text = truncateUtf8(text.slice(0, limits.bytes), limits.bytes);
147
+ }
148
+ write(JSON.stringify(text));
149
+ return;
112
150
  }
113
151
  if (item === null || typeof item === "boolean" || typeof item === "number") {
114
152
  if (typeof item === "number" && !Number.isFinite(item))
115
153
  throw new TypeError("Invalid number");
116
- charge(JSON.stringify(item).length);
117
- return item;
154
+ write(JSON.stringify(item));
155
+ return;
118
156
  }
119
157
  if (!item || typeof item !== "object" || ancestors.has(item))
120
158
  throw new TypeError("Invalid JSON");
@@ -126,32 +164,47 @@ export function encodeContent(value, limits = contentLimits) {
126
164
  if (!array && ![Object.prototype, null].includes(Object.getPrototypeOf(item)))
127
165
  throw new TypeError("Expected JSON data");
128
166
  ancestors.add(item);
129
- charge(2);
130
- const result = array ? [] : Object.create(null);
167
+ write(array ? "[" : "{");
131
168
  // Own descriptors avoid executing application accessors during capture.
132
169
  const keys = array
133
170
  ? Array.from({ length: Math.min(item.length, limits.nodes + 1) }, (_, i) => String(i))
134
171
  : Object.keys(item);
135
172
  if (keys.length > limits.nodes)
136
173
  throw new RangeError("Content complexity limit exceeded");
174
+ let first = true;
137
175
  for (const key of keys) {
176
+ // Past the budget, the rest is cut anyway: nothing more is copied.
177
+ if (truncated)
178
+ break;
138
179
  const descriptor = Object.getOwnPropertyDescriptor(item, key);
139
180
  if (!descriptor || !("value" in descriptor))
140
181
  throw new TypeError("Expected JSON data property");
141
- charge(1 + (array ? 0 : Buffer.byteLength(JSON.stringify(key)) + 1));
142
- const child = visit(descriptor.value, depth + 1);
143
- if (array)
144
- result.push(child);
145
- else
146
- result[key] = child;
182
+ write(`${first ? "" : ","}${array ? "" : `${JSON.stringify(key)}:`}`);
183
+ first = false;
184
+ visit(descriptor.value, depth + 1);
147
185
  }
186
+ write(array ? "]" : "}");
148
187
  ancestors.delete(item);
149
- return result;
150
188
  };
151
- const encoded = JSON.stringify(visit(value, 0));
152
- if (Buffer.byteLength(encoded) > limits.bytes)
153
- throw new RangeError("Content limit exceeded");
154
- return encoded;
189
+ visit(value, 0);
190
+ const encoded = parts.join("");
191
+ if (!truncated)
192
+ return { text: encoded, truncated: false };
193
+ // A cut value is past the cap as written (a cut text alone, with its quotes, is over it), so
194
+ // the prefix ends at the cap, where the receiver recognizes Hue's cut.
195
+ return { text: truncateUtf8(encoded, limits.bytes), truncated: true };
196
+ }
197
+ /** `value` cut to at most `max` UTF-8 bytes, ending on a character boundary. The copy is bounded
198
+ * before the cut: `max` code units hold at least `max` bytes, so a gigabyte of text is never
199
+ * encoded whole to be cut to a quarter megabyte. */
200
+ export function truncateUtf8(value, max) {
201
+ if (Buffer.byteLength(value, "utf8") <= max)
202
+ return value;
203
+ const bytes = Buffer.from(value.slice(0, max), "utf8");
204
+ let end = Math.min(max, bytes.byteLength);
205
+ while (end > 0 && end < bytes.byteLength && (bytes[end] & 0xc0) === 0x80)
206
+ end--;
207
+ return bytes.toString("utf8", 0, end);
155
208
  }
156
209
  /** Bounded conservative accounting for the record data retained by our queue, not total process RSS. */
157
210
  export function estimateRecordBytes(value, limit) {
@@ -42,6 +42,10 @@ export declare class HueTransport {
42
42
  private logs;
43
43
  private pendingBytes;
44
44
  private dropped;
45
+ /** Records of each trace the SDK never sent (dropped from the queue, refused as invalid or
46
+ * over the request limit), reported on the trace's root span as `hue.sdk.dropped_records` when
47
+ * the root is exported, so Hue reads the trace as incomplete by that many records. */
48
+ private droppedByTrace;
45
49
  private instrumentationFailures;
46
50
  private diagnosticPending;
47
51
  private lastDiagnosticAt;
@@ -53,6 +57,7 @@ export declare class HueTransport {
53
57
  private live;
54
58
  private liveTimer?;
55
59
  private liveSpans;
60
+ private readonly announcesLiveSpans;
56
61
  private placeholdersRejected;
57
62
  private placeholders;
58
63
  private placeholderSources;