bitfab 0.23.3 → 0.24.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/node.cjs CHANGED
@@ -100,6 +100,54 @@ var init_errors = __esm({
100
100
  }
101
101
  });
102
102
 
103
+ // src/warnOnce.ts
104
+ function warnOnce(key, message) {
105
+ if (warned.has(key)) {
106
+ return;
107
+ }
108
+ warned.add(key);
109
+ try {
110
+ console.warn(`[bitfab] ${message}`);
111
+ } catch {
112
+ }
113
+ }
114
+ var warned;
115
+ var init_warnOnce = __esm({
116
+ "src/warnOnce.ts"() {
117
+ "use strict";
118
+ warned = /* @__PURE__ */ new Set();
119
+ }
120
+ });
121
+
122
+ // src/randomUuid.ts
123
+ function randomUuid() {
124
+ const globalCrypto = globalThis.crypto;
125
+ if (typeof globalCrypto?.randomUUID === "function") {
126
+ try {
127
+ return globalCrypto.randomUUID();
128
+ } catch {
129
+ }
130
+ }
131
+ warnOnce(
132
+ "crypto-unavailable",
133
+ "global crypto.randomUUID is unavailable; using a non-cryptographic fallback for trace/span ids. Tracing works normally (ids are correlation-only, not security-sensitive)."
134
+ );
135
+ return fallbackUuidV4();
136
+ }
137
+ function fallbackUuidV4() {
138
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
139
+ const rand = Math.random() * 16 | 0;
140
+ const value = char === "x" ? rand : rand & 3 | 8;
141
+ return value.toString(16);
142
+ });
143
+ }
144
+ var init_randomUuid = __esm({
145
+ "src/randomUuid.ts"() {
146
+ "use strict";
147
+ init_warnOnce();
148
+ }
149
+ });
150
+
103
151
  // src/serialize.ts
