From 3e9345bd2a2be4f5221110aabaab0431099866c9 Mon Sep 17 00:00:00 2001 From: Fernando Celmer Date: Sat, 15 Aug 2026 23:06:56 -0300 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20FEATURE-#58:=20Resolve?= =?UTF-8?q?=20bundled=20provider=20templates=20by=20bare=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pycodeloop/providers/__init__.py | 33 +++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/pycodeloop/providers/__init__.py b/pycodeloop/providers/__init__.py index 527a47e..49aa5a1 100644 --- a/pycodeloop/providers/__init__.py +++ b/pycodeloop/providers/__init__.py @@ -2,13 +2,17 @@ import importlib import os +from pathlib import Path from pycodeloop.constants import ENV_API_KEY from pycodeloop.providers.generic import GenericProvider PROVIDERS = {"generic": GenericProvider} -__all__ = ["GenericProvider", "get_provider", "PROVIDERS"] +TEMPLATES_DIR = Path(__file__).parent / "templates" +BUNDLED_TEMPLATES = {p.stem for p in TEMPLATES_DIR.glob("*.json")} + +__all__ = ["GenericProvider", "get_provider", "PROVIDERS", "BUNDLED_TEMPLATES"] def get_provider(name: str, **kwargs): @@ -17,20 +21,18 @@ def get_provider(name: str, **kwargs): `name` is one of: - a path to a JSON config file (see `pycodeloop.providers.generic`), the standard way to point at any HTTP LLM API; + - the bare name of a template shipped in `providers/templates/` + (e.g. `"anthropic"`, `"ollama"`) — resolved from the installed + package, so it works from any directory without a path; - `"generic"`, paired with `url=`/`model=` kwargs for an ad-hoc config with no file; - `'module.path:ClassName'` for a custom `Provider` subclass. """ if name.endswith(".json"): - provider = GenericProvider.from_json(name) - - if kwargs.get("model"): - provider.model = kwargs["model"] - - if kwargs.get("api_key"): - provider.api_key = kwargs["api_key"] + return _from_json_path(name, kwargs) - return provider + if name in BUNDLED_TEMPLATES: + return _from_json_path(str(TEMPLATES_DIR / f"{name}.json"), kwargs) if ":" in name: module_path, class_name = name.split(":", 1) @@ -44,6 +46,7 @@ def get_provider(name: str, **kwargs): except KeyError: raise ValueError( f"Unknown provider '{name}'. Available: {list(PROVIDERS)}, " + f"a bundled template ({', '.join(sorted(BUNDLED_TEMPLATES))}), " "a path to a JSON config file, or 'module.path:ClassName' " "for a custom Provider." ) from None @@ -51,6 +54,18 @@ def get_provider(name: str, **kwargs): return provider_cls(**_with_env_api_key(kwargs)) +def _from_json_path(path: str, kwargs: dict) -> GenericProvider: + provider = GenericProvider.from_json(path) + + if kwargs.get("model"): + provider.model = kwargs["model"] + + if kwargs.get("api_key"): + provider.api_key = kwargs["api_key"] + + return provider + + def _with_env_api_key(kwargs: dict) -> dict: if kwargs.get("api_key"): return kwargs From cfa9416eb1f5469da2317d7aaa8dba451434563f Mon Sep 17 00:00:00 2001 From: Fernando Celmer Date: Sat, 15 Aug 2026 23:06:56 -0300 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20FEATURE-#58:=20Treat?= =?UTF-8?q?=20bundled=20template=20names=20like=20JSON=20paths=20in=20reso?= =?UTF-8?q?lve=5Fprovider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pycodeloop/cli/flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodeloop/cli/flow.py b/pycodeloop/cli/flow.py index 6b16d02..8550b80 100644 --- a/pycodeloop/cli/flow.py +++ b/pycodeloop/cli/flow.py @@ -24,7 +24,7 @@ from pycodeloop.core.codeloop import CodeLoop from pycodeloop.core.config import Config from pycodeloop.mcp import MCPServer, MCPServerRegistry, load_mcp_tools -from pycodeloop.providers import get_provider +from pycodeloop.providers import BUNDLED_TEMPLATES, get_provider from pycodeloop.settings import Settings from pycodeloop.store.json_store import default_store from pycodeloop.tools import DEFAULT_TOOLS @@ -105,7 +105,7 @@ def resolve_provider( """Build a `Provider` from CLI-style args, returning it alongside the resolved provider name — shared by the interactive CLI and `serve`.""" provider_name = provider_name or Settings.PROVIDER - is_json_config = provider_name.endswith(".json") + is_json_config = provider_name.endswith(".json") or provider_name in BUNDLED_TEMPLATES if is_json_config: provider_kwargs: dict = {} From ae68caeac609322e0fd3ca772b023157ab998ab8 Mon Sep 17 00:00:00 2001 From: Fernando Celmer Date: Sat, 15 Aug 2026 23:08:43 -0300 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=93=9D=20PEP8-#58:=20Apply=20ruff=20f?= =?UTF-8?q?ormat=20to=20flow.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pycodeloop/cli/flow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pycodeloop/cli/flow.py b/pycodeloop/cli/flow.py index 8550b80..665c194 100644 --- a/pycodeloop/cli/flow.py +++ b/pycodeloop/cli/flow.py @@ -105,7 +105,9 @@ def resolve_provider( """Build a `Provider` from CLI-style args, returning it alongside the resolved provider name — shared by the interactive CLI and `serve`.""" provider_name = provider_name or Settings.PROVIDER - is_json_config = provider_name.endswith(".json") or provider_name in BUNDLED_TEMPLATES + is_json_config = ( + provider_name.endswith(".json") or provider_name in BUNDLED_TEMPLATES + ) if is_json_config: provider_kwargs: dict = {}