1use std::str::FromStr;
20
21use crate::cli::command::CommandRequest;
22use crate::cli::command::path_ref::tokenize;
23
24#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
36#[serde(tag = "by", rename_all = "snake_case")]
37#[schemars(rename = "cli.command.agents.logs.read.all.Target")]
38pub enum Target {
39 #[schemars(title = "Direct")]
40 Direct {
41 #[serde(default, skip_serializing_if = "Option::is_none")]
44 #[schemars(extend("omitempty" = true))]
45 parent_agent_instance_hierarchy: Option<String>,
46 agent_instance: String,
48 },
49 #[schemars(title = "Tag")]
50 Tag { agent_tag: String },
51 #[schemars(title = "Me")]
55 Me,
56}
57
58impl FromStr for Target {
59 type Err = String;
60 fn from_str(s: &str) -> Result<Self, Self::Err> {
64 if s.trim() == "me" {
67 return Ok(Target::Me);
68 }
69 let mut tag: Option<String> = None;
70 let mut parent: Option<String> = None;
71 let mut instance: Option<String> = None;
72 for (k, v) in tokenize(s)? {
73 match k {
74 "tag" => tag = Some(v.to_string()),
75 "instance" => instance = Some(v.to_string()),
76 "parent" => parent = Some(v.to_string()),
77 other => return Err(format!("unknown key: {other}")),
78 }
79 }
80 match (tag, instance, parent) {
81 (Some(t), None, None) => Ok(Target::Tag { agent_tag: t }),
82 (Some(_), _, _) => Err(
83 "tag is mutually exclusive with instance and parent".to_string(),
84 ),
85 (None, Some(i), p) => Ok(Target::Direct {
86 parent_agent_instance_hierarchy: p,
87 agent_instance: i,
88 }),
89 (None, None, _) => Err("instance or tag is required".to_string()),
90 }
91 }
92}
93
94impl Target {
95 pub fn into_arg_string(&self) -> String {
98 match self {
99 Target::Me => "me".to_string(),
100 Target::Tag { agent_tag } => format!("tag={agent_tag}"),
101 Target::Direct {
102 parent_agent_instance_hierarchy: None,
103 agent_instance,
104 } => format!("instance={agent_instance}"),
105 Target::Direct {
106 parent_agent_instance_hierarchy: Some(p),
107 agent_instance,
108 } => format!("instance={agent_instance},parent={p}"),
109 }
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
114#[schemars(rename = "cli.command.agents.logs.read.all.Request")]
115pub struct Request {
116 pub path_type: Path,
117 pub targets: Vec<Target>,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
121 #[schemars(extend("omitempty" = true))]
122 pub after_id: Option<i64>,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
125 #[schemars(extend("omitempty" = true))]
126 pub limit: Option<i64>,
127 #[serde(flatten)]
128 pub base: crate::cli::command::RequestBase,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
132#[schemars(rename = "cli.command.agents.logs.read.all.Path")]
133pub enum Path {
134 #[serde(rename = "agents/logs/read/all")]
135 AgentsLogsReadAll,
136}
137
138impl CommandRequest for Request {
139 fn into_command(&self) -> Vec<String> {
140 let mut argv = vec![
141 "agents".to_string(),
142 "logs".to_string(),
143 "read".to_string(),
144 "all".to_string(),
145 ];
146 for target in &self.targets {
147 argv.push("--target".to_string());
148 argv.push(target.into_arg_string());
149 }
150 if let Some(after_id) = self.after_id {
151 argv.push("--after-id".to_string());
152 argv.push(after_id.to_string());
153 }
154 if let Some(limit) = self.limit {
155 argv.push("--limit".to_string());
156 argv.push(limit.to_string());
157 }
158 self.base.push_flags(&mut argv);
159 argv
160 }
161
162 fn request_base(&self) -> &crate::cli::command::RequestBase {
163 &self.base
164 }
165
166 fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
167 Some(&mut self.base)
168 }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
174#[serde(rename_all = "snake_case")]
175#[schemars(rename = "cli.command.agents.logs.read.all.ClientNotificationPartType")]
176pub enum ClientNotificationPartType {
177 Text,
178 Image,
179 Audio,
180 Video,
181 File,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
189#[schemars(rename = "cli.command.agents.logs.read.all.ClientNotificationPart")]
190pub struct ClientNotificationPart {
191 pub id: i64,
195 pub delivered_at: String,
199 pub r#type: ClientNotificationPartType,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
214#[serde(tag = "type", rename_all = "snake_case")]
215#[schemars(rename = "cli.command.agents.logs.read.all.AssistantResponsePart")]
216pub enum AssistantResponsePart {
217 #[schemars(title = "ToolCall")]
218 ToolCall {
219 id: i64,
223 delivered_at: String,
224 function_name: String,
226 tool_call_id: String,
228 tool_call_index: i64,
231 },
232 #[schemars(title = "Refusal")]
233 Refusal { id: i64, delivered_at: String },
234 #[schemars(title = "Reasoning")]
235 Reasoning { id: i64, delivered_at: String },
236 #[schemars(title = "Text")]
237 Text { id: i64, delivered_at: String },
238 #[schemars(title = "Image")]
239 Image { id: i64, delivered_at: String },
240 #[schemars(title = "Audio")]
241 Audio { id: i64, delivered_at: String },
242 #[schemars(title = "Video")]
243 Video { id: i64, delivered_at: String },
244 #[schemars(title = "File")]
245 File { id: i64, delivered_at: String },
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
253#[serde(rename_all = "snake_case")]
254#[schemars(rename = "cli.command.agents.logs.read.all.ToolResponsePartType")]
255pub enum ToolResponsePartType {
256 Text,
257 Image,
258 Audio,
259 Video,
260 File,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
265#[schemars(rename = "cli.command.agents.logs.read.all.ToolResponsePart")]
266pub struct ToolResponsePart {
267 pub id: i64,
270 pub delivered_at: String,
271 pub r#type: ToolResponsePartType,
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
294#[serde(tag = "type", rename_all = "snake_case")]
295#[schemars(rename = "cli.command.agents.logs.read.all.ResponseItem")]
296pub enum ResponseItem {
297 #[schemars(title = "AgentCompletionRequest")]
298 AgentCompletionRequest {
299 id: i64,
300 agent_instance_hierarchy: String,
301 sender_agent_instance_hierarchy: String,
304 delivered_at: String,
305 response_id: String,
306 },
307 #[schemars(title = "VectorCompletionRequest")]
308 VectorCompletionRequest {
309 id: i64,
310 agent_instance_hierarchy: String,
311 sender_agent_instance_hierarchy: String,
312 delivered_at: String,
313 response_id: String,
314 },
315 #[schemars(title = "FunctionExecutionRequest")]
316 FunctionExecutionRequest {
317 id: i64,
318 agent_instance_hierarchy: String,
319 sender_agent_instance_hierarchy: String,
320 delivered_at: String,
321 response_id: String,
322 },
323 #[schemars(title = "ClientNotification")]
324 ClientNotification {
325 agent_instance_hierarchy: String,
326 sender_agent_instance_hierarchy: String,
329 response_id: String,
330 queued_at: String,
336 #[serde(default, skip_serializing_if = "Option::is_none")]
341 #[schemars(extend("omitempty" = true))]
342 key: Option<String>,
343 parts: Vec<ClientNotificationPart>,
344 },
345 #[schemars(title = "AssistantResponse")]
349 AssistantResponse {
350 agent_instance_hierarchy: String,
351 response_id: String,
352 parts: Vec<AssistantResponsePart>,
353 },
354 #[schemars(title = "ToolResponse")]
359 ToolResponse {
360 agent_instance_hierarchy: String,
361 response_id: String,
362 tool_call_id: String,
364 parts: Vec<ToolResponsePart>,
365 },
366}
367
368#[derive(clap::Args)]
369pub struct Args {
370 #[arg(long = "target", required = true)]
375 pub targets: Vec<String>,
376 #[arg(long)]
378 pub after_id: Option<i64>,
379 #[arg(long)]
381 pub limit: Option<i64>,
382 #[command(flatten)]
383 pub base: crate::cli::command::RequestBaseArgs,
384}
385
386#[derive(clap::Args)]
387#[command(args_conflicts_with_subcommands = true)]
388pub struct Command {
389 #[command(flatten)]
390 pub args: Args,
391 #[command(subcommand)]
392 pub schema: Option<Schema>,
393}
394
395#[derive(clap::Subcommand)]
396pub enum Schema {
397 RequestSchema(request_schema::Args),
399 ResponseSchema(response_schema::Args),
401}
402
403impl TryFrom<Args> for Request {
404 type Error = crate::cli::command::FromArgsError;
405 fn try_from(args: Args) -> Result<Self, Self::Error> {
406 let targets = args
407 .targets
408 .iter()
409 .map(|s| {
410 s.parse::<Target>().map_err(|msg| {
411 crate::cli::command::FromArgsError::path_parse("target", msg)
412 })
413 })
414 .collect::<Result<Vec<_>, _>>()?;
415 Ok(Self {
416 path_type: Path::AgentsLogsReadAll,
417 targets,
418 after_id: args.after_id,
419 limit: args.limit,
420 base: args.base.into(),
421 })
422 }
423}
424
425#[cfg(feature = "cli-executor")]
426pub async fn execute<E: crate::cli::command::CommandExecutor>(
427 executor: &E,
428 mut request: Request,
429
430 agent_arguments: Option<&crate::cli::command::AgentArguments>,
431 ) -> Result<E::Stream<ResponseItem>, E::Error> {
432 request.base.clear_transform();
433 executor.execute(request, agent_arguments).await
434}
435
436#[cfg(feature = "cli-executor")]
437pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
438 executor: &E,
439 mut request: Request,
440 transform: crate::cli::command::Transform,
441
442 agent_arguments: Option<&crate::cli::command::AgentArguments>,
443 ) -> Result<E::Stream<serde_json::Value>, E::Error> {
444 request.base.set_transform(transform);
445 executor.execute(request, agent_arguments).await
446}
447
448#[cfg(feature = "mcp")]
449impl crate::cli::command::CommandResponse for ResponseItem {
450 fn into_mcp(self) -> crate::cli::command::McpResponseItem {
451 crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
452 }
453}
454
455pub mod request_schema;
456
457
458pub mod response_schema;
459
460#[cfg(test)]
461mod tests {
462 use super::Target;
463
464 #[test]
465 fn me_target_parses_and_round_trips() {
466 assert_eq!("me".parse::<Target>(), Ok(Target::Me));
467 assert_eq!(" me ".parse::<Target>(), Ok(Target::Me));
469 assert_eq!(Target::Me.into_arg_string(), "me");
470 }
471
472 #[test]
473 fn me_is_mutually_exclusive_with_other_keys() {
474 assert!("me,instance=x".parse::<Target>().is_err());
477 }
478
479 #[test]
480 fn json_wire_shape_is_by_me() {
481 assert_eq!(
482 serde_json::to_value(Target::Me).unwrap(),
483 serde_json::json!({ "by": "me" }),
484 );
485 }
486}