use lellm_core::{ChatResponse, Message};
use lellm_graph::WorkflowState;
use super::context::estimate_tokens;
use super::event::StopReason;
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct AgentState {
pub messages: Vec<Message>,
pub iterations: usize,
pub total_tool_calls: usize,
pub output_tokens: usize,
pub reasoning_tokens: usize,
pub compact_count: usize,
pub stop_reason: Option<StopReason>,
pub last_response: Option<ChatResponse>,
}
impl AgentState {
pub fn from_messages(messages: Vec<Message>) -> Self {
Self {
messages,
iterations: 0,
total_tool_calls: 0,
output_tokens: 0,
reasoning_tokens: 0,
compact_count: 0,
stop_reason: None,
last_response: None,
}
}
pub fn estimated_context_tokens(&self) -> usize {
estimate_tokens(&self.messages)
}
pub fn reached_max(&self, max_iterations: usize) -> bool {
self.iterations >= max_iterations
}
pub fn exceeded_output(&self, max: Option<u32>) -> bool {
match max {
Some(limit) => self.output_tokens >= limit as usize,
None => false,
}
}
pub fn exceeded_reasoning(&self, max: Option<u32>) -> bool {
match max {
Some(limit) => self.reasoning_tokens >= limit as usize,
None => false,
}
}
pub fn exceeded_output_with_extra(&self, max: Option<u32>, extra: usize) -> bool {
match max {
Some(limit) => self.output_tokens + extra >= limit as usize,
None => false,
}
}
pub fn exceeded_reasoning_with_extra(&self, max: Option<u32>, extra: usize) -> bool {
match max {
Some(limit) => self.reasoning_tokens + extra >= limit as usize,
None => false,
}
}
pub fn is_terminal(&self) -> bool {
self.stop_reason.is_some()
}
pub fn messages_ref(&self) -> &[Message] {
&self.messages
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum AgentEffect {
AppendMessage(Message),
AppendMessages(Vec<Message>),
IncrementIteration,
AddToolCalls(usize),
AddOutputTokens(usize),
AddReasoningTokens(usize),
IncrementCompactCount,
ReplaceMessages(Vec<Message>),
SetStopReason(StopReason),
SetLastResponse(ChatResponse),
}
impl lellm_graph::Effect for AgentEffect {}
impl WorkflowState for AgentState {
type Effect = AgentEffect;
fn apply(&mut self, effect: Self::Effect) {
match effect {
AgentEffect::AppendMessage(msg) => {
self.messages.push(msg);
}
AgentEffect::AppendMessages(msgs) => {
self.messages.extend(msgs);
}
AgentEffect::IncrementIteration => {
self.iterations += 1;
}
AgentEffect::AddToolCalls(n) => {
self.total_tool_calls += n;
}
AgentEffect::AddOutputTokens(n) => {
self.output_tokens += n;
}
AgentEffect::AddReasoningTokens(n) => {
self.reasoning_tokens += n;
}
AgentEffect::IncrementCompactCount => {
self.compact_count += 1;
}
AgentEffect::ReplaceMessages(msgs) => {
self.messages = msgs;
}
AgentEffect::SetStopReason(reason) => {
self.stop_reason = Some(reason);
}
AgentEffect::SetLastResponse(response) => {
self.last_response = Some(response);
}
}
}
}
pub struct AgentStateMerge;
impl lellm_graph::MergeStrategy<AgentState> for AgentStateMerge {
fn merge(branches: Vec<AgentState>) -> Result<AgentState, lellm_graph::WorkflowError> {
let mut iter = branches.into_iter();
let mut merged = iter.next().ok_or_else(|| {
lellm_graph::WorkflowError::MergeConflict("no branches to merge".into())
})?;
for branch in iter {
merged.messages.extend(branch.messages);
merged.iterations = merged.iterations.max(branch.iterations);
merged.total_tool_calls = merged.total_tool_calls.max(branch.total_tool_calls);
merged.output_tokens += branch.output_tokens;
merged.reasoning_tokens += branch.reasoning_tokens;
merged.compact_count += branch.compact_count;
if merged.stop_reason.is_none() {
merged.stop_reason = branch.stop_reason;
}
if merged.last_response.is_none() {
merged.last_response = branch.last_response;
}
}
Ok(merged)
}
fn default_instance() -> Self {
AgentStateMerge
}
}