bitfab 0.19.1 → 0.21.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.d.cts CHANGED
@@ -396,6 +396,13 @@ interface ReplayOptions {
396
396
  environment?: ReplayEnvironment;
397
397
  /** Group ID to associate this replay with an experiment group for live streaming in Studio. */
398
398
  experimentGroupId?: string;
399
+ /**
400
+ * Dataset this replay runs against. When set, the resulting experiment is
401
+ * durably attributed to the dataset (stored on the test run), so it appears
402
+ * under the dataset's experiments even if the trace lineage can't be
403
+ * reconstructed. Validated server-side against the org.
404
+ */
405
+ datasetId?: string;
399
406
  /**
400
407
  * Reshape recorded inputs before they are spread into `fn`.
401
408
  *
@@ -749,6 +756,25 @@ interface SpanOptions {
749
756
  * only the marked descendants return their recorded output.
750
757
  */
751
758
  mockOnReplay?: boolean;
759
+ /**
760
+ * Record a serializable view of a non-serializable result (e.g. a live
761
+ * stream object) as the span output.
762
+ *
763
+ * When set, the wrapped function's raw return value is handed back to the
764
+ * caller unchanged (so streaming and first-byte latency are untouched),
765
+ * but instead of serializing that raw value, the span records
766
+ * `await finalize(result)`. Use this to trace functions that return a live
767
+ * stream consumed by the caller (Vercel AI SDK `streamText`, a
768
+ * `ReadableStream`, an SSE response) while still capturing a serializable,
769
+ * replayable output such as `{ text, usage, toolCalls }`.
770
+ *
771
+ * Reading from a multi-consumer stream result (like the AI SDK's, which
772
+ * tees internally) does not disturb the caller's own consumption. For the
773
+ * Vercel AI SDK shape, pass the prebuilt `finalizers.aiSdk` helper.
774
+ *
775
+ * Ignored for async-generator results, which are captured automatically.
776
+ */
777
+ finalize?: (result: any) => unknown | Promise<unknown>;
752
778
  }
753
779
 
754
780
  /**
@@ -1063,7 +1089,7 @@ declare class BitfabFunction {
1063
1089
  /**
1064
1090
  * SDK version from package.json (injected at build time)
1065
1091
  */
1066
- declare const __version__ = "0.19.1";
1092
+ declare const __version__ = "0.21.0";
1067
1093
 
1068
1094
  /**
1069
1095
  * Constants for the Bitfab SDK.
@@ -1073,4 +1099,62 @@ declare const __version__ = "0.19.1";
1073
1099
  */
1074
1100
  declare const DEFAULT_SERVICE_URL = "https://bitfab.ai";
1075
1101
 
