ollama: Ensure only single task fetches models (#19830)

Thorsten Ball and Bennet created

Before this change, we'd see a ton of requests from the Ollama provider
trying to fetch models:

```
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: https://api.zed.dev/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
```

Turns out we'd send a request on *every* change to settings.

Now, with this change, we only send a single request.

Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>

Change summary

crates/language_model/src/provider/ollama.rs | 34 ++++++++++++++++-----
1 file changed, 26 insertions(+), 8 deletions(-)

Detailed changes

crates/language_model/src/provider/ollama.rs 🔗

@@ -54,6 +54,7 @@ pub struct OllamaLanguageModelProvider {
 pub struct State {
     http_client: Arc<dyn HttpClient>,
     available_models: Vec<ollama::Model>,
+    fetch_model_task: Option<Task<Result<()>>>,
     _subscription: Subscription,
 }
 
@@ -89,6 +90,11 @@ impl State {
         })
     }
 
+    fn restart_fetch_models_task(&mut self, cx: &mut ModelContext<Self>) {
+        let task = self.fetch_models(cx);
+        self.fetch_model_task.replace(task);
+    }
+
     fn authenticate(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
         if self.is_authenticated() {
             Task::ready(Ok(()))
@@ -102,17 +108,29 @@ impl OllamaLanguageModelProvider {
     pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Self {
         let this = Self {
             http_client: http_client.clone(),
-            state: cx.new_model(|cx| State {
-                http_client,
-                available_models: Default::default(),
-                _subscription: cx.observe_global::<SettingsStore>(|this: &mut State, cx| {
-                    this.fetch_models(cx).detach();
-                    cx.notify();
-                }),
+            state: cx.new_model(|cx| {
+                let subscription = cx.observe_global::<SettingsStore>({
+                    let mut settings = AllLanguageModelSettings::get_global(cx).ollama.clone();
+                    move |this: &mut State, cx| {
+                        let new_settings = &AllLanguageModelSettings::get_global(cx).ollama;
+                        if &settings != new_settings {
+                            settings = new_settings.clone();
+                            this.restart_fetch_models_task(cx);
+                            cx.notify();
+                        }
+                    }
+                });
+
+                State {
+                    http_client,
+                    available_models: Default::default(),
+                    fetch_model_task: None,
+                    _subscription: subscription,
+                }
             }),
         };
         this.state
-            .update(cx, |state, cx| state.fetch_models(cx).detach());
+            .update(cx, |state, cx| state.restart_fetch_models_task(cx));
         this
     }
 }