@hue-run/sdk 0.20.6 → 0.20.8

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.20.6`, `@opentelemetry/api@1.9.1` and
45
+ not a completed integration. TypeScript uses `@hue-run/sdk@0.20.8`, `@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
 
@@ -381,8 +381,8 @@ and Hue never receives or stores them. The command is part of the `hue` executab
381
381
  # LANGFUSE_PUBLIC_KEY=pk-lf-...
382
382
  # LANGFUSE_SECRET_KEY=sk-lf-...
383
383
  # LANGFUSE_BASE_URL=https://cloud.langfuse.com
384
- npx -y @hue-run/sdk@0.20.6 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
385
- npx -y @hue-run/sdk@0.20.6 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
384
+ npx -y @hue-run/sdk@0.20.8 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
385
+ npx -y @hue-run/sdk@0.20.8 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
386
386
  ```
387
387
 
388
388
  `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` come from the environment or `--env-path`
@@ -536,8 +536,9 @@ instrumentation scope `hue.import.atif`, and the run's agent span is named `invo
536
536
  <agent.name>` from the export's own agent. ATIF records instants, not durations, so model and tool
537
537
  spans are points in time, and a tool's result is kept as text with no error status of the
538
538
  importer's making. The same trace in two files is one trace when the copies agree and a refusal
539
- when they differ; a trace with more than 100,000 spans, 64 batches or 1,000 values is refused and
540
- named. Nothing about where a file sat or what it was called reaches a span: a renamed, reordered
539
+ when they differ; a trace with more than 100,000 spans, 512 batches or 10,000 values is refused and
540
+ named, and an input of more than 25,000 traces, or whose manifest would exceed 32 MiB (4 MiB
541
+ compressed), is refused whole. Nothing about where a file sat or what it was called reaches a span: a renamed, reordered
541
542
  or re-zipped copy of the bundle prepares to byte-identical batches and the same manifest digest.
542
543
 
543
544
  The report (`--json` prints it as one object) names `manifest_digest`, `totals` (traces, batches,
@@ -553,7 +554,7 @@ no upload has started in it.
553
554
 
554
555
  **Authorize.** Call `prepare_trace_import` over the Hue MCP with the report's `manifest_digest`,
555
556
  `totals` and `source`. It answers the import id, `upload_url` and an `authorization` (`hue_it_` and
556
- 43 characters) valid for an hour. Save its answer, or the authorization alone, in a private file
557
+ 43 characters) valid for a day. Save its answer, or the authorization alone, in a private file
557
558
  (mode 0600; never in a transcript, a message or a shell history). The same digest in the same
558
559
  project is the same import: calling again renews the authorization for an interrupted upload and
559
560
  revokes the earlier one.
@@ -697,7 +698,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
697
698
  # Set project to an existing supported fixture; use the same directory on resume.
698
699
  project=/absolute/path/to/supported-fixture
699
700
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
700
- --archive .artifacts/typescript/hue-run-sdk-0.20.6.tgz \
701
+ --archive .artifacts/typescript/hue-run-sdk-0.20.8.tgz \
701
702
  --origin https://STAGING_ORIGIN \
702
703
  --project "$project" --command setup \
703
704
  --evidence .context/setup-staging-before-claim.json
@@ -708,7 +709,7 @@ the private local handoff and finish the real browser claim, then reconcile the
708
709
 
709
710
  ```sh
710
711
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
711
- --archive .artifacts/typescript/hue-run-sdk-0.20.6.tgz \
712
+ --archive .artifacts/typescript/hue-run-sdk-0.20.8.tgz \
712
713
  --origin https://STAGING_ORIGIN \
713
714
  --project "$project" --command claim \
714
715
  --evidence .context/setup-staging-after-claim.json
