@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/index.cjs CHANGED
@@ -477,6 +477,7 @@ __export(index_exports, {
477
477
  BitfabFunction: () => BitfabFunction,
478
478
  BitfabLangChainCallbackHandler: () => BitfabLangGraphCallbackHandler,
479
479
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
480
+ BitfabOpenAIAgentHandler: () => BitfabOpenAIAgentHandler,
480
481
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
481
482
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
482
483
  ReplayEnvironment: () => ReplayEnvironment,
@@ -490,7 +491,7 @@ __export(index_exports, {
490
491
  module.exports = __toCommonJS(index_exports);
491
492
 
492
493
  // src/version.generated.ts
493
- var __version__ = "0.21.1";
494
+ var __version__ = "0.22.0";
494
495
 
495
496
  // src/constants.ts
496
497
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -961,6 +962,13 @@ var BitfabClaudeAgentHandler = class {
961
962
  this.currentLlmHistorySnapshot = [];
962
963
  // Subagent tracking
963
964
  this.activeSubagentSpans = /* @__PURE__ */ new Map();
965
+ // Synthetic root span (handler-only replay). When an `input` is supplied to
966
+ // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
967
+ // input, so a handler-instrumented run is replayable WITHOUT an enclosing
968
+ // withSpan — matching the LangGraph handler, which records the graph input as
969
+ // its root. The prompt is not present anywhere in the message stream, so it
970
+ // must be handed in explicitly.
971
+ this.hasRootInput = false;
964
972
  this.httpClient = new HttpClient({
965
973
  apiKey: config.apiKey,
966
974
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -995,7 +1003,30 @@ var BitfabClaudeAgentHandler = class {
995
1003
  return subagentSpanId;
996
1004
  }
997
1005
  }
998
- return this.activeContext?.spanId ?? this.rootSpanId ?? null;
1006
+ return this.rootSpanId ?? this.activeContext?.spanId ?? null;
1007
+ }
1008
+ // Emit the synthetic root `agent` span once, before any child spans. No-op
1009
+ // unless an `input` was supplied AND there is no enclosing withSpan (in which
1010
+ // case that outer span is already the replayable root).
1011
+ maybeStartRootSpan() {
1012
+ if (!this.hasRootInput || this.rootSpanId !== null) {
1013
+ return;
1014
+ }
1015
+ this.ensureTrace();
1016
+ if (this.activeContext !== null) {
1017
+ return;
1018
+ }
1019
+ const spanId = crypto.randomUUID();
1020
+ this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1021
+ this.rootSpanId = spanId;
1022
+ }
1023
+ completeRootSpan() {
1024
+ if (this.rootSpanId === null) {
1025
+ return;
1026
+ }
1027
+ const spanId = this.rootSpanId;
1028
+ this.rootSpanId = null;
1029
+ this.completeSpan(spanId, this.rootOutput);
999
1030
  }
1000
1031
  // ── span helpers ─────────────────────────────────────────────
1001
1032
  startSpan(spanId, name, spanType, inputData, parentId) {
@@ -1191,26 +1222,53 @@ var BitfabClaudeAgentHandler = class {
1191
1222
  return options;
1192
1223
  }
1193
1224
  /**
1194
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
1225
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
1226
+ *
1227
+ * Yields every message unchanged while capturing assistant message
1228
+ * content as LLM turn spans. Kept for naming symmetry with the Python
1229
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
1230
+ * in TypeScript, prefer `wrapQuery` around `query()`.
1195
1231
  *
1196
- * Yields every message unchanged while capturing AssistantMessage
1197
- * content as LLM turn spans.
1232
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
1233
+ * `wrapQuery`.
1198
1234
  */
1199
- async *wrapResponse(stream) {
1235
+ async *wrapResponse(stream, opts) {
1236
+ this.setRootInput(opts);
1200
1237
  yield* this.processStream(stream);
1201
1238
  }
1202
1239
  /**
1203
1240
  * Wrap a `query()` async iterator to capture LLM turns.
1204
1241
  *
1205
- * Same as `wrapResponse` but for the simpler `query()` API
1206
- * which does not support hooks (no tool/subagent spans).
1242
+ * Tool and subagent spans are captured separately via the hooks injected
1243
+ * by `instrumentOptions` into the `options` passed to `query()`.
1244
+ *
1245
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
1246
+ * — to make a handler-only run replayable: the handler records a root `agent`
1247
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
1248
+ * when an enclosing `withSpan` already supplies the replayable root.
1249
+ *
1250
+ * ```typescript
1251
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
1252
+ * ```
1207
1253
  */
1208
- async *wrapQuery(stream) {
1254
+ async *wrapQuery(stream, opts) {
1255
+ this.setRootInput(opts);
1209
1256
  yield* this.processStream(stream);
1210
1257
  }
1258
+ setRootInput(opts) {
1259
+ if (opts && opts.input !== void 0) {
1260
+ this.hasRootInput = true;
1261
+ this.rootInput = opts.input;
1262
+ } else {
1263
+ this.hasRootInput = false;
1264
+ this.rootInput = void 0;
1265
+ }
1266
+ this.rootOutput = void 0;
1267
+ }
1211
1268
  // ── stream processing ────────────────────────────────────────
1212
1269
  async *processStream(stream) {
1213
1270
  try {
1271
+ this.maybeStartRootSpan();
1214
1272
  for await (const message of stream) {
1215
1273
  try {
1216
1274
  this.processMessage(message);
@@ -1221,6 +1279,7 @@ var BitfabClaudeAgentHandler = class {
1221
1279
  } finally {
1222
1280
  try {
1223
1281
  this.flushLlmTurn();
1282
+ this.completeRootSpan();
1224
1283
  this.sendTraceCompletion();
1225
1284
  } catch {
1226
1285
  }
@@ -1228,18 +1287,19 @@ var BitfabClaudeAgentHandler = class {
1228
1287
  }
1229
1288
  }
1230
1289
  processMessage(message) {
1231
- const typeName = message.constructor?.name ?? "";
1232
- if (typeName === "AssistantMessage") {
1290
+ const typeName = message.type;
1291
+ if (typeName === "assistant") {
1233
1292
  this.handleAssistantMessage(message);
1234
- } else if (typeName === "UserMessage") {
1293
+ } else if (typeName === "user") {
1235
1294
  this.handleUserMessage(message);
1236
- } else if (typeName === "ResultMessage") {
1295
+ } else if (typeName === "result") {
1237
1296
  this.handleResultMessage(message);
1238
1297
  }
1239
1298
  }
1240
1299
  handleAssistantMessage(message) {
1241
1300
  this.ensureTrace();
1242
- const messageId = message.message_id;
1301
+ const inner = message.message ?? {};
1302
+ const messageId = inner.id ?? message.uuid;
1243
1303
  if (messageId !== this.currentLlmMessageId) {
1244
1304
  this.flushLlmTurn();
1245
1305
  this.conversationHistory.push(...this.pendingMessages);
@@ -1247,26 +1307,27 @@ var BitfabClaudeAgentHandler = class {
1247
1307
  this.currentLlmSpanId = crypto.randomUUID();
1248
1308
  this.currentLlmMessageId = messageId ?? null;
1249
1309
  this.currentLlmContent = [];
1250
- this.currentLlmModel = message.model ?? null;
1310
+ this.currentLlmModel = inner.model ?? null;
1251
1311
  this.currentLlmUsage = {};
1252
1312
  this.currentLlmStartedAt = nowIso();
1253
1313
  this.currentLlmHistorySnapshot = [...this.conversationHistory];
1254
1314
  }
1255
- const content = message.content;
1315
+ const content = inner.content;
1256
1316
  if (Array.isArray(content)) {
1257
1317
  this.currentLlmContent.push(...extractContentBlocks(content));
1258
1318
  }
1259
- const usage = extractUsage(message);
1319
+ const usage = extractUsage(inner);
1260
1320
  if (Object.keys(usage).length > 0) {
1261
1321
  Object.assign(this.currentLlmUsage, usage);
1262
1322
  }
1263
- const model = message.model;
1323
+ const model = inner.model;
1264
1324
  if (model) {
1265
1325
  this.currentLlmModel = model;
1266
1326
  }
1267
1327
  }
1268
1328
  handleUserMessage(message) {
1269
- const content = message.content;
1329
+ const inner = message.message ?? {};
1330
+ const content = inner.content;
1270
1331
  const toolUseResult = message.tool_use_result;
1271
1332
  if (toolUseResult !== void 0) {
1272
1333
  this.pendingMessages.push({
@@ -1283,6 +1344,10 @@ var BitfabClaudeAgentHandler = class {
1283
1344
  }
1284
1345
  handleResultMessage(message) {
1285
1346
  this.flushLlmTurn();
1347
+ if (message.result !== void 0) {
1348
+ this.rootOutput = message.result;
1349
+ }
1350
+ this.completeRootSpan();
1286
1351
  const metadata = {};
1287
1352
  for (const attr of [
1288
1353
  "num_turns",
@@ -1346,6 +1411,9 @@ var BitfabClaudeAgentHandler = class {
1346
1411
  this.runToSpan.clear();
1347
1412
  this.traceId = null;
1348
1413
  this.rootSpanId = null;
1414
+ this.hasRootInput = false;
1415
+ this.rootInput = void 0;
1416
+ this.rootOutput = void 0;
1349
1417
  this.activeContext = null;
1350
1418
  this.traceStartedAt = null;
1351
1419
  this.conversationHistory = [];
@@ -2205,6 +2273,39 @@ var BitfabLangGraphCallbackHandler = class {
2205
2273
  }
2206
2274
  };
2207
2275
 
2276
+ // src/openaiAgentSdk.ts
2277
+ var BitfabOpenAIAgentHandler = class {
2278
+ constructor(config) {
2279
+ this.traceFunctionKey = config.traceFunctionKey;
2280
+ this.withSpanFn = config.withSpan;
2281
+ }
2282
+ async wrapRun(agent, input, options) {
2283
+ const { run } = await import("@openai/agents");
2284
+ const isStreaming = options?.stream === true;
2285
+ const finalize = async (result) => {
2286
+ const res = result;
2287
+ if (isStreaming && res?.completed) {
2288
+ try {
2289
+ await res.completed;
2290
+ } catch {
2291
+ }
2292
+ }
2293
+ return res?.finalOutput;
2294
+ };
2295
+ const options_ = { type: "agent", finalize };
2296
+ const traced = this.withSpanFn(
2297
+ this.traceFunctionKey,
2298
+ options_,
2299
+ (agentInput) => run(
2300
+ agent,
2301
+ agentInput,
2302
+ options
2303
+ )
2304
+ );
2305
+ return traced(input);
2306
+ }
2307
+ };
2308
+
2208
2309
  // src/client.ts
2209
2310
  init_replayContext();
2210
2311
 
@@ -2898,6 +2999,34 @@ var Bitfab = class {
2898
2999
  }
2899
3000
  });
2900
3001
  }
3002
+ /**
3003
+ * Get an OpenAI Agents SDK handler that records a replayable root span.
3004
+ *
3005
+ * The processor from {@link getOpenAiTracingProcessor} captures everything
3006
+ * inside a run (LLM calls, tools, handoffs) but never sees the caller's
3007
+ * input, so a processor-only run records an empty-input root and is not
3008
+ * replayable. This handler's `wrapRun` is a drop-in for `run()` that opens a
3009
+ * `withSpan` root carrying the input and final output; the processor's spans
3010
+ * nest beneath it. Register the processor once at startup, then call
3011
+ * `handler.wrapRun(agent, input)` in place of `run(agent, input)`.
3012
+ *
3013
+ * ```typescript
3014
+ * import { addTraceProcessor, Agent, run } from "@openai/agents";
3015
+ *
3016
+ * addTraceProcessor(client.getOpenAiTracingProcessor());
3017
+ * const handler = client.getOpenAiAgentHandler("research-topic");
3018
+ * const result = await handler.wrapRun(agent, "Find X");
3019
+ * ```
3020
+ *
3021
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
3022
+ * @returns A BitfabOpenAIAgentHandler configured for this client
3023
+ */
3024
+ getOpenAiAgentHandler(traceFunctionKey) {
3025
+ return new BitfabOpenAIAgentHandler({
3026
+ traceFunctionKey,
3027
+ withSpan: this.withSpan.bind(this)
3028
+ });
3029
+ }
2901
3030
  /**
2902
3031
  * Get a LangGraph/LangChain callback handler for tracing.
2903
3032
  *
@@ -2951,14 +3080,15 @@ var Bitfab = class {
2951
3080
  * execution as Bitfab spans with proper parent-child hierarchy.
2952
3081
  *
2953
3082
  * ```typescript
3083
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
3084
+ *
2954
3085
  * const handler = client.getClaudeAgentHandler("my-agent");
2955
3086
  * const options = handler.instrumentOptions({
2956
3087
  * model: "claude-sonnet-4-5-...",
2957
3088
  * });
2958
- * const sdkClient = new ClaudeSDKClient(options);
2959
- * await sdkClient.connect();
2960
- * await sdkClient.query("Do something");
2961
- * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
3089
+ * for await (const msg of handler.wrapQuery(
3090
+ * query({ prompt: "Do something", options })
3091
+ * )) {
2962
3092
  * // process messages
2963
3093
  * }
2964
3094
  * ```
@@ -3647,6 +3777,7 @@ var finalizers = {
3647
3777
  BitfabFunction,
3648
3778
  BitfabLangChainCallbackHandler,
3649
3779
  BitfabLangGraphCallbackHandler,
3780
+ BitfabOpenAIAgentHandler,
3650
3781
  BitfabOpenAITracingProcessor,
3651
3782
  DEFAULT_SERVICE_URL,
3652
3783
  ReplayEnvironment,