bitfab 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/node.cjs CHANGED
@@ -659,7 +659,7 @@ registerAsyncLocalStorageClass(
659
659
  );
660
660
 
661
661
  // src/version.generated.ts
662
- var __version__ = "0.27.2";
662
+ var __version__ = "0.28.0";
663
663
 
664
664
  // src/constants.ts
665
665
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -818,6 +818,13 @@ var HttpClient = class {
818
818
  this.serviceUrl = config.serviceUrl;
819
819
  this.timeout = config.timeout ?? 12e4;
820
820
  }
821
+ /**
822
+ * Resolve the API key at the moment it is needed (request time), invoking
823
+ * the function form if one was supplied. Never read at construction.
824
+ */
825
+ resolveApiKey() {
826
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
827
+ }
821
828
  /**
822
829
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
823
830
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -848,7 +855,7 @@ var HttpClient = class {
848
855
  method,
849
856
  headers: {
850
857
  "Content-Type": "application/json",
851
- Authorization: `Bearer ${this.apiKey}`
858
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
852
859
  },
853
860
  body,
854
861
  signal: controller.signal
@@ -1009,7 +1016,7 @@ var HttpClient = class {
1009
1016
  try {
1010
1017
  const response = await fetch(url, {
1011
1018
  method: "GET",
1012
- headers: { Authorization: `Bearer ${this.apiKey}` },
1019
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1013
1020
  signal: controller.signal
1014
1021
  });
1015
1022
  if (!response.ok) {
@@ -1045,7 +1052,7 @@ var HttpClient = class {
1045
1052
  try {
1046
1053
  const response = await fetch(url, {
1047
1054
  method: "GET",
1048
- headers: { Authorization: `Bearer ${this.apiKey}` },
1055
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1049
1056
  signal: controller.signal
1050
1057
  });
1051
1058
  if (!response.ok) {
@@ -3375,6 +3382,12 @@ function getCurrentTrace() {
3375
3382
  }
3376
3383
  };
3377
3384
  }
3385
+ function readEnv(name) {
3386
+ if (typeof process !== "undefined" && process.env) {
3387
+ return process.env[name];
3388
+ }
3389
+ return void 0;
3390
+ }
3378
3391
  var Bitfab = class {
3379
3392
  /**
3380
3393
  * Initialize the Bitfab client.
@@ -3382,30 +3395,75 @@ var Bitfab = class {
3382
3395
  * @param config - Configuration options for the client
3383
3396
  */
3384
3397
  constructor(config) {
3385
- this.apiKey = config.apiKey;
3398
+ /** Gate the empty-key warning to fire at most once. */
3399
+ this.apiKeyWarned = false;
3400
+ this.apiKeyConfig = config.apiKey;
3386
3401
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
3387
3402
  this.timeout = config.timeout ?? 12e4;
3388
3403
  this.envVars = config.envVars ?? {};
3389
- const enabled = config.enabled ?? true;
3390
- if (enabled && (!config.apiKey || config.apiKey.trim() === "")) {
3391
- console.warn(
3392
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3393
- );
3394
- this.enabled = false;
3395
- } else {
3396
- this.enabled = enabled;
3397
- }
3404
+ this.explicitlyEnabled = config.enabled ?? true;
3405
+ this.strict = config.strict ?? false;
3398
3406
  this.bamlClient = config.bamlClient ?? null;
3399
3407
  if (config.dbSnapshot) {
3400
3408
  validateDbSnapshotConfig(config.dbSnapshot);
3401
3409
  }
3402
3410
  this.dbSnapshot = config.dbSnapshot;
3403
3411
  this.httpClient = new HttpClient({
3404
- apiKey: this.apiKey,
3412
+ apiKey: () => this.resolveApiKey(),
3405
3413
  serviceUrl: this.serviceUrl,
3406
3414
  timeout: this.timeout
3407
3415
  });
3408
3416
  }
