Files
hermes-agent/plugins/model-providers/actual/__init__.py
T
Teknium b6d55a790e fix: adapt Actual provider salvage to current main
- fetch_models(): accept base_url kwarg (interface grew on main since May)
- runtime_provider: config-driven loopback base_url now reaches the local
  no-auth placeholder before the usable-secret gate (added on main in the
  interim, would otherwise AuthError on keyless local setups)
- test: fetch is now called with base_url by the generic live-fetch path
2026-08-05 14:08:32 -07:00

90 lines
2.7 KiB
Python

"""Actual Computer provider profile."""
from __future__ import annotations
import json
import logging
import os
from urllib.parse import urlparse
import urllib.request
from providers import register_provider
from providers.base import ProviderProfile, _profile_user_agent
logger = logging.getLogger(__name__)
DEFAULT_ACTUAL_BASE_URL = "https://api.actual.inc/v1"
DEFAULT_ACTUAL_LOCAL_BASE_URL = "http://127.0.0.1:8080/v1"
def _normalize_actual_base_url(base_url: str) -> str:
url = str(base_url or "").strip().rstrip("/")
if not url:
return DEFAULT_ACTUAL_BASE_URL
try:
parsed = urlparse(url)
host = (parsed.hostname or "").lower().rstrip(".")
path = parsed.path.rstrip("/")
except Exception:
return url
if host == "api.actual.inc" and path in {"", "/"}:
return url + "/v1"
if host in {"localhost", "127.0.0.1", "::1", "0.0.0.0"} and path in {"", "/"}:
return url + "/v1"
return url
class ActualProfile(ProviderProfile):
"""Actual Computer provider.
Hosted inference defaults to api.actual.inc. Local inference is exposed by
the Actual client only when it runs in offline mode, so users opt into it by
setting ACTUAL_BASE_URL to the local API URL.
"""
def fetch_models(
self,
*,
api_key: str | None = None,
base_url: str | None = None,
timeout: float = 8.0,
) -> list[str] | None:
base_url = _normalize_actual_base_url(
os.getenv("ACTUAL_BASE_URL", "").strip() or base_url or self.base_url
)
if not base_url:
return None
req = urllib.request.Request(base_url + "/models")
if api_key:
req.add_header("Authorization", f"Bearer {api_key}")
req.add_header("Accept", "application/json")
req.add_header("User-Agent", _profile_user_agent())
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
data = json.loads(resp.read().decode())
items = data if isinstance(data, list) else data.get("data", [])
return [m["id"] for m in items if isinstance(m, dict) and "id" in m]
except Exception as exc:
logger.debug("fetch_models(actual): %s", exc)
return None
actual = ActualProfile(
name="actual",
aliases=("actual-computer", "actualcomputer", "aci"),
display_name="Actual Computer",
description=(
"Actual Computer - hosted inference via api.actual.inc, or local "
"offline inference via ACTUAL_BASE_URL"
),
signup_url="https://actual.inc",
env_vars=("ACTUAL_API_KEY", "ACTUAL_BASE_URL"),
base_url=DEFAULT_ACTUAL_BASE_URL,
auth_type="api_key",
api_mode="codex_responses",
)
register_provider(actual)