Skip to content

feat(providers): ProviderProfile ABC + plugins/model-providers/ (salvage of #14424 + pluggable migration)#20324

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-fee7225c
May 5, 2026
Merged

feat(providers): ProviderProfile ABC + plugins/model-providers/ (salvage of #14424 + pluggable migration)#20324
teknium1 merged 2 commits into
mainfrom
hermes/hermes-fee7225c

Conversation

@teknium1
Copy link
Copy Markdown
Contributor

@teknium1 teknium1 commented May 5, 2026

Provider declarations become one plugin each. Adding a simple api-key provider now requires a single plugins/model-providers/<name>/ directory (2 files: __init__.py + plugin.yaml) and zero edits anywhere else. Bundled plugins can be overridden from $HERMES_HOME/plugins/model-providers/<name>/ without editing the repo.

Salvaged from @kshitijk4poor's PR #14424 onto current main, then migrated to the same plugin pattern used for IRC and Teams gateway adapters.

Commits in this PR

  1. feat: provider modules — ProviderProfile ABC, 33 providers, fetch_models, transport single-path — the salvaged feat: provider modules — ProviderProfile ABC, 29 providers, fetch_models, transport single-path #14424 refactor, updated for current main
  2. feat(providers): make all 33 providers pluggable under plugins/model-providers/ — moves every profile to a bundled plugin dir, adds user-override path, auto-coerces user-installed plugins to kind: model-provider
  3. docs(providers): add model-provider-plugin authoring guide + fix stale refs — new /docs/developer-guide/model-provider-plugin page (authoring guide: layout, fields, hooks, user overrides, testing, pip distribution); stale comment cleanup in main.py/doctor.py/auth.py/models.py; updates to AGENTS.md, user-guide/features/plugins.md (plugin types: 3→4), adding-providers.md + provider-runtime.md cross-references

What changes for plugin authors

Before:

providers/myprovider.py         # one file in repo, required editing the tree

After:

plugins/model-providers/myprovider/
├── __init__.py                 # calls register_provider(ProviderProfile(...))
└── plugin.yaml                 # name, kind: model-provider, version, description

Or for a user override (no repo edit):

~/.hermes/plugins/model-providers/myprovider/
├── __init__.py
└── plugin.yaml

User plugins win over bundled plugins on name collision (last-writer-wins in register_provider()).

Changes (commit 2 of 2)

  • plugins/model-providers/ — new category, 28 bundled plugin dirs (covering 33 profiles — minimax, kimi, opencode, gemini each ship two profiles in one plugin)
  • providers/__init__.py_discover_providers() now scans bundled + user plugin dirs first, then falls back to legacy providers/*.py
  • providers/ — now only base.py (ABC), __init__.py (registry), README.md
  • hermes_cli/plugins.py — adds kind: model-provider to _VALID_PLUGIN_KINDS; general PluginManager skips imports of these (providers/ discovery owns them); heuristic auto-coerces user-installed standalone plugins whose __init__.py calls register_provider with a ProviderProfile to kind: model-provider
  • skip_names extended so plugins/model-providers/ isn't double-scanned by the general manager
  • 4 new tests in tests/providers/test_plugin_discovery.py — bundled discovery, user override, general-loader isolation

Docs

  • plugins/model-providers/README.md — plugin contract + authoring guide
  • providers/README.md — rewritten, points at plugin dir
  • website/docs/developer-guide/adding-providers.md — fast path now describes plugin layout
  • website/docs/developer-guide/provider-runtime.md — references both paths

Changes (commit 1 of 2 — salvage)

Original #14424 changes, same as documented before:

  • providers/ — ProviderProfile ABC + registry + 33 profiles
  • agent/transports/chat_completions.py — profile path via _build_kwargs_from_profile() for registered providers; legacy flag path retained for lmstudio / tencent-tokenhub
  • run_agent.py — profile path integrated; legacy-path variables scoped correctly
  • hermes_cli/{auth,config,doctor,main,models,runtime_provider}.py — auto-extend from list_providers()
  • agent/{auxiliary_client,model_metadata}.py — auto-wire
  • New 3 profiles missing from original PR: alibaba-coding-plan, azure-foundry, minimax-oauth

Salvage notes (from commit 1)

  • 965 commits stale; 10 conflict points re-resolved
  • agent/copilot_acp_client.py left in place (PR's relocation would have reverted 7 landed fixes)
  • lmstudio / tencent-tokenhub stay on legacy flag path — reasoning-effort logic has no clean profile-hook equivalent yet
  • Dropped lmstudio aliases from custom profile
  • Skipped openrouter / custom from PROVIDER_REGISTRY auto-extension (resolve_provider() special-cases them)
  • runtime_provider.py: profile.api_mode is a fallback, not an override (PR's version broke minimax /v1 URL)
  • GeminiProfile.build_extra_body translates thinking_config (native + OpenAI-compat nested)

Validation

Before (main) After
tests/providers/ 83 / 83
tests/run_agent/test_provider_parity.py 93 / 93 93 / 93
tests/hermes_cli/test_runtime_provider_resolution.py 109 / 109 109 / 109
tests/hermes_cli/test_plugin* + tests/test_plugin_skills.py 166 / 173 166 / 173
tests/hermes_cli/ (full dir) 3735 / 3744 (9 pre-existing) 3733 / 3744
tests/run_agent/ (full dir) 1224 / 1226 (2 pre-existing) 1224 / 1226
tests/agent/ (full dir) 2482 / 2483 (1 pre-existing) 2482 / 2483

All failing tests are reproduced on origin/main without this PR. Not regressions.

E2E smoke test confirms:

  • 33 profiles load from plugins/model-providers/
  • Auto-wired into PROVIDER_REGISTRY, CANONICAL_PROVIDERS, OPTIONAL_ENV_VARS, doctor, _URL_TO_PROVIDER
  • openrouter / custom correctly absent from PROVIDER_REGISTRY (behavior parity)
  • lmstudio / tencent-tokenhub correctly fall through to legacy path
  • Gemini thinking_config produces correct native + OpenAI-compat nested shapes
  • User override: $HERMES_HOME/plugins/model-providers/gmi/ replaces bundled gmi profile

Closes #14418. Original PR #14424 by @kshitijk4poor — salvage preserves their authorship on commit 1 via rebase-merge.

…els, transport single-path

Introduces providers/ package — single source of truth for every
inference provider. Adding a simple api-key provider now requires one
providers/<name>.py file with zero edits anywhere else.

What this PR ships:
- providers/ package (ProviderProfile ABC + 33 profiles across 4 api_modes)
- ProviderProfile declarative fields: name, api_mode, aliases, display_name,
  env_vars, base_url, models_url, auth_type, fallback_models, hostname,
  default_headers, fixed_temperature, default_max_tokens, default_aux_model
- 4 overridable hooks: prepare_messages, build_extra_body,
  build_api_kwargs_extras, fetch_models
- chat_completions.build_kwargs: profile path via _build_kwargs_from_profile,
  legacy flag path retained for lmstudio/tencent-tokenhub (which have
  session-aware reasoning probing that doesn't map cleanly to hooks yet)
- run_agent.py: profile path for all registered providers; legacy path
  variable scoping fixed (all flags defined before branching)
- Auto-wires: auth.PROVIDER_REGISTRY, models.CANONICAL_PROVIDERS,
  doctor health checks, config.OPTIONAL_ENV_VARS, model_metadata._URL_TO_PROVIDER
- GeminiProfile: thinking_config translation (native + openai-compat nested)
- New tests/providers/ (79 tests covering profile declarations, transport
  parity, hook overrides, e2e kwargs assembly)

Deltas vs original PR (salvaged onto current main):
- Added profiles: alibaba-coding-plan, azure-foundry, minimax-oauth
  (were added to main since original PR)
- Skipped profiles: lmstudio, tencent-tokenhub stay on legacy path (their
  reasoning_effort probing has no clean hook equivalent yet)
- Removed lmstudio alias from custom profile (it's a separate provider now)
- Skipped openrouter/custom from PROVIDER_REGISTRY auto-extension
  (resolve_provider special-cases them; adding breaks runtime resolution)
- runtime_provider: profile.api_mode only as fallback when URL detection
  finds nothing (was breaking minimax /v1 override)
- Preserved main's legacy-path improvements: deepseek reasoning_content
  preserve, gemini Gemma skip, OpenRouter response caching, Anthropic 1M
  beta recovery, etc.
- Kept agent/copilot_acp_client.py in place (rejected PR's relocation —
  main has 7 fixes landed since; relocation would revert them)
- _API_KEY_PROVIDER_AUX_MODELS alias kept for backward compat with existing
  test imports

Co-authored-by: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>
Closes #14418
@alt-glitch alt-glitch added type/refactor Code restructuring, no behavior change P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder comp/cli CLI entry point, hermes_cli/, setup wizard labels May 5, 2026
@alt-glitch
Copy link
Copy Markdown
Collaborator

Supersedes #16326 (which salvaged #14424). This is the most current attempt — 33 providers (vs 29 in #16326), rebased on current main with all conflict points re-resolved. Related tracking issue: #14418.

…providers/

Every provider profile is now a self-contained plugin under
plugins/model-providers/<name>/, mirroring the plugins/platforms/
pattern established for IRC and Teams. The ProviderProfile ABC
stays in providers/; the per-provider profile data moves out.

- plugins/model-providers/<name>/__init__.py calls register_provider()
- plugins/model-providers/<name>/plugin.yaml declares kind: model-provider
- providers/__init__.py._discover_providers() lazily scans bundled plugins
  then $HERMES_HOME/plugins/model-providers/<name>/ (user override path)
- User plugins with the same name override bundled ones (last-writer-wins
  in register_provider)
- Legacy providers/<name>.py layout still supported for back-compat with
  out-of-tree editable installs
- Hermes PluginManager: new kind=model-provider; skipped like memory
  plugins (providers/ discovery owns them); standalone plugins with
  register_provider+ProviderProfile in their __init__.py auto-coerce to
  this kind (same heuristic as memory providers)
- skip_names extended to include 'model-providers' so the general
  PluginManager doesn't double-scan the category
- 4 new tests in tests/providers/test_plugin_discovery.py covering
  bundled discovery, user override, and general-loader isolation
- Docs updated: website/docs/developer-guide/adding-providers.md,
  provider-runtime.md, providers/README.md, plugins/model-providers/README.md

No API break: auth.py / config.py / doctor.py / models.py / runtime_provider.py /
model_metadata.py / auxiliary_client.py / chat_completions.py / run_agent.py
all still consume providers via get_provider_profile() / list_providers() —
they just now see plugin-discovered entries instead of pkgutil-iterated ones.

Third parties can now drop a single directory into
~/.hermes/plugins/model-providers/<name>/ to add or override an inference
provider without touching the repo.
@teknium1 teknium1 changed the title feat(providers): ProviderProfile ABC + 33 providers (salvage of #14424) feat(providers): ProviderProfile ABC + plugins/model-providers/ (salvage of #14424 + pluggable migration) May 5, 2026
@teknium1 teknium1 merged commit 9022804 into main May 5, 2026
12 of 14 checks passed
@teknium1 teknium1 deleted the hermes/hermes-fee7225c branch May 5, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tracking: provider modules refactor — Cycle 2 of transport/provider infrastructure

3 participants