| 1 | from pathlib import Path |
| 2 | |
| 3 | import yaml |
| 4 | |
| 5 | PROJECT_ROOT = Path(__file__).resolve().parents[1] |
| 6 | |
| 7 | |
| 8 | def test_onboarding_contains_unified_provider_step(): |
| 9 | html = (PROJECT_ROOT / "plugins/_onboarding/webui/onboarding.html").read_text(encoding="utf-8") |
| 10 | store = (PROJECT_ROOT / "plugins/_onboarding/webui/onboarding-store.js").read_text(encoding="utf-8") |
| 11 | gate_store = (PROJECT_ROOT / "webui/components/chat/model-gate-store.js").read_text(encoding="utf-8") |
| 12 | |
| 13 | assert "Cloud" in html |
| 14 | assert "Local" in html |
| 15 | assert "Welcome to Agent Zero" in html |
| 16 | assert "Choose your AI provider" in store |
| 17 | |
| 18 | # The illustrated Cloud/Local path screen is gone: one merged provider step. |
| 19 | assert "Choose how to use AI models in Agent Zero" not in html + store |
| 20 | assert "path-card" not in html |
| 21 | assert "cloud-card.webp" not in html |
| 22 | assert "local-card.webp" not in html |
| 23 | assert not (PROJECT_ROOT / "plugins/_onboarding/webui/assets/cloud-card.webp").exists() |
| 24 | assert not (PROJECT_ROOT / "plugins/_onboarding/webui/assets/local-card.webp").exists() |
| 25 | assert "choosePath" not in html + store + gate_store |
| 26 | assert "isStep('connect')" in html |
| 27 | assert "isStep('path')" not in html |
| 28 | assert "isStep('cloud')" not in html |
| 29 | assert "isStep('local')" not in html |
| 30 | |
| 31 | # Cloud/Account/Local segmented switch on the merged step. |
| 32 | assert "mode-switch" in html |
| 33 | assert "setProviderMode('cloud')" in html |
| 34 | assert "setProviderMode('account')" in html |
| 35 | assert "setProviderMode('local')" in html |
| 36 | assert "providerMode === 'cloud'" in html |
| 37 | assert "providerMode === 'account'" in html |
| 38 | assert "providerMode === 'local'" in html |
| 39 | assert html.index("setProviderMode('cloud')") < html.index("setProviderMode('account')") < html.index("setProviderMode('local')") |
| 40 | assert '{ step: "connect", label: "Choose provider" }' in store |
| 41 | |
| 42 | # Cloud pane lists every provider directly and only mentions API keys; |
| 43 | # accounts live in their own pane. |
| 44 | assert "Connect with an API key" in html |
| 45 | assert "cloudProviders()" in html |
| 46 | assert "Click here if you don't see your provider" not in html |
| 47 | assert "moreCloudOpen" not in html + store |
| 48 | assert "API key or account connection" not in html |
| 49 | |
| 50 | # The chat model gate presets the mode before the modal opens (no flash). |
| 51 | assert "presetMode" in store |
| 52 | assert "onboardingStore.presetMode = this.choice;" in gate_store |
| 53 | assert "applyOnboardingChoice" not in gate_store |
| 54 | |
| 55 | assert "oauthProviderCards()" in html + store |
| 56 | assert "selectOAuthProvider(provider.provider_id)" in html |
| 57 | assert "Connect one or more account-backed providers." not in html |
| 58 | assert "Connect ChatGPT/Codex Account" not in html |
| 59 | assert "Main model" in html |
| 60 | assert "Refresh model list" in html |
| 61 | assert "Search or enter Utility Model" in html |
| 62 | |
| 63 | # The utility model is an intentional choice: no "same as main" shortcut. |
| 64 | assert "Use same as Main Model" not in html |
| 65 | assert "sameAsMain" not in html + store |
| 66 | assert "Advanced Settings" in html |
| 67 | assert "selectedProviderName() + ' Docs'" in html |
| 68 | assert "openSelectedProviderDocs" in html + store |
| 69 | assert "Connect account" in html + store |
| 70 | assert "oauthAccountActionLabel" in html + store |
| 71 | assert "connectOAuth" in html + store |
| 72 | assert "submitOAuthManualCallback" in html + store |
| 73 | assert 'const OAUTH_START_API = "/plugins/_oauth/start_login";' in store |
| 74 | assert "/plugins/_oauth/start_device_login" not in store |
| 75 | assert "provider-description" not in html |
| 76 | assert "main-model-field" in html |
| 77 | assert "wide-inline-field" in html |
| 78 | assert "utility-panel" in html |
| 79 | assert "showApiBaseField()" in html + store |
| 80 | assert "localGuidance()" in html + store |
| 81 | |
| 82 | |
| 83 | def test_onboarding_closes_before_opening_advanced_model_settings(): |
| 84 | store = (PROJECT_ROOT / "plugins/_onboarding/webui/onboarding-store.js").read_text( |
| 85 | encoding="utf-8" |
| 86 | ) |
| 87 | method = store.split(" async openAdvancedSettings() {", 1)[1].split("\n },", 1)[0] |
| 88 | |
| 89 | close_call = "await window.closeModal?.();" |
| 90 | open_call = "await modelConfigStore.openPresetEditor(" |
| 91 | |
| 92 | assert close_call in method |
| 93 | assert open_call in method |
| 94 | assert method.index(close_call) < method.index(open_call) |
| 95 | |
| 96 | |
| 97 | def test_onboarding_provider_grid_names_are_present_in_metadata(): |
| 98 | provider_yaml = (PROJECT_ROOT / "conf/model_providers.yaml").read_text(encoding="utf-8") |
| 99 | provider_ui = (PROJECT_ROOT / "plugins/_onboarding/webui/onboarding-providers.js").read_text(encoding="utf-8") |
| 100 | model_metadata = (PROJECT_ROOT / "plugins/_model_config/provider_metadata.yaml").read_text(encoding="utf-8") |
| 101 | |
| 102 | assert "TOP_CLOUD_PROVIDER_IDS" in provider_ui |
| 103 | assert '"venice"' in provider_ui |
| 104 | assert '"xai"' in provider_ui |
| 105 | assert '"nebius"' in provider_ui |
| 106 | assert provider_ui.index('"venice"') < provider_ui.index('"zai"') |
| 107 | assert provider_ui.index('"xai"') > provider_ui.index("MORE_CLOUD_PROVIDER_IDS") |
| 108 | assert 'name: "Google"' in provider_ui |
| 109 | assert 'docs_url: "https://openrouter.ai/workspaces/default/keys"' in provider_ui |
| 110 | assert 'docs_url: "https://ai.google.dev/gemini-api/docs/api-key"' in provider_ui |
| 111 | assert 'docs_url: "https://docs.venice.ai/guides/getting-started/generating-api-key"' in provider_ui |
| 112 | assert 'docs_url: "https://docs.tokenfactory.nebius.com/api-reference/introduction"' in provider_ui |
| 113 | assert 'docs_url: "https://lmstudio.ai/docs/developer/core/authentication"' in provider_ui |
| 114 | assert 'logo: "/plugins/_onboarding/webui/assets/provider-logos/llama-cpp.svg"' in provider_ui |
| 115 | assert 'docs_url: "https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md"' in provider_ui |
| 116 | assert 'default_api_base: "http://host.docker.internal:8080/v1"' in provider_ui |
| 117 | assert 'logo: "/plugins/_onboarding/webui/assets/provider-logos/omlx.svg"' in provider_ui |
| 118 | assert 'docs_url: "https://github.com/jundot/omlx#readme"' in provider_ui |
| 119 | assert 'default_api_base: "http://host.docker.internal:8000/v1"' in provider_ui |
| 120 | assert 'logo: "/plugins/_onboarding/webui/assets/provider-logos/vllm.svg"' in provider_ui |
| 121 | assert 'docs_url: "https://docs.vllm.ai/en/stable/serving/online_serving/"' in provider_ui |
| 122 | assert 'logo: "/plugins/_onboarding/webui/assets/provider-logos/cerebras.svg"' in provider_ui |
| 123 | assert 'docs_url: "https://inference-docs.cerebras.ai/quickstart"' in provider_ui |
| 124 | assert 'default_chat_model: "gpt-oss-120b"' in provider_ui |
| 125 | assert 'docs_url: ""' in provider_ui |
| 126 | assert "api_key_mode: none" in model_metadata |
| 127 | assert "api_key_mode: optional" in model_metadata |
| 128 | assert "Ollama Cloud" in provider_yaml |
| 129 | assert "https://ollama.com/v1" in provider_yaml |
| 130 | assert "llama.cpp" in provider_yaml |
| 131 | assert "http://host.docker.internal:8080/v1" in provider_yaml |
| 132 | assert "oMLX" in provider_yaml |
| 133 | assert "http://host.docker.internal:8000/v1" in provider_yaml |
| 134 | assert "vLLM" in provider_yaml |
| 135 | assert "Nebius Token Factory" in provider_yaml |
| 136 | assert "https://api.tokenfactory.nebius.com/v1" in provider_yaml |
| 137 | assert not (PROJECT_ROOT / "plugins/_model_config/conf/model_providers.yaml").exists() |
| 138 | |
| 139 | for name in [ |
| 140 | "OpenRouter", |
| 141 | "Agent Zero API", |
| 142 | "OpenAI", |
| 143 | "Anthropic", |
| 144 | "Google", |
| 145 | "DeepSeek", |
| 146 | "xAI", |
| 147 | "Moonshot AI", |
| 148 | "Nebius Token Factory", |
| 149 | "Z.AI", |
| 150 | "Mistral AI", |
| 151 | "Azure OpenAI", |
| 152 | "llama.cpp", |
| 153 | "oMLX", |
| 154 | "vLLM", |
| 155 | ]: |
| 156 | assert name in provider_yaml + provider_ui |
| 157 | |
| 158 | for name in ["Ollama Cloud", "AWS Bedrock", "Groq", "Cerebras"]: |
| 159 | assert name in provider_yaml + provider_ui |
| 160 | |
| 161 | for forbidden in [ |
| 162 | "onboarding_category", |
| 163 | "onboarding_rank", |
| 164 | "short_description", |
| 165 | "setup_url", |
| 166 | "api_key_url", |
| 167 | "docs_url", |
| 168 | "logo:", |
| 169 | "api_key_mode", |
| 170 | "model_list_autoload", |
| 171 | "default_chat_model", |
| 172 | "default_utility_model", |
| 173 | "default_api_base", |
| 174 | ]: |
| 175 | assert forbidden not in provider_yaml |
| 176 | |
| 177 | for logo in [ |
| 178 | "deepseek.svg", |
| 179 | "google-gemini.svg", |
| 180 | "groq.svg", |
| 181 | "sambanova.png", |
| 182 | "cometapi.ico", |
| 183 | "cerebras.svg", |
| 184 | "github-copilot.svg", |
| 185 | "llama-cpp.svg", |
| 186 | "zai-logo.svg", |
| 187 | "omlx.svg", |
| 188 | "vllm.svg", |
| 189 | ]: |
| 190 | assert logo in provider_ui |
| 191 | |
| 192 | assert (PROJECT_ROOT / "plugins/_onboarding/webui/assets/provider-logos/llama-cpp.svg").exists() |
| 193 | assert (PROJECT_ROOT / "plugins/_onboarding/webui/assets/provider-logos/cerebras.svg").exists() |
| 194 | assert (PROJECT_ROOT / "plugins/_onboarding/webui/assets/provider-logos/omlx.svg").exists() |
| 195 | assert (PROJECT_ROOT / "plugins/_onboarding/webui/assets/provider-logos/vllm.svg").exists() |
| 196 | |
| 197 | |
| 198 | def test_nebius_provider_config_uses_openai_compatible_token_factory_endpoint(): |
| 199 | provider_path = PROJECT_ROOT / "conf/model_providers.yaml" |
| 200 | provider_config = yaml.safe_load(provider_path.read_text(encoding="utf-8")) |
| 201 | nebius = provider_config["chat"]["nebius"] |
| 202 | |
| 203 | assert nebius["name"] == "Nebius Token Factory" |
| 204 | assert nebius["litellm_provider"] == "openai" |
| 205 | assert nebius["kwargs"]["api_base"] == "https://api.tokenfactory.nebius.com/v1" |
| 206 | assert nebius["models_list"]["endpoint_url"] == "/models" |
| 207 | assert "api_key_mode" not in nebius |
| 208 | |
| 209 | |
| 210 | def test_nvidia_nim_is_a_first_class_provider(): |
| 211 | provider_config = yaml.safe_load( |
| 212 | (PROJECT_ROOT / "conf/model_providers.yaml").read_text(encoding="utf-8") |
| 213 | ) |
| 214 | provider_ui = (PROJECT_ROOT / "plugins/_onboarding/webui/onboarding-providers.js").read_text( |
| 215 | encoding="utf-8" |
| 216 | ) |
| 217 | |
| 218 | for model_type in ("chat", "embedding"): |
| 219 | provider = provider_config[model_type]["nvidia_nim"] |
| 220 | assert provider["litellm_provider"] == "nvidia_nim" |
| 221 | assert provider["models_list"]["endpoint_url"] == ( |
| 222 | "https://integrate.api.nvidia.com/v1/models" |
| 223 | ) |
| 224 | |
| 225 | assert '"nvidia_nim"' in provider_ui |
| 226 | assert "https://docs.api.nvidia.com/nim/reference/llm-apis" in provider_ui |
| 227 | |
| 228 | |
| 229 | def test_discovery_auto_modal_extension_contains_required_guards(): |
| 230 | content = (PROJECT_ROOT / "plugins/_discovery/extensions/webui/initFw_end/auto-modal.js").read_text(encoding="utf-8") |
| 231 | |
| 232 | assert "auto_modal_path" in content |
| 233 | assert "chat-created" in content |
| 234 | assert "modalAlreadyOpen" in content |
| 235 | assert "discovery_auto_modal_closed" in content |
| 236 | assert "auto_modal_surfaces" in content |
| 237 | |
| 238 | |
| 239 | def test_onboarding_success_filters_oauth_discovery_cards(): |
| 240 | content = ( |
| 241 | PROJECT_ROOT |
| 242 | / "plugins/_discovery/extensions/webui/onboarding-success-end/discovery-cards.html" |
| 243 | ).read_text(encoding="utf-8") |
| 244 | |
| 245 | assert "discovery-codex-oauth" in content |
| 246 | assert "discovery-oauth-accounts" in content |
| 247 | assert "includes(card.id)" in content |