bitfab 0.27.2 → 0.28.1

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.js CHANGED
@@ -23,12 +23,12 @@ import {
23
23
  flushTraces,
24
24
  getCurrentSpan,
25
25
  getCurrentTrace
26
- } from "./chunk-DW246LVL.js";
26
+ } from "./chunk-3YNAPGPA.js";
27
27
  import {
28
28
  BITFAB_PROGRESS_PREFIX,
29
29
  BitfabError,
30
30
  reportReplayProgress
31
- } from "./chunk-MD4XQGAF.js";
31
+ } from "./chunk-FTXZWRTG.js";
32
32
  export {
33
33
  BITFAB_PROGRESS_PREFIX,
34
34
  Bitfab,
package/dist/node.cjs CHANGED
@@ -545,6 +545,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
545
545
  }
546
546
  try {
547
547
  options?.onProgress?.({
548
+ testRunId,
548
549
  completed,
549
550
  total,
550
551
  succeeded,
@@ -554,8 +555,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
554
555
  // that just settled. The item's own traceId is the new replay
555
556
  // trace and is assigned later (below), so use the server item.
556
557
  traceId: serverItems[index]?.traceId ?? null,
558
+ replayTraceId: item.traceId,
559
+ input: item.input,
560
+ result: item.result,
561
+ originalOutput: item.originalOutput,
557
562
  error: item.error,
558
- durationMs: item.durationMs
563
+ durationMs: item.durationMs,
564
+ tokens: item.tokens,
565
+ model: item.model,
566
+ dbSnapshotRef: item.dbSnapshotRef
559
567
  }
560
568
  });
561
569
  } catch {
@@ -659,7 +667,7 @@ registerAsyncLocalStorageClass(
659
667
  );
660
668
 
661
669
  // src/version.generated.ts
662
- var __version__ = "0.27.2";
670
+ var __version__ = "0.28.1";
663
671
 
664
672
  // src/constants.ts
665
673
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -818,6 +826,13 @@ var HttpClient = class {
818
826
  this.serviceUrl = config.serviceUrl;
819
827
  this.timeout = config.timeout ?? 12e4;
820
828
  }
829
+ /**
830
+ * Resolve the API key at the moment it is needed (request time), invoking
831
+ * the function form if one was supplied. Never read at construction.
832
+ */
833
+ resolveApiKey() {
834
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
835
+ }
821
836
  /**
822
837
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
823
838
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -848,7 +863,7 @@ var HttpClient = class {
848
863
  method,
849
864
  headers: {
850
865
  "Content-Type": "application/json",
851
- Authorization: `Bearer ${this.apiKey}`
866
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
852
867
  },
853
868
  body,
854
869
  signal: controller.signal
@@ -1009,7 +1024,7 @@ var HttpClient = class {
1009
1024
  try {
1010
1025
  const response = await fetch(url, {
1011
1026
  method: "GET",
1012
- headers: { Authorization: `Bearer ${this.apiKey}` },
1027
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1013
1028
  signal: controller.signal
1014
1029
  });
1015
1030
  if (!response.ok) {
@@ -1045,7 +1060,7 @@ var HttpClient = class {
1045
1060
  try {
1046
1061
  const response = await fetch(url, {
1047
1062
  method: "GET",
1048
- headers: { Authorization: `Bearer ${this.apiKey}` },
1063
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1049
1064
  signal: controller.signal
1050
1065
  });
1051
1066
  if (!response.ok) {
@@ -3375,6 +3390,12 @@ function getCurrentTrace() {
3375
3390
  }
3376
3391
  };
3377
3392
  }
3393
+ function readEnv(name) {
3394
+ if (typeof process !== "undefined" && process.env) {
3395
+ return process.env[name];
3396
+ }
3397
+ return void 0;
3398
+ }
3378
3399
  var Bitfab = class {
3379
3400
  /**
3380
3401
  * Initialize the Bitfab client.
@@ -3382,30 +3403,75 @@ var Bitfab = class {
3382
3403
  * @param config - Configuration options for the client
3383
3404
  */
3384
3405
  constructor(config) {
3385
- this.apiKey = config.apiKey;
3406
+ /** Gate the empty-key warning to fire at most once. */
3407
+ this.apiKeyWarned = false;
3408
+ this.apiKeyConfig = config.apiKey;
3386
3409
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
3387
3410
  this.timeout = config.timeout ?? 12e4;
3388
3411
  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
- }
3412
+ this.explicitlyEnabled = config.enabled ?? true;
3413
+ this.strict = config.strict ?? false;
3398
3414
  this.bamlClient = config.bamlClient ?? null;
3399
3415
  if (config.dbSnapshot) {
3400
3416
  validateDbSnapshotConfig(config.dbSnapshot);
3401
3417
  }
3402
3418
  this.dbSnapshot = config.dbSnapshot;
3403
3419
  this.httpClient = new HttpClient({
3404
- apiKey: this.apiKey,
3420
+ apiKey: () => this.resolveApiKey(),
3405
3421
  serviceUrl: this.serviceUrl,
3406
3422
  timeout: this.timeout
3407
3423
  });
3408
3424
  }
