bitfab 0.23.2 → 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.2";
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;
@@ -1456,6 +1554,16 @@ var BitfabClaudeAgentHandler = class {
1456
1554
  // src/client.ts
1457
1555
  init_asyncStorage();
1458
1556
 
1557
+ // src/optionalPeer.ts
1558
+ function importOptionalPeer(specifierParts) {
1559
+ const specifier = specifierParts.join("/");
1560
+ return import(
1561
+ /* webpackIgnore: true */
1562
+ /* @vite-ignore */
1563
+ specifier
1564
+ );
1565
+ }
1566
+
1459
1567
  // src/baml.ts
1460
1568
  var cachedBaml = null;
1461
1569
  async function loadBaml() {
@@ -1463,7 +1571,7 @@ async function loadBaml() {
1463
1571
  return cachedBaml;
1464
1572
  }
1465
1573
  try {
1466
- cachedBaml = await import("@boundaryml/baml");
1574
+ cachedBaml = await importOptionalPeer(["@boundaryml", "baml"]);
1467
1575
  return cachedBaml;
1468
1576
  } catch {
1469
1577
  throw new Error(
@@ -1762,6 +1870,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1762
1870
  }
1763
1871
 
1764
1872
  // src/langgraph.ts
1873
+ init_randomUuid();
1765
1874
  init_serialize();
1766
1875
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
1767
1876
  var LANGGRAPH_METADATA_KEYS = [
@@ -2010,7 +2119,7 @@ var BitfabLangGraphCallbackHandler = class {
2010
2119
  } else {
2011
2120
  const activeContext = this.getActiveSpanContext?.() ?? null;
2012
2121
  invocation = {
2013
- traceId: activeContext ? activeContext.traceId : crypto.randomUUID(),
2122
+ traceId: activeContext ? activeContext.traceId : randomUuid(),
2014
2123
  activeContext,
2015
2124
  rootRunId: runId
2016
2125
  };
@@ -2305,8 +2414,25 @@ var BitfabOpenAIAgentHandler = class {
2305
2414
  this.withSpanFn = config.withSpan;
2306
2415
  this.getActiveSpanContext = config.getActiveSpanContext;
2307
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
+ */
2308
2431
  async wrapRun(agent, input, options) {
2309
- const { run } = await import("@openai/agents");
2432
+ const { run } = await importOptionalPeer([
2433
+ "@openai",
2434
+ "agents"
2435
+ ]);
2310
2436
  if (this.getActiveSpanContext?.() != null) {
2311
2437
  return run(
2312
2438
  agent,
@@ -2340,6 +2466,7 @@ var BitfabOpenAIAgentHandler = class {
2340
2466
  };
2341
2467
 
2342
2468
  // src/client.ts
2469
+ init_randomUuid();
2343
2470
  init_replayContext();
2344
2471
 
2345
2472
  // src/replayEnvironment.ts
@@ -2759,6 +2886,7 @@ var BitfabVercelAiHandler = class {
2759
2886
  };
2760
2887
 
2761
2888
  // src/client.ts
2889
+ init_warnOnce();
2762
2890
  var activeTraceStates = /* @__PURE__ */ new Map();
2763
2891
  var pendingSpanPromises = /* @__PURE__ */ new Map();
2764
2892
  var asyncLocalStorage = null;
@@ -2857,7 +2985,10 @@ async function loadCollectorClass() {
2857
2985
  return cachedCollectorClass;
2858
2986
  }
2859
2987
  try {
2860
- const baml = await import("@boundaryml/baml");
2988
+ const baml = await importOptionalPeer([
2989
+ "@boundaryml",
2990
+ "baml"
2991
+ ]);
2861
2992
  cachedCollectorClass = baml.Collector;
2862
2993
  return cachedCollectorClass;
2863
2994
  } catch {
@@ -3114,7 +3245,20 @@ var Bitfab = class {
3114
3245
  functionVersion.providers,
3115
3246
  this.envVars
3116
3247
  );
3117
- 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
+ }
3118
3262
  this.httpClient.sendInternalTrace(functionVersion.id, {
3119
3263
  result: resultStr,
3120
3264
  source: "typescript-sdk",
@@ -3363,11 +3507,26 @@ var Bitfab = class {
3363
3507
  return await bamlClient[methodName](...args);
3364
3508
  }
3365
3509
  const collector = new CollectorClass("bitfab-baml-tracing");
3366
- const trackedClient = bamlClient.withOptions({ collector });
3367
- const trackedMethod = trackedClient[methodName];
3368
- const result = await trackedMethod.bind(
3369
- trackedClient
3370
- )(...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);
3371
3530
  wrappedFn.collector = collector;
3372
3531
  try {
3373
3532
  const prompt = extractPromptFromCollector(collector);
@@ -3443,200 +3602,229 @@ var Bitfab = class {
3443
3602
  () => wrappedFn.apply(this, args)
3444
3603
  );
3445
3604
  }
3446
- const currentStack = getSpanStack();
3447
- const parentContext = currentStack[currentStack.length - 1];
3448
- const replayCtxForTraceId = parentContext ? null : getReplayContext();
3449
- const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? crypto.randomUUID();
3450
- const spanId = crypto.randomUUID();
3451
- const parentSpanId = parentContext?.spanId ?? null;
3452
- const isRootSpan = parentSpanId === null;
3453
- const newContext = { traceId, spanId, contexts: [] };
3454
- const newStack = [...currentStack, newContext];
3455
- const inputs = args;
3456
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3457
- if (isRootSpan && !activeTraceStates.has(traceId)) {
3458
- const replayCtxAtRoot = getReplayContext();
3459
- const dbSnapshotRef = buildSnapshotRef(self.dbSnapshot, startedAt);
3460
- 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,
3461
3643
  traceId,
3644
+ spanId,
3645
+ parentSpanId,
3646
+ inputs,
3462
3647
  startedAt,
3463
- contexts: [],
3464
- ...replayCtxAtRoot?.testRunId && {
3465
- testRunId: replayCtxAtRoot.testRunId
3466
- },
3467
- ...replayCtxAtRoot?.inputSourceTraceId && {
3468
- inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
3469
- },
3470
- dbSnapshotRef
3471
- });
3472
- pendingSpanPromises.set(traceId, []);
3473
- }
3474
- const functionName = fn.name !== "" ? fn.name : void 0;
3475
- const baseSpanParams = {
3476
- traceFunctionKey,
3477
- functionName,
3478
- spanName: options.name ?? functionName ?? traceFunctionKey,
3479
- traceId,
3480
- spanId,
3481
- parentSpanId,
3482
- inputs,
3483
- startedAt,
3484
- spanType: options.type ?? "custom"
3485
- };
3486
- const sendSpan = async (params, spanOpts) => {
3487
- const replayCtx = getReplayContext();
3488
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3489
- let resolvePersistence;
3490
- if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
3491
- persistenceCollector.push(
3492
- new Promise((resolve) => {
3493
- resolvePersistence = resolve;
3494
- })
3495
- );
3496
- }
3497
- try {
3498
- const endedAt = (/* @__PURE__ */ new Date()).toISOString();
3499
- const spanPromise = self.sendWrapperSpan({
3500
- ...baseSpanParams,
3501
- ...params,
3502
- contexts: newContext.contexts,
3503
- prompt: newContext.prompt,
3504
- endedAt,
3505
- ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
3506
- ...replayCtx?.inputSourceSpanId && {
3507
- inputSourceSpanId: replayCtx.inputSourceSpanId
3508
- }
3509
- });
3510
- if (isRootSpan) {
3511
- const pending = pendingSpanPromises.get(traceId) ?? [];
3512
- pending.push(spanPromise);
3513
- if (persistenceCollector) {
3514
- await Promise.allSettled(pending);
3515
- } else {
3516
- await Promise.race([
3517
- Promise.allSettled(pending),
3518
- new Promise((resolve) => setTimeout(resolve, 5e3))
3519
- ]);
3520
- }
3521
- pendingSpanPromises.delete(traceId);
3522
- const traceState = activeTraceStates.get(traceId);
3523
- const completionPromise = self.sendTraceCompletion({
3524
- traceFunctionKey,
3525
- traceId,
3526
- 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,
3527
3668
  endedAt,
3528
- sessionId: traceState?.sessionId,
3529
- metadata: traceState?.metadata,
3530
- contexts: traceState?.contexts ?? [],
3531
- testRunId: traceState?.testRunId,
3532
- inputSourceTraceId: traceState?.inputSourceTraceId,
3533
- dbSnapshotRef: traceState?.dbSnapshotRef,
3534
- // Built AFTER the wrapped fn finished, so `accessed` reflects
3535
- // whether customer code obtained the branch URL during this
3536
- // item. Omitted entirely when no lease was attached, so the
3537
- // server can distinguish "no branch" from "branch ignored".
3538
- ...replayCtx?.dbBranchLease && {
3539
- dbSnapshotUsage: {
3540
- neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3541
- snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3542
- sourceTraceId: replayCtx.sourceBitfabTraceId,
3543
- accessed: replayCtx.dbSnapshotAccessed === true
3544
- }
3669
+ ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
3670
+ ...replayCtx?.inputSourceSpanId && {
3671
+ inputSourceSpanId: replayCtx.inputSourceSpanId
3545
3672
  }
3546
3673
  });
3547
- activeTraceStates.delete(traceId);
3548
- if (persistenceCollector) {
3549
- await completionPromise;
3550
- }
3551
- } else {
3552
- const pending = pendingSpanPromises.get(traceId);
3553
- if (pending) {
3674
+ if (isRootSpan) {
3675
+ const pending = pendingSpanPromises.get(traceId) ?? [];
3554
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
+ }
3555
3725
  } else {
3556
- 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
+ }
3557
3732
  }
3733
+ } catch {
3734
+ } finally {
3735
+ resolvePersistence?.();
3558
3736
  }
3559
- } catch {
3560
- } finally {
3561
- resolvePersistence?.();
3562
- }
3563
- };
3564
- const replayCtxForMock = getReplayContext();
3565
- if (replayCtxForMock?.mockTree && !isRootSpan) {
3566
- const counters = replayCtxForMock.callCounters;
3567
- const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
3568
- const callIndex = counters.get(counterKey) ?? 0;
3569
- counters.set(counterKey, callIndex + 1);
3570
- const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3571
- if (shouldMock) {
3572
- const mockKey = `${counterKey}:${callIndex}`;
3573
- const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3574
- if (mockSpan) {
3575
- let output = mockSpan.output;
3576
- if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3577
- output = deserializeValue({
3578
- json: mockSpan.output,
3579
- meta: mockSpan.outputMeta
3580
- });
3581
- }
3582
- void sendSpan({ result: output });
3583
- if (fnReturnsPromise) {
3584
- 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;
3585
3761
  }
3586
- return output;
3587
3762
  }
3588
3763
  }
3589
- }
3590
- const recordSpan = (result) => {
3591
- if (options.finalize) {
3592
- const replayCtx = getReplayContext();
3593
- const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
3594
- let resolvePersistence;
3595
- if (persistenceCollector) {
3596
- persistenceCollector.push(
3597
- new Promise((resolve) => {
3598
- resolvePersistence = resolve;
3599
- })
3600
- );
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 });
3601
3792
  }
3602
- void Promise.resolve().then(() => options.finalize(result)).then(
3603
- (output) => sendSpan(
3604
- { result: output },
3605
- { skipPersistenceRegistration: true }
3606
- )
3607
- ).catch(
3608
- (error) => sendSpan(
3609
- {
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({
3610
3802
  result: void 0,
3611
- error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3612
- },
3613
- { skipPersistenceRegistration: true }
3614
- )
3615
- ).finally(() => resolvePersistence?.());
3616
- } else {
3617
- void sendSpan({ result });
3618
- }
3619
- };
3620
- const executeWithContext = () => {
3621
- const result = fn(...args);
3622
- if (result instanceof Promise) {
3623
- return result.then((resolvedResult) => {
3624
- recordSpan(resolvedResult);
3625
- return resolvedResult;
3626
- }).catch((error) => {
3627
- void sendSpan({
3628
- result: void 0,
3629
- error: error instanceof Error ? error.message : String(error)
3803
+ error: error instanceof Error ? error.message : String(error)
3804
+ });
3805
+ throw error;
3630
3806
  });
3631
- throw error;
3632
- });
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);
3633
3818
  }
3634
- if (isAsyncGenerator(result)) {
3635
- return wrapAsyncGenerator(result, newStack, sendSpan);
3819
+ if (getReplayContext()) {
3820
+ throw setupError;
3636
3821
  }
3637
- recordSpan(result);
3638
- return result;
3639
- };
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
+ }
3640
3828
  return runWithSpanStack(newStack, executeWithContext);
3641
3829
  };
3642
3830
  Object.defineProperty(wrappedFn, "_bitfabTraceFunctionKey", {