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/index.cjs CHANGED
@@ -54,6 +54,54 @@ var init_errors = __esm({
54
54
  }
55
55
  });
56
56
 
57
+ // src/warnOnce.ts
58
+ function warnOnce(key, message) {
59
+ if (warned.has(key)) {
60
+ return;
61
+ }
62
+ warned.add(key);
63
+ try {
64
+ console.warn(`[bitfab] ${message}`);
65
+ } catch {
66
+ }
67
+ }
68
+ var warned;
69
+ var init_warnOnce = __esm({
70
+ "src/warnOnce.ts"() {
71
+ "use strict";
72
+ warned = /* @__PURE__ */ new Set();
73
+ }
74
+ });
75
+
76
+ // src/randomUuid.ts
77
+ function randomUuid() {
78
+ const globalCrypto = globalThis.crypto;
79
+ if (typeof globalCrypto?.randomUUID === "function") {
80
+ try {
81
+ return globalCrypto.randomUUID();
82
+ } catch {
83
+ }
84
+ }
85
+ warnOnce(
86
+ "crypto-unavailable",
87
+ "global crypto.randomUUID is unavailable; using a non-cryptographic fallback for trace/span ids. Tracing works normally (ids are correlation-only, not security-sensitive)."
88
+ );
89
+ return fallbackUuidV4();
90
+ }
91
+ function fallbackUuidV4() {
92
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
93
+ const rand = Math.random() * 16 | 0;
94
+ const value = char === "x" ? rand : rand & 3 | 8;
95
+ return value.toString(16);
96
+ });
97
+ }
98
+ var init_randomUuid = __esm({
99
+ "src/randomUuid.ts"() {
100
+ "use strict";
101
+ init_warnOnce();
102
+ }
103
+ });
104
+
57
105
  // src/serialize.ts
