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.list.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.list.Request")]
115pub struct Request {
116 pub path_type: Path,
117 pub pending: bool,
120 pub targets: Vec<Target>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
124 #[schemars(extend("omitempty" = true))]
125 pub after_id: Option<i64>,
126 #[serde(default, skip_serializing_if = "Option::is_none")]
128 #[schemars(extend("omitempty" = true))]
129 pub limit: Option<i64>,
130 #[serde(flatten)]
131 pub base: crate::cli::command::RequestBase,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
135#[schemars(rename = "cli.command.agents.logs.list.Path")]
136pub enum Path {
137 #[serde(rename = "agents/logs/list")]
138 AgentsLogsList,
139}
140
141impl CommandRequest for Request {
142 fn request_base(&self) -> &crate::cli::command::RequestBase {
143 &self.base
144 }
145
146 fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
147 Some(&mut self.base)
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
154#[serde(rename_all = "snake_case")]
155#[schemars(rename = "cli.command.agents.logs.list.ClientNotificationPartType")]
156pub enum ClientNotificationPartType {
157 Text,
158 Image,
159 Audio,
160 Video,
161 File,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
169#[schemars(rename = "cli.command.agents.logs.list.ClientNotificationPart")]
170pub struct ClientNotificationPart {
171 pub id: i64,
175 pub delivered_at: String,
179 pub r#type: ClientNotificationPartType,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
194#[serde(tag = "type", rename_all = "snake_case")]
195#[schemars(rename = "cli.command.agents.logs.list.AssistantResponsePart")]
196pub enum AssistantResponsePart {
197 #[schemars(title = "ToolCall")]
198 ToolCall {
199 id: i64,
203 delivered_at: String,
204 function_name: String,
206 tool_call_id: String,
208 tool_call_index: i64,
211 },
212 #[schemars(title = "Refusal")]
213 Refusal { id: i64, delivered_at: String },
214 #[schemars(title = "Reasoning")]
215 Reasoning { id: i64, delivered_at: String },
216 #[schemars(title = "Text")]
217 Text { id: i64, delivered_at: String },
218 #[schemars(title = "Image")]
219 Image { id: i64, delivered_at: String },
220 #[schemars(title = "Audio")]
221 Audio { id: i64, delivered_at: String },
222 #[schemars(title = "Video")]
223 Video { id: i64, delivered_at: String },
224 #[schemars(title = "File")]
225 File { id: i64, delivered_at: String },
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
233#[serde(rename_all = "snake_case")]
234#[schemars(rename = "cli.command.agents.logs.list.ToolResponsePartType")]
235pub enum ToolResponsePartType {
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.list.ToolResponsePart")]
246pub struct ToolResponsePart {
247 pub id: i64,
250 pub delivered_at: String,
251 pub r#type: ToolResponsePartType,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
257#[serde(rename_all = "snake_case")]
258#[schemars(rename = "cli.command.agents.logs.list.RequestMessageUserPartType")]
259pub enum RequestMessageUserPartType {
260 Text,
261 Image,
262 Audio,
263 Video,
264 File,
265}
266
267#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
271#[schemars(rename = "cli.command.agents.logs.list.RequestMessageUserPart")]
272pub struct RequestMessageUserPart {
273 pub id: i64,
275 pub delivered_at: String,
276 pub r#type: RequestMessageUserPartType,
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
282#[serde(rename_all = "snake_case")]
283#[schemars(rename = "cli.command.agents.logs.list.VectorRequestChoicePartType")]
284pub enum VectorRequestChoicePartType {
285 Text,
286 Image,
287 Audio,
288 Video,
289 File,
290}
291
292#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
295#[schemars(rename = "cli.command.agents.logs.list.VectorRequestChoicePart")]
296pub struct VectorRequestChoicePart {
297 pub id: i64,
299 pub delivered_at: String,
300 pub r#type: VectorRequestChoicePartType,
301}
302
303#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
307#[schemars(rename = "cli.command.agents.logs.list.VectorRequestChoice")]
308pub struct VectorRequestChoice {
309 pub key: String,
311 pub parts: Vec<VectorRequestChoicePart>,
312}
313
314#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
334#[serde(tag = "type", rename_all = "snake_case")]
335#[schemars(rename = "cli.command.agents.logs.list.ResponseItem")]
336pub enum ResponseItem {
337 #[schemars(title = "RequestMessageUser")]
340 RequestMessageUser {
341 agent_instance_hierarchy: String,
342 response_id: String,
343 parts: Vec<RequestMessageUserPart>,
344 },
345 #[schemars(title = "RequestMessageAssistant")]
349 RequestMessageAssistant {
350 agent_instance_hierarchy: String,
351 response_id: String,
352 parts: Vec<AssistantResponsePart>,
353 },
354 #[schemars(title = "RequestMessageTool")]
357 RequestMessageTool {
358 agent_instance_hierarchy: String,
359 response_id: String,
360 tool_call_id: String,
362 parts: Vec<ToolResponsePart>,
363 },
364 #[schemars(title = "VectorRequestChoices")]
368 VectorRequestChoices {
369 agent_instance_hierarchy: String,
370 response_id: String,
371 choices: Vec<VectorRequestChoice>,
372 },
373 #[schemars(title = "VectorResponseVote")]
377 VectorResponseVote {
378 agent_instance_hierarchy: String,
379 response_id: String,
380 #[serde(deserialize_with = "crate::serde_util::vec_decimal")]
381 #[schemars(with = "Vec<f64>")]
382 vote: Vec<rust_decimal::Decimal>,
383 },
384 #[schemars(title = "ClientNotification")]
385 ClientNotification {
386 agent_instance_hierarchy: String,
387 sender_agent_instance_hierarchy: String,
390 response_id: String,
391 queued_at: String,
397 #[serde(default, skip_serializing_if = "Option::is_none")]
402 #[schemars(extend("omitempty" = true))]
403 key: Option<String>,
404 parts: Vec<ClientNotificationPart>,
405 },
406 #[schemars(title = "AssistantResponse")]
410 AssistantResponse {
411 agent_instance_hierarchy: String,
412 response_id: String,
413 parts: Vec<AssistantResponsePart>,
414 },
415 #[schemars(title = "ToolResponse")]
420 ToolResponse {
421 agent_instance_hierarchy: String,
422 response_id: String,
423 tool_call_id: String,
425 parts: Vec<ToolResponsePart>,
426 },
427 #[schemars(title = "Error")]
434 Error {
435 agent_instance_hierarchy: String,
436 #[serde(default, skip_serializing_if = "Option::is_none")]
440 #[schemars(extend("omitempty" = true))]
441 response_id: Option<String>,
442 id: i64,
444 delivered_at: String,
445 error: serde_json::Value,
448 },
449}
450
451#[derive(clap::Args)]
452#[command(group(
453 clap::ArgGroup::new("logs_list_mode")
454 .required(true)
455 .multiple(false)
456 .args(["all", "pending"])
457))]
458pub struct Args {
459 #[arg(long)]
462 pub all: bool,
463 #[arg(long)]
466 pub pending: bool,
467 #[arg(long = "target", required = true)]
472 pub targets: Vec<String>,
473 #[arg(long)]
475 pub after_id: Option<i64>,
476 #[arg(long)]
478 pub limit: Option<i64>,
479 #[command(flatten)]
480 pub base: crate::cli::command::RequestBaseArgs,
481}
482
483#[derive(clap::Args)]
484#[command(args_conflicts_with_subcommands = true)]
485pub struct Command {
486 #[command(flatten)]
487 pub args: Args,
488 #[command(subcommand)]
489 pub schema: Option<Schema>,
490}
491
492#[derive(clap::Subcommand)]
493pub enum Schema {
494 RequestSchema(request_schema::Args),
496 ResponseSchema(response_schema::Args),
498}
499
500impl TryFrom<Args> for Request {
501 type Error = crate::cli::command::FromArgsError;
502 fn try_from(args: Args) -> Result<Self, Self::Error> {
503 let targets = args
504 .targets
505 .iter()
506 .map(|s| {
507 s.parse::<Target>().map_err(|msg| {
508 crate::cli::command::FromArgsError::path_parse("target", msg)
509 })
510 })
511 .collect::<Result<Vec<_>, _>>()?;
512 Ok(Self {
513 path_type: Path::AgentsLogsList,
514 pending: args.pending,
517 targets,
518 after_id: args.after_id,
519 limit: args.limit,
520 base: args.base.into(),
521 })
522 }
523}
524
525#[cfg(feature = "cli-executor")]
526pub async fn execute<E: crate::cli::command::CommandExecutor>(
527 executor: &E,
528 mut request: Request,
529
530 agent_arguments: Option<&crate::cli::command::AgentArguments>,
531 ) -> Result<E::Stream<ResponseItem>, E::Error> {
532 request.base.clear_transform();
533 executor.execute(request, agent_arguments).await
534}
535
536#[cfg(feature = "cli-executor")]
537pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
538 executor: &E,
539 mut request: Request,
540 transform: crate::cli::command::Transform,
541
542 agent_arguments: Option<&crate::cli::command::AgentArguments>,
543 ) -> Result<E::Stream<serde_json::Value>, E::Error> {
544 request.base.set_transform(transform);
545 executor.execute(request, agent_arguments).await
546}
547
548#[cfg(feature = "mcp")]
549impl crate::cli::command::CommandResponse for ResponseItem {
550 fn into_mcp(self) -> crate::cli::command::McpResponseItem {
551 crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
552 }
553}
554
555pub mod request_schema;
556
557pub mod response_schema;
558
559#[cfg(test)]
560mod tests {
561 use super::Target;
562
563 #[test]
564 fn me_target_parses_and_round_trips() {
565 assert_eq!("me".parse::<Target>(), Ok(Target::Me));
566 assert_eq!(" me ".parse::<Target>(), Ok(Target::Me));
568 assert_eq!(Target::Me.into_arg_string(), "me");
569 }
570
571 #[test]
572 fn me_is_mutually_exclusive_with_other_keys() {
573 assert!("me,instance=x".parse::<Target>().is_err());
576 }
577
578 #[test]
579 fn json_wire_shape_is_by_me() {
580 assert_eq!(
581 serde_json::to_value(Target::Me).unwrap(),
582 serde_json::json!({ "by": "me" }),
583 );
584 }
585}
586
587#[cfg(feature = "cli-listener")]
592pub struct ListenerExecution {
593 pub request: Request,
594 pub agent_arguments: crate::cli::command::AgentArguments,
595 pub response: crate::cli::websocket_listener::ResponseItemStream<ResponseItem>,
596}