use std::borrow::Cow;
use foundation_core::valtron::Stream;
use serde::{Deserialize, Serialize};
use crate::agentic::errors::AgenticError;
use crate::types::{Messages, ModelId, ModelState, SessionRecord, UsageReport};
pub type AgentStream = Stream<SessionRecord, AgentProgress>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum AgentProgress {
Initializing { step: Cow<'static, str> },
Generating {
model: ModelId,
tokens_so_far: Option<u64>,
},
ToolCallRequested { name: String },
ExecutingTools { total: usize, completed: usize },
ToolCallCancelled { id: String },
ProcessingMemory { kind: MemoryKind },
FlushingRecords { count: usize },
Steering { source: Cow<'static, str> },
TurnComplete { usage: UsageReport },
SessionEnding,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum MemoryKind {
Working,
Observation,
Reflection,
}
impl From<&ModelState> for AgentProgress {
fn from(state: &ModelState) -> Self {
match state {
ModelState::GeneratingTokens(usage) => AgentProgress::Generating {
model: ModelId::Name(String::new(), None),
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
tokens_so_far: usage.as_ref().map(|u| (u.input + u.output) as u64),
},
ModelState::GeneratingEmbeddings => AgentProgress::Initializing {
step: Cow::Borrowed("generating_embeddings"),
},
ModelState::Finished => AgentProgress::SessionEnding,
ModelState::Error(msg) => AgentProgress::Initializing {
step: Cow::Owned(format!("error: {msg}")),
},
}
}
}
#[must_use]
pub fn lift_model_item(item: Stream<Messages, ModelState>, model: &ModelId) -> AgentStream {
match item {
Stream::Next(message) => Stream::Next(SessionRecord::Conversation { message }),
Stream::Pending(ModelState::Error(msg)) => {
Stream::Next(AgenticError::Unexpected(msg).into_failed_action())
}
Stream::Pending(ModelState::GeneratingTokens(usage)) => {
Stream::Pending(AgentProgress::Generating {
model: model.clone(),
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
tokens_so_far: usage.as_ref().map(|u| (u.input + u.output) as u64),
})
}
Stream::Pending(state) => Stream::Pending(AgentProgress::from(&state)),
Stream::Init => Stream::Init,
Stream::Ignore => Stream::Ignore,
Stream::Wait => Stream::Wait,
Stream::Delayed(d) => Stream::Delayed(d),
Stream::Spread(items) => {
use foundation_core::valtron::StreamSpread;
let lifted = items
.into_iter()
.map(|s| match s {
StreamSpread::Done(message) => {
StreamSpread::Done(SessionRecord::Conversation { message })
}
StreamSpread::Pending(state) => {
StreamSpread::Pending(AgentProgress::from(&state))
}
})
.collect();
Stream::Spread(lifted)
}
}
}