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