@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/node.cjs CHANGED
@@ -650,7 +650,7 @@ registerAsyncLocalStorageClass(
650
650
  );
651
651
 
652
652
  // src/version.generated.ts
653
- var __version__ = "0.27.2";
653
+ var __version__ = "0.28.0";
654
654
 
655
655
  // src/constants.ts
656
656
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -809,6 +809,13 @@ var HttpClient = class {
809
809
  this.serviceUrl = config.serviceUrl;
810
810
  this.timeout = config.timeout ?? 12e4;
811
811
  }
812
+ /**
813
+ * Resolve the API key at the moment it is needed (request time), invoking
814
+ * the function form if one was supplied. Never read at construction.
815
+ */
816
+ resolveApiKey() {
817
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
818
+ }
812
819
  /**
813
820
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
814
821
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -839,7 +846,7 @@ var HttpClient = class {
839
846
  method,
840
847
  headers: {
841
848
  "Content-Type": "application/json",
842
- Authorization: `Bearer ${this.apiKey}`
849
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
843
850
  },
844
851
  body,
845
852
  signal: controller.signal
@@ -1000,7 +1007,7 @@ var HttpClient = class {
1000
1007
  try {
1001
1008
  const response = await fetch(url, {
1002
1009
  method: "GET",
1003
- headers: { Authorization: `Bearer ${this.apiKey}` },
1010
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1004
1011
  signal: controller.signal
1005
1012
  });
1006
1013
  if (!response.ok) {
@@ -1036,7 +1043,7 @@ var HttpClient = class {
1036
1043
  try {
1037
1044
  const response = await fetch(url, {
1038
1045
  method: "GET",
1039
- headers: { Authorization: `Bearer ${this.apiKey}` },
1046
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1040
1047
  signal: controller.signal
1041
1048
  });
1042
1049
  if (!response.ok) {
@@ -3366,6 +3373,12 @@ function getCurrentTrace() {
3366
3373
  }
3367
3374
  };
3368
3375
  }
3376
+ function readEnv(name) {
3377
+ if (typeof process !== "undefined" && process.env) {
3378
+ return process.env[name];
3379
+ }
3380
+ return void 0;
3381
+ }
3369
3382
  var Bitfab = class {
3370
3383
  /**
3371
3384
  * Initialize the Bitfab client.
@@ -3373,30 +3386,75 @@ var Bitfab = class {
3373
3386
  * @param config - Configuration options for the client
3374
3387
  */
3375
3388
  constructor(config) {
3376
- this.apiKey = config.apiKey;
3389
+ /** Gate the empty-key warning to fire at most once. */
3390
+ this.apiKeyWarned = false;
3391
+ this.apiKeyConfig = config.apiKey;
3377
3392
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
3378
3393
  this.timeout = config.timeout ?? 12e4;
3379
3394
  this.envVars = config.envVars ?? {};
3380
- const enabled = config.enabled ?? true;
3381
- if (enabled && (!config.apiKey || config.apiKey.trim() === "")) {
3382
- console.warn(
3383
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3384
- );
3385
- this.enabled = false;
3386
- } else {
3387
- this.enabled = enabled;
3388
- }
3395
+ this.explicitlyEnabled = config.enabled ?? true;
3396
+ this.strict = config.strict ?? false;
3389
3397
  this.bamlClient = config.bamlClient ?? null;
3390
3398
  if (config.dbSnapshot) {
3391
3399
  validateDbSnapshotConfig(config.dbSnapshot);
3392
3400
  }
3393
3401
  this.dbSnapshot = config.dbSnapshot;
3394
3402
  this.httpClient = new HttpClient({
3395
- apiKey: this.apiKey,
3403
+ apiKey: () => this.resolveApiKey(),
3396
3404
  serviceUrl: this.serviceUrl,
3397
3405
  timeout: this.timeout
3398
3406
  });
3399
3407
  }
