bitfab 0.17.0 → 0.18.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.d.cts CHANGED
@@ -225,7 +225,7 @@ interface CodeChangeFile {
225
225
  * LangGraph/LangChain callback handler for Bitfab tracing.
226
226
  *
227
227
  * Hooks into LangGraph's callback system to capture graph node execution,
228
- * LLM calls, and tool invocations as Bitfab spans without requiring users
228
+ * LLM calls, and tool invocations as Bitfab spans, without requiring users
229
229
  * to wrap their functions with withSpan (which fails on non-serializable args).
230
230
  *
231
231
  * Duck-typed to match LangChain.js's BaseCallbackHandler interface.
@@ -238,8 +238,8 @@ interface ActiveSpanContext$1 {
238
238
  /**
239
239
  * LangChain/LangGraph callback handler that sends traces to Bitfab.
240
240
  *
241
- * Duck-typed to match LangChain.js's BaseCallbackHandler no
242
- * `@langchain/core` dependency required. Pass as a callback:
241
+ * Duck-typed to match LangChain.js's BaseCallbackHandler, so no
242
+ * `@langchain/core` dependency is required. Pass as a callback:
243
243
  *
244
244
  * ```typescript
245
245
  * const handler = bitfab.getLangGraphCallbackHandler("my-agent");
@@ -978,11 +978,15 @@ declare class Bitfab {
978
978
  * Fetches the last N traces for the given trace function key, re-runs each
979
979
  * through the provided function, and returns comparison data.
980
980
  *
981
- * The function must have been wrapped with `withSpan` replay injects
982
- * `testRunId` via async context so new spans are linked to the test run.
981
+ * Accepts either a `withSpan`-wrapped function (under the same key) or any
982
+ * plain callable: plain callables are wrapped internally so each replayed
983
+ * invocation records a trace tied to the test run. The plain-callable form
984
+ * is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
985
+ * SDK) replay — those record traces under a key with no `withSpan`-wrapped
986
+ * root in the app.
983
987
  *
984
988
  * @param traceFunctionKey - The trace function key to replay
985
- * @param fn - The function to replay (must be the return value of `withSpan`)
989
+ * @param fn - The function to run recorded inputs through
986
990
  * @param options - Optional replay options. When `traceIds` is passed,
987
991
  * `limit` is ignored (with a warning): an explicit ID list already
988
992
  * determines how many traces replay.
@@ -1051,7 +1055,7 @@ declare class BitfabFunction {
1051
1055
  /**
1052
1056
  * SDK version from package.json (injected at build time)
1053
1057
  */
1054
- declare const __version__ = "0.17.0";
1058
+ declare const __version__ = "0.18.1";
1055
1059
 
1056
1060
  /**
1057
1061
  * Constants for the Bitfab SDK.
package/dist/index.d.ts CHANGED
@@ -225,7 +225,7 @@ interface CodeChangeFile {
225
225
  * LangGraph/LangChain callback handler for Bitfab tracing.
226
226
  *
227
227
  * Hooks into LangGraph's callback system to capture graph node execution,
228
- * LLM calls, and tool invocations as Bitfab spans without requiring users
228
+ * LLM calls, and tool invocations as Bitfab spans, without requiring users
229
229
  * to wrap their functions with withSpan (which fails on non-serializable args).
230
230
  *
231
231
  * Duck-typed to match LangChain.js's BaseCallbackHandler interface.
@@ -238,8 +238,8 @@ interface ActiveSpanContext$1 {
238
238
  /**
239
239
  * LangChain/LangGraph callback handler that sends traces to Bitfab.
240
240
  *
241
- * Duck-typed to match LangChain.js's BaseCallbackHandler no
242
- * `@langchain/core` dependency required. Pass as a callback:
241
+ * Duck-typed to match LangChain.js's BaseCallbackHandler, so no
242
+ * `@langchain/core` dependency is required. Pass as a callback:
243
243
  *
244
244
  * ```typescript
245
245
  * const handler = bitfab.getLangGraphCallbackHandler("my-agent");
@@ -978,11 +978,15 @@ declare class Bitfab {
978
978
  * Fetches the last N traces for the given trace function key, re-runs each
979
979
  * through the provided function, and returns comparison data.
980
980
  *
981
- * The function must have been wrapped with `withSpan` replay injects
982
- * `testRunId` via async context so new spans are linked to the test run.
981
+ * Accepts either a `withSpan`-wrapped function (under the same key) or any
982
+ * plain callable: plain callables are wrapped internally so each replayed
983
+ * invocation records a trace tied to the test run. The plain-callable form
984
+ * is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
985
+ * SDK) replay — those record traces under a key with no `withSpan`-wrapped
986
+ * root in the app.
983
987
  *
984
988
  * @param traceFunctionKey - The trace function key to replay
985
- * @param fn - The function to replay (must be the return value of `withSpan`)
989
+ * @param fn - The function to run recorded inputs through
986
990
  * @param options - Optional replay options. When `traceIds` is passed,
987
991
  * `limit` is ignored (with a warning): an explicit ID list already
988
992
  * determines how many traces replay.
@@ -1051,7 +1055,7 @@ declare class BitfabFunction {
1051
1055
  /**
1052
1056
  * SDK version from package.json (injected at build time)
1053
1057
  */
1054
- declare const __version__ = "0.17.0";
1058
+ declare const __version__ = "0.18.1";
1055
1059
 
1056
1060
  /**
1057
1061
  * Constants for the Bitfab SDK.
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  flushTraces,
21
21
  getCurrentSpan,
22
22
  getCurrentTrace
23
- } from "./chunk-M6N633CX.js";
23
+ } from "./chunk-ILIUTS5D.js";
24
24
  import {
25
25
  BitfabError
26
26
  } from "./chunk-QT7HWOKU.js";
package/dist/node.cjs CHANGED
@@ -456,7 +456,7 @@ registerAsyncLocalStorageClass(
456
456
  );
457
457
 
458
458
  // src/version.generated.ts
459
- var __version__ = "0.17.0";
459
+ var __version__ = "0.18.1";
460
460
 
461
461
  // src/constants.ts
462
462
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -1686,21 +1686,131 @@ function extractModelName(serialized, metadata) {
1686
1686
  }
1687
1687
  return void 0;
1688
1688
  }
1689
+ function asTokenCount(value) {
1690
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1691
+ }
1692
+ function normalizeTokenUsage(raw) {
1693
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1694
+ return null;
1695
+ }
1696
+ const u = raw;
1697
+ if ("cache_read_input_tokens" in u || "cache_creation_input_tokens" in u) {
1698
+ const cacheRead = asTokenCount(u.cache_read_input_tokens);
1699
+ const cacheCreation = asTokenCount(u.cache_creation_input_tokens);
1700
+ const baseInput = asTokenCount(u.input_tokens);
1701
+ const outputTokens = asTokenCount(u.output_tokens);
1702
+ if (cacheRead === null && cacheCreation === null && baseInput === null && outputTokens === null) {
1703
+ return null;
1704
+ }
1705
+ const inputTokens = (baseInput ?? 0) + (cacheRead ?? 0) + (cacheCreation ?? 0);
1706
+ return {
1707
+ inputTokens,
1708
+ outputTokens,
1709
+ totalTokens: inputTokens + (outputTokens ?? 0),
1710
+ cachedInputTokens: cacheRead
1711
+ };
1712
+ }
1713
+ if ("prompt_tokens" in u || "completion_tokens" in u || "promptTokens" in u || "completionTokens" in u) {
1714
+ const promptDetails = u.prompt_tokens_details ?? {};
1715
+ return withAnyTokenCount({
1716
+ inputTokens: asTokenCount(u.prompt_tokens) ?? asTokenCount(u.promptTokens),
1717
+ outputTokens: asTokenCount(u.completion_tokens) ?? asTokenCount(u.completionTokens),
1718
+ totalTokens: asTokenCount(u.total_tokens) ?? asTokenCount(u.totalTokens),
1719
+ cachedInputTokens: asTokenCount(promptDetails.cached_tokens)
1720
+ });
1721
+ }
1722
+ if ("prompt_token_count" in u || "candidates_token_count" in u) {
1723
+ return withAnyTokenCount({
1724
+ inputTokens: asTokenCount(u.prompt_token_count),
1725
+ outputTokens: asTokenCount(u.candidates_token_count),
1726
+ totalTokens: asTokenCount(u.total_token_count),
1727
+ cachedInputTokens: asTokenCount(u.cached_content_token_count)
1728
+ });
1729
+ }
1730
+ if ("input_tokens" in u || "output_tokens" in u) {
1731
+ const inputDetails = u.input_token_details ?? {};
1732
+ const inputTokens = asTokenCount(u.input_tokens);
1733
+ const outputTokens = asTokenCount(u.output_tokens);
1734
+ let totalTokens = asTokenCount(u.total_tokens);
1735
+ if (totalTokens === null && inputTokens !== null && outputTokens !== null) {
1736
+ totalTokens = inputTokens + outputTokens;
1737
+ }
1738
+ return withAnyTokenCount({
1739
+ inputTokens,
1740
+ outputTokens,
1741
+ totalTokens,
1742
+ cachedInputTokens: asTokenCount(inputDetails.cache_read)
1743
+ });
1744
+ }
1745
+ return null;
1746
+ }
1747
+ function withAnyTokenCount(usage) {
1748
+ const hasCount = usage.inputTokens !== null || usage.outputTokens !== null || usage.totalTokens !== null || usage.cachedInputTokens !== null;
1749
+ return hasCount ? usage : null;
1750
+ }
1751
+ function addUsage(totals, usage) {
1752
+ for (const key of [
1753
+ "inputTokens",
1754
+ "outputTokens",
1755
+ "totalTokens",
1756
+ "cachedInputTokens"
1757
+ ]) {
1758
+ const value = usage[key];
1759
+ if (value !== null) {
1760
+ totals[key] = (totals[key] ?? 0) + value;
1761
+ }
1762
+ }
1763
+ }
1764
+ function usageFromGenerations(generations) {
1765
+ if (!generations?.length) {
1766
+ return null;
1767
+ }
1768
+ const totals = {
1769
+ inputTokens: null,
1770
+ outputTokens: null,
1771
+ totalTokens: null,
1772
+ cachedInputTokens: null
1773
+ };
1774
+ let found = false;
1775
+ for (const batch of generations) {
1776
+ if (!Array.isArray(batch)) {
1777
+ continue;
1778
+ }
1779
+ for (const gen of batch) {
1780
+ const msg = gen?.message;
1781
+ if (!msg || typeof msg !== "object") {
1782
+ continue;
1783
+ }
1784
+ const responseMetadata = msg.response_metadata;
1785
+ const usage = normalizeTokenUsage(msg.usage_metadata) ?? normalizeTokenUsage(responseMetadata?.token_usage) ?? normalizeTokenUsage(responseMetadata?.usage) ?? normalizeTokenUsage(responseMetadata?.tokenUsage);
1786
+ if (!usage) {
1787
+ continue;
1788
+ }
1789
+ found = true;
1790
+ addUsage(totals, usage);
1791
+ }
1792
+ }
1793
+ return found ? totals : null;
1794
+ }
1689
1795
  function extractUsage2(output) {
1796
+ const generations = output.generations;
1797
+ const llmOutput = output.llmOutput ?? output.llm_output;
1798
+ const normalized = usageFromGenerations(generations) ?? normalizeTokenUsage(llmOutput?.tokenUsage) ?? normalizeTokenUsage(llmOutput?.token_usage) ?? normalizeTokenUsage(llmOutput?.usage);
1690
1799
  const usage = {};
1691
- const llmOutput = output.llmOutput;
1692
- const tokenUsage = llmOutput?.tokenUsage ?? llmOutput?.token_usage ?? llmOutput?.usage ?? {};
1693
- const inputTokens = tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? tokenUsage.input_tokens;
1694
- const outputTokens = tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? tokenUsage.output_tokens;
1695
- const totalTokens = tokenUsage.totalTokens ?? tokenUsage.total_tokens;
1696
- if (inputTokens !== void 0 && inputTokens !== null) {
1697
- usage.inputTokens = inputTokens;
1800
+ if (!normalized) {
1801
+ return usage;
1802
+ }
1803
+ if (normalized.inputTokens !== null) {
1804
+ usage.inputTokens = normalized.inputTokens;
1805
+ }
1806
+ if (normalized.outputTokens !== null) {
1807
+ usage.outputTokens = normalized.outputTokens;
1698
1808
  }
1699
- if (outputTokens !== void 0 && outputTokens !== null) {
1700
- usage.outputTokens = outputTokens;
1809
+ if (normalized.totalTokens !== null) {
1810
+ usage.totalTokens = normalized.totalTokens;
1701
1811
  }
1702
- if (totalTokens !== void 0 && totalTokens !== null) {
1703
- usage.totalTokens = totalTokens;
1812
+ if (normalized.cachedInputTokens !== null) {
1813
+ usage.cachedInputTokens = normalized.cachedInputTokens;
1704
1814
  }
1705
1815
  return usage;
1706
1816
  }
@@ -3088,6 +3198,9 @@ var Bitfab = class {
3088
3198
  };
3089
3199
  return runWithSpanStack(newStack, executeWithContext);
3090
3200
  };
3201
+ Object.defineProperty(wrappedFn, "_bitfabTraceFunctionKey", {
3202
+ value: traceFunctionKey
3203
+ });
3091
3204
  return wrappedFn;
3092
3205
  }
3093
3206
  /**
@@ -3259,23 +3372,40 @@ var Bitfab = class {
3259
3372
  * Fetches the last N traces for the given trace function key, re-runs each
3260
3373
  * through the provided function, and returns comparison data.
3261
3374
  *
3262
- * The function must have been wrapped with `withSpan` replay injects
3263
- * `testRunId` via async context so new spans are linked to the test run.
3375
+ * Accepts either a `withSpan`-wrapped function (under the same key) or any
3376
+ * plain callable: plain callables are wrapped internally so each replayed
3377
+ * invocation records a trace tied to the test run. The plain-callable form
3378
+ * is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
3379
+ * SDK) replay — those record traces under a key with no `withSpan`-wrapped
3380
+ * root in the app.
3264
3381
  *
3265
3382
  * @param traceFunctionKey - The trace function key to replay
3266
- * @param fn - The function to replay (must be the return value of `withSpan`)
3383
+ * @param fn - The function to run recorded inputs through
3267
3384
  * @param options - Optional replay options. When `traceIds` is passed,
3268
3385
  * `limit` is ignored (with a warning): an explicit ID list already
3269
3386
  * determines how many traces replay.
3270
3387
  * @returns ReplayResult with items, testRunId, and testRunUrl
3271
3388
  */
3272
3389
  async replay(traceFunctionKey, fn, options) {
3390
+ const wrappedKey = fn._bitfabTraceFunctionKey;
3391
+ let replayFn = fn;
3392
+ if (wrappedKey === void 0) {
3393
+ replayFn = this.withSpan(
3394
+ traceFunctionKey,
3395
+ { name: fn.name || "Replay", type: "agent" },
3396
+ fn
3397
+ );
3398
+ } else if (wrappedKey !== traceFunctionKey) {
3399
+ throw new BitfabError(
3400
+ `Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
3401
+ );
3402
+ }
3273
3403
  const { replay: doReplay } = await Promise.resolve().then(() => (init_replay(), replay_exports));
3274
3404
  return doReplay(
3275
3405
  this.httpClient,
3276
3406
  this.serviceUrl,
3277
3407
  traceFunctionKey,
3278
- fn,
3408
+ replayFn,
3279
3409
  options
3280
3410
  );
3281
3411
  }