@bitfab/sdk 0.34.1 → 0.36.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.
@@ -207,8 +207,78 @@ var BitfabError = class extends Error {
207
207
  }
208
208
  };
209
209
 
210
+ // src/readEnv.ts
211
+ function readEnv(name) {
212
+ if (typeof process !== "undefined" && process.env) {
213
+ return process.env[name];
214
+ }
215
+ return void 0;
216
+ }
217
+
218
+ // src/compress.ts
219
+ var DISABLE_COMPRESSION_ENV = "BITFAB_DISABLE_COMPRESSION";
220
+ var MIN_COMPRESSED_BYTES = 8192;
221
+ var gzipNode;
222
+ var _nodeGzipReady = (typeof process !== "undefined" && process.versions?.node ? (
223
+ // The join trick hides "node:zlib" from static analysis so bundlers that
224
+ // ban Node.js built-ins don't fail at build time. webpackIgnore tells
225
+ // webpack/turbopack to emit a native import() so Node.js can resolve the
226
+ // module at runtime. Same pattern as `asyncStorage.ts`.
227
+ import(
228
+ /* webpackIgnore: true */
229
+ ["node", "zlib"].join(":")
230
+ ).then(({ gzip }) => {
231
+ gzipNode = (data) => new Promise((resolve, reject) => {
232
+ gzip(data, (error, result) => {
233
+ if (error) {
234
+ reject(error);
235
+ } else {
236
+ resolve(result);
237
+ }
238
+ });
239
+ });
240
+ }).catch(() => {
241
+ })
242
+ ) : Promise.resolve()).then(() => {
243
+ });
244
+ function toArrayBuffer(view) {
245
+ return view.buffer.slice(
246
+ view.byteOffset,
247
+ view.byteOffset + view.byteLength
248
+ );
249
+ }
250
+ async function gzipViaStream(bytes) {
251
+ const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
252
+ return await new Response(stream).arrayBuffer();
253
+ }
254
+ function encodeRequestBody(body) {
255
+ if (readEnv(DISABLE_COMPRESSION_ENV)) {
256
+ return { body };
257
+ }
258
+ const bytes = new TextEncoder().encode(body);
259
+ if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
260
+ return { body };
261
+ }
262
+ if (gzipNode) {
263
+ return gzipNode(bytes).then(
264
+ (compressed) => ({
265
+ body: toArrayBuffer(compressed),
266
+ contentEncoding: "gzip"
267
+ }),
268
+ () => ({ body })
269
+ );
270
+ }
271
+ if (typeof CompressionStream === "undefined") {
272
+ return { body };
273
+ }
274
+ return gzipViaStream(bytes).then(
275
+ (compressed) => ({ body: compressed, contentEncoding: "gzip" }),
276
+ () => ({ body })
277
+ );
278
+ }
279
+
210
280
  // src/version.generated.ts
211
- var __version__ = "0.34.1";
281
+ var __version__ = "0.36.0";
212
282
 
213
283
  // src/constants.ts
214
284
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -279,6 +349,125 @@ function runWithReplayContext(ctx, fn) {
279
349
  return fn();
280
350
  }
281
351
 
