bitfab 0.18.0 → 0.18.2

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
@@ -442,7 +442,7 @@ __export(index_exports, {
442
442
  module.exports = __toCommonJS(index_exports);
443
443
 
444
444
  // src/version.generated.ts
445
- var __version__ = "0.18.0";
445
+ var __version__ = "0.18.2";
446
446
 
447
447
  // src/constants.ts
448
448
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -1672,21 +1672,131 @@ function extractModelName(serialized, metadata) {
1672
1672
  }
1673
1673
  return void 0;
1674
1674
  }
1675
+ function asTokenCount(value) {
1676
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1677
+ }
1678
+ function normalizeTokenUsage(raw) {
1679
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1680
+ return null;
1681
+ }
1682
+ const u = raw;
1683
+ if ("cache_read_input_tokens" in u || "cache_creation_input_tokens" in u) {
1684
+ const cacheRead = asTokenCount(u.cache_read_input_tokens);
1685
+ const cacheCreation = asTokenCount(u.cache_creation_input_tokens);
1686
+ const baseInput = asTokenCount(u.input_tokens);
1687
+ const outputTokens = asTokenCount(u.output_tokens);
1688
+ if (cacheRead === null && cacheCreation === null && baseInput === null && outputTokens === null) {
1689
+ return null;
1690
+ }
1691
+ const inputTokens = (baseInput ?? 0) + (cacheRead ?? 0) + (cacheCreation ?? 0);
1692
+ return {
1693
+ inputTokens,
1694
+ outputTokens,
1695
+ totalTokens: inputTokens + (outputTokens ?? 0),
1696
+ cachedInputTokens: cacheRead
1697
+ };
1698
+ }
1699
+ if ("prompt_tokens" in u || "completion_tokens" in u || "promptTokens" in u || "completionTokens" in u) {
1700
+ const promptDetails = u.prompt_tokens_details ?? {};
1701
+ return withAnyTokenCount({
1702
+ inputTokens: asTokenCount(u.prompt_tokens) ?? asTokenCount(u.promptTokens),
1703
+ outputTokens: asTokenCount(u.completion_tokens) ?? asTokenCount(u.completionTokens),
1704
+ totalTokens: asTokenCount(u.total_tokens) ?? asTokenCount(u.totalTokens),
1705
+ cachedInputTokens: asTokenCount(promptDetails.cached_tokens)
1706
+ });
1707
+ }
1708
+ if ("prompt_token_count" in u || "candidates_token_count" in u) {
1709
+ return withAnyTokenCount({
1710
+ inputTokens: asTokenCount(u.prompt_token_count),
1711
+ outputTokens: asTokenCount(u.candidates_token_count),
1712
+ totalTokens: asTokenCount(u.total_token_count),
1713
+ cachedInputTokens: asTokenCount(u.cached_content_token_count)
1714
+ });
1715
+ }
1716
+ if ("input_tokens" in u || "output_tokens" in u) {
1717
+ const inputDetails = u.input_token_details ?? {};
1718
+ const inputTokens = asTokenCount(u.input_tokens);
1719
+ const outputTokens = asTokenCount(u.output_tokens);
1720
+ let totalTokens = asTokenCount(u.total_tokens);
1721
+ if (totalTokens === null && inputTokens !== null && outputTokens !== null) {
1722
+ totalTokens = inputTokens + outputTokens;
1723
+ }
1724
+ return withAnyTokenCount({
1725
+ inputTokens,
1726
+ outputTokens,
1727
+ totalTokens,
1728
+ cachedInputTokens: asTokenCount(inputDetails.cache_read)
1729
+ });
1730
+ }
1731
+ return null;
1732
+ }
1733
+ function withAnyTokenCount(usage) {
1734
+ const hasCount = usage.inputTokens !== null || usage.outputTokens !== null || usage.totalTokens !== null || usage.cachedInputTokens !== null;
1735
+ return hasCount ? usage : null;
1736
+ }
1737
+ function addUsage(totals, usage) {
1738
+ for (const key of [
1739
+ "inputTokens",
1740
+ "outputTokens",
1741
+ "totalTokens",
1742
+ "cachedInputTokens"
1743
+ ]) {
1744
+ const value = usage[key];
1745
+ if (value !== null) {
1746
+ totals[key] = (totals[key] ?? 0) + value;
1747
+ }
1748
+ }
1749
+ }
1750
+ function usageFromGenerations(generations) {
1751
+ if (!generations?.length) {
1752
+ return null;
1753
+ }
1754
+ const totals = {
1755
+ inputTokens: null,
1756
+ outputTokens: null,
1757
+ totalTokens: null,
1758
+ cachedInputTokens: null
1759
+ };
1760
+ let found = false;
1761
+ for (const batch of generations) {
1762
+ if (!Array.isArray(batch)) {
1763
+ continue;
1764
+ }
1765
+ for (const gen of batch) {
1766
+ const msg = gen?.message;
1767
+ if (!msg || typeof msg !== "object") {
1768
+ continue;
1769
+ }
1770
+ const responseMetadata = msg.response_metadata;
1771
+ const usage = normalizeTokenUsage(msg.usage_metadata) ?? normalizeTokenUsage(responseMetadata?.token_usage) ?? normalizeTokenUsage(responseMetadata?.usage) ?? normalizeTokenUsage(responseMetadata?.tokenUsage);
1772
+ if (!usage) {
1773
+ continue;
1774
+ }
1775
+ found = true;
1776
+ addUsage(totals, usage);
1777
+ }
1778
+ }
1779
+ return found ? totals : null;
1780
+ }
1675
1781
  function extractUsage2(output) {
1782
+ const generations = output.generations;
1783
+ const llmOutput = output.llmOutput ?? output.llm_output;
1784
+ const normalized = usageFromGenerations(generations) ?? normalizeTokenUsage(llmOutput?.tokenUsage) ?? normalizeTokenUsage(llmOutput?.token_usage) ?? normalizeTokenUsage(llmOutput?.usage);
1676
1785
  const usage = {};
1677
- const llmOutput = output.llmOutput;
1678
- const tokenUsage = llmOutput?.tokenUsage ?? llmOutput?.token_usage ?? llmOutput?.usage ?? {};
1679
- const inputTokens = tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? tokenUsage.input_tokens;
1680
- const outputTokens = tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? tokenUsage.output_tokens;
1681
- const totalTokens = tokenUsage.totalTokens ?? tokenUsage.total_tokens;
1682
- if (inputTokens !== void 0 && inputTokens !== null) {
1683
- usage.inputTokens = inputTokens;
1786
+ if (!normalized) {
1787
+ return usage;
1788
+ }
1789
+ if (normalized.inputTokens !== null) {
1790
+ usage.inputTokens = normalized.inputTokens;
1684
1791
  }
1685
- if (outputTokens !== void 0 && outputTokens !== null) {
1686
- usage.outputTokens = outputTokens;
1792
+ if (normalized.outputTokens !== null) {
1793
+ usage.outputTokens = normalized.outputTokens;
1687
1794
  }
1688
- if (totalTokens !== void 0 && totalTokens !== null) {
1689
- usage.totalTokens = totalTokens;
1795
+ if (normalized.totalTokens !== null) {
1796
+ usage.totalTokens = normalized.totalTokens;
1797
+ }
1798
+ if (normalized.cachedInputTokens !== null) {
1799
+ usage.cachedInputTokens = normalized.cachedInputTokens;
1690
1800
  }
1691
1801
  return usage;
1692
1802
  }
@@ -2047,7 +2157,9 @@ var ReplayEnvironment = class {
2047
2157
  * Throws if read outside a replay item.
2048
2158
  */
2049
2159
  get databaseUrl() {
2050
- return this.require().databaseUrl;
2160
+ const snapshot = this.require();
2161
+ this.markAccessed();
2162
+ return snapshot.databaseUrl;
2051
2163
  }
2052
2164
  /** When the per-trace branch URL stops being valid. ISO-8601. */
2053
2165
  get expiresAt() {
@@ -2075,7 +2187,24 @@ var ReplayEnvironment = class {
2075
2187
  }
2076
2188
  /** Non-throwing variant for callers that handle the inactive case. */
2077
2189
  snapshot() {
2078
- return this.read();
2190
+ const snapshot = this.read();
2191
+ if (snapshot) {
2192
+ this.markAccessed();
2193
+ }
2194
+ return snapshot;
2195
+ }
2196
+ /**
2197
+ * Record on the replay context that customer code obtained the branch
2198
+ * URL. Only `databaseUrl` and `snapshot()` count — `active`, `readOnly`
2199
+ * and friends inspect the lease without exposing the connection string,
2200
+ * so they don't prove the replayed code could have connected to the
2201
+ * branch.
2202
+ */
2203
+ markAccessed() {
2204
+ const ctx = getReplayContext();
2205
+ if (ctx?.dbBranchLease) {
2206
+ ctx.dbSnapshotAccessed = true;
2207
+ }
2079
2208
  }
2080
2209
  read() {
2081
2210
  const ctx = getReplayContext();
@@ -3007,7 +3136,19 @@ var Bitfab = class {
3007
3136
  contexts: traceState?.contexts ?? [],
3008
3137
  testRunId: traceState?.testRunId,
3009
3138
  inputSourceTraceId: traceState?.inputSourceTraceId,
3010
- dbSnapshotRef: traceState?.dbSnapshotRef
3139
+ dbSnapshotRef: traceState?.dbSnapshotRef,
3140
+ // Built AFTER the wrapped fn finished, so `accessed` reflects
3141
+ // whether customer code obtained the branch URL during this
3142
+ // item. Omitted entirely when no lease was attached, so the
3143
+ // server can distinguish "no branch" from "branch ignored".
3144
+ ...replayCtx?.dbBranchLease && {
3145
+ dbSnapshotUsage: {
3146
+ neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3147
+ snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3148
+ sourceTraceId: replayCtx.sourceBitfabTraceId,
3149
+ accessed: replayCtx.dbSnapshotAccessed === true
3150
+ }
3151
+ }
3011
3152
  });
3012
3153
  activeTraceStates.delete(traceId);
3013
3154
  if (persistenceCollector) {
@@ -3179,6 +3320,18 @@ var Bitfab = class {
3179
3320
  if (params.dbSnapshotRef) {
3180
3321
  rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3181
3322
  }
3323
+ if (params.dbSnapshotUsage) {
3324
+ rawTrace.db_snapshot_usage = {
3325
+ neon_branch_id: params.dbSnapshotUsage.neonBranchId,
3326
+ ...params.dbSnapshotUsage.snapshotTimestamp && {
3327
+ snapshot_timestamp: params.dbSnapshotUsage.snapshotTimestamp
3328
+ },
3329
+ ...params.dbSnapshotUsage.sourceTraceId && {
3330
+ source_trace_id: params.dbSnapshotUsage.sourceTraceId
3331
+ },
3332
+ accessed: params.dbSnapshotUsage.accessed
3333
+ };
3334
+ }
3182
3335
  return this.httpClient.sendExternalTrace({
3183
3336
  type: "sdk-function",
3184
3337
  source: "typescript-sdk-function",