vibe/tests/core/test_config_resolution.py
Mathias Gesbert 3f8487f761
v2.14.0 (#743)
Co-authored-by: Alexis Tacnet <alexis@mistral.ai>
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Lucas Marandat <31749711+lucasmrdt@users.noreply.github.com>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: p.vezia <166131032+le-codeur-rapide@users.noreply.github.com>
Co-authored-by: Hiba Chaabnia <Hiba-Chaabnia@users.noreply.github.com>
Co-authored-by: Nikhil Bhima <nikhilbhima@users.noreply.github.com>
Co-authored-by: Nkipohcs <Nkipohcs@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-06-04 18:26:35 +02:00

1506 lines
53 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
import ssl
import tomllib
from typing import Literal, TypedDict, Unpack
from unittest.mock import MagicMock, patch
import pytest
import tomli_w
from tests.conftest import build_test_vibe_config
from vibe.core.config import ModelConfig, ProviderConfig, VibeConfig
from vibe.core.config._settings import (
DEFAULT_MISTRAL_BROWSER_AUTH_API_BASE_URL,
DEFAULT_MISTRAL_BROWSER_AUTH_BASE_URL,
DEFAULT_PROVIDERS,
)
from vibe.core.config.harness_files import (
HarnessFilesManager,
init_harness_files_manager,
reset_harness_files_manager,
)
from vibe.core.paths import VIBE_HOME
from vibe.core.trusted_folders import trusted_folders_manager
from vibe.core.types import Backend
from vibe.core.utils.http import build_ssl_context, configure_ssl_context
from vibe.setup.onboarding.context import OnboardingContext
class _ProviderConfigOverrides(TypedDict, total=False):
api_key_env_var: str
browser_auth_base_url: str | None
browser_auth_api_base_url: str | None
api_style: str
backend: Backend
reasoning_field_name: str
project_id: str
region: str
class _ModelConfigOverrides(TypedDict, total=False):
temperature: float
input_price: float
output_price: float
thinking: Literal["off", "low", "medium", "high"]
auto_compact_threshold: int
def _default_provider(name: str) -> ProviderConfig:
return next(provider for provider in DEFAULT_PROVIDERS if provider.name == name)
def _custom_provider(**overrides: Unpack[_ProviderConfigOverrides]) -> ProviderConfig:
return ProviderConfig(
name="custom-provider", api_base="https://custom.example/v1", **overrides
)
def _custom_model(**overrides: Unpack[_ModelConfigOverrides]) -> ModelConfig:
return ModelConfig(
name="custom-model",
provider="custom-provider",
alias="custom-model",
**overrides,
)
def _custom_provider_payload(**overrides: object) -> dict[str, object]:
payload: dict[str, object] = {
"name": "custom-provider",
"api_base": "https://custom.example/v1",
"api_key_env_var": "CUSTOM_API_KEY",
}
payload.update(overrides)
return payload
def _custom_model_payload(**overrides: object) -> dict[str, object]:
payload: dict[str, object] = {
"name": "custom-model",
"provider": "custom-provider",
"alias": "custom-model",
}
payload.update(overrides)
return payload
class TestResolveConfigFile:
def test_resolves_local_config_when_exists_and_folder_is_trusted(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
local_config_dir = tmp_path / ".vibe"
local_config_dir.mkdir()
local_config = local_config_dir / "config.toml"
local_config.write_text('active_model = "test"', encoding="utf-8")
monkeypatch.setattr(trusted_folders_manager, "is_trusted", lambda _: True)
reset_harness_files_manager()
init_harness_files_manager("user", "project")
from vibe.core.config.harness_files import get_harness_files_manager
mgr = get_harness_files_manager()
resolved = mgr.config_file
assert resolved is not None
assert resolved == local_config
assert resolved.is_file()
assert resolved.read_text(encoding="utf-8") == 'active_model = "test"'
def test_resolves_global_config_when_folder_is_not_trusted(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
local_config_dir = tmp_path / ".vibe"
local_config_dir.mkdir()
local_config = local_config_dir / "config.toml"
local_config.write_text('active_model = "test"', encoding="utf-8")
reset_harness_files_manager()
init_harness_files_manager("user", "project")
from vibe.core.config.harness_files import get_harness_files_manager
mgr = get_harness_files_manager()
assert mgr.config_file == VIBE_HOME.path / "config.toml"
def test_falls_back_to_global_config_when_local_missing(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
# Ensure no local config exists
assert not (tmp_path / ".vibe" / "config.toml").exists()
reset_harness_files_manager()
init_harness_files_manager("user", "project")
from vibe.core.config.harness_files import get_harness_files_manager
mgr = get_harness_files_manager()
assert mgr.config_file == VIBE_HOME.path / "config.toml"
def test_respects_vibe_home_env_var(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
assert VIBE_HOME.path != tmp_path
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
assert VIBE_HOME.path == tmp_path
def test_returns_none_when_no_sources(self) -> None:
mgr = HarnessFilesManager(sources=())
assert mgr.config_file is None
def test_user_only_returns_global_config(self) -> None:
mgr = HarnessFilesManager(sources=("user",))
assert mgr.config_file == VIBE_HOME.path / "config.toml"
class TestSaveUpdates:
def test_merges_nested_tool_updates_without_materializing_defaults(
self, config_dir: Path
) -> None:
config_file = config_dir / "config.toml"
data = {"tools": {"bash": {"default_timeout": 600}}}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
VibeConfig.save_updates({"tools": {"bash": {"permission": "always"}}})
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result == {
"tools": {"bash": {"default_timeout": 600, "permission": "always"}}
}
def test_replaces_lists_instead_of_unioning_them(self, config_dir: Path) -> None:
config_file = config_dir / "config.toml"
data = {"installed_agents": ["lean", "other"]}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
VibeConfig.save_updates({"installed_agents": ["lean"]})
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["installed_agents"] == ["lean"]
def test_prunes_nested_none_values_before_writing(self, config_dir: Path) -> None:
config_file = config_dir / "config.toml"
data = {"tools": {"bash": {"default_timeout": 600, "permission": "always"}}}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
VibeConfig.save_updates({"tools": {"bash": {"permission": None}}})
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result == {"tools": {"bash": {"default_timeout": 600}}}
class TestSystemTrustStoreConfig:
def test_load_configures_ssl_context_from_toml(self, config_dir: Path) -> None:
config_file = config_dir / "config.toml"
with config_file.open("rb") as f:
data = tomllib.load(f)
data["enable_system_trust_store"] = True
with config_file.open("wb") as f:
tomli_w.dump(data, f)
with patch("vibe.core.config._settings.configure_ssl_context") as configure:
config = VibeConfig.load()
assert config.enable_system_trust_store is True
configure.assert_called_once_with(enable_system_trust_store=True)
def test_load_configures_ssl_context_from_env(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_ENABLE_SYSTEM_TRUST_STORE", "true")
with patch("vibe.core.config._settings.configure_ssl_context") as configure:
config = VibeConfig.load()
assert config.enable_system_trust_store is True
configure.assert_called_once_with(enable_system_trust_store=True)
def test_load_clears_cached_ssl_context_when_setting_changes(
self, config_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.delenv("SSL_CERT_FILE", raising=False)
monkeypatch.delenv("SSL_CERT_DIR", raising=False)
configure_ssl_context(enable_system_trust_store=False)
build_ssl_context.cache_clear()
try:
config_file = config_dir / "config.toml"
with config_file.open("rb") as f:
data = tomllib.load(f)
data["enable_system_trust_store"] = False
with config_file.open("wb") as f:
tomli_w.dump(data, f)
default_ctx = MagicMock(spec=ssl.SSLContext)
with patch(
"vibe.core.utils.http.ssl.create_default_context",
return_value=default_ctx,
):
VibeConfig.load()
assert build_ssl_context() is default_ctx
data["enable_system_trust_store"] = True
with config_file.open("wb") as f:
tomli_w.dump(data, f)
truststore_ctx = MagicMock(spec=ssl.SSLContext)
with patch(
"vibe.core.utils.http.truststore.SSLContext",
return_value=truststore_ctx,
):
VibeConfig.load()
assert build_ssl_context() is truststore_ctx
finally:
configure_ssl_context(enable_system_trust_store=False)
build_ssl_context.cache_clear()
class TestSetThinking:
def test_persists_thinking_to_toml(self, config_dir: Path) -> None:
config_file = config_dir / "config.toml"
data = {
"active_model": "my-model",
"models": [
{"name": "my-model", "provider": "mistral", "alias": "my-model"}
],
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
cfg = VibeConfig.load()
cfg.set_thinking("high")
reloaded = VibeConfig.load()
assert reloaded.get_active_model().thinking == "high"
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["models"][0]["thinking"] == "high"
def test_persists_thinking_for_correct_model(self, config_dir: Path) -> None:
config_file = config_dir / "config.toml"
data = {
"active_model": "model-b",
"models": [
{"name": "model-a", "provider": "mistral", "alias": "model-a"},
{"name": "model-b", "provider": "mistral", "alias": "model-b"},
],
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
cfg = VibeConfig.load()
cfg.set_thinking("max")
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["models"][0].get("thinking") is None
assert result["models"][1]["thinking"] == "max"
def test_preserves_supports_images_when_materializing_defaults(
self, config_dir: Path
) -> None:
config_file = config_dir / "config.toml"
data = {"active_model": "mistral-medium-3.5"}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
cfg = VibeConfig.load()
cfg.set_thinking("low")
reloaded = VibeConfig.load()
active = reloaded.get_active_model()
assert active.alias == "mistral-medium-3.5"
assert active.thinking == "low"
assert active.supports_images is True
with config_file.open("rb") as f:
result = tomllib.load(f)
active_entry = result["models"][0]
assert active_entry["supports_images"] is True
assert "temperature" not in active_entry
assert "input_price" not in active_entry
assert "output_price" not in active_entry
assert "auto_compact_threshold" not in active_entry
assert "supports_images" not in result["models"][1]
class TestMigrateLeavesFindInBashAllowlist:
def test_keeps_find_in_config_file(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {"tools": {"bash": {"allowlist": ["echo", "ls"]}}}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["tools"]["bash"]["allowlist"] == ["echo", "find", "ls"]
def test_noop_when_find_already_present(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {"tools": {"bash": {"allowlist": ["echo", "find", "ls"]}}}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["tools"]["bash"]["allowlist"] == ["echo", "find", "ls"]
def test_noop_when_no_bash_tools_section(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {"active_model": "test"}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert "tools" not in result
class TestMigrateStripsBashAllowlistWildcardSuffix:
def test_strips_trailing_wildcard_from_bash_allowlist_entries(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"tools": {"bash": {"allowlist": ["git commit *", "npm install *", "echo"]}}
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["tools"]["bash"]["allowlist"] == [
"echo",
"find",
"git commit",
"npm install",
]
def test_dedupes_when_stripping_collides_with_existing_entry(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"tools": {"bash": {"allowlist": ["git commit *", "git commit", "find"]}}
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["tools"]["bash"]["allowlist"] == ["find", "git commit"]
def test_noop_when_no_wildcard_suffix_present(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {"tools": {"bash": {"allowlist": ["echo", "find", "ls"]}}}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["tools"]["bash"]["allowlist"] == ["echo", "find", "ls"]
class TestMigrateMistralVibeCliLatestDefaults:
def test_updates_alias_temperature_and_thinking_for_default_model(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "devstral-2",
"temperature": 0.2,
"thinking": "off",
}
]
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["models"][0]["alias"] == "mistral-medium-3.5"
assert result["models"][0]["temperature"] == 1.0
assert result["models"][0]["input_price"] == 1.5
assert result["models"][0]["output_price"] == 7.5
assert result["models"][0]["thinking"] == "high"
def test_updates_active_model_when_devstral_2(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"active_model": "devstral-2",
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "devstral-2",
}
],
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["active_model"] == "mistral-medium-3.5"
def test_adds_temperature_and_thinking_when_missing(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "devstral-2",
}
]
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["models"][0]["alias"] == "mistral-medium-3.5"
assert result["models"][0]["temperature"] == 1.0
assert result["models"][0]["input_price"] == 1.5
assert result["models"][0]["output_price"] == 7.5
assert result["models"][0]["thinking"] == "high"
def test_skips_model_with_customized_alias(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "my-custom-alias",
"temperature": 0.2,
"thinking": "off",
}
]
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["models"][0]["alias"] == "my-custom-alias"
assert result["models"][0]["temperature"] == 0.2
assert result["models"][0]["thinking"] == "off"
def test_does_not_touch_other_models(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "devstral-2",
},
{
"name": "other-model",
"provider": "mistral",
"alias": "devstral-2-clone",
"temperature": 0.5,
"thinking": "low",
},
]
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["models"][1]["alias"] == "devstral-2-clone"
assert result["models"][1]["temperature"] == 0.5
assert result["models"][1]["thinking"] == "low"
def test_noop_when_no_models_section(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {"theme": "dark"}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result == {"theme": "dark"}
def test_idempotent_when_already_migrated(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"active_model": "mistral-medium-3.5",
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "mistral-medium-3.5",
"temperature": 1.0,
"input_price": 1.5,
"output_price": 7.5,
"thinking": "high",
}
],
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["active_model"] == "mistral-medium-3.5"
assert result["models"][0]["alias"] == "mistral-medium-3.5"
assert result["models"][0]["temperature"] == 1.0
assert result["models"][0]["input_price"] == 1.5
assert result["models"][0]["output_price"] == 7.5
assert result["models"][0]["thinking"] == "high"
def test_migrates_model_and_active_model_together(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"active_model": "devstral-2",
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "devstral-2",
}
],
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["active_model"] == "mistral-medium-3.5"
assert result["models"][0]["alias"] == "mistral-medium-3.5"
assert result["models"][0]["temperature"] == 1.0
assert result["models"][0]["input_price"] == 1.5
assert result["models"][0]["output_price"] == 7.5
assert result["models"][0]["thinking"] == "high"
def test_backfills_supports_images_on_existing_mistral_medium_entry(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"active_model": "mistral-medium-3.5",
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "mistral-medium-3.5",
"temperature": 1.0,
"input_price": 1.5,
"output_price": 7.5,
"thinking": "high",
}
],
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["models"][0]["supports_images"] is True
def test_preserves_explicit_supports_images_false(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "mistral-medium-3.5",
"supports_images": False,
}
]
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["models"][0]["supports_images"] is False
class TestAutoCompactThresholdFallback:
def test_model_without_explicit_threshold_inherits_global(self) -> None:
model = ModelConfig(name="m", provider="p", alias="m")
cfg = build_test_vibe_config(
auto_compact_threshold=42_000, models=[model], active_model="m"
)
assert cfg.get_active_model().auto_compact_threshold == 42_000
def test_model_with_explicit_threshold_keeps_own_value(self) -> None:
model = ModelConfig(
name="m", provider="p", alias="m", auto_compact_threshold=99_000
)
cfg = build_test_vibe_config(
auto_compact_threshold=42_000, models=[model], active_model="m"
)
assert cfg.get_active_model().auto_compact_threshold == 99_000
def test_default_global_threshold_used_when_nothing_set(self) -> None:
model = ModelConfig(name="m", provider="p", alias="m")
cfg = build_test_vibe_config(models=[model], active_model="m")
assert cfg.get_active_model().auto_compact_threshold == 200_000
def test_changed_global_threshold_propagates_on_reload(self) -> None:
model = ModelConfig(name="m", provider="p", alias="m")
cfg1 = build_test_vibe_config(
auto_compact_threshold=50_000, models=[model], active_model="m"
)
assert cfg1.get_active_model().auto_compact_threshold == 50_000
# Simulate config reload with a different global threshold
cfg2 = build_test_vibe_config(
auto_compact_threshold=75_000, models=[model], active_model="m"
)
assert cfg2.get_active_model().auto_compact_threshold == 75_000
class TestDefaultProviderConfig:
def test_default_mistral_provider_is_mistral_backend(self) -> None:
provider = _default_provider("mistral")
assert provider.name == "mistral"
assert provider.backend.value == "mistral"
assert provider.browser_auth_base_url == DEFAULT_MISTRAL_BROWSER_AUTH_BASE_URL
assert (
provider.browser_auth_api_base_url
== DEFAULT_MISTRAL_BROWSER_AUTH_API_BASE_URL
)
assert provider.supports_browser_sign_in is True
def test_non_mistral_provider_does_not_inherit_browser_auth_defaults(self) -> None:
provider = _default_provider("llamacpp")
assert provider.browser_auth_base_url is None
assert provider.browser_auth_api_base_url is None
assert provider.supports_browser_sign_in is False
class TestMistralBrowserAuthConfig:
def test_provider_browser_auth_urls_are_dumped_when_set(self) -> None:
cfg = build_test_vibe_config()
provider = cfg.get_active_provider()
dumped = cfg.model_dump(mode="json")
assert provider.browser_auth_base_url == DEFAULT_MISTRAL_BROWSER_AUTH_BASE_URL
assert (
provider.browser_auth_api_base_url
== DEFAULT_MISTRAL_BROWSER_AUTH_API_BASE_URL
)
assert (
dumped["providers"][0]["browser_auth_base_url"]
== DEFAULT_MISTRAL_BROWSER_AUTH_BASE_URL
)
assert (
dumped["providers"][0]["browser_auth_api_base_url"]
== DEFAULT_MISTRAL_BROWSER_AUTH_API_BASE_URL
)
def test_legacy_explicit_mistral_provider_backfills_browser_auth_urls_without_changing_backend(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
with config_file.open("wb") as f:
tomli_w.dump(
{
"active_model": "devstral-2",
"providers": [
{
"name": "mistral",
"api_base": "https://api.mistral.ai/v1",
"api_key_env_var": "MISTRAL_API_KEY",
"reasoning_field_name": "thoughts",
}
],
"models": [
{
"name": "mistral-vibe-cli-latest",
"provider": "mistral",
"alias": "devstral-2",
}
],
},
f,
)
reset_harness_files_manager()
init_harness_files_manager("user")
context = OnboardingContext.load()
assert context.provider.name == "mistral"
assert context.provider.backend.value == "generic"
assert context.provider.reasoning_field_name == "thoughts"
assert (
context.provider.browser_auth_base_url
== DEFAULT_MISTRAL_BROWSER_AUTH_BASE_URL
)
assert (
context.provider.browser_auth_api_base_url
== DEFAULT_MISTRAL_BROWSER_AUTH_API_BASE_URL
)
assert context.supports_browser_sign_in is True
def test_legacy_explicit_mistral_provider_backfills_only_missing_browser_auth_url(
self,
) -> None:
provider = ProviderConfig(
name="mistral",
api_base="https://api.mistral.ai/v1",
api_key_env_var="MISTRAL_API_KEY",
browser_auth_base_url="https://custom-console.example",
)
assert provider.backend.value == "generic"
assert provider.browser_auth_base_url == "https://custom-console.example"
assert (
provider.browser_auth_api_base_url
== DEFAULT_MISTRAL_BROWSER_AUTH_API_BASE_URL
)
assert provider.supports_browser_sign_in is True
def test_legacy_mistral_provider_keeps_browser_sign_in_after_round_trip(
self,
) -> None:
provider = ProviderConfig(
name="mistral",
api_base="https://api.mistral.ai/v1",
api_key_env_var="MISTRAL_API_KEY",
)
reloaded_provider = ProviderConfig.model_validate(
provider.model_dump(mode="json")
)
assert reloaded_provider.supports_browser_sign_in is True
def test_explicit_generic_mistral_provider_does_not_get_browser_auth_defaults(
self,
) -> None:
provider = ProviderConfig(
name="mistral",
api_base="https://api.mistral.ai/v1",
api_key_env_var="MISTRAL_API_KEY",
backend=Backend.GENERIC,
)
assert provider.backend.value == "generic"
assert provider.browser_auth_base_url is None
assert provider.browser_auth_api_base_url is None
assert provider.supports_browser_sign_in is False
def test_custom_provider_browser_auth_urls_round_trip(self) -> None:
custom_provider = _custom_provider(
browser_auth_base_url="https://custom.example/sign-in",
browser_auth_api_base_url="https://custom.example/api",
backend=Backend.MISTRAL,
)
cfg = build_test_vibe_config(
active_model="custom-model",
providers=[custom_provider],
models=[_custom_model()],
)
dumped = cfg.model_dump(mode="json")
reloaded_provider = ProviderConfig.model_validate(dumped["providers"][0])
assert (
reloaded_provider.browser_auth_base_url == "https://custom.example/sign-in"
)
assert (
reloaded_provider.browser_auth_api_base_url == "https://custom.example/api"
)
assert reloaded_provider.supports_browser_sign_in is True
def test_custom_mistral_provider_without_browser_auth_urls_is_not_capable(
self,
) -> None:
provider = _custom_provider(backend=Backend.MISTRAL)
assert provider.browser_auth_base_url is None
assert provider.browser_auth_api_base_url is None
assert provider.supports_browser_sign_in is False
def test_non_mistral_provider_with_browser_auth_urls_is_not_capable(self) -> None:
provider = _custom_provider(
browser_auth_base_url="https://custom.example/sign-in",
browser_auth_api_base_url="https://custom.example/api",
)
assert provider.supports_browser_sign_in is False
class TestOnboardingContextResolution:
def test_load_uses_explicit_overrides_when_harness_manager_is_uninitialized(
self,
) -> None:
reset_harness_files_manager()
context = OnboardingContext.load(
active_model="custom-model",
providers=[_custom_provider_payload()],
models=[_custom_model_payload()],
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_uses_env_overrides(self, monkeypatch: pytest.MonkeyPatch) -> None:
reset_harness_files_manager()
monkeypatch.setenv("VIBE_ACTIVE_MODEL", "env-model")
monkeypatch.setenv("VIBE_VIBE_BASE_URL", "https://env-vibe.example.com")
monkeypatch.setenv(
"VIBE_PROVIDERS",
json.dumps([
{
"name": "env-provider",
"api_base": "https://env.example/v1",
"api_key_env_var": "ENV_API_KEY",
}
]),
)
monkeypatch.setenv(
"VIBE_MODELS",
json.dumps([
{"name": "env-model", "provider": "env-provider", "alias": "env-model"}
]),
)
context = OnboardingContext.load()
assert context.provider.name == "env-provider"
assert context.provider.api_key_env_var == "ENV_API_KEY"
assert context.vibe_base_url == "https://env-vibe.example.com"
def test_load_prefers_explicit_overrides_over_toml_and_env(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
with config_file.open("wb") as file:
tomli_w.dump(
{
"active_model": "toml-model",
"providers": [
{
"name": "toml-provider",
"api_base": "https://toml.example/v1",
"api_key_env_var": "TOML_API_KEY",
}
],
"models": [
{
"name": "toml-model",
"provider": "toml-provider",
"alias": "toml-model",
}
],
},
file,
)
monkeypatch.setenv("VIBE_ACTIVE_MODEL", "env-model")
monkeypatch.setenv(
"VIBE_PROVIDERS",
json.dumps([
{
"name": "env-provider",
"api_base": "https://env.example/v1",
"api_key_env_var": "ENV_API_KEY",
}
]),
)
monkeypatch.setenv(
"VIBE_MODELS",
json.dumps([
{"name": "env-model", "provider": "env-provider", "alias": "env-model"}
]),
)
reset_harness_files_manager()
init_harness_files_manager("user")
context = OnboardingContext.load(
active_model="custom-model",
providers=[_custom_provider_payload()],
models=[_custom_model_payload()],
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_accepts_typed_provider_and_model_overrides(self) -> None:
context = OnboardingContext.load(
active_model="custom-model",
providers=[_custom_provider(api_key_env_var="CUSTOM_API_KEY")],
models=[_custom_model()],
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_preserves_explicit_overrides_when_onboarding_toml_is_invalid(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
config_file.write_text("invalid = [", encoding="utf-8")
reset_harness_files_manager()
init_harness_files_manager("user")
context = OnboardingContext.load(
active_model="custom-model",
providers=[_custom_provider_payload()],
models=[_custom_model_payload()],
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_preserves_explicit_provider_and_model_overrides_when_toml_is_invalid(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
config_file.write_text("invalid = [", encoding="utf-8")
reset_harness_files_manager()
init_harness_files_manager("user")
context = OnboardingContext.load(
providers=[_custom_provider_payload()],
models=[_custom_model_payload(alias="devstral-2")],
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_preserves_explicit_provider_override_when_toml_is_invalid(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
config_file.write_text("invalid = [", encoding="utf-8")
reset_harness_files_manager()
init_harness_files_manager("user")
context = OnboardingContext.load(
active_model="custom-model", providers=[_custom_provider_payload()]
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_preserves_explicit_overrides_when_onboarding_env_is_invalid(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
reset_harness_files_manager()
monkeypatch.setenv("VIBE_PROVIDERS", "not-json")
context = OnboardingContext.load(
active_model="custom-model",
providers=[_custom_provider_payload()],
models=[_custom_model_payload()],
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_preserves_explicit_provider_override_when_onboarding_env_is_invalid(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
reset_harness_files_manager()
monkeypatch.setenv("VIBE_MODELS", "not-json")
context = OnboardingContext.load(
active_model="custom-model", providers=[_custom_provider_payload()]
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_uses_valid_active_provider_when_unrelated_provider_is_malformed(
self,
) -> None:
context = OnboardingContext.load(
active_model="custom-model",
providers=[
_custom_provider_payload(),
{"name": "broken-provider", "backend": "mistral"},
],
models=[_custom_model_payload()],
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_uses_valid_active_provider_when_unrelated_model_is_malformed(
self,
) -> None:
context = OnboardingContext.load(
active_model="custom-model",
providers=[_custom_provider_payload()],
models=[
_custom_model_payload(),
{"name": "broken-model", "alias": "broken-model"},
],
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_uses_single_valid_provider_when_no_matching_model_exists(
self,
) -> None:
context = OnboardingContext.load(
active_model="custom-model", providers=[_custom_provider_payload()]
)
assert context.provider.name == "custom-provider"
assert context.provider.api_key_env_var == "CUSTOM_API_KEY"
def test_load_preserves_browser_sign_in_for_valid_active_provider_with_unrelated_invalid_entry(
self,
) -> None:
context = OnboardingContext.load(
active_model="custom-model",
providers=[
_custom_provider_payload(
browser_auth_base_url="https://custom.example/sign-in",
browser_auth_api_base_url="https://custom.example/api",
backend="mistral",
),
{"name": "broken-provider", "backend": "mistral"},
],
models=[_custom_model_payload()],
)
assert context.provider.name == "custom-provider"
assert context.supports_browser_sign_in is True
def test_load_falls_back_when_active_provider_is_invalid(self) -> None:
context = OnboardingContext.load(
active_model="custom-model",
providers=[{"name": "custom-provider", "backend": "mistral"}],
models=[_custom_model_payload()],
)
assert context.provider.name == "mistral"
def test_load_falls_back_when_no_valid_provider_model_pair_exists(self) -> None:
context = OnboardingContext.load(
active_model="broken-model",
providers=[{"name": "broken-provider", "backend": "mistral"}],
models=[{"name": "broken-model", "alias": "broken-model"}],
)
assert context.provider.name == "mistral"
def test_load_falls_back_when_onboarding_toml_is_invalid(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
config_file.write_text("invalid = [", encoding="utf-8")
reset_harness_files_manager()
init_harness_files_manager("user")
context = OnboardingContext.load()
assert context.provider.name == "mistral"
def test_load_falls_back_when_onboarding_env_payload_is_invalid(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
reset_harness_files_manager()
monkeypatch.setenv("VIBE_PROVIDERS", "not-json")
context = OnboardingContext.load()
assert context.provider.name == "mistral"
class TestCompactionModel:
def test_get_compaction_model_returns_active_when_unset(self) -> None:
cfg = build_test_vibe_config()
assert cfg.get_compaction_model() == cfg.get_active_model()
def test_get_compaction_model_returns_configured_model(self) -> None:
compaction = ModelConfig(
name="compact-model", provider="mistral", alias="compact"
)
cfg = build_test_vibe_config(compaction_model=compaction)
assert cfg.get_compaction_model().name == "compact-model"
def test_compaction_model_provider_must_match_active(self) -> None:
from vibe.core.config import ProviderConfig
compaction = ModelConfig(
name="compact-model", provider="other", alias="compact"
)
providers = [
ProviderConfig(
name="mistral",
api_base="https://api.mistral.ai/v1",
api_key_env_var="MISTRAL_API_KEY",
),
ProviderConfig(
name="other",
api_base="https://other.ai/v1",
api_key_env_var="MISTRAL_API_KEY",
),
]
with pytest.raises(ValueError, match="must share the same provider"):
build_test_vibe_config(compaction_model=compaction, providers=providers)
def test_compaction_model_provider_must_exist(self) -> None:
compaction = ModelConfig(
name="compact-model", provider="missing-provider", alias="compact"
)
with pytest.raises(
ValueError,
match="Provider 'missing-provider' for model 'compact-model' not found in configuration",
):
build_test_vibe_config(compaction_model=compaction)
def test_compaction_model_excluded_from_model_dump_when_none(self) -> None:
cfg = build_test_vibe_config()
dumped = cfg.model_dump()
assert "compaction_model" not in dumped
class TestGetMistralProvider:
def test_returns_active_provider_when_it_is_mistral(self) -> None:
cfg = build_test_vibe_config()
provider = cfg.get_mistral_provider()
active = cfg.get_active_provider()
assert provider is active
assert provider is not None
assert provider.backend == Backend.MISTRAL
def test_falls_back_to_first_mistral_provider_when_active_is_not_mistral(
self,
) -> None:
mistral_provider = ProviderConfig(
name="mistral",
api_base="https://api.mistral.ai/v1",
api_key_env_var="MISTRAL_API_KEY",
backend=Backend.MISTRAL,
)
llamacpp_provider = ProviderConfig(
name="llamacpp", api_base="http://127.0.0.1:8080/v1", api_key_env_var=""
)
llamacpp_model = ModelConfig(
name="llama-local", provider="llamacpp", alias="llama-local"
)
cfg = build_test_vibe_config(
providers=[llamacpp_provider, mistral_provider],
models=[llamacpp_model],
active_model="llama-local",
)
provider = cfg.get_mistral_provider()
assert provider is mistral_provider
def test_returns_none_when_no_mistral_provider(self) -> None:
llamacpp_provider = ProviderConfig(
name="llamacpp", api_base="http://127.0.0.1:8080/v1", api_key_env_var=""
)
llamacpp_model = ModelConfig(
name="llama-local", provider="llamacpp", alias="llama-local"
)
cfg = build_test_vibe_config(
providers=[llamacpp_provider],
models=[llamacpp_model],
active_model="llama-local",
)
assert cfg.get_mistral_provider() is None
def test_falls_back_to_iterating_when_active_model_is_misconfigured(self) -> None:
mistral_provider = ProviderConfig(
name="mistral",
api_base="https://api.mistral.ai/v1",
api_key_env_var="MISTRAL_API_KEY",
backend=Backend.MISTRAL,
)
llamacpp_model = ModelConfig(
name="llama-local", provider="llamacpp", alias="llama-local"
)
cfg = build_test_vibe_config(
providers=[mistral_provider],
models=[llamacpp_model],
active_model="llama-local",
)
provider = cfg.get_mistral_provider()
assert provider is mistral_provider
class TestIsActiveModelMistral:
def test_returns_true_when_active_provider_is_mistral(self) -> None:
cfg = build_test_vibe_config()
assert cfg.is_active_model_mistral() is True
def test_returns_false_when_active_provider_is_not_mistral(self) -> None:
cfg = build_test_vibe_config(
providers=[
ProviderConfig(
name="llamacpp",
api_base="http://127.0.0.1:8080/v1",
api_key_env_var="",
)
],
models=[
ModelConfig(
name="llama-local", provider="llamacpp", alias="llama-local"
)
],
active_model="llama-local",
)
assert cfg.is_active_model_mistral() is False
def test_returns_false_when_active_model_resolution_fails(self) -> None:
cfg = build_test_vibe_config(
providers=[
ProviderConfig(
name="mistral",
api_base="https://api.mistral.ai/v1",
api_key_env_var="MISTRAL_API_KEY",
backend=Backend.MISTRAL,
)
],
models=[
ModelConfig(
name="llama-local", provider="llamacpp", alias="llama-local"
)
],
active_model="llama-local",
)
assert cfg.is_active_model_mistral() is False
class TestMigrateRenamedTools:
def test_renames_read_file_and_search_replace_keys(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"tools": {
"read_file": {
"permission": "always",
"allowlist": ["src/**"],
"max_read_bytes": 64000,
},
"search_replace": {
"allowlist": ["src/**"],
"max_content_size": 100000,
"create_backup": True,
},
}
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
tools = result["tools"]
assert "read_file" not in tools
assert "search_replace" not in tools
assert tools["read"] == {
"permission": "always",
"allowlist": ["src/**"],
"max_read_bytes": 64000,
}
# Common options carry over; edit-incompatible options are dropped.
assert tools["edit"] == {"allowlist": ["src/**"]}
def test_prefers_existing_new_key_and_drops_legacy(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"tools": {
"read_file": {"permission": "always"},
"read": {"permission": "ask"},
}
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert "read_file" not in result["tools"]
assert result["tools"]["read"] == {"permission": "ask"}
def test_renames_entries_in_enabled_and_disabled_lists(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"enabled_tools": ["read_file", "grep"],
"disabled_tools": ["search_replace"],
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["enabled_tools"] == ["read", "grep"]
assert result["disabled_tools"] == ["edit"]
def test_noop_when_no_legacy_tool_names(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {"tools": {"read": {"permission": "always"}}}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["tools"] == {"read": {"permission": "always"}}