agentping-sdk 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.
- agentping_sdk-0.1.0/.gitignore +11 -0
- agentping_sdk-0.1.0/PKG-INFO +184 -0
- agentping_sdk-0.1.0/README.md +172 -0
- agentping_sdk-0.1.0/agentping/__init__.py +24 -0
- agentping_sdk-0.1.0/agentping/client.py +150 -0
- agentping_sdk-0.1.0/agentping/exceptions.py +35 -0
- agentping_sdk-0.1.0/agentping/py.typed +0 -0
- agentping_sdk-0.1.0/agentping/tool.py +129 -0
- agentping_sdk-0.1.0/pyproject.toml +35 -0
- agentping_sdk-0.1.0/tests/__init__.py +0 -0
- agentping_sdk-0.1.0/tests/test_client.py +235 -0
- agentping_sdk-0.1.0/tests/test_tool.py +130 -0
- agentping_sdk-0.1.0/uv.lock +229 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentping-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for AgentPing — escalation alerts for AI agents via SMS and voice calls.
|
|
5
|
+
Project-URL: Homepage, https://agentping.me
|
|
6
|
+
Project-URL: Documentation, https://agentping.me/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/agentping/agentping-sdk
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# AgentPing Python SDK
|
|
14
|
+
|
|
15
|
+
Official Python SDK for [AgentPing](https://agentping.me) — escalation alerts for AI agents via voice calls.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install agentping
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from agentping import AgentPingClient
|
|
27
|
+
|
|
28
|
+
client = AgentPingClient(api_key="ap_sk_...")
|
|
29
|
+
|
|
30
|
+
# Send an alert (voice call with retry)
|
|
31
|
+
alert = client.send_alert(
|
|
32
|
+
title="Deploy approval needed",
|
|
33
|
+
severity="normal",
|
|
34
|
+
message="v2.4.1 ready for production. 3 migrations pending.",
|
|
35
|
+
alert_type="approval",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
print(alert["id"]) # "550e8400-..."
|
|
39
|
+
print(alert["status"]) # "waiting_for_primary_ack"
|
|
40
|
+
|
|
41
|
+
# Check status later
|
|
42
|
+
status = client.get_alert(alert["id"])
|
|
43
|
+
print(status["status"]) # "acknowledged", "escalating_sms", etc.
|
|
44
|
+
|
|
45
|
+
# Acknowledge programmatically (stops escalation)
|
|
46
|
+
ack = client.acknowledge(alert["id"])
|
|
47
|
+
print(ack["status"]) # "acknowledged"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Agent framework integration
|
|
51
|
+
|
|
52
|
+
The SDK includes an OpenAI-compatible tool definition you can plug into any agent framework:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from agentping import AgentPingClient, tool_definition, handle_tool_call
|
|
56
|
+
|
|
57
|
+
client = AgentPingClient(api_key="ap_sk_...")
|
|
58
|
+
|
|
59
|
+
# 1. Add to your agent's tools
|
|
60
|
+
tools = [tool_definition()]
|
|
61
|
+
|
|
62
|
+
# 2. When the agent calls the tool, handle it:
|
|
63
|
+
# (assuming `tool_call` is the parsed tool call from your framework)
|
|
64
|
+
import json
|
|
65
|
+
|
|
66
|
+
args = json.loads(tool_call.function.arguments)
|
|
67
|
+
result = handle_tool_call(client, args)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### OpenAI example
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from openai import OpenAI
|
|
74
|
+
from agentping import AgentPingClient, tool_definition, handle_tool_call
|
|
75
|
+
import json
|
|
76
|
+
|
|
77
|
+
openai = OpenAI()
|
|
78
|
+
agentping = AgentPingClient(api_key="ap_sk_...")
|
|
79
|
+
|
|
80
|
+
messages = [
|
|
81
|
+
{"role": "system", "content": """You are a helpful assistant.
|
|
82
|
+
When you need the user's attention and they haven't responded to your message,
|
|
83
|
+
use the agentping_alert tool to escalate via voice call.
|
|
84
|
+
Always try messaging in chat first — use agentping_alert as a fallback."""},
|
|
85
|
+
{"role": "user", "content": "Monitor my deploy and alert me if it fails."},
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
response = openai.chat.completions.create(
|
|
89
|
+
model="gpt-4o",
|
|
90
|
+
messages=messages,
|
|
91
|
+
tools=[tool_definition()],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Handle tool calls
|
|
95
|
+
for tool_call in response.choices[0].message.tool_calls or []:
|
|
96
|
+
if tool_call.function.name == "agentping_alert":
|
|
97
|
+
args = json.loads(tool_call.function.arguments)
|
|
98
|
+
result = handle_tool_call(agentping, args)
|
|
99
|
+
print(f"Alert sent: {result['id']}")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Anthropic Claude example
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from anthropic import Anthropic
|
|
106
|
+
from agentping import AgentPingClient, tool_definition, handle_tool_call
|
|
107
|
+
import json
|
|
108
|
+
|
|
109
|
+
anthropic = Anthropic()
|
|
110
|
+
agentping = AgentPingClient(api_key="ap_sk_...")
|
|
111
|
+
|
|
112
|
+
# Convert OpenAI tool format to Anthropic format
|
|
113
|
+
openai_tool = tool_definition()
|
|
114
|
+
anthropic_tool = {
|
|
115
|
+
"name": openai_tool["function"]["name"],
|
|
116
|
+
"description": openai_tool["function"]["description"],
|
|
117
|
+
"input_schema": openai_tool["function"]["parameters"],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
response = anthropic.messages.create(
|
|
121
|
+
model="claude-sonnet-4-20250514",
|
|
122
|
+
max_tokens=1024,
|
|
123
|
+
system="""You are a helpful assistant.
|
|
124
|
+
When you need the user's attention and they haven't responded,
|
|
125
|
+
use the agentping_alert tool to escalate via voice call.""",
|
|
126
|
+
messages=[{"role": "user", "content": "Monitor my deploy and alert me if it fails."}],
|
|
127
|
+
tools=[anthropic_tool],
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Handle tool use
|
|
131
|
+
for block in response.content:
|
|
132
|
+
if block.type == "tool_use" and block.name == "agentping_alert":
|
|
133
|
+
result = handle_tool_call(agentping, block.input)
|
|
134
|
+
print(f"Alert sent: {result['id']}")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Severity levels
|
|
138
|
+
|
|
139
|
+
| Severity | Delivery | Behavior |
|
|
140
|
+
|----------|----------|----------|
|
|
141
|
+
| `normal` | Voice call | Call with retry (recommended default) |
|
|
142
|
+
| `critical` | Immediate call | More retries, bypasses quiet hours |
|
|
143
|
+
| `persistent_critical` | Repeated calls | Calls until acknowledged or retry limit hit |
|
|
144
|
+
|
|
145
|
+
> **Deprecated:** `low` and `urgent` are still accepted but mapped to `normal` by the API.
|
|
146
|
+
|
|
147
|
+
## Alert types
|
|
148
|
+
|
|
149
|
+
| Type | Default Delay | Use when |
|
|
150
|
+
|------|---------------|----------|
|
|
151
|
+
| `approval` | 300s (5 min) | Agent needs a decision |
|
|
152
|
+
| `task_failure` | 120s (2 min) | Something broke |
|
|
153
|
+
| `threshold` | 600s (10 min) | Metric crossed a boundary |
|
|
154
|
+
| `reminder` | 300s (5 min) | Time-sensitive nudge |
|
|
155
|
+
| `other` | 0s (immediate) | General escalation |
|
|
156
|
+
|
|
157
|
+
## Error handling
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from agentping import AgentPingClient, RateLimitError, ForbiddenError
|
|
161
|
+
|
|
162
|
+
client = AgentPingClient(api_key="ap_sk_...")
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
client.send_alert(title="Test", severity="normal")
|
|
166
|
+
except RateLimitError:
|
|
167
|
+
print("Too many alerts — wait before retrying")
|
|
168
|
+
except ForbiddenError as e:
|
|
169
|
+
print(f"Policy violation: {e}")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## API reference
|
|
173
|
+
|
|
174
|
+
- `AgentPingClient(api_key, base_url=..., timeout=30.0)` — create a client
|
|
175
|
+
- `client.send_alert(title, severity, message=..., alert_type=..., delay_seconds=..., phone_number=..., expires_in_minutes=..., metadata=...)` — create an alert
|
|
176
|
+
- `client.get_alert(alert_id)` — get alert status
|
|
177
|
+
- `client.acknowledge(alert_id, ack_source="api")` — acknowledge an alert
|
|
178
|
+
- `client.close()` — close the HTTP client (also works as a context manager)
|
|
179
|
+
|
|
180
|
+
## Links
|
|
181
|
+
|
|
182
|
+
- [AgentPing docs](https://agentping.me/docs)
|
|
183
|
+
- [API reference](https://agentping.me/docs)
|
|
184
|
+
- [GitHub](https://github.com/agentping/agentping-sdk/tree/main/python)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# AgentPing Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [AgentPing](https://agentping.me) — escalation alerts for AI agents via voice calls.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install agentping
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from agentping import AgentPingClient
|
|
15
|
+
|
|
16
|
+
client = AgentPingClient(api_key="ap_sk_...")
|
|
17
|
+
|
|
18
|
+
# Send an alert (voice call with retry)
|
|
19
|
+
alert = client.send_alert(
|
|
20
|
+
title="Deploy approval needed",
|
|
21
|
+
severity="normal",
|
|
22
|
+
message="v2.4.1 ready for production. 3 migrations pending.",
|
|
23
|
+
alert_type="approval",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
print(alert["id"]) # "550e8400-..."
|
|
27
|
+
print(alert["status"]) # "waiting_for_primary_ack"
|
|
28
|
+
|
|
29
|
+
# Check status later
|
|
30
|
+
status = client.get_alert(alert["id"])
|
|
31
|
+
print(status["status"]) # "acknowledged", "escalating_sms", etc.
|
|
32
|
+
|
|
33
|
+
# Acknowledge programmatically (stops escalation)
|
|
34
|
+
ack = client.acknowledge(alert["id"])
|
|
35
|
+
print(ack["status"]) # "acknowledged"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Agent framework integration
|
|
39
|
+
|
|
40
|
+
The SDK includes an OpenAI-compatible tool definition you can plug into any agent framework:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from agentping import AgentPingClient, tool_definition, handle_tool_call
|
|
44
|
+
|
|
45
|
+
client = AgentPingClient(api_key="ap_sk_...")
|
|
46
|
+
|
|
47
|
+
# 1. Add to your agent's tools
|
|
48
|
+
tools = [tool_definition()]
|
|
49
|
+
|
|
50
|
+
# 2. When the agent calls the tool, handle it:
|
|
51
|
+
# (assuming `tool_call` is the parsed tool call from your framework)
|
|
52
|
+
import json
|
|
53
|
+
|
|
54
|
+
args = json.loads(tool_call.function.arguments)
|
|
55
|
+
result = handle_tool_call(client, args)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### OpenAI example
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from openai import OpenAI
|
|
62
|
+
from agentping import AgentPingClient, tool_definition, handle_tool_call
|
|
63
|
+
import json
|
|
64
|
+
|
|
65
|
+
openai = OpenAI()
|
|
66
|
+
agentping = AgentPingClient(api_key="ap_sk_...")
|
|
67
|
+
|
|
68
|
+
messages = [
|
|
69
|
+
{"role": "system", "content": """You are a helpful assistant.
|
|
70
|
+
When you need the user's attention and they haven't responded to your message,
|
|
71
|
+
use the agentping_alert tool to escalate via voice call.
|
|
72
|
+
Always try messaging in chat first — use agentping_alert as a fallback."""},
|
|
73
|
+
{"role": "user", "content": "Monitor my deploy and alert me if it fails."},
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
response = openai.chat.completions.create(
|
|
77
|
+
model="gpt-4o",
|
|
78
|
+
messages=messages,
|
|
79
|
+
tools=[tool_definition()],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Handle tool calls
|
|
83
|
+
for tool_call in response.choices[0].message.tool_calls or []:
|
|
84
|
+
if tool_call.function.name == "agentping_alert":
|
|
85
|
+
args = json.loads(tool_call.function.arguments)
|
|
86
|
+
result = handle_tool_call(agentping, args)
|
|
87
|
+
print(f"Alert sent: {result['id']}")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Anthropic Claude example
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from anthropic import Anthropic
|
|
94
|
+
from agentping import AgentPingClient, tool_definition, handle_tool_call
|
|
95
|
+
import json
|
|
96
|
+
|
|
97
|
+
anthropic = Anthropic()
|
|
98
|
+
agentping = AgentPingClient(api_key="ap_sk_...")
|
|
99
|
+
|
|
100
|
+
# Convert OpenAI tool format to Anthropic format
|
|
101
|
+
openai_tool = tool_definition()
|
|
102
|
+
anthropic_tool = {
|
|
103
|
+
"name": openai_tool["function"]["name"],
|
|
104
|
+
"description": openai_tool["function"]["description"],
|
|
105
|
+
"input_schema": openai_tool["function"]["parameters"],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
response = anthropic.messages.create(
|
|
109
|
+
model="claude-sonnet-4-20250514",
|
|
110
|
+
max_tokens=1024,
|
|
111
|
+
system="""You are a helpful assistant.
|
|
112
|
+
When you need the user's attention and they haven't responded,
|
|
113
|
+
use the agentping_alert tool to escalate via voice call.""",
|
|
114
|
+
messages=[{"role": "user", "content": "Monitor my deploy and alert me if it fails."}],
|
|
115
|
+
tools=[anthropic_tool],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Handle tool use
|
|
119
|
+
for block in response.content:
|
|
120
|
+
if block.type == "tool_use" and block.name == "agentping_alert":
|
|
121
|
+
result = handle_tool_call(agentping, block.input)
|
|
122
|
+
print(f"Alert sent: {result['id']}")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Severity levels
|
|
126
|
+
|
|
127
|
+
| Severity | Delivery | Behavior |
|
|
128
|
+
|----------|----------|----------|
|
|
129
|
+
| `normal` | Voice call | Call with retry (recommended default) |
|
|
130
|
+
| `critical` | Immediate call | More retries, bypasses quiet hours |
|
|
131
|
+
| `persistent_critical` | Repeated calls | Calls until acknowledged or retry limit hit |
|
|
132
|
+
|
|
133
|
+
> **Deprecated:** `low` and `urgent` are still accepted but mapped to `normal` by the API.
|
|
134
|
+
|
|
135
|
+
## Alert types
|
|
136
|
+
|
|
137
|
+
| Type | Default Delay | Use when |
|
|
138
|
+
|------|---------------|----------|
|
|
139
|
+
| `approval` | 300s (5 min) | Agent needs a decision |
|
|
140
|
+
| `task_failure` | 120s (2 min) | Something broke |
|
|
141
|
+
| `threshold` | 600s (10 min) | Metric crossed a boundary |
|
|
142
|
+
| `reminder` | 300s (5 min) | Time-sensitive nudge |
|
|
143
|
+
| `other` | 0s (immediate) | General escalation |
|
|
144
|
+
|
|
145
|
+
## Error handling
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from agentping import AgentPingClient, RateLimitError, ForbiddenError
|
|
149
|
+
|
|
150
|
+
client = AgentPingClient(api_key="ap_sk_...")
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
client.send_alert(title="Test", severity="normal")
|
|
154
|
+
except RateLimitError:
|
|
155
|
+
print("Too many alerts — wait before retrying")
|
|
156
|
+
except ForbiddenError as e:
|
|
157
|
+
print(f"Policy violation: {e}")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## API reference
|
|
161
|
+
|
|
162
|
+
- `AgentPingClient(api_key, base_url=..., timeout=30.0)` — create a client
|
|
163
|
+
- `client.send_alert(title, severity, message=..., alert_type=..., delay_seconds=..., phone_number=..., expires_in_minutes=..., metadata=...)` — create an alert
|
|
164
|
+
- `client.get_alert(alert_id)` — get alert status
|
|
165
|
+
- `client.acknowledge(alert_id, ack_source="api")` — acknowledge an alert
|
|
166
|
+
- `client.close()` — close the HTTP client (also works as a context manager)
|
|
167
|
+
|
|
168
|
+
## Links
|
|
169
|
+
|
|
170
|
+
- [AgentPing docs](https://agentping.me/docs)
|
|
171
|
+
- [API reference](https://agentping.me/docs)
|
|
172
|
+
- [GitHub](https://github.com/agentping/agentping-sdk/tree/main/python)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""AgentPing Python SDK — escalation alerts for AI agents."""
|
|
2
|
+
|
|
3
|
+
from agentping.client import AgentPingClient
|
|
4
|
+
from agentping.exceptions import (
|
|
5
|
+
AgentPingError,
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
ForbiddenError,
|
|
8
|
+
NotFoundError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
)
|
|
12
|
+
from agentping.tool import handle_tool_call, tool_definition
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AgentPingClient",
|
|
16
|
+
"AgentPingError",
|
|
17
|
+
"AuthenticationError",
|
|
18
|
+
"ForbiddenError",
|
|
19
|
+
"NotFoundError",
|
|
20
|
+
"RateLimitError",
|
|
21
|
+
"ValidationError",
|
|
22
|
+
"handle_tool_call",
|
|
23
|
+
"tool_definition",
|
|
24
|
+
]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""AgentPing API client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from agentping.exceptions import (
|
|
10
|
+
AgentPingError,
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
ForbiddenError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
RateLimitError,
|
|
15
|
+
ValidationError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
Severity = Literal[
|
|
19
|
+
"normal",
|
|
20
|
+
"critical",
|
|
21
|
+
"persistent_critical",
|
|
22
|
+
"low", # deprecated — mapped to "normal" by the API
|
|
23
|
+
"urgent", # deprecated — mapped to "normal" by the API
|
|
24
|
+
]
|
|
25
|
+
AlertType = Literal["approval", "task_failure", "threshold", "reminder", "other"]
|
|
26
|
+
AckSource = Literal["dtmf", "sms_link", "sms_reply", "api", "chat", "manual"]
|
|
27
|
+
|
|
28
|
+
DEFAULT_BASE_URL = "https://api.agentping.me"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AgentPingClient:
|
|
32
|
+
"""Thin client for the AgentPing alert API.
|
|
33
|
+
|
|
34
|
+
Usage::
|
|
35
|
+
|
|
36
|
+
from agentping import AgentPingClient
|
|
37
|
+
|
|
38
|
+
client = AgentPingClient(api_key="ap_sk_...")
|
|
39
|
+
alert = client.send_alert(
|
|
40
|
+
title="Deploy approval needed",
|
|
41
|
+
severity="normal",
|
|
42
|
+
alert_type="approval",
|
|
43
|
+
)
|
|
44
|
+
print(alert["id"], alert["status"])
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
api_key: str,
|
|
51
|
+
*,
|
|
52
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
53
|
+
timeout: float = 30.0,
|
|
54
|
+
) -> None:
|
|
55
|
+
self._api_key = api_key
|
|
56
|
+
self._base_url = base_url.rstrip("/")
|
|
57
|
+
self._client = httpx.Client(
|
|
58
|
+
base_url=self._base_url,
|
|
59
|
+
headers={
|
|
60
|
+
"X-API-Key": self._api_key,
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
},
|
|
63
|
+
timeout=timeout,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# ── Public API ──────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
def send_alert(
|
|
69
|
+
self,
|
|
70
|
+
*,
|
|
71
|
+
title: str,
|
|
72
|
+
severity: Severity,
|
|
73
|
+
message: str | None = None,
|
|
74
|
+
alert_type: AlertType | None = None,
|
|
75
|
+
delay_seconds: int | None = None,
|
|
76
|
+
phone_number: str | None = None,
|
|
77
|
+
expires_in_minutes: int | None = None,
|
|
78
|
+
metadata: dict[str, Any] | None = None,
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
"""Create an alert. Returns the created alert object."""
|
|
81
|
+
body: dict[str, Any] = {"title": title, "severity": severity}
|
|
82
|
+
if message is not None:
|
|
83
|
+
body["message"] = message
|
|
84
|
+
if alert_type is not None:
|
|
85
|
+
body["alert_type"] = alert_type
|
|
86
|
+
if delay_seconds is not None:
|
|
87
|
+
body["delay_seconds"] = delay_seconds
|
|
88
|
+
if phone_number is not None:
|
|
89
|
+
body["phone_number"] = phone_number
|
|
90
|
+
if expires_in_minutes is not None:
|
|
91
|
+
body["expires_in_minutes"] = expires_in_minutes
|
|
92
|
+
if metadata is not None:
|
|
93
|
+
body["metadata"] = metadata
|
|
94
|
+
|
|
95
|
+
resp = self._client.post("/v1/alerts", json=body)
|
|
96
|
+
return self._handle_response(resp)
|
|
97
|
+
|
|
98
|
+
def get_alert(self, alert_id: str) -> dict[str, Any]:
|
|
99
|
+
"""Get alert status and delivery details."""
|
|
100
|
+
resp = self._client.get(f"/v1/alerts/{alert_id}")
|
|
101
|
+
return self._handle_response(resp)
|
|
102
|
+
|
|
103
|
+
def acknowledge(
|
|
104
|
+
self,
|
|
105
|
+
alert_id: str,
|
|
106
|
+
*,
|
|
107
|
+
ack_source: AckSource = "api",
|
|
108
|
+
) -> dict[str, Any]:
|
|
109
|
+
"""Acknowledge an alert to stop escalation."""
|
|
110
|
+
resp = self._client.post(
|
|
111
|
+
f"/v1/alerts/{alert_id}/acknowledge",
|
|
112
|
+
json={"ack_source": ack_source},
|
|
113
|
+
)
|
|
114
|
+
return self._handle_response(resp)
|
|
115
|
+
|
|
116
|
+
def close(self) -> None:
|
|
117
|
+
"""Close the underlying HTTP client."""
|
|
118
|
+
self._client.close()
|
|
119
|
+
|
|
120
|
+
def __enter__(self) -> AgentPingClient:
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
def __exit__(self, *args: Any) -> None:
|
|
124
|
+
self.close()
|
|
125
|
+
|
|
126
|
+
# ── Internals ───────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
def _handle_response(self, resp: httpx.Response) -> dict[str, Any]:
|
|
129
|
+
if resp.status_code in (200, 201):
|
|
130
|
+
return resp.json()
|
|
131
|
+
|
|
132
|
+
detail = ""
|
|
133
|
+
try:
|
|
134
|
+
body = resp.json()
|
|
135
|
+
detail = body.get("detail", str(body))
|
|
136
|
+
except Exception:
|
|
137
|
+
detail = resp.text
|
|
138
|
+
|
|
139
|
+
if resp.status_code == 401:
|
|
140
|
+
raise AuthenticationError(str(detail), status_code=401)
|
|
141
|
+
if resp.status_code == 403:
|
|
142
|
+
raise ForbiddenError(str(detail), status_code=403)
|
|
143
|
+
if resp.status_code == 404:
|
|
144
|
+
raise NotFoundError(str(detail), status_code=404)
|
|
145
|
+
if resp.status_code == 422:
|
|
146
|
+
errors = detail if isinstance(detail, list) else []
|
|
147
|
+
raise ValidationError(str(detail), errors=errors)
|
|
148
|
+
if resp.status_code == 429:
|
|
149
|
+
raise RateLimitError(str(detail), status_code=429)
|
|
150
|
+
raise AgentPingError(str(detail), status_code=resp.status_code)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Custom exceptions for the AgentPing SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AgentPingError(Exception):
|
|
7
|
+
"""Base exception for all AgentPing errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, status_code: int | None = None) -> None:
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.status_code = status_code
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthenticationError(AgentPingError):
|
|
15
|
+
"""Raised on 401 — missing, invalid, or revoked API key."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ForbiddenError(AgentPingError):
|
|
19
|
+
"""Raised on 403 — policy violation, trial expired, or quota exceeded."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NotFoundError(AgentPingError):
|
|
23
|
+
"""Raised on 404 — alert not found or does not belong to user."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ValidationError(AgentPingError):
|
|
27
|
+
"""Raised on 422 — invalid request body."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, message: str, errors: list[dict] | None = None) -> None:
|
|
30
|
+
super().__init__(message, status_code=422)
|
|
31
|
+
self.errors = errors or []
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RateLimitError(AgentPingError):
|
|
35
|
+
"""Raised on 429 — hourly or per-key rate limit exceeded."""
|
|
File without changes
|