@hue-run/sdk 0.1.5 → 0.2.1
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/ENVIRONMENTS.md +182 -0
- package/EVALUATIONS.md +12 -0
- package/README.md +192 -18
- package/dist/ai-sdk.d.ts +9 -1
- package/dist/ai-sdk.js +34 -8
- package/dist/client.d.ts +121 -6
- package/dist/client.js +329 -56
- package/dist/config.d.ts +11 -2
- package/dist/config.js +36 -7
- package/dist/environment/client.d.ts +73 -0
- package/dist/environment/client.js +209 -0
- package/dist/environment/tools.d.ts +30 -0
- package/dist/environment/tools.js +24 -0
- package/dist/environment/types.d.ts +429 -0
- package/dist/environment/types.js +1 -0
- package/dist/environment.d.ts +5 -0
- package/dist/environment.js +2 -0
- package/dist/evals/attempt.d.ts +454 -0
- package/dist/evals/attempt.js +687 -0
- package/dist/evals/client.d.ts +99 -5
- package/dist/evals/client.js +136 -7
- package/dist/evals/environment-evidence.d.ts +6 -0
- package/dist/evals/environment-evidence.js +123 -0
- package/dist/evals/environment-json.d.ts +3 -0
- package/dist/evals/environment-json.js +76 -0
- package/dist/evals/json.d.ts +9 -1
- package/dist/evals/json.js +14 -6
- package/dist/evals/runner.d.ts +61 -2
- package/dist/evals/runner.js +71 -9
- package/dist/evals/scorer-publication.d.ts +2 -0
- package/dist/evals/scorer-publication.js +84 -0
- package/dist/evals/scorers.d.ts +11 -0
- package/dist/evals/scorers.js +56 -5
- package/dist/evals/simulation.d.ts +184 -0
- package/dist/evals/simulation.js +603 -0
- package/dist/evals/types.d.ts +304 -0
- package/dist/evals.d.ts +5 -1
- package/dist/evals.js +3 -1
- package/dist/experimental-telemetry.d.ts +8 -0
- package/dist/experimental-telemetry.js +13 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/managed.d.ts +51 -1
- package/dist/managed.js +11 -1
- package/dist/privacy.d.ts +2 -0
- package/dist/privacy.js +16 -1
- package/dist/receipt.d.ts +12 -1
- package/dist/receipt.js +10 -1
- package/dist/safety.d.ts +1 -2
- package/dist/snapshot.js +4 -0
- package/dist/transport.d.ts +41 -9
- package/dist/transport.js +80 -22
- package/dist/types.d.ts +144 -8
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- 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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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 {
|
package/dist/transport.d.ts
CHANGED
|
@@ -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
|
|
5
|
-
|
|
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(
|
|
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
|
-
/**
|
|
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
|
-
|
|
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 {
|
|
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(
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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 (
|
|
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
|
-
|
|
369
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
}
|