mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
feat: add JSON Schema for config.yaml with typed config accessors
Goose has no machine-readable schema for config.yaml and config keys were accessed via untyped string literals across the codebase. This adds a JSON Schema for IDE autocomplete and validation, with GooseConfigSchema as the authoritative registry for user-facing config keys. Typed accessors replace raw string-key reads across user-facing config call sites while compile-time assertions keep accessor declarations tied to schema membership. Raw get_param and set_param remain available for dynamic and internal config storage. Signed-off-by: Will Pfleger <wpfleger@block.xyz>
This commit is contained in:
@@ -194,6 +194,11 @@ jobs:
|
||||
source ./bin/activate-hermit
|
||||
just check-acp-schema
|
||||
|
||||
- name: Check Config Schema is Up-to-Date
|
||||
run: |
|
||||
source ./bin/activate-hermit
|
||||
just check-config-schema
|
||||
|
||||
desktop-lint:
|
||||
name: Test and Lint Electron Desktop App
|
||||
runs-on: macos-latest
|
||||
|
||||
@@ -17,6 +17,8 @@ check-everything:
|
||||
cd ui/desktop && pnpm run lint:check
|
||||
@echo " → Validating OpenAPI schema..."
|
||||
./scripts/check-openapi-schema.sh
|
||||
@echo " → Validating config schema..."
|
||||
just check-config-schema
|
||||
@echo ""
|
||||
@echo "✅ All style checks passed!"
|
||||
|
||||
@@ -226,6 +228,26 @@ generate-acp-types: generate-acp-schema
|
||||
cd ui/sdk && npx tsx generate-schema.ts
|
||||
@echo "ACP TypeScript types generated in ui/sdk/src/generated/"
|
||||
|
||||
# Generate config.yaml JSON schema from Rust types
|
||||
generate-config-schema:
|
||||
@echo "Generating config.yaml JSON schema..."
|
||||
cargo run -p goose --bin generate_config_schema
|
||||
@echo "Config schema generated: crates/goose/config.schema.json"
|
||||
|
||||
# Check if config.yaml JSON schema is up-to-date
|
||||
check-config-schema: generate-config-schema
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "Checking config schema is up-to-date..."
|
||||
if ! git diff --exit-code crates/goose/config.schema.json; then
|
||||
echo ""
|
||||
echo "Config schema is out of date!"
|
||||
echo ""
|
||||
echo "Run 'just generate-config-schema' locally, then commit the changes."
|
||||
exit 1
|
||||
fi
|
||||
echo "Config schema is up-to-date"
|
||||
|
||||
# Build SDK TypeScript package (schema + types + compile)
|
||||
build-sdk: generate-acp-types
|
||||
@echo "Compiling ACP TypeScript..."
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::recipes::github_recipe::GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY;
|
||||
use cliclack::spinner;
|
||||
use console::style;
|
||||
use goose::agents::extension::{ToolInfo, PLATFORM_EXTENSIONS};
|
||||
@@ -21,14 +20,13 @@ use goose::config::{
|
||||
};
|
||||
use goose::model::ModelConfig;
|
||||
#[cfg(feature = "telemetry")]
|
||||
use goose::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY};
|
||||
use goose::posthog::get_telemetry_choice;
|
||||
use goose::providers::base::ConfigKey;
|
||||
use goose::providers::chatgpt_codex::reasoning_levels_for_model;
|
||||
use goose::providers::formats::anthropic::supports_adaptive_thinking;
|
||||
use goose::providers::provider_test::test_provider_configuration;
|
||||
use goose::providers::{create, providers, retry_operation, RetryConfig};
|
||||
use goose::session::SessionType;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// useful for light themes where there is no discernible colour contrast between
|
||||
@@ -96,7 +94,7 @@ pub fn configure_telemetry_consent_dialog() -> anyhow::Result<bool> {
|
||||
.initial_value(true)
|
||||
.interact()?;
|
||||
|
||||
config.set_param(TELEMETRY_ENABLED_KEY, enabled)?;
|
||||
config.set_goose_telemetry_enabled(enabled)?;
|
||||
|
||||
if enabled {
|
||||
let _ = cliclack::log::success("Thank you for helping improve goose!");
|
||||
@@ -1429,7 +1427,7 @@ pub fn configure_telemetry_dialog() -> anyhow::Result<()> {
|
||||
.initial_value(current_choice.unwrap_or(true))
|
||||
.interact()?;
|
||||
|
||||
config.set_param(TELEMETRY_ENABLED_KEY, enabled)?;
|
||||
config.set_goose_telemetry_enabled(enabled)?;
|
||||
|
||||
if enabled {
|
||||
cliclack::outro("Telemetry enabled - thank you for helping improve goose!")?;
|
||||
@@ -1456,15 +1454,15 @@ pub fn configure_tool_output_dialog() -> anyhow::Result<()> {
|
||||
|
||||
match tool_log_level {
|
||||
"high" => {
|
||||
config.set_param("GOOSE_CLI_MIN_PRIORITY", 0.8)?;
|
||||
config.set_goose_cli_min_priority(0.8)?;
|
||||
cliclack::outro("Showing tool output of high importance only.")?;
|
||||
}
|
||||
"medium" => {
|
||||
config.set_param("GOOSE_CLI_MIN_PRIORITY", 0.2)?;
|
||||
config.set_goose_cli_min_priority(0.2)?;
|
||||
cliclack::outro("Showing tool output of medium importance.")?;
|
||||
}
|
||||
"all" => {
|
||||
config.set_param("GOOSE_CLI_MIN_PRIORITY", 0.0)?;
|
||||
config.set_goose_cli_min_priority(0.0)?;
|
||||
cliclack::outro("Showing all tool output.")?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@@ -1482,7 +1480,10 @@ pub fn configure_keyring_dialog() -> anyhow::Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
let currently_disabled = config.get_param::<String>("GOOSE_DISABLE_KEYRING").is_ok();
|
||||
let currently_disabled = config
|
||||
.get_goose_disable_keyring()
|
||||
.ok()
|
||||
.is_some_and(|value| matches!(value.as_str(), "1") || value.eq_ignore_ascii_case("true"));
|
||||
|
||||
let current_status = if currently_disabled {
|
||||
"Disabled (using file-based storage)"
|
||||
@@ -1513,14 +1514,14 @@ pub fn configure_keyring_dialog() -> anyhow::Result<()> {
|
||||
match storage_option {
|
||||
"keyring" => {
|
||||
// Set to empty string to enable keyring (absence or empty = enabled)
|
||||
config.set_param("GOOSE_DISABLE_KEYRING", Value::String("".to_string()))?;
|
||||
config.set_goose_disable_keyring("".to_string())?;
|
||||
cliclack::outro("Secret storage set to system keyring (secure)")?;
|
||||
let _ =
|
||||
cliclack::log::info("You may need to restart goose for this change to take effect");
|
||||
}
|
||||
"file" => {
|
||||
// Set the disable flag to use file storage
|
||||
config.set_param("GOOSE_DISABLE_KEYRING", Value::String("true".to_string()))?;
|
||||
config.set_goose_disable_keyring("true".to_string())?;
|
||||
cliclack::outro(format!(
|
||||
"Secret storage set to file ({}). Keep this file secure!",
|
||||
secrets_path.display(),
|
||||
@@ -1745,11 +1746,10 @@ pub async fn configure_tool_permissions_dialog() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
fn configure_recipe_dialog() -> anyhow::Result<()> {
|
||||
let key_name = GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY;
|
||||
let config = Config::global();
|
||||
let default_recipe_repo = std::env::var(key_name)
|
||||
let default_recipe_repo = std::env::var("GOOSE_RECIPE_GITHUB_REPO")
|
||||
.ok()
|
||||
.or_else(|| config.get_param(key_name).unwrap_or(None));
|
||||
.or_else(|| config.get_goose_recipe_github_repo().unwrap_or(None));
|
||||
let mut recipe_repo_input = cliclack::input(
|
||||
"Enter your goose recipe GitHub repo (owner/repo): eg: my_org/goose-recipes",
|
||||
)
|
||||
@@ -1759,9 +1759,9 @@ fn configure_recipe_dialog() -> anyhow::Result<()> {
|
||||
}
|
||||
let input_value: String = recipe_repo_input.interact()?;
|
||||
if input_value.clone().trim().is_empty() {
|
||||
config.delete(key_name)?;
|
||||
config.delete("GOOSE_RECIPE_GITHUB_REPO")?;
|
||||
} else {
|
||||
config.set_param(key_name, &input_value)?;
|
||||
config.set_goose_recipe_github_repo(Some(input_value))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1769,7 +1769,7 @@ fn configure_recipe_dialog() -> anyhow::Result<()> {
|
||||
pub fn configure_max_turns_dialog() -> anyhow::Result<()> {
|
||||
let config = Config::global();
|
||||
|
||||
let current_max_turns: u32 = config.get_param("GOOSE_MAX_TURNS").unwrap_or(1000);
|
||||
let current_max_turns: u32 = config.get_goose_max_turns().unwrap_or(1000);
|
||||
|
||||
let max_turns_input: String =
|
||||
cliclack::input("Set maximum number of agent turns without user input:")
|
||||
@@ -1788,7 +1788,7 @@ pub fn configure_max_turns_dialog() -> anyhow::Result<()> {
|
||||
.interact()?;
|
||||
|
||||
let max_turns: u32 = max_turns_input.parse()?;
|
||||
config.set_param("GOOSE_MAX_TURNS", max_turns)?;
|
||||
config.set_goose_max_turns(max_turns)?;
|
||||
|
||||
cliclack::outro(format!(
|
||||
"Set maximum turns to {} - goose will ask for input after {} consecutive actions",
|
||||
|
||||
@@ -30,7 +30,6 @@ pub enum RecipeSource {
|
||||
GitHub,
|
||||
}
|
||||
|
||||
pub const GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY: &str = "GOOSE_RECIPE_GITHUB_REPO";
|
||||
pub fn retrieve_recipe_from_github(
|
||||
recipe_name: &str,
|
||||
recipe_repo_full_name: &str,
|
||||
|
||||
@@ -4,7 +4,6 @@ use goose::recipe::read_recipe_file_content::RecipeFile;
|
||||
|
||||
use super::github_recipe::{
|
||||
list_github_recipes, retrieve_recipe_from_github, RecipeInfo, RecipeSource,
|
||||
GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY,
|
||||
};
|
||||
use goose::recipe::local_recipes::{list_local_recipes, load_local_recipe_file};
|
||||
|
||||
@@ -20,7 +19,7 @@ pub fn load_recipe_file(recipe_name: &str) -> Result<RecipeFile> {
|
||||
|
||||
fn configured_github_recipe_repo() -> Option<String> {
|
||||
let config = Config::global();
|
||||
match config.get_param(GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY) {
|
||||
match config.get_goose_recipe_github_repo() {
|
||||
Ok(Some(recipe_repo_full_name)) => Some(recipe_repo_full_name),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -466,7 +466,7 @@ async fn configure_session_prompts(
|
||||
.await;
|
||||
}
|
||||
|
||||
let system_prompt_file: Option<String> = config.get_param("GOOSE_SYSTEM_PROMPT_FILE_PATH").ok();
|
||||
let system_prompt_file: Option<String> = config.get_goose_system_prompt_file_path().ok();
|
||||
if let Some(ref path) = system_prompt_file {
|
||||
let override_prompt = std::fs::read_to_string(path).unwrap_or_else(|e| {
|
||||
output::render_error(&format!(
|
||||
@@ -581,19 +581,20 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
// Extensions are loaded after session creation because we may change directory when resuming
|
||||
let agent_ptr = resolve_and_load_extensions(agent, extensions_for_provider, &session_id).await;
|
||||
|
||||
let edit_mode = config
|
||||
.get_param::<String>("EDIT_MODE")
|
||||
.ok()
|
||||
.and_then(|edit_mode| match edit_mode.to_lowercase().as_str() {
|
||||
"emacs" => Some(EditMode::Emacs),
|
||||
"vi" => Some(EditMode::Vi),
|
||||
_ => {
|
||||
eprintln!("Invalid EDIT_MODE specified, defaulting to Emacs");
|
||||
None
|
||||
}
|
||||
});
|
||||
let edit_mode =
|
||||
config
|
||||
.get_edit_mode()
|
||||
.ok()
|
||||
.and_then(|edit_mode| match edit_mode.to_lowercase().as_str() {
|
||||
"emacs" => Some(EditMode::Emacs),
|
||||
"vi" => Some(EditMode::Vi),
|
||||
_ => {
|
||||
eprintln!("Invalid EDIT_MODE specified, defaulting to Emacs");
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let debug_mode = session_config.debug || config.get_param("GOOSE_DEBUG").unwrap_or(false);
|
||||
let debug_mode = session_config.debug || config.get_goose_debug().unwrap_or(false);
|
||||
|
||||
let session = CliSession::new(
|
||||
Arc::try_unwrap(agent_ptr).unwrap_or_else(|_| panic!("There should be no more references")),
|
||||
|
||||
@@ -84,7 +84,7 @@ impl rustyline::ConditionalEventHandler for CtrlCHandler {
|
||||
|
||||
pub fn get_newline_key() -> char {
|
||||
Config::global()
|
||||
.get_param::<String>("GOOSE_CLI_NEWLINE_KEY")
|
||||
.get_goose_cli_newline_key()
|
||||
.ok()
|
||||
.and_then(|s| s.chars().next())
|
||||
.map(|c| c.to_ascii_lowercase())
|
||||
|
||||
@@ -1455,9 +1455,7 @@ impl CliSession {
|
||||
let context_limit = model_config.context_limit();
|
||||
|
||||
let config = Config::global();
|
||||
let show_cost = config
|
||||
.get_param::<bool>("GOOSE_CLI_SHOW_COST")
|
||||
.unwrap_or(false);
|
||||
let show_cost = config.get_goose_cli_show_cost().unwrap_or(false);
|
||||
|
||||
let provider_name = config
|
||||
.get_goose_provider()
|
||||
@@ -1850,7 +1848,7 @@ fn format_logging_notification(
|
||||
Some("response_generated") => {
|
||||
let config = Config::global();
|
||||
let min_priority = config
|
||||
.get_param::<f32>("GOOSE_CLI_MIN_PRIORITY")
|
||||
.get_goose_cli_min_priority()
|
||||
.ok()
|
||||
.unwrap_or(output::DEFAULT_MIN_PRIORITY);
|
||||
|
||||
@@ -1916,7 +1914,7 @@ fn display_log_notification(
|
||||
} else if ntype == "shell_output" {
|
||||
let config = Config::global();
|
||||
let min_priority = config
|
||||
.get_param::<f32>("GOOSE_CLI_MIN_PRIORITY")
|
||||
.get_goose_cli_min_priority()
|
||||
.ok()
|
||||
.unwrap_or(output::DEFAULT_MIN_PRIORITY);
|
||||
|
||||
@@ -2023,7 +2021,7 @@ async fn get_reasoner() -> Result<Arc<dyn Provider>, anyhow::Error> {
|
||||
let config = Config::global();
|
||||
|
||||
// Try planner-specific provider first, fall back to default provider
|
||||
let provider = if let Ok(provider) = config.get_param::<String>("GOOSE_PLANNER_PROVIDER") {
|
||||
let provider = if let Ok(provider) = config.get_goose_planner_provider() {
|
||||
provider
|
||||
} else {
|
||||
println!("WARNING: GOOSE_PLANNER_PROVIDER not found. Using default provider...");
|
||||
@@ -2033,7 +2031,7 @@ async fn get_reasoner() -> Result<Arc<dyn Provider>, anyhow::Error> {
|
||||
};
|
||||
|
||||
// Try planner-specific model first, fall back to default model
|
||||
let model = if let Ok(model) = config.get_param::<String>("GOOSE_PLANNER_MODEL") {
|
||||
let model = if let Ok(model) = config.get_goose_planner_model() {
|
||||
model
|
||||
} else {
|
||||
println!("WARNING: GOOSE_PLANNER_MODEL not found. Using default model...");
|
||||
|
||||
@@ -37,10 +37,10 @@ impl Theme {
|
||||
fn as_str(&self) -> String {
|
||||
match self {
|
||||
Theme::Light => Config::global()
|
||||
.get_param::<String>("GOOSE_CLI_LIGHT_THEME")
|
||||
.get_goose_cli_light_theme()
|
||||
.unwrap_or(DEFAULT_CLI_LIGHT_THEME.to_string()),
|
||||
Theme::Dark => Config::global()
|
||||
.get_param::<String>("GOOSE_CLI_DARK_THEME")
|
||||
.get_goose_cli_dark_theme()
|
||||
.unwrap_or(DEFAULT_CLI_DARK_THEME.to_string()),
|
||||
Theme::Ansi => "base16".to_string(),
|
||||
}
|
||||
@@ -70,20 +70,20 @@ thread_local! {
|
||||
std::env::var("GOOSE_CLI_THEME").ok()
|
||||
.map(|val| Theme::from_config_str(&val))
|
||||
.unwrap_or_else(||
|
||||
Config::global().get_param::<String>("GOOSE_CLI_THEME").ok()
|
||||
Config::global().get_goose_cli_theme().ok()
|
||||
.map(|val| Theme::from_config_str(&val))
|
||||
.unwrap_or(Theme::Ansi)
|
||||
)
|
||||
);
|
||||
static SHOW_FULL_TOOL_OUTPUT: RefCell<bool> = RefCell::new(
|
||||
Config::global().get_param::<bool>("GOOSE_SHOW_FULL_OUTPUT").unwrap_or(false)
|
||||
Config::global().get_goose_show_full_output().unwrap_or(false)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_theme(theme: Theme) {
|
||||
let config = Config::global();
|
||||
config
|
||||
.set_param("GOOSE_CLI_THEME", theme.as_config_string())
|
||||
.set_goose_cli_theme(theme.as_config_string())
|
||||
.expect("Failed to set theme");
|
||||
CURRENT_THEME.with(|t| *t.borrow_mut() = theme);
|
||||
|
||||
@@ -94,7 +94,7 @@ pub fn set_theme(theme: Theme) {
|
||||
Theme::Ansi => "ansi",
|
||||
};
|
||||
|
||||
if let Err(e) = config.set_param("GOOSE_CLI_THEME", theme_str) {
|
||||
if let Err(e) = config.set_goose_cli_theme(theme_str) {
|
||||
eprintln!("Failed to save theme setting to config: {}", e);
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ impl ThinkingIndicator {
|
||||
let spinner = cliclack::spinner();
|
||||
let hint = style("(Ctrl+C to interrupt)").dim();
|
||||
if Config::global()
|
||||
.get_param("RANDOM_THINKING_MESSAGES")
|
||||
.get_random_thinking_messages()
|
||||
.unwrap_or(true)
|
||||
{
|
||||
spinner.start(format!(
|
||||
@@ -177,7 +177,7 @@ pub fn hide_thinking() {
|
||||
}
|
||||
|
||||
pub fn run_status_hook(status: &str) {
|
||||
if let Ok(hook) = Config::global().get_param::<String>("GOOSE_STATUS_HOOK") {
|
||||
if let Ok(hook) = Config::global().get_goose_status_hook() {
|
||||
let status = status.to_string();
|
||||
std::thread::spawn(move || {
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -449,7 +449,7 @@ pub fn goose_mode_message(text: &str) {
|
||||
|
||||
fn should_show_thinking() -> bool {
|
||||
Config::global()
|
||||
.get_param::<bool>("GOOSE_CLI_SHOW_THINKING")
|
||||
.get_goose_cli_show_thinking()
|
||||
.unwrap_or(false)
|
||||
&& std::io::stdout().is_terminal()
|
||||
}
|
||||
@@ -507,7 +507,7 @@ fn render_tool_response(resp: &ToolResponse, debug: bool) {
|
||||
}
|
||||
|
||||
let min_priority = config
|
||||
.get_param::<f32>("GOOSE_CLI_MIN_PRIORITY")
|
||||
.get_goose_cli_min_priority()
|
||||
.ok()
|
||||
.unwrap_or(DEFAULT_MIN_PRIORITY);
|
||||
|
||||
|
||||
@@ -113,9 +113,7 @@ impl TunnelManager {
|
||||
}
|
||||
|
||||
fn get_auto_start() -> bool {
|
||||
Config::global()
|
||||
.get_param("tunnel_auto_start")
|
||||
.unwrap_or(false)
|
||||
Config::global().get_tunnel_auto_start().unwrap_or(false)
|
||||
}
|
||||
|
||||
fn get_secret() -> Option<String> {
|
||||
@@ -200,7 +198,7 @@ impl TunnelManager {
|
||||
|
||||
pub fn set_auto_start(auto_start: bool) -> anyhow::Result<()> {
|
||||
Config::global()
|
||||
.set_param("tunnel_auto_start", auto_start)
|
||||
.set_tunnel_auto_start(auto_start)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to save tunnel config: {}", e))
|
||||
}
|
||||
|
||||
|
||||
@@ -253,6 +253,10 @@ path = "src/providers/canonical/build_canonical_models.rs"
|
||||
name = "generate-acp-schema"
|
||||
path = "src/bin/generate_acp_schema.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "generate_config_schema"
|
||||
path = "src/bin/generate_config_schema.rs"
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
|
||||
ignored = [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -111,14 +111,11 @@ impl GooseAcpAgent {
|
||||
|
||||
let config = self.config()?;
|
||||
config
|
||||
.set_param_values(&[(
|
||||
"GOOSE_PROVIDER".to_string(),
|
||||
serde_json::Value::String(provider_id.clone()),
|
||||
)])
|
||||
.set_goose_provider(provider_id.clone())
|
||||
.internal_err_ctx("Failed to save default provider")?;
|
||||
if let Some(model_id) = model_id.as_deref() {
|
||||
config
|
||||
.set_param("GOOSE_MODEL", model_id)
|
||||
.set_goose_model(model_id.to_string())
|
||||
.internal_err_ctx("Failed to save default model")?;
|
||||
} else {
|
||||
config
|
||||
|
||||
@@ -403,8 +403,7 @@ impl Agent {
|
||||
self.tool_inspection_manager.apply_tool_annotations(&tools);
|
||||
}
|
||||
|
||||
let tool_call_cut_off = match Config::global().get_param::<usize>("GOOSE_TOOL_CALL_CUTOFF")
|
||||
{
|
||||
let tool_call_cut_off = match Config::global().get_goose_tool_call_cutoff() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
let context_limit = self
|
||||
@@ -413,7 +412,7 @@ impl Agent {
|
||||
.map(|p| p.get_model_config().context_limit())
|
||||
.unwrap_or(crate::model::DEFAULT_CONTEXT_LIMIT);
|
||||
let compaction_threshold = Config::global()
|
||||
.get_param::<f64>("GOOSE_AUTO_COMPACT_THRESHOLD")
|
||||
.get_goose_auto_compact_threshold()
|
||||
.unwrap_or(crate::context_mgmt::DEFAULT_COMPACTION_THRESHOLD);
|
||||
crate::context_mgmt::compute_tool_call_cutoff(context_limit, compaction_threshold)
|
||||
}
|
||||
@@ -1168,7 +1167,7 @@ impl Agent {
|
||||
} else {
|
||||
let config = Config::global();
|
||||
let threshold = config
|
||||
.get_param::<f64>("GOOSE_AUTO_COMPACT_THRESHOLD")
|
||||
.get_goose_auto_compact_threshold()
|
||||
.unwrap_or(DEFAULT_COMPACTION_THRESHOLD);
|
||||
let threshold_percentage = (threshold * 100.0) as u32;
|
||||
|
||||
@@ -1292,7 +1291,7 @@ impl Agent {
|
||||
let mut turns_taken = 0u32;
|
||||
let max_turns = session_config.max_turns.unwrap_or_else(|| {
|
||||
Config::global()
|
||||
.get_param::<u32>("GOOSE_MAX_TURNS")
|
||||
.get_goose_max_turns()
|
||||
.unwrap_or(DEFAULT_MAX_TURNS)
|
||||
});
|
||||
let mut compaction_attempts = 0;
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::config::Config;
|
||||
use rmcp::model::Tool;
|
||||
use rmcp::service::ClientInitializeError;
|
||||
use rmcp::ServiceError as ClientError;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
@@ -58,7 +59,7 @@ pub enum ExtensionError {
|
||||
|
||||
pub type ExtensionResult<T> = Result<T, ExtensionError>;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default, ToSchema, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default, ToSchema, PartialEq, JsonSchema)]
|
||||
pub struct Envs {
|
||||
/// A map of environment variables to set, e.g. API_KEY -> some_secret, HOST -> host
|
||||
#[serde(default)]
|
||||
@@ -148,7 +149,7 @@ impl Envs {
|
||||
}
|
||||
|
||||
/// Represents the different types of MCP extensions that can be added to the manager
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, PartialEq, JsonSchema)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ExtensionConfig {
|
||||
/// SSE transport is no longer supported - kept only for config file compatibility
|
||||
|
||||
@@ -518,7 +518,7 @@ impl McpClientTrait for CodeExecutionClient {
|
||||
pub fn get_tool_disclosure() -> ToolDisclosure {
|
||||
let config = crate::config::Config::global();
|
||||
let tool_disclosure_str: String = config
|
||||
.get_param("CODE_MODE_TOOL_DISCLOSURE")
|
||||
.get_code_mode_tool_disclosure()
|
||||
.unwrap_or_else(|_| "catalog".to_string());
|
||||
serde_json::from_value(serde_json::json!(tool_disclosure_str)).unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ fn current_epoch_millis() -> u64 {
|
||||
/// Get maximum number of concurrent background tasks
|
||||
fn max_background_tasks() -> usize {
|
||||
Config::global()
|
||||
.get_param::<usize>("GOOSE_MAX_BACKGROUND_TASKS")
|
||||
.get_goose_max_background_tasks()
|
||||
.unwrap_or(5)
|
||||
}
|
||||
|
||||
@@ -1290,11 +1290,7 @@ impl SummonClient {
|
||||
.as_ref()
|
||||
.and_then(|s| s.goose_provider.clone())
|
||||
})
|
||||
.or_else(|| {
|
||||
Config::global()
|
||||
.get_param::<String>("GOOSE_SUBAGENT_PROVIDER")
|
||||
.ok()
|
||||
})
|
||||
.or_else(|| Config::global().get_goose_subagent_provider().ok())
|
||||
.or_else(|| session.provider_name.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("No provider configured"))?;
|
||||
|
||||
@@ -1311,7 +1307,7 @@ impl SummonClient {
|
||||
.and_then(|s| s.goose_model.as_ref())
|
||||
{
|
||||
model_config.model_name = model.clone();
|
||||
} else if let Ok(model) = Config::global().get_param::<String>("GOOSE_SUBAGENT_MODEL") {
|
||||
} else if let Ok(model) = Config::global().get_goose_subagent_model() {
|
||||
model_config.model_name = model;
|
||||
}
|
||||
|
||||
@@ -1335,11 +1331,7 @@ impl SummonClient {
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
})
|
||||
.or_else(|| {
|
||||
Config::global()
|
||||
.get_param::<usize>("GOOSE_SUBAGENT_MAX_TURNS")
|
||||
.ok()
|
||||
})
|
||||
.or_else(|| Config::global().get_goose_subagent_max_turns().ok())
|
||||
.unwrap_or(DEFAULT_SUBAGENT_MAX_TURNS)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,6 @@ pub enum RetryResult {
|
||||
Retried,
|
||||
}
|
||||
|
||||
/// Environment variable for configuring retry timeout globally
|
||||
const GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS: &str = "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS";
|
||||
|
||||
/// Environment variable for configuring on_failure timeout globally
|
||||
const GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS: &str = "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS";
|
||||
|
||||
/// Manages retry state and operations for agent execution
|
||||
#[derive(Debug)]
|
||||
pub struct RetryManager {
|
||||
@@ -169,7 +163,7 @@ fn get_retry_timeout(retry_config: &RetryConfig) -> Duration {
|
||||
.timeout_seconds
|
||||
.or_else(|| {
|
||||
let config = Config::global();
|
||||
config.get_param(GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS).ok()
|
||||
config.get_goose_recipe_retry_timeout_seconds().ok()
|
||||
})
|
||||
.unwrap_or(DEFAULT_RETRY_TIMEOUT_SECONDS);
|
||||
|
||||
@@ -183,9 +177,7 @@ fn get_on_failure_timeout(retry_config: &RetryConfig) -> Duration {
|
||||
.on_failure_timeout_seconds
|
||||
.or_else(|| {
|
||||
let config = Config::global();
|
||||
config
|
||||
.get_param(GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS)
|
||||
.ok()
|
||||
config.get_goose_recipe_on_failure_timeout_seconds().ok()
|
||||
})
|
||||
.unwrap_or(DEFAULT_ON_FAILURE_TIMEOUT_SECONDS);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ impl TaskConfig {
|
||||
extensions,
|
||||
max_turns: Some(
|
||||
Config::global()
|
||||
.get_param::<usize>("GOOSE_SUBAGENT_MAX_TURNS")
|
||||
.get_goose_subagent_max_turns()
|
||||
.unwrap_or(DEFAULT_SUBAGENT_MAX_TURNS),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
use goose::config::schema::GooseConfigSchema;
|
||||
use schemars::schema_for;
|
||||
use std::{env, fs, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
let schema = schema_for!(GooseConfigSchema);
|
||||
let json = serde_json::to_string_pretty(&schema).expect("failed to serialize schema");
|
||||
|
||||
let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let path = PathBuf::from(&dir).join("config.schema.json");
|
||||
fs::write(&path, format!("{json}\n")).expect("failed to write schema file");
|
||||
eprintln!("Generated config schema at {}", path.display());
|
||||
}
|
||||
@@ -204,6 +204,14 @@ pub trait ConfigValue {
|
||||
|
||||
macro_rules! config_value {
|
||||
($key:ident, $type:ty) => {
|
||||
const _: () = assert!(
|
||||
crate::config::schema::GooseConfigSchema::has_key(stringify!($key)),
|
||||
concat!(
|
||||
"Config key ",
|
||||
stringify!($key),
|
||||
" is not registered in GooseConfigSchema"
|
||||
)
|
||||
);
|
||||
impl Config {
|
||||
pastey::paste! {
|
||||
pub fn [<get_ $key:lower>](&self) -> Result<$type, ConfigError> {
|
||||
@@ -219,6 +227,14 @@ macro_rules! config_value {
|
||||
};
|
||||
|
||||
($key:ident, $inner:ty, $default:expr) => {
|
||||
const _: () = assert!(
|
||||
crate::config::schema::GooseConfigSchema::has_key(stringify!($key)),
|
||||
concat!(
|
||||
"Config key ",
|
||||
stringify!($key),
|
||||
" is not registered in GooseConfigSchema"
|
||||
)
|
||||
);
|
||||
pastey::paste! {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
@@ -1033,6 +1049,124 @@ config_value!(CLAUDE_THINKING_EFFORT, String);
|
||||
config_value!(CLAUDE_THINKING_BUDGET, i32);
|
||||
config_value!(GOOSE_DEFAULT_EXTENSION_TIMEOUT, u64);
|
||||
|
||||
// Core Goose Settings
|
||||
config_value!(GOOSE_MAX_TOKENS, i32);
|
||||
config_value!(GOOSE_CONTEXT_LIMIT, usize);
|
||||
config_value!(GOOSE_INPUT_LIMIT, usize);
|
||||
config_value!(GOOSE_MAX_TURNS, u32);
|
||||
config_value!(GOOSE_AUTO_COMPACT_THRESHOLD, f64);
|
||||
config_value!(GOOSE_TOOL_PAIR_SUMMARIZATION, bool);
|
||||
config_value!(GOOSE_TOOL_CALL_CUTOFF, usize);
|
||||
config_value!(GOOSE_STREAM_TIMEOUT, u64);
|
||||
config_value!(GOOSE_DISABLE_KEYRING, String);
|
||||
config_value!(GOOSE_TELEMETRY_ENABLED, bool);
|
||||
config_value!(GOOSE_ALLOWLIST, String);
|
||||
config_value!(GOOSE_SYSTEM_PROMPT_FILE_PATH, String);
|
||||
config_value!(GOOSE_DEBUG, bool);
|
||||
config_value!(GOOSE_SHOW_FULL_OUTPUT, bool);
|
||||
config_value!(GOOSE_STATUS_HOOK, String);
|
||||
config_value!(GOOSE_LOCAL_ENABLE_THINKING, bool);
|
||||
config_value!(GOOSE_DATABRICKS_CLIENT_REQUEST_ID, bool);
|
||||
config_value!(CONTEXT_FILE_NAMES, Vec<String>);
|
||||
config_value!(EDIT_MODE, String);
|
||||
config_value!(RANDOM_THINKING_MESSAGES, bool);
|
||||
config_value!(CODE_MODE_TOOL_DISCLOSURE, String);
|
||||
|
||||
// mTLS Settings
|
||||
config_value!(GOOSE_CLIENT_CERT_PATH, String);
|
||||
config_value!(GOOSE_CLIENT_KEY_PATH, String);
|
||||
config_value!(GOOSE_CA_CERT_PATH, String);
|
||||
|
||||
// Planner & Subagent Settings
|
||||
config_value!(GOOSE_PLANNER_PROVIDER, String);
|
||||
config_value!(GOOSE_PLANNER_MODEL, String);
|
||||
config_value!(GOOSE_SUBAGENT_PROVIDER, String);
|
||||
config_value!(GOOSE_SUBAGENT_MODEL, String);
|
||||
config_value!(GOOSE_SUBAGENT_MAX_TURNS, usize);
|
||||
config_value!(GOOSE_MAX_BACKGROUND_TASKS, usize);
|
||||
|
||||
// Recipe Settings
|
||||
config_value!(GOOSE_RECIPE_GITHUB_REPO, Option<String>);
|
||||
config_value!(GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS, u64);
|
||||
config_value!(GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS, u64);
|
||||
|
||||
// CLI Settings
|
||||
config_value!(GOOSE_CLI_MIN_PRIORITY, f32);
|
||||
config_value!(GOOSE_CLI_THEME, String);
|
||||
config_value!(GOOSE_CLI_LIGHT_THEME, String);
|
||||
config_value!(GOOSE_CLI_DARK_THEME, String);
|
||||
config_value!(GOOSE_CLI_SHOW_COST, bool);
|
||||
config_value!(GOOSE_CLI_SHOW_THINKING, bool);
|
||||
config_value!(GOOSE_CLI_NEWLINE_KEY, String);
|
||||
|
||||
// Security Settings
|
||||
config_value!(SECURITY_PROMPT_ENABLED, bool);
|
||||
config_value!(SECURITY_PROMPT_THRESHOLD, f64);
|
||||
config_value!(SECURITY_PROMPT_CLASSIFIER_ENABLED, bool);
|
||||
config_value!(SECURITY_PROMPT_CLASSIFIER_MODEL, String);
|
||||
config_value!(SECURITY_PROMPT_CLASSIFIER_ENDPOINT, String);
|
||||
config_value!(SECURITY_COMMAND_CLASSIFIER_ENABLED, bool);
|
||||
|
||||
// Provider Settings
|
||||
config_value!(OPENAI_HOST, String);
|
||||
config_value!(OPENAI_BASE_URL, String);
|
||||
config_value!(OPENAI_BASE_PATH, String);
|
||||
config_value!(OPENAI_ORGANIZATION, String);
|
||||
config_value!(OPENAI_PROJECT, String);
|
||||
config_value!(OPENAI_TIMEOUT, u64);
|
||||
config_value!(ANTHROPIC_HOST, String);
|
||||
config_value!(OLLAMA_HOST, String);
|
||||
config_value!(OLLAMA_TIMEOUT, u64);
|
||||
config_value!(OLLAMA_STREAM_TIMEOUT, u64);
|
||||
config_value!(OLLAMA_STREAM_USAGE, bool);
|
||||
config_value!(DATABRICKS_HOST, String);
|
||||
config_value!(DATABRICKS_MAX_RETRIES, String);
|
||||
config_value!(DATABRICKS_INITIAL_RETRY_INTERVAL_MS, String);
|
||||
config_value!(DATABRICKS_BACKOFF_MULTIPLIER, String);
|
||||
config_value!(DATABRICKS_MAX_RETRY_INTERVAL_MS, String);
|
||||
config_value!(AZURE_OPENAI_ENDPOINT, String);
|
||||
config_value!(AZURE_OPENAI_DEPLOYMENT_NAME, String);
|
||||
config_value!(AZURE_OPENAI_API_VERSION, String);
|
||||
config_value!(GOOGLE_HOST, String);
|
||||
config_value!(GCP_PROJECT_ID, String);
|
||||
config_value!(GCP_LOCATION, String);
|
||||
config_value!(GCP_MAX_RETRIES, String);
|
||||
config_value!(GCP_INITIAL_RETRY_INTERVAL_MS, String);
|
||||
config_value!(GCP_BACKOFF_MULTIPLIER, String);
|
||||
config_value!(GCP_MAX_RETRY_INTERVAL_MS, String);
|
||||
config_value!(AWS_REGION, String);
|
||||
config_value!(AWS_PROFILE, String);
|
||||
config_value!(BEDROCK_MAX_RETRIES, usize);
|
||||
config_value!(BEDROCK_INITIAL_RETRY_INTERVAL_MS, u64);
|
||||
config_value!(BEDROCK_BACKOFF_MULTIPLIER, f64);
|
||||
config_value!(BEDROCK_MAX_RETRY_INTERVAL_MS, u64);
|
||||
config_value!(BEDROCK_ENABLE_CACHING, bool);
|
||||
config_value!(SAGEMAKER_ENDPOINT_NAME, String);
|
||||
config_value!(LITELLM_HOST, String);
|
||||
config_value!(LITELLM_BASE_PATH, String);
|
||||
config_value!(LITELLM_TIMEOUT, u64);
|
||||
config_value!(SNOWFLAKE_HOST, String);
|
||||
config_value!(GITHUB_COPILOT_HOST, String);
|
||||
config_value!(GITHUB_COPILOT_CLIENT_ID, String);
|
||||
config_value!(GITHUB_COPILOT_TOKEN_URL, String);
|
||||
config_value!(XAI_HOST, String);
|
||||
config_value!(OPENROUTER_HOST, String);
|
||||
config_value!(VENICE_HOST, String);
|
||||
config_value!(VENICE_BASE_PATH, String);
|
||||
config_value!(VENICE_MODELS_PATH, String);
|
||||
config_value!(TETRATE_HOST, String);
|
||||
config_value!(AVIAN_HOST, String);
|
||||
|
||||
// Observability Settings
|
||||
config_value!(otel_exporter_otlp_endpoint, String);
|
||||
config_value!(otel_exporter_otlp_timeout, u64);
|
||||
|
||||
// Tunnel Settings
|
||||
config_value!(tunnel_auto_start, bool);
|
||||
|
||||
// Thinking Settings
|
||||
config_value!(GEMINI25_THINKING_BUDGET, i32);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::base::Config;
|
||||
use crate::agents::extension::PLATFORM_EXTENSIONS;
|
||||
use crate::agents::ExtensionConfig;
|
||||
use indexmap::IndexMap;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Mapping;
|
||||
use tracing::warn;
|
||||
@@ -17,7 +18,7 @@ fn default_extension_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, ToSchema, JsonSchema)]
|
||||
pub struct ExtensionEntry {
|
||||
#[serde(default = "default_extension_enabled")]
|
||||
pub enabled: bool,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumMessage, EnumString, IntoStaticStr, VariantNames};
|
||||
use utoipa::ToSchema;
|
||||
@@ -18,6 +19,7 @@ use utoipa::ToSchema;
|
||||
IntoStaticStr,
|
||||
VariantNames,
|
||||
ToSchema,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod goose_mode;
|
||||
mod migrations;
|
||||
pub mod paths;
|
||||
pub mod permission;
|
||||
pub mod schema;
|
||||
pub mod search_path;
|
||||
pub mod signup_nanogpt;
|
||||
pub mod signup_openrouter;
|
||||
|
||||
@@ -0,0 +1,476 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::extensions::ExtensionEntry;
|
||||
use crate::config::goose_mode::GooseMode;
|
||||
use crate::slash_commands::SlashCommandMapping;
|
||||
|
||||
/// JSON Schema representation of Goose's config.yaml.
|
||||
///
|
||||
/// All keys are optional. Unknown keys are allowed (additionalProperties: true)
|
||||
/// because Goose passes undocumented provider-specific keys through as
|
||||
/// environment variable overrides.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct GooseConfigSchema {
|
||||
// === Core Goose Settings ===
|
||||
#[serde(rename = "GOOSE_PROVIDER")]
|
||||
pub goose_provider: Option<String>,
|
||||
#[serde(rename = "GOOSE_MODEL")]
|
||||
pub goose_model: Option<String>,
|
||||
#[serde(rename = "GOOSE_MODE")]
|
||||
pub goose_mode: Option<GooseMode>,
|
||||
#[serde(rename = "GOOSE_MAX_TOKENS")]
|
||||
pub goose_max_tokens: Option<i32>,
|
||||
#[serde(rename = "GOOSE_CONTEXT_LIMIT")]
|
||||
pub goose_context_limit: Option<u64>,
|
||||
#[serde(rename = "GOOSE_INPUT_LIMIT")]
|
||||
pub goose_input_limit: Option<u64>,
|
||||
#[serde(rename = "GOOSE_MAX_TURNS")]
|
||||
pub goose_max_turns: Option<u32>,
|
||||
#[serde(rename = "GOOSE_MAX_ACTIVE_AGENTS")]
|
||||
pub goose_max_active_agents: Option<u64>,
|
||||
#[serde(rename = "GOOSE_AUTO_COMPACT_THRESHOLD")]
|
||||
pub goose_auto_compact_threshold: Option<f64>,
|
||||
#[serde(rename = "GOOSE_TOOL_PAIR_SUMMARIZATION")]
|
||||
pub goose_tool_pair_summarization: Option<bool>,
|
||||
#[serde(rename = "GOOSE_TOOL_CALL_CUTOFF")]
|
||||
pub goose_tool_call_cutoff: Option<u64>,
|
||||
#[serde(rename = "GOOSE_STREAM_TIMEOUT")]
|
||||
pub goose_stream_timeout: Option<u64>,
|
||||
#[serde(rename = "GOOSE_SEARCH_PATHS")]
|
||||
pub goose_search_paths: Option<Vec<String>>,
|
||||
#[serde(rename = "GOOSE_DISABLE_SESSION_NAMING")]
|
||||
pub goose_disable_session_naming: Option<bool>,
|
||||
#[serde(rename = "GOOSE_DISABLE_KEYRING")]
|
||||
pub goose_disable_keyring: Option<String>,
|
||||
#[serde(rename = "GOOSE_TELEMETRY_ENABLED")]
|
||||
pub goose_telemetry_enabled: Option<bool>,
|
||||
#[serde(rename = "GOOSE_DEFAULT_EXTENSION_TIMEOUT")]
|
||||
pub goose_default_extension_timeout: Option<u64>,
|
||||
#[serde(rename = "GOOSE_PROMPT_EDITOR")]
|
||||
pub goose_prompt_editor: Option<String>,
|
||||
#[serde(rename = "GOOSE_PROMPT_EDITOR_ALWAYS")]
|
||||
pub goose_prompt_editor_always: Option<bool>,
|
||||
#[serde(rename = "GOOSE_ALLOWLIST")]
|
||||
pub goose_allowlist: Option<String>,
|
||||
#[serde(rename = "GOOSE_SYSTEM_PROMPT_FILE_PATH")]
|
||||
pub goose_system_prompt_file_path: Option<String>,
|
||||
#[serde(rename = "GOOSE_DEBUG")]
|
||||
pub goose_debug: Option<bool>,
|
||||
#[serde(rename = "GOOSE_SHOW_FULL_OUTPUT")]
|
||||
pub goose_show_full_output: Option<bool>,
|
||||
#[serde(rename = "GOOSE_STATUS_HOOK")]
|
||||
pub goose_status_hook: Option<String>,
|
||||
#[serde(rename = "GOOSE_LOCAL_ENABLE_THINKING")]
|
||||
pub goose_local_enable_thinking: Option<bool>,
|
||||
#[serde(rename = "GOOSE_DATABRICKS_CLIENT_REQUEST_ID")]
|
||||
pub goose_databricks_client_request_id: Option<bool>,
|
||||
#[serde(rename = "CONTEXT_FILE_NAMES")]
|
||||
pub context_file_names: Option<Vec<String>>,
|
||||
#[serde(rename = "EDIT_MODE")]
|
||||
pub edit_mode: Option<String>,
|
||||
#[serde(rename = "RANDOM_THINKING_MESSAGES")]
|
||||
pub random_thinking_messages: Option<bool>,
|
||||
#[serde(rename = "CODE_MODE_TOOL_DISCLOSURE")]
|
||||
pub code_mode_tool_disclosure: Option<String>,
|
||||
|
||||
// === mTLS Settings ===
|
||||
#[serde(rename = "GOOSE_CLIENT_CERT_PATH")]
|
||||
pub goose_client_cert_path: Option<String>,
|
||||
#[serde(rename = "GOOSE_CLIENT_KEY_PATH")]
|
||||
pub goose_client_key_path: Option<String>,
|
||||
#[serde(rename = "GOOSE_CA_CERT_PATH")]
|
||||
pub goose_ca_cert_path: Option<String>,
|
||||
|
||||
// === Planner & Subagent Settings ===
|
||||
#[serde(rename = "GOOSE_PLANNER_PROVIDER")]
|
||||
pub goose_planner_provider: Option<String>,
|
||||
#[serde(rename = "GOOSE_PLANNER_MODEL")]
|
||||
pub goose_planner_model: Option<String>,
|
||||
#[serde(rename = "GOOSE_SUBAGENT_PROVIDER")]
|
||||
pub goose_subagent_provider: Option<String>,
|
||||
#[serde(rename = "GOOSE_SUBAGENT_MODEL")]
|
||||
pub goose_subagent_model: Option<String>,
|
||||
#[serde(rename = "GOOSE_SUBAGENT_MAX_TURNS")]
|
||||
pub goose_subagent_max_turns: Option<u64>,
|
||||
#[serde(rename = "GOOSE_MAX_BACKGROUND_TASKS")]
|
||||
pub goose_max_background_tasks: Option<u64>,
|
||||
|
||||
// === Recipe Settings ===
|
||||
#[serde(rename = "GOOSE_RECIPE_GITHUB_REPO")]
|
||||
pub goose_recipe_github_repo: Option<String>,
|
||||
#[serde(rename = "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS")]
|
||||
pub goose_recipe_retry_timeout_seconds: Option<u64>,
|
||||
#[serde(rename = "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS")]
|
||||
pub goose_recipe_on_failure_timeout_seconds: Option<u64>,
|
||||
|
||||
// === CLI Settings ===
|
||||
#[serde(rename = "GOOSE_CLI_MIN_PRIORITY")]
|
||||
pub goose_cli_min_priority: Option<f32>,
|
||||
#[serde(rename = "GOOSE_CLI_THEME")]
|
||||
pub goose_cli_theme: Option<String>,
|
||||
#[serde(rename = "GOOSE_CLI_LIGHT_THEME")]
|
||||
pub goose_cli_light_theme: Option<String>,
|
||||
#[serde(rename = "GOOSE_CLI_DARK_THEME")]
|
||||
pub goose_cli_dark_theme: Option<String>,
|
||||
#[serde(rename = "GOOSE_CLI_SHOW_COST")]
|
||||
pub goose_cli_show_cost: Option<bool>,
|
||||
#[serde(rename = "GOOSE_CLI_SHOW_THINKING")]
|
||||
pub goose_cli_show_thinking: Option<bool>,
|
||||
#[serde(rename = "GOOSE_CLI_NEWLINE_KEY")]
|
||||
pub goose_cli_newline_key: Option<String>,
|
||||
|
||||
// === AI Agent / Thinking Settings ===
|
||||
#[serde(rename = "CLAUDE_CODE_COMMAND")]
|
||||
pub claude_code_command: Option<String>,
|
||||
#[serde(rename = "GEMINI_CLI_COMMAND")]
|
||||
pub gemini_cli_command: Option<String>,
|
||||
#[serde(rename = "CURSOR_AGENT_COMMAND")]
|
||||
pub cursor_agent_command: Option<String>,
|
||||
#[serde(rename = "CODEX_COMMAND")]
|
||||
pub codex_command: Option<String>,
|
||||
#[serde(rename = "CODEX_REASONING_EFFORT")]
|
||||
pub codex_reasoning_effort: Option<String>,
|
||||
#[serde(rename = "CODEX_ENABLE_SKILLS")]
|
||||
pub codex_enable_skills: Option<String>,
|
||||
#[serde(rename = "CODEX_SKIP_GIT_CHECK")]
|
||||
pub codex_skip_git_check: Option<String>,
|
||||
#[serde(rename = "CHATGPT_CODEX_REASONING_EFFORT")]
|
||||
pub chatgpt_codex_reasoning_effort: Option<String>,
|
||||
#[serde(rename = "CLAUDE_THINKING_TYPE")]
|
||||
pub claude_thinking_type: Option<String>,
|
||||
#[serde(rename = "CLAUDE_THINKING_EFFORT")]
|
||||
pub claude_thinking_effort: Option<String>,
|
||||
#[serde(rename = "CLAUDE_THINKING_BUDGET")]
|
||||
pub claude_thinking_budget: Option<i32>,
|
||||
#[serde(rename = "GEMINI3_THINKING_LEVEL")]
|
||||
pub gemini3_thinking_level: Option<String>,
|
||||
#[serde(rename = "GEMINI25_THINKING_BUDGET")]
|
||||
pub gemini25_thinking_budget: Option<i32>,
|
||||
|
||||
// === Security Settings ===
|
||||
#[serde(rename = "SECURITY_PROMPT_ENABLED")]
|
||||
pub security_prompt_enabled: Option<bool>,
|
||||
#[serde(rename = "SECURITY_PROMPT_THRESHOLD")]
|
||||
pub security_prompt_threshold: Option<f64>,
|
||||
#[serde(rename = "SECURITY_PROMPT_CLASSIFIER_ENABLED")]
|
||||
pub security_prompt_classifier_enabled: Option<bool>,
|
||||
#[serde(rename = "SECURITY_PROMPT_CLASSIFIER_MODEL")]
|
||||
pub security_prompt_classifier_model: Option<String>,
|
||||
#[serde(rename = "SECURITY_PROMPT_CLASSIFIER_ENDPOINT")]
|
||||
pub security_prompt_classifier_endpoint: Option<String>,
|
||||
#[serde(rename = "SECURITY_COMMAND_CLASSIFIER_ENABLED")]
|
||||
pub security_command_classifier_enabled: Option<bool>,
|
||||
|
||||
// === Provider Settings ===
|
||||
#[serde(rename = "OPENAI_HOST")]
|
||||
pub openai_host: Option<String>,
|
||||
#[serde(rename = "OPENAI_BASE_URL")]
|
||||
pub openai_base_url: Option<String>,
|
||||
#[serde(rename = "OPENAI_BASE_PATH")]
|
||||
pub openai_base_path: Option<String>,
|
||||
#[serde(rename = "OPENAI_ORGANIZATION")]
|
||||
pub openai_organization: Option<String>,
|
||||
#[serde(rename = "OPENAI_PROJECT")]
|
||||
pub openai_project: Option<String>,
|
||||
#[serde(rename = "OPENAI_TIMEOUT")]
|
||||
pub openai_timeout: Option<u64>,
|
||||
#[serde(rename = "ANTHROPIC_HOST")]
|
||||
pub anthropic_host: Option<String>,
|
||||
#[serde(rename = "OLLAMA_HOST")]
|
||||
pub ollama_host: Option<String>,
|
||||
#[serde(rename = "OLLAMA_TIMEOUT")]
|
||||
pub ollama_timeout: Option<u64>,
|
||||
#[serde(rename = "OLLAMA_STREAM_TIMEOUT")]
|
||||
pub ollama_stream_timeout: Option<u64>,
|
||||
#[serde(rename = "OLLAMA_STREAM_USAGE")]
|
||||
pub ollama_stream_usage: Option<bool>,
|
||||
#[serde(rename = "DATABRICKS_HOST")]
|
||||
pub databricks_host: Option<String>,
|
||||
#[serde(rename = "DATABRICKS_MAX_RETRIES")]
|
||||
pub databricks_max_retries: Option<String>,
|
||||
#[serde(rename = "DATABRICKS_INITIAL_RETRY_INTERVAL_MS")]
|
||||
pub databricks_initial_retry_interval_ms: Option<String>,
|
||||
#[serde(rename = "DATABRICKS_BACKOFF_MULTIPLIER")]
|
||||
pub databricks_backoff_multiplier: Option<String>,
|
||||
#[serde(rename = "DATABRICKS_MAX_RETRY_INTERVAL_MS")]
|
||||
pub databricks_max_retry_interval_ms: Option<String>,
|
||||
#[serde(rename = "AZURE_OPENAI_ENDPOINT")]
|
||||
pub azure_openai_endpoint: Option<String>,
|
||||
#[serde(rename = "AZURE_OPENAI_DEPLOYMENT_NAME")]
|
||||
pub azure_openai_deployment_name: Option<String>,
|
||||
#[serde(rename = "AZURE_OPENAI_API_VERSION")]
|
||||
pub azure_openai_api_version: Option<String>,
|
||||
#[serde(rename = "GOOGLE_HOST")]
|
||||
pub google_host: Option<String>,
|
||||
#[serde(rename = "GCP_PROJECT_ID")]
|
||||
pub gcp_project_id: Option<String>,
|
||||
#[serde(rename = "GCP_LOCATION")]
|
||||
pub gcp_location: Option<String>,
|
||||
#[serde(rename = "GCP_MAX_RETRIES")]
|
||||
pub gcp_max_retries: Option<String>,
|
||||
#[serde(rename = "GCP_INITIAL_RETRY_INTERVAL_MS")]
|
||||
pub gcp_initial_retry_interval_ms: Option<String>,
|
||||
#[serde(rename = "GCP_BACKOFF_MULTIPLIER")]
|
||||
pub gcp_backoff_multiplier: Option<String>,
|
||||
#[serde(rename = "GCP_MAX_RETRY_INTERVAL_MS")]
|
||||
pub gcp_max_retry_interval_ms: Option<String>,
|
||||
#[serde(rename = "AWS_REGION")]
|
||||
pub aws_region: Option<String>,
|
||||
#[serde(rename = "AWS_PROFILE")]
|
||||
pub aws_profile: Option<String>,
|
||||
#[serde(rename = "BEDROCK_MAX_RETRIES")]
|
||||
pub bedrock_max_retries: Option<u64>,
|
||||
#[serde(rename = "BEDROCK_INITIAL_RETRY_INTERVAL_MS")]
|
||||
pub bedrock_initial_retry_interval_ms: Option<u64>,
|
||||
#[serde(rename = "BEDROCK_BACKOFF_MULTIPLIER")]
|
||||
pub bedrock_backoff_multiplier: Option<f64>,
|
||||
#[serde(rename = "BEDROCK_MAX_RETRY_INTERVAL_MS")]
|
||||
pub bedrock_max_retry_interval_ms: Option<u64>,
|
||||
#[serde(rename = "BEDROCK_ENABLE_CACHING")]
|
||||
pub bedrock_enable_caching: Option<bool>,
|
||||
#[serde(rename = "SAGEMAKER_ENDPOINT_NAME")]
|
||||
pub sagemaker_endpoint_name: Option<String>,
|
||||
#[serde(rename = "LITELLM_HOST")]
|
||||
pub litellm_host: Option<String>,
|
||||
#[serde(rename = "LITELLM_BASE_PATH")]
|
||||
pub litellm_base_path: Option<String>,
|
||||
#[serde(rename = "LITELLM_TIMEOUT")]
|
||||
pub litellm_timeout: Option<u64>,
|
||||
#[serde(rename = "SNOWFLAKE_HOST")]
|
||||
pub snowflake_host: Option<String>,
|
||||
#[serde(rename = "GITHUB_COPILOT_HOST")]
|
||||
pub github_copilot_host: Option<String>,
|
||||
#[serde(rename = "GITHUB_COPILOT_CLIENT_ID")]
|
||||
pub github_copilot_client_id: Option<String>,
|
||||
#[serde(rename = "GITHUB_COPILOT_TOKEN_URL")]
|
||||
pub github_copilot_token_url: Option<String>,
|
||||
#[serde(rename = "XAI_HOST")]
|
||||
pub xai_host: Option<String>,
|
||||
#[serde(rename = "OPENROUTER_HOST")]
|
||||
pub openrouter_host: Option<String>,
|
||||
#[serde(rename = "VENICE_HOST")]
|
||||
pub venice_host: Option<String>,
|
||||
#[serde(rename = "VENICE_BASE_PATH")]
|
||||
pub venice_base_path: Option<String>,
|
||||
#[serde(rename = "VENICE_MODELS_PATH")]
|
||||
pub venice_models_path: Option<String>,
|
||||
#[serde(rename = "TETRATE_HOST")]
|
||||
pub tetrate_host: Option<String>,
|
||||
#[serde(rename = "AVIAN_HOST")]
|
||||
pub avian_host: Option<String>,
|
||||
|
||||
// === Observability Settings (lowercase keys) ===
|
||||
pub otel_exporter_otlp_endpoint: Option<String>,
|
||||
pub otel_exporter_otlp_timeout: Option<u64>,
|
||||
|
||||
// === Tunnel Settings (lowercase keys) ===
|
||||
pub tunnel_auto_start: Option<bool>,
|
||||
|
||||
// === Structured Config (lowercase keys) ===
|
||||
pub extensions: Option<HashMap<String, ExtensionEntry>>,
|
||||
pub slash_commands: Option<Vec<SlashCommandMapping>>,
|
||||
pub experiments: Option<HashMap<String, bool>>,
|
||||
}
|
||||
|
||||
impl GooseConfigSchema {
|
||||
/// All user-facing config keys that get `config_value!` typed accessors.
|
||||
/// Category B keys (extensions, slash_commands, experiments) are in the struct
|
||||
/// for schema generation but NOT here — they use dedicated module helpers.
|
||||
pub const ALL_KEYS: &[&str] = &[
|
||||
// Core Goose Settings
|
||||
"GOOSE_PROVIDER",
|
||||
"GOOSE_MODEL",
|
||||
"GOOSE_MODE",
|
||||
"GOOSE_MAX_TOKENS",
|
||||
"GOOSE_CONTEXT_LIMIT",
|
||||
"GOOSE_INPUT_LIMIT",
|
||||
"GOOSE_MAX_TURNS",
|
||||
"GOOSE_MAX_ACTIVE_AGENTS",
|
||||
"GOOSE_AUTO_COMPACT_THRESHOLD",
|
||||
"GOOSE_TOOL_PAIR_SUMMARIZATION",
|
||||
"GOOSE_TOOL_CALL_CUTOFF",
|
||||
"GOOSE_STREAM_TIMEOUT",
|
||||
"GOOSE_SEARCH_PATHS",
|
||||
"GOOSE_DISABLE_SESSION_NAMING",
|
||||
"GOOSE_DISABLE_KEYRING",
|
||||
"GOOSE_TELEMETRY_ENABLED",
|
||||
"GOOSE_DEFAULT_EXTENSION_TIMEOUT",
|
||||
"GOOSE_PROMPT_EDITOR",
|
||||
"GOOSE_PROMPT_EDITOR_ALWAYS",
|
||||
"GOOSE_ALLOWLIST",
|
||||
"GOOSE_SYSTEM_PROMPT_FILE_PATH",
|
||||
"GOOSE_DEBUG",
|
||||
"GOOSE_SHOW_FULL_OUTPUT",
|
||||
"GOOSE_STATUS_HOOK",
|
||||
"GOOSE_LOCAL_ENABLE_THINKING",
|
||||
"GOOSE_DATABRICKS_CLIENT_REQUEST_ID",
|
||||
"CONTEXT_FILE_NAMES",
|
||||
"EDIT_MODE",
|
||||
"RANDOM_THINKING_MESSAGES",
|
||||
"CODE_MODE_TOOL_DISCLOSURE",
|
||||
// mTLS Settings
|
||||
"GOOSE_CLIENT_CERT_PATH",
|
||||
"GOOSE_CLIENT_KEY_PATH",
|
||||
"GOOSE_CA_CERT_PATH",
|
||||
// Planner & Subagent Settings
|
||||
"GOOSE_PLANNER_PROVIDER",
|
||||
"GOOSE_PLANNER_MODEL",
|
||||
"GOOSE_SUBAGENT_PROVIDER",
|
||||
"GOOSE_SUBAGENT_MODEL",
|
||||
"GOOSE_SUBAGENT_MAX_TURNS",
|
||||
"GOOSE_MAX_BACKGROUND_TASKS",
|
||||
// Recipe Settings
|
||||
"GOOSE_RECIPE_GITHUB_REPO",
|
||||
"GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS",
|
||||
"GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS",
|
||||
// CLI Settings
|
||||
"GOOSE_CLI_MIN_PRIORITY",
|
||||
"GOOSE_CLI_THEME",
|
||||
"GOOSE_CLI_LIGHT_THEME",
|
||||
"GOOSE_CLI_DARK_THEME",
|
||||
"GOOSE_CLI_SHOW_COST",
|
||||
"GOOSE_CLI_SHOW_THINKING",
|
||||
"GOOSE_CLI_NEWLINE_KEY",
|
||||
// AI Agent / Thinking Settings
|
||||
"CLAUDE_CODE_COMMAND",
|
||||
"GEMINI_CLI_COMMAND",
|
||||
"CURSOR_AGENT_COMMAND",
|
||||
"CODEX_COMMAND",
|
||||
"CODEX_REASONING_EFFORT",
|
||||
"CODEX_ENABLE_SKILLS",
|
||||
"CODEX_SKIP_GIT_CHECK",
|
||||
"CHATGPT_CODEX_REASONING_EFFORT",
|
||||
"CLAUDE_THINKING_TYPE",
|
||||
"CLAUDE_THINKING_EFFORT",
|
||||
"CLAUDE_THINKING_BUDGET",
|
||||
"GEMINI3_THINKING_LEVEL",
|
||||
"GEMINI25_THINKING_BUDGET",
|
||||
// Security Settings
|
||||
"SECURITY_PROMPT_ENABLED",
|
||||
"SECURITY_PROMPT_THRESHOLD",
|
||||
"SECURITY_PROMPT_CLASSIFIER_ENABLED",
|
||||
"SECURITY_PROMPT_CLASSIFIER_MODEL",
|
||||
"SECURITY_PROMPT_CLASSIFIER_ENDPOINT",
|
||||
"SECURITY_COMMAND_CLASSIFIER_ENABLED",
|
||||
// Provider Settings
|
||||
"OPENAI_HOST",
|
||||
"OPENAI_BASE_URL",
|
||||
"OPENAI_BASE_PATH",
|
||||
"OPENAI_ORGANIZATION",
|
||||
"OPENAI_PROJECT",
|
||||
"OPENAI_TIMEOUT",
|
||||
"ANTHROPIC_HOST",
|
||||
"OLLAMA_HOST",
|
||||
"OLLAMA_TIMEOUT",
|
||||
"OLLAMA_STREAM_TIMEOUT",
|
||||
"OLLAMA_STREAM_USAGE",
|
||||
"DATABRICKS_HOST",
|
||||
"DATABRICKS_MAX_RETRIES",
|
||||
"DATABRICKS_INITIAL_RETRY_INTERVAL_MS",
|
||||
"DATABRICKS_BACKOFF_MULTIPLIER",
|
||||
"DATABRICKS_MAX_RETRY_INTERVAL_MS",
|
||||
"AZURE_OPENAI_ENDPOINT",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME",
|
||||
"AZURE_OPENAI_API_VERSION",
|
||||
"GOOGLE_HOST",
|
||||
"GCP_PROJECT_ID",
|
||||
"GCP_LOCATION",
|
||||
"GCP_MAX_RETRIES",
|
||||
"GCP_INITIAL_RETRY_INTERVAL_MS",
|
||||
"GCP_BACKOFF_MULTIPLIER",
|
||||
"GCP_MAX_RETRY_INTERVAL_MS",
|
||||
"AWS_REGION",
|
||||
"AWS_PROFILE",
|
||||
"BEDROCK_MAX_RETRIES",
|
||||
"BEDROCK_INITIAL_RETRY_INTERVAL_MS",
|
||||
"BEDROCK_BACKOFF_MULTIPLIER",
|
||||
"BEDROCK_MAX_RETRY_INTERVAL_MS",
|
||||
"BEDROCK_ENABLE_CACHING",
|
||||
"SAGEMAKER_ENDPOINT_NAME",
|
||||
"LITELLM_HOST",
|
||||
"LITELLM_BASE_PATH",
|
||||
"LITELLM_TIMEOUT",
|
||||
"SNOWFLAKE_HOST",
|
||||
"GITHUB_COPILOT_HOST",
|
||||
"GITHUB_COPILOT_CLIENT_ID",
|
||||
"GITHUB_COPILOT_TOKEN_URL",
|
||||
"XAI_HOST",
|
||||
"OPENROUTER_HOST",
|
||||
"VENICE_HOST",
|
||||
"VENICE_BASE_PATH",
|
||||
"VENICE_MODELS_PATH",
|
||||
"TETRATE_HOST",
|
||||
"AVIAN_HOST",
|
||||
// Observability Settings
|
||||
"otel_exporter_otlp_endpoint",
|
||||
"otel_exporter_otlp_timeout",
|
||||
// Tunnel Settings
|
||||
"tunnel_auto_start",
|
||||
];
|
||||
|
||||
pub const fn has_key(key: &str) -> bool {
|
||||
let key_bytes = key.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < Self::ALL_KEYS.len() {
|
||||
let candidate = Self::ALL_KEYS[i].as_bytes();
|
||||
if candidate.len() == key_bytes.len() {
|
||||
let mut j = 0;
|
||||
let mut eq = true;
|
||||
while j < key_bytes.len() {
|
||||
if candidate[j] != key_bytes[j] {
|
||||
eq = false;
|
||||
break;
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
if eq {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use schemars::schema_for;
|
||||
|
||||
#[test]
|
||||
fn all_keys_matches_struct_fields() {
|
||||
let schema = schema_for!(GooseConfigSchema);
|
||||
let obj = schema.as_object().expect("schema should be an object");
|
||||
let properties = obj
|
||||
.get("properties")
|
||||
.and_then(|p| p.as_object())
|
||||
.expect("schema should have properties");
|
||||
|
||||
let schema_keys: std::collections::HashSet<&str> =
|
||||
properties.keys().map(|k| k.as_str()).collect();
|
||||
|
||||
for key in GooseConfigSchema::ALL_KEYS {
|
||||
assert!(
|
||||
schema_keys.contains(key),
|
||||
"ALL_KEYS contains '{key}' but GooseConfigSchema has no field with serde(rename = \"{key}\")"
|
||||
);
|
||||
}
|
||||
|
||||
// Category B keys are in the struct but NOT in ALL_KEYS — that's intentional
|
||||
let category_b = ["extensions", "slash_commands", "experiments"];
|
||||
for key in &category_b {
|
||||
assert!(
|
||||
schema_keys.contains(key),
|
||||
"Category B key '{key}' should be in the schema struct for IDE autocomplete"
|
||||
);
|
||||
assert!(
|
||||
!GooseConfigSchema::has_key(key),
|
||||
"Category B key '{key}' should NOT be in ALL_KEYS"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ const TOOLCALL_SUMMARIZATION_BATCH_SIZE: usize = 10;
|
||||
|
||||
fn tool_pair_summarization_enabled() -> bool {
|
||||
Config::global()
|
||||
.get_param::<bool>("GOOSE_TOOL_PAIR_SUMMARIZATION")
|
||||
.get_goose_tool_pair_summarization()
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ pub async fn check_if_compaction_needed(
|
||||
let config = Config::global();
|
||||
let threshold = threshold_override.unwrap_or_else(|| {
|
||||
config
|
||||
.get_param::<f64>("GOOSE_AUTO_COMPACT_THRESHOLD")
|
||||
.get_goose_auto_compact_threshold()
|
||||
.unwrap_or(DEFAULT_COMPACTION_THRESHOLD)
|
||||
});
|
||||
|
||||
|
||||
@@ -210,13 +210,13 @@ fn build_api_client(provider: DictationProvider) -> Result<(ApiClient, String)>
|
||||
})?;
|
||||
|
||||
let (base_url, query_params, endpoint_path) = if provider == DictationProvider::OpenAI {
|
||||
let openai_base_url = config.get_param::<String>("OPENAI_BASE_URL").ok();
|
||||
let openai_base_url = config.get_openai_base_url().ok();
|
||||
|
||||
if let Ok(host) = std::env::var("OPENAI_HOST") {
|
||||
(host, vec![], def.endpoint_path.to_string())
|
||||
} else if let Some(target) = resolve_openai_base_url_target(openai_base_url.as_deref())? {
|
||||
target
|
||||
} else if let Ok(host) = config.get_param::<String>("OPENAI_HOST") {
|
||||
} else if let Ok(host) = config.get_openai_host() {
|
||||
(host, vec![], def.endpoint_path.to_string())
|
||||
} else {
|
||||
(
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn get_context_filenames() -> Vec<String> {
|
||||
use crate::config::Config;
|
||||
|
||||
Config::global()
|
||||
.get_param::<Vec<String>>("CONTEXT_FILE_NAMES")
|
||||
.get_context_file_names()
|
||||
.unwrap_or_else(|_| {
|
||||
vec![
|
||||
GOOSE_HINTS_FILENAME.to_string(),
|
||||
|
||||
@@ -88,7 +88,7 @@ impl ModelConfig {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
match crate::config::Config::global().get_param::<usize>("GOOSE_CONTEXT_LIMIT") {
|
||||
match crate::config::Config::global().get_goose_context_limit() {
|
||||
Ok(limit) => {
|
||||
if limit == 0 {
|
||||
return Err(ConfigError::InvalidRange(
|
||||
@@ -213,7 +213,7 @@ impl ModelConfig {
|
||||
}
|
||||
|
||||
fn parse_max_tokens() -> Result<Option<i32>, ConfigError> {
|
||||
match crate::config::Config::global().get_param::<i32>("GOOSE_MAX_TOKENS") {
|
||||
match crate::config::Config::global().get_goose_max_tokens() {
|
||||
Ok(tokens) => {
|
||||
if tokens <= 0 {
|
||||
return Err(ConfigError::InvalidRange(
|
||||
|
||||
@@ -83,12 +83,12 @@ pub fn signal_exporter(signal: &str) -> Option<ExporterType> {
|
||||
/// Promotes goose config-file OTel settings to env vars before exporter build.
|
||||
pub fn promote_config_to_env(config: &crate::config::Config) {
|
||||
if env::var("OTEL_EXPORTER_OTLP_ENDPOINT").is_err() {
|
||||
if let Ok(endpoint) = config.get_param::<String>("otel_exporter_otlp_endpoint") {
|
||||
if let Ok(endpoint) = config.get_otel_exporter_otlp_endpoint() {
|
||||
env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", endpoint);
|
||||
}
|
||||
}
|
||||
if env::var("OTEL_EXPORTER_OTLP_TIMEOUT").is_err() {
|
||||
if let Ok(timeout) = config.get_param::<u64>("otel_exporter_otlp_timeout") {
|
||||
if let Ok(timeout) = config.get_otel_exporter_otlp_timeout() {
|
||||
env::set_var("OTEL_EXPORTER_OTLP_TIMEOUT", timeout.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,6 @@ use uuid::Uuid;
|
||||
const POSTHOG_API_KEY: &str = "phc_RyX5CaY01VtZJCQyhSR5KFh6qimUy81YwxsEpotAftT";
|
||||
const POSTHOG_CAPTURE_URL: &str = "https://us.i.posthog.com/capture/";
|
||||
|
||||
/// Config key for telemetry opt-out preference
|
||||
pub const TELEMETRY_ENABLED_KEY: &str = "GOOSE_TELEMETRY_ENABLED";
|
||||
|
||||
static TELEMETRY_DISABLED_BY_ENV: Lazy<AtomicBool> = Lazy::new(|| {
|
||||
std::env::var("GOOSE_TELEMETRY_OFF")
|
||||
.map(|v| v == "1" || v.to_lowercase() == "true")
|
||||
@@ -36,7 +33,7 @@ pub fn get_telemetry_choice() -> Option<bool> {
|
||||
}
|
||||
|
||||
let config = Config::global();
|
||||
config.get_param::<bool>(TELEMETRY_ENABLED_KEY).ok()
|
||||
config.get_goose_telemetry_enabled().ok()
|
||||
}
|
||||
|
||||
/// Check if telemetry is enabled.
|
||||
@@ -355,10 +352,10 @@ async fn send_error_event(
|
||||
}
|
||||
|
||||
let config = Config::global();
|
||||
if let Ok(provider) = config.get_param::<String>("GOOSE_PROVIDER") {
|
||||
if let Ok(provider) = config.get_goose_provider() {
|
||||
insert(&mut props, "provider", provider);
|
||||
}
|
||||
if let Ok(model) = config.get_param::<String>("GOOSE_MODEL") {
|
||||
if let Ok(model) = config.get_goose_model() {
|
||||
insert(&mut props, "model", model);
|
||||
}
|
||||
|
||||
@@ -407,17 +404,17 @@ async fn send_session_event(installation: &InstallationData) -> Result<(), Strin
|
||||
insert(&mut props, "days_since_install", days_since_install);
|
||||
|
||||
let config = Config::global();
|
||||
if let Ok(provider) = config.get_param::<String>("GOOSE_PROVIDER") {
|
||||
if let Ok(provider) = config.get_goose_provider() {
|
||||
insert(&mut props, "provider", provider);
|
||||
}
|
||||
if let Ok(model) = config.get_param::<String>("GOOSE_MODEL") {
|
||||
if let Ok(model) = config.get_goose_model() {
|
||||
insert(&mut props, "model", model);
|
||||
}
|
||||
|
||||
if let Ok(mode) = config.get_param::<String>("GOOSE_MODE") {
|
||||
insert(&mut props, "setting_mode", mode);
|
||||
if let Ok(mode) = config.get_goose_mode() {
|
||||
insert(&mut props, "setting_mode", mode.to_string());
|
||||
}
|
||||
if let Ok(max_turns) = config.get_param::<i64>("GOOSE_MAX_TURNS") {
|
||||
if let Some(max_turns) = config.get_goose_max_turns().ok().map(|v| v as i64) {
|
||||
insert(&mut props, "setting_max_turns", max_turns);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ impl AnthropicProvider {
|
||||
let config = crate::config::Config::global();
|
||||
let api_key: String = config.get_secret("ANTHROPIC_API_KEY")?;
|
||||
let host: String = config
|
||||
.get_param("ANTHROPIC_HOST")
|
||||
.get_anthropic_host()
|
||||
.unwrap_or_else(|_| "https://api.anthropic.com".to_string());
|
||||
|
||||
let auth = AuthMethod::ApiKey {
|
||||
@@ -248,7 +248,7 @@ impl ProviderDef for AnthropicProvider {
|
||||
.with_public(
|
||||
"host",
|
||||
config
|
||||
.get_param::<String>("ANTHROPIC_HOST")
|
||||
.get_anthropic_host()
|
||||
.unwrap_or_else(|_| "https://api.anthropic.com".to_string()),
|
||||
);
|
||||
|
||||
|
||||
@@ -58,8 +58,8 @@ impl TlsConfig {
|
||||
let mut tls_config = TlsConfig::new();
|
||||
let mut has_tls_config = false;
|
||||
|
||||
let client_cert_path = config.get_param::<String>("GOOSE_CLIENT_CERT_PATH").ok();
|
||||
let client_key_path = config.get_param::<String>("GOOSE_CLIENT_KEY_PATH").ok();
|
||||
let client_cert_path = config.get_goose_client_cert_path().ok();
|
||||
let client_key_path = config.get_goose_client_key_path().ok();
|
||||
|
||||
// Validate that both cert and key are provided if either is provided
|
||||
match (client_cert_path, client_key_path) {
|
||||
@@ -83,7 +83,7 @@ impl TlsConfig {
|
||||
(None, None) => {}
|
||||
}
|
||||
|
||||
if let Ok(ca_cert_path) = config.get_param::<String>("GOOSE_CA_CERT_PATH") {
|
||||
if let Ok(ca_cert_path) = config.get_goose_ca_cert_path() {
|
||||
tls_config = tls_config.with_ca_cert(std::path::PathBuf::from(ca_cert_path));
|
||||
has_tls_config = true;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ impl ProviderDef for AvianProvider {
|
||||
let config = crate::config::Config::global();
|
||||
let api_key: String = config.get_secret("AVIAN_API_KEY")?;
|
||||
let host: String = config
|
||||
.get_param("AVIAN_HOST")
|
||||
.get_avian_host()
|
||||
.unwrap_or_else(|_| AVIAN_API_HOST.to_string());
|
||||
|
||||
let api_client = ApiClient::new(host, AuthMethod::BearerToken(api_key))?;
|
||||
|
||||
@@ -75,10 +75,10 @@ impl ProviderDef for AzureProvider {
|
||||
) -> BoxFuture<'static, Result<Self::Provider>> {
|
||||
Box::pin(async move {
|
||||
let config = crate::config::Config::global();
|
||||
let endpoint: String = config.get_param("AZURE_OPENAI_ENDPOINT")?;
|
||||
let deployment_name: String = config.get_param("AZURE_OPENAI_DEPLOYMENT_NAME")?;
|
||||
let endpoint: String = config.get_azure_openai_endpoint()?;
|
||||
let deployment_name: String = config.get_azure_openai_deployment_name()?;
|
||||
let api_version: String = config
|
||||
.get_param("AZURE_OPENAI_API_VERSION")
|
||||
.get_azure_openai_api_version()
|
||||
.unwrap_or_else(|_| AZURE_DEFAULT_API_VERSION.to_string());
|
||||
|
||||
let api_key = config
|
||||
|
||||
@@ -91,7 +91,7 @@ impl BedrockProvider {
|
||||
};
|
||||
|
||||
// Get AWS_REGION from config if explicitly set (optional - SDK can resolve from other sources)
|
||||
let region = match config.get_param::<String>("AWS_REGION") {
|
||||
let region = match config.get_aws_region() {
|
||||
Ok(r) if !r.is_empty() => Some(r),
|
||||
Ok(_) => None,
|
||||
Err(_) => None,
|
||||
@@ -100,7 +100,7 @@ impl BedrockProvider {
|
||||
// Use load_defaults() which supports AWS SSO, profiles, and environment variables
|
||||
let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest());
|
||||
|
||||
if let Ok(profile_name) = config.get_param::<String>("AWS_PROFILE") {
|
||||
if let Ok(profile_name) = config.get_aws_profile() {
|
||||
if !profile_name.is_empty() {
|
||||
loader = loader.profile_name(&profile_name);
|
||||
}
|
||||
@@ -166,19 +166,19 @@ impl BedrockProvider {
|
||||
|
||||
fn load_retry_config(config: &crate::config::Config) -> RetryConfig {
|
||||
let max_retries = config
|
||||
.get_param::<usize>("BEDROCK_MAX_RETRIES")
|
||||
.get_bedrock_max_retries()
|
||||
.unwrap_or(BEDROCK_DEFAULT_MAX_RETRIES);
|
||||
|
||||
let initial_interval_ms = config
|
||||
.get_param::<u64>("BEDROCK_INITIAL_RETRY_INTERVAL_MS")
|
||||
.get_bedrock_initial_retry_interval_ms()
|
||||
.unwrap_or(BEDROCK_DEFAULT_INITIAL_RETRY_INTERVAL_MS);
|
||||
|
||||
let backoff_multiplier = config
|
||||
.get_param::<f64>("BEDROCK_BACKOFF_MULTIPLIER")
|
||||
.get_bedrock_backoff_multiplier()
|
||||
.unwrap_or(BEDROCK_DEFAULT_BACKOFF_MULTIPLIER);
|
||||
|
||||
let max_interval_ms = config
|
||||
.get_param::<u64>("BEDROCK_MAX_RETRY_INTERVAL_MS")
|
||||
.get_bedrock_max_retry_interval_ms()
|
||||
.unwrap_or(BEDROCK_DEFAULT_MAX_RETRY_INTERVAL_MS);
|
||||
|
||||
RetryConfig::new(
|
||||
@@ -192,9 +192,7 @@ impl BedrockProvider {
|
||||
fn should_enable_caching(&self) -> bool {
|
||||
let config = crate::config::Config::global();
|
||||
|
||||
let enabled = config
|
||||
.get_param::<bool>("BEDROCK_ENABLE_CACHING")
|
||||
.unwrap_or(false);
|
||||
let enabled = config.get_bedrock_enable_caching().unwrap_or(false);
|
||||
enabled && self.model.model_name.contains("anthropic.claude")
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ impl DatabricksProvider {
|
||||
pub async fn from_env(model: ModelConfig) -> Result<Self> {
|
||||
let config = crate::config::Config::global();
|
||||
|
||||
let mut host: Result<String, ConfigError> = config.get_param("DATABRICKS_HOST");
|
||||
let mut host: Result<String, ConfigError> = config.get_databricks_host();
|
||||
if host.is_err() {
|
||||
host = config.get_secret("DATABRICKS_HOST")
|
||||
}
|
||||
@@ -198,25 +198,25 @@ impl DatabricksProvider {
|
||||
|
||||
fn load_retry_config(config: &crate::config::Config) -> RetryConfig {
|
||||
let max_retries = config
|
||||
.get_param("DATABRICKS_MAX_RETRIES")
|
||||
.get_databricks_max_retries()
|
||||
.ok()
|
||||
.and_then(|v: String| v.parse::<usize>().ok())
|
||||
.unwrap_or(DEFAULT_MAX_RETRIES);
|
||||
|
||||
let initial_interval_ms = config
|
||||
.get_param("DATABRICKS_INITIAL_RETRY_INTERVAL_MS")
|
||||
.get_databricks_initial_retry_interval_ms()
|
||||
.ok()
|
||||
.and_then(|v: String| v.parse::<u64>().ok())
|
||||
.unwrap_or(DEFAULT_INITIAL_RETRY_INTERVAL_MS);
|
||||
|
||||
let backoff_multiplier = config
|
||||
.get_param("DATABRICKS_BACKOFF_MULTIPLIER")
|
||||
.get_databricks_backoff_multiplier()
|
||||
.ok()
|
||||
.and_then(|v: String| v.parse::<f64>().ok())
|
||||
.unwrap_or(DEFAULT_BACKOFF_MULTIPLIER);
|
||||
|
||||
let max_interval_ms = config
|
||||
.get_param("DATABRICKS_MAX_RETRY_INTERVAL_MS")
|
||||
.get_databricks_max_retry_interval_ms()
|
||||
.ok()
|
||||
.and_then(|v: String| v.parse::<u64>().ok())
|
||||
.unwrap_or(DEFAULT_MAX_RETRY_INTERVAL_MS);
|
||||
@@ -259,7 +259,7 @@ impl DatabricksProvider {
|
||||
|
||||
fn resolve_instance_id() -> Option<String> {
|
||||
let enabled = crate::config::Config::global()
|
||||
.get_param::<bool>("GOOSE_DATABRICKS_CLIENT_REQUEST_ID")
|
||||
.get_goose_databricks_client_request_id()
|
||||
.unwrap_or(false);
|
||||
if enabled {
|
||||
Some(get_instance_id().to_string())
|
||||
|
||||
@@ -166,7 +166,7 @@ impl GcpVertexAIProvider {
|
||||
/// * `model` - Configuration for the model to be used
|
||||
pub async fn from_env(model: ModelConfig) -> Result<Self> {
|
||||
let config = crate::config::Config::global();
|
||||
let project_id = config.get_param("GCP_PROJECT_ID")?;
|
||||
let project_id = config.get_gcp_project_id()?;
|
||||
let location = Self::determine_location(config)?;
|
||||
let host = Self::build_host_url(&location);
|
||||
|
||||
@@ -195,25 +195,25 @@ impl GcpVertexAIProvider {
|
||||
fn load_retry_config(config: &crate::config::Config) -> RetryConfig {
|
||||
// Load max retries for 429 rate limit errors
|
||||
let max_retries = config
|
||||
.get_param("GCP_MAX_RETRIES")
|
||||
.get_gcp_max_retries()
|
||||
.ok()
|
||||
.and_then(|v: String| v.parse::<usize>().ok())
|
||||
.unwrap_or(DEFAULT_MAX_RETRIES);
|
||||
|
||||
let initial_interval_ms = config
|
||||
.get_param("GCP_INITIAL_RETRY_INTERVAL_MS")
|
||||
.get_gcp_initial_retry_interval_ms()
|
||||
.ok()
|
||||
.and_then(|v: String| v.parse::<u64>().ok())
|
||||
.unwrap_or(DEFAULT_INITIAL_RETRY_INTERVAL_MS);
|
||||
|
||||
let backoff_multiplier = config
|
||||
.get_param("GCP_BACKOFF_MULTIPLIER")
|
||||
.get_gcp_backoff_multiplier()
|
||||
.ok()
|
||||
.and_then(|v: String| v.parse::<f64>().ok())
|
||||
.unwrap_or(DEFAULT_BACKOFF_MULTIPLIER);
|
||||
|
||||
let max_interval_ms = config
|
||||
.get_param("GCP_MAX_RETRY_INTERVAL_MS")
|
||||
.get_gcp_max_retry_interval_ms()
|
||||
.ok()
|
||||
.and_then(|v: String| v.parse::<u64>().ok())
|
||||
.unwrap_or(DEFAULT_MAX_RETRY_INTERVAL_MS);
|
||||
@@ -233,7 +233,7 @@ impl GcpVertexAIProvider {
|
||||
/// 2. Global default location (Iowa)
|
||||
fn determine_location(config: &crate::config::Config) -> Result<String> {
|
||||
Ok(config
|
||||
.get_param("GCP_LOCATION")
|
||||
.get_gcp_location()
|
||||
.ok()
|
||||
.filter(|location: &String| !location.trim().is_empty())
|
||||
.unwrap_or_else(|| GcpLocation::Iowa.to_string()))
|
||||
|
||||
@@ -182,7 +182,7 @@ impl GithubCopilotProvider {
|
||||
let config = Config::global();
|
||||
let host = normalize_host(
|
||||
&config
|
||||
.get_param::<String>("GITHUB_COPILOT_HOST")
|
||||
.get_github_copilot_host()
|
||||
.unwrap_or_else(|_| DEFAULT_GITHUB_HOST.to_string()),
|
||||
);
|
||||
DiskCache::new(&host).clear().await
|
||||
@@ -215,13 +215,13 @@ impl GithubCopilotProvider {
|
||||
let config = Config::global();
|
||||
let host = normalize_host(
|
||||
&config
|
||||
.get_param::<String>("GITHUB_COPILOT_HOST")
|
||||
.get_github_copilot_host()
|
||||
.unwrap_or_else(|_| DEFAULT_GITHUB_HOST.to_string()),
|
||||
);
|
||||
let client_id: String = config
|
||||
.get_param("GITHUB_COPILOT_CLIENT_ID")
|
||||
.get_github_copilot_client_id()
|
||||
.unwrap_or_else(|_| DEFAULT_GITHUB_COPILOT_CLIENT_ID.to_string());
|
||||
let copilot_token_url: Option<String> = config.get_param("GITHUB_COPILOT_TOKEN_URL").ok();
|
||||
let copilot_token_url: Option<String> = config.get_github_copilot_token_url().ok();
|
||||
let urls = GithubCopilotUrls::new(&host, copilot_token_url.as_deref());
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(600))
|
||||
|
||||
@@ -73,7 +73,7 @@ impl GoogleProvider {
|
||||
let config = crate::config::Config::global();
|
||||
let api_key: String = config.get_secret("GOOGLE_API_KEY")?;
|
||||
let host: String = config
|
||||
.get_param("GOOGLE_HOST")
|
||||
.get_google_host()
|
||||
.unwrap_or_else(|_| GOOGLE_API_HOST.to_string());
|
||||
|
||||
let auth = AuthMethod::ApiKey {
|
||||
@@ -147,7 +147,7 @@ impl ProviderDef for GoogleProvider {
|
||||
.with_public(
|
||||
"host",
|
||||
config
|
||||
.get_param::<String>("GOOGLE_HOST")
|
||||
.get_google_host()
|
||||
.unwrap_or_else(|_| GOOGLE_API_HOST.to_string()),
|
||||
);
|
||||
|
||||
|
||||
@@ -39,16 +39,16 @@ impl LiteLLMProvider {
|
||||
.unwrap_or_default();
|
||||
let api_key = secrets.get("LITELLM_API_KEY").cloned().unwrap_or_default();
|
||||
let host: String = config
|
||||
.get_param("LITELLM_HOST")
|
||||
.get_litellm_host()
|
||||
.unwrap_or_else(|_| "https://api.litellm.ai".to_string());
|
||||
let base_path: String = config
|
||||
.get_param("LITELLM_BASE_PATH")
|
||||
.get_litellm_base_path()
|
||||
.unwrap_or_else(|_| "v1/chat/completions".to_string());
|
||||
let custom_headers: Option<HashMap<String, String>> = secrets
|
||||
.get("LITELLM_CUSTOM_HEADERS")
|
||||
.cloned()
|
||||
.map(parse_custom_headers);
|
||||
let timeout_secs: u64 = config.get_param("LITELLM_TIMEOUT").unwrap_or(600);
|
||||
let timeout_secs: u64 = config.get_litellm_timeout().unwrap_or(600);
|
||||
|
||||
let auth = if api_key.is_empty() {
|
||||
AuthMethod::NoAuth
|
||||
|
||||
@@ -56,7 +56,7 @@ pub struct OllamaProvider {
|
||||
}
|
||||
fn resolve_ollama_num_ctx(model_config: &ModelConfig) -> Option<usize> {
|
||||
let config = crate::config::Config::global();
|
||||
let input_limit = match config.get_param::<usize>("GOOSE_INPUT_LIMIT") {
|
||||
let input_limit = match config.get_goose_input_limit() {
|
||||
Ok(limit) if limit > 0 => Some(limit),
|
||||
Ok(_) => None,
|
||||
Err(crate::config::ConfigError::NotFound(_)) => None,
|
||||
@@ -71,7 +71,7 @@ fn resolve_ollama_num_ctx(model_config: &ModelConfig) -> Option<usize> {
|
||||
|
||||
fn resolve_ollama_stream_usage() -> bool {
|
||||
let config = crate::config::Config::global();
|
||||
match config.get_param::<bool>("OLLAMA_STREAM_USAGE") {
|
||||
match config.get_ollama_stream_usage() {
|
||||
Ok(val) => val,
|
||||
// Key not set: default to true. Ollama supports stream_options since
|
||||
// mid-2025 and most installs benefit from token usage tracking.
|
||||
@@ -123,18 +123,18 @@ fn apply_ollama_options(payload: &mut Value, model_config: &ModelConfig) {
|
||||
}
|
||||
|
||||
fn ollama_host_configured(config: &crate::config::Config) -> bool {
|
||||
config.get_param::<String>("OLLAMA_HOST").is_ok()
|
||||
config.get_ollama_host().is_ok()
|
||||
}
|
||||
|
||||
impl OllamaProvider {
|
||||
pub async fn from_env(model: ModelConfig) -> Result<Self> {
|
||||
let config = crate::config::Config::global();
|
||||
let host: String = config
|
||||
.get_param("OLLAMA_HOST")
|
||||
.get_ollama_host()
|
||||
.unwrap_or_else(|_| OLLAMA_HOST.to_string());
|
||||
|
||||
let timeout: Duration =
|
||||
Duration::from_secs(config.get_param("OLLAMA_TIMEOUT").unwrap_or(OLLAMA_TIMEOUT));
|
||||
Duration::from_secs(config.get_ollama_timeout().unwrap_or(OLLAMA_TIMEOUT));
|
||||
|
||||
let base = if host.starts_with("http://") || host.starts_with("https://") {
|
||||
host.clone()
|
||||
@@ -276,7 +276,7 @@ impl ProviderDef for OllamaProvider {
|
||||
InventoryIdentityInput::new(OLLAMA_PROVIDER_NAME, OLLAMA_PROVIDER_NAME).with_public(
|
||||
"host",
|
||||
config
|
||||
.get_param::<String>("OLLAMA_HOST")
|
||||
.get_ollama_host()
|
||||
.unwrap_or_else(|_| OLLAMA_HOST.to_string()),
|
||||
),
|
||||
)
|
||||
@@ -391,17 +391,17 @@ const OLLAMA_DEFAULT_CHUNK_TIMEOUT_SECS: u64 = 120;
|
||||
fn resolve_ollama_chunk_timeout() -> u64 {
|
||||
let config = crate::config::Config::global();
|
||||
|
||||
if let Ok(val) = config.get_param::<u64>("OLLAMA_STREAM_TIMEOUT") {
|
||||
if let Ok(val) = config.get_ollama_stream_timeout() {
|
||||
if val > 0 {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
if let Ok(val) = config.get_param::<u64>("GOOSE_STREAM_TIMEOUT") {
|
||||
if let Ok(val) = config.get_goose_stream_timeout() {
|
||||
if val > 0 {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
match config.get_param::<u64>("OLLAMA_TIMEOUT") {
|
||||
match config.get_ollama_timeout() {
|
||||
Ok(val) if val > 0 => val,
|
||||
_ => OLLAMA_DEFAULT_CHUNK_TIMEOUT_SECS,
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ impl OpenAiProvider {
|
||||
from_base_url: false,
|
||||
}
|
||||
} else if let Some(raw_url) = config
|
||||
.get_param::<String>("OPENAI_BASE_URL")
|
||||
.get_openai_base_url()
|
||||
.ok()
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
@@ -164,7 +164,7 @@ impl OpenAiProvider {
|
||||
Self::parse_base_url(&raw_url)?
|
||||
} else {
|
||||
let h: String = config
|
||||
.get_param("OPENAI_HOST")
|
||||
.get_openai_host()
|
||||
.unwrap_or_else(|_| "https://api.openai.com".to_string());
|
||||
ParsedBaseUrl {
|
||||
host: h,
|
||||
@@ -191,7 +191,7 @@ impl OpenAiProvider {
|
||||
std::env::var("OPENAI_BASE_PATH").unwrap_or_else(|_| default_bp())
|
||||
} else {
|
||||
config
|
||||
.get_param("OPENAI_BASE_PATH")
|
||||
.get_openai_base_path()
|
||||
.unwrap_or_else(|_| default_bp())
|
||||
};
|
||||
|
||||
@@ -227,9 +227,9 @@ impl OpenAiProvider {
|
||||
.cloned()
|
||||
.map(parse_custom_headers);
|
||||
|
||||
let organization: Option<String> = config.get_param("OPENAI_ORGANIZATION").ok();
|
||||
let project: Option<String> = config.get_param("OPENAI_PROJECT").ok();
|
||||
let timeout_secs: u64 = config.get_param("OPENAI_TIMEOUT").unwrap_or(600);
|
||||
let organization: Option<String> = config.get_openai_organization().ok();
|
||||
let project: Option<String> = config.get_openai_project().ok();
|
||||
let timeout_secs: u64 = config.get_openai_timeout().unwrap_or(600);
|
||||
|
||||
let auth = match api_key {
|
||||
Some(key) if !key.is_empty() => AuthMethod::BearerToken(key),
|
||||
@@ -588,9 +588,16 @@ impl ProviderDef for OpenAiProvider {
|
||||
|
||||
fn inventory_configured() -> bool {
|
||||
let config = crate::config::Config::global();
|
||||
if config
|
||||
.get_openai_base_url()
|
||||
.ok()
|
||||
.is_some_and(|base_url| !base_url.trim().is_empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// If the host is explicitly set to something non-default, trust the user's
|
||||
// custom setup (e.g. a local server that doesn't require an API key).
|
||||
if let Ok(host) = config.get_param::<String>("OPENAI_HOST") {
|
||||
if let Ok(host) = config.get_openai_host() {
|
||||
if host != "https://api.openai.com" {
|
||||
return true;
|
||||
}
|
||||
@@ -603,25 +610,38 @@ impl ProviderDef for OpenAiProvider {
|
||||
|
||||
fn inventory_identity() -> Result<InventoryIdentityInput> {
|
||||
let config = crate::config::Config::global();
|
||||
let (host, base_path) = if let Some(raw_url) = config
|
||||
.get_openai_base_url()
|
||||
.ok()
|
||||
.filter(|base_url| !base_url.trim().is_empty())
|
||||
{
|
||||
let parsed = Self::parse_base_url(&raw_url)?;
|
||||
let base_path = if parsed.has_v1 {
|
||||
OPEN_AI_DEFAULT_BASE_PATH.to_string()
|
||||
} else {
|
||||
OPEN_AI_VERSIONLESS_BASE_PATH.to_string()
|
||||
};
|
||||
(parsed.host, base_path)
|
||||
} else {
|
||||
(
|
||||
config
|
||||
.get_openai_host()
|
||||
.unwrap_or_else(|_| "https://api.openai.com".to_string()),
|
||||
config
|
||||
.get_openai_base_path()
|
||||
.unwrap_or_else(|_| OPEN_AI_DEFAULT_BASE_PATH.to_string()),
|
||||
)
|
||||
};
|
||||
|
||||
let mut identity =
|
||||
InventoryIdentityInput::new(OPEN_AI_PROVIDER_NAME, OPEN_AI_PROVIDER_NAME)
|
||||
.with_public(
|
||||
"host",
|
||||
config
|
||||
.get_param::<String>("OPENAI_HOST")
|
||||
.unwrap_or_else(|_| "https://api.openai.com".to_string()),
|
||||
)
|
||||
.with_public(
|
||||
"base_path",
|
||||
config
|
||||
.get_param::<String>("OPENAI_BASE_PATH")
|
||||
.unwrap_or_else(|_| OPEN_AI_DEFAULT_BASE_PATH.to_string()),
|
||||
);
|
||||
.with_public("host", host)
|
||||
.with_public("base_path", base_path);
|
||||
|
||||
if let Ok(organization) = config.get_param::<String>("OPENAI_ORGANIZATION") {
|
||||
if let Ok(organization) = config.get_openai_organization() {
|
||||
identity = identity.with_public("organization", organization);
|
||||
}
|
||||
if let Ok(project) = config.get_param::<String>("OPENAI_PROJECT") {
|
||||
if let Ok(project) = config.get_openai_project() {
|
||||
identity = identity.with_public("project", project);
|
||||
}
|
||||
if let Some(api_key) = config_secret_value(config, "OPENAI_API_KEY") {
|
||||
|
||||
@@ -52,7 +52,7 @@ impl OpenRouterProvider {
|
||||
let config = crate::config::Config::global();
|
||||
let api_key: String = config.get_secret("OPENROUTER_API_KEY")?;
|
||||
let host: String = config
|
||||
.get_param("OPENROUTER_HOST")
|
||||
.get_openrouter_host()
|
||||
.unwrap_or_else(|_| "https://openrouter.ai".to_string());
|
||||
|
||||
let auth = AuthMethod::BearerToken(api_key);
|
||||
|
||||
@@ -44,7 +44,7 @@ impl SageMakerTgiProvider {
|
||||
let config = crate::config::Config::global();
|
||||
|
||||
// Get SageMaker endpoint name (just the name, not full URL)
|
||||
let endpoint_name: String = config.get_param("SAGEMAKER_ENDPOINT_NAME").map_err(|_| {
|
||||
let endpoint_name: String = config.get_sagemaker_endpoint_name().map_err(|_| {
|
||||
anyhow::anyhow!("SAGEMAKER_ENDPOINT_NAME is required for SageMaker TGI provider")
|
||||
})?;
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ pub struct SnowflakeProvider {
|
||||
impl SnowflakeProvider {
|
||||
pub async fn from_env(model: ModelConfig) -> Result<Self> {
|
||||
let config = crate::config::Config::global();
|
||||
let mut host: Result<String, ConfigError> = config.get_param("SNOWFLAKE_HOST");
|
||||
let mut host: Result<String, ConfigError> = config.get_snowflake_host();
|
||||
if host.is_err() {
|
||||
host = config.get_secret("SNOWFLAKE_HOST")
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ impl TetrateProvider {
|
||||
let config = crate::config::Config::global();
|
||||
let api_key: String = config.get_secret("TETRATE_API_KEY")?;
|
||||
let host: String = config
|
||||
.get_param("TETRATE_HOST")
|
||||
.get_tetrate_host()
|
||||
.unwrap_or_else(|_| "https://api.router.tetrate.ai".to_string());
|
||||
|
||||
let auth = AuthMethod::BearerToken(api_key);
|
||||
|
||||
@@ -91,13 +91,13 @@ impl VeniceProvider {
|
||||
let config = crate::config::Config::global();
|
||||
let api_key: String = config.get_secret("VENICE_API_KEY")?;
|
||||
let host: String = config
|
||||
.get_param("VENICE_HOST")
|
||||
.get_venice_host()
|
||||
.unwrap_or_else(|_| VENICE_DEFAULT_HOST.to_string());
|
||||
let base_path: String = config
|
||||
.get_param("VENICE_BASE_PATH")
|
||||
.get_venice_base_path()
|
||||
.unwrap_or_else(|_| VENICE_DEFAULT_BASE_PATH.to_string());
|
||||
let models_path: String = config
|
||||
.get_param("VENICE_MODELS_PATH")
|
||||
.get_venice_models_path()
|
||||
.unwrap_or_else(|_| VENICE_DEFAULT_MODELS_PATH.to_string());
|
||||
|
||||
let auth = AuthMethod::BearerToken(api_key);
|
||||
|
||||
@@ -59,7 +59,7 @@ impl ProviderDef for XaiProvider {
|
||||
let config = crate::config::Config::global();
|
||||
let api_key: String = config.get_secret("XAI_API_KEY")?;
|
||||
let host: String = config
|
||||
.get_param("XAI_HOST")
|
||||
.get_xai_host()
|
||||
.unwrap_or_else(|_| XAI_API_HOST.to_string());
|
||||
|
||||
let api_client = ApiClient::new(host, AuthMethod::BearerToken(api_key))?;
|
||||
|
||||
@@ -61,20 +61,18 @@ impl SecurityManager {
|
||||
pub fn is_prompt_injection_detection_enabled(&self) -> bool {
|
||||
let config = Config::global();
|
||||
|
||||
config
|
||||
.get_param::<bool>("SECURITY_PROMPT_ENABLED")
|
||||
.unwrap_or(false)
|
||||
config.get_security_prompt_enabled().unwrap_or(false)
|
||||
}
|
||||
|
||||
fn is_ml_scanning_enabled(&self) -> bool {
|
||||
let config = Config::global();
|
||||
|
||||
let prompt_enabled = config
|
||||
.get_param::<bool>("SECURITY_PROMPT_CLASSIFIER_ENABLED")
|
||||
.get_security_prompt_classifier_enabled()
|
||||
.unwrap_or(false);
|
||||
|
||||
let command_enabled = config
|
||||
.get_param::<bool>("SECURITY_COMMAND_CLASSIFIER_ENABLED")
|
||||
.get_security_command_classifier_enabled()
|
||||
.unwrap_or(false);
|
||||
|
||||
prompt_enabled || command_enabled
|
||||
@@ -96,10 +94,10 @@ impl SecurityManager {
|
||||
let scanner = self.scanner.get_or_init(|| {
|
||||
let config = Config::global();
|
||||
let command_classifier_enabled = config
|
||||
.get_param::<bool>("SECURITY_COMMAND_CLASSIFIER_ENABLED")
|
||||
.get_security_command_classifier_enabled()
|
||||
.unwrap_or(false);
|
||||
let prompt_classifier_enabled = config
|
||||
.get_param::<bool>("SECURITY_PROMPT_CLASSIFIER_ENABLED")
|
||||
.get_security_prompt_classifier_enabled()
|
||||
.unwrap_or(false);
|
||||
|
||||
tracing::info!(
|
||||
|
||||
@@ -114,7 +114,7 @@ impl PromptInjectionScanner {
|
||||
|
||||
pub fn get_threshold_from_config(&self) -> f32 {
|
||||
Config::global()
|
||||
.get_param::<f64>("SECURITY_PROMPT_THRESHOLD")
|
||||
.get_security_prompt_threshold()
|
||||
.unwrap_or(0.8) as f32
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
|
||||
@@ -9,7 +10,7 @@ use crate::recipe::Recipe;
|
||||
|
||||
const SLASH_COMMANDS_CONFIG_KEY: &str = "slash_commands";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct SlashCommandMapping {
|
||||
pub command: String,
|
||||
pub recipe_path: String,
|
||||
|
||||
Reference in New Issue
Block a user