@@ -750,10 +751,10 @@ otherwise. Never hand the agent Hue-native tools (`context.tools`, `context.mcp`
750
751
  production agent lacks. Pin the SDK, so the agent's repository gains no dependency:
751
752
 
752
753
  ```sh
753
- npx -y @hue-run/sdk@0.20.6 eval --case "Refund an eligible charge" --command "node agent.js"
754
- npx -y @hue-run/sdk@0.20.6 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
755
- npx -y @hue-run/sdk@0.20.6 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
756
- npx -y @hue-run/sdk@0.20.6 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
754
+ npx -y @hue-run/sdk@0.20.8 eval --case "Refund an eligible charge" --command "node agent.js"
755
+ npx -y @hue-run/sdk@0.20.8 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
756
+ npx -y @hue-run/sdk@0.20.8 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
757
+ npx -y @hue-run/sdk@0.20.8 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
757
758
  ```
758
759
 
759
760
  One invocation creates one run holding every selected case; it prints the run URL
@@ -791,7 +792,7 @@ export default function runMyAgent(inputs: JsonValue, context: SimulationTargetC
791
792
  ```
792
793
 
793
794
  ```sh
794
- npx -y @hue-run/sdk@0.20.6 eval --case "Refund an eligible charge" ./hue-agent.ts --content
795
+ npx -y @hue-run/sdk@0.20.8 eval --case "Refund an eligible charge" ./hue-agent.ts --content
795
796
  ```
796
797
 
797
798
  `--scenario` remains an alias for `--case` for existing scripts. Pass one selection flag.
package/README.md CHANGED
@@ -536,8 +536,8 @@ the Langfuse project's own keys (`LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, a
536
536
  `HUE_API_KEY`:
537
537
 
538
538
  ```sh
539
- npx -y @hue-run/sdk@0.20.6 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
540
- npx -y @hue-run/sdk@0.20.6 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
539
+ npx -y @hue-run/sdk@0.20.8 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
540
+ npx -y @hue-run/sdk@0.20.8 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
541
541
  ```
542
542
 
543
543
  - It reads Langfuse Cloud and self-hosted v4 through the Observations API v2, and self-hosted v3
@@ -610,7 +610,9 @@ installed `ai` major version once per process and throws a `TypeError` below 7.
610
610
 
611
611
  ## Vercel AI SDK 7
612
612
 
613
- Compatible optional peers are `ai@^7.0.99` and `@ai-sdk/otel@^1.0.99`, alongside
613
+ Compatible optional peers are `ai@^7.0.0` and `@ai-sdk/otel@^1.0.0` (install `@ai-sdk/otel` with
614
+ the matching patch number, for example `ai@7.0.99` with `@ai-sdk/otel@1.0.99`; each `@ai-sdk/otel`
615
+ release depends on exactly that `ai` release), alongside
614
616
  `@opentelemetry/api@1.9.1`. For an app without global AI SDK telemetry integrations,
615
617
  configure telemetry on each agent or generation call:
616
618
 
@@ -1043,7 +1045,9 @@ receipt request. It does not flush, run your application, create a test span, or
1043
1045
  read captured values. `fields` reports the presence of stored normalized input,
1044
1046
  output, model, usage, and session data across the trace; it does not establish
1045
1047
  content correctness or that every possible span has arrived. Leave unknown usage
1046
- and intentionally disabled content out of `requiredFields`.
1048
+ and intentionally disabled content out of `requiredFields`. When Hue reports the trace
1049
+ missing, `project` names the project the key searched: a trace absent there was sent with a
1050
+ different key (for example one still exported in the shell) or is not stored yet.
1047
1051
 
1048
1052
  The default budget is 10 seconds; set `timeoutMillis` up to 60,000. Only a recognized
1049
1053
  missing trace, HTTP 429, or HTTP 503 is retried, respecting `Retry-After` and the
package/dist/cli/login.js CHANGED
@@ -220,11 +220,8 @@ async function readBoundedText(response) {
220
220
  function isRecord(value) {
221
221
  return typeof value === "object" && value !== null && !Array.isArray(value);
222
222
  }
223
- /**
224
- * Mirrors `checkConnection()` with `GET /api/v1/projects/current`, then confirms evaluation access
225
- * with `GET /api/v1/datasets`. No redirects are followed.
226
- */
227
- async function checkEvaluationsKey(fetchImpl, origin, apiKey) {
223
+ /** Mirrors `checkConnection()` with `GET /api/v1/projects/current`; `detail` is the project name. */
224
+ async function checkProject(fetchImpl, origin, apiKey) {
228
225
  let response;
229
226
  try {
230
227
  response = await fetchImpl(`${origin}/api/v1/projects/current`, {
@@ -253,16 +250,21 @@ async function checkEvaluationsKey(fetchImpl, origin, apiKey) {
253
250
  detail: `Hue answered HTTP ${response.status} while checking the evaluations key.`,
254
251
  };
255
252
  }
256
- let projectName;
257
253
  try {
258
254
  const project = JSON.parse(await readBoundedText(response));
259
255
  if (!isRecord(project) || typeof project.name !== "string")
260
256
  throw new Error("Invalid project");
261
- projectName = project.name;
257
+ return { ok: true, detail: project.name };
262
258
  }
263
259
  catch {
264
260
  return { ok: false, rejected: false, detail: "Hue returned an unexpected project response." };
265
261
  }
262
+ }
263
+ /** {@link checkProject}, then confirms evaluation access with `GET /api/v1/datasets`. */
264
+ async function checkEvaluationsKey(fetchImpl, origin, apiKey) {
265
+ const project = await checkProject(fetchImpl, origin, apiKey);
266
+ if (!project.ok)
267
+ return project;
266
268
  // Every valid key reaches the project check; only a key with write access can list eval sets,
267
269
  // so a Read or Tracing only key is refused here instead of failing later in `hue eval`.
268
270
  let evaluations;
@@ -290,7 +292,7 @@ async function checkEvaluationsKey(fetchImpl, origin, apiKey) {
290
292
  rejected: false,
291
293
  detail: `Hue answered HTTP ${evaluations.status} while checking evaluation access.`,
292
294
  };
293
- return { ok: true, detail: projectName };
295
+ return project;
294
296
  }
295
297
  /** Reads JSON-RPC messages from a JSON body or a `text/event-stream` body. */
296
298
  function parseJsonRpcMessages(text, contentType) {
@@ -689,6 +691,21 @@ export async function runLoginCommand(argv, io = {}) {
689
691
  out(kind === "evaluations"
690
692
  ? `Evaluations access accepted for project "${check.detail}".`
691
693
  : `Coding-agent access accepted; the Hue MCP server lists ${check.detail} tools.`);
694
+ // Loaders such as `uv run --env-file` and dotenv keep an exported variable over the file,
695
+ // so a stale shell key silently sends traces to another project. Name projects, never keys.
696
+ if (kind === "evaluations" && env.HUE_API_KEY && env.HUE_API_KEY !== value) {
697
+ // The shell key only goes to the server it was exported for.
698
+ const shellOrigin = env.HUE_BASE_URL ?? DEFAULT_ORIGIN;
699
+ const shell = shellOrigin === origin ? await checkProject(fetchImpl, origin, env.HUE_API_KEY) : null;
700
+ const owner = !shell
701
+ ? ` for ${shellOrigin}`
702
+ : shell.ok
703
+ ? ` for project "${shell.detail}"`
704
+ : shell.rejected
705
+ ? " that Hue does not accept"
706
+ : " whose project could not be checked";
707
+ stderr.write(`Warning: this shell already exports a different HUE_API_KEY${owner}. It takes precedence over ${envDisplay} (project "${check.detail}") in tools that load env files without overriding them; unset it before running the application.\n`);
708
+ }
692
709
  }
693
710
  if (!failure) {
694
711
  const text = mergeEnvText(envFile.text, updates);
@@ -738,12 +755,55 @@ export async function runLoginCommand(argv, io = {}) {
738
755
  }
739
756
  if (failure)
740
757
  return fail(failure.message, failure.code);
741
- out("Next steps:");
758
+ const steps = [];
742
759
  if (stored.includes("coding-agent"))
743
760
  // `hue mcp install` defaults to production; a non-default origin needs its own endpoint.
744
761
  // Without --auth it configures sign-in unless HUE_MCP_KEY is exported, so name the key stored.
745
- out(` hue mcp install --client claude-code --auth key${mcpUrl === mcpUrlForOrigin(DEFAULT_ORIGIN) ? "" : ` --url ${mcpUrl}`}`);
746
- if (stored.includes("evaluations"))
747
- out(` hue eval --case "<name>" ./hue-agent.ts${envDisplay === DEFAULT_ENV_FILE ? "" : ` --env-file ${envDisplay}`}`);
762
+ steps.push(`hue mcp install --client ${(await detectMcpClient(cwd)) ?? "<claude-code|codex|conductor|cursor|vscode|windsurf|gemini>"} --auth key${mcpUrl === mcpUrlForOrigin(DEFAULT_ORIGIN) ? "" : ` --url ${mcpUrl}`}`);
763
+ if (stored.includes("evaluations")) {
764
+ const agent = await detectEvalAgent(cwd);
765
+ if (agent)
766
+ steps.push(`hue eval --case "<name>" ${agent}${envDisplay === DEFAULT_ENV_FILE ? "" : ` --env-file ${envDisplay}`}`);
767
+ }
768
+ if (steps.length > 0) {
769
+ out("Next steps:");
770
+ for (const step of steps)
771
+ out(` ${step}`);
772
+ }
748
773
  return 0;
749
774
  }
775
+ /** Client configuration present in the repository root; `hue mcp install` writes the first three. */
776
+ const MCP_CLIENT_MARKERS = [
777
+ ["claude-code", ".mcp.json"],
778
+ ["cursor", ".cursor"],
779
+ ["vscode", ".vscode"],
780
+ ["claude-code", ".claude"],
781
+ ["codex", ".codex"],
782
+ ["gemini", ".gemini"],
783
+ ["windsurf", ".windsurf"],
784
+ ];
785
+ /** The one client whose configuration the repository holds; undefined when none or several do. */
786
+ async function detectMcpClient(cwd) {
787
+ const found = new Set();
788
+ for (const [client, marker] of MCP_CLIENT_MARKERS)
789
+ if (await access(join(cwd, marker)).then(() => true, () => false))
790
+ found.add(client);
791
+ return found.size === 1 ? [...found][0] : undefined;
792
+ }
793
+ /** Manifests in the repository root that name its language; the CLI stays outside `hue setup`. */
794
+ const LANGUAGE_MARKERS = [
795
+ ["./hue-agent.ts", "tsconfig.json"],
796
+ ['--command "<agent start command>"', "pyproject.toml"],
797
+ ['--command "<agent start command>"', "requirements.txt"],
798
+ ['--command "<agent start command>"', "uv.lock"],
799
+ ['--command "<agent start command>"', "poetry.lock"],
800
+ // Last: a Python repository often carries a package.json for its tooling.
801
+ ["./hue-agent.ts", "package.json"],
802
+ ];
803
+ /** The `hue eval` agent argument for the repository's language; undefined when it is unknown. */
804
+ async function detectEvalAgent(cwd) {
805
+ for (const [agent, marker] of LANGUAGE_MARKERS)
806
+ if (await access(join(cwd, marker)).then(() => true, () => false))
807
+ return agent;
808
+ return undefined;
809
+ }
@@ -12,11 +12,14 @@ export declare const traceImportOrigins: readonly ["production", "evaluation", "
12
12
  export type TraceImportOrigin = (typeof traceImportOrigins)[number];
13
13
  /** The bounds the import routes hold a manifest to. */
14
14
  export declare const traceImportLimits: {
15
- readonly traces: 10000;
16
- readonly batchesPerTrace: 64;
17
- readonly blobsPerTrace: 1000;
15
+ readonly traces: 25000;
16
+ readonly batchesPerTrace: 512;
17
+ readonly blobsPerTrace: 10000;
18
18
  readonly spansPerTrace: 100000;
19
+ /** The manifest decoded. */
19
20
  readonly manifestBytes: number;
21
+ /** The manifest as registration takes it, gzip. */
22
+ readonly manifestWireBytes: number;
20
23
  readonly batchBytes: number;
21
24
  readonly blobBytes: 1000000000;
22
25
  };
@@ -17,11 +17,14 @@ export const traceImportFormats = ["otlp_json", "langfuse_observations", "atif"]
17
17
  export const traceImportOrigins = ["production", "evaluation", "simulation", "unknown"];
18
18
  /** The bounds the import routes hold a manifest to. */
19
19
  export const traceImportLimits = {
20
- traces: 10_000,
21
- batchesPerTrace: 64,
22
- blobsPerTrace: 1_000,
20
+ traces: 25_000,
21
+ batchesPerTrace: 512,
22
+ blobsPerTrace: 10_000,
23
23
  spansPerTrace: 100_000,
24
- manifestBytes: 8 * 1024 * 1024,
24
+ /** The manifest decoded. */
25
+ manifestBytes: 32 * 1024 * 1024,
26
+ /** The manifest as registration takes it, gzip. */
27
+ manifestWireBytes: 4 * 1024 * 1024,
25
28
  batchBytes: 4 * 1024 * 1024,
26
29
  blobBytes: 1_000_000_000,
27
30
  };
@@ -57,6 +57,9 @@ export declare class PrepareError extends Error {
57
57
  }
58
58
  /** The default output directory: `<input>.hue-import` beside the input. */
59
59
  export declare function defaultOutputDirectory(input: string): string;
60
+ /** Why a manifest cannot be registered, or undefined: Hue reads at most 32 MiB decoded and takes
61
+ * at most 4 MiB on the wire, gzip as the upload sends it. Checked here, before any upload. */
62
+ export declare function manifestTooLarge(bytes: Uint8Array): string | undefined;
60
63
  export interface PrepareOptions {
61
64
  out?: string;
62
65
  }
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { mkdir, open, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
3
3
  import { basename, join, relative, resolve, sep } from "node:path";
4
+ import { gzipSync } from "node:zlib";
4
5
  import { sdkVersion } from "../version.js";
5
6
  import { batchEntries, ConversionError, convertAtifExport, detectFormat, LangfuseTraceCollector, manifestTotals, manifestTraceHasIdentity, OtlpTraceCollector, sha256Hex, traceImportFormats, traceImportLimits, validateManifest, } from "./traces-import-convert.js";
6
7
  import { isZipSignature, ZipArchive, ZipError } from "./traces-import-zip.js";
@@ -193,6 +194,18 @@ async function prepareOutputDirectory(directory) {
193
194
  await rm(path, { force: true });
194
195
  }
195
196
  }
197
+ /** Why a manifest cannot be registered, or undefined: Hue reads at most 32 MiB decoded and takes
198
+ * at most 4 MiB on the wire, gzip as the upload sends it. Checked here, before any upload. */
199
+ export function manifestTooLarge(bytes) {
200
+ // No division by a name: the published package's import-boundary check reads one as a regex.
201
+ const mebibytes = (value) => `${Math.ceil(value * 2 ** -20)} MiB`;
202
+ if (bytes.byteLength > traceImportLimits.manifestBytes)
203
+ return `The manifest is ${mebibytes(bytes.byteLength)}, over the ${mebibytes(traceImportLimits.manifestBytes)} Hue reads; prepare the input in smaller parts`;
204
+ const wire = gzipSync(bytes, { level: 6 }).byteLength;
205
+ if (wire > traceImportLimits.manifestWireBytes)
206
+ return `The manifest is ${mebibytes(wire)} compressed, over the ${mebibytes(traceImportLimits.manifestWireBytes)} a registration takes; prepare the input in smaller parts`;
207
+ return undefined;
208
+ }
196
209
  async function writePrivate(path, bytes) {
197
210
  const temporary = `${path}.tmp`;
198
211
  await writeFile(temporary, bytes, { mode: 0o600, flag: "w" });
@@ -422,8 +435,9 @@ export async function prepareTraceBundle(input, options = {}) {
422
435
  if (invalid)
423
436
  throw new PrepareError(`The prepared manifest is not valid: ${invalid}`);
424
437
  const bytes = Buffer.from(JSON.stringify(manifest), "utf8");
425
- if (bytes.byteLength > traceImportLimits.manifestBytes)
426
- throw new PrepareError("The manifest exceeds 8 MiB; prepare the input in smaller parts");
438
+ const tooLarge = manifestTooLarge(bytes);
439
+ if (tooLarge)
440
+ throw new PrepareError(tooLarge);
427
441
  await writePrivate(join(outputDirectory, MANIFEST_FILE), bytes);
428
442
  return {
429
443
  manifest_digest: createHash("sha256").update(bytes).digest("hex"),
@@ -3,7 +3,7 @@ import { join } from "node:path";
3
3
  import { gunzipSync, gzipSync } from "node:zlib";
4
4
  import { sdkVersion } from "../version.js";
5
5
  import { manifestTotals, sha256Hex, validateManifest, } from "./traces-import-convert.js";
6
- import { MANIFEST_FILE, PROGRESS_FILE } from "./traces-import-prepare.js";
6
+ import { MANIFEST_FILE, manifestTooLarge, PROGRESS_FILE } from "./traces-import-prepare.js";
7
7
  /**
8
8
  * `hue traces import upload`: delivers a prepared bundle to Hue's import routes with the
9
9
  * short-lived import authorization `prepare_trace_import` minted. The manifest's exact bytes
@@ -229,6 +229,10 @@ export async function uploadTraceBundle(options, io = {}) {
229
229
  const invalid = validateManifest(manifest);
230
230
  if (invalid)
231
231
  throw new UploadStop(`${MANIFEST_FILE} is not valid: ${invalid}`, "bundle_invalid");
232
+ // A manifest registration would refuse (413) is refused here, before anything is sent.
233
+ const tooLarge = manifestTooLarge(manifestBytes);
234
+ if (tooLarge)
235
+ throw new UploadStop(tooLarge, "bundle_invalid");
232
236
  const manifestDigest = sha256Hex(manifestBytes);
233
237
  const totals = manifestTotals(manifest);
234
238
  // Every declared file must be present before the first request: a bundle missing a batch
package/dist/client.js CHANGED
@@ -269,10 +269,12 @@ export class HueClient {
269
269
  * @throws HueTraceVerificationError for authentication, unsupported endpoint, transport or invalid response failures.
270
270
  * @throws TypeError for an invalid trace ID or options.
271
271
  */
272
- verifyTrace(traceId, options = {}) {
272
+ async verifyTrace(traceId, options = {}) {
273
273
  if (!this.enabled)
274
- return Promise.reject(new HueConnectionError("Hue telemetry is disabled"));
275
- return verifyTrace(this.transport.options, traceId, options);
274
+ throw new HueConnectionError("Hue telemetry is disabled");
275
+ // Once Hue reports the trace missing, name the project this key searched so a wrong key reads
276
+ // differently from a trace that is not stored yet.
277
+ return verifyTrace(this.transport.options, traceId, options, () => this.checkConnection().catch(() => undefined));
276
278
  }
277
279
  /** The helper's current context: the innermost active Hue span, else the OpenTelemetry active context. */
278
280
  getContext() {
package/dist/receipt.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { HueOptions, TraceVerification, VerifyTraceOptions } from "./types.js";
1
+ import type { ProjectConnection, HueOptions, TraceVerification, VerifyTraceOptions } from "./types.js";
2
2
  /**
3
3
  * Thrown by {@link HueClient.verifyTrace} when verification cannot proceed: denied key, unsupported
4
4
  * or refusing server, unreachable network or an invalid receipt. A timeout is not an error; it
@@ -18,4 +18,4 @@ export declare class HueTraceVerificationError extends Error {
18
18
  /** Observe persisted evidence after the application and its exporter have finished. */
19
19
  export declare function verifyTrace(connection: Pick<HueOptions, "apiKey"> & {
20
20
  baseUrl: string;
21
- }, traceId: string, options?: VerifyTraceOptions): Promise<TraceVerification>;
21
+ }, traceId: string, options?: VerifyTraceOptions, onMissing?: () => Promise<ProjectConnection | undefined>): Promise<TraceVerification>;
package/dist/receipt.js CHANGED
@@ -145,14 +145,16 @@ async function pause(milliseconds, signal) {
145
145
  });
146
146
  }
147
147
  /** Observe persisted evidence after the application and its exporter have finished. */
148
- export async function verifyTrace(connection, traceId, options = {}) {
149
- return verifyTraceAtPath(connection, traceId, options, false, fetch);
148
+ export async function verifyTrace(connection, traceId, options = {}, onMissing) {
149
+ return verifyTraceAtPath(connection, traceId, options, false, fetch, undefined, onMissing);
150
150
  }
151
151
  /** @internal Dedicated receipt path for setup credentials; ordinary clients stay unchanged. */
152
152
  export async function verifySetupTrace(connection, traceId, options, fetcher, signal) {
153
153
  return verifyTraceAtPath(connection, traceId, options, true, fetcher, signal);
154
154
  }
155
- async function verifyTraceAtPath(connection, traceId, options, setup, fetcher, signal) {
155
+ async function verifyTraceAtPath(connection, traceId, options, setup, fetcher, signal,
156
+ /** Looks up the project the key searched once Hue has reported the trace missing. */
157
+ onMissing) {
156
158
  if (!validId(traceId, 32))
157
159
  throw new TypeError("traceId must be a nonzero lowercase 32-character OpenTelemetry trace ID");
158
160
  if (options === null || typeof options !== "object" || Array.isArray(options))
@@ -181,6 +183,8 @@ async function verifyTraceAtPath(connection, traceId, options, setup, fetcher, s
181
183
  const timer = setTimeout(() => controller.abort(), timeout);
182
184
  const requestSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
183
185
  let receipt = null;
186
+ let missing = false;
187
+ let lookup;
184
188
  let delay = 250;
185
189
  try {
186
190
  while (!requestSignal.aborted && performance.now() < deadline) {
@@ -204,6 +208,7 @@ async function verifyTraceAtPath(connection, traceId, options, setup, fetcher, s
204
208
  const body = await readJson(response);
205
209
  if (!record(body) || body.code !== "TRACE_NOT_FOUND")
206
210
  throw new HueTraceVerificationError("http", "This Hue server does not support trace receipts. Check the server version and baseUrl.", 404);
211
+ missing = true;
207
212
  }
208
213
  else {
209
214
  await response.body?.cancel();
@@ -221,6 +226,12 @@ async function verifyTraceAtPath(connection, traceId, options, setup, fetcher, s
221
226
  if (remaining <= 0)
222
227
  break;
223
228
  const wait = Math.max(delay, retryAfter);
229
+ // Once the backoff would outlast the budget, the rest of it names the project the key
230
+ // searched instead of sleeping.
231
+ if (wait >= remaining && missing && onMissing) {
232
+ lookup = onMissing();
233
+ break;
234
+ }
224
235
  await pause(Math.min(wait, remaining), requestSignal);
225
236
  // A truncated backoff exhausts this call even if a timer wakes just early.
226
237
  if (wait >= remaining)
@@ -240,5 +251,13 @@ async function verifyTraceAtPath(connection, traceId, options, setup, fetcher, s
240
251
  finally {
241
252
  clearTimeout(timer);
242
253
  }
254
+ if (lookup && !receipt) {
255
+ const project = await Promise.race([
256
+ lookup,
257
+ pause(Math.max(0, deadline - performance.now()), controller.signal),
258
+ ]);
259
+ if (project)
260
+ return { verified: false, receipt, project };
261
+ }
243
262
  return { verified: false, receipt };
244
263
  }
package/dist/transport.js CHANGED
@@ -2141,12 +2141,13 @@ class OtlpHttpTransport {
2141
2141
  if (this.aborted)
2142
2142
  throw new Error("Hue export deadline exceeded");
2143
2143
  const url = new URL(this.url);
2144
- const protocol = url.protocol;
2145
2144
  // Loaded on first use, as OpenTelemetry's exporter does, so importing Hue never loads http
2146
- // before the application's http instrumentation can patch it. A request measured before it
2147
- // was sent is not compressed again.
2145
+ // before the application's http instrumentation can patch it. Each specifier is a literal:
2146
+ // bundlers (Next.js Turbopack, webpack) cannot resolve a computed one and replace the import
2147
+ // with a throw, so every export would fail. A request measured before it was sent is not
2148
+ // compressed again.
2148
2149
  const [{ Agent: ConnectionAgent, request }, body] = await Promise.all([
2149
- import(protocol === "https:" ? "node:https" : "node:http"),
2150
+ url.protocol === "https:" ? import("node:https") : import("node:http"),
2150
2151
  this.body && data === this.body.data ? this.body.compressed : compress(data),
2151
2152
  ]);
2152
2153
  if (this.aborted)
package/dist/types.d.ts CHANGED
@@ -390,4 +390,9 @@ export interface TraceVerification {
390
390
  verified: boolean;
391
391
  /** Latest receipt observed, or `null` when the trace was never found. */
392
392
  receipt: TraceReceipt | null;
393
+ /**
394
+ * The project the key searched, looked up when no receipt arrived: a trace missing there was
395
+ * sent with a different key or is not stored yet. Absent when that lookup failed.
396
+ */
397
+ project?: ProjectConnection;
393
398
  }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Package version shared by the instrumentation scope and the export User-Agent. */
2
- export declare const sdkVersion = "0.20.6";
2
+ export declare const sdkVersion = "0.20.8";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/write-version.mjs from package.json; do not edit by hand.
2
2
  /** Package version shared by the instrumentation scope and the export User-Agent. */
3
- export const sdkVersion = "0.20.6";
3
+ export const sdkVersion = "0.20.8";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hue-run/sdk",
3
- "version": "0.20.6",
3
+ "version": "0.20.8",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -112,9 +112,9 @@
112
112
  "typescript": "7.0.2"
113
113
  },
114
114
  "peerDependencies": {
115
- "@ai-sdk/otel": "^1.0.99",
115
+ "@ai-sdk/otel": "^1.0.0",
116
116
  "@opentelemetry/api": "^1.9.1",
117
- "ai": "^6.0.0 || ^7.0.99",
117
+ "ai": "^6.0.0 || ^7.0.0",
118
118
  "ajv": "^8.17.0"
119
119
  },
120
120
  "peerDependenciesMeta": {