@bitfab/sdk 0.33.7 → 0.34.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,5 +1,7 @@
1
1
  import {
2
2
  BitfabError,
3
+ DEFAULT_SERVICE_URL,
4
+ HttpClient,
3
5
  __privateAdd,
4
6
  __privateGet,
5
7
  __privateSet,
@@ -14,513 +16,7 @@ import {
14
16
  toJsonSafe,
15
17
  toJsonSafeReport,
16
18
  warnOnce
17
- } from "./chunk-DPV6PBWE.js";
18
-
19
- // src/version.generated.ts
20
- var __version__ = "0.33.7";
21
-
22
- // src/constants.ts
23
- var DEFAULT_SERVICE_URL = "https://bitfab.ai";
24
-
25
- // src/unrefTimer.ts
26
- function unrefTimer(timer) {
27
- const handle = timer;
28
- if (typeof handle.unref === "function") {
29
- handle.unref();
30
- }
31
- }
32
-
33
- // src/http.ts
34
- var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
35
- function serializePayloadBody(payload) {
36
- try {
37
- return { body: JSON.stringify(payload), dropped: [] };
38
- } catch {
39
- const dropped = [];
40
- const sanitize = (value, seen) => {
41
- const t = typeof value;
42
- if (value === null || t === "string" || t === "number" || t === "boolean") {
43
- return value;
44
- }
45
- if (t === "bigint") {
46
- dropped.push("BigInt");
47
- return "<unserializable: BigInt>";
48
- }
49
- if (t === "function") {
50
- const name = value.name || "Function";
51
- dropped.push(name);
52
- return `<unserializable: ${name}>`;
53
- }
54
- if (t === "symbol") {
55
- dropped.push("Symbol");
56
- return "<unserializable: Symbol>";
57
- }
58
- if (t !== "object") {
59
- return void 0;
60
- }
61
- const obj = value;
62
- const className = obj.constructor?.name || "object";
63
- if (seen.has(obj)) {
64
- dropped.push(className);
65
- return `<cycle: ${className}>`;
66
- }
67
- seen.add(obj);
68
- let result;
69
- if (Array.isArray(obj)) {
70
- result = obj.map((item) => sanitize(item, seen));
71
- } else if (typeof obj.toJSON === "function") {
72
- try {
73
- result = sanitize(obj.toJSON(), seen);
74
- } catch {
75
- dropped.push(className);
76
- result = `<unserializable: ${className}>`;
77
- }
78
- } else {
79
- try {
80
- const out = {};
81
- for (const [k, v] of Object.entries(obj)) {
82
- out[k] = sanitize(v, seen);
83
- }
84
- result = out;
85
- } catch {
86
- warnOnce(
87
- "payload:field-getter-threw",
88
- "a value with a throwing getter/proxy could not be serialized into a span payload; it was replaced with a placeholder. The span still ships with its other fields intact."
89
- );
90
- dropped.push(className);
91
- result = `<unserializable: ${className}>`;
92
- }
93
- }
94
- seen.delete(obj);
95
- return result;
96
- };
97
- let sanitized;
98
- try {
99
- sanitized = sanitize(payload, /* @__PURE__ */ new WeakSet());
100
- } catch (error) {
101
- const message = error instanceof Error ? error.message : String(error);
102
- return {
103
- body: JSON.stringify({ error: `payload_serialize_failed: ${message}` }),
104
- dropped
105
- };
106
- }
107
- if (dropped.length > 0 && typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
108
- const obj = sanitized;
109
- const existing = Array.isArray(obj.errors) ? obj.errors : [];
110
- obj.errors = [
111
- ...existing,
112
- {
113
- source: "sdk",
114
- step: "json_serialize",
115
- error: `stubbed non-serializable value(s): ${[
116
- ...new Set(dropped)
117
- ].join(", ")}`
118
- }
119
- ];
120
- }
121
- return { body: JSON.stringify(sanitized), dropped };
122
- }
123
- }
124
- var pendingTracePromises = /* @__PURE__ */ new Set();
125
- function awaitOnExit(promise) {
126
- pendingTracePromises.add(promise);
127
- void promise.finally(() => {
128
- pendingTracePromises.delete(promise);
129
- }).catch(() => {
130
- });
131
- return promise;
132
- }
133
- async function flushTraces(timeoutMs = 5e3) {
134
- if (pendingTracePromises.size === 0) {
135
- return;
136
- }
137
- let timer;
138
- try {
139
- await Promise.race([
140
- Promise.allSettled(Array.from(pendingTracePromises)),
141
- new Promise((resolve) => {
142
- timer = setTimeout(resolve, timeoutMs);
143
- unrefTimer(timer);
144
- })
145
- ]);
146
- } finally {
147
- if (timer) {
148
- clearTimeout(timer);
149
- }
150
- }
151
- }
152
- if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
153
- let isFlushing = false;
154
- process.on("beforeExit", () => {
155
- if (pendingTracePromises.size > 0 && !isFlushing) {
156
- isFlushing = true;
157
- Promise.allSettled(
158
- Array.from(pendingTracePromises).map(
159
- (p) => p.catch(() => {
160
- })
161
- )
162
- ).then(() => {
163
- isFlushing = false;
164
- }).catch(() => {
165
- isFlushing = false;
166
- });
167
- }
168
- });
169
- }
170
- var HttpClient = class {
171
- constructor(config) {
172
- this.apiKey = config.apiKey;
173
- this.serviceUrl = config.serviceUrl;
174
- this.timeout = config.timeout ?? 12e4;
175
- }
176
- /**
177
- * Resolve the API key at the moment it is needed (request time), invoking
178
- * the function form if one was supplied. Never read at construction.
179
- */
180
- resolveApiKey() {
181
- return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
182
- }
183
- /**
184
- * Make an HTTP request to the Bitfab API. Defaults to POST; pass
185
- * `options.method` to use a different verb (e.g. "PATCH").
186
- *
187
- * @param endpoint - The API endpoint (without base URL)
188
- * @param payload - The request body
189
- * @param options - Optional request options
190
- * @returns The parsed JSON response
191
- * @throws {BitfabError} If the request fails
192
- */
193
- async request(endpoint, payload, options) {
194
- const url = `${this.serviceUrl}${endpoint}`;
195
- const timeout = options?.timeout ?? this.timeout;
196
- const method = options?.method ?? "POST";
197
- const controller = new AbortController();
198
- const timeoutId = setTimeout(() => controller.abort(), timeout);
199
- const { body, dropped } = serializePayloadBody(payload);
200
- if (dropped.length > 0) {
201
- try {
202
- console.warn(
203
- `Bitfab: request body to ${endpoint} held ${dropped.length} non-serializable value(s) (${[...new Set(dropped)].join(", ")}); they were stubbed so the span still sends, but the trace may be incomplete or not replayable. Capture a JSON-safe projection of this input to make it replayable.`
204
- );
205
- } catch {
206
- }
207
- }
208
- try {
209
- const response = await fetch(url, {
210
- method,
211
- headers: {
212
- "Content-Type": "application/json",
213
- Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
214
- },
215
- body,
216
- signal: controller.signal
217
- });
218
- if (!response.ok) {
219
- const errorText = await response.text();
220
- throw new BitfabError(
221
- `HTTP ${response.status}: ${errorText.slice(0, 500)}`
222
- );
223
- }
224
- const result = await response.json();
225
- if (result.error) {
226
- if (result.url) {
227
- throw new BitfabError(
228
- `${result.error} Configure it at: ${this.serviceUrl}${result.url}`,
229
- result.url
230
- );
231
- }
232
- throw new BitfabError(result.error);
233
- }
234
- return result;
235
- } catch (error) {
236
- if (error instanceof BitfabError) {
237
- throw error;
238
- }
239
- if (error instanceof Error) {
240
- if (error.name === "AbortError") {
241
- throw new BitfabError(`Request timed out after ${timeout}ms`);
242
- }
243
- throw new BitfabError(error.message);
244
- }
245
- throw new BitfabError("Unknown error occurred");
246
- } finally {
247
- clearTimeout(timeoutId);
248
- }
249
- }
250
- /**
251
- * Look up a function by name.
252
- * Blocks until complete - needed for function execution.
253
- */
254
- async lookupFunction(name) {
255
- return this.request("/api/sdk/functions/lookup", { name });
256
- }
257
- async getTraceSpan(traceId, lookup) {
258
- const searchParams = new URLSearchParams();
259
- if (lookup.id !== void 0) {
260
- searchParams.set("id", lookup.id);
261
- } else {
262
- searchParams.set("name", lookup.name);
263
- searchParams.set("occurrence", String(lookup.occurrence ?? "last"));
264
- }
265
- const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}/span?${searchParams.toString()}`;
266
- const response = await this.get(endpoint);
267
- return response.span;
268
- }
269
- async get(endpoint) {
270
- const url = `${this.serviceUrl}${endpoint}`;
271
- const controller = new AbortController();
272
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
273
- try {
274
- const response = await fetch(url, {
275
- method: "GET",
276
- headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
277
- signal: controller.signal
278
- });
279
- if (!response.ok) {
280
- const errorText = await response.text();
281
- throw new BitfabError(
282
- `HTTP ${response.status}: ${errorText.slice(0, 500)}`
283
- );
284
- }
285
- return await response.json();
286
- } catch (error) {
287
- if (error instanceof BitfabError) {
288
- throw error;
289
- }
290
- if (error instanceof Error) {
291
- if (error.name === "AbortError") {
292
- throw new BitfabError(`Request timed out after ${this.timeout}ms`);
293
- }
294
- throw new BitfabError(error.message);
295
- }
296
- throw new BitfabError("Unknown error occurred");
297
- } finally {
298
- clearTimeout(timeoutId);
299
- }
300
- }
301
- /**
302
- * Send an internal trace (from BAML execution).
303
- * Fire-and-forget with awaitOnExit - doesn't block the caller.
304
- */
305
- sendInternalTrace(functionId, payload) {
306
- void awaitOnExit(
307
- this.request(`/api/sdk/functions/${functionId}/traces`, {
308
- ...payload,
309
- sdkVersion: __version__
310
- })
311
- ).catch((error) => {
312
- try {
313
- console.error("Bitfab: Failed to create trace:", error);
314
- } catch {
315
- }
316
- });
317
- }
318
- /**
319
- * Send an external span (from withSpan wrapper or OpenAI tracing).
320
- * Fire-and-forget with awaitOnExit - doesn't block the caller.
321
- * Returns the tracked promise so callers can optionally await it.
322
- */
323
- sendExternalSpan(payload) {
324
- return awaitOnExit(
325
- this.request("/api/sdk/externalSpans", {
326
- ...payload,
327
- sdkVersion: __version__
328
- })
329
- ).catch((error) => {
330
- try {
331
- console.error("Bitfab: Failed to create external span:", error);
332
- } catch {
333
- }
334
- });
335
- }
336
- /**
337
- * Send an external trace (from OpenAI tracing).
338
- * Fire-and-forget with awaitOnExit - doesn't block the caller.
339
- * Returns the tracked promise so callers can optionally await it
340
- * (the replay path does, so trace completions are persisted before
341
- * `completeReplay` builds the trace-ID mapping).
342
- */
343
- sendExternalTrace(payload) {
344
- return awaitOnExit(
345
- this.request("/api/sdk/externalTraces", {
346
- ...payload,
347
- sdkVersion: __version__
348
- })
349
- ).catch((error) => {
350
- try {
351
- console.error("Bitfab: Failed to create external trace:", error);
352
- } catch {
353
- }
354
- });
355
- }
356
- /**
357
- * Partial update of an existing trace identified by its Bitfab trace ID.
358
- * Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
359
- * returns a tracked promise that callers may optionally await.
360
- */
361
- patchTrace(traceId, payload) {
362
- const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}`;
363
- return awaitOnExit(
364
- this.request(endpoint, payload, { method: "PATCH" })
365
- ).catch((error) => {
366
- try {
367
- console.error("Bitfab: Failed to patch trace:", error);
368
- } catch {
369
- }
370
- });
371
- }
372
- /**
373
- * Start a replay session by fetching historical traces.
374
- * Blocking call - creates a test run and returns lightweight item references.
375
- */
376
- async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds, dbBranchSettings) {
377
- const payload = { traceFunctionKey };
378
- if (limit !== void 0) {
379
- payload.limit = limit;
380
- }
381
- if (traceIds) {
382
- payload.traceIds = traceIds;
383
- }
384
- if (name !== void 0) {
385
- payload.name = name;
386
- }
387
- if (codeChangeDescription !== void 0) {
388
- payload.codeChangeDescription = codeChangeDescription;
389
- }
390
- if (codeChangeFiles !== void 0) {
391
- payload.codeChangeFiles = codeChangeFiles;
392
- }
393
- if (includeDbBranchLease) {
394
- payload.includeDbBranchLease = true;
395
- payload.lazyDbBranchLease = true;
396
- }
397
- if (experimentGroupId !== void 0) {
398
- payload.experimentGroupId = experimentGroupId;
399
- }
400
- if (datasetId !== void 0) {
401
- payload.datasetId = datasetId;
402
- }
403
- if (graderIds !== void 0) {
404
- payload.graderIds = graderIds;
405
- }
406
- if (dbBranchSettings !== void 0) {
407
- payload.dbBranchSettings = dbBranchSettings;
408
- }
409
- const timeout = includeDbBranchLease ? REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS : 3e4;
410
- return this.request("/api/sdk/replay/start", payload, {
411
- timeout
412
- });
413
- }
414
- /**
415
- * Fetch an external span by ID.
416
- * Blocking GET request.
417
- */
418
- async getExternalSpan(spanId) {
419
- const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}`;
420
- const controller = new AbortController();
421
- const timeoutId = setTimeout(() => controller.abort(), 3e4);
422
- try {
423
- const response = await fetch(url, {
424
- method: "GET",
425
- headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
426
- signal: controller.signal
427
- });
428
- if (!response.ok) {
429
- const errorText = await response.text();
430
- throw new BitfabError(
431
- `HTTP ${response.status}: ${errorText.slice(0, 500)}`
432
- );
433
- }
434
- return await response.json();
435
- } catch (error) {
436
- if (error instanceof BitfabError) {
437
- throw error;
438
- }
439
- if (error instanceof Error) {
440
- if (error.name === "AbortError") {
441
- throw new BitfabError("Request timed out after 30000ms");
442
- }
443
- throw new BitfabError(error.message);
444
- }
445
- throw new BitfabError("Unknown error occurred");
446
- } finally {
447
- clearTimeout(timeoutId);
448
- }
449
- }
450
- /**
451
- * Fetch the span tree for a root span.
452
- * Blocking GET request.
453
- *
454
- * Pass `includeOutputs: false` for a payload-free tree (structure +
455
- * `externalSpanId` only), so recorded outputs are fetched lazily per mocked
456
- * span instead of all up front. Omit it (default eager) for `mock: "all"`.
457
- */
458
- async getSpanTree(externalSpanId, options) {
459
- const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
460
- const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
461
- const controller = new AbortController();
462
- const timeoutId = setTimeout(() => controller.abort(), 3e4);
463
- try {
464
- const response = await fetch(url, {
465
- method: "GET",
466
- headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
467
- signal: controller.signal
468
- });
469
- if (!response.ok) {
470
- const errorText = await response.text();
471
- throw new BitfabError(
472
- `HTTP ${response.status}: ${errorText.slice(0, 500)}`
473
- );
474
- }
475
- return await response.json();
476
- } catch (error) {
477
- if (error instanceof BitfabError) {
478
- throw error;
479
- }
480
- if (error instanceof Error) {
481
- if (error.name === "AbortError") {
482
- throw new BitfabError("Request timed out after 30000ms");
483
- }
484
- throw new BitfabError(error.message);
485
- }
486
- throw new BitfabError("Unknown error occurred");
487
- } finally {
488
- clearTimeout(timeoutId);
489
- }
490
- }
491
- /**
492
- * Mark a replay test run as completed.
493
- * Blocking call.
494
- */
495
- async completeReplay(testRunId) {
496
- return this.request(
497
- "/api/sdk/replay/complete",
498
- { testRunId },
499
- { timeout: 3e4 }
500
- );
501
- }
502
- /**
503
- * Ask the server to materialize a per-trace DB branch lease from a
504
- * captured `dbSnapshotRef`. Blocking - the resolver creates a Neon
505
- * snapshot + preview branch and polls operations to readiness, which
506
- * can take seconds.
507
- */
508
- async resolveDbBranchLease(testRunId, traceId, dbBranchSettings) {
509
- return this.request(
510
- "/api/sdk/replay/resolveDbBranchLease",
511
- { testRunId, traceId, dbBranchSettings },
512
- { timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
513
- );
514
- }
515
- /** Release a previously-resolved DB branch by deleting its Neon branch. Idempotent server-side. */
516
- async releaseDbBranchLease(neonBranchId) {
517
- await this.request(
518
- "/api/sdk/replay/releaseDbBranchLease",
519
- { neonBranchId },
520
- { timeout: 3e4 }
521
- );
522
- }
523
- };
19
+ } from "./chunk-4J36FZ4K.js";
524
20
 
525
21
  // src/processorPayload.ts
526
22
  var SERIALIZATION_DEGRADED_STEP = "serialization_degraded";
@@ -641,7 +137,8 @@ var BitfabClaudeAgentHandler = class {
641
137
  // its root. The prompt is not present anywhere in the message stream, so it
642
138
  // must be handed in explicitly.
643
139
  this.hasRootInput = false;
644
- this.httpClient = new HttpClient({
140
+ this.ownsHttpClient = config._httpClient === void 0;
141
+ this.httpClient = config._httpClient ?? new HttpClient({
645
142
  apiKey: config.apiKey,
646
143
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
647
144
  timeout: config.timeout ?? 1e4
@@ -654,6 +151,14 @@ var BitfabClaudeAgentHandler = class {
654
151
  this.subagentStartHook = this.subagentStartHook.bind(this);
655
152
  this.subagentStopHook = this.subagentStopHook.bind(this);
656
153
  }
154
+ /**
155
+ * Flush and release the span transport this handler started. A no-op when
156
+ * the handler borrowed a `Bitfab` client's HTTP client: that client's
157
+ * `close()` owns the worker's lifetime.
158
+ */
159
+ async close(timeoutMs) {
160
+ return this.ownsHttpClient ? this.httpClient.close(timeoutMs) : true;
161
+ }
657
162
  // ── trace lifecycle ──────────────────────────────────────────
658
163
  ensureTrace() {
659
164
  if (this.traceId !== null) {
@@ -1659,7 +1164,8 @@ var BitfabLangGraphCallbackHandler = class {
1659
1164
  this.ignoreCustomEvent = true;
1660
1165
  this.runToSpan = /* @__PURE__ */ new Map();
1661
1166
  this.invocations = /* @__PURE__ */ new Map();
1662
- this.httpClient = new HttpClient({
1167
+ this.ownsHttpClient = config._httpClient === void 0;
1168
+ this.httpClient = config._httpClient ?? new HttpClient({
1663
1169
  apiKey: config.apiKey,
1664
1170
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
1665
1171
  timeout: config.timeout ?? 1e4
@@ -1667,6 +1173,14 @@ var BitfabLangGraphCallbackHandler = class {
1667
1173
  this.traceFunctionKey = config.traceFunctionKey;
1668
1174
  this.getActiveSpanContext = config.getActiveSpanContext ?? null;
1669
1175
  }
1176
+ /**
1177
+ * Flush and release the span transport this handler started. A no-op when
1178
+ * the handler borrowed a `Bitfab` client's HTTP client: that client's
1179
+ * `close()` owns the worker's lifetime.
1180
+ */
1181
+ async close(timeoutMs) {
1182
+ return this.ownsHttpClient ? this.httpClient.close(timeoutMs) : true;
1183
+ }
1670
1184
  // ── lifecycle helpers ──────────────────────────────────────────
1671
1185
  startSpan(runId, parentRunId, name, spanType, inputData, metadata, tags) {
1672
1186
  const parentSpan = parentRunId ? this.runToSpan.get(parentRunId) : void 0;
@@ -2138,7 +1652,8 @@ var BitfabOpenAITracingProcessor = class {
2138
1652
  this.activeTraces = {};
2139
1653
  this.activeSpanMappings = {};
2140
1654
  this.canonicalTraceIds = {};
2141
- this.httpClient = new HttpClient({
1655
+ this.ownsHttpClient = config._httpClient === void 0;
1656
+ this.httpClient = config._httpClient ?? new HttpClient({
2142
1657
  apiKey: config.apiKey,
2143
1658
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
2144
1659
  timeout: config.timeout ?? 1e4
@@ -2154,6 +1669,14 @@ var BitfabOpenAITracingProcessor = class {
2154
1669
  this.canonicalTraceIds[sourceTraceId] = created;
2155
1670
  return created;
2156
1671
  }
1672
+ /**
1673
+ * Flush and release the span transport this processor started. A no-op when
1674
+ * the processor borrowed a `Bitfab` client's HTTP client: that client's
1675
+ * `close()` owns the worker's lifetime.
1676
+ */
1677
+ async close(timeoutMs) {
1678
+ return this.ownsHttpClient ? this.httpClient.close(timeoutMs) : true;
1679
+ }
2157
1680
  /**
2158
1681
  * Called when a trace is started.
2159
1682
  * If there's an active withSpan context, the trace ID is remapped to the
@@ -2208,14 +1731,16 @@ var BitfabOpenAITracingProcessor = class {
2208
1731
  * Called when a trace is being flushed.
2209
1732
  */
2210
1733
  async forceFlush() {
1734
+ await this.httpClient.waitForPendingRequests();
2211
1735
  }
2212
1736
  /**
2213
1737
  * Called when the trace processor is shutting down.
2214
1738
  */
2215
- async shutdown(_timeout) {
1739
+ async shutdown(timeout) {
2216
1740
  this.activeTraces = {};
2217
1741
  this.activeSpanMappings = {};
2218
1742
  this.canonicalTraceIds = {};
1743
+ await this.close(timeout);
2219
1744
  }
2220
1745
  /**
2221
1746
  * Send trace to Bitfab API (fire-and-forget).
@@ -2481,7 +2006,6 @@ var BitfabVercelAiHandler = class {
2481
2006
 
2482
2007
  // src/client.ts
2483
2008
  var activeTraceStates = /* @__PURE__ */ new Map();
2484
- var pendingSpanPromises = /* @__PURE__ */ new Map();
2485
2009
  var asyncLocalStorage = null;
2486
2010
  var SPAN_CONTEXT_STORAGE_SYMBOL = /* @__PURE__ */ Symbol.for("bitfab.spanContextStorage");
2487
2011
  var initializeAsyncContext = () => {
@@ -2837,6 +2361,23 @@ var Bitfab = class {
2837
2361
  timeout: this.timeout
2838
2362
  });
2839
2363
  }
2364
+ /**
2365
+ * Flush and permanently close this client's tracing resources: its pending
2366
+ * requests and the single span-transport worker shared by its decorators and
2367
+ * framework handlers.
2368
+ *
2369
+ * Resolves `false` when delivery failed or the deadline expired. Long-lived
2370
+ * processes never need this (the transport batches in the background and the
2371
+ * exit hook drains it); scripts and tests that want a hard guarantee should
2372
+ * await it.
2373
+ *
2374
+ * Deliberately not a `Symbol.asyncDispose` method: the SDK targets runtimes
2375
+ * where that symbol may be absent, and a computed key on a missing symbol
2376
+ * throws at class-definition time, taking the whole SDK down on load.
2377
+ */
2378
+ close(timeoutMs) {
2379
+ return this.httpClient.close(timeoutMs);
2380
+ }
2840
2381
  /**
2841
2382
  * Resolve the API key lazily, the first time a span actually needs it.
2842
2383
  *
@@ -2986,7 +2527,8 @@ var Bitfab = class {
2986
2527
  getActiveSpanContext: () => {
2987
2528
  const stack = getSpanStack();
2988
2529
  return stack[stack.length - 1] ?? null;
2989
- }
2530
+ },
2531
+ _httpClient: this.httpClient
2990
2532
  });
2991
2533
  }
2992
2534
  /**
@@ -3046,7 +2588,8 @@ var Bitfab = class {
3046
2588
  getActiveSpanContext: () => {
3047
2589
  const stack = getSpanStack();
3048
2590
  return stack[stack.length - 1] ?? null;
3049
- }
2591
+ },
2592
+ _httpClient: this.httpClient
3050
2593
  });
3051
2594
  }
3052
2595
  /**
@@ -3098,7 +2641,8 @@ var Bitfab = class {
3098
2641
  getActiveSpanContext: () => {
3099
2642
  const stack = getSpanStack();
3100
2643
  return stack[stack.length - 1] ?? null;
3101
- }
2644
+ },
2645
+ _httpClient: this.httpClient
3102
2646
  });
3103
2647
  }
3104
2648
  /**
@@ -3351,7 +2895,6 @@ var Bitfab = class {
3351
2895
  },
3352
2896
  dbSnapshotRef
3353
2897
  });
3354
- pendingSpanPromises.set(traceId, []);
3355
2898
  registeredTraceId = traceId;
3356
2899
  }
3357
2900
  const functionName = fn.name !== "" ? fn.name : void 0;
@@ -3366,57 +2909,29 @@ var Bitfab = class {
3366
2909
  startedAt,
3367
2910
  spanType: options.type ?? "custom"
3368
2911
  };
3369
- const sendSpan = async (params, spanOpts) => {
2912
+ const sendSpan = async (params) => {
3370
2913
  const replayCtx = getReplayContext();
3371
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3372
- let resolvePersistence;
3373
- if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3374
- persistenceCollector.push(
3375
- new Promise((resolve) => {
3376
- resolvePersistence = resolve;
3377
- })
3378
- );
3379
- }
3380
2914
  try {
3381
2915
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
3382
2916
  const traceDropped = activeTraceStates.get(traceId)?.dropped === true;
3383
- const spanPromise = traceDropped ? Promise.resolve() : self.sendWrapperSpan({
3384
- ...baseSpanParams,
3385
- ...params,
3386
- contexts: newContext.contexts,
3387
- prompt: newContext.prompt,
3388
- endedAt,
3389
- ...replayCtx?.testRunId && {
3390
- testRunId: replayCtx.testRunId
3391
- },
3392
- ...replayCtx?.inputSourceSpanId && {
3393
- inputSourceSpanId: replayCtx.inputSourceSpanId
3394
- }
3395
- });
3396
- if (isRootSpan) {
3397
- const pending = pendingSpanPromises.get(traceId) ?? [];
3398
- pending.push(spanPromise);
3399
- if (persistenceCollector) {
3400
- await Promise.allSettled(pending);
3401
- } else {
3402
- let raceTimer;
3403
- try {
3404
- await Promise.race([
3405
- Promise.allSettled(pending),
3406
- new Promise((resolve) => {
3407
- raceTimer = setTimeout(resolve, 5e3);
3408
- unrefTimer(raceTimer);
3409
- })
3410
- ]);
3411
- } finally {
3412
- if (raceTimer) {
3413
- clearTimeout(raceTimer);
3414
- }
2917
+ if (!traceDropped) {
2918
+ self.sendWrapperSpan({
2919
+ ...baseSpanParams,
2920
+ ...params,
2921
+ contexts: newContext.contexts,
2922
+ prompt: newContext.prompt,
2923
+ endedAt,
2924
+ ...replayCtx?.testRunId && {
2925
+ testRunId: replayCtx.testRunId
2926
+ },
2927
+ ...replayCtx?.inputSourceSpanId && {
2928
+ inputSourceSpanId: replayCtx.inputSourceSpanId
3415
2929
  }
3416
- }
3417
- pendingSpanPromises.delete(traceId);
2930
+ });
2931
+ }
2932
+ if (isRootSpan) {
3418
2933
  const traceState = activeTraceStates.get(traceId);
3419
- const completionPromise = self.sendTraceCompletion({
2934
+ self.sendTraceCompletion({
3420
2935
  traceFunctionKey,
3421
2936
  traceId,
3422
2937
  startedAt: traceState?.startedAt ?? startedAt,
@@ -3442,20 +2957,8 @@ var Bitfab = class {
3442
2957
  }
3443
2958
  });
3444
2959
  activeTraceStates.delete(traceId);
3445
- if (persistenceCollector) {
3446
- await completionPromise;
3447
- }
3448
- } else {
3449
- const pending = pendingSpanPromises.get(traceId);
3450
- if (pending) {
3451
- pending.push(spanPromise);
3452
- } else {
3453
- pendingSpanPromises.set(traceId, [spanPromise]);
3454
- }
3455
2960
  }
3456
2961
  } catch {
3457
- } finally {
3458
- resolvePersistence?.();
3459
2962
  }
3460
2963
  };
3461
2964
  const replayCtxForMock = getReplayContext();
@@ -3539,30 +3042,14 @@ var Bitfab = class {
3539
3042
  }
3540
3043
  const recordSpan = (result) => {
3541
3044
  if (options.finalize) {
3542
- const replayCtx = getReplayContext();
3543
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3544
- let resolvePersistence;
3545
- if (persistenceCollector) {
3546
- persistenceCollector.push(
3547
- new Promise((resolve) => {
3548
- resolvePersistence = resolve;
3549
- })
3550
- );
3551
- }
3552
- void Promise.resolve().then(() => options.finalize(result)).then(
3553
- (output) => sendSpan(
3554
- { result: output },
3555
- { skipPersistenceRegistration: true }
3556
- )
3557
- ).catch(
3558
- (error) => sendSpan(
3559
- {
3045
+ void self.httpClient.trackDeferred(
3046
+ Promise.resolve().then(() => options.finalize(result)).then((output) => sendSpan({ result: output })).catch(
3047
+ (error) => sendSpan({
3560
3048
  result: void 0,
3561
3049
  error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3562
- },
3563
- { skipPersistenceRegistration: true }
3050
+ })
3564
3051
  )
3565
- ).finally(() => resolvePersistence?.());
3052
+ );
3566
3053
  } else {
3567
3054
  void sendSpan({ result });
3568
3055
  }
@@ -3590,7 +3077,6 @@ var Bitfab = class {
3590
3077
  } catch (setupError) {
3591
3078
  if (registeredTraceId) {
3592
3079
  activeTraceStates.delete(registeredTraceId);
3593
- pendingSpanPromises.delete(registeredTraceId);
3594
3080
  }
3595
3081
  if (getReplayContext()) {
3596
3082
  throw setupError;
@@ -3713,7 +3199,7 @@ var Bitfab = class {
3713
3199
  /**
3714
3200
  * Send trace completion when a root span ends.
3715
3201
  * Internal method to record trace completion with end time.
3716
- * Fire-and-forget - sends to externalTraces endpoint via httpClient.
3202
+ * Queued on the client's span transport; delivery is the transport's job.
3717
3203
  */
3718
3204
  sendTraceCompletion(params) {
3719
3205
  const rawTrace = {
@@ -3749,7 +3235,7 @@ var Bitfab = class {
3749
3235
  accessed: params.dbSnapshotUsage.accessed
3750
3236
  };
3751
3237
  }
3752
- return this.httpClient.sendExternalTrace({
3238
+ this.httpClient.sendExternalTrace({
3753
3239
  id: params.traceId,
3754
3240
  type: "sdk-function",
3755
3241
  source: "typescript-sdk-function",
@@ -3764,7 +3250,7 @@ var Bitfab = class {
3764
3250
  /**
3765
3251
  * Send a wrapper span from function execution.
3766
3252
  * Internal method to record spans when using withSpan.
3767
- * Fire-and-forget - sends to externalSpans endpoint via httpClient.
3253
+ * Queued on the client's span transport; delivery is the transport's job.
3768
3254
  */
3769
3255
  sendWrapperSpan(params) {
3770
3256
  const serializedInputs = serializeValue(params.inputs);
@@ -3805,7 +3291,7 @@ var Bitfab = class {
3805
3291
  if (params.inputSourceSpanId) {
3806
3292
  externalSpan.input_source_span_id = params.inputSourceSpanId;
3807
3293
  }
3808
- return this.httpClient.sendExternalSpan({
3294
+ this.httpClient.sendExternalSpan({
3809
3295
  id: params.spanId,
3810
3296
  traceId: params.traceId,
3811
3297
  type: "sdk-function",
@@ -3839,7 +3325,7 @@ var Bitfab = class {
3839
3325
  `Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
3840
3326
  );
3841
3327
  }
3842
- const { replay: doReplay } = await import("./replay-SRQI4QMY.js");
3328
+ const { replay: doReplay } = await import("./replay-QFNYP7EY.js");
3843
3329
  return doReplay(
3844
3330
  this.httpClient,
3845
3331
  this.serviceUrl,
@@ -4043,9 +3529,6 @@ var finalizers = {
4043
3529
  };
4044
3530
 
4045
3531
  export {
4046
- __version__,
4047
- DEFAULT_SERVICE_URL,
4048
- flushTraces,
4049
3532
  BitfabClaudeAgentHandler,
4050
3533
  SUPPORTED_PROVIDERS,
4051
3534
  BitfabLangGraphCallbackHandler,
@@ -4059,4 +3542,4 @@ export {
4059
3542
  BitfabFunction,
4060
3543
  finalizers
4061
3544
  };
4062
- //# sourceMappingURL=chunk-DZJ5K75J.js.map
3545
+ //# sourceMappingURL=chunk-ESKBRHVG.js.map