feat(ollama): add explicit Ollama mode config, cloud endpoint storage, and scope‑availability caching with status annotations.

This commit is contained in:
2025-10-15 10:05:34 +02:00
parent 5210e196f2
commit 708c626176
9 changed files with 1845 additions and 241 deletions

View File

@@ -16,7 +16,14 @@ use std::time::Duration;
pub const DEFAULT_CONFIG_PATH: &str = "~/.config/owlen/config.toml";
/// Current schema version written to `config.toml`.
pub const CONFIG_SCHEMA_VERSION: &str = "1.4.0";
pub const CONFIG_SCHEMA_VERSION: &str = "1.5.0";
/// Provider config key for forcing Ollama provider mode.
pub const OLLAMA_MODE_KEY: &str = "ollama_mode";
/// Extra config key storing the preferred Ollama Cloud endpoint.
pub const OLLAMA_CLOUD_ENDPOINT_KEY: &str = "cloud_endpoint";
/// Canonical Ollama Cloud base URL.
pub const OLLAMA_CLOUD_BASE_URL: &str = "https://ollama.com";
/// Core configuration shared by all OWLEN clients
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -574,6 +581,23 @@ impl Config {
self.merge_legacy_ollama_provider(legacy_cloud);
}
if let Some(ollama) = self.providers.get_mut("ollama") {
let previous_mode = ollama
.extra
.get(OLLAMA_MODE_KEY)
.and_then(|value| value.as_str())
.map(|value| value.to_ascii_lowercase());
ensure_ollama_mode_extra(ollama);
if previous_mode.as_deref().unwrap_or("auto") == "auto"
&& is_cloud_base_url(ollama.base_url.as_ref())
{
ollama.extra.insert(
OLLAMA_MODE_KEY.to_string(),
serde_json::Value::String("cloud".to_string()),
);
}
}
self.schema_version = CONFIG_SCHEMA_VERSION.to_string();
}
@@ -594,9 +618,12 @@ impl Config {
if target.extra.is_empty() && !legacy_cloud.extra.is_empty() {
target.extra = legacy_cloud.extra;
}
ensure_ollama_mode_extra(target);
}
Entry::Vacant(entry) => {
entry.insert(legacy_cloud);
let mut inserted = legacy_cloud;
ensure_ollama_mode_extra(&mut inserted);
entry.insert(inserted);
}
}
}
@@ -669,12 +696,47 @@ impl Config {
}
fn default_ollama_provider_config() -> ProviderConfig {
ProviderConfig {
let mut config = ProviderConfig {
provider_type: "ollama".to_string(),
base_url: Some("http://localhost:11434".to_string()),
api_key: None,
extra: HashMap::new(),
};
ensure_ollama_mode_extra(&mut config);
config
}
fn ensure_ollama_mode_extra(provider: &mut ProviderConfig) {
if provider.provider_type != "ollama" {
return;
}
let entry = provider
.extra
.entry(OLLAMA_MODE_KEY.to_string())
.or_insert_with(|| serde_json::Value::String("auto".to_string()));
if let Some(value) = entry.as_str() {
let normalized = value.trim().to_ascii_lowercase();
if matches!(normalized.as_str(), "auto" | "local" | "cloud") {
if normalized != value {
*entry = serde_json::Value::String(normalized);
}
} else {
*entry = serde_json::Value::String("auto".to_string());
}
} else {
*entry = serde_json::Value::String("auto".to_string());
}
}
fn is_cloud_base_url(base_url: Option<&String>) -> bool {
base_url
.map(|url| {
let trimmed = url.trim_end_matches('/');
trimmed == OLLAMA_CLOUD_BASE_URL || trimmed.starts_with("https://ollama.com/")
})
.unwrap_or(false)
}
fn validate_mcp_server_entry(server: &McpServerConfig, scope: McpConfigScope) -> Result<()> {
@@ -1603,9 +1665,11 @@ pub fn ensure_provider_config<'a>(
}
match config.providers.entry(provider_name.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Occupied(mut entry) => {
ensure_ollama_mode_extra(entry.get_mut());
}
Entry::Vacant(entry) => {
let default = match provider_name {
let mut default = match provider_name {
"ollama" => default_ollama_provider_config(),
other => ProviderConfig {
provider_type: other.to_string(),
@@ -1614,9 +1678,15 @@ pub fn ensure_provider_config<'a>(
extra: HashMap::new(),
},
};
entry.insert(default)
ensure_ollama_mode_extra(&mut default);
entry.insert(default);
}
}
config
.providers
.get(provider_name)
.expect("provider entry must exist")
}
/// Calculate absolute timeout for session data based on configuration
@@ -1723,6 +1793,14 @@ mod tests {
fn default_config_contains_local_provider() {
let config = Config::default();
assert!(config.providers.contains_key("ollama"));
let provider = config.providers.get("ollama").unwrap();
assert_eq!(
provider
.extra
.get(OLLAMA_MODE_KEY)
.and_then(|value| value.as_str()),
Some("auto")
);
}
#[test]
@@ -1732,6 +1810,13 @@ mod tests {
let cloud = ensure_provider_config(&mut config, "ollama-cloud");
assert_eq!(cloud.provider_type, "ollama");
assert_eq!(cloud.base_url.as_deref(), Some("http://localhost:11434"));
assert_eq!(
cloud
.extra
.get(OLLAMA_MODE_KEY)
.and_then(|value| value.as_str()),
Some("auto")
);
assert!(config.providers.contains_key("ollama"));
assert!(!config.providers.contains_key("ollama-cloud"));
}
@@ -1758,6 +1843,33 @@ mod tests {
assert_eq!(cloud.provider_type, "ollama");
assert_eq!(cloud.base_url.as_deref(), Some("https://api.ollama.com"));
assert_eq!(cloud.api_key.as_deref(), Some("secret"));
assert_eq!(
cloud
.extra
.get(OLLAMA_MODE_KEY)
.and_then(|value| value.as_str()),
Some("auto")
);
}
#[test]
fn migration_sets_cloud_mode_for_cloud_base() {
let mut config = Config::default();
if let Some(ollama) = config.providers.get_mut("ollama") {
ollama.base_url = Some(OLLAMA_CLOUD_BASE_URL.to_string());
ollama.extra.remove(OLLAMA_MODE_KEY);
}
config.apply_schema_migrations("1.4.0");
let provider = config.providers.get("ollama").expect("ollama provider");
assert_eq!(
provider
.extra
.get(OLLAMA_MODE_KEY)
.and_then(|value| value.as_str()),
Some("cloud")
);
}
#[test]