@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/index.cjs
CHANGED
|
@@ -282,6 +282,22 @@ var init_asyncStorage = __esm({
|
|
|
282
282
|
}
|
|
283
283
|
});
|
|
284
284
|
|
|
285
|
+
// src/mockOverride.ts
|
|
286
|
+
function resolveMockValue(value, ctx) {
|
|
287
|
+
return typeof value === "function" ? value(ctx) : value;
|
|
288
|
+
}
|
|
289
|
+
function normalizeMockOverrides(mockOverride) {
|
|
290
|
+
if (mockOverride === void 0) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
return Array.isArray(mockOverride) ? mockOverride : [mockOverride];
|
|
294
|
+
}
|
|
295
|
+
var init_mockOverride = __esm({
|
|
296
|
+
"src/mockOverride.ts"() {
|
|
297
|
+
"use strict";
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
285
301
|
// src/replayContext.ts
|
|
286
302
|
function getReplayContext() {
|
|
287
303
|
return replayContextStorage?.getStore() ?? null;
|
|
@@ -357,6 +373,7 @@ function buildMockTree(rootNode) {
|
|
|
357
373
|
counters.set(counterKey, index + 1);
|
|
358
374
|
spans.set(`${counterKey}:${index}`, {
|
|
359
375
|
sourceSpanId: node.sourceSpanId,
|
|
376
|
+
externalSpanId: node.externalSpanId,
|
|
360
377
|
output: node.output,
|
|
361
378
|
outputMeta: node.outputMeta
|
|
362
379
|
});
|
|
@@ -370,7 +387,7 @@ function buildMockTree(rootNode) {
|
|
|
370
387
|
}
|
|
371
388
|
return { spans };
|
|
372
389
|
}
|
|
373
|
-
async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment, adaptInputs) {
|
|
390
|
+
async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, environment, adaptInputs) {
|
|
374
391
|
const lease = environment ? serverItem.dbBranchLease : void 0;
|
|
375
392
|
let inputs = [];
|
|
376
393
|
let originalOutput;
|
|
@@ -389,28 +406,45 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
389
406
|
sourceSpanId: serverItem.externalSpanId
|
|
390
407
|
});
|
|
391
408
|
}
|
|
409
|
+
const hasOverrides = resolvedOverrides.length > 0;
|
|
410
|
+
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
411
|
+
const includeOutputs = mockStrategy === "all";
|
|
392
412
|
let mockTree;
|
|
393
|
-
if (
|
|
413
|
+
if (needTree) {
|
|
394
414
|
try {
|
|
395
415
|
const treeResponse = await httpClient.getSpanTree(
|
|
396
|
-
serverItem.externalSpanId
|
|
416
|
+
serverItem.externalSpanId,
|
|
417
|
+
{ includeOutputs }
|
|
397
418
|
);
|
|
398
419
|
if (treeResponse.root) {
|
|
399
420
|
mockTree = buildMockTree(treeResponse.root);
|
|
400
|
-
} else if (mockStrategy === "all") {
|
|
421
|
+
} else if (mockStrategy === "all" || hasOverrides) {
|
|
401
422
|
throw new BitfabError(
|
|
402
|
-
`Replay mock strategy "
|
|
423
|
+
`Replay mock strategy "${mockStrategy}"${hasOverrides ? " with overrides" : ""} requires a span tree root for source span ${serverItem.externalSpanId}.`
|
|
403
424
|
);
|
|
404
425
|
} else {
|
|
405
426
|
mockTree = void 0;
|
|
406
427
|
}
|
|
407
428
|
} catch (e) {
|
|
408
|
-
if (mockStrategy === "all") {
|
|
429
|
+
if (mockStrategy === "all" || hasOverrides) {
|
|
409
430
|
throw e;
|
|
410
431
|
}
|
|
411
432
|
mockTree = void 0;
|
|
412
433
|
}
|
|
413
434
|
}
|
|
435
|
+
const outputCache = /* @__PURE__ */ new Map();
|
|
436
|
+
const fetchSpanOutput = mockTree && !includeOutputs ? (externalSpanId) => {
|
|
437
|
+
let pending = outputCache.get(externalSpanId);
|
|
438
|
+
if (!pending) {
|
|
439
|
+
pending = httpClient.getExternalSpan(externalSpanId).then(
|
|
440
|
+
(s) => deserializeOutput(
|
|
441
|
+
s.rawData?.span_data ?? {}
|
|
442
|
+
)
|
|
443
|
+
);
|
|
444
|
+
outputCache.set(externalSpanId, pending);
|
|
445
|
+
}
|
|
446
|
+
return pending;
|
|
447
|
+
} : void 0;
|
|
414
448
|
const maybePromise = runWithReplayContext(
|
|
415
449
|
{
|
|
416
450
|
testRunId,
|
|
@@ -421,6 +455,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
421
455
|
mockTree,
|
|
422
456
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
423
457
|
mockStrategy,
|
|
458
|
+
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
459
|
+
fetchSpanOutput,
|
|
424
460
|
dbBranchLease: lease,
|
|
425
461
|
pendingPersistence
|
|
426
462
|
},
|
|
@@ -477,7 +513,7 @@ async function mapWithConcurrency(tasks, maxConcurrency, onSettled) {
|
|
|
477
513
|
await Promise.all(workers);
|
|
478
514
|
return results;
|
|
479
515
|
}
|
|
480
|
-
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
516
|
+
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
|
|
481
517
|
if (options?.traceIds !== void 0) {
|
|
482
518
|
if (options.traceIds.length === 0) {
|
|
483
519
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -517,6 +553,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
517
553
|
);
|
|
518
554
|
const mockStrategy = options?.mock ?? "marked";
|
|
519
555
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
556
|
+
const resolvedOverrides = [
|
|
557
|
+
...normalizeMockOverrides(options?.mockOverride),
|
|
558
|
+
...registeredOverrides
|
|
559
|
+
];
|
|
520
560
|
const tasks = serverItems.map(
|
|
521
561
|
(serverItem) => () => processItem(
|
|
522
562
|
httpClient,
|
|
@@ -524,6 +564,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
524
564
|
fn,
|
|
525
565
|
testRunId,
|
|
526
566
|
mockStrategy,
|
|
567
|
+
resolvedOverrides,
|
|
527
568
|
options?.environment,
|
|
528
569
|
options?.adaptInputs
|
|
529
570
|
)
|
|
@@ -650,6 +691,7 @@ var init_replay = __esm({
|
|
|
650
691
|
"src/replay.ts"() {
|
|
651
692
|
"use strict";
|
|
652
693
|
init_errors();
|
|
694
|
+
init_mockOverride();
|
|
653
695
|
init_randomUuid();
|
|
654
696
|
init_replayContext();
|
|
655
697
|
init_serialize();
|
|
@@ -683,7 +725,7 @@ __export(index_exports, {
|
|
|
683
725
|
module.exports = __toCommonJS(index_exports);
|
|
684
726
|
|
|
685
727
|
// src/version.generated.ts
|
|
686
|
-
var __version__ = "0.
|
|
728
|
+
var __version__ = "0.29.0";
|
|
687
729
|
|
|
688
730
|
// src/constants.ts
|
|
689
731
|
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
@@ -923,6 +965,50 @@ var HttpClient = class {
|
|
|
923
965
|
async lookupFunction(name) {
|
|
924
966
|
return this.request("/api/sdk/functions/lookup", { name });
|
|
925
967
|
}
|
|
968
|
+
async getTraceSpan(traceId, lookup) {
|
|
969
|
+
const searchParams = new URLSearchParams();
|
|
970
|
+
if (lookup.id !== void 0) {
|
|
971
|
+
searchParams.set("id", lookup.id);
|
|
972
|
+
} else {
|
|
973
|
+
searchParams.set("name", lookup.name);
|
|
974
|
+
searchParams.set("occurrence", String(lookup.occurrence ?? "last"));
|
|
975
|
+
}
|
|
976
|
+
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}/span?${searchParams.toString()}`;
|
|
977
|
+
const response = await this.get(endpoint);
|
|
978
|
+
return response.span;
|
|
979
|
+
}
|
|
980
|
+
async get(endpoint) {
|
|
981
|
+
const url = `${this.serviceUrl}${endpoint}`;
|
|
982
|
+
const controller = new AbortController();
|
|
983
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
984
|
+
try {
|
|
985
|
+
const response = await fetch(url, {
|
|
986
|
+
method: "GET",
|
|
987
|
+
headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
|
|
988
|
+
signal: controller.signal
|
|
989
|
+
});
|
|
990
|
+
if (!response.ok) {
|
|
991
|
+
const errorText = await response.text();
|
|
992
|
+
throw new BitfabError(
|
|
993
|
+
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
return await response.json();
|
|
997
|
+
} catch (error) {
|
|
998
|
+
if (error instanceof BitfabError) {
|
|
999
|
+
throw error;
|
|
1000
|
+
}
|
|
1001
|
+
if (error instanceof Error) {
|
|
1002
|
+
if (error.name === "AbortError") {
|
|
1003
|
+
throw new BitfabError(`Request timed out after ${this.timeout}ms`);
|
|
1004
|
+
}
|
|
1005
|
+
throw new BitfabError(error.message);
|
|
1006
|
+
}
|
|
1007
|
+
throw new BitfabError("Unknown error occurred");
|
|
1008
|
+
} finally {
|
|
1009
|
+
clearTimeout(timeoutId);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
926
1012
|
/**
|
|
927
1013
|
* Send an internal trace (from BAML execution).
|
|
928
1014
|
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
@@ -979,12 +1065,12 @@ var HttpClient = class {
|
|
|
979
1065
|
});
|
|
980
1066
|
}
|
|
981
1067
|
/**
|
|
982
|
-
* Partial update of an existing
|
|
1068
|
+
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
983
1069
|
* Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
|
|
984
1070
|
* returns a tracked promise that callers may optionally await.
|
|
985
1071
|
*/
|
|
986
|
-
patchTrace(
|
|
987
|
-
const endpoint = `/api/sdk/
|
|
1072
|
+
patchTrace(traceId, payload) {
|
|
1073
|
+
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}`;
|
|
988
1074
|
return awaitOnExit(
|
|
989
1075
|
this.request(endpoint, payload, { method: "PATCH" })
|
|
990
1076
|
).catch((error) => {
|
|
@@ -1068,9 +1154,14 @@ var HttpClient = class {
|
|
|
1068
1154
|
/**
|
|
1069
1155
|
* Fetch the span tree for a root span.
|
|
1070
1156
|
* Blocking GET request.
|
|
1157
|
+
*
|
|
1158
|
+
* Pass `includeOutputs: false` for a payload-free tree (structure +
|
|
1159
|
+
* `externalSpanId` only), so recorded outputs are fetched lazily per mocked
|
|
1160
|
+
* span instead of all up front. Omit it (default eager) for `mock: "all"`.
|
|
1071
1161
|
*/
|
|
1072
|
-
async getSpanTree(externalSpanId) {
|
|
1073
|
-
const
|
|
1162
|
+
async getSpanTree(externalSpanId, options) {
|
|
1163
|
+
const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
|
|
1164
|
+
const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
|
|
1074
1165
|
const controller = new AbortController();
|
|
1075
1166
|
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
1076
1167
|
try {
|
|
@@ -1322,6 +1413,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1322
1413
|
const traceId = this.ensureTrace();
|
|
1323
1414
|
const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
|
|
1324
1415
|
const spanInfo = {
|
|
1416
|
+
id: randomUuid(),
|
|
1325
1417
|
spanId,
|
|
1326
1418
|
traceId,
|
|
1327
1419
|
parentId: parentId ?? null,
|
|
@@ -1385,6 +1477,8 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1385
1477
|
rawSpan.parent_id = spanInfo.parentId;
|
|
1386
1478
|
}
|
|
1387
1479
|
const payload = {
|
|
1480
|
+
id: spanInfo.id,
|
|
1481
|
+
traceId: spanInfo.traceId,
|
|
1388
1482
|
type: "sdk-function",
|
|
1389
1483
|
source: "typescript-sdk-claude-agent-sdk",
|
|
1390
1484
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -1414,6 +1508,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1414
1508
|
externalTrace.metadata = metadata;
|
|
1415
1509
|
}
|
|
1416
1510
|
const traceData = {
|
|
1511
|
+
id: traceId,
|
|
1417
1512
|
type: "sdk-function",
|
|
1418
1513
|
source: "typescript-sdk-claude-agent-sdk",
|
|
1419
1514
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -1682,6 +1777,7 @@ var BitfabClaudeAgentHandler = class {
|
|
|
1682
1777
|
}
|
|
1683
1778
|
Object.assign(llmContext, this.currentLlmUsage);
|
|
1684
1779
|
const spanInfo = {
|
|
1780
|
+
id: randomUuid(),
|
|
1685
1781
|
spanId,
|
|
1686
1782
|
traceId,
|
|
1687
1783
|
parentId,
|
|
@@ -2328,6 +2424,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2328
2424
|
const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
|
|
2329
2425
|
const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
|
|
2330
2426
|
const spanInfo = {
|
|
2427
|
+
id: randomUuid(),
|
|
2331
2428
|
spanId: runId,
|
|
2332
2429
|
traceId: invocation.traceId,
|
|
2333
2430
|
rootRunId: invocation.rootRunId,
|
|
@@ -2406,6 +2503,8 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2406
2503
|
rawSpan.parent_id = spanInfo.parentId;
|
|
2407
2504
|
}
|
|
2408
2505
|
const payload = {
|
|
2506
|
+
id: spanInfo.id,
|
|
2507
|
+
traceId: spanInfo.traceId,
|
|
2409
2508
|
type: "sdk-function",
|
|
2410
2509
|
source: "typescript-sdk-langgraph",
|
|
2411
2510
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2421,6 +2520,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2421
2520
|
sendTraceCompletion(rootSpan, activeContext) {
|
|
2422
2521
|
const completed = activeContext === null;
|
|
2423
2522
|
const traceData = {
|
|
2523
|
+
id: rootSpan.traceId,
|
|
2424
2524
|
type: "sdk-function",
|
|
2425
2525
|
source: "typescript-sdk-langgraph",
|
|
2426
2526
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2440,6 +2540,7 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2440
2540
|
}
|
|
2441
2541
|
sendTraceStart(rootSpan) {
|
|
2442
2542
|
const traceData = {
|
|
2543
|
+
id: rootSpan.traceId,
|
|
2443
2544
|
type: "sdk-function",
|
|
2444
2545
|
source: "typescript-sdk-langgraph",
|
|
2445
2546
|
traceFunctionKey: this.traceFunctionKey,
|
|
@@ -2646,6 +2747,9 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
2646
2747
|
}
|
|
2647
2748
|
};
|
|
2648
2749
|
|
|
2750
|
+
// src/client.ts
|
|
2751
|
+
init_mockOverride();
|
|
2752
|
+
|
|
2649
2753
|
// src/openaiAgentSdk.ts
|
|
2650
2754
|
var BitfabOpenAIAgentHandler = class {
|
|
2651
2755
|
constructor(config) {
|
|
@@ -2798,6 +2902,7 @@ var ReplayEnvironment = class {
|
|
|
2798
2902
|
init_serialize();
|
|
2799
2903
|
|
|
2800
2904
|
// src/tracing.ts
|
|
2905
|
+
init_randomUuid();
|
|
2801
2906
|
var BitfabOpenAITracingProcessor = class {
|
|
2802
2907
|
/**
|
|
2803
2908
|
* Initialize the tracing processor.
|
|
@@ -2807,6 +2912,7 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2807
2912
|
constructor(config) {
|
|
2808
2913
|
this.activeTraces = {};
|
|
2809
2914
|
this.activeSpanMappings = {};
|
|
2915
|
+
this.canonicalTraceIds = {};
|
|
2810
2916
|
this.httpClient = new HttpClient({
|
|
2811
2917
|
apiKey: config.apiKey,
|
|
2812
2918
|
serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
|
|
@@ -2814,6 +2920,15 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2814
2920
|
});
|
|
2815
2921
|
this.getActiveSpanContext = config.getActiveSpanContext ?? null;
|
|
2816
2922
|
}
|
|
2923
|
+
getCanonicalTraceId(sourceTraceId) {
|
|
2924
|
+
const existing = this.canonicalTraceIds[sourceTraceId];
|
|
2925
|
+
if (existing) {
|
|
2926
|
+
return existing;
|
|
2927
|
+
}
|
|
2928
|
+
const created = randomUuid();
|
|
2929
|
+
this.canonicalTraceIds[sourceTraceId] = created;
|
|
2930
|
+
return created;
|
|
2931
|
+
}
|
|
2817
2932
|
/**
|
|
2818
2933
|
* Called when a trace is started.
|
|
2819
2934
|
* If there's an active withSpan context, the trace ID is remapped to the
|
|
@@ -2825,7 +2940,12 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2825
2940
|
if (activeContext) {
|
|
2826
2941
|
this.activeSpanMappings[trace.traceId] = activeContext;
|
|
2827
2942
|
}
|
|
2828
|
-
|
|
2943
|
+
const canonicalTraceId = activeContext?.traceId ?? this.getCanonicalTraceId(trace.traceId);
|
|
2944
|
+
this.canonicalTraceIds[trace.traceId] = canonicalTraceId;
|
|
2945
|
+
this.sendTrace(trace, {
|
|
2946
|
+
id: canonicalTraceId,
|
|
2947
|
+
sourceTraceId: activeContext?.traceId
|
|
2948
|
+
});
|
|
2829
2949
|
}
|
|
2830
2950
|
/**
|
|
2831
2951
|
* Called when a trace is ended.
|
|
@@ -2834,11 +2954,13 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2834
2954
|
*/
|
|
2835
2955
|
async onTraceEnd(trace) {
|
|
2836
2956
|
const mapping = this.activeSpanMappings[trace.traceId];
|
|
2837
|
-
this.sendTrace(
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2957
|
+
this.sendTrace(trace, {
|
|
2958
|
+
completed: mapping === void 0,
|
|
2959
|
+
id: mapping?.traceId ?? this.getCanonicalTraceId(trace.traceId),
|
|
2960
|
+
sourceTraceId: mapping?.traceId
|
|
2961
|
+
});
|
|
2841
2962
|
delete this.activeSpanMappings[trace.traceId];
|
|
2963
|
+
delete this.canonicalTraceIds[trace.traceId];
|
|
2842
2964
|
delete this.activeTraces[trace.traceId];
|
|
2843
2965
|
}
|
|
2844
2966
|
/**
|
|
@@ -2868,22 +2990,25 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2868
2990
|
async shutdown(_timeout) {
|
|
2869
2991
|
this.activeTraces = {};
|
|
2870
2992
|
this.activeSpanMappings = {};
|
|
2993
|
+
this.canonicalTraceIds = {};
|
|
2871
2994
|
}
|
|
2872
2995
|
/**
|
|
2873
2996
|
* Send trace to Bitfab API (fire-and-forget).
|
|
2874
2997
|
* When traceIdOverride is provided, the trace ID is remapped to link
|
|
2875
2998
|
* the OpenAI trace into an outer withSpan trace.
|
|
2876
2999
|
*/
|
|
2877
|
-
sendTrace(trace,
|
|
3000
|
+
sendTrace(trace, options = {}) {
|
|
2878
3001
|
try {
|
|
2879
|
-
const { completed, ...traceOverrides } = overrides;
|
|
2880
3002
|
const traceData = trace.toJSON();
|
|
2881
|
-
|
|
3003
|
+
if (options.sourceTraceId) {
|
|
3004
|
+
traceData.id = options.sourceTraceId;
|
|
3005
|
+
}
|
|
2882
3006
|
this.httpClient.sendExternalTrace({
|
|
3007
|
+
...options.id && { id: options.id },
|
|
2883
3008
|
type: "openai",
|
|
2884
3009
|
source: "typescript-sdk-openai-tracing",
|
|
2885
3010
|
externalTrace: traceData,
|
|
2886
|
-
completed: completed ?? false
|
|
3011
|
+
completed: options.completed ?? false
|
|
2887
3012
|
});
|
|
2888
3013
|
} catch {
|
|
2889
3014
|
}
|
|
@@ -2969,6 +3094,7 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2969
3094
|
*/
|
|
2970
3095
|
buildSpanPayload(serializedSpan, errors) {
|
|
2971
3096
|
const payload = {
|
|
3097
|
+
id: randomUuid(),
|
|
2972
3098
|
type: "openai",
|
|
2973
3099
|
source: "typescript-sdk-openai-tracing",
|
|
2974
3100
|
sourceTraceId: serializedSpan.trace_id ?? "unknown",
|
|
@@ -2991,6 +3117,10 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2991
3117
|
this.extractSpanInputResponse(span, serializedSpan, errors);
|
|
2992
3118
|
this.applySpanOverrides(serializedSpan, span.traceId ?? "");
|
|
2993
3119
|
const payload = this.buildSpanPayload(serializedSpan, errors);
|
|
3120
|
+
const canonicalTraceId = span.traceId ? this.getCanonicalTraceId(span.traceId) : void 0;
|
|
3121
|
+
if (canonicalTraceId) {
|
|
3122
|
+
payload.traceId = canonicalTraceId;
|
|
3123
|
+
}
|
|
2994
3124
|
this.httpClient.sendExternalSpan(payload);
|
|
2995
3125
|
}
|
|
2996
3126
|
};
|
|
@@ -3307,24 +3437,19 @@ function extractContextFromCollector(collector) {
|
|
|
3307
3437
|
return null;
|
|
3308
3438
|
}
|
|
3309
3439
|
}
|
|
3310
|
-
var
|
|
3311
|
-
var TRACE_ID_MAX_LENGTH = 256;
|
|
3440
|
+
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;
|
|
3312
3441
|
function validateTraceId(traceId) {
|
|
3313
|
-
if (typeof traceId !== "string" || traceId
|
|
3314
|
-
throw new BitfabError("traceId
|
|
3315
|
-
}
|
|
3316
|
-
if (traceId.length > TRACE_ID_MAX_LENGTH) {
|
|
3317
|
-
throw new BitfabError(
|
|
3318
|
-
`traceId must be ${TRACE_ID_MAX_LENGTH} characters or fewer`
|
|
3319
|
-
);
|
|
3442
|
+
if (typeof traceId !== "string" || !UUID_PATTERN.test(traceId)) {
|
|
3443
|
+
throw new BitfabError("traceId must be a valid Bitfab trace ID");
|
|
3320
3444
|
}
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
);
|
|
3445
|
+
}
|
|
3446
|
+
function validateSpanId(id) {
|
|
3447
|
+
if (typeof id !== "string" || !UUID_PATTERN.test(id)) {
|
|
3448
|
+
throw new BitfabError("id must be a valid Bitfab span ID");
|
|
3325
3449
|
}
|
|
3326
3450
|
}
|
|
3327
3451
|
var noOpSpan = {
|
|
3452
|
+
id: "",
|
|
3328
3453
|
traceId: "",
|
|
3329
3454
|
addContext() {
|
|
3330
3455
|
},
|
|
@@ -3348,6 +3473,7 @@ function getCurrentSpan() {
|
|
|
3348
3473
|
return noOpSpan;
|
|
3349
3474
|
}
|
|
3350
3475
|
return {
|
|
3476
|
+
id: current.spanId,
|
|
3351
3477
|
traceId: current.traceId,
|
|
3352
3478
|
addContext(context) {
|
|
3353
3479
|
try {
|
|
@@ -3439,6 +3565,12 @@ var Bitfab = class {
|
|
|
3439
3565
|
constructor(config) {
|
|
3440
3566
|
/** Gate the empty-key warning to fire at most once. */
|
|
3441
3567
|
this.apiKeyWarned = false;
|
|
3568
|
+
/**
|
|
3569
|
+
* Mock overrides registered via {@link Bitfab.registerMockOverride}, applied
|
|
3570
|
+
* to every `replay` on this client (after any per-call `mockOverride`). In
|
|
3571
|
+
* registration order; first matcher wins within this list.
|
|
3572
|
+
*/
|
|
3573
|
+
this.mockOverrides = [];
|
|
3442
3574
|
this.apiKeyConfig = config.apiKey;
|
|
3443
3575
|
this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
|
|
3444
3576
|
this.timeout = config.timeout ?? 12e4;
|
|
@@ -4051,24 +4183,77 @@ var Bitfab = class {
|
|
|
4051
4183
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
4052
4184
|
const callIndex = counters.get(counterKey) ?? 0;
|
|
4053
4185
|
counters.set(counterKey, callIndex + 1);
|
|
4054
|
-
const
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
if (
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4186
|
+
const mockKey = `${counterKey}:${callIndex}`;
|
|
4187
|
+
const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
|
|
4188
|
+
const emitMock = (output) => {
|
|
4189
|
+
void sendSpan({ result: output, mocked: true });
|
|
4190
|
+
if (fnReturnsPromise) {
|
|
4191
|
+
return Promise.resolve(output);
|
|
4192
|
+
}
|
|
4193
|
+
return output;
|
|
4194
|
+
};
|
|
4195
|
+
const emitMockAsync = (pending) => {
|
|
4196
|
+
if (!fnReturnsPromise) {
|
|
4197
|
+
throw new BitfabError(
|
|
4198
|
+
`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.`
|
|
4199
|
+
);
|
|
4200
|
+
}
|
|
4201
|
+
return (async () => {
|
|
4202
|
+
const output = await pending;
|
|
4066
4203
|
void sendSpan({ result: output, mocked: true });
|
|
4067
|
-
if (fnReturnsPromise) {
|
|
4068
|
-
return Promise.resolve(output);
|
|
4069
|
-
}
|
|
4070
4204
|
return output;
|
|
4205
|
+
})();
|
|
4206
|
+
};
|
|
4207
|
+
const resolveRecordedOutput = () => {
|
|
4208
|
+
const hasInlineOutput = mockSpan?.output !== void 0 || mockSpan?.outputMeta !== void 0;
|
|
4209
|
+
if (!hasInlineOutput && replayCtxForMock.fetchSpanOutput && mockSpan?.externalSpanId) {
|
|
4210
|
+
return replayCtxForMock.fetchSpanOutput(mockSpan.externalSpanId);
|
|
4211
|
+
}
|
|
4212
|
+
if (!mockSpan) {
|
|
4213
|
+
return Promise.reject(
|
|
4214
|
+
new BitfabError(
|
|
4215
|
+
`No recorded span to source output for "${traceFunctionKey}".`
|
|
4216
|
+
)
|
|
4217
|
+
);
|
|
4218
|
+
}
|
|
4219
|
+
let output = mockSpan.output;
|
|
4220
|
+
if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
|
|
4221
|
+
output = deserializeValue({
|
|
4222
|
+
json: mockSpan.output,
|
|
4223
|
+
meta: mockSpan.outputMeta
|
|
4224
|
+
});
|
|
4225
|
+
}
|
|
4226
|
+
return output;
|
|
4227
|
+
};
|
|
4228
|
+
if (replayCtxForMock.mockOverrides?.length) {
|
|
4229
|
+
const nodeMeta = {
|
|
4230
|
+
traceFunctionKey,
|
|
4231
|
+
spanName: baseSpanParams.spanName,
|
|
4232
|
+
type: options.type ?? "custom",
|
|
4233
|
+
originalSpanId: mockSpan?.sourceSpanId
|
|
4234
|
+
};
|
|
4235
|
+
const override = replayCtxForMock.mockOverrides.find(
|
|
4236
|
+
(o) => o.match(nodeMeta)
|
|
4237
|
+
);
|
|
4238
|
+
if (override) {
|
|
4239
|
+
const injected = resolveMockValue(override.value, {
|
|
4240
|
+
node: nodeMeta,
|
|
4241
|
+
inputs: args,
|
|
4242
|
+
getOriginalOutput: () => Promise.resolve(resolveRecordedOutput())
|
|
4243
|
+
});
|
|
4244
|
+
if (injected instanceof Promise) {
|
|
4245
|
+
return emitMockAsync(injected);
|
|
4246
|
+
}
|
|
4247
|
+
return emitMock(injected);
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
|
|
4251
|
+
if (shouldMock && mockSpan) {
|
|
4252
|
+
const recorded = resolveRecordedOutput();
|
|
4253
|
+
if (recorded instanceof Promise) {
|
|
4254
|
+
return emitMockAsync(recorded);
|
|
4071
4255
|
}
|
|
4256
|
+
return emitMock(recorded);
|
|
4072
4257
|
}
|
|
4073
4258
|
}
|
|
4074
4259
|
const recordSpan = (result) => {
|
|
@@ -4144,20 +4329,19 @@ var Bitfab = class {
|
|
|
4144
4329
|
}
|
|
4145
4330
|
/**
|
|
4146
4331
|
* Get a detached handle to a previously-created trace, looked up by the
|
|
4147
|
-
*
|
|
4332
|
+
* canonical Bitfab trace ID.
|
|
4148
4333
|
*
|
|
4149
4334
|
* The returned handle is not tied to AsyncLocalStorage - each method sends
|
|
4150
4335
|
* to the server immediately. Useful for adding context to a trace from a
|
|
4151
4336
|
* different process or thread than the one that created it.
|
|
4152
4337
|
*
|
|
4153
|
-
* Throws synchronously if `traceId` is
|
|
4154
|
-
*
|
|
4155
|
-
* no trace exists with that id in the org; the failure surfaces as a
|
|
4338
|
+
* Throws synchronously if `traceId` is not a valid Bitfab trace ID. The
|
|
4339
|
+
* server returns 404 if no trace exists with that ID in the org; the failure surfaces as a
|
|
4156
4340
|
* logged warning (fire-and-forget) or via the awaited promise.
|
|
4157
4341
|
*
|
|
4158
4342
|
* Example:
|
|
4159
4343
|
* ```typescript
|
|
4160
|
-
* const trace = client.getTrace(
|
|
4344
|
+
* const trace = client.getTrace(traceId);
|
|
4161
4345
|
* await trace.addContext({ refund_status: "approved" });
|
|
4162
4346
|
* await trace.setMetadata({ region: "us-west" });
|
|
4163
4347
|
* ```
|
|
@@ -4197,6 +4381,33 @@ var Bitfab = class {
|
|
|
4197
4381
|
}
|
|
4198
4382
|
};
|
|
4199
4383
|
}
|
|
4384
|
+
/**
|
|
4385
|
+
* Fetch one persisted span from a trace without loading the full trace.
|
|
4386
|
+
* Name lookups return the last matching span by default. Pass `occurrence`
|
|
4387
|
+
* as `"first"` or a zero-based index to select a different match.
|
|
4388
|
+
*/
|
|
4389
|
+
async getTraceSpan(traceId, lookup) {
|
|
4390
|
+
validateTraceId(traceId);
|
|
4391
|
+
const hasId = lookup.id !== void 0;
|
|
4392
|
+
const hasName = lookup.name !== void 0;
|
|
4393
|
+
if (hasId === hasName) {
|
|
4394
|
+
throw new BitfabError("Provide exactly one of id or name");
|
|
4395
|
+
}
|
|
4396
|
+
if (hasId) {
|
|
4397
|
+
validateSpanId(lookup.id);
|
|
4398
|
+
} else {
|
|
4399
|
+
if (lookup.name.length === 0) {
|
|
4400
|
+
throw new BitfabError("name must be a non-empty string");
|
|
4401
|
+
}
|
|
4402
|
+
const occurrence = lookup.occurrence ?? "last";
|
|
4403
|
+
if (occurrence !== "first" && occurrence !== "last" && (!Number.isInteger(occurrence) || occurrence < 0)) {
|
|
4404
|
+
throw new BitfabError(
|
|
4405
|
+
'occurrence must be "first", "last", or a non-negative integer'
|
|
4406
|
+
);
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
return this.httpClient.getTraceSpan(traceId, lookup);
|
|
4410
|
+
}
|
|
4200
4411
|
/**
|
|
4201
4412
|
* Get a function wrapper for a specific trace function key.
|
|
4202
4413
|
*
|
|
@@ -4255,6 +4466,7 @@ var Bitfab = class {
|
|
|
4255
4466
|
};
|
|
4256
4467
|
}
|
|
4257
4468
|
return this.httpClient.sendExternalTrace({
|
|
4469
|
+
id: params.traceId,
|
|
4258
4470
|
type: "sdk-function",
|
|
4259
4471
|
source: "typescript-sdk-function",
|
|
4260
4472
|
traceFunctionKey: params.traceFunctionKey,
|
|
@@ -4310,6 +4522,8 @@ var Bitfab = class {
|
|
|
4310
4522
|
externalSpan.input_source_span_id = params.inputSourceSpanId;
|
|
4311
4523
|
}
|
|
4312
4524
|
return this.httpClient.sendExternalSpan({
|
|
4525
|
+
id: params.spanId,
|
|
4526
|
+
traceId: params.traceId,
|
|
4313
4527
|
type: "sdk-function",
|
|
4314
4528
|
source: "typescript-sdk-function",
|
|
4315
4529
|
sourceTraceId: params.traceId,
|
|
@@ -4319,26 +4533,14 @@ var Bitfab = class {
|
|
|
4319
4533
|
...params.mocked && { mocked: true }
|
|
4320
4534
|
});
|
|
4321
4535
|
}
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
* invocation records a trace tied to the test run. The plain-callable form
|
|
4331
|
-
* is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
|
|
4332
|
-
* SDK) replay - those record traces under a key with no `withSpan`-wrapped
|
|
4333
|
-
* root in the app.
|
|
4334
|
-
*
|
|
4335
|
-
* @param traceFunctionKey - The trace function key to replay
|
|
4336
|
-
* @param fn - The function to run recorded inputs through
|
|
4337
|
-
* @param options - Optional replay options. When `traceIds` is passed,
|
|
4338
|
-
* `limit` is ignored (with a warning): an explicit ID list already
|
|
4339
|
-
* determines how many traces replay.
|
|
4340
|
-
* @returns ReplayResult with items, testRunId, and testRunUrl
|
|
4341
|
-
*/
|
|
4536
|
+
registerMockOverride(overrideOrMatch, value) {
|
|
4537
|
+
const override = typeof overrideOrMatch === "function" ? { match: overrideOrMatch, value } : overrideOrMatch;
|
|
4538
|
+
this.mockOverrides.push(override);
|
|
4539
|
+
}
|
|
4540
|
+
/** Remove all overrides registered via {@link registerMockOverride}. */
|
|
4541
|
+
clearMockOverrides() {
|
|
4542
|
+
this.mockOverrides.length = 0;
|
|
4543
|
+
}
|
|
4342
4544
|
async replay(traceFunctionKey, fn, options) {
|
|
4343
4545
|
const wrappedKey = fn._bitfabTraceFunctionKey;
|
|
4344
4546
|
let replayFn = fn;
|
|
@@ -4359,7 +4561,8 @@ var Bitfab = class {
|
|
|
4359
4561
|
this.serviceUrl,
|
|
4360
4562
|
traceFunctionKey,
|
|
4361
4563
|
replayFn,
|
|
4362
|
-
options
|
|
4564
|
+
options,
|
|
4565
|
+
this.mockOverrides
|
|
4363
4566
|
);
|
|
4364
4567
|
}
|
|
4365
4568
|
};
|