bitfab 0.9.2 → 0.11.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.
@@ -7,7 +7,481 @@ import {
7
7
  getReplayContext,
8
8
  isAsyncStorageInitDone,
9
9
  serializeValue
10
- } from "./chunk-VGALEXKQ.js";
10
+ } from "./chunk-C4KRLEXZ.js";
11
+
12
+ // src/claudeAgentSdk.ts
13
+ function nowIso() {
14
+ return (/* @__PURE__ */ new Date()).toISOString();
15
+ }
16
+ function safeSerialize(value) {
17
+ if (value === null || value === void 0) {
18
+ return value;
19
+ }
20
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
21
+ return value;
22
+ }
23
+ if (Array.isArray(value)) {
24
+ return value.map(safeSerialize);
25
+ }
26
+ if (typeof value === "object") {
27
+ if (typeof value.toJSON === "function") {
28
+ return value.toJSON();
29
+ }
30
+ const result = {};
31
+ for (const [k, v] of Object.entries(value)) {
32
+ if (!k.startsWith("_")) {
33
+ result[k] = safeSerialize(v);
34
+ }
35
+ }
36
+ return result;
37
+ }
38
+ return String(value);
39
+ }
40
+ function extractContentBlocks(content) {
41
+ if (!Array.isArray(content)) {
42
+ return [];
43
+ }
44
+ return content.map((block) => safeSerialize(block));
45
+ }
46
+ function extractUsage(message) {
47
+ const usageInfo = {};
48
+ const usage = message.usage;
49
+ if (!usage) {
50
+ return usageInfo;
51
+ }
52
+ const mapping = {
53
+ input_tokens: "inputTokens",
54
+ output_tokens: "outputTokens",
55
+ cache_read_input_tokens: "cacheReadTokens",
56
+ cache_creation_input_tokens: "cacheCreationTokens"
57
+ };
58
+ for (const [srcKey, dstKey] of Object.entries(mapping)) {
59
+ const val = usage[srcKey];
60
+ if (val !== void 0 && val !== null) {
61
+ usageInfo[dstKey] = val;
62
+ }
63
+ }
64
+ return usageInfo;
65
+ }
66
+ var BitfabClaudeAgentHandler = class {
67
+ constructor(config) {
68
+ // Span tracking
69
+ this.runToSpan = /* @__PURE__ */ new Map();
70
+ this.traceId = null;
71
+ this.rootSpanId = null;
72
+ this.activeContext = null;
73
+ this.traceStartedAt = null;
74
+ // LLM turn tracking
75
+ this.conversationHistory = [];
76
+ this.pendingMessages = [];
77
+ this.currentLlmSpanId = null;
78
+ this.currentLlmMessageId = null;
79
+ this.currentLlmContent = [];
80
+ this.currentLlmModel = null;
81
+ this.currentLlmUsage = {};
82
+ this.currentLlmStartedAt = null;
83
+ this.currentLlmHistorySnapshot = [];
84
+ // Subagent tracking
85
+ this.activeSubagentSpans = /* @__PURE__ */ new Map();
86
+ this.httpClient = new HttpClient({
87
+ apiKey: config.apiKey,
88
+ serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
89
+ timeout: config.timeout ?? 1e4
90
+ });
91
+ this.traceFunctionKey = config.traceFunctionKey;
92
+ this.getActiveSpanContext = config.getActiveSpanContext ?? null;
93
+ this.preToolUseHook = this.preToolUseHook.bind(this);
94
+ this.postToolUseHook = this.postToolUseHook.bind(this);
95
+ this.postToolUseFailureHook = this.postToolUseFailureHook.bind(this);
96
+ this.subagentStartHook = this.subagentStartHook.bind(this);
97
+ this.subagentStopHook = this.subagentStopHook.bind(this);
98
+ }
99
+ // ── trace lifecycle ──────────────────────────────────────────
100
+ ensureTrace() {
101
+ if (this.traceId !== null) {
102
+ return this.traceId;
103
+ }
104
+ this.activeContext = this.getActiveSpanContext?.() ?? null;
105
+ if (this.activeContext) {
106
+ this.traceId = this.activeContext.traceId;
107
+ } else {
108
+ this.traceId = crypto.randomUUID();
109
+ }
110
+ this.traceStartedAt = nowIso();
111
+ return this.traceId;
112
+ }
113
+ getParentId(agentId) {
114
+ if (agentId) {
115
+ const subagentSpanId = this.activeSubagentSpans.get(agentId);
116
+ if (subagentSpanId) {
117
+ return subagentSpanId;
118
+ }
119
+ }
120
+ return this.activeContext?.spanId ?? this.rootSpanId ?? null;
121
+ }
122
+ // ── span helpers ─────────────────────────────────────────────
123
+ startSpan(spanId, name, spanType, inputData, parentId) {
124
+ const traceId = this.ensureTrace();
125
+ const spanInfo = {
126
+ spanId,
127
+ traceId,
128
+ parentId: parentId ?? null,
129
+ startedAt: nowIso(),
130
+ name,
131
+ type: spanType,
132
+ input: safeSerialize(inputData),
133
+ contexts: []
134
+ };
135
+ this.runToSpan.set(spanId, spanInfo);
136
+ return spanInfo;
137
+ }
138
+ completeSpan(spanId, output, error, extraContexts) {
139
+ const spanInfo = this.runToSpan.get(spanId);
140
+ if (!spanInfo) {
141
+ return;
142
+ }
143
+ this.runToSpan.delete(spanId);
144
+ spanInfo.endedAt = nowIso();
145
+ spanInfo.output = safeSerialize(output);
146
+ if (error !== void 0) {
147
+ spanInfo.error = error;
148
+ }
149
+ if (extraContexts) {
150
+ spanInfo.contexts.push(extraContexts);
151
+ }
152
+ this.sendSpan(spanInfo);
153
+ }
154
+ sendSpan(spanInfo) {
155
+ const spanData = {
156
+ name: spanInfo.name,
157
+ type: spanInfo.type
158
+ };
159
+ if (spanInfo.input !== void 0) {
160
+ spanData.input = spanInfo.input;
161
+ }
162
+ if (spanInfo.output !== void 0) {
163
+ spanData.output = spanInfo.output;
164
+ }
165
+ if (spanInfo.error !== void 0) {
166
+ spanData.error = spanInfo.error;
167
+ }
168
+ if (spanInfo.contexts.length > 0) {
169
+ spanData.contexts = spanInfo.contexts;
170
+ }
171
+ const rawSpan = {
172
+ id: spanInfo.spanId,
173
+ trace_id: spanInfo.traceId,
174
+ started_at: spanInfo.startedAt,
175
+ ended_at: spanInfo.endedAt ?? nowIso(),
176
+ span_data: spanData
177
+ };
178
+ if (spanInfo.parentId !== null) {
179
+ rawSpan.parent_id = spanInfo.parentId;
180
+ }
181
+ const payload = {
182
+ type: "sdk-function",
183
+ source: "typescript-sdk-claude-agent-sdk",
184
+ traceFunctionKey: this.traceFunctionKey,
185
+ sourceTraceId: spanInfo.traceId,
186
+ rawSpan
187
+ };
188
+ try {
189
+ this.httpClient.sendExternalSpan(payload);
190
+ } catch {
191
+ }
192
+ }
193
+ sendTraceCompletion(endedAt, metadata) {
194
+ if (this.traceId === null) {
195
+ return;
196
+ }
197
+ const completed = this.activeContext === null;
198
+ const traceId = this.traceId;
199
+ this.traceId = null;
200
+ const externalTrace = {
201
+ id: traceId,
202
+ started_at: this.traceStartedAt ?? nowIso(),
203
+ ended_at: endedAt ?? nowIso(),
204
+ workflow_name: this.traceFunctionKey
205
+ };
206
+ if (metadata) {
207
+ externalTrace.metadata = metadata;
208
+ }
209
+ const traceData = {
210
+ type: "sdk-function",
211
+ source: "typescript-sdk-claude-agent-sdk",
212
+ traceFunctionKey: this.traceFunctionKey,
213
+ externalTrace,
214
+ completed
215
+ };
216
+ try {
217
+ this.httpClient.sendExternalTrace(traceData);
218
+ } catch {
219
+ }
220
+ }
221
+ // ── hook callbacks ───────────────────────────────────────────
222
+ async preToolUseHook(inputData, toolUseId, _context) {
223
+ try {
224
+ const sid = inputData.tool_use_id ?? toolUseId ?? crypto.randomUUID();
225
+ const toolName = inputData.tool_name ?? "tool";
226
+ const toolInput = inputData.tool_input ?? {};
227
+ const agentId = inputData.agent_id;
228
+ const parentId = this.getParentId(agentId);
229
+ this.startSpan(sid, toolName, "function", toolInput, parentId);
230
+ } catch {
231
+ }
232
+ return {};
233
+ }
234
+ async postToolUseHook(inputData, toolUseId, _context) {
235
+ try {
236
+ const sid = inputData.tool_use_id ?? toolUseId ?? "";
237
+ const toolResponse = inputData.tool_response;
238
+ this.completeSpan(sid, toolResponse);
239
+ } catch {
240
+ }
241
+ return {};
242
+ }
243
+ async postToolUseFailureHook(inputData, toolUseId, _context) {
244
+ try {
245
+ const sid = inputData.tool_use_id ?? toolUseId ?? "";
246
+ const error = String(inputData.error ?? "Unknown error");
247
+ this.completeSpan(sid, void 0, error);
248
+ } catch {
249
+ }
250
+ return {};
251
+ }
252
+ async subagentStartHook(inputData, _toolUseId, _context) {
253
+ try {
254
+ const agentId = inputData.agent_id ?? crypto.randomUUID();
255
+ const agentType = inputData.agent_type ?? "subagent";
256
+ const parentId = this.getParentId();
257
+ const spanId = crypto.randomUUID();
258
+ this.activeSubagentSpans.set(agentId, spanId);
259
+ this.startSpan(
260
+ spanId,
261
+ `Agent: ${agentType}`,
262
+ "agent",
263
+ void 0,
264
+ parentId
265
+ );
266
+ } catch {
267
+ }
268
+ return {};
269
+ }
270
+ async subagentStopHook(inputData, _toolUseId, _context) {
271
+ try {
272
+ const agentId = inputData.agent_id ?? "";
273
+ const spanId = this.activeSubagentSpans.get(agentId);
274
+ if (spanId) {
275
+ this.activeSubagentSpans.delete(agentId);
276
+ this.completeSpan(spanId);
277
+ }
278
+ } catch {
279
+ }
280
+ return {};
281
+ }
282
+ // ── public API ───────────────────────────────────────────────
283
+ /**
284
+ * Inject Bitfab tracing hooks into Claude Agent SDK options.
285
+ *
286
+ * Modifies the options object and returns it for convenience.
287
+ * The SDK's `HookMatcher` is constructed as a plain object
288
+ * (`{ matcher: null, hooks: [callback] }`) to avoid requiring
289
+ * `@anthropic-ai/claude-agent-sdk` as a dependency.
290
+ *
291
+ * @param options - Options object with a `hooks` property
292
+ * @returns The modified options object with Bitfab hooks injected
293
+ */
294
+ instrumentOptions(options) {
295
+ const hooks = options.hooks ?? {};
296
+ if (!options.hooks) {
297
+ ;
298
+ options.hooks = hooks;
299
+ }
300
+ const hookConfig = [
301
+ ["PreToolUse", this.preToolUseHook],
302
+ ["PostToolUse", this.postToolUseHook],
303
+ ["PostToolUseFailure", this.postToolUseFailureHook],
304
+ ["SubagentStart", this.subagentStartHook],
305
+ ["SubagentStop", this.subagentStopHook]
306
+ ];
307
+ for (const [event, callback] of hookConfig) {
308
+ if (!hooks[event]) {
309
+ hooks[event] = [];
310
+ }
311
+ hooks[event].push({ matcher: null, hooks: [callback] });
312
+ }
313
+ return options;
314
+ }
315
+ /**
316
+ * Wrap a `ClaudeSDKClient.receiveResponse()` stream to capture LLM turns.
317
+ *
318
+ * Yields every message unchanged while capturing AssistantMessage
319
+ * content as LLM turn spans.
320
+ */
321
+ async *wrapResponse(stream) {
322
+ yield* this.processStream(stream);
323
+ }
324
+ /**
325
+ * Wrap a `query()` async iterator to capture LLM turns.
326
+ *
327
+ * Same as `wrapResponse` but for the simpler `query()` API
328
+ * which does not support hooks (no tool/subagent spans).
329
+ */
330
+ async *wrapQuery(stream) {
331
+ yield* this.processStream(stream);
332
+ }
333
+ // ── stream processing ────────────────────────────────────────
334
+ async *processStream(stream) {
335
+ try {
336
+ for await (const message of stream) {
337
+ try {
338
+ this.processMessage(message);
339
+ } catch {
340
+ }
341
+ yield message;
342
+ }
343
+ } finally {
344
+ try {
345
+ this.flushLlmTurn();
346
+ this.sendTraceCompletion();
347
+ } catch {
348
+ }
349
+ this.resetState();
350
+ }
351
+ }
352
+ processMessage(message) {
353
+ const typeName = message.constructor?.name ?? "";
354
+ if (typeName === "AssistantMessage") {
355
+ this.handleAssistantMessage(message);
356
+ } else if (typeName === "UserMessage") {
357
+ this.handleUserMessage(message);
358
+ } else if (typeName === "ResultMessage") {
359
+ this.handleResultMessage(message);
360
+ }
361
+ }
362
+ handleAssistantMessage(message) {
363
+ this.ensureTrace();
364
+ const messageId = message.message_id;
365
+ if (messageId !== this.currentLlmMessageId) {
366
+ this.flushLlmTurn();
367
+ this.conversationHistory.push(...this.pendingMessages);
368
+ this.pendingMessages = [];
369
+ this.currentLlmSpanId = crypto.randomUUID();
370
+ this.currentLlmMessageId = messageId ?? null;
371
+ this.currentLlmContent = [];
372
+ this.currentLlmModel = message.model ?? null;
373
+ this.currentLlmUsage = {};
374
+ this.currentLlmStartedAt = nowIso();
375
+ this.currentLlmHistorySnapshot = [...this.conversationHistory];
376
+ }
377
+ const content = message.content;
378
+ if (Array.isArray(content)) {
379
+ this.currentLlmContent.push(...extractContentBlocks(content));
380
+ }
381
+ const usage = extractUsage(message);
382
+ if (Object.keys(usage).length > 0) {
383
+ Object.assign(this.currentLlmUsage, usage);
384
+ }
385
+ const model = message.model;
386
+ if (model) {
387
+ this.currentLlmModel = model;
388
+ }
389
+ }
390
+ handleUserMessage(message) {
391
+ const content = message.content;
392
+ const toolUseResult = message.tool_use_result;
393
+ if (toolUseResult !== void 0) {
394
+ this.pendingMessages.push({
395
+ role: "tool",
396
+ content: safeSerialize(content),
397
+ tool_result: safeSerialize(toolUseResult)
398
+ });
399
+ } else {
400
+ this.pendingMessages.push({
401
+ role: "user",
402
+ content: safeSerialize(content)
403
+ });
404
+ }
405
+ }
406
+ handleResultMessage(message) {
407
+ this.flushLlmTurn();
408
+ const metadata = {};
409
+ for (const attr of [
410
+ "num_turns",
411
+ "total_cost_usd",
412
+ "duration_ms",
413
+ "duration_api_ms",
414
+ "session_id"
415
+ ]) {
416
+ const val = message[attr];
417
+ if (val !== void 0 && val !== null) {
418
+ metadata[attr] = val;
419
+ }
420
+ }
421
+ const usage = message.usage;
422
+ if (usage && typeof usage === "object") {
423
+ metadata.usage = safeSerialize(usage);
424
+ }
425
+ this.sendTraceCompletion(
426
+ void 0,
427
+ Object.keys(metadata).length > 0 ? metadata : void 0
428
+ );
429
+ }
430
+ flushLlmTurn() {
431
+ if (this.currentLlmSpanId === null) {
432
+ return;
433
+ }
434
+ const spanId = this.currentLlmSpanId;
435
+ const traceId = this.ensureTrace();
436
+ const parentId = this.getParentId();
437
+ const llmContext = {};
438
+ if (this.currentLlmModel) {
439
+ llmContext.model = this.currentLlmModel;
440
+ }
441
+ Object.assign(llmContext, this.currentLlmUsage);
442
+ const spanInfo = {
443
+ spanId,
444
+ traceId,
445
+ parentId,
446
+ startedAt: this.currentLlmStartedAt ?? nowIso(),
447
+ endedAt: nowIso(),
448
+ name: this.currentLlmModel ?? "llm",
449
+ type: "llm",
450
+ input: this.currentLlmHistorySnapshot,
451
+ output: this.currentLlmContent,
452
+ contexts: Object.keys(llmContext).length > 0 ? [llmContext] : []
453
+ };
454
+ this.sendSpan(spanInfo);
455
+ this.conversationHistory.push({
456
+ role: "assistant",
457
+ content: this.currentLlmContent
458
+ });
459
+ this.currentLlmSpanId = null;
460
+ this.currentLlmMessageId = null;
461
+ this.currentLlmContent = [];
462
+ this.currentLlmModel = null;
463
+ this.currentLlmUsage = {};
464
+ this.currentLlmStartedAt = null;
465
+ this.currentLlmHistorySnapshot = [];
466
+ }
467
+ resetState() {
468
+ this.runToSpan.clear();
469
+ this.traceId = null;
470
+ this.rootSpanId = null;
471
+ this.activeContext = null;
472
+ this.traceStartedAt = null;
473
+ this.conversationHistory = [];
474
+ this.pendingMessages = [];
475
+ this.currentLlmSpanId = null;
476
+ this.currentLlmMessageId = null;
477
+ this.currentLlmContent = [];
478
+ this.currentLlmModel = null;
479
+ this.currentLlmUsage = {};
480
+ this.currentLlmStartedAt = null;
481
+ this.currentLlmHistorySnapshot = [];
482
+ this.activeSubagentSpans.clear();
483
+ }
484
+ };
11
485
 
