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.
Files changed (4) hide show
  1. package/index.js +93 -188
  2. package/package.json +4 -6
  3. package/prover.js +0 -391
  4. 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
- * Client library for Agent Attestation Protocol.
5
- * Enables AI agents to prove their identity to verification servers.
4
+ * WebSocket client for Agent Attestation Protocol.
5
+ * Batch mode: receive all challenges, solve, submit.
6
6
  */
7
7
 
8
- import { Prover } from './prover.js';
9
- import { Identity } from 'aap-agent-core';
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 - High-level interface for verification
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?.replace(/\/$/, '');
39
- this.llmCallback = options.llmCallback;
40
- this.prover = new Prover({ storagePath: options.storagePath });
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
- * 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 (challenges) => answers[]
56
25
  * @returns {Promise<Object>} Verification result
57
26
  */
58
- async verify(serverUrl, solutionOrCallback) {
59
- const baseUrl = (serverUrl || this.serverUrl)?.replace(/\/$/, '');
60
- if (!baseUrl) {
61
- throw new Error('Server URL is required');
62
- }
63
-
64
- const callback = solutionOrCallback || this.llmCallback;
65
-
66
- // Step 1: Request challenge (with timeout)
67
- let challengeRes;
68
- try {
69
- challengeRes = await fetchWithTimeout(`${baseUrl}/challenge`, {
70
- method: 'POST',
71
- headers: { 'Content-Type': 'application/json' }
72
- }, 10000);
73
- } catch (error) {
74
- if (error.name === 'AbortError') {
75
- throw new Error('Challenge request timed out');
76
- }
77
- throw error;
78
- }
79
-
80
- if (!challengeRes.ok) {
81
- throw new Error(`Failed to get challenge: ${challengeRes.status}`);
82
- }
83
-
84
- const challenge = await challengeRes.json();
85
-
86
- // Step 2: Generate proof
87
- const proof = await this.prover.generateProof(challenge, callback);
88
-
89
- // Step 3: Submit proof (with timeout)
90
- let verifyRes;
91
- try {
92
- verifyRes = await fetchWithTimeout(`${baseUrl}/verify`, {
93
- method: 'POST',
94
- headers: { 'Content-Type': 'application/json' },
95
- body: JSON.stringify(proof)
96
- }, 15000);
97
- } catch (error) {
98
- if (error.name === 'AbortError') {
99
- throw new Error('Verification request timed out');
100
- }
101
- throw error;
102
- }
103
-
104
- const result = await verifyRes.json();
105
-
106
- return {
107
- ...result,
108
- challenge,
109
- proof: {
110
- solution: proof.solution,
111
- responseTimeMs: proof.responseTimeMs,
112
- publicId: proof.publicId
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
- * 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
- }
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
- return res.json();
191
- }
98
+ Respond with ONLY a JSON array like: [{...}, {...}, ...]`;
192
99
 
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
- }
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
- // Re-export Prover
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": "2.7.0",
4
- "description": "Client library for Agent Attestation Protocol - prove your AI agent identity",
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", "llm"],
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;