1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum Mode {
9 Conversation,
10 Diff,
11 Raw,
12}
13
14impl std::fmt::Display for Mode {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 match self {
17 Mode::Conversation => write!(f, "conversation"),
18 Mode::Diff => write!(f, "diff"),
19 Mode::Raw => write!(f, "raw"),
20 }
21 }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum Role {
27 User,
28 Assistant,
29 System,
30}
31
32impl std::fmt::Display for Role {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 Role::User => write!(f, "user"),
36 Role::Assistant => write!(f, "assistant"),
37 Role::System => write!(f, "system"),
38 }
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45pub struct HistoryRecord {
46 pub role: Role,
47 pub content: String,
48 pub mode: Mode,
49 #[serde(default = "default_timestamp")]
50 pub timestamp: DateTime<Utc>,
51
52 #[serde(default)]
53 pub passthrough: bool,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub piped_content: Option<String>,
56
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub model: Option<String>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub token_usage: Option<TokenUsage>,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub cost: Option<f64>,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub duration_ms: Option<u64>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub derived: Option<DerivedContent>,
68
69 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub edit_of: Option<usize>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
75pub struct SessionView {
76 pub model: String,
77 #[serde(default)]
78 pub context_files: Vec<String>,
79 #[serde(default)]
80 pub message_indices: Vec<usize>,
81 #[serde(default)]
82 pub history_start_pair: usize,
83 #[serde(default)]
84 pub excluded_pairs: Vec<usize>,
85 #[serde(default = "default_timestamp")]
86 pub created_at: DateTime<Utc>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct SessionPointer {
93 #[serde(rename = "type")]
94 pub pointer_type: String, pub path: String,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
101pub struct TokenUsage {
102 pub prompt_tokens: u32,
103 pub completion_tokens: u32,
104 pub total_tokens: u32,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub cached_tokens: Option<u32>,
107 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub reasoning_tokens: Option<u32>,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub cost: Option<f64>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
114pub struct DerivedContent {
115 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub unified_diff: Option<String>,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub display_content: Option<Vec<DisplayItem>>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
122#[serde(tag = "type", content = "content", rename_all = "lowercase")]
123pub enum DisplayItem {
124 #[serde(alias = "text")]
125 Markdown(String),
126 Diff(String),
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct ContextFile {
131 pub path: String,
132 pub content: String,
133 pub mtime: f64,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct MessageWithId {
138 #[serde(flatten)]
139 pub record: HistoryRecord,
140 pub id: usize,
141}
142
143#[derive(Debug, Clone)]
144pub struct MessageWithContext {
145 pub record: HistoryRecord,
146 pub global_index: usize,
147 pub pair_index: usize,
148 pub is_excluded: bool,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct MessagePairJson {
153 pub pair_index: usize,
154 pub user: MessageWithId,
155 pub assistant: MessageWithId,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
159pub enum AddonSource {
160 Project,
161 User,
162 Bundled,
163}
164
165impl std::fmt::Display for AddonSource {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 match self {
168 AddonSource::Project => write!(f, "project"),
169 AddonSource::User => write!(f, "user"),
170 AddonSource::Bundled => write!(f, "bundled"),
171 }
172 }
173}
174
175#[derive(Debug, Clone)]
176pub struct AddonInfo {
177 pub name: String,
178 pub path: std::path::PathBuf,
179 pub help_text: String,
180 pub source: AddonSource,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct TokenInfo {
185 pub description: String,
186 pub tokens: u32,
187 pub cost: Option<f64>,
188}
189
190pub struct ActiveWindowSummary {
191 pub active_pairs: usize,
192 pub active_start_id: usize,
193 pub active_end_id: usize,
194 pub excluded_in_window: usize,
195 pub pairs_sent: usize,
196 pub has_dangling: bool,
197}
198
199#[derive(Debug, Clone, PartialEq)]
202pub struct AIPatch {
203 pub llm_file_path: String,
204 pub search_content: String,
205 pub replace_content: String,
206 pub indent: String,
207 pub raw_block: String,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
211pub struct ProcessedDiffBlock {
212 pub llm_file_path: String,
213 pub unified_diff: String,
214}
215
216#[derive(Debug, Clone, PartialEq)]
217pub struct FileHeader {
218 pub llm_file_path: String,
219}
220
221#[derive(Debug, Clone, PartialEq)]
222pub struct WarningMessage {
223 pub text: String,
224}
225
226#[derive(Debug, Clone, PartialEq)]
227pub struct UnparsedBlock {
228 pub text: String,
229}
230
231#[derive(Debug, Clone, PartialEq)]
232pub enum StreamYieldItem {
233 Text(String),
234 IncompleteBlock(String),
235 FileHeader(FileHeader),
236 DiffBlock(ProcessedDiffBlock),
237 Patch(AIPatch),
238 Warning(WarningMessage),
239 Unparsed(UnparsedBlock),
240}
241
242impl StreamYieldItem {
243 pub fn is_warning(&self) -> bool {
244 matches!(self, StreamYieldItem::Warning(_))
245 }
246
247 pub fn to_display_item(self, is_final: bool) -> Option<DisplayItem> {
248 match self {
249 StreamYieldItem::Text(t) => Some(DisplayItem::Markdown(t)),
250 StreamYieldItem::FileHeader(h) => Some(DisplayItem::Markdown(format!(
251 "File: `{}`\n",
252 h.llm_file_path
253 ))),
254 StreamYieldItem::DiffBlock(db) => Some(DisplayItem::Diff(db.unified_diff)),
255 StreamYieldItem::Warning(w) => {
256 Some(DisplayItem::Markdown(format!("[!WARNING]\n{}\n\n", w.text)))
257 }
258 StreamYieldItem::Unparsed(u) => Some(DisplayItem::Markdown(format!(
259 "\n`````text\n{}\n`````\n",
260 u.text
261 ))),
262 StreamYieldItem::IncompleteBlock(t) => {
263 if is_final {
264 Some(DisplayItem::Markdown(t))
265 } else {
266 None
267 }
268 }
269 StreamYieldItem::Patch(_) => None,
270 }
271 }
272}
273
274#[derive(Debug, Serialize, Deserialize)]
275pub struct StatusResponse {
276 pub session_name: String,
277 pub model: String,
278 pub context_files: Vec<String>,
279 #[serde(skip_serializing_if = "Option::is_none")]
280 pub total_tokens: Option<u32>,
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub total_cost: Option<f64>,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
286pub struct InteractionResult {
287 pub content: String,
288 pub display_items: Option<Vec<DisplayItem>>,
289 pub token_usage: Option<TokenUsage>,
290 pub cost: Option<f64>,
291 pub duration_ms: u64,
292 pub unified_diff: Option<String>,
293}
294
295#[derive(Debug, Clone)]
296pub struct InteractionConfig {
297 pub mode: Mode,
298 pub no_history: bool,
299 pub passthrough: bool,
300 pub model_override: Option<String>,
301}
302
303#[derive(Debug, Clone)]
304pub struct ContextState<'a> {
305 pub static_files: Vec<(&'a str, &'a str)>,
306 pub floating_files: Vec<(&'a str, &'a str)>,
307 pub splice_idx: usize,
308}
309
310pub fn default_timestamp() -> DateTime<Utc> {
313 Utc::now()
314}