aap-agent-client 2.7.0 → 3.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/index.js +121 -189
- package/package.json +4 -6
- package/prover.js +0 -391
- package/websocket.js +0 -162
package/index.js
CHANGED
|
@@ -1,214 +1,146 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @aap/client
|
|
2
|
+
* @aap/client v3.0.0
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* WebSocket client for Agent Attestation Protocol.
|
|
5
|
+
* Connect, solve sequential challenges, prove your intelligence.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Fetch with timeout helper
|
|
12
|
-
async function fetchWithTimeout(url, options = {}, timeoutMs = 30000) {
|
|
13
|
-
const controller = new AbortController();
|
|
14
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const response = await fetch(url, {
|
|
18
|
-
...options,
|
|
19
|
-
signal: controller.signal
|
|
20
|
-
});
|
|
21
|
-
return response;
|
|
22
|
-
} finally {
|
|
23
|
-
clearTimeout(timeout);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
8
|
+
import WebSocket from 'ws';
|
|
9
|
+
|
|
10
|
+
export const PROTOCOL_VERSION = '3.0.0';
|
|
26
11
|
|
|
27
12
|
/**
|
|
28
|
-
* AAP Client
|
|
13
|
+
* AAP WebSocket Client
|
|
29
14
|
*/
|
|
30
15
|
export class AAPClient {
|
|
31
|
-
/**
|
|
32
|
-
* @param {Object} [options]
|
|
33
|
-
* @param {string} [options.serverUrl] - Default verification server URL
|
|
34
|
-
* @param {string} [options.storagePath] - Identity storage path
|
|
35
|
-
* @param {Function} [options.llmCallback] - Default LLM callback for solutions
|
|
36
|
-
*/
|
|
37
16
|
constructor(options = {}) {
|
|
38
|
-
this.serverUrl = options.serverUrl
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
17
|
+
this.serverUrl = options.serverUrl || 'ws://localhost:3000/aap';
|
|
18
|
+
this.publicId = options.publicId || null;
|
|
19
|
+
this.solver = options.solver || null;
|
|
41
20
|
}
|
|
42
21
|
|
|
43
22
|
/**
|
|
44
|
-
*
|
|
45
|
-
* @
|
|
46
|
-
*/
|
|
47
|
-
getIdentity() {
|
|
48
|
-
return this.prover.getIdentity();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Perform full verification against a server
|
|
53
|
-
*
|
|
54
|
-
* @param {string} [serverUrl] - Override default server URL
|
|
55
|
-
* @param {Function|string} [solutionOrCallback] - Solution or LLM callback
|
|
23
|
+
* Connect and verify
|
|
24
|
+
* @param {Function} [solver] - async (challengeString, challengeId) => answerObject
|
|
56
25
|
* @returns {Promise<Object>} Verification result
|
|
57
26
|
*/
|
|
58
|
-
async verify(
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return { healthy: false, error: `HTTP ${res.status}` };
|
|
132
|
-
}
|
|
133
|
-
return { healthy: true, ...(await res.json()) };
|
|
134
|
-
} catch (error) {
|
|
135
|
-
return { healthy: false, error: error.message };
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Get a challenge without verifying (for manual flow)
|
|
141
|
-
* @param {string} [serverUrl] - Override default server URL
|
|
142
|
-
* @returns {Promise<Object>}
|
|
143
|
-
*/
|
|
144
|
-
async getChallenge(serverUrl) {
|
|
145
|
-
const baseUrl = (serverUrl || this.serverUrl)?.replace(/\/$/, '');
|
|
146
|
-
if (!baseUrl) {
|
|
147
|
-
throw new Error('Server URL is required');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const res = await fetch(`${baseUrl}/challenge`, {
|
|
151
|
-
method: 'POST',
|
|
152
|
-
headers: { 'Content-Type': 'application/json' }
|
|
27
|
+
async verify(solver) {
|
|
28
|
+
const solve = solver || this.solver;
|
|
29
|
+
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const ws = new WebSocket(this.serverUrl);
|
|
32
|
+
let result = null;
|
|
33
|
+
|
|
34
|
+
ws.on('open', () => {
|
|
35
|
+
// Wait for handshake
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
ws.on('message', async (data) => {
|
|
39
|
+
try {
|
|
40
|
+
const msg = JSON.parse(data.toString());
|
|
41
|
+
|
|
42
|
+
switch (msg.type) {
|
|
43
|
+
case 'handshake':
|
|
44
|
+
// Send ready
|
|
45
|
+
ws.send(JSON.stringify({
|
|
46
|
+
type: 'ready',
|
|
47
|
+
publicId: this.publicId
|
|
48
|
+
}));
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
case 'challenge':
|
|
52
|
+
if (!solve) {
|
|
53
|
+
// No solver - send empty answer (will fail)
|
|
54
|
+
ws.send(JSON.stringify({ type: 'answer', answer: {} }));
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const answer = await solve(msg.challenge, msg.id);
|
|
60
|
+
ws.send(JSON.stringify({ type: 'answer', answer }));
|
|
61
|
+
} catch (e) {
|
|
62
|
+
ws.send(JSON.stringify({ type: 'answer', answer: { error: e.message } }));
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
case 'ack':
|
|
67
|
+
// Challenge acknowledged, waiting for next
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
case 'timeout':
|
|
71
|
+
// Too slow
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case 'result':
|
|
75
|
+
result = msg;
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case 'error':
|
|
79
|
+
reject(new Error(msg.message || 'Unknown error'));
|
|
80
|
+
ws.close();
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
reject(e);
|
|
85
|
+
ws.close();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
ws.on('close', () => {
|
|
90
|
+
if (result) {
|
|
91
|
+
resolve(result);
|
|
92
|
+
} else {
|
|
93
|
+
reject(new Error('Connection closed without result'));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
ws.on('error', (err) => {
|
|
98
|
+
reject(err);
|
|
99
|
+
});
|
|
153
100
|
});
|
|
154
|
-
|
|
155
|
-
if (!res.ok) {
|
|
156
|
-
throw new Error(`Failed to get challenge: ${res.status}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return res.json();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Generate a proof for a challenge (for manual flow)
|
|
164
|
-
* @param {Object} challenge - Challenge from getChallenge()
|
|
165
|
-
* @param {Function|string} [solutionOrCallback] - Solution or callback
|
|
166
|
-
* @returns {Promise<Object>}
|
|
167
|
-
*/
|
|
168
|
-
async generateProof(challenge, solutionOrCallback) {
|
|
169
|
-
return this.prover.generateProof(challenge, solutionOrCallback || this.llmCallback);
|
|
170
101
|
}
|
|
102
|
+
}
|
|
171
103
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
async
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Create a solver function from an LLM callback
|
|
106
|
+
* @param {Function} llm - async (prompt) => responseString
|
|
107
|
+
* @returns {Function} Solver function for verify()
|
|
108
|
+
*/
|
|
109
|
+
export function createSolver(llm) {
|
|
110
|
+
return async (challengeString, challengeId) => {
|
|
111
|
+
const prompt = `Solve this challenge. Respond with ONLY the JSON object, no explanation:
|
|
112
|
+
|
|
113
|
+
${challengeString}`;
|
|
114
|
+
|
|
115
|
+
const response = await llm(prompt);
|
|
116
|
+
|
|
117
|
+
// Extract JSON from response
|
|
118
|
+
const match = response.match(/\{[\s\S]*?\}/);
|
|
119
|
+
if (!match) {
|
|
120
|
+
throw new Error('No JSON found in response');
|
|
182
121
|
}
|
|
122
|
+
|
|
123
|
+
return JSON.parse(match[0]);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
183
126
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
* Sign arbitrary data
|
|
195
|
-
* @param {string} data - Data to sign
|
|
196
|
-
* @returns {Object}
|
|
197
|
-
*/
|
|
198
|
-
sign(data) {
|
|
199
|
-
return this.prover.sign(data);
|
|
200
|
-
}
|
|
127
|
+
/**
|
|
128
|
+
* Quick verify helper
|
|
129
|
+
* @param {string} serverUrl - WebSocket URL
|
|
130
|
+
* @param {Function} [solver] - Solver function
|
|
131
|
+
* @param {string} [publicId] - Public ID
|
|
132
|
+
* @returns {Promise<Object>} Verification result
|
|
133
|
+
*/
|
|
134
|
+
export async function verify(serverUrl, solver, publicId) {
|
|
135
|
+
const client = new AAPClient({ serverUrl, solver, publicId });
|
|
136
|
+
return client.verify();
|
|
201
137
|
}
|
|
202
138
|
|
|
203
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Create client instance
|
|
141
|
+
*/
|
|
204
142
|
export function createClient(options) {
|
|
205
143
|
return new AAPClient(options);
|
|
206
144
|
}
|
|
207
145
|
|
|
208
|
-
|
|
209
|
-
export { Prover };
|
|
210
|
-
|
|
211
|
-
// WebSocket client (v2.7+)
|
|
212
|
-
export { AAPWebSocketClient, createSolver, verifyWithWebSocket } from './websocket.js';
|
|
213
|
-
|
|
214
|
-
export default { AAPClient, createClient, Prover };
|
|
146
|
+
export default { AAPClient, createClient, createSolver, verify, PROTOCOL_VERSION };
|
package/package.json
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aap-agent-client",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "WebSocket client for Agent Attestation Protocol - prove your AI agent identity",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"exports": {
|
|
9
|
-
".": "./index.js"
|
|
10
|
-
"./prover": "./prover.js"
|
|
9
|
+
".": "./index.js"
|
|
11
10
|
},
|
|
12
11
|
"files": ["*.js", "README.md"],
|
|
13
|
-
"keywords": ["aap", "agent", "attestation", "client", "ai", "verification", "
|
|
12
|
+
"keywords": ["aap", "agent", "attestation", "client", "ai", "verification", "websocket"],
|
|
14
13
|
"author": "ira-hash",
|
|
15
14
|
"license": "MIT",
|
|
16
15
|
"repository": {
|
|
@@ -20,7 +19,6 @@
|
|
|
20
19
|
},
|
|
21
20
|
"homepage": "https://github.com/ira-hash/agent-attestation-protocol#readme",
|
|
22
21
|
"dependencies": {
|
|
23
|
-
"aap-agent-core": "^2.6.0",
|
|
24
22
|
"ws": "^8.16.0"
|
|
25
23
|
},
|
|
26
24
|
"engines": {
|
package/prover.js
DELETED
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @aap/client - Prover
|
|
3
|
-
*
|
|
4
|
-
* Generates proofs for AAP verification (supports batch challenges).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createHash } from 'node:crypto';
|
|
8
|
-
import { Identity, createProofData } from 'aap-agent-core';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Prover class for generating AAP proofs
|
|
12
|
-
*/
|
|
13
|
-
export class Prover {
|
|
14
|
-
/**
|
|
15
|
-
* @param {Object} [options]
|
|
16
|
-
* @param {Identity} [options.identity] - Pre-existing identity instance
|
|
17
|
-
* @param {string} [options.storagePath] - Path for identity storage
|
|
18
|
-
*/
|
|
19
|
-
constructor(options = {}) {
|
|
20
|
-
this.identity = options.identity || new Identity({ storagePath: options.storagePath });
|
|
21
|
-
this.identity.init();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get public identity info
|
|
26
|
-
* @returns {Object}
|
|
27
|
-
*/
|
|
28
|
-
getIdentity() {
|
|
29
|
-
return this.identity.getPublic();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Generate proofs for a batch of challenges
|
|
34
|
-
*
|
|
35
|
-
* @param {Object} challengeBatch - Batch challenge from server
|
|
36
|
-
* @param {string} challengeBatch.nonce - Server-provided nonce
|
|
37
|
-
* @param {Array} challengeBatch.challenges - Array of challenges
|
|
38
|
-
* @param {Function|null} [llmCallback] - Async LLM callback for solving
|
|
39
|
-
* @returns {Promise<Object>} Proof object ready for submission
|
|
40
|
-
*/
|
|
41
|
-
async generateBatchProof(challengeBatch, llmCallback) {
|
|
42
|
-
const startTime = Date.now();
|
|
43
|
-
const { nonce, challenges } = challengeBatch;
|
|
44
|
-
|
|
45
|
-
// Solve all challenges
|
|
46
|
-
const solutions = [];
|
|
47
|
-
|
|
48
|
-
if (llmCallback) {
|
|
49
|
-
// Use LLM to solve all at once (more efficient)
|
|
50
|
-
try {
|
|
51
|
-
const combinedPrompt = this.createBatchPrompt(challenges);
|
|
52
|
-
const llmResponse = await llmCallback(combinedPrompt);
|
|
53
|
-
const parsedSolutions = this.parseBatchResponse(llmResponse, challenges.length);
|
|
54
|
-
solutions.push(...parsedSolutions);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.error('[AAP] LLM callback failed:', error.message);
|
|
57
|
-
// Fallback to placeholder solutions (will fail verification but won't crash)
|
|
58
|
-
for (const c of challenges) {
|
|
59
|
-
const saltMatch = c.challenge_string.match(/\[REQ-([A-Z0-9]+)\]/);
|
|
60
|
-
const salt = saltMatch ? saltMatch[1] : 'ERROR';
|
|
61
|
-
solutions.push(JSON.stringify({ salt, error: 'LLM callback failed' }));
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
// Use built-in solvers
|
|
66
|
-
for (const challenge of challenges) {
|
|
67
|
-
const solution = this.solve(challenge.challenge_string, nonce, challenge.type);
|
|
68
|
-
solutions.push(solution);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Create proof
|
|
73
|
-
const identity = this.identity.getPublic();
|
|
74
|
-
const timestamp = Date.now();
|
|
75
|
-
|
|
76
|
-
const solutionsString = JSON.stringify(solutions);
|
|
77
|
-
const proofData = createProofData({
|
|
78
|
-
nonce,
|
|
79
|
-
solution: solutionsString,
|
|
80
|
-
publicId: identity.publicId,
|
|
81
|
-
timestamp
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const signature = this.identity.sign(proofData);
|
|
85
|
-
const responseTimeMs = Date.now() - startTime;
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
solutions,
|
|
89
|
-
signature,
|
|
90
|
-
publicKey: identity.publicKey,
|
|
91
|
-
publicId: identity.publicId,
|
|
92
|
-
nonce,
|
|
93
|
-
timestamp,
|
|
94
|
-
responseTimeMs,
|
|
95
|
-
protocol: 'AAP',
|
|
96
|
-
version: '2.0.0'
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Create a combined prompt for batch solving
|
|
102
|
-
* @private
|
|
103
|
-
*/
|
|
104
|
-
createBatchPrompt(challenges) {
|
|
105
|
-
let prompt = `Solve all of the following challenges. Respond with a JSON array of solutions.\n\n`;
|
|
106
|
-
|
|
107
|
-
challenges.forEach((c, i) => {
|
|
108
|
-
prompt += `Challenge ${i + 1} (${c.type}):\n${c.challenge_string}\n\n`;
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
prompt += `\nRespond with ONLY a JSON array like: [{"result": ...}, {"items": [...]}, {"answer": "..."}]`;
|
|
112
|
-
|
|
113
|
-
return prompt;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Parse LLM response into solutions array
|
|
118
|
-
* @private
|
|
119
|
-
*/
|
|
120
|
-
parseBatchResponse(response, expectedCount) {
|
|
121
|
-
try {
|
|
122
|
-
// Try to find JSON array in response
|
|
123
|
-
const match = response.match(/\[[\s\S]*\]/);
|
|
124
|
-
if (match) {
|
|
125
|
-
const parsed = JSON.parse(match[0]);
|
|
126
|
-
if (Array.isArray(parsed) && parsed.length === expectedCount) {
|
|
127
|
-
return parsed.map(s => typeof s === 'string' ? s : JSON.stringify(s));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
} catch (e) {
|
|
131
|
-
// Fall through to fallback
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Fallback: try to extract individual JSON objects
|
|
135
|
-
const solutions = [];
|
|
136
|
-
const jsonMatches = response.matchAll(/\{[^{}]*\}/g);
|
|
137
|
-
for (const match of jsonMatches) {
|
|
138
|
-
solutions.push(match[0]);
|
|
139
|
-
if (solutions.length >= expectedCount) break;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Pad with empty if needed
|
|
143
|
-
while (solutions.length < expectedCount) {
|
|
144
|
-
solutions.push('{}');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return solutions;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Generate a proof for a single challenge (legacy)
|
|
152
|
-
*/
|
|
153
|
-
async generateProof(challenge, solutionOrCallback) {
|
|
154
|
-
const startTime = Date.now();
|
|
155
|
-
const { challenge_string, nonce, type } = challenge;
|
|
156
|
-
|
|
157
|
-
let solution;
|
|
158
|
-
if (typeof solutionOrCallback === 'function') {
|
|
159
|
-
solution = await solutionOrCallback(challenge_string, nonce, type);
|
|
160
|
-
} else if (typeof solutionOrCallback === 'string') {
|
|
161
|
-
solution = solutionOrCallback;
|
|
162
|
-
} else {
|
|
163
|
-
solution = this.solve(challenge_string, nonce, type);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const identity = this.identity.getPublic();
|
|
167
|
-
const timestamp = Date.now();
|
|
168
|
-
|
|
169
|
-
const proofData = createProofData({
|
|
170
|
-
nonce,
|
|
171
|
-
solution,
|
|
172
|
-
publicId: identity.publicId,
|
|
173
|
-
timestamp
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const signature = this.identity.sign(proofData);
|
|
177
|
-
const responseTimeMs = Date.now() - startTime;
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
solution,
|
|
181
|
-
signature,
|
|
182
|
-
publicKey: identity.publicKey,
|
|
183
|
-
publicId: identity.publicId,
|
|
184
|
-
nonce,
|
|
185
|
-
timestamp,
|
|
186
|
-
responseTimeMs,
|
|
187
|
-
protocol: 'AAP',
|
|
188
|
-
version: '2.0.0'
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Solve a challenge programmatically
|
|
194
|
-
*/
|
|
195
|
-
solve(challengeString, nonce, type) {
|
|
196
|
-
const solvers = {
|
|
197
|
-
nlp_math: () => {
|
|
198
|
-
// "Subtract B from A, then multiply by C"
|
|
199
|
-
let match = challengeString.match(/Subtract (\d+) from (\d+), then multiply.*?(\d+)/i);
|
|
200
|
-
if (match) {
|
|
201
|
-
const result = (parseInt(match[2]) - parseInt(match[1])) * parseInt(match[3]);
|
|
202
|
-
return JSON.stringify({ result });
|
|
203
|
-
}
|
|
204
|
-
// "Add A and B together, then divide by C"
|
|
205
|
-
match = challengeString.match(/Add (\d+) and (\d+).*?divide by (\d+)/i);
|
|
206
|
-
if (match) {
|
|
207
|
-
const result = Math.round(((parseInt(match[1]) + parseInt(match[2])) / parseInt(match[3])) * 100) / 100;
|
|
208
|
-
return JSON.stringify({ result });
|
|
209
|
-
}
|
|
210
|
-
// "Divide A by C, then add B"
|
|
211
|
-
match = challengeString.match(/Divide (\d+) by (\d+), then add (\d+)/i);
|
|
212
|
-
if (match) {
|
|
213
|
-
const result = Math.round((parseInt(match[1]) / parseInt(match[2]) + parseInt(match[3])) * 100) / 100;
|
|
214
|
-
return JSON.stringify({ result });
|
|
215
|
-
}
|
|
216
|
-
return JSON.stringify({ result: 0 });
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
nlp_extract: () => {
|
|
220
|
-
// Extract items from quoted sentence
|
|
221
|
-
const sentenceMatch = challengeString.match(/Sentence: "([^"]+)"/);
|
|
222
|
-
if (!sentenceMatch) return JSON.stringify({ items: [] });
|
|
223
|
-
|
|
224
|
-
const sentence = sentenceMatch[1].toLowerCase();
|
|
225
|
-
|
|
226
|
-
const animals = ['cat', 'dog', 'rabbit', 'tiger', 'lion', 'elephant', 'giraffe', 'penguin', 'eagle', 'shark'];
|
|
227
|
-
const fruits = ['apple', 'banana', 'orange', 'grape', 'strawberry', 'watermelon', 'peach', 'kiwi', 'mango', 'cherry'];
|
|
228
|
-
const colors = ['red', 'blue', 'yellow', 'green', 'purple', 'orange', 'pink', 'black', 'white', 'brown'];
|
|
229
|
-
|
|
230
|
-
let pool = animals;
|
|
231
|
-
if (challengeString.toLowerCase().includes('fruit')) pool = fruits;
|
|
232
|
-
if (challengeString.toLowerCase().includes('color')) pool = colors;
|
|
233
|
-
|
|
234
|
-
const found = pool.filter(item => sentence.includes(item.toLowerCase()));
|
|
235
|
-
return JSON.stringify({ items: found });
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
nlp_transform: () => {
|
|
239
|
-
const inputMatch = challengeString.match(/"([^"]+)"/);
|
|
240
|
-
if (!inputMatch) return JSON.stringify({ output: '' });
|
|
241
|
-
const input = inputMatch[1];
|
|
242
|
-
|
|
243
|
-
if (challengeString.toLowerCase().includes('reverse') && challengeString.toLowerCase().includes('uppercase')) {
|
|
244
|
-
return JSON.stringify({ output: input.split('').reverse().join('').toUpperCase() });
|
|
245
|
-
}
|
|
246
|
-
if (challengeString.toLowerCase().includes('digits') && challengeString.toLowerCase().includes('sum')) {
|
|
247
|
-
const sum = input.split('').filter(c => /\d/.test(c)).reduce((a, b) => a + parseInt(b), 0);
|
|
248
|
-
return JSON.stringify({ output: sum });
|
|
249
|
-
}
|
|
250
|
-
if (challengeString.toLowerCase().includes('letters') && challengeString.toLowerCase().includes('sort')) {
|
|
251
|
-
const sorted = input.split('').filter(c => /[a-zA-Z]/.test(c)).sort().join('');
|
|
252
|
-
return JSON.stringify({ output: sorted });
|
|
253
|
-
}
|
|
254
|
-
if (challengeString.toLowerCase().includes('hyphen')) {
|
|
255
|
-
return JSON.stringify({ output: input.split('').join('-') });
|
|
256
|
-
}
|
|
257
|
-
return JSON.stringify({ output: input });
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
nlp_logic: () => {
|
|
261
|
-
// "If the larger number between A and B is greater than C"
|
|
262
|
-
let match = challengeString.match(/larger.*?between (\d+) and (\d+).*?greater than (\d+).*?"(\w+)".*?"(\w+)"/i);
|
|
263
|
-
if (match) {
|
|
264
|
-
const answer = Math.max(parseInt(match[1]), parseInt(match[2])) > parseInt(match[3]) ? match[4] : match[5];
|
|
265
|
-
return JSON.stringify({ answer });
|
|
266
|
-
}
|
|
267
|
-
// "If the sum of A and B is less than C"
|
|
268
|
-
match = challengeString.match(/sum of (\d+) and (\d+).*?less than (\d+).*?"(\w+)".*?"(\w+)"/i);
|
|
269
|
-
if (match) {
|
|
270
|
-
const answer = (parseInt(match[1]) + parseInt(match[2])) < parseInt(match[3]) ? match[4] : match[5];
|
|
271
|
-
return JSON.stringify({ answer });
|
|
272
|
-
}
|
|
273
|
-
// "If A is even and B is odd"
|
|
274
|
-
match = challengeString.match(/If (\d+) is even and (\d+) is odd.*?"(\w+)".*?"(\w+)"/i);
|
|
275
|
-
if (match) {
|
|
276
|
-
const answer = (parseInt(match[1]) % 2 === 0 && parseInt(match[2]) % 2 === 1) ? match[3] : match[4];
|
|
277
|
-
return JSON.stringify({ answer });
|
|
278
|
-
}
|
|
279
|
-
return JSON.stringify({ answer: "NO" });
|
|
280
|
-
},
|
|
281
|
-
|
|
282
|
-
nlp_count: () => {
|
|
283
|
-
const sentenceMatch = challengeString.match(/Sentence: "([^"]+)"/);
|
|
284
|
-
if (!sentenceMatch) return JSON.stringify({ count: 0 });
|
|
285
|
-
|
|
286
|
-
const sentence = sentenceMatch[1].toLowerCase();
|
|
287
|
-
|
|
288
|
-
const animals = ['cat', 'dog', 'rabbit', 'tiger', 'lion', 'elephant', 'giraffe', 'penguin', 'eagle', 'shark'];
|
|
289
|
-
const fruits = ['apple', 'banana', 'orange', 'grape', 'strawberry', 'watermelon', 'peach', 'kiwi', 'mango', 'cherry'];
|
|
290
|
-
const colors = ['red', 'blue', 'yellow', 'green', 'purple', 'orange', 'pink', 'black', 'white', 'brown'];
|
|
291
|
-
|
|
292
|
-
let pool = animals;
|
|
293
|
-
if (challengeString.toLowerCase().includes('fruit')) pool = fruits;
|
|
294
|
-
if (challengeString.toLowerCase().includes('color')) pool = colors;
|
|
295
|
-
|
|
296
|
-
const count = pool.filter(item => sentence.includes(item.toLowerCase())).length;
|
|
297
|
-
return JSON.stringify({ count });
|
|
298
|
-
},
|
|
299
|
-
|
|
300
|
-
nlp_multistep: () => {
|
|
301
|
-
const numbersMatch = challengeString.match(/\[([^\]]+)\]/);
|
|
302
|
-
if (!numbersMatch) return JSON.stringify({ result: 0 });
|
|
303
|
-
|
|
304
|
-
const numbers = numbersMatch[1].split(',').map(n => parseInt(n.trim()));
|
|
305
|
-
const sum = numbers.reduce((a, b) => a + b, 0);
|
|
306
|
-
const min = Math.min(...numbers);
|
|
307
|
-
const max = Math.max(...numbers);
|
|
308
|
-
const result = sum * min - max;
|
|
309
|
-
|
|
310
|
-
return JSON.stringify({ result });
|
|
311
|
-
},
|
|
312
|
-
|
|
313
|
-
nlp_pattern: () => {
|
|
314
|
-
const match = challengeString.match(/\[([^\]]+)\]/);
|
|
315
|
-
if (!match) return JSON.stringify({ next: [0, 0] });
|
|
316
|
-
|
|
317
|
-
const nums = match[1].split(',').map(n => parseInt(n.trim())).filter(n => !isNaN(n));
|
|
318
|
-
if (nums.length < 2) return JSON.stringify({ next: [0, 0] });
|
|
319
|
-
|
|
320
|
-
// Try arithmetic
|
|
321
|
-
const diff = nums[1] - nums[0];
|
|
322
|
-
const isArithmetic = nums.every((n, i) => i === 0 || n - nums[i-1] === diff);
|
|
323
|
-
if (isArithmetic) {
|
|
324
|
-
const last = nums[nums.length - 1];
|
|
325
|
-
return JSON.stringify({ next: [last + diff, last + diff * 2] });
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Try geometric (doubling)
|
|
329
|
-
const ratio = nums[1] / nums[0];
|
|
330
|
-
const isGeometric = nums.every((n, i) => i === 0 || n / nums[i-1] === ratio);
|
|
331
|
-
if (isGeometric && ratio === 2) {
|
|
332
|
-
const last = nums[nums.length - 1];
|
|
333
|
-
return JSON.stringify({ next: [last * 2, last * 4] });
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Try Fibonacci-like
|
|
337
|
-
const isFib = nums.length >= 3 && nums.slice(2).every((n, i) => n === nums[i] + nums[i+1]);
|
|
338
|
-
if (isFib) {
|
|
339
|
-
const n1 = nums[nums.length - 2] + nums[nums.length - 1];
|
|
340
|
-
const n2 = nums[nums.length - 1] + n1;
|
|
341
|
-
return JSON.stringify({ next: [n1, n2] });
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return JSON.stringify({ next: [0, 0] });
|
|
345
|
-
},
|
|
346
|
-
|
|
347
|
-
nlp_analysis: () => {
|
|
348
|
-
const listMatch = challengeString.match(/list: ([^\.]+)/i);
|
|
349
|
-
if (!listMatch) return JSON.stringify({ answer: '' });
|
|
350
|
-
|
|
351
|
-
const words = listMatch[1].split(',').map(w => w.trim().toLowerCase());
|
|
352
|
-
|
|
353
|
-
if (challengeString.toLowerCase().includes('longest')) {
|
|
354
|
-
const longest = words.reduce((a, b) => a.length >= b.length ? a : b);
|
|
355
|
-
return JSON.stringify({ answer: longest });
|
|
356
|
-
}
|
|
357
|
-
if (challengeString.toLowerCase().includes('shortest')) {
|
|
358
|
-
const shortest = words.reduce((a, b) => a.length <= b.length ? a : b);
|
|
359
|
-
return JSON.stringify({ answer: shortest });
|
|
360
|
-
}
|
|
361
|
-
if (challengeString.toLowerCase().includes('alphabetically')) {
|
|
362
|
-
const first = [...words].sort()[0];
|
|
363
|
-
return JSON.stringify({ answer: first });
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return JSON.stringify({ answer: words[0] || '' });
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
const solver = solvers[type];
|
|
371
|
-
if (solver) {
|
|
372
|
-
return solver();
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return JSON.stringify({ error: `Unknown type: ${type}` });
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Sign arbitrary data
|
|
380
|
-
*/
|
|
381
|
-
sign(data) {
|
|
382
|
-
return {
|
|
383
|
-
data,
|
|
384
|
-
signature: this.identity.sign(data),
|
|
385
|
-
publicId: this.identity.getPublic().publicId,
|
|
386
|
-
timestamp: Date.now()
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
export default { Prover };
|
package/websocket.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AAP WebSocket Client v2.7
|
|
3
|
-
*
|
|
4
|
-
* Connects to AAP WebSocket server, receives sequential challenges,
|
|
5
|
-
* and responds in real-time.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import WebSocket from 'ws';
|
|
9
|
-
import { Identity } from 'aap-agent-core';
|
|
10
|
-
|
|
11
|
-
const PROTOCOL_VERSION = '2.7.0';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* AAP WebSocket Client
|
|
15
|
-
*/
|
|
16
|
-
export class AAPWebSocketClient {
|
|
17
|
-
constructor(options = {}) {
|
|
18
|
-
this.serverUrl = options.serverUrl || 'ws://localhost:3000/aap';
|
|
19
|
-
this.identity = options.identity || new Identity(options);
|
|
20
|
-
this.llmCallback = options.llmCallback || null;
|
|
21
|
-
this.autoSolve = options.autoSolve !== false;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get public identity info
|
|
26
|
-
*/
|
|
27
|
-
getIdentity() {
|
|
28
|
-
return this.identity.getPublic();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Connect and verify
|
|
33
|
-
* @param {Function} [llmCallback] - async (challengeString) => solutionObject
|
|
34
|
-
* @returns {Promise<Object>} Verification result
|
|
35
|
-
*/
|
|
36
|
-
async verify(llmCallback) {
|
|
37
|
-
const solver = llmCallback || this.llmCallback;
|
|
38
|
-
|
|
39
|
-
return new Promise((resolve, reject) => {
|
|
40
|
-
const ws = new WebSocket(this.serverUrl);
|
|
41
|
-
let sessionId = null;
|
|
42
|
-
let result = null;
|
|
43
|
-
|
|
44
|
-
ws.on('open', () => {
|
|
45
|
-
// Wait for handshake
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
ws.on('message', async (data) => {
|
|
49
|
-
try {
|
|
50
|
-
const msg = JSON.parse(data.toString());
|
|
51
|
-
|
|
52
|
-
switch (msg.type) {
|
|
53
|
-
case 'handshake':
|
|
54
|
-
sessionId = msg.sessionId;
|
|
55
|
-
// Send ready with identity
|
|
56
|
-
const identity = this.identity.getPublic();
|
|
57
|
-
ws.send(JSON.stringify({
|
|
58
|
-
type: 'ready',
|
|
59
|
-
publicKey: identity.publicKey,
|
|
60
|
-
publicId: identity.publicId
|
|
61
|
-
}));
|
|
62
|
-
break;
|
|
63
|
-
|
|
64
|
-
case 'challenge':
|
|
65
|
-
// Solve challenge
|
|
66
|
-
if (!solver) {
|
|
67
|
-
ws.send(JSON.stringify({
|
|
68
|
-
type: 'answer',
|
|
69
|
-
answer: {} // Empty - will fail
|
|
70
|
-
}));
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const answer = await solver(msg.challenge, msg.id);
|
|
76
|
-
ws.send(JSON.stringify({
|
|
77
|
-
type: 'answer',
|
|
78
|
-
answer
|
|
79
|
-
}));
|
|
80
|
-
} catch (e) {
|
|
81
|
-
ws.send(JSON.stringify({
|
|
82
|
-
type: 'answer',
|
|
83
|
-
answer: { error: e.message }
|
|
84
|
-
}));
|
|
85
|
-
}
|
|
86
|
-
break;
|
|
87
|
-
|
|
88
|
-
case 'ack':
|
|
89
|
-
// Challenge acknowledged, wait for next
|
|
90
|
-
break;
|
|
91
|
-
|
|
92
|
-
case 'timeout':
|
|
93
|
-
// Too slow
|
|
94
|
-
break;
|
|
95
|
-
|
|
96
|
-
case 'result':
|
|
97
|
-
result = msg;
|
|
98
|
-
ws.close();
|
|
99
|
-
break;
|
|
100
|
-
|
|
101
|
-
case 'error':
|
|
102
|
-
reject(new Error(msg.message));
|
|
103
|
-
ws.close();
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
} catch (e) {
|
|
107
|
-
reject(e);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
ws.on('close', () => {
|
|
112
|
-
if (result) {
|
|
113
|
-
resolve(result);
|
|
114
|
-
} else {
|
|
115
|
-
reject(new Error('Connection closed without result'));
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
ws.on('error', (err) => {
|
|
120
|
-
reject(err);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Create solver function from LLM
|
|
128
|
-
* @param {Function} llm - async (prompt) => response string
|
|
129
|
-
* @returns {Function} Solver function
|
|
130
|
-
*/
|
|
131
|
-
export function createSolver(llm) {
|
|
132
|
-
return async (challengeString, challengeId) => {
|
|
133
|
-
const prompt = `Solve this challenge and respond with ONLY valid JSON (no markdown, no explanation):
|
|
134
|
-
|
|
135
|
-
${challengeString}
|
|
136
|
-
|
|
137
|
-
Important:
|
|
138
|
-
- Include the exact salt from the challenge
|
|
139
|
-
- Follow the response format exactly
|
|
140
|
-
- Return ONLY the JSON object`;
|
|
141
|
-
|
|
142
|
-
const response = await llm(prompt);
|
|
143
|
-
|
|
144
|
-
// Parse JSON from response
|
|
145
|
-
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
146
|
-
if (!jsonMatch) {
|
|
147
|
-
throw new Error('No JSON found in response');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return JSON.parse(jsonMatch[0]);
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Quick verify helper
|
|
156
|
-
*/
|
|
157
|
-
export async function verifyWithWebSocket(serverUrl, llmCallback) {
|
|
158
|
-
const client = new AAPWebSocketClient({ serverUrl, llmCallback });
|
|
159
|
-
return client.verify();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export default AAPWebSocketClient;
|