From da43de72b832f904cabf203397442f00dd4ead7c Mon Sep 17 00:00:00 2001 From: b3nw Date: Tue, 30 Jun 2026 00:44:44 +0000 Subject: [PATCH] fix(model): repair bare custom-provider suffix on resolver slow path (#5225) Mirror the #5128 fast-path suffix repair when profile context is present but requested_provider is omitted, which async delegation completion can trigger via process_wakeup. Co-authored-by: b3nw --- api/routes.py | 18 ++++++++ ...est_issue5127_process_wakeup_bare_model.py | 41 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/api/routes.py b/api/routes.py index 77c48254..08e7a358 100644 --- a/api/routes.py +++ b/api/routes.py @@ -5578,6 +5578,24 @@ def _resolve_compatible_session_model_state( _target = _profile_default or default_model return _target, profile_provider, True + # Async server-side continuations (for example delegate_task completion + # re-entry) can arrive here with profile context but without a usable + # requested_provider, bypassing the fast-path custom-provider repair + # above. If the profile's configured custom-provider default is a + # slash-qualified model whose suffix matches the bare session model, + # repair back to the profile default before the provider call (#5225). + if ( + "/" not in model + and _profile_default + and "/" in _profile_default + and _profile_default.rsplit("/", 1)[-1] == model + and ( + _profile_provider_normalized == "custom" + or str(profile_provider).startswith("custom:") + ) + ): + return _profile_default, profile_provider, True + return model, profile_provider, False if not model: diff --git a/tests/test_issue5127_process_wakeup_bare_model.py b/tests/test_issue5127_process_wakeup_bare_model.py index 40ed5f3a..849c5eae 100644 --- a/tests/test_issue5127_process_wakeup_bare_model.py +++ b/tests/test_issue5127_process_wakeup_bare_model.py @@ -82,6 +82,47 @@ class TestIssue5127CustomProviderBareSuffixRepair: assert mock_catalog.call_count == 0 assert result == ("x-ai/grok-composer-2.5-fast", "custom:my-proxy", False) + def test_slow_path_repairs_bare_suffix_to_profile_default_for_custom_provider(self): + """Regression #5225: async continuations may arrive without usable model_provider.""" + from api.routes import _resolve_compatible_session_model_state + + with patch("api.routes.get_available_models") as mock_catalog: + mock_catalog.return_value = { + "active_provider": "openrouter", + "default_model": "openai/gpt-5.5", + "groups": [], + } + result = _resolve_compatible_session_model_state( + "grok-composer-2.5-fast", + None, + profile_provider="custom:my-proxy", + profile_default_model="x-ai/grok-composer-2.5-fast", + prefer_cached_catalog=True, + ) + + assert mock_catalog.call_count == 1 + assert result == ("x-ai/grok-composer-2.5-fast", "custom:my-proxy", True) + + def test_slow_path_does_not_rewrite_unrelated_bare_model_for_custom_provider(self): + from api.routes import _resolve_compatible_session_model_state + + with patch("api.routes.get_available_models") as mock_catalog: + mock_catalog.return_value = { + "active_provider": "openrouter", + "default_model": "openai/gpt-5.5", + "groups": [], + } + result = _resolve_compatible_session_model_state( + "other-model", + None, + profile_provider="custom:my-proxy", + profile_default_model="x-ai/grok-composer-2.5-fast", + prefer_cached_catalog=True, + ) + + assert mock_catalog.call_count == 1 + assert result == ("other-model", "custom:my-proxy", False) + def test_openai_codex_stale_openai_slash_still_uses_slow_path(self): """Regression: openai/... under openai-codex must not take bare fast return.""" from api.routes import _resolve_compatible_session_model_state