@bitfab/sdk 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.cjs CHANGED
@@ -433,7 +433,7 @@ __export(index_exports, {
433
433
  module.exports = __toCommonJS(index_exports);
434
434
 
435
435
  // src/version.generated.ts
436
- var __version__ = "0.17.0";
436
+ var __version__ = "0.18.1";
437
437
 
438
438
  // src/constants.ts
439
439
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -1663,21 +1663,131 @@ function extractModelName(serialized, metadata) {
1663
1663
  }
1664
1664
  return void 0;
1665
1665
  }
1666
+ function asTokenCount(value) {
1667
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1668
+ }
1669
+ function normalizeTokenUsage(raw) {
1670
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1671
+ return null;
1672
+ }
1673
+ const u = raw;
1674
+ if ("cache_read_input_tokens" in u || "cache_creation_input_tokens" in u) {
1675
+ const cacheRead = asTokenCount(u.cache_read_input_tokens);
1676
+ const cacheCreation = asTokenCount(u.cache_creation_input_tokens);
1677
+ const baseInput = asTokenCount(u.input_tokens);
1678
+ const outputTokens = asTokenCount(u.output_tokens);
1679
+ if (cacheRead === null && cacheCreation === null && baseInput === null && outputTokens === null) {
1680
+ return null;
1681
+ }
1682
+ const inputTokens = (baseInput ?? 0) + (cacheRead ?? 0) + (cacheCreation ?? 0);
1683
+ return {
1684
+ inputTokens,
1685
+ outputTokens,
1686
+ totalTokens: inputTokens + (outputTokens ?? 0),
1687
+ cachedInputTokens: cacheRead
1688
+ };
1689
+ }
1690
+ if ("prompt_tokens" in u || "completion_tokens" in u || "promptTokens" in u || "completionTokens" in u) {
1691
+ const promptDetails = u.prompt_tokens_details ?? {};
1692
+ return withAnyTokenCount({
1693
+ inputTokens: asTokenCount(u.prompt_tokens) ?? asTokenCount(u.promptTokens),
1694
+ outputTokens: asTokenCount(u.completion_tokens) ?? asTokenCount(u.completionTokens),
1695
+ totalTokens: asTokenCount(u.total_tokens) ?? asTokenCount(u.totalTokens),
1696
+ cachedInputTokens: asTokenCount(promptDetails.cached_tokens)
1697
+ });
1698
+ }
1699
+ if ("prompt_token_count" in u || "candidates_token_count" in u) {
1700
+ return withAnyTokenCount({
1701
+ inputTokens: asTokenCount(u.prompt_token_count),
1702
+ outputTokens: asTokenCount(u.candidates_token_count),
1703
+ totalTokens: asTokenCount(u.total_token_count),
1704
+ cachedInputTokens: asTokenCount(u.cached_content_token_count)
1705
+ });
1706
+ }
1707
+ if ("input_tokens" in u || "output_tokens" in u) {
1708
+ const inputDetails = u.input_token_details ?? {};
1709
+ const inputTokens = asTokenCount(u.input_tokens);
1710
+ const outputTokens = asTokenCount(u.output_tokens);
1711
+ let totalTokens = asTokenCount(u.total_tokens);
1712
+ if (totalTokens === null && inputTokens !== null && outputTokens !== null) {
1713
+ totalTokens = inputTokens + outputTokens;
1714
+ }
1715
+ return withAnyTokenCount({
1716
+ inputTokens,
1717
+ outputTokens,
1718
+ totalTokens,
1719
+ cachedInputTokens: asTokenCount(inputDetails.cache_read)
1720
+ });
1721
+ }
1722
+ return null;
1723
+ }
1724
+ function withAnyTokenCount(usage) {
1725
+ const hasCount = usage.inputTokens !== null || usage.outputTokens !== null || usage.totalTokens !== null || usage.cachedInputTokens !== null;
1726
+ return hasCount ? usage : null;
1727
+ }
1728
+ function addUsage(totals, usage) {
1729
+ for (const key of [
1730
+ "inputTokens",
1731
+ "outputTokens",
1732
+ "totalTokens",
1733
+ "cachedInputTokens"
1734
+ ]) {
1735
+ const value = usage[key];
1736
+ if (value !== null) {
1737
+ totals[key] = (totals[key] ?? 0) + value;
1738
+ }
1739
+ }
1740
+ }
1741
+ function usageFromGenerations(generations) {
1742
+ if (!generations?.length) {
1743
+ return null;
1744
+ }
1745
+ const totals = {
1746
+ inputTokens: null,
1747
+ outputTokens: null,
1748
+ totalTokens: null,
1749
+ cachedInputTokens: null
1750
+ };
1751
+ let found = false;
1752
+ for (const batch of generations) {
1753
+ if (!Array.isArray(batch)) {
1754
+ continue;
1755
+ }
1756
+ for (const gen of batch) {
1757
+ const msg = gen?.message;
1758
+ if (!msg || typeof msg !== "object") {
1759
+ continue;
1760
+ }
1761
+ const responseMetadata = msg.response_metadata;
1762
+ const usage = normalizeTokenUsage(msg.usage_metadata) ?? normalizeTokenUsage(responseMetadata?.token_usage) ?? normalizeTokenUsage(responseMetadata?.usage) ?? normalizeTokenUsage(responseMetadata?.tokenUsage);
1763
+ if (!usage) {
1764
+ continue;
1765
+ }
1766
+ found = true;
1767
+ addUsage(totals, usage);
1768
+ }
1769
+ }
1770
+ return found ? totals : null;
1771
+ }
1666
1772
  function extractUsage2(output) {
1773
+ const generations = output.generations;
1774
+ const llmOutput = output.llmOutput ?? output.llm_output;
1775
+ const normalized = usageFromGenerations(generations) ?? normalizeTokenUsage(llmOutput?.tokenUsage) ?? normalizeTokenUsage(llmOutput?.token_usage) ?? normalizeTokenUsage(llmOutput?.usage);
1667
1776
  const usage = {};
1668
- const llmOutput = output.llmOutput;
1669
- const tokenUsage = llmOutput?.tokenUsage ?? llmOutput?.token_usage ?? llmOutput?.usage ?? {};
1670
- const inputTokens = tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? tokenUsage.input_tokens;
1671
- const outputTokens = tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? tokenUsage.output_tokens;
1672
- const totalTokens = tokenUsage.totalTokens ?? tokenUsage.total_tokens;
1673
- if (inputTokens !== void 0 && inputTokens !== null) {
1674
- usage.inputTokens = inputTokens;
1777
+ if (!normalized) {
1778
+ return usage;
1779
+ }
1780
+ if (normalized.inputTokens !== null) {
1781
+ usage.inputTokens = normalized.inputTokens;
1782
+ }
1783
+ if (normalized.outputTokens !== null) {
1784
+ usage.outputTokens = normalized.outputTokens;
1675
1785
  }
1676
- if (outputTokens !== void 0 && outputTokens !== null) {
1677
- usage.outputTokens = outputTokens;
1786
+ if (normalized.totalTokens !== null) {
1787
+ usage.totalTokens = normalized.totalTokens;
1678
1788
  }
1679
- if (totalTokens !== void 0 && totalTokens !== null) {
1680
- usage.totalTokens = totalTokens;
1789
+ if (normalized.cachedInputTokens !== null) {
1790
+ usage.cachedInputTokens = normalized.cachedInputTokens;
1681
1791
  }
1682
1792
  return usage;
1683
1793
  }
@@ -3065,6 +3175,9 @@ var Bitfab = class {
3065
3175
  };
3066
3176
  return runWithSpanStack(newStack, executeWithContext);
3067
3177
  };
3178
+ Object.defineProperty(wrappedFn, "_bitfabTraceFunctionKey", {
3179
+ value: traceFunctionKey
3180
+ });
3068
3181
  return wrappedFn;
3069
3182
  }
3070
3183
  /**
@@ -3236,23 +3349,40 @@ var Bitfab = class {
3236
3349
  * Fetches the last N traces for the given trace function key, re-runs each
3237
3350
  * through the provided function, and returns comparison data.
3238
3351
  *
3239
- * The function must have been wrapped with `withSpan` replay injects
3240
- * `testRunId` via async context so new spans are linked to the test run.
3352
+ * Accepts either a `withSpan`-wrapped function (under the same key) or any
3353
+ * plain callable: plain callables are wrapped internally so each replayed
3354
+ * invocation records a trace tied to the test run. The plain-callable form
3355
+ * is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
3356
+ * SDK) replay — those record traces under a key with no `withSpan`-wrapped
3357
+ * root in the app.
3241
3358
  *
3242
3359
  * @param traceFunctionKey - The trace function key to replay
3243
- * @param fn - The function to replay (must be the return value of `withSpan`)
3360
+ * @param fn - The function to run recorded inputs through
3244
3361
  * @param options - Optional replay options. When `traceIds` is passed,
3245
3362
  * `limit` is ignored (with a warning): an explicit ID list already
3246
3363
  * determines how many traces replay.
3247
3364
  * @returns ReplayResult with items, testRunId, and testRunUrl
3248
3365
  */
3249
3366
  async replay(traceFunctionKey, fn, options) {
3367
+ const wrappedKey = fn._bitfabTraceFunctionKey;
3368
+ let replayFn = fn;
3369
+ if (wrappedKey === void 0) {
3370
+ replayFn = this.withSpan(
3371
+ traceFunctionKey,
3372
+ { name: fn.name || "Replay", type: "agent" },
3373
+ fn
3374
+ );
3375
+ } else if (wrappedKey !== traceFunctionKey) {
3376
+ throw new BitfabError(
3377
+ `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.`
3378
+ );
3379
+ }
3250
3380
  const { replay: doReplay } = await Promise.resolve().then(() => (init_replay(), replay_exports));
3251
3381
  return doReplay(
3252
3382
  this.httpClient,
3253
3383
  this.serviceUrl,
3254
3384
  traceFunctionKey,
3255
- fn,
3385
+ replayFn,
3256
3386
  options
3257
3387
  );
3258
3388
  }