@hue-run/sdk 0.13.2 → 0.15.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.
package/dist/snapshot.js CHANGED
@@ -2,32 +2,165 @@ import { createTraceState } from "@opentelemetry/api";
2
2
  import { types as utilTypes } from "node:util";
3
3
  import { resourceFromAttributes } from "@opentelemetry/resources";
4
4
  import { hashInlineFiles, INLINE_FILE_LIMIT, INLINE_FILE_TEXT_PER_RECORD, isMessageKey, } from "./inline-files.js";
5
+ import { scrubsToolCredentials, scrubToolCredentials } from "./tool-definitions.js";
6
+ import { MAX_RECORD_NODES } from "./config.js";
7
+ import { truncatedMarker } from "./privacy.js";
8
+ import { MAX_BLOB_BYTES } from "./blobs.js";
5
9
  // Intrinsic accessors are captured once and invoked with an explicit receiver so a
6
10
  // hostile object cannot override them; the unbound reference is the point.
7
11
  // eslint-disable-next-line @typescript-eslint/unbound-method
8
12
  const typedArrayByteLength = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), "byteLength").get;
9
13
  // eslint-disable-next-line @typescript-eslint/unbound-method
10
14
  const typedArraySet = Uint8Array.prototype.set;
15
+ /** `value` cut to at most `units` UTF-16 code units, never between the halves of a pair. */
16
+ function cutToUnits(value, units) {
17
+ if (value.length <= units)
18
+ return value;
19
+ const end = /[\uD800-\uDBFF]/.test(value[units - 1] ?? "") ? units - 1 : units;
20
+ // A slice can keep the whole original string alive; the copy holds only what is kept.
21
+ return Buffer.from(value.slice(0, end), "utf16le").toString("utf16le");
22
+ }
23
+ /** The record's byte or value budget ran out: a span's events and links past it are left out and
24
+ * counted as dropped, where anything else loses the record. */
25
+ class BudgetExceeded extends RangeError {
26
+ }
11
27
  /** Copies only exported data, with the same finite budget used for admission. */
