diff options
| -rw-r--r-- | llamachat/config.py | 13 | ||||
| -rwxr-xr-x | test_llamachat.py | 42 |
2 files changed, 55 insertions, 0 deletions
diff --git a/llamachat/config.py b/llamachat/config.py index 7bdaf6e..ac53823 100644 --- a/llamachat/config.py +++ b/llamachat/config.py @@ -19,6 +19,8 @@ import tomllib from dataclasses import dataclass from pathlib import Path +from . import providers as providers_mod + CONFIG_PATH = Path( os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config") ) / "llamachat" / "config.toml" @@ -87,6 +89,8 @@ class Config: search_snippet_chars: int search_timeout: int max_searches: int + providers: dict[str, providers_mod.Provider] + models_path: Path def _runtime_dir() -> Path: @@ -119,6 +123,11 @@ def load(path: Path = CONFIG_PATH) -> Config: search_url = str(values["search_url"]).rstrip("/") search_enabled = bool(values["search_enabled"]) and bool(search_url) + # Providers are built from the raw values so a bare base_url still + # synthesizes the local entry. DEFAULTS supplies base_url when the file + # names neither, which keeps a config with no network settings working. + provider_table = providers_mod.parse(values) + return Config( base_url=str(values["base_url"]).rstrip("/"), presets_path=Path(values["presets"]).expanduser(), @@ -139,6 +148,10 @@ def load(path: Path = CONFIG_PATH) -> Config: search_snippet_chars=int(values["search_snippet_chars"]), search_timeout=int(values["search_timeout"]), max_searches=int(values["max_searches"]), + providers=provider_table, + # Cloud model metadata, cached beside state.ini for the same reason: + # it is machine-written, not user-editable config. + models_path=path.parent / "models.ini", ) diff --git a/test_llamachat.py b/test_llamachat.py index b943976..0eea9e1 100755 --- a/test_llamachat.py +++ b/test_llamachat.py @@ -1207,6 +1207,47 @@ def test_key_resolution(): print("ok api key resolution") +def test_config_providers(): + """config.load exposes the provider table and the models.ini path.""" + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "config.toml" + + # A legacy config: bare base_url only. + path.write_text('base_url = "http://localhost:9999"\n') + cfg = config.load(path) + assert set(cfg.providers) == {"local"} + assert cfg.providers["local"].base_url == "http://localhost:9999" + # base_url stays populated: existing code still reads it. + assert cfg.base_url == "http://localhost:9999" + assert cfg.models_path == path.parent / "models.ini" + + # A config with an explicit cloud provider. + path.write_text( + 'base_url = "http://localhost:9999"\n' + "\n" + "[providers.together]\n" + 'base_url = "https://api.example.org"\n' + 'api_key = "pass:api/together"\n' + 'filter = ["qwen"]\n' + "ctx_size = 32768\n" + "price_in = 0.6\n" + "price_out = 0.9\n" + ) + cfg = config.load(path) + assert set(cfg.providers) == {"local", "together"} + assert cfg.providers["together"].api_key == "pass:api/together" + assert cfg.providers["together"].filter == ["qwen"] + assert cfg.providers["together"].price_out == 0.9 + + # A config with no base_url and no providers still loads, with the + # built-in default synthesizing local. + path.write_text("request_timeout = 60\n") + cfg = config.load(path) + assert set(cfg.providers) == {"local"} + assert cfg.providers["local"].base_url == config.DEFAULTS["base_url"] + print("ok config provider table") + + class _FakeResponse: """Enough of an http.client response for urlopen's context manager.""" @@ -1947,6 +1988,7 @@ if __name__ == "__main__": test_provider_parsing() test_model_ids_and_filtering() test_key_resolution() + test_config_providers() test_search_tool_schema() test_search_results_sanitising() test_tool_call_accumulation() |
