Skip to content
Closed
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
36 changes: 32 additions & 4 deletions tests/test_tui_gateway_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1233,18 +1233,19 @@ def update_session_cwd(self, *_args):


def test_stored_session_runtime_overrides_skips_bare_billing_provider():
"""A bare billing bucket ("custom"/"auto"/"openrouter") must not be restored as the
provider identity on resume. A custom endpoint that never used `/model` persists only
"""A bare billing bucket ("custom"/"auto") must not be restored as the provider
identity on resume. A custom endpoint that never used `/model` persists only
`billing_provider="custom"`; restoring that broke `session.resume` with "No LLM provider
configured" (agent_init treats it as non-routable). A real provider, or an explicit
configured" (agent_init treats it as non-routable). ``"openrouter"`` is NOT a bare bucket
β€” it is a fully routable provider; see #57588. A real provider, or an explicit
`model_config.provider`, is still restored.
"""
# Bare "custom" bucket, no explicit model_config.provider: no provider override restored.
ov = server._stored_session_runtime_overrides({"model": "my-model", "billing_provider": "custom"})
assert "provider_override" not in ov
assert ov["model_override"]["provider"] is None

for bare in ("auto", "openrouter", "custom"):
for bare in ("auto", "custom"):
ov = server._stored_session_runtime_overrides({"model": "m", "billing_provider": bare})
assert "provider_override" not in ov

Expand All @@ -1261,6 +1262,33 @@ def test_stored_session_runtime_overrides_skips_bare_billing_provider():
assert ov["model_override"]["provider"] == "custom:myendpoint"


def test_openrouter_session_resume_restores_provider():
"""OpenRouter is a fully routable provider β€” sessions that used OpenRouter must
restore the "openrouter" provider override on resume, not fall through to whatever
the current global model is. (#57588)
"""
# OpenRouter session with no explicit model_config.provider (the common case
# for sessions that never used /model): billing_provider="openrouter" should
# be restored as the provider override.
ov = server._stored_session_runtime_overrides(
{"model": "anthropic/claude-opus-4.8", "billing_provider": "openrouter"}
)
assert ov["provider_override"] == "openrouter"
assert ov["model_override"]["provider"] == "openrouter"
assert ov["model_override"]["model"] == "anthropic/claude-opus-4.8"

# When an explicit model_config.provider exists, it takes precedence over
# billing_provider (this path was already correct).
ov = server._stored_session_runtime_overrides(
{
"model": "anthropic/claude-opus-4.8",
"billing_provider": "openrouter",
"model_config": {"provider": "openrouter", "base_url": "https://openrouter.ai/api/v1"},
}
)
assert ov["provider_override"] == "openrouter"


def test_persist_live_session_runtime_preserves_resume_metadata(monkeypatch):
updates = {}

Expand Down
7 changes: 6 additions & 1 deletion tui_gateway/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2008,7 +2008,12 @@ def _resolve_startup_runtime() -> tuple[str, str | None]:

# Bare billing buckets are not routable provider identities (kept in parity with the
# provider gate in agent_init). Restoring one as a session provider override breaks resume.
_BARE_BILLING_PROVIDERS = {"auto", "openrouter", "custom"}
# ``openrouter`` is deliberately excluded β€” it is a fully routable provider with its own
# API key and base_url. Sessions that used OpenRouter store
# ``billing_provider="openrouter"``; dropping it forces resume to the current global
# model (e.g. a custom endpoint), which is the wrong provider for the stored model.
# See #57588.
_BARE_BILLING_PROVIDERS = {"auto", "custom"}


def _stored_session_runtime_overrides(row: dict | None) -> dict:
Expand Down
Loading