@hue-run/sdk 0.1.4 → 0.2.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 (58) hide show
  1. package/ENVIRONMENTS.md +182 -0
  2. package/EVALUATIONS.md +12 -0
  3. package/README.md +204 -21
  4. package/dist/ai-sdk.d.ts +9 -1
  5. package/dist/ai-sdk.js +37 -2
  6. package/dist/client.d.ts +130 -5
  7. package/dist/client.js +518 -110
  8. package/dist/config.d.ts +11 -2
  9. package/dist/config.js +50 -4
  10. package/dist/environment/client.d.ts +73 -0
  11. package/dist/environment/client.js +209 -0
  12. package/dist/environment/tools.d.ts +30 -0
  13. package/dist/environment/tools.js +24 -0
  14. package/dist/environment/types.d.ts +429 -0
  15. package/dist/environment/types.js +1 -0
  16. package/dist/environment.d.ts +5 -0
  17. package/dist/environment.js +2 -0
  18. package/dist/evals/attempt.d.ts +454 -0
  19. package/dist/evals/attempt.js +687 -0
  20. package/dist/evals/client.d.ts +99 -5
  21. package/dist/evals/client.js +136 -7
  22. package/dist/evals/environment-evidence.d.ts +6 -0
  23. package/dist/evals/environment-evidence.js +123 -0
  24. package/dist/evals/environment-json.d.ts +3 -0
  25. package/dist/evals/environment-json.js +76 -0
  26. package/dist/evals/json.d.ts +9 -1
  27. package/dist/evals/json.js +14 -6
  28. package/dist/evals/runner.d.ts +61 -2
  29. package/dist/evals/runner.js +71 -9
  30. package/dist/evals/scorer-publication.d.ts +2 -0
  31. package/dist/evals/scorer-publication.js +84 -0
  32. package/dist/evals/scorers.d.ts +11 -0
  33. package/dist/evals/scorers.js +56 -5
  34. package/dist/evals/simulation.d.ts +184 -0
  35. package/dist/evals/simulation.js +603 -0
  36. package/dist/evals/types.d.ts +304 -0
  37. package/dist/evals.d.ts +5 -1
  38. package/dist/evals.js +3 -1
  39. package/dist/experimental-telemetry.d.ts +8 -0
  40. package/dist/experimental-telemetry.js +13 -0
  41. package/dist/index.d.ts +4 -1
  42. package/dist/index.js +3 -1
  43. package/dist/managed.d.ts +51 -1
  44. package/dist/managed.js +11 -1
  45. package/dist/privacy.d.ts +2 -0
  46. package/dist/privacy.js +54 -21
  47. package/dist/receipt.d.ts +12 -1
  48. package/dist/receipt.js +10 -1
  49. package/dist/safety.d.ts +7 -0
  50. package/dist/safety.js +179 -0
  51. package/dist/snapshot.d.ts +12 -0
  52. package/dist/snapshot.js +200 -0
  53. package/dist/transport.d.ts +46 -8
  54. package/dist/transport.js +266 -48
  55. package/dist/types.d.ts +167 -8
  56. package/dist/version.d.ts +2 -0
  57. package/dist/version.js +3 -0
  58. package/package.json +51 -15
