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 = Arc<
30 dyn Fn(Vec<AgentMessage>) -> BoxFuture<'static, Vec<Message>> + Send + Sync,
31>;
32
33pub type TransformContext = Arc<
36 dyn Fn(
37 Vec<AgentMessage>,
38 CancellationToken,
39 ) -> BoxFuture<'static, Vec<AgentMessage>>
40 + Send
41 + Sync,
42>;
43
44pub type GetApiKey =
46 Arc<dyn Fn(&str) -> BoxFuture<'static, Option<String>> + Send + Sync>;
47
48pub type ShouldStopAfterTurn =
51 Arc<dyn Fn(ShouldStopAfterTurnContext<'_>) -> BoxFuture<'static, bool> + Send + Sync>;
52
53pub type PrepareNextTurn = Arc<
56 dyn Fn(ShouldStopAfterTurnContext<'_>) -> BoxFuture<'static, Option<AgentLoopTurnUpdate>>
57 + Send
58 + Sync,
59>;
60
61pub type GetSteeringMessages =
63 Arc<dyn Fn() -> BoxFuture<'static, Vec<AgentMessage>> + Send + Sync>;
64
65pub type GetFollowUpMessages =
67 Arc<dyn Fn() -> BoxFuture<'static, Vec<AgentMessage>> + Send + Sync>;
68
69pub type BeforeToolCall = Arc<
72 dyn Fn(
73 BeforeToolCallContext<'_>,
74 CancellationToken,
75 ) -> BoxFuture<'static, Option<BeforeToolCallResult>>
76 + Send
77 + Sync,
78>;
79
80pub type AfterToolCall = Arc<
83 dyn Fn(
84 AfterToolCallContext<'_>,
85 CancellationToken,
86 ) -> BoxFuture<'static, Option<AfterToolCallResult>>
87 + Send
88 + Sync,
89>;
90
91#[derive(Clone)]
97pub struct AgentLoopConfig {
98 pub model: Model,
99
100 pub convert_to_llm: ConvertToLlm,
104
105 pub transform_context: Option<TransformContext>,
106 pub get_api_key: Option<GetApiKey>,
107 pub should_stop_after_turn: Option<ShouldStopAfterTurn>,
108 pub prepare_next_turn: Option<PrepareNextTurn>,
109 pub get_steering_messages: Option<GetSteeringMessages>,
110 pub get_follow_up_messages: Option<GetFollowUpMessages>,
111 pub before_tool_call: Option<BeforeToolCall>,
112 pub after_tool_call: Option<AfterToolCall>,
113
114 pub tool_execution: ToolExecutionMode,
116
117 pub thinking_level: ThinkingLevel,
119 pub api_key: Option<String>,
120 pub timeout: Option<Duration>,
121 pub max_retries: Option<u32>,
122 pub max_retry_delay: Option<Duration>,
123 pub cache_retention: CacheRetention,
124 pub session_id: Option<String>,
125 pub signal: CancellationToken,
127}
128
129impl std::fmt::Debug for AgentLoopConfig {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 f.debug_struct("AgentLoopConfig")
132 .field("model", &self.model)
133 .field("tool_execution", &self.tool_execution)
134 .field("thinking_level", &self.thinking_level)
135 .field("cache_retention", &self.cache_retention)
136 .field("session_id", &self.session_id)
137 .field("transform_context", &self.transform_context.is_some())
138 .field("get_api_key", &self.get_api_key.is_some())
139 .field("should_stop_after_turn", &self.should_stop_after_turn.is_some())
140 .field("prepare_next_turn", &self.prepare_next_turn.is_some())
141 .field("get_steering_messages", &self.get_steering_messages.is_some())
142 .field("get_follow_up_messages", &self.get_follow_up_messages.is_some())
143 .field("before_tool_call", &self.before_tool_call.is_some())
144 .field("after_tool_call", &self.after_tool_call.is_some())
145 .finish()
146 }
147}
148
149impl AgentLoopConfig {
153 pub fn to_stream_options(&self, api_key: Option<String>) -> SimpleStreamOptions {
154 let mut opts = SimpleStreamOptions {
155 api_key,
156 timeout: self.timeout,
157 max_retries: self.max_retries,
158 max_retry_delay: self.max_retry_delay,
159 headers: None,
160 metadata: None,
161 cache_retention: self.cache_retention,
162 session_id: self.session_id.clone(),
163 signal: self.signal.clone(),
164 ..SimpleStreamOptions::default()
165 };
166 match self.thinking_level {
171 ThinkingLevel::Off => opts.reasoning = None,
172 other => opts.reasoning = Some(other),
173 }
174 opts
175 }
176}
177
178pub fn default_convert_to_llm(messages: Vec<AgentMessage>) -> Vec<Message> {
181 messages
182 .into_iter()
183 .filter_map(|m| match m {
184 AgentMessage::User(u) => Some(Message::User(u)),
185 AgentMessage::Assistant(a) => Some(Message::Assistant(a)),
186 AgentMessage::ToolResult(t) => Some(Message::ToolResult(t)),
187 AgentMessage::Custom(_) => None,
188 })
189 .collect()
190}
191
192pub fn default_convert_to_llm_fn() -> ConvertToLlm {
195 Arc::new(|messages: Vec<AgentMessage>| {
196 let out = default_convert_to_llm(messages);
197 Box::pin(async move { out })
198 })
199}