use serde::{Deserialize, Serialize, Serializer};
use super::content::{DisplayBlock, ToolReturnValue};
use super::event::HookAction;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Request {
ApprovalRequest(ApprovalRequest),
ToolCallRequest(ToolCallRequest),
QuestionRequest(QuestionRequest),
HookRequest(HookRequest),
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
#[allow(clippy::enum_variant_names)]
pub(crate) enum FlatRequest {
ApprovalRequest(ApprovalRequest),
ToolCallRequest(ToolCallRequest),
QuestionRequest(QuestionRequest),
HookRequest(HookRequest),
}
impl From<Request> for FlatRequest {
fn from(req: Request) -> Self {
match req {
Request::ApprovalRequest(inner) => FlatRequest::ApprovalRequest(inner),
Request::ToolCallRequest(inner) => FlatRequest::ToolCallRequest(inner),
Request::QuestionRequest(inner) => FlatRequest::QuestionRequest(inner),
Request::HookRequest(inner) => FlatRequest::HookRequest(inner),
}
}
}
impl From<FlatRequest> for Request {
fn from(req: FlatRequest) -> Self {
match req {
FlatRequest::ApprovalRequest(inner) => Request::ApprovalRequest(inner),
FlatRequest::ToolCallRequest(inner) => Request::ToolCallRequest(inner),
FlatRequest::QuestionRequest(inner) => Request::QuestionRequest(inner),
FlatRequest::HookRequest(inner) => Request::HookRequest(inner),
}
}
}
#[derive(Serialize, Deserialize)]
struct RequestEnvelope {
#[serde(rename = "type")]
type_name: String,
payload: serde_json::Value,
}
impl Serialize for Request {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let flat = FlatRequest::from(self.clone());
let mut value = serde_json::to_value(&flat).map_err(serde::ser::Error::custom)?;
let obj = value
.as_object_mut()
.ok_or_else(|| serde::ser::Error::custom("expected object"))?;
let type_name = obj
.remove("type")
.and_then(|v| v.as_str().map(String::from))
.ok_or_else(|| serde::ser::Error::custom("missing type"))?;
RequestEnvelope { type_name, payload: value }.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Request {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let envelope = RequestEnvelope::deserialize(deserializer)?;
let mut value = envelope.payload;
if let Some(obj) = value.as_object_mut() {
obj.insert("type".to_string(), serde_json::Value::String(envelope.type_name));
}
let flat: FlatRequest = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
Ok(Request::from(flat))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ApprovalRequest {
pub id: String,
pub tool_call_id: String,
pub sender: String,
pub action: String,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub display: Option<Vec<DisplayBlock>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_kind: Option<SourceKind>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subagent_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum SourceKind {
ForegroundTurn,
BackgroundAgent,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolCallRequest {
pub id: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct QuestionRequest {
pub id: String,
pub tool_call_id: String,
pub questions: Vec<QuestionItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct QuestionItem {
pub question: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub header: Option<String>,
pub options: Vec<QuestionOption>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multi_select: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct QuestionOption {
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HookRequest {
pub id: String,
pub subscription_id: String,
pub event: String,
pub target: String,
pub input_data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ApprovalResponse {
pub request_id: String,
pub response: ApprovalResponseKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub feedback: Option<String>,
}
pub use crate::protocol::event::ApprovalResponseKind;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolCallResponse {
pub tool_call_id: String,
pub return_value: ToolReturnValue,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct QuestionResponse {
pub request_id: String,
pub answers: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HookResponse {
pub request_id: String,
pub action: HookAction,
pub reason: String,
}