@bitfab/sdk 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
|
@@ -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,7 +394,7 @@ 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, environment, adaptInputs) {
|
|
381
398
|
const lease = environment ? serverItem.dbBranchLease : void 0;
|
|
382
399
|
let inputs = [];
|
|
383
400
|
let originalOutput;
|
|
@@ -396,28 +413,45 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
396
413
|
sourceSpanId: serverItem.externalSpanId
|
|
397
414
|
});
|
|
398
415
|
}
|
|
416
|
+
const hasOverrides = resolvedOverrides.length > 0;
|
|
417
|
+
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
418
|
+
const includeOutputs = mockStrategy === "all";
|
|
399
419
|
let mockTree;
|
|
400
|
-
if (
|
|
420
|
+
if (needTree) {
|
|
401
421
|
try {
|
|
402
422
|
const treeResponse = await httpClient.getSpanTree(
|
|
403
|
-
serverItem.externalSpanId
|
|
423
|
+
serverItem.externalSpanId,
|
|
424
|
+
{ includeOutputs }
|
|
404
425
|
);
|
|
405
426
|
if (treeResponse.root) {
|
|
406
427
|
mockTree = buildMockTree(treeResponse.root);
|
|
407
|
-
} else if (mockStrategy === "all") {
|
|
428
|
+
} else if (mockStrategy === "all" || hasOverrides) {
|
|
408
429
|
throw new BitfabError(
|
|
409
|
-
`Replay mock strategy "
|
|
430
|
+
`Replay mock strategy "${mockStrategy}"${hasOverrides ? " with overrides" : ""} requires a span tree root for source span ${serverItem.externalSpanId}.`
|
|
410
431
|
);
|
|
411
432
|
} else {
|
|
412
433
|
mockTree = void 0;
|
|
413
434
|
}
|
|
414
435
|
} catch (e) {
|
|
415
|
-
if (mockStrategy === "all") {
|
|
436
|
+
if (mockStrategy === "all" || hasOverrides) {
|
|
416
437
|
throw e;
|
|
417
438
|
}
|
|
418
439
|
mockTree = void 0;
|
|
419
440
|
}
|
|
420
441
|
}
|
|
442
|
+
const outputCache = /* @__PURE__ */ new Map();
|
|
443
|
+
const fetchSpanOutput = mockTree && !includeOutputs ? (externalSpanId) => {
|
|
444
|
+
let pending = outputCache.get(externalSpanId);
|
|
445
|
+
if (!pending) {
|
|
446
|
+
pending = httpClient.getExternalSpan(externalSpanId).then(
|
|
447
|
+
(s) => deserializeOutput(
|
|
448
|
+
s.rawData?.span_data ?? {}
|
|
449
|
+
)
|
|
450
|
+
);
|
|
451
|
+
outputCache.set(externalSpanId, pending);
|
|
452
|
+
}
|
|
453
|
+
return pending;
|
|
454
|
+
} : void 0;
|
|
421
455
|
const maybePromise = runWithReplayContext(
|
|
422
456
|
{
|
|
423
457
|
testRunId,
|
|
@@ -428,6 +462,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
428
462
|
mockTree,
|
|
429
463
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
430
464
|
mockStrategy,
|
|
465
|
+
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
466
|
+
fetchSpanOutput,
|
|
431
467
|
dbBranchLease: lease,
|
|
432
468
|
pendingPersistence
|
|
433
469
|
},
|
|
@@ -484,7 +520,7 @@ async function mapWithConcurrency(tasks, maxConcurrency, onSettled) {
|
|
|
484
520
|
await Promise.all(workers);
|
|
485
521
|
return results;
|
|
486
522
|
}
|
|
487
|
-
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
523
|
+
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
|
|
488
524
|
if (options?.traceIds !== void 0) {
|
|
489
525
|
if (options.traceIds.length === 0) {
|
|
490
526
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -524,6 +560,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
524
560
|
);
|
|
525
561
|
const mockStrategy = options?.mock ?? "marked";
|
|
526
562
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
563
|
+
const resolvedOverrides = [
|
|
564
|
+
...normalizeMockOverrides(options?.mockOverride),
|
|
565
|
+
...registeredOverrides
|
|
566
|
+
];
|
|
527
567
|
const tasks = serverItems.map(
|
|
528
568
|
(serverItem) => () => processItem(
|
|
529
569
|
httpClient,
|
|
@@ -531,6 +571,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
531
571
|
fn,
|
|
532
572
|
testRunId,
|
|
533
573
|
mockStrategy,
|
|
574
|
+
resolvedOverrides,
|
|
534
575
|
options?.environment,
|
|
535
576
|
options?.adaptInputs
|
|
536
577
|
)
|
|
@@ -657,6 +698,7 @@ var init_replay = __esm({
|
|
|
657
698
|
"src/replay.ts"() {
|
|
658
699
|
"use strict";
|
|
659
700
|
init_errors();
|
|
701
|
+
init_mockOverride();
|
|
660
702
|
init_randomUuid();
|
|
661
703
|
init_replayContext();
|
|
662
704
|
init_serialize();
|
|
@@ -697,7 +739,7 @@ registerAsyncLocalStorageClass(
|
|
|
697
739
|
);
|
|
698
740
|
|
|
699
741
|
// src/version.generated.ts
|
|
700
|
-
var __version__ = "0.
|
|
742
|
+
var __version__ = "0.29.0";
|
|
701
743
|
|
|
702
744
|
// src/constants.ts
|
|
703
745
|
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
@@ -937,6 +979,50 @@ var HttpClient = class {
|
|
|
937
979
|
async lookupFunction(name) {
|
|
938
980
|
return this.request("/api/sdk/functions/lookup", { name });
|
|
939
981
|
}
|
|
982
|
+
async getTraceSpan(traceId, lookup) {
|
|
983
|
+
const searchParams = new URLSearchParams();
|
|
984
|
+
if (lookup.id !== void 0) {
|
|
985
|
+
searchParams.set("id", lookup.id);
|
|
986
|
+
} else {
|
|
987
|
+
searchParams.set("name", lookup.name);
|
|
988
|
+
searchParams.set("occurrence", String(lookup.occurrence ?? "last"));
|
|
989
|
+
}
|
|
990
|
+
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}/span?${searchParams.toString()}`;
|
|
991
|
+
const response = await this.get(endpoint);
|
|
992
|
+
return response.span;
|
|
993
|
+
}
|
|
994
|
+
async get(endpoint) {
|
|
995
|
+
const url = `${this.serviceUrl}${endpoint}`;
|
|
996
|
+
const controller = new AbortController();
|
|
997
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
998
|
+
try {
|
|
999
|
+
const response = await fetch(url, {
|
|
1000
|
+
method: "GET",
|
|
1001
|
+
headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
|
|
1002
|
+
signal: controller.signal
|
|
1003
|
+
});
|
|
1004
|
+
if (!response.ok) {
|
|
1005
|
+
const errorText = await response.text();
|
|
1006
|
+
throw new BitfabError(
|
|
1007
|
+
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
return await response.json();
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
if (error instanceof BitfabError) {
|
|
1013
|
+
throw error;
|
|
1014
|
+
}
|
|
1015
|
+
if (error instanceof Error) {
|
|
1016
|
+
if (error.name === "AbortError") {
|
|
1017
|
+
throw new BitfabError(`Request timed out after ${this.timeout}ms`);
|
|
1018
|
+
}
|
|
1019
|
+
throw new BitfabError(error.message);
|
|
1020
|
+
}
|
|
1021
|
+
throw new BitfabError("Unknown error occurred");
|
|
1022
|
+
} finally {
|
|
1023
|
+
clearTimeout(timeoutId);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
940
1026
|
/**
|
|
941
1027
|
* Send an internal trace (from BAML execution).
|
|
942
1028
|
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
@@ -993,12 +1079,12 @@ var HttpClient = class {
|
|
|
993
1079
|
});
|
|
994
1080
|
}
|
|
995
1081
|
/**
|
|
996
|
-
* Partial update of an existing
|
|
1082
|
+
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
997
1083
|
* Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
|
|
998
1084
|
* returns a tracked promise that callers may optionally await.
|
|
999
1085
|
*/
|
|
1000
|
-
patchTrace(
|
|
1001
|
-
const endpoint = `/api/sdk/
|
|
1086
|
+
patchTrace(traceId, payload) {
|
|
1087
|
+
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}`;
|
|
1002
1088
|
return awaitOnExit(
|
|
1003
1089
|
this.request(endpoint, payload, { method: "PATCH" })
|
|
1004
1090
|
).catch((error) => {
|
|
@@ -1082,9 +1168,14 @@ var HttpClient = class {
|
|
|
1082
1168
|
/**
|
|
1083
1169
|
* Fetch the span tree for a root span.
|
|
1084
1170
|
* Blocking GET request.
|
|
1171
|
+
*
|
|
1172
|
+
* Pass `includeOutputs: false` for a payload-free tree (structure +
|
|
1173
|
+
* `externalSpanId` only), so recorded outputs are fetched lazily per mocked
|
|
1174
|
+
* span instead of all up front. Omit it (default eager) for `mock: "all"`.
|
|
1085
1175
|
*/
|
|
1086
|
-
async getSpanTree(externalSpanId) {
|
|
1087
|
-
const
|
|
1176
|
+
async getSpanTree(externalSpanId, options) {
|
|
1177
|
+
const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
|
|
1178
|
+
const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
|
|
1088
1179
|
const controller = new AbortController();
|
|
1089
1180
|
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
1090
1181
|
try {
|
|
@@ -1336,6 +1427,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1336
1427
|
const traceId = this.ensureTrace();
|
|
1337
1428
|
const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
|
|
1338
1429
|
const spanInfo = {
|
|
1430
|
+
id: randomUuid(),
|
|
1339
1431
|
spanId,
|
|
1340
1432
|
traceId,
|
|
1341
1433
|
parentId: parentId ?? null,
|
|
@@ -1399,6 +1491,8 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1399
1491
|
rawSpan.parent_id = spanInfo.parentId;
|
|
1400
1492
|
}
|
|
1401
1493
|
const payload = {
|
|
1494
|
+
id: spanInfo.id,
|
|
1495
|
+
traceId: spanInfo.traceId,
|
|
1402
1496
|
type: "sdk-function",
|
|
1403
1497
|
source: "typescript-sdk-claude-agent-sdk",
|
|
1404
1498
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -1428,6 +1522,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1428
1522
|
externalTrace.metadata = metadata;
|
|
1429
1523
|
}
|
|
1430
1524
|
const traceData = {
|
|
1525
|
+
id: traceId,
|
|
1431
1526
|
type: "sdk-function",
|
|
1432
1527
|
source: "typescript-sdk-claude-agent-sdk",
|
|
1433
1528
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -1696,6 +1791,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1696
1791
|
}
|
|
1697
1792
|
Object.assign(llmContext, this.currentLlmUsage);
|
|
1698
1793
|
const spanInfo = {
|
|
1794
|
+
id: randomUuid(),
|
|
1699
1795
|
spanId,
|
|
1700
1796
|
traceId,
|
|
1701
1797
|
parentId,
|
|
@@ -2342,6 +2438,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2342
2438
|
const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
|
|
2343
2439
|
const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
|
|
2344
2440
|
const spanInfo = {
|
|
2441
|
+
id: randomUuid(),
|
|
2345
2442
|
spanId: runId,
|
|
2346
2443
|
traceId: invocation.traceId,
|
|
2347
2444
|
rootRunId: invocation.rootRunId,
|
|
@@ -2420,6 +2517,8 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2420
2517
|
rawSpan.parent_id = spanInfo.parentId;
|
|
2421
2518
|
}
|
|
2422
2519
|
const payload = {
|
|
2520
|
+
id: spanInfo.id,
|
|
2521
|
+
traceId: spanInfo.traceId,
|
|
2423
2522
|
type: "sdk-function",
|
|
2424
2523
|
source: "typescript-sdk-langgraph",
|
|
2425
2524
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2435,6 +2534,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2435
2534
|
sendTraceCompletion(rootSpan, activeContext) {
|
|
2436
2535
|
const completed = activeContext === null;
|
|
2437
2536
|
const traceData = {
|
|
2537
|
+
id: rootSpan.traceId,
|
|
2438
2538
|
type: "sdk-function",
|
|
2439
2539
|
source: "typescript-sdk-langgraph",
|
|
2440
2540
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2454,6 +2554,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2454
2554
|
}
|
|
2455
2555
|
sendTraceStart(rootSpan) {
|
|
2456
2556
|
const traceData = {
|
|
2557
|
+
id: rootSpan.traceId,
|
|
2457
2558
|
type: "sdk-function",
|
|
2458
2559
|
source: "typescript-sdk-langgraph",
|
|
2459
2560
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2660,6 +2761,9 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2660
2761
|
}
|
|
2661
2762
|
};
|
|
2662
2763
|
|
|
2764
|
+
// src/client.ts
|
|
2765
|
+
init_mockOverride();
|
|
2766
|
+
|
|
2663
2767
|
// src/openaiAgentSdk.ts
|
|
2664
2768
|
var BitfabOpenAIAgentHandler = class {
|
|
2665
2769
|
constructor(config) {
|
|
@@ -2812,6 +2916,7 @@ var ReplayEnvironment = class {
|
|
|
2812
2916
|
init_serialize();
|
|
2813
2917
|
|
|
2814
2918
|
// src/tracing.ts
|
|
2919
|
+
init_randomUuid();
|
|
2815
2920
|
var BitfabOpenAITracingProcessor = class {
|
|
2816
2921
|
/**
|
|
2817
2922
|
* Initialize the tracing processor.
|
|
@@ -2821,6 +2926,7 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2821
2926
|
constructor(config) {
|
|
2822
2927
|
this.activeTraces = {};
|
|
2823
2928
|
this.activeSpanMappings = {};
|
|
2929
|
+
this.canonicalTraceIds = {};
|
|
2824
2930
|
this.httpClient = new HttpClient({
|
|
2825
2931
|
apiKey: config.apiKey,
|
|
2826
2932
|
serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
|
|
@@ -2828,6 +2934,15 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2828
2934
|
});
|
|
2829
2935
|
this.getActiveSpanContext = config.getActiveSpanContext ?? null;
|
|
2830
2936
|
}
|
|
2937
|
+
getCanonicalTraceId(sourceTraceId) {
|
|
2938
|
+
const existing = this.canonicalTraceIds[sourceTraceId];
|
|
2939
|
+
if (existing) {
|
|
2940
|
+
return existing;
|
|
2941
|
+
}
|
|
2942
|
+
const created = randomUuid();
|
|
2943
|
+
this.canonicalTraceIds[sourceTraceId] = created;
|
|
2944
|
+
return created;
|
|
2945
|
+
}
|
|
2831
2946
|
/**
|
|
2832
2947
|
* Called when a trace is started.
|
|
2833
2948
|
* If there's an active withSpan context, the trace ID is remapped to the
|
|
@@ -2839,7 +2954,12 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2839
2954
|
if (activeContext) {
|
|
2840
2955
|
this.activeSpanMappings[trace.traceId] = activeContext;
|
|
2841
2956
|
}
|
|
2842
|
-
|
|
2957
|
+
const canonicalTraceId = activeContext?.traceId ?? this.getCanonicalTraceId(trace.traceId);
|
|
2958
|
+
this.canonicalTraceIds[trace.traceId] = canonicalTraceId;
|
|
2959
|
+
this.sendTrace(trace, {
|
|
2960
|
+
id: canonicalTraceId,
|
|
2961
|
+
sourceTraceId: activeContext?.traceId
|
|
2962
|
+
});
|
|
2843
2963
|
}
|
|
2844
2964
|
/**
|
|
2845
2965
|
* Called when a trace is ended.
|
|
@@ -2848,11 +2968,13 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2848
2968
|
*/
|
|
2849
2969
|
async onTraceEnd(trace) {
|
|
2850
2970
|
const mapping = this.activeSpanMappings[trace.traceId];
|
|
2851
|
-
this.sendTrace(
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2971
|
+
this.sendTrace(trace, {
|
|
2972
|
+
completed: mapping === void 0,
|
|
2973
|
+
id: mapping?.traceId ?? this.getCanonicalTraceId(trace.traceId),
|
|
2974
|
+
sourceTraceId: mapping?.traceId
|
|
2975
|
+
});
|
|
2855
2976
|
delete this.activeSpanMappings[trace.traceId];
|
|
2977
|
+
delete this.canonicalTraceIds[trace.traceId];
|
|
2856
2978
|
delete this.activeTraces[trace.traceId];
|
|
2857
2979
|
}
|
|
2858
2980
|
/**
|
|
@@ -2882,22 +3004,25 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2882
3004
|
async shutdown(_timeout) {
|
|
2883
3005
|
this.activeTraces = {};
|
|
2884
3006
|
this.activeSpanMappings = {};
|
|
3007
|
+
this.canonicalTraceIds = {};
|
|
2885
3008
|
}
|
|
2886
3009
|
/**
|
|
2887
3010
|
* Send trace to Bitfab API (fire-and-forget).
|
|
2888
3011
|
* When traceIdOverride is provided, the trace ID is remapped to link
|
|
2889
3012
|
* the OpenAI trace into an outer withSpan trace.
|
|
2890
3013
|
*/
|
|
2891
|
-
sendTrace(trace,
|
|
3014
|
+
sendTrace(trace, options = {}) {
|
|
2892
3015
|
try {
|
|
2893
|
-
const { completed, ...traceOverrides } = overrides;
|
|
2894
3016
|
const traceData = trace.toJSON();
|
|
2895
|
-
|
|
3017
|
+
if (options.sourceTraceId) {
|
|
3018
|
+
traceData.id = options.sourceTraceId;
|
|
3019
|
+
}
|
|
2896
3020
|
this.httpClient.sendExternalTrace({
|
|
3021
|
+
...options.id && { id: options.id },
|
|
2897
3022
|
type: "openai",
|
|
2898
3023
|
source: "typescript-sdk-openai-tracing",
|
|
2899
3024
|
externalTrace: traceData,
|
|
2900
|
-
completed: completed ?? false
|
|
3025
|
+
completed: options.completed ?? false
|
|
2901
3026
|
});
|
|
2902
3027
|
} catch {
|
|
2903
3028
|
}
|
|
@@ -2983,6 +3108,7 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2983
3108
|
*/
|
|
2984
3109
|
buildSpanPayload(serializedSpan, errors) {
|
|
2985
3110
|
const payload = {
|
|
3111
|
+
id: randomUuid(),
|
|
2986
3112
|
type: "openai",
|
|
2987
3113
|
source: "typescript-sdk-openai-tracing",
|
|
2988
3114
|
sourceTraceId: serializedSpan.trace_id ?? "unknown",
|
|
@@ -3005,6 +3131,10 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
3005
3131
|
this.extractSpanInputResponse(span, serializedSpan, errors);
|
|
3006
3132
|
this.applySpanOverrides(serializedSpan, span.traceId ?? "");
|
|
3007
3133
|
const payload = this.buildSpanPayload(serializedSpan, errors);
|
|
3134
|
+
const canonicalTraceId = span.traceId ? this.getCanonicalTraceId(span.traceId) : void 0;
|
|
3135
|
+
if (canonicalTraceId) {
|
|
3136
|
+
payload.traceId = canonicalTraceId;
|
|
3137
|
+
}
|
|
3008
3138
|
this.httpClient.sendExternalSpan(payload);
|
|
3009
3139
|
}
|
|
3010
3140
|
};
|
|
@@ -3321,24 +3451,19 @@ function extractContextFromCollector(collector) {
|
|
|
3321
3451
|
return null;
|
|
3322
3452
|
}
|
|
3323
3453
|
}
|
|
3324
|
-
var
|
|
3325
|
-
var TRACE_ID_MAX_LENGTH = 256;
|
|
3454
|
+
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;
|
|
3326
3455
|
function validateTraceId(traceId) {
|
|
3327
|
-
if (typeof traceId !== "string" || traceId
|
|
3328
|
-
throw new BitfabError("traceId
|
|
3329
|
-
}
|
|
3330
|
-
if (traceId.length > TRACE_ID_MAX_LENGTH) {
|
|
3331
|
-
throw new BitfabError(
|
|
3332
|
-
`traceId must be ${TRACE_ID_MAX_LENGTH} characters or fewer`
|
|
3333
|
-
);
|
|
3456
|
+
if (typeof traceId !== "string" || !UUID_PATTERN.test(traceId)) {
|
|
3457
|
+
throw new BitfabError("traceId must be a valid Bitfab trace ID");
|
|
3334
3458
|
}
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
);
|
|
3459
|
+
}
|
|
3460
|
+
function validateSpanId(id) {
|
|
3461
|
+
if (typeof id !== "string" || !UUID_PATTERN.test(id)) {
|
|
3462
|
+
throw new BitfabError("id must be a valid Bitfab span ID");
|
|
3339
3463
|
}
|
|
3340
3464
|
}
|
|
3341
3465
|
var noOpSpan = {
|
|
3466
|
+
id: "",
|
|
3342
3467
|
traceId: "",
|
|
3343
3468
|
addContext() {
|
|
3344
3469
|
},
|
|
@@ -3362,6 +3487,7 @@ function getCurrentSpan() {
|
|
|
3362
3487
|
return noOpSpan;
|
|
3363
3488
|
}
|
|
3364
3489
|
return {
|
|
3490
|
+
id: current.spanId,
|
|
3365
3491
|
traceId: current.traceId,
|
|
3366
3492
|
addContext(context) {
|
|
3367
3493
|
try {
|
|
@@ -3453,6 +3579,12 @@ var Bitfab = class {
|
|
|
3453
3579
|
constructor(config) {
|
|
3454
3580
|
/** Gate the empty-key warning to fire at most once. */
|
|
3455
3581
|
this.apiKeyWarned = false;
|
|
3582
|
+
/**
|
|
3583
|
+
* Mock overrides registered via {@link Bitfab.registerMockOverride}, applied
|
|
3584
|
+
* to every `replay` on this client (after any per-call `mockOverride`). In
|
|
3585
|
+
* registration order; first matcher wins within this list.
|
|
3586
|
+
*/
|
|
3587
|
+
this.mockOverrides = [];
|
|
3456
3588
|
this.apiKeyConfig = config.apiKey;
|
|
3457
3589
|
this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
|
|
3458
3590
|
this.timeout = config.timeout ?? 12e4;
|
|
@@ -4065,24 +4197,77 @@ var Bitfab = class {
|
|
|
4065
4197
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
4066
4198
|
const callIndex = counters.get(counterKey) ?? 0;
|
|
4067
4199
|
counters.set(counterKey, callIndex + 1);
|
|
4068
|
-
const
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
if (
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4200
|
+
const mockKey = `${counterKey}:${callIndex}`;
|
|
4201
|
+
const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
|
|
4202
|
+
const emitMock = (output) => {
|
|
4203
|
+
void sendSpan({ result: output, mocked: true });
|
|
4204
|
+
if (fnReturnsPromise) {
|
|
4205
|
+
return Promise.resolve(output);
|
|
4206
|
+
}
|
|
4207
|
+
return output;
|
|
4208
|
+
};
|
|
4209
|
+
const emitMockAsync = (pending) => {
|
|
4210
|
+
if (!fnReturnsPromise) {
|
|
4211
|
+
throw new BitfabError(
|
|
4212
|
+
`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.`
|
|
4213
|
+
);
|
|
4214
|
+
}
|
|
4215
|
+
return (async () => {
|
|
4216
|
+
const output = await pending;
|
|
4080
4217
|
void sendSpan({ result: output, mocked: true });
|
|
4081
|
-
if (fnReturnsPromise) {
|
|
4082
|
-
return Promise.resolve(output);
|
|
4083
|
-
}
|
|
4084
4218
|
return output;
|
|
4219
|
+
})();
|
|
4220
|
+
};
|
|
4221
|
+
const resolveRecordedOutput = () => {
|
|
4222
|
+
const hasInlineOutput = mockSpan?.output !== void 0 || mockSpan?.outputMeta !== void 0;
|
|
4223
|
+
if (!hasInlineOutput && replayCtxForMock.fetchSpanOutput && mockSpan?.externalSpanId) {
|
|
4224
|
+
return replayCtxForMock.fetchSpanOutput(mockSpan.externalSpanId);
|
|
4225
|
+
}
|
|
4226
|
+
if (!mockSpan) {
|
|
4227
|
+
return Promise.reject(
|
|
4228
|
+
new BitfabError(
|
|
4229
|
+
`No recorded span to source output for "${traceFunctionKey}".`
|
|
4230
|
+
)
|
|
4231
|
+
);
|
|
4232
|
+
}
|
|
4233
|
+
let output = mockSpan.output;
|
|
4234
|
+
if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
|
|
4235
|
+
output = deserializeValue({
|
|
4236
|
+
json: mockSpan.output,
|
|
4237
|
+
meta: mockSpan.outputMeta
|
|
4238
|
+
});
|
|
4239
|
+
}
|
|
4240
|
+
return output;
|
|
4241
|
+
};
|
|
4242
|
+
if (replayCtxForMock.mockOverrides?.length) {
|
|
4243
|
+
const nodeMeta = {
|
|
4244
|
+
traceFunctionKey,
|
|
4245
|
+
spanName: baseSpanParams.spanName,
|
|
4246
|
+
type: options.type ?? "custom",
|
|
4247
|
+
originalSpanId: mockSpan?.sourceSpanId
|
|
4248
|
+
};
|
|
4249
|
+
const override = replayCtxForMock.mockOverrides.find(
|
|
4250
|
+
(o) => o.match(nodeMeta)
|
|
4251
|
+
);
|
|
4252
|
+
if (override) {
|
|
4253
|
+
const injected = resolveMockValue(override.value, {
|
|
4254
|
+
node: nodeMeta,
|
|
4255
|
+
inputs: args,
|
|
4256
|
+
getOriginalOutput: () => Promise.resolve(resolveRecordedOutput())
|
|
4257
|
+
});
|
|
4258
|
+
if (injected instanceof Promise) {
|
|
4259
|
+
return emitMockAsync(injected);
|
|
4260
|
+
}
|
|
4261
|
+
return emitMock(injected);
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
|
|
4265
|
+
if (shouldMock && mockSpan) {
|
|
4266
|
+
const recorded = resolveRecordedOutput();
|
|
4267
|
+
if (recorded instanceof Promise) {
|
|
4268
|
+
return emitMockAsync(recorded);
|
|
4085
4269
|
}
|
|
4270
|
+
return emitMock(recorded);
|
|
4086
4271
|
}
|
|
4087
4272
|
}
|
|
4088
4273
|
const recordSpan = (result) => {
|
|
@@ -4158,20 +4343,19 @@ var Bitfab = class {
|
|
|
4158
4343
|
}
|
|
4159
4344
|
/**
|
|
4160
4345
|
* Get a detached handle to a previously-created trace, looked up by the
|
|
4161
|
-
*
|
|
4346
|
+
* canonical Bitfab trace ID.
|
|
4162
4347
|
*
|
|
4163
4348
|
* The returned handle is not tied to AsyncLocalStorage - each method sends
|
|
4164
4349
|
* to the server immediately. Useful for adding context to a trace from a
|
|
4165
4350
|
* different process or thread than the one that created it.
|
|
4166
4351
|
*
|
|
4167
|
-
* Throws synchronously if `traceId` is
|
|
4168
|
-
*
|
|
4169
|
-
* no trace exists with that id in the org; the failure surfaces as a
|
|
4352
|
+
* Throws synchronously if `traceId` is not a valid Bitfab trace ID. The
|
|
4353
|
+
* server returns 404 if no trace exists with that ID in the org; the failure surfaces as a
|
|
4170
4354
|
* logged warning (fire-and-forget) or via the awaited promise.
|
|
4171
4355
|
*
|
|
4172
4356
|
* Example:
|
|
4173
4357
|
* ```typescript
|
|
4174
|
-
* const trace = client.getTrace(
|
|
4358
|
+
* const trace = client.getTrace(traceId);
|
|
4175
4359
|
* await trace.addContext({ refund_status: "approved" });
|
|
4176
4360
|
* await trace.setMetadata({ region: "us-west" });
|
|
4177
4361
|
* ```
|
|
@@ -4211,6 +4395,33 @@ var Bitfab = class {
|
|
|
4211
4395
|
}
|
|
4212
4396
|
};
|
|
4213
4397
|
}
|
|
4398
|
+
/**
|
|
4399
|
+
* Fetch one persisted span from a trace without loading the full trace.
|
|
4400
|
+
* Name lookups return the last matching span by default. Pass `occurrence`
|
|
4401
|
+
* as `"first"` or a zero-based index to select a different match.
|
|
4402
|
+
*/
|
|
4403
|
+
async getTraceSpan(traceId, lookup) {
|
|
4404
|
+
validateTraceId(traceId);
|
|
4405
|
+
const hasId = lookup.id !== void 0;
|
|
4406
|
+
const hasName = lookup.name !== void 0;
|
|
4407
|
+
if (hasId === hasName) {
|
|
4408
|
+
throw new BitfabError("Provide exactly one of id or name");
|
|
4409
|
+
}
|
|
4410
|
+
if (hasId) {
|
|
4411
|
+
validateSpanId(lookup.id);
|
|
4412
|
+
} else {
|
|
4413
|
+
if (lookup.name.length === 0) {
|
|
4414
|
+
throw new BitfabError("name must be a non-empty string");
|
|
4415
|
+
}
|
|
4416
|
+
const occurrence = lookup.occurrence ?? "last";
|
|
4417
|
+
if (occurrence !== "first" && occurrence !== "last" && (!Number.isInteger(occurrence) || occurrence < 0)) {
|
|
4418
|
+
throw new BitfabError(
|
|
4419
|
+
'occurrence must be "first", "last", or a non-negative integer'
|
|
4420
|
+
);
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
return this.httpClient.getTraceSpan(traceId, lookup);
|
|
4424
|
+
}
|
|
4214
4425
|
/**
|
|
4215
4426
|
* Get a function wrapper for a specific trace function key.
|
|
4216
4427
|
*
|
|
@@ -4269,6 +4480,7 @@ var Bitfab = class {
|
|
|
4269
4480
|
};
|
|
4270
4481
|
}
|
|
4271
4482
|
return this.httpClient.sendExternalTrace({
|
|
4483
|
+
id: params.traceId,
|
|
4272
4484
|
type: "sdk-function",
|
|
4273
4485
|
source: "typescript-sdk-function",
|
|
4274
4486
|
traceFunctionKey: params.traceFunctionKey,
|
|
@@ -4324,6 +4536,8 @@ var Bitfab = class {
|
|
|
4324
4536
|
externalSpan.input_source_span_id = params.inputSourceSpanId;
|
|
4325
4537
|
}
|
|
4326
4538
|
return this.httpClient.sendExternalSpan({
|
|
4539
|
+
id: params.spanId,
|
|
4540
|
+
traceId: params.traceId,
|
|
4327
4541
|
type: "sdk-function",
|
|
4328
4542
|
source: "typescript-sdk-function",
|
|
4329
4543
|
sourceTraceId: params.traceId,
|
|
@@ -4333,26 +4547,14 @@ var Bitfab = class {
|
|
|
4333
4547
|
...params.mocked && { mocked: true }
|
|
4334
4548
|
});
|
|
4335
4549
|
}
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
* invocation records a trace tied to the test run. The plain-callable form
|
|
4345
|
-
* is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
|
|
4346
|
-
* SDK) replay - those record traces under a key with no `withSpan`-wrapped
|
|
4347
|
-
* root in the app.
|
|
4348
|
-
*
|
|
4349
|
-
* @param traceFunctionKey - The trace function key to replay
|
|
4350
|
-
* @param fn - The function to run recorded inputs through
|
|
4351
|
-
* @param options - Optional replay options. When `traceIds` is passed,
|
|
4352
|
-
* `limit` is ignored (with a warning): an explicit ID list already
|
|
4353
|
-
* determines how many traces replay.
|
|
4354
|
-
* @returns ReplayResult with items, testRunId, and testRunUrl
|
|
4355
|
-
*/
|
|
4550
|
+
registerMockOverride(overrideOrMatch, value) {
|
|
4551
|
+
const override = typeof overrideOrMatch === "function" ? { match: overrideOrMatch, value } : overrideOrMatch;
|
|
4552
|
+
this.mockOverrides.push(override);
|
|
4553
|
+
}
|
|
4554
|
+
/** Remove all overrides registered via {@link registerMockOverride}. */
|
|
4555
|
+
clearMockOverrides() {
|
|
4556
|
+
this.mockOverrides.length = 0;
|
|
4557
|
+
}
|
|
4356
4558
|
async replay(traceFunctionKey, fn, options) {
|
|
4357
4559
|
const wrappedKey = fn._bitfabTraceFunctionKey;
|
|
4358
4560
|
let replayFn = fn;
|
|
@@ -4373,7 +4575,8 @@ var Bitfab = class {
|
|
|
4373
4575
|
this.serviceUrl,
|
|
4374
4576
|
traceFunctionKey,
|
|
4375
4577
|
replayFn,
|
|
4376
|
-
options
|
|
4578
|
+
options,
|
|
4579
|
+
this.mockOverrides
|
|
4377
4580
|
);
|
|
4378
4581
|
}
|
|
4379
4582
|
};
|