feat(phase-2): workstream 2 — Python SDK (sentryagent-idp)
Sync (requests) and async (httpx) clients with identical API surface to the Node.js SDK. Delivered: - pyproject.toml — python>=3.9, hatchling build, mypy strict config - types.py — all 14-endpoint request/response dataclasses - errors.py — AgentIdPError with from_api_error, from_oauth2_error, network_error - token_manager.py — thread-safe sync TokenManager, 60s refresh buffer - async_token_manager.py — asyncio-safe AsyncTokenManager (httpx) - _request.py — shared sync/async request helper (DRY) - services/agents.py — AgentRegistryClient + AsyncAgentRegistryClient (5 methods each) - services/credentials.py — CredentialClient + AsyncCredentialClient (4 methods each) - services/token.py — TokenClient + AsyncTokenClient (introspect + revoke) - services/audit.py — AuditClient + AsyncAuditClient (query + get) - client.py — AgentIdPClient + AsyncAgentIdPClient - __init__.py — barrel exports - README.md — installation, quick start, full API reference QA gates: - mypy --strict: 0 errors (12 source files) - pytest: 57/57 passed - Coverage: 90.83% (required >= 80%) - All 14 endpoints covered (sync + async) - AgentIdPError raised on all failure paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
214
sdk-python/README.md
Normal file
214
sdk-python/README.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# sentryagent-idp
|
||||
|
||||
Python SDK for the [SentryAgent.ai AgentIdP](https://sentryagent.ai) — the open-source Identity Provider for AI agents.
|
||||
|
||||
Handles token acquisition and caching automatically. Covers all 14 AgentIdP API endpoints. Provides both synchronous (`requests`) and asynchronous (`httpx`) clients.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.9 or later
|
||||
- A running AgentIdP server
|
||||
- A registered agent with a valid `client_id` and `client_secret`
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install sentryagent-idp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
### Synchronous
|
||||
|
||||
```python
|
||||
from sentryagent_idp import AgentIdPClient
|
||||
|
||||
client = AgentIdPClient(
|
||||
base_url="http://localhost:3000",
|
||||
client_id="your-agent-id", # the agent's agentId (UUID)
|
||||
client_secret="your-client-secret",
|
||||
)
|
||||
|
||||
# List agents — token is acquired and cached automatically
|
||||
result = client.agents.list_agents()
|
||||
print(result.data)
|
||||
```
|
||||
|
||||
### Asynchronous
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from sentryagent_idp import AsyncAgentIdPClient
|
||||
|
||||
async def main() -> None:
|
||||
client = AsyncAgentIdPClient(
|
||||
base_url="http://localhost:3000",
|
||||
client_id="your-agent-id",
|
||||
client_secret="your-client-secret",
|
||||
)
|
||||
result = await client.agents.list_agents()
|
||||
print(result.data)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
```python
|
||||
client = AgentIdPClient(
|
||||
base_url="http://localhost:3000",
|
||||
client_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
client_secret="your-client-secret",
|
||||
# Optional: restrict scopes. Defaults to all four.
|
||||
scopes=["agents:read", "tokens:read"],
|
||||
)
|
||||
```
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `base_url` | Yes | Base URL of the AgentIdP server |
|
||||
| `client_id` | Yes | The agent's `agentId` (UUID) |
|
||||
| `client_secret` | Yes | The credential secret |
|
||||
| `scopes` | No | OAuth 2.0 scopes to request. Defaults to all four. |
|
||||
|
||||
---
|
||||
|
||||
## Token management
|
||||
|
||||
The SDK fetches and caches access tokens automatically. A new token is requested when the cached token is within 60 seconds of expiry.
|
||||
|
||||
```python
|
||||
# Force a fresh token on the next request (e.g. after rotating credentials)
|
||||
client.clear_token_cache()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agent Registry
|
||||
|
||||
```python
|
||||
from sentryagent_idp import RegisterAgentRequest, UpdateAgentRequest
|
||||
|
||||
# Register a new agent
|
||||
agent = client.agents.register_agent(RegisterAgentRequest(
|
||||
email="classifier-v2@myorg.ai",
|
||||
agent_type="classifier",
|
||||
version="2.0.0",
|
||||
capabilities=["text-classification", "sentiment-analysis"],
|
||||
owner="platform-team",
|
||||
deployment_env="production",
|
||||
))
|
||||
print(agent.agent_id) # UUID assigned by AgentIdP
|
||||
|
||||
# List agents
|
||||
result = client.agents.list_agents(status="active", limit=20)
|
||||
|
||||
# Get a single agent
|
||||
agent = client.agents.get_agent("a1b2c3d4-...")
|
||||
|
||||
# Update an agent
|
||||
updated = client.agents.update_agent("a1b2c3d4-...", UpdateAgentRequest(
|
||||
version="2.1.0",
|
||||
capabilities=["text-classification", "sentiment-analysis", "intent-detection"],
|
||||
))
|
||||
|
||||
# Decommission (irreversible)
|
||||
client.agents.decommission_agent("a1b2c3d4-...")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
```python
|
||||
# Generate a credential — client_secret shown once, store it securely
|
||||
cred = client.credentials.generate_credential("a1b2c3d4-...")
|
||||
print(cred.client_secret) # only available here
|
||||
|
||||
# List credentials
|
||||
result = client.credentials.list_credentials("a1b2c3d4-...")
|
||||
|
||||
# Rotate — same credential_id, new secret, old secret immediately invalid
|
||||
rotated = client.credentials.rotate_credential("a1b2c3d4-...", "cred-uuid")
|
||||
print(rotated.client_secret) # new secret — store immediately
|
||||
|
||||
# Revoke
|
||||
client.credentials.revoke_credential("a1b2c3d4-...", "cred-uuid")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Token operations
|
||||
|
||||
```python
|
||||
# Introspect — check whether a token is active
|
||||
result = client.tokens.introspect_token(some_token)
|
||||
if result.active:
|
||||
print(f"Token valid, expires at {result.exp}")
|
||||
else:
|
||||
print("Token is expired or revoked")
|
||||
|
||||
# Revoke — immediately invalidates the token
|
||||
client.tokens.revoke_token(some_token)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audit log
|
||||
|
||||
```python
|
||||
# Query audit events
|
||||
result = client.audit.query_audit_log(
|
||||
agent_id="a1b2c3d4-...",
|
||||
action="token.issued",
|
||||
outcome="success",
|
||||
from_date="2026-03-01T00:00:00Z",
|
||||
to_date="2026-03-31T23:59:59Z",
|
||||
limit=50,
|
||||
)
|
||||
|
||||
# Get a single event
|
||||
event = client.audit.get_audit_event("event-uuid")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error handling
|
||||
|
||||
All API errors are raised as `AgentIdPError`:
|
||||
|
||||
```python
|
||||
from sentryagent_idp import AgentIdPClient, AgentIdPError
|
||||
|
||||
try:
|
||||
client.agents.get_agent("non-existent-id")
|
||||
except AgentIdPError as err:
|
||||
print(err.code) # e.g. "AgentNotFoundError"
|
||||
print(err.http_status) # e.g. 404
|
||||
print(str(err)) # human-readable description
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available scopes
|
||||
|
||||
| Scope | What it allows |
|
||||
|-------|----------------|
|
||||
| `agents:read` | Read agent records |
|
||||
| `agents:write` | Create, update, decommission agents |
|
||||
| `tokens:read` | Introspect tokens |
|
||||
| `audit:read` | Query audit logs |
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 — see `LICENSE` in the repository root.
|
||||
Reference in New Issue
Block a user