Skip to content

Commit 178a5e1

Browse files
author
AgentSIM
committed
feat: extract Python SDK from monorepo
- agentsim-sdk v0.9.0 - Async and sync context managers - Auto-reroute on carrier timeout - Full error hierarchy - Unit and integration tests
1 parent 6691086 commit 178a5e1

12 files changed

Lines changed: 1319 additions & 0 deletions

File tree

‎.env.example‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
AGENTSIM_API_KEY=asm_live_your_key_here
2+
# For local development against a local API server:
3+
# AGENTSIM_BASE_URL=http://localhost:3000

‎.gitignore‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
__pycache__/
2+
*.py[cod]
3+
*$py.class
4+
*.egg-info/
5+
dist/
6+
build/
7+
.eggs/
8+
*.egg
9+
.env
10+
.venv/
11+
venv/
12+
.pytest_cache/
13+
.mypy_cache/
14+
.ruff_cache/

‎README.md‎

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<p align="center">
2+
<a href="https://agentsim.dev">
3+
<img src="https://agentsim.dev/logo.svg" alt="AgentSIM" width="80" />
4+
</a>
5+
</p>
6+
7+
<h1 align="center">agentsim-sdk</h1>
8+
9+
<p align="center">
10+
<strong>Python SDK for AgentSIM — real SIM-backed phone numbers for AI agents</strong>
11+
</p>
12+
13+
<p align="center">
14+
<a href="https://pypi.org/project/agentsim-sdk/"><img src="https://img.shields.io/pypi/v/agentsim-sdk?color=%2334D058&label=pypi" alt="PyPI version"></a>
15+
<a href="https://pypi.org/project/agentsim-sdk/"><img src="https://img.shields.io/pypi/pyversions/agentsim-sdk" alt="Python versions"></a>
16+
<a href="https://github.com/agentsimdev/agentsim-python/blob/main/LICENSE"><img src="https://img.shields.io/github/license/agentsimdev/agentsim-python" alt="License"></a>
17+
</p>
18+
19+
<p align="center">
20+
<a href="https://docs.agentsim.dev">Docs</a> ·
21+
<a href="https://agentsim.dev/dashboard">Dashboard</a> ·
22+
<a href="https://github.com/agentsimdev/agentsim-examples">Examples</a> ·
23+
<a href="https://github.com/agentsimdev/agentsim-mcp">MCP Server</a>
24+
</p>
25+
26+
---
27+
28+
Provision real carrier-routed mobile numbers, receive inbound SMS, and get parsed OTP codes — all from your AI agent. No VoIP. No human relay. Carrier lookup returns `mobile`.
29+
30+
## Install
31+
32+
```bash
33+
pip install agentsim-sdk
34+
```
35+
36+
Or with [uv](https://docs.astral.sh/uv/):
37+
38+
```bash
39+
uv add agentsim-sdk
40+
```
41+
42+
## Quick Start
43+
44+
```python
45+
import agentsim
46+
47+
async with agentsim.provision(agent_id="checkout-bot", country="US") as num:
48+
await enter_phone_number(num.number) # "+14155552671"
49+
otp = await num.wait_for_otp(timeout=60)
50+
await enter_otp(otp.otp_code) # "391847"
51+
# number auto-released
52+
```
53+
54+
## Authentication
55+
56+
Set `AGENTSIM_API_KEY` in your environment, or call `configure()` at startup:
57+
58+
```python
59+
agentsim.configure(api_key="asm_live_xxx")
60+
```
61+
62+
Get your API key at [agentsim.dev/dashboard](https://agentsim.dev/dashboard).
63+
64+
## Usage
65+
66+
### Async (recommended)
67+
68+
```python
69+
import agentsim
70+
71+
async with agentsim.provision(agent_id="signup-bot", country="US") as num:
72+
print(num.number) # E.164 phone number
73+
print(num.session_id) # session identifier
74+
75+
otp = await num.wait_for_otp(timeout=60)
76+
print(otp.otp_code) # "847291"
77+
```
78+
79+
### Sync
80+
81+
```python
82+
import agentsim
83+
84+
with agentsim.provision_sync(agent_id="checkout-bot") as num:
85+
otp = num.wait_for_otp_sync(timeout=60)
86+
print(otp.otp_code)
87+
```
88+
89+
### Auto-reroute
90+
91+
If the first number doesn't receive an OTP (carrier cold-start filtering), automatically swap to a fresh number:
92+
93+
```python
94+
async with agentsim.provision(agent_id="resilient-bot") as num:
95+
otp = await num.wait_for_otp(
96+
timeout=60,
97+
auto_reroute=True,
98+
max_reroutes=2,
99+
on_reregistration_needed=handle_new_number,
100+
)
101+
```
102+
103+
## API Reference
104+
105+
### `agentsim.provision()`
106+
107+
| Parameter | Type | Default | Description |
108+
|-----------|------|---------|-------------|
109+
| `agent_id` | `str` | required | Identifier for your agent |
110+
| `country` | `str` | `"US"` | ISO 3166-1 alpha-2 country code |
111+
| `ttl_seconds` | `int` | `3600` | Auto-release after N seconds |
112+
| `webhook_url` | `str` | `None` | Receive OTPs via webhook |
113+
114+
Returns an async context manager. Provisions a number on enter, auto-releases on exit.
115+
116+
### `num.wait_for_otp()`
117+
118+
| Parameter | Type | Default | Description |
119+
|-----------|------|---------|-------------|
120+
| `timeout` | `int` | `60` | Max seconds to wait |
121+
| `auto_reroute` | `bool` | `False` | Swap number on timeout |
122+
| `max_reroutes` | `int` | `2` | Max reroute attempts |
123+
124+
Returns `OtpResult(otp_code, from_number, received_at)`.
125+
126+
### `num.release()`
127+
128+
Release the number early. Called automatically when the context manager exits.
129+
130+
## Error Handling
131+
132+
```python
133+
from agentsim import OtpTimeoutError, PoolExhaustedError
134+
135+
try:
136+
async with agentsim.provision(agent_id="my-bot") as num:
137+
otp = await num.wait_for_otp(timeout=30)
138+
except OtpTimeoutError:
139+
print("No OTP received — not billed")
140+
except PoolExhaustedError:
141+
print("No numbers available in this country")
142+
```
143+
144+
| Exception | HTTP | When |
145+
|-----------|------|------|
146+
| `AuthenticationError` | 401 | Missing or invalid API key |
147+
| `ForbiddenError` | 403 | Key revoked or lacking permissions |
148+
| `PoolExhaustedError` | 503 | No numbers available in requested country |
149+
| `OtpTimeoutError` | 408 | No OTP arrived within timeout (not billed) |
150+
| `RateLimitError` | 429 | Too many requests |
151+
| `SessionNotFoundError` | 404 | Session expired or already released |
152+
| `CountryNotAllowedError` | 403 | Country not on your plan |
153+
154+
## Pricing
155+
156+
- **Hobby**: 10 free sessions/month
157+
- **Builder**: $0.99/session
158+
- Sessions that time out (`OtpTimeoutError`) are **not billed**
159+
160+
## Links
161+
162+
- [Documentation](https://docs.agentsim.dev)
163+
- [TypeScript SDK](https://github.com/agentsimdev/agentsim-typescript)
164+
- [MCP Server](https://github.com/agentsimdev/agentsim-mcp)
165+
- [Examples](https://github.com/agentsimdev/agentsim-examples)
166+
167+
## License
168+
169+
[MIT](LICENSE)

‎agentsim/__init__.py‎

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""AgentSIM Python SDK — autonomous OTP relay for AI agents."""
2+
3+
from __future__ import annotations
4+
5+
import os
6+
from typing import Optional
7+
8+
from .client import AgentSimClient, NumberSession, _SyncNumberSessionCtx
9+
from .exceptions import (
10+
AgentSimError,
11+
AuthenticationError,
12+
ForbiddenError,
13+
OtpTimeoutError,
14+
PoolExhaustedError,
15+
RateLimitError,
16+
SessionNotFoundError,
17+
ValidationError,
18+
ApiError,
19+
)
20+
from .models import OtpResult, ProvisionedNumber, SmsMessage, UsageStats
21+
22+
__all__ = [
23+
"configure",
24+
"provision",
25+
"provision_sync",
26+
"AgentSimClient",
27+
"NumberSession",
28+
"AgentSimError",
29+
"AuthenticationError",
30+
"ForbiddenError",
31+
"OtpTimeoutError",
32+
"PoolExhaustedError",
33+
"RateLimitError",
34+
"SessionNotFoundError",
35+
"ValidationError",
36+
"ApiError",
37+
"OtpResult",
38+
"ProvisionedNumber",
39+
"SmsMessage",
40+
"UsageStats",
41+
]
42+
43+
_default_api_key: Optional[str] = os.environ.get("AGENTSIM_API_KEY")
44+
_default_base_url: str = os.environ.get("AGENTSIM_BASE_URL", "https://api.agentsim.dev/v1")
45+
46+
47+
class _AsyncProvisionCtx:
48+
"""Wraps the async provision coroutine so `async with agentsim.provision(...)` works."""
49+
50+
__slots__ = ("_coro", "_session")
51+
52+
def __init__(self, coro: object) -> None:
53+
self._coro = coro
54+
self._session: Optional[NumberSession] = None
55+
56+
async def __aenter__(self) -> NumberSession:
57+
self._session = await self._coro # type: ignore[misc]
58+
return self._session
59+
60+
async def __aexit__(self, *args: object) -> None:
61+
if self._session is not None:
62+
await self._session.__aexit__(*args)
63+
64+
65+
def configure(*, api_key: str, base_url: Optional[str] = None) -> None:
66+
"""Set module-level defaults used by `provision()` and `provision_sync()`."""
67+
global _default_api_key, _default_base_url
68+
_default_api_key = api_key
69+
if base_url:
70+
_default_base_url = base_url
71+
72+
73+
def provision(
74+
*,
75+
agent_id: str,
76+
country: Optional[str] = None,
77+
service_url: Optional[str] = None,
78+
ttl_seconds: int = 3600,
79+
webhook_url: Optional[str] = None,
80+
api_key: Optional[str] = None,
81+
base_url: Optional[str] = None,
82+
) -> NumberSession:
83+
"""Async context manager — provision a number, auto-release on exit.
84+
85+
Starts a billable session. $0.99 per session on the Builder plan.
86+
Free on Hobby (10 sessions/month limit). Sessions that raise
87+
``OtpTimeoutError`` are NOT billed.
88+
89+
Usage::
90+
91+
async with agentsim.provision(agent_id="checkout-bot") as num:
92+
otp = await num.wait_for_otp(timeout=60)
93+
"""
94+
resolved_key = api_key or _default_api_key
95+
if not resolved_key:
96+
raise AuthenticationError(
97+
"No API key provided. Set AGENTSIM_API_KEY or call agentsim.configure(api_key=...)."
98+
)
99+
client = AgentSimClient(resolved_key, base_url=base_url or _default_base_url)
100+
return _AsyncProvisionCtx(client.provision(
101+
agent_id=agent_id,
102+
country=country,
103+
service_url=service_url,
104+
ttl_seconds=ttl_seconds,
105+
webhook_url=webhook_url,
106+
))
107+
108+
109+
def provision_sync(
110+
*,
111+
agent_id: str,
112+
country: Optional[str] = None,
113+
service_url: Optional[str] = None,
114+
ttl_seconds: int = 3600,
115+
webhook_url: Optional[str] = None,
116+
api_key: Optional[str] = None,
117+
base_url: Optional[str] = None,
118+
) -> _SyncNumberSessionCtx:
119+
"""Synchronous context manager — provision a number, auto-release on exit.
120+
121+
Starts a billable session. $0.99 per session on the Builder plan.
122+
Free on Hobby (10 sessions/month limit). Sessions that raise
123+
``OtpTimeoutError`` are NOT billed.
124+
125+
Usage::
126+
127+
with agentsim.provision_sync(agent_id="checkout-bot") as num:
128+
otp = num.wait_for_otp_sync(timeout=60)
129+
"""
130+
resolved_key = api_key or _default_api_key
131+
if not resolved_key:
132+
raise AuthenticationError(
133+
"No API key provided. Set AGENTSIM_API_KEY or call agentsim.configure(api_key=...)."
134+
)
135+
from .client import provision_sync as _ps
136+
137+
return _ps(
138+
resolved_key,
139+
agent_id=agent_id,
140+
country=country,
141+
service_url=service_url,
142+
ttl_seconds=ttl_seconds,
143+
webhook_url=webhook_url,
144+
base_url=base_url or _default_base_url,
145+
)

0 commit comments

Comments
 (0)