@bitfab/sdk 0.27.2 → 0.28.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.
package/dist/index.cjs CHANGED
@@ -636,7 +636,7 @@ __export(index_exports, {
636
636
  module.exports = __toCommonJS(index_exports);
637
637
 
638
638
  // src/version.generated.ts
639
- var __version__ = "0.27.2";
639
+ var __version__ = "0.28.0";
640
640
 
641
641
  // src/constants.ts
642
642
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -795,6 +795,13 @@ var HttpClient = class {
795
795
  this.serviceUrl = config.serviceUrl;
796
796
  this.timeout = config.timeout ?? 12e4;
797
797
  }
798
+ /**
799
+ * Resolve the API key at the moment it is needed (request time), invoking
800
+ * the function form if one was supplied. Never read at construction.
801
+ */
802
+ resolveApiKey() {
803
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
804
+ }
798
805
  /**
799
806
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
800
807
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -825,7 +832,7 @@ var HttpClient = class {
825
832
  method,
826
833
  headers: {
827
834
  "Content-Type": "application/json",
828
- Authorization: `Bearer ${this.apiKey}`
835
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
829
836
  },
830
837
  body,
831
838
  signal: controller.signal
@@ -986,7 +993,7 @@ var HttpClient = class {
986
993
  try {
987
994
  const response = await fetch(url, {
988
995
  method: "GET",
989
- headers: { Authorization: `Bearer ${this.apiKey}` },
996
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
990
997
  signal: controller.signal
991
998
  });
992
999
  if (!response.ok) {
@@ -1022,7 +1029,7 @@ var HttpClient = class {
1022
1029
  try {
1023
1030
  const response = await fetch(url, {
1024
1031
  method: "GET",
1025
- headers: { Authorization: `Bearer ${this.apiKey}` },
1032
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1026
1033
  signal: controller.signal
1027
1034
  });
1028
1035
  if (!response.ok) {
@@ -3352,6 +3359,12 @@ function getCurrentTrace() {
3352
3359
  }
3353
3360
  };
3354
3361
  }
3362
+ function readEnv(name) {
3363
+ if (typeof process !== "undefined" && process.env) {
3364
+ return process.env[name];
3365
+ }
3366
+ return void 0;
3367
+ }
3355
3368
  var Bitfab = class {
3356
3369
  /**
3357
3370
  * Initialize the Bitfab client.
@@ -3359,30 +3372,75 @@ var Bitfab = class {
3359
3372
  * @param config - Configuration options for the client
3360
3373
  */
3361
3374
  constructor(config) {
3362
- this.apiKey = config.apiKey;
3375
+ /** Gate the empty-key warning to fire at most once. */
3376
+ this.apiKeyWarned = false;
3377
+ this.apiKeyConfig = config.apiKey;
3363
3378
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
3364
3379
  this.timeout = config.timeout ?? 12e4;
3365
3380
  this.envVars = config.envVars ?? {};
3366
- const enabled = config.enabled ?? true;
3367
- if (enabled && (!config.apiKey || config.apiKey.trim() === "")) {
3368
- console.warn(
3369
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3370
- );
3371
- this.enabled = false;
3372
- } else {
3373
- this.enabled = enabled;
3374
- }
3381
+ this.explicitlyEnabled = config.enabled ?? true;
3382
+ this.strict = config.strict ?? false;
3375
3383
  this.bamlClient = config.bamlClient ?? null;
3376
3384
  if (config.dbSnapshot) {
3377
3385
  validateDbSnapshotConfig(config.dbSnapshot);
3378
3386
  }
3379
3387
  this.dbSnapshot = config.dbSnapshot;
3380
3388
  this.httpClient = new HttpClient({
3381
- apiKey: this.apiKey,
3389
+ apiKey: () => this.resolveApiKey(),
3382
3390
  serviceUrl: this.serviceUrl,
3383
3391
  timeout: this.timeout
3384
3392
  });
3385
3393
  }
3394
+ /**
3395
+ * Resolve the API key lazily, the first time a span actually needs it.
3396
+ *
3397
+ * The key is intentionally NOT read at construction. In ESM, a shim that
3398
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
3399
+ * evaluated before the importing script's body runs `dotenv.config()`, so
3400
+ * the key would be empty at construction even though it is set moments
3401
+ * later. Resolving here (at first `withSpan` call / first request) reads
3402
+ * the key after env loading has run.
3403
+ *
3404
+ * Resolution order: the configured value (string, or function called each
3405
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
3406
+ * from the environment. Once a non-empty key is found it is cached, so an
3407
+ * early resolve that found nothing never poisons a later one.
3408
+ */
3409
+ resolveApiKey() {
3410
+ if (this.resolvedApiKey !== void 0) {
3411
+ return this.resolvedApiKey;
3412
+ }
3413
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
3414
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
3415
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
3416
+ if (key) {
3417
+ this.resolvedApiKey = key;
3418
+ return key;
3419
+ }
3420
+ if (this.strict) {
3421
+ throw new BitfabError(
3422
+ "Bitfab: no API key resolved. Set BITFAB_API_KEY or pass apiKey to new Bitfab(). If a script loads env with dotenv, load it before the module that constructs the client is imported (e.g. `node --env-file=.env script.ts`), or pass `apiKey: () => process.env.BITFAB_API_KEY`."
3423
+ );
3424
+ }
3425
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
3426
+ this.apiKeyWarned = true;
3427
+ console.warn(
3428
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3429
+ );
3430
+ }
3431
+ return void 0;
3432
+ }
3433
+ /**
3434
+ * Whether tracing should run, decided lazily at call time (not frozen at
3435
+ * construction). An explicit `enabled: false` short-circuits without ever
3436
+ * touching the key.
3437
+ */
3438
+ isTracingEnabled() {
3439
+ if (!this.explicitlyEnabled) {
3440
+ return false;
3441
+ }
3442
+ return this.resolveApiKey() !== void 0;
3443
+ }
3386
3444
  /**
3387
3445
  * Fetch the function with its current version and BAML prompt from the server.
3388
3446
  *
@@ -3475,7 +3533,9 @@ var Bitfab = class {
3475
3533
  */
3476
3534
  getOpenAiTracingProcessor() {
3477
3535
  return new BitfabOpenAITracingProcessor({
3478
- apiKey: this.apiKey,
3536
+ // Resolved at getter-call time (framework handlers are set up after env
3537
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
3538
+ apiKey: this.resolveApiKey(),
3479
3539
  serviceUrl: this.serviceUrl,
3480
3540
  getActiveSpanContext: () => {
3481
3541
  const stack = getSpanStack();
@@ -3534,7 +3594,7 @@ var Bitfab = class {
3534
3594
  */
3535
3595
  getLangGraphCallbackHandler(traceFunctionKey) {
3536
3596
  return new BitfabLangGraphCallbackHandler({
3537
- apiKey: this.apiKey,
3597
+ apiKey: this.resolveApiKey(),
3538
3598
  traceFunctionKey,
3539
3599
  serviceUrl: this.serviceUrl,
3540
3600
  getActiveSpanContext: () => {
@@ -3586,7 +3646,7 @@ var Bitfab = class {
3586
3646
  */
3587
3647
  getClaudeAgentHandler(traceFunctionKey) {
3588
3648
  return new BitfabClaudeAgentHandler({
3589
- apiKey: this.apiKey,
3649
+ apiKey: this.resolveApiKey(),
3590
3650
  traceFunctionKey,
3591
3651
  serviceUrl: this.serviceUrl,
3592
3652
  getActiveSpanContext: () => {
@@ -3758,9 +3818,8 @@ var Bitfab = class {
3758
3818
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3759
3819
  */
3760
3820
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3761
- if (!this.enabled) {
3762
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3763
- return fn2;
3821
+ if (!this.explicitlyEnabled) {
3822
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3764
3823
  }
3765
3824
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3766
3825
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3775,6 +3834,9 @@ var Bitfab = class {
3775
3834
  }
3776
3835
  })();
3777
3836
  const wrappedFn = function(...args) {
3837
+ if (!self.isTracingEnabled()) {
3838
+ return fn.apply(this, args);
3839
+ }
3778
3840
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3779
3841
  return asyncLocalStorageReady.then(
3780
3842
  () => wrappedFn.apply(this, args)
@@ -4035,7 +4097,7 @@ var Bitfab = class {
4035
4097
  return {
4036
4098
  traceId,
4037
4099
  addContext: (context) => {
4038
- if (!this.enabled) {
4100
+ if (!this.isTracingEnabled()) {
4039
4101
  return Promise.resolve();
4040
4102
  }
4041
4103
  if (typeof context !== "object" || context === null) {
@@ -4046,7 +4108,7 @@ var Bitfab = class {
4046
4108
  });
4047
4109
  },
4048
4110
  setMetadata: (metadata) => {
4049
- if (!this.enabled) {
4111
+ if (!this.isTracingEnabled()) {
4050
4112
  return Promise.resolve();
4051
4113
  }
4052
4114
  if (typeof metadata !== "object" || metadata === null) {
@@ -4055,7 +4117,7 @@ var Bitfab = class {
4055
4117
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
4056
4118
  },
4057
4119
  setSessionId: (sessionId) => {
4058
- if (!this.enabled) {
4120
+ if (!this.isTracingEnabled()) {
4059
4121
  return Promise.resolve();
4060
4122
  }
4061
4123
  if (typeof sessionId !== "string" || sessionId.length === 0) {