bitfab 0.9.2 → 0.11.4

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/node.cjs CHANGED
@@ -81,7 +81,7 @@ var __version__;
81
81
  var init_version_generated = __esm({
82
82
  "src/version.generated.ts"() {
83
83
  "use strict";
84
- __version__ = "0.9.2";
84
+ __version__ = "0.11.4";
85
85
  }
86
86
  });
87
87
 
@@ -441,7 +441,15 @@ async function processItem(httpClient, serverItem, fn, testRunId) {
441
441
  } catch (e) {
442
442
  error = e instanceof Error ? e.message : String(e);
443
443
  }
444
- return { input: inputs, result, originalOutput, error };
444
+ return {
445
+ input: inputs,
446
+ result,
447
+ originalOutput,
448
+ error,
449
+ durationMs: serverItem.durationMs ?? null,
450
+ tokens: serverItem.tokens ?? null,
451
+ model: serverItem.model ?? null
452
+ };
445
453
  }
446
454
  async function mapWithConcurrency(tasks, maxConcurrency) {
447
455
  const results = new Array(tasks.length);
@@ -503,8 +511,10 @@ var init_replay = __esm({
503
511
  var node_exports = {};
504
512
  __export(node_exports, {
505
513
  Bitfab: () => Bitfab,
514
+ BitfabClaudeAgentHandler: () => BitfabClaudeAgentHandler,
506
515
  BitfabError: () => BitfabError,
507
516
  BitfabFunction: () => BitfabFunction,
517
+ BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
508
518
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
509
519
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
510
520
  __version__: () => __version__,
@@ -521,6 +531,482 @@ registerAsyncLocalStorageClass(
521
531
  import_node_async_hooks.AsyncLocalStorage
522
532
  );
523
533
 
534
+ // src/claudeAgentSdk.ts
535
+ init_constants();
536
+ init_http();
537
+ function nowIso() {
538
+ return (/* @__PURE__ */ new Date()).toISOString();
539
+ }
540
+ function safeSerialize(value) {
541
+ if (value === null || value === void 0) {
542
+ return value;
543
+ }
544
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
545
+ return value;
546
+ }
547
+ if (Array.isArray(value)) {
548
+ return value.map(safeSerialize);
549
+ }
550
+ if (typeof value === "object") {
551
+ if (typeof value.toJSON === "function") {
552
+ return value.toJSON();
553
+ }
554
+ const result = {};
555
+ for (const [k, v] of Object.entries(value)) {
556
+ if (!k.startsWith("_")) {
557
+ result[k] = safeSerialize(v);
558
+ }
559
+ }
560
+ return result;
561
+ }
562
+ return String(value);
563
+ }
564
+ function extractContentBlocks(content) {
565
+ if (!Array.isArray(content)) {
566
+ return [];
567
+ }
568
+ return content.map((block) => safeSerialize(block));
569
+ }
570
+ function extractUsage(message) {
571
+ const usageInfo = {};
572
+ const usage = message.usage;
573
+ if (!usage) {
574
+ return usageInfo;
575
+ }
576
+ const mapping = {
577
+ input_tokens: "inputTokens",
578
+ output_tokens: "outputTokens",
579
+ cache_read_input_tokens: "cacheReadTokens",
580
+ cache_creation_input_tokens: "cacheCreationTokens"
581
+ };
582
+ for (const [srcKey, dstKey] of Object.entries(mapping)) {
583
+ const val = usage[srcKey];
584
+ if (val !== void 0 && val !== null) {
585
+ usageInfo[dstKey] = val;
586
+ }
587
+ }
588
+ return usageInfo;
589
+ }
590
+ var BitfabClaudeAgentHandler = class {
591
+ constructor(config) {
592
+ // Span tracking
593
+ this.runToSpan = /* @__PURE__ */ new Map();
594
+ this.traceId = null;
595
+ this.rootSpanId = null;
596
+ this.activeContext = null;
597
+ this.traceStartedAt = null;
598
+ // LLM turn tracking
599
+ this.conversationHistory = [];
600
+ this.pendingMessages = [];
601
+ this.currentLlmSpanId = null;
602
+ this.currentLlmMessageId = null;
603
+ this.currentLlmContent = [];
604
+ this.currentLlmModel = null;
605
+ this.currentLlmUsage = {};
606
+ this.currentLlmStartedAt = null;
607
+ this.currentLlmHistorySnapshot = [];
608
+ // Subagent tracking
609
+ this.activeSubagentSpans = /* @__PURE__ */ new Map();
610
+ this.httpClient = new HttpClient({
611
+ apiKey: config.apiKey,
612
+ serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
613
+ timeout: config.timeout ?? 1e4
614
+ });
615
+ this.traceFunctionKey = config.traceFunctionKey;
616
+ this.getActiveSpanContext = config.getActiveSpanContext ?? null;
617
+ this.preToolUseHook = this.preToolUseHook.bind(this);
618
+ this.postToolUseHook = this.postToolUseHook.bind(this);
619
+ this.postToolUseFailureHook = this.postToolUseFailureHook.bind(this);
620
+ this.subagentStartHook = this.subagentStartHook.bind(this);
621
+ this.subagentStopHook = this.subagentStopHook.bind(this);
622
+ }
623
+ // ── trace lifecycle ──────────────────────────────────────────
624
+ ensureTrace() {
625
+ if (this.traceId !== null) {
626
+ return this.traceId;
627
+ }
628
+ this.activeContext = this.getActiveSpanContext?.() ?? null;
629
+ if (this.activeContext) {
630
+ this.traceId = this.activeContext.traceId;
631
+ } else {
632
+ this.traceId = crypto.randomUUID();
633
+ }
634
+ this.traceStartedAt = nowIso();
635
+ return this.traceId;
636
+ }
637
+ getParentId(agentId) {
638
+ if (agentId) {
639
+ const subagentSpanId = this.activeSubagentSpans.get(agentId);
640
+ if (subagentSpanId) {
641
+ return subagentSpanId;
642
+ }
643
+ }
644
+ return this.activeContext?.spanId ?? this.rootSpanId ?? null;
645
+ }
646
+ // ── span helpers ─────────────────────────────────────────────
647
+ startSpan(spanId, name, spanType, inputData, parentId) {
648
+ const traceId = this.ensureTrace();
649
+ const spanInfo = {
650
+ spanId,
651
+ traceId,
652
+ parentId: parentId ?? null,
653
+ startedAt: nowIso(),
654
+ name,
655
+ type: spanType,
656
+ input: safeSerialize(inputData),
657
+ contexts: []
658
+ };
659
+ this.runToSpan.set(spanId, spanInfo);
660
+ return spanInfo;
661
+ }
662
+ completeSpan(spanId, output, error, extraContexts) {
663
+ const spanInfo = this.runToSpan.get(spanId);
664
+ if (!spanInfo) {
665
+ return;
666
+ }
667
+ this.runToSpan.delete(spanId);
668
+ spanInfo.endedAt = nowIso();
669
+ spanInfo.output = safeSerialize(output);
670
+ if (error !== void 0) {
671
+ spanInfo.error = error;
672
+ }
673
+ if (extraContexts) {
674
+ spanInfo.contexts.push(extraContexts);
675
+ }
676
+ this.sendSpan(spanInfo);
677
+ }
678
+ sendSpan(spanInfo) {
679
+ const spanData = {
680
+ name: spanInfo.name,
681
+ type: spanInfo.type
682
+ };
683
+ if (spanInfo.input !== void 0) {
684
+ spanData.input = spanInfo.input;
685
+ }
686
+ if (spanInfo.output !== void 0) {
687
+ spanData.output = spanInfo.output;
688
+ }
689
+ if (spanInfo.error !== void 0) {
690
+ spanData.error = spanInfo.error;
691
+ }
692
+ if (spanInfo.contexts.length > 0) {
693
+ spanData.contexts = spanInfo.contexts;
694
+ }
695
+ const rawSpan = {
696
+ id: spanInfo.spanId,
697
+ trace_id: spanInfo.traceId,
698
+ started_at: spanInfo.startedAt,
699
+ ended_at: spanInfo.endedAt ?? nowIso(),
700
+ span_data: spanData
701
+ };
702
+ if (spanInfo.parentId !== null) {
703
+ rawSpan.parent_id = spanInfo.parentId;
704
+ }
705
+ const payload = {
706
+ type: "sdk-function",
707
+ source: "typescript-sdk-claude-agent-sdk",
708
+ traceFunctionKey: this.traceFunctionKey,
709
+ sourceTraceId: spanInfo.traceId,
710
+ rawSpan
711
+ };
712
+ try {
713
+ this.httpClient.sendExternalSpan(payload);
714
+ } catch {
715
+ }
716
+ }
717
+ sendTraceCompletion(endedAt, metadata) {
718
+ if (this.traceId === null) {
719
+ return;
720
+ }
721
+ const completed = this.activeContext === null;
722
+ const traceId = this.traceId;
723
+ this.traceId = null;
724
+ const externalTrace = {
725
+ id: traceId,
726
+ started_at: this.traceStartedAt ?? nowIso(),
727
+ ended_at: endedAt ?? nowIso(),
728
+ workflow_name: this.traceFunctionKey
729
+ };
730
+ if (metadata) {
731
+ externalTrace.metadata = metadata;
732
+ }
733
+ const traceData = {
734
+ type: "sdk-function",
735
+ source: "typescript-sdk-claude-agent-sdk",
736
+ traceFunctionKey: this.traceFunctionKey,
737
+ externalTrace,
738
+ completed
739
+ };
740
+ try {
741
+ this.httpClient.sendExternalTrace(traceData);
742
+ } catch {
743
+ }
744
+ }
745
+ // ── hook callbacks ───────────────────────────────────────────
746
+ async preToolUseHook(inputData, toolUseId, _context) {
747
+ try {
748
+ const sid = inputData.tool_use_id ?? toolUseId ?? crypto.randomUUID();
749
+ const toolName = inputData.tool_name ?? "tool";
750
+ const toolInput = inputData.tool_input ?? {};
751
+ const agentId = inputData.agent_id;
752
+ const parentId = this.getParentId(agentId);
753
+ this.startSpan(sid, toolName, "function", toolInput, parentId);
754
+ } catch {
755
+ }
756
+ return {};
757
+ }
758
+ async postToolUseHook(inputData, toolUseId, _context) {
759
+ try {
760
+ const sid = inputData.tool_use_id ?? toolUseId ?? "";
761
+ const toolResponse = inputData.tool_response;
762
+ this.completeSpan(sid, toolResponse);
763
+ } catch {
764
+ }
765
+ return {};
766
+ }
767
+ async postToolUseFailureHook(inputData, toolUseId, _context) {
768
+ try {
769
+ const sid = inputData.tool_use_id ?? toolUseId ?? "";
770
+ const error = String(inputData.error ?? "Unknown error");
771
+ this.completeSpan(sid, void 0, error);
772
+ } catch {
773
+ }
774
+ return {};
775
+ }
776
+ async subagentStartHook(inputData, _toolUseId, _context) {
777
+ try {
778
+ const agentId = inputData.agent_id ?? crypto.randomUUID();
779
+ const agentType = inputData.agent_type ?? "subagent";
780
+ const parentId = this.getParentId();
781
+ const spanId = crypto.randomUUID();
782
+ this.activeSubagentSpans.set(agentId, spanId);
783
+ this.startSpan(
784
+ spanId,
785
+ `Agent: ${agentType}`,
786
+ "agent",
787
+ void 0,
788
+ parentId
789
+ );
790
+ } catch {
791
+ }
792
+ return {};
793
+ }
794
+ async subagentStopHook(inputData, _toolUseId, _context) {
795
+ try {
796
+ const agentId = inputData.agent_id ?? "";
797
+ const spanId = this.activeSubagentSpans.get(agentId);
798
+ if (spanId) {
799
+ this.activeSubagentSpans.delete(agentId);
800
+ this.completeSpan(spanId);
801
+ }
802
+ } catch {
803
+ }
804
+ return {};
805
+ }
806
+ // ── public API ───────────────────────────────────────────────
807
+ /**
808
+ * Inject Bitfab tracing hooks into Claude Agent SDK options.
809
+ *
810
+ * Modifies the options object and returns it for convenience.
811
+ * The SDK's `HookMatcher` is constructed as a plain object
812
+ * (`{ matcher: null, hooks: [callback] }`) to avoid requiring
813
+ * `@anthropic-ai/claude-agent-sdk` as a dependency.
814
+ *
815
+ * @param options - Options object with a `hooks` property
816
+ * @returns The modified options object with Bitfab hooks injected
817
+ */
818
+ instrumentOptions(options) {
819
+ const hooks = options.hooks ?? {};
820
+ if (!options.hooks) {
821
+ ;
822
+ options.hooks = hooks;
823
+ }
824
+ const hookConfig = [
825
+ ["PreToolUse", this.preToolUseHook],
826
+ ["PostToolUse", this.postToolUseHook],
827
+ ["PostToolUseFailure", this.postToolUseFailureHook],
828
+ ["SubagentStart", this.subagentStartHook],
829
+ ["SubagentStop", this.subagentStopHook]
830
+ ];
831
+ for (const [event, callback] of hookConfig) {
832
+ if (!hooks[event]) {
833
+ hooks[event] = [];
834
+ }
835
+ hooks[event].push({ matcher: null, hooks: [callback] });
836
+ }
837
+ return options;
838
+ }
839
+ /**
840
+ * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
841
+ *
842
+ * Yields every message unchanged while capturing AssistantMessage
843
+ * content as LLM turn spans.
844
+ */
845
+ async *wrapResponse(stream) {
846
+ yield* this.processStream(stream);
847
+ }
848
+ /**
849
+ * Wrap a `query()` async iterator to capture LLM turns.
850
+ *
851
+ * Same as `wrapResponse` but for the simpler `query()` API
852
+ * which does not support hooks (no tool/subagent spans).
853
+ */
854
+ async *wrapQuery(stream) {
855
+ yield* this.processStream(stream);
856
+ }
857
+ // ── stream processing ────────────────────────────────────────
858
+ async *processStream(stream) {
859
+ try {
860
+ for await (const message of stream) {
861
+ try {
862
+ this.processMessage(message);
863
+ } catch {
864
+ }
865
+ yield message;
866
+ }
867
+ } finally {
868
+ try {
869
+ this.flushLlmTurn();
870
+ this.sendTraceCompletion();
871
+ } catch {
872
+ }
873
+ this.resetState();
874
+ }
875
+ }
876
+ processMessage(message) {
877
+ const typeName = message.constructor?.name ?? "";
878
+ if (typeName === "AssistantMessage") {
879
+ this.handleAssistantMessage(message);
880
+ } else if (typeName === "UserMessage") {
881
+ this.handleUserMessage(message);
882
+ } else if (typeName === "ResultMessage") {
883
+ this.handleResultMessage(message);
884
+ }
885
+ }
886
+ handleAssistantMessage(message) {
887
+ this.ensureTrace();
888
+ const messageId = message.message_id;
889
+ if (messageId !== this.currentLlmMessageId) {
890
+ this.flushLlmTurn();
891
+ this.conversationHistory.push(...this.pendingMessages);
892
+ this.pendingMessages = [];
893
+ this.currentLlmSpanId = crypto.randomUUID();
894
+ this.currentLlmMessageId = messageId ?? null;
895
+ this.currentLlmContent = [];
896
+ this.currentLlmModel = message.model ?? null;
897
+ this.currentLlmUsage = {};
898
+ this.currentLlmStartedAt = nowIso();
899
+ this.currentLlmHistorySnapshot = [...this.conversationHistory];
900
+ }
901
+ const content = message.content;
902
+ if (Array.isArray(content)) {
903
+ this.currentLlmContent.push(...extractContentBlocks(content));
904
+ }
905
+ const usage = extractUsage(message);
906
+ if (Object.keys(usage).length > 0) {
907
+ Object.assign(this.currentLlmUsage, usage);
908
+ }
909
+ const model = message.model;
910
+ if (model) {
911
+ this.currentLlmModel = model;
912
+ }
913
+ }
914
+ handleUserMessage(message) {
915
+ const content = message.content;
916
+ const toolUseResult = message.tool_use_result;
917
+ if (toolUseResult !== void 0) {
918
+ this.pendingMessages.push({
919
+ role: "tool",
920
+ content: safeSerialize(content),
921
+ tool_result: safeSerialize(toolUseResult)
922
+ });
923
+ } else {
924
+ this.pendingMessages.push({
925
+ role: "user",
926
+ content: safeSerialize(content)
927
+ });
928
+ }
929
+ }
930
+ handleResultMessage(message) {
931
+ this.flushLlmTurn();
932
+ const metadata = {};
933
+ for (const attr of [
934
+ "num_turns",
935
+ "total_cost_usd",
936
+ "duration_ms",
937
+ "duration_api_ms",
938
+ "session_id"
939
+ ]) {
940
+ const val = message[attr];
941
+ if (val !== void 0 && val !== null) {
942
+ metadata[attr] = val;
943
+ }
944
+ }
945
+ const usage = message.usage;
946
+ if (usage && typeof usage === "object") {
947
+ metadata.usage = safeSerialize(usage);
948
+ }
949
+ this.sendTraceCompletion(
950
+ void 0,
951
+ Object.keys(metadata).length > 0 ? metadata : void 0
952
+ );
953
+ }
954
+ flushLlmTurn() {
955
+ if (this.currentLlmSpanId === null) {
956
+ return;
957
+ }
958
+ const spanId = this.currentLlmSpanId;
959
+ const traceId = this.ensureTrace();
960
+ const parentId = this.getParentId();
961
+ const llmContext = {};
962
+ if (this.currentLlmModel) {
963
+ llmContext.model = this.currentLlmModel;
964
+ }
965
+ Object.assign(llmContext, this.currentLlmUsage);
966
+ const spanInfo = {
967
+ spanId,
968
+ traceId,
969
+ parentId,
970
+ startedAt: this.currentLlmStartedAt ?? nowIso(),
971
+ endedAt: nowIso(),
972
+ name: this.currentLlmModel ?? "llm",
973
+ type: "llm",
974
+ input: this.currentLlmHistorySnapshot,
975
+ output: this.currentLlmContent,
976
+ contexts: Object.keys(llmContext).length > 0 ? [llmContext] : []
977
+ };
978
+ this.sendSpan(spanInfo);
979
+ this.conversationHistory.push({
980
+ role: "assistant",
981
+ content: this.currentLlmContent
982
+ });
983
+ this.currentLlmSpanId = null;
984
+ this.currentLlmMessageId = null;
985
+ this.currentLlmContent = [];
986
+ this.currentLlmModel = null;
987
+ this.currentLlmUsage = {};
988
+ this.currentLlmStartedAt = null;
989
+ this.currentLlmHistorySnapshot = [];
990
+ }
991
+ resetState() {
992
+ this.runToSpan.clear();
993
+ this.traceId = null;
994
+ this.rootSpanId = null;
995
+ this.activeContext = null;
996
+ this.traceStartedAt = null;
997
+ this.conversationHistory = [];
998
+ this.pendingMessages = [];
999
+ this.currentLlmSpanId = null;
1000
+ this.currentLlmMessageId = null;
1001
+ this.currentLlmContent = [];
1002
+ this.currentLlmModel = null;
1003
+ this.currentLlmUsage = {};
1004
+ this.currentLlmStartedAt = null;
1005
+ this.currentLlmHistorySnapshot = [];
1006
+ this.activeSubagentSpans.clear();
1007
+ }
1008
+ };
1009
+
524
1010
  // src/client.ts
525
1011
  init_asyncStorage();
526
1012
 
@@ -815,6 +1301,484 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
815
1301
  // src/client.ts
816
1302
  init_constants();
817
1303
  init_http();
1304
+
1305
+ // src/langgraph.ts
1306
+ init_constants();
1307
+ init_http();
1308
+ var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
1309
+ var LANGGRAPH_METADATA_KEYS = [
1310
+ "langgraph_step",
1311
+ "langgraph_node",
1312
+ "langgraph_triggers",
1313
+ "langgraph_path",
1314
+ "langgraph_checkpoint_ns"
1315
+ ];
1316
+ function nowIso2() {
1317
+ return (/* @__PURE__ */ new Date()).toISOString();
1318
+ }
1319
+ var MAX_SERIALIZE_DEPTH = 6;
1320
+ function safeSerialize2(value) {
1321
+ return safeSerializeInner(value, 0, /* @__PURE__ */ new WeakSet());
1322
+ }
1323
+ function safeSerializeInner(value, depth, seen) {
1324
+ if (value === null || value === void 0) {
1325
+ return value;
1326
+ }
1327
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1328
+ return value;
1329
+ }
1330
+ const className = value?.constructor?.name ?? typeof value;
1331
+ if (depth > MAX_SERIALIZE_DEPTH) {
1332
+ return `<${className}>`;
1333
+ }
1334
+ if (typeof value === "object") {
1335
+ if (seen.has(value)) {
1336
+ return `<cycle ${className}>`;
1337
+ }
1338
+ seen.add(value);
1339
+ }
1340
+ if (Array.isArray(value)) {
1341
+ return value.map((item) => safeSerializeInner(item, depth + 1, seen));
1342
+ }
1343
+ if (typeof value === "object") {
1344
+ if (typeof value.toJSON === "function") {
1345
+ try {
1346
+ return value.toJSON();
1347
+ } catch {
1348
+ return `<${className}>`;
1349
+ }
1350
+ }
1351
+ try {
1352
+ const result = {};
1353
+ for (const [k, v] of Object.entries(value)) {
1354
+ if (!k.startsWith("_")) {
1355
+ result[k] = safeSerializeInner(v, depth + 1, seen);
1356
+ }
1357
+ }
1358
+ return result;
1359
+ } catch {
1360
+ return `<${className}>`;
1361
+ }
1362
+ }
1363
+ try {
1364
+ return String(value);
1365
+ } catch {
1366
+ return `<${className}>`;
1367
+ }
1368
+ }
1369
+ function convertMessage(message) {
1370
+ if (typeof message !== "object" || message === null) {
1371
+ return { role: "unknown", content: String(message) };
1372
+ }
1373
+ const msg = message;
1374
+ if (typeof msg.toDict === "function") {
1375
+ return msg.toDict();
1376
+ }
1377
+ const typeToRole = {
1378
+ human: "user",
1379
+ ai: "assistant",
1380
+ system: "system",
1381
+ tool: "tool",
1382
+ function: "function"
1383
+ };
1384
+ const result = {};
1385
+ const msgType = msg._getType ? String(msg._getType()) : msg.type;
1386
+ result.role = (msgType ? typeToRole[msgType] : void 0) ?? msg.role ?? "unknown";
1387
+ result.content = msg.content ?? "";
1388
+ if (msg.tool_calls) {
1389
+ result.tool_calls = msg.tool_calls;
1390
+ }
1391
+ if (msg.tool_call_id) {
1392
+ result.tool_call_id = msg.tool_call_id;
1393
+ }
1394
+ if (msg.name) {
1395
+ result.name = msg.name;
1396
+ }
1397
+ return result;
1398
+ }
1399
+ function extractModelName(serialized, metadata) {
1400
+ if (serialized) {
1401
+ const kwargs = serialized.kwargs;
1402
+ if (kwargs) {
1403
+ const model = kwargs.model_name ?? kwargs.model ?? kwargs.model_id;
1404
+ if (model) {
1405
+ return String(model);
1406
+ }
1407
+ }
1408
+ }
1409
+ if (metadata) {
1410
+ const lsModel = metadata.ls_model_name;
1411
+ if (lsModel) {
1412
+ return String(lsModel);
1413
+ }
1414
+ }
1415
+ return void 0;
1416
+ }
1417
+ function extractUsage2(output) {
1418
+ const usage = {};
1419
+ const llmOutput = output.llmOutput;
1420
+ const tokenUsage = llmOutput?.tokenUsage ?? llmOutput?.token_usage ?? llmOutput?.usage ?? {};
1421
+ const inputTokens = tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? tokenUsage.input_tokens;
1422
+ const outputTokens = tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? tokenUsage.output_tokens;
1423
+ const totalTokens = tokenUsage.totalTokens ?? tokenUsage.total_tokens;
1424
+ if (inputTokens !== void 0 && inputTokens !== null) {
1425
+ usage.inputTokens = inputTokens;
1426
+ }
1427
+ if (outputTokens !== void 0 && outputTokens !== null) {
1428
+ usage.outputTokens = outputTokens;
1429
+ }
1430
+ if (totalTokens !== void 0 && totalTokens !== null) {
1431
+ usage.totalTokens = totalTokens;
1432
+ }
1433
+ return usage;
1434
+ }
1435
+ function extractLangGraphMetadata(metadata) {
1436
+ if (!metadata) {
1437
+ return {};
1438
+ }
1439
+ const result = {};
1440
+ for (const key of LANGGRAPH_METADATA_KEYS) {
1441
+ if (key in metadata) {
1442
+ result[key] = metadata[key];
1443
+ }
1444
+ }
1445
+ return result;
1446
+ }
1447
+ var BitfabLangGraphCallbackHandler = class {
1448
+ constructor(config) {
1449
+ this.name = "BitfabLangGraphCallbackHandler";
1450
+ this.ignoreRetriever = true;
1451
+ this.ignoreRetry = true;
1452
+ this.ignoreCustomEvent = true;
1453
+ this.runToSpan = /* @__PURE__ */ new Map();
1454
+ this.invocations = /* @__PURE__ */ new Map();
1455
+ this.httpClient = new HttpClient({
1456
+ apiKey: config.apiKey,
1457
+ serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
1458
+ timeout: config.timeout ?? 1e4
1459
+ });
1460
+ this.traceFunctionKey = config.traceFunctionKey;
1461
+ this.getActiveSpanContext = config.getActiveSpanContext ?? null;
1462
+ }
1463
+ // ── lifecycle helpers ──────────────────────────────────────────
1464
+ startSpan(runId, parentRunId, name, spanType, inputData, metadata, tags) {
1465
+ const parentSpan = parentRunId ? this.runToSpan.get(parentRunId) : void 0;
1466
+ const willHide = tags?.includes(LANGSMITH_HIDDEN_TAG) === true;
1467
+ let invocation;
1468
+ let effectiveParentId;
1469
+ if (parentSpan) {
1470
+ const existing = this.invocations.get(parentSpan.rootRunId);
1471
+ if (existing) {
1472
+ invocation = existing;
1473
+ } else {
1474
+ invocation = {
1475
+ traceId: parentSpan.traceId,
1476
+ activeContext: null,
1477
+ rootRunId: parentSpan.rootRunId
1478
+ };
1479
+ this.invocations.set(invocation.rootRunId, invocation);
1480
+ }
1481
+ if (!willHide) {
1482
+ let resolved = parentSpan;
1483
+ while (resolved?.hidden === true) {
1484
+ resolved = resolved.parentId ? this.runToSpan.get(resolved.parentId) : void 0;
1485
+ }
1486
+ effectiveParentId = resolved ? resolved.spanId : invocation.activeContext?.spanId ?? null;
1487
+ } else {
1488
+ effectiveParentId = parentRunId ?? null;
1489
+ }
1490
+ } else {
1491
+ const activeContext = this.getActiveSpanContext?.() ?? null;
1492
+ invocation = {
1493
+ traceId: activeContext ? activeContext.traceId : crypto.randomUUID(),
1494
+ activeContext,
1495
+ rootRunId: runId
1496
+ };
1497
+ this.invocations.set(runId, invocation);
1498
+ effectiveParentId = activeContext?.spanId ?? null;
1499
+ }
1500
+ const lgMetadata = extractLangGraphMetadata(metadata);
1501
+ const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
1502
+ const spanInfo = {
1503
+ spanId: runId,
1504
+ traceId: invocation.traceId,
1505
+ rootRunId: invocation.rootRunId,
1506
+ parentId: effectiveParentId,
1507
+ startedAt: nowIso2(),
1508
+ name,
1509
+ type: spanType,
1510
+ input: safeSerialize2(inputData),
1511
+ contexts
1512
+ };
1513
+ if (willHide) {
1514
+ spanInfo.hidden = true;
1515
+ }
1516
+ this.runToSpan.set(runId, spanInfo);
1517
+ return spanInfo;
1518
+ }
1519
+ completeSpan(runId, output, error, extraContexts) {
1520
+ const spanInfo = this.runToSpan.get(runId);
1521
+ if (!spanInfo) {
1522
+ return;
1523
+ }
1524
+ this.runToSpan.delete(runId);
1525
+ spanInfo.endedAt = nowIso2();
1526
+ spanInfo.output = safeSerialize2(output);
1527
+ if (error !== void 0) {
1528
+ spanInfo.error = error;
1529
+ }
1530
+ if (extraContexts && Object.keys(extraContexts).length > 0) {
1531
+ spanInfo.contexts.push(extraContexts);
1532
+ }
1533
+ this.sendSpan(spanInfo);
1534
+ if (runId === spanInfo.rootRunId) {
1535
+ const invocation = this.invocations.get(runId);
1536
+ this.sendTraceCompletion(spanInfo, invocation?.activeContext ?? null);
1537
+ this.invocations.delete(runId);
1538
+ }
1539
+ }
1540
+ sendSpan(spanInfo) {
1541
+ const spanData = {
1542
+ name: spanInfo.name,
1543
+ type: spanInfo.type
1544
+ };
1545
+ if (spanInfo.input !== void 0) {
1546
+ spanData.input = spanInfo.input;
1547
+ }
1548
+ if (spanInfo.output !== void 0) {
1549
+ spanData.output = spanInfo.output;
1550
+ }
1551
+ if (spanInfo.error !== void 0) {
1552
+ spanData.error = spanInfo.error;
1553
+ }
1554
+ if (spanInfo.contexts.length > 0) {
1555
+ spanData.contexts = spanInfo.contexts;
1556
+ }
1557
+ if (spanInfo.hidden) {
1558
+ spanData.hidden = true;
1559
+ }
1560
+ const rawSpan = {
1561
+ id: spanInfo.spanId,
1562
+ trace_id: spanInfo.traceId,
1563
+ started_at: spanInfo.startedAt,
1564
+ ended_at: spanInfo.endedAt ?? nowIso2(),
1565
+ span_data: spanData
1566
+ };
1567
+ if (spanInfo.parentId !== null) {
1568
+ rawSpan.parent_id = spanInfo.parentId;
1569
+ }
1570
+ const payload = {
1571
+ type: "sdk-function",
1572
+ source: "typescript-sdk-langgraph",
1573
+ traceFunctionKey: this.traceFunctionKey,
1574
+ sourceTraceId: spanInfo.traceId,
1575
+ rawSpan
1576
+ };
1577
+ try {
1578
+ this.httpClient.sendExternalSpan(payload);
1579
+ } catch {
1580
+ }
1581
+ }
1582
+ sendTraceCompletion(rootSpan, activeContext) {
1583
+ const completed = activeContext === null;
1584
+ const traceData = {
1585
+ type: "sdk-function",
1586
+ source: "typescript-sdk-langgraph",
1587
+ traceFunctionKey: this.traceFunctionKey,
1588
+ externalTrace: {
1589
+ id: rootSpan.traceId,
1590
+ started_at: rootSpan.startedAt,
1591
+ ended_at: rootSpan.endedAt ?? nowIso2(),
1592
+ workflow_name: this.traceFunctionKey
1593
+ },
1594
+ completed
1595
+ };
1596
+ try {
1597
+ this.httpClient.sendExternalTrace(traceData);
1598
+ } catch {
1599
+ }
1600
+ }
1601
+ // ── chain callbacks (graph nodes) ─────────────────────────────
1602
+ async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
1603
+ try {
1604
+ const idArr = chain.id;
1605
+ const name = chain.name ?? idArr?.[idArr.length - 1] ?? "chain";
1606
+ this.startSpan(
1607
+ runId,
1608
+ parentRunId,
1609
+ String(name),
1610
+ "agent",
1611
+ inputs,
1612
+ metadata,
1613
+ tags
1614
+ );
1615
+ } catch {
1616
+ }
1617
+ }
1618
+ async handleChainEnd(outputs, runId) {
1619
+ try {
1620
+ this.completeSpan(runId, outputs);
1621
+ } catch {
1622
+ }
1623
+ }
1624
+ async handleChainError(error, runId) {
1625
+ try {
1626
+ const errorObj = error;
1627
+ if (errorObj?.constructor?.name === "GraphBubbleUp") {
1628
+ this.completeSpan(runId, void 0, void 0);
1629
+ return;
1630
+ }
1631
+ this.completeSpan(
1632
+ runId,
1633
+ void 0,
1634
+ error instanceof Error ? error.message : String(error)
1635
+ );
1636
+ } catch {
1637
+ }
1638
+ }
1639
+ // ── LLM callbacks ─────────────────────────────────────────────
1640
+ async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata) {
1641
+ try {
1642
+ const model = extractModelName(llm, metadata);
1643
+ const idArr = llm.id;
1644
+ const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
1645
+ const converted = messages.map((batch) => batch.map(convertMessage));
1646
+ const spanInfo = this.startSpan(
1647
+ runId,
1648
+ parentRunId,
1649
+ String(name),
1650
+ "llm",
1651
+ converted,
1652
+ metadata,
1653
+ tags
1654
+ );
1655
+ spanInfo.model = model;
1656
+ } catch {
1657
+ }
1658
+ }
1659
+ async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata) {
1660
+ try {
1661
+ const model = extractModelName(llm, metadata);
1662
+ const idArr = llm.id;
1663
+ const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
1664
+ const spanInfo = this.startSpan(
1665
+ runId,
1666
+ parentRunId,
1667
+ String(name),
1668
+ "llm",
1669
+ prompts,
1670
+ metadata,
1671
+ tags
1672
+ );
1673
+ spanInfo.model = model;
1674
+ } catch {
1675
+ }
1676
+ }
1677
+ async handleLLMEnd(output, runId) {
1678
+ try {
1679
+ let llmOutput;
1680
+ const generations = output.generations;
1681
+ if (generations?.length && generations[generations.length - 1]?.length) {
1682
+ const gen = generations[generations.length - 1][generations[generations.length - 1].length - 1];
1683
+ const msg = gen.message;
1684
+ llmOutput = msg ? convertMessage(msg) : gen.text ?? String(gen);
1685
+ }
1686
+ const usage = extractUsage2(output);
1687
+ const spanInfo = this.runToSpan.get(runId);
1688
+ const model = spanInfo?.model;
1689
+ const llmContext = {};
1690
+ if (model) {
1691
+ llmContext.model = model;
1692
+ }
1693
+ Object.assign(llmContext, usage);
1694
+ this.completeSpan(
1695
+ runId,
1696
+ llmOutput,
1697
+ void 0,
1698
+ Object.keys(llmContext).length > 0 ? llmContext : void 0
1699
+ );
1700
+ } catch {
1701
+ }
1702
+ }
1703
+ async handleLLMError(error, runId) {
1704
+ try {
1705
+ this.completeSpan(
1706
+ runId,
1707
+ void 0,
1708
+ error instanceof Error ? error.message : String(error)
1709
+ );
1710
+ } catch {
1711
+ }
1712
+ }
1713
+ async handleLLMNewToken() {
1714
+ }
1715
+ // ── tool callbacks ────────────────────────────────────────────
1716
+ async handleToolStart(tool, input, runId, parentRunId, tags, metadata) {
1717
+ try {
1718
+ const name = tool.name ?? "tool";
1719
+ this.startSpan(
1720
+ runId,
1721
+ parentRunId,
1722
+ String(name),
1723
+ "function",
1724
+ input,
1725
+ metadata,
1726
+ tags
1727
+ );
1728
+ } catch {
1729
+ }
1730
+ }
1731
+ async handleToolEnd(output, runId) {
1732
+ try {
1733
+ this.completeSpan(runId, output);
1734
+ } catch {
1735
+ }
1736
+ }
1737
+ async handleToolError(error, runId) {
1738
+ try {
1739
+ this.completeSpan(
1740
+ runId,
1741
+ void 0,
1742
+ error instanceof Error ? error.message : String(error)
1743
+ );
1744
+ } catch {
1745
+ }
1746
+ }
1747
+ // ── retriever callbacks ───────────────────────────────────────
1748
+ async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata) {
1749
+ try {
1750
+ const name = retriever.name ?? "retriever";
1751
+ this.startSpan(
1752
+ runId,
1753
+ parentRunId,
1754
+ String(name),
1755
+ "function",
1756
+ query,
1757
+ metadata,
1758
+ tags
1759
+ );
1760
+ } catch {
1761
+ }
1762
+ }
1763
+ async handleRetrieverEnd(documents, runId) {
1764
+ try {
1765
+ this.completeSpan(runId, documents);
1766
+ } catch {
1767
+ }
1768
+ }
1769
+ async handleRetrieverError(error, runId) {
1770
+ try {
1771
+ this.completeSpan(
1772
+ runId,
1773
+ void 0,
1774
+ error instanceof Error ? error.message : String(error)
1775
+ );
1776
+ } catch {
1777
+ }
1778
+ }
1779
+ };
1780
+
1781
+ // src/client.ts
818
1782
  init_replayContext();
