use crate::store::{CostEstimate, MemoryEntry, Task, TaskStore, TokenUsage};
use anyhow::anyhow;
use std::sync::Arc;
pub type StoreResult<T> = anyhow::Result<Option<T>>;
fn token_i64_to_u64_non_negative(tokens: i64) -> u64 {
u64::try_from(tokens.max(0)).unwrap_or(0)
}
pub async fn resolve_store_id(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
) -> Option<i64> {
let s = store.as_ref()?;
match s.resolve_task_id(repo, task_id).await {
Ok(Some(id)) => Some(id),
Ok(None) => None,
Err(e) => {
tracing::warn!(task_id, error = %e, "failed to resolve task id for store operations");
None
}
}
}
pub async fn opt_store_get_task_by_id(
store: &Option<Arc<TaskStore>>,
store_id: i64,
) -> Option<Task> {
let s = store.as_ref()?;
match s.get(store_id).await {
Ok(task) => Some(task),
Err(e) => {
tracing::warn!(store_id, error = %e, "failed to fetch task from store");
None
}
}
}
pub async fn opt_store_get_task(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
) -> Option<Task> {
let s = store.as_ref()?;
let store_id = match s.resolve_task_id(repo, task_id).await {
Ok(Some(id)) => id,
Ok(None) => return None,
Err(e) => {
tracing::warn!(task_id, error = %e, "failed to resolve task id in store");
return None;
}
};
opt_store_get_task_by_id(store, store_id).await
}
pub async fn store_set_by_id(
store: &Option<&Arc<TaskStore>>,
store_id: i64,
store_fields: &[(&str, serde_json::Value)],
) -> anyhow::Result<()> {
if let Some(store) = store {
store.set_fields(store_id, store_fields).await?;
}
Ok(())
}
pub async fn store_set(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
store_fields: &[(&str, serde_json::Value)],
) {
if let Some(ref store) = store {
match store.resolve_task_id(repo, task_id).await {
Ok(Some(store_id)) => {
if let Err(e) = store.set_fields(store_id, store_fields).await {
tracing::warn!(task_id, error = %e, "store set_fields failed");
}
}
Ok(None) => {} Err(e) => {
tracing::warn!(task_id, error = %e, "resolve_task_id failed in store write helper");
}
}
}
}
pub async fn store_set_result_by_id(
store: &Option<Arc<TaskStore>>,
store_id: i64,
store_fields: &[(&str, serde_json::Value)],
) -> anyhow::Result<()> {
if let Some(ref store) = store {
store.set_fields(store_id, store_fields).await?;
}
Ok(())
}
pub async fn store_set_result(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
store_fields: &[(&str, serde_json::Value)],
) -> anyhow::Result<()> {
if let Some(ref store) = store {
let store_id = store
.resolve_task_id(repo, task_id)
.await?
.ok_or_else(|| anyhow::anyhow!("task {} not found in store", task_id))?;
store.set_fields(store_id, store_fields).await?;
}
Ok(())
}
#[allow(dead_code)]
pub async fn store_touch_updated_at_by_id(store: &Option<Arc<TaskStore>>, store_id: i64) {
if let Some(ref s) = store {
if let Err(e) = s.touch_updated_at(store_id).await {
tracing::warn!(store_id, error = %e, "store touch_updated_at failed");
}
}
}
pub async fn store_touch_updated_at(store: &Option<Arc<TaskStore>>, repo: &str, task_id: &str) {
if let Some(ref s) = store {
match s.resolve_task_id(repo, task_id).await {
Ok(Some(store_id)) => {
if let Err(e) = s.touch_updated_at(store_id).await {
tracing::warn!(task_id, error = %e, "store touch_updated_at failed");
} else {
tracing::debug!(task_id, store_id, "store touch_updated_at succeeded");
}
}
Ok(None) => {} Err(e) => {
tracing::warn!(task_id, error = %e, "resolve_task_id failed in store write helper");
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub async fn store_log_activity(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
event_type: &str,
from_status: Option<&str>,
to_status: Option<&str>,
agent: Option<&str>,
model: Option<&str>,
details: Option<&serde_json::Value>,
) {
if let Some(ref s) = store {
match s.resolve_task_id(repo, task_id).await {
Ok(Some(store_id)) => {
if let Err(e) = s
.append_activity(
store_id,
event_type,
from_status,
to_status,
agent,
model,
details,
)
.await
{
tracing::warn!(task_id, event_type, error = %e, "store append_activity failed");
}
}
Ok(None) => {} Err(e) => {
tracing::warn!(task_id, error = %e, "resolve_task_id failed in store write helper");
}
}
}
}
pub async fn review_session_expected(store: &Arc<TaskStore>, repo: &str, task_id: &str) -> bool {
match store.resolve_task_id(repo, task_id).await {
Ok(Some(store_id)) => store
.get(store_id)
.await
.map(|task| task.review_session_expected)
.unwrap_or(false),
Ok(None) | Err(_) => false,
}
}
pub async fn set_review_session_expected(
store: &Arc<TaskStore>,
repo: &str,
task_id: &str,
expected: bool,
) {
store_set(
&Some(Arc::clone(store)),
repo,
task_id,
&[("review_session_expected", serde_json::json!(expected))],
)
.await;
}
pub async fn store_increment_by_id(
store: &Option<Arc<TaskStore>>,
store_id: i64,
field: &str,
) -> anyhow::Result<u64> {
let s = store
.as_ref()
.ok_or_else(|| anyhow!("task store unavailable"))?;
match s.increment(store_id, field).await {
Ok(new_val) => Ok(new_val as u64),
Err(e) => Err(anyhow!("store.increment failed: {}", e)),
}
}
pub async fn store_increment(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
field: &str,
) -> anyhow::Result<u64> {
let s = store
.as_ref()
.ok_or_else(|| anyhow!("task store unavailable"))?;
match s.resolve_task_id(repo, task_id).await {
Ok(Some(store_id)) => store_increment_by_id(store, store_id, field).await,
Ok(None) => Err(anyhow!("task not present in store: {}/{}", repo, task_id)),
Err(e) => {
tracing::warn!(task_id, error = %e, "resolve_task_id failed in store write helper");
Err(anyhow!("resolve_task_id failed: {}", e))
}
}
}
pub async fn get_task_field(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
field: &str,
) -> Option<String> {
let task = opt_store_get_task(store, repo, task_id).await?;
match field {
"no_code_last_agent" => Some(task.no_code_last_agent),
_ => {
tracing::warn!(task_id, field, "get_task_field: unknown field");
None
}
}
}
pub async fn get_task_field_direct(
store: &Arc<TaskStore>,
repo: &str,
task_id: &str,
field: &str,
) -> Option<String> {
let store_opt: &Option<Arc<TaskStore>> = &Some(store.clone());
get_task_field(store_opt, repo, task_id, field).await
}
pub async fn store_reset_counters(store: &Option<Arc<TaskStore>>, repo: &str, task_id: &str) {
if let Some(ref store) = store {
match store.resolve_task_id(repo, task_id).await {
Ok(Some(store_id)) => {
if let Err(e) = store.reset_counters(store_id).await {
tracing::warn!(task_id, err = %e, "store reset_counters failed");
}
}
Ok(None) => {} Err(e) => {
tracing::warn!(task_id, error = %e, "resolve_task_id failed in store write helper");
}
}
}
}
pub async fn store_reset_failure_counters(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
) {
if let Some(ref store) = store {
match store.resolve_task_id(repo, task_id).await {
Ok(Some(store_id)) => {
if let Err(e) = store.reset_failure_counters(store_id).await {
tracing::warn!(task_id, err = %e, "failed to reset failure counters after review dispatch");
}
}
Ok(None) => {} Err(e) => {
tracing::warn!(task_id, error = %e, "resolve_task_id failed in store write helper");
}
}
}
}
pub async fn get_token_usage_result(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
) -> StoreResult<TokenUsage> {
let s = store
.as_ref()
.ok_or_else(|| anyhow!("task store unavailable"))?;
let store_id = s
.resolve_task_id(repo, task_id)
.await?
.ok_or_else(|| anyhow!("task {}/{} not found in store", repo, task_id))?;
let task = s.get(store_id).await?;
Ok(Some(TokenUsage {
input_tokens: token_i64_to_u64_non_negative(task.input_tokens),
output_tokens: token_i64_to_u64_non_negative(task.output_tokens),
}))
}
pub async fn get_token_usage(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
) -> TokenUsage {
match get_token_usage_result(store, repo, task_id).await {
Ok(Some(usage)) => usage,
Ok(None) => TokenUsage::default(),
Err(e) => {
tracing::warn!(task_id, error = %e, "get_token_usage failed — returning zero usage");
TokenUsage::default()
}
}
}
pub async fn get_cost_estimate_result(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
) -> StoreResult<CostEstimate> {
let s = store
.as_ref()
.ok_or_else(|| anyhow!("task store unavailable"))?;
let store_id = s
.resolve_task_id(repo, task_id)
.await?
.ok_or_else(|| anyhow!("task {}/{} not found in store", repo, task_id))?;
let task = s.get(store_id).await?;
Ok(Some(CostEstimate {
input_cost_usd: task.input_cost_usd,
output_cost_usd: task.output_cost_usd,
total_cost_usd: task.total_cost_usd,
}))
}
pub async fn get_cost_estimate(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
) -> CostEstimate {
match get_cost_estimate_result(store, repo, task_id).await {
Ok(Some(cost)) => cost,
Ok(None) => CostEstimate::default(),
Err(e) => {
tracing::warn!(task_id, error = %e, "get_cost_estimate failed — returning zero cost");
CostEstimate::default()
}
}
}
pub async fn get_recent_memory_result(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
max: usize,
) -> StoreResult<Vec<MemoryEntry>> {
let s = store
.as_ref()
.ok_or_else(|| anyhow!("task store unavailable"))?;
let store_id = s
.resolve_task_id(repo, task_id)
.await?
.ok_or_else(|| anyhow!("task {}/{} not found in store", repo, task_id))?;
let entries = s.recent_memory(store_id, max).await?;
Ok(Some(entries))
}
pub async fn get_recent_memory(
store: &Option<Arc<TaskStore>>,
repo: &str,
task_id: &str,
max: usize,
) -> Vec<MemoryEntry> {
match get_recent_memory_result(store, repo, task_id, max).await {
Ok(Some(entries)) => entries,
Ok(None) => Vec::new(),
Err(e) => {
tracing::warn!(task_id, error = %e, "get_recent_memory failed — returning empty memory");
Vec::new()
}
}
}