@hue-run/sdk 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLI.md +100 -29
- package/ENVIRONMENTS.md +73 -8
- package/EVALUATIONS.md +39 -19
- package/README.md +51 -22
- package/dist/cli/eval-direct.d.ts +12 -1
- package/dist/cli/eval-direct.js +145 -33
- package/dist/cli/eval.d.ts +28 -1
- package/dist/cli/eval.js +469 -72
- package/dist/client.js +10 -20
- package/dist/environment/client.d.ts +10 -2
- package/dist/environment/client.js +43 -12
- package/dist/environment/types.d.ts +30 -10
- package/dist/environment.d.ts +1 -1
- package/dist/environment.js +1 -1
- package/dist/evals/checkpoint.d.ts +15 -1
- package/dist/evals/checkpoint.js +34 -3
- package/dist/evals/client.d.ts +19 -3
- package/dist/evals/client.js +82 -34
- package/dist/evals/environment-target.d.ts +23 -3
- package/dist/evals/environment-target.js +73 -10
- package/dist/evals/exit-cleanup.d.ts +4 -0
- package/dist/evals/exit-cleanup.js +26 -0
- package/dist/evals/files.d.ts +59 -1
- package/dist/evals/files.js +187 -27
- package/dist/evals/local-worker.d.ts +21 -3
- package/dist/evals/local-worker.js +14 -0
- package/dist/evals/runner.d.ts +45 -0
- package/dist/evals/runner.js +164 -13
- package/dist/evals/scenarios.d.ts +8 -6
- package/dist/evals/scenarios.js +75 -15
- package/dist/evals/simulation.d.ts +17 -4
- package/dist/evals/simulation.js +8 -0
- package/dist/evals/types.d.ts +10 -1
- package/dist/evals/verdicts.d.ts +15 -2
- package/dist/evals/verdicts.js +43 -9
- package/dist/evals.d.ts +4 -3
- package/dist/evals.js +2 -2
- package/dist/inline-files.d.ts +4 -0
- package/dist/inline-files.js +40 -17
- package/dist/provider-tools.js +30 -3
- package/dist/snapshot.d.ts +2 -2
- package/dist/snapshot.js +47 -12
- package/dist/transport.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/evals/verdicts.d.ts
CHANGED
|
@@ -35,6 +35,11 @@ export interface VerdictResult {
|
|
|
35
35
|
explanation: string | null;
|
|
36
36
|
/** Scorer failure for `state: "error"`, otherwise `null`. */
|
|
37
37
|
error: TypedError | null;
|
|
38
|
+
/** Whether Hue recorded that the evaluator does not apply to the case; such a result counts as
|
|
39
|
+
* neither a pass nor a failure. Absent means false. */
|
|
40
|
+
notApplicable?: boolean;
|
|
41
|
+
/** What a not-applicable evaluator needs that the case lacks, such as `outcome_criteria`. */
|
|
42
|
+
requires?: string;
|
|
38
43
|
}
|
|
39
44
|
/** Outcome of {@link waitForResults}. */
|
|
40
45
|
export interface VerdictResults {
|
|
@@ -65,10 +70,15 @@ export interface CaseVerdict {
|
|
|
65
70
|
externalKey: string;
|
|
66
71
|
/** Subject scored for the case, or `null` before completion. */
|
|
67
72
|
subjectId: string | null;
|
|
68
|
-
/** `passed` and `failed` are scored verdicts; `error`, `skipped` and `pending` carry no verdict.
|
|
73
|
+
/** `passed` and `failed` are scored verdicts; `error`, `skipped` and `pending` carry no verdict.
|
|
74
|
+
* Only the evaluators that apply to the case decide it; one where none applies is an error. */
|
|
69
75
|
state: "passed" | "failed" | "error" | "skipped" | "pending";
|
|
70
|
-
/** True only for `state: "passed"`: every scored metric
|
|
76
|
+
/** True only for `state: "passed"`: every scored metric of an applicable pin passed and no
|
|
77
|
+
* applicable pin errored or is missing. */
|
|
71
78
|
passed: boolean;
|
|
79
|
+
/** Pinned scorer versions Hue recorded as not applicable to the case; always set by
|
|
80
|
+
* {@link summarizeVerdicts}. */
|
|
81
|
+
notApplicable?: string[];
|
|
72
82
|
/** Reported metrics across pinned scorers, in result order. */
|
|
73
83
|
metrics: (Metric & {
|
|
74
84
|
/** Scorer version that reported the metric. */
|
|
@@ -97,6 +107,9 @@ export interface VerdictSummary {
|
|
|
97
107
|
skipped: number;
|
|
98
108
|
/** Cases still missing a result. */
|
|
99
109
|
pending: number;
|
|
110
|
+
/** Results of evaluators that do not apply to their case, a case can have several; always
|
|
111
|
+
* set by {@link summarizeVerdicts}. */
|
|
112
|
+
notApplicable?: number;
|
|
100
113
|
};
|
|
101
114
|
}
|
|
102
115
|
/** Whether one reported metric counts as passing: an explicit `passed`, else a true boolean. */
|
package/dist/evals/verdicts.js
CHANGED
|
@@ -66,6 +66,10 @@ export async function waitForResults(client, options) {
|
|
|
66
66
|
if (!id)
|
|
67
67
|
continue;
|
|
68
68
|
const stored = await client.getResult(id);
|
|
69
|
+
// Only Hue's own flag on a skipped result marks it not applicable; the evidence alone
|
|
70
|
+
// never does, and a scored or errored result keeps its verdict whatever it carries.
|
|
71
|
+
const notApplicable = stored.state === "skipped" && stored.notApplicable === true;
|
|
72
|
+
const evidence = stored.evidence;
|
|
69
73
|
results.push({
|
|
70
74
|
id: stored.id,
|
|
71
75
|
itemId: stored.itemId,
|
|
@@ -75,6 +79,10 @@ export async function waitForResults(client, options) {
|
|
|
75
79
|
metrics: stored.state === "scored" ? stored.metrics : [],
|
|
76
80
|
explanation: stored.explanation ?? null,
|
|
77
81
|
error: stored.error ?? null,
|
|
82
|
+
notApplicable,
|
|
83
|
+
...(notApplicable && typeof evidence?.requires === "string"
|
|
84
|
+
? { requires: evidence.requires }
|
|
85
|
+
: {}),
|
|
78
86
|
});
|
|
79
87
|
}
|
|
80
88
|
return { complete, items, results };
|
|
@@ -105,11 +113,16 @@ export function summarizeVerdicts(results, options) {
|
|
|
105
113
|
const subjectId = subjectByCase.get(item.id) ?? null;
|
|
106
114
|
const own = subjectId ? results.results.filter((result) => result.subjectId === subjectId) : [];
|
|
107
115
|
const metrics = own.flatMap((result) => result.metrics.map((metric) => ({ ...metric, scorerVersionId: result.scorerVersionId })));
|
|
108
|
-
|
|
109
|
-
const
|
|
116
|
+
// An evaluator that does not apply to the case neither passes nor fails it.
|
|
117
|
+
const skippedAsInapplicable = (result) => result.state === "skipped" && result.notApplicable === true;
|
|
118
|
+
const inapplicable = own.filter(skippedAsInapplicable);
|
|
119
|
+
const applicable = own.filter((result) => !skippedAsInapplicable(result));
|
|
120
|
+
const errors = applicable.filter((result) => result.state === "error");
|
|
121
|
+
const scored = applicable.filter((result) => result.state === "scored");
|
|
110
122
|
const failing = scored.filter((result) => !result.metrics.every(metricPassed));
|
|
111
123
|
const missing = [...pins].some((pin) => !own.some((result) => result.scorerVersionId === pin));
|
|
112
|
-
const
|
|
124
|
+
const noneApplies = !missing && own.length > 0 && !applicable.length;
|
|
125
|
+
const state = errors.length || noneApplies
|
|
113
126
|
? "error"
|
|
114
127
|
: failing.length
|
|
115
128
|
? "failed"
|
|
@@ -118,23 +131,44 @@ export function summarizeVerdicts(results, options) {
|
|
|
118
131
|
: scored.length
|
|
119
132
|
? "passed"
|
|
120
133
|
: "skipped";
|
|
134
|
+
const requires = [
|
|
135
|
+
...new Set(inapplicable.flatMap((result) => (result.requires ? [result.requires] : []))),
|
|
136
|
+
];
|
|
121
137
|
return {
|
|
122
138
|
caseId: item.id,
|
|
123
139
|
externalKey: item.externalKey,
|
|
124
140
|
subjectId,
|
|
125
141
|
state,
|
|
126
142
|
passed: state === "passed",
|
|
143
|
+
notApplicable: inapplicable.map((result) => result.scorerVersionId),
|
|
127
144
|
metrics,
|
|
128
|
-
explanations:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
explanations: [
|
|
146
|
+
...(noneApplies
|
|
147
|
+
? [
|
|
148
|
+
`No pinned evaluator applies to this case${requires.length ? ` (they need ${requires.join(" or ")})` : ""}; pin one that grades it`,
|
|
149
|
+
]
|
|
150
|
+
: []),
|
|
151
|
+
...applicable
|
|
152
|
+
.filter((result) => result.state !== "scored" || failing.includes(result))
|
|
153
|
+
.map((result) => result.explanation)
|
|
154
|
+
.filter((explanation) => !!explanation),
|
|
155
|
+
],
|
|
132
156
|
errors: errors.map((result) => result.error?.type ?? "ScorerError"),
|
|
133
157
|
};
|
|
134
158
|
});
|
|
135
|
-
const totals = {
|
|
136
|
-
|
|
159
|
+
const totals = {
|
|
160
|
+
cases: cases.length,
|
|
161
|
+
passed: 0,
|
|
162
|
+
failed: 0,
|
|
163
|
+
error: 0,
|
|
164
|
+
skipped: 0,
|
|
165
|
+
pending: 0,
|
|
166
|
+
notApplicable: 0,
|
|
167
|
+
};
|
|
168
|
+
for (const item of cases) {
|
|
137
169
|
totals[item.state]++;
|
|
170
|
+
totals.notApplicable += item.notApplicable?.length ?? 0;
|
|
171
|
+
}
|
|
138
172
|
return { cases, totals };
|
|
139
173
|
}
|
|
140
174
|
/** Diffs the current summary against a baseline by case key. */
|
package/dist/evals.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export { createEvaluationClient, EvaluationClient, HueApiError } from "./evals/client.js";
|
|
1
|
+
export { ArtifactSizeError, createEvaluationClient, EvaluationClient, HueApiError, } from "./evals/client.js";
|
|
2
2
|
export type { EvaluationClientOptions } from "./evals/client.js";
|
|
3
3
|
export { runExperiment, rescore, UncertainExecutionError, OutcomeSerializationError, TargetCancelledError, TargetOutcomeUncertainError, } from "./evals/runner.js";
|
|
4
|
-
export type { RunExperimentOptions, RunExperimentTargetContext, RescoreOptions, RunnerReport, } from "./evals/runner.js";
|
|
4
|
+
export type { RunExperimentOptions, RunExperimentTargetContext, RescoreOptions, RunnerReport, TelemetryIssueCount, TelemetryNotAccepted, } from "./evals/runner.js";
|
|
5
5
|
export { TargetResult, withFiles } from "./evals/types.js";
|
|
6
|
-
export { outputContentTypes, outputFileLimits, OutputFileError, safeFilename, targetFileRoles, } from "./evals/files.js";
|
|
6
|
+
export { CaseFileError, outputContentTypes, outputFileLimits, OutputFileError, safeFilename, targetFileRoles, } from "./evals/files.js";
|
|
7
|
+
export type { CaseFileErrorCode } from "./evals/files.js";
|
|
7
8
|
export { runSimulation } from "./evals/simulation.js";
|
|
8
9
|
export type { RepositorySimulationCase, RepositorySimulationScorer, RunSimulationOptions, SimulationDefinition, SimulationProgress, SimulationReport, SimulationScenario, SimulationTargetContext, } from "./evals/simulation.js";
|
|
9
10
|
export { actualAgentManifestV2, agentManifestDigestV2, attemptBaselineV2, attemptBindingRead, attemptConnectionBundleV2, attemptIdentityV2, dependencyManifestV2, dependencyProviderV2, expectedAgentManifestV2, executionManifestDigestV2, parityEvidenceV2, preflightFindingV2, preflightReportV2, prepareAttemptInputV2, projectMcpConnectionV2, secretFreeBindingV2, surfaceBindingV2, } from "./evals/attempt.js";
|
package/dist/evals.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { createEvaluationClient, EvaluationClient, HueApiError } from "./evals/client.js";
|
|
1
|
+
export { ArtifactSizeError, createEvaluationClient, EvaluationClient, HueApiError, } from "./evals/client.js";
|
|
2
2
|
export { runExperiment, rescore, UncertainExecutionError, OutcomeSerializationError, TargetCancelledError, TargetOutcomeUncertainError, } from "./evals/runner.js";
|
|
3
3
|
export { TargetResult, withFiles } from "./evals/types.js";
|
|
4
|
-
export { outputContentTypes, outputFileLimits, OutputFileError, safeFilename, targetFileRoles, } from "./evals/files.js";
|
|
4
|
+
export { CaseFileError, outputContentTypes, outputFileLimits, OutputFileError, safeFilename, targetFileRoles, } from "./evals/files.js";
|
|
5
5
|
export { runSimulation } from "./evals/simulation.js";
|
|
6
6
|
export { actualAgentManifestV2, agentManifestDigestV2, attemptBaselineV2, attemptBindingRead, attemptConnectionBundleV2, attemptIdentityV2, dependencyManifestV2, dependencyProviderV2, expectedAgentManifestV2, executionManifestDigestV2, parityEvidenceV2, preflightFindingV2, preflightReportV2, prepareAttemptInputV2, projectMcpConnectionV2, secretFreeBindingV2, surfaceBindingV2, } from "./evals/attempt.js";
|
|
7
7
|
export { builtins, defineLocalScorer, scoreLocally } from "./evals/scorers.js";
|
package/dist/inline-files.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/** Inline file content larger than this many UTF-8 bytes is exported as its digest instead. */
|
|
2
2
|
export declare const INLINE_FILE_LIMIT: number;
|
|
3
|
+
/** Message text one record inspects for inline files in all, in UTF-16 code units. */
|
|
4
|
+
export declare const INLINE_FILE_TEXT_PER_RECORD: number;
|
|
5
|
+
/** Whether an attribute is one of the recorded message attributes that can inline files. */
|
|
6
|
+
export declare function isMessageKey(key: string): boolean;
|
|
3
7
|
/**
|
|
4
8
|
* Replaces inline files longer than {@link INLINE_FILE_LIMIT} in a recorded message attribute
|
|
5
9
|
* with their SHA-256 and byte size, so a span that inlines a large file exports the file's
|
package/dist/inline-files.js
CHANGED
|
@@ -3,32 +3,56 @@ import { MAX_BODY_BYTES } from "./config.js";
|
|
|
3
3
|
/** Inline file content larger than this many UTF-8 bytes is exported as its digest instead. */
|
|
4
4
|
export const INLINE_FILE_LIMIT = 64 * 1024;
|
|
5
5
|
const MAX_INLINE_FILE_TEXT = 8 * MAX_BODY_BYTES;
|
|
6
|
+
/** Message text one record inspects for inline files in all, in UTF-16 code units. */
|
|
7
|
+
export const INLINE_FILE_TEXT_PER_RECORD = 2 * MAX_INLINE_FILE_TEXT;
|
|
6
8
|
/** Message attributes whose JSON can inline files: GenAI blob parts and AI SDK 6 file parts. */
|
|
7
9
|
const messageKeys = new Set([
|
|
8
10
|
"gen_ai.input.messages",
|
|
9
11
|
"gen_ai.output.messages",
|
|
10
12
|
"ai.prompt.messages",
|
|
11
13
|
]);
|
|
14
|
+
/** Whether an attribute is one of the recorded message attributes that can inline files. */
|
|
15
|
+
export function isMessageKey(key) {
|
|
16
|
+
return messageKeys.has(key);
|
|
17
|
+
}
|
|
12
18
|
/** Strict base64: alphabet characters only, padded to a multiple of four. */
|
|
13
19
|
const base64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
/** An RFC 2397 `data:` URL's media type and parameters, up to the comma before its data. */
|
|
21
|
+
const dataUrl = /^data:([^;,]*)((?:;[^;,]*)*),/;
|
|
22
|
+
const hexPair = /^[0-9A-Fa-f]{2}$/;
|
|
23
|
+
/** RFC 2397 data without `;base64`: percent-escaped octets, other characters as UTF-8. Undefined
|
|
24
|
+
* when a `%` does not start an escape. */
|
|
25
|
+
function percentDecoded(payload) {
|
|
26
|
+
// Escapes only shrink the text, so its UTF-8 length bounds the bytes.
|
|
27
|
+
const bytes = Buffer.alloc(Buffer.byteLength(payload, "utf8"));
|
|
28
|
+
let length = 0;
|
|
29
|
+
let start = 0;
|
|
30
|
+
for (let index = payload.indexOf("%"); index !== -1; index = payload.indexOf("%", start)) {
|
|
31
|
+
const hex = payload.slice(index + 1, index + 3);
|
|
32
|
+
if (!hexPair.test(hex))
|
|
33
|
+
return undefined;
|
|
34
|
+
length += bytes.write(payload.slice(start, index), length, "utf8");
|
|
35
|
+
bytes[length++] = Number.parseInt(hex, 16);
|
|
36
|
+
start = index + 3;
|
|
37
|
+
}
|
|
38
|
+
length += bytes.write(payload.slice(start), length, "utf8");
|
|
39
|
+
return bytes.subarray(0, length);
|
|
19
40
|
}
|
|
20
41
|
/**
|
|
21
|
-
* The bytes an inline file part carries
|
|
22
|
-
*
|
|
42
|
+
* The bytes an inline file part carries, the file's own bytes whatever its media type: a `data:`
|
|
43
|
+
* URL decoded by its own encoding (base64 with `;base64`, percent-escapes otherwise), content in
|
|
44
|
+
* the base64 alphabet decoded, and anything else, such as a text file's own text, as UTF-8. A
|
|
45
|
+
* `data:` URL whose data does not decode is taken as UTF-8 too.
|
|
23
46
|
*/
|
|
24
|
-
function fileBytes(content
|
|
25
|
-
const
|
|
26
|
-
const payload = content.slice(
|
|
27
|
-
|
|
28
|
-
payload
|
|
29
|
-
base64.test(payload)
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
function fileBytes(content) {
|
|
48
|
+
const url = dataUrl.exec(content);
|
|
49
|
+
const payload = url ? content.slice(url[0].length) : content;
|
|
50
|
+
const decoded = url && !url[2].split(";").includes("base64")
|
|
51
|
+
? percentDecoded(payload)
|
|
52
|
+
: payload.length % 4 === 0 && base64.test(payload)
|
|
53
|
+
? Buffer.from(payload, "base64")
|
|
54
|
+
: undefined;
|
|
55
|
+
return decoded ?? Buffer.from(content, "utf8");
|
|
32
56
|
}
|
|
33
57
|
/** The key holding a part's inline content: GenAI `blob` parts and AI SDK 6 `file` parts. */
|
|
34
58
|
function contentKey(part) {
|
|
@@ -44,9 +68,8 @@ function hashNode(value, state, depth) {
|
|
|
44
68
|
const part = value;
|
|
45
69
|
const key = contentKey(part);
|
|
46
70
|
const inline = key === undefined ? undefined : part[key];
|
|
47
|
-
const mimeType = part.mime_type ?? part.mediaType;
|
|
48
71
|
if (key !== undefined && typeof inline === "string") {
|
|
49
|
-
const bytes = fileBytes(inline
|
|
72
|
+
const bytes = fileBytes(inline);
|
|
50
73
|
if (bytes.byteLength > INLINE_FILE_LIMIT) {
|
|
51
74
|
const { [key]: _omitted, ...rest } = part;
|
|
52
75
|
state.changed = true;
|
package/dist/provider-tools.js
CHANGED
|
@@ -5,6 +5,30 @@ const MAX_PROVIDER_SERVERS = 512;
|
|
|
5
5
|
function isItem(value) {
|
|
6
6
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
7
|
}
|
|
8
|
+
/** Only provider-executed tool items contribute to invalid-item diagnostics when a response is
|
|
9
|
+
* truncated; messages and reasoning are harmless response content. */
|
|
10
|
+
function isProviderToolItem(provider, value) {
|
|
11
|
+
if (!isItem(value) || typeof value.type !== "string")
|
|
12
|
+
return false;
|
|
13
|
+
if (provider === "openai")
|
|
14
|
+
return (value.type === "mcp_call" ||
|
|
15
|
+
value.type === "mcp_list_tools" ||
|
|
16
|
+
value.type === "web_search_call" ||
|
|
17
|
+
value.type === "file_search_call" ||
|
|
18
|
+
value.type === "code_interpreter_call");
|
|
19
|
+
return value.type === "mcp_tool_use" || value.type === "server_tool_use";
|
|
20
|
+
}
|
|
21
|
+
function countProviderToolItems(provider, items, start = 0) {
|
|
22
|
+
let count = 0;
|
|
23
|
+
for (const key of Object.keys(items)) {
|
|
24
|
+
const index = Number(key);
|
|
25
|
+
if (!Number.isInteger(index) || index < start || index >= items.length || String(index) !== key)
|
|
26
|
+
continue;
|
|
27
|
+
if (isProviderToolItem(provider, items[index]))
|
|
28
|
+
count++;
|
|
29
|
+
}
|
|
30
|
+
return count;
|
|
31
|
+
}
|
|
8
32
|
function text(value) {
|
|
9
33
|
return typeof value === "string" &&
|
|
10
34
|
value.trim() !== "" &&
|
|
@@ -14,6 +38,9 @@ function text(value) {
|
|
|
14
38
|
? value
|
|
15
39
|
: undefined;
|
|
16
40
|
}
|
|
41
|
+
function errorCode(value) {
|
|
42
|
+
return typeof value === "string" && /^[a-z0-9_]{1,64}$/.test(value) ? value : "error";
|
|
43
|
+
}
|
|
17
44
|
/** MCP arguments arrive as a JSON string; record the structure when it parses, else the text. */
|
|
18
45
|
function jsonArguments(value) {
|
|
19
46
|
if (typeof value !== "string")
|
|
@@ -30,7 +57,7 @@ function jsonArguments(value) {
|
|
|
30
57
|
/** OpenAI Responses `output` items. Built-in tools are named by their kind; MCP calls by tool. */
|
|
31
58
|
function openaiCalls(items, activity) {
|
|
32
59
|
const count = Math.min(items.length, MAX_PROVIDER_ITEMS);
|
|
33
|
-
activity.skipped += items
|
|
60
|
+
activity.skipped += countProviderToolItems("openai", items, count);
|
|
34
61
|
for (let index = 0; index < count; index++) {
|
|
35
62
|
const item = items[index];
|
|
36
63
|
if (!isItem(item))
|
|
@@ -121,7 +148,7 @@ function anthropicCalls(blocks, activity) {
|
|
|
121
148
|
const results = new Map();
|
|
122
149
|
const count = Math.min(blocks.length, MAX_PROVIDER_ITEMS);
|
|
123
150
|
const truncated = blocks.length > MAX_PROVIDER_ITEMS;
|
|
124
|
-
activity.skipped += blocks
|
|
151
|
+
activity.skipped += countProviderToolItems("anthropic", blocks, count);
|
|
125
152
|
for (let index = 0; index < count; index++) {
|
|
126
153
|
const block = blocks[index];
|
|
127
154
|
if (isItem(block) &&
|
|
@@ -152,7 +179,7 @@ function anthropicCalls(blocks, activity) {
|
|
|
152
179
|
if (result?.is_error === true)
|
|
153
180
|
errorType = "mcp_error";
|
|
154
181
|
else if (isItem(content) && typeof content.type === "string" && content.type.endsWith("_error"))
|
|
155
|
-
errorType =
|
|
182
|
+
errorType = errorCode(content.error_code);
|
|
156
183
|
activity.calls.push({
|
|
157
184
|
name,
|
|
158
185
|
callId,
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ReadableSpan } from "@opentelemetry/sdk-trace";
|
|
2
2
|
import type { ReadableLogRecord, ReadWriteLogRecord } from "@opentelemetry/sdk-logs";
|
|
3
|
-
export declare function snapshotSpan(source: ReadableSpan, limit: number): {
|
|
3
|
+
export declare function snapshotSpan(source: ReadableSpan, limit: number, captureContent?: boolean): {
|
|
4
4
|
record: ReadableSpan;
|
|
5
5
|
bytes: number;
|
|
6
6
|
unresolvedResource: boolean;
|
|
7
7
|
};
|
|
8
|
-
export declare function snapshotLog(source: ReadableLogRecord, limit: number): {
|
|
8
|
+
export declare function snapshotLog(source: ReadableLogRecord, limit: number, captureContent?: boolean): {
|
|
9
9
|
record: ReadWriteLogRecord;
|
|
10
10
|
bytes: number;
|
|
11
11
|
unresolvedResource: boolean;
|
package/dist/snapshot.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createTraceState } from "@opentelemetry/api";
|
|
2
2
|
import { types as utilTypes } from "node:util";
|
|
3
3
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
4
|
+
import { hashInlineFiles, INLINE_FILE_LIMIT, INLINE_FILE_TEXT_PER_RECORD, isMessageKey, } from "./inline-files.js";
|
|
4
5
|
// Intrinsic accessors are captured once and invoked with an explicit receiver so a
|
|
5
6
|
// hostile object cannot override them; the unbound reference is the point.
|
|
6
7
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
@@ -10,20 +11,52 @@ const typedArraySet = Uint8Array.prototype.set;
|
|
|
10
11
|
/** Copies only exported data, with the same finite budget used for admission. */
|
|
11
12
|
class Snapshot {
|
|
12
13
|
limit;
|
|
14
|
+
captureContent;
|
|
13
15
|
bytes = 512;
|
|
14
16
|
unresolvedResource = false;
|
|
15
17
|
nodes = 0;
|
|
16
18
|
ancestors = new Set();
|
|
17
19
|
copied = new Map();
|
|
18
|
-
|
|
20
|
+
hashed = new Map();
|
|
21
|
+
inspected = 0;
|
|
22
|
+
/** In metadata-only mode the recorded messages, which export strips, are not copied at all. */
|
|
23
|
+
constructor(limit, captureContent = true) {
|
|
19
24
|
this.limit = limit;
|
|
25
|
+
this.captureContent = captureContent;
|
|
20
26
|
}
|
|
21
27
|
charge(bytes) {
|
|
22
28
|
this.bytes += bytes;
|
|
23
29
|
if (this.bytes > this.limit)
|
|
24
30
|
throw new RangeError("Telemetry byte budget exceeded");
|
|
25
31
|
}
|
|
26
|
-
|
|
32
|
+
/**
|
|
33
|
+
* An attribute map. A large inline file in a message attribute shrinks to its digest before it
|
|
34
|
+
* is charged: export replaces it anyway, and charging the file could drop the whole record.
|
|
35
|
+
*/
|
|
36
|
+
attributes(value) {
|
|
37
|
+
return this.copy(value, 1, "attributes");
|
|
38
|
+
}
|
|
39
|
+
/** A message attribute with its large inline files hashed. The same text is hashed once per
|
|
40
|
+
* record, and a record inspects at most {@link INLINE_FILE_TEXT_PER_RECORD} of it in all;
|
|
41
|
+
* beyond that, messages are charged as recorded. */
|
|
42
|
+
inlineFiles(key, value) {
|
|
43
|
+
// A UTF-16 unit is at most three UTF-8 bytes, so shorter text cannot hold a file to hash.
|
|
44
|
+
if (typeof value !== "string" || !isMessageKey(key) || value.length * 3 <= INLINE_FILE_LIMIT)
|
|
45
|
+
return value;
|
|
46
|
+
if (this.hashed.has(value))
|
|
47
|
+
return this.hashed.get(value);
|
|
48
|
+
if (this.inspected + value.length > INLINE_FILE_TEXT_PER_RECORD)
|
|
49
|
+
return value;
|
|
50
|
+
this.inspected += value.length;
|
|
51
|
+
const result = hashInlineFiles(key, value);
|
|
52
|
+
this.hashed.set(value, result);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
/** Span events, whose attributes are attribute maps. */
|
|
56
|
+
events(value) {
|
|
57
|
+
return this.copy(value, 1, "events");
|
|
58
|
+
}
|
|
59
|
+
copy(value, depth = 0, shape = "value") {
|
|
27
60
|
if (++this.nodes > 16384 || depth > 32)
|
|
28
61
|
throw new RangeError("Telemetry complexity limit exceeded");
|
|
29
62
|
this.charge(16);
|
|
@@ -69,7 +102,7 @@ class Snapshot {
|
|
|
69
102
|
const descriptor = Object.getOwnPropertyDescriptor(value, index);
|
|
70
103
|
if (descriptor && !("value" in descriptor))
|
|
71
104
|
throw new TypeError("Telemetry accessors are unsupported");
|
|
72
|
-
copy.push(this.copy(descriptor?.value, depth + 1));
|
|
105
|
+
copy.push(this.copy(descriptor?.value, depth + 1, shape === "events" ? "event" : "value"));
|
|
73
106
|
}
|
|
74
107
|
}
|
|
75
108
|
else {
|
|
@@ -79,8 +112,10 @@ class Snapshot {
|
|
|
79
112
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
80
113
|
if (!descriptor || !("value" in descriptor))
|
|
81
114
|
throw new TypeError("Telemetry accessors are unsupported");
|
|
115
|
+
if (shape === "attributes" && !this.captureContent && isMessageKey(key))
|
|
116
|
+
continue;
|
|
82
117
|
this.charge(key.length * 2 + 16);
|
|
83
|
-
copy[key] = this.copy(descriptor.value, depth + 1);
|
|
118
|
+
copy[key] = this.copy(shape === "attributes" ? this.inlineFiles(key, descriptor.value) : descriptor.value, depth + 1, shape === "event" && key === "attributes" ? "attributes" : "value");
|
|
84
119
|
}
|
|
85
120
|
}
|
|
86
121
|
this.ancestors.delete(value);
|
|
@@ -126,14 +161,14 @@ class Snapshot {
|
|
|
126
161
|
function contextReader(context) {
|
|
127
162
|
return () => context;
|
|
128
163
|
}
|
|
129
|
-
export function snapshotSpan(source, limit) {
|
|
130
|
-
const snapshot = new Snapshot(limit);
|
|
164
|
+
export function snapshotSpan(source, limit, captureContent = true) {
|
|
165
|
+
const snapshot = new Snapshot(limit, captureContent);
|
|
131
166
|
const context = snapshot.context(source.spanContext());
|
|
132
167
|
if (source.links.length > 16384)
|
|
133
168
|
throw new RangeError("Link complexity limit exceeded");
|
|
134
169
|
const links = source.links.map((link) => ({
|
|
135
170
|
context: snapshot.context(link.context),
|
|
136
|
-
attributes: snapshot.
|
|
171
|
+
attributes: snapshot.attributes(link.attributes),
|
|
137
172
|
droppedAttributesCount: snapshot.copy(link.droppedAttributesCount),
|
|
138
173
|
}));
|
|
139
174
|
const record = {
|
|
@@ -145,13 +180,13 @@ export function snapshotSpan(source, limit) {
|
|
|
145
180
|
duration: source.duration,
|
|
146
181
|
ended: source.ended,
|
|
147
182
|
status: source.status,
|
|
148
|
-
attributes: source.attributes,
|
|
149
|
-
events: source.events,
|
|
150
183
|
instrumentationScope: source.instrumentationScope,
|
|
151
184
|
droppedAttributesCount: source.droppedAttributesCount,
|
|
152
185
|
droppedEventsCount: source.droppedEventsCount,
|
|
153
186
|
droppedLinksCount: source.droppedLinksCount,
|
|
154
187
|
}),
|
|
188
|
+
attributes: snapshot.attributes(source.attributes),
|
|
189
|
+
events: snapshot.events(source.events),
|
|
155
190
|
spanContext: contextReader(context),
|
|
156
191
|
parentSpanContext: snapshot.context(source.parentSpanContext),
|
|
157
192
|
links,
|
|
@@ -159,8 +194,8 @@ export function snapshotSpan(source, limit) {
|
|
|
159
194
|
};
|
|
160
195
|
return { record, bytes: snapshot.bytes, unresolvedResource: snapshot.unresolvedResource };
|
|
161
196
|
}
|
|
162
|
-
export function snapshotLog(source, limit) {
|
|
163
|
-
const snapshot = new Snapshot(limit);
|
|
197
|
+
export function snapshotLog(source, limit, captureContent = true) {
|
|
198
|
+
const snapshot = new Snapshot(limit, captureContent);
|
|
164
199
|
// Only our batching processor sees this copy. Its writer methods deliberately
|
|
165
200
|
// cannot mutate the admitted snapshot or invalidate its charged byte count.
|
|
166
201
|
const record = {
|
|
@@ -171,10 +206,10 @@ export function snapshotLog(source, limit) {
|
|
|
171
206
|
severityNumber: source.severityNumber,
|
|
172
207
|
eventName: source.eventName,
|
|
173
208
|
body: source.body,
|
|
174
|
-
attributes: source.attributes,
|
|
175
209
|
instrumentationScope: source.instrumentationScope,
|
|
176
210
|
droppedAttributesCount: source.droppedAttributesCount,
|
|
177
211
|
}),
|
|
212
|
+
attributes: snapshot.attributes(source.attributes),
|
|
178
213
|
spanContext: snapshot.context(source.spanContext),
|
|
179
214
|
resource: snapshot.resource(source.resource),
|
|
180
215
|
setAttribute() {
|
package/dist/transport.js
CHANGED
|
@@ -191,8 +191,8 @@ export class HueTransport {
|
|
|
191
191
|
const remaining = (advisory ? this.options.maxQueueBytes / 4 : this.options.maxQueueBytes) -
|
|
192
192
|
this.pendingBytes;
|
|
193
193
|
const snapshot = signal === "traces"
|
|
194
|
-
? snapshotSpan(record, remaining)
|
|
195
|
-
: snapshotLog(record, remaining);
|
|
194
|
+
? snapshotSpan(record, remaining, this.options.captureContent)
|
|
195
|
+
: snapshotLog(record, remaining, this.options.captureContent);
|
|
196
196
|
this.pendingBytes += snapshot.bytes;
|
|
197
197
|
if (signal === "traces")
|
|
198
198
|
this.spans.set(snapshot.record, snapshot.bytes);
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Package version shared by the instrumentation scope and the export User-Agent. */
|
|
2
|
-
export declare const sdkVersion = "0.
|
|
2
|
+
export declare const sdkVersion = "0.10.0";
|
package/dist/version.js
CHANGED