@hue-run/sdk 0.18.0 → 0.18.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.0`, `@opentelemetry/api@1.9.1` and
45
+ not a completed integration. TypeScript uses `@hue-run/sdk@0.18.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
 
@@ -114,6 +114,7 @@ hue setup --origin http://127.0.0.1:PORT # isolated loopback tests only
114
114
  hue login # validate keys created in Hue and store them in ./.env.hue
115
115
  hue mcp install --client claude-code # write the Hue MCP configuration for a coding agent
116
116
  hue listen --subscription <id> --forward-to http://localhost:3000/slack/events # deliver events to a local bot
117
+ hue import langfuse --from 2026-01-01 --dry-run # count a Langfuse project's traces, then import them
117
118
  ```
118
119
 
119
120
  Hosted setup accepts HTTPS origins only. HTTP is accepted solely for `localhost`, `127.0.0.1` and
@@ -365,6 +366,127 @@ codes: `0` stopped, `1` Hue refused the credential, the subscription (unknown, r
365
366
  delivers to a request URL) or the pull itself (a redirect, another refusal or an answer without
366
367
  deliveries), `2` usage error, `130` interrupted twice.
367
368
 
369
+ ## Import traces from Langfuse
370
+
371
+ `hue import langfuse` copies a Langfuse project's traces into Hue, for a team moving from Langfuse.
372
+ It runs on your machine: it reads Langfuse's public API with the project's own keys and sends the
373
+ spans with your Hue project key through the SDK's exporter, so the Langfuse keys go only to Langfuse
374
+ and Hue never receives or stores them. The command is part of the `hue` executable and runs with
375
+ `npx` whatever language the application is written in; the Python SDK has no counterpart.
376
+
377
+ ```sh
378
+ # .env.langfuse, kept out of version control:
379
+ # LANGFUSE_PUBLIC_KEY=pk-lf-...
380
+ # LANGFUSE_SECRET_KEY=sk-lf-...
381
+ # LANGFUSE_BASE_URL=https://cloud.langfuse.com
382
+ npx -y @hue-run/sdk@0.18.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
383
+ npx -y @hue-run/sdk@0.18.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
384
+ ```
385
+
386
+ `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` come from the environment or `--env-path`
387
+ (`--env-file` also works when the file exists), loaded first; a variable already set keeps its
388
+ value. `--langfuse-public-key` replaces the public key on the command line, but the secret key is
389
+ never taken from it, where other local processes and shell history can read it. The host is
390
+ `--langfuse-host`, then `LANGFUSE_BASE_URL`, then `LANGFUSE_HOST`, then `https://cloud.langfuse.com`:
391
+ use your region's host or your self-hosted URL, over HTTPS (plain HTTP only on this machine). A
392
+ request carrying the keys follows no redirect. `HUE_API_KEY` is the Hue project key the spans are
393
+ sent with, from the environment, the env file, or `./.env.hue` (where `hue login` stores it) when
394
+ neither sets it (a dry run, which needs no Hue key, reads no `./.env.hue` beside a named file); `--origin` (default `HUE_BASE_URL`, then `https://app.hue.run`) is checked with
395
+ `GET /api/v1/projects/current` before Langfuse is read. Keys are never printed.
396
+
397
+ **What is read.** The command first asks for `GET /api/public/v2/observations`, which Langfuse Cloud
398
+ and self-hosted Langfuse v4 answer. The traces are then the root observations
399
+ (`isRootObservation=true`) that started in the window, listed newest first in cursor pages, and each
400
+ trace's observations are read 100 a page with the field groups
401
+ `core,basic,time,io,metadata,model,usage,prompt,trace_context`; a page whose metadata values Langfuse
402
+ cut at 200 characters is read again with those keys expanded. A deployment that answers `404` there
403
+ (self-hosted Langfuse v3) is read through `GET /api/public/traces` (`fromTimestamp`, `toTimestamp`,
404
+ `orderBy=timestamp.desc`, `fields=core,io`) and `GET /api/public/observations?traceId=…`, in numbered
405
+ pages. An answer over 32 MiB, or a `413`, is asked for again in smaller pages.
406
+
407
+ **Selection.** `--from` and `--to` take an ISO 8601 date (midnight UTC) or a date and time with a time
408
+ zone. A trace is selected by when it started, its root observation's start time or a v1 trace's
409
+ timestamp, `--from` inclusive and `--to` exclusive. Without `--to` the window ends an hour before the
410
+ command starts, so a trace still running, or still arriving in Langfuse, is left for a later run
411
+ rather than sent half recorded. `--limit N` imports the N
412
+ newest traces; `--trace-id ID` (repeatable, up to 1,000) imports those traces only and is not combined
413
+ with the window or the limit.
414
+
415
+ **What Hue receives.** Each observation becomes a completed span as a Langfuse OpenTelemetry SDK
416
+ records it: its name, start, end (an observation without an end lasts no time) and parent, with
417
+ `langfuse.observation.type` (lower case), `langfuse.observation.input` and `.output` (a string as
418
+ recorded, any other value as its JSON), one `langfuse.observation.metadata.<key>` per metadata key,
419
+ `.level`, `.status_message`, `.model.name`, `.model.parameters`, `.usage_details`, `.cost_details`,
420
+ `.completion_start_time`, `.prompt.name` and `.prompt.version`, and the trace's
421
+ `langfuse.trace.name`, `langfuse.trace.tags`, `langfuse.user.id`, `langfuse.session.id`,
422
+ `langfuse.release`, `langfuse.version` and `langfuse.environment`. The model is also
423
+ `gen_ai.request.model`, and Langfuse's `input` and `output` usage counts are
424
+ `gen_ai.usage.input_tokens` and `gen_ai.usage.output_tokens`. An observation at level `ERROR` is an
425
+ error span with its status message. A v1 trace is also a span of its own, named as the trace and
426
+ carrying `langfuse.trace.input`, `.output`, `.metadata.<key>`, `.tags` and `.public`; its observations
427
+ without a parent are its children, and it lasts from the earliest of its timestamp and their starts
428
+ to their last end. Spans carry `service.name` `langfuse` (`--service-name` sets another) and the
429
+ instrumentation scope `@hue-run/sdk/import/langfuse`.
430
+
431
+ **Ids and resuming.** A Langfuse id that already is an OpenTelemetry id (32 lowercase hex digits for
432
+ a trace, 16 for an observation, as Langfuse's OpenTelemetry SDKs write them) is kept; any other id,
433
+ such as an older SDK's UUID, becomes one derived from it with SHA-256. The original ids are kept as
434
+ `langfuse.trace.id` and `langfuse.observation.id`. Running the same command again sends the same
435
+ spans, and Hue keeps one copy of an exact replay of a completed span, so an interrupted or partly
436
+ failed import is resumed by running it again. Hue refuses a different payload for a span it already
437
+ holds: keep `--media`, `--no-content` and `--service-name` the same when resuming, and expect a span
438
+ whose Langfuse data changed after it was imported to be refused. For the same reason the import
439
+ sends nothing that depends on the run: a trace's root never carries `hue.sdk.dropped_records`, and
440
+ files and large values are stored before their span is sent. Leave out of the window any traces that
441
+ already reached Hue through OpenTelemetry.
442
+
443
+ **Export and content.** Spans go through the SDK's own exporter, as `createHue` sends them: batched,
444
+ compressed, retried and within the limits Hue advertises. Before a span is sent, its content is
445
+ stored as that exporter stores it, with the exporter's own placement: a file inline in an attribute
446
+ Hue reads files from (a `data:` URL, or the bytes of a provider part, that Langfuse kept in place) is
447
+ uploaded and placed in its digest form, and a text value still over Hue's inline limit (1 MiB) is
448
+ uploaded whole and replaced by its first 16 KiB, each listed in `hue.blobs` (a whole value also in
449
+ `hue.truncated`). Doing this first means a file or value that cannot be stored right now holds its
450
+ span back for the next run instead of being kept inline or cut; one over a limit (Hue's size limit,
451
+ or a span's 64 entries) is placed the same way on every run and counted in the summary. Content is
452
+ captured, the SDK's default; `--no-content` sends metadata only, as `captureContent: false` does (no
453
+ inputs, outputs, status messages or model parameters; metadata, model names and usage stay, and
454
+ nothing is uploaded). The import waits for its queue to be sent every 1,024 spans or 32 MiB, so its
455
+ memory stays bounded whatever the project's size.
456
+
457
+ **Media.** Langfuse's SDKs replace a file in an input, output or metadata value with a reference,
458
+ `@@@langfuseMedia:type=<media type>|id=<id>|source=<source>@@@`. Without `--media`, references are
459
+ sent as recorded, and Hue shows them as files held in Langfuse. With `--media`, each reference where
460
+ Hue reads a file (a whole string, or the content of a provider part such as an OpenAI `image_url` or
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
464
+ `PUT`, completion), and placed in the digest form the SDK writes for media it records: a whole string
465
+ becomes `{"type":"blob","mime_type":…,"modality":…,"sha256":…,"size":…}` and a provider part loses its
466
+ bytes and gains `sha256` and `size`, every other byte of the value's text kept, each file listed in
467
+ `hue.blobs` under `<attribute>#<JSON pointer>`. Each file is downloaded once per trace. A reference
468
+ stays as recorded when Langfuse holds no bytes for it, its file is over 25 MiB, it sits inside longer
469
+ text, or its span already lists 64 files. A file that cannot be downloaded or stored right now holds
470
+ its span back: the trace is reported, and the next run sends it.
471
+
472
+ **Rate limits and failures.** A `429` from Langfuse is waited out for as long as its `Retry-After`
473
+ asks, up to 15 minutes and ten times in a row; connection failures, timeouts and `5xx` answers are
474
+ retried after 1, 2, 4, 8 and 16 seconds. Langfuse Cloud limits each organization's requests per
475
+ minute by plan, so a large project imports at that pace: a request per page of traces and per page
476
+ of each trace's observations, and one per media file. A refusal of the Langfuse keys (`401` or `403`)
477
+ or of `HUE_API_KEY` stops the import before anything is sent; a trace Langfuse cannot answer for is
478
+ reported and the import goes on.
479
+
480
+ **Output and exit codes.** `--dry-run` reads and counts traces, spans and media references, needs no
481
+ Hue key and sends nothing. Without `--yes` the command asks for confirmation in a terminal (declining
482
+ exits `1`) and refuses to start without one. Progress is printed every 100 traces or 10 seconds, then
483
+ a summary of the traces imported, the spans sent and accepted, and the media files uploaded and left
484
+ in Langfuse; each trace not fully imported is named with the reason. Ctrl+C or `SIGTERM` stops
485
+ reading, sends what was read and exits `130`; a second Ctrl+C a second or more later exits `130` at
486
+ once, abandoning exports still in flight for the next run to send. Exit codes: `0` every selected
487
+ trace was imported, `1` a trace, span or file was not (or a key was refused), `2` usage error, `130`
488
+ stopped.
489
+
368
490
  ## Local state and conflicts
369
491
 
370
492
  Before its first network write, setup creates a lowercase UUIDv4 and a 32-byte random installation
@@ -469,7 +591,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
469
591
  # Set project to an existing supported fixture; use the same directory on resume.
470
592
  project=/absolute/path/to/supported-fixture
471
593
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
472
- --archive .artifacts/typescript/hue-run-sdk-0.18.0.tgz \
594
+ --archive .artifacts/typescript/hue-run-sdk-0.18.1.tgz \
473
595
  --origin https://STAGING_ORIGIN \
474
596
  --project "$project" --command setup \
475
597
  --evidence .context/setup-staging-before-claim.json
@@ -480,7 +602,7 @@ the private local handoff and finish the real browser claim, then reconcile the
480
602
 
481
603
  ```sh
482
604
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
483
- --archive .artifacts/typescript/hue-run-sdk-0.18.0.tgz \
605
+ --archive .artifacts/typescript/hue-run-sdk-0.18.1.tgz \
484
606
  --origin https://STAGING_ORIGIN \
485
607
  --project "$project" --command claim \
486
608
  --evidence .context/setup-staging-after-claim.json
@@ -522,10 +644,10 @@ otherwise. Never hand the agent Hue-native tools (`context.tools`, `context.mcp`
522
644
  production agent lacks. Pin the SDK, so the agent's repository gains no dependency:
523
645
 
524
646
  ```sh
525
- npx -y @hue-run/sdk@0.18.0 eval --case "Refund an eligible charge" --command "node agent.js"
526
- npx -y @hue-run/sdk@0.18.0 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
527
- npx -y @hue-run/sdk@0.18.0 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
528
- npx -y @hue-run/sdk@0.18.0 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
647
+ npx -y @hue-run/sdk@0.18.1 eval --case "Refund an eligible charge" --command "node agent.js"
648
+ npx -y @hue-run/sdk@0.18.1 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.1 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
650
+ npx -y @hue-run/sdk@0.18.1 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
529
651
  ```
530
652
 
531
653
  One invocation creates one run holding every selected case; it prints the run URL
@@ -563,7 +685,7 @@ export default function runMyAgent(inputs: JsonValue, context: SimulationTargetC
563
685
  ```
564
686
 
565
687
  ```sh
566
- npx -y @hue-run/sdk@0.18.0 eval --case "Refund an eligible charge" ./hue-agent.ts --content
688
+ npx -y @hue-run/sdk@0.18.1 eval --case "Refund an eligible charge" ./hue-agent.ts --content
567
689
  ```
568
690
 
569
691
  `--scenario` remains an alias for `--case` for existing scripts. Pass one selection flag.
package/EVALUATIONS.md CHANGED
@@ -317,10 +317,13 @@ An uploaded file needs one of `outputContentTypes`, a well-formed name of 1–25
317
317
  (as JavaScript counts a string's `length`) without path separators or control characters, and 1
318
318
  byte to 25 MiB. Every file, and the case itself as it will be sent (its revision, key, values and
319
319
  size), is checked before the first upload; a file outside that policy, a `path` that cannot be
320
- read, or a case Hue would refuse throws `TypeError` or `RangeError` without a request. The
321
- reservation key names the version, the case's `externalKey`, the role, the bytes, the name and the
322
- content type, so sending the same case again reuses its artifacts and a corrected name or type
323
- reserves a new one; a reference to the artifact a file uploads as pins it once. `source`, `attached_template`, `attached_reference` and `original` files reach
320
+ read, or a case Hue would refuse throws `TypeError` or `RangeError` without a request. A `path`
321
+ is hashed a slice at a time when it is checked and read again when it is uploaded, one file at a
322
+ time, so the write holds at most one file in memory and sends only the bytes its check hashed. A
323
+ file that changed in between, or can no longer be read, sends nothing: the write throws `TypeError`
324
+ and the case is not written. The reservation key names the version, the case's
325
+ `externalKey`, the role, the bytes, the name and the content type, so sending the same case again
326
+ reuses its artifacts and a corrected name or type reserves a new one; a reference to the artifact a file uploads as pins it once. `source`, `attached_template`, `attached_reference` and `original` files reach
324
327
  the target as `context.files`; `org_template` and `evaluator_reference` files reach only scorers.
325
328
 
326
329
  Recorded messages copied into a case's inputs keep their large files as digest parts. Pin each file
package/README.md CHANGED
@@ -274,7 +274,8 @@ Each uploaded file is listed in the record's `hue.blobs`, keyed `<attribute>#<JS
274
274
  through the string, which no JSON pointer can address, so Hue matches it by its digest.
275
275
  - Text media over 64 KiB of decoded bytes in a provider part keeps a UTF-8-safe 16 KiB prefix in
276
276
  its content field instead, and its key names that field
277
- (`gen_ai.input.messages#/0/parts/1/content`). Smaller text and empty fields stay inline.
277
+ (`gen_ai.input.messages#/0/parts/1/content`). Smaller text and empty fields stay inline. A text
278
+ file a GenAI `blob`, Vertex `media` or AI SDK part holds as bare base64 takes the digest form.
278
279
 
279
280
  Only the file's own value, or the members of the part that held it, change: every other byte of the
280
281
  recorded text, its key order, number spellings, escapes and spacing included, is kept, in an encoded
@@ -286,9 +287,11 @@ Entries use the recorded media type and, when valid, the filename, name or title
286
287
  is nonblank after trimming, at most 256 UTF-16 code units and well-formed Unicode, with no C0
287
288
  characters (U+0000–U+001F) or DEL (U+007F). Invalid names are omitted from the entry without
288
289
  rewriting the original field. Data URLs and fields explicitly encoded as base64 by the provider
289
- contract are decoded before hashing. Other non-data strings with a valid declared textual media
290
- type remain raw UTF-8: `YWJj` hashes as those four characters. The digest matches
291
- `hue.recordFile`'s digest for the same bytes.
290
+ contract are decoded before hashing. Other content is read as Hue's reader reads it, whatever its
291
+ media type: content that reads as base64 (a length divisible by four, base64 characters only, as
292
+ the GenAI conventions and `client.media.resolve` write a `blob` or `media` part's content) is the
293
+ file's base64 and is decoded, so `YWJj` hashes as `abc`; anything else is the file's own UTF-8
294
+ text. The digest matches `hue.recordFile`'s digest for the same bytes.
292
295
 
293
296
  A file that is not uploaded (the upload failed, the receiver has no upload route, or a budget ran
294
297
  out) keeps its digest without an entry in the recorded messages (`gen_ai.input.messages`,
@@ -362,7 +365,11 @@ With `captureContent: false` or a setup credential no bytes leave the process: a
362
365
  attribute still records the part, without its name, and content values are not recorded at all. A
363
366
  part the redactor removes or rewrites is not uploaded. A part that cannot be uploaded keeps its
364
367
  digest and is counted in `report.uploadFallbacks`, as is one nested more than 10 levels deep in its
365
- value, where Hue does not read parts, or one in a value cut to the value limit. Creating an
368
+ value, where Hue does not read parts, or one in a value cut to the value limit. An attachment in a
369
+ value that is still over the value limit with its files as digest parts is uploaded and listed as
370
+ usual, and `client.media.fetch({ traceId, sha256 })` and Hue's span read reach it by digest; the
371
+ value itself uploads whole and keeps only its first 16 KiB inline, so readers do not place the file
372
+ at its position in the value. Creating an
366
373
  attachment never throws: one without exactly one of `data` and `path`, with unreadable or larger
367
374
  data or an invalid content type is recorded as `null` and counted as an instrumentation failure;
368
375
  an invalid name alone is omitted and counted.
@@ -480,7 +487,37 @@ the recorded model call again with the real image, document or audio. See
480
487
  | `LangfuseMedia` | `Attachment` | [`Attachment`](#attach-a-file-anywhere), at the same position |
481
488
  | `resolveMediaReferences({ obj, resolveWith: "base64DataUri" })` | | `client.media.resolve(obj, { traceId })` |
482
489
  | `GET /api/public/media/{mediaId}` | `ReadonlyAttachment.data()` | `client.media.fetch({ traceId, sha256 })` |
483
- | Media tokens (`@@@langfuseMedia:…@@@`) | `ExternalAttachment` | Left as recorded: Hue never fetches a remote URL or a file another service holds |
490
+ | 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 |
491
+
492
+ ## Import from Langfuse
493
+
494
+ `hue import langfuse` copies a Langfuse project's history into Hue. It runs on your machine with
495
+ the Langfuse project's own keys (`LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, and
496
+ `LANGFUSE_BASE_URL` or `LANGFUSE_HOST`), which go only to Langfuse, and sends the spans with your
497
+ `HUE_API_KEY`:
498
+
499
+ ```sh
500
+ npx -y @hue-run/sdk@0.18.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
501
+ npx -y @hue-run/sdk@0.18.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
502
+ ```
503
+
504
+ - It reads Langfuse Cloud and self-hosted v4 through the Observations API v2, and self-hosted v3
505
+ through the v1 traces and observations API, newest first; `--from`, `--to`, `--limit` and
506
+ `--trace-id` select traces, and `--dry-run` counts them without sending anything.
507
+ - Each observation becomes the span a Langfuse OpenTelemetry SDK records, with the
508
+ `langfuse.observation.*` and `langfuse.trace.*` attributes Hue reads, the user, session, release
509
+ and environment, and the model and token counts as GenAI attributes. Hex ids are kept and other
510
+ ids derived from them, so running the same command again resumes an import without duplicates.
511
+ - Spans are exported by the SDK's transport with its content-capture default; `--no-content`
512
+ sends metadata only.
513
+ - With `--media`, files that Langfuse media references point to are downloaded and uploaded under
514
+ the trace in the [digest form](#automatically-captured-media) the SDK writes, listed in
515
+ `hue.blobs`; a reference Langfuse holds no bytes for stays as recorded.
516
+ - Langfuse's rate limits are waited out for as long as `Retry-After` asks.
517
+
518
+ The command has no Python counterpart; it runs with `npx` for an application in any language. See
519
+ [Import traces from Langfuse](CLI.md#import-traces-from-langfuse) for every option, the attribute
520
+ mapping, media handling, limits and exit codes.
484
521
 
485
522
  ## Vercel AI SDK 6
486
523
 
package/dist/blobs.js CHANGED
@@ -626,7 +626,7 @@ export async function offloadRecord(attributes, collector, options) {
626
626
  const textual = isTextMediaType(contentType);
627
627
  if (textual && decoded.bytes.byteLength <= INLINE_FILE_LIMIT)
628
628
  return Promise.resolve();
629
- const prefix = keepsPrefix(file, textual);
629
+ const prefix = keepsPrefix(file, textual, decoded);
630
630
  const partKey = `${key}#${prefix ? file.pointer : file.partPointer}`;
631
631
  const slot = slots > 0 && isBlobKey(partKey) ? (slots--, true) : false;
632
632
  return (async () => {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `hue import langfuse`: copies a Langfuse project's traces into Hue from this machine. Langfuse
3
+ * is read with the project's own keys through its public API; each observation becomes the span
4
+ * the Langfuse OpenTelemetry SDKs would have recorded and is exported through the SDK's own
5
+ * transport, so batching, retries, limits, content capture and large-value uploads are the SDK's.
6
+ * Ids are kept or derived from Langfuse's, so a second run sends the same spans again and Hue
7
+ * keeps one copy of each: an interrupted or partly failed import is resumed by running it again.
8
+ */
9
+ /** Streams, environment and timing seams for {@link runImportCommand}; tests inject these. */
10
+ export interface ImportCommandIo {
11
+ stdout?: NodeJS.WritableStream;
12
+ stderr?: NodeJS.WritableStream;
13
+ stdin?: NodeJS.ReadableStream & {
14
+ isTTY?: boolean;
15
+ };
16
+ env?: NodeJS.ProcessEnv;
17
+ cwd?: string;
18
+ fetch?: typeof fetch;
19
+ /** Waits `ms`, resolving early when the import is stopped. */
20
+ sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
21
+ /** Registers the stop request (SIGINT and SIGTERM by default) and returns its removal. */
22
+ onInterrupt?: (listener: () => void) => () => void;
23
+ /** The current time, for the default end of the window. */
24
+ now?: () => Date;
25
+ /** Ends the process after a second stop request (`process.exit` by default), so exports still
26
+ * in flight do not hold it until their requests time out. */
27
+ exit?: (code: number) => void;
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).";
30
+ /** An instant of the window: an ISO 8601 date (midnight UTC) or a date and time with a zone. */
31
+ export declare function parseInstant(option: string, value: string): string;
32
+ /** The Langfuse host as a base URL without a trailing slash, or why it is refused. */
33
+ export declare function parseLangfuseHost(value: string): string;
34
+ /** Runs `hue import langfuse`; resolves to the exit code. */
35
+ export declare function runImportCommand(argv: string[], io?: ImportCommandIo): Promise<number>;