fix(cli): honor target_model when resolving custom providers
resolve_runtime_provider() documents target_model as the explicit model override for mid-session switches and auxiliary slots, but the custom provider path (_resolve_named_custom_runtime) never received it and silently substituted the provider's configured default_model instead. This made auxiliary slots such as auxiliary.background_review silently run the provider's default model rather than the configured one — e.g. an ocx-proxy slot configured for gemini-flash actually executed cursor/claude-sonnet-5, hitting upstream rate limits. Pass target_model through to the custom runtime resolver and prefer it over the provider's default model in both the pooled and non-pooled credential paths.
This commit is contained in:
@@ -1167,7 +1167,8 @@ def _resolve_named_custom_runtime(
|
||||
if pool_result:
|
||||
# Propagate the model name even when using pooled credentials —
|
||||
# the pool doesn't know about the custom_providers model field.
|
||||
model_name = custom_provider.get("model")
|
||||
# An explicit ``target_model`` wins (same rule as the non-pool path).
|
||||
model_name = target_model or custom_provider.get("model")
|
||||
if model_name:
|
||||
pool_result["model"] = model_name
|
||||
if isinstance(custom_provider.get("max_output_tokens"), int):
|
||||
@@ -1228,7 +1229,13 @@ def _resolve_named_custom_runtime(
|
||||
}
|
||||
# Propagate the model name so callers can override self.model when the
|
||||
# provider name differs from the actual model string the API expects.
|
||||
if custom_provider.get("model"):
|
||||
# An explicit ``target_model`` wins over the provider's configured
|
||||
# default (regression: auxiliary slots / background-review resolve a
|
||||
# concrete model for a custom provider and must not silently fall back
|
||||
# to ``default_model``).
|
||||
if target_model:
|
||||
result["model"] = target_model
|
||||
elif custom_provider.get("model"):
|
||||
result["model"] = custom_provider["model"]
|
||||
if isinstance(custom_provider.get("max_output_tokens"), int):
|
||||
result["max_output_tokens"] = custom_provider["max_output_tokens"]
|
||||
|
||||
@@ -1677,3 +1677,73 @@ def test_resolve_runtime_provider_opencode_free_missing_env_still_resolves(monke
|
||||
assert resolved["provider"] == "opencode-free"
|
||||
assert resolved["api_key"] == "opencode-zen-free-keyless"
|
||||
assert resolved["base_url"] == "https://opencode.ai/zen/v1"
|
||||
|
||||
|
||||
def test_custom_provider_explicit_target_model_wins(monkeypatch):
|
||||
"""An explicit target_model must not be silently replaced by the custom
|
||||
provider's configured default model (regression: auxiliary slots such as
|
||||
background-review resolve a concrete model and got default_model instead)."""
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_get_named_custom_provider",
|
||||
lambda p: {
|
||||
"name": "myproxy",
|
||||
"base_url": "http://127.0.0.1:10100/v1",
|
||||
"api_key": "no-key-required",
|
||||
"model": "default-model",
|
||||
},
|
||||
)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="myproxy", target_model="myproxy/gemini-flash")
|
||||
|
||||
assert resolved is not None
|
||||
assert resolved["provider"] == "custom"
|
||||
assert resolved["model"] == "myproxy/gemini-flash"
|
||||
assert resolved["base_url"] == "http://127.0.0.1:10100/v1"
|
||||
|
||||
|
||||
def test_custom_provider_without_target_model_keeps_default(monkeypatch):
|
||||
"""No target_model -> the provider's configured model is preserved."""
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_get_named_custom_provider",
|
||||
lambda p: {
|
||||
"name": "myproxy",
|
||||
"base_url": "http://127.0.0.1:10100/v1",
|
||||
"api_key": "no-key-required",
|
||||
"model": "default-model",
|
||||
},
|
||||
)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="myproxy")
|
||||
|
||||
assert resolved is not None
|
||||
assert resolved["model"] == "default-model"
|
||||
|
||||
|
||||
def test_custom_provider_pool_target_model_wins(monkeypatch):
|
||||
"""Pooled-credentials path also honors target_model over the default."""
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_try_resolve_from_custom_pool",
|
||||
lambda *a, **k: {
|
||||
"provider": "custom",
|
||||
"api_key": "pooled-key",
|
||||
"base_url": "http://127.0.0.1:10100/v1",
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_get_named_custom_provider",
|
||||
lambda p: {
|
||||
"name": "myproxy",
|
||||
"base_url": "http://127.0.0.1:10100/v1",
|
||||
"api_key": "no-key-required",
|
||||
"model": "default-model",
|
||||
},
|
||||
)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="myproxy", target_model="myproxy/gemini-flash")
|
||||
|
||||
assert resolved is not None
|
||||
assert resolved["model"] == "myproxy/gemini-flash"
|
||||
|
||||
Reference in New Issue
Block a user