aico/
models.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4// --- Enums ---
5
6#[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// --- Shared History Models (historystore/models.py) ---
43
44#[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    // Assistant-only optional metadata
58    #[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    // Edit lineage
70    #[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// --- Session Pointer (.ai_session.json) ---
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct SessionPointer {
93    #[serde(rename = "type")]
94    pub pointer_type: String, // "aico_session_pointer_v1"
95    pub path: String,
96}
97
98// --- Supporting Structs ---
99
100#[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// --- Diffing / Streaming Models ---
200
201#[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
310// --- Helpers ---
311
312pub fn default_timestamp() -> DateTime<Utc> {
313    Utc::now()
314}