everruns_core/
agent_definition.rs1use serde::{Deserialize, Serialize};
13
14use crate::capability_types::AgentCapabilityConfig;
15use crate::mcp_server::{ScopedMcpServers, scoped_mcp_servers_is_empty};
16use crate::network_access::NetworkAccessList;
17use crate::session_file::InitialFile;
18use crate::tool_types::ToolDefinition;
19use crate::typed_id::{AgentId, ModelId};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct AgentDefinition {
29 pub id: AgentId,
32 pub name: String,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub display_name: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub description: Option<String>,
40 pub system_prompt: String,
42 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub default_model_id: Option<ModelId>,
45 #[serde(default)]
47 pub capabilities: Vec<AgentCapabilityConfig>,
48 #[serde(default, skip_serializing_if = "Vec::is_empty")]
50 pub initial_files: Vec<InitialFile>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub network_access: Option<NetworkAccessList>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub max_iterations: Option<usize>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub parallel_tool_calls: Option<bool>,
60 #[serde(default, skip_serializing_if = "Vec::is_empty")]
62 pub tools: Vec<ToolDefinition>,
63 #[serde(
65 default,
66 rename = "mcpServers",
67 alias = "mcp_servers",
68 skip_serializing_if = "scoped_mcp_servers_is_empty"
69 )]
70 pub mcp_servers: ScopedMcpServers,
71}
72
73impl AgentDefinition {
74 pub fn new(id: AgentId, name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
77 Self {
78 id,
79 name: name.into(),
80 display_name: None,
81 description: None,
82 system_prompt: system_prompt.into(),
83 default_model_id: None,
84 capabilities: vec![],
85 initial_files: vec![],
86 network_access: None,
87 max_iterations: None,
88 parallel_tool_calls: None,
89 tools: vec![],
90 mcp_servers: ScopedMcpServers::default(),
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn definition_serde_round_trips() {
101 let definition = AgentDefinition::new(
102 "agent_01933b5a000070008000000000000001".parse().unwrap(),
103 "test",
104 "You are helpful.",
105 );
106 let json = serde_json::to_value(&definition).unwrap();
107 assert_eq!(json["id"], "agent_01933b5a000070008000000000000001");
108 let round_tripped: AgentDefinition = serde_json::from_value(json.clone()).unwrap();
109 assert_eq!(serde_json::to_value(&round_tripped).unwrap(), json);
110 }
111
112 #[test]
113 fn definition_carries_no_persistence_metadata() {
114 let definition = AgentDefinition::new(
115 "agent_01933b5a000070008000000000000001".parse().unwrap(),
116 "test",
117 "prompt",
118 );
119 let json = serde_json::to_value(&definition).unwrap();
120 for persistence_field in [
121 "status",
122 "created_at",
123 "updated_at",
124 "archived_at",
125 "deleted_at",
126 "default_version_id",
127 "forked_from_agent_id",
128 "usage",
129 ] {
130 assert!(
131 json.get(persistence_field).is_none(),
132 "portable definition must not expose {persistence_field}"
133 );
134 }
135 }
136}