@bitfab/sdk 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
@@ -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.18.0";
436
+ var __version__ = "0.18.2";
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;
1675
1782
  }
1676
- if (outputTokens !== void 0 && outputTokens !== null) {
1677
- usage.outputTokens = outputTokens;
1783
+ if (normalized.outputTokens !== null) {
1784
+ usage.outputTokens = normalized.outputTokens;
1678
1785
  }
1679
- if (totalTokens !== void 0 && totalTokens !== null) {
1680
- usage.totalTokens = totalTokens;
1786
+ if (normalized.totalTokens !== null) {
1787
+ usage.totalTokens = normalized.totalTokens;
1788
+ }
1789
+ if (normalized.cachedInputTokens !== null) {
1790
+ usage.cachedInputTokens = normalized.cachedInputTokens;
1681
1791
  }
1682
1792
  return usage;
1683
1793
  }
@@ -2038,7 +2148,9 @@ var ReplayEnvironment = class {
2038
2148
  * Throws if read outside a replay item.
2039
2149
  */
2040
2150
  get databaseUrl() {
2041
- return this.require().databaseUrl;
2151
+ const snapshot = this.require();
2152
+ this.markAccessed();
2153
+ return snapshot.databaseUrl;
2042
2154
  }
2043
2155
  /** When the per-trace branch URL stops being valid. ISO-8601. */
2044
2156
  get expiresAt() {
@@ -2066,7 +2178,24 @@ var ReplayEnvironment = class {
2066
2178
  }
2067
2179
  /** Non-throwing variant for callers that handle the inactive case. */
2068
2180
  snapshot() {
2069
- return this.read();
2181
+ const snapshot = this.read();
2182
+ if (snapshot) {
2183
+ this.markAccessed();
2184
+ }
2185
+ return snapshot;
2186
+ }
2187
+ /**
2188
+ * Record on the replay context that customer code obtained the branch
2189
+ * URL. Only `databaseUrl` and `snapshot()` count — `active`, `readOnly`
2190
+ * and friends inspect the lease without exposing the connection string,
2191
+ * so they don't prove the replayed code could have connected to the
2192
+ * branch.
2193
+ */
2194
+ markAccessed() {
2195
+ const ctx = getReplayContext();
2196
+ if (ctx?.dbBranchLease) {
2197
+ ctx.dbSnapshotAccessed = true;
2198
+ }
2070
2199
  }
2071
2200
  read() {
2072
2201
  const ctx = getReplayContext();
@@ -2998,7 +3127,19 @@ var Bitfab = class {
2998
3127
  contexts: traceState?.contexts ?? [],
2999
3128
  testRunId: traceState?.testRunId,
3000
3129
  inputSourceTraceId: traceState?.inputSourceTraceId,
3001
- dbSnapshotRef: traceState?.dbSnapshotRef
3130
+ dbSnapshotRef: traceState?.dbSnapshotRef,
3131
+ // Built AFTER the wrapped fn finished, so `accessed` reflects
3132
+ // whether customer code obtained the branch URL during this
3133
+ // item. Omitted entirely when no lease was attached, so the
3134
+ // server can distinguish "no branch" from "branch ignored".
3135
+ ...replayCtx?.dbBranchLease && {
3136
+ dbSnapshotUsage: {
3137
+ neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3138
+ snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3139
+ sourceTraceId: replayCtx.sourceBitfabTraceId,
3140
+ accessed: replayCtx.dbSnapshotAccessed === true
3141
+ }
3142
+ }
3002
3143
  });
3003
3144
  activeTraceStates.delete(traceId);
3004
3145
  if (persistenceCollector) {
@@ -3170,6 +3311,18 @@ var Bitfab = class {
3170
3311
  if (params.dbSnapshotRef) {
3171
3312
  rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3172
3313
  }
3314
+ if (params.dbSnapshotUsage) {
3315
+ rawTrace.db_snapshot_usage = {
3316
+ neon_branch_id: params.dbSnapshotUsage.neonBranchId,
3317
+ ...params.dbSnapshotUsage.snapshotTimestamp && {
3318
+ snapshot_timestamp: params.dbSnapshotUsage.snapshotTimestamp
3319
+ },
3320
+ ...params.dbSnapshotUsage.sourceTraceId && {
3321
+ source_trace_id: params.dbSnapshotUsage.sourceTraceId
3322
+ },
3323
+ accessed: params.dbSnapshotUsage.accessed
3324
+ };
3325
+ }
3173
3326
  return this.httpClient.sendExternalTrace({
3174
3327
  type: "sdk-function",
3175
3328
  source: "typescript-sdk-function",