1mod options;
11mod prepare_call;
12mod tool_loop_agent;
13
14use std::fmt;
15use std::future::Future;
16use std::sync::Arc;
17
18use ferrin_message::Message;
19use ferrin_tool::ToolSet;
20use tokio_util::sync::CancellationToken;
21
22pub use prepare_call::PrepareCall;
23pub use prepare_call::PrepareCallInput;
24pub use prepare_call::PreparedCall;
25pub use tool_loop_agent::AGENT_USER_AGENT;
26pub use tool_loop_agent::ToolLoopAgent;
27pub use tool_loop_agent::ToolLoopAgentBuilder;
28
29use crate::error::Error;
30use crate::generate_text::GenerateTextResult;
31use crate::generate_text::StepResult;
32use crate::hooks::HookFn;
33use crate::hooks::Hooks;
34use crate::stream_text::OnErrorFn;
35use crate::stream_text::StreamConfig;
36use crate::stream_text::StreamEvent;
37use crate::stream_text::StreamTextResult;
38use crate::stream_text::StreamTransform;
39use crate::telemetry::AbortEvent;
40use crate::telemetry::EndEvent;
41use crate::timeout::Timeout;
42
43pub trait Agent: Send + Sync {
46 type Options: Send + 'static;
48 type Output: Send + 'static;
50
51 fn id(&self) -> Option<&str>;
53
54 fn tools(&self) -> &ToolSet;
56
57 fn generate(
59 &self,
60 call: AgentCall<Self::Options>,
61 ) -> impl Future<Output = Result<GenerateTextResult<Self::Output>, Error>> + Send;
62
63 fn stream(
65 &self,
66 call: AgentStreamCall<Self::Options>,
67 ) -> impl Future<Output = Result<StreamTextResult<Self::Output>, Error>> + Send;
68}
69
70#[derive(Debug, Clone, PartialEq)]
72pub enum AgentInput {
73 Prompt(String),
75 Messages(Vec<Message>),
77}
78
79impl From<&str> for AgentInput {
80 fn from(text: &str) -> Self {
81 Self::Prompt(text.to_owned())
82 }
83}
84
85impl From<String> for AgentInput {
86 fn from(text: String) -> Self {
87 Self::Prompt(text)
88 }
89}
90
91impl From<Vec<Message>> for AgentInput {
92 fn from(messages: Vec<Message>) -> Self {
93 Self::Messages(messages)
94 }
95}
96
97pub struct AgentCall<O> {
99 pub input: AgentInput,
101 pub options: O,
103 pub cancellation: CancellationToken,
105 pub timeout: Option<Timeout>,
107 pub hooks: Hooks,
109 #[cfg(feature = "sandbox")]
111 pub sandbox: Option<Arc<dyn ferrin_tool::Sandbox>>,
112}
113
114impl<O: fmt::Debug> fmt::Debug for AgentCall<O> {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 f.debug_struct("AgentCall")
117 .field("input", &self.input)
118 .field("options", &self.options)
119 .field("timeout", &self.timeout)
120 .finish_non_exhaustive()
121 }
122}
123
124impl AgentCall<()> {
125 #[must_use]
127 pub fn new(input: impl Into<AgentInput>) -> Self {
128 Self {
129 input: input.into(),
130 options: (),
131 cancellation: CancellationToken::new(),
132 timeout: None,
133 hooks: Hooks::default(),
134 #[cfg(feature = "sandbox")]
135 sandbox: None,
136 }
137 }
138
139 #[must_use]
141 pub fn prompt(text: impl Into<String>) -> Self {
142 Self::new(AgentInput::Prompt(text.into()))
143 }
144
145 #[must_use]
147 pub fn messages(messages: impl IntoIterator<Item = Message>) -> Self {
148 Self::new(AgentInput::Messages(messages.into_iter().collect()))
149 }
150}
151
152impl<O> AgentCall<O> {
153 #[must_use]
155 pub fn options<P>(self, options: P) -> AgentCall<P> {
156 AgentCall {
157 input: self.input,
158 options,
159 cancellation: self.cancellation,
160 timeout: self.timeout,
161 hooks: self.hooks,
162 #[cfg(feature = "sandbox")]
163 sandbox: self.sandbox,
164 }
165 }
166
167 #[must_use]
169 pub fn cancellation(mut self, token: CancellationToken) -> Self {
170 self.cancellation = token;
171 self
172 }
173
174 #[must_use]
176 pub fn timeout(mut self, timeout: impl Into<Timeout>) -> Self {
177 self.timeout = Some(timeout.into());
178 self
179 }
180
181 #[must_use]
183 pub fn hooks(mut self, hooks: Hooks) -> Self {
184 self.hooks = self.hooks.merged(hooks);
185 self
186 }
187
188 #[must_use]
190 pub fn on_step_end(mut self, f: impl HookFn<StepResult>) -> Self {
191 self.hooks.on_step_end.push(Arc::new(f));
192 self
193 }
194
195 #[must_use]
197 pub fn on_end(mut self, f: impl HookFn<EndEvent>) -> Self {
198 self.hooks.on_end.push(Arc::new(f));
199 self
200 }
201
202 #[cfg(feature = "sandbox")]
204 #[must_use]
205 pub fn sandbox(mut self, sandbox: Arc<dyn ferrin_tool::Sandbox>) -> Self {
206 self.sandbox = Some(sandbox);
207 self
208 }
209
210 #[must_use]
212 pub fn streaming(self) -> AgentStreamCall<O> {
213 AgentStreamCall::new(self)
214 }
215}
216
217pub struct AgentStreamCall<O> {
219 pub call: AgentCall<O>,
221 pub(crate) stream: StreamConfig,
222}
223
224impl<O: fmt::Debug> fmt::Debug for AgentStreamCall<O> {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 f.debug_struct("AgentStreamCall")
227 .field("call", &self.call)
228 .field("stream", &self.stream)
229 .finish()
230 }
231}
232
233impl<O> From<AgentCall<O>> for AgentStreamCall<O> {
234 fn from(call: AgentCall<O>) -> Self {
235 Self::new(call)
236 }
237}
238
239impl<O> AgentStreamCall<O> {
240 #[must_use]
242 pub fn new(call: AgentCall<O>) -> Self {
243 Self {
244 call,
245 stream: StreamConfig::default(),
246 }
247 }
248
249 #[must_use]
251 pub fn transform(mut self, transform: impl StreamTransform + 'static) -> Self {
252 self.stream.transforms.push(Arc::new(transform));
253 self
254 }
255
256 #[must_use]
258 pub fn include_raw_chunks(mut self) -> Self {
259 self.stream.include_raw_chunks = true;
260 self
261 }
262
263 #[must_use]
265 pub fn stream_retries(mut self, retries: u32) -> Self {
266 self.stream.stream_retries = Some(retries);
267 self
268 }
269
270 #[must_use]
272 pub fn on_error(mut self, f: impl OnErrorFn) -> Self {
273 self.stream.on_error = Some(Arc::new(f));
274 self
275 }
276
277 #[must_use]
279 pub fn on_chunk(mut self, f: impl HookFn<StreamEvent>) -> Self {
280 self.call.hooks.on_chunk.push(Arc::new(f));
281 self
282 }
283
284 #[must_use]
286 pub fn on_abort(mut self, f: impl HookFn<AbortEvent>) -> Self {
287 self.call.hooks.on_abort.push(Arc::new(f));
288 self
289 }
290}