use crate::runtime::agent::{
hooks::{Hooks, Stage},
model_steps,
node::Step,
};
use crate::{Cancellation, Error, Message, event::Event};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct State {
pub stage: String,
pub idx: usize,
pub message: Message,
pub finish_reason: Option<String>,
}
impl State {
pub(crate) fn new(message: Message, finish_reason: Option<String>) -> Self {
Self {
stage: "after".into(),
idx: 0,
message,
finish_reason,
}
}
pub(crate) fn validate(&self, count: usize) -> Result<(), Error> {
if !matches!(self.stage.as_str(), "after" | "complete" | "committed")
|| self.idx > count
|| (self.stage != "after" && self.idx != count)
{
return Err(Error::new(
"INVALID_STATE",
"invalid Agent completion phase",
));
}
Ok(())
}
pub(crate) fn pending(&self) -> bool {
self.stage != "committed"
}
}
pub(super) async fn model(
hooks: &Hooks,
mut saved: Step,
mut commit: impl FnMut(Step) -> Result<(), Error>,
cancellation: &Cancellation,
) -> Result<Event, Error> {
let message = saved
.message
.clone()
.ok_or_else(|| Error::new("INVALID_STATE", "missing Model result"))?;
let result = Box::pin(hooks.after_from(
Stage::Model,
message,
saved.idx.unwrap_or(0),
|idx, message| {
saved.idx = Some(idx);
saved.message = Some(message);
commit(saved.clone())
},
cancellation,
))
.await;
match result {
Ok(message) => {
saved.message = Some(message);
saved.stage = Some("committed".into());
saved.status = "succeeded".into();
saved.error = None;
commit(saved.clone())?;
model_steps::event(&saved)
}
Err(error) => {
model_steps::stopped(std::slice::from_mut(&mut saved), &error);
commit(saved)?;
Err(error)
}
}
}
pub(super) async fn agent(
hooks: &Hooks,
mut saved: State,
mut commit: impl FnMut(State) -> Result<(), Error>,
cancellation: &Cancellation,
) -> Result<Event, Error> {
if saved.stage == "after" {
let message = Box::pin(hooks.after_from(
Stage::Execute,
saved.message.clone(),
saved.idx,
|idx, message| {
saved.idx = idx;
saved.message = message;
commit(saved.clone())
},
cancellation,
))
.await?;
saved.message = message;
saved.stage = "complete".into();
commit(saved.clone())?;
}
Ok(Event::Complete {
usage: saved.message.usage.clone(),
message: saved.message,
finish_reason: saved.finish_reason,
})
}
pub(super) fn has_calls(message: &Message) -> bool {
message
.content
.iter()
.any(|part| part.r#type == "tool_call")
}
pub(super) fn partial(completion: Option<&State>, steps: &[Step]) -> Option<Message> {
completion
.filter(|state| state.pending())
.map(|state| state.message.clone())
.or_else(|| model_steps::pending(steps).and_then(|step| step.message.clone()))
}