agentmux_srv\drone/
types.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Drone + run types. Mirrors the frontend shape so RPC payloads
5//! flow through `serde_json::to_value` without manual mapping.
6
7use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10
11/// Block (node) kinds for Phase 1. Phase 2 adds Function, Loop, Parallel,
12/// Router, Subdrone. Stored as `kind` field on `FlowNode.data`.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum BlockKind {
16    Agent,
17    Condition,
18    Api,
19    Response,
20    Variables,
21}
22
23impl BlockKind {
24    pub fn parse(s: &str) -> Option<Self> {
25        match s {
26            "agent" => Some(Self::Agent),
27            "condition" => Some(Self::Condition),
28            "api" => Some(Self::Api),
29            "response" => Some(Self::Response),
30            "variables" => Some(Self::Variables),
31            _ => None,
32        }
33    }
34}
35
36/// Position-and-data shape of a node on the canvas. Mirrors xyflow's
37/// `Node` — id, position, data, type are the fields the canvas reads.
38/// Anything inside `data` is block-specific config.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct FlowNode {
41    pub id: String,
42    /// xyflow position. Saved as-is.
43    #[serde(default)]
44    pub position: NodePosition,
45    /// Block kind + per-kind config (`task`, `url`, `expr`, etc.).
46    pub data: serde_json::Value,
47    /// Optional xyflow node type — keeps the canvas configurable.
48    #[serde(default, rename = "type", skip_serializing_if = "String::is_empty")]
49    pub node_type: String,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, Default)]
53pub struct NodePosition {
54    pub x: f64,
55    pub y: f64,
56}
57
58/// xyflow `Edge` — source/target ids, optional handle ids.
59///
60/// Wire format matches xyflow's TS shape (camelCase: `sourceHandle` /
61/// `targetHandle`) so JSON roundtrips through the canvas + frontend
62/// `DroneFlowEdge` type without field-name translation.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct FlowEdge {
66    pub id: String,
67    pub source: String,
68    pub target: String,
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub source_handle: Option<String>,
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub target_handle: Option<String>,
73}
74
75/// Top-level graph payload — what the canvas saves and the executor reads.
76#[derive(Debug, Clone, Serialize, Deserialize, Default)]
77pub struct DroneGraph {
78    #[serde(default)]
79    pub nodes: Vec<FlowNode>,
80    #[serde(default)]
81    pub edges: Vec<FlowEdge>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct DroneViewport {
86    pub x: f64,
87    pub y: f64,
88    pub zoom: f64,
89}
90
91impl Default for DroneViewport {
92    fn default() -> Self {
93        Self { x: 0.0, y: 0.0, zoom: 1.0 }
94    }
95}
96
97/// Wstore row shape. Matches `db_drone_definitions` schema.
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct DroneDefinition {
100    pub id: String,
101    pub name: String,
102    #[serde(default)]
103    pub description: String,
104    #[serde(default)]
105    pub graph: DroneGraph,
106    #[serde(default)]
107    pub viewport: DroneViewport,
108    pub created_at: i64,
109    pub updated_at: i64,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
113#[serde(rename_all = "snake_case")]
114pub enum RunStatus {
115    Running,
116    Done,
117    Failed,
118}
119
120impl RunStatus {
121    pub fn as_str(&self) -> &'static str {
122        match self {
123            Self::Running => "running",
124            Self::Done => "done",
125            Self::Failed => "failed",
126        }
127    }
128}
129
130/// One row in `db_drone_runs`. Append-only history of executions.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct DroneRun {
133    pub id: String,
134    pub drone_id: String,
135    pub status: String,
136    pub started_at: i64,
137    pub ended_at: i64,
138    /// Map of block_id → BlockState snapshot at run completion.
139    #[serde(default)]
140    pub block_states: HashMap<String, BlockState>,
141    /// Final output captured by the Response block (stringified JSON).
142    #[serde(default)]
143    pub output: String,
144    /// Top-level error message if the run failed before reaching Response.
145    #[serde(default)]
146    pub error: String,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct BlockState {
151    pub status: String, // "pending" | "running" | "done" | "error" | "skipped"
152    #[serde(default, skip_serializing_if = "Option::is_none")]
153    pub output: Option<serde_json::Value>,
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub error: Option<String>,
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub started_at: Option<i64>,
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub completed_at: Option<i64>,
160}
161
162impl BlockState {
163    pub fn pending() -> Self {
164        Self {
165            status: "pending".to_string(),
166            output: None,
167            error: None,
168            started_at: None,
169            completed_at: None,
170        }
171    }
172}