diff --git a/hermes_cli/model_setup_flows.py b/hermes_cli/model_setup_flows.py index 9bad641b1f3..e5881b3531f 100644 --- a/hermes_cli/model_setup_flows.py +++ b/hermes_cli/model_setup_flows.py @@ -2563,11 +2563,17 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""): if existing_key: break - existing_key, abort = _prompt_api_key( - pconfig, existing_key, provider_id=provider_id - ) - if abort: - return + if provider_id == "ollama": + # Local Ollama servers are unauthenticated — there is no key to + # prompt for. The runtime substitutes a placeholder bearer; a key + # for a reverse-proxied server can be set via model.api_key. + pass + else: + existing_key, abort = _prompt_api_key( + pconfig, existing_key, provider_id=provider_id + ) + if abort: + return # Gemini free-tier gate: free-tier daily quotas (<= 250 RPD for Flash) # are exhausted in a handful of agent turns, so refuse to wire up the @@ -2659,14 +2665,18 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""): except (KeyboardInterrupt, EOFError): print() override = "" - if override and base_url_env: + if override: if not override.startswith(("http://", "https://")): print( " Invalid URL — must start with http:// or https://. Keeping current value." ) - else: + elif base_url_env: save_env_value(base_url_env, override) effective_base = override + else: + # No env var for this provider (e.g. ollama): the override + # still takes effect via model.base_url, written below. + effective_base = override # Model selection — resolution order: # 1. models.dev registry (cached, filtered for agentic/tool-capable models) @@ -2690,6 +2700,15 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""): model_list = [] if model_list: print(f" Found {len(model_list)} model(s) from LM Studio") + elif provider_id == "ollama": + from hermes_cli.models import fetch_ollama_local_models + + model_list = fetch_ollama_local_models(base_url=effective_base, timeout=3.0) + if model_list: + print(f" Found {len(model_list)} installed model(s) on the Ollama server") + else: + print(f" No models found — is Ollama running at {effective_base}?") + print(" Pull one first (e.g. `ollama pull qwen3:8b`) or enter a name below.") elif provider_id == "ollama-cloud": from hermes_cli.models import fetch_ollama_cloud_models diff --git a/tests/hermes_cli/test_provider_catalog.py b/tests/hermes_cli/test_provider_catalog.py index 1b0ecc252c5..bfda8ecac0d 100644 --- a/tests/hermes_cli/test_provider_catalog.py +++ b/tests/hermes_cli/test_provider_catalog.py @@ -95,11 +95,14 @@ def test_api_key_providers_expose_a_credential_env_var(): surface at least one env var to write the key into (otherwise the GUI can't configure it). - Exemptions: ``aws_sdk`` (bedrock — uses AWS_REGION/AWS_PROFILE) and the + Exemptions: ``aws_sdk`` (bedrock — uses AWS_REGION/AWS_PROFILE); the ``custom`` bring-your-own-endpoint pseudo-provider, which is configured - inline via the local-endpoint flow rather than a fixed env var. + inline via the local-endpoint flow rather than a fixed env var; and + ``ollama``, whose local server is unauthenticated by design — the GUI + configures it via server detection, and a key for a reverse-proxied + server goes in model.api_key. """ - exempt = {"custom"} + exempt = {"custom", "ollama"} for d in provider_catalog(): if d.auth_type == "api_key" and d.slug not in exempt: assert d.api_key_env_vars, f"{d.slug} is api_key but exposes no env var"