@bitfab/sdk 0.21.1 → 0.21.2

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
@@ -490,7 +490,7 @@ __export(index_exports, {
490
490
  module.exports = __toCommonJS(index_exports);
491
491
 
492
492
  // src/version.generated.ts
493
- var __version__ = "0.21.1";
493
+ var __version__ = "0.21.2";
494
494
 
495
495
  // src/constants.ts
496
496
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -961,6 +961,13 @@ var BitfabClaudeAgentHandler = class {
961
961
  this.currentLlmHistorySnapshot = [];
962
962
  // Subagent tracking
963
963
  this.activeSubagentSpans = /* @__PURE__ */ new Map();
964
+ // Synthetic root span (handler-only replay). When an `input` is supplied to
965
+ // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
966
+ // input, so a handler-instrumented run is replayable WITHOUT an enclosing
967
+ // withSpan — matching the LangGraph handler, which records the graph input as
968
+ // its root. The prompt is not present anywhere in the message stream, so it
969
+ // must be handed in explicitly.
970
+ this.hasRootInput = false;
964
971
  this.httpClient = new HttpClient({
965
972
  apiKey: config.apiKey,
966
973
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -995,7 +1002,30 @@ var BitfabClaudeAgentHandler = class {
995
1002
  return subagentSpanId;
996
1003
  }
997
1004
  }
998
- return this.activeContext?.spanId ?? this.rootSpanId ?? null;
1005
+ return this.rootSpanId ?? this.activeContext?.spanId ?? null;
1006
+ }
1007
+ // Emit the synthetic root `agent` span once, before any child spans. No-op
1008
+ // unless an `input` was supplied AND there is no enclosing withSpan (in which
1009
+ // case that outer span is already the replayable root).
1010
+ maybeStartRootSpan() {
1011
+ if (!this.hasRootInput || this.rootSpanId !== null) {
1012
+ return;
1013
+ }
1014
+ this.ensureTrace();
1015
+ if (this.activeContext !== null) {
1016
+ return;
1017
+ }
1018
+ const spanId = crypto.randomUUID();
1019
+ this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1020
+ this.rootSpanId = spanId;
1021
+ }
1022
+ completeRootSpan() {
1023
+ if (this.rootSpanId === null) {
1024
+ return;
1025
+ }
1026
+ const spanId = this.rootSpanId;
1027
+ this.rootSpanId = null;
1028
+ this.completeSpan(spanId, this.rootOutput);
999
1029
  }
1000
1030
  // ── span helpers ─────────────────────────────────────────────
1001
1031
  startSpan(spanId, name, spanType, inputData, parentId) {
@@ -1191,26 +1221,53 @@ var BitfabClaudeAgentHandler = class {
1191
1221
  return options;
1192
1222
  }
1193
1223
  /**
1194
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
1224
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
1195
1225
  *
1196
- * Yields every message unchanged while capturing AssistantMessage
1197
- * content as LLM turn spans.
1226
+ * Yields every message unchanged while capturing assistant message
1227
+ * content as LLM turn spans. Kept for naming symmetry with the Python
1228
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
1229
+ * in TypeScript, prefer `wrapQuery` around `query()`.
1230
+ *
1231
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
1232
+ * `wrapQuery`.
1198
1233
  */
1199
- async *wrapResponse(stream) {
1234
+ async *wrapResponse(stream, opts) {
1235
+ this.setRootInput(opts);
1200
1236
  yield* this.processStream(stream);
1201
1237
  }
1202
1238
  /**
1203
1239
  * Wrap a `query()` async iterator to capture LLM turns.
1204
1240
  *
1205
- * Same as `wrapResponse` but for the simpler `query()` API
1206
- * which does not support hooks (no tool/subagent spans).
1241
+ * Tool and subagent spans are captured separately via the hooks injected
1242
+ * by `instrumentOptions` into the `options` passed to `query()`.
1243
+ *
1244
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
1245
+ * — to make a handler-only run replayable: the handler records a root `agent`
1246
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
1247
+ * when an enclosing `withSpan` already supplies the replayable root.
1248
+ *
1249
+ * ```typescript
1250
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
1251
+ * ```
1207
1252
  */
1208
- async *wrapQuery(stream) {
1253
+ async *wrapQuery(stream, opts) {
1254
+ this.setRootInput(opts);
1209
1255
  yield* this.processStream(stream);
1210
1256
  }
1257
+ setRootInput(opts) {
1258
+ if (opts && opts.input !== void 0) {
1259
+ this.hasRootInput = true;
1260
+ this.rootInput = opts.input;
1261
+ } else {
1262
+ this.hasRootInput = false;
1263
+ this.rootInput = void 0;
1264
+ }
1265
+ this.rootOutput = void 0;
1266
+ }
1211
1267
  // ── stream processing ────────────────────────────────────────
1212
1268
  async *processStream(stream) {
1213
1269
  try {
1270
+ this.maybeStartRootSpan();
1214
1271
  for await (const message of stream) {
1215
1272
  try {
1216
1273
  this.processMessage(message);
@@ -1221,6 +1278,7 @@ var BitfabClaudeAgentHandler = class {
1221
1278
  } finally {
1222
1279
  try {
1223
1280
  this.flushLlmTurn();
1281
+ this.completeRootSpan();
1224
1282
  this.sendTraceCompletion();
1225
1283
  } catch {
1226
1284
  }
@@ -1228,18 +1286,19 @@ var BitfabClaudeAgentHandler = class {
1228
1286
  }
1229
1287
  }
1230
1288
  processMessage(message) {
1231
- const typeName = message.constructor?.name ?? "";
1232
- if (typeName === "AssistantMessage") {
1289
+ const typeName = message.type;
1290
+ if (typeName === "assistant") {
1233
1291
  this.handleAssistantMessage(message);
1234
- } else if (typeName === "UserMessage") {
1292
+ } else if (typeName === "user") {
1235
1293
  this.handleUserMessage(message);
1236
- } else if (typeName === "ResultMessage") {
1294
+ } else if (typeName === "result") {
1237
1295
  this.handleResultMessage(message);
1238
1296
  }
1239
1297
  }
1240
1298
  handleAssistantMessage(message) {
1241
1299
  this.ensureTrace();
1242
- const messageId = message.message_id;
1300
+ const inner = message.message ?? {};
1301
+ const messageId = inner.id ?? message.uuid;
1243
1302
  if (messageId !== this.currentLlmMessageId) {
1244
1303
  this.flushLlmTurn();
1245
1304
  this.conversationHistory.push(...this.pendingMessages);
@@ -1247,26 +1306,27 @@ var BitfabClaudeAgentHandler = class {
1247
1306
  this.currentLlmSpanId = crypto.randomUUID();
1248
1307
  this.currentLlmMessageId = messageId ?? null;
1249
1308
  this.currentLlmContent = [];
1250
- this.currentLlmModel = message.model ?? null;
1309
+ this.currentLlmModel = inner.model ?? null;
1251
1310
  this.currentLlmUsage = {};
1252
1311
  this.currentLlmStartedAt = nowIso();
1253
1312
  this.currentLlmHistorySnapshot = [...this.conversationHistory];
1254
1313
  }
1255
- const content = message.content;
1314
+ const content = inner.content;
1256
1315
  if (Array.isArray(content)) {
1257
1316
  this.currentLlmContent.push(...extractContentBlocks(content));
1258
1317
  }
1259
- const usage = extractUsage(message);
1318
+ const usage = extractUsage(inner);
1260
1319
  if (Object.keys(usage).length > 0) {
1261
1320
  Object.assign(this.currentLlmUsage, usage);
1262
1321
  }
1263
- const model = message.model;
1322
+ const model = inner.model;
1264
1323
  if (model) {
1265
1324
  this.currentLlmModel = model;
1266
1325
  }
1267
1326
  }
1268
1327
  handleUserMessage(message) {
1269
- const content = message.content;
1328
+ const inner = message.message ?? {};
1329
+ const content = inner.content;
1270
1330
  const toolUseResult = message.tool_use_result;
1271
1331
  if (toolUseResult !== void 0) {
1272
1332
  this.pendingMessages.push({
@@ -1283,6 +1343,10 @@ var BitfabClaudeAgentHandler = class {
1283
1343
  }
1284
1344
  handleResultMessage(message) {
1285
1345
  this.flushLlmTurn();
1346
+ if (message.result !== void 0) {
1347
+ this.rootOutput = message.result;
1348
+ }
1349
+ this.completeRootSpan();
1286
1350
  const metadata = {};
1287
1351
  for (const attr of [
1288
1352
  "num_turns",
@@ -1346,6 +1410,9 @@ var BitfabClaudeAgentHandler = class {
1346
1410
  this.runToSpan.clear();
1347
1411
  this.traceId = null;
1348
1412
  this.rootSpanId = null;
1413
+ this.hasRootInput = false;
1414
+ this.rootInput = void 0;
1415
+ this.rootOutput = void 0;
1349
1416
  this.activeContext = null;
1350
1417
  this.traceStartedAt = null;
1351
1418
  this.conversationHistory = [];
@@ -2951,14 +3018,15 @@ var Bitfab = class {
2951
3018
  * execution as Bitfab spans with proper parent-child hierarchy.
2952
3019
  *
2953
3020
  * ```typescript
3021
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
3022
+ *
2954
3023
  * const handler = client.getClaudeAgentHandler("my-agent");
2955
3024
  * const options = handler.instrumentOptions({
2956
3025
  * model: "claude-sonnet-4-5-...",
2957
3026
  * });
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())) {
3027
+ * for await (const msg of handler.wrapQuery(
3028
+ * query({ prompt: "Do something", options })
3029
+ * )) {
2962
3030
  * // process messages
2963
3031
  * }
2964
3032
  * ```