use anyhow::Result;
use sqlx::PgPool;
use tracing::{info, instrument, warn};
use crate::utils::toon_config::ToonAgentConfig;
#[instrument(skip(pool, agents), fields(count = agents.len()))]
pub async fn record_agent_versions(
pool: &PgPool,
agents: &[ToonAgentConfig],
change_source: &str,
) -> Result<()> {
let mut recorded = 0usize;
for agent in agents {
let config_json = serde_json::to_value(agent)
.unwrap_or_else(|_| serde_json::json!({"name": agent.name}));
let sql = if change_source == "rollback" {
"INSERT INTO agent_config_versions \
(agent_id, version, config_json, is_active, change_source) \
VALUES ($1, $2, $3, true, $4) \
ON CONFLICT (agent_id, version) DO UPDATE \
SET change_source = EXCLUDED.change_source, is_active = true"
} else {
"INSERT INTO agent_config_versions \
(agent_id, version, config_json, is_active, change_source) \
VALUES ($1, $2, $3, true, $4) \
ON CONFLICT (agent_id, version) DO NOTHING"
};
match sqlx::query(sql)
.bind(&agent.name)
.bind(&agent.version)
.bind(&config_json)
.bind(change_source)
.execute(pool)
.await
{
Ok(r) if r.rows_affected() > 0 => {
recorded += 1;
}
Ok(_) => {
}
Err(e) => {
warn!(
agent = %agent.name,
version = %agent.version,
error = %e,
"Failed to record agent version"
);
}
}
}
if recorded > 0 {
info!(
recorded,
source = change_source,
"Agent config versions recorded"
);
}
Ok(())
}
pub async fn get_agent_version_history(
pool: &PgPool,
agent_id: &str,
limit: i64,
) -> Result<Vec<AgentVersionRecord>> {
let rows = sqlx::query_as::<_, AgentVersionRecord>(
r#"SELECT id, agent_id, version, config_json, is_active, change_source, created_at
FROM agent_config_versions
WHERE agent_id = $1
ORDER BY created_at DESC
LIMIT $2"#,
)
.bind(agent_id)
.bind(limit)
.fetch_all(pool)
.await?;
Ok(rows)
}
#[derive(Debug, Clone, serde::Serialize, sqlx::FromRow)]
pub struct AgentVersionRecord {
pub id: String,
pub agent_id: String,
pub version: String,
pub config_json: serde_json::Value,
pub is_active: bool,
pub change_source: String,
pub created_at: chrono::DateTime<chrono::Utc>,
}