1use std::time::Duration;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum TurnOutcomeStatus {
10 Completed,
11 Interrupted,
12 Failed,
13}
14
15#[derive(Debug, Clone)]
17pub struct TurnToolCall {
18 pub id: String,
19 pub name: String,
20 pub input: serde_json::Value,
21 pub result: Option<String>,
22 pub error: Option<String>,
23 pub duration: Option<Duration>,
24}
25
26impl TurnToolCall {
27 pub fn new(id: String, name: String, input: serde_json::Value) -> Self {
28 Self {
29 id,
30 name,
31 input,
32 result: None,
33 error: None,
34 duration: None,
35 }
36 }
37
38 pub fn set_result(&mut self, result: String, duration: Duration) {
39 self.result = Some(result);
40 self.duration = Some(duration);
41 }
42
43 pub fn set_error(&mut self, error: String, duration: Duration) {
44 self.error = Some(error);
45 self.duration = Some(duration);
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
51#[serde(rename_all = "snake_case")]
52pub enum TurnLoopMode {
53 Agent,
54 Yolo,
55 Plan,
56}
57
58impl TurnLoopMode {
59 #[must_use]
60 pub const fn is_plan(self) -> bool {
61 matches!(self, Self::Plan)
62 }
63
64 #[must_use]
69 pub fn from_setting(value: &str) -> Self {
70 match value.trim().to_ascii_lowercase().as_str() {
71 "plan" => Self::Plan,
72 "yolo" => Self::Yolo,
73 _ => Self::Agent,
74 }
75 }
76}
77
78#[derive(Debug)]
80pub struct TurnContext {
81 pub id: String,
82 pub step: u32,
83 pub max_steps: u32,
84 pub tool_calls: Vec<TurnToolCall>,
85 pub cancelled: bool,
86 pub usage: crate::models::Usage,
87}
88
89impl TurnContext {
90 #[must_use]
91 pub fn new(max_steps: u32) -> Self {
92 Self {
93 id: uuid::Uuid::new_v4().to_string(),
94 step: 0,
95 max_steps,
96 tool_calls: Vec::new(),
97 cancelled: false,
98 usage: crate::models::Usage::default(),
99 }
100 }
101
102 pub fn next_step(&mut self) -> bool {
103 self.step += 1;
104 self.step <= self.max_steps
105 }
106
107 #[must_use]
108 pub fn at_max_steps(&self) -> bool {
109 self.step >= self.max_steps
110 }
111
112 #[must_use]
114 pub fn steps_remaining(&self) -> u32 {
115 self.max_steps.saturating_sub(self.step)
116 }
117
118 pub fn record_tool_call(&mut self, call: TurnToolCall) {
119 self.tool_calls.push(call);
120 }
121
122 pub fn cancel(&mut self) {
123 self.cancelled = true;
124 }
125
126 pub fn add_usage(&mut self, usage: &crate::models::Usage) {
127 self.usage.input_tokens += usage.input_tokens;
128 self.usage.output_tokens += usage.output_tokens;
129 self.usage.prompt_cache_hit_tokens = add_optional_usage(
130 self.usage.prompt_cache_hit_tokens,
131 usage.prompt_cache_hit_tokens,
132 );
133 self.usage.prompt_cache_miss_tokens = add_optional_usage(
134 self.usage.prompt_cache_miss_tokens,
135 usage.prompt_cache_miss_tokens,
136 );
137 self.usage.reasoning_tokens =
138 add_optional_usage(self.usage.reasoning_tokens, usage.reasoning_tokens);
139 }
140}
141
142fn add_optional_usage(total: Option<u32>, delta: Option<u32>) -> Option<u32> {
143 match (total, delta) {
144 (Some(total), Some(delta)) => Some(total.saturating_add(delta)),
145 (None, Some(delta)) => Some(delta),
146 (Some(total), None) => Some(total),
147 (None, None) => None,
148 }
149}
150
151#[derive(Debug)]
156pub struct TurnState {
157 pub id: String,
158 pub step: u32,
159 pub max_steps: u32,
160 pub tool_calls: Vec<TurnToolCall>,
161 pub cancelled: bool,
162}
163
164impl TurnState {
165 pub fn new(max_steps: u32) -> Self {
166 Self {
167 id: uuid::Uuid::new_v4().to_string(),
168 step: 0,
169 max_steps,
170 tool_calls: Vec::new(),
171 cancelled: false,
172 }
173 }
174
175 pub fn next_step(&mut self) -> bool {
176 self.step += 1;
177 self.step <= self.max_steps
178 }
179
180 pub fn at_max_steps(&self) -> bool {
181 self.step >= self.max_steps
182 }
183
184 pub fn record_tool_call(&mut self, call: TurnToolCall) {
185 self.tool_calls.push(call);
186 }
187
188 pub fn cancel(&mut self) {
189 self.cancelled = true;
190 }
191
192 pub fn tool_names(&self) -> Vec<String> {
194 let mut names: Vec<String> = self.tool_calls.iter().map(|tc| tc.name.clone()).collect();
195 names.sort();
196 names.dedup();
197 names
198 }
199}