1#[cfg(feature = "harness")]
21pub mod assembly;
22#[cfg(feature = "harness")]
23pub mod compaction;
24pub mod context;
25pub mod context_cache;
26#[cfg(feature = "harness")]
27pub mod cost;
28#[cfg(feature = "harness")]
29pub mod messages;
30pub mod model_request;
31#[cfg(feature = "harness")]
32pub mod permission;
33#[cfg(feature = "harness")]
34pub mod runtime_extensions;
35pub mod run_loop;
37#[cfg(feature = "harness")]
38pub mod session;
39#[cfg(feature = "harness")]
40pub mod skills;
41#[cfg(feature = "harness")]
42pub mod system_prompt;
43#[cfg(feature = "harness")]
44pub mod types;
45use std::sync::Arc;
46
47use parking_lot::Mutex;
48use tokio::sync::{Notify, broadcast};
49use tokio_util::sync::CancellationToken;
50
51use crate::agent::run_loop::{run_agent_loop, run_agent_loop_continue};
52use crate::observability::{
53 ObservationContext, OperationId, RuntimeObserver, noop_runtime_observer,
54};
55use crate::types::*;
56
57use theway_llm_provider::Message;
58
59pub type LoopListener = Arc<
63 dyn Fn(
64 LoopEvent,
65 CancellationToken,
66 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>
67 + Send
68 + Sync,
69>;
70
71pub type LoopSyncCallback = Arc<dyn Fn(&LoopEvent) + Send + Sync>;
75
76pub const LOOP_EVENT_BROADCAST_CAPACITY: usize = 256;
78
79pub struct AgentOptions {
81 pub initial_state: Option<AgentState>,
82 pub convert_to_llm: Option<ConvertToLlm>,
83 pub transform_context: Option<TransformContext>,
84 pub transform_model_request: Option<TransformModelRequest>,
85 pub transform_message: Option<TransformMessage>,
86 pub provider_request_interceptor: Option<theway_llm_provider::ProviderRequestInterceptorHandle>,
87 pub stream_fn: Option<StreamFn>,
88 pub get_api_key: Option<GetApiKey>,
89 pub before_tool_call: Option<BeforeToolCallHook>,
90 pub after_tool_call: Option<AfterToolCallHook>,
91 pub transform_tool_result: Option<AfterToolCallHook>,
94 pub on_control_plane_prompt: Option<OnControlPlanePromptHook>,
95 pub should_stop_after_turn: Option<ShouldStopHook>,
96 pub prepare_next_turn: Option<PrepareNextTurnHook>,
97 pub steering_mode: QueueMode,
98 pub follow_up_mode: QueueMode,
99 pub session_id: Option<String>,
100 pub observer: Arc<dyn RuntimeObserver>,
102 pub observation_context: ObservationContext,
104 pub observation_parent: Option<OperationId>,
106 pub tool_execution: ToolExecutionMode,
107 pub max_iterations: Option<u32>,
113}
114
115impl Default for AgentOptions {
116 fn default() -> Self {
117 Self {
118 initial_state: None,
119 convert_to_llm: None,
120 transform_context: None,
121 transform_model_request: None,
122 transform_message: None,
123 provider_request_interceptor: None,
124 stream_fn: None,
125 get_api_key: None,
126 before_tool_call: None,
127 after_tool_call: None,
128 transform_tool_result: None,
129 on_control_plane_prompt: None,
130 should_stop_after_turn: None,
131 prepare_next_turn: None,
132 steering_mode: QueueMode::default(),
133 follow_up_mode: QueueMode::default(),
134 session_id: None,
135 observer: noop_runtime_observer(),
136 observation_context: ObservationContext::default(),
137 observation_parent: None,
138 tool_execution: ToolExecutionMode::default(),
139 max_iterations: None,
140 }
141 }
142}
143
144pub struct Agent {
146 inner: Arc<AgentInner>,
147}
148
149pub(crate) struct AgentInner {
150 pub run_active: Mutex<bool>,
152 pub state: Mutex<AgentState>,
153 pub sync_callbacks: Mutex<Vec<LoopSyncCallback>>,
155 pub await_listeners: Mutex<Vec<LoopListener>>,
157 pub broadcast_tx: broadcast::Sender<LoopEvent>,
159 pub steering: Mutex<PendingMessageQueue>,
160 pub follow_up: Mutex<PendingMessageQueue>,
161 pub options: AgentOptions,
162 pub active_cancel: Mutex<Option<CancellationToken>>,
163 pub active_run_operation: Mutex<Option<OperationId>>,
166 pub active_turn_operation: Mutex<Option<(OperationId, u32)>>,
167 pub turn_cancel: Mutex<Option<CancellationToken>>,
170 pub idle: Notify,
171 pub max_iterations: Option<u32>,
173 pub context_cache: Mutex<crate::agent::context_cache::ContextCacheTracker>,
176}
177
178pub(crate) struct AgentRunPermit {
179 inner: Arc<AgentInner>,
180}
181
182impl AgentRunPermit {
183 pub(crate) fn acquire(inner: Arc<AgentInner>) -> Result<Self, AgentRunError> {
184 let mut active = inner.run_active.lock();
185 if *active {
186 return Err(AgentRunError::AlreadyStreaming);
187 }
188 *active = true;
189 {
190 let mut state = inner.state.lock();
191 state.is_streaming = true;
192 state.error_message = None;
193 }
194 drop(active);
195 Ok(Self { inner })
196 }
197}
198
199impl Drop for AgentRunPermit {
200 fn drop(&mut self) {
201 self.inner.release_run();
202 }
203}
204
205impl AgentInner {
206 pub(crate) fn release_run(&self) {
207 let mut active = self.run_active.lock();
208 if !*active {
209 return;
210 }
211 *self.active_cancel.lock() = None;
212 *self.active_run_operation.lock() = None;
213 *self.active_turn_operation.lock() = None;
214 *self.turn_cancel.lock() = None;
215 self.state.lock().is_streaming = false;
216 *active = false;
217 drop(active);
218 self.idle.notify_waiters();
219 }
220}
221
222pub(crate) struct PendingMessageQueue {
223 mode: QueueMode,
224 items: Vec<AgentMessage>,
225}
226
227impl PendingMessageQueue {
228 fn new(mode: QueueMode) -> Self {
229 Self {
230 mode,
231 items: Vec::new(),
232 }
233 }
234
235 pub fn enqueue(&mut self, m: AgentMessage) {
236 self.items.push(m);
237 }
238
239 pub fn drain(&mut self) -> Vec<AgentMessage> {
240 match self.mode {
241 QueueMode::All => std::mem::take(&mut self.items),
242 QueueMode::OneAtATime => {
243 if self.items.is_empty() {
244 Vec::new()
245 } else {
246 vec![self.items.remove(0)]
247 }
248 }
249 }
250 }
251}
252
253impl Agent {
254 pub fn new(mut options: AgentOptions) -> Self {
255 let state = options.initial_state.take().unwrap_or_default();
256 if options.convert_to_llm.is_none() {
257 options.convert_to_llm = Some(default_convert_to_llm());
258 }
259 let max_iterations = options.max_iterations;
260 let (broadcast_tx, _) = broadcast::channel(LOOP_EVENT_BROADCAST_CAPACITY);
261 let inner = AgentInner {
262 run_active: Mutex::new(false),
263 state: Mutex::new(state),
264 sync_callbacks: Mutex::new(Vec::new()),
265 await_listeners: Mutex::new(Vec::new()),
266 broadcast_tx,
267 steering: Mutex::new(PendingMessageQueue::new(options.steering_mode)),
268 follow_up: Mutex::new(PendingMessageQueue::new(options.follow_up_mode)),
269 options,
270 active_cancel: Mutex::new(None),
271 active_run_operation: Mutex::new(None),
272 active_turn_operation: Mutex::new(None),
273 turn_cancel: Mutex::new(None),
274 idle: Notify::new(),
275 max_iterations,
276 context_cache: Mutex::new(crate::agent::context_cache::ContextCacheTracker::new()),
277 };
278 Self {
279 inner: Arc::new(inner),
280 }
281 }
282
283 pub fn subscribe(&self, listener: LoopListener) -> impl FnOnce() {
290 let inner = self.inner.clone();
291 inner.await_listeners.lock().push(listener.clone());
292 move || {
293 let mut listeners = inner.await_listeners.lock();
294 if let Some(pos) = listeners.iter().position(|l| Arc::ptr_eq(l, &listener)) {
295 listeners.remove(pos);
296 }
297 }
298 }
299
300 pub fn subscribe_sync(&self, callback: LoopSyncCallback) -> impl FnOnce() {
303 let inner = self.inner.clone();
304 inner.sync_callbacks.lock().push(callback.clone());
305 move || {
306 let mut cbs = inner.sync_callbacks.lock();
307 if let Some(pos) = cbs.iter().position(|c| Arc::ptr_eq(c, &callback)) {
308 cbs.remove(pos);
309 }
310 }
311 }
312
313 pub fn subscribe_broadcast(&self) -> broadcast::Receiver<LoopEvent> {
316 self.inner.broadcast_tx.subscribe()
317 }
318
319 pub fn state(&self) -> parking_lot::MutexGuard<'_, AgentState> {
321 self.inner.state.lock()
322 }
323
324 pub fn is_streaming(&self) -> bool {
325 self.inner.state.lock().is_streaming
326 }
327
328 pub fn runtime_observer(&self) -> Arc<dyn RuntimeObserver> {
330 Arc::clone(&self.inner.options.observer)
331 }
332
333 pub fn observation_context(&self) -> ObservationContext {
335 self.inner.options.observation_context.clone()
336 }
337
338 pub fn active_run_operation(&self) -> Option<OperationId> {
340 *self.inner.active_run_operation.lock()
341 }
342
343 pub fn enqueue_steering(&self, message: AgentMessage) {
344 self.inner.steering.lock().enqueue(message);
345 }
346
347 pub fn enqueue_follow_up(&self, message: AgentMessage) {
348 self.inner.follow_up.lock().enqueue(message);
349 }
350
351 pub fn abort(&self) {
353 if let Some(token) = self.inner.active_cancel.lock().as_ref() {
354 token.cancel();
355 }
356 }
357
358 pub fn interrupt(&self) {
361 if let Some(token) = self.inner.turn_cancel.lock().as_ref() {
362 token.cancel();
363 }
364 }
365
366 pub fn active_token(&self) -> Option<CancellationToken> {
368 self.inner.active_cancel.lock().clone()
369 }
370
371 pub async fn wait_until_idle(&self) {
374 loop {
375 let notified = self.inner.idle.notified();
376 if !self.is_streaming() {
377 return;
378 }
379 notified.await;
380 }
381 }
382
383 pub async fn prompt(&self, message: AgentMessage) -> Result<(), AgentRunError> {
385 self.prompt_many(vec![message]).await
386 }
387
388 pub async fn prompt_many(&self, messages: Vec<AgentMessage>) -> Result<(), AgentRunError> {
390 run_agent_loop(self.inner.clone(), messages).await
391 }
392
393 pub async fn continue_(&self) -> Result<(), AgentRunError> {
395 run_agent_loop_continue(self.inner.clone()).await
396 }
397}
398
399#[derive(Debug, thiserror::Error)]
401pub enum AgentRunError {
402 #[error(
403 "Agent is already processing a prompt. Use enqueue_steering/enqueue_follow_up or wait for completion."
404 )]
405 AlreadyStreaming,
406 #[error("turn interrupted")]
409 TurnInterrupted,
410 #[error("{0}")]
411 Other(String),
412}
413
414impl AgentInner {
415 pub fn convert_to_llm(&self, msgs: &[AgentMessage]) -> Vec<Message> {
416 self.options
417 .convert_to_llm
418 .as_ref()
419 .expect("convert_to_llm is always set in Agent::new")(msgs)
420 }
421}
422
423#[cfg(test)]
424tests_bridge_macro::tests_bridge!("agent");