3425
+ /**
3426
+ * Resolve the API key lazily, the first time a span actually needs it.
3427
+ *
3428
+ * The key is intentionally NOT read at construction. In ESM, a shim that
3429
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
3430
+ * evaluated before the importing script's body runs `dotenv.config()`, so
3431
+ * the key would be empty at construction even though it is set moments
3432
+ * later. Resolving here (at first `withSpan` call / first request) reads
3433
+ * the key after env loading has run.
3434
+ *
3435
+ * Resolution order: the configured value (string, or function called each
3436
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
3437
+ * from the environment. Once a non-empty key is found it is cached, so an
3438
+ * early resolve that found nothing never poisons a later one.
3439
+ */
3440
+ resolveApiKey() {
3441
+ if (this.resolvedApiKey !== void 0) {
3442
+ return this.resolvedApiKey;
3443
+ }
3444
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
3445
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
3446
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
3447
+ if (key) {
3448
+ this.resolvedApiKey = key;
3449
+ return key;
3450
+ }
3451
+ if (this.strict) {
3452
+ throw new BitfabError(
3453
+ "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`."
3454
+ );
3455
+ }
3456
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
3457
+ this.apiKeyWarned = true;
3458
+ console.warn(
3459
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3460
+ );
3461
+ }
3462
+ return void 0;
3463
+ }
3464
+ /**
3465
+ * Whether tracing should run, decided lazily at call time (not frozen at
3466
+ * construction). An explicit `enabled: false` short-circuits without ever
3467
+ * touching the key.
3468
+ */
3469
+ isTracingEnabled() {
3470
+ if (!this.explicitlyEnabled) {
3471
+ return false;
3472
+ }
3473
+ return this.resolveApiKey() !== void 0;
3474
+ }
3409
3475
  /**
3410
3476
  * Fetch the function with its current version and BAML prompt from the server.
3411
3477
  *
@@ -3498,7 +3564,9 @@ var Bitfab = class {
3498
3564
  */
3499
3565
  getOpenAiTracingProcessor() {
3500
3566
  return new BitfabOpenAITracingProcessor({
3501
- apiKey: this.apiKey,
3567
+ // Resolved at getter-call time (framework handlers are set up after env
3568
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
3569
+ apiKey: this.resolveApiKey(),
3502
3570
  serviceUrl: this.serviceUrl,
3503
3571
  getActiveSpanContext: () => {
3504
3572
  const stack = getSpanStack();
@@ -3557,7 +3625,7 @@ var Bitfab = class {
3557
3625
  */
3558
3626
  getLangGraphCallbackHandler(traceFunctionKey) {
3559
3627
  return new BitfabLangGraphCallbackHandler({
3560
- apiKey: this.apiKey,
3628
+ apiKey: this.resolveApiKey(),
3561
3629
  traceFunctionKey,
3562
3630
  serviceUrl: this.serviceUrl,
3563
3631
  getActiveSpanContext: () => {
@@ -3609,7 +3677,7 @@ var Bitfab = class {
3609
3677
  */
3610
3678
  getClaudeAgentHandler(traceFunctionKey) {
3611
3679
  return new BitfabClaudeAgentHandler({
3612
- apiKey: this.apiKey,
3680
+ apiKey: this.resolveApiKey(),
3613
3681
  traceFunctionKey,
3614
3682
  serviceUrl: this.serviceUrl,
3615
3683
  getActiveSpanContext: () => {
@@ -3781,9 +3849,8 @@ var Bitfab = class {
3781
3849
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3782
3850
  */
3783
3851
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3784
- if (!this.enabled) {
3785
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3786
- return fn2;
3852
+ if (!this.explicitlyEnabled) {
3853
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3787
3854
  }
3788
3855
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3789
3856
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3798,6 +3865,9 @@ var Bitfab = class {
3798
3865
  }
3799
3866
  })();
3800
3867
  const wrappedFn = function(...args) {
3868
+ if (!self.isTracingEnabled()) {
3869
+ return fn.apply(this, args);
3870
+ }
3801
3871
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3802
3872
  return asyncLocalStorageReady.then(
3803
3873
  () => wrappedFn.apply(this, args)
@@ -4058,7 +4128,7 @@ var Bitfab = class {
4058
4128
  return {
4059
4129
  traceId,
4060
4130
  addContext: (context) => {
4061
- if (!this.enabled) {
4131
+ if (!this.isTracingEnabled()) {
4062
4132
  return Promise.resolve();
4063
4133
  }
4064
4134
  if (typeof context !== "object" || context === null) {
@@ -4069,7 +4139,7 @@ var Bitfab = class {
4069
4139
  });
4070
4140
  },
4071
4141
  setMetadata: (metadata) => {
4072
- if (!this.enabled) {
4142
+ if (!this.isTracingEnabled()) {
4073
4143
  return Promise.resolve();
4074
4144
  }
4075
4145
  if (typeof metadata !== "object" || metadata === null) {
@@ -4078,7 +4148,7 @@ var Bitfab = class {
4078
4148
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
4079
4149
  },
4080
4150
  setSessionId: (sessionId) => {
4081
- if (!this.enabled) {
4151
+ if (!this.isTracingEnabled()) {
4082
4152
  return Promise.resolve();
4083
4153
  }
4084
4154
  if (typeof sessionId !== "string" || sessionId.length === 0) {