From e9c324f7109b29436d62ec32c5618bb90c7575c3 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 7 Jul 2026 13:45:59 +0200 Subject: [PATCH] acp: Show descriptions for elicitation options (#60527) image Release Notes: - N/A --- Cargo.lock | 12 +- Cargo.toml | 2 +- .../src/conversation_view/elicitation.rs | 117 ++++++++++++++---- 3 files changed, 102 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ada6ef9e5..c0711dee73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,9 +322,9 @@ dependencies = [ [[package]] name = "agent-client-protocol" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16302d16c7531355db16593d99c38c8297db0c4653aa7dd80c3556bb17f4cd8c" +checksum = "ed34c8297a72c9d99fc7493ba6370f45c69fdcd51fdfb469d80f266cf63e5a98" dependencies = [ "agent-client-protocol-derive", "agent-client-protocol-schema", @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "agent-client-protocol-derive" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b37d552feb6981a0109febda6b71fc723678cd065974c2d76279c19c407095" +checksum = "0de0877cd41073fa0a124edb972df0d02e88c70adde11f49cf2231fd63aae738" dependencies = [ "quote", "syn 2.0.117", @@ -354,9 +354,9 @@ dependencies = [ [[package]] name = "agent-client-protocol-schema" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac542aba230234b1591ace7286a47c0514fe3efc3037d43296bde31ba7ee5728" +checksum = "06679e1542356341f4550ccfb16338b64f37f6af70de2105446ef6fbb078234c" dependencies = [ "anyhow", "derive_more", diff --git a/Cargo.toml b/Cargo.toml index 15c7b4401a..3611fce865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -508,7 +508,7 @@ accesskit = "0.24.0" accesskit_macos = "0.26.0" accesskit_unix = "0.21.0" accesskit_windows = "0.33.1" -agent-client-protocol = { version = "=1.0.1", features = ["unstable"] } +agent-client-protocol = { version = "=1.1.0", features = ["unstable"] } aho-corasick = "1.1" alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "4c129667ce56611becdc82de6e28218c80e2e88f" } any_vec = "0.14" diff --git a/crates/agent_ui/src/conversation_view/elicitation.rs b/crates/agent_ui/src/conversation_view/elicitation.rs index ae3f3a81b8..3eaaa0652b 100644 --- a/crates/agent_ui/src/conversation_view/elicitation.rs +++ b/crates/agent_ui/src/conversation_view/elicitation.rs @@ -16,6 +16,7 @@ use ui::{ struct ElicitationOption { value: String, label: SharedString, + description: Option, } enum ElicitationFieldState { @@ -429,6 +430,51 @@ mod tests { ); } + #[test] + fn single_select_options_include_titled_descriptions() { + let schema = acp::StringPropertySchema::new().one_of(vec![ + acp::EnumOption::new("production", "Production").description("Use live resources"), + ]); + + let options = single_select_options(&schema); + + let [option] = options.as_slice() else { + panic!("expected one option, got {}", options.len()); + }; + assert_eq!(option.value, "production"); + assert_eq!(option.label.to_string(), "Production"); + assert_eq!( + option + .description + .as_ref() + .map(|description| description.to_string()), + Some("Use live resources".to_string()) + ); + } + + #[test] + fn multi_select_options_include_titled_descriptions() { + let schema = acp::MultiSelectPropertySchema::titled(vec![ + acp::EnumOption::new("repository", "Repository Access") + .description("Read and update repositories"), + ]); + + let options = multi_select_options(&schema); + + let [option] = options.as_slice() else { + panic!("expected one option, got {}", options.len()); + }; + assert_eq!(option.value, "repository"); + assert_eq!(option.label.to_string(), "Repository Access"); + assert_eq!( + option + .description + .as_ref() + .map(|description| description.to_string()), + Some("Read and update repositories".to_string()) + ); + } + #[gpui::test] fn form_state_preserves_string_whitespace(cx: &mut TestAppContext) { crate::conversation_view::tests::init_test(cx); @@ -773,8 +819,10 @@ fn preview_form_schema() -> acp::ElicitationSchema { .title("Environment") .description("Select the environment this credential should target.") .one_of(vec![ - acp::EnumOption::new("production", "Production"), - acp::EnumOption::new("staging", "Staging"), + acp::EnumOption::new("production", "Production") + .description("Use the live account and production resources."), + acp::EnumOption::new("staging", "Staging") + .description("Validate changes against staging data first."), acp::EnumOption::new("development", "Development"), ]) .default_value("staging"), @@ -783,8 +831,10 @@ fn preview_form_schema() -> acp::ElicitationSchema { .property( "scopes", acp::MultiSelectPropertySchema::titled(vec![ - acp::EnumOption::new("profile", "Profile"), - acp::EnumOption::new("repository", "Repository Access"), + acp::EnumOption::new("profile", "Profile") + .description("Read account identity and basic profile details."), + acp::EnumOption::new("repository", "Repository Access") + .description("Read and update repositories connected to this account."), acp::EnumOption::new("terminal", "Terminal Commands"), ]) .title("Access") @@ -810,6 +860,7 @@ fn single_select_options(schema: &acp::StringPropertySchema) -> Vec Vec Vec { match &schema.items { - acp::MultiSelectItems::Untitled(items) => items + acp::MultiSelectItems::String(items) => items .values .iter() .map(|value| ElicitationOption { value: value.clone(), label: SharedString::from(value.clone()), + description: None, }) .collect(), acp::MultiSelectItems::Titled(items) => items @@ -857,6 +910,7 @@ fn multi_select_options(schema: &acp::MultiSelectPropertySchema) -> Vec Vec::new(), @@ -1310,11 +1364,7 @@ impl<'a> ElicitationCard<'a> { cx, ); }) - .child( - div() - .mt_0p5() - .child(Checkbox::new(checkbox_id, checkbox_state)), - ) + .child(div().child(Checkbox::new(checkbox_id, checkbox_state))) .child( v_flex() .gap_0p5() @@ -1412,8 +1462,8 @@ impl<'a> ElicitationCard<'a> { ))) .w_full() .min_h(rems_from_px(28.)) - .items_center() - .gap_2() + .items_start() + .gap_1p5() .rounded_sm() .border_1() .border_color(field_border_color.opacity(0.5)) @@ -1430,8 +1480,8 @@ impl<'a> ElicitationCard<'a> { cx, ); }) - .child(Checkbox::new(checkbox_id, checkbox_state)) - .child(Label::new(option.label).size(LabelSize::Small).truncate()) + .child(div().child(Checkbox::new(checkbox_id, checkbox_state))) + .child(Self::render_option_content(option)) })) .into_any_element() } @@ -1479,8 +1529,8 @@ impl<'a> ElicitationCard<'a> { .id(option_id) .w_full() .min_h(rems_from_px(28.)) - .items_center() - .gap_2() + .items_start() + .gap_1p5() .rounded_sm() .border_1() .border_color(border_color.opacity(0.5)) @@ -1496,16 +1546,39 @@ impl<'a> ElicitationCard<'a> { cx, ); }) - .child(Self::render_radio_indicator( - is_selected, - border_color, - control_background, - )) - .child(Label::new(option.label).size(LabelSize::Small).truncate()) + .child( + div() + .size(Checkbox::container_size()) + .flex_none() + .flex() + .items_center() + .justify_center() + .child(Self::render_radio_indicator( + is_selected, + border_color, + control_background, + )), + ) + .child(Self::render_option_content(option)) })) .into_any_element() } + fn render_option_content(option: ElicitationOption) -> Div { + v_flex() + .min_w_0() + .flex_1() + .gap_0p5() + .child(Label::new(option.label).size(LabelSize::Small).truncate()) + .when_some(option.description, |this, description| { + this.child( + Label::new(description) + .size(LabelSize::Small) + .color(Color::Muted), + ) + }) + } + fn option_row_background(is_selected: bool, cx: &App) -> Hsla { let editor_background = cx.theme().colors().editor_background; if is_selected {