Skip to main content

tap_cli/commands/
agent_management.rs

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    /// Add agents to a transaction (TAIP-5)
14    #[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        /// Transaction ID to add agents to
25        #[arg(long)]
26        transaction_id: String,
27        /// Agents as JSON array of objects with @id, role, and for fields
28        #[arg(long)]
29        agents: String,
30    },
31    /// Remove an agent from a transaction (TAIP-5)
32    #[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        /// Transaction ID to remove agent from
41        #[arg(long)]
42        transaction_id: String,
43        /// DID of the agent to remove
44        #[arg(long)]
45        agent_to_remove: String,
46    },
47    /// Replace an agent in a transaction (TAIP-5)
48    #[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        /// Transaction ID to replace agent in
59        #[arg(long)]
60        transaction_id: String,
61        /// DID of the agent to replace
62        #[arg(long)]
63        original: String,
64        /// New agent as JSON object with @id, role, and for fields
65        #[arg(long)]
66        new_agent: String,
67    },
68    /// Update policies for a transaction (TAIP-7)
69    #[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        /// Transaction ID to update policies for
84        #[arg(long)]
85        transaction_id: String,
86        /// Policies as JSON array of objects with @type and optional attributes
87        #[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}