aistatus 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
aistatus-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LeTau Robotics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: aistatus
3
+ Version: 0.0.1
4
+ Summary: Smart AI model routing with real-time status awareness. Pre-flight checks, auto-fallback, budget constraints.
5
+ Author-email: LeTau Robotics <hello@aistatus.cc>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://aistatus.cc
8
+ Project-URL: Documentation, https://aistatus.cc/docs
9
+ Project-URL: Repository, https://github.com/fangxm233/aistatus-python
10
+ Keywords: ai,llm,routing,fallback,agent,openai,anthropic,gemini
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: httpx>=0.27
19
+ Provides-Extra: anthropic
20
+ Requires-Dist: anthropic>=0.40; extra == "anthropic"
21
+ Provides-Extra: openai
22
+ Requires-Dist: openai>=1.50; extra == "openai"
23
+ Provides-Extra: google
24
+ Requires-Dist: google-genai>=1.0; extra == "google"
25
+ Provides-Extra: gateway
26
+ Requires-Dist: aiohttp>=3.9; extra == "gateway"
27
+ Requires-Dist: pyyaml>=6.0; extra == "gateway"
28
+ Provides-Extra: all
29
+ Requires-Dist: anthropic>=0.40; extra == "all"
30
+ Requires-Dist: openai>=1.50; extra == "all"
31
+ Requires-Dist: google-genai>=1.0; extra == "all"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=8.0; extra == "dev"
34
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
35
+ Requires-Dist: ruff>=0.8; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # aistatus
39
+
40
+ Smart AI model routing with real-time status awareness.
41
+
42
+ `aistatus` checks provider/model availability through `aistatus.cc`, then calls
43
+ your installed provider SDK directly. Your prompts and API keys stay on your side.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install aistatus
49
+ pip install aistatus[anthropic]
50
+ pip install aistatus[openai]
51
+ pip install aistatus[google]
52
+ pip install aistatus[all]
53
+ ```
54
+
55
+ ## Quickstart
56
+
57
+ Set at least one provider API key, then route by model name:
58
+
59
+ ```python
60
+ from aistatus import route
61
+
62
+ resp = route(
63
+ "Summarize the latest deployment status.",
64
+ model="claude-sonnet-4-6",
65
+ )
66
+
67
+ print(resp.content)
68
+ print(resp.model_used)
69
+ print(resp.provider_used)
70
+ print(resp.was_fallback)
71
+ ```
72
+
73
+ If the primary provider is unavailable, `aistatus` tries other compatible providers
74
+ that are available in your environment.
75
+
76
+ ## Tier Routing
77
+
78
+ Tier routing is supported, but tiers must be configured explicitly:
79
+
80
+ ```python
81
+ from aistatus import Router
82
+
83
+ router = Router(check_timeout=2.0)
84
+ router.add_tier("fast", [
85
+ "claude-haiku-4-5",
86
+ "gpt-4o-mini",
87
+ "gemini-2.0-flash",
88
+ ])
89
+ router.add_tier("standard", [
90
+ "claude-sonnet-4-6",
91
+ "gpt-4o",
92
+ "gemini-2.5-pro",
93
+ ])
94
+
95
+ resp = router.route(
96
+ "Explain quantum computing in one sentence.",
97
+ tier="fast",
98
+ )
99
+ ```
100
+
101
+ ## Async
102
+
103
+ ```python
104
+ from aistatus import aroute
105
+
106
+ resp = await aroute(
107
+ [{"role": "user", "content": "Hello"}],
108
+ model="gpt-4o-mini",
109
+ )
110
+ ```
111
+
112
+ ## Status API
113
+
114
+ ```python
115
+ from aistatus import StatusAPI
116
+
117
+ api = StatusAPI()
118
+
119
+ check = api.check_provider("anthropic")
120
+ print(check.status)
121
+ print(check.is_available)
122
+
123
+ for provider in api.providers():
124
+ print(provider.name, provider.status.value)
125
+
126
+ for model in api.search_models("sonnet"):
127
+ print(model.id, model.prompt_price, model.completion_price)
128
+ ```
129
+
130
+ ## Response Object
131
+
132
+ Every `route()` call returns a `RouteResponse`:
133
+
134
+ ```python
135
+ @dataclass
136
+ class RouteResponse:
137
+ content: str
138
+ model_used: str
139
+ provider_used: str
140
+ was_fallback: bool
141
+ fallback_reason: str | None = None
142
+ input_tokens: int = 0
143
+ output_tokens: int = 0
144
+ cost_usd: float = 0.0
145
+ raw: Any = None
146
+ ```
147
+
148
+ ## Errors
149
+
150
+ ```python
151
+ from aistatus import AllProvidersDown, ProviderNotInstalled, route
152
+
153
+ try:
154
+ resp = route("Hello", model="claude-sonnet-4-6")
155
+ except AllProvidersDown as e:
156
+ print(e.tried)
157
+ except ProviderNotInstalled as e:
158
+ print(f"Install support for: {e.provider}")
159
+ ```
160
+
161
+ ## Environment Variables
162
+
163
+ The SDK auto-discovers providers from standard environment variables:
164
+
165
+ ```bash
166
+ ANTHROPIC_API_KEY=...
167
+ OPENAI_API_KEY=...
168
+ GEMINI_API_KEY=...
169
+ OPENROUTER_API_KEY=...
170
+ DEEPSEEK_API_KEY=...
171
+ MISTRAL_API_KEY=...
172
+ XAI_API_KEY=...
173
+ GROQ_API_KEY=...
174
+ TOGETHER_API_KEY=...
175
+ MOONSHOT_API_KEY=...
176
+ DASHSCOPE_API_KEY=...
177
+ ```
178
+
179
+ ## License
180
+
181
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,144 @@
1
+ # aistatus
2
+
3
+ Smart AI model routing with real-time status awareness.
4
+
5
+ `aistatus` checks provider/model availability through `aistatus.cc`, then calls
6
+ your installed provider SDK directly. Your prompts and API keys stay on your side.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install aistatus
12
+ pip install aistatus[anthropic]
13
+ pip install aistatus[openai]
14
+ pip install aistatus[google]
15
+ pip install aistatus[all]
16
+ ```
17
+
18
+ ## Quickstart
19
+
20
+ Set at least one provider API key, then route by model name:
21
+
22
+ ```python
23
+ from aistatus import route
24
+
25
+ resp = route(
26
+ "Summarize the latest deployment status.",
27
+ model="claude-sonnet-4-6",
28
+ )
29
+
30
+ print(resp.content)
31
+ print(resp.model_used)
32
+ print(resp.provider_used)
33
+ print(resp.was_fallback)
34
+ ```
35
+
36
+ If the primary provider is unavailable, `aistatus` tries other compatible providers
37
+ that are available in your environment.
38
+
39
+ ## Tier Routing
40
+
41
+ Tier routing is supported, but tiers must be configured explicitly:
42
+
43
+ ```python
44
+ from aistatus import Router
45
+
46
+ router = Router(check_timeout=2.0)
47
+ router.add_tier("fast", [
48
+ "claude-haiku-4-5",
49
+ "gpt-4o-mini",
50
+ "gemini-2.0-flash",
51
+ ])
52
+ router.add_tier("standard", [
53
+ "claude-sonnet-4-6",
54
+ "gpt-4o",
55
+ "gemini-2.5-pro",
56
+ ])
57
+
58
+ resp = router.route(
59
+ "Explain quantum computing in one sentence.",
60
+ tier="fast",
61
+ )
62
+ ```
63
+
64
+ ## Async
65
+
66
+ ```python
67
+ from aistatus import aroute
68
+
69
+ resp = await aroute(
70
+ [{"role": "user", "content": "Hello"}],
71
+ model="gpt-4o-mini",
72
+ )
73
+ ```
74
+
75
+ ## Status API
76
+
77
+ ```python
78
+ from aistatus import StatusAPI
79
+
80
+ api = StatusAPI()
81
+
82
+ check = api.check_provider("anthropic")
83
+ print(check.status)
84
+ print(check.is_available)
85
+
86
+ for provider in api.providers():
87
+ print(provider.name, provider.status.value)
88
+
89
+ for model in api.search_models("sonnet"):
90
+ print(model.id, model.prompt_price, model.completion_price)
91
+ ```
92
+
93
+ ## Response Object
94
+
95
+ Every `route()` call returns a `RouteResponse`:
96
+
97
+ ```python
98
+ @dataclass
99
+ class RouteResponse:
100
+ content: str
101
+ model_used: str
102
+ provider_used: str
103
+ was_fallback: bool
104
+ fallback_reason: str | None = None
105
+ input_tokens: int = 0
106
+ output_tokens: int = 0
107
+ cost_usd: float = 0.0
108
+ raw: Any = None
109
+ ```
110
+
111
+ ## Errors
112
+
113
+ ```python
114
+ from aistatus import AllProvidersDown, ProviderNotInstalled, route
115
+
116
+ try:
117
+ resp = route("Hello", model="claude-sonnet-4-6")
118
+ except AllProvidersDown as e:
119
+ print(e.tried)
120
+ except ProviderNotInstalled as e:
121
+ print(f"Install support for: {e.provider}")
122
+ ```
123
+
124
+ ## Environment Variables
125
+
126
+ The SDK auto-discovers providers from standard environment variables:
127
+
128
+ ```bash
129
+ ANTHROPIC_API_KEY=...
130
+ OPENAI_API_KEY=...
131
+ GEMINI_API_KEY=...
132
+ OPENROUTER_API_KEY=...
133
+ DEEPSEEK_API_KEY=...
134
+ MISTRAL_API_KEY=...
135
+ XAI_API_KEY=...
136
+ GROQ_API_KEY=...
137
+ TOGETHER_API_KEY=...
138
+ MOONSHOT_API_KEY=...
139
+ DASHSCOPE_API_KEY=...
140
+ ```
141
+
142
+ ## License
143
+
144
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,139 @@
1
+ """aistatus — Smart AI model routing with real-time status awareness.
2
+
3
+ Quickstart::
4
+
5
+ from aistatus import route
6
+
7
+ resp = route("Hello!", model="claude-sonnet-4-6")
8
+ print(resp.content) # "Hello! How can I help?"
9
+ print(resp.model_used) # "anthropic/claude-sonnet-4-6"
10
+ print(resp.was_fallback) # False
11
+
12
+ Tier-based routing (requires configuration)::
13
+
14
+ from aistatus import Router
15
+
16
+ router = Router()
17
+ router.add_tier("fast", ["claude-haiku-4-5", "gpt-4o-mini"])
18
+ resp = router.route("Hello!", tier="fast")
19
+ """
20
+
21
+ __version__ = "0.0.1"
22
+
23
+ # Core routing
24
+ from .router import Router # noqa: F401
25
+
26
+ # Data models (for type hints and advanced usage)
27
+ from .models import ( # noqa: F401
28
+ RouteResponse,
29
+ RouteConfig,
30
+ CheckResult,
31
+ Status,
32
+ ProviderStatus,
33
+ ProviderConfig,
34
+ ModelInfo,
35
+ )
36
+
37
+ # Status API (for direct queries without routing)
38
+ from .api import StatusAPI # noqa: F401
39
+
40
+ # Exceptions
41
+ from .exceptions import ( # noqa: F401
42
+ AIStatusError,
43
+ AllProvidersDown,
44
+ ProviderCallFailed,
45
+ NoBudgetMatch,
46
+ ProviderNotInstalled,
47
+ CheckAPIUnreachable,
48
+ )
49
+
50
+ # Trigger adapter registration on import
51
+ from . import providers as _providers # noqa: F401
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Module-level convenience functions (lazy-initialized default Router)
55
+ # ---------------------------------------------------------------------------
56
+
57
+ _default_router: Router | None = None
58
+
59
+
60
+ def _get_default_router() -> Router:
61
+ global _default_router
62
+ if _default_router is None:
63
+ _default_router = Router()
64
+ return _default_router
65
+
66
+
67
+ def route(
68
+ messages: str | list[dict],
69
+ *,
70
+ model: str | None = None,
71
+ tier: str | None = None,
72
+ system: str | None = None,
73
+ allow_fallback: bool = True,
74
+ timeout: float = 30.0,
75
+ prefer: list[str] | None = None,
76
+ **kwargs,
77
+ ) -> RouteResponse:
78
+ """One-liner routing with auto-discovered providers.
79
+
80
+ Args:
81
+ messages: User message string or OpenAI-style message list.
82
+ model: Model name (e.g. "claude-sonnet-4-6"). Provider resolved via API.
83
+ tier: Tier name. Requires prior configuration on the default router.
84
+ system: Optional system prompt (convenience for string messages).
85
+ allow_fallback: Try alternatives if primary provider is down.
86
+ timeout: Provider call timeout in seconds.
87
+ prefer: Provider preference order for fallback.
88
+ **kwargs: Passed to provider SDK (max_tokens, temperature, etc.).
89
+ """
90
+ return _get_default_router().route(
91
+ messages, model=model, tier=tier, system=system,
92
+ allow_fallback=allow_fallback, timeout=timeout, prefer=prefer,
93
+ **kwargs,
94
+ )
95
+
96
+
97
+ async def aroute(
98
+ messages: str | list[dict],
99
+ *,
100
+ model: str | None = None,
101
+ tier: str | None = None,
102
+ system: str | None = None,
103
+ allow_fallback: bool = True,
104
+ timeout: float = 30.0,
105
+ prefer: list[str] | None = None,
106
+ **kwargs,
107
+ ) -> RouteResponse:
108
+ """Async version of route()."""
109
+ return await _get_default_router().aroute(
110
+ messages, model=model, tier=tier, system=system,
111
+ allow_fallback=allow_fallback, timeout=timeout, prefer=prefer,
112
+ **kwargs,
113
+ )
114
+
115
+
116
+ __all__ = [
117
+ # One-liners
118
+ "route",
119
+ "aroute",
120
+ # Router class
121
+ "Router",
122
+ # API client
123
+ "StatusAPI",
124
+ # Models
125
+ "RouteResponse",
126
+ "RouteConfig",
127
+ "CheckResult",
128
+ "Status",
129
+ "ProviderStatus",
130
+ "ProviderConfig",
131
+ "ModelInfo",
132
+ # Exceptions
133
+ "AIStatusError",
134
+ "AllProvidersDown",
135
+ "ProviderCallFailed",
136
+ "NoBudgetMatch",
137
+ "ProviderNotInstalled",
138
+ "CheckAPIUnreachable",
139
+ ]
@@ -0,0 +1,39 @@
1
+ """Default configuration constants for auto-discovery."""
2
+
3
+ from __future__ import annotations
4
+
5
+ # Provider slug → (default_env_var, adapter_type_name)
6
+ # Used by Router to auto-discover available providers from environment variables.
7
+ AUTO_PROVIDERS: dict[str, tuple[str, str]] = {
8
+ "anthropic": ("ANTHROPIC_API_KEY", "anthropic"),
9
+ "openai": ("OPENAI_API_KEY", "openai"),
10
+ "google": ("GEMINI_API_KEY", "google"),
11
+ "openrouter": ("OPENROUTER_API_KEY", "openrouter"),
12
+ "deepseek": ("DEEPSEEK_API_KEY", "deepseek"),
13
+ "mistral": ("MISTRAL_API_KEY", "mistralai"),
14
+ "xai": ("XAI_API_KEY", "xai"),
15
+ "groq": ("GROQ_API_KEY", "groq"),
16
+ "together": ("TOGETHER_API_KEY", "together"),
17
+ "moonshot": ("MOONSHOT_API_KEY", "moonshotai"),
18
+ "qwen": ("DASHSCOPE_API_KEY", "qwen"),
19
+ }
20
+
21
+ # Model name prefix → provider slug.
22
+ # Used as fallback when aistatus.cc API is unreachable.
23
+ MODEL_PREFIX_MAP: dict[str, str] = {
24
+ "claude": "anthropic",
25
+ "gpt": "openai",
26
+ "o1": "openai",
27
+ "o3": "openai",
28
+ "o4": "openai",
29
+ "chatgpt": "openai",
30
+ "gemini": "google",
31
+ "deepseek": "deepseek",
32
+ "mistral": "mistral",
33
+ "codestral": "mistral",
34
+ "pixtral": "mistral",
35
+ "grok": "xai",
36
+ "llama": "groq",
37
+ "qwen": "qwen",
38
+ "moonshot": "moonshot",
39
+ }
@@ -0,0 +1,114 @@
1
+ """Thin client for the aistatus.cc public API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import httpx
6
+
7
+ from .models import Alternative, CheckResult, ModelInfo, ProviderStatus, Status
8
+
9
+ BASE_URL = "https://aistatus.cc"
10
+
11
+
12
+ class StatusAPI:
13
+ """Stateless HTTP client for aistatus.cc. All methods are class-level."""
14
+
15
+ def __init__(self, base_url: str = BASE_URL, timeout: float = 3.0):
16
+ self._base = base_url.rstrip("/")
17
+ self._timeout = timeout
18
+
19
+ # ---- sync helpers ------------------------------------------------
20
+
21
+ def _get(self, path: str, params: dict | None = None) -> dict:
22
+ with httpx.Client(timeout=self._timeout) as c:
23
+ r = c.get(f"{self._base}{path}", params=params)
24
+ r.raise_for_status()
25
+ return r.json()
26
+
27
+ async def _aget(self, path: str, params: dict | None = None) -> dict:
28
+ async with httpx.AsyncClient(timeout=self._timeout) as c:
29
+ r = await c.get(f"{self._base}{path}", params=params)
30
+ r.raise_for_status()
31
+ return r.json()
32
+
33
+ # ---- public methods (sync) ----------------------------------------
34
+
35
+ def check_provider(self, slug: str) -> CheckResult:
36
+ """Pre-flight check for a provider. GET /api/check?provider=..."""
37
+ data = self._get("/api/check", {"provider": slug})
38
+ return self._parse_check(data)
39
+
40
+ def check_model(self, model_id: str) -> CheckResult:
41
+ """Pre-flight check for a specific model. GET /api/check?model=..."""
42
+ data = self._get("/api/check", {"model": model_id})
43
+ return self._parse_check(data)
44
+
45
+ def providers(self) -> list[ProviderStatus]:
46
+ """All provider statuses. GET /api/providers"""
47
+ data = self._get("/api/providers")
48
+ return [
49
+ ProviderStatus(
50
+ slug=p["slug"],
51
+ name=p["name"],
52
+ status=Status(p["status"]),
53
+ status_detail=p.get("statusDetail"),
54
+ model_count=p.get("modelCount", 0),
55
+ )
56
+ for p in data.get("providers", [])
57
+ ]
58
+
59
+ def model(self, model_id: str) -> ModelInfo | None:
60
+ """Get single model info. GET /api/models/:provider/:model"""
61
+ try:
62
+ data = self._get(f"/api/models/{model_id}")
63
+ except httpx.HTTPStatusError:
64
+ return None
65
+ return self._parse_model(data)
66
+
67
+ def search_models(self, query: str) -> list[ModelInfo]:
68
+ """Search models. GET /api/models?q=..."""
69
+ data = self._get("/api/models", {"q": query})
70
+ return [self._parse_model(m) for m in data.get("models", [])]
71
+
72
+ # ---- public methods (async) ----------------------------------------
73
+
74
+ async def acheck_provider(self, slug: str) -> CheckResult:
75
+ data = await self._aget("/api/check", {"provider": slug})
76
+ return self._parse_check(data)
77
+
78
+ async def acheck_model(self, model_id: str) -> CheckResult:
79
+ data = await self._aget("/api/check", {"model": model_id})
80
+ return self._parse_check(data)
81
+
82
+ # ---- parsers -------------------------------------------------------
83
+
84
+ @staticmethod
85
+ def _parse_check(data: dict) -> CheckResult:
86
+ return CheckResult(
87
+ provider=data.get("provider", data.get("slug", "")),
88
+ status=Status(data.get("status", "unknown")),
89
+ status_detail=data.get("statusDetail"),
90
+ model=data.get("model"),
91
+ alternatives=[
92
+ Alternative(
93
+ slug=a["slug"],
94
+ name=a["name"],
95
+ status=Status(a["status"]),
96
+ suggested_model=a.get("suggestedModel", ""),
97
+ )
98
+ for a in data.get("alternatives", [])
99
+ ],
100
+ )
101
+
102
+ @staticmethod
103
+ def _parse_model(data: dict) -> ModelInfo:
104
+ pricing = data.get("pricing", {})
105
+ prov = data.get("provider", {})
106
+ return ModelInfo(
107
+ id=data.get("id", ""),
108
+ name=data.get("name", ""),
109
+ provider_slug=prov.get("slug", "") if isinstance(prov, dict) else "",
110
+ context_length=data.get("context_length", 0),
111
+ modality=data.get("modality", "text->text"),
112
+ prompt_price=float(pricing.get("prompt", 0)),
113
+ completion_price=float(pricing.get("completion", 0)),
114
+ )
@@ -0,0 +1,58 @@
1
+ """Exceptions for aistatus SDK."""
2
+
3
+
4
+ class AIStatusError(Exception):
5
+ """Base exception for all aistatus errors."""
6
+
7
+
8
+ class AllProvidersDown(AIStatusError):
9
+ """Raised when no provider in the routing chain is available."""
10
+
11
+ def __init__(self, tried: list[str]):
12
+ self.tried = tried
13
+ super().__init__(
14
+ f"All providers unavailable. Tried: {', '.join(tried)}. "
15
+ f"Check https://aistatus.cc for current status."
16
+ )
17
+
18
+
19
+ class ProviderCallFailed(AIStatusError):
20
+ """Raised when a provider API call fails (after status was operational)."""
21
+
22
+ def __init__(self, provider: str, model: str, cause: Exception):
23
+ self.provider = provider
24
+ self.model = model
25
+ self.cause = cause
26
+ super().__init__(f"{provider} ({model}) call failed: {cause}")
27
+
28
+
29
+ class NoBudgetMatch(AIStatusError):
30
+ """Raised when no available model fits the budget constraint."""
31
+
32
+ def __init__(self, max_cost: float, tier: str):
33
+ self.max_cost = max_cost
34
+ super().__init__(
35
+ f"No operational model in tier '{tier}' under ${max_cost}/M tokens."
36
+ )
37
+
38
+
39
+ class ProviderNotInstalled(AIStatusError):
40
+ """Raised when the required provider SDK is not installed."""
41
+
42
+ def __init__(self, provider: str, package: str):
43
+ self.provider = provider
44
+ self.package = package
45
+ super().__init__(
46
+ f"Provider '{provider}' requires package '{package}'. "
47
+ f"Install with: pip install aistatus[{provider}]"
48
+ )
49
+
50
+
51
+ class CheckAPIUnreachable(AIStatusError):
52
+ """Raised when aistatus.cc API itself is unreachable."""
53
+
54
+ def __init__(self):
55
+ super().__init__(
56
+ "Could not reach aistatus.cc API. "
57
+ "Proceeding with primary provider without status check."
58
+ )