feat(config): separate Ollama into local/cloud providers, add OpenAI & Anthropic defaults, bump schema version to 1.6.0
This commit is contained in:
@@ -7,7 +7,8 @@ use clap::Subcommand;
|
||||
use owlen_core::LlmProvider;
|
||||
use owlen_core::ProviderConfig;
|
||||
use owlen_core::config::{
|
||||
self as core_config, Config, OLLAMA_CLOUD_BASE_URL, OLLAMA_CLOUD_ENDPOINT_KEY, OLLAMA_MODE_KEY,
|
||||
self as core_config, Config, OLLAMA_CLOUD_API_KEY_ENV, OLLAMA_CLOUD_BASE_URL,
|
||||
OLLAMA_CLOUD_ENDPOINT_KEY, OLLAMA_MODE_KEY,
|
||||
};
|
||||
use owlen_core::credentials::{ApiCredentials, CredentialManager, OLLAMA_CLOUD_CREDENTIAL_ID};
|
||||
use owlen_core::encryption;
|
||||
@@ -17,6 +18,7 @@ use serde_json::Value;
|
||||
|
||||
const DEFAULT_CLOUD_ENDPOINT: &str = OLLAMA_CLOUD_BASE_URL;
|
||||
const CLOUD_ENDPOINT_KEY: &str = OLLAMA_CLOUD_ENDPOINT_KEY;
|
||||
const CLOUD_PROVIDER_KEY: &str = "ollama_cloud";
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum CloudCommand {
|
||||
@@ -28,8 +30,8 @@ pub enum CloudCommand {
|
||||
/// Override the cloud endpoint (default: https://ollama.com)
|
||||
#[arg(long)]
|
||||
endpoint: Option<String>,
|
||||
/// Provider name to configure (default: ollama)
|
||||
#[arg(long, default_value = "ollama")]
|
||||
/// Provider name to configure (default: ollama_cloud)
|
||||
#[arg(long, default_value = "ollama_cloud")]
|
||||
provider: String,
|
||||
/// Overwrite the provider base URL with the cloud endpoint
|
||||
#[arg(long)]
|
||||
@@ -37,20 +39,20 @@ pub enum CloudCommand {
|
||||
},
|
||||
/// Check connectivity to Ollama Cloud
|
||||
Status {
|
||||
/// Provider name to check (default: ollama)
|
||||
#[arg(long, default_value = "ollama")]
|
||||
/// Provider name to check (default: ollama_cloud)
|
||||
#[arg(long, default_value = "ollama_cloud")]
|
||||
provider: String,
|
||||
},
|
||||
/// List available cloud-hosted models
|
||||
Models {
|
||||
/// Provider name to query (default: ollama)
|
||||
#[arg(long, default_value = "ollama")]
|
||||
/// Provider name to query (default: ollama_cloud)
|
||||
#[arg(long, default_value = "ollama_cloud")]
|
||||
provider: String,
|
||||
},
|
||||
/// Remove stored Ollama Cloud credentials
|
||||
Logout {
|
||||
/// Provider name to clear (default: ollama)
|
||||
#[arg(long, default_value = "ollama")]
|
||||
/// Provider name to clear (default: ollama_cloud)
|
||||
#[arg(long, default_value = "ollama_cloud")]
|
||||
provider: String,
|
||||
},
|
||||
}
|
||||
@@ -82,6 +84,7 @@ async fn setup(
|
||||
|
||||
let base_changed = {
|
||||
let entry = ensure_provider_entry(&mut config, &provider);
|
||||
entry.enabled = true;
|
||||
configure_cloud_endpoint(entry, &endpoint, force_cloud_base_url)
|
||||
};
|
||||
|
||||
@@ -140,6 +143,7 @@ async fn status(provider: String) -> Result<()> {
|
||||
let api_key = hydrate_api_key(&mut config, manager.as_ref()).await?;
|
||||
{
|
||||
let entry = ensure_provider_entry(&mut config, &provider);
|
||||
entry.enabled = true;
|
||||
configure_cloud_endpoint(entry, DEFAULT_CLOUD_ENDPOINT, false);
|
||||
}
|
||||
|
||||
@@ -190,6 +194,7 @@ async fn models(provider: String) -> Result<()> {
|
||||
|
||||
{
|
||||
let entry = ensure_provider_entry(&mut config, &provider);
|
||||
entry.enabled = true;
|
||||
configure_cloud_endpoint(entry, DEFAULT_CLOUD_ENDPOINT, false);
|
||||
}
|
||||
|
||||
@@ -245,8 +250,9 @@ async fn logout(provider: String) -> Result<()> {
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(entry) = provider_entry_mut(&mut config) {
|
||||
if let Some(entry) = config.providers.get_mut(&provider) {
|
||||
entry.api_key = None;
|
||||
entry.enabled = false;
|
||||
}
|
||||
|
||||
crate::config::save_config(&config)?;
|
||||
@@ -255,28 +261,7 @@ async fn logout(provider: String) -> Result<()> {
|
||||
}
|
||||
|
||||
fn ensure_provider_entry<'a>(config: &'a mut Config, provider: &str) -> &'a mut ProviderConfig {
|
||||
if provider == "ollama"
|
||||
&& config.providers.contains_key("ollama-cloud")
|
||||
&& !config.providers.contains_key("ollama")
|
||||
{
|
||||
if let Some(mut legacy) = config.providers.remove("ollama-cloud") {
|
||||
legacy.provider_type = "ollama".to_string();
|
||||
config.providers.insert("ollama".to_string(), legacy);
|
||||
}
|
||||
}
|
||||
|
||||
core_config::ensure_provider_config(config, provider);
|
||||
|
||||
let entry = config
|
||||
.providers
|
||||
.get_mut(provider)
|
||||
.expect("provider entry must exist");
|
||||
|
||||
if entry.provider_type != "ollama" {
|
||||
entry.provider_type = "ollama".to_string();
|
||||
}
|
||||
|
||||
entry
|
||||
core_config::ensure_provider_config_mut(config, provider)
|
||||
}
|
||||
|
||||
fn configure_cloud_endpoint(entry: &mut ProviderConfig, endpoint: &str, force: bool) -> bool {
|
||||
@@ -287,6 +272,10 @@ fn configure_cloud_endpoint(entry: &mut ProviderConfig, endpoint: &str, force: b
|
||||
Value::String(normalized.clone()),
|
||||
);
|
||||
|
||||
if entry.api_key_env.is_none() {
|
||||
entry.api_key_env = Some(OLLAMA_CLOUD_API_KEY_ENV.to_string());
|
||||
}
|
||||
|
||||
if force
|
||||
|| entry
|
||||
.base_url
|
||||
@@ -298,10 +287,7 @@ fn configure_cloud_endpoint(entry: &mut ProviderConfig, endpoint: &str, force: b
|
||||
}
|
||||
|
||||
if force {
|
||||
entry.extra.insert(
|
||||
OLLAMA_MODE_KEY.to_string(),
|
||||
Value::String("cloud".to_string()),
|
||||
);
|
||||
entry.enabled = true;
|
||||
}
|
||||
|
||||
entry.base_url != previous_base
|
||||
@@ -333,10 +319,11 @@ fn normalize_endpoint(endpoint: &str) -> String {
|
||||
}
|
||||
|
||||
fn canonical_provider_name(provider: &str) -> String {
|
||||
let normalized = provider.trim().replace('_', "-").to_ascii_lowercase();
|
||||
let normalized = provider.trim().to_ascii_lowercase().replace('-', "_");
|
||||
match normalized.as_str() {
|
||||
"" => "ollama".to_string(),
|
||||
"ollama-cloud" => "ollama".to_string(),
|
||||
"" => CLOUD_PROVIDER_KEY.to_string(),
|
||||
"ollama" => CLOUD_PROVIDER_KEY.to_string(),
|
||||
"ollama_cloud" => CLOUD_PROVIDER_KEY.to_string(),
|
||||
value => value.to_string(),
|
||||
}
|
||||
}
|
||||
@@ -362,21 +349,6 @@ fn set_env_if_missing(var: &str, value: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
fn provider_entry_mut(config: &mut Config) -> Option<&mut ProviderConfig> {
|
||||
if config.providers.contains_key("ollama") {
|
||||
config.providers.get_mut("ollama")
|
||||
} else {
|
||||
config.providers.get_mut("ollama-cloud")
|
||||
}
|
||||
}
|
||||
|
||||
fn provider_entry(config: &Config) -> Option<&ProviderConfig> {
|
||||
if let Some(entry) = config.providers.get("ollama") {
|
||||
return Some(entry);
|
||||
}
|
||||
config.providers.get("ollama-cloud")
|
||||
}
|
||||
|
||||
fn unlock_credential_manager(
|
||||
config: &Config,
|
||||
storage: Arc<StorageManager>,
|
||||
@@ -463,14 +435,13 @@ async fn hydrate_api_key(
|
||||
set_env_if_missing("OLLAMA_CLOUD_API_KEY", &key);
|
||||
}
|
||||
|
||||
let Some(cfg) = provider_entry_mut(config) else {
|
||||
return Ok(Some(key));
|
||||
};
|
||||
let cfg = core_config::ensure_provider_config_mut(config, CLOUD_PROVIDER_KEY);
|
||||
configure_cloud_endpoint(cfg, &credentials.endpoint, false);
|
||||
return Ok(Some(key));
|
||||
}
|
||||
|
||||
if let Some(key) = provider_entry(config)
|
||||
if let Some(key) = config
|
||||
.provider(CLOUD_PROVIDER_KEY)
|
||||
.and_then(|cfg| cfg.api_key.as_ref())
|
||||
.map(|value| value.trim())
|
||||
.filter(|value| !value.is_empty())
|
||||
@@ -501,8 +472,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn canonicalises_provider_names() {
|
||||
assert_eq!(canonical_provider_name("OLLAMA_CLOUD"), "ollama");
|
||||
assert_eq!(canonical_provider_name(" ollama-cloud"), "ollama");
|
||||
assert_eq!(canonical_provider_name(""), "ollama");
|
||||
assert_eq!(canonical_provider_name("OLLAMA_CLOUD"), CLOUD_PROVIDER_KEY);
|
||||
assert_eq!(canonical_provider_name(" ollama-cloud"), CLOUD_PROVIDER_KEY);
|
||||
assert_eq!(canonical_provider_name(""), CLOUD_PROVIDER_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ fn build_local_provider(cfg: &Config) -> anyhow::Result<Arc<dyn Provider>> {
|
||||
})?;
|
||||
|
||||
match provider_cfg.provider_type.as_str() {
|
||||
"ollama" | "ollama-cloud" => {
|
||||
"ollama" | "ollama_cloud" => {
|
||||
let provider = OllamaProvider::from_config(provider_cfg, Some(&cfg.general))?;
|
||||
Ok(Arc::new(provider) as Arc<dyn Provider>)
|
||||
}
|
||||
@@ -172,40 +172,16 @@ fn run_config_doctor() -> Result<()> {
|
||||
changes.push("created configuration file from defaults".to_string());
|
||||
}
|
||||
|
||||
if !config
|
||||
.providers
|
||||
.contains_key(&config.general.default_provider)
|
||||
{
|
||||
config.general.default_provider = "ollama".to_string();
|
||||
changes.push("default provider missing; reset to 'ollama'".to_string());
|
||||
if config.provider(&config.general.default_provider).is_none() {
|
||||
config.general.default_provider = "ollama_local".to_string();
|
||||
changes.push("default provider missing; reset to 'ollama_local'".to_string());
|
||||
}
|
||||
|
||||
if let Some(mut legacy) = config.providers.remove("ollama-cloud") {
|
||||
legacy.provider_type = "ollama".to_string();
|
||||
use std::collections::hash_map::Entry;
|
||||
match config.providers.entry("ollama".to_string()) {
|
||||
Entry::Occupied(mut existing) => {
|
||||
let entry = existing.get_mut();
|
||||
if entry.api_key.is_none() {
|
||||
entry.api_key = legacy.api_key.take();
|
||||
}
|
||||
if entry.base_url.is_none() && legacy.base_url.is_some() {
|
||||
entry.base_url = legacy.base_url.take();
|
||||
}
|
||||
entry.extra.extend(legacy.extra);
|
||||
}
|
||||
Entry::Vacant(slot) => {
|
||||
slot.insert(legacy);
|
||||
}
|
||||
for key in ["ollama_local", "ollama_cloud", "openai", "anthropic"] {
|
||||
if !config.providers.contains_key(key) {
|
||||
core_config::ensure_provider_config_mut(&mut config, key);
|
||||
changes.push(format!("added default configuration for provider '{key}'"));
|
||||
}
|
||||
changes.push(
|
||||
"migrated legacy 'ollama-cloud' provider into unified 'ollama' entry".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if !config.providers.contains_key("ollama") {
|
||||
core_config::ensure_provider_config(&mut config, "ollama");
|
||||
changes.push("added default ollama provider configuration".to_string());
|
||||
}
|
||||
|
||||
match config.mcp.mode {
|
||||
|
||||
Reference in New Issue
Block a user