bitfab 0.21.0 → 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
@@ -499,7 +499,7 @@ __export(index_exports, {
499
499
  module.exports = __toCommonJS(index_exports);
500
500
 
501
501
  // src/version.generated.ts
502
- var __version__ = "0.21.0";
502
+ var __version__ = "0.21.2";
503
503
 
504
504
  // src/constants.ts
505
505
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -970,6 +970,13 @@ var BitfabClaudeAgentHandler = class {
970
970
  this.currentLlmHistorySnapshot = [];
971
971
  // Subagent tracking
972
972
  this.activeSubagentSpans = /* @__PURE__ */ new Map();
973
+ // Synthetic root span (handler-only replay). When an `input` is supplied to
974
+ // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
975
+ // input, so a handler-instrumented run is replayable WITHOUT an enclosing
976
+ // withSpan — matching the LangGraph handler, which records the graph input as
977
+ // its root. The prompt is not present anywhere in the message stream, so it
978
+ // must be handed in explicitly.
979
+ this.hasRootInput = false;
973
980
  this.httpClient = new HttpClient({
974
981
  apiKey: config.apiKey,
975
982
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -1004,7 +1011,30 @@ var BitfabClaudeAgentHandler = class {
1004
1011
  return subagentSpanId;
1005
1012
  }
1006
1013
  }
1007
- return this.activeContext?.spanId ?? this.rootSpanId ?? null;
1014
+ return this.rootSpanId ?? this.activeContext?.spanId ?? null;
1015
+ }
1016
+ // Emit the synthetic root `agent` span once, before any child spans. No-op
1017
+ // unless an `input` was supplied AND there is no enclosing withSpan (in which
1018
+ // case that outer span is already the replayable root).
1019
+ maybeStartRootSpan() {
1020
+ if (!this.hasRootInput || this.rootSpanId !== null) {
1021
+ return;
1022
+ }
1023
+ this.ensureTrace();
1024
+ if (this.activeContext !== null) {
1025
+ return;
1026
+ }
1027
+ const spanId = crypto.randomUUID();
1028
+ this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1029
+ this.rootSpanId = spanId;
1030
+ }
1031
+ completeRootSpan() {
1032
+ if (this.rootSpanId === null) {
1033
+ return;
1034
+ }
1035
+ const spanId = this.rootSpanId;
1036
+ this.rootSpanId = null;
1037
+ this.completeSpan(spanId, this.rootOutput);
1008
1038
  }
1009
1039
  // ── span helpers ─────────────────────────────────────────────
1010
1040
  startSpan(spanId, name, spanType, inputData, parentId) {
@@ -1200,26 +1230,53 @@ var BitfabClaudeAgentHandler = class {
1200
1230
  return options;
1201
1231
  }
1202
1232
  /**
1203
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
1233
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
1234
+ *
1235
+ * Yields every message unchanged while capturing assistant message
1236
+ * content as LLM turn spans. Kept for naming symmetry with the Python
1237
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
1238
+ * in TypeScript, prefer `wrapQuery` around `query()`.
1204
1239
  *
1205
- * Yields every message unchanged while capturing AssistantMessage
1206
- * content as LLM turn spans.
1240
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
1241
+ * `wrapQuery`.
1207
1242
  */
1208
- async *wrapResponse(stream) {
1243
+ async *wrapResponse(stream, opts) {
1244
+ this.setRootInput(opts);
1209
1245
  yield* this.processStream(stream);
1210
1246
  }
1211
1247
  /**
1212
1248
  * Wrap a `query()` async iterator to capture LLM turns.
1213
1249
  *
1214
- * Same as `wrapResponse` but for the simpler `query()` API
1215
- * which does not support hooks (no tool/subagent spans).
1250
+ * Tool and subagent spans are captured separately via the hooks injected
1251
+ * by `instrumentOptions` into the `options` passed to `query()`.
1252
+ *
1253
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
1254
+ * — to make a handler-only run replayable: the handler records a root `agent`
1255
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
1256
+ * when an enclosing `withSpan` already supplies the replayable root.
1257
+ *
1258
+ * ```typescript
1259
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
1260
+ * ```
1216
1261
  */
1217
- async *wrapQuery(stream) {
1262
+ async *wrapQuery(stream, opts) {
1263
+ this.setRootInput(opts);
1218
1264
  yield* this.processStream(stream);
1219
1265
  }
1266
+ setRootInput(opts) {
1267
+ if (opts && opts.input !== void 0) {
1268
+ this.hasRootInput = true;
1269
+ this.rootInput = opts.input;
1270
+ } else {
1271
+ this.hasRootInput = false;
1272
+ this.rootInput = void 0;
1273
+ }
1274
+ this.rootOutput = void 0;
1275
+ }
1220
1276
  // ── stream processing ────────────────────────────────────────
1221
1277
  async *processStream(stream) {
1222
1278
  try {
1279
+ this.maybeStartRootSpan();
1223
1280
  for await (const message of stream) {
1224
1281
  try {
1225
1282
  this.processMessage(message);
@@ -1230,6 +1287,7 @@ var BitfabClaudeAgentHandler = class {
1230
1287
  } finally {
1231
1288
  try {
1232
1289
  this.flushLlmTurn();
1290
+ this.completeRootSpan();
1233
1291
  this.sendTraceCompletion();
1234
1292
  } catch {
1235
1293
  }
@@ -1237,18 +1295,19 @@ var BitfabClaudeAgentHandler = class {
1237
1295
  }
1238
1296
  }
1239
1297
  processMessage(message) {
1240
- const typeName = message.constructor?.name ?? "";
1241
- if (typeName === "AssistantMessage") {
1298
+ const typeName = message.type;
1299
+ if (typeName === "assistant") {
1242
1300
  this.handleAssistantMessage(message);
1243
- } else if (typeName === "UserMessage") {
1301
+ } else if (typeName === "user") {
1244
1302
  this.handleUserMessage(message);
1245
- } else if (typeName === "ResultMessage") {
1303
+ } else if (typeName === "result") {
1246
1304
  this.handleResultMessage(message);
1247
1305
  }
1248
1306
  }
1249
1307
  handleAssistantMessage(message) {
1250
1308
  this.ensureTrace();
1251
- const messageId = message.message_id;
1309
+ const inner = message.message ?? {};
1310
+ const messageId = inner.id ?? message.uuid;
1252
1311
  if (messageId !== this.currentLlmMessageId) {
1253
1312
  this.flushLlmTurn();
1254
1313
  this.conversationHistory.push(...this.pendingMessages);
@@ -1256,26 +1315,27 @@ var BitfabClaudeAgentHandler = class {
1256
1315
  this.currentLlmSpanId = crypto.randomUUID();
1257
1316
  this.currentLlmMessageId = messageId ?? null;
1258
1317
  this.currentLlmContent = [];
1259
- this.currentLlmModel = message.model ?? null;
1318
+ this.currentLlmModel = inner.model ?? null;
1260
1319
  this.currentLlmUsage = {};
1261
1320
  this.currentLlmStartedAt = nowIso();
1262
1321
  this.currentLlmHistorySnapshot = [...this.conversationHistory];
1263
1322
  }
1264
- const content = message.content;
1323
+ const content = inner.content;
1265
1324
  if (Array.isArray(content)) {
1266
1325
  this.currentLlmContent.push(...extractContentBlocks(content));
1267
1326
  }
1268
- const usage = extractUsage(message);
1327
+ const usage = extractUsage(inner);
1269
1328
  if (Object.keys(usage).length > 0) {
1270
1329
  Object.assign(this.currentLlmUsage, usage);
1271
1330
  }
1272
- const model = message.model;
1331
+ const model = inner.model;
1273
1332
  if (model) {
1274
1333
  this.currentLlmModel = model;
1275
1334
  }
1276
1335
  }
1277
1336
  handleUserMessage(message) {
1278
- const content = message.content;
1337
+ const inner = message.message ?? {};
1338
+ const content = inner.content;
1279
1339
  const toolUseResult = message.tool_use_result;
1280
1340
  if (toolUseResult !== void 0) {
1281
1341
  this.pendingMessages.push({
@@ -1292,6 +1352,10 @@ var BitfabClaudeAgentHandler = class {
1292
1352
  }
1293
1353
  handleResultMessage(message) {
1294
1354
  this.flushLlmTurn();
1355
+ if (message.result !== void 0) {
1356
+ this.rootOutput = message.result;
1357
+ }
1358
+ this.completeRootSpan();
1295
1359
  const metadata = {};
1296
1360
  for (const attr of [
1297
1361
  "num_turns",
@@ -1355,6 +1419,9 @@ var BitfabClaudeAgentHandler = class {
1355
1419
  this.runToSpan.clear();
1356
1420
  this.traceId = null;
1357
1421
  this.rootSpanId = null;
1422
+ this.hasRootInput = false;
1423
+ this.rootInput = void 0;
1424
+ this.rootOutput = void 0;
1358
1425
  this.activeContext = null;
1359
1426
  this.traceStartedAt = null;
1360
1427
  this.conversationHistory = [];
@@ -2432,9 +2499,15 @@ var BitfabOpenAITracingProcessor = class {
2432
2499
  * Extract and add input/response to serialized span, updating errors list.
2433
2500
  */
2434
2501
  extractSpanInputResponse(span, serializedSpan, errors) {
2502
+ if (span.spanData?.type !== "response") {
2503
+ return;
2504
+ }
2435
2505
  const spanData = serializedSpan.span_data;
2436
2506
  try {
2437
- spanData.input = span.spanData?._input || [];
2507
+ const input = span.spanData?._input;
2508
+ if (input !== void 0) {
2509
+ spanData.input = input;
2510
+ }
2438
2511
  } catch (error) {
2439
2512
  errors.push({
2440
2513
  source: "sdk",
@@ -2443,7 +2516,10 @@ var BitfabOpenAITracingProcessor = class {
2443
2516
  });
2444
2517
  }
2445
2518
  try {
2446
- spanData.response = span.spanData?._response || null;
2519
+ const response = span.spanData?._response;
2520
+ if (response !== void 0) {
2521
+ spanData.response = response;
2522
+ }
2447
2523
  } catch (error) {
2448
2524
  errors.push({
2449
2525
  source: "sdk",
@@ -2951,14 +3027,15 @@ var Bitfab = class {
2951
3027
  * execution as Bitfab spans with proper parent-child hierarchy.
2952
3028
  *
2953
3029
  * ```typescript
3030
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
3031
+ *
2954
3032
  * const handler = client.getClaudeAgentHandler("my-agent");
2955
3033
  * const options = handler.instrumentOptions({
2956
3034
  * model: "claude-sonnet-4-5-...",
2957
3035
  * });
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())) {
3036
+ * for await (const msg of handler.wrapQuery(
3037
+ * query({ prompt: "Do something", options })
3038
+ * )) {
2962
3039
  * // process messages
2963
3040
  * }
2964
3041
  * ```