use crate::agent::AgentContext;
use crate::error::{Error, LlmError};
use crate::ids::ThreadId;
use crate::llm::{FinishReason, Message, Usage};
use crate::memory::Episode;
use tracing::{debug, info};
use super::dispatch::dispatch_tool_calls;
pub(crate) struct StepOutcome {
pub message: Message,
pub finish_reason: FinishReason,
pub usage: Usage,
pub latency_ms: u32,
}
pub(crate) enum StepDisposition {
Done(String),
Continue,
}
pub(crate) async fn record_and_dispatch(
ctx: &AgentContext,
thread: &ThreadId,
step: u32,
outcome: StepOutcome,
dispatch_label: &'static str,
) -> Result<StepDisposition, Error> {
let StepOutcome {
message,
finish_reason,
usage,
latency_ms,
} = outcome;
ctx.episodic
.record(
ctx.run_id,
Episode::LlmCall {
tokens: usage.prompt_tokens + usage.completion_tokens,
latency_ms,
},
)
.await?;
ctx.short_term
.append(thread.clone(), message.clone())
.await?;
match finish_reason {
FinishReason::Stop | FinishReason::Length => {
info!(
step,
content_len = message.content.len(),
mode = dispatch_label,
"agent finished"
);
ctx.episodic.record(ctx.run_id, Episode::Completed).await?;
Ok(StepDisposition::Done(message.content))
}
FinishReason::ToolCalls => {
debug!(
step,
count = message.tool_calls.len(),
mode = dispatch_label,
"dispatching tools"
);
dispatch_tool_calls(ctx, thread, &message.tool_calls, dispatch_label).await?;
Ok(StepDisposition::Continue)
}
FinishReason::ContentFilter => Err(Error::Llm(LlmError::BadRequest(
"content-filter triggered".into(),
))),
FinishReason::Error => Err(Error::Llm(LlmError::Server(
"provider returned Error finish_reason".into(),
))),
}
}