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.
- agentphone-0.1.0/.gitignore +5 -0
- agentphone-0.1.0/PKG-INFO +161 -0
- agentphone-0.1.0/README.md +130 -0
- agentphone-0.1.0/agentphone/__init__.py +78 -0
- agentphone-0.1.0/agentphone/_http.py +44 -0
- agentphone-0.1.0/agentphone/async_client.py +210 -0
- agentphone-0.1.0/agentphone/client.py +238 -0
- agentphone-0.1.0/agentphone/models.py +362 -0
- agentphone-0.1.0/agentphone/webhook.py +81 -0
- agentphone-0.1.0/examples/make_call.py +27 -0
- agentphone-0.1.0/examples/webhook_server.py +45 -0
- agentphone-0.1.0/pyproject.toml +38 -0
- agentphone-0.1.0/test_live.py +75 -0
|
@@ -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()
|