agents_kit/config/custom.rs
1use std::num::NonZeroUsize;
2
3use crate::{KitError, ModelSelector, ToolSelection};
4
5/// Bounded work admitted by one Custom prompt.
6///
7/// ```
8/// use agents_kit::config::CustomLoopLimits;
9/// let limits = CustomLoopLimits::new(8, 0, 1)?;
10/// assert_eq!(limits.max_tool_actions(), 0);
11/// # Ok::<(), agents_kit::KitError>(())
12/// ```
13#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
14pub struct CustomLoopLimits {
15 max_model_turns: NonZeroUsize,
16 max_tool_actions: usize,
17 max_parallel_tool_actions: NonZeroUsize,
18}
19
20impl CustomLoopLimits {
21 /// Validates the model-turn, aggregate-action, and parallel-action bounds.
22 ///
23 /// # Errors
24 /// Returns [`crate::error::ErrorKind::InvalidConfig`] for zero model turns or
25 /// parallel actions, or more than 65,536 aggregate actions. Zero aggregate
26 /// actions disables tools without disabling model turns.
27 ///
28 /// ```
29 /// use agents_kit::config::CustomLoopLimits;
30 /// let limits = CustomLoopLimits::new(8, 0, 1)?;
31 /// assert_eq!(limits.max_tool_actions(), 0);
32 /// # Ok::<(), agents_kit::KitError>(())
33 /// ```
34 pub fn new(
35 max_model_turns: usize,
36 max_tool_actions: usize,
37 max_parallel_tool_actions: usize,
38 ) -> Result<Self, KitError> {
39 let max_model_turns = NonZeroUsize::new(max_model_turns)
40 .ok_or_else(|| KitError::invalid_config("Custom model-turn limit must be positive"))?;
41 let max_parallel_tool_actions =
42 NonZeroUsize::new(max_parallel_tool_actions).ok_or_else(|| {
43 KitError::invalid_config("Custom parallel-tool limit must be positive")
44 })?;
45 if max_tool_actions > 65_536 {
46 return Err(KitError::invalid_config(
47 "Custom aggregate tool-action limit exceeds the portable bound",
48 ));
49 }
50 Ok(Self {
51 max_model_turns,
52 max_tool_actions,
53 max_parallel_tool_actions,
54 })
55 }
56
57 /// Returns the maximum number of model turns admitted by one prompt.
58 ///
59 /// ```
60 /// use agents_kit::config::CustomLoopLimits;
61 ///
62 /// let limits = CustomLoopLimits::new(8, 16, 2)?;
63 /// assert_eq!(limits.max_model_turns(), 8);
64 /// # Ok::<(), agents_kit::KitError>(())
65 /// ```
66 #[must_use]
67 pub fn max_model_turns(self) -> usize {
68 self.max_model_turns.get()
69 }
70
71 /// Returns the aggregate tool-action limit for one prompt; zero disables actions.
72 ///
73 /// ```
74 /// use agents_kit::config::CustomLoopLimits;
75 ///
76 /// let limits = CustomLoopLimits::new(8, 0, 2)?;
77 /// assert_eq!(limits.max_tool_actions(), 0);
78 /// # Ok::<(), agents_kit::KitError>(())
79 /// ```
80 #[must_use]
81 pub fn max_tool_actions(self) -> usize {
82 self.max_tool_actions
83 }
84
85 /// Returns the maximum number of tool actions that may execute concurrently.
86 ///
87 /// ```
88 /// use agents_kit::config::CustomLoopLimits;
89 ///
90 /// let limits = CustomLoopLimits::new(8, 16, 2)?;
91 /// assert_eq!(limits.max_parallel_tool_actions(), 2);
92 /// # Ok::<(), agents_kit::KitError>(())
93 /// ```
94 #[must_use]
95 pub fn max_parallel_tool_actions(self) -> usize {
96 self.max_parallel_tool_actions.get()
97 }
98
99 pub(crate) fn validate(self) -> Result<(), KitError> {
100 if self.max_tool_actions > 65_536 {
101 return Err(KitError::invalid_state(
102 "stored Custom tool-action limit exceeds the portable bound",
103 ));
104 }
105 Ok(())
106 }
107}
108
109/// Approval behavior for model-requested tool actions.
110#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
111#[serde(rename_all = "snake_case")]
112pub enum CustomToolApproval {
113 /// Execute admitted actions without a caller interaction.
114 Allow,
115 /// Reject every model-requested action.
116 Deny,
117 /// Publish one durable notification and await its resolution.
118 Interaction,
119}
120
121/// Passive policy for the provider-backed Custom runtime.
122///
123/// ```
124/// use agents_kit::{config::{CustomAgentConfig, CustomLoopLimits, CustomToolApproval},
125/// model::ModelSelector, tool::ToolSelection};
126/// let config = CustomAgentConfig::new(ModelSelector::new("exact-model-id")?,
127/// ToolSelection::None, CustomLoopLimits::new(4, 0, 1)?, CustomToolApproval::Deny)?;
128/// assert_eq!(config.default_tool_selection(), &ToolSelection::None);
129/// # Ok::<(), agents_kit::KitError>(())
130/// ```
131#[derive(Clone, Debug)]
132pub struct CustomAgentConfig {
133 default_model: ModelSelector,
134 default_tool_selection: ToolSelection,
135 loop_limits: CustomLoopLimits,
136 tool_approval: CustomToolApproval,
137}
138
139impl CustomAgentConfig {
140 /// Constructs a complete Custom policy with no runtime resources.
141 ///
142 /// # Errors
143 /// Returns [`crate::error::ErrorKind::InvalidInput`] for an empty named tool
144 /// selection, duplicate names, or names outside the portable tool vocabulary.
145 /// Model availability is checked by the selected backend during use.
146 ///
147 /// ```
148 /// use agents_kit::{config::{CustomAgentConfig, CustomLoopLimits, CustomToolApproval},
149 /// model::ModelSelector, tool::ToolSelection};
150 /// let config = CustomAgentConfig::new(ModelSelector::new("exact-model-id")?,
151 /// ToolSelection::None, CustomLoopLimits::new(4, 0, 1)?, CustomToolApproval::Deny)?;
152 /// assert_eq!(config.default_tool_selection(), &ToolSelection::None);
153 /// # Ok::<(), agents_kit::KitError>(())
154 /// ```
155 pub fn new(
156 default_model: ModelSelector,
157 default_tool_selection: ToolSelection,
158 loop_limits: CustomLoopLimits,
159 tool_approval: CustomToolApproval,
160 ) -> Result<Self, KitError> {
161 default_tool_selection.validate()?;
162 Ok(Self {
163 default_model,
164 default_tool_selection,
165 loop_limits,
166 tool_approval,
167 })
168 }
169
170 /// Returns the configured default model selector.
171 ///
172 /// ```
173 /// use agents_kit::{
174 /// config::{CustomAgentConfig, CustomLoopLimits, CustomToolApproval},
175 /// model::ModelSelector,
176 /// tool::ToolSelection,
177 /// };
178 ///
179 /// let config = CustomAgentConfig::new(
180 /// ModelSelector::new("model-slug")?, ToolSelection::Auto,
181 /// CustomLoopLimits::new(8, 16, 2)?, CustomToolApproval::Allow,
182 /// )?;
183 /// assert_eq!(config.default_model().as_str(), "model-slug");
184 /// # Ok::<(), agents_kit::KitError>(())
185 /// ```
186 #[must_use]
187 pub fn default_model(&self) -> &ModelSelector {
188 &self.default_model
189 }
190
191 /// Returns the configured default tool selection.
192 ///
193 /// ```
194 /// use agents_kit::{
195 /// config::{CustomAgentConfig, CustomLoopLimits, CustomToolApproval},
196 /// model::ModelSelector,
197 /// tool::ToolSelection,
198 /// };
199 ///
200 /// let config = CustomAgentConfig::new(
201 /// ModelSelector::new("model-slug")?, ToolSelection::Auto,
202 /// CustomLoopLimits::new(8, 16, 2)?, CustomToolApproval::Allow,
203 /// )?;
204 /// assert_eq!(config.default_tool_selection(), &ToolSelection::Auto);
205 /// # Ok::<(), agents_kit::KitError>(())
206 /// ```
207 #[must_use]
208 pub fn default_tool_selection(&self) -> &ToolSelection {
209 &self.default_tool_selection
210 }
211
212 /// Returns the configured bounds for one prompt's model and tool work.
213 ///
214 /// ```
215 /// use agents_kit::{
216 /// config::{CustomAgentConfig, CustomLoopLimits, CustomToolApproval},
217 /// model::ModelSelector,
218 /// tool::ToolSelection,
219 /// };
220 ///
221 /// let limits = CustomLoopLimits::new(8, 16, 2)?;
222 /// let config = CustomAgentConfig::new(
223 /// ModelSelector::new("model-slug")?, ToolSelection::Auto,
224 /// limits, CustomToolApproval::Allow,
225 /// )?;
226 /// assert_eq!(config.loop_limits(), limits);
227 /// # Ok::<(), agents_kit::KitError>(())
228 /// ```
229 #[must_use]
230 pub fn loop_limits(&self) -> CustomLoopLimits {
231 self.loop_limits
232 }
233
234 /// Returns the approval policy for model-requested tool actions.
235 ///
236 /// ```
237 /// use agents_kit::{
238 /// config::{CustomAgentConfig, CustomLoopLimits, CustomToolApproval},
239 /// model::ModelSelector,
240 /// tool::ToolSelection,
241 /// };
242 ///
243 /// let config = CustomAgentConfig::new(
244 /// ModelSelector::new("model-slug")?, ToolSelection::Auto,
245 /// CustomLoopLimits::new(8, 16, 2)?, CustomToolApproval::Interaction,
246 /// )?;
247 /// assert_eq!(config.tool_approval(), CustomToolApproval::Interaction);
248 /// # Ok::<(), agents_kit::KitError>(())
249 /// ```
250 #[must_use]
251 pub fn tool_approval(&self) -> CustomToolApproval {
252 self.tool_approval
253 }
254
255 pub(crate) fn validate(&self) -> Result<(), KitError> {
256 self.default_tool_selection.validate()?;
257 self.loop_limits.validate()
258 }
259}