systemprompt_models/services/agent_config/
mod.rs1mod card;
8mod disk;
9mod summary;
10
11pub use card::{
12 AgentCardConfig, AgentMetadataConfig, AgentProviderInfo, AgentSkillConfig, CapabilitiesConfig,
13 OAuthConfig,
14};
15pub use disk::DiskAgentConfig;
16pub use summary::AgentSummary;
17
18use crate::auth::Permission;
19use crate::errors::ConfigValidationError;
20use serde::{Deserialize, Serialize};
21
22pub const AGENT_CONFIG_FILENAME: &str = "config.yaml";
23pub const DEFAULT_AGENT_SYSTEM_PROMPT_FILE: &str = "system_prompt.md";
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct AgentConfig {
27 pub name: String,
28 pub port: u16,
29 pub endpoint: String,
30 pub enabled: bool,
31 #[serde(default)]
32 pub dev_only: bool,
33 #[serde(default)]
34 pub is_primary: bool,
35 #[serde(default)]
36 pub default: bool,
37 #[serde(default)]
38 pub tags: Vec<String>,
39 pub card: AgentCardConfig,
40 pub metadata: AgentMetadataConfig,
41 #[serde(default)]
42 pub oauth: OAuthConfig,
43}
44
45impl AgentConfig {
46 pub fn validate(&self, name: &str) -> Result<(), ConfigValidationError> {
47 if self.name != name {
48 return Err(ConfigValidationError::invalid_field(format!(
49 "Agent config key '{}' does not match name field '{}'",
50 name, self.name
51 )));
52 }
53
54 if !self
55 .name
56 .chars()
57 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
58 {
59 return Err(ConfigValidationError::invalid_field(format!(
60 "Agent name '{}' must be lowercase alphanumeric with underscores only",
61 self.name
62 )));
63 }
64
65 if self.name.len() < 3 || self.name.len() > 50 {
66 return Err(ConfigValidationError::invalid_field(format!(
67 "Agent name '{}' must be between 3 and 50 characters",
68 self.name
69 )));
70 }
71
72 if self.port == 0 {
73 return Err(ConfigValidationError::invalid_field(format!(
74 "Agent '{}' has invalid port {}",
75 self.name, self.port
76 )));
77 }
78
79 Ok(())
80 }
81
82 pub fn extract_oauth_scopes_from_card(&mut self) {
83 if let Some(security_vec) = &self.card.security {
84 for security_obj in security_vec {
85 if let Some(oauth2_scopes) = security_obj.get("oauth2").and_then(|v| v.as_array()) {
86 let mut permissions = Vec::new();
87 for scope_val in oauth2_scopes {
88 if let Some(scope_str) = scope_val.as_str() {
89 match scope_str {
90 "admin" => permissions.push(Permission::Admin),
91 "user" => permissions.push(Permission::User),
92 "service" => permissions.push(Permission::Service),
93 "a2a" => permissions.push(Permission::A2a),
94 "mcp" => permissions.push(Permission::Mcp),
95 "anonymous" => permissions.push(Permission::Anonymous),
96 _ => {},
97 }
98 }
99 }
100 if !permissions.is_empty() {
101 self.oauth.scopes = permissions;
102 self.oauth.required = true;
103 }
104 }
105 }
106 }
107 }
108
109 #[must_use]
110 pub fn construct_url(&self, base_url: &str) -> String {
111 format!(
112 "{}/api/v1/agents/{}",
113 base_url.trim_end_matches('/'),
114 self.name
115 )
116 }
117}