@hue-run/sdk 0.21.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CLI.md +246 -52
  2. package/README.md +69 -14
  3. package/WORLDS.md +6 -3
  4. package/dist/ai-sdk.d.ts +23 -1
  5. package/dist/ai-sdk.js +60 -2
  6. package/dist/cli/import-formats/adapter.d.ts +80 -0
  7. package/dist/cli/import-formats/adapter.js +1 -0
  8. package/dist/cli/import-formats/agent-events.d.ts +5 -0
  9. package/dist/cli/import-formats/agent-events.js +16 -0
  10. package/dist/cli/import-formats/atif.d.ts +3 -0
  11. package/dist/cli/import-formats/atif.js +15 -0
  12. package/dist/cli/import-formats/braintrust.d.ts +2 -0
  13. package/dist/cli/import-formats/braintrust.js +222 -0
  14. package/dist/cli/import-formats/containers.d.ts +32 -0
  15. package/dist/cli/import-formats/containers.js +399 -0
  16. package/dist/cli/import-formats/datadog.d.ts +2 -0
  17. package/dist/cli/import-formats/datadog.js +264 -0
  18. package/dist/cli/import-formats/helicone.d.ts +2 -0
  19. package/dist/cli/import-formats/helicone.js +161 -0
  20. package/dist/cli/import-formats/identity.d.ts +112 -0
  21. package/dist/cli/import-formats/identity.js +594 -0
  22. package/dist/cli/import-formats/jaeger.d.ts +3 -0
  23. package/dist/cli/import-formats/jaeger.js +266 -0
  24. package/dist/cli/import-formats/langfuse.d.ts +7 -0
  25. package/dist/cli/import-formats/langfuse.js +510 -0
  26. package/dist/cli/import-formats/langsmith.d.ts +2 -0
  27. package/dist/cli/import-formats/langsmith.js +244 -0
  28. package/dist/cli/import-formats/messages.d.ts +28 -0
  29. package/dist/cli/import-formats/messages.js +301 -0
  30. package/dist/cli/import-formats/mlflow.d.ts +2 -0
  31. package/dist/cli/import-formats/mlflow.js +260 -0
  32. package/dist/cli/import-formats/openai-agents.d.ts +2 -0
  33. package/dist/cli/import-formats/openai-agents.js +221 -0
  34. package/dist/cli/import-formats/opik.d.ts +2 -0
  35. package/dist/cli/import-formats/opik.js +193 -0
  36. package/dist/cli/import-formats/otel-family.d.ts +5 -0
  37. package/dist/cli/import-formats/otel-family.js +17 -0
  38. package/dist/cli/import-formats/otel-spans.d.ts +94 -0
  39. package/dist/cli/import-formats/otel-spans.js +262 -0
  40. package/dist/cli/import-formats/otlp-protobuf.d.ts +35 -0
  41. package/dist/cli/import-formats/otlp-protobuf.js +486 -0
  42. package/dist/cli/import-formats/otlp.d.ts +4 -0
  43. package/dist/cli/import-formats/otlp.js +15 -0
  44. package/dist/cli/import-formats/phoenix.d.ts +3 -0
  45. package/dist/cli/import-formats/phoenix.js +256 -0
  46. package/dist/cli/import-formats/raindrop.d.ts +21 -0
  47. package/dist/cli/import-formats/raindrop.js +255 -0
  48. package/dist/cli/import-formats/reader.d.ts +56 -0
  49. package/dist/cli/import-formats/reader.js +364 -0
  50. package/dist/cli/import-formats/registry.d.ts +36 -0
  51. package/dist/cli/import-formats/registry.js +71 -0
  52. package/dist/cli/import-formats/row-platform-fields.d.ts +32 -0
  53. package/dist/cli/import-formats/row-platform-fields.js +76 -0
  54. package/dist/cli/import-formats/rows.d.ts +165 -0
  55. package/dist/cli/import-formats/rows.js +638 -0
  56. package/dist/cli/import-formats/transcripts.d.ts +25 -0
  57. package/dist/cli/import-formats/transcripts.js +498 -0
  58. package/dist/cli/import-formats/weave.d.ts +2 -0
  59. package/dist/cli/import-formats/weave.js +159 -0
  60. package/dist/cli/import-formats/zipkin.d.ts +2 -0
  61. package/dist/cli/import-formats/zipkin.js +219 -0
  62. package/dist/cli/import.d.ts +1 -1
  63. package/dist/cli/import.js +4 -2
  64. package/dist/cli/traces-import-convert.d.ts +65 -33
  65. package/dist/cli/traces-import-convert.js +211 -214
  66. package/dist/cli/traces-import-prepare.d.ts +26 -22
  67. package/dist/cli/traces-import-prepare.js +51 -122
  68. package/dist/cli/traces-import.d.ts +4 -4
  69. package/dist/cli/traces-import.js +63 -11
  70. package/dist/transport.d.ts +12 -0
  71. package/dist/transport.js +139 -24
  72. package/dist/version.d.ts +1 -1
  73. package/dist/version.js +1 -1
  74. package/package.json +2 -1
