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.
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-QT7HWOKU.js";
10
10
 
11
11
  // src/version.generated.ts
12
- var __version__ = "0.17.0";
12
+ var __version__ = "0.18.1";
13
13
 
14
14
  // src/constants.ts
15
15
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -1234,21 +1234,131 @@ function extractModelName(serialized, metadata) {
1234
1234
  }
1235
1235
  return void 0;
1236
1236
  }
1237
+ function asTokenCount(value) {
1238
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1239
+ }
1240
+ function normalizeTokenUsage(raw) {
1241
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1242
+ return null;
1243
+ }
1244
+ const u = raw;
1245
+ if ("cache_read_input_tokens" in u || "cache_creation_input_tokens" in u) {
1246
+ const cacheRead = asTokenCount(u.cache_read_input_tokens);
1247
+ const cacheCreation = asTokenCount(u.cache_creation_input_tokens);
1248
+ const baseInput = asTokenCount(u.input_tokens);
1249
+ const outputTokens = asTokenCount(u.output_tokens);
1250
+ if (cacheRead === null && cacheCreation === null && baseInput === null && outputTokens === null) {
1251
+ return null;
1252
+ }
1253
+ const inputTokens = (baseInput ?? 0) + (cacheRead ?? 0) + (cacheCreation ?? 0);
1254
+ return {
1255
+ inputTokens,
1256
+ outputTokens,
1257
+ totalTokens: inputTokens + (outputTokens ?? 0),
1258
+ cachedInputTokens: cacheRead
1259
+ };
1260
+ }
1261
+ if ("prompt_tokens" in u || "completion_tokens" in u || "promptTokens" in u || "completionTokens" in u) {
1262
+ const promptDetails = u.prompt_tokens_details ?? {};
1263
+ return withAnyTokenCount({
1264
+ inputTokens: asTokenCount(u.prompt_tokens) ?? asTokenCount(u.promptTokens),
1265
+ outputTokens: asTokenCount(u.completion_tokens) ?? asTokenCount(u.completionTokens),
1266
+ totalTokens: asTokenCount(u.total_tokens) ?? asTokenCount(u.totalTokens),
1267
+ cachedInputTokens: asTokenCount(promptDetails.cached_tokens)
1268
+ });
1269
+ }
1270
+ if ("prompt_token_count" in u || "candidates_token_count" in u) {
1271
+ return withAnyTokenCount({
1272
+ inputTokens: asTokenCount(u.prompt_token_count),
1273
+ outputTokens: asTokenCount(u.candidates_token_count),
1274
+ totalTokens: asTokenCount(u.total_token_count),
1275
+ cachedInputTokens: asTokenCount(u.cached_content_token_count)
1276
+ });
1277
+ }
1278
+ if ("input_tokens" in u || "output_tokens" in u) {
1279
+ const inputDetails = u.input_token_details ?? {};
1280
+ const inputTokens = asTokenCount(u.input_tokens);
1281
+ const outputTokens = asTokenCount(u.output_tokens);
1282
+ let totalTokens = asTokenCount(u.total_tokens);
1283
+ if (totalTokens === null && inputTokens !== null && outputTokens !== null) {
1284
+ totalTokens = inputTokens + outputTokens;
1285
+ }
1286
+ return withAnyTokenCount({
1287
+ inputTokens,
1288
+ outputTokens,
1289
+ totalTokens,
1290
+ cachedInputTokens: asTokenCount(inputDetails.cache_read)
1291
+ });
1292
+ }
1293
+ return null;
1294
+ }
1295
+ function withAnyTokenCount(usage) {
1296
+ const hasCount = usage.inputTokens !== null || usage.outputTokens !== null || usage.totalTokens !== null || usage.cachedInputTokens !== null;
1297
+ return hasCount ? usage : null;
1298
+ }
1299
+ function addUsage(totals, usage) {
1300
+ for (const key of [
1301
+ "inputTokens",
1302
+ "outputTokens",
1303
+ "totalTokens",
1304
+ "cachedInputTokens"
1305
+ ]) {
1306
+ const value = usage[key];
1307
+ if (value !== null) {
1308
+ totals[key] = (totals[key] ?? 0) + value;
1309
+ }
1310
+ }
1311
+ }
1312
+ function usageFromGenerations(generations) {
1313
+ if (!generations?.length) {
1314
+ return null;
1315
+ }
1316
+ const totals = {
1317
+ inputTokens: null,
1318
+ outputTokens: null,
1319
+ totalTokens: null,
1320
+ cachedInputTokens: null
1321
+ };
1322
+ let found = false;
1323
+ for (const batch of generations) {
1324
+ if (!Array.isArray(batch)) {
1325
+ continue;
1326
+ }
1327
+ for (const gen of batch) {
1328
+ const msg = gen?.message;
1329
+ if (!msg || typeof msg !== "object") {
1330
+ continue;
1331
+ }
1332
+ const responseMetadata = msg.response_metadata;
1333
+ const usage = normalizeTokenUsage(msg.usage_metadata) ?? normalizeTokenUsage(responseMetadata?.token_usage) ?? normalizeTokenUsage(responseMetadata?.usage) ?? normalizeTokenUsage(responseMetadata?.tokenUsage);
1334
+ if (!usage) {
1335
+ continue;
1336
+ }
1337
+ found = true;
1338
+ addUsage(totals, usage);
1339
+ }
1340
+ }
1341
+ return found ? totals : null;
1342
+ }
1237
1343
  function extractUsage2(output) {
1344
+ const generations = output.generations;
1345
+ const llmOutput = output.llmOutput ?? output.llm_output;
1346
+ const normalized = usageFromGenerations(generations) ?? normalizeTokenUsage(llmOutput?.tokenUsage) ?? normalizeTokenUsage(llmOutput?.token_usage) ?? normalizeTokenUsage(llmOutput?.usage);
1238
1347
  const usage = {};
1239
- const llmOutput = output.llmOutput;
1240
- const tokenUsage = llmOutput?.tokenUsage ?? llmOutput?.token_usage ?? llmOutput?.usage ?? {};
1241
- const inputTokens = tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? tokenUsage.input_tokens;
1242
- const outputTokens = tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? tokenUsage.output_tokens;
1243
- const totalTokens = tokenUsage.totalTokens ?? tokenUsage.total_tokens;
1244
- if (inputTokens !== void 0 && inputTokens !== null) {
1245
- usage.inputTokens = inputTokens;
1348
+ if (!normalized) {
1349
+ return usage;
1350
+ }
1351
+ if (normalized.inputTokens !== null) {
1352
+ usage.inputTokens = normalized.inputTokens;
1353
+ }
1354
+ if (normalized.outputTokens !== null) {
1355
+ usage.outputTokens = normalized.outputTokens;
1246
1356
  }
1247
- if (outputTokens !== void 0 && outputTokens !== null) {
1248
- usage.outputTokens = outputTokens;
1357
+ if (normalized.totalTokens !== null) {
1358
+ usage.totalTokens = normalized.totalTokens;
1249
1359
  }
1250
- if (totalTokens !== void 0 && totalTokens !== null) {
1251
- usage.totalTokens = totalTokens;
1360
+ if (normalized.cachedInputTokens !== null) {
1361
+ usage.cachedInputTokens = normalized.cachedInputTokens;
1252
1362
  }
1253
1363
  return usage;
1254
1364
  }
@@ -2629,6 +2739,9 @@ var Bitfab = class {
2629
2739
  };
2630
2740
  return runWithSpanStack(newStack, executeWithContext);
2631
2741
  };
2742
+ Object.defineProperty(wrappedFn, "_bitfabTraceFunctionKey", {
2743
+ value: traceFunctionKey
2744
+ });
2632
2745
  return wrappedFn;
2633
2746
  }
2634
2747
  /**
@@ -2800,23 +2913,40 @@ var Bitfab = class {
2800
2913
  * Fetches the last N traces for the given trace function key, re-runs each
2801
2914
  * through the provided function, and returns comparison data.
2802
2915
  *
2803
- * The function must have been wrapped with `withSpan` replay injects
2804
- * `testRunId` via async context so new spans are linked to the test run.
2916
+ * Accepts either a `withSpan`-wrapped function (under the same key) or any
2917
+ * plain callable: plain callables are wrapped internally so each replayed
2918
+ * invocation records a trace tied to the test run. The plain-callable form
2919
+ * is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
2920
+ * SDK) replay — those record traces under a key with no `withSpan`-wrapped
2921
+ * root in the app.
2805
2922
  *
2806
2923
  * @param traceFunctionKey - The trace function key to replay
2807
- * @param fn - The function to replay (must be the return value of `withSpan`)
2924
+ * @param fn - The function to run recorded inputs through
2808
2925
  * @param options - Optional replay options. When `traceIds` is passed,
2809
2926
  * `limit` is ignored (with a warning): an explicit ID list already
2810
2927
  * determines how many traces replay.
2811
2928
  * @returns ReplayResult with items, testRunId, and testRunUrl
2812
2929
  */
2813
2930
  async replay(traceFunctionKey, fn, options) {
2931
+ const wrappedKey = fn._bitfabTraceFunctionKey;
2932
+ let replayFn = fn;
2933
+ if (wrappedKey === void 0) {
2934
+ replayFn = this.withSpan(
2935
+ traceFunctionKey,
2936
+ { name: fn.name || "Replay", type: "agent" },
2937
+ fn
2938
+ );
2939
+ } else if (wrappedKey !== traceFunctionKey) {
2940
+ throw new BitfabError(
2941
+ `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.`
2942
+ );
2943
+ }
2814
2944
  const { replay: doReplay } = await import("./replay-BIPIDXX6.js");
2815
2945
  return doReplay(
2816
2946
  this.httpClient,
2817
2947
  this.serviceUrl,
2818
2948
  traceFunctionKey,
2819
- fn,
2949
+ replayFn,
2820
2950
  options
2821
2951
  );
2822
2952
  }
@@ -2890,4 +3020,4 @@ export {
2890
3020
  Bitfab,
2891
3021
  BitfabFunction
2892
3022
  };
2893
- //# sourceMappingURL=chunk-M6N633CX.js.map
3023
+ //# sourceMappingURL=chunk-ILIUTS5D.js.map