bitfab 0.9.2 → 0.11.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/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.0";
85
85
  }
86
86
  });
87
87
 
@@ -503,8 +503,10 @@ var init_replay = __esm({
503
503
  var node_exports = {};
504
504
  __export(node_exports, {
505
505
  Bitfab: () => Bitfab,
506
+ BitfabClaudeAgentHandler: () => BitfabClaudeAgentHandler,
506
507
  BitfabError: () => BitfabError,
507
508
  BitfabFunction: () => BitfabFunction,
509
+ BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
508
510
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
509
511
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
510
512
  __version__: () => __version__,
@@ -521,6 +523,482 @@ registerAsyncLocalStorageClass(
521
523
  import_node_async_hooks.AsyncLocalStorage
522
524
  );
523
525
 
526
+ // src/claudeAgentSdk.ts
527
+ init_constants();
528
+ init_http();
529
+ function nowIso() {
530
+ return (/* @__PURE__ */ new Date()).toISOString();
531
+ }
532
+ function safeSerialize(value) {
533
+ if (value === null || value === void 0) {
534
+ return value;
535
+ }
536
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
537
+ return value;
538
+ }
539
+ if (Array.isArray(value)) {
540
+ return value.map(safeSerialize);
541
+ }
542
+ if (typeof value === "object") {
543
+ if (typeof value.toJSON === "function") {
544
+ return value.toJSON();
545
+ }
546
+ const result = {};
547
+ for (const [k, v] of Object.entries(value)) {
548
+ if (!k.startsWith("_")) {
549
+ result[k] = safeSerialize(v);
550
+ }
551
+ }
552
+ return result;
553
+ }
554
+ return String(value);
555
+ }
556
+ function extractContentBlocks(content) {
557
+ if (!Array.isArray(content)) {
558
+ return [];
559
+ }
560
+ return content.map((block) => safeSerialize(block));
561
+ }
562
+ function extractUsage(message) {
563
+ const usageInfo = {};
564
+ const usage = message.usage;
565
+ if (!usage) {
566
+ return usageInfo;
567
+ }
568
+ const mapping = {
569
+ input_tokens: "inputTokens",
570
+ output_tokens: "outputTokens",
571
+ cache_read_input_tokens: "cacheReadTokens",
572
+ cache_creation_input_tokens: "cacheCreationTokens"
573
+ };
574
+ for (const [srcKey, dstKey] of Object.entries(mapping)) {
575
+ const val = usage[srcKey];
576
+ if (val !== void 0 && val !== null) {
577
+ usageInfo[dstKey] = val;
578
+ }
579
+ }
580
+ return usageInfo;
581
+ }
582
+ var BitfabClaudeAgentHandler = class {
583
+ constructor(config) {
584
+ // Span tracking
585
+ this.runToSpan = /* @__PURE__ */ new Map();
586
+ this.traceId = null;
587
+ this.rootSpanId = null;
588
+ this.activeContext = null;
589
+ this.traceStartedAt = null;
590
+ // LLM turn tracking
591
+ this.conversationHistory = [];
592
+ this.pendingMessages = [];
593
+ this.currentLlmSpanId = null;
594
+ this.currentLlmMessageId = null;
595
+ this.currentLlmContent = [];
596
+ this.currentLlmModel = null;
597
+ this.currentLlmUsage = {};
598
+ this.currentLlmStartedAt = null;
599
+ this.currentLlmHistorySnapshot = [];
600
+ // Subagent tracking
601
+ this.activeSubagentSpans = /* @__PURE__ */ new Map();
602
+ this.httpClient = new HttpClient({
603
+ apiKey: config.apiKey,
604
+ serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
605
+ timeout: config.timeout ?? 1e4
606
+ });
607
+ this.traceFunctionKey = config.traceFunctionKey;
608
+ this.getActiveSpanContext = config.getActiveSpanContext ?? null;
609
+ this.preToolUseHook = this.preToolUseHook.bind(this);
610
+ this.postToolUseHook = this.postToolUseHook.bind(this);
611
+ this.postToolUseFailureHook = this.postToolUseFailureHook.bind(this);
612
+ this.subagentStartHook = this.subagentStartHook.bind(this);
613
+ this.subagentStopHook = this.subagentStopHook.bind(this);
614
+ }
615
+ // ── trace lifecycle ──────────────────────────────────────────
616
+ ensureTrace() {
617
+ if (this.traceId !== null) {
618
+ return this.traceId;
619
+ }
620
+ this.activeContext = this.getActiveSpanContext?.() ?? null;
621
+ if (this.activeContext) {
622
+ this.traceId = this.activeContext.traceId;
623
+ } else {
624
+ this.traceId = crypto.randomUUID();
625
+ }
626
+ this.traceStartedAt = nowIso();
627
+ return this.traceId;
628
+ }
629
+ getParentId(agentId) {
630
+ if (agentId) {
631
+ const subagentSpanId = this.activeSubagentSpans.get(agentId);
632
+ if (subagentSpanId) {
633
+ return subagentSpanId;
634
+ }
635
+ }
636
+ return this.activeContext?.spanId ?? this.rootSpanId ?? null;
637
+ }
638
+ // ── span helpers ─────────────────────────────────────────────
639
+ startSpan(spanId, name, spanType, inputData, parentId) {
640
+ const traceId = this.ensureTrace();
641
+ const spanInfo = {
642
+ spanId,
643
+ traceId,
644
+ parentId: parentId ?? null,
645
+ startedAt: nowIso(),
646
+ name,
647
+ type: spanType,
648
+ input: safeSerialize(inputData),
649
+ contexts: []
650
+ };
651
+ this.runToSpan.set(spanId, spanInfo);
652
+ return spanInfo;
653
+ }
654
+ completeSpan(spanId, output, error, extraContexts) {
655
+ const spanInfo = this.runToSpan.get(spanId);
656
+ if (!spanInfo) {
657
+ return;
658
+ }
659
+ this.runToSpan.delete(spanId);
660
+ spanInfo.endedAt = nowIso();
661
+ spanInfo.output = safeSerialize(output);
662
+ if (error !== void 0) {
663
+ spanInfo.error = error;
664
+ }
665
+ if (extraContexts) {
666
+ spanInfo.contexts.push(extraContexts);
667
+ }
668
+ this.sendSpan(spanInfo);
669
+ }
670
+ sendSpan(spanInfo) {
671
+ const spanData = {
672
+ name: spanInfo.name,
673
+ type: spanInfo.type
674
+ };
675
+ if (spanInfo.input !== void 0) {
676
+ spanData.input = spanInfo.input;
677
+ }
678
+ if (spanInfo.output !== void 0) {
679
+ spanData.output = spanInfo.output;
680
+ }
681
+ if (spanInfo.error !== void 0) {
682
+ spanData.error = spanInfo.error;
683
+ }
684
+ if (spanInfo.contexts.length > 0) {
685
+ spanData.contexts = spanInfo.contexts;
686
+ }
687
+ const rawSpan = {
688
+ id: spanInfo.spanId,
689
+ trace_id: spanInfo.traceId,
690
+ started_at: spanInfo.startedAt,
691
+ ended_at: spanInfo.endedAt ?? nowIso(),
692
+ span_data: spanData
693
+ };
694
+ if (spanInfo.parentId !== null) {
695
+ rawSpan.parent_id = spanInfo.parentId;
696
+ }
697
+ const payload = {
698
+ type: "sdk-function",
699
+ source: "typescript-sdk-claude-agent-sdk",
700
+ traceFunctionKey: this.traceFunctionKey,
701
+ sourceTraceId: spanInfo.traceId,
702
+ rawSpan
703
+ };
704
+ try {
705
+ this.httpClient.sendExternalSpan(payload);
706
+ } catch {
707
+ }
708
+ }
709
+ sendTraceCompletion(endedAt, metadata) {
710
+ if (this.traceId === null) {
711
+ return;
712
+ }
713
+ const completed = this.activeContext === null;
714
+ const traceId = this.traceId;
715
+ this.traceId = null;
716
+ const externalTrace = {
717
+ id: traceId,
718
+ started_at: this.traceStartedAt ?? nowIso(),
719
+ ended_at: endedAt ?? nowIso(),
720
+ workflow_name: this.traceFunctionKey
721
+ };
722
+ if (metadata) {
723
+ externalTrace.metadata = metadata;
724
+ }
725
+ const traceData = {
726
+ type: "sdk-function",
727
+ source: "typescript-sdk-claude-agent-sdk",
728
+ traceFunctionKey: this.traceFunctionKey,
729
+ externalTrace,
730
+ completed
731
+ };
732
+ try {
733
+ this.httpClient.sendExternalTrace(traceData);
734
+ } catch {
735
+ }
736
+ }
737
+ // ── hook callbacks ───────────────────────────────────────────
738
+ async preToolUseHook(inputData, toolUseId, _context) {
739
+ try {
740
+ const sid = inputData.tool_use_id ?? toolUseId ?? crypto.randomUUID();
741
+ const toolName = inputData.tool_name ?? "tool";
742
+ const toolInput = inputData.tool_input ?? {};
743
+ const agentId = inputData.agent_id;
744
+ const parentId = this.getParentId(agentId);
745
+ this.startSpan(sid, toolName, "function", toolInput, parentId);
746
+ } catch {
747
+ }
748
+ return {};
749
+ }
750
+ async postToolUseHook(inputData, toolUseId, _context) {
751
+ try {
752
+ const sid = inputData.tool_use_id ?? toolUseId ?? "";
753
+ const toolResponse = inputData.tool_response;
754
+ this.completeSpan(sid, toolResponse);
755
+ } catch {
756
+ }
757
+ return {};
758
+ }
759
+ async postToolUseFailureHook(inputData, toolUseId, _context) {
760
+ try {
761
+ const sid = inputData.tool_use_id ?? toolUseId ?? "";
762
+ const error = String(inputData.error ?? "Unknown error");
763
+ this.completeSpan(sid, void 0, error);
764
+ } catch {
765
+ }
766
+ return {};
767
+ }
768
+ async subagentStartHook(inputData, _toolUseId, _context) {
769
+ try {
770
+ const agentId = inputData.agent_id ?? crypto.randomUUID();
771
+ const agentType = inputData.agent_type ?? "subagent";
772
+ const parentId = this.getParentId();
773
+ const spanId = crypto.randomUUID();
774
+ this.activeSubagentSpans.set(agentId, spanId);
775
+ this.startSpan(
776
+ spanId,
777
+ `Agent: ${agentType}`,
778
+ "agent",
779
+ void 0,
780
+ parentId
781
+ );
782
+ } catch {
783
+ }
784
+ return {};
785
+ }
786
+ async subagentStopHook(inputData, _toolUseId, _context) {
787
+ try {
788
+ const agentId = inputData.agent_id ?? "";
789
+ const spanId = this.activeSubagentSpans.get(agentId);
790
+ if (spanId) {
791
+ this.activeSubagentSpans.delete(agentId);
792
+ this.completeSpan(spanId);
793
+ }
794
+ } catch {
795
+ }
796
+ return {};
797
+ }
798
+ // ── public API ───────────────────────────────────────────────
799
+ /**
800
+ * Inject Bitfab tracing hooks into Claude Agent SDK options.
801
+ *
802
+ * Modifies the options object and returns it for convenience.
803
+ * The SDK's `HookMatcher` is constructed as a plain object
804
+ * (`{ matcher: null, hooks: [callback] }`) to avoid requiring
805
+ * `@anthropic-ai/claude-agent-sdk` as a dependency.
806
+ *
807
+ * @param options - Options object with a `hooks` property
808
+ * @returns The modified options object with Bitfab hooks injected
809
+ */
810
+ instrumentOptions(options) {
811
+ const hooks = options.hooks ?? {};
812
+ if (!options.hooks) {
813
+ ;
814
+ options.hooks = hooks;
815
+ }
816
+ const hookConfig = [
817
+ ["PreToolUse", this.preToolUseHook],
818
+ ["PostToolUse", this.postToolUseHook],
819
+ ["PostToolUseFailure", this.postToolUseFailureHook],
820
+ ["SubagentStart", this.subagentStartHook],
821
+ ["SubagentStop", this.subagentStopHook]
822
+ ];
823
+ for (const [event, callback] of hookConfig) {
824
+ if (!hooks[event]) {
825
+ hooks[event] = [];
826
+ }
827
+ hooks[event].push({ matcher: null, hooks: [callback] });
828
+ }
829
+ return options;
830
+ }
831
+ /**
832
+ * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
833
+ *
834
+ * Yields every message unchanged while capturing AssistantMessage
835
+ * content as LLM turn spans.
836
+ */
837
+ async *wrapResponse(stream) {
838
+ yield* this.processStream(stream);
839
+ }
840
+ /**
841
+ * Wrap a `query()` async iterator to capture LLM turns.
842
+ *
843
+ * Same as `wrapResponse` but for the simpler `query()` API
844
+ * which does not support hooks (no tool/subagent spans).
845
+ */
846
+ async *wrapQuery(stream) {
847
+ yield* this.processStream(stream);
848
+ }
849
+ // ── stream processing ────────────────────────────────────────
850
+ async *processStream(stream) {
851
+ try {
852
+ for await (const message of stream) {
853
+ try {
854
+ this.processMessage(message);
855
+ } catch {
856
+ }
857
+ yield message;
858
+ }
859
+ } finally {
860
+ try {
861
+ this.flushLlmTurn();
862
+ this.sendTraceCompletion();
863
+ } catch {
864
+ }
865
+ this.resetState();
866
+ }
867
+ }
868
+ processMessage(message) {
869
+ const typeName = message.constructor?.name ?? "";
870
+ if (typeName === "AssistantMessage") {
871
+ this.handleAssistantMessage(message);
872
+ } else if (typeName === "UserMessage") {
873
+ this.handleUserMessage(message);
874
+ } else if (typeName === "ResultMessage") {
875
+ this.handleResultMessage(message);
876
+ }
877
+ }
878
+ handleAssistantMessage(message) {
879
+ this.ensureTrace();
880
+ const messageId = message.message_id;
881
+ if (messageId !== this.currentLlmMessageId) {
882
+ this.flushLlmTurn();
883
+ this.conversationHistory.push(...this.pendingMessages);
884
+ this.pendingMessages = [];
885
+ this.currentLlmSpanId = crypto.randomUUID();
886
+ this.currentLlmMessageId = messageId ?? null;
887
+ this.currentLlmContent = [];
888
+ this.currentLlmModel = message.model ?? null;
889
+ this.currentLlmUsage = {};
890
+ this.currentLlmStartedAt = nowIso();
891
+ this.currentLlmHistorySnapshot = [...this.conversationHistory];
892
+ }
893
+ const content = message.content;
894
+ if (Array.isArray(content)) {
895
+ this.currentLlmContent.push(...extractContentBlocks(content));
896
+ }
897
+ const usage = extractUsage(message);
898
+ if (Object.keys(usage).length > 0) {
899
+ Object.assign(this.currentLlmUsage, usage);
900
+ }
901
+ const model = message.model;
902
+ if (model) {
903
+ this.currentLlmModel = model;
904
+ }
905
+ }
906
+ handleUserMessage(message) {
907
+ const content = message.content;
908
+ const toolUseResult = message.tool_use_result;
909
+ if (toolUseResult !== void 0) {
910
+ this.pendingMessages.push({
911
+ role: "tool",
912
+ content: safeSerialize(content),
913
+ tool_result: safeSerialize(toolUseResult)
914
+ });
915
+ } else {
916
+ this.pendingMessages.push({
917
+ role: "user",
918
+ content: safeSerialize(content)
919
+ });
920
+ }
921
+ }
922
+ handleResultMessage(message) {
923
+ this.flushLlmTurn();
924
+ const metadata = {};
925
+ for (const attr of [
926
+ "num_turns",
927
+ "total_cost_usd",
928
+ "duration_ms",
929
+ "duration_api_ms",
930
+ "session_id"
931
+ ]) {
932
+ const val = message[attr];
933
+ if (val !== void 0 && val !== null) {
934
+ metadata[attr] = val;
935
+ }
936
+ }
937
+ const usage = message.usage;
938
+ if (usage && typeof usage === "object") {
939
+ metadata.usage = safeSerialize(usage);
940
+ }
941
+ this.sendTraceCompletion(
942
+ void 0,
943
+ Object.keys(metadata).length > 0 ? metadata : void 0
944
+ );
945
+ }
946
+ flushLlmTurn() {
947
+ if (this.currentLlmSpanId === null) {
948
+ return;
949
+ }
950
+ const spanId = this.currentLlmSpanId;
951
+ const traceId = this.ensureTrace();
952
+ const parentId = this.getParentId();
953
+ const llmContext = {};
954
+ if (this.currentLlmModel) {
955
+ llmContext.model = this.currentLlmModel;
956
+ }
957
+ Object.assign(llmContext, this.currentLlmUsage);
958
+ const spanInfo = {
959
+ spanId,
960
+ traceId,
961
+ parentId,
962
+ startedAt: this.currentLlmStartedAt ?? nowIso(),
963
+ endedAt: nowIso(),
964
+ name: this.currentLlmModel ?? "llm",
965
+ type: "llm",
966
+ input: this.currentLlmHistorySnapshot,
967
+ output: this.currentLlmContent,
968
+ contexts: Object.keys(llmContext).length > 0 ? [llmContext] : []
969
+ };
970
+ this.sendSpan(spanInfo);
971
+ this.conversationHistory.push({
972
+ role: "assistant",
973
+ content: this.currentLlmContent
974
+ });
975
+ this.currentLlmSpanId = null;
976
+ this.currentLlmMessageId = null;
977
+ this.currentLlmContent = [];
978
+ this.currentLlmModel = null;
979
+ this.currentLlmUsage = {};
980
+ this.currentLlmStartedAt = null;
981
+ this.currentLlmHistorySnapshot = [];
982
+ }
983
+ resetState() {
984
+ this.runToSpan.clear();
985
+ this.traceId = null;
986
+ this.rootSpanId = null;
987
+ this.activeContext = null;
988
+ this.traceStartedAt = null;
989
+ this.conversationHistory = [];
990
+ this.pendingMessages = [];
991
+ this.currentLlmSpanId = null;
992
+ this.currentLlmMessageId = null;
993
+ this.currentLlmContent = [];
994
+ this.currentLlmModel = null;
995
+ this.currentLlmUsage = {};
996
+ this.currentLlmStartedAt = null;
997
+ this.currentLlmHistorySnapshot = [];
998
+ this.activeSubagentSpans.clear();
999
+ }
1000
+ };
1001
+
524
1002
  // src/client.ts
525
1003
  init_asyncStorage();
526
1004
 
@@ -815,6 +1293,484 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
815
1293
  // src/client.ts
816
1294
  init_constants();
817
1295
  init_http();
1296
+
1297
+ // src/langgraph.ts
1298
+ init_constants();
1299
+ init_http();
1300
+ var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
1301
+ var LANGGRAPH_METADATA_KEYS = [
1302
+ "langgraph_step",
1303
+ "langgraph_node",
1304
+ "langgraph_triggers",
1305
+ "langgraph_path",
1306
+ "langgraph_checkpoint_ns"
1307
+ ];
1308
+ function nowIso2() {
1309
+ return (/* @__PURE__ */ new Date()).toISOString();
1310
+ }
1311
+ var MAX_SERIALIZE_DEPTH = 6;
1312
+ function safeSerialize2(value) {
1313
+ return safeSerializeInner(value, 0, /* @__PURE__ */ new WeakSet());
1314
+ }
1315
+ function safeSerializeInner(value, depth, seen) {
1316
+ if (value === null || value === void 0) {
1317
+ return value;
1318
+ }
1319
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1320
+ return value;
1321
+ }
1322
+ const className = value?.constructor?.name ?? typeof value;
1323
+ if (depth > MAX_SERIALIZE_DEPTH) {
1324
+ return `<${className}>`;
1325
+ }
1326
+ if (typeof value === "object") {
1327
+ if (seen.has(value)) {
1328
+ return `<cycle ${className}>`;
1329
+ }
1330
+ seen.add(value);
1331
+ }
1332
+ if (Array.isArray(value)) {
1333
+ return value.map((item) => safeSerializeInner(item, depth + 1, seen));
1334
+ }
1335
+ if (typeof value === "object") {
1336
+ if (typeof value.toJSON === "function") {
1337
+ try {
1338
+ return value.toJSON();
1339
+ } catch {
1340
+ return `<${className}>`;
1341
+ }
1342
+ }
1343
+ try {
1344
+ const result = {};
1345
+ for (const [k, v] of Object.entries(value)) {
1346
+ if (!k.startsWith("_")) {
1347
+ result[k] = safeSerializeInner(v, depth + 1, seen);
1348
+ }
1349
+ }
1350
+ return result;
1351
+ } catch {
1352
+ return `<${className}>`;
1353
+ }
1354
+ }
1355
+ try {
1356
+ return String(value);
1357
+ } catch {
1358
+ return `<${className}>`;
1359
+ }
1360
+ }
1361
+ function convertMessage(message) {
1362
+ if (typeof message !== "object" || message === null) {
1363
+ return { role: "unknown", content: String(message) };
1364
+ }
1365
+ const msg = message;
1366
+ if (typeof msg.toDict === "function") {
1367
+ return msg.toDict();
1368
+ }
1369
+ const typeToRole = {
1370
+ human: "user",
1371
+ ai: "assistant",
1372
+ system: "system",
1373
+ tool: "tool",
1374
+ function: "function"
1375
+ };
1376
+ const result = {};
1377
+ const msgType = msg._getType ? String(msg._getType()) : msg.type;
1378
+ result.role = (msgType ? typeToRole[msgType] : void 0) ?? msg.role ?? "unknown";
1379
+ result.content = msg.content ?? "";
1380
+ if (msg.tool_calls) {
1381
+ result.tool_calls = msg.tool_calls;
1382
+ }
1383
+ if (msg.tool_call_id) {
1384
+ result.tool_call_id = msg.tool_call_id;
1385
+ }
1386
+ if (msg.name) {
1387
+ result.name = msg.name;
1388
+ }
1389
+ return result;
1390
+ }
1391
+ function extractModelName(serialized, metadata) {
1392
+ if (serialized) {
1393
+ const kwargs = serialized.kwargs;
1394
+ if (kwargs) {
1395
+ const model = kwargs.model_name ?? kwargs.model ?? kwargs.model_id;
1396
+ if (model) {
1397
+ return String(model);
1398
+ }
1399
+ }
1400
+ }
1401
+ if (metadata) {
1402
+ const lsModel = metadata.ls_model_name;
1403
+ if (lsModel) {
1404
+ return String(lsModel);
1405
+ }
1406
+ }
1407
+ return void 0;
1408
+ }
1409
+ function extractUsage2(output) {
1410
+ const usage = {};
1411
+ const llmOutput = output.llmOutput;
1412
+ const tokenUsage = llmOutput?.tokenUsage ?? llmOutput?.token_usage ?? llmOutput?.usage ?? {};
1413
+ const inputTokens = tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? tokenUsage.input_tokens;
1414
+ const outputTokens = tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? tokenUsage.output_tokens;
1415
+ const totalTokens = tokenUsage.totalTokens ?? tokenUsage.total_tokens;
1416
+ if (inputTokens !== void 0 && inputTokens !== null) {
1417
+ usage.inputTokens = inputTokens;
1418
+ }
1419
+ if (outputTokens !== void 0 && outputTokens !== null) {
1420
+ usage.outputTokens = outputTokens;
1421
+ }
1422
+ if (totalTokens !== void 0 && totalTokens !== null) {
1423
+ usage.totalTokens = totalTokens;
1424
+ }
1425
+ return usage;
1426
+ }
1427
+ function extractLangGraphMetadata(metadata) {
1428
+ if (!metadata) {
1429
+ return {};
1430
+ }
1431
+ const result = {};
1432
+ for (const key of LANGGRAPH_METADATA_KEYS) {
1433
+ if (key in metadata) {
1434
+ result[key] = metadata[key];
1435
+ }
1436
+ }
1437
+ return result;
1438
+ }
1439
+ var BitfabLangGraphCallbackHandler = class {
1440
+ constructor(config) {
1441
+ this.name = "BitfabLangGraphCallbackHandler";
1442
+ this.ignoreRetriever = true;
1443
+ this.ignoreRetry = true;
1444
+ this.ignoreCustomEvent = true;
1445
+ this.runToSpan = /* @__PURE__ */ new Map();
1446
+ this.invocations = /* @__PURE__ */ new Map();
1447
+ this.httpClient = new HttpClient({
1448
+ apiKey: config.apiKey,
1449
+ serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
1450
+ timeout: config.timeout ?? 1e4
1451
+ });
1452
+ this.traceFunctionKey = config.traceFunctionKey;
1453
+ this.getActiveSpanContext = config.getActiveSpanContext ?? null;
1454
+ }
1455
+ // ── lifecycle helpers ──────────────────────────────────────────
1456
+ startSpan(runId, parentRunId, name, spanType, inputData, metadata, tags) {
1457
+ const parentSpan = parentRunId ? this.runToSpan.get(parentRunId) : void 0;
1458
+ const willHide = tags?.includes(LANGSMITH_HIDDEN_TAG) === true;
1459
+ let invocation;
1460
+ let effectiveParentId;
1461
+ if (parentSpan) {
1462
+ const existing = this.invocations.get(parentSpan.rootRunId);
1463
+ if (existing) {
1464
+ invocation = existing;
1465
+ } else {
1466
+ invocation = {
1467
+ traceId: parentSpan.traceId,
1468
+ activeContext: null,
1469
+ rootRunId: parentSpan.rootRunId
1470
+ };
1471
+ this.invocations.set(invocation.rootRunId, invocation);
1472
+ }
1473
+ if (!willHide) {
1474
+ let resolved = parentSpan;
1475
+ while (resolved?.hidden === true) {
1476
+ resolved = resolved.parentId ? this.runToSpan.get(resolved.parentId) : void 0;
1477
+ }
1478
+ effectiveParentId = resolved ? resolved.spanId : invocation.activeContext?.spanId ?? null;
1479
+ } else {
1480
+ effectiveParentId = parentRunId ?? null;
1481
+ }
1482
+ } else {
1483
+ const activeContext = this.getActiveSpanContext?.() ?? null;
1484
+ invocation = {
1485
+ traceId: activeContext ? activeContext.traceId : crypto.randomUUID(),
1486
+ activeContext,
1487
+ rootRunId: runId
1488
+ };
1489
+ this.invocations.set(runId, invocation);
1490
+ effectiveParentId = activeContext?.spanId ?? null;
1491
+ }
1492
+ const lgMetadata = extractLangGraphMetadata(metadata);
1493
+ const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
1494
+ const spanInfo = {
1495
+ spanId: runId,
1496
+ traceId: invocation.traceId,
1497
+ rootRunId: invocation.rootRunId,
1498
+ parentId: effectiveParentId,
1499
+ startedAt: nowIso2(),
1500
+ name,
1501
+ type: spanType,
1502
+ input: safeSerialize2(inputData),
1503
+ contexts
1504
+ };
1505
+ if (willHide) {
1506
+ spanInfo.hidden = true;
1507
+ }
1508
+ this.runToSpan.set(runId, spanInfo);
1509
+ return spanInfo;
1510
+ }
1511
+ completeSpan(runId, output, error, extraContexts) {
1512
+ const spanInfo = this.runToSpan.get(runId);
1513
+ if (!spanInfo) {
1514
+ return;
1515
+ }
1516
+ this.runToSpan.delete(runId);
1517
+ spanInfo.endedAt = nowIso2();
1518
+ spanInfo.output = safeSerialize2(output);
1519
+ if (error !== void 0) {
1520
+ spanInfo.error = error;
1521
+ }
1522
+ if (extraContexts && Object.keys(extraContexts).length > 0) {
1523
+ spanInfo.contexts.push(extraContexts);
1524
+ }
1525
+ this.sendSpan(spanInfo);
1526
+ if (runId === spanInfo.rootRunId) {
1527
+ const invocation = this.invocations.get(runId);
1528
+ this.sendTraceCompletion(spanInfo, invocation?.activeContext ?? null);
1529
+ this.invocations.delete(runId);
1530
+ }
1531
+ }
1532
+ sendSpan(spanInfo) {
1533
+ const spanData = {
1534
+ name: spanInfo.name,
1535
+ type: spanInfo.type
1536
+ };
1537
+ if (spanInfo.input !== void 0) {
1538
+ spanData.input = spanInfo.input;
1539
+ }
1540
+ if (spanInfo.output !== void 0) {
1541
+ spanData.output = spanInfo.output;
1542
+ }
1543
+ if (spanInfo.error !== void 0) {
1544
+ spanData.error = spanInfo.error;
1545
+ }
1546
+ if (spanInfo.contexts.length > 0) {
1547
+ spanData.contexts = spanInfo.contexts;
1548
+ }
1549
+ if (spanInfo.hidden) {
1550
+ spanData.hidden = true;
1551
+ }
1552
+ const rawSpan = {
1553
+ id: spanInfo.spanId,
1554
+ trace_id: spanInfo.traceId,
1555
+ started_at: spanInfo.startedAt,
1556
+ ended_at: spanInfo.endedAt ?? nowIso2(),
1557
+ span_data: spanData
1558
+ };
1559
+ if (spanInfo.parentId !== null) {
1560
+ rawSpan.parent_id = spanInfo.parentId;
1561
+ }
1562
+ const payload = {
1563
+ type: "sdk-function",
1564
+ source: "typescript-sdk-langgraph",
1565
+ traceFunctionKey: this.traceFunctionKey,
1566
+ sourceTraceId: spanInfo.traceId,
1567
+ rawSpan
1568
+ };
1569
+ try {
1570
+ this.httpClient.sendExternalSpan(payload);
1571
+ } catch {
1572
+ }
1573
+ }
1574
+ sendTraceCompletion(rootSpan, activeContext) {
1575
+ const completed = activeContext === null;
1576
+ const traceData = {
1577
+ type: "sdk-function",
1578
+ source: "typescript-sdk-langgraph",
1579
+ traceFunctionKey: this.traceFunctionKey,
1580
+ externalTrace: {
1581
+ id: rootSpan.traceId,
1582
+ started_at: rootSpan.startedAt,
1583
+ ended_at: rootSpan.endedAt ?? nowIso2(),
1584
+ workflow_name: this.traceFunctionKey
1585
+ },
1586
+ completed
1587
+ };
1588
+ try {
1589
+ this.httpClient.sendExternalTrace(traceData);
1590
+ } catch {
1591
+ }
1592
+ }
1593
+ // ── chain callbacks (graph nodes) ─────────────────────────────
1594
+ async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
1595
+ try {
1596
+ const idArr = chain.id;
1597
+ const name = chain.name ?? idArr?.[idArr.length - 1] ?? "chain";
1598
+ this.startSpan(
1599
+ runId,
1600
+ parentRunId,
1601
+ String(name),
1602
+ "agent",
1603
+ inputs,
1604
+ metadata,
1605
+ tags
1606
+ );
1607
+ } catch {
1608
+ }
1609
+ }
1610
+ async handleChainEnd(outputs, runId) {
1611
+ try {
1612
+ this.completeSpan(runId, outputs);
1613
+ } catch {
1614
+ }
1615
+ }
1616
+ async handleChainError(error, runId) {
1617
+ try {
1618
+ const errorObj = error;
1619
+ if (errorObj?.constructor?.name === "GraphBubbleUp") {
1620
+ this.completeSpan(runId, void 0, void 0);
1621
+ return;
1622
+ }
1623
+ this.completeSpan(
1624
+ runId,
1625
+ void 0,
1626
+ error instanceof Error ? error.message : String(error)
1627
+ );
1628
+ } catch {
1629
+ }
1630
+ }
1631
+ // ── LLM callbacks ─────────────────────────────────────────────
1632
+ async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata) {
1633
+ try {
1634
+ const model = extractModelName(llm, metadata);
1635
+ const idArr = llm.id;
1636
+ const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
1637
+ const converted = messages.map((batch) => batch.map(convertMessage));
1638
+ const spanInfo = this.startSpan(
1639
+ runId,
1640
+ parentRunId,
1641
+ String(name),
1642
+ "llm",
1643
+ converted,
1644
+ metadata,
1645
+ tags
1646
+ );
1647
+ spanInfo.model = model;
1648
+ } catch {
1649
+ }
1650
+ }
1651
+ async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata) {
1652
+ try {
1653
+ const model = extractModelName(llm, metadata);
1654
+ const idArr = llm.id;
1655
+ const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
1656
+ const spanInfo = this.startSpan(
1657
+ runId,
1658
+ parentRunId,
1659
+ String(name),
1660
+ "llm",
1661
+ prompts,
1662
+ metadata,
1663
+ tags
1664
+ );
1665
+ spanInfo.model = model;
1666
+ } catch {
1667
+ }
1668
+ }
1669
+ async handleLLMEnd(output, runId) {
1670
+ try {
1671
+ let llmOutput;
1672
+ const generations = output.generations;
1673
+ if (generations?.length && generations[generations.length - 1]?.length) {
1674
+ const gen = generations[generations.length - 1][generations[generations.length - 1].length - 1];
1675
+ const msg = gen.message;
1676
+ llmOutput = msg ? convertMessage(msg) : gen.text ?? String(gen);
1677
+ }
1678
+ const usage = extractUsage2(output);
1679
+ const spanInfo = this.runToSpan.get(runId);
1680
+ const model = spanInfo?.model;
1681
+ const llmContext = {};
1682
+ if (model) {
1683
+ llmContext.model = model;
1684
+ }
1685
+ Object.assign(llmContext, usage);
1686
+ this.completeSpan(
1687
+ runId,
1688
+ llmOutput,
1689
+ void 0,
1690
+ Object.keys(llmContext).length > 0 ? llmContext : void 0
1691
+ );
1692
+ } catch {
1693
+ }
1694
+ }
1695
+ async handleLLMError(error, runId) {
1696
+ try {
1697
+ this.completeSpan(
1698
+ runId,
1699
+ void 0,
1700
+ error instanceof Error ? error.message : String(error)
1701
+ );
1702
+ } catch {
1703
+ }
1704
+ }
1705
+ async handleLLMNewToken() {
1706
+ }
1707
+ // ── tool callbacks ────────────────────────────────────────────
1708
+ async handleToolStart(tool, input, runId, parentRunId, tags, metadata) {
1709
+ try {
1710
+ const name = tool.name ?? "tool";
1711
+ this.startSpan(
1712
+ runId,
1713
+ parentRunId,
1714
+ String(name),
1715
+ "function",
1716
+ input,
1717
+ metadata,
1718
+ tags
1719
+ );
1720
+ } catch {
1721
+ }
1722
+ }
1723
+ async handleToolEnd(output, runId) {
1724
+ try {
1725
+ this.completeSpan(runId, output);
1726
+ } catch {
1727
+ }
1728
+ }
1729
+ async handleToolError(error, runId) {
1730
+ try {
1731
+ this.completeSpan(
1732
+ runId,
1733
+ void 0,
1734
+ error instanceof Error ? error.message : String(error)
1735
+ );
1736
+ } catch {
1737
+ }
1738
+ }
1739
+ // ── retriever callbacks ───────────────────────────────────────
1740
+ async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata) {
1741
+ try {
1742
+ const name = retriever.name ?? "retriever";
1743
+ this.startSpan(
1744
+ runId,
1745
+ parentRunId,
1746
+ String(name),
1747
+ "function",
1748
+ query,
1749
+ metadata,
1750
+ tags
1751
+ );
1752
+ } catch {
1753
+ }
1754
+ }
1755
+ async handleRetrieverEnd(documents, runId) {
1756
+ try {
1757
+ this.completeSpan(runId, documents);
1758
+ } catch {
1759
+ }
1760
+ }
1761
+ async handleRetrieverError(error, runId) {
1762
+ try {
1763
+ this.completeSpan(
1764
+ runId,
1765
+ void 0,
1766
+ error instanceof Error ? error.message : String(error)
1767
+ );
1768
+ } catch {
1769
+ }
1770
+ }
1771
+ };
1772
+
1773
+ // src/client.ts
818
1774
  init_replayContext();
