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