58
106
  function describeValue(value) {
59
107
  try {
@@ -66,6 +114,10 @@ function describeValue(value) {
66
114
  return typeof value;
67
115
  }
68
116
  function unserializableStub(value, reason) {
117
+ warnOnce(
118
+ `serialize:${reason.replace(/\d+/g, "N")}`,
119
+ `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.`
120
+ );
69
121
  let summary;
70
122
  try {
71
123
  summary = `<unserializable: ${describeValue(value)} (${reason})>`;
@@ -105,7 +157,19 @@ function deserializeValue(serialized) {
105
157
  });
106
158
  }
107
159
  function toJsonSafe(value) {
108
- return toJsonSafeInner(value, 0, /* @__PURE__ */ new WeakSet());
160
+ const safe = toJsonSafeInner(value, 0, /* @__PURE__ */ new WeakSet());
161
+ try {
162
+ const size = JSON.stringify(safe)?.length ?? 0;
163
+ if (size > MAX_FRAMEWORK_SERIALIZED_BYTES) {
164
+ warnOnce(
165
+ "toJsonSafe:too_large",
166
+ `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.`
167
+ );
168
+ return `<unserializable: too_large_${size}_bytes>`;
169
+ }
170
+ } catch {
171
+ }
172
+ return safe;
109
173
  }
110
174
  function toJsonSafeInner(value, depth, seen) {
111
175
  if (value === null || value === void 0) {
@@ -158,12 +222,14 @@ function toJsonSafeInner(value, depth, seen) {
158
222
  seen.delete(value);
159
223
  return result;
160
224
  }
161
- var import_superjson, MAX_SERIALIZED_BYTES, MAX_SAFE_DEPTH;
225
+ var import_superjson, MAX_SERIALIZED_BYTES, MAX_FRAMEWORK_SERIALIZED_BYTES, MAX_SAFE_DEPTH;
162
226
  var init_serialize = __esm({
163
227
  "src/serialize.ts"() {
164
228
  "use strict";
165
229
  import_superjson = __toESM(require("superjson"), 1);
230
+ init_warnOnce();
166
231
  MAX_SERIALIZED_BYTES = 512e3;
232
+ MAX_FRAMEWORK_SERIALIZED_BYTES = 2e6;
167
233
  MAX_SAFE_DEPTH = 6;
168
234
  }
169
235
  });
@@ -288,7 +354,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
288
354
  let originalOutput;
289
355
  let result;
290
356
  let error = null;
291
- const replayedTraceId = crypto.randomUUID();
357
+ const replayedTraceId = randomUuid();
292
358
  const pendingPersistence = [];
293
359
  try {
294
360
  const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
@@ -472,6 +538,7 @@ var init_replay = __esm({
472
538
  "src/replay.ts"() {
473
539
  "use strict";
474
540
  init_errors();
541
+ init_randomUuid();
475
542
  init_replayContext();
476
543
  init_serialize();
477
544
  }
@@ -501,13 +568,24 @@ __export(index_exports, {
501
568
  module.exports = __toCommonJS(index_exports);
502
569
 
503
570
  // src/version.generated.ts
504
- var __version__ = "0.23.3";
571
+ var __version__ = "0.24.0";
505
572
 
506
573
  // src/constants.ts
507
574
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
508
575
 
509
576
  // src/http.ts
510
577
  init_errors();
578
+
579
+ // src/unrefTimer.ts
580
+ function unrefTimer(timer) {
581
+ const handle = timer;
582
+ if (typeof handle.unref === "function") {
583
+ handle.unref();
584
+ }
585
+ }
586
+
587
+ // src/http.ts
588
+ init_warnOnce();
511
589
  function serializePayloadBody(payload) {
512
590
  try {
513
591
  return { body: JSON.stringify(payload), dropped: [] };
@@ -552,11 +630,20 @@ function serializePayloadBody(payload) {
552
630
  result = `<unserializable: ${className}>`;
553
631
  }
554
632
  } else {
555
- const out = {};
556
- for (const [k, v] of Object.entries(obj)) {
557
- out[k] = sanitize(v, seen);
633
+ try {
634
+ const out = {};
635
+ for (const [k, v] of Object.entries(obj)) {
636
+ out[k] = sanitize(v, seen);
637
+ }
638
+ result = out;
639
+ } catch {
640
+ warnOnce(
641
+ "payload:field-getter-threw",
642
+ "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."
643
+ );
644
+ dropped.push(className);
645
+ result = `<unserializable: ${className}>`;
558
646
  }
559
- result = out;
560
647
  }
561
648
  seen.delete(obj);
562
649
  return result;
@@ -601,10 +688,20 @@ async function flushTraces(timeoutMs = 5e3) {
601
688
  if (pendingTracePromises.size === 0) {
602
689
  return;
603
690
  }
604
- await Promise.race([
605
- Promise.allSettled(Array.from(pendingTracePromises)),
606
- new Promise((resolve) => setTimeout(resolve, timeoutMs))
607
- ]);
691
+ let timer;
692
+ try {
693
+ await Promise.race([
694
+ Promise.allSettled(Array.from(pendingTracePromises)),
695
+ new Promise((resolve) => {
696
+ timer = setTimeout(resolve, timeoutMs);
697
+ unrefTimer(timer);
698
+ })
699
+ ]);
700
+ } finally {
701
+ if (timer) {
702
+ clearTimeout(timer);
703
+ }
704
+ }
608
705
  }
609
706
  if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
610
707
  let isFlushing = false;
@@ -914,6 +1011,7 @@ var HttpClient = class {
914
1011
  };
915
1012
 
916
1013
  // src/claudeAgentSdk.ts
1014
+ init_randomUuid();
917
1015
  init_serialize();
918
1016
  function nowIso() {
919
1017
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -1001,7 +1099,7 @@ var BitfabClaudeAgentHandler = class {
1001
1099
  if (this.activeContext) {
1002
1100
  this.traceId = this.activeContext.traceId;
1003
1101
  } else {
1004
- this.traceId = crypto.randomUUID();
1102
+ this.traceId = randomUuid();
1005
1103
  }
1006
1104
  this.traceStartedAt = nowIso();
1007
1105
  return this.traceId;
@@ -1026,7 +1124,7 @@ var BitfabClaudeAgentHandler = class {
1026
1124
  if (this.activeContext !== null) {
1027
1125
  return;
1028
1126
  }
1029
- const spanId = crypto.randomUUID();
1127
+ const spanId = randomUuid();
1030
1128
  this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1031
1129
  this.rootSpanId = spanId;
1032
1130
  }
@@ -1140,7 +1238,7 @@ var BitfabClaudeAgentHandler = class {
1140
1238
  // ── hook callbacks ───────────────────────────────────────────
1141
1239
  async preToolUseHook(inputData, toolUseId, _context) {
1142
1240
  try {
1143
- const sid = inputData.tool_use_id ?? toolUseId ?? crypto.randomUUID();
1241
+ const sid = inputData.tool_use_id ?? toolUseId ?? randomUuid();
1144
1242
  const toolName = inputData.tool_name ?? "tool";
1145
1243
  const toolInput = inputData.tool_input ?? {};
1146
1244
  const agentId = inputData.agent_id;
@@ -1170,10 +1268,10 @@ var BitfabClaudeAgentHandler = class {
1170
1268
  }
1171
1269
  async subagentStartHook(inputData, _toolUseId, _context) {
1172
1270
  try {
1173
- const agentId = inputData.agent_id ?? crypto.randomUUID();
1271
+ const agentId = inputData.agent_id ?? randomUuid();
1174
1272
  const agentType = inputData.agent_type ?? "subagent";
1175
1273
  const parentId = this.getParentId();
1176
- const spanId = crypto.randomUUID();
1274
+ const spanId = randomUuid();
1177
1275
  this.activeSubagentSpans.set(agentId, spanId);
1178
1276
  this.startSpan(
1179
1277
  spanId,
@@ -1314,7 +1412,7 @@ var BitfabClaudeAgentHandler = class {
1314
1412
  this.flushLlmTurn();
1315
1413
  this.conversationHistory.push(...this.pendingMessages);
1316
1414
  this.pendingMessages = [];
1317
- this.currentLlmSpanId = crypto.randomUUID();
1415
+ this.currentLlmSpanId = randomUuid();
1318
1416
  this.currentLlmMessageId = messageId ?? null;
1319
1417
  this.currentLlmContent = [];
1320
1418
  this.currentLlmModel = inner.model ?? null;
@@ -1758,6 +1856,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1758
1856
  }
1759
1857
 
1760
1858
  // src/langgraph.ts
1859
+ init_randomUuid();
1761
1860
  init_serialize();
1762
1861
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
1763
1862
  var LANGGRAPH_METADATA_KEYS = [
@@ -2006,7 +2105,7 @@ var BitfabLangGraphCallbackHandler = class {
2006
2105
  } else {
2007
2106
  const activeContext = this.getActiveSpanContext?.() ?? null;
2008
2107
  invocation = {
2009
- traceId: activeContext ? activeContext.traceId : crypto.randomUUID(),
2108
+ traceId: activeContext ? activeContext.traceId : randomUuid(),
2010
2109
  activeContext,
2011
2110
  rootRunId: runId
2012
2111
  };
@@ -2301,6 +2400,20 @@ var BitfabOpenAIAgentHandler = class {
2301
2400
  this.withSpanFn = config.withSpan;
2302
2401
  this.getActiveSpanContext = config.getActiveSpanContext;
2303
2402
  }
2403
+ /**
2404
+ * Drop-in replacement for the OpenAI Agents SDK's `run()` that records a
2405
+ * replayable root `agent` span.
2406
+ *
2407
+ * The `input` is captured as the root span's input (as a single positional
2408
+ * argument, so `replay(key, fn)` re-feeds it), and the run's `finalOutput`
2409
+ * is recorded as the root output. For streaming runs (`{ stream: true }`),
2410
+ * the result is handed back immediately and the final output is recorded
2411
+ * once the stream completes — first-byte latency is untouched.
2412
+ *
2413
+ * The process-wide tracing processor (`getOpenAiTracingProcessor`) must still
2414
+ * be registered: it captures the LLM/tool/handoff spans that nest beneath
2415
+ * this root.
2416
+ */
2304
2417
  async wrapRun(agent, input, options) {
2305
2418
  const { run } = await importOptionalPeer([
2306
2419
  "@openai",
@@ -2339,6 +2452,7 @@ var BitfabOpenAIAgentHandler = class {
2339
2452
  };
2340
2453
 
2341
2454
  // src/client.ts
2455
+ init_randomUuid();
2342
2456
  init_replayContext();
2343
2457
 
2344
2458
  // src/replayEnvironment.ts
@@ -2758,6 +2872,7 @@ var BitfabVercelAiHandler = class {
2758
2872
  };
2759
2873
 
2760
2874
  // src/client.ts
2875
+ init_warnOnce();
2761
2876
  var activeTraceStates = /* @__PURE__ */ new Map();
2762
2877
  var pendingSpanPromises = /* @__PURE__ */ new Map();
2763
2878
  var asyncLocalStorage = null;
@@ -3116,7 +3231,20 @@ var Bitfab = class {
3116
3231
  functionVersion.providers,
3117
3232
  this.envVars
3118
3233
  );
3119
- const resultStr = typeof executionResult.result === "string" ? executionResult.result : JSON.stringify(executionResult.result);
3234
+ let resultStr;
3235
+ if (typeof executionResult.result === "string") {
3236
+ resultStr = executionResult.result;
3237
+ } else {
3238
+ try {
3239
+ resultStr = JSON.stringify(executionResult.result) ?? String(executionResult.result);
3240
+ } catch {
3241
+ warnOnce(
3242
+ "call-result-serialize",
3243
+ "a local execution result could not be JSON-serialized; storing its String() form instead. The call still returns its real value."
3244
+ );
3245
+ resultStr = String(executionResult.result);
3246
+ }
3247
+ }
3120
3248
  this.httpClient.sendInternalTrace(functionVersion.id, {
3121
3249
  result: resultStr,
3122
3250
  source: "typescript-sdk",
@@ -3365,11 +3493,26 @@ var Bitfab = class {
3365
3493
  return await bamlClient[methodName](...args);
3366
3494
  }
3367
3495
  const collector = new CollectorClass("bitfab-baml-tracing");
3368
- const trackedClient = bamlClient.withOptions({ collector });
3369
- const trackedMethod = trackedClient[methodName];
3370
- const result = await trackedMethod.bind(
3371
- trackedClient
3372
- )(...args);
3496
+ let trackedClient;
3497
+ let trackedMethod;
3498
+ try {
3499
+ trackedClient = bamlClient.withOptions({ collector });
3500
+ const method2 = trackedClient[methodName];
3501
+ if (typeof method2 !== "function") {
3502
+ throw new BitfabError(
3503
+ "bamlClient.withOptions did not return the wrapped method"
3504
+ );
3505
+ }
3506
+ trackedMethod = method2;
3507
+ } catch {
3508
+ warnOnce(
3509
+ `wrapBAML-setup:${methodName}`,
3510
+ `BAML tracing setup failed for "${methodName}" (incompatible bamlClient or BAML version); calling it untraced. The call still runs; no span is recorded.`
3511
+ );
3512
+ wrappedFn.collector = null;
3513
+ return await bamlClient[methodName](...args);
3514
+ }
3515
+ const result = await trackedMethod.bind(trackedClient)(...args);
3373
3516
  wrappedFn.collector = collector;
3374
3517
  try {
3375
3518
  const prompt = extractPromptFromCollector(collector);
@@ -3445,200 +3588,229 @@ var Bitfab = class {
3445
3588
  () => wrappedFn.apply(this, args)
3446
3589
  );
3447
3590
  }
3448
- const currentStack = getSpanStack();
3449
- const parentContext = currentStack[currentStack.length - 1];
3450
- const replayCtxForTraceId = parentContext ? null : getReplayContext();
3451
- const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? crypto.randomUUID();
3452
- const spanId = crypto.randomUUID();
3453
- const parentSpanId = parentContext?.spanId ?? null;
3454
- const isRootSpan = parentSpanId === null;
3455
- const newContext = { traceId, spanId, contexts: [] };
3456
- const newStack = [...currentStack, newContext];
3457
- const inputs = args;
3458
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3459
- if (isRootSpan && !activeTraceStates.has(traceId)) {
3460
- const replayCtxAtRoot = getReplayContext();
3461
- const dbSnapshotRef = buildSnapshotRef(self.dbSnapshot, startedAt);
3462
- activeTraceStates.set(traceId, {
3591
+ let newStack;
3592
+ let executeWithContext;
3593
+ let registeredTraceId;
3594
+ try {
3595
+ const currentStack = getSpanStack();
3596
+ const parentContext = currentStack[currentStack.length - 1];
3597
+ const replayCtxForTraceId = parentContext ? null : getReplayContext();
3598
+ const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? randomUuid();
3599
+ const spanId = randomUuid();
3600
+ const parentSpanId = parentContext?.spanId ?? null;
3601
+ const isRootSpan = parentSpanId === null;
3602
+ const newContext = { traceId, spanId, contexts: [] };
3603
+ newStack = [...currentStack, newContext];
3604
+ const inputs = args;
3605
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3606
+ if (isRootSpan && !activeTraceStates.has(traceId)) {
3607
+ const replayCtxAtRoot = getReplayContext();
3608
+ const dbSnapshotRef = buildSnapshotRef(self.dbSnapshot, startedAt);
3609
+ activeTraceStates.set(traceId, {
3610
+ traceId,
3611
+ startedAt,
3612
+ contexts: [],
3613
+ ...replayCtxAtRoot?.testRunId && {
3614
+ testRunId: replayCtxAtRoot.testRunId
3615
+ },
3616
+ ...replayCtxAtRoot?.inputSourceTraceId && {
3617
+ inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
3618
+ },
3619
+ dbSnapshotRef
3620
+ });
3621
+ pendingSpanPromises.set(traceId, []);
3622
+ registeredTraceId = traceId;
3623
+ }
3624
+ const functionName = fn.name !== "" ? fn.name : void 0;
3625
+ const baseSpanParams = {
3626
+ traceFunctionKey,
3627
+ functionName,
3628
+ spanName: options.name ?? functionName ?? traceFunctionKey,
3463
3629
  traceId,
3630
+ spanId,
3631
+ parentSpanId,
3632
+ inputs,
3464
3633
  startedAt,
3465
- contexts: [],
3466
- ...replayCtxAtRoot?.testRunId && {
3467
- testRunId: replayCtxAtRoot.testRunId
3468
- },
3469
- ...replayCtxAtRoot?.inputSourceTraceId && {
3470
- inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
3471
- },
3472
- dbSnapshotRef
3473
- });
3474
- pendingSpanPromises.set(traceId, []);
3475
- }
3476
- const functionName = fn.name !== "" ? fn.name : void 0;
3477
- const baseSpanParams = {
3478
- traceFunctionKey,
3479
- functionName,
3480
- spanName: options.name ?? functionName ?? traceFunctionKey,
3481
- traceId,
3482
- spanId,
3483
- parentSpanId,
3484
- inputs,
3485
- startedAt,
3486
- spanType: options.type ?? "custom"
3487
- };
3488
- const sendSpan = async (params, spanOpts) => {
3489
- const replayCtx = getReplayContext();
3490
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3491
- let resolvePersistence;
3492
- if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3493
- persistenceCollector.push(
3494
- new Promise((resolve) => {
3495
- resolvePersistence = resolve;
3496
- })
3497
- );
3498
- }
3499
- try {
3500
- const endedAt = (/* @__PURE__ */ new Date()).toISOString();
3501
- const spanPromise = self.sendWrapperSpan({
3502
- ...baseSpanParams,
3503
- ...params,
3504
- contexts: newContext.contexts,
3505
- prompt: newContext.prompt,
3506
- endedAt,
3507
- ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
3508
- ...replayCtx?.inputSourceSpanId && {
3509
- inputSourceSpanId: replayCtx.inputSourceSpanId
3510
- }
3511
- });
3512
- if (isRootSpan) {
3513
- const pending = pendingSpanPromises.get(traceId) ?? [];
3514
- pending.push(spanPromise);
3515
- if (persistenceCollector) {
3516
- await Promise.allSettled(pending);
3517
- } else {
3518
- await Promise.race([
3519
- Promise.allSettled(pending),
3520
- new Promise((resolve) => setTimeout(resolve, 5e3))
3521
- ]);
3522
- }
3523
- pendingSpanPromises.delete(traceId);
3524
- const traceState = activeTraceStates.get(traceId);
3525
- const completionPromise = self.sendTraceCompletion({
3526
- traceFunctionKey,
3527
- traceId,
3528
- startedAt: traceState?.startedAt ?? startedAt,
3634
+ spanType: options.type ?? "custom"
3635
+ };
3636
+ const sendSpan = async (params, spanOpts) => {
3637
+ const replayCtx = getReplayContext();
3638
+ const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3639
+ let resolvePersistence;
3640
+ if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3641
+ persistenceCollector.push(
3642
+ new Promise((resolve) => {
3643
+ resolvePersistence = resolve;
3644
+ })
3645
+ );
3646
+ }
3647
+ try {
3648
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
3649
+ const spanPromise = self.sendWrapperSpan({
3650
+ ...baseSpanParams,
3651
+ ...params,
3652
+ contexts: newContext.contexts,
3653
+ prompt: newContext.prompt,
3529
3654
  endedAt,
3530
- sessionId: traceState?.sessionId,
3531
- metadata: traceState?.metadata,
3532
- contexts: traceState?.contexts ?? [],
3533
- testRunId: traceState?.testRunId,
3534
- inputSourceTraceId: traceState?.inputSourceTraceId,
3535
- dbSnapshotRef: traceState?.dbSnapshotRef,
3536
- // Built AFTER the wrapped fn finished, so `accessed` reflects
3537
- // whether customer code obtained the branch URL during this
3538
- // item. Omitted entirely when no lease was attached, so the
3539
- // server can distinguish "no branch" from "branch ignored".
3540
- ...replayCtx?.dbBranchLease && {
3541
- dbSnapshotUsage: {
3542
- neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3543
- snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3544
- sourceTraceId: replayCtx.sourceBitfabTraceId,
3545
- accessed: replayCtx.dbSnapshotAccessed === true
3546
- }
3655
+ ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
3656
+ ...replayCtx?.inputSourceSpanId && {
3657
+ inputSourceSpanId: replayCtx.inputSourceSpanId
3547
3658
  }
3548
3659
  });
3549
- activeTraceStates.delete(traceId);
3550
- if (persistenceCollector) {
3551
- await completionPromise;
3552
- }
3553
- } else {
3554
- const pending = pendingSpanPromises.get(traceId);
3555
- if (pending) {
3660
+ if (isRootSpan) {
3661
+ const pending = pendingSpanPromises.get(traceId) ?? [];
3556
3662
  pending.push(spanPromise);
3663
+ if (persistenceCollector) {
3664
+ await Promise.allSettled(pending);
3665
+ } else {
3666
+ let raceTimer;
3667
+ try {
3668
+ await Promise.race([
3669
+ Promise.allSettled(pending),
3670
+ new Promise((resolve) => {
3671
+ raceTimer = setTimeout(resolve, 5e3);
3672
+ unrefTimer(raceTimer);
3673
+ })
3674
+ ]);
3675
+ } finally {
3676
+ if (raceTimer) {
3677
+ clearTimeout(raceTimer);
3678
+ }
3679
+ }
3680
+ }
3681
+ pendingSpanPromises.delete(traceId);
3682
+ const traceState = activeTraceStates.get(traceId);
3683
+ const completionPromise = self.sendTraceCompletion({
3684
+ traceFunctionKey,
3685
+ traceId,
3686
+ startedAt: traceState?.startedAt ?? startedAt,
3687
+ endedAt,
3688
+ sessionId: traceState?.sessionId,
3689
+ metadata: traceState?.metadata,
3690
+ contexts: traceState?.contexts ?? [],
3691
+ testRunId: traceState?.testRunId,
3692
+ inputSourceTraceId: traceState?.inputSourceTraceId,
3693
+ dbSnapshotRef: traceState?.dbSnapshotRef,
3694
+ // Built AFTER the wrapped fn finished, so `accessed` reflects
3695
+ // whether customer code obtained the branch URL during this
3696
+ // item. Omitted entirely when no lease was attached, so the
3697
+ // server can distinguish "no branch" from "branch ignored".
3698
+ ...replayCtx?.dbBranchLease && {
3699
+ dbSnapshotUsage: {
3700
+ neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3701
+ snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3702
+ sourceTraceId: replayCtx.sourceBitfabTraceId,
3703
+ accessed: replayCtx.dbSnapshotAccessed === true
3704
+ }
3705
+ }
3706
+ });
3707
+ activeTraceStates.delete(traceId);
3708
+ if (persistenceCollector) {
3709
+ await completionPromise;
3710
+ }
3557
3711
  } else {
3558
- pendingSpanPromises.set(traceId, [spanPromise]);
3712
+ const pending = pendingSpanPromises.get(traceId);
3713
+ if (pending) {
3714
+ pending.push(spanPromise);
3715
+ } else {
3716
+ pendingSpanPromises.set(traceId, [spanPromise]);
3717
+ }
3559
3718
  }
3719
+ } catch {
3720
+ } finally {
3721
+ resolvePersistence?.();
3560
3722
  }
3561
- } catch {
3562
- } finally {
3563
- resolvePersistence?.();
3564
- }
3565
- };
3566
- const replayCtxForMock = getReplayContext();
3567
- if (replayCtxForMock?.mockTree && !isRootSpan) {
3568
- const counters = replayCtxForMock.callCounters;
3569
- const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
3570
- const callIndex = counters.get(counterKey) ?? 0;
3571
- counters.set(counterKey, callIndex + 1);
3572
- const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3573
- if (shouldMock) {
3574
- const mockKey = `${counterKey}:${callIndex}`;
3575
- const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3576
- if (mockSpan) {
3577
- let output = mockSpan.output;
3578
- if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3579
- output = deserializeValue({
3580
- json: mockSpan.output,
3581
- meta: mockSpan.outputMeta
3582
- });
3583
- }
3584
- void sendSpan({ result: output });
3585
- if (fnReturnsPromise) {
3586
- return Promise.resolve(output);
3723
+ };
3724
+ const replayCtxForMock = getReplayContext();
3725
+ if (replayCtxForMock?.mockTree && !isRootSpan) {
3726
+ const counters = replayCtxForMock.callCounters;
3727
+ const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
3728
+ const callIndex = counters.get(counterKey) ?? 0;
3729
+ counters.set(counterKey, callIndex + 1);
3730
+ const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3731
+ if (shouldMock) {
3732
+ const mockKey = `${counterKey}:${callIndex}`;
3733
+ const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3734
+ if (mockSpan) {
3735
+ let output = mockSpan.output;
3736
+ if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3737
+ output = deserializeValue({
3738
+ json: mockSpan.output,
3739
+ meta: mockSpan.outputMeta
3740
+ });
3741
+ }
3742
+ void sendSpan({ result: output });
3743
+ if (fnReturnsPromise) {
3744
+ return Promise.resolve(output);
3745
+ }
3746
+ return output;
3587
3747
  }
3588
- return output;
3589
3748
  }
3590
3749
  }
3591
- }
3592
- const recordSpan = (result) => {
3593
- if (options.finalize) {
3594
- const replayCtx = getReplayContext();
3595
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3596
- let resolvePersistence;
3597
- if (persistenceCollector) {
3598
- persistenceCollector.push(
3599
- new Promise((resolve) => {
3600
- resolvePersistence = resolve;
3601
- })
3602
- );
3750
+ const recordSpan = (result) => {
3751
+ if (options.finalize) {
3752
+ const replayCtx = getReplayContext();
3753
+ const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3754
+ let resolvePersistence;
3755
+ if (persistenceCollector) {
3756
+ persistenceCollector.push(
3757
+ new Promise((resolve) => {
3758
+ resolvePersistence = resolve;
3759
+ })
3760
+ );
3761
+ }
3762
+ void Promise.resolve().then(() => options.finalize(result)).then(
3763
+ (output) => sendSpan(
3764
+ { result: output },
3765
+ { skipPersistenceRegistration: true }
3766
+ )
3767
+ ).catch(
3768
+ (error) => sendSpan(
3769
+ {
3770
+ result: void 0,
3771
+ error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3772
+ },
3773
+ { skipPersistenceRegistration: true }
3774
+ )
3775
+ ).finally(() => resolvePersistence?.());
3776
+ } else {
3777
+ void sendSpan({ result });
3603
3778
  }
3604
- void Promise.resolve().then(() => options.finalize(result)).then(
3605
- (output) => sendSpan(
3606
- { result: output },
3607
- { skipPersistenceRegistration: true }
3608
- )
3609
- ).catch(
3610
- (error) => sendSpan(
3611
- {
3779
+ };
3780
+ executeWithContext = () => {
3781
+ const result = fn(...args);
3782
+ if (result instanceof Promise) {
3783
+ return result.then((resolvedResult) => {
3784
+ recordSpan(resolvedResult);
3785
+ return resolvedResult;
3786
+ }).catch((error) => {
3787
+ void sendSpan({
3612
3788
  result: void 0,
3613
- error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3614
- },
3615
- { skipPersistenceRegistration: true }
3616
- )
3617
- ).finally(() => resolvePersistence?.());
3618
- } else {
3619
- void sendSpan({ result });
3620
- }
3621
- };
3622
- const executeWithContext = () => {
3623
- const result = fn(...args);
3624
- if (result instanceof Promise) {
3625
- return result.then((resolvedResult) => {
3626
- recordSpan(resolvedResult);
3627
- return resolvedResult;
3628
- }).catch((error) => {
3629
- void sendSpan({
3630
- result: void 0,
3631
- error: error instanceof Error ? error.message : String(error)
3789
+ error: error instanceof Error ? error.message : String(error)
3790
+ });
3791
+ throw error;
3632
3792
  });
3633
- throw error;
3634
- });
3793
+ }
3794
+ if (isAsyncGenerator(result)) {
3795
+ return wrapAsyncGenerator(result, newStack, sendSpan);
3796
+ }
3797
+ recordSpan(result);
3798
+ return result;
3799
+ };
3800
+ } catch (setupError) {
3801
+ if (registeredTraceId) {
3802
+ activeTraceStates.delete(registeredTraceId);
3803
+ pendingSpanPromises.delete(registeredTraceId);
3635
3804
  }
3636
- if (isAsyncGenerator(result)) {
3637
- return wrapAsyncGenerator(result, newStack, sendSpan);
3805
+ if (getReplayContext()) {
3806
+ throw setupError;
3638
3807
  }
3639
- recordSpan(result);
3640
- return result;
3641
- };
3808
+ warnOnce(
3809
+ `withSpan-setup:${traceFunctionKey}`,
3810
+ `tracing setup failed for "${traceFunctionKey}"; running it untraced. The function still runs and returns normally; no span is recorded.`
3811
+ );
3812
+ return fn(...args);
3813
+ }
3642
3814
  return runWithSpanStack(newStack, executeWithContext);
3643
3815
  };
3644
3816
  Object.defineProperty(wrappedFn, "_bitfabTraceFunctionKey", {