Commit Graph

63 Commits

Author SHA1 Message Date
teknium1
a750c09129 fix(providers): opencode-go vision models stay on the Go endpoint and attach images natively
Two independent gaps hit a user on provider opencode-go with
deepseek-v4-flash-vision-exp (#96066):

- Vision: the model is absent from models.dev, so get_model_capabilities()
  returned None and image_input_mode: auto detoured images through the lossy
  text describe path. OpenCode Zen/Go resell vendor previews before the catalog
  indexes them; the id's `-vision` token is the vendor's own capability marker,
  so _apply_overrides() now fills the catalog gap with a vision-only base for
  any OpenCode-family provider (built-in or family-slug custom). Catalog entries,
  explicit and _default overrides stay authoritative; reasoning stays unknown.

- Route: _resolve_agent_model_runtime() applied a resumed row's persisted
  api_mode/base_url verbatim. A row written while the session ran an
  anthropic_messages model (MiniMax on Go, or an older build's /zen/v1 relay)
  pinned that wire onto a chat_completions model, surfacing as 401s from the
  Anthropic transport or the Zen relay. Providers that pick the wire per model
  (OpenCode families, Copilot, Nous — the existing _PROVIDER_API_MODE_OVERRIDES
  table, now reachable via model_derived_api_mode()) re-derive api_mode from the
  target model on resume and heal the relay URL; fixed-wire providers keep
  honoring their row.

Salvages #96116 (@Finn763): same two atoms, redone slim (data-driven table
instead of an opencode-only hostname guard in the tui_gateway facade).
2026-09-19 00:05:47 -07:00
Ritesh Patel
998f614c7f feat(providers): remove the keyless opencode-free tier
OpenCode's free tier now returns HTTP 403 for anonymous traffic outside
the OpenCode client, so the built-in keyless provider is dead weight:

- drop the opencode-free provider row, aliases (free/opencode_free), model
  catalog, keyless runtime ladder rung, header wiring, and cached slugs
- delete the model-providers/opencode-free plugin
- update tests and the compat manifest for the removed symbols
- keep a migration hint in auth.py so users who had it configured see a
  clear error naming the removal

Existing opencode-free configs can move to opencode-zen (pay-as-you-go)
or opencode-go (flat subscription).
2026-09-18 15:40:37 +05:30
teknium1
4e41ab0979 fix(tools): warn once when a check_fn drops a core tool; reject a mistyped catalog_provider
Issue #112649 atom 4: a core (`_HERMES_CORE_TOOLS`) tool whose check_fn returns
False leaves neither the schema nor the tool_search catalog (it is not
deferrable), so the model's "no such tool" is accurate and nothing in the log
points at the probe. ae5666f7fc made the False verdict INFO for optional,
unconfigured toolsets — that stays; the core-tool drop is now a WARNING that
names the dropped tool(s), once per probe per process (the 30 s TTL re-probe
would otherwise re-warn every turn), reset by invalidate_check_fn_cache().

Review minor: `catalog_provider: deepsek` was accepted silently and the typo
leaked into ModelInfo.provider_id. An alias that is neither a Hermes provider
id nor a models.dev id (loaded catalog, no network) now warns once, mirroring
the unknown-key warning, and the row keeps its own slug.
2026-09-17 08:53:48 -07:00
teknium1
d1c6786d1e fix(models): custom providers can inherit a vendor catalog; unknown models get no synthesized output cap
A metadata-only model_overrides entry on a custom provider (only context_window set) took the
unknown-model branch of get_model_capabilities and reported max_output_tokens=8192 from the
_UNKNOWN_MODEL_BASE template; /model info showed "Max output: 8,192 tokens" for a model nobody
knows. The output limit is now left unknown (ModelCapabilities.max_output_tokens=None,
ModelInfo.max_output=0), matching the fail-open treatment #113146 gave supports_vision and
supports_reasoning. Consumers already handle a missing value: the dashboard only renders a
positive limit and the /model card only prints a truthy max_output.

There was also no way for a custom provider that resells a catalogued vendor's models (a
gateway/proxy) to reach _BUILTIN_MODEL_METADATA or the models.dev catalog, which is what forced
the metadata-only override in the first place. providers.<name>.catalog_provider (also honoured
on legacy custom_providers[] rows) names that vendor; models_dev resolves the catalog id through
it for capabilities, context, model info and provider info. It affects metadata lookups only,
never routing or credentials, and an explicit model_overrides entry still wins.

