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.
Files changed (59) hide show
  1. package/dist/client.cjs +6 -2
  2. package/dist/client.js +6 -2
  3. package/dist/experimental/anthropic/context.cjs +419 -49
  4. package/dist/experimental/anthropic/context.js +420 -50
  5. package/dist/experimental/anthropic/index.cjs +78 -10
  6. package/dist/experimental/anthropic/index.js +80 -12
  7. package/dist/experimental/anthropic/messages.cjs +53 -0
  8. package/dist/experimental/anthropic/messages.d.ts +6 -0
  9. package/dist/experimental/anthropic/messages.js +52 -0
  10. package/dist/experimental/anthropic/transcripts.cjs +144 -0
  11. package/dist/experimental/anthropic/transcripts.d.ts +22 -0
  12. package/dist/experimental/anthropic/transcripts.js +141 -0
  13. package/dist/experimental/anthropic/types.d.ts +1 -0
  14. package/dist/experimental/anthropic/usage.cjs +19 -20
  15. package/dist/experimental/anthropic/usage.d.ts +2 -1
  16. package/dist/experimental/anthropic/usage.js +18 -20
  17. package/dist/experimental/opencode/index.cjs +36 -0
  18. package/dist/experimental/opencode/index.d.ts +3 -0
  19. package/dist/experimental/opencode/index.js +32 -0
  20. package/dist/experimental/opencode/tracer.cjs +389 -0
  21. package/dist/experimental/opencode/tracer.d.ts +30 -0
  22. package/dist/experimental/opencode/tracer.js +385 -0
  23. package/dist/experimental/otel/setup.cjs +1 -1
  24. package/dist/experimental/otel/setup.js +1 -1
  25. package/dist/experimental/sandbox/sandbox.cjs +6 -2
  26. package/dist/experimental/sandbox/sandbox.d.ts +5 -1
  27. package/dist/experimental/sandbox/sandbox.js +6 -2
  28. package/dist/experimental/sandbox/types.d.ts +3 -1
  29. package/dist/experimental/vercel/index.cjs +1 -1
  30. package/dist/experimental/vercel/index.js +1 -1
  31. package/dist/experimental/vercel/middleware.cjs +2 -1
  32. package/dist/experimental/vercel/middleware.js +2 -1
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/singletons/traceable.cjs +1 -3
  37. package/dist/singletons/traceable.js +1 -3
  38. package/dist/traceable.cjs +1 -3
  39. package/dist/traceable.js +1 -3
  40. package/dist/utils/_git.cjs +2 -2
  41. package/dist/utils/_git.js +2 -2
  42. package/dist/utils/env.cjs +2 -2
  43. package/dist/utils/env.js +2 -2
  44. package/dist/utils/error.cjs +2 -2
  45. package/dist/utils/error.js +2 -2
  46. package/dist/utils/jestlike/reporter.cjs +1 -1
  47. package/dist/utils/jestlike/reporter.js +1 -1
  48. package/dist/utils/jestlike/vendor/chain.cjs +2 -3
  49. package/dist/utils/jestlike/vendor/chain.js +2 -3
  50. package/dist/vitest/utils/esm.mjs +4 -4
  51. package/dist/wrappers/gemini.cjs +1 -1
  52. package/dist/wrappers/gemini.js +1 -1
  53. package/dist/wrappers/openai.cjs +2 -6
  54. package/dist/wrappers/openai.js +2 -6
  55. package/experimental/opencode.cjs +1 -0
  56. package/experimental/opencode.d.cts +1 -0
  57. package/experimental/opencode.d.ts +1 -0
  58. package/experimental/opencode.js +1 -0
  59. 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
+ }
@@ -6,6 +6,7 @@ export type SDKAssistantMessage = {
6
6
  content: Record<string, any>[];
7
7
  usage?: Record<string, any>;
8
8
  model?: string;
9
+ stop_reason?: string | null;
9
10
  };
10
11
  parent_tool_use_id: string | null;
11
12
  };
@@ -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 extractUsageFromMessage(message) {
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
- // Get cache tokens
62
- const cacheRead = (0, utils_js_1.getNumberProperty)(usage, "cache_read_input_tokens") || 0;
63
- const cacheCreation = (0, utils_js_1.getNumberProperty)(usage, "cache_creation_input_tokens") || 0;
64
- // Build input_token_details if we have cache tokens
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 + cacheRead + cacheCreation;
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
- export {};
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 extractUsageFromMessage(message) {
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
- // Get cache tokens
57
- const cacheRead = getNumberProperty(usage, "cache_read_input_tokens") || 0;
58
- const cacheCreation = getNumberProperty(usage, "cache_creation_input_tokens") || 0;
59
- // Build input_token_details if we have cache tokens
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 + cacheRead + cacheCreation;
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,3 @@
1
+ type Plugin = (ctx: any) => any;
2
+ export declare const LangSmithPlugin: Plugin;
3
+ export {};
@@ -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
+ };