Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pycodeloop/cli/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
is_json_config = (
provider_name.endswith(".json") or provider_name in BUNDLED_TEMPLATES
)

if is_json_config:
provider_kwargs: dict = {}
Expand Down
33 changes: 24 additions & 9 deletions pycodeloop/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand All @@ -44,13 +46,26 @@ 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

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
Expand Down
Loading