#[cfg(feature = "harness")]
pub mod assembly;
#[cfg(feature = "harness")]
pub mod compaction;
pub mod context;
pub mod context_cache;
#[cfg(feature = "harness")]
pub mod cost;
#[cfg(feature = "harness")]
pub mod messages;
pub mod model_request;
#[cfg(feature = "harness")]
pub mod permission;
#[cfg(feature = "harness")]
pub mod runtime_extensions;
pub mod run_loop;
#[cfg(feature = "harness")]
pub mod session;
#[cfg(feature = "harness")]
pub mod skills;
#[cfg(feature = "harness")]
pub mod system_prompt;
#[cfg(feature = "harness")]
pub mod types;
use std::sync::Arc;
use parking_lot::Mutex;
use tokio::sync::{Notify, broadcast};
use tokio_util::sync::CancellationToken;
use crate::agent::run_loop::{run_agent_loop, run_agent_loop_continue};
use crate::observability::{
ObservationContext, OperationId, RuntimeObserver, noop_runtime_observer,
};
use crate::types::*;
use theway_llm_provider::Message;
pub type LoopListener = Arc<
dyn Fn(
LoopEvent,
CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>
+ Send
+ Sync,
>;
pub type LoopSyncCallback = Arc<dyn Fn(&LoopEvent) + Send + Sync>;
pub const LOOP_EVENT_BROADCAST_CAPACITY: usize = 256;
pub struct AgentOptions {
pub initial_state: Option<AgentState>,
pub convert_to_llm: Option<ConvertToLlm>,
pub transform_context: Option<TransformContext>,
pub transform_model_request: Option<TransformModelRequest>,
pub transform_message: Option<TransformMessage>,
pub provider_request_interceptor: Option<theway_llm_provider::ProviderRequestInterceptorHandle>,
pub stream_fn: Option<StreamFn>,
pub get_api_key: Option<GetApiKey>,
pub before_tool_call: Option<BeforeToolCallHook>,
pub after_tool_call: Option<AfterToolCallHook>,
pub transform_tool_result: Option<AfterToolCallHook>,
pub on_control_plane_prompt: Option<OnControlPlanePromptHook>,
pub should_stop_after_turn: Option<ShouldStopHook>,
pub prepare_next_turn: Option<PrepareNextTurnHook>,
pub steering_mode: QueueMode,
pub follow_up_mode: QueueMode,
pub session_id: Option<String>,
pub observer: Arc<dyn RuntimeObserver>,
pub observation_context: ObservationContext,
pub observation_parent: Option<OperationId>,
pub tool_execution: ToolExecutionMode,
pub max_iterations: Option<u32>,
}
impl Default for AgentOptions {
fn default() -> Self {
Self {
initial_state: None,
convert_to_llm: None,
transform_context: None,
transform_model_request: None,
transform_message: None,
provider_request_interceptor: None,
stream_fn: None,
get_api_key: None,
before_tool_call: None,
after_tool_call: None,
transform_tool_result: None,
on_control_plane_prompt: None,
should_stop_after_turn: None,
prepare_next_turn: None,
steering_mode: QueueMode::default(),
follow_up_mode: QueueMode::default(),
session_id: None,
observer: noop_runtime_observer(),
observation_context: ObservationContext::default(),
observation_parent: None,
tool_execution: ToolExecutionMode::default(),
max_iterations: None,
}
}
}
pub struct Agent {
inner: Arc<AgentInner>,
}
pub(crate) struct AgentInner {
pub run_active: Mutex<bool>,
pub state: Mutex<AgentState>,
pub sync_callbacks: Mutex<Vec<LoopSyncCallback>>,
pub await_listeners: Mutex<Vec<LoopListener>>,
pub broadcast_tx: broadcast::Sender<LoopEvent>,
pub steering: Mutex<PendingMessageQueue>,
pub follow_up: Mutex<PendingMessageQueue>,
pub options: AgentOptions,
pub active_cancel: Mutex<Option<CancellationToken>>,
pub active_run_operation: Mutex<Option<OperationId>>,
pub active_turn_operation: Mutex<Option<(OperationId, u32)>>,
pub turn_cancel: Mutex<Option<CancellationToken>>,
pub idle: Notify,
pub max_iterations: Option<u32>,
pub context_cache: Mutex<crate::agent::context_cache::ContextCacheTracker>,
}
pub(crate) struct AgentRunPermit {
inner: Arc<AgentInner>,
}
impl AgentRunPermit {
pub(crate) fn acquire(inner: Arc<AgentInner>) -> Result<Self, AgentRunError> {
let mut active = inner.run_active.lock();
if *active {
return Err(AgentRunError::AlreadyStreaming);
}
*active = true;
{
let mut state = inner.state.lock();
state.is_streaming = true;
state.error_message = None;
}
drop(active);
Ok(Self { inner })
}
}
impl Drop for AgentRunPermit {
fn drop(&mut self) {
self.inner.release_run();
}
}
impl AgentInner {
pub(crate) fn release_run(&self) {
let mut active = self.run_active.lock();
if !*active {
return;
}
*self.active_cancel.lock() = None;
*self.active_run_operation.lock() = None;
*self.active_turn_operation.lock() = None;
*self.turn_cancel.lock() = None;
self.state.lock().is_streaming = false;
*active = false;
drop(active);
self.idle.notify_waiters();
}
}
pub(crate) struct PendingMessageQueue {
mode: QueueMode,
items: Vec<AgentMessage>,
}
impl PendingMessageQueue {
fn new(mode: QueueMode) -> Self {
Self {
mode,
items: Vec::new(),
}
}
pub fn enqueue(&mut self, m: AgentMessage) {
self.items.push(m);
}
pub fn drain(&mut self) -> Vec<AgentMessage> {
match self.mode {
QueueMode::All => std::mem::take(&mut self.items),
QueueMode::OneAtATime => {
if self.items.is_empty() {
Vec::new()
} else {
vec![self.items.remove(0)]
}
}
}
}
}
impl Agent {
pub fn new(mut options: AgentOptions) -> Self {
let state = options.initial_state.take().unwrap_or_default();
if options.convert_to_llm.is_none() {
options.convert_to_llm = Some(default_convert_to_llm());
}
let max_iterations = options.max_iterations;
let (broadcast_tx, _) = broadcast::channel(LOOP_EVENT_BROADCAST_CAPACITY);
let inner = AgentInner {
run_active: Mutex::new(false),
state: Mutex::new(state),
sync_callbacks: Mutex::new(Vec::new()),
await_listeners: Mutex::new(Vec::new()),
broadcast_tx,
steering: Mutex::new(PendingMessageQueue::new(options.steering_mode)),
follow_up: Mutex::new(PendingMessageQueue::new(options.follow_up_mode)),
options,
active_cancel: Mutex::new(None),
active_run_operation: Mutex::new(None),
active_turn_operation: Mutex::new(None),
turn_cancel: Mutex::new(None),
idle: Notify::new(),
max_iterations,
context_cache: Mutex::new(crate::agent::context_cache::ContextCacheTracker::new()),
};
Self {
inner: Arc::new(inner),
}
}
pub fn subscribe(&self, listener: LoopListener) -> impl FnOnce() {
let inner = self.inner.clone();
inner.await_listeners.lock().push(listener.clone());
move || {
let mut listeners = inner.await_listeners.lock();
if let Some(pos) = listeners.iter().position(|l| Arc::ptr_eq(l, &listener)) {
listeners.remove(pos);
}
}
}
pub fn subscribe_sync(&self, callback: LoopSyncCallback) -> impl FnOnce() {
let inner = self.inner.clone();
inner.sync_callbacks.lock().push(callback.clone());
move || {
let mut cbs = inner.sync_callbacks.lock();
if let Some(pos) = cbs.iter().position(|c| Arc::ptr_eq(c, &callback)) {
cbs.remove(pos);
}
}
}
pub fn subscribe_broadcast(&self) -> broadcast::Receiver<LoopEvent> {
self.inner.broadcast_tx.subscribe()
}
pub fn state(&self) -> parking_lot::MutexGuard<'_, AgentState> {
self.inner.state.lock()
}
pub fn is_streaming(&self) -> bool {
self.inner.state.lock().is_streaming
}
pub fn runtime_observer(&self) -> Arc<dyn RuntimeObserver> {
Arc::clone(&self.inner.options.observer)
}
pub fn observation_context(&self) -> ObservationContext {
self.inner.options.observation_context.clone()
}
pub fn active_run_operation(&self) -> Option<OperationId> {
*self.inner.active_run_operation.lock()
}
pub fn enqueue_steering(&self, message: AgentMessage) {
self.inner.steering.lock().enqueue(message);
}
pub fn enqueue_follow_up(&self, message: AgentMessage) {
self.inner.follow_up.lock().enqueue(message);
}
pub fn abort(&self) {
if let Some(token) = self.inner.active_cancel.lock().as_ref() {
token.cancel();
}
}
pub fn interrupt(&self) {
if let Some(token) = self.inner.turn_cancel.lock().as_ref() {
token.cancel();
}
}
pub fn active_token(&self) -> Option<CancellationToken> {
self.inner.active_cancel.lock().clone()
}
pub async fn wait_until_idle(&self) {
loop {
let notified = self.inner.idle.notified();
if !self.is_streaming() {
return;
}
notified.await;
}
}
pub async fn prompt(&self, message: AgentMessage) -> Result<(), AgentRunError> {
self.prompt_many(vec![message]).await
}
pub async fn prompt_many(&self, messages: Vec<AgentMessage>) -> Result<(), AgentRunError> {
run_agent_loop(self.inner.clone(), messages).await
}
pub async fn continue_(&self) -> Result<(), AgentRunError> {
run_agent_loop_continue(self.inner.clone()).await
}
}
#[derive(Debug, thiserror::Error)]
pub enum AgentRunError {
#[error(
"Agent is already processing a prompt. Use enqueue_steering/enqueue_follow_up or wait for completion."
)]
AlreadyStreaming,
#[error("turn interrupted")]
TurnInterrupted,
#[error("{0}")]
Other(String),
}
impl AgentInner {
pub fn convert_to_llm(&self, msgs: &[AgentMessage]) -> Vec<Message> {
self.options
.convert_to_llm
.as_ref()
.expect("convert_to_llm is always set in Agent::new")(msgs)
}
}
#[cfg(test)]
tests_bridge_macro::tests_bridge!("agent");