feat(cli): add /free-models command to list free OpenRouter models - #17994
Seenfinity wants to merge 1 commit into
Conversation
Adds a new /free-models slash command and a fetch_openrouter_free_models() helper that surfaces every OpenRouter model with zero pricing and tool support, bypassing the curated picker list. This addresses the gap where free :free variants (e.g. nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free) exist on OpenRouter but never make it into the curated catalog. The helper falls back to the :free entries already in OPENROUTER_MODELS when the live API is unreachable, so the command stays useful offline. Resolves NousResearch#17923
da690d3 to
ebc17b6
Compare
|
Rebased onto current Re: #23359 — this is a slash command, not a |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for isolating live free-model discovery from the curated picker. The underlying request remains unmet on current main: hermes_cli/models.py:1395-1432 only returns preferred curated IDs, while website/docs/user-guide/configuring-models.md:44 documents that intentional picker behavior.
Problems
hermes_cli/models.py:1223,:1227, and:1247usemid.endswith(":free")for fallback. But the PR-head catalog marksopenrouter/elephant-alphaandopenrouter/owl-alphaas"free"athermes_cli/models.py:62-63; neither has that suffix. A network, parse, or empty-result fallback therefore omits known free models.
Suggested changes
- Centralize fallback selection around the catalog's
"free"description and use it at all three fallback sites. - Add a regression test for a free static entry without a
:freesuffix, including the fetch-failure path.
Automated hermes-sweeper review.
| with urllib.request.urlopen(req, timeout=timeout) as resp: | ||
| payload = json.loads(resp.read().decode()) | ||
| except Exception: | ||
| return [(mid, "free") for mid, _ in OPENROUTER_MODELS if mid.endswith(":free")] |
There was a problem hiding this comment.
This fallback loses static entries whose catalog description is "free" but whose ID has no :free suffix: this PR's OPENROUTER_MODELS includes openrouter/elephant-alpha and openrouter/owl-alpha. Filter by the catalog's free description through a shared fallback helper, and use it at the other two fallback sites as well.
What
Adds
/free-modelsslash command that lists every OpenRouter model with free pricing and tool-calling support, fetched live fromopenrouter.ai/api/v1/models.Resolves #17923.
Why
Free models are a key entry point for users evaluating Hermes Agent, but the existing
/modelpicker only surfaces models that are explicitly in the curatedOPENROUTER_MODELSlist. Free:freevariants likenvidia/nemotron-3-nano-omni-30b-a3b-reasoning:freeexist on OpenRouter but aren't discoverable through the CLI today.How
New helper
fetch_openrouter_free_models()inhermes_cli/models.pythat hits the live OpenRouter/v1/modelsendpoint, filters to models wherepricing.promptandpricing.completionare both zero ANDsupported_parametersadvertisestools, then returns[(id, "free"), ...]. Reuses the existing_openrouter_model_is_freeand_openrouter_model_supports_toolshelpers so policy stays consistent with the curated picker.Network/parse failures fall back to the
:freeentries already inOPENROUTER_MODELSso the command still works offline.New
/free-modelsCommandDefinhermes_cli/commands.pyand handler_handle_free_models_commandincli.pythat prints the list with a hint on how to switch.Six tests covering: free+tool filtering, tool-support filtering, permissive handling of missing
supported_parameters, network-failure fallback, malformed-payload fallback, and de-duplication.Why a separate command and not
/model --freeThe issue suggested either a
--freeflag on/modelor a separate/free-modelscommand. I went with the separate command because the/modelpicker pulls from a hardcoded curated list across multiple providers (OPENROUTER_MODELS,_PROVIDER_MODELS), and threading a free-only mode throughparse_model_flags,switch_model, andlist_authenticated_providerswould be a much larger change that touches the CLI, gateway, and TUI gateway. A standalone command keeps the diff small and surface area predictable. Happy to reshape if you'd prefer the flag approach — just let me know.Test plan
python3 -m py_compileon all modified filesfetch_openrouter_free_modelswith mocked OpenRouter responses (filter, fallback, dedup paths)resolve_command("free-models")returns the registeredCommandDefFollowup ideas
context_length)/modelpicker behind a--freeflag (separate PR if the maintainer wants this)