cellstate/entity/
delegation.rs1use anyhow::{anyhow, Result};
6use clap::{Arg, ArgMatches, Command};
7
8use cellstate_core::api_types::{
9 CreateDelegationRequest, DelegationResponse, DelegationResultRequest,
10};
11use cellstate_core::DelegationResultStatus;
12
13use super::client::ApiClient;
14use super::output::OutputConfig;
15
16const BASE: &str = "/api/v1/delegations";
17
18pub fn build_command() -> Command {
19 Command::new("delegation")
20 .about("Manage task delegations between agents")
21 .subcommand_required(true)
22 .subcommand(
23 Command::new("create")
24 .about("Create a new delegation")
25 .arg(
26 Arg::new("from-agent-id")
27 .long("from-agent-id")
28 .required(true)
29 .help("Agent delegating the task"),
30 )
31 .arg(
32 Arg::new("to-agent-id")
33 .long("to-agent-id")
34 .required(true)
35 .help("Agent receiving the delegation"),
36 )
37 .arg(
38 Arg::new("trajectory-id")
39 .long("trajectory-id")
40 .help("Trajectory for the delegated task (defaults to session trajectory)"),
41 )
42 .arg(
43 Arg::new("scope-id")
44 .long("scope-id")
45 .help("Scope for the delegated task (defaults to session scope)"),
46 )
47 .arg(
48 Arg::new("task-description")
49 .long("task-description")
50 .required(true)
51 .help("Task description"),
52 )
53 .arg(
54 Arg::new("context")
55 .long("context")
56 .help("Additional context (JSON string)"),
57 ),
58 )
59 .subcommand(
60 Command::new("get")
61 .about("Get delegation details")
62 .arg(Arg::new("id").required(true).help("Delegation ID")),
63 )
64 .subcommand(
65 Command::new("accept")
66 .about("Accept a delegation")
67 .arg(Arg::new("id").required(true).help("Delegation ID"))
68 .arg(
69 Arg::new("agent-id")
70 .long("agent-id")
71 .help("Agent accepting the delegation (defaults to session agent)"),
72 ),
73 )
74 .subcommand(
75 Command::new("reject")
76 .about("Reject a delegation")
77 .arg(Arg::new("id").required(true).help("Delegation ID"))
78 .arg(
79 Arg::new("agent-id")
80 .long("agent-id")
81 .help("Agent rejecting the delegation (defaults to session agent)"),
82 )
83 .arg(
84 Arg::new("reason")
85 .long("reason")
86 .required(true)
87 .help("Reason for rejection"),
88 ),
89 )
90 .subcommand(
91 Command::new("complete")
92 .about("Complete a delegation with results")
93 .arg(Arg::new("id").required(true).help("Delegation ID"))
94 .arg(
95 Arg::new("agent-id")
96 .long("agent-id")
97 .help("Agent completing the delegation (defaults to session agent)"),
98 )
99 .arg(
100 Arg::new("status")
101 .long("status")
102 .required(true)
103 .help("Result status (success, partial, failure)"),
104 )
105 .arg(
106 Arg::new("output")
107 .long("output")
108 .help("Output from the delegated task"),
109 )
110 .arg(
111 Arg::new("error")
112 .long("error")
113 .help("Error message (if failed)"),
114 ),
115 )
116}
117
118pub async fn dispatch(
119 matches: &ArgMatches,
120 client: &ApiClient,
121 output: &OutputConfig,
122 session: &crate::session::CliSession,
123) -> Result<()> {
124 match matches.subcommand() {
125 Some(("create", sub)) => {
126 let req = CreateDelegationRequest {
127 from_agent_id: sub.get_one::<String>("from-agent-id").unwrap().parse()?,
128 to_agent_id: sub.get_one::<String>("to-agent-id").unwrap().parse()?,
129 trajectory_id: super::require_arg(
130 sub,
131 "trajectory-id",
132 session.trajectory_id.as_deref(),
133 "trajectory",
134 )?
135 .parse()?,
136 scope_id: super::require_arg(
137 sub,
138 "scope-id",
139 session.scope_id.as_deref(),
140 "scope",
141 )?
142 .parse()?,
143 task_description: sub.get_one::<String>("task-description").unwrap().clone(),
144 expected_completion: None,
145 context: sub
146 .get_one::<String>("context")
147 .map(|s| serde_json::from_str(s))
148 .transpose()
149 .map_err(|e| anyhow!("Invalid context JSON: {e}"))?,
150 };
151 let resp: DelegationResponse = client.post(BASE, &req).await?;
152 output.print(&resp);
153 }
154 Some(("get", sub)) => {
155 let id = sub.get_one::<String>("id").unwrap();
156 let resp: DelegationResponse = client.get(&format!("{BASE}/{id}")).await?;
157 output.print(&resp);
158 }
159 Some(("accept", sub)) => {
160 let id = sub.get_one::<String>("id").unwrap();
161 let agent_id =
162 super::require_arg(sub, "agent-id", session.agent_id.as_deref(), "agent")?;
163 let req = serde_json::json!({
164 "accepting_agent_id": agent_id,
165 });
166 let resp: serde_json::Value = client
167 .post_raw(&format!("{BASE}/{id}/accept"), &req)
168 .await?;
169 output.print_value(&resp);
170 }
171 Some(("reject", sub)) => {
172 let id = sub.get_one::<String>("id").unwrap();
173 let agent_id =
174 super::require_arg(sub, "agent-id", session.agent_id.as_deref(), "agent")?;
175 let reason = sub.get_one::<String>("reason").unwrap();
176 let req = serde_json::json!({
177 "rejecting_agent_id": agent_id,
178 "reason": reason,
179 });
180 let resp: serde_json::Value = client
181 .post_raw(&format!("{BASE}/{id}/reject"), &req)
182 .await?;
183 output.print_value(&resp);
184 }
185 Some(("complete", sub)) => {
186 let id = sub.get_one::<String>("id").unwrap();
187 let agent_id =
188 super::require_arg(sub, "agent-id", session.agent_id.as_deref(), "agent")?;
189 let status: DelegationResultStatus =
190 sub.get_one::<String>("status").unwrap().parse()?;
191 let result = DelegationResultRequest {
192 status,
193 output: sub.get_one::<String>("output").cloned(),
194 artifacts: vec![],
195 error: sub.get_one::<String>("error").cloned(),
196 };
197 let req = serde_json::json!({
198 "completing_agent_id": agent_id,
199 "result": result,
200 });
201 let resp: serde_json::Value = client
202 .post_raw(&format!("{BASE}/{id}/complete"), &req)
203 .await?;
204 output.print_value(&resp);
205 }
206 _ => unreachable!("subcommand_required(true) prevents this"),
207 }
208 Ok(())
209}