Skip to main content

ferrin_core/agent/
prepare_call.rs

1//! Per-call agent configuration and preparation callbacks.
2
3use std::fmt;
4use std::future::Future;
5use std::sync::Arc;
6
7use ferrin_spec::BoxFuture;
8use ferrin_spec::JsonValue;
9use ferrin_spec::LanguageModelRef;
10use ferrin_spec::ToolChoice;
11use ferrin_spec::ToolName;
12use ferrin_tool::ToolCallers;
13use ferrin_tool::ToolSet;
14use secrecy::SecretBox;
15
16use super::AgentInput;
17use crate::error::Error;
18use crate::generate_text::ApprovalPolicy;
19use crate::generate_text::Include;
20use crate::generate_text::PrepareStep;
21use crate::generate_text::RefineToolInputs;
22use crate::generate_text::StopCondition;
23use crate::generate_text::ToolCallRepair;
24use crate::prompt::CallSettings;
25use crate::prompt::DownloadFn;
26use crate::prompt::Instructions;
27use crate::retry::RetryPolicy;
28use crate::telemetry::TelemetryOptions;
29use crate::timeout::Timeout;
30
31/// The effective settings of one call, as seen by [`PrepareCall`].
32///
33/// Fields start with the agent's configuration and the call's input; the
34/// prepare function may rewrite them. `None` means "not set". An explicit
35/// call timeout takes precedence after preparation.
36#[derive(Clone)]
37pub struct PreparedCall {
38    /// The prompt or conversation.
39    pub input: AgentInput,
40    /// System instructions.
41    pub instructions: Option<Instructions>,
42    /// Whether system messages are allowed inside the conversation.
43    pub allow_system_in_messages: bool,
44    /// The model.
45    pub model: LanguageModelRef,
46    /// The tools.
47    pub tools: ToolSet,
48    /// Tool choice.
49    pub tool_choice: Option<ToolChoice>,
50    /// Tools sent to the model (all when `None`).
51    pub active_tools: Option<Vec<ToolName>>,
52    /// Tool order sent to the model.
53    pub tool_order: Vec<ToolName>,
54    /// Shared tool context.
55    pub tools_context: Option<JsonValue>,
56    /// Application state for the generation lifecycle, separate from tool context.
57    pub runtime_context: Option<JsonValue>,
58    /// Call-level approval policy; `None` uses the tools' own approval declarations.
59    pub tool_approval: Option<Arc<dyn ApprovalPolicy>>,
60    /// Secret used to sign approval requests, if configured.
61    pub tool_approval_secret: Option<Arc<SecretBox<[u8]>>>,
62    /// Allowed callers for individual tools.
63    pub tool_callers: ToolCallers,
64    /// Callback that selects settings before each step.
65    pub prepare_step: Option<Arc<dyn PrepareStep>>,
66    /// Callback that repairs invalid tool calls.
67    pub repair_tool_call: Option<Arc<dyn ToolCallRepair>>,
68    /// Input normalization functions indexed by tool name.
69    pub refine_tool_inputs: RefineToolInputs,
70    /// Downloader for file URLs that the model cannot fetch itself.
71    pub download: Option<Arc<dyn DownloadFn>>,
72    /// Sampling settings, headers and provider options.
73    pub settings: CallSettings,
74    /// Stop conditions (default: twenty steps).
75    pub stop_conditions: Vec<Arc<dyn StopCondition>>,
76    /// Timeouts.
77    pub timeout: Timeout,
78    /// Retry policy.
79    pub retry_policy: RetryPolicy,
80    /// Payloads copied into step results.
81    pub include: Include,
82    /// Tool concurrency limit.
83    pub max_tool_concurrency: Option<usize>,
84    /// Telemetry options.
85    pub telemetry: TelemetryOptions,
86}
87
88impl fmt::Debug for PreparedCall {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        f.debug_struct("PreparedCall")
91            .field("input", &self.input)
92            .field("instructions", &self.instructions)
93            .field("allow_system_in_messages", &self.allow_system_in_messages)
94            .field("model", &self.model)
95            .field("tools", &self.tools.names().collect::<Vec<_>>())
96            .field("tool_choice", &self.tool_choice)
97            .field("active_tools", &self.active_tools)
98            .field("tool_order", &self.tool_order)
99            .field("tools_context", &self.tools_context)
100            .field("runtime_context", &self.runtime_context.is_some())
101            .field("tool_approval", &self.tool_approval.is_some())
102            .field("tool_approval_secret", &self.tool_approval_secret.is_some())
103            .field("tool_callers", &self.tool_callers)
104            .field("prepare_step", &self.prepare_step.is_some())
105            .field("repair_tool_call", &self.repair_tool_call.is_some())
106            .field("refine_tool_inputs", &self.refine_tool_inputs)
107            .field("download", &self.download.is_some())
108            .field("settings", &self.settings)
109            .field("stop_conditions", &self.stop_conditions.len())
110            .field("timeout", &self.timeout)
111            .field("retry_policy", &self.retry_policy)
112            .field("include", &self.include)
113            .field("max_tool_concurrency", &self.max_tool_concurrency)
114            .finish_non_exhaustive()
115    }
116}
117
118/// Input of [`PrepareCall`].
119pub struct PrepareCallInput<Opt> {
120    /// The per-call options.
121    pub options: Opt,
122    /// The effective settings of the call, to be returned (possibly
123    /// modified).
124    pub defaults: PreparedCall,
125    /// The sandbox supplied on this invocation, visible during preparation.
126    #[cfg(feature = "sandbox")]
127    pub sandbox: Option<Arc<dyn ferrin_tool::Sandbox>>,
128}
129
130impl<Opt: fmt::Debug> fmt::Debug for PrepareCallInput<Opt> {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        let mut debug = f.debug_struct("PrepareCallInput");
133        debug
134            .field("options", &self.options)
135            .field("defaults", &self.defaults);
136        #[cfg(feature = "sandbox")]
137        debug.field("has_sandbox", &self.sandbox.is_some());
138        debug.finish()
139    }
140}
141
142/// Rewrites the settings of a call from its options (templated
143/// instructions, per-tenant tools, ...).
144pub trait PrepareCall<Opt>: Send + Sync {
145    /// Produces the settings to use.
146    fn prepare_call(
147        &self,
148        input: PrepareCallInput<Opt>,
149    ) -> BoxFuture<'_, Result<PreparedCall, Error>>;
150}
151
152impl<Opt, F, Fut> PrepareCall<Opt> for F
153where
154    F: Fn(PrepareCallInput<Opt>) -> Fut + Send + Sync,
155    Fut: Future<Output = Result<PreparedCall, Error>> + Send + 'static,
156{
157    fn prepare_call(
158        &self,
159        input: PrepareCallInput<Opt>,
160    ) -> BoxFuture<'_, Result<PreparedCall, Error>> {
161        Box::pin(self(input))
162    }
163}