mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-26 03:30:36 +00:00
@@ -8,6 +8,7 @@
|
||||
- Recover already-journaled visible assistant text and tool cards even when restart repair first syncs a populated Hermes core transcript into an otherwise empty WebUI sidecar. The core-sync branch now merges non-duplicate run-journal output before clearing stale stream state, closing the rare #2427 carve-out where recoverable partial output could be silently skipped. Fixes #2434.
|
||||
- Compact live Thinking cards now reuse the same timeline card across sequential tool calls, preventing repeated Thinking cards from stacking during one multi-tool turn.
|
||||
- Refresh context-window metadata when a session's resolved model changes during load or when the user switches models, so high-context models do not stay stuck on a stale prior window and trigger premature compression. Fixes #2442.
|
||||
- **PR #2445** by @Michaelyklam (fixes #2443) — `/api/models` now fingerprints model-catalog inputs as part of its persisted cache metadata, so server-side catalog additions and Codex local catalog changes invalidate `models_cache.json` immediately instead of waiting for the 24-hour TTL or manual cache deletion.
|
||||
|
||||
## [v0.51.82] — 2026-05-17 — Release BF (stage-375 — 2-PR batch — table renderer pipe protection + Catppuccin appearance skin)
|
||||
|
||||
|
||||
+36
-1
@@ -11,6 +11,7 @@ Discovery order for all paths:
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -2205,11 +2206,45 @@ def _models_cache_file_fingerprint(path: Path) -> dict:
|
||||
return fingerprint
|
||||
|
||||
|
||||
def _models_cache_catalog_fingerprint() -> dict:
|
||||
"""Return non-secret model-catalog identity metadata for cache invalidation.
|
||||
|
||||
The /api/models payload is not only a function of user config/auth files.
|
||||
It also depends on the provider/model catalog baked into this module and on
|
||||
small local catalogs such as Codex's models_cache.json. Keep this cheap and
|
||||
deterministic so a server restart after catalog changes does not keep
|
||||
serving an otherwise-valid persisted models_cache.json until the 24h TTL
|
||||
expires (#2443).
|
||||
"""
|
||||
catalog_payload = {
|
||||
"provider_models": _PROVIDER_MODELS,
|
||||
"provider_display": _PROVIDER_DISPLAY,
|
||||
}
|
||||
try:
|
||||
encoded = json.dumps(
|
||||
catalog_payload,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
ensure_ascii=True,
|
||||
default=str,
|
||||
).encode("utf-8")
|
||||
provider_catalog_sha = hashlib.sha256(encoded).hexdigest()
|
||||
except Exception:
|
||||
provider_catalog_sha = "unavailable"
|
||||
|
||||
codex_home = Path(os.getenv("CODEX_HOME", "").strip() or (HOME / ".codex")).expanduser()
|
||||
return {
|
||||
"provider_catalog_sha256": provider_catalog_sha,
|
||||
"codex_models_cache": _models_cache_file_fingerprint(codex_home / "models_cache.json"),
|
||||
}
|
||||
|
||||
|
||||
def _models_cache_source_fingerprint() -> dict:
|
||||
"""Return the current config/auth-store fingerprint for /api/models cache."""
|
||||
"""Return the current config/auth/catalog fingerprint for /api/models cache."""
|
||||
return {
|
||||
"config_yaml": _models_cache_file_fingerprint(_get_config_path()),
|
||||
"auth_json": _models_cache_file_fingerprint(_get_auth_store_path()),
|
||||
"catalog": _models_cache_catalog_fingerprint(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -142,3 +142,39 @@ def test_disk_models_cache_still_loads_when_auth_and_config_sources_are_unchange
|
||||
result = config.get_available_models()
|
||||
|
||||
assert result == fresh_opencode
|
||||
|
||||
|
||||
def test_memory_models_cache_invalidates_when_static_catalog_changes(tmp_path, monkeypatch):
|
||||
_configure_isolated_sources(tmp_path, monkeypatch, "opencode-go")
|
||||
stale_opencode = _valid_models_cache("opencode-go", "glm-5.1")
|
||||
with config._available_models_cache_lock:
|
||||
config._available_models_cache = stale_opencode
|
||||
config._available_models_cache_ts = time.monotonic()
|
||||
config._available_models_cache_source_fingerprint = config._models_cache_source_fingerprint()
|
||||
|
||||
updated_models = list(config._PROVIDER_MODELS["opencode-go"])
|
||||
updated_models.append({"id": "new-catalog-model", "label": "New Catalog Model"})
|
||||
monkeypatch.setitem(config._PROVIDER_MODELS, "opencode-go", updated_models)
|
||||
|
||||
result = config.get_available_models()
|
||||
|
||||
opencode_group = next(g for g in result["groups"] if g.get("provider_id") == "opencode-go")
|
||||
assert any(m.get("id") == "new-catalog-model" for m in opencode_group["models"])
|
||||
|
||||
|
||||
def test_disk_models_cache_invalidates_when_static_catalog_changes(tmp_path, monkeypatch):
|
||||
_configure_isolated_sources(tmp_path, monkeypatch, "opencode-go")
|
||||
stale_opencode = _valid_models_cache("opencode-go", "glm-5.1")
|
||||
config._save_models_cache_to_disk(stale_opencode)
|
||||
assert config._models_cache_path.exists()
|
||||
|
||||
updated_models = list(config._PROVIDER_MODELS["opencode-go"])
|
||||
updated_models.append({"id": "new-disk-catalog-model", "label": "New Disk Catalog Model"})
|
||||
monkeypatch.setitem(config._PROVIDER_MODELS, "opencode-go", updated_models)
|
||||
_reset_memory_cache()
|
||||
|
||||
result = config.get_available_models()
|
||||
|
||||
assert result != stale_opencode
|
||||
opencode_group = next(g for g in result["groups"] if g.get("provider_id") == "opencode-go")
|
||||
assert any(m.get("id") == "new-disk-catalog-model" for m in opencode_group["models"])
|
||||
|
||||
Reference in New Issue
Block a user