use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use super::{Extra, Field, tagged_enum};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ControlRequest {
pub request_id: String,
pub request: ControlRequestBody,
#[serde(flatten)]
pub extra: Extra,
}
impl ControlRequest {
pub fn new(request_id: impl Into<String>, request: impl Into<ControlRequestBody>) -> Self {
Self {
request_id: request_id.into(),
request: request.into(),
extra: Extra::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ControlRequestBody {
Interrupt(InterruptRequest),
Initialize(InitializeRequest),
SetPermissionMode(SetPermissionModeRequest),
SetModel(SetModelRequest),
StopTask(StopTaskRequest),
CanUseTool(CanUseToolRequest),
HookCallback(HookCallbackRequest),
McpMessage(McpMessageRequest),
Other {
subtype: String,
raw: Value,
},
}
tagged_enum!(ControlRequestBody, key = "subtype", skip = [], other = subtype, {
"interrupt" => Interrupt,
"initialize" => Initialize,
"set_permission_mode" => SetPermissionMode,
"set_model" => SetModel,
"stop_task" => StopTask,
"can_use_tool" => CanUseTool,
"hook_callback" => HookCallback,
"mcp_message" => McpMessage,
});
macro_rules! into_body {
($($t:ident => $v:ident),* $(,)?) => {$(
impl From<$t> for ControlRequestBody {
fn from(r: $t) -> Self {
Self::$v(r)
}
}
)*};
}
into_body!(
InterruptRequest => Interrupt,
InitializeRequest => Initialize,
SetPermissionModeRequest => SetPermissionMode,
SetModelRequest => SetModel,
StopTaskRequest => StopTask,
CanUseToolRequest => CanUseTool,
HookCallbackRequest => HookCallback,
McpMessageRequest => McpMessage,
);
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct InterruptRequest {
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct InitializeRequest {
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub hooks: Field<Map<String, Value>>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub agents: Field<Map<String, Value>>,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct SetPermissionModeRequest {
pub mode: String,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct SetModelRequest {
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub model: Field<String>,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct StopTaskRequest {
pub task_id: String,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct CanUseToolRequest {
pub tool_name: String,
pub input: Value,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub permission_suggestions: Field<Vec<Value>>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub blocked_path: Field<String>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub decision_reason: Field<String>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub title: Field<String>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub display_name: Field<String>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub description: Field<String>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub tool_use_id: Field<String>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub agent_id: Field<String>,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct HookCallbackRequest {
pub callback_id: String,
pub input: Value,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub tool_use_id: Field<String>,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct McpMessageRequest {
pub server_name: String,
pub message: Value,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ControlResponse {
pub response: ControlResponseBody,
#[serde(flatten)]
pub extra: Extra,
}
impl ControlResponse {
pub fn success(request_id: impl Into<String>, response: Option<Value>) -> Self {
Self::of(ControlResponseBody::Success(ControlSuccess {
request_id: request_id.into(),
response: response.into(),
extra: Extra::new(),
}))
}
pub fn error(request_id: impl Into<String>, error: impl Into<String>) -> Self {
Self::of(ControlResponseBody::Error(ControlError {
request_id: request_id.into(),
error: error.into(),
extra: Extra::new(),
}))
}
pub fn permission(
request_id: impl Into<String>,
result: &PermissionResult,
) -> Result<Self, serde_json::Error> {
let v = serde_json::to_value(result)?;
Ok(Self::success(request_id, Some(v)))
}
fn of(response: ControlResponseBody) -> Self {
Self {
response,
extra: Map::new(),
}
}
pub fn request_id(&self) -> Option<&str> {
match &self.response {
ControlResponseBody::Success(s) => Some(&s.request_id),
ControlResponseBody::Error(e) => Some(&e.request_id),
ControlResponseBody::Other { raw, .. } => raw.get("request_id")?.as_str(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ControlResponseBody {
Success(ControlSuccess),
Error(ControlError),
Other {
subtype: String,
raw: Value,
},
}
tagged_enum!(ControlResponseBody, key = "subtype", skip = [], other = subtype, {
"success" => Success,
"error" => Error,
});
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ControlSuccess {
pub request_id: String,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub response: Field<Value>,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ControlError {
pub request_id: String,
pub error: String,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ControlCancelRequest {
pub request_id: String,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "behavior", rename_all = "snake_case")]
#[non_exhaustive]
pub enum PermissionResult {
Allow(PermissionAllow),
Deny(PermissionDeny),
}
impl PermissionResult {
pub fn allow(input: Value) -> Self {
Self::Allow(PermissionAllow {
updated_input: input,
updated_permissions: None,
extra: Extra::new(),
})
}
pub fn deny(message: impl Into<String>) -> Self {
Self::Deny(PermissionDeny {
message: message.into(),
interrupt: None,
extra: Extra::new(),
})
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct PermissionAllow {
#[serde(rename = "updatedInput")]
pub updated_input: Value,
#[serde(
default,
rename = "updatedPermissions",
skip_serializing_if = "Option::is_none"
)]
pub updated_permissions: Option<Vec<Value>>,
#[serde(flatten)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct PermissionDeny {
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub interrupt: Option<bool>,
#[serde(flatten)]
pub extra: Extra,
}