@hue-run/sdk 0.12.0 → 0.13.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.
- package/CLI.md +14 -7
- package/ENVIRONMENTS.md +18 -4
- package/EVALUATIONS.md +3 -1
- package/README.md +29 -13
- package/dist/cli/eval.js +31 -8
- package/dist/client.d.ts +8 -0
- package/dist/client.js +23 -2
- package/dist/environment/types.d.ts +8 -1
- package/dist/environment/world.d.ts +8 -0
- package/dist/environment/world.js +17 -0
- package/dist/environment.d.ts +1 -1
- package/dist/environment.js +1 -1
- package/dist/evals/failure.d.ts +35 -0
- package/dist/evals/failure.js +252 -0
- package/dist/evals/local-worker.d.ts +1 -1
- package/dist/evals/runner.d.ts +21 -2
- package/dist/evals/runner.js +73 -13
- package/dist/evals/simulation.d.ts +1 -1
- package/dist/evals/types.d.ts +18 -4
- package/dist/evals.d.ts +1 -1
- package/dist/privacy.d.ts +18 -0
- package/dist/privacy.js +147 -57
- package/dist/safety.d.ts +18 -0
- package/dist/safety.js +76 -23
- package/dist/transport.d.ts +5 -0
- package/dist/transport.js +277 -57
- 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.13.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
|
|
|
@@ -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.
|
|
472
|
+
--archive .artifacts/typescript/hue-run-sdk-0.13.0.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.
|
|
483
|
+
--archive .artifacts/typescript/hue-run-sdk-0.13.0.tgz \
|
|
484
484
|
--origin https://STAGING_ORIGIN \
|
|
485
485
|
--project "$project" --command claim \
|
|
486
486
|
--evidence .context/setup-staging-after-claim.json
|
|
@@ -576,7 +576,9 @@ Hue-owned `world_outcome` checks are graded after the world seals. An experiment
|
|
|
576
576
|
every case of the saved version; there is no case subset.
|
|
577
577
|
|
|
578
578
|
`--command "<shell command>"` spawns the command once per case with the world's environment
|
|
579
|
-
(`HUE_WORLD_ID`, `HUE_WORLD_TOKEN`,
|
|
579
|
+
(`HUE_WORLD_ID`, `HUE_WORLD_TOKEN`, `HUE_WORLD_NOW` (the world's clock at creation, RFC 3339: read
|
|
580
|
+
today's date from it, never from the wall clock), one `HUE_SIM_<SURFACE ID>_URL` per provider
|
|
581
|
+
mirror,
|
|
580
582
|
`HUE_MCP_CONFIG` naming an owner-only `mcpServers` file that is removed after the case, and
|
|
581
583
|
`HUE_MCP_URL`, `HUE_MCP_TOKEN`, `HUE_MCP_EXPIRES_AT` for the first MCP mirror; these name the
|
|
582
584
|
world's simulated MCP server and replace a `HUE_MCP_URL` that `hue login` stored for Hue's project
|
|
@@ -618,6 +620,11 @@ for example, needs a reviewed `attemptBaselineV2` (see
|
|
|
618
620
|
registers none, so put `"attemptBaselineV2"` in the run's configuration JSON when you launch the
|
|
619
621
|
run from Hue.
|
|
620
622
|
|
|
623
|
+
`--trace-not-accepted pending` completes a case whose telemetry Hue did not accept in time in its
|
|
624
|
+
true state with the trace still to arrive, carrying the count of spans the case ended; Hue scores
|
|
625
|
+
the sealed world at once and attaches the trace once its export is complete. The default,
|
|
626
|
+
`fail_case`, completes such a case as failed with its evidence omitted.
|
|
627
|
+
|
|
621
628
|
A Scenario or eval set whose dataset version is not saved cannot back an experiment: the command
|
|
622
629
|
exits 1 and asks for **Save eval-set version** in Hue or `--save-version`, which freezes that
|
|
623
630
|
version at its current revision. Connection settings are `HUE_API_KEY` and `HUE_BASE_URL`
|
|
@@ -663,9 +670,9 @@ an interrupted run without invoking the agent again; a different selection is re
|
|
|
663
670
|
unfinished one is resumed or its directory is removed.
|
|
664
671
|
|
|
665
672
|
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
|
|
668
|
-
still pending at `--wait`, `2` usage or configuration error (including a missing key), `130`
|
|
673
|
+
`1` a case failed, errored (including one no pinned evaluator applies to or decides), was skipped,
|
|
674
|
+
was inconclusive (a required judge could not run or answer; the table says `INCONCLUSIVE` and the
|
|
675
|
+
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
676
|
interrupted. On Node.js 22, load TypeScript adapters with `NODE_OPTIONS=--experimental-strip-types`;
|
|
670
677
|
non-erasable syntax (enums, parameter properties, namespaces) needs a loader such as `--import tsx`
|
|
671
678
|
on any Node.js version.
|
package/ENVIRONMENTS.md
CHANGED
|
@@ -220,10 +220,24 @@ automatically because identity creation and publication have no request key.
|
|
|
220
220
|
Where a Hue deployment has the simulation gateway on, `createRun` returns the World API handoff
|
|
221
221
|
beside the run: `token` (a `hue_world_…` credential that lives exactly as long as the world),
|
|
222
222
|
`surfaces[]` (one mirror URL per pinned provider surface, such as the Gmail MCP and REST mirrors),
|
|
223
|
-
`
|
|
224
|
-
`
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
`now` (the world's clock at creation as an RFC 3339 timestamp), `env` (`HUE_WORLD_ID`,
|
|
224
|
+
`HUE_WORLD_TOKEN`, `HUE_WORLD_NOW`, `BAGGAGE`, `TRACEPARENT` and one `HUE_SIM_<SURFACE ID>_URL`
|
|
225
|
+
per surface) and `mcpConfig` (the common `mcpServers` shape with the token in the `Authorization`
|
|
226
|
+
header). The agent is pointed at the mirrors by configuration only: its own Gmail MCP or REST
|
|
227
|
+
client, the mirror URL, the world token where the Google credential went.
|
|
228
|
+
|
|
229
|
+
The world has its own date. A trace-built world's clock is frozen at the recorded start, so a
|
|
230
|
+
run that begins days or months after the recording still finds the recorded emails, events and
|
|
231
|
+
records at their recorded times. An agent that reads the wall clock for "today", "tomorrow" or
|
|
232
|
+
`newer_than:7d` asks the world about dates it holds nothing for; one that reads the world's date
|
|
233
|
+
lands on the recorded ones. Read it with `worldNow`: from the handoff, or from the environment of
|
|
234
|
+
an agent child, where `agentEnvironment` and `hue eval --command` set `HUE_WORLD_NOW`.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { worldNow } from "@hue-run/sdk/environment";
|
|
238
|
+
|
|
239
|
+
const today = worldNow() ?? new Date(); // HUE_WORLD_NOW in this process, else the wall clock
|
|
240
|
+
```
|
|
227
241
|
|
|
228
242
|
```ts
|
|
229
243
|
import {
|
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 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.
|
|
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
|
|
|
@@ -371,6 +371,8 @@ files directory must be owned by the current user and closed to everyone else (m
|
|
|
371
371
|
target again; if result content is not persisted, the JSON output cannot be reconstructed and the
|
|
372
372
|
case is reported as uncertain.
|
|
373
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
|
+
|
|
374
376
|
`rescore` downloads a subject's frozen `files` — the pinned inputs and the generated outputs, with
|
|
375
377
|
`role: "output"` for the documents a run produced — so a code evaluator can grade saved documents
|
|
376
378
|
without invoking an agent. Local scorers receive them as `context.files` exactly as they do during
|
package/README.md
CHANGED
|
@@ -45,7 +45,6 @@ await hue.withSpan(
|
|
|
45
45
|
async (span) => {
|
|
46
46
|
const output = await hue.tool("uppercase", "hello", () => "hello".toUpperCase());
|
|
47
47
|
span.setOutput(output);
|
|
48
|
-
hue.recordMessages({ output: [{ role: "assistant", content: output }] });
|
|
49
48
|
},
|
|
50
49
|
{ sessionId: "session-123", userId: "user-123", input: "hello" },
|
|
51
50
|
);
|
|
@@ -107,7 +106,8 @@ by the semantic conventions'
|
|
|
107
106
|
[input messages](https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/gen-ai/gen-ai-input-messages.json)
|
|
108
107
|
and
|
|
109
108
|
[output messages](https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/gen-ai/gen-ai-output-messages.json)
|
|
110
|
-
JSON schemas
|
|
109
|
+
JSON schemas. GenAI conventions are in Development; Hue supports the fields described here
|
|
110
|
+
and accepts caller-supplied JSON without validating the full draft. Convert provider-native
|
|
111
111
|
messages before recording them:
|
|
112
112
|
|
|
113
113
|
```ts
|
|
@@ -313,7 +313,7 @@ policy forbids sending that content to another service. `redact` and the credent
|
|
|
313
313
|
described below apply in both modes.
|
|
314
314
|
|
|
315
315
|
`captureContent: false` disables manual input/output/messages/tool content and
|
|
316
|
-
removes recognized GenAI, Vercel, OpenInference and
|
|
316
|
+
removes recognized GenAI, Vercel, OpenInference, OpenLLMetry and Langfuse content attributes,
|
|
317
317
|
legacy GenAI content events, log bodies, status messages and exception text before
|
|
318
318
|
export. The exported `contentPrefixes` array lists the attribute keys (and their dotted
|
|
319
319
|
children) that are removed. Model/provider/token metadata remains available. Generic custom attribute
|
|
@@ -323,7 +323,13 @@ names cannot be classified automatically; use them deliberately.
|
|
|
323
323
|
there is no SDK retention timer or automatic content expiry. To redact strings
|
|
324
324
|
before export, supply `redact(value, path)`; it applies to supported strings in
|
|
325
325
|
attributes, resources, event/link attributes and log bodies. Return a string.
|
|
326
|
-
Invalid
|
|
326
|
+
Invalid helper content is omitted with an instrumentation failure; the span can still be delivered.
|
|
327
|
+
A content value over Hue's 256 KiB value cap is not omitted: text is cut to a UTF-8 prefix and
|
|
328
|
+
bytes or a structured log body are replaced by the receiver's own marker
|
|
329
|
+
(`{ "hue.truncated": true, "hue.truncated_bytes": <size> }`), and the value's key is listed in
|
|
330
|
+
the record's `hue.truncated` attribute, where Hue's receiver lists the values it cuts itself,
|
|
331
|
+
so the span still carries the call, the recorded part of its value and the rest of its evidence.
|
|
332
|
+
Export-time redactor failures reject the affected record and are reported by flush. Shared resources are redacted once per
|
|
327
333
|
export batch. Do not put user content or secrets in span names or scope names.
|
|
328
334
|
|
|
329
335
|
Hosted tools carry credentials in their definitions, such as the `authorization` and `headers` of
|
|
@@ -355,7 +361,7 @@ rotates. Only definitions another integration recorded can be summarized: `hueTe
|
|
|
355
361
|
inputs and exports through Hue's attached processors gets the summary.
|
|
356
362
|
Recorded messages can inline files: GenAI `blob` parts in `gen_ai.input.messages` /
|
|
357
363
|
`gen_ai.output.messages` (what the AI SDK 7 adapter records for a file part) and AI SDK 6 `file`
|
|
358
|
-
parts in `ai.prompt.messages`. A span whose messages exceed 256 KiB would be
|
|
364
|
+
parts in `ai.prompt.messages`. A span whose messages exceed 256 KiB would be cut, so when a
|
|
359
365
|
record is queued Hue replaces the `content`/`data` of any such part longer than 64 KiB with the
|
|
360
366
|
file's `sha256` and `size`, both of the file's own bytes whatever its media type (base64 content
|
|
361
367
|
and `;base64` `data:` URLs decoded, other `data:` URLs percent-decoded, anything else as UTF-8 text), keeping the part's other fields such as `type`, `mime_type` and `mediaType`. Smaller inline
|
|
@@ -379,8 +385,8 @@ span in `finally`.
|
|
|
379
385
|
the active span, with the messages in its body. The record also carries `gen_ai.operation.name`,
|
|
380
386
|
`gen_ai.provider.name` and `gen_ai.request.model` as attributes, copied from the enclosing
|
|
381
387
|
`hue.model()` span or passed as `operation`, `provider` and `model`, and `gen_ai.conversation.id`
|
|
382
|
-
from the active session,
|
|
383
|
-
context.
|
|
388
|
+
from the active session, when those values are known. Use `recordMessages` inside `hue.model()` or
|
|
389
|
+
supply `operation` and `provider` explicitly to include the required inference context.
|
|
384
390
|
|
|
385
391
|
## Existing OpenTelemetry providers
|
|
386
392
|
|
|
@@ -447,7 +453,8 @@ that accepts `/api/v1/otlp/v1/traces` and `/api/v1/otlp/v1/logs` (for example an
|
|
|
447
453
|
Collector `otlp` receiver with `http.traces_url_path` and `logs_url_path` set to those paths,
|
|
448
454
|
forwarding to Jaeger or the debug exporter) and pass any placeholder `apiKey`; HTTP is allowed
|
|
449
455
|
for loopback origins. `checkConnection()` and `verifyTrace()` are Hue-only diagnostics and are
|
|
450
|
-
not available against a generic collector.
|
|
456
|
+
not available against a generic collector. Set `liveSpans: false` so Hue-specific placeholders
|
|
457
|
+
do not reach the collector's other destinations.
|
|
451
458
|
|
|
452
459
|
A collector on a private network is not loopback: a docker-compose sibling such as
|
|
453
460
|
`http://otel-collector:4318` or an in-cluster service requires the explicit opt-in
|
|
@@ -461,6 +468,7 @@ const hue = createHue({
|
|
|
461
468
|
serviceName: "my-agent",
|
|
462
469
|
captureContent: true,
|
|
463
470
|
baseUrl: "http://otel-collector:4318",
|
|
471
|
+
liveSpans: false,
|
|
464
472
|
allowInsecureHttp: true,
|
|
465
473
|
});
|
|
466
474
|
```
|
|
@@ -470,7 +478,11 @@ const hue = createHue({
|
|
|
470
478
|
Exports retry temporary HTTP/network failures (429, 502, 503, 504 and connection errors,
|
|
471
479
|
honoring `Retry-After`) within the export timeout, by OpenTelemetry's OTLP/HTTP exporter rules. Each
|
|
472
480
|
request is limited to 1 MiB before gzip (with space reserved for gzip overhead) and each content value to 256 KiB. Batches
|
|
473
|
-
split at record boundaries.
|
|
481
|
+
split at record boundaries. A record over the request limit on its own sheds its content values,
|
|
482
|
+
largest first, each replaced by the receiver's marker and listed under `hue.truncated`, until it
|
|
483
|
+
fits; only a record too large without any content value is lost, reported as an `invalid` issue,
|
|
484
|
+
and counted on its trace's root span as `hue.sdk.dropped_records` when the root is exported, so
|
|
485
|
+
Hue reads the trace as incomplete by that many records. Each signal queues at most 2,048 records, including
|
|
474
486
|
exports in flight; overflow is reported through the callback, counters and next
|
|
475
487
|
flush. This is an in-memory queue, not durable storage.
|
|
476
488
|
|
|
@@ -522,10 +534,14 @@ short span may send none and appear in Hue only when it finishes.
|
|
|
522
534
|
record and byte budgets, and skipped silently otherwise. While queued they count in
|
|
523
535
|
`pendingSpans` and `pendingBytes`, but never as accepted, rejected, failed or dropped records.
|
|
524
536
|
Losing only placeholders records a warning and does not make `flush()` throw.
|
|
525
|
-
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
537
|
+
- Placeholders travel in their own requests, after an export's completed spans, so a rejection
|
|
538
|
+
count is always one kind of record's: a completed span's rejection is never credited to a
|
|
539
|
+
placeholder, and a rejected request of placeholders is a warning. A Hue server that accepts
|
|
540
|
+
placeholders sends `Hue-Pending-Spans: 1` on every trace acknowledgement. The first trace
|
|
541
|
+
acknowledgement without it, whatever its request carried, shows the receiver predates them: the
|
|
542
|
+
transport records one warning (counting the placeholders the receiver rejected in that request, if any) and stops
|
|
543
|
+
sending placeholders for that client, so a receiver that acknowledged completed spans first
|
|
544
|
+
never receives one.
|
|
529
545
|
- Opt out with `liveSpans: false`. Setup credentials never send placeholders.
|
|
530
546
|
- With an existing provider, announcements start in `spanProcessor.onStart`. A wrapping processor
|
|
531
547
|
that forwards `onStart` should forward `onEnd` for the same spans: a span that ends without
|
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
|
|
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
|
-
|
|
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 : [
|
|
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
|
-
|
|
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: "
|
|
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: "
|
|
1245
|
+
traceNotAccepted: traceNotAcceptedPolicy(values["trace-not-accepted"]),
|
|
1225
1246
|
onTelemetryNotAccepted: telemetry.report,
|
|
1226
1247
|
concurrency: run.concurrency,
|
|
1227
1248
|
scorers: [],
|
|
@@ -1297,7 +1318,7 @@ async function runWorker(values, connection, adapter, agent, hue, output, signal
|
|
|
1297
1318
|
},
|
|
1298
1319
|
scorers: [],
|
|
1299
1320
|
concurrency,
|
|
1300
|
-
traceNotAccepted: "
|
|
1321
|
+
traceNotAccepted: traceNotAcceptedPolicy(values["trace-not-accepted"]),
|
|
1301
1322
|
onTelemetryNotAccepted: telemetry.report,
|
|
1302
1323
|
deprecationWarnings: false,
|
|
1303
1324
|
signal,
|
|
@@ -1402,6 +1423,8 @@ export async function runEvalCommand(argv) {
|
|
|
1402
1423
|
let json = false;
|
|
1403
1424
|
try {
|
|
1404
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"]);
|
|
1405
1428
|
json = values.json;
|
|
1406
1429
|
const output = {
|
|
1407
1430
|
log: (line) => (json ? process.stderr : process.stdout).write(`${line}\n`),
|
package/dist/client.d.ts
CHANGED
|
@@ -154,6 +154,14 @@ export declare class HueClient {
|
|
|
154
154
|
* alone is omitted and counted while the rest is recorded.
|
|
155
155
|
*/
|
|
156
156
|
recordFile(file: FileRecord, explicitContext?: Context): void;
|
|
157
|
+
/** The keys of each span's content values that were cut to Hue's value cap, for `hue.truncated`. */
|
|
158
|
+
private truncatedKeys;
|
|
159
|
+
/**
|
|
160
|
+
* A content value as a JSON attribute within Hue's 256 KiB value cap. A larger value is cut to
|
|
161
|
+
* a UTF-8 prefix and its key listed under `hue.truncated`, where Hue's receiver lists the
|
|
162
|
+
* values it cuts itself: the span carries the call and the recorded part of its value instead
|
|
163
|
+
* of losing the value whole. Only a value that cannot be encoded is omitted and counted.
|
|
164
|
+
*/
|
|
157
165
|
private setContent;
|
|
158
166
|
/**
|
|
159
167
|
* Confirms the key and origin by reading the current project; a setup and CI diagnostic, not a
|
package/dist/client.js
CHANGED
|
@@ -10,7 +10,8 @@ import { defaultResource, resourceFromAttributes } from "@opentelemetry/resource
|
|
|
10
10
|
import { HUE_SCOPE } from "./config.js";
|
|
11
11
|
import { hostedServerAddresses, hostedToolActivity, hostedToolProvider, providerErrorDescription, } from "./provider-tools.js";
|
|
12
12
|
import { toolCatalogSummary } from "./tool-definitions.js";
|
|
13
|
-
import { encodeContent, noopSpan, safeSpan } from "./safety.js";
|
|
13
|
+
import { encodeBoundedContent, encodeContent, noopSpan, safeSpan, } from "./safety.js";
|
|
14
|
+
import { TRUNCATED_KEY, withTruncatedKeys } from "./privacy.js";
|
|
14
15
|
import { createHueTransport, HueExportError } from "./transport.js";
|
|
15
16
|
import { verifyTrace } from "./receipt.js";
|
|
16
17
|
import { sdkVersion } from "./version.js";
|
|
@@ -712,11 +713,31 @@ export class HueClient {
|
|
|
712
713
|
this.instrumentationFailed();
|
|
713
714
|
}
|
|
714
715
|
}
|
|
716
|
+
/** The keys of each span's content values that were cut to Hue's value cap, for `hue.truncated`. */
|
|
717
|
+
truncatedKeys = new WeakMap();
|
|
718
|
+
/**
|
|
719
|
+
* A content value as a JSON attribute within Hue's 256 KiB value cap. A larger value is cut to
|
|
720
|
+
* a UTF-8 prefix and its key listed under `hue.truncated`, where Hue's receiver lists the
|
|
721
|
+
* values it cuts itself: the span carries the call and the recorded part of its value instead
|
|
722
|
+
* of losing the value whole. Only a value that cannot be encoded is omitted and counted.
|
|
723
|
+
*/
|
|
715
724
|
setContent(span, key, value) {
|
|
716
725
|
if (!this.enabled || this.closed || !this.captureContent)
|
|
717
726
|
return;
|
|
718
727
|
try {
|
|
719
|
-
|
|
728
|
+
const { text, truncated } = encodeBoundedContent(value);
|
|
729
|
+
span.setAttribute(key, text);
|
|
730
|
+
// The span's list as it stands: the application's own entries (read from the SDK span,
|
|
731
|
+
// which carries its attributes; the API alone shows none) and this helper's earlier ones.
|
|
732
|
+
const listed = withTruncatedKeys(span.attributes?.[TRUNCATED_KEY], this.truncatedKeys.get(span) ?? []);
|
|
733
|
+
const keys = truncated
|
|
734
|
+
? withTruncatedKeys(listed, [key])
|
|
735
|
+
: listed.filter((entry) => entry !== key);
|
|
736
|
+
if (truncated || listed.includes(key)) {
|
|
737
|
+
// A whole value replacing a cut one unmarks its key; an untouched list is left alone.
|
|
738
|
+
this.truncatedKeys.set(span, keys);
|
|
739
|
+
span.setAttribute(TRUNCATED_KEY, keys);
|
|
740
|
+
}
|
|
720
741
|
}
|
|
721
742
|
catch {
|
|
722
743
|
this.instrumentationFailed();
|
|
@@ -364,7 +364,11 @@ export interface WorldHandoff {
|
|
|
364
364
|
baggage: string;
|
|
365
365
|
/** Mirror surfaces of every provider instance the environment version binds. */
|
|
366
366
|
surfaces: WorldSurface[];
|
|
367
|
-
/**
|
|
367
|
+
/** The world's clock at creation (RFC 3339): the date an agent computes its dates from in
|
|
368
|
+
* place of the wall clock, a trace-built world's recorded start. Also `env.HUE_WORLD_NOW`;
|
|
369
|
+
* null for a Hue that names neither. */
|
|
370
|
+
now: string | null;
|
|
371
|
+
/** `HUE_WORLD_ID`, `HUE_WORLD_TOKEN`, `HUE_WORLD_NOW`, `BAGGAGE`, `TRACEPARENT` and one
|
|
368
372
|
* `HUE_SIM_<SURFACE ID>_URL` per surface. */
|
|
369
373
|
env: Record<string, string>;
|
|
370
374
|
/** The `mcpServers` configuration for the MCP surfaces. */
|
|
@@ -378,6 +382,9 @@ export interface EnvironmentRun {
|
|
|
378
382
|
environmentVersionId: string;
|
|
379
383
|
/** Current virtual nanoseconds as a decimal string. */
|
|
380
384
|
clockNs: string;
|
|
385
|
+
/** The world's clock at creation as an RFC 3339 timestamp (`clockNs` as a date): a
|
|
386
|
+
* trace-built world's recorded start. Absent from a Hue that predates it. */
|
|
387
|
+
now?: string;
|
|
381
388
|
/** Digest of current world state. */
|
|
382
389
|
stateDigest: string;
|
|
383
390
|
/** Maximum recorded actions. */
|
|
@@ -5,6 +5,14 @@ import type { EnvironmentRun, LegacyMcpCapability, WorldHandoff } from "./types.
|
|
|
5
5
|
* mirror URLs. The handoff carries the world token; keep it out of logs and checkpoints.
|
|
6
6
|
*/
|
|
7
7
|
export declare function worldHandoff(run: EnvironmentRun): WorldHandoff | null;
|
|
8
|
+
/**
|
|
9
|
+
* The world's clock: what an agent reads for today's date in place of the wall clock, so a
|
|
10
|
+
* date-relative request (tomorrow, `newer_than:7d`, this month) lands on the dates the world
|
|
11
|
+
* holds however long after the recording the run starts. From the handoff's `now`, else from
|
|
12
|
+
* `HUE_WORLD_NOW` in the given environment (this process's by default, where `hue eval
|
|
13
|
+
* --command` and `agentEnvironment` set it). Null when neither names it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function worldNow(source?: WorldHandoff | Record<string, string | undefined>): Date | null;
|
|
8
16
|
/** Variables that authenticate against Hue's control plane rather than a simulated provider. */
|
|
9
17
|
export declare const HUE_CONTROL_PLANE_VARIABLES: readonly string[];
|
|
10
18
|
/** True for a control-plane variable by name, or for any variable holding a Hue credential. */
|
|
@@ -18,6 +18,7 @@ export function worldHandoff(run) {
|
|
|
18
18
|
completingUntil: run.completingUntil ?? null,
|
|
19
19
|
traceparent: run.traceparent ?? null,
|
|
20
20
|
baggage: run.baggage ?? `hue-world=${id}`,
|
|
21
|
+
now: run.now ?? run.env.HUE_WORLD_NOW ?? null,
|
|
21
22
|
surfaces: run.surfaces.map((surface) => ({ ...surface })),
|
|
22
23
|
env: { ...run.env },
|
|
23
24
|
mcpConfig: {
|
|
@@ -28,6 +29,22 @@ export function worldHandoff(run) {
|
|
|
28
29
|
},
|
|
29
30
|
};
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The world's clock: what an agent reads for today's date in place of the wall clock, so a
|
|
34
|
+
* date-relative request (tomorrow, `newer_than:7d`, this month) lands on the dates the world
|
|
35
|
+
* holds however long after the recording the run starts. From the handoff's `now`, else from
|
|
36
|
+
* `HUE_WORLD_NOW` in the given environment (this process's by default, where `hue eval
|
|
37
|
+
* --command` and `agentEnvironment` set it). Null when neither names it.
|
|
38
|
+
*/
|
|
39
|
+
export function worldNow(source = process.env) {
|
|
40
|
+
const text = "env" in source && typeof source.env === "object" && source.env !== null
|
|
41
|
+
? (source.now ?? source.env.HUE_WORLD_NOW)
|
|
42
|
+
: source.HUE_WORLD_NOW;
|
|
43
|
+
if (!text)
|
|
44
|
+
return null;
|
|
45
|
+
const date = new Date(text);
|
|
46
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
47
|
+
}
|
|
31
48
|
/** Variables that authenticate against Hue's control plane rather than a simulated provider. */
|
|
32
49
|
export const HUE_CONTROL_PLANE_VARIABLES = [
|
|
33
50
|
"HUE_API_KEY",
|
package/dist/environment.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export type { EnvironmentClientOptions } from "./environment/client.js";
|
|
|
3
3
|
export { bindEnvironmentTools } from "./environment/tools.js";
|
|
4
4
|
export type { BindEnvironmentToolsOptions, EnvironmentTool } from "./environment/tools.js";
|
|
5
5
|
export type * from "./environment/types.js";
|
|
6
|
-
export { agentEnvironment, HUE_CONTROL_PLANE_VARIABLES, isHueControlPlaneCredential, legacyMcpCapability, stripHueControlPlaneCredentials, worldHandoff, writeMcpConfig, } from "./environment/world.js";
|
|
6
|
+
export { agentEnvironment, HUE_CONTROL_PLANE_VARIABLES, isHueControlPlaneCredential, legacyMcpCapability, stripHueControlPlaneCredentials, worldHandoff, worldNow, writeMcpConfig, } from "./environment/world.js";
|
|
7
7
|
export type { AgentEnvironmentOptions, McpConfigFile } from "./environment/world.js";
|
package/dist/environment.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { createEnvironmentClient, EnvironmentClient, HueEnvironmentError, isTransientEnvironmentError, } from "./environment/client.js";
|
|
2
2
|
export { bindEnvironmentTools } from "./environment/tools.js";
|
|
3
|
-
export { agentEnvironment, HUE_CONTROL_PLANE_VARIABLES, isHueControlPlaneCredential, legacyMcpCapability, stripHueControlPlaneCredentials, worldHandoff, writeMcpConfig, } from "./environment/world.js";
|
|
3
|
+
export { agentEnvironment, HUE_CONTROL_PLANE_VARIABLES, isHueControlPlaneCredential, legacyMcpCapability, stripHueControlPlaneCredentials, worldHandoff, worldNow, writeMcpConfig, } from "./environment/world.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a target's throw shows about a service the agent called, when a service rather than the
|
|
3
|
+
* agent's own code stopped it: `ServiceRefused` for a retryable HTTP status (408, 429 or 5xx) a
|
|
4
|
+
* model provider, Hue or another service answered with, such as a rate limit or an outage;
|
|
5
|
+
* `ConnectionFailed` for a connection that failed; `TimedOut` for a call that timed out; and
|
|
6
|
+
* `ConfigurationRejected` for any other status Hue's own clients answered with (a 4xx: a key, a
|
|
7
|
+
* project, an environment version or a world Hue refused), which is the caller's configuration;
|
|
8
|
+
* the clients fetch with `redirect: "error"`, so a redirect surfaces without a status, as a
|
|
9
|
+
* failed connection.
|
|
10
|
+
* The runner records the type on the execution in place of `TargetError`; Hue classes the first
|
|
11
|
+
* three as infrastructure and the last as configuration, so an error from Hue's own clients is
|
|
12
|
+
* never filed as the agent's. Anything else is the agent's own error.
|
|
13
|
+
*
|
|
14
|
+
* A simulated world the agent reaches through its own client (an MCP client, a REST client, a
|
|
15
|
+
* fetch) answers Hue's gateway refusals with a status and a diagnostic: `503
|
|
16
|
+
* authorization_unavailable` when Hue could not authorize the world token because its database
|
|
17
|
+
* could not answer, `503 gateway_failure` when the gateway failed. The MCP SDK's transport throws
|
|
18
|
+
* these as `StreamableHTTPError` or `SseError` with the status as `code` and no response, so the
|
|
19
|
+
* status is read from there too; the diagnostic is the error's cause (`serviceFailureCause`),
|
|
20
|
+
* recorded beside the type so a run's reader sees what refused it.
|
|
21
|
+
*/
|
|
22
|
+
export type ServiceFailureType = "ServiceRefused" | "ConnectionFailed" | "TimedOut" | "ConfigurationRejected";
|
|
23
|
+
/** The service failure a target's throw shows, by the errors linked from it, in precedence
|
|
24
|
+
* order: a timeout, a failed connection, a retryable refusal, a status Hue's own client was
|
|
25
|
+
* refused with; null for the agent's own error. */
|
|
26
|
+
export declare function serviceFailureType(error: unknown): ServiceFailureType | null;
|
|
27
|
+
/**
|
|
28
|
+
* The service's own word for what failed, when the answer a target's throw carries names one:
|
|
29
|
+
* Hue's gateway diagnostic (`authorization_unavailable`, `gateway_failure`), read from the
|
|
30
|
+
* `x-hue-diagnostic` header or the JSON body's `diagnostic` of any error linked from the throw,
|
|
31
|
+
* nearest first. Undefined when no answer names one, or the name is not a diagnostic's token.
|
|
32
|
+
* Read for a service failure (`serviceFailureType`), where the runner records it as the error's
|
|
33
|
+
* `cause`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function serviceFailureCause(error: unknown): string | undefined;
|