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/index.cjs CHANGED
@@ -486,6 +486,7 @@ __export(index_exports, {
486
486
  BitfabFunction: () => BitfabFunction,
487
487
  BitfabLangChainCallbackHandler: () => BitfabLangGraphCallbackHandler,
488
488
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
489
+ BitfabOpenAIAgentHandler: () => BitfabOpenAIAgentHandler,
489
490
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
490
491
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
491
492
  ReplayEnvironment: () => ReplayEnvironment,
@@ -499,7 +500,7 @@ __export(index_exports, {
499
500
  module.exports = __toCommonJS(index_exports);
500
501
 
501
502
  // src/version.generated.ts
502
- var __version__ = "0.21.1";
503
+ var __version__ = "0.22.0";
503
504
 
504
505
  // src/constants.ts
505
506
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -970,6 +971,13 @@ var BitfabClaudeAgentHandler = class {
970
971
  this.currentLlmHistorySnapshot = [];
971
972
  // Subagent tracking
972
973
  this.activeSubagentSpans = /* @__PURE__ */ new Map();
974
+ // Synthetic root span (handler-only replay). When an `input` is supplied to
975
+ // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
976
+ // input, so a handler-instrumented run is replayable WITHOUT an enclosing
977
+ // withSpan — matching the LangGraph handler, which records the graph input as
978
+ // its root. The prompt is not present anywhere in the message stream, so it
979
+ // must be handed in explicitly.
980
+ this.hasRootInput = false;
973
981
  this.httpClient = new HttpClient({
974
982
  apiKey: config.apiKey,
975
983
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -1004,7 +1012,30 @@ var BitfabClaudeAgentHandler = class {
1004
1012
  return subagentSpanId;
1005
1013
  }
1006
1014
  }
1007
- return this.activeContext?.spanId ?? this.rootSpanId ?? null;
1015
+ return this.rootSpanId ?? this.activeContext?.spanId ?? null;
1016
+ }
1017
+ // Emit the synthetic root `agent` span once, before any child spans. No-op
1018
+ // unless an `input` was supplied AND there is no enclosing withSpan (in which
1019
+ // case that outer span is already the replayable root).
1020
+ maybeStartRootSpan() {
1021
+ if (!this.hasRootInput || this.rootSpanId !== null) {
1022
+ return;
1023
+ }
1024
+ this.ensureTrace();
1025
+ if (this.activeContext !== null) {
1026
+ return;
1027
+ }
1028
+ const spanId = crypto.randomUUID();
1029
+ this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1030
+ this.rootSpanId = spanId;
1031
+ }
1032
+ completeRootSpan() {
1033
+ if (this.rootSpanId === null) {
1034
+ return;
1035
+ }
1036
+ const spanId = this.rootSpanId;
1037
+ this.rootSpanId = null;
1038
+ this.completeSpan(spanId, this.rootOutput);
1008
1039
  }
1009
1040
  // ── span helpers ─────────────────────────────────────────────
1010
1041
  startSpan(spanId, name, spanType, inputData, parentId) {
@@ -1200,26 +1231,53 @@ var BitfabClaudeAgentHandler = class {
1200
1231
  return options;
1201
1232
  }
1202
1233
  /**
1203
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
1234
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
1235
+ *
1236
+ * Yields every message unchanged while capturing assistant message
1237
+ * content as LLM turn spans. Kept for naming symmetry with the Python
1238
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
1239
+ * in TypeScript, prefer `wrapQuery` around `query()`.
1204
1240
  *
1205
- * Yields every message unchanged while capturing AssistantMessage
1206
- * content as LLM turn spans.
1241
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
1242
+ * `wrapQuery`.
1207
1243
  */
1208
- async *wrapResponse(stream) {
1244
+ async *wrapResponse(stream, opts) {
1245
+ this.setRootInput(opts);
1209
1246
  yield* this.processStream(stream);
1210
1247
  }
1211
1248
  /**
1212
1249
  * Wrap a `query()` async iterator to capture LLM turns.
1213
1250
  *
1214
- * Same as `wrapResponse` but for the simpler `query()` API
1215
- * which does not support hooks (no tool/subagent spans).
1251
+ * Tool and subagent spans are captured separately via the hooks injected
1252
+ * by `instrumentOptions` into the `options` passed to `query()`.
1253
+ *
1254
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
1255
+ * — to make a handler-only run replayable: the handler records a root `agent`
1256
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
1257
+ * when an enclosing `withSpan` already supplies the replayable root.
1258
+ *
1259
+ * ```typescript
1260
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
1261
+ * ```
1216
1262
  */
1217
- async *wrapQuery(stream) {
1263
+ async *wrapQuery(stream, opts) {
1264
+ this.setRootInput(opts);
1218
1265
  yield* this.processStream(stream);
1219
1266
  }
1267
+ setRootInput(opts) {
1268
+ if (opts && opts.input !== void 0) {
1269
+ this.hasRootInput = true;
1270
+ this.rootInput = opts.input;
1271
+ } else {
1272
+ this.hasRootInput = false;
1273
+ this.rootInput = void 0;
1274
+ }
1275
+ this.rootOutput = void 0;
1276
+ }
1220
1277
  // ── stream processing ────────────────────────────────────────
1221
1278
  async *processStream(stream) {
1222
1279
  try {
1280
+ this.maybeStartRootSpan();
1223
1281
  for await (const message of stream) {
1224
1282
  try {
1225
1283
  this.processMessage(message);
@@ -1230,6 +1288,7 @@ var BitfabClaudeAgentHandler = class {
1230
1288
  } finally {
1231
1289
  try {
1232
1290
  this.flushLlmTurn();
1291
+ this.completeRootSpan();
1233
1292
  this.sendTraceCompletion();
1234
1293
  } catch {
1235
1294
  }
@@ -1237,18 +1296,19 @@ var BitfabClaudeAgentHandler = class {
1237
1296
  }
1238
1297
  }
1239
1298
  processMessage(message) {
1240
- const typeName = message.constructor?.name ?? "";
1241
- if (typeName === "AssistantMessage") {
1299
+ const typeName = message.type;
1300
+ if (typeName === "assistant") {
1242
1301
  this.handleAssistantMessage(message);
1243
- } else if (typeName === "UserMessage") {
1302
+ } else if (typeName === "user") {
1244
1303
  this.handleUserMessage(message);
1245
- } else if (typeName === "ResultMessage") {
1304
+ } else if (typeName === "result") {
1246
1305
  this.handleResultMessage(message);
1247
1306
  }
1248
1307
  }
1249
1308
  handleAssistantMessage(message) {
1250
1309
  this.ensureTrace();
1251
- const messageId = message.message_id;
1310
+ const inner = message.message ?? {};
1311
+ const messageId = inner.id ?? message.uuid;
1252
1312
  if (messageId !== this.currentLlmMessageId) {
1253
1313
  this.flushLlmTurn();
1254
1314
  this.conversationHistory.push(...this.pendingMessages);
@@ -1256,26 +1316,27 @@ var BitfabClaudeAgentHandler = class {
1256
1316
  this.currentLlmSpanId = crypto.randomUUID();
1257
1317
  this.currentLlmMessageId = messageId ?? null;
1258
1318
  this.currentLlmContent = [];
1259
- this.currentLlmModel = message.model ?? null;
1319
+ this.currentLlmModel = inner.model ?? null;
1260
1320
  this.currentLlmUsage = {};
1261
1321
  this.currentLlmStartedAt = nowIso();
1262
1322
  this.currentLlmHistorySnapshot = [...this.conversationHistory];
1263
1323
  }
1264
- const content = message.content;
1324
+ const content = inner.content;
1265
1325
  if (Array.isArray(content)) {
1266
1326
  this.currentLlmContent.push(...extractContentBlocks(content));
1267
1327
  }
1268
- const usage = extractUsage(message);
1328
+ const usage = extractUsage(inner);
1269
1329
  if (Object.keys(usage).length > 0) {
1270
1330
  Object.assign(this.currentLlmUsage, usage);
1271
1331
  }
1272
- const model = message.model;
1332
+ const model = inner.model;
1273
1333
  if (model) {
1274
1334
  this.currentLlmModel = model;
1275
1335
  }
1276
1336
  }
1277
1337
  handleUserMessage(message) {
1278
- const content = message.content;
1338
+ const inner = message.message ?? {};
1339
+ const content = inner.content;
1279
1340
  const toolUseResult = message.tool_use_result;
1280
1341
  if (toolUseResult !== void 0) {
1281
1342
  this.pendingMessages.push({
@@ -1292,6 +1353,10 @@ var BitfabClaudeAgentHandler = class {
1292
1353
  }
1293
1354
  handleResultMessage(message) {
1294
1355
  this.flushLlmTurn();
1356
+ if (message.result !== void 0) {
1357
+ this.rootOutput = message.result;
1358
+ }
1359
+ this.completeRootSpan();
1295
1360
  const metadata = {};
1296
1361
  for (const attr of [
1297
1362
  "num_turns",
@@ -1355,6 +1420,9 @@ var BitfabClaudeAgentHandler = class {
1355
1420
  this.runToSpan.clear();
1356
1421
  this.traceId = null;
1357
1422
  this.rootSpanId = null;
1423
+ this.hasRootInput = false;
1424
+ this.rootInput = void 0;
1425
+ this.rootOutput = void 0;
1358
1426
  this.activeContext = null;
1359
1427
  this.traceStartedAt = null;
1360
1428
  this.conversationHistory = [];
@@ -2214,6 +2282,39 @@ var BitfabLangGraphCallbackHandler = class {
2214
2282
  }
2215
2283
  };
2216
2284
 
2285
+ // src/openaiAgentSdk.ts
2286
+ var BitfabOpenAIAgentHandler = class {
2287
+ constructor(config) {
2288
+ this.traceFunctionKey = config.traceFunctionKey;
2289
+ this.withSpanFn = config.withSpan;
2290
+ }
2291
+ async wrapRun(agent, input, options) {
2292
+ const { run } = await import("@openai/agents");
2293
+ const isStreaming = options?.stream === true;
2294
+ const finalize = async (result) => {
2295
+ const res = result;
2296
+ if (isStreaming && res?.completed) {
2297
+ try {
2298
+ await res.completed;
2299
+ } catch {
2300
+ }
2301
+ }
2302
+ return res?.finalOutput;
2303
+ };
2304
+ const options_ = { type: "agent", finalize };
2305
+ const traced = this.withSpanFn(
2306
+ this.traceFunctionKey,
2307
+ options_,
2308
+ (agentInput) => run(
2309
+ agent,
2310
+ agentInput,
2311
+ options
2312
+ )
2313
+ );
2314
+ return traced(input);
2315
+ }
2316
+ };
2317
+
2217
2318
  // src/client.ts
2218
2319
  init_replayContext();
2219
2320
 
@@ -2907,6 +3008,34 @@ var Bitfab = class {
2907
3008
  }
2908
3009
  });
2909
3010
  }
3011
+ /**
3012
+ * Get an OpenAI Agents SDK handler that records a replayable root span.
3013
+ *
3014
+ * The processor from {@link getOpenAiTracingProcessor} captures everything
3015
+ * inside a run (LLM calls, tools, handoffs) but never sees the caller's
3016
+ * input, so a processor-only run records an empty-input root and is not
3017
+ * replayable. This handler's `wrapRun` is a drop-in for `run()` that opens a
3018
+ * `withSpan` root carrying the input and final output; the processor's spans
3019
+ * nest beneath it. Register the processor once at startup, then call
3020
+ * `handler.wrapRun(agent, input)` in place of `run(agent, input)`.
3021
+ *
3022
+ * ```typescript
3023
+ * import { addTraceProcessor, Agent, run } from "@openai/agents";
3024
+ *
3025
+ * addTraceProcessor(client.getOpenAiTracingProcessor());
3026
+ * const handler = client.getOpenAiAgentHandler("research-topic");
3027
+ * const result = await handler.wrapRun(agent, "Find X");
3028
+ * ```
3029
+ *
3030
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
3031
+ * @returns A BitfabOpenAIAgentHandler configured for this client
3032
+ */
3033
+ getOpenAiAgentHandler(traceFunctionKey) {
3034
+ return new BitfabOpenAIAgentHandler({
3035
+ traceFunctionKey,
3036
+ withSpan: this.withSpan.bind(this)
3037
+ });
3038
+ }
2910
3039
  /**
2911
3040
  * Get a LangGraph/LangChain callback handler for tracing.
2912
3041
  *
@@ -2960,14 +3089,15 @@ var Bitfab = class {
2960
3089
  * execution as Bitfab spans with proper parent-child hierarchy.
2961
3090
  *
2962
3091
  * ```typescript
3092
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
3093
+ *
2963
3094
  * const handler = client.getClaudeAgentHandler("my-agent");
2964
3095
  * const options = handler.instrumentOptions({
2965
3096
  * model: "claude-sonnet-4-5-...",
2966
3097
  * });
2967
- * const sdkClient = new ClaudeSDKClient(options);
2968
- * await sdkClient.connect();
2969
- * await sdkClient.query("Do something");
2970
- * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
3098
+ * for await (const msg of handler.wrapQuery(
3099
+ * query({ prompt: "Do something", options })
3100
+ * )) {
2971
3101
  * // process messages
2972
3102
  * }
2973
3103
  * ```
@@ -3656,6 +3786,7 @@ var finalizers = {
3656
3786
  BitfabFunction,
3657
3787
  BitfabLangChainCallbackHandler,
3658
3788
  BitfabLangGraphCallbackHandler,
3789
+ BitfabOpenAIAgentHandler,
3659
3790
  BitfabOpenAITracingProcessor,
3660
3791
  DEFAULT_SERVICE_URL,
3661
3792
  ReplayEnvironment,