Skip to main content

agentic_vision/
types.rs

1//! Core data types for visual observations and memory.
2
3use serde::{Deserialize, Serialize};
4
5fn default_quality_score() -> f32 {
6    0.0
7}
8
9/// A captured visual observation stored in visual memory.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct VisualObservation {
12    pub id: u64,
13    pub timestamp: u64,
14    pub session_id: u32,
15    pub source: CaptureSource,
16    pub embedding: Vec<f32>,
17    pub thumbnail: Vec<u8>,
18    pub metadata: ObservationMeta,
19    pub memory_link: Option<u64>,
20}
21
22/// How the image was captured.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(tag = "type", rename_all = "snake_case")]
25pub enum CaptureSource {
26    File { path: String },
27    Base64 { mime: String },
28    Screenshot { region: Option<Rect> },
29    Clipboard,
30}
31
32/// Metadata about a visual observation.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ObservationMeta {
35    pub width: u32,
36    pub height: u32,
37    pub original_width: u32,
38    pub original_height: u32,
39    pub labels: Vec<String>,
40    pub description: Option<String>,
41    /// Signal quality score in [0.0, 1.0] (resolution, metadata richness, model quality).
42    #[serde(default = "default_quality_score")]
43    pub quality_score: f32,
44}
45
46/// Pixel-level diff between two captures.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct VisualDiff {
49    pub before_id: u64,
50    pub after_id: u64,
51    pub similarity: f32,
52    pub changed_regions: Vec<Rect>,
53    pub pixel_diff_ratio: f32,
54}
55
56/// A rectangle region.
57#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
58pub struct Rect {
59    pub x: u32,
60    pub y: u32,
61    pub w: u32,
62    pub h: u32,
63}
64
65/// A similarity match result.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SimilarityMatch {
68    pub id: u64,
69    pub similarity: f32,
70}
71
72/// In-memory container for all visual observations.
73#[derive(Debug, Clone)]
74pub struct VisualMemoryStore {
75    pub observations: Vec<VisualObservation>,
76    pub embedding_dim: u32,
77    pub next_id: u64,
78    pub session_count: u32,
79    pub created_at: u64,
80    pub updated_at: u64,
81}
82
83impl VisualMemoryStore {
84    /// Create a new empty store.
85    pub fn new(embedding_dim: u32) -> Self {
86        let now = std::time::SystemTime::now()
87            .duration_since(std::time::UNIX_EPOCH)
88            .unwrap_or_default()
89            .as_secs();
90
91        Self {
92            observations: Vec::new(),
93            embedding_dim,
94            next_id: 1,
95            session_count: 0,
96            created_at: now,
97            updated_at: now,
98        }
99    }
100
101    /// Get an observation by ID.
102    pub fn get(&self, id: u64) -> Option<&VisualObservation> {
103        self.observations.iter().find(|o| o.id == id)
104    }
105
106    /// Get a mutable observation by ID.
107    pub fn get_mut(&mut self, id: u64) -> Option<&mut VisualObservation> {
108        self.observations.iter_mut().find(|o| o.id == id)
109    }
110
111    /// Add an observation and return its assigned ID.
112    pub fn add(&mut self, mut obs: VisualObservation) -> u64 {
113        let id = self.next_id;
114        obs.id = id;
115        self.next_id += 1;
116        self.updated_at = std::time::SystemTime::now()
117            .duration_since(std::time::UNIX_EPOCH)
118            .unwrap_or_default()
119            .as_secs();
120        self.observations.push(obs);
121        id
122    }
123
124    /// Return the number of observations.
125    pub fn count(&self) -> usize {
126        self.observations.len()
127    }
128
129    /// Get observations filtered by session ID.
130    pub fn by_session(&self, session_id: u32) -> Vec<&VisualObservation> {
131        self.observations
132            .iter()
133            .filter(|o| o.session_id == session_id)
134            .collect()
135    }
136
137    /// Get observations in a timestamp range.
138    pub fn in_time_range(&self, start: u64, end: u64) -> Vec<&VisualObservation> {
139        self.observations
140            .iter()
141            .filter(|o| o.timestamp >= start && o.timestamp <= end)
142            .collect()
143    }
144
145    /// Get the most recent observations.
146    pub fn recent(&self, limit: usize) -> Vec<&VisualObservation> {
147        let mut sorted: Vec<_> = self.observations.iter().collect();
148        sorted.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
149        sorted.truncate(limit);
150        sorted
151    }
152}
153
154/// Errors that can occur in the vision library.
155#[derive(thiserror::Error, Debug)]
156pub enum VisionError {
157    #[error("Image error: {0}")]
158    Image(#[from] image::ImageError),
159
160    #[error("IO error: {0}")]
161    Io(#[from] std::io::Error),
162
163    #[error("Embedding error: {0}")]
164    Embedding(String),
165
166    #[error("Storage error: {0}")]
167    Storage(String),
168
169    #[error("Capture not found: {0}")]
170    CaptureNotFound(u64),
171
172    #[error("Invalid input: {0}")]
173    InvalidInput(String),
174
175    #[error("Capture error: {0}")]
176    Capture(String),
177
178    #[error("Model not available: {0}")]
179    ModelNotAvailable(String),
180}
181
182/// Convenience result type.
183pub type VisionResult<T> = Result<T, VisionError>;