settings.rs

  1use anyhow::{Result, bail};
  2use serde_json::Value;
  3
  4use crate::migrations::migrate_settings;
  5
  6const AGENT_KEY: &str = "agent";
  7const ALWAYS_ALLOW_TOOL_ACTIONS: &str = "always_allow_tool_actions";
  8const DEFAULT_KEY: &str = "default";
  9const DEFAULT_MODE_KEY: &str = "default_mode";
 10const PROFILES_KEY: &str = "profiles";
 11const TOOL_PERMISSIONS_KEY: &str = "tool_permissions";
 12const TOOLS_KEY: &str = "tools";
 13
 14pub fn migrate_tool_permission_defaults(value: &mut Value) -> Result<()> {
 15    migrate_settings(value, &mut migrate_one)
 16}
 17
 18fn migrate_one(obj: &mut serde_json::Map<String, Value>) -> Result<()> {
 19    if let Some(agent) = obj.get_mut(AGENT_KEY) {
 20        migrate_agent_with_profiles(agent)?;
 21    }
 22
 23    Ok(())
 24}
 25
 26fn migrate_agent_with_profiles(agent: &mut Value) -> Result<()> {
 27    migrate_agent_tool_permissions(agent)?;
 28
 29    if let Some(agent_object) = agent.as_object_mut() {
 30        if let Some(profiles) = agent_object.get_mut(PROFILES_KEY) {
 31            if let Some(profiles_object) = profiles.as_object_mut() {
 32                for (_profile_name, profile) in profiles_object.iter_mut() {
 33                    migrate_agent_tool_permissions(profile)?;
 34                }
 35            }
 36        }
 37    }
 38
 39    Ok(())
 40}
 41
 42fn migrate_agent_tool_permissions(agent: &mut Value) -> Result<()> {
 43    let Some(agent_object) = agent.as_object_mut() else {
 44        return Ok(());
 45    };
 46
 47    let should_migrate_always_allow = match agent_object.get(ALWAYS_ALLOW_TOOL_ACTIONS) {
 48        Some(Value::Bool(true)) => {
 49            agent_object.remove(ALWAYS_ALLOW_TOOL_ACTIONS);
 50            true
 51        }
 52        Some(Value::Bool(false)) | Some(Value::Null) | None => {
 53            agent_object.remove(ALWAYS_ALLOW_TOOL_ACTIONS);
 54            false
 55        }
 56        Some(_) => {
 57            // Non-boolean value — leave it in place so the schema validator
 58            // can report it, rather than silently dropping user data.
 59            false
 60        }
 61    };
 62
 63    if should_migrate_always_allow {
 64        if matches!(
 65            agent_object.get(TOOL_PERMISSIONS_KEY),
 66            None | Some(Value::Null)
 67        ) {
 68            agent_object.insert(
 69                TOOL_PERMISSIONS_KEY.to_string(),
 70                Value::Object(Default::default()),
 71            );
 72        }
 73
 74        let Some(Value::Object(tool_permissions_object)) =
 75            agent_object.get_mut(TOOL_PERMISSIONS_KEY)
 76        else {
 77            bail!(
 78                "agent.tool_permissions should be an object or null when migrating \
 79                 always_allow_tool_actions"
 80            );
 81        };
 82
 83        if !tool_permissions_object.contains_key(DEFAULT_KEY)
 84            && !tool_permissions_object.contains_key(DEFAULT_MODE_KEY)
 85        {
 86            tool_permissions_object
 87                .insert(DEFAULT_KEY.to_string(), Value::String("allow".to_string()));
 88        }
 89    }
 90
 91    if let Some(tool_permissions) = agent_object.get_mut(TOOL_PERMISSIONS_KEY) {
 92        migrate_default_mode_to_default(tool_permissions)?;
 93    }
 94
 95    Ok(())
 96}
 97
 98fn migrate_default_mode_to_default(tool_permissions: &mut Value) -> Result<()> {
 99    let Some(tool_permissions_object) = tool_permissions.as_object_mut() else {
100        return Ok(());
101    };
102
103    if let Some(default_mode) = tool_permissions_object.remove(DEFAULT_MODE_KEY) {
104        if !tool_permissions_object.contains_key(DEFAULT_KEY) {
105            tool_permissions_object.insert(DEFAULT_KEY.to_string(), default_mode);
106        }
107    }
108
109    if let Some(tools) = tool_permissions_object.get_mut(TOOLS_KEY) {
110        if let Some(tools_object) = tools.as_object_mut() {
111            for (_tool_name, tool_rules) in tools_object.iter_mut() {
112                if let Some(tool_rules_object) = tool_rules.as_object_mut() {
113                    if let Some(default_mode) = tool_rules_object.remove(DEFAULT_MODE_KEY) {
114                        if !tool_rules_object.contains_key(DEFAULT_KEY) {
115                            tool_rules_object.insert(DEFAULT_KEY.to_string(), default_mode);
116                        }
117                    }
118                }
119            }
120        }
121    }
122
123    Ok(())
124}