diff --git a/README.md b/README.md index 2183d8a..595230f 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ windows-use |--------------|-----------------------------------------------| | Anthropic | `from windows_use.providers.anthropic import ChatAnthropic` | | OpenAI | `from windows_use.providers.openai import ChatOpenAI` | +| Atlas Cloud | `from windows_use.providers.atlas_cloud import ChatAtlasCloud` | | Google | `from windows_use.providers.google import ChatGoogle` | | Groq | `from windows_use.providers.groq import ChatGroq` | | Ollama | `from windows_use.providers.ollama import ChatOllama` | @@ -161,6 +162,14 @@ windows-use | NVIDIA | `from windows_use.providers.nvidia import ChatNvidia` | | vLLM | `from windows_use.providers.vllm import ChatVLLM` | +Atlas Cloud uses its OpenAI-compatible API and reads `ATLAS_CLOUD_API_KEY` by default: + +```python +from windows_use.providers.atlas_cloud import ChatAtlasCloud + +llm = ChatAtlasCloud(model="openai/gpt-4.1-mini") +``` + ## 🧰 Agent Configuration ```python diff --git a/tests/unit/providers/test_atlas_cloud.py b/tests/unit/providers/test_atlas_cloud.py new file mode 100644 index 0000000..ed201b8 --- /dev/null +++ b/tests/unit/providers/test_atlas_cloud.py @@ -0,0 +1,41 @@ +from unittest.mock import patch + +from windows_use.cli.registry import ( + get_models, + get_provider_display, + get_providers, + provider_requires_api_key, +) +from windows_use.providers.atlas_cloud import ChatAtlasCloud +from windows_use.providers.atlas_cloud.llm import ATLAS_CLOUD_BASE_URL + + +@patch("windows_use.providers.openai.llm.AsyncOpenAI") +@patch("windows_use.providers.openai.llm.OpenAI") +def test_atlas_cloud_defaults(mock_openai, mock_async_openai, monkeypatch): + monkeypatch.setenv("ATLAS_CLOUD_API_KEY", "atlas-key") + + llm = ChatAtlasCloud() + + assert llm.provider == "atlas_cloud" + assert llm.model_name == "openai/gpt-4.1-mini" + assert llm.base_url == ATLAS_CLOUD_BASE_URL + mock_openai.assert_called_once_with( + api_key="atlas-key", + base_url=ATLAS_CLOUD_BASE_URL, + timeout=600.0, + max_retries=2, + ) + mock_async_openai.assert_called_once_with( + api_key="atlas-key", + base_url=ATLAS_CLOUD_BASE_URL, + timeout=600.0, + max_retries=2, + ) + + +def test_atlas_cloud_is_available_in_cli_registry(): + assert ("Atlas Cloud", "atlas_cloud") in get_providers() + assert get_models("atlas_cloud") == [("OpenAI GPT-4.1 mini", "openai/gpt-4.1-mini")] + assert get_provider_display("atlas_cloud") == "Atlas Cloud" + assert provider_requires_api_key("atlas_cloud") is True diff --git a/windows_use/cli/registry.py b/windows_use/cli/registry.py index 0081c6f..9ebe266 100644 --- a/windows_use/cli/registry.py +++ b/windows_use/cli/registry.py @@ -11,6 +11,7 @@ PROVIDERS: list[tuple[str, str]] = [ ("Groq", "groq"), ("OpenAI", "openai"), + ("Atlas Cloud", "atlas_cloud"), ("Anthropic", "anthropic"), ("Google", "google"), ("Ollama", "ollama"), @@ -54,6 +55,9 @@ ("GPT-4 Turbo", "gpt-4-turbo"), ("GPT-3.5 Turbo", "gpt-3.5-turbo"), ], + "atlas_cloud": [ + ("OpenAI GPT-4.1 mini", "openai/gpt-4.1-mini"), + ], "perplexity": [ ("GPT-5.4 (recommended)", "openai/gpt-5.4"), ("Gemini 3.1 Pro Preview", "google/gemini-3.1-pro-preview"), @@ -174,6 +178,7 @@ PROVIDERS_REQUIRING_API_KEY: set[str] = { "groq", "openai", + "atlas_cloud", "anthropic", "google", "mistral", @@ -196,6 +201,7 @@ def provider_requires_api_key(provider_key: str) -> bool: PROVIDER_DISPLAY: dict[str, str] = { "groq": "Groq", "openai": "OpenAI", + "atlas_cloud": "Atlas Cloud", "anthropic": "Anthropic", "google": "Google", "ollama": "Ollama", diff --git a/windows_use/cli/setup.py b/windows_use/cli/setup.py index c37ceec..025bfed 100644 --- a/windows_use/cli/setup.py +++ b/windows_use/cli/setup.py @@ -367,6 +367,10 @@ def create_llm(provider: str, model: str, api_key: str | None = None, base_url: from windows_use.providers.openai import ChatOpenAI return ChatOpenAI(model=model, api_key=key, base_url=base_url) + if provider == "atlas_cloud": + from windows_use.providers.atlas_cloud import ChatAtlasCloud + + return ChatAtlasCloud(model=model, api_key=key, base_url=base_url) if provider == "anthropic": from windows_use.providers.anthropic import ChatAnthropic @@ -751,6 +755,7 @@ def _env_api_key_for_provider(provider: str) -> str | None: env_map = { "groq": "GROQ_API_KEY", "openai": "OPENAI_API_KEY", + "atlas_cloud": "ATLAS_CLOUD_API_KEY", "anthropic": "ANTHROPIC_API_KEY", "google": "GEMINI_API_KEY", # or GOOGLE_API_KEY "mistral": "MISTRAL_API_KEY", diff --git a/windows_use/providers/__init__.py b/windows_use/providers/__init__.py index 36aebd5..1a0472e 100644 --- a/windows_use/providers/__init__.py +++ b/windows_use/providers/__init__.py @@ -14,6 +14,7 @@ # Base protocols & data models # LLM providers from windows_use.providers.anthropic import ChatAnthropic +from windows_use.providers.atlas_cloud import ChatAtlasCloud from windows_use.providers.azure_openai import ChatAzureOpenAI from windows_use.providers.base import BaseChatLLM, BaseSTT, BaseTTS from windows_use.providers.cerebras import ChatCerebras @@ -73,6 +74,7 @@ "ToolCall", # LLM providers "ChatAnthropic", + "ChatAtlasCloud", "ChatGoogle", "ChatOpenAI", "ChatOllama", diff --git a/windows_use/providers/atlas_cloud/__init__.py b/windows_use/providers/atlas_cloud/__init__.py new file mode 100644 index 0000000..803b5d7 --- /dev/null +++ b/windows_use/providers/atlas_cloud/__init__.py @@ -0,0 +1,3 @@ +from windows_use.providers.atlas_cloud.llm import ChatAtlasCloud + +__all__ = ["ChatAtlasCloud"] diff --git a/windows_use/providers/atlas_cloud/llm.py b/windows_use/providers/atlas_cloud/llm.py new file mode 100644 index 0000000..264fed0 --- /dev/null +++ b/windows_use/providers/atlas_cloud/llm.py @@ -0,0 +1,37 @@ +"""Atlas Cloud LLM provider via its OpenAI-compatible API.""" + +import os + +from windows_use.providers.openai.llm import ChatOpenAI + +ATLAS_CLOUD_BASE_URL = "https://api.atlascloud.ai/v1" + + +class ChatAtlasCloud(ChatOpenAI): + """Chat model served through Atlas Cloud's OpenAI-compatible endpoint.""" + + def __init__( + self, + model: str = "openai/gpt-4.1-mini", + api_key: str | None = None, + base_url: str | None = None, + timeout: float = 600.0, + max_retries: int = 2, + temperature: float | None = None, + **kwargs, + ): + api_key = api_key or os.environ.get("ATLAS_CLOUD_API_KEY") + base_url = base_url or os.environ.get("ATLAS_CLOUD_API_BASE") or ATLAS_CLOUD_BASE_URL + super().__init__( + model=model, + api_key=api_key, + base_url=base_url, + timeout=timeout, + max_retries=max_retries, + temperature=temperature, + **kwargs, + ) + + @property + def provider(self) -> str: + return "atlas_cloud"