agent-fleet/src/core/models.rs
Zer4tul b75546bda6 feat: agent registry API + heartbeat checker + core unit tests
Tasks completed:
- 2.7: Core unit tests (14 tests: state machine, event store, queue, timeout, retry)
- 3.1: POST /api/v1/agents/register (upsert on duplicate)
- 3.2: POST /api/v1/agents/heartbeat
- 3.3: POST /api/v1/agents/deregister (offline + requeue running tasks)
- 3.4: GET /api/v1/agents (filter by capability + status)
- 3.5: Background heartbeat checker (marks offline, sets tasks agent_lost)
- 3.6: API unit tests (register, duplicate, heartbeat, deregister, checker)

All 14 tests pass. cargo check clean (warnings only).
2026-05-11 19:29:16 +08:00

211 lines
5.6 KiB
Rust

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
// ─── Agent ───────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum AgentType {
OpenClaw,
ClaudeCode,
CodexCli,
Hermes,
Acp,
Shell,
Other(String),
}
impl AgentType {
pub fn as_str(&self) -> &str {
match self {
Self::OpenClaw => "openclaw",
Self::ClaudeCode => "claude-code",
Self::CodexCli => "codex-cli",
Self::Hermes => "hermes",
Self::Acp => "acp",
Self::Shell => "shell",
Self::Other(value) => value.as_str(),
}
}
pub fn from_str(value: &str) -> Self {
match value {
"openclaw" => Self::OpenClaw,
"claude-code" => Self::ClaudeCode,
"codex-cli" => Self::CodexCli,
"hermes" => Self::Hermes,
"acp" => Self::Acp,
"shell" => Self::Shell,
other => Self::Other(other.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum AgentStatus {
Online,
Offline,
Draining,
}
impl AgentStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Online => "online",
Self::Offline => "offline",
Self::Draining => "draining",
}
}
pub fn from_str(value: &str) -> Self {
match value {
"online" => Self::Online,
"offline" => Self::Offline,
"draining" => Self::Draining,
_ => Self::Offline,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Agent {
pub agent_id: String,
pub agent_type: AgentType,
pub hostname: String,
pub capabilities: Vec<String>,
pub max_concurrency: u32,
pub current_tasks: u32,
pub status: AgentStatus,
pub last_heartbeat_at: DateTime<Utc>,
pub registered_at: DateTime<Utc>,
pub metadata: HashMap<String, String>,
}
// ─── Task ────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TaskStatus {
Created,
Assigned,
Running,
Completed,
Failed,
AgentLost,
Cancelled,
}
impl TaskStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Created => "created",
Self::Assigned => "assigned",
Self::Running => "running",
Self::Completed => "completed",
Self::Failed => "failed",
Self::AgentLost => "agent_lost",
Self::Cancelled => "cancelled",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum Priority {
Low,
Normal,
High,
Urgent,
}
impl Priority {
pub fn order(&self) -> u8 {
match self {
Self::Urgent => 0,
Self::High => 1,
Self::Normal => 2,
Self::Low => 3,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Low => "low",
Self::Normal => "normal",
Self::High => "high",
Self::Urgent => "urgent",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Task {
pub task_id: String,
pub source: String,
pub task_type: String,
pub priority: Priority,
pub status: TaskStatus,
pub assigned_agent_id: Option<String>,
pub requirements: String,
pub labels: Vec<String>,
pub created_at: DateTime<Utc>,
pub assigned_at: Option<DateTime<Utc>>,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub retry_count: u32,
pub max_retries: u32,
pub timeout_seconds: u64,
}
// ─── Receipt ─────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ReceiptStatus {
Completed,
Failed,
Partial,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ArtifactType {
Pr,
Commit,
File,
Comment,
Url,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Artifact {
pub artifact_type: ArtifactType,
pub url: Option<String>,
pub path: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Receipt {
pub task_id: String,
pub agent_id: String,
pub status: ReceiptStatus,
pub duration_seconds: u64,
pub summary: String,
pub artifacts: Vec<Artifact>,
pub error: Option<String>,
}
// ─── TaskEvent (event sourcing) ──────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TaskEvent {
pub event_id: String,
pub task_id: String,
pub event_type: String,
pub agent_id: Option<String>,
pub timestamp: DateTime<Utc>,
pub payload: serde_json::Value,
}