fix(providers/ollama): strengthen model cache + scope status UI
This commit is contained in:
@@ -88,6 +88,7 @@ struct ScopeSnapshot {
|
||||
availability: ScopeAvailability,
|
||||
last_error: Option<String>,
|
||||
last_checked: Option<Instant>,
|
||||
last_success_at: Option<Instant>,
|
||||
}
|
||||
|
||||
impl Default for ScopeSnapshot {
|
||||
@@ -98,10 +99,29 @@ impl Default for ScopeSnapshot {
|
||||
availability: ScopeAvailability::Unknown,
|
||||
last_error: None,
|
||||
last_checked: None,
|
||||
last_success_at: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScopeSnapshot {
|
||||
fn is_stale(&self, ttl: Duration) -> bool {
|
||||
match self.fetched_at {
|
||||
Some(ts) => ts.elapsed() >= ttl,
|
||||
None => !self.models.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn last_checked_age_secs(&self) -> Option<u64> {
|
||||
self.last_checked.map(|instant| instant.elapsed().as_secs())
|
||||
}
|
||||
|
||||
fn last_success_age_secs(&self) -> Option<u64> {
|
||||
self.last_success_at
|
||||
.map(|instant| instant.elapsed().as_secs())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OllamaOptions {
|
||||
mode: OllamaMode,
|
||||
@@ -410,22 +430,29 @@ impl OllamaProvider {
|
||||
return None;
|
||||
}
|
||||
|
||||
entry.fetched_at.and_then(|ts| {
|
||||
if entry.models.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(ts) = entry.fetched_at {
|
||||
if ts.elapsed() < self.model_cache_ttl {
|
||||
Some(entry.models.clone())
|
||||
} else {
|
||||
None
|
||||
return Some(entry.models.clone());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Fallback to last good models even if stale; UI will mark as degraded
|
||||
Some(entry.models.clone())
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_scope_success(&self, scope: OllamaMode, models: &[ModelInfo]) {
|
||||
let mut cache = self.scope_cache.write().await;
|
||||
let entry = cache.entry(scope).or_default();
|
||||
let now = Instant::now();
|
||||
entry.models = models.to_vec();
|
||||
entry.fetched_at = Some(Instant::now());
|
||||
entry.last_checked = Some(Instant::now());
|
||||
entry.fetched_at = Some(now);
|
||||
entry.last_checked = Some(now);
|
||||
entry.last_success_at = Some(now);
|
||||
entry.availability = ScopeAvailability::Available;
|
||||
entry.last_error = None;
|
||||
}
|
||||
@@ -461,6 +488,45 @@ impl OllamaProvider {
|
||||
}
|
||||
}
|
||||
|
||||
let stale = snapshot.is_stale(self.model_cache_ttl);
|
||||
let stale_capability = format!(
|
||||
"scope-status-stale:{}:{}",
|
||||
scope_key,
|
||||
if stale { "1" } else { "0" }
|
||||
);
|
||||
for model in models.iter_mut() {
|
||||
if !model
|
||||
.capabilities
|
||||
.iter()
|
||||
.any(|cap| cap == &stale_capability)
|
||||
{
|
||||
model.capabilities.push(stale_capability.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(age) = snapshot.last_checked_age_secs() {
|
||||
let age_capability = format!("scope-status-age:{}:{}", scope_key, age);
|
||||
for model in models.iter_mut() {
|
||||
if !model.capabilities.iter().any(|cap| cap == &age_capability) {
|
||||
model.capabilities.push(age_capability.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(success_age) = snapshot.last_success_age_secs() {
|
||||
let success_capability =
|
||||
format!("scope-status-success-age:{}:{}", scope_key, success_age);
|
||||
for model in models.iter_mut() {
|
||||
if !model
|
||||
.capabilities
|
||||
.iter()
|
||||
.any(|cap| cap == &success_capability)
|
||||
{
|
||||
model.capabilities.push(success_capability.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(raw_reason) = snapshot.last_error.as_ref() {
|
||||
let cleaned = raw_reason.replace('\n', " ").trim().to_string();
|
||||
if !cleaned.is_empty() {
|
||||
@@ -1658,6 +1724,7 @@ fn annotate_scope_status_adds_capabilities_for_unavailable_scopes() {
|
||||
let entry = cache.entry(OllamaMode::Cloud).or_default();
|
||||
entry.availability = ScopeAvailability::Unavailable;
|
||||
entry.last_error = Some("Cloud endpoint unreachable".to_string());
|
||||
entry.last_checked = Some(Instant::now());
|
||||
}
|
||||
|
||||
provider.annotate_scope_status(&mut models).await;
|
||||
@@ -1674,4 +1741,14 @@ fn annotate_scope_status_adds_capabilities_for_unavailable_scopes() {
|
||||
.iter()
|
||||
.any(|cap| cap.starts_with("scope-status-message:cloud:"))
|
||||
);
|
||||
assert!(
|
||||
capabilities
|
||||
.iter()
|
||||
.any(|cap| cap.starts_with("scope-status-age:cloud:"))
|
||||
);
|
||||
assert!(
|
||||
capabilities
|
||||
.iter()
|
||||
.any(|cap| cap == "scope-status-stale:cloud:0")
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user