mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
feat(azure-foundry): add Microsoft Entra ID auth
Use azure-identity DefaultAzureCredential for keyless Foundry auth. Preserve refreshable callable credentials through OpenAI and Anthropic client paths. Add setup, doctor, auth status, docs, and tests for Entra auth. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
457fa913b8
commit
9df9816dab
38 changed files with 3772 additions and 122 deletions
|
|
@ -5334,7 +5334,9 @@ def get_external_process_provider_status(provider_id: str) -> Dict[str, Any]:
|
|||
|
||||
def get_auth_status(provider_id: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Generic auth status dispatcher."""
|
||||
target = provider_id or get_active_provider()
|
||||
target = (provider_id or get_active_provider() or "").strip().lower()
|
||||
if not target:
|
||||
return {"logged_in": False}
|
||||
if target == "spotify":
|
||||
return get_spotify_auth_status()
|
||||
if target == "nous":
|
||||
|
|
@ -5351,6 +5353,8 @@ def get_auth_status(provider_id: Optional[str] = None) -> Dict[str, Any]:
|
|||
return get_minimax_oauth_auth_status()
|
||||
if target == "copilot-acp":
|
||||
return get_external_process_provider_status(target)
|
||||
if target == "azure-foundry":
|
||||
return _get_azure_foundry_auth_status()
|
||||
# API-key providers
|
||||
pconfig = PROVIDER_REGISTRY.get(target)
|
||||
if pconfig and pconfig.auth_type == "api_key":
|
||||
|
|
@ -5365,6 +5369,83 @@ def get_auth_status(provider_id: Optional[str] = None) -> Dict[str, Any]:
|
|||
return {"logged_in": False}
|
||||
|
||||
|
||||
def _get_azure_foundry_auth_status() -> Dict[str, Any]:
|
||||
"""Return structural auth status for Azure Foundry.
|
||||
|
||||
``logged_in`` is structural, matching other non-OAuth provider status
|
||||
checks:
|
||||
|
||||
* ``auth_mode == "entra_id"`` AND ``azure-identity`` is importable
|
||||
(we do NOT mint a token here; ``hermes doctor`` runs the live
|
||||
probe and reports whether the credential chain can acquire one).
|
||||
* ``auth_mode == "api_key"`` (default) AND ``AZURE_FOUNDRY_API_KEY``
|
||||
is set with a usable value.
|
||||
|
||||
Never invokes the Entra credential chain — keeps CLI startup latency
|
||||
flat regardless of token-service / az login state.
|
||||
"""
|
||||
info: Dict[str, Any] = {"provider": "azure-foundry"}
|
||||
try:
|
||||
from hermes_cli.config import load_config, get_env_value
|
||||
cfg = load_config()
|
||||
except Exception:
|
||||
cfg = {}
|
||||
|
||||
model_cfg = cfg.get("model") if isinstance(cfg, dict) else None
|
||||
auth_mode = "api_key"
|
||||
base_url = ""
|
||||
if isinstance(model_cfg, dict):
|
||||
auth_mode = str(model_cfg.get("auth_mode") or "api_key").strip().lower() or "api_key"
|
||||
base_url = str(model_cfg.get("base_url") or "").strip()
|
||||
info["auth_mode"] = auth_mode
|
||||
info["base_url"] = base_url
|
||||
|
||||
if auth_mode == "entra_id":
|
||||
try:
|
||||
from agent.azure_identity_adapter import (
|
||||
EntraIdentityConfig,
|
||||
SCOPE_AI_AZURE_DEFAULT,
|
||||
has_azure_identity_installed,
|
||||
)
|
||||
installed = has_azure_identity_installed()
|
||||
entra_cfg = {}
|
||||
if isinstance(model_cfg, dict) and isinstance(model_cfg.get("entra"), dict):
|
||||
entra_cfg = model_cfg["entra"]
|
||||
identity_config = EntraIdentityConfig.from_dict(
|
||||
entra_cfg,
|
||||
default_scope=SCOPE_AI_AZURE_DEFAULT,
|
||||
)
|
||||
info["azure_identity_installed"] = installed
|
||||
info["scope"] = identity_config.scope
|
||||
info["credential_probe"] = "not_run"
|
||||
info["credential_verified"] = False
|
||||
info["logged_in"] = bool(installed)
|
||||
if not installed:
|
||||
info["hint"] = (
|
||||
"azure-identity not installed. Install with: "
|
||||
"pip install azure-identity (or rely on Hermes' "
|
||||
"lazy-install at first use)."
|
||||
)
|
||||
else:
|
||||
info["hint"] = (
|
||||
"azure-identity is installed; live credential validation "
|
||||
"is skipped here. Run `hermes doctor` to verify token acquisition."
|
||||
)
|
||||
return info
|
||||
except Exception as exc:
|
||||
info["logged_in"] = False
|
||||
info["error"] = f"azure-identity check failed: {exc}"
|
||||
return info
|
||||
|
||||
# api_key mode (default)
|
||||
try:
|
||||
api_key = get_env_value("AZURE_FOUNDRY_API_KEY") or os.getenv("AZURE_FOUNDRY_API_KEY", "")
|
||||
except Exception:
|
||||
api_key = os.getenv("AZURE_FOUNDRY_API_KEY", "")
|
||||
info["logged_in"] = has_usable_secret(api_key)
|
||||
return info
|
||||
|
||||
|
||||
def resolve_api_key_provider_credentials(provider_id: str) -> Dict[str, Any]:
|
||||
"""Resolve API key and base URL for an API-key provider.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue