Allow configuring GCP project and location in provider settings (#56560)

## Why

Deriving a Vertex AI endpoint previously required environment variables. Allow the project and location to be specified directly in GCP provider configuration.

## What changed

- Add `model_providers.gcp.project_id` and `model_providers.gcp.location`, with each taking precedence over its corresponding environment variable. Keep `global` as the default location and preserve explicit `base_url` overrides.
- Validate configured endpoint fields and preserve them through thread configuration serialization.
- Apply built-in GCP defaults, including default command authentication, when checking managed provider requirements so unchanged requirements are accepted.

## Testing

Add coverage for endpoint precedence, invalid explicit configuration, provider construction, thread configuration round trips, and managed GCP requirement checks, including repeated model listing.

GitOrigin-RevId: c3704bcc0b7d4872374e2cae623fd24d75fb02b4
This commit is contained in:
alexsong-oai
2026-10-23 18:50:54 +00:00
committed by copyberry
parent 7848e3393c
commit 970111f6cb
23 changed files with 315 additions and 51 deletions
+14 -4
View File
@@ -4,6 +4,7 @@ use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigLayerStack;
use codex_config::LoaderOverrides;
use codex_config::ThreadConfigLoader;
use codex_config::apply_default_gcp_auth;
use codex_config::loader::load_config_layers_state;
use codex_config::loader::load_managed_requirements_state;
use codex_core::config::Config;
@@ -15,6 +16,7 @@ use codex_login::AuthManager;
use codex_login::default_client::set_default_client_residency_requirement;
use codex_model_provider_info::AMAZON_BEDROCK_PROVIDER_ID;
use codex_model_provider_info::AMAZON_BEDROCK_RUNTIME_PROVIDER_ID;
use codex_model_provider_info::GCP_PROVIDER_ID;
use codex_model_provider_info::built_in_model_providers;
use codex_model_provider_info::merge_configured_model_providers;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -371,16 +373,24 @@ impl ConfigManager {
Some(provider)
if matches!(
current.model_provider_id.as_str(),
AMAZON_BEDROCK_PROVIDER_ID | AMAZON_BEDROCK_RUNTIME_PROVIDER_ID
AMAZON_BEDROCK_PROVIDER_ID
| AMAZON_BEDROCK_RUNTIME_PROVIDER_ID
| GCP_PROVIDER_ID
) =>
{
// Bedrock requirements contain overrides of the built-in provider.
merge_configured_model_providers(
// Managed overrides must receive the same built-in defaults as startup config.
let mut provider = merge_configured_model_providers(
built_in_model_providers(/*openai_base_url*/ None),
HashMap::from([(current.model_provider_id.clone(), provider)]),
)
.map_err(|message| std::io::Error::new(std::io::ErrorKind::InvalidData, message))?
.remove(&current.model_provider_id)
.remove(&current.model_provider_id);
if current.model_provider_id == GCP_PROVIDER_ID
&& let Some(provider) = provider.as_mut()
{
apply_default_gcp_auth(provider, &current.cwd);
}
provider
}
Some(provider) => Some(provider),
None => None,
@@ -14,13 +14,22 @@ use pretty_assertions::assert_eq;
use tempfile::tempdir;
use test_case::test_case;
#[test_case(AMAZON_BEDROCK_PROVIDER_ID; "bedrock")]
#[test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID; "bedrock_runtime")]
#[test_case(AMAZON_BEDROCK_PROVIDER_ID, "aws.region = 'us-west-2'", "us-west-2", "us-east-1"; "bedrock")]
#[test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "aws.region = 'us-west-2'", "us-west-2", "us-east-1"; "bedrock_runtime")]
#[test_case(GCP_PROVIDER_ID, "project_id = 'example-project'\nlocation = 'us-central1'", "example-project", "other-project"; "gcp_project")]
#[test_case(GCP_PROVIDER_ID, "project_id = 'example-project'\nlocation = 'us-central1'", "us-central1", "us-east4"; "gcp_location")]
#[test_case(GCP_PROVIDER_ID, "project_id = 'example-project'\nauth.command = 'print-token'", "print-token", "other-token"; "gcp_auth")]
#[tokio::test]
async fn provider_requirements_resolve_bedrock_overrides(provider_id: &str) -> Result<()> {
async fn provider_requirements_resolve_builtin_overrides(
provider_id: &str,
provider_config: &str,
original: &str,
replacement: &str,
) -> Result<()> {
let home = tempdir()?;
let workspace = tempdir()?;
let requirements = format!(
"model_provider = '{provider_id}'\n[model_providers.{provider_id}.aws]\nregion = 'us-west-2'"
"model_provider = '{provider_id}'\n[model_providers.{provider_id}]\n{provider_config}"
);
let manager = ConfigManager::new_for_tests(
home.path().to_path_buf(),
@@ -28,7 +37,9 @@ async fn provider_requirements_resolve_bedrock_overrides(provider_id: &str) -> R
LoaderOverrides::without_managed_config_for_tests(),
CloudConfigBundleFixture::loader_with_enterprise_requirement(&requirements),
);
let current = manager.load_latest_config(/*fallback_cwd*/ None).await?;
let current = manager
.load_latest_config(Some(workspace.path().to_path_buf()))
.await?;
manager.check_thread_model_provider(&current).await?;
let changed = ConfigManager::new_for_tests(
@@ -36,7 +47,7 @@ async fn provider_requirements_resolve_bedrock_overrides(provider_id: &str) -> R
Vec::new(),
LoaderOverrides::without_managed_config_for_tests(),
CloudConfigBundleFixture::loader_with_enterprise_requirement(
requirements.replace("us-west-2", "us-east-1"),
requirements.replace(original, replacement),
),
);
assert_eq!(
@@ -655,6 +655,8 @@ mod thread_processor_behavior_tests {
supports_websockets: true,
supports_standalone_web_search: false,
capabilities: None,
project_id: None,
location: None,
include_internal_metadata: false,
};
let config_manager = ConfigManager::new(