@bitfab/sdk 0.38.6 → 0.38.7

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
@@ -88,7 +88,7 @@ var __version__, __packageName__;
88
88
  var init_version_generated = __esm({
89
89
  "src/version.generated.ts"() {
90
90
  "use strict";
91
- __version__ = "0.38.6";
91
+ __version__ = "0.38.7";
92
92
  __packageName__ = "@bitfab/sdk";
93
93
  }
94
94
  });
@@ -505,8 +505,8 @@ function encodePayloadBody(payload) {
505
505
  const marker = { error: `payload_serialize_failed: ${message}` };
506
506
  return { body: JSON.stringify(marker), dropped, value: marker };
507
507
  }
508
- const isRecord = typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized);
509
- if (dropped.length > 0 && isRecord) {
508
+ const isRecord2 = typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized);
509
+ if (dropped.length > 0 && isRecord2) {
510
510
  const obj = sanitized;
511
511
  const existing = Array.isArray(obj.errors) ? obj.errors : [];
512
512
  obj.errors = [
@@ -523,7 +523,7 @@ function encodePayloadBody(payload) {
523
523
  return {
524
524
  body: JSON.stringify(sanitized),
525
525
  dropped,
526
- value: isRecord ? sanitized : void 0
526
+ value: isRecord2 ? sanitized : void 0
527
527
  };
528
528
  }
529
529
  }
@@ -3139,6 +3139,7 @@ __export(node_exports, {
3139
3139
  BitfabFunction: () => BitfabFunction,
3140
3140
  BitfabLangChainCallbackHandler: () => BitfabLangGraphCallbackHandler,
3141
3141
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
3142
+ BitfabLangGraphIntegration: () => BitfabLangGraphIntegration,
3142
3143
  BitfabOpenAIAgentHandler: () => BitfabOpenAIAgentHandler,
3143
3144
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
3144
3145
  BitfabVercelAiHandler: () => BitfabVercelAiHandler,
@@ -4470,6 +4471,7 @@ var BitfabLangGraphCallbackHandler = class {
4470
4471
  });
4471
4472
  this.traceFunctionKey = config.traceFunctionKey;
4472
4473
  this.getActiveSpanContext = config.getActiveSpanContext ?? null;
4474
+ this.captureTools = config.captureTools ?? true;
4473
4475
  }
4474
4476
  /**
4475
4477
  * Flush and release the span transport this handler started. A no-op when
@@ -4778,6 +4780,9 @@ var BitfabLangGraphCallbackHandler = class {
4778
4780
  }
4779
4781
  // ── tool callbacks ────────────────────────────────────────────
4780
4782
  async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
4783
+ if (!this.captureTools) {
4784
+ return;
4785
+ }
4781
4786
  try {
4782
4787
  const serialized = tool ?? {};
4783
4788
  const name = runName ?? serialized.name ?? "tool";
@@ -4794,12 +4799,18 @@ var BitfabLangGraphCallbackHandler = class {
4794
4799
  }
4795
4800
  }
4796
4801
  async handleToolEnd(output, runId) {
4802
+ if (!this.captureTools) {
4803
+ return;
4804
+ }
4797
4805
  try {
4798
4806
  this.completeSpan(runId, output);
4799
4807
  } catch {
4800
4808
  }
4801
4809
  }
4802
4810
  async handleToolError(error, runId) {
4811
+ if (!this.captureTools) {
4812
+ return;
4813
+ }
4803
4814
  try {
4804
4815
  this.completeSpan(
4805
4816
  runId,
@@ -4844,6 +4855,258 @@ var BitfabLangGraphCallbackHandler = class {
4844
4855
  }
4845
4856
  };
4846
4857
 
4858
+ // src/langgraphIntegration.ts
4859
+ init_http();
4860
+ init_replayContext();
4861
+ var TOOL_RESULT_TAG = "__bitfabLangGraphToolResult";
4862
+ function isRecord(value) {
4863
+ return typeof value === "object" && value !== null;
4864
+ }
4865
+ function isToolMessage(value) {
4866
+ return isRecord(value) && value.lc_direct_tool_output === true && value.type === "tool" && (typeof value.content === "string" || Array.isArray(value.content));
4867
+ }
4868
+ function isCommand(value) {
4869
+ return isRecord(value) && value.lg_name === "Command";
4870
+ }
4871
+ function encodeNested(value) {
4872
+ if (isToolMessage(value) || isCommand(value)) {
4873
+ return encodeNativeToolResult(value);
4874
+ }
4875
+ if (Array.isArray(value)) {
4876
+ return value.map(encodeNested);
4877
+ }
4878
+ if (isRecord(value)) {
4879
+ return Object.fromEntries(
4880
+ Object.entries(value).map(([key, entry]) => [key, encodeNested(entry)])
4881
+ );
4882
+ }
4883
+ return value;
4884
+ }
4885
+ function encodeNativeToolResult(value) {
4886
+ if (isToolMessage(value)) {
4887
+ return {
4888
+ [TOOL_RESULT_TAG]: "tool-message",
4889
+ content: value.content,
4890
+ name: typeof value.name === "string" ? value.name : void 0,
4891
+ id: typeof value.id === "string" ? value.id : void 0,
4892
+ status: value.status === "success" || value.status === "error" ? value.status : void 0,
4893
+ artifact: encodeNested(value.artifact),
4894
+ metadata: encodeNested(value.metadata),
4895
+ additionalKwargs: encodeNested(value.additional_kwargs),
4896
+ responseMetadata: encodeNested(value.response_metadata)
4897
+ };
4898
+ }
4899
+ return {
4900
+ [TOOL_RESULT_TAG]: "command",
4901
+ graph: isRecord(value) && typeof value.graph === "string" ? value.graph : void 0,
4902
+ update: isRecord(value) ? encodeNested(value.update) : void 0,
4903
+ resume: isRecord(value) ? encodeNested(value.resume) : void 0,
4904
+ goto: isRecord(value) ? encodeNested(value.goto) : void 0
4905
+ };
4906
+ }
4907
+ function finalizeToolResult(value) {
4908
+ return isToolMessage(value) || isCommand(value) ? encodeNativeToolResult(value) : value;
4909
+ }
4910
+ function isEncodedToolResult(value) {
4911
+ return isRecord(value) && typeof value[TOOL_RESULT_TAG] === "string";
4912
+ }
4913
+ async function loadLangChainCore() {
4914
+ try {
4915
+ return await importOptionalPeer([
4916
+ "@langchain",
4917
+ "core",
4918
+ "messages"
4919
+ ]);
4920
+ } catch {
4921
+ throw new BitfabError(
4922
+ "LangGraph tool replay requires @langchain/core. Install @langchain/langgraph before using getLangGraphIntegration().",
4923
+ "https://docs.bitfab.ai/frameworks/langgraph"
4924
+ );
4925
+ }
4926
+ }
4927
+ async function loadLangGraph() {
4928
+ try {
4929
+ return await importOptionalPeer([
4930
+ "@langchain",
4931
+ "langgraph"
4932
+ ]);
4933
+ } catch {
4934
+ throw new BitfabError(
4935
+ "Replaying a LangGraph Command requires @langchain/langgraph.",
4936
+ "https://docs.bitfab.ai/frameworks/langgraph"
4937
+ );
4938
+ }
4939
+ }
4940
+ async function reviveNested(value, toolCallId) {
4941
+ if (isEncodedToolResult(value)) {
4942
+ return reviveToolResult(value, toolCallId);
4943
+ }
4944
+ if (Array.isArray(value)) {
4945
+ return Promise.all(value.map((entry) => reviveNested(entry, toolCallId)));
4946
+ }
4947
+ if (isRecord(value)) {
4948
+ const entries = await Promise.all(
4949
+ Object.entries(value).map(async ([key, entry]) => [
4950
+ key,
4951
+ await reviveNested(entry, toolCallId)
4952
+ ])
4953
+ );
4954
+ return Object.fromEntries(entries);
4955
+ }
4956
+ return value;
4957
+ }
4958
+ async function reviveToolResult(value, toolCallId) {
4959
+ if (value[TOOL_RESULT_TAG] === "tool-message") {
4960
+ const { ToolMessage } = await loadLangChainCore();
4961
+ return new ToolMessage({
4962
+ content: value.content,
4963
+ tool_call_id: toolCallId,
4964
+ ...typeof value.name === "string" && { name: value.name },
4965
+ ...typeof value.id === "string" && { id: value.id },
4966
+ ...(value.status === "success" || value.status === "error") && {
4967
+ status: value.status
4968
+ },
4969
+ ...value.artifact !== void 0 && {
4970
+ artifact: await reviveNested(value.artifact, toolCallId)
4971
+ },
4972
+ ...isRecord(value.metadata) && {
4973
+ metadata: await reviveNested(value.metadata, toolCallId)
4974
+ },
4975
+ ...isRecord(value.additionalKwargs) && {
4976
+ additional_kwargs: await reviveNested(
4977
+ value.additionalKwargs,
4978
+ toolCallId
4979
+ )
4980
+ },
4981
+ ...isRecord(value.responseMetadata) && {
4982
+ response_metadata: await reviveNested(
4983
+ value.responseMetadata,
4984
+ toolCallId
4985
+ )
4986
+ }
4987
+ });
4988
+ }
4989
+ const { Command } = await loadLangGraph();
4990
+ return new Command({
4991
+ ...typeof value.graph === "string" && { graph: value.graph },
4992
+ ...value.update !== void 0 && {
4993
+ update: await reviveNested(value.update, toolCallId)
4994
+ },
4995
+ ...value.resume !== void 0 && {
4996
+ resume: await reviveNested(value.resume, toolCallId)
4997
+ },
4998
+ ...value.goto !== void 0 && {
4999
+ goto: await reviveNested(value.goto, toolCallId)
5000
+ }
5001
+ });
5002
+ }
5003
+ var BitfabLangGraphIntegration = class {
5004
+ constructor(config) {
5005
+ this.client = config.client;
5006
+ this.traceFunctionKey = config.traceFunctionKey;
5007
+ this.callbackHandler = config.callbackHandler;
5008
+ this.mockToolsOnReplay = config.options?.mockToolsOnReplay ?? true;
5009
+ }
5010
+ /**
5011
+ * Wrap tools before passing the same returned array to both
5012
+ * `model.bindTools()` and `new ToolNode()`. Each invocation becomes an
5013
+ * independently mockable child span.
5014
+ */
5015
+ wrapTools(tools) {
5016
+ return tools.map((tool) => this.wrapTool(tool));
5017
+ }
5018
+ /**
5019
+ * Create the normal graph entry point. The returned function adds Bitfab's
5020
+ * callback handler, preserves invocation config, and records only the graph
5021
+ * input as the replayable root input.
5022
+ *
5023
+ * @experimental This API may change before it is stable.
5024
+ */
5025
+ createInvoker(graph) {
5026
+ const configuredGraph = graph.withConfig({
5027
+ callbacks: [this.callbackHandler]
5028
+ });
5029
+ return (input, config) => {
5030
+ const invoke = this.wrapInvoke(
5031
+ (rootInput) => configuredGraph.invoke(rootInput, config)
5032
+ );
5033
+ return invoke(input);
5034
+ };
5035
+ }
5036
+ wrapTool(tool) {
5037
+ const toolName = tool.name;
5038
+ if (typeof toolName !== "string" || toolName.length === 0) {
5039
+ throw new BitfabError(
5040
+ "LangGraph replayable tools must have a name.",
5041
+ "https://docs.bitfab.ai/frameworks/langgraph"
5042
+ );
5043
+ }
5044
+ const mockToolsOnReplay = this.mockToolsOnReplay;
5045
+ const shouldMock = typeof mockToolsOnReplay === "boolean" ? mockToolsOnReplay : mockToolsOnReplay.includes(toolName);
5046
+ const originalInvoke = tool.invoke.bind(tool);
5047
+ return new Proxy(tool, {
5048
+ get: (target, property) => {
5049
+ if (property === "invoke") {
5050
+ return async (input, ...rest) => {
5051
+ const toolCallId = isRecord(input) && typeof input.id === "string" ? input.id : "";
5052
+ const args = isRecord(input) && "args" in input ? input.args : input;
5053
+ this.assertReplayToolResultExists(toolName, shouldMock);
5054
+ const execute = this.client.withSpan(
5055
+ this.traceFunctionKey,
5056
+ {
5057
+ name: toolName,
5058
+ type: "function",
5059
+ captureWhen: "nested",
5060
+ mockOnReplay: shouldMock,
5061
+ finalize: finalizeToolResult
5062
+ },
5063
+ async (_args) => await originalInvoke(input, ...rest)
5064
+ );
5065
+ const result = await execute(args);
5066
+ return isEncodedToolResult(result) ? await reviveToolResult(result, toolCallId) : result;
5067
+ };
5068
+ }
5069
+ const value = Reflect.get(target, property, target);
5070
+ return typeof value === "function" ? value.bind(target) : value;
5071
+ }
5072
+ });
5073
+ }
5074
+ assertReplayToolResultExists(toolName, shouldMock) {
5075
+ const replayContext = getReplayContext();
5076
+ if (!replayContext?.mockTree) {
5077
+ return;
5078
+ }
5079
+ const counterKey = `${this.traceFunctionKey}:${toolName}`;
5080
+ const callIndex = replayContext.callCounters?.get(counterKey) ?? 0;
5081
+ const mockSpan = replayContext.mockTree.spans.get(
5082
+ `${counterKey}:${callIndex}`
5083
+ );
5084
+ const hasMatchingOverride = replayContext.mockOverrides?.some(
5085
+ (override) => override.match({
5086
+ traceFunctionKey: this.traceFunctionKey,
5087
+ spanName: toolName,
5088
+ type: "function",
5089
+ originalSpanId: mockSpan?.sourceSpanId
5090
+ })
5091
+ );
5092
+ const expectsRecordedOutput = replayContext.mockStrategy === "all" || replayContext.mockStrategy === "marked" && shouldMock;
5093
+ if (hasMatchingOverride !== true && expectsRecordedOutput && !mockSpan) {
5094
+ throw new BitfabError(
5095
+ `No recorded LangGraph tool result for "${toolName}" at call ${callIndex + 1}; refusing to execute the live tool during replay.`,
5096
+ "https://docs.bitfab.ai/frameworks/langgraph"
5097
+ );
5098
+ }
5099
+ }
5100
+ /** Wrap the function that invokes the compiled graph as the replay root. */
5101
+ wrapInvoke(fn) {
5102
+ return this.client.withSpan(
5103
+ this.traceFunctionKey,
5104
+ { name: this.traceFunctionKey, type: "agent" },
5105
+ fn
5106
+ );
5107
+ }
5108
+ };
5109
+
4847
5110
  // src/client.ts