819
1775
  init_serialize();
820
1776
 
@@ -1328,6 +2284,67 @@ var Bitfab = class {
1328
2284
  }
1329
2285
  });
1330
2286
  }
2287
+ /**
2288
+ * Get a LangGraph/LangChain callback handler for tracing.
2289
+ *
2290
+ * The handler captures graph node execution, LLM calls, and tool
2291
+ * invocations as Bitfab spans with proper parent-child hierarchy.
2292
+ *
2293
+ * ```typescript
2294
+ * const handler = client.getLangGraphCallbackHandler("my-agent");
2295
+ * const result = await agent.invoke(
2296
+ * { messages: [...] },
2297
+ * { callbacks: [handler] },
2298
+ * );
2299
+ * ```
2300
+ *
2301
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
2302
+ * @returns A BitfabLangGraphCallbackHandler configured for this client
2303
+ */
2304
+ getLangGraphCallbackHandler(traceFunctionKey) {
2305
+ return new BitfabLangGraphCallbackHandler({
2306
+ apiKey: this.apiKey,
2307
+ traceFunctionKey,
2308
+ serviceUrl: this.serviceUrl,
2309
+ getActiveSpanContext: () => {
2310
+ const stack = getSpanStack();
2311
+ return stack[stack.length - 1] ?? null;
2312
+ }
2313
+ });
2314
+ }
2315
+ /**
2316
+ * Get a Claude Agent SDK handler for tracing.
2317
+ *
2318
+ * The handler captures LLM turns, tool invocations, and subagent
2319
+ * execution as Bitfab spans with proper parent-child hierarchy.
2320
+ *
2321
+ * ```typescript
2322
+ * const handler = client.getClaudeAgentHandler("my-agent");
2323
+ * const options = handler.instrumentOptions({
2324
+ * model: "claude-sonnet-4-5-...",
2325
+ * });
2326
+ * const sdkClient = new ClaudeSDKClient(options);
2327
+ * await sdkClient.connect();
2328
+ * await sdkClient.query("Do something");
2329
+ * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
2330
+ * // process messages
2331
+ * }
2332
+ * ```
2333
+ *
2334
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
2335
+ * @returns A BitfabClaudeAgentHandler configured for this client
2336
+ */
2337
+ getClaudeAgentHandler(traceFunctionKey) {
2338
+ return new BitfabClaudeAgentHandler({
2339
+ apiKey: this.apiKey,
2340
+ traceFunctionKey,
2341
+ serviceUrl: this.serviceUrl,
2342
+ getActiveSpanContext: () => {
2343
+ const stack = getSpanStack();
2344
+ return stack[stack.length - 1] ?? null;
2345
+ }
2346
+ });
2347
+ }
1331
2348
  /**
1332
2349
  * Wrap a BAML client method to automatically capture prompt and LLM metadata.
1333
2350
  *
@@ -1737,8 +2754,10 @@ assertAsyncStorageRegistered();
1737
2754
  // Annotate the CommonJS export names for ESM import in node:
1738
2755
  0 && (module.exports = {
1739
2756
  Bitfab,
2757
+ BitfabClaudeAgentHandler,
1740
2758
  BitfabError,
1741
2759
  BitfabFunction,
2760
+ BitfabLangGraphCallbackHandler,
1742
2761
  BitfabOpenAITracingProcessor,
1743
2762
  DEFAULT_SERVICE_URL,
1744
2763
  __version__,