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:
王雪帆
2026-08-04 23:20:35 +08:00
committed by Teknium
parent 6d48fbed1b
commit c4871226f1
2 changed files with 79 additions and 2 deletions
+9 -2
View File
@@ -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"