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