1076
- export { type ActiveSpanContext, type AdaptContext, type AdaptInputsFn, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler as BitfabLangChainCallbackHandler, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type DbSnapshotConfig, type DbSnapshotProvider, type DbSnapshotRef, type DetachedTrace, type MockStrategy, type ProviderDefinition, ReplayEnvironment, type ReplayEnvironmentSnapshot, type ReplayItem, type ReplayOptions, type ReplayResult, SUPPORTED_PROVIDERS, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
1102
+ /**
1103
+ * Prebuilt `finalize` helpers for `withSpan({ finalize }, fn)`.
1104
+ *
1105
+ * A streaming function returns a live stream object that the caller consumes
1106
+ * directly (SSE, a UI message stream). `withSpan` hands that object back
1107
+ * unchanged; a `finalize` function tells it what serializable view to record
1108
+ * as the span output instead of the raw, non-serializable stream.
1109
+ */
1110
+ /**
1111
+ * Drain a Vercel AI SDK streaming result into a serializable, replayable
1112
+ * span output: `{ text, usage, finishReason, toolCalls, toolResults }`.
1113
+ *
1114
+ * Pass it straight to `withSpan`:
1115
+ *
1116
+ * ```ts
1117
+ * import { finalizers } from "@bitfab/sdk"
1118
+ *
1119
+ * const traced = bitfab.withSpan(
1120
+ * "chat-turn",
1121
+ * { type: "agent", finalize: finalizers.aiSdk },
1122
+ * () => streamText({ model, messages }),
1123
+ * )
1124
+ * const result = traced() // caller still gets the live StreamTextResult
1125
+ * return result.toUIMessageStreamResponse()
1126
+ * ```
1127
+ *
1128
+ * Never throws: any field that is absent or rejects is recorded as
1129
+ * `undefined` so finalize never drops the span.
1130
+ */
1131
+ declare function aiSdk(result: unknown): Promise<Record<string, unknown>>;
1132
+ /**
1133
+ * Collect a `ReadableStream`'s chunks into an array for the span output,
1134
+ * via a `tee()` so the caller's branch is untouched. The caller MUST use
1135
+ * the returned stream, not the original, since a stream can only be read
1136
+ * once:
1137
+ *
1138
+ * ```ts
1139
+ * let live: ReadableStream
1140
+ * const traced = bitfab.withSpan(
1141
+ * "render",
1142
+ * { finalize: (r) => finalizers.readableStream(r, (s) => { live = s }) },
1143
+ * () => makeReadableStream(),
1144
+ * )
1145
+ * traced()
1146
+ * return new Response(live!)
1147
+ * ```
1148
+ *
1149
+ * Prefer `aiSdk` for the Vercel AI SDK, whose result tees internally and
1150
+ * needs no caller rewiring.
1151
+ */
1152
+ declare function readableStream(stream: ReadableStream, onLive: (live: ReadableStream) => void): Promise<{
1153
+ chunks: unknown[];
1154
+ }>;
1155
+ declare const finalizers: {
1156
+ aiSdk: typeof aiSdk;
1157
+ readableStream: typeof readableStream;
1158
+ };
1159
+
1160
+ export { type ActiveSpanContext, type AdaptContext, type AdaptInputsFn, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler as BitfabLangChainCallbackHandler, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type DbSnapshotConfig, type DbSnapshotProvider, type DbSnapshotRef, type DetachedTrace, type MockStrategy, type ProviderDefinition, ReplayEnvironment, type ReplayEnvironmentSnapshot, type ReplayItem, type ReplayOptions, type ReplayResult, SUPPORTED_PROVIDERS, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, finalizers, flushTraces, getCurrentSpan, getCurrentTrace };
package/dist/index.d.ts CHANGED
@@ -396,6 +396,13 @@ interface ReplayOptions {
396
396
  environment?: ReplayEnvironment;
397
397
  /** Group ID to associate this replay with an experiment group for live streaming in Studio. */
398
398
  experimentGroupId?: string;
399
+ /**
400
+ * Dataset this replay runs against. When set, the resulting experiment is
401
+ * durably attributed to the dataset (stored on the test run), so it appears
402
+ * under the dataset's experiments even if the trace lineage can't be
403
+ * reconstructed. Validated server-side against the org.
404
+ */
405
+ datasetId?: string;
399
406
  /**
400
407
  * Reshape recorded inputs before they are spread into `fn`.
401
408
  *
@@ -749,6 +756,25 @@ interface SpanOptions {
749
756
  * only the marked descendants return their recorded output.
750
757
  */
751
758
  mockOnReplay?: boolean;
759
+ /**
760
+ * Record a serializable view of a non-serializable result (e.g. a live
761
+ * stream object) as the span output.
762
+ *
763
+ * When set, the wrapped function's raw return value is handed back to the
764
+ * caller unchanged (so streaming and first-byte latency are untouched),
765
+ * but instead of serializing that raw value, the span records
766
+ * `await finalize(result)`. Use this to trace functions that return a live
767
+ * stream consumed by the caller (Vercel AI SDK `streamText`, a
768
+ * `ReadableStream`, an SSE response) while still capturing a serializable,
769
+ * replayable output such as `{ text, usage, toolCalls }`.
770
+ *
771
+ * Reading from a multi-consumer stream result (like the AI SDK's, which
772
+ * tees internally) does not disturb the caller's own consumption. For the
773
+ * Vercel AI SDK shape, pass the prebuilt `finalizers.aiSdk` helper.
774
+ *
775
+ * Ignored for async-generator results, which are captured automatically.
776
+ */
777
+ finalize?: (result: any) => unknown | Promise<unknown>;
752
778
  }
753
779
 
754
780
  /**
@@ -1063,7 +1089,7 @@ declare class BitfabFunction {
1063
1089
  /**
1064
1090
  * SDK version from package.json (injected at build time)
1065
1091
  */
1066
- declare const __version__ = "0.19.1";
1092
+ declare const __version__ = "0.21.0";
1067
1093
 
1068
1094
  /**
1069
1095
  * Constants for the Bitfab SDK.
@@ -1073,4 +1099,62 @@ declare const __version__ = "0.19.1";
1073
1099
  */
1074
1100
  declare const DEFAULT_SERVICE_URL = "https://bitfab.ai";
1075
1101
 
1076
- export { type ActiveSpanContext, type AdaptContext, type AdaptInputsFn, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler as BitfabLangChainCallbackHandler, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type DbSnapshotConfig, type DbSnapshotProvider, type DbSnapshotRef, type DetachedTrace, type MockStrategy, type ProviderDefinition, ReplayEnvironment, type ReplayEnvironmentSnapshot, type ReplayItem, type ReplayOptions, type ReplayResult, SUPPORTED_PROVIDERS, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
1102
+ /**
1103
+ * Prebuilt `finalize` helpers for `withSpan({ finalize }, fn)`.
1104
+ *
1105
+ * A streaming function returns a live stream object that the caller consumes
1106
+ * directly (SSE, a UI message stream). `withSpan` hands that object back
1107
+ * unchanged; a `finalize` function tells it what serializable view to record
1108
+ * as the span output instead of the raw, non-serializable stream.
1109
+ */
1110
+ /**
1111
+ * Drain a Vercel AI SDK streaming result into a serializable, replayable
1112
+ * span output: `{ text, usage, finishReason, toolCalls, toolResults }`.
1113
+ *
1114
+ * Pass it straight to `withSpan`:
1115
+ *
1116
+ * ```ts
1117
+ * import { finalizers } from "@bitfab/sdk"
1118
+ *
1119
+ * const traced = bitfab.withSpan(
1120
+ * "chat-turn",
1121
+ * { type: "agent", finalize: finalizers.aiSdk },
1122
+ * () => streamText({ model, messages }),
1123
+ * )
1124
+ * const result = traced() // caller still gets the live StreamTextResult
1125
+ * return result.toUIMessageStreamResponse()
1126
+ * ```
1127
+ *
1128
+ * Never throws: any field that is absent or rejects is recorded as
1129
+ * `undefined` so finalize never drops the span.
1130
+ */
1131
+ declare function aiSdk(result: unknown): Promise<Record<string, unknown>>;
1132
+ /**
1133
+ * Collect a `ReadableStream`'s chunks into an array for the span output,
1134
+ * via a `tee()` so the caller's branch is untouched. The caller MUST use
1135
+ * the returned stream, not the original, since a stream can only be read
1136
+ * once:
1137
+ *
1138
+ * ```ts
1139
+ * let live: ReadableStream
1140
+ * const traced = bitfab.withSpan(
1141
+ * "render",
1142
+ * { finalize: (r) => finalizers.readableStream(r, (s) => { live = s }) },
1143
+ * () => makeReadableStream(),
1144
+ * )
1145
+ * traced()
1146
+ * return new Response(live!)
1147
+ * ```
1148
+ *
1149
+ * Prefer `aiSdk` for the Vercel AI SDK, whose result tees internally and
1150
+ * needs no caller rewiring.
1151
+ */
1152
+ declare function readableStream(stream: ReadableStream, onLive: (live: ReadableStream) => void): Promise<{
1153
+ chunks: unknown[];
1154
+ }>;
1155
+ declare const finalizers: {
1156
+ aiSdk: typeof aiSdk;
1157
+ readableStream: typeof readableStream;
1158
+ };
1159
+
1160
+ export { type ActiveSpanContext, type AdaptContext, type AdaptInputsFn, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler as BitfabLangChainCallbackHandler, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type DbSnapshotConfig, type DbSnapshotProvider, type DbSnapshotRef, type DetachedTrace, type MockStrategy, type ProviderDefinition, ReplayEnvironment, type ReplayEnvironmentSnapshot, type ReplayItem, type ReplayOptions, type ReplayResult, SUPPORTED_PROVIDERS, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, finalizers, flushTraces, getCurrentSpan, getCurrentTrace };
package/dist/index.js CHANGED
@@ -17,10 +17,11 @@ import {
17
17
  ReplayEnvironment,
18
18
  SUPPORTED_PROVIDERS,
19
19
  __version__,
20
+ finalizers,
20
21
  flushTraces,
21
22
  getCurrentSpan,
22
23
  getCurrentTrace
23
- } from "./chunk-WZ72P5SX.js";
24
+ } from "./chunk-UO3CIQ7R.js";
24
25
  import {
25
26
  BitfabError
26
27
  } from "./chunk-EQI6ZJC3.js";
@@ -36,6 +37,7 @@ export {
36
37
  ReplayEnvironment,
37
38
  SUPPORTED_PROVIDERS,
38
39
  __version__,
40
+ finalizers,
39
41
  flushTraces,
40
42
  getCurrentSpan,
41
43
  getCurrentTrace
package/dist/node.cjs CHANGED
@@ -410,7 +410,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
410
410
  options?.codeChangeFiles,
411
411
  options?.environment !== void 0,
412
412
  // includeDbBranchLease
413
- options?.experimentGroupId
413
+ options?.experimentGroupId,
414
+ options?.datasetId
414
415
  );
415
416
  const mockStrategy = options?.mock ?? "none";
416
417
  const maxConcurrency = options?.maxConcurrency ?? 10;
@@ -497,6 +498,7 @@ __export(node_exports, {
497
498
  ReplayEnvironment: () => ReplayEnvironment,
498
499
  SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
499
500
  __version__: () => __version__,
501
+ finalizers: () => finalizers,
500
502
  flushTraces: () => flushTraces,
501
503
  getCurrentSpan: () => getCurrentSpan,
502
504
  getCurrentTrace: () => getCurrentTrace
@@ -511,7 +513,7 @@ registerAsyncLocalStorageClass(
511
513
  );
512
514
 
513
515
  // src/version.generated.ts
514
- var __version__ = "0.19.1";
516
+ var __version__ = "0.21.0";
515
517
 
516
518
  // src/constants.ts
517
519
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -789,7 +791,7 @@ var HttpClient = class {
789
791
  * Start a replay session by fetching historical traces.
790
792
  * Blocking call — creates a test run and returns lightweight item references.
791
793
  */
792
- async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId) {
794
+ async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId) {
793
795
  const payload = { traceFunctionKey };
794
796
  if (limit !== void 0) {
795
797
  payload.limit = limit;
@@ -809,6 +811,9 @@ var HttpClient = class {
809
811
  if (experimentGroupId !== void 0) {
810
812
  payload.experimentGroupId = experimentGroupId;
811
813
  }
814
+ if (datasetId !== void 0) {
815
+ payload.datasetId = datasetId;
816
+ }
812
817
  const timeout = includeDbBranchLease ? 18e4 : 3e4;
813
818
  return this.request("/api/sdk/replay/start", payload, {
814
819
  timeout
@@ -3166,11 +3171,11 @@ var Bitfab = class {
3166
3171
  startedAt,
3167
3172
  spanType: options.type ?? "custom"
3168
3173
  };
3169
- const sendSpan = async (params) => {
3174
+ const sendSpan = async (params, spanOpts) => {
3170
3175
  const replayCtx = getReplayContext();
3171
3176
  const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3172
3177
  let resolvePersistence;
3173
- if (persistenceCollector) {
3178
+ if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3174
3179
  persistenceCollector.push(
3175
3180
  new Promise((resolve) => {
3176
3181
  resolvePersistence = resolve;
@@ -3270,11 +3275,41 @@ var Bitfab = class {
3270
3275
  }
3271
3276
  }
3272
3277
  }
3278
+ const recordSpan = (result) => {
3279
+ if (options.finalize) {
3280
+ const replayCtx = getReplayContext();
3281
+ const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3282
+ let resolvePersistence;
3283
+ if (persistenceCollector) {
3284
+ persistenceCollector.push(
3285
+ new Promise((resolve) => {
3286
+ resolvePersistence = resolve;
3287
+ })
3288
+ );
3289
+ }
3290
+ void Promise.resolve().then(() => options.finalize(result)).then(
3291
+ (output) => sendSpan(
3292
+ { result: output },
3293
+ { skipPersistenceRegistration: true }
3294
+ )
3295
+ ).catch(
3296
+ (error) => sendSpan(
3297
+ {
3298
+ result: void 0,
3299
+ error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3300
+ },
3301
+ { skipPersistenceRegistration: true }
3302
+ )
3303
+ ).finally(() => resolvePersistence?.());
3304
+ } else {
3305
+ void sendSpan({ result });
3306
+ }
3307
+ };
3273
3308
  const executeWithContext = () => {
3274
3309
  const result = fn(...args);
3275
3310
  if (result instanceof Promise) {
3276
3311
  return result.then((resolvedResult) => {
3277
- void sendSpan({ result: resolvedResult });
3312
+ recordSpan(resolvedResult);
3278
3313
  return resolvedResult;
3279
3314
  }).catch((error) => {
3280
3315
  void sendSpan({
@@ -3287,7 +3322,7 @@ var Bitfab = class {
3287
3322
  if (isAsyncGenerator(result)) {
3288
3323
  return wrapAsyncGenerator(result, newStack, sendSpan);
3289
3324
  }
3290
- void sendSpan({ result });
3325
+ recordSpan(result);
3291
3326
  return result;
3292
3327
  };
3293
3328
  return runWithSpanStack(newStack, executeWithContext);
@@ -3571,6 +3606,54 @@ var BitfabFunction = class {
3571
3606
  }
3572
3607
  };
3573
3608
 
3609
+ // src/finalizers.ts
3610
+ async function settle(value) {
3611
+ try {
3612
+ return await value;
3613
+ } catch {
3614
+ return void 0;
3615
+ }
3616
+ }
3617
+ async function aiSdk(result) {
3618
+ const r = result ?? {};
3619
+ const [text, usage, totalUsage, finishReason, toolCalls, toolResults] = await Promise.all([
3620
+ settle(r.text),
3621
+ settle(r.usage),
3622
+ settle(r.totalUsage),
3623
+ settle(r.finishReason),
3624
+ settle(r.toolCalls),
3625
+ settle(r.toolResults)
3626
+ ]);
3627
+ return {
3628
+ text,
3629
+ usage: totalUsage ?? usage,
3630
+ finishReason,
3631
+ toolCalls,
3632
+ toolResults
3633
+ };
3634
+ }
3635
+ async function readableStream(stream, onLive) {
3636
+ const [live, copy] = stream.tee();
3637
+ onLive(live);
3638
+ const chunks = [];
3639
+ const reader = copy.getReader();
3640
+ try {
3641
+ for (; ; ) {
3642
+ const { done, value } = await reader.read();
3643
+ if (done) {
3644
+ break;
3645
+ }
3646
+ chunks.push(value);
3647
+ }
3648
+ } catch {
3649
+ }
3650
+ return { chunks };
3651
+ }
3652
+ var finalizers = {
3653
+ aiSdk,
3654
+ readableStream
3655
+ };
3656
+
3574
3657
  // src/node.ts
3575
3658
  init_asyncStorage();
3576
3659
  assertAsyncStorageRegistered();
@@ -3587,6 +3670,7 @@ assertAsyncStorageRegistered();
3587
3670
  ReplayEnvironment,
3588
3671
  SUPPORTED_PROVIDERS,
3589
3672
  __version__,
3673
+ finalizers,
3590
3674
  flushTraces,
3591
3675
  getCurrentSpan,
3592
3676
  getCurrentTrace