1use std::str::FromStr;
19
20use crate::cli::command::CommandRequest;
21use crate::cli::command::path_ref::tokenize;
22
23#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
35#[serde(tag = "by", rename_all = "snake_case")]
36#[schemars(rename = "cli.command.agents.logs.read.all.Target")]
37pub enum Target {
38 #[schemars(title = "Direct")]
39 Direct {
40 #[serde(default, skip_serializing_if = "Option::is_none")]
43 #[schemars(extend("omitempty" = true))]
44 parent_agent_instance_hierarchy: Option<String>,
45 agent_instance: String,
47 },
48 #[schemars(title = "Tag")]
49 Tag { agent_tag: String },
50 #[schemars(title = "Me")]
54 Me,
55}
56
57impl FromStr for Target {
58 type Err = String;
59 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 if s.trim() == "me" {
66 return Ok(Target::Me);
67 }
68 let mut tag: Option<String> = None;
69 let mut parent: Option<String> = None;
70 let mut instance: Option<String> = None;
71 for (k, v) in tokenize(s)? {
72 match k {
73 "tag" => tag = Some(v.to_string()),
74 "instance" => instance = Some(v.to_string()),
75 "parent" => parent = Some(v.to_string()),
76 other => return Err(format!("unknown key: {other}")),
77 }
78 }
79 match (tag, instance, parent) {
80 (Some(t), None, None) => Ok(Target::Tag { agent_tag: t }),
81 (Some(_), _, _) => Err(
82 "tag is mutually exclusive with instance and parent".to_string(),
83 ),
84 (None, Some(i), p) => Ok(Target::Direct {
85 parent_agent_instance_hierarchy: p,
86 agent_instance: i,
87 }),
88 (None, None, _) => Err("instance or tag is required".to_string()),
89 }
90 }
91}
92
93impl Target {
94 pub fn into_arg_string(&self) -> String {
97 match self {
98 Target::Me => "me".to_string(),
99 Target::Tag { agent_tag } => format!("tag={agent_tag}"),
100 Target::Direct {
101 parent_agent_instance_hierarchy: None,
102 agent_instance,
103 } => format!("instance={agent_instance}"),
104 Target::Direct {
105 parent_agent_instance_hierarchy: Some(p),
106 agent_instance,
107 } => format!("instance={agent_instance},parent={p}"),
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
113#[schemars(rename = "cli.command.agents.logs.read.all.Request")]
114pub struct Request {
115 pub path_type: Path,
116 pub targets: Vec<Target>,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
120 #[schemars(extend("omitempty" = true))]
121 pub after_id: Option<i64>,
122 #[serde(default, skip_serializing_if = "Option::is_none")]
124 #[schemars(extend("omitempty" = true))]
125 pub limit: Option<i64>,
126 pub jq: Option<String>,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
130#[schemars(rename = "cli.command.agents.logs.read.all.Path")]
131pub enum Path {
132 #[serde(rename = "agents/logs/read/all")]
133 AgentsLogsReadAll,
134}
135
136impl CommandRequest for Request {
137 fn into_command(&self) -> Vec<String> {
138 let mut argv = vec![
139 "agents".to_string(),
140 "logs".to_string(),
141 "read".to_string(),
142 "all".to_string(),
143 ];
144 for target in &self.targets {
145 argv.push("--target".to_string());
146 argv.push(target.into_arg_string());
147 }
148 if let Some(after_id) = self.after_id {
149 argv.push("--after-id".to_string());
150 argv.push(after_id.to_string());
151 }
152 if let Some(limit) = self.limit {
153 argv.push("--limit".to_string());
154 argv.push(limit.to_string());
155 }
156 if let Some(jq) = &self.jq {
157 argv.push("--jq".to_string());
158 argv.push(jq.clone());
159 }
160 argv
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
167#[serde(rename_all = "snake_case")]
168#[schemars(rename = "cli.command.agents.logs.read.all.ClientNotificationPartType")]
169pub enum ClientNotificationPartType {
170 Text,
171 Image,
172 Audio,
173 Video,
174 File,
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
182#[schemars(rename = "cli.command.agents.logs.read.all.ClientNotificationPart")]
183pub struct ClientNotificationPart {
184 pub id: i64,
188 pub timestamp_delivered: i64,
192 pub r#type: ClientNotificationPartType,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
198#[serde(rename_all = "snake_case")]
199#[schemars(rename = "cli.command.agents.logs.read.all.AssistantResponsePartType")]
200pub enum AssistantResponsePartType {
201 Refusal,
202 Reasoning,
203 ToolCall,
204 Text,
205 Image,
206 Audio,
207 Video,
208 File,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
213#[schemars(rename = "cli.command.agents.logs.read.all.AssistantResponsePart")]
214pub struct AssistantResponsePart {
215 pub id: i64,
218 pub timestamp_delivered: i64,
219 pub r#type: AssistantResponsePartType,
220 pub function_name: String,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
232#[serde(rename_all = "snake_case")]
233#[schemars(rename = "cli.command.agents.logs.read.all.ToolResponsePartType")]
234pub enum ToolResponsePartType {
235 Container,
236 Text,
237 Image,
238 Audio,
239 Video,
240 File,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
245#[schemars(rename = "cli.command.agents.logs.read.all.ToolResponsePart")]
246pub struct ToolResponsePart {
247 pub id: i64,
250 pub timestamp_delivered: i64,
251 pub r#type: ToolResponsePartType,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
272#[serde(tag = "type", rename_all = "snake_case")]
273#[schemars(rename = "cli.command.agents.logs.read.all.ResponseItem")]
274pub enum ResponseItem {
275 #[schemars(title = "AgentCompletionRequest")]
276 AgentCompletionRequest {
277 id: i64,
278 agent_instance_hierarchy: String,
279 sender_agent_instance_hierarchy: String,
282 timestamp_delivered: i64,
283 response_id: String,
284 },
285 #[schemars(title = "VectorCompletionRequest")]
286 VectorCompletionRequest {
287 id: i64,
288 agent_instance_hierarchy: String,
289 sender_agent_instance_hierarchy: String,
290 timestamp_delivered: i64,
291 response_id: String,
292 },
293 #[schemars(title = "FunctionExecutionRequest")]
294 FunctionExecutionRequest {
295 id: i64,
296 agent_instance_hierarchy: String,
297 sender_agent_instance_hierarchy: String,
298 timestamp_delivered: i64,
299 response_id: String,
300 },
301 #[schemars(title = "ClientNotification")]
302 ClientNotification {
303 agent_instance_hierarchy: String,
304 sender_agent_instance_hierarchy: String,
307 response_id: String,
308 timestamp_queued: i64,
314 #[serde(default, skip_serializing_if = "Option::is_none")]
319 #[schemars(extend("omitempty" = true))]
320 key: Option<String>,
321 parts: Vec<ClientNotificationPart>,
322 },
323 #[schemars(title = "AssistantResponse")]
327 AssistantResponse {
328 agent_instance_hierarchy: String,
329 response_id: String,
330 parts: Vec<AssistantResponsePart>,
331 },
332 #[schemars(title = "ToolResponse")]
333 ToolResponse {
334 agent_instance_hierarchy: String,
335 response_id: String,
336 parts: Vec<ToolResponsePart>,
337 },
338}
339
340#[derive(clap::Args)]
341pub struct Args {
342 #[arg(long = "target", required = true)]
347 pub targets: Vec<String>,
348 #[arg(long)]
350 pub after_id: Option<i64>,
351 #[arg(long)]
353 pub limit: Option<i64>,
354 #[arg(long)]
356 pub jq: Option<String>,
357}
358
359#[derive(clap::Args)]
360#[command(args_conflicts_with_subcommands = true)]
361pub struct Command {
362 #[command(flatten)]
363 pub args: Args,
364 #[command(subcommand)]
365 pub schema: Option<Schema>,
366}
367
368#[derive(clap::Subcommand)]
369pub enum Schema {
370 RequestSchema(request_schema::Args),
372 ResponseSchema(response_schema::Args),
374}
375
376impl TryFrom<Args> for Request {
377 type Error = crate::cli::command::FromArgsError;
378 fn try_from(args: Args) -> Result<Self, Self::Error> {
379 let targets = args
380 .targets
381 .iter()
382 .map(|s| {
383 s.parse::<Target>().map_err(|msg| {
384 crate::cli::command::FromArgsError::path_parse("target", msg)
385 })
386 })
387 .collect::<Result<Vec<_>, _>>()?;
388 Ok(Self {
389 path_type: Path::AgentsLogsReadAll,
390 targets,
391 after_id: args.after_id,
392 limit: args.limit,
393 jq: args.jq,
394 })
395 }
396}
397
398#[cfg(feature = "cli-executor")]
399pub async fn execute<E: crate::cli::command::CommandExecutor>(
400 executor: &E,
401 mut request: Request,
402
403 agent_arguments: Option<&crate::cli::command::AgentArguments>,
404 ) -> Result<E::Stream<ResponseItem>, E::Error> {
405 request.jq = None;
406 executor.execute(request, agent_arguments).await
407}
408
409#[cfg(feature = "cli-executor")]
410pub async fn execute_jq<E: crate::cli::command::CommandExecutor>(
411 executor: &E,
412 mut request: Request,
413 jq: String,
414
415 agent_arguments: Option<&crate::cli::command::AgentArguments>,
416 ) -> Result<E::Stream<serde_json::Value>, E::Error> {
417 request.jq = Some(jq);
418 executor.execute(request, agent_arguments).await
419}
420
421#[cfg(feature = "mcp")]
422impl crate::cli::command::CommandResponse for ResponseItem {
423 fn into_mcp(self) -> crate::cli::command::McpResponseItem {
424 crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
425 }
426}
427
428pub mod request_schema;
429
430
431pub mod response_schema;
432
433#[cfg(test)]
434mod tests {
435 use super::Target;
436
437 #[test]
438 fn me_target_parses_and_round_trips() {
439 assert_eq!("me".parse::<Target>(), Ok(Target::Me));
440 assert_eq!(" me ".parse::<Target>(), Ok(Target::Me));
442 assert_eq!(Target::Me.into_arg_string(), "me");
443 }
444
445 #[test]
446 fn me_is_mutually_exclusive_with_other_keys() {
447 assert!("me,instance=x".parse::<Target>().is_err());
450 }
451
452 #[test]
453 fn json_wire_shape_is_by_me() {
454 assert_eq!(
455 serde_json::to_value(Target::Me).unwrap(),
456 serde_json::json!({ "by": "me" }),
457 );
458 }
459}