@agent-inspect/viewer 6.31.31 → 6.31.33

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
@@ -2226,6 +2226,11 @@ function compareFindings(eventById) {
2226
2226
  if (STATUS_RANK[a.status] !== STATUS_RANK[b.status]) {
2227
2227
  return STATUS_RANK[a.status] - STATUS_RANK[b.status];
2228
2228
  }
2229
+ if (a.ruleId === "tool.usage") {
2230
+ const aHasEvent = a.evidence[0]?.eventId !== void 0;
2231
+ const bHasEvent = b.evidence[0]?.eventId !== void 0;
2232
+ if (aHasEvent !== bHasEvent) return aHasEvent ? -1 : 1;
2233
+ }
2229
2234
  const byRun = compareStrings(a.evidence[0]?.runId, b.evidence[0]?.runId);
2230
2235
  if (byRun !== 0) return byRun;
2231
2236
  const byTime = eventTimestamp(a, eventById).localeCompare(eventTimestamp(b, eventById));
@@ -2344,6 +2349,43 @@ function failFinding(ruleId, message, evidence, expected, actual, meta) {
2344
2349
  function toolName(event) {
2345
2350
  return resolveCanonicalToolName(event);
2346
2351
  }
2352
+ function formatToolNameForMessage(name) {
2353
+ const details = [];
2354
+ for (let index = 0; index < name.length; ) {
2355
+ const codePoint = name.codePointAt(index);
2356
+ const width = codePoint > 65535 ? 2 : 1;
2357
+ if (codePoint > 127) {
2358
+ details.push(
2359
+ `U+${codePoint.toString(16).toUpperCase().padStart(4, "0")} at index ${index}`
2360
+ );
2361
+ }
2362
+ index += width;
2363
+ }
2364
+ if (details.length === 0) return name;
2365
+ return `"${name}" (contains ${details.join("; ")})`;
2366
+ }
2367
+ function describeRunningEvent(event) {
2368
+ if (event.kind === "TOOL") {
2369
+ return `tool ${formatToolNameForMessage(resolveCanonicalToolName(event))}`;
2370
+ }
2371
+ if (event.kind === "LLM") {
2372
+ return `llm ${stepEndpointName(event)}`;
2373
+ }
2374
+ return `${event.kind} ${event.name}`;
2375
+ }
2376
+ function incompleteRunningMessage(prefix, running) {
2377
+ const labels = running.map(describeRunningEvent);
2378
+ if (labels.length === 1) {
2379
+ return `${prefix}: ${labels[0]} never completed.`;
2380
+ }
2381
+ return `${prefix}: ${labels.join("; ")}.`;
2382
+ }
2383
+ function stepEndpointName(event, kind) {
2384
+ for (const prefix of ["llm:", "generation:", "transcription:", "speech:"]) {
2385
+ if (event.name.startsWith(prefix)) return event.name.slice(prefix.length);
2386
+ }
2387
+ return event.name;
2388
+ }
2347
2389
  function semanticEvents(context) {
2348
2390
  return context.logicalEvents ?? context.events;
2349
2391
  }
@@ -2397,10 +2439,13 @@ function createRunStatusRule(options = {}) {
2397
2439
  findings.push(
2398
2440
  failFinding(
2399
2441
  "run.status",
2400
- "Run contains incomplete running events.",
2442
+ incompleteRunningMessage("Run contains incomplete running events", running),
2401
2443
  running.map((event) => eventEvidence(event)),
2402
2444
  "no running events",
2403
- running.length
2445
+ {
2446
+ count: running.length,
2447
+ unfinished: running.map(describeRunningEvent)
2448
+ }
2404
2449
  )
2405
2450
  );
2406
2451
  }
@@ -2468,6 +2513,34 @@ function createToolUsageRule(options) {
2468
2513
  const names = tools.map(toolName);
2469
2514
  const nameSet = new Set(names);
2470
2515
  const findings = [];
2516
+ const forbidden = new Set(options.forbidden ?? []);
2517
+ const allowed = options.allowed?.length ? new Set(options.allowed) : void 0;
2518
+ for (const event of tools) {
2519
+ const name = toolName(event);
2520
+ const display = formatToolNameForMessage(name);
2521
+ if (forbidden.has(name)) {
2522
+ findings.push(
2523
+ failFinding(
2524
+ "tool.usage",
2525
+ `Forbidden tool ${display} appeared.`,
2526
+ [eventEvidence(event)],
2527
+ "tool absent",
2528
+ name
2529
+ )
2530
+ );
2531
+ }
2532
+ if (allowed && !allowed.has(name)) {
2533
+ findings.push(
2534
+ failFinding(
2535
+ "tool.usage",
2536
+ `Tool ${display} is not in the allowed tool set.`,
2537
+ [eventEvidence(event)],
2538
+ [...allowed].sort(),
2539
+ name
2540
+ )
2541
+ );
2542
+ }
2543
+ }
2471
2544
  for (const required of options.required ?? []) {
2472
2545
  if (!nameSet.has(required)) {
2473
2546
  const otherKinds = [
@@ -2485,7 +2558,7 @@ function createToolUsageRule(options) {
2485
2558
  findings.push(
2486
2559
  failFinding(
2487
2560
  "tool.usage",
2488
- `Required tool ${required} did not appear.${hint}`,
2561
+ `Required tool ${formatToolNameForMessage(required)} did not appear.${hint}`,
2489
2562
  runEvidence(context.selectedRun),
2490
2563
  required,
2491
2564
  names
@@ -2493,21 +2566,6 @@ function createToolUsageRule(options) {
2493
2566
  );
2494
2567
  }
2495
2568
  }
2496
- const forbidden = new Set(options.forbidden ?? []);
2497
- const allowed = options.allowed?.length ? new Set(options.allowed) : void 0;
2498
- for (const event of tools) {
2499
- const name = toolName(event);
2500
- if (forbidden.has(name)) {
2501
- findings.push(
2502
- failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence(event)], "tool absent", name)
2503
- );
2504
- }
2505
- if (allowed && !allowed.has(name)) {
2506
- findings.push(
2507
- failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence(event)], [...allowed].sort(), name)
2508
- );
2509
- }
2510
- }
2511
2569
  if (options.minCount !== void 0 && tools.length < options.minCount) {
2512
2570
  findings.push(
2513
2571
  failFinding("tool.usage", `Tool count ${tools.length} was below minimum ${options.minCount}.`, runEvidence(context.selectedRun), { minCount: options.minCount }, tools.length)