@hue-run/sdk 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CLI.md +27 -10
  2. package/ENVIRONMENTS.md +5 -2
  3. package/EVALUATIONS.md +6 -1
  4. package/README.md +12 -4
  5. package/WORLDS.md +122 -0
  6. package/dist/blobs.d.ts +2 -1
  7. package/dist/blobs.js +7 -5
  8. package/dist/cli/eval.d.ts +4 -0
  9. package/dist/cli/eval.js +434 -186
  10. package/dist/cli/listen.js +1 -1
  11. package/dist/environment/client.d.ts +2 -95
  12. package/dist/environment/client.js +2 -283
  13. package/dist/environment/tools.d.ts +2 -35
  14. package/dist/environment/tools.js +2 -30
  15. package/dist/environment/types.d.ts +2 -623
  16. package/dist/environment/types.js +2 -1
  17. package/dist/environment/world.d.ts +2 -65
  18. package/dist/environment/world.js +2 -171
  19. package/dist/environment.d.ts +7 -7
  20. package/dist/environment.js +3 -3
  21. package/dist/evals/attempt.d.ts +1 -1
  22. package/dist/evals/client.d.ts +3 -2
  23. package/dist/evals/client.js +2 -1
  24. package/dist/evals/environment-target.d.ts +3 -3
  25. package/dist/evals/environment-target.js +3 -3
  26. package/dist/evals/failure.js +1 -1
  27. package/dist/evals/local-worker.d.ts +8 -3
  28. package/dist/evals/local-worker.js +10 -2
  29. package/dist/evals/simulation.d.ts +6 -3
  30. package/dist/evals/simulation.js +2 -1
  31. package/dist/evals/types.d.ts +2 -2
  32. package/dist/inline-files.d.ts +21 -4
  33. package/dist/inline-files.js +100 -14
  34. package/dist/large-media.d.ts +2 -1
  35. package/dist/large-media.js +7 -4
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/dist/world/client.d.ts +150 -0
  39. package/dist/world/client.js +368 -0
  40. package/dist/world/definitions.d.ts +47 -0
  41. package/dist/world/definitions.js +1 -0
  42. package/dist/world/model-types.d.ts +117 -0
  43. package/dist/world/model-types.js +1 -0
  44. package/dist/world/models.d.ts +40 -0
  45. package/dist/world/models.js +56 -0
  46. package/dist/world/tools.d.ts +35 -0
  47. package/dist/world/tools.js +30 -0
  48. package/dist/world/types.d.ts +625 -0
  49. package/dist/world/types.js +1 -0
  50. package/dist/world/world.d.ts +65 -0
  51. package/dist/world/world.js +175 -0
  52. package/dist/world.d.ts +13 -0
  53. package/dist/world.js +5 -0
  54. package/package.json +8 -2
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.19.0`, `@opentelemetry/api@1.9.1` and
45
+ not a completed integration. TypeScript uses `@hue-run/sdk@0.20.0`, `@opentelemetry/api@1.9.1` and
46
46
  `@opentelemetry/context-async-hooks@2.11.0`; Python setup uses its separately tested package pin.
47
47
  Content capture requires an ordinary account-managed key and a later explicit application decision.
48
48
 
@@ -379,8 +379,8 @@ and Hue never receives or stores them. The command is part of the `hue` executab
379
379
  # LANGFUSE_PUBLIC_KEY=pk-lf-...
380
380
  # LANGFUSE_SECRET_KEY=sk-lf-...
381
381
  # LANGFUSE_BASE_URL=https://cloud.langfuse.com
382
- npx -y @hue-run/sdk@0.19.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
383
- npx -y @hue-run/sdk@0.19.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
382
+ npx -y @hue-run/sdk@0.20.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
383
+ npx -y @hue-run/sdk@0.20.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
384
384
  ```
385
385
 
386
386
  `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` come from the environment or `--env-path`
@@ -595,7 +595,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
595
595
  # Set project to an existing supported fixture; use the same directory on resume.
596
596
  project=/absolute/path/to/supported-fixture
