Files
sentryagent-idp/sdk-rust/README.md
SentryAgent.ai Developer a4aab1b5b3 feat(phase-5): WS1 — Rust SDK
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>
2026-04-03 02:48:14 +00:00

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.