langsmith 0.5.24 → 0.5.26
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/client.cjs +9 -3
- package/dist/client.js +9 -3
- package/dist/experimental/anthropic/context.cjs +419 -49
- package/dist/experimental/anthropic/context.js +420 -50
- package/dist/experimental/anthropic/index.cjs +78 -10
- package/dist/experimental/anthropic/index.js +80 -12
- package/dist/experimental/anthropic/messages.cjs +53 -0
- package/dist/experimental/anthropic/messages.d.ts +6 -0
- package/dist/experimental/anthropic/messages.js +52 -0
- package/dist/experimental/anthropic/transcripts.cjs +144 -0
- package/dist/experimental/anthropic/transcripts.d.ts +22 -0
- package/dist/experimental/anthropic/transcripts.js +141 -0
- package/dist/experimental/anthropic/types.d.ts +1 -0
- package/dist/experimental/anthropic/usage.cjs +19 -20
- package/dist/experimental/anthropic/usage.d.ts +2 -1
- package/dist/experimental/anthropic/usage.js +18 -20
- package/dist/experimental/opencode/index.cjs +36 -0
- package/dist/experimental/opencode/index.d.ts +3 -0
- package/dist/experimental/opencode/index.js +32 -0
- package/dist/experimental/opencode/tracer.cjs +389 -0
- package/dist/experimental/opencode/tracer.d.ts +30 -0
- package/dist/experimental/opencode/tracer.js +385 -0
- package/dist/experimental/otel/setup.cjs +1 -1
- package/dist/experimental/otel/setup.js +1 -1
- package/dist/experimental/sandbox/sandbox.cjs +6 -2
- package/dist/experimental/sandbox/sandbox.d.ts +5 -1
- package/dist/experimental/sandbox/sandbox.js +6 -2
- package/dist/experimental/sandbox/types.d.ts +3 -1
- package/dist/experimental/vercel/index.cjs +1 -1
- package/dist/experimental/vercel/index.js +1 -1
- package/dist/experimental/vercel/middleware.cjs +2 -1
- package/dist/experimental/vercel/middleware.js +2 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/schemas.d.ts +4 -0
- package/dist/singletons/traceable.cjs +1 -3
- package/dist/singletons/traceable.js +1 -3
- package/dist/traceable.cjs +1 -3
- package/dist/traceable.js +1 -3
- package/dist/utils/_git.cjs +2 -2
- package/dist/utils/_git.js +2 -2
- package/dist/utils/env.cjs +2 -2
- package/dist/utils/env.js +2 -2
- package/dist/utils/error.cjs +2 -2
- package/dist/utils/error.js +2 -2
- package/dist/utils/jestlike/reporter.cjs +1 -1
- package/dist/utils/jestlike/reporter.js +1 -1
- package/dist/utils/jestlike/vendor/chain.cjs +2 -3
- package/dist/utils/jestlike/vendor/chain.js +2 -3
- package/dist/vitest/utils/esm.mjs +4 -4
- package/dist/wrappers/gemini.cjs +1 -1
- package/dist/wrappers/gemini.js +1 -1
- package/dist/wrappers/openai.cjs +2 -6
- package/dist/wrappers/openai.js +2 -6
- package/experimental/opencode.cjs +1 -0
- package/experimental/opencode.d.cts +1 -0
- package/experimental/opencode.d.ts +1 -0
- package/experimental/opencode.js +1 -0
- package/package.json +24 -18
|
@@ -4,14 +4,15 @@ exports.wrapClaudeAgentSDK = wrapClaudeAgentSDK;
|
|
|
4
4
|
const traceable_js_1 = require("../../traceable.cjs");
|
|
5
5
|
const context_js_1 = require("./context.cjs");
|
|
6
6
|
const messages_js_1 = require("./messages.cjs");
|
|
7
|
+
const WRAPPED_TOOL_SYMBOL = Symbol.for("langsmith:claude_agent_sdk:wrapped_tool");
|
|
8
|
+
const WRAPPED_TOOL_HANDLER_SYMBOL = Symbol.for("langsmith:claude_agent_sdk:wrapped_tool_handler");
|
|
7
9
|
/**
|
|
8
10
|
* Wraps the Claude Agent SDK's query function to add LangSmith tracing.
|
|
9
11
|
* Traces the entire agent interaction including all streaming messages.
|
|
10
12
|
* @internal Use `wrapClaudeAgentSDK` instead.
|
|
11
13
|
*/
|
|
12
14
|
function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
13
|
-
async function* generator(originalGenerator, prompt) {
|
|
14
|
-
const streamManager = new context_js_1.StreamManager();
|
|
15
|
+
async function* generator(originalGenerator, prompt, streamManager) {
|
|
15
16
|
try {
|
|
16
17
|
let systemCount = 0;
|
|
17
18
|
for await (const message of originalGenerator) {
|
|
@@ -19,9 +20,9 @@ function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
|
19
20
|
const content = getLatestInput(prompt, systemCount);
|
|
20
21
|
systemCount += 1;
|
|
21
22
|
if (content != null)
|
|
22
|
-
streamManager.addMessage(content);
|
|
23
|
+
await streamManager.addMessage(content);
|
|
23
24
|
}
|
|
24
|
-
streamManager.addMessage(message);
|
|
25
|
+
await streamManager.addMessage(message);
|
|
25
26
|
yield message;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -78,6 +79,32 @@ function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
|
78
79
|
},
|
|
79
80
|
});
|
|
80
81
|
}
|
|
82
|
+
function addLangSmithHooks(params, streamManager) {
|
|
83
|
+
const options = { ...(params.options ?? {}) };
|
|
84
|
+
const hooks = { ...(options.hooks ?? {}) };
|
|
85
|
+
const addHook = (eventName) => {
|
|
86
|
+
const existing = Array.isArray(hooks[eventName])
|
|
87
|
+
? hooks[eventName]
|
|
88
|
+
: [];
|
|
89
|
+
hooks[eventName] = [
|
|
90
|
+
...existing,
|
|
91
|
+
{
|
|
92
|
+
hooks: [
|
|
93
|
+
async (input, toolUseId) => {
|
|
94
|
+
streamManager.addHookEvent(input, toolUseId);
|
|
95
|
+
return {};
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
};
|
|
101
|
+
addHook("PreToolUse");
|
|
102
|
+
addHook("SubagentStart");
|
|
103
|
+
addHook("SubagentStop");
|
|
104
|
+
addHook("Stop");
|
|
105
|
+
options.hooks = hooks;
|
|
106
|
+
return { ...params, options };
|
|
107
|
+
}
|
|
81
108
|
function processOutputs(rawOutputs) {
|
|
82
109
|
if ("outputs" in rawOutputs && Array.isArray(rawOutputs.outputs)) {
|
|
83
110
|
const sdkMessages = rawOutputs.outputs;
|
|
@@ -87,14 +114,17 @@ function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
|
87
114
|
return true;
|
|
88
115
|
return message.parent_tool_use_id == null;
|
|
89
116
|
})
|
|
90
|
-
.flatMap(messages_js_1.convertFromAnthropicMessage)
|
|
117
|
+
.flatMap(messages_js_1.convertFromAnthropicMessage)
|
|
118
|
+
.reduce((acc, message) => (0, messages_js_1.mergeMessagesById)(acc, [message]), []);
|
|
91
119
|
return { output: { messages } };
|
|
92
120
|
}
|
|
93
121
|
return rawOutputs;
|
|
94
122
|
}
|
|
95
123
|
return (0, traceable_js_1.traceable)((params, ...args) => {
|
|
96
|
-
const
|
|
97
|
-
const
|
|
124
|
+
const streamManager = new context_js_1.StreamManager();
|
|
125
|
+
const paramsWithHooks = addLangSmithHooks(params, streamManager);
|
|
126
|
+
const actualGenerator = queryFn.call(defaultThis, paramsWithHooks, ...args);
|
|
127
|
+
const wrappedGenerator = generator(actualGenerator, params.prompt, streamManager);
|
|
98
128
|
for (const method of Object.getOwnPropertyNames(Object.getPrototypeOf(actualGenerator)).filter((method) => !["constructor", "next", "throw", "return"].includes(method))) {
|
|
99
129
|
Object.defineProperty(wrappedGenerator, method, {
|
|
100
130
|
get() {
|
|
@@ -151,16 +181,54 @@ function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
|
151
181
|
function wrapClaudeAgentSDK(sdk, config) {
|
|
152
182
|
const inputSdk = sdk;
|
|
153
183
|
const wrappedSdk = { ...sdk };
|
|
154
|
-
|
|
184
|
+
const toolAlreadyWrapped = "tool" in inputSdk &&
|
|
185
|
+
typeof inputSdk.tool === "function" &&
|
|
186
|
+
WRAPPED_TOOL_SYMBOL in inputSdk.tool;
|
|
187
|
+
if (("query" in inputSdk && (0, traceable_js_1.isTraceableFunction)(inputSdk.query)) ||
|
|
188
|
+
toolAlreadyWrapped) {
|
|
155
189
|
throw new Error("This instance of Claude Agent SDK has been already wrapped by `wrapClaudeAgentSDK`.");
|
|
156
190
|
}
|
|
157
191
|
// Wrap the query method if it exists
|
|
158
192
|
if ("query" in inputSdk && typeof inputSdk.query === "function") {
|
|
159
193
|
wrappedSdk.query = wrapClaudeAgentQuery(inputSdk.query, inputSdk, config);
|
|
160
194
|
}
|
|
161
|
-
// Wrap the tool method if it exists
|
|
195
|
+
// Wrap the tool method if it exists. The SDK invokes MCP tool handlers in an
|
|
196
|
+
// async context that may not inherit the query trace context, so bind nested
|
|
197
|
+
// traceable calls back to the active tool run when possible.
|
|
162
198
|
if ("tool" in inputSdk && typeof inputSdk.tool === "function") {
|
|
163
|
-
wrappedSdk.tool =
|
|
199
|
+
wrappedSdk.tool = (...args) => {
|
|
200
|
+
const toolName = typeof args[0] === "string" ? args[0] : undefined;
|
|
201
|
+
let handlerIndex = -1;
|
|
202
|
+
for (let index = args.length - 1; index >= 0; index -= 1) {
|
|
203
|
+
if (typeof args[index] === "function") {
|
|
204
|
+
handlerIndex = index;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (handlerIndex < 0) {
|
|
209
|
+
return inputSdk.tool?.(...args);
|
|
210
|
+
}
|
|
211
|
+
const originalHandler = args[handlerIndex];
|
|
212
|
+
if (WRAPPED_TOOL_HANDLER_SYMBOL in originalHandler) {
|
|
213
|
+
return inputSdk.tool?.(...args);
|
|
214
|
+
}
|
|
215
|
+
const wrappedHandler = (...handlerArgs) => {
|
|
216
|
+
const activeToolRun = context_js_1.StreamManager.getActiveToolRun(toolName, handlerArgs[0]);
|
|
217
|
+
if (activeToolRun == null) {
|
|
218
|
+
return originalHandler(...handlerArgs);
|
|
219
|
+
}
|
|
220
|
+
return (0, traceable_js_1.withRunTree)(activeToolRun, () => originalHandler(...handlerArgs));
|
|
221
|
+
};
|
|
222
|
+
Object.defineProperty(wrappedHandler, WRAPPED_TOOL_HANDLER_SYMBOL, {
|
|
223
|
+
value: true,
|
|
224
|
+
});
|
|
225
|
+
const wrappedArgs = [...args];
|
|
226
|
+
wrappedArgs[handlerIndex] = wrappedHandler;
|
|
227
|
+
return inputSdk.tool?.(...wrappedArgs);
|
|
228
|
+
};
|
|
229
|
+
Object.defineProperty(wrappedSdk.tool, WRAPPED_TOOL_SYMBOL, {
|
|
230
|
+
value: true,
|
|
231
|
+
});
|
|
164
232
|
}
|
|
165
233
|
// Keep createSdkMcpServer and other methods as-is (bound to original SDK)
|
|
166
234
|
if ("createSdkMcpServer" in inputSdk &&
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { traceable, isTraceableFunction } from "../../traceable.js";
|
|
1
|
+
import { traceable, isTraceableFunction, withRunTree, } from "../../traceable.js";
|
|
2
2
|
import { StreamManager } from "./context.js";
|
|
3
|
-
import { convertFromAnthropicMessage } from "./messages.js";
|
|
3
|
+
import { convertFromAnthropicMessage, mergeMessagesById } from "./messages.js";
|
|
4
|
+
const WRAPPED_TOOL_SYMBOL = Symbol.for("langsmith:claude_agent_sdk:wrapped_tool");
|
|
5
|
+
const WRAPPED_TOOL_HANDLER_SYMBOL = Symbol.for("langsmith:claude_agent_sdk:wrapped_tool_handler");
|
|
4
6
|
/**
|
|
5
7
|
* Wraps the Claude Agent SDK's query function to add LangSmith tracing.
|
|
6
8
|
* Traces the entire agent interaction including all streaming messages.
|
|
7
9
|
* @internal Use `wrapClaudeAgentSDK` instead.
|
|
8
10
|
*/
|
|
9
11
|
function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
10
|
-
async function* generator(originalGenerator, prompt) {
|
|
11
|
-
const streamManager = new StreamManager();
|
|
12
|
+
async function* generator(originalGenerator, prompt, streamManager) {
|
|
12
13
|
try {
|
|
13
14
|
let systemCount = 0;
|
|
14
15
|
for await (const message of originalGenerator) {
|
|
@@ -16,9 +17,9 @@ function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
|
16
17
|
const content = getLatestInput(prompt, systemCount);
|
|
17
18
|
systemCount += 1;
|
|
18
19
|
if (content != null)
|
|
19
|
-
streamManager.addMessage(content);
|
|
20
|
+
await streamManager.addMessage(content);
|
|
20
21
|
}
|
|
21
|
-
streamManager.addMessage(message);
|
|
22
|
+
await streamManager.addMessage(message);
|
|
22
23
|
yield message;
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -75,6 +76,32 @@ function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
|
75
76
|
},
|
|
76
77
|
});
|
|
77
78
|
}
|
|
79
|
+
function addLangSmithHooks(params, streamManager) {
|
|
80
|
+
const options = { ...(params.options ?? {}) };
|
|
81
|
+
const hooks = { ...(options.hooks ?? {}) };
|
|
82
|
+
const addHook = (eventName) => {
|
|
83
|
+
const existing = Array.isArray(hooks[eventName])
|
|
84
|
+
? hooks[eventName]
|
|
85
|
+
: [];
|
|
86
|
+
hooks[eventName] = [
|
|
87
|
+
...existing,
|
|
88
|
+
{
|
|
89
|
+
hooks: [
|
|
90
|
+
async (input, toolUseId) => {
|
|
91
|
+
streamManager.addHookEvent(input, toolUseId);
|
|
92
|
+
return {};
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
};
|
|
98
|
+
addHook("PreToolUse");
|
|
99
|
+
addHook("SubagentStart");
|
|
100
|
+
addHook("SubagentStop");
|
|
101
|
+
addHook("Stop");
|
|
102
|
+
options.hooks = hooks;
|
|
103
|
+
return { ...params, options };
|
|
104
|
+
}
|
|
78
105
|
function processOutputs(rawOutputs) {
|
|
79
106
|
if ("outputs" in rawOutputs && Array.isArray(rawOutputs.outputs)) {
|
|
80
107
|
const sdkMessages = rawOutputs.outputs;
|
|
@@ -84,14 +111,17 @@ function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
|
84
111
|
return true;
|
|
85
112
|
return message.parent_tool_use_id == null;
|
|
86
113
|
})
|
|
87
|
-
.flatMap(convertFromAnthropicMessage)
|
|
114
|
+
.flatMap(convertFromAnthropicMessage)
|
|
115
|
+
.reduce((acc, message) => mergeMessagesById(acc, [message]), []);
|
|
88
116
|
return { output: { messages } };
|
|
89
117
|
}
|
|
90
118
|
return rawOutputs;
|
|
91
119
|
}
|
|
92
120
|
return traceable((params, ...args) => {
|
|
93
|
-
const
|
|
94
|
-
const
|
|
121
|
+
const streamManager = new StreamManager();
|
|
122
|
+
const paramsWithHooks = addLangSmithHooks(params, streamManager);
|
|
123
|
+
const actualGenerator = queryFn.call(defaultThis, paramsWithHooks, ...args);
|
|
124
|
+
const wrappedGenerator = generator(actualGenerator, params.prompt, streamManager);
|
|
95
125
|
for (const method of Object.getOwnPropertyNames(Object.getPrototypeOf(actualGenerator)).filter((method) => !["constructor", "next", "throw", "return"].includes(method))) {
|
|
96
126
|
Object.defineProperty(wrappedGenerator, method, {
|
|
97
127
|
get() {
|
|
@@ -148,16 +178,54 @@ function wrapClaudeAgentQuery(queryFn, defaultThis, baseConfig) {
|
|
|
148
178
|
export function wrapClaudeAgentSDK(sdk, config) {
|
|
149
179
|
const inputSdk = sdk;
|
|
150
180
|
const wrappedSdk = { ...sdk };
|
|
151
|
-
|
|
181
|
+
const toolAlreadyWrapped = "tool" in inputSdk &&
|
|
182
|
+
typeof inputSdk.tool === "function" &&
|
|
183
|
+
WRAPPED_TOOL_SYMBOL in inputSdk.tool;
|
|
184
|
+
if (("query" in inputSdk && isTraceableFunction(inputSdk.query)) ||
|
|
185
|
+
toolAlreadyWrapped) {
|
|
152
186
|
throw new Error("This instance of Claude Agent SDK has been already wrapped by `wrapClaudeAgentSDK`.");
|
|
153
187
|
}
|
|
154
188
|
// Wrap the query method if it exists
|
|
155
189
|
if ("query" in inputSdk && typeof inputSdk.query === "function") {
|
|
156
190
|
wrappedSdk.query = wrapClaudeAgentQuery(inputSdk.query, inputSdk, config);
|
|
157
191
|
}
|
|
158
|
-
// Wrap the tool method if it exists
|
|
192
|
+
// Wrap the tool method if it exists. The SDK invokes MCP tool handlers in an
|
|
193
|
+
// async context that may not inherit the query trace context, so bind nested
|
|
194
|
+
// traceable calls back to the active tool run when possible.
|
|
159
195
|
if ("tool" in inputSdk && typeof inputSdk.tool === "function") {
|
|
160
|
-
wrappedSdk.tool =
|
|
196
|
+
wrappedSdk.tool = (...args) => {
|
|
197
|
+
const toolName = typeof args[0] === "string" ? args[0] : undefined;
|
|
198
|
+
let handlerIndex = -1;
|
|
199
|
+
for (let index = args.length - 1; index >= 0; index -= 1) {
|
|
200
|
+
if (typeof args[index] === "function") {
|
|
201
|
+
handlerIndex = index;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (handlerIndex < 0) {
|
|
206
|
+
return inputSdk.tool?.(...args);
|
|
207
|
+
}
|
|
208
|
+
const originalHandler = args[handlerIndex];
|
|
209
|
+
if (WRAPPED_TOOL_HANDLER_SYMBOL in originalHandler) {
|
|
210
|
+
return inputSdk.tool?.(...args);
|
|
211
|
+
}
|
|
212
|
+
const wrappedHandler = (...handlerArgs) => {
|
|
213
|
+
const activeToolRun = StreamManager.getActiveToolRun(toolName, handlerArgs[0]);
|
|
214
|
+
if (activeToolRun == null) {
|
|
215
|
+
return originalHandler(...handlerArgs);
|
|
216
|
+
}
|
|
217
|
+
return withRunTree(activeToolRun, () => originalHandler(...handlerArgs));
|
|
218
|
+
};
|
|
219
|
+
Object.defineProperty(wrappedHandler, WRAPPED_TOOL_HANDLER_SYMBOL, {
|
|
220
|
+
value: true,
|
|
221
|
+
});
|
|
222
|
+
const wrappedArgs = [...args];
|
|
223
|
+
wrappedArgs[handlerIndex] = wrappedHandler;
|
|
224
|
+
return inputSdk.tool?.(...wrappedArgs);
|
|
225
|
+
};
|
|
226
|
+
Object.defineProperty(wrappedSdk.tool, WRAPPED_TOOL_SYMBOL, {
|
|
227
|
+
value: true,
|
|
228
|
+
});
|
|
161
229
|
}
|
|
162
230
|
// Keep createSdkMcpServer and other methods as-is (bound to original SDK)
|
|
163
231
|
if ("createSdkMcpServer" in inputSdk &&
|
|
@@ -1,10 +1,63 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeMessagesById = mergeMessagesById;
|
|
3
4
|
exports.flattenContentBlocks = flattenContentBlocks;
|
|
4
5
|
exports.convertFromAnthropicMessage = convertFromAnthropicMessage;
|
|
5
6
|
exports.isTaskTool = isTaskTool;
|
|
6
7
|
exports.isToolBlock = isToolBlock;
|
|
7
8
|
const utils_js_1 = require("./utils.cjs");
|
|
9
|
+
function getContentBlockKey(block) {
|
|
10
|
+
if (typeof block !== "object" || block == null)
|
|
11
|
+
return undefined;
|
|
12
|
+
const record = block;
|
|
13
|
+
if (typeof record.id === "string")
|
|
14
|
+
return `id:${record.id}`;
|
|
15
|
+
if (typeof record.signature === "string")
|
|
16
|
+
return `signature:${record.signature}`;
|
|
17
|
+
if (typeof record.type === "string" && typeof record.text === "string") {
|
|
18
|
+
return `text:${record.text}`;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return `json:${JSON.stringify(record)}`;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function mergeContentBlocks(existing, incoming) {
|
|
28
|
+
if (!Array.isArray(existing) || !Array.isArray(incoming))
|
|
29
|
+
return incoming;
|
|
30
|
+
const merged = [...existing];
|
|
31
|
+
const keys = new Set(merged.map(getContentBlockKey).filter(Boolean));
|
|
32
|
+
for (const block of incoming) {
|
|
33
|
+
const key = getContentBlockKey(block);
|
|
34
|
+
if (key != null && keys.has(key))
|
|
35
|
+
continue;
|
|
36
|
+
merged.push(block);
|
|
37
|
+
if (key != null)
|
|
38
|
+
keys.add(key);
|
|
39
|
+
}
|
|
40
|
+
return merged;
|
|
41
|
+
}
|
|
42
|
+
function mergeMessagesById(previousMessages, newMessages) {
|
|
43
|
+
const merged = [...previousMessages];
|
|
44
|
+
for (const message of newMessages) {
|
|
45
|
+
const existingIndex = merged.findIndex((prev) => prev.role === message.role &&
|
|
46
|
+
message.id != null &&
|
|
47
|
+
prev.id === message.id);
|
|
48
|
+
if (existingIndex < 0) {
|
|
49
|
+
merged.push(message);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const existing = merged[existingIndex];
|
|
53
|
+
merged[existingIndex] = {
|
|
54
|
+
...existing,
|
|
55
|
+
...message,
|
|
56
|
+
content: mergeContentBlocks(existing.content, message.content),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return merged;
|
|
60
|
+
}
|
|
8
61
|
/**
|
|
9
62
|
* Converts SDK content blocks into serializable objects.
|
|
10
63
|
* Matches Python's flatten_content_blocks behavior.
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { BetaContentBlock } from "./types.js";
|
|
2
|
+
export type LangSmithMessage = {
|
|
3
|
+
content?: unknown;
|
|
4
|
+
role?: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
} & Record<string, unknown>;
|
|
7
|
+
export declare function mergeMessagesById(previousMessages: LangSmithMessage[], newMessages: LangSmithMessage[]): LangSmithMessage[];
|
|
2
8
|
/**
|
|
3
9
|
* Converts SDK content blocks into serializable objects.
|
|
4
10
|
* Matches Python's flatten_content_blocks behavior.
|
|
@@ -1,4 +1,56 @@
|
|
|
1
1
|
import { isIterable } from "./utils.js";
|
|
2
|
+
function getContentBlockKey(block) {
|
|
3
|
+
if (typeof block !== "object" || block == null)
|
|
4
|
+
return undefined;
|
|
5
|
+
const record = block;
|
|
6
|
+
if (typeof record.id === "string")
|
|
7
|
+
return `id:${record.id}`;
|
|
8
|
+
if (typeof record.signature === "string")
|
|
9
|
+
return `signature:${record.signature}`;
|
|
10
|
+
if (typeof record.type === "string" && typeof record.text === "string") {
|
|
11
|
+
return `text:${record.text}`;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
return `json:${JSON.stringify(record)}`;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function mergeContentBlocks(existing, incoming) {
|
|
21
|
+
if (!Array.isArray(existing) || !Array.isArray(incoming))
|
|
22
|
+
return incoming;
|
|
23
|
+
const merged = [...existing];
|
|
24
|
+
const keys = new Set(merged.map(getContentBlockKey).filter(Boolean));
|
|
25
|
+
for (const block of incoming) {
|
|
26
|
+
const key = getContentBlockKey(block);
|
|
27
|
+
if (key != null && keys.has(key))
|
|
28
|
+
continue;
|
|
29
|
+
merged.push(block);
|
|
30
|
+
if (key != null)
|
|
31
|
+
keys.add(key);
|
|
32
|
+
}
|
|
33
|
+
return merged;
|
|
34
|
+
}
|
|
35
|
+
export function mergeMessagesById(previousMessages, newMessages) {
|
|
36
|
+
const merged = [...previousMessages];
|
|
37
|
+
for (const message of newMessages) {
|
|
38
|
+
const existingIndex = merged.findIndex((prev) => prev.role === message.role &&
|
|
39
|
+
message.id != null &&
|
|
40
|
+
prev.id === message.id);
|
|
41
|
+
if (existingIndex < 0) {
|
|
42
|
+
merged.push(message);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const existing = merged[existingIndex];
|
|
46
|
+
merged[existingIndex] = {
|
|
47
|
+
...existing,
|
|
48
|
+
...message,
|
|
49
|
+
content: mergeContentBlocks(existing.content, message.content),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return merged;
|
|
53
|
+
}
|
|
2
54
|
/**
|
|
3
55
|
* Converts SDK content blocks into serializable objects.
|
|
4
56
|
* Matches Python's flatten_content_blocks behavior.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readTranscript = readTranscript;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const messages_js_1 = require("./messages.cjs");
|
|
6
|
+
const usage_js_1 = require("./usage.cjs");
|
|
7
|
+
function parseTimestamp(value) {
|
|
8
|
+
if (typeof value !== "string" || value.length === 0)
|
|
9
|
+
return undefined;
|
|
10
|
+
const timestamp = Date.parse(value);
|
|
11
|
+
return Number.isNaN(timestamp) ? undefined : timestamp;
|
|
12
|
+
}
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
function toSDKAssistantMessage(data) {
|
|
17
|
+
const message = data.message;
|
|
18
|
+
if (!isRecord(message))
|
|
19
|
+
return undefined;
|
|
20
|
+
const id = message.id;
|
|
21
|
+
if (typeof id !== "string" || id.length === 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
return {
|
|
24
|
+
type: "assistant",
|
|
25
|
+
parent_tool_use_id: typeof data.parent_tool_use_id === "string"
|
|
26
|
+
? data.parent_tool_use_id
|
|
27
|
+
: null,
|
|
28
|
+
message: {
|
|
29
|
+
...message,
|
|
30
|
+
id,
|
|
31
|
+
content: Array.isArray(message.content)
|
|
32
|
+
? message.content
|
|
33
|
+
: [],
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function toSDKUserMessage(data) {
|
|
38
|
+
const message = data.message;
|
|
39
|
+
if (!isRecord(message))
|
|
40
|
+
return undefined;
|
|
41
|
+
return {
|
|
42
|
+
type: "user",
|
|
43
|
+
parent_tool_use_id: typeof data.parent_tool_use_id === "string"
|
|
44
|
+
? data.parent_tool_use_id
|
|
45
|
+
: null,
|
|
46
|
+
session_id: typeof data.session_id === "string" ? data.session_id : "",
|
|
47
|
+
message: message,
|
|
48
|
+
tool_use_result: data.tool_use_result,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read a Claude Agent SDK JSONL transcript and return final assistant turns,
|
|
53
|
+
* final usage by message id, and tool_result blocks.
|
|
54
|
+
*
|
|
55
|
+
* The transcript format is not a contracted SDK API. This mirrors the Python
|
|
56
|
+
* reconciler and is intentionally best-effort: malformed lines and unreadable
|
|
57
|
+
* files are ignored so tracing never affects the user conversation.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
async function readTranscript(filePath) {
|
|
61
|
+
let contents;
|
|
62
|
+
try {
|
|
63
|
+
contents = await (0, promises_1.readFile)(filePath, "utf8");
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return { turns: [], usageByMessageId: {}, toolResults: [] };
|
|
67
|
+
}
|
|
68
|
+
const entriesById = new Map();
|
|
69
|
+
const usageByMessageId = {};
|
|
70
|
+
const toolResults = [];
|
|
71
|
+
const conversation = [];
|
|
72
|
+
for (const line of contents.split(/\r?\n/)) {
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
if (!trimmed)
|
|
75
|
+
continue;
|
|
76
|
+
let data;
|
|
77
|
+
try {
|
|
78
|
+
data = JSON.parse(trimmed);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (!isRecord(data))
|
|
84
|
+
continue;
|
|
85
|
+
if (data.type === "user") {
|
|
86
|
+
const sdkMessage = toSDKUserMessage(data);
|
|
87
|
+
if (sdkMessage == null)
|
|
88
|
+
continue;
|
|
89
|
+
const content = sdkMessage.message.content;
|
|
90
|
+
const toolUseResultIsError = isRecord(sdkMessage.tool_use_result)
|
|
91
|
+
? sdkMessage.tool_use_result.is_error === true ||
|
|
92
|
+
sdkMessage.tool_use_result.isError === true
|
|
93
|
+
: false;
|
|
94
|
+
if (Array.isArray(content)) {
|
|
95
|
+
for (const block of content) {
|
|
96
|
+
if (!isRecord(block) || block.type !== "tool_result")
|
|
97
|
+
continue;
|
|
98
|
+
const toolUseId = block.tool_use_id;
|
|
99
|
+
if (typeof toolUseId !== "string")
|
|
100
|
+
continue;
|
|
101
|
+
toolResults.push({
|
|
102
|
+
toolUseId,
|
|
103
|
+
content: block.content,
|
|
104
|
+
isError: block.is_error === true ||
|
|
105
|
+
block.isError === true ||
|
|
106
|
+
toolUseResultIsError,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
conversation.push(...(0, messages_js_1.convertFromAnthropicMessage)(sdkMessage));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (data.type !== "assistant")
|
|
114
|
+
continue;
|
|
115
|
+
const sdkMessage = toSDKAssistantMessage(data);
|
|
116
|
+
if (sdkMessage == null)
|
|
117
|
+
continue;
|
|
118
|
+
const messageId = sdkMessage.message.id;
|
|
119
|
+
const rawUsage = sdkMessage.message.usage;
|
|
120
|
+
if (rawUsage != null) {
|
|
121
|
+
usageByMessageId[messageId] = (0, usage_js_1.extractUsageMetadata)(rawUsage);
|
|
122
|
+
}
|
|
123
|
+
const turn = {
|
|
124
|
+
messageId,
|
|
125
|
+
model: sdkMessage.message.model,
|
|
126
|
+
content: sdkMessage.message.content,
|
|
127
|
+
usage: rawUsage,
|
|
128
|
+
usageMetadata: rawUsage != null ? (0, usage_js_1.extractUsageMetadata)(rawUsage) : undefined,
|
|
129
|
+
timestamp: parseTimestamp(data.timestamp),
|
|
130
|
+
inputMessages: conversation.slice(),
|
|
131
|
+
message: sdkMessage,
|
|
132
|
+
};
|
|
133
|
+
// Always overwrite. The final chunk (where stop_reason is set) appears last.
|
|
134
|
+
entriesById.set(messageId, turn);
|
|
135
|
+
if (sdkMessage.message.stop_reason) {
|
|
136
|
+
conversation.push(...(0, messages_js_1.convertFromAnthropicMessage)(sdkMessage));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
turns: Array.from(entriesById.values()).filter((turn) => turn.message.message.stop_reason),
|
|
141
|
+
usageByMessageId,
|
|
142
|
+
toolResults,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type LangSmithMessage } from "./messages.js";
|
|
2
|
+
import type { SDKAssistantMessage } from "./types.js";
|
|
3
|
+
export type TranscriptAssistantTurn = {
|
|
4
|
+
messageId: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
content: Record<string, unknown>[];
|
|
7
|
+
usage?: Record<string, unknown>;
|
|
8
|
+
usageMetadata?: Record<string, unknown>;
|
|
9
|
+
timestamp?: number;
|
|
10
|
+
inputMessages: LangSmithMessage[];
|
|
11
|
+
message: SDKAssistantMessage;
|
|
12
|
+
};
|
|
13
|
+
export type TranscriptToolResult = {
|
|
14
|
+
toolUseId: string;
|
|
15
|
+
content: unknown;
|
|
16
|
+
isError?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type TranscriptData = {
|
|
19
|
+
turns: TranscriptAssistantTurn[];
|
|
20
|
+
usageByMessageId: Record<string, Record<string, unknown>>;
|
|
21
|
+
toolResults: TranscriptToolResult[];
|
|
22
|
+
};
|