1use crate::error::{Error, Result};
2use crate::output::{print_success, OutputFormat};
3use crate::tap_integration::TapIntegration;
4use clap::Subcommand;
5use serde::Serialize;
6use tap_msg::message::policy::Policy;
7use tap_msg::message::tap_message_trait::TapMessageBody;
8use tap_msg::message::{AddAgents, Agent, RemoveAgent, ReplaceAgent, UpdatePolicies};
9use tracing::debug;
10
11#[derive(Subcommand, Debug)]
12pub enum AgentManagementCommands {
13 #[command(long_about = "\
15Add agents to an existing transaction (TAIP-5).
16
17Sends an AddAgents message to include new agents (VASPs, compliance officers, etc.) \
18in the transaction.
19
20Examples:
21 tap-cli agent-mgmt add-agents --transaction-id <ID> \\
22 --agents '[{\"@id\":\"did:key:z6Mk...\",\"role\":\"ComplianceOfficer\",\"for\":\"did:key:z6Mk...\"}]'")]
23 AddAgents {
24 #[arg(long)]
26 transaction_id: String,
27 #[arg(long)]
29 agents: String,
30 },
31 #[command(long_about = "\
33Remove an agent from an existing transaction (TAIP-5).
34
35Sends a RemoveAgent message to remove an agent from the transaction.
36
37Examples:
38 tap-cli agent-mgmt remove-agent --transaction-id <ID> --agent-to-remove did:key:z6Mk...")]
39 RemoveAgent {
40 #[arg(long)]
42 transaction_id: String,
43 #[arg(long)]
45 agent_to_remove: String,
46 },
47 #[command(long_about = "\
49Replace an agent in an existing transaction (TAIP-5).
50
51Sends a ReplaceAgent message to swap one agent for another.
52
53Examples:
54 tap-cli agent-mgmt replace-agent --transaction-id <ID> \\
55 --original did:key:z6MkOld... \\
56 --new-agent '{\"@id\":\"did:key:z6MkNew...\",\"role\":\"SourceAgent\",\"for\":\"did:key:z6Mk...\"}'")]
57 ReplaceAgent {
58 #[arg(long)]
60 transaction_id: String,
61 #[arg(long)]
63 original: String,
64 #[arg(long)]
66 new_agent: String,
67 },
68 #[command(long_about = "\
70Update policies for an existing transaction (TAIP-7).
71
72Sends an UpdatePolicies message to set or modify the transaction's policies. \
73Policies control what is required before certain actions can be taken.
74
75Policy types: RequireAuthorization, RequirePresentation, RequireProofOfControl
76
77Examples:
78 tap-cli agent-mgmt update-policies --transaction-id <ID> \\
79 --policies '[{\"@type\":\"RequireAuthorization\"}]'
80 tap-cli agent-mgmt update-policies --transaction-id <ID> \\
81 --policies '[{\"@type\":\"RequirePresentation\",\"presentation_definition\":{...}}]'")]
82 UpdatePolicies {
83 #[arg(long)]
85 transaction_id: String,
86 #[arg(long)]
88 policies: String,
89 },
90}
91
92#[derive(Debug, Serialize)]
93struct AgentManagementResponse {
94 transaction_id: String,
95 message_id: String,
96 status: String,
97 action: String,
98 timestamp: String,
99}
100
101#[derive(Debug, serde::Deserialize)]
102struct AgentInput {
103 #[serde(rename = "@id")]
104 id: String,
105 role: String,
106 #[serde(rename = "for")]
107 for_party: String,
108}
109
110pub async fn handle(
111 cmd: &AgentManagementCommands,
112 format: OutputFormat,
113 agent_did: &str,
114 tap_integration: &TapIntegration,
115) -> Result<()> {
116 match cmd {
117 AgentManagementCommands::AddAgents {
118 transaction_id,
119 agents,
120 } => handle_add_agents(agent_did, transaction_id, agents, format, tap_integration).await,
121 AgentManagementCommands::RemoveAgent {
122 transaction_id,
123 agent_to_remove,
124 } => {
125 handle_remove_agent(
126 agent_did,
127 transaction_id,
128 agent_to_remove,
129 format,
130 tap_integration,
131 )
132 .await
133 }
134 AgentManagementCommands::ReplaceAgent {
135 transaction_id,
136 original,
137 new_agent,
138 } => {
139 handle_replace_agent(
140 agent_did,
141 transaction_id,
142 original,
143 new_agent,
144 format,
145 tap_integration,
146 )
147 .await
148 }
149 AgentManagementCommands::UpdatePolicies {
150 transaction_id,
151 policies,
152 } => {
153 handle_update_policies(agent_did, transaction_id, policies, format, tap_integration)
154 .await
155 }
156 }
157}
158
159async fn handle_add_agents(
160 agent_did: &str,
161 transaction_id: &str,
162 agents_json: &str,
163 format: OutputFormat,
164 tap_integration: &TapIntegration,
165) -> Result<()> {
166 let inputs: Vec<AgentInput> = serde_json::from_str(agents_json)
167 .map_err(|e| Error::invalid_parameter(format!("Invalid agents JSON: {}", e)))?;
168
169 let agents: Vec<Agent> = inputs
170 .iter()
171 .map(|a| Agent::new(&a.id, &a.role, &a.for_party))
172 .collect();
173
174 let add_agents = AddAgents::new(transaction_id, agents);
175
176 add_agents
177 .validate()
178 .map_err(|e| Error::invalid_parameter(format!("AddAgents validation failed: {}", e)))?;
179
180 let didcomm_message = add_agents
181 .to_didcomm(agent_did)
182 .map_err(|e| Error::command_failed(format!("Failed to create DIDComm message: {}", e)))?;
183
184 debug!("Sending add-agents for transaction {}", transaction_id);
185
186 tap_integration
187 .node()
188 .send_message(agent_did.to_string(), didcomm_message.clone())
189 .await
190 .map_err(|e| Error::command_failed(format!("Failed to send add-agents: {}", e)))?;
191
192 let response = AgentManagementResponse {
193 transaction_id: transaction_id.to_string(),
194 message_id: didcomm_message.id,
195 status: "sent".to_string(),
196 action: "add_agents".to_string(),
197 timestamp: chrono::Utc::now().to_rfc3339(),
198 };
199 print_success(format, &response);
200 Ok(())
201}
202
203async fn handle_remove_agent(
204 agent_did: &str,
205 transaction_id: &str,
206 agent_to_remove: &str,
207 format: OutputFormat,
208 tap_integration: &TapIntegration,
209) -> Result<()> {
210 let remove_agent = RemoveAgent::new(transaction_id, agent_to_remove);
211
212 remove_agent
213 .validate()
214 .map_err(|e| Error::invalid_parameter(format!("RemoveAgent validation failed: {}", e)))?;
215
216 let didcomm_message = remove_agent
217 .to_didcomm(agent_did)
218 .map_err(|e| Error::command_failed(format!("Failed to create DIDComm message: {}", e)))?;
219
220 debug!("Sending remove-agent for transaction {}", transaction_id);
221
222 tap_integration
223 .node()
224 .send_message(agent_did.to_string(), didcomm_message.clone())
225 .await
226 .map_err(|e| Error::command_failed(format!("Failed to send remove-agent: {}", e)))?;
227
228 let response = AgentManagementResponse {
229 transaction_id: transaction_id.to_string(),
230 message_id: didcomm_message.id,
231 status: "sent".to_string(),
232 action: "remove_agent".to_string(),
233 timestamp: chrono::Utc::now().to_rfc3339(),
234 };
235 print_success(format, &response);
236 Ok(())
237}
238
239async fn handle_replace_agent(
240 agent_did: &str,
241 transaction_id: &str,
242 original_agent: &str,
243 new_agent_json: &str,
244 format: OutputFormat,
245 tap_integration: &TapIntegration,
246) -> Result<()> {
247 let input: AgentInput = serde_json::from_str(new_agent_json)
248 .map_err(|e| Error::invalid_parameter(format!("Invalid new agent JSON: {}", e)))?;
249
250 let replacement = Agent::new(&input.id, &input.role, &input.for_party);
251 let replace_agent = ReplaceAgent::new(transaction_id, original_agent, replacement);
252
253 replace_agent
254 .validate()
255 .map_err(|e| Error::invalid_parameter(format!("ReplaceAgent validation failed: {}", e)))?;
256
257 let didcomm_message = replace_agent
258 .to_didcomm(agent_did)
259 .map_err(|e| Error::command_failed(format!("Failed to create DIDComm message: {}", e)))?;
260
261 debug!("Sending replace-agent for transaction {}", transaction_id);
262
263 tap_integration
264 .node()
265 .send_message(agent_did.to_string(), didcomm_message.clone())
266 .await
267 .map_err(|e| Error::command_failed(format!("Failed to send replace-agent: {}", e)))?;
268
269 let response = AgentManagementResponse {
270 transaction_id: transaction_id.to_string(),
271 message_id: didcomm_message.id,
272 status: "sent".to_string(),
273 action: "replace_agent".to_string(),
274 timestamp: chrono::Utc::now().to_rfc3339(),
275 };
276 print_success(format, &response);
277 Ok(())
278}
279
280async fn handle_update_policies(
281 agent_did: &str,
282 transaction_id: &str,
283 policies_json: &str,
284 format: OutputFormat,
285 tap_integration: &TapIntegration,
286) -> Result<()> {
287 let policies: Vec<Policy> = serde_json::from_str(policies_json)
288 .map_err(|e| Error::invalid_parameter(format!("Invalid policies JSON: {}", e)))?;
289
290 let update_policies = UpdatePolicies::new(transaction_id, policies);
291
292 update_policies.validate().map_err(|e| {
293 Error::invalid_parameter(format!("UpdatePolicies validation failed: {}", e))
294 })?;
295
296 let didcomm_message = update_policies
297 .to_didcomm(agent_did)
298 .map_err(|e| Error::command_failed(format!("Failed to create DIDComm message: {}", e)))?;
299
300 debug!("Sending update-policies for transaction {}", transaction_id);
301
302 tap_integration
303 .node()
304 .send_message(agent_did.to_string(), didcomm_message.clone())
305 .await
306 .map_err(|e| Error::command_failed(format!("Failed to send update-policies: {}", e)))?;
307
308 let response = AgentManagementResponse {
309 transaction_id: transaction_id.to_string(),
310 message_id: didcomm_message.id,
311 status: "sent".to_string(),
312 action: "update_policies".to_string(),
313 timestamp: chrono::Utc::now().to_rfc3339(),
314 };
315 print_success(format, &response);
316 Ok(())
317}