Skip to main content

spice_framework/
agent.rs

1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use std::time::Duration;
4
5use crate::error::SpiceError;
6
7/// Configuration passed to the agent under test. Wraps arbitrary JSON.
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct AgentConfig {
10    pub data: serde_json::Value,
11}
12
13impl AgentConfig {
14    pub fn new(data: serde_json::Value) -> Self {
15        Self { data }
16    }
17
18    pub fn empty() -> Self {
19        Self {
20            data: serde_json::Value::Null,
21        }
22    }
23}
24
25/// A single tool call made by the agent.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ToolCall {
28    pub id: String,
29    pub name: String,
30    pub arguments: serde_json::Value,
31}
32
33/// A single turn in the agent's execution.
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct Turn {
36    pub index: usize,
37    pub output_text: Option<String>,
38    pub tool_calls: Vec<ToolCall>,
39    pub tool_results: Vec<serde_json::Value>,
40    pub stop_reason: Option<String>,
41    pub duration: Duration,
42}
43
44/// The complete output of an agent run.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct AgentOutput {
47    pub final_text: String,
48    pub turns: Vec<Turn>,
49    pub tools_called: Vec<String>,
50    pub duration: Duration,
51    pub error: Option<String>,
52}
53
54impl AgentOutput {
55    /// Collect all tool calls across all turns.
56    pub fn all_tool_calls(&self) -> Vec<&ToolCall> {
57        self.turns
58            .iter()
59            .flat_map(|t| t.tool_calls.iter())
60            .collect()
61    }
62
63    /// Get tool calls filtered by name.
64    pub fn tool_calls_by_name(&self, name: &str) -> Vec<&ToolCall> {
65        self.all_tool_calls()
66            .into_iter()
67            .filter(|tc| tc.name == name)
68            .collect()
69    }
70}
71
72/// Trait that the agent under test must implement.
73#[async_trait]
74pub trait AgentUnderTest: Send + Sync {
75    /// Run the agent with a user message and config, return full output with trace.
76    async fn run(&self, user_message: &str, config: &AgentConfig)
77        -> Result<AgentOutput, SpiceError>;
78
79    /// Return tool names available for this config (for allowlist assertions).
80    fn available_tools(&self, config: &AgentConfig) -> Vec<String>;
81
82    /// Human-readable agent name (for reports).
83    fn name(&self) -> &str {
84        "agent"
85    }
86}