feat: Phase 2 Workstream 3 — Go SDK (github.com/sentryagent/idp-sdk-go)
Single-package agentidp SDK in sdk-go/: - AgentIdPClient composing AgentRegistryClient, CredentialClient, TokenServiceClient, AuditClient — all 14 endpoints covered - Goroutine-safe TokenManager (sync.Mutex) with 60s refresh buffer - AgentIdPError implementing error interface with Code/HTTPStatus/Details - Context-aware: all service methods take context.Context as first arg - doRequest shared helper; token endpoints use form-encoded POST directly - go vet: 0 warnings | staticcheck: 0 warnings - go test ./...: 37/37 passed | coverage: 81.0% (>80% gate) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
200
sdk-go/README.md
Normal file
200
sdk-go/README.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# SentryAgent.ai AgentIdP — Go SDK
|
||||
|
||||
Official Go client for the [SentryAgent.ai AgentIdP](https://sentryagent.ai) — an open-source Identity Provider for AI agents built on OAuth 2.0 (RFC 6749) and aligned with the [AGNTCY](https://agntcy.org) open standard.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.21+
|
||||
- A running AgentIdP server
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/sentryagent/idp-sdk-go
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
agentidp "github.com/sentryagent/idp-sdk-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
client := agentidp.NewAgentIdPClient(agentidp.AgentIdPClientConfig{
|
||||
BaseURL: "https://idp.example.com",
|
||||
ClientID: "your-agent-client-id",
|
||||
ClientSecret: "sk_live_...",
|
||||
})
|
||||
|
||||
// Register a new AI agent
|
||||
agent, err := client.Agents.RegisterAgent(ctx, agentidp.RegisterAgentRequest{
|
||||
Email: "screener@example.com",
|
||||
AgentType: "screener",
|
||||
Version: "1.0.0",
|
||||
Capabilities: []string{"read", "classify"},
|
||||
Owner: "platform-team",
|
||||
DeploymentEnv: "production",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Registered agent: %s\n", agent.AgentID)
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
The SDK handles OAuth 2.0 Client Credentials automatically. Tokens are cached and refreshed 60 seconds before expiry. All operations are goroutine-safe.
|
||||
|
||||
```go
|
||||
client := agentidp.NewAgentIdPClient(agentidp.AgentIdPClientConfig{
|
||||
BaseURL: "https://idp.example.com",
|
||||
ClientID: "my-client-id",
|
||||
ClientSecret: "my-client-secret",
|
||||
Scope: "agents:read agents:write", // optional, defaults to all four scopes
|
||||
})
|
||||
```
|
||||
|
||||
## Agent Registry
|
||||
|
||||
```go
|
||||
ctx := context.Background()
|
||||
|
||||
// Register
|
||||
agent, err := client.Agents.RegisterAgent(ctx, agentidp.RegisterAgentRequest{...})
|
||||
|
||||
// List (with optional filters)
|
||||
agents, err := client.Agents.ListAgents(ctx, &agentidp.ListAgentsParams{
|
||||
Status: "active",
|
||||
AgentType: "screener",
|
||||
Page: 1,
|
||||
Limit: 20,
|
||||
})
|
||||
|
||||
// Get by ID
|
||||
agent, err := client.Agents.GetAgent(ctx, "agent-uuid")
|
||||
|
||||
// Partial update
|
||||
version := "2.0.0"
|
||||
agent, err := client.Agents.UpdateAgent(ctx, "agent-uuid", agentidp.UpdateAgentRequest{
|
||||
Version: &version,
|
||||
})
|
||||
|
||||
// Decommission (permanent)
|
||||
err = client.Agents.DecommissionAgent(ctx, "agent-uuid")
|
||||
```
|
||||
|
||||
## Credential Management
|
||||
|
||||
```go
|
||||
// Generate (returns one-time ClientSecret)
|
||||
cred, err := client.Credentials.GenerateCredential(ctx, "agent-uuid")
|
||||
fmt.Println(cred.ClientSecret) // store this — it is never shown again
|
||||
|
||||
// List
|
||||
creds, err := client.Credentials.ListCredentials(ctx, "agent-uuid", 1, 20)
|
||||
|
||||
// Rotate (old secret is immediately invalidated)
|
||||
newCred, err := client.Credentials.RotateCredential(ctx, "agent-uuid", "cred-uuid")
|
||||
|
||||
// Revoke
|
||||
revoked, err := client.Credentials.RevokeCredential(ctx, "agent-uuid", "cred-uuid")
|
||||
```
|
||||
|
||||
## Token Operations
|
||||
|
||||
```go
|
||||
// Introspect (RFC 7662)
|
||||
result, err := client.Tokens.IntrospectToken(ctx, "access-token-to-check")
|
||||
if result.Active {
|
||||
fmt.Printf("Token belongs to: %s\n", *result.Sub)
|
||||
}
|
||||
|
||||
// Revoke
|
||||
err = client.Tokens.RevokeToken(ctx, "access-token-to-revoke")
|
||||
```
|
||||
|
||||
## Audit Log
|
||||
|
||||
```go
|
||||
// Query with filters
|
||||
events, err := client.Audit.QueryAuditLog(ctx, &agentidp.QueryAuditParams{
|
||||
AgentID: "agent-uuid",
|
||||
Action: "token.issued",
|
||||
Outcome: "success",
|
||||
FromDate: "2026-01-01",
|
||||
ToDate: "2026-01-31",
|
||||
Page: 1,
|
||||
Limit: 50,
|
||||
})
|
||||
|
||||
// Get single event
|
||||
event, err := client.Audit.GetAuditEvent(ctx, "event-uuid")
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
All errors are returned as `*AgentIdPError`:
|
||||
|
||||
```go
|
||||
agent, err := client.Agents.GetAgent(ctx, "unknown-id")
|
||||
if err != nil {
|
||||
if apiErr, ok := err.(*agentidp.AgentIdPError); ok {
|
||||
fmt.Printf("code=%s status=%d\n", apiErr.Code, apiErr.HTTPStatus)
|
||||
// e.g. code=AgentNotFoundError status=404
|
||||
}
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|--------------|--------------------------|-------------------------------------------------|
|
||||
| `Code` | `string` | Machine-readable error code |
|
||||
| `Message` | `string` | Human-readable description |
|
||||
| `HTTPStatus` | `int` | HTTP status code (0 for network/build errors) |
|
||||
| `Details` | `map[string]interface{}` | Optional structured context from the API |
|
||||
|
||||
## Custom HTTP Client
|
||||
|
||||
Inject a custom `*http.Client` for proxy support, custom timeouts, or test mocking:
|
||||
|
||||
```go
|
||||
client := agentidp.NewAgentIdPClient(agentidp.AgentIdPClientConfig{
|
||||
BaseURL: "https://idp.example.com",
|
||||
ClientID: "cid",
|
||||
ClientSecret: "secret",
|
||||
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
||||
})
|
||||
```
|
||||
|
||||
## API Coverage
|
||||
|
||||
| Endpoint | Method | SDK Method |
|
||||
|--------------------------------------------------|--------|-----------------------------------------|
|
||||
| POST /api/v1/agents | POST | `Agents.RegisterAgent` |
|
||||
| GET /api/v1/agents | GET | `Agents.ListAgents` |
|
||||
| GET /api/v1/agents/:id | GET | `Agents.GetAgent` |
|
||||
| PATCH /api/v1/agents/:id | PATCH | `Agents.UpdateAgent` |
|
||||
| DELETE /api/v1/agents/:id | DELETE | `Agents.DecommissionAgent` |
|
||||
| POST /api/v1/agents/:id/credentials | POST | `Credentials.GenerateCredential` |
|
||||
| GET /api/v1/agents/:id/credentials | GET | `Credentials.ListCredentials` |
|
||||
| POST /api/v1/agents/:id/credentials/:cid/rotate | POST | `Credentials.RotateCredential` |
|
||||
| DELETE /api/v1/agents/:id/credentials/:cid | DELETE | `Credentials.RevokeCredential` |
|
||||
| POST /api/v1/token | POST | (TokenManager — automatic) |
|
||||
| POST /api/v1/token/introspect | POST | `Tokens.IntrospectToken` |
|
||||
| POST /api/v1/token/revoke | POST | `Tokens.RevokeToken` |
|
||||
| GET /api/v1/audit | GET | `Audit.QueryAuditLog` |
|
||||
| GET /api/v1/audit/:id | GET | `Audit.GetAuditEvent` |
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 — see [LICENSE](../LICENSE).
|
||||
Reference in New Issue
Block a user