@agentidcard/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -0
- package/index.d.ts +122 -0
- package/index.mjs +9 -0
- package/package.json +38 -0
- package/src/client.mjs +211 -0
- package/src/crypto.mjs +105 -0
- package/src/envelope.mjs +109 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @agentidcard/sdk
|
|
2
|
+
|
|
3
|
+
Official JavaScript SDK for Agent ID Card.
|
|
4
|
+
|
|
5
|
+
Works in Node.js 18+ and modern browsers (uses WebCrypto API).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @agentidcard/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { AilClient, verifyOffline } from "@agentidcard/sdk";
|
|
17
|
+
|
|
18
|
+
const client = new AilClient({ serverUrl: "https://api.agentidcard.org" });
|
|
19
|
+
|
|
20
|
+
// 1. Register owner
|
|
21
|
+
const owner = await client.registerOwner({ email: "you@example.com", org: "your_org" });
|
|
22
|
+
|
|
23
|
+
// 2. Verify email with the OTP sent to the owner's inbox
|
|
24
|
+
await client.verifyEmail({ owner_key_id: owner.owner_key_id, otp: "123456" });
|
|
25
|
+
|
|
26
|
+
// 3. Register agent (SDK handles signing automatically)
|
|
27
|
+
const agent = await client.registerAgent({
|
|
28
|
+
owner_key_id: owner.owner_key_id,
|
|
29
|
+
private_key_jwk: owner.private_key_jwk,
|
|
30
|
+
payload: {
|
|
31
|
+
display_name: "MyAgent",
|
|
32
|
+
role: "assistant",
|
|
33
|
+
scope: {
|
|
34
|
+
network: "none",
|
|
35
|
+
secrets: "none",
|
|
36
|
+
write_access: false,
|
|
37
|
+
approval_policy: {
|
|
38
|
+
irreversible_actions: "human_required",
|
|
39
|
+
external_posting: "human_required",
|
|
40
|
+
destructive_file_ops: "human_required",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log(agent.ail_id); // AIL-2026-00001
|
|
47
|
+
|
|
48
|
+
// 4. Verify credential (online)
|
|
49
|
+
const result = await client.verify(agent.credential.token);
|
|
50
|
+
console.log(result.valid, result.display_name);
|
|
51
|
+
|
|
52
|
+
// 5. Verify offline (no server call)
|
|
53
|
+
const keys = await client.getPublicKeys();
|
|
54
|
+
const offline = await verifyOffline(agent.credential.token, keys.keys[0]);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Session-based registration
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
const login = await client.loginOwner({ email: "you@example.com" });
|
|
61
|
+
|
|
62
|
+
const session = await client.verifyLogin({
|
|
63
|
+
owner_key_id: login.owner_key_id,
|
|
64
|
+
otp: "123456",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const agent = await client.registerAgentWithSession({
|
|
68
|
+
session_token: session.session_token,
|
|
69
|
+
payload: {
|
|
70
|
+
display_name: "OpsAgent",
|
|
71
|
+
role: "automation",
|
|
72
|
+
provider: "openai",
|
|
73
|
+
model: "gpt-5.4",
|
|
74
|
+
scope: {
|
|
75
|
+
network: "restricted",
|
|
76
|
+
secrets: "none",
|
|
77
|
+
write_access: false,
|
|
78
|
+
approval_policy: {
|
|
79
|
+
irreversible_actions: "human_required",
|
|
80
|
+
external_posting: "human_required",
|
|
81
|
+
destructive_file_ops: "human_required",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Build a v1 envelope
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
import { buildEnvelope } from "@agentidcard/sdk";
|
|
92
|
+
|
|
93
|
+
const envelope = buildEnvelope({
|
|
94
|
+
ail_id: agent.ail_id,
|
|
95
|
+
credential: agent.credential,
|
|
96
|
+
signal_glyph: agent.signal_glyph,
|
|
97
|
+
behavior_fingerprint: agent.behavior_fingerprint,
|
|
98
|
+
agent: {
|
|
99
|
+
id: "agent_myagent_01",
|
|
100
|
+
provider: "anthropic",
|
|
101
|
+
model: "claude-sonnet-4-6",
|
|
102
|
+
runtime: "claude_code",
|
|
103
|
+
},
|
|
104
|
+
owner: {
|
|
105
|
+
key_id: owner.owner_key_id,
|
|
106
|
+
org: "your_org",
|
|
107
|
+
email_hash: "sha256:...",
|
|
108
|
+
},
|
|
109
|
+
scope: agent_scope,
|
|
110
|
+
delegation: { mode: "direct", chain_depth: 0 },
|
|
111
|
+
runtime: {
|
|
112
|
+
session_id: "sess_001",
|
|
113
|
+
run_id: "run_001",
|
|
114
|
+
surface: "cli",
|
|
115
|
+
host: "localhost",
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Revoke an agent
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
await client.revokeAgent({
|
|
124
|
+
ail_id: agent.ail_id,
|
|
125
|
+
owner_key_id: owner.owner_key_id,
|
|
126
|
+
private_key_jwk: owner.private_key_jwk,
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## API
|
|
131
|
+
|
|
132
|
+
### `new AilClient({ serverUrl })`
|
|
133
|
+
|
|
134
|
+
### `client.registerOwner({ email, org? })`
|
|
135
|
+
### `client.verifyEmail({ owner_key_id, otp })`
|
|
136
|
+
### `client.loginOwner({ email })`
|
|
137
|
+
### `client.verifyLogin({ owner_key_id, otp })`
|
|
138
|
+
### `client.registerAgent({ owner_key_id, private_key_jwk, payload })`
|
|
139
|
+
### `client.registerAgentWithSession({ session_token, payload })`
|
|
140
|
+
### `client.revokeAgent({ ail_id, owner_key_id, private_key_jwk })`
|
|
141
|
+
### `client.verify(token)` - online verification
|
|
142
|
+
### `client.verifyOffline(token)` - offline (fetches JWKS once, caches)
|
|
143
|
+
### `client.getPublicKeys()` - returns JWKS
|
|
144
|
+
|
|
145
|
+
### `verifyOffline(token, publicKeyJwk)` - standalone offline verification
|
|
146
|
+
|
|
147
|
+
### `buildEnvelope(options)` - assemble a v1 envelope
|
|
148
|
+
|
|
149
|
+
### `generateOwnerKeypair()` - generate EC P-256 keypair (returns `{ public_key_jwk, private_key_jwk }`)
|
|
150
|
+
### `signPayload(payload, private_key_jwk)` - sign a payload, returns base64url
|
|
151
|
+
### `canonicalJson(obj)` - canonical JSON serialization
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export type Jwk = Record<string, unknown>;
|
|
2
|
+
export type JsonValue =
|
|
3
|
+
| string
|
|
4
|
+
| number
|
|
5
|
+
| boolean
|
|
6
|
+
| null
|
|
7
|
+
| JsonValue[]
|
|
8
|
+
| { [key: string]: JsonValue };
|
|
9
|
+
|
|
10
|
+
export interface OwnerRegistrationResponse {
|
|
11
|
+
owner_key_id: string;
|
|
12
|
+
public_key_jwk: Jwk;
|
|
13
|
+
private_key_jwk: Jwk;
|
|
14
|
+
message?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LoginResponse {
|
|
18
|
+
owner_key_id: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface VerifyLoginResponse {
|
|
23
|
+
authenticated: boolean;
|
|
24
|
+
session_token: string;
|
|
25
|
+
owner: Record<string, unknown>;
|
|
26
|
+
agents: Array<Record<string, unknown>>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AgentRegistrationOptions {
|
|
30
|
+
display_name: string;
|
|
31
|
+
role: string;
|
|
32
|
+
provider?: string | null;
|
|
33
|
+
model?: string | null;
|
|
34
|
+
scope: Record<string, unknown>;
|
|
35
|
+
wallet_address?: string;
|
|
36
|
+
plan?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CredentialResponse {
|
|
40
|
+
ail_id: string;
|
|
41
|
+
credential: Record<string, unknown>;
|
|
42
|
+
signal_glyph: Record<string, unknown>;
|
|
43
|
+
behavior_fingerprint: Record<string, unknown>;
|
|
44
|
+
nft_image_url?: string;
|
|
45
|
+
nft_metadata_url?: string;
|
|
46
|
+
nft?: {
|
|
47
|
+
token_id: string;
|
|
48
|
+
tx_hash?: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface OfflineVerificationResult {
|
|
53
|
+
valid: boolean;
|
|
54
|
+
ail_id?: string;
|
|
55
|
+
display_name?: string;
|
|
56
|
+
role?: string;
|
|
57
|
+
owner_org?: string;
|
|
58
|
+
issued?: string;
|
|
59
|
+
expires?: string;
|
|
60
|
+
reason?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AilClientOptions {
|
|
64
|
+
serverUrl?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ReputationQueryParams {
|
|
68
|
+
source?: string;
|
|
69
|
+
season?: number;
|
|
70
|
+
limit?: number;
|
|
71
|
+
dimension?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export declare class AilClient {
|
|
75
|
+
constructor(options?: AilClientOptions);
|
|
76
|
+
registerOwner(options: { email: string; org?: string }): Promise<OwnerRegistrationResponse>;
|
|
77
|
+
verifyEmail(options: { owner_key_id: string; otp: string }): Promise<Record<string, unknown>>;
|
|
78
|
+
loginOwner(options: { email: string }): Promise<LoginResponse>;
|
|
79
|
+
verifyLogin(options: { owner_key_id: string; otp: string }): Promise<VerifyLoginResponse>;
|
|
80
|
+
registerAgent(options: {
|
|
81
|
+
owner_key_id: string;
|
|
82
|
+
private_key_jwk: Jwk;
|
|
83
|
+
payload: AgentRegistrationOptions;
|
|
84
|
+
}): Promise<CredentialResponse>;
|
|
85
|
+
registerAgentWithSession(options: {
|
|
86
|
+
session_token: string;
|
|
87
|
+
payload: AgentRegistrationOptions;
|
|
88
|
+
}): Promise<CredentialResponse>;
|
|
89
|
+
revokeAgent(options: {
|
|
90
|
+
ail_id: string;
|
|
91
|
+
owner_key_id: string;
|
|
92
|
+
private_key_jwk: Jwk;
|
|
93
|
+
}): Promise<Record<string, unknown>>;
|
|
94
|
+
verify(token: string): Promise<Record<string, unknown>>;
|
|
95
|
+
verifyOffline(token: string): Promise<OfflineVerificationResult>;
|
|
96
|
+
getPublicKeys(): Promise<Record<string, unknown>>;
|
|
97
|
+
getReputation(ailId: string): Promise<Record<string, unknown>>;
|
|
98
|
+
getReputationHistory(ailId: string, params?: ReputationQueryParams): Promise<Record<string, unknown>>;
|
|
99
|
+
compareAgents(ailId: string, otherAilId: string): Promise<Record<string, unknown>>;
|
|
100
|
+
getLeaderboard(params?: ReputationQueryParams): Promise<Record<string, unknown>>;
|
|
101
|
+
getBadges(ailId: string): Promise<Record<string, unknown>>;
|
|
102
|
+
getSeasonReport(ailId: string, season: number, params?: { source?: string }): Promise<Record<string, unknown>>;
|
|
103
|
+
awardBadge(options: {
|
|
104
|
+
source_name: string;
|
|
105
|
+
agent_id: string;
|
|
106
|
+
badge_id: string;
|
|
107
|
+
private_key_jwk: Jwk;
|
|
108
|
+
merkle_proof?: string | null;
|
|
109
|
+
}): Promise<Record<string, unknown>>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export declare function verifyOffline(token: string, publicKeyJwk: Jwk): Promise<OfflineVerificationResult>;
|
|
113
|
+
export declare function buildEnvelope(options: Record<string, unknown>): Record<string, unknown>;
|
|
114
|
+
export declare function generateOwnerKeypair(): Promise<{ public_key_jwk: Jwk; private_key_jwk: Jwk }>;
|
|
115
|
+
export declare function signPayload(payload: JsonValue | Record<string, unknown>, privateKeyJwk: Jwk): Promise<string>;
|
|
116
|
+
export declare function verifyOwnerSignature(
|
|
117
|
+
payload: JsonValue | Record<string, unknown>,
|
|
118
|
+
signatureB64url: string,
|
|
119
|
+
publicKeyJwk: Jwk
|
|
120
|
+
): Promise<boolean>;
|
|
121
|
+
export declare function canonicalJson(obj: JsonValue | Record<string, unknown>): string;
|
|
122
|
+
export declare function sha256hexAsync(data: string): Promise<string>;
|
package/index.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentidcard/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official JavaScript SDK for Agent ID Card - AI agent identity credentials",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.mjs",
|
|
7
|
+
"types": "./index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./index.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"index.d.ts",
|
|
19
|
+
"index.mjs",
|
|
20
|
+
"src/",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"keywords": ["agent-identity", "agentidcard", "ail", "credential", "jwt", "nft", "erc721"],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Agent ID Card <contact@agentidcard.org>",
|
|
26
|
+
"homepage": "https://agentidcard.org",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/sinmb79/Agent-Identity-Layer.git",
|
|
30
|
+
"directory": "sdk/js"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"jose": "^5.2.3"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/client.mjs
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { signPayload } from "./crypto.mjs";
|
|
2
|
+
import { jwtVerify, importJWK } from "jose";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AilClient communicates with the Agent ID Card issuance server.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const client = new AilClient({ serverUrl: "https://api.agentidcard.org" });
|
|
9
|
+
*/
|
|
10
|
+
export class AilClient {
|
|
11
|
+
constructor({ serverUrl = "https://api.agentidcard.org" } = {}) {
|
|
12
|
+
this.serverUrl = serverUrl.replace(/\/$/, "");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#withQuery(path, params = {}) {
|
|
16
|
+
const search = new URLSearchParams();
|
|
17
|
+
for (const [key, value] of Object.entries(params)) {
|
|
18
|
+
if (value === undefined || value === null || value === "") continue;
|
|
19
|
+
search.set(key, String(value));
|
|
20
|
+
}
|
|
21
|
+
const query = search.toString();
|
|
22
|
+
return query ? `${path}?${query}` : path;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async #post(path, body) {
|
|
26
|
+
const res = await fetch(`${this.serverUrl}${path}`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: { "Content-Type": "application/json" },
|
|
29
|
+
body: JSON.stringify(body),
|
|
30
|
+
});
|
|
31
|
+
const data = await res.json();
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
const err = new Error(data.message ?? data.error ?? `HTTP ${res.status}`);
|
|
34
|
+
err.code = data.error;
|
|
35
|
+
err.status = res.status;
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async #get(path) {
|
|
42
|
+
const res = await fetch(`${this.serverUrl}${path}`);
|
|
43
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
44
|
+
return res.json();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// -------------------------------------------------------------------------
|
|
48
|
+
// Owner registration and login
|
|
49
|
+
// -------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Register a new owner and receive their EC P-256 keypair.
|
|
53
|
+
* Store private_key_jwk securely - it is not kept by the server.
|
|
54
|
+
*/
|
|
55
|
+
async registerOwner({ email, org }) {
|
|
56
|
+
return this.#post("/owners/register", { email, org });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Verify owner email with the OTP received after registration.
|
|
61
|
+
*/
|
|
62
|
+
async verifyEmail({ owner_key_id, otp }) {
|
|
63
|
+
return this.#post("/owners/verify-email", { owner_key_id, otp });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Request a login OTP for an existing verified owner.
|
|
68
|
+
*/
|
|
69
|
+
async loginOwner({ email }) {
|
|
70
|
+
return this.#post("/owners/login", { email });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Verify a login OTP and receive a session token for session-based registration.
|
|
75
|
+
*/
|
|
76
|
+
async verifyLogin({ owner_key_id, otp }) {
|
|
77
|
+
return this.#post("/owners/verify-login", { owner_key_id, otp });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// -------------------------------------------------------------------------
|
|
81
|
+
// Agent registration
|
|
82
|
+
// -------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Register an agent and receive a signed v1 credential.
|
|
86
|
+
*
|
|
87
|
+
* The SDK handles signing the payload with the owner's private key automatically.
|
|
88
|
+
*/
|
|
89
|
+
async registerAgent({ owner_key_id, private_key_jwk, payload }) {
|
|
90
|
+
const owner_signature = await signPayload(payload, private_key_jwk);
|
|
91
|
+
return this.#post("/agents/register", { owner_key_id, payload, owner_signature });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Register an agent using a session token from verifyLogin.
|
|
96
|
+
*/
|
|
97
|
+
async registerAgentWithSession({ session_token, payload }) {
|
|
98
|
+
return this.#post("/agents/register-session", { session_token, payload });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// -------------------------------------------------------------------------
|
|
102
|
+
// Revocation
|
|
103
|
+
// -------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Revoke an agent credential.
|
|
107
|
+
* The SDK handles signing the revoke payload automatically.
|
|
108
|
+
*/
|
|
109
|
+
async revokeAgent({ ail_id, owner_key_id, private_key_jwk }) {
|
|
110
|
+
const revokePayload = { action: "revoke", ail_id };
|
|
111
|
+
const owner_signature = await signPayload(revokePayload, private_key_jwk);
|
|
112
|
+
const res = await fetch(`${this.serverUrl}/agents/${ail_id}/revoke`, {
|
|
113
|
+
method: "DELETE",
|
|
114
|
+
headers: { "Content-Type": "application/json" },
|
|
115
|
+
body: JSON.stringify({ owner_key_id, owner_signature }),
|
|
116
|
+
});
|
|
117
|
+
const data = await res.json();
|
|
118
|
+
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
119
|
+
return data;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// -------------------------------------------------------------------------
|
|
123
|
+
// Verification
|
|
124
|
+
// -------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Verify a credential online (calls the server).
|
|
128
|
+
*/
|
|
129
|
+
async verify(token) {
|
|
130
|
+
return this.#post("/verify", { token });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Verify a credential offline using the server's public key.
|
|
135
|
+
* Fetches the JWKS once and caches it.
|
|
136
|
+
*/
|
|
137
|
+
async verifyOffline(token) {
|
|
138
|
+
if (!this._cachedJwks) {
|
|
139
|
+
this._cachedJwks = await this.#get("/keys");
|
|
140
|
+
}
|
|
141
|
+
return verifyOffline(token, this._cachedJwks.keys[0]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Fetch the Agent ID Card JWKS (public keys for offline verification).
|
|
146
|
+
*/
|
|
147
|
+
async getPublicKeys() {
|
|
148
|
+
return this.#get("/keys");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// -------------------------------------------------------------------------
|
|
152
|
+
// Reputation and achievements
|
|
153
|
+
// -------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
async getReputation(ailId) {
|
|
156
|
+
return this.#get(`/reputation/${encodeURIComponent(ailId)}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async getReputationHistory(ailId, params = {}) {
|
|
160
|
+
return this.#get(this.#withQuery(`/reputation/${encodeURIComponent(ailId)}/history`, params));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async compareAgents(ailId, otherAilId) {
|
|
164
|
+
return this.#get(this.#withQuery(`/reputation/${encodeURIComponent(ailId)}/compare`, { with: otherAilId }));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getLeaderboard(params = {}) {
|
|
168
|
+
return this.#get(this.#withQuery("/reputation/leaderboard", params));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async getBadges(ailId) {
|
|
172
|
+
return this.#get(`/reputation/${encodeURIComponent(ailId)}/badges`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async getSeasonReport(ailId, season, params = {}) {
|
|
176
|
+
return this.#get(
|
|
177
|
+
this.#withQuery(`/reputation/${encodeURIComponent(ailId)}/season/${season}`, params)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async awardBadge({ source_name, agent_id, badge_id, private_key_jwk, merkle_proof = null }) {
|
|
182
|
+
const payload = { source_name, agent_id, badge_id, merkle_proof };
|
|
183
|
+
const signature = await signPayload(payload, private_key_jwk);
|
|
184
|
+
return this.#post("/reputation/badge", { ...payload, signature });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Standalone offline verification (no server required)
|
|
190
|
+
// Provide the public key JWK from GET /keys
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
export async function verifyOffline(token, publicKeyJwk) {
|
|
193
|
+
try {
|
|
194
|
+
const publicKey = await importJWK(publicKeyJwk, "ES256");
|
|
195
|
+
const { payload } = await jwtVerify(token, publicKey, {
|
|
196
|
+
issuer: ["agentidcard.org", "22blabs.ai"],
|
|
197
|
+
algorithms: ["ES256"],
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
valid: true,
|
|
201
|
+
ail_id: payload.ail_id,
|
|
202
|
+
display_name: payload.display_name,
|
|
203
|
+
role: payload.role,
|
|
204
|
+
owner_org: payload.owner_org,
|
|
205
|
+
issued: new Date(payload.iat * 1000).toISOString(),
|
|
206
|
+
expires: new Date(payload.exp * 1000).toISOString(),
|
|
207
|
+
};
|
|
208
|
+
} catch (err) {
|
|
209
|
+
return { valid: false, reason: err.message };
|
|
210
|
+
}
|
|
211
|
+
}
|
package/src/crypto.mjs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto utilities for the AIL JS SDK.
|
|
3
|
+
* Uses the WebCrypto API (globalThis.crypto.subtle) — works in Node.js 18+
|
|
4
|
+
* and all modern browsers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Canonical JSON
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
export function canonicalJson(obj) {
|
|
13
|
+
return JSON.stringify(obj, (key, value) => {
|
|
14
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
15
|
+
return Object.fromEntries(
|
|
16
|
+
Object.entries(value).sort(([a], [b]) => a.localeCompare(b))
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// SHA-256 hex
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
export function sha256hex(data) {
|
|
27
|
+
// Works in Node.js; in browsers, use SubtleCrypto instead
|
|
28
|
+
try {
|
|
29
|
+
return createHash("sha256").update(data).digest("hex");
|
|
30
|
+
} catch {
|
|
31
|
+
// Browser fallback (sync not available — caller should use sha256hexAsync)
|
|
32
|
+
throw new Error("Use sha256hexAsync in browser environments.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function sha256hexAsync(data) {
|
|
37
|
+
const buf = new TextEncoder().encode(data);
|
|
38
|
+
const hash = await globalThis.crypto.subtle.digest("SHA-256", buf);
|
|
39
|
+
return Array.from(new Uint8Array(hash))
|
|
40
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
41
|
+
.join("");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Owner keypair — EC P-256
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
export async function generateOwnerKeypair() {
|
|
48
|
+
const { privateKey, publicKey } = await globalThis.crypto.subtle.generateKey(
|
|
49
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
50
|
+
true,
|
|
51
|
+
["sign", "verify"]
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const private_key_jwk = await globalThis.crypto.subtle.exportKey("jwk", privateKey);
|
|
55
|
+
const public_key_jwk = await globalThis.crypto.subtle.exportKey("jwk", publicKey);
|
|
56
|
+
|
|
57
|
+
// owner_key_id is assigned by the server; return a local placeholder
|
|
58
|
+
return { public_key_jwk, private_key_jwk };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Sign payload (owner signs registration or revoke payload)
|
|
63
|
+
// Returns base64url IEEE P1363 signature (64 bytes for P-256)
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
export async function signPayload(payload, privateKeyJwk) {
|
|
66
|
+
const key = await globalThis.crypto.subtle.importKey(
|
|
67
|
+
"jwk",
|
|
68
|
+
privateKeyJwk,
|
|
69
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
70
|
+
false,
|
|
71
|
+
["sign"]
|
|
72
|
+
);
|
|
73
|
+
const data = new TextEncoder().encode(canonicalJson(payload));
|
|
74
|
+
const sig = await globalThis.crypto.subtle.sign(
|
|
75
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
76
|
+
key,
|
|
77
|
+
data
|
|
78
|
+
);
|
|
79
|
+
return Buffer.from(sig).toString("base64url");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Verify owner signature
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
export async function verifyOwnerSignature(payload, signatureB64url, publicKeyJwk) {
|
|
86
|
+
try {
|
|
87
|
+
const key = await globalThis.crypto.subtle.importKey(
|
|
88
|
+
"jwk",
|
|
89
|
+
publicKeyJwk,
|
|
90
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
91
|
+
false,
|
|
92
|
+
["verify"]
|
|
93
|
+
);
|
|
94
|
+
const data = new TextEncoder().encode(canonicalJson(payload));
|
|
95
|
+
const sig = Buffer.from(signatureB64url, "base64url");
|
|
96
|
+
return await globalThis.crypto.subtle.verify(
|
|
97
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
98
|
+
key,
|
|
99
|
+
sig,
|
|
100
|
+
data
|
|
101
|
+
);
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/envelope.mjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buildEnvelope — assembles a complete v1 AIL envelope from a registered credential.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* const envelope = buildEnvelope({
|
|
6
|
+
* ail_id, credential, signal_glyph, behavior_fingerprint,
|
|
7
|
+
* agent: { id, provider, model, runtime },
|
|
8
|
+
* owner: { key_id, org, email_hash },
|
|
9
|
+
* scope,
|
|
10
|
+
* delegation: { mode, delegated_by, approved_by, chain_depth, task_ref },
|
|
11
|
+
* runtime: { session_id, run_id, surface, host, cwd },
|
|
12
|
+
* })
|
|
13
|
+
*/
|
|
14
|
+
export function buildEnvelope({
|
|
15
|
+
ail_id,
|
|
16
|
+
credential,
|
|
17
|
+
signal_glyph,
|
|
18
|
+
behavior_fingerprint,
|
|
19
|
+
agent,
|
|
20
|
+
owner = null,
|
|
21
|
+
scope,
|
|
22
|
+
delegation,
|
|
23
|
+
runtime,
|
|
24
|
+
extensions = {},
|
|
25
|
+
}) {
|
|
26
|
+
const now = new Date().toISOString();
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
version: "ail.v1",
|
|
30
|
+
ail_id: ail_id ?? null,
|
|
31
|
+
|
|
32
|
+
credential: credential ?? null,
|
|
33
|
+
|
|
34
|
+
agent: {
|
|
35
|
+
id: agent.id,
|
|
36
|
+
display_name: agent.display_name ?? credential?.display_name,
|
|
37
|
+
role: agent.role ?? credential?.role,
|
|
38
|
+
provider: agent.provider ?? null,
|
|
39
|
+
model: agent.model ?? null,
|
|
40
|
+
runtime: agent.runtime ?? null,
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
owner: owner
|
|
44
|
+
? {
|
|
45
|
+
key_id: owner.key_id,
|
|
46
|
+
org: owner.org ?? null,
|
|
47
|
+
email_hash: owner.email_hash ?? null,
|
|
48
|
+
}
|
|
49
|
+
: null,
|
|
50
|
+
|
|
51
|
+
scope: scope ?? {
|
|
52
|
+
workspace: [],
|
|
53
|
+
repos: [],
|
|
54
|
+
network: "restricted",
|
|
55
|
+
secrets: "none",
|
|
56
|
+
write_access: false,
|
|
57
|
+
approval_policy: {
|
|
58
|
+
irreversible_actions: "human_required",
|
|
59
|
+
external_posting: "human_required",
|
|
60
|
+
destructive_file_ops: "human_required",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
signal_glyph: signal_glyph ?? null,
|
|
65
|
+
behavior_fingerprint: behavior_fingerprint ?? null,
|
|
66
|
+
|
|
67
|
+
delegation: {
|
|
68
|
+
mode: delegation?.mode ?? "direct",
|
|
69
|
+
delegated_by: delegation?.delegated_by ?? null,
|
|
70
|
+
approved_by: delegation?.approved_by ?? null,
|
|
71
|
+
chain_depth: delegation?.chain_depth ?? 0,
|
|
72
|
+
task_ref: delegation?.task_ref ?? null,
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
runtime: {
|
|
76
|
+
session_id: runtime?.session_id ?? null,
|
|
77
|
+
run_id: runtime?.run_id ?? null,
|
|
78
|
+
surface: runtime?.surface ?? null,
|
|
79
|
+
host: runtime?.host ?? null,
|
|
80
|
+
cwd: runtime?.cwd ?? null,
|
|
81
|
+
time: runtime?.time ?? now,
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
verification: ail_id && credential
|
|
85
|
+
? {
|
|
86
|
+
strength: "cryptographically_signed",
|
|
87
|
+
issuer: credential.issuer,
|
|
88
|
+
issuer_key_id: credential.issuer_key_id,
|
|
89
|
+
token_type: "JWT",
|
|
90
|
+
signed: true,
|
|
91
|
+
verify_url: `${getIssuerBase(credential.issuer)}/verify`,
|
|
92
|
+
evidence: ["22blabs_registry", "owner_key_delegation", "jwt_signature"],
|
|
93
|
+
attestation_ref: `ail_id:${ail_id}`,
|
|
94
|
+
}
|
|
95
|
+
: {
|
|
96
|
+
strength: "local_runtime_asserted",
|
|
97
|
+
signed: false,
|
|
98
|
+
evidence: ["runtime_session_binding"],
|
|
99
|
+
attestation_ref: null,
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
extensions,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getIssuerBase(issuer) {
|
|
107
|
+
if (!issuer.startsWith("http")) return `https://${issuer}/api`;
|
|
108
|
+
return issuer;
|
|
109
|
+
}
|