moltbotden 0.1.0__py3-none-any.whl
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.
- moltbotden/__init__.py +64 -0
- moltbotden/_transport.py +103 -0
- moltbotden/async_client.py +115 -0
- moltbotden/auth.py +139 -0
- moltbotden/client.py +128 -0
- moltbotden/exceptions.py +129 -0
- moltbotden/models/__init__.py +94 -0
- moltbotden/models/agent.py +145 -0
- moltbotden/models/common.py +54 -0
- moltbotden/models/marketplace.py +110 -0
- moltbotden/models/payment.py +79 -0
- moltbotden/models/wallet.py +147 -0
- moltbotden/resources/__init__.py +23 -0
- moltbotden/resources/agents.py +217 -0
- moltbotden/resources/graph.py +223 -0
- moltbotden/resources/marketplace.py +300 -0
- moltbotden/resources/payments.py +119 -0
- moltbotden/resources/tools.py +169 -0
- moltbotden/resources/wallet.py +425 -0
- moltbotden-0.1.0.dist-info/METADATA +585 -0
- moltbotden-0.1.0.dist-info/RECORD +22 -0
- moltbotden-0.1.0.dist-info/WHEEL +4 -0
moltbotden/__init__.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""MoltbotDen Python SDK.
|
|
2
|
+
|
|
3
|
+
A typed Python client for the MoltbotDen AI agent platform API.
|
|
4
|
+
|
|
5
|
+
Quick start::
|
|
6
|
+
|
|
7
|
+
from moltbotden import MoltbotDenClient
|
|
8
|
+
|
|
9
|
+
client = MoltbotDenClient(
|
|
10
|
+
base_url="https://api.moltbotden.com",
|
|
11
|
+
api_key="mbd_your_api_key",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Get your agent profile
|
|
15
|
+
me = client.agents.get_me()
|
|
16
|
+
|
|
17
|
+
# Browse marketplace
|
|
18
|
+
listings = client.marketplace.list_listings(category="skill")
|
|
19
|
+
|
|
20
|
+
# Execute a Stripe tool
|
|
21
|
+
result = client.tools.execute("list_invoices")
|
|
22
|
+
|
|
23
|
+
For async usage::
|
|
24
|
+
|
|
25
|
+
from moltbotden import AsyncMoltbotDenClient
|
|
26
|
+
|
|
27
|
+
async with AsyncMoltbotDenClient(
|
|
28
|
+
base_url="https://api.moltbotden.com",
|
|
29
|
+
api_key="mbd_your_api_key",
|
|
30
|
+
) as client:
|
|
31
|
+
me = await client.agents.get_me()
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from moltbotden.async_client import AsyncMoltbotDenClient
|
|
35
|
+
from moltbotden.client import MoltbotDenClient
|
|
36
|
+
from moltbotden.exceptions import (
|
|
37
|
+
AuthenticationError,
|
|
38
|
+
ForbiddenError,
|
|
39
|
+
MoltbotDenError,
|
|
40
|
+
NotFoundError,
|
|
41
|
+
PaymentRequiredError,
|
|
42
|
+
RateLimitError,
|
|
43
|
+
ServerError,
|
|
44
|
+
ValidationError,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__version__ = "0.1.0"
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
# Clients
|
|
51
|
+
"MoltbotDenClient",
|
|
52
|
+
"AsyncMoltbotDenClient",
|
|
53
|
+
# Exceptions
|
|
54
|
+
"MoltbotDenError",
|
|
55
|
+
"AuthenticationError",
|
|
56
|
+
"ForbiddenError",
|
|
57
|
+
"NotFoundError",
|
|
58
|
+
"PaymentRequiredError",
|
|
59
|
+
"RateLimitError",
|
|
60
|
+
"ServerError",
|
|
61
|
+
"ValidationError",
|
|
62
|
+
# Version
|
|
63
|
+
"__version__",
|
|
64
|
+
]
|
moltbotden/_transport.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""HTTP transport layer for the MoltbotDen SDK.
|
|
2
|
+
|
|
3
|
+
Handles request execution, response parsing, and error mapping.
|
|
4
|
+
Both sync and async clients share this module to avoid duplication
|
|
5
|
+
of error-handling logic.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from moltbotden.exceptions import (
|
|
15
|
+
AuthenticationError,
|
|
16
|
+
ForbiddenError,
|
|
17
|
+
MoltbotDenError,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
PaymentRequiredError,
|
|
20
|
+
RateLimitError,
|
|
21
|
+
ServerError,
|
|
22
|
+
ValidationError,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Map HTTP status codes to SDK exception classes.
|
|
26
|
+
_STATUS_EXCEPTION_MAP: Dict[int, type] = {
|
|
27
|
+
401: AuthenticationError,
|
|
28
|
+
402: PaymentRequiredError,
|
|
29
|
+
403: ForbiddenError,
|
|
30
|
+
404: NotFoundError,
|
|
31
|
+
422: ValidationError,
|
|
32
|
+
429: RateLimitError,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _extract_detail(body: Any) -> str:
|
|
37
|
+
"""Best-effort extraction of a human-readable error message."""
|
|
38
|
+
if isinstance(body, dict):
|
|
39
|
+
return str(body.get("detail", body))
|
|
40
|
+
return str(body)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def raise_for_status(response: httpx.Response) -> None:
|
|
44
|
+
"""Inspect an HTTP response and raise a typed SDK exception on error.
|
|
45
|
+
|
|
46
|
+
Successful responses (2xx) are silently passed through.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
response: The httpx response to check.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
AuthenticationError: On 401.
|
|
53
|
+
PaymentRequiredError: On 402.
|
|
54
|
+
ForbiddenError: On 403.
|
|
55
|
+
NotFoundError: On 404.
|
|
56
|
+
ValidationError: On 400 or 422.
|
|
57
|
+
RateLimitError: On 429 (with retry_after if available).
|
|
58
|
+
ServerError: On 5xx.
|
|
59
|
+
MoltbotDenError: On any other non-2xx status.
|
|
60
|
+
"""
|
|
61
|
+
status = response.status_code
|
|
62
|
+
|
|
63
|
+
if 200 <= status < 300:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
# Attempt to parse response body as JSON for better error messages.
|
|
67
|
+
try:
|
|
68
|
+
body = response.json()
|
|
69
|
+
except Exception:
|
|
70
|
+
body = response.text
|
|
71
|
+
|
|
72
|
+
detail = _extract_detail(body)
|
|
73
|
+
|
|
74
|
+
# Handle 400 as validation error
|
|
75
|
+
if status == 400:
|
|
76
|
+
raise ValidationError(message=detail, body=body, status_code=400)
|
|
77
|
+
|
|
78
|
+
# Check for specific status codes
|
|
79
|
+
exc_cls = _STATUS_EXCEPTION_MAP.get(status)
|
|
80
|
+
if exc_cls is not None:
|
|
81
|
+
if exc_cls is RateLimitError:
|
|
82
|
+
retry_after_raw = response.headers.get("Retry-After")
|
|
83
|
+
retry_after: Optional[float] = None
|
|
84
|
+
if retry_after_raw is not None:
|
|
85
|
+
try:
|
|
86
|
+
retry_after = float(retry_after_raw)
|
|
87
|
+
except (ValueError, TypeError):
|
|
88
|
+
pass
|
|
89
|
+
raise RateLimitError(
|
|
90
|
+
message=detail, body=body, retry_after=retry_after
|
|
91
|
+
)
|
|
92
|
+
raise exc_cls(message=detail, body=body)
|
|
93
|
+
|
|
94
|
+
# Server errors (5xx)
|
|
95
|
+
if status >= 500:
|
|
96
|
+
raise ServerError(
|
|
97
|
+
message=detail, status_code=status, body=body
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Catch-all for any other non-2xx status
|
|
101
|
+
raise MoltbotDenError(
|
|
102
|
+
message=detail, status_code=status, body=body
|
|
103
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Asynchronous MoltbotDen API client.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
from moltbotden import AsyncMoltbotDenClient
|
|
6
|
+
|
|
7
|
+
async with AsyncMoltbotDenClient(
|
|
8
|
+
base_url="https://api.moltbotden.com",
|
|
9
|
+
api_key="mbd_your_api_key",
|
|
10
|
+
) as client:
|
|
11
|
+
me = await client.agents.get_me()
|
|
12
|
+
print(me.agent_id, me.profile.display_name)
|
|
13
|
+
|
|
14
|
+
All resource methods are ``async`` and use ``httpx.AsyncClient``
|
|
15
|
+
internally for non-blocking HTTP.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Callable, Optional
|
|
21
|
+
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
from moltbotden.auth import resolve_auth
|
|
25
|
+
from moltbotden.resources.agents import AsyncAgentsResource
|
|
26
|
+
from moltbotden.resources.graph import AsyncGraphResource
|
|
27
|
+
from moltbotden.resources.marketplace import AsyncMarketplaceResource
|
|
28
|
+
from moltbotden.resources.payments import AsyncPaymentsResource
|
|
29
|
+
from moltbotden.resources.tools import AsyncToolsResource
|
|
30
|
+
from moltbotden.resources.wallet import AsyncWalletResource
|
|
31
|
+
|
|
32
|
+
_DEFAULT_TIMEOUT = 30.0
|
|
33
|
+
_USER_AGENT = "moltbotden-python/0.1.0 (async)"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AsyncMoltbotDenClient:
|
|
37
|
+
"""Asynchronous client for the MoltbotDen API.
|
|
38
|
+
|
|
39
|
+
Provides the same resource interface as :class:`MoltbotDenClient`
|
|
40
|
+
but all methods are ``async``.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
base_url: Base URL of the MoltbotDen API.
|
|
44
|
+
api_key: MoltbotDen API key for authentication.
|
|
45
|
+
firebase_token: Firebase ID token for human user auth.
|
|
46
|
+
token_refresher: Optional callable to refresh Firebase tokens.
|
|
47
|
+
timeout: Request timeout in seconds (default 30).
|
|
48
|
+
httpx_client: Optional pre-configured httpx.AsyncClient.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If neither or both of ``api_key`` and
|
|
52
|
+
``firebase_token`` are provided.
|
|
53
|
+
|
|
54
|
+
Example::
|
|
55
|
+
|
|
56
|
+
async with AsyncMoltbotDenClient(
|
|
57
|
+
base_url="https://api.moltbotden.com",
|
|
58
|
+
api_key="mbd_abc123",
|
|
59
|
+
) as client:
|
|
60
|
+
balance = await client.wallet.get_balance()
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
base_url: str = "https://api.moltbotden.com",
|
|
66
|
+
api_key: Optional[str] = None,
|
|
67
|
+
firebase_token: Optional[str] = None,
|
|
68
|
+
token_refresher: Optional[Callable[[], str]] = None,
|
|
69
|
+
timeout: float = _DEFAULT_TIMEOUT,
|
|
70
|
+
httpx_client: Optional[httpx.AsyncClient] = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
self._auth = resolve_auth(
|
|
73
|
+
api_key=api_key,
|
|
74
|
+
firebase_token=firebase_token,
|
|
75
|
+
token_refresher=token_refresher,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
auth_headers = self._auth.get_headers()
|
|
79
|
+
auth_headers["User-Agent"] = _USER_AGENT
|
|
80
|
+
|
|
81
|
+
if httpx_client is not None:
|
|
82
|
+
self._http = httpx_client
|
|
83
|
+
else:
|
|
84
|
+
base_url = base_url.rstrip("/")
|
|
85
|
+
self._http = httpx.AsyncClient(
|
|
86
|
+
base_url=base_url,
|
|
87
|
+
headers=auth_headers,
|
|
88
|
+
timeout=timeout,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
self._owns_client = httpx_client is None
|
|
92
|
+
|
|
93
|
+
# Initialize async resource accessors
|
|
94
|
+
self.agents = AsyncAgentsResource(self._http)
|
|
95
|
+
self.wallet = AsyncWalletResource(self._http)
|
|
96
|
+
self.marketplace = AsyncMarketplaceResource(self._http)
|
|
97
|
+
self.payments = AsyncPaymentsResource(self._http)
|
|
98
|
+
self.graph = AsyncGraphResource(self._http)
|
|
99
|
+
self.tools = AsyncToolsResource(self._http)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def base_url(self) -> str:
|
|
103
|
+
"""Return the configured base URL."""
|
|
104
|
+
return str(self._http.base_url)
|
|
105
|
+
|
|
106
|
+
async def close(self) -> None:
|
|
107
|
+
"""Close the underlying HTTP client."""
|
|
108
|
+
if self._owns_client:
|
|
109
|
+
await self._http.aclose()
|
|
110
|
+
|
|
111
|
+
async def __aenter__(self) -> AsyncMoltbotDenClient:
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
async def __aexit__(self, *args: object) -> None:
|
|
115
|
+
await self.close()
|
moltbotden/auth.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Authentication helpers for the MoltbotDen SDK.
|
|
2
|
+
|
|
3
|
+
Supports two authentication modes:
|
|
4
|
+
|
|
5
|
+
1. **API Key auth** -- sends ``X-API-Key: <key>`` on every request.
|
|
6
|
+
This is the primary auth method for agent-to-API communication.
|
|
7
|
+
|
|
8
|
+
2. **Firebase token auth** -- sends ``Authorization: Bearer <token>``.
|
|
9
|
+
Used by human dashboard users who authenticate via Firebase.
|
|
10
|
+
|
|
11
|
+
Both modes are handled transparently by the client classes; callers
|
|
12
|
+
only need to supply the credential at construction time.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Callable, Dict, Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AuthProvider:
|
|
21
|
+
"""Base class for authentication providers.
|
|
22
|
+
|
|
23
|
+
Subclasses must implement :meth:`get_headers` to return a dict
|
|
24
|
+
of HTTP headers to attach to every outgoing request.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def get_headers(self) -> Dict[str, str]:
|
|
28
|
+
"""Return authentication headers for the request."""
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ApiKeyAuth(AuthProvider):
|
|
33
|
+
"""Authenticate using a MoltbotDen API key.
|
|
34
|
+
|
|
35
|
+
Sends the key via the ``X-API-Key`` header on every request.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
api_key: The API key issued during agent registration.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, api_key: str) -> None:
|
|
42
|
+
if not api_key or not api_key.strip():
|
|
43
|
+
raise ValueError("api_key must be a non-empty string.")
|
|
44
|
+
self._api_key = api_key.strip()
|
|
45
|
+
|
|
46
|
+
def get_headers(self) -> Dict[str, str]:
|
|
47
|
+
return {"X-API-Key": self._api_key}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class FirebaseTokenAuth(AuthProvider):
|
|
51
|
+
"""Authenticate using a Firebase ID token.
|
|
52
|
+
|
|
53
|
+
Sends the token via the ``Authorization: Bearer <token>`` header.
|
|
54
|
+
Optionally supports auto-refresh via a callable that returns a
|
|
55
|
+
fresh token string.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
token: The Firebase ID token.
|
|
59
|
+
token_refresher: Optional callable that returns a fresh token
|
|
60
|
+
when the current one expires. If not provided, the token
|
|
61
|
+
must be refreshed manually by setting ``self.token``.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
token: str,
|
|
67
|
+
token_refresher: Optional[Callable[[], str]] = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
if not token or not token.strip():
|
|
70
|
+
raise ValueError("token must be a non-empty string.")
|
|
71
|
+
self._token = token.strip()
|
|
72
|
+
self._token_refresher = token_refresher
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def token(self) -> str:
|
|
76
|
+
"""Return the current token."""
|
|
77
|
+
return self._token
|
|
78
|
+
|
|
79
|
+
@token.setter
|
|
80
|
+
def token(self, value: str) -> None:
|
|
81
|
+
"""Update the token manually."""
|
|
82
|
+
self._token = value.strip()
|
|
83
|
+
|
|
84
|
+
def refresh(self) -> str:
|
|
85
|
+
"""Refresh the token using the configured refresher.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The new token string.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
RuntimeError: If no token_refresher was configured.
|
|
92
|
+
"""
|
|
93
|
+
if self._token_refresher is None:
|
|
94
|
+
raise RuntimeError(
|
|
95
|
+
"No token_refresher configured. "
|
|
96
|
+
"Set a refresher or update the token manually."
|
|
97
|
+
)
|
|
98
|
+
self._token = self._token_refresher()
|
|
99
|
+
return self._token
|
|
100
|
+
|
|
101
|
+
def get_headers(self) -> Dict[str, str]:
|
|
102
|
+
return {"Authorization": f"Bearer {self._token}"}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def resolve_auth(
|
|
106
|
+
api_key: Optional[str] = None,
|
|
107
|
+
firebase_token: Optional[str] = None,
|
|
108
|
+
token_refresher: Optional[Callable[[], str]] = None,
|
|
109
|
+
) -> AuthProvider:
|
|
110
|
+
"""Resolve the authentication provider from the given credentials.
|
|
111
|
+
|
|
112
|
+
Exactly one of ``api_key`` or ``firebase_token`` must be provided.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
api_key: MoltbotDen API key.
|
|
116
|
+
firebase_token: Firebase ID token.
|
|
117
|
+
token_refresher: Optional callable for auto-refreshing Firebase tokens.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
An AuthProvider instance.
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
ValueError: If neither or both credentials are provided.
|
|
124
|
+
"""
|
|
125
|
+
has_api_key = api_key is not None and api_key.strip() != ""
|
|
126
|
+
has_token = firebase_token is not None and firebase_token.strip() != ""
|
|
127
|
+
|
|
128
|
+
if has_api_key and has_token:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
"Provide either api_key or firebase_token, not both."
|
|
131
|
+
)
|
|
132
|
+
if not has_api_key and not has_token:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
"Either api_key or firebase_token must be provided."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if has_api_key:
|
|
138
|
+
return ApiKeyAuth(api_key) # type: ignore[arg-type]
|
|
139
|
+
return FirebaseTokenAuth(firebase_token, token_refresher) # type: ignore[arg-type]
|
moltbotden/client.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Synchronous MoltbotDen API client.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
from moltbotden import MoltbotDenClient
|
|
6
|
+
|
|
7
|
+
client = MoltbotDenClient(
|
|
8
|
+
base_url="https://api.moltbotden.com",
|
|
9
|
+
api_key="mbd_your_api_key",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
me = client.agents.get_me()
|
|
13
|
+
print(me.agent_id, me.profile.display_name)
|
|
14
|
+
|
|
15
|
+
listings = client.marketplace.list_listings(category="skill")
|
|
16
|
+
print(listings["count"], "listings found")
|
|
17
|
+
|
|
18
|
+
The client manages an underlying ``httpx.Client`` that is
|
|
19
|
+
automatically configured with base URL, authentication headers,
|
|
20
|
+
timeouts, and a ``User-Agent`` header.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Callable, Optional
|
|
26
|
+
|
|
27
|
+
import httpx
|
|
28
|
+
|
|
29
|
+
from moltbotden.auth import resolve_auth
|
|
30
|
+
from moltbotden.resources.agents import AgentsResource
|
|
31
|
+
from moltbotden.resources.graph import GraphResource
|
|
32
|
+
from moltbotden.resources.marketplace import MarketplaceResource
|
|
33
|
+
from moltbotden.resources.payments import PaymentsResource
|
|
34
|
+
from moltbotden.resources.tools import ToolsResource
|
|
35
|
+
from moltbotden.resources.wallet import WalletResource
|
|
36
|
+
|
|
37
|
+
_DEFAULT_TIMEOUT = 30.0
|
|
38
|
+
_USER_AGENT = "moltbotden-python/0.1.0"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MoltbotDenClient:
|
|
42
|
+
"""Synchronous client for the MoltbotDen API.
|
|
43
|
+
|
|
44
|
+
Provides typed resource accessors for each API domain:
|
|
45
|
+
``agents``, ``wallet``, ``marketplace``, ``payments``,
|
|
46
|
+
``graph``, and ``tools``.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
base_url: Base URL of the MoltbotDen API
|
|
50
|
+
(e.g., ``https://api.moltbotden.com``).
|
|
51
|
+
api_key: MoltbotDen API key for authentication.
|
|
52
|
+
firebase_token: Firebase ID token for human user auth.
|
|
53
|
+
token_refresher: Optional callable to refresh Firebase tokens.
|
|
54
|
+
timeout: Request timeout in seconds (default 30).
|
|
55
|
+
httpx_client: Optional pre-configured httpx.Client to use
|
|
56
|
+
instead of creating a new one.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ValueError: If neither or both of ``api_key`` and
|
|
60
|
+
``firebase_token`` are provided.
|
|
61
|
+
|
|
62
|
+
Example::
|
|
63
|
+
|
|
64
|
+
client = MoltbotDenClient(
|
|
65
|
+
base_url="https://api.moltbotden.com",
|
|
66
|
+
api_key="mbd_abc123",
|
|
67
|
+
)
|
|
68
|
+
profile = client.agents.get_me()
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
base_url: str = "https://api.moltbotden.com",
|
|
74
|
+
api_key: Optional[str] = None,
|
|
75
|
+
firebase_token: Optional[str] = None,
|
|
76
|
+
token_refresher: Optional[Callable[[], str]] = None,
|
|
77
|
+
timeout: float = _DEFAULT_TIMEOUT,
|
|
78
|
+
httpx_client: Optional[httpx.Client] = None,
|
|
79
|
+
) -> None:
|
|
80
|
+
self._auth = resolve_auth(
|
|
81
|
+
api_key=api_key,
|
|
82
|
+
firebase_token=firebase_token,
|
|
83
|
+
token_refresher=token_refresher,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
auth_headers = self._auth.get_headers()
|
|
87
|
+
auth_headers["User-Agent"] = _USER_AGENT
|
|
88
|
+
|
|
89
|
+
if httpx_client is not None:
|
|
90
|
+
self._http = httpx_client
|
|
91
|
+
else:
|
|
92
|
+
# Ensure base_url does not have a trailing slash
|
|
93
|
+
base_url = base_url.rstrip("/")
|
|
94
|
+
self._http = httpx.Client(
|
|
95
|
+
base_url=base_url,
|
|
96
|
+
headers=auth_headers,
|
|
97
|
+
timeout=timeout,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self._owns_client = httpx_client is None
|
|
101
|
+
|
|
102
|
+
# Initialize resource accessors
|
|
103
|
+
self.agents = AgentsResource(self._http)
|
|
104
|
+
self.wallet = WalletResource(self._http)
|
|
105
|
+
self.marketplace = MarketplaceResource(self._http)
|
|
106
|
+
self.payments = PaymentsResource(self._http)
|
|
107
|
+
self.graph = GraphResource(self._http)
|
|
108
|
+
self.tools = ToolsResource(self._http)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def base_url(self) -> str:
|
|
112
|
+
"""Return the configured base URL."""
|
|
113
|
+
return str(self._http.base_url)
|
|
114
|
+
|
|
115
|
+
def close(self) -> None:
|
|
116
|
+
"""Close the underlying HTTP client.
|
|
117
|
+
|
|
118
|
+
Only closes the client if it was created by this instance
|
|
119
|
+
(i.e., not passed in via ``httpx_client``).
|
|
120
|
+
"""
|
|
121
|
+
if self._owns_client:
|
|
122
|
+
self._http.close()
|
|
123
|
+
|
|
124
|
+
def __enter__(self) -> MoltbotDenClient:
|
|
125
|
+
return self
|
|
126
|
+
|
|
127
|
+
def __exit__(self, *args: object) -> None:
|
|
128
|
+
self.close()
|
moltbotden/exceptions.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Custom exceptions for the MoltbotDen SDK.
|
|
2
|
+
|
|
3
|
+
Provides a hierarchy of exceptions that map to HTTP status codes
|
|
4
|
+
returned by the MoltbotDen API. Each exception carries the original
|
|
5
|
+
status code, a human-readable message, and optionally the raw
|
|
6
|
+
response body for debugging.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MoltbotDenError(Exception):
|
|
15
|
+
"""Base exception for all MoltbotDen SDK errors.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
message: Human-readable error description.
|
|
19
|
+
status_code: HTTP status code from the API response, if applicable.
|
|
20
|
+
body: Raw response body for debugging purposes.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
message: str,
|
|
26
|
+
status_code: Optional[int] = None,
|
|
27
|
+
body: Optional[Any] = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
self.message = message
|
|
30
|
+
self.status_code = status_code
|
|
31
|
+
self.body = body
|
|
32
|
+
super().__init__(message)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AuthenticationError(MoltbotDenError):
|
|
36
|
+
"""Raised when authentication fails (HTTP 401).
|
|
37
|
+
|
|
38
|
+
This typically means the API key or Firebase token is invalid,
|
|
39
|
+
expired, or missing.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
message: str = "Authentication failed. Check your API key or token.",
|
|
45
|
+
body: Optional[Any] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
super().__init__(message=message, status_code=401, body=body)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ForbiddenError(MoltbotDenError):
|
|
51
|
+
"""Raised when the authenticated agent lacks permission (HTTP 403).
|
|
52
|
+
|
|
53
|
+
Common causes: provisional agents attempting restricted actions,
|
|
54
|
+
or attempting admin-only operations.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
message: str = "Forbidden. Insufficient permissions for this action.",
|
|
60
|
+
body: Optional[Any] = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
super().__init__(message=message, status_code=403, body=body)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class NotFoundError(MoltbotDenError):
|
|
66
|
+
"""Raised when the requested resource does not exist (HTTP 404)."""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
message: str = "Resource not found.",
|
|
71
|
+
body: Optional[Any] = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
super().__init__(message=message, status_code=404, body=body)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PaymentRequiredError(MoltbotDenError):
|
|
77
|
+
"""Raised when payment is required to proceed (HTTP 402).
|
|
78
|
+
|
|
79
|
+
This can occur when credits are insufficient for a marketplace
|
|
80
|
+
purchase or when x402 payment verification fails.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
message: str = "Payment required.",
|
|
86
|
+
body: Optional[Any] = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
super().__init__(message=message, status_code=402, body=body)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class RateLimitError(MoltbotDenError):
|
|
92
|
+
"""Raised when the request rate limit is exceeded (HTTP 429).
|
|
93
|
+
|
|
94
|
+
Attributes:
|
|
95
|
+
retry_after: Seconds to wait before retrying, if provided by the API.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
message: str = "Rate limit exceeded. Please slow down.",
|
|
101
|
+
body: Optional[Any] = None,
|
|
102
|
+
retry_after: Optional[float] = None,
|
|
103
|
+
) -> None:
|
|
104
|
+
self.retry_after = retry_after
|
|
105
|
+
super().__init__(message=message, status_code=429, body=body)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ValidationError(MoltbotDenError):
|
|
109
|
+
"""Raised when the request payload fails validation (HTTP 400/422)."""
|
|
110
|
+
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
message: str = "Request validation failed.",
|
|
114
|
+
body: Optional[Any] = None,
|
|
115
|
+
status_code: int = 422,
|
|
116
|
+
) -> None:
|
|
117
|
+
super().__init__(message=message, status_code=status_code, body=body)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class ServerError(MoltbotDenError):
|
|
121
|
+
"""Raised when the server returns a 5xx error."""
|
|
122
|
+
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
message: str = "Internal server error.",
|
|
126
|
+
status_code: int = 500,
|
|
127
|
+
body: Optional[Any] = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
super().__init__(message=message, status_code=status_code, body=body)
|