tirea_agent_loop/runtime/loop_runner/
outcome.rs1use super::*;
2use serde_json::{json, Value};
3
4#[derive(Debug, Clone, Default, PartialEq, Eq)]
6pub struct LoopUsage {
7 pub prompt_tokens: usize,
8 pub completion_tokens: usize,
9 pub total_tokens: usize,
10}
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct LoopStats {
15 pub duration_ms: u64,
16 pub steps: usize,
17 pub llm_calls: usize,
18 pub llm_retries: usize,
19 pub tool_calls: usize,
20 pub tool_errors: usize,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub(super) enum LoopFailure {
25 Llm(String),
26 State(String),
27}
28
29#[derive(Debug)]
31pub struct LoopOutcome {
32 pub run_ctx: crate::contracts::RunContext,
33 pub termination: TerminationReason,
34 pub response: Option<String>,
35 pub usage: LoopUsage,
36 pub stats: LoopStats,
37 #[allow(dead_code)]
39 pub(super) failure: Option<LoopFailure>,
40}
41
42impl LoopOutcome {
43 pub fn run_finish_result(&self) -> Option<Value> {
45 if !matches!(self.termination, TerminationReason::NaturalEnd) {
46 return None;
47 }
48 self.response
49 .as_ref()
50 .filter(|s| !s.is_empty())
51 .map(|text| json!({ "response": text }))
52 }
53
54 pub fn to_run_finish_event(self, run_id: String) -> AgentEvent {
56 AgentEvent::RunFinish {
57 thread_id: self.run_ctx.thread_id().to_string(),
58 run_id,
59 result: self.run_finish_result(),
60 termination: self.termination,
61 }
62 }
63}
64
65#[derive(Debug, thiserror::Error)]
67pub enum AgentLoopError {
68 #[error("LLM error: {0}")]
69 LlmError(String),
70 #[error("State error: {0}")]
71 StateError(String),
72 #[error("Run cancelled")]
74 Cancelled,
75}
76
77impl From<crate::contracts::runtime::ToolExecutorError> for AgentLoopError {
78 fn from(value: crate::contracts::runtime::ToolExecutorError) -> Self {
79 match value {
80 crate::contracts::runtime::ToolExecutorError::Cancelled { .. } => Self::Cancelled,
81 crate::contracts::runtime::ToolExecutorError::Failed { message } => {
82 Self::StateError(message)
83 }
84 }
85 }
86}
87
88pub fn tool_map<I, T>(tools: I) -> HashMap<String, Arc<dyn Tool>>
90where
91 I: IntoIterator<Item = T>,
92 T: Tool + 'static,
93{
94 tools
95 .into_iter()
96 .map(|t| {
97 let name = t.descriptor().id.clone();
98 (name, Arc::new(t) as Arc<dyn Tool>)
99 })
100 .collect()
101}
102
103pub fn tool_map_from_arc<I>(tools: I) -> HashMap<String, Arc<dyn Tool>>
105where
106 I: IntoIterator<Item = Arc<dyn Tool>>,
107{
108 tools
109 .into_iter()
110 .map(|t| (t.descriptor().id.clone(), t))
111 .collect()
112}