352
+ // src/payloadBudget.ts
353
+ var MAX_SPAN_CARRIER_BYTES = 28e5;
354
+ var textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
355
+ function byteLength(value) {
356
+ return textEncoder ? textEncoder.encode(value).length : value.length;
357
+ }
358
+ function carrierByteLength(body) {
359
+ return carrierBytesOf(textEncoder ? textEncoder.encode(body) : null, body);
360
+ }
361
+ function carrierBytesOf(encoded, body) {
362
+ if (!encoded) {
363
+ return body.length + 2;
364
+ }
365
+ let extra = 2;
366
+ for (let i = 0; i < encoded.length; i++) {
367
+ const byte = encoded[i];
368
+ if (byte === 34 || byte === 92) {
369
+ extra += 1;
370
+ } else if (byte < 32) {
371
+ extra += byte === 8 || byte === 9 || byte === 10 || byte === 12 || byte === 13 ? 1 : 5;
372
+ }
373
+ }
374
+ return encoded.length + extra;
375
+ }
376
+ var MAX_BYTES_PER_UNIT = 3;
377
+ function fitsCarrierBudget(body) {
378
+ const units = body.length;
379
+ if (units * MAX_BYTES_PER_UNIT + 2 <= MAX_SPAN_CARRIER_BYTES) {
380
+ return true;
381
+ }
382
+ if (units + 2 > MAX_SPAN_CARRIER_BYTES) {
383
+ return false;
384
+ }
385
+ return carrierByteLength(body) <= MAX_SPAN_CARRIER_BYTES;
386
+ }
387
+ var STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
388
+ "name",
389
+ "type",
390
+ "function_name",
391
+ "error_source"
392
+ ]);
393
+ function asRecord(value) {
394
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
395
+ }
396
+ function cloneTrimmable(payload) {
397
+ const copy = { ...payload };
398
+ const containers = [];
399
+ const spanData = asRecord(copy.span_data);
400
+ if (spanData) {
401
+ const clone = { ...spanData };
402
+ copy.span_data = clone;
403
+ containers.push(clone);
404
+ }
405
+ const rawSpan = asRecord(copy.rawSpan);
406
+ const rawSpanData = rawSpan && asRecord(rawSpan.span_data);
407
+ if (rawSpan && rawSpanData) {
408
+ const clone = { ...rawSpanData };
409
+ copy.rawSpan = { ...rawSpan, span_data: clone };
410
+ containers.push(clone);
411
+ }
412
+ if (containers.length === 0) {
413
+ containers.push(copy);
414
+ }
415
+ return { copy, containers };
416
+ }
417
+ function collectCandidates(containers) {
418
+ const candidates = [];
419
+ for (const container of containers) {
420
+ for (const [key, value] of Object.entries(container)) {
421
+ if (STRUCTURAL_SPAN_KEYS.has(key) || value == null) {
422
+ continue;
423
+ }
424
+ let size;
425
+ try {
426
+ size = byteLength(JSON.stringify(value) ?? "");
427
+ } catch {
428
+ continue;
429
+ }
430
+ candidates.push({ container, key, size });
431
+ }
432
+ }
433
+ return candidates.sort((a, b) => b.size - a.size);
434
+ }
435
+ function trimPayloadToBudget(payload, encode) {
436
+ const { copy, containers } = cloneTrimmable(payload);
437
+ const candidates = collectCandidates(containers);
438
+ if (candidates.length === 0) {
439
+ return void 0;
440
+ }
441
+ const trimmed = [];
442
+ for (const candidate of candidates) {
443
+ candidate.container[candidate.key] = `<unserializable: too_large_${candidate.size}_bytes>`;
444
+ trimmed.push(candidate.key);
445
+ let body;
446
+ try {
447
+ body = encode(copy);
448
+ } catch {
449
+ return void 0;
450
+ }
451
+ if (fitsCarrierBudget(body)) {
452
+ return { value: copy, trimmed };
453
+ }
454
+ }
455
+ return void 0;
456
+ }
457
+ function markPayloadTrimmed(value, trimmed) {
458
+ const existing = Array.isArray(value.errors) ? value.errors : [];
459
+ value.errors = [
460
+ ...existing,
461
+ {
462
+ source: "sdk",
463
+ step: "payload_budget",
464
+ error: `trimmed oversized field(s) to fit the ${MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: ${[
465
+ ...new Set(trimmed)
466
+ ].join(", ")}`
467
+ }
468
+ ];
469
+ }
470
+
282
471
  // src/warnOnce.ts
283
472
  var warned = /* @__PURE__ */ new Set();
