gopherhole_openclaw_a2a 0.3.14 → 0.4.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/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/package.json +15 -7
- package/A2A-FIX-GUIDE.md +0 -122
- package/index.ts +0 -232
- package/src/channel.ts +0 -385
- package/src/connection.test.ts +0 -298
- package/src/connection.ts +0 -533
- package/src/gateway-client.ts +0 -328
- package/src/logger.ts +0 -118
- package/src/types.ts +0 -75
- package/test-image.mjs +0 -29
- package/test-image2.mjs +0 -37
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -9
- /package/{clawdbot.plugin.json → openclaw.plugin.json} +0 -0
package/src/connection.ts
DELETED
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A2A Connection Manager
|
|
3
|
-
* Uses @gopherhole/sdk for GopherHole hub connectivity
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { GopherHole, Message, getTaskResponseText } from '@gopherhole/sdk';
|
|
7
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
8
|
-
import type {
|
|
9
|
-
A2AMessage,
|
|
10
|
-
A2AResponse,
|
|
11
|
-
A2AChannelConfig,
|
|
12
|
-
} from './types.js';
|
|
13
|
-
|
|
14
|
-
export type MessageHandler = (agentId: string, message: A2AMessage) => Promise<void>;
|
|
15
|
-
|
|
16
|
-
export class A2AConnectionManager {
|
|
17
|
-
private gopherhole: GopherHole | null = null;
|
|
18
|
-
private messageHandler: MessageHandler | null = null;
|
|
19
|
-
private config: A2AChannelConfig;
|
|
20
|
-
private agentId: string;
|
|
21
|
-
private connected = false;
|
|
22
|
-
|
|
23
|
-
constructor(config: A2AChannelConfig) {
|
|
24
|
-
this.config = config;
|
|
25
|
-
this.agentId = config.agentId ?? 'openclaw';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
setMessageHandler(handler: MessageHandler): void {
|
|
29
|
-
this.messageHandler = handler;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async start(): Promise<void> {
|
|
33
|
-
// Connect to GopherHole if configured (flat config: enabled + apiKey)
|
|
34
|
-
if (this.config.enabled && this.config.apiKey) {
|
|
35
|
-
await this.connectToGopherHole();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private async connectToGopherHole(): Promise<void> {
|
|
40
|
-
const hubUrl = this.config.bridgeUrl || 'wss://hub.gopherhole.ai/ws';
|
|
41
|
-
const timeoutMs = this.config.requestTimeoutMs ?? 180000;
|
|
42
|
-
|
|
43
|
-
this.gopherhole = new GopherHole({
|
|
44
|
-
apiKey: this.config.apiKey!,
|
|
45
|
-
hubUrl,
|
|
46
|
-
autoReconnect: true,
|
|
47
|
-
reconnectDelay: this.config.reconnectIntervalMs ?? 5000,
|
|
48
|
-
maxReconnectDelay: 300000, // 5 min cap on backoff
|
|
49
|
-
// maxReconnectAttempts defaults to 0 (infinite) in SDK
|
|
50
|
-
requestTimeout: timeoutMs,
|
|
51
|
-
messageTimeout: timeoutMs,
|
|
52
|
-
agentCard: this.config.agentCard ?? {
|
|
53
|
-
name: this.config.agentName ?? 'OpenClaw',
|
|
54
|
-
description: 'Personal AI assistant with tools, web search, browser control, and various skills',
|
|
55
|
-
version: '0.1.0',
|
|
56
|
-
skills: [
|
|
57
|
-
{
|
|
58
|
-
id: 'chat',
|
|
59
|
-
name: 'Chat',
|
|
60
|
-
description: 'General conversation and Q&A',
|
|
61
|
-
tags: ['conversation', 'assistant'],
|
|
62
|
-
inputModes: ['text/plain'],
|
|
63
|
-
outputModes: ['text/plain', 'text/markdown'],
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
id: 'web-search',
|
|
67
|
-
name: 'Web Search',
|
|
68
|
-
description: 'Search the web and summarize results',
|
|
69
|
-
tags: ['search', 'research', 'web'],
|
|
70
|
-
inputModes: ['text/plain'],
|
|
71
|
-
outputModes: ['text/plain', 'text/markdown'],
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: 'coding',
|
|
75
|
-
name: 'Coding',
|
|
76
|
-
description: 'Write, review, and debug code',
|
|
77
|
-
tags: ['code', 'programming', 'development'],
|
|
78
|
-
inputModes: ['text/plain'],
|
|
79
|
-
outputModes: ['text/plain', 'text/markdown'],
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
id: 'files',
|
|
83
|
-
name: 'File Operations',
|
|
84
|
-
description: 'Read, write, and manage files',
|
|
85
|
-
tags: ['files', 'documents'],
|
|
86
|
-
inputModes: ['text/plain', 'application/pdf', 'image/*'],
|
|
87
|
-
outputModes: ['text/plain', 'text/markdown'],
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Set up event handlers
|
|
94
|
-
this.gopherhole.on('connect', () => {
|
|
95
|
-
this.connected = true;
|
|
96
|
-
console.log('[a2a] Connected to GopherHole Hub via SDK');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
this.gopherhole.on('disconnect', (reason) => {
|
|
100
|
-
this.connected = false;
|
|
101
|
-
console.log(`[a2a] Disconnected from GopherHole: ${reason}`);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
this.gopherhole.on('reconnecting', ({ attempt, delayMs }) => {
|
|
105
|
-
console.log(`[a2a] Reconnecting to GopherHole (attempt ${attempt}, waiting ${delayMs}ms)...`);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
this.gopherhole.on('error', (error) => {
|
|
109
|
-
console.error('[a2a] GopherHole SDK error:', error.message);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
this.gopherhole.on('message', (message: Message) => {
|
|
113
|
-
this.handleIncomingMessage(message);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// Handle system messages (rate limits, budget alerts, announcements)
|
|
117
|
-
this.gopherhole.on('system', (message) => {
|
|
118
|
-
this.handleSystemMessage(message);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Connect
|
|
122
|
-
try {
|
|
123
|
-
await this.gopherhole.connect();
|
|
124
|
-
console.log(`[a2a] GopherHole SDK connected, agent ID: ${this.gopherhole.id}`);
|
|
125
|
-
} catch (err) {
|
|
126
|
-
console.error('[a2a] Failed to connect to GopherHole:', (err as Error).message);
|
|
127
|
-
throw err;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
private handleIncomingMessage(message: Message): void {
|
|
132
|
-
if (!this.messageHandler) return;
|
|
133
|
-
|
|
134
|
-
console.log(`[a2a] Received message from ${message.from}, taskId=${message.taskId}`);
|
|
135
|
-
console.log(`[a2a] Raw message payload:`, JSON.stringify(message.payload, null, 2).slice(0, 500));
|
|
136
|
-
|
|
137
|
-
// Validate taskId - critical for response routing
|
|
138
|
-
if (!message.taskId) {
|
|
139
|
-
console.error(`[a2a] WARNING: No taskId in incoming message! Response relay will fail.`);
|
|
140
|
-
console.error(`[a2a] Full message object:`, JSON.stringify(message, null, 2));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Convert SDK message to our A2AMessage format
|
|
144
|
-
const a2aMsg: A2AMessage = {
|
|
145
|
-
type: 'message',
|
|
146
|
-
taskId: message.taskId || `gph-${Date.now()}`,
|
|
147
|
-
from: message.from,
|
|
148
|
-
content: {
|
|
149
|
-
parts: message.payload.parts.map(p => ({
|
|
150
|
-
kind: p.kind,
|
|
151
|
-
text: p.text,
|
|
152
|
-
data: p.data,
|
|
153
|
-
mimeType: p.mimeType,
|
|
154
|
-
})),
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
console.log(`[a2a] Dispatching to messageHandler with taskId=${a2aMsg.taskId}`);
|
|
159
|
-
|
|
160
|
-
this.messageHandler('gopherhole', a2aMsg).catch((err) => {
|
|
161
|
-
console.error('[a2a] Error handling incoming message:', err);
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Handle system messages from GopherHole Hub
|
|
167
|
-
* These include spending alerts, account alerts, notices, maintenance, etc.
|
|
168
|
-
*/
|
|
169
|
-
private handleSystemMessage(message: Message): void {
|
|
170
|
-
const kind = message.metadata?.kind;
|
|
171
|
-
const text = message.payload.parts.find(p => p.kind === 'text')?.text || '';
|
|
172
|
-
const data = message.metadata?.data;
|
|
173
|
-
|
|
174
|
-
// Log all system messages
|
|
175
|
-
console.log(`[a2a] System message (${kind || 'unknown'}): ${text}`);
|
|
176
|
-
|
|
177
|
-
// Handle specific message kinds
|
|
178
|
-
switch (kind) {
|
|
179
|
-
case 'spending_alert':
|
|
180
|
-
console.warn(`[a2a] 💰 Spending alert: ${text}`);
|
|
181
|
-
if (data) {
|
|
182
|
-
console.warn(`[a2a] Spending data:`, JSON.stringify(data));
|
|
183
|
-
}
|
|
184
|
-
break;
|
|
185
|
-
|
|
186
|
-
case 'account_alert':
|
|
187
|
-
console.warn(`[a2a] ⚠️ Account alert: ${text}`);
|
|
188
|
-
break;
|
|
189
|
-
|
|
190
|
-
case 'system_notice':
|
|
191
|
-
console.log(`[a2a] 📢 System notice: ${text}`);
|
|
192
|
-
break;
|
|
193
|
-
|
|
194
|
-
case 'maintenance':
|
|
195
|
-
console.warn(`[a2a] 🔧 Maintenance notice: ${text}`);
|
|
196
|
-
break;
|
|
197
|
-
|
|
198
|
-
default:
|
|
199
|
-
// Log but don't warn for unknown types
|
|
200
|
-
if (kind) {
|
|
201
|
-
console.log(`[a2a] System message "${kind}": ${text}`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async stop(): Promise<void> {
|
|
207
|
-
if (this.gopherhole) {
|
|
208
|
-
this.gopherhole.disconnect();
|
|
209
|
-
this.gopherhole = null;
|
|
210
|
-
}
|
|
211
|
-
this.connected = false;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Send a message to another agent via GopherHole and wait for response
|
|
216
|
-
*/
|
|
217
|
-
async sendMessage(
|
|
218
|
-
targetAgentId: string,
|
|
219
|
-
text: string,
|
|
220
|
-
_contextId?: string
|
|
221
|
-
): Promise<A2AResponse> {
|
|
222
|
-
return this.sendPartsViaGopherHole(targetAgentId, [{ kind: 'text', text }]);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Send a multi-part message via GopherHole hub
|
|
227
|
-
* Supports text, images, and other MIME types
|
|
228
|
-
*/
|
|
229
|
-
async sendPartsViaGopherHole(
|
|
230
|
-
targetAgentId: string,
|
|
231
|
-
parts: Array<{ kind: string; text?: string; data?: string; mimeType?: string }>,
|
|
232
|
-
contextId?: string
|
|
233
|
-
): Promise<A2AResponse> {
|
|
234
|
-
if (!this.gopherhole || !this.connected) {
|
|
235
|
-
throw new Error('GopherHole not connected');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
console.log(`[a2a] Sending to ${targetAgentId} via SDK, parts=${parts.length}`);
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
// Use SDK's send method with polling for completion
|
|
242
|
-
const task = await this.gopherhole.send(
|
|
243
|
-
targetAgentId,
|
|
244
|
-
{
|
|
245
|
-
role: 'agent',
|
|
246
|
-
parts: parts.map(p => ({
|
|
247
|
-
kind: p.kind as 'text' | 'file' | 'data',
|
|
248
|
-
text: p.text,
|
|
249
|
-
data: p.data,
|
|
250
|
-
mimeType: p.mimeType,
|
|
251
|
-
})),
|
|
252
|
-
},
|
|
253
|
-
{ contextId }
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
// Wait for task completion
|
|
257
|
-
const completedTask = await this.gopherhole.waitForTask(task.id, {
|
|
258
|
-
pollIntervalMs: 1000,
|
|
259
|
-
maxWaitMs: this.config.requestTimeoutMs ?? 180000,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
if (completedTask.status.state === 'failed') {
|
|
263
|
-
throw new Error(completedTask.status.message ?? 'Task failed');
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Extract response text using SDK helper
|
|
267
|
-
const responseText = getTaskResponseText(completedTask);
|
|
268
|
-
|
|
269
|
-
console.log(`[a2a] Got response from ${targetAgentId}: ${responseText.slice(0, 100)}...`);
|
|
270
|
-
|
|
271
|
-
return {
|
|
272
|
-
text: responseText,
|
|
273
|
-
status: completedTask.status.state,
|
|
274
|
-
from: targetAgentId,
|
|
275
|
-
};
|
|
276
|
-
} catch (err) {
|
|
277
|
-
console.error(`[a2a] Failed to send to ${targetAgentId}:`, (err as Error).message);
|
|
278
|
-
throw err;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Send a response to an incoming message via GopherHole
|
|
284
|
-
* Uses SDK's respond() method to complete the original task
|
|
285
|
-
*/
|
|
286
|
-
sendResponseViaGopherHole(
|
|
287
|
-
_targetAgentId: string,
|
|
288
|
-
taskId: string,
|
|
289
|
-
text: string,
|
|
290
|
-
_contextId?: string
|
|
291
|
-
): void {
|
|
292
|
-
if (!this.gopherhole || !this.connected) {
|
|
293
|
-
console.error('[a2a] Cannot send response - GopherHole not connected');
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Validate taskId
|
|
298
|
-
if (!taskId) {
|
|
299
|
-
console.error('[a2a] Cannot respond - taskId is null/undefined!');
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (taskId.startsWith('gph-')) {
|
|
304
|
-
console.error(`[a2a] Cannot respond - taskId "${taskId}" is a fallback ID (not a real task). Response will be lost!`);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
console.log(`[a2a] Responding to taskId=${taskId}: "${text.slice(0, 200)}..." (total ${text.length} chars)`);
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
// Use SDK's respond method to complete the task
|
|
312
|
-
this.gopherhole.respond(taskId, text);
|
|
313
|
-
console.log(`[a2a] respond() called successfully for taskId=${taskId}`);
|
|
314
|
-
} catch (err) {
|
|
315
|
-
console.error('[a2a] Failed to send response:', (err as Error).message);
|
|
316
|
-
console.error('[a2a] Error details:', err);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Legacy alias for sendPartsViaGopherHole with text-only
|
|
322
|
-
*/
|
|
323
|
-
async sendViaGopherHole(
|
|
324
|
-
targetAgentId: string,
|
|
325
|
-
text: string,
|
|
326
|
-
contextId?: string
|
|
327
|
-
): Promise<A2AResponse> {
|
|
328
|
-
return this.sendPartsViaGopherHole(targetAgentId, [{ kind: 'text', text }], contextId);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Legacy sendResponse (routes to GopherHole)
|
|
333
|
-
*/
|
|
334
|
-
sendResponse(agentId: string, taskId: string, text: string, contextId?: string): void {
|
|
335
|
-
this.sendResponseViaGopherHole(agentId, taskId, text, contextId);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Check if GopherHole is connected
|
|
340
|
-
*/
|
|
341
|
-
isGopherHoleConnected(): boolean {
|
|
342
|
-
return this.connected && this.gopherhole?.connected === true;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Make an A2A JSON-RPC call
|
|
347
|
-
*/
|
|
348
|
-
private async a2aRpc<T>(method: string, params?: Record<string, unknown>): Promise<T | null> {
|
|
349
|
-
if (!this.config.apiKey) {
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const hubUrl = this.config.bridgeUrl || 'wss://hub.gopherhole.ai/ws';
|
|
354
|
-
const apiBase = hubUrl.replace('wss://', 'https://').replace('/ws', '');
|
|
355
|
-
|
|
356
|
-
try {
|
|
357
|
-
const response = await fetch(`${apiBase}/a2a`, {
|
|
358
|
-
method: 'POST',
|
|
359
|
-
headers: {
|
|
360
|
-
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
361
|
-
'Content-Type': 'application/json',
|
|
362
|
-
},
|
|
363
|
-
body: JSON.stringify({
|
|
364
|
-
jsonrpc: '2.0',
|
|
365
|
-
method,
|
|
366
|
-
params: params || {},
|
|
367
|
-
id: Date.now(),
|
|
368
|
-
}),
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
if (!response.ok) {
|
|
372
|
-
console.error(`[a2a] RPC failed: ${response.status}`);
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const data = await response.json() as { result?: T; error?: { message: string } };
|
|
377
|
-
if (data.error) {
|
|
378
|
-
console.error(`[a2a] RPC error: ${data.error.message}`);
|
|
379
|
-
return null;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return data.result || null;
|
|
383
|
-
} catch (err) {
|
|
384
|
-
console.error('[a2a] RPC error:', (err as Error).message);
|
|
385
|
-
return null;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* List available agents from GopherHole (agents you have access to)
|
|
391
|
-
*/
|
|
392
|
-
async listAvailableAgents(options?: { query?: string; public?: boolean }): Promise<Array<{
|
|
393
|
-
id: string;
|
|
394
|
-
name: string;
|
|
395
|
-
description?: string;
|
|
396
|
-
verified?: boolean;
|
|
397
|
-
accessType: 'same-tenant' | 'public' | 'granted';
|
|
398
|
-
}>> {
|
|
399
|
-
const result = await this.a2aRpc<{ agents: Array<{
|
|
400
|
-
id: string;
|
|
401
|
-
name: string;
|
|
402
|
-
description?: string;
|
|
403
|
-
verified?: boolean;
|
|
404
|
-
accessType: string;
|
|
405
|
-
}> }>('x-gopherhole/agents.available', options);
|
|
406
|
-
|
|
407
|
-
if (!result?.agents) {
|
|
408
|
-
return [];
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
return result.agents.map(a => ({
|
|
412
|
-
id: a.id,
|
|
413
|
-
name: a.name,
|
|
414
|
-
description: a.description,
|
|
415
|
-
verified: a.verified,
|
|
416
|
-
accessType: a.accessType as 'same-tenant' | 'public' | 'granted',
|
|
417
|
-
}));
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Discover public agents in the marketplace
|
|
422
|
-
*/
|
|
423
|
-
async discoverAgents(options?: {
|
|
424
|
-
query?: string;
|
|
425
|
-
category?: string;
|
|
426
|
-
tag?: string;
|
|
427
|
-
skillTag?: string;
|
|
428
|
-
contentMode?: string;
|
|
429
|
-
sort?: string;
|
|
430
|
-
owner?: string; // Filter by organization/tenant name
|
|
431
|
-
verified?: boolean; // Only show agents from verified organizations
|
|
432
|
-
limit?: number;
|
|
433
|
-
offset?: number;
|
|
434
|
-
scope?: string;
|
|
435
|
-
}): Promise<Array<{
|
|
436
|
-
id: string;
|
|
437
|
-
name: string;
|
|
438
|
-
description?: string;
|
|
439
|
-
verified?: boolean;
|
|
440
|
-
tenantName?: string;
|
|
441
|
-
avgRating?: number;
|
|
442
|
-
}>> {
|
|
443
|
-
const result = await this.a2aRpc<{ agents: Array<{
|
|
444
|
-
id: string;
|
|
445
|
-
name: string;
|
|
446
|
-
description?: string;
|
|
447
|
-
verified?: boolean;
|
|
448
|
-
tenantName?: string;
|
|
449
|
-
avgRating?: number;
|
|
450
|
-
}> }>('x-gopherhole/agents.discover', options);
|
|
451
|
-
|
|
452
|
-
return result?.agents || [];
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Discover agents near a geographic location
|
|
457
|
-
*/
|
|
458
|
-
async discoverNearby(options: {
|
|
459
|
-
lat: number;
|
|
460
|
-
lng: number;
|
|
461
|
-
radius?: number;
|
|
462
|
-
tag?: string;
|
|
463
|
-
category?: string;
|
|
464
|
-
limit?: number;
|
|
465
|
-
offset?: number;
|
|
466
|
-
}): Promise<Array<{
|
|
467
|
-
id: string;
|
|
468
|
-
name: string;
|
|
469
|
-
description?: string;
|
|
470
|
-
verified?: boolean;
|
|
471
|
-
tenantName?: string;
|
|
472
|
-
avgRating?: number;
|
|
473
|
-
location?: {
|
|
474
|
-
name: string;
|
|
475
|
-
lat: number;
|
|
476
|
-
lng: number;
|
|
477
|
-
country: string;
|
|
478
|
-
};
|
|
479
|
-
distance?: number;
|
|
480
|
-
}>> {
|
|
481
|
-
const result = await this.a2aRpc<{ agents: Array<{
|
|
482
|
-
id: string;
|
|
483
|
-
name: string;
|
|
484
|
-
description?: string;
|
|
485
|
-
verified?: boolean;
|
|
486
|
-
tenantName?: string;
|
|
487
|
-
avgRating?: number;
|
|
488
|
-
location?: {
|
|
489
|
-
name: string;
|
|
490
|
-
lat: number;
|
|
491
|
-
lng: number;
|
|
492
|
-
country: string;
|
|
493
|
-
};
|
|
494
|
-
distance?: number;
|
|
495
|
-
}> }>('x-gopherhole/agents.discover.nearby', options);
|
|
496
|
-
|
|
497
|
-
return result?.agents || [];
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* List connection status (for backward compatibility)
|
|
502
|
-
*/
|
|
503
|
-
listAgents(): Array<{ id: string; name: string; connected: boolean }> {
|
|
504
|
-
const agents: Array<{ id: string; name: string; connected: boolean }> = [];
|
|
505
|
-
|
|
506
|
-
if (this.gopherhole) {
|
|
507
|
-
agents.push({
|
|
508
|
-
id: 'gopherhole',
|
|
509
|
-
name: 'GopherHole Hub',
|
|
510
|
-
connected: this.connected,
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
return agents;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Check if an agent is connected
|
|
519
|
-
*/
|
|
520
|
-
isConnected(agentId: string): boolean {
|
|
521
|
-
if (agentId === 'gopherhole') {
|
|
522
|
-
return this.isGopherHoleConnected();
|
|
523
|
-
}
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* Get the underlying GopherHole SDK instance (for advanced usage)
|
|
529
|
-
*/
|
|
530
|
-
getSDK(): GopherHole | null {
|
|
531
|
-
return this.gopherhole;
|
|
532
|
-
}
|
|
533
|
-
}
|