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, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
274#[serde(tag = "type", rename_all = "snake_case")]
275#[schemars(rename = "cli.command.agents.logs.list.ResponseItem")]
276pub enum ResponseItem {
277 #[schemars(title = "AgentCompletionRequest")]
278 AgentCompletionRequest {
279 id: i64,
280 agent_instance_hierarchy: String,
281 sender_agent_instance_hierarchy: String,
284 delivered_at: String,
285 response_id: String,
286 },
287 #[schemars(title = "VectorCompletionRequest")]
288 VectorCompletionRequest {
289 id: i64,
290 agent_instance_hierarchy: String,
291 sender_agent_instance_hierarchy: String,
292 delivered_at: String,
293 response_id: String,
294 },
295 #[schemars(title = "FunctionExecutionRequest")]
296 FunctionExecutionRequest {
297 id: i64,
298 agent_instance_hierarchy: String,
299 sender_agent_instance_hierarchy: String,
300 delivered_at: String,
301 response_id: String,
302 },
303 #[schemars(title = "ClientNotification")]
304 ClientNotification {
305 agent_instance_hierarchy: String,
306 sender_agent_instance_hierarchy: String,
309 response_id: String,
310 queued_at: String,
316 #[serde(default, skip_serializing_if = "Option::is_none")]
321 #[schemars(extend("omitempty" = true))]
322 key: Option<String>,
323 parts: Vec<ClientNotificationPart>,
324 },
325 #[schemars(title = "AssistantResponse")]
329 AssistantResponse {
330 agent_instance_hierarchy: String,
331 response_id: String,
332 parts: Vec<AssistantResponsePart>,
333 },
334 #[schemars(title = "ToolResponse")]
339 ToolResponse {
340 agent_instance_hierarchy: String,
341 response_id: String,
342 tool_call_id: String,
344 parts: Vec<ToolResponsePart>,
345 },
346}
347
348#[derive(clap::Args)]
349#[command(group(
350 clap::ArgGroup::new("logs_list_mode")
351 .required(true)
352 .multiple(false)
353 .args(["all", "pending"])
354))]
355pub struct Args {
356 #[arg(long)]
359 pub all: bool,
360 #[arg(long)]
363 pub pending: bool,
364 #[arg(long = "target", required = true)]
369 pub targets: Vec<String>,
370 #[arg(long)]
372 pub after_id: Option<i64>,
373 #[arg(long)]
375 pub limit: Option<i64>,
376 #[command(flatten)]
377 pub base: crate::cli::command::RequestBaseArgs,
378}
379
380#[derive(clap::Args)]
381#[command(args_conflicts_with_subcommands = true)]
382pub struct Command {
383 #[command(flatten)]
384 pub args: Args,
385 #[command(subcommand)]
386 pub schema: Option<Schema>,
387}
388
389#[derive(clap::Subcommand)]
390pub enum Schema {
391 RequestSchema(request_schema::Args),
393 ResponseSchema(response_schema::Args),
395}
396
397impl TryFrom<Args> for Request {
398 type Error = crate::cli::command::FromArgsError;
399 fn try_from(args: Args) -> Result<Self, Self::Error> {
400 let targets = args
401 .targets
402 .iter()
403 .map(|s| {
404 s.parse::<Target>().map_err(|msg| {
405 crate::cli::command::FromArgsError::path_parse("target", msg)
406 })
407 })
408 .collect::<Result<Vec<_>, _>>()?;
409 Ok(Self {
410 path_type: Path::AgentsLogsList,
411 pending: args.pending,
414 targets,
415 after_id: args.after_id,
416 limit: args.limit,
417 base: args.base.into(),
418 })
419 }
420}
421
422#[cfg(feature = "cli-executor")]
423pub async fn execute<E: crate::cli::command::CommandExecutor>(
424 executor: &E,
425 mut request: Request,
426
427 agent_arguments: Option<&crate::cli::command::AgentArguments>,
428 ) -> Result<E::Stream<ResponseItem>, E::Error> {
429 request.base.clear_transform();
430 executor.execute(request, agent_arguments).await
431}
432
433#[cfg(feature = "cli-executor")]
434pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
435 executor: &E,
436 mut request: Request,
437 transform: crate::cli::command::Transform,
438
439 agent_arguments: Option<&crate::cli::command::AgentArguments>,
440 ) -> Result<E::Stream<serde_json::Value>, E::Error> {
441 request.base.set_transform(transform);
442 executor.execute(request, agent_arguments).await
443}
444
445#[cfg(feature = "mcp")]
446impl crate::cli::command::CommandResponse for ResponseItem {
447 fn into_mcp(self) -> crate::cli::command::McpResponseItem {
448 crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
449 }
450}
451
452pub mod request_schema;
453
454
455pub mod response_schema;
456
457#[cfg(test)]
458mod tests {
459 use super::Target;
460
461 #[test]
462 fn me_target_parses_and_round_trips() {
463 assert_eq!("me".parse::<Target>(), Ok(Target::Me));
464 assert_eq!(" me ".parse::<Target>(), Ok(Target::Me));
466 assert_eq!(Target::Me.into_arg_string(), "me");
467 }
468
469 #[test]
470 fn me_is_mutually_exclusive_with_other_keys() {
471 assert!("me,instance=x".parse::<Target>().is_err());
474 }
475
476 #[test]
477 fn json_wire_shape_is_by_me() {
478 assert_eq!(
479 serde_json::to_value(Target::Me).unwrap(),
480 serde_json::json!({ "by": "me" }),
481 );
482 }
483}