language_models: Remove unnecessary LM Studio connection refused log (#37277)

Umesh Yadav created

In zed logs you can see these logs of lmstudio connection refused.
Currently zed connects to lmstudio by default as there is no credential
mechanism to check if the user has enabled lmstudio previously or not
like we do with other providers using api keys.

This pr removes the below annoying log and makes the zed logs less
polluted.

```
2025-09-01T02:11:33+05:30 ERROR [language_models] Other(error sending request for url (http://localhost:1234/api/v0/models)

Caused by:
    0: client error (Connect)
    1: tcp connect error: Connection refused (os error 61)
    2: Connection refused (os error 61))
```

Release Notes:

- N/A

---------

Signed-off-by: Umesh Yadav <git@umesh.dev>

Change summary

crates/agent2/src/agent.rs                      | 62 ++++++++++--------
crates/language_model/src/language_model.rs     |  2 
crates/language_models/src/provider/lmstudio.rs | 25 +++++++
3 files changed, 61 insertions(+), 28 deletions(-)

Detailed changes

crates/agent2/src/agent.rs 🔗

@@ -166,33 +166,41 @@ impl LanguageModels {
         cx.background_spawn(async move {
             for (provider_id, provider_name, authenticate_task) in authenticate_all_providers {
                 if let Err(err) = authenticate_task.await {
-                    if matches!(err, language_model::AuthenticateError::CredentialsNotFound) {
-                        // Since we're authenticating these providers in the
-                        // background for the purposes of populating the
-                        // language selector, we don't care about providers
-                        // where the credentials are not found.
-                    } else {
-                        // Some providers have noisy failure states that we
-                        // don't want to spam the logs with every time the
-                        // language model selector is initialized.
-                        //
-                        // Ideally these should have more clear failure modes
-                        // that we know are safe to ignore here, like what we do
-                        // with `CredentialsNotFound` above.
-                        match provider_id.0.as_ref() {
-                            "lmstudio" | "ollama" => {
-                                // LM Studio and Ollama both make fetch requests to the local APIs to determine if they are "authenticated".
-                                //
-                                // These fail noisily, so we don't log them.
-                            }
-                            "copilot_chat" => {
-                                // Copilot Chat returns an error if Copilot is not enabled, so we don't log those errors.
-                            }
-                            _ => {
-                                log::error!(
-                                    "Failed to authenticate provider: {}: {err}",
-                                    provider_name.0
-                                );
+                    match err {
+                        language_model::AuthenticateError::CredentialsNotFound => {
+                            // Since we're authenticating these providers in the
+                            // background for the purposes of populating the
+                            // language selector, we don't care about providers
+                            // where the credentials are not found.
+                        }
+                        language_model::AuthenticateError::ConnectionRefused => {
+                            // Not logging connection refused errors as they are mostly from LM Studio's noisy auth failures.
+                            // LM Studio only has one auth method (endpoint call) which fails for users who haven't enabled it.
+                            // TODO: Better manage LM Studio auth logic to avoid these noisy failures.
+                        }
+                        _ => {
+                            // Some providers have noisy failure states that we
+                            // don't want to spam the logs with every time the
+                            // language model selector is initialized.
+                            //
+                            // Ideally these should have more clear failure modes
+                            // that we know are safe to ignore here, like what we do
+                            // with `CredentialsNotFound` above.
+                            match provider_id.0.as_ref() {
+                                "lmstudio" | "ollama" => {
+                                    // LM Studio and Ollama both make fetch requests to the local APIs to determine if they are "authenticated".
+                                    //
+                                    // These fail noisily, so we don't log them.
+                                }
+                                "copilot_chat" => {
+                                    // Copilot Chat returns an error if Copilot is not enabled, so we don't log those errors.
+                                }
+                                _ => {
+                                    log::error!(
+                                        "Failed to authenticate provider: {}: {err}",
+                                        provider_name.0
+                                    );
+                                }
                             }
                         }
                     }

crates/language_model/src/language_model.rs 🔗

@@ -681,6 +681,8 @@ pub trait LanguageModelTool: 'static + DeserializeOwned + JsonSchema {
 /// An error that occurred when trying to authenticate the language model provider.
 #[derive(Debug, Error)]
 pub enum AuthenticateError {
+    #[error("connection refused")]
+    ConnectionRefused,
     #[error("credentials not found")]
     CredentialsNotFound,
     #[error(transparent)]

crates/language_models/src/provider/lmstudio.rs 🔗

@@ -111,7 +111,30 @@ impl State {
         }
 
         let fetch_models_task = self.fetch_models(cx);
-        cx.spawn(async move |_this, _cx| Ok(fetch_models_task.await?))
+        cx.spawn(async move |_this, _cx| {
+            match fetch_models_task.await {
+                Ok(()) => Ok(()),
+                Err(err) => {
+                    // If any cause in the error chain is an std::io::Error with
+                    // ErrorKind::ConnectionRefused, treat this as "credentials not found"
+                    // (i.e. LM Studio not running).
+                    let mut connection_refused = false;
+                    for cause in err.chain() {
+                        if let Some(io_err) = cause.downcast_ref::<std::io::Error>() {
+                            if io_err.kind() == std::io::ErrorKind::ConnectionRefused {
+                                connection_refused = true;
+                                break;
+                            }
+                        }
+                    }
+                    if connection_refused {
+                        Err(AuthenticateError::ConnectionRefused)
+                    } else {
+                        Err(AuthenticateError::Other(err))
+                    }
+                }
+            }
+        })
     }
 }