# 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> { // 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`. 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.