feat(models): seed model-catalog disk cache from checkout on update (#42614)
hermes update pulls the latest repo, so the freshly-pulled website/static/api/model-catalog.json is already the newest catalog. Copy it straight over ~/.hermes/cache/model_catalog.json instead of relying on a network fetch (which can be Vercel bot-gated or hit a Portal hiccup and silently degrade the picker to a stale/short list). Adds seed_cache_from_checkout() in model_catalog.py (read shipped manifest, validate, atomic write via _write_disk_cache, reset in-process cache) and calls it from both update paths in main.py: _cmd_update_impl (git pull) and _update_via_zip (Docker/no-git). Non-fatal on missing/malformed/invalid files — the normal network refresh still applies on next picker open.
This commit is contained in:
parent
c1927d2342
commit
54318c65b0
@ -5806,6 +5806,16 @@ def _update_via_zip(args):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Seed the model-catalog disk cache from the freshly-unpacked checkout
|
||||||
|
# (same rationale as the git-pull path in _cmd_update_impl). Non-fatal.
|
||||||
|
try:
|
||||||
|
from hermes_cli.model_catalog import seed_cache_from_checkout
|
||||||
|
|
||||||
|
if seed_cache_from_checkout(PROJECT_ROOT):
|
||||||
|
print(" ✓ Model catalog cache refreshed from checkout")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("Model catalog seed during zip update failed: %s", e)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print("✓ Update complete!")
|
print("✓ Update complete!")
|
||||||
try:
|
try:
|
||||||
@ -8365,6 +8375,22 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
|||||||
print()
|
print()
|
||||||
print("✓ Code updated!")
|
print("✓ Code updated!")
|
||||||
|
|
||||||
|
# Seed the model-catalog disk cache from the freshly-pulled checkout.
|
||||||
|
# The repo ships the canonical catalog at
|
||||||
|
# website/static/api/model-catalog.json, and `git pull` just made it
|
||||||
|
# current — so copy it straight over ~/.hermes/cache/model_catalog.json
|
||||||
|
# instead of waiting on a network fetch (which can be bot-gated or hit a
|
||||||
|
# Portal hiccup). Keeps the model picker's curated/free lists in sync
|
||||||
|
# with the version the user just installed. Non-fatal on failure: the
|
||||||
|
# normal network refresh still applies on the next picker open.
|
||||||
|
try:
|
||||||
|
from hermes_cli.model_catalog import seed_cache_from_checkout
|
||||||
|
|
||||||
|
if seed_cache_from_checkout(PROJECT_ROOT):
|
||||||
|
print(" ✓ Model catalog cache refreshed from checkout")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("Model catalog seed during update failed: %s", e)
|
||||||
|
|
||||||
# After git pull, source files on disk are newer than cached Python
|
# After git pull, source files on disk are newer than cached Python
|
||||||
# modules in this process. Reload hermes_constants so that any lazy
|
# modules in this process. Reload hermes_constants so that any lazy
|
||||||
# import executed below (skills sync, gateway restart) sees new
|
# import executed below (skills sync, gateway restart) sees new
|
||||||
|
|||||||
@ -356,6 +356,37 @@ def get_curated_nous_models() -> list[str] | None:
|
|||||||
return out or None
|
return out or None
|
||||||
|
|
||||||
|
|
||||||
|
def seed_cache_from_checkout(project_root: "Path | str") -> bool:
|
||||||
|
"""Overwrite the disk cache with the catalog shipped in a local checkout.
|
||||||
|
|
||||||
|
``hermes update`` pulls the latest repo, so the freshly-pulled
|
||||||
|
``website/static/api/model-catalog.json`` IS the newest catalog — no
|
||||||
|
network round-trip needed. Copying it straight over the disk cache keeps
|
||||||
|
the model picker current even when the remote manifest fetch is bot-gated
|
||||||
|
or the Portal hiccups.
|
||||||
|
|
||||||
|
Reads the shipped manifest, validates it against the schema, and writes it
|
||||||
|
to ``~/.hermes/cache/model_catalog.json`` via the same atomic writer the
|
||||||
|
network path uses. Returns ``True`` on success, ``False`` if the file is
|
||||||
|
missing, malformed, or fails validation (caller should treat a ``False``
|
||||||
|
as non-fatal — the network fetch path still applies on the next picker
|
||||||
|
open).
|
||||||
|
"""
|
||||||
|
src = Path(project_root) / "website" / "static" / "api" / "model-catalog.json"
|
||||||
|
try:
|
||||||
|
with open(src, encoding="utf-8") as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
except (OSError, json.JSONDecodeError) as exc:
|
||||||
|
logger.debug("model catalog seed from checkout skipped (%s): %s", src, exc)
|
||||||
|
return False
|
||||||
|
if not _validate_manifest(data):
|
||||||
|
logger.debug("model catalog seed from checkout skipped: invalid manifest at %s", src)
|
||||||
|
return False
|
||||||
|
_write_disk_cache(data)
|
||||||
|
reset_cache() # drop the in-process copy so the next read picks up the seed
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def reset_cache() -> None:
|
def reset_cache() -> None:
|
||||||
"""Clear the in-process cache. Used by tests and ``hermes model --refresh``."""
|
"""Clear the in-process cache. Used by tests and ``hermes model --refresh``."""
|
||||||
global _catalog_cache, _catalog_cache_source_mtime
|
global _catalog_cache, _catalog_cache_source_mtime
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user