@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
package/dist/ai-sdk.d.ts CHANGED
@@ -1,12 +1,34 @@
1
1
  import type { TelemetryOptions } from "ai";
2
2
  import type { HueClient } from "./client.js";
3
+ /** Options for {@link hueTelemetry}. */
4
+ export interface HueTelemetryOptions {
5
+ /**
6
+ * The runtime-context key whose value names the end user, recorded as `user.id` on the call's
7
+ * spans. `"userId"` by default; `false` records no user from runtime context.
8
+ */
9
+ userKey?: string | false;
10
+ /**
11
+ * The runtime-context keys whose value names the conversation, recorded as
12
+ * `gen_ai.conversation.id` on the call's spans; the first key with a value wins.
13
+ * `["sessionId", "threadId"]` by default; `false` records no conversation from runtime context.
14
+ */
15
+ sessionKey?: string | readonly string[] | false;
16
+ }
3
17
  /**
4
18
  * Per-call telemetry for AI SDK 7: pass as an agent's or generation call's `telemetry` option.
5
19
  * Spans come from Hue's tracer, so they parent under `withSpan` and inherit session, user and
6
20
  * workspace identifiers; prompt and response recording follow `captureContent`. It does not
7
21
  * change global AI SDK integrations.
8
22
  *
23
+ * The end user and conversation can also come from the call's AI SDK runtime context: the value
24
+ * under `userKey` (`userId` by default) is recorded as `user.id`, and the first value under
25
+ * `sessionKey` (`sessionId`, then `threadId`) as `gen_ai.conversation.id`, on every span of the
26
+ * call. The returned `includeRuntimeContext` names those keys, which AI SDK otherwise withholds
27
+ * from telemetry; no other runtime-context value is recorded. An enclosing `withSpan`'s
28
+ * `userId` or `sessionId` takes precedence.
29
+ *
9
30
  * @throws TypeError when the client is enabled and the installed `ai` major version is below 7;
10
31
  * AI SDK 6 applications use `hueExperimentalTelemetry` from `@hue-run/sdk` instead.
32
+ * @throws TypeError when `userKey` or `sessionKey` names an empty key.
11
33
  */
12
- export declare function hueTelemetry(hue: HueClient): TelemetryOptions;
34
+ export declare function hueTelemetry(hue: HueClient, options?: HueTelemetryOptions): TelemetryOptions;
package/dist/ai-sdk.js CHANGED
@@ -19,16 +19,51 @@ function aiMajor() {
19
19
  }
20
20
  return Number.isNaN(installedAiMajor) ? undefined : installedAiMajor;
21
21
  }
22
+ const runtimeKeys = (value, option) => {
23
+ const keys = value === false ? [] : typeof value === "string" ? [value] : [...value];
24
+ if (keys.some((key) => typeof key !== "string" || !key))
25
+ throw new TypeError(`hueTelemetry ${option} must be a non-empty runtime-context key or false`);
26
+ return keys;
27
+ };
28
+ /** A runtime-context value usable as an identifier: a string or finite number, 1–4096 valid
29
+ * characters with no NUL, as `withSpan` accepts for `userId` and `sessionId`. */
30
+ function identifierFrom(value) {
31
+ const text = typeof value === "string"
32
+ ? value
33
+ : typeof value === "number" && Number.isFinite(value)
34
+ ? String(value)
35
+ : undefined;
36
+ return text && text.length <= 4096 && !text.includes("\u0000") && text.isWellFormed()
37
+ ? text
38
+ : undefined;
39
+ }
40
+ /** The identifier under the first of `keys` that holds a usable one. */
41
+ function firstIdentifier(context, keys) {
42
+ for (const key of keys) {
43
+ const value = identifierFrom(context?.[key]);
44
+ if (value)
45
+ return value;
46
+ }
47
+ return undefined;
48
+ }
22
49
  /**
23
50
  * Per-call telemetry for AI SDK 7: pass as an agent's or generation call's `telemetry` option.
24
51
  * Spans come from Hue's tracer, so they parent under `withSpan` and inherit session, user and
25
52
  * workspace identifiers; prompt and response recording follow `captureContent`. It does not
26
53
  * change global AI SDK integrations.
27
54
  *
55
+ * The end user and conversation can also come from the call's AI SDK runtime context: the value
56
+ * under `userKey` (`userId` by default) is recorded as `user.id`, and the first value under
57
+ * `sessionKey` (`sessionId`, then `threadId`) as `gen_ai.conversation.id`, on every span of the
58
+ * call. The returned `includeRuntimeContext` names those keys, which AI SDK otherwise withholds
59
+ * from telemetry; no other runtime-context value is recorded. An enclosing `withSpan`'s
60
+ * `userId` or `sessionId` takes precedence.
61
+ *
28
62
  * @throws TypeError when the client is enabled and the installed `ai` major version is below 7;
29
63
  * AI SDK 6 applications use `hueExperimentalTelemetry` from `@hue-run/sdk` instead.
64
+ * @throws TypeError when `userKey` or `sessionKey` names an empty key.
30
65
  */
