From 77cecdb79933163eae12455c4d7224b2d44a9426 Mon Sep 17 00:00:00 2001 From: Loren Phillips Date: Wed, 27 May 2026 15:27:23 -0700 Subject: [PATCH] fix(run-templates): send plural array IDs to match API schema The v1 API requires agent_ids/persona_ids/test_set_ids as arrays (min length 1), but the CLI was sending the singular variants and getting 400 INVALID_ARGUMENT on every create/update call. Flags are now repeatable or comma-delimited: coval run-templates create \ --agent-ids agentA,agentB \ --persona-ids p1 --persona-ids p2 \ --test-set-ids ts1 --- src/client/models/run_template.rs | 47 +++++++++++++++++-------------- src/commands/run_templates.rs | 36 +++++++++++------------ 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/src/client/models/run_template.rs b/src/client/models/run_template.rs index 44665ca..7e8e5cd 100644 --- a/src/client/models/run_template.rs +++ b/src/client/models/run_template.rs @@ -12,18 +12,26 @@ fn truncate(s: &str, max: usize) -> String { } } +fn summarize_ids(ids: &[String]) -> String { + match ids.len() { + 0 => String::new(), + 1 => ids[0].clone(), + n => format!("{} (+{})", ids[0], n - 1), + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RunTemplate { pub id: String, pub display_name: String, #[serde(default)] pub description: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub persona_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub test_set_id: Option, + #[serde(default)] + pub agent_ids: Vec, + #[serde(default)] + pub persona_ids: Vec, + #[serde(default)] + pub test_set_ids: Vec, #[serde(default)] pub metric_ids: Vec, #[serde(default)] @@ -50,12 +58,9 @@ pub struct CreateRunTemplateRequest { pub display_name: String, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub persona_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub test_set_id: Option, + pub agent_ids: Vec, + pub persona_ids: Vec, + pub test_set_ids: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub metric_ids: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -79,11 +84,11 @@ pub struct UpdateRunTemplateRequest { #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub agent_id: Option, + pub agent_ids: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub persona_id: Option, + pub persona_ids: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub test_set_id: Option, + pub test_set_ids: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub metric_ids: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -126,9 +131,9 @@ impl Tabular for RunTemplate { vec![ "ID", "NAME", - "AGENT", - "PERSONA", - "TEST SET", + "AGENTS", + "PERSONAS", + "TEST SETS", "ITERATIONS", "CONCURRENCY", ] @@ -138,9 +143,9 @@ impl Tabular for RunTemplate { vec![ self.id.clone(), truncate(&self.display_name, 25), - self.agent_id.clone().unwrap_or_default(), - self.persona_id.clone().unwrap_or_default(), - self.test_set_id.clone().unwrap_or_default(), + summarize_ids(&self.agent_ids), + summarize_ids(&self.persona_ids), + summarize_ids(&self.test_set_ids), self.iteration_count .map(|c| c.to_string()) .unwrap_or_default(), diff --git a/src/commands/run_templates.rs b/src/commands/run_templates.rs index 366b5f9..8fa1aab 100644 --- a/src/commands/run_templates.rs +++ b/src/commands/run_templates.rs @@ -55,12 +55,12 @@ pub struct CreateArgs { name: Option, #[arg(long)] description: Option, - #[arg(long)] - agent_id: Option, - #[arg(long)] - persona_id: Option, - #[arg(long)] - test_set_id: Option, + #[arg(long, value_delimiter = ',')] + agent_ids: Option>, + #[arg(long, value_delimiter = ',')] + persona_ids: Option>, + #[arg(long, value_delimiter = ',')] + test_set_ids: Option>, #[arg(long, value_delimiter = ',')] metric_ids: Option>, #[arg(long, value_delimiter = ',')] @@ -84,12 +84,12 @@ pub struct UpdateArgs { name: Option, #[arg(long)] description: Option, - #[arg(long)] - agent_id: Option, - #[arg(long)] - persona_id: Option, - #[arg(long)] - test_set_id: Option, + #[arg(long, value_delimiter = ',')] + agent_ids: Option>, + #[arg(long, value_delimiter = ',')] + persona_ids: Option>, + #[arg(long, value_delimiter = ',')] + test_set_ids: Option>, #[arg(long, value_delimiter = ',')] metric_ids: Option>, #[arg(long, value_delimiter = ',')] @@ -155,9 +155,9 @@ pub async fn execute( let mut input = args.input_json.object()?; input_json::insert(&mut input, "display_name", args.name)?; input_json::insert(&mut input, "description", args.description)?; - input_json::insert(&mut input, "agent_id", args.agent_id)?; - input_json::insert(&mut input, "persona_id", args.persona_id)?; - input_json::insert(&mut input, "test_set_id", args.test_set_id)?; + input_json::insert(&mut input, "agent_ids", args.agent_ids)?; + input_json::insert(&mut input, "persona_ids", args.persona_ids)?; + input_json::insert(&mut input, "test_set_ids", args.test_set_ids)?; input_json::insert(&mut input, "metric_ids", args.metric_ids)?; input_json::insert(&mut input, "mutation_ids", args.mutation_ids)?; input_json::insert(&mut input, "iteration_count", args.iteration_count)?; @@ -178,9 +178,9 @@ pub async fn execute( let mut input = args.input_json.object()?; input_json::insert(&mut input, "display_name", args.name)?; input_json::insert(&mut input, "description", args.description)?; - input_json::insert(&mut input, "agent_id", args.agent_id)?; - input_json::insert(&mut input, "persona_id", args.persona_id)?; - input_json::insert(&mut input, "test_set_id", args.test_set_id)?; + input_json::insert(&mut input, "agent_ids", args.agent_ids)?; + input_json::insert(&mut input, "persona_ids", args.persona_ids)?; + input_json::insert(&mut input, "test_set_ids", args.test_set_ids)?; input_json::insert(&mut input, "metric_ids", args.metric_ids)?; input_json::insert(&mut input, "mutation_ids", args.mutation_ids)?; input_json::insert(&mut input, "iteration_count", args.iteration_count)?;