diff --git a/nanobot/webui/settings_api.py b/nanobot/webui/settings_api.py index e6831e3bc..315c7e2f3 100644 --- a/nanobot/webui/settings_api.py +++ b/nanobot/webui/settings_api.py @@ -269,7 +269,11 @@ def update_model_call_order( config_path: Path | None = None, ) -> dict[str, Any]: config = _load_settings_config(config_path) - if models.update_model_call_order(config, query): + if models.update_model_call_order( + config, + query, + oauth_status=_oauth_provider_status, + ): _save_settings_config(config, config_path) return settings_payload(config_path=config_path) diff --git a/nanobot/webui/settings_models.py b/nanobot/webui/settings_models.py index a6ef7b624..d819bb632 100644 --- a/nanobot/webui/settings_models.py +++ b/nanobot/webui/settings_models.py @@ -1312,7 +1312,12 @@ def update_model_configuration( return changed -def update_model_call_order(config: Config, query: QueryParams) -> bool: +def update_model_call_order( + config: Config, + query: QueryParams, + *, + oauth_status: OAuthStatusReader, +) -> bool: raw_order = query_first_alias(query, "order", "presetNames") if raw_order is None: raise WebUISettingsError("model call order is required") @@ -1331,15 +1336,16 @@ def update_model_call_order(config: Config, query: QueryParams) -> bool: raise WebUISettingsError("model call order must contain at least one preset") normalized_order = [cast(str, name).strip() for name in cast(list[object], order)] + unknown = [name for name in normalized_order if name not in config.model_presets] + if unknown: + raise WebUISettingsError(f"unknown model preset: {unknown[0]}") + _, editable = _model_call_order_state(config) - if not editable: + if not editable and _legacy_model_configuration_migratable(config, oauth_status): raise WebUISettingsError( "convert the existing model configuration to presets first", status=409, ) - unknown = [name for name in normalized_order if name not in config.model_presets] - if unknown: - raise WebUISettingsError(f"unknown model preset: {unknown[0]}") defaults = config.agents.defaults fallback_models: list[FallbackCandidate] = list(normalized_order[1:]) diff --git a/tests/webui/test_settings_api.py b/tests/webui/test_settings_api.py index 790f9016e..a6b8c29e6 100644 --- a/tests/webui/test_settings_api.py +++ b/tests/webui/test_settings_api.py @@ -592,7 +592,7 @@ def test_update_model_call_order_sets_primary_and_fallbacks( assert saved.agents.defaults.fallback_models == ["primary"] -def test_update_model_call_order_requires_named_primary( +def test_update_model_call_order_activates_existing_named_preset( tmp_path, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -602,11 +602,36 @@ def test_update_model_call_order_requires_named_primary( save_config(config, config_path) monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) + payload = update_model_call_order({"order": [json.dumps(["backup"])]}) + + assert payload["model_call_order"] == ["backup"] + assert payload["model_call_order_editable"] is True + assert load_config(config_path).agents.defaults.model_preset == "backup" + + +def test_update_model_call_order_preserves_real_legacy_configuration( + tmp_path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + config_path = tmp_path / "config.json" + config = Config() + config.providers.openai.api_key = "sk-test" + config.agents.defaults.model = "openai/gpt-4o" + config.agents.defaults.provider = "openai" + config.model_presets["backup"] = ModelPresetConfig( + model="openai/gpt-4.1-mini", + provider="openai", + ) + save_config(config, config_path) + monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) + with pytest.raises(WebUISettingsError) as error: update_model_call_order({"order": [json.dumps(["backup"])]}) assert error.value.status == 409 - assert load_config(config_path).agents.defaults.model_preset is None + saved = load_config(config_path) + assert saved.agents.defaults.model_preset is None + assert saved.agents.defaults.model == "openai/gpt-4o" def test_migrate_model_configurations_preserves_legacy_chain(