@hue-run/sdk 0.20.5 → 0.20.7

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.20.5`, `@opentelemetry/api@1.9.1` and
45
+ not a completed integration. TypeScript uses `@hue-run/sdk@0.20.7`, `@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
 
@@ -381,8 +381,8 @@ and Hue never receives or stores them. The command is part of the `hue` executab
381
381
  # LANGFUSE_PUBLIC_KEY=pk-lf-...
382
382
  # LANGFUSE_SECRET_KEY=sk-lf-...
383
383
  # LANGFUSE_BASE_URL=https://cloud.langfuse.com
384
- npx -y @hue-run/sdk@0.20.5 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
385
- npx -y @hue-run/sdk@0.20.5 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
384
+ npx -y @hue-run/sdk@0.20.7 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
385
+ npx -y @hue-run/sdk@0.20.7 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
386
386
  ```
387
387
 
388
388
  `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` come from the environment or `--env-path`
@@ -430,17 +430,25 @@ without a parent are its children, and it lasts from the earliest of its timesta
430
430
  to their last end. Spans carry `service.name` `langfuse` (`--service-name` sets another) and the
431
431
  instrumentation scope `@hue-run/sdk/import/langfuse`.
432
432
 
433
+ **Environment and Worlds.** A span whose observation, or v1 trace, records an environment also
434
+ carries it as recorded in `hue.import.source_environment`, which marks the trace as imported; one
435
+ that records none carries neither attribute, and the resource never declares
436
+ `deployment.environment.name`, since one import can mix environments. A trace adds to its user's
437
+ World only when Langfuse recorded it in its `production` environment: Hue reads the root's
438
+ `langfuse.environment` as the trace's deployment environment, and never takes an imported trace in
439
+ which any span names another environment, such as Langfuse's `default`, as production history.
440
+
433
441
  **Ids and resuming.** A Langfuse id that already is an OpenTelemetry id (32 lowercase hex digits for
434
442
  a trace, 16 for an observation, as Langfuse's OpenTelemetry SDKs write them) is kept; any other id,
435
443
  such as an older SDK's UUID, becomes one derived from it with SHA-256. The original ids are kept as
436
444
  `langfuse.trace.id` and `langfuse.observation.id`. Running the same command again sends the same
437
445
  spans, and Hue keeps one copy of an exact replay of a completed span, so an interrupted or partly
438
446
  failed import is resumed by running it again. Hue refuses a different payload for a span it already
439
- holds: keep `--media`, `--no-content` and `--service-name` the same when resuming, and expect a span
440
- whose Langfuse data changed after it was imported to be refused. For the same reason the import
441
- sends nothing that depends on the run: a trace's root never carries `hue.sdk.dropped_records`, and
442
- files and large values are stored before their span is sent. Leave out of the window any traces that
443
- already reached Hue through OpenTelemetry.
447
+ holds: keep `--media`, `--no-content`, `--service-name` and the SDK version the same when
448
+ resuming, and expect a span whose Langfuse data changed after it was imported to be refused. For
449
+ the same reason the import sends nothing that depends on the run: a trace's root never carries
450
+ `hue.sdk.dropped_records`, and files and large values are stored before their span is sent. Leave
451
+ out of the window any traces that already reached Hue through OpenTelemetry.
444
452
 
445
453
  **Export and content.** Spans go through the SDK's own exporter, as `createHue` sends them: batched,
446
454
  compressed, retried and within the limits Hue advertises. Before a span is sent, its content is
@@ -528,8 +536,9 @@ instrumentation scope `hue.import.atif`, and the run's agent span is named `invo
528
536
  <agent.name>` from the export's own agent. ATIF records instants, not durations, so model and tool
529
537
  spans are points in time, and a tool's result is kept as text with no error status of the
530
538
  importer's making. The same trace in two files is one trace when the copies agree and a refusal
531
- when they differ; a trace with more than 100,000 spans, 64 batches or 1,000 values is refused and
532
- named. Nothing about where a file sat or what it was called reaches a span: a renamed, reordered
539
+ when they differ; a trace with more than 100,000 spans, 512 batches or 10,000 values is refused and
540
+ named, and an input of more than 25,000 traces, or whose manifest would exceed 32 MiB (4 MiB
541
+ compressed), is refused whole. Nothing about where a file sat or what it was called reaches a span: a renamed, reordered
533
542
  or re-zipped copy of the bundle prepares to byte-identical batches and the same manifest digest.
534
543
 
535
544
  The report (`--json` prints it as one object) names `manifest_digest`, `totals` (traces, batches,
@@ -545,7 +554,7 @@ no upload has started in it.
545
554
 
546
555
  **Authorize.** Call `prepare_trace_import` over the Hue MCP with the report's `manifest_digest`,
547
556
  `totals` and `source`. It answers the import id, `upload_url` and an `authorization` (`hue_it_` and
548
- 43 characters) valid for an hour. Save its answer, or the authorization alone, in a private file
557
+ 43 characters) valid for a day. Save its answer, or the authorization alone, in a private file
549
558
  (mode 0600; never in a transcript, a message or a shell history). The same digest in the same
550
559
  project is the same import: calling again renews the authorization for an interrupted upload and
551
560
  revokes the earlier one.
@@ -689,7 +698,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
689
698
  # Set project to an existing supported fixture; use the same directory on resume.
690
699
  project=/absolute/path/to/supported-fixture
691
700
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
692
- --archive .artifacts/typescript/hue-run-sdk-0.20.5.tgz \
701
+ --archive .artifacts/typescript/hue-run-sdk-0.20.7.tgz \
693
702
  --origin https://STAGING_ORIGIN \
694
703
  --project "$project" --command setup \
695
704
  --evidence .context/setup-staging-before-claim.json
@@ -700,7 +709,7 @@ the private local handoff and finish the real browser claim, then reconcile the
700
709
 
701
710
  ```sh
