bitfab 0.20.0 → 0.21.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.
package/dist/index.d.cts CHANGED
@@ -756,6 +756,25 @@ interface SpanOptions {
756
756
  * only the marked descendants return their recorded output.
757
757
  */
758
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>;
759
778
  }
760
779
 
761
780
  /**
@@ -1070,7 +1089,7 @@ declare class BitfabFunction {
1070
1089
  /**
1071
1090
  * SDK version from package.json (injected at build time)
1072
1091
  */
1073
- declare const __version__ = "0.20.0";
1092
+ declare const __version__ = "0.21.1";
1074
1093
 
1075
1094
  /**
1076
1095
  * Constants for the Bitfab SDK.
@@ -1080,4 +1099,62 @@ declare const __version__ = "0.20.0";
1080
1099
  */
1081
1100
  declare const DEFAULT_SERVICE_URL = "https://bitfab.ai";
1082
1101
 
1083
- 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
@@ -756,6 +756,25 @@ interface SpanOptions {
756
756
  * only the marked descendants return their recorded output.
757
757
  */
758
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>;
759
778
  }
760
779
 
761
780
  /**
@@ -1070,7 +1089,7 @@ declare class BitfabFunction {
1070
1089
  /**
1071
1090
  * SDK version from package.json (injected at build time)
1072
1091
  */
1073
- declare const __version__ = "0.20.0";
1092
+ declare const __version__ = "0.21.1";
1074
1093
 
1075
1094
  /**
1076
1095
  * Constants for the Bitfab SDK.
@@ -1080,4 +1099,62 @@ declare const __version__ = "0.20.0";
1080
1099
  */
1081
1100
  declare const DEFAULT_SERVICE_URL = "https://bitfab.ai";
1082
1101
 
