autotel-devtools 20.0.1 → 21.0.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.
package/README.md CHANGED
@@ -214,6 +214,7 @@ AUTOTEL_MAX_METRIC_COUNT=50000 # Max metrics to keep (default: 100)
214
214
  AUTOTEL_DEVTOOLS_PORT=4318 # Server port (default: 4318)
215
215
  AUTOTEL_DEVTOOLS_HOST=127.0.0.1 # Bind host (default: 127.0.0.1)
216
216
  AUTOTEL_DEVTOOLS_TITLE="My App" # Dashboard title (optional)
217
+ AUTOTEL_DEVTOOLS_SOURCE_ROOT=. # Root GET /source may read (default: cwd on a loopback bind, else off; `false` disables)
217
218
  ```
218
219
 
219
220
  ### CLI Options
package/dist/cli.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- const require_http = require('./http-j-oBI6r6.cjs');
3
- const require_listen = require('./listen-DGzVi7DE.cjs');
2
+ const require_http = require('./http-CXSzX4ee.cjs');
3
+ const require_listen = require('./listen-CEJ3nYJf.cjs');
4
4
  let node_http = require("node:http");
5
5
  let node_fs = require("node:fs");
6
6
  let node_path = require("node:path");
@@ -79,9 +79,11 @@ async function startReceiver(options) {
79
79
  host: options.host,
80
80
  verbose: true
81
81
  });
82
+ const sourceRoot = require_http.resolveSourceRoot(process.env.AUTOTEL_DEVTOOLS_SOURCE_ROOT, process.cwd(), loopbackOnly);
82
83
  require_http.attachDevtoolsRoutes(httpServer, wsServer, {
83
84
  loopbackOnly,
84
- title: options.title
85
+ title: options.title,
86
+ sourceRoot
85
87
  });
86
88
  const listeners = require_listen.listenLoopbackDualStack({
87
89
  primary: httpServer,
@@ -89,7 +91,8 @@ async function startReceiver(options) {
89
91
  host: options.host,
90
92
  attachSecondary: (s) => require_http.attachDevtoolsRoutes(s, wsServer, {
91
93
  loopbackOnly,
92
- title: options.title
94
+ title: options.title,
95
+ sourceRoot
93
96
  })
94
97
  });
95
98
  const { addresses, warnings, port: boundPort } = await listeners.ready;
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { d as DevtoolsServer, i as probePortHolder, p as hostHeaderIsLoopback, t as attachDevtoolsRoutes } from "./http-uVPjDDld.js";
3
- import { t as listenLoopbackDualStack } from "./listen-NVMbBWHw.js";
2
+ import { a as probePortHolder, f as DevtoolsServer, m as hostHeaderIsLoopback, r as resolveSourceRoot, t as attachDevtoolsRoutes } from "./http-CNZMrnzv.js";
3
+ import { t as listenLoopbackDualStack } from "./listen-DBfsfcdd.js";
4
4
  import { createServer } from "node:http";
5
5
  import { readFileSync } from "node:fs";
6
6
  import { dirname, resolve } from "node:path";
@@ -79,9 +79,11 @@ async function startReceiver(options) {
79
79
  host: options.host,
80
80
  verbose: true
81
81
  });
82
+ const sourceRoot = resolveSourceRoot(process.env.AUTOTEL_DEVTOOLS_SOURCE_ROOT, process.cwd(), loopbackOnly);
82
83
  attachDevtoolsRoutes(httpServer, wsServer, {
83
84
  loopbackOnly,
84
- title: options.title
85
+ title: options.title,
86
+ sourceRoot
85
87
  });
86
88
  const listeners = listenLoopbackDualStack({
87
89
  primary: httpServer,
@@ -89,7 +91,8 @@ async function startReceiver(options) {
89
91
  host: options.host,
90
92
  attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer, {
91
93
  loopbackOnly,
92
- title: options.title
94
+ title: options.title,
95
+ sourceRoot
93
96
  })
94
97
  });
95
98
  const { addresses, warnings, port: boundPort } = await listeners.ready;
@@ -1,5 +1,15 @@
1
- import { a as ErrorGroup, o as ErrorOccurrence, u as TraceData } from "./exporter-CEKKJTci.cjs";
1
+ import { a as ErrorGroup, o as ErrorOccurrence, u as TraceData } from "./exporter-Due9Rd4s.js";
2
2
  //#region src/server/error-aggregator.d.ts
3
+ /** How many errors the aggregator is holding, and what they are. */
4
+ interface ErrorStats {
5
+ totalGroups: number;
6
+ totalErrors: number;
7
+ recentErrors: number;
8
+ topErrorTypes: Array<{
9
+ type: string;
10
+ count: number;
11
+ }>;
12
+ }
3
13
  interface ErrorAggregatorOptions {
4
14
  /**
5
15
  * Maximum number of error groups to track (default: 100)
@@ -96,15 +106,7 @@ declare class ErrorAggregator {
96
106
  /**
97
107
  * Get error statistics
98
108
  */
99
- getStats(): {
100
- totalGroups: number;
101
- totalErrors: number;
102
- recentErrors: number;
103
- topErrorTypes: Array<{
104
- type: string;
105
- count: number;
106
- }>;
107
- };
109
+ getStats(): ErrorStats;
108
110
  /**
109
111
  * Clear all error groups
110
112
  */
@@ -1,5 +1,15 @@
1
- import { a as ErrorGroup, o as ErrorOccurrence, u as TraceData } from "./exporter-BJwufQwg.js";
1
+ import { a as ErrorGroup, o as ErrorOccurrence, u as TraceData } from "./exporter-Dt4kx128.cjs";
2
2
  //#region src/server/error-aggregator.d.ts
3
+ /** How many errors the aggregator is holding, and what they are. */
4
+ interface ErrorStats {
5
+ totalGroups: number;
6
+ totalErrors: number;
7
+ recentErrors: number;
8
+ topErrorTypes: Array<{
9
+ type: string;
10
+ count: number;
11
+ }>;
12
+ }
3
13
  interface ErrorAggregatorOptions {
4
14
  /**
5
15
  * Maximum number of error groups to track (default: 100)
@@ -96,15 +106,7 @@ declare class ErrorAggregator {
96
106
  /**
97
107
  * Get error statistics
98
108
  */
99
- getStats(): {
100
- totalGroups: number;
101
- totalErrors: number;
102
- recentErrors: number;
103
- topErrorTypes: Array<{
104
- type: string;
105
- count: number;
106
- }>;
107
- };
109
+ getStats(): ErrorStats;
108
110
  /**
109
111
  * Clear all error groups
110
112
  */
@@ -1,3 +1,4 @@
1
+ import { n as SpanAttributes } from "./types-DHDvZnnn.cjs";
1
2
  import { Server } from "node:http";
2
3
  import { AgentRawEvent, AgentSession, OtelMetricRecord } from "autotel-agents";
3
4
  import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
@@ -12,7 +13,7 @@ interface SpanData {
12
13
  startTime: number;
13
14
  endTime: number;
14
15
  duration: number;
15
- attributes: Record<string, any>;
16
+ attributes: SpanAttributes;
16
17
  status: {
17
18
  code: 'OK' | 'ERROR' | 'UNSET';
18
19
  message?: string;
@@ -20,12 +21,12 @@ interface SpanData {
20
21
  events?: Array<{
21
22
  name: string;
22
23
  timestamp: number;
23
- attributes?: Record<string, any>;
24
+ attributes?: SpanAttributes;
24
25
  }>;
25
26
  links?: Array<{
26
27
  traceId: string;
27
28
  spanId: string;
28
- attributes?: Record<string, any>;
29
+ attributes?: SpanAttributes;
29
30
  }>;
30
31
  scope?: {
31
32
  name?: string;
@@ -61,14 +62,14 @@ interface LogData {
61
62
  severityNumber?: number;
62
63
  body: string | Record<string, unknown>;
63
64
  timestamp: number;
64
- attributes?: Record<string, unknown>;
65
+ attributes?: SpanAttributes;
65
66
  resource?: Record<string, unknown>;
66
67
  }
67
68
  interface MetricData {
68
69
  type: 'event' | 'funnel' | 'outcome' | 'value';
69
70
  name: string;
70
71
  value?: number;
71
- attributes: Record<string, any>;
72
+ attributes: SpanAttributes;
72
73
  timestamp: number;
73
74
  traceId?: string;
74
75
  }
@@ -83,7 +84,7 @@ interface ErrorGroup {
83
84
  affectedTraces: string[];
84
85
  affectedSpans: string[];
85
86
  service?: string;
86
- attributes?: Record<string, unknown>;
87
+ attributes?: SpanAttributes;
87
88
  }
88
89
  interface ErrorOccurrence {
89
90
  traceId: string;
@@ -95,8 +96,15 @@ interface ErrorOccurrence {
95
96
  type: string;
96
97
  message: string;
97
98
  stackTrace?: string;
99
+ /**
100
+ * Grouping key the emitting SDK already decided on (`exception.fingerprint`,
101
+ * written by autotel's `exceptionFingerprint()` enricher). When present the
102
+ * aggregator groups by it instead of re-deriving one from the stack string,
103
+ * so this tab agrees with every other backend receiving the same spans.
104
+ */
105
+ fingerprint?: string;
98
106
  };
99
- attributes?: Record<string, unknown>;
107
+ attributes?: SpanAttributes;
100
108
  }
101
109
  interface DevtoolsData {
102
110
  traces: TraceData[];
@@ -1,3 +1,4 @@
1
+ import { n as SpanAttributes } from "./types-DHDvZnnn.js";
1
2
  import { Server } from "node:http";
2
3
  import { AgentRawEvent, AgentSession, OtelMetricRecord } from "autotel-agents";
3
4
  import { ExportResult } from "@opentelemetry/core";
@@ -12,7 +13,7 @@ interface SpanData {
12
13
  startTime: number;
13
14
  endTime: number;
14
15
  duration: number;
15
- attributes: Record<string, any>;
16
+ attributes: SpanAttributes;
16
17
  status: {
17
18
  code: 'OK' | 'ERROR' | 'UNSET';
18
19
  message?: string;
@@ -20,12 +21,12 @@ interface SpanData {
20
21
  events?: Array<{
21
22
  name: string;
22
23
  timestamp: number;
23
- attributes?: Record<string, any>;
24
+ attributes?: SpanAttributes;
24
25
  }>;
25
26
  links?: Array<{
26
27
  traceId: string;
27
28
  spanId: string;
28
- attributes?: Record<string, any>;
29
+ attributes?: SpanAttributes;
29
30
  }>;
30
31
  scope?: {
31
32
  name?: string;
@@ -61,14 +62,14 @@ interface LogData {
61
62
  severityNumber?: number;
62
63
  body: string | Record<string, unknown>;
63
64
  timestamp: number;
64
- attributes?: Record<string, unknown>;
65
+ attributes?: SpanAttributes;
65
66
  resource?: Record<string, unknown>;
66
67
  }
67
68
  interface MetricData {
68
69
  type: 'event' | 'funnel' | 'outcome' | 'value';
69
70
  name: string;
70
71
  value?: number;
71
- attributes: Record<string, any>;
72
+ attributes: SpanAttributes;
72
73
  timestamp: number;
73
74
  traceId?: string;
74
75
  }
@@ -83,7 +84,7 @@ interface ErrorGroup {
83
84
  affectedTraces: string[];
84
85
  affectedSpans: string[];
85
86
  service?: string;
86
- attributes?: Record<string, unknown>;
87
+ attributes?: SpanAttributes;
87
88
  }
88
89
  interface ErrorOccurrence {
89
90
  traceId: string;
@@ -95,8 +96,15 @@ interface ErrorOccurrence {
95
96
  type: string;
96
97
  message: string;
97
98
  stackTrace?: string;
99
+ /**
100
+ * Grouping key the emitting SDK already decided on (`exception.fingerprint`,
101
+ * written by autotel's `exceptionFingerprint()` enricher). When present the
102
+ * aggregator groups by it instead of re-deriving one from the stack string,
103
+ * so this tab agrees with every other backend receiving the same spans.
104
+ */
105
+ fingerprint?: string;
98
106
  };
99
- attributes?: Record<string, unknown>;
107
+ attributes?: SpanAttributes;
100
108
  }
101
109
  interface DevtoolsData {
102
110
  traces: TraceData[];
@@ -1,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_json_fields = require('../json-fields-DZFlNqT2.cjs');
2
3
 
3
4
  //#region src/widget/genai/detect.ts
4
5
  const GENAI_MARKERS = [
@@ -150,25 +151,6 @@ function priceCall(inputs) {
150
151
 
151
152
  //#endregion
152
153
  //#region src/widget/genai/normalize.ts
153
- function str(v) {
154
- return typeof v === "string" ? v : void 0;
155
- }
156
- function num(v) {
157
- if (typeof v === "number") return v;
158
- if (typeof v === "string" && v !== "") {
159
- const n = Number(v);
160
- return Number.isFinite(n) ? n : void 0;
161
- }
162
- }
163
- function strArray(v) {
164
- if (Array.isArray(v) && v.every((x) => typeof x === "string")) return v;
165
- if (typeof v === "string") return [v];
166
- }
167
- function bool(v) {
168
- if (typeof v === "boolean") return v;
169
- if (v === "true") return true;
170
- if (v === "false") return false;
171
- }
172
154
  function parseJson(v) {
173
155
  if (v == null) return void 0;
174
156
  if (typeof v === "object") return v;
@@ -221,9 +203,10 @@ function normalizeMessageParts(raw) {
221
203
  value: p
222
204
  };
223
205
  });
224
- if (typeof raw.content === "string") return [{
206
+ const legacyText = require_json_fields.asString(raw.content);
207
+ if (legacyText !== void 0) return [{
225
208
  kind: "text",
226
- text: raw.content
209
+ text: legacyText
227
210
  }];
228
211
  if (raw.content != null) return [{
229
212
  kind: "json",
@@ -232,7 +215,8 @@ function normalizeMessageParts(raw) {
232
215
  return [];
233
216
  }
234
217
  function unwrapToolResult(payload) {
235
- return payload && typeof payload === "object" && "value" in payload ? payload.value : payload;
218
+ const wrapper = require_json_fields.asObject(payload);
219
+ return wrapper && "value" in wrapper ? wrapper.value : payload;
236
220
  }
237
221
  function classifyAiSdkPart(part) {
238
222
  if (part.type === "tool-call") return {
@@ -346,8 +330,9 @@ function messagesFromEvents(events) {
346
330
  for (const ev of events) {
347
331
  const attrs = ev.attributes ?? {};
348
332
  if (ev.name === "gen_ai.choice") {
349
- const message = parseJson(attrs.message) ?? attrs.message;
350
- const finishReason = str(attrs.finish_reason);
333
+ const unparsedMessage = attrs.message;
334
+ const message = parseJson(attrs.message) ?? unparsedMessage;
335
+ const finishReason = require_json_fields.asString(attrs.finish_reason);
351
336
  if (message) {
352
337
  const normalized = normalizeMessage(message);
353
338
  if (finishReason) normalized.finishReason = finishReason;
@@ -359,12 +344,13 @@ function messagesFromEvents(events) {
359
344
  if (!m) continue;
360
345
  const role = m[1];
361
346
  const content = attrs.content;
362
- const parsed = typeof content === "string" ? parseJson(content) ?? content : content;
347
+ const contentText = require_json_fields.asString(content);
348
+ const parsed = contentText === void 0 ? content : parseJson(contentText) ?? contentText;
363
349
  out.push(normalizeMessage({
364
350
  role,
365
351
  content: parsed,
366
352
  tool_calls: parseJson(attrs.tool_calls),
367
- tool_call_id: str(attrs.id) ?? str(attrs.tool_call_id)
353
+ tool_call_id: require_json_fields.asString(attrs.id) ?? require_json_fields.asString(attrs.tool_call_id)
368
354
  }));
369
355
  }
370
356
  return out;
@@ -372,7 +358,7 @@ function messagesFromEvents(events) {
372
358
  function readToolDefinitions(attrs) {
373
359
  const raw = parseJson(attrs["gen_ai.tool.definitions"] ?? attrs["gen_ai.orchestrator.agent.definitions"]);
374
360
  if (!Array.isArray(raw)) return void 0;
375
- return raw.filter((d) => typeof d.name === "string").map((d) => ({
361
+ return raw.filter((d) => require_json_fields.asString(d.name) !== void 0).map((d) => ({
376
362
  name: d.name,
377
363
  description: d.description,
378
364
  type: d.type,
@@ -381,18 +367,18 @@ function readToolDefinitions(attrs) {
381
367
  }
382
368
  function readUsage(attrs) {
383
369
  return {
384
- inputTokens: num(attrs["gen_ai.usage.input_tokens"]) ?? num(attrs["gen_ai.usage.prompt_tokens"]) ?? num(attrs["llm.usage.prompt_tokens"]) ?? num(attrs["ai.usage.inputTokens"]),
385
- outputTokens: num(attrs["gen_ai.usage.output_tokens"]) ?? num(attrs["gen_ai.usage.completion_tokens"]) ?? num(attrs["llm.usage.completion_tokens"]) ?? num(attrs["ai.usage.outputTokens"]),
386
- reasoningOutputTokens: num(attrs["gen_ai.usage.reasoning.output_tokens"]),
387
- cacheReadInputTokens: num(attrs["gen_ai.usage.cache_read.input_tokens"]),
388
- cacheCreationInputTokens: num(attrs["gen_ai.usage.cache_creation.input_tokens"])
370
+ inputTokens: require_json_fields.asNumber(attrs["gen_ai.usage.input_tokens"]) ?? require_json_fields.asNumber(attrs["gen_ai.usage.prompt_tokens"]) ?? require_json_fields.asNumber(attrs["llm.usage.prompt_tokens"]) ?? require_json_fields.asNumber(attrs["ai.usage.inputTokens"]),
371
+ outputTokens: require_json_fields.asNumber(attrs["gen_ai.usage.output_tokens"]) ?? require_json_fields.asNumber(attrs["gen_ai.usage.completion_tokens"]) ?? require_json_fields.asNumber(attrs["llm.usage.completion_tokens"]) ?? require_json_fields.asNumber(attrs["ai.usage.outputTokens"]),
372
+ reasoningOutputTokens: require_json_fields.asNumber(attrs["gen_ai.usage.reasoning.output_tokens"]),
373
+ cacheReadInputTokens: require_json_fields.asNumber(attrs["gen_ai.usage.cache_read.input_tokens"]),
374
+ cacheCreationInputTokens: require_json_fields.asNumber(attrs["gen_ai.usage.cache_creation.input_tokens"])
389
375
  };
390
376
  }
391
377
  function readStreaming(attrs) {
392
- const timeToFirstChunkS = num(attrs["gen_ai.response.time_to_first_chunk"]);
393
- const timeToFinishS = num(attrs["gen_ai.response.time_to_finish"]);
394
- const outputTokensPerSecond = num(attrs["gen_ai.response.output_tokens_per_second"]);
395
- const timePerOutputChunkS = num(attrs["gen_ai.response.time_per_output_chunk"]);
378
+ const timeToFirstChunkS = require_json_fields.asNumber(attrs["gen_ai.response.time_to_first_chunk"]);
379
+ const timeToFinishS = require_json_fields.asNumber(attrs["gen_ai.response.time_to_finish"]);
380
+ const outputTokensPerSecond = require_json_fields.asNumber(attrs["gen_ai.response.output_tokens_per_second"]);
381
+ const timePerOutputChunkS = require_json_fields.asNumber(attrs["gen_ai.response.time_per_output_chunk"]);
396
382
  if (timeToFirstChunkS === void 0 && timeToFinishS === void 0 && outputTokensPerSecond === void 0 && timePerOutputChunkS === void 0) return;
397
383
  return {
398
384
  timeToFirstChunkS,
@@ -402,12 +388,12 @@ function readStreaming(attrs) {
402
388
  };
403
389
  }
404
390
  function readSession(attrs) {
405
- const costUsd = num(attrs["gen_ai.session.cost.usd"]);
406
- const inputTokens = num(attrs["gen_ai.session.input_tokens"]);
407
- const outputTokens = num(attrs["gen_ai.session.output_tokens"]);
408
- const stepCount = num(attrs["gen_ai.session.step.count"]);
409
- const toolCallCount = num(attrs["gen_ai.session.tool_call.count"]);
410
- const errorCount = num(attrs["gen_ai.session.error.count"]);
391
+ const costUsd = require_json_fields.asNumber(attrs["gen_ai.session.cost.usd"]);
392
+ const inputTokens = require_json_fields.asNumber(attrs["gen_ai.session.input_tokens"]);
393
+ const outputTokens = require_json_fields.asNumber(attrs["gen_ai.session.output_tokens"]);
394
+ const stepCount = require_json_fields.asNumber(attrs["gen_ai.session.step.count"]);
395
+ const toolCallCount = require_json_fields.asNumber(attrs["gen_ai.session.tool_call.count"]);
396
+ const errorCount = require_json_fields.asNumber(attrs["gen_ai.session.error.count"]);
411
397
  if (costUsd === void 0 && inputTokens === void 0 && outputTokens === void 0 && stepCount === void 0 && toolCallCount === void 0 && errorCount === void 0) return;
412
398
  return {
413
399
  costUsd,
@@ -419,18 +405,18 @@ function readSession(attrs) {
419
405
  };
420
406
  }
421
407
  function readGuardDetails(a, defaultAction) {
422
- const rule = str(a["gen_ai.guard.rule"]);
408
+ const rule = require_json_fields.asString(a["gen_ai.guard.rule"]);
423
409
  if (!rule) return void 0;
424
410
  return {
425
411
  rule,
426
- action: str(a["gen_ai.guard.action"]) ?? defaultAction,
427
- message: str(a["gen_ai.guard.message"]),
428
- observed: num(a["gen_ai.guard.observed"]),
429
- limit: num(a["gen_ai.guard.limit"])
412
+ action: require_json_fields.asString(a["gen_ai.guard.action"]) ?? defaultAction,
413
+ message: require_json_fields.asString(a["gen_ai.guard.message"]),
414
+ observed: require_json_fields.asNumber(a["gen_ai.guard.observed"]),
415
+ limit: require_json_fields.asNumber(a["gen_ai.guard.limit"])
430
416
  };
431
417
  }
432
418
  function readGuardAndWarnings(attrs, events) {
433
- const stopped = bool(attrs["gen_ai.guard.stopped"]);
419
+ const stopped = require_json_fields.asBoolean(attrs["gen_ai.guard.stopped"]);
434
420
  const warnings = [];
435
421
  let guard = readGuardDetails(attrs);
436
422
  const apply = (next) => {
@@ -539,17 +525,17 @@ const KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
539
525
  ]);
540
526
  function toGenAiSpan(span) {
541
527
  const attrs = span.attributes ?? {};
542
- const provider = normalizeProviderName(str(attrs["gen_ai.provider.name"]) ?? str(attrs["gen_ai.system"]) ?? str(attrs["ai.model.provider"]) ?? "unknown");
543
- const operation = str(attrs["gen_ai.operation.name"]) ?? "chat";
544
- const requestModel = str(attrs["gen_ai.request.model"]) ?? str(attrs["llm.request.model"]) ?? str(attrs["model_name"]) ?? str(attrs["ai.model.id"]) ?? "unknown";
545
- const responseModel = str(attrs["gen_ai.response.model"]) ?? str(attrs["llm.response.model"]) ?? str(attrs["ai.response.model"]);
528
+ const provider = normalizeProviderName(require_json_fields.asString(attrs["gen_ai.provider.name"]) ?? require_json_fields.asString(attrs["gen_ai.system"]) ?? require_json_fields.asString(attrs["ai.model.provider"]) ?? "unknown");
529
+ const operation = require_json_fields.asString(attrs["gen_ai.operation.name"]) ?? "chat";
530
+ const requestModel = require_json_fields.asString(attrs["gen_ai.request.model"]) ?? require_json_fields.asString(attrs["llm.request.model"]) ?? require_json_fields.asString(attrs["model_name"]) ?? require_json_fields.asString(attrs["ai.model.id"]) ?? "unknown";
531
+ const responseModel = require_json_fields.asString(attrs["gen_ai.response.model"]) ?? require_json_fields.asString(attrs["llm.response.model"]) ?? require_json_fields.asString(attrs["ai.response.model"]);
546
532
  const inputMessages = readMessagesAttribute(attrs, "gen_ai.input.messages") ?? [];
547
533
  const outputMessages = readMessagesAttribute(attrs, "gen_ai.output.messages") ?? [];
548
534
  let systemInstructions = [];
549
535
  const rawSystem = parseJson(attrs["gen_ai.system_instructions"]);
550
536
  if (Array.isArray(rawSystem) && rawSystem.length > 0) {
551
- const first = rawSystem[0];
552
- if (first && typeof first === "object" && "role" in first) systemInstructions = rawSystem.map((m) => normalizeMessage(m));
537
+ const first = require_json_fields.asObject(rawSystem[0]);
538
+ if (first && "role" in first) systemInstructions = rawSystem.map((m) => normalizeMessage(m));
553
539
  else systemInstructions = [{
554
540
  role: "system",
555
541
  parts: rawSystem.map((p) => p.type === "text" || p.content !== void 0 ? {
@@ -566,8 +552,8 @@ function toGenAiSpan(span) {
566
552
  ...inputMessages,
567
553
  ...outputMessages
568
554
  ] : messagesFromEvents(span.events);
569
- const inputRef = str(attrs["gen_ai.input.messages.ref"]);
570
- const outputRef = str(attrs["gen_ai.output.messages.ref"]);
555
+ const inputRef = require_json_fields.asString(attrs["gen_ai.input.messages.ref"]);
556
+ const outputRef = require_json_fields.asString(attrs["gen_ai.output.messages.ref"]);
571
557
  const hasInputContent = messages.some((m) => m.role === "system" || m.role === "user");
572
558
  const hasOutputContent = messages.some((m) => m.role === "assistant");
573
559
  if (inputRef && !hasInputContent) messages.push({
@@ -610,14 +596,14 @@ function toGenAiSpan(span) {
610
596
  }
611
597
  }
612
598
  const aiResponseToolCalls = parseJson(attrs["ai.response.toolCalls"]);
613
- const assistantToolCalls = Array.isArray(aiResponseToolCalls) ? aiResponseToolCalls.filter((c) => typeof c.toolName === "string").map((c) => ({
599
+ const assistantToolCalls = Array.isArray(aiResponseToolCalls) ? aiResponseToolCalls.filter((c) => require_json_fields.asString(c.toolName) !== void 0).map((c) => ({
614
600
  id: c.toolCallId,
615
601
  name: c.toolName,
616
602
  arguments: parseJson(c.input ?? c.args) ?? c.input ?? c.args ?? {}
617
603
  })) : void 0;
618
- const aiResponseText = str(attrs["ai.response.text"]);
604
+ const aiResponseText = require_json_fields.asString(attrs["ai.response.text"]);
619
605
  if (aiResponseText || assistantToolCalls && assistantToolCalls.length > 0) {
620
- const finishReason = strArray(attrs["gen_ai.response.finish_reasons"])?.[0] ?? str(attrs["ai.response.finishReason"]);
606
+ const finishReason = require_json_fields.asStringArray(attrs["gen_ai.response.finish_reasons"])?.[0] ?? require_json_fields.asString(attrs["ai.response.finishReason"]);
621
607
  const msg = normalizeMessage({
622
608
  role: "assistant",
623
609
  content: aiResponseText ?? ""
@@ -634,7 +620,7 @@ function toGenAiSpan(span) {
634
620
  if (messages.length === 0) {
635
621
  const legacy = [];
636
622
  for (let i = 0; i < 32; i++) {
637
- const role = str(attrs[`gen_ai.prompt.${i}.role`]) ?? str(attrs[`llm.prompts.${i}.role`]);
623
+ const role = require_json_fields.asString(attrs[`gen_ai.prompt.${i}.role`]) ?? require_json_fields.asString(attrs[`llm.prompts.${i}.role`]);
638
624
  const content = attrs[`gen_ai.prompt.${i}.content`] ?? attrs[`llm.prompts.${i}.content`];
639
625
  if (!role) break;
640
626
  legacy.push(normalizeMessage({
@@ -643,9 +629,9 @@ function toGenAiSpan(span) {
643
629
  }));
644
630
  }
645
631
  for (let i = 0; i < 32; i++) {
646
- const role = str(attrs[`gen_ai.completion.${i}.role`]) ?? str(attrs[`llm.completions.${i}.role`]);
632
+ const role = require_json_fields.asString(attrs[`gen_ai.completion.${i}.role`]) ?? require_json_fields.asString(attrs[`llm.completions.${i}.role`]);
647
633
  const content = attrs[`gen_ai.completion.${i}.content`] ?? attrs[`llm.completions.${i}.content`];
648
- const finishReason = str(attrs[`gen_ai.completion.${i}.finish_reason`]);
634
+ const finishReason = require_json_fields.asString(attrs[`gen_ai.completion.${i}.finish_reason`]);
649
635
  if (!role) break;
650
636
  const msg = normalizeMessage({
651
637
  role,
@@ -692,7 +678,7 @@ function toGenAiSpan(span) {
692
678
  cacheReadInputTokens: usage.cacheReadInputTokens,
693
679
  cacheCreationInputTokens: usage.cacheCreationInputTokens
694
680
  });
695
- const reportedCost = num(attrs["gen_ai.usage.cost.usd"]);
681
+ const reportedCost = require_json_fields.asNumber(attrs["gen_ai.usage.cost.usd"]);
696
682
  const cost = reportedCost !== void 0 ? {
697
683
  currency: "USD",
698
684
  input: tableCost?.input ?? 0,
@@ -705,34 +691,34 @@ function toGenAiSpan(span) {
705
691
  const streaming = readStreaming(attrs);
706
692
  const session = readSession(attrs);
707
693
  const { guard, warnings } = readGuardAndWarnings(attrs, span.events);
708
- const finishReasons = strArray(attrs["gen_ai.response.finish_reasons"]);
709
- const agentName = str(attrs["gen_ai.agent.name"]);
710
- const agentId = str(attrs["gen_ai.agent.id"]);
711
- const agentDescription = str(attrs["gen_ai.agent.description"]);
712
- const toolName = str(attrs["gen_ai.tool.name"]);
713
- const toolCallId = str(attrs["gen_ai.tool.call.id"]);
714
- const handoffFrom = str(attrs["gen_ai.handoff.from_agent"]);
715
- const handoffTo = str(attrs["gen_ai.handoff.to_agent"]);
716
- const guardrailName = str(attrs["gen_ai.guardrail.name"]);
717
- const guardrailTriggered = bool(attrs["gen_ai.guardrail.triggered"]);
718
- const evalName = str(attrs["gen_ai.evaluation.name"]);
719
- const evalScoreValue = num(attrs["gen_ai.evaluation.score.value"]);
720
- const evalScoreLabel = str(attrs["gen_ai.evaluation.score.label"]);
721
- const evalExplanation = str(attrs["gen_ai.evaluation.explanation"]);
722
- const audioIn = str(attrs["gen_ai.audio.input.format"]);
723
- const audioOut = str(attrs["gen_ai.audio.output.format"]);
724
- const speechVoice = str(attrs["gen_ai.speech.voice"]);
725
- const speechInputText = str(attrs["gen_ai.speech.input_text"]);
726
- const transcriptionText = str(attrs["gen_ai.transcription.text"]);
727
- const embeddingDims = num(attrs["gen_ai.embeddings.dimension.count"]);
728
- const consentOutcome = str(attrs["agent.consent.outcome"]);
729
- const policyDecision = str(attrs["policy.decision"]);
730
- const injectionVerdict = str(attrs["mcp.security.injection.verdict"]);
731
- const actionRiskClass = str(attrs["agent.action.risk_class"]);
732
- const inputProvenance = str(attrs["agent.input.provenance"]);
733
- const planStepIndex = num(attrs["agent.plan.step_index"]);
734
- const securityEvent = str(attrs["security.event"]);
735
- const guardStoppedAttr = bool(attrs["gen_ai.guard.stopped"]);
694
+ const finishReasons = require_json_fields.asStringArray(attrs["gen_ai.response.finish_reasons"]);
695
+ const agentName = require_json_fields.asString(attrs["gen_ai.agent.name"]);
696
+ const agentId = require_json_fields.asString(attrs["gen_ai.agent.id"]);
697
+ const agentDescription = require_json_fields.asString(attrs["gen_ai.agent.description"]);
698
+ const toolName = require_json_fields.asString(attrs["gen_ai.tool.name"]);
699
+ const toolCallId = require_json_fields.asString(attrs["gen_ai.tool.call.id"]);
700
+ const handoffFrom = require_json_fields.asString(attrs["gen_ai.handoff.from_agent"]);
701
+ const handoffTo = require_json_fields.asString(attrs["gen_ai.handoff.to_agent"]);
702
+ const guardrailName = require_json_fields.asString(attrs["gen_ai.guardrail.name"]);
703
+ const guardrailTriggered = require_json_fields.asBoolean(attrs["gen_ai.guardrail.triggered"]);
704
+ const evalName = require_json_fields.asString(attrs["gen_ai.evaluation.name"]);
705
+ const evalScoreValue = require_json_fields.asNumber(attrs["gen_ai.evaluation.score.value"]);
706
+ const evalScoreLabel = require_json_fields.asString(attrs["gen_ai.evaluation.score.label"]);
707
+ const evalExplanation = require_json_fields.asString(attrs["gen_ai.evaluation.explanation"]);
708
+ const audioIn = require_json_fields.asString(attrs["gen_ai.audio.input.format"]);
709
+ const audioOut = require_json_fields.asString(attrs["gen_ai.audio.output.format"]);
710
+ const speechVoice = require_json_fields.asString(attrs["gen_ai.speech.voice"]);
711
+ const speechInputText = require_json_fields.asString(attrs["gen_ai.speech.input_text"]);
712
+ const transcriptionText = require_json_fields.asString(attrs["gen_ai.transcription.text"]);
713
+ const embeddingDims = require_json_fields.asNumber(attrs["gen_ai.embeddings.dimension.count"]);
714
+ const consentOutcome = require_json_fields.asString(attrs["agent.consent.outcome"]);
715
+ const policyDecision = require_json_fields.asString(attrs["policy.decision"]);
716
+ const injectionVerdict = require_json_fields.asString(attrs["mcp.security.injection.verdict"]);
717
+ const actionRiskClass = require_json_fields.asString(attrs["agent.action.risk_class"]);
718
+ const inputProvenance = require_json_fields.asString(attrs["agent.input.provenance"]);
719
+ const planStepIndex = require_json_fields.asNumber(attrs["agent.plan.step_index"]);
720
+ const securityEvent = require_json_fields.asString(attrs["security.event"]);
721
+ const guardStoppedAttr = require_json_fields.asBoolean(attrs["gen_ai.guard.stopped"]);
736
722
  const agentSecurity = consentOutcome || policyDecision || injectionVerdict || actionRiskClass || inputProvenance || planStepIndex !== void 0 || securityEvent || guardStoppedAttr !== void 0 || guard?.stopped ? {
737
723
  ...consentOutcome && { consentOutcome },
738
724
  ...policyDecision && { policyDecision },
@@ -764,15 +750,15 @@ function toGenAiSpan(span) {
764
750
  requestModel,
765
751
  responseModel,
766
752
  params: {
767
- temperature: num(attrs["gen_ai.request.temperature"]),
768
- topP: num(attrs["gen_ai.request.top_p"]),
769
- topK: num(attrs["gen_ai.request.top_k"]),
770
- maxTokens: num(attrs["gen_ai.request.max_tokens"]),
771
- stopSequences: strArray(attrs["gen_ai.request.stop_sequences"]),
772
- seed: num(attrs["gen_ai.request.seed"]),
773
- frequencyPenalty: num(attrs["gen_ai.request.frequency_penalty"]),
774
- presencePenalty: num(attrs["gen_ai.request.presence_penalty"]),
775
- choiceCount: num(attrs["gen_ai.request.choice.count"])
753
+ temperature: require_json_fields.asNumber(attrs["gen_ai.request.temperature"]),
754
+ topP: require_json_fields.asNumber(attrs["gen_ai.request.top_p"]),
755
+ topK: require_json_fields.asNumber(attrs["gen_ai.request.top_k"]),
756
+ maxTokens: require_json_fields.asNumber(attrs["gen_ai.request.max_tokens"]),
757
+ stopSequences: require_json_fields.asStringArray(attrs["gen_ai.request.stop_sequences"]),
758
+ seed: require_json_fields.asNumber(attrs["gen_ai.request.seed"]),
759
+ frequencyPenalty: require_json_fields.asNumber(attrs["gen_ai.request.frequency_penalty"]),
760
+ presencePenalty: require_json_fields.asNumber(attrs["gen_ai.request.presence_penalty"]),
761
+ choiceCount: require_json_fields.asNumber(attrs["gen_ai.request.choice.count"])
776
762
  },
777
763
  messages,
778
764
  toolDefinitions: readToolDefinitions(attrs),
@@ -784,7 +770,7 @@ function toGenAiSpan(span) {
784
770
  session,
785
771
  warnings,
786
772
  finishReasons,
787
- responseId: str(attrs["gen_ai.response.id"]),
773
+ responseId: require_json_fields.asString(attrs["gen_ai.response.id"]),
788
774
  agent: agentId || agentName || agentDescription ? {
789
775
  id: agentId,
790
776
  name: agentName,
@@ -802,7 +788,7 @@ function toGenAiSpan(span) {
802
788
  name: guardrailName,
803
789
  triggered: guardrailTriggered
804
790
  } : void 0,
805
- conversationId: str(attrs["gen_ai.conversation.id"]),
791
+ conversationId: require_json_fields.asString(attrs["gen_ai.conversation.id"]),
806
792
  agentSecurity,
807
793
  evaluation: evalName || evalScoreValue !== void 0 || evalScoreLabel || evalExplanation ? {
808
794
  name: evalName,
@@ -819,12 +805,12 @@ function toGenAiSpan(span) {
819
805
  embeddingDimensions: embeddingDims
820
806
  } : void 0,
821
807
  extras: {
822
- openaiServiceTier: str(attrs["gen_ai.openai.request.service_tier"]) || str(attrs["gen_ai.openai.response.service_tier"]) || str(attrs["openai.request.service_tier"]) || str(attrs["openai.response.service_tier"]) ? {
823
- request: str(attrs["gen_ai.openai.request.service_tier"]) ?? str(attrs["openai.request.service_tier"]),
824
- response: str(attrs["gen_ai.openai.response.service_tier"]) ?? str(attrs["openai.response.service_tier"])
808
+ openaiServiceTier: require_json_fields.asString(attrs["gen_ai.openai.request.service_tier"]) || require_json_fields.asString(attrs["gen_ai.openai.response.service_tier"]) || require_json_fields.asString(attrs["openai.request.service_tier"]) || require_json_fields.asString(attrs["openai.response.service_tier"]) ? {
809
+ request: require_json_fields.asString(attrs["gen_ai.openai.request.service_tier"]) ?? require_json_fields.asString(attrs["openai.request.service_tier"]),
810
+ response: require_json_fields.asString(attrs["gen_ai.openai.response.service_tier"]) ?? require_json_fields.asString(attrs["openai.response.service_tier"])
825
811
  } : void 0,
826
- openaiSystemFingerprint: str(attrs["gen_ai.openai.response.system_fingerprint"]) ?? str(attrs["openai.response.system_fingerprint"]),
827
- openaiResponseFormat: str(attrs["gen_ai.openai.request.response_format"]) ?? str(attrs["openai.request.response_format"]),
812
+ openaiSystemFingerprint: require_json_fields.asString(attrs["gen_ai.openai.response.system_fingerprint"]) ?? require_json_fields.asString(attrs["openai.response.system_fingerprint"]),
813
+ openaiResponseFormat: require_json_fields.asString(attrs["gen_ai.openai.request.response_format"]) ?? require_json_fields.asString(attrs["openai.request.response_format"]),
828
814
  raw
829
815
  }
830
816
  };