hermes - ✅(Solved) Fix fix: custom_providers models list-of-dicts and id field not recognized — context_length always falls back to 256K [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#19857Fetched 2026-05-05 06:04:42
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Participants
Timeline (top)
labeled ×4cross-referenced ×2

Root Cause

Two bugs in hermes_cli/config.py_normalize_custom_provider_entry:

Fix Action

Fixed

PR fix notes

PR #19862: fix(config): recognize id field and list-of-dicts models in custom_providers normalization

Description (problem / solution / changelog)

Summary

Fixes two bugs in _normalize_custom_provider_entry() that cause custom provider config entries using id (instead of name) and list-of-dicts models to be silently dropped or lose per-model metadata.

Root Cause

Bug 1: id field not recognized as provider name

The normalizer only reads entry.get("name"). Config entries using id (common in hand-edited configs and Hermes-generated entries) have name stay empty, causing the function to return None — silently dropping the entire provider entry.

Bug 2: models list-of-dicts not parsed

The models normalizer only handles list[str] via isinstance(m, str). When models are written as list-of-dicts with id + context_length fields, each item fails the string check, producing an empty dict. The configured context_length is lost and the CLI falls back to the 256K default.

Fix

hermes_cli/config.py (3 changes):

  1. Add "id" to _KNOWN_KEYS to prevent false "unknown key" warnings
  2. raw_name = entry.get("name") or entry.get("id") — recognize id as fallback provider name
  3. Replace the models list comprehension with a loop that handles both str and dict items, extracting id/name/model keys and preserving all other metadata (like context_length)

Regression Coverage

tests/hermes_cli/test_provider_config_validation.py (+8 tests):

  • test_id_field_recognized_as_provider_nameid used as provider name
  • test_id_field_does_not_warn_as_unknown_key — no spurious warning
  • test_name_takes_precedence_over_idname wins when both present
  • test_models_list_of_dicts_parsed — list-of-dicts with id + context_length
  • test_models_list_of_dicts_with_name_key — dict entries using name key
  • test_models_mixed_list_of_strings_and_dicts — mixed list format
  • test_models_dict_entry_without_id_or_name_skipped — malformed entries skipped
  • test_models_dict_entry_strips_id_and_name_from_metadata — only metadata preserved

Testing

All 24 tests pass (16 existing + 8 new).

Config that triggers the bug

custom_providers:
  - base_url: http://127.0.0.1:38238/v1
    id: manifest          # uses id, not name
    key_env: CUSTOM_API_KEY
    models:
      - id: auto          # list-of-dicts, not list-of-strings
        context_length: 200000

Before fix: provider entry silently dropped, context_length lost → 256K fallback. After fix: provider entry normalized correctly, context_length preserved.

Fixes fix: custom_providers models list-of-dicts and id field not recognized — context_length always falls back to 256K #19857

Changed files

  • gateway/platforms/whatsapp.py (modified, +4/-1)
  • hermes_cli/config.py (modified, +14/-5)
  • tests/hermes_cli/test_provider_config_validation.py (modified, +111/-0)
  • tests/tools/test_file_tools.py (modified, +37/-0)
  • tools/file_tools.py (modified, +6/-6)

PR #19945: fix: harden voice key and custom provider parsing

Description (problem / solution / changelog)

Summary

This PR fixes two config/model-selection regressions reported in upstream issues:

  1. voice.record_key: "" no longer crashes CLI startup.

    • Empty, whitespace-only, or non-string values now normalize back to the safe default ctrl+b.
    • The same normalization path is reused for startup messaging, /voice status output, and prompt_toolkit keybinding registration.
  2. custom_providers normalization now preserves more real-world config shapes.

    • id is accepted as a provider-name alias alongside name.
    • models can be expressed as either list[str] or list[dict].
    • Per-model metadata such as context_length is retained so downstream context resolution does not silently fall back.

Tests

  • scripts/run_tests.sh tests/cli/test_voice_record_key_config.py tests/hermes_cli/test_provider_config_validation.py tests/hermes_cli/test_custom_provider_context_length.py

Related Issues

  • Fixes #19915
  • Fixes #19857

Notes

  • Scope is intentionally limited to regression hardening and normalization behavior.
  • No provider-routing logic changes are included in this PR.

Changed files

  • cli.py (modified, +23/-7)
  • hermes_cli/config.py (modified, +28/-9)
  • tests/cli/test_voice_record_key_config.py (added, +17/-0)
  • tests/hermes_cli/test_custom_provider_context_length.py (modified, +22/-0)
  • tests/hermes_cli/test_provider_config_validation.py (modified, +30/-0)

Code Example

# Before (drops entry when id is used instead of name)
raw_name = entry.get("name")

---

# Fix
raw_name = entry.get("name") or entry.get("id")

---

models:
  - id: auto
    context_length: 200000

---

# Before (produces empty dict for list-of-dicts)
normalized["models"] = {
    str(m): {} for m in models if isinstance(m, str) and m.strip()
}

---

# Fix
models_dict: Dict[str, Any] = {}
for m in models:
    if isinstance(m, str) and m.strip():
        models_dict[m.strip()] = {}
    elif isinstance(m, dict):
        model_id = m.get("id") or m.get("name") or m.get("model")
        if model_id and isinstance(model_id, str) and model_id.strip():
            models_dict[model_id.strip()] = {
                k: v for k, v in m.items() if k not in ("id", "name", "model")
            }
normalized["models"] = models_dict

---

custom_providers:
  - base_url: http://127.0.0.1:38238/v1
    id: manifest          # ← uses id, not name
    key_env: CUSTOM_API_KEY
    models:
      - id: auto          # ← list-of-dicts, not list-of-strings
        context_length: 200000

---

ctx = get_custom_provider_context_length('auto', 'http://...', config=cfg)
# Before fix: None  → resolves to 256K default
# After fix:  200000
RAW_BUFFERClick to expand / collapse

Bug

When custom_providers entries use id instead of name, and models is written as a list of dicts (with id + context_length fields), the configured context_length is silently ignored and the CLI falls back to the 256K default. This causes premature context exhaustion and failed compaction in long sessions.

Root Cause

Two bugs in hermes_cli/config.py_normalize_custom_provider_entry:

1. id field not recognized as provider name

The normalizer only reads entry.get("name") and provider_key. If the config entry uses id (common in hand-edited configs and Hermes-generated entries), name stays empty and the function returns None — silently dropping the entire provider entry.

# Before (drops entry when id is used instead of name)
raw_name = entry.get("name")
# Fix
raw_name = entry.get("name") or entry.get("id")

2. models list-of-dicts not parsed

The normalizer handles list[str] but not list[dict]. When models are written as:

models:
  - id: auto
    context_length: 200000

Each item fails isinstance(m, str) → empty dict → get_custom_provider_context_length returns None → 256K default used.

# Before (produces empty dict for list-of-dicts)
normalized["models"] = {
    str(m): {} for m in models if isinstance(m, str) and m.strip()
}
# Fix
models_dict: Dict[str, Any] = {}
for m in models:
    if isinstance(m, str) and m.strip():
        models_dict[m.strip()] = {}
    elif isinstance(m, dict):
        model_id = m.get("id") or m.get("name") or m.get("model")
        if model_id and isinstance(model_id, str) and model_id.strip():
            models_dict[model_id.strip()] = {
                k: v for k, v in m.items() if k not in ("id", "name", "model")
            }
normalized["models"] = models_dict

Config that triggers the bug

custom_providers:
  - base_url: http://127.0.0.1:38238/v1
    id: manifest          # ← uses id, not name
    key_env: CUSTOM_API_KEY
    models:
      - id: auto          # ← list-of-dicts, not list-of-strings
        context_length: 200000

Impact

  • CLI uses 256K context for any model configured this way
  • Compaction triggers too late (at 75% of 256K = 192K instead of 150K)
  • Sessions crash/fail to compact when the model's actual limit is lower

Verification

After applying both fixes:

ctx = get_custom_provider_context_length('auto', 'http://...', config=cfg)
# Before fix: None  → resolves to 256K default
# After fix:  200000

extent analysis

TL;DR

Update the _normalize_custom_provider_entry function in hermes_cli/config.py to recognize id as a provider name and parse models as a list of dictionaries.

Guidance

  • Modify the raw_name assignment to use entry.get("name") or entry.get("id") to handle cases where id is used instead of name.
  • Update the models parsing logic to handle both string and dictionary cases, using the provided fix to create a models_dict with the correct model IDs and context lengths.
  • Verify the fix by checking the return value of get_custom_provider_context_length for a configured model, ensuring it returns the expected context length instead of the default 256K.
  • Test the updated configuration with a sample model to confirm that compaction occurs at the correct threshold.

Example

# Updated _normalize_custom_provider_entry function
def _normalize_custom_provider_entry(entry):
    raw_name = entry.get("name") or entry.get("id")
    # ...
    models_dict: Dict[str, Any] = {}
    for m in models:
        if isinstance(m, str) and m.strip():
            models_dict[m.strip()] = {}
        elif isinstance(m, dict):
            model_id = m.get("id") or m.get("name") or m.get("model")
            if model_id and isinstance(model_id, str) and model_id.strip():
                models_dict[model_id.strip()] = {
                    k: v for k, v in m.items() if k not in ("id", "name", "model")
                }
    normalized["models"] = models_dict
    return normalized

Notes

The provided fix assumes that the id field in the models list of dictionaries is used as the model ID. If this is not the case, additional modifications may be necessary.

Recommendation

Apply the workaround by updating the `_normalize_custom

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix fix: custom_providers models list-of-dicts and id field not recognized — context_length always falls back to 256K [2 pull requests, 1 participants]