Files
hermes-agent/agent/credential_source.py
T
Teknium e7a81e7520 fix(cli): label a callable API key by its credential source, not by "is it callable"
/config (cli.py show_config) and the agent's key banner (_print_key_banner)
printed "Microsoft Entra ID" for every callable api_key, so a custom
provider's key_cmd bearer or MiniMax OAuth read as Azure. /config also
picked Vertex out with a __module__ string compare, and the banner had no
Vertex case at all.

Each builder now tags the callable it returns with a source
(agent/credential_source.py): build_token_provider -> entra-id,
vertex_access_token -> vertex-oauth, CommandTokenSource -> key-cmd,
build_minimax_oauth_token_provider -> minimax-oauth. Both surfaces resolve
the label through one source-to-label table. An untagged callable gets the
neutral "Token provider" label. The tag is read with getattr, so no display
path calls the callable (no token minted just to mask it).

New catalog keys key_cmd_token, minimax_oauth and token_provider in every
locale (English text, matching the brand-name convention of the existing
microsoft_entra_id / google_vertex_oauth keys).

Tests: tests/agent/test_credential_source_labels.py, one red/green case per
label on each surface (7 red before: key_cmd/minimax/unknown on /config,
vertex/key_cmd/minimax/unknown on the banner). Each case's callable raises
if invoked. The old "any other callable is Entra" pin now uses a real
Entra-tagged provider.

Review finding by @unsupportedpastels on #140469
Review finding by @alt-glitch on #140469
Refs #134889 (T6)
Refs #140337
2026-10-17 07:23:06 -07:00

58 lines
2.1 KiB
Python

"""Name a callable API key's credential source without calling it.
A callable ``api_key`` mints a bearer on first use (Entra ID, Vertex OAuth, a custom provider's
``key_cmd``, MiniMax OAuth). Display paths (``/config``, the agent's key banner) must say which
kind it is, but calling it would mint a token just to mask it. Each builder tags the callable it
returns with a source; display reads the tag through one source-to-label table. An untagged
callable gets a neutral label, never another vendor's name.
"""
from __future__ import annotations
from typing import Any, Callable, Optional, TypeVar
ENTRA_ID = "entra-id"
VERTEX_OAUTH = "vertex-oauth"
KEY_CMD = "key-cmd"
MINIMAX_OAUTH = "minimax-oauth"
# Attribute read with getattr only: reading it never invokes the callable.
SOURCE_ATTR = "hermes_credential_source"
# Credential source -> i18n key. Both display surfaces resolve labels here.
SOURCE_LABEL_KEYS = {
ENTRA_ID: "cli.config.microsoft_entra_id",
VERTEX_OAUTH: "cli.config.google_vertex_oauth",
KEY_CMD: "cli.config.key_cmd_token",
MINIMAX_OAUTH: "cli.config.minimax_oauth",
}
UNKNOWN_LABEL_KEY = "cli.config.token_provider"
_F = TypeVar("_F", bound=Callable[..., Any])
def tag_credential_source(provider: _F, source: str) -> _F:
"""Record *source* on *provider* and return it (a no-op for objects that refuse attributes)."""
try:
setattr(provider, SOURCE_ATTR, source)
except (AttributeError, TypeError):
pass
return provider
def credential_source(key: Any) -> Optional[str]:
"""The source tag of a callable key, or None (string keys and untagged callables)."""
if isinstance(key, str) or not callable(key):
return None
source = getattr(key, SOURCE_ATTR, None)
return source if isinstance(source, str) else None
def credential_label(key: Any) -> Optional[str]:
"""Translated label for a callable key's source (neutral when untagged); None for a non-callable."""
if isinstance(key, str) or not callable(key):
return None
from agent.i18n import t
return t(SOURCE_LABEL_KEYS.get(credential_source(key) or "", UNKNOWN_LABEL_KEY))