@hue-run/sdk 0.18.2 → 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.18.2`, `@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.18.2 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
383
- npx -y @hue-run/sdk@0.18.2 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`
@@ -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 its
463
- store's redirects, without the Langfuse keys), uploaded under the span's trace through Hue's upload route (reservation, presigned
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.18.2.tgz \
598
+ --archive .artifacts/typescript/hue-run-sdk-0.19.1.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.18.2.tgz \
609
+ --archive .artifacts/typescript/hue-run-sdk-0.19.1.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.18.2 eval --case "Refund an eligible charge" --command "node agent.js"
648
- npx -y @hue-run/sdk@0.18.2 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
649
- npx -y @hue-run/sdk@0.18.2 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
650
- npx -y @hue-run/sdk@0.18.2 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>
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.18.2 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
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 → Integrations & API keys** and expose it to this server-side process as `HUE_API_KEY`. A **Tracing only** key cannot author datasets or evaluation runs.
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 share the export's upload budget and the 128
222
- MiB held for values awaiting upload, and fall back without failing the export; a file used more than
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
 
@@ -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
@@ -293,10 +300,11 @@ the GenAI conventions and `client.media.resolve` write a `blob` or `media` part'
293
300
  file's base64 and is decoded, so `YWJj` hashes as `abc`; anything else is the file's own UTF-8
294
301
  text. The digest matches `hue.recordFile`'s digest for the same bytes.
295
302
 
296
- A file that is not uploaded (the upload failed, the receiver has no upload route, or a budget ran
297
- out) keeps its digest without an entry in the recorded messages (`gen_ai.input.messages`,
298
- `gen_ai.output.messages`, `ai.prompt.messages`); in every other attribute it keeps its bytes
299
- inline under the ordinary value limit.
303
+ A file refused when its record is exported (the receiver is known to have no upload route, uploads
304
+ were ended by shutdown, or a budget ran out) keeps its digest without an entry in the recorded
305
+ messages (`gen_ai.input.messages`, `gen_ai.output.messages`, `ai.prompt.messages`); in every other
306
+ attribute it keeps its bytes inline under the ordinary value limit. A file whose upload fails after
307
+ its record was exported keeps its digest form and its entry, which Hue reads as missing.
300
308
 
301
309
  Log records upload their media the same way when they are correlated with a span:
302
310
  `recordMessages`'s `gen_ai.client.inference.operation.details` record and any `gen_ai.*` event
@@ -305,10 +313,11 @@ them in its own `hue.blobs`, keyed `body#<JSON pointer>` into the body
305
313
  (`body#/gen_ai.input.messages/0/parts/1`, or `body#/content/1` for a deprecated
306
314
  `gen_ai.user.message` event) or `<attribute>#<JSON pointer>` for the attributes above. One step
307
315
  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 that is not uploaded is placed as
309
- log records were before they uploaded: a file over 64 KiB in a recorded message attribute becomes
310
- its digest and any other file keeps its bytes; text still over the value limit is cut inline and
311
- listed under `hue.truncated`. While uploads are available, `recordMessages` records a body over
316
+ stays as it is, and no log value is uploaded whole. A log file refused when its record is exported
317
+ is placed as log records were before they uploaded: a file over 64 KiB in a recorded message
318
+ attribute becomes its digest and any other file keeps its bytes; text still over the value limit is
319
+ cut inline and listed under `hue.truncated`. One whose upload fails afterwards keeps its digest and
320
+ its entry, as on a span. While uploads are available, `recordMessages` records a body over
312
321
  the value limit when it fits the limit with its files as their digest.
313
322
 
314
323
  Inspection is bounded to 8 Mi UTF-16 code units of text per attribute and 16 Mi per record, a log
