@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.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.1";
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.1";
1120
+ declare const __version__ = "0.21.2";
1093
1121
 
1094
1122
  /**
1095
1123
  * Constants for the Bitfab SDK.
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  flushTraces,
13
13
  getCurrentSpan,
14
14
  getCurrentTrace
15
- } from "./chunk-75LZO6JS.js";
15
+ } from "./chunk-3YKMCCDV.js";
16
16
  import {
17
17
  BitfabError
18
18
  } from "./chunk-EQI6ZJC3.js";
package/dist/node.cjs CHANGED
@@ -504,7 +504,7 @@ registerAsyncLocalStorageClass(
504
504
  );
505
505
 
506
506
  // src/version.generated.ts
507
- var __version__ = "0.21.1";
507
+ var __version__ = "0.21.2";
508
508
 
509
509
  // src/constants.ts
510
510
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -975,6 +975,13 @@ var BitfabClaudeAgentHandler = class {
975
975
  this.currentLlmHistorySnapshot = [];
976
976
  // Subagent tracking
977
977
  this.activeSubagentSpans = /* @__PURE__ */ new Map();
978
+ // Synthetic root span (handler-only replay). When an `input` is supplied to
979
+ // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
980
+ // input, so a handler-instrumented run is replayable WITHOUT an enclosing
981
+ // withSpan — matching the LangGraph handler, which records the graph input as
982
+ // its root. The prompt is not present anywhere in the message stream, so it
983
+ // must be handed in explicitly.
984
+ this.hasRootInput = false;
978
985
  this.httpClient = new HttpClient({
979
986
  apiKey: config.apiKey,
980
987
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -1009,7 +1016,30 @@ var BitfabClaudeAgentHandler = class {
1009
1016
  return subagentSpanId;
1010
1017
  }
1011
1018
  }
1012
- return this.activeContext?.spanId ?? this.rootSpanId ?? null;
1019
+ return this.rootSpanId ?? this.activeContext?.spanId ?? null;
1020
+ }
1021
+ // Emit the synthetic root `agent` span once, before any child spans. No-op
1022
+ // unless an `input` was supplied AND there is no enclosing withSpan (in which
1023
+ // case that outer span is already the replayable root).
1024
+ maybeStartRootSpan() {
1025
+ if (!this.hasRootInput || this.rootSpanId !== null) {
1026
+ return;
1027
+ }
1028
+ this.ensureTrace();
1029
+ if (this.activeContext !== null) {
1030
+ return;
1031
+ }
1032
+ const spanId = crypto.randomUUID();
1033
+ this.startSpan(spanId, this.traceFunctionKey, "agent", this.rootInput, null);
1034
+ this.rootSpanId = spanId;
1035
+ }
1036
+ completeRootSpan() {
1037
+ if (this.rootSpanId === null) {
1038
+ return;
1039
+ }
1040
+ const spanId = this.rootSpanId;
1041
+ this.rootSpanId = null;
1042
+ this.completeSpan(spanId, this.rootOutput);
1013
1043
  }
1014
1044
  // ── span helpers ─────────────────────────────────────────────
