@hue-run/sdk 0.9.0 → 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/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";
@@ -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
@@ -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
- const base64DataUrl = /^data:[^,]*;base64,/;
15
- function binaryMimeType(value) {
16
- return (typeof value === "string" &&
17
- !/^text\//i.test(value) &&
18
- value.toLowerCase() !== "application/json");
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: base64 (plain or as a `data:` URL) is decoded, and
22
- * anything else, such as a text file's content, is taken as UTF-8.
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, mimeType) {
25
- const prefix = base64DataUrl.exec(content)?.[0].length ?? 0;
26
- const payload = content.slice(prefix);
27
- return (prefix > 0 || binaryMimeType(mimeType)) &&
28
- payload.length % 4 === 0 &&
29
- base64.test(payload)
30
- ? Buffer.from(payload, "base64")
31
- : Buffer.from(content, "utf8");
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, mimeType);
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;
@@ -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
- constructor(limit) {
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
- copy(value, depth = 0) {
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.copy(link.attributes),
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.9.0";
2
+ export declare const sdkVersion = "0.10.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/write-version.mjs from package.json; do not edit by hand.
2
2
  /** Package version shared by the instrumentation scope and the export User-Agent. */
3
- export const sdkVersion = "0.9.0";
3
+ export const sdkVersion = "0.10.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hue-run/sdk",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "publishConfig": {