diff --git a/hermes_cli/plugins.py b/hermes_cli/plugins.py index 1433cfdad2..a35692c37a 100644 --- a/hermes_cli/plugins.py +++ b/hermes_cli/plugins.py @@ -1053,67 +1053,61 @@ class PluginContext: # -- scoped provider registrars ------------------------------------------------------------------ # Every ``register__provider`` shares one body (:meth:`PluginContext._register_scoped_provider`): # type-check, register in the scope-keyed process-global registry, lease the slot so unload restores -# the displaced entry. Rows: (method, kind, registry module, base-class module:attr, label, options). -# ``normalize`` defaults to ``str.strip``; ``None`` keeps the raw name; ``lower`` also lowercases. -_SCOPED_PROVIDER_REGISTRARS: Tuple[Tuple[str, str, str, str, str, Dict[str, Any]], ...] = ( +# the displaced entry. Rows: (method, kind, registry module, base-class module:attr, label, docstring, +# options). ``normalize``: ``strip`` (default), ``lower`` (strip+lowercase) or ``None`` (raw name). +_SCOPED_PROVIDER_REGISTRARS: Tuple[Tuple[str, str, str, str, str, str, Dict[str, Any]], ...] = ( ("register_image_gen_provider", "image_gen_provider", "agent.image_gen_registry", - "agent.image_gen_provider:ImageGenProvider", "image_gen provider", {"article": "an"}), + "agent.image_gen_provider:ImageGenProvider", "image_gen provider", + "Register an :class:`agent.image_gen_provider.ImageGenProvider`; " + "``provider.name`` is matched by ``image_gen.provider``.", {"article": "an"}), ("register_video_gen_provider", "video_gen_provider", "agent.video_gen_registry", - "agent.video_gen_provider:VideoGenProvider", "video_gen provider", {}), + "agent.video_gen_provider:VideoGenProvider", "video_gen provider", + "Register an :class:`agent.video_gen_provider.VideoGenProvider`; " + "``provider.name`` is matched by ``video_gen.provider``.", {}), ("register_web_search_provider", "web_search_provider", "agent.web_search_registry", - "agent.web_search_provider:WebSearchProvider", "web provider", {}), + "agent.web_search_provider:WebSearchProvider", "web provider", + "Register an :class:`agent.web_search_provider.WebSearchProvider`; " + "``provider.name`` is matched by ``web.search_backend`` / ``web.extract_backend`` / ``web.backend``.", + {}), ("register_browser_provider", "browser_provider", "agent.browser_registry", - "agent.browser_provider:BrowserProvider", "browser provider", {}), + "agent.browser_provider:BrowserProvider", "browser provider", + "Register an :class:`agent.browser_provider.BrowserProvider`; " + "``provider.name`` is matched by ``browser.cloud_provider`` (consulted by " + "``tools.browser_tool._get_cloud_provider``).", {}), ("register_terminal_environment_provider", "terminal_environment_provider", "agent.terminal_env_registry", "agent.terminal_env_provider:TerminalEnvironmentProvider", "terminal environment provider", + "Register a :class:`agent.terminal_env_provider.TerminalEnvironmentProvider`; ``provider.name`` " + "is matched by ``terminal.backend`` when no built-in backend has that name. Built-in names (local, " + "docker, singularity, modal, daytona, vercel_sandbox, ssh) are rejected — plugins never shadow " + "in-tree backends.", {"normalize": "lower", "reject_message": "Plugin '%s' terminal environment provider rejected: %s"}), ("register_secret_source", "secret_source", "agent.secret_sources.registry", "agent.secret_sources.base:SecretSource", "secret source", + "Register a :class:`agent.secret_sources.base.SecretSource`, run by ``load_hermes_dotenv()`` " + "(after ``~/.hermes/.env``, before credentials are read) when ``secrets.`` is enabled. The " + "orchestrator owns ordering/precedence/provenance; the source only fetches. Since dotenv usually " + "loads before discovery, the manager re-pulls enabled plugin sources afterwards.", {"normalize": None, "register": "register_source", "param": "source"}), ("register_tts_provider", "tts_provider", "agent.tts_registry", - "agent.tts_provider:TTSProvider", "TTS provider", {"normalize": "lower"}), + "agent.tts_provider:TTSProvider", "TTS provider", + "Register an :class:`agent.tts_provider.TTSProvider`; ``provider.name`` is matched by " + "``tts.provider`` unless it is a built-in name (rejected with a warning) or a " + "``tts.providers.: type: command`` entry shares it (command-providers win).", + {"normalize": "lower"}), ("register_transcription_provider", "transcription_provider", "agent.transcription_registry", "agent.transcription_provider:TranscriptionProvider", "transcription provider", - {"normalize": "lower"}), + "Register an :class:`agent.transcription_provider.TranscriptionProvider`; ``provider.name`` is " + "matched by ``stt.provider`` unless it is a built-in name (rejected) or a ``stt.providers.: " + "type: command`` entry shares it (command-providers win).", {"normalize": "lower"}), ) -_SCOPED_PROVIDER_DOCS: Dict[str, str] = { - "register_image_gen_provider": "Register an :class:`agent.image_gen_provider.ImageGenProvider`; " - "``provider.name`` is matched by ``image_gen.provider``.", - "register_video_gen_provider": "Register an :class:`agent.video_gen_provider.VideoGenProvider`; " - "``provider.name`` is matched by ``video_gen.provider``.", - "register_web_search_provider": "Register an :class:`agent.web_search_provider.WebSearchProvider`; " - "``provider.name`` is matched by ``web.search_backend`` / ``web.extract_backend`` / ``web.backend``.", - "register_browser_provider": "Register an :class:`agent.browser_provider.BrowserProvider`; " - "``provider.name`` is matched by ``browser.cloud_provider`` (consulted by " - "``tools.browser_tool._get_cloud_provider``).", - "register_terminal_environment_provider": "Register a " - ":class:`agent.terminal_env_provider.TerminalEnvironmentProvider`; ``provider.name`` is matched " - "by ``terminal.backend`` when no built-in backend has that name. Built-in names (local, docker, " - "singularity, modal, daytona, vercel_sandbox, ssh) are rejected — plugins never shadow in-tree " - "backends.", - "register_secret_source": "Register a :class:`agent.secret_sources.base.SecretSource`, run by " - "``load_hermes_dotenv()`` (after ``~/.hermes/.env``, before credentials are read) when " - "``secrets.`` is enabled. The orchestrator owns ordering/precedence/provenance; the source " - "only fetches. Since dotenv usually loads before discovery, the manager re-pulls enabled plugin " - "sources afterwards.", - "register_tts_provider": "Register an :class:`agent.tts_provider.TTSProvider`; ``provider.name`` is " - "matched by ``tts.provider`` unless it is a built-in name (rejected with a warning) or a " - "``tts.providers.: type: command`` entry shares it (command-providers win).", - "register_transcription_provider": "Register an " - ":class:`agent.transcription_provider.TranscriptionProvider`; ``provider.name`` is matched by " - "``stt.provider`` unless it is a built-in name (rejected) or a ``stt.providers.: type: " - "command`` entry shares it (command-providers win).", -} - - _NAME_NORMALIZERS: Dict[Optional[str], Optional[Callable[[str], str]]] = { "strip": lambda n: n.strip(), "lower": lambda n: n.strip().lower(), None: None, } -def _make_scoped_provider_registrar(method_name, kind, registry_mod, base_ref, label, options): +def _make_scoped_provider_registrar(method_name, kind, registry_mod, base_ref, label, doc, options): """Build one ``register__provider`` method from a ``_SCOPED_PROVIDER_REGISTRARS`` row.""" base_mod, base_attr = base_ref.split(":") normalize_fn = _NAME_NORMALIZERS[options.get("normalize", "strip")] @@ -1135,7 +1129,7 @@ def _make_scoped_provider_registrar(method_name, kind, registry_mod, base_ref, l method = register_source if options.get("param") == "source" else register method.__name__ = method_name method.__qualname__ = f"PluginContext.{method_name}" - method.__doc__ = _SCOPED_PROVIDER_DOCS[method_name] + method.__doc__ = doc return _serialized_replacement(method)