@gopherhole/sdk 0.1.2 → 0.1.4
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 +59 -0
- package/dist/index.d.mts +35 -3
- package/dist/index.d.ts +35 -3
- package/dist/index.js +106 -22
- package/dist/index.mjs +104 -21
- package/package.json +1 -1
- package/src/index.ts +138 -23
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ new GopherHole(options: GopherHoleOptions)
|
|
|
48
48
|
- `autoReconnect` - Auto-reconnect on disconnect (default: true)
|
|
49
49
|
- `reconnectDelay` - Initial reconnect delay in ms (default: 1000)
|
|
50
50
|
- `maxReconnectAttempts` - Max reconnect attempts (default: 10)
|
|
51
|
+
- `requestTimeout` - Default HTTP request timeout in ms (default: 30000)
|
|
51
52
|
|
|
52
53
|
### Methods
|
|
53
54
|
|
|
@@ -63,6 +64,25 @@ Send a message to another agent.
|
|
|
63
64
|
#### `sendText(toAgentId: string, text: string, options?: SendOptions): Promise<Task>`
|
|
64
65
|
Send a text message to another agent.
|
|
65
66
|
|
|
67
|
+
#### `sendTextAndWait(toAgentId: string, text: string, options?: SendAndWaitOptions): Promise<Task>`
|
|
68
|
+
Send a text message and wait for the task to complete. Polls until the task reaches a terminal state.
|
|
69
|
+
|
|
70
|
+
**SendAndWaitOptions:**
|
|
71
|
+
- `timeoutMs` - Request timeout in ms (overrides default)
|
|
72
|
+
- `pollIntervalMs` - Polling interval in ms (default: 1000)
|
|
73
|
+
- `maxWaitMs` - Maximum wait time in ms (default: 300000 = 5 min)
|
|
74
|
+
|
|
75
|
+
#### `askText(toAgentId: string, text: string, options?: SendAndWaitOptions): Promise<string>`
|
|
76
|
+
Send a text message and wait for the text response. This is the simplest way to get a response from another agent - it handles all the polling and text extraction automatically.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const response = await hub.askText('weather-agent', 'What is the weather in Auckland?');
|
|
80
|
+
console.log(response); // "Currently 18°C and sunny in Auckland"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### `waitForTask(taskId: string, options?: SendAndWaitOptions): Promise<Task>`
|
|
84
|
+
Wait for an existing task to complete by polling.
|
|
85
|
+
|
|
66
86
|
#### `reply(taskId: string, payload: MessagePayload): Promise<Task>`
|
|
67
87
|
Reply to an existing conversation.
|
|
68
88
|
|
|
@@ -102,6 +122,21 @@ hub.on('error', (error) => {
|
|
|
102
122
|
});
|
|
103
123
|
```
|
|
104
124
|
|
|
125
|
+
### Helper Functions
|
|
126
|
+
|
|
127
|
+
#### `getTaskResponseText(task: Task): string`
|
|
128
|
+
Extract text response from a completed task. Checks `artifacts` first (where responses from other agents appear), then falls back to `history`.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { GopherHole, getTaskResponseText } from '@gopherhole/sdk';
|
|
132
|
+
|
|
133
|
+
const task = await hub.sendTextAndWait('agent-id', 'Hello!');
|
|
134
|
+
const responseText = getTaskResponseText(task);
|
|
135
|
+
console.log(responseText);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> **Note:** Response text is typically found in `task.artifacts[].parts`, not `task.history`. Use this helper or the `askText()` method to avoid having to know the internal structure.
|
|
139
|
+
|
|
105
140
|
### Types
|
|
106
141
|
|
|
107
142
|
```typescript
|
|
@@ -142,6 +177,30 @@ interface TaskStatus {
|
|
|
142
177
|
|
|
143
178
|
## Examples
|
|
144
179
|
|
|
180
|
+
### Send and Wait for Response
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { GopherHole, getTaskResponseText } from '@gopherhole/sdk';
|
|
184
|
+
|
|
185
|
+
const hub = new GopherHole({
|
|
186
|
+
apiKey: process.env.GOPHERHOLE_API_KEY!,
|
|
187
|
+
requestTimeout: 60000, // 60s default timeout
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Option 1: Use askText() for simplest usage
|
|
191
|
+
const response = await hub.askText('weather-agent', 'What is the weather in Auckland?');
|
|
192
|
+
console.log('Response:', response);
|
|
193
|
+
|
|
194
|
+
// Option 2: Use sendTextAndWait() with helper function for more control
|
|
195
|
+
const task = await hub.sendTextAndWait('weather-agent', 'What is the weather in Auckland?', {
|
|
196
|
+
maxWaitMs: 120000, // Wait up to 2 minutes
|
|
197
|
+
pollIntervalMs: 2000, // Poll every 2 seconds
|
|
198
|
+
});
|
|
199
|
+
const responseText = getTaskResponseText(task);
|
|
200
|
+
console.log('Response:', responseText);
|
|
201
|
+
console.log('Task status:', task.status.state);
|
|
202
|
+
```
|
|
203
|
+
|
|
145
204
|
### Echo Bot
|
|
146
205
|
|
|
147
206
|
```typescript
|
package/dist/index.d.mts
CHANGED
|
@@ -192,6 +192,8 @@ interface GopherHoleOptions {
|
|
|
192
192
|
reconnectDelay?: number;
|
|
193
193
|
/** Max reconnect attempts */
|
|
194
194
|
maxReconnectAttempts?: number;
|
|
195
|
+
/** Default request timeout in ms (default: 30000) */
|
|
196
|
+
requestTimeout?: number;
|
|
195
197
|
}
|
|
196
198
|
/** Agent card configuration for registration */
|
|
197
199
|
interface AgentCardConfig {
|
|
@@ -241,11 +243,18 @@ interface TaskStatus {
|
|
|
241
243
|
message?: string;
|
|
242
244
|
}
|
|
243
245
|
interface Artifact {
|
|
244
|
-
name
|
|
245
|
-
|
|
246
|
+
name?: string;
|
|
247
|
+
artifactId?: string;
|
|
248
|
+
mimeType?: string;
|
|
249
|
+
parts?: MessagePart[];
|
|
246
250
|
data?: string;
|
|
247
251
|
uri?: string;
|
|
248
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Extract text response from a completed task.
|
|
255
|
+
* Checks artifacts first (where responses live), then falls back to history.
|
|
256
|
+
*/
|
|
257
|
+
declare function getTaskResponseText(task: Task): string;
|
|
249
258
|
interface SendOptions {
|
|
250
259
|
/** Existing context/conversation ID */
|
|
251
260
|
contextId?: string;
|
|
@@ -253,6 +262,14 @@ interface SendOptions {
|
|
|
253
262
|
pushNotificationUrl?: string;
|
|
254
263
|
/** History length to include */
|
|
255
264
|
historyLength?: number;
|
|
265
|
+
/** Request timeout in ms (overrides default) */
|
|
266
|
+
timeoutMs?: number;
|
|
267
|
+
}
|
|
268
|
+
interface SendAndWaitOptions extends SendOptions {
|
|
269
|
+
/** Polling interval in ms (default: 1000) */
|
|
270
|
+
pollIntervalMs?: number;
|
|
271
|
+
/** Max wait time in ms (default: 300000 = 5 min) */
|
|
272
|
+
maxWaitMs?: number;
|
|
256
273
|
}
|
|
257
274
|
type EventMap = {
|
|
258
275
|
connect: () => void;
|
|
@@ -269,6 +286,7 @@ declare class GopherHole extends EventEmitter<EventMap> {
|
|
|
269
286
|
private autoReconnect;
|
|
270
287
|
private reconnectDelay;
|
|
271
288
|
private maxReconnectAttempts;
|
|
289
|
+
private requestTimeout;
|
|
272
290
|
private reconnectAttempts;
|
|
273
291
|
private reconnectTimer;
|
|
274
292
|
private pingInterval;
|
|
@@ -295,6 +313,20 @@ declare class GopherHole extends EventEmitter<EventMap> {
|
|
|
295
313
|
* Send a text message to another agent
|
|
296
314
|
*/
|
|
297
315
|
sendText(toAgentId: string, text: string, options?: SendOptions): Promise<Task>;
|
|
316
|
+
/**
|
|
317
|
+
* Send a text message and wait for completion
|
|
318
|
+
* Returns the completed task with response artifacts
|
|
319
|
+
*/
|
|
320
|
+
sendTextAndWait(toAgentId: string, text: string, options?: SendAndWaitOptions): Promise<Task>;
|
|
321
|
+
/**
|
|
322
|
+
* Send a text message and wait for the text response
|
|
323
|
+
* This is a convenience method that extracts the response text automatically
|
|
324
|
+
*/
|
|
325
|
+
askText(toAgentId: string, text: string, options?: SendAndWaitOptions): Promise<string>;
|
|
326
|
+
/**
|
|
327
|
+
* Wait for a task to complete (polling)
|
|
328
|
+
*/
|
|
329
|
+
waitForTask(taskId: string, options?: SendAndWaitOptions): Promise<Task>;
|
|
298
330
|
/**
|
|
299
331
|
* Get a task by ID
|
|
300
332
|
*/
|
|
@@ -502,4 +534,4 @@ interface RatingResult {
|
|
|
502
534
|
ratingCount: number;
|
|
503
535
|
}
|
|
504
536
|
|
|
505
|
-
export { type A2AArtifact, type A2AMessage, type A2ATask, type A2ATaskStatus, type AgentAuthentication, type AgentCapabilities, type AgentCard, type AgentCardConfig, type AgentCategory, type AgentInfoResult, type AgentReview, type AgentSkill, type AgentSkillConfig, type Artifact, type ContentMode, type DataContent, type DataPart, type DiscoverOptions, type DiscoverResult, type FileContent, type FilePart, GopherHole, type GopherHoleOptions, type InputMode, type JsonRpcError, JsonRpcErrorCodes, type JsonRpcRequest, type JsonRpcResponse, type Message, type MessagePart, type MessagePayload, type MessageSendConfiguration, type OutputMode, type Part, type PublicAgent, type PushNotificationConfig, type RatingResult, type SendOptions, type Task, type TaskArtifactUpdateEvent, type TaskEvent, type TaskListConfiguration, type TaskPushNotificationConfig, type TaskQueryConfiguration, type TaskState, type TaskStatus, type TaskStatusUpdateEvent, type TextPart, GopherHole as default };
|
|
537
|
+
export { type A2AArtifact, type A2AMessage, type A2ATask, type A2ATaskStatus, type AgentAuthentication, type AgentCapabilities, type AgentCard, type AgentCardConfig, type AgentCategory, type AgentInfoResult, type AgentReview, type AgentSkill, type AgentSkillConfig, type Artifact, type ContentMode, type DataContent, type DataPart, type DiscoverOptions, type DiscoverResult, type FileContent, type FilePart, GopherHole, type GopherHoleOptions, type InputMode, type JsonRpcError, JsonRpcErrorCodes, type JsonRpcRequest, type JsonRpcResponse, type Message, type MessagePart, type MessagePayload, type MessageSendConfiguration, type OutputMode, type Part, type PublicAgent, type PushNotificationConfig, type RatingResult, type SendAndWaitOptions, type SendOptions, type Task, type TaskArtifactUpdateEvent, type TaskEvent, type TaskListConfiguration, type TaskPushNotificationConfig, type TaskQueryConfiguration, type TaskState, type TaskStatus, type TaskStatusUpdateEvent, type TextPart, GopherHole as default, getTaskResponseText };
|
package/dist/index.d.ts
CHANGED
|
@@ -192,6 +192,8 @@ interface GopherHoleOptions {
|
|
|
192
192
|
reconnectDelay?: number;
|
|
193
193
|
/** Max reconnect attempts */
|
|
194
194
|
maxReconnectAttempts?: number;
|
|
195
|
+
/** Default request timeout in ms (default: 30000) */
|
|
196
|
+
requestTimeout?: number;
|
|
195
197
|
}
|
|
196
198
|
/** Agent card configuration for registration */
|
|
197
199
|
interface AgentCardConfig {
|
|
@@ -241,11 +243,18 @@ interface TaskStatus {
|
|
|
241
243
|
message?: string;
|
|
242
244
|
}
|
|
243
245
|
interface Artifact {
|
|
244
|
-
name
|
|
245
|
-
|
|
246
|
+
name?: string;
|
|
247
|
+
artifactId?: string;
|
|
248
|
+
mimeType?: string;
|
|
249
|
+
parts?: MessagePart[];
|
|
246
250
|
data?: string;
|
|
247
251
|
uri?: string;
|
|
248
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Extract text response from a completed task.
|
|
255
|
+
* Checks artifacts first (where responses live), then falls back to history.
|
|
256
|
+
*/
|
|
257
|
+
declare function getTaskResponseText(task: Task): string;
|
|
249
258
|
interface SendOptions {
|
|
250
259
|
/** Existing context/conversation ID */
|
|
251
260
|
contextId?: string;
|
|
@@ -253,6 +262,14 @@ interface SendOptions {
|
|
|
253
262
|
pushNotificationUrl?: string;
|
|
254
263
|
/** History length to include */
|
|
255
264
|
historyLength?: number;
|
|
265
|
+
/** Request timeout in ms (overrides default) */
|
|
266
|
+
timeoutMs?: number;
|
|
267
|
+
}
|
|
268
|
+
interface SendAndWaitOptions extends SendOptions {
|
|
269
|
+
/** Polling interval in ms (default: 1000) */
|
|
270
|
+
pollIntervalMs?: number;
|
|
271
|
+
/** Max wait time in ms (default: 300000 = 5 min) */
|
|
272
|
+
maxWaitMs?: number;
|
|
256
273
|
}
|
|
257
274
|
type EventMap = {
|
|
258
275
|
connect: () => void;
|
|
@@ -269,6 +286,7 @@ declare class GopherHole extends EventEmitter<EventMap> {
|
|
|
269
286
|
private autoReconnect;
|
|
270
287
|
private reconnectDelay;
|
|
271
288
|
private maxReconnectAttempts;
|
|
289
|
+
private requestTimeout;
|
|
272
290
|
private reconnectAttempts;
|
|
273
291
|
private reconnectTimer;
|
|
274
292
|
private pingInterval;
|
|
@@ -295,6 +313,20 @@ declare class GopherHole extends EventEmitter<EventMap> {
|
|
|
295
313
|
* Send a text message to another agent
|
|
296
314
|
*/
|
|
297
315
|
sendText(toAgentId: string, text: string, options?: SendOptions): Promise<Task>;
|
|
316
|
+
/**
|
|
317
|
+
* Send a text message and wait for completion
|
|
318
|
+
* Returns the completed task with response artifacts
|
|
319
|
+
*/
|
|
320
|
+
sendTextAndWait(toAgentId: string, text: string, options?: SendAndWaitOptions): Promise<Task>;
|
|
321
|
+
/**
|
|
322
|
+
* Send a text message and wait for the text response
|
|
323
|
+
* This is a convenience method that extracts the response text automatically
|
|
324
|
+
*/
|
|
325
|
+
askText(toAgentId: string, text: string, options?: SendAndWaitOptions): Promise<string>;
|
|
326
|
+
/**
|
|
327
|
+
* Wait for a task to complete (polling)
|
|
328
|
+
*/
|
|
329
|
+
waitForTask(taskId: string, options?: SendAndWaitOptions): Promise<Task>;
|
|
298
330
|
/**
|
|
299
331
|
* Get a task by ID
|
|
300
332
|
*/
|
|
@@ -502,4 +534,4 @@ interface RatingResult {
|
|
|
502
534
|
ratingCount: number;
|
|
503
535
|
}
|
|
504
536
|
|
|
505
|
-
export { type A2AArtifact, type A2AMessage, type A2ATask, type A2ATaskStatus, type AgentAuthentication, type AgentCapabilities, type AgentCard, type AgentCardConfig, type AgentCategory, type AgentInfoResult, type AgentReview, type AgentSkill, type AgentSkillConfig, type Artifact, type ContentMode, type DataContent, type DataPart, type DiscoverOptions, type DiscoverResult, type FileContent, type FilePart, GopherHole, type GopherHoleOptions, type InputMode, type JsonRpcError, JsonRpcErrorCodes, type JsonRpcRequest, type JsonRpcResponse, type Message, type MessagePart, type MessagePayload, type MessageSendConfiguration, type OutputMode, type Part, type PublicAgent, type PushNotificationConfig, type RatingResult, type SendOptions, type Task, type TaskArtifactUpdateEvent, type TaskEvent, type TaskListConfiguration, type TaskPushNotificationConfig, type TaskQueryConfiguration, type TaskState, type TaskStatus, type TaskStatusUpdateEvent, type TextPart, GopherHole as default };
|
|
537
|
+
export { type A2AArtifact, type A2AMessage, type A2ATask, type A2ATaskStatus, type AgentAuthentication, type AgentCapabilities, type AgentCard, type AgentCardConfig, type AgentCategory, type AgentInfoResult, type AgentReview, type AgentSkill, type AgentSkillConfig, type Artifact, type ContentMode, type DataContent, type DataPart, type DiscoverOptions, type DiscoverResult, type FileContent, type FilePart, GopherHole, type GopherHoleOptions, type InputMode, type JsonRpcError, JsonRpcErrorCodes, type JsonRpcRequest, type JsonRpcResponse, type Message, type MessagePart, type MessagePayload, type MessageSendConfiguration, type OutputMode, type Part, type PublicAgent, type PushNotificationConfig, type RatingResult, type SendAndWaitOptions, type SendOptions, type Task, type TaskArtifactUpdateEvent, type TaskEvent, type TaskListConfiguration, type TaskPushNotificationConfig, type TaskQueryConfiguration, type TaskState, type TaskStatus, type TaskStatusUpdateEvent, type TextPart, GopherHole as default, getTaskResponseText };
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,8 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
GopherHole: () => GopherHole,
|
|
24
24
|
JsonRpcErrorCodes: () => JsonRpcErrorCodes,
|
|
25
|
-
default: () => index_default
|
|
25
|
+
default: () => index_default,
|
|
26
|
+
getTaskResponseText: () => getTaskResponseText
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
28
29
|
var import_eventemitter3 = require("eventemitter3");
|
|
@@ -44,6 +45,38 @@ var JsonRpcErrorCodes = {
|
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
// src/index.ts
|
|
48
|
+
function getTaskResponseText(task) {
|
|
49
|
+
if (task.artifacts?.length) {
|
|
50
|
+
const texts = [];
|
|
51
|
+
for (const artifact of task.artifacts) {
|
|
52
|
+
if (artifact.parts) {
|
|
53
|
+
for (const part of artifact.parts) {
|
|
54
|
+
if (part.kind === "text" && part.text) {
|
|
55
|
+
texts.push(part.text);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (texts.length > 0) {
|
|
61
|
+
return texts.join("\n");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (task.history?.length) {
|
|
65
|
+
const lastMessage = task.history[task.history.length - 1];
|
|
66
|
+
if (lastMessage.parts) {
|
|
67
|
+
const texts = [];
|
|
68
|
+
for (const part of lastMessage.parts) {
|
|
69
|
+
if (part.kind === "text" && part.text) {
|
|
70
|
+
texts.push(part.text);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (texts.length > 0) {
|
|
74
|
+
return texts.join("\n");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
47
80
|
var DEFAULT_HUB_URL = "wss://gopherhole.helixdata.workers.dev/ws";
|
|
48
81
|
var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
49
82
|
constructor(apiKeyOrOptions) {
|
|
@@ -62,6 +95,7 @@ var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
|
62
95
|
this.autoReconnect = options.autoReconnect ?? true;
|
|
63
96
|
this.reconnectDelay = options.reconnectDelay ?? 1e3;
|
|
64
97
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10;
|
|
98
|
+
this.requestTimeout = options.requestTimeout ?? 3e4;
|
|
65
99
|
}
|
|
66
100
|
/**
|
|
67
101
|
* Update agent card (sends to hub if connected)
|
|
@@ -132,13 +166,14 @@ var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
|
132
166
|
* Send a message to another agent
|
|
133
167
|
*/
|
|
134
168
|
async send(toAgentId, payload, options) {
|
|
169
|
+
const { timeoutMs, ...config } = options || {};
|
|
135
170
|
const response = await this.rpc("message/send", {
|
|
136
171
|
message: payload,
|
|
137
172
|
configuration: {
|
|
138
173
|
agentId: toAgentId,
|
|
139
|
-
...
|
|
174
|
+
...config
|
|
140
175
|
}
|
|
141
|
-
});
|
|
176
|
+
}, timeoutMs);
|
|
142
177
|
return response;
|
|
143
178
|
}
|
|
144
179
|
/**
|
|
@@ -150,6 +185,41 @@ var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
|
150
185
|
parts: [{ kind: "text", text }]
|
|
151
186
|
}, options);
|
|
152
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Send a text message and wait for completion
|
|
190
|
+
* Returns the completed task with response artifacts
|
|
191
|
+
*/
|
|
192
|
+
async sendTextAndWait(toAgentId, text, options) {
|
|
193
|
+
const task = await this.sendText(toAgentId, text, options);
|
|
194
|
+
return this.waitForTask(task.id, options);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Send a text message and wait for the text response
|
|
198
|
+
* This is a convenience method that extracts the response text automatically
|
|
199
|
+
*/
|
|
200
|
+
async askText(toAgentId, text, options) {
|
|
201
|
+
const task = await this.sendTextAndWait(toAgentId, text, options);
|
|
202
|
+
if (task.status.state === "failed") {
|
|
203
|
+
throw new Error(task.status.message || "Task failed");
|
|
204
|
+
}
|
|
205
|
+
return getTaskResponseText(task);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Wait for a task to complete (polling)
|
|
209
|
+
*/
|
|
210
|
+
async waitForTask(taskId, options) {
|
|
211
|
+
const pollInterval = options?.pollIntervalMs ?? 1e3;
|
|
212
|
+
const maxWait = options?.maxWaitMs ?? 3e5;
|
|
213
|
+
const startTime = Date.now();
|
|
214
|
+
while (Date.now() - startTime < maxWait) {
|
|
215
|
+
const task = await this.getTask(taskId);
|
|
216
|
+
if (task.status.state === "completed" || task.status.state === "failed" || task.status.state === "canceled" || task.status.state === "rejected") {
|
|
217
|
+
return task;
|
|
218
|
+
}
|
|
219
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
220
|
+
}
|
|
221
|
+
throw new Error(`Task ${taskId} did not complete within ${maxWait}ms`);
|
|
222
|
+
}
|
|
153
223
|
/**
|
|
154
224
|
* Get a task by ID
|
|
155
225
|
*/
|
|
@@ -209,25 +279,38 @@ var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
|
209
279
|
/**
|
|
210
280
|
* Make a JSON-RPC call to the A2A endpoint
|
|
211
281
|
*/
|
|
212
|
-
async rpc(method, params) {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
282
|
+
async rpc(method, params, timeoutMs) {
|
|
283
|
+
const timeout = timeoutMs ?? this.requestTimeout;
|
|
284
|
+
const controller = new AbortController();
|
|
285
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
286
|
+
try {
|
|
287
|
+
const response = await fetch(`${this.apiUrl}/a2a`, {
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: {
|
|
290
|
+
"Content-Type": "application/json",
|
|
291
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
jsonrpc: "2.0",
|
|
295
|
+
method,
|
|
296
|
+
params,
|
|
297
|
+
id: Date.now()
|
|
298
|
+
}),
|
|
299
|
+
signal: controller.signal
|
|
300
|
+
});
|
|
301
|
+
const data = await response.json();
|
|
302
|
+
if (data.error) {
|
|
303
|
+
throw new Error(data.error.message || "RPC error");
|
|
304
|
+
}
|
|
305
|
+
return data.result;
|
|
306
|
+
} catch (err) {
|
|
307
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
308
|
+
throw new Error(`Request timeout after ${timeout}ms`);
|
|
309
|
+
}
|
|
310
|
+
throw err;
|
|
311
|
+
} finally {
|
|
312
|
+
clearTimeout(timeoutId);
|
|
229
313
|
}
|
|
230
|
-
return data.result;
|
|
231
314
|
}
|
|
232
315
|
/**
|
|
233
316
|
* Handle incoming WebSocket messages
|
|
@@ -455,5 +538,6 @@ var index_default = GopherHole;
|
|
|
455
538
|
// Annotate the CommonJS export names for ESM import in node:
|
|
456
539
|
0 && (module.exports = {
|
|
457
540
|
GopherHole,
|
|
458
|
-
JsonRpcErrorCodes
|
|
541
|
+
JsonRpcErrorCodes,
|
|
542
|
+
getTaskResponseText
|
|
459
543
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -25,6 +25,38 @@ var JsonRpcErrorCodes = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
// src/index.ts
|
|
28
|
+
function getTaskResponseText(task) {
|
|
29
|
+
if (task.artifacts?.length) {
|
|
30
|
+
const texts = [];
|
|
31
|
+
for (const artifact of task.artifacts) {
|
|
32
|
+
if (artifact.parts) {
|
|
33
|
+
for (const part of artifact.parts) {
|
|
34
|
+
if (part.kind === "text" && part.text) {
|
|
35
|
+
texts.push(part.text);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (texts.length > 0) {
|
|
41
|
+
return texts.join("\n");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (task.history?.length) {
|
|
45
|
+
const lastMessage = task.history[task.history.length - 1];
|
|
46
|
+
if (lastMessage.parts) {
|
|
47
|
+
const texts = [];
|
|
48
|
+
for (const part of lastMessage.parts) {
|
|
49
|
+
if (part.kind === "text" && part.text) {
|
|
50
|
+
texts.push(part.text);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (texts.length > 0) {
|
|
54
|
+
return texts.join("\n");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
28
60
|
var DEFAULT_HUB_URL = "wss://gopherhole.helixdata.workers.dev/ws";
|
|
29
61
|
var GopherHole = class extends EventEmitter {
|
|
30
62
|
constructor(apiKeyOrOptions) {
|
|
@@ -43,6 +75,7 @@ var GopherHole = class extends EventEmitter {
|
|
|
43
75
|
this.autoReconnect = options.autoReconnect ?? true;
|
|
44
76
|
this.reconnectDelay = options.reconnectDelay ?? 1e3;
|
|
45
77
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10;
|
|
78
|
+
this.requestTimeout = options.requestTimeout ?? 3e4;
|
|
46
79
|
}
|
|
47
80
|
/**
|
|
48
81
|
* Update agent card (sends to hub if connected)
|
|
@@ -113,13 +146,14 @@ var GopherHole = class extends EventEmitter {
|
|
|
113
146
|
* Send a message to another agent
|
|
114
147
|
*/
|
|
115
148
|
async send(toAgentId, payload, options) {
|
|
149
|
+
const { timeoutMs, ...config } = options || {};
|
|
116
150
|
const response = await this.rpc("message/send", {
|
|
117
151
|
message: payload,
|
|
118
152
|
configuration: {
|
|
119
153
|
agentId: toAgentId,
|
|
120
|
-
...
|
|
154
|
+
...config
|
|
121
155
|
}
|
|
122
|
-
});
|
|
156
|
+
}, timeoutMs);
|
|
123
157
|
return response;
|
|
124
158
|
}
|
|
125
159
|
/**
|
|
@@ -131,6 +165,41 @@ var GopherHole = class extends EventEmitter {
|
|
|
131
165
|
parts: [{ kind: "text", text }]
|
|
132
166
|
}, options);
|
|
133
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Send a text message and wait for completion
|
|
170
|
+
* Returns the completed task with response artifacts
|
|
171
|
+
*/
|
|
172
|
+
async sendTextAndWait(toAgentId, text, options) {
|
|
173
|
+
const task = await this.sendText(toAgentId, text, options);
|
|
174
|
+
return this.waitForTask(task.id, options);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Send a text message and wait for the text response
|
|
178
|
+
* This is a convenience method that extracts the response text automatically
|
|
179
|
+
*/
|
|
180
|
+
async askText(toAgentId, text, options) {
|
|
181
|
+
const task = await this.sendTextAndWait(toAgentId, text, options);
|
|
182
|
+
if (task.status.state === "failed") {
|
|
183
|
+
throw new Error(task.status.message || "Task failed");
|
|
184
|
+
}
|
|
185
|
+
return getTaskResponseText(task);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Wait for a task to complete (polling)
|
|
189
|
+
*/
|
|
190
|
+
async waitForTask(taskId, options) {
|
|
191
|
+
const pollInterval = options?.pollIntervalMs ?? 1e3;
|
|
192
|
+
const maxWait = options?.maxWaitMs ?? 3e5;
|
|
193
|
+
const startTime = Date.now();
|
|
194
|
+
while (Date.now() - startTime < maxWait) {
|
|
195
|
+
const task = await this.getTask(taskId);
|
|
196
|
+
if (task.status.state === "completed" || task.status.state === "failed" || task.status.state === "canceled" || task.status.state === "rejected") {
|
|
197
|
+
return task;
|
|
198
|
+
}
|
|
199
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
200
|
+
}
|
|
201
|
+
throw new Error(`Task ${taskId} did not complete within ${maxWait}ms`);
|
|
202
|
+
}
|
|
134
203
|
/**
|
|
135
204
|
* Get a task by ID
|
|
136
205
|
*/
|
|
@@ -190,25 +259,38 @@ var GopherHole = class extends EventEmitter {
|
|
|
190
259
|
/**
|
|
191
260
|
* Make a JSON-RPC call to the A2A endpoint
|
|
192
261
|
*/
|
|
193
|
-
async rpc(method, params) {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
262
|
+
async rpc(method, params, timeoutMs) {
|
|
263
|
+
const timeout = timeoutMs ?? this.requestTimeout;
|
|
264
|
+
const controller = new AbortController();
|
|
265
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
266
|
+
try {
|
|
267
|
+
const response = await fetch(`${this.apiUrl}/a2a`, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: {
|
|
270
|
+
"Content-Type": "application/json",
|
|
271
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
272
|
+
},
|
|
273
|
+
body: JSON.stringify({
|
|
274
|
+
jsonrpc: "2.0",
|
|
275
|
+
method,
|
|
276
|
+
params,
|
|
277
|
+
id: Date.now()
|
|
278
|
+
}),
|
|
279
|
+
signal: controller.signal
|
|
280
|
+
});
|
|
281
|
+
const data = await response.json();
|
|
282
|
+
if (data.error) {
|
|
283
|
+
throw new Error(data.error.message || "RPC error");
|
|
284
|
+
}
|
|
285
|
+
return data.result;
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
288
|
+
throw new Error(`Request timeout after ${timeout}ms`);
|
|
289
|
+
}
|
|
290
|
+
throw err;
|
|
291
|
+
} finally {
|
|
292
|
+
clearTimeout(timeoutId);
|
|
210
293
|
}
|
|
211
|
-
return data.result;
|
|
212
294
|
}
|
|
213
295
|
/**
|
|
214
296
|
* Handle incoming WebSocket messages
|
|
@@ -436,5 +518,6 @@ var index_default = GopherHole;
|
|
|
436
518
|
export {
|
|
437
519
|
GopherHole,
|
|
438
520
|
JsonRpcErrorCodes,
|
|
439
|
-
index_default as default
|
|
521
|
+
index_default as default,
|
|
522
|
+
getTaskResponseText
|
|
440
523
|
};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface GopherHoleOptions {
|
|
|
16
16
|
reconnectDelay?: number;
|
|
17
17
|
/** Max reconnect attempts */
|
|
18
18
|
maxReconnectAttempts?: number;
|
|
19
|
+
/** Default request timeout in ms (default: 30000) */
|
|
20
|
+
requestTimeout?: number;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/** Agent card configuration for registration */
|
|
@@ -73,12 +75,55 @@ export interface TaskStatus {
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
export interface Artifact {
|
|
76
|
-
name
|
|
77
|
-
|
|
78
|
+
name?: string;
|
|
79
|
+
artifactId?: string;
|
|
80
|
+
mimeType?: string;
|
|
81
|
+
parts?: MessagePart[];
|
|
78
82
|
data?: string;
|
|
79
83
|
uri?: string;
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Extract text response from a completed task.
|
|
88
|
+
* Checks artifacts first (where responses live), then falls back to history.
|
|
89
|
+
*/
|
|
90
|
+
export function getTaskResponseText(task: Task): string {
|
|
91
|
+
// Check artifacts first (this is where responses from other agents appear)
|
|
92
|
+
if (task.artifacts?.length) {
|
|
93
|
+
const texts: string[] = [];
|
|
94
|
+
for (const artifact of task.artifacts) {
|
|
95
|
+
if (artifact.parts) {
|
|
96
|
+
for (const part of artifact.parts) {
|
|
97
|
+
if (part.kind === 'text' && part.text) {
|
|
98
|
+
texts.push(part.text);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (texts.length > 0) {
|
|
104
|
+
return texts.join('\n');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Fall back to history (last message)
|
|
109
|
+
if (task.history?.length) {
|
|
110
|
+
const lastMessage = task.history[task.history.length - 1];
|
|
111
|
+
if (lastMessage.parts) {
|
|
112
|
+
const texts: string[] = [];
|
|
113
|
+
for (const part of lastMessage.parts) {
|
|
114
|
+
if (part.kind === 'text' && part.text) {
|
|
115
|
+
texts.push(part.text);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (texts.length > 0) {
|
|
119
|
+
return texts.join('\n');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
126
|
+
|
|
82
127
|
export interface SendOptions {
|
|
83
128
|
/** Existing context/conversation ID */
|
|
84
129
|
contextId?: string;
|
|
@@ -86,6 +131,15 @@ export interface SendOptions {
|
|
|
86
131
|
pushNotificationUrl?: string;
|
|
87
132
|
/** History length to include */
|
|
88
133
|
historyLength?: number;
|
|
134
|
+
/** Request timeout in ms (overrides default) */
|
|
135
|
+
timeoutMs?: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface SendAndWaitOptions extends SendOptions {
|
|
139
|
+
/** Polling interval in ms (default: 1000) */
|
|
140
|
+
pollIntervalMs?: number;
|
|
141
|
+
/** Max wait time in ms (default: 300000 = 5 min) */
|
|
142
|
+
maxWaitMs?: number;
|
|
89
143
|
}
|
|
90
144
|
|
|
91
145
|
type EventMap = {
|
|
@@ -107,6 +161,7 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
107
161
|
private autoReconnect: boolean;
|
|
108
162
|
private reconnectDelay: number;
|
|
109
163
|
private maxReconnectAttempts: number;
|
|
164
|
+
private requestTimeout: number;
|
|
110
165
|
private reconnectAttempts = 0;
|
|
111
166
|
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
112
167
|
private pingInterval: ReturnType<typeof setInterval> | null = null;
|
|
@@ -127,6 +182,7 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
127
182
|
this.autoReconnect = options.autoReconnect ?? true;
|
|
128
183
|
this.reconnectDelay = options.reconnectDelay ?? 1000;
|
|
129
184
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10;
|
|
185
|
+
this.requestTimeout = options.requestTimeout ?? 30000;
|
|
130
186
|
}
|
|
131
187
|
|
|
132
188
|
/**
|
|
@@ -209,13 +265,14 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
209
265
|
* Send a message to another agent
|
|
210
266
|
*/
|
|
211
267
|
async send(toAgentId: string, payload: MessagePayload, options?: SendOptions): Promise<Task> {
|
|
268
|
+
const { timeoutMs, ...config } = options || {};
|
|
212
269
|
const response = await this.rpc('message/send', {
|
|
213
270
|
message: payload,
|
|
214
271
|
configuration: {
|
|
215
272
|
agentId: toAgentId,
|
|
216
|
-
...
|
|
273
|
+
...config,
|
|
217
274
|
},
|
|
218
|
-
});
|
|
275
|
+
}, timeoutMs);
|
|
219
276
|
|
|
220
277
|
return response as Task;
|
|
221
278
|
}
|
|
@@ -230,6 +287,50 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
230
287
|
}, options);
|
|
231
288
|
}
|
|
232
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Send a text message and wait for completion
|
|
292
|
+
* Returns the completed task with response artifacts
|
|
293
|
+
*/
|
|
294
|
+
async sendTextAndWait(toAgentId: string, text: string, options?: SendAndWaitOptions): Promise<Task> {
|
|
295
|
+
const task = await this.sendText(toAgentId, text, options);
|
|
296
|
+
return this.waitForTask(task.id, options);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Send a text message and wait for the text response
|
|
301
|
+
* This is a convenience method that extracts the response text automatically
|
|
302
|
+
*/
|
|
303
|
+
async askText(toAgentId: string, text: string, options?: SendAndWaitOptions): Promise<string> {
|
|
304
|
+
const task = await this.sendTextAndWait(toAgentId, text, options);
|
|
305
|
+
if (task.status.state === 'failed') {
|
|
306
|
+
throw new Error(task.status.message || 'Task failed');
|
|
307
|
+
}
|
|
308
|
+
return getTaskResponseText(task);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Wait for a task to complete (polling)
|
|
313
|
+
*/
|
|
314
|
+
async waitForTask(taskId: string, options?: SendAndWaitOptions): Promise<Task> {
|
|
315
|
+
const pollInterval = options?.pollIntervalMs ?? 1000;
|
|
316
|
+
const maxWait = options?.maxWaitMs ?? 300000; // 5 min default
|
|
317
|
+
const startTime = Date.now();
|
|
318
|
+
|
|
319
|
+
while (Date.now() - startTime < maxWait) {
|
|
320
|
+
const task = await this.getTask(taskId);
|
|
321
|
+
|
|
322
|
+
if (task.status.state === 'completed' || task.status.state === 'failed' ||
|
|
323
|
+
task.status.state === 'canceled' || task.status.state === 'rejected') {
|
|
324
|
+
return task;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Wait before polling again
|
|
328
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
throw new Error(`Task ${taskId} did not complete within ${maxWait}ms`);
|
|
332
|
+
}
|
|
333
|
+
|
|
233
334
|
/**
|
|
234
335
|
* Get a task by ID
|
|
235
336
|
*/
|
|
@@ -305,28 +406,42 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
305
406
|
/**
|
|
306
407
|
* Make a JSON-RPC call to the A2A endpoint
|
|
307
408
|
*/
|
|
308
|
-
private async rpc(method: string, params: Record<string, unknown
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
'Content-Type': 'application/json',
|
|
313
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
314
|
-
},
|
|
315
|
-
body: JSON.stringify({
|
|
316
|
-
jsonrpc: '2.0',
|
|
317
|
-
method,
|
|
318
|
-
params,
|
|
319
|
-
id: Date.now(),
|
|
320
|
-
}),
|
|
321
|
-
});
|
|
409
|
+
private async rpc(method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<unknown> {
|
|
410
|
+
const timeout = timeoutMs ?? this.requestTimeout;
|
|
411
|
+
const controller = new AbortController();
|
|
412
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
322
413
|
|
|
323
|
-
|
|
414
|
+
try {
|
|
415
|
+
const response = await fetch(`${this.apiUrl}/a2a`, {
|
|
416
|
+
method: 'POST',
|
|
417
|
+
headers: {
|
|
418
|
+
'Content-Type': 'application/json',
|
|
419
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
420
|
+
},
|
|
421
|
+
body: JSON.stringify({
|
|
422
|
+
jsonrpc: '2.0',
|
|
423
|
+
method,
|
|
424
|
+
params,
|
|
425
|
+
id: Date.now(),
|
|
426
|
+
}),
|
|
427
|
+
signal: controller.signal,
|
|
428
|
+
});
|
|
324
429
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
430
|
+
const data = await response.json();
|
|
431
|
+
|
|
432
|
+
if (data.error) {
|
|
433
|
+
throw new Error(data.error.message || 'RPC error');
|
|
434
|
+
}
|
|
328
435
|
|
|
329
|
-
|
|
436
|
+
return data.result;
|
|
437
|
+
} catch (err) {
|
|
438
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
439
|
+
throw new Error(`Request timeout after ${timeout}ms`);
|
|
440
|
+
}
|
|
441
|
+
throw err;
|
|
442
|
+
} finally {
|
|
443
|
+
clearTimeout(timeoutId);
|
|
444
|
+
}
|
|
330
445
|
}
|
|
331
446
|
|
|
332
447
|
/**
|