@agent-inspect/viewer 6.31.7 → 6.31.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -771,7 +771,18 @@ async function readTraceEventsFromFile(filePath) {
771
771
  }
772
772
 
773
773
  // packages/core/src/context.ts
774
- new async_hooks.AsyncLocalStorage();
774
+ var GLOBAL_CONTEXT_ALS_KEY = "agent-inspect:execution-context-als:v1";
775
+ function getSharedContextStorage() {
776
+ const host = globalThis;
777
+ const existing = host[GLOBAL_CONTEXT_ALS_KEY];
778
+ if (existing instanceof async_hooks.AsyncLocalStorage) {
779
+ return existing;
780
+ }
781
+ const created = new async_hooks.AsyncLocalStorage();
782
+ host[GLOBAL_CONTEXT_ALS_KEY] = created;
783
+ return created;
784
+ }
785
+ getSharedContextStorage();
775
786
 
776
787
  // packages/core/src/outcomes/types.ts
777
788
  var OBSERVED_OUTCOME_METHODS = [
@@ -1374,7 +1385,7 @@ function enrichSessionSummary(summary, runs, options = {}) {
1374
1385
  }
1375
1386
  const lastMs = latestActivityMs(sessionRuns);
1376
1387
  const lastActivity = lastMs > 0 ? new Date(lastMs).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString();
1377
- const retryCount = summary.retries.filter(
1388
+ const retryCount2 = summary.retries.filter(
1378
1389
  (retry) => retry.retryOf !== void 0 || (retry.attempt ?? 0) > 1
1379
1390
  ).length;
1380
1391
  return {
@@ -1388,7 +1399,7 @@ function enrichSessionSummary(summary, runs, options = {}) {
1388
1399
  workflowId,
1389
1400
  lastError: deriveLastError(sessionRuns),
1390
1401
  lastActivity,
1391
- retryCount,
1402
+ retryCount: retryCount2,
1392
1403
  observationSummary: deriveObservationSummary(sessionRuns),
1393
1404
  checkSummary: deriveCheckSummary(sessionRuns)
1394
1405
  };
@@ -2261,6 +2272,13 @@ function metadataStringAttr(event, keys) {
2261
2272
  }
2262
2273
  return void 0;
2263
2274
  }
2275
+ function numericAttr(event, keys) {
2276
+ for (const key of keys) {
2277
+ const value = event.attributes?.[key];
2278
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2279
+ }
2280
+ return void 0;
2281
+ }
2264
2282
  function stripPrefix(name, prefixes) {
2265
2283
  for (const prefix of prefixes) {
2266
2284
  if (name.startsWith(prefix)) return name.slice(prefix.length);
@@ -2316,9 +2334,12 @@ function llmFinishReason(event) {
2316
2334
  const keys = ["finishReason", "rawFinishReason", "finish_reason"];
2317
2335
  return stringAttr(event, keys) ?? metadataStringAttr(event, keys);
2318
2336
  }
2337
+ function retryCount(event) {
2338
+ return numericAttr(event, ["retryCount", "retryAttempt", "retry_attempt", "attempt"]);
2339
+ }
2319
2340
  function finishedEvents(context, kind) {
2320
2341
  return semanticEvents(context).filter(
2321
- (event) => (event.kind === kind) && event.status !== "running"
2342
+ (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
2322
2343
  );
2323
2344
  }
2324
2345
  function toolInvocationEvents(context) {
@@ -2383,6 +2404,35 @@ function createRunDurationRule(options) {
2383
2404
  }
2384
2405
  };
2385
2406
  }
2407
+ function createRunDepthRule(options) {
2408
+ return {
2409
+ id: "run.depth",
2410
+ category: "run",
2411
+ defaultSeverity: "error",
2412
+ evaluate(context) {
2413
+ const nodes = [...context.nodesByEventId.values()];
2414
+ const maxDepth = nodes.reduce((max, node) => Math.max(max, node.depth), 0);
2415
+ if (maxDepth <= options.maxDepth) return [];
2416
+ const deepest = nodes.filter((node) => node.depth === maxDepth);
2417
+ return [
2418
+ failFinding(
2419
+ "run.depth",
2420
+ `Run depth ${maxDepth} exceeded ${options.maxDepth}.`,
2421
+ deepest.map((node) => ({
2422
+ runId: node.event.runId,
2423
+ eventId: node.event.eventId,
2424
+ parentId: node.event.parentId,
2425
+ kind: node.event.kind,
2426
+ name: node.event.name,
2427
+ status: node.event.status
2428
+ })),
2429
+ { maxDepth: options.maxDepth },
2430
+ maxDepth
2431
+ )
2432
+ ];
2433
+ }
2434
+ };
2435
+ }
2386
2436
  function createToolUsageRule(options) {
2387
2437
  return {
2388
2438
  id: "tool.usage",
@@ -2447,6 +2497,45 @@ function createToolUsageRule(options) {
2447
2497
  }
2448
2498
  };
2449
2499
  }
2500
+ function createToolFailureRule(options) {
2501
+ return {
2502
+ id: "tool.failures",
2503
+ category: "tool",
2504
+ defaultSeverity: "error",
2505
+ evaluate(context) {
2506
+ const tools = finishedEvents(context, "TOOL");
2507
+ const failures = tools.filter((event) => event.status === "error");
2508
+ const retries = tools.map((event) => ({ event, count: retryCount(event) })).filter((item) => item.count !== void 0);
2509
+ const findings = [];
2510
+ if (options.maxFailures !== void 0 && failures.length > options.maxFailures) {
2511
+ findings.push(
2512
+ failFinding(
2513
+ "tool.failures",
2514
+ `Tool failure count ${failures.length} exceeded ${options.maxFailures}.`,
2515
+ failures.map((event) => eventEvidence(event)),
2516
+ { maxFailures: options.maxFailures },
2517
+ failures.length
2518
+ )
2519
+ );
2520
+ }
2521
+ if (options.maxRetries !== void 0) {
2522
+ const excessiveRetries = retries.filter((item) => item.count > options.maxRetries);
2523
+ if (excessiveRetries.length > 0) {
2524
+ findings.push(
2525
+ failFinding(
2526
+ "tool.failures",
2527
+ `Tool retry count exceeded ${options.maxRetries}.`,
2528
+ excessiveRetries.map((item) => eventEvidence(item.event, "attributes.retryCount")),
2529
+ { maxRetries: options.maxRetries },
2530
+ excessiveRetries.map((item) => ({ tool: toolName(item.event), retries: item.count }))
2531
+ )
2532
+ );
2533
+ }
2534
+ }
2535
+ return findings;
2536
+ }
2537
+ };
2538
+ }
2450
2539
  function createLlmUsageRule(options) {
2451
2540
  return {
2452
2541
  id: "llm.usage",
@@ -3892,8 +3981,13 @@ function persistedInspectEventsToInspectEvents(events, options) {
3892
3981
  }
3893
3982
 
3894
3983
  // packages/core/src/persisted/tree-bridge.ts
3984
+ function logicalToPersisted(event) {
3985
+ const { sourceEventIds: _sourceEventIds, projection: _projection, ...rest } = event;
3986
+ return rest;
3987
+ }
3895
3988
  function persistedInspectEventsToRunTrees(events, options) {
3896
- const inspectEvents = persistedInspectEventsToInspectEvents(events, {
3989
+ const sourceEvents = options?.coalesceLifecycle === true ? projectLogicalEvents(events).logicalEvents.map(logicalToPersisted) : events;
3990
+ const inspectEvents = persistedInspectEventsToInspectEvents(sourceEvents, {
3897
3991
  skipInvalid: options?.skipInvalid
3898
3992
  });
3899
3993
  return new TreeBuilder().build(inspectEvents);
@@ -4587,7 +4681,7 @@ function mapOpenInferenceSpan(span, index, version) {
4587
4681
  const errorMessage = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
4588
4682
  const event = {
4589
4683
  schemaVersion: "0.2",
4590
- eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
4684
+ eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : `${traceId}:${spanId}`,
4591
4685
  runId: typeof rawAttributes["agent_inspect.run_id"] === "string" ? rawAttributes["agent_inspect.run_id"] : traceId,
4592
4686
  kind,
4593
4687
  name,
@@ -4638,12 +4732,16 @@ function mapOpenInferenceEvents(document) {
4638
4732
  const mapped = document.spans.map(
4639
4733
  (span, index) => mapOpenInferenceSpan(span, index, document.version)
4640
4734
  );
4641
- const spanIdToEventId = new Map(
4642
- mapped.map((span) => [span.spanId, span.event.eventId])
4735
+ const spanKeyToEventId = new Map(
4736
+ mapped.map((span) => {
4737
+ const traceId = typeof span.event.trace?.traceId === "string" ? span.event.trace.traceId : "";
4738
+ return [`${traceId}:${span.spanId}`, span.event.eventId];
4739
+ })
4643
4740
  );
4644
4741
  for (const span of mapped) {
4645
4742
  if (span.parentSpanId === void 0) continue;
4646
- span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
4743
+ const traceId = typeof span.event.trace?.traceId === "string" ? span.event.trace.traceId : "";
4744
+ span.event.parentId = spanKeyToEventId.get(`${traceId}:${span.parentSpanId}`) ?? span.parentSpanId;
4647
4745
  }
4648
4746
  return {
4649
4747
  events: mapped.map((span) => span.event),
@@ -5192,7 +5290,7 @@ function mapOtlpSpan(context) {
5192
5290
  const errorMessage = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
5193
5291
  const event = {
5194
5292
  schemaVersion: "0.2",
5195
- eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
5293
+ eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : `${traceId}:${spanId}`,
5196
5294
  runId: typeof parsedSpanAttributes.attributes["agent_inspect.run_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.run_id"] : traceId,
5197
5295
  kind,
5198
5296
  name: readStringField(span, ["name"]) ?? spanId,
@@ -5241,12 +5339,16 @@ function mapOtlpSpan(context) {
5241
5339
  }
5242
5340
  function mapOtlpEventsToPersisted(document) {
5243
5341
  const mapped = document.spans.map((span) => mapOtlpSpan(span));
5244
- const spanIdToEventId = new Map(
5245
- mapped.map((span) => [span.spanId, span.event.eventId])
5342
+ const spanKeyToEventId = new Map(
5343
+ mapped.map((span) => {
5344
+ const traceId = typeof span.event.trace?.traceId === "string" ? span.event.trace.traceId : "";
5345
+ return [`${traceId}:${span.spanId}`, span.event.eventId];
5346
+ })
5246
5347
  );
5247
5348
  for (const span of mapped) {
5248
5349
  if (span.parentSpanId === void 0) continue;
5249
- span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
5350
+ const traceId = typeof span.event.trace?.traceId === "string" ? span.event.trace.traceId : "";
5351
+ span.event.parentId = spanKeyToEventId.get(`${traceId}:${span.parentSpanId}`) ?? span.parentSpanId;
5250
5352
  }
5251
5353
  return {
5252
5354
  events: mapped.map((span) => span.event),
@@ -5537,21 +5639,48 @@ function openTrace(input, options = {}) {
5537
5639
  }
5538
5640
 
5539
5641
  // packages/core/src/suite/run.ts
5642
+ var KNOWN_SUITE_SELECT_IDS = /* @__PURE__ */ new Set([
5643
+ "run.status",
5644
+ "run.duration",
5645
+ "run.depth",
5646
+ "outcome.status",
5647
+ "tool.usage",
5648
+ "tool.failures",
5649
+ "llm.usage"
5650
+ ]);
5540
5651
  function diagnostic4(code, message, severity = "error", caseId) {
5541
5652
  return { code, message, severity, ...caseId !== void 0 ? { caseId } : {} };
5542
5653
  }
5543
- function buildCaseRules(suiteCase, config) {
5654
+ function buildCaseAssertions(suiteCase, config) {
5544
5655
  const rules = [];
5545
5656
  const select = new Set(config.checks?.select ?? []);
5546
- if (select.has("run.status")) {
5657
+ const configDiagnostics = [];
5658
+ const evalConfig = config.eval;
5659
+ const declaredSelect = [...select];
5660
+ for (const id of declaredSelect) {
5661
+ if (!KNOWN_SUITE_SELECT_IDS.has(id)) {
5662
+ configDiagnostics.push(
5663
+ diagnostic4(
5664
+ "AI_SUITE_UNKNOWN_SELECTOR",
5665
+ `Unknown or unsupported suite check selector "${id}".`,
5666
+ "error",
5667
+ suiteCase.id
5668
+ )
5669
+ );
5670
+ }
5671
+ }
5672
+ if (select.has("run.status") || evalConfig?.requireSuccess === true) {
5547
5673
  rules.push(createRunStatusRule());
5674
+ select.add("run.status");
5548
5675
  }
5549
5676
  const requiredTools = [
5550
5677
  ...config.checks?.tool?.required ?? [],
5678
+ ...evalConfig?.requiredTools ?? [],
5551
5679
  ...suiteCase.requireTools ?? []
5552
5680
  ];
5553
5681
  const forbiddenTools = [
5554
5682
  ...config.checks?.tool?.forbidden ?? [],
5683
+ ...evalConfig?.forbiddenTools ?? [],
5555
5684
  ...suiteCase.forbidTools ?? []
5556
5685
  ];
5557
5686
  if (requiredTools.length > 0 || forbiddenTools.length > 0) {
@@ -5563,20 +5692,51 @@ function buildCaseRules(suiteCase, config) {
5563
5692
  );
5564
5693
  select.add("tool.usage");
5565
5694
  }
5566
- const maxDurationMs = suiteCase.maxDurationMs ?? config.checks?.run?.maxDurationMs ?? config.eval?.maxDurationMs;
5695
+ const maxDurationMs = suiteCase.maxDurationMs ?? config.checks?.run?.maxDurationMs ?? evalConfig?.maxDurationMs;
5567
5696
  if (maxDurationMs !== void 0) {
5568
5697
  rules.push(createRunDurationRule({ maxDurationMs }));
5569
5698
  select.add("run.duration");
5570
5699
  }
5571
- const llm = config.checks?.llm;
5572
- if (llm?.allowedModels !== void 0 || llm?.maxTotalTokens !== void 0) {
5573
- rules.push(createLlmUsageRule(llm));
5700
+ const maxDepth = config.checks?.run?.maxDepth ?? evalConfig?.maxDepth;
5701
+ if (maxDepth !== void 0) {
5702
+ rules.push(createRunDepthRule({ maxDepth }));
5703
+ select.add("run.depth");
5704
+ }
5705
+ if (evalConfig?.maxRetries !== void 0) {
5706
+ rules.push(createToolFailureRule({ maxRetries: evalConfig.maxRetries }));
5707
+ select.add("tool.failures");
5708
+ }
5709
+ const allowedModels = config.checks?.llm?.allowedModels;
5710
+ const maxTotalTokens = config.checks?.llm?.maxTotalTokens ?? evalConfig?.maxTotalTokens;
5711
+ if (allowedModels !== void 0 || maxTotalTokens !== void 0) {
5712
+ rules.push(
5713
+ createLlmUsageRule({
5714
+ ...allowedModels !== void 0 ? { allowedModels } : {},
5715
+ ...maxTotalTokens !== void 0 ? { maxTotalTokens } : {}
5716
+ })
5717
+ );
5574
5718
  select.add("llm.usage");
5575
5719
  }
5576
5720
  if (select.has("outcome.status")) {
5577
5721
  rules.push(createObservedOutcomeRule({ failOn: ["failed"] }));
5578
5722
  }
5579
- return { rules, select: [...select] };
5723
+ const observationCount = suiteCase.expectedObservations?.length ?? 0;
5724
+ if (rules.length === 0 && observationCount === 0 && configDiagnostics.length === 0) {
5725
+ configDiagnostics.push(
5726
+ diagnostic4(
5727
+ "AI_SUITE_NO_ASSERTIONS",
5728
+ `Suite case "${suiteCase.id}" declares no effective checks, eval controls, or expected observations.`,
5729
+ "error",
5730
+ suiteCase.id
5731
+ )
5732
+ );
5733
+ }
5734
+ return {
5735
+ rules,
5736
+ select: [...select],
5737
+ observationCount,
5738
+ configDiagnostics
5739
+ };
5580
5740
  }
5581
5741
  function outcomesFromRead(read) {
5582
5742
  const persistedOutcomes = extractOutcomesFromPersistedEvents(
@@ -5657,8 +5817,20 @@ async function runSuiteCase(suiteCase, config, options) {
5657
5817
  ]
5658
5818
  };
5659
5819
  }
5660
- const { rules, select } = buildCaseRules(suiteCase, config);
5661
- const checkResult = rules.length > 0 ? runTraceChecks({ read }, { rules, select }) : {
5820
+ const compiled = buildCaseAssertions(suiteCase, config);
5821
+ if (compiled.configDiagnostics.length > 0) {
5822
+ return {
5823
+ id: suiteCase.id,
5824
+ status: "error",
5825
+ tracePath: resolved.tracePath,
5826
+ ...resolved.runId !== void 0 ? { runId: resolved.runId } : {},
5827
+ checkOk: false,
5828
+ ...config.eval?.requireSuccess === true ? { evalOk: false } : {},
5829
+ message: compiled.configDiagnostics.map((item) => item.message).join("; "),
5830
+ diagnostics: compiled.configDiagnostics
5831
+ };
5832
+ }
5833
+ const checkResult = compiled.rules.length > 0 ? runTraceChecks({ read }, { rules: compiled.rules, select: compiled.select }) : {
5662
5834
  ok: true,
5663
5835
  status: "pass",
5664
5836
  format: read.format,
@@ -5686,6 +5858,9 @@ async function runSuiteCase(suiteCase, config, options) {
5686
5858
  ];
5687
5859
  const checkOk = checkResult.ok;
5688
5860
  const observationsOk = observationResult.ok;
5861
+ const evalOk = config.eval?.requireSuccess === true ? checkResult.findings.every(
5862
+ (finding) => finding.ruleId !== "run.status" || finding.status !== "fail"
5863
+ ) && checkResult.diagnostics.every((item) => item.severity !== "error") : void 0;
5689
5864
  const ok = checkOk && observationsOk;
5690
5865
  const status = ok ? "pass" : checkResult.status === "error" ? "error" : "fail";
5691
5866
  return {
@@ -5694,6 +5869,7 @@ async function runSuiteCase(suiteCase, config, options) {
5694
5869
  tracePath: resolved.tracePath,
5695
5870
  ...resolved.runId !== void 0 ? { runId: resolved.runId } : {},
5696
5871
  checkOk,
5872
+ ...evalOk !== void 0 ? { evalOk } : {},
5697
5873
  observationsOk,
5698
5874
  diagnostics,
5699
5875
  ...ok ? {} : {