Implements the sentryagent-idp Rust SDK crate (sdk-rust/) with: - TokenManager with Arc<Mutex<TokenCache>> for thread-safe token caching - AgentIdPClient with full method coverage: agents, oauth2, credentials, audit, marketplace, delegation - Error hierarchy via thiserror (AgentIdPError enum) - All model types with serde derive - 429 RateLimited handling with Retry-After parsing; zero unwrap() calls - Unit tests (mockito), doc tests, and integration tests (#[ignore]) - quickstart example, full README, cargo doc clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
172 lines
5.6 KiB
Markdown
172 lines
5.6 KiB
Markdown
# sentryagent-idp — Rust SDK
|
|
|
|
Production-grade Rust client for the [SentryAgent.ai](https://sentryagent.ai) AgentIdP API. Covers all 14 API endpoints across agent identity, OAuth 2.0 token management, credential rotation, audit logs, the public marketplace, and A2A delegation.
|
|
|
|
## Features
|
|
|
|
- Async-first — every API call is `async` and backed by `tokio`
|
|
- Thread-safe token cache — `TokenManager` refreshes tokens automatically before expiry
|
|
- Typed errors — every failure maps to a variant of `AgentIdPError`
|
|
- Zero `unwrap()` in library code — all errors propagated with `?`
|
|
- Full `//!` and `///` doc coverage — `cargo doc --no-deps` generates clean docs
|
|
- `#![deny(warnings)]` enforced — zero clippy warnings
|
|
|
|
## Installation
|
|
|
|
Add to your `Cargo.toml`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
sentryagent-idp = "1.0"
|
|
tokio = { version = "1", features = ["full"] }
|
|
```
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Purpose |
|
|
|---|---|
|
|
| `AGENTIDP_API_URL` | Base URL of the AgentIdP API (e.g. `https://api.sentryagent.ai`) |
|
|
| `AGENTIDP_CLIENT_ID` | OAuth 2.0 client identifier |
|
|
| `AGENTIDP_CLIENT_SECRET` | OAuth 2.0 client secret |
|
|
|
|
## Quickstart
|
|
|
|
```rust
|
|
use sentryagent_idp::{AgentIdPClient, RegisterAgentRequest};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Build from environment variables.
|
|
let client = AgentIdPClient::from_env()?;
|
|
|
|
// Register a new agent.
|
|
let agent = client.register_agent(RegisterAgentRequest {
|
|
name: "my-agent".to_owned(),
|
|
description: Some("Does useful things".to_owned()),
|
|
agent_type: "worker".to_owned(),
|
|
capabilities: vec!["read:data".to_owned()],
|
|
metadata: None,
|
|
}).await?;
|
|
|
|
println!("Agent registered: {} (DID: {})", agent.id, agent.did);
|
|
|
|
// Issue a scoped access token.
|
|
let token = client.issue_token(&agent.id, &["agents:read"]).await?;
|
|
println!("Token issued, expires in {}s", token.expires_in);
|
|
|
|
// Clean up.
|
|
client.delete_agent(&agent.id).await?;
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
## Method Reference
|
|
|
|
### Agent Registry
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| `register_agent(req)` | `POST /agents` | Register a new agent identity |
|
|
| `get_agent(id)` | `GET /agents/{id}` | Retrieve an agent by ID |
|
|
| `list_agents(page, per_page)` | `GET /agents` | List all agents (paginated) |
|
|
| `update_agent(id, req)` | `PATCH /agents/{id}` | Partially update an agent |
|
|
| `delete_agent(id)` | `DELETE /agents/{id}` | Permanently delete an agent |
|
|
|
|
### OAuth 2.0
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| `issue_token(agent_id, scopes)` | `POST /oauth2/token` | Issue a scoped access token |
|
|
|
|
### Credentials
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| `generate_credentials(agent_id)` | `POST /agents/{id}/credentials` | Generate credentials (returns secret once) |
|
|
| `rotate_credentials(agent_id)` | `POST /agents/{id}/credentials/rotate` | Rotate credentials (invalidates previous) |
|
|
| `revoke_credentials(agent_id, cred_id)` | `DELETE /agents/{id}/credentials/{cred_id}` | Revoke a specific credential set |
|
|
|
|
### Audit Logs
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| `list_audit_logs(filters)` | `GET /audit-logs` | Query audit events with optional filters |
|
|
|
|
### Marketplace (unauthenticated)
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| `list_public_agents(filters)` | `GET /marketplace/agents` | Browse public marketplace agents |
|
|
| `get_public_agent(id)` | `GET /marketplace/agents/{id}` | Retrieve a single marketplace agent |
|
|
|
|
### Delegation
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| `delegate(req)` | `POST /delegation` | Create an A2A delegation token |
|
|
| `verify_delegation(token)` | `POST /delegation/verify` | Verify and decode a delegation token |
|
|
|
|
## Error Handling
|
|
|
|
All methods return `Result<T, AgentIdPError>`. Match on variants for fine-grained handling:
|
|
|
|
```rust
|
|
use sentryagent_idp::AgentIdPError;
|
|
|
|
match client.get_agent("unknown-id").await {
|
|
Err(AgentIdPError::NotFound(msg)) => {
|
|
eprintln!("Agent not found: {}", msg);
|
|
}
|
|
Err(AgentIdPError::RateLimited { retry_after_secs }) => {
|
|
eprintln!("Rate limited — retry after {}s", retry_after_secs);
|
|
}
|
|
Err(AgentIdPError::AuthError(msg)) => {
|
|
eprintln!("Authentication failed: {}", msg);
|
|
}
|
|
Err(AgentIdPError::ApiError { status, message, code }) => {
|
|
eprintln!("API error {}: {} (code: {:?})", status, message, code);
|
|
}
|
|
Err(e) => eprintln!("Unexpected error: {}", e),
|
|
Ok(agent) => println!("Found: {}", agent.name),
|
|
}
|
|
```
|
|
|
|
### Error Variants
|
|
|
|
| Variant | Cause |
|
|
|---|---|
|
|
| `HttpError(reqwest::Error)` | Network-level transport failure |
|
|
| `ApiError { status, message, code }` | Non-2xx HTTP response with error body |
|
|
| `AuthError(String)` | 401 or 403 — invalid credentials or insufficient scope |
|
|
| `NotFound(String)` | 404 — resource does not exist |
|
|
| `RateLimited { retry_after_secs }` | 429 — too many requests |
|
|
| `ConfigError(String)` | Missing environment variable on `from_env()` |
|
|
| `SerdeError(serde_json::Error)` | JSON parsing failure |
|
|
| `DelegationError(String)` | Invalid or revoked delegation chain |
|
|
|
|
## Running Integration Tests
|
|
|
|
Integration tests are ignored by default. Set the three environment variables and run:
|
|
|
|
```bash
|
|
AGENTIDP_API_URL=https://api.sentryagent.ai \
|
|
AGENTIDP_CLIENT_ID=your-client-id \
|
|
AGENTIDP_CLIENT_SECRET=your-client-secret \
|
|
cargo test -- --ignored
|
|
```
|
|
|
|
## Publishing to crates.io
|
|
|
|
This crate is published as `sentryagent-idp` version `1.0.0`. To publish a new version:
|
|
|
|
```bash
|
|
# Update version in Cargo.toml, then:
|
|
cargo publish --registry crates-io
|
|
```
|
|
|
|
Ensure `CARGO_REGISTRY_TOKEN` is set to a valid crates.io API token before publishing.
|
|
|
|
## License
|
|
|
|
MIT — see LICENSE for details.
|