597
597
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
598
- --archive .artifacts/typescript/hue-run-sdk-0.19.0.tgz \
598
+ --archive .artifacts/typescript/hue-run-sdk-0.20.0.tgz \
599
599
  --origin https://STAGING_ORIGIN \
600
600
  --project "$project" --command setup \
601
601
  --evidence .context/setup-staging-before-claim.json
@@ -606,7 +606,7 @@ the private local handoff and finish the real browser claim, then reconcile the
606
606
 
607
607
  ```sh
608
608
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
609
- --archive .artifacts/typescript/hue-run-sdk-0.19.0.tgz \
609
+ --archive .artifacts/typescript/hue-run-sdk-0.20.0.tgz \
610
610
  --origin https://STAGING_ORIGIN \
611
611
  --project "$project" --command claim \
612
612
  --evidence .context/setup-staging-after-claim.json
@@ -648,10 +648,10 @@ otherwise. Never hand the agent Hue-native tools (`context.tools`, `context.mcp`
648
648
  production agent lacks. Pin the SDK, so the agent's repository gains no dependency:
649
649
 
650
650
  ```sh
651
- npx -y @hue-run/sdk@0.19.0 eval --case "Refund an eligible charge" --command "node agent.js"
652
- npx -y @hue-run/sdk@0.19.0 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
653
- npx -y @hue-run/sdk@0.19.0 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
654
- npx -y @hue-run/sdk@0.19.0 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
651
+ npx -y @hue-run/sdk@0.20.0 eval --case "Refund an eligible charge" --command "node agent.js"
652
+ npx -y @hue-run/sdk@0.20.0 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
653
+ npx -y @hue-run/sdk@0.20.0 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
654
+ npx -y @hue-run/sdk@0.20.0 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
655
655
  ```
656
656
 
657
657
  One invocation creates one run holding every selected case; it prints the run URL
@@ -689,7 +689,7 @@ export default function runMyAgent(inputs: JsonValue, context: SimulationTargetC
689
689
  ```
690
690
 
691
691
  ```sh
692
- npx -y @hue-run/sdk@0.19.0 eval --case "Refund an eligible charge" ./hue-agent.ts --content
692
+ npx -y @hue-run/sdk@0.20.0 eval --case "Refund an eligible charge" ./hue-agent.ts --content
693
693
  ```
694
694
 
695
695
  `--scenario` remains an alias for `--case` for existing scripts. Pass one selection flag.
@@ -731,6 +731,23 @@ former; a case failed for its telemetry has `state: "error"`,
731
731
  Hue-owned `world_outcome` checks are graded after the world seals. An experiment always covers
732
732
  every case of the saved version; there is no case subset.
733
733
 
734
+ `--judges` requests the run's pinned
735
+ `llm_judge` evaluators after outputs upload, before verdict polling. It is off by default and
736
+ works in direct, simulation and worker modes. Requests use the project's hosted judge budget;
737
+ Hue applies its admission controls to new jobs. An accepted request remains replayable if the
738
+ budget is later disabled. Cases without stored output are excluded, and combining
739
+ `--judges` with `--no-output` is refused. `--check` creates no judge jobs.
740
+
741
+ With `--judges --wait 0`, the CLI requests the jobs and returns their current verdict state;
742
+ pending jobs continue in Hue. Uploaded outputs are checkpointed before the attempt or worker
743
+ queue is marked completed. If this handoff or dispatch fails, or its acknowledgement is lost,
744
+ rerun the unchanged command with the same checkpoint directory. The CLI resumes judging the uploaded
745
+ outputs without invoking the agent, using the same idempotency keys. After dispatch succeeds,
746
+ the next ordinary invocation starts a new run as usual.
747
+
748
+ `--worker --judges` discards cancelled or released handoffs. Once judge requests succeed, a
749
+ verdict-read failure is logged and the worker keeps accepting runs.
750
+
734
751
  `--command "<shell command>"` spawns the command once per case with the world's environment
