@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.
package/dist/node.cjs CHANGED
@@ -484,6 +484,7 @@ __export(node_exports, {
484
484
  BitfabFunction: () => BitfabFunction,
485
485
  BitfabLangChainCallbackHandler: () => BitfabLangGraphCallbackHandler,
486
486
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
487
+ BitfabOpenAIAgentHandler: () => BitfabOpenAIAgentHandler,
487
488
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
488
489
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
489
490
  ReplayEnvironment: () => ReplayEnvironment,
@@ -504,7 +505,7 @@ registerAsyncLocalStorageClass(
504
505
  );
505
506
 
506
507
  // src/version.generated.ts
507
- var __version__ = "0.21.1";
508
+ var __version__ = "0.22.0";
508
509
 
509
510
  // src/constants.ts
510
511
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -975,6 +976,13 @@ var BitfabClaudeAgentHandler = class {
975
976
  this.currentLlmHistorySnapshot = [];
976
977
  // Subagent tracking
977
978
  this.activeSubagentSpans = /* @__PURE__ */ new Map();
979
+ // Synthetic root span (handler-only replay). When an `input` is supplied to
980
+ // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
981
+ // input, so a handler-instrumented run is replayable WITHOUT an enclosing
982
+ // withSpan — matching the LangGraph handler, which records the graph input as
983
+ // its root. The prompt is not present anywhere in the message stream, so it
984
+ // must be handed in explicitly.
985
+ this.hasRootInput = false;
978
986
  this.httpClient = new HttpClient({
979
987
  apiKey: config.apiKey,
980
988
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -1009,7 +1017,30 @@ var BitfabClaudeAgentHandler = class {
1009
1017
  return subagentSpanId;
1010
1018
  }
1011
1019
  }
1012
- return this.activeContext?.spanId ?? this.rootSpanId ?? null;
1020
+ return this.rootSpanId ?? this.activeContext?.spanId ?? null;
1021
+ }
1022
+ // Emit the synthetic root `agent` span once, before any child spans. No-op
1023
+ // unless an `input` was supplied AND there is no enclosing withSpan (in which
1024
+ // case that outer span is already the replayable root).
1025
+ maybeStartRootSpan() {
1026
+ if (!this.hasRootInput || this.rootSpanId !== null) {
1027
+ return;
1028
+ }
1029
+ this.ensureTrace();
1030
+ if (this.activeContext !== null) {
1031
+ return;
1032
+ }
1033
+ const spanId = crypto.randomUUID();
1034
+ this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1035
+ this.rootSpanId = spanId;
1036
+ }
1037
+ completeRootSpan() {
1038
+ if (this.rootSpanId === null) {
1039
+ return;
1040
+ }
1041
+ const spanId = this.rootSpanId;
1042
+ this.rootSpanId = null;
1043
+ this.completeSpan(spanId, this.rootOutput);
1013
1044
  }
1014
1045
  // ── span helpers ─────────────────────────────────────────────
1015
1046
  startSpan(spanId, name, spanType, inputData, parentId) {
@@ -1205,26 +1236,53 @@ var BitfabClaudeAgentHandler = class {
1205
1236
  return options;
1206
1237
  }
1207
1238
  /**
1208
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
1239
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
1240
+ *
1241
+ * Yields every message unchanged while capturing assistant message
1242
+ * content as LLM turn spans. Kept for naming symmetry with the Python
1243
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
1244
+ * in TypeScript, prefer `wrapQuery` around `query()`.
1209
1245
  *
1210
- * Yields every message unchanged while capturing AssistantMessage
1211
- * content as LLM turn spans.
1246
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
1247
+ * `wrapQuery`.
1212
1248
  */
1213
- async *wrapResponse(stream) {
1249
+ async *wrapResponse(stream, opts) {
1250
+ this.setRootInput(opts);
1214
1251
  yield* this.processStream(stream);
1215
1252
  }
1216
1253
  /**
1217
1254
  * Wrap a `query()` async iterator to capture LLM turns.
1218
1255
  *
1219
- * Same as `wrapResponse` but for the simpler `query()` API
1220
- * which does not support hooks (no tool/subagent spans).
1256
+ * Tool and subagent spans are captured separately via the hooks injected
1257
+ * by `instrumentOptions` into the `options` passed to `query()`.
1258
+ *
1259
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
1260
+ * — to make a handler-only run replayable: the handler records a root `agent`
1261
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
1262
+ * when an enclosing `withSpan` already supplies the replayable root.
1263
+ *
1264
+ * ```typescript
1265
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
1266
+ * ```
1221
1267
  */
1222
- async *wrapQuery(stream) {
1268
+ async *wrapQuery(stream, opts) {
1269
+ this.setRootInput(opts);
1223
1270
  yield* this.processStream(stream);
1224
1271
  }
1272
+ setRootInput(opts) {
1273
+ if (opts && opts.input !== void 0) {
1274
+ this.hasRootInput = true;
1275
+ this.rootInput = opts.input;
1276
+ } else {
1277
+ this.hasRootInput = false;
1278
+ this.rootInput = void 0;
1279
+ }
1280
+ this.rootOutput = void 0;
1281
+ }
1225
1282
  // ── stream processing ────────────────────────────────────────
1226
1283
  async *processStream(stream) {
1227
1284
  try {
1285
+ this.maybeStartRootSpan();
1228
1286
  for await (const message of stream) {
1229
1287
  try {
1230
1288
  this.processMessage(message);
@@ -1235,6 +1293,7 @@ var BitfabClaudeAgentHandler = class {
1235
1293
  } finally {
1236
1294
  try {
1237
1295
  this.flushLlmTurn();
1296
+ this.completeRootSpan();
1238
1297
  this.sendTraceCompletion();
1239
1298
  } catch {
1240
1299
  }
@@ -1242,18 +1301,19 @@ var BitfabClaudeAgentHandler = class {
1242
1301
  }
1243
1302
  }
1244
1303
  processMessage(message) {
1245
- const typeName = message.constructor?.name ?? "";
1246
- if (typeName === "AssistantMessage") {
1304
+ const typeName = message.type;
1305
+ if (typeName === "assistant") {
1247
1306
  this.handleAssistantMessage(message);
1248
- } else if (typeName === "UserMessage") {
1307
+ } else if (typeName === "user") {
1249
1308
  this.handleUserMessage(message);
1250
- } else if (typeName === "ResultMessage") {
1309
+ } else if (typeName === "result") {
1251
1310
  this.handleResultMessage(message);
1252
1311
  }
1253
1312
  }
1254
1313
  handleAssistantMessage(message) {
1255
1314
  this.ensureTrace();
1256
- const messageId = message.message_id;
1315
+ const inner = message.message ?? {};
1316
+ const messageId = inner.id ?? message.uuid;
1257
1317
  if (messageId !== this.currentLlmMessageId) {
1258
1318
  this.flushLlmTurn();
1259
1319
  this.conversationHistory.push(...this.pendingMessages);
@@ -1261,26 +1321,27 @@ var BitfabClaudeAgentHandler = class {
1261
1321
  this.currentLlmSpanId = crypto.randomUUID();
1262
1322
  this.currentLlmMessageId = messageId ?? null;
1263
1323
  this.currentLlmContent = [];
1264
- this.currentLlmModel = message.model ?? null;
1324
+ this.currentLlmModel = inner.model ?? null;
1265
1325
  this.currentLlmUsage = {};
1266
1326
  this.currentLlmStartedAt = nowIso();
1267
1327
  this.currentLlmHistorySnapshot = [...this.conversationHistory];
1268
1328
  }
1269
- const content = message.content;
1329
+ const content = inner.content;
1270
1330
  if (Array.isArray(content)) {
1271
1331
  this.currentLlmContent.push(...extractContentBlocks(content));
1272
1332
  }
1273
- const usage = extractUsage(message);
1333
+ const usage = extractUsage(inner);
1274
1334
  if (Object.keys(usage).length > 0) {
1275
1335
  Object.assign(this.currentLlmUsage, usage);
1276
1336
  }
1277
- const model = message.model;
1337
+ const model = inner.model;
1278
1338
  if (model) {
1279
1339
  this.currentLlmModel = model;
1280
1340
  }
1281
1341
  }
1282
1342
  handleUserMessage(message) {
1283
- const content = message.content;
1343
+ const inner = message.message ?? {};
1344
+ const content = inner.content;
1284
1345
  const toolUseResult = message.tool_use_result;
1285
1346
  if (toolUseResult !== void 0) {
1286
1347
  this.pendingMessages.push({
@@ -1297,6 +1358,10 @@ var BitfabClaudeAgentHandler = class {
1297
1358
  }
1298
1359
  handleResultMessage(message) {
1299
1360
  this.flushLlmTurn();
1361
+ if (message.result !== void 0) {
1362
+ this.rootOutput = message.result;
1363
+ }
1364
+ this.completeRootSpan();
1300
1365
  const metadata = {};
1301
1366
  for (const attr of [
1302
1367
  "num_turns",
@@ -1360,6 +1425,9 @@ var BitfabClaudeAgentHandler = class {
1360
1425
  this.runToSpan.clear();
1361
1426
  this.traceId = null;
1362
1427
  this.rootSpanId = null;
1428
+ this.hasRootInput = false;
1429
+ this.rootInput = void 0;
1430
+ this.rootOutput = void 0;
1363
1431
  this.activeContext = null;
1364
1432
  this.traceStartedAt = null;
1365
1433
  this.conversationHistory = [];
@@ -2219,6 +2287,39 @@ var BitfabLangGraphCallbackHandler = class {
2219
2287
  }
2220
2288
  };
2221
2289
 
2290
+ // src/openaiAgentSdk.ts
2291
+ var BitfabOpenAIAgentHandler = class {
2292
+ constructor(config) {
2293
+ this.traceFunctionKey = config.traceFunctionKey;
2294
+ this.withSpanFn = config.withSpan;
2295
+ }
2296
+ async wrapRun(agent, input, options) {
2297
+ const { run } = await import("@openai/agents");
2298
+ const isStreaming = options?.stream === true;
2299
+ const finalize = async (result) => {
2300
+ const res = result;
2301
+ if (isStreaming && res?.completed) {
2302
+ try {
2303
+ await res.completed;
2304
+ } catch {
2305
+ }
2306
+ }
2307
+ return res?.finalOutput;
2308
+ };
2309
+ const options_ = { type: "agent", finalize };
2310
+ const traced = this.withSpanFn(
2311
+ this.traceFunctionKey,
2312
+ options_,
2313
+ (agentInput) => run(
2314
+ agent,
2315
+ agentInput,
2316
+ options
2317
+ )
2318
+ );
2319
+ return traced(input);
2320
+ }
2321
+ };
2322
+
2222
2323
  // src/client.ts
2223
2324
  init_replayContext();
2224
2325
 
@@ -2912,6 +3013,34 @@ var Bitfab = class {
2912
3013
  }
2913
3014
  });
2914
3015
  }
3016
+ /**
3017
+ * Get an OpenAI Agents SDK handler that records a replayable root span.
3018
+ *
3019
+ * The processor from {@link getOpenAiTracingProcessor} captures everything
3020
+ * inside a run (LLM calls, tools, handoffs) but never sees the caller's
3021
+ * input, so a processor-only run records an empty-input root and is not
3022
+ * replayable. This handler's `wrapRun` is a drop-in for `run()` that opens a
3023
+ * `withSpan` root carrying the input and final output; the processor's spans
3024
+ * nest beneath it. Register the processor once at startup, then call
3025
+ * `handler.wrapRun(agent, input)` in place of `run(agent, input)`.
3026
+ *
3027
+ * ```typescript
3028
+ * import { addTraceProcessor, Agent, run } from "@openai/agents";
3029
+ *
3030
+ * addTraceProcessor(client.getOpenAiTracingProcessor());
3031
+ * const handler = client.getOpenAiAgentHandler("research-topic");
3032
+ * const result = await handler.wrapRun(agent, "Find X");
3033
+ * ```
3034
+ *
3035
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
3036
+ * @returns A BitfabOpenAIAgentHandler configured for this client
3037
+ */
3038
+ getOpenAiAgentHandler(traceFunctionKey) {
3039
+ return new BitfabOpenAIAgentHandler({
3040
+ traceFunctionKey,
3041
+ withSpan: this.withSpan.bind(this)
3042
+ });
3043
+ }
2915
3044
  /**
2916
3045
  * Get a LangGraph/LangChain callback handler for tracing.
2917
3046
  *
@@ -2965,14 +3094,15 @@ var Bitfab = class {
2965
3094
  * execution as Bitfab spans with proper parent-child hierarchy.
2966
3095
  *
2967
3096
  * ```typescript
3097
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
3098
+ *
2968
3099
  * const handler = client.getClaudeAgentHandler("my-agent");
2969
3100
  * const options = handler.instrumentOptions({
2970
3101
  * model: "claude-sonnet-4-5-...",
2971
3102
  * });
2972
- * const sdkClient = new ClaudeSDKClient(options);
2973
- * await sdkClient.connect();
2974
- * await sdkClient.query("Do something");
2975
- * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
3103
+ * for await (const msg of handler.wrapQuery(
3104
+ * query({ prompt: "Do something", options })
3105
+ * )) {
2976
3106
  * // process messages
2977
3107
  * }
2978
3108
  * ```
@@ -3665,6 +3795,7 @@ assertAsyncStorageRegistered();
3665
3795
  BitfabFunction,
3666
3796
  BitfabLangChainCallbackHandler,
3667
3797
  BitfabLangGraphCallbackHandler,
3798
+ BitfabOpenAIAgentHandler,
3668
3799
  BitfabOpenAITracingProcessor,
3669
3800
  DEFAULT_SERVICE_URL,
3670
3801
  ReplayEnvironment,