1083
- 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-IUZIGC6T.js";
24
+ } from "./chunk-75LZO6JS.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
@@ -498,6 +498,7 @@ __export(node_exports, {
498
498
  ReplayEnvironment: () => ReplayEnvironment,
499
499
  SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
500
500
  __version__: () => __version__,
501
+ finalizers: () => finalizers,
501
502
  flushTraces: () => flushTraces,
502
503
  getCurrentSpan: () => getCurrentSpan,
503
504
  getCurrentTrace: () => getCurrentTrace
@@ -512,7 +513,7 @@ registerAsyncLocalStorageClass(
512
513
  );
513
514
 
514
515
  // src/version.generated.ts
515
- var __version__ = "0.20.0";
516
+ var __version__ = "0.21.1";
516
517
 
517
518
  // src/constants.ts
518
519
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -2445,9 +2446,15 @@ var BitfabOpenAITracingProcessor = class {
2445
2446
  * Extract and add input/response to serialized span, updating errors list.
2446
2447
  */
2447
2448
  extractSpanInputResponse(span, serializedSpan, errors) {
2449
+ if (span.spanData?.type !== "response") {
2450
+ return;
2451
+ }
2448
2452
  const spanData = serializedSpan.span_data;
2449
2453
  try {
2450
- spanData.input = span.spanData?._input || [];
2454
+ const input = span.spanData?._input;
2455
+ if (input !== void 0) {
2456
+ spanData.input = input;
2457
+ }
2451
2458
  } catch (error) {
2452
2459
  errors.push({
2453
2460
  source: "sdk",
@@ -2456,7 +2463,10 @@ var BitfabOpenAITracingProcessor = class {
2456
2463
  });
2457
2464
  }
2458
2465
  try {
2459
- spanData.response = span.spanData?._response || null;
2466
+ const response = span.spanData?._response;
2467
+ if (response !== void 0) {
2468
+ spanData.response = response;
2469
+ }
2460
2470
  } catch (error) {
2461
2471
  errors.push({
2462
2472
  source: "sdk",
@@ -3170,11 +3180,11 @@ var Bitfab = class {
3170
3180
  startedAt,
3171
3181
  spanType: options.type ?? "custom"
3172
3182
  };
3173
- const sendSpan = async (params) => {
3183
+ const sendSpan = async (params, spanOpts) => {
3174
3184
  const replayCtx = getReplayContext();
3175
3185
  const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3176
3186
  let resolvePersistence;
3177
- if (persistenceCollector) {
3187
+ if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3178
3188
  persistenceCollector.push(
3179
3189
  new Promise((resolve) => {
3180
3190
  resolvePersistence = resolve;
@@ -3274,11 +3284,41 @@ var Bitfab = class {
3274
3284
  }
3275
3285
  }
3276
3286
  }
3287
+ const recordSpan = (result) => {
3288
+ if (options.finalize) {
3289
+ const replayCtx = getReplayContext();
3290
+ const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3291
+ let resolvePersistence;
3292
+ if (persistenceCollector) {
3293
+ persistenceCollector.push(
3294
+ new Promise((resolve) => {
3295
+ resolvePersistence = resolve;
3296
+ })
3297
+ );
3298
+ }
3299
+ void Promise.resolve().then(() => options.finalize(result)).then(
3300
+ (output) => sendSpan(
3301
+ { result: output },
3302
+ { skipPersistenceRegistration: true }
3303
+ )
3304
+ ).catch(
3305
+ (error) => sendSpan(
3306
+ {
3307
+ result: void 0,
3308
+ error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3309
+ },
3310
+ { skipPersistenceRegistration: true }
3311
+ )
3312
+ ).finally(() => resolvePersistence?.());
3313
+ } else {
3314
+ void sendSpan({ result });
3315
+ }
3316
+ };
3277
3317
  const executeWithContext = () => {
3278
3318
  const result = fn(...args);
3279
3319
  if (result instanceof Promise) {
3280
3320
  return result.then((resolvedResult) => {
3281
- void sendSpan({ result: resolvedResult });
3321
+ recordSpan(resolvedResult);
3282
3322
  return resolvedResult;
3283
3323
  }).catch((error) => {
3284
3324
  void sendSpan({
@@ -3291,7 +3331,7 @@ var Bitfab = class {
3291
3331
  if (isAsyncGenerator(result)) {
3292
3332
  return wrapAsyncGenerator(result, newStack, sendSpan);
3293
3333
  }
3294
- void sendSpan({ result });
3334
+ recordSpan(result);
3295
3335
  return result;
3296
3336
  };
3297
3337
  return runWithSpanStack(newStack, executeWithContext);
@@ -3575,6 +3615,54 @@ var BitfabFunction = class {
3575
3615
  }
3576
3616
  };
3577
3617
 
3618
+ // src/finalizers.ts
3619
+ async function settle(value) {
3620
+ try {
3621
+ return await value;
3622
+ } catch {
3623
+ return void 0;
3624
+ }
3625
+ }
3626
+ async function aiSdk(result) {
3627
+ const r = result ?? {};
3628
+ const [text, usage, totalUsage, finishReason, toolCalls, toolResults] = await Promise.all([
3629
+ settle(r.text),
3630
+ settle(r.usage),
3631
+ settle(r.totalUsage),
3632
+ settle(r.finishReason),
3633
+ settle(r.toolCalls),
3634
+ settle(r.toolResults)
3635
+ ]);
3636
+ return {
3637
+ text,
3638
+ usage: totalUsage ?? usage,
3639
+ finishReason,
3640
+ toolCalls,
3641
+ toolResults
3642
+ };
3643
+ }
3644
+ async function readableStream(stream, onLive) {
3645
+ const [live, copy] = stream.tee();
3646
+ onLive(live);
3647
+ const chunks = [];
3648
+ const reader = copy.getReader();
3649
+ try {
3650
+ for (; ; ) {
3651
+ const { done, value } = await reader.read();
3652
+ if (done) {
3653
+ break;
3654
+ }
3655
+ chunks.push(value);
3656
+ }
3657
+ } catch {
3658
+ }
3659
+ return { chunks };
3660
+ }
3661
+ var finalizers = {
3662
+ aiSdk,
3663
+ readableStream
3664
+ };
3665
+
3578
3666
  // src/node.ts
3579
3667
  init_asyncStorage();
3580
3668
  assertAsyncStorageRegistered();
@@ -3591,6 +3679,7 @@ assertAsyncStorageRegistered();
3591
3679
  ReplayEnvironment,
3592
3680
  SUPPORTED_PROVIDERS,
3593
3681
  __version__,
3682
+ finalizers,
3594
3683
  flushTraces,
3595
3684
  getCurrentSpan,
3596
3685
  getCurrentTrace