@bitfab/sdk 0.34.1 → 0.34.2

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/README.md CHANGED
@@ -104,22 +104,6 @@ pnpm add bitfab @openai/agents
104
104
 
105
105
  The `@openai/agents` package is an optional peer dependency - the SDK works fine without it unless you need tracing functionality.
106
106
 
107
- ### With an OpenTelemetry Collector
108
-
109
- Spans are batched and delivered through a private OpenTelemetry pipeline that
110
- the SDK owns. Direct delivery to Bitfab is the default and needs no extra
111
- configuration.
112
-
113
- To route traces through an OTel Collector instead, set the receiver URL:
114
-
115
- ```bash
116
- BITFAB_OTEL_EXPORTER_ENDPOINT=http://localhost:4318
117
- ```
118
-
119
- The protobuf exporter Collector mode uses ships with the SDK and is code-split,
120
- so it loads only when you set an endpoint. See [OpenTelemetry Transport
121
- Architecture](https://docs.bitfab.ai/otel-architecture) for the full model.
122
-
123
107
  ## Local Development
124
108
 
125
109
  ```bash
@@ -277,7 +261,6 @@ When `executeLocally: false`, the SDK:
277
261
 
278
262
  - `BITFAB_URL`: The base URL for the Bitfab API (used if `serviceUrl` is not provided)
279
263
  - `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.: LLM provider API keys (required for local execution)
280
- - `BITFAB_OTEL_EXPORTER_ENDPOINT`: OTLP/HTTP Collector base URL; unset means batches go directly to Bitfab
281
264
  - `BITFAB_OTEL_MAX_REQUEST_BYTES`: request-size target for exports (positive integer up to `3000000`)
282
265
  - `BITFAB_OTEL_EXPORT_CONCURRENCY`: concurrent direct requests per export window (`1`-`64`, default `32`)
283
266
 
@@ -16,7 +16,7 @@ import {
16
16
  toJsonSafe,
17
17
  toJsonSafeReport,
18
18
  warnOnce
19
- } from "./chunk-5ZMEY5NX.js";
19
+ } from "./chunk-SBQQOFA5.js";
20
20
 
21
21
  // src/processorPayload.ts
22
22
  var SERIALIZATION_DEGRADED_STEP = "serialization_degraded";