3417
+ /**
3418
+ * Resolve the API key lazily, the first time a span actually needs it.
3419
+ *
3420
+ * The key is intentionally NOT read at construction. In ESM, a shim that
3421
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
3422
+ * evaluated before the importing script's body runs `dotenv.config()`, so
3423
+ * the key would be empty at construction even though it is set moments
3424
+ * later. Resolving here (at first `withSpan` call / first request) reads
3425
+ * the key after env loading has run.
3426
+ *
3427
+ * Resolution order: the configured value (string, or function called each
3428
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
3429
+ * from the environment. Once a non-empty key is found it is cached, so an
3430
+ * early resolve that found nothing never poisons a later one.
3431
+ */
3432
+ resolveApiKey() {
3433
+ if (this.resolvedApiKey !== void 0) {
3434
+ return this.resolvedApiKey;
3435
+ }
3436
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
3437
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
3438
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
3439
+ if (key) {
3440
+ this.resolvedApiKey = key;
3441
+ return key;
3442
+ }
3443
+ if (this.strict) {
3444
+ throw new BitfabError(
3445
+ "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`."
3446
+ );
3447
+ }
3448
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
3449
+ this.apiKeyWarned = true;
3450
+ console.warn(
3451
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3452
+ );
3453
+ }
3454
+ return void 0;
3455
+ }
3456
+ /**
3457
+ * Whether tracing should run, decided lazily at call time (not frozen at
3458
+ * construction). An explicit `enabled: false` short-circuits without ever
3459
+ * touching the key.
3460
+ */
3461
+ isTracingEnabled() {
3462
+ if (!this.explicitlyEnabled) {
3463
+ return false;
3464
+ }
3465
+ return this.resolveApiKey() !== void 0;
3466
+ }
3409
3467
  /**
3410
3468
  * Fetch the function with its current version and BAML prompt from the server.
3411
3469
  *
@@ -3498,7 +3556,9 @@ var Bitfab = class {
3498
3556
  */
3499
3557
  getOpenAiTracingProcessor() {
3500
3558
  return new BitfabOpenAITracingProcessor({
3501
- apiKey: this.apiKey,
3559
+ // Resolved at getter-call time (framework handlers are set up after env
3560
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
3561
+ apiKey: this.resolveApiKey(),
3502
3562
  serviceUrl: this.serviceUrl,
3503
3563
  getActiveSpanContext: () => {
3504
3564
  const stack = getSpanStack();
@@ -3557,7 +3617,7 @@ var Bitfab = class {
3557
3617
  */
3558
3618
  getLangGraphCallbackHandler(traceFunctionKey) {
3559
3619
  return new BitfabLangGraphCallbackHandler({
3560
- apiKey: this.apiKey,
3620
+ apiKey: this.resolveApiKey(),
3561
3621
  traceFunctionKey,
3562
3622
  serviceUrl: this.serviceUrl,
3563
3623
  getActiveSpanContext: () => {
@@ -3609,7 +3669,7 @@ var Bitfab = class {
3609
3669
  */
3610
3670
  getClaudeAgentHandler(traceFunctionKey) {
3611
3671
  return new BitfabClaudeAgentHandler({
3612
- apiKey: this.apiKey,
3672
+ apiKey: this.resolveApiKey(),
3613
3673
  traceFunctionKey,
3614
3674
  serviceUrl: this.serviceUrl,
3615
3675
  getActiveSpanContext: () => {
@@ -3781,9 +3841,8 @@ var Bitfab = class {
3781
3841
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3782
3842
  */
3783
3843
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3784
- if (!this.enabled) {
3785
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3786
- return fn2;
3844
+ if (!this.explicitlyEnabled) {
3845
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3787
3846
  }
3788
3847
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3789
3848
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3798,6 +3857,9 @@ var Bitfab = class {
3798
3857
  }
3799
3858
  })();
3800
3859
  const wrappedFn = function(...args) {
3860
+ if (!self.isTracingEnabled()) {
3861
+ return fn.apply(this, args);
3862
+ }
3801
3863
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3802
3864
  return asyncLocalStorageReady.then(
3803
3865
  () => wrappedFn.apply(this, args)
@@ -4058,7 +4120,7 @@ var Bitfab = class {
4058
4120
  return {
4059
4121
  traceId,
4060
4122
  addContext: (context) => {
4061
- if (!this.enabled) {
4123
+ if (!this.isTracingEnabled()) {
4062
4124
  return Promise.resolve();
4063
4125
  }
4064
4126
  if (typeof context !== "object" || context === null) {
@@ -4069,7 +4131,7 @@ var Bitfab = class {
4069
4131
  });
4070
4132
  },
4071
4133
  setMetadata: (metadata) => {
4072
- if (!this.enabled) {
4134
+ if (!this.isTracingEnabled()) {
4073
4135
  return Promise.resolve();
4074
4136
  }
4075
4137
  if (typeof metadata !== "object" || metadata === null) {
@@ -4078,7 +4140,7 @@ var Bitfab = class {
4078
4140
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
4079
4141
  },
4080
4142
  setSessionId: (sessionId) => {
4081
- if (!this.enabled) {
4143
+ if (!this.isTracingEnabled()) {
4082
4144
  return Promise.resolve();
4083
4145
  }
4084
4146
  if (typeof sessionId !== "string" || sessionId.length === 0) {