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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user