# 11 — SDK Integration Guide AgentIdP ships four official client SDKs — Node.js, Python, Go, and Java. All four expose an identical API surface, handle OAuth 2.0 token acquisition automatically, and throw typed errors. This document covers installation, complete working examples, error handling, and the contribution guide for adding new endpoints. --- ## 1. SDK Architecture Overview Every SDK composes the same four service clients: | Service client | Node.js | Python | Go | Java | |---------------|---------|--------|----|------| | Agent Registry | `AgentRegistryClient` | `AgentRegistryClient` | `AgentsClient` | `AgentServiceClient` | | Credential Management | `CredentialClient` | `CredentialClient` | `CredentialsClient` | `CredentialServiceClient` | | Token Operations | `TokenClient` | `TokenClient` | `TokenServiceClient` | `TokenServiceClient` | | Audit Log | `AuditClient` | `AuditClient` | `AuditClient` | `AuditServiceClient` | All four SDKs also implement: - **`AgentIdPClient`** — the top-level client that composes all four service clients and wires them to a shared `TokenManager`. - **`TokenManager`** — fetches and caches the OAuth 2.0 access token. Automatically requests a new token when the cached one is within 60 seconds of expiry. Thread-safe / goroutine-safe. - **Typed error class** — `AgentIdPError` (Node.js, Python, Go) or `AgentIdPException` (Java) — with `code`, `httpStatus`, and `details` fields. This consistency is a maintained standard. When a new API endpoint is added to the server, it must be added to all four SDKs simultaneously. --- ## 2. Node.js SDK **Install:** ```bash npm install @sentryagent/idp-sdk ``` **Requirements:** Node.js 18+ (uses native `fetch`). **Complete example:** ```typescript import { AgentIdPClient, AgentIdPError } from '@sentryagent/idp-sdk'; const client = new AgentIdPClient({ baseUrl: 'http://localhost:3000', clientId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', clientSecret: 'sk_live_...', // Optional: restrict scopes. Defaults to all four. // scopes: ['agents:read', 'tokens:read'], }); // Register a new agent const agent = await client.agents.registerAgent({ email: 'classifier-v2@myorg.ai', agentType: 'classifier', version: '2.0.0', capabilities: ['resume:read', 'classify'], owner: 'platform-team', deploymentEnv: 'production', }); console.log('Registered:', agent.agentId); // List active agents (token acquired automatically) const { data: agents } = await client.agents.listAgents({ status: 'active' }); console.log('Active agents:', agents.length); // Generate credentials for an agent const cred = await client.credentials.generateCredential(agent.agentId); console.log('Client secret (store this — shown once):', cred.clientSecret); // Rotate credentials const newCred = await client.credentials.rotateCredential(agent.agentId, cred.credentialId); console.log('New secret:', newCred.clientSecret); // Introspect a token const introspection = await client.tokens.introspectToken('eyJ...'); console.log('Active:', introspection.active); // Error handling try { await client.agents.getAgent('non-existent-id'); } catch (err) { if (err instanceof AgentIdPError) { console.error(err.code); // e.g. AGENT_NOT_FOUND console.error(err.httpStatus); // e.g. 404 console.error(err.details); // optional structured context } } // Force a fresh token on the next call (e.g. after credential rotation) client.clearTokenCache(); ``` **Token manager behaviour:** `TokenManager` in `sdk/src/token-manager.ts` caches the token and requests a new one when fewer than 60 seconds remain before expiry. **Service clients are accessible at:** - `client.agents` — `AgentRegistryClient` (register, list, get, update, decommission) - `client.credentials` — `CredentialClient` (generate, list, rotate, revoke) - `client.tokens` — `TokenClient` (introspect, revoke) - `client.audit` — `AuditClient` (query, get event) --- ## 3. Python SDK **Install:** ```bash pip install sentryagent-idp ``` **Requirements:** Python 3.9+. Synchronous client uses `requests`; asynchronous client uses `httpx`. ### Synchronous example ```python from sentryagent_idp import AgentIdPClient, AgentIdPError, RegisterAgentRequest client = AgentIdPClient( base_url="http://localhost:3000", client_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890", client_secret="sk_live_...", # scopes=["agents:read", "tokens:read"], # optional ) # Register an agent agent = client.agents.register_agent(RegisterAgentRequest( email="screener@myorg.ai", agent_type="screener", version="1.0.0", capabilities=["resume:read"], owner="recruiting-team", deployment_env="production", )) print("Registered:", agent.agent_id) # List agents result = client.agents.list_agents(status="active", page=1, limit=20) for a in result.data: print(a.agent_id, a.status) # Generate credentials cred = client.credentials.generate_credential(agent.agent_id) print("Client secret (shown once):", cred.client_secret) # Error handling try: client.agents.get_agent("non-existent-id") except AgentIdPError as e: print(e.code) # e.g. AGENT_NOT_FOUND print(e.http_status) # e.g. 404 print(e.details) # optional dict ``` ### Asynchronous example ```python import asyncio from sentryagent_idp import AsyncAgentIdPClient, AgentIdPError async def main() -> None: client = AsyncAgentIdPClient( base_url="http://localhost:3000", client_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890", client_secret="sk_live_...", ) result = await client.agents.list_agents(status="active") print(f"Found {result.total} active agents") # Rotate a credential new_cred = await client.credentials.rotate_credential( "agent-uuid", "credential-uuid" ) print("New secret:", new_cred.client_secret) asyncio.run(main()) ``` `AsyncAgentIdPClient` uses an `AsyncTokenManager` backed by `httpx.AsyncClient`. Both sync and async clients are available from the `sentryagent_idp` top-level package. --- ## 4. Go SDK **Install:** ```bash go get github.com/sentryagent/idp-sdk-go ``` **Requirements:** Go 1.21+. **Complete example:** ```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: "http://localhost:3000", ClientID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", ClientSecret: "sk_live_...", // Scope: "agents:read agents:write", // optional }) // Register an agent agent, err := client.Agents.RegisterAgent(ctx, agentidp.RegisterAgentRequest{ Email: "screener@myorg.ai", AgentType: "screener", Version: "1.0.0", Capabilities: []string{"resume:read"}, Owner: "recruiting-team", DeploymentEnv: "production", }) if err != nil { // Type-assert for structured error information var idpErr *agentidp.AgentIdPError if errors.As(err, &idpErr) { log.Fatalf("API error: code=%s status=%d", idpErr.Code, idpErr.HTTPStatus) } log.Fatal(err) } fmt.Println("Registered:", agent.AgentID) // List agents with filters list, err := client.Agents.ListAgents(ctx, &agentidp.ListAgentsParams{ Status: "active", Page: 1, Limit: 20, }) if err != nil { log.Fatal(err) } fmt.Printf("Found %d agents\n", list.Total) // Generate credentials cred, err := client.Credentials.GenerateCredential(ctx, agent.AgentID, nil) if err != nil { log.Fatal(err) } fmt.Println("Client secret (shown once):", cred.ClientSecret) // Rotate credentials newCred, err := client.Credentials.RotateCredential(ctx, agent.AgentID, cred.CredentialID, nil) if err != nil { log.Fatal(err) } fmt.Println("New secret:", newCred.ClientSecret) } ``` `context.Context` is the first parameter of every method — use `context.Background()` for simple cases or a derived context with deadline/cancellation for production code. The `TokenManager` is goroutine-safe and the client is safe for concurrent use. --- ## 5. Java SDK **Maven dependency:** ```xml ai.sentryagent idp-sdk 1.0.0 ``` **Gradle:** ```groovy implementation 'ai.sentryagent:idp-sdk:1.0.0' ``` **Requirements:** Java 17+. ### Synchronous example ```java import ai.sentryagent.idp.AgentIdPClient; import ai.sentryagent.idp.AgentIdPException; import ai.sentryagent.idp.models.*; // Builder pattern — scope is optional (defaults to all four scopes) AgentIdPClient client = new AgentIdPClient( "http://localhost:3000", "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "sk_live_..." ); // Register an agent Agent agent = client.agents().registerAgent( RegisterAgentRequest.builder() .email("screener@myorg.ai") .agentType("screener") .version("1.0.0") .capabilities(List.of("resume:read")) .owner("recruiting-team") .deploymentEnv("production") .build() ); System.out.println("Registered: " + agent.getAgentId()); // List agents PaginatedAgents result = client.agents().listAgents( ListAgentsParams.builder().status("active").page(1).limit(20).build() ); System.out.println("Total: " + result.getTotal()); // Generate credentials CredentialWithSecret cred = client.credentials().generateCredential(agent.getAgentId()); System.out.println("Client secret (shown once): " + cred.getClientSecret()); // Rotate credentials CredentialWithSecret newCred = client.credentials().rotateCredential( agent.getAgentId(), cred.getCredentialId() ); System.out.println("New secret: " + newCred.getClientSecret()); // Error handling try { client.agents().getAgent("non-existent-id"); } catch (AgentIdPException ex) { System.out.printf("code=%s status=%d%n", ex.getCode(), ex.getHttpStatus()); // e.g. code=AGENT_NOT_FOUND status=404 } ``` ### Async example (CompletableFuture) ```java import java.util.concurrent.CompletableFuture; // Every sync method has an async counterpart CompletableFuture future = client.agents().getAgentAsync("agent-uuid"); future.thenAccept(a -> System.out.println(a.getAgentId())); // Compose multiple async calls client.agents().getAgentAsync("agent-uuid") .thenCompose(a -> client.credentials().generateCredentialAsync(a.getAgentId())) .thenAccept(cred -> System.out.println("New secret: " + cred.getClientSecret())) .exceptionally(ex -> { if (ex.getCause() instanceof AgentIdPException idpEx) { System.err.printf("code=%s%n", idpEx.getCode()); } return null; }); ``` The `TokenManager` is thread-safe. `AgentIdPClient` is safe for concurrent use from multiple threads. --- ## 6. SDK Contribution Guide — Adding a New Endpoint When the server adds a new API endpoint, update all four SDKs. The checklist below covers each SDK. ### Node.js SDK (`sdk/`) ``` src/ services/ agents.ts # AgentRegistryClient credentials.ts # CredentialClient token.ts # TokenClient audit.ts # AuditClient types.ts # All request/response type definitions token-manager.ts # TokenManager client.ts # AgentIdPClient (top-level) errors.ts # AgentIdPError ``` Checklist: - [ ] Add method to the appropriate service client in `src/services/.ts` - [ ] Add TypeScript request/response types in `src/types.ts` - [ ] Add JSDoc with `@param`, `@returns`, and `@throws` - [ ] Add unit test in `tests/.test.ts` - [ ] Verify `npx tsc --strict` exits 0 ### Python SDK (`sdk-python/`) ``` src/sentryagent_idp/ services/ agents.py # AgentRegistryClient + AsyncAgentRegistryClient credentials.py # CredentialClient + AsyncCredentialClient token.py # TokenClient + AsyncTokenClient audit.py # AuditClient + AsyncAuditClient client.py # AgentIdPClient + AsyncAgentIdPClient token_manager.py # TokenManager (sync) async_token_manager.py # AsyncTokenManager errors.py # AgentIdPError types.py # TypedDict / dataclass definitions ``` Checklist: - [ ] Add method to both the sync and async service clients - [ ] Add type hints (all parameters and return types) - [ ] Verify `mypy --strict` passes - [ ] Add unit test in `tests/` - [ ] Verify `pytest` passes with >80% coverage ### Go SDK (`sdk-go/`) ``` agentidp/ client.go # AgentIdPClient + AgentIdPClientConfig agents.go # AgentsClient credentials.go # CredentialsClient token_service.go # TokenServiceClient audit.go # AuditClient token_manager.go # TokenManager (goroutine-safe) errors.go # AgentIdPError types.go # All request/response struct types request.go # Shared HTTP request helper ``` Checklist: - [ ] Add method to the appropriate `*Client` type - [ ] Use `context.Context` as the first parameter - [ ] Add godoc comment above the method - [ ] Add request/response struct types in `types.go` if needed - [ ] Add unit test in `_test.go` - [ ] Verify `go vet ./... && staticcheck ./...` pass ### Java SDK (`sdk-java/`) ``` src/main/java/ai/sentryagent/idp/ AgentIdPClient.java # Top-level client services/ AgentServiceClient.java # Agent Registry CredentialServiceClient.java TokenServiceClient.java AuditServiceClient.java models/ # Request/response POJOs (@JsonProperty) TokenManager.java # Thread-safe token caching AgentIdPException.java # Typed exception ``` Checklist: - [ ] Add sync method to the appropriate service client - [ ] Add `CompletableFuture` async counterpart with the `Async` suffix - [ ] Add request/response POJO in `models/` with `@JsonProperty` annotations - [ ] Add Javadoc on the method - [ ] Add JUnit 5 test in `src/test/java/` - [ ] Verify `mvn verify` passes (compiles, tests, and checks coverage)