@bitfab/sdk 0.38.6 → 0.38.8

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.
@@ -19,7 +19,7 @@ import {
19
19
  toJsonSafe,
20
20
  toJsonSafeReport,
21
21
  warnOnce
22
- } from "./chunk-QTTKZMHM.js";
22
+ } from "./chunk-VCGFTX2G.js";
23
23
  import {
24
24
  __privateAdd,
25
25
  __privateGet,
@@ -1183,6 +1183,7 @@ var BitfabLangGraphCallbackHandler = class {
1183
1183
  });
1184
1184
  this.traceFunctionKey = config.traceFunctionKey;
1185
1185
  this.getActiveSpanContext = config.getActiveSpanContext ?? null;
1186
+ this.captureTools = config.captureTools ?? true;
1186
1187
  }
1187
1188
  /**
1188
1189
  * Flush and release the span transport this handler started. A no-op when
@@ -1491,6 +1492,9 @@ var BitfabLangGraphCallbackHandler = class {
1491
1492
  }
1492
1493
  // ── tool callbacks ────────────────────────────────────────────
1493
1494
  async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
1495
+ if (!this.captureTools) {
1496
+ return;
1497
+ }
1494
1498
  try {
1495
1499
  const serialized = tool ?? {};
1496
1500
  const name = runName ?? serialized.name ?? "tool";
@@ -1507,12 +1511,18 @@ var BitfabLangGraphCallbackHandler = class {
1507
1511
  }
1508
1512
  }
1509
1513
  async handleToolEnd(output, runId) {
1514
+ if (!this.captureTools) {
1515
+ return;
1516
+ }
1510
1517
  try {
1511
1518
  this.completeSpan(runId, output);
1512
1519
  } catch {
1513
1520
  }
1514
1521
  }
1515
1522
  async handleToolError(error, runId) {
1523
+ if (!this.captureTools) {
1524
+ return;
1525
+ }
1516
1526
  try {
1517
1527
  this.completeSpan(
1518
1528
  runId,
@@ -1557,6 +1567,256 @@ var BitfabLangGraphCallbackHandler = class {
1557
1567
  }
1558
1568
  };
1559
1569
 
1570
+ // src/langgraphIntegration.ts
1571
+ var TOOL_RESULT_TAG = "__bitfabLangGraphToolResult";
1572
+ function isRecord(value) {
1573
+ return typeof value === "object" && value !== null;
1574
+ }
1575
+ function isToolMessage(value) {
1576
+ return isRecord(value) && value.lc_direct_tool_output === true && value.type === "tool" && (typeof value.content === "string" || Array.isArray(value.content));
1577
+ }
1578
+ function isCommand(value) {
1579
+ return isRecord(value) && value.lg_name === "Command";
1580
+ }
1581
+ function encodeNested(value) {
1582
+ if (isToolMessage(value) || isCommand(value)) {
1583
+ return encodeNativeToolResult(value);
1584
+ }
1585
+ if (Array.isArray(value)) {
1586
+ return value.map(encodeNested);
1587
+ }
1588
+ if (isRecord(value)) {
1589
+ return Object.fromEntries(
1590
+ Object.entries(value).map(([key, entry]) => [key, encodeNested(entry)])
1591
+ );
1592
+ }
1593
+ return value;
1594
+ }
1595
+ function encodeNativeToolResult(value) {
1596
+ if (isToolMessage(value)) {
1597
+ return {
1598
+ [TOOL_RESULT_TAG]: "tool-message",
1599
+ content: value.content,
1600
+ name: typeof value.name === "string" ? value.name : void 0,
1601
+ id: typeof value.id === "string" ? value.id : void 0,
1602
+ status: value.status === "success" || value.status === "error" ? value.status : void 0,
1603
+ artifact: encodeNested(value.artifact),
1604
+ metadata: encodeNested(value.metadata),
1605
+ additionalKwargs: encodeNested(value.additional_kwargs),
1606
+ responseMetadata: encodeNested(value.response_metadata)
1607
+ };
1608
+ }
1609
+ return {
1610
+ [TOOL_RESULT_TAG]: "command",
1611
+ graph: isRecord(value) && typeof value.graph === "string" ? value.graph : void 0,
1612
+ update: isRecord(value) ? encodeNested(value.update) : void 0,
1613
+ resume: isRecord(value) ? encodeNested(value.resume) : void 0,
1614
+ goto: isRecord(value) ? encodeNested(value.goto) : void 0
1615
+ };
1616
+ }
1617
+ function finalizeToolResult(value) {
1618
+ return isToolMessage(value) || isCommand(value) ? encodeNativeToolResult(value) : value;
1619
+ }
1620
+ function isEncodedToolResult(value) {
1621
+ return isRecord(value) && typeof value[TOOL_RESULT_TAG] === "string";
1622
+ }
1623
+ async function loadLangChainCore() {
1624
+ try {
1625
+ return await importOptionalPeer([
1626
+ "@langchain",
1627
+ "core",
1628
+ "messages"
1629
+ ]);
1630
+ } catch {
1631
+ throw new BitfabError(
1632
+ "LangGraph tool replay requires @langchain/core. Install @langchain/langgraph before using getLangGraphIntegration().",
1633
+ "https://docs.bitfab.ai/frameworks/langgraph"
1634
+ );
1635
+ }
1636
+ }
1637
+ async function loadLangGraph() {
1638
+ try {
1639
+ return await importOptionalPeer([
1640
+ "@langchain",
1641
+ "langgraph"
1642
+ ]);
1643
+ } catch {
1644
+ throw new BitfabError(
1645
+ "Replaying a LangGraph Command requires @langchain/langgraph.",
1646
+ "https://docs.bitfab.ai/frameworks/langgraph"
1647
+ );
1648
+ }
1649
+ }
1650
+ async function reviveNested(value, toolCallId) {
1651
+ if (isEncodedToolResult(value)) {
1652
+ return reviveToolResult(value, toolCallId);
1653
+ }
1654
+ if (Array.isArray(value)) {
1655
+ return Promise.all(value.map((entry) => reviveNested(entry, toolCallId)));
1656
+ }
1657
+ if (isRecord(value)) {
1658
+ const entries = await Promise.all(
1659
+ Object.entries(value).map(async ([key, entry]) => [
1660
+ key,
1661
+ await reviveNested(entry, toolCallId)
1662
+ ])
1663
+ );
1664
+ return Object.fromEntries(entries);
1665
+ }
1666
+ return value;
1667
+ }
1668
+ async function reviveToolResult(value, toolCallId) {
1669
+ if (value[TOOL_RESULT_TAG] === "tool-message") {
1670
+ const { ToolMessage } = await loadLangChainCore();
1671
+ return new ToolMessage({
1672
+ content: value.content,
1673
+ tool_call_id: toolCallId,
1674
+ ...typeof value.name === "string" && { name: value.name },
1675
+ ...typeof value.id === "string" && { id: value.id },
1676
+ ...(value.status === "success" || value.status === "error") && {
1677
+ status: value.status
1678
+ },
1679
+ ...value.artifact !== void 0 && {
1680
+ artifact: await reviveNested(value.artifact, toolCallId)
1681
+ },
1682
+ ...isRecord(value.metadata) && {
1683
+ metadata: await reviveNested(value.metadata, toolCallId)
1684
+ },
1685
+ ...isRecord(value.additionalKwargs) && {
1686
+ additional_kwargs: await reviveNested(
1687
+ value.additionalKwargs,
1688
+ toolCallId
1689
+ )
1690
+ },
1691
+ ...isRecord(value.responseMetadata) && {
1692
+ response_metadata: await reviveNested(
1693
+ value.responseMetadata,
1694
+ toolCallId
1695
+ )
1696
+ }
1697
+ });
1698
+ }
1699
+ const { Command } = await loadLangGraph();
1700
+ return new Command({
1701
+ ...typeof value.graph === "string" && { graph: value.graph },
1702
+ ...value.update !== void 0 && {
1703
+ update: await reviveNested(value.update, toolCallId)
1704
+ },
1705
+ ...value.resume !== void 0 && {
1706
+ resume: await reviveNested(value.resume, toolCallId)
1707
+ },
1708
+ ...value.goto !== void 0 && {
1709
+ goto: await reviveNested(value.goto, toolCallId)
1710
+ }
1711
+ });
1712
+ }
1713
+ var BitfabLangGraphIntegration = class {
1714
+ constructor(config) {
1715
+ this.client = config.client;
1716
+ this.traceFunctionKey = config.traceFunctionKey;
1717
+ this.callbackHandler = config.callbackHandler;
1718
+ this.mockToolsOnReplay = config.options?.mockToolsOnReplay ?? true;
1719
+ }
1720
+ /**
1721
+ * Wrap tools before passing the same returned array to both
1722
+ * `model.bindTools()` and `new ToolNode()`. Each invocation becomes an
1723
+ * independently mockable child span.
1724
+ */
1725
+ wrapTools(tools) {
1726
+ return tools.map((tool) => this.wrapTool(tool));
1727
+ }
1728
+ /**
1729
+ * Create the normal graph entry point. The returned function adds Bitfab's
1730
+ * callback handler, preserves invocation config, and records only the graph
1731
+ * input as the replayable root input.
1732
+ *
1733
+ * @experimental This API may change before it is stable.
1734
+ */
1735
+ createInvoker(graph) {
1736
+ const configuredGraph = graph.withConfig({
1737
+ callbacks: [this.callbackHandler]
1738
+ });
1739
+ return (input, config) => {
1740
+ const invoke = this.wrapInvoke(
1741
+ (rootInput) => configuredGraph.invoke(rootInput, config)
1742
+ );
1743
+ return invoke(input);
1744
+ };
1745
+ }
1746
+ wrapTool(tool) {
1747
+ const toolName = tool.name;
1748
+ if (typeof toolName !== "string" || toolName.length === 0) {
1749
+ throw new BitfabError(
1750
+ "LangGraph replayable tools must have a name.",
1751
+ "https://docs.bitfab.ai/frameworks/langgraph"
1752
+ );
1753
+ }
1754
+ const mockToolsOnReplay = this.mockToolsOnReplay;
1755
+ const shouldMock = typeof mockToolsOnReplay === "boolean" ? mockToolsOnReplay : mockToolsOnReplay.includes(toolName);
1756
+ const originalInvoke = tool.invoke.bind(tool);
1757
+ return new Proxy(tool, {
1758
+ get: (target, property) => {
1759
+ if (property === "invoke") {
1760
+ return async (input, ...rest) => {
1761
+ const toolCallId = isRecord(input) && typeof input.id === "string" ? input.id : "";
1762
+ const args = isRecord(input) && "args" in input ? input.args : input;
1763
+ this.assertReplayToolResultExists(toolName, shouldMock);
1764
+ const execute = this.client.withSpan(
1765
+ this.traceFunctionKey,
1766
+ {
1767
+ name: toolName,
1768
+ type: "function",
1769
+ captureWhen: "nested",
1770
+ mockOnReplay: shouldMock,
1771
+ finalize: finalizeToolResult
1772
+ },
1773
+ async (_args) => await originalInvoke(input, ...rest)
1774
+ );
1775
+ const result = await execute(args);
1776
+ return isEncodedToolResult(result) ? await reviveToolResult(result, toolCallId) : result;
1777
+ };
1778
+ }
1779
+ const value = Reflect.get(target, property, target);
1780
+ return typeof value === "function" ? value.bind(target) : value;
1781
+ }
1782
+ });
1783
+ }
1784
+ assertReplayToolResultExists(toolName, shouldMock) {
1785
+ const replayContext = getReplayContext();
1786
+ if (!replayContext?.mockTree) {
1787
+ return;
1788
+ }
1789
+ const counterKey = `${this.traceFunctionKey}:${toolName}`;
1790
+ const callIndex = replayContext.callCounters?.get(counterKey) ?? 0;
1791
+ const mockSpan = replayContext.mockTree.spans.get(
1792
+ `${counterKey}:${callIndex}`
1793
+ );
1794
+ const hasMatchingOverride = replayContext.mockOverrides?.some(
1795
+ (override) => override.match({
1796
+ traceFunctionKey: this.traceFunctionKey,
1797
+ spanName: toolName,
1798
+ type: "function",
1799
+ originalSpanId: mockSpan?.sourceSpanId
1800
+ })
1801
+ );
1802
+ const expectsRecordedOutput = replayContext.mockStrategy === "all" || replayContext.mockStrategy === "marked" && shouldMock;
1803
+ if (hasMatchingOverride !== true && expectsRecordedOutput && !mockSpan) {
1804
+ throw new BitfabError(
1805
+ `No recorded LangGraph tool result for "${toolName}" at call ${callIndex + 1}; refusing to execute the live tool during replay.`,
1806
+ "https://docs.bitfab.ai/frameworks/langgraph"
1807
+ );
1808
+ }
1809
+ }
1810
+ /** Wrap the function that invokes the compiled graph as the replay root. */
1811
+ wrapInvoke(fn) {
1812
+ return this.client.withSpan(
1813
+ this.traceFunctionKey,
1814
+ { name: this.traceFunctionKey, type: "agent" },
1815
+ fn
1816
+ );
1817
+ }
1818
+ };
1819
+
1560
1820
  // src/openaiAgentSdk.ts
