Files
sentryagent-idp/docs/engineering/11-sdk-guide.md
SentryAgent.ai Developer eced5f8699 docs: engineering knowledge base for new hires
Complete docs/engineering/ suite — 12 documents covering company overview,
system architecture, tech stack ADRs, codebase structure, service deep dives,
annotated code walkthroughs, dev setup, engineering workflow, testing strategy,
deployment/ops, SDK guide, and README index. All content verified against
source files. All 82 tasks in openspec/changes/engineering-docs/tasks.md
marked complete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 12:38:42 +00:00

14 KiB

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 classAgentIdPError (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:

npm install @sentryagent/idp-sdk

Requirements: Node.js 18+ (uses native fetch).

Complete example:

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.agentsAgentRegistryClient (register, list, get, update, decommission)
  • client.credentialsCredentialClient (generate, list, rotate, revoke)
  • client.tokensTokenClient (introspect, revoke)
  • client.auditAuditClient (query, get event)

3. Python SDK

Install:

pip install sentryagent-idp

Requirements: Python 3.9+. Synchronous client uses requests; asynchronous client uses httpx.

Synchronous example

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

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:

go get github.com/sentryagent/idp-sdk-go

Requirements: Go 1.21+.

Complete example:

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:

<dependency>
  <groupId>ai.sentryagent</groupId>
  <artifactId>idp-sdk</artifactId>
  <version>1.0.0</version>
</dependency>

Gradle:

implementation 'ai.sentryagent:idp-sdk:1.0.0'

Requirements: Java 17+.

Synchronous example

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)

import java.util.concurrent.CompletableFuture;

// Every sync method has an async counterpart
CompletableFuture<Agent> 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/<client>.ts
  • Add TypeScript request/response types in src/types.ts
  • Add JSDoc with @param, @returns, and @throws
  • Add unit test in tests/<client>.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 <file>_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<T> 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)