@hue-run/sdk 0.19.0 → 0.19.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/CLI.md CHANGED
@@ -42,7 +42,7 @@ project manifest are refused because managers can update ancestor locks; Python
42
42
  The generated `hue.setup.mjs` or `hue_setup.py` always selects `captureContent: false` /
43
43
  `capture_content=False`. For a supported application, setup installs the dependency and adds the
44
44
  managed import and middleware registration to the existing entrypoint; an unreferenced helper is
45
- not a completed integration. TypeScript uses `@hue-run/sdk@0.19.0`, `@opentelemetry/api@1.9.1` and
45
+ not a completed integration. TypeScript uses `@hue-run/sdk@0.19.1`, `@opentelemetry/api@1.9.1` and
46
46
  `@opentelemetry/context-async-hooks@2.11.0`; Python setup uses its separately tested package pin.
47
47
  Content capture requires an ordinary account-managed key and a later explicit application decision.
48
48
 
@@ -379,8 +379,8 @@ and Hue never receives or stores them. The command is part of the `hue` executab
379
379
  # LANGFUSE_PUBLIC_KEY=pk-lf-...
380
380
  # LANGFUSE_SECRET_KEY=sk-lf-...
381
381
  # LANGFUSE_BASE_URL=https://cloud.langfuse.com
382
- npx -y @hue-run/sdk@0.19.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
383
- npx -y @hue-run/sdk@0.19.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
382
+ npx -y @hue-run/sdk@0.19.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
383
+ npx -y @hue-run/sdk@0.19.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
384
384
  ```
385
385
 
386
386
  `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` come from the environment or `--env-path`
@@ -595,7 +595,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
595
595
  # Set project to an existing supported fixture; use the same directory on resume.
596
596
  project=/absolute/path/to/supported-fixture
597
597
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
598
- --archive .artifacts/typescript/hue-run-sdk-0.19.0.tgz \
598
+ --archive .artifacts/typescript/hue-run-sdk-0.19.1.tgz \
599
599
  --origin https://STAGING_ORIGIN \
600
600
  --project "$project" --command setup \
601
601
  --evidence .context/setup-staging-before-claim.json
@@ -606,7 +606,7 @@ the private local handoff and finish the real browser claim, then reconcile the
606
606
 
607
607
  ```sh
608
608
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
609
- --archive .artifacts/typescript/hue-run-sdk-0.19.0.tgz \
609
+ --archive .artifacts/typescript/hue-run-sdk-0.19.1.tgz \
610
610
  --origin https://STAGING_ORIGIN \
611
611
  --project "$project" --command claim \
612
612
  --evidence .context/setup-staging-after-claim.json