104
152
  function describeValue(value) {
105
153
  try {
@@ -112,6 +160,10 @@ function describeValue(value) {
112
160
  return typeof value;
113
161
  }
114
162
  function unserializableStub(value, reason) {
163
+ warnOnce(
164
+ `serialize:${reason.replace(/\d+/g, "N")}`,
165
+ `a value could not be fully serialized for a span (${reason}); it was replaced with a placeholder. The span still ships, but its captured input/output is incomplete.`
166
+ );
115
167
  let summary;
116
168
  try {
117
169
  summary = `<unserializable: ${describeValue(value)} (${reason})>`;
@@ -151,7 +203,19 @@ function deserializeValue(serialized) {
151
203
  });
152
204
  }
153
205
  function toJsonSafe(value) {
154
- return toJsonSafeInner(value, 0, /* @__PURE__ */ new WeakSet());
206
+ const safe = toJsonSafeInner(value, 0, /* @__PURE__ */ new WeakSet());
207
+ try {
208
+ const size = JSON.stringify(safe)?.length ?? 0;
209
+ if (size > MAX_FRAMEWORK_SERIALIZED_BYTES) {
210
+ warnOnce(
211
+ "toJsonSafe:too_large",
212
+ `a framework payload exceeded ${MAX_FRAMEWORK_SERIALIZED_BYTES} bytes and was replaced with a placeholder so the span still ships. The captured state for this span is incomplete.`
213
+ );
214
+ return `<unserializable: too_large_${size}_bytes>`;
215
+ }
216
+ } catch {
217
+ }
218
+ return safe;
155
219
  }
156
220
  function toJsonSafeInner(value, depth, seen) {
157
221
  if (value === null || value === void 0) {
@@ -204,12 +268,14 @@ function toJsonSafeInner(value, depth, seen) {
204
268
  seen.delete(value);
205
269
  return result;
206
270
  }
207
- var import_superjson, MAX_SERIALIZED_BYTES, MAX_SAFE_DEPTH;
271
+ var import_superjson, MAX_SERIALIZED_BYTES, MAX_FRAMEWORK_SERIALIZED_BYTES, MAX_SAFE_DEPTH;
208
272
  var init_serialize = __esm({
209
273
  "src/serialize.ts"() {
210
274
  "use strict";
211
275
  import_superjson = __toESM(require("superjson"), 1);
276
+ init_warnOnce();
212
277
  MAX_SERIALIZED_BYTES = 512e3;
278
+ MAX_FRAMEWORK_SERIALIZED_BYTES = 2e6;
213
279
  MAX_SAFE_DEPTH = 6;
214
280
  }
215
281
  });
@@ -295,7 +361,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
295
361
  let originalOutput;
296
362
  let result;
297
363
  let error = null;
298
- const replayedTraceId = crypto.randomUUID();
364
+ const replayedTraceId = randomUuid();
299
365
  const pendingPersistence = [];
300
366
  try {
301
367
  const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
@@ -479,6 +545,7 @@ var init_replay = __esm({
479
545
  "src/replay.ts"() {
480
546
  "use strict";
481
547
  init_errors();
548
+ init_randomUuid();
482
549
  init_replayContext();
483
550
  init_serialize();
484
551
  }
@@ -515,13 +582,24 @@ registerAsyncLocalStorageClass(
515
582
  );
516
583
 
517
584
  // src/version.generated.ts
518
- var __version__ = "0.23.3";
585
+ var __version__ = "0.24.0";
519
586
 
520
587
  // src/constants.ts
521
588
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
522
589
 
523
590
  // src/http.ts
524
591
  init_errors();
592
+
593
+ // src/unrefTimer.ts
594
+ function unrefTimer(timer) {
595
+ const handle = timer;
596
+ if (typeof handle.unref === "function") {
597
+ handle.unref();
598
+ }
599
+ }
600
+
601
+ // src/http.ts
602
+ init_warnOnce();
525
603
  function serializePayloadBody(payload) {
526
604
  try {
527
605
  return { body: JSON.stringify(payload), dropped: [] };
@@ -566,11 +644,20 @@ function serializePayloadBody(payload) {
566
644
  result = `<unserializable: ${className}>`;
567
645
  }
568
646
  } else {
569
- const out = {};
570
- for (const [k, v] of Object.entries(obj)) {
571
- out[k] = sanitize(v, seen);
647
+ try {
648
+ const out = {};
649
+ for (const [k, v] of Object.entries(obj)) {
650
+ out[k] = sanitize(v, seen);
651
+ }
652
+ result = out;
653
+ } catch {
654
+ warnOnce(
655
+ "payload:field-getter-threw",
656
+ "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."
657
+ );
658
+ dropped.push(className);
659
+ result = `<unserializable: ${className}>`;
572
660
  }
573
- result = out;
574
661
  }
575
662
  seen.delete(obj);
576
663
  return result;
@@ -615,10 +702,20 @@ async function flushTraces(timeoutMs = 5e3) {
615
702
  if (pendingTracePromises.size === 0) {
616
703
  return;
617
704
  }
618
- await Promise.race([
619
- Promise.allSettled(Array.from(pendingTracePromises)),
620
- new Promise((resolve) => setTimeout(resolve, timeoutMs))
621
- ]);
705
+ let timer;
706
+ try {
707
+ await Promise.race([
708
+ Promise.allSettled(Array.from(pendingTracePromises)),
709
+ new Promise((resolve) => {
710
+ timer = setTimeout(resolve, timeoutMs);
711
+ unrefTimer(timer);
712
+ })
713
+ ]);
714
+ } finally {
715
+ if (timer) {
716
+ clearTimeout(timer);
717
+ }
718
+ }
622
719
  }
623
720
  if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
624
721
  let isFlushing = false;
@@ -928,6 +1025,7 @@ var HttpClient = class {
928
1025
  };
929
1026
 
930
1027
  // src/claudeAgentSdk.ts
1028
+ init_randomUuid();
931
1029
  init_serialize();
932
1030
  function nowIso() {
933
1031
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -1015,7 +1113,7 @@ var BitfabClaudeAgentHandler = class {
1015
1113
  if (this.activeContext) {
1016
1114
  this.traceId = this.activeContext.traceId;
1017
1115
  } else {
1018
- this.traceId = crypto.randomUUID();
1116
+ this.traceId = randomUuid();
1019
1117
  }
1020
1118
  this.traceStartedAt = nowIso();
1021
1119
  return this.traceId;
@@ -1040,7 +1138,7 @@ var BitfabClaudeAgentHandler = class {
1040
1138
  if (this.activeContext !== null) {
1041
1139
  return;
1042
1140
  }
1043
- const spanId = crypto.randomUUID();
1141
+ const spanId = randomUuid();
1044
1142
  this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1045
1143
  this.rootSpanId = spanId;
1046
1144
  }
@@ -1154,7 +1252,7 @@ var BitfabClaudeAgentHandler = class {
1154
1252
  // ── hook callbacks ───────────────────────────────────────────
1155
1253
  async preToolUseHook(inputData, toolUseId, _context) {
1156
1254
  try {
1157
- const sid = inputData.tool_use_id ?? toolUseId ?? crypto.randomUUID();
1255
+ const sid = inputData.tool_use_id ?? toolUseId ?? randomUuid();
1158
1256
  const toolName = inputData.tool_name ?? "tool";
1159
1257
  const toolInput = inputData.tool_input ?? {};
1160
1258
  const agentId = inputData.agent_id;
@@ -1184,10 +1282,10 @@ var BitfabClaudeAgentHandler = class {
1184
1282
  }
1185
1283
  async subagentStartHook(inputData, _toolUseId, _context) {
1186
1284
  try {
1187
- const agentId = inputData.agent_id ?? crypto.randomUUID();
1285
+ const agentId = inputData.agent_id ?? randomUuid();
1188
1286
  const agentType = inputData.agent_type ?? "subagent";
1189
1287
  const parentId = this.getParentId();
1190
- const spanId = crypto.randomUUID();
1288
+ const spanId = randomUuid();
1191
1289
  this.activeSubagentSpans.set(agentId, spanId);
1192
1290
  this.startSpan(
1193
1291
  spanId,
@@ -1328,7 +1426,7 @@ var BitfabClaudeAgentHandler = class {
1328
1426
  this.flushLlmTurn();
1329
1427
  this.conversationHistory.push(...this.pendingMessages);
1330
1428
  this.pendingMessages = [];
1331
- this.currentLlmSpanId = crypto.randomUUID();
1429
+ this.currentLlmSpanId = randomUuid();
1332
1430
  this.currentLlmMessageId = messageId ?? null;
1333
1431
  this.currentLlmContent = [];
1334
1432
  this.currentLlmModel = inner.model ?? null;
@@ -1772,6 +1870,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1772
1870
  }
1773
1871
 
1774
1872
  // src/langgraph.ts
1873
+ init_randomUuid();
1775
1874
  init_serialize();
1776
1875
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
1777
1876
  var LANGGRAPH_METADATA_KEYS = [
@@ -2020,7 +2119,7 @@ var BitfabLangGraphCallbackHandler = class {
2020
2119
  } else {
2021
2120
  const activeContext = this.getActiveSpanContext?.() ?? null;
2022
2121
  invocation = {
2023
- traceId: activeContext ? activeContext.traceId : crypto.randomUUID(),
2122
+ traceId: activeContext ? activeContext.traceId : randomUuid(),
2024
2123
  activeContext,
2025
2124
  rootRunId: runId
2026
2125
  };
@@ -2315,6 +2414,20 @@ var BitfabOpenAIAgentHandler = class {
2315
2414
  this.withSpanFn = config.withSpan;
2316
2415
  this.getActiveSpanContext = config.getActiveSpanContext;
2317
2416
  }
2417
+ /**
2418
+ * Drop-in replacement for the OpenAI Agents SDK's `run()` that records a
2419
+ * replayable root `agent` span.
2420
+ *
2421
+ * The `input` is captured as the root span's input (as a single positional
2422
+ * argument, so `replay(key, fn)` re-feeds it), and the run's `finalOutput`
2423
+ * is recorded as the root output. For streaming runs (`{ stream: true }`),
2424
+ * the result is handed back immediately and the final output is recorded
2425
+ * once the stream completes — first-byte latency is untouched.
2426
+ *
2427
+ * The process-wide tracing processor (`getOpenAiTracingProcessor`) must still
2428
+ * be registered: it captures the LLM/tool/handoff spans that nest beneath
2429
+ * this root.
2430
+ */
2318
2431
  async wrapRun(agent, input, options) {
2319
2432
  const { run } = await importOptionalPeer([
2320
2433
  "@openai",
@@ -2353,6 +2466,7 @@ var BitfabOpenAIAgentHandler = class {
2353
2466
  };
2354
2467
 
2355
2468
  // src/client.ts
2469
+ init_randomUuid();
2356
2470
  init_replayContext();
2357
2471
 
2358
2472
  // src/replayEnvironment.ts
@@ -2772,6 +2886,7 @@ var BitfabVercelAiHandler = class {
2772
2886
  };
2773
2887
 
2774
2888
  // src/client.ts
2889
+ init_warnOnce();
2775
2890
  var activeTraceStates = /* @__PURE__ */ new Map();
2776
2891
  var pendingSpanPromises = /* @__PURE__ */ new Map();
2777
2892
  var asyncLocalStorage = null;
@@ -3130,7 +3245,20 @@ var Bitfab = class {
3130
3245
  functionVersion.providers,
3131
3246
  this.envVars
3132
3247
  );
3133
- const resultStr = typeof executionResult.result === "string" ? executionResult.result : JSON.stringify(executionResult.result);
3248
+ let resultStr;
3249
+ if (typeof executionResult.result === "string") {
3250
+ resultStr = executionResult.result;
3251
+ } else {
3252
+ try {
3253
+ resultStr = JSON.stringify(executionResult.result) ?? String(executionResult.result);
3254
+ } catch {
3255
+ warnOnce(
3256
+ "call-result-serialize",
3257
+ "a local execution result could not be JSON-serialized; storing its String() form instead. The call still returns its real value."
3258
+ );
3259
+ resultStr = String(executionResult.result);
3260
+ }
3261
+ }
3134
3262
  this.httpClient.sendInternalTrace(functionVersion.id, {
3135
3263
  result: resultStr,
3136
3264
  source: "typescript-sdk",
@@ -3379,11 +3507,26 @@ var Bitfab = class {
3379
3507
  return await bamlClient[methodName](...args);
3380
3508
  }
3381
3509
  const collector = new CollectorClass("bitfab-baml-tracing");
3382
- const trackedClient = bamlClient.withOptions({ collector });
3383
- const trackedMethod = trackedClient[methodName];
3384
- const result = await trackedMethod.bind(
3385
- trackedClient
3386
- )(...args);
3510
+ let trackedClient;
3511
+ let trackedMethod;
3512
+ try {
3513
+ trackedClient = bamlClient.withOptions({ collector });
3514
+ const method2 = trackedClient[methodName];
3515
+ if (typeof method2 !== "function") {
3516
+ throw new BitfabError(
3517
+ "bamlClient.withOptions did not return the wrapped method"
3518
+ );
3519
+ }
3520
+ trackedMethod = method2;
3521
+ } catch {
3522
+ warnOnce(
3523
+ `wrapBAML-setup:${methodName}`,
3524
+ `BAML tracing setup failed for "${methodName}" (incompatible bamlClient or BAML version); calling it untraced. The call still runs; no span is recorded.`
3525
+ );
3526
+ wrappedFn.collector = null;
3527
+ return await bamlClient[methodName](...args);
3528
+ }
3529
+ const result = await trackedMethod.bind(trackedClient)(...args);
3387
3530
  wrappedFn.collector = collector;
3388
3531
  try {
3389
3532
  const prompt = extractPromptFromCollector(collector);
@@ -3459,200 +3602,229 @@ var Bitfab = class {
3459
3602
  () => wrappedFn.apply(this, args)
3460
3603
  );
3461
3604
  }
3462
- const currentStack = getSpanStack();
3463
- const parentContext = currentStack[currentStack.length - 1];
3464
- const replayCtxForTraceId = parentContext ? null : getReplayContext();
3465
- const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? crypto.randomUUID();
3466
- const spanId = crypto.randomUUID();
3467
- const parentSpanId = parentContext?.spanId ?? null;
3468
- const isRootSpan = parentSpanId === null;
3469
- const newContext = { traceId, spanId, contexts: [] };
3470
- const newStack = [...currentStack, newContext];
3471
- const inputs = args;
3472
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3473
- if (isRootSpan && !activeTraceStates.has(traceId)) {
3474
- const replayCtxAtRoot = getReplayContext();
3475
- const dbSnapshotRef = buildSnapshotRef(self.dbSnapshot, startedAt);
3476
- activeTraceStates.set(traceId, {
3605
+ let newStack;
3606
+ let executeWithContext;
3607
+ let registeredTraceId;
3608
+ try {
3609
+ const currentStack = getSpanStack();
3610
+ const parentContext = currentStack[currentStack.length - 1];
3611
+ const replayCtxForTraceId = parentContext ? null : getReplayContext();
3612
+ const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? randomUuid();
3613
+ const spanId = randomUuid();
3614
+ const parentSpanId = parentContext?.spanId ?? null;
3615
+ const isRootSpan = parentSpanId === null;
3616
+ const newContext = { traceId, spanId, contexts: [] };
3617
+ newStack = [...currentStack, newContext];
3618
+ const inputs = args;
3619
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3620
+ if (isRootSpan && !activeTraceStates.has(traceId)) {
3621
+ const replayCtxAtRoot = getReplayContext();
3622
+ const dbSnapshotRef = buildSnapshotRef(self.dbSnapshot, startedAt);
3623
+ activeTraceStates.set(traceId, {
3624
+ traceId,
3625
+ startedAt,
3626
+ contexts: [],
3627
+ ...replayCtxAtRoot?.testRunId && {
3628
+ testRunId: replayCtxAtRoot.testRunId
3629
+ },
3630
+ ...replayCtxAtRoot?.inputSourceTraceId && {
3631
+ inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
3632
+ },
3633
+ dbSnapshotRef
3634
+ });
3635
+ pendingSpanPromises.set(traceId, []);
3636
+ registeredTraceId = traceId;
3637
+ }
3638
+ const functionName = fn.name !== "" ? fn.name : void 0;
3639
+ const baseSpanParams = {
3640
+ traceFunctionKey,
3641
+ functionName,
3642
+ spanName: options.name ?? functionName ?? traceFunctionKey,
3477
3643
  traceId,
3644
+ spanId,
3645
+ parentSpanId,
3646
+ inputs,
3478
3647
  startedAt,
3479
- contexts: [],
3480
- ...replayCtxAtRoot?.testRunId && {
3481
- testRunId: replayCtxAtRoot.testRunId
3482
- },
3483
- ...replayCtxAtRoot?.inputSourceTraceId && {
3484
- inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
3485
- },
3486
- dbSnapshotRef
3487
- });
3488
- pendingSpanPromises.set(traceId, []);
3489
- }
3490
- const functionName = fn.name !== "" ? fn.name : void 0;
3491
- const baseSpanParams = {
3492
- traceFunctionKey,
3493
- functionName,
3494
- spanName: options.name ?? functionName ?? traceFunctionKey,
3495
- traceId,
3496
- spanId,
3497
- parentSpanId,
3498
- inputs,
3499
- startedAt,
3500
- spanType: options.type ?? "custom"
3501
- };
3502
- const sendSpan = async (params, spanOpts) => {
3503
- const replayCtx = getReplayContext();
3504
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3505
- let resolvePersistence;
3506
- if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3507
- persistenceCollector.push(
3508
- new Promise((resolve) => {
3509
- resolvePersistence = resolve;
3510
- })
3511
- );
3512
- }
3513
- try {
3514
- const endedAt = (/* @__PURE__ */ new Date()).toISOString();
3515
- const spanPromise = self.sendWrapperSpan({
3516
- ...baseSpanParams,
3517
- ...params,
3518
- contexts: newContext.contexts,
3519
- prompt: newContext.prompt,
3520
- endedAt,
3521
- ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
3522
- ...replayCtx?.inputSourceSpanId && {
3523
- inputSourceSpanId: replayCtx.inputSourceSpanId
3524
- }
3525
- });
3526
- if (isRootSpan) {
3527
- const pending = pendingSpanPromises.get(traceId) ?? [];
3528
- pending.push(spanPromise);
3529
- if (persistenceCollector) {
3530
- await Promise.allSettled(pending);
3531
- } else {
3532
- await Promise.race([
3533
- Promise.allSettled(pending),
3534
- new Promise((resolve) => setTimeout(resolve, 5e3))
3535
- ]);
3536
- }
3537
- pendingSpanPromises.delete(traceId);
3538
- const traceState = activeTraceStates.get(traceId);
3539
- const completionPromise = self.sendTraceCompletion({
3540
- traceFunctionKey,
3541
- traceId,
3542
- startedAt: traceState?.startedAt ?? startedAt,
3648
+ spanType: options.type ?? "custom"
3649
+ };
3650
+ const sendSpan = async (params, spanOpts) => {
3651
+ const replayCtx = getReplayContext();
3652
+ const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3653
+ let resolvePersistence;
3654
+ if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3655
+ persistenceCollector.push(
3656
+ new Promise((resolve) => {
3657
+ resolvePersistence = resolve;
3658
+ })
3659
+ );
3660
+ }
3661
+ try {
3662
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
3663
+ const spanPromise = self.sendWrapperSpan({
3664
+ ...baseSpanParams,
3665
+ ...params,
3666
+ contexts: newContext.contexts,
3667
+ prompt: newContext.prompt,
3543
3668
  endedAt,
3544
- sessionId: traceState?.sessionId,
3545
- metadata: traceState?.metadata,
3546
- contexts: traceState?.contexts ?? [],
3547
- testRunId: traceState?.testRunId,
3548
- inputSourceTraceId: traceState?.inputSourceTraceId,
3549
- dbSnapshotRef: traceState?.dbSnapshotRef,
3550
- // Built AFTER the wrapped fn finished, so `accessed` reflects
3551
- // whether customer code obtained the branch URL during this
3552
- // item. Omitted entirely when no lease was attached, so the
3553
- // server can distinguish "no branch" from "branch ignored".
3554
- ...replayCtx?.dbBranchLease && {
3555
- dbSnapshotUsage: {
3556
- neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3557
- snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3558
- sourceTraceId: replayCtx.sourceBitfabTraceId,
3559
- accessed: replayCtx.dbSnapshotAccessed === true
3560
- }
3669
+ ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
3670
+ ...replayCtx?.inputSourceSpanId && {
3671
+ inputSourceSpanId: replayCtx.inputSourceSpanId
3561
3672
  }
3562
3673
  });
3563
- activeTraceStates.delete(traceId);
3564
- if (persistenceCollector) {
3565
- await completionPromise;
3566
- }
3567
- } else {
3568
- const pending = pendingSpanPromises.get(traceId);
3569
- if (pending) {
3674
+ if (isRootSpan) {
3675
+ const pending = pendingSpanPromises.get(traceId) ?? [];
3570
3676
  pending.push(spanPromise);
3677
+ if (persistenceCollector) {
3678
+ await Promise.allSettled(pending);
3679
+ } else {
3680
+ let raceTimer;
3681
+ try {
3682
+ await Promise.race([
3683
+ Promise.allSettled(pending),
3684
+ new Promise((resolve) => {
3685
+ raceTimer = setTimeout(resolve, 5e3);
3686
+ unrefTimer(raceTimer);
3687
+ })
3688
+ ]);
3689
+ } finally {
3690
+ if (raceTimer) {
3691
+ clearTimeout(raceTimer);
3692
+ }
3693
+ }
3694
+ }
3695
+ pendingSpanPromises.delete(traceId);
3696
+ const traceState = activeTraceStates.get(traceId);
3697
+ const completionPromise = self.sendTraceCompletion({
3698
+ traceFunctionKey,
3699
+ traceId,
3700
+ startedAt: traceState?.startedAt ?? startedAt,
3701
+ endedAt,
3702
+ sessionId: traceState?.sessionId,
3703
+ metadata: traceState?.metadata,
3704
+ contexts: traceState?.contexts ?? [],
3705
+ testRunId: traceState?.testRunId,
3706
+ inputSourceTraceId: traceState?.inputSourceTraceId,
3707
+ dbSnapshotRef: traceState?.dbSnapshotRef,
3708
+ // Built AFTER the wrapped fn finished, so `accessed` reflects
3709
+ // whether customer code obtained the branch URL during this
3710
+ // item. Omitted entirely when no lease was attached, so the
3711
+ // server can distinguish "no branch" from "branch ignored".
3712
+ ...replayCtx?.dbBranchLease && {
3713
+ dbSnapshotUsage: {
3714
+ neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3715
+ snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3716
+ sourceTraceId: replayCtx.sourceBitfabTraceId,
3717
+ accessed: replayCtx.dbSnapshotAccessed === true
3718
+ }
3719
+ }
3720
+ });
3721
+ activeTraceStates.delete(traceId);
3722
+ if (persistenceCollector) {
3723
+ await completionPromise;
3724
+ }
3571
3725
  } else {
3572
- pendingSpanPromises.set(traceId, [spanPromise]);
3726
+ const pending = pendingSpanPromises.get(traceId);
3727
+ if (pending) {
3728
+ pending.push(spanPromise);
3729
+ } else {
3730
+ pendingSpanPromises.set(traceId, [spanPromise]);
3731
+ }
3573
3732
  }
3733
+ } catch {
3734
+ } finally {
3735
+ resolvePersistence?.();
3574
3736
  }
3575
- } catch {
3576
- } finally {
3577
- resolvePersistence?.();
3578
- }
3579
- };
3580
- const replayCtxForMock = getReplayContext();
3581
- if (replayCtxForMock?.mockTree && !isRootSpan) {
3582
- const counters = replayCtxForMock.callCounters;
3583
- const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
3584
- const callIndex = counters.get(counterKey) ?? 0;
3585
- counters.set(counterKey, callIndex + 1);
3586
- const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3587
- if (shouldMock) {
3588
- const mockKey = `${counterKey}:${callIndex}`;
3589
- const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3590
- if (mockSpan) {
3591
- let output = mockSpan.output;
3592
- if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3593
- output = deserializeValue({
3594
- json: mockSpan.output,
3595
- meta: mockSpan.outputMeta
3596
- });
3597
- }
3598
- void sendSpan({ result: output });
3599
- if (fnReturnsPromise) {
3600
- return Promise.resolve(output);
3737
+ };
3738
+ const replayCtxForMock = getReplayContext();
3739
+ if (replayCtxForMock?.mockTree && !isRootSpan) {
3740
+ const counters = replayCtxForMock.callCounters;
3741
+ const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
3742
+ const callIndex = counters.get(counterKey) ?? 0;
3743
+ counters.set(counterKey, callIndex + 1);
3744
+ const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3745
+ if (shouldMock) {
3746
+ const mockKey = `${counterKey}:${callIndex}`;
3747
+ const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3748
+ if (mockSpan) {
3749
+ let output = mockSpan.output;
3750
+ if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3751
+ output = deserializeValue({
3752
+ json: mockSpan.output,
3753
+ meta: mockSpan.outputMeta
3754
+ });
3755
+ }
3756
+ void sendSpan({ result: output });
3757
+ if (fnReturnsPromise) {
3758
+ return Promise.resolve(output);
3759
+ }
3760
+ return output;
3601
3761
  }
3602
- return output;
3603
3762
  }
3604
3763
  }
3605
- }
3606
- const recordSpan = (result) => {
3607
- if (options.finalize) {
3608
- const replayCtx = getReplayContext();
3609
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3610
- let resolvePersistence;
3611
- if (persistenceCollector) {
3612
- persistenceCollector.push(
3613
- new Promise((resolve) => {
3614
- resolvePersistence = resolve;
3615
- })
3616
- );
3764
+ const recordSpan = (result) => {
3765
+ if (options.finalize) {
3766
+ const replayCtx = getReplayContext();
3767
+ const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3768
+ let resolvePersistence;
3769
+ if (persistenceCollector) {
3770
+ persistenceCollector.push(
3771
+ new Promise((resolve) => {
3772
+ resolvePersistence = resolve;
3773
+ })
3774
+ );
3775
+ }
3776
+ void Promise.resolve().then(() => options.finalize(result)).then(
3777
+ (output) => sendSpan(
3778
+ { result: output },
3779
+ { skipPersistenceRegistration: true }
3780
+ )
3781
+ ).catch(
3782
+ (error) => sendSpan(
3783
+ {
3784
+ result: void 0,
3785
+ error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3786
+ },
3787
+ { skipPersistenceRegistration: true }
3788
+ )
3789
+ ).finally(() => resolvePersistence?.());
3790
+ } else {
3791
+ void sendSpan({ result });
3617
3792
  }
3618
- void Promise.resolve().then(() => options.finalize(result)).then(
3619
- (output) => sendSpan(
3620
- { result: output },
3621
- { skipPersistenceRegistration: true }
3622
- )
3623
- ).catch(
3624
- (error) => sendSpan(
3625
- {
3793
+ };
3794
+ executeWithContext = () => {
3795
+ const result = fn(...args);
3796
+ if (result instanceof Promise) {
3797
+ return result.then((resolvedResult) => {
3798
+ recordSpan(resolvedResult);
3799
+ return resolvedResult;
3800
+ }).catch((error) => {
3801
+ void sendSpan({
3626
3802
  result: void 0,
3627
- error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3628
- },
3629
- { skipPersistenceRegistration: true }
3630
- )
3631
- ).finally(() => resolvePersistence?.());
3632
- } else {
3633
- void sendSpan({ result });
3634
- }
3635
- };
3636
- const executeWithContext = () => {
3637
- const result = fn(...args);
3638
- if (result instanceof Promise) {
3639
- return result.then((resolvedResult) => {
3640
- recordSpan(resolvedResult);
3641
- return resolvedResult;
3642
- }).catch((error) => {
3643
- void sendSpan({
3644
- result: void 0,
3645
- error: error instanceof Error ? error.message : String(error)
3803
+ error: error instanceof Error ? error.message : String(error)
3804
+ });
3805
+ throw error;
3646
3806
  });
3647
- throw error;
3648
- });
3807
+ }
3808
+ if (isAsyncGenerator(result)) {
3809
+ return wrapAsyncGenerator(result, newStack, sendSpan);
3810
+ }
3811
+ recordSpan(result);
3812
+ return result;
3813
+ };
3814
+ } catch (setupError) {
3815
+ if (registeredTraceId) {
3816
+ activeTraceStates.delete(registeredTraceId);
3817
+ pendingSpanPromises.delete(registeredTraceId);
3649
3818
  }
3650
- if (isAsyncGenerator(result)) {
3651
- return wrapAsyncGenerator(result, newStack, sendSpan);
3819
+ if (getReplayContext()) {
3820
+ throw setupError;
3652
3821
  }
3653
- recordSpan(result);
3654
- return result;
3655
- };
3822
+ warnOnce(
3823
+ `withSpan-setup:${traceFunctionKey}`,
3824
+ `tracing setup failed for "${traceFunctionKey}"; running it untraced. The function still runs and returns normally; no span is recorded.`
3825
+ );
3826
+ return fn(...args);
3827
+ }
3656
3828
  return runWithSpanStack(newStack, executeWithContext);
3657
3829
  };
3658
3830
  Object.defineProperty(wrappedFn, "_bitfabTraceFunctionKey", {