819
1783
  init_serialize();
820
1784
 
@@ -1039,6 +2003,65 @@ function runWithSpanStack(stack, fn) {
1039
2003
  throw error;
1040
2004
  }
1041
2005
  }
2006
+ function isAsyncGenerator(value) {
2007
+ if (value === null || typeof value !== "object") {
2008
+ return false;
2009
+ }
2010
+ const candidate = value;
2011
+ return typeof candidate.next === "function" && typeof candidate.return === "function" && typeof candidate.throw === "function" && typeof candidate[Symbol.asyncIterator] === "function";
2012
+ }
2013
+ function wrapAsyncGenerator(source, spanStack, sendSpan) {
2014
+ const yielded = [];
2015
+ let returnValue;
2016
+ let finalized = false;
2017
+ const finalize = (errorMsg) => {
2018
+ if (finalized) {
2019
+ return;
2020
+ }
2021
+ finalized = true;
2022
+ void sendSpan({
2023
+ result: { yielded, return: returnValue },
2024
+ ...errorMsg && { error: errorMsg }
2025
+ });
2026
+ };
2027
+ const step = (method, arg) => runWithSpanStack(spanStack, () => {
2028
+ const op = source[method];
2029
+ return op.call(source, arg);
2030
+ });
2031
+ const handle = async (method, arg) => {
2032
+ try {
2033
+ const result = await step(method, arg);
2034
+ if (result.done) {
2035
+ returnValue = result.value;
2036
+ finalize();
2037
+ } else {
2038
+ yielded.push(result.value);
2039
+ }
2040
+ return result;
2041
+ } catch (error) {
2042
+ finalize(error instanceof Error ? error.message : String(error));
2043
+ throw error;
2044
+ }
2045
+ };
2046
+ const wrapped = {
2047
+ next(arg) {
2048
+ return handle("next", arg);
2049
+ },
2050
+ return(value) {
2051
+ return handle("return", value);
2052
+ },
2053
+ throw(err) {
2054
+ return handle("throw", err);
2055
+ },
2056
+ [Symbol.asyncIterator]() {
2057
+ return wrapped;
2058
+ },
2059
+ [Symbol.asyncDispose]() {
2060
+ return handle("return", void 0).then(() => void 0);
2061
+ }
2062
+ };
2063
+ return wrapped;
2064
+ }
1042
2065
  var cachedCollectorClass;
