use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AgentDefinition {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub system_prompt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default)]
pub tool_allowlist: Vec<String>,
}
impl AgentDefinition {
pub fn new(name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
Self {
name: name.into(),
description: None,
system_prompt: system_prompt.into(),
model: None,
tool_allowlist: Vec::new(),
}
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
pub fn with_tools(mut self, tools: Vec<String>) -> Self {
self.tool_allowlist = tools;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ControlRequest {
#[serde(rename = "permission_check")]
PermissionCheck(ToolPermissionRequest),
#[serde(rename = "hook")]
Hook {
event_type: String,
data: serde_json::Value,
},
#[serde(rename = "permission_mode")]
PermissionModeChange {
mode: PermissionMode,
},
#[serde(rename = "interrupt")]
Interrupt,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolPermissionRequest {
pub tool: String,
pub input: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub cli_suggestion: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ControlResponse {
pub request_id: String,
pub approved: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub modified_input: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PermissionResponse {
pub allow: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub modified_input: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub permission_request_suggestion: Option<String>,
}
impl PermissionResponse {
pub fn allow() -> Self {
Self {
allow: true,
modified_input: None,
permission_request_suggestion: None,
}
}
pub fn deny() -> Self {
Self {
allow: false,
modified_input: None,
permission_request_suggestion: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum HookEvent {
#[serde(rename = "pre_tool_use")]
PreToolUse {
tool: ToolHookData,
},
#[serde(rename = "post_tool_use")]
PostToolUse {
tool: ToolHookData,
result: ToolResultHookData,
},
#[serde(rename = "user_prompt_submit")]
UserPromptSubmit {
prompt: String,
},
#[serde(rename = "stop")]
Stop,
#[serde(rename = "subagent_stop")]
SubagentStop,
#[serde(rename = "pre_compact")]
PreCompact,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolHookData {
pub id: String,
pub name: String,
pub input: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolResultHookData {
pub tool_use_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(default)]
pub is_error: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HookResponse {
pub continue_: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub modified_inputs: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<String>,
#[serde(default)]
pub hide_from_transcript: bool,
}
impl HookResponse {
pub fn continue_exec() -> Self {
Self {
continue_: true,
modified_inputs: None,
context: None,
hide_from_transcript: false,
}
}
pub fn stop() -> Self {
Self {
continue_: false,
modified_inputs: None,
context: None,
hide_from_transcript: false,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PermissionMode {
Default,
AcceptEdits,
BypassPermissions,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_definition() {
let agent = AgentDefinition::new("DataAnalyzer", "Analyze data")
.with_description("Analyzes datasets")
.with_model("claude-3-5-sonnet")
.with_tools(vec!["bash".to_string(), "file_editor".to_string()]);
assert_eq!(agent.name, "DataAnalyzer");
assert_eq!(agent.tool_allowlist.len(), 2);
}
#[test]
fn test_permission_response_serialization() {
let resp = PermissionResponse::allow();
let json = serde_json::to_string(&resp).unwrap();
let deserialized: PermissionResponse = serde_json::from_str(&json).unwrap();
assert_eq!(resp, deserialized);
}
#[test]
fn test_hook_response() {
let resp = HookResponse::continue_exec();
assert!(resp.continue_);
let resp = HookResponse::stop();
assert!(!resp.continue_);
}
}