use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitationParams {
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<Value>,
#[serde(default)]
pub vox_eligible: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub vox_channel_hint: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ElicitationAction {
Accept,
Decline,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ElicitationSource {
Tui,
Vox,
Api,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitationResult {
pub action: ElicitationAction,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<ElicitationSource>,
}
impl crate::HostProxy {
pub async fn elicit(&self, params: ElicitationParams) -> crate::Result<ElicitationResult> {
let value = self
.request("elicitation/request", serde_json::to_value(¶ms)?)
.await?;
serde_json::from_value(value).map_err(|e| crate::Error::parse_error(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_elicitation_params_roundtrip() {
let params = ElicitationParams {
message: "Which Slack workspace should I connect to?".to_string(),
schema: Some(serde_json::json!({
"type": "object",
"properties": {
"workspace": {
"type": "string",
"enum": ["styrene-labs", "client-workspace"]
},
"confirm": { "type": "boolean" }
},
"required": ["workspace"]
})),
timeout_ms: Some(60000),
default: Some(serde_json::json!({"workspace": "styrene-labs"})),
vox_eligible: true,
vox_channel_hint: Some("discord".to_string()),
};
let json = serde_json::to_string(¶ms).unwrap();
let parsed: ElicitationParams = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.message, "Which Slack workspace should I connect to?");
assert!(parsed.vox_eligible);
assert_eq!(parsed.vox_channel_hint.as_deref(), Some("discord"));
assert_eq!(parsed.timeout_ms, Some(60000));
}
#[test]
fn test_elicitation_params_minimal() {
let json = r#"{"message":"Continue?"}"#;
let parsed: ElicitationParams = serde_json::from_str(json).unwrap();
assert_eq!(parsed.message, "Continue?");
assert!(!parsed.vox_eligible);
assert!(parsed.schema.is_none());
assert!(parsed.default.is_none());
}
#[test]
fn test_elicitation_result_accept() {
let result = ElicitationResult {
action: ElicitationAction::Accept,
content: Some(serde_json::json!({"workspace": "styrene-labs", "confirm": true})),
source: Some(ElicitationSource::Tui),
};
let json = serde_json::to_string(&result).unwrap();
let parsed: ElicitationResult = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.action, ElicitationAction::Accept);
assert!(parsed.content.is_some());
assert_eq!(parsed.source, Some(ElicitationSource::Tui));
}
#[test]
fn test_elicitation_result_decline() {
let result = ElicitationResult {
action: ElicitationAction::Decline,
content: None,
source: None,
};
let json = serde_json::to_string(&result).unwrap();
let parsed: ElicitationResult = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.action, ElicitationAction::Decline);
assert!(parsed.content.is_none());
assert!(parsed.source.is_none());
}
#[test]
fn test_elicitation_result_via_vox() {
let result = ElicitationResult {
action: ElicitationAction::Accept,
content: Some(serde_json::json!("yes")),
source: Some(ElicitationSource::Vox),
};
let json = serde_json::to_string(&result).unwrap();
let parsed: ElicitationResult = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.source, Some(ElicitationSource::Vox));
}
#[test]
fn test_action_serialization() {
assert_eq!(
serde_json::to_string(&ElicitationAction::Accept).unwrap(),
r#""accept""#
);
assert_eq!(
serde_json::to_string(&ElicitationAction::Decline).unwrap(),
r#""decline""#
);
}
#[test]
fn test_source_serialization() {
assert_eq!(
serde_json::to_string(&ElicitationSource::Tui).unwrap(),
r#""tui""#
);
assert_eq!(
serde_json::to_string(&ElicitationSource::Vox).unwrap(),
r#""vox""#
);
assert_eq!(
serde_json::to_string(&ElicitationSource::Api).unwrap(),
r#""api""#
);
}
}