agent-inspect 1.5.0 → 1.7.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 (87) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +14 -4
  3. package/docs/ADAPTER-CONFORMANCE.md +35 -0
  4. package/docs/ADAPTERS.md +79 -1
  5. package/docs/API.md +184 -10
  6. package/docs/ARCHITECTURE.md +4 -0
  7. package/docs/CLI.md +41 -12
  8. package/docs/KNOWN-ISSUES.md +11 -1
  9. package/docs/LIMITATIONS.md +19 -2
  10. package/docs/SCHEMA.md +17 -7
  11. package/package.json +23 -2
  12. package/packages/cli/dist/index.cjs +2449 -157
  13. package/packages/cli/dist/index.cjs.map +1 -1
  14. package/packages/cli/dist/index.mjs +2450 -158
  15. package/packages/cli/dist/index.mjs.map +1 -1
  16. package/packages/core/dist/advanced.cjs +839 -18
  17. package/packages/core/dist/advanced.cjs.map +1 -1
  18. package/packages/core/dist/advanced.d.cts +98 -3
  19. package/packages/core/dist/advanced.d.ts +98 -3
  20. package/packages/core/dist/advanced.mjs +7 -4
  21. package/packages/core/dist/chunk-57S5D6HR.mjs +655 -0
  22. package/packages/core/dist/chunk-57S5D6HR.mjs.map +1 -0
  23. package/packages/core/dist/chunk-6QSLZCBJ.mjs +743 -0
  24. package/packages/core/dist/chunk-6QSLZCBJ.mjs.map +1 -0
  25. package/packages/core/dist/chunk-6SZPTECC.mjs +342 -0
  26. package/packages/core/dist/chunk-6SZPTECC.mjs.map +1 -0
  27. package/packages/core/dist/{chunk-QX3ZMPUF.mjs → chunk-74XZ6N7Q.mjs} +13 -55
  28. package/packages/core/dist/chunk-74XZ6N7Q.mjs.map +1 -0
  29. package/packages/core/dist/{chunk-QPAU2TPA.mjs → chunk-HR7G62IE.mjs} +4 -4
  30. package/packages/core/dist/{chunk-QPAU2TPA.mjs.map → chunk-HR7G62IE.mjs.map} +1 -1
  31. package/packages/core/dist/chunk-S4YWKV4G.mjs +48 -0
  32. package/packages/core/dist/chunk-S4YWKV4G.mjs.map +1 -0
  33. package/packages/core/dist/chunk-TFLPUZ56.mjs +1571 -0
  34. package/packages/core/dist/chunk-TFLPUZ56.mjs.map +1 -0
  35. package/packages/core/dist/{chunk-Q6EPNB3V.mjs → chunk-TZISEVLQ.mjs} +34 -183
  36. package/packages/core/dist/chunk-TZISEVLQ.mjs.map +1 -0
  37. package/packages/core/dist/chunk-U2BGPESY.mjs +150 -0
  38. package/packages/core/dist/chunk-U2BGPESY.mjs.map +1 -0
  39. package/packages/core/dist/chunk-VTIB5MDK.mjs +304 -0
  40. package/packages/core/dist/chunk-VTIB5MDK.mjs.map +1 -0
  41. package/packages/core/dist/{chunk-5EMIZZXD.mjs → chunk-Y56BPA3B.mjs} +87 -4
  42. package/packages/core/dist/chunk-Y56BPA3B.mjs.map +1 -0
  43. package/packages/core/dist/diff.d.cts +3 -2
  44. package/packages/core/dist/diff.d.ts +3 -2
  45. package/packages/core/dist/exporters.cjs.map +1 -1
  46. package/packages/core/dist/exporters.d.cts +3 -2
  47. package/packages/core/dist/exporters.d.ts +3 -2
  48. package/packages/core/dist/exporters.mjs +2 -2
  49. package/packages/core/dist/index.cjs +2975 -229
  50. package/packages/core/dist/index.cjs.map +1 -1
  51. package/packages/core/dist/index.d.cts +27 -6
  52. package/packages/core/dist/index.d.ts +27 -6
  53. package/packages/core/dist/index.mjs +113 -60
  54. package/packages/core/dist/index.mjs.map +1 -1
  55. package/packages/core/dist/{log-config-BzGmDYum.d.cts → inspect-event-Des4JDHo.d.cts} +1 -31
  56. package/packages/core/dist/{log-config-BzGmDYum.d.ts → inspect-event-Des4JDHo.d.ts} +1 -31
  57. package/packages/core/dist/log-config-BnH8Ykcb.d.cts +33 -0
  58. package/packages/core/dist/log-config-C1GcJPIM.d.ts +33 -0
  59. package/packages/core/dist/logs.d.cts +3 -2
  60. package/packages/core/dist/logs.d.ts +3 -2
  61. package/packages/core/dist/logs.mjs +3 -3
  62. package/packages/core/dist/persisted-inspect-event-0kaRADsp.d.cts +56 -0
  63. package/packages/core/dist/persisted-inspect-event-DiFto0K2.d.ts +56 -0
  64. package/packages/core/dist/persisted.cjs +38 -40
  65. package/packages/core/dist/persisted.cjs.map +1 -1
  66. package/packages/core/dist/persisted.d.cts +6 -55
  67. package/packages/core/dist/persisted.d.ts +6 -55
  68. package/packages/core/dist/persisted.mjs +4 -2
  69. package/packages/core/dist/readers.cjs +2590 -0
  70. package/packages/core/dist/readers.cjs.map +1 -0
  71. package/packages/core/dist/readers.d.cts +80 -0
  72. package/packages/core/dist/readers.d.ts +80 -0
  73. package/packages/core/dist/readers.mjs +9 -0
  74. package/packages/core/dist/readers.mjs.map +1 -0
  75. package/packages/core/dist/{types-CNbheSdk.d.cts → types-DB8jB6Jg.d.cts} +7 -1
  76. package/packages/core/dist/{types-Bkt7LS01.d.ts → types-tSix7tfv.d.ts} +7 -1
  77. package/packages/core/dist/writers.cjs +997 -0
  78. package/packages/core/dist/writers.cjs.map +1 -0
  79. package/packages/core/dist/writers.d.cts +62 -0
  80. package/packages/core/dist/writers.d.ts +62 -0
  81. package/packages/core/dist/writers.mjs +9 -0
  82. package/packages/core/dist/writers.mjs.map +1 -0
  83. package/packages/core/dist/chunk-5EMIZZXD.mjs.map +0 -1
  84. package/packages/core/dist/chunk-Q6EPNB3V.mjs.map +0 -1
  85. package/packages/core/dist/chunk-QX3ZMPUF.mjs.map +0 -1
  86. package/packages/core/dist/chunk-XDBND27A.mjs +0 -975
  87. package/packages/core/dist/chunk-XDBND27A.mjs.map +0 -1
@@ -3,7 +3,7 @@ import { realpathSync, createReadStream } from 'fs';
3
3
  import path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { Command, Option } from 'commander';
6
- import { unlink, stat, mkdir, writeFile, appendFile, readdir, readFile, open } from 'fs/promises';
6
+ import { unlink, stat, mkdir, writeFile, appendFile, readdir, readFile, access, open } from 'fs/promises';
7
7
  import crypto, { webcrypto } from 'crypto';
8
8
  import os from 'os';
9
9
  import { AsyncLocalStorage } from 'async_hooks';
@@ -11,6 +11,9 @@ import { createInterface } from 'readline';
11
11
  import process2, { stdin, stdout } from 'process';
12
12
  import tty from 'tty';
13
13
 
14
+ // package.json
15
+ var version = "1.7.0";
16
+
14
17
  // packages/core/src/types.ts
15
18
  var STEP_TYPES = [
16
19
  "run",
@@ -133,6 +136,7 @@ function isPersistedTokenUsage(value) {
133
136
  if (!isOptionalNonNegativeNumber(value.input)) return false;
134
137
  if (!isOptionalNonNegativeNumber(value.output)) return false;
135
138
  if (!isOptionalNonNegativeNumber(value.total)) return false;
139
+ if (!isOptionalNonNegativeNumber(value.cached)) return false;
136
140
  return true;
137
141
  }
138
142
  function isPersistedTraceContext(value) {
@@ -178,8 +182,405 @@ function isPersistedInspectEvent(value) {
178
182
  return true;
179
183
  }
180
184
 
181
- // packages/core/src/persisted/to-trace-event.ts
185
+ // packages/core/src/correlation-metadata.ts
186
+ var TRACE_CORRELATION_KEYS = [
187
+ "correlationId",
188
+ "requestId",
189
+ "decisionId",
190
+ "groupId"
191
+ ];
192
+ function isNonEmptyString2(value) {
193
+ return typeof value === "string" && value.length > 0;
194
+ }
195
+ function extractCorrelationMetadata(record) {
196
+ if (!record) {
197
+ return void 0;
198
+ }
199
+ const out = {};
200
+ let found = false;
201
+ for (const key of TRACE_CORRELATION_KEYS) {
202
+ const value = record[key];
203
+ if (isNonEmptyString2(value)) {
204
+ out[key] = value;
205
+ found = true;
206
+ }
207
+ }
208
+ return found ? out : void 0;
209
+ }
210
+
211
+ // packages/core/src/persisted/token-usage.ts
212
+ function isRecord3(value) {
213
+ return typeof value === "object" && value !== null && !Array.isArray(value);
214
+ }
215
+ function nonNegativeFinite(value) {
216
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
217
+ }
218
+ function normalizeTokenUsage(value) {
219
+ if (!isRecord3(value)) return void 0;
220
+ const input3 = nonNegativeFinite(value.input);
221
+ const output2 = nonNegativeFinite(value.output);
222
+ const suppliedTotal = nonNegativeFinite(value.total);
223
+ const cached = nonNegativeFinite(value.cached);
224
+ const derivedTotal = input3 !== void 0 && output2 !== void 0 && Number.isFinite(input3 + output2) ? input3 + output2 : void 0;
225
+ const total = suppliedTotal ?? derivedTotal;
226
+ if (input3 === void 0 && output2 === void 0 && total === void 0 && cached === void 0) {
227
+ return void 0;
228
+ }
229
+ return {
230
+ ...input3 !== void 0 ? { input: input3 } : {},
231
+ ...output2 !== void 0 ? { output: output2 } : {},
232
+ ...total !== void 0 ? { total } : {},
233
+ ...cached !== void 0 ? { cached } : {}
234
+ };
235
+ }
236
+
237
+ // packages/core/src/persisted/from-trace-event.ts
238
+ function sanitizeIdPart(value) {
239
+ return value.replace(/[^a-zA-Z0-9_-]/g, "_");
240
+ }
241
+ function nodeIdForEvent(event) {
242
+ switch (event.event) {
243
+ case "run_started":
244
+ case "run_completed":
245
+ return event.runId;
246
+ case "step_started":
247
+ case "step_completed":
248
+ return event.stepId;
249
+ default:
250
+ return "unknown";
251
+ }
252
+ }
253
+ function createPersistedEventId(event, eventIndex) {
254
+ const runId = sanitizeIdPart(event.runId);
255
+ const ev = sanitizeIdPart(event.event);
256
+ const node = sanitizeIdPart(nodeIdForEvent(event));
257
+ return `manual:${runId}:${ev}:${node}:${eventIndex}`;
258
+ }
259
+ function toIsoTimestamp(ms) {
260
+ if (typeof ms !== "number" || !Number.isFinite(ms)) {
261
+ return { iso: (/* @__PURE__ */ new Date(0)).toISOString(), invalidTimestamp: true };
262
+ }
263
+ return { iso: new Date(ms).toISOString(), invalidTimestamp: false };
264
+ }
265
+ function buildSource(options) {
266
+ return {
267
+ type: "manual",
268
+ name: options?.sourceName ?? "trace-event",
269
+ version: options?.sourceVersion ?? "0.1"
270
+ };
271
+ }
272
+ function mapStepTypeToInspectKind(type) {
273
+ switch (type) {
274
+ case "run":
275
+ return "RUN";
276
+ case "llm":
277
+ return "LLM";
278
+ case "tool":
279
+ return "TOOL";
280
+ case "decision":
281
+ return "DECISION";
282
+ case "logic":
283
+ case "state":
284
+ case "custom":
285
+ return "LOGIC";
286
+ default:
287
+ return "LOGIC";
288
+ }
289
+ }
290
+ function mapRunOrStepStatus(status) {
291
+ return status === "success" ? "ok" : "error";
292
+ }
293
+ function mapErrorInfo(error) {
294
+ if (!error?.message) {
295
+ return {};
296
+ }
297
+ const out = {
298
+ persisted: {
299
+ message: error.message,
300
+ name: "Error"
301
+ }
302
+ };
303
+ if (typeof error.stack === "string" && error.stack.length > 0) {
304
+ out.errorStack = error.stack;
305
+ }
306
+ return out;
307
+ }
308
+ function mapTokenUsageFromMetadata(metadata) {
309
+ return normalizeTokenUsage(metadata?.tokens);
310
+ }
311
+ function compactAttributes(entries) {
312
+ const out = {};
313
+ for (const [key, value] of Object.entries(entries)) {
314
+ if (value !== void 0) {
315
+ out[key] = value;
316
+ }
317
+ }
318
+ return Object.keys(out).length > 0 ? out : void 0;
319
+ }
320
+ function traceEventToPersistedInspectEvent(event, options) {
321
+ const eventIndex = options?.eventIndex ?? 0;
322
+ const eventId = createPersistedEventId(event, eventIndex);
323
+ const source = buildSource(options);
324
+ const tsMain = toIsoTimestamp(event.timestamp);
325
+ switch (event.event) {
326
+ case "run_started": {
327
+ const tsStart = toIsoTimestamp(event.startTime);
328
+ const correlation = extractCorrelationMetadata(event.metadata);
329
+ const attributes = compactAttributes({
330
+ legacyEvent: "run_started",
331
+ metadata: event.metadata !== void 0 ? { ...event.metadata } : void 0,
332
+ correlationId: correlation?.correlationId,
333
+ requestId: correlation?.requestId,
334
+ decisionId: correlation?.decisionId,
335
+ groupId: correlation?.groupId,
336
+ invalidTimestamp: tsMain.invalidTimestamp || tsStart.invalidTimestamp ? true : void 0
337
+ });
338
+ return {
339
+ schemaVersion: "0.2",
340
+ eventId,
341
+ runId: event.runId,
342
+ kind: "RUN",
343
+ name: event.name,
344
+ status: "running",
345
+ timestamp: tsMain.iso,
346
+ startedAt: tsStart.iso,
347
+ confidence: "explicit",
348
+ source,
349
+ attributes
350
+ };
351
+ }
352
+ case "run_completed": {
353
+ const tsEnd = toIsoTimestamp(event.endTime);
354
+ const { persisted: error, errorStack } = mapErrorInfo(event.error);
355
+ const attributes = compactAttributes({
356
+ legacyEvent: "run_completed",
357
+ errorStack,
358
+ invalidTimestamp: tsMain.invalidTimestamp || tsEnd.invalidTimestamp ? true : void 0
359
+ });
360
+ return {
361
+ schemaVersion: "0.2",
362
+ eventId,
363
+ runId: event.runId,
364
+ kind: "RUN",
365
+ name: "run",
366
+ status: mapRunOrStepStatus(event.status),
367
+ timestamp: tsMain.iso,
368
+ endedAt: tsEnd.iso,
369
+ durationMs: event.durationMs,
370
+ confidence: "explicit",
371
+ source,
372
+ attributes,
373
+ error
374
+ };
375
+ }
376
+ case "step_started": {
377
+ const tsStart = toIsoTimestamp(event.startTime);
378
+ const tokenUsage = mapTokenUsageFromMetadata(event.metadata);
379
+ const attributes = compactAttributes({
380
+ legacyEvent: "step_started",
381
+ stepId: event.stepId,
382
+ stepType: event.type,
383
+ metadata: event.metadata !== void 0 ? { ...event.metadata } : void 0,
384
+ invalidTimestamp: tsMain.invalidTimestamp || tsStart.invalidTimestamp ? true : void 0
385
+ });
386
+ const out = {
387
+ schemaVersion: "0.2",
388
+ eventId,
389
+ runId: event.runId,
390
+ kind: mapStepTypeToInspectKind(event.type),
391
+ name: event.name,
392
+ status: "running",
393
+ timestamp: tsMain.iso,
394
+ startedAt: tsStart.iso,
395
+ confidence: "explicit",
396
+ source,
397
+ attributes
398
+ };
399
+ if (event.parentId !== void 0) {
400
+ out.parentId = event.parentId;
401
+ }
402
+ if (tokenUsage !== void 0) {
403
+ out.tokenUsage = tokenUsage;
404
+ }
405
+ return out;
406
+ }
407
+ case "step_completed": {
408
+ const tsEnd = toIsoTimestamp(event.endTime);
409
+ const { persisted: error, errorStack } = mapErrorInfo(event.error);
410
+ const attributes = compactAttributes({
411
+ legacyEvent: "step_completed",
412
+ stepId: event.stepId,
413
+ errorStack,
414
+ invalidTimestamp: tsMain.invalidTimestamp || tsEnd.invalidTimestamp ? true : void 0
415
+ });
416
+ return {
417
+ schemaVersion: "0.2",
418
+ eventId,
419
+ runId: event.runId,
420
+ kind: "LOGIC",
421
+ name: event.stepId,
422
+ status: mapRunOrStepStatus(event.status),
423
+ timestamp: tsMain.iso,
424
+ endedAt: tsEnd.iso,
425
+ durationMs: event.durationMs,
426
+ confidence: "explicit",
427
+ source,
428
+ attributes,
429
+ error
430
+ };
431
+ }
432
+ default: {
433
+ const _exhaustive = event;
434
+ throw new Error(`Unsupported trace event: ${_exhaustive.event}`);
435
+ }
436
+ }
437
+ }
438
+ function traceEventsToPersistedInspectEvents(events, options) {
439
+ return events.map(
440
+ (event, index) => traceEventToPersistedInspectEvent(event, { ...options, eventIndex: index })
441
+ );
442
+ }
443
+
444
+ // packages/core/src/persisted/to-inspect-event.ts
445
+ function compactAttributes2(entries) {
446
+ const out = {};
447
+ for (const [key, value] of Object.entries(entries)) {
448
+ if (value !== void 0) {
449
+ out[key] = value;
450
+ }
451
+ }
452
+ return Object.keys(out).length > 0 ? out : void 0;
453
+ }
182
454
  function parseIsoToMs(iso) {
455
+ const parsed = Date.parse(iso);
456
+ if (!Number.isFinite(parsed)) {
457
+ return { ms: 0, invalidTimestamp: true };
458
+ }
459
+ return { ms: parsed, invalidTimestamp: false };
460
+ }
461
+ function mapPersistedSourceToInspect(event) {
462
+ const attrs = event.attributes ?? {};
463
+ const sourceName = event.source.name;
464
+ if (sourceName === "pino") {
465
+ return {
466
+ type: "pino",
467
+ file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
468
+ line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
469
+ };
470
+ }
471
+ if (sourceName === "winston") {
472
+ return {
473
+ type: "winston",
474
+ file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
475
+ line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
476
+ };
477
+ }
478
+ const mapType = (t) => {
479
+ switch (t) {
480
+ case "manual":
481
+ return "manual";
482
+ case "json-log":
483
+ return "json-log";
484
+ case "log4js":
485
+ return "log4js";
486
+ case "adapter":
487
+ case "ai-sdk":
488
+ case "otel":
489
+ return "adapter";
490
+ default:
491
+ return "json-log";
492
+ }
493
+ };
494
+ return {
495
+ type: mapType(event.source.type),
496
+ file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
497
+ line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
498
+ };
499
+ }
500
+ function buildInspectAttributes(event) {
501
+ const attrs = event.attributes !== void 0 ? { ...event.attributes } : {};
502
+ if (event.inputSummary !== void 0) {
503
+ attrs.inputSummary = event.inputSummary;
504
+ }
505
+ if (event.outputSummary !== void 0) {
506
+ attrs.outputSummary = event.outputSummary;
507
+ }
508
+ if (event.error) {
509
+ if (event.error.name !== void 0) {
510
+ attrs.errorName = event.error.name;
511
+ }
512
+ attrs.errorMessage = event.error.message;
513
+ if (event.error.code !== void 0) {
514
+ attrs.errorCode = event.error.code;
515
+ }
516
+ }
517
+ if (event.tokenUsage) {
518
+ attrs.tokens = { ...event.tokenUsage };
519
+ }
520
+ if (event.source.type === "ai-sdk" || event.source.type === "otel") {
521
+ attrs.originalSourceType = event.source.type;
522
+ }
523
+ if (event.source.name !== void 0) {
524
+ attrs.sourceName = event.source.name;
525
+ }
526
+ if (event.source.version !== void 0) {
527
+ attrs.sourceVersion = event.source.version;
528
+ }
529
+ return attrs;
530
+ }
531
+ function persistedInspectEventToInspectEvent(event) {
532
+ if (!isPersistedInspectEvent(event)) {
533
+ throw new Error("Invalid PersistedInspectEvent: failed isPersistedInspectEvent");
534
+ }
535
+ const ts = parseIsoToMs(event.timestamp);
536
+ const attrs = buildInspectAttributes(event);
537
+ if (ts.invalidTimestamp) {
538
+ attrs.invalidTimestamp = true;
539
+ }
540
+ let status;
541
+ if (event.status === "running" || event.status === "ok" || event.status === "error") {
542
+ status = event.status;
543
+ } else if (event.status === "unknown") {
544
+ attrs.persistedStatus = "unknown";
545
+ }
546
+ const out = {
547
+ eventId: event.eventId,
548
+ runId: event.runId,
549
+ name: event.name,
550
+ kind: event.kind,
551
+ timestamp: ts.ms,
552
+ confidence: event.confidence,
553
+ source: mapPersistedSourceToInspect(event),
554
+ attributes: compactAttributes2(attrs)
555
+ };
556
+ if (event.parentId !== void 0) {
557
+ out.parentId = event.parentId;
558
+ }
559
+ if (status !== void 0) {
560
+ out.status = status;
561
+ }
562
+ if (event.durationMs !== void 0 && Number.isFinite(event.durationMs) && event.durationMs >= 0) {
563
+ out.durationMs = event.durationMs;
564
+ }
565
+ return out;
566
+ }
567
+ function persistedInspectEventsToInspectEvents(events, options) {
568
+ const skipInvalid = options?.skipInvalid === true;
569
+ const out = [];
570
+ for (const event of events) {
571
+ if (!isPersistedInspectEvent(event)) {
572
+ if (skipInvalid) {
573
+ continue;
574
+ }
575
+ throw new Error("Invalid PersistedInspectEvent: failed isPersistedInspectEvent");
576
+ }
577
+ out.push(persistedInspectEventToInspectEvent(event));
578
+ }
579
+ return out;
580
+ }
581
+
582
+ // packages/core/src/persisted/to-trace-event.ts
583
+ function parseIsoToMs2(iso) {
183
584
  const parsed = Date.parse(iso);
184
585
  return Number.isFinite(parsed) ? parsed : 0;
185
586
  }
@@ -221,10 +622,10 @@ function mapPersistedStatusToRunStatus(status) {
221
622
  return void 0;
222
623
  }
223
624
  }
224
- function mapPersistedError(error) {
625
+ function mapPersistedError(error, attributes) {
225
626
  if (!error?.message) return void 0;
226
627
  const out = { message: error.message };
227
- const stack = typeof error.name === "string" && error.name.length > 0 ? error.name : void 0;
628
+ const stack = typeof attributes?.errorStack === "string" && attributes.errorStack.length > 0 ? attributes.errorStack : void 0;
228
629
  if (stack) {
229
630
  out.stack = stack;
230
631
  }
@@ -238,7 +639,9 @@ function mapTokenUsageToMetadata(tokenUsage, attributes) {
238
639
  if (tokenUsage) {
239
640
  metadata.tokens = {
240
641
  ...tokenUsage.input !== void 0 ? { input: tokenUsage.input } : {},
241
- ...tokenUsage.output !== void 0 ? { output: tokenUsage.output } : {}
642
+ ...tokenUsage.output !== void 0 ? { output: tokenUsage.output } : {},
643
+ ...tokenUsage.total !== void 0 ? { total: tokenUsage.total } : {},
644
+ ...tokenUsage.cached !== void 0 ? { cached: tokenUsage.cached } : {}
242
645
  };
243
646
  }
244
647
  return Object.keys(metadata).length > 0 ? metadata : void 0;
@@ -277,9 +680,9 @@ function resolveStepType(event) {
277
680
  return mapInspectKindToStepType(event.kind);
278
681
  }
279
682
  function resolveTimes(event) {
280
- const timestamp = parseIsoToMs(event.timestamp);
281
- const startTime = event.startedAt !== void 0 ? parseIsoToMs(event.startedAt) : timestamp;
282
- let endTime = event.endedAt !== void 0 ? parseIsoToMs(event.endedAt) : timestamp;
683
+ const timestamp = parseIsoToMs2(event.timestamp);
684
+ const startTime = event.startedAt !== void 0 ? parseIsoToMs2(event.startedAt) : timestamp;
685
+ let endTime = event.endedAt !== void 0 ? parseIsoToMs2(event.endedAt) : timestamp;
283
686
  if (event.durationMs !== void 0 && Number.isFinite(event.durationMs) && event.durationMs >= 0 && event.endedAt === void 0) {
284
687
  endTime = startTime + event.durationMs;
285
688
  }
@@ -311,7 +714,7 @@ function fromLegacyRunCompleted(event) {
311
714
  endTime,
312
715
  durationMs: event.durationMs ?? Math.max(0, endTime - timestamp)
313
716
  };
314
- const error = mapPersistedError(event.error);
717
+ const error = mapPersistedError(event.error, event.attributes);
315
718
  if (error) out.error = error;
316
719
  return out;
317
720
  }
@@ -345,7 +748,7 @@ function fromLegacyStepCompleted(event) {
345
748
  endTime,
346
749
  durationMs: event.durationMs ?? Math.max(0, endTime - timestamp)
347
750
  };
348
- const error = mapPersistedError(event.error);
751
+ const error = mapPersistedError(event.error, event.attributes);
349
752
  if (error) out.error = error;
350
753
  return out;
351
754
  }
@@ -376,7 +779,7 @@ function fromNativeRun(event) {
376
779
  endTime,
377
780
  durationMs: event.durationMs ?? Math.max(0, endTime - startTime)
378
781
  };
379
- const error = mapPersistedError(event.error);
782
+ const error = mapPersistedError(event.error, event.attributes);
380
783
  if (error) completed.error = error;
381
784
  out.push(completed);
382
785
  }
@@ -418,7 +821,7 @@ function fromNativeStep(event) {
418
821
  endTime,
419
822
  durationMs: event.durationMs ?? Math.max(0, endTime - startTime)
420
823
  };
421
- const error = mapPersistedError(event.error);
824
+ const error = mapPersistedError(event.error, event.attributes);
422
825
  if (error) completed.error = error;
423
826
  out.push(completed);
424
827
  }
@@ -530,7 +933,15 @@ var TreeBuilder = class {
530
933
  return out;
531
934
  }
532
935
  };