@@ -0,0 +1,182 @@
1
+ # Simulated environments
2
+
3
+ Install the optional evaluation runtime-contract peer with the SDK:
4
+
5
+ ```bash
6
+ npm install @hue-run/sdk zod
7
+ ```
8
+
9
+ Your agent runs in your process while a disposable simulated world runs in Hue. The world is
10
+ authoritative and records an ordered journal; Hue does not execute your agent code or provider
11
+ credentials.
12
+
13
+ ## Run a scenario like a test
14
+
15
+ `runSimulation` is the one-shot developer path. It calls your existing callback directly, so
16
+ IDE breakpoints and cooperative cancellation work. It does not register a worker, poll for jobs,
17
+ host your laptop or require an inbound tunnel.
18
+
19
+ ```ts
20
+ import { createHue } from "@hue-run/sdk";
21
+ import { createEnvironmentClient } from "@hue-run/sdk/environment";
22
+ import { createEvaluationClient, runSimulation } from "@hue-run/sdk/evals";
23
+
24
+ const connection = { apiKey: process.env.HUE_API_KEY! };
25
+ const hue = createHue({ ...connection, serviceName: "agent-test", captureContent: true });
26
+
27
+ try {
28
+ const report = await runSimulation({
29
+ client: createEvaluationClient(connection),
30
+ environmentClient: createEnvironmentClient(connection),
31
+ hue,
32
+ checkpointDirectory: ".hue-checkpoints/refund-scenario",
33
+ scenario: { kind: "experiment", experimentId: process.env.HUE_EXPERIMENT_ID! },
34
+ persistResultContent: true,
35
+ traceEvidence: { mode: "required" },
36
+ target: (inputs, { tools, mcp, config, signal }) =>
37
+ runMyExistingAgent({ inputs, config, tools, mcp, signal }),
38
+ onProgress(event) {
39
+ if (event.type === "run_created") console.log(`Inspect this run: ${event.runUrl}`);
40
+ },
41
+ });
42
+ console.log(report.runUrl);
43
+ } finally {
44
+ await hue.shutdownSafe();
45
+ }
46
+ ```
47
+
48
+ The referenced app-authored experiment is a template. Each completed invocation clones its
49
+ exact frozen dataset, configuration and scorer-version pins into a fresh experiment and creates
50
+ one isolated world per case. An interrupted invocation resumes through the private checkpoint
51
+ directory. If the agent may have run without a saved outcome, resume fails explicitly and never
52
+ calls it again. If Hue cannot confirm whether the world sealed, the execution likewise stays
53
+ uncertain and a resume refuses to re-invoke the agent.
54
+
55
+ `tools` contains framework-neutral local callables. `mcp` is a short-lived bearer for the same
56
+ run's closed catalog when a model provider executes MCP remotely. It is scoped to one execution
57
+ and world and is not the Hue project key. Configuration alone does not redirect real provider
58
+ calls; give one of these connections to the agent's actual tool boundary. `environmentRunId`
59
+ identifies the same world for adapter control operations such as
60
+ `environmentClient.recordCoverageGap`; it is not a credential. The MCP token is delivered only
61
+ to the callback and is never written to checkpoints.
62
+
63
+ ### Pinned provider-profile preflight
64
+
65
+ An experiment with an immutable `attemptBaselineV2` can require the local process to describe
66
+ the agent configuration it is actually about to run. Supply `actualAgentManifest`, the exact
67
+ ordered `requestedProviders`, and an `mcpSurface` selected from that request. Hue compares the
68
+ agent, prompt, model, tools, approvals, orchestration, MCP catalogs and native-helper
69
+ configuration before the callback or model runs. Missing evidence stays explicitly `missing`; it
70
+ is never treated as a match.
71
+
72
+ On a ready decision, `context.connectionBundle` contains the selected V2 provider surfaces and
73
+ `context.mcp` remains the backwards-compatible projection of the selected MCP surface. Endpoints,
74
+ bearers, expiry and credential generation stay in callback memory: the runner does not write
75
+ them to checkpoints or progress events, and it never mutates global `process.env`. A durable
76
+ `environment_incomplete` decision skips both the callback and scoring. If a ready response or
77
+ world seal cannot be confirmed, the checkpoint remains uncertain and resume neither reacquires
78
+ credentials nor invokes the callback again.
79
+
80
+ This is currently a control-plane contract. Hue can issue provider endpoints under
81
+ `/api/v1/provider-facades/{bindingId}/{grantId}`, but a provider data-plane facade call has not
82
+ yet been proven by the released integration. The existing generic Hue MCP capability remains the
83
+ runnable hosted-tool path; do not interpret preparation or local-tool tests as evidence of a
84
+ hosted Gmail or Slack MCP call.
85
+
86
+ ## Repository-authored scenarios
87
+
88
+ Repository scenarios publish through the same validated environment, dataset, scorer and
89
+ experiment APIs as Hue-authored scenarios. Stable slugs reuse matching immutable content
90
+ digests; changed definitions, tasks or scorers publish new versions. Hue does not synchronize
91
+ files back from its UI, and the helper refuses an unrelated mutable dataset draft instead of
92
+ overwriting it.
93
+
94
+ Repository publication supports the public `ScorerDefinition` union: exact match, includes,
95
+ JSON Schema, local code, manual and model-judge definitions. `runSimulation` applies the same
96
+ identity-affecting defaults as Hue before resolving versions and rejects unknown or server-only
97
+ kinds. In particular, `document_verifier` is not part of this SDK contract and is rejected rather
98
+ than published with a guessed digest.
99
+
100
+ ```ts
101
+ const scenario = {
102
+ kind: "repository" as const,
103
+ name: "Refund an eligible charge",
104
+ slug: "refund-eligible-charge",
105
+ environment: {
106
+ name: "Refund fixture",
107
+ slug: "refund-fixture",
108
+ definition: refundWorld,
109
+ },
110
+ cases: [
111
+ {
112
+ externalKey: "eligible-charge",
113
+ inputs: { request: "Refund charge ch_2" },
114
+ metadata: { suite: "billing" },
115
+ },
116
+ ],
117
+ scorers: [{ name: "Refund saved", slug: "refund-saved", scorer: refundScorer }],
118
+ config: { agentMode: "support" },
119
+ };
120
+
121
+ await runSimulation({
122
+ client,
123
+ environmentClient,
124
+ hue,
125
+ checkpointDirectory: ".hue-checkpoints/refund-scenario",
126
+ scenario,
127
+ persistResultContent: false,
128
+ traceEvidence: { mode: "required" },
129
+ target: (inputs, context) => runMyExistingAgent({ inputs, ...context }),
130
+ });
131
+ ```
132
+
133
+ Repeat the command after an edit for a fresh attempt and world. The run URL joins task, trace,
134
+ world effects, final state, target outcome and scorer results. Target failures and cancellations
135
+ seal the world as `abandoned`; scorer errors remain separate. Cancellation is cooperative, so
136
+ pass `context.signal` into the provider or agent call.
137
+
138
+ ## Direct environment tools
139
+
140
+ For lower-level use, create a run and bind its generated catalog:
141
+
142
+ ```ts
143
+ import { randomUUID } from "node:crypto";
144
+ import { bindEnvironmentTools, createEnvironmentClient } from "@hue-run/sdk/environment";
145
+
146
+ const client = createEnvironmentClient(connection);
147
+ const run = await client.createRun({
148
+ idempotencyKey: randomUUID(),
149
+ environmentVersionId,
150
+ });
151
+ const tools = bindEnvironmentTools({ hue, client, run });
152
+ await tools.refund_charge!.execute({ charge_id: "ch_2" });
153
+ await client.finishRun(run.id, { idempotencyKey: randomUUID(), status: "completed" });
154
+ ```
155
+
156
+ An observation with `status: "error"` is a recorded world answer, not a transport exception.
157
+ Run mutations retry with stable invocation/idempotency identities. Registry writes do not retry
158
+ automatically because identity creation and publication have no request key.
159
+
160
+ ## Coverage gaps
161
+
162
+ A provider adapter can record a known valid provider request that the environment cannot
163
+ implement with `client.recordCoverageGap(run.id, { idempotencyKey, provider, operation, code,
164
+ args, description })`. Use a durable UUID idempotency key and repeat the identical request to
165
+ recover a lost acknowledgement. This is a runner/adapter control operation, not an agent tool.
166
+ Arguments must be a JSON object of at most 16,000 encoded bytes.
167
+
168
+ Hue preserves the first report, marks `validity: "environment_incomplete"`, and refuses new
169
+ actions while still replaying already-recorded invocation receipts. `coverageGap` retains the
170
+ request and reporting provenance. An absent gap means `not_assessed`; it does not establish
171
+ provider parity.
172
+
173
+ Local scoring and historical rescoring skip incomplete evidence before calling a scorer, even
174
+ when the target returned no output or failed. Unsupported caller syntax and real provider errors
175
+ are not automatically coverage gaps; the adapter must identify a known missing provider
176
+ behavior. `runSimulation` checks the authoritative world when its callback throws: a durably
177
+ recorded gap finishes as environment-incomplete rather than `TargetError`, while a gap or seal
178
+ that cannot be confirmed stays uncertain and never causes the agent to be replayed.
179
+
180
+ The hosted MCP connection exposes Hue's bounded native actions; it is not general Gmail or
181
+ Slack HTTP parity and does not proxy arbitrary provider traffic. Forking, in-place reset and
182
+ arbitrary-step diffs are outside this interface.
package/EVALUATIONS.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Local evaluations
2
2
 
