bitfab 0.28.10 → 0.29.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/{chunk-SK4TNXRC.js → chunk-2M5AWVVQ.js} +230 -72
- package/dist/chunk-2M5AWVVQ.js.map +1 -0
- package/dist/{chunk-5E4BUIYA.js → chunk-V3XORTWI.js} +45 -8
- package/dist/chunk-V3XORTWI.js.map +1 -0
- package/dist/index.cjs +279 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +153 -9
- package/dist/index.d.ts +153 -9
- package/dist/index.js +2 -2
- package/dist/node.cjs +279 -76
- 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-HEGU3YU2.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-5E4BUIYA.js.map +0 -1
- package/dist/chunk-SK4TNXRC.js.map +0 -1
- /package/dist/{replay-IEE4RY57.js.map → replay-HEGU3YU2.js.map} +0 -0
package/dist/node.cjs
CHANGED
|
@@ -298,6 +298,22 @@ var init_randomUuid = __esm({
|
|
|
298
298
|
}
|
|
299
299
|
});
|
|
300
300
|
|
|
301
|
+
// src/mockOverride.ts
|
|
302
|
+
function resolveMockValue(value, ctx) {
|
|
303
|
+
return typeof value === "function" ? value(ctx) : value;
|
|
304
|
+
}
|
|
305
|
+
function normalizeMockOverrides(mockOverride) {
|
|
306
|
+
if (mockOverride === void 0) {
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
return Array.isArray(mockOverride) ? mockOverride : [mockOverride];
|
|
310
|
+
}
|
|
311
|
+
var init_mockOverride = __esm({
|
|
312
|
+
"src/mockOverride.ts"() {
|
|
313
|
+
"use strict";
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
301
317
|
// src/replayContext.ts
|
|
302
318
|
function getReplayContext() {
|
|
303
319
|
return replayContextStorage?.getStore() ?? null;
|
|
@@ -373,6 +389,7 @@ function buildMockTree(rootNode) {
|
|
|
373
389
|
counters.set(counterKey, index + 1);
|
|
374
390
|
spans.set(`${counterKey}:${index}`, {
|
|
375
391
|
sourceSpanId: node.sourceSpanId,
|
|
392
|
+
externalSpanId: node.externalSpanId,
|
|
376
393
|
output: node.output,
|
|
377
394
|
outputMeta: node.outputMeta
|
|
378
395
|
});
|
|
@@ -386,7 +403,7 @@ function buildMockTree(rootNode) {
|
|
|
386
403
|
}
|
|
387
404
|
return { spans };
|
|
388
405
|
}
|
|
389
|
-
async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment, adaptInputs) {
|
|
406
|
+
async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, environment, adaptInputs) {
|
|
390
407
|
const lease = environment ? serverItem.dbBranchLease : void 0;
|
|
391
408
|
let inputs = [];
|
|
392
409
|
let originalOutput;
|
|
@@ -405,28 +422,45 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
405
422
|
sourceSpanId: serverItem.externalSpanId
|
|
406
423
|
});
|
|
407
424
|
}
|
|
425
|
+
const hasOverrides = resolvedOverrides.length > 0;
|
|
426
|
+
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
427
|
+
const includeOutputs = mockStrategy === "all";
|
|
408
428
|
let mockTree;
|
|
409
|
-
if (
|
|
429
|
+
if (needTree) {
|
|
410
430
|
try {
|
|
411
431
|
const treeResponse = await httpClient.getSpanTree(
|
|
412
|
-
serverItem.externalSpanId
|
|
432
|
+
serverItem.externalSpanId,
|
|
433
|
+
{ includeOutputs }
|
|
413
434
|
);
|
|
414
435
|
if (treeResponse.root) {
|
|
415
436
|
mockTree = buildMockTree(treeResponse.root);
|
|
416
|
-
} else if (mockStrategy === "all") {
|
|
437
|
+
} else if (mockStrategy === "all" || hasOverrides) {
|
|
417
438
|
throw new BitfabError(
|
|
418
|
-
`Replay mock strategy "
|
|
439
|
+
`Replay mock strategy "${mockStrategy}"${hasOverrides ? " with overrides" : ""} requires a span tree root for source span ${serverItem.externalSpanId}.`
|
|
419
440
|
);
|
|
420
441
|
} else {
|
|
421
442
|
mockTree = void 0;
|
|
422
443
|
}
|
|
423
444
|
} catch (e) {
|
|
424
|
-
if (mockStrategy === "all") {
|
|
445
|
+
if (mockStrategy === "all" || hasOverrides) {
|
|
425
446
|
throw e;
|
|
426
447
|
}
|
|
427
448
|
mockTree = void 0;
|
|
428
449
|
}
|
|
429
450
|
}
|
|
451
|
+
const outputCache = /* @__PURE__ */ new Map();
|
|
452
|
+
const fetchSpanOutput = mockTree && !includeOutputs ? (externalSpanId) => {
|
|
453
|
+
let pending = outputCache.get(externalSpanId);
|
|
454
|
+
if (!pending) {
|
|
455
|
+
pending = httpClient.getExternalSpan(externalSpanId).then(
|
|
456
|
+
(s) => deserializeOutput(
|
|
457
|
+
s.rawData?.span_data ?? {}
|
|
458
|
+
)
|
|
459
|
+
);
|
|
460
|
+
outputCache.set(externalSpanId, pending);
|
|
461
|
+
}
|
|
462
|
+
return pending;
|
|
463
|
+
} : void 0;
|
|
430
464
|
const maybePromise = runWithReplayContext(
|
|
431
465
|
{
|
|
432
466
|
testRunId,
|
|
@@ -437,6 +471,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
437
471
|
mockTree,
|
|
438
472
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
439
473
|
mockStrategy,
|
|
474
|
+
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
475
|
+
fetchSpanOutput,
|
|
440
476
|
dbBranchLease: lease,
|
|
441
477
|
pendingPersistence
|
|
442
478
|
},
|
|
@@ -493,7 +529,7 @@ async function mapWithConcurrency(tasks, maxConcurrency, onSettled) {
|
|
|
493
529
|
await Promise.all(workers);
|
|
494
530
|
return results;
|
|
495
531
|
}
|
|
496
|
-
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
532
|
+
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
|
|
497
533
|
if (options?.traceIds !== void 0) {
|
|
498
534
|
if (options.traceIds.length === 0) {
|
|
499
535
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -533,6 +569,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
533
569
|
);
|
|
534
570
|
const mockStrategy = options?.mock ?? "marked";
|
|
535
571
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
572
|
+
const resolvedOverrides = [
|
|
573
|
+
...normalizeMockOverrides(options?.mockOverride),
|
|
574
|
+
...registeredOverrides
|
|
575
|
+
];
|
|
536
576
|
const tasks = serverItems.map(
|
|
537
577
|
(serverItem) => () => processItem(
|
|
538
578
|
httpClient,
|
|
@@ -540,6 +580,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
540
580
|
fn,
|
|
541
581
|
testRunId,
|
|
542
582
|
mockStrategy,
|
|
583
|
+
resolvedOverrides,
|
|
543
584
|
options?.environment,
|
|
544
585
|
options?.adaptInputs
|
|
545
586
|
)
|
|
@@ -666,6 +707,7 @@ var init_replay = __esm({
|
|
|
666
707
|
"src/replay.ts"() {
|
|
667
708
|
"use strict";
|
|
668
709
|
init_errors();
|
|
710
|
+
init_mockOverride();
|
|
669
711
|
init_randomUuid();
|
|
670
712
|
init_replayContext();
|
|
671
713
|
init_serialize();
|
|
@@ -706,7 +748,7 @@ registerAsyncLocalStorageClass(
|
|
|
706
748
|
);
|
|
707
749
|
|
|
708
750
|
// src/version.generated.ts
|
|
709
|
-
var __version__ = "0.
|
|
751
|
+
var __version__ = "0.29.0";
|
|
710
752
|
|
|
711
753
|
// src/constants.ts
|
|
712
754
|
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
@@ -946,6 +988,50 @@ var HttpClient = class {
|
|
|
946
988
|
async lookupFunction(name) {
|
|
947
989
|
return this.request("/api/sdk/functions/lookup", { name });
|
|
948
990
|
}
|
|
991
|
+
async getTraceSpan(traceId, lookup) {
|
|
992
|
+
const searchParams = new URLSearchParams();
|
|
993
|
+
if (lookup.id !== void 0) {
|
|
994
|
+
searchParams.set("id", lookup.id);
|
|
995
|
+
} else {
|
|
996
|
+
searchParams.set("name", lookup.name);
|
|
997
|
+
searchParams.set("occurrence", String(lookup.occurrence ?? "last"));
|
|
998
|
+
}
|
|
999
|
+
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}/span?${searchParams.toString()}`;
|
|
1000
|
+
const response = await this.get(endpoint);
|
|
1001
|
+
return response.span;
|
|
1002
|
+
}
|
|
1003
|
+
async get(endpoint) {
|
|
1004
|
+
const url = `${this.serviceUrl}${endpoint}`;
|
|
1005
|
+
const controller = new AbortController();
|
|
1006
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1007
|
+
try {
|
|
1008
|
+
const response = await fetch(url, {
|
|
1009
|
+
method: "GET",
|
|
1010
|
+
headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
|
|
1011
|
+
signal: controller.signal
|
|
1012
|
+
});
|
|
1013
|
+
if (!response.ok) {
|
|
1014
|
+
const errorText = await response.text();
|
|
1015
|
+
throw new BitfabError(
|
|
1016
|
+
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
return await response.json();
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
if (error instanceof BitfabError) {
|
|
1022
|
+
throw error;
|
|
1023
|
+
}
|
|
1024
|
+
if (error instanceof Error) {
|
|
1025
|
+
if (error.name === "AbortError") {
|
|
1026
|
+
throw new BitfabError(`Request timed out after ${this.timeout}ms`);
|
|
1027
|
+
}
|
|
1028
|
+
throw new BitfabError(error.message);
|
|
1029
|
+
}
|
|
1030
|
+
throw new BitfabError("Unknown error occurred");
|
|
1031
|
+
} finally {
|
|
1032
|
+
clearTimeout(timeoutId);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
949
1035
|
/**
|
|
950
1036
|
* Send an internal trace (from BAML execution).
|
|
951
1037
|
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
@@ -1002,12 +1088,12 @@ var HttpClient = class {
|
|
|
1002
1088
|
});
|
|
1003
1089
|
}
|
|
1004
1090
|
/**
|
|
1005
|
-
* Partial update of an existing
|
|
1091
|
+
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
1006
1092
|
* Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
|
|
1007
1093
|
* returns a tracked promise that callers may optionally await.
|
|
1008
1094
|
*/
|
|
1009
|
-
patchTrace(
|
|
1010
|
-
const endpoint = `/api/sdk/
|
|
1095
|
+
patchTrace(traceId, payload) {
|
|
1096
|
+
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}`;
|
|
1011
1097
|
return awaitOnExit(
|
|
1012
1098
|
this.request(endpoint, payload, { method: "PATCH" })
|
|
1013
1099
|
).catch((error) => {
|
|
@@ -1091,9 +1177,14 @@ var HttpClient = class {
|
|
|
1091
1177
|
/**
|
|
1092
1178
|
* Fetch the span tree for a root span.
|
|
1093
1179
|
* Blocking GET request.
|
|
1180
|
+
*
|
|
1181
|
+
* Pass `includeOutputs: false` for a payload-free tree (structure +
|
|
1182
|
+
* `externalSpanId` only), so recorded outputs are fetched lazily per mocked
|
|
1183
|
+
* span instead of all up front. Omit it (default eager) for `mock: "all"`.
|
|
1094
1184
|
*/
|
|
1095
|
-
async getSpanTree(externalSpanId) {
|
|
1096
|
-
const
|
|
1185
|
+
async getSpanTree(externalSpanId, options) {
|
|
1186
|
+
const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
|
|
1187
|
+
const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
|
|
1097
1188
|
const controller = new AbortController();
|
|
1098
1189
|
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
1099
1190
|
try {
|
|
@@ -1345,6 +1436,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1345
1436
|
const traceId = this.ensureTrace();
|
|
1346
1437
|
const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
|
|
1347
1438
|
const spanInfo = {
|
|
1439
|
+
id: randomUuid(),
|
|
1348
1440
|
spanId,
|
|
1349
1441
|
traceId,
|
|
1350
1442
|
parentId: parentId ?? null,
|
|
@@ -1408,6 +1500,8 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1408
1500
|
rawSpan.parent_id = spanInfo.parentId;
|
|
1409
1501
|
}
|
|
1410
1502
|
const payload = {
|
|
1503
|
+
id: spanInfo.id,
|
|
1504
|
+
traceId: spanInfo.traceId,
|
|
1411
1505
|
type: "sdk-function",
|
|
1412
1506
|
source: "typescript-sdk-claude-agent-sdk",
|
|
1413
1507
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -1437,6 +1531,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1437
1531
|
externalTrace.metadata = metadata;
|
|
1438
1532
|
}
|
|
1439
1533
|
const traceData = {
|
|
1534
|
+
id: traceId,
|
|
1440
1535
|
type: "sdk-function",
|
|
1441
1536
|
source: "typescript-sdk-claude-agent-sdk",
|
|
1442
1537
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -1705,6 +1800,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1705
1800
|
}
|
|
1706
1801
|
Object.assign(llmContext, this.currentLlmUsage);
|
|
1707
1802
|
const spanInfo = {
|
|
1803
|
+
id: randomUuid(),
|
|
1708
1804
|
spanId,
|
|
1709
1805
|
traceId,
|
|
1710
1806
|
parentId,
|
|
@@ -2351,6 +2447,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2351
2447
|
const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
|
|
2352
2448
|
const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
|
|
2353
2449
|
const spanInfo = {
|
|
2450
|
+
id: randomUuid(),
|
|
2354
2451
|
spanId: runId,
|
|
2355
2452
|
traceId: invocation.traceId,
|
|
2356
2453
|
rootRunId: invocation.rootRunId,
|
|
@@ -2429,6 +2526,8 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2429
2526
|
rawSpan.parent_id = spanInfo.parentId;
|
|
2430
2527
|
}
|
|
2431
2528
|
const payload = {
|
|
2529
|
+
id: spanInfo.id,
|
|
2530
|
+
traceId: spanInfo.traceId,
|
|
2432
2531
|
type: "sdk-function",
|
|
2433
2532
|
source: "typescript-sdk-langgraph",
|
|
2434
2533
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2444,6 +2543,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2444
2543
|
sendTraceCompletion(rootSpan, activeContext) {
|
|
2445
2544
|
const completed = activeContext === null;
|
|
2446
2545
|
const traceData = {
|
|
2546
|
+
id: rootSpan.traceId,
|
|
2447
2547
|
type: "sdk-function",
|
|
2448
2548
|
source: "typescript-sdk-langgraph",
|
|
2449
2549
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2463,6 +2563,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2463
2563
|
}
|
|
2464
2564
|
sendTraceStart(rootSpan) {
|
|
2465
2565
|
const traceData = {
|
|
2566
|
+
id: rootSpan.traceId,
|
|
2466
2567
|
type: "sdk-function",
|
|
2467
2568
|
source: "typescript-sdk-langgraph",
|
|
2468
2569
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2669,6 +2770,9 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2669
2770
|
}
|
|
2670
2771
|
};
|
|
2671
2772
|
|
|
2773
|
+
// src/client.ts
|
|
2774
|
+
init_mockOverride();
|
|
2775
|
+
|
|
2672
2776
|
// src/openaiAgentSdk.ts
|
|
2673
2777
|
var BitfabOpenAIAgentHandler = class {
|
|
2674
2778
|
constructor(config) {
|
|
@@ -2821,6 +2925,7 @@ var ReplayEnvironment = class {
|
|
|
2821
2925
|
init_serialize();
|
|
2822
2926
|
|
|
2823
2927
|
// src/tracing.ts
|
|
2928
|
+
init_randomUuid();
|
|
2824
2929
|
var BitfabOpenAITracingProcessor = class {
|
|
2825
2930
|
/**
|
|
2826
2931
|
* Initialize the tracing processor.
|
|
@@ -2830,6 +2935,7 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2830
2935
|
constructor(config) {
|
|
2831
2936
|
this.activeTraces = {};
|
|
2832
2937
|
this.activeSpanMappings = {};
|
|
2938
|
+
this.canonicalTraceIds = {};
|
|
2833
2939
|
this.httpClient = new HttpClient({
|
|
2834
2940
|
apiKey: config.apiKey,
|
|
2835
2941
|
serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
|
|
@@ -2837,6 +2943,15 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2837
2943
|
});
|
|
2838
2944
|
this.getActiveSpanContext = config.getActiveSpanContext ?? null;
|
|
2839
2945
|
}
|
|
2946
|
+
getCanonicalTraceId(sourceTraceId) {
|
|
2947
|
+
const existing = this.canonicalTraceIds[sourceTraceId];
|
|
2948
|
+
if (existing) {
|
|
2949
|
+
return existing;
|
|
2950
|
+
}
|
|
2951
|
+
const created = randomUuid();
|
|
2952
|
+
this.canonicalTraceIds[sourceTraceId] = created;
|
|
2953
|
+
return created;
|
|
2954
|
+
}
|
|
2840
2955
|
/**
|
|
2841
2956
|
* Called when a trace is started.
|
|
2842
2957
|
* If there's an active withSpan context, the trace ID is remapped to the
|
|
@@ -2848,7 +2963,12 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2848
2963
|
if (activeContext) {
|
|
2849
2964
|
this.activeSpanMappings[trace.traceId] = activeContext;
|
|
2850
2965
|
}
|
|
2851
|
-
|
|
2966
|
+
const canonicalTraceId = activeContext?.traceId ?? this.getCanonicalTraceId(trace.traceId);
|
|
2967
|
+
this.canonicalTraceIds[trace.traceId] = canonicalTraceId;
|
|
2968
|
+
this.sendTrace(trace, {
|
|
2969
|
+
id: canonicalTraceId,
|
|
2970
|
+
sourceTraceId: activeContext?.traceId
|
|
2971
|
+
});
|
|
2852
2972
|
}
|
|
2853
2973
|
/**
|
|
2854
2974
|
* Called when a trace is ended.
|
|
@@ -2857,11 +2977,13 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2857
2977
|
*/
|
|
2858
2978
|
async onTraceEnd(trace) {
|
|
2859
2979
|
const mapping = this.activeSpanMappings[trace.traceId];
|
|
2860
|
-
this.sendTrace(
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2980
|
+
this.sendTrace(trace, {
|
|
2981
|
+
completed: mapping === void 0,
|
|
2982
|
+
id: mapping?.traceId ?? this.getCanonicalTraceId(trace.traceId),
|
|
2983
|
+
sourceTraceId: mapping?.traceId
|
|
2984
|
+
});
|
|
2864
2985
|
delete this.activeSpanMappings[trace.traceId];
|
|
2986
|
+
delete this.canonicalTraceIds[trace.traceId];
|
|
2865
2987
|
delete this.activeTraces[trace.traceId];
|
|
2866
2988
|
}
|
|
2867
2989
|
/**
|
|
@@ -2891,22 +3013,25 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2891
3013
|
async shutdown(_timeout) {
|
|
2892
3014
|
this.activeTraces = {};
|
|
2893
3015
|
this.activeSpanMappings = {};
|
|
3016
|
+
this.canonicalTraceIds = {};
|
|
2894
3017
|
}
|
|
2895
3018
|
/**
|
|
2896
3019
|
* Send trace to Bitfab API (fire-and-forget).
|
|
2897
3020
|
* When traceIdOverride is provided, the trace ID is remapped to link
|
|
2898
3021
|
* the OpenAI trace into an outer withSpan trace.
|
|
2899
3022
|
*/
|
|
2900
|
-
sendTrace(trace,
|
|
3023
|
+
sendTrace(trace, options = {}) {
|
|
2901
3024
|
try {
|
|
2902
|
-
const { completed, ...traceOverrides } = overrides;
|
|
2903
3025
|
const traceData = trace.toJSON();
|
|
2904
|
-
|
|
3026
|
+
if (options.sourceTraceId) {
|
|
3027
|
+
traceData.id = options.sourceTraceId;
|
|
3028
|
+
}
|
|
2905
3029
|
this.httpClient.sendExternalTrace({
|
|
3030
|
+
...options.id && { id: options.id },
|
|
2906
3031
|
type: "openai",
|
|
2907
3032
|
source: "typescript-sdk-openai-tracing",
|
|
2908
3033
|
externalTrace: traceData,
|
|
2909
|
-
completed: completed ?? false
|
|
3034
|
+
completed: options.completed ?? false
|
|
2910
3035
|
});
|
|
2911
3036
|
} catch {
|
|
2912
3037
|
}
|
|
@@ -2992,6 +3117,7 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2992
3117
|
*/
|
|
2993
3118
|
buildSpanPayload(serializedSpan, errors) {
|
|
2994
3119
|
const payload = {
|
|
3120
|
+
id: randomUuid(),
|
|
2995
3121
|
type: "openai",
|
|
2996
3122
|
source: "typescript-sdk-openai-tracing",
|
|
2997
3123
|
sourceTraceId: serializedSpan.trace_id ?? "unknown",
|
|
@@ -3014,6 +3140,10 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
3014
3140
|
this.extractSpanInputResponse(span, serializedSpan, errors);
|
|
3015
3141
|
this.applySpanOverrides(serializedSpan, span.traceId ?? "");
|
|
3016
3142
|
const payload = this.buildSpanPayload(serializedSpan, errors);
|
|
3143
|
+
const canonicalTraceId = span.traceId ? this.getCanonicalTraceId(span.traceId) : void 0;
|
|
3144
|
+
if (canonicalTraceId) {
|
|
3145
|
+
payload.traceId = canonicalTraceId;
|
|
3146
|
+
}
|
|
3017
3147
|
this.httpClient.sendExternalSpan(payload);
|
|
3018
3148
|
}
|
|
3019
3149
|
};
|
|
@@ -3330,24 +3460,19 @@ function extractContextFromCollector(collector) {
|
|
|
3330
3460
|
return null;
|
|
3331
3461
|
}
|
|
3332
3462
|
}
|
|
3333
|
-
var
|
|
3334
|
-
var TRACE_ID_MAX_LENGTH = 256;
|
|
3463
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
3335
3464
|
function validateTraceId(traceId) {
|
|
3336
|
-
if (typeof traceId !== "string" || traceId
|
|
3337
|
-
throw new BitfabError("traceId
|
|
3338
|
-
}
|
|
3339
|
-
if (traceId.length > TRACE_ID_MAX_LENGTH) {
|
|
3340
|
-
throw new BitfabError(
|
|
3341
|
-
`traceId must be ${TRACE_ID_MAX_LENGTH} characters or fewer`
|
|
3342
|
-
);
|
|
3465
|
+
if (typeof traceId !== "string" || !UUID_PATTERN.test(traceId)) {
|
|
3466
|
+
throw new BitfabError("traceId must be a valid Bitfab trace ID");
|
|
3343
3467
|
}
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
);
|
|
3468
|
+
}
|
|
3469
|
+
function validateSpanId(id) {
|
|
3470
|
+
if (typeof id !== "string" || !UUID_PATTERN.test(id)) {
|
|
3471
|
+
throw new BitfabError("id must be a valid Bitfab span ID");
|
|
3348
3472
|
}
|
|
3349
3473
|
}
|
|
3350
3474
|
var noOpSpan = {
|
|
3475
|
+
id: "",
|
|
3351
3476
|
traceId: "",
|
|
3352
3477
|
addContext() {
|
|
3353
3478
|
},
|
|
@@ -3371,6 +3496,7 @@ function getCurrentSpan() {
|
|
|
3371
3496
|
return noOpSpan;
|
|
3372
3497
|
}
|
|
3373
3498
|
return {
|
|
3499
|
+
id: current.spanId,
|
|
3374
3500
|
traceId: current.traceId,
|
|
3375
3501
|
addContext(context) {
|
|
3376
3502
|
try {
|
|
@@ -3462,6 +3588,12 @@ var Bitfab = class {
|
|
|
3462
3588
|
constructor(config) {
|
|
3463
3589
|
/** Gate the empty-key warning to fire at most once. */
|
|
3464
3590
|
this.apiKeyWarned = false;
|
|
3591
|
+
/**
|
|
3592
|
+
* Mock overrides registered via {@link Bitfab.registerMockOverride}, applied
|
|
3593
|
+
* to every `replay` on this client (after any per-call `mockOverride`). In
|
|
3594
|
+
* registration order; first matcher wins within this list.
|
|
3595
|
+
*/
|
|
3596
|
+
this.mockOverrides = [];
|
|
3465
3597
|
this.apiKeyConfig = config.apiKey;
|
|
3466
3598
|
this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
|
|
3467
3599
|
this.timeout = config.timeout ?? 12e4;
|
|
@@ -4074,24 +4206,77 @@ var Bitfab = class {
|
|
|
4074
4206
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
4075
4207
|
const callIndex = counters.get(counterKey) ?? 0;
|
|
4076
4208
|
counters.set(counterKey, callIndex + 1);
|
|
4077
|
-
const
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
if (
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4209
|
+
const mockKey = `${counterKey}:${callIndex}`;
|
|
4210
|
+
const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
|
|
4211
|
+
const emitMock = (output) => {
|
|
4212
|
+
void sendSpan({ result: output, mocked: true });
|
|
4213
|
+
if (fnReturnsPromise) {
|
|
4214
|
+
return Promise.resolve(output);
|
|
4215
|
+
}
|
|
4216
|
+
return output;
|
|
4217
|
+
};
|
|
4218
|
+
const emitMockAsync = (pending) => {
|
|
4219
|
+
if (!fnReturnsPromise) {
|
|
4220
|
+
throw new BitfabError(
|
|
4221
|
+
`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.`
|
|
4222
|
+
);
|
|
4223
|
+
}
|
|
4224
|
+
return (async () => {
|
|
4225
|
+
const output = await pending;
|
|
4089
4226
|
void sendSpan({ result: output, mocked: true });
|
|
4090
|
-
if (fnReturnsPromise) {
|
|
4091
|
-
return Promise.resolve(output);
|
|
4092
|
-
}
|
|
4093
4227
|
return output;
|
|
4228
|
+
})();
|
|
4229
|
+
};
|
|
4230
|
+
const resolveRecordedOutput = () => {
|
|
4231
|
+
const hasInlineOutput = mockSpan?.output !== void 0 || mockSpan?.outputMeta !== void 0;
|
|
4232
|
+
if (!hasInlineOutput && replayCtxForMock.fetchSpanOutput && mockSpan?.externalSpanId) {
|
|
4233
|
+
return replayCtxForMock.fetchSpanOutput(mockSpan.externalSpanId);
|
|
4234
|
+
}
|
|
4235
|
+
if (!mockSpan) {
|
|
4236
|
+
return Promise.reject(
|
|
4237
|
+
new BitfabError(
|
|
4238
|
+
`No recorded span to source output for "${traceFunctionKey}".`
|
|
4239
|
+
)
|
|
4240
|
+
);
|
|
4241
|
+
}
|
|
4242
|
+
let output = mockSpan.output;
|
|
4243
|
+
if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
|
|
4244
|
+
output = deserializeValue({
|
|
4245
|
+
json: mockSpan.output,
|
|
4246
|
+
meta: mockSpan.outputMeta
|
|
4247
|
+
});
|
|
4248
|
+
}
|
|
4249
|
+
return output;
|
|
4250
|
+
};
|
|
4251
|
+
if (replayCtxForMock.mockOverrides?.length) {
|
|
4252
|
+
const nodeMeta = {
|
|
4253
|
+
traceFunctionKey,
|
|
4254
|
+
spanName: baseSpanParams.spanName,
|
|
4255
|
+
type: options.type ?? "custom",
|
|
4256
|
+
originalSpanId: mockSpan?.sourceSpanId
|
|
4257
|
+
};
|
|
4258
|
+
const override = replayCtxForMock.mockOverrides.find(
|
|
4259
|
+
(o) => o.match(nodeMeta)
|
|
4260
|
+
);
|
|
4261
|
+
if (override) {
|
|
4262
|
+
const injected = resolveMockValue(override.value, {
|
|
4263
|
+
node: nodeMeta,
|
|
4264
|
+
inputs: args,
|
|
4265
|
+
getOriginalOutput: () => Promise.resolve(resolveRecordedOutput())
|
|
4266
|
+
});
|
|
4267
|
+
if (injected instanceof Promise) {
|
|
4268
|
+
return emitMockAsync(injected);
|
|
4269
|
+
}
|
|
4270
|
+
return emitMock(injected);
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
|
|
4274
|
+
if (shouldMock && mockSpan) {
|
|
4275
|
+
const recorded = resolveRecordedOutput();
|
|
4276
|
+
if (recorded instanceof Promise) {
|
|
4277
|
+
return emitMockAsync(recorded);
|
|
4094
4278
|
}
|
|
4279
|
+
return emitMock(recorded);
|
|
4095
4280
|
}
|
|
4096
4281
|
}
|
|
4097
4282
|
const recordSpan = (result) => {
|
|
@@ -4167,20 +4352,19 @@ var Bitfab = class {
|
|
|
4167
4352
|
}
|
|
4168
4353
|
/**
|
|
4169
4354
|
* Get a detached handle to a previously-created trace, looked up by the
|
|
4170
|
-
*
|
|
4355
|
+
* canonical Bitfab trace ID.
|
|
4171
4356
|
*
|
|
4172
4357
|
* The returned handle is not tied to AsyncLocalStorage - each method sends
|
|
4173
4358
|
* to the server immediately. Useful for adding context to a trace from a
|
|
4174
4359
|
* different process or thread than the one that created it.
|
|
4175
4360
|
*
|
|
4176
|
-
* Throws synchronously if `traceId` is
|
|
4177
|
-
*
|
|
4178
|
-
* no trace exists with that id in the org; the failure surfaces as a
|
|
4361
|
+
* Throws synchronously if `traceId` is not a valid Bitfab trace ID. The
|
|
4362
|
+
* server returns 404 if no trace exists with that ID in the org; the failure surfaces as a
|
|
4179
4363
|
* logged warning (fire-and-forget) or via the awaited promise.
|
|
4180
4364
|
*
|
|
4181
4365
|
* Example:
|
|
4182
4366
|
* ```typescript
|
|
4183
|
-
* const trace = client.getTrace(
|
|
4367
|
+
* const trace = client.getTrace(traceId);
|
|
4184
4368
|
* await trace.addContext({ refund_status: "approved" });
|
|
4185
4369
|
* await trace.setMetadata({ region: "us-west" });
|
|
4186
4370
|
* ```
|
|
@@ -4220,6 +4404,33 @@ var Bitfab = class {
|
|
|
4220
4404
|
}
|
|
4221
4405
|
};
|
|
4222
4406
|
}
|
|
4407
|
+
/**
|
|
4408
|
+
* Fetch one persisted span from a trace without loading the full trace.
|
|
4409
|
+
* Name lookups return the last matching span by default. Pass `occurrence`
|
|
4410
|
+
* as `"first"` or a zero-based index to select a different match.
|
|
4411
|
+
*/
|
|
4412
|
+
async getTraceSpan(traceId, lookup) {
|
|
4413
|
+
validateTraceId(traceId);
|
|
4414
|
+
const hasId = lookup.id !== void 0;
|
|
4415
|
+
const hasName = lookup.name !== void 0;
|
|
4416
|
+
if (hasId === hasName) {
|
|
4417
|
+
throw new BitfabError("Provide exactly one of id or name");
|
|
4418
|
+
}
|
|
4419
|
+
if (hasId) {
|
|
4420
|
+
validateSpanId(lookup.id);
|
|
4421
|
+
} else {
|
|
4422
|
+
if (lookup.name.length === 0) {
|
|
4423
|
+
throw new BitfabError("name must be a non-empty string");
|
|
4424
|
+
}
|
|
4425
|
+
const occurrence = lookup.occurrence ?? "last";
|
|
4426
|
+
if (occurrence !== "first" && occurrence !== "last" && (!Number.isInteger(occurrence) || occurrence < 0)) {
|
|
4427
|
+
throw new BitfabError(
|
|
4428
|
+
'occurrence must be "first", "last", or a non-negative integer'
|
|
4429
|
+
);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
return this.httpClient.getTraceSpan(traceId, lookup);
|
|
4433
|
+
}
|
|
4223
4434
|
/**
|
|
4224
4435
|
* Get a function wrapper for a specific trace function key.
|
|
4225
4436
|
*
|
|
@@ -4278,6 +4489,7 @@ var Bitfab = class {
|
|
|
4278
4489
|
};
|
|
4279
4490
|
}
|
|
4280
4491
|
return this.httpClient.sendExternalTrace({
|
|
4492
|
+
id: params.traceId,
|
|
4281
4493
|
type: "sdk-function",
|
|
4282
4494
|
source: "typescript-sdk-function",
|
|
4283
4495
|
traceFunctionKey: params.traceFunctionKey,
|
|
@@ -4333,6 +4545,8 @@ var Bitfab = class {
|
|
|
4333
4545
|
externalSpan.input_source_span_id = params.inputSourceSpanId;
|
|
4334
4546
|
}
|
|
4335
4547
|
return this.httpClient.sendExternalSpan({
|
|
4548
|
+
id: params.spanId,
|
|
4549
|
+
traceId: params.traceId,
|
|
4336
4550
|
type: "sdk-function",
|
|
4337
4551
|
source: "typescript-sdk-function",
|
|
4338
4552
|
sourceTraceId: params.traceId,
|
|
@@ -4342,26 +4556,14 @@ var Bitfab = class {
|
|
|
4342
4556
|
...params.mocked && { mocked: true }
|
|
4343
4557
|
});
|
|
4344
4558
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
* invocation records a trace tied to the test run. The plain-callable form
|
|
4354
|
-
* is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
|
|
4355
|
-
* SDK) replay - those record traces under a key with no `withSpan`-wrapped
|
|
4356
|
-
* root in the app.
|
|
4357
|
-
*
|
|
4358
|
-
* @param traceFunctionKey - The trace function key to replay
|
|
4359
|
-
* @param fn - The function to run recorded inputs through
|
|
4360
|
-
* @param options - Optional replay options. When `traceIds` is passed,
|
|
4361
|
-
* `limit` is ignored (with a warning): an explicit ID list already
|
|
4362
|
-
* determines how many traces replay.
|
|
4363
|
-
* @returns ReplayResult with items, testRunId, and testRunUrl
|
|
4364
|
-
*/
|
|
4559
|
+
registerMockOverride(overrideOrMatch, value) {
|
|
4560
|
+
const override = typeof overrideOrMatch === "function" ? { match: overrideOrMatch, value } : overrideOrMatch;
|
|
4561
|
+
this.mockOverrides.push(override);
|
|
4562
|
+
}
|
|
4563
|
+
/** Remove all overrides registered via {@link registerMockOverride}. */
|
|
4564
|
+
clearMockOverrides() {
|
|
4565
|
+
this.mockOverrides.length = 0;
|
|
4566
|
+
}
|
|
4365
4567
|
async replay(traceFunctionKey, fn, options) {
|
|
4366
4568
|
const wrappedKey = fn._bitfabTraceFunctionKey;
|
|
4367
4569
|
let replayFn = fn;
|
|
@@ -4382,7 +4584,8 @@ var Bitfab = class {
|
|
|
4382
4584
|
this.serviceUrl,
|
|
4383
4585
|
traceFunctionKey,
|
|
4384
4586
|
replayFn,
|
|
4385
|
-
options
|
|
4587
|
+
options,
|
|
4588
|
+
this.mockOverrides
|
|
4386
4589
|
);
|
|
4387
4590
|
}
|
|
4388
4591
|
};
|