From e9e36d33726daa9138a9fa9a873f46fd056a45d1 Mon Sep 17 00:00:00 2001 From: Chukwuebuka-2003 Date: Wed, 29 Jul 2026 23:23:03 +0000 Subject: [PATCH 1/2] fix(buzz-agent): honor OPENAI_BASE_URL as fallback for base URL The OpenAI provider's base URL resolution only checked OPENAI_COMPAT_BASE_URL, ignoring the more commonly expected OPENAI_BASE_URL env var. This meant users setting a custom base URL via the desktop persona env vars (which uses OPENAI_BASE_URL) would have it silently ignored, with the agent always falling back to api.openai.com. Now checks OPENAI_BASE_URL first, falls back to OPENAI_COMPAT_BASE_URL for backward compatibility, then defaults to https://api.openai.com/v1. Fixes #3630 Signed-off-by: Chukwuebuka-2003 (cherry picked from commit 953c4305ddaa64c1c0d2d0c45dbea6b3f3500b41) --- crates/buzz-agent/src/config.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/buzz-agent/src/config.rs b/crates/buzz-agent/src/config.rs index a0e64f1a9d..9cdf8a44b3 100644 --- a/crates/buzz-agent/src/config.rs +++ b/crates/buzz-agent/src/config.rs @@ -791,7 +791,9 @@ impl Config { env("OPENAI_COMPAT_MODEL").as_deref(), ) .ok_or_else(|| "config: OPENAI_COMPAT_MODEL required".to_string())?, - env_or("OPENAI_COMPAT_BASE_URL", "https://api.openai.com/v1"), + env("OPENAI_BASE_URL") + .or_else(|| env("OPENAI_COMPAT_BASE_URL")) + .unwrap_or_else(|| "https://api.openai.com/v1".into()), parse_openai_api(env("OPENAI_COMPAT_API").as_deref())?, ), Provider::Databricks | Provider::DatabricksV2 => ( From f00fd0893d9153662cfbd43b345ef4e582dc7080 Mon Sep 17 00:00:00 2001 From: Chukwuebuka-2003 Date: Thu, 30 Jul 2026 19:27:01 +0000 Subject: [PATCH 2/2] fix(buzz-agent): fix OPENAI_BASE_URL fallback precedence Flip the or_else order so OPENAI_COMPAT_BASE_URL (Buzz-specific) takes precedence over the generic OPENAI_BASE_URL, with the default as final fallback. This matches the PR description and the standard convention of specific-over-generic env var precedence. Also adds three unit tests covering the full env matrix: - Both vars set: compat wins - Only OPENAI_BASE_URL set: used as fallback - Neither set: default used Signed-off-by: Chukwuebuka-2003 --- Cargo.lock | 12 +++++- crates/buzz-agent/Cargo.toml | 1 + crates/buzz-agent/src/config.rs | 67 ++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49104b22d2..de6b99c01c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -835,6 +835,7 @@ dependencies = [ "serde_json", "serde_yaml", "sha2 0.11.0", + "temp-env", "tempfile", "tokio", "tracing", @@ -2177,7 +2178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -8714,6 +8715,15 @@ dependencies = [ "xattr", ] +[[package]] +name = "temp-env" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050" +dependencies = [ + "parking_lot", +] + [[package]] name = "tempfile" version = "3.27.0" diff --git a/crates/buzz-agent/Cargo.toml b/crates/buzz-agent/Cargo.toml index 7889ad34a7..0fa69a222d 100644 --- a/crates/buzz-agent/Cargo.toml +++ b/crates/buzz-agent/Cargo.toml @@ -55,3 +55,4 @@ hex = { workspace = true } serde = { workspace = true } sha2 = { workspace = true } tempfile = "3" +temp-env = "0.3" diff --git a/crates/buzz-agent/src/config.rs b/crates/buzz-agent/src/config.rs index 9cdf8a44b3..852e06051b 100644 --- a/crates/buzz-agent/src/config.rs +++ b/crates/buzz-agent/src/config.rs @@ -791,8 +791,8 @@ impl Config { env("OPENAI_COMPAT_MODEL").as_deref(), ) .ok_or_else(|| "config: OPENAI_COMPAT_MODEL required".to_string())?, - env("OPENAI_BASE_URL") - .or_else(|| env("OPENAI_COMPAT_BASE_URL")) + env("OPENAI_COMPAT_BASE_URL") + .or_else(|| env("OPENAI_BASE_URL")) .unwrap_or_else(|| "https://api.openai.com/v1".into()), parse_openai_api(env("OPENAI_COMPAT_API").as_deref())?, ), @@ -2753,4 +2753,67 @@ mod tests { let err = resolve_provider(Some("openrouter"), None, None, None).unwrap_err(); assert!(err.contains("OPENROUTER_API_KEY")); } + + #[test] + fn base_url_prefers_compat_over_generic() { + temp_env::with_vars( + [ + ("BUZZ_AGENT_PROVIDER", Some("openai")), + ("OPENAI_COMPAT_API_KEY", Some("sk-test")), + ("OPENAI_COMPAT_MODEL", Some("gpt-4")), + ("OPENAI_COMPAT_BASE_URL", Some("https://custom-openai.local/v1")), + ("OPENAI_BASE_URL", Some("https://generic-openai.local/v1")), + ], + || { + let config = Config::from_env().unwrap(); + assert_eq!( + config.base_url, + "https://custom-openai.local/v1", + "specific OPENAI_COMPAT_BASE_URL should win over generic OPENAI_BASE_URL" + ); + }, + ); + } + + #[test] + fn base_url_falls_back_to_generic_when_compat_unset() { + temp_env::with_vars( + [ + ("BUZZ_AGENT_PROVIDER", Some("openai")), + ("OPENAI_COMPAT_API_KEY", Some("sk-test")), + ("OPENAI_COMPAT_MODEL", Some("gpt-4")), + ("OPENAI_COMPAT_BASE_URL", None), + ("OPENAI_BASE_URL", Some("https://generic-openai.local/v1")), + ], + || { + let config = Config::from_env().unwrap(); + assert_eq!( + config.base_url, + "https://generic-openai.local/v1", + "OPENAI_BASE_URL should be used when OPENAI_COMPAT_BASE_URL is unset" + ); + }, + ); + } + + #[test] + fn base_url_defaults_when_none_set() { + temp_env::with_vars( + [ + ("BUZZ_AGENT_PROVIDER", Some("openai")), + ("OPENAI_COMPAT_API_KEY", Some("sk-test")), + ("OPENAI_COMPAT_MODEL", Some("gpt-4")), + ("OPENAI_COMPAT_BASE_URL", None), + ("OPENAI_BASE_URL", None), + ], + || { + let config = Config::from_env().unwrap(); + assert_eq!( + config.base_url, + "https://api.openai.com/v1", + "should default when neither env var is set" + ); + }, + ); + } }