702
711
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
703
- --archive .artifacts/typescript/hue-run-sdk-0.20.5.tgz \
712
+ --archive .artifacts/typescript/hue-run-sdk-0.20.7.tgz \
704
713
  --origin https://STAGING_ORIGIN \
705
714
  --project "$project" --command claim \
706
715
  --evidence .context/setup-staging-after-claim.json
@@ -742,10 +751,10 @@ otherwise. Never hand the agent Hue-native tools (`context.tools`, `context.mcp`
742
751
  production agent lacks. Pin the SDK, so the agent's repository gains no dependency:
743
752
 
744
753
  ```sh
745
- npx -y @hue-run/sdk@0.20.5 eval --case "Refund an eligible charge" --command "node agent.js"
746
- npx -y @hue-run/sdk@0.20.5 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
747
- npx -y @hue-run/sdk@0.20.5 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
748
- npx -y @hue-run/sdk@0.20.5 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
754
+ npx -y @hue-run/sdk@0.20.7 eval --case "Refund an eligible charge" --command "node agent.js"
755
+ npx -y @hue-run/sdk@0.20.7 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
756
+ npx -y @hue-run/sdk@0.20.7 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
757
+ npx -y @hue-run/sdk@0.20.7 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
749
758
  ```
750
759
 
751
760
  One invocation creates one run holding every selected case; it prints the run URL
@@ -783,7 +792,7 @@ export default function runMyAgent(inputs: JsonValue, context: SimulationTargetC
783
792
  ```
784
793
 
785
794
  ```sh
786
- npx -y @hue-run/sdk@0.20.5 eval --case "Refund an eligible charge" ./hue-agent.ts --content
795
+ npx -y @hue-run/sdk@0.20.7 eval --case "Refund an eligible charge" ./hue-agent.ts --content
787
796
  ```
788
797
 
789
798
  `--scenario` remains an alias for `--case` for existing scripts. Pass one selection flag.
package/README.md CHANGED
@@ -536,8 +536,8 @@ the Langfuse project's own keys (`LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, a
536
536
  `HUE_API_KEY`:
537
537
 
538
538
  ```sh
539
- npx -y @hue-run/sdk@0.20.5 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
540
- npx -y @hue-run/sdk@0.20.5 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
539
+ npx -y @hue-run/sdk@0.20.7 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
540
+ npx -y @hue-run/sdk@0.20.7 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
541
541
  ```
542
542
 
543
543
  - It reads Langfuse Cloud and self-hosted v4 through the Observations API v2, and self-hosted v3
@@ -547,6 +547,8 @@ npx -y @hue-run/sdk@0.20.5 import langfuse --env-path .env.langfuse --from 2026-
547
547
  `langfuse.observation.*` and `langfuse.trace.*` attributes Hue reads, the user, session, release
548
548
  and environment, and the model and token counts as GenAI attributes. Hex ids are kept and other
549
549
  ids derived from them, so running the same command again resumes an import without duplicates.
550
+ - Langfuse's environment is also sent, as recorded, as `hue.import.source_environment`; only
551
+ traces Langfuse recorded in `production` add to a user's World.
550
552
  - Spans are exported by the SDK's transport with its content-capture default; `--no-content`
551
553
  sends metadata only.
552
554
  - With `--media`, files that Langfuse media references point to are downloaded and uploaded under
@@ -26,7 +26,7 @@ export interface ImportCommandIo {
26
26
  * in flight do not hold it until their requests time out. */
27
27
  exit?: (code: number) => void;
28
28
  }
29
- export declare const IMPORT_USAGE = "Usage: hue import langfuse [options]\n\nCopy traces from a Langfuse project into Hue. The import runs on this machine with your own\nLangfuse keys, which are sent only to Langfuse. Each observation becomes a span with the\nlangfuse.* attributes Hue reads; ids are kept, so running the same command again resumes an\nimport without duplicating what was already sent.\n\nLangfuse (keys from the environment or --env-path; the secret key never from the command line):\n LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY The Langfuse project's API keys\n LANGFUSE_BASE_URL or LANGFUSE_HOST Langfuse host (default https://cloud.langfuse.com)\n --langfuse-host URL Langfuse host, instead of the variables\n --langfuse-public-key KEY Public key, instead of LANGFUSE_PUBLIC_KEY\n\nHue:\n HUE_API_KEY The Hue project key the traces are sent with (environment, --env-path\n or ./.env.hue); not needed with --dry-run\n --origin URL Hue origin (default HUE_BASE_URL, then https://app.hue.run)\n --service-name NAME service.name of the imported spans (default langfuse)\n\nSelection (newest first):\n --from TIME Traces that started at or after TIME: an ISO 8601 date (midnight UTC)\n or a date and time with a zone, such as 2026-01-01T09:00:00Z\n --to TIME Traces that started before TIME (default: an hour before the command\n starts, so traces still running are left for a later run)\n --limit N At most N traces\n --trace-id ID This Langfuse trace (repeatable); not with --from, --to or --limit\n\nContent:\n --media Download the files Langfuse media references (@@@langfuseMedia:\u2026@@@)\n point to and upload them to Hue; without it, references stay as text\n --no-content Send metadata only: no inputs, outputs, status messages or model\n parameters (content is sent by default, as the SDK records it)\n\nRun:\n --dry-run Read Langfuse and count what would be imported; send nothing to Hue\n --yes Import without asking for confirmation (required without a terminal)\n --env-path PATH Load a dotenv file first (--env-file also works when it exists);\n ./.env.hue loads when HUE_API_KEY is not set. Variables already set win\n -h, --help Show this help\n\nExit codes: 0 every trace was imported, 1 a trace, span or file was not, 2 usage error,\n130 stopped (run the same command again to resume).";
29
+ export declare const IMPORT_USAGE = "Usage: hue import langfuse [options]\n\nCopy traces from a Langfuse project into Hue. The import runs on this machine with your own\nLangfuse keys, which are sent only to Langfuse. Each observation becomes a span with the\nlangfuse.* attributes Hue reads; ids are kept, so running the same command again resumes an\nimport without duplicating what was already sent. Langfuse's environment is also sent, as\nrecorded, as hue.import.source_environment; only traces Langfuse recorded in production add to\na user's World.\n\nLangfuse (keys from the environment or --env-path; the secret key never from the command line):\n LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY The Langfuse project's API keys\n LANGFUSE_BASE_URL or LANGFUSE_HOST Langfuse host (default https://cloud.langfuse.com)\n --langfuse-host URL Langfuse host, instead of the variables\n --langfuse-public-key KEY Public key, instead of LANGFUSE_PUBLIC_KEY\n\nHue:\n HUE_API_KEY The Hue project key the traces are sent with (environment, --env-path\n or ./.env.hue); not needed with --dry-run\n --origin URL Hue origin (default HUE_BASE_URL, then https://app.hue.run)\n --service-name NAME service.name of the imported spans (default langfuse)\n\nSelection (newest first):\n --from TIME Traces that started at or after TIME: an ISO 8601 date (midnight UTC)\n or a date and time with a zone, such as 2026-01-01T09:00:00Z\n --to TIME Traces that started before TIME (default: an hour before the command\n starts, so traces still running are left for a later run)\n --limit N At most N traces\n --trace-id ID This Langfuse trace (repeatable); not with --from, --to or --limit\n\nContent:\n --media Download the files Langfuse media references (@@@langfuseMedia:\u2026@@@)\n point to and upload them to Hue; without it, references stay as text\n --no-content Send metadata only: no inputs, outputs, status messages or model\n parameters (content is sent by default, as the SDK records it)\n\nRun:\n --dry-run Read Langfuse and count what would be imported; send nothing to Hue\n --yes Import without asking for confirmation (required without a terminal)\n --env-path PATH Load a dotenv file first (--env-file also works when it exists);\n ./.env.hue loads when HUE_API_KEY is not set. Variables already set win\n -h, --help Show this help\n\nExit codes: 0 every trace was imported, 1 a trace, span or file was not, 2 usage error,\n130 stopped (run the same command again to resume).";
30
30
  /** An instant of the window: an ISO 8601 date (midnight UTC) or a date and time with a zone. */
31
31
  export declare function parseInstant(option: string, value: string): string;
32
32
  /** The Langfuse host as a base URL without a trailing slash, or why it is refused. */
@@ -44,7 +44,9 @@ export const IMPORT_USAGE = `Usage: hue import langfuse [options]
44
44
  Copy traces from a Langfuse project into Hue. The import runs on this machine with your own
45
45
  Langfuse keys, which are sent only to Langfuse. Each observation becomes a span with the
46
46
  langfuse.* attributes Hue reads; ids are kept, so running the same command again resumes an
47
- import without duplicating what was already sent.
47
+ import without duplicating what was already sent. Langfuse's environment is also sent, as
48
+ recorded, as hue.import.source_environment; only traces Langfuse recorded in production add to
49
+ a user's World.
48
50
 
49
51
  Langfuse (keys from the environment or --env-path; the secret key never from the command line):
50
52
  LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY The Langfuse project's API keys
@@ -201,7 +201,8 @@ export interface TraceContext {
201
201
  * `langfuse.session.id`, `langfuse.release`, `langfuse.version` and `langfuse.environment`, with
202
202
  * `gen_ai.request.model` and the input and output token counts as GenAI attributes. The
203
203
  * observation's and trace's own ids are kept as `langfuse.observation.id` and
204
- * `langfuse.trace.id`. Throws for a start time that is no ISO 8601 instant.
204
+ * `langfuse.trace.id`, and its environment also as `hue.import.source_environment`. Throws for a
205
+ * start time that is no ISO 8601 instant.
205
206
  */
206
207
  export declare function observationSpan(observation: LangfuseObservation, context: TraceContext): ImportSpan;
207
208
  /** The context a v1 trace gives its observations. */
@@ -209,9 +210,9 @@ export declare function traceContext(trace: LangfuseTrace): TraceContext;
209
210
  /**
210
211
  * The span that stands for a v1 trace, the parent of its observations without one, as the trace
211
212
  * Langfuse shows above them: `langfuse.trace.*` (name, input, output, one `metadata.<key>`
212
- * attribute per key, tags, public), the user, session, release, version and environment. It
213
- * covers the trace's timestamp and its observations, `observed`. Throws for a timestamp that is
214
- * no ISO 8601 instant.
213
+ * attribute per key, tags, public), the user, session, release, version and environment (also as
214
+ * `hue.import.source_environment`). It covers the trace's timestamp and its observations,
215
+ * `observed`. Throws for a timestamp that is no ISO 8601 instant.
215
216
  */
216
217
  export declare function traceSpan(trace: LangfuseTrace, observed: TimeBounds | undefined): ImportSpan;
217
218
  /** A converted span as the OpenTelemetry SDK hands a finished span to a processor. */
@@ -719,6 +719,16 @@ function setText(into, key, value) {
719
719
  if (value !== undefined && value !== "")
720
720
  into[key] = value;
721
721
  }
722
+ /** Hue's import provenance: the source's own environment, as Langfuse recorded it. Hue's World
723
+ * capture treats a trace carrying `hue.import.*` attributes as imported, and never takes one in
724
+ * which a span names an environment other than exactly `production` as production history. */
725
+ const IMPORT_SOURCE_ENVIRONMENT = "hue.import.source_environment";
726
+ /** Langfuse's environment, verbatim, as `langfuse.environment` and as Hue's import provenance;
727
+ * an absent environment stays absent, never defaulted. */
728
+ function setEnvironment(into, environment) {
729
+ setText(into, "langfuse.environment", environment);
730
+ setText(into, IMPORT_SOURCE_ENVIRONMENT, environment);
731
+ }
722
732
  /**
723
733
  * A Langfuse observation as the span the Langfuse OpenTelemetry SDKs would have recorded:
724
734
  * `langfuse.observation.*` (type in lower case, input, output, one `metadata.<key>` attribute per
@@ -727,7 +737,8 @@ function setText(into, key, value) {
727
737
  * `langfuse.session.id`, `langfuse.release`, `langfuse.version` and `langfuse.environment`, with
728
738
  * `gen_ai.request.model` and the input and output token counts as GenAI attributes. The
729
739
  * observation's and trace's own ids are kept as `langfuse.observation.id` and
730
- * `langfuse.trace.id`. Throws for a start time that is no ISO 8601 instant.
740
+ * `langfuse.trace.id`, and its environment also as `hue.import.source_environment`. Throws for a
741
+ * start time that is no ISO 8601 instant.
731
742
  */
732
743
  export function observationSpan(observation, context) {
733
744
  const start = hrTimeOf(observation.startTime);
@@ -774,7 +785,7 @@ export function observationSpan(observation, context) {
774
785
  setText(attributes, "langfuse.session.id", observation.sessionId ?? context.sessionId);
775
786
  setText(attributes, "langfuse.release", observation.release ?? context.release);
776
787
  setText(attributes, "langfuse.version", observation.version);
777
- setText(attributes, "langfuse.environment", observation.environment);
788
+ setEnvironment(attributes, observation.environment);
778
789
  attributes["langfuse.trace.id"] = context.langfuseTraceId;
779
790
  attributes["langfuse.observation.id"] = observation.id;
780
791
  const parent = observation.parentObservationId
@@ -806,9 +817,9 @@ export function traceContext(trace) {
806
817
  /**
807
818
  * The span that stands for a v1 trace, the parent of its observations without one, as the trace
808
819
  * Langfuse shows above them: `langfuse.trace.*` (name, input, output, one `metadata.<key>`
809
- * attribute per key, tags, public), the user, session, release, version and environment. It
810
- * covers the trace's timestamp and its observations, `observed`. Throws for a timestamp that is
811
- * no ISO 8601 instant.
820
+ * attribute per key, tags, public), the user, session, release, version and environment (also as
821
+ * `hue.import.source_environment`). It covers the trace's timestamp and its observations,
822
+ * `observed`. Throws for a timestamp that is no ISO 8601 instant.
812
823
  */
813
824
  export function traceSpan(trace, observed) {
814
825
  const timestamp = hrTimeOf(trace.timestamp);
@@ -829,7 +840,7 @@ export function traceSpan(trace, observed) {
829
840
  setText(attributes, "langfuse.session.id", trace.sessionId);
830
841
  setText(attributes, "langfuse.release", trace.release);
831
842
  setText(attributes, "langfuse.version", trace.version);
832
- setText(attributes, "langfuse.environment", trace.environment);
843
+ setEnvironment(attributes, trace.environment);
833
844
  attributes["langfuse.trace.id"] = trace.id;
834
845
  return {
835
846
  traceId: hueTraceId(trace.id),
@@ -12,11 +12,14 @@ export declare const traceImportOrigins: readonly ["production", "evaluation", "
12
12
  export type TraceImportOrigin = (typeof traceImportOrigins)[number];
13
13
  /** The bounds the import routes hold a manifest to. */
14
14
  export declare const traceImportLimits: {
15
- readonly traces: 10000;
16
- readonly batchesPerTrace: 64;
17
- readonly blobsPerTrace: 1000;
15
+ readonly traces: 25000;
16
+ readonly batchesPerTrace: 512;
17
+ readonly blobsPerTrace: 10000;
18
18
  readonly spansPerTrace: 100000;
19
+ /** The manifest decoded. */
19
20
  readonly manifestBytes: number;
21
+ /** The manifest as registration takes it, gzip. */
22
+ readonly manifestWireBytes: number;
20
23
  readonly batchBytes: number;
21
24
  readonly blobBytes: 1000000000;
22
25
  };
@@ -17,11 +17,14 @@ export const traceImportFormats = ["otlp_json", "langfuse_observations", "atif"]
17
17
  export const traceImportOrigins = ["production", "evaluation", "simulation", "unknown"];
18
18
  /** The bounds the import routes hold a manifest to. */
19
19
  export const traceImportLimits = {
20
- traces: 10_000,
21
- batchesPerTrace: 64,
22
- blobsPerTrace: 1_000,
20
+ traces: 25_000,
21
+ batchesPerTrace: 512,
22
+ blobsPerTrace: 10_000,
23
23
  spansPerTrace: 100_000,
24
- manifestBytes: 8 * 1024 * 1024,
24
+ /** The manifest decoded. */
25
+ manifestBytes: 32 * 1024 * 1024,
26
+ /** The manifest as registration takes it, gzip. */
27
+ manifestWireBytes: 4 * 1024 * 1024,
25
28
  batchBytes: 4 * 1024 * 1024,
26
29
  blobBytes: 1_000_000_000,
27
30
  };
@@ -57,6 +57,9 @@ export declare class PrepareError extends Error {
57
57
  }
58
58
  /** The default output directory: `<input>.hue-import` beside the input. */
59
59
  export declare function defaultOutputDirectory(input: string): string;
60
+ /** Why a manifest cannot be registered, or undefined: Hue reads at most 32 MiB decoded and takes
61
+ * at most 4 MiB on the wire, gzip as the upload sends it. Checked here, before any upload. */
62
+ export declare function manifestTooLarge(bytes: Uint8Array): string | undefined;
60
63
  export interface PrepareOptions {
61
64
  out?: string;
62
65
  }
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { mkdir, open, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
3
3
  import { basename, join, relative, resolve, sep } from "node:path";
4
+ import { gzipSync } from "node:zlib";
4
5
  import { sdkVersion } from "../version.js";
5
6
  import { batchEntries, ConversionError, convertAtifExport, detectFormat, LangfuseTraceCollector, manifestTotals, manifestTraceHasIdentity, OtlpTraceCollector, sha256Hex, traceImportFormats, traceImportLimits, validateManifest, } from "./traces-import-convert.js";
6
7
  import { isZipSignature, ZipArchive, ZipError } from "./traces-import-zip.js";
@@ -193,6 +194,18 @@ async function prepareOutputDirectory(directory) {
193
194
  await rm(path, { force: true });
194
195
  }
195
196
  }
197
+ /** Why a manifest cannot be registered, or undefined: Hue reads at most 32 MiB decoded and takes
198
+ * at most 4 MiB on the wire, gzip as the upload sends it. Checked here, before any upload. */
199
+ export function manifestTooLarge(bytes) {
200
+ // No division by a name: the published package's import-boundary check reads one as a regex.
201
+ const mebibytes = (value) => `${Math.ceil(value * 2 ** -20)} MiB`;
202
+ if (bytes.byteLength > traceImportLimits.manifestBytes)
203
+ return `The manifest is ${mebibytes(bytes.byteLength)}, over the ${mebibytes(traceImportLimits.manifestBytes)} Hue reads; prepare the input in smaller parts`;
204
+ const wire = gzipSync(bytes, { level: 6 }).byteLength;
205
+ if (wire > traceImportLimits.manifestWireBytes)
206
+ return `The manifest is ${mebibytes(wire)} compressed, over the ${mebibytes(traceImportLimits.manifestWireBytes)} a registration takes; prepare the input in smaller parts`;
207
+ return undefined;
208
+ }
196
209
  async function writePrivate(path, bytes) {
197
210
  const temporary = `${path}.tmp`;
198
211
  await writeFile(temporary, bytes, { mode: 0o600, flag: "w" });
@@ -422,8 +435,9 @@ export async function prepareTraceBundle(input, options = {}) {
422
435
  if (invalid)
423
436
  throw new PrepareError(`The prepared manifest is not valid: ${invalid}`);
424
437
  const bytes = Buffer.from(JSON.stringify(manifest), "utf8");
425
- if (bytes.byteLength > traceImportLimits.manifestBytes)
426
- throw new PrepareError("The manifest exceeds 8 MiB; prepare the input in smaller parts");
438
+ const tooLarge = manifestTooLarge(bytes);
439
+ if (tooLarge)
440
+ throw new PrepareError(tooLarge);
427
441
  await writePrivate(join(outputDirectory, MANIFEST_FILE), bytes);
428
442
  return {
429
443
  manifest_digest: createHash("sha256").update(bytes).digest("hex"),
@@ -3,7 +3,7 @@ import { join } from "node:path";
3
3
  import { gunzipSync, gzipSync } from "node:zlib";
4
4
  import { sdkVersion } from "../version.js";
5
5
  import { manifestTotals, sha256Hex, validateManifest, } from "./traces-import-convert.js";
6
- import { MANIFEST_FILE, PROGRESS_FILE } from "./traces-import-prepare.js";
6
+ import { MANIFEST_FILE, manifestTooLarge, PROGRESS_FILE } from "./traces-import-prepare.js";
7
7
  /**
8
8
  * `hue traces import upload`: delivers a prepared bundle to Hue's import routes with the
9
9
  * short-lived import authorization `prepare_trace_import` minted. The manifest's exact bytes
@@ -229,6 +229,10 @@ export async function uploadTraceBundle(options, io = {}) {
229
229
  const invalid = validateManifest(manifest);
230
230
  if (invalid)
231
231
  throw new UploadStop(`${MANIFEST_FILE} is not valid: ${invalid}`, "bundle_invalid");
232
+ // A manifest registration would refuse (413) is refused here, before anything is sent.
233
+ const tooLarge = manifestTooLarge(manifestBytes);
234
+ if (tooLarge)
235
+ throw new UploadStop(tooLarge, "bundle_invalid");
232
236
  const manifestDigest = sha256Hex(manifestBytes);
233
237
  const totals = manifestTotals(manifest);
234
238
  // Every declared file must be present before the first request: a bundle missing a batch
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.20.5";
2
+ export declare const sdkVersion = "0.20.7";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/write-version.mjs from package.json; do not edit by hand.
2
2
  /** Package version shared by the instrumentation scope and the export User-Agent. */
3
- export const sdkVersion = "0.20.5";
3
+ export const sdkVersion = "0.20.7";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hue-run/sdk",
3
- "version": "0.20.5",
3
+ "version": "0.20.7",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "publishConfig": {