1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Deserialize)]
10pub struct SyncPullResponse {
11 pub patterns: Vec<RemotePattern>,
12 pub version: i64,
13}
14
15#[derive(Debug, Deserialize)]
17pub struct RemotePattern {
18 pub id: String,
19 pub name: String,
20 pub content: String,
21 pub version: i64,
22 #[serde(default)]
23 pub deleted: bool,
24}
25
26#[derive(Debug, Serialize)]
28pub struct SyncPushRequest {
29 pub base_version: i64,
30 pub changes: Vec<PatternChange>,
31 #[serde(default)]
32 pub force_local: bool,
33}
34
35#[derive(Debug, Serialize)]
37pub struct PatternChange {
38 pub action: String,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub id: Option<String>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub pattern: Option<PatternPayload>,
43}
44
45#[derive(Debug, Serialize)]
46pub struct PatternPayload {
47 pub name: String,
48 pub content: String,
49}
50
51#[derive(Debug, Deserialize)]
53pub struct SyncPushResponse {
54 pub ok: bool,
55 #[serde(default)]
56 pub version: Option<i64>,
57 #[serde(default)]
58 pub conflict: Option<bool>,
59}
60
61#[derive(Debug, Deserialize)]
63pub struct WorkflowListResponse {
64 pub data: Vec<serde_json::Value>,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "snake_case")]
72pub enum FleetEntityType {
73 AgentProfile,
74 ModelBinding,
75 Skill,
76}
77
78impl FleetEntityType {
79 pub fn path_segment(self) -> &'static str {
81 match self {
82 Self::AgentProfile => "agent_profile",
83 Self::ModelBinding => "model_binding",
84 Self::Skill => "skill",
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct FleetChange {
92 pub action: String,
94 pub logical_id: String,
96 pub content_hash: String,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub payload: Option<String>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct FleetEntity {
106 pub logical_id: String,
107 pub content_hash: String,
108 pub version: i64,
109 pub deleted: bool,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub payload: Option<String>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct FleetPushRequest {
116 pub base_version: i64,
117 pub entity_type: FleetEntityType,
118 pub changes: Vec<FleetChange>,
119 #[serde(default)]
120 pub force_local: bool,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct FleetPushResponse {
125 pub ok: bool,
126 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub version: Option<i64>,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub conflict: Option<bool>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct FleetPullResponse {
134 pub entities: Vec<FleetEntity>,
135 pub version: i64,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct SkillFleetPayload {
144 pub manifest_yaml: String,
145 pub events_jsonl: String,
146 pub content_sha256: String,
147}
148
149#[cfg(test)]
150mod fleet_tests {
151 use super::*;
152
153 #[test]
154 fn fleet_push_request_roundtrips() {
155 let req = FleetPushRequest {
156 base_version: 7,
157 entity_type: FleetEntityType::AgentProfile,
158 changes: vec![FleetChange {
159 action: "upsert".into(),
160 logical_id: "agent-abc".into(),
161 content_hash: "deadbeef".into(),
162 payload: Some("name: scout\n".into()),
163 }],
164 force_local: false,
165 };
166 let json = serde_json::to_string(&req).unwrap();
167 let back: FleetPushRequest = serde_json::from_str(&json).unwrap();
168 assert_eq!(back.base_version, 7);
169 assert_eq!(back.entity_type, FleetEntityType::AgentProfile);
170 assert_eq!(back.changes[0].logical_id, "agent-abc");
171 }
172
173 #[test]
174 fn fleet_entity_type_serializes_snake_case() {
175 assert_eq!(
176 serde_json::to_string(&FleetEntityType::ModelBinding).unwrap(),
177 "\"model_binding\""
178 );
179 }
180
181 #[test]
182 fn skill_entity_type_roundtrips() {
183 let s = serde_json::to_string(&FleetEntityType::Skill).unwrap();
184 assert_eq!(s, r#""skill""#);
185 let back: FleetEntityType = serde_json::from_str(&s).unwrap();
186 assert_eq!(back, FleetEntityType::Skill);
187 assert_eq!(FleetEntityType::Skill.path_segment(), "skill");
188 }
189
190 #[test]
191 fn skill_fleet_payload_roundtrips() {
192 let p = SkillFleetPayload {
193 manifest_yaml: "name: foo\n".into(),
194 events_jsonl: "{\"kind\":\"retrieval\"}\n".into(),
195 content_sha256: "abc123".into(),
196 };
197 let s = serde_json::to_string(&p).unwrap();
198 let back: SkillFleetPayload = serde_json::from_str(&s).unwrap();
199 assert_eq!(back.content_sha256, "abc123");
200 }
201}