aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--llamachat/config.py13
-rwxr-xr-xtest_llamachat.py42
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()