@agents-space/client
v0.1.0
Published
Agent SDK for agentids.space — fetch ID Tokens, sign DPoP proofs, and call any agentids-aware service. Works in Node 22+ and modern browsers.
Maintainers
Readme
@agents-space/client
Agent SDK for agentids.space. Sign requests with a private_key_jwt client assertion to obtain an ID Token, then attach DPoP-bound proofs to every Relying Party call.
Install
pnpm add @agents-space/clientWorks in Node 22+ and modern browsers (uses Web Crypto only — no node:* imports).
Confidential agent (server-side)
import { AgentClient } from "@agents-space/client";
const client = new AgentClient({
agentId: process.env.AGENTIDS_AGENT_ID!,
privateKey: process.env.AGENTIDS_PRIVATE_KEY_PEM!,
issuer: "https://agentids.space",
});
const res = await client.fetch("https://rp.example.com/api/comment", {
method: "POST",
audience: "rp.example.com",
headers: { "content-type": "application/json" },
body: JSON.stringify({ text: "hi" }),
});The SDK:
- mints an ID Token (cached per audience for ~10 min, refreshed ~60s before expiry)
- builds a fresh DPoP proof bound to (method, URL, sha256(id_token)) for each request
- attaches
Authorization: AgentIDs <token>andDPoP: <proof>
Device agent (on-device)
For browser extensions, desktop apps, mobile clients — anywhere the agent runs on a user's machine.
const result = await AgentClient.bootstrap({
issuer: "https://agentids.space",
agentName: "My Browser Helper",
deviceLabel: "Hoang's MacBook",
onUserCode: ({ userCode, verificationUriComplete }) => {
window.open(verificationUriComplete, "_blank");
showInUI(`Code: ${userCode}`);
},
});
const client = new AgentClient({
agentId: result.agentId,
privateKey: result.privateKey, // CryptoKey, generated locally
publicJwk: result.publicJwk, // required when passing CryptoKey
issuer: "https://agentids.space",
});The keypair is generated on-device with crypto.subtle.generateKey({name:"ECDSA", namedCurve:"P-256"}). Only the public key reaches the server.
Persisting on-device keys
The returned privateKey is a CryptoKey — extractable by default so you can store it however you like.
// IndexedDB (browser)
import { openDB } from "idb";
const db = await openDB("agentids", 1, { upgrade: (d) => d.createObjectStore("k") });
await db.put("k", { agentId, privateKey, publicJwk }, "current");
// PEM file (Node)
const pkcs8 = await crypto.subtle.exportKey("pkcs8", result.privateKey);
const pem = `-----BEGIN PRIVATE KEY-----\n${Buffer.from(pkcs8).toString("base64").match(/.{1,64}/g).join("\n")}\n-----END PRIVATE KEY-----\n`;
fs.writeFileSync(path, pem, { mode: 0o600 });Token lifecycle
- Cache key:
audience(different RPs get different tokens). - TTL: 10 minutes.
- Refresh threshold: 60s before expiry.
- Concurrent calls dedupe — no thundering herd at /token.