3
+ Install the optional runtime-contract peer with the SDK before importing
4
+ `@hue-run/sdk/evals`:
5
+
6
+ ```bash
7
+ npm install @hue-run/sdk zod
8
+ ```
9
+
3
10
  The SDK executes targets and scorers on your machine. Hue stores pinned definitions, experiment progress and results. It does not execute uploaded source code. Follow the [installation guide](https://docs.hue.run/installation) to add `@hue-run/sdk` to your application.
4
11
 
5
12
  ```ts
@@ -63,6 +70,11 @@ try {
63
70
 
64
71
  Create another experiment with the same frozen version and different `config` to compare configurations. The runner reads the exact experiment case/version and scorer definitions; it never resolves a mutable latest version. `rescore` accepts an existing evaluation-run ID and has no target callback. Subject IDs refer to immutable saved outputs and trace evidence.
65
72
 
73
+ For the shorter agent-against-a-hosted-world workflow, use `runSimulation`. It owns immutable
74
+ resolution, a fresh linked world per case, local and hosted MCP tools, finalization, sealed
75
+ evidence and scoring while retaining this runner's checkpoint guarantees. See
76
+ [Simulated environments](ENVIRONMENTS.md#run-a-scenario-like-a-test).
77
+
66
78
  ## Content and result states
67
79
 
68
80
  Both choices are required and independent:
package/README.md CHANGED
@@ -1,9 +1,19 @@
1
+ <p align="center">
2
+ <img alt="Hue" src="https://raw.githubusercontent.com/hue-run/hue-sdk/df0443f98c6096ff331fd0400715e4f3a1936607/.github/assets/hue-ascii-neutral.png" width="720">
3
+ </p>
4
+
1
5
  # Hue TypeScript SDK
2
6
 
3
- A Node 24 / Bun 1.3.9 client for Hue's standard OTLP HTTP endpoints. It uses the
7
+ [![npm](https://img.shields.io/npm/v/%40hue-run%2Fsdk?label=%40hue-run%2Fsdk)](https://www.npmjs.com/package/@hue-run/sdk) ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)
8
+
9
+ A client for Hue's standard OTLP HTTP endpoints on Node.js 22 or 24 and Bun 1.4.2. It uses the
4
10
  OpenTelemetry JavaScript SDK and official OTLP protobuf exporter components for
5
11
  traces and correlated logs. The package is named `@hue-run/sdk`.
6
12
 
13
+ [Documentation](https://docs.hue.run) · [Sign in](https://app.hue.run)
14
+
15
+ _Hue (hue.run) is a tracing and evaluation platform for AI agents. It is not affiliated with Philips Hue / Signify smart lighting or Cloudera Hue._
16
+
7
17
  ## Install
8
18
 
9
19
  ```bash
@@ -16,7 +26,8 @@ Or with Bun:
16
26
  bun add @hue-run/sdk
17
27
  ```
18
28
 
19
- Run the command in your application's server package. See the [compatibility guide](https://docs.hue.run/sdks/compatibility) before adding Hue to an application with existing OpenTelemetry or AI SDK dependencies.
29
+ Run the command in your application's server package. The package is ESM; CommonJS applications on
30
+ Node.js 22.12 or later load it with `require("@hue-run/sdk")`. See the [compatibility guide](https://docs.hue.run/sdks/compatibility) before adding Hue to an application with existing OpenTelemetry or AI SDK dependencies.
20
31
 
21
32
  ## Start
22
33
 
@@ -52,9 +63,90 @@ await hue.shutdown(); // flushes and releases providers owned by this client
52
63
  The default destination is `https://app.hue.run`. Data goes to
53
64
  `/api/v1/otlp/v1/traces` and `/api/v1/otlp/v1/logs` with a Bearer project key.
54
65
  Set `baseUrl` only for another Hue deployment. It must be an origin without an API path; a trailing slash is accepted.
55
- HTTPS is required except for loopback HTTP. Redirects are refused for both
56
- project checks and exports. There is no proprietary tracing protocol, lab API
57
- wrapper, database dependency, or dependency on the Hue application workspace.
66
+ HTTPS is required except for loopback HTTP or the explicit
67
+ [`allowInsecureHttp`](#local-development-without-a-hue-account) opt-in. Redirects are refused for both
68
+ project checks and exports.
69
+
70
+ `checkConnection()` rejects with `HueConnectionError`: its fixed message is safe to log, `status`
71
+ carries the HTTP status when Hue answered, and `cause` carries the underlying network, timeout or
72
+ parsing error. `serviceVersion` and `resourceAttributes` (for example
73
+ `{ "deployment.environment.name": "production", "service.namespace": "agents" }`) describe the
74
+ deployment; a client that owns its providers merges them into its resource, with `serviceName`
75
+ and `serviceVersion` taking precedence over same-named keys.
76
+
77
+ ## Model spans without a framework adapter
78
+
79
+ When you call a provider SDK directly, `hue.model()` creates the GenAI client span for the call.
80
+ Inside it, `setInput` and `setOutput` record `gen_ai.input.messages` / `gen_ai.output.messages`
81
+ when `captureContent` is true. Those attributes carry the OpenTelemetry GenAI message shape
82
+ (`{ role, parts: [{ type: "text", content }] }`, with `finish_reason` on output messages) defined
83
+ by the semantic conventions'
84
+ [input messages](https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/gen-ai/gen-ai-input-messages.json)
85
+ and
86
+ [output messages](https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/gen-ai/gen-ai-output-messages.json)
87
+ JSON schemas, so any semantic-convention-aware backend can read them. Convert provider-native
88
+ messages before recording them:
89
+
90
+ ```ts
91
+ await hue.model(
92
+ "gpt-5-mini",
93
+ async (span) => {
94
+ span.setInput(
95
+ messages.map((message) => ({
96
+ role: message.role,
97
+ parts: [{ type: "text", content: message.content }],
98
+ })),
99
+ );
100
+ const response = await openai.chat.completions.create({ model: "gpt-5-mini", messages });
101
+ span.setOutput(
102
+ response.choices.map((choice) => ({
103
+ role: choice.message.role,
104
+ parts: [{ type: "text", content: choice.message.content ?? "" }],
105
+ finish_reason: choice.finish_reason,
106
+ })),
107
+ );
108
+ span.setUsage({
109
+ inputTokens: response.usage?.prompt_tokens,
110
+ outputTokens: response.usage?.completion_tokens,
111
+ });
112
+ return response;
113
+ },
114
+ { provider: "openai" },
115
+ );
116
+ ```
117
+
118
+ The span is named `{operation} {model}` (`operation` defaults to `chat`) with
119
+ `gen_ai.operation.name`, `gen_ai.request.model` and `gen_ai.provider.name`. Like `withSpan`, the
120
+ options come after the callback and also accept `name`, `sessionId`, `userId`, `input` (recorded as
121
+ `gen_ai.input.messages`) and `parentContext`. `setUsage` records
122
+ nonnegative integer `gen_ai.usage.input_tokens` / `output_tokens`; other values are omitted and
123
+ counted as instrumentation failures. Unknown usage stays absent. `hue.tool(name, input, execute)`
124
+ creates an `execute_tool {name}` span with `gen_ai.tool.name`, arguments and result; an optional
125
+ fourth argument `{ callId }` records the provider's tool call id as `gen_ai.tool.call.id`. Content
126
+ helpers (`setInput`, `setOutput`, `tool` arguments and results, `recordMessages`,
127
+ `SpanOptions.input`) accept any value and encode plain JSON data (`JsonValue`) at runtime; a value
128
+ that is not JSON, such as a `Date` or a class instance, is omitted with an instrumentation failure
129
+ while the callback result is returned unchanged.
130
+
131
+ ## Vercel AI SDK 6
132
+
133
+ AI SDK 6 accepts a per-call tracer through `experimental_telemetry`. Pass
134
+ `hueExperimentalTelemetry(hue)` from the core entry point; the generated spans parent under
135
+ `withSpan`, inherit session/user identifiers, and record prompts and responses only when
136
+ `captureContent` is true:
137
+
138
+ ```ts
139
+ import { hueExperimentalTelemetry } from "@hue-run/sdk";
140
+
141
+ const result = await generateText({
142
+ model,
143
+ prompt,
144
+ experimental_telemetry: hueExperimentalTelemetry(hue),
145
+ });
146
+ ```
147
+
148
+ This requires no `@ai-sdk/otel` peer. `hueTelemetry` remains AI SDK 7 only: it reads the
149
+ installed `ai` major version once per process and throws a `TypeError` below 7.
58
150
 
59
151
  ## Vercel AI SDK 7
60
152
 
@@ -93,7 +185,7 @@ Reuse the client across server requests. Await stream completion before flushing
93
185
  returning a streaming `Response` does not mean its stream has finished. The
94
186
  [Next.js streaming recipe](https://docs.hue.run/integrations/opentelemetry#flush-streamed-responses-in-next-js)
95
187
  shows how to keep completion and flushing within the request's background lifetime.
96
- For a standalone script, put the operation in `try` and call `await hue.shutdown()`
188
+ For a standalone script, put the operation in `try` and call `await hue.shutdownSafe()`
97
189
  in `finally`. Shut down a shared server client only when the application stops.
98
190
 
99
191
  The integration creates real Vercel provider, streaming and tool spans and passes
@@ -110,27 +202,38 @@ normal OTel setup; Hue does not silently replace it.
110
202
  `captureContent: false` disables manual input/output/messages/tool content and
111
203
  removes recognized GenAI, Vercel, OpenInference and OpenLLMetry content attributes,
112
204
  legacy GenAI content events, log bodies, status messages and exception text before
113
- export. Model/provider/token metadata remains available. Generic custom attribute
205
+ export. The exported `contentPrefixes` array lists the attribute keys (and their dotted
206
+ children) that are removed. Model/provider/token metadata remains available. Generic custom attribute
114
207
  names cannot be classified automatically; use them deliberately.
115
208
 
116
209
  `captureContent: true` captures supplied content. Accepted content is stored by Hue;
117
210
  there is no SDK retention timer or automatic content expiry. To redact strings
118
211
  before export, supply `redact(value, path)`; it applies to supported strings in
119
212
  attributes, resources, event/link attributes and log bodies. Return a string.
120
- A throwing callback or invalid/oversized content fails closed: that record is
121
- counted as failed and the flush reports it. Shared resources are redacted once per
213
+ Invalid/oversized helper content is omitted with an instrumentation failure; the span can still be delivered. Export-time redactor failures reject the affected record and are reported by flush. Shared resources are redacted once per
122
214
  export batch. Do not put user content or secrets in span names or scope names.
123
215
 
124
216
  Manual helpers encode JSON values without converting null into absence. Unknown
125
- outputs and usage remain absent. This SDK does not estimate tokens or cost. Error
126
- helpers mark span status and record an exception; thrown application errors remain
127
- errors and are rethrown unchanged. `withSpan` ends its span in `finally`.
217
+ outputs and usage remain absent. This SDK does not estimate tokens or cost. A thrown
218
+ application error marks the span with `error.type` (the error's `name`), an ERROR status and an
219
+ `exception` event carrying only the type; exception messages and stack traces are never recorded by
220
+ the helpers, whatever `captureContent` is, and the error is rethrown unchanged. `withSpan` ends its
221
+ span in `finally`.
222
+
223
+ `recordMessages` emits the `gen_ai.client.inference.operation.details` log record correlated with
224
+ the active span, with the messages in its body. The record also carries `gen_ai.operation.name`,
225
+ `gen_ai.provider.name` and `gen_ai.request.model` as attributes, copied from the enclosing
226
+ `hue.model()` span or passed as `operation`, `provider` and `model`, and `gen_ai.conversation.id`
227
+ from the active session, so a collector fan-out to another GenAI-aware backend keeps the request
228
+ context.
128
229
 
129
230
  ## Existing OpenTelemetry providers
130
231
 
131
232
  Attach processors while constructing your providers. Hue uses local async context
132
233
  for its own helpers and never registers/replaces the global tracer, logger, or
133
- context manager.
234
+ context manager. When your application has registered a context manager, Hue helpers also make
235
+ their span the active OpenTelemetry span for the duration of the callback, so spans from other
236
+ instrumentations (HTTP clients, provider SDKs) that use the global API parent under it.
134
237
 
135
238
  ```ts
136
239
  import { TracerProvider } from "@opentelemetry/sdk-trace";
@@ -150,10 +253,60 @@ await hue.shutdown(); // flushes; does not shut down these externally owned prov
150
253
  // During application shutdown, shut down your providers, then await transport.shutdown().
151
254
  ```
152
255
 
153
- For external parent context pass `parentContext` to `withSpan`, or use standard
154
- OTel context propagation in your application. `getContext()` exposes the helper's
155
- current context for APIs taking an explicit context. Session/user identifiers are
156
- inherited within a client callback. Separate requests require separate callbacks.
256
+ The application's providers own the resource in this mode, so `resourceAttributes` on the
257
+ transport options is ignored and reported as a `warning` issue; set `deployment.environment.name`
258
+ and similar attributes on your own providers.
259
+
260
+ For external parent context pass `parentContext` to `withSpan`. Across processes, use
261
+ `hue.inject(carrier)` inside the producing span and `hue.extract(carrier)` in the worker; both
262
+ speak W3C `traceparent` only and never include the API key or baggage. Hue registers no global
263
+ propagator, so `propagation.inject()` from `@opentelemetry/api` is a no-op unless your
264
+ application configured one. `getContext()` exposes the helper's current context for APIs taking
265
+ an explicit context. Session/user identifiers are inherited within a client callback and are
266
+ stamped only on spans created through Hue's tracer (helpers and the AI SDK adapters); spans from
267
+ other instrumentations on a shared provider carry them only if that instrumentation sets them.
268
+ Separate requests require separate callbacks.
269
+
270
+ In attach mode Hue's span processor exports every span that ends on that provider, the same
271
+ default as other OpenTelemetry exporters. To send only part of a provider's spans, wrap the
272
+ processor:
273
+
274
+ ```ts
275
+ const aiSpansOnly = {
276
+ onStart: () => {},
277
+ onEnd: (span) => {
278
+ if ("gen_ai.operation.name" in span.attributes || span.name.startsWith("ai."))
279
+ transport.spanProcessor.onEnd(span);
280
+ },
281
+ forceFlush: () => transport.spanProcessor.forceFlush(),
282
+ shutdown: () => transport.spanProcessor.shutdown(),
283
+ };
284
+ ```
285
+
286
+ ## Local development without a Hue account
287
+
288
+ Hue speaks standard OTLP, so any local collector works. Point `baseUrl` at a loopback receiver
289
+ that accepts `/api/v1/otlp/v1/traces` and `/api/v1/otlp/v1/logs` (for example an OpenTelemetry
290
+ Collector `otlp` receiver with `http.traces_url_path` and `logs_url_path` set to those paths,
291
+ forwarding to Jaeger or the debug exporter) and pass any placeholder `apiKey`; HTTP is allowed
292
+ for loopback origins. `checkConnection()` and `verifyTrace()` are Hue-only diagnostics and are
293
+ not available against a generic collector.
294
+
295
+ A collector on a private network is not loopback: a docker-compose sibling such as
296
+ `http://otel-collector:4318` or an in-cluster service requires the explicit opt-in
297
+ `allowInsecureHttp: true`. The client then records a one-time `warning` issue because the key and
298
+ telemetry travel unencrypted. Use a placeholder key with such a collector, and never enable the
299
+ option for a real project key on a network you do not control.
300
+
301
+ ```ts
302
+ const hue = createHue({
303
+ apiKey: "local-placeholder",
304
+ serviceName: "my-agent",
305
+ captureContent: true,
306
+ baseUrl: "http://otel-collector:4318",
307
+ allowInsecureHttp: true,
308
+ });
309
+ ```
157
310
 
158
311
  ## Delivery behavior
159
312
 
@@ -165,7 +318,7 @@ flush. This is an in-memory queue, not durable storage.
165
318
 
166
319
  `flush()` waits for the current trace and log export work. A partial rejection,
167
320
  invalid acknowledgement, queue drop or failure throws `HueExportError`; its
168
- `report` contains cumulative accepted/rejected/failed/pending counts. Accepted
321
+ `report` contains cumulative accepted/rejected/failed counts and current pending gauges. Accepted
169
322
  means the collector acknowledged receipt, not that a complete trace has arrived.
170
323
  A malformed response reports uncertain acceptance as failure. Partial successes
171
324
  are not retried. Warning-only acknowledgements with zero rejected records remain
@@ -210,9 +363,25 @@ HTTP `status`. Missing expected spans and required fields remain explicit; a 200
210
363
  response alone is not success. A successful result verifies those requested
211
364
  conditions only. Use Hue's UI to inspect captured values and redaction.
212
365
 
366
+ ## Dependencies
367
+
368
+ The tracing core depends only on official `@opentelemetry/*` packages. The optional
369
+ `@hue-run/sdk/evals` entry point uses `zod` for its bounded runtime contracts; install that peer
370
+ when you use evaluations or simulations. JSON Schema scoring also uses `ajv`, an optional peer
371
+ loaded inside a worker only when `builtins.jsonSchema` scores a case; without it that scorer
372
+ reports `SchemaValidatorUnavailable`. Install it when you use that scorer:
373
+
374
+ ```bash
375
+ npm install zod
376
+ # Add ajv too when using builtins.jsonSchema.
377
+ npm install ajv
378
+ ```
379
+
380
+ See [THIRD_PARTY_NOTICES.md](https://github.com/hue-run/hue-sdk/blob/main/THIRD_PARTY_NOTICES.md) for licenses.
381
+
213
382
  ## Package verification
214
383
 
215
- From the repository root with Node 24 and Bun 1.3.9 on PATH:
384
+ From the repository root with Node 24 and Bun 1.4.2 on PATH:
216
385
 
217
386
  ```sh
218
387
  node packages/sdk-typescript/scripts/verify-package.mjs
@@ -224,7 +393,7 @@ HTTP exporter suite against that installed package, and installs/builds the
224
393
  standalone reference chatbot. It prints the artifact paths. No package is
225
394
  published. The chatbot README describes running that external installation.
226
395
 
227
- # Local evaluation workflows
396
+ ## Local evaluation workflows
228
397
 
229
398
  The optional `@hue-run/sdk/evals` entry point supports dataset/scorer registration, frozen-version experiments, local built-in/custom scoring, upload resume, and historical rescoring. See the [evaluation guide](https://docs.hue.run/evaluations/first-evaluation) for the complete journey, content policy and checkpoint recovery contract.
230
399
 
@@ -241,10 +410,16 @@ export const POST = createManagedTargetHandler({
241
410
  // Application-owned functions: keep your current provider, tools and tracing.
242
411
  target: async ({ input, config, inputFiles, signal }) =>
243
412
  runAgentForEvaluation({ input, config, inputFiles, signal }),
413
+ tracer: hue.tracer, // Required with a Hue-owned client: Hue never registers a global tracer.
244
414
  flushTelemetry: () => hue.flush(), // Existing Hue client; flush traces and logs.
245
415
  });
246
416
  ```
247
417
 
418
+ Without `tracer`, the handler falls back to the global OpenTelemetry tracer, its span is not
419
+ recorded, and every invocation returns `uncertain`. An application that only uses `createHue()`
420
+ also needs an OpenTelemetry context manager installed for the handler's span to propagate; see
421
+ the [managed-run guide](https://docs.hue.run/evaluations/managed-runs).
422
+
248
423
  `runAgentForEvaluation` adapts your application result to `{ output, files? }`.
249
424
  Files contain `filename`, `contentType`, actual `Uint8Array` data and an optional
250
425
  `primary` flag. The helper verifies input bytes, claims the invocation, saves the
@@ -253,5 +428,13 @@ Use a 120-second host request limit for the default 90-second execution and
253
428
  30-second finalization budget; your target must honor `signal`.
254
429
 
255
430
  See the [managed-run guide](https://docs.hue.run/evaluations/managed-runs) and the
256
- [full adapter contract](MANAGED_TARGETS.md) for registration, file handling,
431
+ [full adapter contract](https://github.com/hue-run/hue-sdk/blob/main/packages/sdk-typescript/MANAGED_TARGETS.md) for registration, file handling,
257
432
  existing-provider flush callbacks and recovery. Local/CI runners remain available.
433
+
434
+ ## Serving safely
435
+
436
+ Use `createHueSafe(options)` for best-effort startup. Invalid initialization returns a disabled client that keeps your `onExportIssue` hook and records the reason as an instrumentation failure. Pass `enabled: false` to disable Hue without a key or a `captureContent` choice; disabled helpers still execute the application callback, and `inject()` keeps propagating the application's own trace context. `flushSafe({ timeoutMillis: 1000 })` and `shutdownSafe({ timeoutMillis: 1000 })` return `{ ok, timedOut, report }` without rejecting. Strict initialization, connection checks and `flush()` remain available for diagnostics; do not gate application readiness or responses on them.
437
+
438
+ Capture/serialization/redaction/provider failures omit unsafe telemetry, record failures, and preserve the original business result/error. Async diagnostic rejections are contained; diagnostics are rate-limited. The default `maxQueueBytes` is 8 MiB across traces/logs including in-flight work, alongside the existing record cap. `pendingBytes` is a current queue gauge; `droppedSpans`, `droppedLogs` and `instrumentationFailures` are cumulative failure counters. This is a telemetry budget, not a total process memory ceiling. A timeout bounds the caller and does not cancel a borrowed provider. Never retry the business operation to recover telemetry. See [production safety](https://docs.hue.run/guides/production-safety).
439
+
440
+ Queued records snapshot supported telemetry values when a span ends or a log is emitted; later caller mutations cannot change queued data. Resource attributes still awaiting detection are omitted with a sanitized warning. Later records include them after detection finishes; await resource detection before instrumentation when those attributes are required.
package/dist/ai-sdk.d.ts CHANGED
@@ -1,4 +1,12 @@
1
1
  import type { TelemetryOptions } from "ai";
2
2
  import type { HueClient } from "./client.js";
3
- /** Use as the call's telemetry option; it does not change global AI SDK integrations. */
3
+ /**
4
+ * Per-call telemetry for AI SDK 7: pass as an agent's or generation call's `telemetry` option.
5
+ * Spans come from Hue's tracer, so they parent under `withSpan` and inherit session/user
6
+ * identifiers; prompt and response recording follow `captureContent`. It does not change global
7
+ * AI SDK integrations.
8
+ *
9
+ * @throws TypeError when the client is enabled and the installed `ai` major version is below 7;
10
+ * AI SDK 6 applications use `hueExperimentalTelemetry` from `@hue-run/sdk` instead.
11
+ */
4
12
  export declare function hueTelemetry(hue: HueClient): TelemetryOptions;
package/dist/ai-sdk.js CHANGED
@@ -1,8 +1,43 @@
1
+ import { createRequire } from "node:module";
1
2
  import { OpenTelemetry } from "@ai-sdk/otel";
2
- /** Use as the call's telemetry option; it does not change global AI SDK integrations. */
3
+ let installedAiMajor;
4
+ /**
5
+ * The installed `ai` major version, read once per process. An unreadable or unparsable version
6
+ * is left to the peer dependency range rather than rejected here.
7
+ */
8
+ function aiMajor() {
9
+ if (installedAiMajor === undefined) {
10
+ let major = Number.NaN;
11
+ try {
12
+ const manifest = createRequire(import.meta.url)("ai/package.json");
13
+ major = Number(String(manifest.version).split(".")[0]);
14
+ }
15
+ catch {
16
+ /* The peer range decides. */
17
+ }
18
+ installedAiMajor = Number.isInteger(major) ? major : Number.NaN;
19
+ }
20
+ return Number.isNaN(installedAiMajor) ? undefined : installedAiMajor;
21
+ }
22
+ /**
23
+ * Per-call telemetry for AI SDK 7: pass as an agent's or generation call's `telemetry` option.
24
+ * Spans come from Hue's tracer, so they parent under `withSpan` and inherit session/user
25
+ * identifiers; prompt and response recording follow `captureContent`. It does not change global
26
+ * AI SDK integrations.
27
+ *
28
+ * @throws TypeError when the client is enabled and the installed `ai` major version is below 7;
29
+ * AI SDK 6 applications use `hueExperimentalTelemetry` from `@hue-run/sdk` instead.
30
+ */
3
31
  export function hueTelemetry(hue) {
32
+ // Core tracing also installs beside AI SDK 6. This adapter needs the v7 per-call
33
+ // integration API, so fail explicitly during configuration instead of silently on v6.
34
+ if (hue.enabled) {
35
+ const major = aiMajor();
36
+ if (major !== undefined && major < 7)
37
+ throw new TypeError("hueTelemetry requires ai@7 or later; AI SDK 6 applications pass hueExperimentalTelemetry(hue) from @hue-run/sdk as experimental_telemetry");
38
+ }
4
39
  return {
5
- isEnabled: true,
40
+ isEnabled: hue.enabled,
6
41
  recordInputs: hue.captureContent,
7
42
  recordOutputs: hue.captureContent,
8
43
  integrations: [new OpenTelemetry({ tracer: hue.tracer, usage: true })],