bitfab 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
@@ -97,7 +97,7 @@ var __version__, __packageName__;
97
97
  var init_version_generated = __esm({
98
98
  "src/version.generated.ts"() {
99
99
  "use strict";
100
- __version__ = "0.38.6";
100
+ __version__ = "0.38.7";
101
101
  __packageName__ = "bitfab";
102
102
  }
103
103
  });
@@ -514,8 +514,8 @@ function encodePayloadBody(payload) {
514
514
  const marker = { error: `payload_serialize_failed: ${message}` };
515
515
  return { body: JSON.stringify(marker), dropped, value: marker };
516
516
  }
517
- const isRecord = typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized);
518
- if (dropped.length > 0 && isRecord) {
517
+ const isRecord2 = typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized);
518
+ if (dropped.length > 0 && isRecord2) {
519
519
  const obj = sanitized;
520
520
  const existing = Array.isArray(obj.errors) ? obj.errors : [];
521
521
  obj.errors = [
@@ -532,7 +532,7 @@ function encodePayloadBody(payload) {
532
532
  return {
533
533
  body: JSON.stringify(sanitized),
534
534
  dropped,
535
- value: isRecord ? sanitized : void 0
535
+ value: isRecord2 ? sanitized : void 0
536
536
  };
537
537
  }
538
538
  }
@@ -3148,6 +3148,7 @@ __export(node_exports, {
3148
3148
  BitfabFunction: () => BitfabFunction,
3149
3149
  BitfabLangChainCallbackHandler: () => BitfabLangGraphCallbackHandler,
3150
3150
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
3151
+ BitfabLangGraphIntegration: () => BitfabLangGraphIntegration,
3151
3152
  BitfabOpenAIAgentHandler: () => BitfabOpenAIAgentHandler,
3152
3153
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
3153
3154
  BitfabVercelAiHandler: () => BitfabVercelAiHandler,
@@ -4479,6 +4480,7 @@ var BitfabLangGraphCallbackHandler = class {
4479
4480
  });
4480
4481
  this.traceFunctionKey = config.traceFunctionKey;
4481
4482
  this.getActiveSpanContext = config.getActiveSpanContext ?? null;
4483
+ this.captureTools = config.captureTools ?? true;
4482
4484
  }
4483
4485
  /**
4484
4486
  * Flush and release the span transport this handler started. A no-op when
@@ -4787,6 +4789,9 @@ var BitfabLangGraphCallbackHandler = class {
4787
4789
  }
4788
4790
  // ── tool callbacks ────────────────────────────────────────────
4789
4791
  async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
4792
+ if (!this.captureTools) {
4793
+ return;
4794
+ }
4790
4795
  try {
4791
4796
  const serialized = tool ?? {};
4792
4797
  const name = runName ?? serialized.name ?? "tool";
@@ -4803,12 +4808,18 @@ var BitfabLangGraphCallbackHandler = class {
4803
4808
  }
4804
4809
  }
4805
4810
  async handleToolEnd(output, runId) {
4811
+ if (!this.captureTools) {
4812
+ return;
4813
+ }
4806
4814
  try {
4807
4815
  this.completeSpan(runId, output);
4808
4816
  } catch {
4809
4817
  }
4810
4818
  }
4811
4819
  async handleToolError(error, runId) {
4820
+ if (!this.captureTools) {
4821
+ return;
4822
+ }
4812
4823
  try {
4813
4824
  this.completeSpan(
4814
4825
  runId,
@@ -4853,6 +4864,258 @@ var BitfabLangGraphCallbackHandler = class {
4853
4864
  }
4854
4865
  };
4855
4866
 
4867
+ // src/langgraphIntegration.ts
4868
+ init_http();
4869
+ init_replayContext();
4870
+ var TOOL_RESULT_TAG = "__bitfabLangGraphToolResult";
4871
+ function isRecord(value) {
4872
+ return typeof value === "object" && value !== null;
4873
+ }
4874
+ function isToolMessage(value) {
4875
+ return isRecord(value) && value.lc_direct_tool_output === true && value.type === "tool" && (typeof value.content === "string" || Array.isArray(value.content));
4876
+ }
4877
+ function isCommand(value) {
4878
+ return isRecord(value) && value.lg_name === "Command";
4879
+ }
4880
+ function encodeNested(value) {
4881
+ if (isToolMessage(value) || isCommand(value)) {
4882
+ return encodeNativeToolResult(value);
4883
+ }
4884
+ if (Array.isArray(value)) {
4885
+ return value.map(encodeNested);
4886
+ }
4887
+ if (isRecord(value)) {
4888
+ return Object.fromEntries(
4889
+ Object.entries(value).map(([key, entry]) => [key, encodeNested(entry)])
4890
+ );
4891
+ }
4892
+ return value;
4893
+ }
4894
+ function encodeNativeToolResult(value) {
4895
+ if (isToolMessage(value)) {
4896
+ return {
4897
+ [TOOL_RESULT_TAG]: "tool-message",
4898
+ content: value.content,
4899
+ name: typeof value.name === "string" ? value.name : void 0,
4900
+ id: typeof value.id === "string" ? value.id : void 0,
4901
+ status: value.status === "success" || value.status === "error" ? value.status : void 0,
4902
+ artifact: encodeNested(value.artifact),
4903
+ metadata: encodeNested(value.metadata),
4904
+ additionalKwargs: encodeNested(value.additional_kwargs),
4905
+ responseMetadata: encodeNested(value.response_metadata)
4906
+ };
4907
+ }
4908
+ return {
4909
+ [TOOL_RESULT_TAG]: "command",
4910
+ graph: isRecord(value) && typeof value.graph === "string" ? value.graph : void 0,
4911
+ update: isRecord(value) ? encodeNested(value.update) : void 0,
4912
+ resume: isRecord(value) ? encodeNested(value.resume) : void 0,
4913
+ goto: isRecord(value) ? encodeNested(value.goto) : void 0
4914
+ };
4915
+ }
4916
+ function finalizeToolResult(value) {
4917
+ return isToolMessage(value) || isCommand(value) ? encodeNativeToolResult(value) : value;
4918
+ }
4919
+ function isEncodedToolResult(value) {
4920
+ return isRecord(value) && typeof value[TOOL_RESULT_TAG] === "string";
4921
+ }
4922
+ async function loadLangChainCore() {
4923
+ try {
4924
+ return await importOptionalPeer([
4925
+ "@langchain",
4926
+ "core",
4927
+ "messages"
4928
+ ]);
4929
+ } catch {
4930
+ throw new BitfabError(
4931
+ "LangGraph tool replay requires @langchain/core. Install @langchain/langgraph before using getLangGraphIntegration().",
4932
+ "https://docs.bitfab.ai/frameworks/langgraph"
4933
+ );
4934
+ }
4935
+ }
4936
+ async function loadLangGraph() {
4937
+ try {
4938
+ return await importOptionalPeer([
4939
+ "@langchain",
4940
+ "langgraph"
4941
+ ]);
4942
+ } catch {
4943
+ throw new BitfabError(
4944
+ "Replaying a LangGraph Command requires @langchain/langgraph.",
4945
+ "https://docs.bitfab.ai/frameworks/langgraph"
4946
+ );
4947
+ }
4948
+ }
4949
+ async function reviveNested(value, toolCallId) {
4950
+ if (isEncodedToolResult(value)) {
4951
+ return reviveToolResult(value, toolCallId);
4952
+ }
4953
+ if (Array.isArray(value)) {
4954
+ return Promise.all(value.map((entry) => reviveNested(entry, toolCallId)));
4955
+ }
4956
+ if (isRecord(value)) {
4957
+ const entries = await Promise.all(
4958
+ Object.entries(value).map(async ([key, entry]) => [
4959
+ key,
4960
+ await reviveNested(entry, toolCallId)
4961
+ ])
4962
+ );
4963
+ return Object.fromEntries(entries);
4964
+ }
4965
+ return value;
4966
+ }
4967
+ async function reviveToolResult(value, toolCallId) {
4968
+ if (value[TOOL_RESULT_TAG] === "tool-message") {
4969
+ const { ToolMessage } = await loadLangChainCore();
4970
+ return new ToolMessage({
4971
+ content: value.content,
4972
+ tool_call_id: toolCallId,
4973
+ ...typeof value.name === "string" && { name: value.name },
4974
+ ...typeof value.id === "string" && { id: value.id },
4975
+ ...(value.status === "success" || value.status === "error") && {
4976
+ status: value.status
4977
+ },
4978
+ ...value.artifact !== void 0 && {
4979
+ artifact: await reviveNested(value.artifact, toolCallId)
4980
+ },
4981
+ ...isRecord(value.metadata) && {
4982
+ metadata: await reviveNested(value.metadata, toolCallId)
4983
+ },
4984
+ ...isRecord(value.additionalKwargs) && {
4985
+ additional_kwargs: await reviveNested(
4986
+ value.additionalKwargs,
4987
+ toolCallId
4988
+ )
4989
+ },
4990
+ ...isRecord(value.responseMetadata) && {
4991
+ response_metadata: await reviveNested(
4992
+ value.responseMetadata,
4993
+ toolCallId
4994
+ )
4995
+ }
4996
+ });
4997
+ }
4998
+ const { Command } = await loadLangGraph();
4999
+ return new Command({
5000
+ ...typeof value.graph === "string" && { graph: value.graph },
5001
+ ...value.update !== void 0 && {
5002
+ update: await reviveNested(value.update, toolCallId)
5003
+ },
5004
+ ...value.resume !== void 0 && {
5005
+ resume: await reviveNested(value.resume, toolCallId)
5006
+ },
5007
+ ...value.goto !== void 0 && {
5008
+ goto: await reviveNested(value.goto, toolCallId)
5009
+ }
5010
+ });
5011
+ }
5012
+ var BitfabLangGraphIntegration = class {
5013
+ constructor(config) {
5014
+ this.client = config.client;
5015
+ this.traceFunctionKey = config.traceFunctionKey;
5016
+ this.callbackHandler = config.callbackHandler;
5017
+ this.mockToolsOnReplay = config.options?.mockToolsOnReplay ?? true;
5018
+ }
5019
+ /**
5020
+ * Wrap tools before passing the same returned array to both
5021
+ * `model.bindTools()` and `new ToolNode()`. Each invocation becomes an
5022
+ * independently mockable child span.
5023
+ */
5024
+ wrapTools(tools) {
5025
+ return tools.map((tool) => this.wrapTool(tool));
5026
+ }
5027
+ /**
5028
+ * Create the normal graph entry point. The returned function adds Bitfab's
5029
+ * callback handler, preserves invocation config, and records only the graph
5030
+ * input as the replayable root input.
5031
+ *
5032
+ * @experimental This API may change before it is stable.
5033
+ */
5034
+ createInvoker(graph) {
5035
+ const configuredGraph = graph.withConfig({
5036
+ callbacks: [this.callbackHandler]
5037
+ });
5038
+ return (input, config) => {
5039
+ const invoke = this.wrapInvoke(
5040
+ (rootInput) => configuredGraph.invoke(rootInput, config)
5041
+ );
5042
+ return invoke(input);
5043
+ };
5044
+ }
5045
+ wrapTool(tool) {
5046
+ const toolName = tool.name;
5047
+ if (typeof toolName !== "string" || toolName.length === 0) {
5048
+ throw new BitfabError(
5049
+ "LangGraph replayable tools must have a name.",
5050
+ "https://docs.bitfab.ai/frameworks/langgraph"
5051
+ );
5052
+ }
5053
+ const mockToolsOnReplay = this.mockToolsOnReplay;
5054
+ const shouldMock = typeof mockToolsOnReplay === "boolean" ? mockToolsOnReplay : mockToolsOnReplay.includes(toolName);
5055
+ const originalInvoke = tool.invoke.bind(tool);
5056
+ return new Proxy(tool, {
5057
+ get: (target, property) => {
5058
+ if (property === "invoke") {
5059
+ return async (input, ...rest) => {
5060
+ const toolCallId = isRecord(input) && typeof input.id === "string" ? input.id : "";
5061
+ const args = isRecord(input) && "args" in input ? input.args : input;
5062
+ this.assertReplayToolResultExists(toolName, shouldMock);
5063
+ const execute = this.client.withSpan(
5064
+ this.traceFunctionKey,
5065
+ {
5066
+ name: toolName,
5067
+ type: "function",
5068
+ captureWhen: "nested",
5069
+ mockOnReplay: shouldMock,
5070
+ finalize: finalizeToolResult
5071
+ },
5072
+ async (_args) => await originalInvoke(input, ...rest)
5073
+ );
5074
+ const result = await execute(args);
5075
+ return isEncodedToolResult(result) ? await reviveToolResult(result, toolCallId) : result;
5076
+ };
5077
+ }
5078
+ const value = Reflect.get(target, property, target);
5079
+ return typeof value === "function" ? value.bind(target) : value;
5080
+ }
5081
+ });
5082
+ }
5083
+ assertReplayToolResultExists(toolName, shouldMock) {
5084
+ const replayContext = getReplayContext();
5085
+ if (!replayContext?.mockTree) {
5086
+ return;
5087
+ }
5088
+ const counterKey = `${this.traceFunctionKey}:${toolName}`;
5089
+ const callIndex = replayContext.callCounters?.get(counterKey) ?? 0;
5090
+ const mockSpan = replayContext.mockTree.spans.get(
5091
+ `${counterKey}:${callIndex}`
5092
+ );
5093
+ const hasMatchingOverride = replayContext.mockOverrides?.some(
5094
+ (override) => override.match({
5095
+ traceFunctionKey: this.traceFunctionKey,
5096
+ spanName: toolName,
5097
+ type: "function",
5098
+ originalSpanId: mockSpan?.sourceSpanId
5099
+ })
5100
+ );
5101
+ const expectsRecordedOutput = replayContext.mockStrategy === "all" || replayContext.mockStrategy === "marked" && shouldMock;
5102
+ if (hasMatchingOverride !== true && expectsRecordedOutput && !mockSpan) {
5103
+ throw new BitfabError(
5104
+ `No recorded LangGraph tool result for "${toolName}" at call ${callIndex + 1}; refusing to execute the live tool during replay.`,
5105
+ "https://docs.bitfab.ai/frameworks/langgraph"
5106
+ );
5107
+ }
5108
+ }
5109
+ /** Wrap the function that invokes the compiled graph as the replay root. */
5110
+ wrapInvoke(fn) {
5111
+ return this.client.withSpan(
5112
+ this.traceFunctionKey,
5113
+ { name: this.traceFunctionKey, type: "agent" },
5114
+ fn
5115
+ );
5116
+ }
5117
+ };
5118
+
4856
5119
  // src/client.ts
