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.
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ dist/
3
+ *.tsbuildinfo
4
+
5
+ __pycache__/
6
+ *.pyc
7
+ *.egg-info/
8
+ .eggs/
9
+ build/
10
+ dist/
11
+ .venv/
@@ -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