feat(delegation): backends declare their own route keys; route saves keep them

A delegation backend's routes can carry keys of its own (e.g. a
per-route `daybreak` opt-in), but PUT /api/delegation/backends/model_map
copied only core's route fields, so any Desktop save silently dropped
them and the route kept running without the setting.

- DelegationProvider.route_keys (default empty): keys of the backend's
  own route entries that settings editors may write and must keep.
- BackendInfo / GET /api/delegation/routing list each backend's
  declared route_keys (blank, non-string and repeated entries dropped;
  a broken plugin property declares nothing instead of hiding the
  backend).
- The model_map save keeps declared keys as sent (the backend validates
  them) and any other key the stored route already carries when the save
  sends it back unchanged; a key nobody stored is still dropped.
This commit is contained in:
unsupportedpastels authored and Mark S. committed 2026-11-05 15:44:58 -05:00
1 parent ab84eb7aa0
commit 494b618eb9
4 files changed
+92 -9

No files matched your search

+14 -1
View File
@@ -35,13 +35,26 @@ class BackendInfo:
description: str
available: bool
reason: str = ""
#: The backend's own route-entry keys settings editors may write (``DelegationProvider.route_keys``).
route_keys: tuple[str, ...] = ()
def _route_keys(provider: Any) -> tuple[str, ...]:
# A plugin's answer (possibly a list over the plugin host's wire); anything malformed declares nothing.
try:
raw = provider.route_keys
except Exception: # health: allow BLE001 -- a broken plugin property must not hide its backend
return ()
if not isinstance(raw, (list, tuple)):
return ()
return tuple(dict.fromkeys(k.strip() for k in raw if isinstance(k, str) and k.strip()))
def _info(name: str) -> BackendInfo:
provider, reason = backend_status(name)
if provider is None:
return BackendInfo(name, name, "", False, reason)
return BackendInfo(name, provider.display_name, provider.summary, not reason, reason)
return BackendInfo(name, provider.display_name, provider.summary, not reason, reason, _route_keys(provider))
def list_backends() -> list[BackendInfo]:
+7
View File
@@ -78,6 +78,13 @@ class DelegationProvider(ProviderBase):
"""One user-facing line for backend pickers (``/delegation``); never shown to the model."""
return ""
@property
def route_keys(self) -> tuple[str, ...]:
"""Keys of this backend's own route entries, beyond core's route fields, that settings editors may write and
must keep (e.g. a per-route ``daybreak`` opt-in). Editors offer a control only when its key is listed here,
so an older plugin never gets a setting it would ignore. The backend validates their values itself."""
return ()
def is_available(self) -> bool:
"""Whether this provider can run here. MUST NOT make network calls. False fails delegation closed: a
``delegate_task`` call under this backend is a tool error naming it, never a run on another route."""
+19 -6
View File
@@ -150,12 +150,13 @@ def _check_default(default: Optional[str], names: list[str], what: str) -> Optio
return value or None
def _clean_model_option(index: int, entry: dict, stored: dict[str, dict]) -> dict[str, Any]:
def _clean_model_option(index: int, entry: dict, stored: dict[str, dict],
declared: tuple[str, ...] = ()) -> dict[str, Any]:
from agent.delegation_provider import ROUTE_KEYS
from agent.fast_mode import STATIC_TIERS, parse_exact_service_tier
from hermes_constants import VALID_REASONING_EFFORTS, parse_reasoning_effort
row = {key: _text(entry, key) for key in _OPTION_TEXT_KEYS if _text(entry, key)}
row: dict[str, Any] = {key: _text(entry, key) for key in _OPTION_TEXT_KEYS if _text(entry, key)}
label = f"Route {index + 1}"
if not row.get("name"):
raise _bad_request(f"{label} needs a name.")
@@ -188,11 +189,22 @@ def _clean_model_option(index: int, entry: dict, stored: dict[str, dict]) -> dic
for field in sorted(ROUTE_KEYS - validated):
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.
for field in declared:
if field not in row and field not in ROUTE_KEYS and entry.get(field) is not None:
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).
source = stored.get(_text(entry, "original_name")) or stored.get(row["name"]) or {}
# 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:
row[field] = value
key = entry.get("api_key")
if is_redacted_credential_preview(key):
# The renderer only ever saw a preview: keep the stored key of the route it came from (a rename names
# it in original_name; once that save landed, the route is stored under its new name).
source = stored.get(_text(entry, "original_name")) or stored.get(row["name"]) or {}
# The renderer only ever saw a preview: keep the stored key of the route it came from.
key = source.get("api_key")
if isinstance(key, str) and key.strip():
row["api_key"] = key.strip()
@@ -217,7 +229,8 @@ async def save_model_routes(body: DelegationModelMapUpdate, profile: Optional[st
"""Replace ``delegation.backends.model_map`` (``options`` + ``default``) after validating every route."""
def _build(current: dict) -> dict:
stored = {_text(o, "name"): o for o in current.get("options") or [] if isinstance(o, dict)}
options = [_clean_model_option(i, dict(o), stored) for i, o in enumerate(body.options)]
declared = next((tuple(b.get("route_keys") or ()) for b in _registered_backends() if b["name"] == MODEL_MAP), ())
options = [_clean_model_option(i, dict(o), stored, declared) for i, o in enumerate(body.options)]
names = [o["name"] for o in options]
duplicate = next((n for n in names if names.count(n) > 1), None)
if duplicate:
@@ -16,19 +16,24 @@ from agent.delegation_provider import DelegationProvider
class _Routes(DelegationProvider):
def __init__(self, name):
def __init__(self, name, route_keys=()):
self._name = name
self._route_keys = route_keys
@property
def name(self):
return self._name
@property
def route_keys(self):
return self._route_keys
def route_task(self, task, config):
return dict(config)
def register(ctx):
ctx.register_delegation_provider(_Routes("model_map"))
ctx.register_delegation_provider(_Routes("model_map", ("daybreak", "", 3, "daybreak")))
ctx.register_delegation_provider(_Routes("profile_routes"))
'''
@@ -165,6 +170,51 @@ def test_saving_model_routes_replaces_the_block_and_keeps_the_stored_key(client,
assert "backends" not in _delegation(root)
def test_routing_lists_each_backends_declared_route_keys(client):
backends = {b["name"]: b["route_keys"] for b in client.get("/api/delegation/routing?profile=work").json()["backends"]}
# Blank, non-string and repeated declarations are dropped; a backend that declares nothing lists nothing.
assert backends == {"default": [], "model_map": ["daybreak"], "profile_routes": []}
def test_a_declared_route_key_round_trips_and_removing_it_turns_it_off(client, homes):
_root, work = homes
route = {"name": "daybreak", "description": "Sol, reduced guardrails", "provider": "openai-codex",
"model": "gpt-6-sol", "service_tier": "fast"}
resp = client.put("/api/delegation/backends/model_map?profile=work", json={"options": [{**route, "daybreak": True}]})
assert resp.status_code == 200, resp.text
assert _delegation(work)["backends"]["model_map"]["options"][0]["daybreak"] is True
# Re-saving what the renderer was shown keeps it (an unrelated edit must never silently drop it).
shown = client.get("/api/delegation/routing?profile=work").json()["model_map"]["options"]
resp = client.put("/api/delegation/backends/model_map?profile=work",
json={"options": [{**shown[0], "original_name": "daybreak", "reasoning_effort": "high"}]})
assert resp.status_code == 200, resp.text
assert _delegation(work)["backends"]["model_map"]["options"][0]["daybreak"] is True
# The editor turns it off by leaving the key out.
assert client.put("/api/delegation/backends/model_map?profile=work", json={"options": [route]}).status_code == 200
assert "daybreak" not in _delegation(work)["backends"]["model_map"]["options"][0]
def test_an_undeclared_key_survives_only_an_untouched_round_trip(client, homes):
_root, work = homes
stored = {"name": "deep", "description": "Careful", "provider": "anthropic", "model": "m-deep", "legacy": {"a": 1}}
assert client.put("/api/delegation/backends/model_map?profile=work", json={"options": [stored]}).status_code == 200
# A key nobody stored (no backend declares it) is dropped, however the renderer sends it.
assert "legacy" not in _delegation(work)["backends"]["model_map"]["options"][0]
block = _delegation(work)
block["backends"]["model_map"]["options"][0]["legacy"] = {"a": 1} # hand-written in config.yaml
(work / "config.yaml").write_text(yaml.safe_dump({"plugins": {"enabled": ["routes"]}, "delegation": block}),
encoding="utf-8")
shown = client.get("/api/delegation/routing?profile=work").json()["model_map"]["options"][0]
resp = client.put("/api/delegation/backends/model_map?profile=work",
json={"options": [{**shown, "original_name": "deep", "model": "m-deeper"}]})
assert resp.status_code == 200, resp.text
assert _delegation(work)["backends"]["model_map"]["options"][0]["legacy"] == {"a": 1}
changed = {**shown, "legacy": {"a": 2}}
assert client.put("/api/delegation/backends/model_map?profile=work", json={"options": [changed]}).status_code == 200
assert "legacy" not in _delegation(work)["backends"]["model_map"]["options"][0]
def test_profile_routes_only_name_existing_profiles(client, homes):
_root, work = homes
before = (work / "config.yaml").read_text(encoding="utf-8")