feat: provider modules — ProviderProfile ABC, 29 providers, fetch_models, transport single-path#14424
feat: provider modules — ProviderProfile ABC, 29 providers, fetch_models, transport single-path#14424kshitijk4poor wants to merge 1 commit into
Conversation
|
|
||
| def test_base_url(self): | ||
| p = get_provider_profile("nvidia") | ||
| assert "nvidia.com" in p.base_url |
f1b0ed4 to
93b00f4
Compare
7c4950c to
cecfc88
Compare
cecfc88 to
b61e6c6
Compare
|
added |
dee0cab to
370fa41
Compare
06398a5 to
ecf579e
Compare
0e25739 to
c4afe52
Compare
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
c4afe52 to
03a71c2
Compare
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
03a71c2 to
b398ce2
Compare
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
b398ce2 to
27d8a67
Compare
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
|
Reviewed the flagged files. I don’t see supply-chain payload indicators in these changes:
That said, the filename-based scanner is right to force maintainer review here. These filenames are high-signal in package-install contexts, and both should be eyeballed before merge. One non-supply-chain issue worth checking: the Google OAuth scope extraction change appears to discard the raw callback URL before trying to read Recommendation:
|
27d8a67 to
ad0ecc6
Compare
| continue | ||
| if _pp.auth_type != "api_key" or not _pp.env_vars: | ||
| continue | ||
| _label = _pp.display_name or _pp.name |
| continue | ||
| _label = _pp.display_name or _pp.name | ||
| if _label in _known_doctor_names: | ||
| continue |
040a7d6 to
bc262ff
Compare
|
Concrete implementation offer The design judgment in #14424 that I would build on is the move to After #14424 lands, I would be happy to take a follow-up PR for three non-interactive commands, each roughly 50-150 LOC and built entirely on that substrate:
Sketch of the shape I have in mind: def build_models_payload(provider: str | None = None) -> dict[str, object]:
profiles = [get_provider_profile(provider)] if provider else list_providers()
rows = []
for profile in filter(None, profiles):
state = resolve_provider_state(profile)
if not state.configured:
continue
model_ids = profile.fetch_models(api_key=state.api_key) or list(profile.fallback_models)
rows.append({
"provider": profile.name,
"auth_state": state.auth_state,
"models": [{"id": m, "configured": is_configured(profile.name, m)} for m in model_ids],
})
return {"schema_version": 1, "providers": rows}
@pytest.fixture
def demo_provider(monkeypatch):
profile = ProviderProfile(name="demo", base_url="https://demo.test/v1")
monkeypatch.setattr(profile, "fetch_models", lambda **_: ["demo/model-a"])
return profileIssue close-out map I would propose:
Want me to open the follow-up PR against Context: I maintain the |
735bf2f to
8d0710a
Compare
…els, transport single-path feat: provider modules — ProviderProfile ABC, 29 providers, fetch_models, transport single-path Introduces providers/ as the single source of truth for every inference provider. All 29 providers declared with correct data cross-checked against auth.py, runtime_provider.py and auxiliary_client.py. Rebased onto main (30307a9). Incorporates post-salvage fixes from 5672414 (gmi aux model google/gemini-3.1-flash-lite-preview, already set in providers/gmi.py).
8d0710a to
84d1673
Compare
Hey @perlowja, that sounds awesome! I’d love to discuss this further to ensure it doesn’t get overlooked. Can we move to Discord? I’m kxee there. |
|
Hey @kxee — happy to coordinate but let's do it over email rather than Discord. Drop a desired-plan note to jperlow@gmail.com (stacking-vs-master-after, target merge order, scope-creep to avoid, anything you want to gate the follow-up on) and we'll go from there. Reference for the docs surface I have in flight: #15214 — To be clear: your #14424 is the architectural basis. The CLI follow-ups ( Talk soon. |
|
On vacation will follow up with you in a week.
*Jason Perlow | Argonaut Media Communications*
*Voice/Text * (954) 242-3484 | ***@***.*** |
*Blog: techbroiler.net <http://techbroiler.net>Read my Tech and Food
Industry articles:* https://linktr.ee/jperlow
*Bluesky:* https://bsky.app/profile/jperlow.bsky.social
*Need to schedule a meeting with me? *https://bit.ly/3y8P3Gp
<https://calendly.com/jasonperlow>
…On Sun, May 10, 2026, 12:11 PM kshitij ***@***.***> wrote:
Closed #14424 <#14424>.
—
Reply to this email directly, view it on GitHub
<#14424 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/BGW2SJFHOYGO3N77QHYILLL42CZ5DAVCNFSM6AAAAACYDMPGRKVHI2DSMVQWIX3LMV45UABCJFZXG5LFIV3GK3TUJZXXI2LGNFRWC5DJN5XDWMRVGM2TMMZQGM3DQNY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Summary
This PR introduces
providers/as the single source of truth for every inferenceprovider Hermes supports — all 30 profiles declared, all per-provider quirks
centralised, and the transport reduced to a single profile-driven code path. Adding
a new simple API-key provider now requires one file with zero edits to any other file in the
repo.
What this PR does
providers/ package — 30 profiles, fully wired
Every field a provider needs is declared in one
providers/<name>.pyfile:namestrapi_modestrchat_completions|codex_responses|anthropic_messages|bedrock_converse|copilot_acpaliasestuple[str, ...]get_provider_profile()display_namestrdescriptionstrsignup_urlstrenv_varstuple[str, ...]*_BASE_URLbase_urlstrmodels_urlstr{base_url}/models)auth_typestrapi_key|oauth_device_code|oauth_external|copilot|aws_sdk|external_processfallback_modelstuple[str, ...]hostnamestrbase_urlwhen empty)default_headersdict[str, str]fixed_temperatureAnyNone= use caller's value;OMIT_TEMPERATURE= don't send temperature at alldefault_max_tokensint | Nonedefault_aux_modelstr4 overridable hooks
prepare_messages(messages)list[dict] -> list[dict]cache_controlbuild_extra_body(**ctx)-> dictextra_bodyfields — Nous: attribution tags; OpenRouter: provider preferencesbuild_api_kwargs_extras(**ctx)-> (dict, dict)(extra_body_additions, top_level_kwargs)— handles the Kimi/Qwen split where some fields go top-level, others toextra_bodyfetch_models(*, api_key, timeout)-> list[str] | None{models_url or base_url}/modelswith Bearer auth; override for custom auth (Anthropic), no REST endpoint (Bedrock), or public catalog (OpenRouter)Auto-wiring table
Adding a
providers/<name>.pyfile automatically extends all of the following —no edits to any other file required:
PROVIDER_REGISTRYin auth.pyhermes_cli/auth.py:373list_providers()at module load; skips names already declaredCANONICAL_PROVIDERSlisthermes_cli/models.py:755ProviderEntry(name, display_name, description)for api_key providers--providerCLI choiceshermes_cli/main.py:7221_build_provider_choices()derives fromCANONICAL_PROVIDERS_is_profile_api_key_provider()catch-allhermes_cli/main.py:1450get_provider_profile()lookup — new providers route to_model_flow_api_key_providerwithout a newelifprovider_model_ids()profile pathhermes_cli/models.py:1892profile.fetch_models()thenprofile.fallback_modelsfor any api_key providerhermes_cli/doctor.py:958(display_name, env_vars, models_url, base_env, True)for api_key providers not already listedOPTIONAL_ENV_VARSconfig registryhermes_cli/config.py:4170_inject_profile_env_vars()adds eachenv_varsentry with description, prompt, URL, category_URL_TO_PROVIDERdomain mapagent/model_metadata.py:316get_hostname()auto-derived frombase_url; maps domain → provider name for URL-based detectionagent/transports/chat_completions.py:97_build_kwargs_from_profile()— profile present → all 4 hooks run, legacy flags bypassedruntime_provider.pyapi_modehermes_cli/runtime_provider.py:237profile.api_moderead directly; no new elif branch in api_mode resolutionauxiliary_client.pyaux modelagent/auxiliary_client.py:159profile.default_aux_modelread first; falls back to legacy hardcoded dictrun_agent.pyprofile pathrun_agent.py:7521get_provider_profile(self.provider)— every registered provider gets profile-driven kwargsProvider count: 30 profiles across all api_modes
chat_completions(24)codex_responses(2)anthropic_messages(3)bedrock_converse(1)Key code changes
transport — legacy flags gone, profile path is THE path
ChatCompletionsTransport.build_kwargsnow has two branches:_build_kwargs_from_profile()runsthe 4 hooks and returns. No flag params needed.
get_provider_profile()returnsNone— i.e. a completely unknown custom endpoint not inproviders/. Sends aclean chat_completions request with no provider-specific quirks.
The 20+ boolean flags that were threaded through
build_kwargs(supports_reasoning,is_openrouter,is_nous,qwen_vl_high_resolution, etc.) are gone from thefunction's effective code path for all known providers.
Before:
After:
run_agent.py — all 30 providers live via profile path
run_agent.pycallsget_provider_profile(self.provider). When a profile is found(all 30 registered providers), it assembles per-call context and passes
provider_profile=_profileto the transport. No per-providerifbranches remainin the kwargs-building path.
runtime_provider.py — reads
profile.api_mode_resolve_api_mode()now callsget_provider_profile(provider)and returnsprofile.api_modefor all registered providers. No newelifbranch needed fornew providers.
auxiliary_client.py — reads
profile.default_aux_model_get_aux_model_for_provider()triesprofile.default_aux_modelfirst, then fallsback to the legacy hardcoded dict (kept for Anthropic only, which predates profiles).
auth.py — auto-extends
PROVIDER_REGISTRYAt module load, loops over
list_providers()and adds aProviderConfigentry forevery api_key provider not already declared. New providers need zero changes here.
models.py — auto-extends
CANONICAL_PROVIDERS+ profile path inprovider_model_idsCANONICAL_PROVIDERSlist extended at import fromlist_providers().provider_model_ids(provider)callsprofile.fetch_models()thenprofile.fallback_modelsfor any api_key provider with a profile, replacingper-provider copy-paste fetch blocks.
doctor.py — auto-extends health checks
The
_apikey_providers_staticlist is extended at runtime fromlist_providers().New providers automatically get a
/modelshealth check inhermes doctor.config.py —
_inject_profile_env_vars()Eagerly populates
OPTIONAL_ENV_VARSfrom every profile'senv_varstuple atimport time. Descriptions, prompts, URLs, and categories are derived from the
profile — no manual entries needed.
model_metadata.py — auto-extends
_URL_TO_PROVIDERAt module load, calls
profile.get_hostname()for every profile and inserts anyhostname not already in
_URL_TO_PROVIDER. URL-based provider detection worksautomatically for new providers.
main.py —
_build_provider_choices()+_is_profile_api_key_provider()_build_provider_choices()derives the--providerargparse choices directlyfrom
CANONICAL_PROVIDERS, which is already auto-extended by profiles._is_profile_api_key_provider()is a catch-all inselect_provider_and_model()that routes any api_key profile to
_model_flow_api_key_providerwithoutrequiring an explicit
eliffor each provider.Proof: one file, zero other changes
providers/gmi.pyis 26 lines and required zero edits to any other file (for api_key auth_type):After adding this file: GMI appears in
--providerchoices,hermes doctorchecksits key,
hermes modellists its models, the setup wizard prompts forGMI_API_KEY,and
run_agent.pyroutes requests through the profile path — no other files touched.Tests
Provider-specific test breakdown:
Files changed (summary)
providers/— 28 provider files +__init__.py+base.py+README.mdacp_adapter/copilot_client.py(ACP client relocated fromagent/)tests/providers/— 4 test files, 869 linesagent/transports/chat_completions.py—_build_kwargs_from_profile(), legacy flags removed from known-provider pathrun_agent.py— all 30 providers go through profile pathhermes_cli/runtime_provider.py— readsprofile.api_modeagent/auxiliary_client.py— readsprofile.default_aux_modelhermes_cli/auth.py— auto-extendsPROVIDER_REGISTRYfrom profileshermes_cli/models.py— auto-extendsCANONICAL_PROVIDERS, profile path inprovider_model_ids()hermes_cli/doctor.py— auto-extends health checks from profileshermes_cli/config.py—_inject_profile_env_vars()agent/model_metadata.py— auto-extends_URL_TO_PROVIDERhermes_cli/main.py—_build_provider_choices()+_is_profile_api_key_provider()58 files changed, 3811 insertions, 369 deletions
Closes #14418.