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>
5.6 KiB
5.6 KiB
sentryagent-idp — Rust SDK
Production-grade Rust client for the 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
asyncand backed bytokio - Thread-safe token cache —
TokenManagerrefreshes 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-depsgenerates clean docs #![deny(warnings)]enforced — zero clippy warnings
Installation
Add to your Cargo.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
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:
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:
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:
# 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.