bitfab 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
@@ -493,6 +493,7 @@ __export(node_exports, {
493
493
  BitfabFunction: () => BitfabFunction,
494
494
  BitfabLangChainCallbackHandler: () => BitfabLangGraphCallbackHandler,
495
495
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
496
+ BitfabOpenAIAgentHandler: () => BitfabOpenAIAgentHandler,
496
497
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
497
498
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
498
499
  ReplayEnvironment: () => ReplayEnvironment,
@@ -513,7 +514,7 @@ registerAsyncLocalStorageClass(
513
514
  );
514
515
 
515
516
  // src/version.generated.ts
516
- var __version__ = "0.21.1";
517
+ var __version__ = "0.22.0";
517
518
 
518
519
  // src/constants.ts
519
520
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -984,6 +985,13 @@ var BitfabClaudeAgentHandler = class {
984
985
  this.currentLlmHistorySnapshot = [];
985
986
  // Subagent tracking
986
987
  this.activeSubagentSpans = /* @__PURE__ */ new Map();
988
+ // Synthetic root span (handler-only replay). When an `input` is supplied to
989
+ // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
990
+ // input, so a handler-instrumented run is replayable WITHOUT an enclosing
991
+ // withSpan — matching the LangGraph handler, which records the graph input as
992
+ // its root. The prompt is not present anywhere in the message stream, so it
993
+ // must be handed in explicitly.
994
+ this.hasRootInput = false;
987
995
  this.httpClient = new HttpClient({
988
996
  apiKey: config.apiKey,
989
997
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -1018,7 +1026,30 @@ var BitfabClaudeAgentHandler = class {
1018
1026
  return subagentSpanId;
1019
1027
  }
1020
1028
  }
1021
- return this.activeContext?.spanId ?? this.rootSpanId ?? null;
1029
+ return this.rootSpanId ?? this.activeContext?.spanId ?? null;
1030
+ }
1031
+ // Emit the synthetic root `agent` span once, before any child spans. No-op
1032
+ // unless an `input` was supplied AND there is no enclosing withSpan (in which
1033
+ // case that outer span is already the replayable root).
1034
+ maybeStartRootSpan() {
1035
+ if (!this.hasRootInput || this.rootSpanId !== null) {
1036
+ return;
1037
+ }
1038
+ this.ensureTrace();
1039
+ if (this.activeContext !== null) {
1040
+ return;
1041
+ }
1042
+ const spanId = crypto.randomUUID();
1043
+ this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1044
+ this.rootSpanId = spanId;
1045
+ }
1046
+ completeRootSpan() {
1047
+ if (this.rootSpanId === null) {
1048
+ return;
1049
+ }
1050
+ const spanId = this.rootSpanId;
1051
+ this.rootSpanId = null;
1052
+ this.completeSpan(spanId, this.rootOutput);
1022
1053
  }
1023
1054
  // ── span helpers ─────────────────────────────────────────────
1024
1055
  startSpan(spanId, name, spanType, inputData, parentId) {
@@ -1214,26 +1245,53 @@ var BitfabClaudeAgentHandler = class {
1214
1245
  return options;
1215
1246
  }
1216
1247
  /**
1217
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
1248
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
1249
+ *
1250
+ * Yields every message unchanged while capturing assistant message
1251
+ * content as LLM turn spans. Kept for naming symmetry with the Python
1252
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
1253
+ * in TypeScript, prefer `wrapQuery` around `query()`.
1218
1254
  *
1219
- * Yields every message unchanged while capturing AssistantMessage
1220
- * content as LLM turn spans.
1255
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
1256
+ * `wrapQuery`.
1221
1257
  */
1222
- async *wrapResponse(stream) {
1258
+ async *wrapResponse(stream, opts) {
1259
+ this.setRootInput(opts);
1223
1260
  yield* this.processStream(stream);
1224
1261
  }
1225
1262
  /**
1226
1263
  * Wrap a `query()` async iterator to capture LLM turns.
1227
1264
  *
1228
- * Same as `wrapResponse` but for the simpler `query()` API
1229
- * which does not support hooks (no tool/subagent spans).
1265
+ * Tool and subagent spans are captured separately via the hooks injected
1266
+ * by `instrumentOptions` into the `options` passed to `query()`.
1267
+ *
1268
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
1269
+ * — to make a handler-only run replayable: the handler records a root `agent`
1270
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
1271
+ * when an enclosing `withSpan` already supplies the replayable root.
1272
+ *
1273
+ * ```typescript
1274
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
1275
+ * ```
1230
1276
  */
1231
- async *wrapQuery(stream) {
1277
+ async *wrapQuery(stream, opts) {
1278
+ this.setRootInput(opts);
1232
1279
  yield* this.processStream(stream);
1233
1280
  }
1281
+ setRootInput(opts) {
1282
+ if (opts && opts.input !== void 0) {
1283
+ this.hasRootInput = true;
1284
+ this.rootInput = opts.input;
1285
+ } else {
1286
+ this.hasRootInput = false;
1287
+ this.rootInput = void 0;
1288
+ }
1289
+ this.rootOutput = void 0;
1290
+ }
1234
1291
  // ── stream processing ────────────────────────────────────────
1235
1292
  async *processStream(stream) {
1236
1293
  try {
1294
+ this.maybeStartRootSpan();
1237
1295
  for await (const message of stream) {
1238
1296
  try {
1239
1297
  this.processMessage(message);
@@ -1244,6 +1302,7 @@ var BitfabClaudeAgentHandler = class {
1244
1302
  } finally {
1245
1303
  try {
1246
1304
  this.flushLlmTurn();
1305
+ this.completeRootSpan();
1247
1306
  this.sendTraceCompletion();
1248
1307
  } catch {
1249
1308
  }
@@ -1251,18 +1310,19 @@ var BitfabClaudeAgentHandler = class {
1251
1310
  }
1252
1311
  }
1253
1312
  processMessage(message) {
1254
- const typeName = message.constructor?.name ?? "";
1255
- if (typeName === "AssistantMessage") {
1313
+ const typeName = message.type;
1314
+ if (typeName === "assistant") {
1256
1315
  this.handleAssistantMessage(message);
1257
- } else if (typeName === "UserMessage") {
1316
+ } else if (typeName === "user") {
1258
1317
  this.handleUserMessage(message);
1259
- } else if (typeName === "ResultMessage") {
1318
+ } else if (typeName === "result") {
1260
1319
  this.handleResultMessage(message);
1261
1320
  }
1262
1321
  }
1263
1322
  handleAssistantMessage(message) {
1264
1323
  this.ensureTrace();
1265
- const messageId = message.message_id;
1324
+ const inner = message.message ?? {};
1325
+ const messageId = inner.id ?? message.uuid;
1266
1326
  if (messageId !== this.currentLlmMessageId) {
1267
1327
  this.flushLlmTurn();
1268
1328
  this.conversationHistory.push(...this.pendingMessages);
@@ -1270,26 +1330,27 @@ var BitfabClaudeAgentHandler = class {
1270
1330
  this.currentLlmSpanId = crypto.randomUUID();
1271
1331
  this.currentLlmMessageId = messageId ?? null;
1272
1332
  this.currentLlmContent = [];
1273
- this.currentLlmModel = message.model ?? null;
1333
+ this.currentLlmModel = inner.model ?? null;
1274
1334
  this.currentLlmUsage = {};
1275
1335
  this.currentLlmStartedAt = nowIso();
1276
1336
  this.currentLlmHistorySnapshot = [...this.conversationHistory];
1277
1337
  }
1278
- const content = message.content;
1338
+ const content = inner.content;
1279
1339
  if (Array.isArray(content)) {
1280
1340
  this.currentLlmContent.push(...extractContentBlocks(content));
1281
1341
  }
1282
- const usage = extractUsage(message);
1342
+ const usage = extractUsage(inner);
1283
1343
  if (Object.keys(usage).length > 0) {
1284
1344
  Object.assign(this.currentLlmUsage, usage);
1285
1345
  }
1286
- const model = message.model;
1346
+ const model = inner.model;
1287
1347
  if (model) {
1288
1348
  this.currentLlmModel = model;
1289
1349
  }
1290
1350
  }
1291
1351
  handleUserMessage(message) {
1292
- const content = message.content;
1352
+ const inner = message.message ?? {};
1353
+ const content = inner.content;
1293
1354
  const toolUseResult = message.tool_use_result;
1294
1355
  if (toolUseResult !== void 0) {
1295
1356
  this.pendingMessages.push({
@@ -1306,6 +1367,10 @@ var BitfabClaudeAgentHandler = class {
1306
1367
  }
1307
1368
  handleResultMessage(message) {
1308
1369
  this.flushLlmTurn();
1370
+ if (message.result !== void 0) {
1371
+ this.rootOutput = message.result;
1372
+ }
1373
+ this.completeRootSpan();
1309
1374
  const metadata = {};
1310
1375
  for (const attr of [
1311
1376
  "num_turns",
@@ -1369,6 +1434,9 @@ var BitfabClaudeAgentHandler = class {
1369
1434
  this.runToSpan.clear();
1370
1435
  this.traceId = null;
1371
1436
  this.rootSpanId = null;
1437
+ this.hasRootInput = false;
1438
+ this.rootInput = void 0;
1439
+ this.rootOutput = void 0;
1372
1440
  this.activeContext = null;
1373
1441
  this.traceStartedAt = null;
1374
1442
  this.conversationHistory = [];
@@ -2228,6 +2296,39 @@ var BitfabLangGraphCallbackHandler = class {
2228
2296
  }
2229
2297
  };
2230
2298
 
2299
+ // src/openaiAgentSdk.ts
2300
+ var BitfabOpenAIAgentHandler = class {
2301
+ constructor(config) {
2302
+ this.traceFunctionKey = config.traceFunctionKey;
2303
+ this.withSpanFn = config.withSpan;
2304
+ }
2305
+ async wrapRun(agent, input, options) {
2306
+ const { run } = await import("@openai/agents");
2307
+ const isStreaming = options?.stream === true;
2308
+ const finalize = async (result) => {
2309
+ const res = result;
2310
+ if (isStreaming && res?.completed) {
2311
+ try {
2312
+ await res.completed;
2313
+ } catch {
2314
+ }
2315
+ }
2316
+ return res?.finalOutput;
2317
+ };
2318
+ const options_ = { type: "agent", finalize };
2319
+ const traced = this.withSpanFn(
2320
+ this.traceFunctionKey,
2321
+ options_,
2322
+ (agentInput) => run(
2323
+ agent,
2324
+ agentInput,
2325
+ options
2326
+ )
2327
+ );
2328
+ return traced(input);
2329
+ }
2330
+ };
2331
+
2231
2332
  // src/client.ts
2232
2333
  init_replayContext();
2233
2334
 
@@ -2921,6 +3022,34 @@ var Bitfab = class {
2921
3022
  }
2922
3023
  });
2923
3024
  }
3025
+ /**
3026
+ * Get an OpenAI Agents SDK handler that records a replayable root span.
3027
+ *
3028
+ * The processor from {@link getOpenAiTracingProcessor} captures everything
3029
+ * inside a run (LLM calls, tools, handoffs) but never sees the caller's
3030
+ * input, so a processor-only run records an empty-input root and is not
3031
+ * replayable. This handler's `wrapRun` is a drop-in for `run()` that opens a
3032
+ * `withSpan` root carrying the input and final output; the processor's spans
3033
+ * nest beneath it. Register the processor once at startup, then call
3034
+ * `handler.wrapRun(agent, input)` in place of `run(agent, input)`.
3035
+ *
3036
+ * ```typescript
3037
+ * import { addTraceProcessor, Agent, run } from "@openai/agents";
3038
+ *
3039
+ * addTraceProcessor(client.getOpenAiTracingProcessor());
3040
+ * const handler = client.getOpenAiAgentHandler("research-topic");
3041
+ * const result = await handler.wrapRun(agent, "Find X");
3042
+ * ```
3043
+ *
3044
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
3045
+ * @returns A BitfabOpenAIAgentHandler configured for this client
3046
+ */
3047
+ getOpenAiAgentHandler(traceFunctionKey) {
3048
+ return new BitfabOpenAIAgentHandler({
3049
+ traceFunctionKey,
3050
+ withSpan: this.withSpan.bind(this)
3051
+ });
3052
+ }
2924
3053
  /**
2925
3054
  * Get a LangGraph/LangChain callback handler for tracing.
2926
3055
  *
@@ -2974,14 +3103,15 @@ var Bitfab = class {
2974
3103
  * execution as Bitfab spans with proper parent-child hierarchy.
2975
3104
  *
2976
3105
  * ```typescript
3106
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
3107
+ *
2977
3108
  * const handler = client.getClaudeAgentHandler("my-agent");
2978
3109
  * const options = handler.instrumentOptions({
2979
3110
  * model: "claude-sonnet-4-5-...",
2980
3111
  * });
2981
- * const sdkClient = new ClaudeSDKClient(options);
2982
- * await sdkClient.connect();
2983
- * await sdkClient.query("Do something");
2984
- * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
3112
+ * for await (const msg of handler.wrapQuery(
3113
+ * query({ prompt: "Do something", options })
3114
+ * )) {
2985
3115
  * // process messages
2986
3116
  * }
2987
3117
  * ```
@@ -3674,6 +3804,7 @@ assertAsyncStorageRegistered();
3674
3804
  BitfabFunction,
3675
3805
  BitfabLangChainCallbackHandler,
3676
3806
  BitfabLangGraphCallbackHandler,
3807
+ BitfabOpenAIAgentHandler,
3677
3808
  BitfabOpenAITracingProcessor,
3678
3809
  DEFAULT_SERVICE_URL,
3679
3810
  ReplayEnvironment,