Stringly typed config (#5463)
This commit is contained in:
@@ -101,11 +101,11 @@ impl GooseAcpAgent {
|
||||
let config = Config::global();
|
||||
|
||||
let provider_name: String = config
|
||||
.get_param("GOOSE_PROVIDER")
|
||||
.get_goose_provider()
|
||||
.map_err(|e| anyhow::anyhow!("No provider configured: {}", e))?;
|
||||
|
||||
let model_name: String = config
|
||||
.get_param("GOOSE_MODEL")
|
||||
.get_goose_model()
|
||||
.map_err(|e| anyhow::anyhow!("No model configured: {}", e))?;
|
||||
|
||||
let model_config = goose::model::ModelConfig {
|
||||
|
||||
@@ -14,7 +14,8 @@ use goose::config::paths::Paths;
|
||||
use goose::config::permission::PermissionLevel;
|
||||
use goose::config::signup_tetrate::TetrateAuth;
|
||||
use goose::config::{
|
||||
configure_tetrate, Config, ConfigError, ExperimentManager, ExtensionEntry, PermissionManager,
|
||||
configure_tetrate, Config, ConfigError, ExperimentManager, ExtensionEntry, GooseMode,
|
||||
PermissionManager,
|
||||
};
|
||||
use goose::conversation::message::Message;
|
||||
use goose::model::ModelConfig;
|
||||
@@ -421,7 +422,7 @@ fn select_model_from_list(
|
||||
}
|
||||
|
||||
fn try_store_secret(config: &Config, key_name: &str, value: String) -> anyhow::Result<bool> {
|
||||
match config.set_secret(key_name, Value::String(value)) {
|
||||
match config.set_secret(key_name, &value) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => {
|
||||
cliclack::outro(style(format!(
|
||||
@@ -450,7 +451,7 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
|
||||
.collect();
|
||||
|
||||
// Get current default provider if it exists
|
||||
let current_provider: Option<String> = config.get_param("GOOSE_PROVIDER").ok();
|
||||
let current_provider: Option<String> = config.get_goose_provider().ok();
|
||||
let default_provider = current_provider.unwrap_or_default();
|
||||
|
||||
// Select provider
|
||||
@@ -487,7 +488,7 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
config.set_param(&key.name, Value::String(env_value))?;
|
||||
config.set_param(&key.name, &env_value)?;
|
||||
}
|
||||
let _ = cliclack::log::info(format!("Saved {} to {}", key.name, config.path()));
|
||||
}
|
||||
@@ -529,7 +530,7 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
config.set_param(&key.name, Value::String(value))?;
|
||||
config.set_param(&key.name, &value)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -558,9 +559,9 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
|
||||
};
|
||||
|
||||
if key.secret {
|
||||
config.set_secret(&key.name, Value::String(value))?;
|
||||
config.set_secret(&key.name, &value)?;
|
||||
} else {
|
||||
config.set_param(&key.name, Value::String(value))?;
|
||||
config.set_param(&key.name, &value)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,9 +649,8 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
|
||||
|
||||
match result {
|
||||
Ok((_message, _usage)) => {
|
||||
// Update config with new values only if the test succeeds
|
||||
config.set_param("GOOSE_PROVIDER", Value::String(provider_name.to_string()))?;
|
||||
config.set_param("GOOSE_MODEL", Value::String(model.clone()))?;
|
||||
config.set_goose_provider(provider_name)?;
|
||||
config.set_goose_model(&model)?;
|
||||
print_config_file_saved()?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -877,7 +877,7 @@ pub fn configure_extensions_dialog() -> anyhow::Result<()> {
|
||||
|
||||
// Try to store in keychain
|
||||
let keychain_key = key.to_string();
|
||||
match config.set_secret(&keychain_key, Value::String(value.clone())) {
|
||||
match config.set_secret(&keychain_key, &value) {
|
||||
Ok(_) => {
|
||||
// Successfully stored in keychain, add to env_keys
|
||||
env_keys.push(keychain_key);
|
||||
@@ -973,7 +973,7 @@ pub fn configure_extensions_dialog() -> anyhow::Result<()> {
|
||||
|
||||
// Try to store in keychain
|
||||
let keychain_key = key.to_string();
|
||||
match config.set_secret(&keychain_key, Value::String(value.clone())) {
|
||||
match config.set_secret(&keychain_key, &value) {
|
||||
Ok(_) => {
|
||||
// Successfully stored in keychain, add to env_keys
|
||||
env_keys.push(keychain_key);
|
||||
@@ -1093,7 +1093,7 @@ pub fn configure_extensions_dialog() -> anyhow::Result<()> {
|
||||
|
||||
// Try to store in keychain
|
||||
let keychain_key = key.to_string();
|
||||
match config.set_secret(&keychain_key, Value::String(value.clone())) {
|
||||
match config.set_secret(&keychain_key, &Value::String(value.clone())) {
|
||||
Ok(_) => {
|
||||
// Successfully stored in keychain, add to env_keys
|
||||
env_keys.push(keychain_key);
|
||||
@@ -1273,46 +1273,35 @@ pub fn configure_goose_mode_dialog() -> anyhow::Result<()> {
|
||||
|
||||
let mode = cliclack::select("Which goose mode would you like to configure?")
|
||||
.item(
|
||||
"auto",
|
||||
GooseMode::Auto,
|
||||
"Auto Mode",
|
||||
"Full file modification, extension usage, edit, create and delete files freely"
|
||||
)
|
||||
.item(
|
||||
"approve",
|
||||
GooseMode::Approve,
|
||||
"Approve Mode",
|
||||
"All tools, extensions and file modifications will require human approval"
|
||||
)
|
||||
.item(
|
||||
"smart_approve",
|
||||
GooseMode::SmartApprove,
|
||||
"Smart Approve Mode",
|
||||
"Editing, creating, deleting files and using extensions will require human approval"
|
||||
)
|
||||
.item(
|
||||
"chat",
|
||||
GooseMode::Chat,
|
||||
"Chat Mode",
|
||||
"Engage with the selected provider without using tools, extensions, or file modification"
|
||||
)
|
||||
.interact()?;
|
||||
|
||||
match mode {
|
||||
"auto" => {
|
||||
config.set_param("GOOSE_MODE", Value::String("auto".to_string()))?;
|
||||
cliclack::outro("Set to Auto Mode - full file modification enabled")?;
|
||||
}
|
||||
"approve" => {
|
||||
config.set_param("GOOSE_MODE", Value::String("approve".to_string()))?;
|
||||
cliclack::outro("Set to Approve Mode - all tools and modifications require approval")?;
|
||||
}
|
||||
"smart_approve" => {
|
||||
config.set_param("GOOSE_MODE", Value::String("smart_approve".to_string()))?;
|
||||
cliclack::outro("Set to Smart Approve Mode - modifications require approval")?;
|
||||
}
|
||||
"chat" => {
|
||||
config.set_param("GOOSE_MODE", Value::String("chat".to_string()))?;
|
||||
cliclack::outro("Set to Chat Mode - no tools or modifications enabled")?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
config.set_goose_mode(mode)?;
|
||||
let msg = match mode {
|
||||
GooseMode::Auto => "Set to Auto Mode - full file modification enabled",
|
||||
GooseMode::Approve => "Set to Approve Mode - all tools and modifications require approval",
|
||||
GooseMode::SmartApprove => "Set to Smart Approve Mode - modifications require approval",
|
||||
GooseMode::Chat => "Set to Chat Mode - no tools or modifications enabled",
|
||||
};
|
||||
cliclack::outro(msg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1321,28 +1310,25 @@ pub fn configure_goose_router_strategy_dialog() -> anyhow::Result<()> {
|
||||
|
||||
let enable_router = cliclack::select("Would you like to enable smart tool routing?")
|
||||
.item(
|
||||
"true",
|
||||
true,
|
||||
"Enable Router",
|
||||
"Use LLM-based intelligence to select tools",
|
||||
)
|
||||
.item(
|
||||
"false",
|
||||
false,
|
||||
"Disable Router",
|
||||
"Use the default tool selection strategy",
|
||||
)
|
||||
.interact()?;
|
||||
|
||||
match enable_router {
|
||||
"true" => {
|
||||
config.set_param("GOOSE_ENABLE_ROUTER", Value::String("true".to_string()))?;
|
||||
cliclack::outro("Router enabled - using LLM-based intelligence for tool selection")?;
|
||||
}
|
||||
"false" => {
|
||||
config.set_param("GOOSE_ENABLE_ROUTER", Value::String("false".to_string()))?;
|
||||
cliclack::outro("Router disabled - using default tool selection")?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
config.set_param("GOOSE_ENABLE_ROUTER", enable_router)?;
|
||||
let msg = if enable_router {
|
||||
"Router enabled - using LLM-based intelligence for tool selection"
|
||||
} else {
|
||||
"Router disabled - using default tool selection"
|
||||
};
|
||||
cliclack::outro(msg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1360,15 +1346,15 @@ pub fn configure_tool_output_dialog() -> anyhow::Result<()> {
|
||||
|
||||
match tool_log_level {
|
||||
"high" => {
|
||||
config.set_param("GOOSE_CLI_MIN_PRIORITY", Value::from(0.8))?;
|
||||
config.set_param("GOOSE_CLI_MIN_PRIORITY", 0.8)?;
|
||||
cliclack::outro("Showing tool output of high importance only.")?;
|
||||
}
|
||||
"medium" => {
|
||||
config.set_param("GOOSE_CLI_MIN_PRIORITY", Value::from(0.2))?;
|
||||
config.set_param("GOOSE_CLI_MIN_PRIORITY", 0.2)?;
|
||||
cliclack::outro("Showing tool output of medium importance.")?;
|
||||
}
|
||||
"all" => {
|
||||
config.set_param("GOOSE_CLI_MIN_PRIORITY", Value::from(0.0))?;
|
||||
config.set_param("GOOSE_CLI_MIN_PRIORITY", 0.0)?;
|
||||
cliclack::outro("Showing all tool output.")?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@@ -1441,11 +1427,11 @@ pub async fn configure_tool_permissions_dialog() -> anyhow::Result<()> {
|
||||
let config = Config::global();
|
||||
|
||||
let provider_name: String = config
|
||||
.get_param("GOOSE_PROVIDER")
|
||||
.get_goose_provider()
|
||||
.expect("No provider configured. Please set model provider first");
|
||||
|
||||
let model: String = config
|
||||
.get_param("GOOSE_MODEL")
|
||||
.get_goose_model()
|
||||
.expect("No model configured. Please set model first");
|
||||
let model_config = ModelConfig::new(&model)?;
|
||||
|
||||
@@ -1591,7 +1577,7 @@ fn configure_recipe_dialog() -> anyhow::Result<()> {
|
||||
if input_value.clone().trim().is_empty() {
|
||||
config.delete(key_name)?;
|
||||
} else {
|
||||
config.set_param(key_name, Value::String(input_value))?;
|
||||
config.set_param(key_name, &input_value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1618,7 +1604,7 @@ pub fn configure_max_turns_dialog() -> anyhow::Result<()> {
|
||||
.interact()?;
|
||||
|
||||
let max_turns: u32 = max_turns_input.parse()?;
|
||||
config.set_param("GOOSE_MAX_TURNS", Value::from(max_turns))?;
|
||||
config.set_param("GOOSE_MAX_TURNS", max_turns)?;
|
||||
|
||||
cliclack::outro(format!(
|
||||
"Set maximum turns to {} - goose will ask for input after {} consecutive actions",
|
||||
@@ -1651,7 +1637,7 @@ pub async fn handle_openrouter_auth() -> anyhow::Result<()> {
|
||||
|
||||
// Test configuration - get the model that was configured
|
||||
println!("\nTesting configuration...");
|
||||
let configured_model: String = config.get_param("GOOSE_MODEL")?;
|
||||
let configured_model: String = config.get_goose_model()?;
|
||||
let model_config = match goose::model::ModelConfig::new(&configured_model) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
@@ -1729,7 +1715,7 @@ pub async fn handle_tetrate_auth() -> anyhow::Result<()> {
|
||||
|
||||
// Test configuration
|
||||
println!("\nTesting configuration...");
|
||||
let configured_model: String = config.get_param("GOOSE_MODEL")?;
|
||||
let configured_model: String = config.get_goose_model()?;
|
||||
let model_config = match goose::model::ModelConfig::new(&configured_model) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
|
||||
@@ -139,7 +139,7 @@ pub async fn handle_web(
|
||||
|
||||
let config = goose::config::Config::global();
|
||||
|
||||
let provider_name: String = match config.get_param("GOOSE_PROVIDER") {
|
||||
let provider_name: String = match config.get_goose_provider() {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
eprintln!("No provider configured. Run 'goose configure' first");
|
||||
@@ -147,7 +147,7 @@ pub async fn handle_web(
|
||||
}
|
||||
};
|
||||
|
||||
let model: String = match config.get_param("GOOSE_MODEL") {
|
||||
let model: String = match config.get_goose_model() {
|
||||
Ok(m) => m,
|
||||
Err(_) => {
|
||||
eprintln!("No model configured. Run 'goose configure' first");
|
||||
|
||||
@@ -11,7 +11,6 @@ use goose::recipe::build_recipe::{
|
||||
};
|
||||
use goose::recipe::validate_recipe::parse_and_validate_parameters;
|
||||
use goose::recipe::Recipe;
|
||||
use serde_json::Value;
|
||||
|
||||
fn create_user_prompt_callback() -> impl Fn(&str, &str) -> Result<String> {
|
||||
|key: &str, description: &str| -> Result<String> {
|
||||
@@ -98,7 +97,7 @@ pub fn collect_missing_secrets(requirements: &[SecretRequirement]) -> Result<()>
|
||||
.unwrap_or_else(|_| String::new());
|
||||
|
||||
if !value.trim().is_empty() {
|
||||
config.set_secret(&req.key, Value::String(value))?;
|
||||
config.set_secret(&req.key, &value)?;
|
||||
println!("✅ Secret stored securely for {}", req.extension_name);
|
||||
} else {
|
||||
println!("⏭️ Skipped {} for {}", req.key, req.extension_name);
|
||||
|
||||
@@ -208,7 +208,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
.as_ref()
|
||||
.and_then(|s| s.goose_provider.clone())
|
||||
})
|
||||
.or_else(|| config.get_param("GOOSE_PROVIDER").ok())
|
||||
.or_else(|| config.get_goose_provider().ok())
|
||||
.expect("No provider configured. Run 'goose configure' first");
|
||||
|
||||
let model_name = session_config
|
||||
@@ -219,7 +219,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
.as_ref()
|
||||
.and_then(|s| s.goose_model.clone())
|
||||
})
|
||||
.or_else(|| config.get_param("GOOSE_MODEL").ok())
|
||||
.or_else(|| config.get_goose_model().ok())
|
||||
.expect("No model configured. Run 'goose configure' first");
|
||||
|
||||
let temperature = session_config.settings.as_ref().and_then(|s| s.temperature);
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::session::task_execution_display::{
|
||||
};
|
||||
use goose::conversation::Conversation;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use self::export::message_to_markdown;
|
||||
pub use builder::{build_session, SessionBuilderConfig, SessionSettings};
|
||||
@@ -28,7 +29,7 @@ use completion::GooseCompleter;
|
||||
use goose::agents::extension::{Envs, ExtensionConfig};
|
||||
use goose::agents::types::RetryConfig;
|
||||
use goose::agents::{Agent, SessionConfig};
|
||||
use goose::config::Config;
|
||||
use goose::config::{Config, GooseMode};
|
||||
use goose::providers::pricing::initialize_pricing_cache;
|
||||
use goose::session::SessionManager;
|
||||
use input::InputResult;
|
||||
@@ -545,21 +546,18 @@ impl CliSession {
|
||||
save_history(&mut editor);
|
||||
|
||||
let config = Config::global();
|
||||
let mode = mode.to_lowercase();
|
||||
|
||||
// Check if mode is valid
|
||||
if !["auto", "approve", "chat", "smart_approve"].contains(&mode.as_str()) {
|
||||
output::render_error(&format!(
|
||||
"Invalid mode '{}'. Mode must be one of: auto, approve, chat",
|
||||
mode
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
config
|
||||
.set_param("GOOSE_MODE", Value::String(mode.to_string()))
|
||||
.unwrap();
|
||||
output::goose_mode_message(&format!("Goose mode set to '{}'", mode));
|
||||
let mode = match GooseMode::from_str(&mode.to_lowercase()) {
|
||||
Ok(mode) => mode,
|
||||
Err(_) => {
|
||||
output::render_error(&format!(
|
||||
"Invalid mode '{}'. Mode must be one of: auto, approve, chat, smart_approve",
|
||||
mode
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
config.set_goose_mode(mode)?;
|
||||
output::goose_mode_message(&format!("Goose mode set to '{:?}'", mode));
|
||||
continue;
|
||||
}
|
||||
input::InputResult::Plan(options) => {
|
||||
@@ -787,12 +785,9 @@ impl CliSession {
|
||||
self.run_mode = RunMode::Normal;
|
||||
// set goose mode: auto if that isn't already the case
|
||||
let config = Config::global();
|
||||
let curr_goose_mode =
|
||||
config.get_param("GOOSE_MODE").unwrap_or("auto".to_string());
|
||||
if curr_goose_mode != "auto" {
|
||||
config
|
||||
.set_param("GOOSE_MODE", Value::String("auto".to_string()))
|
||||
.unwrap();
|
||||
let curr_goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto);
|
||||
if curr_goose_mode != GooseMode::Auto {
|
||||
config.set_goose_mode(GooseMode::Auto).unwrap();
|
||||
}
|
||||
|
||||
// clear the messages before acting on the plan
|
||||
@@ -807,10 +802,8 @@ impl CliSession {
|
||||
output::hide_thinking();
|
||||
|
||||
// Reset run & goose mode
|
||||
if curr_goose_mode != "auto" {
|
||||
config
|
||||
.set_param("GOOSE_MODE", Value::String(curr_goose_mode.to_string()))
|
||||
.unwrap();
|
||||
if curr_goose_mode != GooseMode::Auto {
|
||||
config.set_goose_mode(curr_goose_mode)?;
|
||||
}
|
||||
} else {
|
||||
// add the plan response (assistant message) & carry the conversation forward
|
||||
@@ -1324,7 +1317,7 @@ impl CliSession {
|
||||
.unwrap_or(false);
|
||||
|
||||
let provider_name = config
|
||||
.get_param::<String>("GOOSE_PROVIDER")
|
||||
.get_goose_provider()
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
|
||||
// Do not get costing information if show cost is disabled
|
||||
@@ -1488,7 +1481,7 @@ async fn get_reasoner() -> Result<Arc<dyn Provider>, anyhow::Error> {
|
||||
} else {
|
||||
println!("WARNING: GOOSE_PLANNER_PROVIDER not found. Using default provider...");
|
||||
config
|
||||
.get_param::<String>("GOOSE_PROVIDER")
|
||||
.get_goose_provider()
|
||||
.expect("No provider configured. Run 'goose configure' first")
|
||||
};
|
||||
|
||||
@@ -1498,7 +1491,7 @@ async fn get_reasoner() -> Result<Arc<dyn Provider>, anyhow::Error> {
|
||||
} else {
|
||||
println!("WARNING: GOOSE_PLANNER_MODEL not found. Using default model...");
|
||||
config
|
||||
.get_param::<String>("GOOSE_MODEL")
|
||||
.get_goose_model()
|
||||
.expect("No model configured. Run 'goose configure' first")
|
||||
};
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ thread_local! {
|
||||
pub fn set_theme(theme: Theme) {
|
||||
let config = Config::global();
|
||||
config
|
||||
.set_param("GOOSE_CLI_THEME", Value::String(theme.as_config_string()))
|
||||
.set_param("GOOSE_CLI_THEME", theme.as_config_string())
|
||||
.expect("Failed to set theme");
|
||||
CURRENT_THEME.with(|t| *t.borrow_mut() = theme);
|
||||
|
||||
@@ -79,7 +79,7 @@ pub fn set_theme(theme: Theme) {
|
||||
Theme::Ansi => "ansi",
|
||||
};
|
||||
|
||||
if let Err(e) = config.set_param("GOOSE_CLI_THEME", Value::String(theme_str.to_string())) {
|
||||
if let Err(e) = config.set_param("GOOSE_CLI_THEME", theme_str) {
|
||||
eprintln!("Failed to save theme setting to config: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use axum::{
|
||||
use goose::config::PermissionManager;
|
||||
|
||||
use goose::agents::ExtensionConfig;
|
||||
use goose::config::Config;
|
||||
use goose::config::{Config, GooseMode};
|
||||
use goose::model::ModelConfig;
|
||||
use goose::prompt_template::render_global_file;
|
||||
use goose::providers::{create, create_with_named_model};
|
||||
@@ -211,15 +211,12 @@ async fn resume_agent(
|
||||
let config = Config::global();
|
||||
|
||||
let provider_result = async {
|
||||
let provider_name: String =
|
||||
config
|
||||
.get_param("GOOSE_PROVIDER")
|
||||
.map_err(|_| ErrorResponse {
|
||||
message: "Could not configure agent: missing provider".into(),
|
||||
status: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
})?;
|
||||
let provider_name: String = config.get_goose_provider().map_err(|_| ErrorResponse {
|
||||
message: "Could not configure agent: missing provider".into(),
|
||||
status: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
})?;
|
||||
|
||||
let model: String = config.get_param("GOOSE_MODEL").map_err(|_| ErrorResponse {
|
||||
let model: String = config.get_goose_model().map_err(|_| ErrorResponse {
|
||||
message: "Could not configure agent: missing model".into(),
|
||||
status: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
})?;
|
||||
@@ -348,7 +345,7 @@ async fn get_tools(
|
||||
Query(query): Query<GetToolsQuery>,
|
||||
) -> Result<Json<Vec<ToolInfo>>, StatusCode> {
|
||||
let config = Config::global();
|
||||
let goose_mode = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string());
|
||||
let goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto);
|
||||
let agent = state.get_agent_for_route(query.session_id).await?;
|
||||
let permission_manager = PermissionManager::default();
|
||||
|
||||
@@ -360,9 +357,9 @@ async fn get_tools(
|
||||
let permission = permission_manager
|
||||
.get_user_permission(&tool.name)
|
||||
.or_else(|| {
|
||||
if goose_mode == "smart_approve" {
|
||||
if goose_mode == GooseMode::SmartApprove {
|
||||
permission_manager.get_smart_approve_permission(&tool.name)
|
||||
} else if goose_mode == "approve" {
|
||||
} else if goose_mode == GooseMode::Approve {
|
||||
Some(PermissionLevel::AskBefore)
|
||||
} else {
|
||||
None
|
||||
@@ -406,10 +403,7 @@ async fn update_agent_provider(
|
||||
.await?;
|
||||
|
||||
let config = Config::global();
|
||||
let model = match payload
|
||||
.model
|
||||
.or_else(|| config.get_param("GOOSE_MODEL").ok())
|
||||
{
|
||||
let model = match payload.model.or_else(|| config.get_goose_model().ok()) {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
tracing::error!("No model specified");
|
||||
|
||||
@@ -251,7 +251,7 @@ async fn transcribe_elevenlabs_handler(
|
||||
// Migrate to secret storage
|
||||
if let Err(e) = config.set(
|
||||
"ELEVENLABS_API_KEY",
|
||||
serde_json::Value::String(key.clone()),
|
||||
&serde_json::Value::String(key.clone()),
|
||||
true,
|
||||
) {
|
||||
tracing::error!("Failed to migrate ElevenLabs API key: {:?}", e);
|
||||
|
||||
@@ -101,7 +101,7 @@ pub async fn upsert_config(
|
||||
Json(query): Json<UpsertConfigQuery>,
|
||||
) -> Result<Json<Value>, StatusCode> {
|
||||
let config = Config::global();
|
||||
let result = config.set(&query.key, query.value, query.is_secret);
|
||||
let result = config.set(&query.key, &query.value, query.is_secret);
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(Json(Value::String(format!("Upserted key {}", query.key)))),
|
||||
|
||||
@@ -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