12
28
  class Snapshot {
13
29
  limit;
14
30
  captureContent;
31
+ valueUnits;
32
+ hold;
15
33
  bytes = 512;
16
34
  unresolvedResource = false;
35
+ /** The value strings this snapshot cut to `valueUnits`; export recognizes them, so the
36
+ * redactor's answer for one loses the end the redactor saw without its continuation. */
37
+ cut = new Set();
17
38
  nodes = 0;
18
39
  ancestors = new Set();
19
40
  copied = new Map();
20
41
  hashed = new Map();
21
42
  inspected = 0;
22
- /** In metadata-only mode the recorded messages, which export strips, are not copied at all. */
23
- constructor(limit, captureContent = true) {
43
+ /** Bytes of values held whole for upload, charged to the held-value budget. */
44
+ held = 0;
45
+ /** Set while the span's own attributes are copied: only they may be held for upload. */
46
+ own = false;
47
+ /** The span's own messages whose large inline files shrank to their digest when queued, by
48
+ * attribute: the text queued (cut or not), and how many files it lost. */
49
+ digested = new Map();
50
+ /** How many files each message text {@link inlineFiles} hashed replaced by their digest. */
51
+ hashedFiles = new Map();
52
+ /**
53
+ * In metadata-only mode the recorded messages, which export strips, are not copied at all. A
54
+ * value string (an attribute's, an event's or link's attribute's, a log body or a status
55
+ * message) longer than `valueUnits` is cut to that length: export cuts it to the value cap
56
+ * anyway, and charging it whole could drop the record before that cut ever ran.
57
+ */
58
+ constructor(limit, captureContent = true, valueUnits = Infinity, hold) {
24
59
  this.limit = limit;
25
60
  this.captureContent = captureContent;
61
+ this.valueUnits = valueUnits;
62
+ this.hold = hold;
63
+ }
64
+ /** Whether the held-value budget takes `bytes` more, charging them when it does. */
65
+ holds(bytes) {
66
+ const hold = this.hold;
67
+ if (this.held + bytes > hold.budget && !(hold.first && this.held === 0))
68
+ return false;
69
+ this.held += bytes;
70
+ return true;
71
+ }
72
+ /**
73
+ * One of the span's own attribute values held whole for export to upload, when it may be: text
74
+ * longer than the queued value length or bytes over the cap, within the held-value budget, and
75
+ * a recorded message that may inline a large file kept as it is (export uploads the file).
76
+ * Undefined for a value copied as before; text over the length the budget cannot hold is then
77
+ * cut and its large inline files shrink to their digest, as before uploads existed.
78
+ */
79
+ heldValue(key, value) {
80
+ if (!this.hold || !this.own)
81
+ return undefined;
82
+ if (typeof value === "string") {
83
+ if (value.length > this.valueUnits) {
84
+ // The queue must take the cut the value falls back to, as it would have taken it before.
85
+ const fallback = this.valueUnits * 2 + 16;
86
+ if (this.bytes + fallback > this.limit || !this.holds(value.length * 2))
87
+ return undefined;
88
+ this.charge(fallback);
89
+ return { value };
90
+ }
91
+ // Within the queued length: a recorded message holding a large inline file is held whole
92
+ // for export to upload the file, within the held-value budget, and the queue is charged
93
+ // its copy with the files as their digest, as before. Past the budget, the files shrink to
94
+ // their digest when queued, as before uploads existed.
95
+ if (!isMessageKey(key))
96
+ return undefined;
97
+ const digested = this.inlineFiles(key, value);
98
+ if (digested === value || !this.holds(value.length * 2))
99
+ return undefined;
100
+ this.copy(digested, 1, "value", true);
101
+ return { value };
102
+ }
103
+ if (!utilTypes.isUint8Array(value))
104
+ return undefined;
105
+ const length = typedArrayByteLength.call(value);
106
+ // Bytes over Hue's upload limit could only be exported cut: not copied whole for that.
107
+ if (length <= this.hold.valueBytes || length > MAX_BLOB_BYTES || !this.holds(length))
108
+ return undefined;
109
+ this.charge(16);
110
+ // Copied, so a later change by the application cannot alter what was recorded; charged to
111
+ // the held-value budget instead of the queue's.
112
+ const copy = new Uint8Array(length);
113
+ typedArraySet.call(copy, value);
114
+ return { value: copy };
26
115
  }
27
116
  charge(bytes) {
28
117
  this.bytes += bytes;
29
118
  if (this.bytes > this.limit)
30
- throw new RangeError("Telemetry byte budget exceeded");
119
+ throw new BudgetExceeded("Telemetry byte budget exceeded");
120
+ }
121
+ mark() {
122
+ return { bytes: this.bytes, nodes: this.nodes, copied: this.copied.size };
123
+ }
124
+ /** Back to `mark`, forgetting the values copied since: a later copy must not reuse a partial
125
+ * copy of an item that was left out. */
126
+ restore(mark) {
127
+ this.bytes = mark.bytes;
128
+ this.nodes = mark.nodes;
129
+ this.ancestors.clear();
130
+ let index = 0;
131
+ for (const key of [...this.copied.keys()])
132
+ if (index++ >= mark.copied)
133
+ this.copied.delete(key);
134
+ }
135
+ /**
136
+ * As many of a span's newest `items` (its events or links) as the record's budget still holds,
137
+ * each copied by `copyItem`, and how many older ones it could not hold, as OpenTelemetry keeps a
138
+ * span's newest events and links past its own count limits. A span whose events or links
139
+ * outgrow the budget keeps its name, timing, attributes and the rest of them, counting the ones
140
+ * left out as dropped, instead of being lost whole.
141
+ */
142
+ newest(items, copyItem) {
143
+ if (!Array.isArray(items) || utilTypes.isProxy(items))
144
+ throw new TypeError("Telemetry must contain data objects");
145
+ const count = items.length;
146
+ const kept = [];
147
+ for (let index = count - 1; index >= 0; index--) {
148
+ const descriptor = Object.getOwnPropertyDescriptor(items, index);
149
+ if (descriptor && !("value" in descriptor))
150
+ throw new TypeError("Telemetry accessors are unsupported");
151
+ const mark = this.mark();
152
+ try {
153
+ kept.push(copyItem(descriptor?.value));
154
+ }
155
+ catch (error) {
156
+ if (!(error instanceof BudgetExceeded))
157
+ throw error;
158
+ this.restore(mark);
159
+ break;
160
+ }
161
+ }
162
+ kept.reverse();
163
+ return { kept, dropped: count - kept.length };
31
164
  }
32
165
  /**
33
166
  * An attribute map. A large inline file in a message attribute shrinks to its digest before it
@@ -36,6 +169,16 @@ class Snapshot {
36
169
  attributes(value) {
37
170
  return this.copy(value, 1, "attributes");
38
171
  }
172
+ /** The span's own attribute map, whose values over the cap may be held whole for upload. */
173
+ ownAttributes(value) {
174
+ this.own = true;
175
+ try {
176
+ return this.attributes(value);
177
+ }
178
+ finally {
179
+ this.own = false;
180
+ }
181
+ }
39
182
  /** A message attribute with its large inline files hashed. The same text is hashed once per
40
183
  * record, and a record inspects at most {@link INLINE_FILE_TEXT_PER_RECORD} of it in all;
41
184
  * beyond that, messages are charged as recorded. */
@@ -48,21 +191,69 @@ class Snapshot {
48
191
  if (this.inspected + value.length > INLINE_FILE_TEXT_PER_RECORD)
49
192
  return value;
50
193
  this.inspected += value.length;
51
- const result = hashInlineFiles(key, value);
194
+ const count = { files: 0 };
195
+ const result = hashInlineFiles(key, value, count);
52
196
  this.hashed.set(value, result);
197
+ if (count.files)
198
+ this.hashedFiles.set(value, count.files);
53
199
  return result;
54
200
  }
55
- /** Span events, whose attributes are attribute maps. */
56
- events(value) {
57
- return this.copy(value, 1, "events");
201
+ /**
202
+ * An attribute whose value will be cut and whose credentials export removes (tool definitions
203
+ * and recorded requests) has them removed first, from the whole value: a cut prefix no longer
204
+ * parses, so export could not find them in it. A value longer than what the record's budget has
205
+ * left is not parsed on the application's thread at all: the receiver's marker replaces it, as
206
+ * in the Python SDK, rather than a cut that could hold credentials.
207
+ */
208
+ scrubbedBeforeCut(key, value) {
209
+ if (this.valueUnits === Infinity || !scrubsToolCredentials(key))
210
+ return value;
211
+ const scrubbed = (units, scrub, bytes) => units * 2 > this.limit - this.bytes ? truncatedMarker(bytes()) : scrub();
212
+ if (typeof value === "string")
213
+ return value.length > this.valueUnits
214
+ ? scrubbed(value.length, () => scrubToolCredentials(key, value), () => Buffer.byteLength(value))
215
+ : value;
216
+ // A list of definitions (AI SDK 6 `ai.prompt.tools`), read through its own data properties;
217
+ // anything else is left for the copy to accept or refuse.
218
+ if (!Array.isArray(value) || utilTypes.isProxy(value) || value.length > MAX_RECORD_NODES)
219
+ return value;
220
+ const items = [];
221
+ let long = false;
222
+ let units = 0;
223
+ for (let index = 0; index < value.length; index++) {
224
+ const descriptor = Object.getOwnPropertyDescriptor(value, index);
225
+ if (descriptor && !("value" in descriptor))
226
+ return value;
227
+ const item = descriptor?.value;
228
+ if (typeof item === "string") {
229
+ units += item.length;
230
+ if (item.length > this.valueUnits)
231
+ long = true;
232
+ }
233
+ items.push(item);
234
+ }
235
+ return long
236
+ ? scrubbed(units, () => scrubToolCredentials(key, items), () => items.reduce((total, item) => total + (typeof item === "string" ? Buffer.byteLength(item) : 0), 0))
237
+ : value;
238
+ }
239
+ /** A value whose strings are cut to the queued value length: a log body or a status. */
240
+ value(value, depth = 1) {
241
+ return this.copy(value, depth, "value", true);
58
242
  }
59
- copy(value, depth = 0, shape = "value") {
60
- if (++this.nodes > 16384 || depth > 32)
61
- throw new RangeError("Telemetry complexity limit exceeded");
243
+ copy(value, depth = 0, shape = "value", content = false) {
244
+ if (++this.nodes > MAX_RECORD_NODES)
245
+ throw new BudgetExceeded("Telemetry complexity limit exceeded");
246
+ if (depth > 32)
247
+ throw new RangeError("Telemetry nesting limit exceeded");
62
248
  this.charge(16);
63
249
  if (typeof value === "string") {
64
- this.charge(value.length * 2);
65
- return value;
250
+ let text = value;
251
+ if (content && text.length > this.valueUnits) {
252
+ text = cutToUnits(text, this.valueUnits);
253
+ this.cut.add(text);
254
+ }
255
+ this.charge(text.length * 2);
256
+ return text;
66
257
  }
67
258
  if (value === null ||
68
259
  value === undefined ||
@@ -96,13 +287,13 @@ class Snapshot {
96
287
  this.copied.set(value, copy);
97
288
  this.ancestors.add(value);
98
289
  if (array) {
99
- if (value.length > 16384)
100
- throw new RangeError("Telemetry complexity limit exceeded");
290
+ if (value.length > MAX_RECORD_NODES)
291
+ throw new BudgetExceeded("Telemetry complexity limit exceeded");
101
292
  for (let index = 0; index < value.length; index++) {
102
293
  const descriptor = Object.getOwnPropertyDescriptor(value, index);
103
294
  if (descriptor && !("value" in descriptor))
104
295
  throw new TypeError("Telemetry accessors are unsupported");
105
- copy.push(this.copy(descriptor?.value, depth + 1, shape === "events" ? "event" : "value"));
296
+ copy.push(this.copy(descriptor?.value, depth + 1, "value", content || shape === "attributes"));
106
297
  }
107
298
  }
108
299
  else {
@@ -115,7 +306,23 @@ class Snapshot {
115
306
  if (shape === "attributes" && !this.captureContent && isMessageKey(key))
116
307
  continue;
117
308
  this.charge(key.length * 2 + 16);
118
- copy[key] = this.copy(shape === "attributes" ? this.inlineFiles(key, descriptor.value) : descriptor.value, depth + 1, shape === "event" && key === "attributes" ? "attributes" : "value");
309
+ const held = shape === "attributes" ? this.heldValue(key, descriptor.value) : undefined;
310
+ if (held) {
311
+ copy[key] = held.value;
312
+ continue;
313
+ }
314
+ const source = descriptor.value;
315
+ const copied = this.copy(shape === "attributes"
316
+ ? this.scrubbedBeforeCut(key, this.inlineFiles(key, source))
317
+ : source, depth + 1, shape === "event" && key === "attributes" ? "attributes" : "value", content || shape === "attributes");
318
+ copy[key] = copied;
319
+ // A span's own message whose large inline files shrank to their digest is named as it
320
+ // was queued, cut or not, so export reports each file as not uploaded.
321
+ const files = this.own && shape === "attributes" && typeof source === "string"
322
+ ? this.hashedFiles.get(source)
323
+ : undefined;
324
+ if (files && typeof copied === "string")
325
+ this.digested.set(key, { text: copied, files });
119
326
  }
120
327
  }
121
328
  this.ancestors.delete(value);
@@ -153,7 +360,7 @@ class Snapshot {
153
360
  if (value == null || Object.hasOwn(attributes, key))
154
361
  continue;
155
362
  this.charge(key.length * 2 + 16);
156
- attributes[key] = this.copy(value);
363
+ attributes[key] = this.copy(this.scrubbedBeforeCut(key, value), 0, "value", true);
157
364
  }
158
365
  return resourceFromAttributes(attributes, { schemaUrl: this.copy(source.schemaUrl) });
159
366
  }
@@ -161,41 +368,58 @@ class Snapshot {
161
368
  function contextReader(context) {
162
369
  return () => context;
163
370
  }
164
- export function snapshotSpan(source, limit, captureContent = true) {
165
- const snapshot = new Snapshot(limit, captureContent);
371
+ export function snapshotSpan(source, limit, captureContent = true, valueUnits = Infinity, hold) {
372
+ const snapshot = new Snapshot(limit, captureContent, valueUnits, hold);
166
373
  const context = snapshot.context(source.spanContext());
167
- if (source.links.length > 16384)
168
- throw new RangeError("Link complexity limit exceeded");
169
- const links = source.links.map((link) => ({
170
- context: snapshot.context(link.context),
171
- attributes: snapshot.attributes(link.attributes),
172
- droppedAttributesCount: snapshot.copy(link.droppedAttributesCount),
173
- }));
374
+ const fields = snapshot.copy({
375
+ name: source.name,
376
+ kind: source.kind,
377
+ startTime: source.startTime,
378
+ endTime: source.endTime,
379
+ duration: source.duration,
380
+ ended: source.ended,
381
+ instrumentationScope: source.instrumentationScope,
382
+ droppedAttributesCount: source.droppedAttributesCount,
383
+ droppedEventsCount: source.droppedEventsCount,
384
+ droppedLinksCount: source.droppedLinksCount,
385
+ });
386
+ const status = snapshot.value(source.status);
387
+ const attributes = snapshot.ownAttributes(source.attributes);
388
+ const parentSpanContext = snapshot.context(source.parentSpanContext);
389
+ const resource = snapshot.resource(source.resource);
390
+ // Last, with what the budget has left: past it, events and links are counted as dropped.
391
+ const events = snapshot.newest(source.events, (event) => snapshot.copy(event, 2, "event"));
392
+ const links = snapshot.newest(source.links, (item) => {
393
+ const link = item;
394
+ return {
395
+ context: snapshot.context(link.context),
396
+ attributes: snapshot.attributes(link.attributes),
397
+ droppedAttributesCount: snapshot.copy(link.droppedAttributesCount),
398
+ };
399
+ });
174
400
  const record = {
175
- ...snapshot.copy({
176
- name: source.name,
177
- kind: source.kind,
178
- startTime: source.startTime,
179
- endTime: source.endTime,
180
- duration: source.duration,
181
- ended: source.ended,
182
- status: source.status,
183
- instrumentationScope: source.instrumentationScope,
184
- droppedAttributesCount: source.droppedAttributesCount,
185
- droppedEventsCount: source.droppedEventsCount,
186
- droppedLinksCount: source.droppedLinksCount,
187
- }),
188
- attributes: snapshot.attributes(source.attributes),
189
- events: snapshot.events(source.events),
401
+ ...fields,
402
+ droppedEventsCount: (fields.droppedEventsCount ?? 0) + events.dropped,
403
+ droppedLinksCount: (fields.droppedLinksCount ?? 0) + links.dropped,
404
+ status,
405
+ attributes,
406
+ events: events.kept,
190
407
  spanContext: contextReader(context),
191
- parentSpanContext: snapshot.context(source.parentSpanContext),
192
- links,
193
- resource: snapshot.resource(source.resource),
408
+ parentSpanContext,
409
+ links: links.kept,
410
+ resource,
411
+ };
412
+ return {
413
+ record,
414
+ bytes: snapshot.bytes,
415
+ unresolvedResource: snapshot.unresolvedResource,
416
+ cut: snapshot.cut,
417
+ held: snapshot.held,
418
+ digested: snapshot.digested,
194
419
  };
195
- return { record, bytes: snapshot.bytes, unresolvedResource: snapshot.unresolvedResource };
196
420
  }
197
- export function snapshotLog(source, limit, captureContent = true) {
198
- const snapshot = new Snapshot(limit, captureContent);
421
+ export function snapshotLog(source, limit, captureContent = true, valueUnits = Infinity) {
422
+ const snapshot = new Snapshot(limit, captureContent, valueUnits);
199
423
  // Only our batching processor sees this copy. Its writer methods deliberately
200
424
  // cannot mutate the admitted snapshot or invalidate its charged byte count.
201
425
  const record = {
@@ -205,10 +429,10 @@ export function snapshotLog(source, limit, captureContent = true) {
205
429
  severityText: source.severityText,
206
430
  severityNumber: source.severityNumber,
207
431
  eventName: source.eventName,
208
- body: source.body,
209
432
  instrumentationScope: source.instrumentationScope,
210
433
  droppedAttributesCount: source.droppedAttributesCount,
211
434
  }),
435
+ body: snapshot.value(source.body),
212
436
  attributes: snapshot.attributes(source.attributes),
213
437
  spanContext: snapshot.context(source.spanContext),
214
438
  resource: snapshot.resource(source.resource),
@@ -231,5 +455,12 @@ export function snapshotLog(source, limit, captureContent = true) {
231
455
  return this;
232
456
  },
233
457
  };
234
- return { record, bytes: snapshot.bytes, unresolvedResource: snapshot.unresolvedResource };
458
+ return {
459
+ record,
460
+ bytes: snapshot.bytes,
461
+ unresolvedResource: snapshot.unresolvedResource,
462
+ cut: snapshot.cut,
463
+ held: 0,
464
+ digested: new Map(),
465
+ };
235
466
  }
@@ -21,6 +21,10 @@ export declare function scrubCredentialText(text: string, cut?: boolean): string
21
21
  /** `scrubCredentialText` without its bound on the text's length, for tests that time long texts;
22
22
  * callers use `scrubCredentialText`. */
23
23
  export declare function scrubCredentialTextUnbounded(text: string, cut?: boolean): string;
24
+ /** Whether {@link scrubToolCredentials} inspects an attribute's value: its credentials must be
25
+ * removed from the whole recorded value, before anything cuts it to a prefix that no longer
26
+ * parses. */
27
+ export declare function scrubsToolCredentials(key: string): boolean;
24
28
  /**
25
29
  * Removes hosted-tool credentials from an exported attribute value, before the caller's `redact`.
26
30
  * Tool definitions come from OpenTelemetry GenAI (`gen_ai.tool.definitions`), AI SDK 6
@@ -949,6 +949,17 @@ function scrubRequestText(text) {
949
949
  request[field] = scrubNode(request[field], state, 0, false);
950
950
  return state.changed ? JSON.stringify(request) : text;
951
951
  }
952
+ /** Whether {@link scrubToolCredentials} inspects an attribute's value: its credentials must be
953
+ * removed from the whole recorded value, before anything cuts it to a prefix that no longer
954
+ * parses. */
955
+ export function scrubsToolCredentials(key) {
956
+ return (key === "gen_ai.tool.definitions" ||
957
+ key === "ai.prompt.tools" ||
958
+ openInferenceTool.test(key) ||
959
+ key === "input.value" ||
960
+ key === "output.value" ||
961
+ key === "llm.invocation_parameters");
962
+ }
952
963
  /**
953
964
  * Removes hosted-tool credentials from an exported attribute value, before the caller's `redact`.
954
965
  * Tool definitions come from OpenTelemetry GenAI (`gen_ai.tool.definitions`), AI SDK 6
@@ -4,6 +4,17 @@ import { validateOptions } from "./config.js";
4
4
  import type { ExportIssue, ExportReport, HueOptions } from "./types.js";
5
5
  /** A finished span or emitted log record as the OpenTelemetry SDK hands it to a processor. */
6
6
  export type RecordValue = ReadableSpan | ReadableLogRecord;
7
+ /**
8
+ * How long one export holds its records for a receiver that refused them for their rate (HTTP 429)
9
+ * and asked to be retried later than a request's deadline allows: the records stay queued, counted
10
+ * against the queue's bounds, and are sent again once `Retry-After` has passed. A longer wait loses
11
+ * them, reported like any refused request.
12
+ */
13
+ export declare const MAX_RATE_LIMIT_HOLD_MILLIS = 60000;
14
+ /** How long one export may take with requests of `timeoutMillis` each: its uploads of values over
15
+ * the inline limit, its requests, and a hold for a rate-limited receiver on top. Processors and
16
+ * providers wait this long for a drain. */
17
+ export declare function exportBudgetMillis(timeoutMillis: number): number;
7
18
  /**
8
19
  * Rejection of {@link HueClient.flush} and {@link HueClient.shutdown}: telemetry was not fully
9
20
  * accepted. Carries sanitized counts only, never server response text or content.
@@ -62,6 +73,19 @@ export declare class HueTransport {
62
73
  private placeholders;
63
74
  private placeholderSources;
64
75
  private batchSpans?;
76
+ private limits;
77
+ /** The rate-limit holds in progress, each ended early by calling it. */
78
+ private holds;
79
+ private holdsReleased;
80
+ private admissionCuts;
81
+ private admissionDigests;
82
+ /** Uploads values over the inline limit apart from their spans; absent when disabled. */
83
+ private uploader?;
84
+ /** Bytes of values over the inline limit queued spans hold whole for upload, and each span's. */
85
+ private heldBytes;
86
+ private held;
87
+ private uploaded;
88
+ private uploadFallbacks;
65
89
  constructor(options: HueOptions);
66
90
  /**
67
91
  * Advisory records (placeholders) are admitted only while the queue is under a quarter of its
@@ -74,6 +98,8 @@ export declare class HueTransport {
74
98
  private announceLiveSpans;
75
99
  private stopLiveTimer;
76
100
  private stopLiveSpans;
101
+ /** Bounded: a trace whose root never arrives (an abandoned run) gives way to newer ones. */
102
+ private countDropped;
77
103
  /** Cumulative counters and current queue gauges. */
78
104
  getReport(): ExportReport;
79
105
  /** Copies of the latest 128 sanitized issues, oldest first. */