@hue-run/sdk 0.11.4 → 0.12.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.11.4`, `@opentelemetry/api@1.9.1` and
45
+ not a completed integration. TypeScript uses `@hue-run/sdk@0.12.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
 
@@ -469,7 +469,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
469
469
  # Set project to an existing supported fixture; use the same directory on resume.
470
470
  project=/absolute/path/to/supported-fixture
471
471
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
472
- --archive .artifacts/typescript/hue-run-sdk-0.11.4.tgz \
472
+ --archive .artifacts/typescript/hue-run-sdk-0.12.1.tgz \
473
473
  --origin https://STAGING_ORIGIN \
474
474
  --project "$project" --command setup \
475
475
  --evidence .context/setup-staging-before-claim.json
@@ -480,7 +480,7 @@ the private local handoff and finish the real browser claim, then reconcile the
480
480
 
481
481
  ```sh
482
482
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
483
- --archive .artifacts/typescript/hue-run-sdk-0.11.4.tgz \
483
+ --archive .artifacts/typescript/hue-run-sdk-0.12.1.tgz \
484
484
  --origin https://STAGING_ORIGIN \
485
485
  --project "$project" --command claim \
486
486
  --evidence .context/setup-staging-after-claim.json
@@ -618,6 +618,11 @@ for example, needs a reviewed `attemptBaselineV2` (see
618
618
  registers none, so put `"attemptBaselineV2"` in the run's configuration JSON when you launch the
619
619
  run from Hue.
620
620
 
621
+ `--trace-not-accepted pending` completes a case whose telemetry Hue did not accept in time in its
622
+ true state with the trace still to arrive, carrying the count of spans the case ended; Hue scores
623
+ the sealed world at once and attaches the trace once its export is complete. The default,
624
+ `fail_case`, completes such a case as failed with its evidence omitted.
625
+
621
626
  A Scenario or eval set whose dataset version is not saved cannot back an experiment: the command
622
627
  exits 1 and asks for **Save eval-set version** in Hue or `--save-version`, which freezes that
623
628
  version at its current revision. Connection settings are `HUE_API_KEY` and `HUE_BASE_URL`
@@ -663,9 +668,9 @@ an interrupted run without invoking the agent again; a different selection is re
663
668
  unfinished one is resumed or its directory is removed.
664
669
 
665
670
  Exit codes: `0` every case passed the evaluators that apply to it (advisory ones never count),
666
- `1` a case failed, errored (including one no pinned evaluator applies to or decides), was skipped
667
- or Hue's checks were
668
- still pending at `--wait`, `2` usage or configuration error (including a missing key), `130`
671
+ `1` a case failed, errored (including one no pinned evaluator applies to or decides), was skipped,
672
+ was inconclusive (a required judge could not run or answer; the table says `INCONCLUSIVE` and the
673
+ summary counts it apart from skipped cases) or Hue's checks were still pending at `--wait`, `2` usage or configuration error (including a missing key), `130`
669
674
  interrupted. On Node.js 22, load TypeScript adapters with `NODE_OPTIONS=--experimental-strip-types`;
670
675
  non-erasable syntax (enums, parameter properties, namespaces) needs a loader such as `--import tsx`
671
676
  on any Node.js version.
package/EVALUATIONS.md CHANGED
@@ -116,7 +116,7 @@ Both choices are required and independent:
116
116
 
117
117
  Frozen dataset inputs/references already exist on Hue. Their presence is independent of these switches. No output is inferred from telemetry. JavaScript `undefined` means unavailable output; JSON `null`, false, zero and empty string remain present values. Historical subjects with unavailable output produce skipped results without executing a scorer callback. A failed quality metric remains `state:"scored"` with `passed:false`. Target errors and scorer errors remain separate. Target error types are generalized to `TargetError`; when content is enabled, bounded messages may be stored. Never put credentials in error messages, output, custom metric values, names or metadata.
118
118
 
119
- `traceEvidence:{mode:"required"}` waits for trace and log export acknowledgement after the root span ends. The explicit alternative `{mode:"omit",reason:"..."}` stores the omission reason and declared trace ID without a fake snapshot. It can complete despite an export failure; export diagnostics remain available on the Hue client. There is no automatic fallback to omission. When required telemetry is not accepted, the runner by default keeps the saved outcome, leaves the execution started and rejects (`traceNotAccepted: "stop"`). With `traceNotAccepted: "fail_case"` it instead completes that case as failed (a `TelemetryNotAccepted` error, the evidence omitted with a reason starting `telemetry_not_accepted`, and no output or generated files attached, so no scorer can pass it), calls `onTelemetryNotAccepted(entry)` once, as soon as it completes the case (a resumed call only lists it), lists it in `report.telemetryNotAccepted` with sanitized issue counts, and goes on; `hue eval` uses this. A scorer that grades only the world can still score such a case, so treat a listed case as failed whatever its results say, as `hue eval` does.
119
+ `traceEvidence:{mode:"required"}` waits for trace and log export acknowledgement after the root span ends. The transport is shared by every case in flight, so each export issue names the traces of the records it concerned (`ExportIssue.traceIds`): a refused or rejected batch, a dropped record, a capture failure inside a case. A failure that names the case's trace is that case's own and final (its checkpoint reads `failed`, and resume refuses it); one that names only other traces leaves the case accepted; one that names no trace (a processor flush that failed) or a rejection in a batch of several traces is decided by the case's trace receipt, at once or on resume for a checkpoint still `pending`: the receipt must hold the case's root span and at least as many spans as the case ended (the checkpoint records the count at the flush; without it the receipt does not accept). The explicit alternative `{mode:"omit",reason:"..."}` stores the omission reason and declared trace ID without a fake snapshot. It can complete despite an export failure; export diagnostics remain available on the Hue client. There is no automatic fallback to omission. When required telemetry is not accepted, the runner by default keeps the saved outcome, leaves the execution started and rejects (`traceNotAccepted: "stop"`). With `traceNotAccepted: "fail_case"` it instead completes that case as failed (a `TelemetryNotAccepted` error, the evidence omitted with a reason starting `telemetry_not_accepted`, and no output or generated files attached, so no scorer can pass it), calls `onTelemetryNotAccepted(entry)` once, as soon as it completes the case (a resumed call only lists it), lists it in `report.telemetryNotAccepted` with sanitized issue counts, and goes on; `hue eval` uses this. A scorer that grades only the world can still score such a case, so treat a listed case as failed whatever its results say, as `hue eval` does. `traceNotAccepted:"pending"` completes a case whose telemetry Hue did not accept in time in its true state with `traceEvidence:"pending"` and the count of spans the case ended (`traceSpanCount`): Hue scores the sealed world at once, attaches the trace once it holds every span (a root span alone is not the trace), or records the evidence omitted a day later; such cases are listed in `RunnerReport.tracePending`. A saved outcome whose export the receipt does not accept on resume completes the same way. `hue eval --trace-not-accepted pending` chooses it for the CLI.
120
120
 
121
121
  ## Local scorers
122
122
 
@@ -252,14 +252,30 @@ without invoking the target or scorers. A lost preparation acknowledgement remai
252
252
  and is never recovered through binding reads, credential refresh or target replay. Synthetic
253
253
  acceptance does not contact official Gmail or claim universal provider parity.
254
254
 
255
- Completion or result-upload failures keep the run claimed by the durable worker identity.
256
- Restart with the same checkpoint directory to resume saved uploads without invoking the
257
- candidate again. A lost world-seal acknowledgement is recovered by reading authoritative world
258
- state. If the seal or candidate outcome cannot be confirmed, or an outcome cannot be serialized,
259
- the worker reports `attention` and stops; operator investigation is required. Such runs are not
260
- automatically reclaimed, and presenting the same uncertain checkpoint again cannot replay the
261
- candidate. Public package acceptance proves this lifecycle against local fixtures; exact
262
- installed-registry-package to hosted-facade acceptance remains a post-publication Fern gate.
255
+ The worker does not die with one failure. A read, or a mutation the server deduplicates by its
256
+ idempotency key (the worker's own register, claim, heartbeat and completion included), is sent
257
+ again after a connection failure, a timeout or a 408 or 5xx that carried no `Retry-After`, up to
258
+ `maxAttempts` (default 4) times; a mutation without a key is sent once. A registration or claim
259
+ that still fails transiently is reported through `onPollError` and polled again with a growing
260
+ wait; a refusal Hue decided on (a revoked key, a disabled agent) is thrown. A transient failure
261
+ of a completion or result upload keeps the run claimed by the durable worker identity: the next
262
+ claim returns it to the same process, which resumes saved uploads without invoking the candidate
263
+ again (`onRunFailed` says `retry`), and after `maxRunAttempts` (default 5) such failures the run
264
+ is given up as `attention` for a project member to requeue or cancel from its run page; a failure
265
+ that would recur (a refusal Hue decided on, a case file whose bytes differ from its manifest) is
266
+ given up at once. A lost
267
+ world-seal acknowledgement is recovered by reading authoritative world state. If the seal or
268
+ candidate outcome cannot be confirmed, or an outcome cannot be serialized, the run is given up
269
+ as `attention` at once and the worker goes on to other runs; when Hue cannot record that either,
270
+ the worker stops with the error. Presenting the same uncertain checkpoint again cannot replay the
271
+ candidate. `maxRuns` counts runs that settled either way. Inside a run, one case's own failure
272
+ does not stop the cases beside it; a failure every case would share (Hue not answering or
273
+ refusing, telemetry not accepted, a world that could not be sealed) stops new cases from
274
+ starting until the run resumes. Each world's lifetime is `worldTtlSeconds` (the server's default
275
+ is an hour): a target still running past it is told through `context.signal`, its case ends as
276
+ an `error` of type `TargetTimeout` and the world is sealed abandoned. Public package acceptance
277
+ proves this lifecycle against local fixtures; exact installed-registry-package to hosted-facade
278
+ acceptance remains a post-publication Fern gate.
263
279
 
264
280
  ## Direct cases and files
265
281
 
@@ -355,6 +371,8 @@ files directory must be owned by the current user and closed to everyone else (m
355
371
  target again; if result content is not persisted, the JSON output cannot be reconstructed and the
356
372
  case is reported as uncertain.
357
373
 
374
+ A target that throws completes its execution as `error` with a type that says what stopped the case. `TargetError` is the agent's own failure. A service the agent called that refused with a retryable status (408, 429 or 5xx, such as a model provider's rate limit or outage, when the error carries the exchange: headers, a response or a URL) is `ServiceRefused`; a connection that failed (`ECONNRESET`, `ECONNREFUSED`, `fetch failed`, an `APIConnectionError`) is `ConnectionFailed`; a call that timed out (`TimeoutError`, `APITimeoutError`) is `TimedOut`. The runner reads the error's `cause`, `lastError` and `errors` chains for these signals. Hue counts the three as infrastructure and leaves them out of the agent's pass rate; a refusal the agent caused (a 400 from a model provider or another service) stays `TargetError`. Any other status Hue's own clients answered with (a 4xx: a key, a project, an environment version or a world Hue refused) is `ConfigurationRejected`, which Hue counts as configuration and also leaves out of the agent's pass rate: an error from Hue's own clients is never the agent's. The clients fetch with `redirect: "error"`, so a redirect from the configured endpoint surfaces without a status, as `ConnectionFailed`. A runner of your own that completes an execution with a `type` of its own can say which class Hue files it under with the error's `class`.
375
+
358
376
  `rescore` downloads a subject's frozen `files` — the pinned inputs and the generated outputs, with
359
377
  `role: "output"` for the documents a run produced — so a code evaluator can grade saved documents
360
378
  without invoking an agent. Local scorers receive them as `context.files` exactly as they do during
package/dist/cli/eval.js CHANGED
@@ -83,16 +83,28 @@ Output and limits:
83
83
  --save-version Freeze an unsaved eval-set version before running
84
84
  --checkpoint-dir <path> Private checkpoint directory (default: .hue/eval/<agent-key>)
85
85
  --concurrency <n> Cases in flight, 1-64 (default: 1)
86
+ --trace-not-accepted <policy> A case whose telemetry Hue did not accept in time: fail_case
87
+ (default) completes it as failed with the evidence omitted;
88
+ pending completes it in its true state with the trace still to
89
+ arrive, which Hue attaches once its export is complete
86
90
  --timeout <seconds> Per-case --command timeout (default: 600)
87
91
  --wait <seconds> Verdict wait after the run finishes (default: 300)
88
92
  -h, --help Show this help
89
93
 
90
94
  HUE_API_KEY must be a "Read and write" project key; it is never printed.
91
95
  Code evaluators pinned to a direct run are graded by Hue's executor after the upload; the wait
92
- covers them. Exit codes: 0 every case passed, 1 a case failed, errored or is incomplete,
93
- 2 usage error, 130 interrupted.
96
+ covers them. Exit codes: 0 every case passed, 1 a case failed, errored, is inconclusive or is
97
+ incomplete, 2 usage error, 130 interrupted.
94
98
  `;