31
- export function hueTelemetry(hue) {
66
+ export function hueTelemetry(hue, options = {}) {
32
67
  // Core tracing also installs beside AI SDK 6. This adapter needs the v7 per-call
33
68
  // integration API, so fail explicitly during configuration instead of silently on v6.
34
69
  if (hue.enabled) {
@@ -36,10 +71,33 @@ export function hueTelemetry(hue) {
36
71
  if (major !== undefined && major < 7)
37
72
  throw new TypeError("hueTelemetry requires ai@7 or later; AI SDK 6 applications pass hueExperimentalTelemetry(hue) from @hue-run/sdk as experimental_telemetry");
38
73
  }
74
+ const userKeys = runtimeKeys(options.userKey ?? "userId", "userKey");
75
+ const sessionKeys = runtimeKeys(options.sessionKey ?? ["sessionId", "threadId"], "sessionKey");
76
+ const keys = [...new Set([...userKeys, ...sessionKeys])];
39
77
  return {
40
78
  isEnabled: hue.enabled,
41
79
  recordInputs: hue.captureContent,
42
80
  recordOutputs: hue.captureContent,
43
- integrations: [new OpenTelemetry({ tracer: hue.tracer, usage: true })],
81
+ ...(keys.length
82
+ ? { includeRuntimeContext: Object.fromEntries(keys.map((key) => [key, true])) }
83
+ : {}),
84
+ integrations: [
85
+ new OpenTelemetry({
86
+ tracer: hue.tracer,
87
+ usage: true,
88
+ ...(keys.length
89
+ ? {
90
+ enrichSpan: ({ runtimeContext }) => {
91
+ const user = firstIdentifier(runtimeContext, userKeys);
92
+ const session = firstIdentifier(runtimeContext, sessionKeys);
93
+ return {
94
+ ...(user ? { "user.id": user } : {}),
95
+ ...(session ? { "gen_ai.conversation.id": session } : {}),
96
+ };
97
+ },
98
+ }
99
+ : {}),
100
+ }),
101
+ ],
44
102
  };
45
103
  }
@@ -0,0 +1,80 @@
1
+ import type { ConvertedTrace, Refusal, TraceImportFormat } from "../traces-import-convert.js";
2
+ import type { IdentityKeys } from "./identity.js";
3
+ import type { EventRow, SpanRow } from "./rows.js";
4
+ /**
5
+ * The contract every import format implements; see `registry.ts` for how the formats are tried
6
+ * and how to add one.
7
+ */
8
+ /** A source format `hue traces import prepare` reads. */
9
+ export interface ImportAdapter {
10
+ /** The format's id: `hue.import.format` on every span it emits, the report's name for it and
11
+ * the namespace of the ids it derives. Lowercase, `[a-z0-9_]`. */
12
+ readonly id: string;
13
+ /** The format Hue's import registration knows the traces under (`source.formats` of the
14
+ * manifest). A format that emits canonical rows declares `otlp_json`, the encoding of the
15
+ * batches it writes, until Hue lists its own id. */
16
+ readonly manifestFormat: TraceImportFormat;
17
+ /** Whether a parsed JSON value is this format's: a whole document, one record of a JSON array,
18
+ * or one line of JSON Lines. Decided by distinctive top-level keys alone, never by a file name,
19
+ * and never claiming another format's value. */
20
+ detect(value: unknown): boolean;
21
+ /** Reads one value `detect` claimed. Rows, events and traces go to the context; a record that
22
+ * cannot be read is reported with `context.refuse` and the rest of the value is still read.
23
+ * Throwing a `ConversionError` reports the whole value as unreadable, with its message. */
24
+ convert(value: unknown, context: ConvertContext): void;
25
+ }
26
+ /** Where a record was read, for the report: never written to a span. */
27
+ export interface Location {
28
+ file: string;
29
+ /** 1-based line of JSON Lines. */
30
+ line?: number;
31
+ /** 1-based position in a JSON array. */
32
+ record?: number;
33
+ }
34
+ /** Where a value was read, for the report: never written to a span. */
35
+ export interface ValueSource {
36
+ /** The SHA-256 of the file's decoded content: the same for a renamed or recompressed copy. */
37
+ readonly digest: string;
38
+ /** The JSON text the value was parsed from: the document or the line (an array record has
39
+ * its re-serialized text). */
40
+ readonly text: string;
41
+ /** The UTF-8 size of `text`. */
42
+ readonly bytes: number;
43
+ /** Whether the text held an integer JavaScript cannot represent exactly; such an integer is
44
+ * kept as its decimal text in the parsed value. */
45
+ readonly unsafeIntegers: boolean;
46
+ /** The value's 1-based line, for JSON Lines. */
47
+ readonly line?: number;
48
+ /** The value's 0-based position, for a record of a JSON array. */
49
+ readonly index?: number;
50
+ }
51
+ /** Run-wide state a format gathers across files and lines; `finish` runs once all are read. */
52
+ export interface TraceCollector {
53
+ finish(): {
54
+ traces: ConvertedTrace[];
55
+ refused: Refusal[];
56
+ };
57
+ /** Whether the traces it emits already carry `--user-key` and `--session-key`, as the shared
58
+ * row emitter writes them; otherwise the reader applies the keys to its traces. */
59
+ readonly appliesKeys?: boolean;
60
+ }
61
+ /** What a format's `convert` hands its results to. */
62
+ export interface ConvertContext {
63
+ readonly source: ValueSource;
64
+ /** `--user-key` and `--session-key`, for a collector that emits rows itself. */
65
+ readonly keys: IdentityKeys;
66
+ /** Adds one canonical span row; rows are grouped into traces by `traceKey` across every file
67
+ * and line, and emitted as OTLP spans by the shared emitter (`rows.ts`). */
68
+ row(row: SpanRow): void;
69
+ /** Attaches an event (a log line, a message) to a span row by its trace and span keys. */
70
+ event(event: EventRow): void;
71
+ /** Adds a trace the format converted whole. */
72
+ trace(trace: ConvertedTrace): void;
73
+ /** Reports a record that could not be read. With the record's `traceKey`, that trace is
74
+ * refused whole and named; without one, the record alone is reported. */
75
+ refuse(reason: string, traceKey?: string): void;
76
+ /** This format's run-wide collector, created on first use by `create`. Formats that name the
77
+ * same `shared` key share one collector, so a trace whose spans they split between files is
78
+ * gathered as one; a shared collector names each trace's format itself. */
79
+ collector<T extends TraceCollector>(create: () => T, shared?: string): T;
80
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { ImportAdapter } from "./adapter.js";
2
+ /** Agent runtimes, event logs and plain chat transcripts: OpenAI Agents SDK traces, Raindrop
3
+ * events and span rows, Helicone request logs, and OpenAI chat, OpenAI Responses and Anthropic Messages
4
+ * transcripts. No two claim the same value, so their order here is free. */
5
+ export declare const agentEventAdapters: readonly ImportAdapter[];
@@ -0,0 +1,16 @@
1
+ import { helicone } from "./helicone.js";
2
+ import { openaiAgents } from "./openai-agents.js";
3
+ import { raindrop, raindropSpans } from "./raindrop.js";
4
+ import { anthropicMessages, openaiChat, openaiResponses } from "./transcripts.js";
5
+ /** Agent runtimes, event logs and plain chat transcripts: OpenAI Agents SDK traces, Raindrop
6
+ * events and span rows, Helicone request logs, and OpenAI chat, OpenAI Responses and Anthropic Messages
7
+ * transcripts. No two claim the same value, so their order here is free. */
8
+ export const agentEventAdapters = [
9
+ openaiAgents,
10
+ raindrop,
11
+ raindropSpans,
12
+ helicone,
13
+ openaiChat,
14
+ openaiResponses,
15
+ anthropicMessages,
16
+ ];
@@ -0,0 +1,3 @@
1
+ import type { ImportAdapter } from "./adapter.js";
2
+ /** Text ATIF v1.7 and v1.8 exports, one standalone document each, converted whole. */
3
+ export declare const atif: ImportAdapter;
@@ -0,0 +1,15 @@
1
+ import { ConversionError, convertAtifExport, isObject } from "../traces-import-convert.js";
2
+ /** Text ATIF v1.7 and v1.8 exports, one standalone document each, converted whole. */
3
+ export const atif = {
4
+ id: "atif",
5
+ manifestFormat: "atif",
6
+ detect: (value) => isObject(value) &&
7
+ typeof value.schema_version === "string" &&
8
+ value.schema_version.startsWith("ATIF-"),
9
+ convert(value, context) {
10
+ if (!isObject(value) || !Array.isArray(value.steps))
11
+ throw new ConversionError("an ATIF document without steps");
12
+ const { text, unsafeIntegers, bytes } = context.source;
13
+ context.trace(convertAtifExport({ text, document: value, unsafeIntegers }, bytes));
14
+ },
15
+ };
@@ -0,0 +1,2 @@
1
+ import type { ImportAdapter } from "./adapter.js";
2
+ export declare const braintrust: ImportAdapter;
@@ -0,0 +1,222 @@
1
+ import { ConversionError, isObject, parseNestedJsonText, unixNano, } from "../traces-import-convert.js";
2
+ import { identityText } from "./identity.js";
3
+ import { metadataUser } from "./row-platform-fields.js";
4
+ import { nanosFrom, readRecords } from "./rows.js";
5
+ const isRow = (value) => isObject(value) && typeof value.span_id === "string" && typeof value.root_span_id === "string";
6
+ /** The rows an envelope (`events` of a fetch, `data` of a query) holds, when it holds any. */
7
+ function envelopeRows(value) {
8
+ if (!isObject(value))
9
+ return undefined;
10
+ for (const key of ["events", "data"])
11
+ if (Array.isArray(value[key]) && value[key].some(isRow))
12
+ return value[key];
13
+ return undefined;
14
+ }
15
+ const object = (value) => isObject(value) ? value : undefined;
16
+ const text = (value) => typeof value === "string" && value ? value : undefined;
17
+ const first = (fields, names) => {
18
+ for (const name of names) {
19
+ const value = identityText(fields?.[name]);
20
+ if (value !== undefined)
21
+ return value;
22
+ }
23
+ return undefined;
24
+ };
25
+ const SESSION_FIELDS = [
26
+ "session_id",
27
+ "sessionId",
28
+ "thread_id",
29
+ "threadId",
30
+ "conversation_id",
31
+ "conversationId",
32
+ "gen_ai.conversation.id",
33
+ "langgraph.thread_id",
34
+ ];
35
+ const KINDS = { llm: "llm", tool: "tool" };
36
+ /** Braintrust's own activity inside a trace (`SpanType` in its OpenAPI): scoring, not the
37
+ * agent's work. */
38
+ const SCORING_TYPES = new Set([
39
+ "score",
40
+ "automation",
41
+ "facet",
42
+ "classifier",
43
+ "preprocessor",
44
+ "review",
45
+ ]);
46
+ /** A token count Braintrust recorded, when it is a nonnegative integer. */
47
+ const count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
48
+ /** A Mastra tool span's input is the assistant message that requested it; its arguments are
49
+ * that call's. Any other input is the arguments as recorded. */
50
+ function toolArguments(input) {
51
+ const message = Array.isArray(input) && input.length === 1 ? object(input[0]) : undefined;
52
+ const calls = message?.tool_calls;
53
+ const requested = Array.isArray(calls) && calls.length === 1 ? object(calls[0]) : undefined;
54
+ const fn = object(requested?.function);
55
+ if (!requested || !fn)
56
+ return input === undefined ? {} : { arguments: input };
57
+ let args = fn.arguments ?? null;
58
+ if (typeof args === "string")
59
+ try {
60
+ args = parseNestedJsonText(args).value;
61
+ }
62
+ catch {
63
+ // Arguments that are not JSON stay the text recorded.
64
+ }
65
+ return { arguments: args, ...(text(requested.id) ? { callId: text(requested.id) } : {}) };
66
+ }
67
+ /** A tool span's output as a tool message (`{role: "tool", content, tool_call_id}`) is read as
68
+ * its content. */
69
+ function toolResult(output) {
70
+ const message = object(output);
71
+ if (message?.role === "tool" && "content" in message)
72
+ return {
73
+ result: message.content,
74
+ ...(text(message.tool_call_id) ? { callId: text(message.tool_call_id) } : {}),
75
+ };
76
+ return output === undefined ? {} : { result: output };
77
+ }
78
+ function readRow(record, context) {
79
+ const traceKey = text(record.root_span_id);
80
+ const spanKey = text(record.span_id);
81
+ if (!traceKey || !spanKey)
82
+ throw new ConversionError("a row without a span_id or root_span_id");
83
+ const parents = record.span_parents ?? [];
84
+ if (!Array.isArray(parents) || !parents.every((parent) => typeof parent === "string" && parent))
85
+ throw new ConversionError("span_parents is not a list of span ids");
86
+ const attributes = object(record.span_attributes);
87
+ if (record.span_attributes != null && !attributes)
88
+ throw new ConversionError("span_attributes is not an object");
89
+ const metadata = object(record.metadata);
90
+ if (record.metadata != null && !metadata)
91
+ throw new ConversionError("metadata is not an object");
92
+ const metrics = object(record.metrics);
93
+ if (record.metrics != null && !metrics)
94
+ throw new ConversionError("metrics is not an object");
95
+ const instant = (value) => value == null ? undefined : nanosFrom(value, "s");
96
+ let start = instant(metrics?.start);
97
+ const end = instant(metrics?.end);
98
+ let timing;
99
+ if (start === undefined && typeof record.created === "string") {
100
+ start = BigInt(unixNano(record.created));
101
+ timing = "start_from_created";
102
+ }
103
+ const type = text(attributes?.type);
104
+ const name = text(attributes?.name) ?? "span";
105
+ if (type === "log") {
106
+ context.event({
107
+ traceKey,
108
+ spanKey,
109
+ name: text(record.output) ?? name,
110
+ ...(start !== undefined ? { time: start } : {}),
111
+ ...(metadata ? { attributes: { "braintrust.metadata": JSON.stringify(metadata) } } : {}),
112
+ record,
113
+ });
114
+ return;
115
+ }
116
+ const context_ = object(record.context);
117
+ const origin = object(context_?.span_origin);
118
+ const instrumentation = text(object(origin?.instrumentation)?.name);
119
+ const environment = object(origin?.environment);
120
+ const mastra = instrumentation === "mastra" ||
121
+ metadata?.spanType !== undefined ||
122
+ metadata?.["mastra-trace-id"] !== undefined;
123
+ const openaiAgents = instrumentation === "openai-agents" || traceKey.startsWith("trace_");
124
+ const kind = type && Object.hasOwn(KINDS, type)
125
+ ? KINDS[type]
126
+ : mastra && metadata?.spanType === "agent_run"
127
+ ? "agent"
128
+ : undefined;
129
+ const error = record.error;
130
+ const scoring = attributes?.purpose === "scorer" || (type !== undefined && SCORING_TYPES.has(type));
131
+ const row = {
132
+ traceKey,
133
+ spanKey,
134
+ ...(scoring ? { scoring } : {}),
135
+ ...(parents[0] !== undefined ? { parentKey: parents[0] } : {}),
136
+ ...(parents.length > 1 ? { linkKeys: parents.slice(1) } : {}),
137
+ name,
138
+ ...(kind ? { kind } : {}),
139
+ ...(start !== undefined ? { start } : {}),
140
+ ...(end !== undefined ? { end } : {}),
141
+ ...(timing ? { timing } : {}),
142
+ ...(record.input !== undefined ? { input: record.input } : {}),
143
+ ...(record.output !== undefined ? { output: record.output } : {}),
144
+ record,
145
+ ...(metadata ? { metadata } : {}),
146
+ ...(text(record._xact_id) ? { revision: text(record._xact_id) } : {}),
147
+ attributes: {
148
+ ...(attributes ? { "braintrust.span_attributes": JSON.stringify(attributes) } : {}),
149
+ ...(metadata && Object.keys(metadata).length
150
+ ? { "braintrust.metadata": JSON.stringify(metadata) }
151
+ : {}),
152
+ ...(object(record.scores) ? { "braintrust.scores": JSON.stringify(record.scores) } : {}),
153
+ ...(Array.isArray(record.tags) && record.tags.every((tag) => typeof tag === "string")
154
+ ? { "braintrust.tags": record.tags }
155
+ : {}),
156
+ },
157
+ };
158
+ if (error !== undefined && error !== null && error !== false && error !== "")
159
+ row.error = {
160
+ message: typeof error === "string" ? error : (text(object(error)?.message) ?? JSON.stringify(error)),
161
+ ...(text(object(error)?.name) ? { type: text(object(error)?.name) } : {}),
162
+ };
163
+ if (kind === "llm") {
164
+ const model = text(metadata?.model);
165
+ const provider = text(metadata?.provider);
166
+ if (model)
167
+ row.model = model;
168
+ if (provider)
169
+ row.provider = provider;
170
+ row.usage = {
171
+ input: count(metrics?.prompt_tokens),
172
+ output: count(metrics?.completion_tokens),
173
+ cacheRead: count(metrics?.prompt_cached_tokens),
174
+ cacheWrite: count(metrics?.prompt_cache_creation_tokens),
175
+ reasoning: count(metrics?.completion_reasoning_tokens),
176
+ };
177
+ }
178
+ if (kind === "tool") {
179
+ const requested = toolArguments(record.input);
180
+ const returned = toolResult(record.output);
181
+ const quoted = /'([^']+)'\s*$/.exec(name)?.[1];
182
+ row.tool = {
183
+ name: text(metadata?.toolName) ?? text(metadata?.tool_name) ?? quoted ?? name,
184
+ ...(requested.arguments !== undefined ? { arguments: requested.arguments } : {}),
185
+ ...(returned.result !== undefined ? { result: returned.result } : {}),
186
+ };
187
+ const callId = text(metadata?.toolCallId) ??
188
+ text(metadata?.tool_call_id) ??
189
+ requested.callId ??
190
+ returned.callId;
191
+ if (callId)
192
+ row.tool.callId = callId;
193
+ }
194
+ else {
195
+ const user = metadataUser(metadata) ?? (mastra ? first(metadata, ["resourceId"]) : undefined);
196
+ const session = first(metadata, SESSION_FIELDS) ?? (openaiAgents ? first(metadata, ["group_id"]) : undefined);
197
+ if (user !== undefined)
198
+ row.user = user;
199
+ if (session !== undefined)
200
+ row.session = session;
201
+ }
202
+ if (text(record.experiment_id) || text(record.dataset_id))
203
+ row.origin = "evaluation";
204
+ // `vercel` and other platform names are where the code ran, not a deployment environment.
205
+ const environmentName = text(environment?.name);
206
+ if (environmentName === "production" || environmentName === "staging")
207
+ row.environment = environmentName;
208
+ context.row(row);
209
+ }
210
+ export const braintrust = {
211
+ id: "braintrust",
212
+ manifestFormat: "otlp_json",
213
+ detect: (value) => isRow(value) || envelopeRows(value) !== undefined,
214
+ convert(value, context) {
215
+ const records = isRow(value) ? [value] : (envelopeRows(value) ?? []);
216
+ readRecords(context, records, (record) => (isRow(record) ? text(record.root_span_id) : undefined), (record) => {
217
+ if (!isRow(record))
218
+ throw new ConversionError("a record that is not a Braintrust span row");
219
+ readRow(record, context);
220
+ });
221
+ },
222
+ };
@@ -0,0 +1,32 @@
1
+ /** The file's content with its compression undone, and what of it could not be read; or why
2
+ * none of it can be. */
3
+ export type Decoded = {
4
+ bytes: Uint8Array;
5
+ note?: string;
6
+ } | {
7
+ unsupported: string;
8
+ };
9
+ /** The file's content with its compression undone, or why it cannot be read. `maxBytes` bounds
10
+ * the decompressed size. */
11
+ export declare function decodeContainer(bytes: Uint8Array, maxBytes: number): Decoded;
12
+ /** One JSON value read from a file: the whole document, or one line of JSON Lines. */
13
+ export interface ParsedValue {
14
+ value: unknown;
15
+ /** The text it was parsed from. */
16
+ text: string;
17
+ unsafeIntegers: boolean;
18
+ /** Its 1-based line, for JSON Lines. */
19
+ line?: number;
20
+ /** A repair the value was read with, for the report. */
21
+ note?: string;
22
+ }
23
+ /** The JSON values of decoded content, the lines that are not JSON, or why none can be read. */
24
+ export declare function parseValues(bytes: Uint8Array): {
25
+ values: ParsedValue[];
26
+ problems: {
27
+ line: number;
28
+ reason: string;
29
+ }[];
30
+ } | {
31
+ unsupported: string;
32
+ };