assay-core 3.9.1

High-performance evaluation framework for LLM agents (Core)
Documentation
use super::super::{HandleResult, ToolCallHandler, ToolCallHandlerConfig};
use super::fixtures::{make_tool_call_request, CountingEmitter};
use crate::mcp::policy::{McpPolicy, PolicyState};
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;

#[test]
fn delegated_context_emits_typed_fields_for_supported_flow() {
    let emitter = Arc::new(CountingEmitter(AtomicUsize::new(0)));
    let handler = ToolCallHandler::new(
        McpPolicy::default(),
        None,
        emitter.clone(),
        ToolCallHandlerConfig::default(),
    );

    let request = make_tool_call_request(
        "safe_tool",
        serde_json::json!({
            "_meta": {
                "delegation": {
                    "delegated_from": "agent:planner",
                    "delegation_depth": 1
                }
            }
        }),
    );
    let mut state = PolicyState::default();
    let result = handler.handle_tool_call(&request, &mut state, None, None, None);

    match result {
        HandleResult::Allow { decision_event, .. } => {
            assert_eq!(
                decision_event.data.delegated_from.as_deref(),
                Some("agent:planner")
            );
            assert_eq!(decision_event.data.delegation_depth, Some(1));
        }
        other => panic!("expected allow result, got {:?}", other),
    }

    assert_eq!(emitter.0.load(std::sync::atomic::Ordering::SeqCst), 1);
}

#[test]
fn direct_authorization_flow_omits_delegation_fields() {
    let emitter = Arc::new(CountingEmitter(AtomicUsize::new(0)));
    let handler = ToolCallHandler::new(
        McpPolicy::default(),
        None,
        emitter.clone(),
        ToolCallHandlerConfig::default(),
    );

    let request = make_tool_call_request("safe_tool", serde_json::json!({}));
    let mut state = PolicyState::default();
    let result = handler.handle_tool_call(&request, &mut state, None, None, None);

    match result {
        HandleResult::Allow { decision_event, .. } => {
            assert_eq!(decision_event.data.delegated_from, None);
            assert_eq!(decision_event.data.delegation_depth, None);
        }
        other => panic!("expected allow result, got {:?}", other),
    }

    assert_eq!(emitter.0.load(std::sync::atomic::Ordering::SeqCst), 1);
}

#[test]
fn unstructured_delegation_hints_do_not_emit_typed_fields() {
    let emitter = Arc::new(CountingEmitter(AtomicUsize::new(0)));
    let handler = ToolCallHandler::new(
        McpPolicy::default(),
        None,
        emitter.clone(),
        ToolCallHandlerConfig::default(),
    );

    let request = make_tool_call_request(
        "safe_tool",
        serde_json::json!({
            "_meta": {
                "delegation_hint": "planner maybe delegated this",
                "delegation": {
                    "note": "human-readable only"
                }
            }
        }),
    );
    let mut state = PolicyState::default();
    let result = handler.handle_tool_call(&request, &mut state, None, None, None);

    match result {
        HandleResult::Allow { decision_event, .. } => {
            assert_eq!(decision_event.data.delegated_from, None);
            assert_eq!(decision_event.data.delegation_depth, None);
        }
        other => panic!("expected allow result, got {:?}", other),
    }

    assert_eq!(emitter.0.load(std::sync::atomic::Ordering::SeqCst), 1);
}