@telemetry-dev/sdk 0.1.5 → 0.1.6
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/README.md +8 -1
- package/dist/index.d.mts +29 -1
- package/dist/index.mjs +145 -1
- package/package.json +1 -1
- package/src/attrs.ts +18 -0
- package/src/capture.ts +229 -0
- package/src/client.ts +6 -0
- package/src/index.ts +16 -1
package/README.md
CHANGED
|
@@ -90,12 +90,19 @@ The SDK installs the [session sampling wrapper](../otel/README.md#correlation-at
|
|
|
90
90
|
context.
|
|
91
91
|
- `extractW3cContext(carrier, options?)` / `injectW3cContext(context, carrier, options?)` — extract
|
|
92
92
|
or inject W3C trace context. Baggage is omitted unless `includeBaggage` is true.
|
|
93
|
+
- `captureEnabled("input" | "output")` — report whether the active initialized client permits that
|
|
94
|
+
capture direction. Returns `false` when no client is initialized.
|
|
95
|
+
- `boundedCapture(value, options?)` — copy a JSON-compatible value within bounded work and retained
|
|
96
|
+
size. Defaults to 48 KiB, depth 32, and 1,000 traversal items. Returns `{ value, truncated }`;
|
|
97
|
+
`value` can be partial or `undefined` when a limit, cycle, getter, or skip rule prevents capture.
|
|
93
98
|
- `flush()` / `shutdown()` — export pending data; required before serverless freeze/exit.
|
|
94
99
|
|
|
95
100
|
Span types: `span` (default) | `generation` | `tool` | `agent` | `embedding` — mapped to
|
|
96
101
|
`gen_ai.operation.name` `function` / `chat` / `execute_tool` / `invoke_agent` / `embeddings`.
|
|
97
102
|
Generation fields (`model`, `provider`, `usage`, `costUsd`, sampling params, …) map to the OTel
|
|
98
|
-
GenAI semantic conventions
|
|
103
|
+
GenAI semantic conventions. An explicit `costUsd` takes precedence; otherwise, the server estimates
|
|
104
|
+
standard token cost when reported usage and matching model pricing are available. Missing pricing leaves cost
|
|
105
|
+
unavailable, not zero, and does not prevent tracing or token accounting.
|
|
99
106
|
|
|
100
107
|
Duration and token-usage histograms (`gen_ai.client.operation.duration`,
|
|
101
108
|
`gen_ai.client.token.usage`) are recorded automatically for generation/agent/embedding/tool spans.
|
package/dist/index.d.mts
CHANGED
|
@@ -59,6 +59,24 @@ interface TelemetryOptions {
|
|
|
59
59
|
sdkName?: string;
|
|
60
60
|
}
|
|
61
61
|
//#endregion
|
|
62
|
+
//#region src/capture.d.ts
|
|
63
|
+
interface BoundedCaptureOptions {
|
|
64
|
+
maxBytes?: number;
|
|
65
|
+
maxDepth?: number;
|
|
66
|
+
maxItems?: number;
|
|
67
|
+
skip?: (key: string, value: unknown, parent: unknown, path: readonly string[]) => boolean;
|
|
68
|
+
}
|
|
69
|
+
interface BoundedCaptureResult {
|
|
70
|
+
value: unknown;
|
|
71
|
+
truncated: boolean;
|
|
72
|
+
}
|
|
73
|
+
interface BoundedCaptureDetails extends BoundedCaptureResult {
|
|
74
|
+
bytes: number;
|
|
75
|
+
items: number;
|
|
76
|
+
}
|
|
77
|
+
declare function boundedCaptureDetails(input: unknown, options?: BoundedCaptureOptions): BoundedCaptureDetails;
|
|
78
|
+
declare function boundedCapture(input: unknown, options?: BoundedCaptureOptions): BoundedCaptureResult;
|
|
79
|
+
//#endregion
|
|
62
80
|
//#region src/attrs.d.ts
|
|
63
81
|
type SpanType = "span" | "generation" | "tool" | "agent" | "embedding";
|
|
64
82
|
interface TokenUsage {
|
|
@@ -68,6 +86,15 @@ interface TokenUsage {
|
|
|
68
86
|
cacheReadInputTokens?: number;
|
|
69
87
|
cacheCreationInputTokens?: number;
|
|
70
88
|
reasoningOutputTokens?: number;
|
|
89
|
+
textInputTokens?: number;
|
|
90
|
+
textOutputTokens?: number;
|
|
91
|
+
textCacheReadInputTokens?: number;
|
|
92
|
+
imageInputTokens?: number;
|
|
93
|
+
imageOutputTokens?: number;
|
|
94
|
+
imageCacheReadInputTokens?: number;
|
|
95
|
+
audioInputTokens?: number;
|
|
96
|
+
audioOutputTokens?: number;
|
|
97
|
+
audioCacheReadInputTokens?: number;
|
|
71
98
|
}
|
|
72
99
|
interface GenerationFields {
|
|
73
100
|
model?: string;
|
|
@@ -126,6 +153,7 @@ interface TelemetryClient {
|
|
|
126
153
|
/** Flush + teardown + unregister globals; the client becomes a no-op afterwards. */
|
|
127
154
|
shutdown(): Promise<void>;
|
|
128
155
|
}
|
|
156
|
+
declare function captureEnabled(kind: "input" | "output"): boolean;
|
|
129
157
|
declare function flush(): Promise<void>;
|
|
130
158
|
declare function shutdown(): Promise<void>;
|
|
131
159
|
declare function init(options?: TelemetryOptions, overrides?: ClientOverrides): TelemetryClient;
|
|
@@ -207,4 +235,4 @@ interface ObserveOptions extends StartSpanOptions {
|
|
|
207
235
|
*/
|
|
208
236
|
declare function observe<TThis, TArgs extends unknown[], TReturn>(fn: (this: TThis, ...args: TArgs) => TReturn, options?: ObserveOptions): (...args: TArgs) => TReturn;
|
|
209
237
|
//#endregion
|
|
210
|
-
export { type AgentFields, type BatchOptions, type ClientOverrides, type ExportMode, type GenerationFields, type LogLevel, type LogOptions, type MaskFn, type ObserveOptions, type ParentRef, type PropagatedAttributes, SCOPE_NAME, type SdkLogLevel, type SpanFields, type SpanHandle, type SpanType, type StartSpanOptions, type TelemetryClient, type TelemetryOptions, TelemetrySpanProcessor, type TelemetrySpanProcessorOptions, type TokenUsage, type ToolFields, activeContext, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
|
|
238
|
+
export { type AgentFields, type BatchOptions, type BoundedCaptureDetails, type BoundedCaptureOptions, type BoundedCaptureResult, type ClientOverrides, type ExportMode, type GenerationFields, type LogLevel, type LogOptions, type MaskFn, type ObserveOptions, type ParentRef, type PropagatedAttributes, SCOPE_NAME, type SdkLogLevel, type SpanFields, type SpanHandle, type SpanType, type StartSpanOptions, type TelemetryClient, type TelemetryOptions, TelemetrySpanProcessor, type TelemetrySpanProcessorOptions, type TokenUsage, type ToolFields, activeContext, boundedCapture, boundedCaptureDetails, captureEnabled, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
|
package/dist/index.mjs
CHANGED
|
@@ -48,6 +48,10 @@ let activeClient;
|
|
|
48
48
|
function currentClient() {
|
|
49
49
|
return activeClient ?? NOOP_CLIENT;
|
|
50
50
|
}
|
|
51
|
+
function captureEnabled(kind) {
|
|
52
|
+
const config = currentClient().core?.config;
|
|
53
|
+
return kind === "input" ? config?.captureInput ?? false : config?.captureOutput ?? false;
|
|
54
|
+
}
|
|
51
55
|
function flush() {
|
|
52
56
|
return currentClient().flush();
|
|
53
57
|
}
|
|
@@ -186,6 +190,137 @@ function initInner(options, overrides) {
|
|
|
186
190
|
//#endregion
|
|
187
191
|
//#region src/capture.ts
|
|
188
192
|
const TRUNCATION_MARKER = "...[truncated]";
|
|
193
|
+
const jsonStringByteLength = (value, limit) => {
|
|
194
|
+
let bytes = 2;
|
|
195
|
+
for (let index = 0; index < value.length && bytes <= limit; index += 1) {
|
|
196
|
+
const code = value.charCodeAt(index);
|
|
197
|
+
if (code === 34 || code === 92 || code === 8 || code === 9 || code === 10 || code === 12 || code === 13) bytes += 2;
|
|
198
|
+
else if (code < 32) bytes += 6;
|
|
199
|
+
else if (code < 128) bytes += 1;
|
|
200
|
+
else if (code < 2048) bytes += 2;
|
|
201
|
+
else if (code >= 55296 && code <= 56319) {
|
|
202
|
+
const next = value.charCodeAt(index + 1);
|
|
203
|
+
if (next >= 56320 && next <= 57343) {
|
|
204
|
+
bytes += 4;
|
|
205
|
+
index += 1;
|
|
206
|
+
} else bytes += 6;
|
|
207
|
+
} else if (code >= 56320 && code <= 57343) bytes += 6;
|
|
208
|
+
else bytes += 3;
|
|
209
|
+
}
|
|
210
|
+
return bytes;
|
|
211
|
+
};
|
|
212
|
+
const captureLimit = (name, value, fallback) => {
|
|
213
|
+
if (value === void 0) return fallback;
|
|
214
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
215
|
+
return value;
|
|
216
|
+
};
|
|
217
|
+
function boundedCaptureDetails(input, options = {}) {
|
|
218
|
+
const maxBytes = captureLimit("maxBytes", options.maxBytes, 48 * 1024);
|
|
219
|
+
const maxDepth = captureLimit("maxDepth", options.maxDepth, 32);
|
|
220
|
+
const maxItems = captureLimit("maxItems", options.maxItems, 1e3);
|
|
221
|
+
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
222
|
+
let bytes = 0;
|
|
223
|
+
let items = 0;
|
|
224
|
+
let truncated = false;
|
|
225
|
+
const visit = (value, depth, path) => {
|
|
226
|
+
if (items >= maxItems) {
|
|
227
|
+
truncated = true;
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
items += 1;
|
|
231
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
232
|
+
const length = typeof value === "string" ? jsonStringByteLength(value, maxBytes - bytes) : JSON.stringify(value)?.length ?? 0;
|
|
233
|
+
if (bytes + length > maxBytes) {
|
|
234
|
+
truncated = true;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
bytes += length;
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
240
|
+
if (typeof value !== "object") return void 0;
|
|
241
|
+
if (depth >= maxDepth || ancestors.has(value) || bytes + 2 > maxBytes) {
|
|
242
|
+
truncated = true;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
ancestors.add(value);
|
|
246
|
+
bytes += 2;
|
|
247
|
+
try {
|
|
248
|
+
if (Array.isArray(value)) {
|
|
249
|
+
const result = [];
|
|
250
|
+
for (const item of value) {
|
|
251
|
+
if (items >= maxItems) {
|
|
252
|
+
truncated = true;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
const previousBytes = bytes;
|
|
256
|
+
if (result.length > 0) bytes += 1;
|
|
257
|
+
const converted = visit(item, depth + 1, path);
|
|
258
|
+
if (converted === void 0) bytes = previousBytes;
|
|
259
|
+
else result.push(converted);
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
const result = {};
|
|
264
|
+
let emitted = false;
|
|
265
|
+
for (const key in value) {
|
|
266
|
+
if (items >= maxItems) {
|
|
267
|
+
truncated = true;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
if (!Object.hasOwn(value, key)) {
|
|
271
|
+
items += 1;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const keyBytes = jsonStringByteLength(key, maxBytes - bytes) + 1 + (emitted ? 1 : 0);
|
|
275
|
+
if (bytes + keyBytes > maxBytes) {
|
|
276
|
+
items += 1;
|
|
277
|
+
truncated = true;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
let item;
|
|
281
|
+
try {
|
|
282
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
283
|
+
item = descriptor?.get ? descriptor.get.call(value) : descriptor?.value;
|
|
284
|
+
if (options.skip?.(key, item, value, path)) {
|
|
285
|
+
items += 1;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
} catch {
|
|
289
|
+
items += 1;
|
|
290
|
+
truncated = true;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const previousBytes = bytes;
|
|
294
|
+
bytes += keyBytes;
|
|
295
|
+
const converted = visit(item, depth + 1, [...path, key]);
|
|
296
|
+
if (converted === void 0) bytes = previousBytes;
|
|
297
|
+
else {
|
|
298
|
+
result[key] = converted;
|
|
299
|
+
emitted = true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
} catch {
|
|
304
|
+
truncated = true;
|
|
305
|
+
return;
|
|
306
|
+
} finally {
|
|
307
|
+
ancestors.delete(value);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
return {
|
|
311
|
+
value: visit(input, 0, []),
|
|
312
|
+
truncated,
|
|
313
|
+
bytes,
|
|
314
|
+
items
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function boundedCapture(input, options = {}) {
|
|
318
|
+
const { value, truncated } = boundedCaptureDetails(input, options);
|
|
319
|
+
return {
|
|
320
|
+
value,
|
|
321
|
+
truncated
|
|
322
|
+
};
|
|
323
|
+
}
|
|
189
324
|
function truncate(value, maxLength) {
|
|
190
325
|
if (value.length <= maxLength) return value;
|
|
191
326
|
return value.slice(0, Math.max(maxLength - 14, 0)) + TRUNCATION_MARKER;
|
|
@@ -308,6 +443,15 @@ function fieldsToAttributes(fields, type, cfg) {
|
|
|
308
443
|
"gen_ai.usage.cache_read.input_tokens": fields.usage?.cacheReadInputTokens,
|
|
309
444
|
"gen_ai.usage.cache_creation.input_tokens": fields.usage?.cacheCreationInputTokens,
|
|
310
445
|
"gen_ai.usage.reasoning.output_tokens": fields.usage?.reasoningOutputTokens,
|
|
446
|
+
"gen_ai.usage.text.input_tokens": fields.usage?.textInputTokens,
|
|
447
|
+
"gen_ai.usage.text.output_tokens": fields.usage?.textOutputTokens,
|
|
448
|
+
"gen_ai.usage.text.cache_read.input_tokens": fields.usage?.textCacheReadInputTokens,
|
|
449
|
+
"gen_ai.usage.image.input_tokens": fields.usage?.imageInputTokens,
|
|
450
|
+
"gen_ai.usage.image.output_tokens": fields.usage?.imageOutputTokens,
|
|
451
|
+
"gen_ai.usage.image.cache_read.input_tokens": fields.usage?.imageCacheReadInputTokens,
|
|
452
|
+
"gen_ai.usage.audio.input_tokens": fields.usage?.audioInputTokens,
|
|
453
|
+
"gen_ai.usage.audio.output_tokens": fields.usage?.audioOutputTokens,
|
|
454
|
+
"gen_ai.usage.audio.cache_read.input_tokens": fields.usage?.audioCacheReadInputTokens,
|
|
311
455
|
"gen_ai.usage.cost": fields.costUsd,
|
|
312
456
|
"gen_ai.response.finish_reasons": fields.finishReason ? [fields.finishReason] : void 0,
|
|
313
457
|
"gen_ai.output.type": fields.outputType,
|
|
@@ -625,4 +769,4 @@ function observe(fn, options) {
|
|
|
625
769
|
return wrapped;
|
|
626
770
|
}
|
|
627
771
|
//#endregion
|
|
628
|
-
export { SCOPE_NAME, TelemetrySpanProcessor, activeContext, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
|
|
772
|
+
export { SCOPE_NAME, TelemetrySpanProcessor, activeContext, boundedCapture, boundedCaptureDetails, captureEnabled, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
|
package/package.json
CHANGED
package/src/attrs.ts
CHANGED
|
@@ -32,6 +32,15 @@ export interface TokenUsage {
|
|
|
32
32
|
cacheReadInputTokens?: number;
|
|
33
33
|
cacheCreationInputTokens?: number;
|
|
34
34
|
reasoningOutputTokens?: number;
|
|
35
|
+
textInputTokens?: number;
|
|
36
|
+
textOutputTokens?: number;
|
|
37
|
+
textCacheReadInputTokens?: number;
|
|
38
|
+
imageInputTokens?: number;
|
|
39
|
+
imageOutputTokens?: number;
|
|
40
|
+
imageCacheReadInputTokens?: number;
|
|
41
|
+
audioInputTokens?: number;
|
|
42
|
+
audioOutputTokens?: number;
|
|
43
|
+
audioCacheReadInputTokens?: number;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
export interface GenerationFields {
|
|
@@ -103,6 +112,15 @@ export function fieldsToAttributes(
|
|
|
103
112
|
"gen_ai.usage.cache_read.input_tokens": fields.usage?.cacheReadInputTokens,
|
|
104
113
|
"gen_ai.usage.cache_creation.input_tokens": fields.usage?.cacheCreationInputTokens,
|
|
105
114
|
"gen_ai.usage.reasoning.output_tokens": fields.usage?.reasoningOutputTokens,
|
|
115
|
+
"gen_ai.usage.text.input_tokens": fields.usage?.textInputTokens,
|
|
116
|
+
"gen_ai.usage.text.output_tokens": fields.usage?.textOutputTokens,
|
|
117
|
+
"gen_ai.usage.text.cache_read.input_tokens": fields.usage?.textCacheReadInputTokens,
|
|
118
|
+
"gen_ai.usage.image.input_tokens": fields.usage?.imageInputTokens,
|
|
119
|
+
"gen_ai.usage.image.output_tokens": fields.usage?.imageOutputTokens,
|
|
120
|
+
"gen_ai.usage.image.cache_read.input_tokens": fields.usage?.imageCacheReadInputTokens,
|
|
121
|
+
"gen_ai.usage.audio.input_tokens": fields.usage?.audioInputTokens,
|
|
122
|
+
"gen_ai.usage.audio.output_tokens": fields.usage?.audioOutputTokens,
|
|
123
|
+
"gen_ai.usage.audio.cache_read.input_tokens": fields.usage?.audioCacheReadInputTokens,
|
|
106
124
|
"gen_ai.usage.cost": fields.costUsd,
|
|
107
125
|
"gen_ai.response.finish_reasons": fields.finishReason ? [fields.finishReason] : undefined,
|
|
108
126
|
"gen_ai.output.type": fields.outputType,
|
package/src/capture.ts
CHANGED
|
@@ -11,6 +11,235 @@ export interface CaptureConfig {
|
|
|
11
11
|
onError?: (cause: Error) => void;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export interface BoundedCaptureOptions {
|
|
15
|
+
maxBytes?: number;
|
|
16
|
+
maxDepth?: number;
|
|
17
|
+
maxItems?: number;
|
|
18
|
+
skip?: (key: string, value: unknown, parent: unknown, path: readonly string[]) => boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface BoundedCaptureResult {
|
|
22
|
+
value: unknown;
|
|
23
|
+
truncated: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface BoundedCaptureDetails extends BoundedCaptureResult {
|
|
27
|
+
bytes: number;
|
|
28
|
+
items: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface CapturedObject {
|
|
32
|
+
[key: string]: CapturedValue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type CapturedValue = null | boolean | number | string | CapturedValue[] | CapturedObject;
|
|
36
|
+
|
|
37
|
+
const jsonStringByteLength = (value: string, limit: number) => {
|
|
38
|
+
let bytes = 2;
|
|
39
|
+
|
|
40
|
+
for (let index = 0; index < value.length && bytes <= limit; index += 1) {
|
|
41
|
+
const code = value.charCodeAt(index);
|
|
42
|
+
|
|
43
|
+
if (
|
|
44
|
+
code === 0x22 ||
|
|
45
|
+
code === 0x5c ||
|
|
46
|
+
code === 0x08 ||
|
|
47
|
+
code === 0x09 ||
|
|
48
|
+
code === 0x0a ||
|
|
49
|
+
code === 0x0c ||
|
|
50
|
+
code === 0x0d
|
|
51
|
+
) {
|
|
52
|
+
bytes += 2;
|
|
53
|
+
} else if (code < 0x20) {
|
|
54
|
+
bytes += 6;
|
|
55
|
+
} else if (code < 0x80) {
|
|
56
|
+
bytes += 1;
|
|
57
|
+
} else if (code < 0x800) {
|
|
58
|
+
bytes += 2;
|
|
59
|
+
} else if (code >= 0xd800 && code <= 0xdbff) {
|
|
60
|
+
const next = value.charCodeAt(index + 1);
|
|
61
|
+
|
|
62
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
63
|
+
bytes += 4;
|
|
64
|
+
index += 1;
|
|
65
|
+
} else {
|
|
66
|
+
bytes += 6;
|
|
67
|
+
}
|
|
68
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
69
|
+
bytes += 6;
|
|
70
|
+
} else {
|
|
71
|
+
bytes += 3;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return bytes;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const captureLimit = (name: string, value: number | undefined, fallback: number) => {
|
|
79
|
+
if (value === undefined) return fallback;
|
|
80
|
+
|
|
81
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
82
|
+
throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return value;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export function boundedCaptureDetails(
|
|
89
|
+
input: unknown,
|
|
90
|
+
options: BoundedCaptureOptions = {},
|
|
91
|
+
): BoundedCaptureDetails {
|
|
92
|
+
const maxBytes = captureLimit("maxBytes", options.maxBytes, 48 * 1024);
|
|
93
|
+
const maxDepth = captureLimit("maxDepth", options.maxDepth, 32);
|
|
94
|
+
const maxItems = captureLimit("maxItems", options.maxItems, 1_000);
|
|
95
|
+
const ancestors = new WeakSet<object>();
|
|
96
|
+
let bytes = 0;
|
|
97
|
+
let items = 0;
|
|
98
|
+
let truncated = false;
|
|
99
|
+
|
|
100
|
+
const visit = (
|
|
101
|
+
value: unknown,
|
|
102
|
+
depth: number,
|
|
103
|
+
path: readonly string[],
|
|
104
|
+
): CapturedValue | undefined => {
|
|
105
|
+
if (items >= maxItems) {
|
|
106
|
+
truncated = true;
|
|
107
|
+
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
items += 1;
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
value === null ||
|
|
115
|
+
typeof value === "boolean" ||
|
|
116
|
+
typeof value === "number" ||
|
|
117
|
+
typeof value === "string"
|
|
118
|
+
) {
|
|
119
|
+
const length =
|
|
120
|
+
typeof value === "string"
|
|
121
|
+
? jsonStringByteLength(value, maxBytes - bytes)
|
|
122
|
+
: (JSON.stringify(value)?.length ?? 0);
|
|
123
|
+
|
|
124
|
+
if (bytes + length > maxBytes) {
|
|
125
|
+
truncated = true;
|
|
126
|
+
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
bytes += length;
|
|
131
|
+
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (typeof value !== "object") return undefined;
|
|
136
|
+
|
|
137
|
+
if (depth >= maxDepth || ancestors.has(value) || bytes + 2 > maxBytes) {
|
|
138
|
+
truncated = true;
|
|
139
|
+
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
ancestors.add(value);
|
|
144
|
+
bytes += 2;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
if (Array.isArray(value)) {
|
|
148
|
+
const result: CapturedValue[] = [];
|
|
149
|
+
|
|
150
|
+
for (const item of value) {
|
|
151
|
+
if (items >= maxItems) {
|
|
152
|
+
truncated = true;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const previousBytes = bytes;
|
|
157
|
+
|
|
158
|
+
if (result.length > 0) bytes += 1;
|
|
159
|
+
const converted = visit(item, depth + 1, path);
|
|
160
|
+
|
|
161
|
+
if (converted === undefined) {
|
|
162
|
+
bytes = previousBytes;
|
|
163
|
+
} else {
|
|
164
|
+
result.push(converted);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const result: CapturedObject = {};
|
|
172
|
+
let emitted = false;
|
|
173
|
+
|
|
174
|
+
for (const key in value) {
|
|
175
|
+
if (items >= maxItems) {
|
|
176
|
+
truncated = true;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!Object.hasOwn(value, key)) {
|
|
181
|
+
items += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const keyBytes = jsonStringByteLength(key, maxBytes - bytes) + 1 + (emitted ? 1 : 0);
|
|
186
|
+
|
|
187
|
+
if (bytes + keyBytes > maxBytes) {
|
|
188
|
+
items += 1;
|
|
189
|
+
truncated = true;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let item: unknown;
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
197
|
+
item = descriptor?.get ? descriptor.get.call(value) : descriptor?.value;
|
|
198
|
+
|
|
199
|
+
if (options.skip?.(key, item, value, path)) {
|
|
200
|
+
items += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
items += 1;
|
|
205
|
+
truncated = true;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const previousBytes = bytes;
|
|
210
|
+
bytes += keyBytes;
|
|
211
|
+
const converted = visit(item, depth + 1, [...path, key]);
|
|
212
|
+
|
|
213
|
+
if (converted === undefined) {
|
|
214
|
+
bytes = previousBytes;
|
|
215
|
+
} else {
|
|
216
|
+
result[key] = converted;
|
|
217
|
+
emitted = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return result;
|
|
222
|
+
} catch {
|
|
223
|
+
truncated = true;
|
|
224
|
+
|
|
225
|
+
return undefined;
|
|
226
|
+
} finally {
|
|
227
|
+
ancestors.delete(value);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return { value: visit(input, 0, []), truncated, bytes, items };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function boundedCapture(
|
|
235
|
+
input: unknown,
|
|
236
|
+
options: BoundedCaptureOptions = {},
|
|
237
|
+
): BoundedCaptureResult {
|
|
238
|
+
const { value, truncated } = boundedCaptureDetails(input, options);
|
|
239
|
+
|
|
240
|
+
return { value, truncated };
|
|
241
|
+
}
|
|
242
|
+
|
|
14
243
|
export function truncate(value: string, maxLength: number): string {
|
|
15
244
|
if (value.length <= maxLength) return value;
|
|
16
245
|
|
package/src/client.ts
CHANGED
|
@@ -75,6 +75,12 @@ export function currentClient(): ClientHandle {
|
|
|
75
75
|
return activeClient ?? NOOP_CLIENT;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export function captureEnabled(kind: "input" | "output"): boolean {
|
|
79
|
+
const config = currentClient().core?.config;
|
|
80
|
+
|
|
81
|
+
return kind === "input" ? (config?.captureInput ?? false) : (config?.captureOutput ?? false);
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
export function flush(): Promise<void> {
|
|
79
85
|
return currentClient().flush();
|
|
80
86
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,14 @@ export type {
|
|
|
7
7
|
ToolFields,
|
|
8
8
|
} from "./attrs.ts";
|
|
9
9
|
|
|
10
|
-
export {
|
|
10
|
+
export {
|
|
11
|
+
captureEnabled,
|
|
12
|
+
type ClientOverrides,
|
|
13
|
+
flush,
|
|
14
|
+
init,
|
|
15
|
+
shutdown,
|
|
16
|
+
type TelemetryClient,
|
|
17
|
+
} from "./client.ts";
|
|
11
18
|
|
|
12
19
|
export type {
|
|
13
20
|
BatchOptions,
|
|
@@ -18,6 +25,14 @@ export type {
|
|
|
18
25
|
TelemetryOptions,
|
|
19
26
|
} from "./config.ts";
|
|
20
27
|
|
|
28
|
+
export {
|
|
29
|
+
boundedCapture,
|
|
30
|
+
boundedCaptureDetails,
|
|
31
|
+
type BoundedCaptureDetails,
|
|
32
|
+
type BoundedCaptureOptions,
|
|
33
|
+
type BoundedCaptureResult,
|
|
34
|
+
} from "./capture.ts";
|
|
35
|
+
|
|
21
36
|
export {
|
|
22
37
|
propagateAttributes,
|
|
23
38
|
type PropagatedAttributes,
|