Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 72 additions & 25 deletions codex-rs/tui/src/bottom_pane/mcp_server_elicitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ pub(crate) struct McpServerElicitationFormRequest {
thread_id: ThreadId,
server_name: String,
request_id: McpRequestId,
is_high_risk: bool,
message: String,
subtitle: Option<String>,
approval_display_params: Vec<McpToolApprovalDisplayParam>,
response_mode: McpServerElicitationResponseMode,
fields: Vec<McpServerElicitationField>,
Expand Down Expand Up @@ -264,6 +266,20 @@ impl McpServerElicitationFormRequest {
message: String,
requested_schema: Value,
) -> Option<Self> {
let is_high_risk = meta
.as_ref()
.and_then(Value::as_object)
.and_then(|meta| meta.get("riskLevel"))
.and_then(Value::as_str)
== Some("high");
let subtitle = meta
.as_ref()
.and_then(Value::as_object)
.and_then(|meta| meta.get("subtitle"))
.and_then(Value::as_str)
.map(str::trim)
.filter(|subtitle| !subtitle.is_empty())
.map(str::to_string);
let tool_suggestion = parse_tool_suggestion_request(meta.as_ref());
let is_tool_approval = meta
.as_ref()
Expand Down Expand Up @@ -367,7 +383,9 @@ impl McpServerElicitationFormRequest {
thread_id,
server_name,
request_id,
is_high_risk,
message,
subtitle,
approval_display_params,
response_mode,
fields,
Expand Down Expand Up @@ -946,25 +964,42 @@ impl McpServerElicitationOverlay {
.collect()
}

fn wrapped_prompt_lines(&self, width: u16) -> Vec<String> {
textwrap::wrap(&self.current_prompt_text(), width.max(1) as usize)
.into_iter()
.map(|line| line.to_string())
.collect()
}

fn current_prompt_text(&self) -> String {
fn prompt_lines(&self, width: u16, answered: bool) -> Vec<Line<'static>> {
let wrap_width = width.max(1) as usize;
let mut lines = Vec::new();
let request_message = format_tool_approval_display_message(
&self.request.message,
&self.request.approval_display_params,
);
let Some(field) = self.current_field() else {
return request_message;
};
let mut sections = Vec::new();
if !request_message.trim().is_empty() {
sections.push(request_message);
for (idx, line) in textwrap::wrap(&request_message, wrap_width)
.into_iter()
.enumerate()
{
let text = if self.request.is_high_risk && idx == 0 {
format!("⚠ {line}")
} else {
line.to_string()
};
let line = if self.request.is_high_risk {
Line::from(text).red()
} else if answered {
Line::from(text)
} else {
Line::from(text).cyan()
};
lines.push(line);
}
}
if let Some(subtitle) = self.request.subtitle.as_deref() {
for line in textwrap::wrap(subtitle, wrap_width) {
lines.push(Line::from(line.to_string()));
}
}

let Some(field) = self.current_field() else {
return lines;
};
let field_prompt = if field.label.trim().is_empty()
|| field.prompt.trim().is_empty()
|| field.label == field.prompt
Expand All @@ -978,9 +1013,19 @@ impl McpServerElicitationOverlay {
format!("{}\n{}", field.label, field.prompt)
};
if !field_prompt.trim().is_empty() {
sections.push(field_prompt);
if !lines.is_empty() {
lines.push(Line::from(String::new()));
}
for line in textwrap::wrap(&field_prompt, wrap_width) {
let line = if answered {
Line::from(line.to_string())
} else {
Line::from(line.to_string()).cyan()
};
lines.push(line);
}
}
sections.join("\n\n")
lines
}

fn footer_tips(&self) -> Vec<FooterTip> {
Expand Down Expand Up @@ -1268,17 +1313,12 @@ impl McpServerElicitationOverlay {
return;
}
let answered = self.is_current_field_answered();
for (offset, line) in self.wrapped_prompt_lines(area.width).iter().enumerate() {
for (offset, line) in self.prompt_lines(area.width, answered).iter().enumerate() {
let y = area.y.saturating_add(offset as u16);
if y >= area.y + area.height {
break;
}
let line = if answered {
Line::from(line.clone())
} else {
Line::from(line.clone()).cyan()
};
Paragraph::new(line).render(
Paragraph::new(line.clone()).render(
Rect {
x: area.x,
y,
Expand Down Expand Up @@ -1371,8 +1411,9 @@ impl Renderable for McpServerElicitationOverlay {
let outer = Rect::new(0, 0, width, u16::MAX);
let inner = menu_surface_inset(outer);
let inner_width = inner.width.max(1);
let answered = self.is_current_field_answered();
let height = 1u16
.saturating_add(self.wrapped_prompt_lines(inner_width).len() as u16)
.saturating_add(self.prompt_lines(inner_width, answered).len() as u16)
.saturating_add(self.input_height(inner_width))
.saturating_add(self.footer_tip_lines(inner_width).len() as u16)
.saturating_add(menu_surface_padding_height());
Expand All @@ -1387,7 +1428,7 @@ impl Renderable for McpServerElicitationOverlay {
if content_area.width == 0 || content_area.height == 0 {
return;
}
let prompt_lines = self.wrapped_prompt_lines(content_area.width);
let prompt_lines = self.prompt_lines(content_area.width, self.is_current_field_answered());
let footer_lines = self.footer_tip_lines(content_area.width);
let mut remaining = content_area.height;

Expand Down Expand Up @@ -1461,7 +1502,7 @@ impl Renderable for McpServerElicitationOverlay {
if content_area.width == 0 || content_area.height == 0 {
return None;
}
let prompt_lines = self.wrapped_prompt_lines(content_area.width);
let prompt_lines = self.prompt_lines(content_area.width, self.is_current_field_answered());
let footer_lines = self.footer_tip_lines(content_area.width);
let mut remaining = content_area.height;
remaining = remaining.saturating_sub(u16::from(remaining > 0));
Expand Down Expand Up @@ -1813,7 +1854,9 @@ mod tests {
thread_id,
server_name: "server-1".to_string(),
request_id: McpRequestId::String("request-1".to_string()),
is_high_risk: false,
message: "Allow this request?".to_string(),
subtitle: None,
approval_display_params: Vec::new(),
response_mode: McpServerElicitationResponseMode::FormContent,
fields: vec![McpServerElicitationField {
Expand Down Expand Up @@ -1879,7 +1922,9 @@ mod tests {
thread_id,
server_name: "server-1".to_string(),
request_id: McpRequestId::String("request-1".to_string()),
is_high_risk: false,
message: "Allow this request?".to_string(),
subtitle: None,
approval_display_params: Vec::new(),
response_mode: McpServerElicitationResponseMode::ApprovalAction,
fields: vec![McpServerElicitationField {
Expand Down Expand Up @@ -1936,7 +1981,9 @@ mod tests {
thread_id,
server_name: "server-1".to_string(),
request_id: McpRequestId::String("request-1".to_string()),
is_high_risk: false,
message: "Allow this request?".to_string(),
subtitle: None,
approval_display_params: Vec::new(),
response_mode: McpServerElicitationResponseMode::ApprovalAction,
fields: vec![McpServerElicitationField {
Expand Down
Loading