Stringly typed config (#5463)
This commit is contained in:
@@ -29,7 +29,7 @@ use crate::agents::tool_route_manager::ToolRouteManager;
|
||||
use crate::agents::tool_router_index_manager::ToolRouterIndexManager;
|
||||
use crate::agents::types::SessionConfig;
|
||||
use crate::agents::types::{FrontendTool, SharedProvider, ToolResultReceiver};
|
||||
use crate::config::{get_enabled_extensions, Config};
|
||||
use crate::config::{get_enabled_extensions, Config, GooseMode};
|
||||
use crate::context_mgmt::DEFAULT_COMPACTION_THRESHOLD;
|
||||
use crate::conversation::{debug_conversation_fix, fix_conversation, Conversation};
|
||||
use crate::mcp_utils::ToolResult;
|
||||
@@ -73,7 +73,7 @@ pub struct ReplyContext {
|
||||
pub tools: Vec<Tool>,
|
||||
pub toolshim_tools: Vec<Tool>,
|
||||
pub system_prompt: String,
|
||||
pub goose_mode: String,
|
||||
pub goose_mode: GooseMode,
|
||||
pub initial_messages: Vec<Message>,
|
||||
pub config: &'static Config,
|
||||
}
|
||||
@@ -193,7 +193,7 @@ impl Agent {
|
||||
// Add permission inspector (medium-high priority)
|
||||
// Note: mode will be updated dynamically based on session config
|
||||
tool_inspection_manager.add_inspector(Box::new(PermissionInspector::new(
|
||||
"smart_approve".to_string(),
|
||||
GooseMode::SmartApprove,
|
||||
std::collections::HashSet::new(), // readonly tools - will be populated from extension manager
|
||||
std::collections::HashSet::new(), // regular tools - will be populated from extension manager
|
||||
)));
|
||||
@@ -264,7 +264,7 @@ impl Agent {
|
||||
|
||||
// Update permission inspector mode to match the session mode
|
||||
self.tool_inspection_manager
|
||||
.update_permission_inspector_mode(goose_mode.clone())
|
||||
.update_permission_inspector_mode(goose_mode)
|
||||
.await;
|
||||
|
||||
Ok(ReplyContext {
|
||||
@@ -1030,8 +1030,7 @@ impl Agent {
|
||||
yield AgentEvent::Message(msg);
|
||||
}
|
||||
|
||||
let mode = goose_mode.clone();
|
||||
if mode.as_str() == "chat" {
|
||||
if goose_mode == GooseMode::Chat {
|
||||
// Skip all tool calls in chat mode
|
||||
for request in remaining_requests {
|
||||
let mut response = message_tool_response.lock().await;
|
||||
@@ -1258,15 +1257,13 @@ impl Agent {
|
||||
}))
|
||||
}
|
||||
|
||||
fn determine_goose_mode(session: Option<&SessionConfig>, config: &Config) -> String {
|
||||
fn determine_goose_mode(session: Option<&SessionConfig>, config: &Config) -> GooseMode {
|
||||
let mode = session.and_then(|s| s.execution_mode.as_deref());
|
||||
|
||||
match mode {
|
||||
Some("foreground") => "chat".to_string(),
|
||||
Some("background") => "auto".to_string(),
|
||||
_ => config
|
||||
.get_param("GOOSE_MODE")
|
||||
.unwrap_or_else(|_| "auto".to_string()),
|
||||
Some("foreground") => GooseMode::Chat,
|
||||
Some("background") => GooseMode::Auto,
|
||||
_ => config.get_goose_mode().unwrap_or(GooseMode::Auto),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1525,7 +1522,7 @@ impl Agent {
|
||||
// but it doesn't know and the plumbing looks complicated.
|
||||
let config = Config::global();
|
||||
let provider_name: String = config
|
||||
.get_param("GOOSE_PROVIDER")
|
||||
.get_goose_provider()
|
||||
.expect("No provider configured. Run 'goose configure' first");
|
||||
|
||||
let settings = Settings {
|
||||
|
||||
@@ -3,13 +3,16 @@ use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::agents::extension::ExtensionInfo;
|
||||
use crate::agents::recipe_tools::dynamic_task_tools::should_enabled_subagents;
|
||||
use crate::agents::router_tools::llm_search_tool_prompt;
|
||||
use crate::{config::Config, prompt_template, utils::sanitize_unicode_tags};
|
||||
use crate::{
|
||||
config::{Config, GooseMode},
|
||||
prompt_template,
|
||||
utils::sanitize_unicode_tags,
|
||||
};
|
||||
|
||||
const MAX_EXTENSIONS: usize = 5;
|
||||
const MAX_TOOLS: usize = 50;
|
||||
@@ -34,7 +37,7 @@ struct SystemPromptContext {
|
||||
current_date_time: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
extension_tool_limits: Option<(usize, usize)>,
|
||||
goose_mode: String,
|
||||
goose_mode: GooseMode,
|
||||
is_autonomous: bool,
|
||||
enable_subagents: bool,
|
||||
max_extensions: usize,
|
||||
@@ -106,9 +109,7 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> {
|
||||
.collect();
|
||||
|
||||
let config = Config::global();
|
||||
let goose_mode = config
|
||||
.get_param("GOOSE_MODE")
|
||||
.unwrap_or_else(|_| Cow::from("auto"));
|
||||
let goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto);
|
||||
|
||||
let extension_tool_limits = self
|
||||
.extension_tool_count
|
||||
@@ -119,8 +120,8 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> {
|
||||
tool_selection_strategy: self.router_enabled.then(llm_search_tool_prompt),
|
||||
current_date_time: self.manager.current_date_timestamp.clone(),
|
||||
extension_tool_limits,
|
||||
goose_mode: goose_mode.to_string(),
|
||||
is_autonomous: goose_mode == "auto",
|
||||
goose_mode,
|
||||
is_autonomous: goose_mode == GooseMode::Auto,
|
||||
enable_subagents: should_enabled_subagents(self.model_name.as_str()),
|
||||
max_extensions: MAX_EXTENSIONS,
|
||||
max_tools: MAX_TOOLS,
|
||||
@@ -137,7 +138,7 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> {
|
||||
});
|
||||
|
||||
let mut system_prompt_extras = self.manager.system_prompt_extras.clone();
|
||||
if goose_mode == "chat" {
|
||||
if goose_mode == GooseMode::Chat {
|
||||
system_prompt_extras.push(
|
||||
"Right now you are in the chat only mode, no access to any tool use and system."
|
||||
.to_string(),
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::agents::subagent_execution_tool::{
|
||||
task_types::{Task, TaskType},
|
||||
};
|
||||
use crate::agents::tool_execution::ToolCallResult;
|
||||
use crate::config::GooseMode;
|
||||
use crate::recipe::{Recipe, RecipeBuilder};
|
||||
use anyhow::{anyhow, Result};
|
||||
use rmcp::model::{Content, ErrorCode, ErrorData, Tool, ToolAnnotations};
|
||||
@@ -93,7 +94,7 @@ pub struct TaskParameter {
|
||||
|
||||
pub fn should_enabled_subagents(model_name: &str) -> bool {
|
||||
let config = crate::config::Config::global();
|
||||
let is_autonomous = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()) == "auto";
|
||||
let is_autonomous = config.get_goose_mode().unwrap_or(GooseMode::Auto) == GooseMode::Auto;
|
||||
if !is_autonomous {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::config::paths::Paths;
|
||||
use crate::config::GooseMode;
|
||||
use fs2::FileExt;
|
||||
use keyring::Entry;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
@@ -143,6 +144,11 @@ macro_rules! declare_param {
|
||||
self.get_param(stringify!($param_name))
|
||||
}
|
||||
}
|
||||
paste::paste! {
|
||||
pub fn [<set_ $param_name:lower>](&self, v: impl Into<$param_type>) -> Result<(), ConfigError> {
|
||||
self.set_param(stringify!($param_name), &v.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -551,7 +557,10 @@ impl Config {
|
||||
}
|
||||
|
||||
// save a parameter in the appropriate location based on if it's secret or not
|
||||
pub fn set(&self, key: &str, value: Value, is_secret: bool) -> Result<(), ConfigError> {
|
||||
pub fn set<V>(&self, key: &str, value: &V, is_secret: bool) -> Result<(), ConfigError>
|
||||
where
|
||||
V: Serialize,
|
||||
{
|
||||
if is_secret {
|
||||
self.set_secret(key, value)
|
||||
} else {
|
||||
@@ -606,17 +615,10 @@ impl Config {
|
||||
/// Returns a ConfigError if:
|
||||
/// - There is an error reading or writing the config file
|
||||
/// - There is an error serializing the value
|
||||
pub fn set_param(&self, key: &str, value: Value) -> Result<(), ConfigError> {
|
||||
// Lock before reading to prevent race condition.
|
||||
pub fn set_param<V: Serialize>(&self, key: &str, value: V) -> Result<(), ConfigError> {
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
|
||||
// Load current values with recovery if needed
|
||||
let mut values = self.load_values()?;
|
||||
|
||||
// Modify values
|
||||
values.insert(key.to_string(), value);
|
||||
|
||||
// Save all values using the atomic write approach
|
||||
values.insert(key.to_string(), serde_json::to_value(&value)?);
|
||||
self.save_values(values)
|
||||
}
|
||||
|
||||
@@ -689,12 +691,15 @@ impl Config {
|
||||
/// Returns a ConfigError if:
|
||||
/// - There is an error accessing the keyring
|
||||
/// - There is an error serializing the value
|
||||
pub fn set_secret(&self, key: &str, value: Value) -> Result<(), ConfigError> {
|
||||
pub fn set_secret<V>(&self, key: &str, value: &V) -> Result<(), ConfigError>
|
||||
where
|
||||
V: Serialize,
|
||||
{
|
||||
// Lock before reading to prevent race condition.
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
|
||||
let mut values = self.load_secrets()?;
|
||||
values.insert(key.to_string(), value);
|
||||
values.insert(key.to_string(), serde_json::to_value(value)?);
|
||||
|
||||
match &self.secrets {
|
||||
SecretStorage::Keyring { service } => {
|
||||
@@ -742,6 +747,9 @@ impl Config {
|
||||
}
|
||||
|
||||
declare_param!(GOOSE_SEARCH_PATHS, Vec<String>);
|
||||
declare_param!(GOOSE_MODE, GooseMode);
|
||||
declare_param!(GOOSE_PROVIDER, String);
|
||||
declare_param!(GOOSE_MODEL, String);
|
||||
}
|
||||
|
||||
/// Load init-config.yaml from workspace root if it exists.
|
||||
@@ -819,7 +827,7 @@ mod tests {
|
||||
let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?;
|
||||
|
||||
// Set a simple string value
|
||||
config.set_param("test_key", Value::String("test_value".to_string()))?;
|
||||
config.set_param("test_key", "test_value")?;
|
||||
|
||||
// Test simple string retrieval
|
||||
let value: String = config.get_param("test_key")?;
|
||||
@@ -874,8 +882,8 @@ mod tests {
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?;
|
||||
|
||||
config.set_param("key1", Value::String("value1".to_string()))?;
|
||||
config.set_param("key2", Value::Number(42.into()))?;
|
||||
config.set_param("key1", "value1")?;
|
||||
config.set_param("key2", 42)?;
|
||||
|
||||
// Read the file directly to check YAML formatting
|
||||
let content = std::fs::read_to_string(temp_file.path())?;
|
||||
@@ -890,12 +898,11 @@ mod tests {
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?;
|
||||
|
||||
config.set_param("key", Value::String("value".to_string()))?;
|
||||
config.set_param("test_key", "test_value")?;
|
||||
config.set_param("another_key", 42)?;
|
||||
config.set_param("third_key", true)?;
|
||||
|
||||
let value: String = config.get_param("key")?;
|
||||
assert_eq!(value, "value");
|
||||
|
||||
config.delete("key")?;
|
||||
let _values = config.load_values()?;
|
||||
|
||||
let result: Result<String, ConfigError> = config.get_param("key");
|
||||
assert!(matches!(result, Err(ConfigError::NotFound(_))));
|
||||
@@ -909,7 +916,7 @@ mod tests {
|
||||
let secrets_file = NamedTempFile::new().unwrap();
|
||||
let config = Config::new_with_file_secrets(config_file.path(), secrets_file.path())?;
|
||||
|
||||
config.set_secret("key", Value::String("value".to_string()))?;
|
||||
config.set_secret("key", &"value")?;
|
||||
|
||||
let value: String = config.get_secret("key")?;
|
||||
assert_eq!(value, "value");
|
||||
@@ -930,7 +937,7 @@ mod tests {
|
||||
let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?;
|
||||
|
||||
// Test setting and getting a simple secret
|
||||
config.set_secret("api_key", Value::String("secret123".to_string()))?;
|
||||
config.set_secret("api_key", &Value::String("secret123".to_string()))?;
|
||||
let value: String = config.get_secret("api_key")?;
|
||||
assert_eq!(value, "secret123");
|
||||
|
||||
@@ -957,8 +964,8 @@ mod tests {
|
||||
let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?;
|
||||
|
||||
// Set multiple secrets
|
||||
config.set_secret("key1", Value::String("secret1".to_string()))?;
|
||||
config.set_secret("key2", Value::String("secret2".to_string()))?;
|
||||
config.set_secret("key1", &Value::String("secret1".to_string()))?;
|
||||
config.set_secret("key2", &Value::String("secret2".to_string()))?;
|
||||
|
||||
// Verify both exist
|
||||
let value1: String = config.get_secret("key1")?;
|
||||
@@ -1056,7 +1063,7 @@ mod tests {
|
||||
let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?;
|
||||
|
||||
// Create a valid config first
|
||||
config.set_param("key1", Value::String("value1".to_string()))?;
|
||||
config.set_param("key1", "value1")?;
|
||||
|
||||
// Verify the backup was created by the first write
|
||||
let backup_paths = config.get_backup_paths();
|
||||
@@ -1066,7 +1073,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// Make another write to ensure backup is created
|
||||
config.set_param("key2", Value::Number(42.into()))?;
|
||||
config.set_param("key2", 42)?;
|
||||
|
||||
// Check again
|
||||
for (i, path) in backup_paths.iter().enumerate() {
|
||||
@@ -1160,15 +1167,15 @@ mod tests {
|
||||
let config = Config::new(config_path, TEST_KEYRING_SERVICE)?;
|
||||
|
||||
// First, create a config with some data
|
||||
config.set_param("test_key_backup", Value::String("backup_value".to_string()))?;
|
||||
config.set_param("another_key", Value::Number(42.into()))?;
|
||||
config.set_param("test_key_backup", "backup_value")?;
|
||||
config.set_param("another_key", 42)?;
|
||||
|
||||
// Verify the backup was created
|
||||
let backup_paths = config.get_backup_paths();
|
||||
let primary_backup = &backup_paths[0]; // .bak file
|
||||
|
||||
// Make sure we have a backup by doing another write
|
||||
config.set_param("third_key", Value::Bool(true))?;
|
||||
config.set_param("third_key", true)?;
|
||||
assert!(primary_backup.exists(), "Backup should exist after writes");
|
||||
|
||||
// Now delete the main config file to simulate it being lost
|
||||
@@ -1204,7 +1211,7 @@ mod tests {
|
||||
let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?;
|
||||
|
||||
// Set initial values
|
||||
config.set_param("key1", Value::String("value1".to_string()))?;
|
||||
config.set_param("key1", "value1")?;
|
||||
|
||||
// Verify the config file exists and is valid
|
||||
assert!(temp_file.path().exists());
|
||||
@@ -1225,7 +1232,7 @@ mod tests {
|
||||
|
||||
// Create multiple versions to test rotation
|
||||
for i in 1..=7 {
|
||||
config.set_param("version", Value::Number(i.into()))?;
|
||||
config.set_param("version", i)?;
|
||||
}
|
||||
|
||||
let backup_paths = config.get_backup_paths();
|
||||
@@ -1460,7 +1467,7 @@ mod tests {
|
||||
let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?;
|
||||
|
||||
// Set value in config file
|
||||
config.set_param("test_precedence", Value::String("file_value".to_string()))?;
|
||||
config.set_param("test_precedence", "file_value")?;
|
||||
|
||||
// Verify file value is returned when no env var
|
||||
let value: String = config.get_param("test_precedence")?;
|
||||
|
||||
@@ -97,7 +97,7 @@ pub fn create_custom_provider(
|
||||
let api_key_name = generate_api_key_name(&id);
|
||||
|
||||
let config = Config::global();
|
||||
config.set_secret(&api_key_name, serde_json::Value::String(api_key))?;
|
||||
config.set_secret(&api_key_name, &api_key)?;
|
||||
|
||||
let model_infos: Vec<ModelInfo> = models
|
||||
.into_iter()
|
||||
@@ -147,10 +147,7 @@ pub fn update_custom_provider(
|
||||
|
||||
let config = Config::global();
|
||||
if !api_key.is_empty() {
|
||||
config.set_secret(
|
||||
&existing_config.api_key_env,
|
||||
serde_json::Value::String(api_key),
|
||||
)?;
|
||||
config.set_secret(&existing_config.api_key_env, &api_key)?;
|
||||
}
|
||||
|
||||
if editable {
|
||||
|
||||
@@ -35,7 +35,7 @@ impl ExperimentManager {
|
||||
Self::refresh_experiments(&mut experiments);
|
||||
experiments.insert(name.to_string(), enabled);
|
||||
|
||||
config.set_param("experiments", serde_json::to_value(experiments)?)?;
|
||||
config.set_param("experiments", experiments)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -101,15 +101,9 @@ fn get_extensions_map() -> HashMap<String, ExtensionEntry> {
|
||||
|
||||
fn save_extensions_map(extensions: HashMap<String, ExtensionEntry>) {
|
||||
let config = Config::global();
|
||||
match serde_json::to_value(extensions) {
|
||||
Ok(value) => {
|
||||
if let Err(e) = config.set_param(EXTENSIONS_CONFIG_KEY, value) {
|
||||
tracing::debug!("Failed to save extensions config: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!("Failed to serialize extensions: {}", e);
|
||||
}
|
||||
if let Err(e) = config.set_param(EXTENSIONS_CONFIG_KEY, &extensions) {
|
||||
// TODO(jack) why is this just a debug statement?
|
||||
tracing::debug!("Failed to save extensions config: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GooseMode {
|
||||
Auto,
|
||||
Approve,
|
||||
SmartApprove,
|
||||
Chat,
|
||||
}
|
||||
|
||||
impl FromStr for GooseMode {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"auto" => Ok(GooseMode::Auto),
|
||||
"approve" => Ok(GooseMode::Approve),
|
||||
"smart_approve" => Ok(GooseMode::SmartApprove),
|
||||
"chat" => Ok(GooseMode::Chat),
|
||||
_ => Err(format!("invalid mode: {}", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ pub mod base;
|
||||
pub mod declarative_providers;
|
||||
mod experiments;
|
||||
pub mod extensions;
|
||||
pub mod goose_mode;
|
||||
pub mod paths;
|
||||
pub mod permission;
|
||||
pub mod search_path;
|
||||
@@ -16,6 +17,7 @@ pub use extensions::{
|
||||
get_all_extension_names, get_all_extensions, get_enabled_extensions, get_extension_by_name,
|
||||
is_extension_enabled, remove_extension, set_extension, set_extension_enabled, ExtensionEntry,
|
||||
};
|
||||
pub use goose_mode::GooseMode;
|
||||
pub use permission::PermissionManager;
|
||||
pub use signup_openrouter::configure_openrouter;
|
||||
pub use signup_tetrate::configure_tetrate;
|
||||
|
||||
@@ -162,14 +162,10 @@ impl PkceAuthFlow {
|
||||
pub use self::PkceAuthFlow as OpenRouterAuth;
|
||||
|
||||
use crate::config::Config;
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn configure_openrouter(config: &Config, api_key: String) -> Result<()> {
|
||||
config.set_secret("OPENROUTER_API_KEY", Value::String(api_key))?;
|
||||
config.set_param("GOOSE_PROVIDER", Value::String("openrouter".to_string()))?;
|
||||
config.set_param(
|
||||
"GOOSE_MODEL",
|
||||
Value::String(OPENROUTER_DEFAULT_MODEL.to_string()),
|
||||
)?;
|
||||
config.set_secret("OPENROUTER_API_KEY", &api_key)?;
|
||||
config.set_goose_provider("openrouter")?;
|
||||
config.set_goose_model(OPENROUTER_DEFAULT_MODEL)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -163,14 +163,10 @@ impl PkceAuthFlow {
|
||||
pub use self::PkceAuthFlow as TetrateAuth;
|
||||
|
||||
use crate::config::Config;
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn configure_tetrate(config: &Config, api_key: String) -> Result<()> {
|
||||
config.set_secret("TETRATE_API_KEY", Value::String(api_key))?;
|
||||
config.set_param("GOOSE_PROVIDER", Value::String("tetrate".to_string()))?;
|
||||
config.set_param(
|
||||
"GOOSE_MODEL",
|
||||
Value::String(TETRATE_DEFAULT_MODEL.to_string()),
|
||||
)?;
|
||||
config.set_secret("TETRATE_API_KEY", &api_key)?;
|
||||
config.set_goose_provider("tetrate")?;
|
||||
config.set_goose_model(TETRATE_DEFAULT_MODEL)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -76,12 +76,9 @@ fn test_configure_tetrate() {
|
||||
config.get_secret::<String>("TETRATE_API_KEY").unwrap(),
|
||||
test_key
|
||||
);
|
||||
assert_eq!(config.get_goose_provider().unwrap(), "tetrate");
|
||||
assert_eq!(
|
||||
config.get_param::<String>("GOOSE_PROVIDER").unwrap(),
|
||||
"tetrate"
|
||||
);
|
||||
assert_eq!(
|
||||
config.get_param::<String>("GOOSE_MODEL").unwrap(),
|
||||
config.get_goose_model().unwrap(),
|
||||
TETRATE_DEFAULT_MODEL.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,9 +27,8 @@ pub async fn save_credentials(
|
||||
token_response,
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(&credentials)?;
|
||||
let key = secret_key(name);
|
||||
config.set_secret(&key, value)?;
|
||||
config.set_secret(&key, &credentials)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::agents::extension_manager_extension::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE;
|
||||
use crate::config::permission::PermissionLevel;
|
||||
use crate::config::PermissionManager;
|
||||
use crate::config::{GooseMode, PermissionManager};
|
||||
use crate::conversation::message::{Message, ToolRequest};
|
||||
use crate::permission::permission_judge::PermissionCheckResult;
|
||||
use crate::tool_inspection::{InspectionAction, InspectionResult, ToolInspector};
|
||||
@@ -12,7 +12,7 @@ use tokio::sync::Mutex;
|
||||
|
||||
/// Permission Inspector that handles tool permission checking
|
||||
pub struct PermissionInspector {
|
||||
mode: Arc<Mutex<String>>,
|
||||
mode: Arc<Mutex<GooseMode>>,
|
||||
readonly_tools: HashSet<String>,
|
||||
regular_tools: HashSet<String>,
|
||||
pub permission_manager: Arc<Mutex<PermissionManager>>,
|
||||
@@ -20,7 +20,7 @@ pub struct PermissionInspector {
|
||||
|
||||
impl PermissionInspector {
|
||||
pub fn new(
|
||||
mode: String,
|
||||
mode: GooseMode,
|
||||
readonly_tools: HashSet<String>,
|
||||
regular_tools: HashSet<String>,
|
||||
) -> Self {
|
||||
@@ -33,7 +33,7 @@ impl PermissionInspector {
|
||||
}
|
||||
|
||||
pub fn with_permission_manager(
|
||||
mode: String,
|
||||
mode: GooseMode,
|
||||
readonly_tools: HashSet<String>,
|
||||
regular_tools: HashSet<String>,
|
||||
permission_manager: Arc<Mutex<PermissionManager>>,
|
||||
@@ -47,7 +47,7 @@ impl PermissionInspector {
|
||||
}
|
||||
|
||||
/// Update the mode of this permission inspector
|
||||
pub async fn update_mode(&self, new_mode: String) {
|
||||
pub async fn update_mode(&self, new_mode: GooseMode) {
|
||||
let mut mode = self.mode.lock().await;
|
||||
*mode = new_mode;
|
||||
}
|
||||
@@ -139,45 +139,42 @@ impl ToolInspector for PermissionInspector {
|
||||
if let Ok(tool_call) = &request.tool_call {
|
||||
let tool_name = &tool_call.name;
|
||||
|
||||
// Handle different modes
|
||||
let action = if *mode == "chat" {
|
||||
// In chat mode, all tools are skipped (handled elsewhere)
|
||||
continue;
|
||||
} else if *mode == "auto" {
|
||||
// In auto mode, all tools are approved
|
||||
InspectionAction::Allow
|
||||
} else {
|
||||
// Smart mode - check permissions
|
||||
|
||||
// 1. Check user-defined permission first
|
||||
if let Some(level) = permission_manager.get_user_permission(tool_name) {
|
||||
match level {
|
||||
PermissionLevel::AlwaysAllow => InspectionAction::Allow,
|
||||
PermissionLevel::NeverAllow => InspectionAction::Deny,
|
||||
PermissionLevel::AskBefore => InspectionAction::RequireApproval(None),
|
||||
let action = match *mode {
|
||||
GooseMode::Chat => continue,
|
||||
GooseMode::Auto => InspectionAction::Allow,
|
||||
GooseMode::Approve | GooseMode::SmartApprove => {
|
||||
// 1. Check user-defined permission first
|
||||
if let Some(level) = permission_manager.get_user_permission(tool_name) {
|
||||
match level {
|
||||
PermissionLevel::AlwaysAllow => InspectionAction::Allow,
|
||||
PermissionLevel::NeverAllow => InspectionAction::Deny,
|
||||
PermissionLevel::AskBefore => {
|
||||
InspectionAction::RequireApproval(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. Check if it's a readonly or regular tool (both pre-approved)
|
||||
else if self.readonly_tools.contains(tool_name.as_ref())
|
||||
|| self.regular_tools.contains(tool_name.as_ref())
|
||||
{
|
||||
InspectionAction::Allow
|
||||
}
|
||||
// 4. Special case for extension management
|
||||
else if tool_name == MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE {
|
||||
InspectionAction::RequireApproval(Some(
|
||||
"Extension management requires approval for security".to_string(),
|
||||
))
|
||||
}
|
||||
// 5. Default: require approval for unknown tools
|
||||
else {
|
||||
InspectionAction::RequireApproval(None)
|
||||
}
|
||||
}
|
||||
// 2. Check if it's a readonly or regular tool (both pre-approved)
|
||||
else if self.readonly_tools.contains(tool_name.as_ref())
|
||||
|| self.regular_tools.contains(tool_name.as_ref())
|
||||
{
|
||||
InspectionAction::Allow
|
||||
}
|
||||
// 4. Special case for extension management
|
||||
else if tool_name == MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE {
|
||||
InspectionAction::RequireApproval(Some(
|
||||
"Extension management requires approval for security".to_string(),
|
||||
))
|
||||
}
|
||||
// 5. Default: require approval for unknown tools
|
||||
else {
|
||||
InspectionAction::RequireApproval(None)
|
||||
}
|
||||
};
|
||||
|
||||
let reason = match &action {
|
||||
InspectionAction::Allow => {
|
||||
if *mode == "auto" {
|
||||
if *mode == GooseMode::Auto {
|
||||
"Auto mode - all tools approved".to_string()
|
||||
} else if self.readonly_tools.contains(tool_name.as_ref()) {
|
||||
"Tool marked as read-only".to_string()
|
||||
|
||||
@@ -10,7 +10,7 @@ use tokio::process::Command;
|
||||
use super::base::{ConfigKey, Provider, ProviderMetadata, ProviderUsage, Usage};
|
||||
use super::errors::ProviderError;
|
||||
use super::utils::RequestLog;
|
||||
use crate::config::Config;
|
||||
use crate::config::{Config, GooseMode};
|
||||
use crate::conversation::message::{Message, MessageContent};
|
||||
use crate::model::ModelConfig;
|
||||
use rmcp::model::Tool;
|
||||
@@ -338,10 +338,8 @@ impl ClaudeCodeProvider {
|
||||
|
||||
// Add permission mode based on GOOSE_MODE setting
|
||||
let config = Config::global();
|
||||
if let Ok(goose_mode) = config.get_param::<String>("GOOSE_MODE") {
|
||||
if goose_mode.as_str() == "auto" {
|
||||
cmd.arg("--permission-mode").arg("acceptEdits");
|
||||
}
|
||||
if let Ok(GooseMode::Auto) = config.get_goose_mode() {
|
||||
cmd.arg("--permission-mode").arg("acceptEdits");
|
||||
}
|
||||
|
||||
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
@@ -523,18 +521,6 @@ mod tests {
|
||||
use super::ModelConfig;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_permission_mode_flag_construction() {
|
||||
// Test that in auto mode, the --permission-mode acceptEdits flag is added
|
||||
std::env::set_var("GOOSE_MODE", "auto");
|
||||
|
||||
let config = Config::global();
|
||||
let goose_mode: String = config.get_param("GOOSE_MODE").unwrap();
|
||||
assert_eq!(goose_mode, "auto");
|
||||
|
||||
std::env::remove_var("GOOSE_MODE");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_claude_code_invalid_model_no_fallback() {
|
||||
// Test that an invalid model is kept as-is (no fallback)
|
||||
|
||||
@@ -235,7 +235,7 @@ impl GithubCopilotProvider {
|
||||
.get_access_token()
|
||||
.await
|
||||
.context("unable to login into github")?;
|
||||
config.set_secret("GITHUB_COPILOT_TOKEN", Value::String(token.clone()))?;
|
||||
config.set_secret("GITHUB_COPILOT_TOKEN", &token)?;
|
||||
token
|
||||
}
|
||||
_ => return Err(err.into()),
|
||||
@@ -500,7 +500,7 @@ impl Provider for GithubCopilotProvider {
|
||||
|
||||
// Save the token
|
||||
config
|
||||
.set_secret("GITHUB_COPILOT_TOKEN", Value::String(token))
|
||||
.set_secret("GITHUB_COPILOT_TOKEN", &token)
|
||||
.map_err(|e| ProviderError::ExecutionError(format!("Failed to save token: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -6,6 +6,7 @@ use super::utils::{
|
||||
get_model, handle_response_openai_compat, handle_status_openai_compat, RequestLog,
|
||||
};
|
||||
use crate::config::declarative_providers::DeclarativeProviderConfig;
|
||||
use crate::config::GooseMode;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::conversation::Conversation;
|
||||
|
||||
@@ -199,8 +200,12 @@ impl Provider for OllamaProvider {
|
||||
tools: &[Tool],
|
||||
) -> Result<(Message, ProviderUsage), ProviderError> {
|
||||
let config = crate::config::Config::global();
|
||||
let goose_mode = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string());
|
||||
let filtered_tools = if goose_mode == "chat" { &[] } else { tools };
|
||||
let goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto);
|
||||
let filtered_tools = if goose_mode == GooseMode::Chat {
|
||||
&[]
|
||||
} else {
|
||||
tools
|
||||
};
|
||||
|
||||
let payload = create_request(
|
||||
&self.model,
|
||||
|
||||
@@ -1104,7 +1104,7 @@ async fn run_scheduled_job_internal(
|
||||
agent_provider = provider;
|
||||
} else {
|
||||
let global_config = Config::global();
|
||||
let provider_name: String = match global_config.get_param("GOOSE_PROVIDER") {
|
||||
let provider_name: String = match global_config.get_goose_provider() {
|
||||
Ok(name) => name,
|
||||
Err(_) => return Err(JobExecutionError {
|
||||
job_id: job.id.clone(),
|
||||
@@ -1114,7 +1114,7 @@ async fn run_scheduled_job_internal(
|
||||
}),
|
||||
};
|
||||
let model_name: String =
|
||||
match global_config.get_param("GOOSE_MODEL") {
|
||||
match global_config.get_goose_model() {
|
||||
Ok(name) => name,
|
||||
Err(_) => return Err(JobExecutionError {
|
||||
job_id: job.id.clone(),
|
||||
|
||||
@@ -2,6 +2,7 @@ use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::config::GooseMode;
|
||||
use crate::conversation::message::{Message, ToolRequest};
|
||||
use crate::permission::permission_inspector::PermissionInspector;
|
||||
use crate::permission::permission_judge::PermissionCheckResult;
|
||||
@@ -116,7 +117,7 @@ impl ToolInspectionManager {
|
||||
}
|
||||
|
||||
/// Update the permission inspector's mode
|
||||
pub async fn update_permission_inspector_mode(&self, mode: String) {
|
||||
pub async fn update_permission_inspector_mode(&self, mode: GooseMode) {
|
||||
for inspector in &self.inspectors {
|
||||
if inspector.name() == "permission" {
|
||||
// Downcast to PermissionInspector to access update_mode method
|
||||
|
||||
@@ -301,16 +301,10 @@ mod tests {
|
||||
|
||||
// Set values in config
|
||||
test_config
|
||||
.set_param(
|
||||
"otel_exporter_otlp_endpoint",
|
||||
serde_json::Value::String("http://config:4318".to_string()),
|
||||
)
|
||||
.set_param("otel_exporter_otlp_endpoint", "http://config:4318")
|
||||
.unwrap();
|
||||
test_config
|
||||
.set_param(
|
||||
"otel_exporter_otlp_timeout",
|
||||
serde_json::Value::Number(3000.into()),
|
||||
)
|
||||
.set_param("otel_exporter_otlp_timeout", 3000)
|
||||
.unwrap();
|
||||
|
||||
// Test that from_config reads from the config file
|
||||
|
||||
Reference in New Issue
Block a user