@bitfab/sdk 0.21.1 → 0.22.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.
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-EQI6ZJC3.js";
11
11
 
12
12
  // src/version.generated.ts
13
- var __version__ = "0.21.1";
13
+ var __version__ = "0.22.0";
14
14
 
15
15
  // src/constants.ts
16
16
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -479,6 +479,13 @@ var BitfabClaudeAgentHandler = class {
479
479
  this.currentLlmHistorySnapshot = [];
480
480
  // Subagent tracking
481
481
  this.activeSubagentSpans = /* @__PURE__ */ new Map();
482
+ // Synthetic root span (handler-only replay). When an `input` is supplied to
483
+ // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
484
+ // input, so a handler-instrumented run is replayable WITHOUT an enclosing
485
+ // withSpan — matching the LangGraph handler, which records the graph input as
486
+ // its root. The prompt is not present anywhere in the message stream, so it
487
+ // must be handed in explicitly.
488
+ this.hasRootInput = false;
482
489
  this.httpClient = new HttpClient({
483
490
  apiKey: config.apiKey,
484
491
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -513,7 +520,30 @@ var BitfabClaudeAgentHandler = class {
513
520
  return subagentSpanId;
514
521
  }
515
522
  }
516
- return this.activeContext?.spanId ?? this.rootSpanId ?? null;
523
+ return this.rootSpanId ?? this.activeContext?.spanId ?? null;
524
+ }
525
+ // Emit the synthetic root `agent` span once, before any child spans. No-op
526
+ // unless an `input` was supplied AND there is no enclosing withSpan (in which
527
+ // case that outer span is already the replayable root).
528
+ maybeStartRootSpan() {
529
+ if (!this.hasRootInput || this.rootSpanId !== null) {
530
+ return;
531
+ }
532
+ this.ensureTrace();
533
+ if (this.activeContext !== null) {
534
+ return;
535
+ }
536
+ const spanId = crypto.randomUUID();
537
+ this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
538
+ this.rootSpanId = spanId;
539
+ }
540
+ completeRootSpan() {
541
+ if (this.rootSpanId === null) {
542
+ return;
543
+ }
544
+ const spanId = this.rootSpanId;
545
+ this.rootSpanId = null;
546
+ this.completeSpan(spanId, this.rootOutput);
517
547
  }
518
548
  // ── span helpers ─────────────────────────────────────────────
519
549
  startSpan(spanId, name, spanType, inputData, parentId) {
@@ -709,26 +739,53 @@ var BitfabClaudeAgentHandler = class {
709
739
  return options;
710
740
  }
711
741
  /**
712
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
742
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
743
+ *
744
+ * Yields every message unchanged while capturing assistant message
745
+ * content as LLM turn spans. Kept for naming symmetry with the Python
746
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
747
+ * in TypeScript, prefer `wrapQuery` around `query()`.
713
748
  *
714
- * Yields every message unchanged while capturing AssistantMessage
715
- * content as LLM turn spans.
749
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
750
+ * `wrapQuery`.
716
751
  */
717
- async *wrapResponse(stream) {
752
+ async *wrapResponse(stream, opts) {
753
+ this.setRootInput(opts);
718
754
  yield* this.processStream(stream);
719
755
  }
720
756
  /**
721
757
  * Wrap a `query()` async iterator to capture LLM turns.
722
758
  *
723
- * Same as `wrapResponse` but for the simpler `query()` API
724
- * which does not support hooks (no tool/subagent spans).
759
+ * Tool and subagent spans are captured separately via the hooks injected
760
+ * by `instrumentOptions` into the `options` passed to `query()`.
761
+ *
762
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
763
+ * — to make a handler-only run replayable: the handler records a root `agent`
764
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
765
+ * when an enclosing `withSpan` already supplies the replayable root.
766
+ *
767
+ * ```typescript
768
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
769
+ * ```
725
770
  */
726
- async *wrapQuery(stream) {
771
+ async *wrapQuery(stream, opts) {
772
+ this.setRootInput(opts);
727
773
  yield* this.processStream(stream);
728
774
  }
775
+ setRootInput(opts) {
776
+ if (opts && opts.input !== void 0) {
777
+ this.hasRootInput = true;
778
+ this.rootInput = opts.input;
779
+ } else {
780
+ this.hasRootInput = false;
781
+ this.rootInput = void 0;
782
+ }
783
+ this.rootOutput = void 0;
784
+ }
729
785
  // ── stream processing ────────────────────────────────────────
730
786
  async *processStream(stream) {
731
787
  try {
788
+ this.maybeStartRootSpan();
732
789
  for await (const message of stream) {
733
790
  try {
734
791
  this.processMessage(message);
@@ -739,6 +796,7 @@ var BitfabClaudeAgentHandler = class {
739
796
  } finally {
740
797
  try {
741
798
  this.flushLlmTurn();
799
+ this.completeRootSpan();
742
800
  this.sendTraceCompletion();
743
801
  } catch {
744
802
  }
@@ -746,18 +804,19 @@ var BitfabClaudeAgentHandler = class {
746
804
  }
747
805
  }
748
806
  processMessage(message) {
749
- const typeName = message.constructor?.name ?? "";
750
- if (typeName === "AssistantMessage") {
807
+ const typeName = message.type;
808
+ if (typeName === "assistant") {
751
809
  this.handleAssistantMessage(message);
752
- } else if (typeName === "UserMessage") {
810
+ } else if (typeName === "user") {
753
811
  this.handleUserMessage(message);
754
- } else if (typeName === "ResultMessage") {
812
+ } else if (typeName === "result") {
755
813
  this.handleResultMessage(message);
756
814
  }
757
815
  }
758
816
  handleAssistantMessage(message) {
759
817
  this.ensureTrace();
760
- const messageId = message.message_id;
818
+ const inner = message.message ?? {};
819
+ const messageId = inner.id ?? message.uuid;
761
820
  if (messageId !== this.currentLlmMessageId) {
762
821
  this.flushLlmTurn();
763
822
  this.conversationHistory.push(...this.pendingMessages);
@@ -765,26 +824,27 @@ var BitfabClaudeAgentHandler = class {
765
824
  this.currentLlmSpanId = crypto.randomUUID();
766
825
  this.currentLlmMessageId = messageId ?? null;
767
826
  this.currentLlmContent = [];
768
- this.currentLlmModel = message.model ?? null;
827
+ this.currentLlmModel = inner.model ?? null;
769
828
  this.currentLlmUsage = {};
770
829
  this.currentLlmStartedAt = nowIso();
771
830
  this.currentLlmHistorySnapshot = [...this.conversationHistory];
772
831
  }
773
- const content = message.content;
832
+ const content = inner.content;
774
833
  if (Array.isArray(content)) {
775
834
  this.currentLlmContent.push(...extractContentBlocks(content));
776
835
  }
777
- const usage = extractUsage(message);
836
+ const usage = extractUsage(inner);
778
837
  if (Object.keys(usage).length > 0) {
779
838
  Object.assign(this.currentLlmUsage, usage);
780
839
  }
781
- const model = message.model;
840
+ const model = inner.model;
782
841
  if (model) {
783
842
  this.currentLlmModel = model;
784
843
  }
785
844
  }
786
845
  handleUserMessage(message) {
787
- const content = message.content;
846
+ const inner = message.message ?? {};
847
+ const content = inner.content;
788
848
  const toolUseResult = message.tool_use_result;
789
849
  if (toolUseResult !== void 0) {
790
850
  this.pendingMessages.push({
@@ -801,6 +861,10 @@ var BitfabClaudeAgentHandler = class {
801
861
  }
802
862
  handleResultMessage(message) {
803
863
  this.flushLlmTurn();
864
+ if (message.result !== void 0) {
865
+ this.rootOutput = message.result;
866
+ }
867
+ this.completeRootSpan();
804
868
  const metadata = {};
805
869
  for (const attr of [
806
870
  "num_turns",
@@ -864,6 +928,9 @@ var BitfabClaudeAgentHandler = class {
864
928
  this.runToSpan.clear();
865
929
  this.traceId = null;
866
930
  this.rootSpanId = null;
931
+ this.hasRootInput = false;
932
+ this.rootInput = void 0;
933
+ this.rootOutput = void 0;
867
934
  this.activeContext = null;
868
935
  this.traceStartedAt = null;
869
936
  this.conversationHistory = [];
@@ -1718,6 +1785,39 @@ var BitfabLangGraphCallbackHandler = class {
1718
1785
  }
1719
1786
  };
1720
1787
 
1788
+ // src/openaiAgentSdk.ts
1789
+ var BitfabOpenAIAgentHandler = class {
1790
+ constructor(config) {
1791
+ this.traceFunctionKey = config.traceFunctionKey;
1792
+ this.withSpanFn = config.withSpan;
1793
+ }
1794
+ async wrapRun(agent, input, options) {
1795
+ const { run } = await import("@openai/agents");
1796
+ const isStreaming = options?.stream === true;
1797
+ const finalize = async (result) => {
1798
+ const res = result;
1799
+ if (isStreaming && res?.completed) {
1800
+ try {
1801
+ await res.completed;
1802
+ } catch {
1803
+ }
1804
+ }
1805
+ return res?.finalOutput;
1806
+ };
1807
+ const options_ = { type: "agent", finalize };
1808
+ const traced = this.withSpanFn(
1809
+ this.traceFunctionKey,
1810
+ options_,
1811
+ (agentInput) => run(
1812
+ agent,
1813
+ agentInput,
1814
+ options
1815
+ )
1816
+ );
1817
+ return traced(input);
1818
+ }
1819
+ };
1820
+
1721
1821
  // src/replayEnvironment.ts
1722
1822
  var ReplayEnvironment = class {
1723
1823
  /**
@@ -2404,6 +2504,34 @@ var Bitfab = class {
2404
2504
  }
2405
2505
  });
2406
2506
  }
2507
+ /**
2508
+ * Get an OpenAI Agents SDK handler that records a replayable root span.
2509
+ *
2510
+ * The processor from {@link getOpenAiTracingProcessor} captures everything
2511
+ * inside a run (LLM calls, tools, handoffs) but never sees the caller's
2512
+ * input, so a processor-only run records an empty-input root and is not
2513
+ * replayable. This handler's `wrapRun` is a drop-in for `run()` that opens a
2514
+ * `withSpan` root carrying the input and final output; the processor's spans
2515
+ * nest beneath it. Register the processor once at startup, then call
2516
+ * `handler.wrapRun(agent, input)` in place of `run(agent, input)`.
2517
+ *
2518
+ * ```typescript
2519
+ * import { addTraceProcessor, Agent, run } from "@openai/agents";
2520
+ *
2521
+ * addTraceProcessor(client.getOpenAiTracingProcessor());
2522
+ * const handler = client.getOpenAiAgentHandler("research-topic");
2523
+ * const result = await handler.wrapRun(agent, "Find X");
2524
+ * ```
2525
+ *
2526
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
2527
+ * @returns A BitfabOpenAIAgentHandler configured for this client
2528
+ */
2529
+ getOpenAiAgentHandler(traceFunctionKey) {
2530
+ return new BitfabOpenAIAgentHandler({
2531
+ traceFunctionKey,
2532
+ withSpan: this.withSpan.bind(this)
2533
+ });
2534
+ }
2407
2535
  /**
2408
2536
  * Get a LangGraph/LangChain callback handler for tracing.
2409
2537
  *
@@ -2457,14 +2585,15 @@ var Bitfab = class {
2457
2585
  * execution as Bitfab spans with proper parent-child hierarchy.
2458
2586
  *
2459
2587
  * ```typescript
2588
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
2589
+ *
2460
2590
  * const handler = client.getClaudeAgentHandler("my-agent");
2461
2591
  * const options = handler.instrumentOptions({
2462
2592
  * model: "claude-sonnet-4-5-...",
2463
2593
  * });
2464
- * const sdkClient = new ClaudeSDKClient(options);
2465
- * await sdkClient.connect();
2466
- * await sdkClient.query("Do something");
2467
- * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
2594
+ * for await (const msg of handler.wrapQuery(
2595
+ * query({ prompt: "Do something", options })
2596
+ * )) {
2468
2597
  * // process messages
2469
2598
  * }
2470
2599
  * ```
@@ -3153,6 +3282,7 @@ export {
3153
3282
  BitfabClaudeAgentHandler,
3154
3283
  SUPPORTED_PROVIDERS,
3155
3284
  BitfabLangGraphCallbackHandler,
3285
+ BitfabOpenAIAgentHandler,
3156
3286
  ReplayEnvironment,
3157
3287
  BitfabOpenAITracingProcessor,
3158
3288
  getCurrentSpan,
@@ -3161,4 +3291,4 @@ export {
3161
3291
  BitfabFunction,
3162
3292
  finalizers
3163
3293
  };
3164
- //# sourceMappingURL=chunk-75LZO6JS.js.map
3294
+ //# sourceMappingURL=chunk-4SLFC266.js.map