mod critic;
mod executor;
mod plan;
mod reflection;
mod types;
pub use critic::{CompositeCritic, CompositeStrategy, Critic, StaticCritic, ThresholdCritic};
pub use executor::{Executor, ReactExecutor, SimpleExecutor};
pub use plan::{
IssueSeverity, Plan, PlanOutput, PlanStep, PlanStepOutput, PlanStore, PlanSummary,
PlanValidationIssue, Planner, StaticPlanner, StepResult, StepStatus, plan_output_schema,
};
pub use reflection::{
InMemoryReflectionStore, ReflectionExperience, ReflectionRecord, ReflectionStore,
default_refinement_prompt, default_reflection_prompt,
};
pub use types::{Critique, CritiqueOutput, critique_output_schema};
use crate::error::{ReactError, Result};
use crate::llm::ToolDefinition;
use crate::llm::types::Message;
use futures::future::BoxFuture;
use futures::stream::BoxStream;
use futures::stream::StreamExt as _;
use serde_json::Value;
pub use tokio_util::sync::CancellationToken;
#[derive(Debug)]
#[non_exhaustive]
pub enum AgentEvent {
Token(String),
ThinkStart,
ThinkEnd {
prompt_tokens: usize,
completion_tokens: usize,
},
ToolCall {
name: String,
args: Value,
},
ToolResult {
name: String,
output: String,
},
ToolError {
name: String,
error: String,
},
PlanGenerated {
steps: Vec<String>,
},
StepStart {
step_index: usize,
description: String,
},
StepEnd {
step_index: usize,
success: bool,
},
GuardTriggered {
guard: String,
blocked: bool,
},
MemoryRecalled {
count: usize,
},
ContextCompressed {
before_count: usize,
after_count: usize,
before_tokens: usize,
after_tokens: usize,
},
HandoffStart {
from: String,
to: String,
},
HandoffEnd {
to: String,
},
ReflectionStart {
iteration: usize,
},
ReflectionEnd {
iteration: usize,
score: f64,
passed: bool,
},
CritiqueGenerated {
score: f64,
passed: bool,
feedback: String,
},
Refining {
iteration: usize,
},
Chart { spec: Value },
Error {
source: String,
message: String,
},
FinalAnswer(String),
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AgentPhase {
Thinking,
Acting,
Planning,
Reflecting,
HandingOff,
Terminal,
}
impl AgentEvent {
pub fn prompt_tokens(&self) -> Option<usize> {
match self {
AgentEvent::ThinkEnd { prompt_tokens, .. } => Some(*prompt_tokens),
_ => None,
}
}
pub fn completion_tokens(&self) -> Option<usize> {
match self {
AgentEvent::ThinkEnd {
completion_tokens, ..
} => Some(*completion_tokens),
_ => None,
}
}
pub fn total_tokens(&self) -> Option<usize> {
match self {
AgentEvent::ThinkEnd {
prompt_tokens,
completion_tokens,
} => Some(prompt_tokens + completion_tokens),
_ => None,
}
}
pub fn tokens_used(&self) -> Option<usize> {
self.total_tokens()
}
pub fn phase(&self) -> AgentPhase {
match self {
AgentEvent::Token(_)
| AgentEvent::ThinkStart
| AgentEvent::ThinkEnd { .. }
| AgentEvent::MemoryRecalled { .. }
| AgentEvent::ContextCompressed { .. }
| AgentEvent::Chart { .. } => AgentPhase::Thinking,
AgentEvent::ToolCall { .. }
| AgentEvent::ToolResult { .. }
| AgentEvent::ToolError { .. }
| AgentEvent::GuardTriggered { .. } => AgentPhase::Acting,
AgentEvent::PlanGenerated { .. }
| AgentEvent::StepStart { .. }
| AgentEvent::StepEnd { .. } => AgentPhase::Planning,
AgentEvent::ReflectionStart { .. }
| AgentEvent::ReflectionEnd { .. }
| AgentEvent::CritiqueGenerated { .. }
| AgentEvent::Refining { .. } => AgentPhase::Reflecting,
AgentEvent::HandoffStart { .. } | AgentEvent::HandoffEnd { .. } => {
AgentPhase::HandingOff
}
AgentEvent::FinalAnswer(_) | AgentEvent::Cancelled | AgentEvent::Error { .. } => {
AgentPhase::Terminal
}
}
}
pub fn is_checkpoint(&self) -> bool {
matches!(
self,
AgentEvent::ThinkEnd { .. }
| AgentEvent::ToolResult { .. }
| AgentEvent::ToolError { .. }
| AgentEvent::PlanGenerated { .. }
| AgentEvent::StepEnd { .. }
| AgentEvent::ReflectionEnd { .. }
| AgentEvent::HandoffEnd { .. }
| AgentEvent::ContextCompressed { .. }
| AgentEvent::FinalAnswer(_)
| AgentEvent::Cancelled
| AgentEvent::Error { .. }
)
}
}
#[derive(Debug)]
pub enum StepType {
Thought(String),
Call {
tool_call_id: String,
function_name: String,
arguments: Value,
},
}
pub trait Agent: Send + Sync {
fn name(&self) -> &str;
fn model_name(&self) -> &str;
fn system_prompt(&self) -> &str;
fn tool_names(&self) -> Vec<String> {
vec![]
}
fn tool_definitions(&self) -> Vec<ToolDefinition> {
vec![]
}
fn skill_names(&self) -> Vec<String> {
vec![]
}
fn mcp_server_names(&self) -> Vec<String> {
vec![]
}
fn close<'a>(&'a self) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn execute<'a>(&'a self, task: &'a str) -> BoxFuture<'a, Result<String>>;
fn execute_stream<'a>(
&'a self,
task: &'a str,
) -> BoxFuture<'a, Result<BoxStream<'a, Result<AgentEvent>>>>;
fn execute_stream_with_cancel<'a>(
&'a self,
task: &'a str,
cancel: CancellationToken,
) -> BoxFuture<'a, Result<BoxStream<'a, Result<AgentEvent>>>> {
Box::pin(async move {
let mut stream = self.execute_stream(task).await?;
let wrapped = async_stream::try_stream! {
loop {
tokio::select! {
_ = cancel.cancelled() => {
yield AgentEvent::Cancelled;
break;
}
next = stream.next() => {
match next {
Some(event) => yield event?,
None => break,
}
}
}
}
};
Ok(Box::pin(wrapped) as BoxStream<'a, Result<AgentEvent>>)
})
}
fn chat<'a>(&'a self, message: &'a str) -> BoxFuture<'a, Result<String>> {
self.execute(message)
}
fn chat_stream<'a>(
&'a self,
message: &'a str,
) -> BoxFuture<'a, Result<BoxStream<'a, Result<AgentEvent>>>> {
self.execute_stream(message)
}
fn chat_stream_with_cancel<'a>(
&'a self,
message: &'a str,
cancel: CancellationToken,
) -> BoxFuture<'a, Result<BoxStream<'a, Result<AgentEvent>>>> {
Box::pin(async move {
let mut stream = self.chat_stream(message).await?;
let wrapped = async_stream::try_stream! {
loop {
tokio::select! {
_ = cancel.cancelled() => {
yield AgentEvent::Cancelled;
break;
}
next = stream.next() => {
match next {
Some(event) => yield event?,
None => break,
}
}
}
}
};
Ok(Box::pin(wrapped) as BoxStream<'a, Result<AgentEvent>>)
})
}
fn reset(&self) {}
}
pub trait AgentCallback: Send + Sync {
fn on_think_start<'a>(
&'a self,
_agent: &'a str,
_messages: &'a [Message],
) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn on_think_end<'a>(
&'a self,
_agent: &'a str,
_steps: &'a [StepType],
_prompt_tokens: usize,
_completion_tokens: usize,
) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn on_tool_start<'a>(
&'a self,
_agent: &'a str,
_tool: &'a str,
_args: &'a Value,
) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn on_tool_end<'a>(
&'a self,
_agent: &'a str,
_tool: &'a str,
_result: &'a str,
) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn on_tool_error<'a>(
&'a self,
_agent: &'a str,
_tool: &'a str,
_err: &'a ReactError,
) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn on_final_answer<'a>(&'a self, _agent: &'a str, _answer: &'a str) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn on_iteration<'a>(&'a self, _agent: &'a str, _iteration: usize) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
}