1use crate::message::AgentMessage;
14use crate::types::{
15 AfterToolCallContext, AfterToolCallResult, AgentLoopTurnUpdate, BeforeToolCallContext,
16 BeforeToolCallResult, ShouldStopAfterTurnContext, ToolExecutionMode,
17};
18use futures::future::BoxFuture;
19use rpi_ai::provider::{CacheRetention, SimpleStreamOptions};
20use rpi_ai::types::{Message, ThinkingLevel};
21use rpi_ai::Model;
22use std::sync::Arc;
23use std::time::Duration;
24use tokio_util::sync::CancellationToken;
25
26pub type ConvertToLlm =
30 Arc<dyn Fn(Vec<AgentMessage>) -> BoxFuture<'static, Vec<Message>> + Send + Sync>;
31
32pub type TransformContext = Arc<
35 dyn Fn(Vec<AgentMessage>, CancellationToken) -> BoxFuture<'static, Vec<AgentMessage>>
36 + Send
37 + Sync,
38>;
39
40pub type GetApiKey = Arc<dyn Fn(&str) -> BoxFuture<'static, Option<String>> + Send + Sync>;
42
43pub type ShouldStopAfterTurn =
46 Arc<dyn Fn(ShouldStopAfterTurnContext<'_>) -> BoxFuture<'static, bool> + Send + Sync>;
47
48pub type PrepareNextTurn = Arc<
51 dyn Fn(ShouldStopAfterTurnContext<'_>) -> BoxFuture<'static, Option<AgentLoopTurnUpdate>>
52 + Send
53 + Sync,
54>;
55
56pub type AfterToolResults = Arc<
60 dyn Fn(ShouldStopAfterTurnContext<'_>) -> BoxFuture<'static, Option<AgentLoopTurnUpdate>>
61 + Send
62 + Sync,
63>;
64
65pub type GetSteeringMessages = Arc<dyn Fn() -> BoxFuture<'static, Vec<AgentMessage>> + Send + Sync>;
67
68pub type GetFollowUpMessages = Arc<dyn Fn() -> BoxFuture<'static, Vec<AgentMessage>> + Send + Sync>;
70
71pub type BeforeToolCall = Arc<
74 dyn Fn(
75 BeforeToolCallContext<'_>,
76 CancellationToken,
77 ) -> BoxFuture<'static, Option<BeforeToolCallResult>>
78 + Send
79 + Sync,
80>;
81
82pub type AfterToolCall = Arc<
85 dyn Fn(
86 AfterToolCallContext<'_>,
87 CancellationToken,
88 ) -> BoxFuture<'static, Option<AfterToolCallResult>>
89 + Send
90 + Sync,
91>;
92
93#[derive(Clone)]
99pub struct AgentLoopConfig {
100 pub model: Model,
101
102 pub convert_to_llm: ConvertToLlm,
106
107 pub transform_context: Option<TransformContext>,
108 pub get_api_key: Option<GetApiKey>,
109 pub should_stop_after_turn: Option<ShouldStopAfterTurn>,
110 pub prepare_next_turn: Option<PrepareNextTurn>,
111 pub after_tool_results: Option<AfterToolResults>,
112 pub get_steering_messages: Option<GetSteeringMessages>,
113 pub get_follow_up_messages: Option<GetFollowUpMessages>,
114 pub before_tool_call: Option<BeforeToolCall>,
115 pub after_tool_call: Option<AfterToolCall>,
116
117 pub tool_execution: ToolExecutionMode,
119
120 pub thinking_level: ThinkingLevel,
122 pub api_key: Option<String>,
123 pub timeout: Option<Duration>,
124 pub max_retries: Option<u32>,
125 pub max_retry_delay: Option<Duration>,
126 pub cache_retention: CacheRetention,
127 pub session_id: Option<String>,
128 pub signal: CancellationToken,
130}
131
132impl std::fmt::Debug for AgentLoopConfig {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 f.debug_struct("AgentLoopConfig")
135 .field("model", &self.model)
136 .field("tool_execution", &self.tool_execution)
137 .field("thinking_level", &self.thinking_level)
138 .field("cache_retention", &self.cache_retention)
139 .field("session_id", &self.session_id)
140 .field("transform_context", &self.transform_context.is_some())
141 .field("get_api_key", &self.get_api_key.is_some())
142 .field(
143 "should_stop_after_turn",
144 &self.should_stop_after_turn.is_some(),
145 )
146 .field("prepare_next_turn", &self.prepare_next_turn.is_some())
147 .field("after_tool_results", &self.after_tool_results.is_some())
148 .field(
149 "get_steering_messages",
150 &self.get_steering_messages.is_some(),
151 )
152 .field(
153 "get_follow_up_messages",
154 &self.get_follow_up_messages.is_some(),
155 )
156 .field("before_tool_call", &self.before_tool_call.is_some())
157 .field("after_tool_call", &self.after_tool_call.is_some())
158 .finish()
159 }
160}
161
162impl AgentLoopConfig {
166 pub fn to_stream_options(&self, api_key: Option<String>) -> SimpleStreamOptions {
167 let mut opts = SimpleStreamOptions {
168 api_key,
169 timeout: self.timeout,
170 max_retries: self.max_retries,
171 max_retry_delay: self.max_retry_delay,
172 headers: None,
173 metadata: None,
174 cache_retention: self.cache_retention,
175 session_id: self.session_id.clone(),
176 signal: self.signal.clone(),
177 ..SimpleStreamOptions::default()
178 };
179 match self.thinking_level {
184 ThinkingLevel::Off => opts.reasoning = None,
185 other => opts.reasoning = Some(other),
186 }
187 opts
188 }
189}
190
191pub fn default_convert_to_llm(messages: Vec<AgentMessage>) -> Vec<Message> {
194 messages
195 .into_iter()
196 .filter_map(|m| match m {
197 AgentMessage::User(u) => Some(Message::User(u)),
198 AgentMessage::Assistant(a) => Some(Message::Assistant(a)),
199 AgentMessage::ToolResult(t) => Some(Message::ToolResult(t)),
200 AgentMessage::Custom(_) => None,
201 })
202 .collect()
203}
204
205pub fn default_convert_to_llm_fn() -> ConvertToLlm {
208 Arc::new(|messages: Vec<AgentMessage>| {
209 let out = default_convert_to_llm(messages);
210 Box::pin(async move { out })
211 })
212}