@hue-run/sdk 0.21.1 → 0.23.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 (74) hide show
  1. package/CLI.md +246 -52
  2. package/README.md +69 -14
  3. package/WORLDS.md +6 -3
  4. package/dist/ai-sdk.d.ts +23 -1
  5. package/dist/ai-sdk.js +60 -2
  6. package/dist/cli/import-formats/adapter.d.ts +80 -0
  7. package/dist/cli/import-formats/adapter.js +1 -0
  8. package/dist/cli/import-formats/agent-events.d.ts +5 -0
  9. package/dist/cli/import-formats/agent-events.js +16 -0
  10. package/dist/cli/import-formats/atif.d.ts +3 -0
  11. package/dist/cli/import-formats/atif.js +15 -0
  12. package/dist/cli/import-formats/braintrust.d.ts +2 -0
  13. package/dist/cli/import-formats/braintrust.js +222 -0
  14. package/dist/cli/import-formats/containers.d.ts +32 -0
  15. package/dist/cli/import-formats/containers.js +399 -0
  16. package/dist/cli/import-formats/datadog.d.ts +2 -0
  17. package/dist/cli/import-formats/datadog.js +264 -0
  18. package/dist/cli/import-formats/helicone.d.ts +2 -0
  19. package/dist/cli/import-formats/helicone.js +161 -0
  20. package/dist/cli/import-formats/identity.d.ts +112 -0
  21. package/dist/cli/import-formats/identity.js +594 -0
  22. package/dist/cli/import-formats/jaeger.d.ts +3 -0
  23. package/dist/cli/import-formats/jaeger.js +266 -0
  24. package/dist/cli/import-formats/langfuse.d.ts +7 -0
  25. package/dist/cli/import-formats/langfuse.js +510 -0
  26. package/dist/cli/import-formats/langsmith.d.ts +2 -0
  27. package/dist/cli/import-formats/langsmith.js +244 -0
  28. package/dist/cli/import-formats/messages.d.ts +28 -0
  29. package/dist/cli/import-formats/messages.js +301 -0
  30. package/dist/cli/import-formats/mlflow.d.ts +2 -0
  31. package/dist/cli/import-formats/mlflow.js +260 -0
  32. package/dist/cli/import-formats/openai-agents.d.ts +2 -0
  33. package/dist/cli/import-formats/openai-agents.js +221 -0
  34. package/dist/cli/import-formats/opik.d.ts +2 -0
  35. package/dist/cli/import-formats/opik.js +193 -0
  36. package/dist/cli/import-formats/otel-family.d.ts +5 -0
  37. package/dist/cli/import-formats/otel-family.js +17 -0
  38. package/dist/cli/import-formats/otel-spans.d.ts +94 -0
  39. package/dist/cli/import-formats/otel-spans.js +262 -0
  40. package/dist/cli/import-formats/otlp-protobuf.d.ts +35 -0
  41. package/dist/cli/import-formats/otlp-protobuf.js +486 -0
  42. package/dist/cli/import-formats/otlp.d.ts +4 -0
  43. package/dist/cli/import-formats/otlp.js +15 -0
  44. package/dist/cli/import-formats/phoenix.d.ts +3 -0
  45. package/dist/cli/import-formats/phoenix.js +256 -0
  46. package/dist/cli/import-formats/raindrop.d.ts +21 -0
  47. package/dist/cli/import-formats/raindrop.js +255 -0
  48. package/dist/cli/import-formats/reader.d.ts +56 -0
  49. package/dist/cli/import-formats/reader.js +364 -0
  50. package/dist/cli/import-formats/registry.d.ts +36 -0
  51. package/dist/cli/import-formats/registry.js +71 -0
  52. package/dist/cli/import-formats/row-platform-fields.d.ts +32 -0
  53. package/dist/cli/import-formats/row-platform-fields.js +76 -0
  54. package/dist/cli/import-formats/rows.d.ts +165 -0
  55. package/dist/cli/import-formats/rows.js +638 -0
  56. package/dist/cli/import-formats/transcripts.d.ts +25 -0
  57. package/dist/cli/import-formats/transcripts.js +498 -0
  58. package/dist/cli/import-formats/weave.d.ts +2 -0
  59. package/dist/cli/import-formats/weave.js +159 -0
  60. package/dist/cli/import-formats/zipkin.d.ts +2 -0
  61. package/dist/cli/import-formats/zipkin.js +219 -0
  62. package/dist/cli/import.d.ts +1 -1
  63. package/dist/cli/import.js +4 -2
  64. package/dist/cli/traces-import-convert.d.ts +65 -33
  65. package/dist/cli/traces-import-convert.js +211 -214
  66. package/dist/cli/traces-import-prepare.d.ts +26 -22
  67. package/dist/cli/traces-import-prepare.js +51 -122
  68. package/dist/cli/traces-import.d.ts +4 -4
  69. package/dist/cli/traces-import.js +63 -11
  70. package/dist/transport.d.ts +12 -0
  71. package/dist/transport.js +139 -24
  72. package/dist/version.d.ts +1 -1
  73. package/dist/version.js +1 -1
  74. package/package.json +2 -1