735
752
  (`HUE_WORLD_ID`, `HUE_WORLD_TOKEN`, `HUE_WORLD_NOW` (the world's clock at creation, RFC 3339; `worldNow()` reads it and returns
736
753
  `null` without it, so fall back explicitly with `worldNow() ?? new Date()`), one `HUE_SIM_<SURFACE ID>_URL` per provider
package/ENVIRONMENTS.md CHANGED
@@ -1,6 +1,6 @@
1
- # Simulated environments
1
+ # Simulated worlds: compatible client
2
2
 
3
- The provider-aware environment APIs shipped in TypeScript `0.3.0` and are publicly available:
3
+ The provider-aware APIs shipped in TypeScript `0.3.0` and are publicly available:
4
4
 
5
5
  ```bash
6
6
  npm install @hue-run/sdk zod
@@ -10,6 +10,9 @@ Your agent runs in your process while a disposable simulated world runs in Hue.
10
10
  authoritative and records an ordered journal; Hue does not execute your agent code or provider
11
11
  credentials.
12
12
 
13
+ The `environment` module and its exported names remain supported for existing integrations.
14
+ The canonical client names for the next release are documented in [Worlds](WORLDS.md).
15
+
13
16
  ## Run a definition like a test
14
17
 
15
18
  `runSimulation` is the one-shot developer path. It calls your existing callback directly, so
package/EVALUATIONS.md CHANGED
@@ -104,6 +104,11 @@ resolution, a fresh linked world per case, local and hosted MCP tools, finalizat
104
104
  evidence and scoring while retaining this runner's checkpoint guarantees. See
105
105
  [Simulated environments](ENVIRONMENTS.md#run-a-definition-like-a-test).
106
106
 
107
+ The `onUploaded` callback in `runSimulation` and `runLocalAgent` supports durable
108
+ application handoffs after outputs upload and before the attempt or worker queue is marked
109
+ completed. A callback failure stops completion and preserves the uploaded work for recovery.
110
+ The callback may run again after an interruption, so make its writes idempotent.
111
+
107
112
  Cases whose inputs are a task plus pinned documents, and rescoring over documents a run already
108
113
  saved, use the same runner without a world. See [Direct cases and files](#direct-cases-and-files).
109
114
 
@@ -179,7 +184,7 @@ Scorer deferral shipped in TypeScript `0.3.1`: the local runner executes only th
179
184
 
180
185
  `world_outcome` pins (`hue.conversion_outcome.v1` and `.v2`, and the `hue.outcome_assertions.v2` and `.v3` case-outcome evaluators, `.v3` with its pinned judge in `config.judge`) run inside Hue and need no local callback or executable source digest. A supporting server owns their execution from saved world evidence. Legacy `local_code` pins still require the exact registered callback; changing the worker cannot convert those immutable pins into hosted ones.
181
186
 
182
- Manual results require a human session. Hosted model-judge dispatch is an explicit separate API operation: inspect `getJudgeBudget()`, then call `createJudgeJobs(runId,{idempotencyKey,jobs:[{evaluationItemId,scorerVersionId}]})`. `listJudgeJobs`, `getJudgeJob` and `cancelJudgeJob` expose job progress and cancellation requests. These methods never claim that local execution has hosted provenance. Hosted job endpoints are covered by HTTP contract tests here; live hosted model execution is a separate platform acceptance phase. `listResults` and `getResult` read recorded local or hosted results.
187
+ Manual results require a human session. Hosted model-judge dispatch is explicit: call `createJudgeJobs(runId,{idempotencyKey,jobs:[{evaluationItemId,scorerVersionId}]})`. Hue checks the project's budget when admitting new requests. `getJudgeBudget()` reports its current state, but replay an interrupted request with the same key and body even if that state changed. The [`hue eval --judges`](./CLI.md#evaluate-an-agent-against-a-case) option performs this dispatch after outputs upload; it is off by default. `listJudgeJobs`, `getJudgeJob` and `cancelJudgeJob` expose job progress and cancellation requests. These methods never claim that local execution has hosted provenance. Hosted job endpoints are covered by HTTP contract tests here; live hosted model execution is a separate platform acceptance phase. `listResults` and `getResult` read recorded local or hosted results.
183
188
 
184
189
  When present, the budget's `authentication` reports credential resolution only. An
185
190
  `available` status or `configured: true` does not prove that a provider accepted the
package/README.md CHANGED
@@ -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
@@ -516,6 +523,7 @@ the recorded model call again with the real image, document or audio. See
516
523
  | --- | --- | --- |
517
524
  | Base64 data URIs in an observation's input, output or metadata | | [Automatic capture](#automatically-captured-media), for the attributes and shapes listed there |
518
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 |
519
527
  | `resolveMediaReferences({ obj, resolveWith: "base64DataUri" })` | | `client.media.resolve(obj, { traceId })` |
520
528
  | `GET /api/public/media/{mediaId}` | `ReadonlyAttachment.data()` | `client.media.fetch({ traceId, sha256 })` |
521
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 |
@@ -528,8 +536,8 @@ the Langfuse project's own keys (`LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, a
528
536
  `HUE_API_KEY`:
529
537
 
530
538
  ```sh
531
- npx -y @hue-run/sdk@0.19.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
532
- npx -y @hue-run/sdk@0.19.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
539
+ npx -y @hue-run/sdk@0.20.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
540
+ npx -y @hue-run/sdk@0.20.0 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
533
541
  ```
534
542
 
535
543
  - It reads Langfuse Cloud and self-hosted v4 through the Observations API v2, and self-hosted v3
package/WORLDS.md ADDED
@@ -0,0 +1,122 @@
1
+ # Simulated worlds
2
+
3
+ The World clients require a Hue deployment that serves the canonical World and instance routes.
4
+ The [compatible environment client](ENVIRONMENTS.md) remains supported.
5
+
6
+ Your agent runs locally against an isolated hosted instance of a simulated world:
7
+
8
+ ```bash
9
+ npm install @hue-run/sdk zod
10
+ ```
11
+
12
+ Your agent runs in your process while a disposable simulated world runs in Hue. The world is
13
+ authoritative and records an ordered journal; Hue does not execute your agent code or provider
14
+ credentials.
15
+
16
+ A World is a reconstructed starting state. An instance is an isolated running copy of that
17
+ state. Starting another instance does not share the first instance's changes. The handoff's
18
+ legacy `worldId` field identifies the runtime instance, not a durable World model.
19
+
20
+ ## Reconstruct a user's World
21
+
22
+ The World model client requires the corresponding service routes. An entity's
23
+ namespace and full ID bind its history to the project identified by the service key.
24
+
25
+ ```ts
26
+ import { WorldModelClient } from "@hue-run/sdk/world";
27
+
28
+ const models = new WorldModelClient({ apiKey: process.env.HUE_API_KEY! });
29
+ const { worldModel } = await models.createModel({
30
+ entity: { namespace: "user", id: "user-123" },
31
+ });
32
+ const { world, instance } = await models.startInstance(worldModel.id, {
33
+ idempotencyKey: "review-user-123-at-t2",
34
+ at: "2026-10-17T12:00:00Z",
35
+ ttlSeconds: 600,
36
+ });
37
+ ```
38
+
39
+ `world.at` is the represented time. `world.evidenceCutoff` names the committed evidence used to
40
+ reconstruct it, including qualifying evidence from traces recorded later. A subsequent start
41
+ with a new idempotency key uses the latest completed reconstruction. Retrying the same key
42
+ replays the original start. `instance.id` identifies the isolated running copy.
43
+
44
+ `materialize(modelId, { at })` reconstructs without starting an instance.
45
+ `reproduceInstance(instance.id, { idempotencyKey })` starts a fresh isolated instance from the
46
+ original starting World. It does not copy the earlier instance's mutations or reuse its token.
47
+ Erased or unavailable starting data fails explicitly. `eraseModel(modelId)` revokes the model
48
+ and prevents its starting Worlds from being reproduced.
49
+
50
+ Materialization, instance creation and reproduction wait up to two minutes when Hue explicitly
51
+ reports that the World is preparing. Retries preserve the same request bytes and identity.
52
+ Other failures keep the client's `maxAttempts` limit. Pass `{ signal }` as the last options
53
+ argument to cancel a preparation wait or an active request.
54
+
55
+ `listModels` and `listAccounts` accept `after` and `limit` for pagination. Use each account's
56
+ `providerInstanceKey` to distinguish connections, including several accounts for one app.
57
+
58
+ ## Capture user and account identity
59
+
60
+ Automatic World capture requires a compatible service.
61
+ Your existing tracing remains valid without these attributes; unresolved identity does not become
62
+ World state.
63
+
64
+ Record the complete user ID on the request's root span with `userId`. For another entity kind,
65
+ set both `hue.world.entity.namespace` and `hue.world.entity.id` on that root. Tool observations
66
+ need an explicit connection binding on the actual tool span:
67
+
68
+ | Attribute | Value |
69
+ | --- | --- |
70
+ | `hue.connection.provider_id` | Registered app ID, such as `google.gmail`. |
71
+ | `hue.connection.account_id` | Your stable account or connection ID, scoped to this user and project. |
72
+ | `hue.connection.generation` | Positive integer identifying that connection generation. Increment when the connection is replaced. |
73
+ | `hue.connection.scope` | Optional app partition, such as a workspace. Keep it stable. |
74
+ | `hue.connection.principal` | The app's acting identity when its simulation requires one. |
75
+
76
+ For example, attach the binding inside an existing tool callback. `searchThreads` below is your
77
+ application's tool implementation:
78
+
79
+ ```ts
80
+ import { trace } from "@opentelemetry/api";
81
+ import { createHue } from "@hue-run/sdk";
82
+
83
+ const hue = createHue({
84
+ serviceName: "assistant",
85
+ apiKey: process.env.HUE_API_KEY!,
86
+ resourceAttributes: { "deployment.environment.name": "production" },
87
+ });
88
+
89
+ await hue.withSpan("assistant request", () =>
90
+ hue.tool("search_threads", { query: "project update" }, async () => {
91
+ trace.getSpan(hue.getContext())?.setAttributes({
92
+ "hue.connection.provider_id": "google.gmail",
93
+ "hue.connection.account_id": "connection-work",
94
+ "hue.connection.generation": 1,
95
+ "hue.connection.principal": "owner@example.test",
96
+ });
97
+ return searchThreads({ query: "project update" });
98
+ }, { mcp: { name: "gmail", provider: "google.gmail", surface: "mcp" } }),
99
+ { userId: "user-123" },
100
+ );
101
+ await hue.shutdownSafe();
102
+ ```
103
+
104
+ An attached client uses your application's existing OpenTelemetry resource; configure production
105
+ provenance there. Mark faithful production traffic accurately. Eval, simulation and excluded
106
+ traffic cannot populate a live user's World. Historical imports require separate trusted admission.
107
+
108
+ Use the same fields with your framework's tool-span hook. A parent-only account label cannot
109
+ resolve several accounts used by different tool calls. Account IDs and resource IDs stay separate;
110
+ a tool argument or matching email address alone does not establish a binding. Do not record
111
+ OAuth tokens or provider credentials in these fields.
112
+
113
+ ## Publish an authored World
114
+
115
+ `createWorldClient` also publishes authored definitions and starts instances from their
116
+ immutable versions. Use `WorldDefinitionV2` and `WorldProviderInstance` for arbitrary registered
117
+ apps and multiple accounts of the same app. The deployment validates each app's JSON
118
+ configuration and supported dialects.
119
+
120
+ The [compatible client guide](ENVIRONMENTS.md) covers authored definitions, action catalogs,
121
+ instance journals and simulation runners. Its examples use the retained environment client;
122
+ the canonical client uses the same request bodies through World and instance routes.
package/dist/blobs.d.ts CHANGED
@@ -249,7 +249,8 @@ export declare class BlobUploader {
249
249
  private reserve;
250
250
  /** A 200 answer read: `exists`, or an upload whose URL and headers are usable as given. */
251
251
  private reservation;
252
- /** The PUT of the value's bytes, with exactly the headers Hue signed. A source that no longer
252
+ /** The PUT of the value's bytes, with exactly the headers Hue signed. No answer, a 408, a 429 or
253
+ * a 5xx is transient, with the answer's `Retry-After` when it gave one. A source that no longer
253
254
  * holds its bytes (a file changed since it was recorded) ends the request before its body is
254
255
  * complete, and fails the value without a retry. */
255
256
  private put;
package/dist/blobs.js CHANGED
@@ -542,8 +542,9 @@ export class BlobUploader {
542
542
  return { ok: false, reason: "failed", changed: true };
543
543
  if (this.closed)
544
544
  return { ok: false, reason: "failed" };
545
- if (put === "transient") {
546
- if (++attempt >= UPLOAD_ATTEMPTS || !(await this.retryWait(attempt - 1, undefined, upload)))
545
+ if (typeof put === "object") {
546
+ if (++attempt >= UPLOAD_ATTEMPTS ||
547
+ !(await this.retryWait(attempt - 1, put.retryAfter, upload)))
547
548
  return { ok: false, reason: "failed" };
548
549
  if (Date.now() < grant.expiresAt)
549
550
  continue;
@@ -662,7 +663,8 @@ export class BlobUploader {
662
663
  grant: { url, headers, expiresAt: Number.isFinite(expiresAt) ? expiresAt : Infinity },
663
664
  };
664
665
  }
665
- /** The PUT of the value's bytes, with exactly the headers Hue signed. A source that no longer
666
+ /** The PUT of the value's bytes, with exactly the headers Hue signed. No answer, a 408, a 429 or
667
+ * a 5xx is transient, with the answer's `Retry-After` when it gave one. A source that no longer
666
668
  * holds its bytes (a file changed since it was recorded) ends the request before its body is
667
669
  * complete, and fails the value without a retry. */
668
670
  async put(grant, value, size, deadline) {
@@ -670,7 +672,7 @@ export class BlobUploader {
670
672
  if (isByteSource(value) && value.changed)
671
673
  return "changed";
672
674
  if (!reply)
673
- return "transient";
675
+ return { retryAfter: undefined };
674
676
  // 412: the object exists already (another upload of the same value won). A success answered
675
677
  // before the whole body was written stores nothing that was sent.
676
678
  if (reply.status === 412)
@@ -680,7 +682,7 @@ export class BlobUploader {
680
682
  if (reply.status === 403)
681
683
  return reply.body.toString("utf8").includes("Request has expired") ? "expired" : "failed";
682
684
  if (reply.status >= 500 || reply.status === 408 || reply.status === 429)
683
- return "transient";
685
+ return { retryAfter: retryAfterMillis(reply.headers) };
684
686
  return "failed";
685
687
  }
686
688
  /**
@@ -1,3 +1,4 @@
1
+ import { EvaluationClient } from "../evals/client.js";
1
2
  import type { ExperimentCase, LocalFile } from "../evals/types.js";
2
3
  import { TargetResult } from "../evals/types.js";
3
4
  import { type SimulationTargetContext } from "../evals/simulation.js";
@@ -51,6 +52,9 @@ export declare function spawnAgentCommand(command: string, options: {
51
52
  }): Promise<string>;
52
53
  export declare function renderTable(verdicts: ExperimentVerdicts, output: Output): void;
53
54
  export declare function explain(error: unknown): string;
55
+ /** Requests only the run's pinned model judges. Stable batches let an interrupted CLI resume
56
+ * dispatch without creating a second paid job for the same scoring item and evaluator. */
57
+ export declare function dispatchPinnedJudges(client: Pick<EvaluationClient, "getExperiment" | "listEvaluationItems" | "createJudgeJobs">, experimentId: string, subjectIds: string[], signal?: AbortSignal): Promise<number>;
54
58
  /**
55
59
  * `hue eval`: runs a local adapter or command against a Scenario or eval set through
56
60
  * `runSimulation`, or registers it as an outbound worker through `runLocalAgent`.