feat: handle Elicitation and ElicitationResult hook events (#223)

Parses [Elicitation Hook] and [ElicitationResult Hook] from Claude Code
stderr, emits claude:elicitation and claude:elicitation-result Tauri
events, and renders an ElicitationModal for MCP server input requests.
This commit is contained in:
2026-03-20 09:21:32 -07:00
committed by Naomi Carrigan
parent 8220ab6b85
commit efdc7af58a
8 changed files with 569 additions and 6 deletions
+79
View File
@@ -280,6 +280,26 @@ pub struct UserQuestionEvent {
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitationEvent {
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub server_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitationResultEvent {
pub action: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentStartEvent {
pub tool_use_id: String,
@@ -566,4 +586,63 @@ mod tests {
panic!("Expected RateLimitEvent variant");
}
}
#[test]
fn test_elicitation_event_serialization() {
let event = ElicitationEvent {
message: "Please provide the API endpoint".to_string(),
server_name: Some("my-server".to_string()),
request_id: Some("req-123".to_string()),
conversation_id: Some("conv-abc".to_string()),
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"message\":\"Please provide the API endpoint\""));
assert!(serialized.contains("\"server_name\":\"my-server\""));
assert!(serialized.contains("\"request_id\":\"req-123\""));
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
}
#[test]
fn test_elicitation_event_omits_none_fields() {
let event = ElicitationEvent {
message: "Enter your token".to_string(),
server_name: None,
request_id: None,
conversation_id: None,
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"message\":\"Enter your token\""));
assert!(!serialized.contains("server_name"));
assert!(!serialized.contains("request_id"));
assert!(!serialized.contains("conversation_id"));
}
#[test]
fn test_elicitation_result_event_serialization() {
let event = ElicitationResultEvent {
action: "accept".to_string(),
request_id: Some("req-123".to_string()),
conversation_id: Some("conv-abc".to_string()),
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"action\":\"accept\""));
assert!(serialized.contains("\"request_id\":\"req-123\""));
}
#[test]
fn test_elicitation_result_event_cancel_omits_none_fields() {
let event = ElicitationResultEvent {
action: "cancel".to_string(),
request_id: None,
conversation_id: None,
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"action\":\"cancel\""));
assert!(!serialized.contains("request_id"));
assert!(!serialized.contains("conversation_id"));
}
}