@aamp/protocol 1.1.2 → 1.1.4

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/dist/types.d.ts CHANGED
@@ -21,31 +21,37 @@ export declare enum QualityFlag {
21
21
  HIGH_QUALITY = "HIGH_QUALITY"
22
22
  }
23
23
  /**
24
- * Optional Rate Limiting (The Speed Limit)
25
- * Defines technical boundaries for the handshake.
24
+ * DNS Identity Manifest
25
+ * Hosted at: https://{agent_id}/.well-known/aamp-agent.json
26
26
  */
27
- export interface RateLimitConfig {
28
- requestsPerMinute: number;
29
- tokensPerMinute?: number;
27
+ export interface AgentIdentityManifest {
28
+ agent_id: string;
29
+ public_key: string;
30
+ contact_email?: string;
31
+ }
32
+ /**
33
+ * PRODUCTION INFRASTRUCTURE: Cache Interface
34
+ * Required for Serverless/Edge environments to prevent repeated DNS fetches.
35
+ */
36
+ export interface IdentityCache {
37
+ get(key: string): Promise<string | null>;
38
+ set(key: string, value: string, ttlSeconds: number): Promise<void>;
30
39
  }
31
40
  /**
32
41
  * Optional Monetization (The Settlement Layer)
33
- *
34
- * AAMP is neutral. Settlement can happen via:
35
- * 1. BROKER: A 3rd party clearing house (e.g., "AI-AdSense").
36
- * 2. CRYPTO: Direct on-chain settlement.
37
- * 3. TREATY: A private legal contract signed offline (Enterprise).
38
42
  */
39
43
  export interface MonetizationConfig {
40
44
  method: 'BROKER' | 'CRYPTO' | 'PRIVATE_TREATY';
41
- /**
42
- * The destination for settlement.
43
- * - If BROKER: The API URL of the clearing house.
44
- * - If CRYPTO: The wallet address.
45
- * - If TREATY: The Contract ID or "Contact Sales".
46
- */
47
45
  location: string;
48
46
  }
47
+ /**
48
+ * Handling Non-AAMP Visitors
49
+ *
50
+ * PASSIVE: Allow everyone (Legacy web behavior).
51
+ * HYBRID: Allow verified Agents AND likely Humans (Browser Heuristics). Block bots.
52
+ * STRICT: Allow ONLY verified AAMP Agents. (API Mode).
53
+ */
54
+ export type UnauthenticatedStrategy = 'PASSIVE' | 'HYBRID' | 'STRICT';
49
55
  export interface AccessPolicy {
50
56
  version: '1.1';
51
57
  allowTraining: boolean;
@@ -54,7 +60,7 @@ export interface AccessPolicy {
54
60
  allowAdSupportedAccess: boolean;
55
61
  requiresPayment: boolean;
56
62
  paymentPointer?: string;
57
- rateLimit?: RateLimitConfig;
63
+ requireIdentityBinding?: boolean;
58
64
  monetization?: MonetizationConfig;
59
65
  }
60
66
  export interface ProtocolHeader {
@@ -79,3 +85,10 @@ export interface FeedbackSignal {
79
85
  flags: QualityFlag[];
80
86
  timestamp: string;
81
87
  }
88
+ export interface EvaluationResult {
89
+ allowed: boolean;
90
+ status: 200 | 400 | 401 | 403;
91
+ reason: string;
92
+ visitorType: 'VERIFIED_AGENT' | 'LIKELY_HUMAN' | 'UNIDENTIFIED_BOT';
93
+ metadata?: any;
94
+ }
package/package.json CHANGED
@@ -1,24 +1,24 @@
1
- {
2
- "name": "@aamp/protocol",
3
- "version": "1.1.2",
4
- "description": "TypeScript reference implementation of AAMP",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsc",
9
- "test": "ts-node test/handshake.spec.ts",
10
- "prepublishOnly": "npm run test && npm run build"
11
- },
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/aamp-protocol/aamp.git"
15
- },
16
- "publishConfig": {
17
- "access": "public"
18
- },
19
- "devDependencies": {
20
- "typescript": "^5.0.0",
21
- "ts-node": "^10.9.0",
22
- "@types/node": "^20.0.0"
23
- }
1
+ {
2
+ "name": "@aamp/protocol",
3
+ "version": "1.1.4",
4
+ "description": "TypeScript reference implementation of AAMP v1.1",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "ts-node test/handshake.spec.ts",
10
+ "prepublishOnly": "npm run test && npm run build"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/aamp-protocol/aamp.git"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "devDependencies": {
20
+ "typescript": "^5.0.0",
21
+ "ts-node": "^10.9.0",
22
+ "@types/node": "^20.0.0"
23
+ }
24
24
  }
package/src/agent.ts CHANGED
@@ -1,72 +1,88 @@
1
- /**
2
- * Layer 2: Agent SDK
3
- */
4
- import { AccessPurpose, ProtocolHeader, SignedAccessRequest, FeedbackSignal, QualityFlag } from './types';
5
- import { generateKeyPair, signData, exportPublicKey } from './crypto';
6
- import { AAMP_VERSION } from './constants';
7
-
8
- export interface AccessOptions {
9
- adsDisplayed?: boolean;
10
- }
11
-
12
- export class AAMPAgent {
13
- private keyPair: CryptoKeyPair | null = null;
14
- public agentId: string = "pending";
15
-
16
- /**
17
- * Initialize the Agent Identity (Ephemeral or Persisted)
18
- * @param customAgentId Optional persistent ID for this agent. If omitted, generates a session ID.
19
- */
20
- async initialize(customAgentId?: string) {
21
- this.keyPair = await generateKeyPair();
22
- // Use the provided ID (authentic) or generate a session ID (ephemeral)
23
- this.agentId = customAgentId || "agent_" + Math.random().toString(36).substring(7);
24
- }
25
-
26
- async createAccessRequest(
27
- resource: string,
28
- purpose: AccessPurpose,
29
- options: AccessOptions = {}
30
- ): Promise<SignedAccessRequest> {
31
- if (!this.keyPair) throw new Error("Agent not initialized. Call initialize() first.");
32
-
33
- const header: ProtocolHeader = {
34
- v: AAMP_VERSION,
35
- ts: new Date().toISOString(),
36
- agent_id: this.agentId,
37
- resource,
38
- purpose,
39
- context: {
40
- ads_displayed: options.adsDisplayed || false
41
- }
42
- };
43
-
44
- const signature = await signData(this.keyPair.privateKey, JSON.stringify(header));
45
- const publicKeyExport = await exportPublicKey(this.keyPair.publicKey);
46
-
47
- return { header, signature, publicKey: publicKeyExport };
48
- }
49
-
50
- /**
51
- * NEW IN V1.1: Quality Feedback Loop
52
- * Allows the Agent to report spam or verify quality of a resource.
53
- */
54
- async generateFeedback(
55
- resource: string,
56
- score: number,
57
- flags: QualityFlag[]
58
- ): Promise<{ signal: FeedbackSignal, signature: string }> {
59
- if (!this.keyPair) throw new Error("Agent not initialized.");
60
-
61
- const signal: FeedbackSignal = {
62
- target_resource: resource,
63
- agent_id: this.agentId,
64
- quality_score: Math.max(0, Math.min(1, score)),
65
- flags,
66
- timestamp: new Date().toISOString()
67
- };
68
-
69
- const signature = await signData(this.keyPair.privateKey, JSON.stringify(signal));
70
- return { signal, signature };
71
- }
1
+ /**
2
+ * Layer 2: Agent SDK
3
+ */
4
+ import { AccessPurpose, ProtocolHeader, SignedAccessRequest, FeedbackSignal, QualityFlag, AgentIdentityManifest } from './types';
5
+ import { generateKeyPair, signData, exportPublicKey } from './crypto';
6
+ import { AAMP_VERSION } from './constants';
7
+
8
+ export interface AccessOptions {
9
+ adsDisplayed?: boolean;
10
+ }
11
+
12
+ export class AAMPAgent {
13
+ private keyPair: CryptoKeyPair | null = null;
14
+ public agentId: string = "pending";
15
+
16
+ /**
17
+ * Initialize the Agent Identity (Ephemeral or Persisted)
18
+ * @param customAgentId For PRODUCTION, this should be your domain (e.g., "bot.openai.com")
19
+ */
20
+ async initialize(customAgentId?: string) {
21
+ this.keyPair = await generateKeyPair();
22
+ // Use the provided ID (authentic) or generate a session ID (ephemeral)
23
+ this.agentId = customAgentId || "agent_" + Math.random().toString(36).substring(7);
24
+ }
25
+
26
+ async createAccessRequest(
27
+ resource: string,
28
+ purpose: AccessPurpose,
29
+ options: AccessOptions = {}
30
+ ): Promise<SignedAccessRequest> {
31
+ if (!this.keyPair) throw new Error("Agent not initialized. Call initialize() first.");
32
+
33
+ const header: ProtocolHeader = {
34
+ v: AAMP_VERSION,
35
+ ts: new Date().toISOString(),
36
+ agent_id: this.agentId,
37
+ resource,
38
+ purpose,
39
+ context: {
40
+ ads_displayed: options.adsDisplayed || false
41
+ }
42
+ };
43
+
44
+ const signature = await signData(this.keyPair.privateKey, JSON.stringify(header));
45
+ const publicKeyExport = await exportPublicKey(this.keyPair.publicKey);
46
+
47
+ return { header, signature, publicKey: publicKeyExport };
48
+ }
49
+
50
+ /**
51
+ * Helper: Generate the JSON file you must host on your domain
52
+ * Host this at: https://{agentId}/.well-known/aamp-agent.json
53
+ */
54
+ async getIdentityManifest(contactEmail?: string): Promise<AgentIdentityManifest> {
55
+ if (!this.keyPair) throw new Error("Agent not initialized.");
56
+
57
+ const publicKey = await exportPublicKey(this.keyPair.publicKey);
58
+
59
+ return {
60
+ agent_id: this.agentId,
61
+ public_key: publicKey,
62
+ contact_email: contactEmail
63
+ };
64
+ }
65
+
66
+ /**
67
+ * NEW IN V1.1: Quality Feedback Loop
68
+ * Allows the Agent to report spam or verify quality of a resource.
69
+ */
70
+ async generateFeedback(
71
+ resource: string,
72
+ score: number,
73
+ flags: QualityFlag[]
74
+ ): Promise<{ signal: FeedbackSignal, signature: string }> {
75
+ if (!this.keyPair) throw new Error("Agent not initialized.");
76
+
77
+ const signal: FeedbackSignal = {
78
+ target_resource: resource,
79
+ agent_id: this.agentId,
80
+ quality_score: Math.max(0, Math.min(1, score)),
81
+ flags,
82
+ timestamp: new Date().toISOString()
83
+ };
84
+
85
+ const signature = await signData(this.keyPair.privateKey, JSON.stringify(signal));
86
+ return { signal, signature };
87
+ }
72
88
  }
package/src/constants.ts CHANGED
@@ -1,35 +1,39 @@
1
- /**
2
- * Layer 1: Protocol Constants
3
- * These values are immutable and defined by the AAMP Specification.
4
- */
5
-
6
- export const AAMP_VERSION = '1.1';
7
-
8
- // HTTP Headers used for the handshake
9
- export const HEADERS = {
10
- // Transport: The signed payload (Base64 encoded JSON of ProtocolHeader)
11
- PAYLOAD: 'x-aamp-payload',
12
- // Transport: The cryptographic signature (Hex)
13
- SIGNATURE: 'x-aamp-signature',
14
- // Transport: The Agent's Public Key (Base64 SPKI)
15
- PUBLIC_KEY: 'x-aamp-public-key',
16
-
17
- // Informational / Legacy (Optional if Payload is present)
18
- AGENT_ID: 'x-aamp-agent-id',
19
- TIMESTAMP: 'x-aamp-timestamp',
20
- ALGORITHM: 'x-aamp-alg',
21
-
22
- // v1.1 Addition: Provenance (Server to Agent)
23
- CONTENT_ORIGIN: 'x-aamp-content-origin',
24
- PROVENANCE_SIG: 'x-aamp-provenance-sig'
25
- } as const;
26
-
27
- // Cryptographic Settings
28
- export const CRYPTO_CONFIG = {
29
- ALGORITHM_NAME: 'ECDSA',
30
- CURVE: 'P-256',
31
- HASH: 'SHA-256',
32
- } as const;
33
-
34
- // Tolerance
1
+ /**
2
+ * Layer 1: Protocol Constants
3
+ * These values are immutable and defined by the AAMP Specification.
4
+ */
5
+
6
+ export const AAMP_VERSION = '1.1';
7
+
8
+ // The path where Agents MUST host their public key to prove identity.
9
+ // Example: https://bot.openai.com/.well-known/aamp-agent.json
10
+ export const WELL_KNOWN_AGENT_PATH = '/.well-known/aamp-agent.json';
11
+
12
+ // HTTP Headers used for the handshake
13
+ export const HEADERS = {
14
+ // Transport: The signed payload (Base64 encoded JSON of ProtocolHeader)
15
+ PAYLOAD: 'x-aamp-payload',
16
+ // Transport: The cryptographic signature (Hex)
17
+ SIGNATURE: 'x-aamp-signature',
18
+ // Transport: The Agent's Public Key (Base64 SPKI)
19
+ PUBLIC_KEY: 'x-aamp-public-key',
20
+
21
+ // Informational / Legacy (Optional if Payload is present)
22
+ AGENT_ID: 'x-aamp-agent-id',
23
+ TIMESTAMP: 'x-aamp-timestamp',
24
+ ALGORITHM: 'x-aamp-alg',
25
+
26
+ // v1.1 Addition: Provenance (Server to Agent)
27
+ CONTENT_ORIGIN: 'x-aamp-content-origin',
28
+ PROVENANCE_SIG: 'x-aamp-provenance-sig'
29
+ } as const;
30
+
31
+ // Cryptographic Settings
32
+ export const CRYPTO_CONFIG = {
33
+ ALGORITHM_NAME: 'ECDSA',
34
+ CURVE: 'P-256',
35
+ HASH: 'SHA-256',
36
+ } as const;
37
+
38
+ // Tolerance
35
39
  export const MAX_CLOCK_SKEW_MS = 5 * 60 * 1000; // 5 minutes
package/src/crypto.ts CHANGED
@@ -1,69 +1,69 @@
1
- /**
2
- * Layer 1: Cryptographic Primitives
3
- * Implementation of ECDSA P-256 signing/verification.
4
- */
5
-
6
- export async function generateKeyPair(): Promise<CryptoKeyPair> {
7
- // Uses standard Web Crypto API (Node 19+ compatible)
8
- return await crypto.subtle.generateKey(
9
- { name: "ECDSA", namedCurve: "P-256" },
10
- true,
11
- ["sign", "verify"]
12
- );
13
- }
14
-
15
- export async function signData(privateKey: CryptoKey, data: string): Promise<string> {
16
- const encoder = new TextEncoder();
17
- const encoded = encoder.encode(data);
18
- const signature = await crypto.subtle.sign(
19
- { name: "ECDSA", hash: { name: "SHA-256" } },
20
- privateKey,
21
- encoded as any
22
- );
23
- return bufToHex(signature);
24
- }
25
-
26
- export async function verifySignature(publicKey: CryptoKey, data: string, signatureHex: string): Promise<boolean> {
27
- const encoder = new TextEncoder();
28
- const encodedData = encoder.encode(data);
29
- const signatureBytes = hexToBuf(signatureHex);
30
-
31
- return await crypto.subtle.verify(
32
- { name: "ECDSA", hash: { name: "SHA-256" } },
33
- publicKey,
34
- signatureBytes as any,
35
- encodedData as any
36
- );
37
- }
38
-
39
- export async function exportPublicKey(key: CryptoKey): Promise<string> {
40
- const exported = await crypto.subtle.exportKey("spki", key);
41
- return btoa(String.fromCharCode(...new Uint8Array(exported)));
42
- }
43
-
44
- export async function importPublicKey(keyData: string): Promise<CryptoKey> {
45
- const binaryString = atob(keyData);
46
- const bytes = new Uint8Array(binaryString.length);
47
- for (let i = 0; i < binaryString.length; i++) {
48
- bytes[i] = binaryString.charCodeAt(i);
49
- }
50
-
51
- return await crypto.subtle.importKey(
52
- "spki",
53
- bytes,
54
- { name: "ECDSA", namedCurve: "P-256" },
55
- true,
56
- ["verify"]
57
- );
58
- }
59
-
60
- // Helpers
61
- function bufToHex(buffer: ArrayBuffer): string {
62
- return Array.from(new Uint8Array(buffer))
63
- .map(b => b.toString(16).padStart(2, '0'))
64
- .join('');
65
- }
66
-
67
- function hexToBuf(hex: string): Uint8Array {
68
- return new Uint8Array(hex.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)));
1
+ /**
2
+ * Layer 1: Cryptographic Primitives
3
+ * Implementation of ECDSA P-256 signing/verification.
4
+ */
5
+
6
+ export async function generateKeyPair(): Promise<CryptoKeyPair> {
7
+ // Uses standard Web Crypto API (Node 19+ compatible)
8
+ return await crypto.subtle.generateKey(
9
+ { name: "ECDSA", namedCurve: "P-256" },
10
+ true,
11
+ ["sign", "verify"]
12
+ );
13
+ }
14
+
15
+ export async function signData(privateKey: CryptoKey, data: string): Promise<string> {
16
+ const encoder = new TextEncoder();
17
+ const encoded = encoder.encode(data);
18
+ const signature = await crypto.subtle.sign(
19
+ { name: "ECDSA", hash: { name: "SHA-256" } },
20
+ privateKey,
21
+ encoded as any
22
+ );
23
+ return bufToHex(signature);
24
+ }
25
+
26
+ export async function verifySignature(publicKey: CryptoKey, data: string, signatureHex: string): Promise<boolean> {
27
+ const encoder = new TextEncoder();
28
+ const encodedData = encoder.encode(data);
29
+ const signatureBytes = hexToBuf(signatureHex);
30
+
31
+ return await crypto.subtle.verify(
32
+ { name: "ECDSA", hash: { name: "SHA-256" } },
33
+ publicKey,
34
+ signatureBytes as any,
35
+ encodedData as any
36
+ );
37
+ }
38
+
39
+ export async function exportPublicKey(key: CryptoKey): Promise<string> {
40
+ const exported = await crypto.subtle.exportKey("spki", key);
41
+ return btoa(String.fromCharCode(...new Uint8Array(exported)));
42
+ }
43
+
44
+ export async function importPublicKey(keyData: string): Promise<CryptoKey> {
45
+ const binaryString = atob(keyData);
46
+ const bytes = new Uint8Array(binaryString.length);
47
+ for (let i = 0; i < binaryString.length; i++) {
48
+ bytes[i] = binaryString.charCodeAt(i);
49
+ }
50
+
51
+ return await crypto.subtle.importKey(
52
+ "spki",
53
+ bytes,
54
+ { name: "ECDSA", namedCurve: "P-256" },
55
+ true,
56
+ ["verify"]
57
+ );
58
+ }
59
+
60
+ // Helpers
61
+ function bufToHex(buffer: ArrayBuffer): string {
62
+ return Array.from(new Uint8Array(buffer))
63
+ .map(b => b.toString(16).padStart(2, '0'))
64
+ .join('');
65
+ }
66
+
67
+ function hexToBuf(hex: string): Uint8Array {
68
+ return new Uint8Array(hex.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)));
69
69
  }