mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 01:21:43 +00:00
fix: allow local providers (Ollama, LM Studio) without API keys in delegation
Local model servers running on localhost, 127.0.0.1, .local mDNS hostnames, or RFC 1918 private networks don't require authentication. Previously, _resolve_delegation_credentials() hard-required an API key for any base_url, making it impossible to use Ollama or similar local servers for subagent delegation without setting a dummy key. Changes: - Add _is_local_base_url() helper detecting localhost, loopback, .local, and RFC 1918 private network addresses - base_url path: skip API key requirement for local endpoints, use 'ollama' placeholder key (accepted by local servers) - provider path: same logic — if resolve_runtime_provider returns an empty API key but the resolved base_url is local, use 'ollama' placeholder - Remote endpoints still require a real API key (no security regression) - Update existing test to use remote URL (was testing localhost which is now allowed) - Add 19 new tests covering local provider credential resolution
This commit is contained in:
parent
94f1758742
commit
0e4bc9474d
3 changed files with 280 additions and 5 deletions
|
|
@ -2098,6 +2098,37 @@ def _resolve_child_credential_pool(effective_provider: Optional[str], parent_age
|
|||
return None
|
||||
|
||||
|
||||
def _is_local_base_url(base_url: Optional[str]) -> bool:
|
||||
"""Return True if base_url points to a local/private network address.
|
||||
|
||||
Local providers (Ollama, LM Studio, llama.cpp server, etc.) typically
|
||||
don't require authentication. This check covers:
|
||||
- localhost / loopback (127.0.0.1, ::1)
|
||||
- .local mDNS hostnames (e.g. studio.local)
|
||||
- RFC 1918 private networks (10/8, 172.16/12, 192.168/16)
|
||||
"""
|
||||
if not base_url:
|
||||
return False
|
||||
hostname = base_url_hostname(base_url)
|
||||
if not hostname:
|
||||
return False
|
||||
# localhost variants
|
||||
if hostname in ("localhost", "127.0.0.1", "::1"):
|
||||
return True
|
||||
# mDNS .local hostnames
|
||||
if hostname.endswith(".local"):
|
||||
return True
|
||||
# RFC 1918 private subnets
|
||||
import ipaddress
|
||||
|
||||
try:
|
||||
ip = ipaddress.ip_address(hostname)
|
||||
return ip.is_private or ip.is_loopback
|
||||
except ValueError:
|
||||
pass # not an IP address, that's fine
|
||||
return False
|
||||
|
||||
|
||||
def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict:
|
||||
"""Resolve credentials for subagent delegation.
|
||||
|
||||
|
|
@ -2111,6 +2142,10 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict:
|
|||
If neither base_url nor provider is configured, returns None values so the
|
||||
child inherits everything from the parent agent.
|
||||
|
||||
Local endpoints (localhost, 127.0.0.1, .local, RFC 1918 private nets)
|
||||
don't require API keys — a placeholder "ollama" key is used when none
|
||||
is provided, since these servers accept any or no authentication.
|
||||
|
||||
Raises ValueError with a user-friendly message on credential failure.
|
||||
"""
|
||||
configured_model = str(cfg.get("model") or "").strip() or None
|
||||
|
|
@ -2120,6 +2155,10 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict:
|
|||
|
||||
if configured_base_url:
|
||||
api_key = configured_api_key or os.getenv("OPENAI_API_KEY", "").strip()
|
||||
# Local endpoints (Ollama, LM Studio, etc.) don't require auth.
|
||||
# Use a dummy key so the OpenAI client doesn't reject the request.
|
||||
if not api_key and _is_local_base_url(configured_base_url):
|
||||
api_key = "ollama"
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
"Delegation base_url is configured but no API key was found. "
|
||||
|
|
@ -2175,10 +2214,15 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict:
|
|||
|
||||
api_key = runtime.get("api_key", "")
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
f"Delegation provider '{configured_provider}' resolved but has no API key. "
|
||||
f"Set the appropriate environment variable or run 'hermes auth'."
|
||||
)
|
||||
# Local providers don't require real API keys.
|
||||
resolved_base = runtime.get("base_url", "")
|
||||
if _is_local_base_url(resolved_base):
|
||||
api_key = "ollama"
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Delegation provider '{configured_provider}' resolved but has no API key. "
|
||||
f"Set the appropriate environment variable or run 'hermes auth'."
|
||||
)
|
||||
|
||||
return {
|
||||
"model": configured_model,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue