forked from boos/BoosAPI
chore: sync current workspace changes
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "agent_runs")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: String,
|
||||
pub session_id: String,
|
||||
pub conversation_id: String,
|
||||
pub status: String,
|
||||
pub assistant_message_id: Option<String>,
|
||||
pub error_message: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
pub finished_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
@@ -10,6 +10,12 @@ pub struct Model {
|
||||
pub cwd: Option<String>,
|
||||
pub permission_mode: String,
|
||||
pub runtime_status: String,
|
||||
pub current_run_id: Option<String>,
|
||||
pub host_access_enabled: bool,
|
||||
pub revision: i64,
|
||||
pub context_version: i32,
|
||||
pub last_provider_id: Option<String>,
|
||||
pub last_model_id: Option<String>,
|
||||
pub sdk_context_json: Option<String>,
|
||||
pub sdk_context_backup_json: Option<String>,
|
||||
pub total_tokens: i32,
|
||||
|
||||
@@ -40,5 +40,6 @@ pub mod retrieval_hits;
|
||||
pub mod stored_files;
|
||||
|
||||
pub mod agent_sessions;
|
||||
pub mod agent_runs;
|
||||
|
||||
pub use sea_orm;
|
||||
|
||||
@@ -27,3 +27,4 @@ pub mod skill;
|
||||
pub mod stored_file;
|
||||
pub mod tool_execution;
|
||||
pub mod agent_session;
|
||||
pub mod agent_run;
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
use crate::entity::agent_runs;
|
||||
use crate::error::{BoosApiError, Result};
|
||||
use sea_orm::*;
|
||||
|
||||
const TERMINAL_STATUSES: &[&str] = &["succeeded", "failed", "cancelled", "interrupted"];
|
||||
|
||||
fn now() -> String {
|
||||
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
|
||||
}
|
||||
|
||||
pub async fn create_agent_run(
|
||||
db: &DatabaseConnection,
|
||||
run_id: &str,
|
||||
session_id: &str,
|
||||
conversation_id: &str,
|
||||
) -> Result<()> {
|
||||
let timestamp = now();
|
||||
agent_runs::ActiveModel {
|
||||
id: Set(run_id.to_string()),
|
||||
session_id: Set(session_id.to_string()),
|
||||
conversation_id: Set(conversation_id.to_string()),
|
||||
status: Set("starting".to_string()),
|
||||
assistant_message_id: Set(None),
|
||||
error_message: Set(None),
|
||||
created_at: Set(timestamp.clone()),
|
||||
updated_at: Set(timestamp),
|
||||
finished_at: Set(None),
|
||||
}
|
||||
.insert(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_agent_run_status(
|
||||
db: &DatabaseConnection,
|
||||
run_id: &str,
|
||||
status: &str,
|
||||
assistant_message_id: Option<&str>,
|
||||
error_message: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let timestamp = now();
|
||||
let mut update = agent_runs::Entity::update_many()
|
||||
.col_expr(agent_runs::Column::Status, Expr::value(status.to_string()))
|
||||
.col_expr(
|
||||
agent_runs::Column::UpdatedAt,
|
||||
Expr::value(timestamp.clone()),
|
||||
)
|
||||
.filter(agent_runs::Column::Id.eq(run_id))
|
||||
.filter(agent_runs::Column::Status.is_in([
|
||||
"starting",
|
||||
"running",
|
||||
"waiting_approval",
|
||||
"waiting_input",
|
||||
"cancelling",
|
||||
]));
|
||||
if let Some(message_id) = assistant_message_id {
|
||||
update = update.col_expr(
|
||||
agent_runs::Column::AssistantMessageId,
|
||||
Expr::value(Some(message_id.to_string())),
|
||||
);
|
||||
}
|
||||
if let Some(error) = error_message {
|
||||
update = update.col_expr(
|
||||
agent_runs::Column::ErrorMessage,
|
||||
Expr::value(Some(error.to_string())),
|
||||
);
|
||||
}
|
||||
if TERMINAL_STATUSES.contains(&status) {
|
||||
update = update.col_expr(agent_runs::Column::FinishedAt, Expr::value(Some(timestamp)));
|
||||
}
|
||||
let result = update.exec(db).await?;
|
||||
if result.rows_affected != 1 {
|
||||
return Err(BoosApiError::Validation(format!(
|
||||
"Agent run {run_id} is no longer active"
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn interrupt_active_runs(db: &DatabaseConnection) -> Result<u64> {
|
||||
let timestamp = now();
|
||||
let result = agent_runs::Entity::update_many()
|
||||
.col_expr(
|
||||
agent_runs::Column::Status,
|
||||
sea_orm::sea_query::Expr::value("interrupted"),
|
||||
)
|
||||
.col_expr(
|
||||
agent_runs::Column::UpdatedAt,
|
||||
sea_orm::sea_query::Expr::value(timestamp.clone()),
|
||||
)
|
||||
.col_expr(
|
||||
agent_runs::Column::FinishedAt,
|
||||
sea_orm::sea_query::Expr::value(Some(timestamp)),
|
||||
)
|
||||
.filter(agent_runs::Column::Status.is_in([
|
||||
"starting",
|
||||
"running",
|
||||
"waiting_approval",
|
||||
"waiting_input",
|
||||
"cancelling",
|
||||
]))
|
||||
.exec(db)
|
||||
.await?;
|
||||
Ok(result.rows_affected)
|
||||
}
|
||||
@@ -12,6 +12,12 @@ fn model_to_agent_session(model: agent_sessions::Model) -> AgentSession {
|
||||
cwd: model.cwd,
|
||||
permission_mode: model.permission_mode,
|
||||
runtime_status: model.runtime_status,
|
||||
current_run_id: model.current_run_id,
|
||||
host_access_enabled: model.host_access_enabled,
|
||||
revision: model.revision,
|
||||
context_version: model.context_version,
|
||||
last_provider_id: model.last_provider_id,
|
||||
last_model_id: model.last_model_id,
|
||||
sdk_context_json: model.sdk_context_json,
|
||||
sdk_context_backup_json: model.sdk_context_backup_json,
|
||||
total_tokens: model.total_tokens,
|
||||
@@ -55,6 +61,12 @@ pub async fn upsert_agent_session(
|
||||
cwd: Set(cwd.map(|s| s.to_string())),
|
||||
permission_mode: Set(permission_mode.unwrap_or("default").to_string()),
|
||||
runtime_status: Set("idle".to_string()),
|
||||
current_run_id: Set(None),
|
||||
host_access_enabled: Set(false),
|
||||
revision: Set(0),
|
||||
context_version: Set(1),
|
||||
last_provider_id: Set(None),
|
||||
last_model_id: Set(None),
|
||||
sdk_context_json: Set(None),
|
||||
sdk_context_backup_json: Set(None),
|
||||
total_tokens: Set(0),
|
||||
@@ -99,6 +111,92 @@ pub async fn update_agent_session_status(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Atomically binds an idle session to a run. The in-process lease is acquired
|
||||
/// before any await; this database guard prevents stale cleanup from taking
|
||||
/// ownership of a newer run after a restart.
|
||||
pub async fn begin_agent_run(
|
||||
db: &DatabaseConnection,
|
||||
id: &str,
|
||||
run_id: &str,
|
||||
provider_id: &str,
|
||||
model_id: &str,
|
||||
) -> Result<()> {
|
||||
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let result = agent_sessions::Entity::update_many()
|
||||
.col_expr(agent_sessions::Column::RuntimeStatus, Expr::value("active"))
|
||||
.col_expr(
|
||||
agent_sessions::Column::CurrentRunId,
|
||||
Expr::value(Some(run_id.to_string())),
|
||||
)
|
||||
.col_expr(
|
||||
agent_sessions::Column::LastProviderId,
|
||||
Expr::value(Some(provider_id.to_string())),
|
||||
)
|
||||
.col_expr(
|
||||
agent_sessions::Column::LastModelId,
|
||||
Expr::value(Some(model_id.to_string())),
|
||||
)
|
||||
.col_expr(
|
||||
agent_sessions::Column::Revision,
|
||||
Expr::col(agent_sessions::Column::Revision).add(1),
|
||||
)
|
||||
.col_expr(agent_sessions::Column::UpdatedAt, Expr::value(now))
|
||||
.filter(agent_sessions::Column::Id.eq(id))
|
||||
.filter(agent_sessions::Column::CurrentRunId.is_null())
|
||||
.exec(db)
|
||||
.await?;
|
||||
if result.rows_affected != 1 {
|
||||
return Err(BoosApiError::Validation(
|
||||
"Agent session already has an active run".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_agent_session_status_for_run(
|
||||
db: &DatabaseConnection,
|
||||
id: &str,
|
||||
run_id: &str,
|
||||
runtime_status: &str,
|
||||
) -> Result<bool> {
|
||||
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let result = agent_sessions::Entity::update_many()
|
||||
.col_expr(
|
||||
agent_sessions::Column::RuntimeStatus,
|
||||
Expr::value(runtime_status.to_string()),
|
||||
)
|
||||
.col_expr(agent_sessions::Column::UpdatedAt, Expr::value(now))
|
||||
.filter(agent_sessions::Column::Id.eq(id))
|
||||
.filter(agent_sessions::Column::CurrentRunId.eq(run_id))
|
||||
.exec(db)
|
||||
.await?;
|
||||
Ok(result.rows_affected == 1)
|
||||
}
|
||||
|
||||
pub async fn update_agent_session_host_access(
|
||||
db: &DatabaseConnection,
|
||||
conversation_id: &str,
|
||||
enabled: bool,
|
||||
) -> Result<AgentSession> {
|
||||
let model = agent_sessions::Entity::find()
|
||||
.filter(agent_sessions::Column::ConversationId.eq(conversation_id))
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| BoosApiError::NotFound(format!("AgentSession {conversation_id}")))?;
|
||||
if model.current_run_id.is_some() {
|
||||
return Err(BoosApiError::Validation(
|
||||
"Cannot change host access while an agent is running".to_string(),
|
||||
));
|
||||
}
|
||||
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let next_revision = model.revision + 1;
|
||||
let mut active: agent_sessions::ActiveModel = model.into();
|
||||
active.host_access_enabled = Set(enabled);
|
||||
active.revision = Set(next_revision);
|
||||
active.updated_at = Set(now);
|
||||
Ok(model_to_agent_session(active.update(db).await?))
|
||||
}
|
||||
|
||||
/// Update the working directory of an agent session.
|
||||
pub async fn update_agent_session_cwd(
|
||||
db: &DatabaseConnection,
|
||||
@@ -137,22 +235,26 @@ pub async fn update_agent_session_permission_mode(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset all running/waiting_approval sessions to idle (for app startup recovery).
|
||||
/// Mark sessions interrupted on startup and release any stale run binding.
|
||||
pub async fn reset_running_sessions(db: &DatabaseConnection) -> Result<u64> {
|
||||
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let result = agent_sessions::Entity::update_many()
|
||||
.col_expr(
|
||||
agent_sessions::Column::RuntimeStatus,
|
||||
Expr::value("idle"),
|
||||
Expr::value("interrupted"),
|
||||
)
|
||||
.col_expr(agent_sessions::Column::CurrentRunId, Expr::value(None::<String>))
|
||||
.col_expr(
|
||||
agent_sessions::Column::UpdatedAt,
|
||||
Expr::value(now),
|
||||
)
|
||||
.filter(
|
||||
Condition::any()
|
||||
.add(agent_sessions::Column::RuntimeStatus.eq("active"))
|
||||
.add(agent_sessions::Column::RuntimeStatus.eq("running"))
|
||||
.add(agent_sessions::Column::RuntimeStatus.eq("waiting_approval")),
|
||||
.add(agent_sessions::Column::RuntimeStatus.eq("waiting_approval"))
|
||||
.add(agent_sessions::Column::RuntimeStatus.eq("waiting_input"))
|
||||
.add(agent_sessions::Column::RuntimeStatus.eq("cancelling")),
|
||||
)
|
||||
.exec(db)
|
||||
.await?;
|
||||
@@ -164,12 +266,14 @@ pub async fn reset_running_sessions(db: &DatabaseConnection) -> Result<u64> {
|
||||
pub async fn update_agent_session_after_query(
|
||||
db: &DatabaseConnection,
|
||||
id: &str,
|
||||
run_id: &str,
|
||||
runtime_status: &str,
|
||||
sdk_context_json: Option<&str>,
|
||||
tokens_delta: i32,
|
||||
cost_delta: f64,
|
||||
) -> Result<()> {
|
||||
let model = agent_sessions::Entity::find_by_id(id)
|
||||
.filter(agent_sessions::Column::CurrentRunId.eq(run_id))
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| BoosApiError::NotFound(format!("AgentSession {}", id)))?;
|
||||
@@ -177,11 +281,13 @@ pub async fn update_agent_session_after_query(
|
||||
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let mut am: agent_sessions::ActiveModel = model.clone().into();
|
||||
am.runtime_status = Set(runtime_status.to_string());
|
||||
am.current_run_id = Set(None);
|
||||
if let Some(ctx) = sdk_context_json {
|
||||
am.sdk_context_json = Set(Some(ctx.to_string()));
|
||||
}
|
||||
am.total_tokens = Set(model.total_tokens + tokens_delta);
|
||||
am.total_cost_usd = Set(model.total_cost_usd + cost_delta);
|
||||
am.revision = Set(model.revision + 1);
|
||||
am.updated_at = Set(now);
|
||||
am.update(db).await?;
|
||||
Ok(())
|
||||
|
||||
@@ -1450,6 +1450,12 @@ pub struct AgentSession {
|
||||
pub cwd: Option<String>,
|
||||
pub permission_mode: String,
|
||||
pub runtime_status: String,
|
||||
pub current_run_id: Option<String>,
|
||||
pub host_access_enabled: bool,
|
||||
pub revision: i64,
|
||||
pub context_version: i32,
|
||||
pub last_provider_id: Option<String>,
|
||||
pub last_model_id: Option<String>,
|
||||
pub sdk_context_json: Option<String>,
|
||||
pub sdk_context_backup_json: Option<String>,
|
||||
pub total_tokens: i32,
|
||||
|
||||
Reference in New Issue
Block a user