distri_types/configuration/
package.rs1use crate::ToolDefinition;
2use crate::agent::StandardDefinition;
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use utoipa::ToSchema;
6
7#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema, JsonSchema)]
12pub struct AgentCloudMetadata {
13 #[serde(default, skip_serializing_if = "Option::is_none")]
14 pub is_owner: Option<bool>,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
17 pub is_workspace: Option<bool>,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub workspace_slug: Option<String>,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub updated_at: Option<chrono::DateTime<chrono::Utc>>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
29pub struct AgentConfigWithTools {
30 #[serde(flatten)]
31 #[schema(value_type = Object)]
32 pub agent: AgentConfig,
33 #[serde(default, skip_serializing_if = "Vec::is_empty")]
34 pub resolved_tools: Vec<ToolDefinition>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub markdown: Option<String>,
37 #[serde(flatten, default)]
39 pub cloud: AgentCloudMetadata,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
44#[serde(tag = "agent_type", rename_all = "snake_case")]
45#[allow(clippy::large_enum_variant)]
46pub enum AgentConfig {
47 #[schema(value_type = Object)]
49 StandardAgent(StandardDefinition),
50 #[schema(value_type = Object)]
52 WorkflowAgent(WorkflowAgentDefinition),
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
59pub struct WorkflowAgentDefinition {
60 pub name: String,
61 pub description: String,
62 #[serde(default = "default_version")]
63 pub version: String,
64 pub definition: serde_json::Value,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub input_schema: Option<serde_json::Value>,
69 #[serde(default, skip_serializing_if = "Vec::is_empty")]
74 pub triggers: Vec<crate::WorkflowTrigger>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub channels: Option<crate::channel_commands::ChannelBindings>,
78 #[serde(default, skip_serializing_if = "Vec::is_empty")]
87 pub connections: Vec<crate::connections::ConnectionRequirement>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub tools: Option<crate::ToolsConfig>,
93}
94
95fn default_version() -> String {
96 "0.1.0".to_string()
97}
98
99impl AgentConfig {
100 pub fn get_name(&self) -> &str {
102 match self {
103 AgentConfig::StandardAgent(def) => &def.name,
104 AgentConfig::WorkflowAgent(def) => &def.name,
105 }
106 }
107
108 pub fn get_definition(&self) -> &StandardDefinition {
109 match self {
110 AgentConfig::StandardAgent(def) => def,
111 AgentConfig::WorkflowAgent(_) => {
112 panic!("WorkflowAgent does not have a StandardDefinition")
113 }
114 }
115 }
116
117 pub fn version(&self) -> Option<String> {
120 match self {
121 AgentConfig::StandardAgent(def) => def
122 .version
123 .clone()
124 .or_else(crate::agent::default_agent_version),
125 AgentConfig::WorkflowAgent(def) => Some(def.version.clone()),
126 }
127 }
128
129 pub fn get_description(&self) -> &str {
131 match self {
132 AgentConfig::StandardAgent(def) => &def.description,
133 AgentConfig::WorkflowAgent(def) => &def.description,
134 }
135 }
136
137 pub fn get_tools_config(&self) -> Option<&crate::ToolsConfig> {
139 match self {
140 AgentConfig::StandardAgent(def) => def.tools.as_ref(),
141 AgentConfig::WorkflowAgent(_) => None,
142 }
143 }
144
145 pub fn get_schedule_triggers(&self) -> Vec<&crate::WorkflowTrigger> {
150 match self {
151 AgentConfig::StandardAgent(_) => vec![],
152 AgentConfig::WorkflowAgent(def) => def
153 .triggers
154 .iter()
155 .filter(|t| matches!(t, crate::WorkflowTrigger::Schedule { .. }))
156 .collect(),
157 }
158 }
159
160 pub fn validate(&self) -> anyhow::Result<()> {
162 match self {
163 AgentConfig::StandardAgent(def) => def.validate(),
164 AgentConfig::WorkflowAgent(_def) => Ok(()), }
166 }
167
168 pub fn to_card(&self, server_config: &crate::configuration::ServerConfig) -> distri_a2a::AgentCard {
178 let (name, description, version, icon_url, skills) = match self {
179 AgentConfig::StandardAgent(def) => (
180 def.name.clone(),
181 def.description.clone(),
182 def.version.clone(),
183 def.icon_url.clone(),
184 def.skills_description.clone(),
185 ),
186 AgentConfig::WorkflowAgent(def) => (
187 def.name.clone(),
188 def.description.clone(),
189 Some(def.version.clone()),
190 None,
191 Vec::new(),
192 ),
193 };
194
195 let version = version
196 .or_else(crate::agent::default_agent_version)
197 .unwrap_or_else(|| distri_a2a::A2A_VERSION.to_string());
198
199 distri_a2a::AgentCard {
200 version,
201 url: format!("{}/agents/{}", server_config.base_url, name),
202 name,
203 description,
204 icon_url,
205 documentation_url: server_config.documentation_url.clone(),
206 provider: Some(server_config.agent_provider.clone()),
207 preferred_transport: server_config.preferred_transport.clone(),
208 capabilities: server_config.capabilities.clone(),
209 default_input_modes: server_config.default_input_modes.clone(),
210 default_output_modes: server_config.default_output_modes.clone(),
211 skills,
212 security_schemes: server_config.security_schemes.clone(),
213 security: server_config.security.clone(),
214 }
215 }
216}
217
218#[cfg(test)]
219mod channel_binding_tests {
220 use super::*;
221
222 #[test]
223 fn workflow_agent_accepts_channels_field() {
224 let json = serde_json::json!({
225 "name": "z", "description": "d",
226 "definition": {"id":"w","steps":[]},
227 "channels": {"telegram": {"web_app_base": "https://a.app"}}
228 });
229 let def: WorkflowAgentDefinition = serde_json::from_value(json).unwrap();
230 assert!(def.channels.is_some());
231 }
232
233 #[test]
234 fn workflow_agent_channels_optional() {
235 let json = serde_json::json!({
236 "name": "z", "description": "d", "definition": {"id":"w","steps":[]}
237 });
238 let def: WorkflowAgentDefinition = serde_json::from_value(json).unwrap();
239 assert!(def.channels.is_none());
240 }
241}