Commit graph

16476 commits

Author SHA1 Message Date
nousbot-eng
e89bc58a5b
fmt(js): npm run fix on merge (#67995)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-20 10:47:15 +00:00
Teknium
4ef92d2e5d
fix(desktop): survive older backends missing the batched sidebar endpoint (#67986)
Two stacked failures turned desktop/runtime version skew into a full
'Hermes couldn't start' boot brick:

1. listSidebarSessions() had no fallback when the backend predates the
   batched /api/profiles/sessions/sidebar route (added Jul 18) — the
   backend catch-all 404 ('No such API endpoint') rejected the refresh.
2. boot() awaited refreshSessions() unguarded inside Promise.all —
   unlike the reconnect and softSwitch call sites — so a session-LIST
   failure rejected the whole boot and raised failDesktopBoot() even
   though the gateway WS was already open and the app was usable.

Fixes:
- listSidebarSessions() now detects endpoint-missing errors (backend
  catch-all, IPC-wrapped 404, Electron HTML-guard), falls back to the
  three proven per-slice /api/profiles/sessions calls with identical
  scoping (recents on the caller's profile, cron + messaging
  cross-profile), and remembers the verdict so later refreshes skip the
  dead probe. Transient failures (timeout, 5xx, ECONNREFUSED) still
  throw — no silent fast-path degradation from one blip.
- wipeSessionListsForGatewaySwitch() resets the capability flag so a
  soft gateway switch re-probes the next backend instead of leaking the
  old one's verdict (hard re-homes reload the window and reset anyway).
- boot() treats refreshSessions() as non-fatal, matching its sibling
  call sites: worst case is an empty sidebar that the next
  reconnect/turn refresh repopulates, never a bricked boot.

Tests: endpoint-missing fallback slices + scoping, sticky verdict (no
re-probe), re-probe after gateway switch, transient errors NOT
triggering fallback, and a real-hook harness test proving a rejecting
refreshSessions still completes boot with no overlay.
2026-07-20 03:40:04 -07:00
Teknium
34a304abb3 fix: unpack judge_goal 4-tuple in salvaged CLI gate; harden tests
Follow-up to the cherry-picked CLI judge gate (#55854): the gate carried
the same 3-value unpack bug just fixed on the tool path in PR #67973 —
the ValueError would have been swallowed by the fail-open handler,
silently disabling the gate.

Also: test mock now returns the real 4-value judge contract (the old
3-value mock masked the bug), tests track complete_task invocations and
assert the rejection path never writes, and the unused _make_goal_task
helper is dropped.
2026-07-20 03:36:44 -07:00
srojk34
aa32154e4b fix(kanban): apply goal_mode judge gate to CLI complete command
The three-commit hardening series (Issue #38367, PR #55408) added a
pre-completion judge gate to `tools/kanban_tools.py:_handle_complete`
(the kanban_complete tool used by agent tool-calls).  The structurally
identical `hermes_cli/kanban.py:_cmd_complete` (the `hermes kanban
complete` CLI subcommand) was left unguarded.

A goal_mode worker with terminal tool access — the overwhelming default
for coding agents — can bypass the judge entirely by running:
    hermes kanban complete <task_id>
This transitions the task to `done` status with no judge verdict, making
the acceptance-criteria enforcement worthless on that path.

Fix: apply the same gate in _cmd_complete before calling kb.complete_task.
When a judge is reachable and returns anything other than "done", the
command prints an actionable rejection message and exits non-zero without
modifying the task.  The fail-open policy (no judge configured → allowed)
is preserved to match the tool-call path.
2026-07-20 03:36:44 -07:00
Teknium
73543744bc fix(gateway): key-presence precedence for session_reset/stt nested fallback
Follow-up for salvaged PR #59779: the session_reset and stt fallbacks
used truthiness/type checks, so a present-but-empty top-level value was
silently replaced by the nested gateway.* form — inconsistent with the
key-presence precedence every other key in the block uses. Switch both
to 'key not in yaml_cfg' gating and add precedence regression tests.
2026-07-20 03:34:58 -07:00
pierrenode
e9bd3b6eeb fix(gateway): honor nested gateway.* form for 9 more top-level keys
load_gateway_config() already accepted both the top-level key and the
nested gateway.<key> form (written by `hermes config set gateway.<key>
...`) for multiplex_profiles, max_concurrent_sessions, streaming, and
write_sessions_json — each fixed one at a time as users hit it (most
recently #59320 for multiplex_profiles). Nine sibling top-level keys
never got the same nested fallback: session_reset, quick_commands, stt,
stt_echo_transcripts, group_sessions_per_user, thread_sessions_per_user,
reset_triggers, always_log_local, and unauthorized_dm_behavior.

`hermes config set gateway.<any-of-these> ...` builds exactly this nested
shape (hermes_cli/config.py's _set_nested has no schema, so it accepts
any dotted path), so a user following the same pattern that legitimately
works for gateway.multiplex_profiles/gateway.streaming gets a silent
no-op for these nine keys instead.

Fix: read `gateway: {...}` into a single `gateway_section` variable once
(consolidating three separate `yaml_cfg.get("gateway")` calls already in
the function) and add the same top-level-wins/nested-fallback check for
each of the nine keys, mirroring the existing write_sessions_json
precedent exactly.

Note: because every fallback here is guarded by
`isinstance(gateway_section, dict)`, this also makes the streaming
fallback tolerate a scalar `gateway:` block (e.g. `gateway: disabled`)
without crashing — the same crash #40837 (open) targets specifically for
streaming. This change doesn't set out to fix that PR's issue, but the
consolidated guard covers it as a side effect; flagging it for the
reviewer rather than leaving it to be found in review.
2026-07-20 03:34:58 -07:00
Teknium
c8027d5e60 chore: map contributor email for whitespace-block fix 2026-07-20 03:27:38 -07:00
polyhistor
a7e911150c fix(bedrock): address review — route list-string items through _safe_text, update stale placeholder assertions
Addresses hermes-sweeper review on PR #66167:

1. _convert_content_to_converse() still emitted {"text": part} directly
   for plain-string items inside a content list (as opposed to
   {"type": "text"} dicts), bypassing _safe_text() entirely. A
   whitespace-only string item (e.g. ["   "]) could still reach Bedrock
   as a blank block. Now routed through _safe_text().

2. tests/agent/test_bedrock_adapter.py::TestEmptyTextBlockFix asserted
   the pre-fix behavior (whitespace -> literal space " "), contradicting
   the new _safe_text()/_EMPTY_TEXT_PLACEHOLDER behavior added in
   4618095a. Updated assertions to expect the non-whitespace placeholder,
   plus added a regression test for the list-string-item case above.
2026-07-20 03:27:38 -07:00
polyhistor
9172048a2f fix(bedrock): use non-whitespace placeholder for empty text blocks
Bedrock Converse rejects text content blocks that are empty OR
whitespace-only (ValidationException: "text content blocks must
contain non-whitespace text"). The prior fix attempt substituted a
single space (" ") for missing content -- but a lone space IS
whitespace, so it was rejected by the exact same validation rule it
was meant to satisfy. This caused a deterministic, unrecoverable
retry-loop failure once any blank/whitespace assistant, tool, or user
turn entered history (most commonly via context-compaction rewriting
a turn to a blank string).

Adds _safe_text()/_EMPTY_TEXT_PLACEHOLDER ("(empty)") and applies it
everywhere a blank text block could reach the wire: user/assistant
content conversion, tool results, the assistant-empty-turn fallback,
and the first/last-message user-alternation padding. System-prompt
blocks are the one exception: blank parts are dropped entirely rather
than placeholder-filled, since a system prompt block should never
carry meaningless placeholder text.

Adds tests/agent/test_bedrock_empty_text_blocks.py (11 tests, was
already present uncommitted -- codifies the exact failing history
from issue #9486 and asserts no blank block ever reaches Bedrock).

Verified against the actual failed request dump from this session
(27-message payload) -- replaying it through the fixed converter now
produces zero blank/whitespace-only blocks.
2026-07-20 03:27:38 -07:00
Teknium
7120f9cba9 chore: map bedrock-cluster contributor emails 2026-07-20 03:27:28 -07:00
iizotov
81d4b3654a fix(bedrock): map Claude opus/sonnet 4.6+ to 1M context window
The Bedrock static context table only had entries up to the 4-6
generation and capped all Claude models at 200K. Newer model IDs
(opus/sonnet 4-7, 4-8) silently inherited 200K via the generic
"anthropic.claude-opus-4" / "...-sonnet-4" substring fallback,
contradicting the native Anthropic table in model_metadata.py which
already maps these to 1M.

Map opus/sonnet 4-6/4-7/4-8 to 1_000_000 on Bedrock. Haiku 4.5,
sonnet 4.5, legacy Claude 4 and 3.x stay at 200K (no 1M window).

Add opus-4-8 coverage and haiku/sonnet-4-5 200K guards.
2026-07-20 03:27:28 -07:00
Patrick Muller
a8602e4afa test(bedrock): update context-length tests to match Anthropic docs
Update TestBedrockContextLength to assert the corrected values from
BEDROCK_CONTEXT_LENGTHS:

- test_claude_opus_4_6: 200_000 -> 1_000_000 (1M GA for Opus 4.6)
- test_claude_sonnet_versioned: 200_000 -> 1_000_000 (1M GA for Sonnet 4.6)
- test_inference_profile_resolves: 200_000 -> 1_000_000
  (us.anthropic.claude-sonnet-4-6 resolves to Sonnet 4.6's 1M value)

Also adds three new test cases that document Anthropic's published
context windows explicitly and guard against future regressions:

- test_claude_opus_4_7: asserts 1_000_000 and cites the models overview
- test_claude_sonnet_4_5_is_200k: asserts 200_000 and cites the
  April 30, 2026 release note retiring the 1M beta for Sonnet 4.5
- test_claude_haiku_4_5_is_200k: asserts 200_000 for Haiku 4.5

All 178 tests in test_bedrock_adapter.py pass after the change.
2026-07-20 03:27:28 -07:00
Patrick Muller
3cf6ee7355 fix(bedrock): correct Sonnet 4.5 and Haiku 4.5 context to 200K per Anthropic docs
The previous commit in this branch bumped claude-sonnet-4-5 and
claude-haiku-4-5 to 1_000_000 on the assumption the context-1m-2025-08-07
beta enabled 1M on all Claude 4.x models. Verification against Anthropic's
own documentation shows that is incorrect:

- Claude Haiku 4.5 is a standard 200K model per
  https://platform.claude.com/docs/en/about-claude/models/overview
  (the 'Latest models comparison' table shows '200k tokens' for Haiku 4.5).

- Claude Sonnet 4.5 had its 1M beta retired on April 30, 2026 per
  https://platform.claude.com/docs/en/release-notes/overview:
  'We've retired the 1M token context window beta (context-1m-2025-08-07)
  for Claude Sonnet 4.5 and Claude Sonnet 4. The beta header now has no
  effect on these models, and requests exceeding the standard 200k-token
  context window return an error.'

Revert both entries to 200_000. Opus 4.7, Opus 4.6, and Sonnet 4.6
remain at 1_000_000 — those three have 1M generally available with no
beta header required per the same source.

Also updates the header comment to cite the Anthropic models overview
and the April 30 2026 release note so future readers have an upstream
source of truth.
2026-07-20 03:27:28 -07:00
Patrick Muller
c02466d5e8 fix(bedrock): raise Claude 4.x context window to 1M and add opus-4-7
Claude 4.x models on Bedrock support a 1M-token context window via the
context-1m-2025-08-07 beta header, which Hermes already injects
automatically in build_anthropic_bedrock_client
(agent/anthropic_adapter.py). However, BEDROCK_CONTEXT_LENGTHS in
agent/bedrock_adapter.py still reported 200K for opus-4-6, sonnet-4-6,
sonnet-4-5, and haiku-4-5, and had no entry at all for opus-4-7 (which
falls back via substring match to the 200K opus-4 entry).

This caused Hermes to display a 200K window, compress conversations
earlier than necessary (compression.threshold * 200K instead of * 1M),
and generally under-utilize the full 1M context users are paying for.

The fix is metadata-only — the Bedrock API and beta header already
support 1M end-to-end. agent/model_metadata.py's DEFAULT_CONTEXT_LENGTHS
table already lists claude-opus-4-7 / -4-6 / sonnet-4-6 at 1M for the
non-Bedrock paths, so this change brings the Bedrock table into
alignment.

Changes:
- Add anthropic.claude-opus-4-7 at 1_000_000
- Bump anthropic.claude-opus-4-6 from 200_000 to 1_000_000
- Bump anthropic.claude-sonnet-4-6 from 200_000 to 1_000_000
- Bump anthropic.claude-sonnet-4-5 from 200_000 to 1_000_000
- Bump anthropic.claude-haiku-4-5 from 200_000 to 1_000_000
- Add explanatory comment pointing readers at the beta-header injection
  site in agent/anthropic_adapter.py
2026-07-20 03:27:28 -07:00
Teknium
47fb20c0bd
fix: session-scoped /fast + full /new reset to config defaults (#67979)
* fix(fast): default /fast to session scope on CLI and gateway

Completes the session-first policy from #67946 for the /fast toggle
(the remaining half of #54084). A bare /fast fast|normal now applies to
the current session only; --global persists agent.service_tier to
config.yaml.

Gateway: new _session_service_tier_overrides dict (registered in
_CONVERSATION_SCOPED_STATE so /new clears it) resolved at both agent
turn sites via _resolve_session_service_tier(); the /fast handler and
its choice picker apply session overrides and evict the cached agent.
The TUI config.set fast path was already session-scoped.

CLI: /fast parses --global (parity with /reasoning); bare toggles
mutate self.service_tier only.

* fix(sessions): /new resets model, reasoning, and fast to config defaults

/new and /reset are full conversation boundaries: session-scoped
runtime overrides do not carry into the next session (#48055, #23131).

CLI new_session(): clears the one-turn model restore, re-derives
service_tier from config, and — when the session's model differs from
the config default — switches back via the shared switch_model()
pipeline (live agent swap included; best-effort so an unreachable
default never blocks /new).

TUI _reset_session_agent(): stops forwarding model_override /
create_reasoning_override / create_service_tier_override into the
rebuilt agent and pops the pins so later rebuilds can't resurrect
them. The gateway already cleared its per-session overrides via
_clear_conversation_scope on /new.

Cross-session contamination stays impossible: nothing here touches
process-global env or other sessions' pins.
2026-07-20 03:27:22 -07:00
Teknium
3441b80f4f feat(pricing): add Bedrock rows for Opus 4.8/4.7, correct Opus 4.6 to $5/$25
Adds current-gen Claude Opus pricing rows on Bedrock keyed to Anthropic's
published list price, which commercial Bedrock on-demand mirrors. Also
corrects the existing Opus 4.6 row: it carried Claude-3-era Opus pricing
($15/$75); Opus 4.5+ list at $5/$25 with cache write 1.25x / read 0.1x.

The AWS Price List API had not published these SKUs machine-readably as
of 2026-07, so these are commercial-list snapshots pending an
authoritative machine source.

Reapplied from PR #62327 (commit authored under a placeholder identity,
so cherry-pick was not usable; sonnet-5 row from that PR already landed
via #67932).

Co-authored-by: pgregg88 <4943027+pgregg88@users.noreply.github.com>
2026-07-20 03:27:16 -07:00
Osraka
e101bbaebb fix(pricing): restrict Bedrock profile normalization 2026-07-20 03:27:16 -07:00
Osraka
54418a888e fix(pricing): resolve versioned Bedrock profile IDs 2026-07-20 03:27:16 -07:00
Teknium
9ca8ce4335
fix(kanban): unpack judge_goal's 4-tuple at the completion gate (#67973)
judge_goal() returns (verdict, reason, parse_failed, wait_directive) since
the goals.py wait-directive change, but the kanban goal-mode completion gate
at tools/kanban_tools.py still unpacked 3 values. Every judge call raised
ValueError, the defensive except swallowed it, and the pre-initialized
verdict='done' let every completion through — the acceptance gate was
silently disabled.

Now unpacks all 4 values; the test mock is updated to match the real
contract. The other two judge_goal consumers (hermes_cli/goals.py) already
use 4-value unpacks.

Reported and diagnosed by @bill3wits in PR #57276; reimplemented under
project authorship because the original commit was authored under a
non-existent local identity (bash@hermes.local) that cannot be carried
into history. Also fixes #58066 (duplicate report by @Gibcity).
2026-07-20 03:13:44 -07:00
Teknium
c1af3772fc chore: map salvage contributor deepujain 2026-07-20 03:06:02 -07:00
nnnet
523a64a726 feat(providers): post-filter picker by `enabled: false` for built-ins
Sections 1-2 of ``list_authenticated_providers`` emit rows directly
from ``PROVIDER_REGISTRY`` (auth-driven built-ins) before reaching the
per-section gate I added for section 3 (user-config providers). That
means flipping ``providers.openrouter.enabled: false`` hid OpenRouter
from a user-config block but the built-in OpenRouter row still showed
because its row came from section 1's auth-status path.

Add a single post-filter at the end of ``list_authenticated_providers``
that drops every row whose ``provider_id`` or ``slug`` matches a
disabled name in ``providers``. Same source of truth, applied once at
the end, covers all four sections in one pass.

Wrapped in ``try/except`` so a degraded config can't break the picker —
if anything fails reading the config, the filter no-ops and the picker
shows the un-filtered list (same as before this PR).
2026-07-20 03:06:02 -07:00
nnnet
305ecac8b2 feat(providers): extend `enabled: false` gate to built-in resolution
The first commit's gate sat inside ``_get_named_custom_provider`` —
which only handles user-defined custom blocks. Built-in provider names
(``openai`` / ``anthropic`` / ``openrouter`` / ``gemini`` / ...) have
their own resolution paths in ``resolve_runtime_provider`` (pool /
explicit / generic / ``resolve_provider``) and bypass that gate.

So a user who flipped ``providers.openrouter.enabled: false`` would
still see OpenRouter resolved when something explicitly requested it
(e.g. a fallback chain entry). That defeats the point of the flag.

This commit moves the gate one level up: right after
``requested_provider`` is computed, before any custom / built-in /
Azure short-circuit. It now raises a typed ``ValueError`` referencing
the YAML path, so callers can recognise it and advance to the next
fallback instead of silently using a disabled provider.

3 new tests cover:
* disabled custom provider raises
* disabled built-in provider raises
* enabled provider doesn't hit the gate

All 20 tests in the providers suite pass.
2026-07-20 03:06:02 -07:00
nnnet
7de06f700e feat(providers): add `enabled: false` flag to hide a provider
A ``providers.<name>`` block in ``config.yaml`` can now opt out of being
listed anywhere by setting ``enabled: false`` — without removing the
block, so re-enabling it stays a one-line edit. Missing or ``true`` keeps
the previous behaviour (enabled), so this is fully backwards-compatible.

The flag is honoured in four places:

* ``hermes_cli/model_switch.py`` — model-override validation (the
  allow-list that the picker consults to accept a non-public model id)
  and the picker's own endpoint iteration. A disabled provider no longer
  appears as a row and its models can't be silently accepted via
  override.
* ``hermes_cli/runtime_provider.py`` — the runtime resolver skips
  disabled blocks, so an explicit ``--provider X`` against a disabled
  entry fails fast instead of using stale base_url / api_key from the
  ignored block.
* ``hermes_cli/doctor.py`` — the doctor's "configured providers" set
  excludes disabled entries, so health checks don't flag missing API
  keys for providers the user has turned off.

Motivation: when a user has 20+ providers wired up in ``config.yaml``
(many of them only used occasionally) the picker becomes noisy and the
runtime resolver may pick a suboptimal one on ambiguous --provider names.
There's currently no way to hide a provider short of deleting its block
— which loses the api_key + base_url + custom routing config the user
spent time wiring. ``enabled: false`` lets them keep the config but get
it out of the way.

The helper ``is_provider_enabled()`` in ``hermes_cli/config.py``
centralises the gate (and accepts YAML-stringified booleans like
``"false"`` for hand-edited configs). 17 unit tests cover the defaults
and edge cases.

A follow-up PR can wire ``hermes provider enable/disable <name>`` and a
dashboard toggle on top of this primitive — they reduce to mutating the
flag.
2026-07-20 03:06:02 -07:00
Craig French
b239ee2123 feat(model-switch): excluded_providers config to hide providers from /model picker 2026-07-20 03:06:02 -07:00
Deepak Jain
1b56d0d1a2 test(cli): isolate model picker Ollama probes
Fixes #30604
2026-07-20 03:06:02 -07:00
Jan-Stefan Janetzky
766c617e83 fix(compression): detect semantic no-op results 2026-07-20 02:25:57 -07:00
Jan-Stefan Janetzky
75af6dc57c fix(redaction): normalize URL credential key aliases 2026-07-20 02:25:57 -07:00
Jan-Stefan Janetzky
763c7f79d4 test(compression): isolate provider handoff setup 2026-07-20 02:25:57 -07:00
Jan-Stefan Janetzky
46e4891c64 fix(compression): close post-dispatch lock scope 2026-07-20 02:25:57 -07:00
Jan-Stefan Janetzky
62a00a7391 fix(redaction): cover strict URL reference forms 2026-07-20 02:25:57 -07:00
Jan-Stefan Janetzky
a48315e322 fix(compression): guard lock refresher startup 2026-07-20 02:25:57 -07:00
Jan-Stefan Janetzky
34bab1c6ac fix(compression): harden provider context handoff 2026-07-20 02:25:57 -07:00
Jan-Stefan Janetzky
192ef93ad5 fix(agent): harden pre-compress context handoff 2026-07-20 02:25:57 -07:00
Tranquil-Flow
ad8c533cc7 fix(agent): capture on_pre_compress return value and pass to compressor
The MemoryProvider.on_pre_compress() hook returns text that providers
want preserved in the compression summary, but run_agent.py discarded
the return value. Additionally, compress() and _generate_summary() had
no mechanism to accept this context.

- Capture on_pre_compress() return value in run_agent.py
- Add memory_context parameter to compress() and _generate_summary()
- Inject memory provider insights into summarization prompts

Fixes all MemoryProvider plugins that return context from
on_pre_compress() (currently silently broken for every plugin).
2026-07-20 02:25:57 -07:00
Teknium
2ec1e81036 chore: add contributor email mapping for GottZ 2026-07-20 02:25:57 -07:00
Teknium
8decd39844 test(file-sync): patch module-level _monotonic alias instead of shared stdlib time module
Follow-up for salvaged PR #39946: file_sync.py already aliases time.sleep
as _sleep specifically to avoid tests mutating the shared stdlib module
object. Apply the same convention to the rate-limit clock (_monotonic)
and point the new regression test at it.
2026-07-20 02:25:53 -07:00
Taylor H. Perkins
f9158b818b fix(file-sync): don't rate-limit retry after a failed sync cycle
FileSyncManager.sync() is rate-limited to once per _sync_interval via
_last_sync_time, and its docstring promises that on failure "state rolls
back so the next cycle retries everything". But the except handler also
set _last_sync_time = time.monotonic() on failure, so the next non-forced
sync() within the interval hit the rate-limit guard and returned early —
suppressing the retry the rollback had just prepared.

Because the non-forced sync() runs before every command on the SSH, Modal
and Daytona backends, a single transient upload failure (network blip,
dropped channel) left the remote with stale files for the next command
(up to _sync_interval, default 5s). Forced syncs bypass the guard, which
is why it was intermittent.

Remove the failure-path timestamp bump so the clock only advances on a
successful or no-op cycle, matching the documented contract. Add a
regression test that fails before this change and passes after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 02:25:53 -07:00
Teknium
f904f185ee fix: regenerate model-catalog.json to dedupe sonnet-5 entries
The salvaged #55853 commit added anthropic/claude-sonnet-5 blocks to the
manifest, but main already carried them from #56617 — leaving duplicate
entries the manifest-sync test rejects. Rebuilt via
scripts/build_model_catalog.py.
2026-07-20 02:25:44 -07:00
Teknium
24ea13a8e9 fix: align gmi fallback_models ordering with curated list
test_gmi_provider asserts fallback_models == _PROVIDER_MODELS["gmi"];
the salvaged plugin commit placed sonnet-5 after sonnet-4.6 while the
curated list has it before. Match the curated ordering.
2026-07-20 02:25:44 -07:00
Teknium
d587048fca chore: map salvage contributor emails to GitHub logins 2026-07-20 02:25:44 -07:00
Teknium
f6abfc05be fix: keep Sonnet 5 intro pricing over duplicate standard-rate entry
PR #55848 and #60410 both added an (anthropic, claude-sonnet-5) pricing
key; the later duplicate (/$15 standard rate) would silently win in
the dict literal. Keep the intro pricing entry ($2/$10 through
2026-08-31 per Anthropic docs) which carries the reversion note.
2026-07-20 02:25:44 -07:00
Daniel Brunsdon
f44074df7f feat(pricing): add Claude Sonnet 5 intro pricing entry
Sonnet 5 launched 2026-06-30 with introductory pricing ($2/$10 per
MTok input/output) through 2026-08-31, after which it reverts to
$3/$15. The model had no entry in the official-docs pricing snapshot,
so any session on claude-sonnet-5 was tracked as cost_status=unknown
with $0 estimated cost -- silently hiding real spend from
hermes insights and any downstream cost sync.

Source: https://platform.claude.com/docs/en/about-claude/pricing
2026-07-20 02:25:44 -07:00
liuhao1024
5d7326a90e fix(gmi): add claude-sonnet-5 to fallback_models
The test test_provider_model_ids_falls_back_to_static_models asserts
provider_model_ids('gmi') == list(_PROVIDER_MODELS['gmi']), but when
live API is unavailable the function returns fallback_models from the
provider profile instead of _PROVIDER_MODELS. Add claude-sonnet-5 to
the GMI plugin's fallback_models to match the curated list update.
2026-07-20 02:25:44 -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
9bda6438d4
fix(config): remove unknown-top-level-key warning — top-level keys bridge to env (#67924)
The 'Unknown top-level config key' warning (f5bacee27) assumed a
closed-world allowlist of valid roots, but top-level scalars in
config.yaml are deliberately bridged into os.environ (gateway/run.py,
hermes send) so skills and external apps Hermes drives can read
arbitrary env-style keys (DISCORD_HOME_CHANNEL, MY_APP_TOKEN, ...).
An allowlist can never enumerate those — two widening follow-ups
(7c2ece53c, 3c7217706) already proved the whack-a-mole. Drop the
generic warning entirely; keep the targeted provider-like-field
misplacement hint (base_url/api_key at root).
2026-07-20 02:25:33 -07:00
Craig French
1c3a48965b fix(model-switch): keep same-endpoint custom providers with different names as separate picker rows 2026-07-20 02:22:37 -07:00
Alex López
7ed18dae90 test(model): isolate custom-provider grouping tests from live discovery 2026-07-20 02:22:26 -07:00
Teknium
76fca0b4b3 docs: session-scoped /model and /reasoning defaults 2026-07-20 02:22:22 -07:00