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/index.cjs
CHANGED
|
@@ -291,6 +291,22 @@ var init_asyncStorage = __esm({
|
|
|
291
291
|
}
|
|
292
292
|
});
|
|
293
293
|
|
|
294
|
+
// src/mockOverride.ts
|
|
295
|
+
function resolveMockValue(value, ctx) {
|
|
296
|
+
return typeof value === "function" ? value(ctx) : value;
|
|
297
|
+
}
|
|
298
|
+
function normalizeMockOverrides(mockOverride) {
|
|
299
|
+
if (mockOverride === void 0) {
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
return Array.isArray(mockOverride) ? mockOverride : [mockOverride];
|
|
303
|
+
}
|
|
304
|
+
var init_mockOverride = __esm({
|
|
305
|
+
"src/mockOverride.ts"() {
|
|
306
|
+
"use strict";
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
294
310
|
// src/replayContext.ts
|
|
295
311
|
function getReplayContext() {
|
|
296
312
|
return replayContextStorage?.getStore() ?? null;
|
|
@@ -366,6 +382,7 @@ function buildMockTree(rootNode) {
|
|
|
366
382
|
counters.set(counterKey, index + 1);
|
|
367
383
|
spans.set(`${counterKey}:${index}`, {
|
|
368
384
|
sourceSpanId: node.sourceSpanId,
|
|
385
|
+
externalSpanId: node.externalSpanId,
|
|
369
386
|
output: node.output,
|
|
370
387
|
outputMeta: node.outputMeta
|
|
371
388
|
});
|
|
@@ -379,7 +396,7 @@ function buildMockTree(rootNode) {
|
|
|
379
396
|
}
|
|
380
397
|
return { spans };
|
|
381
398
|
}
|
|
382
|
-
async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment, adaptInputs) {
|
|
399
|
+
async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, environment, adaptInputs) {
|
|
383
400
|
const lease = environment ? serverItem.dbBranchLease : void 0;
|
|
384
401
|
let inputs = [];
|
|
385
402
|
let originalOutput;
|
|
@@ -398,28 +415,45 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
398
415
|
sourceSpanId: serverItem.externalSpanId
|
|
399
416
|
});
|
|
400
417
|
}
|
|
418
|
+
const hasOverrides = resolvedOverrides.length > 0;
|
|
419
|
+
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
420
|
+
const includeOutputs = mockStrategy === "all";
|
|
401
421
|
let mockTree;
|
|
402
|
-
if (
|
|
422
|
+
if (needTree) {
|
|
403
423
|
try {
|
|
404
424
|
const treeResponse = await httpClient.getSpanTree(
|
|
405
|
-
serverItem.externalSpanId
|
|
425
|
+
serverItem.externalSpanId,
|
|
426
|
+
{ includeOutputs }
|
|
406
427
|
);
|
|
407
428
|
if (treeResponse.root) {
|
|
408
429
|
mockTree = buildMockTree(treeResponse.root);
|
|
409
|
-
} else if (mockStrategy === "all") {
|
|
430
|
+
} else if (mockStrategy === "all" || hasOverrides) {
|
|
410
431
|
throw new BitfabError(
|
|
411
|
-
`Replay mock strategy "
|
|
432
|
+
`Replay mock strategy "${mockStrategy}"${hasOverrides ? " with overrides" : ""} requires a span tree root for source span ${serverItem.externalSpanId}.`
|
|
412
433
|
);
|
|
413
434
|
} else {
|
|
414
435
|
mockTree = void 0;
|
|
415
436
|
}
|
|
416
437
|
} catch (e) {
|
|
417
|
-
if (mockStrategy === "all") {
|
|
438
|
+
if (mockStrategy === "all" || hasOverrides) {
|
|
418
439
|
throw e;
|
|
419
440
|
}
|
|
420
441
|
mockTree = void 0;
|
|
421
442
|
}
|
|
422
443
|
}
|
|
444
|
+
const outputCache = /* @__PURE__ */ new Map();
|
|
445
|
+
const fetchSpanOutput = mockTree && !includeOutputs ? (externalSpanId) => {
|
|
446
|
+
let pending = outputCache.get(externalSpanId);
|
|
447
|
+
if (!pending) {
|
|
448
|
+
pending = httpClient.getExternalSpan(externalSpanId).then(
|
|
449
|
+
(s) => deserializeOutput(
|
|
450
|
+
s.rawData?.span_data ?? {}
|
|
451
|
+
)
|
|
452
|
+
);
|
|
453
|
+
outputCache.set(externalSpanId, pending);
|
|
454
|
+
}
|
|
455
|
+
return pending;
|
|
456
|
+
} : void 0;
|
|
423
457
|
const maybePromise = runWithReplayContext(
|
|
424
458
|
{
|
|
425
459
|
testRunId,
|
|
@@ -430,6 +464,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
430
464
|
mockTree,
|
|
431
465
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
432
466
|
mockStrategy,
|
|
467
|
+
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
468
|
+
fetchSpanOutput,
|
|
433
469
|
dbBranchLease: lease,
|
|
434
470
|
pendingPersistence
|
|
435
471
|
},
|
|
@@ -486,7 +522,7 @@ async function mapWithConcurrency(tasks, maxConcurrency, onSettled) {
|
|
|
486
522
|
await Promise.all(workers);
|
|
487
523
|
return results;
|
|
488
524
|
}
|
|
489
|
-
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
525
|
+
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
|
|
490
526
|
if (options?.traceIds !== void 0) {
|
|
491
527
|
if (options.traceIds.length === 0) {
|
|
492
528
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -526,6 +562,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
526
562
|
);
|
|
527
563
|
const mockStrategy = options?.mock ?? "marked";
|
|
528
564
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
565
|
+
const resolvedOverrides = [
|
|
566
|
+
...normalizeMockOverrides(options?.mockOverride),
|
|
567
|
+
...registeredOverrides
|
|
568
|
+
];
|
|
529
569
|
const tasks = serverItems.map(
|
|
530
570
|
(serverItem) => () => processItem(
|
|
531
571
|
httpClient,
|
|
@@ -533,6 +573,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
533
573
|
fn,
|
|
534
574
|
testRunId,
|
|
535
575
|
mockStrategy,
|
|
576
|
+
resolvedOverrides,
|
|
536
577
|
options?.environment,
|
|
537
578
|
options?.adaptInputs
|
|
538
579
|
)
|
|
@@ -659,6 +700,7 @@ var init_replay = __esm({
|
|
|
659
700
|
"src/replay.ts"() {
|
|
660
701
|
"use strict";
|
|
661
702
|
init_errors();
|
|
703
|
+
init_mockOverride();
|
|
662
704
|
init_randomUuid();
|
|
663
705
|
init_replayContext();
|
|
664
706
|
init_serialize();
|
|
@@ -692,7 +734,7 @@ __export(index_exports, {
|
|
|
692
734
|
module.exports = __toCommonJS(index_exports);
|
|
693
735
|
|
|
694
736
|
// src/version.generated.ts
|
|
695
|
-
var __version__ = "0.
|
|
737
|
+
var __version__ = "0.29.0";
|
|
696
738
|
|
|
697
739
|
// src/constants.ts
|
|
698
740
|
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
@@ -932,6 +974,50 @@ var HttpClient = class {
|
|
|
932
974
|
async lookupFunction(name) {
|
|
933
975
|
return this.request("/api/sdk/functions/lookup", { name });
|
|
934
976
|
}
|
|
977
|
+
async getTraceSpan(traceId, lookup) {
|
|
978
|
+
const searchParams = new URLSearchParams();
|
|
979
|
+
if (lookup.id !== void 0) {
|
|
980
|
+
searchParams.set("id", lookup.id);
|
|
981
|
+
} else {
|
|
982
|
+
searchParams.set("name", lookup.name);
|
|
983
|
+
searchParams.set("occurrence", String(lookup.occurrence ?? "last"));
|
|
984
|
+
}
|
|
985
|
+
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}/span?${searchParams.toString()}`;
|
|
986
|
+
const response = await this.get(endpoint);
|
|
987
|
+
return response.span;
|
|
988
|
+
}
|
|
989
|
+
async get(endpoint) {
|
|
990
|
+
const url = `${this.serviceUrl}${endpoint}`;
|
|
991
|
+
const controller = new AbortController();
|
|
992
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
993
|
+
try {
|
|
994
|
+
const response = await fetch(url, {
|
|
995
|
+
method: "GET",
|
|
996
|
+
headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
|
|
997
|
+
signal: controller.signal
|
|
998
|
+
});
|
|
999
|
+
if (!response.ok) {
|
|
1000
|
+
const errorText = await response.text();
|
|
1001
|
+
throw new BitfabError(
|
|
1002
|
+
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
return await response.json();
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
if (error instanceof BitfabError) {
|
|
1008
|
+
throw error;
|
|
1009
|
+
}
|
|
1010
|
+
if (error instanceof Error) {
|
|
1011
|
+
if (error.name === "AbortError") {
|
|
1012
|
+
throw new BitfabError(`Request timed out after ${this.timeout}ms`);
|
|
1013
|
+
}
|
|
1014
|
+
throw new BitfabError(error.message);
|
|
1015
|
+
}
|
|
1016
|
+
throw new BitfabError("Unknown error occurred");
|
|
1017
|
+
} finally {
|
|
1018
|
+
clearTimeout(timeoutId);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
935
1021
|
/**
|
|
936
1022
|
* Send an internal trace (from BAML execution).
|
|
937
1023
|
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
@@ -988,12 +1074,12 @@ var HttpClient = class {
|
|
|
988
1074
|
});
|
|
989
1075
|
}
|
|
990
1076
|
/**
|
|
991
|
-
* Partial update of an existing
|
|
1077
|
+
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
992
1078
|
* Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
|
|
993
1079
|
* returns a tracked promise that callers may optionally await.
|
|
994
1080
|
*/
|
|
995
|
-
patchTrace(
|
|
996
|
-
const endpoint = `/api/sdk/
|
|
1081
|
+
patchTrace(traceId, payload) {
|
|
1082
|
+
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}`;
|
|
997
1083
|
return awaitOnExit(
|
|
998
1084
|
this.request(endpoint, payload, { method: "PATCH" })
|
|
999
1085
|
).catch((error) => {
|
|
@@ -1077,9 +1163,14 @@ var HttpClient = class {
|
|
|
1077
1163
|
/**
|
|
1078
1164
|
* Fetch the span tree for a root span.
|
|
1079
1165
|
* Blocking GET request.
|
|
1166
|
+
*
|
|
1167
|
+
* Pass `includeOutputs: false` for a payload-free tree (structure +
|
|
1168
|
+
* `externalSpanId` only), so recorded outputs are fetched lazily per mocked
|
|
1169
|
+
* span instead of all up front. Omit it (default eager) for `mock: "all"`.
|
|
1080
1170
|
*/
|
|
1081
|
-
async getSpanTree(externalSpanId) {
|
|
1082
|
-
const
|
|
1171
|
+
async getSpanTree(externalSpanId, options) {
|
|
1172
|
+
const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
|
|
1173
|
+
const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
|
|
1083
1174
|
const controller = new AbortController();
|
|
1084
1175
|
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
1085
1176
|
try {
|
|
@@ -1331,6 +1422,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1331
1422
|
const traceId = this.ensureTrace();
|
|
1332
1423
|
const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
|
|
1333
1424
|
const spanInfo = {
|
|
1425
|
+
id: randomUuid(),
|
|
1334
1426
|
spanId,
|
|
1335
1427
|
traceId,
|
|
1336
1428
|
parentId: parentId ?? null,
|
|
@@ -1394,6 +1486,8 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1394
1486
|
rawSpan.parent_id = spanInfo.parentId;
|
|
1395
1487
|
}
|
|
1396
1488
|
const payload = {
|
|
1489
|
+
id: spanInfo.id,
|
|
1490
|
+
traceId: spanInfo.traceId,
|
|
1397
1491
|
type: "sdk-function",
|
|
1398
1492
|
source: "typescript-sdk-claude-agent-sdk",
|
|
1399
1493
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -1423,6 +1517,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1423
1517
|
externalTrace.metadata = metadata;
|
|
1424
1518
|
}
|
|
1425
1519
|
const traceData = {
|
|
1520
|
+
id: traceId,
|
|
1426
1521
|
type: "sdk-function",
|
|
1427
1522
|
source: "typescript-sdk-claude-agent-sdk",
|
|
1428
1523
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -1691,6 +1786,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1691
1786
|
}
|
|
1692
1787
|
Object.assign(llmContext, this.currentLlmUsage);
|
|
1693
1788
|
const spanInfo = {
|
|
1789
|
+
id: randomUuid(),
|
|
1694
1790
|
spanId,
|
|
1695
1791
|
traceId,
|
|
1696
1792
|
parentId,
|
|
@@ -2337,6 +2433,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2337
2433
|
const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
|
|
2338
2434
|
const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
|
|
2339
2435
|
const spanInfo = {
|
|
2436
|
+
id: randomUuid(),
|
|
2340
2437
|
spanId: runId,
|
|
2341
2438
|
traceId: invocation.traceId,
|
|
2342
2439
|
rootRunId: invocation.rootRunId,
|
|
@@ -2415,6 +2512,8 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2415
2512
|
rawSpan.parent_id = spanInfo.parentId;
|
|
2416
2513
|
}
|
|
2417
2514
|
const payload = {
|
|
2515
|
+
id: spanInfo.id,
|
|
2516
|
+
traceId: spanInfo.traceId,
|
|
2418
2517
|
type: "sdk-function",
|
|
2419
2518
|
source: "typescript-sdk-langgraph",
|
|
2420
2519
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2430,6 +2529,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2430
2529
|
sendTraceCompletion(rootSpan, activeContext) {
|
|
2431
2530
|
const completed = activeContext === null;
|
|
2432
2531
|
const traceData = {
|
|
2532
|
+
id: rootSpan.traceId,
|
|
2433
2533
|
type: "sdk-function",
|
|
2434
2534
|
source: "typescript-sdk-langgraph",
|
|
2435
2535
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2449,6 +2549,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2449
2549
|
}
|
|
2450
2550
|
sendTraceStart(rootSpan) {
|
|
2451
2551
|
const traceData = {
|
|
2552
|
+
id: rootSpan.traceId,
|
|
2452
2553
|
type: "sdk-function",
|
|
2453
2554
|
source: "typescript-sdk-langgraph",
|
|
2454
2555
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2655,6 +2756,9 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2655
2756
|
}
|
|
2656
2757
|
};
|
|
2657
2758
|
|
|
2759
|
+
// src/client.ts
|
|
2760
|
+
init_mockOverride();
|
|
2761
|
+
|
|
2658
2762
|
// src/openaiAgentSdk.ts
|
|
2659
2763
|
var BitfabOpenAIAgentHandler = class {
|
|
2660
2764
|
constructor(config) {
|
|
@@ -2807,6 +2911,7 @@ var ReplayEnvironment = class {
|
|
|
2807
2911
|
init_serialize();
|
|
2808
2912
|
|
|
2809
2913
|
// src/tracing.ts
|
|
2914
|
+
init_randomUuid();
|
|
2810
2915
|
var BitfabOpenAITracingProcessor = class {
|
|
2811
2916
|
/**
|
|
2812
2917
|
* Initialize the tracing processor.
|
|
@@ -2816,6 +2921,7 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2816
2921
|
constructor(config) {
|
|
2817
2922
|
this.activeTraces = {};
|
|
2818
2923
|
this.activeSpanMappings = {};
|
|
2924
|
+
this.canonicalTraceIds = {};
|
|
2819
2925
|
this.httpClient = new HttpClient({
|
|
2820
2926
|
apiKey: config.apiKey,
|
|
2821
2927
|
serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
|
|
@@ -2823,6 +2929,15 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2823
2929
|
});
|
|
2824
2930
|
this.getActiveSpanContext = config.getActiveSpanContext ?? null;
|
|
2825
2931
|
}
|
|
2932
|
+
getCanonicalTraceId(sourceTraceId) {
|
|
2933
|
+
const existing = this.canonicalTraceIds[sourceTraceId];
|
|
2934
|
+
if (existing) {
|
|
2935
|
+
return existing;
|
|
2936
|
+
}
|
|
2937
|
+
const created = randomUuid();
|
|
2938
|
+
this.canonicalTraceIds[sourceTraceId] = created;
|
|
2939
|
+
return created;
|
|
2940
|
+
}
|
|
2826
2941
|
/**
|
|
2827
2942
|
* Called when a trace is started.
|
|
2828
2943
|
* If there's an active withSpan context, the trace ID is remapped to the
|
|
@@ -2834,7 +2949,12 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2834
2949
|
if (activeContext) {
|
|
2835
2950
|
this.activeSpanMappings[trace.traceId] = activeContext;
|
|
2836
2951
|
}
|
|
2837
|
-
|
|
2952
|
+
const canonicalTraceId = activeContext?.traceId ?? this.getCanonicalTraceId(trace.traceId);
|
|
2953
|
+
this.canonicalTraceIds[trace.traceId] = canonicalTraceId;
|
|
2954
|
+
this.sendTrace(trace, {
|
|
2955
|
+
id: canonicalTraceId,
|
|
2956
|
+
sourceTraceId: activeContext?.traceId
|
|
2957
|
+
});
|
|
2838
2958
|
}
|
|
2839
2959
|
/**
|
|
2840
2960
|
* Called when a trace is ended.
|
|
@@ -2843,11 +2963,13 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2843
2963
|
*/
|
|
2844
2964
|
async onTraceEnd(trace) {
|
|
2845
2965
|
const mapping = this.activeSpanMappings[trace.traceId];
|
|
2846
|
-
this.sendTrace(
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2966
|
+
this.sendTrace(trace, {
|
|
2967
|
+
completed: mapping === void 0,
|
|
2968
|
+
id: mapping?.traceId ?? this.getCanonicalTraceId(trace.traceId),
|
|
2969
|
+
sourceTraceId: mapping?.traceId
|
|
2970
|
+
});
|
|
2850
2971
|
delete this.activeSpanMappings[trace.traceId];
|
|
2972
|
+
delete this.canonicalTraceIds[trace.traceId];
|
|
2851
2973
|
delete this.activeTraces[trace.traceId];
|
|
2852
2974
|
}
|
|
2853
2975
|
/**
|
|
@@ -2877,22 +2999,25 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2877
2999
|
async shutdown(_timeout) {
|
|
2878
3000
|
this.activeTraces = {};
|
|
2879
3001
|
this.activeSpanMappings = {};
|
|
3002
|
+
this.canonicalTraceIds = {};
|
|
2880
3003
|
}
|
|
2881
3004
|
/**
|
|
2882
3005
|
* Send trace to Bitfab API (fire-and-forget).
|
|
2883
3006
|
* When traceIdOverride is provided, the trace ID is remapped to link
|
|
2884
3007
|
* the OpenAI trace into an outer withSpan trace.
|
|
2885
3008
|
*/
|
|
2886
|
-
sendTrace(trace,
|
|
3009
|
+
sendTrace(trace, options = {}) {
|
|
2887
3010
|
try {
|
|
2888
|
-
const { completed, ...traceOverrides } = overrides;
|
|
2889
3011
|
const traceData = trace.toJSON();
|
|
2890
|
-
|
|
3012
|
+
if (options.sourceTraceId) {
|
|
3013
|
+
traceData.id = options.sourceTraceId;
|
|
3014
|
+
}
|
|
2891
3015
|
this.httpClient.sendExternalTrace({
|
|
3016
|
+
...options.id && { id: options.id },
|
|
2892
3017
|
type: "openai",
|
|
2893
3018
|
source: "typescript-sdk-openai-tracing",
|
|
2894
3019
|
externalTrace: traceData,
|
|
2895
|
-
completed: completed ?? false
|
|
3020
|
+
completed: options.completed ?? false
|
|
2896
3021
|
});
|
|
2897
3022
|
} catch {
|
|
2898
3023
|
}
|
|
@@ -2978,6 +3103,7 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2978
3103
|
*/
|
|
2979
3104
|
buildSpanPayload(serializedSpan, errors) {
|
|
2980
3105
|
const payload = {
|
|
3106
|
+
id: randomUuid(),
|
|
2981
3107
|
type: "openai",
|
|
2982
3108
|
source: "typescript-sdk-openai-tracing",
|
|
2983
3109
|
sourceTraceId: serializedSpan.trace_id ?? "unknown",
|
|
@@ -3000,6 +3126,10 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
3000
3126
|
this.extractSpanInputResponse(span, serializedSpan, errors);
|
|
3001
3127
|
this.applySpanOverrides(serializedSpan, span.traceId ?? "");
|
|
3002
3128
|
const payload = this.buildSpanPayload(serializedSpan, errors);
|
|
3129
|
+
const canonicalTraceId = span.traceId ? this.getCanonicalTraceId(span.traceId) : void 0;
|
|
3130
|
+
if (canonicalTraceId) {
|
|
3131
|
+
payload.traceId = canonicalTraceId;
|
|
3132
|
+
}
|
|
3003
3133
|
this.httpClient.sendExternalSpan(payload);
|
|
3004
3134
|
}
|
|
3005
3135
|
};
|
|
@@ -3316,24 +3446,19 @@ function extractContextFromCollector(collector) {
|
|
|
3316
3446
|
return null;
|
|
3317
3447
|
}
|
|
3318
3448
|
}
|
|
3319
|
-
var
|
|
3320
|
-
var TRACE_ID_MAX_LENGTH = 256;
|
|
3449
|
+
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;
|
|
3321
3450
|
function validateTraceId(traceId) {
|
|
3322
|
-
if (typeof traceId !== "string" || traceId
|
|
3323
|
-
throw new BitfabError("traceId
|
|
3324
|
-
}
|
|
3325
|
-
if (traceId.length > TRACE_ID_MAX_LENGTH) {
|
|
3326
|
-
throw new BitfabError(
|
|
3327
|
-
`traceId must be ${TRACE_ID_MAX_LENGTH} characters or fewer`
|
|
3328
|
-
);
|
|
3451
|
+
if (typeof traceId !== "string" || !UUID_PATTERN.test(traceId)) {
|
|
3452
|
+
throw new BitfabError("traceId must be a valid Bitfab trace ID");
|
|
3329
3453
|
}
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
);
|
|
3454
|
+
}
|
|
3455
|
+
function validateSpanId(id) {
|
|
3456
|
+
if (typeof id !== "string" || !UUID_PATTERN.test(id)) {
|
|
3457
|
+
throw new BitfabError("id must be a valid Bitfab span ID");
|
|
3334
3458
|
}
|
|
3335
3459
|
}
|
|
3336
3460
|
var noOpSpan = {
|
|
3461
|
+
id: "",
|
|
3337
3462
|
traceId: "",
|
|
3338
3463
|
addContext() {
|
|
3339
3464
|
},
|
|
@@ -3357,6 +3482,7 @@ function getCurrentSpan() {
|
|
|
3357
3482
|
return noOpSpan;
|
|
3358
3483
|
}
|
|
3359
3484
|
return {
|
|
3485
|
+
id: current.spanId,
|
|
3360
3486
|
traceId: current.traceId,
|
|
3361
3487
|
addContext(context) {
|
|
3362
3488
|
try {
|
|
@@ -3448,6 +3574,12 @@ var Bitfab = class {
|
|
|
3448
3574
|
constructor(config) {
|
|
3449
3575
|
/** Gate the empty-key warning to fire at most once. */
|
|
3450
3576
|
this.apiKeyWarned = false;
|
|
3577
|
+
/**
|
|
3578
|
+
* Mock overrides registered via {@link Bitfab.registerMockOverride}, applied
|
|
3579
|
+
* to every `replay` on this client (after any per-call `mockOverride`). In
|
|
3580
|
+
* registration order; first matcher wins within this list.
|
|
3581
|
+
*/
|
|
3582
|
+
this.mockOverrides = [];
|
|
3451
3583
|
this.apiKeyConfig = config.apiKey;
|
|
3452
3584
|
this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
|
|
3453
3585
|
this.timeout = config.timeout ?? 12e4;
|
|
@@ -4060,24 +4192,77 @@ var Bitfab = class {
|
|
|
4060
4192
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
4061
4193
|
const callIndex = counters.get(counterKey) ?? 0;
|
|
4062
4194
|
counters.set(counterKey, callIndex + 1);
|
|
4063
|
-
const
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
if (
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4195
|
+
const mockKey = `${counterKey}:${callIndex}`;
|
|
4196
|
+
const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
|
|
4197
|
+
const emitMock = (output) => {
|
|
4198
|
+
void sendSpan({ result: output, mocked: true });
|
|
4199
|
+
if (fnReturnsPromise) {
|
|
4200
|
+
return Promise.resolve(output);
|
|
4201
|
+
}
|
|
4202
|
+
return output;
|
|
4203
|
+
};
|
|
4204
|
+
const emitMockAsync = (pending) => {
|
|
4205
|
+
if (!fnReturnsPromise) {
|
|
4206
|
+
throw new BitfabError(
|
|
4207
|
+
`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.`
|
|
4208
|
+
);
|
|
4209
|
+
}
|
|
4210
|
+
return (async () => {
|
|
4211
|
+
const output = await pending;
|
|
4075
4212
|
void sendSpan({ result: output, mocked: true });
|
|
4076
|
-
if (fnReturnsPromise) {
|
|
4077
|
-
return Promise.resolve(output);
|
|
4078
|
-
}
|
|
4079
4213
|
return output;
|
|
4214
|
+
})();
|
|
4215
|
+
};
|
|
4216
|
+
const resolveRecordedOutput = () => {
|
|
4217
|
+
const hasInlineOutput = mockSpan?.output !== void 0 || mockSpan?.outputMeta !== void 0;
|
|
4218
|
+
if (!hasInlineOutput && replayCtxForMock.fetchSpanOutput && mockSpan?.externalSpanId) {
|
|
4219
|
+
return replayCtxForMock.fetchSpanOutput(mockSpan.externalSpanId);
|
|
4220
|
+
}
|
|
4221
|
+
if (!mockSpan) {
|
|
4222
|
+
return Promise.reject(
|
|
4223
|
+
new BitfabError(
|
|
4224
|
+
`No recorded span to source output for "${traceFunctionKey}".`
|
|
4225
|
+
)
|
|
4226
|
+
);
|
|
4227
|
+
}
|
|
4228
|
+
let output = mockSpan.output;
|
|
4229
|
+
if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
|
|
4230
|
+
output = deserializeValue({
|
|
4231
|
+
json: mockSpan.output,
|
|
4232
|
+
meta: mockSpan.outputMeta
|
|
4233
|
+
});
|
|
4234
|
+
}
|
|
4235
|
+
return output;
|
|
4236
|
+
};
|
|
4237
|
+
if (replayCtxForMock.mockOverrides?.length) {
|
|
4238
|
+
const nodeMeta = {
|
|
4239
|
+
traceFunctionKey,
|
|
4240
|
+
spanName: baseSpanParams.spanName,
|
|
4241
|
+
type: options.type ?? "custom",
|
|
4242
|
+
originalSpanId: mockSpan?.sourceSpanId
|
|
4243
|
+
};
|
|
4244
|
+
const override = replayCtxForMock.mockOverrides.find(
|
|
4245
|
+
(o) => o.match(nodeMeta)
|
|
4246
|
+
);
|
|
4247
|
+
if (override) {
|
|
4248
|
+
const injected = resolveMockValue(override.value, {
|
|
4249
|
+
node: nodeMeta,
|
|
4250
|
+
inputs: args,
|
|
4251
|
+
getOriginalOutput: () => Promise.resolve(resolveRecordedOutput())
|
|
4252
|
+
});
|
|
4253
|
+
if (injected instanceof Promise) {
|
|
4254
|
+
return emitMockAsync(injected);
|
|
4255
|
+
}
|
|
4256
|
+
return emitMock(injected);
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
|
|
4260
|
+
if (shouldMock && mockSpan) {
|
|
4261
|
+
const recorded = resolveRecordedOutput();
|
|
4262
|
+
if (recorded instanceof Promise) {
|
|
4263
|
+
return emitMockAsync(recorded);
|
|
4080
4264
|
}
|
|
4265
|
+
return emitMock(recorded);
|
|
4081
4266
|
}
|
|
4082
4267
|
}
|
|
4083
4268
|
const recordSpan = (result) => {
|
|
@@ -4153,20 +4338,19 @@ var Bitfab = class {
|
|
|
4153
4338
|
}
|
|
4154
4339
|
/**
|
|
4155
4340
|
* Get a detached handle to a previously-created trace, looked up by the
|
|
4156
|
-
*
|
|
4341
|
+
* canonical Bitfab trace ID.
|
|
4157
4342
|
*
|
|
4158
4343
|
* The returned handle is not tied to AsyncLocalStorage - each method sends
|
|
4159
4344
|
* to the server immediately. Useful for adding context to a trace from a
|
|
4160
4345
|
* different process or thread than the one that created it.
|
|
4161
4346
|
*
|
|
4162
|
-
* Throws synchronously if `traceId` is
|
|
4163
|
-
*
|
|
4164
|
-
* no trace exists with that id in the org; the failure surfaces as a
|
|
4347
|
+
* Throws synchronously if `traceId` is not a valid Bitfab trace ID. The
|
|
4348
|
+
* server returns 404 if no trace exists with that ID in the org; the failure surfaces as a
|
|
4165
4349
|
* logged warning (fire-and-forget) or via the awaited promise.
|
|
4166
4350
|
*
|
|
4167
4351
|
* Example:
|
|
4168
4352
|
* ```typescript
|
|
4169
|
-
* const trace = client.getTrace(
|
|
4353
|
+
* const trace = client.getTrace(traceId);
|
|
4170
4354
|
* await trace.addContext({ refund_status: "approved" });
|
|
4171
4355
|
* await trace.setMetadata({ region: "us-west" });
|
|
4172
4356
|
* ```
|
|
@@ -4206,6 +4390,33 @@ var Bitfab = class {
|
|
|
4206
4390
|
}
|
|
4207
4391
|
};
|
|
4208
4392
|
}
|
|
4393
|
+
/**
|
|
4394
|
+
* Fetch one persisted span from a trace without loading the full trace.
|
|
4395
|
+
* Name lookups return the last matching span by default. Pass `occurrence`
|
|
4396
|
+
* as `"first"` or a zero-based index to select a different match.
|
|
4397
|
+
*/
|
|
4398
|
+
async getTraceSpan(traceId, lookup) {
|
|
4399
|
+
validateTraceId(traceId);
|
|
4400
|
+
const hasId = lookup.id !== void 0;
|
|
4401
|
+
const hasName = lookup.name !== void 0;
|
|
4402
|
+
if (hasId === hasName) {
|
|
4403
|
+
throw new BitfabError("Provide exactly one of id or name");
|
|
4404
|
+
}
|
|
4405
|
+
if (hasId) {
|
|
4406
|
+
validateSpanId(lookup.id);
|
|
4407
|
+
} else {
|
|
4408
|
+
if (lookup.name.length === 0) {
|
|
4409
|
+
throw new BitfabError("name must be a non-empty string");
|
|
4410
|
+
}
|
|
4411
|
+
const occurrence = lookup.occurrence ?? "last";
|
|
4412
|
+
if (occurrence !== "first" && occurrence !== "last" && (!Number.isInteger(occurrence) || occurrence < 0)) {
|
|
4413
|
+
throw new BitfabError(
|
|
4414
|
+
'occurrence must be "first", "last", or a non-negative integer'
|
|
4415
|
+
);
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
return this.httpClient.getTraceSpan(traceId, lookup);
|
|
4419
|
+
}
|
|
4209
4420
|
/**
|
|
4210
4421
|
* Get a function wrapper for a specific trace function key.
|
|
4211
4422
|
*
|
|
@@ -4264,6 +4475,7 @@ var Bitfab = class {
|
|
|
4264
4475
|
};
|
|
4265
4476
|
}
|
|
4266
4477
|
return this.httpClient.sendExternalTrace({
|
|
4478
|
+
id: params.traceId,
|
|
4267
4479
|
type: "sdk-function",
|
|
4268
4480
|
source: "typescript-sdk-function",
|
|
4269
4481
|
traceFunctionKey: params.traceFunctionKey,
|
|
@@ -4319,6 +4531,8 @@ var Bitfab = class {
|
|
|
4319
4531
|
externalSpan.input_source_span_id = params.inputSourceSpanId;
|
|
4320
4532
|
}
|
|
4321
4533
|
return this.httpClient.sendExternalSpan({
|
|
4534
|
+
id: params.spanId,
|
|
4535
|
+
traceId: params.traceId,
|
|
4322
4536
|
type: "sdk-function",
|
|
4323
4537
|
source: "typescript-sdk-function",
|
|
4324
4538
|
sourceTraceId: params.traceId,
|
|
@@ -4328,26 +4542,14 @@ var Bitfab = class {
|
|
|
4328
4542
|
...params.mocked && { mocked: true }
|
|
4329
4543
|
});
|
|
4330
4544
|
}
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
* invocation records a trace tied to the test run. The plain-callable form
|
|
4340
|
-
* is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
|
|
4341
|
-
* SDK) replay - those record traces under a key with no `withSpan`-wrapped
|
|
4342
|
-
* root in the app.
|
|
4343
|
-
*
|
|
4344
|
-
* @param traceFunctionKey - The trace function key to replay
|
|
4345
|
-
* @param fn - The function to run recorded inputs through
|
|
4346
|
-
* @param options - Optional replay options. When `traceIds` is passed,
|
|
4347
|
-
* `limit` is ignored (with a warning): an explicit ID list already
|
|
4348
|
-
* determines how many traces replay.
|
|
4349
|
-
* @returns ReplayResult with items, testRunId, and testRunUrl
|
|
4350
|
-
*/
|
|
4545
|
+
registerMockOverride(overrideOrMatch, value) {
|
|
4546
|
+
const override = typeof overrideOrMatch === "function" ? { match: overrideOrMatch, value } : overrideOrMatch;
|
|
4547
|
+
this.mockOverrides.push(override);
|
|
4548
|
+
}
|
|
4549
|
+
/** Remove all overrides registered via {@link registerMockOverride}. */
|
|
4550
|
+
clearMockOverrides() {
|
|
4551
|
+
this.mockOverrides.length = 0;
|
|
4552
|
+
}
|
|
4351
4553
|
async replay(traceFunctionKey, fn, options) {
|
|
4352
4554
|
const wrappedKey = fn._bitfabTraceFunctionKey;
|
|
4353
4555
|
let replayFn = fn;
|
|
@@ -4368,7 +4570,8 @@ var Bitfab = class {
|
|
|
4368
4570
|
this.serviceUrl,
|
|
4369
4571
|
traceFunctionKey,
|
|
4370
4572
|
replayFn,
|
|
4371
|
-
options
|
|
4573
|
+
options,
|
|
4574
|
+
this.mockOverrides
|
|
4372
4575
|
);
|
|
4373
4576
|
}
|
|
4374
4577
|
};
|