aap-agent-client 2.7.0 → 3.1.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 +93 -188
- package/package.json +4 -6
- package/prover.js +0 -391
- package/websocket.js +0 -162
package/index.js
CHANGED
|
@@ -1,214 +1,119 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @aap/client
|
|
2
|
+
* @aap/client v3.1.0
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* WebSocket client for Agent Attestation Protocol.
|
|
5
|
+
* Batch mode: receive all challenges, solve, submit.
|
|
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.1.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.
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get agent's public identity
|
|
45
|
-
* @returns {Object}
|
|
46
|
-
*/
|
|
47
|
-
getIdentity() {
|
|
48
|
-
return this.prover.getIdentity();
|
|
17
|
+
this.serverUrl = options.serverUrl || 'ws://localhost:3000/aap';
|
|
18
|
+
this.publicId = options.publicId || null;
|
|
19
|
+
this.solver = options.solver || null;
|
|
49
20
|
}
|
|
50
21
|
|
|
51
22
|
/**
|
|
52
|
-
*
|
|
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 (challenges) => answers[]
|
|
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
|
-
* Check server health
|
|
119
|
-
* @param {string} [serverUrl] - Override default server URL
|
|
120
|
-
* @returns {Promise<Object>}
|
|
121
|
-
*/
|
|
122
|
-
async checkHealth(serverUrl) {
|
|
123
|
-
const baseUrl = (serverUrl || this.serverUrl)?.replace(/\/$/, '');
|
|
124
|
-
if (!baseUrl) {
|
|
125
|
-
throw new Error('Server URL is required');
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const res = await fetch(`${baseUrl}/health`);
|
|
130
|
-
if (!res.ok) {
|
|
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
|
+
|
|
36
|
+
ws.on('message', async (data) => {
|
|
37
|
+
try {
|
|
38
|
+
const msg = JSON.parse(data.toString());
|
|
39
|
+
|
|
40
|
+
switch (msg.type) {
|
|
41
|
+
case 'handshake':
|
|
42
|
+
ws.send(JSON.stringify({
|
|
43
|
+
type: 'ready',
|
|
44
|
+
publicId: this.publicId
|
|
45
|
+
}));
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case 'challenges':
|
|
49
|
+
if (!solve) {
|
|
50
|
+
ws.send(JSON.stringify({ type: 'answers', answers: [] }));
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const answers = await solve(msg.challenges);
|
|
56
|
+
ws.send(JSON.stringify({ type: 'answers', answers }));
|
|
57
|
+
} catch (e) {
|
|
58
|
+
ws.send(JSON.stringify({ type: 'answers', answers: [] }));
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
case 'result':
|
|
63
|
+
result = msg;
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
case 'error':
|
|
67
|
+
reject(new Error(msg.message || 'Unknown error'));
|
|
68
|
+
ws.close();
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {
|
|
72
|
+
reject(e);
|
|
73
|
+
ws.close();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
ws.on('close', () => {
|
|
78
|
+
if (result) resolve(result);
|
|
79
|
+
else reject(new Error('Connection closed without result'));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
ws.on('error', reject);
|
|
153
83
|
});
|
|
154
|
-
|
|
155
|
-
if (!res.ok) {
|
|
156
|
-
throw new Error(`Failed to get challenge: ${res.status}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return res.json();
|
|
160
84
|
}
|
|
85
|
+
}
|
|
161
86
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
async
|
|
169
|
-
|
|
170
|
-
}
|
|
87
|
+
/**
|
|
88
|
+
* Create a solver function from an LLM callback
|
|
89
|
+
* @param {Function} llm - async (prompt) => responseString
|
|
90
|
+
* @returns {Function} Solver function
|
|
91
|
+
*/
|
|
92
|
+
export function createSolver(llm) {
|
|
93
|
+
return async (challenges) => {
|
|
94
|
+
const prompt = `Solve ALL these challenges. Return a JSON array of answers in order.
|
|
171
95
|
|
|
172
|
-
|
|
173
|
-
* Submit a proof for verification (for manual flow)
|
|
174
|
-
* @param {string} serverUrl - Server URL
|
|
175
|
-
* @param {Object} proof - Proof from generateProof()
|
|
176
|
-
* @returns {Promise<Object>}
|
|
177
|
-
*/
|
|
178
|
-
async submitProof(serverUrl, proof) {
|
|
179
|
-
const baseUrl = (serverUrl || this.serverUrl)?.replace(/\/$/, '');
|
|
180
|
-
if (!baseUrl) {
|
|
181
|
-
throw new Error('Server URL is required');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const res = await fetch(`${baseUrl}/verify`, {
|
|
185
|
-
method: 'POST',
|
|
186
|
-
headers: { 'Content-Type': 'application/json' },
|
|
187
|
-
body: JSON.stringify(proof)
|
|
188
|
-
});
|
|
96
|
+
${challenges.map((c, i) => `[${i}] ${c.challenge}`).join('\n\n')}
|
|
189
97
|
|
|
190
|
-
|
|
191
|
-
}
|
|
98
|
+
Respond with ONLY a JSON array like: [{...}, {...}, ...]`;
|
|
192
99
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
100
|
+
const response = await llm(prompt);
|
|
101
|
+
const match = response.match(/\[[\s\S]*\]/);
|
|
102
|
+
if (!match) throw new Error('No JSON array found');
|
|
103
|
+
return JSON.parse(match[0]);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Quick verify helper
|
|
109
|
+
*/
|
|
110
|
+
export async function verify(serverUrl, solver, publicId) {
|
|
111
|
+
const client = new AAPClient({ serverUrl, solver, publicId });
|
|
112
|
+
return client.verify();
|
|
201
113
|
}
|
|
202
114
|
|
|
203
|
-
// Convenience factory
|
|
204
115
|
export function createClient(options) {
|
|
205
116
|
return new AAPClient(options);
|
|
206
117
|
}
|
|
207
118
|
|
|
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 };
|
|
119
|
+
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.1.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;
|