@hue-run/sdk 0.17.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 +130 -8
- package/EVALUATIONS.md +56 -2
- package/README.md +299 -37
- package/dist/attachment.d.ts +82 -0
- package/dist/attachment.js +225 -0
- package/dist/blobs.d.ts +41 -3
- package/dist/blobs.js +126 -41
- package/dist/cli/import.d.ts +35 -0
- package/dist/cli/import.js +692 -0
- package/dist/cli/langfuse.d.ts +309 -0
- package/dist/cli/langfuse.js +1075 -0
- package/dist/client.d.ts +18 -0
- package/dist/client.js +148 -13
- package/dist/evals/client.d.ts +19 -1
- package/dist/evals/client.js +91 -11
- package/dist/evals/files.d.ts +11 -1
- package/dist/evals/files.js +248 -20
- package/dist/evals/media.d.ts +221 -0
- package/dist/evals/media.js +919 -0
- package/dist/evals/runner.js +130 -106
- package/dist/evals/types.d.ts +49 -0
- package/dist/evals.d.ts +2 -0
- package/dist/evals.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inline-files.d.ts +140 -8
- package/dist/inline-files.js +572 -51
- package/dist/json-edits.d.ts +24 -0
- package/dist/json-edits.js +187 -0
- package/dist/privacy.d.ts +13 -0
- package/dist/privacy.js +84 -17
- package/dist/safety.d.ts +6 -2
- package/dist/safety.js +15 -3
- package/dist/setup/cli.js +7 -3
- package/dist/snapshot.d.ts +1 -1
- package/dist/snapshot.js +123 -29
- package/dist/transport.d.ts +10 -0
- package/dist/transport.js +296 -56
- package/dist/types.d.ts +25 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CLI.md
CHANGED
|
@@ -42,7 +42,7 @@ project manifest are refused because managers can update ancestor locks; Python
|
|
|
42
42
|
The generated `hue.setup.mjs` or `hue_setup.py` always selects `captureContent: false` /
|
|
43
43
|
`capture_content=False`. For a supported application, setup installs the dependency and adds the
|
|
44
44
|
managed import and middleware registration to the existing entrypoint; an unreferenced helper is
|
|
45
|
-
not a completed integration. TypeScript uses `@hue-run/sdk@0.
|
|
45
|
+
not a completed integration. TypeScript uses `@hue-run/sdk@0.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.
|
|
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.
|
|
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.
|
|
526
|
-
npx -y @hue-run/sdk@0.
|
|
527
|
-
npx -y @hue-run/sdk@0.
|
|
528
|
-
npx -y @hue-run/sdk@0.
|
|
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.
|
|
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
|
@@ -295,13 +295,66 @@ an `error` of type `TargetTimeout` and the world is sealed abandoned. Public pac
|
|
|
295
295
|
proves this lifecycle against local fixtures; exact installed-registry-package to hosted-facade
|
|
296
296
|
acceptance remains a post-publication Fern gate.
|
|
297
297
|
|
|
298
|
+
## Case files
|
|
299
|
+
|
|
300
|
+
`addCase` and `addEvalSetCase` pin up to 32 input files with `files`. Name an artifact the
|
|
301
|
+
project already holds as `{ artifactId, role }`, or give the file, from `bytes` or a `path`, and
|
|
302
|
+
the client uploads it as a verified artifact first:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
await client.addEvalSetCase(versionId, {
|
|
306
|
+
expectedRevision,
|
|
307
|
+
externalKey: "chart-question",
|
|
308
|
+
inputs: { messages },
|
|
309
|
+
files: [
|
|
310
|
+
{ role: "source", filename: "chart.png", contentType: "image/png", bytes: chart },
|
|
311
|
+
{ role: "evaluator_reference", filename: "answers.csv", contentType: "text/csv", path: "answers.csv" },
|
|
312
|
+
],
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
An uploaded file needs one of `outputContentTypes`, a well-formed name of 1–255 UTF-16 code units
|
|
317
|
+
(as JavaScript counts a string's `length`) without path separators or control characters, and 1
|
|
318
|
+
byte to 25 MiB. Every file, and the case itself as it will be sent (its revision, key, values and
|
|
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. 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
|
|
327
|
+
the target as `context.files`; `org_template` and `evaluator_reference` files reach only scorers.
|
|
328
|
+
|
|
329
|
+
Recorded messages copied into a case's inputs keep their large files as digest parts. Pin each file
|
|
330
|
+
to the case, and the target can send the recorded model call again with the real image, document
|
|
331
|
+
or audio:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
import { rehydrateMedia, runExperiment } from "@hue-run/sdk/evals";
|
|
335
|
+
|
|
336
|
+
await runExperiment({
|
|
337
|
+
// ...client, hue, experimentId, checkpointDirectory and the other options
|
|
338
|
+
target: async (inputs, { files }) => {
|
|
339
|
+
const { value } = await rehydrateMedia(inputs, files);
|
|
340
|
+
return callModel(value);
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
`rehydrateMedia(value, files, { as })` matches digest parts to files by SHA-256, checks each
|
|
346
|
+
file's bytes against the digest and writes them as `client.media.resolve` does (see the
|
|
347
|
+
[README](README.md#read-recorded-media-back)); `files` may also hold `{ sha256, contentType, bytes }`
|
|
348
|
+
entries. A part without a matching file stays as it is and is listed in `failures`.
|
|
349
|
+
|
|
298
350
|
## Direct cases and files
|
|
299
351
|
|
|
300
352
|
Cases without a simulated world — answer-only tasks, or document workflows with pinned files —
|
|
301
353
|
run on the same runner under the same checkpoint rules. They shipped in `@hue-run/sdk` `0.5.0`
|
|
302
354
|
and require a Hue deployment that serves case `inputFiles` on experiment
|
|
303
355
|
items, subject `files`, and the artifact reservation, upload, completion and download APIs. The
|
|
304
|
-
Python SDK
|
|
356
|
+
Python SDK's `run_experiment` and `rescore` hand pinned input files to targets and scorers;
|
|
357
|
+
generated files and the outbound worker are TypeScript-only.
|
|
305
358
|
|
|
306
359
|
Since TypeScript 0.13.2, the `hue eval --worker --command "…"` wrapper supplies both callbacks, so one
|
|
307
360
|
command worker accepts direct and world cases, including mixed eval sets. It keeps the launch
|
|
@@ -380,7 +433,8 @@ evaluator material must stay out of its reach. Verified copies live under `files
|
|
|
380
433
|
`<checkpointDirectory>/files`, created mode 0700), evaluator-only files apart from the agent's, and
|
|
381
434
|
each case gets its own private `context.outputDirectory` to write into. The files of a case pinned
|
|
382
435
|
to a world are removed when it ends, except staged outputs an interrupted upload resumes from;
|
|
383
|
-
those of a direct case stay for inspection
|
|
436
|
+
those of a direct case stay for inspection, except its evaluator-only files, which are removed when
|
|
437
|
+
it ends so no later case's agent finds them.
|
|
384
438
|
|
|
385
439
|
Return generated documents by wrapping the output in `withFiles(output, files)`. Each entry names a
|
|
386
440
|
`path` or in-memory `bytes`, a `filename`, a Hue-accepted `contentType` and at most one `primary`.
|