use futures::future::BoxFuture;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
pub type CanUseToolCallback = Arc<
dyn Fn(String, serde_json::Value, ToolPermissionContext) -> BoxFuture<'static, PermissionResult>
+ Send
+ Sync,
>;
#[derive(Debug, Clone, Default)]
pub struct ToolPermissionContext {
pub signal: Option<()>,
pub suggestions: Vec<PermissionUpdate>,
pub tool_use_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "behavior", rename_all = "lowercase")]
pub enum PermissionResult {
Allow(PermissionResultAllow),
Deny(PermissionResultDeny),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PermissionResultAllow {
#[serde(skip_serializing_if = "Option::is_none", rename = "updatedInput")]
pub updated_input: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none", rename = "updatedPermissions")]
pub updated_permissions: Option<Vec<PermissionUpdate>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionResultDeny {
pub message: String,
pub interrupt: bool,
}
impl Default for PermissionResultDeny {
fn default() -> Self {
Self {
message: "Tool use denied".to_string(),
interrupt: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionUpdate {
#[serde(rename = "type")]
pub type_: PermissionUpdateType,
#[serde(skip_serializing_if = "Option::is_none")]
pub rules: Option<Vec<PermissionRuleValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub behavior: Option<PermissionBehavior>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<super::config::PermissionMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub directories: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destination: Option<PermissionUpdateDestination>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum PermissionUpdateType {
AddRules,
ReplaceRules,
RemoveRules,
SetMode,
AddDirectories,
RemoveDirectories,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRuleValue {
#[serde(rename = "toolName")]
pub tool_name: String,
#[serde(skip_serializing_if = "Option::is_none", rename = "ruleContent")]
pub rule_content: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum PermissionBehavior {
Allow,
Deny,
Ask,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum PermissionUpdateDestination {
UserSettings,
ProjectSettings,
LocalSettings,
#[serde(rename = "session")]
Session,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_permission_behavior_serialization() {
assert_eq!(
serde_json::to_string(&PermissionBehavior::Allow).unwrap(),
"\"allow\""
);
assert_eq!(
serde_json::to_string(&PermissionBehavior::Deny).unwrap(),
"\"deny\""
);
assert_eq!(
serde_json::to_string(&PermissionBehavior::Ask).unwrap(),
"\"ask\""
);
}
#[test]
fn test_permission_result_allow_serialization() {
let result = PermissionResult::Allow(PermissionResultAllow {
updated_input: Some(json!({"modified": true})),
updated_permissions: None,
});
let json = serde_json::to_value(&result).unwrap();
assert_eq!(json["behavior"], "allow");
assert_eq!(json["updatedInput"]["modified"], true);
}
#[test]
fn test_permission_result_deny_serialization() {
let result = PermissionResult::Deny(PermissionResultDeny {
message: "Access denied".to_string(),
interrupt: true,
});
let json = serde_json::to_value(&result).unwrap();
assert_eq!(json["behavior"], "deny");
assert_eq!(json["message"], "Access denied");
assert_eq!(json["interrupt"], true);
}
#[test]
fn test_permission_update_type_serialization() {
assert_eq!(
serde_json::to_string(&PermissionUpdateType::AddRules).unwrap(),
"\"addRules\""
);
assert_eq!(
serde_json::to_string(&PermissionUpdateType::SetMode).unwrap(),
"\"setMode\""
);
assert_eq!(
serde_json::to_string(&PermissionUpdateType::RemoveDirectories).unwrap(),
"\"removeDirectories\""
);
}
#[test]
fn test_permission_update_destination_serialization() {
assert_eq!(
serde_json::to_string(&PermissionUpdateDestination::UserSettings).unwrap(),
"\"userSettings\""
);
assert_eq!(
serde_json::to_string(&PermissionUpdateDestination::ProjectSettings).unwrap(),
"\"projectSettings\""
);
assert_eq!(
serde_json::to_string(&PermissionUpdateDestination::LocalSettings).unwrap(),
"\"localSettings\""
);
assert_eq!(
serde_json::to_string(&PermissionUpdateDestination::Session).unwrap(),
"\"session\""
);
}
#[test]
fn test_permission_update_with_rules() {
let update = PermissionUpdate {
type_: PermissionUpdateType::AddRules,
rules: Some(vec![PermissionRuleValue {
tool_name: "Bash".to_string(),
rule_content: Some("allow echo".to_string()),
}]),
behavior: Some(PermissionBehavior::Allow),
mode: None,
directories: None,
destination: Some(PermissionUpdateDestination::Session),
};
let json = serde_json::to_value(&update).unwrap();
assert_eq!(json["type"], "addRules");
assert_eq!(json["rules"][0]["toolName"], "Bash");
assert_eq!(json["rules"][0]["ruleContent"], "allow echo");
assert_eq!(json["behavior"], "allow");
assert_eq!(json["destination"], "session");
}
#[test]
fn test_permission_update_optional_fields_omitted() {
let update = PermissionUpdate {
type_: PermissionUpdateType::SetMode,
rules: None,
behavior: None,
mode: Some(super::super::config::PermissionMode::AcceptEdits),
directories: None,
destination: None,
};
let json = serde_json::to_value(&update).unwrap();
assert_eq!(json["type"], "setMode");
assert!(json.get("rules").is_none());
assert!(json.get("behavior").is_none());
assert!(json.get("destination").is_none());
}
}