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.d.cts CHANGED
@@ -52,9 +52,14 @@ interface ActiveSpanContext$2 {
52
52
  * Captures LLM turns, tool invocations, and subagent execution as
53
53
  * Bitfab spans with proper parent-child hierarchy.
54
54
  *
55
+ * The TypeScript Claude Agent SDK exposes a single `query()` entry point (there
56
+ * is no `ClaudeSDKClient` class — that exists only in the Python SDK). Wrap the
57
+ * `query()` async iterator with `wrapQuery`; tool and subagent spans come from
58
+ * the hooks injected by `instrumentOptions`.
59
+ *
55
60
  * ```typescript
56
61
  * import { Bitfab } from "@bitfab/sdk";
57
- * import { ClaudeSDKClient } from "@anthropic-ai/claude-agent-sdk";
62
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
58
63
  *
59
64
  * const bitfab = new Bitfab({ apiKey: "..." });
60
65
  * const handler = bitfab.getClaudeAgentHandler("my-agent");
@@ -63,11 +68,9 @@ interface ActiveSpanContext$2 {
63
68
  * model: "claude-sonnet-4-5-...",
64
69
  * });
65
70
  *
66
- * const client = new ClaudeSDKClient(options);
67
- * await client.connect();
68
- * await client.query("Do something");
69
- *
70
- * for await (const message of handler.wrapResponse(client.receiveResponse())) {
71
+ * for await (const message of handler.wrapQuery(
72
+ * query({ prompt: "Do something", options })
73
+ * )) {
71
74
  * // process messages normally
72
75
  * }
73
76
  * ```
@@ -91,6 +94,9 @@ declare class BitfabClaudeAgentHandler {
91
94
  private currentLlmStartedAt;
92
95
  private currentLlmHistorySnapshot;
93
96
  private activeSubagentSpans;
97
+ private hasRootInput;
98
+ private rootInput;
99
+ private rootOutput;
94
100
  constructor(config: {
95
101
  apiKey?: string;
96
102
  traceFunctionKey: string;
@@ -100,6 +106,8 @@ declare class BitfabClaudeAgentHandler {
100
106
  });
101
107
  private ensureTrace;
102
108
  private getParentId;
109
+ private maybeStartRootSpan;
110
+ private completeRootSpan;
103
111
  private startSpan;
104
112
  private completeSpan;
105
113
  private sendSpan;
@@ -122,19 +130,38 @@ declare class BitfabClaudeAgentHandler {
122
130
  */
123
131
  instrumentOptions<T extends Record<string, unknown>>(options: T): T;
124
132
  /**
125
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
133
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
126
134
  *
127
- * Yields every message unchanged while capturing AssistantMessage
128
- * content as LLM turn spans.
135
+ * Yields every message unchanged while capturing assistant message
136
+ * content as LLM turn spans. Kept for naming symmetry with the Python
137
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
138
+ * in TypeScript, prefer `wrapQuery` around `query()`.
139
+ *
140
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
141
+ * `wrapQuery`.
129
142
  */
130
- wrapResponse(stream: AsyncIterable<unknown>): AsyncIterable<unknown>;
143
+ wrapResponse(stream: AsyncIterable<unknown>, opts?: {
144
+ input?: unknown;
145
+ }): AsyncIterable<unknown>;
131
146
  /**
132
147
  * Wrap a `query()` async iterator to capture LLM turns.
133
148
  *
134
- * Same as `wrapResponse` but for the simpler `query()` API
135
- * which does not support hooks (no tool/subagent spans).
149
+ * Tool and subagent spans are captured separately via the hooks injected
150
+ * by `instrumentOptions` into the `options` passed to `query()`.
151
+ *
152
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
153
+ * — to make a handler-only run replayable: the handler records a root `agent`
154
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
155
+ * when an enclosing `withSpan` already supplies the replayable root.
156
+ *
157
+ * ```typescript
158
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
159
+ * ```
136
160
  */
137
- wrapQuery(stream: AsyncIterable<unknown>): AsyncIterable<unknown>;
161
+ wrapQuery(stream: AsyncIterable<unknown>, opts?: {
162
+ input?: unknown;
163
+ }): AsyncIterable<unknown>;
164
+ private setRootInput;
138
165
  private processStream;
139
166
  private processMessage;
140
167
  private handleAssistantMessage;
@@ -877,14 +904,15 @@ declare class Bitfab {
877
904
  * execution as Bitfab spans with proper parent-child hierarchy.
878
905
  *
879
906
  * ```typescript
907
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
908
+ *
880
909
  * const handler = client.getClaudeAgentHandler("my-agent");
881
910
  * const options = handler.instrumentOptions({
882
911
  * model: "claude-sonnet-4-5-...",
883
912
  * });
884
- * const sdkClient = new ClaudeSDKClient(options);
885
- * await sdkClient.connect();
886
- * await sdkClient.query("Do something");
887
- * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
913
+ * for await (const msg of handler.wrapQuery(
914
+ * query({ prompt: "Do something", options })
915
+ * )) {
888
916
  * // process messages
889
917
  * }
890
918
  * ```
@@ -1089,7 +1117,7 @@ declare class BitfabFunction {
1089
1117
  /**
1090
1118
  * SDK version from package.json (injected at build time)
1091
1119
  */
1092
- declare const __version__ = "0.21.0";
1120
+ declare const __version__ = "0.21.2";
1093
1121
 
1094
1122
  /**
1095
1123
  * Constants for the Bitfab SDK.
package/dist/index.d.ts CHANGED
@@ -52,9 +52,14 @@ interface ActiveSpanContext$2 {
52
52
  * Captures LLM turns, tool invocations, and subagent execution as
53
53
  * Bitfab spans with proper parent-child hierarchy.
54
54
  *
55
+ * The TypeScript Claude Agent SDK exposes a single `query()` entry point (there
56
+ * is no `ClaudeSDKClient` class — that exists only in the Python SDK). Wrap the
57
+ * `query()` async iterator with `wrapQuery`; tool and subagent spans come from
58
+ * the hooks injected by `instrumentOptions`.
59
+ *
55
60
  * ```typescript
56
61
  * import { Bitfab } from "@bitfab/sdk";
57
- * import { ClaudeSDKClient } from "@anthropic-ai/claude-agent-sdk";
62
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
58
63
  *
59
64
  * const bitfab = new Bitfab({ apiKey: "..." });
60
65
  * const handler = bitfab.getClaudeAgentHandler("my-agent");
@@ -63,11 +68,9 @@ interface ActiveSpanContext$2 {
63
68
  * model: "claude-sonnet-4-5-...",
64
69
  * });
65
70
  *
66
- * const client = new ClaudeSDKClient(options);
67
- * await client.connect();
68
- * await client.query("Do something");
69
- *
70
- * for await (const message of handler.wrapResponse(client.receiveResponse())) {
71
+ * for await (const message of handler.wrapQuery(
72
+ * query({ prompt: "Do something", options })
73
+ * )) {
71
74
  * // process messages normally
72
75
  * }
73
76
  * ```
@@ -91,6 +94,9 @@ declare class BitfabClaudeAgentHandler {
91
94
  private currentLlmStartedAt;
92
95
  private currentLlmHistorySnapshot;
93
96
  private activeSubagentSpans;
97
+ private hasRootInput;
98
+ private rootInput;
99
+ private rootOutput;
94
100
  constructor(config: {
95
101
  apiKey?: string;
96
102
  traceFunctionKey: string;
@@ -100,6 +106,8 @@ declare class BitfabClaudeAgentHandler {
100
106
  });
101
107
  private ensureTrace;
102
108
  private getParentId;
109
+ private maybeStartRootSpan;
110
+ private completeRootSpan;
103
111
  private startSpan;
104
112
  private completeSpan;
105
113
  private sendSpan;
@@ -122,19 +130,38 @@ declare class BitfabClaudeAgentHandler {
122
130
  */
123
131
  instrumentOptions<T extends Record<string, unknown>>(options: T): T;
124
132
  /**
125
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
133
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
126
134
  *
127
- * Yields every message unchanged while capturing AssistantMessage
128
- * content as LLM turn spans.
135
+ * Yields every message unchanged while capturing assistant message
136
+ * content as LLM turn spans. Kept for naming symmetry with the Python
137
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
138
+ * in TypeScript, prefer `wrapQuery` around `query()`.
139
+ *
140
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
141
+ * `wrapQuery`.
129
142
  */
130
- wrapResponse(stream: AsyncIterable<unknown>): AsyncIterable<unknown>;
143
+ wrapResponse(stream: AsyncIterable<unknown>, opts?: {
144
+ input?: unknown;
145
+ }): AsyncIterable<unknown>;
131
146
  /**
132
147
  * Wrap a `query()` async iterator to capture LLM turns.
133
148
  *
134
- * Same as `wrapResponse` but for the simpler `query()` API
135
- * which does not support hooks (no tool/subagent spans).
149
+ * Tool and subagent spans are captured separately via the hooks injected
150
+ * by `instrumentOptions` into the `options` passed to `query()`.
151
+ *
152
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
153
+ * — to make a handler-only run replayable: the handler records a root `agent`
154
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
155
+ * when an enclosing `withSpan` already supplies the replayable root.
156
+ *
157
+ * ```typescript
158
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
159
+ * ```
136
160
  */
137
- wrapQuery(stream: AsyncIterable<unknown>): AsyncIterable<unknown>;
161
+ wrapQuery(stream: AsyncIterable<unknown>, opts?: {
162
+ input?: unknown;
163
+ }): AsyncIterable<unknown>;
164
+ private setRootInput;
138
165
  private processStream;
139
166
  private processMessage;
140
167
  private handleAssistantMessage;
@@ -877,14 +904,15 @@ declare class Bitfab {
877
904
  * execution as Bitfab spans with proper parent-child hierarchy.
878
905
  *
879
906
  * ```typescript
907
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
908
+ *
880
909
  * const handler = client.getClaudeAgentHandler("my-agent");
881
910
  * const options = handler.instrumentOptions({
882
911
  * model: "claude-sonnet-4-5-...",
883
912
  * });
884
- * const sdkClient = new ClaudeSDKClient(options);
885
- * await sdkClient.connect();
886
- * await sdkClient.query("Do something");
887
- * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
913
+ * for await (const msg of handler.wrapQuery(
914
+ * query({ prompt: "Do something", options })
915
+ * )) {
888
916
  * // process messages
889
917
  * }
890
918
  * ```
@@ -1089,7 +1117,7 @@ declare class BitfabFunction {
1089
1117
  /**
1090
1118
  * SDK version from package.json (injected at build time)
1091
1119
  */
1092
- declare const __version__ = "0.21.0";
1120
+ declare const __version__ = "0.21.2";
1093
1121
 
1094
1122
  /**
1095
1123
  * Constants for the Bitfab SDK.
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  flushTraces,
22
22
  getCurrentSpan,
23
23
  getCurrentTrace
24
- } from "./chunk-UO3CIQ7R.js";
24
+ } from "./chunk-3YKMCCDV.js";
25
25
  import {
26
26
  BitfabError
27
27
  } from "./chunk-EQI6ZJC3.js";
package/dist/node.cjs CHANGED
@@ -513,7 +513,7 @@ registerAsyncLocalStorageClass(
513
513
  );
514
514
 
515
515
  // src/version.generated.ts
516
- var __version__ = "0.21.0";
516
+ var __version__ = "0.21.2";
517
517
 
518
518
  // src/constants.ts
519
519
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -984,6 +984,13 @@ var BitfabClaudeAgentHandler = class {
984
984
  this.currentLlmHistorySnapshot = [];
985
985
  // Subagent tracking
986
986
  this.activeSubagentSpans = /* @__PURE__ */ new Map();
987
+ // Synthetic root span (handler-only replay). When an `input` is supplied to
988
+ // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
989
+ // input, so a handler-instrumented run is replayable WITHOUT an enclosing
990
+ // withSpan — matching the LangGraph handler, which records the graph input as
991
+ // its root. The prompt is not present anywhere in the message stream, so it
992
+ // must be handed in explicitly.
993
+ this.hasRootInput = false;
987
994
  this.httpClient = new HttpClient({
988
995
  apiKey: config.apiKey,
989
996
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -1018,7 +1025,30 @@ var BitfabClaudeAgentHandler = class {
1018
1025
  return subagentSpanId;
1019
1026
  }
1020
1027
  }
1021
- return this.activeContext?.spanId ?? this.rootSpanId ?? null;
1028
+ return this.rootSpanId ?? this.activeContext?.spanId ?? null;
1029
+ }
1030
+ // Emit the synthetic root `agent` span once, before any child spans. No-op
1031
+ // unless an `input` was supplied AND there is no enclosing withSpan (in which
1032
+ // case that outer span is already the replayable root).
1033
+ maybeStartRootSpan() {
1034
+ if (!this.hasRootInput || this.rootSpanId !== null) {
1035
+ return;
1036
+ }
1037
+ this.ensureTrace();
1038
+ if (this.activeContext !== null) {
1039
+ return;
1040
+ }
1041
+ const spanId = crypto.randomUUID();
1042
+ this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1043
+ this.rootSpanId = spanId;
1044
+ }
1045
+ completeRootSpan() {
1046
+ if (this.rootSpanId === null) {
1047
+ return;
1048
+ }
1049
+ const spanId = this.rootSpanId;
1050
+ this.rootSpanId = null;
1051
+ this.completeSpan(spanId, this.rootOutput);
1022
1052
  }
1023
1053
  // ── span helpers ─────────────────────────────────────────────
1024
1054
  startSpan(spanId, name, spanType, inputData, parentId) {
@@ -1214,26 +1244,53 @@ var BitfabClaudeAgentHandler = class {
1214
1244
  return options;
1215
1245
  }
1216
1246
  /**
1217
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
1247
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
1248
+ *
1249
+ * Yields every message unchanged while capturing assistant message
1250
+ * content as LLM turn spans. Kept for naming symmetry with the Python
1251
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
1252
+ * in TypeScript, prefer `wrapQuery` around `query()`.
1218
1253
  *
1219
- * Yields every message unchanged while capturing AssistantMessage
1220
- * content as LLM turn spans.
1254
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
1255
+ * `wrapQuery`.
1221
1256
  */
1222
- async *wrapResponse(stream) {
1257
+ async *wrapResponse(stream, opts) {
1258
+ this.setRootInput(opts);
1223
1259
  yield* this.processStream(stream);
1224
1260
  }
1225
1261
  /**
1226
1262
  * Wrap a `query()` async iterator to capture LLM turns.
1227
1263
  *
1228
- * Same as `wrapResponse` but for the simpler `query()` API
1229
- * which does not support hooks (no tool/subagent spans).
1264
+ * Tool and subagent spans are captured separately via the hooks injected
1265
+ * by `instrumentOptions` into the `options` passed to `query()`.
1266
+ *
1267
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
1268
+ * — to make a handler-only run replayable: the handler records a root `agent`
1269
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
1270
+ * when an enclosing `withSpan` already supplies the replayable root.
1271
+ *
1272
+ * ```typescript
1273
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
1274
+ * ```
1230
1275
  */
1231
- async *wrapQuery(stream) {
1276
+ async *wrapQuery(stream, opts) {
1277
+ this.setRootInput(opts);
1232
1278
  yield* this.processStream(stream);
1233
1279
  }
1280
+ setRootInput(opts) {
1281
+ if (opts && opts.input !== void 0) {
1282
+ this.hasRootInput = true;
1283
+ this.rootInput = opts.input;
1284
+ } else {
1285
+ this.hasRootInput = false;
1286
+ this.rootInput = void 0;
1287
+ }
1288
+ this.rootOutput = void 0;
1289
+ }
1234
1290
  // ── stream processing ────────────────────────────────────────
1235
1291
  async *processStream(stream) {
1236
1292
  try {
1293
+ this.maybeStartRootSpan();
1237
1294
  for await (const message of stream) {
1238
1295
  try {
1239
1296
  this.processMessage(message);
@@ -1244,6 +1301,7 @@ var BitfabClaudeAgentHandler = class {
1244
1301
  } finally {
1245
1302
  try {
1246
1303
  this.flushLlmTurn();
1304
+ this.completeRootSpan();
1247
1305
  this.sendTraceCompletion();
1248
1306
  } catch {
1249
1307
  }
@@ -1251,18 +1309,19 @@ var BitfabClaudeAgentHandler = class {
1251
1309
  }
1252
1310
  }
1253
1311
  processMessage(message) {
1254
- const typeName = message.constructor?.name ?? "";
1255
- if (typeName === "AssistantMessage") {
1312
+ const typeName = message.type;
1313
+ if (typeName === "assistant") {
1256
1314
  this.handleAssistantMessage(message);
1257
- } else if (typeName === "UserMessage") {
1315
+ } else if (typeName === "user") {
1258
1316
  this.handleUserMessage(message);
1259
- } else if (typeName === "ResultMessage") {
1317
+ } else if (typeName === "result") {
1260
1318
  this.handleResultMessage(message);
1261
1319
  }
1262
1320
  }
1263
1321
  handleAssistantMessage(message) {
1264
1322
  this.ensureTrace();
1265
- const messageId = message.message_id;
1323
+ const inner = message.message ?? {};
1324
+ const messageId = inner.id ?? message.uuid;
1266
1325
  if (messageId !== this.currentLlmMessageId) {
1267
1326
  this.flushLlmTurn();
1268
1327
  this.conversationHistory.push(...this.pendingMessages);
@@ -1270,26 +1329,27 @@ var BitfabClaudeAgentHandler = class {
1270
1329
  this.currentLlmSpanId = crypto.randomUUID();
1271
1330
  this.currentLlmMessageId = messageId ?? null;
1272
1331
  this.currentLlmContent = [];
1273
- this.currentLlmModel = message.model ?? null;
1332
+ this.currentLlmModel = inner.model ?? null;
1274
1333
  this.currentLlmUsage = {};
1275
1334
  this.currentLlmStartedAt = nowIso();
1276
1335
  this.currentLlmHistorySnapshot = [...this.conversationHistory];
1277
1336
  }
1278
- const content = message.content;
1337
+ const content = inner.content;
1279
1338
  if (Array.isArray(content)) {
1280
1339
  this.currentLlmContent.push(...extractContentBlocks(content));
1281
1340
  }
1282
- const usage = extractUsage(message);
1341
+ const usage = extractUsage(inner);
1283
1342
  if (Object.keys(usage).length > 0) {
1284
1343
  Object.assign(this.currentLlmUsage, usage);
1285
1344
  }
1286
- const model = message.model;
1345
+ const model = inner.model;
1287
1346
  if (model) {
1288
1347
  this.currentLlmModel = model;
1289
1348
  }
1290
1349
  }
1291
1350
  handleUserMessage(message) {
1292
- const content = message.content;
1351
+ const inner = message.message ?? {};
1352
+ const content = inner.content;
1293
1353
  const toolUseResult = message.tool_use_result;
1294
1354
  if (toolUseResult !== void 0) {
1295
1355
  this.pendingMessages.push({
@@ -1306,6 +1366,10 @@ var BitfabClaudeAgentHandler = class {
1306
1366
  }
1307
1367
  handleResultMessage(message) {
1308
1368
  this.flushLlmTurn();
1369
+ if (message.result !== void 0) {
1370
+ this.rootOutput = message.result;
1371
+ }
1372
+ this.completeRootSpan();
1309
1373
  const metadata = {};
1310
1374
  for (const attr of [
1311
1375
  "num_turns",
@@ -1369,6 +1433,9 @@ var BitfabClaudeAgentHandler = class {
1369
1433
  this.runToSpan.clear();
1370
1434
  this.traceId = null;
1371
1435
  this.rootSpanId = null;
1436
+ this.hasRootInput = false;
1437
+ this.rootInput = void 0;
1438
+ this.rootOutput = void 0;
1372
1439
  this.activeContext = null;
1373
1440
  this.traceStartedAt = null;
1374
1441
  this.conversationHistory = [];
@@ -2446,9 +2513,15 @@ var BitfabOpenAITracingProcessor = class {
2446
2513
  * Extract and add input/response to serialized span, updating errors list.
2447
2514
  */
2448
2515
  extractSpanInputResponse(span, serializedSpan, errors) {
2516
+ if (span.spanData?.type !== "response") {
2517
+ return;
2518
+ }
2449
2519
  const spanData = serializedSpan.span_data;
2450
2520
  try {
2451
- spanData.input = span.spanData?._input || [];
2521
+ const input = span.spanData?._input;
2522
+ if (input !== void 0) {
2523
+ spanData.input = input;
2524
+ }
2452
2525
  } catch (error) {
2453
2526
  errors.push({
2454
2527
  source: "sdk",
@@ -2457,7 +2530,10 @@ var BitfabOpenAITracingProcessor = class {
2457
2530
  });
2458
2531
  }
2459
2532
  try {
2460
- spanData.response = span.spanData?._response || null;
2533
+ const response = span.spanData?._response;
2534
+ if (response !== void 0) {
2535
+ spanData.response = response;
2536
+ }
2461
2537
  } catch (error) {
2462
2538
  errors.push({
2463
2539
  source: "sdk",
@@ -2965,14 +3041,15 @@ var Bitfab = class {
2965
3041
  * execution as Bitfab spans with proper parent-child hierarchy.
2966
3042
  *
2967
3043
  * ```typescript
3044
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
3045
+ *
2968
3046
  * const handler = client.getClaudeAgentHandler("my-agent");
2969
3047
  * const options = handler.instrumentOptions({
2970
3048
  * model: "claude-sonnet-4-5-...",
2971
3049
  * });
2972
- * const sdkClient = new ClaudeSDKClient(options);
2973
- * await sdkClient.connect();
2974
- * await sdkClient.query("Do something");
2975
- * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
3050
+ * for await (const msg of handler.wrapQuery(
3051
+ * query({ prompt: "Do something", options })
3052
+ * )) {
2976
3053
  * // process messages
2977
3054
  * }
2978
3055
  * ```