This PR removes the code for Burn Mode, as we won't need it anymore after the 17th. Closes CLO-79. Release Notes: - N/A
743 lines
27 KiB
Rust
743 lines
27 KiB
Rust
use collections::{HashMap, IndexMap};
|
|
use schemars::{JsonSchema, json_schema};
|
|
use serde::{Deserialize, Serialize};
|
|
use settings_macros::{MergeFrom, with_fallible_options};
|
|
use std::sync::Arc;
|
|
use std::{borrow::Cow, path::PathBuf};
|
|
|
|
use crate::ExtendingVec;
|
|
|
|
use crate::{DockPosition, DockSide};
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, Default)]
|
|
pub struct AgentSettingsContent {
|
|
/// Whether the Agent is enabled.
|
|
///
|
|
/// Default: true
|
|
pub enabled: Option<bool>,
|
|
/// Whether to show the agent panel button in the status bar.
|
|
///
|
|
/// Default: true
|
|
pub button: Option<bool>,
|
|
/// Where to dock the agent panel.
|
|
///
|
|
/// Default: right
|
|
pub dock: Option<DockPosition>,
|
|
/// Where to dock the utility pane (the thread view pane).
|
|
///
|
|
/// Default: left
|
|
pub agents_panel_dock: Option<DockSide>,
|
|
/// Default width in pixels when the agent panel is docked to the left or right.
|
|
///
|
|
/// Default: 640
|
|
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
|
|
pub default_width: Option<f32>,
|
|
/// Default height in pixels when the agent panel is docked to the bottom.
|
|
///
|
|
/// Default: 320
|
|
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
|
|
pub default_height: Option<f32>,
|
|
/// The default model to use when creating new chats and for other features when a specific model is not specified.
|
|
pub default_model: Option<LanguageModelSelection>,
|
|
/// Favorite models to show at the top of the model selector.
|
|
#[serde(default)]
|
|
pub favorite_models: Vec<LanguageModelSelection>,
|
|
/// Model to use for the inline assistant. Defaults to default_model when not specified.
|
|
pub inline_assistant_model: Option<LanguageModelSelection>,
|
|
/// Model to use for the inline assistant when streaming tools are enabled.
|
|
///
|
|
/// Default: true
|
|
pub inline_assistant_use_streaming_tools: Option<bool>,
|
|
/// Model to use for generating git commit messages.
|
|
///
|
|
/// Default: true
|
|
pub commit_message_model: Option<LanguageModelSelection>,
|
|
/// Model to use for generating thread summaries. Defaults to default_model when not specified.
|
|
pub thread_summary_model: Option<LanguageModelSelection>,
|
|
/// Additional models with which to generate alternatives when performing inline assists.
|
|
pub inline_alternatives: Option<Vec<LanguageModelSelection>>,
|
|
/// The default profile to use in the Agent.
|
|
///
|
|
/// Default: write
|
|
pub default_profile: Option<Arc<str>>,
|
|
/// Which view type to show by default in the agent panel.
|
|
///
|
|
/// Default: "thread"
|
|
pub default_view: Option<DefaultAgentView>,
|
|
/// The available agent profiles.
|
|
pub profiles: Option<IndexMap<Arc<str>, AgentProfileContent>>,
|
|
/// Whenever a tool action would normally wait for your confirmation
|
|
/// that you allow it, always choose to allow it.
|
|
///
|
|
/// This setting has no effect on external agents that support permission modes, such as Claude Code.
|
|
///
|
|
/// Set `agent_servers.claude.default_mode` to `bypassPermissions`, to disable all permission requests when using Claude Code.
|
|
///
|
|
/// Default: false
|
|
pub always_allow_tool_actions: Option<bool>,
|
|
/// Where to show a popup notification when the agent is waiting for user input.
|
|
///
|
|
/// Default: "primary_screen"
|
|
pub notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
|
|
/// Whether to play a sound when the agent has either completed its response, or needs user input.
|
|
///
|
|
/// Default: false
|
|
pub play_sound_when_agent_done: Option<bool>,
|
|
/// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
|
|
///
|
|
/// Default: true
|
|
pub single_file_review: Option<bool>,
|
|
/// Additional parameters for language model requests. When making a request
|
|
/// to a model, parameters will be taken from the last entry in this list
|
|
/// that matches the model's provider and name. In each entry, both provider
|
|
/// and model are optional, so that you can specify parameters for either
|
|
/// one.
|
|
///
|
|
/// Default: []
|
|
#[serde(default)]
|
|
pub model_parameters: Vec<LanguageModelParameters>,
|
|
/// Whether to show thumb buttons for feedback in the agent panel.
|
|
///
|
|
/// Default: true
|
|
pub enable_feedback: Option<bool>,
|
|
/// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
|
|
///
|
|
/// Default: true
|
|
pub expand_edit_card: Option<bool>,
|
|
/// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
|
|
///
|
|
/// Default: true
|
|
pub expand_terminal_card: Option<bool>,
|
|
/// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel.
|
|
///
|
|
/// Default: false
|
|
pub use_modifier_to_send: Option<bool>,
|
|
/// Minimum number of lines of height the agent message editor should have.
|
|
///
|
|
/// Default: 4
|
|
pub message_editor_min_lines: Option<usize>,
|
|
/// Whether to show turn statistics (elapsed time during generation, final turn duration).
|
|
///
|
|
/// Default: false
|
|
pub show_turn_stats: Option<bool>,
|
|
/// Per-tool permission rules for granular control over which tool actions require confirmation.
|
|
///
|
|
/// This setting only applies to the native Zed agent. External agent servers (Claude Code, Gemini CLI, etc.)
|
|
/// have their own permission systems and are not affected by these settings.
|
|
pub tool_permissions: Option<ToolPermissionsContent>,
|
|
}
|
|
|
|
impl AgentSettingsContent {
|
|
pub fn set_dock(&mut self, dock: DockPosition) {
|
|
self.dock = Some(dock);
|
|
}
|
|
|
|
pub fn set_model(&mut self, language_model: LanguageModelSelection) {
|
|
// let model = language_model.id().0.to_string();
|
|
// let provider = language_model.provider_id().0.to_string();
|
|
// self.default_model = Some(LanguageModelSelection {
|
|
// provider: provider.into(),
|
|
// model,
|
|
// });
|
|
self.default_model = Some(language_model)
|
|
}
|
|
|
|
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
|
|
self.inline_assistant_model = Some(LanguageModelSelection {
|
|
provider: provider.into(),
|
|
model,
|
|
});
|
|
}
|
|
pub fn set_inline_assistant_use_streaming_tools(&mut self, use_tools: bool) {
|
|
self.inline_assistant_use_streaming_tools = Some(use_tools);
|
|
}
|
|
|
|
pub fn set_commit_message_model(&mut self, provider: String, model: String) {
|
|
self.commit_message_model = Some(LanguageModelSelection {
|
|
provider: provider.into(),
|
|
model,
|
|
});
|
|
}
|
|
|
|
pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
|
|
self.thread_summary_model = Some(LanguageModelSelection {
|
|
provider: provider.into(),
|
|
model,
|
|
});
|
|
}
|
|
|
|
pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
|
|
self.always_allow_tool_actions = Some(allow);
|
|
}
|
|
|
|
pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
|
|
self.play_sound_when_agent_done = Some(allow);
|
|
}
|
|
|
|
pub fn set_single_file_review(&mut self, allow: bool) {
|
|
self.single_file_review = Some(allow);
|
|
}
|
|
|
|
pub fn set_use_modifier_to_send(&mut self, always_use: bool) {
|
|
self.use_modifier_to_send = Some(always_use);
|
|
}
|
|
|
|
pub fn set_profile(&mut self, profile_id: Arc<str>) {
|
|
self.default_profile = Some(profile_id);
|
|
}
|
|
|
|
pub fn add_favorite_model(&mut self, model: LanguageModelSelection) {
|
|
if !self.favorite_models.contains(&model) {
|
|
self.favorite_models.push(model);
|
|
}
|
|
}
|
|
|
|
pub fn remove_favorite_model(&mut self, model: &LanguageModelSelection) {
|
|
self.favorite_models.retain(|m| m != model);
|
|
}
|
|
|
|
pub fn set_tool_default_mode(&mut self, tool_id: &str, mode: ToolPermissionMode) {
|
|
let tool_permissions = self.tool_permissions.get_or_insert_default();
|
|
let tool_rules = tool_permissions
|
|
.tools
|
|
.entry(Arc::from(tool_id))
|
|
.or_default();
|
|
tool_rules.default_mode = Some(mode);
|
|
}
|
|
|
|
pub fn add_tool_allow_pattern(&mut self, tool_name: &str, pattern: String) {
|
|
let tool_permissions = self.tool_permissions.get_or_insert_default();
|
|
let tool_rules = tool_permissions
|
|
.tools
|
|
.entry(Arc::from(tool_name))
|
|
.or_default();
|
|
let always_allow = tool_rules.always_allow.get_or_insert_default();
|
|
if !always_allow.0.iter().any(|r| r.pattern == pattern) {
|
|
always_allow.0.push(ToolRegexRule {
|
|
pattern,
|
|
case_sensitive: None,
|
|
});
|
|
}
|
|
}
|
|
|
|
pub fn add_tool_deny_pattern(&mut self, tool_name: &str, pattern: String) {
|
|
let tool_permissions = self.tool_permissions.get_or_insert_default();
|
|
let tool_rules = tool_permissions
|
|
.tools
|
|
.entry(Arc::from(tool_name))
|
|
.or_default();
|
|
let always_deny = tool_rules.always_deny.get_or_insert_default();
|
|
if !always_deny.0.iter().any(|r| r.pattern == pattern) {
|
|
always_deny.0.push(ToolRegexRule {
|
|
pattern,
|
|
case_sensitive: None,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
|
pub struct AgentProfileContent {
|
|
pub name: Arc<str>,
|
|
#[serde(default)]
|
|
pub tools: IndexMap<Arc<str>, bool>,
|
|
/// Whether all context servers are enabled by default.
|
|
pub enable_all_context_servers: Option<bool>,
|
|
#[serde(default)]
|
|
pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
|
|
/// The default language model selected when using this profile.
|
|
pub default_model: Option<LanguageModelSelection>,
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
|
pub struct ContextServerPresetContent {
|
|
pub tools: IndexMap<Arc<str>, bool>,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum DefaultAgentView {
|
|
#[default]
|
|
Thread,
|
|
TextThread,
|
|
}
|
|
|
|
#[derive(
|
|
Copy,
|
|
Clone,
|
|
Default,
|
|
Debug,
|
|
Serialize,
|
|
Deserialize,
|
|
JsonSchema,
|
|
MergeFrom,
|
|
PartialEq,
|
|
strum::VariantArray,
|
|
strum::VariantNames,
|
|
)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum NotifyWhenAgentWaiting {
|
|
#[default]
|
|
PrimaryScreen,
|
|
AllScreens,
|
|
Never,
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
|
|
pub struct LanguageModelSelection {
|
|
pub provider: LanguageModelProviderSetting,
|
|
pub model: String,
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
|
|
pub struct LanguageModelParameters {
|
|
pub provider: Option<LanguageModelProviderSetting>,
|
|
pub model: Option<String>,
|
|
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
|
|
pub temperature: Option<f32>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, MergeFrom)]
|
|
pub struct LanguageModelProviderSetting(pub String);
|
|
|
|
impl JsonSchema for LanguageModelProviderSetting {
|
|
fn schema_name() -> Cow<'static, str> {
|
|
"LanguageModelProviderSetting".into()
|
|
}
|
|
|
|
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
|
// list the builtin providers as a subset so that we still auto complete them in the settings
|
|
json_schema!({
|
|
"anyOf": [
|
|
{
|
|
"type": "string",
|
|
"enum": [
|
|
"amazon-bedrock",
|
|
"anthropic",
|
|
"copilot_chat",
|
|
"deepseek",
|
|
"google",
|
|
"lmstudio",
|
|
"mistral",
|
|
"ollama",
|
|
"openai",
|
|
"openrouter",
|
|
"vercel",
|
|
"x_ai",
|
|
"zed.dev"
|
|
]
|
|
},
|
|
{
|
|
"type": "string",
|
|
}
|
|
]
|
|
})
|
|
}
|
|
}
|
|
|
|
impl From<String> for LanguageModelProviderSetting {
|
|
fn from(provider: String) -> Self {
|
|
Self(provider)
|
|
}
|
|
}
|
|
|
|
impl From<&str> for LanguageModelProviderSetting {
|
|
fn from(provider: &str) -> Self {
|
|
Self(provider.to_string())
|
|
}
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Default, PartialEq, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug)]
|
|
pub struct AllAgentServersSettings {
|
|
pub gemini: Option<BuiltinAgentServerSettings>,
|
|
pub claude: Option<BuiltinAgentServerSettings>,
|
|
pub codex: Option<BuiltinAgentServerSettings>,
|
|
|
|
/// Custom agent servers configured by the user
|
|
#[serde(flatten)]
|
|
pub custom: HashMap<String, CustomAgentServerSettings>,
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)]
|
|
pub struct BuiltinAgentServerSettings {
|
|
/// Absolute path to a binary to be used when launching this agent.
|
|
///
|
|
/// This can be used to run a specific binary without automatic downloads or searching `$PATH`.
|
|
#[serde(rename = "command")]
|
|
pub path: Option<PathBuf>,
|
|
/// If a binary is specified in `command`, it will be passed these arguments.
|
|
pub args: Option<Vec<String>>,
|
|
/// If a binary is specified in `command`, it will be passed these environment variables.
|
|
pub env: Option<HashMap<String, String>>,
|
|
/// Whether to skip searching `$PATH` for an agent server binary when
|
|
/// launching this agent.
|
|
///
|
|
/// This has no effect if a `command` is specified. Otherwise, when this is
|
|
/// `false`, Zed will search `$PATH` for an agent server binary and, if one
|
|
/// is found, use it for threads with this agent. If no agent binary is
|
|
/// found on `$PATH`, Zed will automatically install and use its own binary.
|
|
/// When this is `true`, Zed will not search `$PATH`, and will always use
|
|
/// its own binary.
|
|
///
|
|
/// Default: true
|
|
pub ignore_system_version: Option<bool>,
|
|
/// The default mode to use for this agent.
|
|
///
|
|
/// Note: Not only all agents support modes.
|
|
///
|
|
/// Default: None
|
|
pub default_mode: Option<String>,
|
|
/// The default model to use for this agent.
|
|
///
|
|
/// This should be the model ID as reported by the agent.
|
|
///
|
|
/// Default: None
|
|
pub default_model: Option<String>,
|
|
/// The favorite models for this agent.
|
|
///
|
|
/// These are the model IDs as reported by the agent.
|
|
///
|
|
/// Default: []
|
|
#[serde(default)]
|
|
pub favorite_models: Vec<String>,
|
|
/// Default values for session config options.
|
|
///
|
|
/// This is a map from config option ID to value ID.
|
|
///
|
|
/// Default: {}
|
|
#[serde(default)]
|
|
pub default_config_options: HashMap<String, String>,
|
|
/// Favorited values for session config options.
|
|
///
|
|
/// This is a map from config option ID to a list of favorited value IDs.
|
|
///
|
|
/// Default: {}
|
|
#[serde(default)]
|
|
pub favorite_config_option_values: HashMap<String, Vec<String>>,
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)]
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
|
pub enum CustomAgentServerSettings {
|
|
Custom {
|
|
#[serde(rename = "command")]
|
|
path: PathBuf,
|
|
#[serde(default)]
|
|
args: Vec<String>,
|
|
env: Option<HashMap<String, String>>,
|
|
/// The default mode to use for this agent.
|
|
///
|
|
/// Note: Not only all agents support modes.
|
|
///
|
|
/// Default: None
|
|
default_mode: Option<String>,
|
|
/// The default model to use for this agent.
|
|
///
|
|
/// This should be the model ID as reported by the agent.
|
|
///
|
|
/// Default: None
|
|
default_model: Option<String>,
|
|
/// The favorite models for this agent.
|
|
///
|
|
/// These are the model IDs as reported by the agent.
|
|
///
|
|
/// Default: []
|
|
#[serde(default)]
|
|
favorite_models: Vec<String>,
|
|
/// Default values for session config options.
|
|
///
|
|
/// This is a map from config option ID to value ID.
|
|
///
|
|
/// Default: {}
|
|
#[serde(default)]
|
|
default_config_options: HashMap<String, String>,
|
|
/// Favorited values for session config options.
|
|
///
|
|
/// This is a map from config option ID to a list of favorited value IDs.
|
|
///
|
|
/// Default: {}
|
|
#[serde(default)]
|
|
favorite_config_option_values: HashMap<String, Vec<String>>,
|
|
},
|
|
Extension {
|
|
/// The default mode to use for this agent.
|
|
///
|
|
/// Note: Not only all agents support modes.
|
|
///
|
|
/// Default: None
|
|
default_mode: Option<String>,
|
|
/// The default model to use for this agent.
|
|
///
|
|
/// This should be the model ID as reported by the agent.
|
|
///
|
|
/// Default: None
|
|
default_model: Option<String>,
|
|
/// The favorite models for this agent.
|
|
///
|
|
/// These are the model IDs as reported by the agent.
|
|
///
|
|
/// Default: []
|
|
#[serde(default)]
|
|
favorite_models: Vec<String>,
|
|
/// Default values for session config options.
|
|
///
|
|
/// This is a map from config option ID to value ID.
|
|
///
|
|
/// Default: {}
|
|
#[serde(default)]
|
|
default_config_options: HashMap<String, String>,
|
|
/// Favorited values for session config options.
|
|
///
|
|
/// This is a map from config option ID to a list of favorited value IDs.
|
|
///
|
|
/// Default: {}
|
|
#[serde(default)]
|
|
favorite_config_option_values: HashMap<String, Vec<String>>,
|
|
},
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
|
pub struct ToolPermissionsContent {
|
|
/// Per-tool permission rules.
|
|
/// Keys: terminal, edit_file, delete_path, move_path, create_directory,
|
|
/// save_file, fetch, web_search
|
|
#[serde(default)]
|
|
pub tools: HashMap<Arc<str>, ToolRulesContent>,
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
|
pub struct ToolRulesContent {
|
|
/// Default mode when no regex rules match.
|
|
/// Default: confirm
|
|
pub default_mode: Option<ToolPermissionMode>,
|
|
|
|
/// Regexes for inputs to auto-approve.
|
|
/// For terminal: matches command. For file tools: matches path. For fetch: matches URL.
|
|
/// Default: []
|
|
pub always_allow: Option<ExtendingVec<ToolRegexRule>>,
|
|
|
|
/// Regexes for inputs to auto-reject.
|
|
/// **SECURITY**: These take precedence over ALL other rules, across ALL settings layers.
|
|
/// Default: []
|
|
pub always_deny: Option<ExtendingVec<ToolRegexRule>>,
|
|
|
|
/// Regexes for inputs that must always prompt.
|
|
/// Takes precedence over always_allow but not always_deny.
|
|
/// Default: []
|
|
pub always_confirm: Option<ExtendingVec<ToolRegexRule>>,
|
|
}
|
|
|
|
#[with_fallible_options]
|
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
|
pub struct ToolRegexRule {
|
|
/// The regex pattern to match.
|
|
#[serde(default)]
|
|
pub pattern: String,
|
|
|
|
/// Whether the regex is case-sensitive.
|
|
/// Default: false (case-insensitive)
|
|
pub case_sensitive: Option<bool>,
|
|
}
|
|
|
|
#[derive(
|
|
Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom,
|
|
)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ToolPermissionMode {
|
|
/// Auto-approve without prompting.
|
|
Allow,
|
|
/// Auto-reject with an error.
|
|
Deny,
|
|
/// Always prompt for confirmation (default behavior).
|
|
#[default]
|
|
Confirm,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_set_tool_default_mode_creates_structure() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
assert!(settings.tool_permissions.is_none());
|
|
|
|
settings.set_tool_default_mode("terminal", ToolPermissionMode::Allow);
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
assert_eq!(terminal_rules.default_mode, Some(ToolPermissionMode::Allow));
|
|
}
|
|
|
|
#[test]
|
|
fn test_set_tool_default_mode_updates_existing() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
|
|
settings.set_tool_default_mode("terminal", ToolPermissionMode::Confirm);
|
|
settings.set_tool_default_mode("terminal", ToolPermissionMode::Allow);
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
assert_eq!(terminal_rules.default_mode, Some(ToolPermissionMode::Allow));
|
|
}
|
|
|
|
#[test]
|
|
fn test_set_tool_default_mode_for_mcp_tool() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
|
|
settings.set_tool_default_mode("mcp:github:create_issue", ToolPermissionMode::Allow);
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let mcp_rules = tool_permissions
|
|
.tools
|
|
.get("mcp:github:create_issue")
|
|
.unwrap();
|
|
assert_eq!(mcp_rules.default_mode, Some(ToolPermissionMode::Allow));
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_tool_allow_pattern_creates_structure() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
assert!(settings.tool_permissions.is_none());
|
|
|
|
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
|
|
assert_eq!(always_allow.0.len(), 1);
|
|
assert_eq!(always_allow.0[0].pattern, "^cargo\\s");
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_tool_allow_pattern_appends_to_existing() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
|
|
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
|
|
settings.add_tool_allow_pattern("terminal", "^npm\\s".to_string());
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
|
|
assert_eq!(always_allow.0.len(), 2);
|
|
assert_eq!(always_allow.0[0].pattern, "^cargo\\s");
|
|
assert_eq!(always_allow.0[1].pattern, "^npm\\s");
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_tool_allow_pattern_does_not_duplicate() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
|
|
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
|
|
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
|
|
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
|
|
assert_eq!(
|
|
always_allow.0.len(),
|
|
1,
|
|
"Duplicate patterns should not be added"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_tool_allow_pattern_for_different_tools() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
|
|
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
|
|
settings.add_tool_allow_pattern("fetch", "^https?://github\\.com".to_string());
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
assert_eq!(
|
|
terminal_rules.always_allow.as_ref().unwrap().0[0].pattern,
|
|
"^cargo\\s"
|
|
);
|
|
|
|
let fetch_rules = tool_permissions.tools.get("fetch").unwrap();
|
|
assert_eq!(
|
|
fetch_rules.always_allow.as_ref().unwrap().0[0].pattern,
|
|
"^https?://github\\.com"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_tool_deny_pattern_creates_structure() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
assert!(settings.tool_permissions.is_none());
|
|
|
|
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
|
|
assert_eq!(always_deny.0.len(), 1);
|
|
assert_eq!(always_deny.0[0].pattern, "^rm\\s");
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_tool_deny_pattern_appends_to_existing() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
|
|
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
|
|
settings.add_tool_deny_pattern("terminal", "^sudo\\s".to_string());
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
|
|
assert_eq!(always_deny.0.len(), 2);
|
|
assert_eq!(always_deny.0[0].pattern, "^rm\\s");
|
|
assert_eq!(always_deny.0[1].pattern, "^sudo\\s");
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_tool_deny_pattern_does_not_duplicate() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
|
|
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
|
|
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
|
|
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
|
|
assert_eq!(
|
|
always_deny.0.len(),
|
|
1,
|
|
"Duplicate patterns should not be added"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_tool_deny_and_allow_patterns_separate() {
|
|
let mut settings = AgentSettingsContent::default();
|
|
|
|
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
|
|
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
|
|
|
|
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
|
|
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
|
|
|
|
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
|
|
assert_eq!(always_allow.0.len(), 1);
|
|
assert_eq!(always_allow.0[0].pattern, "^cargo\\s");
|
|
|
|
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
|
|
assert_eq!(always_deny.0.len(), 1);
|
|
assert_eq!(always_deny.0[0].pattern, "^rm\\s");
|
|
}
|
|
}
|