bitfab 0.14.0 → 0.16.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.
@@ -1,14 +1,350 @@
1
1
  import {
2
2
  BitfabError,
3
- DEFAULT_SERVICE_URL,
4
- HttpClient,
5
3
  asyncStorageReady,
6
4
  createAsyncLocalStorage,
7
5
  deserializeValue,
8
6
  getReplayContext,
9
7
  isAsyncStorageInitDone,
10
8
  serializeValue
11
- } from "./chunk-OW2EJK7T.js";
9
+ } from "./chunk-QT7HWOKU.js";
10
+
11
+ // src/version.generated.ts
12
+ var __version__ = "0.16.0";
13
+
14
+ // src/constants.ts
15
+ var DEFAULT_SERVICE_URL = "https://bitfab.ai";
16
+
17
+ // src/http.ts
18
+ var pendingTracePromises = /* @__PURE__ */ new Set();
19
+ function awaitOnExit(promise) {
20
+ pendingTracePromises.add(promise);
21
+ void promise.finally(() => {
22
+ pendingTracePromises.delete(promise);
23
+ }).catch(() => {
24
+ });
25
+ return promise;
26
+ }
27
+ async function flushTraces(timeoutMs = 5e3) {
28
+ if (pendingTracePromises.size === 0) {
29
+ return;
30
+ }
31
+ await Promise.race([
32
+ Promise.allSettled(Array.from(pendingTracePromises)),
33
+ new Promise((resolve) => setTimeout(resolve, timeoutMs))
34
+ ]);
35
+ }
36
+ if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
37
+ let isFlushing = false;
38
+ process.on("beforeExit", () => {
39
+ if (pendingTracePromises.size > 0 && !isFlushing) {
40
+ isFlushing = true;
41
+ Promise.allSettled(
42
+ Array.from(pendingTracePromises).map(
43
+ (p) => p.catch(() => {
44
+ })
45
+ )
46
+ ).then(() => {
47
+ isFlushing = false;
48
+ }).catch(() => {
49
+ isFlushing = false;
50
+ });
51
+ }
52
+ });
53
+ }
54
+ var HttpClient = class {
55
+ constructor(config) {
56
+ this.apiKey = config.apiKey;
57
+ this.serviceUrl = config.serviceUrl;
58
+ this.timeout = config.timeout ?? 12e4;
59
+ }
60
+ /**
61
+ * Make an HTTP request to the Bitfab API. Defaults to POST; pass
62
+ * `options.method` to use a different verb (e.g. "PATCH").
63
+ *
64
+ * @param endpoint - The API endpoint (without base URL)
65
+ * @param payload - The request body
66
+ * @param options - Optional request options
67
+ * @returns The parsed JSON response
68
+ * @throws {BitfabError} If the request fails
69
+ */
70
+ async request(endpoint, payload, options) {
71
+ const url = `${this.serviceUrl}${endpoint}`;
72
+ const timeout = options?.timeout ?? this.timeout;
73
+ const method = options?.method ?? "POST";
74
+ const controller = new AbortController();
75
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
76
+ let body;
77
+ let serializationError;
78
+ try {
79
+ body = JSON.stringify(payload);
80
+ } catch (error) {
81
+ serializationError = error instanceof Error ? error.message : String(error);
82
+ body = JSON.stringify({
83
+ ...Object.fromEntries(
84
+ Object.entries(payload).filter(
85
+ ([, v]) => typeof v === "string" || typeof v === "number"
86
+ )
87
+ ),
88
+ rawSpan: {},
89
+ errors: [
90
+ { source: "sdk", step: "json_serialize", error: serializationError }
91
+ ]
92
+ });
93
+ }
94
+ try {
95
+ const response = await fetch(url, {
96
+ method,
97
+ headers: {
98
+ "Content-Type": "application/json",
99
+ Authorization: `Bearer ${this.apiKey}`
100
+ },
101
+ body,
102
+ signal: controller.signal
103
+ });
104
+ if (!response.ok) {
105
+ const errorText = await response.text();
106
+ throw new BitfabError(
107
+ `HTTP ${response.status}: ${errorText.slice(0, 500)}`
108
+ );
109
+ }
110
+ const result = await response.json();
111
+ if (result.error) {
112
+ if (result.url) {
113
+ throw new BitfabError(
114
+ `${result.error} Configure it at: ${this.serviceUrl}${result.url}`,
115
+ result.url
116
+ );
117
+ }
118
+ throw new BitfabError(result.error);
119
+ }
120
+ return result;
121
+ } catch (error) {
122
+ if (error instanceof BitfabError) {
123
+ throw error;
124
+ }
125
+ if (error instanceof Error) {
126
+ if (error.name === "AbortError") {
127
+ throw new BitfabError(`Request timed out after ${timeout}ms`);
128
+ }
129
+ throw new BitfabError(error.message);
130
+ }
131
+ throw new BitfabError("Unknown error occurred");
132
+ } finally {
133
+ clearTimeout(timeoutId);
134
+ }
135
+ }
136
+ /**
137
+ * Look up a function by name.
138
+ * Blocks until complete - needed for function execution.
139
+ */
140
+ async lookupFunction(name) {
141
+ return this.request("/api/sdk/functions/lookup", { name });
142
+ }
143
+ /**
144
+ * Send an internal trace (from BAML execution).
145
+ * Fire-and-forget with awaitOnExit - doesn't block the caller.
146
+ */
147
+ sendInternalTrace(functionId, payload) {
148
+ void awaitOnExit(
149
+ this.request(`/api/sdk/functions/${functionId}/traces`, {
150
+ ...payload,
151
+ sdkVersion: __version__
152
+ })
153
+ ).catch((error) => {
154
+ try {
155
+ console.error("Bitfab: Failed to create trace:", error);
156
+ } catch {
157
+ }
158
+ });
159
+ }
160
+ /**
161
+ * Send an external span (from withSpan wrapper or OpenAI tracing).
162
+ * Fire-and-forget with awaitOnExit - doesn't block the caller.
163
+ * Returns the tracked promise so callers can optionally await it.
164
+ */
165
+ sendExternalSpan(payload) {
166
+ return awaitOnExit(
167
+ this.request("/api/sdk/externalSpans", {
168
+ ...payload,
169
+ sdkVersion: __version__
170
+ })
171
+ ).catch((error) => {
172
+ try {
173
+ console.error("Bitfab: Failed to create external span:", error);
174
+ } catch {
175
+ }
176
+ });
177
+ }
178
+ /**
179
+ * Send an external trace (from OpenAI tracing).
180
+ * Fire-and-forget with awaitOnExit - doesn't block the caller.
181
+ * Returns the tracked promise so callers can optionally await it
182
+ * (the replay path does, so trace completions are persisted before
183
+ * `completeReplay` builds the trace-ID mapping).
184
+ */
185
+ sendExternalTrace(payload) {
186
+ return awaitOnExit(
187
+ this.request("/api/sdk/externalTraces", {
188
+ ...payload,
189
+ sdkVersion: __version__
190
+ })
191
+ ).catch((error) => {
192
+ try {
193
+ console.error("Bitfab: Failed to create external trace:", error);
194
+ } catch {
195
+ }
196
+ });
197
+ }
198
+ /**
199
+ * Partial update of an existing external trace identified by sourceTraceId.
200
+ * Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
201
+ * returns a tracked promise that callers may optionally await.
202
+ */
203
+ patchTrace(sourceTraceId, payload) {
204
+ const endpoint = `/api/sdk/externalTraces/${encodeURIComponent(sourceTraceId)}`;
205
+ return awaitOnExit(
206
+ this.request(endpoint, payload, { method: "PATCH" })
207
+ ).catch((error) => {
208
+ try {
209
+ console.error("Bitfab: Failed to patch trace:", error);
210
+ } catch {
211
+ }
212
+ });
213
+ }
214
+ /**
215
+ * Start a replay session by fetching historical traces.
216
+ * Blocking call — creates a test run and returns lightweight item references.
217
+ */
218
+ async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId) {
219
+ const payload = { traceFunctionKey };
220
+ if (limit !== void 0) {
221
+ payload.limit = limit;
222
+ }
223
+ if (traceIds) {
224
+ payload.traceIds = traceIds;
225
+ }
226
+ if (codeChangeDescription !== void 0) {
227
+ payload.codeChangeDescription = codeChangeDescription;
228
+ }
229
+ if (codeChangeFiles !== void 0) {
230
+ payload.codeChangeFiles = codeChangeFiles;
231
+ }
232
+ if (includeDbBranchLease) {
233
+ payload.includeDbBranchLease = true;
234
+ }
235
+ if (experimentGroupId !== void 0) {
236
+ payload.experimentGroupId = experimentGroupId;
237
+ }
238
+ const timeout = includeDbBranchLease ? 18e4 : 3e4;
239
+ return this.request("/api/sdk/replay/start", payload, {
240
+ timeout
241
+ });
242
+ }
243
+ /**
244
+ * Fetch an external span by ID.
245
+ * Blocking GET request.
246
+ */
247
+ async getExternalSpan(spanId) {
248
+ const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}`;
249
+ const controller = new AbortController();
250
+ const timeoutId = setTimeout(() => controller.abort(), 3e4);
251
+ try {
252
+ const response = await fetch(url, {
253
+ method: "GET",
254
+ headers: { Authorization: `Bearer ${this.apiKey}` },
255
+ signal: controller.signal
256
+ });
257
+ if (!response.ok) {
258
+ const errorText = await response.text();
259
+ throw new BitfabError(
260
+ `HTTP ${response.status}: ${errorText.slice(0, 500)}`
261
+ );
262
+ }
263
+ return await response.json();
264
+ } catch (error) {
265
+ if (error instanceof BitfabError) {
266
+ throw error;
267
+ }
268
+ if (error instanceof Error) {
269
+ if (error.name === "AbortError") {
270
+ throw new BitfabError("Request timed out after 30000ms");
271
+ }
272
+ throw new BitfabError(error.message);
273
+ }
274
+ throw new BitfabError("Unknown error occurred");
275
+ } finally {
276
+ clearTimeout(timeoutId);
277
+ }
278
+ }
279
+ /**
280
+ * Fetch the span tree for a root span.
281
+ * Blocking GET request.
282
+ */
283
+ async getSpanTree(externalSpanId) {
284
+ const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}`;
285
+ const controller = new AbortController();
286
+ const timeoutId = setTimeout(() => controller.abort(), 3e4);
287
+ try {
288
+ const response = await fetch(url, {
289
+ method: "GET",
290
+ headers: { Authorization: `Bearer ${this.apiKey}` },
291
+ signal: controller.signal
292
+ });
293
+ if (!response.ok) {
294
+ const errorText = await response.text();
295
+ throw new BitfabError(
296
+ `HTTP ${response.status}: ${errorText.slice(0, 500)}`
297
+ );
298
+ }
299
+ return await response.json();
300
+ } catch (error) {
301
+ if (error instanceof BitfabError) {
302
+ throw error;
303
+ }
304
+ if (error instanceof Error) {
305
+ if (error.name === "AbortError") {
306
+ throw new BitfabError("Request timed out after 30000ms");
307
+ }
308
+ throw new BitfabError(error.message);
309
+ }
310
+ throw new BitfabError("Unknown error occurred");
311
+ } finally {
312
+ clearTimeout(timeoutId);
313
+ }
314
+ }
315
+ /**
316
+ * Mark a replay test run as completed.
317
+ * Blocking call.
318
+ */
319
+ async completeReplay(testRunId) {
320
+ return this.request(
321
+ "/api/sdk/replay/complete",
322
+ { testRunId },
323
+ { timeout: 3e4 }
324
+ );
325
+ }
326
+ /**
327
+ * Ask the server to materialize a per-trace DB branch lease from a
328
+ * captured `dbSnapshotRef`. Blocking — the resolver creates a Neon
329
+ * snapshot + preview branch and polls operations to readiness, which
330
+ * can take seconds.
331
+ */
332
+ async resolveDbBranchLease(testRunId, traceId, dbSnapshotRef) {
333
+ return this.request(
334
+ "/api/sdk/replay/resolveDbBranchLease",
335
+ { testRunId, traceId, dbSnapshotRef },
336
+ { timeout: 9e4 }
337
+ );
338
+ }
339
+ /** Release a previously-resolved DB branch by deleting its Neon branch. Idempotent server-side. */
340
+ async releaseDbBranchLease(neonBranchId) {
341
+ await this.request(
342
+ "/api/sdk/replay/releaseDbBranchLease",
343
+ { neonBranchId },
344
+ { timeout: 3e4 }
345
+ );
346
+ }
347
+ };
12
348
 