@@ -648,10 +648,10 @@ otherwise. Never hand the agent Hue-native tools (`context.tools`, `context.mcp`
648
648
  production agent lacks. Pin the SDK, so the agent's repository gains no dependency:
649
649
 
650
650
  ```sh
651
- npx -y @hue-run/sdk@0.19.0 eval --case "Refund an eligible charge" --command "node agent.js"
652
- npx -y @hue-run/sdk@0.19.0 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
653
- npx -y @hue-run/sdk@0.19.0 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
654
- npx -y @hue-run/sdk@0.19.0 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
651
+ npx -y @hue-run/sdk@0.19.1 eval --case "Refund an eligible charge" --command "node agent.js"
652
+ npx -y @hue-run/sdk@0.19.1 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
653
+ npx -y @hue-run/sdk@0.19.1 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
654
+ npx -y @hue-run/sdk@0.19.1 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
655
655
  ```
656
656
 
657
657
  One invocation creates one run holding every selected case; it prints the run URL
@@ -689,7 +689,7 @@ export default function runMyAgent(inputs: JsonValue, context: SimulationTargetC
689
689
  ```
690
690
 
691
691
  ```sh
692
- npx -y @hue-run/sdk@0.19.0 eval --case "Refund an eligible charge" ./hue-agent.ts --content
692
+ npx -y @hue-run/sdk@0.19.1 eval --case "Refund an eligible charge" ./hue-agent.ts --content
693
693
  ```
694
694
 
695
695
  `--scenario` remains an alias for `--case` for existing scripts. Pass one selection flag.
package/README.md CHANGED
@@ -266,10 +266,17 @@ Each uploaded file is listed in the record's `hue.blobs`, keyed `<attribute>#<JS
266
266
  - An OpenAI assistant message's `audio` loses `audio.data` and the message carries `sha256` and
267
267
  `size` (`gen_ai.output.messages#/0`). Its media type comes from `audio.format`, else from the
268
268
  bytes' WAV, MP3, AAC, FLAC or Ogg signature, else `application/octet-stream`.
269
+ - Raw 16-bit PCM keeps the format its type declares: `audio/pcm`, `audio/x-pcm` or `audio/L16`
270
+ with a `rate` from 8000 to 192000 and 1 or 2 `channels` is listed as
271
+ `audio/pcm;rate=24000;channels=1` (`audio/L16;…` for big-endian samples), and OpenAI `pcm16`
272
+ audio as `audio/pcm;rate=24000;channels=1`, so Hue plays the stored file. Other types are listed
273
+ without their parameters.
269
274
  - A whole `data:` URL string becomes a GenAI `blob` part,
270
275
  `{"type":"blob","mime_type":"image/png","modality":"image","sha256":"…","size":68}`, keyed at
271
- the string (`input.value#/image`). A value that is entirely one, such as a metadata attribute,
272
- becomes that part's JSON text, keyed `<attribute>#`.
276
+ the string (`input.value#/image`). A value that is entirely one, such as a metadata attribute
277
+ or the string input or output a Langfuse SDK records as it is, becomes that part's JSON text,
278
+ keyed `<attribute>#`: an attribute read as JSON whose text is not JSON is read as that text, as
279
+ Hue reads it.
273
280
  - A file inside a JSON-encoded tool string takes the digest form inside the string. Its key goes on
274
281
  through the string, which no JSON pointer can address, so Hue matches it by its digest.
275
282
  - Text media over 64 KiB of decoded bytes in a provider part keeps a UTF-8-safe 16 KiB prefix in
@@ -516,6 +523,7 @@ the recorded model call again with the real image, document or audio. See
516
523
  | --- | --- | --- |
517
524
  | Base64 data URIs in an observation's input, output or metadata | | [Automatic capture](#automatically-captured-media), for the attributes and shapes listed there |
518
525
  | `LangfuseMedia` | `Attachment` | [`Attachment`](#attach-a-file-anywhere), at the same position |
526
+ | `LangfuseMedia` and data URIs, keeping Langfuse's SDK | | Unchanged: with Hue's processor [beside Langfuse's](#existing-opentelemetry-providers), each file uploads to Hue in the background |
519
527
  | `resolveMediaReferences({ obj, resolveWith: "base64DataUri" })` | | `client.media.resolve(obj, { traceId })` |
520
528
  | `GET /api/public/media/{mediaId}` | `ReadonlyAttachment.data()` | `client.media.fetch({ traceId, sha256 })` |
521
529
  | Media tokens (`@@@langfuseMedia:…@@@`) | `ExternalAttachment` | Left as recorded: Hue never fetches a remote URL or a file another service holds. [`hue import langfuse --media`](#import-from-langfuse) copies Langfuse's files with your keys |
@@ -528,8 +536,8 @@ the Langfuse project's own keys (`LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, a
528
536
  `HUE_API_KEY`:
529
537
 
530
538
  ```sh
531
- npx -y @hue-run/sdk@0.19.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
532
- npx -y @hue-run/sdk@0.19.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
539
+ npx -y @hue-run/sdk@0.19.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
540
+ npx -y @hue-run/sdk@0.19.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
533
541
  ```
534
542
 
535
543
  - It reads Langfuse Cloud and self-hosted v4 through the Observations API v2, and self-hosted v3
package/dist/blobs.d.ts CHANGED
@@ -249,7 +249,8 @@ export declare class BlobUploader {
249
249
  private reserve;
250
250
  /** A 200 answer read: `exists`, or an upload whose URL and headers are usable as given. */
251
251
  private reservation;
252
- /** The PUT of the value's bytes, with exactly the headers Hue signed. A source that no longer
252
+ /** The PUT of the value's bytes, with exactly the headers Hue signed. No answer, a 408, a 429 or
253
+ * a 5xx is transient, with the answer's `Retry-After` when it gave one. A source that no longer
253
254
  * holds its bytes (a file changed since it was recorded) ends the request before its body is
254
255
  * complete, and fails the value without a retry. */
255
256
  private put;
package/dist/blobs.js CHANGED
@@ -542,8 +542,9 @@ export class BlobUploader {
542
542
  return { ok: false, reason: "failed", changed: true };
543
543
  if (this.closed)
544
544
  return { ok: false, reason: "failed" };
545
- if (put === "transient") {
546
- if (++attempt >= UPLOAD_ATTEMPTS || !(await this.retryWait(attempt - 1, undefined, upload)))
545
+ if (typeof put === "object") {
546
+ if (++attempt >= UPLOAD_ATTEMPTS ||
547
+ !(await this.retryWait(attempt - 1, put.retryAfter, upload)))
547
548
  return { ok: false, reason: "failed" };
548
549
  if (Date.now() < grant.expiresAt)
549
550
  continue;
@@ -662,7 +663,8 @@ export class BlobUploader {
662
663
  grant: { url, headers, expiresAt: Number.isFinite(expiresAt) ? expiresAt : Infinity },
663
664
  };
664
665
  }
665
- /** The PUT of the value's bytes, with exactly the headers Hue signed. A source that no longer
666
+ /** The PUT of the value's bytes, with exactly the headers Hue signed. No answer, a 408, a 429 or
667
+ * a 5xx is transient, with the answer's `Retry-After` when it gave one. A source that no longer
666
668
  * holds its bytes (a file changed since it was recorded) ends the request before its body is
667
669
  * complete, and fails the value without a retry. */
668
670
  async put(grant, value, size, deadline) {
@@ -670,7 +672,7 @@ export class BlobUploader {
670
672
  if (isByteSource(value) && value.changed)
671
673
  return "changed";
672
674
  if (!reply)
673
- return "transient";
675
+ return { retryAfter: undefined };
674
676
  // 412: the object exists already (another upload of the same value won). A success answered
675
677
  // before the whole body was written stores nothing that was sent.
676
678
  if (reply.status === 412)
@@ -680,7 +682,7 @@ export class BlobUploader {
680
682
  if (reply.status === 403)
681
683
  return reply.body.toString("utf8").includes("Request has expired") ? "expired" : "failed";
682
684
  if (reply.status >= 500 || reply.status === 408 || reply.status === 429)
683
- return "transient";
685
+ return { retryAfter: retryAfterMillis(reply.headers) };
684
686
  return "failed";
685
687
  }
686
688
  /**
@@ -21,6 +21,19 @@ export declare function literalKeys(attributes: unknown): ReadonlySet<string>;
21
21
  /** Whether a span attribute is one whose value the SDK inspects for inline media: the attributes
22
22
  * Hue's reader reads files from. */
23
23
  export declare function isMediaKey(key: string): boolean;
24
+ /**
25
+ * The canonical media type of raw 16-bit PCM whose type declares its format, as Hue's reader reads
26
+ * one and names it: `audio/pcm;rate=<Hz>;channels=<n>` for little-endian `audio/pcm` and
27
+ * `audio/x-pcm`, `audio/L16;rate=<Hz>;channels=<n>` for big-endian `audio/L16`, `rate` a whole
28
+ * 8000 to 192000 and `channels` 1 or 2 (1 when absent). Undefined for any other type, raw PCM
29
+ * without a rate or with any other parameter included: Hue plays raw PCM only in a format its
30
+ * type declares.
31
+ */
32
+ export declare function pcmMediaType(mediaType: string): string | undefined;
33
+ /** The media type a `data:` URL's header (between `data:` and its comma) gives its file: raw PCM
34
+ * whose parameters declare its format as its canonical type (`pcmMediaType`), any other type
35
+ * without its parameters. Empty when the header names no type. */
36
+ export declare function headerMediaType(header: string): string;
24
37
  /** An inline file's bytes, whether they were decoded from the part's text (base64 or
25
38
  * percent-escapes) rather than taken as its UTF-8, and the media type its `data:` URL names. */
26
39
  export interface InlineFileBytes {
@@ -124,10 +137,12 @@ export interface MediaValue {
124
137
  export declare function mediaIn(value: unknown, source?: string): MediaValue;
125
138
  /**
126
139
  * An attribute's recorded text read for files as Hue's reader reads that attribute: JSON by its
127
- * convention; metadata as JSON when it reads as an object or array, otherwise as the text itself;
128
- * an OpenLLMetry content as the parts list it encodes (any other content stays text); an
129
- * OpenInference image URL as the text itself. Undefined for text that reading finds nothing in.
130
- * Throws for JSON that does not parse or nests too deep.
140
+ * convention, text that does not parse as the text itself (a Langfuse SDK records a string input
141
+ * or output as it is, so a whole `data:` URL is a file); metadata as JSON when it reads as an
142
+ * object or array, otherwise as the text itself; an OpenLLMetry content as the parts list it
143
+ * encodes (any other content stays text); an OpenInference image URL as the text itself.
144
+ * Undefined for text that reading finds nothing in. Throws for metadata or parts JSON that does
145
+ * not parse.
131
146
  */
132
147
  export declare function readMediaText(key: string, text: string): MediaValue | undefined;
133
148
  /** The recorded value's text once its files are placed: its JSON text as recorded with only the
@@ -151,6 +166,8 @@ export declare function keepsPrefix(file: InlineFilePart, textual: boolean, deco
151
166
  * `client.media.resolve`, write in a `blob` or `media` part for every media type), anything else
152
167
  * as its UTF-8. */
153
168
  export declare function mediaBytes(file: InlineFilePart): InlineFileBytes;
169
+ /** The type a file is reserved and listed under: the type its part declares, else its `data:`
170
+ * URL's, raw PCM whose type declares its format in its canonical form (`pcmMediaType`). */
154
171
  export declare function mediaContentType(file: InlineFilePart, decoded: Pick<InlineFileBytes, "decoded" | "urlMediaType">): string;
155
172
  export interface FileDigest {
156
173
  sha256: string;
@@ -102,6 +102,74 @@ function base64Header(header) {
102
102
  return semicolon !== -1 && `${header.slice(semicolon)};`.includes(";base64;");
103
103
  }
104
104
  const hexPair = /^[0-9A-Fa-f]{2}$/;
105
+ /** Raw 16-bit PCM media types and the byte order of their samples, as Hue's reader names them. */
106
+ const pcmEndian = new Map([
107
+ ["audio/l16", "big"],
108
+ ["audio/pcm", "little"],
109
+ ["audio/x-pcm", "little"],
110
+ ]);
111
+ /** One media type parameter (`name=value`, the value a token or a quoted string) or an empty one,
112
+ * then the `;` that ends it or the end of the type (RFC 9110 section 8.3.1). */
113
+ const pcmParameter = /[ \t]*(?:([!#$%&'*+.^_`|~0-9A-Za-z-]+)[ \t]*=[ \t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+|"(?:[^"\\]|\\.)*")[ \t]*)?(;|$)/y;
114
+ /** A whole number written in decimal digits alone, within `[min, max]`. */
115
+ function wholeIn(value, min, max) {
116
+ if (value === undefined || !/^\d{1,7}$/.test(value))
117
+ return undefined;
118
+ const number = Number(value);
119
+ return number >= min && number <= max ? number : undefined;
120
+ }
121
+ /**
122
+ * The canonical media type of raw 16-bit PCM whose type declares its format, as Hue's reader reads
123
+ * one and names it: `audio/pcm;rate=<Hz>;channels=<n>` for little-endian `audio/pcm` and
124
+ * `audio/x-pcm`, `audio/L16;rate=<Hz>;channels=<n>` for big-endian `audio/L16`, `rate` a whole
125
+ * 8000 to 192000 and `channels` 1 or 2 (1 when absent). Undefined for any other type, raw PCM
126
+ * without a rate or with any other parameter included: Hue plays raw PCM only in a format its
127
+ * type declares.
128
+ */
129
+ export function pcmMediaType(mediaType) {
130
+ if (mediaType.length > 255)
131
+ return undefined;
132
+ const endian = pcmEndian.get(mediaType.split(";", 1)[0].trim().toLowerCase());
133
+ if (!endian)
134
+ return undefined;
135
+ const parameters = new Map();
136
+ const start = mediaType.indexOf(";");
137
+ if (start !== -1) {
138
+ pcmParameter.lastIndex = start + 1;
139
+ for (;;) {
140
+ const match = pcmParameter.exec(mediaType);
141
+ if (!match)
142
+ return undefined;
143
+ if (match[1] !== undefined) {
144
+ const name = match[1].toLowerCase();
145
+ if (parameters.has(name))
146
+ return undefined;
147
+ const value = match[2];
148
+ parameters.set(name, value.startsWith('"') ? value.slice(1, -1).replace(/\\(.)/g, "$1") : value);
149
+ }
150
+ if (match[3] === "")
151
+ break;
152
+ }
153
+ }
154
+ for (const name of parameters.keys())
155
+ if (name !== "rate" && name !== "channels")
156
+ return undefined;
157
+ const rate = wholeIn(parameters.get("rate"), 8_000, 192_000);
158
+ const channels = parameters.has("channels") ? wholeIn(parameters.get("channels"), 1, 2) : 1;
159
+ if (rate === undefined || channels === undefined)
160
+ return undefined;
161
+ return `${endian === "big" ? "audio/L16" : "audio/pcm"};rate=${rate};channels=${channels}`;
162
+ }
163
+ /** The media type a `data:` URL's header (between `data:` and its comma) gives its file: raw PCM
164
+ * whose parameters declare its format as its canonical type (`pcmMediaType`), any other type
165
+ * without its parameters. Empty when the header names no type. */
166
+ export function headerMediaType(header) {
167
+ const type = header.split(";", 1)[0].trim();
168
+ if (!type)
169
+ return type;
170
+ const declared = header.endsWith(";base64") ? header.slice(0, -";base64".length) : header;
171
+ return pcmMediaType(declared) ?? type;
172
+ }
105
173
  /** RFC 2397 data without `;base64`: percent-escaped octets, other characters as UTF-8. Undefined
106
174
  * when a `%` does not start an escape. */
107
175
  function percentDecoded(payload) {
@@ -135,7 +203,7 @@ export function inlineFileBytes(content) {
135
203
  ? Buffer.from(payload, "base64")
136
204
  : undefined;
137
205
  const header = url?.[1];
138
- const mediaType = header === undefined ? undefined : header.split(";")[0].trim();
206
+ const mediaType = header === undefined ? undefined : headerMediaType(header);
139
207
  return {
140
208
  bytes: decoded ?? Buffer.from(content, "utf8"),
141
209
  decoded: decoded !== undefined,
@@ -143,6 +211,8 @@ export function inlineFileBytes(content) {
143
211
  ...(decoded !== undefined && !url ? { bareBase64: true } : {}),
144
212
  };
145
213
  }
214
+ /** OpenAI's `pcm16` audio: raw 16-bit little-endian PCM, 24 kHz mono, as Hue's reader types it. */
215
+ const PCM16 = "audio/pcm;rate=24000;channels=1";
146
216
  const audioTypes = {
147
217
  wav: "audio/wav",
148
218
  mp3: "audio/mpeg",
@@ -152,7 +222,7 @@ const audioTypes = {
152
222
  aac: "audio/aac",
153
223
  m4a: "audio/mp4",
154
224
  webm: "audio/webm",
155
- pcm16: "audio/pcm",
225
+ pcm16: PCM16,
156
226
  };
157
227
  const documentTypes = {
158
228
  pdf: "application/pdf",
@@ -342,16 +412,18 @@ export function contentPath(part, pointer = "") {
342
412
  }
343
413
  /** A media type as a `data:` URL declares one: `type/subtype`, no parameters. */
344
414
  const declaredMediaType = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*\/[A-Za-z0-9!#$&^_.+-]+$/;
345
- /** The media type a whole string that is a `data:` URL declares, when it declares one: such a
346
- * string is a file wherever it sits. A web URL, or any other text, is not. */
415
+ /** The media type of a whole string that is a `data:` URL declaring one (`headerMediaType`): such
416
+ * a string is a file wherever it sits. A web URL, or any other text, is not. */
347
417
  function dataUrlType(text) {
348
418
  if (!text.startsWith("data:"))
349
419
  return undefined;
350
420
  const header = dataUrl.exec(text);
351
421
  if (!header)
352
422
  return undefined;
353
- const type = header[1].split(";")[0].trim();
354
- return declaredMediaType.test(type) && isMediaContentType(type) ? type : undefined;
423
+ const type = header[1].split(";", 1)[0].trim();
424
+ return declaredMediaType.test(type) && isMediaContentType(type)
425
+ ? headerMediaType(header[1])
426
+ : undefined;
355
427
  }
356
428
  /** The modality a media type implies, as Hue's reader names it. */
357
429
  export function modalityOf(mediaType) {
@@ -387,7 +459,7 @@ const audioFormats = {
387
459
  m4a: "audio/mp4",
388
460
  mp4: "audio/mp4",
389
461
  webm: "audio/webm",
390
- pcm16: "audio/pcm",
462
+ pcm16: PCM16,
391
463
  pcm: "audio/pcm",
392
464
  };
393
465
  function audioFormatMediaType(format) {
@@ -654,17 +726,27 @@ export function mediaIn(value, source) {
654
726
  }
655
727
  /**
656
728
  * An attribute's recorded text read for files as Hue's reader reads that attribute: JSON by its
657
- * convention; metadata as JSON when it reads as an object or array, otherwise as the text itself;
658
- * an OpenLLMetry content as the parts list it encodes (any other content stays text); an
659
- * OpenInference image URL as the text itself. Undefined for text that reading finds nothing in.
660
- * Throws for JSON that does not parse or nests too deep.
729
+ * convention, text that does not parse as the text itself (a Langfuse SDK records a string input
730
+ * or output as it is, so a whole `data:` URL is a file); metadata as JSON when it reads as an
731
+ * object or array, otherwise as the text itself; an OpenLLMetry content as the parts list it
732
+ * encodes (any other content stays text); an OpenInference image URL as the text itself.
733
+ * Undefined for text that reading finds nothing in. Throws for metadata or parts JSON that does
734
+ * not parse.
661
735
  */
662
736
  export function readMediaText(key, text) {
663
737
  /** JSON text read as Hue's reader reads it: text it keeps as text holds no file. */
664
738
  const json = (parsed) => (readerParses(parsed) ? mediaIn(parsed, text) : undefined);
665
739
  switch (readingOf(key)) {
666
- case "json":
667
- return json(JSON.parse(text));
740
+ case "json": {
741
+ let parsed;
742
+ try {
743
+ parsed = JSON.parse(text);
744
+ }
745
+ catch {
746
+ return mediaIn(text);
747
+ }
748
+ return json(parsed);
749
+ }
668
750
  case "metadata":
669
751
  return /^\s*[[{]/.test(text) ? json(JSON.parse(text)) : mediaIn(text);
670
752
  case "parts": {
@@ -729,8 +811,12 @@ export function mediaBytes(file) {
729
811
  return { bytes: Buffer.from(file.content, "base64"), decoded: true };
730
812
  return inlineFileBytes(file.content);
731
813
  }
814
+ /** The type a file is reserved and listed under: the type its part declares, else its `data:`
815
+ * URL's, raw PCM whose type declares its format in its canonical form (`pcmMediaType`). */
732
816
  export function mediaContentType(file, decoded) {
733
- return ((isMediaContentType(file.contentType) ? file.contentType : undefined) ??
817
+ return ((isMediaContentType(file.contentType)
818
+ ? (pcmMediaType(file.contentType) ?? file.contentType)
819
+ : undefined) ??
734
820
  (isMediaContentType(decoded.urlMediaType) ? decoded.urlMediaType : undefined) ??
735
821
  (decoded.decoded ? "application/octet-stream" : "text/plain; charset=utf-8"));
736
822
  }
@@ -67,7 +67,8 @@ export interface LargeMediaValue extends MediaValue {
67
67
  * An attribute's recorded text read for files as `readMediaText` reads it, its long base64 and
68
68
  * base64 `data:` URL strings read in place (see {@link skeletonOf}). Undefined for text whose JSON
69
69
  * around those strings is over the 8 Mi bound, or that reading finds nothing in. Throws for JSON
70
- * that does not parse.
70
+ * that does not parse, except text read by JSON convention that is a `data:` URL, which is read
71
+ * as the text itself, as `readMediaText` reads it.
71
72
  */
72
73
  export declare function readLargeMediaText(key: string, text: string, longUnits?: number): Promise<LargeMediaValue | undefined>;
73
74
  /**
@@ -1,5 +1,5 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { MAX_INLINE_FILE_TEXT, mediaIn, placedMediaText, readerParses, readingOf, } from "./inline-files.js";
2
+ import { headerMediaType, MAX_INLINE_FILE_TEXT, mediaIn, placedMediaText, readerParses, readingOf, } from "./inline-files.js";
3
3
  /**
4
4
  * Inline media in a value too long for the media inspection bounds (8 Mi UTF-16 code units for one
5
5
  * attribute, 16 Mi for a record), read without parsing the files out of it. A span's own attribute
@@ -218,7 +218,8 @@ async function skeletonOf(text, longUnits, held) {
218
218
  * An attribute's recorded text read for files as `readMediaText` reads it, its long base64 and
219
219
  * base64 `data:` URL strings read in place (see {@link skeletonOf}). Undefined for text whose JSON
220
220
  * around those strings is over the 8 Mi bound, or that reading finds nothing in. Throws for JSON
221
- * that does not parse.
221
+ * that does not parse, except text read by JSON convention that is a `data:` URL, which is read
222
+ * as the text itself, as `readMediaText` reads it.
222
223
  */
223
224
  export async function readLargeMediaText(key, text, longUnits = LONG_STRING_UNITS) {
224
225
  const reading = readingOf(key);
@@ -255,7 +256,9 @@ export async function readLargeMediaText(key, text, longUnits = LONG_STRING_UNIT
255
256
  };
256
257
  switch (reading) {
257
258
  case "json":
258
- return json();
259
+ // No JSON text starts with `data:`: such text is the text itself, as `readMediaText` reads
260
+ // text that does not parse, and a whole `data:` URL is the file.
261
+ return text.startsWith("data:") ? whole() : json();
259
262
  case "metadata":
260
263
  return /^\s*[[{]/.test(text) ? json() : whole();
261
264
  case "parts":
@@ -408,7 +411,7 @@ export function longFileBytes(file, long) {
408
411
  ...(file.base64 ? {} : { bareBase64: true }),
409
412
  source,
410
413
  };
411
- const mediaType = long.header.split(";")[0].trim();
414
+ const mediaType = headerMediaType(long.header);
412
415
  return {
413
416
  size: source.size,
414
417
  decoded: true,
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.19.0";
2
+ export declare const sdkVersion = "0.19.1";
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.19.0";
3
+ export const sdkVersion = "0.19.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hue-run/sdk",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "publishConfig": {