fix(tui-gateway): scope the hot-reload context pin to the configured default route

The Desktop config-save path re-applied model.context_length to any session, undoing the switch path's scoping; both live paths now share config_context_length_for_runtime.
This commit is contained in:
teknium1
2026-09-20 15:32:08 -07:00
committed by Teknium
parent 36b6efc297
commit 54315c15e2
3 changed files with 35 additions and 12 deletions

View File

@@ -163,6 +163,7 @@ def _neutral_session(**compression_ctor):
agent = SimpleNamespace(
model="unset-test-model",
provider="",
base_url="",
context_compressor=compressor,
compression_enabled=True,
compression_idle_compact_after_seconds=0,

View File

@@ -24,6 +24,7 @@ def _session(config_context_length=PIN):
agent = SimpleNamespace(
model="model-a",
provider="custom:acme",
base_url="http://127.0.0.1:8123/v1",
_config_context_length=config_context_length,
context_compressor=compressor,
compression_enabled=True,
@@ -55,8 +56,28 @@ def test_changed_context_length_refreshes_the_agent_pin_too(monkeypatch):
"""A new ``model.context_length`` must land on both cached copies."""
session, compressor = _session()
_sync(monkeypatch, session, {"model": {"context_length": 400_000}, "compression": {}})
_sync(monkeypatch, session, {"model": {"default": "model-a", "provider": "custom:acme",
"context_length": 400_000}, "compression": {}})
assert compressor._config_context_length == 400_000
assert compressor.context_length == 400_000
assert session["agent"]._config_context_length == 400_000
def test_hot_reload_does_not_pin_a_session_on_another_route(monkeypatch):
"""The pin describes the configured default route; a session that switched elsewhere must not
inherit it on the next config save (same scoping as the switch path)."""
session, compressor = _session(config_context_length=None)
session["agent"].base_url = "https://openrouter.ai/api/v1"
session["agent"].provider = "openrouter"
monkeypatch.setattr(cc_mod, "get_model_context_length", lambda *a, **k: 256_000)
_sync(monkeypatch, session, {
"model": {"default": "model-b", "provider": "custom:acme", "base_url": "http://127.0.0.1:8123/v1",
"context_length": PIN},
"custom_providers": [{"name": "acme", "base_url": "http://127.0.0.1:8123/v1", "models": {}}],
"compression": {},
})
assert compressor._config_context_length is None
assert session["agent"]._config_context_length is None

View File

@@ -85,7 +85,7 @@ def _apply_live_compression_config(agent: Any, cfg: dict | None) -> None:
cfg = cfg if isinstance(cfg, dict) else {}
compression = cfg.get("compression") if isinstance(cfg.get("compression"), dict) else {}
model_cfg = cfg.get("model") if isinstance(cfg.get("model"), dict) else {}
from agent.agent_init import set_config_context_length
from agent.agent_init import config_context_length_for_runtime, set_config_context_length
enabled_raw = compression.get("enabled", True)
agent.compression_enabled = enabled_raw if isinstance(enabled_raw, bool) else str(enabled_raw).lower() in {"true", "1", "yes"}
agent.codex_responses_native_compaction = is_truthy_value(compression.get("codex_responses_native", False))
@@ -137,16 +137,17 @@ def _apply_live_compression_config(agent: Any, cfg: dict | None) -> None:
cc.threshold_percent = cc._effective_threshold_percent(cc.context_length, base)
except Exception:
cc.threshold_percent = pct
raw_ctx = model_cfg.get("context_length")
if raw_ctx is not None:
with contextlib.suppress(TypeError, ValueError):
if (new_ctx := int(raw_ctx)) > 0:
# Both cached copies: the compressor's (its own re-resolution) and the agent's
# (switch/fallback + every display surface). Writing one left the other stale, so the
# session showed a pinned ceiling while compressing against a different window (#116467).
set_config_context_length(agent, new_ctx)
with contextlib.suppress(Exception):
cc.context_length = new_ctx
# Same scoping rule as construction and the switch path: the pin describes the configured default
# route, so a session that /model-switched elsewhere must not have it re-applied on a config save
# (None = absent, invalid, or scoped out).
new_ctx = config_context_length_for_runtime(agent, cfg)
if new_ctx is not None:
# Both cached copies: the compressor's (its own re-resolution) and the agent's
# (switch/fallback + every display surface). Writing one left the other stale, so the
# session showed a pinned ceiling while compressing against a different window (#116467).
set_config_context_length(agent, new_ctx)
with contextlib.suppress(Exception):
cc.context_length = new_ctx
elif getattr(cc, "_config_context_length", None) is not None:
# model.context_length removed: drop the override and force re-inference from model metadata on
# next access (construction's deferred resolution); re-applies the small-context floor too.