81 Commits
Author SHA1 Message Date
webtecnicaandnesquena-hermes 7a5e6bc27a fix(providers): reduce pool-exhaustion TTL for HTTP 402 from 1h to 2min (#6626)
* fix(providers): reduce pool-exhaustion TTL for HTTP 402 from 1h to 2min

Mirrors the agent-side fix: when a provider returns HTTP 402 (Payment
Required), the credential pool marks the key as exhausted with a 1-hour
TTL — even though recharging credits restores the key immediately.

Both copies of _entry_exhausted_ttl_seconds() now handle HTTP 402 with
a 2-minute TTL, matching the new EXHAUSTED_TTL_402_SECONDS constant in
agent/credential_pool.py.

* fix(providers): tie HTTP 402 TTL to installed runtime contract (#6626)

* test(providers): behavior-test 402 TTL 119/120s boundary on both helper copies (#6626)

Add executed boundary tests for the HTTP 402 pool-exhaustion TTL:

- Host helper: with the runtime TTL monkeypatched to 120, an exhausted
  entry is still unavailable at 119s and becomes available at 120s, for
  both the integer 402 and the persisted string "402" error codes.
- Embedded _ACCOUNT_USAGE_SUBPROCESS_CODE copy: execute the subprocess
  code under the existing quota-subprocess harness and assert the same
  TTL and 119/120s eligibility boundary against the embedded helper,
  instead of counting source-string occurrences.
- Keep the legacy 3600s fallback and last_error_reset_at precedence
  assertions.

* test(providers): make 402 TTL parity tests self-contained for standalone CI

The parity tests imported agent.credential_pool directly, but the WebUI
test suite runs standalone (Hermes Agent runtime is not installed in CI),
causing ModuleNotFoundError on every shard since 08-02. Simulate the
runtime contract with a fake agent.credential_pool module (same pattern
already used by the embedded-subprocess helper test) so both the 120s
cooldown and the legacy 1h fallback paths are covered.

---------

Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
2026-08-13 20:15:30 -07:00
Kim Pedersen 0f7f3b6740 fix: match picker's sticky-before-plural ordering for provider card
The model picker (api/config.py:7308-7314) places the singular ``model``
field first, then appends unique IDs from the ``models`` catalog with
dedup. The Providers card was doing the opposite — dropping the singular
model entirely when a non-empty catalog existed.

This caused a silent disagreement: the picker showed the sticky default
model first, while the Providers card omitted it. Verified empirically
with ``model: sticky-default, models: {Best, Fast}`` — picker returned
the sticky model, card did not.

Now the card uses the same sticky-before-plural ordering as the picker:
singular model first (stripped), then _configured_model_ids appended
with dedup.

Updated tests:
- test_singular_model_appears_first_before_dict_models (was
  test_dict_models_ignore_legacy_model_field): now asserts sticky-first
  ordering instead of exclusion
- test_whitespace_and_duplicate_keys_normalized: updated for sticky-first
  prepend
2026-08-07 22:36:19 +00:00
Kim Pedersen 7b91484d79 refactor: reuse _configured_model_ids for custom provider catalog
Replace the manual if/elif/elif model-collection chain with a call to
the shared _configured_model_ids normalizer from api/config.py. This
ensures the Providers card applies the same whitespace stripping, empty-
ID dropping, and de-duplication as the model picker — closing a gap
where keys like "Best", " Best ", and "   " would produce duplicate
or empty-looking pills on the card while the picker reported a clean
catalog.

The refactored fallback also guards against whitespace-only singular
model fields, preventing empty pills from the fallback path.

Added two regression tests:
- whitespace/duplicate key normalization
- whitespace-only model field produces no entries
2026-08-07 22:36:19 +00:00
Kim Pedersen fd7666d5eb fix: empty dict/list falls through to model field for provider card
Greptile identified a real inconsistency (P1): when models is an empty
dict and a singular model field exists, the Providers card showed zero
models while the model picker (get_available_models) still surfaced the
configured default model via its separate default_model path.

Fix: gate both the list and dict branches on truthiness so an empty
container falls through to the singular model fallback — matching the
picker's behaviour and ensuring the card always shows at least the
configured default model.

Updated test_empty_dict_falls_through_to_model_field accordingly.
2026-08-07 22:36:19 +00:00
Kim Pedersen 8d936a86a3 fix: expand dict-shaped models in custom_providers for get_providers()
The `models` field in a custom_providers config entry can be a list or a
dict. The dict shape is the standard format produced by `hermes config set`:

    custom_providers:
      - name: litellm
        model: Coding
        models:
          Best: {context_length: 128000}
          Coding: {context_length: 1000000}
          Fast: {}

get_providers() only handled the list shape (#6127), so the Providers
settings card showed just the single `model` field instead of the full
catalog. The model picker (get_available_models in api/config.py) already
handled both shapes via _configured_model_ids — only the providers card
was broken.

Add a dict branch alongside the existing list branch, with regression
tests covering dict, list, default-model interaction, and empty-dict
edge cases.
2026-08-07 22:36:19 +00:00
seanxgao ab4d64f157 perf(providers): invalidate cache on oauth credential changes 2026-07-13 19:58:41 +08:00
seanxgao d3720fa724 perf(providers): cache provider list responses 2026-07-13 17:12:07 +08:00
Frank Song 92ea69c7c3 Require fingerprint evidence for wakeup credential recovery 2026-07-10 12:23:02 +08:00
Frank Song fc262a4aec Fix named-profile wakeup credential recovery 2026-07-10 11:14:08 +08:00
Frank Song 67b001e57c Fix wakeup pause recovery races 2026-07-10 07:08:36 +08:00
Frank Song 153207eaad fix(wakeup): allow distinct config key recovery 2026-07-09 10:24:49 +08:00
Frank Song d8b7ac23d6 fix(wakeup): require pool recovery before clearing pause 2026-07-09 09:43:48 +08:00
Frank Song 68e4770c2b fix(wakeup): revalidate recovered credential pauses 2026-07-08 08:56:05 +08:00
nesquena-hermesandrodboev 7220747a1d feat(#3260): self-hosted provider setup (Ollama/LM Studio) in Settings (i18n complete)
Clean rebase of rodboev's #5408 (rebase-first).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-02 08:17:44 +00:00
nesquena-hermes 0b74397fa6 Merge #5120 (rodboev budget limit) into stage 2026-06-30 21:28:51 +00:00
Brett Yelverton 0c338711df feat: add Neuralwatt provider support (env var mapping) 2026-06-29 20:41:16 +00:00
Rod Boev 04d0cde843 fix(#692): keep budget validation consistent across save and read 2026-06-29 01:59:51 -04:00
Rod Boev 62e25ad9c1 feat(#692): add configurable budget limit with percentage-used tracking 2026-06-28 10:10:35 -04:00
nesquena-hermesandlidi1011 94d952c3bb fix: harden None providers config across onboarding/providers/routes (salvage of #3967)
A config.yaml with an explicit empty/null `providers:` key parses to None in
PyYAML. `cfg.get("providers", {})` only returns the {} default when the key is
*absent*, so an explicit null yields None instead of an empty mapping. master
already guards each read with `isinstance(providers_cfg, dict)`, but the value
fed to that guard is still a footgun: the natural chained form
`cfg.get("providers", {}).get(...)` (already fixed once in api/config.py)
crashes with AttributeError on None.

Harden every providers-key read in the three remaining files to
`cfg.get("providers") or {}` so an explicit null degrades to an empty dict at
the source, matching the existing api/config.py convention. Sites hardened:

  api/onboarding.py:578  (_provider_api_key_present)
  api/providers.py:1051  (_provider_has_shadowed_codex_oauth_value)
  api/providers.py:1219  (_provider_has_key)
  api/providers.py:1265  (_get_provider_api_key)
  api/providers.py:2348  (get_providers catalog)
  api/providers.py:2781  (_clean_provider_key_from_config)
  api/routes.py:5045     (_context_length_config_api_key_for_provider)
  api/routes.py:5110     (_context_length_lookup_inputs_for_model)
  api/routes.py:15916    (_handle_live_models)

Adds tests/test_none_providers_config_guard.py: per-file source-form pins (fail
on master / on any single-file revert) plus behavioural checks driving the real
functions with `providers: None` to prove no crash and parity with the
empty-mapping config. Updates the existing test_issue3717 routes-string pin to
match the hardened form.

This is a focused salvage of grab-bag PR #3967 -- only the None-providers
hardening is taken; the PR's unrelated changes (account-usage worker-pool
refactor, function removals) are intentionally dropped.

Co-authored-by: lidi1011 <lidi1011@users.noreply.github.com>
2026-06-28 08:57:39 +00:00
nesquena-hermesandrodboev 4eb069be97 fix(#3961): complete credential scrub with non-registry agent creds (CUSTOM_API_KEY, AWS/Bedrock)
Codex re-gate found the registry-derived scrub still missed credential env vars
the agent resolves via raw os.getenv() that aren't in PROVIDER_REGISTRY:
CUSTOM_API_KEY (hermes_cli/models.py), AWS_BEARER_TOKEN_BEDROCK
(hermes_cli/model_switch.py), AZURE_ANTHROPIC_KEY (runtime_provider.py), and the
boto3 AWS_* credential family (agent/bedrock_adapter.py). Same cross-profile leak
class on custom/AWS-backed provider quota + detached-worker paths.

- Add _NON_REGISTRY_AGENT_CREDENTIAL_ENV_NAMES fail-closed set, union into
  _profile_secret_env_names so BOTH leak sites pick it up.
- _account_usage_subprocess_env now relies on the centralized scrub set (WebUI-
  only fallback only if the api.profiles import fails), dropping the partial
  local AWS pair.
- 2 more regressions (quota subprocess + detached worker) covering CUSTOM_API_KEY
  + AWS family, fail without the fix.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-22 19:19:56 +00:00
Rod Boev 7f7ec57ebd Skip dead and exhausted pool entries in _get_provider_api_key to match prior pool.select() contract 2026-06-20 21:42:51 -04:00
Rod Boev 58b50d243e fix(#3961): close load_pool secret-scope bypass and extend subprocess env strip set 2026-06-20 20:50:13 -04:00
Rod Boev 44437f7a26 fix(#3961): fail closed for profile-scoped credential pool reads 2026-06-20 19:52:52 -04:00
Rod Boev 7a0d868635 Keep profile-scoped reads from falling back to server credentials 2026-06-20 03:37:35 -04:00
Rod Boev 846951304d fix(#3961): route provider and model credential reads through the thread-local profile channel 2026-06-20 02:24:07 -04:00
nesquena-hermes 83a479d7a5 fix(#4365): wrap /api/provider/quota in profile_env_for_active_request
Codex gate caught that the new load_pool() path runs under the process-default
profile (sibling endpoints /api/providers + /api/models/live already wrap).
Without it a multi-profile client sees+seeds the default profile's pool
(#4247/#4067 profile-isolation class).
2026-06-19 05:27:42 +00:00
nesquena-hermesandakrhin b81a92ecd5 fix: credential pool fallback for custom providers (#4247)
Custom/aliased providers with keys in the credential pool (added via
hermes auth add) were reported unconfigured in the WebUI because provider
detection only checked env vars. Add _has_explicit_pool_credentials() that
checks the pool while filtering the ambient gh_cli entry (so Copilot doesn't
falsely show configured on any machine with gh installed), cache-routed to
avoid the cold per-provider load_pool latency, and resolve provider aliases
before load_pool() at every retrieval site so detection and retrieval read
the same pool (glm->zai, github->copilot, etc.). /api/models/live wrapped in
profile scope; getattr-safe runtime_api_key for OAuth-only entries.

Co-authored-by: akrhin <akrhin@users.noreply.github.com>
2026-06-16 17:44:10 +00:00
Rod Boev bb8d5e9587 fix(providers): claim probe workers under the pool lock (#3787) 2026-06-13 16:42:43 -04:00
Rod Boev 60570f1fa6 Distinguish closed workers from idle ones in cleanup 2026-06-11 12:19:17 -04:00
Rod Boev 0652cf9b16 fix(providers): treat proc=None as idle, not dead, in cleanup 2026-06-11 12:19:17 -04:00
Rod Boev b5f1965b3c fix(providers): replenish pool after partial cleanup removes stale workers 2026-06-11 12:19:17 -04:00
Rod Boev 40abc0f701 fix(providers): hold probe-worker lock across selector handoff 2026-06-11 12:19:17 -04:00
Rod Boev 501a587916 perf(providers): per-home probe-worker pool + scoped credential invalidation (#3787) 2026-06-11 12:19:17 -04:00
nesquena-hermes ce9adc5e2c Release v0.51.318 — Release KH (Phase 3 light: warm account-usage probe worker pool, #3722) (#3792)
Phase-3-LOW backend refactor. #3722 (@rodboev, #1912): per-probe subprocess.run -> warm worker pool for codex/anthropic quota probes; all hardening retained + idle reaper + credential invalidation + fallback. Codex SAFE, Opus SHIP, real-thread self-verify clean, CI 11/11. Follow-up #3787. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-07 12:07:13 -07:00
58528a4d88 Release v0.51.270 — Release IL (stage-u1 — un-hold batch: #3517 #3624 #3613) (#3674)
* feat(commands): add /use to force a skill for the next turn (#3517, #2977)

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* fix(auth): cap pending passkey challenges by evicting oldest, not rejecting (#3624)

Co-authored-by: Hinotobi <paperlantern.agent@gmail.com>

* fix(providers): expose model-provider plugins in WebUI (#3613)

Co-authored-by: Pamnard <pamnard@users.noreply.github.com>

* docs(changelog): v0.51.270 — Release IL (stage-u1, 3-PR un-hold batch; #3448 + #3618 dropped)

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: Hinotobi <paperlantern.agent@gmail.com>
Co-authored-by: Pamnard <pamnard@users.noreply.github.com>
2026-06-05 11:35:26 -07:00
442b033e67 Release v0.51.268 — Release IJ (stage-b1 — low-risk perf + provider/clarify fixes) (#3671)
* perf(providers): O(1) codex cache merge membership checks (#3656)

Co-authored-by: Pamnard <pamnard@users.noreply.github.com>

* fix(models): add MiniMax-M3 to WebUI MiniMax fallback catalog test (#3627)

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* fix(config): make DeepSeek reasoning-effort heuristic position-independent (#3650)

Co-authored-by: happy5318 <happy5318@users.noreply.github.com>

* fix(clarify): don't stash clarify draft while submission is in flight (#3651)

Co-authored-by: carryzuo00 <carryzuo00@gmail.com>

* perf(sessions): batch lineage report child fetch by parent id (#3659)

Co-authored-by: Pamnard <pamnard@users.noreply.github.com>

* perf(sessions): batch orphan sidecar state.db existence probes (#3657)

Co-authored-by: Pamnard <pamnard@users.noreply.github.com>

* test(streaming): pin DOM-INFLIGHT reattach invariant (#3572)

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* docs(changelog): v0.51.268 — Release IJ (stage-b1)

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: Pamnard <pamnard@users.noreply.github.com>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: happy5318 <happy5318@users.noreply.github.com>
Co-authored-by: carryzuo00 <carryzuo00@gmail.com>
2026-06-05 10:44:02 -07:00
AJV20 d2686eaedb fix: honor shared OpenCode key at runtime 2026-05-30 02:18:06 +00:00
fxd-jason 56575bd393 feat: sort configured/custom providers to top in model picker and settings 2026-05-22 16:13:46 +00:00
Michael Lam 8ef8fae831 fix: show config-managed custom providers 2026-05-20 06:27:00 -07:00
Michael Lam 1827ea3efd fix: add Grok OAuth provider catalog support 2026-05-18 19:51:01 -07:00
nesquena-hermesandMichael Lam b3bf2347e4 Stage 374: PR #2418 — fix(cost-history): provider-specific POSIX file lock for snapshot updates by @Michaelyklam (fixes #2402)
Co-authored-by: Michael Lam <michael@example.local>
2026-05-17 02:49:15 +00:00
Michael Lam 15513b81f4 fix: harden OpenRouter cost snapshots 2026-05-15 21:28:54 -07:00
Michael Lam 690b666887 Add OpenRouter cost history backend 2026-05-15 21:28:54 -07:00
starship-s ab663cdfc8 fix(providers): avoid caching transient quota probe failures 2026-05-13 19:01:47 -06:00
starship-s cc3080d5ac fix(providers): localize pooled quota retry text 2026-05-12 22:27:51 -06:00
starship-s c562ce2e8c fix(providers): preserve quota cache on refresh failure 2026-05-12 21:16:34 -06:00
starship-s a166625e02 fix(providers): refresh pooled Codex quota state 2026-05-12 21:00:24 -06:00
starship-s 1904eaed6b fix(providers): show fallback pool cooldown times 2026-05-12 20:05:20 -06:00
starship-s 0eb9dbc3e5 fix(providers): skip exhausted Codex quota probes 2026-05-12 19:09:05 -06:00
starship-s b87548b68e fix(providers): show pooled Codex quota status 2026-05-12 19:09:05 -06:00