@hue-run/sdk 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/ENVIRONMENTS.md +182 -0
  2. package/EVALUATIONS.md +12 -0
  3. package/README.md +204 -21
  4. package/dist/ai-sdk.d.ts +9 -1
  5. package/dist/ai-sdk.js +37 -2
  6. package/dist/client.d.ts +130 -5
  7. package/dist/client.js +518 -110
  8. package/dist/config.d.ts +11 -2
  9. package/dist/config.js +50 -4
  10. package/dist/environment/client.d.ts +73 -0
  11. package/dist/environment/client.js +209 -0
  12. package/dist/environment/tools.d.ts +30 -0
  13. package/dist/environment/tools.js +24 -0
  14. package/dist/environment/types.d.ts +429 -0
  15. package/dist/environment/types.js +1 -0
  16. package/dist/environment.d.ts +5 -0
  17. package/dist/environment.js +2 -0
  18. package/dist/evals/attempt.d.ts +454 -0
  19. package/dist/evals/attempt.js +687 -0
  20. package/dist/evals/client.d.ts +99 -5
  21. package/dist/evals/client.js +136 -7
  22. package/dist/evals/environment-evidence.d.ts +6 -0
  23. package/dist/evals/environment-evidence.js +123 -0
  24. package/dist/evals/environment-json.d.ts +3 -0
  25. package/dist/evals/environment-json.js +76 -0
  26. package/dist/evals/json.d.ts +9 -1
  27. package/dist/evals/json.js +14 -6
  28. package/dist/evals/runner.d.ts +61 -2
  29. package/dist/evals/runner.js +71 -9
  30. package/dist/evals/scorer-publication.d.ts +2 -0
  31. package/dist/evals/scorer-publication.js +84 -0
  32. package/dist/evals/scorers.d.ts +11 -0
  33. package/dist/evals/scorers.js +56 -5
  34. package/dist/evals/simulation.d.ts +184 -0
  35. package/dist/evals/simulation.js +603 -0
  36. package/dist/evals/types.d.ts +304 -0
  37. package/dist/evals.d.ts +5 -1
  38. package/dist/evals.js +3 -1
  39. package/dist/experimental-telemetry.d.ts +8 -0
  40. package/dist/experimental-telemetry.js +13 -0
  41. package/dist/index.d.ts +4 -1
  42. package/dist/index.js +3 -1
  43. package/dist/managed.d.ts +51 -1
  44. package/dist/managed.js +11 -1
  45. package/dist/privacy.d.ts +2 -0
  46. package/dist/privacy.js +54 -21
  47. package/dist/receipt.d.ts +12 -1
  48. package/dist/receipt.js +10 -1
  49. package/dist/safety.d.ts +7 -0
  50. package/dist/safety.js +179 -0
  51. package/dist/snapshot.d.ts +12 -0
  52. package/dist/snapshot.js +200 -0
  53. package/dist/transport.d.ts +46 -8
  54. package/dist/transport.js +266 -48
  55. package/dist/types.d.ts +167 -8
  56. package/dist/version.d.ts +2 -0
  57. package/dist/version.js +3 -0
  58. package/package.json +51 -15
package/dist/managed.d.ts CHANGED
@@ -1,65 +1,115 @@
1
1
  import { type Tracer } from "@opentelemetry/api";
2
2
  import type { JsonValue } from "./types.js";
3
+ /** A declared input file the handler downloads and verifies before calling the target. */
3
4
  export interface ManagedInputFile {
5
+ /** Hue artifact ID. */
4
6
  artifactId: string;
7
+ /** File name without path separators. */
5
8
  filename: string;
9
+ /** MIME type. */
6
10
  contentType: string;
11
+ /** Exact size in bytes; verified after download. */
7
12
  byteSize: number;
13
+ /** Lowercase hex SHA-256 of the bytes; verified after download. */
8
14
  sha256: string;
15
+ /** Caller-defined role of the file in the case, up to 64 characters. */
9
16
  role: string;
10
17
  }