95
99
  /** Thrown for invalid arguments or configuration; exits with status 2. */
100
+ /** The `--trace-not-accepted` policy: the failed-case default, or pending evidence. */
101
+ function traceNotAcceptedPolicy(value) {
102
+ if (value === undefined || value === "fail_case")
103
+ return "fail_case";
104
+ if (value === "pending")
105
+ return "pending";
106
+ throw new UsageError("--trace-not-accepted must be fail_case or pending");
107
+ }
96
108
  class UsageError extends Error {
97
109
  constructor(message) {
98
110
  super(message);
@@ -129,6 +141,7 @@ function parse(argv) {
129
141
  json: { type: "boolean", default: false },
130
142
  content: { type: "boolean", default: false },
131
143
  "no-output": { type: "boolean", default: false },
144
+ "trace-not-accepted": { type: "string" },
132
145
  "save-version": { type: "boolean", default: false },
133
146
  "checkpoint-dir": { type: "string" },
134
147
  concurrency: { type: "string" },
@@ -702,6 +715,11 @@ const STATE_LABEL = {
702
715
  skipped: "SKIPPED",
703
716
  pending: "PENDING",
704
717
  };
718
+ /** A skipped case whose deciding evaluator could not decide it, as an answer-graded case whose
719
+ * required judge could not run or answer: Hue's explanation begins "Inconclusive:". It is named
720
+ * apart from a case its evaluators did not grade, since it needs a judge, not another pin. */
721
+ const inconclusive = (item) => item.state === "skipped" && item.explanations.some((text) => /^inconclusive:/i.test(text));
722
+ const stateLabel = (item) => inconclusive(item) ? "INCONCLUSIVE" : STATE_LABEL[item.state];
705
723
  export function renderTable(verdicts, output) {
706
724
  const cases = verdicts.summary.cases;
707
725
  // A column per metric name; a name several evaluators report, such as two judges' `verdict`,
@@ -736,7 +754,7 @@ export function renderTable(verdicts, output) {
736
754
  // reporting the metric shows "-".
737
755
  return item.notApplicable?.includes(column.version) ? "n/a" : "-";
738
756
  }),
739
- STATE_LABEL[item.state],
757
+ stateLabel(item),
740
758
  ]);
741
759
  const widths = header.map((cell, index) => Math.max(cell.length, ...rows.map((row) => row[index].length)));
742
760
  const line = (cells) => cells
@@ -751,15 +769,18 @@ export function renderTable(verdicts, output) {
751
769
  if (item.state === "passed")
752
770
  continue;
753
771
  const details = [...item.errors.map((type) => `scorer error ${type}`), ...item.explanations];
754
- for (const detail of details.length ? details : [STATE_LABEL[item.state].toLowerCase()])
772
+ for (const detail of details.length ? details : [stateLabel(item).toLowerCase()])
755
773
  output.log(` ${item.externalKey}: ${detail}`);
756
774
  }
757
775
  const totals = verdicts.summary.totals;
776
+ const inconclusiveCount = cases.filter(inconclusive).length;
777
+ const skipped = totals.skipped - inconclusiveCount;
758
778
  const advisoryFailures = cases.reduce((count, item) => count +
759
779
  item.metrics.filter((metric) => item.advisory?.includes(metric.scorerVersionId) && !metricPassed(metric)).length, 0);
760
780
  const extra = [
761
781
  totals.error ? `${totals.error} error` : "",
762
- totals.skipped ? `${totals.skipped} skipped` : "",
782
+ inconclusiveCount ? `${inconclusiveCount} inconclusive` : "",
783
+ skipped > 0 ? `${skipped} skipped` : "",
763
784
  totals.pending ? `${totals.pending} pending` : "",
764
785
  totals.notApplicable
765
786
  ? `${totals.notApplicable} evaluator result${totals.notApplicable === 1 ? "" : "s"} not applicable`
@@ -1116,7 +1137,7 @@ async function runOnce(values, connection, agents, agent, hue, output, signal) {
1116
1137
  persistResultContent: !values["no-output"],
1117
1138
  traceEvidence: { mode: "required" },
1118
1139
  // A case whose telemetry Hue did not accept fails instead of staying started.
1119
- traceNotAccepted: "fail_case",
1140
+ traceNotAccepted: traceNotAcceptedPolicy(values["trace-not-accepted"]),
1120
1141
  onTelemetryNotAccepted: telemetry.report,
1121
1142
  concurrency,
1122
1143
  agentRevision: agent.revision,
@@ -1221,7 +1242,7 @@ async function runDirect(run, connection) {
1221
1242
  // only the telemetry.
1222
1243
  persistResultContent: !values["no-output"],
1223
1244
  traceEvidence: { mode: "required" },
1224
- traceNotAccepted: "fail_case",
1245
+ traceNotAccepted: traceNotAcceptedPolicy(values["trace-not-accepted"]),
1225
1246
  onTelemetryNotAccepted: telemetry.report,
1226
1247
  concurrency: run.concurrency,
1227
1248
  scorers: [],
@@ -1297,11 +1318,25 @@ async function runWorker(values, connection, adapter, agent, hue, output, signal
1297
1318
  },
1298
1319
  scorers: [],
1299
1320
  concurrency,
1300
- traceNotAccepted: "fail_case",
1321
+ traceNotAccepted: traceNotAcceptedPolicy(values["trace-not-accepted"]),
1301
1322
  onTelemetryNotAccepted: telemetry.report,
1302
1323
  deprecationWarnings: false,
1303
1324
  signal,
1304
1325
  ...(maxRuns === undefined ? {} : { maxRuns }),
1326
+ onPollError(error) {
1327
+ output.error(`Hue did not answer the poll (${explain(error)}); polling again`);
1328
+ },
1329
+ onRunFailed(failure) {
1330
+ const run = `Run ${failure.runId}`;
1331
+ if (failure.outcome === "retry") {
1332
+ const waitMillis = failure.waitMillis ?? 0;
1333
+ output.error(`${run} attempt ${failure.attempt} failed (${explain(failure.error)}); resuming in ${Math.round(waitMillis / 1000)} s`);
1334
+ }
1335
+ else if (failure.outcome === "attention")
1336
+ output.error(`${run} needs attention (${explain(failure.error)}); requeue or cancel it from its run page`);
1337
+ else
1338
+ output.error(`${run} was released or cancelled in Hue; its outcome here is not recorded`);
1339
+ },
1305
1340
  target(inputs, tools, context) {
1306
1341
  output.log(`[${context.item.externalKey}] agent started`);
1307
1342
  return adapter(inputs, {
@@ -1315,7 +1350,9 @@ async function runWorker(values, connection, adapter, agent, hue, output, signal
1315
1350
  ...(context.connectionBundle ? { connectionBundle: context.connectionBundle } : {}),
1316
1351
  files: context.files,
1317
1352
  outputDirectory: context.outputDirectory,
1318
- signal,
1353
+ // The case's own signal: the worker stopping, or the world's deadline passing, both
1354
+ // end a command that would otherwise keep running past its timed-out case.
1355
+ signal: context.signal ?? signal,
1319
1356
  });
1320
1357
  },
1321
1358
  async onCompleted(report) {
@@ -1386,6 +1423,8 @@ export async function runEvalCommand(argv) {
1386
1423
  let json = false;
1387
1424
  try {
1388
1425
  const { values, positionals } = parse(argv);
1426
+ // A usage error before anything is prepared or created: no run is left to recover from it.
1427
+ traceNotAcceptedPolicy(values["trace-not-accepted"]);
1389
1428
  json = values.json;
1390
1429
  const output = {
1391
1430
  log: (line) => (json ? process.stderr : process.stdout).write(`${line}\n`),
package/dist/client.d.ts CHANGED
@@ -43,6 +43,9 @@ export declare class HueClient {
43
43
  readonly enabled: boolean;
44
44
  private logger;
45
45
  private storage;
46
+ /** Reports a contained capture or instrumentation failure, named to the trace active where
47
+ * it happened when there is one. */
48
+ private instrumentationFailed;
46
49
  private tracerProvider;
47
50
  private loggerProvider;
48
51
  private ownedProviders?;
package/dist/client.js CHANGED
@@ -169,6 +169,20 @@ export class HueClient {
169
169
  enabled;
170
170
  logger;
171
171
  storage = new AsyncLocalStorage();
172
+ /** Reports a contained capture or instrumentation failure, named to the trace active where
173
+ * it happened when there is one. */
174
+ instrumentationFailed(signal = "traces", count = 1) {
175
+ let traceId;
176
+ try {
177
+ traceId = trace
178
+ .getSpan(this.storage.getStore()?.context ?? context.active())
179
+ ?.spanContext().traceId;
180
+ }
181
+ catch {
182
+ // The failure is recorded without a trace.
183
+ }
184
+ this.transport.instrumentationFailure(signal, undefined, count, traceId);
185
+ }
172
186
  tracerProvider;
173
187
  loggerProvider;
174
188
  ownedProviders;
@@ -210,7 +224,7 @@ export class HueClient {
210
224
  }
211
225
  this.captureContent = this.transport.options.captureContent;
212
226
  this.enabled = this.transport.options.enabled !== false;
213
- this.tracer = new ContextualTracer(this.tracerProvider.getTracer(HUE_SCOPE, sdkVersion), this.storage, () => this.enabled && !this.closed, () => this.transport.instrumentationFailure());
227
+ this.tracer = new ContextualTracer(this.tracerProvider.getTracer(HUE_SCOPE, sdkVersion), this.storage, () => this.enabled && !this.closed, () => this.instrumentationFailed());
214
228
  this.logger = this.loggerProvider.getLogger(HUE_SCOPE, sdkVersion);
215
229
  }
216
230
  /**
@@ -232,7 +246,7 @@ export class HueClient {
232
246
  return this.storage.getStore()?.context ?? context.active();
233
247
  }
234
248
  catch {
235
- this.transport.instrumentationFailure();
249
+ this.instrumentationFailed();
236
250
  return ROOT_CONTEXT;
237
251
  }
238
252
  }
@@ -256,7 +270,7 @@ export class HueClient {
256
270
  span = this.storage.run(active, () => this.tracer.startSpan(name, { kind: options.kind ?? SpanKind.INTERNAL, attributes: options.attributes }, active.context));
257
271
  }
258
272
  catch {
259
- this.transport.instrumentationFailure();
273
+ this.instrumentationFailed();
260
274
  }
261
275
  }
262
276
  // A disabled or failed client creates no span; the application's own active span then stays
@@ -268,7 +282,7 @@ export class HueClient {
268
282
  spanContext = trace.setSpan(active.context, span);
269
283
  }
270
284
  catch {
271
- this.transport.instrumentationFailure();
285
+ this.instrumentationFailed();
272
286
  spanContext = trace.setSpan(ROOT_CONTEXT, span);
273
287
  }
274
288
  const handle = {
@@ -287,7 +301,7 @@ export class HueClient {
287
301
  handle.setInput(options.input);
288
302
  }
289
303
  catch {
290
- this.transport.instrumentationFailure();
304
+ this.instrumentationFailed();
291
305
  }
292
306
  try {
293
307
  return await callback(handle);
@@ -303,9 +317,7 @@ export class HueClient {
303
317
  // The span is also OpenTelemetry's active span while the callback runs, so spans from other
304
318
  // instrumentations (HTTP clients, provider SDKs) join this trace when the application has
305
319
  // registered a context manager. Hue still registers none itself.
306
- return this.storage.run({ ...active, context: spanContext }, () => created
307
- ? runInContext(spanContext, execute, () => this.transport.instrumentationFailure())
308
- : execute());
320
+ return this.storage.run({ ...active, context: spanContext }, () => created ? runInContext(spanContext, execute, () => this.instrumentationFailed()) : execute());
309
321
  }
310
322
  /**
311
323
  * Runs `execute` inside an `execute_tool` span named after the tool. `input` and a defined result
@@ -329,7 +341,7 @@ export class HueClient {
329
341
  if (isLabel(value))
330
342
  attributes[key] = value;
331
343
  else if (this.enabled && !this.closed)
332
- this.transport.instrumentationFailure();
344
+ this.instrumentationFailed();
333
345
  };
334
346
  stamp("gen_ai.tool.call.id", options.callId);
335
347
  stamp("mcp.server.name", options.mcp?.name);
@@ -364,7 +376,7 @@ export class HueClient {
364
376
  if (isLabel(value))
365
377
  return value;
366
378
  if (active)
367
- this.transport.instrumentationFailure();
379
+ this.instrumentationFailed();
368
380
  return fallback;
369
381
  };
370
382
  const requestModel = label(model, "unknown");
@@ -413,14 +425,14 @@ export class HueClient {
413
425
  if (value === undefined)
414
426
  continue;
415
427
  if (!Number.isInteger(value) || value < 0) {
416
- this.transport.instrumentationFailure();
428
+ this.instrumentationFailed();
417
429
  continue;
418
430
  }
419
431
  try {
420
432
  span.setAttribute(key, value);
421
433
  }
422
434
  catch {
423
- this.transport.instrumentationFailure();
435
+ this.instrumentationFailed();
424
436
  }
425
437
  }
426
438
  }
@@ -433,7 +445,7 @@ export class HueClient {
433
445
  propagator.inject(activeContext, carrier, defaultTextMapSetter);
434
446
  }
435
447
  catch {
436
- this.transport.instrumentationFailure();
448
+ this.instrumentationFailed();
437
449
  }
438
450
  }
439
451
  /** Reads W3C trace context from a carrier for use as `parentContext`. */
@@ -442,7 +454,7 @@ export class HueClient {
442
454
  return propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
443
455
  }
444
456
  catch {
445
- this.transport.instrumentationFailure();
457
+ this.instrumentationFailed();
446
458
  return ROOT_CONTEXT;
447
459
  }
448
460
  }
@@ -461,7 +473,7 @@ export class HueClient {
461
473
  span.addEvent("exception", { "exception.type": type });
462
474
  }
463
475
  catch {
464
- this.transport.instrumentationFailure();
476
+ this.instrumentationFailed();
465
477
  }
466
478
  }
467
479
  /**
@@ -498,7 +510,7 @@ export class HueClient {
498
510
  else if (typeof explicit === "string" && explicit.trim() && explicit.length <= 256)
499
511
  attributes[key] = explicit;
500
512
  else
501
- this.transport.instrumentationFailure("logs");
513
+ this.instrumentationFailed("logs");
502
514
  };
503
515
  stamp("gen_ai.operation.name", messages.operation, enclosing?.model?.operation);
504
516
  stamp("gen_ai.provider.name", messages.provider, enclosing?.model?.provider);
@@ -513,7 +525,7 @@ export class HueClient {
513
525
  });
514
526
  }
515
527
  catch {
516
- this.transport.instrumentationFailure("logs");
528
+ this.instrumentationFailed("logs");
517
529
  }
518
530
  }
519
531
  /**
@@ -545,7 +557,7 @@ export class HueClient {
545
557
  const addresses = hostedServerAddresses(provider, options.request);
546
558
  const activity = hostedToolActivity(provider, response);
547
559
  if (activity.skipped > 0)
548
- this.transport.instrumentationFailure("traces", undefined, activity.skipped);
560
+ this.instrumentationFailed("traces", activity.skipped);
549
561
  const server = (label) => {
550
562
  const attributes = {};
551
563
  if (label === undefined)
@@ -564,7 +576,7 @@ export class HueClient {
564
576
  if (isLabel(value))
565
577
  attributes[key] = value;
566
578
  else
567
- this.transport.instrumentationFailure();
579
+ this.instrumentationFailed();
568
580
  }
569
581
  const address = addresses.get(label);
570
582
  if (address !== undefined)
@@ -617,7 +629,7 @@ export class HueClient {
617
629
  span.setAttributes(toolCatalogSummary(encodeContent(listing.definitions, catalogLimits)));
618
630
  }
619
631
  catch {
620
- this.transport.instrumentationFailure();
632
+ this.instrumentationFailed();
621
633
  }
622
634
  if (listing.errorType !== undefined)
623
635
  fail(span, listing.errorType);
@@ -625,7 +637,7 @@ export class HueClient {
625
637
  }
626
638
  }
627
639
  catch {
628
- this.transport.instrumentationFailure();
640
+ this.instrumentationFailed();
629
641
  }
630
642
  }
631
643
  /**
@@ -692,12 +704,12 @@ export class HueClient {
692
704
  if (isLabel(name))
693
705
  attributes["hue.file.name"] = name;
694
706
  else
695
- this.transport.instrumentationFailure();
707
+ this.instrumentationFailed();
696
708
  }
697
709
  span.addEvent("hue.file", attributes);
698
710
  }
699
711
  catch {
700
- this.transport.instrumentationFailure();
712
+ this.instrumentationFailed();
701
713
  }
702
714
  }
703
715
  setContent(span, key, value) {
@@ -707,7 +719,7 @@ export class HueClient {
707
719
  span.setAttribute(key, encodeContent(value));
708
720
  }
709
721
  catch {
710
- this.transport.instrumentationFailure();
722
+ this.instrumentationFailed();
711
723
  }
712
724
  }
713
725
  /**
@@ -9,6 +9,10 @@ export interface EvaluationClientOptions {
9
9
  baseUrl?: string;
10
10
  /** Per-request budget in milliseconds, 100–60000. Default 10000. */
11
11
  timeoutMillis?: number;
12
+ /** Bounded attempts for a read or a mutation the server deduplicates by its key, after a
13
+ * connection failure, a timeout or a 408 or 5xx Hue did not time with `Retry-After`. Default 4,
14
+ * maximum 10; 1 sends every such request once. */
15
+ maxAttempts?: number;
12
16
  }
13
17
  /** Thrown for a failed evaluation API request; the message is fixed and never includes response text. */
14
18
  export declare class HueApiError extends Error {
@@ -16,12 +20,22 @@ export declare class HueApiError extends Error {
16
20
  readonly status?: number | undefined;
17
21
  /** Seconds Hue asked the caller to wait (`Retry-After` on a 429 or 503), when it said. */
18
22
  readonly retryAfterSeconds?: number | undefined;
23
+ /** Hue's `X-Hue-Diagnostic` code, which tells refusals of one status apart, when it sent one. */
24
+ readonly diagnostic?: string | undefined;
19
25
  constructor(
20
26
  /** HTTP status when Hue answered; absent for network, timeout and parsing failures. */
21
27
  status?: number | undefined,
22
28
  /** Seconds Hue asked the caller to wait (`Retry-After` on a 429 or 503), when it said. */
23
- retryAfterSeconds?: number | undefined);
29
+ retryAfterSeconds?: number | undefined,
30
+ /** Hue's `X-Hue-Diagnostic` code, which tells refusals of one status apart, when it sent one. */
31
+ diagnostic?: string | undefined);
24
32
  }
33
+ /**
34
+ * Whether the same request, sent again, can succeed: a connection failure, a timeout, or a status
35
+ * Hue answers while it cannot act yet (408, 429, 500, 502, 503, 504). A refusal Hue decided on,
36
+ * any other 4xx, is not, and no number of attempts changes it.
37
+ */
38
+ export declare function isTransientApiError(error: unknown): boolean;
25
39
  /** An artifact download that ran past the size its caller expected; the rest is not read. */
26
40
  export declare class ArtifactSizeError extends Error {
27
41
  /** The size the caller expected, in bytes. */
@@ -32,22 +46,28 @@ export declare class ArtifactSizeError extends Error {
32
46
  }
33
47
  /**
34
48
  * Typed client for Hue's evaluation REST API: datasets, scorers, experiments, executions, runs,
35
- * results and hosted judge jobs. No implicit mutation retry: callers retain stable idempotency keys
36
- * for experiments and results. The one exception is a request Hue refused before acting on it with
37
- * a short `Retry-After` (HTTP 429 or 503, at most 5 seconds): it is sent again after that wait, up
38
- * to four times. Responses are bounded to 4 MiB.
49
+ * results and hosted judge jobs. A request Hue refused before acting on it with a short
50
+ * `Retry-After` (HTTP 429 or 503, at most 5 seconds) is sent again after that wait whatever its
51
+ * method, up to four times; a longer wait is the caller's error, carried as `retryAfterSeconds`.
52
+ * A read, or a mutation the server deduplicates by its idempotency key, is also sent again after
53
+ * a failure Hue did not time, a connection failure, a timeout or a 408 or 5xx without the header,
54
+ * up to `maxAttempts` times with a jittered backoff. A mutation without a key is never retried
55
+ * implicitly: callers retain stable idempotency keys for experiments and results. Responses are
56
+ * bounded to 4 MiB.
39
57
  */
40
58
  export declare class EvaluationClient {
41
59
  /** Validated Hue origin. */
42
60
  readonly baseUrl: string;
43
61
  private readonly apiKey;
44
62
  private readonly timeoutMillis;
63
+ private readonly maxAttempts;
45
64
  constructor(options: EvaluationClientOptions);
46
65
  /** Sends one request to Hue, again after the wait while Hue refuses it with a short
47
66
  * `Retry-After`. `init` runs for every attempt, so each has its own timeout; `signal` also ends
48
67
  * each attempt, and a wait to send one again, whose refusal is then the caller's error. */
49
68
  private send;
50
69
  private request;
70
+ private requestOnce;
51
71
  /** Verified bytes of one ready artifact in this project, bounded to the 25 MiB pilot file size.
52
72
  * With `maxBytes`, the download stops one byte past it and throws `ArtifactSizeError`. */
53
73
  downloadArtifact(id: string, options?: {