@bitfab/sdk 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
@@ -42,7 +42,7 @@ var __version__, __packageName__;
42
42
  var init_version_generated = __esm({
43
43
  "src/version.generated.ts"() {
44
44
  "use strict";
45
- __version__ = "0.38.10";
45
+ __version__ = "0.40.0";
46
46
  __packageName__ = "@bitfab/sdk";
47
47
  }
48
48
  });
@@ -1198,6 +1198,16 @@ var init_transport = __esm({
1198
1198
  });
1199
1199
 
1200
1200
  // src/http.ts
1201
+ var http_exports = {};
1202
+ __export(http_exports, {
1203
+ BitfabError: () => BitfabError,
1204
+ HttpClient: () => HttpClient,
1205
+ awaitOnExit: () => awaitOnExit,
1206
+ awaitPendingRequests: () => awaitPendingRequests,
1207
+ flushTraces: () => flushTraces,
1208
+ parseRetryAfterMs: () => parseRetryAfterMs,
1209
+ serializePayloadBody: () => serializePayloadBody
1210
+ });
1201
1211
  function awaitOnExit(promise) {
1202
1212
  pendingTracePromises.add(promise);
1203
1213
  void promise.finally(() => {
@@ -1722,6 +1732,10 @@ var init_http = __esm({
1722
1732
  const response = await this.get(endpoint);
1723
1733
  return response.span;
1724
1734
  }
1735
+ /**
1736
+ * GET a JSON endpoint on the service with the client's API key. Throws a
1737
+ * `BitfabError` carrying the status text for any non-2xx response.
1738
+ */
1725
1739
  async get(endpoint) {
1726
1740
  const url = `${this.serviceUrl}${endpoint}`;
1727
1741
  const controller = new AbortController();
@@ -1735,7 +1749,10 @@ var init_http = __esm({
1735
1749
  if (!response.ok) {
1736
1750
  const errorText = await response.text();
1737
1751
  throw new BitfabError(
1738
- `HTTP ${response.status}: ${errorText.slice(0, 500)}`
1752
+ `HTTP ${response.status}: ${errorText.slice(0, 500)}`,
1753
+ void 0,
1754
+ response.status,
1755
+ parseRetryAfterMs(readHeader(response, "retry-after"))
1739
1756
  );
1740
1757
  }
1741
1758
  return await response.json();
@@ -2542,7 +2559,7 @@ function buildMockTree(rootNode) {
2542
2559
  }
2543
2560
  return { spans };
2544
2561
  }
2545
- async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, includeDbBranchLease, dbBranchSettings, adaptInputs) {
2562
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
2546
2563
  let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
2547
2564
  let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
2548
2565
  let dbSnapshotRef = serverItem.dbSnapshotRef;
@@ -2637,6 +2654,32 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2637
2654
  }
2638
2655
  return pending;
2639
2656
  } : void 0;
2657
+ if (dryRun) {
2658
+ return {
2659
+ traceId: null,
2660
+ originalTraceId,
2661
+ originalSpanId,
2662
+ sourceTraceId: originalTraceId,
2663
+ sourceSpanId: originalSpanId,
2664
+ input: inputs,
2665
+ result: void 0,
2666
+ originalOutput,
2667
+ ...serverItem.ingestionType && {
2668
+ ingestionType: serverItem.ingestionType
2669
+ },
2670
+ error: null,
2671
+ traceError: null,
2672
+ replayError: null,
2673
+ durationMs: null,
2674
+ originalDurationMs: serverItem.originalDurationMs ?? serverItem.durationMs ?? null,
2675
+ originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
2676
+ originalModel: serverItem.originalModel ?? serverItem.model ?? null,
2677
+ tokens: null,
2678
+ model: serverItem.originalModel ?? serverItem.model ?? null,
2679
+ dbSnapshotRef: dbSnapshotRef ?? null,
2680
+ dbBranchTimings: dbBranchTimings ?? null
2681
+ };
2682
+ }
2640
2683
  try {
2641
2684
  replayStarted = performance.now();
2642
2685
  const maybePromise = runWithReplayContext(
@@ -2700,6 +2743,9 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2700
2743
  input: inputs,
2701
2744
  result,
2702
2745
  originalOutput,
2746
+ ...serverItem.ingestionType && {
2747
+ ingestionType: serverItem.ingestionType
2748
+ },
2703
2749
  error,
2704
2750
  traceError,
2705
2751
  replayError,
@@ -2847,13 +2893,23 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2847
2893
  options?.name,
2848
2894
  codeChangeDescription,
2849
2895
  codeChangeFiles,
2850
- dbBranchEnabled(options?.dbBranch),
2851
- // includeDbBranchLease
2896
+ // A dry run executes nothing, so a branch would be provisioned (and billed)
2897
+ // for code that never runs, and a seeded source's refusal would fail the item
2898
+ // before it could report its resolved inputs.
2899
+ dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
2852
2900
  options?.experimentGroupId,
2853
2901
  options?.datasetId,
2854
2902
  options?.graderIds,
2855
2903
  resolveDbBranchSettings(options?.dbBranch)
2856
2904
  );
2905
+ if (serverItems.length === 0) {
2906
+ try {
2907
+ console.warn(
2908
+ `Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
2909
+ );
2910
+ } catch {
2911
+ }
2912
+ }
2857
2913
  const mockStrategy = options?.mock ?? "marked";
2858
2914
  const maxConcurrency = options?.maxConcurrency ?? 10;
2859
2915
  const fullTestRunUrl = `${serviceUrl}${testRunUrl}`;
@@ -2872,9 +2928,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2872
2928
  mockStrategy,
2873
2929
  resolvedOverrides,
2874
2930
  replayedTraceIds[index],
2875
- dbBranchEnabled(options?.dbBranch),
2931
+ dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
2876
2932
  resolveDbBranchSettings(options?.dbBranch),
2877
- options?.adaptInputs
2933
+ options?.adaptInputs,
2934
+ options?.dryRun === true
2878
2935
  )
2879
2936
  );
2880
2937
  const total = tasks.length;
@@ -2982,6 +3039,16 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2982
3039
  }
2983
3040
  } : void 0
2984
3041
  );
3042
+ if (options?.dryRun === true) {
3043
+ await httpClient.completeReplay(testRunId).catch(() => void 0);
3044
+ const dryResult = {
3045
+ items: resultItems,
3046
+ testRunId,
3047
+ testRunUrl: fullTestRunUrl
3048
+ };
3049
+ await writeReplayResultFile(dryResult);
3050
+ return dryResult;
3051
+ }
2985
3052
  const deliveredTraceIds = await preserveReplayFailure(
2986
3053
  () => waitForReplayPersistence(httpClient, testRunId, replayedTraceIds),
2987
3054
  resultItems,
@@ -3137,6 +3204,7 @@ __export(index_exports, {
3137
3204
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
3138
3205
  BitfabVercelAiHandler: () => BitfabVercelAiHandler,
3139
3206
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
3207
+ DatasetsClient: () => DatasetsClient,
3140
3208
  DbBranchReplayError: () => DbBranchReplayError,
3141
3209
  HttpClient: () => HttpClient,
3142
3210
  NO_MOCK_OVERRIDE: () => NO_MOCK_OVERRIDE,
@@ -3150,6 +3218,7 @@ __export(index_exports, {
3150
3218
  getCurrentSpan: () => getCurrentSpan,
3151
3219
  getCurrentTrace: () => getCurrentTrace,
3152
3220
  reportReplayProgress: () => reportReplayProgress,
3221
+ seedFromRegistry: () => seedFromRegistry,
3153
3222
  serializeReplayResult: () => serializeReplayResult
3154
3223
  });
3155
3224
  module.exports = __toCommonJS(index_exports);
@@ -4215,6 +4284,131 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
4215
4284
  // src/client.ts
4216
4285
  init_constants();
4217
4286
 
4287
+ // src/datasets.ts
4288
+ var DEFAULT_RERUN_TIMEOUT_MS = 9e4;
4289
+ var DEFAULT_RERUN_POLL_INTERVAL_MS = 1e3;
4290
+ var TERMINAL_RERUN_STATUSES = /* @__PURE__ */ new Set([
4291
+ "completed",
4292
+ "errored"
4293
+ ]);
4294
+ function sleep(ms) {
4295
+ return new Promise((resolve) => setTimeout(resolve, ms));
4296
+ }
4297
+ function datasetPath(datasetId, suffix = "") {
4298
+ return `/api/sdk/datasets/${encodeURIComponent(datasetId)}${suffix}`;
4299
+ }
4300
+ var DatasetsClient = class {
4301
+ constructor(httpClient) {
4302
+ this.httpClient = httpClient;
4303
+ }
4304
+ /**
4305
+ * Create a dataset, or update the one already named this way under the same
4306
+ * trace function. `created` reports which happened. An omitted description
4307
+ * leaves an existing one untouched.
4308
+ */
4309
+ async save(params) {
4310
+ return this.httpClient.request("/api/sdk/datasets", {
4311
+ traceFunctionKey: params.traceFunctionKey,
4312
+ name: params.name,
4313
+ ...params.description === void 0 ? {} : { description: params.description }
4314
+ });
4315
+ }
4316
+ /**
4317
+ * List datasets, scoped to one trace function when `traceFunctionKey` is
4318
+ * given and organization-wide otherwise.
4319
+ */
4320
+ async list(params = {}) {
4321
+ const query = params.traceFunctionKey === void 0 ? "" : `?traceFunctionKey=${encodeURIComponent(params.traceFunctionKey)}`;
4322
+ const response = await this.httpClient.get(
4323
+ `/api/sdk/datasets${query}`
4324
+ );
4325
+ return response.datasets;
4326
+ }
4327
+ /** Fetch one dataset by id. Rejects with a 404 `BitfabError` when it is not in this organization. */
4328
+ async get(datasetId) {
4329
+ const response = await this.httpClient.get(
4330
+ datasetPath(datasetId)
4331
+ );
4332
+ return response.dataset;
4333
+ }
4334
+ /** The ids of every trace in the dataset, the same membership a replay with `datasetId` selects. */
4335
+ async listTraces(datasetId) {
4336
+ return this.httpClient.get(
4337
+ datasetPath(datasetId, "/traces")
4338
+ );
4339
+ }
4340
+ /**
4341
+ * Add traces to the dataset (1 to 100 ids per call). Traces outside the
4342
+ * organization or under another trace function are reported in
4343
+ * `skippedTraceIds` rather than failing the call.
4344
+ */
4345
+ async addTraces(datasetId, traceIds) {
4346
+ return this.httpClient.request(
4347
+ datasetPath(datasetId, "/traces"),
4348
+ { traceIds }
4349
+ );
4350
+ }
4351
+ /** Remove traces from the dataset. The traces themselves are never deleted. */
4352
+ async removeTraces(datasetId, traceIds) {
4353
+ return this.httpClient.request(
4354
+ datasetPath(datasetId, "/removeTraces"),
4355
+ { traceIds }
4356
+ );
4357
+ }
4358
+ /**
4359
+ * Assign graders to the dataset (1 to 100 ids per call). Graders outside the
4360
+ * organization or under another trace function are reported in
4361
+ * `skippedGraderIds` rather than failing the call.
4362
+ */
4363
+ async addGraders(datasetId, graderIds) {
4364
+ return this.httpClient.request(
4365
+ datasetPath(datasetId, "/graders"),
4366
+ { graderIds }
4367
+ );
4368
+ }
4369
+ /** Unassign graders from the dataset. */
4370
+ async removeGraders(datasetId, graderIds) {
4371
+ return this.httpClient.request(
4372
+ datasetPath(datasetId, "/removeGraders"),
4373
+ { graderIds }
4374
+ );
4375
+ }
4376
+ /**
4377
+ * Re-run graders over every trace in the dataset. Defaults to every assigned
4378
+ * grader; an unassigned id is rejected. Waits for the run to finish (up to
4379
+ * `timeoutMs`, default 90s) unless `wait` is `false`, and returns the last
4380
+ * run state seen either way. A request matching an in-flight run joins it.
4381
+ */
4382
+ async rerunGraders(datasetId, options = {}) {
4383
+ const started = await this.httpClient.request(
4384
+ datasetPath(datasetId, "/rerunGraders"),
4385
+ options.graderIds === void 0 ? {} : { graderIds: options.graderIds }
4386
+ );
4387
+ if (options.wait === false) {
4388
+ return started;
4389
+ }
4390
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_RERUN_TIMEOUT_MS);
4391
+ const interval = options.pollIntervalMs ?? DEFAULT_RERUN_POLL_INTERVAL_MS;
4392
+ let run = started.run;
4393
+ while (!TERMINAL_RERUN_STATUSES.has(run.status) && Date.now() < deadline) {
4394
+ await sleep(interval);
4395
+ run = await this.getGraderRerun(datasetId, run.id) ?? run;
4396
+ }
4397
+ return { run, joinedExisting: started.joinedExisting };
4398
+ }
4399
+ /**
4400
+ * The dataset's active grader re-run, or the run named by `runId`. Returns
4401
+ * `null` when nothing is active or the run does not belong to this dataset.
4402
+ */
4403
+ async getGraderRerun(datasetId, runId) {
4404
+ const query = runId === void 0 ? "" : `?runId=${encodeURIComponent(runId)}`;
4405
+ const response = await this.httpClient.get(
4406
+ datasetPath(datasetId, `/rerunGraders${query}`)
4407
+ );
4408
+ return response.run;
4409
+ }
4410
+ };
4411
+
4218
4412
  // src/dbSnapshot.ts
4219
4413
  init_errors();
4220
4414
  var SUPPORTED_PROVIDERS = ["neon"];
@@ -5950,6 +6144,7 @@ var Bitfab = class {
5950
6144
  serviceUrl: this.serviceUrl,
5951
6145
  timeout: this.timeout
5952
6146
  });
6147
+ this.datasets = new DatasetsClient(this.httpClient);
5953
6148
  }
5954
6149
  /**
5955
6150
  * Decorate a class method as an automatically expanded trace root.
@@ -6860,6 +7055,7 @@ var Bitfab = class {
6860
7055
  inputSourceTraceId: traceState?.inputSourceTraceId,
6861
7056
  dbSnapshotRef: traceState?.dbSnapshotRef,
6862
7057
  dropped: traceState?.dropped,
7058
+ ingestionType: traceState?.ingestionType,
6863
7059
  // Built AFTER the wrapped fn finished, so `accessed` reflects
6864
7060
  // whether customer code obtained the branch URL during this
6865
7061
  // item. Omitted entirely when no lease was attached, so the
@@ -7083,6 +7279,7 @@ var Bitfab = class {
7083
7279
  Object.defineProperty(wrappedFn, "_bitfabTraceFunctionKey", {
7084
7280
  value: traceFunctionKey
7085
7281
  });
7282
+ Object.defineProperty(wrappedFn, "_bitfabWrappedFn", { value: fn });
7086
7283
  return wrappedFn;
7087
7284
  }
7088
7285
  /**
@@ -7245,6 +7442,9 @@ var Bitfab = class {
7245
7442
  if (params.dbSnapshotRef) {
7246
7443
  rawTrace.db_snapshot_ref = params.dbSnapshotRef;
7247
7444
  }
7445
+ if (params.ingestionType) {
7446
+ rawTrace.ingestion_type = params.ingestionType;
7447
+ }
7248
7448
  if (params.dbSnapshotUsage) {
7249
7449
  rawTrace.db_snapshot_usage = {
7250
7450
  neon_branch_id: params.dbSnapshotUsage.neonBranchId,
@@ -7391,6 +7591,68 @@ var Bitfab = class {
7391
7591
  clearMockOverrides() {
7392
7592
  this.mockOverrides.length = 0;
7393
7593
  }
7594
+ /**
7595
+ * Write a replayable trace from a case, without running anything.
7596
+ *
7597
+ * Use this to turn a corpus you already hold (a Braintrust dataset, a
7598
+ * spreadsheet, hand-written cases) into traces that {@link replay} can
7599
+ * select. The recorded root span carries `input` as its input and `expected`
7600
+ * as its output, so replay reports each item against the value you expected
7601
+ * rather than against a previous run.
7602
+ *
7603
+ * A seeded trace has no child spans and no database pin, so replay mocking
7604
+ * has nothing recorded to substitute and `dbBranch` refuses it. Pass
7605
+ * `mockOverride` at replay time for calls that must not run.
7606
+ *
7607
+ * @returns The trace ID, usable with `replay({ traceIds: [...] })`.
7608
+ */
7609
+ seedTrace(traceFunctionKey, options) {
7610
+ const { input } = options;
7611
+ const fn = options.fn?._bitfabWrappedFn ?? options.fn;
7612
+ if (fn && input.length < fn.length) {
7613
+ throw new BitfabError(
7614
+ `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.`
7615
+ );
7616
+ }
7617
+ const traceId = randomUuid();
7618
+ const startedAt = nowIsoTimestamp();
7619
+ activeTraceStates.set(traceId, {
7620
+ traceId,
7621
+ startedAt,
7622
+ contexts: [],
7623
+ ingestionType: "seeded",
7624
+ ...options.sessionId !== void 0 && { sessionId: options.sessionId },
7625
+ ...options.metadata !== void 0 && { metadata: options.metadata }
7626
+ });
7627
+ try {
7628
+ this.sendWrapperSpan({
7629
+ traceFunctionKey,
7630
+ spanName: options.spanName ?? traceFunctionKey,
7631
+ traceId,
7632
+ spanId: randomUuid(),
7633
+ parentSpanId: null,
7634
+ inputs: input,
7635
+ result: options.expected,
7636
+ startedAt,
7637
+ endedAt: startedAt,
7638
+ spanType: options.spanType ?? "agent",
7639
+ captureContent: true
7640
+ });
7641
+ this.sendTraceCompletion({
7642
+ traceFunctionKey,
7643
+ traceId,
7644
+ startedAt,
7645
+ endedAt: startedAt,
7646
+ sessionId: options.sessionId,
7647
+ metadata: options.metadata,
7648
+ contexts: [],
7649
+ ingestionType: "seeded"
7650
+ });
7651
+ } finally {
7652
+ activeTraceStates.delete(traceId);
7653
+ }
7654
+ return traceId;
7655
+ }
7394
7656
  async replay(traceFunctionKey, fn, options) {
7395
7657
  const wrappedKey = fn._bitfabTraceFunctionKey;
7396
7658
  let replayFn = fn;
@@ -7651,6 +7913,37 @@ init_replay();
7651
7913
  function defineReplayRegistry(registry) {
7652
7914
  return registry;
7653
7915
  }
7916
+ async function seedFromRegistry(registry, pipeline, cases) {
7917
+ const registration = registry[pipeline];
7918
+ if (registration === void 0) {
7919
+ throw new BitfabError(
7920
+ `Unknown pipeline '${pipeline}'. Registered: ${Object.keys(registry).join(", ")}`
7921
+ );
7922
+ }
7923
+ const traceFunctionKey = resolveTraceFunctionKey(registration);
7924
+ const traceIds = cases.map(
7925
+ (seedCase) => registration.client.seedTrace(traceFunctionKey, {
7926
+ input: seedCase.input,
7927
+ expected: seedCase.expected,
7928
+ fn: registration.fn,
7929
+ metadata: seedCase.metadata,
7930
+ sessionId: seedCase.sessionId
7931
+ })
7932
+ );
7933
+ const { flushTraces: flushTraces2 } = await Promise.resolve().then(() => (init_http(), http_exports));
7934
+ await flushTraces2(3e4);
7935
+ return { pipeline, traceFunctionKey, traceIds };
7936
+ }
7937
+ function resolveTraceFunctionKey(registration) {
7938
+ const wrappedKey = registration.fn._bitfabTraceFunctionKey;
7939
+ const key = registration.traceFunctionKey ?? wrappedKey;
7940
+ if (key === void 0) {
7941
+ throw new BitfabError(
7942
+ "Replay registry entry uses a plain function. Set traceFunctionKey to the key its production handler records."
7943
+ );
7944
+ }
7945
+ return key;
7946
+ }
7654
7947
  // Annotate the CommonJS export names for ESM import in node:
7655
7948
  0 && (module.exports = {
7656
7949
  BITFAB_PROGRESS_PREFIX,
@@ -7665,6 +7958,7 @@ function defineReplayRegistry(registry) {
7665
7958
  BitfabOpenAITracingProcessor,
7666
7959
  BitfabVercelAiHandler,
7667
7960
  DEFAULT_SERVICE_URL,
7961
+ DatasetsClient,
7668
7962
  DbBranchReplayError,
7669
7963
  HttpClient,
7670
7964
  NO_MOCK_OVERRIDE,
@@ -7678,6 +7972,7 @@ function defineReplayRegistry(registry) {
7678
7972
  getCurrentSpan,
7679
7973
  getCurrentTrace,
7680
7974
  reportReplayProgress,
7975
+ seedFromRegistry,
7681
7976
  serializeReplayResult
7682
7977
  });
7683
7978
  //# sourceMappingURL=index.cjs.map