@@ -3325,7 +3325,7 @@ var Bitfab = class {
3325
3325
  `Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
3326
3326
  );
3327
3327
  }
3328
- const { replay: doReplay } = await import("./replay-YI2BUPCV.js");
3328
+ const { replay: doReplay } = await import("./replay-BNAXIBGN.js");
3329
3329
  return doReplay(
3330
3330
  this.httpClient,
3331
3331
  this.serviceUrl,
@@ -3542,4 +3542,4 @@ export {
3542
3542
  BitfabFunction,
3543
3543
  finalizers
3544
3544
  };
3545
- //# sourceMappingURL=chunk-KO373TDH.js.map
3545
+ //# sourceMappingURL=chunk-KQB42J3S.js.map
@@ -208,7 +208,7 @@ var BitfabError = class extends Error {
208
208
  };
209
209
 
210
210
  // src/version.generated.ts
211
- var __version__ = "0.34.1";
211
+ var __version__ = "0.34.2";
212
212
 
213
213
  // src/constants.ts
214
214
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -418,10 +418,8 @@ var OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
418
418
  var MAX_EXPORT_REQUEST_BYTES = 3e6;
419
419
  var MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
420
420
  var EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
421
- var COLLECTOR_ENDPOINT_ENV = "BITFAB_OTEL_EXPORTER_ENDPOINT";
422
421
  var MAX_QUEUE_SIZE = 8192;
423
422
  var DIRECT_MAX_EXPORT_BATCH_SIZE = 512;
424
- var COLLECTOR_MAX_EXPORT_BATCH_SIZE = 32;
425
423
  var DIRECT_MAX_REQUEST_BATCH_SIZE = 8;
426
424
  var DEFAULT_EXPORT_CONCURRENCY = 32;
427
425
  var MAX_EXPORT_CONCURRENCY = 64;
@@ -568,32 +566,32 @@ function spanToOtlp(span) {
568
566
  }
569
567
  return result;
570
568
  }
571
- function buildOtlpRequest(first, spans) {
569
+ var textEncoder = typeof TextEncoder === "undefined" ? void 0 : new TextEncoder();
570
+ function byteLength(value) {
571
+ return textEncoder ? textEncoder.encode(value).length : value.length;
572
+ }
573
+ var SPAN_SEPARATOR_BYTES = 1;
574
+ function encodeSpan(span) {
575
+ const json = JSON.stringify(spanToOtlp(span));
576
+ return { json, size: byteLength(json) };
577
+ }
578
+ function requestEnvelope(first) {
572
579
  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
- };
580
+ const resource = JSON.stringify({
581
+ attributes: otlpAttributes(
582
+ first.resource.attributes
583
+ )
584
+ });
585
+ const scopeJson = JSON.stringify({
586
+ name: scope.name,
587
+ version: scope.version ?? ""
588
+ });
589
+ const head = `{"resourceSpans":[{"resource":${resource},"scopeSpans":[{"scope":${scopeJson},"spans":[`;
590
+ const tail = "]}]}]}";
591
+ return { head, tail, size: byteLength(head) + byteLength(tail) };
590
592
  }
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;
593
+ function encodeRequest(envelope, spans) {
594
+ return envelope.head + spans.map((span) => span.json).join(",") + envelope.tail;
597
595
  }
598
596
  function delay(ms) {
599
597
  return new Promise((resolve) => {
@@ -671,57 +669,55 @@ var BitfabSpanExporter = class {
671
669
  return true;
672
670
  }
673
671
  let encoded;
672
+ let envelope;
674
673
  try {
675
- encoded = spans.map(spanToOtlp);
674
+ encoded = spans.map(encodeSpan);
675
+ envelope = requestEnvelope(spans[0]);
676
676
  } catch (error) {
677
677
  logError("failed to encode an OpenTelemetry span batch", error);
678
678
  return false;
679
679
  }
680
- const first = spans[0];
681
- const batches = this.buildRequestBatches(first, encoded);
680
+ const batches = this.buildRequestBatches(envelope, encoded);
682
681
  const results = await mapWithConcurrency(
683
682
  batches,
684
683
  this.exportConcurrency,
685
- (batch) => this.send(first, batch)
684
+ (batch) => this.send(envelope, batch)
686
685
  );
687
686
  return results.every(Boolean);
688
687
  }
689
- buildRequestBatches(first, spans) {
688
+ buildRequestBatches(envelope, spans) {
690
689
  const batches = [];
691
690
  let current = [];
691
+ let size = envelope.size;
692
692
  for (const span of spans) {
693
- if (current.length >= this.maxRequestBatchSize) {
694
- batches.push(current);
693
+ const addition = span.size + (current.length > 0 ? SPAN_SEPARATOR_BYTES : 0);
694
+ if (current.length > 0 && (current.length >= this.maxRequestBatchSize || size + addition > this.maxRequestBytes)) {
695
+ batches.push({ spans: current, size });
695
696
  current = [];
697
+ size = envelope.size;
696
698
  }
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
- }
699
+ current.push(span);
700
+ size += span.size + (current.length > 1 ? SPAN_SEPARATOR_BYTES : 0);
704
701
  }
705
702
  if (current.length > 0) {
706
- batches.push(current);
703
+ batches.push({ spans: current, size });
707
704
  }
708
705
  return batches;
709
706
  }
710
- async send(first, spans) {
711
- const payload = buildOtlpRequest(first, spans);
712
- if (encodedSize(payload) > this.maxRequestBytes) {
707
+ async send(envelope, batch) {
708
+ if (batch.size > this.maxRequestBytes) {
713
709
  logError(
714
710
  "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
715
711
  );
716
712
  return false;
717
713
  }
718
714
  try {
719
- await this.sendWithRetries(payload);
715
+ await this.sendWithRetries(encodeRequest(envelope, batch.spans));
720
716
  return true;
721
717
  } catch (error) {
722
718
  if (error instanceof OtlpPayloadTooLargeError) {
723
719
  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"
720
+ 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
721
  );
726
722
  return false;
727
723
  }
@@ -745,12 +741,12 @@ var BitfabSpanExporter = class {
745
741
  * the server does not yet understand. The fix is a client-supplied
746
742
  * idempotency key that ingestion dedupes on.
747
743
  */
748
- async sendWithRetries(payload) {
744
+ async sendWithRetries(body) {
749
745
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
750
746
  try {
751
747
  const response = await this.directSender(
752
748
  OTLP_TRACES_ENDPOINT,
753
- payload,
749
+ body,
754
750
  EXPORT_TIMEOUT_MILLIS
755
751
  );
756
752
  const partialSuccess = asRecord(response?.partialSuccess);
@@ -781,115 +777,6 @@ var BitfabSpanExporter = class {
781
777
  async forceFlush() {
782
778
  }
783
779
  };
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
780
  var DeliveryTrackingExporter = class {
894
781
  constructor(exporter) {
895
782
  this.exporter = exporter;
@@ -932,27 +819,22 @@ var DeliveryTrackingExporter = class {
932
819
  var OtelBatchTransport = class {
933
820
  constructor(options) {
934
821
  this.closed = false;
935
- const collectorEndpoint = options.collectorEndpoint;
936
822
  const maxRequestBytes = options.maxRequestBytes ?? MAX_EXPORT_REQUEST_BYTES;
937
823
  const maxRequestBatchSize = options.maxRequestBatchSize ?? DIRECT_MAX_REQUEST_BATCH_SIZE;
938
824
  if (maxRequestBatchSize <= 0) {
939
825
  throw new BitfabError("maxRequestBatchSize must be a positive integer");
940
826
  }
941
827
  this.deliveryTracker = new DeliveryTrackingExporter(
942
- collectorEndpoint === void 0 ? new BitfabSpanExporter(
828
+ new BitfabSpanExporter(
943
829
  options.directSender,
944
830
  maxRequestBytes,
945
831
  maxRequestBatchSize,
946
832
  options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
947
- ) : new CollectorSpanExporter(
948
- normalizeCollectorEndpoint(collectorEndpoint),
949
- options.apiKey,
950
- maxRequestBytes
951
833
  )
952
834
  );
953
835
  this.processor = new BatchSpanProcessor(this.deliveryTracker, {
954
836
  maxQueueSize: options.maxQueueSize ?? MAX_QUEUE_SIZE,
955
- maxExportBatchSize: options.maxExportBatchSize ?? (collectorEndpoint === void 0 ? DIRECT_MAX_EXPORT_BATCH_SIZE : COLLECTOR_MAX_EXPORT_BATCH_SIZE),
837
+ maxExportBatchSize: options.maxExportBatchSize ?? DIRECT_MAX_EXPORT_BATCH_SIZE,
956
838
  scheduledDelayMillis: SCHEDULE_DELAY_MILLIS,
957
839
  exportTimeoutMillis: options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
958
840
  });
@@ -1037,10 +919,6 @@ var OtelBatchTransport = class {
1037
919
  return flushed && shutdownCompleted;
1038
920
  }
1039
921
  };
1040
- function normalizeCollectorEndpoint(endpoint) {
1041
- const trimmed = endpoint.replace(/\/+$/, "");
1042
- return trimmed.endsWith("/v1/traces") ? trimmed : `${trimmed}/v1/traces`;
1043
- }
1044
922
  function endSpan(span, endTime) {
1045
923
  span.end(endTime);
1046
924
  }
@@ -1077,7 +955,6 @@ function hasError(payload) {
1077
955
  function createOtelTransport(options) {
1078
956
  return new OtelBatchTransport({
1079
957
  ...options,
1080
- collectorEndpoint: readEnv(COLLECTOR_ENDPOINT_ENV) || void 0,
1081
958
  exportConcurrency: readBoundedIntEnv(
1082
959
  EXPORT_CONCURRENCY_ENV,
1083
960
  MAX_EXPORT_CONCURRENCY,
@@ -1225,8 +1102,7 @@ var HttpClient = class {
1225
1102
  }
1226
1103
  if (!this.traceTransport) {
1227
1104
  this.traceTransport = createTraceTransport({
1228
- apiKey: () => this.resolveApiKey(),
1229
- directSender: (endpoint, payload, timeoutMs) => this.request(endpoint, payload, {
1105
+ directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1230
1106
  timeout: timeoutMs
1231
1107
  })
1232
1108
  });
@@ -1296,11 +1172,6 @@ var HttpClient = class {
1296
1172
  * @throws {BitfabError} If the request fails
1297
1173
  */
1298
1174
  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
1175
  const { body, dropped } = serializePayloadBody(payload);
1305
1176
  if (dropped.length > 0) {
1306
1177
  try {
@@ -1310,6 +1181,19 @@ var HttpClient = class {
1310
1181
  } catch {
1311
1182
  }
1312
1183
  }
1184
+ return this.sendEncoded(endpoint, body, options);
1185
+ }
1186
+ /**
1187
+ * POST an already-encoded body. The span transport encodes its own batches,
1188
+ * so routing them back through {@link HttpClient.request} would encode the
1189
+ * same data twice.
1190
+ */
1191
+ async sendEncoded(endpoint, body, options) {
1192
+ const url = `${this.serviceUrl}${endpoint}`;
1193
+ const timeout = options?.timeout ?? this.timeout;
1194
+ const method = options?.method ?? "POST";
1195
+ const controller = new AbortController();
1196
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1313
1197
  try {
1314
1198
  const response = await fetch(url, {
1315
1199
  method,
@@ -2296,4 +2180,4 @@ export {
2296
2180
  reportReplayProgress,
2297
2181
  replay
2298
2182
  };
2299
- //# sourceMappingURL=chunk-5ZMEY5NX.js.map
2183
+ //# sourceMappingURL=chunk-SBQQOFA5.js.map