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}