1use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use crate::options::PermissionMode;
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct SubagentDefinition {
17 pub description: String,
19 pub prompt: String,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub tools: Option<Vec<String>>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub disallowed_tools: Option<Vec<String>>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
30 pub model: Option<String>,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub skills: Option<Vec<String>>,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub memory: Option<SubagentMemory>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub mcp_servers: Option<Vec<SubagentMcpServerRef>>,
42 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub initial_prompt: Option<String>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub max_turns: Option<u64>,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub background: Option<bool>,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub effort: Option<SubagentEffort>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub permission_mode: Option<PermissionMode>,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "lowercase")]
65pub enum SubagentMemory {
66 User,
68 Project,
70 Local,
72}
73
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78#[serde(untagged)]
79pub enum SubagentMcpServerRef {
80 Named(String),
82 #[serde(with = "inline_mcp_server")]
84 Inline {
85 name: String,
87 config: Value,
89 },
90}
91
92mod inline_mcp_server {
93 use serde::de::{Error as _, MapAccess, Visitor};
94 use serde::ser::SerializeMap;
95 use serde_json::Value;
96 use std::fmt;
97
98 pub(super) fn serialize<S: serde::Serializer>(
99 name: &str,
100 config: &Value,
101 serializer: S,
102 ) -> Result<S::Ok, S::Error> {
103 let mut map = serializer.serialize_map(Some(1))?;
104 map.serialize_entry(name, config)?;
105 map.end()
106 }
107
108 pub(super) fn deserialize<'de, D: serde::Deserializer<'de>>(
109 d: D,
110 ) -> Result<(String, Value), D::Error> {
111 struct V;
112 impl<'de> Visitor<'de> for V {
113 type Value = (String, Value);
114 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 f.write_str("a single-key {name: config} object")
116 }
117 fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Self::Value, M::Error> {
118 let first: Option<(String, Value)> = map.next_entry()?;
119 let (name, config) =
120 first.ok_or_else(|| M::Error::custom("empty inline MCP server object"))?;
121 if map.next_entry::<String, Value>()?.is_some() {
122 return Err(M::Error::custom(
123 "inline MCP server object must have exactly one key",
124 ));
125 }
126 Ok((name, config))
127 }
128 }
129 d.deserialize_map(V)
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138#[serde(untagged)]
139pub enum SubagentEffort {
140 Preset(EffortPreset),
142 Numeric(i64),
144}
145
146impl SubagentEffort {
147 pub fn as_cli_arg(&self) -> String {
150 match self {
151 Self::Preset(p) => match p {
152 EffortPreset::Low => "low".into(),
153 EffortPreset::Medium => "medium".into(),
154 EffortPreset::High => "high".into(),
155 EffortPreset::Max => "max".into(),
156 },
157 Self::Numeric(n) => n.to_string(),
158 }
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(rename_all = "lowercase")]
166pub enum EffortPreset {
167 Low,
169 Medium,
171 High,
173 Max,
175}