1043
2066
  async function loadCollectorClass() {
1044
2067
  if (cachedCollectorClass !== void 0) {
@@ -1328,6 +2351,67 @@ var Bitfab = class {
1328
2351
  }
1329
2352
  });
1330
2353
  }
2354
+ /**
2355
+ * Get a LangGraph/LangChain callback handler for tracing.
2356
+ *
2357
+ * The handler captures graph node execution, LLM calls, and tool
2358
+ * invocations as Bitfab spans with proper parent-child hierarchy.
2359
+ *
2360
+ * ```typescript
2361
+ * const handler = client.getLangGraphCallbackHandler("my-agent");
2362
+ * const result = await agent.invoke(
2363
+ * { messages: [...] },
2364
+ * { callbacks: [handler] },
2365
+ * );
2366
+ * ```
2367
+ *
2368
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
2369
+ * @returns A BitfabLangGraphCallbackHandler configured for this client
2370
+ */
2371
+ getLangGraphCallbackHandler(traceFunctionKey) {
2372
+ return new BitfabLangGraphCallbackHandler({
2373
+ apiKey: this.apiKey,
2374
+ traceFunctionKey,
2375
+ serviceUrl: this.serviceUrl,
2376
+ getActiveSpanContext: () => {
2377
+ const stack = getSpanStack();
2378
+ return stack[stack.length - 1] ?? null;
2379
+ }
2380
+ });
2381
+ }
2382
+ /**
2383
+ * Get a Claude Agent SDK handler for tracing.
2384
+ *
2385
+ * The handler captures LLM turns, tool invocations, and subagent
2386
+ * execution as Bitfab spans with proper parent-child hierarchy.
2387
+ *
2388
+ * ```typescript
2389
+ * const handler = client.getClaudeAgentHandler("my-agent");
2390
+ * const options = handler.instrumentOptions({
2391
+ * model: "claude-sonnet-4-5-...",
2392
+ * });
2393
+ * const sdkClient = new ClaudeSDKClient(options);
2394
+ * await sdkClient.connect();
2395
+ * await sdkClient.query("Do something");
2396
+ * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
2397
+ * // process messages
2398
+ * }
2399
+ * ```
2400
+ *
2401
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
2402
+ * @returns A BitfabClaudeAgentHandler configured for this client
2403
+ */
2404
+ getClaudeAgentHandler(traceFunctionKey) {
2405
+ return new BitfabClaudeAgentHandler({
2406
+ apiKey: this.apiKey,
2407
+ traceFunctionKey,
2408
+ serviceUrl: this.serviceUrl,
2409
+ getActiveSpanContext: () => {
2410
+ const stack = getSpanStack();
2411
+ return stack[stack.length - 1] ?? null;
2412
+ }
2413
+ });
2414
+ }
1331
2415
  /**
1332
2416
  * Wrap a BAML client method to automatically capture prompt and LLM metadata.
1333
2417
  *
@@ -1470,10 +2554,14 @@ var Bitfab = class {
1470
2554
  const inputs = args;
1471
2555
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1472
2556
  if (isRootSpan && !activeTraceStates.has(traceId)) {
2557
+ const replayCtxAtRoot = getReplayContext();
1473
2558
  activeTraceStates.set(traceId, {
1474
2559
  traceId,
1475
2560
  startedAt,
1476
- contexts: []
2561
+ contexts: [],
2562
+ ...replayCtxAtRoot?.testRunId && {
2563
+ testRunId: replayCtxAtRoot.testRunId
2564
+ }
1477
2565
  });
1478
2566
  pendingSpanPromises.set(traceId, []);
1479
2567
  }
@@ -1520,7 +2608,8 @@ var Bitfab = class {
1520
2608
  endedAt,
1521
2609
  sessionId: traceState?.sessionId,
1522
2610
  metadata: traceState?.metadata,
1523
- contexts: traceState?.contexts ?? []
2611
+ contexts: traceState?.contexts ?? [],
2612
+ testRunId: traceState?.testRunId
1524
2613
  });
1525
2614
  activeTraceStates.delete(traceId);
1526
2615
  } else {
@@ -1548,6 +2637,9 @@ var Bitfab = class {
1548
2637
  throw error;
1549
2638
  });
1550
2639
  }
2640
+ if (isAsyncGenerator(result)) {
2641
+ return wrapAsyncGenerator(result, newStack, sendSpan);
2642
+ }
1551
2643
  void sendSpan({ result });
1552
2644
  return result;
1553
2645
  };
@@ -1600,7 +2692,8 @@ var Bitfab = class {
1600
2692
  traceFunctionKey: params.traceFunctionKey,
1601
2693
  externalTrace: rawTrace,
1602
2694
  completed: true,
1603
- ...params.sessionId && { sessionId: params.sessionId }
2695
+ ...params.sessionId && { sessionId: params.sessionId },
2696
+ ...params.testRunId && { testRunId: params.testRunId }
1604
2697
  });
1605
2698
  }
1606
2699
  /**
@@ -1737,8 +2830,10 @@ assertAsyncStorageRegistered();
1737
2830
  // Annotate the CommonJS export names for ESM import in node:
1738
2831
  0 && (module.exports = {
1739
2832
  Bitfab,
2833
+ BitfabClaudeAgentHandler,
1740
2834
  BitfabError,
1741
2835
  BitfabFunction,
2836
+ BitfabLangGraphCallbackHandler,
1742
2837
  BitfabOpenAITracingProcessor,
1743
2838
  DEFAULT_SERVICE_URL,
1744
2839
  __version__,