lime-agents-sdk 0.2.0__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.
@@ -0,0 +1,64 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ tags: ["v*"]
7
+ pull_request:
8
+ branches: [main, master]
9
+
10
+ jobs:
11
+ quality:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
17
+ steps:
18
+ - name: Checkout
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Install package (dev)
27
+ run: pip install -e ".[dev]"
28
+
29
+ - name: Ruff
30
+ run: ruff check src tests
31
+
32
+ - name: Mypy
33
+ if: matrix.python-version == '3.12'
34
+ run: mypy src/lime_agents
35
+
36
+ - name: Pytest (100% coverage)
37
+ run: pytest --cov=lime_agents --cov-fail-under=100
38
+
39
+ publish:
40
+ needs: quality
41
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
42
+ runs-on: ubuntu-latest
43
+ permissions:
44
+ id-token: write
45
+ steps:
46
+ - name: Checkout
47
+ uses: actions/checkout@v4
48
+
49
+ - name: Set up Python
50
+ uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.12"
53
+
54
+ - name: Install build tools
55
+ run: pip install build hatchling
56
+
57
+ - name: Build package
58
+ run: python -m build
59
+
60
+ - name: Publish to PyPI
61
+ uses: pypa/gh-action-pypi-publish@release/v1
62
+ with:
63
+ attestations: false
64
+ skip-existing: true
@@ -0,0 +1,38 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+
7
+ # Virtual environments
8
+ .venv/
9
+ venv/
10
+ env/
11
+
12
+ # Integration test token cache (never commit)
13
+ tests/integration/.tokens.env
14
+
15
+ # Test / type / lint caches
16
+ .pytest_cache/
17
+ .coverage
18
+ htmlcov/
19
+ coverage.xml
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+ .hypothesis/
23
+
24
+ # Build artifacts
25
+ dist/
26
+ build/
27
+ *.egg-info/
28
+ *.egg
29
+
30
+ # IDE
31
+ .idea/
32
+ .vscode/
33
+ *.swp
34
+ *.swo
35
+
36
+ # OS
37
+ .DS_Store
38
+ Thumbs.db
@@ -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.
@@ -0,0 +1,434 @@
1
+ Metadata-Version: 2.4
2
+ Name: lime-agents-sdk
3
+ Version: 0.2.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 with one public login method: fetch Proof-of-Work challenge, solve SHA-256 PoW, submit approval 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`, `login`). 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() entrypoint."""
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.login(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.login(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 `login()` 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.login(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 login step above.
155
+
156
+ ## Production deployment
157
+
158
+ For agent workers handling many login jobs, create **one** `LimeAgent` per worker process at startup. Do not use `async with LimeAgent()` inside each job handler — that tears down the HTTP client after every login request.
159
+
160
+ - Instantiate `LimeAgent` once when the worker starts.
161
+ - Reuse the same instance for all `login()` and `get_profile()` calls.
162
+ - Optionally pass a shared `httpx.AsyncClient` via `http_client` for connection pooling.
163
+
164
+ ```python
165
+ from lime_agents import LimeAgent
166
+
167
+ # Created once at worker startup
168
+ agent = LimeAgent()
169
+
170
+ # Reused in every task
171
+ async def handle_login(request_id: str) -> str:
172
+ result = await agent.login(request_id)
173
+ return result.status
174
+ ```
175
+
176
+ Call `agent.aclose()` only on worker shutdown (or close your injected `http_client` yourself).
177
+
178
+ ## API reference
179
+
180
+ ### `LimeAgent`
181
+
182
+ Async client for agent-runtime operations (login confirmation, read profile).
183
+
184
+ #### Constructor
185
+
186
+ All arguments are keyword-only.
187
+
188
+ | Parameter | Type | Default | Description |
189
+ |-----------|------|---------|-------------|
190
+ | `agent_token` | `str \| None` | `None` | Agent secret. Falls back to `LIME_AGENT_TOKEN`. |
191
+ | `base_url` | `str \| None` | `None` | API root including `/api/v1`. Falls back to `LIME_API_BASE`, then `https://lime.pics/api/v1`. |
192
+ | `timeout` | `float` | `30.0` | Per-request HTTP timeout in seconds (httpx). |
193
+ | `max_retries` | `int` | `3` | Maximum retries on transient network errors and HTTP 408/429/5xx. |
194
+ | `pow_timeout` | `float` | `10.0` | Wall-clock budget in seconds for the PoW solver loop. |
195
+ | `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. |
196
+
197
+ ```python
198
+ agent = LimeAgent(
199
+ agent_token="at_live_...",
200
+ base_url="https://lime.pics/api/v1",
201
+ timeout=60.0,
202
+ max_retries=5,
203
+ pow_timeout=15.0,
204
+ )
205
+ ```
206
+
207
+ **Context manager:** `async with LimeAgent() as agent:` calls `aclose()` on exit. Call `await agent.aclose()` manually when not using a context manager.
208
+
209
+ #### `async login(request_id: str) -> ApprovalResult`
210
+
211
+ Confirms a site login request on behalf of the agent.
212
+
213
+ **Internal steps:**
214
+
215
+ 1. `GET /auth/requests/{request_id}` (public, no auth) — read `pow_challenge`, `pow_difficulty`
216
+ 2. Solve PoW: find `nonce` such that `int(SHA256(challenge + nonce), 16) < 2**(256 - difficulty)`
217
+ 3. `POST /modules/agent-login/requests/{request_id}/approve` with `X-Agent-Token` and body `{"pow_nonce": "<nonce>"}`
218
+
219
+ **Parameters:**
220
+
221
+ | Name | Type | Description |
222
+ |------|------|-------------|
223
+ | `request_id` | `str` | Login request ID from the site backend (`login_request_id` from create). |
224
+
225
+ **Returns:** `ApprovalResult` with FSM status (typically `DELIVERED` after successful login confirmation).
226
+
227
+ ```python
228
+ from lime_agents import LimeAgent, PowTimeoutError, ApiError
229
+
230
+ async with LimeAgent() as agent:
231
+ try:
232
+ result = await agent.login("550e8400-e29b-41d4-a716-446655440000")
233
+ print(result.status, result.approved_agent_id)
234
+ except PowTimeoutError:
235
+ print("PoW not solved in time; increase pow_timeout or retry")
236
+ except ApiError as exc:
237
+ print(exc.code, exc.http_status, exc.message)
238
+ ```
239
+
240
+ #### `async get_profile() -> AgentProfile`
241
+
242
+ Returns the authenticated agent's Core profile.
243
+
244
+ **HTTP:** `GET /core/agents/me/profile` with `X-Agent-Token`.
245
+
246
+ ```python
247
+ async with LimeAgent() as agent:
248
+ profile = await agent.get_profile()
249
+ print(profile.agent_id)
250
+ print(profile.owner_kyc_level)
251
+ print(profile.agent_reputation)
252
+ ```
253
+
254
+ ## Types
255
+
256
+ ### `ApprovalResult`
257
+
258
+ Frozen dataclass returned by `login()`.
259
+
260
+ | Field | Type | Description |
261
+ |-------|------|-------------|
262
+ | `request_id` | `str` | Login request ID |
263
+ | `site_id` | `str` | Site that created the request |
264
+ | `status` | `str` | FSM value, e.g. `APPROVED`, `DELIVERED` |
265
+ | `expires_at` | `datetime` | Request expiry (timezone-aware when API sends offset) |
266
+ | `approved_agent_id` | `str \| None` | Agent that approved the request |
267
+
268
+ ### `AgentProfile`
269
+
270
+ Frozen dataclass returned by `get_profile()`. Matches `GET /core/agents/me/profile` response fields.
271
+
272
+ | Field | Type | Description |
273
+ |-------|------|-------------|
274
+ | `agent_id` | `str` | Agent identifier |
275
+ | `owner_id` | `str` | Owning LIME user |
276
+ | `display_name` | `str \| None` | Public display name |
277
+ | `avatar_url` | `str \| None` | Avatar URL |
278
+ | `description` | `str \| None` | Public description |
279
+ | `owner_kyc_level` | `int \| None` | Owner KYC level synced from Foundation |
280
+ | `agent_reputation` | `int \| None` | Reputation score |
281
+
282
+ ## Error handling
283
+
284
+ All SDK exceptions inherit from `LimeError`. Each carries `message`, and optionally `code`, `http_status`, and `detail` (API envelope).
285
+
286
+ | Exception | When |
287
+ |-----------|------|
288
+ | `LimeError` | Base class; transport failures after retries, malformed JSON |
289
+ | `AuthenticationError` | Missing/empty token at construct; HTTP 401; `MISSING_AGENT_TOKEN`, `INVALID_AGENT_TOKEN` |
290
+ | `PowTimeoutError` | PoW solver exceeded `pow_timeout` |
291
+ | `RateLimitError` | HTTP 429 / `RATE_LIMIT_EXCEEDED` |
292
+ | `ApiError` | Other API errors (`ok: false` envelope) |
293
+
294
+ `ApiError` attributes: `code`, `message`, `http_status`, `detail`.
295
+
296
+ ```python
297
+ import asyncio
298
+
299
+ from lime_agents import (
300
+ LimeAgent,
301
+ LimeError,
302
+ AuthenticationError,
303
+ PowTimeoutError,
304
+ RateLimitError,
305
+ ApiError,
306
+ )
307
+
308
+ async def run() -> None:
309
+ try:
310
+ async with LimeAgent() as agent:
311
+ await agent.login("lr_abc123")
312
+ except AuthenticationError as exc:
313
+ print("auth:", exc.message)
314
+ except PowTimeoutError as exc:
315
+ print("pow:", exc.message)
316
+ except RateLimitError as exc:
317
+ print("rate limit:", exc.http_status)
318
+ except ApiError as exc:
319
+ print(f"api [{exc.http_status}] {exc.code}: {exc.message}")
320
+ except LimeError as exc:
321
+ print("sdk:", exc.message)
322
+
323
+ asyncio.run(run())
324
+ ```
325
+
326
+ **Non-retried HTTP statuses:** 400, 401, 403, 404, 409 (e.g. `INVALID_POW`, `SITE_LOGIN_CONFLICT`).
327
+
328
+ ## Configuration
329
+
330
+ ### Environment variables
331
+
332
+ | Variable | Required | Description |
333
+ |----------|----------|-------------|
334
+ | `LIME_AGENT_TOKEN` | Yes (unless `agent_token=` passed) | Agent secret (`at_...`) |
335
+ | `LIME_API_BASE` | No | API root, e.g. `https://lime.pics/api/v1` |
336
+
337
+ ### Constructor tuning
338
+
339
+ | Use case | Suggestion |
340
+ |----------|------------|
341
+ | Slow network | Increase `timeout` (e.g. `60.0`) |
342
+ | Flaky upstream | Increase `max_retries` (e.g. `5`) |
343
+ | High PoW difficulty / slow CPU | Increase `pow_timeout` (e.g. `30.0`) |
344
+ | Staging / self-hosted API | Set `base_url` or `LIME_API_BASE` |
345
+
346
+ ### Logging
347
+
348
+ HTTP and retry events are logged under the **`lime`** logger (not `lime_agents`):
349
+
350
+ ```python
351
+ import logging
352
+
353
+ logging.basicConfig(level=logging.DEBUG)
354
+ logging.getLogger("lime").setLevel(logging.DEBUG)
355
+ ```
356
+
357
+ At `DEBUG`, the client logs request method and URL. Tokens, `pow_challenge`, and `pow_nonce` are never logged.
358
+
359
+ ## Advanced usage
360
+
361
+ ### Custom `httpx.AsyncClient`
362
+
363
+ Inject a client for custom TLS, proxies, or tests. **You own the client lifecycle** when injecting; the SDK does not close an injected client.
364
+
365
+ ```python
366
+ import httpx
367
+ from lime_agents import LimeAgent
368
+
369
+
370
+ async def login_with_proxy() -> None:
371
+ client = httpx.AsyncClient(
372
+ timeout=60.0,
373
+ verify="/path/to/corporate-ca.pem",
374
+ proxy="http://proxy.corp.example:8080",
375
+ )
376
+ agent = LimeAgent(agent_token="at_...", http_client=client)
377
+ try:
378
+ await agent.login("lr_abc123")
379
+ finally:
380
+ await client.aclose()
381
+ ```
382
+
383
+ ### Retries and timeouts
384
+
385
+ 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).
386
+
387
+ ```python
388
+ agent = LimeAgent(
389
+ agent_token="at_...",
390
+ max_retries=5,
391
+ timeout=45.0,
392
+ pow_timeout=20.0,
393
+ )
394
+ ```
395
+
396
+ ### PoW debugging
397
+
398
+ PoW runs in a thread pool (`asyncio.to_thread`) so the event loop stays responsive. To observe HTTP flow (not nonce values):
399
+
400
+ ```python
401
+ import logging
402
+
403
+ logging.getLogger("lime").setLevel(logging.DEBUG)
404
+ ```
405
+
406
+ If `PowTimeoutError` occurs, increase `pow_timeout` or verify `pow_difficulty` from `GET /auth/requests/{id}` (default 15 on production).
407
+
408
+ ## Development
409
+
410
+ ```bash
411
+ pip install -e ".[dev]"
412
+ ruff check src tests
413
+ mypy src/lime_agents
414
+ pytest --cov=lime_agents --cov-fail-under=100
415
+ ```
416
+
417
+ ### Live integration
418
+
419
+ ```bash
420
+ pip install lime-agents-sdk lime-sites-sdk
421
+ LIME_INTEGRATION=1 pytest tests/integration -v
422
+ ```
423
+
424
+ **Full cycle (both SDKs):** see [lime-sait-sdk `tests/integration/test_full_cycle_both_sdks.py`](https://github.com/Mawyxx/lime-sait-sdk/blob/main/tests/integration/test_full_cycle_both_sdks.py) — site `LimeSite` + agent `LimeAgent` against `https://lime.pics/api/v1`. From the LIME monorepo, run on the production VPS (SSE-safe): `python scripts/_run_both_sdks_integration_remote.py`.
425
+
426
+ ## Links
427
+
428
+ - **SDK repository:** [github.com/Mawyxx/lime-agents-sdk](https://github.com/Mawyxx/lime-agents-sdk)
429
+ - **LIME API docs:** [lime.pics/docs](https://lime.pics/docs)
430
+ - **LIME platform:** [github.com/Mawyxx/Lime](https://github.com/Mawyxx/Lime)
431
+
432
+ ## License
433
+
434
+ MIT — see [LICENSE](LICENSE).