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