Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

TypeScript client for the AgentScore trust and reputation API.

## Identity Model

## Methods

- `getReputation(address, options?)` — cached reputation lookup (free)
- `assess(address, options?)` — identity gate with policy (paid). Accepts `operatorToken` for non-wallet agents.
- `createSession(options?)` — create verification session for identity bootstrapping
- `pollSession(sessionId, pollSecret)` — poll session status, returns credential when verified
- `createCredential(options?)` — create operator credential (24h TTL default)
- `listCredentials()` — list active credentials
- `revokeCredential(id)` — revoke a credential

## Architecture

Single-package TypeScript library published to npm.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
ci:
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v6
- uses: useblacksmith/checkout@v1
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run lint
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: useblacksmith/checkout@v1

- name: Install osv-scanner
run: |
Expand Down
46 changes: 39 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,7 @@ console.log(rep.score.value, rep.score.grade);
const baseRep = await client.getReputation("0x1234...", { chain: "base" });
console.log(baseRep.agents); // only Base agents

// On-the-fly assessment with policy (paid)
const result = await client.assess("0x1234...", {
policy: { min_grade: "B", min_score: 35 },
});
console.log(result.decision, result.decision_reasons);

// Compliance assessment with verification policy
// Identity gate with policy (paid)
const gated = await client.assess("0x1234...", {
policy: {
require_kyc: true,
Expand All @@ -53,6 +47,44 @@ const verified = await client.getReputation("0x1234...");
console.log(verified.verification_level); // "none" | "wallet_claimed" | "kyc_verified"
```

### Credential-Based Identity

Agents without wallets can use operator credentials for identity:

```typescript
// Assess with an operator credential instead of a wallet address
const result = await client.assess(null, { operatorToken: "opc_..." });
console.log(result.decision); // "allow" | "deny"
```

### Verification Sessions

Bootstrap identity for first-time agents:

```typescript
// Create a session — returns a URL for the user to verify
const session = await client.createSession();
console.log(session.verify_url, session.poll_secret);

// Poll until the user completes verification
const status = await client.pollSession(session.session_id, session.poll_secret);
if (status.status === "verified") {
console.log(status.operator_token); // "opc_..." — use for future requests
}
```

### Credential Management

```typescript
const cred = await client.createCredential({ label: "my-agent", ttl_days: 7 });
console.log(cred.credential); // shown once

const list = await client.listCredentials();
console.log(list); // active, non-expired credentials

await client.revokeCredential(cred.id);
```

## Configuration

| Option | Type | Default | Description |
Expand Down
48 changes: 24 additions & 24 deletions bun.lock

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agent-score/sdk",
"version": "1.4.2",
"version": "1.5.0",
"description": "TypeScript client for the AgentScore trust and reputation API",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
Expand Down Expand Up @@ -50,15 +50,15 @@
"node": ">=18"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"@vitest/coverage-v8": "^4.1.3",
"dotenv": "^17.4.1",
"eslint": "^9.39.4",
"@eslint/js": "9",
"@vitest/coverage-v8": "^4.1.4",
"dotenv": "^17.4.2",
"eslint": "9",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-unused-imports": "^4.4.1",
"tsup": "^8.5.1",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.1",
"vitest": "^4.1.3"
"typescript": "^6.0.3",
"typescript-eslint": "^8.58.2",
"vitest": "^4.1.4"
}
}
70 changes: 68 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import type {
AgentScoreErrorBody,
AssessOptions,
AssessResponse,
CredentialCreateOptions,
CredentialCreateResponse,
CredentialListResponse,
CredentialRevokeResponse,
GetReputationOptions,
ReputationResponse,
SessionCreateOptions,
SessionCreateResponse,
SessionPollResponse,
} from './types';

export { AgentScoreError } from './errors';
Expand Down Expand Up @@ -41,8 +48,12 @@ export class AgentScore {
);
}

async assess(address: string, options?: AssessOptions): Promise<AssessResponse> {
const body: Record<string, unknown> = { address };
async assess(address: string, options?: AssessOptions): Promise<AssessResponse>;
async assess(address: null, options: AssessOptions & { operatorToken: string }): Promise<AssessResponse>;
async assess(address: string | null, options?: AssessOptions): Promise<AssessResponse> {
const body: Record<string, unknown> = {};
if (address) body.address = address;
if (options?.operatorToken) body.operator_token = options.operatorToken;
if (options?.chain) body.chain = options.chain;
if (options?.refresh !== undefined) body.refresh = options.refresh;
if (options?.policy) body.policy = options.policy;
Expand All @@ -54,6 +65,50 @@ export class AgentScore {
});
}

async createSession(options?: SessionCreateOptions): Promise<SessionCreateResponse> {
const body: Record<string, unknown> = {};
if (options?.context) body.context = options.context;
if (options?.product_name) body.product_name = options.product_name;

return this.request<SessionCreateResponse>('/v1/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
}

async pollSession(sessionId: string, pollSecret: string): Promise<SessionPollResponse> {
return this.request<SessionPollResponse>(
`/v1/sessions/${encodeURIComponent(sessionId)}`,
{
headers: { 'X-Poll-Secret': pollSecret },
},
);
}

async createCredential(options?: CredentialCreateOptions): Promise<CredentialCreateResponse> {
const body: Record<string, unknown> = {};
if (options?.label) body.label = options.label;
if (options?.ttl_days !== undefined) body.ttl_days = options.ttl_days;

return this.request<CredentialCreateResponse>('/v1/credentials', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
}

async listCredentials(): Promise<CredentialListResponse> {
return this.request<CredentialListResponse>('/v1/credentials');
}

async revokeCredential(id: string): Promise<CredentialRevokeResponse> {
return this.request<CredentialRevokeResponse>(
`/v1/credentials/${encodeURIComponent(id)}`,
{ method: 'DELETE' },
);
}

private async request<T>(path: string, options?: RequestInit): Promise<T> {
const url = `${this.baseUrl}${path}`;

Expand All @@ -74,6 +129,17 @@ export class AgentScore {
signal,
});

if (response.status === 429) {
const retryAfter = response.headers.get('retry-after');
const waitMs = retryAfter ? Number(retryAfter) * 1000 : 1000;
await new Promise((resolve) => setTimeout(resolve, Math.min(waitMs, 10_000)));

const retry = await fetch(url, { ...options, headers, signal });
if (retry.ok) return (await retry.json()) as T;

throw new AgentScoreError('rate_limited', 'Rate limit exceeded', 429);
}

if (!response.ok) {
let code = 'unknown_error';
let message = `Request failed with status ${response.status}`;
Expand Down
115 changes: 100 additions & 15 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,15 @@ export type VerificationLevel = 'none' | 'wallet_claimed' | 'kyc_verified';
export interface OperatorVerification {
level: VerificationLevel;
operator_type?: string | null;
claimed_at?: string | null;
verified_at?: string | null;
}

export interface DecisionPolicy {
min_grade?: Grade;
min_score?: number;
require_verified_payment_activity?: boolean;
require_kyc?: boolean;
require_sanctions_clear?: boolean;
min_age?: number;
blocked_jurisdictions?: string[];
allowed_jurisdictions?: string[];
require_entity_type?: string;
}

Expand All @@ -152,20 +149,11 @@ export interface AssessRequest {
}

export interface AssessResponse {
subject: Subject;
score: Score;
chains: ChainEntry[];
decision: string | null;
decision_reasons: string[];
on_the_fly: boolean;
data_semantics: string;
caveats: string[];
updated_at: string | null;
operator_score?: OperatorScore;
reputation?: Reputation;
agents?: AgentSummary[];
identity_method: 'wallet' | 'operator_token';
operator_verification?: OperatorVerification;
resolved_operator?: string;
resolved_operator?: string | null;
verify_url?: string;
policy_result?: {
all_passed: boolean;
Expand All @@ -176,6 +164,16 @@ export interface AssessResponse {
actual?: unknown;
}>;
} | null;
on_the_fly: boolean;
updated_at: string | null;
explanation?: Array<{
rule: string;
passed: boolean;
required: unknown;
actual: unknown;
message: string;
how_to_remedy: string | null;
}>;
}

export interface AgentScoreErrorBody {
Expand All @@ -194,4 +192,91 @@ export interface AssessOptions {
chain?: string;
refresh?: boolean;
policy?: DecisionPolicy;
operatorToken?: string;
}

export interface SessionCreateOptions {
context?: string;
product_name?: string;
}

export interface SessionCreateResponse {
session_id: string;
poll_secret: string;
verify_url: string;
poll_url: string;
expires_at: string;
}

export interface SessionPollNextSteps {
action: string;
user_message?: string;
header_name?: string;
poll_interval_seconds?: number;
eta_message?: string;
}

export interface SessionPollResponse {
session_id: string;
status: string;
operator_token?: string;
completed_at?: string;
next_steps?: SessionPollNextSteps;
retry_after_seconds?: number;
token_ttl_seconds?: number;
}

export interface CredentialCreateOptions {
label?: string;
ttl_days?: number;
}

export interface CredentialCreateResponse {
id: string;
credential: string;
prefix: string;
label: string;
expires_at: string;
created_at: string;
}

export interface CredentialCreateErrorResponse {
error: {
code: 'kyc_required';
message: string;
};
verify_url: string;
next_steps: {
action: string;
user_message: string;
};
}

export interface CredentialListItem {
id: string;
prefix: string;
label: string;
expires_at: string;
last_used_at: string | null;
created_at: string;
}

export interface AccountVerification {
kyc_status: string;
kyc_verified_at?: string | null;
jurisdiction?: string | null;
age_verified?: boolean;
age_bracket?: string | null;
sanctions_status?: string | null;
operator_type?: string | null;
}

export interface CredentialListResponse {
credentials: CredentialListItem[];
account_verification: AccountVerification;
}

export interface CredentialRevokeResponse {
id: string;
revoked: true;
}
Loading
Loading