1561
1821
  var BitfabOpenAIAgentHandler = class {
1562
1822
  constructor(config) {
@@ -1731,16 +1991,16 @@ var BitfabOpenAITracingProcessor = class {
1731
1991
  delete this.activeTraces[trace.traceId];
1732
1992
  }
1733
1993
  /**
1734
- * Called when a span is started.
1994
+ * Called when a span is started. Span payloads are authoritative completed
1995
+ * snapshots, so start notifications do not cross the transport boundary.
1735
1996
  */
1736
1997
  // biome-ignore lint/suspicious/noExplicitAny: OpenAI Agents SDK uses any for span data
1737
- async onSpanStart(span) {
1738
- this.sendSpan(span);
1998
+ async onSpanStart(_span) {
1739
1999
  }
1740
2000
  /**
1741
2001
  * Called when a span is ended.
1742
2002
  *
1743
- * Send all spans to Bitfab for complete trace capture.
2003
+ * Send the finalized span snapshot to Bitfab for complete trace capture.
1744
2004
  */
1745
2005
  // biome-ignore lint/suspicious/noExplicitAny: OpenAI Agents SDK uses any for span data
1746
2006
  async onSpanEnd(span) {
@@ -2933,6 +3193,37 @@ var Bitfab = class {
2933
3193
  getLangChainCallbackHandler(traceFunctionKey) {
2934
3194
  return this.getLangGraphCallbackHandler(traceFunctionKey);
2935
3195
  }
3196
+ /**
3197
+ * Get the first-class LangGraph integration for tracing and replaying tools
3198
+ * executed by `ToolNode`.
3199
+ *
3200
+ * The integration combines `wrapTools()` for per-tool replay interception
3201
+ * with `createInvoker()` for a callback-configured replayable graph entry
3202
+ * point. Lower-level callback and root wrappers remain available.
3203
+ *
3204
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
3205
+ * @param options - Controls which tools are marked for replay mocking
3206
+ * @experimental This API may change before it is stable.
3207
+ */
3208
+ getLangGraphIntegration(traceFunctionKey, options) {
3209
+ const callbackHandler = new BitfabLangGraphCallbackHandler({
3210
+ apiKey: this.resolveApiKey(),
3211
+ traceFunctionKey,
3212
+ serviceUrl: this.serviceUrl,
3213
+ getActiveSpanContext: () => {
3214
+ const stack = getSpanStack();
3215
+ return stack[stack.length - 1] ?? null;
3216
+ },
3217
+ captureTools: false,
3218
+ _httpClient: this.httpClient
3219
+ });
3220
+ return new BitfabLangGraphIntegration({
3221
+ client: this,
3222
+ traceFunctionKey,
3223
+ callbackHandler,
3224
+ options
3225
+ });
3226
+ }
2936
3227
  /**
2937
3228
  * Get a Claude Agent SDK handler for tracing.
2938
3229
  *
@@ -3811,7 +4102,7 @@ var Bitfab = class {
3811
4102
  `Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
3812
4103
  );
3813
4104
  }
3814
- const { replay: doReplay } = await import("./replay-H3AS4IF2.js");
4105
+ const { replay: doReplay } = await import("./replay-L2OSAP6N.js");
3815
4106
  return doReplay(
3816
4107
  this.httpClient,
3817
4108
  this.serviceUrl,
@@ -3963,6 +4254,14 @@ var BitfabFunction = class {
3963
4254
  getLangChainCallbackHandler() {
3964
4255
  return this.client.getLangChainCallbackHandler(this.traceFunctionKey);
3965
4256
  }
4257
+ /**
4258
+ * Get the first-class LangGraph tool replay integration bound to this key.
4259
+ *
4260
+ * @experimental This API may change before it is stable.
4261
+ */
4262
+ getLangGraphIntegration(options) {
4263
+ return this.client.getLangGraphIntegration(this.traceFunctionKey, options);
4264
+ }
3966
4265
  /**
3967
4266
  * Wrap a BAML client method to automatically capture prompt and LLM metadata.
3968
4267
  * Delegates to the parent client's wrapBAML method.
@@ -4044,6 +4343,7 @@ export {
4044
4343
  BitfabClaudeAgentHandler,
4045
4344
  SUPPORTED_PROVIDERS,
4046
4345
  BitfabLangGraphCallbackHandler,
4346
+ BitfabLangGraphIntegration,
4047
4347
  BitfabOpenAIAgentHandler,
4048
4348
  BitfabOpenAITracingProcessor,
4049
4349
  BitfabVercelAiHandler,
@@ -4055,4 +4355,4 @@ export {
4055
4355
  finalizers,
4056
4356
  defineReplayRegistry
4057
4357
  };
4058
- //# sourceMappingURL=chunk-P2MYK27A.js.map
4358
+ //# sourceMappingURL=chunk-CKOOG3TW.js.map