Delete the in-tree model-provider plugins plugins/model-providers/copilot/ (provider `copilot`: Copilot API via the GitHub->Copilot token exchange) and plugins/model-providers/copilot-acp/ (provider `copilot-acp`: Copilot CLI over ACP). Both ship as ONE plugin, `github-copilot`, in NousResearch/hermes-labs-plugins; the LEFT_CORE row added before this commit installs it for every profile that routes through copilot/copilot-acp, and every "unknown provider" surface names `hermes plugins install github-copilot`. Core keeps no Copilot code: the generic seams the plugins fill stay, and agent/copilot_acp_client.py stays as the deprecated alias the kimchi-acp catalog plugin subclasses (agent.acp_client.ACPClient is the public base). This commit is revertible on its own: reverting it restores the bundled providers (the labs plugin stays inert on a core that bundles them). Test files that only tested the plugins move to the labs plugin's tests: tests/plugins/model_providers/test_copilot_acp_client.py tests/plugins/model_providers/test_copilot_acp_deprecation.py tests/plugins/model_providers/test_copilot_auth.py tests/plugins/model_providers/test_copilot_catalog_oauth_fallback.py tests/plugins/model_providers/test_copilot_context.py tests/plugins/model_providers/test_copilot_custom_model_ids.py tests/plugins/model_providers/test_copilot_hosts.py tests/plugins/model_providers/test_copilot_pool_exchange.py tests/plugins/model_providers/test_copilot_profile.py tests/plugins/model_providers/test_copilot_refresh_notice.py tests/plugins/model_providers/test_copilot_setup.py tests/plugins/model_providers/test_copilot_token_exchange.py tests/agent/test_copilot_initiator.py tests/agent/test_copilot_native_vision_headers.py tests/hermes_cli/test_copilot_in_model_list.py tests/hermes_cli/test_copilot_model_api_mode.py tests/hermes_cli/test_copilot_runtime_api_mode.py tests/hermes_cli/test_copilot_runtime_api_mode_stale.py tests/hermes_cli/test_model_switch_copilot_api_mode.py tests/e2e/core/providers/test_native_copilot_acp.py tests/e2e/core/providers/test_native_copilot_acp_errors.py tests/e2e/core/providers/test_native_copilot_acp_streaming.py tests/fakes/providers/copilot_acp.py Copilot-behaviour cases extracted from generic test files (same move): test_credential_pool (Copilot pool seeding / gh / env suppression), test_reasoning_carriers (Copilot carriers), test_run_agent (Copilot gpt-5 reasoning, xhigh), test_run_agent_codex_responses (Copilot 401 refresh), test_codex_transport (Copilot reasoning), test_auxiliary_main_first (vision headers), test_private_credential_writers (JWT store row), test_provider_client_seam (copilot-acp client), test_provider_wire_snapshot + its fixture (copilot / github-models cells), test_api_key_providers, test_auth_commands, test_credential_pool_off_loop (exchange single-flight), test_external_process_auth_status (Copilot CLI token stores), test_external_process_provider_seam, test_model_cache_fallback_rows, test_model_validation, test_models_relay_base_url (copilot row), test_multiplex_cli_cache_scope, test_provider_catalog, test_cold_start_gil_stall. Tests that used Copilot only as an example provider now use a fake ProviderProfile (or another in-tree provider) with the same assertions: test_auxiliary_client, test_auxiliary_named_custom_providers, test_init_fallback_on_exhausted_pool, test_intent_ack_continuation, test_models_dev, test_turn_retry_state, test_one_turn_route_publish, test_mcp_catalog_env_boundary, test_plugin_provider_picker_residue, test_delegate_external_provider, test_delegation_profile_route, test_overlay_slug_resolution, test_installed_plugin_discovery, plus the generic halves of test_run_agent_codex_responses, test_codex_transport, test_api_key_providers, test_external_process_auth_status and test_model_cache_fallback_rows.
Model Provider Plugins
Each subdirectory is a self-contained provider profile plugin. The
directory layout mirrors plugins/platforms/:
plugins/model-providers/
├── openrouter/
│ ├── __init__.py # registers the ProviderProfile
│ └── plugin.yaml # manifest: name, kind, version, description
├── anthropic/
│ ├── __init__.py
│ └── plugin.yaml
└── ...
How discovery works
providers/__init__.py._discover_providers() scans this directory (and
$HERMES_HOME/plugins/model-providers/) the first time anything calls
get_provider_profile() or list_providers(). Each __init__.py is
imported and expected to call providers.register_provider(profile).
User plugins at $HERMES_HOME/plugins/model-providers/<name>/ override
bundled plugins of the same name — last-writer-wins in
register_provider(). Drop a file there to replace a built-in.
Adding a new provider
-
Create
plugins/model-providers/<your_provider>/__init__.py:from providers import register_provider from providers.base import ProviderProfile my_provider = ProviderProfile( name="your-provider", aliases=("alias1", "alias2"), display_name="Your Provider", description="One-line description shown in the setup picker", signup_url="https://your-provider.example.com/keys", env_vars=("YOUR_PROVIDER_API_KEY", "YOUR_PROVIDER_BASE_URL"), base_url="https://api.your-provider.example.com/v1", default_aux_model="your-cheap-model", ) register_provider(my_provider) -
Create
plugins/model-providers/<your_provider>/plugin.yaml:name: your-provider-profile kind: model-provider version: 1.0.0 description: Short sentence about the provider author: Your Name
Nothing else needs to change. auth.py, config.py, models.py,
doctor.py, model_metadata.py, runtime_provider.py, and the
chat_completions transport all auto-wire from the registry.
Non-trivial profiles
Override the ProviderProfile hooks in a subclass for per-provider
quirks — see plugins/model-providers/openrouter/__init__.py for
build_extra_body and build_api_kwargs_extras examples, and
plugins/model-providers/gemini/__init__.py for thinking_config
translation.