4848
5111
  init_mockOverride();
4849
5112
 
@@ -6234,6 +6497,37 @@ var Bitfab = class {
6234
6497
  getLangChainCallbackHandler(traceFunctionKey) {
6235
6498
  return this.getLangGraphCallbackHandler(traceFunctionKey);
6236
6499
  }
6500
+ /**
6501
+ * Get the first-class LangGraph integration for tracing and replaying tools
6502
+ * executed by `ToolNode`.
6503
+ *
6504
+ * The integration combines `wrapTools()` for per-tool replay interception
6505
+ * with `createInvoker()` for a callback-configured replayable graph entry
6506
+ * point. Lower-level callback and root wrappers remain available.
6507
+ *
6508
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
6509
+ * @param options - Controls which tools are marked for replay mocking
6510
+ * @experimental This API may change before it is stable.
6511
+ */
6512
+ getLangGraphIntegration(traceFunctionKey, options) {
6513
+ const callbackHandler = new BitfabLangGraphCallbackHandler({
6514
+ apiKey: this.resolveApiKey(),
6515
+ traceFunctionKey,
6516
+ serviceUrl: this.serviceUrl,
6517
+ getActiveSpanContext: () => {
6518
+ const stack = getSpanStack();
6519
+ return stack[stack.length - 1] ?? null;
6520
+ },
6521
+ captureTools: false,
6522
+ _httpClient: this.httpClient
6523
+ });
6524
+ return new BitfabLangGraphIntegration({
6525
+ client: this,
6526
+ traceFunctionKey,
6527
+ callbackHandler,
6528
+ options
6529
+ });
6530
+ }
6237
6531
  /**
6238
6532
  * Get a Claude Agent SDK handler for tracing.
6239
6533
  *
@@ -7264,6 +7558,14 @@ var BitfabFunction = class {
7264
7558
  getLangChainCallbackHandler() {
7265
7559
  return this.client.getLangChainCallbackHandler(this.traceFunctionKey);
7266
7560
  }
7561
+ /**
7562
+ * Get the first-class LangGraph tool replay integration bound to this key.
7563
+ *
7564
+ * @experimental This API may change before it is stable.
7565
+ */
7566
+ getLangGraphIntegration(options) {
7567
+ return this.client.getLangGraphIntegration(this.traceFunctionKey, options);
7568
+ }
7267
7569
  /**
7268
7570
  * Wrap a BAML client method to automatically capture prompt and LLM metadata.
7269
7571
  * Delegates to the parent client's wrapBAML method.
@@ -7363,6 +7665,7 @@ assertAsyncStorageRegistered();
7363
7665
  BitfabFunction,
7364
7666
  BitfabLangChainCallbackHandler,
7365
7667
  BitfabLangGraphCallbackHandler,
7668
+ BitfabLangGraphIntegration,
7366
7669
  BitfabOpenAIAgentHandler,
7367
7670
  BitfabOpenAITracingProcessor,
7368
7671
  BitfabVercelAiHandler,