@bitfab/sdk 0.28.11 → 0.29.1
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/{chunk-ZBWTCBVQ.js → chunk-RTPEBWVO.js} +102 -45
- package/dist/chunk-RTPEBWVO.js.map +1 -0
- package/dist/{chunk-5E4BUIYA.js → chunk-YZU6WFG2.js} +113 -55
- package/dist/chunk-YZU6WFG2.js.map +1 -0
- package/dist/index.cjs +218 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -15
- package/dist/index.d.ts +165 -15
- package/dist/index.js +2 -2
- package/dist/node.cjs +218 -96
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +2 -2
- package/dist/{replay-IEE4RY57.js → replay-SKW5A7II.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-5E4BUIYA.js.map +0 -1
- package/dist/chunk-ZBWTCBVQ.js.map +0 -1
- /package/dist/{replay-IEE4RY57.js.map → replay-SKW5A7II.js.map} +0 -0
package/dist/node.cjs
CHANGED
|
@@ -289,6 +289,22 @@ var init_randomUuid = __esm({
|
|
|
289
289
|
}
|
|
290
290
|
});
|
|
291
291
|
|
|
292
|
+
// src/mockOverride.ts
|
|
293
|
+
function resolveMockValue(value, ctx) {
|
|
294
|
+
return typeof value === "function" ? value(ctx) : value;
|
|
295
|
+
}
|
|
296
|
+
function normalizeMockOverrides(mockOverride) {
|
|
297
|
+
if (mockOverride === void 0) {
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
return Array.isArray(mockOverride) ? mockOverride : [mockOverride];
|
|
301
|
+
}
|
|
302
|
+
var init_mockOverride = __esm({
|
|
303
|
+
"src/mockOverride.ts"() {
|
|
304
|
+
"use strict";
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
292
308
|
// src/replayContext.ts
|
|
293
309
|
function getReplayContext() {
|
|
294
310
|
return replayContextStorage?.getStore() ?? null;
|
|
@@ -364,6 +380,7 @@ function buildMockTree(rootNode) {
|
|
|
364
380
|
counters.set(counterKey, index + 1);
|
|
365
381
|
spans.set(`${counterKey}:${index}`, {
|
|
366
382
|
sourceSpanId: node.sourceSpanId,
|
|
383
|
+
externalSpanId: node.externalSpanId,
|
|
367
384
|
output: node.output,
|
|
368
385
|
outputMeta: node.outputMeta
|
|
369
386
|
});
|
|
@@ -377,57 +394,79 @@ function buildMockTree(rootNode) {
|
|
|
377
394
|
}
|
|
378
395
|
return { spans };
|
|
379
396
|
}
|
|
380
|
-
async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment, adaptInputs) {
|
|
397
|
+
async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, environment, adaptInputs) {
|
|
381
398
|
const lease = environment ? serverItem.dbBranchLease : void 0;
|
|
382
399
|
let inputs = [];
|
|
383
400
|
let originalOutput;
|
|
384
401
|
let result;
|
|
385
402
|
let error = null;
|
|
386
|
-
const replayedTraceId = randomUuid();
|
|
387
403
|
const pendingPersistence = [];
|
|
404
|
+
const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
|
|
405
|
+
const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
|
|
388
406
|
try {
|
|
389
|
-
const span = await httpClient.getExternalSpan(
|
|
407
|
+
const span = await httpClient.getExternalSpan(originalSpanId);
|
|
390
408
|
const spanData = span.rawData?.span_data ?? {};
|
|
391
409
|
inputs = deserializeInputs(spanData);
|
|
392
410
|
originalOutput = deserializeOutput(spanData);
|
|
393
411
|
if (adaptInputs) {
|
|
394
412
|
inputs = adaptInputs(inputs, {
|
|
395
|
-
|
|
396
|
-
|
|
413
|
+
originalTraceId,
|
|
414
|
+
originalSpanId,
|
|
415
|
+
// Deprecated aliases for originalTraceId/originalSpanId.
|
|
416
|
+
sourceTraceId: originalTraceId,
|
|
417
|
+
sourceSpanId: originalSpanId
|
|
397
418
|
});
|
|
398
419
|
}
|
|
420
|
+
const hasOverrides = resolvedOverrides.length > 0;
|
|
421
|
+
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
422
|
+
const includeOutputs = mockStrategy === "all";
|
|
399
423
|
let mockTree;
|
|
400
|
-
if (
|
|
424
|
+
if (needTree) {
|
|
401
425
|
try {
|
|
402
|
-
const treeResponse = await httpClient.getSpanTree(
|
|
403
|
-
|
|
404
|
-
);
|
|
426
|
+
const treeResponse = await httpClient.getSpanTree(originalSpanId, {
|
|
427
|
+
includeOutputs
|
|
428
|
+
});
|
|
405
429
|
if (treeResponse.root) {
|
|
406
430
|
mockTree = buildMockTree(treeResponse.root);
|
|
407
|
-
} else if (mockStrategy === "all") {
|
|
431
|
+
} else if (mockStrategy === "all" || hasOverrides) {
|
|
408
432
|
throw new BitfabError(
|
|
409
|
-
`Replay mock strategy "
|
|
433
|
+
`Replay mock strategy "${mockStrategy}"${hasOverrides ? " with overrides" : ""} requires a span tree root for original span ${originalSpanId}.`
|
|
410
434
|
);
|
|
411
435
|
} else {
|
|
412
436
|
mockTree = void 0;
|
|
413
437
|
}
|
|
414
438
|
} catch (e) {
|
|
415
|
-
if (mockStrategy === "all") {
|
|
439
|
+
if (mockStrategy === "all" || hasOverrides) {
|
|
416
440
|
throw e;
|
|
417
441
|
}
|
|
418
442
|
mockTree = void 0;
|
|
419
443
|
}
|
|
420
444
|
}
|
|
445
|
+
const outputCache = /* @__PURE__ */ new Map();
|
|
446
|
+
const fetchSpanOutput = mockTree && !includeOutputs ? (externalSpanId) => {
|
|
447
|
+
let pending = outputCache.get(externalSpanId);
|
|
448
|
+
if (!pending) {
|
|
449
|
+
pending = httpClient.getExternalSpan(externalSpanId).then(
|
|
450
|
+
(s) => deserializeOutput(
|
|
451
|
+
s.rawData?.span_data ?? {}
|
|
452
|
+
)
|
|
453
|
+
);
|
|
454
|
+
outputCache.set(externalSpanId, pending);
|
|
455
|
+
}
|
|
456
|
+
return pending;
|
|
457
|
+
} : void 0;
|
|
421
458
|
const maybePromise = runWithReplayContext(
|
|
422
459
|
{
|
|
423
460
|
testRunId,
|
|
424
461
|
traceId: replayedTraceId,
|
|
425
462
|
inputSourceSpanId: span.id,
|
|
426
463
|
inputSourceTraceId: span.externalTraceId,
|
|
427
|
-
sourceBitfabTraceId:
|
|
464
|
+
sourceBitfabTraceId: originalTraceId,
|
|
428
465
|
mockTree,
|
|
429
466
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
430
467
|
mockStrategy,
|
|
468
|
+
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
469
|
+
fetchSpanOutput,
|
|
431
470
|
dbBranchLease: lease,
|
|
432
471
|
pendingPersistence
|
|
433
472
|
},
|
|
@@ -452,7 +491,15 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
452
491
|
}
|
|
453
492
|
}
|
|
454
493
|
return {
|
|
455
|
-
|
|
494
|
+
// Written in by replay() from the complete-replay response once the server
|
|
495
|
+
// has minted this replay trace's row. Null until then: the client-side
|
|
496
|
+
// correlation id (replayedTraceId) is never surfaced as the item's traceId.
|
|
497
|
+
traceId: null,
|
|
498
|
+
originalTraceId,
|
|
499
|
+
originalSpanId,
|
|
500
|
+
// Deprecated aliases for originalTraceId/originalSpanId.
|
|
501
|
+
sourceTraceId: originalTraceId,
|
|
502
|
+
sourceSpanId: originalSpanId,
|
|
456
503
|
input: inputs,
|
|
457
504
|
result,
|
|
458
505
|
originalOutput,
|
|
@@ -484,7 +531,7 @@ async function mapWithConcurrency(tasks, maxConcurrency, onSettled) {
|
|
|
484
531
|
await Promise.all(workers);
|
|
485
532
|
return results;
|
|
486
533
|
}
|
|
487
|
-
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
534
|
+
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
|
|
488
535
|
if (options?.traceIds !== void 0) {
|
|
489
536
|
if (options.traceIds.length === 0) {
|
|
490
537
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -524,13 +571,20 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
524
571
|
);
|
|
525
572
|
const mockStrategy = options?.mock ?? "marked";
|
|
526
573
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
574
|
+
const resolvedOverrides = [
|
|
575
|
+
...normalizeMockOverrides(options?.mockOverride),
|
|
576
|
+
...registeredOverrides
|
|
577
|
+
];
|
|
578
|
+
const replayedTraceIds = serverItems.map(() => randomUuid());
|
|
527
579
|
const tasks = serverItems.map(
|
|
528
|
-
(serverItem) => () => processItem(
|
|
580
|
+
(serverItem, index) => () => processItem(
|
|
529
581
|
httpClient,
|
|
530
582
|
serverItem,
|
|
531
583
|
fn,
|
|
532
584
|
testRunId,
|
|
533
585
|
mockStrategy,
|
|
586
|
+
resolvedOverrides,
|
|
587
|
+
replayedTraceIds[index],
|
|
534
588
|
options?.environment,
|
|
535
589
|
options?.adaptInputs
|
|
536
590
|
)
|
|
@@ -557,11 +611,17 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
557
611
|
succeeded,
|
|
558
612
|
errored,
|
|
559
613
|
item: {
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
|
|
564
|
-
|
|
614
|
+
// The server replay trace id isn't known until completeReplay
|
|
615
|
+
// runs (below), so it can't be reported mid-run and we never
|
|
616
|
+
// emit the client-side placeholder. originalTraceId (the
|
|
617
|
+
// historical trace) is known now and is what a UI keys on to
|
|
618
|
+
// identify what just settled.
|
|
619
|
+
traceId: null,
|
|
620
|
+
originalTraceId: item.originalTraceId ?? null,
|
|
621
|
+
originalSpanId: item.originalSpanId ?? null,
|
|
622
|
+
// Deprecated aliases for originalTraceId/originalSpanId.
|
|
623
|
+
sourceTraceId: item.originalTraceId ?? null,
|
|
624
|
+
sourceSpanId: item.originalSpanId ?? null,
|
|
565
625
|
input: item.input,
|
|
566
626
|
result: item.result,
|
|
567
627
|
originalOutput: item.originalOutput,
|
|
@@ -579,56 +639,58 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
579
639
|
const completeResult = await httpClient.completeReplay(testRunId);
|
|
580
640
|
const serverTraceIds = completeResult.traceIds;
|
|
581
641
|
const replayTokens = completeResult.tokens;
|
|
582
|
-
if (serverTraceIds
|
|
583
|
-
try {
|
|
584
|
-
console.warn(
|
|
585
|
-
"Bitfab: server did not return replay trace IDs; item.traceId will be null (server upgrade required for verdict persistence)"
|
|
586
|
-
);
|
|
587
|
-
} catch {
|
|
588
|
-
}
|
|
589
|
-
for (const item of resultItems) {
|
|
590
|
-
item.traceId = null;
|
|
591
|
-
}
|
|
592
|
-
} else {
|
|
642
|
+
if (serverTraceIds !== void 0) {
|
|
593
643
|
const missing = [];
|
|
594
644
|
let completedCount = 0;
|
|
595
|
-
for (
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (mapped !== void 0) {
|
|
605
|
-
item.tokens = replayTokens?.[mapped] ?? null;
|
|
645
|
+
for (let index = 0; index < resultItems.length; index += 1) {
|
|
646
|
+
const item = resultItems[index];
|
|
647
|
+
const localId = replayedTraceIds[index];
|
|
648
|
+
const mapped = localId ? serverTraceIds[localId] : void 0;
|
|
649
|
+
item.traceId = mapped ?? null;
|
|
650
|
+
if (item.error === null) {
|
|
651
|
+
completedCount += 1;
|
|
652
|
+
if (mapped === void 0) {
|
|
653
|
+
missing.push(localId ?? item.originalTraceId);
|
|
606
654
|
}
|
|
607
|
-
|
|
655
|
+
}
|
|
656
|
+
if (mapped !== void 0) {
|
|
657
|
+
item.tokens = replayTokens?.[mapped] ?? null;
|
|
608
658
|
}
|
|
609
659
|
}
|
|
610
|
-
if (missing.length
|
|
660
|
+
if (completedCount > 0 && missing.length === completedCount) {
|
|
611
661
|
const serverCount = completeResult.traceCount !== void 0 ? ` The server persisted ${completeResult.traceCount} trace(s) for this run.` : "";
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
662
|
+
throw new BitfabError(
|
|
663
|
+
`Replay completed but the server has no persisted trace for any of the ${completedCount} completed item(s) (testRunId ${testRunId}).${serverCount} Trace uploads were awaited, so either the uploads failed (check for "Bitfab: Failed to create" errors above) or the replayed function is not wrapped with withSpan.`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
if (missing.length > 0) {
|
|
617
667
|
try {
|
|
618
668
|
console.error(
|
|
619
|
-
`Bitfab: server has no persisted trace for ${missing.length} of ${completedCount} completed replay item(s) (testRunId ${testRunId})
|
|
669
|
+
`Bitfab: server has no persisted trace for ${missing.length} of ${completedCount} completed replay item(s) (testRunId ${testRunId}). Their replay token usage is unavailable and they cannot be labeled.`
|
|
620
670
|
);
|
|
621
671
|
} catch {
|
|
622
672
|
}
|
|
623
673
|
}
|
|
624
674
|
}
|
|
625
|
-
const
|
|
675
|
+
const result = {
|
|
626
676
|
items: resultItems,
|
|
627
677
|
testRunId,
|
|
628
678
|
testRunUrl: `${serviceUrl}${testRunUrl}`
|
|
629
679
|
};
|
|
630
|
-
await writeReplayResultFile(
|
|
631
|
-
|
|
680
|
+
await writeReplayResultFile(result);
|
|
681
|
+
try {
|
|
682
|
+
options?.onProgress?.({
|
|
683
|
+
type: "complete",
|
|
684
|
+
testRunId,
|
|
685
|
+
completed: total,
|
|
686
|
+
total,
|
|
687
|
+
succeeded,
|
|
688
|
+
errored,
|
|
689
|
+
result
|
|
690
|
+
});
|
|
691
|
+
} catch {
|
|
692
|
+
}
|
|
693
|
+
return result;
|
|
632
694
|
}
|
|
633
695
|
async function writeReplayResultFile(result) {
|
|
634
696
|
const resultPath = typeof process !== "undefined" ? process.env?.BITFAB_REPLAY_RESULT_PATH : void 0;
|
|
@@ -657,6 +719,7 @@ var init_replay = __esm({
|
|
|
657
719
|
"src/replay.ts"() {
|
|
658
720
|
"use strict";
|
|
659
721
|
init_errors();
|
|
722
|
+
init_mockOverride();
|
|
660
723
|
init_randomUuid();
|
|
661
724
|
init_replayContext();
|
|
662
725
|
init_serialize();
|
|
@@ -697,7 +760,7 @@ registerAsyncLocalStorageClass(
|
|
|
697
760
|
);
|
|
698
761
|
|
|
699
762
|
// src/version.generated.ts
|
|
700
|
-
var __version__ = "0.
|
|
763
|
+
var __version__ = "0.29.1";
|
|
701
764
|
|
|
702
765
|
// src/constants.ts
|
|
703
766
|
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
@@ -1126,9 +1189,14 @@ var HttpClient = class {
|
|
|
1126
1189
|
/**
|
|
1127
1190
|
* Fetch the span tree for a root span.
|
|
1128
1191
|
* Blocking GET request.
|
|
1192
|
+
*
|
|
1193
|
+
* Pass `includeOutputs: false` for a payload-free tree (structure +
|
|
1194
|
+
* `externalSpanId` only), so recorded outputs are fetched lazily per mocked
|
|
1195
|
+
* span instead of all up front. Omit it (default eager) for `mock: "all"`.
|
|
1129
1196
|
*/
|
|
1130
|
-
async getSpanTree(externalSpanId) {
|
|
1131
|
-
const
|
|
1197
|
+
async getSpanTree(externalSpanId, options) {
|
|
1198
|
+
const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
|
|
1199
|
+
const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
|
|
1132
1200
|
const controller = new AbortController();
|
|
1133
1201
|
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
1134
1202
|
try {
|
|
@@ -2714,6 +2782,9 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2714
2782
|
}
|
|
2715
2783
|
};
|
|
2716
2784
|
|
|
2785
|
+
// src/client.ts
|
|
2786
|
+
init_mockOverride();
|
|
2787
|
+
|
|
2717
2788
|
// src/openaiAgentSdk.ts
|
|
2718
2789
|
var BitfabOpenAIAgentHandler = class {
|
|
2719
2790
|
constructor(config) {
|
|
@@ -3529,6 +3600,12 @@ var Bitfab = class {
|
|
|
3529
3600
|
constructor(config) {
|
|
3530
3601
|
/** Gate the empty-key warning to fire at most once. */
|
|
3531
3602
|
this.apiKeyWarned = false;
|
|
3603
|
+
/**
|
|
3604
|
+
* Mock overrides registered via {@link Bitfab.registerMockOverride}, applied
|
|
3605
|
+
* to every `replay` on this client (after any per-call `mockOverride`). In
|
|
3606
|
+
* registration order; first matcher wins within this list.
|
|
3607
|
+
*/
|
|
3608
|
+
this.mockOverrides = [];
|
|
3532
3609
|
this.apiKeyConfig = config.apiKey;
|
|
3533
3610
|
this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
|
|
3534
3611
|
this.timeout = config.timeout ?? 12e4;
|
|
@@ -4113,7 +4190,7 @@ var Bitfab = class {
|
|
|
4113
4190
|
dbSnapshotUsage: {
|
|
4114
4191
|
neonBranchId: replayCtx.dbBranchLease.neonBranchId,
|
|
4115
4192
|
snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
|
|
4116
|
-
|
|
4193
|
+
originalTraceId: replayCtx.sourceBitfabTraceId,
|
|
4117
4194
|
accessed: replayCtx.dbSnapshotAccessed === true
|
|
4118
4195
|
}
|
|
4119
4196
|
}
|
|
@@ -4141,24 +4218,77 @@ var Bitfab = class {
|
|
|
4141
4218
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
4142
4219
|
const callIndex = counters.get(counterKey) ?? 0;
|
|
4143
4220
|
counters.set(counterKey, callIndex + 1);
|
|
4144
|
-
const
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
if (
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4221
|
+
const mockKey = `${counterKey}:${callIndex}`;
|
|
4222
|
+
const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
|
|
4223
|
+
const emitMock = (output) => {
|
|
4224
|
+
void sendSpan({ result: output, mocked: true });
|
|
4225
|
+
if (fnReturnsPromise) {
|
|
4226
|
+
return Promise.resolve(output);
|
|
4227
|
+
}
|
|
4228
|
+
return output;
|
|
4229
|
+
};
|
|
4230
|
+
const emitMockAsync = (pending) => {
|
|
4231
|
+
if (!fnReturnsPromise) {
|
|
4232
|
+
throw new BitfabError(
|
|
4233
|
+
`Cannot mock synchronous span "${traceFunctionKey}" with an asynchronously-resolved value (lazy recorded-output fetch or an async value function). Make the wrapped function async, or use mock: "all" so recorded outputs are fetched eagerly.`
|
|
4234
|
+
);
|
|
4235
|
+
}
|
|
4236
|
+
return (async () => {
|
|
4237
|
+
const output = await pending;
|
|
4156
4238
|
void sendSpan({ result: output, mocked: true });
|
|
4157
|
-
if (fnReturnsPromise) {
|
|
4158
|
-
return Promise.resolve(output);
|
|
4159
|
-
}
|
|
4160
4239
|
return output;
|
|
4240
|
+
})();
|
|
4241
|
+
};
|
|
4242
|
+
const resolveRecordedOutput = () => {
|
|
4243
|
+
const hasInlineOutput = mockSpan?.output !== void 0 || mockSpan?.outputMeta !== void 0;
|
|
4244
|
+
if (!hasInlineOutput && replayCtxForMock.fetchSpanOutput && mockSpan?.externalSpanId) {
|
|
4245
|
+
return replayCtxForMock.fetchSpanOutput(mockSpan.externalSpanId);
|
|
4246
|
+
}
|
|
4247
|
+
if (!mockSpan) {
|
|
4248
|
+
return Promise.reject(
|
|
4249
|
+
new BitfabError(
|
|
4250
|
+
`No recorded span to source output for "${traceFunctionKey}".`
|
|
4251
|
+
)
|
|
4252
|
+
);
|
|
4161
4253
|
}
|
|
4254
|
+
let output = mockSpan.output;
|
|
4255
|
+
if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
|
|
4256
|
+
output = deserializeValue({
|
|
4257
|
+
json: mockSpan.output,
|
|
4258
|
+
meta: mockSpan.outputMeta
|
|
4259
|
+
});
|
|
4260
|
+
}
|
|
4261
|
+
return output;
|
|
4262
|
+
};
|
|
4263
|
+
if (replayCtxForMock.mockOverrides?.length) {
|
|
4264
|
+
const nodeMeta = {
|
|
4265
|
+
traceFunctionKey,
|
|
4266
|
+
spanName: baseSpanParams.spanName,
|
|
4267
|
+
type: options.type ?? "custom",
|
|
4268
|
+
originalSpanId: mockSpan?.sourceSpanId
|
|
4269
|
+
};
|
|
4270
|
+
const override = replayCtxForMock.mockOverrides.find(
|
|
4271
|
+
(o) => o.match(nodeMeta)
|
|
4272
|
+
);
|
|
4273
|
+
if (override) {
|
|
4274
|
+
const injected = resolveMockValue(override.value, {
|
|
4275
|
+
node: nodeMeta,
|
|
4276
|
+
inputs: args,
|
|
4277
|
+
getOriginalOutput: () => Promise.resolve(resolveRecordedOutput())
|
|
4278
|
+
});
|
|
4279
|
+
if (injected instanceof Promise) {
|
|
4280
|
+
return emitMockAsync(injected);
|
|
4281
|
+
}
|
|
4282
|
+
return emitMock(injected);
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
|
|
4286
|
+
if (shouldMock && mockSpan) {
|
|
4287
|
+
const recorded = resolveRecordedOutput();
|
|
4288
|
+
if (recorded instanceof Promise) {
|
|
4289
|
+
return emitMockAsync(recorded);
|
|
4290
|
+
}
|
|
4291
|
+
return emitMock(recorded);
|
|
4162
4292
|
}
|
|
4163
4293
|
}
|
|
4164
4294
|
const recordSpan = (result) => {
|
|
@@ -4364,8 +4494,11 @@ var Bitfab = class {
|
|
|
4364
4494
|
...params.dbSnapshotUsage.snapshotTimestamp && {
|
|
4365
4495
|
snapshot_timestamp: params.dbSnapshotUsage.snapshotTimestamp
|
|
4366
4496
|
},
|
|
4367
|
-
...params.dbSnapshotUsage.
|
|
4368
|
-
|
|
4497
|
+
...params.dbSnapshotUsage.originalTraceId && {
|
|
4498
|
+
original_trace_id: params.dbSnapshotUsage.originalTraceId,
|
|
4499
|
+
// Deprecated wire alias, kept so this SDK still reports usage
|
|
4500
|
+
// against servers that predate the rename.
|
|
4501
|
+
source_trace_id: params.dbSnapshotUsage.originalTraceId
|
|
4369
4502
|
},
|
|
4370
4503
|
accessed: params.dbSnapshotUsage.accessed
|
|
4371
4504
|
};
|
|
@@ -4438,26 +4571,14 @@ var Bitfab = class {
|
|
|
4438
4571
|
...params.mocked && { mocked: true }
|
|
4439
4572
|
});
|
|
4440
4573
|
}
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
* invocation records a trace tied to the test run. The plain-callable form
|
|
4450
|
-
* is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
|
|
4451
|
-
* SDK) replay - those record traces under a key with no `withSpan`-wrapped
|
|
4452
|
-
* root in the app.
|
|
4453
|
-
*
|
|
4454
|
-
* @param traceFunctionKey - The trace function key to replay
|
|
4455
|
-
* @param fn - The function to run recorded inputs through
|
|
4456
|
-
* @param options - Optional replay options. When `traceIds` is passed,
|
|
4457
|
-
* `limit` is ignored (with a warning): an explicit ID list already
|
|
4458
|
-
* determines how many traces replay.
|
|
4459
|
-
* @returns ReplayResult with items, testRunId, and testRunUrl
|
|
4460
|
-
*/
|
|
4574
|
+
registerMockOverride(overrideOrMatch, value) {
|
|
4575
|
+
const override = typeof overrideOrMatch === "function" ? { match: overrideOrMatch, value } : overrideOrMatch;
|
|
4576
|
+
this.mockOverrides.push(override);
|
|
4577
|
+
}
|
|
4578
|
+
/** Remove all overrides registered via {@link registerMockOverride}. */
|
|
4579
|
+
clearMockOverrides() {
|
|
4580
|
+
this.mockOverrides.length = 0;
|
|
4581
|
+
}
|
|
4461
4582
|
async replay(traceFunctionKey, fn, options) {
|
|
4462
4583
|
const wrappedKey = fn._bitfabTraceFunctionKey;
|
|
4463
4584
|
let replayFn = fn;
|
|
@@ -4478,7 +4599,8 @@ var Bitfab = class {
|
|
|
4478
4599
|
this.serviceUrl,
|
|
4479
4600
|
traceFunctionKey,
|
|
4480
4601
|
replayFn,
|
|
4481
|
-
options
|
|
4602
|
+
options,
|
|
4603
|
+
this.mockOverrides
|
|
4482
4604
|
);
|
|
4483
4605
|
}
|
|
4484
4606
|
};
|