bitfab 0.38.10 → 0.40.0

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
@@ -51,7 +51,7 @@ var __version__, __packageName__;
51
51
  var init_version_generated = __esm({
52
52
  "src/version.generated.ts"() {
53
53
  "use strict";
54
- __version__ = "0.38.10";
54
+ __version__ = "0.40.0";
55
55
  __packageName__ = "bitfab";
56
56
  }
57
57
  });
@@ -1207,6 +1207,16 @@ var init_transport = __esm({
1207
1207
  });
1208
1208
 
1209
1209
  // src/http.ts
1210
+ var http_exports = {};
1211
+ __export(http_exports, {
1212
+ BitfabError: () => BitfabError,
1213
+ HttpClient: () => HttpClient,
1214
+ awaitOnExit: () => awaitOnExit,
1215
+ awaitPendingRequests: () => awaitPendingRequests,
1216
+ flushTraces: () => flushTraces,
1217
+ parseRetryAfterMs: () => parseRetryAfterMs,
1218
+ serializePayloadBody: () => serializePayloadBody
1219
+ });
1210
1220
  function awaitOnExit(promise) {
1211
1221
  pendingTracePromises.add(promise);
1212
1222
  void promise.finally(() => {
@@ -1731,6 +1741,10 @@ var init_http = __esm({
1731
1741
  const response = await this.get(endpoint);
1732
1742
  return response.span;
1733
1743
  }
1744
+ /**
1745
+ * GET a JSON endpoint on the service with the client's API key. Throws a
1746
+ * `BitfabError` carrying the status text for any non-2xx response.
1747
+ */
1734
1748
  async get(endpoint) {
1735
1749
  const url = `${this.serviceUrl}${endpoint}`;
1736
1750
  const controller = new AbortController();
@@ -1744,7 +1758,10 @@ var init_http = __esm({
1744
1758
  if (!response.ok) {
1745
1759
  const errorText = await response.text();
1746
1760
  throw new BitfabError(
1747
- `HTTP ${response.status}: ${errorText.slice(0, 500)}`
1761
+ `HTTP ${response.status}: ${errorText.slice(0, 500)}`,
1762
+ void 0,
1763
+ response.status,
1764
+ parseRetryAfterMs(readHeader(response, "retry-after"))
1748
1765
  );
1749
1766
  }
1750
1767
  return await response.json();
@@ -2551,7 +2568,7 @@ function buildMockTree(rootNode) {
2551
2568
  }
2552
2569
  return { spans };
2553
2570
  }
2554
- async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, includeDbBranchLease, dbBranchSettings, adaptInputs) {
2571
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
2555
2572
  let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
2556
2573
  let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
2557
2574
  let dbSnapshotRef = serverItem.dbSnapshotRef;
@@ -2646,6 +2663,32 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2646
2663
  }
2647
2664
  return pending;
2648
2665
  } : void 0;
2666
+ if (dryRun) {
2667
+ return {
2668
+ traceId: null,
2669
+ originalTraceId,
2670
+ originalSpanId,
2671
+ sourceTraceId: originalTraceId,
2672
+ sourceSpanId: originalSpanId,
2673
+ input: inputs,
2674
+ result: void 0,
2675
+ originalOutput,
2676
+ ...serverItem.ingestionType && {
2677
+ ingestionType: serverItem.ingestionType
2678
+ },
2679
+ error: null,
2680
+ traceError: null,
2681
+ replayError: null,
2682
+ durationMs: null,
2683
+ originalDurationMs: serverItem.originalDurationMs ?? serverItem.durationMs ?? null,
2684
+ originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
2685
+ originalModel: serverItem.originalModel ?? serverItem.model ?? null,
2686
+ tokens: null,
2687
+ model: serverItem.originalModel ?? serverItem.model ?? null,
2688
+ dbSnapshotRef: dbSnapshotRef ?? null,
2689
+ dbBranchTimings: dbBranchTimings ?? null
2690
+ };
2691
+ }
2649
2692
  try {
2650
2693
  replayStarted = performance.now();
2651
2694
  const maybePromise = runWithReplayContext(
@@ -2709,6 +2752,9 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2709
2752
  input: inputs,
2710
2753
  result,
2711
2754
  originalOutput,
2755
+ ...serverItem.ingestionType && {
2756
+ ingestionType: serverItem.ingestionType
2757
+ },
2712
2758
  error,
2713
2759
  traceError,
2714
2760
  replayError,
@@ -2856,13 +2902,23 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2856
2902
  options?.name,
2857
2903
  codeChangeDescription,
2858
2904
  codeChangeFiles,
2859
- dbBranchEnabled(options?.dbBranch),
2860
- // includeDbBranchLease
2905
+ // A dry run executes nothing, so a branch would be provisioned (and billed)
2906
+ // for code that never runs, and a seeded source's refusal would fail the item
2907
+ // before it could report its resolved inputs.
2908
+ dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
2861
2909
  options?.experimentGroupId,
2862
2910
  options?.datasetId,
2863
2911
  options?.graderIds,
2864
2912
  resolveDbBranchSettings(options?.dbBranch)
2865
2913
  );
2914
+ if (serverItems.length === 0) {
2915
+ try {
2916
+ console.warn(
2917
+ `Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
2918
+ );
2919
+ } catch {
2920
+ }
2921
+ }
2866
2922
  const mockStrategy = options?.mock ?? "marked";
2867
2923
  const maxConcurrency = options?.maxConcurrency ?? 10;
2868
2924
  const fullTestRunUrl = `${serviceUrl}${testRunUrl}`;
@@ -2881,9 +2937,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2881
2937
  mockStrategy,
2882
2938
  resolvedOverrides,
2883
2939
  replayedTraceIds[index],
2884
- dbBranchEnabled(options?.dbBranch),
2940
+ dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
2885
2941
  resolveDbBranchSettings(options?.dbBranch),
2886
- options?.adaptInputs
2942
+ options?.adaptInputs,
2943
+ options?.dryRun === true
2887
2944
  )
2888
2945
  );
2889
2946
  const total = tasks.length;
@@ -2991,6 +3048,16 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2991
3048
  }
2992
3049
  } : void 0
2993
3050
  );
3051
+ if (options?.dryRun === true) {
3052
+ await httpClient.completeReplay(testRunId).catch(() => void 0);
3053
+ const dryResult = {
3054
+ items: resultItems,
3055
+ testRunId,
3056
+ testRunUrl: fullTestRunUrl
3057
+ };
3058
+ await writeReplayResultFile(dryResult);
3059
+ return dryResult;
3060
+ }
2994
3061
  const deliveredTraceIds = await preserveReplayFailure(
2995
3062
  () => waitForReplayPersistence(httpClient, testRunId, replayedTraceIds),
2996
3063
  resultItems,
@@ -3146,6 +3213,7 @@ __export(index_exports, {
3146
3213
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
3147
3214
  BitfabVercelAiHandler: () => BitfabVercelAiHandler,
3148
3215
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
3216
+ DatasetsClient: () => DatasetsClient,
3149
3217
  DbBranchReplayError: () => DbBranchReplayError,
3150
3218
  HttpClient: () => HttpClient,
3151
3219
  NO_MOCK_OVERRIDE: () => NO_MOCK_OVERRIDE,
@@ -3159,6 +3227,7 @@ __export(index_exports, {
3159
3227
  getCurrentSpan: () => getCurrentSpan,
3160
3228
  getCurrentTrace: () => getCurrentTrace,
3161
3229
  reportReplayProgress: () => reportReplayProgress,
3230
+ seedFromRegistry: () => seedFromRegistry,
3162
3231
  serializeReplayResult: () => serializeReplayResult
3163
3232
  });
3164
3233
  module.exports = __toCommonJS(index_exports);
@@ -4224,6 +4293,131 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
4224
4293
  // src/client.ts
4225
4294
  init_constants();
4226
4295
 
4296
+ // src/datasets.ts
4297
+ var DEFAULT_RERUN_TIMEOUT_MS = 9e4;
4298
+ var DEFAULT_RERUN_POLL_INTERVAL_MS = 1e3;
4299
+ var TERMINAL_RERUN_STATUSES = /* @__PURE__ */ new Set([
4300
+ "completed",
4301
+ "errored"
4302
+ ]);
4303
+ function sleep(ms) {
4304
+ return new Promise((resolve) => setTimeout(resolve, ms));
4305
+ }
4306
+ function datasetPath(datasetId, suffix = "") {
4307
+ return `/api/sdk/datasets/${encodeURIComponent(datasetId)}${suffix}`;
4308
+ }
4309
+ var DatasetsClient = class {
4310
+ constructor(httpClient) {
4311
+ this.httpClient = httpClient;
4312
+ }
4313
+ /**
4314
+ * Create a dataset, or update the one already named this way under the same
4315
+ * trace function. `created` reports which happened. An omitted description
4316
+ * leaves an existing one untouched.
4317
+ */
4318
+ async save(params) {
4319
+ return this.httpClient.request("/api/sdk/datasets", {
4320
+ traceFunctionKey: params.traceFunctionKey,
4321
+ name: params.name,
4322
+ ...params.description === void 0 ? {} : { description: params.description }
4323
+ });
4324
+ }
4325
+ /**
4326
+ * List datasets, scoped to one trace function when `traceFunctionKey` is
4327
+ * given and organization-wide otherwise.
4328
+ */
4329
+ async list(params = {}) {
4330
+ const query = params.traceFunctionKey === void 0 ? "" : `?traceFunctionKey=${encodeURIComponent(params.traceFunctionKey)}`;
4331
+ const response = await this.httpClient.get(
4332
+ `/api/sdk/datasets${query}`
4333
+ );
4334
+ return response.datasets;
4335
+ }
4336
+ /** Fetch one dataset by id. Rejects with a 404 `BitfabError` when it is not in this organization. */
4337
+ async get(datasetId) {
4338
+ const response = await this.httpClient.get(
4339
+ datasetPath(datasetId)
4340
+ );
4341
+ return response.dataset;
4342
+ }
4343
+ /** The ids of every trace in the dataset, the same membership a replay with `datasetId` selects. */
4344
+ async listTraces(datasetId) {
4345
+ return this.httpClient.get(
4346
+ datasetPath(datasetId, "/traces")
4347
+ );
4348
+ }
4349
+ /**
4350
+ * Add traces to the dataset (1 to 100 ids per call). Traces outside the
4351
+ * organization or under another trace function are reported in
4352
+ * `skippedTraceIds` rather than failing the call.
4353
+ */
4354
+ async addTraces(datasetId, traceIds) {
4355
+ return this.httpClient.request(
4356
+ datasetPath(datasetId, "/traces"),
4357
+ { traceIds }
4358
+ );
4359
+ }
4360
+ /** Remove traces from the dataset. The traces themselves are never deleted. */
4361
+ async removeTraces(datasetId, traceIds) {
4362
+ return this.httpClient.request(
4363
+ datasetPath(datasetId, "/removeTraces"),
4364
+ { traceIds }
4365
+ );
4366
+ }
4367
+ /**
4368
+ * Assign graders to the dataset (1 to 100 ids per call). Graders outside the
4369
+ * organization or under another trace function are reported in
4370
+ * `skippedGraderIds` rather than failing the call.
4371
+ */
4372
+ async addGraders(datasetId, graderIds) {
4373
+ return this.httpClient.request(
4374
+ datasetPath(datasetId, "/graders"),
4375
+ { graderIds }
4376
+ );
4377
+ }
4378
+ /** Unassign graders from the dataset. */
4379
+ async removeGraders(datasetId, graderIds) {
4380
+ return this.httpClient.request(
4381
+ datasetPath(datasetId, "/removeGraders"),
4382
+ { graderIds }
4383
+ );
4384
+ }
4385
+ /**
4386
+ * Re-run graders over every trace in the dataset. Defaults to every assigned
4387
+ * grader; an unassigned id is rejected. Waits for the run to finish (up to
4388
+ * `timeoutMs`, default 90s) unless `wait` is `false`, and returns the last
4389
+ * run state seen either way. A request matching an in-flight run joins it.
4390
+ */
4391
+ async rerunGraders(datasetId, options = {}) {
4392
+ const started = await this.httpClient.request(
4393
+ datasetPath(datasetId, "/rerunGraders"),
4394
+ options.graderIds === void 0 ? {} : { graderIds: options.graderIds }
4395
+ );
4396
+ if (options.wait === false) {
4397
+ return started;
4398
+ }
4399
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_RERUN_TIMEOUT_MS);
4400
+ const interval = options.pollIntervalMs ?? DEFAULT_RERUN_POLL_INTERVAL_MS;
4401
+ let run = started.run;
4402
+ while (!TERMINAL_RERUN_STATUSES.has(run.status) && Date.now() < deadline) {
4403
+ await sleep(interval);
4404
+ run = await this.getGraderRerun(datasetId, run.id) ?? run;
4405
+ }
4406
+ return { run, joinedExisting: started.joinedExisting };
4407
+ }
4408
+ /**
4409
+ * The dataset's active grader re-run, or the run named by `runId`. Returns
4410
+ * `null` when nothing is active or the run does not belong to this dataset.
4411
+ */
4412
+ async getGraderRerun(datasetId, runId) {
4413
+ const query = runId === void 0 ? "" : `?runId=${encodeURIComponent(runId)}`;
4414
+ const response = await this.httpClient.get(
4415
+ datasetPath(datasetId, `/rerunGraders${query}`)
4416
+ );
4417
+ return response.run;
4418
+ }
4419
+ };
4420
+
4227
4421
  // src/dbSnapshot.ts
4228
4422
  init_errors();
4229
4423
  var SUPPORTED_PROVIDERS = ["neon"];
@@ -5959,6 +6153,7 @@ var Bitfab = class {
5959
6153
  serviceUrl: this.serviceUrl,
5960
6154
  timeout: this.timeout
5961
6155
  });
6156
+ this.datasets = new DatasetsClient(this.httpClient);
5962
6157
  }
5963
6158
  /**
5964
6159
  * Decorate a class method as an automatically expanded trace root.
@@ -6869,6 +7064,7 @@ var Bitfab = class {
6869
7064
  inputSourceTraceId: traceState?.inputSourceTraceId,
6870
7065
  dbSnapshotRef: traceState?.dbSnapshotRef,
6871
7066
  dropped: traceState?.dropped,
7067
+ ingestionType: traceState?.ingestionType,
6872
7068
  // Built AFTER the wrapped fn finished, so `accessed` reflects
6873
7069
  // whether customer code obtained the branch URL during this
6874
7070
  // item. Omitted entirely when no lease was attached, so the
@@ -7092,6 +7288,7 @@ var Bitfab = class {
7092
7288
  Object.defineProperty(wrappedFn, "_bitfabTraceFunctionKey", {
7093
7289
  value: traceFunctionKey
7094
7290
  });
7291
+ Object.defineProperty(wrappedFn, "_bitfabWrappedFn", { value: fn });
7095
7292
  return wrappedFn;
7096
7293
  }
7097
7294
  /**
@@ -7254,6 +7451,9 @@ var Bitfab = class {
7254
7451
  if (params.dbSnapshotRef) {
7255
7452
  rawTrace.db_snapshot_ref = params.dbSnapshotRef;
7256
7453
  }
7454
+ if (params.ingestionType) {
7455
+ rawTrace.ingestion_type = params.ingestionType;
7456
+ }
7257
7457
  if (params.dbSnapshotUsage) {
7258
7458
  rawTrace.db_snapshot_usage = {
7259
7459
  neon_branch_id: params.dbSnapshotUsage.neonBranchId,
@@ -7400,6 +7600,68 @@ var Bitfab = class {
7400
7600
  clearMockOverrides() {
7401
7601
  this.mockOverrides.length = 0;
7402
7602
  }
7603
+ /**
7604
+ * Write a replayable trace from a case, without running anything.
7605
+ *
7606
+ * Use this to turn a corpus you already hold (a Braintrust dataset, a
7607
+ * spreadsheet, hand-written cases) into traces that {@link replay} can
7608
+ * select. The recorded root span carries `input` as its input and `expected`
7609
+ * as its output, so replay reports each item against the value you expected
7610
+ * rather than against a previous run.
7611
+ *
7612
+ * A seeded trace has no child spans and no database pin, so replay mocking
7613
+ * has nothing recorded to substitute and `dbBranch` refuses it. Pass
7614
+ * `mockOverride` at replay time for calls that must not run.
7615
+ *
7616
+ * @returns The trace ID, usable with `replay({ traceIds: [...] })`.
7617
+ */
7618
+ seedTrace(traceFunctionKey, options) {
7619
+ const { input } = options;
7620
+ const fn = options.fn?._bitfabWrappedFn ?? options.fn;
7621
+ if (fn && input.length < fn.length) {
7622
+ throw new BitfabError(
7623
+ `Seeded case supplies ${input.length} argument(s) but ${fn.name === "" ? "the function" : fn.name} requires ${fn.length}. Fix the case, or omit fn to seed it anyway.`
7624
+ );
7625
+ }
7626
+ const traceId = randomUuid();
7627
+ const startedAt = nowIsoTimestamp();
7628
+ activeTraceStates.set(traceId, {
7629
+ traceId,
7630
+ startedAt,
7631
+ contexts: [],
7632
+ ingestionType: "seeded",
7633
+ ...options.sessionId !== void 0 && { sessionId: options.sessionId },
7634
+ ...options.metadata !== void 0 && { metadata: options.metadata }
7635
+ });
7636
+ try {
7637
+ this.sendWrapperSpan({
7638
+ traceFunctionKey,
7639
+ spanName: options.spanName ?? traceFunctionKey,
7640
+ traceId,
7641
+ spanId: randomUuid(),
7642
+ parentSpanId: null,
7643
+ inputs: input,
7644
+ result: options.expected,
7645
+ startedAt,
7646
+ endedAt: startedAt,
7647
+ spanType: options.spanType ?? "agent",
7648
+ captureContent: true
7649
+ });
7650
+ this.sendTraceCompletion({
7651
+ traceFunctionKey,
7652
+ traceId,
7653
+ startedAt,
7654
+ endedAt: startedAt,
7655
+ sessionId: options.sessionId,
7656
+ metadata: options.metadata,
7657
+ contexts: [],
7658
+ ingestionType: "seeded"
7659
+ });
7660
+ } finally {
7661
+ activeTraceStates.delete(traceId);
7662
+ }
7663
+ return traceId;
7664
+ }
7403
7665
  async replay(traceFunctionKey, fn, options) {
7404
7666
  const wrappedKey = fn._bitfabTraceFunctionKey;
7405
7667
  let replayFn = fn;
@@ -7660,6 +7922,37 @@ init_replay();
7660
7922
  function defineReplayRegistry(registry) {
7661
7923
  return registry;
7662
7924
  }
7925
+ async function seedFromRegistry(registry, pipeline, cases) {
7926
+ const registration = registry[pipeline];
7927
+ if (registration === void 0) {
7928
+ throw new BitfabError(
7929
+ `Unknown pipeline '${pipeline}'. Registered: ${Object.keys(registry).join(", ")}`
7930
+ );
7931
+ }
7932
+ const traceFunctionKey = resolveTraceFunctionKey(registration);
7933
+ const traceIds = cases.map(
7934
+ (seedCase) => registration.client.seedTrace(traceFunctionKey, {
7935
+ input: seedCase.input,
7936
+ expected: seedCase.expected,
7937
+ fn: registration.fn,
7938
+ metadata: seedCase.metadata,
7939
+ sessionId: seedCase.sessionId
7940
+ })
7941
+ );
7942
+ const { flushTraces: flushTraces2 } = await Promise.resolve().then(() => (init_http(), http_exports));
7943
+ await flushTraces2(3e4);
7944
+ return { pipeline, traceFunctionKey, traceIds };
7945
+ }
7946
+ function resolveTraceFunctionKey(registration) {
7947
+ const wrappedKey = registration.fn._bitfabTraceFunctionKey;
7948
+ const key = registration.traceFunctionKey ?? wrappedKey;
7949
+ if (key === void 0) {
7950
+ throw new BitfabError(
7951
+ "Replay registry entry uses a plain function. Set traceFunctionKey to the key its production handler records."
7952
+ );
7953
+ }
7954
+ return key;
7955
+ }
7663
7956
  // Annotate the CommonJS export names for ESM import in node:
7664
7957
  0 && (module.exports = {
7665
7958
  BITFAB_PROGRESS_PREFIX,
@@ -7674,6 +7967,7 @@ function defineReplayRegistry(registry) {
7674
7967
  BitfabOpenAITracingProcessor,
7675
7968
  BitfabVercelAiHandler,
7676
7969
  DEFAULT_SERVICE_URL,
7970
+ DatasetsClient,
7677
7971
  DbBranchReplayError,
7678
7972
  HttpClient,
7679
7973
  NO_MOCK_OVERRIDE,
@@ -7687,6 +7981,7 @@ function defineReplayRegistry(registry) {
7687
7981
  getCurrentSpan,
7688
7982
  getCurrentTrace,
7689
7983
  reportReplayProgress,
7984
+ seedFromRegistry,
7690
7985
  serializeReplayResult
7691
7986
  });
7692
7987
  //# sourceMappingURL=index.cjs.map