18
+ /** The request body Hue POSTs to a managed target (protocol version 1). */
11
19
  export interface ManagedInvocation {
20
+ /** Always 1. */
12
21
  protocolVersion: 1;
22
+ /** Execution to claim and complete. */
13
23
  executionId: string;
24
+ /** Attempt number, starting at 1. */
14
25
  attempt: number;
26
+ /** Frozen case inputs. */
15
27
  input: JsonValue;
28
+ /** Experiment configuration. */
16
29
  config: JsonValue;
30
+ /** Up to 16 declared input files. */
17
31
  inputFiles: ManagedInputFile[];
32
+ /** RFC 3339 UTC deadline for the whole invocation. */
18
33
  deadline: string;
34
+ /** Sampled W3C `traceparent` the target span must continue. */
19
35
  traceparent: string;
20
36
  }
37
+ /** What the target callback receives after the claim and file verification succeed. */
21
38
  export interface ManagedTargetContext {
39
+ /** Execution being run. */
22
40
  executionId: string;
41
+ /** Attempt number, starting at 1. */
23
42
  attempt: number;
43
+ /** Frozen case inputs. */
24
44
  input: JsonValue;
45
+ /** Experiment configuration. */
25
46
  config: JsonValue;
47
+ /** Verified input files with their bytes. */
26
48
  inputFiles: Array<ManagedInputFile & {
49
+ /** Verified file contents. */
27
50
  data: Uint8Array;
28
51
  }>;
52
+ /** Aborts at the execution deadline; the target must honor it. */
29
53
  signal: AbortSignal;
54
+ /** Trace ID of the `ai.managed_target` span the target runs under. */
30
55
  traceId: string;
31
56
  }
57
+ /** A file the target returns for upload. */
32
58
  export interface ManagedOutputFile {
59
+ /** File name without path separators. */
33
60
  filename: string;
61
+ /** MIME type. */
34
62
  contentType: string;
63
+ /** Exact bytes to store, at most 25 MiB. */
35
64
  data: Uint8Array;
65
+ /** Marks the single primary artifact of the outcome. */
36
66
  primary?: boolean;
37
67
  }
68
+ /** The target callback's return value. */
38
69
  export interface ManagedTargetResult {
70
+ /** Outcome state; `succeeded` by default. */
39
71
  state?: "succeeded" | "error" | "cancelled";
72
+ /** Output JSON; omitted means unavailable, `null` is a present output. */
40
73
  output?: JsonValue;
41
74
  /** Public, safe error summary. Never include provider errors, credentials or stacks. */
42
75
  error?: {
76
+ /** Stable lowercase type such as `target_error`. */
43
77
  type: string;
78
+ /** Bounded public message. */
44
79
  message?: string;
45
80
  };
81
+ /** Up to 16 files, 64 MiB in total. */
46
82
  files?: ManagedOutputFile[];
83
+ /** Provider-reported token usage, when known. */
47
84
  usage?: {
85
+ /** Prompt tokens. */
48
86
  inputTokens?: number;
87
+ /** Completion tokens. */
49
88
  outputTokens?: number;
50
89
  };
51
90
  }
91
+ /** Options for {@link createManagedTargetHandler}. */
52
92
  export interface ManagedTargetOptions {
93
+ /** Dedicated shared secret Hue presents as a Bearer token; validated before any network access. */
53
94
  machineCredential: string;
95
+ /** Hue origin for invocation callbacks, `https://app.hue.run` by default; never taken from a request. */
54
96
  baseUrl?: string;
97
+ /** The application's existing agent function; called at most once per invocation. */
55
98
  target: (invocation: ManagedTargetContext) => Promise<ManagedTargetResult>;
56
99
  /** Flush the application's existing trace AND log pipelines; do not shut them down. */
57
100
  flushTelemetry: () => Promise<unknown>;
101
+ /** Tracer that records under the incoming trace context; defaults to the global tracer, which must be configured. */
58
102
  tracer?: Tracer;
59
103
  /** Reserve finalization time inside the host's request limit. Default 90 seconds. */
60
104
  maxExecutionMillis?: number;
61
105
  /** Upload/checkpoint/flush grace after execution deadline, at most 30 seconds. */
62
106
  finalizationMillis?: number;
63
107
  }