3408
+ /**
3409
+ * Resolve the API key lazily, the first time a span actually needs it.
3410
+ *
3411
+ * The key is intentionally NOT read at construction. In ESM, a shim that
3412
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
3413
+ * evaluated before the importing script's body runs `dotenv.config()`, so
3414
+ * the key would be empty at construction even though it is set moments
3415
+ * later. Resolving here (at first `withSpan` call / first request) reads
3416
+ * the key after env loading has run.
3417
+ *
3418
+ * Resolution order: the configured value (string, or function called each
3419
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
3420
+ * from the environment. Once a non-empty key is found it is cached, so an
3421
+ * early resolve that found nothing never poisons a later one.
3422
+ */
3423
+ resolveApiKey() {
3424
+ if (this.resolvedApiKey !== void 0) {
3425
+ return this.resolvedApiKey;
3426
+ }
3427
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
3428
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
3429
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
3430
+ if (key) {
3431
+ this.resolvedApiKey = key;
3432
+ return key;
3433
+ }
3434
+ if (this.strict) {
3435
+ throw new BitfabError(
3436
+ "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`."
3437
+ );
3438
+ }
3439
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
3440
+ this.apiKeyWarned = true;
3441
+ console.warn(
3442
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3443
+ );
3444
+ }
3445
+ return void 0;
3446
+ }
3447
+ /**
3448
+ * Whether tracing should run, decided lazily at call time (not frozen at
3449
+ * construction). An explicit `enabled: false` short-circuits without ever
3450
+ * touching the key.
3451
+ */
3452
+ isTracingEnabled() {
3453
+ if (!this.explicitlyEnabled) {
3454
+ return false;
3455
+ }
3456
+ return this.resolveApiKey() !== void 0;
3457
+ }
3400
3458
  /**
3401
3459
  * Fetch the function with its current version and BAML prompt from the server.
3402
3460
  *
@@ -3489,7 +3547,9 @@ var Bitfab = class {
3489
3547
  */
3490
3548
  getOpenAiTracingProcessor() {
3491
3549
  return new BitfabOpenAITracingProcessor({
3492
- apiKey: this.apiKey,
3550
+ // Resolved at getter-call time (framework handlers are set up after env
3551
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
3552
+ apiKey: this.resolveApiKey(),
3493
3553
  serviceUrl: this.serviceUrl,
3494
3554
  getActiveSpanContext: () => {
3495
3555
  const stack = getSpanStack();
@@ -3548,7 +3608,7 @@ var Bitfab = class {
3548
3608
  */
3549
3609
  getLangGraphCallbackHandler(traceFunctionKey) {
3550
3610
  return new BitfabLangGraphCallbackHandler({
3551
- apiKey: this.apiKey,
3611
+ apiKey: this.resolveApiKey(),
3552
3612
  traceFunctionKey,
3553
3613
  serviceUrl: this.serviceUrl,
3554
3614
  getActiveSpanContext: () => {
@@ -3600,7 +3660,7 @@ var Bitfab = class {
3600
3660
  */
3601
3661
  getClaudeAgentHandler(traceFunctionKey) {
3602
3662
  return new BitfabClaudeAgentHandler({
3603
- apiKey: this.apiKey,
3663
+ apiKey: this.resolveApiKey(),
3604
3664
  traceFunctionKey,
3605
3665
  serviceUrl: this.serviceUrl,
3606
3666
  getActiveSpanContext: () => {
@@ -3772,9 +3832,8 @@ var Bitfab = class {
3772
3832
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3773
3833
  */
3774
3834
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3775
- if (!this.enabled) {
3776
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3777
- return fn2;
3835
+ if (!this.explicitlyEnabled) {
3836
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3778
3837
  }
3779
3838
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3780
3839
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3789,6 +3848,9 @@ var Bitfab = class {
3789
3848
  }
3790
3849
  })();
3791
3850
  const wrappedFn = function(...args) {
3851
+ if (!self.isTracingEnabled()) {
3852
+ return fn.apply(this, args);
3853
+ }
3792
3854
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3793
3855
  return asyncLocalStorageReady.then(
3794
3856
  () => wrappedFn.apply(this, args)
@@ -4049,7 +4111,7 @@ var Bitfab = class {
4049
4111
  return {
4050
4112
  traceId,
4051
4113
  addContext: (context) => {
4052
- if (!this.enabled) {
4114
+ if (!this.isTracingEnabled()) {
4053
4115
  return Promise.resolve();
4054
4116
  }
4055
4117
  if (typeof context !== "object" || context === null) {
@@ -4060,7 +4122,7 @@ var Bitfab = class {
4060
4122
  });
4061
4123
  },
4062
4124
  setMetadata: (metadata) => {
4063
- if (!this.enabled) {
4125
+ if (!this.isTracingEnabled()) {
4064
4126
  return Promise.resolve();
4065
4127
  }
4066
4128
  if (typeof metadata !== "object" || metadata === null) {
@@ -4069,7 +4131,7 @@ var Bitfab = class {
4069
4131
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
4070
4132
  },
4071
4133
  setSessionId: (sessionId) => {
4072
- if (!this.enabled) {
4134
+ if (!this.isTracingEnabled()) {
4073
4135
  return Promise.resolve();
4074
4136
  }
4075
4137
  if (typeof sessionId !== "string" || sessionId.length === 0) {