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 AgentMutation {
AppendMessage(Message),
AppendMessages(Vec<Message>),
IncrementIteration,
AddToolCalls(usize),
AddOutputTokens(usize),
AddReasoningTokens(usize),
IncrementCompactCount,
ReplaceMessages(Vec<Message>),
SetStopReason(StopReason),
SetLastResponse(ChatResponse),
}
impl lellm_graph::workflow_state::StateMutation<AgentState> for AgentMutation {
fn apply(self, state: &mut AgentState) {
match self {
AgentMutation::AppendMessage(msg) => {
state.messages.push(msg);
}
AgentMutation::AppendMessages(msgs) => {
state.messages.extend(msgs);
}
AgentMutation::IncrementIteration => {
state.iterations += 1;
}
AgentMutation::AddToolCalls(n) => {
state.total_tool_calls += n;
}
AgentMutation::AddOutputTokens(n) => {
state.output_tokens += n;
}
AgentMutation::AddReasoningTokens(n) => {
state.reasoning_tokens += n;
}
AgentMutation::IncrementCompactCount => {
state.compact_count += 1;
}
AgentMutation::ReplaceMessages(msgs) => {
state.messages = msgs;
}
AgentMutation::SetStopReason(reason) => {
state.stop_reason = Some(reason);
}
AgentMutation::SetLastResponse(response) => {
state.last_response = Some(response);
}
}
}
}
impl WorkflowState for AgentState {
type Mutation = AgentMutation;
}
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
}
}