@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/CLI.md +26 -19
- package/EVALUATIONS.md +1 -1
- package/README.md +117 -24
- package/dist/blobs.d.ts +164 -0
- package/dist/blobs.js +703 -0
- package/dist/cli/eval.js +9 -5
- package/dist/client.d.ts +15 -5
- package/dist/client.js +63 -13
- package/dist/config.d.ts +20 -0
- package/dist/config.js +31 -4
- package/dist/environment/client.d.ts +1 -1
- package/dist/environment/client.js +3 -3
- package/dist/evals/client.d.ts +1 -1
- package/dist/evals/client.js +4 -4
- package/dist/evals/verdicts.d.ts +10 -7
- package/dist/evals/verdicts.js +52 -16
- package/dist/inline-files.d.ts +44 -1
- package/dist/inline-files.js +65 -6
- package/dist/limits.d.ts +32 -0
- package/dist/limits.js +54 -0
- package/dist/privacy.d.ts +27 -5
- package/dist/privacy.js +140 -39
- package/dist/provider-tools.js +1 -1
- package/dist/safety.d.ts +2 -0
- package/dist/safety.js +10 -4
- package/dist/snapshot.d.ts +30 -8
- package/dist/snapshot.js +280 -49
- package/dist/tool-definitions.d.ts +4 -0
- package/dist/tool-definitions.js +11 -0
- package/dist/transport.d.ts +26 -0
- package/dist/transport.js +579 -177
- package/dist/types.d.ts +14 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
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
|
-
/**
|
|
23
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
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 >
|
|
61
|
-
throw new
|
|
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
|
-
|
|
65
|
-
|
|
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 >
|
|
100
|
-
throw new
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
...
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
192
|
-
links,
|
|
193
|
-
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 {
|
|
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
|
package/dist/tool-definitions.js
CHANGED
|
@@ -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
|
package/dist/transport.d.ts
CHANGED
|
@@ -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. */
|