package/dist/transport.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { gzip } from "node:zlib";
3
+ import { waitUntil } from "@vercel/functions";
3
4
  import { ExportResultCode } from "@opentelemetry/core";
4
5
  import { createOtlpNetworkExportDelegate, OTLPExporterBase, OTLPExporterError, } from "@opentelemetry/otlp-exporter-base";
5
6
  import { createOtlpHttpExporterMetrics } from "@opentelemetry/otlp-exporter-base/node-http";
@@ -182,6 +183,10 @@ export class HueTransport {
182
183
  failed = { traces: 0, logs: 0 };
183
184
  spans = new Map();
184
185
  logs = new Map();
186
+ // A cohort closes when export starts, so later requests cannot extend its lifetime.
187
+ deliveryCohort;
188
+ deliveries = new WeakMap();
189
+ deliveryUploads = new WeakMap();
185
190
  pendingBytes = 0;
186
191
  dropped = { traces: 0, logs: 0 };
187
192
  /** Records of each trace the SDK never sent (dropped from the queue, refused as invalid or
@@ -214,6 +219,8 @@ export class HueTransport {
214
219
  placeholders = new WeakMap();
215
220
  // The running span each admitted placeholder announces, so one it has outlived is not sent.
216
221
  placeholderSources = new WeakMap();
222
+ forcedSpanDrains = 0;
223
+ completedSpanDrains = 0;
217
224
  batchSpans;
218
225
  // What the receiver accepts, as it last advertised; the limits of this release until it does.
219
226
  limits = DEFAULT_RECEIVER_LIMITS;
@@ -340,6 +347,7 @@ export class HueTransport {
340
347
  this.protectMedia(admitted);
341
348
  this.countEnded(admitted);
342
349
  spans.onEnd(admitted);
350
+ this.protectDelivery(admitted);
343
351
  }
344
352
  catch {
345
353
  if (admitted)
@@ -352,12 +360,18 @@ export class HueTransport {
352
360
  // The spans are sent first, then the uploads queued so far are waited for, unless a
353
361
  // flush sends them: that flush waits for the uploads after its send.
354
362
  forceFlush: async () => {
363
+ const finish = this.beginSpanDrain();
355
364
  try {
356
365
  await spans.forceFlush();
357
366
  }
358
367
  finally {
359
- if (!this.sendingRecords.getStore())
360
- await this.drainUploads();
368
+ try {
369
+ if (!this.sendingRecords.getStore())
370
+ await this.drainUploads();
371
+ }
372
+ finally {
373
+ finish();
374
+ }
361
375
  }
362
376
  },
363
377
  shutdown: async () => {
@@ -391,6 +405,7 @@ export class HueTransport {
391
405
  admitted = queued;
392
406
  this.admitLogParts(log, queued);
393
407
  logs.onEmit(queued);
408
+ this.protectDelivery(admitted);
394
409
  }
395
410
  catch {
396
411
  if (admitted)
@@ -399,12 +414,18 @@ export class HueTransport {
399
414
  }
400
415
  },
401
416
  forceFlush: async () => {
417
+ const finish = this.beginSpanDrain();
402
418
  try {
403
419
  await logs.forceFlush();
404
420
  }
405
421
  finally {
406
- if (!this.sendingRecords.getStore())
407
- await this.drainUploads();
422
+ try {
423
+ if (!this.sendingRecords.getStore())
424
+ await this.drainUploads();
425
+ }
426
+ finally {
427
+ finish();
428
+ }
408
429
  }
409
430
  },
410
431
  shutdown: async () => {
@@ -472,6 +493,14 @@ export class HueTransport {
472
493
  this.logs.set(snapshot.record, snapshot.bytes);
473
494
  if (snapshot.unresolvedResource && !advisory)
474
495
  this.issue(signal, "warning", 0, "Unresolved resource attributes omitted from the telemetry snapshot");
496
+ if (!advisory) {
497
+ const cohort = (this.deliveryCohort ??= {
498
+ pending: 0,
499
+ ...Promise.withResolvers(),
500
+ });
501
+ cohort.pending++;
502
+ this.deliveries.set(snapshot.record, cohort);
503
+ }
475
504
  return snapshot.record;
476
505
  }
477
506
  catch {
@@ -481,11 +510,42 @@ export class HueTransport {
481
510
  return undefined;
482
511
  }
483
512
  }
513
+ /** Register in the caller's request before it can return; outside Vercel this is a no-op. */
514
+ protectDelivery(record) {
515
+ const cohort = this.deliveries.get(record);
516
+ if (!cohort)
517
+ return;
518
+ try {
519
+ waitUntil(cohort.promise);
520
+ }
521
+ catch {
522
+ // A host hook must never undo admission or change the application's result.
523
+ }
524
+ }
525
+ /** @internal Seal the batch's cohort before export can yield to another request. */
526
+ exporting(records) {
527
+ if (records.some((record) => this.deliveries.get(record) === this.deliveryCohort))
528
+ this.deliveryCohort = undefined;
529
+ }
530
+ holdDelivery(cohort, work) {
531
+ if (!cohort)
532
+ return;
533
+ cohort.pending++;
534
+ const finish = () => this.finishDelivery(cohort);
535
+ void work.then(finish, finish);
536
+ }
537
+ finishDelivery(cohort) {
538
+ if (--cohort.pending !== 0)
539
+ return;
540
+ if (this.deliveryCohort === cohort)
541
+ this.deliveryCohort = undefined;
542
+ cohort.resolve();
543
+ }
484
544
  track(span) {
485
545
  try {
486
546
  if (!this.liveSpans || this.live.size >= MAX_LIVE_SPANS)
487
547
  return;
488
- if (!(span.spanContext().traceFlags & 1) || !span.isRecording() || !announcesLiveSpan(span))
548
+ if (!(span.spanContext().traceFlags & 1) || span.ended || !announcesLiveSpan(span))
489
549
  return;
490
550
  this.live.add(span);
491
551
  if (!this.liveTimer) {
@@ -500,6 +560,8 @@ export class HueTransport {
500
560
  }
501
561
  /** Placeholders are built lazily, so input set right after a span starts is included. */
502
562
  announceLiveSpans() {
563
+ if (this.forcedSpanDrains)
564
+ return;
503
565
  for (const span of this.live) {
504
566
  this.live.delete(span);
505
567
  try {
@@ -547,6 +609,28 @@ export class HueTransport {
547
609
  placeholderSettled(record) {
548
610
  return this.placeholderSources.get(record)?.ended === true;
549
611
  }
612
+ beginSpanDrain() {
613
+ const completed = [...this.spans.keys()].some((span) => !this.placeholders.has(span));
614
+ this.forcedSpanDrains++;
615
+ if (completed)
616
+ this.completedSpanDrains++;
617
+ return () => {
618
+ this.forcedSpanDrains--;
619
+ if (completed)
620
+ this.completedSpanDrains--;
621
+ };
622
+ }
623
+ /** @internal A forced drain sends completed spans without another advisory round trip. */
624
+ deferPlaceholders(records) {
625
+ if (!this.completedSpanDrains)
626
+ return false;
627
+ for (const record of records) {
628
+ const source = this.placeholderSources.get(record);
629
+ if (!this.closed && source)
630
+ this.track(source);
631
+ }
632
+ return true;
633
+ }
550
634
  /** @internal Exporter callback: false once the receiver refused placeholders; queued ones are then dropped. */
551
635
  sendsPlaceholders() {
552
636
  return !this.placeholdersRejected;
@@ -570,6 +654,11 @@ export class HueTransport {
570
654
  /** @internal Exporter callback: releases queued records after an export attempt settles. */
571
655
  finish(signal, records) {
572
656
  for (const record of records) {
657
+ const cohort = this.deliveries.get(record);
658
+ if (cohort) {
659
+ this.deliveries.delete(record);
660
+ this.finishDelivery(cohort);
661
+ }
573
662
  this.releaseMedia(record);
574
663
  this.logMedia.delete(record);
575
664
  const pending = signal === "traces" ? this.spans : this.logs;
@@ -737,6 +826,7 @@ export class HueTransport {
737
826
  * them. */
738
827
  uploadQueued(record, settled, shared, released) {
739
828
  this.awaitUploads(settled);
829
+ this.holdDelivery(this.deliveries.get(record), settled);
740
830
  const identity = this.mediaIdentity(record);
741
831
  const staged = identity
742
832
  ? (this.media.get(identity) ?? []).reduce((total, file) => total + heldBytesOf(file.bytes), 0)
@@ -853,8 +943,8 @@ export class HueTransport {
853
943
  return this.offloadsValues() && this.uploader?.available() === true;
854
944
  }
855
945
  /** @internal Exporter callback: a redacted span with the values its redaction left to the
856
- * upload step placed: listed under `hue.blobs` with their uploads queued (each added to `late`
857
- * once it settles, and charged in place of the `queued` record it came from), or cut as before.
946
+ * upload step placed: listed under `hue.blobs` with their uploads queued (reported once settled,
947
+ * and charged in place of the `queued` record they came from), or cut as before.
858
948
  * Never rejects. */
859
949
  async offloadSpan(span, collector, upload, tally, late, queued) {
860
950
  // A record without a readable context cannot name its trace: its values are cut.
@@ -878,8 +968,7 @@ export class HueTransport {
878
968
  memo: this.mediaMemo,
879
969
  background: own,
880
970
  });
881
- if (own.settled.length)
882
- late.settled.push(mergeWhenSettled(late.tally, own.tally, own.settled));
971
+ this.collectDeliveryUploads(queued, own, late);
883
972
  return { ...span, attributes: attributes };
884
973
  }
885
974
  catch {
@@ -919,14 +1008,37 @@ export class HueTransport {
919
1008
  memo: this.mediaMemo,
920
1009
  background: own,
921
1010
  });
922
- if (own.settled.length)
923
- late.settled.push(mergeWhenSettled(late.tally, own.tally, own.settled));
1011
+ this.collectDeliveryUploads(queued, own, late);
924
1012
  return placed(result);
925
1013
  }
926
1014
  catch {
927
1015
  return placed(fallbackRecord(log.attributes, collector, valueBytes, tally, traceId, this.mediaMemo));
928
1016
  }
929
1017
  }
1018
+ /** Keep an export's warning aggregation within each cohort's upload ownership. */
1019
+ collectDeliveryUploads(record, uploads, late) {
1020
+ const { settled, tally } = uploads;
1021
+ if (!settled.length)
1022
+ return;
1023
+ let groups = this.deliveryUploads.get(late);
1024
+ if (!groups)
1025
+ this.deliveryUploads.set(late, (groups = new Map()));
1026
+ const cohort = this.deliveries.get(record);
1027
+ let group = groups.get(cohort);
1028
+ if (!group)
1029
+ groups.set(cohort, (group = { tally: newTally(), settled: [] }));
1030
+ group.settled.push(mergeWhenSettled(group.tally, tally, settled));
1031
+ }
1032
+ /** @internal Report only the uploads each cohort owns, without retaining their records. */
1033
+ reportUploads(late, signal) {
1034
+ const groups = this.deliveryUploads.get(late);
1035
+ this.deliveryUploads.delete(late);
1036
+ for (const [cohort, { settled, tally }] of groups ?? []) {
1037
+ const report = Promise.all(settled).then(() => this.reportOffload(tally, signal, true));
1038
+ this.awaitUploads(report);
1039
+ this.holdDelivery(cohort, report);
1040
+ }
1041
+ }
930
1042
  /** @internal Exporter callback: counts an export's uploads and reports each kind of fallback
931
1043
  * once, as a warning naming its traces: the values were exported cut as before, or, `late`,
932
1044
  * their uploads failed after their records listed them. */
@@ -1147,13 +1259,19 @@ export class HueTransport {
1147
1259
  */
1148
1260
  flushRecords(providers) {
1149
1261
  const from = this.observedSequence;
1150
- const join = () => this.sendRecords();
1262
+ let finish;
1263
+ const join = () => {
1264
+ finish = this.beginSpanDrain();
1265
+ return this.sendRecords();
1266
+ };
1151
1267
  const sent = providers ? this.sendingRecords.run(true, providers).then(join, join) : join();
1152
- const report = sent.then(async (processors) => {
1268
+ const report = sent
1269
+ .then(async (processors) => {
1153
1270
  // Exports the processors did not start (a timer's, still in flight) queued uploads too.
1154
1271
  await this.drainUploads();
1155
1272
  return this.flushReport(from, processors);
1156
- });
1273
+ })
1274
+ .finally(() => finish?.());
1157
1275
  return { sent, report };
1158
1276
  }
1159
1277
  /** Sends what the processors and exporters hold, once the previous flush's send ended. */
@@ -1387,6 +1505,7 @@ class ReportingExporter {
1387
1505
  this.redact = redact;
1388
1506
  }
1389
1507
  export(records, callback) {
1508
+ this.transport.exporting(records);
1390
1509
  const work = this.exportRecords(records)
1391
1510
  .then(() => callback({ code: ExportResultCode.SUCCESS }), () => callback({
1392
1511
  code: ExportResultCode.FAILED,
@@ -1423,23 +1542,16 @@ class ReportingExporter {
1423
1542
  real.push(placeholder);
1424
1543
  return real;
1425
1544
  }
1426
- /** Places and sends the records; the uploads their placement queued are reported once they
1427
- * settle, after the records were sent, never awaited here. */
1545
+ /** Queue final upload reports before releasing the records that own their cohorts. */
1428
1546
  async exportRecords(records) {
1429
1547
  const late = { tally: newTally(), settled: [] };
1430
1548
  try {
1431
1549
  await this.placeAndSend(records, late);
1432
1550
  }
1433
1551
  finally {
1434
- if (late.settled.length)
1435
- this.reportWhenSettled(late);
1552
+ this.transport.reportUploads(late, this.signal);
1436
1553
  }
1437
1554
  }
1438
- /** Reports an export's uploads once they all settle. Built apart from the export, so a pending
1439
- * upload never keeps the export's records alive. */
1440
- reportWhenSettled(late) {
1441
- this.transport.awaitUploads(Promise.all(late.settled).then(() => this.transport.reportOffload(late.tally, this.signal, true)));
1442
- }
1443
1555
  async placeAndSend(records, late) {
1444
1556
  // Completed records and placeholders travel in separate requests, completed records first,
1445
1557
  // so a rejection count is always one kind of record's. Placeholders are advisory: losing one
@@ -1850,7 +1962,10 @@ class ReportingExporter {
1850
1962
  // records' acknowledgements may have just shown, nor after this export's completed records were
1851
1963
  // refused or unanswered: a receiver that just refused or timed out gets no second request with
1852
1964
  // its own retries. A record this side could not send keeps the placeholders going out.
1853
- if (pending.length && !refused && this.transport.sendsPlaceholders())
1965
+ if (pending.length &&
1966
+ !refused &&
1967
+ this.transport.sendsPlaceholders() &&
1968
+ !this.transport.deferPlaceholders(records))
1854
1969
  await pack(pending, true);
1855
1970
  if (failed)
1856
1971
  throw new Error("Hue telemetry export failed");
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /** Package version shared by the instrumentation scope and the export User-Agent. */
2
- export declare const sdkVersion = "0.21.1";
2
+ export declare const sdkVersion = "0.23.0";
3
3
  /** OTLP dependency version sent in the export User-Agent. */
4
4
  export declare const otlpExporterVersion = "0.223.0";
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
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.21.1";
3
+ export const sdkVersion = "0.23.0";
4
4
  /** OTLP dependency version sent in the export User-Agent. */
5
5
  export const otlpExporterVersion = "0.223.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hue-run/sdk",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -98,6 +98,7 @@
98
98
  "@opentelemetry/resources": "2.12.0",
99
99
  "@opentelemetry/sdk-logs": "0.223.0",
100
100
  "@opentelemetry/sdk-trace": "2.12.0",
101
+ "@vercel/functions": "3.9.7",
101
102
  "zod": "^3.25.76 || ^4.6.5"
102
103
  },
103
104
  "devDependencies": {