533
- function isRecord3(v) {
936
+
937
+ // packages/core/src/persisted/tree-bridge.ts
938
+ function persistedInspectEventsToRunTrees(events, options) {
939
+ const inspectEvents = persistedInspectEventsToInspectEvents(events, {
940
+ skipInvalid: options?.skipInvalid
941
+ });
942
+ return new TreeBuilder().build(inspectEvents);
943
+ }
944
+ function isRecord4(v) {
534
945
  return typeof v === "object" && v !== null && !Array.isArray(v);
535
946
  }
536
947
  function isNonEmptyStringArray(v) {
@@ -542,7 +953,7 @@ function validateRedact(redact) {
542
953
  }
543
954
  for (const r of redact) {
544
955
  if (typeof r === "string") continue;
545
- if (!isRecord3(r)) {
956
+ if (!isRecord4(r)) {
546
957
  throw new Error("Invalid config: redact entries must be strings or objects");
547
958
  }
548
959
  if (typeof r.key !== "string" || r.key.trim() === "") {
@@ -561,7 +972,7 @@ function validateRedact(redact) {
561
972
  }
562
973
  }
563
974
  function validateMappings(mappings) {
564
- if (!isRecord3(mappings)) {
975
+ if (!isRecord4(mappings)) {
565
976
  throw new Error("Invalid config: mappings must be an object");
566
977
  }
567
978
  }
@@ -611,7 +1022,7 @@ async function loadLogIngestConfig(configPath) {
611
1022
  const msg = e instanceof Error ? e.message : String(e);
612
1023
  throw new Error(`Invalid JSON in config file: ${configPath} (${msg})`);
613
1024
  }
614
- if (!isRecord3(parsed)) {
1025
+ if (!isRecord4(parsed)) {
615
1026
  throw new Error("Invalid config: expected a JSON object at top-level");
616
1027
  }
617
1028
  const user = parsed;
@@ -648,7 +1059,7 @@ async function loadLogIngestConfig(configPath) {
648
1059
  }
649
1060
  return mergeLogIngestConfig(DEFAULT_LOG_INGEST_CONFIG, user);
650
1061
  }
651
- function isRecord4(v) {
1062
+ function isRecord5(v) {
652
1063
  return typeof v === "object" && v !== null && !Array.isArray(v);
653
1064
  }
654
1065
  var JsonLogParser = class {
@@ -673,7 +1084,7 @@ var JsonLogParser = class {
673
1084
  });
674
1085
  continue;
675
1086
  }
676
- if (!isRecord4(parsed)) {
1087
+ if (!isRecord5(parsed)) {
677
1088
  warnings.push({
678
1089
  code: "MALFORMED_JSON",
679
1090
  message: "JSON log line must be an object",
@@ -704,7 +1115,7 @@ var JsonLogParser = class {
704
1115
  return this.parseLines(lines, filePath);
705
1116
  }
706
1117
  };
707
- function isRecord5(v) {
1118
+ function isRecord6(v) {
708
1119
  return typeof v === "object" && v !== null && !Array.isArray(v);
709
1120
  }
710
1121
  function findLastJsonObjectSubstring(line) {
@@ -780,7 +1191,7 @@ var Log4jsParser = class {
780
1191
  });
781
1192
  continue;
782
1193
  }
783
- if (!isRecord5(parsed)) {
1194
+ if (!isRecord6(parsed)) {
784
1195
  warnings.push({
785
1196
  code: "UNSUPPORTED_LOG4JS_PAYLOAD",
786
1197
  message: "Embedded JSON payload must be an object",
@@ -858,7 +1269,7 @@ var DEFAULT_REDACT_KEYS = [
858
1269
  "secret",
859
1270
  "email"
860
1271
  ];
861
- function isRecord6(v) {
1272
+ function isRecord7(v) {
862
1273
  return typeof v === "object" && v !== null && !Array.isArray(v);
863
1274
  }
864
1275
  function toKey(s) {
@@ -931,7 +1342,7 @@ var Redactor = class {
931
1342
  if (Array.isArray(value)) {
932
1343
  return value.map((v) => this.#redactNested(v));
933
1344
  }
934
- if (isRecord6(value)) {
1345
+ if (isRecord7(value)) {
935
1346
  const out = {};
936
1347
  for (const [k, v] of Object.entries(value)) {
937
1348
  out[k] = this.redactValue(k, v);
@@ -1688,66 +2099,78 @@ function truncateStringForProfile(value, key, maxMetadataValueLength, maxPreview
1688
2099
  }
1689
2100
 
1690
2101
  // packages/core/src/read-trace.ts
1691
- function isRecord7(value) {
2102
+ function isRecord8(value) {
1692
2103
  return typeof value === "object" && value !== null && !Array.isArray(value);
1693
2104
  }
1694
2105
  function detectLineFormat(parsed) {
1695
- if (!isRecord7(parsed)) return "unknown";
2106
+ if (!isRecord8(parsed)) return "unknown";
1696
2107
  if (parsed.schemaVersion === "0.1") return "0.1";
1697
2108
  if (parsed.schemaVersion === "0.2") return "0.2";
1698
2109
  return "unknown";
1699
2110
  }
1700
2111
  function parseTraceJsonl(raw, options = {}) {
1701
2112
  const validate = options.validate ?? isTraceEvent;
2113
+ const emitWarning = (message) => {
2114
+ if (options.warnings !== false) warn(message);
2115
+ };
1702
2116
  const persisted = [];
1703
2117
  const traceEvents = [];
2118
+ const rows = [];
2119
+ let sourceEventCount = 0;
1704
2120
  let saw01 = false;
1705
2121
  let saw02 = false;
2122
+ let lineNumber = 0;
1706
2123
  for (const line of raw.split(/\r?\n/)) {
2124
+ lineNumber += 1;
1707
2125
  const trimmed = line.trim();
1708
2126
  if (trimmed === "") continue;
1709
2127
  let parsed;
1710
2128
  try {
1711
2129
  parsed = JSON.parse(trimmed);
1712
2130
  } catch {
1713
- warn("Skipped invalid JSON line in trace file");
2131
+ emitWarning("Skipped invalid JSON line in trace file");
1714
2132
  continue;
1715
2133
  }
1716
2134
  const format2 = detectLineFormat(parsed);
1717
2135
  if (format2 === "0.1") {
1718
2136
  saw01 = true;
1719
2137
  if (validate(parsed)) {
2138
+ sourceEventCount += 1;
1720
2139
  traceEvents.push(parsed);
2140
+ rows.push({ format: "0.1", event: parsed, sourceLine: lineNumber });
1721
2141
  } else {
1722
- warn("Skipped invalid trace event line in trace file");
2142
+ emitWarning("Skipped invalid trace event line in trace file");
1723
2143
  }
1724
2144
  continue;
1725
2145
  }
1726
2146
  if (format2 === "0.2") {
1727
2147
  saw02 = true;
1728
2148
  if (isPersistedInspectEvent(parsed)) {
2149
+ sourceEventCount += 1;
1729
2150
  persisted.push(parsed);
2151
+ rows.push({ format: "0.2", event: parsed, sourceLine: lineNumber });
2152
+ traceEvents.push(...persistedInspectEventToTraceEvents(parsed));
1730
2153
  } else {
1731
- warn("Skipped invalid persisted inspect event line in trace file");
2154
+ emitWarning("Skipped invalid persisted inspect event line in trace file");
1732
2155
  }
1733
2156
  continue;
1734
2157
  }
1735
- warn("Skipped trace line with unknown schemaVersion");
2158
+ emitWarning("Skipped trace line with unknown schemaVersion");
1736
2159
  }
1737
2160
  if (saw01 && saw02) {
1738
- warn("Trace file mixes schemaVersion 0.1 and 0.2 lines; normalizing all rows");
2161
+ emitWarning(
2162
+ "Trace file mixes schemaVersion 0.1 and 0.2 lines; normalizing all rows"
2163
+ );
1739
2164
  }
1740
- const converted = persisted.length > 0 ? persistedInspectEventsToTraceEvents(persisted) : [];
1741
- const events = [...traceEvents, ...converted];
1742
2165
  let format = "empty";
1743
2166
  if (saw01 && saw02) format = "mixed";
1744
2167
  else if (saw01) format = "0.1";
1745
2168
  else if (saw02) format = "0.2";
1746
- return { format, events, persisted };
2169
+ return { format, sourceEventCount, events: traceEvents, persisted, rows };
1747
2170
  }
1748
2171
 
1749
2172
  // packages/core/src/storage.ts
1750
- function isRecord8(value) {
2173
+ function isRecord9(value) {
1751
2174
  return typeof value === "object" && value !== null && !Array.isArray(value);
1752
2175
  }
1753
2176
  function nonEmptyString(value) {
@@ -1758,7 +2181,7 @@ function finiteNumber(value) {
1758
2181
  }
1759
2182
  function optionalErrorInfo(value) {
1760
2183
  if (value === void 0) return true;
1761
- if (!isRecord8(value)) return false;
2184
+ if (!isRecord9(value)) return false;
1762
2185
  if (typeof value.message !== "string") return false;
1763
2186
  if ("stack" in value && value.stack !== void 0) {
1764
2187
  if (typeof value.stack !== "string") return false;
@@ -1766,7 +2189,7 @@ function optionalErrorInfo(value) {
1766
2189
  return true;
1767
2190
  }
1768
2191
  function validateEvent(event) {
1769
- if (!isRecord8(event)) return false;
2192
+ if (!isRecord9(event)) return false;
1770
2193
  if (event.schemaVersion !== "0.1") return false;
1771
2194
  if (!finiteNumber(event.timestamp)) return false;
1772
2195
  if (typeof event.event !== "string") return false;
@@ -1775,7 +2198,7 @@ function validateEvent(event) {
1775
2198
  if (!nonEmptyString(event.runId) || !nonEmptyString(event.name) || !finiteNumber(event.startTime)) {
1776
2199
  return false;
1777
2200
  }
1778
- if (event.metadata !== void 0 && !isRecord8(event.metadata)) {
2201
+ if (event.metadata !== void 0 && !isRecord9(event.metadata)) {
1779
2202
  return false;
1780
2203
  }
1781
2204
  return true;
@@ -1790,7 +2213,7 @@ function validateEvent(event) {
1790
2213
  if (event.parentId !== void 0 && typeof event.parentId !== "string") {
1791
2214
  return false;
1792
2215
  }
1793
- if (event.metadata !== void 0 && !isRecord8(event.metadata)) {
2216
+ if (event.metadata !== void 0 && !isRecord9(event.metadata)) {
1794
2217
  return false;
1795
2218
  }
1796
2219
  return true;
@@ -1848,27 +2271,15 @@ async function writeTraceEvent(event, traceDir) {
1848
2271
  }
1849
2272
  warn("Failed to append trace event to fallback directory");
1850
2273
  }
1851
- async function readTraceFile(runId, traceDir) {
2274
+ async function readTraceEventsFromFile(filePath) {
1852
2275
  try {
1853
- const filePath = getTraceFilePath(runId, traceDir);
1854
- return await readFile(filePath, "utf-8");
2276
+ const raw = await readFile(filePath, "utf-8");
2277
+ return parseTraceJsonl(raw, { validate: validateEvent }).events;
1855
2278
  } catch (e) {
1856
2279
  if (e && typeof e === "object" && "code" in e && e.code === "ENOENT") {
1857
- return void 0;
1858
- }
1859
- warn("Unexpected error reading trace file", e);
1860
- return void 0;
1861
- }
1862
- }
1863
- async function readTraceEvents(runId, traceDir) {
1864
- try {
1865
- const raw = await readTraceFile(runId, traceDir);
1866
- if (raw === void 0) {
1867
2280
  return [];
1868
2281
  }
1869
- return parseTraceJsonl(raw, { validate: validateEvent }).events;
1870
- } catch (e) {
1871
- warn("Failed to read trace events", e);
2282
+ warn("Failed to read trace events from file", e);
1872
2283
  return [];
1873
2284
  }
1874
2285
  }
@@ -1877,7 +2288,7 @@ async function readTraceEvents(runId, traceDir) {
1877
2288
  var DEFAULT_MAX_METADATA_VALUE_LENGTH = 2e3;
1878
2289
  var DEFAULT_MAX_PREVIEW_LENGTH = 500;
1879
2290
  var DEFAULT_MAX_EVENT_BYTES = 65536;
1880
- function isRecord9(value) {
2291
+ function isRecord10(value) {
1881
2292
  return typeof value === "object" && value !== null && !Array.isArray(value);
1882
2293
  }
1883
2294
  function isPreviewKey2(key) {
@@ -1897,21 +2308,10 @@ function resolveTraceSafetyOptions(options) {
1897
2308
  {
1898
2309
  redactEnabled = true;
1899
2310
  }
1900
- const profile = options?.redactionProfile ?? "local";
2311
+ const profile = "local";
1901
2312
  const resolvedProfile = resolveRedactionProfile(profile);
1902
- const userMaxMetadata = "undefined" === "number" && Number.isFinite(options.maxMetadataValueLength) && options.maxMetadataValueLength >= 0 ? Math.floor(options.maxMetadataValueLength) : void 0;
1903
- const userMaxPreview = "undefined" === "number" && Number.isFinite(options.maxPreviewLength) && options.maxPreviewLength >= 0 ? Math.floor(options.maxPreviewLength) : void 0;
1904
- let maxMetadataValueLength = userMaxMetadata ?? DEFAULT_MAX_METADATA_VALUE_LENGTH;
1905
- let maxPreviewLength = userMaxPreview ?? DEFAULT_MAX_PREVIEW_LENGTH;
1906
- if (redactEnabled && profile !== "local") {
1907
- const capped = applyProfileMetadataCaps(
1908
- maxMetadataValueLength,
1909
- maxPreviewLength,
1910
- resolvedProfile
1911
- );
1912
- maxMetadataValueLength = capped.maxMetadataValueLength;
1913
- maxPreviewLength = capped.maxPreviewLength;
1914
- }
2313
+ let maxMetadataValueLength = DEFAULT_MAX_METADATA_VALUE_LENGTH;
2314
+ let maxPreviewLength = DEFAULT_MAX_PREVIEW_LENGTH;
1915
2315
  return {
1916
2316
  redactEnabled,
1917
2317
  redactionRules,
@@ -1919,11 +2319,26 @@ function resolveTraceSafetyOptions(options) {
1919
2319
  profileExtraKeys: redactEnabled ? resolvedProfile.extraKeys : [],
1920
2320
  maxMetadataValueLength,
1921
2321
  maxPreviewLength,
1922
- maxEventBytes: "undefined" === "number" && Number.isFinite(options.maxEventBytes) && options.maxEventBytes > 0 ? Math.floor(options.maxEventBytes) : DEFAULT_MAX_EVENT_BYTES
2322
+ maxEventBytes: DEFAULT_MAX_EVENT_BYTES
1923
2323
  };
1924
2324
  }
1925
2325
  function boundMetadataValue(key, value, opts, seen, depth) {
1926
2326
  if (depth > 32) return "[MaxDepth]";
2327
+ if (typeof value === "bigint") {
2328
+ return `${value.toString()}n`;
2329
+ }
2330
+ if (typeof value === "function") {
2331
+ return "[Function]";
2332
+ }
2333
+ if (typeof value === "symbol") {
2334
+ return "[Symbol]";
2335
+ }
2336
+ if (typeof value === "number" && !Number.isFinite(value)) {
2337
+ return String(value);
2338
+ }
2339
+ if (value === void 0) {
2340
+ return null;
2341
+ }
1927
2342
  if (value === null || typeof value !== "object") {
1928
2343
  if (typeof value === "string") {
1929
2344
  const max = isPreviewKey2(key) ? opts.maxPreviewLength : opts.maxMetadataValueLength;
@@ -1945,8 +2360,12 @@ function boundMetadataValue(key, value, opts, seen, depth) {
1945
2360
  }
1946
2361
  const record = value;
1947
2362
  const out = {};
1948
- for (const [k, v] of Object.entries(record)) {
1949
- out[k] = boundMetadataValue(k, v, opts, seen, depth + 1);
2363
+ try {
2364
+ for (const [k, v] of Object.entries(record)) {
2365
+ out[k] = boundMetadataValue(k, v, opts, seen, depth + 1);
2366
+ }
2367
+ } catch {
2368
+ return { truncated: true, reason: "metadataEnumerationFailed" };
1950
2369
  }
1951
2370
  return out;
1952
2371
  }
@@ -1960,16 +2379,25 @@ function redactMetadata(metadata, opts) {
1960
2379
  }
1961
2380
  function prepareMetadataForDisk(metadata, opts) {
1962
2381
  try {
1963
- const redacted = redactMetadata(metadata, opts);
1964
- const seen = /* @__PURE__ */ new WeakSet();
2382
+ const preBounded = boundMetadataValue(
2383
+ "metadata",
2384
+ metadata,
2385
+ opts,
2386
+ /* @__PURE__ */ new WeakSet(),
2387
+ 0
2388
+ );
2389
+ const redacted = redactMetadata(
2390
+ isRecord10(preBounded) ? preBounded : {},
2391
+ opts
2392
+ );
1965
2393
  const bounded = boundMetadataValue(
1966
2394
  "metadata",
1967
2395
  redacted,
1968
2396
  opts,
1969
- seen,
2397
+ /* @__PURE__ */ new WeakSet(),
1970
2398
  0
1971
2399
  );
1972
- return isRecord9(bounded) ? bounded : {};
2400
+ return isRecord10(bounded) ? bounded : {};
1973
2401
  } catch {
1974
2402
  return { truncated: true, reason: "metadataPreparationFailed" };
1975
2403
  }
@@ -2219,12 +2647,10 @@ var TraceDirectory = class {
2219
2647
  function isFiniteNumber2(v) {
2220
2648
  return typeof v === "number" && Number.isFinite(v);
2221
2649
  }
2222
- function safeParseJson(line) {
2223
- try {
2224
- return JSON.parse(line);
2225
- } catch {
2226
- return void 0;
2227
- }
2650
+ function parseIsoToMs3(value) {
2651
+ if (value === void 0) return void 0;
2652
+ const parsed = Date.parse(value);
2653
+ return Number.isFinite(parsed) ? parsed : void 0;
2228
2654
  }
2229
2655
  async function extractMetadata(filePath, _quickScan) {
2230
2656
  const stats = await stat(filePath);
@@ -2233,8 +2659,7 @@ async function extractMetadata(filePath, _quickScan) {
2233
2659
  runIdFromFile = runIdFromFile.slice(0, -".jsonl".length);
2234
2660
  }
2235
2661
  const raw = await readFile(filePath, "utf-8");
2236
- const lines = raw.split(/\r?\n/);
2237
- let eventCount = 0;
2662
+ const parsedTrace = parseTraceJsonl(raw, { warnings: false });
2238
2663
  let runId;
2239
2664
  let name;
2240
2665
  let startedAt;
@@ -2244,16 +2669,32 @@ async function extractMetadata(filePath, _quickScan) {
2244
2669
  let hasRunCompleted = false;
2245
2670
  let runCompletedStatus;
2246
2671
  let anyStepError = false;
2247
- let anyKnownEvent = false;
2248
- for (const line of lines) {
2249
- const trimmed = line.trim();
2250
- if (trimmed === "") continue;
2251
- const parsed = safeParseJson(trimmed);
2252
- if (!parsed) continue;
2253
- if (!isTraceEvent(parsed)) continue;
2254
- const e = parsed;
2255
- anyKnownEvent = true;
2256
- eventCount += 1;
2672
+ const anyKnownEvent = parsedTrace.sourceEventCount > 0;
2673
+ let persistedStatus;
2674
+ const persistedRun = parsedTrace.persisted.find(
2675
+ (event) => event.kind === "RUN"
2676
+ );
2677
+ if (persistedRun) {
2678
+ runId = persistedRun.runId;
2679
+ if (persistedRun.name.trim() !== "") {
2680
+ name = persistedRun.name;
2681
+ }
2682
+ startedAt = parseIsoToMs3(persistedRun.startedAt) ?? parseIsoToMs3(persistedRun.timestamp);
2683
+ endedAt = parseIsoToMs3(persistedRun.endedAt);
2684
+ if (isFiniteNumber2(persistedRun.durationMs)) {
2685
+ explicitDurationMs = persistedRun.durationMs;
2686
+ if (endedAt === void 0 && startedAt !== void 0) {
2687
+ endedAt = startedAt + persistedRun.durationMs;
2688
+ }
2689
+ }
2690
+ if (persistedRun.status === "ok") persistedStatus = "success";
2691
+ else if (persistedRun.status === "error") persistedStatus = "error";
2692
+ else if (persistedRun.status === "running") persistedStatus = "running";
2693
+ else if (persistedRun.status === "unknown") persistedStatus = "unknown";
2694
+ } else {
2695
+ runId = parsedTrace.persisted[0]?.runId;
2696
+ }
2697
+ for (const e of parsedTrace.events) {
2257
2698
  if (runId === void 0 && typeof e.runId === "string") {
2258
2699
  runId = e.runId;
2259
2700
  }
@@ -2290,6 +2731,8 @@ async function extractMetadata(filePath, _quickScan) {
2290
2731
  status = runCompletedStatus;
2291
2732
  } else if (anyStepError) {
2292
2733
  status = "error";
2734
+ } else if (persistedStatus !== void 0) {
2735
+ status = persistedStatus;
2293
2736
  } else if (hasRunStarted && !hasRunCompleted) {
2294
2737
  status = "running";
2295
2738
  } else if (anyKnownEvent) {
@@ -2305,12 +2748,15 @@ async function extractMetadata(filePath, _quickScan) {
2305
2748
  startedAt,
2306
2749
  endedAt,
2307
2750
  durationMs,
2308
- eventCount,
2751
+ eventCount: parsedTrace.sourceEventCount,
2309
2752
  filePath,
2310
2753
  fileSize: stats.size,
2311
2754
  createdAt: stats.birthtime
2312
2755
  };
2313
2756
  }
2757
+ function isNonNegativeFiniteNumber(value) {
2758
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
2759
+ }
2314
2760
  function buildRunSummary(events) {
2315
2761
  const started = events.find(
2316
2762
  (e) => e.event === "run_started"
@@ -2333,8 +2779,10 @@ function buildRunSummary(events) {
2333
2779
  name: s.name,
2334
2780
  status: "running",
2335
2781
  parentId: s.parentId,
2336
- tokensInput: typeof s.metadata?.tokens?.input === "number" ? s.metadata.tokens.input : void 0,
2337
- tokensOutput: typeof s.metadata?.tokens?.output === "number" ? s.metadata.tokens.output : void 0
2782
+ tokensInput: isNonNegativeFiniteNumber(s.metadata?.tokens?.input) ? s.metadata.tokens.input : void 0,
2783
+ tokensOutput: isNonNegativeFiniteNumber(s.metadata?.tokens?.output) ? s.metadata.tokens.output : void 0,
2784
+ tokensTotal: isNonNegativeFiniteNumber(s.metadata?.tokens?.total) ? s.metadata.tokens.total : void 0,
2785
+ tokensCached: isNonNegativeFiniteNumber(s.metadata?.tokens?.cached) ? s.metadata.tokens.cached : void 0
2338
2786
  });
2339
2787
  }
2340
2788
  }
@@ -2356,7 +2804,11 @@ function buildRunSummary(events) {
2356
2804
  let longestStep;
2357
2805
  let totalTokensInput = 0;
2358
2806
  let totalTokensOutput = 0;
2359
- let hasAnyTokens = false;
2807
+ let totalTokensTotal = 0;
2808
+ let totalTokensCached = 0;
2809
+ let tokenBearingSteps = 0;
2810
+ let stepsWithKnownTotal = 0;
2811
+ let hasCachedTokens = false;
2360
2812
  const depthCache = /* @__PURE__ */ new Map();
2361
2813
  const computeDepth = (stepId) => {
2362
2814
  const cached = depthCache.get(stepId);
@@ -2385,10 +2837,21 @@ function buildRunSummary(events) {
2385
2837
  longestStep = { name: s.name, durationMs: s.durationMs, type: s.type };
2386
2838
  }
2387
2839
  }
2388
- if (typeof s.tokensInput === "number" || typeof s.tokensOutput === "number") {
2389
- hasAnyTokens = true;
2390
- if (typeof s.tokensInput === "number") totalTokensInput += s.tokensInput;
2391
- if (typeof s.tokensOutput === "number") totalTokensOutput += s.tokensOutput;
2840
+ if (s.tokensInput !== void 0 || s.tokensOutput !== void 0 || s.tokensTotal !== void 0 || s.tokensCached !== void 0) {
2841
+ tokenBearingSteps += 1;
2842
+ if (s.tokensInput !== void 0) totalTokensInput += s.tokensInput;
2843
+ if (s.tokensOutput !== void 0) totalTokensOutput += s.tokensOutput;
2844
+ if (s.tokensTotal !== void 0) {
2845
+ totalTokensTotal += s.tokensTotal;
2846
+ stepsWithKnownTotal += 1;
2847
+ } else if (s.tokensInput !== void 0 && s.tokensOutput !== void 0) {
2848
+ totalTokensTotal += s.tokensInput + s.tokensOutput;
2849
+ stepsWithKnownTotal += 1;
2850
+ }
2851
+ if (s.tokensCached !== void 0) {
2852
+ totalTokensCached += s.tokensCached;
2853
+ hasCachedTokens = true;
2854
+ }
2392
2855
  }
2393
2856
  }
2394
2857
  const summary = {
@@ -2403,7 +2866,14 @@ function buildRunSummary(events) {
2403
2866
  errorSteps,
2404
2867
  maxDepth,
2405
2868
  ...longestStep ? { longestStep } : {},
2406
- ...hasAnyTokens ? { totalTokens: { input: totalTokensInput, output: totalTokensOutput } } : {}
2869
+ ...tokenBearingSteps > 0 ? {
2870
+ totalTokens: {
2871
+ input: totalTokensInput,
2872
+ output: totalTokensOutput,
2873
+ ...stepsWithKnownTotal === tokenBearingSteps ? { total: totalTokensTotal } : {},
2874
+ ...hasCachedTokens ? { cached: totalTokensCached } : {}
2875
+ }
2876
+ } : {}
2407
2877
  };
2408
2878
  return summary;
2409
2879
  }
@@ -2708,9 +3178,17 @@ function renderRunWhat(summary, options = {}) {
2708
3178
  `Status: ${summary.status} \xB7 Duration: ${duration} \xB7 Steps: ${summary.totalSteps} (${stepMixLine(summary)})`
2709
3179
  );
2710
3180
  if (summary.totalTokens) {
2711
- lines.push(
2712
- `Tokens: ${summary.totalTokens.input} in / ${summary.totalTokens.output} out`
2713
- );
3181
+ const tokenParts = [
3182
+ `${summary.totalTokens.input} in`,
3183
+ `${summary.totalTokens.output} out`
3184
+ ];
3185
+ if (summary.totalTokens.total !== void 0) {
3186
+ tokenParts.push(`${summary.totalTokens.total} total`);
3187
+ }
3188
+ if (summary.totalTokens.cached !== void 0) {
3189
+ tokenParts.push(`${summary.totalTokens.cached} cached`);
3190
+ }
3191
+ lines.push(`Tokens: ${tokenParts.join(" / ")}`);
2714
3192
  }
2715
3193
  if (showCorrelation && summary.correlation) {
2716
3194
  const parts = [];
@@ -2790,7 +3268,7 @@ function stableJson(value, pretty) {
2790
3268
  const sorted = sortKeysDeep(value);
2791
3269
  return pretty === true ? JSON.stringify(sorted, null, 2) : JSON.stringify(sorted);
2792
3270
  }
2793
- function compactAttributes(attrs, options) {
3271
+ function compactAttributes3(attrs, options) {
2794
3272
  if (attrs === void 0) return {};
2795
3273
  const maxLen = options?.maxLength ?? 500;
2796
3274
  const redacted = options?.redacted ?? true;
@@ -2935,7 +3413,7 @@ function exportHtml(tree, options) {
2935
3413
  attrsHtml += "<h2>Attributes (bounded)</h2>";
2936
3414
  for (const n of flat) {
2937
3415
  if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
2938
- const compact = compactAttributes(n.event.attributes, {
3416
+ const compact = compactAttributes3(n.event.attributes, {
2939
3417
  maxLength: maxLen,
2940
3418
  redacted
2941
3419
  });
@@ -3086,7 +3564,7 @@ function exportMarkdown(tree, options) {
3086
3564
  lines.push("");
3087
3565
  for (const n of flat) {
3088
3566
  if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
3089
- const compact = compactAttributes(n.event.attributes, {
3567
+ const compact = compactAttributes3(n.event.attributes, {
3090
3568
  maxLength: maxLen,
3091
3569
  redacted
3092
3570
  });
@@ -3249,7 +3727,7 @@ function manualTraceEventsToRunTree(events) {
3249
3727
  }
3250
3728
 
3251
3729
  // packages/core/src/exporters/redact-export.ts
3252
- function isRecord10(value) {
3730
+ function isRecord11(value) {
3253
3731
  return typeof value === "object" && value !== null && !Array.isArray(value);
3254
3732
  }
3255
3733
  function deepClone(value) {
@@ -3323,7 +3801,7 @@ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPrevi
3323
3801
  0
3324
3802
  );
3325
3803
  const err = bounded.error;
3326
- if (isRecord10(err) && typeof err.message === "string") {
3804
+ if (isRecord11(err) && typeof err.message === "string") {
3327
3805
  bounded.error = {
3328
3806
  ...err,
3329
3807
  message: truncateStringForProfile(
@@ -3344,6 +3822,89 @@ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPrevi
3344
3822
  }
3345
3823
  return bounded;
3346
3824
  }
3825
+ function redactErrorInfo(error, redactor, maxMetadataValueLength, maxPreviewLength) {
3826
+ if (error === void 0) return void 0;
3827
+ const record = redactEventAttributes(
3828
+ { error },
3829
+ redactor,
3830
+ maxMetadataValueLength,
3831
+ maxPreviewLength
3832
+ );
3833
+ const redacted = record?.error;
3834
+ if (!isRecord11(redacted) || typeof redacted.message !== "string") {
3835
+ return void 0;
3836
+ }
3837
+ return {
3838
+ message: redacted.message,
3839
+ ...typeof redacted.stack === "string" ? { stack: redacted.stack } : {}
3840
+ };
3841
+ }
3842
+ function redactTraceEventsForReport(events, options) {
3843
+ const profile = options?.redactionProfile ?? "local";
3844
+ if (profile === "local") {
3845
+ return deepClone(events);
3846
+ }
3847
+ const resolved = resolveRedactionProfile(profile);
3848
+ const { maxMetadataValueLength, maxPreviewLength } = applyProfileMetadataCaps(
3849
+ 2e3,
3850
+ 500,
3851
+ resolved
3852
+ );
3853
+ const redactor = new Redactor({ extraKeys: resolved.extraKeys });
3854
+ return events.map((event) => {
3855
+ switch (event.event) {
3856
+ case "run_started":
3857
+ return {
3858
+ ...event,
3859
+ name: truncateStringForProfile(
3860
+ event.name,
3861
+ "name",
3862
+ maxMetadataValueLength,
3863
+ maxPreviewLength
3864
+ ),
3865
+ ...event.metadata !== void 0 ? {
3866
+ metadata: redactEventAttributes(
3867
+ event.metadata,
3868
+ redactor,
3869
+ maxMetadataValueLength,
3870
+ maxPreviewLength
3871
+ )
3872
+ } : {}
3873
+ };
3874
+ case "step_started":
3875
+ return {
3876
+ ...event,
3877
+ name: truncateStringForProfile(
3878
+ event.name,
3879
+ "name",
3880
+ maxMetadataValueLength,
3881
+ maxPreviewLength
3882
+ ),
3883
+ ...event.metadata !== void 0 ? {
3884
+ metadata: redactEventAttributes(
3885
+ event.metadata,
3886
+ redactor,
3887
+ maxMetadataValueLength,
3888
+ maxPreviewLength
3889
+ )
3890
+ } : {}
3891
+ };
3892
+ case "run_completed":
3893
+ case "step_completed":
3894
+ return {
3895
+ ...event,
3896
+ ...event.error !== void 0 ? {
3897
+ error: redactErrorInfo(
3898
+ event.error,
3899
+ redactor,
3900
+ maxMetadataValueLength,
3901
+ maxPreviewLength
3902
+ )
3903
+ } : {}
3904
+ };
3905
+ }
3906
+ });
3907
+ }
3347
3908
  function redactRunTreeForExport(tree, options) {
3348
3909
  const profile = options?.redactionProfile ?? "local";
3349
3910
  if (profile === "local") {
@@ -3407,12 +3968,15 @@ footer{margin-top:2rem;font-size:0.85rem;color:#555}
3407
3968
  `.trim();
3408
3969
  function buildRunReport(events, options) {
3409
3970
  const profile = options.redactionProfile ?? "local";
3410
- const whatSummary = buildRunWhatSummary(events);
3971
+ const safeEvents = redactTraceEventsForReport(events, {
3972
+ redactionProfile: profile
3973
+ });
3974
+ const whatSummary = buildRunWhatSummary(safeEvents);
3411
3975
  const whatText = renderRunWhat(whatSummary, {
3412
3976
  correlation: options.correlation !== false
3413
3977
  });
3414
- const timelineText = renderTimeline(buildRunTimeline(events));
3415
- const tree = resolveTree(events, profile);
3978
+ const timelineText = renderTimeline(buildRunTimeline(safeEvents));
3979
+ const tree = resolveTree(safeEvents, profile);
3416
3980
  const exportOpts = {
3417
3981
  includeMetadata: false,
3418
3982
  includeAttributes: options.includeAttributes === true,
@@ -3493,6 +4057,8 @@ ${attrsSection}
3493
4057
  fileExtension: ".html"
3494
4058
  };
3495
4059
  }
4060
+
4061
+ // packages/core/src/stats.ts
3496
4062
  function percentile(sorted, p) {
3497
4063
  if (sorted.length === 0) return void 0;
3498
4064
  const idx = Math.min(
@@ -3503,19 +4069,10 @@ function percentile(sorted, p) {
3503
4069
  }
3504
4070
  async function readRunStartedMetadata(filePath) {
3505
4071
  try {
3506
- const raw = await readFile(filePath, "utf-8");
3507
- for (const line of raw.split(/\r?\n/)) {
3508
- const trimmed = line.trim();
3509
- if (trimmed === "") continue;
3510
- let parsed;
3511
- try {
3512
- parsed = JSON.parse(trimmed);
3513
- } catch {
3514
- continue;
3515
- }
3516
- if (!isTraceEvent(parsed)) continue;
3517
- if (parsed.event !== "run_started") continue;
3518
- const rs = parsed;
4072
+ const events = await readTraceEventsFromFile(filePath);
4073
+ for (const event of events) {
4074
+ if (event.event !== "run_started") continue;
4075
+ const rs = event;
3519
4076
  if (rs.metadata && typeof rs.metadata === "object") {
3520
4077
  return rs.metadata;
3521
4078
  }
@@ -3574,7 +4131,7 @@ async function buildTraceStats(metas, options) {
3574
4131
  });
3575
4132
  }
3576
4133
  try {
3577
- const events = await readTraceEvents(m.runId, options.traceDir);
4134
+ const events = await readTraceEventsFromFile(m.filePath);
3578
4135
  if (events.length === 0) continue;
3579
4136
  const summary = buildRunSummary(events);
3580
4137
  totalSteps += summary.totalSteps;
@@ -3762,7 +4319,7 @@ async function searchTraces(metas, options) {
3762
4319
  if (options.status && m.status !== options.status) continue;
3763
4320
  let events = [];
3764
4321
  try {
3765
- events = await readTraceEvents(m.runId, options.traceDir);
4322
+ events = await readTraceEventsFromFile(m.filePath);
3766
4323
  } catch {
3767
4324
  continue;
3768
4325
  }
@@ -3898,7 +4455,7 @@ var KNOWN_EVENTS = /* @__PURE__ */ new Set([
3898
4455
  "step_started",
3899
4456
  "step_completed"
3900
4457
  ]);
3901
- function isRecord11(value) {
4458
+ function isRecord12(value) {
3902
4459
  return typeof value === "object" && value !== null && !Array.isArray(value);
3903
4460
  }
3904
4461
  function safeParse(line) {
@@ -3920,7 +4477,7 @@ async function isAgentInspectTrace(filePath) {
3920
4477
  if (trimmed === "") continue;
3921
4478
  const parsed = safeParse(trimmed);
3922
4479
  if (!parsed) continue;
3923
- if (!isRecord11(parsed)) continue;
4480
+ if (!isRecord12(parsed)) continue;
3924
4481
  checked += 1;
3925
4482
  if (isTraceEvent(parsed)) return true;
3926
4483
  const ev = parsed.event;
@@ -3935,9 +4492,1572 @@ async function isAgentInspectTrace(filePath) {
3935
4492
  return false;
3936
4493
  }
3937
4494
  }
3938
-
3939
- // packages/core/src/diff/comparable.ts
3940
- function extractOutputPreview(meta) {
4495
+ resolveTraceSafetyOptions();
4496
+ var DEFAULT_MAX_TRACE_INPUT_BYTES = 10 * 1024 * 1024;
4497
+ var MIN_DETECTION_CONFIDENCE = 0.5;
4498
+ var AMBIGUOUS_CONFIDENCE_DELTA = 0.05;
4499
+ var resolvedInputCache = /* @__PURE__ */ new WeakMap();
4500
+ var OPENINFERENCE_READER_FORMAT = "openinference-json";
4501
+ var OTLP_READER_FORMAT = "otlp-json";
4502
+ var OPENINFERENCE_SPAN_KEYS = /* @__PURE__ */ new Set([
4503
+ "trace_id",
4504
+ "traceId",
4505
+ "span_id",
4506
+ "spanId",
4507
+ "parent_span_id",
4508
+ "parentSpanId",
4509
+ "name",
4510
+ "start_time_unix_nano",
4511
+ "startTimeUnixNano",
4512
+ "end_time_unix_nano",
4513
+ "endTimeUnixNano",
4514
+ "start_time",
4515
+ "startTime",
4516
+ "end_time",
4517
+ "endTime",
4518
+ "attributes",
4519
+ "status",
4520
+ "kind",
4521
+ "span_kind",
4522
+ "spanKind"
4523
+ ]);
4524
+ var OPENINFERENCE_SENSITIVE_ATTRIBUTE_KEYS = [
4525
+ "input.value",
4526
+ "output.value",
4527
+ "input.mime_type",
4528
+ "output.mime_type",
4529
+ "llm.input_messages",
4530
+ "llm.output_messages",
4531
+ "llm.prompts",
4532
+ "llm.completions",
4533
+ "retrieval.documents",
4534
+ "reranker.input_documents",
4535
+ "reranker.output_documents",
4536
+ "document.content",
4537
+ "gen_ai.prompt",
4538
+ "gen_ai.completion",
4539
+ "gen_ai.input.messages",
4540
+ "gen_ai.output.messages"
4541
+ ];
4542
+ var OTLP_SPAN_KEYS = /* @__PURE__ */ new Set([
4543
+ "traceId",
4544
+ "spanId",
4545
+ "parentSpanId",
4546
+ "name",
4547
+ "kind",
4548
+ "startTimeUnixNano",
4549
+ "endTimeUnixNano",
4550
+ "attributes",
4551
+ "events",
4552
+ "status",
4553
+ "droppedAttributesCount",
4554
+ "droppedEventsCount",
4555
+ "droppedLinksCount",
4556
+ "links",
4557
+ "flags"
4558
+ ]);
4559
+ var TraceReadError = class extends Error {
4560
+ code;
4561
+ warnings;
4562
+ constructor(code, message, warnings = []) {
4563
+ super(message);
4564
+ this.name = "TraceReadError";
4565
+ this.code = code;
4566
+ this.warnings = warnings;
4567
+ }
4568
+ };
4569
+ function normalizeCandidate(reader, candidate) {
4570
+ const confidence = Number.isFinite(candidate.confidence) ? Math.max(0, Math.min(1, candidate.confidence)) : 0;
4571
+ return {
4572
+ ...candidate,
4573
+ format: candidate.format || reader.format,
4574
+ confidence,
4575
+ readerName: candidate.readerName ?? reader.name
4576
+ };
4577
+ }
4578
+ function sortCandidates(candidates) {
4579
+ return [...candidates].sort((a, b) => {
4580
+ if (b.confidence !== a.confidence) return b.confidence - a.confidence;
4581
+ return a.format.localeCompare(b.format);
4582
+ });
4583
+ }
4584
+ function collectWarnings(candidates) {
4585
+ return candidates.flatMap((candidate) => candidate.warnings ?? []);
4586
+ }
4587
+ function dedupeWarnings(warnings) {
4588
+ const seen = /* @__PURE__ */ new Set();
4589
+ const out = [];
4590
+ for (const warning of warnings) {
4591
+ const key = [
4592
+ warning.code,
4593
+ warning.message,
4594
+ warning.severity ?? "",
4595
+ warning.sourceFile ?? "",
4596
+ warning.line ?? "",
4597
+ warning.field ?? ""
4598
+ ].join("\0");
4599
+ if (seen.has(key)) continue;
4600
+ seen.add(key);
4601
+ out.push(warning);
4602
+ }
4603
+ return out;
4604
+ }
4605
+ function attachSingleSourceFile(warnings, resolved) {
4606
+ if (resolved.sourceFiles.length !== 1) return [...warnings];
4607
+ const [sourceFile] = resolved.sourceFiles;
4608
+ return warnings.map((warning) => ({
4609
+ ...warning,
4610
+ sourceFile: warning.sourceFile ?? sourceFile
4611
+ }));
4612
+ }
4613
+ function findReaderByFormat(format, readers) {
4614
+ return readers.find((reader) => reader.format === format);
4615
+ }
4616
+ async function jsonlFilesInDirectory(dirPath) {
4617
+ const entries = await readdir(dirPath, { withFileTypes: true });
4618
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
4619
+ }
4620
+ async function resolveInput(input3) {
4621
+ const cached = resolvedInputCache.get(input3);
4622
+ if (cached) return cached;
4623
+ const promise = resolveInputUncached(input3);
4624
+ resolvedInputCache.set(input3, promise);
4625
+ return promise;
4626
+ }
4627
+ function assertInputWithinBounds(content, sourceFile) {
4628
+ const bytes = Buffer.byteLength(content, "utf8");
4629
+ if (bytes <= DEFAULT_MAX_TRACE_INPUT_BYTES) return;
4630
+ throw new TraceReadError("unsupported_format", "Trace input exceeds the local reader size limit.", [
4631
+ {
4632
+ code: "input_too_large",
4633
+ message: `Trace input is ${bytes} bytes; max is ${DEFAULT_MAX_TRACE_INPUT_BYTES} bytes.`,
4634
+ severity: "error",
4635
+ ...sourceFile !== void 0 ? { sourceFile } : {}
4636
+ }
4637
+ ]);
4638
+ }
4639
+ async function resolveInputUncached(input3) {
4640
+ if (input3.type === "string") {
4641
+ assertInputWithinBounds(input3.content);
4642
+ return { content: input3.content, sourceFiles: [] };
4643
+ }
4644
+ if (input3.type === "buffer") {
4645
+ const content = input3.content.toString("utf-8");
4646
+ assertInputWithinBounds(content);
4647
+ return { content, sourceFiles: [] };
4648
+ }
4649
+ if (input3.type === "file") {
4650
+ const content = await readFile(input3.path, "utf-8");
4651
+ assertInputWithinBounds(content, input3.path);
4652
+ return { content, sourceFiles: [input3.path] };
4653
+ }
4654
+ if (input3.type === "directory") {
4655
+ const files = await jsonlFilesInDirectory(input3.path);
4656
+ const parts = await Promise.all(
4657
+ files.map(async (file) => (await readFile(file, "utf-8")).trimEnd())
4658
+ );
4659
+ const content = parts.filter((part) => part.trim() !== "").join("\n");
4660
+ assertInputWithinBounds(content, input3.path);
4661
+ return {
4662
+ content,
4663
+ sourceFiles: files
4664
+ };
4665
+ }
4666
+ return void 0;
4667
+ }
4668
+ function detectJsonlFormat(content) {
4669
+ let saw01 = false;
4670
+ let saw02 = false;
4671
+ let validRows = 0;
4672
+ let invalidJsonRows = 0;
4673
+ let unknownSchemaRows = 0;
4674
+ let firstInvalidJsonLine;
4675
+ let firstUnknownSchemaLine;
4676
+ let lineNumber = 0;
4677
+ for (const line of content.split(/\r?\n/)) {
4678
+ lineNumber += 1;
4679
+ const trimmed = line.trim();
4680
+ if (trimmed === "") continue;
4681
+ let parsed;
4682
+ try {
4683
+ parsed = JSON.parse(trimmed);
4684
+ } catch {
4685
+ invalidJsonRows += 1;
4686
+ firstInvalidJsonLine ??= lineNumber;
4687
+ continue;
4688
+ }
4689
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "schemaVersion" in parsed) {
4690
+ const version2 = parsed.schemaVersion;
4691
+ if (version2 === "0.1") {
4692
+ saw01 = true;
4693
+ validRows += 1;
4694
+ continue;
4695
+ }
4696
+ if (version2 === "0.2") {
4697
+ saw02 = true;
4698
+ validRows += 1;
4699
+ continue;
4700
+ }
4701
+ }
4702
+ unknownSchemaRows += 1;
4703
+ firstUnknownSchemaLine ??= lineNumber;
4704
+ }
4705
+ const warnings = [];
4706
+ if (invalidJsonRows > 0) {
4707
+ warnings.push({
4708
+ code: "invalid_jsonl_rows",
4709
+ message: `Skipped ${invalidJsonRows} invalid JSONL row(s) during format detection.`,
4710
+ severity: "warning",
4711
+ ...firstInvalidJsonLine !== void 0 ? { line: firstInvalidJsonLine } : {}
4712
+ });
4713
+ }
4714
+ if (unknownSchemaRows > 0) {
4715
+ warnings.push({
4716
+ code: "unknown_schema_rows",
4717
+ message: `Skipped ${unknownSchemaRows} row(s) with unknown schemaVersion during format detection.`,
4718
+ severity: "warning",
4719
+ ...firstUnknownSchemaLine !== void 0 ? { line: firstUnknownSchemaLine } : {}
4720
+ });
4721
+ }
4722
+ let format = "empty";
4723
+ if (saw01 && saw02) format = "mixed";
4724
+ else if (saw01) format = "0.1";
4725
+ else if (saw02) format = "0.2";
4726
+ return { format, validRows, warnings };
4727
+ }
4728
+ function agentInspectFormatLabel(format) {
4729
+ switch (format) {
4730
+ case "0.1":
4731
+ return "agent-inspect-v0.1-jsonl";
4732
+ case "0.2":
4733
+ return "agent-inspect-v0.2-jsonl";
4734
+ case "mixed":
4735
+ return "agent-inspect-mixed-jsonl";
4736
+ default:
4737
+ return "agent-inspect-jsonl";
4738
+ }
4739
+ }
4740
+ function persistedEventsForParsedTrace(parsed) {
4741
+ if (parsed.format === "0.2" && parsed.persisted.length > 0) {
4742
+ return [...parsed.persisted];
4743
+ }
4744
+ if (parsed.format === "mixed" && parsed.rows.length > 0) {
4745
+ return parsed.rows.map((row, index) => {
4746
+ if (row.format === "0.2") return row.event;
4747
+ return traceEventToPersistedInspectEvent(row.event, {
4748
+ eventIndex: index,
4749
+ sourceName: "agent-inspect-jsonl-reader"
4750
+ });
4751
+ });
4752
+ }
4753
+ return traceEventsToPersistedInspectEvents(parsed.events, {
4754
+ sourceName: "agent-inspect-jsonl-reader"
4755
+ });
4756
+ }
4757
+ function isRecord13(value) {
4758
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4759
+ }
4760
+ function isNonEmptyString3(value) {
4761
+ return typeof value === "string" && value.trim() !== "";
4762
+ }
4763
+ function readStringField(record, keys) {
4764
+ for (const key of keys) {
4765
+ const value = record[key];
4766
+ if (isNonEmptyString3(value)) return value;
4767
+ }
4768
+ return void 0;
4769
+ }
4770
+ function readRecordField(record, key) {
4771
+ const value = record[key];
4772
+ return isRecord13(value) ? value : void 0;
4773
+ }
4774
+ function parseJsonDocument(content) {
4775
+ return JSON.parse(content);
4776
+ }
4777
+ function looksLikeOpenInferenceSpan(value) {
4778
+ if (!isRecord13(value)) return false;
4779
+ const attributes = readRecordField(value, "attributes");
4780
+ return readStringField(value, ["trace_id", "traceId"]) !== void 0 && readStringField(value, ["span_id", "spanId"]) !== void 0 && (readStringField(value, ["name"]) !== void 0 || attributes?.["openinference.span.kind"] !== void 0);
4781
+ }
4782
+ function extractOpenInferenceDocument(root) {
4783
+ const warnings = [];
4784
+ const unsupportedFields = [];
4785
+ if (Array.isArray(root)) {
4786
+ const spans = root.filter(looksLikeOpenInferenceSpan);
4787
+ if (spans.length === 0) return void 0;
4788
+ if (spans.length !== root.length) {
4789
+ warnings.push({
4790
+ code: "openinference_skipped_items",
4791
+ message: "Skipped non-span item(s) in OpenInference span array.",
4792
+ severity: "warning"
4793
+ });
4794
+ }
4795
+ return {
4796
+ spans,
4797
+ confidence: 0.82,
4798
+ description: "OpenInference span array",
4799
+ warnings,
4800
+ unsupportedFields
4801
+ };
4802
+ }
4803
+ if (!isRecord13(root)) return void 0;
4804
+ const rootFormat = root.format;
4805
+ const rootCompatibility = root.compatibility;
4806
+ const version2 = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
4807
+ if (Array.isArray(root.spans)) {
4808
+ const spans = root.spans.filter(looksLikeOpenInferenceSpan);
4809
+ if (spans.length === 0 && (rootFormat === "openinference" || rootCompatibility === "openinference-compatible")) {
4810
+ warnings.push({
4811
+ code: "openinference_no_valid_spans",
4812
+ message: "OpenInference document did not contain any valid spans.",
4813
+ severity: "error"
4814
+ });
4815
+ return {
4816
+ spans,
4817
+ confidence: 0.7,
4818
+ description: "Malformed OpenInference document",
4819
+ version: version2,
4820
+ warnings,
4821
+ unsupportedFields
4822
+ };
4823
+ }
4824
+ if (spans.length === 0) return void 0;
4825
+ if (spans.length !== root.spans.length) {
4826
+ warnings.push({
4827
+ code: "openinference_skipped_spans",
4828
+ message: "Skipped invalid OpenInference span item(s).",
4829
+ severity: "warning"
4830
+ });
4831
+ }
4832
+ return {
4833
+ spans,
4834
+ confidence: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? 0.9 : 0.84,
4835
+ description: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? "OpenInference document" : "OpenInference spans document",
4836
+ version: version2,
4837
+ warnings,
4838
+ unsupportedFields
4839
+ };
4840
+ }
4841
+ if (Array.isArray(root.data)) {
4842
+ const spans = root.data.filter(looksLikeOpenInferenceSpan);
4843
+ if (spans.length === 0) return void 0;
4844
+ if (spans.length !== root.data.length) {
4845
+ warnings.push({
4846
+ code: "openinference_skipped_data_items",
4847
+ message: "Skipped non-span item(s) in OpenInference data array.",
4848
+ severity: "warning"
4849
+ });
4850
+ }
4851
+ return {
4852
+ spans,
4853
+ confidence: 0.8,
4854
+ description: "OpenInference data document",
4855
+ version: version2,
4856
+ warnings,
4857
+ unsupportedFields
4858
+ };
4859
+ }
4860
+ if (looksLikeOpenInferenceSpan(root)) {
4861
+ return {
4862
+ spans: [root],
4863
+ confidence: 0.76,
4864
+ description: "OpenInference single span",
4865
+ version: version2,
4866
+ warnings,
4867
+ unsupportedFields
4868
+ };
4869
+ }
4870
+ if (rootFormat === "openinference" || rootCompatibility === "openinference-compatible") {
4871
+ warnings.push({
4872
+ code: "openinference_missing_spans",
4873
+ message: "OpenInference document is missing a spans array.",
4874
+ severity: "error"
4875
+ });
4876
+ return {
4877
+ spans: [],
4878
+ confidence: 0.7,
4879
+ description: "Malformed OpenInference document",
4880
+ version: version2,
4881
+ warnings,
4882
+ unsupportedFields
4883
+ };
4884
+ }
4885
+ return void 0;
4886
+ }
4887
+ function parseUnixNanoToIso(value) {
4888
+ if (typeof value === "bigint" && value >= 0n) {
4889
+ return new Date(Number(value / 1000000n)).toISOString();
4890
+ }
4891
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
4892
+ return new Date(Math.floor(value / 1e6)).toISOString();
4893
+ }
4894
+ if (typeof value === "string" && /^\d+$/.test(value)) {
4895
+ return new Date(Number(BigInt(value) / 1000000n)).toISOString();
4896
+ }
4897
+ return void 0;
4898
+ }
4899
+ function parseIsoTime(value) {
4900
+ if (!isNonEmptyString3(value)) return void 0;
4901
+ const ms = Date.parse(value);
4902
+ if (!Number.isFinite(ms)) return void 0;
4903
+ return new Date(ms).toISOString();
4904
+ }
4905
+ function readOpenInferenceTimestamp(span, nanoKeys, isoKeys) {
4906
+ for (const key of nanoKeys) {
4907
+ const iso = parseUnixNanoToIso(span[key]);
4908
+ if (iso !== void 0) return iso;
4909
+ }
4910
+ for (const key of isoKeys) {
4911
+ const iso = parseIsoTime(span[key]);
4912
+ if (iso !== void 0) return iso;
4913
+ }
4914
+ return void 0;
4915
+ }
4916
+ function durationBetweenIso(startedAt, endedAt) {
4917
+ if (startedAt === void 0 || endedAt === void 0) return void 0;
4918
+ const startMs = Date.parse(startedAt);
4919
+ const endMs = Date.parse(endedAt);
4920
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) {
4921
+ return void 0;
4922
+ }
4923
+ return endMs - startMs;
4924
+ }
4925
+ function isSensitiveOpenInferenceAttribute(key) {
4926
+ return OPENINFERENCE_SENSITIVE_ATTRIBUTE_KEYS.some(
4927
+ (sensitiveKey) => key === sensitiveKey || key.startsWith(`${sensitiveKey}.`) || key.endsWith(".message.content") || key.endsWith(".document.content")
4928
+ );
4929
+ }
4930
+ function summarizeAttributeValue(value) {
4931
+ if (typeof value === "string") {
4932
+ return { type: "string", length: value.length };
4933
+ }
4934
+ if (typeof value === "number") {
4935
+ return { type: "number", finite: Number.isFinite(value) };
4936
+ }
4937
+ if (typeof value === "boolean") {
4938
+ return { type: "boolean" };
4939
+ }
4940
+ if (Array.isArray(value)) {
4941
+ return { type: "array", length: value.length };
4942
+ }
4943
+ if (isRecord13(value)) {
4944
+ return { type: "object", keyCount: Object.keys(value).length };
4945
+ }
4946
+ if (value === null) {
4947
+ return { type: "null" };
4948
+ }
4949
+ return { type: typeof value };
4950
+ }
4951
+ function sanitizeOpenInferenceAttributes(attributes, pathPrefix) {
4952
+ const out = {};
4953
+ const warnings = [];
4954
+ const unsupportedFields = [];
4955
+ const summarizedKeys = [];
4956
+ for (const [key, value] of Object.entries(attributes)) {
4957
+ if (isSensitiveOpenInferenceAttribute(key)) {
4958
+ summarizedKeys.push(key);
4959
+ out[`${key}.summary`] = summarizeAttributeValue(value);
4960
+ unsupportedFields.push(`${pathPrefix}.attributes.${key}`);
4961
+ continue;
4962
+ }
4963
+ out[key] = value;
4964
+ }
4965
+ if (summarizedKeys.length > 0) {
4966
+ out["openinference.summarized_attributes"] = summarizedKeys;
4967
+ warnings.push({
4968
+ code: "openinference_sensitive_attribute_summarized",
4969
+ message: "OpenInference prompt/output/document attribute(s) were summarized instead of copied verbatim.",
4970
+ severity: "warning"
4971
+ });
4972
+ }
4973
+ return { attributes: out, warnings, unsupportedFields };
4974
+ }
4975
+ function mapOpenInferenceKind(span, attributes, pathPrefix) {
4976
+ const warnings = [];
4977
+ const agentInspectKind = attributes["agent_inspect.kind"];
4978
+ if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG") {
4979
+ return { kind: agentInspectKind, warnings };
4980
+ }
4981
+ const rawKind = readStringField(span, ["kind", "span_kind", "spanKind"]) ?? (typeof attributes["openinference.span.kind"] === "string" ? attributes["openinference.span.kind"] : void 0);
4982
+ const normalized = rawKind?.toUpperCase();
4983
+ switch (normalized) {
4984
+ case "LLM":
4985
+ return { kind: "LLM", warnings };
4986
+ case "TOOL":
4987
+ return { kind: "TOOL", warnings };
4988
+ case "CHAIN":
4989
+ return { kind: "CHAIN", warnings };
4990
+ case "RETRIEVER":
4991
+ return { kind: "RETRIEVER", warnings };
4992
+ case "AGENT":
4993
+ return { kind: "AGENT", warnings };
4994
+ case "EMBEDDING":
4995
+ warnings.push({
4996
+ code: "openinference_kind_semantic_loss",
4997
+ message: "OpenInference EMBEDDING span kind mapped to AgentInspect LLM.",
4998
+ severity: "warning",
4999
+ field: `${pathPrefix}.attributes.openinference.span.kind`
5000
+ });
5001
+ return { kind: "LLM", warnings };
5002
+ case "RERANKER":
5003
+ warnings.push({
5004
+ code: "openinference_kind_semantic_loss",
5005
+ message: "OpenInference RERANKER span kind mapped to AgentInspect RETRIEVER.",
5006
+ severity: "warning",
5007
+ field: `${pathPrefix}.attributes.openinference.span.kind`
5008
+ });
5009
+ return { kind: "RETRIEVER", warnings };
5010
+ case "UNKNOWN":
5011
+ case void 0:
5012
+ warnings.push({
5013
+ code: "openinference_kind_unknown",
5014
+ message: "OpenInference span kind was missing or unknown; mapped to AgentInspect LOGIC.",
5015
+ severity: "warning",
5016
+ field: `${pathPrefix}.attributes.openinference.span.kind`
5017
+ });
5018
+ return { kind: "LOGIC", warnings };
5019
+ default:
5020
+ warnings.push({
5021
+ code: "openinference_kind_unsupported",
5022
+ message: `Unsupported OpenInference span kind "${rawKind}" mapped to AgentInspect LOGIC.`,
5023
+ severity: "warning",
5024
+ field: `${pathPrefix}.attributes.openinference.span.kind`
5025
+ });
5026
+ return { kind: "LOGIC", warnings };
5027
+ }
5028
+ }
5029
+ function mapOpenInferenceStatus(status) {
5030
+ if (!isRecord13(status)) return void 0;
5031
+ const rawCode = status.code;
5032
+ if (typeof rawCode !== "string") return void 0;
5033
+ switch (rawCode.toUpperCase()) {
5034
+ case "OK":
5035
+ return "ok";
5036
+ case "ERROR":
5037
+ return "error";
5038
+ case "UNSET":
5039
+ return "unknown";
5040
+ default:
5041
+ return "unknown";
5042
+ }
5043
+ }
5044
+ function readOpenInferenceTokenUsage(attributes) {
5045
+ const prompt = attributes["llm.token_count.prompt"];
5046
+ const completion = attributes["llm.token_count.completion"];
5047
+ const total = attributes["llm.token_count.total"];
5048
+ const cached = attributes["llm.token_count.prompt_details.cache_read"];
5049
+ const usage = {};
5050
+ if (typeof prompt === "number" && Number.isFinite(prompt) && prompt >= 0) {
5051
+ usage.input = prompt;
5052
+ }
5053
+ if (typeof completion === "number" && Number.isFinite(completion) && completion >= 0) {
5054
+ usage.output = completion;
5055
+ }
5056
+ if (typeof total === "number" && Number.isFinite(total) && total >= 0) {
5057
+ usage.total = total;
5058
+ }
5059
+ if (typeof cached === "number" && Number.isFinite(cached) && cached >= 0) {
5060
+ usage.cached = cached;
5061
+ }
5062
+ if (usage.total === void 0 && usage.input !== void 0 && usage.output !== void 0) {
5063
+ usage.total = usage.input + usage.output;
5064
+ }
5065
+ return Object.keys(usage).length > 0 ? usage : void 0;
5066
+ }
5067
+ function readOpenInferenceConfidence(attributes) {
5068
+ const confidence = attributes["agent_inspect.confidence"];
5069
+ if (confidence === "explicit" || confidence === "correlated" || confidence === "heuristic" || confidence === "unknown") {
5070
+ return confidence;
5071
+ }
5072
+ return "correlated";
5073
+ }
5074
+ function mapOpenInferenceSpan(span, index, version2) {
5075
+ const pathPrefix = `spans[${index}]`;
5076
+ const warnings = [];
5077
+ const unsupportedFields = [];
5078
+ const rawAttributes = readRecordField(span, "attributes") ?? {};
5079
+ const sanitized = sanitizeOpenInferenceAttributes(rawAttributes, pathPrefix);
5080
+ warnings.push(...sanitized.warnings);
5081
+ unsupportedFields.push(...sanitized.unsupportedFields);
5082
+ const attributes = { ...sanitized.attributes };
5083
+ for (const [key, value] of Object.entries(span)) {
5084
+ if (OPENINFERENCE_SPAN_KEYS.has(key)) continue;
5085
+ unsupportedFields.push(`${pathPrefix}.${key}`);
5086
+ if (value === null || typeof value !== "object") {
5087
+ attributes[`openinference.${key}`] = value;
5088
+ } else {
5089
+ attributes[`openinference.${key}.summary`] = summarizeAttributeValue(value);
5090
+ warnings.push({
5091
+ code: "openinference_unsupported_field_summarized",
5092
+ message: `Unsupported OpenInference span field "${key}" was summarized.`,
5093
+ severity: "warning",
5094
+ field: `${pathPrefix}.${key}`
5095
+ });
5096
+ }
5097
+ }
5098
+ const traceId = readStringField(span, ["trace_id", "traceId"]) ?? `trace-${index}`;
5099
+ const spanId = readStringField(span, ["span_id", "spanId"]) ?? `span-${index}`;
5100
+ const parentSpanId = readStringField(span, ["parent_span_id", "parentSpanId"]);
5101
+ const name = readStringField(span, ["name"]) ?? spanId;
5102
+ const startedAt = readOpenInferenceTimestamp(
5103
+ span,
5104
+ ["start_time_unix_nano", "startTimeUnixNano"],
5105
+ ["start_time", "startTime"]
5106
+ );
5107
+ const endedAt = readOpenInferenceTimestamp(
5108
+ span,
5109
+ ["end_time_unix_nano", "endTimeUnixNano"],
5110
+ ["end_time", "endTime"]
5111
+ );
5112
+ const timestamp = startedAt ?? "1970-01-01T00:00:00.000Z";
5113
+ if (startedAt === void 0) {
5114
+ warnings.push({
5115
+ code: "openinference_missing_start_time",
5116
+ message: "OpenInference span is missing a valid start time; using Unix epoch.",
5117
+ severity: "warning",
5118
+ field: `${pathPrefix}.start_time_unix_nano`
5119
+ });
5120
+ unsupportedFields.push(`${pathPrefix}.start_time_unix_nano`);
5121
+ }
5122
+ const { kind, warnings: kindWarnings } = mapOpenInferenceKind(
5123
+ span,
5124
+ rawAttributes,
5125
+ pathPrefix
5126
+ );
5127
+ warnings.push(...kindWarnings);
5128
+ const status = mapOpenInferenceStatus(span.status);
5129
+ const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
5130
+ const errorMessage = isRecord13(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
5131
+ const event = {
5132
+ schemaVersion: "0.2",
5133
+ eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
5134
+ runId: typeof rawAttributes["agent_inspect.run_id"] === "string" ? rawAttributes["agent_inspect.run_id"] : traceId,
5135
+ kind,
5136
+ name,
5137
+ timestamp,
5138
+ confidence: readOpenInferenceConfidence(rawAttributes),
5139
+ source: {
5140
+ type: "otel",
5141
+ name: "openinference",
5142
+ ...version2 !== void 0 ? { version: version2 } : {}
5143
+ },
5144
+ attributes,
5145
+ trace: {
5146
+ traceId,
5147
+ spanId,
5148
+ ...parentSpanId !== void 0 ? { parentSpanId } : {}
5149
+ }
5150
+ };
5151
+ if (status !== void 0) {
5152
+ event.status = status;
5153
+ }
5154
+ if (startedAt !== void 0) {
5155
+ event.startedAt = startedAt;
5156
+ }
5157
+ if (endedAt !== void 0) {
5158
+ event.endedAt = endedAt;
5159
+ }
5160
+ const durationMs = durationBetweenIso(startedAt, endedAt);
5161
+ if (durationMs !== void 0) {
5162
+ event.durationMs = durationMs;
5163
+ }
5164
+ if (tokenUsage !== void 0) {
5165
+ event.tokenUsage = tokenUsage;
5166
+ }
5167
+ if (status === "error") {
5168
+ event.error = {
5169
+ message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OpenInference span error"
5170
+ };
5171
+ }
5172
+ return {
5173
+ event,
5174
+ warnings,
5175
+ unsupportedFields,
5176
+ spanId,
5177
+ ...parentSpanId !== void 0 ? { parentSpanId } : {}
5178
+ };
5179
+ }
5180
+ function mapOpenInferenceEvents(document) {
5181
+ const mapped = document.spans.map(
5182
+ (span, index) => mapOpenInferenceSpan(span, index, document.version)
5183
+ );
5184
+ const spanIdToEventId = new Map(
5185
+ mapped.map((span) => [span.spanId, span.event.eventId])
5186
+ );
5187
+ for (const span of mapped) {
5188
+ if (span.parentSpanId === void 0) continue;
5189
+ span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
5190
+ }
5191
+ return {
5192
+ events: mapped.map((span) => span.event),
5193
+ warnings: mapped.flatMap((span) => span.warnings),
5194
+ unsupportedFields: mapped.flatMap((span) => span.unsupportedFields)
5195
+ };
5196
+ }
5197
+ var openInferenceJsonReader = {
5198
+ format: OPENINFERENCE_READER_FORMAT,
5199
+ name: "OpenInference JSON",
5200
+ async detect(input3) {
5201
+ const resolved = await resolveInput(input3);
5202
+ if (!resolved) return void 0;
5203
+ let parsed;
5204
+ try {
5205
+ parsed = parseJsonDocument(resolved.content);
5206
+ } catch {
5207
+ return void 0;
5208
+ }
5209
+ const document = extractOpenInferenceDocument(parsed);
5210
+ if (!document) return void 0;
5211
+ return {
5212
+ format: OPENINFERENCE_READER_FORMAT,
5213
+ confidence: document.confidence,
5214
+ readerName: "OpenInference JSON",
5215
+ description: document.description,
5216
+ warnings: attachSingleSourceFile(document.warnings, resolved)
5217
+ };
5218
+ },
5219
+ async read(input3) {
5220
+ const resolved = await resolveInput(input3);
5221
+ if (!resolved) {
5222
+ throw new TraceReadError(
5223
+ "unsupported_format",
5224
+ "OpenInference JSON reader requires file, string, or buffer input."
5225
+ );
5226
+ }
5227
+ let parsed;
5228
+ try {
5229
+ parsed = parseJsonDocument(resolved.content);
5230
+ } catch {
5231
+ throw new TraceReadError("unsupported_format", "OpenInference JSON input is not valid JSON.", [
5232
+ {
5233
+ code: "openinference_invalid_json",
5234
+ message: "OpenInference JSON reader could not parse the input as JSON.",
5235
+ severity: "error"
5236
+ }
5237
+ ]);
5238
+ }
5239
+ const document = extractOpenInferenceDocument(parsed);
5240
+ if (!document || document.spans.length === 0) {
5241
+ throw new TraceReadError(
5242
+ "unsupported_format",
5243
+ "No valid OpenInference spans found.",
5244
+ attachSingleSourceFile(
5245
+ document?.warnings ?? [
5246
+ {
5247
+ code: "openinference_no_valid_spans",
5248
+ message: "OpenInference JSON input did not contain valid spans.",
5249
+ severity: "error"
5250
+ }
5251
+ ],
5252
+ resolved
5253
+ )
5254
+ );
5255
+ }
5256
+ const mapped = mapOpenInferenceEvents(document);
5257
+ const warnings = attachSingleSourceFile(
5258
+ [...document.warnings, ...mapped.warnings],
5259
+ resolved
5260
+ );
5261
+ const unsupportedFields = [
5262
+ ...document.unsupportedFields,
5263
+ ...mapped.unsupportedFields
5264
+ ].sort((a, b) => a.localeCompare(b));
5265
+ return {
5266
+ format: OPENINFERENCE_READER_FORMAT,
5267
+ events: mapped.events,
5268
+ runs: persistedInspectEventsToRunTrees(mapped.events, { skipInvalid: true }),
5269
+ warnings,
5270
+ unsupportedFields,
5271
+ sourceFiles: resolved.sourceFiles
5272
+ };
5273
+ }
5274
+ };
5275
+ function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
5276
+ if (!isRecord13(value)) {
5277
+ unsupportedFields.push(field);
5278
+ warnings.push({
5279
+ code: "otlp_attribute_value_invalid",
5280
+ message: "OTLP attribute value was not an AnyValue object.",
5281
+ severity: "warning",
5282
+ field
5283
+ });
5284
+ return void 0;
5285
+ }
5286
+ if (typeof value.stringValue === "string") return value.stringValue;
5287
+ if (typeof value.boolValue === "boolean") return value.boolValue;
5288
+ if (typeof value.intValue === "number" && Number.isFinite(value.intValue)) {
5289
+ return value.intValue;
5290
+ }
5291
+ if (typeof value.intValue === "string" && value.intValue.trim() !== "") {
5292
+ const n = Number(value.intValue);
5293
+ if (Number.isFinite(n)) return n;
5294
+ }
5295
+ if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
5296
+ return value.doubleValue;
5297
+ }
5298
+ if (isRecord13(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
5299
+ return value.arrayValue.values.map(
5300
+ (item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
5301
+ );
5302
+ }
5303
+ if (isRecord13(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
5304
+ const out = {};
5305
+ for (const [index, item] of value.kvlistValue.values.entries()) {
5306
+ if (!isRecord13(item) || typeof item.key !== "string") {
5307
+ unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
5308
+ continue;
5309
+ }
5310
+ out[item.key] = parseOtlpAnyValue(
5311
+ item.value,
5312
+ `${field}.kvlistValue.values[${index}].value`,
5313
+ warnings,
5314
+ unsupportedFields
5315
+ );
5316
+ }
5317
+ return out;
5318
+ }
5319
+ if (typeof value.bytesValue === "string") {
5320
+ unsupportedFields.push(field);
5321
+ warnings.push({
5322
+ code: "otlp_bytes_value_summarized",
5323
+ message: "OTLP bytesValue attribute was summarized instead of decoded.",
5324
+ severity: "warning",
5325
+ field
5326
+ });
5327
+ return { type: "bytes", length: value.bytesValue.length };
5328
+ }
5329
+ unsupportedFields.push(field);
5330
+ warnings.push({
5331
+ code: "otlp_attribute_value_unsupported",
5332
+ message: "OTLP attribute value used an unsupported AnyValue shape.",
5333
+ severity: "warning",
5334
+ field
5335
+ });
5336
+ return void 0;
5337
+ }
5338
+ function parseOtlpAttributes(value, pathPrefix) {
5339
+ const attributes = {};
5340
+ const warnings = [];
5341
+ const unsupportedFields = [];
5342
+ if (value === void 0) {
5343
+ return { attributes, warnings, unsupportedFields };
5344
+ }
5345
+ if (!Array.isArray(value)) {
5346
+ unsupportedFields.push(pathPrefix);
5347
+ warnings.push({
5348
+ code: "otlp_attributes_invalid",
5349
+ message: "OTLP attributes field was not an array.",
5350
+ severity: "warning",
5351
+ field: pathPrefix
5352
+ });
5353
+ return { attributes, warnings, unsupportedFields };
5354
+ }
5355
+ for (const [index, item] of value.entries()) {
5356
+ const field = `${pathPrefix}[${index}]`;
5357
+ if (!isRecord13(item) || typeof item.key !== "string") {
5358
+ unsupportedFields.push(field);
5359
+ warnings.push({
5360
+ code: "otlp_attribute_invalid",
5361
+ message: "Skipped OTLP attribute without a string key.",
5362
+ severity: "warning",
5363
+ field
5364
+ });
5365
+ continue;
5366
+ }
5367
+ const parsed = parseOtlpAnyValue(
5368
+ item.value,
5369
+ `${field}.value`,
5370
+ warnings,
5371
+ unsupportedFields
5372
+ );
5373
+ if (parsed !== void 0) {
5374
+ attributes[item.key] = parsed;
5375
+ }
5376
+ }
5377
+ return { attributes, warnings, unsupportedFields };
5378
+ }
5379
+ function looksLikeOtlpSpan(value) {
5380
+ return isRecord13(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
5381
+ }
5382
+ function extractOtlpDocument(root) {
5383
+ if (!isRecord13(root) || !Array.isArray(root.resourceSpans)) return void 0;
5384
+ const spans = [];
5385
+ const warnings = [];
5386
+ const unsupportedFields = [];
5387
+ for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
5388
+ const resourcePath = `resourceSpans[${resourceIndex}]`;
5389
+ if (!isRecord13(resourceSpan)) {
5390
+ unsupportedFields.push(resourcePath);
5391
+ continue;
5392
+ }
5393
+ const resource = readRecordField(resourceSpan, "resource");
5394
+ const resourceParsed = parseOtlpAttributes(
5395
+ resource?.attributes,
5396
+ `${resourcePath}.resource.attributes`
5397
+ );
5398
+ warnings.push(...resourceParsed.warnings);
5399
+ unsupportedFields.push(...resourceParsed.unsupportedFields);
5400
+ if (!Array.isArray(resourceSpan.scopeSpans)) {
5401
+ unsupportedFields.push(`${resourcePath}.scopeSpans`);
5402
+ warnings.push({
5403
+ code: "otlp_scope_spans_missing",
5404
+ message: "OTLP resourceSpans entry did not contain a scopeSpans array.",
5405
+ severity: "warning",
5406
+ field: `${resourcePath}.scopeSpans`
5407
+ });
5408
+ continue;
5409
+ }
5410
+ for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
5411
+ const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
5412
+ if (!isRecord13(scopeSpan)) {
5413
+ unsupportedFields.push(scopePath);
5414
+ continue;
5415
+ }
5416
+ const scope = readRecordField(scopeSpan, "scope");
5417
+ const scopeParsed = parseOtlpAttributes(
5418
+ scope?.attributes,
5419
+ `${scopePath}.scope.attributes`
5420
+ );
5421
+ warnings.push(...scopeParsed.warnings);
5422
+ unsupportedFields.push(...scopeParsed.unsupportedFields);
5423
+ if (!Array.isArray(scopeSpan.spans)) {
5424
+ unsupportedFields.push(`${scopePath}.spans`);
5425
+ warnings.push({
5426
+ code: "otlp_spans_missing",
5427
+ message: "OTLP scopeSpans entry did not contain a spans array.",
5428
+ severity: "warning",
5429
+ field: `${scopePath}.spans`
5430
+ });
5431
+ continue;
5432
+ }
5433
+ for (const [spanIndex, span] of scopeSpan.spans.entries()) {
5434
+ const spanPath = `${scopePath}.spans[${spanIndex}]`;
5435
+ if (!looksLikeOtlpSpan(span)) {
5436
+ unsupportedFields.push(spanPath);
5437
+ warnings.push({
5438
+ code: "otlp_invalid_span",
5439
+ message: "Skipped OTLP span without required traceId, spanId, or name.",
5440
+ severity: "warning",
5441
+ field: spanPath
5442
+ });
5443
+ continue;
5444
+ }
5445
+ spans.push({
5446
+ span,
5447
+ resourceAttributes: resourceParsed.attributes,
5448
+ scopeAttributes: scopeParsed.attributes,
5449
+ scopeName: readStringField(scope ?? {}, ["name"]),
5450
+ scopeVersion: readStringField(scope ?? {}, ["version"]),
5451
+ pathPrefix: spanPath
5452
+ });
5453
+ }
5454
+ }
5455
+ }
5456
+ if (spans.length === 0) {
5457
+ warnings.push({
5458
+ code: "otlp_no_valid_spans",
5459
+ message: "OTLP JSON payload did not contain any valid spans.",
5460
+ severity: "error"
5461
+ });
5462
+ return {
5463
+ spans,
5464
+ confidence: 0.7,
5465
+ description: "Malformed OTLP JSON trace payload",
5466
+ warnings,
5467
+ unsupportedFields
5468
+ };
5469
+ }
5470
+ return {
5471
+ spans,
5472
+ confidence: 0.93,
5473
+ description: "OTLP JSON trace payload",
5474
+ warnings,
5475
+ unsupportedFields
5476
+ };
5477
+ }
5478
+ function mapOtlpStatus(status) {
5479
+ if (!isRecord13(status)) return void 0;
5480
+ const rawCode = status.code;
5481
+ if (typeof rawCode !== "string") return void 0;
5482
+ switch (rawCode.toUpperCase()) {
5483
+ case "STATUS_CODE_OK":
5484
+ case "OK":
5485
+ return "ok";
5486
+ case "STATUS_CODE_ERROR":
5487
+ case "ERROR":
5488
+ return "error";
5489
+ case "STATUS_CODE_UNSET":
5490
+ case "UNSET":
5491
+ return "unknown";
5492
+ default:
5493
+ return "unknown";
5494
+ }
5495
+ }
5496
+ function readOtlpKind(attributes, pathPrefix) {
5497
+ const warnings = [];
5498
+ const agentInspectKind = attributes["agent_inspect.kind"];
5499
+ if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG") {
5500
+ return { kind: agentInspectKind, warnings };
5501
+ }
5502
+ const operation = attributes["gen_ai.operation.name"];
5503
+ if (typeof operation === "string") {
5504
+ switch (operation) {
5505
+ case "generate_content":
5506
+ case "chat":
5507
+ return { kind: "LLM", warnings };
5508
+ case "execute_tool":
5509
+ return { kind: "TOOL", warnings };
5510
+ case "invoke_agent":
5511
+ return { kind: "AGENT", warnings };
5512
+ default:
5513
+ warnings.push({
5514
+ code: "otlp_gen_ai_operation_semantic_loss",
5515
+ message: `OTLP GenAI operation "${operation}" mapped to AgentInspect LOGIC.`,
5516
+ severity: "warning",
5517
+ field: `${pathPrefix}.attributes.gen_ai.operation.name`
5518
+ });
5519
+ return { kind: "LOGIC", warnings };
5520
+ }
5521
+ }
5522
+ warnings.push({
5523
+ code: "otlp_kind_unknown",
5524
+ message: "OTLP span had no AgentInspect kind or GenAI operation; mapped to LOGIC.",
5525
+ severity: "warning",
5526
+ field: `${pathPrefix}.attributes`
5527
+ });
5528
+ return { kind: "LOGIC", warnings };
5529
+ }
5530
+ function readOtlpTokenUsage(attributes) {
5531
+ const input3 = attributes["gen_ai.usage.input_tokens"];
5532
+ const output2 = attributes["gen_ai.usage.output_tokens"];
5533
+ const usage = {};
5534
+ if (typeof input3 === "number" && Number.isFinite(input3) && input3 >= 0) {
5535
+ usage.input = input3;
5536
+ }
5537
+ if (typeof output2 === "number" && Number.isFinite(output2) && output2 >= 0) {
5538
+ usage.output = output2;
5539
+ }
5540
+ if (usage.input !== void 0 && usage.output !== void 0) {
5541
+ usage.total = usage.input + usage.output;
5542
+ }
5543
+ return Object.keys(usage).length > 0 ? usage : void 0;
5544
+ }
5545
+ function readOtlpConfidence(attributes) {
5546
+ return readOpenInferenceConfidence(attributes);
5547
+ }
5548
+ function sanitizeOtlpAttributes(attributes, pathPrefix) {
5549
+ const ownerPath = pathPrefix.endsWith(".attributes") ? pathPrefix.slice(0, -".attributes".length) : pathPrefix;
5550
+ const sanitized = sanitizeOpenInferenceAttributes(attributes, ownerPath);
5551
+ return {
5552
+ ...sanitized,
5553
+ warnings: sanitized.warnings.map(
5554
+ (warning) => warning.code === "openinference_sensitive_attribute_summarized" ? {
5555
+ ...warning,
5556
+ code: "otlp_sensitive_attribute_summarized",
5557
+ message: "OTLP prompt/output/document attribute(s) were summarized instead of copied verbatim."
5558
+ } : warning
5559
+ )
5560
+ };
5561
+ }
5562
+ function mapOtlpEvents(value, pathPrefix) {
5563
+ const warnings = [];
5564
+ const unsupportedFields = [];
5565
+ if (value === void 0) return { warnings, unsupportedFields };
5566
+ if (!Array.isArray(value)) {
5567
+ unsupportedFields.push(pathPrefix);
5568
+ warnings.push({
5569
+ code: "otlp_events_invalid",
5570
+ message: "OTLP events field was not an array.",
5571
+ severity: "warning",
5572
+ field: pathPrefix
5573
+ });
5574
+ return { warnings, unsupportedFields };
5575
+ }
5576
+ const events = [];
5577
+ for (const [index, event] of value.entries()) {
5578
+ const eventPath = `${pathPrefix}[${index}]`;
5579
+ if (!isRecord13(event)) {
5580
+ unsupportedFields.push(eventPath);
5581
+ continue;
5582
+ }
5583
+ const parsedAttributes = parseOtlpAttributes(
5584
+ event.attributes,
5585
+ `${eventPath}.attributes`
5586
+ );
5587
+ warnings.push(...parsedAttributes.warnings);
5588
+ unsupportedFields.push(...parsedAttributes.unsupportedFields);
5589
+ const sanitized = sanitizeOtlpAttributes(
5590
+ parsedAttributes.attributes,
5591
+ `${eventPath}.attributes`
5592
+ );
5593
+ warnings.push(...sanitized.warnings);
5594
+ unsupportedFields.push(...sanitized.unsupportedFields);
5595
+ const out = {};
5596
+ const name = readStringField(event, ["name"]);
5597
+ if (name !== void 0) {
5598
+ out.name = name;
5599
+ }
5600
+ const timestamp = parseUnixNanoToIso(event.timeUnixNano);
5601
+ if (timestamp !== void 0) {
5602
+ out.timestamp = timestamp;
5603
+ } else if (event.timeUnixNano !== void 0) {
5604
+ unsupportedFields.push(`${eventPath}.timeUnixNano`);
5605
+ warnings.push({
5606
+ code: "otlp_event_timestamp_invalid",
5607
+ message: "OTLP event timeUnixNano could not be parsed.",
5608
+ severity: "warning",
5609
+ field: `${eventPath}.timeUnixNano`
5610
+ });
5611
+ }
5612
+ if (Object.keys(sanitized.attributes).length > 0) {
5613
+ out.attributes = sanitized.attributes;
5614
+ }
5615
+ events.push(out);
5616
+ }
5617
+ return {
5618
+ events: events.length > 0 ? events : void 0,
5619
+ warnings,
5620
+ unsupportedFields
5621
+ };
5622
+ }
5623
+ function mapOtlpSpan(context) {
5624
+ const { span, pathPrefix } = context;
5625
+ const warnings = [];
5626
+ const unsupportedFields = [];
5627
+ const parsedSpanAttributes = parseOtlpAttributes(
5628
+ span.attributes,
5629
+ `${pathPrefix}.attributes`
5630
+ );
5631
+ warnings.push(...parsedSpanAttributes.warnings);
5632
+ unsupportedFields.push(...parsedSpanAttributes.unsupportedFields);
5633
+ const sanitizedSpanAttributes = sanitizeOtlpAttributes(
5634
+ parsedSpanAttributes.attributes,
5635
+ `${pathPrefix}.attributes`
5636
+ );
5637
+ warnings.push(...sanitizedSpanAttributes.warnings);
5638
+ unsupportedFields.push(...sanitizedSpanAttributes.unsupportedFields);
5639
+ const attributes = {
5640
+ ...sanitizedSpanAttributes.attributes
5641
+ };
5642
+ for (const [key, value] of Object.entries(context.resourceAttributes)) {
5643
+ attributes[`resource.${key}`] = value;
5644
+ }
5645
+ for (const [key, value] of Object.entries(context.scopeAttributes)) {
5646
+ attributes[`scope.${key}`] = value;
5647
+ }
5648
+ if (context.scopeName !== void 0) {
5649
+ attributes["scope.name"] = context.scopeName;
5650
+ }
5651
+ if (context.scopeVersion !== void 0) {
5652
+ attributes["scope.version"] = context.scopeVersion;
5653
+ }
5654
+ for (const [key, value] of Object.entries(span)) {
5655
+ if (OTLP_SPAN_KEYS.has(key)) continue;
5656
+ unsupportedFields.push(`${pathPrefix}.${key}`);
5657
+ if (value === null || typeof value !== "object") {
5658
+ attributes[`otlp.${key}`] = value;
5659
+ } else {
5660
+ attributes[`otlp.${key}.summary`] = summarizeAttributeValue(value);
5661
+ warnings.push({
5662
+ code: "otlp_unsupported_field_summarized",
5663
+ message: `Unsupported OTLP span field "${key}" was summarized.`,
5664
+ severity: "warning",
5665
+ field: `${pathPrefix}.${key}`
5666
+ });
5667
+ }
5668
+ }
5669
+ for (const key of [
5670
+ "droppedAttributesCount",
5671
+ "droppedEventsCount",
5672
+ "droppedLinksCount",
5673
+ "links"
5674
+ ]) {
5675
+ if (span[key] !== void 0) {
5676
+ unsupportedFields.push(`${pathPrefix}.${key}`);
5677
+ warnings.push({
5678
+ code: "otlp_span_field_not_mapped",
5679
+ message: `OTLP span field "${key}" is not represented in AgentInspect events.`,
5680
+ severity: "warning",
5681
+ field: `${pathPrefix}.${key}`
5682
+ });
5683
+ }
5684
+ }
5685
+ const events = mapOtlpEvents(span.events, `${pathPrefix}.events`);
5686
+ warnings.push(...events.warnings);
5687
+ unsupportedFields.push(...events.unsupportedFields);
5688
+ if (events.events !== void 0) {
5689
+ attributes["otlp.events"] = events.events;
5690
+ }
5691
+ const traceId = readStringField(span, ["traceId"]) ?? "trace-unknown";
5692
+ const spanId = readStringField(span, ["spanId"]) ?? "span-unknown";
5693
+ const parentSpanId = readStringField(span, ["parentSpanId"]);
5694
+ const startedAt = readOpenInferenceTimestamp(
5695
+ span,
5696
+ ["startTimeUnixNano"],
5697
+ []
5698
+ );
5699
+ const endedAt = readOpenInferenceTimestamp(span, ["endTimeUnixNano"], []);
5700
+ const timestamp = startedAt ?? "1970-01-01T00:00:00.000Z";
5701
+ if (startedAt === void 0) {
5702
+ unsupportedFields.push(`${pathPrefix}.startTimeUnixNano`);
5703
+ warnings.push({
5704
+ code: "otlp_missing_start_time",
5705
+ message: "OTLP span is missing a valid startTimeUnixNano; using Unix epoch.",
5706
+ severity: "warning",
5707
+ field: `${pathPrefix}.startTimeUnixNano`
5708
+ });
5709
+ }
5710
+ const { kind, warnings: kindWarnings } = readOtlpKind(
5711
+ parsedSpanAttributes.attributes,
5712
+ pathPrefix
5713
+ );
5714
+ warnings.push(...kindWarnings);
5715
+ const status = mapOtlpStatus(span.status);
5716
+ const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
5717
+ const errorMessage = isRecord13(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
5718
+ const event = {
5719
+ schemaVersion: "0.2",
5720
+ eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
5721
+ runId: typeof parsedSpanAttributes.attributes["agent_inspect.run_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.run_id"] : traceId,
5722
+ kind,
5723
+ name: readStringField(span, ["name"]) ?? spanId,
5724
+ timestamp,
5725
+ confidence: readOtlpConfidence(parsedSpanAttributes.attributes),
5726
+ source: {
5727
+ type: "otel",
5728
+ name: context.scopeName ?? (typeof context.resourceAttributes["service.name"] === "string" ? context.resourceAttributes["service.name"] : "otlp-json"),
5729
+ ...context.scopeVersion !== void 0 ? { version: context.scopeVersion } : {}
5730
+ },
5731
+ attributes,
5732
+ trace: {
5733
+ traceId,
5734
+ spanId,
5735
+ ...parentSpanId !== void 0 ? { parentSpanId } : {}
5736
+ }
5737
+ };
5738
+ if (status !== void 0) {
5739
+ event.status = status;
5740
+ }
5741
+ if (startedAt !== void 0) {
5742
+ event.startedAt = startedAt;
5743
+ }
5744
+ if (endedAt !== void 0) {
5745
+ event.endedAt = endedAt;
5746
+ }
5747
+ const durationMs = durationBetweenIso(startedAt, endedAt);
5748
+ if (durationMs !== void 0) {
5749
+ event.durationMs = durationMs;
5750
+ }
5751
+ if (tokenUsage !== void 0) {
5752
+ event.tokenUsage = tokenUsage;
5753
+ }
5754
+ if (status === "error") {
5755
+ event.error = {
5756
+ message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OTLP span error"
5757
+ };
5758
+ }
5759
+ return {
5760
+ event,
5761
+ warnings,
5762
+ unsupportedFields,
5763
+ spanId,
5764
+ ...parentSpanId !== void 0 ? { parentSpanId } : {}
5765
+ };
5766
+ }
5767
+ function mapOtlpEventsToPersisted(document) {
5768
+ const mapped = document.spans.map((span) => mapOtlpSpan(span));
5769
+ const spanIdToEventId = new Map(
5770
+ mapped.map((span) => [span.spanId, span.event.eventId])
5771
+ );
5772
+ for (const span of mapped) {
5773
+ if (span.parentSpanId === void 0) continue;
5774
+ span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
5775
+ }
5776
+ return {
5777
+ events: mapped.map((span) => span.event),
5778
+ warnings: mapped.flatMap((span) => span.warnings),
5779
+ unsupportedFields: mapped.flatMap((span) => span.unsupportedFields)
5780
+ };
5781
+ }
5782
+ var otlpJsonReader = {
5783
+ format: OTLP_READER_FORMAT,
5784
+ name: "OTLP JSON",
5785
+ async detect(input3) {
5786
+ const resolved = await resolveInput(input3);
5787
+ if (!resolved) return void 0;
5788
+ let parsed;
5789
+ try {
5790
+ parsed = parseJsonDocument(resolved.content);
5791
+ } catch {
5792
+ return void 0;
5793
+ }
5794
+ const document = extractOtlpDocument(parsed);
5795
+ if (!document) return void 0;
5796
+ return {
5797
+ format: OTLP_READER_FORMAT,
5798
+ confidence: document.confidence,
5799
+ readerName: "OTLP JSON",
5800
+ description: document.description,
5801
+ warnings: attachSingleSourceFile(document.warnings, resolved)
5802
+ };
5803
+ },
5804
+ async read(input3) {
5805
+ const resolved = await resolveInput(input3);
5806
+ if (!resolved) {
5807
+ throw new TraceReadError(
5808
+ "unsupported_format",
5809
+ "OTLP JSON reader requires file, string, or buffer input."
5810
+ );
5811
+ }
5812
+ let parsed;
5813
+ try {
5814
+ parsed = parseJsonDocument(resolved.content);
5815
+ } catch {
5816
+ throw new TraceReadError("unsupported_format", "OTLP JSON input is not valid JSON.", [
5817
+ {
5818
+ code: "otlp_invalid_json",
5819
+ message: "OTLP JSON reader could not parse the input as JSON.",
5820
+ severity: "error"
5821
+ }
5822
+ ]);
5823
+ }
5824
+ const document = extractOtlpDocument(parsed);
5825
+ if (!document || document.spans.length === 0) {
5826
+ throw new TraceReadError(
5827
+ "unsupported_format",
5828
+ "No valid OTLP spans found.",
5829
+ attachSingleSourceFile(
5830
+ document?.warnings ?? [
5831
+ {
5832
+ code: "otlp_no_valid_spans",
5833
+ message: "OTLP JSON input did not contain valid spans.",
5834
+ severity: "error"
5835
+ }
5836
+ ],
5837
+ resolved
5838
+ )
5839
+ );
5840
+ }
5841
+ const mapped = mapOtlpEventsToPersisted(document);
5842
+ const warnings = attachSingleSourceFile(
5843
+ [...document.warnings, ...mapped.warnings],
5844
+ resolved
5845
+ );
5846
+ const unsupportedFields = [
5847
+ ...document.unsupportedFields,
5848
+ ...mapped.unsupportedFields
5849
+ ].sort((a, b) => a.localeCompare(b));
5850
+ return {
5851
+ format: OTLP_READER_FORMAT,
5852
+ events: mapped.events,
5853
+ runs: persistedInspectEventsToRunTrees(mapped.events, { skipInvalid: true }),
5854
+ warnings,
5855
+ unsupportedFields,
5856
+ sourceFiles: resolved.sourceFiles
5857
+ };
5858
+ }
5859
+ };
5860
+ var agentInspectJsonlReader = {
5861
+ format: "agent-inspect-jsonl",
5862
+ name: "AgentInspect JSONL",
5863
+ async detect(input3) {
5864
+ const resolved = await resolveInput(input3);
5865
+ if (!resolved) return void 0;
5866
+ const detected = detectJsonlFormat(resolved.content);
5867
+ if (detected.validRows === 0 || detected.format === "empty") {
5868
+ return void 0;
5869
+ }
5870
+ return {
5871
+ format: "agent-inspect-jsonl",
5872
+ confidence: 0.95,
5873
+ readerName: "AgentInspect JSONL",
5874
+ description: agentInspectFormatLabel(detected.format),
5875
+ warnings: attachSingleSourceFile(detected.warnings, resolved)
5876
+ };
5877
+ },
5878
+ async read(input3) {
5879
+ const resolved = await resolveInput(input3);
5880
+ if (!resolved) {
5881
+ throw new Error("AgentInspect JSONL reader requires file, directory, string, or buffer input.");
5882
+ }
5883
+ const parsed = parseTraceJsonl(resolved.content, { warnings: false });
5884
+ if (parsed.sourceEventCount === 0) {
5885
+ throw new Error("No valid AgentInspect JSONL events found.");
5886
+ }
5887
+ const events = persistedEventsForParsedTrace(parsed);
5888
+ return {
5889
+ format: agentInspectFormatLabel(parsed.format),
5890
+ events,
5891
+ runs: persistedInspectEventsToRunTrees(events, { skipInvalid: true }),
5892
+ warnings: parsed.format === "mixed" ? attachSingleSourceFile(
5893
+ [
5894
+ {
5895
+ code: "mixed_agent_inspect_jsonl",
5896
+ message: "Trace input mixes schemaVersion 0.1 and 0.2 rows; events were normalized for reading.",
5897
+ severity: "warning"
5898
+ }
5899
+ ],
5900
+ resolved
5901
+ ) : [],
5902
+ unsupportedFields: [],
5903
+ sourceFiles: resolved.sourceFiles
5904
+ };
5905
+ }
5906
+ };
5907
+ var DEFAULT_TRACE_READERS = [
5908
+ agentInspectJsonlReader,
5909
+ openInferenceJsonReader,
5910
+ otlpJsonReader
5911
+ ];
5912
+ async function detectTraceFormat(input3, options = {}) {
5913
+ const readers = options.readers ?? DEFAULT_TRACE_READERS;
5914
+ if (options.format !== void 0) {
5915
+ const reader = findReaderByFormat(options.format, readers);
5916
+ if (!reader) {
5917
+ return {
5918
+ status: "unsupported",
5919
+ candidates: [],
5920
+ warnings: [
5921
+ {
5922
+ code: "unsupported_format",
5923
+ message: `No trace reader is registered for format "${options.format}".`,
5924
+ severity: "error"
5925
+ }
5926
+ ]
5927
+ };
5928
+ }
5929
+ return {
5930
+ status: "detected",
5931
+ format: reader.format,
5932
+ candidates: [
5933
+ {
5934
+ format: reader.format,
5935
+ confidence: 1,
5936
+ readerName: reader.name,
5937
+ description: "Explicit format override"
5938
+ }
5939
+ ],
5940
+ warnings: []
5941
+ };
5942
+ }
5943
+ const candidates = [];
5944
+ const warnings = [];
5945
+ for (const reader of readers) {
5946
+ try {
5947
+ const candidate = await reader.detect(input3);
5948
+ if (candidate !== void 0) {
5949
+ candidates.push(normalizeCandidate(reader, candidate));
5950
+ }
5951
+ } catch (error) {
5952
+ if (error instanceof TraceReadError) {
5953
+ warnings.push(...error.warnings);
5954
+ continue;
5955
+ }
5956
+ warnings.push({
5957
+ code: "reader_detect_failed",
5958
+ message: error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed during detection.`,
5959
+ severity: "warning"
5960
+ });
5961
+ }
5962
+ }
5963
+ const sorted = sortCandidates(
5964
+ candidates.filter((candidate) => candidate.confidence >= MIN_DETECTION_CONFIDENCE)
5965
+ );
5966
+ const candidateWarnings = collectWarnings(sorted);
5967
+ const lowConfidenceWarnings = candidates.length > sorted.length ? [
5968
+ {
5969
+ code: "low_confidence_candidates",
5970
+ message: `Ignored ${candidates.length - sorted.length} low-confidence format candidate(s).`,
5971
+ severity: "info"
5972
+ }
5973
+ ] : [];
5974
+ const allWarnings = dedupeWarnings([
5975
+ ...warnings,
5976
+ ...candidateWarnings,
5977
+ ...lowConfidenceWarnings
5978
+ ]);
5979
+ if (sorted.length === 0) {
5980
+ return {
5981
+ status: "unsupported",
5982
+ candidates: [],
5983
+ warnings: allWarnings
5984
+ };
5985
+ }
5986
+ const [best, second] = sorted;
5987
+ if (second !== void 0 && best.confidence - second.confidence <= AMBIGUOUS_CONFIDENCE_DELTA) {
5988
+ return {
5989
+ status: "ambiguous",
5990
+ candidates: sorted,
5991
+ warnings: [
5992
+ ...allWarnings,
5993
+ {
5994
+ code: "ambiguous_format_candidates",
5995
+ message: `Top trace format candidates are within ${AMBIGUOUS_CONFIDENCE_DELTA} confidence.`,
5996
+ severity: "warning"
5997
+ }
5998
+ ]
5999
+ };
6000
+ }
6001
+ return {
6002
+ status: "detected",
6003
+ format: best.format,
6004
+ candidates: sorted,
6005
+ warnings: allWarnings
6006
+ };
6007
+ }
6008
+ async function readTrace(input3, options = {}) {
6009
+ const readers = options.readers ?? DEFAULT_TRACE_READERS;
6010
+ const detection = await detectTraceFormat(input3, options);
6011
+ if (detection.status === "unsupported" || detection.format === void 0) {
6012
+ throw new TraceReadError(
6013
+ "unsupported_format",
6014
+ "No trace reader could detect the input format.",
6015
+ detection.warnings
6016
+ );
6017
+ }
6018
+ if (detection.status === "ambiguous") {
6019
+ throw new TraceReadError(
6020
+ "ambiguous_format",
6021
+ "Multiple trace readers matched the input with equal confidence.",
6022
+ detection.warnings
6023
+ );
6024
+ }
6025
+ const reader = findReaderByFormat(detection.format, readers);
6026
+ if (!reader) {
6027
+ throw new TraceReadError(
6028
+ "unsupported_format",
6029
+ `No trace reader is registered for format "${detection.format}".`,
6030
+ detection.warnings
6031
+ );
6032
+ }
6033
+ try {
6034
+ const result = await reader.read(input3, { format: detection.format });
6035
+ return {
6036
+ ...result,
6037
+ format: result.format || detection.format,
6038
+ warnings: [...detection.warnings, ...result.warnings]
6039
+ };
6040
+ } catch (error) {
6041
+ if (error instanceof TraceReadError) {
6042
+ throw new TraceReadError(
6043
+ error.code,
6044
+ error.message,
6045
+ dedupeWarnings([...detection.warnings, ...error.warnings])
6046
+ );
6047
+ }
6048
+ throw new TraceReadError(
6049
+ "reader_failed",
6050
+ error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed.`,
6051
+ detection.warnings
6052
+ );
6053
+ }
6054
+ }
6055
+ function openTrace(input3, options = {}) {
6056
+ return readTrace(input3, options);
6057
+ }
6058
+
6059
+ // packages/core/src/diff/comparable.ts
6060
+ function extractOutputPreview(meta) {
3941
6061
  if (meta === void 0) return void 0;
3942
6062
  if ("outputPreview" in meta) return meta.outputPreview;
3943
6063
  if ("resultPreview" in meta) return meta.resultPreview;
@@ -4075,13 +6195,13 @@ function pairSteps(left, right) {
4075
6195
  return pairs;
4076
6196
  }
4077
6197
  function compareLeafSteps(L, R, segments, opts, out) {
4078
- const path8 = buildPath(segments);
6198
+ const path10 = buildPath(segments);
4079
6199
  if (L.name !== R.name) {
4080
6200
  out.push({
4081
6201
  kind: "structure",
4082
6202
  severity: "warning",
4083
6203
  message: "Step name differs",
4084
- path: path8,
6204
+ path: path10,
4085
6205
  left: L.name,
4086
6206
  right: R.name
4087
6207
  });
@@ -4091,7 +6211,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
4091
6211
  kind: "step-type",
4092
6212
  severity: "warning",
4093
6213
  message: "Step type differs",
4094
- path: path8,
6214
+ path: path10,
4095
6215
  left: L.type,
4096
6216
  right: R.type
4097
6217
  });
@@ -4101,7 +6221,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
4101
6221
  kind: "step-status",
4102
6222
  severity: "warning",
4103
6223
  message: "Step status differs",
4104
- path: path8,
6224
+ path: path10,
4105
6225
  left: L.status,
4106
6226
  right: R.status
4107
6227
  });
@@ -4113,7 +6233,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
4113
6233
  kind: "error",
4114
6234
  severity: "error",
4115
6235
  message: "Step error message differs",
4116
- path: path8,
6236
+ path: path10,
4117
6237
  left: le || void 0,
4118
6238
  right: re || void 0
4119
6239
  });
@@ -4131,7 +6251,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
4131
6251
  kind: "duration",
4132
6252
  severity: "info",
4133
6253
  message: "Step duration differs",
4134
- path: path8,
6254
+ path: path10,
4135
6255
  left: ld,
4136
6256
  right: rd
4137
6257
  });
@@ -4144,7 +6264,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
4144
6264
  kind: "metadata",
4145
6265
  severity: "info",
4146
6266
  message: "Step metadata differs",
4147
- path: path8,
6267
+ path: path10,
4148
6268
  left: L.metadata,
4149
6269
  right: R.metadata
4150
6270
  });
@@ -4156,7 +6276,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
4156
6276
  kind: "output",
4157
6277
  severity: "info",
4158
6278
  message: "Output preview differs",
4159
- path: path8,
6279
+ path: path10,
4160
6280
  left: L.outputPreview,
4161
6281
  right: R.outputPreview
4162
6282
  });
@@ -4594,10 +6714,10 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
4594
6714
  return 3;
4595
6715
  }
4596
6716
  if ("TERM_PROGRAM" in env) {
4597
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
6717
+ const version2 = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
4598
6718
  switch (env.TERM_PROGRAM) {
4599
6719
  case "iTerm.app": {
4600
- return version >= 3 ? 3 : 2;
6720
+ return version2 >= 3 ? 3 : 2;
4601
6721
  }
4602
6722
  case "Apple_Terminal": {
4603
6723
  return 2;
@@ -4806,11 +6926,11 @@ createChalk({ level: stderrColor ? stderrColor.level : 0 });
4806
6926
  var source_default = chalk;
4807
6927
 
4808
6928
  // packages/core/src/diff/renderer.ts
4809
- function formatPath(path8) {
4810
- if (path8 === void 0 || path8.path.length === 0) {
6929
+ function formatPath(path10) {
6930
+ if (path10 === void 0 || path10.path.length === 0) {
4811
6931
  return "(run)";
4812
6932
  }
4813
- return path8.path.map((s) => s.name).join(" > ");
6933
+ return path10.path.map((s) => s.name).join(" > ");
4814
6934
  }
4815
6935
  function formatValue(v, verbose) {
4816
6936
  if (v === void 0) return "(undefined)";
@@ -5717,18 +7837,38 @@ async function clean(options = {}) {
5717
7837
  process.exitCode = 1;
5718
7838
  }
5719
7839
  }
5720
-
5721
- // packages/cli/src/read-run.ts
5722
- async function readRunTraceEvents(runId, traceDir) {
5723
- const raw = await readTraceFile(runId, traceDir);
5724
- if (raw === void 0) {
5725
- return void 0;
7840
+ function mapReaderFormat(format) {
7841
+ switch (format) {
7842
+ case "agent-inspect-v0.1-jsonl":
7843
+ return "0.1";
7844
+ case "agent-inspect-v0.2-jsonl":
7845
+ return "0.2";
7846
+ case "agent-inspect-mixed-jsonl":
7847
+ return "mixed";
7848
+ default:
7849
+ return "empty";
5726
7850
  }
5727
- const parsed = parseTraceJsonl(raw);
5728
- if (parsed.format === "empty") {
5729
- return { events: [], format: "empty" };
7851
+ }
7852
+ function isMissingFileError(error) {
7853
+ return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
7854
+ }
7855
+ async function readRunTraceEvents(runId, traceDir) {
7856
+ const filePath = getTraceFilePath(runId, traceDir);
7857
+ try {
7858
+ await access(filePath);
7859
+ const result = await openTrace(
7860
+ { type: "file", path: filePath },
7861
+ { format: "agent-inspect-jsonl" }
7862
+ );
7863
+ const events = persistedInspectEventsToTraceEvents(result.events);
7864
+ return {
7865
+ events,
7866
+ format: events.length > 0 ? mapReaderFormat(result.format) : "empty"
7867
+ };
7868
+ } catch (error) {
7869
+ if (isMissingFileError(error)) return void 0;
7870
+ throw error;
5730
7871
  }
5731
- return { events: parsed.events, format: parsed.format };
5732
7872
  }
5733
7873
 
5734
7874
  // packages/cli/src/view.ts
@@ -6799,6 +8939,149 @@ async function reportCommand(runId, options = {}) {
6799
8939
  console.log(result.content);
6800
8940
  }
6801
8941
  }
8942
+ async function readStdin(stdin) {
8943
+ stdin.setEncoding("utf8");
8944
+ let content = "";
8945
+ for await (const chunk of stdin) {
8946
+ content += typeof chunk === "string" ? chunk : String(chunk);
8947
+ }
8948
+ return content;
8949
+ }
8950
+ async function inputFromPathOrStdin(input3, stdin) {
8951
+ if (input3 === void 0 || input3 === "-") {
8952
+ return { type: "string", content: await readStdin(stdin) };
8953
+ }
8954
+ const stats = await stat(input3);
8955
+ if (stats.isDirectory()) return { type: "directory", path: input3 };
8956
+ return { type: "file", path: input3 };
8957
+ }
8958
+ function printWarnings(result) {
8959
+ if (result.warnings.length === 0 && result.unsupportedFields.length === 0) {
8960
+ return;
8961
+ }
8962
+ console.log("");
8963
+ console.log("Diagnostics");
8964
+ for (const warning of result.warnings) {
8965
+ const where = [
8966
+ warning.sourceFile,
8967
+ warning.line !== void 0 ? `line ${warning.line}` : void 0,
8968
+ warning.field
8969
+ ].filter((value) => value !== void 0).join(" ");
8970
+ console.log(
8971
+ `- ${warning.code}: ${warning.message}${where !== "" ? ` (${where})` : ""}`
8972
+ );
8973
+ }
8974
+ for (const field of result.unsupportedFields) {
8975
+ console.log(`- unsupported: ${field}`);
8976
+ }
8977
+ }
8978
+ function printNode(node, depth) {
8979
+ const ev = node.event;
8980
+ const status = ev.status !== void 0 ? ` ${ev.status}` : "";
8981
+ const duration = ev.durationMs !== void 0 ? ` ${formatDuration2(ev.durationMs)}` : "";
8982
+ console.log(`${getIndent(depth)}${ev.kind.toLowerCase()}: ${ev.name}${status}${duration}`);
8983
+ for (const child of node.children) {
8984
+ printNode(child, depth + 1);
8985
+ }
8986
+ }
8987
+ function printRun(result, run) {
8988
+ console.log(`Format: ${result.format}`);
8989
+ console.log(`Run: ${run.runId}`);
8990
+ if (run.name !== void 0) console.log(`Name: ${run.name}`);
8991
+ if (run.status !== void 0) console.log(`Status: ${run.status}`);
8992
+ if (run.startedAt !== void 0) {
8993
+ console.log(`Started: ${formatTimestamp(run.startedAt)}`);
8994
+ }
8995
+ if (run.durationMs !== void 0) {
8996
+ console.log(`Duration: ${formatDuration2(run.durationMs)}`);
8997
+ }
8998
+ console.log(`Events: ${run.metadata.totalEvents}`);
8999
+ for (const node of run.children) {
9000
+ printNode(node, 0);
9001
+ }
9002
+ }
9003
+ function selectRun(result, runId) {
9004
+ if (runId !== void 0) {
9005
+ return result.runs.find((run) => run.runId === runId);
9006
+ }
9007
+ return result.runs.length === 1 ? result.runs[0] : void 0;
9008
+ }
9009
+ function printMultipleRuns(result) {
9010
+ console.error(
9011
+ `Trace contains ${result.runs.length} runs. Re-run with --run <run-id>.`
9012
+ );
9013
+ for (const run of result.runs) {
9014
+ const bits = [
9015
+ run.runId,
9016
+ run.name !== void 0 ? `name=${run.name}` : void 0,
9017
+ run.status !== void 0 ? `status=${run.status}` : void 0,
9018
+ `events=${run.metadata.totalEvents}`
9019
+ ].filter((value) => value !== void 0);
9020
+ console.error(`- ${bits.join(" ")}`);
9021
+ }
9022
+ }
9023
+ function writeJson(output2) {
9024
+ console.log(JSON.stringify(output2, null, 2));
9025
+ }
9026
+ async function openCommand(input3, options = {}, stdin = process.stdin) {
9027
+ try {
9028
+ const traceInput = await inputFromPathOrStdin(input3, stdin);
9029
+ const result = await openTrace(traceInput, {
9030
+ ...options.format !== void 0 ? { format: options.format } : {}
9031
+ });
9032
+ const selected = selectRun(result, options.run);
9033
+ if (selected === void 0) {
9034
+ process.exitCode = 1;
9035
+ const message = options.run !== void 0 ? `Run not found: ${options.run}` : `Trace contains ${result.runs.length} runs. Specify --run <run-id>.`;
9036
+ if (options.json) {
9037
+ writeJson({
9038
+ format: result.format,
9039
+ sourceFiles: result.sourceFiles,
9040
+ warnings: result.warnings,
9041
+ unsupportedFields: result.unsupportedFields,
9042
+ runs: result.runs,
9043
+ error: { message }
9044
+ });
9045
+ } else if (options.run !== void 0) {
9046
+ console.error(message);
9047
+ } else {
9048
+ printMultipleRuns(result);
9049
+ }
9050
+ return;
9051
+ }
9052
+ if (options.json) {
9053
+ writeJson({
9054
+ format: result.format,
9055
+ sourceFiles: result.sourceFiles,
9056
+ warnings: result.warnings,
9057
+ unsupportedFields: result.unsupportedFields,
9058
+ selectedRunId: selected.runId,
9059
+ runs: [selected]
9060
+ });
9061
+ return;
9062
+ }
9063
+ printRun(result, selected);
9064
+ if (options.diagnostics) {
9065
+ printWarnings(result);
9066
+ }
9067
+ } catch (error) {
9068
+ process.exitCode = 1;
9069
+ const message = error instanceof Error ? error.message : String(error);
9070
+ const code = error instanceof TraceReadError ? error.code : void 0;
9071
+ const warnings = error instanceof TraceReadError ? error.warnings : [];
9072
+ if (options.json) {
9073
+ writeJson({
9074
+ warnings,
9075
+ error: { ...code !== void 0 ? { code } : {}, message }
9076
+ });
9077
+ return;
9078
+ }
9079
+ console.error(message);
9080
+ if (options.diagnostics) {
9081
+ printWarnings({ warnings, unsupportedFields: [] });
9082
+ }
9083
+ }
9084
+ }
6802
9085
 
6803
9086
  // packages/cli/src/index.ts
6804
9087
  function runCommand(action) {
@@ -6809,7 +9092,7 @@ function runCommand(action) {
6809
9092
  });
6810
9093
  }
6811
9094
  function createCliProgram() {
6812
- const program = new Command("agent-inspect").description("Local-first execution-tree debugger for AI agents").version("1.0.0");
9095
+ const program = new Command("agent-inspect").description("Local-first execution-tree debugger for AI agents").version(version);
6813
9096
  program.command("list").description("List recent AgentInspect runs").option("--dir <path>", "trace directory").option("--limit <number>", "max runs to show (default 20, max 100)").addOption(
6814
9097
  new Option("--status <status>", "filter by run status").choices([
6815
9098
  "running",
@@ -6892,6 +9175,15 @@ function createCliProgram() {
6892
9175
  ).action((runId, opts) => {
6893
9176
  runCommand(() => exportCommand(runId, opts));
6894
9177
  });
9178
+ program.command("open").description("Open any supported local trace through the reader pipeline").argument("[input]", "trace file, directory, or - for stdin").addOption(
9179
+ new Option("--format <format>", "trace input format").choices([
9180
+ "agent-inspect-jsonl",
9181
+ "openinference-json",
9182
+ "otlp-json"
9183
+ ])
9184
+ ).option("--json", "print result as JSON").option("--diagnostics", "print reader warnings and unsupported fields").option("--run <run-id>", "select a run when the trace contains multiple runs").action((input3, opts) => {
9185
+ runCommand(() => openCommand(input3, opts));
9186
+ });
6895
9187
  program.command("diff").description("Compare two local AgentInspect JSONL traces (read-only)").argument("<left-run-id>", "first run id").argument("<right-run-id>", "second run id").option("--dir <path>", "trace directory").option("--json", "print diff result as JSON").option("--ignore-duration", "omit duration comparisons").option(
6896
9188
  "--duration-threshold <duration>",
6897
9189
  "ignore duration deltas at or below this (e.g. 500ms, 2s, 1m)"
@@ -6947,7 +9239,7 @@ function createCliProgram() {
6947
9239
  ).option("-o, --output <path>", "write report to file (creates parent dirs)").option("--json", "emit JSON wrapper (includes content when writing to stdout)").option("--include-attributes", "include bounded step attributes in tree section").option("--no-errors", "omit error details from tree section").option("--no-correlation", "omit correlation metadata from what section").addOption(
6948
9240
  new Option(
6949
9241
  "--redaction-profile <profile>",
6950
- "redaction profile for tree section: local, share, strict (default: local)"
9242
+ "redaction profile for entire report: local, share, strict (default: local)"
6951
9243
  ).choices(["local", "share", "strict"])
6952
9244
  ).action((runId, opts) => {
6953
9245
  runCommand(() => reportCommand(runId, opts));