use crate::cmd::agent_json_types::{AgentJson, QuotaJson};
use crate::types::{Task, TaskEvent, TaskProfileDeclaration};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct TaskListParams {
pub filter: Option<String>,
}
#[derive(Debug, Serialize, Clone)]
pub struct TaskResponse {
pub id: String,
pub agent: String,
pub custom_agent_name: Option<String>,
pub prompt: String,
pub resolved_prompt: Option<String>,
pub status: String,
pub outcome: String,
pub parent_task_id: Option<String>,
pub workgroup_id: Option<String>,
pub caller_kind: Option<String>,
pub caller_session_id: Option<String>,
pub agent_session_id: Option<String>,
pub repo_path: Option<String>,
pub project_id: Option<String>,
pub worktree_path: Option<String>,
pub effective_dir: Option<String>,
pub worktree_branch: Option<String>,
pub final_head_sha: Option<String>,
pub final_branch: Option<String>,
pub log_path: Option<String>,
pub output_path: Option<String>,
pub tokens: Option<i64>,
pub prompt_tokens: Option<i64>,
pub duration_ms: Option<i64>,
pub requested_model: Option<String>,
pub observed_model: Option<String>,
pub attribution_source: Option<String>,
pub cost_usd: Option<f64>,
pub exit_code: Option<i32>,
pub created_at: String,
pub completed_at: Option<String>,
pub started_at: Option<String>,
pub verify: Option<String>,
pub verify_status: String,
pub pending_reason: Option<String>,
pub delivery_assessment: Option<String>,
pub read_only: bool,
pub budget: bool,
pub prompt_excerpt: String,
pub sector_id: Option<String>,
pub difficulty: Option<String>,
pub rigor: Option<String>,
pub budget_class: Option<String>,
pub urgency: Option<String>,
pub memory_mb: Option<i64>,
pub has_result: bool,
pub has_diff: bool,
pub awaiting_reason: Option<String>,
pub latest_events: Vec<TaskEventResponse>,
pub latest_milestone: Option<String>,
pub latest_error: Option<String>,
}
#[derive(Debug, Default)]
pub struct TaskEnrichment {
pub started_at: Option<String>,
pub memory_mb: Option<i64>,
pub profile: TaskProfileDeclaration,
pub latest_milestone: Option<String>,
pub latest_error: Option<String>,
pub awaiting_reason: Option<String>,
pub latest_events: Vec<TaskEventResponse>,
pub has_diff: bool,
}
impl TaskResponse {
pub(crate) fn from_task(task: Task, enrichment: TaskEnrichment) -> Self {
let outcome = task.outcome().as_str().to_string();
let sector_id = task_sector_id(&task);
Self {
id: task.id.to_string(), agent: task.agent.as_str().to_string(), custom_agent_name: task.custom_agent_name,
prompt_excerpt: task.prompt.chars().take(160).collect(), prompt: task.prompt, resolved_prompt: task.resolved_prompt,
status: task.status.as_str().to_string(), outcome, parent_task_id: task.parent_task_id, workgroup_id: task.workgroup_id,
caller_kind: task.caller_kind, caller_session_id: task.caller_session_id, agent_session_id: task.agent_session_id,
repo_path: task.repo_path, project_id: task.project_id, worktree_path: task.worktree_path,
effective_dir: task.effective_dir, worktree_branch: task.worktree_branch, final_head_sha: task.final_head_sha,
final_branch: task.final_branch, log_path: task.log_path, output_path: task.output_path,
tokens: task.tokens, prompt_tokens: task.prompt_tokens, duration_ms: task.duration_ms,
requested_model: task.requested_model, observed_model: task.observed_model,
attribution_source: task.attribution_source.map(|value| value.as_str().to_string()),
cost_usd: task.cost_usd, exit_code: task.exit_code, created_at: task.created_at.to_rfc3339(),
completed_at: task.completed_at.map(|value| value.to_rfc3339()), started_at: enrichment.started_at,
verify: task.verify, verify_status: task.verify_status.as_str().to_string(), pending_reason: task.pending_reason,
delivery_assessment: task.delivery_assessment.map(|value| value.as_str().to_string()),
read_only: task.read_only, budget: task.budget, sector_id,
difficulty: enrichment.profile.difficulty.map(|value| value.label().to_string()), rigor: enrichment.profile.rigor.map(|value| value.label().to_string()),
budget_class: enrichment.profile.budget.map(|value| value.label().to_string()), urgency: enrichment.profile.urgency.map(|value| value.label().to_string()),
memory_mb: enrichment.memory_mb, has_result: crate::paths::task_dir(task.id.as_str()).join("result.md").is_file(), has_diff: enrichment.has_diff,
awaiting_reason: (task.status == crate::types::TaskStatus::AwaitingInput)
.then_some(enrichment.awaiting_reason)
.flatten(),
latest_events: enrichment.latest_events, latest_milestone: enrichment.latest_milestone, latest_error: enrichment.latest_error,
}
}
}
fn task_sector_id(task: &Task) -> Option<String> {
task.project_id.clone().or_else(|| {
task.repo_path.as_deref().and_then(|path| {
std::path::Path::new(path).file_name().and_then(|name| name.to_str()).map(str::to_string)
})
})
}
#[derive(Debug, Serialize, Clone)]
pub struct TaskEventResponse {
pub task_id: String,
pub timestamp: String,
pub event_kind: String,
pub detail: String,
pub metadata: Option<serde_json::Value>,
}
impl From<TaskEvent> for TaskEventResponse {
fn from(event: TaskEvent) -> Self {
Self {
task_id: event.task_id.to_string(),
timestamp: event.timestamp.to_rfc3339(),
event_kind: event.event_kind.as_str().to_string(),
detail: event.detail,
metadata: event.metadata,
}
}
}
#[derive(Debug, Serialize)]
pub struct TaskOutputResponse {
pub output: String,
}
#[derive(Debug, Serialize)]
pub struct ResultResponse {
pub result: String,
}
#[derive(Debug, Serialize)]
pub struct AgentUsageResponse {
pub agent: String,
pub success_rate: Option<f64>,
pub task_count: usize,
pub avg_cost: Option<f64>,
}
#[derive(Debug, Serialize)]
pub struct UsageResponse {
pub agents: Vec<AgentUsageResponse>,
}
#[derive(Debug, Serialize)]
pub struct ActionResponse {
pub ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub new_task_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct DiffResponse {
pub diff: String,
}
#[derive(Debug, Deserialize)]
pub struct RetryRequest {
pub feedback: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct MessageRequest {
pub message: String,
}
#[derive(Debug, Serialize, Clone)]
pub struct AgentResponse {
pub name: String,
pub kind: String,
pub installed: bool,
pub disabled: bool,
pub provider: String,
pub metering: String,
pub quota: QuotaJson,
pub default_model: Option<String>,
pub observed_model: Option<String>,
pub busy: bool,
pub running_task_ids: Vec<String>,
pub success_rate: Option<f64>,
pub task_count: u64,
pub avg_cost_usd: Option<f64>,
}
impl AgentResponse {
pub(crate) fn from_json(
agent: AgentJson,
running_task_ids: Vec<String>,
observed_model: Option<String>,
) -> Self {
let history = agent.history.as_ref();
Self {
name: agent.name,
kind: agent.kind,
installed: agent.installed,
disabled: agent.disabled,
provider: agent.provider,
metering: agent.metering,
quota: agent.quota,
default_model: agent.models.default,
observed_model,
busy: !running_task_ids.is_empty(),
running_task_ids,
success_rate: history.map(|value| value.success_rate),
task_count: history.map(|value| value.tasks).unwrap_or(0),
avg_cost_usd: history.and_then(|value| value.avg_cost_usd),
}
}
}