13
349
  // src/claudeAgentSdk.ts
14
350
  function nowIso() {
@@ -2162,9 +2498,18 @@ var Bitfab = class {
2162
2498
  spanType: options.type ?? "custom"
2163
2499
  };
2164
2500
  const sendSpan = async (params) => {
2501
+ const replayCtx = getReplayContext();
2502
+ const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
2503
+ let resolvePersistence;
2504
+ if (persistenceCollector) {
2505
+ persistenceCollector.push(
2506
+ new Promise((resolve) => {
2507
+ resolvePersistence = resolve;
2508
+ })
2509
+ );
2510
+ }
2165
2511
  try {
2166
2512
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2167
- const replayCtx = getReplayContext();
2168
2513
  const spanPromise = self.sendWrapperSpan({
2169
2514
  ...baseSpanParams,
2170
2515
  ...params,
@@ -2179,13 +2524,17 @@ var Bitfab = class {
2179
2524
  if (isRootSpan) {
2180
2525
  const pending = pendingSpanPromises.get(traceId) ?? [];
2181
2526
  pending.push(spanPromise);
2182
- await Promise.race([
2183
- Promise.allSettled(pending),
2184
- new Promise((resolve) => setTimeout(resolve, 5e3))
2185
- ]);
2527
+ if (persistenceCollector) {
2528
+ await Promise.allSettled(pending);
2529
+ } else {
2530
+ await Promise.race([
2531
+ Promise.allSettled(pending),
2532
+ new Promise((resolve) => setTimeout(resolve, 5e3))
2533
+ ]);
2534
+ }
2186
2535
  pendingSpanPromises.delete(traceId);
2187
2536
  const traceState = activeTraceStates.get(traceId);
2188
- self.sendTraceCompletion({
2537
+ const completionPromise = self.sendTraceCompletion({
2189
2538
  traceFunctionKey,
2190
2539
  traceId,
2191
2540
  startedAt: traceState?.startedAt ?? startedAt,
@@ -2198,6 +2547,9 @@ var Bitfab = class {
2198
2547
  dbSnapshotRef: traceState?.dbSnapshotRef
2199
2548
  });
2200
2549
  activeTraceStates.delete(traceId);
2550
+ if (persistenceCollector) {
2551
+ await completionPromise;
2552
+ }
2201
2553
  } else {
2202
2554
  const pending = pendingSpanPromises.get(traceId);
2203
2555
  if (pending) {
@@ -2207,6 +2559,8 @@ var Bitfab = class {
2207
2559
  }
2208
2560
  }
2209
2561
  } catch {
2562
+ } finally {
2563
+ resolvePersistence?.();
2210
2564
  }
2211
2565
  };
2212
2566
  const replayCtxForMock = getReplayContext();
@@ -2359,7 +2713,7 @@ var Bitfab = class {
2359
2713
  if (params.dbSnapshotRef) {
2360
2714
  rawTrace.db_snapshot_ref = params.dbSnapshotRef;
2361
2715
  }
2362
- this.httpClient.sendExternalTrace({
2716
+ return this.httpClient.sendExternalTrace({
2363
2717
  type: "sdk-function",
2364
2718
  source: "typescript-sdk-function",
2365
2719
  traceFunctionKey: params.traceFunctionKey,
@@ -2439,7 +2793,7 @@ var Bitfab = class {
2439
2793
  * @returns ReplayResult with items, testRunId, and testRunUrl
2440
2794
  */
2441
2795
  async replay(traceFunctionKey, fn, options) {
2442
- const { replay: doReplay } = await import("./replay-LNP2K3DN.js");
2796
+ const { replay: doReplay } = await import("./replay-WIBKB3BK.js");
2443
2797
  return doReplay(
2444
2798
  this.httpClient,
2445
2799
  this.serviceUrl,
@@ -2505,6 +2859,9 @@ var BitfabFunction = class {
2505
2859
  };
2506
2860
 
2507
2861
  export {
2862
+ __version__,
2863
+ DEFAULT_SERVICE_URL,
2864
+ flushTraces,
2508
2865
  BitfabClaudeAgentHandler,
2509
2866
  SUPPORTED_PROVIDERS,
2510
2867
  BitfabLangGraphCallbackHandler,
@@ -2515,4 +2872,4 @@ export {
2515
2872
  Bitfab,
2516
2873
  BitfabFunction
2517
2874
  };
2518
- //# sourceMappingURL=chunk-RMQX546G.js.map
2875
+ //# sourceMappingURL=chunk-53G5GR7B.js.map