objectiveai_cli/db/
instances.rs1use std::collections::HashMap;
15
16use objectiveai_sdk::cli::command::agents::instances::list::ResponseItem;
17use sqlx::Row as _;
18
19use super::{Error, Pool};
20
21async fn aggregate(
30 pool: &Pool,
31 exact: Option<&str>,
32 prefix: Option<&str>,
33 prefix_deep: Option<&str>,
34) -> Result<Vec<ResponseItem>, Error> {
35 let rows = sqlx::query(
41 "WITH log_agg AS ( \
42 SELECT agent_instance_hierarchy AS aih, \
43 MIN(\"timestamp\") AS spawned, \
44 MAX(\"timestamp\") AS active, \
45 COUNT(*) AS logged \
46 FROM objectiveai.messages \
47 WHERE ( \
48 ($1::text IS NOT NULL AND agent_instance_hierarchy = $1) \
49 OR ($2::text IS NOT NULL \
50 AND agent_instance_hierarchy LIKE $2 \
51 AND agent_instance_hierarchy NOT LIKE $3) \
52 ) \
53 GROUP BY agent_instance_hierarchy \
54 ), \
55 queue_agg AS ( \
56 SELECT eff_aih AS aih, COUNT(*) AS queued \
57 FROM ( \
58 SELECT COALESCE( \
59 mq.agent_instance_hierarchy, \
60 t.agent_instance_hierarchy \
61 ) AS eff_aih \
62 FROM objectiveai.message_queue mq \
63 LEFT JOIN objectiveai.tags t \
64 ON mq.agent_tag IS NOT NULL AND t.name = mq.agent_tag \
65 WHERE mq.active = TRUE \
66 ) x \
67 WHERE ( \
68 ($1::text IS NOT NULL AND x.eff_aih = $1) \
69 OR ($2::text IS NOT NULL \
70 AND x.eff_aih LIKE $2 \
71 AND x.eff_aih NOT LIKE $3) \
72 ) \
73 GROUP BY eff_aih \
74 ) \
75 SELECT COALESCE(l.aih, q.aih) AS aih, \
76 l.spawned AS spawned, \
77 l.active AS active, \
78 COALESCE(l.logged, 0)::bigint AS logged, \
79 COALESCE(q.queued, 0)::bigint AS queued \
80 FROM log_agg l \
81 FULL OUTER JOIN queue_agg q ON q.aih = l.aih \
82 ORDER BY aih ASC",
83 )
84 .bind(exact)
85 .bind(prefix)
86 .bind(prefix_deep)
87 .fetch_all(&**pool)
88 .await?;
89
90 let tag_rows = sqlx::query(
93 "SELECT agent_instance_hierarchy, name FROM objectiveai.tags \
94 WHERE ( \
95 ($1::text IS NOT NULL AND agent_instance_hierarchy = $1) \
96 OR ($2::text IS NOT NULL \
97 AND agent_instance_hierarchy LIKE $2 \
98 AND agent_instance_hierarchy NOT LIKE $3) \
99 ) \
100 ORDER BY updated_at DESC",
101 )
102 .bind(exact)
103 .bind(prefix)
104 .bind(prefix_deep)
105 .fetch_all(&**pool)
106 .await?;
107 let mut tags_by_aih: HashMap<String, Vec<String>> = HashMap::new();
108 for row in tag_rows {
109 let aih: String = row.try_get("agent_instance_hierarchy")?;
110 let name: String = row.try_get("name")?;
111 tags_by_aih.entry(aih).or_default().push(name);
112 }
113
114 let mut out = Vec::with_capacity(rows.len());
115 for row in rows {
116 let aih: String = row.try_get("aih")?;
117 let spawned: Option<i64> = row.try_get("spawned")?;
118 let active: Option<i64> = row.try_get("active")?;
119 let logged: i64 = row.try_get("logged")?;
120 let queued: i64 = row.try_get("queued")?;
121 let tags = tags_by_aih.remove(&aih).unwrap_or_default();
122 out.push(ResponseItem {
123 agent_instance_hierarchy: aih,
124 tags,
125 queued: queued as u64,
126 timestamp_spawned: spawned,
127 timestamp_active: active,
128 logged: logged as u64,
129 });
130 }
131 Ok(out)
132}
133
134pub async fn list_under_parent(
138 pool: &Pool,
139 parent: &str,
140) -> Result<Vec<ResponseItem>, Error> {
141 let prefix = format!("{parent}/%");
142 let prefix_deep = format!("{parent}/%/%");
143 aggregate(pool, None, Some(&prefix), Some(&prefix_deep)).await
144}
145
146pub async fn get_exact(pool: &Pool, aih: &str) -> Result<ResponseItem, Error> {
151 let mut items = aggregate(pool, Some(aih), None, None).await?;
152 if let Some(item) = items.pop() {
153 return Ok(item);
154 }
155 let tags = super::tags::tags_for_hierarchy(pool, aih).await?;
158 Ok(ResponseItem {
159 agent_instance_hierarchy: aih.to_string(),
160 tags,
161 queued: 0,
162 timestamp_spawned: None,
163 timestamp_active: None,
164 logged: 0,
165 })
166}