Add a credential file override for GCP provider authentication (#57188)

## What changed

Support `model_providers.gcp.credential_file` as an absolute path to a Google Application Default Credentials (ADC) file. Pass it as `GOOGLE_APPLICATION_CREDENTIALS` in the provider auth command's environment, retaining the override for token refreshes without changing the parent process environment.

Validate the path before merging GCP defaults and preserve the setting in thread configuration serialization.

## Testing

Add coverage for invalid paths, configuration round trips, separate provider token caches, and credential overrides retained after a `401` refresh. Include a Windows regression test for credential paths containing command metacharacters.

GitOrigin-RevId: d9a1d4b3775a9a4c4325dddaa153d92a2d1430a0
This commit is contained in:
alexsong-oai
2026-10-26 18:33:05 +00:00
committed by copyberry
parent 77eb019ad0
commit be7cd6fd9e
21 changed files with 328 additions and 9 deletions
+22
View File
@@ -986,6 +986,11 @@ pub fn validate_model_providers(
"model_providers.{key}: provider name must not be empty"
));
}
if key == GCP_PROVIDER_ID {
provider
.validate_gcp_credential_file()
.map_err(|message| format!("model_providers.{key}: {message}"))?;
}
provider
.validate()
.map_err(|message| format!("model_providers.{key}: {message}"))?;
@@ -1159,4 +1164,21 @@ command = "print-token"
(Some("https://vertex.example.com/v1"), Some("print-token"))
);
}
#[test]
fn gcp_provider_rejects_relative_credential_file() {
for credential_file in ["", "adc.json", "../adc.json"] {
let error = toml::from_str::<ConfigToml>(&format!(
r#"
[model_providers.gcp]
credential_file = "{credential_file}"
"#,
))
.expect_err("GCP overrides must validate credential paths before merging defaults");
assert!(error.to_string().contains(
"model_providers.gcp: provider credential_file must be an absolute path"
));
}
}
}
+1
View File
@@ -319,6 +319,7 @@ mod tests {
capabilities: None,
project_id: None,
location: None,
credential_file: None,
include_internal_metadata: false,
}
}
@@ -52,6 +52,7 @@ message ModelProvider {
optional string model_catalog_url = 19;
optional string project_id = 20;
optional string location = 21;
optional string credential_file = 22;
}
message StringMap {
@@ -83,6 +83,8 @@ pub struct ModelProvider {
pub project_id: ::core::option::Option<::prost::alloc::string::String>,
#[prost(string, optional, tag = "21")]
pub location: ::core::option::Option<::prost::alloc::string::String>,
#[prost(string, optional, tag = "22")]
pub credential_file: ::core::option::Option<::prost::alloc::string::String>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StringMap {
@@ -198,6 +198,7 @@ fn model_provider_from_proto(
capabilities: None,
project_id: provider.project_id,
location: provider.location,
credential_file: provider.credential_file.map(Into::into),
include_internal_metadata: false,
};
Ok((id, info))
@@ -232,6 +233,7 @@ fn model_provider_to_proto(
supports_websockets,
supports_standalone_web_search,
include_internal_metadata: _,
credential_file,
capabilities: _,
} = provider;
@@ -240,6 +242,7 @@ fn model_provider_to_proto(
name,
base_url,
model_catalog_url: model_catalog_url.map(RedactedString::into_inner),
credential_file: credential_file.map(|path| path.to_string_lossy().into_owned()),
env_key,
env_key_instructions,
experimental_bearer_token: experimental_bearer_token.map(RedactedString::into_inner),
@@ -454,6 +457,7 @@ mod tests {
let mut expected = expected_provider();
expected.project_id = Some("vertex-project".to_string());
expected.location = Some("us-central1".to_string());
expected.credential_file = Some(workspace_dir().join("adc.json").into_path_buf());
expected.auth = None;
expected.experimental_bearer_token = Some("synthetic-provider-token".into());
let proto = model_provider_to_proto("local", expected.clone());
@@ -492,6 +496,7 @@ mod tests {
model_catalog_url: Some(
"http://127.0.0.1:8061/api/codex/models".to_string(),
),
credential_file: None,
env_key: None,
env_key_instructions: None,
experimental_bearer_token: None,
@@ -597,6 +602,7 @@ mod tests {
capabilities: None,
project_id: None,
location: None,
credential_file: None,
include_internal_metadata: false,
}
}