@agent-inspect/viewer 6.31.32 → 6.31.34

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.mjs CHANGED
@@ -2219,6 +2219,11 @@ function compareFindings(eventById) {
2219
2219
  if (STATUS_RANK[a.status] !== STATUS_RANK[b.status]) {
2220
2220
  return STATUS_RANK[a.status] - STATUS_RANK[b.status];
2221
2221
  }
2222
+ if (a.ruleId === "tool.usage") {
2223
+ const aHasEvent = a.evidence[0]?.eventId !== void 0;
2224
+ const bHasEvent = b.evidence[0]?.eventId !== void 0;
2225
+ if (aHasEvent !== bHasEvent) return aHasEvent ? -1 : 1;
2226
+ }
2222
2227
  const byRun = compareStrings(a.evidence[0]?.runId, b.evidence[0]?.runId);
2223
2228
  if (byRun !== 0) return byRun;
2224
2229
  const byTime = eventTimestamp(a, eventById).localeCompare(eventTimestamp(b, eventById));
@@ -2337,6 +2342,43 @@ function failFinding(ruleId, message, evidence, expected, actual, meta) {
2337
2342
  function toolName(event) {
2338
2343
  return resolveCanonicalToolName(event);
2339
2344
  }
2345
+ function formatToolNameForMessage(name) {
2346
+ const details = [];
2347
+ for (let index = 0; index < name.length; ) {
2348
+ const codePoint = name.codePointAt(index);
2349
+ const width = codePoint > 65535 ? 2 : 1;
2350
+ if (codePoint > 127) {
2351
+ details.push(
2352
+ `U+${codePoint.toString(16).toUpperCase().padStart(4, "0")} at index ${index}`
2353
+ );
2354
+ }
2355
+ index += width;
2356
+ }
2357
+ if (details.length === 0) return name;
2358
+ return `"${name}" (contains ${details.join("; ")})`;
2359
+ }
2360
+ function describeRunningEvent(event) {
2361
+ if (event.kind === "TOOL") {
2362
+ return `tool ${formatToolNameForMessage(resolveCanonicalToolName(event))}`;
2363
+ }
2364
+ if (event.kind === "LLM") {
2365
+ return `llm ${stepEndpointName(event)}`;
2366
+ }
2367
+ return `${event.kind} ${event.name}`;
2368
+ }
2369
+ function incompleteRunningMessage(prefix, running) {
2370
+ const labels = running.map(describeRunningEvent);
2371
+ if (labels.length === 1) {
2372
+ return `${prefix}: ${labels[0]} never completed.`;
2373
+ }
2374
+ return `${prefix}: ${labels.join("; ")}.`;
2375
+ }
2376
+ function stepEndpointName(event, kind) {
2377
+ for (const prefix of ["llm:", "generation:", "transcription:", "speech:"]) {
2378
+ if (event.name.startsWith(prefix)) return event.name.slice(prefix.length);
2379
+ }
2380
+ return event.name;
2381
+ }
2340
2382
  function semanticEvents(context) {
2341
2383
  return context.logicalEvents ?? context.events;
2342
2384
  }
@@ -2390,10 +2432,13 @@ function createRunStatusRule(options = {}) {
2390
2432
  findings.push(
2391
2433
  failFinding(
2392
2434
  "run.status",
2393
- "Run contains incomplete running events.",
2435
+ incompleteRunningMessage("Run contains incomplete running events", running),
2394
2436
  running.map((event) => eventEvidence(event)),
2395
2437
  "no running events",
2396
- running.length
2438
+ {
2439
+ count: running.length,
2440
+ unfinished: running.map(describeRunningEvent)
2441
+ }
2397
2442
  )
2398
2443
  );
2399
2444
  }
@@ -2461,6 +2506,34 @@ function createToolUsageRule(options) {
2461
2506
  const names = tools.map(toolName);
2462
2507
  const nameSet = new Set(names);
2463
2508
  const findings = [];
2509
+ const forbidden = new Set(options.forbidden ?? []);
2510
+ const allowed = options.allowed?.length ? new Set(options.allowed) : void 0;
2511
+ for (const event of tools) {
2512
+ const name = toolName(event);
2513
+ const display = formatToolNameForMessage(name);
2514
+ if (forbidden.has(name)) {
2515
+ findings.push(
2516
+ failFinding(
2517
+ "tool.usage",
2518
+ `Forbidden tool ${display} appeared.`,
2519
+ [eventEvidence(event)],
2520
+ "tool absent",
2521
+ name
2522
+ )
2523
+ );
2524
+ }
2525
+ if (allowed && !allowed.has(name)) {
2526
+ findings.push(
2527
+ failFinding(
2528
+ "tool.usage",
2529
+ `Tool ${display} is not in the allowed tool set.`,
2530
+ [eventEvidence(event)],
2531
+ [...allowed].sort(),
2532
+ name
2533
+ )
2534
+ );
2535
+ }
2536
+ }
2464
2537
  for (const required of options.required ?? []) {
2465
2538
  if (!nameSet.has(required)) {
2466
2539
  const otherKinds = [
@@ -2478,7 +2551,7 @@ function createToolUsageRule(options) {
2478
2551
  findings.push(
2479
2552
  failFinding(
2480
2553
  "tool.usage",
2481
- `Required tool ${required} did not appear.${hint}`,
2554
+ `Required tool ${formatToolNameForMessage(required)} did not appear.${hint}`,
2482
2555
  runEvidence(context.selectedRun),
2483
2556
  required,
2484
2557
  names
@@ -2486,21 +2559,6 @@ function createToolUsageRule(options) {
2486
2559
  );
2487
2560
  }
2488
2561
  }
2489
- const forbidden = new Set(options.forbidden ?? []);
2490
- const allowed = options.allowed?.length ? new Set(options.allowed) : void 0;
2491
- for (const event of tools) {
2492
- const name = toolName(event);
2493
- if (forbidden.has(name)) {
2494
- findings.push(
2495
- failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence(event)], "tool absent", name)
2496
- );
2497
- }
2498
- if (allowed && !allowed.has(name)) {
2499
- findings.push(
2500
- failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence(event)], [...allowed].sort(), name)
2501
- );
2502
- }
2503
- }
2504
2562
  if (options.minCount !== void 0 && tools.length < options.minCount) {
2505
2563
  findings.push(
2506
2564
  failFinding("tool.usage", `Tool count ${tools.length} was below minimum ${options.minCount}.`, runEvidence(context.selectedRun), { minCount: options.minCount }, tools.length)