@gopherhole/sdk 0.1.0 → 0.1.2

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.mjs ADDED
@@ -0,0 +1,440 @@
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
+ if (options?.scope) params.set("scope", options.scope);
300
+ const response = await fetch(`${this.apiUrl}/api/discover/agents?${params}`, {
301
+ headers: {
302
+ "Authorization": `Bearer ${this.apiKey}`
303
+ }
304
+ });
305
+ return response.json();
306
+ }
307
+ /**
308
+ * Discover all agents in your tenant (no limit)
309
+ */
310
+ async discoverTenantAgents() {
311
+ return this.discover({ scope: "tenant" });
312
+ }
313
+ /**
314
+ * Search agents with fuzzy matching on description
315
+ */
316
+ async searchAgents(query, options) {
317
+ return this.discover({ ...options, query });
318
+ }
319
+ /**
320
+ * Find agents by category
321
+ */
322
+ async findByCategory(category, options) {
323
+ return this.discover({ ...options, category });
324
+ }
325
+ /**
326
+ * Find agents by tag
327
+ */
328
+ async findByTag(tag, options) {
329
+ return this.discover({ ...options, tag });
330
+ }
331
+ /**
332
+ * Find agents by skill tag (searches within agent skills)
333
+ */
334
+ async findBySkillTag(skillTag, options) {
335
+ return this.discover({ ...options, skillTag });
336
+ }
337
+ /**
338
+ * Find agents that support a specific input/output mode
339
+ */
340
+ async findByContentMode(mode, options) {
341
+ return this.discover({ ...options, contentMode: mode });
342
+ }
343
+ /**
344
+ * Get top-rated agents
345
+ */
346
+ async getTopRated(limit = 10) {
347
+ return this.discover({ sort: "rating", limit });
348
+ }
349
+ /**
350
+ * Get most popular agents (by usage)
351
+ */
352
+ async getPopular(limit = 10) {
353
+ return this.discover({ sort: "popular", limit });
354
+ }
355
+ /**
356
+ * Get featured/curated agents
357
+ */
358
+ async getFeatured() {
359
+ const response = await fetch(`${this.apiUrl}/api/discover/featured`);
360
+ return response.json();
361
+ }
362
+ /**
363
+ * Get available categories
364
+ */
365
+ async getCategories() {
366
+ const response = await fetch(`${this.apiUrl}/api/discover/categories`);
367
+ return response.json();
368
+ }
369
+ /**
370
+ * Get detailed info about a public agent
371
+ */
372
+ async getAgentInfo(agentId) {
373
+ const response = await fetch(`${this.apiUrl}/api/discover/agents/${agentId}`);
374
+ if (!response.ok) {
375
+ throw new Error("Agent not found");
376
+ }
377
+ return response.json();
378
+ }
379
+ /**
380
+ * Rate an agent (requires authentication)
381
+ */
382
+ async rateAgent(agentId, rating, review) {
383
+ const response = await fetch(`${this.apiUrl}/api/discover/agents/${agentId}/rate`, {
384
+ method: "POST",
385
+ headers: {
386
+ "Content-Type": "application/json",
387
+ "Authorization": `Bearer ${this.apiKey}`
388
+ },
389
+ body: JSON.stringify({ rating, review })
390
+ });
391
+ if (!response.ok) {
392
+ const error = await response.json();
393
+ throw new Error(error.error || "Failed to rate agent");
394
+ }
395
+ return response.json();
396
+ }
397
+ /**
398
+ * Get best agent for a task using smart matching
399
+ * Searches by query and returns the top-rated match
400
+ */
401
+ async findBestAgent(query, options) {
402
+ const result = await this.discover({
403
+ query,
404
+ category: options?.category,
405
+ sort: "rating",
406
+ limit: 10
407
+ });
408
+ const agents = result.agents.filter((agent) => {
409
+ if (options?.minRating && agent.avgRating < options.minRating) return false;
410
+ if (options?.pricing === "free" && agent.pricing !== "free") return false;
411
+ if (options?.pricing === "paid" && agent.pricing === "free") return false;
412
+ return true;
413
+ });
414
+ return agents[0] || null;
415
+ }
416
+ /**
417
+ * Find agents similar to a given agent
418
+ */
419
+ async findSimilar(agentId, limit = 5) {
420
+ const info = await this.getAgentInfo(agentId);
421
+ const agent = info.agent;
422
+ if (agent.category) {
423
+ const result = await this.discover({
424
+ category: agent.category,
425
+ sort: "rating",
426
+ limit: limit + 1
427
+ // +1 to exclude self
428
+ });
429
+ result.agents = result.agents.filter((a) => a.id !== agentId).slice(0, limit);
430
+ return result;
431
+ }
432
+ return this.getTopRated(limit);
433
+ }
434
+ };
435
+ var index_default = GopherHole;
436
+ export {
437
+ GopherHole,
438
+ JsonRpcErrorCodes,
439
+ index_default as default
440
+ };
package/package.json CHANGED
@@ -1,20 +1,52 @@
1
1
  {
2
2
  "name": "@gopherhole/sdk",
3
- "version": "0.1.0",
4
- "description": "GopherHole Agent Hub SDK",
5
- "type": "module",
3
+ "version": "0.1.2",
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": "tsc",
10
- "dev": "tsc --watch"
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
- "typescript": "^5.3.3"
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
  }