aios_core/
context_builder.rs1use std::collections::HashMap;
7
8use aios_spec::{
9 AppTransition, ContextSummary, ExtensionCategory, SanitizedEvent, SanitizedEventType,
10 SemanticHint, SourceTier, StructuredContext, SystemStatusSnapshot,
11};
12use uuid::Uuid;
13
14#[derive(Debug)]
18pub struct WindowAggregator {
19 buffer: Vec<SanitizedEvent>,
21 window_secs: u64,
23 window_start_ms: i64,
25}
26
27impl WindowAggregator {
28 pub fn new(window_secs: u64, now_ms: i64) -> Self {
33 Self {
34 buffer: Vec::new(),
35 window_secs,
36 window_start_ms: now_ms,
37 }
38 }
39
40 pub fn push(&mut self, event: SanitizedEvent) {
42 self.buffer.push(event);
43 }
44
45 pub fn len(&self) -> usize {
47 self.buffer.len()
48 }
49
50 pub fn is_empty(&self) -> bool {
52 self.buffer.is_empty()
53 }
54
55 pub fn is_expired(&self, now_ms: i64) -> bool {
57 let elapsed_ms = now_ms.saturating_sub(self.window_start_ms);
58 elapsed_ms >= (self.window_secs * 1000) as i64
59 }
60
61 pub fn close(&mut self, now_ms: i64) -> Option<StructuredContext> {
66 if self.buffer.is_empty() {
67 self.window_start_ms = now_ms;
68 return None;
69 }
70
71 let events = std::mem::take(&mut self.buffer);
72 let summary = build_summary(&events);
73 let context = StructuredContext {
74 window_id: new_id(),
75 window_start_ms: self.window_start_ms,
76 window_end_ms: now_ms,
77 duration_secs: ((now_ms - self.window_start_ms).max(0) / 1000) as u32,
78 events,
79 summary,
80 };
81
82 self.window_start_ms = now_ms;
83
84 Some(context)
85 }
86}
87
88fn build_summary(events: &[SanitizedEvent]) -> ContextSummary {
90 let mut foreground_apps: Vec<String> = Vec::new();
91 let mut notified_apps: Vec<String> = Vec::new();
92 let mut all_semantic_hints: Vec<SemanticHint> = Vec::new();
93 let mut file_activity_counts: HashMap<ExtensionCategory, u32> = HashMap::new();
94 let mut latest_system_status: Option<SystemStatusSnapshot> = None;
95 let mut source_tier = SourceTier::PublicApi;
96
97 for event in events {
98 if event.source_tier == SourceTier::Daemon {
99 source_tier = SourceTier::Daemon;
100 }
101
102 match &event.event_type {
103 SanitizedEventType::AppTransition {
104 package_name,
105 transition: AppTransition::Foreground,
106 ..
107 } if !foreground_apps.contains(package_name) => {
108 foreground_apps.push(package_name.clone());
109 },
110 SanitizedEventType::ProcessResource {
111 package_name: Some(pkg),
112 ..
113 } if !foreground_apps.contains(pkg) => {
114 foreground_apps.push(pkg.clone());
115 },
116 SanitizedEventType::Notification {
117 source_package,
118 semantic_hints,
119 ..
120 } => {
121 if !notified_apps.contains(source_package) {
122 notified_apps.push(source_package.clone());
123 }
124 for hint in semantic_hints {
125 if !all_semantic_hints.contains(hint) {
126 all_semantic_hints.push(hint.clone());
127 }
128 }
129 },
130 SanitizedEventType::FileActivity {
131 extension_category, ..
132 } => {
133 *file_activity_counts
134 .entry(extension_category.clone())
135 .or_insert(0) += 1;
136 },
137 SanitizedEventType::SystemStatus {
138 battery_pct,
139 is_charging,
140 network,
141 ringer_mode,
142 location_type,
143 headphone_connected,
144 } => {
145 latest_system_status = Some(SystemStatusSnapshot {
146 battery_pct: *battery_pct,
147 is_charging: *is_charging,
148 network: network.clone(),
149 ringer_mode: ringer_mode.clone(),
150 location_type: location_type.clone(),
151 headphone_connected: *headphone_connected,
152 });
153 },
154 SanitizedEventType::InterAppInteraction {
155 source_package: Some(pkg),
156 ..
157 } if !foreground_apps.contains(pkg) => {
158 foreground_apps.push(pkg.clone());
159 },
160 _ => {},
161 }
162 }
163
164 let file_activity: Vec<(ExtensionCategory, u32)> = file_activity_counts.into_iter().collect();
165
166 ContextSummary {
167 foreground_apps,
168 notified_apps,
169 all_semantic_hints,
170 file_activity,
171 latest_system_status,
172 source_tier,
173 }
174}
175
176fn new_id() -> String {
177 Uuid::new_v4().to_string()
178}