@agentkeychain/agent
v0.1.0
Published
Agent SDK for AgentKeychain — agent authentication and service token introspection.
Maintainers
Readme
@agentkeychain/agent
Agent SDK for AgentKeychain — agent identity and OAuth-style token issuance for AI agents.
This package contains two clients:
Agent— used by an agent. Holds a refresh token, mints short-lived access tokens, and drives the agent-side of an auth session (binding an agent identity to a service login).ServiceClient— used by a service that consumes agent tokens. Wraps the RFC 7662 token introspection endpoint with HTTP Basic auth (client_id/client_secret).
Installation
npm install @agentkeychain/agent
# or
bun add @agentkeychain/agentAgent
For agents that need to authenticate themselves to AgentKeychain and bind their identity to a service's auth session.
Setup
import { Agent } from "@agentkeychain/agent";
const agent = new Agent({
agentId: process.env.AKC_AGENT_ID!, // "agent_..."
refreshToken: process.env.AKC_REFRESH_TOKEN!, // "art_..."
authServerUrl: "https://agentkeychain.com",
// Persist the new refresh token whenever it rotates:
onTokenRotated: async (newToken) => {
await saveSecret("AKC_REFRESH_TOKEN", newToken);
},
});Get a fresh access token
const accessToken = await agent.ensureAccessToken();ensureAccessToken() returns a cached access token, refreshing it (and rotating the refresh token) if it's missing or within 30 seconds of expiry. Whenever the refresh token rotates, onTokenRotated is called so you can persist the new value — losing it means re-enrolling the agent.
Bind the agent to a pending auth session
When the agent navigates to a service's authorize URL and detects an <meta name="akc-session-id"> tag, it should call:
const result = await agent.authenticate(sessionId);
// { status: "authenticated", client_id, requires_consent }Create a pre-bound auth session (CLI flow)
For flows where the agent generates the authorize URL on behalf of a user (e.g. a CLI handing a link to a human):
const { session_id, authorize_url, expires_in } = await agent.createSession(
"https://example.com/oauth/authorize?client_id=...&..."
);
// Hand `authorize_url` to the user to complete consent.
// Then poll:
const status = await agent.checkSession(session_id);
// status.status: "pending" | "completed" | "expired"Refresh a downstream client session
If your code is also acting as the OAuth client (holding a client_id / client_secret issued by AgentKeychain) and needs to refresh an end-user session:
const session = await agent.refreshClientSession({
clientId: "akc_client_...",
clientSecret: process.env.AKC_CLIENT_SECRET!,
refreshToken: userRefreshToken,
scope: "calendar.read",
});
// { accessToken, refreshToken, idToken, expiresIn, scope }Errors
import { AgentError } from "@agentkeychain/agent";
try {
await agent.ensureAccessToken();
} catch (err) {
if (err instanceof AgentError) {
switch (err.code) {
case "REQUEST_FAILED": // server returned an error response
case "NETWORK_ERROR": // couldn't reach the server
case "TIMEOUT": // request timed out
case "INVALID_RESPONSE": // server returned non-JSON
}
}
}Options
interface AgentOptions {
agentId: string;
refreshToken: string;
authServerUrl: string;
requestTimeout?: number; // ms, default 10_000
fetch?: typeof globalThis.fetch; // override for testing
onTokenRotated?: (newRefreshToken: string) => void | Promise<void>;
}ServiceClient
For services that accept agent-issued tokens and need to verify them via the introspection endpoint.
Setup
import { ServiceClient } from "@agentkeychain/agent";
const service = new ServiceClient({
endpoint: "https://agentkeychain.com",
clientId: "akc_client_...",
clientSecret: process.env.AKC_CLIENT_SECRET!,
});Introspect a token
const result = await service.introspect(token);
// RFC 7662 response: { active, scope?, client_id?, sub?, exp?, ... }
if (!result.active) {
return new Response("Unauthorized", { status: 401 });
}Or just:
if (!(await service.isTokenActive(token))) { ... }Errors
import { ServiceClientError } from "@agentkeychain/agent";
try {
await service.introspect(token);
} catch (err) {
if (err instanceof ServiceClientError) {
switch (err.code) {
case "AUTH_FAILED": // invalid client_id / client_secret
case "REQUEST_FAILED": // server returned an error
case "NETWORK_ERROR": // couldn't reach the server
case "TIMEOUT": // request timed out
case "INVALID_RESPONSE": // server returned non-JSON
}
}
}Options
interface ServiceClientOptions {
endpoint: string;
clientId: string;
clientSecret: string;
requestTimeout?: number; // ms, default 10_000
fetch?: typeof globalThis.fetch; // override for testing
}License
Apache-2.0