1015
1045
  startSpan(spanId, name, spanType, inputData, parentId) {
@@ -1205,26 +1235,53 @@ var BitfabClaudeAgentHandler = class {
1205
1235
  return options;
1206
1236
  }
1207
1237
  /**
1208
- * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
1238
+ * Wrap any Claude Agent SDK message stream to capture LLM turns.
1209
1239
  *
1210
- * Yields every message unchanged while capturing AssistantMessage
1211
- * content as LLM turn spans.
1240
+ * Yields every message unchanged while capturing assistant message
1241
+ * content as LLM turn spans. Kept for naming symmetry with the Python
1242
+ * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
1243
+ * in TypeScript, prefer `wrapQuery` around `query()`.
1244
+ *
1245
+ * Pass `{ input }` (the prompt) to record a replayable root span — see
1246
+ * `wrapQuery`.
1212
1247
  */
1213
- async *wrapResponse(stream) {
1248
+ async *wrapResponse(stream, opts) {
1249
+ this.setRootInput(opts);
1214
1250
  yield* this.processStream(stream);
1215
1251
  }
1216
1252
  /**
1217
1253
  * Wrap a `query()` async iterator to capture LLM turns.
1218
1254
  *
1219
- * Same as `wrapResponse` but for the simpler `query()` API
1220
- * which does not support hooks (no tool/subagent spans).
1255
+ * Tool and subagent spans are captured separately via the hooks injected
1256
+ * by `instrumentOptions` into the `options` passed to `query()`.
1257
+ *
1258
+ * Pass `{ input }` — the prompt (or the serializable args that produced it)
1259
+ * — to make a handler-only run replayable: the handler records a root `agent`
1260
+ * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
1261
+ * when an enclosing `withSpan` already supplies the replayable root.
1262
+ *
1263
+ * ```typescript
1264
+ * handler.wrapQuery(query({ prompt, options }), { input: prompt })
1265
+ * ```
1221
1266
  */
1222
- async *wrapQuery(stream) {
1267
+ async *wrapQuery(stream, opts) {
1268
+ this.setRootInput(opts);
1223
1269
  yield* this.processStream(stream);
1224
1270
  }
1271
+ setRootInput(opts) {
1272
+ if (opts && opts.input !== void 0) {
1273
+ this.hasRootInput = true;
1274
+ this.rootInput = opts.input;
1275
+ } else {
1276
+ this.hasRootInput = false;
1277
+ this.rootInput = void 0;
1278
+ }
1279
+ this.rootOutput = void 0;
1280
+ }
1225
1281
  // ── stream processing ────────────────────────────────────────
1226
1282
  async *processStream(stream) {
1227
1283
  try {
1284
+ this.maybeStartRootSpan();
1228
1285
  for await (const message of stream) {
1229
1286
  try {
1230
1287
  this.processMessage(message);
@@ -1235,6 +1292,7 @@ var BitfabClaudeAgentHandler = class {
1235
1292
  } finally {
1236
1293
  try {
1237
1294
  this.flushLlmTurn();
1295
+ this.completeRootSpan();
1238
1296
  this.sendTraceCompletion();
1239
1297
  } catch {
1240
1298
  }
@@ -1242,18 +1300,19 @@ var BitfabClaudeAgentHandler = class {
1242
1300
  }
1243
1301
  }
1244
1302
  processMessage(message) {
1245
- const typeName = message.constructor?.name ?? "";
1246
- if (typeName === "AssistantMessage") {
1303
+ const typeName = message.type;
1304
+ if (typeName === "assistant") {
1247
1305
  this.handleAssistantMessage(message);
1248
- } else if (typeName === "UserMessage") {
1306
+ } else if (typeName === "user") {
1249
1307
  this.handleUserMessage(message);
1250
- } else if (typeName === "ResultMessage") {
1308
+ } else if (typeName === "result") {
1251
1309
  this.handleResultMessage(message);
1252
1310
  }
1253
1311
  }
1254
1312
  handleAssistantMessage(message) {
1255
1313
  this.ensureTrace();
1256
- const messageId = message.message_id;
1314
+ const inner = message.message ?? {};
1315
+ const messageId = inner.id ?? message.uuid;
1257
1316
  if (messageId !== this.currentLlmMessageId) {
1258
1317
  this.flushLlmTurn();
1259
1318
  this.conversationHistory.push(...this.pendingMessages);
@@ -1261,26 +1320,27 @@ var BitfabClaudeAgentHandler = class {
1261
1320
  this.currentLlmSpanId = crypto.randomUUID();
1262
1321
  this.currentLlmMessageId = messageId ?? null;
1263
1322
  this.currentLlmContent = [];
1264
- this.currentLlmModel = message.model ?? null;
1323
+ this.currentLlmModel = inner.model ?? null;
1265
1324
  this.currentLlmUsage = {};
1266
1325
  this.currentLlmStartedAt = nowIso();
1267
1326
  this.currentLlmHistorySnapshot = [...this.conversationHistory];
1268
1327
  }
1269
- const content = message.content;
1328
+ const content = inner.content;
1270
1329
  if (Array.isArray(content)) {
1271
1330
  this.currentLlmContent.push(...extractContentBlocks(content));
1272
1331
  }
1273
- const usage = extractUsage(message);
1332
+ const usage = extractUsage(inner);
1274
1333
  if (Object.keys(usage).length > 0) {
1275
1334
  Object.assign(this.currentLlmUsage, usage);
1276
1335
  }
1277
- const model = message.model;
1336
+ const model = inner.model;
1278
1337
  if (model) {
1279
1338
  this.currentLlmModel = model;
1280
1339
  }
1281
1340
  }
1282
1341
  handleUserMessage(message) {
1283
- const content = message.content;
1342
+ const inner = message.message ?? {};
1343
+ const content = inner.content;
1284
1344
  const toolUseResult = message.tool_use_result;
1285
1345
  if (toolUseResult !== void 0) {
1286
1346
  this.pendingMessages.push({
@@ -1297,6 +1357,10 @@ var BitfabClaudeAgentHandler = class {
1297
1357
  }
1298
1358
  handleResultMessage(message) {
1299
1359
  this.flushLlmTurn();
1360
+ if (message.result !== void 0) {
1361
+ this.rootOutput = message.result;
1362
+ }
1363
+ this.completeRootSpan();
1300
1364
  const metadata = {};
1301
1365
  for (const attr of [
1302
1366
  "num_turns",
@@ -1360,6 +1424,9 @@ var BitfabClaudeAgentHandler = class {
1360
1424
  this.runToSpan.clear();
1361
1425
  this.traceId = null;
1362
1426
  this.rootSpanId = null;
1427
+ this.hasRootInput = false;
1428
+ this.rootInput = void 0;
1429
+ this.rootOutput = void 0;
1363
1430
  this.activeContext = null;
1364
1431
  this.traceStartedAt = null;
1365
1432
  this.conversationHistory = [];
@@ -2965,14 +3032,15 @@ var Bitfab = class {
2965
3032
  * execution as Bitfab spans with proper parent-child hierarchy.
2966
3033
  *
2967
3034
  * ```typescript
3035
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
3036
+ *
2968
3037
  * const handler = client.getClaudeAgentHandler("my-agent");
2969
3038
  * const options = handler.instrumentOptions({
2970
3039
  * model: "claude-sonnet-4-5-...",
2971
3040
  * });
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())) {
3041
+ * for await (const msg of handler.wrapQuery(
3042
+ * query({ prompt: "Do something", options })
3043
+ * )) {
2976
3044
  * // process messages
2977
3045
  * }
2978
3046
  * ```