@hue-run/sdk 0.1.5 → 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 (56) hide show
  1. package/ENVIRONMENTS.md +182 -0
  2. package/EVALUATIONS.md +12 -0
  3. package/README.md +194 -18
  4. package/dist/ai-sdk.d.ts +9 -1
  5. package/dist/ai-sdk.js +34 -8
  6. package/dist/client.d.ts +121 -6
  7. package/dist/client.js +329 -56
  8. package/dist/config.d.ts +11 -2
  9. package/dist/config.js +36 -7
  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 +3 -0
  42. package/dist/index.js +2 -0
  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 +16 -1
  47. package/dist/receipt.d.ts +12 -1
  48. package/dist/receipt.js +10 -1
  49. package/dist/safety.d.ts +1 -2
  50. package/dist/snapshot.js +4 -0
  51. package/dist/transport.d.ts +41 -9
  52. package/dist/transport.js +80 -22
  53. package/dist/types.d.ts +144 -8
  54. package/dist/version.d.ts +2 -0
  55. package/dist/version.js +3 -0
  56. 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
2
  import { MAX_BODY_BYTES, MAX_CONTENT_BYTES } from "./config.js";
3
- const contentPrefixes = [
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",
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;
package/dist/safety.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { type Span } from "@opentelemetry/api";
2
- import type { JsonValue } from "./types.js";
3
2
  export declare function noopSpan(): Span;
4
3
  export declare function safeSpan(source: Span, failed: () => void): Span;
5
4
  /** Validate a bounded data tree without invoking toJSON or property getters. */
6
- export declare function encodeContent(value: JsonValue): string;
5
+ export declare function encodeContent(value: unknown): string;
7
6
  /** Bounded conservative accounting for the record data retained by our queue, not total process RSS. */
8
7
  export declare function estimateRecordBytes(value: unknown, limit: number): number;
package/dist/snapshot.js CHANGED
@@ -1,7 +1,11 @@
1
1
  import { createTraceState } from "@opentelemetry/api";
2
2
  import { types as utilTypes } from "node:util";
3
3
  import { resourceFromAttributes } from "@opentelemetry/resources";
4
+ // Intrinsic accessors are captured once and invoked with an explicit receiver so a
5
+ // hostile object cannot override them; the unbound reference is the point.
6
+ // eslint-disable-next-line @typescript-eslint/unbound-method
4
7
  const typedArrayByteLength = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), "byteLength").get;
8
+ // eslint-disable-next-line @typescript-eslint/unbound-method
5
9
  const typedArraySet = Uint8Array.prototype.set;
6
10
  /** Copies only exported data, with the same finite budget used for admission. */