12
486
  // src/baml.ts
13
487
  var cachedBaml = null;
@@ -297,6 +771,480 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
297
771
  };
298
772
  }
299
773
 
774
+ // src/langgraph.ts
775
+ var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
776
+ var LANGGRAPH_METADATA_KEYS = [
777
+ "langgraph_step",
778
+ "langgraph_node",
779
+ "langgraph_triggers",
780
+ "langgraph_path",
781
+ "langgraph_checkpoint_ns"
782
+ ];
783
+ function nowIso2() {
784
+ return (/* @__PURE__ */ new Date()).toISOString();
785
+ }
786
+ var MAX_SERIALIZE_DEPTH = 6;
787
+ function safeSerialize2(value) {
788
+ return safeSerializeInner(value, 0, /* @__PURE__ */ new WeakSet());
789
+ }
790
+ function safeSerializeInner(value, depth, seen) {
791
+ if (value === null || value === void 0) {
792
+ return value;
793
+ }
794
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
795
+ return value;
796
+ }
797
+ const className = value?.constructor?.name ?? typeof value;
798
+ if (depth > MAX_SERIALIZE_DEPTH) {
799
+ return `<${className}>`;
800
+ }
801
+ if (typeof value === "object") {
802
+ if (seen.has(value)) {
803
+ return `<cycle ${className}>`;
804
+ }
805
+ seen.add(value);
806
+ }
807
+ if (Array.isArray(value)) {
808
+ return value.map((item) => safeSerializeInner(item, depth + 1, seen));
809
+ }
810
+ if (typeof value === "object") {
811
+ if (typeof value.toJSON === "function") {
812
+ try {
813
+ return value.toJSON();
814
+ } catch {
815
+ return `<${className}>`;
816
+ }
817
+ }
818
+ try {
819
+ const result = {};
820
+ for (const [k, v] of Object.entries(value)) {
821
+ if (!k.startsWith("_")) {
822
+ result[k] = safeSerializeInner(v, depth + 1, seen);
823
+ }
824
+ }
825
+ return result;
826
+ } catch {
827
+ return `<${className}>`;
828
+ }
829
+ }
830
+ try {
831
+ return String(value);
832
+ } catch {
833
+ return `<${className}>`;
834
+ }
835
+ }
836
+ function convertMessage(message) {
837
+ if (typeof message !== "object" || message === null) {
838
+ return { role: "unknown", content: String(message) };
839
+ }
840
+ const msg = message;
841
+ if (typeof msg.toDict === "function") {
842
+ return msg.toDict();
843
+ }
844
+ const typeToRole = {
845
+ human: "user",
846
+ ai: "assistant",
847
+ system: "system",
848
+ tool: "tool",
849
+ function: "function"
850
+ };
851
+ const result = {};
852
+ const msgType = msg._getType ? String(msg._getType()) : msg.type;
853
+ result.role = (msgType ? typeToRole[msgType] : void 0) ?? msg.role ?? "unknown";
854
+ result.content = msg.content ?? "";
855
+ if (msg.tool_calls) {
856
+ result.tool_calls = msg.tool_calls;
857
+ }
858
+ if (msg.tool_call_id) {
859
+ result.tool_call_id = msg.tool_call_id;
860
+ }
861
+ if (msg.name) {
862
+ result.name = msg.name;
863
+ }
864
+ return result;
865
+ }
866
+ function extractModelName(serialized, metadata) {
867
+ if (serialized) {
868
+ const kwargs = serialized.kwargs;
869
+ if (kwargs) {
870
+ const model = kwargs.model_name ?? kwargs.model ?? kwargs.model_id;
871
+ if (model) {
872
+ return String(model);
873
+ }
874
+ }
875
+ }
876
+ if (metadata) {
877
+ const lsModel = metadata.ls_model_name;
878
+ if (lsModel) {
879
+ return String(lsModel);
880
+ }
881
+ }
882
+ return void 0;
883
+ }
884
+ function extractUsage2(output) {
885
+ const usage = {};
886
+ const llmOutput = output.llmOutput;
887
+ const tokenUsage = llmOutput?.tokenUsage ?? llmOutput?.token_usage ?? llmOutput?.usage ?? {};
888
+ const inputTokens = tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? tokenUsage.input_tokens;
889
+ const outputTokens = tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? tokenUsage.output_tokens;
890
+ const totalTokens = tokenUsage.totalTokens ?? tokenUsage.total_tokens;
891
+ if (inputTokens !== void 0 && inputTokens !== null) {
892
+ usage.inputTokens = inputTokens;
893
+ }
894
+ if (outputTokens !== void 0 && outputTokens !== null) {
895
+ usage.outputTokens = outputTokens;
896
+ }
897
+ if (totalTokens !== void 0 && totalTokens !== null) {
898
+ usage.totalTokens = totalTokens;
899
+ }
900
+ return usage;
901
+ }
902
+ function extractLangGraphMetadata(metadata) {
903
+ if (!metadata) {
904
+ return {};
905
+ }
906
+ const result = {};
907
+ for (const key of LANGGRAPH_METADATA_KEYS) {
908
+ if (key in metadata) {
909
+ result[key] = metadata[key];
910
+ }
911
+ }
912
+ return result;
913
+ }
914
+ var BitfabLangGraphCallbackHandler = class {
915
+ constructor(config) {
916
+ this.name = "BitfabLangGraphCallbackHandler";
917
+ this.ignoreRetriever = true;
918
+ this.ignoreRetry = true;
919
+ this.ignoreCustomEvent = true;
920
+ this.runToSpan = /* @__PURE__ */ new Map();
921
+ this.invocations = /* @__PURE__ */ new Map();
922
+ this.httpClient = new HttpClient({
923
+ apiKey: config.apiKey,
924
+ serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
925
+ timeout: config.timeout ?? 1e4
926
+ });
927
+ this.traceFunctionKey = config.traceFunctionKey;
928
+ this.getActiveSpanContext = config.getActiveSpanContext ?? null;
929
+ }
930
+ // ── lifecycle helpers ──────────────────────────────────────────
931
+ startSpan(runId, parentRunId, name, spanType, inputData, metadata, tags) {
932
+ const parentSpan = parentRunId ? this.runToSpan.get(parentRunId) : void 0;
933
+ const willHide = tags?.includes(LANGSMITH_HIDDEN_TAG) === true;
934
+ let invocation;
935
+ let effectiveParentId;
936
+ if (parentSpan) {
937
+ const existing = this.invocations.get(parentSpan.rootRunId);
938
+ if (existing) {
939
+ invocation = existing;
940
+ } else {
941
+ invocation = {
942
+ traceId: parentSpan.traceId,
943
+ activeContext: null,
944
+ rootRunId: parentSpan.rootRunId
945
+ };
946
+ this.invocations.set(invocation.rootRunId, invocation);
947
+ }
948
+ if (!willHide) {
949
+ let resolved = parentSpan;
950
+ while (resolved?.hidden === true) {
951
+ resolved = resolved.parentId ? this.runToSpan.get(resolved.parentId) : void 0;
952
+ }
953
+ effectiveParentId = resolved ? resolved.spanId : invocation.activeContext?.spanId ?? null;
954
+ } else {
955
+ effectiveParentId = parentRunId ?? null;
956
+ }
957
+ } else {
958
+ const activeContext = this.getActiveSpanContext?.() ?? null;
959
+ invocation = {
960
+ traceId: activeContext ? activeContext.traceId : crypto.randomUUID(),
961
+ activeContext,
962
+ rootRunId: runId
963
+ };
964
+ this.invocations.set(runId, invocation);
965
+ effectiveParentId = activeContext?.spanId ?? null;
966
+ }
967
+ const lgMetadata = extractLangGraphMetadata(metadata);
968
+ const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
969
+ const spanInfo = {
970
+ spanId: runId,
971
+ traceId: invocation.traceId,
972
+ rootRunId: invocation.rootRunId,
973
+ parentId: effectiveParentId,
974
+ startedAt: nowIso2(),
975
+ name,
976
+ type: spanType,
977
+ input: safeSerialize2(inputData),
978
+ contexts
979
+ };
980
+ if (willHide) {
981
+ spanInfo.hidden = true;
982
+ }
983
+ this.runToSpan.set(runId, spanInfo);
984
+ return spanInfo;
985
+ }
986
+ completeSpan(runId, output, error, extraContexts) {
987
+ const spanInfo = this.runToSpan.get(runId);
988
+ if (!spanInfo) {
989
+ return;
990
+ }
991
+ this.runToSpan.delete(runId);
992
+ spanInfo.endedAt = nowIso2();
993
+ spanInfo.output = safeSerialize2(output);
994
+ if (error !== void 0) {
995
+ spanInfo.error = error;
996
+ }
997
+ if (extraContexts && Object.keys(extraContexts).length > 0) {
998
+ spanInfo.contexts.push(extraContexts);
999
+ }
1000
+ this.sendSpan(spanInfo);
1001
+ if (runId === spanInfo.rootRunId) {
1002
+ const invocation = this.invocations.get(runId);
1003
+ this.sendTraceCompletion(spanInfo, invocation?.activeContext ?? null);
1004
+ this.invocations.delete(runId);
1005
+ }
1006
+ }
1007
+ sendSpan(spanInfo) {
1008
+ const spanData = {
1009
+ name: spanInfo.name,
1010
+ type: spanInfo.type
1011
+ };
1012
+ if (spanInfo.input !== void 0) {
1013
+ spanData.input = spanInfo.input;
1014
+ }
1015
+ if (spanInfo.output !== void 0) {
1016
+ spanData.output = spanInfo.output;
1017
+ }
1018
+ if (spanInfo.error !== void 0) {
1019
+ spanData.error = spanInfo.error;
1020
+ }
1021
+ if (spanInfo.contexts.length > 0) {
1022
+ spanData.contexts = spanInfo.contexts;
1023
+ }
1024
+ if (spanInfo.hidden) {
1025
+ spanData.hidden = true;
1026
+ }
1027
+ const rawSpan = {
1028
+ id: spanInfo.spanId,
1029
+ trace_id: spanInfo.traceId,
1030
+ started_at: spanInfo.startedAt,
1031
+ ended_at: spanInfo.endedAt ?? nowIso2(),
1032
+ span_data: spanData
1033
+ };
1034
+ if (spanInfo.parentId !== null) {
1035
+ rawSpan.parent_id = spanInfo.parentId;
1036
+ }
1037
+ const payload = {
1038
+ type: "sdk-function",
1039
+ source: "typescript-sdk-langgraph",
1040
+ traceFunctionKey: this.traceFunctionKey,
1041
+ sourceTraceId: spanInfo.traceId,
1042
+ rawSpan
1043
+ };
1044
+ try {
1045
+ this.httpClient.sendExternalSpan(payload);
1046
+ } catch {
1047
+ }
1048
+ }
1049
+ sendTraceCompletion(rootSpan, activeContext) {
1050
+ const completed = activeContext === null;
1051
+ const traceData = {
1052
+ type: "sdk-function",
1053
+ source: "typescript-sdk-langgraph",
1054
+ traceFunctionKey: this.traceFunctionKey,
1055
+ externalTrace: {
1056
+ id: rootSpan.traceId,
1057
+ started_at: rootSpan.startedAt,
1058
+ ended_at: rootSpan.endedAt ?? nowIso2(),
1059
+ workflow_name: this.traceFunctionKey
1060
+ },
1061
+ completed
1062
+ };
1063
+ try {
1064
+ this.httpClient.sendExternalTrace(traceData);
1065
+ } catch {
1066
+ }
1067
+ }
1068
+ // ── chain callbacks (graph nodes) ─────────────────────────────
1069
+ async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
1070
+ try {
1071
+ const idArr = chain.id;
1072
+ const name = chain.name ?? idArr?.[idArr.length - 1] ?? "chain";
1073
+ this.startSpan(
1074
+ runId,
1075
+ parentRunId,
1076
+ String(name),
1077
+ "agent",
1078
+ inputs,
1079
+ metadata,
1080
+ tags
1081
+ );
1082
+ } catch {
1083
+ }
1084
+ }
1085
+ async handleChainEnd(outputs, runId) {
1086
+ try {
1087
+ this.completeSpan(runId, outputs);
1088
+ } catch {
1089
+ }
1090
+ }
1091
+ async handleChainError(error, runId) {
1092
+ try {
1093
+ const errorObj = error;
1094
+ if (errorObj?.constructor?.name === "GraphBubbleUp") {
1095
+ this.completeSpan(runId, void 0, void 0);
1096
+ return;
1097
+ }
1098
+ this.completeSpan(
1099
+ runId,
1100
+ void 0,
1101
+ error instanceof Error ? error.message : String(error)
1102
+ );
1103
+ } catch {
1104
+ }
1105
+ }
1106
+ // ── LLM callbacks ─────────────────────────────────────────────
1107
+ async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata) {
1108
+ try {
1109
+ const model = extractModelName(llm, metadata);
1110
+ const idArr = llm.id;
1111
+ const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
1112
+ const converted = messages.map((batch) => batch.map(convertMessage));
1113
+ const spanInfo = this.startSpan(
1114
+ runId,
1115
+ parentRunId,
1116
+ String(name),
1117
+ "llm",
1118
+ converted,
1119
+ metadata,
1120
+ tags
1121
+ );
1122
+ spanInfo.model = model;
1123
+ } catch {
1124
+ }
1125
+ }
1126
+ async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata) {
1127
+ try {
1128
+ const model = extractModelName(llm, metadata);
1129
+ const idArr = llm.id;
1130
+ const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
1131
+ const spanInfo = this.startSpan(
1132
+ runId,
1133
+ parentRunId,
1134
+ String(name),
1135
+ "llm",
1136
+ prompts,
1137
+ metadata,
1138
+ tags
1139
+ );
1140
+ spanInfo.model = model;
1141
+ } catch {
1142
+ }
1143
+ }
1144
+ async handleLLMEnd(output, runId) {
1145
+ try {
1146
+ let llmOutput;
1147
+ const generations = output.generations;
1148
+ if (generations?.length && generations[generations.length - 1]?.length) {
1149
+ const gen = generations[generations.length - 1][generations[generations.length - 1].length - 1];
1150
+ const msg = gen.message;
1151
+ llmOutput = msg ? convertMessage(msg) : gen.text ?? String(gen);
1152
+ }
1153
+ const usage = extractUsage2(output);
1154
+ const spanInfo = this.runToSpan.get(runId);
1155
+ const model = spanInfo?.model;
1156
+ const llmContext = {};
1157
+ if (model) {
1158
+ llmContext.model = model;
1159
+ }
1160
+ Object.assign(llmContext, usage);
1161
+ this.completeSpan(
1162
+ runId,
1163
+ llmOutput,
1164
+ void 0,
1165
+ Object.keys(llmContext).length > 0 ? llmContext : void 0
1166
+ );
1167
+ } catch {
1168
+ }
1169
+ }
1170
+ async handleLLMError(error, runId) {
1171
+ try {
1172
+ this.completeSpan(
1173
+ runId,
1174
+ void 0,
1175
+ error instanceof Error ? error.message : String(error)
1176
+ );
1177
+ } catch {
1178
+ }
1179
+ }
1180
+ async handleLLMNewToken() {
1181
+ }
1182
+ // ── tool callbacks ────────────────────────────────────────────
1183
+ async handleToolStart(tool, input, runId, parentRunId, tags, metadata) {
1184
+ try {
1185
+ const name = tool.name ?? "tool";
1186
+ this.startSpan(
1187
+ runId,
1188
+ parentRunId,
1189
+ String(name),
1190
+ "function",
1191
+ input,
1192
+ metadata,
1193
+ tags
1194
+ );
1195
+ } catch {
1196
+ }
1197
+ }
1198
+ async handleToolEnd(output, runId) {
1199
+ try {
1200
+ this.completeSpan(runId, output);
1201
+ } catch {
1202
+ }
1203
+ }
1204
+ async handleToolError(error, runId) {
1205
+ try {
1206
+ this.completeSpan(
1207
+ runId,
1208
+ void 0,
1209
+ error instanceof Error ? error.message : String(error)
1210
+ );
1211
+ } catch {
1212
+ }
1213
+ }
1214
+ // ── retriever callbacks ───────────────────────────────────────
1215
+ async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata) {
1216
+ try {
1217
+ const name = retriever.name ?? "retriever";
1218
+ this.startSpan(
1219
+ runId,
1220
+ parentRunId,
1221
+ String(name),
1222
+ "function",
1223
+ query,
1224
+ metadata,
1225
+ tags
1226
+ );
1227
+ } catch {
1228
+ }
1229
+ }
1230
+ async handleRetrieverEnd(documents, runId) {
1231
+ try {
1232
+ this.completeSpan(runId, documents);
1233
+ } catch {
1234
+ }
1235
+ }
1236
+ async handleRetrieverError(error, runId) {
1237
+ try {
1238
+ this.completeSpan(
1239
+ runId,
1240
+ void 0,
1241
+ error instanceof Error ? error.message : String(error)
1242
+ );
1243
+ } catch {
1244
+ }
1245
+ }
1246
+ };
1247
+
300
1248
  // src/tracing.ts
