Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ blockfile, plugin_yaml, port = sys.argv[1], sys.argv[2], sys.argv[3]
with open(plugin_yaml) as fh:
manifest = yaml.safe_load(fh) or {}

models = manifest.get("models") or ["auto"]
models = manifest.get("models")
if not isinstance(models, list) or not models:
# Keep in sync with backend.STATIC_MODELS. setup.sh cannot import
# backend.py (it depends on `requests`), so the fallback catalog is
# duplicated here deliberately. A missing, empty, or non-list `models:`
# in a custom plugin.yaml is treated as "use the built-in catalog", the
# same way backend.list_models() falls back to STATIC_MODELS.
models = ["auto", "claude-sonnet-4.5", "claude-sonnet-4", "claude-haiku-4.5"]
# Coerce all model identifiers to str — YAML 1.1 may parse numeric-looking
# values as int/float (e.g. "4.0" as float). The model catalog must be
# exact strings; backend.list_models() already does this coercion for the
Expand Down
44 changes: 41 additions & 3 deletions tests/test_coverage_gaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,16 @@ def _make_hermes_cli_mock(initial_config=None):
import types

saved = [copy.deepcopy(initial_config or {})]
save_calls = []
fake_cfg = types.ModuleType("hermes_cli.config")
fake_cfg.load_config = lambda: copy.deepcopy(saved[0])
fake_cfg.save_config = lambda c: saved.__setitem__(0, copy.deepcopy(c))

def _save_config(config):
save_calls.append(copy.deepcopy(config))
saved[0] = copy.deepcopy(config)

fake_cfg.save_config = _save_config
fake_cfg.save_calls = save_calls
fake_hermes = types.ModuleType("hermes_cli")
fake_hermes.config = fake_cfg
return fake_hermes, fake_cfg, saved
Expand Down Expand Up @@ -256,8 +263,39 @@ def test_provider_register_noop_when_already_current(monkeypatch):
monkeypatch.setitem(sys.modules, "hermes_cli.config", fake_cfg)
result = _provider.register_provider(8088)
assert result is True
# No write: save_config must not have been called, so the stored config
# is unchanged (comment-preserving idempotency).
# No write: save_config must not have been called at all, otherwise the
# rebuild would round-trip config.yaml and strip its comments. Assert the
# call count, not just the stored value (which would be equal either way).
assert fake_cfg.save_calls == []
assert saved[0] == cfg


def test_provider_register_noop_cross_writer_key(monkeypatch):
"""The redaction sentinel ("***") and the canonical keyless value
("no-key-required") must compare equal, so a config written by setup.sh
(api_key: no-key-required) is not rewritten on first plugin load."""
import sys

import _provider
from _provider import _declared_models

models = [str(m) for m in _declared_models()]
existing = {
"name": "AWS Builder",
"transport": "openai_chat",
"base_url": "http://localhost:8088/v1",
"model": models[0],
"discover_models": False,
"api_key": "no-key-required", # as setup.sh writes it
"models": {m: {} for m in models},
}
cfg = {"providers": {_provider.PROVIDER_SLUG: existing}}
fake_hermes, fake_cfg, saved = _make_hermes_cli_mock(cfg)
monkeypatch.setitem(sys.modules, "hermes_cli", fake_hermes)
monkeypatch.setitem(sys.modules, "hermes_cli.config", fake_cfg)
result = _provider.register_provider(8088)
assert result is True
assert fake_cfg.save_calls == []
assert saved[0] == cfg


Expand Down
16 changes: 16 additions & 0 deletions tests/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,19 @@ def test_setup_default_model_keeps_auto_when_declared(tmp_path, monkeypatch):
"""When `auto` is declared first (the shipped default), model stays auto."""
out = _generate("models:\n - auto\n - claude-sonnet-4.5\n", tmp_path, monkeypatch)
assert 'model: "auto"' in out


def test_setup_models_fallback_matches_static_models(tmp_path, monkeypatch):
"""A manifest without `models:` must fall back to the full built-in
catalog (matching backend.STATIC_MODELS), not a single `auto`."""
out = _generate("name: AWS Builder\n", tmp_path, monkeypatch)
assert "claude-sonnet-4.5" in out
assert "claude-haiku-4.5" in out


def test_setup_models_scalar_is_not_iterated(tmp_path, monkeypatch):
"""A scalar `models: auto` must not be iterated character-by-character
(which would produce `model: "a"`); it falls back to the built-in catalog."""
out = _generate("models: auto\n", tmp_path, monkeypatch)
assert 'model: "auto"' in out
assert 'model: "a"' not in out
Loading