4857
5120
  init_mockOverride();
4858
5121
 
@@ -6243,6 +6506,37 @@ var Bitfab = class {
6243
6506
  getLangChainCallbackHandler(traceFunctionKey) {
6244
6507
  return this.getLangGraphCallbackHandler(traceFunctionKey);
6245
6508
  }
6509
+ /**
6510
+ * Get the first-class LangGraph integration for tracing and replaying tools
6511
+ * executed by `ToolNode`.
6512
+ *
6513
+ * The integration combines `wrapTools()` for per-tool replay interception
6514
+ * with `createInvoker()` for a callback-configured replayable graph entry
6515
+ * point. Lower-level callback and root wrappers remain available.
6516
+ *
6517
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
6518
+ * @param options - Controls which tools are marked for replay mocking
6519
+ * @experimental This API may change before it is stable.
6520
+ */
6521
+ getLangGraphIntegration(traceFunctionKey, options) {
6522
+ const callbackHandler = new BitfabLangGraphCallbackHandler({
6523
+ apiKey: this.resolveApiKey(),
6524
+ traceFunctionKey,
6525
+ serviceUrl: this.serviceUrl,
6526
+ getActiveSpanContext: () => {
6527
+ const stack = getSpanStack();
6528
+ return stack[stack.length - 1] ?? null;
6529
+ },
6530
+ captureTools: false,
6531
+ _httpClient: this.httpClient
6532
+ });
6533
+ return new BitfabLangGraphIntegration({
6534
+ client: this,
6535
+ traceFunctionKey,
6536
+ callbackHandler,
6537
+ options
6538
+ });
6539
+ }
6246
6540
  /**
6247
6541
  * Get a Claude Agent SDK handler for tracing.
6248
6542
  *
@@ -7273,6 +7567,14 @@ var BitfabFunction = class {
7273
7567
  getLangChainCallbackHandler() {
7274
7568
  return this.client.getLangChainCallbackHandler(this.traceFunctionKey);
7275
7569
  }
7570
+ /**
7571
+ * Get the first-class LangGraph tool replay integration bound to this key.
7572
+ *
7573
+ * @experimental This API may change before it is stable.
7574
+ */
7575
+ getLangGraphIntegration(options) {
7576
+ return this.client.getLangGraphIntegration(this.traceFunctionKey, options);
7577
+ }
7276
7578
  /**
7277
7579
  * Wrap a BAML client method to automatically capture prompt and LLM metadata.
7278
7580
  * Delegates to the parent client's wrapBAML method.
@@ -7372,6 +7674,7 @@ assertAsyncStorageRegistered();
7372
7674
  BitfabFunction,
7373
7675
  BitfabLangChainCallbackHandler,
7374
7676
  BitfabLangGraphCallbackHandler,
7677
+ BitfabLangGraphIntegration,
7375
7678
  BitfabOpenAIAgentHandler,
7376
7679
  BitfabOpenAITracingProcessor,
7377
7680
  BitfabVercelAiHandler,