64
- /** A machine-authenticated, framework-neutral POST handler. Never retries the target. */
108
+ /**
109
+ * Creates a machine-authenticated, framework-neutral POST handler that claims the invocation,
110
+ * verifies input files, runs `target` under the incoming trace context, uploads output files,
111
+ * saves the outcome and acknowledges telemetry. It never retries the target.
112
+ *
113
+ * @throws TypeError for an invalid credential, origin, budget or missing callbacks at construction.
114
+ */
65
115
  export declare function createManagedTargetHandler(options: ManagedTargetOptions): (request: Request) => Promise<Response>;
package/dist/managed.js CHANGED
@@ -21,7 +21,13 @@ const STATES = new Set([
21
21
  "superseded",
22
22
  ]);
23
23
  const propagator = new W3CTraceContextPropagator();
24
- /** A machine-authenticated, framework-neutral POST handler. Never retries the target. */
24
+ /**
25
+ * Creates a machine-authenticated, framework-neutral POST handler that claims the invocation,
26
+ * verifies input files, runs `target` under the incoming trace context, uploads output files,
27
+ * saves the outcome and acknowledges telemetry. It never retries the target.
28
+ *
29
+ * @throws TypeError for an invalid credential, origin, budget or missing callbacks at construction.
30
+ */
25
31
  export function createManagedTargetHandler(options) {
26
32
  token(options.machineCredential);
27
33
  const baseUrl = validateOptions({
@@ -343,6 +349,7 @@ function sha256(data) {
343
349
  return createHash("sha256").update(data).digest("hex");
344
350
  }
345
351
  function token(value) {
352
+ // eslint-disable-next-line no-control-regex -- control characters are rejected deliberately
346
353
  if (typeof value !== "string" || !value || value.length > 8192 || /[\s\u0000]/u.test(value))
347
354
  throw new TypeError("Invalid credential");
348
355
  return value;
@@ -364,6 +371,7 @@ function filename(value) {
364
371
  if (typeof value !== "string" ||
365
372
  !value.trim() ||
366
373
  value.length > 255 ||
374
+ // eslint-disable-next-line no-control-regex -- control characters are rejected deliberately
367
375
  /[\x00-\x1f\x7f/\\]/u.test(value) ||
368
376
  value === "." ||
369
377
  value === "..")
@@ -374,6 +382,7 @@ function shortString(value, maximum = 255) {
374
382
  if (typeof value !== "string" ||
375
383
  !value ||
376
384
  value.length > maximum ||
385
+ // eslint-disable-next-line no-control-regex -- control characters are rejected deliberately
377
386
  /[\x00-\x1f\x7f]/u.test(value))
378
387
  throw new TypeError("Invalid string");
379
388
  return value;
@@ -466,6 +475,7 @@ function validateResult(result) {
466
475
  throw new TypeError("Invalid output files");
467
476
  }
468
477
  function safeUploadUrl(value) {
478
+ // eslint-disable-next-line no-control-regex -- control characters are rejected deliberately
469
479
  if (typeof value !== "string" || value.length > 8192 || /[\x00-\x20\x7f]/u.test(value))
470
480
  throw new TypeError("Invalid upload URL");
471
481
  const url = new URL(value);
package/dist/privacy.d.ts CHANGED
@@ -2,6 +2,8 @@ import type { ReadableSpan } from "@opentelemetry/sdk-trace";
2
2
  import type { ReadableLogRecord } from "@opentelemetry/sdk-logs";
3
3
  import { type Resource } from "@opentelemetry/resources";
4
4
  import type { HueOptions } from "./types.js";
5
+ /** Attribute keys (and their dotted children) removed in metadata-only mode. */
6
+ export declare const contentPrefixes: string[];
5
7
  export type ResourceCache = WeakMap<Resource, Resource>;
6
8
  export declare function redactSpan(span: ReadableSpan, options: HueOptions, cache: ResourceCache): ReadableSpan;
7
9
  export declare function redactLog(log: ReadableLogRecord, options: HueOptions, cache: ResourceCache): ReadableLogRecord;
package/dist/privacy.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { resourceFromAttributes } from "@opentelemetry/resources";
2
- import { MAX_CONTENT_BYTES } from "./config.js";
3
- const contentPrefixes = [
2
+ import { MAX_BODY_BYTES, MAX_CONTENT_BYTES } from "./config.js";
3
+ /** Attribute keys (and their dotted children) removed in metadata-only mode. */
4
+ export const contentPrefixes = [
4
5
  "gen_ai.input.messages",
5
6
  "gen_ai.output.messages",
6
7
  "gen_ai.system_instructions",
@@ -15,11 +16,25 @@ const contentPrefixes = [
15
16
  "llm.prompts",
16
17
  "llm.completions",
17
18
  "llm.invocation_parameters",
19
+ "llm.prompt_template.template",
20
+ "llm.prompt_template.variables",
21
+ "llm.tools",
22
+ "llm.function_call",
23
+ "llm.choices",
18
24
  "input.value",
19
25
  "output.value",
26
+ "input.images",
27
+ "output.images",
28
+ "retrieval.documents",
29
+ "embedding.embeddings",
30
+ "reranker.query",
31
+ "reranker.input_documents",
32
+ "reranker.output_documents",
20
33
  "ai.prompt",
21
34
  "ai.response.text",
22
35
  "ai.response.object",
36
+ "ai.response.reasoning",
37
+ "ai.response.files",
23
38
  "ai.response.toolCalls",
24
39
  "ai.response.body",
25
40
  "ai.toolCall.args",
@@ -38,45 +53,62 @@ const contentPrefixes = [
38
53
  function isContentKey(key) {
39
54
  return contentPrefixes.some((prefix) => key === prefix || key.startsWith(`${prefix}.`));
40
55
  }
41
- function redactValue(value, path, options, depth = 0) {
42
- if (depth > 32)
56
+ function redactValue(value, path, options, budget, depth = 0) {
57
+ if (++budget.nodes > 16384 || depth > 32)
43
58
  throw new Error("Telemetry value exceeds the supported nesting limit");
44
59
  if (typeof value === "string") {
45
60
  const result = options.redact ? options.redact(value, path) : value;
46
- if (typeof result !== "string" || !result.isWellFormed() || result.includes("\u0000"))
61
+ // JavaScript can supply an async redactor despite the synchronous contract.
62
+ // Observe its rejection before dropping the invalid record.
63
+ if (result && typeof result === "object")
64
+ void Promise.resolve(result).catch(() => { });
65
+ if (typeof result !== "string" ||
66
+ result.length > MAX_CONTENT_BYTES ||
67
+ !result.isWellFormed() ||
68
+ result.includes("\u0000"))
47
69
  throw new Error("Redaction produced unsupported text");
48
70
  if (Buffer.byteLength(result) > MAX_CONTENT_BYTES)
49
71
  throw new Error("Telemetry text exceeds 256 KiB");
72
+ budget.bytes += Buffer.byteLength(result);
73
+ if (budget.bytes > MAX_BODY_BYTES)
74
+ throw new Error("Redacted record exceeds the content budget");
50
75
  return result;
51
76
  }
52
- if (Array.isArray(value))
53
- return value.map((item, index) => redactValue(item, `${path}.${index}`, options, depth + 1));
77
+ if (Array.isArray(value)) {
78
+ if (value.length > 16384)
79
+ throw new Error("Telemetry array exceeds the complexity limit");
80
+ return value.map((item, index) => redactValue(item, `${path}.${index}`, options, budget, depth + 1));
81
+ }
54
82
  if (value instanceof Uint8Array) {
55
83
  if (value.byteLength > MAX_CONTENT_BYTES)
56
84
  throw new Error("Telemetry bytes exceed 256 KiB");
85
+ budget.bytes += value.byteLength;
86
+ if (budget.bytes > MAX_BODY_BYTES)
87
+ throw new Error("Redacted record exceeds the content budget");
57
88
  return value;
58
89
  }
59
90
  if (value !== null && typeof value === "object")
60
91
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [
61
92
  key,
62
- redactValue(item, `${path}.${key}`, options, depth + 1),
93
+ redactValue(item, `${path}.${key}`, options, budget, depth + 1),
63
94
  ]));
64
95
  return value;
65
96
  }
66
- function attributes(source, options, path) {
97
+ function attributes(source, options, path, budget) {
67
98
  return Object.fromEntries(Object.entries(source).flatMap(([key, value]) => !options.captureContent && isContentKey(key)
68
99
  ? []
69
- : [[key, redactValue(value, `${path}.${key}`, options)]]));
100
+ : [[key, redactValue(value, `${path}.${key}`, options, budget)]]));
70
101
  }
71
- function redactResource(resource, options, cache) {
102
+ function redactResource(resource, options, cache, budget) {
72
103
  let result = cache.get(resource);
73
104
  if (!result) {
74
- result = resourceFromAttributes(attributes(resource.attributes, options, "resource.attributes"), { schemaUrl: resource.schemaUrl });
105
+ result = resourceFromAttributes(attributes(resource.attributes, options, "resource.attributes", budget), { schemaUrl: resource.schemaUrl });
75
106
  cache.set(resource, result);
76
107
  }
77
108
  return result;
78
109
  }
79
110
  export function redactSpan(span, options, cache) {
111
+ const budget = { bytes: 0, nodes: 0 };
80
112
  return {
81
113
  name: span.name,
82
114
  kind: span.kind,
@@ -89,22 +121,22 @@ export function redactSpan(span, options, cache) {
89
121
  status: {
90
122
  code: span.status.code,
91
123
  ...(options.captureContent && span.status.message !== undefined
92
- ? { message: String(redactValue(span.status.message, "status.message", options)) }
124
+ ? { message: String(redactValue(span.status.message, "status.message", options, budget)) }
93
125
  : {}),
94
126
  },
95
- attributes: attributes(span.attributes, options, "attributes"),
127
+ attributes: attributes(span.attributes, options, "attributes", budget),
96
128
  events: span.events
97
129
  .filter((event) => options.captureContent ||
98
130
  !/^gen_ai\.(?:system|user|assistant|tool|choice)/.test(event.name))
99
131
  .map((event) => ({
100
132
  ...event,
101
- attributes: attributes(event.attributes ?? {}, options, `events.${event.name}`),
133
+ attributes: attributes(event.attributes ?? {}, options, `events.${event.name}`, budget),
102
134
  })),
103
135
  links: span.links.map((link) => ({
104
136
  ...link,
105
- attributes: attributes(link.attributes ?? {}, options, "links.attributes"),
137
+ attributes: attributes(link.attributes ?? {}, options, "links.attributes", budget),
106
138
  })),
107
- resource: redactResource(span.resource, options, cache),
139
+ resource: redactResource(span.resource, options, cache, budget),
108
140
  instrumentationScope: span.instrumentationScope,
109
141
  droppedAttributesCount: span.droppedAttributesCount,
110
142
  droppedEventsCount: span.droppedEventsCount,
@@ -112,7 +144,8 @@ export function redactSpan(span, options, cache) {
112
144
  };
113
145
  }
114
146
  export function redactLog(log, options, cache) {
115
- const body = options.captureContent ? redactValue(log.body, "body", options) : undefined;
147
+ const budget = { bytes: 0, nodes: 0 };
148
+ const body = options.captureContent ? redactValue(log.body, "body", options, budget) : undefined;
116
149
  if (body !== undefined && Buffer.byteLength(JSON.stringify(body)) > MAX_CONTENT_BYTES)
117
150
  throw new Error("Telemetry log body exceeds 256 KiB");
118
151
  return {
@@ -123,13 +156,13 @@ export function redactLog(log, options, cache) {
123
156
  severityNumber: log.severityNumber,
124
157
  eventName: log.eventName,
125
158
  body: body,
126
- attributes: attributes(log.attributes, options, "attributes"),
127
- resource: redactResource(log.resource, options, cache),
159
+ attributes: attributes(log.attributes, options, "attributes", budget),
160
+ resource: redactResource(log.resource, options, cache, budget),
128
161
  instrumentationScope: {
129
162
  ...log.instrumentationScope,
130
163
  ...(log.instrumentationScope.attributes
131
164
  ? {
132
- attributes: attributes(log.instrumentationScope.attributes, options, "scope.attributes"),
165
+ attributes: attributes(log.instrumentationScope.attributes, options, "scope.attributes", budget),
133
166
  }
134
167
  : {}),
135
168
  },
package/dist/receipt.d.ts CHANGED
@@ -1,8 +1,19 @@
1
1
  import type { HueOptions, TraceVerification, VerifyTraceOptions } from "./types.js";
2
+ /**
3
+ * Thrown by {@link HueClient.verifyTrace} when verification cannot proceed: denied key, unsupported
4
+ * or refusing server, unreachable network or an invalid receipt. A timeout is not an error; it
5
+ * returns `verified: false`.
6
+ */
2
7
  export declare class HueTraceVerificationError extends Error {
8
+ /** Safe failure class for branching without parsing the message. */
3
9
  readonly code: "authentication" | "http" | "invalid_response" | "transport";
10
+ /** HTTP status when Hue answered. */
4
11
  readonly status?: number | undefined;
5
- constructor(code: "authentication" | "http" | "invalid_response" | "transport", message: string, status?: number | undefined);
12
+ constructor(
13
+ /** Safe failure class for branching without parsing the message. */
14
+ code: "authentication" | "http" | "invalid_response" | "transport", message: string,
15
+ /** HTTP status when Hue answered. */
16
+ status?: number | undefined);
6
17
  }
7
18
  /** Observe persisted evidence after the application and its exporter have finished. */
8
19
  export declare function verifyTrace(connection: Pick<HueOptions, "apiKey"> & {
package/dist/receipt.js CHANGED
@@ -1,9 +1,18 @@
1
1
  const fields = ["input", "output", "model", "usage", "session"];
2
2
  const MAX_RESPONSE_BYTES = 64 * 1024;
3
+ /**
4
+ * Thrown by {@link HueClient.verifyTrace} when verification cannot proceed: denied key, unsupported
5
+ * or refusing server, unreachable network or an invalid receipt. A timeout is not an error; it
6
+ * returns `verified: false`.
7
+ */
3
8
  export class HueTraceVerificationError extends Error {
4
9
  code;
5
10
  status;
6
- constructor(code, message, status) {
11
+ constructor(
12
+ /** Safe failure class for branching without parsing the message. */
13
+ code, message,
14
+ /** HTTP status when Hue answered. */
15
+ status) {
7
16
  super(message);
8
17
  this.code = code;
9
18
  this.status = status;
@@ -0,0 +1,7 @@
1
+ import { type Span } from "@opentelemetry/api";
2
+ export declare function noopSpan(): Span;
3
+ export declare function safeSpan(source: Span, failed: () => void): Span;
4
+ /** Validate a bounded data tree without invoking toJSON or property getters. */
5
+ export declare function encodeContent(value: unknown): string;
6
+ /** Bounded conservative accounting for the record data retained by our queue, not total process RSS. */
7
+ export declare function estimateRecordBytes(value: unknown, limit: number): number;
package/dist/safety.js ADDED
@@ -0,0 +1,179 @@
1
+ import { INVALID_SPAN_CONTEXT, trace } from "@opentelemetry/api";
2
+ import { types as utilTypes } from "node:util";
3
+ import { MAX_CONTENT_BYTES } from "./config.js";
4
+ export function noopSpan() {
5
+ return trace.wrapSpanContext(INVALID_SPAN_CONTEXT);
6
+ }
7
+ /** Isolate the public Span interface without changing its fluent method contract. */
8
+ class SafeSpan {
9
+ source;
10
+ failed;
11
+ constructor(source, failed) {
12
+ this.source = source;
13
+ this.failed = failed;
14
+ }
15
+ write(work) {
16
+ try {
17
+ const result = work();
18
+ // Broken/custom providers can return rejected promises from synchronous
19
+ // OTel methods. Observe them without awaiting on application code paths.
20
+ if (result && typeof result.then === "function")
21
+ void Promise.resolve(result).catch(this.failed);
22
+ }
23
+ catch {
24
+ this.failed();
25
+ }
26
+ }
27
+ spanContext() {
28
+ try {
29
+ const ids = this.source.spanContext();
30
+ if (!ids || typeof ids.traceId !== "string" || typeof ids.spanId !== "string")
31
+ throw new TypeError("Invalid span context");
32
+ return {
33
+ traceId: ids.traceId,
34
+ spanId: ids.spanId,
35
+ traceFlags: ids.traceFlags,
36
+ isRemote: ids.isRemote,
37
+ traceState: ids.traceState,
38
+ };
39
+ }
40
+ catch {
41
+ this.failed();
42
+ return INVALID_SPAN_CONTEXT;
43
+ }
44
+ }
45
+ isRecording() {
46
+ try {
47
+ return this.source.isRecording() === true;
48
+ }
49
+ catch {
50
+ this.failed();
51
+ return false;
52
+ }
53
+ }
54
+ setAttribute(...args) {
55
+ this.write(() => this.source.setAttribute(...args));
56
+ return this;
57
+ }
58
+ setAttributes(...args) {
59
+ this.write(() => this.source.setAttributes(...args));
60
+ return this;
61
+ }
62
+ addEvent(...args) {
63
+ this.write(() => this.source.addEvent(...args));
64
+ return this;
65
+ }
66
+ addLink(...args) {
67
+ this.write(() => this.source.addLink(...args));
68
+ return this;
69
+ }
70
+ addLinks(...args) {
71
+ this.write(() => this.source.addLinks(...args));
72
+ return this;
73
+ }
74
+ setStatus(...args) {
75
+ this.write(() => this.source.setStatus(...args));
76
+ return this;
77
+ }
78
+ updateName(...args) {
79
+ this.write(() => this.source.updateName(...args));
80
+ return this;
81
+ }
82
+ end(...args) {
83
+ this.write(() => this.source.end(...args));
84
+ }
85
+ recordException(...args) {
86
+ this.write(() => this.source.recordException(...args));
87
+ }
88
+ }
89
+ export function safeSpan(source, failed) {
90
+ return new SafeSpan(source, failed);
91
+ }
92
+ /** Validate a bounded data tree without invoking toJSON or property getters. */
93
+ export function encodeContent(value) {
94
+ let nodes = 0;
95
+ let bytes = 0;
96
+ const ancestors = new Set();
97
+ const charge = (amount) => {
98
+ bytes += amount;
99
+ if (bytes > MAX_CONTENT_BYTES)
100
+ throw new RangeError("Content limit exceeded");
101
+ };
102
+ const visit = (item, depth) => {
103
+ if (++nodes > 16384 || depth > 32)
104
+ throw new RangeError("Content complexity limit exceeded");
105
+ if (typeof item === "string") {
106
+ if (item.length > MAX_CONTENT_BYTES)
107
+ throw new RangeError("Content limit exceeded");
108
+ charge(Buffer.byteLength(JSON.stringify(item)));
109
+ return item;
110
+ }
111
+ if (item === null || typeof item === "boolean" || typeof item === "number") {
112
+ if (typeof item === "number" && !Number.isFinite(item))
113
+ throw new TypeError("Invalid number");
114
+ charge(JSON.stringify(item).length);
115
+ return item;
116
+ }
117
+ if (!item || typeof item !== "object" || ancestors.has(item))
118
+ throw new TypeError("Invalid JSON");
119
+ // Even descriptor/prototype reads can execute application code on a Proxy.
120
+ // The native check also handles revoked proxies without invoking their traps.
121
+ if (utilTypes.isProxy(item))
122
+ throw new TypeError("JSON proxies are unsupported");
123
+ const array = Array.isArray(item);
124
+ if (!array && ![Object.prototype, null].includes(Object.getPrototypeOf(item)))
125
+ throw new TypeError("Expected JSON data");
126
+ ancestors.add(item);
127
+ charge(2);
128
+ const result = array ? [] : Object.create(null);
129
+ // Own descriptors avoid executing application accessors during capture.
130
+ const keys = array
131
+ ? Array.from({ length: Math.min(item.length, 16385) }, (_, i) => String(i))
132
+ : Object.keys(item);
133
+ if (keys.length > 16384)
134
+ throw new RangeError("Content complexity limit exceeded");
135
+ for (const key of keys) {
136
+ const descriptor = Object.getOwnPropertyDescriptor(item, key);
137
+ if (!descriptor || !("value" in descriptor))
138
+ throw new TypeError("Expected JSON data property");
139
+ charge(1 + (array ? 0 : Buffer.byteLength(JSON.stringify(key)) + 1));
140
+ const child = visit(descriptor.value, depth + 1);
141
+ if (array)
142
+ result.push(child);
143
+ else
144
+ result[key] = child;
145
+ }
146
+ ancestors.delete(item);
147
+ return result;
148
+ };
149
+ const encoded = JSON.stringify(visit(value, 0));
150
+ if (Buffer.byteLength(encoded) > MAX_CONTENT_BYTES)
151
+ throw new RangeError("Content limit exceeded");
152
+ return encoded;
153
+ }
154
+ /** Bounded conservative accounting for the record data retained by our queue, not total process RSS. */
155
+ export function estimateRecordBytes(value, limit) {
156
+ let bytes = 0;
157
+ let nodes = 0;
158
+ const seen = new Set();
159
+ const visit = (item, depth) => {
160
+ if (++nodes > 16384 || depth > 32)
161
+ throw new RangeError("Telemetry complexity limit exceeded");
162
+ bytes += 16;
163
+ if (typeof item === "string")
164
+ bytes += item.length * 2;
165
+ else if (item instanceof Uint8Array)
166
+ bytes += item.byteLength;
167
+ else if (item && typeof item === "object" && !seen.has(item)) {
168
+ seen.add(item);
169
+ for (const [key, child] of Object.entries(item)) {
170
+ bytes += key.length * 2 + 16;
171
+ visit(child, depth + 1);
172
+ }
173
+ }
174
+ if (bytes > limit)
175
+ throw new RangeError("Telemetry byte limit exceeded");
176
+ };
177
+ visit(value, 0);
178
+ return bytes;
179
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReadableSpan } from "@opentelemetry/sdk-trace";
2
+ import type { ReadableLogRecord, ReadWriteLogRecord } from "@opentelemetry/sdk-logs";
3
+ export declare function snapshotSpan(source: ReadableSpan, limit: number): {
4
+ record: ReadableSpan;
5
+ bytes: number;
6
+ unresolvedResource: boolean;
7
+ };
8
+ export declare function snapshotLog(source: ReadableLogRecord, limit: number): {
9
+ record: ReadWriteLogRecord;
10
+ bytes: number;
11
+ unresolvedResource: boolean;
12
+ };