Commit Graph

354 Commits

Author SHA1 Message Date
Teknium 72b7c6c8d1 fix(models): keep discovery sentinels out of the user-facing models mapping
PR #67934 marked auto-discovered catalogs by writing two sentinel keys
INSIDE the user-facing ``models`` mapping of custom provider entries:
``__discovered_model_catalog__`` (written by
_save_discovered_models_to_config) and ``__explicit_model_allowlist__``
(injected by _normalize_custom_provider_entry). Every consumer of that
mapping — pickers, selectors, gateway/agent readers, and the user's own
config.yaml — had to know to filter those keys, and any site that
didn't listed them as phantom model IDs (``__discovered_model_catalog__``
showing up as a selectable "model"). The v11→v12 config migration and
the ACP session-state test caught exactly that leak on main.

Replace the in-mapping sentinels with a single entry-level flag:

- ``models_discovered: true`` now sits next to ``models``/``base_url``
  on the provider entry; the models mapping stays a clean
  ``{model_id: metadata}`` dict with no reserved keys.
- _save_discovered_models_to_config writes the new shape and refreshes
  catalogs it previously discovered (entry-level flag or legacy
  sentinel) instead of treating them as user-curated metadata.
- _normalize_custom_provider_entry no longer injects
  ``__explicit_model_allowlist__``; a dict-shaped models mapping counts
  as an explicit allowlist exactly when the entry is NOT marked
  models_discovered.
- _models_config_is_allowlist takes the discovered flag as a parameter
  (new helper _entry_models_discovered resolves it, including the
  legacy in-mapping sentinel); all call sites updated
  (model_switch.py, model_setup_flows.py, acp_adapter/server.py).
- Backward compat, no config version bump: configs written by a
  pre-fix Hermes (sentinels inside models) still read correctly —
  ``__discovered_model_catalog__: true`` is treated as
  models_discovered, both sentinel keys are stripped from model
  listings, and the next discovery save migrates the entry to the
  clean shape. Covered by a new regression test.

Also restore ``except Exception:`` on the pre-existing guards this PR
had narrowed to specific exception tuples (the resolve_runtime_provider
fallback in switch_model, the picker discovery/cache guards in
list_authenticated_providers, _get_model_config_dict, and
_credential_fingerprint). Those guards were intentionally broad on
main — a failed resolution or probe must degrade to the fallback path,
never crash the model switch. Guards the PR introduced for its own new
probe code keep their authored tuples.

The ACP new_session payload also goes back to
probe_current_custom_provider=False, matching the contract main's
test_new_session_returns_authenticated_cross_provider_model_state pins
(session opens must not block on live-probing the current custom
endpoint).
2026-08-18 14:26:16 -07:00
Vadelma 81e813507f fix(models): preserve empty catalog and custom model semantics
Co-authored-by: Taneli Mielikäinen <taneli.mielikainen@iki.fi>
2026-08-18 14:26:16 -07:00
Vadelma f70e9abce9 fix(models): close final provider discovery edge cases
Co-authored-by: Taneli Mielikäinen <taneli.mielikainen@iki.fi>
2026-08-18 14:26:16 -07:00
Vadelma 638b72f5ef feat(models): add native Ollama catalog semantics
Co-authored-by: Taneli Mielikäinen <taneli.mielikainen@iki.fi>
2026-08-18 14:26:16 -07:00
Eleanor Berger 9a862483bb feat(models): add Meta Muse Spark 1.2 to OpenRouter curated picker
Surface meta/muse-spark-1.2 in the Hermes model selector (CLI, desktop,
gateway) via the curated OpenRouter list and regenerated model-catalog.json.
The model is live on OpenRouter with tool calling; the picker intentionally
does not show the full OpenRouter catalogue.
2026-08-17 12:57:09 -07:00
lepetitprince716-prog 7e439dbb1b perf: parallelize provider model-list fetches in model picker
When the 1h provider_models_cache.json TTL lapses, the model picker
serially fetches /v1/models for each authenticated provider. With 10+
providers this stacks to 15-30s of blocking before the picker renders.

Add a parallel prefetch step before the serial picker build loops:
- _collect_authed_provider_slugs(): lightweight credential pre-scan
  that mirrors sections 1/2/2b without fetching model lists
- _prefetch_provider_models_parallel(): ThreadPoolExecutor-based
  concurrent fetch of stale/missing cache entries (max 8 workers)
- update_provider_cache_entry(): thread-safe single-entry cache writer
  with threading.Lock to prevent concurrent write races

Guardrails:
- Skipped when <=3 authed providers (overhead not worth it)
- Skipped when refresh=True (serial path force-refreshes)
- Exception-isolated (falls back to serial path on any failure)
- No behavioral change (same model lists, same picker output)

Closes #80413

(cherry picked from commit 89dddd6cb5d53d73278e0518c375fb5b878e5c6b)
2026-08-15 00:34:29 -07:00
Teknium bc5805c35f fix: compare base-URL hostnames, not substrings, in provider-identity checks
Port of the bug class from earendil-works/pi#7933 (DeepSeek base-URL
detection matched by raw substring, missing case variants and matching
lookalike URLs). Hermes had the same class at five sites:

- cli_agent_setup_mixin.py: keyless-custom-endpoint detection treated any
  URL containing the OpenRouter host substring (path segment, lookalike
  domain) as OpenRouter, and missed case variants of the real host.
- models.py validate_requested_model: same substring check for routing an
  openrouter provider with a custom base_url to the custom catalog.
- runtime_provider.py: local-endpoint autodetect matched the string
  localhost anywhere in the URL, including remote hostnames containing it.
- gateway/run.py: /status endpoint display, same local-host substring.
- agent_runtime_helpers.py: Nous Portal cache-layout detection matched
  the nousresearch substring anywhere in the URL.

All sites now use the existing base_url_host_matches / base_url_hostname
helpers (exact host or subdomain, case-insensitive). Regression tests
proven to fail against the old predicates.
2026-08-14 20:47:13 -07:00
SHL0MS b21e0bd8c9 fix(providers): honor per-provider TLS on custom /models and pricing probes
Per-provider ssl_ca_cert / ssl_verify reached the httpx chat client and the
auxiliary clients (#56681), but the endpoint discovery and pricing probes did
not. Both probe families resolved TLS from process-wide env vars only:

- the requests-based metadata/pricing probe
  (agent/model_metadata.py::_resolve_requests_verify)
- the urllib-based /models catalog probe
  (hermes_cli/models.py::probe_api_models)

A custom endpoint whose chain verifies against the provider's configured
bundle, but not the process SSL_CERT_FILE, then logged a spurious
CERTIFICATE_VERIFY_FAILED on every probe even though the chat path worked.
Pointing a global CA env var at the bundle fixes it but changes verification
for every provider, defeating the point of a per-provider setting.

This threads the selected provider's TLS settings into both probe paths,
reusing get_custom_provider_tls_settings so there is no second precedence
chain:

- _resolve_requests_verify(base_url) looks up the provider's ssl_verify /
  ssl_ca_cert before falling back to the env vars. Callers with no base_url
  keep the exact env-only behavior.
- probe_api_models builds an ssl.SSLContext from the provider settings and
  passes it through open_credentialed_url, which gains an ssl_context seam on
  the cloned secure opener. Unmatched or public endpoints pass None and keep
  urllib's default policy.

Tests: tests/agent/test_custom_provider_ca_probes.py covers both probe
families (provider CA, ssl_verify:false, unmatched, missing file, config
lookup failure) plus end-to-end assertions that the resolved verify value and
SSLContext actually reach the request seam. Verified against the neighboring
metadata, pricing, TLS, and urllib-security suites (266 tests) with no
regressions.
2026-08-14 16:37:36 -07:00
rob-maron 0e4e8baf63 add deepseek v5 pro 0813 to model catalog (#86256) 2026-08-14 18:14:27 +00:00
Jaaneek 9c15f0191c fix(models): refresh xAI picker via models.dev; pin grok-4.6
Stop freezing the xAI/xAI-OAuth catalog at import so /model and setup
pick up new Grok IDs after the models.dev cache refreshes. Put xai and
xai-oauth on the shared picker-time models.dev merge path and pin
grok-4.6 as the default headline model.
2026-08-13 22:09:50 -07:00
Teknium b8d9230cf2 feat(models): add google/gemini-3.7-flash to nous + openrouter catalogs, drop gemini-3.6-flash
Swaps the Google flash entry in the OpenRouter and Nous Portal curated
lists to the newly released gemini-3.7-flash (half the price of
3.6-flash: $0.375/M in, $1.875/M out per OpenRouter live metadata;
served on both endpoints, verified live). Also updates the OpenRouter
plugin fallback_models mirror and regenerates model-catalog.json.

Scoped to the two named providers: vertex/gemini/gmi curated lists and
aux defaults still carry 3.6-flash.
2026-08-13 11:28:16 -07:00
Teknium acadd719d3 docs: note Grok 4.6 priority overrides in resolve_fast_mode_overrides docstring
Follow-up to the salvaged #84820; mirrors the doc line from #84848.
2026-08-13 11:07:37 -07:00
fangliquan db5e2402c2 fix(xai): preserve Grok 4.6 wire capabilities 2026-08-13 11:07:37 -07:00
Teknium 94be919411 feat: rename Codex OAuth provider label to "ChatGPT or Codex Subscription"
Renames the openai-codex provider's display label across the CLI
(hermes model picker, provider labels), the dashboard OAuth accounts
catalog, and the Desktop onboarding + settings provider pickers.
Slug, aliases, and auth flows are unchanged.
2026-08-13 03:06:08 -07:00
Teknium 314968f5fb Port from PrimeIntellect-ai/prime-agent#1258: derive OpenRouter reasoning support and effort levels from catalog metadata
OpenRouter's /v1/models entries advertise reasoning capability
(supported_parameters + reasoning.mandatory/supported_efforts). Use that
metadata as the primary gate in _supports_reasoning_extra_body instead of
the hand-maintained vendor-prefix allowlist, which went stale one vendor at
a time (nvidia/ missing -> #75386). Also clamp the requested effort to the
nearest LOWER catalog-supported level in the OpenRouter profile so ultra/max
against a high-capped route no longer 4xxes.

Cache-only on the hot path: capabilities parse for free out of the existing
fetch_openrouter_models() payload, a background warmer covers cold starts,
and unknown models/offline catalogs fall back to the static prefix list
unchanged.
2026-08-12 19:44:32 -07:00
rob-maron 66a4161620 add grok 4.6 (#84837) 2026-08-12 21:52:31 +00:00
rob-maron 3e09adb109 add grok 4.6 (#84661) 2026-08-12 13:37:04 -04:00
Brooklyn Nicholson 071eab821b fix(models): let the titler actually see a provider's model catalog
The fast-model picker reads /v1/models to find the small model a provider
currently serves, and it asked anonymously. Most of those endpoints need a
key, so the fetch 401'd and the empty result read as "this provider has no
small model" — the picker fell back to its curated list and never noticed.

Worse, a failed fetch cached its empty result forever, so one bad moment
during startup disabled live model discovery for the life of the process,
and the processes that read this run for weeks. Give the failure an expiry
and pass the provider's credentials.

The bare family rungs (-mini, -flash, haiku) also picked whichever id
sorted first, which is the oldest generation a provider still serves:
gpt-3.5-mini over gpt-5.4-mini, claude-3-haiku over claude-haiku-4.5.
Compare the digit runs as numbers so the rung meant to keep us current
does.
2026-08-09 04:33:58 -05:00
Austin Pickett e0c3caf3b8 fix(model-picker): serve cached custom-provider catalog on no-probe opens (supersedes #81665, #81556) (#81973)
* fix(model-picker): serve cached custom-provider catalog on no-probe opens

#58183 stopped GUI picker opens from live-probing saved custom
OpenAI-compatible endpoints so a stopped local server could not stall the
picker. It gated the whole discovery block, not just the network call, so
`cached_fetch_api_models()` was skipped too — and with it the catalog an
earlier probe had already written to `provider_models_cache.json`.

A custom endpoint that is not the current provider therefore renders only
the models named in its config entry. A local server with 8 models loaded
shows the 1 model that was saved when the provider was first added, on
every picker open, while an explicit Refresh shows all 8.

Add `cache_only` to `cached_fetch_api_models()`: answer from disk within
the existing stale-serve window, never fetch, never revalidate off-thread,
return None on a miss. Split the three call sites in
`list_authenticated_providers()` into what the user's config permits
(`discover_models`, an explicit `models:` allowlist) and how we may obtain
it, so suppressing the probe now downgrades to a cached read instead of
skipping discovery outright. `discover_models: false` still pins, and a
cache hit no longer writes back to config since the probe that populated
it already did.

The latency win stands: a cold cache is a miss, so picker opens against
offline endpoints still make zero network calls.

* test(model-picker): pin the cached-catalog contract for no-probe opens

Cover both halves of the invariant, since fixing either one alone
reintroduces a bug the other guards against.

`cache_only` on `cached_fetch_api_models()`: a fresh entry and an entry
past its TTL but inside the stale-serve window both serve; an entry beyond
that window, an empty cache, rotated credentials, `force_refresh`, and a
missing base_url are all misses — and none of them fetch or spawn a
background revalidation.

`list_authenticated_providers()` on the GUI path: a non-current endpoint
with a warm cache reports its full catalog across all three provider
shapes (`custom_providers`, `providers:`, bare `provider: custom`) with no
live fetch attempted. A cold cache keeps the configured list and still
makes no network call, which is the #58183 guarantee. `discover_models:
false` keeps pinning, and a cache hit does not write back to config.

* fix: persist discovered custom-provider models in the hermes model flow

The `hermes model` named-custom-provider flow (_model_flow_named_custom)
probes the endpoint and shows the full catalog, but never persists it to the
entry's `models:` list. No-probe surfaces (dashboard, desktop, ACP) call
build_models_payload(..., probe_custom_providers=False) and only render the
configured `models:` list, so a provider added via `hermes model` collapses
to the single `model:` default everywhere except the CLI. OpenAI-compatible
providers added via a probing picker already benefit from
_save_discovered_models_to_config; the CLI flow did not.

Persist the live catalog after a successful probe, mirroring the picker path
in model_switch.py. A failed save is non-fatal.

* fix(model-picker): stop an auto-saved catalog pinning a keyless endpoint

The cached-catalog read added for no-probe picker opens still sat behind
the no-key discovery gate, so it never reached the shape that motivated
it: a keyless local model server.

`bool(api_key) or not has_explicit_models` is a network-cost gate. It
exists so Hermes does not probe an endpoint it cannot authenticate to
when that endpoint already declares its catalog (5f00f36ba, 1039e90b5).
Reading a catalog an earlier probe already paid for costs nothing, so
the gate belongs on the probe, not on discovery as a whole.

Left on the discovery side it re-pins the endpoint it was meant to
spare. A successful probe calls `_save_discovered_models_to_config()`,
which writes a plain list into `models:` — exactly the shape
`_models_config_is_allowlist()` reads back as an explicit user
allowlist. A keyless server therefore froze on the catalog of its first
probe and could never widen again, which is the "lineup changes after
config was written" case. f66319097 already carved the dict shape out of
this trap for the same reason; the list shape is the other door into it.

Move the clause to `_probe_live` at both custom-endpoint sites. Probe
suppression is unchanged — verified byte-identical to main across the
keyed/keyless x declared/undeclared matrix — and `discover_models: false`
remains the documented way to pin a catalog.

* test(model-picker): cover the keyless auto-save pinning trap

Three tests around the gate move, each failing on the code before it:

- a keyless endpoint carrying an auto-saved `models:` list still reads
  its full cached catalog
- the same row, cold cache and probing enabled, still makes zero live
  fetches — the network-cost gate the clause exists for
- an end-to-end round trip: persist a probe result via
  `_save_discovered_models_to_config()`, reload it, and assert the shape
  we wrote does not read back as a user pin

The round-trip test guards the whole chain rather than one branch, so a
future change that makes the saved shape look like an intentional
allowlist fails here even if the gate logic is refactored.

* fix(model-picker): key the custom-endpoint model cache by api_mode

`cached_fetch_api_models()` fingerprints entries with `api_mode`, but no
call site in `list_authenticated_providers()` passed it, so every custom
row resolved to the `api_mode=None` fingerprint. Two rows sharing a
base_url and credential but differing by `api_mode` are deliberately
distinct picker rows — it is part of `group_key` at both sites — yet they
collapsed onto one cache entry.

That was latent while probing was the only way to fill a row: a mismatched
entry was overwritten by the row's own live fetch. Serving that entry
without a probe makes it visible, so an `anthropic_messages` row could
render the catalog an OpenAI-mode row cached against the same URL. The
wire protocols differ (`x-api-key` + `anthropic-version` vs
`Authorization: Bearer`), so those catalogs are not interchangeable.

Persist `api_mode` on the group at both grouping sites — it is already
part of `group_key`, so it is constant across the group — and pass it
into the cache read. Section 3b (bare `provider: custom`) has no
`api_mode` in scope and already reads with the empty-credential
fingerprint, so it is unchanged.

Reported by Copilot review on #81973.

---------

Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
Co-authored-by: Navlem <114683850+Navlem@users.noreply.github.com>
2026-08-08 16:07:03 -04:00
Axmr1 b35cacf8b5 fix(opencode-go): route gpt-* models to /v1/responses (codex_responses)
OpenCode Go serves GPT 5.6 Luna only via the Responses API per its
published endpoint table (https://opencode.ai/docs/go/#endpoints), but
opencode_model_api_mode() had no gpt- case in the Go branch, sending
Luna to /v1/chat/completions. The relay's shim streams full text but
never emits a finish_reason chunk, so every complete answer is
classified as a mid-stream drop and each turn fails with 'Response
remained truncated after 4 continuation attempts'.

Mirror the Zen branch: gpt- on Go -> codex_responses. Base URL needs
no change (normalize_opencode_base_url already keeps /v1 for
codex_responses). Extend test_opencode_go_api_modes_match_docs with
the Luna assertions.
2026-08-08 14:47:11 +05:30
kshitij c015663b21 fix(models): corrupt-at cache rows degrade to live fetch in cached_provider_model_ids
Surfaced during the post-merge review pass on our own #81113 follow-up:
cached_fetch_api_models gained _cache_entry_valid (numeric-'at'
validation) but its sibling cached_provider_model_ids still did
float(entry.get('at', 0)), which raises ValueError/TypeError on a
hand-edited or corrupted provider_models_cache.json row and propagates
uncaught into the /model picker call sites. Same fix, same helper:
corrupt rows are now a cache miss (live fetch), never an exception.
Both wrappers now share the identical validity predicate, closing the
divergence the 'mirrors' docstring promised away.

Also two test nits from the same review: unused OrderedDict import
dropped and the drain-order assertion strengthened to pin LRU-first
FIFO order in tests/gateway/test_agent_cache_pressure.py.

Mutation-checked: restoring the raising float() form makes the new
corrupt-at tests fail.
2026-08-07 23:00:28 +05:30
kshitij 7cf71c32bb fix: follow-ups for salvaged PR #80740
- Give cached_fetch_api_models the same stale-while-revalidate tier as
  cached_provider_model_ids: TTL-expired entries within the 7d window are
  served instantly while a background refresh rewrites the cache —
  without this, every /model open an hour into the session re-blocked on
  the live probe (#72762's stall class, deferred).
- Generalize _spawn_swr_refresh(cache_key, refresh_fn) so non-slug
  custom:<base_url> keys reuse the same inflight-dedupe scaffolding;
  slug behavior unchanged (default refresh_fn preserved).
- Convert the missed sibling site: acp_adapter/server.py
  _named_custom_provider_catalogs() live-probed every custom_providers
  row's /v1/models per ACP catalog build.
- Extract _cache_entry_valid() (the fp/models predicate existed 4x) and
  validate 'at' is numeric so hand-edited/corrupt cache JSON degrades to
  a live fetch instead of raising through the picker's blanket except.
- Flatten the dead api_mode conditional (fetch_api_models declares
  api_mode=None; branch was behaviorally inert).
- Tests: 4 new guards (stale-serve, stale-window cutoff, generalized SWR
  write-through, corrupt-at degradation) — stale-serve and corrupt-at
  mutation-checked; 2 existing tests updated for the new behavior.
2026-08-07 21:02:40 +05:30
Prashant Jain fb435aae97 perf(model): disk-cache custom-provider /v1/models probes
Custom OpenAI-compatible endpoints (named custom_providers rows, bare
provider: custom, and per-endpoint-map entries) called fetch_api_models()
directly at three call sites in model_switch.py, with no disk cache — unlike
first-class providers, which go through cached_provider_model_ids(). Every
plain /model open live-probed the active custom endpoint's /v1/models,
regardless of how recently it had already been probed.

Adds cached_fetch_api_models() in hermes_cli/models.py: a TTL disk-cache
wrapper keyed on custom:<base_url> (custom endpoints have no
PROVIDER_REGISTRY slug to key on) and fingerprinted on api_key/api_mode/
headers, with the same stale-beats-nothing fallback policy as
cached_provider_model_ids(). Routes all three probe call sites through it.

Since prewarm_picker_cache_async() already calls list_authenticated_providers()
with probe_custom_providers defaulting True, this also fixes the endpoint
being warmed on boot (populating the disk cache) instead of that work being
discarded on every open — any custom endpoint (an LLM gateway, a
self-hosted vLLM/SGLang server, etc.), not just one specific provider.

Fixes #72762. Salvaged from #72810 per review feedback: extracts just the
verified custom-endpoint cache fix with real cache-contract test coverage
(hit/stale/rotation/refresh/fallback), leaving the credential-pool and
Copilot-token-exchange costs described in the issue for separate follow-up.
2026-08-07 21:02:40 +05:30
Justin Bennington a9acb400ba feat(providers): add Actual Computer inference provider 2026-08-05 14:08:32 -07:00
Teknium 3c3ae7428d feat(models): add qwen3.8-max to Nous portal + OpenRouter catalogs, replacing qwen3.7-max
Qwen3.8 Max is live on both OpenRouter and the Nous portal
(qwen/qwen3.8-max, 1M context, 131K max output). Per the
newest-max-replaces-last-max convention, it takes qwen3.7-max's slot
in both curated lists.

- hermes_cli/models.py: OPENROUTER_MODELS + _PROVIDER_MODELS[nous]
  swap qwen/qwen3.7-max -> qwen/qwen3.8-max
- agent/model_metadata.py: DEFAULT_CONTEXT_LENGTHS entry for
  qwen3.8-max at 1,000,000 (verified against OpenRouter live
  metadata and Nous /v1/models 2026-08-03)
- tests/test_empty_model_fallback.py: swap incidental catalog fixture
  to the surviving slug
- website/static/api/model-catalog.json: regenerated

Pricing snapshot skipped: both routes bill via official_models_api
(live pricing), verified with resolve_billing_route. Reasoning
timeout floor already covered by the qwen3 prefix (180s).
2026-08-03 17:19:49 -07:00
Xue-1997 bd56440f4c perf(models): cache GitHub Copilot model catalog for 5 minutes
The picker path fetches the Copilot /models catalog multiple times per
process (list_authenticated_providers -> provider_model_ids ->
_fetch_github_models, plus get_copilot_model_context / normalize
helpers). Cache the filtered catalog at module level with a short TTL
so repeated picker opens do not pay a TLS handshake each time.

Fold-fixes on top of the original patch:
- key the cache by api_key so a mid-process credential swap never
  serves the previous account's catalog
- use time.monotonic() so wall-clock adjustments cannot extend the TTL
- deep-copy on store/serve so callers cannot mutate cached entries
- tests updated to patch _urlopen_model_catalog_request (main routes
  catalog fetches through open_credentialed_url now), plus TTL-expiry
  and credential-change coverage

Extracted from #40276.
2026-08-03 13:47:03 +05:30
Teknium 177f1d7c53 fix: display sub-cent model prices with extended precision instead of $0.00
_format_price_per_mtok collapsed any per-Mtok price below one cent to
"$0.00" (and readers treated near-zero as free). Nous Portal's DeepSeek
V4 Flash 0731 promo prices cache hits at $0.0018/Mtok, which rendered as
free in the /model picker and hermes model listings.

Prices under $0.01/Mtok now widen precision to the first significant
digit plus one, with trailing zeros trimmed: 0.0000000018/tok →
$0.0018. Standard prices keep the aligned two-decimal format; exact
zero still renders as "free".
2026-08-02 11:03:41 -07:00
Teknium 9772e3b189 perf(model-picker): serve stale model caches instantly, refresh in background
The remaining /model picker stall after the Copilot backoff fix: whenever
the 1h provider-models disk cache TTL (or the remote model-catalog manifest
TTL) lapsed mid-session, the next picker open blocked on 8-9 serial
/v1/models round-trips (~2-3s measured) plus the catalog manifest fetch
before rendering anything.

Model catalogs change on release timescales, not hourly — so both caches
now use stale-while-revalidate:

- cached_provider_model_ids(): an expired entry whose credential
  fingerprint still matches is served immediately; a deduped daemon thread
  re-fetches the live catalog and rewrites the disk cache for the next
  open. Entries older than 7 days still block on a live fetch, credential
  rotation still busts the entry, and force_refresh still bypasses SWR.
- model_catalog.get_catalog(): an expired disk manifest is served
  immediately with an off-thread refresh; only a truly cold cache (no disk
  copy) blocks on the network.

Measured picker payload build with deliberately-expired caches:
2.9s -> 0.93s (first open in process) / 0.06s (subsequent opens).
Combined with the Copilot fix (#76386): 7.3s -> ~0.06s for the common case.
2026-08-01 14:52:18 -07:00
Victor Kyriazakos 173a73e191 fix(models): honor model.base_url in OpenAI discovery, cache identity, and listing authority
Three catalog-side defects from the same report, all downstream of the
exact-host assumption and the config/env asymmetry:

- Discovery read only $OPENAI_BASE_URL, so a config-set
  model.base_url (the supported way to select a data-residency host)
  was ignored and /model listed the catalog of api.openai.com, not the
  configured endpoint. New _openai_discovery_base_url() resolves
  env override -> matching model.base_url -> canonical default, the same
  precedence inference uses.

- _credential_fingerprint() hashed env vars only, so hermes config set
  model.base_url kept serving the previous endpoint's cached catalog
  until TTL expiry. The effective endpoint is now folded into the
  fingerprint for openai/openai-api.

- is_default_openai matched two literal URLs, so regional hosts (which
  serve the identical 120+ entry dump) bypassed the curated intersection
  and flooded the picker with whisper/tts/embedding/dall-e rows. Now uses
  the shared official-host predicate; custom OpenAI-compatible proxies
  keep the verbatim live list.

- validate_requested_model's curated-catalog soft-accept (#46850) no
  longer applies on official OpenAI hosts: their /v1/models listing is
  access-scoped and authoritative, so accepting an absent model
  manufactures a selection that 400s at first use. Custom proxies and
  other providers keep the #46850 fallback. The #37404
  empty-intersection -> curated picker fallback is deliberately left
  unchanged.
2026-07-31 21:31:56 -07:00
Teknium afc54ca806 feat(models): add deepseek/deepseek-v4-flash-0731 to Nous portal and OpenRouter catalogs
Dated snapshot of deepseek-v4-flash, live on both provider endpoints
(verified via OpenRouter /api/v1/models and Nous portal /v1/models).
Context (1M via deepseek-v4-flash prefix match), reasoning stale-timeout
floor (600s), and pricing (official_models_api live billing on both
routes) all resolve without new entries. model-catalog.json regenerated
via scripts/build_model_catalog.py.
2026-07-31 09:43:03 -07:00
Teknium ad12df6ba4 Revert "remove Vercel AI Gateway and Vercel Sandbox (#33067)"
This reverts commit febc4cfec0.
2026-07-29 19:48:37 -07:00
LunarNexus 8c12fa7cf0 fix(lmstudio): respect applied runtime context 2026-07-29 02:19:13 +05:30
Teknium ee1e789877 fix(vertex): merge duplicate vertex curated-list keys from #68767 salvage
Main gained its own vertex curated list (df051c17cc) two days after
PR #68767 was opened, so the cherry-pick produced a duplicate 'vertex'
dict key (later key silently wins in Python dict literals). Merge the
two into one list: union of both, existing entries preserved, contributor's
live-validated additions (gemini-3.6-flash, 3.5-flash-lite, 3.1-flash-lite)
folded in.
2026-07-28 11:54:46 -07:00
ceverson70 d7f6dadbea feat(vertex): add validated Gemini model catalog for /model picker
Add a "vertex" key to _PROVIDER_MODELS with 6 Gemini 3.x models that
were validated live against the Vertex AI OpenAI-compatible endpoint
(aiplatform.googleapis.com, global region, project antse-tooling) on
2026-07-21. All entries returned HTTP 200; 8 other candidates (e.g.
gemini-3.1-flash, gemini-3-pro-preview) returned 404 and were excluded.

Validated models (google/ prefix required by Vertex endpoint):
- google/gemini-3.5-flash       (frontier Flash, agentic)
- google/gemini-3.6-flash       (newer incremental over 3.5)
- google/gemini-3.5-flash-lite  (lighter/cheaper 3.5 variant)
- google/gemini-3.1-pro-preview (3.1 Pro preview)
- google/gemini-3-flash-preview (3.0 Flash preview)
- google/gemini-3.1-flash-lite  (most cost-efficient 3.x model)

Context lengths are covered by the existing "gemini" prefix entry
(1_048_576) in agent/model_metadata.py DEFAULT_CONTEXT_LENGTHS — no
additional entries needed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-07-28 11:54:46 -07:00
kosta e25d516c8c fix(gemini): bump native provider aux default to gemini-3.6-flash
The native Gemini provider profile's default_aux_model and the curated
model picker catalog were still pinned to gemini-3.5-flash, a stale
generation now superseded by gemini-3.6-flash (documented GA). Bump
both so the auxiliary-task default and the picker stay in sync with
the current model.

Contract test asserts the durable lockstep invariant only
(default_aux_model is a member of _PROVIDER_MODELS["gemini"]) rather
than pinning either side to a frozen model-name string, so it doesn't
need updating on the next model-generation bump.
2026-07-28 11:54:46 -07:00
Teknium fbc878ee2e feat(models): swap Gemini catalog entries to 3.1 Pro + 3.6 Flash; drop retired Qwen models
- openrouter + nous curated lists: replace google/gemini-3-pro-preview with
  google/gemini-3.1-pro-preview as the sole Pro entry, and
  google/gemini-3.5-flash with google/gemini-3.6-flash
- remove qwen/qwen3.7-plus and qwen/qwen3.6-35b-a3b from both lists
- regenerate website/static/api/model-catalog.json
- test fixture: swap qwen3.7-plus catalog-label fixture to qwen3.7-max
  (must be a model present in the nous curated list)

Both new Gemini ids verified live on OpenRouter /api/v1/models and the
Nous portal /v1/models (1,048,576 ctx — covered by the existing 'gemini'
prefix in DEFAULT_CONTEXT_LENGTHS).
2026-07-28 10:07:18 -07:00
Teknium 37e648128b fix(picker): fold live bare k3 wire id into curated kimi-k3 row
Follow-up to the salvaged #67409 search aliases: with kimi-k3 now in
the curated kimi-coding list (#68108), a Coding Plan key rendered TWO
rows for one model — curated 'kimi-k3' plus live-discovered bare 'k3'
(merge dedup was exact-string). Add model_alias_canonical() derived
from the same alias table and use it as the merge dedup key, so the
curated public slug wins and live-only models still surface.
2026-07-26 21:22:40 -07:00
HexLab98 aff48958d3 fix(deepseek): drop retired models from picker and provider defaults
Stop offering deepseek-chat/reasoner in the static catalog and point
fallback/aux defaults at the permanent v4 IDs. Keep retired aliases in
a detection-only map so /model deepseek-chat still resolves to deepseek.
2026-07-26 16:28:41 -07:00
teknium1 306c9f7661 feat(models): add anthropic/claude-opus-5 to OpenRouter and Nous Portal catalogs
Anthropic released Claude Opus 5 (+ -fast variant) — both are live on
OpenRouter and the Nous Portal /models endpoint (verified against both
live APIs). Opus 4.8 entries are kept.

- hermes_cli/models.py: opus-5 + opus-5-fast in OPENROUTER_MODELS;
  opus-5 in _PROVIDER_MODELS[nous] (Portal serves both, curated list
  carries the base model like the rest of the Nous Anthropic block).
  Ordering: below fable-5 flagship, above opus-4.8.
- agent/model_metadata.py: claude-opus-5 -> 1M context (matches live
  OpenRouter metadata).
- agent/reasoning_timeouts.py: claude-opus-5 -> 240s stale-timeout
  floor (same as the opus-4.x thinking family).
- website/static/api/model-catalog.json: regenerated via
  scripts/build_model_catalog.py.

Both providers bill via official_models_api (live pricing), so no
_OFFICIAL_DOCS_PRICING snapshot entry is needed for these routes.
2026-07-24 13:00:15 -07:00
Teknium df051c17cc fix(vertex): surface vertex in the /model picker — credential gate + curated model list
Community verification of #56688 (zmack12344321) found two follow-up gaps
that kept Vertex invisible in the /model menu even after registry
registration:

1. hermes_cli/model_switch.py: list_authenticated_providers() had a
   credential gate hard-coded to API keys (with an aws_sdk special case
   only) — add a vertex branch using has_vertex_credentials(), mirroring
   the aws_sdk shape.
2. hermes_cli/models.py: Vertex's OpenAI-compatible endpoint has no
   /models listing route, so without a curated _PROVIDER_MODELS entry the
   picker only ever showed the current model — add a Gemini curated list.

Follow-up to #56688.
2026-07-23 16:55:41 -07:00
gexin 02f1dd0857 fix(moa): route Copilot slots by target model 2026-07-23 08:26:57 -07:00
rob-maron 0ee05d72f2 Nous portal model pricing (#69579)
* nous portal model pricing

* update top message
2026-07-22 16:47:06 -04:00
Teknium 25eafd7d71 fix(models): complete kimi-k3 rollout across Kimi-direct catalog surfaces
Follow-up widening for salvaged PRs #67115, #67685, #67620:

- _PROVIDER_MODELS: add kimi-k3 atop kimi-coding / moonshot / opencode-go
  curated lists (kimi-coding-cn covered by cherry-picked #67620)
- setup.py _DEFAULT_PROVIDER_MODELS: kimi-k3 for kimi-coding(-cn) + opencode-go
- model_metadata: align DEFAULT_CONTEXT_LENGTHS kimi-k3 entry to 1,048,576
  (matches endpoint-scoped override, models.dev, and OpenRouter live metadata)
- anthropic_adapter: classify the bare Coding Plan slug 'k3' (and k3.x/k3-*)
  as Kimi family so adaptive thinking applies on proxied endpoints
- moonshot_schema: is_moonshot_model matches bare 'k3' so tool-schema
  sanitization runs on the chat-completions path
- contributor mappings for githubespresso407, datachainsystems, Punyko8

Tests: 582 passed across 11 targeted files; hermetic E2E verifies picker
order (kimi-k3 first), no dupes, and 1M context resolution.
2026-07-20 08:47:55 -07:00
Punyko8 495d4acec5 feat: add kimi-k3 + kimi-k2.7-code to kimi-coding-cn whitelist
Moonshot China (api.moonshot.cn) has rolled out kimi-k3 to all
CN-endpoint keys, plus the new kimi-k2.7-code / -highspeed variants.
The kimi-coding-cn curated picker whitelist was still on the k2.6/k2.5
era list, so users holding CN keys could not select any of the new
models from 'hermes model' or the gateway /model picker even though
the underlying provider + endpoint already serve them.

Verified against a live CN key:

    GET https://api.moonshot.cn/v1/models
    -> [kimi-k3, kimi-k2.7-code, kimi-k2.7-code-highspeed, kimi-k2.6,
        kimi-k2.5, moonshot-v1-* ...]

No provider-code changes; pure whitelist addition mirroring the
existing kimi-coding (global) list which already tracks k2.7-code.
2026-07-20 08:47:55 -07:00
Teknium 877fd7edf5 fix: dedupe claude-sonnet-5 entries after overlapping salvages
PRs #55848 and #55853 both added claude-sonnet-5 to the anthropic and
gmi curated lists; keep one entry each (newest-sonnet-first ordering
under claude-fable-5, matching the existing list convention).
2026-07-20 02:25:44 -07:00
liuhao1024 07f39cf9a6 fix(models): add Claude Sonnet 5 to curated model lists
Add claude-sonnet-5 to the static curated lists for Anthropic, OpenRouter,
Nous Portal, Copilot, GMI, OpenCode Zen, and AWS Bedrock so the model
appears in hermes model / /model picker discovery.

Fixes #55846
2026-07-20 02:25:44 -07:00
Ariel Bravy e2561466c7 feat(models): add Claude Sonnet 5 support 2026-07-20 02:25:44 -07:00
Teknium ad86b8f469 fix: add qwen3.7-plus to alibaba list + qwen3-max context fallback
Follow-up to the salvaged #66083/#42792 commits:
- alibaba (Qwen Cloud coding-intl) gets qwen3.7-plus too — same platform
  allowlist as alibaba-coding-plan (issue #44662 comment by @coder-movers)
- qwen3-max substring context entry (262144) so the newly-listed
  qwen3-max-2026-01-23 snapshot doesn't fall to the generic 131072 qwen
  fallback
2026-07-20 00:41:01 -07:00
joezhang 772c232631 fix(providers): update alibaba coding plan supported model list
The model list for alibaba coding plan is currently out of sync with
the actually supported models. See the official documentation[1].

Per the docs, alibaba coding plan does not support qwen3.7-max;
it supports qwen3.7-plus instead. Additionally, qwen3-max-2026-01-23
was missing from the model list.

Changes to the alibaba-coding-plan model list:

- Replace qwen3.7-max with qwen3.7-plus
- Add qwen3-max-2026-01-23

[1] https://www.alibabacloud.com/help/en/model-studio/coding-plan
2026-07-20 00:41:01 -07:00
Teknium 693f935936 feat(picker): fold Qwen providers into one group row in provider pickers (#67758)
Consolidates the three Qwen provider slugs (alibaba / Qwen Cloud,
alibaba-coding-plan / Alibaba Cloud Coding Plan, qwen-oauth / Qwen CLI
OAuth) under a single 'Qwen' group row in the interactive provider
pickers, matching the existing OpenAI / Kimi / MiniMax / xAI groups.

Display-only via PROVIDER_GROUPS — slug identity, --provider, and
/model <provider:model> paths are unchanged. Because group_providers()
is the shared fold, the CLI 'hermes model' picker, the setup wizard,
and the Telegram /model keyboard all pick up the grouping with no
per-surface changes.
2026-07-19 23:34:03 -07:00