@@ -0,0 +1,244 @@
1
+ import { ConversionError, isObject } from "../traces-import-convert.js";
2
+ import { identityText } from "./identity.js";
3
+ import { count, first, isoNanos, metadataUser, object, objectField, SESSION_FIELDS, text, } from "./row-platform-fields.js";
4
+ import { readRecords } from "./rows.js";
5
+ const RUN_TYPES = new Set(["chain", "llm", "embedding", "prompt", "tool", "retriever", "parser"]);
6
+ const KINDS = {
7
+ llm: "llm",
8
+ tool: "tool",
9
+ embedding: "embedding",
10
+ retriever: "retrieval",
11
+ };
12
+ const isRun = (value) => isObject(value) &&
13
+ typeof value.id === "string" &&
14
+ typeof value.run_type === "string" &&
15
+ RUN_TYPES.has(value.run_type.toLowerCase()) &&
16
+ (typeof value.start_time === "string" || typeof value.dotted_order === "string");
17
+ /** The runs a query page (`runs` of v1, `items` of v2) holds, when it holds any. */
18
+ function envelopeRuns(value) {
19
+ if (!isObject(value))
20
+ return undefined;
21
+ for (const key of ["runs", "items"])
22
+ if (Array.isArray(value[key]) && value[key].some(isRun))
23
+ return value[key];
24
+ return undefined;
25
+ }
26
+ /** A run and every run nested in its `child_runs`, each kept without its children. */
27
+ function flatten(record, out) {
28
+ if (!isRun(record) || !Array.isArray(record.child_runs)) {
29
+ out.push(record);
30
+ return;
31
+ }
32
+ const { child_runs: children, ...run } = record;
33
+ out.push(run);
34
+ for (const child of children)
35
+ flatten(child, out);
36
+ }
37
+ /** `dotted_order`'s segments, root first: each `<start>Z<run id>`, the start as
38
+ * `YYYYMMDDTHHMMSSffffff`; undefined when it is not one. */
39
+ function dottedOrder(value) {
40
+ if (typeof value !== "string" || !value)
41
+ return undefined;
42
+ const segments = [];
43
+ for (const segment of value.split(".")) {
44
+ const match = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(\d{0,9})Z(.+)$/.exec(segment);
45
+ if (!match)
46
+ return undefined;
47
+ const [, year, month, day, hour, minute, second, fraction] = match;
48
+ try {
49
+ const start = isoNanos(`${year}-${month}-${day}T${hour}:${minute}:${second}${fraction ? `.${fraction}` : ""}Z`);
50
+ segments.push({ start, id: match[8] });
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ return segments;
57
+ }
58
+ /** The run's trace and parent: `trace_id`, `parent_run_id` (or the last of v2's
59
+ * `parent_run_ids`), else what `dotted_order` records; a run with no parent is its trace's root. */
60
+ function placement(run) {
61
+ const ancestors = Array.isArray(run.parent_run_ids) ? run.parent_run_ids : undefined;
62
+ const order = text(run.trace_id) === undefined || (run.parent_run_id === undefined && ancestors === undefined)
63
+ ? dottedOrder(run.dotted_order)
64
+ : undefined;
65
+ const parentKey = text(run.parent_run_id) ??
66
+ (ancestors ? text(ancestors.at(-1)) : undefined) ??
67
+ (order && order.length > 1 ? order.at(-2).id : undefined);
68
+ const traceKey = text(run.trace_id) ??
69
+ order?.[0]?.id ??
70
+ (parentKey === undefined ? run.id : undefined);
71
+ return {
72
+ ...(traceKey !== undefined ? { traceKey } : {}),
73
+ ...(parentKey !== undefined && parentKey !== run.id ? { parentKey } : {}),
74
+ };
75
+ }
76
+ /** A LangChain `ToolMessage` (serialized or as a dict) as its content and call id. */
77
+ function toolMessage(value) {
78
+ const message = object(value);
79
+ if (!message)
80
+ return undefined;
81
+ const constructor = message.lc === 1 && Array.isArray(message.id) && message.id.at(-1) === "ToolMessage"
82
+ ? object(message.kwargs)
83
+ : undefined;
84
+ const fields = constructor ?? (message.type === "tool" ? message : undefined);
85
+ if (!fields || !("content" in fields))
86
+ return undefined;
87
+ const callId = text(fields.tool_call_id);
88
+ return { content: fields.content, ...(callId ? { callId } : {}) };
89
+ }
90
+ /** A tool run's result: LangChain records it as `{"output": …}`, a `ToolMessage` when the tool
91
+ * answered one. */
92
+ function toolResult(outputs) {
93
+ if (outputs === undefined)
94
+ return {};
95
+ const keys = Object.keys(outputs);
96
+ if (keys.length !== 1 || keys[0] !== "output")
97
+ return { result: outputs };
98
+ const message = toolMessage(outputs.output);
99
+ return message
100
+ ? { result: message.content, callId: message.callId }
101
+ : { result: outputs.output };
102
+ }
103
+ /** The exception class LangChain's error text opens with (`repr(error)`, as in
104
+ * `ValueError('…')`), when it does. */
105
+ const errorType = (message) => message ? /^([A-Za-z_][\w.]*)\(/.exec(message)?.[1] : undefined;
106
+ /** A token count from a `*_token_details` map (v2 wraps it in `raw`) or LangChain's
107
+ * `usage_metadata` details. */
108
+ const detail = (details, name) => count(object(details)?.[name]) ?? count(object(object(details)?.raw)?.[name]);
109
+ /** Cache writes as LangSmith records them: `cache_creation`, the v2 API's `cache_write`, or, as
110
+ * LangSmith's own Anthropic wrapper records them, the sum of `ephemeral_5m_input_tokens` and
111
+ * `ephemeral_1h_input_tokens`. */
112
+ function cacheWrites(details) {
113
+ const recorded = detail(details, "cache_creation") ?? detail(details, "cache_write");
114
+ if (recorded !== undefined)
115
+ return recorded;
116
+ const short = detail(details, "ephemeral_5m_input_tokens");
117
+ const long = detail(details, "ephemeral_1h_input_tokens");
118
+ return short === undefined && long === undefined ? undefined : (short ?? 0) + (long ?? 0);
119
+ }
120
+ function readRun(run, context) {
121
+ const { traceKey, parentKey } = placement(run);
122
+ if (traceKey === undefined)
123
+ throw new ConversionError("a run without a trace_id");
124
+ const runType = run.run_type.toLowerCase();
125
+ const ancestors = run.parent_run_ids;
126
+ if (ancestors != null &&
127
+ (!Array.isArray(ancestors) || !ancestors.every((id) => typeof id === "string" && id)))
128
+ throw new ConversionError("parent_run_ids is not a list of run ids");
129
+ let start = run.start_time == null ? undefined : isoNanos(run.start_time);
130
+ const end = run.end_time == null ? undefined : isoNanos(run.end_time);
131
+ let timing;
132
+ if (start === undefined) {
133
+ const own = dottedOrder(run.dotted_order)?.at(-1);
134
+ if (own?.id === run.id) {
135
+ start = own.start;
136
+ timing = "start_from_dotted_order";
137
+ }
138
+ }
139
+ const extra = objectField(run.extra, "extra");
140
+ const metadata = object(run.metadata) ?? object(extra?.metadata);
141
+ const inputs = objectField(run.inputs, "inputs");
142
+ const outputs = objectField(run.outputs, "outputs");
143
+ const tags = Array.isArray(run.tags) && run.tags.every((tag) => typeof tag === "string")
144
+ ? run.tags
145
+ : undefined;
146
+ const kind = Object.hasOwn(KINDS, runType) ? KINDS[runType] : undefined;
147
+ // A completion model records its prompt as `{"prompts": [text]}`.
148
+ const prompts = inputs?.prompts;
149
+ const input = kind === "llm" &&
150
+ Array.isArray(prompts) &&
151
+ prompts.length === 1 &&
152
+ typeof prompts[0] === "string"
153
+ ? prompts[0]
154
+ : inputs;
155
+ const row = {
156
+ traceKey,
157
+ spanKey: run.id,
158
+ ...(parentKey !== undefined ? { parentKey } : {}),
159
+ name: text(run.name) ?? runType,
160
+ ...(kind ? { kind } : {}),
161
+ ...(start !== undefined ? { start } : {}),
162
+ ...(end !== undefined ? { end } : {}),
163
+ ...(timing ? { timing } : {}),
164
+ ...(input !== undefined ? { input } : {}),
165
+ ...(outputs !== undefined ? { output: outputs } : {}),
166
+ record: run,
167
+ ...(metadata ? { metadata } : {}),
168
+ revision: end !== undefined ? "1" : "0",
169
+ attributes: {
170
+ "langsmith.span.kind": runType,
171
+ ...(metadata && Object.keys(metadata).length
172
+ ? { "langsmith.metadata": JSON.stringify(metadata) }
173
+ : {}),
174
+ ...(tags?.length ? { "langsmith.span.tags": tags } : {}),
175
+ },
176
+ };
177
+ const error = text(run.error);
178
+ if (error !== undefined || text(run.status)?.toLowerCase() === "error") {
179
+ const type = errorType(error);
180
+ row.error = { ...(error !== undefined ? { message: error } : {}), ...(type ? { type } : {}) };
181
+ }
182
+ if (kind === "llm" || kind === "embedding") {
183
+ const params = object(extra?.invocation_params);
184
+ const model = text(metadata?.ls_model_name) ??
185
+ text(params?.model) ??
186
+ text(params?.model_name) ??
187
+ text(inputs?.model);
188
+ const provider = text(metadata?.ls_provider);
189
+ if (model)
190
+ row.model = model;
191
+ if (provider)
192
+ row.provider = provider;
193
+ }
194
+ if (kind === "llm") {
195
+ const usage = object(metadata?.usage_metadata) ?? object(outputs?.usage_metadata);
196
+ row.usage = {
197
+ input: count(run.prompt_tokens) ?? count(usage?.input_tokens),
198
+ output: count(run.completion_tokens) ?? count(usage?.output_tokens),
199
+ cacheRead: detail(run.prompt_token_details, "cache_read") ??
200
+ detail(usage?.input_token_details, "cache_read"),
201
+ cacheWrite: cacheWrites(run.prompt_token_details) ?? cacheWrites(usage?.input_token_details),
202
+ reasoning: detail(run.completion_token_details, "reasoning") ??
203
+ detail(usage?.output_token_details, "reasoning"),
204
+ };
205
+ }
206
+ if (kind === "tool") {
207
+ const returned = toolResult(outputs);
208
+ row.tool = {
209
+ name: text(run.name) ?? "tool",
210
+ ...(inputs !== undefined ? { arguments: inputs } : {}),
211
+ ...(returned.result !== undefined ? { result: returned.result } : {}),
212
+ };
213
+ // LangChain passes the call id a tool answers to the tracer, which keeps it in `extra`.
214
+ const callId = text(extra?.tool_call_id) ?? returned.callId;
215
+ if (callId)
216
+ row.tool.callId = callId;
217
+ }
218
+ else {
219
+ const user = metadataUser(metadata);
220
+ const session = first(metadata, SESSION_FIELDS) ?? identityText(run.thread_id);
221
+ if (user !== undefined)
222
+ row.user = user;
223
+ if (session !== undefined)
224
+ row.session = session;
225
+ }
226
+ if (text(run.reference_example_id))
227
+ row.origin = "evaluation";
228
+ context.row(row);
229
+ }
230
+ export const langsmithRuns = {
231
+ id: "langsmith_runs",
232
+ manifestFormat: "otlp_json",
233
+ detect: (value) => isRun(value) || envelopeRuns(value) !== undefined,
234
+ convert(value, context) {
235
+ const records = [];
236
+ for (const record of isRun(value) ? [value] : (envelopeRuns(value) ?? []))
237
+ flatten(record, records);
238
+ readRecords(context, records, (record) => (isRun(record) ? placement(record).traceKey : undefined), (record) => {
239
+ if (!isRun(record))
240
+ throw new ConversionError("a record that is not a LangSmith run");
241
+ readRun(record, context);
242
+ });
243
+ },
244
+ };
@@ -0,0 +1,28 @@
1
+ import { type JsonValue } from "../traces-import-convert.js";
2
+ /**
3
+ * Chat content as the OpenTelemetry GenAI conventions record it (`gen_ai.input.messages`,
4
+ * `gen_ai.output.messages`): a list of `{role, parts}` messages whose parts are `text`,
5
+ * `tool_call`, `tool_call_response`, `reasoning`, `blob` and `uri`, read from the shapes exports
6
+ * commonly hold: OpenAI chat messages and choices, OpenAI Responses items, Anthropic content
7
+ * blocks, LangChain messages and generations, Vercel AI SDK messages and plain strings. A part of
8
+ * any other type is kept whole under its own `type`; content of no recognized shape converts to
9
+ * nothing, and the emitter keeps it raw instead.
10
+ */
11
+ type Part = {
12
+ type: string;
13
+ } & {
14
+ [key: string]: JsonValue;
15
+ };
16
+ export interface GenAiMessage {
17
+ role: string;
18
+ parts: Part[];
19
+ name?: string;
20
+ finish_reason?: string;
21
+ }
22
+ /**
23
+ * `value` as GenAI messages, or undefined when it holds no recognized chat content. A plain
24
+ * string is one message from `stringRole` when one is given; `{messages: […]}` and
25
+ * `{choices: […]}` wrappers, LangChain's batched lists and `LLMResult` generations are unwrapped.
26
+ */
27
+ export declare function genAiMessages(value: JsonValue | undefined, stringRole?: "user" | "assistant"): GenAiMessage[] | undefined;
28
+ export {};
@@ -0,0 +1,301 @@
1
+ import { isObject, parseNestedJsonText } from "../traces-import-convert.js";
2
+ const object = (value) => isObject(value) ? value : undefined;
3
+ const string = (value) => typeof value === "string" ? value : undefined;
4
+ /** Tool call arguments as recorded: an OpenAI arguments string is read as the JSON it holds. */
5
+ function argumentsOf(value) {
6
+ if (typeof value === "string")
7
+ try {
8
+ return parseNestedJsonText(value).value;
9
+ }
10
+ catch {
11
+ return value;
12
+ }
13
+ return value ?? null;
14
+ }
15
+ const DATA_URL = /^data:([\w.+-]+\/[\w.+-]+)?((?:;[\w.+-]+=[^;,]*)*);base64,(.*)$/s;
16
+ /** An image, audio or file reference: a base64 data URL becomes a blob part, a URL a uri part. */
17
+ function mediaPart(url, modality) {
18
+ const data = DATA_URL.exec(url);
19
+ if (data)
20
+ return {
21
+ type: "blob",
22
+ modality: data[1]?.split("/")[0] ?? modality,
23
+ ...(data[1] ? { mime_type: data[1] } : {}),
24
+ content: data[3],
25
+ };
26
+ return { type: "uri", modality, uri: url };
27
+ }
28
+ /** One content part of any recognized shape, or undefined when it is not one. */
29
+ function contentPart(raw) {
30
+ if (typeof raw === "string")
31
+ return { type: "text", content: raw };
32
+ const part = object(raw);
33
+ const type = string(part?.type);
34
+ if (!part || !type)
35
+ return undefined;
36
+ switch (type) {
37
+ case "text":
38
+ case "input_text":
39
+ case "output_text": {
40
+ const content = string(part.text) ?? string(part.content);
41
+ return content === undefined ? { ...part, type } : { type: "text", content };
42
+ }
43
+ case "image_url": {
44
+ const url = string(object(part.image_url)?.url) ?? string(part.image_url);
45
+ return url === undefined ? { ...part, type } : mediaPart(url, "image");
46
+ }
47
+ case "image": {
48
+ const source = object(part.source);
49
+ if (source?.type === "base64" && typeof source.data === "string")
50
+ return {
51
+ type: "blob",
52
+ modality: "image",
53
+ ...(typeof source.media_type === "string" ? { mime_type: source.media_type } : {}),
54
+ content: source.data,
55
+ };
56
+ if (source?.type === "url" && typeof source.url === "string")
57
+ return { type: "uri", modality: "image", uri: source.url };
58
+ return { ...part, type };
59
+ }
60
+ case "tool_use":
61
+ return {
62
+ type: "tool_call",
63
+ ...(typeof part.id === "string" ? { id: part.id } : {}),
64
+ name: string(part.name) ?? "",
65
+ arguments: part.input ?? null,
66
+ };
67
+ case "tool-call":
68
+ return {
69
+ type: "tool_call",
70
+ ...(typeof part.toolCallId === "string" ? { id: part.toolCallId } : {}),
71
+ name: string(part.toolName) ?? "",
72
+ arguments: argumentsOf(part.input ?? part.args),
73
+ };
74
+ case "tool_result":
75
+ return {
76
+ type: "tool_call_response",
77
+ ...(typeof part.tool_use_id === "string" ? { id: part.tool_use_id } : {}),
78
+ response: part.content ?? null,
79
+ };
80
+ case "tool-result":
81
+ return {
82
+ type: "tool_call_response",
83
+ ...(typeof part.toolCallId === "string" ? { id: part.toolCallId } : {}),
84
+ response: part.output ?? part.result ?? null,
85
+ };
86
+ case "thinking":
87
+ return { type: "reasoning", content: string(part.thinking) ?? "" };
88
+ case "reasoning": {
89
+ const content = string(part.text) ?? string(part.content);
90
+ return content === undefined ? { ...part, type } : { type: "reasoning", content };
91
+ }
92
+ default:
93
+ return { ...part, type };
94
+ }
95
+ }
96
+ /** A message's content: a string, a list of parts or nothing; undefined when not readable. */
97
+ function contentParts(content) {
98
+ if (content === undefined || content === null)
99
+ return [];
100
+ if (typeof content === "string")
101
+ return content ? [{ type: "text", content }] : [];
102
+ if (!Array.isArray(content))
103
+ return undefined;
104
+ const parts = [];
105
+ for (const item of content) {
106
+ const part = contentPart(item);
107
+ if (!part)
108
+ return undefined;
109
+ parts.push(part);
110
+ }
111
+ return parts;
112
+ }
113
+ /** OpenAI-shaped `tool_calls` (`{id, function: {name, arguments}}`) and LangChain's
114
+ * (`{id, name, args}`) as tool call parts. */
115
+ function toolCallParts(calls) {
116
+ if (calls === undefined || calls === null)
117
+ return [];
118
+ if (!Array.isArray(calls))
119
+ return undefined;
120
+ const parts = [];
121
+ for (const raw of calls) {
122
+ const call = object(raw);
123
+ if (!call)
124
+ return undefined;
125
+ const fn = object(call.function);
126
+ parts.push({
127
+ type: "tool_call",
128
+ ...(typeof call.id === "string" ? { id: call.id } : {}),
129
+ name: string(fn?.name) ?? string(call.name) ?? "",
130
+ arguments: fn ? argumentsOf(fn.arguments) : (call.args ?? null),
131
+ });
132
+ }
133
+ return parts;
134
+ }
135
+ const LANGCHAIN_ROLES = {
136
+ human: "user",
137
+ user: "user",
138
+ ai: "assistant",
139
+ assistant: "assistant",
140
+ system: "system",
141
+ tool: "tool",
142
+ function: "tool",
143
+ };
144
+ const LANGCHAIN_CLASSES = {
145
+ HumanMessage: "human",
146
+ HumanMessageChunk: "human",
147
+ AIMessage: "ai",
148
+ AIMessageChunk: "ai",
149
+ SystemMessage: "system",
150
+ ToolMessage: "tool",
151
+ FunctionMessage: "function",
152
+ ChatMessage: "chat",
153
+ };
154
+ /** A LangChain message (`{type, content}`, `{type, data}` or the serialized constructor form)
155
+ * as role and fields, or undefined when it is not one. */
156
+ function langchain(message) {
157
+ if (message.lc === 1 && Array.isArray(message.id) && object(message.kwargs)) {
158
+ const kind = LANGCHAIN_CLASSES[string(message.id.at(-1)) ?? ""];
159
+ const fields = object(message.kwargs);
160
+ if (!kind)
161
+ return undefined;
162
+ const role = kind === "chat" ? string(fields.role) : LANGCHAIN_ROLES[kind];
163
+ return role ? { role, fields } : undefined;
164
+ }
165
+ const type = string(message.type);
166
+ if (!type || message.role !== undefined)
167
+ return undefined;
168
+ const fields = object(message.data) ?? message;
169
+ if (!("content" in fields))
170
+ return undefined;
171
+ const role = type === "chat" ? string(fields.role) : LANGCHAIN_ROLES[type];
172
+ return role ? { role, fields } : undefined;
173
+ }
174
+ /** A tool's answer as one `tool_call_response` part. */
175
+ const responsePart = (id, response) => ({
176
+ type: "tool_call_response",
177
+ ...(typeof id === "string" ? { id } : {}),
178
+ response: response ?? null,
179
+ });
180
+ /** One message of any recognized shape, or undefined. */
181
+ function message(raw) {
182
+ const item = object(raw);
183
+ if (!item)
184
+ return undefined;
185
+ // An OpenAI choice: the message and why the model stopped.
186
+ if (object(item.message) && ("finish_reason" in item || "index" in item)) {
187
+ const inner = message(item.message);
188
+ const finish = string(item.finish_reason);
189
+ return inner && finish ? { ...inner, finish_reason: finish } : inner;
190
+ }
191
+ // OpenAI Responses items.
192
+ if (item.type === "function_call")
193
+ return {
194
+ role: "assistant",
195
+ parts: [
196
+ {
197
+ type: "tool_call",
198
+ ...(typeof item.call_id === "string" ? { id: item.call_id } : {}),
199
+ name: string(item.name) ?? "",
200
+ arguments: argumentsOf(item.arguments),
201
+ },
202
+ ],
203
+ };
204
+ if (item.type === "function_call_output")
205
+ return { role: "tool", parts: [responsePart(item.call_id, item.output)] };
206
+ if (item.type === "reasoning" && Array.isArray(item.summary)) {
207
+ const text = item.summary.map((entry) => string(object(entry)?.text) ?? "").join("\n");
208
+ return { role: "assistant", parts: [{ type: "reasoning", content: text }] };
209
+ }
210
+ const chain = langchain(item);
211
+ if (chain) {
212
+ const { role, fields } = chain;
213
+ if (role === "tool")
214
+ return { role, parts: [responsePart(fields.tool_call_id, fields.content)] };
215
+ const parts = contentParts(fields.content);
216
+ const calls = toolCallParts(fields.tool_calls);
217
+ if (!parts || !calls)
218
+ return undefined;
219
+ return { role, parts: [...parts, ...calls], ...nameOf(fields) };
220
+ }
221
+ const role = string(item.role);
222
+ if (!role)
223
+ return undefined;
224
+ // Already GenAI parts.
225
+ if (Array.isArray(item.parts) &&
226
+ item.parts.every((part) => typeof object(part)?.type === "string"))
227
+ return { ...item };
228
+ if (role === "tool" || role === "function")
229
+ return {
230
+ role: "tool",
231
+ parts: [responsePart(item.tool_call_id, item.content)],
232
+ ...nameOf(item),
233
+ };
234
+ const parts = contentParts(item.content);
235
+ const calls = toolCallParts(item.tool_calls);
236
+ if (!parts || !calls)
237
+ return undefined;
238
+ const legacy = object(item.function_call);
239
+ if (legacy)
240
+ calls.push({
241
+ type: "tool_call",
242
+ name: string(legacy.name) ?? "",
243
+ arguments: argumentsOf(legacy.arguments),
244
+ });
245
+ const stop = string(item.stop_reason);
246
+ return {
247
+ role,
248
+ parts: [...parts, ...calls],
249
+ ...nameOf(item),
250
+ ...(stop ? { finish_reason: stop } : {}),
251
+ };
252
+ }
253
+ const nameOf = (fields) => typeof fields.name === "string" && fields.name ? { name: fields.name } : {};
254
+ /**
255
+ * `value` as GenAI messages, or undefined when it holds no recognized chat content. A plain
256
+ * string is one message from `stringRole` when one is given; `{messages: […]}` and
257
+ * `{choices: […]}` wrappers, LangChain's batched lists and `LLMResult` generations are unwrapped.
258
+ */
259
+ export function genAiMessages(value, stringRole) {
260
+ if (value === undefined || value === null)
261
+ return undefined;
262
+ if (typeof value === "string")
263
+ return stringRole && value
264
+ ? [{ role: stringRole, parts: [{ type: "text", content: value }] }]
265
+ : undefined;
266
+ const wrapper = object(value);
267
+ if (wrapper) {
268
+ for (const key of ["messages", "choices"])
269
+ if (Array.isArray(wrapper[key]))
270
+ return genAiMessages(wrapper[key], stringRole);
271
+ if (Array.isArray(wrapper.generations)) {
272
+ const generations = wrapper.generations.flatMap((batch) => Array.isArray(batch) ? batch : [batch]);
273
+ const messages = generations.map((generation) => {
274
+ const entry = object(generation);
275
+ const inner = object(entry?.message);
276
+ if (inner)
277
+ return message(inner);
278
+ const text = string(entry?.text);
279
+ return text === undefined
280
+ ? undefined
281
+ : { role: "assistant", parts: [{ type: "text", content: text }] };
282
+ });
283
+ return messages.length && messages.every(Boolean) ? messages : undefined;
284
+ }
285
+ const single = message(wrapper);
286
+ return single ? [single] : undefined;
287
+ }
288
+ if (!Array.isArray(value) || !value.length)
289
+ return undefined;
290
+ // LangChain records a batch of prompts as a list of message lists.
291
+ if (value.length === 1 && Array.isArray(value[0]))
292
+ return genAiMessages(value[0], stringRole);
293
+ const messages = [];
294
+ for (const raw of value) {
295
+ const converted = message(raw);
296
+ if (!converted)
297
+ return undefined;
298
+ messages.push(converted);
299
+ }
300
+ return messages;
301
+ }
@@ -0,0 +1,2 @@
1
+ import type { ImportAdapter } from "./adapter.js";
2
+ export declare const mlflowTraces: ImportAdapter;