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>
This commit is contained in:
171
sdk-rust/README.md
Normal file
171
sdk-rust/README.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user