langsmith 0.4.8 → 0.4.9
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/experimental/anthropic/context.cjs +187 -0
- package/dist/experimental/anthropic/context.d.ts +5 -0
- package/dist/experimental/anthropic/context.js +183 -0
- package/dist/experimental/anthropic/index.cjs +82 -863
- package/dist/experimental/anthropic/index.d.ts +1 -1
- package/dist/experimental/anthropic/index.js +83 -864
- package/dist/experimental/anthropic/messages.cjs +102 -0
- package/dist/experimental/anthropic/messages.d.ts +6 -0
- package/dist/experimental/anthropic/messages.js +96 -0
- package/dist/experimental/anthropic/types.cjs +1 -0
- package/dist/experimental/anthropic/types.d.ts +50 -37
- package/dist/experimental/anthropic/types.js +1 -0
- package/dist/experimental/anthropic/usage.cjs +180 -0
- package/dist/experimental/anthropic/usage.d.ts +1 -0
- package/dist/experimental/anthropic/usage.js +175 -0
- package/dist/experimental/anthropic/utils.cjs +14 -0
- package/dist/experimental/anthropic/utils.d.ts +1 -1
- package/dist/experimental/anthropic/utils.js +13 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/usage.cjs +6 -7
- package/dist/utils/usage.js +6 -7
- package/experimental/anthropic.cjs +1 -0
- package/experimental/anthropic.d.cts +1 -0
- package/experimental/anthropic.d.ts +1 -0
- package/experimental/anthropic.js +1 -0
- package/package.json +14 -1
|
@@ -2,569 +2,99 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.wrapClaudeAgentSDK = wrapClaudeAgentSDK;
|
|
4
4
|
const traceable_js_1 = require("../../traceable.cjs");
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const createQueryContext = () => ({
|
|
8
|
-
activeToolRuns: new Map(),
|
|
9
|
-
clientManagedRuns: new Map(),
|
|
10
|
-
subagentSessions: new Map(),
|
|
11
|
-
activeSubagentToolUseId: undefined,
|
|
12
|
-
currentParentRun: undefined,
|
|
13
|
-
});
|
|
14
|
-
/**
|
|
15
|
-
* PreToolUse hook that creates a tool span when a tool execution starts.
|
|
16
|
-
* This traces ALL tools including built-in tools, external MCP tools, and SDK MCP tools.
|
|
17
|
-
* Skips tools that are client-managed (subagent sessions and their children).
|
|
18
|
-
*/
|
|
19
|
-
async function preToolUseHook(input, toolUseId, context) {
|
|
20
|
-
if (!toolUseId)
|
|
21
|
-
return {};
|
|
22
|
-
// Skip if this tool run is already managed by the client (subagent or its children)
|
|
23
|
-
if (context.clientManagedRuns.has(toolUseId)) {
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
const toolName = input.tool_name || "unknown_tool";
|
|
27
|
-
const toolInput = input.tool_input;
|
|
28
|
-
try {
|
|
29
|
-
const parent = context.currentParentRun || (0, traceable_js_1.getCurrentRunTree)();
|
|
30
|
-
if (!parent) {
|
|
31
|
-
return {};
|
|
32
|
-
}
|
|
33
|
-
const startTime = Date.now();
|
|
34
|
-
const toolRun = await parent.createChild({
|
|
35
|
-
name: toolName,
|
|
36
|
-
run_type: "tool",
|
|
37
|
-
inputs: toolInput ? { input: toolInput } : {},
|
|
38
|
-
});
|
|
39
|
-
await toolRun.postRun();
|
|
40
|
-
context.activeToolRuns.set(toolUseId, { run: toolRun, startTime });
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// Silently fail - don't interrupt tool execution
|
|
44
|
-
}
|
|
45
|
-
return {};
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* PostToolUse hook that ends the tool span when a tool execution completes.
|
|
49
|
-
* Handles both regular tool runs and client-managed runs (subagents and their children).
|
|
50
|
-
*/
|
|
51
|
-
async function postToolUseHook(input, toolUseId, context) {
|
|
52
|
-
if (!toolUseId)
|
|
53
|
-
return {};
|
|
54
|
-
const toolResponse = input.tool_response;
|
|
55
|
-
// Format outputs based on response type
|
|
56
|
-
const formatOutputs = (response) => {
|
|
57
|
-
let outputs;
|
|
58
|
-
if (typeof response === "object" && response !== null) {
|
|
59
|
-
if (Array.isArray(response)) {
|
|
60
|
-
outputs = { content: response };
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
outputs = response;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
outputs = response ? { output: String(response) } : {};
|
|
68
|
-
}
|
|
69
|
-
const isError = typeof response === "object" &&
|
|
70
|
-
response !== null &&
|
|
71
|
-
"is_error" in response &&
|
|
72
|
-
response.is_error === true;
|
|
73
|
-
return { outputs, isError };
|
|
74
|
-
};
|
|
75
|
-
try {
|
|
76
|
-
// Check if this is a client-managed run (subagent session or its children)
|
|
77
|
-
const clientRun = context.clientManagedRuns.get(toolUseId);
|
|
78
|
-
if (clientRun) {
|
|
79
|
-
context.clientManagedRuns.delete(toolUseId);
|
|
80
|
-
const { outputs, isError } = formatOutputs(toolResponse);
|
|
81
|
-
await clientRun.end({
|
|
82
|
-
outputs,
|
|
83
|
-
error: isError ? outputs.output?.toString() : undefined,
|
|
84
|
-
});
|
|
85
|
-
await clientRun.patchRun();
|
|
86
|
-
return {};
|
|
87
|
-
}
|
|
88
|
-
// Handle regular tool runs
|
|
89
|
-
const runInfo = context.activeToolRuns.get(toolUseId);
|
|
90
|
-
if (!runInfo) {
|
|
91
|
-
return {};
|
|
92
|
-
}
|
|
93
|
-
context.activeToolRuns.delete(toolUseId);
|
|
94
|
-
const { run: toolRun } = runInfo;
|
|
95
|
-
const { outputs, isError } = formatOutputs(toolResponse);
|
|
96
|
-
await toolRun.end({
|
|
97
|
-
outputs,
|
|
98
|
-
error: isError ? outputs.output?.toString() : undefined,
|
|
99
|
-
});
|
|
100
|
-
await toolRun.patchRun();
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
// Silently fail - don't interrupt tool execution
|
|
104
|
-
}
|
|
105
|
-
return {};
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Creates hook matchers for LangSmith tracing.
|
|
109
|
-
* Returns PreToolUse and PostToolUse hook configurations.
|
|
110
|
-
*/
|
|
111
|
-
function createTracingHooks(context) {
|
|
112
|
-
return {
|
|
113
|
-
PreToolUse: [
|
|
114
|
-
{
|
|
115
|
-
matcher: undefined, // Match all tools
|
|
116
|
-
hooks: [
|
|
117
|
-
async (input, toolUseId, _options) => preToolUseHook(input, toolUseId, context),
|
|
118
|
-
],
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
PostToolUse: [
|
|
122
|
-
{
|
|
123
|
-
matcher: undefined, // Match all tools
|
|
124
|
-
hooks: [
|
|
125
|
-
async (input, toolUseId, _options) => postToolUseHook(input, toolUseId, context),
|
|
126
|
-
],
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
SessionEnd: [
|
|
130
|
-
{
|
|
131
|
-
matcher: undefined,
|
|
132
|
-
hooks: [
|
|
133
|
-
async (_input) => {
|
|
134
|
-
// Clean up at end of session
|
|
135
|
-
clearActiveToolRuns(context);
|
|
136
|
-
return {};
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
SubagentStop: [
|
|
142
|
-
{
|
|
143
|
-
matcher: undefined,
|
|
144
|
-
hooks: [
|
|
145
|
-
async (_input, toolUseId) => {
|
|
146
|
-
// Clean up subagent session
|
|
147
|
-
if (toolUseId) {
|
|
148
|
-
context.subagentSessions.delete(toolUseId);
|
|
149
|
-
context.clientManagedRuns.delete(toolUseId);
|
|
150
|
-
}
|
|
151
|
-
return {};
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
},
|
|
155
|
-
],
|
|
156
|
-
Stop: [
|
|
157
|
-
{
|
|
158
|
-
matcher: undefined,
|
|
159
|
-
hooks: [
|
|
160
|
-
async (_input) => {
|
|
161
|
-
// Clean up on stop - ensure all runs are finalized
|
|
162
|
-
clearActiveToolRuns(context);
|
|
163
|
-
return {};
|
|
164
|
-
},
|
|
165
|
-
],
|
|
166
|
-
},
|
|
167
|
-
],
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Merges LangSmith tracing hooks with existing user hooks.
|
|
172
|
-
*/
|
|
173
|
-
function mergeHooks(existingHooks, context) {
|
|
174
|
-
const tracingHooks = createTracingHooks(context);
|
|
175
|
-
if (!existingHooks)
|
|
176
|
-
return tracingHooks;
|
|
177
|
-
const merged = { ...existingHooks };
|
|
178
|
-
// Prepend tracing hooks so they run first
|
|
179
|
-
for (const [event, matchers] of Object.entries(tracingHooks)) {
|
|
180
|
-
merged[event] = [...matchers, ...(merged[event] ?? [])];
|
|
181
|
-
}
|
|
182
|
-
return merged;
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Type assertion to check if a tool is a Task tool
|
|
186
|
-
* @param tool - The tool to check
|
|
187
|
-
* @returns True if the tool is a Task tool, false otherwise
|
|
188
|
-
*/
|
|
189
|
-
function isTaskTool(tool) {
|
|
190
|
-
return tool.type === "tool_use" && tool.name === "Task";
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Type-assertion to check for tool blocks
|
|
194
|
-
*/
|
|
195
|
-
function isToolBlock(block) {
|
|
196
|
-
if (!block || typeof block !== "object")
|
|
197
|
-
return false;
|
|
198
|
-
return block.type === "tool_use";
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Processes tool uses in an AssistantMessage to detect and create subagent sessions.
|
|
202
|
-
* This matches Python's _handle_assistant_tool_uses behavior.
|
|
203
|
-
*
|
|
204
|
-
* @param message - The AssistantMessage to process
|
|
205
|
-
* @param parentRun - The parent run tree (main conversation chain)
|
|
206
|
-
*/
|
|
207
|
-
async function handleAssistantToolUses(message, parentRun, context) {
|
|
208
|
-
if (!parentRun)
|
|
209
|
-
return;
|
|
210
|
-
const content = message.message?.content;
|
|
211
|
-
if (!Array.isArray(content))
|
|
212
|
-
return;
|
|
213
|
-
const parentToolUseId = message.parent_tool_use_id;
|
|
214
|
-
for (const block of content) {
|
|
215
|
-
if (!isToolBlock(block) || !block.id)
|
|
216
|
-
continue;
|
|
217
|
-
try {
|
|
218
|
-
// Check if this is a Task tool (subagent) at the top level
|
|
219
|
-
if (isTaskTool(block) && !parentToolUseId) {
|
|
220
|
-
// Extract subagent name from input
|
|
221
|
-
const subagentName = block.input.subagent_type ||
|
|
222
|
-
block.input.agent_type ||
|
|
223
|
-
(block.input.description
|
|
224
|
-
? block.input.description.split(" ")[0]
|
|
225
|
-
: null) ||
|
|
226
|
-
"unknown-agent";
|
|
227
|
-
const subagentSession = await parentRun.createChild({
|
|
228
|
-
name: subagentName,
|
|
229
|
-
run_type: "chain",
|
|
230
|
-
inputs: block.input,
|
|
231
|
-
});
|
|
232
|
-
// Post the run to start it, but DON'T end it yet
|
|
233
|
-
// It will be ended when we receive the tool result or at cleanup
|
|
234
|
-
await subagentSession.postRun();
|
|
235
|
-
// Store in both maps
|
|
236
|
-
context.subagentSessions.set(block.id, subagentSession);
|
|
237
|
-
context.clientManagedRuns.set(block.id, subagentSession);
|
|
238
|
-
}
|
|
239
|
-
// Check if tool use is within a subagent
|
|
240
|
-
else if (parentToolUseId &&
|
|
241
|
-
context.subagentSessions.has(parentToolUseId)) {
|
|
242
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
243
|
-
const subagentSession = context.subagentSessions.get(parentToolUseId);
|
|
244
|
-
// Create tool run as child of subagent
|
|
245
|
-
const toolRun = await subagentSession.createChild({
|
|
246
|
-
name: block.name || "unknown_tool",
|
|
247
|
-
run_type: "tool",
|
|
248
|
-
inputs: block.input ? { input: block.input } : {},
|
|
249
|
-
});
|
|
250
|
-
await toolRun.postRun();
|
|
251
|
-
context.clientManagedRuns.set(block.id, toolRun);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
catch {
|
|
255
|
-
// Silently fail - don't interrupt message processing
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Clears all active tool runs and client-managed runs. Called when a conversation ends.
|
|
261
|
-
*/
|
|
262
|
-
function clearActiveToolRuns(context) {
|
|
263
|
-
// Clean up client-managed runs (subagents and their children)
|
|
264
|
-
for (const [, run] of context.clientManagedRuns) {
|
|
265
|
-
try {
|
|
266
|
-
run
|
|
267
|
-
.end({ error: "Run not completed (conversation ended)" })
|
|
268
|
-
.then(() => run.patchRun())
|
|
269
|
-
.catch(() => { });
|
|
270
|
-
}
|
|
271
|
-
catch {
|
|
272
|
-
// Ignore cleanup errors
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
context.clientManagedRuns.clear();
|
|
276
|
-
context.subagentSessions.clear();
|
|
277
|
-
context.activeSubagentToolUseId = undefined;
|
|
278
|
-
// Clean up regular tool runs
|
|
279
|
-
for (const [, { run }] of context.activeToolRuns) {
|
|
280
|
-
try {
|
|
281
|
-
run
|
|
282
|
-
.end({ error: "Tool run not completed (conversation ended)" })
|
|
283
|
-
.then(() => run.patchRun())
|
|
284
|
-
.catch(() => { });
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
// Ignore cleanup errors
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
context.activeToolRuns.clear();
|
|
291
|
-
}
|
|
5
|
+
const context_js_1 = require("./context.cjs");
|
|
6
|
+
const messages_js_1 = require("./messages.cjs");
|
|
292
7
|
/**
|
|
293
8
|
* Wraps the Claude Agent SDK's query function to add LangSmith tracing.
|
|
294
9
|
* Traces the entire agent interaction including all streaming messages.
|
|
295
|
-
*
|
|
10
|
+
* @internal Use `wrapClaudeAgentSDK` instead.
|
|
296
11
|
*/
|
|
297
12
|
function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
const { prompt, options = {} } = params;
|
|
301
|
-
// Inject LangSmith tracing hooks into options
|
|
302
|
-
const mergedHooks = mergeHooks(options.hooks, context);
|
|
303
|
-
const modifiedOptions = { ...options, hooks: mergedHooks };
|
|
304
|
-
const modifiedParams = { ...params, options: modifiedOptions };
|
|
305
|
-
return {
|
|
306
|
-
prompt,
|
|
307
|
-
options: modifiedOptions,
|
|
308
|
-
modifiedArgs: [modifiedParams, ...args.slice(1)],
|
|
309
|
-
};
|
|
310
|
-
};
|
|
311
|
-
async function* generator(originalGenerator, prompt, options, context) {
|
|
312
|
-
const finalResults = [];
|
|
313
|
-
// Track assistant messages by their message ID for proper streaming handling
|
|
314
|
-
// Each message ID maps to { message, startTime } - we keep the latest streaming update
|
|
315
|
-
const pendingMessages = new Map();
|
|
316
|
-
// Track which message IDs have already had spans created
|
|
317
|
-
// This prevents creating duplicate spans when the SDK sends multiple updates
|
|
318
|
-
// for the same message ID with stop_reason set
|
|
319
|
-
const completedMessageIds = new Set();
|
|
320
|
-
// Store child run promises for proper async handling
|
|
321
|
-
const childRunEndPromises = [];
|
|
322
|
-
// Track usage from ResultMessage to add to the parent span
|
|
323
|
-
let resultUsage;
|
|
324
|
-
// Track additional metadata from the SDK
|
|
325
|
-
const extraMetadata = [];
|
|
326
|
-
// Track usage from completed assistant message spans (by model)
|
|
327
|
-
// Used to calculate remaining tokens for pending messages
|
|
328
|
-
const completedUsageByModel = new Map();
|
|
329
|
-
// Create an LLM span for a specific message ID
|
|
330
|
-
const createLLMSpanForId = async (messageId) => {
|
|
331
|
-
// Skip if we've already created a span for this message ID
|
|
332
|
-
if (completedMessageIds.has(messageId)) {
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
const pending = pendingMessages.get(messageId);
|
|
336
|
-
if (!pending)
|
|
337
|
-
return;
|
|
338
|
-
pendingMessages.delete(messageId);
|
|
339
|
-
completedMessageIds.add(messageId);
|
|
340
|
-
// Track the usage before creating the span
|
|
341
|
-
const model = pending.message.message?.model;
|
|
342
|
-
const usage = pending.message.message?.usage;
|
|
343
|
-
if (model && usage) {
|
|
344
|
-
const existing = completedUsageByModel.get(model) || {
|
|
345
|
-
inputTokens: 0,
|
|
346
|
-
outputTokens: 0,
|
|
347
|
-
cacheReadTokens: 0,
|
|
348
|
-
cacheCreationTokens: 0,
|
|
349
|
-
};
|
|
350
|
-
existing.inputTokens += usage.input_tokens || 0;
|
|
351
|
-
existing.outputTokens += usage.output_tokens || 0;
|
|
352
|
-
existing.cacheReadTokens += usage.cache_read_input_tokens || 0;
|
|
353
|
-
existing.cacheCreationTokens += usage.cache_creation_input_tokens || 0;
|
|
354
|
-
completedUsageByModel.set(model, existing);
|
|
355
|
-
}
|
|
356
|
-
const finalMessageContent = await createLLMSpanForMessages([pending.message], pending.messageHistory, options, pending.startTime, context);
|
|
357
|
-
if (finalMessageContent)
|
|
358
|
-
finalResults.push(finalMessageContent);
|
|
359
|
-
};
|
|
13
|
+
async function* generator(originalGenerator, prompt) {
|
|
14
|
+
const streamManager = new context_js_1.StreamManager();
|
|
360
15
|
try {
|
|
16
|
+
let systemCount = 0;
|
|
361
17
|
for await (const message of originalGenerator) {
|
|
362
|
-
const currentTime = Date.now();
|
|
363
18
|
if (message.type === "system") {
|
|
364
|
-
const content = getLatestInput(prompt);
|
|
19
|
+
const content = getLatestInput(prompt, systemCount);
|
|
20
|
+
systemCount += 1;
|
|
365
21
|
if (content != null)
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
// Handle assistant messages - group by message ID for streaming
|
|
369
|
-
// Multiple messages with the same ID are streaming updates; use the last one
|
|
370
|
-
if (message.type === "assistant") {
|
|
371
|
-
const messageId = message.message?.id;
|
|
372
|
-
// If we have an active subagent context and this message doesn't have parent_tool_use_id,
|
|
373
|
-
// check if this is a new main conversation message (which would end the subagent execution)
|
|
374
|
-
if (context.activeSubagentToolUseId && !message.parent_tool_use_id) {
|
|
375
|
-
// Check if this message contains tool uses - if it does, it's part of main conversation
|
|
376
|
-
const content = message.message?.content;
|
|
377
|
-
if (Array.isArray(content)) {
|
|
378
|
-
const hasToolUse = content.some((block) => block &&
|
|
379
|
-
typeof block === "object" &&
|
|
380
|
-
block.type === "tool_use");
|
|
381
|
-
// If this message has tool uses and none are within the subagent, it's a new turn
|
|
382
|
-
if (hasToolUse) {
|
|
383
|
-
// Clean up the subagent session
|
|
384
|
-
context.subagentSessions.delete(context.activeSubagentToolUseId);
|
|
385
|
-
context.activeSubagentToolUseId = undefined;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
if (messageId) {
|
|
390
|
-
// Check if this is a new message or an update to existing
|
|
391
|
-
const existing = pendingMessages.get(messageId);
|
|
392
|
-
if (!existing) {
|
|
393
|
-
// New message arrived - finalize all OTHER pending messages first
|
|
394
|
-
// (they must be complete if we're seeing a new message)
|
|
395
|
-
// Finalize all other pending messages
|
|
396
|
-
for (const [otherId] of pendingMessages) {
|
|
397
|
-
if (otherId !== messageId) {
|
|
398
|
-
const spanPromise = createLLMSpanForId(otherId);
|
|
399
|
-
childRunEndPromises.push(spanPromise);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
pendingMessages.set(messageId, {
|
|
403
|
-
message,
|
|
404
|
-
messageHistory: finalResults.slice(0),
|
|
405
|
-
startTime: currentTime,
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
else {
|
|
409
|
-
// Streaming update - keep the start time, update the message
|
|
410
|
-
pendingMessages.set(messageId, {
|
|
411
|
-
message,
|
|
412
|
-
messageHistory: finalResults.slice(0),
|
|
413
|
-
startTime: existing.startTime,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
// Push the message to the final results,
|
|
417
|
-
// Used to create spans with the full chat history as input
|
|
418
|
-
if ("content" in message.message && message.message.content) {
|
|
419
|
-
finalResults.push({
|
|
420
|
-
content: flattenContentBlocks(message.message.content),
|
|
421
|
-
role: "assistant",
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
// Check if this message has a stop_reason (meaning it's complete)
|
|
425
|
-
// If so, create the span now (createLLMSpanForId will skip if already created)
|
|
426
|
-
if (message.message?.stop_reason) {
|
|
427
|
-
const spanPromise = createLLMSpanForId(messageId);
|
|
428
|
-
childRunEndPromises.push(spanPromise);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
// Process tool uses for subagent detection (matches Python's _handle_assistant_tool_uses)
|
|
432
|
-
await handleAssistantToolUses(message, context.currentParentRun, context);
|
|
433
|
-
}
|
|
434
|
-
// Handle UserMessage - add to conversation history (matches Python)
|
|
435
|
-
if (message.type === "user") {
|
|
436
|
-
if ("content" in message.message && message.message.content) {
|
|
437
|
-
finalResults.push({
|
|
438
|
-
content: flattenContentBlocks(message.message.content),
|
|
439
|
-
role: "user",
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
// If this is a tool result for a Task tool (subagent), we're entering the subagent's execution
|
|
443
|
-
// The subagent's assistant messages will come AFTER this result
|
|
444
|
-
if (message.parent_tool_use_id &&
|
|
445
|
-
context.subagentSessions.has(message.parent_tool_use_id)) {
|
|
446
|
-
context.activeSubagentToolUseId = message.parent_tool_use_id;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
// Handle ResultMessage - extract usage and metadata
|
|
450
|
-
if (message.type === "result") {
|
|
451
|
-
// If modelUsage is available, aggregate from it (includes ALL models)
|
|
452
|
-
// Otherwise fall back to top-level usage field
|
|
453
|
-
if (message.modelUsage) {
|
|
454
|
-
// Aggregate usage from modelUsage (includes ALL models)
|
|
455
|
-
resultUsage = aggregateUsageFromModelUsage(message.modelUsage);
|
|
456
|
-
// Patch token counts for pending messages using modelUsage
|
|
457
|
-
// This handles the SDK limitation where the last assistant message
|
|
458
|
-
// doesn't receive final streaming updates with accurate token counts
|
|
459
|
-
for (const [, { message: pendingMsg }] of pendingMessages) {
|
|
460
|
-
const model = pendingMsg.message?.model;
|
|
461
|
-
if (model &&
|
|
462
|
-
message.modelUsage[model] &&
|
|
463
|
-
pendingMsg.message?.usage) {
|
|
464
|
-
const modelStats = message.modelUsage[model];
|
|
465
|
-
const completed = completedUsageByModel.get(model) || {
|
|
466
|
-
inputTokens: 0,
|
|
467
|
-
outputTokens: 0,
|
|
468
|
-
cacheReadTokens: 0,
|
|
469
|
-
cacheCreationTokens: 0,
|
|
470
|
-
};
|
|
471
|
-
// Calculate remaining tokens = total - completed
|
|
472
|
-
const remainingOutput = (modelStats.outputTokens || 0) - completed.outputTokens;
|
|
473
|
-
const remainingInput = (modelStats.inputTokens || 0) - completed.inputTokens;
|
|
474
|
-
const remainingCacheRead = (modelStats.cacheReadInputTokens || 0) -
|
|
475
|
-
completed.cacheReadTokens;
|
|
476
|
-
const remainingCacheCreation = (modelStats.cacheCreationInputTokens || 0) -
|
|
477
|
-
completed.cacheCreationTokens;
|
|
478
|
-
// Update the pending message's usage with remaining tokens
|
|
479
|
-
pendingMsg.message.usage.output_tokens = Math.max(0, remainingOutput);
|
|
480
|
-
pendingMsg.message.usage.input_tokens = Math.max(0, remainingInput);
|
|
481
|
-
if (remainingCacheRead > 0) {
|
|
482
|
-
pendingMsg.message.usage.cache_read_input_tokens =
|
|
483
|
-
remainingCacheRead;
|
|
484
|
-
}
|
|
485
|
-
if (remainingCacheCreation > 0) {
|
|
486
|
-
pendingMsg.message.usage.cache_creation_input_tokens =
|
|
487
|
-
remainingCacheCreation;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
else if (message.usage) {
|
|
493
|
-
// Fall back to top-level usage if modelUsage not available
|
|
494
|
-
resultUsage = extractUsageFromMessage(message);
|
|
495
|
-
}
|
|
496
|
-
// Add total_cost if available (LangSmith standard field)
|
|
497
|
-
if (message.total_cost_usd != null && resultUsage) {
|
|
498
|
-
resultUsage.total_cost = message.total_cost_usd;
|
|
499
|
-
}
|
|
500
|
-
// Add conversation-level metadata
|
|
501
|
-
if (message.is_error != null) {
|
|
502
|
-
extraMetadata.push(["is_error", message.is_error]);
|
|
503
|
-
}
|
|
504
|
-
if (message.num_turns != null) {
|
|
505
|
-
extraMetadata.push(["num_turns", message.num_turns]);
|
|
506
|
-
}
|
|
507
|
-
if (message.session_id != null) {
|
|
508
|
-
extraMetadata.push(["session_id", message.session_id]);
|
|
509
|
-
}
|
|
510
|
-
if (message.duration_ms != null) {
|
|
511
|
-
extraMetadata.push(["duration_ms", message.duration_ms]);
|
|
512
|
-
}
|
|
513
|
-
if (message.duration_api_ms != null) {
|
|
514
|
-
extraMetadata.push(["duration_api_ms", message.duration_api_ms]);
|
|
515
|
-
}
|
|
22
|
+
streamManager.addMessage(content);
|
|
516
23
|
}
|
|
24
|
+
streamManager.addMessage(message);
|
|
517
25
|
yield message;
|
|
518
26
|
}
|
|
519
|
-
// Create spans for any remaining pending messages (those without stop_reason)
|
|
520
|
-
for (const messageId of pendingMessages.keys()) {
|
|
521
|
-
const spanPromise = createLLMSpanForId(messageId);
|
|
522
|
-
childRunEndPromises.push(spanPromise);
|
|
523
|
-
}
|
|
524
|
-
// Wait for all child runs to complete
|
|
525
|
-
await Promise.all(childRunEndPromises);
|
|
526
|
-
// Apply usage metadata to the chain run using LangSmith's standard fields
|
|
527
|
-
const currentRun = (0, traceable_js_1.getCurrentRunTree)();
|
|
528
|
-
if (currentRun && (resultUsage || extraMetadata.length > 0)) {
|
|
529
|
-
// Initialize metadata object if needed
|
|
530
|
-
currentRun.extra ||= {};
|
|
531
|
-
currentRun.extra.metadata ||= {};
|
|
532
|
-
if (resultUsage) {
|
|
533
|
-
// Add LangSmith-standard usage fields directly to metadata
|
|
534
|
-
if (resultUsage.input_tokens !== undefined) {
|
|
535
|
-
currentRun.extra.metadata.input_tokens = resultUsage.input_tokens;
|
|
536
|
-
}
|
|
537
|
-
if (resultUsage.output_tokens !== undefined) {
|
|
538
|
-
currentRun.extra.metadata.output_tokens = resultUsage.output_tokens;
|
|
539
|
-
}
|
|
540
|
-
if (resultUsage.total_tokens !== undefined) {
|
|
541
|
-
currentRun.extra.metadata.total_tokens = resultUsage.total_tokens;
|
|
542
|
-
}
|
|
543
|
-
if (resultUsage.input_token_details) {
|
|
544
|
-
currentRun.extra.metadata.input_token_details =
|
|
545
|
-
resultUsage.input_token_details;
|
|
546
|
-
}
|
|
547
|
-
if (resultUsage.total_cost !== undefined) {
|
|
548
|
-
currentRun.extra.metadata.total_cost = resultUsage.total_cost;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
for (const [key, value] of extraMetadata) {
|
|
552
|
-
currentRun.extra.metadata[key] = value;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
27
|
}
|
|
556
28
|
finally {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
29
|
+
await streamManager.finish();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function getLatestInput(arg, systemCount) {
|
|
33
|
+
const value = (() => {
|
|
34
|
+
if (typeof arg !== "object" || arg == null)
|
|
35
|
+
return arg;
|
|
36
|
+
const toJSON = arg["toJSON"];
|
|
37
|
+
if (typeof toJSON !== "function")
|
|
38
|
+
return undefined;
|
|
39
|
+
const latest = toJSON();
|
|
40
|
+
return latest?.at(systemCount);
|
|
41
|
+
})();
|
|
42
|
+
if (value == null)
|
|
43
|
+
return undefined;
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
return {
|
|
46
|
+
type: "user",
|
|
47
|
+
message: { content: value, role: "user" },
|
|
48
|
+
parent_tool_use_id: null,
|
|
49
|
+
session_id: "",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return typeof value === "object" && value != null ? value : undefined;
|
|
53
|
+
}
|
|
54
|
+
async function processInputs(rawInputs) {
|
|
55
|
+
const inputs = rawInputs;
|
|
56
|
+
const newInputs = { ...inputs };
|
|
57
|
+
return Object.assign(newInputs, {
|
|
58
|
+
toJSON: () => {
|
|
59
|
+
const toJSON = (value) => {
|
|
60
|
+
if (typeof value !== "object" || value == null)
|
|
61
|
+
return value;
|
|
62
|
+
const fn = value?.toJSON;
|
|
63
|
+
if (typeof fn === "function")
|
|
64
|
+
return fn();
|
|
65
|
+
return value;
|
|
66
|
+
};
|
|
67
|
+
const prompt = toJSON(inputs.prompt);
|
|
68
|
+
const options = inputs.options != null
|
|
69
|
+
? { ...inputs.options }
|
|
70
|
+
: undefined;
|
|
71
|
+
if (options?.mcpServers != null) {
|
|
72
|
+
options.mcpServers = Object.fromEntries(Object.entries(options.mcpServers ?? {}).map(([key, value]) => [
|
|
73
|
+
key,
|
|
74
|
+
{ name: value.name, type: value.type },
|
|
75
|
+
]));
|
|
76
|
+
}
|
|
77
|
+
return { messages: (0, messages_js_1.convertFromAnthropicMessage)(prompt), options };
|
|
78
|
+
},
|
|
79
|
+
});
|
|
561
80
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
81
|
+
function processOutputs(rawOutputs) {
|
|
82
|
+
if ("outputs" in rawOutputs && Array.isArray(rawOutputs.outputs)) {
|
|
83
|
+
const sdkMessages = rawOutputs.outputs;
|
|
84
|
+
const messages = sdkMessages
|
|
85
|
+
.filter((message) => {
|
|
86
|
+
if (!("message" in message))
|
|
87
|
+
return true;
|
|
88
|
+
return message.parent_tool_use_id == null;
|
|
89
|
+
})
|
|
90
|
+
.flatMap(messages_js_1.convertFromAnthropicMessage);
|
|
91
|
+
return { output: { messages } };
|
|
92
|
+
}
|
|
93
|
+
return rawOutputs;
|
|
94
|
+
}
|
|
95
|
+
return (0, traceable_js_1.traceable)((params, ...args) => {
|
|
96
|
+
const actualGenerator = queryFn.call(defaultThis, params, ...args);
|
|
97
|
+
const wrappedGenerator = generator(actualGenerator, params.prompt);
|
|
568
98
|
for (const method of Object.getOwnPropertyNames(Object.getPrototypeOf(actualGenerator)).filter((method) => !["constructor", "next", "throw", "return"].includes(method))) {
|
|
569
99
|
Object.defineProperty(wrappedGenerator, method, {
|
|
570
100
|
get() {
|
|
@@ -576,318 +106,14 @@ function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
|
576
106
|
});
|
|
577
107
|
}
|
|
578
108
|
return wrappedGenerator;
|
|
579
|
-
}
|
|
580
|
-
// Wrap in traceable
|
|
581
|
-
return (0, traceable_js_1.traceable)(wrapped, {
|
|
109
|
+
}, {
|
|
582
110
|
name: "claude.conversation",
|
|
583
111
|
run_type: "chain",
|
|
584
112
|
...baseConfig,
|
|
585
113
|
metadata: { ...baseConfig?.metadata },
|
|
586
114
|
__deferredSerializableArgOptions: { maxDepth: 1 },
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const newInputs = { ...inputs };
|
|
590
|
-
return Object.assign(newInputs, {
|
|
591
|
-
toJSON: () => {
|
|
592
|
-
const toJSON = (value) => {
|
|
593
|
-
if (typeof value !== "object" || value == null)
|
|
594
|
-
return value;
|
|
595
|
-
const fn = value?.toJSON;
|
|
596
|
-
if (typeof fn === "function")
|
|
597
|
-
return fn();
|
|
598
|
-
return value;
|
|
599
|
-
};
|
|
600
|
-
const prompt = toJSON(inputs.prompt);
|
|
601
|
-
const options = toJSON(inputs.options);
|
|
602
|
-
const messages = (() => {
|
|
603
|
-
if (prompt == null)
|
|
604
|
-
return undefined;
|
|
605
|
-
const result = [];
|
|
606
|
-
if (typeof prompt === "string") {
|
|
607
|
-
result.push({ content: prompt, role: "user" });
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
for (const { message } of prompt) {
|
|
611
|
-
if (!message)
|
|
612
|
-
continue;
|
|
613
|
-
result.push({
|
|
614
|
-
content: flattenContentBlocks(message.content),
|
|
615
|
-
role: message.role,
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
return result;
|
|
620
|
-
})();
|
|
621
|
-
return { messages, options };
|
|
622
|
-
},
|
|
623
|
-
});
|
|
624
|
-
},
|
|
625
|
-
processOutputs(rawOutputs) {
|
|
626
|
-
if ("outputs" in rawOutputs && Array.isArray(rawOutputs.outputs)) {
|
|
627
|
-
const sdkMessages = rawOutputs.outputs;
|
|
628
|
-
const messages = sdkMessages.flatMap((sdkMessage) => {
|
|
629
|
-
if ("message" in sdkMessage && sdkMessage.message != null) {
|
|
630
|
-
return {
|
|
631
|
-
role: sdkMessage.message.role,
|
|
632
|
-
content: flattenContentBlocks(sdkMessage.message.content),
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
return [];
|
|
636
|
-
});
|
|
637
|
-
return { output: { messages } };
|
|
638
|
-
}
|
|
639
|
-
return rawOutputs;
|
|
640
|
-
},
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* Wraps a Claude Agent SDK tool definition to add LangSmith tracing for tool executions.
|
|
645
|
-
* Internal use only - use wrapClaudeAgentSDK instead.
|
|
646
|
-
*/
|
|
647
|
-
function wrapClaudeAgentTool(toolDef, baseConfig) {
|
|
648
|
-
return {
|
|
649
|
-
...toolDef,
|
|
650
|
-
handler: (0, traceable_js_1.traceable)(toolDef.handler, {
|
|
651
|
-
name: toolDef.name,
|
|
652
|
-
run_type: "tool",
|
|
653
|
-
...baseConfig,
|
|
654
|
-
}),
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Aggregates usage from modelUsage breakdown (includes all models, including hidden ones).
|
|
659
|
-
* This provides accurate totals when multiple models are used.
|
|
660
|
-
*/
|
|
661
|
-
function aggregateUsageFromModelUsage(modelUsage) {
|
|
662
|
-
const metrics = {};
|
|
663
|
-
let totalInputTokens = 0;
|
|
664
|
-
let totalOutputTokens = 0;
|
|
665
|
-
let totalCacheReadTokens = 0;
|
|
666
|
-
let totalCacheCreationTokens = 0;
|
|
667
|
-
// Aggregate across all models
|
|
668
|
-
for (const modelStats of Object.values(modelUsage)) {
|
|
669
|
-
totalInputTokens += modelStats.inputTokens || 0;
|
|
670
|
-
totalOutputTokens += modelStats.outputTokens || 0;
|
|
671
|
-
totalCacheReadTokens += modelStats.cacheReadInputTokens || 0;
|
|
672
|
-
totalCacheCreationTokens += modelStats.cacheCreationInputTokens || 0;
|
|
673
|
-
}
|
|
674
|
-
// Build input_token_details if we have cache tokens
|
|
675
|
-
if (totalCacheReadTokens > 0 || totalCacheCreationTokens > 0) {
|
|
676
|
-
metrics.input_token_details = {
|
|
677
|
-
cache_read: totalCacheReadTokens,
|
|
678
|
-
cache_creation: totalCacheCreationTokens,
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
// Sum all input tokens (new + cache read + cache creation)
|
|
682
|
-
const totalPromptTokens = totalInputTokens + totalCacheReadTokens + totalCacheCreationTokens;
|
|
683
|
-
metrics.input_tokens = totalPromptTokens;
|
|
684
|
-
metrics.output_tokens = totalOutputTokens;
|
|
685
|
-
metrics.total_tokens = totalPromptTokens + totalOutputTokens;
|
|
686
|
-
return metrics;
|
|
687
|
-
}
|
|
688
|
-
/**
|
|
689
|
-
* Extracts and normalizes usage metrics from a Claude Agent SDK message.
|
|
690
|
-
*/
|
|
691
|
-
function extractUsageFromMessage(message) {
|
|
692
|
-
const metrics = {};
|
|
693
|
-
// Assistant messages contain usage in message.message.usage
|
|
694
|
-
// Result messages contain usage in message.usage
|
|
695
|
-
let usage;
|
|
696
|
-
if (message.type === "assistant") {
|
|
697
|
-
usage = message.message?.usage;
|
|
698
|
-
}
|
|
699
|
-
else if (message.type === "result") {
|
|
700
|
-
usage = message.usage;
|
|
701
|
-
}
|
|
702
|
-
if (!usage || typeof usage !== "object") {
|
|
703
|
-
return metrics;
|
|
704
|
-
}
|
|
705
|
-
// Standard token counts - use LangSmith's expected field names
|
|
706
|
-
const inputTokens = (0, utils_js_1.getNumberProperty)(usage, "input_tokens") || 0;
|
|
707
|
-
const outputTokens = (0, utils_js_1.getNumberProperty)(usage, "output_tokens") || 0;
|
|
708
|
-
// Get cache tokens
|
|
709
|
-
const cacheRead = (0, utils_js_1.getNumberProperty)(usage, "cache_read_input_tokens") || 0;
|
|
710
|
-
const cacheCreation = (0, utils_js_1.getNumberProperty)(usage, "cache_creation_input_tokens") || 0;
|
|
711
|
-
// Build input_token_details if we have cache tokens
|
|
712
|
-
if (cacheRead > 0 || cacheCreation > 0) {
|
|
713
|
-
const inputTokenDetails = (0, usage_js_1.convertAnthropicUsageToInputTokenDetails)(usage);
|
|
714
|
-
if (Object.keys(inputTokenDetails).length > 0) {
|
|
715
|
-
metrics.input_token_details = inputTokenDetails;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
// Sum cache tokens into input_tokens total (matching Python's sum_anthropic_tokens)
|
|
719
|
-
const totalInputTokens = inputTokens + cacheRead + cacheCreation;
|
|
720
|
-
metrics.input_tokens = totalInputTokens;
|
|
721
|
-
metrics.output_tokens = outputTokens;
|
|
722
|
-
metrics.total_tokens = totalInputTokens + outputTokens;
|
|
723
|
-
return metrics;
|
|
724
|
-
}
|
|
725
|
-
function getLatestInput(arg) {
|
|
726
|
-
const value = (() => {
|
|
727
|
-
if (typeof arg !== "object" || arg == null)
|
|
728
|
-
return arg;
|
|
729
|
-
const toJSON = arg["toJSON"];
|
|
730
|
-
if (typeof toJSON !== "function")
|
|
731
|
-
return undefined;
|
|
732
|
-
const latest = toJSON();
|
|
733
|
-
return latest?.at(-1);
|
|
734
|
-
})();
|
|
735
|
-
if (typeof value == null)
|
|
736
|
-
return undefined;
|
|
737
|
-
if (typeof value === "string")
|
|
738
|
-
return { content: value, role: "user" };
|
|
739
|
-
const userMessage = value;
|
|
740
|
-
if (typeof userMessage === "string") {
|
|
741
|
-
return { content: userMessage, role: "user" };
|
|
742
|
-
}
|
|
743
|
-
if (typeof userMessage !== "object" || userMessage == null) {
|
|
744
|
-
return undefined;
|
|
745
|
-
}
|
|
746
|
-
return {
|
|
747
|
-
role: userMessage.message.role || "user",
|
|
748
|
-
content: flattenContentBlocks(userMessage.message.content),
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Creates an LLM span for a group of messages with the same message ID.
|
|
753
|
-
* Returns the final message content to add to conversation history.
|
|
754
|
-
* Handles subagent LLM turns by parenting them to the correct subagent session.
|
|
755
|
-
*/
|
|
756
|
-
async function createLLMSpanForMessages(messages, conversationHistory, options, startTime, context) {
|
|
757
|
-
if (messages.length === 0)
|
|
758
|
-
return undefined;
|
|
759
|
-
const lastMessage = messages[messages.length - 1];
|
|
760
|
-
// Create LLM spans for all AssistantMessages, not just those with usage
|
|
761
|
-
// (matches Python's behavior)
|
|
762
|
-
if (lastMessage.type !== "assistant") {
|
|
763
|
-
return undefined;
|
|
764
|
-
}
|
|
765
|
-
// Extract model from message first, fall back to options (matches Python)
|
|
766
|
-
const model = lastMessage.message.model || options.model;
|
|
767
|
-
const usage = extractUsageFromMessage(lastMessage);
|
|
768
|
-
const input = conversationHistory.length > 0 ? conversationHistory : undefined;
|
|
769
|
-
// Flatten content blocks for proper serialization (matches Python)
|
|
770
|
-
const outputs = messages
|
|
771
|
-
.map((m) => {
|
|
772
|
-
if (!("message" in m) || !("role" in m.message))
|
|
773
|
-
return undefined;
|
|
774
|
-
return {
|
|
775
|
-
content: flattenContentBlocks(m.message.content),
|
|
776
|
-
role: m.message.role,
|
|
777
|
-
};
|
|
778
|
-
})
|
|
779
|
-
.filter((c) => c !== undefined);
|
|
780
|
-
// Check if this message belongs to a subagent
|
|
781
|
-
// First check if message has explicit parent_tool_use_id
|
|
782
|
-
const parentToolUseId = lastMessage.parent_tool_use_id;
|
|
783
|
-
let subagentParent = parentToolUseId
|
|
784
|
-
? context.subagentSessions.get(parentToolUseId)
|
|
785
|
-
: undefined;
|
|
786
|
-
// If no explicit parent, check if we're in an active subagent context
|
|
787
|
-
if (!subagentParent && context.activeSubagentToolUseId) {
|
|
788
|
-
subagentParent = context.subagentSessions.get(context.activeSubagentToolUseId);
|
|
789
|
-
}
|
|
790
|
-
const endTime = Date.now();
|
|
791
|
-
// Format inputs: if we have a single input, use it directly; otherwise wrap as messages
|
|
792
|
-
const formattedInputs = input && input.length === 1 ? input[0] : input ? { messages: input } : {};
|
|
793
|
-
if (subagentParent) {
|
|
794
|
-
// Create LLM run as child of subagent session with proper start and end time
|
|
795
|
-
try {
|
|
796
|
-
const llmRun = await subagentParent.createChild({
|
|
797
|
-
name: "claude.assistant.turn",
|
|
798
|
-
run_type: "llm",
|
|
799
|
-
inputs: formattedInputs,
|
|
800
|
-
outputs: outputs[outputs.length - 1] || { content: outputs },
|
|
801
|
-
start_time: startTime,
|
|
802
|
-
end_time: endTime,
|
|
803
|
-
extra: {
|
|
804
|
-
metadata: {
|
|
805
|
-
...(model ? { ls_model_name: model } : {}),
|
|
806
|
-
usage_metadata: usage,
|
|
807
|
-
},
|
|
808
|
-
},
|
|
809
|
-
});
|
|
810
|
-
await llmRun.postRun();
|
|
811
|
-
}
|
|
812
|
-
catch {
|
|
813
|
-
// Silently fail
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
else {
|
|
817
|
-
// Regular LLM turn under main conversation
|
|
818
|
-
// Note: traceable doesn't support start_time config, so we use getCurrentRunTree
|
|
819
|
-
// and manually create the child run to preserve timing
|
|
820
|
-
const currentRun = (0, traceable_js_1.getCurrentRunTree)();
|
|
821
|
-
if (currentRun) {
|
|
822
|
-
try {
|
|
823
|
-
const llmRun = await currentRun.createChild({
|
|
824
|
-
name: "claude.assistant.turn",
|
|
825
|
-
run_type: "llm",
|
|
826
|
-
inputs: formattedInputs,
|
|
827
|
-
outputs: outputs[outputs.length - 1] || { content: outputs },
|
|
828
|
-
start_time: startTime,
|
|
829
|
-
end_time: endTime,
|
|
830
|
-
extra: {
|
|
831
|
-
metadata: {
|
|
832
|
-
...(model ? { ls_model_name: model } : {}),
|
|
833
|
-
usage_metadata: usage,
|
|
834
|
-
},
|
|
835
|
-
},
|
|
836
|
-
});
|
|
837
|
-
await llmRun.postRun();
|
|
838
|
-
}
|
|
839
|
-
catch {
|
|
840
|
-
// Silently fail
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
// Return flattened content for conversation history
|
|
845
|
-
return lastMessage.message?.content && lastMessage.message?.role
|
|
846
|
-
? {
|
|
847
|
-
content: flattenContentBlocks(lastMessage.message.content),
|
|
848
|
-
role: lastMessage.message.role,
|
|
849
|
-
}
|
|
850
|
-
: undefined;
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
* Converts SDK content blocks into serializable objects.
|
|
854
|
-
* Matches Python's flatten_content_blocks behavior.
|
|
855
|
-
*/
|
|
856
|
-
function flattenContentBlocks(content) {
|
|
857
|
-
if (!Array.isArray(content)) {
|
|
858
|
-
return content;
|
|
859
|
-
}
|
|
860
|
-
return content.map((block) => {
|
|
861
|
-
if (!block || typeof block !== "object" || !("type" in block)) {
|
|
862
|
-
return block;
|
|
863
|
-
}
|
|
864
|
-
const blockType = block.type;
|
|
865
|
-
switch (blockType) {
|
|
866
|
-
case "text":
|
|
867
|
-
return { type: "text", text: block.text || "" };
|
|
868
|
-
case "thinking":
|
|
869
|
-
return {
|
|
870
|
-
type: "thinking",
|
|
871
|
-
thinking: block.thinking || "",
|
|
872
|
-
signature: block.signature || "",
|
|
873
|
-
};
|
|
874
|
-
case "tool_use":
|
|
875
|
-
return {
|
|
876
|
-
type: "tool_use",
|
|
877
|
-
id: block.id,
|
|
878
|
-
name: block.name,
|
|
879
|
-
input: block.input,
|
|
880
|
-
};
|
|
881
|
-
case "tool_result":
|
|
882
|
-
return {
|
|
883
|
-
type: "tool_result",
|
|
884
|
-
tool_use_id: block.tool_use_id,
|
|
885
|
-
content: block.content,
|
|
886
|
-
is_error: block.is_error || false,
|
|
887
|
-
};
|
|
888
|
-
default:
|
|
889
|
-
return block;
|
|
890
|
-
}
|
|
115
|
+
processInputs,
|
|
116
|
+
processOutputs,
|
|
891
117
|
});
|
|
892
118
|
}
|
|
893
119
|
/**
|
|
@@ -930,14 +156,7 @@ function wrapClaudeAgentSDK(sdk, config) {
|
|
|
930
156
|
}
|
|
931
157
|
// Wrap the tool method if it exists
|
|
932
158
|
if ("tool" in inputSdk && typeof inputSdk.tool === "function") {
|
|
933
|
-
|
|
934
|
-
wrappedSdk.tool = function (...args) {
|
|
935
|
-
const toolDef = originalTool.apply(sdk, args);
|
|
936
|
-
if (toolDef && typeof toolDef === "object" && "handler" in toolDef) {
|
|
937
|
-
return wrapClaudeAgentTool(toolDef, config);
|
|
938
|
-
}
|
|
939
|
-
return toolDef;
|
|
940
|
-
};
|
|
159
|
+
wrappedSdk.tool = inputSdk.tool.bind(inputSdk);
|
|
941
160
|
}
|
|
942
161
|
// Keep createSdkMcpServer and other methods as-is (bound to original SDK)
|
|
943
162
|
if ("createSdkMcpServer" in inputSdk &&
|