@bitfab/sdk 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/index.cjs CHANGED
@@ -45,6 +45,54 @@ var init_errors = __esm({
45
45
  }
46
46
  });
47
47
 
48
+ // src/warnOnce.ts
49
+ function warnOnce(key, message) {
50
+ if (warned.has(key)) {
51
+ return;
52
+ }
53
+ warned.add(key);
54
+ try {
55
+ console.warn(`[bitfab] ${message}`);
56
+ } catch {
57
+ }
58
+ }
59
+ var warned;
60
+ var init_warnOnce = __esm({
61
+ "src/warnOnce.ts"() {
62
+ "use strict";
63
+ warned = /* @__PURE__ */ new Set();
64
+ }
65
+ });
66
+
67
+ // src/randomUuid.ts
68
+ function randomUuid() {
69
+ const globalCrypto = globalThis.crypto;
70
+ if (typeof globalCrypto?.randomUUID === "function") {
71
+ try {
72
+ return globalCrypto.randomUUID();
73
+ } catch {
74
+ }
75
+ }
76
+ warnOnce(
77
+ "crypto-unavailable",
78
+ "global crypto.randomUUID is unavailable; using a non-cryptographic fallback for trace/span ids. Tracing works normally (ids are correlation-only, not security-sensitive)."
79
+ );
80
+ return fallbackUuidV4();
81
+ }
82
+ function fallbackUuidV4() {
83
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
84
+ const rand = Math.random() * 16 | 0;
85
+ const value = char === "x" ? rand : rand & 3 | 8;
86
+ return value.toString(16);
87
+ });
88
+ }
89
+ var init_randomUuid = __esm({
90
+ "src/randomUuid.ts"() {
91
+ "use strict";
92
+ init_warnOnce();
93
+ }
94
+ });
95
+
48
96
  // src/serialize.ts
