@hue-run/sdk 0.18.1 → 0.19.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 +16 -12
- package/EVALUATIONS.md +1 -1
- package/README.md +135 -74
- package/dist/attachment.d.ts +51 -7
- package/dist/attachment.js +141 -33
- package/dist/blobs.d.ts +136 -40
- package/dist/blobs.js +535 -225
- package/dist/cli/langfuse.d.ts +21 -5
- package/dist/cli/langfuse.js +123 -16
- package/dist/client.d.ts +20 -11
- package/dist/client.js +81 -41
- package/dist/inline-files.d.ts +5 -4
- package/dist/inline-files.js +6 -5
- package/dist/json-edits.js +26 -7
- package/dist/large-media.d.ts +118 -0
- package/dist/large-media.js +422 -0
- package/dist/snapshot.d.ts +2 -0
- package/dist/snapshot.js +19 -1
- package/dist/transport.d.ts +39 -7
- package/dist/transport.js +262 -75
- package/dist/types.d.ts +15 -5
- 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.
|
|
45
|
+
not a completed integration. TypeScript uses `@hue-run/sdk@0.19.0`, `@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.
|
|
383
|
-
npx -y @hue-run/sdk@0.
|
|
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
|
|
384
384
|
```
|
|
385
385
|
|
|
386
386
|
`LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` come from the environment or `--env-path`
|
|
@@ -459,8 +459,12 @@ memory stays bounded whatever the project's size.
|
|
|
459
459
|
sent as recorded, and Hue shows them as files held in Langfuse. With `--media`, each reference where
|
|
460
460
|
Hue reads a file (a whole string, or the content of a provider part such as an OpenAI `image_url` or
|
|
461
461
|
`input_audio`, an Anthropic `source` or a GenAI `blob`, no deeper than ten levels) is looked up with
|
|
462
|
-
`GET /api/public/media/{id}`. The file is downloaded from the link Langfuse answers (following
|
|
463
|
-
store's redirects, without the Langfuse keys)
|
|
462
|
+
`GET /api/public/media/{id}`. The file is downloaded from the link Langfuse answers (following up to
|
|
463
|
+
five of its store's redirects, without the Langfuse keys). The link and each redirect must use HTTPS
|
|
464
|
+
without credentials or a fragment and reach a public address, or the Langfuse host itself; a link to
|
|
465
|
+
this machine, a private network or a link-local address such as cloud metadata fails the file. When
|
|
466
|
+
the Langfuse host is on this machine, any address is accepted, and plain HTTP to this machine. The
|
|
467
|
+
file is then uploaded under the span's trace through Hue's upload route (reservation, presigned
|
|
464
468
|
`PUT`, completion), and placed in the digest form the SDK writes for media it records: a whole string
|
|
465
469
|
becomes `{"type":"blob","mime_type":…,"modality":…,"sha256":…,"size":…}` and a provider part loses its
|
|
466
470
|
bytes and gains `sha256` and `size`, every other byte of the value's text kept, each file listed in
|
|
@@ -591,7 +595,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
|
|
|
591
595
|
# Set project to an existing supported fixture; use the same directory on resume.
|
|
592
596
|
project=/absolute/path/to/supported-fixture
|
|
593
597
|
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
594
|
-
--archive .artifacts/typescript/hue-run-sdk-0.
|
|
598
|
+
--archive .artifacts/typescript/hue-run-sdk-0.19.0.tgz \
|
|
595
599
|
--origin https://STAGING_ORIGIN \
|
|
596
600
|
--project "$project" --command setup \
|
|
597
601
|
--evidence .context/setup-staging-before-claim.json
|
|
@@ -602,7 +606,7 @@ the private local handoff and finish the real browser claim, then reconcile the
|
|
|
602
606
|
|
|
603
607
|
```sh
|
|
604
608
|
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
605
|
-
--archive .artifacts/typescript/hue-run-sdk-0.
|
|
609
|
+
--archive .artifacts/typescript/hue-run-sdk-0.19.0.tgz \
|
|
606
610
|
--origin https://STAGING_ORIGIN \
|
|
607
611
|
--project "$project" --command claim \
|
|
608
612
|
--evidence .context/setup-staging-after-claim.json
|
|
@@ -644,10 +648,10 @@ otherwise. Never hand the agent Hue-native tools (`context.tools`, `context.mcp`
|
|
|
644
648
|
production agent lacks. Pin the SDK, so the agent's repository gains no dependency:
|
|
645
649
|
|
|
646
650
|
```sh
|
|
647
|
-
npx -y @hue-run/sdk@0.
|
|
648
|
-
npx -y @hue-run/sdk@0.
|
|
649
|
-
npx -y @hue-run/sdk@0.
|
|
650
|
-
npx -y @hue-run/sdk@0.
|
|
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
655
|
```
|
|
652
656
|
|
|
653
657
|
One invocation creates one run holding every selected case; it prints the run URL
|
|
@@ -685,7 +689,7 @@ export default function runMyAgent(inputs: JsonValue, context: SimulationTargetC
|
|
|
685
689
|
```
|
|
686
690
|
|
|
687
691
|
```sh
|
|
688
|
-
npx -y @hue-run/sdk@0.
|
|
692
|
+
npx -y @hue-run/sdk@0.19.0 eval --case "Refund an eligible charge" ./hue-agent.ts --content
|
|
689
693
|
```
|
|
690
694
|
|
|
691
695
|
`--scenario` remains an alias for `--case` for existing scripts. Pass one selection flag.
|
package/EVALUATIONS.md
CHANGED
|
@@ -9,7 +9,7 @@ npm install @hue-run/sdk zod
|
|
|
9
9
|
|
|
10
10
|
The SDK executes targets and scorers on your machine. Hue stores pinned definitions, experiment progress and results. It does not execute uploaded source code. Follow [Install the SDK](https://docs.hue.run/quickstart#2-install-the-sdk) to add `@hue-run/sdk` to your application.
|
|
11
11
|
|
|
12
|
-
Create a **Read and write** project service key under **Settings →
|
|
12
|
+
Create a **Read and write** project service key under **Settings → API keys** and expose it to this server-side process as `HUE_API_KEY`. A **Tracing only** key cannot author datasets or evaluation runs.
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
package/README.md
CHANGED
|
@@ -218,8 +218,8 @@ its place and lists the upload in its `hue.blobs` attribute. Files reach Hue in
|
|
|
218
218
|
[`client.media`](#read-recorded-media-back) reads a recorded file back. Every upload needs
|
|
219
219
|
`captureContent: true` and a project key (never a setup credential, `hue_setup_…`) and follows
|
|
220
220
|
`redact`: nothing the redactor removes is uploaded, a value it rewrites uploads as rewritten, and an
|
|
221
|
-
attachment part it rewrites uploads nothing. Uploads
|
|
222
|
-
MiB held for values awaiting upload, and
|
|
221
|
+
attachment part it rewrites uploads nothing. Uploads run after the record is exported, within the
|
|
222
|
+
128 MiB held for values awaiting upload, and never hold up or fail the export; a file used more than
|
|
223
223
|
once in a trace is stored once. See [Values over the inline limit](#values-over-the-inline-limit)
|
|
224
224
|
for the upload protocol, budgets and fallbacks.
|
|
225
225
|
|
|
@@ -293,10 +293,11 @@ the GenAI conventions and `client.media.resolve` write a `blob` or `media` part'
|
|
|
293
293
|
file's base64 and is decoded, so `YWJj` hashes as `abc`; anything else is the file's own UTF-8
|
|
294
294
|
text. The digest matches `hue.recordFile`'s digest for the same bytes.
|
|
295
295
|
|
|
296
|
-
A file
|
|
297
|
-
out) keeps its digest without an entry in the recorded
|
|
298
|
-
`gen_ai.output.messages`, `ai.prompt.messages`); in every other
|
|
299
|
-
inline under the ordinary value limit.
|
|
296
|
+
A file refused when its record is exported (the receiver is known to have no upload route, uploads
|
|
297
|
+
were ended by shutdown, or a budget ran out) keeps its digest without an entry in the recorded
|
|
298
|
+
messages (`gen_ai.input.messages`, `gen_ai.output.messages`, `ai.prompt.messages`); in every other
|
|
299
|
+
attribute it keeps its bytes inline under the ordinary value limit. A file whose upload fails after
|
|
300
|
+
its record was exported keeps its digest form and its entry, which Hue reads as missing.
|
|
300
301
|
|
|
301
302
|
Log records upload their media the same way when they are correlated with a span:
|
|
302
303
|
`recordMessages`'s `gen_ai.client.inference.operation.details` record and any `gen_ai.*` event
|
|
@@ -305,20 +306,31 @@ them in its own `hue.blobs`, keyed `body#<JSON pointer>` into the body
|
|
|
305
306
|
(`body#/gen_ai.input.messages/0/parts/1`, or `body#/content/1` for a deprecated
|
|
306
307
|
`gen_ai.user.message` event) or `<attribute>#<JSON pointer>` for the attributes above. One step
|
|
307
308
|
places a log record's files, the attachments in its body included. A body recorded as JSON text
|
|
308
|
-
stays as it is, and no log value is uploaded whole. A log file
|
|
309
|
-
log records were before they uploaded: a file over 64 KiB in a recorded message
|
|
310
|
-
its digest and any other file keeps its bytes; text still over the value limit is
|
|
311
|
-
listed under `hue.truncated`.
|
|
309
|
+
stays as it is, and no log value is uploaded whole. A log file refused when its record is exported
|
|
310
|
+
is placed as log records were before they uploaded: a file over 64 KiB in a recorded message
|
|
311
|
+
attribute becomes its digest and any other file keeps its bytes; text still over the value limit is
|
|
312
|
+
cut inline and listed under `hue.truncated`. One whose upload fails afterwards keeps its digest and
|
|
313
|
+
its entry, as on a span. While uploads are available, `recordMessages` records a body over
|
|
312
314
|
the value limit when it fits the limit with its files as their digest.
|
|
313
315
|
|
|
314
316
|
Inspection is bounded to 8 Mi UTF-16 code units of text per attribute and 16 Mi per record, a log
|
|
315
|
-
body's file contents included
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
317
|
+
body's file contents included. Within those bounds, inspection parses the value and decodes each
|
|
318
|
+
file while the record is queued and again when it is exported. A span's own attribute past either
|
|
319
|
+
bound that is held whole for upload (it is over the value limit) has its media read in place when
|
|
320
|
+
it is exported, as a voice agent's audio usually needs: each string in it longer than 16 Ki code
|
|
321
|
+
units and written without escapes as strict base64 or a base64 `data:` URL is decoded, hashed and
|
|
322
|
+
uploaded a 1 MiB window at a time from the recorded text, never parsed or copied out of it, and a
|
|
323
|
+
file may be up to Hue's 1 GB upload limit. The scan that finds those strings also reads the value
|
|
324
|
+
a window at a time, between turns of the event loop, so no step of it stalls the application for
|
|
325
|
+
long; a percent-escaped `data:` URL is parsed with the JSON around it. The value is placed exactly as one within the bounds: the same
|
|
326
|
+
digest form, entries and fallbacks. The JSON around those strings, a file written with escapes or
|
|
327
|
+
inside a JSON-encoded string included, is parsed within the 8 Mi bound; a value past it uses the
|
|
328
|
+
ordinary value limit (it uploads whole). Each such value is read within that bound on its own, apart
|
|
329
|
+
from the record's 16 Mi allowance, while it stays held within the 128 MiB for values awaiting
|
|
330
|
+
upload. Log records keep the bounds. Resources, links and span
|
|
331
|
+
events never upload inline media: in their recorded messages, media up to 64 KiB stays verbatim
|
|
332
|
+
and larger media is replaced by its digest, as in 0.16, and log records that cannot upload (not
|
|
333
|
+
correlated with a span, or while uploads are unavailable) keep the same rule. With
|
|
322
334
|
`captureContent: false`, nothing is inspected or uploaded.
|
|
323
335
|
|
|
324
336
|
### Attach a file anywhere
|
|
@@ -345,15 +357,24 @@ await hue.withSpan(
|
|
|
345
357
|
```
|
|
346
358
|
|
|
347
359
|
Give exactly one of `data` (a `Buffer`, typed array, `ArrayBuffer`, or a base64 string or `data:`
|
|
348
|
-
URL) and `path` (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
360
|
+
URL) and `path` (a file on the file system), a `contentType` and optionally a `name`. `data` is
|
|
361
|
+
read and hashed when the attachment is created and is limited to 25 MiB, the `recordFile` limit:
|
|
362
|
+
it is held in memory from then until it is uploaded, within the 128 MiB budget for values awaiting
|
|
363
|
+
upload. A `path` names a regular file: anything else, such as a named pipe, is
|
|
364
|
+
refused without waiting on it. A `path` up to 25 MiB is read the same way. A larger regular file, up to
|
|
365
|
+
Hue's 1 GB upload limit, is not held: it is hashed a 1 MiB slice at a time when the attachment is
|
|
366
|
+
created and read again from disk when it is uploaded, each slice hashed as it is sent and the last
|
|
367
|
+
sent only once the file ended at the size and SHA-256 it was recorded with. Keep such a file in
|
|
368
|
+
place and unchanged until the client flushes: one that changed, is gone or is no longer a regular
|
|
369
|
+
file is not uploaded (the
|
|
370
|
+
request ends before its body is complete, and Hue's upload URL signs the size and SHA-256, so the
|
|
371
|
+
store refuses other bytes), and it counts in `report.uploadFallbacks`, its part keeping the digest
|
|
372
|
+
it was recorded with.
|
|
352
373
|
|
|
353
374
|
At its position the attachment is recorded as a GenAI `blob` part that names the file by digest,
|
|
354
375
|
`{"type":"blob","mime_type":"application/pdf","modality":"document","sha256":"<64 hex>","size":48213,"name":"contract.pdf"}`,
|
|
355
376
|
with `modality` read from the content type (`image`, `audio`, `video`, `document` or `file`).
|
|
356
|
-
Export
|
|
377
|
+
Export lists the bytes under the record's `hue.blobs` and uploads them once the record is sent, keyed
|
|
357
378
|
`<attribute>#<JSON pointer to the part>`: `input.value#/contract` above, `metadata#/scan` for the
|
|
358
379
|
attribute, and `body#/gen_ai.input.messages/0/parts/1` for a part in a `recordMessages()` log
|
|
359
380
|
record, which uploads with the other media in the body. An attribute value that is an attachment,
|
|
@@ -363,9 +384,10 @@ span's metadata files.
|
|
|
363
384
|
|
|
364
385
|
With `captureContent: false` or a setup credential no bytes leave the process: a metadata
|
|
365
386
|
attribute still records the part, without its name, and content values are not recorded at all. A
|
|
366
|
-
part the redactor removes or rewrites is not uploaded. A part
|
|
367
|
-
|
|
368
|
-
|
|
387
|
+
part the redactor removes or rewrites is not uploaded. A part refused when its record is exported
|
|
388
|
+
keeps its digest without an entry, and one whose upload fails afterwards keeps its digest and its
|
|
389
|
+
entry; either is counted in `report.uploadFallbacks`, as is one nested more than 10 levels deep in
|
|
390
|
+
its value, where Hue does not read parts, or one in a value cut to the value limit. An attachment in a
|
|
369
391
|
value that is still over the value limit with its files as digest parts is uploaded and listed as
|
|
370
392
|
usual, and `client.media.fetch({ traceId, sha256 })` and Hue's span read reach it by digest; the
|
|
371
393
|
value itself uploads whole and keeps only its first 16 KiB inline, so readers do not place the file
|
|
@@ -392,12 +414,21 @@ await hue.withSpan("review contract", async () => {
|
|
|
392
414
|
```
|
|
393
415
|
|
|
394
416
|
`upload` defaults to `false`, which hashes and measures `data` locally without exporting bytes.
|
|
395
|
-
`data` is limited to 25 MiB
|
|
396
|
-
pass `sha256` and `byteSize` when you already have them.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
417
|
+
`data` is limited to 25 MiB, as it is held in memory until it is uploaded. Larger data is omitted
|
|
418
|
+
and counted as an instrumentation failure; pass `sha256` and `byteSize` when you already have them.
|
|
419
|
+
Give `path` instead of `data` for a file on disk: up to 25 MiB it is read as `data` is, and a larger
|
|
420
|
+
regular file, up to Hue's 1 GB upload limit, is hashed a slice at a time now and, with `upload`,
|
|
421
|
+
read again from disk when it is uploaded, as an [`Attachment`](#attach-a-file-anywhere) given by
|
|
422
|
+
path is: it uploads only if it still has the size and SHA-256 it was recorded with. A file over
|
|
423
|
+
1 GB, a path that is no regular file, or `data` and `path` together, is omitted and counted. Upload requires supplied bytes or a
|
|
424
|
+
path, content capture and an ordinary project key. An upload that cannot be held when the file is
|
|
425
|
+
recorded (a span's 65th, a receiver known to lack the upload route, or a full
|
|
426
|
+
[held-value budget](#values-over-the-inline-limit), which uploads not yet settled share) keeps the
|
|
427
|
+
event without its bytes and is counted as an upload fallback, with a warning, and as an
|
|
428
|
+
instrumentation failure. Export adds a `hue.blobs` entry to the same span,
|
|
429
|
+
keyed `event:hue.file:<sha256>`, while preserving the event, once it has fitted the span's events
|
|
430
|
+
and entries; the upload runs after the span is sent, four at a time with the export's other
|
|
431
|
+
uploads. A file whose event was redacted or shed is not uploaded.
|
|
401
432
|
|
|
402
433
|
The event carries `hue.file.sha256`, `hue.file.role` (`input`, `attachment` or `output`),
|
|
403
434
|
`hue.file.media_type` and `hue.file.size` in both capture modes. `hue.file.name` requires
|
|
@@ -497,8 +528,8 @@ the Langfuse project's own keys (`LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, a
|
|
|
497
528
|
`HUE_API_KEY`:
|
|
498
529
|
|
|
499
530
|
```sh
|
|
500
|
-
npx -y @hue-run/sdk@0.
|
|
501
|
-
npx -y @hue-run/sdk@0.
|
|
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
|
|
502
533
|
```
|
|
503
534
|
|
|
504
535
|
- It reads Langfuse Cloud and self-hosted v4 through the Observations API v2, and self-hosted v3
|
|
@@ -745,7 +776,10 @@ Collector `otlp` receiver with `http.traces_url_path` and `logs_url_path` set to
|
|
|
745
776
|
forwarding to Jaeger or the debug exporter) and pass any placeholder `apiKey`; HTTP is allowed
|
|
746
777
|
for loopback origins. `checkConnection()` and `verifyTrace()` are Hue-only diagnostics and are
|
|
747
778
|
not available against a generic collector. Set `liveSpans: false` so Hue-specific placeholders
|
|
748
|
-
do not reach the collector's other destinations.
|
|
779
|
+
do not reach the collector's other destinations. A collector has no upload route: the records
|
|
780
|
+
exported before it answers an upload carry their large values and media as 16 KiB prefixes or
|
|
781
|
+
digests with `hue.blobs` entries that never resolve, and for 10 minutes after each answer values are
|
|
782
|
+
cut inline, as described in [Values over the inline limit](#values-over-the-inline-limit).
|
|
749
783
|
|
|
750
784
|
A collector on a private network is not loopback: a docker-compose sibling such as
|
|
751
785
|
`http://otel-collector:4318` or an in-cluster service requires the explicit opt-in
|
|
@@ -776,7 +810,10 @@ until it is sent. When `shutdownSafe()`'s budget runs out first, a client that o
|
|
|
776
810
|
ends the hold: its records are reported lost to the rate limit and the process can exit. In attach
|
|
777
811
|
mode the application's providers drain with their own timeout (OpenTelemetry's default is 30
|
|
778
812
|
seconds), so a flush during a longer hold can report a provider flush failure while the held
|
|
779
|
-
export continues.
|
|
813
|
+
export continues. Uploads of values over the inline limit never hold an export or draw on its
|
|
814
|
+
hold: they run after their records are sent. `flush()` waits for them once the providers have
|
|
815
|
+
drained; an application's own provider `forceFlush()` in attach mode waits for them within its
|
|
816
|
+
provider timeout, which can then report a provider flush failure while uploads continue.
|
|
780
817
|
|
|
781
818
|
Requests are measured as Hue's receiver measures them: after gzip against its request limit and
|
|
782
819
|
after decompression against its decoded limit. Until a response advertises its own limits
|
|
@@ -805,51 +842,72 @@ Hue keeps a value inline up to its value limit (1 MiB unless the receiver advert
|
|
|
805
842
|
`Hue-Max-Value-Bytes`). A span's own string attribute larger than that (helper content such as
|
|
806
843
|
`input.value`, `output.value` and tool results, or another instrumentation's attribute), and the
|
|
807
844
|
files described in [Images, documents and audio](#images-documents-and-audio), are uploaded to Hue
|
|
808
|
-
apart from their record, up to Hue's upload limit of 1,000,000,000 bytes
|
|
845
|
+
apart from their record, up to Hue's upload limit of 1,000,000,000 bytes. As with Langfuse media,
|
|
846
|
+
the record is sent first and its bytes follow:
|
|
809
847
|
|
|
810
848
|
1. Export computes the SHA-256 of the value's bytes (a string as UTF-8, a file as its own bytes)
|
|
811
|
-
and
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
849
|
+
and places the value: whole string values and large text media keep a UTF-8-safe 16 KiB prefix,
|
|
850
|
+
and binary media keeps its digest on the part. The record lists each value in `hue.blobs`, at
|
|
851
|
+
most 64 compact JSON strings with fields ordered `key`, `sha256`, `size`, `content_type` (the
|
|
852
|
+
type this reference declared) and optional `name`:
|
|
853
|
+
`{"key":"output.value","sha256":"<64 hex>","size":5242880,"content_type":"application/json"}`.
|
|
854
|
+
A whole value is listed under `hue.truncated` as well, since its inline text is a prefix. The
|
|
855
|
+
record is then sent.
|
|
856
|
+
2. In the background, the upload reserves the value with `POST /api/v1/otlp/blobs`, authenticated
|
|
857
|
+
with the project key. Hue answers that it already stores the value under the trace, and nothing
|
|
858
|
+
is uploaded, or with a presigned URL.
|
|
859
|
+
3. The bytes go straight to the project's evidence store in one `PUT` to that URL, with exactly the
|
|
815
860
|
headers Hue signed and never the project key (over HTTPS, or plain HTTP to a loopback store or
|
|
816
861
|
with `allowInsecureHttp`), and
|
|
817
862
|
the upload is confirmed with `POST /api/v1/otlp/blobs/complete`. A PUT the store answers with
|
|
818
863
|
412 (the same value uploaded at the same time by another span) counts as stored.
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
864
|
+
|
|
865
|
+
Hue stores the value under the trace, so the trace's retention, deletion and erasure cover it, and
|
|
866
|
+
reads an entry's bytes whenever they arrive: until then Hue reports the value as missing. Uploads
|
|
867
|
+
never run on the application's thread and never delay, hold or drop a record: hashing has its own
|
|
868
|
+
four places, at most four uploads run at once, and none draws on an export's 60-second hold.
|
|
869
|
+
|
|
870
|
+
| Request | Tried | Waits before trying again | Not tried again |
|
|
871
|
+
| --- | --- | --- | --- |
|
|
872
|
+
| Reservation | 3 times: no answer, 408, 429 or a 5xx other than 501 | Its `Retry-After` up to 60 s, or 1 s then 2 s with jitter | Other refusals |
|
|
873
|
+
| `PUT` | 3 times: no answer, 408, 429 or a 5xx | 1 s then 2 s with jitter | Other refusals; an expired URL is reserved again once |
|
|
874
|
+
| Completion | Once | None | Its outcome is not read: Hue completes the value on its next reservation |
|
|
875
|
+
|
|
876
|
+
Each request gives up after `timeoutMillis` without progress, and each upload ends within six
|
|
877
|
+
export timeouts and a minute of being queued (two minutes at the default
|
|
878
|
+
`timeoutMillis` of 10000), so a 1 GB file needs about 8.5 MB/s at the defaults; raise
|
|
879
|
+
`timeoutMillis` (up to 60000) for slower links. `flush()`, `shutdown()` and the span and log
|
|
880
|
+
processors' `forceFlush()` and `shutdown()` (attach mode included) wait for the uploads queued
|
|
881
|
+
before them, after their records are sent; call one of them, or a `Safe` variant with a budget
|
|
882
|
+
that covers your uploads, before the process exits. A `shutdownSafe()` whose budget runs out ends
|
|
883
|
+
queued and in-flight uploads.
|
|
884
|
+
|
|
885
|
+
A value refused when its record is exported (the receiver is known to have no upload route,
|
|
886
|
+
uploads were ended by shutdown, the value is over 1 GB, or a budget is full) is exported as before
|
|
887
|
+
uploads existed: whole attributes keep a prefix up to the limit and are listed under
|
|
888
|
+
`hue.truncated`; media parts keep their digest without a blob entry; opt-in `recordFile` uploads
|
|
889
|
+
keep the file event. A value whose upload fails after its record was exported (Hue or the store
|
|
890
|
+
refused it or failed, the network or a deadline did) keeps the form it was exported with and its
|
|
891
|
+
`hue.blobs` entry, which Hue reads as missing until a later reference to the same bytes in the
|
|
892
|
+
trace uploads them. Bytes given in memory to `recordFile` or an `Attachment` are limited to 25
|
|
893
|
+
MiB; a file given by path, a file read in place from a value and a whole attribute to 1 GB. An
|
|
837
894
|
export reports such values in a `warning` issue per cause, whose `count` is the values and
|
|
838
|
-
`traceIds` their traces,
|
|
839
|
-
in `report.
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
895
|
+
`traceIds` their traces (those refused when it runs, then those that failed once its uploads
|
|
896
|
+
settle), and counts them in `report.uploadFallbacks`; completed uploads are counted in
|
|
897
|
+
`report.uploadedValues`. Both counters include whole attributes, eligible media, attachments and
|
|
898
|
+
opt-in `recordFile` uploads. A warning never makes `flush()` throw. A receiver without the upload
|
|
899
|
+
route (a 404 that is not Hue's, a 405 or 501, or a 200 that is no reservation) is not asked again
|
|
900
|
+
for 10 minutes: records exported before it answers list entries it never stores, and values
|
|
901
|
+
exported in those 10 minutes fall back as above.
|
|
844
902
|
|
|
845
903
|
To upload a value whole, the queue holds it until export apart from `maxQueueBytes`, within a
|
|
846
|
-
128 MiB budget
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
the receiver is known to lack the upload route, is cut (its files digested) when it
|
|
851
|
-
before, and reported as not uploaded, once per file; a single value larger than the
|
|
852
|
-
is held while nothing else is. While values can be uploaded, helpers encode content whole, up to the
|
|
904
|
+
128 MiB budget shared by held values (a string charged two bytes a code unit) and the bytes of
|
|
905
|
+
uploads not yet settled, and at most 4,096 uploads wait at once; a recorded message holding
|
|
906
|
+
eligible media is held the same way. `maxQueueBytes` is still charged what the value would cost
|
|
907
|
+
cut (a message, its copy with the files as their digest). A value the budget cannot hold, or one
|
|
908
|
+
queued while the receiver is known to lack the upload route, is cut (its files digested) when it
|
|
909
|
+
is queued, as before, and reported as not uploaded, once per file; a single value larger than the
|
|
910
|
+
whole budget is held while nothing else is. While values can be uploaded, helpers encode content whole, up to the
|
|
853
911
|
upload limit and 1,048,576 values, rather than cutting it to the value limit as they record it.
|
|
854
912
|
|
|
855
913
|
Nothing is uploaded with `captureContent: false` or a setup credential (`hue_setup_…`), nor from
|
|
@@ -869,8 +927,11 @@ are not retried. Warning-only acknowledgements with zero rejected records remain
|
|
|
869
927
|
successful; the callback and issue history expose a sanitized warning. The next
|
|
870
928
|
non-overlapping flush reports new failures; overlapping callers also observe failures
|
|
871
929
|
from their shared in-flight work. Counters remain cumulative and `transport.getIssues()`
|
|
872
|
-
keeps the latest 128 sanitized issues. Each concurrent caller receives a fresh
|
|
873
|
-
|
|
930
|
+
keeps the latest 128 sanitized issues. Each concurrent caller receives a fresh drain, including
|
|
931
|
+
records emitted before its call; drains send their records one after another, and each then waits
|
|
932
|
+
for the uploads queued so far on its own, so a later drain's records never wait for an earlier
|
|
933
|
+
drain's uploads. `flushSafe()` joins a drain still sending its records and otherwise starts its
|
|
934
|
+
own. Stop request production
|
|
874
935
|
before shutdown so late spans cannot race it. A client does not own instrumented
|
|
875
936
|
operations still running in the application.
|
|
876
937
|
|
|
@@ -1079,6 +1140,6 @@ existing-provider flush callbacks and recovery. Local/CI runners remain availabl
|
|
|
1079
1140
|
|
|
1080
1141
|
Use `createHueSafe(options)` for best-effort startup. Invalid initialization returns a disabled client that keeps your `onExportIssue` hook and records the reason as an instrumentation failure. Pass `enabled: false` to disable Hue without a key or a `captureContent` choice; disabled helpers still execute the application callback, and `inject()` keeps propagating the application's own trace context. `flushSafe({ timeoutMillis: 1000 })` and `shutdownSafe({ timeoutMillis: 1000 })` return `{ ok, timedOut, report }` without rejecting. Strict initialization, connection checks and `flush()` remain available for diagnostics; do not gate application readiness or responses on them.
|
|
1081
1142
|
|
|
1082
|
-
Capture/serialization/redaction/provider failures omit unsafe telemetry, record failures, and preserve the original business result/error. Async diagnostic rejections are contained; diagnostics are rate-limited. The default `maxQueueBytes` is 8 MiB across traces/logs including in-flight work, alongside the existing record cap. `pendingBytes` is a current queue gauge; `droppedSpans`, `droppedLogs` and `instrumentationFailures` are cumulative failure counters; `uploadedValues` and `uploadFallbacks` count
|
|
1143
|
+
Capture/serialization/redaction/provider failures omit unsafe telemetry, record failures, and preserve the original business result/error. Async diagnostic rejections are contained; diagnostics are rate-limited. The default `maxQueueBytes` is 8 MiB across traces/logs including in-flight work, alongside the existing record cap. `pendingBytes` is a current queue gauge; `droppedSpans`, `droppedLogs` and `instrumentationFailures` are cumulative failure counters; `uploadedValues` and `uploadFallbacks` count completed uploads and upload fallbacks for whole attributes, eligible binary or large text media, and opt-in `recordFile` uploads. Whole-attribute fallbacks refused at export keep a prefix; media fallbacks keep a digest; helper upload fallbacks keep the file event; a value whose upload failed after export keeps its entry as well. This is a telemetry budget, not a total process memory ceiling. A timeout bounds the caller and does not cancel a borrowed provider. Never retry the business operation to recover telemetry. See [production setup](https://docs.hue.run/sdks/typescript#production-setup).
|
|
1083
1144
|
|
|
1084
1145
|
Queued records snapshot supported telemetry values when a span ends or a log is emitted; later caller mutations cannot change queued data. Resource attributes still awaiting detection are omitted with a sanitized warning. Later records include them after detection finishes; await resource detection before instrumentation when those attributes are required.
|
package/dist/attachment.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import type { ByteSource } from "./large-media.js";
|
|
1
2
|
/** How deep in a value an attachment's part may sit and still be uploaded: Hue reads media parts
|
|
2
3
|
* within ten levels of the value's root. */
|
|
3
4
|
export declare const MAX_ATTACHMENT_DEPTH = 10;
|
|
4
5
|
/** Options for {@link Attachment}. Give exactly one of `data` and `path`. */
|
|
5
6
|
export interface AttachmentOptions {
|
|
6
7
|
/** The file's bytes (a `Buffer`, typed array or `ArrayBuffer`), or a string holding them as
|
|
7
|
-
* base64 or a `data:` URL. */
|
|
8
|
+
* base64 or a `data:` URL; up to 25 MiB. */
|
|
8
9
|
data?: ArrayBufferView | ArrayBuffer | string;
|
|
9
|
-
/** A file to read
|
|
10
|
+
/** A file to read (Node.js file system): up to 25 MiB it is read when the attachment is created;
|
|
11
|
+
* a larger regular file, up to Hue's 1 GB upload limit, is hashed then and read again from disk
|
|
12
|
+
* when it is uploaded. */
|
|
10
13
|
path?: string;
|
|
11
14
|
/** The file's media type, such as `image/png` or `application/pdf`. */
|
|
12
15
|
contentType: string;
|
|
@@ -15,15 +18,50 @@ export interface AttachmentOptions {
|
|
|
15
18
|
}
|
|
16
19
|
/** What an attachment holds, read by the client and transport only. */
|
|
17
20
|
export interface AttachmentState {
|
|
18
|
-
/** The file's bytes; undefined for
|
|
21
|
+
/** The file's bytes; undefined for a file read from disk when it is uploaded, and for an
|
|
22
|
+
* attachment that could not be read or is invalid. */
|
|
19
23
|
bytes?: Uint8Array;
|
|
24
|
+
/** A file given by path over 25 MiB, read again from disk when it is uploaded. */
|
|
25
|
+
file?: PathFile;
|
|
20
26
|
sha256?: string;
|
|
27
|
+
/** The file's size; undefined for an invalid attachment. */
|
|
28
|
+
size?: number;
|
|
21
29
|
contentType: string;
|
|
22
30
|
/** The name, when it is a valid media name. */
|
|
23
31
|
name?: string;
|
|
24
32
|
/** A name was given and is not a valid media name: it is omitted and counted. */
|
|
25
33
|
invalidName: boolean;
|
|
26
34
|
}
|
|
35
|
+
/** A regular file given by path over the in-memory limit, as it was hashed when it was recorded:
|
|
36
|
+
* its absolute path, size and SHA-256. Its bytes are read again from disk when it is uploaded. */
|
|
37
|
+
export interface PathFile {
|
|
38
|
+
path: string;
|
|
39
|
+
size: number;
|
|
40
|
+
sha256: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A regular file given by path: its bytes when it is no larger than 25 MiB, and otherwise, up to
|
|
44
|
+
* Hue's 1 GB upload limit, its size and SHA-256 from one reading pass a slice at a time, never
|
|
45
|
+
* holding it whole. Undefined for a file over its limit and for anything that is no regular file
|
|
46
|
+
* (a named pipe, a device), which is opened without waiting and never read; throws for one that
|
|
47
|
+
* cannot be opened.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readPath(path: string): {
|
|
50
|
+
bytes: Uint8Array;
|
|
51
|
+
} | {
|
|
52
|
+
file: PathFile;
|
|
53
|
+
} | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* A file given by path as an upload body, read from disk a slice at a time. Each slice is hashed as
|
|
56
|
+
* it is read and sent once the next one is read; the last is sent only once the file ended at its
|
|
57
|
+
* recorded size with its recorded SHA-256. A file that changed since it was recorded, or can no
|
|
58
|
+
* longer be read, ends the read with an error before its body is complete and sets `changed`, so
|
|
59
|
+
* the upload is abandoned and the value is counted as not uploaded; so does a path that is no
|
|
60
|
+
* longer a regular file, which is opened without waiting (a named pipe in its place would wait for
|
|
61
|
+
* a writer) and never read. Hue's presigned PUT also signs the size and SHA-256, so the store
|
|
62
|
+
* refuses a body that differs.
|
|
63
|
+
*/
|
|
64
|
+
export declare function fileSource(file: PathFile): ByteSource;
|
|
27
65
|
/**
|
|
28
66
|
* A file placed in a recorded value, the equivalent of Langfuse's `LangfuseMedia` and
|
|
29
67
|
* Braintrust's `Attachment`. Put it anywhere in a `withSpan()` input or output, in `model()`
|
|
@@ -32,10 +70,16 @@ export interface AttachmentState {
|
|
|
32
70
|
* names the file by digest (`{ type: "blob", mime_type, modality, sha256, size, name }`) and
|
|
33
71
|
* uploads its bytes during export, listed under the record's `hue.blobs`.
|
|
34
72
|
*
|
|
35
|
-
* The bytes are read and hashed when the attachment is created; `data`
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
73
|
+
* The bytes are read and hashed when the attachment is created; `data` is limited to 25 MiB, as
|
|
74
|
+
* the bytes are held in memory until they are uploaded. A `path` names a regular file, opened
|
|
75
|
+
* without waiting: anything else, such as a named pipe, is invalid. One up to 25 MiB is read then
|
|
76
|
+
* too; a larger regular file, up to Hue's 1 GB upload limit, is hashed then a slice at a time and read
|
|
77
|
+
* again from disk when it is uploaded, so it must stay in place and unchanged until the client
|
|
78
|
+
* flushes: a file whose size or SHA-256 then differs is not uploaded and counts as an upload
|
|
79
|
+
* fallback, its part keeping the digest it was recorded with. Creating one never throws: an
|
|
80
|
+
* attachment without exactly one of `data` and `path`, with unreadable data, larger data or an
|
|
81
|
+
* invalid `contentType` records `null` where it is placed and counts an instrumentation failure;
|
|
82
|
+
* an invalid `name` alone is omitted and counted.
|
|
39
83
|
*/
|
|
40
84
|
export declare class Attachment {
|
|
41
85
|
/** The media type given. */
|