7
11
  class Snapshot {
@@ -1,17 +1,35 @@
1
1
  import { type ReadableSpan, type SpanProcessor } from "@opentelemetry/sdk-trace";
2
2
  import { type LogRecordProcessor, type ReadableLogRecord } from "@opentelemetry/sdk-logs";
3
3
  import { validateOptions } from "./config.js";
4
- import type { ExportIssue, ExportReport, HueOptions, Signal } from "./types.js";
5
- type RecordValue = ReadableSpan | ReadableLogRecord;
4
+ import type { ExportIssue, ExportReport, HueOptions } from "./types.js";
5
+ /** A finished span or emitted log record as the OpenTelemetry SDK hands it to a processor. */
6
+ export type RecordValue = ReadableSpan | ReadableLogRecord;
7
+ /**
8
+ * Rejection of {@link HueClient.flush} and {@link HueClient.shutdown}: telemetry was not fully
9
+ * accepted. Carries sanitized counts only, never server response text or content.
10
+ */
6
11
  export declare class HueExportError extends Error {
12
+ /** Non-warning issues observed since the failing drain began. */
7
13
  readonly issues: ExportIssue[];
14
+ /** Cumulative counters and current gauges at the time of the failure. */
8
15
  readonly report: ExportReport;
9
- constructor(issues: ExportIssue[], report: ExportReport);
16
+ constructor(
17
+ /** Non-warning issues observed since the failing drain began. */
18
+ issues: ExportIssue[],
19
+ /** Cumulative counters and current gauges at the time of the failure. */
20
+ report: ExportReport);
10
21
  }
11
- /** Owned transport components; attach processors during provider construction. */
22
+ /**
23
+ * Hue's export pipeline: OTLP/HTTP exporters behind bounded batch processors, with cumulative
24
+ * counters and a sanitized issue history. A client owns one; in attach mode the application attaches
25
+ * `spanProcessor` and `logRecordProcessor` to its own providers while constructing them.
26
+ */
12
27
  export declare class HueTransport {
28
+ /** Validated options with defaults applied; `baseUrl` is the origin. Not enumerable, so it does not leak the key when logged. */
13
29
  readonly options: ReturnType<typeof validateOptions>;
30
+ /** Span processor to attach to a tracer provider; a no-op when disabled. */
14
31
  readonly spanProcessor: SpanProcessor;
32
+ /** Log record processor to attach to a logger provider; a no-op when disabled. */
15
33
  readonly logRecordProcessor: LogRecordProcessor;
16
34
  private sequence;
17
35
  private observedSequence;
@@ -34,17 +52,31 @@ export declare class HueTransport {
34
52
  private flushPromise?;
35
53
  constructor(options: HueOptions);
36
54
  private enqueue;
37
- finish(signal: Signal, records: RecordValue[]): void;
38
- acceptedRecords(signal: Signal, count: number): void;
39
- issue(signal: Signal, kind: ExportIssue["kind"], count: number, message: string, status?: number): void;
40
- instrumentationFailure(signal?: Signal): void;
55
+ /** Cumulative counters and current queue gauges. */
41
56
  getReport(): ExportReport;
57
+ /** Copies of the latest 128 sanitized issues, oldest first. */
42
58
  getIssues(): ExportIssue[];
43
59
  /** Monotonic failure marker, retained even when the bounded issue history rolls over. */
44
60
  getFailureSequence(): number;
61
+ /**
62
+ * Waits for the processors' and exporters' in-flight work; drain the providers first.
63
+ *
64
+ * @throws HueExportError when a new non-warning issue was recorded since the previous observation.
65
+ */
45
66
  flush(): Promise<ExportReport>;
46
67
  private flushOnce;
68
+ /**
69
+ * Flushes and stops the processors; records emitted afterwards are dropped and counted. In attach
70
+ * mode call it after shutting down the application's providers.
71
+ *
72
+ * @throws HueExportError when the final flush observed new failures.
73
+ */
47
74
  shutdown(): Promise<ExportReport>;
48
75
  }
76
+ /**
77
+ * Creates the export pipeline for attach mode; pass it with the application's providers to
78
+ * {@link createHue}. Validates options like an owned client.
79
+ *
80
+ * @throws TypeError for invalid options; see {@link createHue}.
81
+ */
49
82
  export declare function createHueTransport(options: HueOptions): HueTransport;
50
- export {};
package/dist/transport.js CHANGED
@@ -1,13 +1,16 @@
1
1
  import { ExportResultCode } from "@opentelemetry/core";
2
2
  import { CompressionAlgorithm, OTLPExporterBase, OTLPExporterError, } from "@opentelemetry/otlp-exporter-base";
3
- import { convertLegacyHttpOptions, createOtlpHttpExportDelegate, } from "@opentelemetry/otlp-exporter-base/node-http";
3
+ import { createOtlpHttpExportDelegate } from "@opentelemetry/otlp-exporter-base/node-http";
4
4
  import { LogsExporterMetricsHelper, ProtobufLogsSerializer, ProtobufTraceSerializer, TraceExporterMetricsHelper, } from "@opentelemetry/otlp-transformer";
5
5
  import { BatchSpanProcessor, } from "@opentelemetry/sdk-trace";
6
6
  import { BatchLogRecordProcessor, } from "@opentelemetry/sdk-logs";
7
- import { MAX_BODY_BYTES, validateOptions } from "./config.js";
7
+ import { isInsecureOrigin, MAX_BODY_BYTES, validateOptions } from "./config.js";
8
8
  import { estimateRecordBytes } from "./safety.js";
9
9
  import { snapshotLog, snapshotSpan } from "./snapshot.js";
10
10
  import { redactLog, redactSpan } from "./privacy.js";
11
+ import { sdkVersion } from "./version.js";
12
+ /** Per-record allowance for protobuf length prefixes that grow when records are grouped. */
13
+ const RECORD_FRAMING_BYTES = 64;
11
14
  function recordData(record, signal) {
12
15
  if (signal === "traces") {
13
16
  const span = record;
@@ -31,20 +34,35 @@ function recordData(record, signal) {
31
34
  scope: log.instrumentationScope,
32
35
  };
33
36
  }
37
+ /**
38
+ * Rejection of {@link HueClient.flush} and {@link HueClient.shutdown}: telemetry was not fully
39
+ * accepted. Carries sanitized counts only, never server response text or content.
40
+ */
34
41
  export class HueExportError extends Error {
35
42
  issues;
36
43
  report;
37
- constructor(issues, report) {
44
+ constructor(
45
+ /** Non-warning issues observed since the failing drain began. */
46
+ issues,
47
+ /** Cumulative counters and current gauges at the time of the failure. */
48
+ report) {
38
49
  super("Hue could not accept all telemetry. Inspect issues and report for sanitized counts.");
39
50
  this.issues = issues;
40
51
  this.report = report;
41
52
  this.name = "HueExportError";
42
53
  }
43
54
  }
44
- /** Owned transport components; attach processors during provider construction. */
55
+ /**
56
+ * Hue's export pipeline: OTLP/HTTP exporters behind bounded batch processors, with cumulative
57
+ * counters and a sanitized issue history. A client owns one; in attach mode the application attaches
58
+ * `spanProcessor` and `logRecordProcessor` to its own providers while constructing them.
59
+ */
45
60
  export class HueTransport {
61
+ /** Validated options with defaults applied; `baseUrl` is the origin. Not enumerable, so it does not leak the key when logged. */
46
62
  options;
63
+ /** Span processor to attach to a tracer provider; a no-op when disabled. */
47
64
  spanProcessor;
65
+ /** Log record processor to attach to a logger provider; a no-op when disabled. */
48
66
  logRecordProcessor;
49
67
  sequence = 0;
50
68
  observedSequence = 0;
@@ -75,6 +93,8 @@ export class HueTransport {
75
93
  this.logRecordProcessor = { onEmit() { }, async forceFlush() { }, async shutdown() { } };
76
94
  return;
77
95
  }
96
+ if (isInsecureOrigin(this.options.baseUrl))
97
+ this.issue("traces", "warning", 0, "allowInsecureHttp is set: telemetry and the project key are sent over plain HTTP to a host that is not loopback");
78
98
  const batching = {
79
99
  maxQueueSize: 2048,
80
100
  maxExportBatchSize: 128,
@@ -159,6 +179,7 @@ export class HueTransport {
159
179
  return undefined;
160
180
  }
161
181
  }
182
+ /** @internal Exporter callback: releases queued records after an export attempt settles. */
162
183
  finish(signal, records) {
163
184
  for (const record of records) {
164
185
  const pending = signal === "traces" ? this.spans : this.logs;
@@ -166,9 +187,11 @@ export class HueTransport {
166
187
  pending.delete(record);
167
188
  }
168
189
  }
190
+ /** @internal Exporter callback: counts records the collector acknowledged. */
169
191
  acceptedRecords(signal, count) {
170
192
  this.accepted[signal] += count;
171
193
  }
194
+ /** @internal Records a sanitized issue, updates counters and rate-limits the diagnostic callback. */
172
195
  issue(signal, kind, count, message, status) {
173
196
  if (kind === "dropped")
174
197
  this.dropped[signal] += count;
@@ -195,7 +218,10 @@ export class HueTransport {
195
218
  !this.diagnosticPending &&
196
219
  Date.now() - this.lastDiagnosticAt >= 1000) {
197
220
  this.diagnosticPending = true;
198
- this.lastDiagnosticAt = Date.now();
221
+ // Warnings (for example the allowInsecureHttp notice) do not consume the slot, so the first
222
+ // real failure still reaches the callback promptly.
223
+ if (kind !== "warning")
224
+ this.lastDiagnosticAt = Date.now();
199
225
  void Promise.resolve()
200
226
  .then(() => this.options.onExportIssue?.({ ...issue }))
201
227
  .then(() => {
@@ -205,10 +231,12 @@ export class HueTransport {
205
231
  });
206
232
  }
207
233
  }
208
- instrumentationFailure(signal = "traces") {
234
+ /** @internal Counts a helper capture or instrumentation failure that preserved application execution. */
235
+ instrumentationFailure(signal = "traces", message = "Telemetry capture or instrumentation failed; application execution was preserved") {
209
236
  this.instrumentationFailures++;
210
- this.issue(signal, "invalid", 0, "Telemetry capture or instrumentation failed; application execution was preserved");
237
+ this.issue(signal, "invalid", 0, message);
211
238
  }
239
+ /** Cumulative counters and current queue gauges. */
212
240
  getReport() {
213
241
  return {
214
242
  acceptedSpans: this.accepted.traces,
@@ -225,6 +253,7 @@ export class HueTransport {
225
253
  instrumentationFailures: this.instrumentationFailures,
226
254
  };
227
255
  }
256
+ /** Copies of the latest 128 sanitized issues, oldest first. */
228
257
  getIssues() {
229
258
  return this.issues.map((issue) => ({ ...issue }));
230
259
  }
@@ -232,6 +261,11 @@ export class HueTransport {
232
261
  getFailureSequence() {
233
262
  return this.failureSequence;
234
263
  }
264
+ /**
265
+ * Waits for the processors' and exporters' in-flight work; drain the providers first.
266
+ *
267
+ * @throws HueExportError when a new non-warning issue was recorded since the previous observation.
268
+ */
235
269
  flush() {
236
270
  const from = this.observedSequence;
237
271
  const next = (this.flushPromise ?? Promise.resolve()).then(() => this.flushOnce(from), () => this.flushOnce(from));
@@ -262,6 +296,12 @@ export class HueTransport {
262
296
  throw new HueExportError(issues, report);
263
297
  return report;
264
298
  }
299
+ /**
300
+ * Flushes and stops the processors; records emitted afterwards are dropped and counted. In attach
301
+ * mode call it after shutting down the application's providers.
302
+ *
303
+ * @throws HueExportError when the final flush observed new failures.
304
+ */
265
305
  shutdown() {
266
306
  this.shutdownPromise ??= (async () => {
267
307
  this.closed = true;
@@ -341,7 +381,13 @@ class ReportingExporter {
341
381
  this.transport.issue(this.signal, "invalid", 1, "Telemetry record could not be redacted or exceeds supported content limits");
342
382
  }
343
383
  }
384
+ // Each record is encoded once to measure it; a request is encoded once more when it is sent.
385
+ // Records sharing a resource and scope are grouped on the wire, so the sum of the individual
386
+ // encodings plus a fixed framing margin bounds the request size. Room is left for gzip
387
+ // headers/blocks when otherwise incompressible data is near the wire cap.
388
+ const limit = MAX_BODY_BYTES - 1024;
344
389
  let batch = [];
390
+ let batchBytes = 0;
345
391
  for (const record of accepted) {
346
392
  let recordBytes;
347
393
  try {
@@ -352,21 +398,20 @@ class ReportingExporter {
352
398
  this.transport.issue(this.signal, "invalid", 1, "Telemetry record could not be serialized");
353
399
  continue;
354
400
  }
355
- const candidate = [...batch, record];
356
- // Leave room for gzip headers/blocks when otherwise incompressible data is near the wire cap.
357
- if ((this.serializer.serializeRequest(candidate)?.byteLength ?? 0) <= MAX_BODY_BYTES - 1024) {
358
- batch = candidate;
359
- continue;
401
+ const framedBytes = recordBytes + RECORD_FRAMING_BYTES;
402
+ if (batch.length && batchBytes + framedBytes > limit) {
403
+ if (!(await this.send(batch)))
404
+ failed = true;
405
+ batch = [];
406
+ batchBytes = 0;
360
407
  }
361
- if (batch.length && !(await this.send(batch)))
362
- failed = true;
363
- batch = [];
364
- if (recordBytes > MAX_BODY_BYTES - 1024) {
408
+ if (recordBytes > limit) {
365
409
  failed = true;
366
410
  this.transport.issue(this.signal, "invalid", 1, "Telemetry record exceeds the 1 MiB request limit");
411
+ continue;
367
412
  }
368
- else
369
- batch = [record];
413
+ batch.push(record);
414
+ batchBytes += framedBytes;
370
415
  }
371
416
  if (batch.length && !(await this.send(batch)))
372
417
  failed = true;
@@ -410,13 +455,20 @@ class ReportingExporter {
410
455
  },
411
456
  };
412
457
  const endpoint = `${options.baseUrl}/api/v1/otlp/v1/${this.signal}`;
413
- const delegate = createOtlpHttpExportDelegate(convertLegacyHttpOptions({
458
+ // Explicit configuration only. OTEL_EXPORTER_OTLP_* environment variables are meant
459
+ // for generic exporters; merging them here could send another vendor's headers to Hue.
460
+ const delegate = createOtlpHttpExportDelegate({
414
461
  url: endpoint,
415
- headers: { Authorization: `Bearer ${options.apiKey}` },
462
+ headers: async () => ({
463
+ "Content-Type": "application/x-protobuf",
464
+ Authorization: `Bearer ${options.apiKey}`,
465
+ }),
466
+ // The transport prefixes this to OpenTelemetry's own User-Agent token.
467
+ userAgent: `hue-sdk-typescript/${sdkVersion}`,
416
468
  timeoutMillis: options.timeoutMillis,
417
469
  concurrencyLimit: 1,
418
470
  compression: CompressionAlgorithm.GZIP,
419
- httpAgentOptions: async (protocol) => {
471
+ agentFactory: async (protocol) => {
420
472
  if (expired || Date.now() >= deadline)
421
473
  throw new Error("Hue export deadline exceeded");
422
474
  const { Agent } = await import(protocol === "https:" ? "node:https" : "node:http");
@@ -426,7 +478,7 @@ class ReportingExporter {
426
478
  agent.destroy();
427
479
  return agent;
428
480
  },
429
- }, this.signal === "traces" ? "TRACES" : "LOGS", `v1/${this.signal}`, { "Content-Type": "application/x-protobuf" }), serializer, this.signal === "traces" ? "otlp_http_span_exporter" : "otlp_http_log_exporter", this.metrics, undefined);
481
+ }, serializer, this.signal === "traces" ? "otlp_http_span_exporter" : "otlp_http_log_exporter", this.metrics, undefined);
430
482
  const exporter = new OTLPExporterBase(delegate);
431
483
  try {
432
484
  const result = await new Promise((resolve) => {
@@ -475,6 +527,12 @@ class ReportingExporter {
475
527
  await this.forceFlush();
476
528
  }
477
529
  }
530
+ /**
531
+ * Creates the export pipeline for attach mode; pass it with the application's providers to
532
+ * {@link createHue}. Validates options like an owned client.
533
+ *
534
+ * @throws TypeError for invalid options; see {@link createHue}.
535
+ */
478
536
  export function createHueTransport(options) {
479
537
  return new HueTransport(options);
480
538
  }