diff --git a/llm_connect/openrouter.py b/llm_connect/openrouter.py index 9a7c283..623c5a8 100644 --- a/llm_connect/openrouter.py +++ b/llm_connect/openrouter.py @@ -37,6 +37,14 @@ class OpenRouterAdapter(LLMAdapter): max_retries: Optional[int] = None, ): self._config = config or LLMConfig() + # Track whether the model was explicitly supplied (constructor or + # LLMConfig). Comparing self._model to _DEFAULT_MODEL is not enough — + # callers who pass --model anthropic/claude-sonnet-4 happen to match + # the default and would otherwise be misrouted to RunConfig.model_name + # (which defaults to "gpt-4" — quietly sending every call to OpenAI's + # gpt-4 model, which is what broke the activity-core CUST-WP-0045 + # canary on 2026-06-02). + self._explicit_model = model is not None or self._config.model is not None self._model = model or self._config.model or _DEFAULT_MODEL self._api_base = (api_base or self._config.api_base).rstrip("/") self._system_prompt = system_prompt @@ -56,7 +64,14 @@ class OpenRouterAdapter(LLMAdapter): def execute_prompt(self, prompt: str, config: RunConfig) -> LLMResponse: self._preflight_budget(config) - model = self._model if self._model != _DEFAULT_MODEL else (config.model_name or self._model) + # Explicit constructor/LLMConfig model wins; only fall back to the + # per-call RunConfig.model_name when the adapter wasn't told what to + # use. RunConfig.model_name defaults to "gpt-4", so falling back + # unconditionally would silently misroute callers. + if self._explicit_model: + model = self._model + else: + model = config.model_name or self._model messages: list[Dict[str, str]] = [] if self._system_prompt: