langsmith 0.5.25 → 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 +6 -2
- package/dist/client.js +6 -2
- 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/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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { convertFromAnthropicMessage, } from "./messages.js";
|
|
3
|
+
import { extractUsageMetadata } from "./usage.js";
|
|
4
|
+
function parseTimestamp(value) {
|
|
5
|
+
if (typeof value !== "string" || value.length === 0)
|
|
6
|
+
return undefined;
|
|
7
|
+
const timestamp = Date.parse(value);
|
|
8
|
+
return Number.isNaN(timestamp) ? undefined : timestamp;
|
|
9
|
+
}
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function toSDKAssistantMessage(data) {
|
|
14
|
+
const message = data.message;
|
|
15
|
+
if (!isRecord(message))
|
|
16
|
+
return undefined;
|
|
17
|
+
const id = message.id;
|
|
18
|
+
if (typeof id !== "string" || id.length === 0)
|
|
19
|
+
return undefined;
|
|
20
|
+
return {
|
|
21
|
+
type: "assistant",
|
|
22
|
+
parent_tool_use_id: typeof data.parent_tool_use_id === "string"
|
|
23
|
+
? data.parent_tool_use_id
|
|
24
|
+
: null,
|
|
25
|
+
message: {
|
|
26
|
+
...message,
|
|
27
|
+
id,
|
|
28
|
+
content: Array.isArray(message.content)
|
|
29
|
+
? message.content
|
|
30
|
+
: [],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function toSDKUserMessage(data) {
|
|
35
|
+
const message = data.message;
|
|
36
|
+
if (!isRecord(message))
|
|
37
|
+
return undefined;
|
|
38
|
+
return {
|
|
39
|
+
type: "user",
|
|
40
|
+
parent_tool_use_id: typeof data.parent_tool_use_id === "string"
|
|
41
|
+
? data.parent_tool_use_id
|
|
42
|
+
: null,
|
|
43
|
+
session_id: typeof data.session_id === "string" ? data.session_id : "",
|
|
44
|
+
message: message,
|
|
45
|
+
tool_use_result: data.tool_use_result,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read a Claude Agent SDK JSONL transcript and return final assistant turns,
|
|
50
|
+
* final usage by message id, and tool_result blocks.
|
|
51
|
+
*
|
|
52
|
+
* The transcript format is not a contracted SDK API. This mirrors the Python
|
|
53
|
+
* reconciler and is intentionally best-effort: malformed lines and unreadable
|
|
54
|
+
* files are ignored so tracing never affects the user conversation.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export async function readTranscript(filePath) {
|
|
58
|
+
let contents;
|
|
59
|
+
try {
|
|
60
|
+
contents = await readFile(filePath, "utf8");
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return { turns: [], usageByMessageId: {}, toolResults: [] };
|
|
64
|
+
}
|
|
65
|
+
const entriesById = new Map();
|
|
66
|
+
const usageByMessageId = {};
|
|
67
|
+
const toolResults = [];
|
|
68
|
+
const conversation = [];
|
|
69
|
+
for (const line of contents.split(/\r?\n/)) {
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (!trimmed)
|
|
72
|
+
continue;
|
|
73
|
+
let data;
|
|
74
|
+
try {
|
|
75
|
+
data = JSON.parse(trimmed);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!isRecord(data))
|
|
81
|
+
continue;
|
|
82
|
+
if (data.type === "user") {
|
|
83
|
+
const sdkMessage = toSDKUserMessage(data);
|
|
84
|
+
if (sdkMessage == null)
|
|
85
|
+
continue;
|
|
86
|
+
const content = sdkMessage.message.content;
|
|
87
|
+
const toolUseResultIsError = isRecord(sdkMessage.tool_use_result)
|
|
88
|
+
? sdkMessage.tool_use_result.is_error === true ||
|
|
89
|
+
sdkMessage.tool_use_result.isError === true
|
|
90
|
+
: false;
|
|
91
|
+
if (Array.isArray(content)) {
|
|
92
|
+
for (const block of content) {
|
|
93
|
+
if (!isRecord(block) || block.type !== "tool_result")
|
|
94
|
+
continue;
|
|
95
|
+
const toolUseId = block.tool_use_id;
|
|
96
|
+
if (typeof toolUseId !== "string")
|
|
97
|
+
continue;
|
|
98
|
+
toolResults.push({
|
|
99
|
+
toolUseId,
|
|
100
|
+
content: block.content,
|
|
101
|
+
isError: block.is_error === true ||
|
|
102
|
+
block.isError === true ||
|
|
103
|
+
toolUseResultIsError,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
conversation.push(...convertFromAnthropicMessage(sdkMessage));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (data.type !== "assistant")
|
|
111
|
+
continue;
|
|
112
|
+
const sdkMessage = toSDKAssistantMessage(data);
|
|
113
|
+
if (sdkMessage == null)
|
|
114
|
+
continue;
|
|
115
|
+
const messageId = sdkMessage.message.id;
|
|
116
|
+
const rawUsage = sdkMessage.message.usage;
|
|
117
|
+
if (rawUsage != null) {
|
|
118
|
+
usageByMessageId[messageId] = extractUsageMetadata(rawUsage);
|
|
119
|
+
}
|
|
120
|
+
const turn = {
|
|
121
|
+
messageId,
|
|
122
|
+
model: sdkMessage.message.model,
|
|
123
|
+
content: sdkMessage.message.content,
|
|
124
|
+
usage: rawUsage,
|
|
125
|
+
usageMetadata: rawUsage != null ? extractUsageMetadata(rawUsage) : undefined,
|
|
126
|
+
timestamp: parseTimestamp(data.timestamp),
|
|
127
|
+
inputMessages: conversation.slice(),
|
|
128
|
+
message: sdkMessage,
|
|
129
|
+
};
|
|
130
|
+
// Always overwrite. The final chunk (where stop_reason is set) appears last.
|
|
131
|
+
entriesById.set(messageId, turn);
|
|
132
|
+
if (sdkMessage.message.stop_reason) {
|
|
133
|
+
conversation.push(...convertFromAnthropicMessage(sdkMessage));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
turns: Array.from(entriesById.values()).filter((turn) => turn.message.message.stop_reason),
|
|
138
|
+
usageByMessageId,
|
|
139
|
+
toolResults,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.aggregateUsageFromModelUsage = aggregateUsageFromModelUsage;
|
|
4
|
+
exports.extractUsageMetadata = extractUsageMetadata;
|
|
4
5
|
exports.extractUsageFromMessage = extractUsageFromMessage;
|
|
5
6
|
exports.correctUsageFromResults = correctUsageFromResults;
|
|
6
7
|
const usage_js_1 = require("../../utils/usage.cjs");
|
|
@@ -41,40 +42,38 @@ function aggregateUsageFromModelUsage(modelUsage) {
|
|
|
41
42
|
* Extracts and normalizes usage metrics from a Claude Agent SDK message.
|
|
42
43
|
* @internal
|
|
43
44
|
*/
|
|
44
|
-
function
|
|
45
|
+
function extractUsageMetadata(usage) {
|
|
45
46
|
const metrics = {};
|
|
46
|
-
// Assistant messages contain usage in message.message.usage
|
|
47
|
-
// Result messages contain usage in message.usage
|
|
48
|
-
let usage;
|
|
49
|
-
if (message.type === "assistant") {
|
|
50
|
-
usage = message.message?.usage;
|
|
51
|
-
}
|
|
52
|
-
else if (message.type === "result") {
|
|
53
|
-
usage = message.usage;
|
|
54
|
-
}
|
|
55
47
|
if (!usage || typeof usage !== "object") {
|
|
56
48
|
return metrics;
|
|
57
49
|
}
|
|
58
50
|
// Standard token counts - use LangSmith's expected field names
|
|
59
51
|
const inputTokens = (0, utils_js_1.getNumberProperty)(usage, "input_tokens") || 0;
|
|
60
52
|
const outputTokens = (0, utils_js_1.getNumberProperty)(usage, "output_tokens") || 0;
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (cacheRead > 0 || cacheCreation > 0) {
|
|
66
|
-
const inputTokenDetails = (0, usage_js_1.convertAnthropicUsageToInputTokenDetails)(usage);
|
|
67
|
-
if (Object.keys(inputTokenDetails).length > 0) {
|
|
68
|
-
metrics.input_token_details = inputTokenDetails;
|
|
69
|
-
}
|
|
53
|
+
const inputTokenDetails = (0, usage_js_1.convertAnthropicUsageToInputTokenDetails)(usage);
|
|
54
|
+
const cacheTokens = Object.values(inputTokenDetails).reduce((sum, value) => sum + (typeof value === "number" ? value : 0), 0);
|
|
55
|
+
if (Object.keys(inputTokenDetails).length > 0 && cacheTokens > 0) {
|
|
56
|
+
metrics.input_token_details = inputTokenDetails;
|
|
70
57
|
}
|
|
71
58
|
// Sum cache tokens into input_tokens total (matching Python's sum_anthropic_tokens)
|
|
72
|
-
const totalInputTokens = inputTokens +
|
|
59
|
+
const totalInputTokens = inputTokens + cacheTokens;
|
|
73
60
|
metrics.input_tokens = totalInputTokens;
|
|
74
61
|
metrics.output_tokens = outputTokens;
|
|
75
62
|
metrics.total_tokens = totalInputTokens + outputTokens;
|
|
76
63
|
return metrics;
|
|
77
64
|
}
|
|
65
|
+
function extractUsageFromMessage(message) {
|
|
66
|
+
// Assistant messages contain usage in message.message.usage
|
|
67
|
+
// Result messages contain usage in message.usage
|
|
68
|
+
let usage;
|
|
69
|
+
if (message.type === "assistant") {
|
|
70
|
+
usage = message.message?.usage;
|
|
71
|
+
}
|
|
72
|
+
else if (message.type === "result") {
|
|
73
|
+
usage = message.usage;
|
|
74
|
+
}
|
|
75
|
+
return extractUsageMetadata(usage);
|
|
76
|
+
}
|
|
78
77
|
/**
|
|
79
78
|
* Corrects usage metrics for assistant runs based on the results of the runs.
|
|
80
79
|
* @internal
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { SDKMessage } from "./types.js";
|
|
2
|
+
export declare function extractUsageFromMessage(message: SDKMessage): Record<string, unknown>;
|
|
@@ -36,40 +36,38 @@ export function aggregateUsageFromModelUsage(modelUsage) {
|
|
|
36
36
|
* Extracts and normalizes usage metrics from a Claude Agent SDK message.
|
|
37
37
|
* @internal
|
|
38
38
|
*/
|
|
39
|
-
export function
|
|
39
|
+
export function extractUsageMetadata(usage) {
|
|
40
40
|
const metrics = {};
|
|
41
|
-
// Assistant messages contain usage in message.message.usage
|
|
42
|
-
// Result messages contain usage in message.usage
|
|
43
|
-
let usage;
|
|
44
|
-
if (message.type === "assistant") {
|
|
45
|
-
usage = message.message?.usage;
|
|
46
|
-
}
|
|
47
|
-
else if (message.type === "result") {
|
|
48
|
-
usage = message.usage;
|
|
49
|
-
}
|
|
50
41
|
if (!usage || typeof usage !== "object") {
|
|
51
42
|
return metrics;
|
|
52
43
|
}
|
|
53
44
|
// Standard token counts - use LangSmith's expected field names
|
|
54
45
|
const inputTokens = getNumberProperty(usage, "input_tokens") || 0;
|
|
55
46
|
const outputTokens = getNumberProperty(usage, "output_tokens") || 0;
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (cacheRead > 0 || cacheCreation > 0) {
|
|
61
|
-
const inputTokenDetails = convertAnthropicUsageToInputTokenDetails(usage);
|
|
62
|
-
if (Object.keys(inputTokenDetails).length > 0) {
|
|
63
|
-
metrics.input_token_details = inputTokenDetails;
|
|
64
|
-
}
|
|
47
|
+
const inputTokenDetails = convertAnthropicUsageToInputTokenDetails(usage);
|
|
48
|
+
const cacheTokens = Object.values(inputTokenDetails).reduce((sum, value) => sum + (typeof value === "number" ? value : 0), 0);
|
|
49
|
+
if (Object.keys(inputTokenDetails).length > 0 && cacheTokens > 0) {
|
|
50
|
+
metrics.input_token_details = inputTokenDetails;
|
|
65
51
|
}
|
|
66
52
|
// Sum cache tokens into input_tokens total (matching Python's sum_anthropic_tokens)
|
|
67
|
-
const totalInputTokens = inputTokens +
|
|
53
|
+
const totalInputTokens = inputTokens + cacheTokens;
|
|
68
54
|
metrics.input_tokens = totalInputTokens;
|
|
69
55
|
metrics.output_tokens = outputTokens;
|
|
70
56
|
metrics.total_tokens = totalInputTokens + outputTokens;
|
|
71
57
|
return metrics;
|
|
72
58
|
}
|
|
59
|
+
export function extractUsageFromMessage(message) {
|
|
60
|
+
// Assistant messages contain usage in message.message.usage
|
|
61
|
+
// Result messages contain usage in message.usage
|
|
62
|
+
let usage;
|
|
63
|
+
if (message.type === "assistant") {
|
|
64
|
+
usage = message.message?.usage;
|
|
65
|
+
}
|
|
66
|
+
else if (message.type === "result") {
|
|
67
|
+
usage = message.usage;
|
|
68
|
+
}
|
|
69
|
+
return extractUsageMetadata(usage);
|
|
70
|
+
}
|
|
73
71
|
/**
|
|
74
72
|
* Corrects usage metrics for assistant runs based on the results of the runs.
|
|
75
73
|
* @internal
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LangSmithPlugin = void 0;
|
|
4
|
+
// import type { Plugin } from "@opencode-ai/plugin";
|
|
5
|
+
const tracer_js_1 = require("./tracer.cjs");
|
|
6
|
+
const LangSmithPlugin = async (ctx) => {
|
|
7
|
+
const tracer = new tracer_js_1.OpenCodeSessionTracer();
|
|
8
|
+
async function getSessionHistory(sessionID) {
|
|
9
|
+
const past = await ctx.client.session.messages({
|
|
10
|
+
path: { id: sessionID },
|
|
11
|
+
});
|
|
12
|
+
if (past.error)
|
|
13
|
+
throw past.error;
|
|
14
|
+
return past.data;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
18
|
+
const sessionID = input.sessionID;
|
|
19
|
+
if (!sessionID)
|
|
20
|
+
return;
|
|
21
|
+
await tracer.handleSessionLoad(sessionID, getSessionHistory);
|
|
22
|
+
await tracer.handleSystem(input, output);
|
|
23
|
+
},
|
|
24
|
+
event: async (input) => {
|
|
25
|
+
const sessionID = "sessionID" in input.event.properties &&
|
|
26
|
+
typeof input.event.properties.sessionID === "string"
|
|
27
|
+
? input.event.properties.sessionID
|
|
28
|
+
: undefined;
|
|
29
|
+
if (!sessionID)
|
|
30
|
+
return;
|
|
31
|
+
await tracer.handleSessionLoad(sessionID, getSessionHistory);
|
|
32
|
+
await tracer.handleEvent(input);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
exports.LangSmithPlugin = LangSmithPlugin;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
import { OpenCodeSessionTracer } from "./tracer.js";
|
|
3
|
+
export const LangSmithPlugin = async (ctx) => {
|
|
4
|
+
const tracer = new OpenCodeSessionTracer();
|
|
5
|
+
async function getSessionHistory(sessionID) {
|
|
6
|
+
const past = await ctx.client.session.messages({
|
|
7
|
+
path: { id: sessionID },
|
|
8
|
+
});
|
|
9
|
+
if (past.error)
|
|
10
|
+
throw past.error;
|
|
11
|
+
return past.data;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
15
|
+
const sessionID = input.sessionID;
|
|
16
|
+
if (!sessionID)
|
|
17
|
+
return;
|
|
18
|
+
await tracer.handleSessionLoad(sessionID, getSessionHistory);
|
|
19
|
+
await tracer.handleSystem(input, output);
|
|
20
|
+
},
|
|
21
|
+
event: async (input) => {
|
|
22
|
+
const sessionID = "sessionID" in input.event.properties &&
|
|
23
|
+
typeof input.event.properties.sessionID === "string"
|
|
24
|
+
? input.event.properties.sessionID
|
|
25
|
+
: undefined;
|
|
26
|
+
if (!sessionID)
|
|
27
|
+
return;
|
|
28
|
+
await tracer.handleSessionLoad(sessionID, getSessionHistory);
|
|
29
|
+
await tracer.handleEvent(input);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
};
|