Skip to main content

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