@@ -357,8 +366,8 @@ await hue.withSpan(
357
366
  Give exactly one of `data` (a `Buffer`, typed array, `ArrayBuffer`, or a base64 string or `data:`
358
367
  URL) and `path` (a file on the file system), a `contentType` and optionally a `name`. `data` is
359
368
  read and hashed when the attachment is created and is limited to 25 MiB, the `recordFile` limit:
360
- it is held in memory from then until export, within the 128 MiB budget that queued spans share for
361
- values awaiting upload. A `path` names a regular file: anything else, such as a named pipe, is
369
+ it is held in memory from then until it is uploaded, within the 128 MiB budget for values awaiting
370
+ upload. A `path` names a regular file: anything else, such as a named pipe, is
362
371
  refused without waiting on it. A `path` up to 25 MiB is read the same way. A larger regular file, up to
363
372
  Hue's 1 GB upload limit, is not held: it is hashed a 1 MiB slice at a time when the attachment is
364
373
  created and read again from disk when it is uploaded, each slice hashed as it is sent and the last
@@ -372,7 +381,7 @@ it was recorded with.
372
381
  At its position the attachment is recorded as a GenAI `blob` part that names the file by digest,
373
382
  `{"type":"blob","mime_type":"application/pdf","modality":"document","sha256":"<64 hex>","size":48213,"name":"contract.pdf"}`,
374
383
  with `modality` read from the content type (`image`, `audio`, `video`, `document` or `file`).
375
- Export uploads the bytes and lists them under the record's `hue.blobs`, keyed
384
+ Export lists the bytes under the record's `hue.blobs` and uploads them once the record is sent, keyed
376
385
  `<attribute>#<JSON pointer to the part>`: `input.value#/contract` above, `metadata#/scan` for the
377
386
  attribute, and `body#/gen_ai.input.messages/0/parts/1` for a part in a `recordMessages()` log
378
387
  record, which uploads with the other media in the body. An attribute value that is an attachment,
@@ -382,9 +391,10 @@ span's metadata files.
382
391
 
383
392
  With `captureContent: false` or a setup credential no bytes leave the process: a metadata
384
393
  attribute still records the part, without its name, and content values are not recorded at all. A
385
- part the redactor removes or rewrites is not uploaded. A part that cannot be uploaded keeps its
386
- digest and is counted in `report.uploadFallbacks`, as is one nested more than 10 levels deep in its
387
- value, where Hue does not read parts, or one in a value cut to the value limit. An attachment in a
394
+ part the redactor removes or rewrites is not uploaded. A part refused when its record is exported
395
+ keeps its digest without an entry, and one whose upload fails afterwards keeps its digest and its
396
+ entry; either is counted in `report.uploadFallbacks`, as is one nested more than 10 levels deep in
397
+ its value, where Hue does not read parts, or one in a value cut to the value limit. An attachment in a
388
398
  value that is still over the value limit with its files as digest parts is uploaded and listed as
389
399
  usual, and `client.media.fetch({ traceId, sha256 })` and Hue's span read reach it by digest; the
390
400
  value itself uploads whole and keeps only its first 16 KiB inline, so readers do not place the file
@@ -418,10 +428,14 @@ regular file, up to Hue's 1 GB upload limit, is hashed a slice at a time now and
418
428
  read again from disk when it is uploaded, as an [`Attachment`](#attach-a-file-anywhere) given by
419
429
  path is: it uploads only if it still has the size and SHA-256 it was recorded with. A file over
420
430
  1 GB, a path that is no regular file, or `data` and `path` together, is omitted and counted. Upload requires supplied bytes or a
421
- path, content capture and an ordinary project key. It runs during export and adds a `hue.blobs` entry to the
422
- same span, keyed `event:hue.file:<sha256>`, while preserving the event. An export fits every
423
- span's events and entries before it uploads any of these files, then uploads the files of all its
424
- spans together, four at a time; a file whose event was redacted or shed is not uploaded.
431
+ path, content capture and an ordinary project key. An upload that cannot be held when the file is
432
+ recorded (a span's 65th, a receiver known to lack the upload route, or a full
433
+ [held-value budget](#values-over-the-inline-limit), which uploads not yet settled share) keeps the
434
+ event without its bytes and is counted as an upload fallback, with a warning, and as an
435
+ instrumentation failure. Export adds a `hue.blobs` entry to the same span,
436
+ keyed `event:hue.file:<sha256>`, while preserving the event, once it has fitted the span's events
437
+ and entries; the upload runs after the span is sent, four at a time with the export's other
438
+ uploads. A file whose event was redacted or shed is not uploaded.
425
439
 
426
440
  The event carries `hue.file.sha256`, `hue.file.role` (`input`, `attachment` or `output`),
427
441
  `hue.file.media_type` and `hue.file.size` in both capture modes. `hue.file.name` requires
@@ -509,6 +523,7 @@ the recorded model call again with the real image, document or audio. See
509
523
  | --- | --- | --- |
510
524
  | Base64 data URIs in an observation's input, output or metadata | | [Automatic capture](#automatically-captured-media), for the attributes and shapes listed there |
511
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 |
512
527
  | `resolveMediaReferences({ obj, resolveWith: "base64DataUri" })` | | `client.media.resolve(obj, { traceId })` |
513
528
  | `GET /api/public/media/{mediaId}` | `ReadonlyAttachment.data()` | `client.media.fetch({ traceId, sha256 })` |
514
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 |
@@ -521,8 +536,8 @@ the Langfuse project's own keys (`LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, a
521
536
  `HUE_API_KEY`:
522
537
 
523
538
  ```sh
524
- npx -y @hue-run/sdk@0.18.2 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
525
- npx -y @hue-run/sdk@0.18.2 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
526
541
  ```
527
542
 
528
543
  - It reads Langfuse Cloud and self-hosted v4 through the Observations API v2, and self-hosted v3
@@ -769,7 +784,10 @@ Collector `otlp` receiver with `http.traces_url_path` and `logs_url_path` set to
769
784
  forwarding to Jaeger or the debug exporter) and pass any placeholder `apiKey`; HTTP is allowed
770
785
  for loopback origins. `checkConnection()` and `verifyTrace()` are Hue-only diagnostics and are
771
786
  not available against a generic collector. Set `liveSpans: false` so Hue-specific placeholders
772
- do not reach the collector's other destinations.
787
+ do not reach the collector's other destinations. A collector has no upload route: the records
788
+ exported before it answers an upload carry their large values and media as 16 KiB prefixes or
789
+ digests with `hue.blobs` entries that never resolve, and for 10 minutes after each answer values are
790
+ cut inline, as described in [Values over the inline limit](#values-over-the-inline-limit).
773
791
 
774
792
  A collector on a private network is not loopback: a docker-compose sibling such as
775
793
  `http://otel-collector:4318` or an in-cluster service requires the explicit opt-in
@@ -800,7 +818,10 @@ until it is sent. When `shutdownSafe()`'s budget runs out first, a client that o
800
818
  ends the hold: its records are reported lost to the rate limit and the process can exit. In attach
801
819
  mode the application's providers drain with their own timeout (OpenTelemetry's default is 30
802
820
  seconds), so a flush during a longer hold can report a provider flush failure while the held
803
- export continues.
821
+ export continues. Uploads of values over the inline limit never hold an export or draw on its
822
+ hold: they run after their records are sent. `flush()` waits for them once the providers have
823
+ drained; an application's own provider `forceFlush()` in attach mode waits for them within its
824
+ provider timeout, which can then report a provider flush failure while uploads continue.
804
825
 
805
826
  Requests are measured as Hue's receiver measures them: after gzip against its request limit and
806
827
  after decompression against its decoded limit. Until a response advertises its own limits
@@ -829,54 +850,72 @@ Hue keeps a value inline up to its value limit (1 MiB unless the receiver advert
829
850
  `Hue-Max-Value-Bytes`). A span's own string attribute larger than that (helper content such as
830
851
  `input.value`, `output.value` and tool results, or another instrumentation's attribute), and the
831
852
  files described in [Images, documents and audio](#images-documents-and-audio), are uploaded to Hue
832
- apart from their record, up to Hue's upload limit of 1,000,000,000 bytes:
853
+ apart from their record, up to Hue's upload limit of 1,000,000,000 bytes. As with Langfuse media,
854
+ the record is sent first and its bytes follow:
833
855
 
834
856
  1. Export computes the SHA-256 of the value's bytes (a string as UTF-8, a file as its own bytes)
835
- and reserves it with `POST /api/v1/otlp/blobs`, authenticated with the project key. Hue answers
836
- that it already stores the value under the trace, and nothing is uploaded, or with a presigned
837
- URL.
838
- 2. The bytes go straight to the project's evidence store in one `PUT` to that URL, with exactly the
857
+ and places the value: whole string values and large text media keep a UTF-8-safe 16 KiB prefix,
858
+ and binary media keeps its digest on the part. The record lists each value in `hue.blobs`, at
859
+ most 64 compact JSON strings with fields ordered `key`, `sha256`, `size`, `content_type` (the
860
+ type this reference declared) and optional `name`:
861
+ `{"key":"output.value","sha256":"<64 hex>","size":5242880,"content_type":"application/json"}`.
862
+ A whole value is listed under `hue.truncated` as well, since its inline text is a prefix. The
863
+ record is then sent.
864
+ 2. In the background, the upload reserves the value with `POST /api/v1/otlp/blobs`, authenticated
865
+ with the project key. Hue answers that it already stores the value under the trace, and nothing
866
+ is uploaded, or with a presigned URL.
867
+ 3. The bytes go straight to the project's evidence store in one `PUT` to that URL, with exactly the
839
868
  headers Hue signed and never the project key (over HTTPS, or plain HTTP to a loopback store or
840
869
  with `allowInsecureHttp`), and
841
870
  the upload is confirmed with `POST /api/v1/otlp/blobs/complete`. A PUT the store answers with
842
871
  412 (the same value uploaded at the same time by another span) counts as stored.
843
- 3. Whole string values and large text media keep a UTF-8-safe 16 KiB prefix. Binary media keeps
844
- its digest on the part. The record lists stored values in `hue.blobs`, at most 64 compact JSON
845
- strings with fields ordered `key`, `sha256`, `size`, `content_type` and optional `name`:
846
- `{"key":"output.value","sha256":"<64 hex>","size":5242880,"content_type":"application/json"}`.
847
- A whole value is listed under `hue.truncated` as well, since its inline text is a prefix.
848
-
849
- Hue stores the value under the trace, so the trace's retention, deletion and erasure cover it.
850
- Uploads run in the export, never on the application's thread: at most four at a time, each request
851
- within the export timeout, and all of one export's uploads within six export timeouts (60 seconds
852
- at the default `timeoutMillis` of 10000), so a 1 GB file needs about 17 MB/s at the defaults;
853
- raise `timeoutMillis` (up to 60000) for slower links. A
854
- transient failure is retried once. A rate-limited reservation (429) waits out its `Retry-After`
855
- within the export's 60-second hold. `flush()` and `shutdown()` wait for uploads like any export
856
- work, and a `shutdownSafe()` whose budget runs out ends the uploads in flight.
857
-
858
- A value that cannot be uploaded (Hue or the store refused it or failed, the network or a deadline
859
- did, the receiver has no upload route, or the value is over 1 GB) is exported as before uploads
860
- existed: whole attributes keep a prefix up to the limit and are listed under `hue.truncated`;
861
- media parts keep their digest without a blob entry; failed opt-in `recordFile` uploads preserve
862
- the file event. Bytes given in memory to `recordFile` or an `Attachment` are limited to 25 MiB; a
863
- file given by path, a file read in place from a value and a whole attribute to 1 GB. Each
872
+
873
+ Hue stores the value under the trace, so the trace's retention, deletion and erasure cover it, and
874
+ reads an entry's bytes whenever they arrive: until then Hue reports the value as missing. Uploads
875
+ never run on the application's thread and never delay, hold or drop a record: hashing has its own
876
+ four places, at most four uploads run at once, and none draws on an export's 60-second hold.
877
+
878
+ | Request | Tried | Waits before trying again | Not tried again |
879
+ | --- | --- | --- | --- |
880
+ | 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 |
881
+ | `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 |
882
+ | Completion | Once | None | Its outcome is not read: Hue completes the value on its next reservation |
883
+
884
+ Each request gives up after `timeoutMillis` without progress, and each upload ends within six
885
+ export timeouts and a minute of being queued (two minutes at the default
886
+ `timeoutMillis` of 10000), so a 1 GB file needs about 8.5 MB/s at the defaults; raise
887
+ `timeoutMillis` (up to 60000) for slower links. `flush()`, `shutdown()` and the span and log
888
+ processors' `forceFlush()` and `shutdown()` (attach mode included) wait for the uploads queued
889
+ before them, after their records are sent; call one of them, or a `Safe` variant with a budget
890
+ that covers your uploads, before the process exits. A `shutdownSafe()` whose budget runs out ends
891
+ queued and in-flight uploads.
892
+
893
+ A value refused when its record is exported (the receiver is known to have no upload route,
894
+ uploads were ended by shutdown, the value is over 1 GB, or a budget is full) is exported as before
895
+ uploads existed: whole attributes keep a prefix up to the limit and are listed under
896
+ `hue.truncated`; media parts keep their digest without a blob entry; opt-in `recordFile` uploads
897
+ keep the file event. A value whose upload fails after its record was exported (Hue or the store
898
+ refused it or failed, the network or a deadline did) keeps the form it was exported with and its
899
+ `hue.blobs` entry, which Hue reads as missing until a later reference to the same bytes in the
900
+ trace uploads them. Bytes given in memory to `recordFile` or an `Attachment` are limited to 25
901
+ MiB; a file given by path, a file read in place from a value and a whole attribute to 1 GB. An
864
902
  export reports such values in a `warning` issue per cause, whose `count` is the values and
865
- `traceIds` their traces, and counts them in `report.uploadFallbacks`; successful uploads are counted
866
- in `report.uploadedValues`. Both counters include whole attributes, eligible media, attachments
867
- and opt-in `recordFile` uploads. A warning never makes `flush()` throw. After a transient failure, or
868
- a refusal that is not about the value alone, uploads pause for 30 seconds, and a receiver without
869
- the upload route (a 404 that is not Hue's, a 405 or 501, or a 200 that is no reservation) is not
870
- asked again for 10 minutes, so an unavailable store or another OTLP receiver meets no retry storm.
903
+ `traceIds` their traces (those refused when it runs, then those that failed once its uploads
904
+ settle), and counts them in `report.uploadFallbacks`; completed uploads are counted in
905
+ `report.uploadedValues`. Both counters include whole attributes, eligible media, attachments and
906
+ opt-in `recordFile` uploads. A warning never makes `flush()` throw. A receiver without the upload
907
+ route (a 404 that is not Hue's, a 405 or 501, or a 200 that is no reservation) is not asked again
908
+ for 10 minutes: records exported before it answers list entries it never stores, and values
909
+ exported in those 10 minutes fall back as above.
871
910
 
872
911
  To upload a value whole, the queue holds it until export apart from `maxQueueBytes`, within a
873
- 128 MiB budget of held values (a string charged two bytes a code unit); a recorded message holding
874
- eligible media is held the same way. `maxQueueBytes` is still charged what the value
875
- would cost cut (a message, its copy with the files as their digest), so one that cannot be
876
- uploaded is exported exactly as it was before. A value the budget cannot hold, or one queued while
877
- the receiver is known to lack the upload route, is cut (its files digested) when it is queued, as
878
- before, and reported as not uploaded, once per file; a single value larger than the whole budget
879
- is held while nothing else is. While values can be uploaded, helpers encode content whole, up to the
912
+ 128 MiB budget shared by held values (a string charged two bytes a code unit) and the bytes of
913
+ uploads not yet settled, and at most 4,096 uploads wait at once; a recorded message holding
914
+ eligible media is held the same way. `maxQueueBytes` is still charged what the value would cost
915
+ cut (a message, its copy with the files as their digest). A value the budget cannot hold, or one
916
+ queued while the receiver is known to lack the upload route, is cut (its files digested) when it
917
+ is queued, as before, and reported as not uploaded, once per file; a single value larger than the
918
+ whole budget is held while nothing else is. While values can be uploaded, helpers encode content whole, up to the
880
919
  upload limit and 1,048,576 values, rather than cutting it to the value limit as they record it.
881
920
 
882
921
  Nothing is uploaded with `captureContent: false` or a setup credential (`hue_setup_…`), nor from
@@ -896,8 +935,11 @@ are not retried. Warning-only acknowledgements with zero rejected records remain
896
935
  successful; the callback and issue history expose a sanitized warning. The next
897
936
  non-overlapping flush reports new failures; overlapping callers also observe failures
898
937
  from their shared in-flight work. Counters remain cumulative and `transport.getIssues()`
899
- keeps the latest 128 sanitized issues. Each concurrent caller receives a fresh serialized
900
- drain, including records emitted before its call. Stop request production
938
+ keeps the latest 128 sanitized issues. Each concurrent caller receives a fresh drain, including
939
+ records emitted before its call; drains send their records one after another, and each then waits
940
+ for the uploads queued so far on its own, so a later drain's records never wait for an earlier
941
+ drain's uploads. `flushSafe()` joins a drain still sending its records and otherwise starts its
942
+ own. Stop request production
901
943
  before shutdown so late spans cannot race it. A client does not own instrumented
902
944
  operations still running in the application.
903
945
 
@@ -1106,6 +1148,6 @@ existing-provider flush callbacks and recovery. Local/CI runners remain availabl
1106
1148
 
1107
1149
  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.
1108
1150
 
1109
- 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 successful uploads and upload fallbacks for whole attributes, eligible binary or large text media, and opt-in `recordFile` uploads. Whole-attribute fallbacks keep a prefix; media fallbacks keep a digest; helper upload fallbacks keep the file event. 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).
1151
+ 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).
1110
1152
 
1111
1153
  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.