Closes the remaining atoms of #112649 (docs: providers page).
2026-09-17 08:53:48 -07:00
KoNit-K
24e4d442d9 fix(model): preserve unknown custom capabilities 2026-09-16 17:09:50 -07:00
Konstantin Khlopkov
7f7d229f83 fix(utils): atomic writers refuse to resurrect a deleted named profile home
Background writers that still carry a tombstoned profile as their Hermes
home (reasoning-caps warm thread, models cache, models.dev ETag, gateway
lifecycle ledger, MCP OAuth tokens, memory store) re-created
profiles/<name>/ with a bare mkdir right before an atomic write. Route the
parent-dir creation through mkdir_under_hermes_home so a deleted named
profile raises FileNotFoundError and stays gone, matching the tombstone
contract already enforced for logging and state.
2026-09-16 00:32:15 -07:00
jakobdylanc
f28c52845a fix(models): resolve routed OpenRouter ids in models.dev catalog lookups
Extends the routing-variant fix to the sibling catalog paths, so the whole
bug class is covered rather than just context length (#97820).

_find_model_entry(), lookup_models_dev_context(), get_model_info(), and
get_model_capabilities() all keyed on the full suffixed id, so a routed id
such as z-ai/glm-5.3-flash:floor missed models.dev and fell through to the
generic "glm" family default (202,752 instead of 1,310,720).

The retry with the base id runs LAST — after exact and case-insensitive
matching — so a real catalog SKU always wins over its base.

:free and :batch are deliberately NOT stripped. They are real catalog SKUs,
not routing modifiers: OpenRouter's /models currently lists 18 :free and 65
:batch entries, and 13 of those carry a context window different from their
base (z-ai/glm-5.2:free is 256K vs the base's 1.05M). Stripping them would
report a window LARGER than the model has, so the request fails at the API
instead of merely compacting early — a worse failure than the under-report
this fixes. An absent SKU must also miss rather than inherit the base, so
model_overrides _default fill-gap semantics keep working.

The suffix set is shared from hermes_constants, so validation, context
resolution, and catalog lookup all agree on one definition.
2026-09-11 16:50:20 -07:00
Teknium
759024bdff fix(deepseek): honor Flash 1M window leftovers and native vision
Users on native DeepSeek were told to pin model.context_length and
model.supports_vision in config.yaml. That is the wrong layer: the 1M
window is already in DEFAULT_CONTEXT_LENGTHS, and a global
supports_vision pin would also mark text-only deepseek-v4-pro as
multimodal.

Two catalog gaps still produced the reported symptoms:

- A leftover context_length_cache.yaml entry of 128K (the old
  ``deepseek`` catch-all) outlived the 1M catalog keys because
  deepseek-flash was missing from _PRE_CATALOG_STALE_KEYS.
- When models.dev is empty/cold, Flash has no capability record, so
  image routing falls through to lossy text. Vendor docs (2026-09-10)
  mark deepseek-flash as vision-capable and deepseek-v4-pro as not.

Discard those 128K leftovers, fill Flash (and retired Flash aliases)
via _BUILTIN_MODEL_METADATA, and leave Pro catalog-only.
2026-09-11 12:09:41 -07:00
Eva
c990017482 test(openai): close Astra baseline review gaps
(cherry picked from commit 8c27b7c9316ba675e4d9ebcc7a659754f37f18ef)
2026-09-07 21:43:54 +05:30
Eva
2c315ff59b feat(openai): add GPT-6 Astra baseline support
(cherry picked from commit a8c53d20c6b16cc35745e364e16bb7259166a1d3)
2026-09-07 21:43:54 +05:30
Teknium
27f32bd50b test: exercise output-cap removal across native and child surfaces 2026-09-07 06:15:43 -07:00
Teknium
fd3565deec fix: remove dedicated user-facing output cap controls 2026-09-07 06:15:43 -07:00
Teknium
e83816a4d1 review-fix(comments): restore lost #NNNN rationale comments across non-test source (mechanical sweep, condensed, code unchanged)
For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.
2026-09-03 09:44:26 -07:00
Teknium
0071ba9965 Merge origin/main (561b053f79) into simp/forwardport: forward-port 220 main commits into the simplified tree 2026-09-03 03:31:03 -07:00
Teknium
4359af7705 fix(models_dev): alias opencode-free to the Zen "opencode" catalog; pin Muse Spark 1M invariant
opencode-free had no PROVIDER_TO_MODELS_DEV entry, so every models.dev
lookup on the free tier missed and Muse Spark fell to the 256K default.
The free tier is served by the Zen relay (hermes_cli/models.py:
"opencode-free is Zen-hosted"), and models.dev's "opencode" provider is
the catalog that lists muse-spark-1.2 / -1.2-contributor-free /
-1.3-contributor-free at 1,048,576 — so the alias is "opencode", not
"opencode-go" (Go's catalog carries only the paid -contributor SKUs).

Missing alias identified by @Steve-prog001 in #101905.

Tests: one parametrized offline invariant (models.dev + live /models
mocked away) asserting 1,048,576 on opencode-free / opencode-go /
meta-ai / commandcode — fails on main, passes here — plus the alias pin.
2026-09-03 00:58:16 -07:00
Teknium
4cfe4f4857 refactor(agent): compact models_dev registry/override helpers; inline _fetch_registry 2026-09-02 21:55:07 -07:00
Teknium
0e5c0323fd refactor(agent): model_metadata/models_dev — fold codex variant helpers, compact tables and comments, squeeze blanks 2026-09-02 19:18:35 -07:00
Teknium
2382f18dd4 refactor(agent): models_dev — _quietly/_refresh_locked helpers, pack provider table, trim docstrings 2026-09-02 18:29:12 -07:00
Teknium
9fc1f90f97 refactor(agent): models_dev — compact docstrings/log calls, single-line signatures, walrus guards 2026-09-02 18:26:23 -07:00
Teknium
a7d7ac3f39 refactor(agent): models_dev — shared _dict_or_empty/_cfg_get/_hermes_path/_serve_stale/_apply_overrides helpers, fold thin wrappers 2026-09-02 18:22:51 -07:00
Teknium
77743eac8a refactor(agent/models): compact pricing snapshot, billing/subscription views, reasoning helpers
- usage_pricing: _snap() builder for official-docs pricing entries (table values identical, verified by dump), shared source/version dicts, drop dead DEFAULT_PRICING
- models_dev: _registry_models/_iter_model_entries/_extract_limit helpers replace repeated registry walking; drop dead ModelInfo.format_cost
- billing_view/subscription_view: OrgRoleCapability mixin replaces duplicated is_admin/can_change_plan; shared fetch_portal_state/parse_org_fields
- reasoning_effort/timeouts/summaries, thinking_timeout_guidance, portal_tags: dispatch tables and compacted comment essays; drop dead CODEX_RESPONSES_EFFORTS alias and _match_any
2026-09-02 13:52:51 -07:00
kshitij
5b4c91f1db refactor(models): simplify-pass follow-ups on the refresh path
- Cold force_refresh (fresh CLI process, e.g. hermes config refresh)
  now hydrates the memory cache from disk before fetching, so the
  conditional GET actually fires on the flow the feature was built for
  instead of silently re-downloading the full ~2 MB registry
  (empirically probed: If-None-Match sent, 304 serves disk data).
- Conditional-GET decision is passed in explicitly
  (_fetch_models_dev_from_network(conditional=...)) by callers holding
  the fetch lock, removing the hidden read of module globals inside
  the fetch; the background worker now fetches INSIDE the lock,
  symmetric with foreground (true singleflight — no concurrent
  double-download, no fetching against mid-commit etag state).
- Corrupt disk cache is QUARANTINED (renamed to .json.corrupt) rather
  than left in place: rejection becomes a one-time event instead of a
  re-read + re-parse + warning + unlink on every hot-path call while
  offline (probed: 1 warning across 5 calls, was 5).
- Dropped the dead _DEFAULT_MODELS_DEV_URL constant; module and
  function docstrings updated to match the servable-cache conditional
  semantics.
2026-08-14 03:31:22 +05:30
kshitij
b1ce502535 fix(models): close review findings on the ETag refresh path
- Conditional GET now requires a servable in-memory registry: an
  If-None-Match sent while holding no cache invited a 304 against
  nothing, permanently serving {} with a blocking foreground fetch on
  every call (the exact #35838 class this PR fixes). Empirically
  repro'd and verified fixed (corrupt cache + stale sidecar: was 3
  calls -> {} forever; now 1 unconditional fetch -> real data).
- ETag persists atomically WITH the cache body via
  _commit_registry -> _save_disk_cache(data, etag), wiring up the
  previously-dead etag param; the sidecar can no longer get ahead of
  the registry it vouches for. _save_etag now uses
  utils.atomic_write_text (unique tempnames + fsync) instead of a
  hand-rolled fixed-name .tmp replace.
- Corrupt/unreadable disk cache clears the ETag sidecar so the
  refetch is unconditional; _confirm_cache_not_modified keeps a
  defense-in-depth guard (clear sidecar + arm backoff) should a 304
  ever land on an empty registry.
- allow_network=True paths use the zero-arg fetch_models_dev() call
  shape at all sites (was 1 of 5) — ~46 test sites monkeypatch it
  with zero-arg lambdas; the unconditional kwarg broke
  test_xiaomi_provider (verified fail->pass).
- _get_models_dev_url falls back to the MODELS_DEV_URL module global
  (not the constant) so existing patch sites keep working.
- Tests: replaced two mock-riddled corrupt-cache tests with real
  tmp_path file tests; added regression tests for the 304/empty-cache
  loop, sidecar clearing, and conditional-GET gating.
2026-08-14 03:31:22 +05:30
kshitij
acd8737c10 fix(models): ETag conditional GET, no-network hot-path invariant, mirror URL override for models.dev catalog
Harden the models.dev catalog refresh path (#35838) with three missing
pieces:

1. ETag conditional GET — every network request sends If-None-Match
   with the last-known ETag (persisted alongside the cache file). A 304
   Not Modified re-confirms the existing cache without re-downloading
   the full ~2 MB registry. This makes the 4-hour TTL effectively free
   to maintain.

2. No-network-on-hot-paths invariant — allow_network=False is now the
   default for every query function called on the conversation hot path:
   get_model_capabilities, get_model_info, lookup_models_dev_context,
   _get_provider_models. These are called during vision routing, image
   routing, cost-guard checks, and context-length resolution on every
   turn — they must never block on the network. Interactive flows
   (model picker, model switch) explicitly pass allow_network=True.

3. Mirror URL override — models_dev.url in config.yaml lets deployments
   point at a self-hosted mirror without code changes. Follows the same
   pattern as model_catalog.url.

Additional hardening:
- Cache TTL bumped from 1h to 4h (ETag makes refresh cheap)
- Corrupt/empty disk cache is rejected with a warning instead of being
  served as {} and silently breaking provider/model resolution
- _validate_registry() guards against non-dict and empty-dict payloads

Fixes #35838
2026-08-14 03:31:22 +05:30
kshitij
67ab2f2968 refactor(models): simplify-pass follow-ups on model_overrides
- Deleted the id(cfg)-keyed _OVERRIDE_CACHE layer: id() is unique only
  among live objects, so a config reload could serve stale overrides
  forever when CPython reuses the freed dict's address. The upstream
  load_config_readonly is already (mtime,size)-cached (~1 stat/hit), so
  the local layer was redundant state with a correctness risk.
- _override_to_catalog_shape returns (patch, vision) instead of
  smuggling an in-band _vision_override sentinel key through the merged
  dict; removed the two dead call-site pops.
- _find_model_entry gains the :cloud/-cloud suffix fallback that
  lookup_models_dev_context already had, so 'catalog hit' means the
  same thing to every consumer — a suffix-keyed model (kimi-k2.6:cloud)
  now counts as KNOWN and keeps its catalog capabilities instead of
  being displaced by a fill-gap _default (mutation-checked contract
  test added).
- get_model_info's unknown-model override path seeds the same safe
  defaults as get_model_capabilities (200K ctx, tools on, 8192 out),
  so a partial override no longer yields ctx=0/tools-off on that path
  (contract test added); the DEFAULT_CONFIG defaults claim is now true
  for both paths.
- Activated the previously-dead _MODELS_DEV_TO_PROVIDER reverse map
  (lazily built, many-to-one aware) and used it in
  _provider_override_section instead of a per-call linear scan.
2026-08-14 02:03:54 +05:30
kshitij
de47d19f1f fix(models): one canonical override schema, fill-gap _default semantics
Review follow-ups on the model_overrides feature:

- ONE canonical override schema everywhere. get_model_info previously
  merged the override dict raw into the models.dev catalog shape
  ({**raw, **override}), so the documented context_window/supports_*
  keys silently did nothing on that path (cost guard, inventory) while
  working in capabilities/context paths — same config key, two
  incompatible schemas. Overrides are now translated into the catalog
  shape at the get_model_info boundary (_override_to_catalog_shape),
  and sub-dicts (limit, modalities) are MERGED, not clobbered — an
  override setting only context_window no longer wipes the catalog's
  limit.output.
- _default is now a FILL-GAP default, not an override: it applies only
  to models the catalog does not know (the #8731/#84482 self-unblock
  path) and never displaces catalog data. A
  _default: {context_window: 128000} can no longer clamp every model
  of a provider. Explicit per-provider+model entries keep their
  win-over-catalog semantics.
- Early-chain _override_context_window (model_metadata step 0b) is
  explicit-only, so a _default can never preempt custom_providers
  per-model settings or live probes; fill-gap defaults apply at the
  lookup_models_dev_context catalog-miss boundary (step 5f) instead.
  This fixes the precedence inversion where a provider/global _default
  silently overrode an explicit per-endpoint per-model context_length.
- Provider keys accept BOTH id spaces (Hermes id and models.dev id:
  copilot/github-copilot both work) and model ids match
  case-insensitively, mirroring catalog lookup.
- Malformed override values (context_window: '512k') log a one-shot
  warning instead of being silently swallowed.
- DEFAULT_CONFIG comment: removed the false family/dated-snapshot
  inheritance claim, documented the recognized field list, fill-gap
  semantics, and the id-space rule.
- Tests: rewritten for the new contracts (fill-gap invariants,
  dual-id-space keys, sub-dict merge preservation, one-shot warning);
  added a real-config-yaml e2e plumbing test (mutation-checked: fails
  when the config key wiring is broken).
2026-08-14 02:03:54 +05:30
kshitij
dafdba324a feat(models): per-model metadata overrides via model_overrides config
Add a unified model_overrides config section that lets users manually
declare context_window, max_output_tokens, capabilities, cost, and
family for any provider+model — winning over models.dev, OpenRouter, and
hardcoded defaults.

Resolution order (first hit wins):
  1. model_overrides.<provider>.<model_id>  (per-provider+model)
  2. model_overrides.<provider>._default    (per-provider default)
  3. model_overrides._default               (global default)
  4. Normal catalog resolution

Key subtlety: an unknown model id (not in the
catalog) derives base metadata from sensible defaults before patching,
so overriding a model the catalog doesn't know yet is the supported
self-unblock path. This is exactly the #84482 scenario (Upstage
solar-pro4/syn-pro wrong context) and the #8731 scenario (custom/local
models with manual capability declaration).

Wired into:
  - get_model_capabilities() — patches capability fields; unknown models
    get safe defaults (tools on, vision/reasoning off) before patching
  - lookup_models_dev_context() — context_window override, checked before
    catalog lookup so it works even for providers not in PROVIDER_TO_MODELS_DEV
  - get_model_info() — merges override dict onto catalog entry (shallow
    merge); for unknown models, the override is the sole source of metadata
  - get_model_context_length() — step 0b in the resolution pipeline,
    before custom_providers (0c) and before any network probe

Config example:
  model_overrides:
    upstage:
      solar-pro4:
        context_window: 524288
      syn-pro:
        context_window: 65536
    custom:my-local-vllm:
      my-llava-model:
        context_window: 8192
        supports_vision: true
        supports_reasoning: false
        supports_tools: true
    _default:
      context_window: 128000

Fixes #8731
Fixes #84482
Refs #47247
2026-08-14 02:03:54 +05:30
rob-maron
7113a69248 fix(models_dev): map meta-ai provider to models.dev 'meta' id (salvage #81418) (#84679)
* fix(models_dev): map meta-ai provider to models.dev 'meta' id

Muse Spark models (muse-spark-1.1/1.2/-contributor) are served via the Meta
Model API and reverse-map from api.meta.ai to the Hermes provider id 'meta-ai'.
models.dev keys the same models under the provider id 'meta'.

lookup_models_dev_context() / _get_provider_models() resolve the models.dev id
strictly via PROVIDER_TO_MODELS_DEV.get(provider) (no raw-id fallback, unlike
get_model_info()), so an unmapped 'meta-ai' missed entirely and context fell
back to the generic 256K default instead of the true ~1M window. Add the
meta-ai->meta mapping (plus a defensive meta->meta) so context and pricing
resolve from models.dev: 1.1=1,000,000; 1.2 & -contributor=1,048,576.

* add contributor email

---------

Co-authored-by: Beto de Paola <betodepaola@meta.com>
2026-08-12 14:42:00 -04:00
Teknium
ad12df6ba4 Revert "remove Vercel AI Gateway and Vercel Sandbox (#33067)"
This reverts commit febc4cfec0.
2026-07-29 19:48:37 -07:00
teknium1
d7a4065568 perf(local-endpoints): disk L2 for server-type + ollama ctx probes, faster timeouts
Local-model users paid a fresh probe waterfall on EVERY CLI cold start
inside AIAgent.__init__: detect_local_server_type (up to 4 HTTP GETs,
2s timeout each on a hung server) + /api/show (3s timeout). The
existing caches were in-process only, so back-to-back invocations
(chat -q, cron ticks, subagents) re-paid the network every time.

- New 300s-TTL disk L2 at HERMES_HOME/cache/local_endpoint_probes.json
  for detect_local_server_type verdicts and query_ollama_num_ctx
  results. Only SUCCESSFUL probes persist (a down server never pins a
  negative verdict); stale entries pruned on write; corrupted cache
  degrades to a miss; atomic writes. 300s is strictly fresher than the
  1h in-process TTL that already accepts server-swap staleness.
- models.dev fetch timeout 15 -> (5, 10) connect/read tuple: a
  blackholed connect stalled the first-turn critical path 15s; now
  fails in 5s (matches the OpenRouter fetch convention, #46620).
- _auto_detect_local_model timeout 5 -> (2, 3): runs inside
  _get_model_config() at startup against a LOCAL endpoint; a hung local
  server cost 5s before the banner.

E2E (real HTTP server, two fresh subprocesses, isolated HERMES_HOME):
proc1 = 2 HTTP hits, proc2 = 0 HTTP hits, identical results
(ollama/131072), probe wall 74.5 -> 35.5 ms. 222 targeted tests green
incl. 9 new disk-L2 contract tests.
2026-07-29 10:52:13 -07:00
kshitijk4poor
222ea2b6c9 refactor: fold simplify-code review findings
- extract _commit_registry/_note_refresh_failure shared by the background
  worker and foreground stage-4 (identical 4-step success + failure paths
  were duplicated); worker now commits under _models_dev_fetch_lock so a
  failing background refresh can never re-arm the backoff immediately
  after a successful force_refresh committed (unsynchronized-write race)
- add should_clear_context_pin_async to hermes_cli/route_identity.py
  (matching the get_model_context_length_async precedent) and use it at
  the 4 async gateway sites instead of inline asyncio.to_thread wraps;
  the sync _format_session_info site keeps the sync call (already
  off-loop via its callers' to_thread)
- test the background-refresh success path (the PR's primary new
  behavior): disk saved, mem cache swapped, backoff cleared, in_flight
  reset — mutation-checked
- replace the race-prone spin-wait on _models_dev_refresh_in_flight with
  a named-thread join in the backoff test
2026-07-29 17:13:48 +05:30
kshitijk4poor
ccf7129ed0 fix: restore zero-arg fetch_models_dev call on default paths
The branched call shape in get_provider_info/get_provider is deliberate:
~69 test sites across tests/hermes_cli and tests/gateway monkeypatch
fetch_models_dev (and get_provider_info) with zero/single-arg lambdas.
Passing allow_network= unconditionally broke 5 tests in CI slices 2/3/7.
Documented the constraint inline.
2026-07-29 17:13:48 +05:30
kshitijk4poor
11ca7eedf0 fix: follow-up hardening for salvaged #73621 + #35853
- _mark_stale_cache_grace only moves cache_time forward so a completed
  background refresh is never rewound to a 5-minute grace window
- clear _models_dev_refresh_in_flight if Thread.start() raises so a
  one-off thread-exhaustion failure doesn't disable refresh forever
- move empty-registry validation into _fetch_models_dev_from_network
  (was duplicated in the background worker and the foreground fetch)
- pass allow_network through as a plain kwarg in get_provider_info and
  hermes_cli.providers.get_provider instead of the branched call shape
- refresh the stale module docstring (no bundled snapshot exists; the
  resolution order now describes stale-serve + background refresh)
2026-07-29 17:13:48 +05:30
zapabob
a479a1599f fix(models): use stale cache before models.dev refresh 2026-07-29 17:13:48 +05:30
StellarisW
8c50aaceb6 fix(gateway): keep models.dev refreshes off event loop 2026-07-29 17:13:48 +05:30
Teknium
febc4cfec0 remove Vercel AI Gateway and Vercel Sandbox (#33067)
* remove Vercel AI Gateway provider and Vercel Sandbox terminal backend

Both Vercel-hosted integrations are removed end-to-end. Users on the AI
Gateway should switch to OpenRouter or one of the other aggregators
(Nous Portal, Kilo Code). Users on the Vercel Sandbox backend should
switch to Docker, Modal, Daytona, or SSH.

What's removed:
- `plugins/model-providers/ai-gateway/` provider plugin
- `hermes_cli/vercel_auth.py` Vercel-Sandbox auth helper
- `tools/environments/vercel_sandbox.py` terminal backend
- `ai-gateway` provider wiring across auth, doctor, setup, models,
  config, status, providers, main, web_server, model_normalize, dump
- `vercel_sandbox` backend wiring across terminal_tool, file_tools,
  code_execution_tool, file_operations, approval, skills_tool,
  environments/local, credential_files, lazy_deps, prompt_builder,
  cli, gateway/run
- `AI_GATEWAY_BASE_URL` constant, `_AI_GATEWAY_HEADERS` auxiliary-client
  header set, run_agent base-URL header/reasoning special-cases
- `[vercel]` pyproject extra and `vercel`/`vercel-workers` from uv.lock
- env vars: `AI_GATEWAY_API_KEY`, `AI_GATEWAY_BASE_URL`, `VERCEL_TOKEN`,
  `VERCEL_PROJECT_ID`, `VERCEL_TEAM_ID`, `VERCEL_OIDC_TOKEN`,
  `TERMINAL_VERCEL_RUNTIME`
- Tests: deletes test_ai_gateway_models.py and
  test_vercel_sandbox_environment.py; scrubs references across 23
  surviving test files (no entire tests deleted unless they were
  dedicated to AI Gateway / Sandbox)
- Docs: provider tables, env-var reference, setup guides, security
  notes, tool config, terminal-backend tables — English plus zh-Hans
  i18n parity
- `hermes-agent` skill: provider table entry and remote-backend list

What stays (intentional):
- `popular-web-designs/templates/vercel.md` — CSS design reference,
  unrelated to Vercel-the-AI-product
- `x-vercel-id` in `stream_diag.py` headers — generic Vercel CDN
  response header, useful diag signal on any Vercel-hosted endpoint
- `vercel-labs/agent-browser` URL in browser config — lightpanda
  browser project, different OSS effort
- `userStories.json` historical contributor entry mentioning Vercel
  Sandbox — archive, not active docs

Validation:
- 1153 tests in the 22 targeted files pass (`scripts/run_tests.sh`)
- Full repo `py_compile` clean
- Live import of every touched module + invariant check (no
  `ai-gateway` in `PROVIDER_REGISTRY`, no `_AI_GATEWAY_HEADERS`, no
  `vercel_sandbox` in `_REMOTE_TERMINAL_BACKENDS`)

* test: convert profile-count check from change-detector to invariant

The hardcoded "== 34" assertion broke when ai-gateway was removed.
Per AGENTS.md change-detector-test guidance, assert the relationship
(registry count >= number of plugin dirs) instead of a literal count.
Counts shift when providers are added/removed; that's expected.
2026-05-27 00:43:32 -07:00
Julien Talbot
09afafb87e fix(xai): resolve Grok Build context for OAuth 2026-05-22 13:05:36 -07:00
Alex-wuhu
c76e879574 feat: add NovitaAI as LLM provider
Add NovitaAI as a first-class provider with dedicated model selection
flow, live pricing, and authoritative context length resolution.

- Register provider in PROVIDER_REGISTRY, HERMES_OVERLAYS, and all
  alias/label maps (ID: novita, aliases: novita-ai, novitaai)
- Add dedicated _model_flow_novita() with 3-tier model list fallback:
  Novita API → models.dev → static curated list
- Fetch live pricing from /v1/models with correct unit conversion
  (input_token_price_per_m is 0.0001 USD per Mtok)
- Add Novita-specific context length resolution (step 4b) in
  get_model_context_length(), prioritized over models.dev/OpenRouter
- Register api.novita.ai in _URL_TO_PROVIDER to prevent early return
  from the custom-endpoint code path
- Add models.dev mapping (novita → novita-ai)
- Add default auxiliary model (deepseek/deepseek-v3-0324)
- Add NOVITA_API_KEY to test isolation (conftest.py)
- Update docs: providers page, env vars reference, CLI reference,
  .env.example, README, and landing page
2026-05-13 23:51:15 -07:00
nicoechaniz
e2b713cced fix(model-metadata): skip OpenRouter for known providers, add kimi/moonshot to PROVIDER_TO_MODELS_DEV
Based on PR #23950 by @nicoechaniz.

- Add "kimi" and "moonshot" to PROVIDER_TO_MODELS_DEV → kimi-for-coding
- Gate OpenRouter metadata step behind "if not effective_provider":
  known providers should not be overridden by community-maintained OR data
- Keep the targeted Kimi-family 32k guard as a secondary safety net
  inside the OR gate (for unknown providers with Kimi models)

Co-authored-by: nicoechaniz <nicoechaniz@altermundi.net>
2026-05-11 13:16:07 -07:00
kshitijk4poor
91eef6255e fix: correct context-length resolution for kimi-k2.6 on Ollama Cloud and Kimi Coding
Kimi-k2.6 (which supports 262K context) was incorrectly resolved as 32K,
tripping the 64K minimum-context guard and preventing use of the model on
Ollama Cloud and Kimi Coding / Moonshot providers.

Three fixes in the context-length resolution chain:

1. Ollama Cloud native /api/show query: new _query_ollama_api_show()
   queries the Ollama native API for authoritative GGUF model_info
   context_length.  For hosted Ollama, prefers model_info over num_ctx
   since users can't set their own num_ctx on Cloud.  Added at step 5e
   in get_model_context_length(), before the models.dev fallback.

2. models.dev :cloud/-cloud suffix fallback: lookup_models_dev_context()
   now also tries appending :cloud and -cloud suffixes when the bare
   model name doesn't match.  models.dev stores 'kimi-k2.6:cloud' but
   users and the live API use bare 'kimi-k2.6'.

3. Kimi-family 32K guard: after the OpenRouter metadata step, reject
   exactly 32768 for Kimi-named models (kimi-*, moonshot*) and fall
   through to hardcoded defaults ('kimi': 262144).  OpenRouter reports
   32768 for moonshotai/kimi-k2.6 but the model actually supports 262K.
   Narrow filter — only 32768, only Kimi-family — becomes dead code
   when OpenRouter updates its metadata.

---
2026-05-11 13:16:07 -07:00
Teknium
775c0e22cf perf(models_dev): cache-first lookup, skip network when disk cache is fresh (#22808)
`fetch_models_dev()` is on the hot path of every `AIAgent.__init__`
(via `context_compressor → get_model_context_length`). The previous
policy was "always try network first, only fall back to disk if
network fails," so every fresh `hermes chat` / `hermes gateway` /
batch / cron process paid 250-500 ms re-fetching a 2 MB JSON registry
that was already on disk from earlier runs.

Add a stage 2 between in-mem and network: if
`models_dev_cache.json` exists and its mtime is younger than the
existing `_MODELS_DEV_CACHE_TTL` (1 hour, same TTL the in-mem cache
already uses), load from disk and skip the network call.

The in-mem TTL is anchored to the disk file's age, so a 50-min-old
cache stays in-memory for only 10 more minutes — no surprise
extension of staleness window.

Invariants preserved:
- `force_refresh=True` still always hits the network and only falls
  back to disk on failure (`hermes config refresh` semantics).
- Missing disk cache → fall through to network (first-ever run).
- Stale disk cache (mtime > TTL) → fall through to network.
- Negative file age (clock skew) → fall through to network.
- Network failure → existing stage-4 stale-disk fallback unchanged.

Measured impact (3-run medians, 9950X3D, fresh process per run):
  fetch_models_dev cold:  256 → 17 ms  (-93%)
  hermes chat -q wall:   4.00 → 3.73 s (-7% median)
                         3.99 → 3.60 s (-10% min)

The chat-end-to-end win is bounded below by API latency variance, but
the fetch_models_dev microbenchmark is the cleanest signal: 239 ms
shaved off every fresh-process agent construction.

Win compounds with the previous perf PRs:
  #22681 google_chat lazy-load
  #22766 doctor parallel + IMDS off
  #22790 gateway.platforms PEP 562

Tests: all 30 `tests/agent/test_models_dev.py` pass (added 4 new ones
covering the new disk-cache-first path, force_refresh override, stale
disk fallback, and missing-disk-cache fall-through). Full `tests/agent/`
suite: 2560 passed, 0 failed.
2026-05-09 13:32:38 -07:00
LeonSGP43
14f38822fa fix(models): prefer image modalities for vision routing 2026-05-07 05:54:12 -07:00
teknium1
40a98fb0fa feat(minimax-oauth): full integration with peer OAuth providers
Close integration gaps discovered by auditing qwen-oauth's file coverage.
These are surfaces the original salvage missed — they all existed on
main and were added in the 747 commits since PR #15203 was opened.

Coverage added:
- agent/credential_pool.py: seed pool from auth.json providers.minimax-oauth
  so `hermes auth list` reflects logged-in state and
  `hermes auth remove minimax-oauth <N>` works through the standard flow.
- agent/credential_sources.py: register RemovalStep for minimax-oauth
  with suppression-aware `_clear_auth_store_provider`.
- agent/models_dev.py: PROVIDER_TO_MODELS_DEV mapping (-> 'minimax' family).
- hermes_cli/providers.py: HermesOverlay entry (anthropic_messages transport,
  oauth_external auth_type, api.minimax.io/anthropic base).
- hermes_cli/model_normalize.py: add to _MATCHING_PREFIX_STRIP_PROVIDERS so
  `minimax-oauth/MiniMax-M2.7` in config.yaml gets correctly repaired.
- hermes_cli/status.py: render MiniMax OAuth block in `hermes doctor`
  (logged-in / region / expires_at / error).
- hermes_cli/web_server.py: register in OAUTH_PROVIDER_REGISTRY + dispatch
  branch in _resolve_provider_status so the dashboard auth page shows it.
- website/docs/integrations/providers.md: full 'MiniMax (OAuth)' section.
- website/docs/reference/cli-commands.md: --provider enum.
- website/docs/user-guide/features/fallback-providers.md: fallback table row.
- scripts/release.py AUTHOR_MAP: amanning3390 mapping (CI gate).
2026-04-29 09:53:42 -07:00
zhzouxiaoya12
3d90292eda fix: normalize provider in list_provider_models to support aliases 2026-04-23 01:59:20 -07:00
hengm3467
c6b1ef4e58 feat: add Step Plan provider support (salvage #6005)
Adds a first-class 'stepfun' API-key provider surfaced as Step Plan:

- Support Step Plan setup for both International and China regions
- Discover Step Plan models live from /step_plan/v1/models, with a
  small coding-focused fallback catalog when discovery is unavailable
- Thread StepFun through provider metadata, setup persistence, status
  and doctor output, auxiliary routing, and model normalization
- Add tests for provider resolution, model validation, metadata
  mapping, and StepFun region/model persistence

Based on #6005 by @hengm3467.

Co-authored-by: hengm3467 <100685635+hengm3467@users.noreply.github.com>
2026-04-22 02:59:58 -07:00
helix4u
a7dd6a3449 fix(gemini): hide stale and low-TPM Google models 2026-04-18 12:52:01 -07:00
helix4u
2eab7ee15f fix(gemini): hide low-TPM Gemma models from exposed lists 2026-04-18 12:52:01 -07:00
kshitijk4poor
1b61ec470b feat: add Ollama Cloud as built-in provider
Add ollama-cloud as a first-class provider with full parity to existing
API-key providers (gemini, zai, minimax, etc.):

- PROVIDER_REGISTRY entry with OLLAMA_API_KEY env var
- Provider aliases: ollama -> custom (local), ollama_cloud -> ollama-cloud
- models.dev integration for accurate context lengths
- URL-to-provider mapping (ollama.com -> ollama-cloud)
- Passthrough model normalization (preserves Ollama model:tag format)
- Default auxiliary model (nemotron-3-nano:30b)
- HermesOverlay in providers.py
- CLI --provider choices, CANONICAL_PROVIDERS entry
- Dynamic model discovery with disk caching (1hr TTL)
- 37 provider-specific tests

Cherry-picked from PR #6038 by kshitijk4poor. Closes #3926
2026-04-16 02:22:09 -07:00
Teknium
8d023e43ed refactor: remove dead code — 1,784 lines across 77 files (#9180)
Deep scan with vulture, pyflakes, and manual cross-referencing identified:
- 41 dead functions/methods (zero callers in production)
- 7 production-dead functions (only test callers, tests deleted)
- 5 dead constants/variables
- ~35 unused imports across agent/, hermes_cli/, tools/, gateway/

Categories of dead code removed:
- Refactoring leftovers: _set_default_model, _setup_copilot_reasoning_selection,
  rebuild_lookups, clear_session_context, get_logs_dir, clear_session
- Unused API surface: search_models_dev, get_pricing, skills_categories,
  get_read_files_summary, clear_read_tracker, menu_labels, get_spinner_list
- Dead compatibility wrappers: schedule_cronjob, list_cronjobs, remove_cronjob
- Stale debug helpers: get_debug_session_info copies in 4 tool files
  (centralized version in debug_helpers.py already exists)
- Dead gateway methods: send_emote, send_notice (matrix), send_reaction
  (bluebubbles), _normalize_inbound_text (feishu), fetch_room_history
  (matrix), _start_typing_indicator (signal), parse_feishu_post_content
- Dead constants: NOUS_API_BASE_URL, SKILLS_TOOL_DESCRIPTION,
  FILE_TOOLS, VALID_ASPECT_RATIOS, MEMORY_DIR
- Unused UI code: _interactive_provider_selection,
  _interactive_model_selection (superseded by prompt_toolkit picker)

Test suite verified: 609 tests covering affected files all pass.
Tests for removed functions deleted. Tests using removed utilities
(clear_read_tracker, MEMORY_DIR) updated to use internal APIs directly.
2026-04-13 16:32:04 -07:00
Teknium
0e60a9dc25 fix: add kimi-coding-cn to remaining provider touchpoints
Follow-up for salvaged PR #7637. Adds kimi-coding-cn to:
- model_normalize.py (prefix strip)
- providers.py (models.dev mapping)
- runtime_provider.py (credential resolution)
- setup.py (model list + setup label)
- doctor.py (health check)
- trajectory_compressor.py (URL detection)
- models_dev.py (registry mapping)
- integrations/providers.md (docs)
2026-04-13 11:20:37 -07:00