284
473
  function warnOnce(key, message) {
@@ -294,8 +483,37 @@ function warnOnce(key, message) {
294
483
 
295
484
  // src/serializePayload.ts
296
485
  function serializePayloadBody(payload) {
486
+ const encoded = encodePayloadBody(payload);
487
+ if (fitsCarrierBudget(encoded.body)) {
488
+ return { body: encoded.body, dropped: encoded.dropped };
489
+ }
490
+ return applyPayloadBudget(encoded);
491
+ }
492
+ function applyPayloadBudget(encoded) {
493
+ const result = encoded.value ? trimPayloadToBudget(
494
+ encoded.value,
495
+ (value) => encodePayloadBody(value).body
496
+ ) : void 0;
497
+ if (!result) {
498
+ return { body: encoded.body, dropped: encoded.dropped };
499
+ }
500
+ warnOnce(
501
+ "payload:over-budget",
502
+ `a span payload exceeded the ${MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its largest field(s) (${[
503
+ ...new Set(result.trimmed)
504
+ ].join(
505
+ ", "
506
+ )}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
507
+ );
508
+ markPayloadTrimmed(result.value, result.trimmed);
509
+ return {
510
+ body: encodePayloadBody(result.value).body,
511
+ dropped: encoded.dropped
512
+ };
513
+ }
514
+ function encodePayloadBody(payload) {
297
515
  try {
298
- return { body: JSON.stringify(payload), dropped: [] };
516
+ return { body: JSON.stringify(payload), dropped: [], value: payload };
299
517
  } catch {
300
518
  const dropped = [];
301
519
  const sanitize = (value, seen) => {
@@ -360,12 +578,11 @@ function serializePayloadBody(payload) {
360
578
  sanitized = sanitize(payload, /* @__PURE__ */ new WeakSet());
361
579
  } catch (error) {
362
580
  const message = error instanceof Error ? error.message : String(error);
363
- return {
364
- body: JSON.stringify({ error: `payload_serialize_failed: ${message}` }),
365
- dropped
366
- };
581
+ const marker = { error: `payload_serialize_failed: ${message}` };
582
+ return { body: JSON.stringify(marker), dropped, value: marker };
367
583
  }
368
- if (dropped.length > 0 && typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
584
+ const isRecord = typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized);
585
+ if (dropped.length > 0 && isRecord) {
369
586
  const obj = sanitized;
370
587
  const existing = Array.isArray(obj.errors) ? obj.errors : [];
371
588
  obj.errors = [
@@ -379,7 +596,11 @@ function serializePayloadBody(payload) {
379
596
  }
380
597
  ];
381
598
  }
382
- return { body: JSON.stringify(sanitized), dropped };
599
+ return {
600
+ body: JSON.stringify(sanitized),
601
+ dropped,
602
+ value: isRecord ? sanitized : void 0
603
+ };
383
604
  }
384
605
  }
385
606
 
@@ -395,14 +616,6 @@ import {
395
616
  BatchSpanProcessor
396
617
  } from "@opentelemetry/sdk-trace-base";
397
618
 
398
- // src/readEnv.ts
399
- function readEnv(name) {
400
- if (typeof process !== "undefined" && process.env) {
401
- return process.env[name];
402
- }
403
- return void 0;
404
- }
405
-
406
619
  // src/unrefTimer.ts
407
620
  function unrefTimer(timer) {
408
621
  const handle = timer;
@@ -418,10 +631,8 @@ var OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
418
631
  var MAX_EXPORT_REQUEST_BYTES = 3e6;
419
632
  var MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
420
633
  var EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
421
- var COLLECTOR_ENDPOINT_ENV = "BITFAB_OTEL_EXPORTER_ENDPOINT";
422
634
  var MAX_QUEUE_SIZE = 8192;
423
635
  var DIRECT_MAX_EXPORT_BATCH_SIZE = 512;
424
- var COLLECTOR_MAX_EXPORT_BATCH_SIZE = 32;
425
636
  var DIRECT_MAX_REQUEST_BATCH_SIZE = 8;
426
637
  var DEFAULT_EXPORT_CONCURRENCY = 32;
427
638
  var MAX_EXPORT_CONCURRENCY = 64;
@@ -466,7 +677,7 @@ function recordTraceSubmission(operation, payload) {
466
677
  return;
467
678
  }
468
679
  if (operation === "external_span") {
469
- const rawSpan = asRecord(payload.rawSpan);
680
+ const rawSpan = asRecord2(payload.rawSpan);
470
681
  if (typeof rawSpan?.id !== "string") {
471
682
  submissionCounter += 1;
472
683
  }
@@ -503,14 +714,14 @@ function takeReplaySpanCounts(traceIds) {
503
714
  }
504
715
  return counts;
505
716
  }
506
- function asRecord(value) {
717
+ function asRecord2(value) {
507
718
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
508
719
  }
509
720
  function resolveSourceTraceId(payload) {
510
721
  if (typeof payload.sourceTraceId === "string") {
511
722
  return payload.sourceTraceId;
512
723
  }
513
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
724
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
514
725
  return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
515
726
  }
516
727
  function otlpValue(value) {
@@ -568,32 +779,28 @@ function spanToOtlp(span) {
568
779
  }
569
780
  return result;
570
781
  }
571
- function buildOtlpRequest(first, spans) {
782
+ var SPAN_SEPARATOR_BYTES = 1;
783
+ function encodeSpan(span) {
784
+ const json = JSON.stringify(spanToOtlp(span));
785
+ return { json, size: byteLength(json) };
786
+ }
787
+ function requestEnvelope(first) {
572
788
  const scope = first.instrumentationScope;
573
- return {
574
- resourceSpans: [
575
- {
576
- resource: {
577
- attributes: otlpAttributes(
578
- first.resource.attributes
579
- )
580
- },
581
- scopeSpans: [
582
- {
583
- scope: { name: scope.name, version: scope.version ?? "" },
584
- spans
585
- }
586
- ]
587
- }
588
- ]
589
- };
789
+ const resource = JSON.stringify({
790
+ attributes: otlpAttributes(
791
+ first.resource.attributes
792
+ )
793
+ });
794
+ const scopeJson = JSON.stringify({
795
+ name: scope.name,
796
+ version: scope.version ?? ""
797
+ });
798
+ const head = `{"resourceSpans":[{"resource":${resource},"scopeSpans":[{"scope":${scopeJson},"spans":[`;
799
+ const tail = "]}]}]}";
800
+ return { head, tail, size: byteLength(head) + byteLength(tail) };
590
801
  }
591
- function encodedSize(value) {
592
- const json = JSON.stringify(value);
593
- if (typeof TextEncoder !== "undefined") {
594
- return new TextEncoder().encode(json).length;
595
- }
596
- return json.length;
802
+ function encodeRequest(envelope, spans) {
803
+ return envelope.head + spans.map((span) => span.json).join(",") + envelope.tail;
597
804
  }
598
805
  function delay(ms) {
599
806
  return new Promise((resolve) => {
@@ -671,57 +878,55 @@ var BitfabSpanExporter = class {
671
878
  return true;
672
879
  }
673
880
  let encoded;
881
+ let envelope;
674
882
  try {
675
- encoded = spans.map(spanToOtlp);
883
+ encoded = spans.map(encodeSpan);
884
+ envelope = requestEnvelope(spans[0]);
676
885
  } catch (error) {
677
886
  logError("failed to encode an OpenTelemetry span batch", error);
678
887
  return false;
679
888
  }
680
- const first = spans[0];
681
- const batches = this.buildRequestBatches(first, encoded);
889
+ const batches = this.buildRequestBatches(envelope, encoded);
682
890
  const results = await mapWithConcurrency(
683
891
  batches,
684
892
  this.exportConcurrency,
685
- (batch) => this.send(first, batch)
893
+ (batch) => this.send(envelope, batch)
686
894
  );
687
895
  return results.every(Boolean);
688
896
  }
689
- buildRequestBatches(first, spans) {
897
+ buildRequestBatches(envelope, spans) {
690
898
  const batches = [];
691
899
  let current = [];
900
+ let size = envelope.size;
692
901
  for (const span of spans) {
693
- if (current.length >= this.maxRequestBatchSize) {
694
- batches.push(current);
902
+ const addition = span.size + (current.length > 0 ? SPAN_SEPARATOR_BYTES : 0);
903
+ if (current.length > 0 && (current.length >= this.maxRequestBatchSize || size + addition > this.maxRequestBytes)) {
904
+ batches.push({ spans: current, size });
695
905
  current = [];
906
+ size = envelope.size;
696
907
  }
697
- const candidate = [...current, span];
698
- if (current.length > 0 && encodedSize(buildOtlpRequest(first, candidate)) > this.maxRequestBytes) {
699
- batches.push(current);
700
- current = [span];
701
- } else {
702
- current = candidate;
703
- }
908
+ current.push(span);
909
+ size += span.size + (current.length > 1 ? SPAN_SEPARATOR_BYTES : 0);
704
910
  }
705
911
  if (current.length > 0) {
706
- batches.push(current);
912
+ batches.push({ spans: current, size });
707
913
  }
708
914
  return batches;
709
915
  }
710
- async send(first, spans) {
711
- const payload = buildOtlpRequest(first, spans);
712
- if (encodedSize(payload) > this.maxRequestBytes) {
916
+ async send(envelope, batch) {
917
+ if (batch.size > this.maxRequestBytes) {
713
918
  logError(
714
919
  "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
715
920
  );
716
921
  return false;
717
922
  }
718
923
  try {
719
- await this.sendWithRetries(payload);
924
+ await this.sendWithRetries(encodeRequest(envelope, batch.spans));
720
925
  return true;
721
926
  } catch (error) {
722
927
  if (error instanceof OtlpPayloadTooLargeError) {
723
928
  logError(
724
- spans.length === 1 ? "a single OpenTelemetry span exceeded the ingestion request limit and could not be exported" : "an OpenTelemetry span batch exceeded the ingestion request limit and could not be exported"
929
+ batch.spans.length === 1 ? "a single OpenTelemetry span exceeded the ingestion request limit and could not be exported" : "an OpenTelemetry span batch exceeded the ingestion request limit and could not be exported"
725
930
  );
726
931
  return false;
727
932
  }
@@ -745,15 +950,15 @@ var BitfabSpanExporter = class {
745
950
  * the server does not yet understand. The fix is a client-supplied
746
951
  * idempotency key that ingestion dedupes on.
747
952
  */
748
- async sendWithRetries(payload) {
953
+ async sendWithRetries(body) {
749
954
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
750
955
  try {
751
956
  const response = await this.directSender(
752
957
  OTLP_TRACES_ENDPOINT,
753
- payload,
958
+ body,
754
959
  EXPORT_TIMEOUT_MILLIS
755
960
  );
756
- const partialSuccess = asRecord(response?.partialSuccess);
961
+ const partialSuccess = asRecord2(response?.partialSuccess);
757
962
  const rejected = partialSuccess?.rejectedSpans;
758
963
  if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
759
964
  logError(
@@ -781,115 +986,6 @@ var BitfabSpanExporter = class {
781
986
  async forceFlush() {
782
987
  }
783
988
  };
784
- var CollectorSpanExporter = class {
785
- constructor(endpoint, apiKey, maxRequestBytes) {
786
- this.endpoint = endpoint;
787
- this.apiKey = apiKey;
788
- this.maxRequestBytes = maxRequestBytes;
789
- }
790
- /**
791
- * Loaded through a dynamic import rather than a top-level one so bundlers
792
- * code-split it: Collector delivery is opt-in, and a consumer who never sets
793
- * an endpoint should not pay for the exporter in their initial bundle. It is
794
- * a hard dependency, so this cannot fail for want of the package.
795
- */
796
- loadExporterModule() {
797
- if (!this.pendingModule) {
798
- this.pendingModule = import("@opentelemetry/exporter-trace-otlp-proto");
799
- }
800
- return this.pendingModule;
801
- }
802
- export(spans, resultCallback) {
803
- void this.exportAsync(spans).then(
804
- (succeeded) => {
805
- resultCallback({
806
- code: succeeded ? ExportResultCode.SUCCESS : ExportResultCode.FAILED
807
- });
808
- },
809
- (error) => {
810
- resultCallback({ code: ExportResultCode.FAILED, error });
811
- }
812
- );
813
- }
814
- async exportAsync(spans) {
815
- if (spans.length === 0) {
816
- return true;
817
- }
818
- let delegate;
819
- try {
820
- delegate = await this.resolveDelegate();
821
- } catch (error) {
822
- logError("failed to build the OTLP Collector exporter", error);
823
- return false;
824
- }
825
- const results = await Promise.all(
826
- this.partition(spans).map(
827
- (batch) => new Promise((resolve) => {
828
- try {
829
- delegate.export(batch, (result) => {
830
- resolve(result.code === ExportResultCode.SUCCESS);
831
- });
832
- } catch (error) {
833
- logError("Collector export threw", error);
834
- resolve(false);
835
- }
836
- })
837
- )
838
- );
839
- return results.every(Boolean);
840
- }
841
- /**
842
- * Partition by the encoded JSON size of each carrier rather than its encoded
843
- * protobuf size. Protobuf is strictly smaller than the equivalent JSON for
844
- * these payloads, so the JSON figure is a conservative bound that keeps every
845
- * request under the target without pulling `@opentelemetry/otlp-transformer`
846
- * into the dependency set purely to measure bytes.
847
- */
848
- partition(spans) {
849
- const batches = [];
850
- let current = [];
851
- let currentSize = 0;
852
- for (const span of spans) {
853
- const size = encodedSize(spanToOtlp(span));
854
- if (current.length > 0 && currentSize + size > this.maxRequestBytes) {
855
- batches.push(current);
856
- current = [];
857
- currentSize = 0;
858
- }
859
- current.push(span);
860
- currentSize += size;
861
- }
862
- if (current.length > 0) {
863
- batches.push(current);
864
- }
865
- return batches;
866
- }
867
- async resolveDelegate() {
868
- const apiKey = this.apiKey() ?? "";
869
- if (this.delegate && this.delegateApiKey === apiKey) {
870
- return this.delegate;
871
- }
872
- const { OTLPTraceExporter } = await this.loadExporterModule();
873
- const previous = this.delegate;
874
- this.delegate = new OTLPTraceExporter({
875
- url: this.endpoint,
876
- headers: { Authorization: `Bearer ${apiKey}` },
877
- timeoutMillis: EXPORT_TIMEOUT_MILLIS
878
- });
879
- this.delegateApiKey = apiKey;
880
- if (previous) {
881
- void previous.shutdown().catch(() => {
882
- });
883
- }
884
- return this.delegate;
885
- }
886
- async shutdown() {
887
- await this.delegate?.shutdown();
888
- }
889
- async forceFlush() {
890
- await this.delegate?.forceFlush?.();
891
- }
892
- };
893
989
  var DeliveryTrackingExporter = class {
894
990
  constructor(exporter) {
895
991
  this.exporter = exporter;
@@ -932,27 +1028,22 @@ var DeliveryTrackingExporter = class {
932
1028
  var OtelBatchTransport = class {
933
1029
  constructor(options) {
934
1030
  this.closed = false;
935
- const collectorEndpoint = options.collectorEndpoint;
936
1031
  const maxRequestBytes = options.maxRequestBytes ?? MAX_EXPORT_REQUEST_BYTES;
937
1032
  const maxRequestBatchSize = options.maxRequestBatchSize ?? DIRECT_MAX_REQUEST_BATCH_SIZE;
938
1033
  if (maxRequestBatchSize <= 0) {
939
1034
  throw new BitfabError("maxRequestBatchSize must be a positive integer");
940
1035
  }
941
1036
  this.deliveryTracker = new DeliveryTrackingExporter(
942
- collectorEndpoint === void 0 ? new BitfabSpanExporter(
1037
+ new BitfabSpanExporter(
943
1038
  options.directSender,
944
1039
  maxRequestBytes,
945
1040
  maxRequestBatchSize,
946
1041
  options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
947
- ) : new CollectorSpanExporter(
948
- normalizeCollectorEndpoint(collectorEndpoint),
949
- options.apiKey,
950
- maxRequestBytes
951
1042
  )
952
1043
  );
953
1044
  this.processor = new BatchSpanProcessor(this.deliveryTracker, {
954
1045
  maxQueueSize: options.maxQueueSize ?? MAX_QUEUE_SIZE,
955
- maxExportBatchSize: options.maxExportBatchSize ?? (collectorEndpoint === void 0 ? DIRECT_MAX_EXPORT_BATCH_SIZE : COLLECTOR_MAX_EXPORT_BATCH_SIZE),
1046
+ maxExportBatchSize: options.maxExportBatchSize ?? DIRECT_MAX_EXPORT_BATCH_SIZE,
956
1047
  scheduledDelayMillis: SCHEDULE_DELAY_MILLIS,
957
1048
  exportTimeoutMillis: options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
958
1049
  });
@@ -1037,16 +1128,12 @@ var OtelBatchTransport = class {
1037
1128
  return flushed && shutdownCompleted;
1038
1129
  }
1039
1130
  };
1040
- function normalizeCollectorEndpoint(endpoint) {
1041
- const trimmed = endpoint.replace(/\/+$/, "");
1042
- return trimmed.endsWith("/v1/traces") ? trimmed : `${trimmed}/v1/traces`;
1043
- }
1044
1131
  function endSpan(span, endTime) {
1045
1132
  span.end(endTime);
1046
1133
  }
1047
1134
  function spanName(operation, payload) {
1048
1135
  if (operation === "external_span") {
1049
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
1136
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
1050
1137
  if (typeof spanData?.name === "string") {
1051
1138
  return spanData.name;
1052
1139
  }
@@ -1057,8 +1144,8 @@ function spanName(operation, payload) {
1057
1144
  return `bitfab.${operation}`;
1058
1145
  }
1059
1146
  function payloadTimestamp(payload, field) {
1060
- const rawSpan = asRecord(payload.rawSpan);
1061
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
1147
+ const rawSpan = asRecord2(payload.rawSpan);
1148
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
1062
1149
  const raw = rawSpan?.[field] ?? rawTrace?.[field];
1063
1150
  if (typeof raw !== "string") {
1064
1151
  return void 0;
@@ -1067,7 +1154,7 @@ function payloadTimestamp(payload, field) {
1067
1154
  return Number.isNaN(parsed) ? void 0 : parsed;
1068
1155
  }
1069
1156
  function hasError(payload) {
1070
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
1157
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
1071
1158
  if (spanData?.error != null) {
1072
1159
  return true;
1073
1160
  }
@@ -1077,7 +1164,6 @@ function hasError(payload) {
1077
1164
  function createOtelTransport(options) {
1078
1165
  return new OtelBatchTransport({
1079
1166
  ...options,
1080
- collectorEndpoint: readEnv(COLLECTOR_ENDPOINT_ENV) || void 0,
1081
1167
  exportConcurrency: readBoundedIntEnv(
1082
1168
  EXPORT_CONCURRENCY_ENV,
1083
1169
  MAX_EXPORT_CONCURRENCY,
@@ -1225,8 +1311,7 @@ var HttpClient = class {
1225
1311
  }
1226
1312
  if (!this.traceTransport) {
1227
1313
  this.traceTransport = createTraceTransport({
1228
- apiKey: () => this.resolveApiKey(),
1229
- directSender: (endpoint, payload, timeoutMs) => this.request(endpoint, payload, {
1314
+ directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1230
1315
  timeout: timeoutMs
1231
1316
  })
1232
1317
  });
@@ -1296,11 +1381,6 @@ var HttpClient = class {
1296
1381
  * @throws {BitfabError} If the request fails
1297
1382
  */
1298
1383
  async request(endpoint, payload, options) {
1299
- const url = `${this.serviceUrl}${endpoint}`;
1300
- const timeout = options?.timeout ?? this.timeout;
1301
- const method = options?.method ?? "POST";
1302
- const controller = new AbortController();
1303
- const timeoutId = setTimeout(() => controller.abort(), timeout);
1304
1384
  const { body, dropped } = serializePayloadBody(payload);
1305
1385
  if (dropped.length > 0) {
1306
1386
  try {
@@ -1310,14 +1390,33 @@ var HttpClient = class {
1310
1390
  } catch {
1311
1391
  }
1312
1392
  }
1393
+ return this.sendEncoded(endpoint, body, options);
1394
+ }
1395
+ /**
1396
+ * POST an already-encoded body. The span transport encodes its own batches,
1397
+ * so routing them back through {@link HttpClient.request} would encode the
1398
+ * same data twice.
1399
+ */
1400
+ async sendEncoded(endpoint, body, options) {
1401
+ const url = `${this.serviceUrl}${endpoint}`;
1402
+ const timeout = options?.timeout ?? this.timeout;
1403
+ const method = options?.method ?? "POST";
1404
+ const controller = new AbortController();
1405
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1406
+ const prepared = encodeRequestBody(body);
1407
+ const encoded = prepared instanceof Promise ? await prepared : prepared;
1408
+ const headers = {
1409
+ "Content-Type": "application/json",
1410
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1411
+ };
1412
+ if (encoded.contentEncoding) {
1413
+ headers["Content-Encoding"] = encoded.contentEncoding;
1414
+ }
1313
1415
  try {
1314
1416
  const response = await fetch(url, {
1315
1417
  method,
1316
- headers: {
1317
- "Content-Type": "application/json",
1318
- Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1319
- },
1320
- body,
1418
+ headers,
1419
+ body: encoded.body,
1321
1420
  signal: controller.signal
1322
1421
  });
1323
1422
  if (!response.ok) {
@@ -1657,8 +1756,8 @@ function fallbackUuidV4() {
1657
1756
 
1658
1757
  // src/serialize.ts
1659
1758
  import superjson from "superjson";
1660
- var MAX_SERIALIZED_BYTES = 512e3;
1661
- var MAX_FRAMEWORK_SERIALIZED_BYTES = 2e6;
1759
+ var MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1760
+ var MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1662
1761
  function describeValue(value) {
1663
1762
  try {
1664
1763
  const ctorName = value?.constructor?.name;
@@ -2296,4 +2395,4 @@ export {
2296
2395
  reportReplayProgress,
2297
2396
  replay
2298
2397
  };
2299
- //# sourceMappingURL=chunk-5ZMEY5NX.js.map
2398
+ //# sourceMappingURL=chunk-4YTIVUYX.js.map