Skip to content

Commit 54bbecc

Browse files
committed
fix(config): validate model provider references
1 parent 04c2a7f commit 54bbecc

2 files changed

Lines changed: 77 additions & 1 deletion

File tree

src/config_file.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,9 @@ impl ConfigFile {
316316
}
317317

318318
pub fn validate_auth_references(&self, auth: &AuthConfig) -> Result<(), ConfigFileError> {
319-
if !self.providers.is_empty() && !self.providers.contains_key(&self.model.provider) {
319+
if self.model.provider != "local-agent"
320+
&& !self.providers.contains_key(&self.model.provider)
321+
{
320322
return Err(ConfigFileError::InvalidProviderReference {
321323
path: "model.provider".to_string(),
322324
provider: self.model.provider.clone(),

tests/config_file_tests.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,80 @@ fn invalid_auth_reference_is_rejected_when_loading_a_pair() {
198198
assert!(error.to_string().contains("missing-credential"));
199199
}
200200

201+
#[test]
202+
fn model_provider_references_reject_unknowns_but_allow_builtin_and_defined_names() {
203+
let empty_auth = AuthConfig::from_str("version: 1\n").expect("empty auth document");
204+
let empty_providers = ConfigFile::from_str(
205+
"version: 1\nmodel:\n provider: unknown-empty\n model: local-agent\n",
206+
)
207+
.expect("config with no provider map");
208+
let error = empty_providers
209+
.validate_auth_references(&empty_auth)
210+
.expect_err("an unknown provider must fail without a provider map");
211+
assert!(matches!(
212+
error,
213+
ConfigFileError::InvalidProviderReference { path, provider }
214+
if path == "model.provider" && provider == "unknown-empty"
215+
));
216+
217+
let populated_providers = ConfigFile::from_str(&valid_config("codex-primary").replacen(
218+
" provider: openai-codex",
219+
" provider: unknown-populated",
220+
1,
221+
))
222+
.expect("config with a populated provider map");
223+
let auth = AuthConfig::from_str(&valid_auth("codex-primary")).expect("matching auth document");
224+
let error = populated_providers
225+
.validate_auth_references(&auth)
226+
.expect_err("an unknown provider must fail with a provider map");
227+
assert!(matches!(
228+
error,
229+
ConfigFileError::InvalidProviderReference { path, provider }
230+
if path == "model.provider" && provider == "unknown-populated"
231+
));
232+
233+
let builtin_without_map =
234+
ConfigFile::from_str("version: 1\nmodel:\n provider: local-agent\n model: local-agent\n")
235+
.expect("builtin config without a provider map");
236+
builtin_without_map
237+
.validate_auth_references(&empty_auth)
238+
.expect("local-agent remains valid without a provider map");
239+
240+
let builtin_with_map = ConfigFile::from_str(&valid_config("codex-primary").replacen(
241+
" provider: openai-codex",
242+
" provider: local-agent",
243+
1,
244+
))
245+
.expect("builtin config with a populated provider map");
246+
builtin_with_map
247+
.validate_auth_references(&auth)
248+
.expect("local-agent remains valid with a provider map");
249+
250+
let defined_provider =
251+
ConfigFile::from_str(&valid_config("codex-primary")).expect("defined provider config");
252+
defined_provider
253+
.validate_auth_references(&auth)
254+
.expect("a provider defined in the map remains valid");
255+
}
256+
257+
#[test]
258+
fn provider_auth_references_still_require_a_matching_credential() {
259+
let config = ConfigFile::from_str(&valid_config("codex-primary")).expect("valid config");
260+
let mismatched_auth = AuthConfig::from_str(
261+
&valid_auth("codex-primary").replace("provider: openai-codex", "provider: other-provider"),
262+
)
263+
.expect("valid auth with a different provider");
264+
265+
let error = config
266+
.validate_auth_references(&mismatched_auth)
267+
.expect_err("provider auth references must remain type-checked");
268+
assert!(matches!(
269+
error,
270+
ConfigFileError::InvalidAuthReference { path, credential_id, .. }
271+
if path == "providers.openai-codex.auth" && credential_id == "codex-primary"
272+
));
273+
}
274+
201275
#[test]
202276
fn provider_endpoints_require_https_except_loopback_callback() {
203277
let root = temp_root("https-policy");

0 commit comments

Comments
 (0)