49
97
  function describeValue(value) {
50
98
  try {
@@ -57,6 +105,10 @@ function describeValue(value) {
57
105
  return typeof value;
58
106
  }
59
107
  function unserializableStub(value, reason) {
108
+ warnOnce(
109
+ `serialize:${reason.replace(/\d+/g, "N")}`,
110
+ `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.`
111
+ );
60
112
  let summary;
61
113
  try {
62
114
  summary = `<unserializable: ${describeValue(value)} (${reason})>`;
@@ -96,7 +148,19 @@ function deserializeValue(serialized) {
96
148
  });
97
149
  }
98
150
  function toJsonSafe(value) {
99
- return toJsonSafeInner(value, 0, /* @__PURE__ */ new WeakSet());
151
+ const safe = toJsonSafeInner(value, 0, /* @__PURE__ */ new WeakSet());
152
+ try {
153
+ const size = JSON.stringify(safe)?.length ?? 0;
154
+ if (size > MAX_FRAMEWORK_SERIALIZED_BYTES) {
155
+ warnOnce(
156
+ "toJsonSafe:too_large",
157
+ `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.`
158
+ );
159
+ return `<unserializable: too_large_${size}_bytes>`;
160
+ }
161
+ } catch {
162
+ }
163
+ return safe;
100
164
  }
101
165
  function toJsonSafeInner(value, depth, seen) {
102
166
  if (value === null || value === void 0) {
@@ -149,12 +213,14 @@ function toJsonSafeInner(value, depth, seen) {
149
213
  seen.delete(value);
150
214
  return result;
151
215
  }
152
- var import_superjson, MAX_SERIALIZED_BYTES, MAX_SAFE_DEPTH;
216
+ var import_superjson, MAX_SERIALIZED_BYTES, MAX_FRAMEWORK_SERIALIZED_BYTES, MAX_SAFE_DEPTH;
153
217
  var init_serialize = __esm({
154
218
  "src/serialize.ts"() {
155
219
  "use strict";
156
220
  import_superjson = __toESM(require("superjson"), 1);
221
+ init_warnOnce();
157
222
  MAX_SERIALIZED_BYTES = 512e3;
223
+ MAX_FRAMEWORK_SERIALIZED_BYTES = 2e6;
158
224
  MAX_SAFE_DEPTH = 6;
159
225
  }
160
226
  });
@@ -279,7 +345,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
279
345
  let originalOutput;
280
346
  let result;
281
347
  let error = null;
282
- const replayedTraceId = crypto.randomUUID();
348
+ const replayedTraceId = randomUuid();
283
349
  const pendingPersistence = [];
284
350
  try {
285
351
  const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
@@ -463,6 +529,7 @@ var init_replay = __esm({
463
529
  "src/replay.ts"() {
464
530
  "use strict";
465
531
  init_errors();
532
+ init_randomUuid();
466
533
  init_replayContext();
467
534
  init_serialize();
468
535
  }
@@ -492,13 +559,24 @@ __export(index_exports, {
492
559
  module.exports = __toCommonJS(index_exports);
493
560
 
494
561
  // src/version.generated.ts
495
- var __version__ = "0.23.3";
562
+ var __version__ = "0.24.0";
496
563
 
497
564
  // src/constants.ts
498
565
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
499
566
 
500
567
  // src/http.ts
501
568
  init_errors();
569
+
570
+ // src/unrefTimer.ts
571
+ function unrefTimer(timer) {
572
+ const handle = timer;
573
+ if (typeof handle.unref === "function") {
574
+ handle.unref();
575
+ }
576
+ }
577
+
578
+ // src/http.ts
579
+ init_warnOnce();
502
580
  function serializePayloadBody(payload) {
503
581
  try {
504
582
  return { body: JSON.stringify(payload), dropped: [] };
@@ -543,11 +621,20 @@ function serializePayloadBody(payload) {
543
621
  result = `<unserializable: ${className}>`;
544
622
  }
545
623
  } else {
546
- const out = {};
547
- for (const [k, v] of Object.entries(obj)) {
548
- out[k] = sanitize(v, seen);
624
+ try {
625
+ const out = {};
626
+ for (const [k, v] of Object.entries(obj)) {
627
+ out[k] = sanitize(v, seen);
628
+ }
629
+ result = out;
630
+ } catch {
631
+ warnOnce(
632
+ "payload:field-getter-threw",
633
+ "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."
634
+ );
635
+ dropped.push(className);
636
+ result = `<unserializable: ${className}>`;
549
637
  }
550
- result = out;
551
638
  }
552
639
  seen.delete(obj);
553
640
  return result;
@@ -592,10 +679,20 @@ async function flushTraces(timeoutMs = 5e3) {
592
679
  if (pendingTracePromises.size === 0) {
593
680
  return;
594
681
  }
595
- await Promise.race([
596
- Promise.allSettled(Array.from(pendingTracePromises)),
597
- new Promise((resolve) => setTimeout(resolve, timeoutMs))
598
- ]);
682
+ let timer;
683
+ try {
684
+ await Promise.race([
685
+ Promise.allSettled(Array.from(pendingTracePromises)),
686
+ new Promise((resolve) => {
687
+ timer = setTimeout(resolve, timeoutMs);
688
+ unrefTimer(timer);
689
+ })
690
+ ]);
691
+ } finally {
692
+ if (timer) {
693
+ clearTimeout(timer);
694
+ }
695
+ }
599
696
  }
600
697
  if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
601
698
  let isFlushing = false;
@@ -905,6 +1002,7 @@ var HttpClient = class {
905
1002
  };
906
1003
 
907
1004
  // src/claudeAgentSdk.ts
1005
+ init_randomUuid();
908
1006
  init_serialize();
909
1007
  function nowIso() {
910
1008
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -992,7 +1090,7 @@ var BitfabClaudeAgentHandler = class {
992
1090
  if (this.activeContext) {
993
1091
  this.traceId = this.activeContext.traceId;
994
1092
  } else {
995
- this.traceId = crypto.randomUUID();
1093
+ this.traceId = randomUuid();
996
1094
  }
997
1095
  this.traceStartedAt = nowIso();
998
1096
  return this.traceId;
@@ -1017,7 +1115,7 @@ var BitfabClaudeAgentHandler = class {
1017
1115
  if (this.activeContext !== null) {
1018
1116
  return;
1019
1117
  }
1020
- const spanId = crypto.randomUUID();
1118
+ const spanId = randomUuid();
1021
1119
  this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1022
1120
  this.rootSpanId = spanId;
1023
1121
  }
@@ -1131,7 +1229,7 @@ var BitfabClaudeAgentHandler = class {
1131
1229
  // ── hook callbacks ───────────────────────────────────────────
1132
1230
  async preToolUseHook(inputData, toolUseId, _context) {
1133
1231
  try {
1134
- const sid = inputData.tool_use_id ?? toolUseId ?? crypto.randomUUID();
1232
+ const sid = inputData.tool_use_id ?? toolUseId ?? randomUuid();
1135
1233
  const toolName = inputData.tool_name ?? "tool";
1136
1234
  const toolInput = inputData.tool_input ?? {};
1137
1235
  const agentId = inputData.agent_id;
@@ -1161,10 +1259,10 @@ var BitfabClaudeAgentHandler = class {
1161
1259
  }
1162
1260
  async subagentStartHook(inputData, _toolUseId, _context) {
1163
1261
  try {
1164
- const agentId = inputData.agent_id ?? crypto.randomUUID();
1262
+ const agentId = inputData.agent_id ?? randomUuid();
1165
1263
  const agentType = inputData.agent_type ?? "subagent";
1166
1264
  const parentId = this.getParentId();
1167
- const spanId = crypto.randomUUID();
1265
+ const spanId = randomUuid();
1168
1266
  this.activeSubagentSpans.set(agentId, spanId);
1169
1267
  this.startSpan(
1170
1268
  spanId,
@@ -1305,7 +1403,7 @@ var BitfabClaudeAgentHandler = class {
1305
1403
  this.flushLlmTurn();
1306
1404
  this.conversationHistory.push(...this.pendingMessages);
1307
1405
  this.pendingMessages = [];
1308
- this.currentLlmSpanId = crypto.randomUUID();
1406
+ this.currentLlmSpanId = randomUuid();
1309
1407
  this.currentLlmMessageId = messageId ?? null;
1310
1408
  this.currentLlmContent = [];
1311
1409
  this.currentLlmModel = inner.model ?? null;
@@ -1749,6 +1847,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1749
1847
  }
1750
1848
 
1751
1849
  // src/langgraph.ts
1850
+ init_randomUuid();
1752
1851
  init_serialize();
1753
1852
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
1754
1853
  var LANGGRAPH_METADATA_KEYS = [
@@ -1997,7 +2096,7 @@ var BitfabLangGraphCallbackHandler = class {
1997
2096
  } else {
1998
2097
  const activeContext = this.getActiveSpanContext?.() ?? null;
1999
2098
  invocation = {
2000
- traceId: activeContext ? activeContext.traceId : crypto.randomUUID(),
2099
+ traceId: activeContext ? activeContext.traceId : randomUuid(),
2001
2100
  activeContext,
2002
2101
  rootRunId: runId
2003
2102
  };
@@ -2292,6 +2391,20 @@ var BitfabOpenAIAgentHandler = class {
2292
2391
  this.withSpanFn = config.withSpan;
2293
2392
  this.getActiveSpanContext = config.getActiveSpanContext;
2294
2393
  }
2394
+ /**
2395
+ * Drop-in replacement for the OpenAI Agents SDK's `run()` that records a
2396
+ * replayable root `agent` span.
2397
+ *
2398
+ * The `input` is captured as the root span's input (as a single positional
2399
+ * argument, so `replay(key, fn)` re-feeds it), and the run's `finalOutput`
2400
+ * is recorded as the root output. For streaming runs (`{ stream: true }`),
2401
+ * the result is handed back immediately and the final output is recorded
2402
+ * once the stream completes — first-byte latency is untouched.
2403
+ *
2404
+ * The process-wide tracing processor (`getOpenAiTracingProcessor`) must still
2405
+ * be registered: it captures the LLM/tool/handoff spans that nest beneath
2406
+ * this root.
2407
+ */
2295
2408
  async wrapRun(agent, input, options) {
2296
2409
  const { run } = await importOptionalPeer([
2297
2410
  "@openai",
@@ -2330,6 +2443,7 @@ var BitfabOpenAIAgentHandler = class {
2330
2443
  };
2331
2444
 
2332
2445
  // src/client.ts
2446
+ init_randomUuid();
2333
2447
  init_replayContext();
2334
2448
 
2335
2449
  // src/replayEnvironment.ts
@@ -2749,6 +2863,7 @@ var BitfabVercelAiHandler = class {
2749
2863
  };
2750
2864
 
2751
2865
  // src/client.ts
2866
+ init_warnOnce();
2752
2867
  var activeTraceStates = /* @__PURE__ */ new Map();
2753
2868
  var pendingSpanPromises = /* @__PURE__ */ new Map();
2754
2869
  var asyncLocalStorage = null;
@@ -3107,7 +3222,20 @@ var Bitfab = class {
3107
3222
  functionVersion.providers,
3108
3223
  this.envVars
3109
3224
  );
3110
- const resultStr = typeof executionResult.result === "string" ? executionResult.result : JSON.stringify(executionResult.result);
3225
+ let resultStr;
3226
+ if (typeof executionResult.result === "string") {
3227
+ resultStr = executionResult.result;
3228
+ } else {
3229
+ try {
3230
+ resultStr = JSON.stringify(executionResult.result) ?? String(executionResult.result);
3231
+ } catch {
3232
+ warnOnce(
3233
+ "call-result-serialize",
3234
+ "a local execution result could not be JSON-serialized; storing its String() form instead. The call still returns its real value."
3235
+ );
3236
+ resultStr = String(executionResult.result);
3237
+ }
3238
+ }
3111
3239
  this.httpClient.sendInternalTrace(functionVersion.id, {
3112
3240
  result: resultStr,
3113
3241
  source: "typescript-sdk",
@@ -3356,11 +3484,26 @@ var Bitfab = class {
3356
3484
  return await bamlClient[methodName](...args);
3357
3485
  }
3358
3486
  const collector = new CollectorClass("bitfab-baml-tracing");
3359
- const trackedClient = bamlClient.withOptions({ collector });
3360
- const trackedMethod = trackedClient[methodName];
3361
- const result = await trackedMethod.bind(
3362
- trackedClient
3363
- )(...args);
3487
+ let trackedClient;
3488
+ let trackedMethod;
3489
+ try {
3490
+ trackedClient = bamlClient.withOptions({ collector });
3491
+ const method2 = trackedClient[methodName];
3492
+ if (typeof method2 !== "function") {
3493
+ throw new BitfabError(
3494
+ "bamlClient.withOptions did not return the wrapped method"
3495
+ );
3496
+ }
3497
+ trackedMethod = method2;
3498
+ } catch {
3499
+ warnOnce(
3500
+ `wrapBAML-setup:${methodName}`,
3501
+ `BAML tracing setup failed for "${methodName}" (incompatible bamlClient or BAML version); calling it untraced. The call still runs; no span is recorded.`
3502
+ );
3503
+ wrappedFn.collector = null;
3504
+ return await bamlClient[methodName](...args);
3505
+ }
3506
+ const result = await trackedMethod.bind(trackedClient)(...args);
3364
3507
  wrappedFn.collector = collector;
3365
3508
  try {
3366
3509
  const prompt = extractPromptFromCollector(collector);
@@ -3436,200 +3579,229 @@ var Bitfab = class {
3436
3579
  () => wrappedFn.apply(this, args)
3437
3580
  );
3438
3581
  }
3439
- const currentStack = getSpanStack();
3440
- const parentContext = currentStack[currentStack.length - 1];
3441
- const replayCtxForTraceId = parentContext ? null : getReplayContext();
3442
- const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? crypto.randomUUID();
3443
- const spanId = crypto.randomUUID();
3444
- const parentSpanId = parentContext?.spanId ?? null;
3445
- const isRootSpan = parentSpanId === null;
3446
- const newContext = { traceId, spanId, contexts: [] };
3447
- const newStack = [...currentStack, newContext];
3448
- const inputs = args;
3449
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3450
- if (isRootSpan && !activeTraceStates.has(traceId)) {
3451
- const replayCtxAtRoot = getReplayContext();
3452
- const dbSnapshotRef = buildSnapshotRef(self.dbSnapshot, startedAt);
3453
- activeTraceStates.set(traceId, {
3582
+ let newStack;
3583
+ let executeWithContext;
3584
+ let registeredTraceId;
3585
+ try {
3586
+ const currentStack = getSpanStack();
3587
+ const parentContext = currentStack[currentStack.length - 1];
3588
+ const replayCtxForTraceId = parentContext ? null : getReplayContext();
3589
+ const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? randomUuid();
3590
+ const spanId = randomUuid();
3591
+ const parentSpanId = parentContext?.spanId ?? null;
3592
+ const isRootSpan = parentSpanId === null;
3593
+ const newContext = { traceId, spanId, contexts: [] };
3594
+ newStack = [...currentStack, newContext];
3595
+ const inputs = args;
3596
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3597
+ if (isRootSpan && !activeTraceStates.has(traceId)) {
3598
+ const replayCtxAtRoot = getReplayContext();
3599
+ const dbSnapshotRef = buildSnapshotRef(self.dbSnapshot, startedAt);
3600
+ activeTraceStates.set(traceId, {
3601
+ traceId,
3602
+ startedAt,
3603
+ contexts: [],
3604
+ ...replayCtxAtRoot?.testRunId && {
3605
+ testRunId: replayCtxAtRoot.testRunId
3606
+ },
3607
+ ...replayCtxAtRoot?.inputSourceTraceId && {
3608
+ inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
3609
+ },
3610
+ dbSnapshotRef
3611
+ });
3612
+ pendingSpanPromises.set(traceId, []);
3613
+ registeredTraceId = traceId;
3614
+ }
3615
+ const functionName = fn.name !== "" ? fn.name : void 0;
3616
+ const baseSpanParams = {
3617
+ traceFunctionKey,
3618
+ functionName,
3619
+ spanName: options.name ?? functionName ?? traceFunctionKey,
3454
3620
  traceId,
3621
+ spanId,
3622
+ parentSpanId,
3623
+ inputs,
3455
3624
  startedAt,
3456
- contexts: [],
3457
- ...replayCtxAtRoot?.testRunId && {
3458
- testRunId: replayCtxAtRoot.testRunId
3459
- },
3460
- ...replayCtxAtRoot?.inputSourceTraceId && {
3461
- inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
3462
- },
3463
- dbSnapshotRef
3464
- });
3465
- pendingSpanPromises.set(traceId, []);
3466
- }
3467
- const functionName = fn.name !== "" ? fn.name : void 0;
3468
- const baseSpanParams = {
3469
- traceFunctionKey,
3470
- functionName,
3471
- spanName: options.name ?? functionName ?? traceFunctionKey,
3472
- traceId,
3473
- spanId,
3474
- parentSpanId,
3475
- inputs,
3476
- startedAt,
3477
- spanType: options.type ?? "custom"
3478
- };
3479
- const sendSpan = async (params, spanOpts) => {
3480
- const replayCtx = getReplayContext();
3481
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3482
- let resolvePersistence;
3483
- if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3484
- persistenceCollector.push(
3485
- new Promise((resolve) => {
3486
- resolvePersistence = resolve;
3487
- })
3488
- );
3489
- }
3490
- try {
3491
- const endedAt = (/* @__PURE__ */ new Date()).toISOString();
3492
- const spanPromise = self.sendWrapperSpan({
3493
- ...baseSpanParams,
3494
- ...params,
3495
- contexts: newContext.contexts,
3496
- prompt: newContext.prompt,
3497
- endedAt,
3498
- ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
3499
- ...replayCtx?.inputSourceSpanId && {
3500
- inputSourceSpanId: replayCtx.inputSourceSpanId
3501
- }
3502
- });
3503
- if (isRootSpan) {
3504
- const pending = pendingSpanPromises.get(traceId) ?? [];
3505
- pending.push(spanPromise);
3506
- if (persistenceCollector) {
3507
- await Promise.allSettled(pending);
3508
- } else {
3509
- await Promise.race([
3510
- Promise.allSettled(pending),
3511
- new Promise((resolve) => setTimeout(resolve, 5e3))
3512
- ]);
3513
- }
3514
- pendingSpanPromises.delete(traceId);
3515
- const traceState = activeTraceStates.get(traceId);
3516
- const completionPromise = self.sendTraceCompletion({
3517
- traceFunctionKey,
3518
- traceId,
3519
- startedAt: traceState?.startedAt ?? startedAt,
3625
+ spanType: options.type ?? "custom"
3626
+ };
3627
+ const sendSpan = async (params, spanOpts) => {
3628
+ const replayCtx = getReplayContext();
3629
+ const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3630
+ let resolvePersistence;
3631
+ if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3632
+ persistenceCollector.push(
3633
+ new Promise((resolve) => {
3634
+ resolvePersistence = resolve;
3635
+ })
3636
+ );
3637
+ }
3638
+ try {
3639
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
3640
+ const spanPromise = self.sendWrapperSpan({
3641
+ ...baseSpanParams,
3642
+ ...params,
3643
+ contexts: newContext.contexts,
3644
+ prompt: newContext.prompt,
3520
3645
  endedAt,
3521
- sessionId: traceState?.sessionId,
3522
- metadata: traceState?.metadata,
3523
- contexts: traceState?.contexts ?? [],
3524
- testRunId: traceState?.testRunId,
3525
- inputSourceTraceId: traceState?.inputSourceTraceId,
3526
- dbSnapshotRef: traceState?.dbSnapshotRef,
3527
- // Built AFTER the wrapped fn finished, so `accessed` reflects
3528
- // whether customer code obtained the branch URL during this
3529
- // item. Omitted entirely when no lease was attached, so the
3530
- // server can distinguish "no branch" from "branch ignored".
3531
- ...replayCtx?.dbBranchLease && {
3532
- dbSnapshotUsage: {
3533
- neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3534
- snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3535
- sourceTraceId: replayCtx.sourceBitfabTraceId,
3536
- accessed: replayCtx.dbSnapshotAccessed === true
3537
- }
3646
+ ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
3647
+ ...replayCtx?.inputSourceSpanId && {
3648
+ inputSourceSpanId: replayCtx.inputSourceSpanId
3538
3649
  }
3539
3650
  });
3540
- activeTraceStates.delete(traceId);
3541
- if (persistenceCollector) {
3542
- await completionPromise;
3543
- }
3544
- } else {
3545
- const pending = pendingSpanPromises.get(traceId);
3546
- if (pending) {
3651
+ if (isRootSpan) {
3652
+ const pending = pendingSpanPromises.get(traceId) ?? [];
3547
3653
  pending.push(spanPromise);
3654
+ if (persistenceCollector) {
3655
+ await Promise.allSettled(pending);
3656
+ } else {
3657
+ let raceTimer;
3658
+ try {
3659
+ await Promise.race([
3660
+ Promise.allSettled(pending),
3661
+ new Promise((resolve) => {
3662
+ raceTimer = setTimeout(resolve, 5e3);
3663
+ unrefTimer(raceTimer);
3664
+ })
3665
+ ]);
3666
+ } finally {
3667
+ if (raceTimer) {
3668
+ clearTimeout(raceTimer);
3669
+ }
3670
+ }
3671
+ }
3672
+ pendingSpanPromises.delete(traceId);
3673
+ const traceState = activeTraceStates.get(traceId);
3674
+ const completionPromise = self.sendTraceCompletion({
3675
+ traceFunctionKey,
3676
+ traceId,
3677
+ startedAt: traceState?.startedAt ?? startedAt,
3678
+ endedAt,
3679
+ sessionId: traceState?.sessionId,
3680
+ metadata: traceState?.metadata,
3681
+ contexts: traceState?.contexts ?? [],
3682
+ testRunId: traceState?.testRunId,
3683
+ inputSourceTraceId: traceState?.inputSourceTraceId,
3684
+ dbSnapshotRef: traceState?.dbSnapshotRef,
3685
+ // Built AFTER the wrapped fn finished, so `accessed` reflects
3686
+ // whether customer code obtained the branch URL during this
3687
+ // item. Omitted entirely when no lease was attached, so the
3688
+ // server can distinguish "no branch" from "branch ignored".
3689
+ ...replayCtx?.dbBranchLease && {
3690
+ dbSnapshotUsage: {
3691
+ neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3692
+ snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3693
+ sourceTraceId: replayCtx.sourceBitfabTraceId,
3694
+ accessed: replayCtx.dbSnapshotAccessed === true
3695
+ }
3696
+ }
3697
+ });
3698
+ activeTraceStates.delete(traceId);
3699
+ if (persistenceCollector) {
3700
+ await completionPromise;
3701
+ }
3548
3702
  } else {
3549
- pendingSpanPromises.set(traceId, [spanPromise]);
3703
+ const pending = pendingSpanPromises.get(traceId);
3704
+ if (pending) {
3705
+ pending.push(spanPromise);
3706
+ } else {
3707
+ pendingSpanPromises.set(traceId, [spanPromise]);
3708
+ }
3550
3709
  }
3710
+ } catch {
3711
+ } finally {
3712
+ resolvePersistence?.();
3551
3713
  }
3552
- } catch {
3553
- } finally {
3554
- resolvePersistence?.();
3555
- }
3556
- };
3557
- const replayCtxForMock = getReplayContext();
3558
- if (replayCtxForMock?.mockTree && !isRootSpan) {
3559
- const counters = replayCtxForMock.callCounters;
3560
- const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
3561
- const callIndex = counters.get(counterKey) ?? 0;
3562
- counters.set(counterKey, callIndex + 1);
3563
- const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3564
- if (shouldMock) {
3565
- const mockKey = `${counterKey}:${callIndex}`;
3566
- const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3567
- if (mockSpan) {
3568
- let output = mockSpan.output;
3569
- if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3570
- output = deserializeValue({
3571
- json: mockSpan.output,
3572
- meta: mockSpan.outputMeta
3573
- });
3574
- }
3575
- void sendSpan({ result: output });
3576
- if (fnReturnsPromise) {
3577
- return Promise.resolve(output);
3714
+ };
3715
+ const replayCtxForMock = getReplayContext();
3716
+ if (replayCtxForMock?.mockTree && !isRootSpan) {
3717
+ const counters = replayCtxForMock.callCounters;
3718
+ const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
3719
+ const callIndex = counters.get(counterKey) ?? 0;
3720
+ counters.set(counterKey, callIndex + 1);
3721
+ const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3722
+ if (shouldMock) {
3723
+ const mockKey = `${counterKey}:${callIndex}`;
3724
+ const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3725
+ if (mockSpan) {
3726
+ let output = mockSpan.output;
3727
+ if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3728
+ output = deserializeValue({
3729
+ json: mockSpan.output,
3730
+ meta: mockSpan.outputMeta
3731
+ });
3732
+ }
3733
+ void sendSpan({ result: output });
3734
+ if (fnReturnsPromise) {
3735
+ return Promise.resolve(output);
3736
+ }
3737
+ return output;
3578
3738
  }
3579
- return output;
3580
3739
  }
3581
3740
  }
3582
- }
3583
- const recordSpan = (result) => {
3584
- if (options.finalize) {
3585
- const replayCtx = getReplayContext();
3586
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3587
- let resolvePersistence;
3588
- if (persistenceCollector) {
3589
- persistenceCollector.push(
3590
- new Promise((resolve) => {
3591
- resolvePersistence = resolve;
3592
- })
3593
- );
3741
+ const recordSpan = (result) => {
3742
+ if (options.finalize) {
3743
+ const replayCtx = getReplayContext();
3744
+ const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3745
+ let resolvePersistence;
3746
+ if (persistenceCollector) {
3747
+ persistenceCollector.push(
3748
+ new Promise((resolve) => {
3749
+ resolvePersistence = resolve;
3750
+ })
3751
+ );
3752
+ }
3753
+ void Promise.resolve().then(() => options.finalize(result)).then(
3754
+ (output) => sendSpan(
3755
+ { result: output },
3756
+ { skipPersistenceRegistration: true }
3757
+ )
3758
+ ).catch(
3759
+ (error) => sendSpan(
3760
+ {
3761
+ result: void 0,
3762
+ error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3763
+ },
3764
+ { skipPersistenceRegistration: true }
3765
+ )
3766
+ ).finally(() => resolvePersistence?.());
3767
+ } else {
3768
+ void sendSpan({ result });
3594
3769
  }
3595
- void Promise.resolve().then(() => options.finalize(result)).then(
3596
- (output) => sendSpan(
3597
- { result: output },
3598
- { skipPersistenceRegistration: true }
3599
- )
3600
- ).catch(
3601
- (error) => sendSpan(
3602
- {
3770
+ };
3771
+ executeWithContext = () => {
3772
+ const result = fn(...args);
3773
+ if (result instanceof Promise) {
3774
+ return result.then((resolvedResult) => {
3775
+ recordSpan(resolvedResult);
3776
+ return resolvedResult;
3777
+ }).catch((error) => {
3778
+ void sendSpan({
3603
3779
  result: void 0,
3604
- error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3605
- },
3606
- { skipPersistenceRegistration: true }
3607
- )
3608
- ).finally(() => resolvePersistence?.());
3609
- } else {
3610
- void sendSpan({ result });
3611
- }
3612
- };
3613
- const executeWithContext = () => {
3614
- const result = fn(...args);
3615
- if (result instanceof Promise) {
3616
- return result.then((resolvedResult) => {
3617
- recordSpan(resolvedResult);
3618
- return resolvedResult;
3619
- }).catch((error) => {
3620
- void sendSpan({
3621
- result: void 0,
3622
- error: error instanceof Error ? error.message : String(error)
3780
+ error: error instanceof Error ? error.message : String(error)
3781
+ });
3782
+ throw error;
3623
3783
  });
3624
- throw error;
3625
- });
3784
+ }
3785
+ if (isAsyncGenerator(result)) {
3786
+ return wrapAsyncGenerator(result, newStack, sendSpan);
3787
+ }
3788
+ recordSpan(result);
3789
+ return result;
3790
+ };
3791
+ } catch (setupError) {
3792
+ if (registeredTraceId) {
3793
+ activeTraceStates.delete(registeredTraceId);
3794
+ pendingSpanPromises.delete(registeredTraceId);
3626
3795
  }
3627
- if (isAsyncGenerator(result)) {
3628
- return wrapAsyncGenerator(result, newStack, sendSpan);
3796
+ if (getReplayContext()) {
3797
+ throw setupError;
3629
3798
  }
3630
- recordSpan(result);
3631
- return result;
3632
- };
3799
+ warnOnce(
3800
+ `withSpan-setup:${traceFunctionKey}`,
3801
+ `tracing setup failed for "${traceFunctionKey}"; running it untraced. The function still runs and returns normally; no span is recorded.`
3802
+ );
3803
+ return fn(...args);
3804
+ }
3633
3805
  return runWithSpanStack(newStack, executeWithContext);
3634
3806
  };
3635
3807
  Object.defineProperty(wrappedFn, "_bitfabTraceFunctionKey", {