From 10af2da5cfc8b14f36569035190727882addf3c9 Mon Sep 17 00:00:00 2001 From: Zane Wang Date: Mon, 13 Jul 2026 19:16:22 -0700 Subject: [PATCH] fix(acp): allow custom model as default for non-local providers Selecting a custom/unlisted model for a brand-new chat saves it as the default via the ACP defaults/save handler, which rejected any model missing from the provider inventory with "Invalid params". OpenRouter's inventory is a non-exhaustive 10-model static list, so custom models such as google/gemini-3.5-flash failed on new chats once the desktop switched to ACP in v1.41.0, even though the in-session and CLI paths still accept them. Restore the intended behavior (custom model entry is always allowed, see #7255) by validating the model only for the local provider, whose models cannot be fetched on demand. Fixes #10401 --- crates/goose/src/acp/server/config.rs | 24 +++++++---- .../goose/tests/acp_custom_requests_test.rs | 41 +++++++++++++++++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/crates/goose/src/acp/server/config.rs b/crates/goose/src/acp/server/config.rs index 5ce4324ca6d7..22a0b58b4310 100644 --- a/crates/goose/src/acp/server/config.rs +++ b/crates/goose/src/acp/server/config.rs @@ -214,14 +214,22 @@ impl GooseAcpAgent { .data(format!("Provider is not configured: {provider_id}"))); } - if let Some(model_id) = model_id.as_deref() { - let model_exists = entry.default_model == model_id - || entry.models.iter().any(|model| model.id == model_id) - || (provider_id == "local" && local_inference_model_exists(model_id)?); - if !model_exists { - return Err(agent_client_protocol::Error::invalid_params().data(format!( - "Model '{model_id}' is not available for provider '{provider_id}'" - ))); + // Custom/unlisted model entry is always allowed (#7255), matching the CLI + // and in-session model-selection paths, so a model that is simply absent + // from the provider's (often non-exhaustive) inventory must still be + // accepted as the default here. Local inference is the exception: its + // models cannot be fetched on demand, so they are validated against the + // inventory or the on-disk registry. + if provider_id == "local" { + if let Some(model_id) = model_id.as_deref() { + let model_exists = entry.default_model == model_id + || entry.models.iter().any(|model| model.id == model_id) + || local_inference_model_exists(model_id)?; + if !model_exists { + return Err(agent_client_protocol::Error::invalid_params().data(format!( + "Model '{model_id}' is not available for provider '{provider_id}'" + ))); + } } } diff --git a/crates/goose/tests/acp_custom_requests_test.rs b/crates/goose/tests/acp_custom_requests_test.rs index 36c13e8aa767..5f1c126ab637 100644 --- a/crates/goose/tests/acp_custom_requests_test.rs +++ b/crates/goose/tests/acp_custom_requests_test.rs @@ -901,6 +901,47 @@ fn test_custom_defaults_read() { }); } +// Regression test for #10401: a custom model that is not present in a provider's +// (non-exhaustive) inventory must still be accepted when saved as the default from +// a brand-new chat, matching the CLI and in-session model-selection paths where +// custom/unlisted model entry is always allowed (#7255). +#[test] +#[serial] +fn test_custom_defaults_save_allows_unlisted_model() { + let _env = env_lock::lock_env([("ANTHROPIC_API_KEY", Some("test-key"))]); + let config_dir = write_acp_global_config( + "GOOSE_MODEL: claude-3-5-haiku-latest\nGOOSE_PROVIDER: anthropic\n", + ); + + run_test(async move { + let openai = OpenAiFixture::new(vec![], Arc::new(EnforceSessionId::default())).await; + let config = TestConnectionConfig { + data_root: config_dir, + ..Default::default() + }; + let conn = AcpServerConnection::new(config, openai).await; + + let response = send_custom( + conn.cx(), + "_goose/unstable/defaults/save", + serde_json::json!({ + "providerId": "anthropic", + "modelId": "custom-unlisted-model", + }), + ) + .await + .expect("saving an unlisted custom model as the default should succeed"); + + assert_eq!( + response, + serde_json::json!({ + "providerId": "anthropic", + "modelId": "custom-unlisted-model", + }) + ); + }); +} + #[test] #[serial] fn test_custom_dictation_secret_save_delete() {