301
1249
  var BitfabOpenAITracingProcessor = class {
302
1250
  /**
@@ -805,6 +1753,67 @@ var Bitfab = class {
805
1753
  }
806
1754
  });
807
1755
  }
1756
+ /**
1757
+ * Get a LangGraph/LangChain callback handler for tracing.
1758
+ *
1759
+ * The handler captures graph node execution, LLM calls, and tool
1760
+ * invocations as Bitfab spans with proper parent-child hierarchy.
1761
+ *
1762
+ * ```typescript
1763
+ * const handler = client.getLangGraphCallbackHandler("my-agent");
1764
+ * const result = await agent.invoke(
1765
+ * { messages: [...] },
1766
+ * { callbacks: [handler] },
1767
+ * );
1768
+ * ```
1769
+ *
1770
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
1771
+ * @returns A BitfabLangGraphCallbackHandler configured for this client
1772
+ */
1773
+ getLangGraphCallbackHandler(traceFunctionKey) {
1774
+ return new BitfabLangGraphCallbackHandler({
1775
+ apiKey: this.apiKey,
1776
+ traceFunctionKey,
1777
+ serviceUrl: this.serviceUrl,
1778
+ getActiveSpanContext: () => {
1779
+ const stack = getSpanStack();
1780
+ return stack[stack.length - 1] ?? null;
1781
+ }
1782
+ });
1783
+ }
1784
+ /**
1785
+ * Get a Claude Agent SDK handler for tracing.
1786
+ *
1787
+ * The handler captures LLM turns, tool invocations, and subagent
1788
+ * execution as Bitfab spans with proper parent-child hierarchy.
1789
+ *
1790
+ * ```typescript
1791
+ * const handler = client.getClaudeAgentHandler("my-agent");
1792
+ * const options = handler.instrumentOptions({
1793
+ * model: "claude-sonnet-4-5-...",
1794
+ * });
1795
+ * const sdkClient = new ClaudeSDKClient(options);
1796
+ * await sdkClient.connect();
1797
+ * await sdkClient.query("Do something");
1798
+ * for await (const msg of handler.wrapResponse(sdkClient.receiveResponse())) {
1799
+ * // process messages
1800
+ * }
1801
+ * ```
1802
+ *
1803
+ * @param traceFunctionKey - Groups traces under this key in Bitfab
1804
+ * @returns A BitfabClaudeAgentHandler configured for this client
1805
+ */
1806
+ getClaudeAgentHandler(traceFunctionKey) {
1807
+ return new BitfabClaudeAgentHandler({
1808
+ apiKey: this.apiKey,
1809
+ traceFunctionKey,
1810
+ serviceUrl: this.serviceUrl,
1811
+ getActiveSpanContext: () => {
1812
+ const stack = getSpanStack();
1813
+ return stack[stack.length - 1] ?? null;
1814
+ }
1815
+ });
1816
+ }
808
1817
  /**
809
1818
  * Wrap a BAML client method to automatically capture prompt and LLM metadata.
810
1819
  *
@@ -1145,7 +2154,7 @@ var Bitfab = class {
1145
2154
  * @returns ReplayResult with items, testRunId, and testRunUrl
1146
2155
  */
1147
2156
  async replay(traceFunctionKey, fn, options) {
1148
- const { replay: doReplay } = await import("./replay-DJ2HXTHQ.js");
2157
+ const { replay: doReplay } = await import("./replay-PIWVDXIF.js");
1149
2158
  return doReplay(
1150
2159
  this.httpClient,
1151
2160
  this.serviceUrl,
@@ -1205,10 +2214,12 @@ var BitfabFunction = class {
1205
2214
  };
1206
2215
 
1207
2216
  export {
2217
+ BitfabClaudeAgentHandler,
2218
+ BitfabLangGraphCallbackHandler,
1208
2219
  BitfabOpenAITracingProcessor,
1209
2220
  getCurrentSpan,
1210
2221
  getCurrentTrace,
1211
2222
  Bitfab,
1212
2223
  BitfabFunction
1213
2224
  };
1214
- //# sourceMappingURL=chunk-WUJR72QY.js.map
2225
+ //# sourceMappingURL=chunk-6EZCV5TU.js.map