@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 +10 -10
- package/README.md +12 -4
- package/dist/blobs.d.ts +2 -1
- package/dist/blobs.js +7 -5
- package/dist/inline-files.d.ts +21 -4
- package/dist/inline-files.js +100 -14
- package/dist/large-media.d.ts +2 -1
- package/dist/large-media.js +7 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
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.
|
|
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.
|
|
383
|
-
npx -y @hue-run/sdk@0.19.
|
|
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.
|
|
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.
|
|
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.
|
|
652
|
-
npx -y @hue-run/sdk@0.19.
|
|
653
|
-
npx -y @hue-run/sdk@0.19.
|
|
654
|
-
npx -y @hue-run/sdk@0.19.
|
|
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.
|
|
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,
|
|
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.
|
|
532
|
-
npx -y @hue-run/sdk@0.19.
|
|
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.
|
|
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 === "
|
|
546
|
-
if (++attempt >= UPLOAD_ATTEMPTS ||
|
|
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.
|
|
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
|
|
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
|
|
685
|
+
return { retryAfter: retryAfterMillis(reply.headers) };
|
|
684
686
|
return "failed";
|
|
685
687
|
}
|
|
686
688
|
/**
|
package/dist/inline-files.d.ts
CHANGED
|
@@ -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
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
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;
|
package/dist/inline-files.js
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
|
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)
|
|
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:
|
|
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
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
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
|
-
|
|
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)
|
|
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
|
}
|
package/dist/large-media.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/large-media.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
2
|
+
export declare const sdkVersion = "0.19.1";
|
package/dist/version.js
CHANGED