Stage 376: PR #2445

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
nesquena-hermes
2026-05-17 16:42:09 +00:00
3 changed files with 73 additions and 1 deletions
+1
View File
@@ -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
View File
@@ -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"])