@hue-run/sdk 0.21.0 → 0.22.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 +255 -61
  2. package/README.md +61 -20
  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/mcp.d.ts +5 -1
  65. package/dist/cli/mcp.js +35 -23
  66. package/dist/cli/traces-import-convert.d.ts +65 -33
  67. package/dist/cli/traces-import-convert.js +211 -214
  68. package/dist/cli/traces-import-prepare.d.ts +26 -22
  69. package/dist/cli/traces-import-prepare.js +51 -122
  70. package/dist/cli/traces-import.d.ts +4 -4
  71. package/dist/cli/traces-import.js +63 -11
  72. package/dist/version.d.ts +1 -1
  73. package/dist/version.js +1 -1
  74. package/package.json +3 -3
@@ -0,0 +1,498 @@
1
+ import { canonicalJson, ConversionError, isObject, sha256Hex, } from "../traces-import-convert.js";
2
+ import { identityText } from "./identity.js";
3
+ import { genAiMessages } from "./messages.js";
4
+ import { nanosFrom, readRecords } from "./rows.js";
5
+ const object = (value) => isObject(value) ? value : undefined;
6
+ const text = (value) => typeof value === "string" && value ? value : undefined;
7
+ const count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
8
+ /** Keys of another format's record: a value holding one is never a transcript. */
9
+ const FOREIGN_KEYS = [
10
+ "span_id",
11
+ "root_span_id",
12
+ "trace_id",
13
+ "traceId",
14
+ "resourceSpans",
15
+ "resource_spans",
16
+ "schema_version",
17
+ "steps",
18
+ "observations",
19
+ ];
20
+ const foreign = (value) => FOREIGN_KEYS.some((key) => key in value);
21
+ // --- Message lists ---------------------------------------------------------------------------
22
+ /** A Responses API item type: messages, tool calls and their outputs, reasoning and the rest. */
23
+ const RESPONSES_ITEM = /^(?:message|reasoning|compaction|compaction_trigger|item_reference|configuration_update|additional_tools|program|program_output|tool_search_output|mcp_list_tools|mcp_approval_request|mcp_approval_response|[a-z_]+_call(?:_output)?)$/;
24
+ /** A typed Responses item (never an Anthropic response, which names its model). */
25
+ function isResponsesItem(value) {
26
+ const item = object(value);
27
+ if (!item || typeof item.type !== "string" || !RESPONSES_ITEM.test(item.type))
28
+ return false;
29
+ if ("model" in item || foreign(item))
30
+ return false;
31
+ return item.type !== "message" || typeof item.role === "string";
32
+ }
33
+ /** An untyped message (`{role, content}`), which the Responses API also takes as input. */
34
+ const isEasyMessage = (value) => {
35
+ const item = object(value);
36
+ return (!!item &&
37
+ item.type === undefined &&
38
+ typeof item.role === "string" &&
39
+ (typeof item.content === "string" || Array.isArray(item.content)));
40
+ };
41
+ /** A list of Responses items: every element an item or a message, at least one typed. */
42
+ const isItemList = (value) => Array.isArray(value) &&
43
+ value.length > 0 &&
44
+ value.every((item) => isResponsesItem(item) || isEasyMessage(item)) &&
45
+ value.some(isResponsesItem);
46
+ /**
47
+ * A message list's items as the message shapes `genAiMessages` reads: a Responses item it reads
48
+ * no message from (a hosted tool call, encrypted reasoning) becomes a message holding that item as
49
+ * a part of its own type, from the tool for an `…_output` item and from the assistant otherwise.
50
+ */
51
+ export function chatItems(items) {
52
+ return items.map((raw) => {
53
+ if (genAiMessages([raw]))
54
+ return raw;
55
+ const item = object(raw);
56
+ const type = text(item?.type);
57
+ if (!item || !type || item.role !== undefined)
58
+ return raw;
59
+ return { role: type.endsWith("_output") ? "tool" : "assistant", parts: [item] };
60
+ });
61
+ }
62
+ /** Each item read as one GenAI message, or a ConversionError naming the first that is not. */
63
+ function readMessages(items, what) {
64
+ return items.map((item, index) => {
65
+ const message = genAiMessages([item])?.[0];
66
+ if (!message)
67
+ throw new ConversionError(`${what} ${index + 1} is not a chat message`);
68
+ return message;
69
+ });
70
+ }
71
+ /** System instructions as GenAI parts: text as it is, Anthropic text blocks as text parts. */
72
+ function systemParts(value) {
73
+ if (value === undefined || value === null || value === "")
74
+ return undefined;
75
+ if (typeof value === "string")
76
+ return value;
77
+ return genAiMessages([{ role: "system", content: value }])?.[0]?.parts ?? value;
78
+ }
79
+ // --- Model API bodies (also read by the Helicone format) -------------------------------------
80
+ /** The conversation a model request sends, as message shapes: chat `messages` (OpenAI, Anthropic)
81
+ * or Responses `input`; anything else as recorded. */
82
+ export function requestContent(body) {
83
+ const fields = object(body);
84
+ if (!fields)
85
+ return body;
86
+ if (Array.isArray(fields.messages))
87
+ return chatItems(fields.messages);
88
+ if (typeof fields.input === "string")
89
+ return [{ role: "user", content: fields.input }];
90
+ if (isItemList(fields.input))
91
+ return chatItems(fields.input);
92
+ return body;
93
+ }
94
+ /** A request's system instructions: Anthropic `system` or Responses `instructions`. */
95
+ export const requestInstructions = (body) => systemParts(object(body)?.system ?? object(body)?.instructions);
96
+ /** A model response's content as message shapes: Responses `output` items; a chat completion or
97
+ * Anthropic message as recorded, which `genAiMessages` reads. */
98
+ export function responseContent(body) {
99
+ const fields = object(body);
100
+ return fields && Array.isArray(fields.output) ? chatItems(fields.output) : body;
101
+ }
102
+ /** Token usage of an OpenAI (chat or Responses) or Anthropic response. Anthropic's input count
103
+ * leaves out cached tokens, so they are added back as the GenAI conventions count input. */
104
+ export function responseUsage(usage) {
105
+ const fields = object(usage);
106
+ if (!fields)
107
+ return undefined;
108
+ const input = object(fields.input_tokens_details) ?? object(fields.prompt_tokens_details);
109
+ const output = object(fields.output_tokens_details) ?? object(fields.completion_tokens_details);
110
+ const cacheRead = count(fields.cache_read_input_tokens) ?? count(input?.cached_tokens);
111
+ const cacheWrite = count(fields.cache_creation_input_tokens);
112
+ const anthropic = "cache_read_input_tokens" in fields || "cache_creation_input_tokens" in fields;
113
+ const inputTokens = count(fields.input_tokens) ?? count(fields.prompt_tokens);
114
+ return {
115
+ input: anthropic && inputTokens !== undefined
116
+ ? inputTokens + (cacheRead ?? 0) + (cacheWrite ?? 0)
117
+ : inputTokens,
118
+ output: count(fields.output_tokens) ?? count(fields.completion_tokens),
119
+ cacheRead,
120
+ cacheWrite,
121
+ reasoning: count(output?.reasoning_tokens),
122
+ };
123
+ }
124
+ /** What a transcript's turns may repeat together, as much as one file may decompress to: each
125
+ * turn's span holds the conversation before it, so a long transcript repeats its early messages
126
+ * once per turn. */
127
+ const MAX_REPEATED_BYTES = 64 * 1024 * 1024;
128
+ /** The trace's rows: the agent, a chat span per turn and a tool span per call. */
129
+ function transcriptRows(traceKey, transcript) {
130
+ // Spans carry the items in shapes the emitter reads; records keep them as the source wrote them.
131
+ const raw = transcript.history;
132
+ const items = chatItems(raw);
133
+ const history = readMessages(items, "message");
134
+ const answerItems = transcript.answer ? chatItems(transcript.answer.items) : undefined;
135
+ const answer = answerItems ? readMessages(answerItems, "output item") : undefined;
136
+ const turns = [];
137
+ for (let index = 0; index < history.length; index++) {
138
+ if (history[index].role !== "assistant")
139
+ continue;
140
+ let end = index;
141
+ while (end < history.length && history[end].role === "assistant")
142
+ end++;
143
+ turns.push({ at: index, end, messages: history.slice(index, end) });
144
+ index = end - 1;
145
+ }
146
+ // The conversation each turn repeats, measured before any of it is copied.
147
+ const before = [0];
148
+ for (const item of items)
149
+ before.push(before.at(-1) + Buffer.byteLength(JSON.stringify(item)));
150
+ let repeated = answer ? before[items.length] : 0;
151
+ for (const turn of turns)
152
+ repeated += before[turn.at];
153
+ if (repeated > MAX_REPEATED_BYTES)
154
+ throw new ConversionError(`a transcript whose turns together repeat more than ${MAX_REPEATED_BYTES} bytes of conversation`);
155
+ // A tool call's result, by call id, with the message that holds it.
156
+ const results = new Map();
157
+ for (const [index, message] of history.entries())
158
+ for (const part of message.parts)
159
+ if (part.type === "tool_call_response" &&
160
+ typeof part.id === "string" &&
161
+ !results.has(part.id))
162
+ results.set(part.id, { response: part.response ?? null, item: raw[index] });
163
+ const model = transcript.model ?? transcript.answer?.model;
164
+ const system = transcript.system;
165
+ const firstTurn = turns[0]?.at ?? history.length;
166
+ const last = turns.at(-1);
167
+ const lastOutput = answerItems ?? (last ? items.slice(last.at, last.end) : undefined);
168
+ const rows = [
169
+ {
170
+ traceKey,
171
+ spanKey: "agent",
172
+ name: "invoke_agent",
173
+ kind: "agent",
174
+ ...(firstTurn > 0 ? { input: items.slice(0, firstTurn) } : {}),
175
+ ...(lastOutput?.length ? { output: lastOutput } : {}),
176
+ ...(system !== undefined ? { systemInstructions: system } : {}),
177
+ ...(transcript.user !== undefined ? { user: transcript.user } : {}),
178
+ ...(transcript.session !== undefined ? { session: transcript.session } : {}),
179
+ ...(transcript.tools !== undefined
180
+ ? { attributes: { "gen_ai.tool.definitions": JSON.stringify(transcript.tools) } }
181
+ : {}),
182
+ record: transcript.record,
183
+ sequence: 0,
184
+ },
185
+ ];
186
+ let sequence = 1;
187
+ const addTurn = (number, input, output, fields) => {
188
+ rows.push({
189
+ traceKey,
190
+ spanKey: `turn-${number}`,
191
+ parentKey: "agent",
192
+ name: model ? `chat ${model}` : "chat",
193
+ kind: "llm",
194
+ ...(model ? { model } : {}),
195
+ ...(input.length ? { input } : {}),
196
+ output: output.items,
197
+ ...(system !== undefined ? { systemInstructions: system } : {}),
198
+ record: output.record,
199
+ sequence: sequence++,
200
+ ...fields,
201
+ });
202
+ let call = 0;
203
+ for (const [index, message] of output.messages.entries())
204
+ for (const part of message.parts) {
205
+ if (part.type !== "tool_call")
206
+ continue;
207
+ const id = typeof part.id === "string" ? part.id : undefined;
208
+ const name = typeof part.name === "string" && part.name ? part.name : "tool";
209
+ const result = id === undefined ? undefined : results.get(id);
210
+ rows.push({
211
+ traceKey,
212
+ spanKey: `turn-${number}.tool-${++call}`,
213
+ parentKey: "agent",
214
+ name: `execute_tool ${name}`,
215
+ kind: "tool",
216
+ tool: {
217
+ name,
218
+ ...(id !== undefined ? { callId: id } : {}),
219
+ arguments: part.arguments ?? null,
220
+ ...(result ? { result: result.response } : {}),
221
+ },
222
+ ...(id !== undefined && transcript.failed?.has(id) ? { error: {} } : {}),
223
+ record: result?.item ?? output.raw[index],
224
+ sequence: sequence++,
225
+ });
226
+ }
227
+ };
228
+ for (const [index, turn] of turns.entries()) {
229
+ const own = raw.slice(turn.at, turn.end);
230
+ addTurn(index + 1, items.slice(0, turn.at), {
231
+ items: items.slice(turn.at, turn.end),
232
+ raw: own,
233
+ messages: turn.messages,
234
+ record: own.length === 1 ? own[0] : own,
235
+ }, {});
236
+ }
237
+ if (transcript.answer && answerItems && answer) {
238
+ const { usage, start, error } = transcript.answer;
239
+ addTurn(turns.length + 1, items, {
240
+ items: answerItems,
241
+ raw: transcript.answer.items,
242
+ messages: answer,
243
+ record: transcript.answer.record,
244
+ }, {
245
+ ...(transcript.answer.model ? { responseModel: transcript.answer.model } : {}),
246
+ ...(usage ? { usage } : {}),
247
+ ...(start !== undefined ? { start } : {}),
248
+ ...(error ? { error } : {}),
249
+ });
250
+ }
251
+ return rows;
252
+ }
253
+ /** Converts one transcript; a malformed one refuses only its own trace. `id` reads the source's
254
+ * own id; without one the trace id derives from the content. */
255
+ function convertTranscript(value, context, read, id) {
256
+ // Parsed JSON, an object or a list as `detect` claimed it.
257
+ const json = value;
258
+ const traceKey = (!Array.isArray(json) && id?.(json)) || sha256Hex(canonicalJson(json));
259
+ readRecords(context, [json], () => traceKey, () => {
260
+ for (const row of transcriptRows(traceKey, read(json)))
261
+ context.row(row);
262
+ });
263
+ }
264
+ // --- OpenAI Chat Completions -----------------------------------------------------------------
265
+ /** A request's end user: OpenAI's `safety_identifier` or the older `user` field. */
266
+ const openAiUser = (fields) => identityText(fields.safety_identifier) ?? identityText(fields.user);
267
+ /** Anthropic content block types OpenAI chat never sends. */
268
+ const ANTHROPIC_BLOCKS = new Set([
269
+ "tool_use",
270
+ "tool_result",
271
+ "thinking",
272
+ "redacted_thinking",
273
+ "document",
274
+ "search_result",
275
+ "server_tool_use",
276
+ "web_search_tool_result",
277
+ "web_fetch_tool_result",
278
+ "code_execution_tool_result",
279
+ "mcp_tool_use",
280
+ "mcp_tool_result",
281
+ "container_upload",
282
+ ]);
283
+ const OPENAI_ROLES = new Set(["system", "developer", "tool", "function"]);
284
+ /** A request with a message list: `messages` holding at least one `{role}` message. */
285
+ const isChatRequest = (value) => {
286
+ const fields = object(value);
287
+ return (!!fields &&
288
+ !foreign(fields) &&
289
+ !("object" in fields) &&
290
+ Array.isArray(fields.messages) &&
291
+ fields.messages.some((message) => typeof object(message)?.role === "string"));
292
+ };
293
+ /** Whether a chat request carries something only the Anthropic Messages API sends, and nothing
294
+ * only OpenAI's does. */
295
+ function anthropicRequest(fields) {
296
+ const messages = fields.messages.map(object);
297
+ const openAi = messages.some((message) => OPENAI_ROLES.has(text(message?.role) ?? "") ||
298
+ message?.tool_calls !== undefined ||
299
+ message?.tool_call_id !== undefined ||
300
+ message?.function_call !== undefined) ||
301
+ (Array.isArray(fields.tools) && fields.tools.some((tool) => object(tool)?.type === "function"));
302
+ if (openAi)
303
+ return false;
304
+ return ("system" in fields ||
305
+ "anthropic_version" in fields ||
306
+ "stop_sequences" in fields ||
307
+ (Array.isArray(fields.tools) &&
308
+ fields.tools.some((tool) => "input_schema" in (object(tool) ?? {}))) ||
309
+ messages.some((message) => Array.isArray(message?.content) &&
310
+ message.content.some((block) => ANTHROPIC_BLOCKS.has(text(object(block)?.type) ?? ""))));
311
+ }
312
+ export const openaiChat = {
313
+ id: "openai_chat",
314
+ manifestFormat: "otlp_json",
315
+ detect: (value) => isChatRequest(value) && !anthropicRequest(value),
316
+ convert(value, context) {
317
+ convertTranscript(value, context, (fields) => {
318
+ const request = fields;
319
+ const user = openAiUser(request);
320
+ return {
321
+ record: request,
322
+ history: request.messages,
323
+ ...(text(request.model) ? { model: text(request.model) } : {}),
324
+ ...(request.tools !== undefined ? { tools: request.tools } : {}),
325
+ ...(user !== undefined ? { user } : {}),
326
+ };
327
+ });
328
+ },
329
+ };
330
+ // --- Anthropic Messages ----------------------------------------------------------------------
331
+ const isAnthropicResponse = (value) => {
332
+ const fields = object(value);
333
+ return (!!fields &&
334
+ fields.type === "message" &&
335
+ fields.role === "assistant" &&
336
+ Array.isArray(fields.content) &&
337
+ typeof fields.model === "string");
338
+ };
339
+ /** Tool use ids whose result block says `is_error`. */
340
+ function failedToolUses(messages) {
341
+ const failed = new Set();
342
+ for (const message of messages) {
343
+ const content = object(message)?.content;
344
+ if (!Array.isArray(content))
345
+ continue;
346
+ for (const block of content.map(object))
347
+ if (block?.type === "tool_result" && block.is_error === true && text(block.tool_use_id))
348
+ failed.add(text(block.tool_use_id));
349
+ }
350
+ return failed;
351
+ }
352
+ export const anthropicMessages = {
353
+ id: "anthropic_messages",
354
+ manifestFormat: "otlp_json",
355
+ detect: (value) => isAnthropicResponse(value) || (isChatRequest(value) && anthropicRequest(value)),
356
+ convert(value, context) {
357
+ convertTranscript(value, context, (fields) => {
358
+ const message = fields;
359
+ if (isAnthropicResponse(message))
360
+ return {
361
+ record: message,
362
+ history: [],
363
+ answer: {
364
+ items: [message],
365
+ record: message,
366
+ model: text(message.model),
367
+ usage: responseUsage(message.usage),
368
+ },
369
+ };
370
+ const messages = message.messages;
371
+ const user = identityText(object(message.metadata)?.user_id);
372
+ const system = systemParts(message.system);
373
+ return {
374
+ record: message,
375
+ history: messages,
376
+ ...(text(message.model) ? { model: text(message.model) } : {}),
377
+ ...(system !== undefined ? { system } : {}),
378
+ ...(message.tools !== undefined ? { tools: message.tools } : {}),
379
+ ...(user !== undefined ? { user } : {}),
380
+ failed: failedToolUses(messages),
381
+ };
382
+ }, (fields) => (isAnthropicResponse(fields) ? text(fields.id) : undefined));
383
+ },
384
+ };
385
+ // --- OpenAI Responses ------------------------------------------------------------------------
386
+ const isResponseObject = (value) => {
387
+ const fields = object(value);
388
+ return !!fields && fields.object === "response" && Array.isArray(fields.output);
389
+ };
390
+ /** The fields of a Responses API request, and the answer (`output`, `usage`) logged beside it.
391
+ * `input` and `output` are common names, so a value with any other field is left to the format
392
+ * that owns it. */
393
+ const RESPONSES_REQUEST_FIELDS = new Set([
394
+ "access_programs",
395
+ "background",
396
+ "context_management",
397
+ "conversation",
398
+ "include",
399
+ "input",
400
+ "instructions",
401
+ "max_output_tokens",
402
+ "max_tool_calls",
403
+ "metadata",
404
+ "model",
405
+ "moderation",
406
+ "parallel_tool_calls",
407
+ "previous_response_id",
408
+ "prompt",
409
+ "prompt_cache_key",
410
+ "prompt_cache_options",
411
+ "prompt_cache_retention",
412
+ "reasoning",
413
+ "safety_identifier",
414
+ "service_tier",
415
+ "store",
416
+ "stream",
417
+ "stream_options",
418
+ "temperature",
419
+ "text",
420
+ "tool_choice",
421
+ "tools",
422
+ "top_logprobs",
423
+ "top_p",
424
+ "truncation",
425
+ "user",
426
+ "output",
427
+ "usage",
428
+ ]);
429
+ /** A request (`input`), with the answer beside it (`output`) when it was logged too. */
430
+ const isResponsesRequest = (value) => {
431
+ const fields = object(value);
432
+ if (!fields || !Object.keys(fields).every((key) => RESPONSES_REQUEST_FIELDS.has(key)))
433
+ return false;
434
+ return ((isItemList(fields.input) &&
435
+ (fields.output === undefined || fields.output === null || isItemList(fields.output))) ||
436
+ (typeof fields.input === "string" && isItemList(fields.output)));
437
+ };
438
+ /** A Responses conversation id: `conversation` as an id or `{id}`. */
439
+ const conversationOf = (fields) => identityText(fields.conversation) ?? identityText(object(fields.conversation)?.id);
440
+ export const openaiResponses = {
441
+ id: "openai_responses",
442
+ manifestFormat: "otlp_json",
443
+ detect: (value) => isResponseObject(value) || isResponsesRequest(value) || isItemList(value),
444
+ convert(value, context) {
445
+ convertTranscript(value, context, (read) => {
446
+ if (Array.isArray(read))
447
+ return { record: read, history: read };
448
+ const fields = read;
449
+ const user = openAiUser(fields);
450
+ const session = conversationOf(fields);
451
+ const system = systemParts(fields.instructions);
452
+ const failed = fields.status === "failed";
453
+ const error = object(fields.error);
454
+ const output = Array.isArray(fields.output) ? fields.output : undefined;
455
+ const input = fields.input === undefined || fields.input === null
456
+ ? []
457
+ : typeof fields.input === "string"
458
+ ? [{ role: "user", content: fields.input }]
459
+ : fields.input;
460
+ if (!Array.isArray(input))
461
+ throw new ConversionError("input is not a list of items");
462
+ return {
463
+ record: fields,
464
+ history: input,
465
+ ...(output
466
+ ? {
467
+ answer: {
468
+ items: output,
469
+ record: output,
470
+ ...(isResponseObject(fields) && text(fields.model)
471
+ ? { model: text(fields.model) }
472
+ : {}),
473
+ usage: responseUsage(fields.usage),
474
+ ...(typeof fields.created_at === "number"
475
+ ? { start: nanosFrom(fields.created_at, "s") }
476
+ : {}),
477
+ ...(error || failed
478
+ ? {
479
+ error: {
480
+ ...(text(error?.message) ? { message: text(error?.message) } : {}),
481
+ ...(text(error?.code) ? { type: text(error?.code) } : {}),
482
+ },
483
+ }
484
+ : {}),
485
+ },
486
+ }
487
+ : {}),
488
+ ...(!isResponseObject(fields) && text(fields.model)
489
+ ? { model: text(fields.model) }
490
+ : {}),
491
+ ...(system !== undefined ? { system } : {}),
492
+ ...(fields.tools !== undefined ? { tools: fields.tools } : {}),
493
+ ...(user !== undefined ? { user } : {}),
494
+ ...(session !== undefined ? { session } : {}),
495
+ };
496
+ }, (fields) => (isResponseObject(fields) ? text(fields.id) : undefined));
497
+ },
498
+ };
@@ -0,0 +1,2 @@
1
+ import type { ImportAdapter } from "./adapter.js";
2
+ export declare const weaveCalls: ImportAdapter;
@@ -0,0 +1,159 @@
1
+ import { ConversionError, isObject } from "../traces-import-convert.js";
2
+ import { identityText } from "./identity.js";
3
+ import { count, first, isoNanos, metadataUser, object, SESSION_FIELDS, text, } from "./row-platform-fields.js";
4
+ import { readRecords } from "./rows.js";
5
+ /**
6
+ * W&B Weave calls (`CallSchema` of Weave's trace server): one call per span, joined into traces by
7
+ * `trace_id` with `parent_id`. Read from a single call, JSON Lines of calls (the calls stream
8
+ * query's answer, a UI export), a JSON array of them, the calls query answer (`{"calls": […]}`)
9
+ * and the call read answer (`{"call": …}`).
10
+ *
11
+ * - The op's kind (`attributes.weave.kind`) names the span's: `llm` a model call, `tool` a tool
12
+ * call, `agent`, and `search` a retrieval; `guardrail`, `scorer` and other ops record no
13
+ * operation. Weave records kinds since January 2026; a call without one from an op Weave's
14
+ * integrations patch (`openai.chat.completions.create`, `anthropic.Messages.create`,
15
+ * `litellm.completion`, … a model call; `mcp.server.fastmcp.FastMCP.call_tool` and
16
+ * `crewai_tools.*._run` a tool call) is read as the kind those integrations give it now. The
17
+ * name is the call's `display_name`, else its op's name.
18
+ * - Token counts come from an `llm` call's `summary.usage`, recorded per model; Weave sums a
19
+ * parent's usage over its children, so no other call's is read.
20
+ * - The conversation is the call's `thread_id`, else its `attributes`' (`thread_id`,
21
+ * `session_id`, …); the user comes from its `attributes` (`userId`, `user_id`, …, in the order
22
+ * Hue reads a metadata map). `wb_user_id` names the W&B account that logged the call, never the
23
+ * end user.
24
+ * - A trace with an `Evaluation.*` op, or whose root is a scorer, is evaluation traffic; a scorer
25
+ * applied inside the application's own op (a guardrail) is a span of its production trace. A
26
+ * call exported both before and after it ended keeps the ended copy.
27
+ */
28
+ const isCall = (value) => isObject(value) &&
29
+ typeof value.id === "string" &&
30
+ typeof value.trace_id === "string" &&
31
+ typeof value.op_name === "string" &&
32
+ typeof value.started_at === "string";
33
+ /** The calls a query answer (`calls`) or read answer (`call`) holds, when it holds any. */
34
+ function envelopeCalls(value) {
35
+ if (!isObject(value))
36
+ return undefined;
37
+ if (Array.isArray(value.calls) && value.calls.some(isCall))
38
+ return value.calls;
39
+ if (isCall(value.call))
40
+ return [value.call];
41
+ return undefined;
42
+ }
43
+ const KINDS = {
44
+ llm: "llm",
45
+ tool: "tool",
46
+ agent: "agent",
47
+ search: "retrieval",
48
+ };
49
+ /** An op's own name from its ref (`weave:///<entity>/<project>/op/<name>:<digest>`). */
50
+ const opName = (ref) => /\/op\/([^/:]+)(?::[^/]*)?$/.exec(ref)?.[1] ?? ref;
51
+ /** The ops Weave's integrations patch, by the kind they give each since kinds were recorded
52
+ * (weave `integrations/*`): a call from before then has the op but not the kind. */
53
+ const INTEGRATION_KINDS = [
54
+ [/^openai\.(?:beta\.)?chat\.completions\.(?:create|parse)$/, "llm"],
55
+ [/^openai\.responses\.(?:create|parse)$/, "llm"],
56
+ [/^anthropic\.(?:beta\.)?(?:Async)?Messages\.(?:create|stream)$/, "llm"],
57
+ [/^(?:cerebras|groq|groq\.async)\.chat\.completions\.create$/, "llm"],
58
+ [/^mistralai\.(?:async_client\.)?chat\.(?:complete|stream)$/, "llm"],
59
+ [/^cohere\.(?:Async)?Client(?:V2)?\.chat(?:_stream)?$/, "llm"],
60
+ [/^litellm\.a?completion$/, "llm"],
61
+ [/^huggingface_hub\.(?:Async)?InferenceClient\.chat_completion$/, "llm"],
62
+ [/^google\.genai\.models\.(?:Async)?Models\.generate_content(?:_stream)?$/, "llm"],
63
+ [/^vertexai\.(?:GenerativeModel\.generate_content|ChatSession\.send_message)(?:_async)?$/, "llm"],
64
+ [/^BedrockRuntime\.(?:converse|invoke)$/, "llm"],
65
+ [/^crewai\.LLM\.call$/, "llm"],
66
+ [/^mcp\.server\.fastmcp\.FastMCP\.call_tool$/, "tool"],
67
+ [/^crewai_tools\.[^.]+\._run$/, "tool"],
68
+ ];
69
+ function readCall(call, context) {
70
+ const traceKey = text(call.trace_id);
71
+ const spanKey = text(call.id);
72
+ if (!traceKey || !spanKey)
73
+ throw new ConversionError("a call without an id or trace_id");
74
+ const attributes = object(call.attributes);
75
+ if (call.attributes != null && !attributes)
76
+ throw new ConversionError("attributes is not an object");
77
+ const summary = object(call.summary);
78
+ if (call.summary != null && !summary)
79
+ throw new ConversionError("summary is not an object");
80
+ const op = opName(call.op_name);
81
+ const weaveKind = text(object(attributes?.weave)?.kind);
82
+ const kind = weaveKind !== undefined
83
+ ? Object.hasOwn(KINDS, weaveKind)
84
+ ? KINDS[weaveKind]
85
+ : undefined
86
+ : INTEGRATION_KINDS.find(([pattern]) => pattern.test(op))?.[1];
87
+ const start = isoNanos(call.started_at);
88
+ const end = call.ended_at == null ? undefined : isoNanos(call.ended_at);
89
+ const inputs = call.inputs ?? undefined;
90
+ const output = call.output ?? undefined;
91
+ const row = {
92
+ traceKey,
93
+ spanKey,
94
+ ...(text(call.parent_id) ? { parentKey: text(call.parent_id) } : {}),
95
+ name: text(call.display_name) ?? op,
96
+ ...(kind ? { kind } : {}),
97
+ start,
98
+ ...(end !== undefined ? { end } : {}),
99
+ ...(inputs !== undefined ? { input: inputs } : {}),
100
+ ...(output !== undefined ? { output } : {}),
101
+ record: call,
102
+ ...(attributes ? { metadata: attributes } : {}),
103
+ revision: end !== undefined ? "1" : "0",
104
+ attributes: {
105
+ "weave.op_name": call.op_name,
106
+ ...(attributes ? { "weave.attributes": JSON.stringify(attributes) } : {}),
107
+ },
108
+ };
109
+ const exception = text(call.exception);
110
+ if (exception !== undefined || text(object(summary?.weave)?.status) === "error")
111
+ row.error = exception !== undefined ? { message: exception } : {};
112
+ if (kind === "llm") {
113
+ const usage = object(summary?.usage);
114
+ const models = usage ? Object.keys(usage) : [];
115
+ const model = text(object(inputs)?.model) ?? (models.length === 1 ? models[0] : undefined);
116
+ const responseModel = text(object(output)?.model);
117
+ if (model)
118
+ row.model = model;
119
+ if (responseModel)
120
+ row.responseModel = responseModel;
121
+ const counts = models.length === 1 ? object(usage[models[0]]) : undefined;
122
+ if (counts)
123
+ row.usage = {
124
+ input: count(counts.prompt_tokens) ?? count(counts.input_tokens),
125
+ output: count(counts.completion_tokens) ?? count(counts.output_tokens),
126
+ cacheRead: count(counts.cache_read_input_tokens),
127
+ cacheWrite: count(counts.cache_creation_input_tokens),
128
+ };
129
+ }
130
+ if (kind === "tool")
131
+ row.tool = {
132
+ name: row.name,
133
+ ...(inputs !== undefined ? { arguments: inputs } : {}),
134
+ ...(output !== undefined ? { result: output } : {}),
135
+ };
136
+ else {
137
+ const user = metadataUser(attributes);
138
+ const session = identityText(call.thread_id) ?? first(attributes, SESSION_FIELDS);
139
+ if (user !== undefined)
140
+ row.user = user;
141
+ if (session !== undefined)
142
+ row.session = session;
143
+ }
144
+ if (op.startsWith("Evaluation.") || (weaveKind === "scorer" && row.parentKey === undefined))
145
+ row.origin = "evaluation";
146
+ context.row(row);
147
+ }
148
+ export const weaveCalls = {
149
+ id: "weave_calls",
150
+ manifestFormat: "otlp_json",
151
+ detect: (value) => isCall(value) || envelopeCalls(value) !== undefined,
152
+ convert(value, context) {
153
+ readRecords(context, isCall(value) ? [value] : (envelopeCalls(value) ?? []), (record) => (isCall(record) ? text(record.trace_id) : undefined), (record) => {
154
+ if (!isCall(record))
155
+ throw new ConversionError("a record that is not a Weave call");
156
+ readCall(record, context);
157
+ });
158
+ },
159
+ };
@@ -0,0 +1,2 @@
1
+ import type { ImportAdapter } from "./adapter.js";
2
+ export declare const zipkin: ImportAdapter;