mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
fix(delegation): route saves tell a null extra key from an omitted one
_clean_model_option read extra route keys with entry.get(), so an explicit null and an omitted key looked the same: a declared key sent as null was dropped, and a stored extra key holding null survived a save that left it out (the editor's way of turning a setting off). Both loops now decide on whether the key is present: null is kept as a value, and an omitted key is removed.
This commit is contained in:
1 parent
fcb3085b9b
commit
27f4a045da
2 files changed
+26
-3
No files matched your search
@@ -190,9 +190,9 @@ def _clean_model_option(index: int, entry: dict, stored: dict[str, dict],
|
||||
if entry.get(field) not in (None, "", [], {}):
|
||||
row[field] = entry[field]
|
||||
# The backend's own keys (DelegationProvider.route_keys, e.g. a ``daybreak`` opt-in) are kept as sent; the
|
||||
# backend validates them. Absent = the editor turned it off.
|
||||
# backend validates them. Presence decides, not the value: null is a value, absent = the editor turned it off.
|
||||
for field in declared:
|
||||
if field not in row and field not in ROUTE_KEYS and entry.get(field) is not None:
|
||||
if field not in row and field not in ROUTE_KEYS and field in entry:
|
||||
row[field] = entry[field]
|
||||
# A route the renderer came from (a rename names it in original_name; once that save landed, it is stored
|
||||
# under its new name).
|
||||
@@ -200,7 +200,8 @@ def _clean_model_option(index: int, entry: dict, stored: dict[str, dict],
|
||||
# Any other key the stored route carries (hand-written, or declared by a backend that is not loaded now)
|
||||
# survives an untouched round-trip, so a save never silently drops a setting; a key nobody stored is dropped.
|
||||
for field, value in source.items():
|
||||
if field not in row and field not in ROUTE_KEYS and field != "api_key" and entry.get(field) == value:
|
||||
if (field not in row and field not in ROUTE_KEYS and field != "api_key"
|
||||
and field in entry and entry[field] == value):
|
||||
row[field] = value
|
||||
key = entry.get("api_key")
|
||||
if is_redacted_credential_preview(key):
|
||||
|
||||
@@ -215,6 +215,28 @@ def test_an_undeclared_key_survives_only_an_untouched_round_trip(client, homes):
|
||||
assert "legacy" not in _delegation(work)["backends"]["model_map"]["options"][0]
|
||||
|
||||
|
||||
def test_a_null_extra_key_is_a_value_and_leaving_it_out_removes_it(client, homes):
|
||||
_root, work = homes
|
||||
route = {"name": "deep", "description": "Careful", "provider": "anthropic", "model": "m-deep"}
|
||||
|
||||
def save(**extra):
|
||||
resp = client.put("/api/delegation/backends/model_map?profile=work", json={"options": [{**route, **extra}]})
|
||||
assert resp.status_code == 200, resp.text
|
||||
return _delegation(work)["backends"]["model_map"]["options"][0]
|
||||
|
||||
# A declared key sent as null is kept as sent; leaving it out removes it.
|
||||
assert save(daybreak=None) == {**route, "daybreak": None}
|
||||
assert "daybreak" not in save()
|
||||
|
||||
# A stored null in an undeclared key survives a save that sends it back, and goes when the save leaves it out.
|
||||
block = _delegation(work)
|
||||
block["backends"]["model_map"]["options"][0]["legacy"] = None # hand-written in config.yaml
|
||||
(work / "config.yaml").write_text(yaml.safe_dump({"plugins": {"enabled": ["routes"]}, "delegation": block}),
|
||||
encoding="utf-8")
|
||||
assert save(legacy=None) == {**route, "legacy": None}
|
||||
assert "legacy" not in save()
|
||||
|
||||
|
||||
def test_profile_routes_only_name_existing_profiles(client, homes):
|
||||
_root, work = homes
|
||||
before = (work / "config.yaml").read_text(encoding="utf-8")
|
||||
|
||||
Reference in new issue
Block a user