use crate::agent_loop::{
AfterCompactionEndFn, AfterLoopFn, AfterToolExecutionFn, AfterToolExecutionUpdateFn,
AfterTurnFn, AgentLoopConfig, BeforeCompactionStartFn, BeforeLoopFn, BeforeToolExecutionFn,
BeforeToolExecutionUpdateFn, BeforeTurnFn, ConvertToLlmFn, TransformContextFn,
};
use crate::agents::AgentProfile;
use crate::context::{ContextConfig, ExecutionLimits};
use crate::provider::ModelConfig;
use crate::types::*;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::mpsc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QueueMode {
OneAtATime,
All,
}
#[async_trait::async_trait]
pub trait Agent: Send {
async fn prompt_messages_with_sender(
&mut self,
messages: Vec<AgentMessage>,
tx: mpsc::UnboundedSender<AgentEvent>,
);
async fn continue_loop_with_sender(
&mut self,
tx: mpsc::UnboundedSender<AgentEvent>,
kind: ContinuationKind,
);
async fn prompt_with_sender(&mut self, text: String, tx: mpsc::UnboundedSender<AgentEvent>) {
let msg = AgentMessage::Llm(LlmMessage::new(Message::user(text)));
self.prompt_messages_with_sender(vec![msg], tx).await;
}
async fn prompt(&mut self, text: String) -> mpsc::UnboundedReceiver<AgentEvent> {
let (tx, rx) = mpsc::unbounded_channel();
self.prompt_with_sender(text, tx).await;
rx
}
async fn prompt_messages(
&mut self,
messages: Vec<AgentMessage>,
) -> mpsc::UnboundedReceiver<AgentEvent> {
let (tx, rx) = mpsc::unbounded_channel();
self.prompt_messages_with_sender(messages, tx).await;
rx
}
async fn continue_loop(&mut self) -> mpsc::UnboundedReceiver<AgentEvent> {
let (tx, rx) = mpsc::unbounded_channel();
self.continue_loop_with_sender(tx, ContinuationKind::Default)
.await;
rx
}
fn messages(&self) -> &[AgentMessage];
fn is_streaming(&self) -> bool;
fn agent_id(&self) -> &str;
fn session_id(&self) -> &str;
fn last_loop_id(&self) -> Option<&str> {
None
}
fn clear_messages(&mut self);
fn append_message(&mut self, msg: AgentMessage);
fn replace_messages(&mut self, msgs: Vec<AgentMessage>);
fn save_messages(&self) -> Result<String, serde_json::Error>;
fn restore_messages(&mut self, json: &str) -> Result<(), serde_json::Error>;
fn set_tools(&mut self, tools: Vec<Arc<dyn AgentTool>>);
fn abort(&self);
fn reset(&mut self);
fn steer(&self, _msg: AgentMessage) {}
fn follow_up(&self, _msg: AgentMessage) {}
fn clear_steering_queue(&self) {}
fn clear_follow_up_queue(&self) {}
fn clear_all_queues(&self) {
self.clear_steering_queue();
self.clear_follow_up_queue();
}
fn set_steering_mode(&mut self, _mode: QueueMode) {}
fn set_follow_up_mode(&mut self, _mode: QueueMode) {}
fn profile(&self) -> Option<&AgentProfile> {
None
}
fn system_prompt(&self) -> &str {
""
}
fn model_config(&self) -> Option<&ModelConfig> {
None
}
fn thinking_level(&self) -> ThinkingLevel {
ThinkingLevel::Off
}
fn temperature(&self) -> Option<f32> {
None
}
fn max_tokens(&self) -> Option<u32> {
None
}
fn context_config(&self) -> Option<&ContextConfig> {
None
}
fn execution_limits(&self) -> Option<&ExecutionLimits> {
None
}
fn cache_config(&self) -> CacheConfig {
CacheConfig::default()
}
fn tool_execution(&self) -> ToolExecutionStrategy {
ToolExecutionStrategy::default()
}
fn tool_timeout(&self) -> Option<std::time::Duration> {
None
}
fn response_format(&self) -> crate::provider::ResponseFormat {
crate::provider::ResponseFormat::Text
}
fn retry_config(&self) -> crate::provider::retry::RetryConfig {
crate::provider::retry::RetryConfig::default()
}
fn session(&self) -> Option<&crate::session::Session> {
None
}
fn workspace(&self) -> Option<&Path> {
None
}
fn set_before_turn(&mut self, _f: Option<BeforeTurnFn>) {}
fn set_after_turn(&mut self, _f: Option<AfterTurnFn>) {}
fn set_before_loop(&mut self, _f: Option<BeforeLoopFn>) {}
fn set_after_loop(&mut self, _f: Option<AfterLoopFn>) {}
fn set_before_tool_execution(&mut self, _f: Option<BeforeToolExecutionFn>) {}
fn set_after_tool_execution(&mut self, _f: Option<AfterToolExecutionFn>) {}
fn set_before_tool_execution_update(&mut self, _f: Option<BeforeToolExecutionUpdateFn>) {}
fn set_after_tool_execution_update(&mut self, _f: Option<AfterToolExecutionUpdateFn>) {}
fn set_convert_to_llm(&mut self, _f: Option<ConvertToLlmFn>) {}
fn set_transform_context(&mut self, _f: Option<TransformContextFn>) {}
fn set_block_compaction_strategy(
&mut self,
_s: Option<Arc<dyn crate::context::BlockCompactionStrategy>>,
) {
}
fn set_before_compaction_start(&mut self, _f: Option<BeforeCompactionStartFn>) {}
fn set_after_compaction_end(&mut self, _f: Option<AfterCompactionEndFn>) {}
fn set_prun_enabled(&mut self, _enabled: bool) {}
fn set_context_translation(
&mut self,
_s: Option<Arc<dyn crate::provider::context_translation::ContextTranslationStrategy>>,
) {
}
fn context_translation(
&self,
) -> Option<Arc<dyn crate::provider::context_translation::ContextTranslationStrategy>> {
None
}
fn build_config(&self) -> Result<AgentLoopConfig, AgentBuildError> {
let model_config = self
.model_config()
.ok_or(AgentBuildError::MissingModelConfig)?
.clone();
Ok(AgentLoopConfig {
model_config,
provider_override: None,
thinking_level: self.thinking_level(),
max_tokens: self.max_tokens(),
temperature: self.temperature(),
convert_to_llm: None,
transform_context: None,
get_steering_messages: None,
get_follow_up_messages: None,
context_config: self.context_config().cloned(),
execution_limits: self.execution_limits().cloned(),
cache_config: self.cache_config(),
tool_execution: self.tool_execution(),
tool_timeout: self.tool_timeout(),
response_format: self.response_format(),
retry_config: self.retry_config(),
before_turn: None,
after_turn: None,
before_loop: None,
after_loop: None,
before_tool_execution: None,
after_tool_execution: None,
before_tool_execution_update: None,
after_tool_execution_update: None,
before_compaction_start: None,
after_compaction_end: None,
on_error: None,
input_filters: vec![],
first_turn_trigger: TurnTrigger::User,
config_id: None,
context_translation: self.context_translation(),
prun_pending: None,
})
}
}
#[derive(Debug, thiserror::Error)]
pub enum AgentBuildError {
#[error(
"agent has no model_config; implement Agent::model_config() to return Some(...) \
or override Agent::build_config() entirely"
)]
MissingModelConfig,
}