6 workstreams, 119 tasks — Scale & Ecosystem: - WS1: Rust SDK - WS2: Agent-to-Agent (A2A) Authorization - WS3: Advanced Analytics Dashboard - WS4: Public API Gateway & Rate Limiting SaaS - WS5: Developer Experience (DX) improvements - WS6: AGNTCY Compliance Certification Package Awaiting CEO approval to begin implementation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
290 lines
9.5 KiB
Markdown
290 lines
9.5 KiB
Markdown
## WS1: Rust SDK
|
|
|
|
### Purpose
|
|
|
|
Deliver a production-grade, idiomatic Rust SDK for SentryAgent.ai AgentIdP. The SDK covers all 14 API endpoints, provides a thread-safe `TokenManager` with automatic token refresh, uses `async/await` throughout via `tokio`, and models all errors as a typed `AgentIdPError` enum. Rust developers building high-performance or safety-critical AI agents can integrate with SentryAgent.ai without writing HTTP boilerplate.
|
|
|
|
The SDK is published to crates.io as `sentryagent-idp`. It mirrors the API surface of the Go SDK (the most recently authored and cleanest SDK) to reduce cognitive load for polyglot teams.
|
|
|
|
### New Files to Create
|
|
|
|
| File | Description |
|
|
|---|---|
|
|
| `sdk-rust/Cargo.toml` | Crate manifest — name: `sentryagent-idp`, edition: 2021 |
|
|
| `sdk-rust/src/lib.rs` | Crate root — re-exports `AgentIdPClient`, `TokenManager`, `AgentIdPError`, all model types |
|
|
| `sdk-rust/src/client.rs` | `AgentIdPClient` struct — wraps `reqwest::Client`, holds base URL + credentials |
|
|
| `sdk-rust/src/token_manager.rs` | `TokenManager` struct — `Arc<Mutex<TokenCache>>`, auto-refresh logic |
|
|
| `sdk-rust/src/error.rs` | `AgentIdPError` enum — all typed error variants, implements `std::error::Error` |
|
|
| `sdk-rust/src/models.rs` | All request/response model structs — serde Serialize/Deserialize |
|
|
| `sdk-rust/src/agents.rs` | Agent CRUD methods on `AgentIdPClient` |
|
|
| `sdk-rust/src/oauth2.rs` | Token issuance and refresh methods |
|
|
| `sdk-rust/src/credentials.rs` | Credential management methods |
|
|
| `sdk-rust/src/audit.rs` | Audit log query methods |
|
|
| `sdk-rust/src/marketplace.rs` | Marketplace listing and detail methods |
|
|
| `sdk-rust/src/delegation.rs` | A2A delegation methods (WS2 integration) |
|
|
| `sdk-rust/examples/quickstart.rs` | Working quickstart example — register agent, issue token, make authenticated call |
|
|
| `sdk-rust/README.md` | Installation, configuration, quickstart, all methods with examples |
|
|
| `sdk-rust/tests/integration_test.rs` | Integration tests against a real API instance (reads `AGENTIDP_API_URL` env var) |
|
|
|
|
### Cargo.toml Dependencies
|
|
|
|
```toml
|
|
[dependencies]
|
|
tokio = { version = "1.35", features = ["full"] }
|
|
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
|
serde = { version = "1.0", features = ["derive"] }
|
|
serde_json = "1.0"
|
|
uuid = { version = "1.6", features = ["v4"] }
|
|
thiserror = "1.0"
|
|
async-trait = "0.1"
|
|
|
|
[dev-dependencies]
|
|
tokio-test = "0.4"
|
|
mockito = "1.2"
|
|
```
|
|
|
|
### Public API Surface
|
|
|
|
#### `AgentIdPClient`
|
|
|
|
```rust
|
|
pub struct AgentIdPClient {
|
|
base_url: String,
|
|
client_id: String,
|
|
client_secret: String,
|
|
http: reqwest::Client,
|
|
token_manager: Arc<Mutex<TokenManager>>,
|
|
}
|
|
|
|
impl AgentIdPClient {
|
|
/// Create a new client. Does not make any network calls at construction time.
|
|
pub fn new(base_url: &str, client_id: &str, client_secret: &str) -> Self;
|
|
|
|
/// Create a client from environment variables:
|
|
/// AGENTIDP_API_URL, AGENTIDP_CLIENT_ID, AGENTIDP_CLIENT_SECRET
|
|
pub fn from_env() -> Result<Self, AgentIdPError>;
|
|
|
|
// Agent methods
|
|
pub async fn register_agent(&self, req: RegisterAgentRequest) -> Result<Agent, AgentIdPError>;
|
|
pub async fn get_agent(&self, agent_id: &str) -> Result<Agent, AgentIdPError>;
|
|
pub async fn list_agents(&self, page: u32, per_page: u32) -> Result<AgentList, AgentIdPError>;
|
|
pub async fn update_agent(&self, agent_id: &str, req: UpdateAgentRequest) -> Result<Agent, AgentIdPError>;
|
|
pub async fn delete_agent(&self, agent_id: &str) -> Result<(), AgentIdPError>;
|
|
|
|
// OAuth2 token methods
|
|
pub async fn issue_token(&self, agent_id: &str, scopes: &[&str]) -> Result<TokenResponse, AgentIdPError>;
|
|
|
|
// Credential methods
|
|
pub async fn generate_credentials(&self, agent_id: &str) -> Result<Credentials, AgentIdPError>;
|
|
pub async fn rotate_credentials(&self, agent_id: &str) -> Result<Credentials, AgentIdPError>;
|
|
pub async fn revoke_credentials(&self, agent_id: &str) -> Result<(), AgentIdPError>;
|
|
|
|
// Audit log methods
|
|
pub async fn list_audit_logs(&self, filters: AuditLogFilters) -> Result<AuditLogList, AgentIdPError>;
|
|
|
|
// Marketplace methods
|
|
pub async fn list_public_agents(&self, filters: MarketplaceFilters) -> Result<MarketplaceAgentList, AgentIdPError>;
|
|
pub async fn get_public_agent(&self, agent_id: &str) -> Result<MarketplaceAgent, AgentIdPError>;
|
|
|
|
// Delegation methods (WS2)
|
|
pub async fn delegate(&self, req: DelegateRequest) -> Result<DelegationToken, AgentIdPError>;
|
|
pub async fn verify_delegation(&self, token: &str) -> Result<DelegationVerification, AgentIdPError>;
|
|
}
|
|
```
|
|
|
|
#### `TokenManager`
|
|
|
|
```rust
|
|
/// Thread-safe token cache with automatic refresh.
|
|
/// Holds the current access token and its expiry.
|
|
/// Re-issues a token when it is within 60 seconds of expiry.
|
|
pub struct TokenManager {
|
|
client_id: String,
|
|
client_secret: String,
|
|
api_url: String,
|
|
cache: Arc<Mutex<TokenCache>>,
|
|
}
|
|
|
|
struct TokenCache {
|
|
access_token: Option<String>,
|
|
expires_at: Option<std::time::Instant>,
|
|
}
|
|
|
|
impl TokenManager {
|
|
pub fn new(api_url: &str, client_id: &str, client_secret: &str) -> Self;
|
|
|
|
/// Returns a valid access token. Refreshes automatically if expired or within 60s of expiry.
|
|
pub async fn get_token(&self) -> Result<String, AgentIdPError>;
|
|
}
|
|
```
|
|
|
|
#### `AgentIdPError`
|
|
|
|
```rust
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum AgentIdPError {
|
|
#[error("HTTP request failed: {0}")]
|
|
HttpError(#[from] reqwest::Error),
|
|
|
|
#[error("API error {status}: {message}")]
|
|
ApiError { status: u16, message: String, code: Option<String> },
|
|
|
|
#[error("Authentication failed: {0}")]
|
|
AuthError(String),
|
|
|
|
#[error("Agent not found: {0}")]
|
|
NotFound(String),
|
|
|
|
#[error("Rate limit exceeded. Retry after {retry_after_secs}s")]
|
|
RateLimited { retry_after_secs: u64 },
|
|
|
|
#[error("Invalid configuration: {0}")]
|
|
ConfigError(String),
|
|
|
|
#[error("Serialization error: {0}")]
|
|
SerdeError(#[from] serde_json::Error),
|
|
|
|
#[error("Delegation chain invalid: {0}")]
|
|
DelegationError(String),
|
|
}
|
|
```
|
|
|
|
### Model Structs (complete — no placeholders)
|
|
|
|
```rust
|
|
// Request types
|
|
pub struct RegisterAgentRequest {
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub capabilities: Vec<String>,
|
|
pub metadata: Option<serde_json::Value>,
|
|
}
|
|
|
|
pub struct UpdateAgentRequest {
|
|
pub name: Option<String>,
|
|
pub description: Option<String>,
|
|
pub capabilities: Option<Vec<String>>,
|
|
pub is_public: Option<bool>,
|
|
pub metadata: Option<serde_json::Value>,
|
|
}
|
|
|
|
pub struct AuditLogFilters {
|
|
pub agent_id: Option<String>,
|
|
pub event_type: Option<String>,
|
|
pub from: Option<String>, // ISO 8601
|
|
pub to: Option<String>, // ISO 8601
|
|
pub page: u32,
|
|
pub per_page: u32,
|
|
}
|
|
|
|
pub struct MarketplaceFilters {
|
|
pub q: Option<String>,
|
|
pub capability: Option<String>,
|
|
pub publisher: Option<String>,
|
|
pub page: u32,
|
|
pub per_page: u32,
|
|
}
|
|
|
|
pub struct DelegateRequest {
|
|
pub delegatee_agent_id: String,
|
|
pub scopes: Vec<String>,
|
|
pub ttl_seconds: u64,
|
|
}
|
|
|
|
// Response types
|
|
pub struct Agent {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub capabilities: Vec<String>,
|
|
pub did: String,
|
|
pub is_public: bool,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
pub struct AgentList {
|
|
pub agents: Vec<Agent>,
|
|
pub total: u64,
|
|
pub page: u32,
|
|
pub per_page: u32,
|
|
}
|
|
|
|
pub struct TokenResponse {
|
|
pub access_token: String,
|
|
pub token_type: String,
|
|
pub expires_in: u64,
|
|
pub scope: String,
|
|
}
|
|
|
|
pub struct Credentials {
|
|
pub client_id: String,
|
|
pub client_secret: String, // Only present on generate/rotate — never on read
|
|
pub created_at: String,
|
|
}
|
|
|
|
pub struct AuditLogEntry {
|
|
pub id: String,
|
|
pub agent_id: String,
|
|
pub event_type: String,
|
|
pub actor: String,
|
|
pub metadata: serde_json::Value,
|
|
pub timestamp: String,
|
|
}
|
|
|
|
pub struct AuditLogList {
|
|
pub entries: Vec<AuditLogEntry>,
|
|
pub total: u64,
|
|
pub page: u32,
|
|
pub per_page: u32,
|
|
}
|
|
|
|
pub struct MarketplaceAgent {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub capabilities: Vec<String>,
|
|
pub did_document: serde_json::Value,
|
|
pub publisher: String,
|
|
pub created_at: String,
|
|
}
|
|
|
|
pub struct MarketplaceAgentList {
|
|
pub agents: Vec<MarketplaceAgent>,
|
|
pub total: u64,
|
|
pub page: u32,
|
|
pub per_page: u32,
|
|
}
|
|
|
|
pub struct DelegationToken {
|
|
pub delegation_token: String,
|
|
pub chain_id: String,
|
|
pub expires_at: String,
|
|
}
|
|
|
|
pub struct DelegationVerification {
|
|
pub valid: bool,
|
|
pub chain_id: String,
|
|
pub delegator_agent_id: String,
|
|
pub delegatee_agent_id: String,
|
|
pub scopes: Vec<String>,
|
|
pub expires_at: String,
|
|
}
|
|
```
|
|
|
|
### Database Schema Changes
|
|
|
|
None. The Rust SDK is a client library — it makes HTTP calls to the existing API. No database changes are required for WS1.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- `cargo build` passes with zero warnings (deny warnings enforced via `#![deny(warnings)]` in `lib.rs`)
|
|
- `cargo clippy` passes with zero warnings
|
|
- `cargo test` runs all unit tests — all pass
|
|
- Integration tests pass against a live API instance when `AGENTIDP_API_URL`, `AGENTIDP_CLIENT_ID`, `AGENTIDP_CLIENT_SECRET` are set
|
|
- `TokenManager::get_token()` is thread-safe: concurrent calls from multiple `tokio` tasks do not produce race conditions (verified by a concurrent-call test with 50 parallel futures)
|
|
- Zero `unwrap()` calls in `src/` (only in `examples/` and `tests/` where panicking is acceptable)
|
|
- All public items have `///` doc comments
|
|
- `cargo doc --no-deps` generates docs without errors
|
|
- Published to crates.io as `sentryagent-idp` version `1.0.0`
|