agentphone 0.1.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,5 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ *.egg-info/
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentphone
3
+ Version: 0.1.0
4
+ Summary: AgentPhone Python SDK — give your AI agents phone numbers, SMS, and voice calls
5
+ Project-URL: Homepage, https://agentphone.to
6
+ Project-URL: Repository, https://github.com/AgentPhone-AI/agentphone-python
7
+ Project-URL: Documentation, https://docs.agentphone.to
8
+ Author-email: AgentPhone <hello@agentphone.to>
9
+ License: MIT
10
+ Keywords: agentphone,ai,phone,sms,telephony,voice
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Communications :: Telephony
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: requests>=2.28
23
+ Provides-Extra: async
24
+ Requires-Dist: httpx>=0.24; extra == 'async'
25
+ Provides-Extra: dev
26
+ Requires-Dist: hatch; extra == 'dev'
27
+ Requires-Dist: httpx; extra == 'dev'
28
+ Requires-Dist: pytest; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # AgentPhone Python SDK
33
+
34
+ Official Python SDK for [AgentPhone](https://agentphone.to) — give your AI agents real phone numbers, SMS, and voice calls.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install agentphone
40
+ ```
41
+
42
+ For async support:
43
+
44
+ ```bash
45
+ pip install agentphone[async]
46
+ ```
47
+
48
+ ## Quickstart
49
+
50
+ ```python
51
+ from agentphone import AgentPhone
52
+
53
+ client = AgentPhone(api_key="your-api-key")
54
+
55
+ # Create an agent and buy a number
56
+ agent = client.agents.create(name="My Agent")
57
+ number = client.numbers.buy(country="US", agent_id=agent.id)
58
+
59
+ # Make an AI conversation call — no webhook needed
60
+ call = client.calls.make_conversation(
61
+ agent_id=agent.id,
62
+ to_number="+14155551234",
63
+ topic="You are a friendly assistant. Ask about their day.",
64
+ initial_greeting="Hey! This is an AI calling from AgentPhone.",
65
+ )
66
+ print(call.status) # registered
67
+ ```
68
+
69
+ ## Async
70
+
71
+ ```python
72
+ from agentphone import AsyncAgentPhone
73
+
74
+ async with AsyncAgentPhone(api_key="your-api-key") as client:
75
+ numbers = await client.numbers.list()
76
+ call = await client.calls.make_conversation(...)
77
+ ```
78
+
79
+ ## Resources
80
+
81
+ | Resource | Methods |
82
+ |---|---|
83
+ | `client.numbers` | `list()`, `buy()`, `release()`, `get_messages()` |
84
+ | `client.agents` | `list()`, `create()`, `get()`, `attach_number()` |
85
+ | `client.calls` | `list()`, `get()`, `make()`, `make_conversation()` |
86
+ | `client.conversations` | `list()`, `get()` |
87
+ | `client.webhooks` | `get()`, `set()`, `delete()`, `list_deliveries()`, `test()` |
88
+
89
+ ## Webhook Verification
90
+
91
+ ```python
92
+ from agentphone import construct_event, WebhookVerificationError
93
+
94
+ @app.post("/webhook")
95
+ async def handle(request: Request):
96
+ body = await request.body()
97
+ sig = request.headers["X-Webhook-Signature"]
98
+ try:
99
+ event = construct_event(body, sig, secret="whsec_...")
100
+ except WebhookVerificationError:
101
+ return Response(status_code=403)
102
+
103
+ if event.event == "agent.message":
104
+ print(f"SMS from {event.data.from_number}: {event.data.message}")
105
+ ```
106
+
107
+ ## Error Handling
108
+
109
+ ```python
110
+ from agentphone import AgentPhoneError, AuthenticationError, NotFoundError
111
+
112
+ try:
113
+ call = client.calls.get("bad-id")
114
+ except NotFoundError:
115
+ print("Call not found")
116
+ except AgentPhoneError as e:
117
+ print(f"API error {e.status}: {e.message}")
118
+ ```
119
+
120
+ ## Publishing to PyPI
121
+
122
+ 1. **Install build tools** (one-time):
123
+ ```bash
124
+ pip install hatch twine
125
+ ```
126
+
127
+ 2. **Build** the package:
128
+ ```bash
129
+ hatch build
130
+ ```
131
+ This creates a `dist/` folder with the `.tar.gz` and `.whl` files.
132
+
133
+ 3. **Upload to PyPI**:
134
+ ```bash
135
+ twine upload dist/*
136
+ ```
137
+ You'll be prompted for your PyPI credentials. To use an API token instead:
138
+ ```bash
139
+ twine upload dist/* -u __token__ -p pypi-your-token-here
140
+ ```
141
+
142
+ 4. **Publishing a new version** — bump the version in `pyproject.toml` first:
143
+ ```toml
144
+ version = "0.1.1"
145
+ ```
146
+ Then rebuild and upload:
147
+ ```bash
148
+ hatch build && twine upload dist/*
149
+ ```
150
+
151
+ ## Requirements
152
+
153
+ - Python 3.9+
154
+ - `requests` (sync client)
155
+ - `httpx` (async client, optional)
156
+
157
+ ## Links
158
+
159
+ - [AgentPhone Dashboard](https://agentphone.to)
160
+ - [MCP Server](https://github.com/AgentPhone-AI/agentphone-mcp)
161
+ - [Node.js SDK](https://github.com/AgentPhone-AI/agentphone-node)
@@ -0,0 +1,130 @@
1
+ # AgentPhone Python SDK
2
+
3
+ Official Python SDK for [AgentPhone](https://agentphone.to) — give your AI agents real phone numbers, SMS, and voice calls.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install agentphone
9
+ ```
10
+
11
+ For async support:
12
+
13
+ ```bash
14
+ pip install agentphone[async]
15
+ ```
16
+
17
+ ## Quickstart
18
+
19
+ ```python
20
+ from agentphone import AgentPhone
21
+
22
+ client = AgentPhone(api_key="your-api-key")
23
+
24
+ # Create an agent and buy a number
25
+ agent = client.agents.create(name="My Agent")
26
+ number = client.numbers.buy(country="US", agent_id=agent.id)
27
+
28
+ # Make an AI conversation call — no webhook needed
29
+ call = client.calls.make_conversation(
30
+ agent_id=agent.id,
31
+ to_number="+14155551234",
32
+ topic="You are a friendly assistant. Ask about their day.",
33
+ initial_greeting="Hey! This is an AI calling from AgentPhone.",
34
+ )
35
+ print(call.status) # registered
36
+ ```
37
+
38
+ ## Async
39
+
40
+ ```python
41
+ from agentphone import AsyncAgentPhone
42
+
43
+ async with AsyncAgentPhone(api_key="your-api-key") as client:
44
+ numbers = await client.numbers.list()
45
+ call = await client.calls.make_conversation(...)
46
+ ```
47
+
48
+ ## Resources
49
+
50
+ | Resource | Methods |
51
+ |---|---|
52
+ | `client.numbers` | `list()`, `buy()`, `release()`, `get_messages()` |
53
+ | `client.agents` | `list()`, `create()`, `get()`, `attach_number()` |
54
+ | `client.calls` | `list()`, `get()`, `make()`, `make_conversation()` |
55
+ | `client.conversations` | `list()`, `get()` |
56
+ | `client.webhooks` | `get()`, `set()`, `delete()`, `list_deliveries()`, `test()` |
57
+
58
+ ## Webhook Verification
59
+
60
+ ```python
61
+ from agentphone import construct_event, WebhookVerificationError
62
+
63
+ @app.post("/webhook")
64
+ async def handle(request: Request):
65
+ body = await request.body()
66
+ sig = request.headers["X-Webhook-Signature"]
67
+ try:
68
+ event = construct_event(body, sig, secret="whsec_...")
69
+ except WebhookVerificationError:
70
+ return Response(status_code=403)
71
+
72
+ if event.event == "agent.message":
73
+ print(f"SMS from {event.data.from_number}: {event.data.message}")
74
+ ```
75
+
76
+ ## Error Handling
77
+
78
+ ```python
79
+ from agentphone import AgentPhoneError, AuthenticationError, NotFoundError
80
+
81
+ try:
82
+ call = client.calls.get("bad-id")
83
+ except NotFoundError:
84
+ print("Call not found")
85
+ except AgentPhoneError as e:
86
+ print(f"API error {e.status}: {e.message}")
87
+ ```
88
+
89
+ ## Publishing to PyPI
90
+
91
+ 1. **Install build tools** (one-time):
92
+ ```bash
93
+ pip install hatch twine
94
+ ```
95
+
96
+ 2. **Build** the package:
97
+ ```bash
98
+ hatch build
99
+ ```
100
+ This creates a `dist/` folder with the `.tar.gz` and `.whl` files.
101
+
102
+ 3. **Upload to PyPI**:
103
+ ```bash
104
+ twine upload dist/*
105
+ ```
106
+ You'll be prompted for your PyPI credentials. To use an API token instead:
107
+ ```bash
108
+ twine upload dist/* -u __token__ -p pypi-your-token-here
109
+ ```
110
+
111
+ 4. **Publishing a new version** — bump the version in `pyproject.toml` first:
112
+ ```toml
113
+ version = "0.1.1"
114
+ ```
115
+ Then rebuild and upload:
116
+ ```bash
117
+ hatch build && twine upload dist/*
118
+ ```
119
+
120
+ ## Requirements
121
+
122
+ - Python 3.9+
123
+ - `requests` (sync client)
124
+ - `httpx` (async client, optional)
125
+
126
+ ## Links
127
+
128
+ - [AgentPhone Dashboard](https://agentphone.to)
129
+ - [MCP Server](https://github.com/AgentPhone-AI/agentphone-mcp)
130
+ - [Node.js SDK](https://github.com/AgentPhone-AI/agentphone-node)
@@ -0,0 +1,78 @@
1
+ """
2
+ AgentPhone Python SDK
3
+
4
+ Give your AI agents phone numbers, SMS, and voice calls.
5
+
6
+ Usage::
7
+
8
+ from agentphone import AgentPhone
9
+
10
+ client = AgentPhone(api_key="your-api-key")
11
+ number = client.numbers.buy(country="US")
12
+ call = client.calls.make_conversation(
13
+ agent_id="...",
14
+ to_number="+14155551234",
15
+ topic="You are a friendly assistant. Ask about their day.",
16
+ )
17
+ """
18
+
19
+ from .async_client import AsyncAgentPhone
20
+ from .client import AgentPhone
21
+ from ._http import AgentPhoneError, AuthenticationError, NotFoundError, RateLimitError
22
+ from .models import (
23
+ Agent,
24
+ AgentList,
25
+ AgentNumber,
26
+ Call,
27
+ CallList,
28
+ CallTranscript,
29
+ Conversation,
30
+ ConversationList,
31
+ ConversationMessage,
32
+ Message,
33
+ MessageList,
34
+ PhoneNumber,
35
+ PhoneNumberList,
36
+ Webhook,
37
+ WebhookDelivery,
38
+ WebhookEvent,
39
+ WebhookEventData,
40
+ WebhookHistoryItem,
41
+ )
42
+ from .webhook import WebhookVerificationError, construct_event, verify_webhook
43
+
44
+ __version__ = "0.1.0"
45
+
46
+ __all__ = [
47
+ # Clients
48
+ "AgentPhone",
49
+ "AsyncAgentPhone",
50
+ # Errors
51
+ "AgentPhoneError",
52
+ "AuthenticationError",
53
+ "NotFoundError",
54
+ "RateLimitError",
55
+ "WebhookVerificationError",
56
+ # Webhook helpers
57
+ "verify_webhook",
58
+ "construct_event",
59
+ # Models
60
+ "Agent",
61
+ "AgentList",
62
+ "AgentNumber",
63
+ "Call",
64
+ "CallList",
65
+ "CallTranscript",
66
+ "Conversation",
67
+ "ConversationList",
68
+ "ConversationMessage",
69
+ "Message",
70
+ "MessageList",
71
+ "PhoneNumber",
72
+ "PhoneNumberList",
73
+ "Webhook",
74
+ "WebhookDelivery",
75
+ "WebhookEvent",
76
+ "WebhookEventData",
77
+ "WebhookHistoryItem",
78
+ ]
@@ -0,0 +1,44 @@
1
+ """
2
+ Shared HTTP logic for sync and async clients.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ DEFAULT_BASE_URL = "https://api.agentphone.to"
8
+
9
+
10
+ class AgentPhoneError(Exception):
11
+ """Raised when the AgentPhone API returns a non-2xx response."""
12
+
13
+ def __init__(self, status: int, message: str):
14
+ self.status = status
15
+ self.message = message
16
+ super().__init__(f"AgentPhone API error {status}: {message}")
17
+
18
+
19
+ class AuthenticationError(AgentPhoneError):
20
+ pass
21
+
22
+
23
+ class NotFoundError(AgentPhoneError):
24
+ pass
25
+
26
+
27
+ class RateLimitError(AgentPhoneError):
28
+ pass
29
+
30
+
31
+ def _raise_for_status(status: int, text: str) -> None:
32
+ try:
33
+ import json as _json
34
+ detail = _json.loads(text).get("detail") or _json.loads(text).get("message") or text
35
+ except Exception:
36
+ detail = text
37
+
38
+ if status == 401:
39
+ raise AuthenticationError(status, detail)
40
+ if status == 404:
41
+ raise NotFoundError(status, detail)
42
+ if status == 429:
43
+ raise RateLimitError(status, detail)
44
+ raise AgentPhoneError(status, detail)
@@ -0,0 +1,210 @@
1
+ """
2
+ Asynchronous AgentPhone client (requires httpx).
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Optional, TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ import httpx
11
+
12
+ from ._http import DEFAULT_BASE_URL, _raise_for_status
13
+ from .models import (
14
+ Agent,
15
+ AgentList,
16
+ Call,
17
+ CallList,
18
+ Conversation,
19
+ ConversationList,
20
+ MessageList,
21
+ PhoneNumber,
22
+ PhoneNumberList,
23
+ Webhook,
24
+ WebhookDelivery,
25
+ )
26
+
27
+
28
+ class _AsyncResource:
29
+ def __init__(self, client: "AsyncAgentPhone") -> None:
30
+ self._client = client
31
+
32
+ async def _get(self, path: str, **params) -> dict:
33
+ return await self._client._request("GET", path, params=params or None)
34
+
35
+ async def _post(self, path: str, **body) -> dict:
36
+ return await self._client._request(
37
+ "POST", path, json={k: v for k, v in body.items() if v is not None}
38
+ )
39
+
40
+ async def _delete(self, path: str) -> dict:
41
+ return await self._client._request("DELETE", path)
42
+
43
+
44
+ class AsyncNumbersResource(_AsyncResource):
45
+ async def list(self, limit: int = 20, offset: int = 0) -> PhoneNumberList:
46
+ return PhoneNumberList.from_dict(await self._get("/v1/numbers", limit=limit, offset=offset))
47
+
48
+ async def buy(self, country: str = "US", agent_id: Optional[str] = None) -> PhoneNumber:
49
+ return PhoneNumber.from_dict(await self._post("/v1/numbers", country=country, agentId=agent_id))
50
+
51
+ async def release(self, number_id: str) -> dict:
52
+ return await self._delete(f"/v1/numbers/{number_id}")
53
+
54
+ async def get_messages(self, number_id: str, limit: int = 50) -> MessageList:
55
+ return MessageList.from_dict(await self._get(f"/v1/numbers/{number_id}/messages", limit=limit))
56
+
57
+
58
+ class AsyncAgentsResource(_AsyncResource):
59
+ async def list(self, limit: int = 20, offset: int = 0) -> AgentList:
60
+ return AgentList.from_dict(await self._get("/v1/agents", limit=limit, offset=offset))
61
+
62
+ async def create(self, name: str, description: Optional[str] = None) -> Agent:
63
+ return Agent.from_dict(await self._post("/v1/agents", name=name, description=description))
64
+
65
+ async def get(self, agent_id: str) -> Agent:
66
+ return Agent.from_dict(await self._get(f"/v1/agents/{agent_id}"))
67
+
68
+ async def attach_number(self, agent_id: str, number_id: str) -> dict:
69
+ return await self._post(f"/v1/agents/{agent_id}/numbers", numberId=number_id)
70
+
71
+
72
+ class AsyncCallsResource(_AsyncResource):
73
+ async def list(self, limit: int = 20, offset: int = 0) -> CallList:
74
+ return CallList.from_dict(await self._get("/v1/calls", limit=limit, offset=offset))
75
+
76
+ async def get(self, call_id: str) -> Call:
77
+ return Call.from_dict(await self._get(f"/v1/calls/{call_id}"))
78
+
79
+ async def make(
80
+ self,
81
+ agent_id: str,
82
+ to_number: str,
83
+ initial_greeting: Optional[str] = None,
84
+ ) -> Call:
85
+ return Call.from_dict(
86
+ await self._post(
87
+ "/v1/calls",
88
+ agentId=agent_id,
89
+ toNumber=to_number,
90
+ initialGreeting=initial_greeting,
91
+ )
92
+ )
93
+
94
+ async def make_conversation(
95
+ self,
96
+ agent_id: str,
97
+ to_number: str,
98
+ topic: str,
99
+ initial_greeting: Optional[str] = None,
100
+ model: Optional[str] = None,
101
+ ) -> Call:
102
+ return Call.from_dict(
103
+ await self._post(
104
+ "/v1/calls",
105
+ agentId=agent_id,
106
+ toNumber=to_number,
107
+ systemPrompt=topic,
108
+ initialGreeting=initial_greeting,
109
+ model=model,
110
+ )
111
+ )
112
+
113
+
114
+ class AsyncConversationsResource(_AsyncResource):
115
+ async def list(self, limit: int = 20, offset: int = 0) -> ConversationList:
116
+ return ConversationList.from_dict(await self._get("/v1/conversations", limit=limit, offset=offset))
117
+
118
+ async def get(self, conversation_id: str, message_limit: int = 50) -> Conversation:
119
+ return Conversation.from_dict(
120
+ await self._get(f"/v1/conversations/{conversation_id}", message_limit=message_limit)
121
+ )
122
+
123
+
124
+ class AsyncWebhooksResource(_AsyncResource):
125
+ async def get(self) -> Optional[Webhook]:
126
+ data = await self._get("/v1/webhooks")
127
+ return Webhook.from_dict(data) if data else None
128
+
129
+ async def set(self, url: str, context_limit: Optional[int] = None) -> Webhook:
130
+ return Webhook.from_dict(await self._post("/v1/webhooks", url=url, contextLimit=context_limit))
131
+
132
+ async def delete(self) -> dict:
133
+ return await self._delete("/v1/webhooks")
134
+
135
+ async def list_deliveries(self, limit: int = 50) -> list[WebhookDelivery]:
136
+ data = await self._get("/v1/webhooks/deliveries", limit=limit)
137
+ return [WebhookDelivery.from_dict(d) for d in data]
138
+
139
+ async def test(self) -> dict:
140
+ return await self._post("/v1/webhooks/test")
141
+
142
+
143
+ class AsyncAgentPhone:
144
+ """
145
+ Asynchronous AgentPhone client.
146
+
147
+ Requires: pip install agentphone[async]
148
+
149
+ Example::
150
+
151
+ from agentphone import AsyncAgentPhone
152
+
153
+ async with AsyncAgentPhone(api_key="your-api-key") as client:
154
+ number = await client.numbers.buy(country="US")
155
+ call = await client.calls.make_conversation(
156
+ agent_id="...",
157
+ to_number="+14155551234",
158
+ topic="You are a friendly assistant.",
159
+ )
160
+ """
161
+
162
+ def __init__(
163
+ self,
164
+ api_key: str,
165
+ base_url: str = DEFAULT_BASE_URL,
166
+ timeout: float = 30.0,
167
+ ) -> None:
168
+ try:
169
+ import httpx as _httpx
170
+ except ImportError:
171
+ raise ImportError(
172
+ "The async client requires httpx. Install it with: pip install agentphone[async]"
173
+ )
174
+ self.api_key = api_key
175
+ self.base_url = base_url.rstrip("/")
176
+ self.timeout = timeout
177
+ self._client = _httpx.AsyncClient(
178
+ headers={
179
+ "Authorization": f"Bearer {api_key}",
180
+ "Content-Type": "application/json",
181
+ "User-Agent": "agentphone-python/0.1.0",
182
+ },
183
+ timeout=timeout,
184
+ )
185
+
186
+ self.numbers = AsyncNumbersResource(self)
187
+ self.agents = AsyncAgentsResource(self)
188
+ self.calls = AsyncCallsResource(self)
189
+ self.conversations = AsyncConversationsResource(self)
190
+ self.webhooks = AsyncWebhooksResource(self)
191
+
192
+ async def _request(self, method: str, path: str, **kwargs) -> dict: # type: ignore[override]
193
+ url = f"{self.base_url}{path}"
194
+ resp = await self._client.request(method, url, **kwargs)
195
+
196
+ if not resp.is_success:
197
+ _raise_for_status(resp.status_code, resp.text)
198
+
199
+ if resp.status_code == 204:
200
+ return {}
201
+ return resp.json()
202
+
203
+ async def close(self) -> None:
204
+ await self._client.aclose()
205
+
206
+ async def __aenter__(self) -> "AsyncAgentPhone":
207
+ return self
208
+
209
+ async def __aexit__(self, *args) -> None:
210
+ await self.close()