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/index.cjs CHANGED
@@ -645,7 +645,7 @@ __export(index_exports, {
645
645
  module.exports = __toCommonJS(index_exports);
646
646
 
647
647
  // src/version.generated.ts
648
- var __version__ = "0.27.2";
648
+ var __version__ = "0.28.0";
649
649
 
650
650
  // src/constants.ts
651
651
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -804,6 +804,13 @@ var HttpClient = class {
804
804
  this.serviceUrl = config.serviceUrl;
805
805
  this.timeout = config.timeout ?? 12e4;
806
806
  }
807
+ /**
808
+ * Resolve the API key at the moment it is needed (request time), invoking
809
+ * the function form if one was supplied. Never read at construction.
810
+ */
811
+ resolveApiKey() {
812
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
813
+ }
807
814
  /**
808
815
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
809
816
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -834,7 +841,7 @@ var HttpClient = class {
834
841
  method,
835
842
  headers: {
836
843
  "Content-Type": "application/json",
837
- Authorization: `Bearer ${this.apiKey}`
844
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
838
845
  },
839
846
  body,
840
847
  signal: controller.signal
@@ -995,7 +1002,7 @@ var HttpClient = class {
995
1002
  try {
996
1003
  const response = await fetch(url, {
997
1004
  method: "GET",
998
- headers: { Authorization: `Bearer ${this.apiKey}` },
1005
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
999
1006
  signal: controller.signal
1000
1007
  });
1001
1008
  if (!response.ok) {
@@ -1031,7 +1038,7 @@ var HttpClient = class {
1031
1038
  try {
1032
1039
  const response = await fetch(url, {
1033
1040
  method: "GET",
1034
- headers: { Authorization: `Bearer ${this.apiKey}` },
1041
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1035
1042
  signal: controller.signal
1036
1043
  });
1037
1044
  if (!response.ok) {
@@ -3361,6 +3368,12 @@ function getCurrentTrace() {
3361
3368
  }
3362
3369
  };
3363
3370
  }
3371
+ function readEnv(name) {
3372
+ if (typeof process !== "undefined" && process.env) {
3373
+ return process.env[name];
3374
+ }
3375
+ return void 0;
3376
+ }
3364
3377
  var Bitfab = class {
3365
3378
  /**
3366
3379
  * Initialize the Bitfab client.
@@ -3368,30 +3381,75 @@ var Bitfab = class {
3368
3381
  * @param config - Configuration options for the client
3369
3382
  */
3370
3383
  constructor(config) {
3371
- this.apiKey = config.apiKey;
3384
+ /** Gate the empty-key warning to fire at most once. */
3385
+ this.apiKeyWarned = false;
3386
+ this.apiKeyConfig = config.apiKey;
3372
3387
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
3373
3388
  this.timeout = config.timeout ?? 12e4;
3374
3389
  this.envVars = config.envVars ?? {};
3375
- const enabled = config.enabled ?? true;
3376
- if (enabled && (!config.apiKey || config.apiKey.trim() === "")) {
3377
- console.warn(
3378
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3379
- );
3380
- this.enabled = false;
3381
- } else {
3382
- this.enabled = enabled;
3383
- }
3390
+ this.explicitlyEnabled = config.enabled ?? true;
3391
+ this.strict = config.strict ?? false;
3384
3392
  this.bamlClient = config.bamlClient ?? null;
3385
3393
  if (config.dbSnapshot) {
3386
3394
  validateDbSnapshotConfig(config.dbSnapshot);
3387
3395
  }
3388
3396
  this.dbSnapshot = config.dbSnapshot;
3389
3397
  this.httpClient = new HttpClient({
3390
- apiKey: this.apiKey,
3398
+ apiKey: () => this.resolveApiKey(),
3391
3399
  serviceUrl: this.serviceUrl,
3392
3400
  timeout: this.timeout
3393
3401
  });
3394
3402
  }
3403
+ /**
3404
+ * Resolve the API key lazily, the first time a span actually needs it.
3405
+ *
3406
+ * The key is intentionally NOT read at construction. In ESM, a shim that
3407
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
3408
+ * evaluated before the importing script's body runs `dotenv.config()`, so
3409
+ * the key would be empty at construction even though it is set moments
3410
+ * later. Resolving here (at first `withSpan` call / first request) reads
3411
+ * the key after env loading has run.
3412
+ *
3413
+ * Resolution order: the configured value (string, or function called each
3414
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
3415
+ * from the environment. Once a non-empty key is found it is cached, so an
3416
+ * early resolve that found nothing never poisons a later one.
3417
+ */
3418
+ resolveApiKey() {
3419
+ if (this.resolvedApiKey !== void 0) {
3420
+ return this.resolvedApiKey;
3421
+ }
3422
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
3423
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
3424
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
3425
+ if (key) {
3426
+ this.resolvedApiKey = key;
3427
+ return key;
3428
+ }
3429
+ if (this.strict) {
3430
+ throw new BitfabError(
3431
+ "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`."
3432
+ );
3433
+ }
3434
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
3435
+ this.apiKeyWarned = true;
3436
+ console.warn(
3437
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3438
+ );
3439
+ }
3440
+ return void 0;
3441
+ }
3442
+ /**
3443
+ * Whether tracing should run, decided lazily at call time (not frozen at
3444
+ * construction). An explicit `enabled: false` short-circuits without ever
3445
+ * touching the key.
3446
+ */
3447
+ isTracingEnabled() {
3448
+ if (!this.explicitlyEnabled) {
3449
+ return false;
3450
+ }
3451
+ return this.resolveApiKey() !== void 0;
3452
+ }
3395
3453
  /**
3396
3454
  * Fetch the function with its current version and BAML prompt from the server.
3397
3455
  *
@@ -3484,7 +3542,9 @@ var Bitfab = class {
3484
3542
  */
3485
3543
  getOpenAiTracingProcessor() {
3486
3544
  return new BitfabOpenAITracingProcessor({
3487
- apiKey: this.apiKey,
3545
+ // Resolved at getter-call time (framework handlers are set up after env
3546
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
3547
+ apiKey: this.resolveApiKey(),
3488
3548
  serviceUrl: this.serviceUrl,
3489
3549
  getActiveSpanContext: () => {
3490
3550
  const stack = getSpanStack();
@@ -3543,7 +3603,7 @@ var Bitfab = class {
3543
3603
  */
3544
3604
  getLangGraphCallbackHandler(traceFunctionKey) {
3545
3605
  return new BitfabLangGraphCallbackHandler({
3546
- apiKey: this.apiKey,
3606
+ apiKey: this.resolveApiKey(),
3547
3607
  traceFunctionKey,
3548
3608
  serviceUrl: this.serviceUrl,
3549
3609
  getActiveSpanContext: () => {
@@ -3595,7 +3655,7 @@ var Bitfab = class {
3595
3655
  */
3596
3656
  getClaudeAgentHandler(traceFunctionKey) {
3597
3657
  return new BitfabClaudeAgentHandler({
3598
- apiKey: this.apiKey,
3658
+ apiKey: this.resolveApiKey(),
3599
3659
  traceFunctionKey,
3600
3660
  serviceUrl: this.serviceUrl,
3601
3661
  getActiveSpanContext: () => {
@@ -3767,9 +3827,8 @@ var Bitfab = class {
3767
3827
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3768
3828
  */
3769
3829
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3770
- if (!this.enabled) {
3771
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3772
- return fn2;
3830
+ if (!this.explicitlyEnabled) {
3831
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3773
3832
  }
3774
3833
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3775
3834
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3784,6 +3843,9 @@ var Bitfab = class {
3784
3843
  }
3785
3844
  })();
3786
3845
  const wrappedFn = function(...args) {
3846
+ if (!self.isTracingEnabled()) {
3847
+ return fn.apply(this, args);
3848
+ }
3787
3849
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3788
3850
  return asyncLocalStorageReady.then(
3789
3851
  () => wrappedFn.apply(this, args)
@@ -4044,7 +4106,7 @@ var Bitfab = class {
4044
4106
  return {
4045
4107
  traceId,
4046
4108
  addContext: (context) => {
4047
- if (!this.enabled) {
4109
+ if (!this.isTracingEnabled()) {
4048
4110
  return Promise.resolve();
4049
4111
  }
4050
4112
  if (typeof context !== "object" || context === null) {
@@ -4055,7 +4117,7 @@ var Bitfab = class {
4055
4117
  });
4056
4118
  },
4057
4119
  setMetadata: (metadata) => {
4058
- if (!this.enabled) {
4120
+ if (!this.isTracingEnabled()) {
4059
4121
  return Promise.resolve();
4060
4122
  }
4061
4123
  if (typeof metadata !== "object" || metadata === null) {
@@ -4064,7 +4126,7 @@ var Bitfab = class {
4064
4126
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
4065
4127
  },
4066
4128
  setSessionId: (sessionId) => {
4067
- if (!this.enabled) {
4129
+ if (!this.isTracingEnabled()) {
4068
4130
  return Promise.resolve();
4069
4131
  }
4070
4132
  if (typeof sessionId !== "string" || sessionId.length === 0) {