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)]
38 pub(super) failure: Option<LoopFailure>,
39}
40
41impl LoopOutcome {
42 pub fn run_finish_result(&self) -> Option<Value> {
44 if !matches!(self.termination, TerminationReason::NaturalEnd) {
45 return None;
46 }
47 self.response
48 .as_ref()
49 .filter(|s| !s.is_empty())
50 .map(|text| json!({ "response": text }))
51 }
52
53 pub fn to_run_finish_event(self, run_id: String) -> AgentEvent {
55 AgentEvent::RunFinish {
56 thread_id: self.run_ctx.thread_id().to_string(),
57 run_id,
58 result: self.run_finish_result(),
59 termination: self.termination,
60 }
61 }
62}
63
64#[derive(Debug, thiserror::Error)]
66pub enum AgentLoopError {
67 #[error("LLM error: {0}")]
68 LlmError(String),
69 #[error("State error: {0}")]
70 StateError(String),
71 #[error("Run cancelled")]
73 Cancelled,
74}
75
76impl From<crate::contracts::runtime::ToolExecutorError> for AgentLoopError {
77 fn from(value: crate::contracts::runtime::ToolExecutorError) -> Self {
78 match value {
79 crate::contracts::runtime::ToolExecutorError::Cancelled { .. } => Self::Cancelled,
80 crate::contracts::runtime::ToolExecutorError::Failed { message } => {
81 Self::StateError(message)
82 }
83 }
84 }
85}
86
87pub fn tool_map<I, T>(tools: I) -> HashMap<String, Arc<dyn Tool>>
89where
90 I: IntoIterator<Item = T>,
91 T: Tool + 'static,
92{
93 tools
94 .into_iter()
95 .map(|t| {
96 let name = t.descriptor().id.clone();
97 (name, Arc::new(t) as Arc<dyn Tool>)
98 })
99 .collect()
100}
101
102pub fn tool_map_from_arc<I>(tools: I) -> HashMap<String, Arc<dyn Tool>>
104where
105 I: IntoIterator<Item = Arc<dyn Tool>>,
106{
107 tools
108 .into_iter()
109 .map(|t| (t.descriptor().id.clone(), t))
110 .collect()
111}