From a8180bc51f9c2c900c6a570dc7df34193e793295 Mon Sep 17 00:00:00 2001 From: webtecnica Date: Tue, 22 Sep 2026 22:54:12 -0300 Subject: [PATCH] fix(context-length): guard provider ownership on global model.base_url backfill (#7535) A session on a built-in registry provider (e.g. opencode-go, endpoint in PROVIDER_REGISTRY, empty base_url by design) had its empty base_url slot backfilled with the global model.base_url owned by a *different* provider (custom:ark). The context-length resolver then probed the wrong endpoint's /models catalog and fell back to the generic 128K catalog entry for a 1M-context model, while the on-disk session and SSE stream carried 1,000,000. Only fill the global model.base_url slot when the session provider IS the configured model.provider owner, mirroring the ownership predicate used for model_cfg's API key in _context_length_config_api_key_for_provider. --- api/routes.py | 12 ++- ...e3256_context_length_default_only_guard.py | 73 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/api/routes.py b/api/routes.py index 5f5893ba..407abbac 100644 --- a/api/routes.py +++ b/api/routes.py @@ -7183,7 +7183,17 @@ def _context_length_lookup_inputs_for_model( if not effective_provider: effective_provider = _canonical_context_provider(model_cfg.get("provider")) if not effective_base_url: - effective_base_url = str(model_cfg.get("base_url") or "").strip() + # #7535: the global model.base_url may only fill an empty slot when + # the session provider IS the configured model.provider owner + # (mirror the ownership predicate used for model_cfg's API key in + # _context_length_config_api_key_for_provider). A built-in registry + # provider (empty base_url by design) must keep the slot empty so + # the registry endpoint resolves instead of another provider's URL. + _model_cfg_provider = _canonical_context_provider(model_cfg.get("provider")) + if not effective_provider or _providers_match_for_context( + _model_cfg_provider, effective_provider + ): + effective_base_url = str(model_cfg.get("base_url") or "").strip() custom_providers = cfg.get("custom_providers") if isinstance(cfg, dict) else None if not isinstance(custom_providers, list): diff --git a/tests/test_issue3256_context_length_default_only_guard.py b/tests/test_issue3256_context_length_default_only_guard.py index ea9ac9da..34f4b50f 100644 --- a/tests/test_issue3256_context_length_default_only_guard.py +++ b/tests/test_issue3256_context_length_default_only_guard.py @@ -631,3 +631,76 @@ def test_sse_done_path_broadened_to_any_model_window_mismatch(): assert "_model_matches_configured_default as _mmcd_cc" not in _STREAMING_SRC, ( "the old default-only save matcher (_mmcd_cc) must be removed (#4618)" ) + + +# --- #7535: provider-ownership guard on the global model.base_url backfill --- +# Same family as #3256: a global value scoped to its owner must not leak onto +# sessions owned by a different provider. + + +def test_builtin_provider_lookup_does_not_inherit_other_providers_base_url(): + """#7535: a session on a built-in registry provider (whose base_url is empty + by design because the endpoint lives in PROVIDER_REGISTRY, not config.yaml) + must NOT inherit the global ``model.base_url`` owned by a different (custom) + provider. The unguarded backfill handed the custom endpoint to the + context-length resolver, which probed the wrong ``/models`` catalog and fell + back to the generic 128K catalog entry for a 1M-context model, while the + on-disk session and SSE stream both carried 1,000,000.""" + from api.routes import _context_length_lookup_inputs_for_model + + ark_base = "https://ark.cn-beijing.volces.com/api/coding/v3" + cfg = { + "model": { + "provider": "custom:ark", + "base_url": ark_base, + "default": "ark-code-latest", + "context_length": 1000000, + }, + "custom_providers": [ + { + "name": "ark", + "base_url": ark_base, + "api_key": "ark-key", + "model": "ark-code-latest", + } + ], + } + + lookup = _context_length_lookup_inputs_for_model( + "@opencode-go:deepseek-v4.1-flash", + "opencode-go", + cfg=cfg, + ) + + assert lookup.provider == "opencode-go" + assert lookup.base_url == "", ( + "the global model.base_url belongs to custom:ark; the opencode-go " + "registry slot must stay empty so PROVIDER_REGISTRY's endpoint " + "resolves the context window (#7535)" + ) + + +def test_same_provider_session_keeps_global_base_url_backfill(): + """#7535 control (compat): when the session provider IS the configured + owner of ``model.base_url``, the backfill behavior is preserved.""" + from api.routes import _context_length_lookup_inputs_for_model + + ark_base = "https://ark.cn-beijing.volces.com/api/coding/v3" + cfg = { + "model": { + "provider": "custom:ark", + "base_url": ark_base, + "default": "ark-code-latest", + }, + } + + lookup = _context_length_lookup_inputs_for_model( + "ark-code-latest", + "custom:ark", + cfg=cfg, + ) + + assert lookup.provider == "custom:ark" + assert lookup.base_url == ark_base, ( + "same-owner sessions must keep the model.base_url backfill (#7535 compat)" + )