1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//! Events emitted by Tenx during operation, for display to users.
use heck::ToSnakeCase;
use serde::{Deserialize, Serialize};
use serde_variant::to_variant_name;
use tokio::sync::mpsc;
use crate::{Result, TenxError};
/// Log levels used in events to indicate severity.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
/// Helper function to send an event and handle potential errors.
pub fn send_event(sender: &Option<mpsc::Sender<Event>>, event: Event) -> Result<()> {
if let Some(sender) = sender {
sender
.try_send(event)
.map_err(|e| TenxError::EventSend(e.to_string()))?;
}
Ok(())
}
// The events are listed below roughly in the order they are expected to occur
/// Events emitted during execution to track progress and provide feedback.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Event {
/// The pre check suite has started
PreCheckStart,
/// The pre check suite has ended
PreCheckEnd,
/// The post-patch validation suite has started
PostCheckStart,
/// The post-patch validation suite has ended
PostCheckEnd,
/// Context operations have started
ContextStart,
/// Context operations have ended
ContextEnd,
/// A context refresh operation started
ContextRefreshStart(String),
/// A context refresh operation ended
ContextRefreshEnd(String),
/// A check has started
CheckStart(String),
/// A check has passed
CheckOk(String),
/// A model request has started
PromptStart(String),
/// A model request has completed
PromptEnd(String),
/// We've been throttled for a given number of milliseconds
Throttled(u64),
/// A snippet of output text received from a model
Snippet(String),
/// A a complete, non-streamed response was received from a model
ModelResponse(String),
/// Patch application has started
ApplyPatch,
/// The command has started
Start,
/// The command has finished successfully
Finish,
/// Notify the output subsystem that user interaction is a bout to start.
/// This is needed in some cases to, for instance, stop a spinner.
Interact,
/// A log message with a specified log level
Log(LogLevel, String),
/// A retryable error has occurred
Retry {
/// An error to display to the user
user: String,
/// An error to the model, often the full tool output
model: String,
},
/// A fatal error has occurred
Fatal(String),
}
impl Event {
/// Returns the camelcase name of the event variant
pub fn name(&self) -> String {
to_variant_name(self).unwrap().to_snake_case()
}
/// If this event should have a progress bar or spinner, return an indicator string
pub fn progress_event(&self) -> Option<String> {
match self {
Event::ContextRefreshStart(s) => Some(s.clone()),
Event::CheckStart(s) => Some(s.clone()),
_ => None,
}
}
/// If this event is a section header, return a string description
pub fn header_message(&self) -> Option<String> {
match self {
Event::ApplyPatch => Some("applying patch".to_string()),
Event::ContextStart => Some("preparing context".to_string()),
Event::PreCheckStart => Some("pre checks".to_string()),
Event::PostCheckStart => Some("post checks".to_string()),
Event::PromptStart(n) => Some(format!("prompting {}", n)),
_ => None,
}
}
/// Returns the enclosed string if any, otherwise an empty string
pub fn display(&self) -> String {
match self {
Event::Snippet(s) | Event::CheckStart(s) => s.clone(),
Event::Log(_, s) => s.clone(),
_ => String::new(),
}
}
/// Returns an optional String if there's a commencement message related to the event
pub fn step_start_message(&self) -> Option<String> {
match self {
Event::PreCheckStart => Some("Pre checks...".to_string()),
Event::PostCheckStart => Some("Post checks...".to_string()),
Event::CheckStart(name) => Some(format!("Check {}...", name)),
Event::PromptStart(model) => Some(format!("Prompting {}...", model)),
Event::ApplyPatch => Some("Applying patch...".to_string()),
_ => None,
}
}
}
/// Helper struct to manage event sequencing
pub struct EventBlock {
sender: Option<mpsc::Sender<Event>>,
end_event: Event,
}
impl EventBlock {
/// Creates a new EventBlock, emitting the start event immediately
pub fn new(
sender: &Option<mpsc::Sender<Event>>,
start_event: Event,
end_event: Event,
) -> Result<Self> {
send_event(sender, start_event)?;
Ok(Self {
sender: sender.clone(),
end_event,
})
}
/// Creates a new EventBlock for start/finish operations
pub fn start(sender: &Option<mpsc::Sender<Event>>) -> Result<Self> {
Self::new(sender, Event::Start, Event::Finish)
}
/// Creates a new EventBlock for context operations
pub fn context(sender: &Option<mpsc::Sender<Event>>) -> Result<Self> {
Self::new(sender, Event::ContextStart, Event::ContextEnd)
}
/// Creates a new EventBlock for context refresh operations
pub fn context_refresh(sender: &Option<mpsc::Sender<Event>>, name: &str) -> Result<Self> {
Self::new(
sender,
Event::ContextRefreshStart(name.to_string()),
Event::ContextRefreshEnd(name.to_string()),
)
}
/// Creates a new EventBlock for pre check operations
pub fn pre_check(sender: &Option<mpsc::Sender<Event>>) -> Result<Self> {
Self::new(sender, Event::PreCheckStart, Event::PreCheckEnd)
}
/// Creates a new EventBlock for validator operations
pub fn check(sender: &Option<mpsc::Sender<Event>>, name: &str) -> Result<Self> {
Self::new(
sender,
Event::CheckStart(name.to_string()),
Event::CheckOk(name.to_string()),
)
}
/// Creates a new EventBlock for post-patch validation operations
pub fn post_check(sender: &Option<mpsc::Sender<Event>>) -> Result<Self> {
Self::new(sender, Event::PostCheckStart, Event::PostCheckEnd)
}
/// Creates a new EventBlock for model request operations
pub fn prompt(sender: &Option<mpsc::Sender<Event>>, model: &str) -> Result<Self> {
Self::new(
sender,
Event::PromptStart(model.to_string()),
Event::PromptEnd(model.to_string()),
)
}
}
impl Drop for EventBlock {
fn drop(&mut self) {
let _ = send_event(&self.sender, self.end_event.clone());
}
}