Skip to main content

systemprompt_models/services/agent_config/
card.rs

1//! Agent capability and skill descriptors plus the container
2//! [`AgentCardConfig`] published as the agent's public face.
3
4use serde::{Deserialize, Serialize};
5
6use crate::ai::ToolModelOverrides;
7use crate::auth::{JwtAudience, Permission};
8use crate::services::plugin::PluginComponentRef;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct AgentCardConfig {
13    pub protocol_version: String,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub name: Option<String>,
16    pub display_name: String,
17    pub description: String,
18    pub version: String,
19    #[serde(default = "default_transport")]
20    pub preferred_transport: String,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub icon_url: Option<String>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub documentation_url: Option<String>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub provider: Option<AgentProviderInfo>,
27    #[serde(default)]
28    pub capabilities: CapabilitiesConfig,
29    #[serde(default = "default_input_modes")]
30    pub default_input_modes: Vec<String>,
31    #[serde(default = "default_output_modes")]
32    pub default_output_modes: Vec<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub security_schemes: Option<serde_json::Value>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub security: Option<Vec<serde_json::Value>>,
37    /// DEPRECATED: A2A `card.skills` is COMPUTED at A2A serve time by joining
38    /// `metadata.skills` against the on-disk skill catalog under
39    /// `services/skills/`. Authoring `card.skills` in agent YAML is a no-op for
40    /// the A2A endpoint and the bridge marketplace as of the skill-catalog
41    /// refactor.
42    ///
43    /// The field is tolerated (rather than rejected) so downstream repos can
44    /// land their YAML cleanup in a follow-up commit without breaking
45    /// deserialisation. It is `#[serde(skip_serializing)]` so re-emitted YAML
46    /// no longer carries it, and a warning is logged at services-config load
47    /// time when the vector is non-empty (see
48    /// `infra/loader/src/config_loader/merge.
49    /// rs::warn_on_authored_card_skills`).
50    ///
51    /// To be hard-removed once all downstream YAML has been migrated.
52    #[serde(default, skip_serializing)]
53    pub skills: Vec<AgentSkillConfig>,
54    #[serde(default)]
55    pub supports_authenticated_extended_card: bool,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct AgentSkillConfig {
60    pub id: systemprompt_identifiers::SkillId,
61    pub name: String,
62    pub description: String,
63    #[serde(default)]
64    pub tags: Vec<String>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub examples: Option<Vec<String>>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub input_modes: Option<Vec<String>>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub output_modes: Option<Vec<String>>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub security: Option<Vec<serde_json::Value>>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct AgentProviderInfo {
77    pub organization: String,
78    pub url: String,
79}
80
81#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct CapabilitiesConfig {
84    #[serde(default = "default_true")]
85    pub streaming: bool,
86    #[serde(default)]
87    pub push_notifications: bool,
88    #[serde(default = "default_true")]
89    pub state_transition_history: bool,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct AgentMetadataConfig {
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub system_prompt: Option<String>,
97    #[serde(default)]
98    pub mcp_servers: PluginComponentRef,
99    #[serde(default)]
100    pub skills: PluginComponentRef,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub provider: Option<String>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub model: Option<String>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub max_output_tokens: Option<u32>,
107    #[serde(default)]
108    pub tool_model_overrides: ToolModelOverrides,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct OAuthConfig {
113    #[serde(default)]
114    pub required: bool,
115    #[serde(default)]
116    pub scopes: Vec<Permission>,
117    #[serde(default = "default_audience")]
118    pub audience: JwtAudience,
119}
120
121impl Default for CapabilitiesConfig {
122    fn default() -> Self {
123        Self {
124            streaming: true,
125            push_notifications: false,
126            state_transition_history: true,
127        }
128    }
129}
130
131impl Default for OAuthConfig {
132    fn default() -> Self {
133        Self {
134            required: false,
135            scopes: Vec::new(),
136            audience: JwtAudience::A2a,
137        }
138    }
139}
140
141pub(super) fn default_transport() -> String {
142    "JSONRPC".to_owned()
143}
144
145pub(super) fn default_input_modes() -> Vec<String> {
146    vec!["text/plain".to_owned()]
147}
148
149pub(super) fn default_output_modes() -> Vec<String> {
150    vec!["text/plain".to_owned()]
151}
152
153pub(super) const fn default_true() -> bool {
154    true
155}
156
157pub(super) const fn default_audience() -> JwtAudience {
158    JwtAudience::A2a
159}