use std::collections::VecDeque;
use foundation_core::valtron::{DrivenStreamIterator, Stream, StreamSpread};
use foundation_db::traits::DocumentStore;
use foundation_errstacks::{ErrorTrace, StructuredErrorTrace};
use crate::agentic::agent_loop::AgentLoop;
use crate::agentic::errors::AgenticError;
use crate::agentic::memory_store::MemoryStore;
use crate::agentic::progress::AgentProgress;
use crate::agentic::token_ledger::TokenSnapshot;
use crate::types::{Messages, ModelOutput, SessionRecord, ToolArguments, UserModelContent};
pub(crate) enum TurnItem {
Record(Box<SessionRecord>),
Progress(AgentProgress),
}
pub(crate) fn flatten(item: Stream<SessionRecord, AgentProgress>) -> Vec<TurnItem> {
match item {
Stream::Next(record) => vec![TurnItem::Record(Box::new(record))],
Stream::Pending(progress) => vec![TurnItem::Progress(progress)],
Stream::Spread(items) => items
.into_iter()
.map(|s| match s {
StreamSpread::Done(record) => TurnItem::Record(Box::new(record)),
StreamSpread::Pending(progress) => TurnItem::Progress(progress),
})
.collect(),
Stream::Init | Stream::Ignore | Stream::Wait | Stream::Delayed(_) => Vec::new(),
}
}
fn is_assistant(record: &SessionRecord) -> bool {
matches!(
record,
SessionRecord::Conversation {
message: Messages::Assistant { .. }
}
)
}
#[derive(Debug, Clone, PartialEq)]
pub enum TurnOutcome {
Completed,
Failed {
error: AgenticError,
trace: StructuredErrorTrace,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct Turn {
records: Vec<SessionRecord>,
outcome: TurnOutcome,
}
impl Turn {
pub(crate) fn collect<I>(stream: I) -> Self
where
I: Iterator<Item = Stream<SessionRecord, AgentProgress>>,
{
let mut records = Vec::new();
for item in stream {
for flat in flatten(item) {
let TurnItem::Record(record) = flat else {
continue;
};
match *record {
SessionRecord::FailedAction { error, trace } => {
return Self {
records,
outcome: TurnOutcome::Failed { error, trace },
};
}
SessionRecord::Retracted { reason, .. } => {
tracing::debug!(%reason, "run_turn: dropping a withdrawn turn");
records.retain(|kept| !is_assistant(kept));
}
other => records.push(other),
}
}
}
Self {
records,
outcome: TurnOutcome::Completed,
}
}
#[must_use]
pub fn text(&self) -> String {
self.records.iter().map(Self::text_of).collect()
}
#[must_use]
pub fn text_of(record: &SessionRecord) -> String {
match record {
SessionRecord::Conversation {
message:
Messages::Assistant {
content: ModelOutput::Text(t),
..
},
} => t.content.clone(),
_ => String::new(),
}
}
pub fn tool_calls(&self) -> impl Iterator<Item = (&str, &str, Option<&ToolArguments>)> {
self.records.iter().filter_map(|record| match record {
SessionRecord::Conversation {
message:
Messages::Assistant {
content:
ModelOutput::ToolCall {
id,
name,
arguments,
..
},
..
},
} => Some((id.as_str(), name.as_str(), arguments.as_ref())),
_ => None,
})
}
pub fn tool_results(&self) -> impl Iterator<Item = &Messages> {
self.records.iter().filter_map(|record| match record {
SessionRecord::Conversation {
message: message @ Messages::ToolResult { .. },
} => Some(message),
_ => None,
})
}
#[must_use]
pub fn usage(&self) -> Option<&TokenSnapshot> {
self.records.iter().rev().find_map(|record| match record {
SessionRecord::Summary { usage, .. } => Some(usage),
_ => None,
})
}
#[must_use]
pub fn into_records(self) -> Vec<SessionRecord> {
self.records
}
#[must_use]
pub fn failure(&self) -> Option<&AgenticError> {
match &self.outcome {
TurnOutcome::Completed => None,
TurnOutcome::Failed { error, .. } => Some(error),
}
}
#[must_use]
pub fn outcome(&self) -> &TurnOutcome {
&self.outcome
}
pub fn into_result(self) -> Result<Vec<SessionRecord>, ErrorTrace<AgenticError>> {
match self.outcome {
TurnOutcome::Completed => Ok(self.records),
TurnOutcome::Failed { error, .. } => Err(ErrorTrace::new(error)),
}
}
}
impl std::ops::Deref for Turn {
type Target = Vec<SessionRecord>;
fn deref(&self) -> &Self::Target {
&self.records
}
}
impl IntoIterator for Turn {
type Item = SessionRecord;
type IntoIter = std::vec::IntoIter<SessionRecord>;
fn into_iter(self) -> Self::IntoIter {
self.records.into_iter()
}
}
impl<'a> IntoIterator for &'a Turn {
type Item = &'a SessionRecord;
type IntoIter = std::slice::Iter<'a, SessionRecord>;
fn into_iter(self) -> Self::IntoIter {
self.records.iter()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Answer {
Complete(String),
Failed {
partial_text: String,
error: AgenticError,
trace: StructuredErrorTrace,
},
}
impl Answer {
pub(crate) fn collect<I>(stream: I) -> Self
where
I: Iterator<Item = Stream<SessionRecord, AgentProgress>>,
{
let mut text = String::new();
for item in stream {
for flat in flatten(item) {
let TurnItem::Record(record) = flat else {
continue;
};
match *record {
SessionRecord::FailedAction { error, trace } => {
return Answer::Failed {
partial_text: text,
error,
trace,
};
}
SessionRecord::Retracted { .. } => text.clear(),
record => text.push_str(&Turn::text_of(&record)),
}
}
}
Answer::Complete(text)
}
#[must_use]
pub fn text(&self) -> &str {
match self {
Answer::Complete(text) => text,
Answer::Failed { partial_text, .. } => partial_text,
}
}
#[must_use]
pub fn is_complete(&self) -> bool {
matches!(self, Answer::Complete(_))
}
pub fn into_result(self) -> Result<String, ErrorTrace<AgenticError>> {
match self {
Answer::Complete(text) => Ok(text),
Answer::Failed { error, .. } => Err(ErrorTrace::new(error)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TurnSummary {
pub message_count: u64,
pub usage: TokenSnapshot,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum TurnEvent {
Text(String),
Thinking(String),
ToolCall {
id: String,
name: String,
arguments: Option<ToolArguments>,
},
ToolResult {
tool_call_id: String,
name: String,
content: UserModelContent,
},
Retract { reason: String },
Progress(AgentProgress),
Failed(AgenticError),
Done(TurnSummary),
}
impl TurnEvent {
fn from_record(record: SessionRecord) -> Option<Self> {
match record {
SessionRecord::Conversation { message } => match message {
Messages::Assistant { content, .. } => match content {
ModelOutput::Text(t) => Some(TurnEvent::Text(t.content)),
ModelOutput::ThinkingContent { thinking, .. } => {
Some(TurnEvent::Thinking(thinking))
}
ModelOutput::ToolCall {
id,
name,
arguments,
..
} => Some(TurnEvent::ToolCall {
id,
name,
arguments,
}),
ModelOutput::Image(_) | ModelOutput::Embedding { .. } => None,
},
Messages::ToolResult {
tool_call_id,
name,
content,
..
} => Some(TurnEvent::ToolResult {
tool_call_id,
name,
content,
}),
Messages::User { .. } => None,
},
SessionRecord::Retracted { reason, .. } => Some(TurnEvent::Retract { reason }),
SessionRecord::FailedAction { error, .. } => Some(TurnEvent::Failed(error)),
SessionRecord::Summary {
message_count,
usage,
} => Some(TurnEvent::Done(TurnSummary {
message_count,
usage,
})),
SessionRecord::WorkingMemory { .. }
| SessionRecord::Observation { .. }
| SessionRecord::Reflection { .. } => None,
}
}
#[must_use]
pub fn is_terminal(&self) -> bool {
matches!(self, TurnEvent::Failed(_) | TurnEvent::Done(_))
}
}
pub struct TurnStream<D, M>(DrivenStreamIterator<AgentLoop<D, M>>)
where
D: DocumentStore + 'static,
M: MemoryStore + 'static;
impl<D: DocumentStore + 'static, M: MemoryStore + 'static> TurnStream<D, M> {
pub(crate) fn new(inner: DrivenStreamIterator<AgentLoop<D, M>>) -> Self {
Self(inner)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.0.is_closed()
}
#[must_use]
pub fn into_inner(self) -> DrivenStreamIterator<AgentLoop<D, M>> {
self.0
}
#[must_use]
pub fn inner(&self) -> &DrivenStreamIterator<AgentLoop<D, M>> {
&self.0
}
pub fn inner_mut(&mut self) -> &mut DrivenStreamIterator<AgentLoop<D, M>> {
&mut self.0
}
#[must_use]
pub fn events(self) -> TurnEvents<D, M> {
TurnEvents {
stream: self,
pending: VecDeque::new(),
finished: false,
}
}
}
impl<D: DocumentStore + 'static, M: MemoryStore + 'static> Iterator for TurnStream<D, M> {
type Item = Stream<SessionRecord, AgentProgress>;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl<D: DocumentStore + 'static, M: MemoryStore + 'static> From<TurnStream<D, M>>
for DrivenStreamIterator<AgentLoop<D, M>>
{
fn from(stream: TurnStream<D, M>) -> Self {
stream.0
}
}
pub struct TurnEvents<D, M>
where
D: DocumentStore + 'static,
M: MemoryStore + 'static,
{
stream: TurnStream<D, M>,
pending: VecDeque<TurnEvent>,
finished: bool,
}
impl<D: DocumentStore + 'static, M: MemoryStore + 'static> Iterator for TurnEvents<D, M> {
type Item = TurnEvent;
fn next(&mut self) -> Option<TurnEvent> {
loop {
if self.finished {
return None;
}
if let Some(event) = self.pending.pop_front() {
if event.is_terminal() {
self.finished = true;
self.pending.clear();
}
return Some(event);
}
let item = self.stream.next()?;
for flat in flatten(item) {
let event = match flat {
TurnItem::Record(record) => TurnEvent::from_record(*record),
TurnItem::Progress(progress) => Some(TurnEvent::Progress(progress)),
};
self.pending.extend(event);
}
}
}
}