@gopherhole/sdk 0.1.0 → 0.1.1
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/README.md +140 -73
- package/dist/index.d.mts +499 -0
- package/dist/index.d.ts +472 -40
- package/dist/index.js +442 -197
- package/dist/index.mjs +433 -0
- package/package.json +39 -7
- package/src/index.ts +607 -204
- package/src/types.ts +232 -0
- package/tsconfig.json +9 -6
- package/listen-marketclaw.ts +0 -26
- package/send-from-nova.ts +0 -28
- package/test.ts +0 -86
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
import { EventEmitter } from "eventemitter3";
|
|
10
|
+
|
|
11
|
+
// src/types.ts
|
|
12
|
+
var JsonRpcErrorCodes = {
|
|
13
|
+
ParseError: -32700,
|
|
14
|
+
InvalidRequest: -32600,
|
|
15
|
+
MethodNotFound: -32601,
|
|
16
|
+
InvalidParams: -32602,
|
|
17
|
+
InternalError: -32603,
|
|
18
|
+
// A2A-specific errors
|
|
19
|
+
TaskNotFound: -32001,
|
|
20
|
+
TaskNotCancelable: -32002,
|
|
21
|
+
PushNotificationNotSupported: -32003,
|
|
22
|
+
UnsupportedOperation: -32004,
|
|
23
|
+
ContentTypeNotSupported: -32005,
|
|
24
|
+
InvalidAgentCard: -32006
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/index.ts
|
|
28
|
+
var DEFAULT_HUB_URL = "wss://gopherhole.helixdata.workers.dev/ws";
|
|
29
|
+
var GopherHole = class extends EventEmitter {
|
|
30
|
+
constructor(apiKeyOrOptions) {
|
|
31
|
+
super();
|
|
32
|
+
this.ws = null;
|
|
33
|
+
this.reconnectAttempts = 0;
|
|
34
|
+
this.reconnectTimer = null;
|
|
35
|
+
this.pingInterval = null;
|
|
36
|
+
this.agentId = null;
|
|
37
|
+
this.agentCard = null;
|
|
38
|
+
const options = typeof apiKeyOrOptions === "string" ? { apiKey: apiKeyOrOptions } : apiKeyOrOptions;
|
|
39
|
+
this.apiKey = options.apiKey;
|
|
40
|
+
this.hubUrl = options.hubUrl || DEFAULT_HUB_URL;
|
|
41
|
+
this.apiUrl = this.hubUrl.replace("/ws", "").replace("wss://", "https://").replace("ws://", "http://");
|
|
42
|
+
this.agentCard = options.agentCard || null;
|
|
43
|
+
this.autoReconnect = options.autoReconnect ?? true;
|
|
44
|
+
this.reconnectDelay = options.reconnectDelay ?? 1e3;
|
|
45
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Update agent card (sends to hub if connected)
|
|
49
|
+
*/
|
|
50
|
+
async updateCard(card) {
|
|
51
|
+
this.agentCard = card;
|
|
52
|
+
if (this.ws?.readyState === 1) {
|
|
53
|
+
this.ws.send(JSON.stringify({ type: "update_card", agentCard: card }));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Connect to the GopherHole hub via WebSocket
|
|
58
|
+
*/
|
|
59
|
+
async connect() {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const WS = typeof WebSocket !== "undefined" ? WebSocket : __require("ws");
|
|
62
|
+
const ws = new WS(this.hubUrl, {
|
|
63
|
+
headers: {
|
|
64
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
this.ws = ws;
|
|
68
|
+
ws.onopen = () => {
|
|
69
|
+
this.reconnectAttempts = 0;
|
|
70
|
+
this.startPing();
|
|
71
|
+
this.emit("connect");
|
|
72
|
+
resolve();
|
|
73
|
+
};
|
|
74
|
+
ws.onclose = (event) => {
|
|
75
|
+
this.stopPing();
|
|
76
|
+
const reason = event.reason || "Connection closed";
|
|
77
|
+
this.emit("disconnect", reason);
|
|
78
|
+
if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
79
|
+
this.scheduleReconnect();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
ws.onerror = () => {
|
|
83
|
+
const error = new Error("WebSocket error");
|
|
84
|
+
this.emit("error", error);
|
|
85
|
+
reject(error);
|
|
86
|
+
};
|
|
87
|
+
ws.onmessage = (event) => {
|
|
88
|
+
try {
|
|
89
|
+
const data = JSON.parse(event.data.toString());
|
|
90
|
+
this.handleMessage(data);
|
|
91
|
+
} catch {
|
|
92
|
+
this.emit("error", new Error("Failed to parse message"));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Disconnect from the hub
|
|
99
|
+
*/
|
|
100
|
+
disconnect() {
|
|
101
|
+
this.autoReconnect = false;
|
|
102
|
+
this.stopPing();
|
|
103
|
+
if (this.reconnectTimer) {
|
|
104
|
+
clearTimeout(this.reconnectTimer);
|
|
105
|
+
this.reconnectTimer = null;
|
|
106
|
+
}
|
|
107
|
+
if (this.ws) {
|
|
108
|
+
this.ws.close();
|
|
109
|
+
this.ws = null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Send a message to another agent
|
|
114
|
+
*/
|
|
115
|
+
async send(toAgentId, payload, options) {
|
|
116
|
+
const response = await this.rpc("message/send", {
|
|
117
|
+
message: payload,
|
|
118
|
+
configuration: {
|
|
119
|
+
agentId: toAgentId,
|
|
120
|
+
...options
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return response;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Send a text message to another agent
|
|
127
|
+
*/
|
|
128
|
+
async sendText(toAgentId, text, options) {
|
|
129
|
+
return this.send(toAgentId, {
|
|
130
|
+
role: "agent",
|
|
131
|
+
parts: [{ kind: "text", text }]
|
|
132
|
+
}, options);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get a task by ID
|
|
136
|
+
*/
|
|
137
|
+
async getTask(taskId, historyLength) {
|
|
138
|
+
const response = await this.rpc("tasks/get", {
|
|
139
|
+
id: taskId,
|
|
140
|
+
historyLength
|
|
141
|
+
});
|
|
142
|
+
return response;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* List tasks
|
|
146
|
+
*/
|
|
147
|
+
async listTasks(options) {
|
|
148
|
+
const response = await this.rpc("tasks/list", options || {});
|
|
149
|
+
return response;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Cancel a task
|
|
153
|
+
*/
|
|
154
|
+
async cancelTask(taskId) {
|
|
155
|
+
const response = await this.rpc("tasks/cancel", { id: taskId });
|
|
156
|
+
return response;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Reply to a message/task (sends back to the original caller)
|
|
160
|
+
*/
|
|
161
|
+
async reply(taskId, payload, toAgentId) {
|
|
162
|
+
if (!toAgentId) {
|
|
163
|
+
const task = await this.getTask(taskId);
|
|
164
|
+
const response2 = await this.rpc("message/send", {
|
|
165
|
+
message: payload,
|
|
166
|
+
configuration: {
|
|
167
|
+
contextId: task.contextId
|
|
168
|
+
// Server needs to handle replies via context routing
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return response2;
|
|
172
|
+
}
|
|
173
|
+
const response = await this.rpc("message/send", {
|
|
174
|
+
message: payload,
|
|
175
|
+
configuration: {
|
|
176
|
+
agentId: toAgentId
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return response;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Reply with text
|
|
183
|
+
*/
|
|
184
|
+
async replyText(taskId, text) {
|
|
185
|
+
return this.reply(taskId, {
|
|
186
|
+
role: "agent",
|
|
187
|
+
parts: [{ kind: "text", text }]
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Make a JSON-RPC call to the A2A endpoint
|
|
192
|
+
*/
|
|
193
|
+
async rpc(method, params) {
|
|
194
|
+
const response = await fetch(`${this.apiUrl}/a2a`, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: {
|
|
197
|
+
"Content-Type": "application/json",
|
|
198
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
199
|
+
},
|
|
200
|
+
body: JSON.stringify({
|
|
201
|
+
jsonrpc: "2.0",
|
|
202
|
+
method,
|
|
203
|
+
params,
|
|
204
|
+
id: Date.now()
|
|
205
|
+
})
|
|
206
|
+
});
|
|
207
|
+
const data = await response.json();
|
|
208
|
+
if (data.error) {
|
|
209
|
+
throw new Error(data.error.message || "RPC error");
|
|
210
|
+
}
|
|
211
|
+
return data.result;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Handle incoming WebSocket messages
|
|
215
|
+
*/
|
|
216
|
+
handleMessage(data) {
|
|
217
|
+
if (data.type === "message") {
|
|
218
|
+
this.emit("message", {
|
|
219
|
+
from: data.from,
|
|
220
|
+
taskId: data.taskId,
|
|
221
|
+
payload: data.payload,
|
|
222
|
+
timestamp: data.timestamp || Date.now()
|
|
223
|
+
});
|
|
224
|
+
} else if (data.type === "task_update") {
|
|
225
|
+
this.emit("taskUpdate", data.task);
|
|
226
|
+
} else if (data.type === "pong") {
|
|
227
|
+
} else if (data.type === "welcome") {
|
|
228
|
+
this.agentId = data.agentId;
|
|
229
|
+
if (this.agentCard && this.ws?.readyState === 1) {
|
|
230
|
+
this.ws.send(JSON.stringify({ type: "update_card", agentCard: this.agentCard }));
|
|
231
|
+
}
|
|
232
|
+
} else if (data.type === "card_updated") {
|
|
233
|
+
} else if (data.type === "warning") {
|
|
234
|
+
console.warn("GopherHole warning:", data.message);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Start ping interval
|
|
239
|
+
*/
|
|
240
|
+
startPing() {
|
|
241
|
+
this.pingInterval = setInterval(() => {
|
|
242
|
+
if (this.ws?.readyState === 1) {
|
|
243
|
+
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
244
|
+
}
|
|
245
|
+
}, 3e4);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Stop ping interval
|
|
249
|
+
*/
|
|
250
|
+
stopPing() {
|
|
251
|
+
if (this.pingInterval) {
|
|
252
|
+
clearInterval(this.pingInterval);
|
|
253
|
+
this.pingInterval = null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Schedule reconnection
|
|
258
|
+
*/
|
|
259
|
+
scheduleReconnect() {
|
|
260
|
+
if (this.reconnectTimer) return;
|
|
261
|
+
this.reconnectAttempts++;
|
|
262
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
263
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
264
|
+
this.reconnectTimer = null;
|
|
265
|
+
try {
|
|
266
|
+
await this.connect();
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}, delay);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get connection state
|
|
273
|
+
*/
|
|
274
|
+
get connected() {
|
|
275
|
+
return this.ws?.readyState === 1;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get the agent ID (available after connect)
|
|
279
|
+
*/
|
|
280
|
+
get id() {
|
|
281
|
+
return this.agentId;
|
|
282
|
+
}
|
|
283
|
+
// ============================================================
|
|
284
|
+
// DISCOVERY METHODS
|
|
285
|
+
// ============================================================
|
|
286
|
+
/**
|
|
287
|
+
* Discover public agents with comprehensive search
|
|
288
|
+
*/
|
|
289
|
+
async discover(options) {
|
|
290
|
+
const params = new URLSearchParams();
|
|
291
|
+
if (options?.query) params.set("q", options.query);
|
|
292
|
+
if (options?.category) params.set("category", options.category);
|
|
293
|
+
if (options?.tag) params.set("tag", options.tag);
|
|
294
|
+
if (options?.skillTag) params.set("skillTag", options.skillTag);
|
|
295
|
+
if (options?.contentMode) params.set("contentMode", options.contentMode);
|
|
296
|
+
if (options?.sort) params.set("sort", options.sort);
|
|
297
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
298
|
+
if (options?.offset) params.set("offset", String(options.offset));
|
|
299
|
+
const response = await fetch(`${this.apiUrl}/api/discover/agents?${params}`, {
|
|
300
|
+
headers: {
|
|
301
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return response.json();
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Search agents with fuzzy matching on description
|
|
308
|
+
*/
|
|
309
|
+
async searchAgents(query, options) {
|
|
310
|
+
return this.discover({ ...options, query });
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Find agents by category
|
|
314
|
+
*/
|
|
315
|
+
async findByCategory(category, options) {
|
|
316
|
+
return this.discover({ ...options, category });
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Find agents by tag
|
|
320
|
+
*/
|
|
321
|
+
async findByTag(tag, options) {
|
|
322
|
+
return this.discover({ ...options, tag });
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Find agents by skill tag (searches within agent skills)
|
|
326
|
+
*/
|
|
327
|
+
async findBySkillTag(skillTag, options) {
|
|
328
|
+
return this.discover({ ...options, skillTag });
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Find agents that support a specific input/output mode
|
|
332
|
+
*/
|
|
333
|
+
async findByContentMode(mode, options) {
|
|
334
|
+
return this.discover({ ...options, contentMode: mode });
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Get top-rated agents
|
|
338
|
+
*/
|
|
339
|
+
async getTopRated(limit = 10) {
|
|
340
|
+
return this.discover({ sort: "rating", limit });
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Get most popular agents (by usage)
|
|
344
|
+
*/
|
|
345
|
+
async getPopular(limit = 10) {
|
|
346
|
+
return this.discover({ sort: "popular", limit });
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get featured/curated agents
|
|
350
|
+
*/
|
|
351
|
+
async getFeatured() {
|
|
352
|
+
const response = await fetch(`${this.apiUrl}/api/discover/featured`);
|
|
353
|
+
return response.json();
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Get available categories
|
|
357
|
+
*/
|
|
358
|
+
async getCategories() {
|
|
359
|
+
const response = await fetch(`${this.apiUrl}/api/discover/categories`);
|
|
360
|
+
return response.json();
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get detailed info about a public agent
|
|
364
|
+
*/
|
|
365
|
+
async getAgentInfo(agentId) {
|
|
366
|
+
const response = await fetch(`${this.apiUrl}/api/discover/agents/${agentId}`);
|
|
367
|
+
if (!response.ok) {
|
|
368
|
+
throw new Error("Agent not found");
|
|
369
|
+
}
|
|
370
|
+
return response.json();
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Rate an agent (requires authentication)
|
|
374
|
+
*/
|
|
375
|
+
async rateAgent(agentId, rating, review) {
|
|
376
|
+
const response = await fetch(`${this.apiUrl}/api/discover/agents/${agentId}/rate`, {
|
|
377
|
+
method: "POST",
|
|
378
|
+
headers: {
|
|
379
|
+
"Content-Type": "application/json",
|
|
380
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
381
|
+
},
|
|
382
|
+
body: JSON.stringify({ rating, review })
|
|
383
|
+
});
|
|
384
|
+
if (!response.ok) {
|
|
385
|
+
const error = await response.json();
|
|
386
|
+
throw new Error(error.error || "Failed to rate agent");
|
|
387
|
+
}
|
|
388
|
+
return response.json();
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Get best agent for a task using smart matching
|
|
392
|
+
* Searches by query and returns the top-rated match
|
|
393
|
+
*/
|
|
394
|
+
async findBestAgent(query, options) {
|
|
395
|
+
const result = await this.discover({
|
|
396
|
+
query,
|
|
397
|
+
category: options?.category,
|
|
398
|
+
sort: "rating",
|
|
399
|
+
limit: 10
|
|
400
|
+
});
|
|
401
|
+
const agents = result.agents.filter((agent) => {
|
|
402
|
+
if (options?.minRating && agent.avgRating < options.minRating) return false;
|
|
403
|
+
if (options?.pricing === "free" && agent.pricing !== "free") return false;
|
|
404
|
+
if (options?.pricing === "paid" && agent.pricing === "free") return false;
|
|
405
|
+
return true;
|
|
406
|
+
});
|
|
407
|
+
return agents[0] || null;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Find agents similar to a given agent
|
|
411
|
+
*/
|
|
412
|
+
async findSimilar(agentId, limit = 5) {
|
|
413
|
+
const info = await this.getAgentInfo(agentId);
|
|
414
|
+
const agent = info.agent;
|
|
415
|
+
if (agent.category) {
|
|
416
|
+
const result = await this.discover({
|
|
417
|
+
category: agent.category,
|
|
418
|
+
sort: "rating",
|
|
419
|
+
limit: limit + 1
|
|
420
|
+
// +1 to exclude self
|
|
421
|
+
});
|
|
422
|
+
result.agents = result.agents.filter((a) => a.id !== agentId).slice(0, limit);
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
return this.getTopRated(limit);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
var index_default = GopherHole;
|
|
429
|
+
export {
|
|
430
|
+
GopherHole,
|
|
431
|
+
JsonRpcErrorCodes,
|
|
432
|
+
index_default as default
|
|
433
|
+
};
|
package/package.json
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gopherhole/sdk",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "GopherHole
|
|
5
|
-
"type": "module",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "GopherHole SDK - Connect AI agents via the A2A protocol",
|
|
6
5
|
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
8
15
|
"scripts": {
|
|
9
|
-
"build": "
|
|
10
|
-
"dev": "
|
|
16
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
17
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"a2a",
|
|
22
|
+
"ai",
|
|
23
|
+
"agents",
|
|
24
|
+
"gopherhole",
|
|
25
|
+
"sdk",
|
|
26
|
+
"google-a2a"
|
|
27
|
+
],
|
|
28
|
+
"author": "GopherHole",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/gopherhole/sdk"
|
|
11
33
|
},
|
|
12
34
|
"dependencies": {
|
|
13
|
-
"ws": "^8.16.0"
|
|
35
|
+
"ws": "^8.16.0",
|
|
36
|
+
"eventemitter3": "^5.0.1"
|
|
14
37
|
},
|
|
15
38
|
"devDependencies": {
|
|
16
39
|
"@types/node": "^20.11.0",
|
|
17
40
|
"@types/ws": "^8.5.10",
|
|
18
|
-
"
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "^5.3.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"typescript": ">=4.7.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"typescript": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
19
51
|
}
|
|
20
52
|
}
|