lime-agents-sdk 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.
@@ -0,0 +1,25 @@
1
+ """Official Python SDK for LIME AI agent workers."""
2
+
3
+ from lime_agents._agent import LimeAgent
4
+ from lime_agents._errors import (
5
+ ApiError,
6
+ AuthenticationError,
7
+ LimeError,
8
+ PowTimeoutError,
9
+ RateLimitError,
10
+ )
11
+ from lime_agents._types import AgentProfile, ApprovalResult
12
+
13
+ __version__ = "0.1.0"
14
+
15
+ __all__ = [
16
+ "AgentProfile",
17
+ "ApiError",
18
+ "ApprovalResult",
19
+ "AuthenticationError",
20
+ "LimeAgent",
21
+ "LimeError",
22
+ "PowTimeoutError",
23
+ "RateLimitError",
24
+ "__version__",
25
+ ]
lime_agents/_agent.py ADDED
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import os
5
+ from types import TracebackType
6
+
7
+ import httpx
8
+
9
+ from lime_agents._client import LimeClient
10
+ from lime_agents._errors import AuthenticationError
11
+ from lime_agents._pow import solve
12
+ from lime_agents._types import AgentProfile, ApprovalResult
13
+
14
+ _DEFAULT_BASE_URL = "https://lime.pics/api/v1"
15
+
16
+
17
+ class LimeAgent:
18
+ """Async client for LIME agent workers."""
19
+
20
+ def __init__(
21
+ self,
22
+ *,
23
+ agent_token: str | None = None,
24
+ base_url: str | None = None,
25
+ timeout: float = 30.0,
26
+ max_retries: int = 3,
27
+ pow_timeout: float = 10.0,
28
+ http_client: httpx.AsyncClient | None = None,
29
+ ) -> None:
30
+ resolved_token = (agent_token or os.getenv("LIME_AGENT_TOKEN") or "").strip()
31
+ if not resolved_token:
32
+ raise AuthenticationError(
33
+ "Agent token is required. Pass agent_token= or set LIME_AGENT_TOKEN.",
34
+ )
35
+
36
+ resolved_base = (
37
+ base_url
38
+ or os.getenv("LIME_API_BASE")
39
+ or _DEFAULT_BASE_URL
40
+ ).rstrip("/")
41
+
42
+ self._pow_timeout = pow_timeout
43
+ self._client = LimeClient(
44
+ agent_token=resolved_token,
45
+ base_url=resolved_base,
46
+ timeout=timeout,
47
+ max_retries=max_retries,
48
+ http_client=http_client,
49
+ )
50
+
51
+ async def __aenter__(self) -> LimeAgent:
52
+ return self
53
+
54
+ async def __aexit__(
55
+ self,
56
+ exc_type: type[BaseException] | None,
57
+ exc: BaseException | None,
58
+ tb: TracebackType | None,
59
+ ) -> None:
60
+ await self.aclose()
61
+
62
+ async def aclose(self) -> None:
63
+ await self._client.aclose()
64
+
65
+ async def approve(self, request_id: str) -> ApprovalResult:
66
+ """Fetch PoW challenge, solve it, and approve the login request."""
67
+ challenge_data = await self._client.get_public(f"/auth/requests/{request_id}")
68
+ pow_challenge = str(challenge_data["pow_challenge"])
69
+ pow_difficulty = int(challenge_data["pow_difficulty"])
70
+
71
+ pow_nonce = await asyncio.to_thread(
72
+ solve,
73
+ pow_challenge,
74
+ pow_difficulty,
75
+ max_timeout=self._pow_timeout,
76
+ )
77
+
78
+ approve_data = await self._client.post(
79
+ f"/modules/agent-login/requests/{request_id}/approve",
80
+ {"pow_nonce": pow_nonce},
81
+ )
82
+ return ApprovalResult.from_api(approve_data)
83
+
84
+ async def get_profile(self) -> AgentProfile:
85
+ """Return the authenticated agent's Core profile."""
86
+ profile_data = await self._client.get("/core/agents/me/profile")
87
+ return AgentProfile.from_api(profile_data)
lime_agents/_client.py ADDED
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import random
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from lime_agents._errors import ApiError, AuthenticationError, LimeError, RateLimitError
11
+
12
+ logger = logging.getLogger("lime")
13
+
14
+ _RETRYABLE_STATUS = frozenset({408, 429, 500, 502, 503, 504})
15
+ _AUTH_CODES = frozenset(
16
+ {
17
+ "MISSING_AGENT_TOKEN",
18
+ "INVALID_AGENT_TOKEN",
19
+ },
20
+ )
21
+
22
+
23
+ class LimeClient:
24
+ """Internal HTTP client with envelope parsing and retries."""
25
+
26
+ def __init__(
27
+ self,
28
+ *,
29
+ agent_token: str,
30
+ base_url: str,
31
+ timeout: float,
32
+ max_retries: int,
33
+ http_client: httpx.AsyncClient | None = None,
34
+ ) -> None:
35
+ self._agent_token = agent_token
36
+ self._base_url = base_url.rstrip("/")
37
+ self._max_retries = max_retries
38
+ self._owns_client = http_client is None
39
+ self._client = http_client or httpx.AsyncClient(
40
+ timeout=timeout,
41
+ headers={"Accept": "application/json"},
42
+ )
43
+
44
+ async def aclose(self) -> None:
45
+ if self._owns_client:
46
+ await self._client.aclose()
47
+
48
+ async def get_public(self, path: str) -> dict[str, Any]:
49
+ return await self._request("GET", path, authenticated=False)
50
+
51
+ async def get(self, path: str) -> dict[str, Any]:
52
+ return await self._request("GET", path, authenticated=True)
53
+
54
+ async def post(self, path: str, body: dict[str, Any]) -> dict[str, Any]:
55
+ return await self._request("POST", path, authenticated=True, json_body=body)
56
+
57
+ async def _request(
58
+ self,
59
+ method: str,
60
+ path: str,
61
+ *,
62
+ authenticated: bool,
63
+ json_body: dict[str, Any] | None = None,
64
+ ) -> dict[str, Any]:
65
+ url = f"{self._base_url}/{path.lstrip('/')}"
66
+ headers: dict[str, str] = {}
67
+ if authenticated:
68
+ headers["X-Agent-Token"] = self._agent_token
69
+ headers["Content-Type"] = "application/json"
70
+
71
+ attempt = 0
72
+ while True:
73
+ try:
74
+ response = await self._send(method, url, headers=headers, json_body=json_body)
75
+ except (httpx.TimeoutException, httpx.TransportError) as exc:
76
+ if attempt >= self._max_retries:
77
+ raise LimeError(str(exc)) from exc
78
+ await self._backoff(attempt, method, path)
79
+ attempt += 1
80
+ continue
81
+
82
+ if response.status_code in _RETRYABLE_STATUS and attempt < self._max_retries:
83
+ logger.warning(
84
+ "Retrying %s %s after HTTP %s (attempt %s)",
85
+ method,
86
+ path,
87
+ response.status_code,
88
+ attempt + 1,
89
+ )
90
+ await self._backoff(attempt, method, path)
91
+ attempt += 1
92
+ continue
93
+
94
+ return self._parse_envelope(response)
95
+
96
+ async def _send(
97
+ self,
98
+ method: str,
99
+ url: str,
100
+ *,
101
+ headers: dict[str, str],
102
+ json_body: dict[str, Any] | None,
103
+ ) -> httpx.Response:
104
+ logger.debug("%s %s", method, url)
105
+ if method == "GET":
106
+ return await self._client.get(url, headers=headers)
107
+ return await self._client.post(url, headers=headers, json=json_body)
108
+
109
+ async def _backoff(self, attempt: int, method: str, path: str) -> None:
110
+ delay = (2**attempt) * 0.25 + random.uniform(0, 0.1)
111
+ logger.warning("Backing off %.2fs before retry %s %s", delay, method, path)
112
+ await asyncio.sleep(delay)
113
+
114
+ def _parse_envelope(self, response: httpx.Response) -> dict[str, Any]:
115
+ status = response.status_code
116
+ try:
117
+ payload = response.json()
118
+ except ValueError as exc:
119
+ raise LimeError(
120
+ f"Invalid JSON response (HTTP {status})",
121
+ http_status=status,
122
+ ) from exc
123
+
124
+ if not isinstance(payload, dict):
125
+ raise LimeError(f"Unexpected response shape (HTTP {status})", http_status=status)
126
+
127
+ if payload.get("ok") is True:
128
+ data = payload.get("data")
129
+ if not isinstance(data, dict):
130
+ raise LimeError("Success envelope missing data object", http_status=status)
131
+ return data
132
+
133
+ error = payload.get("error")
134
+ if not isinstance(error, dict):
135
+ raise LimeError(
136
+ f"Error envelope missing error object (HTTP {status})",
137
+ http_status=status,
138
+ )
139
+
140
+ code = str(error.get("code", "UNKNOWN_ERROR"))
141
+ message = str(error.get("message", "Unknown error"))
142
+ detail = error.get("detail")
143
+ detail_dict = detail if isinstance(detail, dict) else None
144
+
145
+ if status == 429 or code == "RATE_LIMIT_EXCEEDED":
146
+ raise RateLimitError(message, code=code, http_status=status, detail=detail_dict)
147
+
148
+ if status == 401 or code in _AUTH_CODES:
149
+ raise AuthenticationError(message, code=code, http_status=status, detail=detail_dict)
150
+
151
+ raise ApiError(code, message, http_status=status, detail=detail_dict)
lime_agents/_errors.py ADDED
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ class LimeError(Exception):
7
+ """Base class for SDK errors."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ *,
13
+ code: str | None = None,
14
+ http_status: int | None = None,
15
+ detail: dict[str, Any] | None = None,
16
+ ) -> None:
17
+ self.message = message
18
+ self.code = code
19
+ self.http_status = http_status
20
+ self.detail = detail
21
+ super().__init__(message)
22
+
23
+
24
+ class AuthenticationError(LimeError):
25
+ """Missing or invalid agent token."""
26
+
27
+
28
+ class PowTimeoutError(LimeError):
29
+ """PoW was not solved within the allotted time."""
30
+
31
+
32
+ class RateLimitError(LimeError):
33
+ """HTTP 429 — rate limit exceeded."""
34
+
35
+
36
+ class ApiError(LimeError):
37
+ """General API error with code and message."""
38
+
39
+ def __init__(
40
+ self,
41
+ code: str,
42
+ message: str,
43
+ *,
44
+ http_status: int,
45
+ detail: dict[str, Any] | None = None,
46
+ ) -> None:
47
+ super().__init__(
48
+ message,
49
+ code=code,
50
+ http_status=http_status,
51
+ detail=detail,
52
+ )
lime_agents/_pow.py ADDED
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import time
5
+
6
+ from lime_agents._errors import PowTimeoutError
7
+
8
+
9
+ def _is_valid_pow(challenge: str, nonce: str, difficulty: int) -> bool:
10
+ digest_hex = hashlib.sha256(f"{challenge}{nonce}".encode()).hexdigest()
11
+ threshold = 2 ** (256 - difficulty)
12
+ return bool(int(digest_hex, 16) < threshold)
13
+
14
+
15
+ def solve(challenge: str, difficulty: int, *, max_timeout: float = 10.0) -> str:
16
+ """Find a nonce satisfying the LIME PoW policy."""
17
+ if difficulty <= 0:
18
+ return "0"
19
+
20
+ deadline = time.monotonic() + max_timeout
21
+ nonce = 0
22
+ while time.monotonic() < deadline:
23
+ candidate = str(nonce)
24
+ if _is_valid_pow(challenge, candidate, difficulty):
25
+ return candidate
26
+ nonce += 1
27
+
28
+ raise PowTimeoutError(
29
+ f"PoW not solved within {max_timeout}s (difficulty={difficulty})",
30
+ )
lime_agents/_types.py ADDED
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import Any
6
+
7
+
8
+ def _parse_datetime(value: str) -> datetime:
9
+ return datetime.fromisoformat(value.replace("Z", "+00:00"))
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class ApprovalResult:
14
+ request_id: str
15
+ site_id: str
16
+ status: str
17
+ expires_at: datetime
18
+ approved_agent_id: str | None = None
19
+
20
+ @classmethod
21
+ def from_api(cls, data: dict[str, Any]) -> ApprovalResult:
22
+ return cls(
23
+ request_id=str(data["request_id"]),
24
+ site_id=str(data["site_id"]),
25
+ status=str(data["status"]),
26
+ expires_at=_parse_datetime(str(data["expires_at"])),
27
+ approved_agent_id=(
28
+ str(data["approved_agent_id"]) if data.get("approved_agent_id") else None
29
+ ),
30
+ )
31
+
32
+
33
+ @dataclass(frozen=True, slots=True)
34
+ class AgentProfile:
35
+ agent_id: str
36
+ owner_id: str
37
+ display_name: str | None
38
+ avatar_url: str | None
39
+ description: str | None
40
+ owner_kyc_level: int | None
41
+ agent_reputation: int | None
42
+
43
+ @classmethod
44
+ def from_api(cls, data: dict[str, Any]) -> AgentProfile:
45
+ return cls(
46
+ agent_id=str(data["agent_id"]),
47
+ owner_id=str(data["owner_id"]),
48
+ display_name=data.get("display_name"),
49
+ avatar_url=data.get("avatar_url"),
50
+ description=data.get("description"),
51
+ owner_kyc_level=data.get("owner_kyc_level"),
52
+ agent_reputation=data.get("agent_reputation"),
53
+ )
lime_agents/py.typed ADDED
File without changes
@@ -0,0 +1,394 @@
1
+ Metadata-Version: 2.4
2
+ Name: lime-agents-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for LIME 2.0 agent authentication. Zero-config, Proof-of-Work auto-solve, async-first.
5
+ Project-URL: Homepage, https://github.com/Mawyxx/lime-agents-sdk
6
+ Project-URL: Repository, https://github.com/Mawyxx/lime-agents-sdk
7
+ Project-URL: Documentation, https://lime.pics/docs
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: httpx<1,>=0.27.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: mypy>=1.11; extra == 'dev'
14
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
15
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
16
+ Requires-Dist: pytest>=8.0; extra == 'dev'
17
+ Requires-Dist: ruff>=0.6; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # LIME Agents SDK
21
+
22
+ [![PyPI version](https://img.shields.io/pypi/v/lime-agents-sdk)](https://pypi.org/project/lime-agents-sdk/)
23
+ [![Python versions](https://img.shields.io/pypi/pyversions/lime-agents-sdk)](https://pypi.org/project/lime-agents-sdk/)
24
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
25
+ [![CI](https://github.com/Mawyxx/lime-agents-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/Mawyxx/lime-agents-sdk/actions/workflows/ci.yml)
26
+
27
+ Official Python SDK for [LIME](https://lime.pics) agent workers. Async-first client that performs site-login approval end-to-end: fetch Proof-of-Work challenge, solve SHA-256 PoW, submit approve with retries.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install lime-agents-sdk
33
+ ```
34
+
35
+ Install the latest commit from GitHub:
36
+
37
+ ```bash
38
+ pip install git+https://github.com/Mawyxx/lime-agents-sdk.git
39
+ ```
40
+
41
+ **Requirements:** Python 3.10+
42
+
43
+ ## Quick start
44
+
45
+ Examples use readable names (`Lime`, `login`) on top of the shipped API (`LimeAgent`, `approve`). See [API reference](#api-reference) for exact types and parameters.
46
+
47
+ ### Minimal
48
+
49
+ ```python
50
+ from lime_agents import LimeAgent as _LimeAgent
51
+
52
+
53
+ class Lime(_LimeAgent):
54
+ """aiogram-style client: token first, login() instead of approve()."""
55
+
56
+ def __init__(self, token: str):
57
+ super().__init__(agent_token=token)
58
+
59
+ async def login(self, request_id: str):
60
+ return await self.approve(request_id)
61
+
62
+
63
+ AGENT_TOKEN = "at_..." # LIME Owner Portal → agent token (copy once)
64
+
65
+ async def login_to_site(request_id: str) -> str:
66
+ """Agent confirms sign-in to a site. Returns status."""
67
+ lime = Lime(AGENT_TOKEN)
68
+ try:
69
+ result = await lime.login(request_id)
70
+ return result.status # "DELIVERED"
71
+ finally:
72
+ await lime.aclose()
73
+ ```
74
+
75
+ ### Production
76
+
77
+ ```python
78
+ from lime_agents import LimeAgent as _LimeAgent, PowTimeoutError, ApiError
79
+
80
+
81
+ class Lime(_LimeAgent):
82
+ def __init__(self, token: str):
83
+ super().__init__(agent_token=token)
84
+
85
+ async def login(self, request_id: str):
86
+ return await self.approve(request_id)
87
+
88
+
89
+ AGENT_TOKEN = "at_..."
90
+
91
+
92
+ class Agent:
93
+ """Autonomous worker that signs in to sites when asked."""
94
+
95
+ def __init__(self):
96
+ self.lime = Lime(AGENT_TOKEN)
97
+
98
+ async def on_login_required(self, request_id: str) -> str | None:
99
+ """Site requires login — agent confirms."""
100
+ try:
101
+ result = await self.lime.login(request_id)
102
+ return result.status
103
+ except PowTimeoutError:
104
+ # Proof-of-Work exceeded pow_timeout (default 10s) — retry once
105
+ try:
106
+ result = await self.lime.login(request_id)
107
+ return result.status
108
+ except PowTimeoutError:
109
+ return None
110
+ except ApiError as exc:
111
+ print(f"[{exc.code}] {exc.message}")
112
+ return None
113
+ ```
114
+
115
+ ## Authentication
116
+
117
+ The SDK authenticates agent HTTP calls with the `X-Agent-Token` header.
118
+
119
+ **Resolution order:**
120
+
121
+ 1. Constructor argument `agent_token="at_..."`
122
+ 2. Environment variable `LIME_AGENT_TOKEN`
123
+
124
+ If neither is set (or the value is empty after trimming), construction raises `AuthenticationError`:
125
+
126
+ ```python
127
+ from lime_agents import LimeAgent, AuthenticationError
128
+
129
+ try:
130
+ agent = LimeAgent()
131
+ except AuthenticationError as exc:
132
+ print(exc.message)
133
+ ```
134
+
135
+ Obtain the agent token once when registering an agent in the LIME owner portal. Store it as a server-side secret in your worker environment.
136
+
137
+ ## Integration pattern: Headless agent
138
+
139
+ Typical embedding: hold one `LimeAgent` per worker process and call `approve()` when a login request arrives.
140
+
141
+ ```python
142
+ from lime_agents import LimeAgent
143
+
144
+
145
+ class TradingAgent:
146
+ def __init__(self, token: str):
147
+ self.lime = LimeAgent(agent_token=token)
148
+
149
+ async def on_login_required(self, request_id: str) -> str:
150
+ result = await self.lime.approve(request_id)
151
+ return result.status
152
+ ```
153
+
154
+ The site backend creates the request (`POST /modules/agent-login/requests`), delivers `login_request_id` to your worker, and long-polls events until status becomes `DELIVERED`. Your worker only runs the approve step above.
155
+
156
+ ## API reference
157
+
158
+ ### `LimeAgent`
159
+
160
+ Async client for agent-runtime operations (approve login, read profile).
161
+
162
+ #### Constructor
163
+
164
+ All arguments are keyword-only.
165
+
166
+ | Parameter | Type | Default | Description |
167
+ |-----------|------|---------|-------------|
168
+ | `agent_token` | `str \| None` | `None` | Agent secret. Falls back to `LIME_AGENT_TOKEN`. |
169
+ | `base_url` | `str \| None` | `None` | API root including `/api/v1`. Falls back to `LIME_API_BASE`, then `https://lime.pics/api/v1`. |
170
+ | `timeout` | `float` | `30.0` | Per-request HTTP timeout in seconds (httpx). |
171
+ | `max_retries` | `int` | `3` | Maximum retries on transient network errors and HTTP 408/429/5xx. |
172
+ | `pow_timeout` | `float` | `10.0` | Wall-clock budget in seconds for the PoW solver loop. |
173
+ | `http_client` | `httpx.AsyncClient \| None` | `None` | Inject a custom async HTTP client (tests, corporate proxy/TLS). When omitted, the SDK creates and owns a client. |
174
+
175
+ ```python
176
+ agent = LimeAgent(
177
+ agent_token="at_live_...",
178
+ base_url="https://lime.pics/api/v1",
179
+ timeout=60.0,
180
+ max_retries=5,
181
+ pow_timeout=15.0,
182
+ )
183
+ ```
184
+
185
+ **Context manager:** `async with LimeAgent() as agent:` calls `aclose()` on exit. Call `await agent.aclose()` manually when not using a context manager.
186
+
187
+ #### `async approve(request_id: str) -> ApprovalResult`
188
+
189
+ Confirms a site login request on behalf of the agent.
190
+
191
+ **Internal steps:**
192
+
193
+ 1. `GET /auth/requests/{request_id}` (public, no auth) — read `pow_challenge`, `pow_difficulty`
194
+ 2. Solve PoW: find `nonce` such that `int(SHA256(challenge + nonce), 16) < 2**(256 - difficulty)`
195
+ 3. `POST /modules/agent-login/requests/{request_id}/approve` with `X-Agent-Token` and body `{"pow_nonce": "<nonce>"}`
196
+
197
+ **Parameters:**
198
+
199
+ | Name | Type | Description |
200
+ |------|------|-------------|
201
+ | `request_id` | `str` | Login request ID from the site backend (`login_request_id` from create). |
202
+
203
+ **Returns:** `ApprovalResult` with FSM status (typically `DELIVERED` after successful approve).
204
+
205
+ ```python
206
+ from lime_agents import LimeAgent, PowTimeoutError, ApiError
207
+
208
+ async with LimeAgent() as agent:
209
+ try:
210
+ result = await agent.approve("550e8400-e29b-41d4-a716-446655440000")
211
+ print(result.status, result.approved_agent_id)
212
+ except PowTimeoutError:
213
+ print("PoW not solved in time; increase pow_timeout or retry")
214
+ except ApiError as exc:
215
+ print(exc.code, exc.http_status, exc.message)
216
+ ```
217
+
218
+ #### `async get_profile() -> AgentProfile`
219
+
220
+ Returns the authenticated agent's Core profile.
221
+
222
+ **HTTP:** `GET /core/agents/me/profile` with `X-Agent-Token`.
223
+
224
+ ```python
225
+ async with LimeAgent() as agent:
226
+ profile = await agent.get_profile()
227
+ print(profile.agent_id)
228
+ print(profile.owner_kyc_level)
229
+ print(profile.agent_reputation)
230
+ ```
231
+
232
+ ## Types
233
+
234
+ ### `ApprovalResult`
235
+
236
+ Frozen dataclass returned by `approve()`.
237
+
238
+ | Field | Type | Description |
239
+ |-------|------|-------------|
240
+ | `request_id` | `str` | Login request ID |
241
+ | `site_id` | `str` | Site that created the request |
242
+ | `status` | `str` | FSM value, e.g. `APPROVED`, `DELIVERED` |
243
+ | `expires_at` | `datetime` | Request expiry (timezone-aware when API sends offset) |
244
+ | `approved_agent_id` | `str \| None` | Agent that approved (set after approve) |
245
+
246
+ ### `AgentProfile`
247
+
248
+ Frozen dataclass returned by `get_profile()`. Matches `GET /core/agents/me/profile` response fields.
249
+
250
+ | Field | Type | Description |
251
+ |-------|------|-------------|
252
+ | `agent_id` | `str` | Agent identifier |
253
+ | `owner_id` | `str` | Owning LIME user |
254
+ | `display_name` | `str \| None` | Public display name |
255
+ | `avatar_url` | `str \| None` | Avatar URL |
256
+ | `description` | `str \| None` | Public description |
257
+ | `owner_kyc_level` | `int \| None` | Owner KYC level synced from Foundation |
258
+ | `agent_reputation` | `int \| None` | Reputation score |
259
+
260
+ ## Error handling
261
+
262
+ All SDK exceptions inherit from `LimeError`. Each carries `message`, and optionally `code`, `http_status`, and `detail` (API envelope).
263
+
264
+ | Exception | When |
265
+ |-----------|------|
266
+ | `LimeError` | Base class; transport failures after retries, malformed JSON |
267
+ | `AuthenticationError` | Missing/empty token at construct; HTTP 401; `MISSING_AGENT_TOKEN`, `INVALID_AGENT_TOKEN` |
268
+ | `PowTimeoutError` | PoW solver exceeded `pow_timeout` |
269
+ | `RateLimitError` | HTTP 429 / `RATE_LIMIT_EXCEEDED` |
270
+ | `ApiError` | Other API errors (`ok: false` envelope) |
271
+
272
+ `ApiError` attributes: `code`, `message`, `http_status`, `detail`.
273
+
274
+ ```python
275
+ import asyncio
276
+
277
+ from lime_agents import (
278
+ LimeAgent,
279
+ LimeError,
280
+ AuthenticationError,
281
+ PowTimeoutError,
282
+ RateLimitError,
283
+ ApiError,
284
+ )
285
+
286
+ async def run() -> None:
287
+ try:
288
+ async with LimeAgent() as agent:
289
+ await agent.approve("lr_abc123")
290
+ except AuthenticationError as exc:
291
+ print("auth:", exc.message)
292
+ except PowTimeoutError as exc:
293
+ print("pow:", exc.message)
294
+ except RateLimitError as exc:
295
+ print("rate limit:", exc.http_status)
296
+ except ApiError as exc:
297
+ print(f"api [{exc.http_status}] {exc.code}: {exc.message}")
298
+ except LimeError as exc:
299
+ print("sdk:", exc.message)
300
+
301
+ asyncio.run(run())
302
+ ```
303
+
304
+ **Non-retried HTTP statuses:** 400, 401, 403, 404, 409 (e.g. `INVALID_POW`, `SITE_LOGIN_CONFLICT`).
305
+
306
+ ## Configuration
307
+
308
+ ### Environment variables
309
+
310
+ | Variable | Required | Description |
311
+ |----------|----------|-------------|
312
+ | `LIME_AGENT_TOKEN` | Yes (unless `agent_token=` passed) | Agent secret (`at_...`) |
313
+ | `LIME_API_BASE` | No | API root, e.g. `https://lime.pics/api/v1` |
314
+
315
+ ### Constructor tuning
316
+
317
+ | Use case | Suggestion |
318
+ |----------|------------|
319
+ | Slow network | Increase `timeout` (e.g. `60.0`) |
320
+ | Flaky upstream | Increase `max_retries` (e.g. `5`) |
321
+ | High PoW difficulty / slow CPU | Increase `pow_timeout` (e.g. `30.0`) |
322
+ | Staging / self-hosted API | Set `base_url` or `LIME_API_BASE` |
323
+
324
+ ### Logging
325
+
326
+ HTTP and retry events are logged under the **`lime`** logger (not `lime_agents`):
327
+
328
+ ```python
329
+ import logging
330
+
331
+ logging.basicConfig(level=logging.DEBUG)
332
+ logging.getLogger("lime").setLevel(logging.DEBUG)
333
+ ```
334
+
335
+ At `DEBUG`, the client logs request method and URL. Tokens, `pow_challenge`, and `pow_nonce` are never logged.
336
+
337
+ ## Advanced usage
338
+
339
+ ### Custom `httpx.AsyncClient`
340
+
341
+ Inject a client for custom TLS, proxies, or tests. **You own the client lifecycle** when injecting; the SDK does not close an injected client.
342
+
343
+ ```python
344
+ import httpx
345
+ from lime_agents import LimeAgent
346
+
347
+
348
+ async def approve_with_proxy() -> None:
349
+ client = httpx.AsyncClient(
350
+ timeout=60.0,
351
+ verify="/path/to/corporate-ca.pem",
352
+ proxy="http://proxy.corp.example:8080",
353
+ )
354
+ agent = LimeAgent(agent_token="at_...", http_client=client)
355
+ try:
356
+ await agent.approve("lr_abc123")
357
+ finally:
358
+ await client.aclose()
359
+ ```
360
+
361
+ ### Retries and timeouts
362
+
363
+ Retries use exponential backoff with jitter on connection errors, timeouts, and HTTP 408, 429, 500, 502, 503, 504. Each retry attempt is bounded by `max_retries` (default 3).
364
+
365
+ ```python
366
+ agent = LimeAgent(
367
+ agent_token="at_...",
368
+ max_retries=5,
369
+ timeout=45.0,
370
+ pow_timeout=20.0,
371
+ )
372
+ ```
373
+
374
+ ### PoW debugging
375
+
376
+ PoW runs in a thread pool (`asyncio.to_thread`) so the event loop stays responsive. To observe HTTP flow (not nonce values):
377
+
378
+ ```python
379
+ import logging
380
+
381
+ logging.getLogger("lime").setLevel(logging.DEBUG)
382
+ ```
383
+
384
+ If `PowTimeoutError` occurs, increase `pow_timeout` or verify `pow_difficulty` from `GET /auth/requests/{id}` (default 15 on production).
385
+
386
+ ## Links
387
+
388
+ - **SDK repository:** [github.com/Mawyxx/lime-agents-sdk](https://github.com/Mawyxx/lime-agents-sdk)
389
+ - **LIME API docs:** [lime.pics/docs](https://lime.pics/docs)
390
+ - **LIME platform:** [github.com/Mawyxx/Lime](https://github.com/Mawyxx/Lime)
391
+
392
+ ## License
393
+
394
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,11 @@
1
+ lime_agents/__init__.py,sha256=Qq1LkGhxCL7H7rsEmqyEjj_ibYpiubiQ0Sti4vBRIQ4,507
2
+ lime_agents/_agent.py,sha256=Bs8K8C1-9buQEhamEq5ZZK2erARzrbWwLTKryJzWuUI,2679
3
+ lime_agents/_client.py,sha256=lLysm5Y9WCRQCSXJo43iUaCCzrNsC4aiUXkDf6Fj4QQ,5122
4
+ lime_agents/_errors.py,sha256=Ffrugaa_Acnl8xxKCljg3uZTueWwvm8qhjneR90Gb_4,1132
5
+ lime_agents/_pow.py,sha256=IjfmcfihScceC3CoNXqNF7IzaXgpvI78Haryt_qiLzk,880
6
+ lime_agents/_types.py,sha256=K7uOBH9_nbInTOEH7k46JSUa8ehOjYcQwYsxYgaugh8,1559
7
+ lime_agents/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ lime_agents_sdk-0.1.0.dist-info/METADATA,sha256=9FG2FNsJ4xBoLndth22ypvilscv9Ae7w4iPz0q01k0I,12606
9
+ lime_agents_sdk-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
10
+ lime_agents_sdk-0.1.0.dist-info/licenses/LICENSE,sha256=oMe8Sdbwa3jl3bGG50j_6QicG-Zm_aQwFMq60XW3H9M,1061
11
+ lime_agents_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LIME
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.