@gopherhole/sdk 0.1.4 → 0.2.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.d.mts +137 -4
- package/dist/index.d.ts +137 -4
- package/dist/index.js +207 -2
- package/dist/index.mjs +206 -2
- package/package.json +1 -1
- package/src/agent.ts +305 -0
- package/src/index.ts +67 -2
package/dist/index.d.mts
CHANGED
|
@@ -97,13 +97,13 @@ interface A2AArtifact {
|
|
|
97
97
|
lastChunk?: boolean;
|
|
98
98
|
metadata?: Record<string, unknown>;
|
|
99
99
|
}
|
|
100
|
-
interface JsonRpcRequest {
|
|
100
|
+
interface JsonRpcRequest$1 {
|
|
101
101
|
jsonrpc: '2.0';
|
|
102
102
|
method: string;
|
|
103
103
|
params?: Record<string, unknown>;
|
|
104
104
|
id: string | number;
|
|
105
105
|
}
|
|
106
|
-
interface JsonRpcResponse<T = unknown> {
|
|
106
|
+
interface JsonRpcResponse$1<T = unknown> {
|
|
107
107
|
jsonrpc: '2.0';
|
|
108
108
|
result?: T;
|
|
109
109
|
error?: JsonRpcError;
|
|
@@ -179,6 +179,116 @@ interface TaskArtifactUpdateEvent {
|
|
|
179
179
|
}
|
|
180
180
|
type TaskEvent = TaskStatusUpdateEvent | TaskArtifactUpdateEvent;
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* GopherHoleAgent - Helper for building webhook-based A2A agents
|
|
184
|
+
*
|
|
185
|
+
* Use this to create agents that receive requests from the GopherHole hub
|
|
186
|
+
* via webhooks (e.g., Cloudflare Workers, Express servers).
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
interface IncomingMessage {
|
|
190
|
+
role: 'user' | 'agent';
|
|
191
|
+
parts: AgentMessagePart[];
|
|
192
|
+
metadata?: Record<string, unknown>;
|
|
193
|
+
}
|
|
194
|
+
interface AgentMessagePart {
|
|
195
|
+
kind: 'text' | 'file' | 'data';
|
|
196
|
+
text?: string;
|
|
197
|
+
mimeType?: string;
|
|
198
|
+
data?: string;
|
|
199
|
+
uri?: string;
|
|
200
|
+
}
|
|
201
|
+
interface AgentTaskResult {
|
|
202
|
+
id: string;
|
|
203
|
+
contextId: string;
|
|
204
|
+
status: AgentTaskStatus;
|
|
205
|
+
messages: IncomingMessage[];
|
|
206
|
+
artifacts?: AgentArtifact[];
|
|
207
|
+
}
|
|
208
|
+
interface AgentTaskStatus {
|
|
209
|
+
state: 'submitted' | 'working' | 'input-required' | 'completed' | 'failed' | 'canceled';
|
|
210
|
+
timestamp: string;
|
|
211
|
+
message?: string;
|
|
212
|
+
}
|
|
213
|
+
interface AgentArtifact {
|
|
214
|
+
name?: string;
|
|
215
|
+
mimeType?: string;
|
|
216
|
+
parts?: AgentMessagePart[];
|
|
217
|
+
}
|
|
218
|
+
interface JsonRpcRequest {
|
|
219
|
+
jsonrpc: '2.0';
|
|
220
|
+
method: string;
|
|
221
|
+
params?: Record<string, unknown>;
|
|
222
|
+
id?: string | number;
|
|
223
|
+
}
|
|
224
|
+
interface JsonRpcResponse {
|
|
225
|
+
jsonrpc: '2.0';
|
|
226
|
+
result?: unknown;
|
|
227
|
+
error?: {
|
|
228
|
+
code: number;
|
|
229
|
+
message: string;
|
|
230
|
+
data?: unknown;
|
|
231
|
+
};
|
|
232
|
+
id?: string | number | null;
|
|
233
|
+
}
|
|
234
|
+
interface MessageContext {
|
|
235
|
+
/** The incoming message */
|
|
236
|
+
message: IncomingMessage;
|
|
237
|
+
/** Extracted text content from message parts */
|
|
238
|
+
text: string;
|
|
239
|
+
/** Task ID if provided */
|
|
240
|
+
taskId?: string;
|
|
241
|
+
/** Context ID if provided */
|
|
242
|
+
contextId?: string;
|
|
243
|
+
/** Full params from JSON-RPC request */
|
|
244
|
+
params: Record<string, unknown>;
|
|
245
|
+
}
|
|
246
|
+
type MessageHandler = (ctx: MessageContext) => Promise<string | AgentTaskResult> | string | AgentTaskResult;
|
|
247
|
+
interface GopherHoleAgentOptions {
|
|
248
|
+
/** Agent card for discovery */
|
|
249
|
+
card: AgentCard;
|
|
250
|
+
/** API key for authentication (from GopherHole hub) */
|
|
251
|
+
apiKey?: string;
|
|
252
|
+
/** Handler for incoming messages */
|
|
253
|
+
onMessage: MessageHandler;
|
|
254
|
+
}
|
|
255
|
+
declare class GopherHoleAgent {
|
|
256
|
+
private card;
|
|
257
|
+
private apiKey?;
|
|
258
|
+
private onMessage;
|
|
259
|
+
constructor(options: GopherHoleAgentOptions);
|
|
260
|
+
/** Get the agent card */
|
|
261
|
+
getCard(): AgentCard;
|
|
262
|
+
/** Verify authorization header */
|
|
263
|
+
verifyAuth(authHeader: string | null): boolean;
|
|
264
|
+
/**
|
|
265
|
+
* Handle an incoming HTTP request
|
|
266
|
+
* Returns a Response object (works with Cloudflare Workers, Bun, etc.)
|
|
267
|
+
*/
|
|
268
|
+
handleRequest(request: Request): Promise<Response>;
|
|
269
|
+
/**
|
|
270
|
+
* Handle a JSON-RPC request directly
|
|
271
|
+
*/
|
|
272
|
+
handleJsonRpc(req: JsonRpcRequest): Promise<JsonRpcResponse>;
|
|
273
|
+
private handleMessageSend;
|
|
274
|
+
/**
|
|
275
|
+
* Create a completed task result from a text response
|
|
276
|
+
*/
|
|
277
|
+
createTaskResult(originalMessage: IncomingMessage, responseText: string, contextId?: string): AgentTaskResult;
|
|
278
|
+
/**
|
|
279
|
+
* Helper to create a text message part
|
|
280
|
+
*/
|
|
281
|
+
static textPart(text: string): AgentMessagePart;
|
|
282
|
+
/**
|
|
283
|
+
* Helper to create a file message part
|
|
284
|
+
*/
|
|
285
|
+
static filePart(uri: string, mimeType: string): AgentMessagePart;
|
|
286
|
+
/**
|
|
287
|
+
* Helper to create a data message part (base64)
|
|
288
|
+
*/
|
|
289
|
+
static dataPart(data: string, mimeType: string): AgentMessagePart;
|
|
290
|
+
}
|
|
291
|
+
|
|
182
292
|
interface GopherHoleOptions {
|
|
183
293
|
/** API key (starts with gph_) */
|
|
184
294
|
apiKey: string;
|
|
@@ -194,6 +304,8 @@ interface GopherHoleOptions {
|
|
|
194
304
|
maxReconnectAttempts?: number;
|
|
195
305
|
/** Default request timeout in ms (default: 30000) */
|
|
196
306
|
requestTimeout?: number;
|
|
307
|
+
/** Default message response timeout in ms (default: 30000) */
|
|
308
|
+
messageTimeout?: number;
|
|
197
309
|
}
|
|
198
310
|
/** Agent card configuration for registration */
|
|
199
311
|
interface AgentCardConfig {
|
|
@@ -287,12 +399,17 @@ declare class GopherHole extends EventEmitter<EventMap> {
|
|
|
287
399
|
private reconnectDelay;
|
|
288
400
|
private maxReconnectAttempts;
|
|
289
401
|
private requestTimeout;
|
|
402
|
+
private messageTimeout;
|
|
290
403
|
private reconnectAttempts;
|
|
291
404
|
private reconnectTimer;
|
|
292
405
|
private pingInterval;
|
|
293
406
|
private agentId;
|
|
294
407
|
private agentCard;
|
|
295
408
|
constructor(apiKeyOrOptions: string | GopherHoleOptions);
|
|
409
|
+
/**
|
|
410
|
+
* Get the configured message timeout
|
|
411
|
+
*/
|
|
412
|
+
getMessageTimeout(): number;
|
|
296
413
|
/**
|
|
297
414
|
* Update agent card (sends to hub if connected)
|
|
298
415
|
*/
|
|
@@ -347,12 +464,28 @@ declare class GopherHole extends EventEmitter<EventMap> {
|
|
|
347
464
|
* Cancel a task
|
|
348
465
|
*/
|
|
349
466
|
cancelTask(taskId: string): Promise<Task>;
|
|
467
|
+
/**
|
|
468
|
+
* Respond to an incoming task via WebSocket (completes the task)
|
|
469
|
+
* Use this when you receive a 'message' event and want to send back a response
|
|
470
|
+
* that completes the original task.
|
|
471
|
+
*/
|
|
472
|
+
respond(taskId: string, text: string, options?: {
|
|
473
|
+
status?: 'completed' | 'failed';
|
|
474
|
+
message?: string;
|
|
475
|
+
}): void;
|
|
476
|
+
/**
|
|
477
|
+
* Respond with a failure to an incoming task
|
|
478
|
+
*/
|
|
479
|
+
respondError(taskId: string, errorMessage: string): void;
|
|
350
480
|
/**
|
|
351
481
|
* Reply to a message/task (sends back to the original caller)
|
|
482
|
+
* Note: This creates a NEW task via HTTP. For completing an existing task,
|
|
483
|
+
* use respond() instead.
|
|
352
484
|
*/
|
|
353
485
|
reply(taskId: string, payload: MessagePayload, toAgentId?: string): Promise<Task>;
|
|
354
486
|
/**
|
|
355
|
-
* Reply with text
|
|
487
|
+
* Reply with text (creates new task)
|
|
488
|
+
* Note: For completing an existing task, use respond() instead.
|
|
356
489
|
*/
|
|
357
490
|
replyText(taskId: string, text: string): Promise<Task>;
|
|
358
491
|
/**
|
|
@@ -534,4 +667,4 @@ interface RatingResult {
|
|
|
534
667
|
ratingCount: number;
|
|
535
668
|
}
|
|
536
669
|
|
|
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 };
|
|
670
|
+
export { type A2AArtifact, type A2AMessage, type A2ATask, type A2ATaskStatus, type AgentArtifact, type AgentAuthentication, type AgentCapabilities, type AgentCard, type AgentCardConfig, type AgentCategory, type AgentInfoResult, type AgentMessagePart, type AgentReview, type AgentSkill, type AgentSkillConfig, type AgentTaskResult, type AgentTaskStatus, type Artifact, type ContentMode, type DataContent, type DataPart, type DiscoverOptions, type DiscoverResult, type FileContent, type FilePart, GopherHole, GopherHoleAgent, type GopherHoleAgentOptions, type GopherHoleOptions, type IncomingMessage, type InputMode, type JsonRpcError, JsonRpcErrorCodes, type JsonRpcRequest$1 as JsonRpcRequest, type JsonRpcResponse$1 as JsonRpcResponse, type Message, type MessageContext, type MessageHandler, 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 AgentTaskResult as TaskResult, type TaskState, type TaskStatus, type TaskStatusUpdateEvent, type TextPart, GopherHole as default, getTaskResponseText };
|
package/dist/index.d.ts
CHANGED
|
@@ -97,13 +97,13 @@ interface A2AArtifact {
|
|
|
97
97
|
lastChunk?: boolean;
|
|
98
98
|
metadata?: Record<string, unknown>;
|
|
99
99
|
}
|
|
100
|
-
interface JsonRpcRequest {
|
|
100
|
+
interface JsonRpcRequest$1 {
|
|
101
101
|
jsonrpc: '2.0';
|
|
102
102
|
method: string;
|
|
103
103
|
params?: Record<string, unknown>;
|
|
104
104
|
id: string | number;
|
|
105
105
|
}
|
|
106
|
-
interface JsonRpcResponse<T = unknown> {
|
|
106
|
+
interface JsonRpcResponse$1<T = unknown> {
|
|
107
107
|
jsonrpc: '2.0';
|
|
108
108
|
result?: T;
|
|
109
109
|
error?: JsonRpcError;
|
|
@@ -179,6 +179,116 @@ interface TaskArtifactUpdateEvent {
|
|
|
179
179
|
}
|
|
180
180
|
type TaskEvent = TaskStatusUpdateEvent | TaskArtifactUpdateEvent;
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* GopherHoleAgent - Helper for building webhook-based A2A agents
|
|
184
|
+
*
|
|
185
|
+
* Use this to create agents that receive requests from the GopherHole hub
|
|
186
|
+
* via webhooks (e.g., Cloudflare Workers, Express servers).
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
interface IncomingMessage {
|
|
190
|
+
role: 'user' | 'agent';
|
|
191
|
+
parts: AgentMessagePart[];
|
|
192
|
+
metadata?: Record<string, unknown>;
|
|
193
|
+
}
|
|
194
|
+
interface AgentMessagePart {
|
|
195
|
+
kind: 'text' | 'file' | 'data';
|
|
196
|
+
text?: string;
|
|
197
|
+
mimeType?: string;
|
|
198
|
+
data?: string;
|
|
199
|
+
uri?: string;
|
|
200
|
+
}
|
|
201
|
+
interface AgentTaskResult {
|
|
202
|
+
id: string;
|
|
203
|
+
contextId: string;
|
|
204
|
+
status: AgentTaskStatus;
|
|
205
|
+
messages: IncomingMessage[];
|
|
206
|
+
artifacts?: AgentArtifact[];
|
|
207
|
+
}
|
|
208
|
+
interface AgentTaskStatus {
|
|
209
|
+
state: 'submitted' | 'working' | 'input-required' | 'completed' | 'failed' | 'canceled';
|
|
210
|
+
timestamp: string;
|
|
211
|
+
message?: string;
|
|
212
|
+
}
|
|
213
|
+
interface AgentArtifact {
|
|
214
|
+
name?: string;
|
|
215
|
+
mimeType?: string;
|
|
216
|
+
parts?: AgentMessagePart[];
|
|
217
|
+
}
|
|
218
|
+
interface JsonRpcRequest {
|
|
219
|
+
jsonrpc: '2.0';
|
|
220
|
+
method: string;
|
|
221
|
+
params?: Record<string, unknown>;
|
|
222
|
+
id?: string | number;
|
|
223
|
+
}
|
|
224
|
+
interface JsonRpcResponse {
|
|
225
|
+
jsonrpc: '2.0';
|
|
226
|
+
result?: unknown;
|
|
227
|
+
error?: {
|
|
228
|
+
code: number;
|
|
229
|
+
message: string;
|
|
230
|
+
data?: unknown;
|
|
231
|
+
};
|
|
232
|
+
id?: string | number | null;
|
|
233
|
+
}
|
|
234
|
+
interface MessageContext {
|
|
235
|
+
/** The incoming message */
|
|
236
|
+
message: IncomingMessage;
|
|
237
|
+
/** Extracted text content from message parts */
|
|
238
|
+
text: string;
|
|
239
|
+
/** Task ID if provided */
|
|
240
|
+
taskId?: string;
|
|
241
|
+
/** Context ID if provided */
|
|
242
|
+
contextId?: string;
|
|
243
|
+
/** Full params from JSON-RPC request */
|
|
244
|
+
params: Record<string, unknown>;
|
|
245
|
+
}
|
|
246
|
+
type MessageHandler = (ctx: MessageContext) => Promise<string | AgentTaskResult> | string | AgentTaskResult;
|
|
247
|
+
interface GopherHoleAgentOptions {
|
|
248
|
+
/** Agent card for discovery */
|
|
249
|
+
card: AgentCard;
|
|
250
|
+
/** API key for authentication (from GopherHole hub) */
|
|
251
|
+
apiKey?: string;
|
|
252
|
+
/** Handler for incoming messages */
|
|
253
|
+
onMessage: MessageHandler;
|
|
254
|
+
}
|
|
255
|
+
declare class GopherHoleAgent {
|
|
256
|
+
private card;
|
|
257
|
+
private apiKey?;
|
|
258
|
+
private onMessage;
|
|
259
|
+
constructor(options: GopherHoleAgentOptions);
|
|
260
|
+
/** Get the agent card */
|
|
261
|
+
getCard(): AgentCard;
|
|
262
|
+
/** Verify authorization header */
|
|
263
|
+
verifyAuth(authHeader: string | null): boolean;
|
|
264
|
+
/**
|
|
265
|
+
* Handle an incoming HTTP request
|
|
266
|
+
* Returns a Response object (works with Cloudflare Workers, Bun, etc.)
|
|
267
|
+
*/
|
|
268
|
+
handleRequest(request: Request): Promise<Response>;
|
|
269
|
+
/**
|
|
270
|
+
* Handle a JSON-RPC request directly
|
|
271
|
+
*/
|
|
272
|
+
handleJsonRpc(req: JsonRpcRequest): Promise<JsonRpcResponse>;
|
|
273
|
+
private handleMessageSend;
|
|
274
|
+
/**
|
|
275
|
+
* Create a completed task result from a text response
|
|
276
|
+
*/
|
|
277
|
+
createTaskResult(originalMessage: IncomingMessage, responseText: string, contextId?: string): AgentTaskResult;
|
|
278
|
+
/**
|
|
279
|
+
* Helper to create a text message part
|
|
280
|
+
*/
|
|
281
|
+
static textPart(text: string): AgentMessagePart;
|
|
282
|
+
/**
|
|
283
|
+
* Helper to create a file message part
|
|
284
|
+
*/
|
|
285
|
+
static filePart(uri: string, mimeType: string): AgentMessagePart;
|
|
286
|
+
/**
|
|
287
|
+
* Helper to create a data message part (base64)
|
|
288
|
+
*/
|
|
289
|
+
static dataPart(data: string, mimeType: string): AgentMessagePart;
|
|
290
|
+
}
|
|
291
|
+
|
|
182
292
|
interface GopherHoleOptions {
|
|
183
293
|
/** API key (starts with gph_) */
|
|
184
294
|
apiKey: string;
|
|
@@ -194,6 +304,8 @@ interface GopherHoleOptions {
|
|
|
194
304
|
maxReconnectAttempts?: number;
|
|
195
305
|
/** Default request timeout in ms (default: 30000) */
|
|
196
306
|
requestTimeout?: number;
|
|
307
|
+
/** Default message response timeout in ms (default: 30000) */
|
|
308
|
+
messageTimeout?: number;
|
|
197
309
|
}
|
|
198
310
|
/** Agent card configuration for registration */
|
|
199
311
|
interface AgentCardConfig {
|
|
@@ -287,12 +399,17 @@ declare class GopherHole extends EventEmitter<EventMap> {
|
|
|
287
399
|
private reconnectDelay;
|
|
288
400
|
private maxReconnectAttempts;
|
|
289
401
|
private requestTimeout;
|
|
402
|
+
private messageTimeout;
|
|
290
403
|
private reconnectAttempts;
|
|
291
404
|
private reconnectTimer;
|
|
292
405
|
private pingInterval;
|
|
293
406
|
private agentId;
|
|
294
407
|
private agentCard;
|
|
295
408
|
constructor(apiKeyOrOptions: string | GopherHoleOptions);
|
|
409
|
+
/**
|
|
410
|
+
* Get the configured message timeout
|
|
411
|
+
*/
|
|
412
|
+
getMessageTimeout(): number;
|
|
296
413
|
/**
|
|
297
414
|
* Update agent card (sends to hub if connected)
|
|
298
415
|
*/
|
|
@@ -347,12 +464,28 @@ declare class GopherHole extends EventEmitter<EventMap> {
|
|
|
347
464
|
* Cancel a task
|
|
348
465
|
*/
|
|
349
466
|
cancelTask(taskId: string): Promise<Task>;
|
|
467
|
+
/**
|
|
468
|
+
* Respond to an incoming task via WebSocket (completes the task)
|
|
469
|
+
* Use this when you receive a 'message' event and want to send back a response
|
|
470
|
+
* that completes the original task.
|
|
471
|
+
*/
|
|
472
|
+
respond(taskId: string, text: string, options?: {
|
|
473
|
+
status?: 'completed' | 'failed';
|
|
474
|
+
message?: string;
|
|
475
|
+
}): void;
|
|
476
|
+
/**
|
|
477
|
+
* Respond with a failure to an incoming task
|
|
478
|
+
*/
|
|
479
|
+
respondError(taskId: string, errorMessage: string): void;
|
|
350
480
|
/**
|
|
351
481
|
* Reply to a message/task (sends back to the original caller)
|
|
482
|
+
* Note: This creates a NEW task via HTTP. For completing an existing task,
|
|
483
|
+
* use respond() instead.
|
|
352
484
|
*/
|
|
353
485
|
reply(taskId: string, payload: MessagePayload, toAgentId?: string): Promise<Task>;
|
|
354
486
|
/**
|
|
355
|
-
* Reply with text
|
|
487
|
+
* Reply with text (creates new task)
|
|
488
|
+
* Note: For completing an existing task, use respond() instead.
|
|
356
489
|
*/
|
|
357
490
|
replyText(taskId: string, text: string): Promise<Task>;
|
|
358
491
|
/**
|
|
@@ -534,4 +667,4 @@ interface RatingResult {
|
|
|
534
667
|
ratingCount: number;
|
|
535
668
|
}
|
|
536
669
|
|
|
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 };
|
|
670
|
+
export { type A2AArtifact, type A2AMessage, type A2ATask, type A2ATaskStatus, type AgentArtifact, type AgentAuthentication, type AgentCapabilities, type AgentCard, type AgentCardConfig, type AgentCategory, type AgentInfoResult, type AgentMessagePart, type AgentReview, type AgentSkill, type AgentSkillConfig, type AgentTaskResult, type AgentTaskStatus, type Artifact, type ContentMode, type DataContent, type DataPart, type DiscoverOptions, type DiscoverResult, type FileContent, type FilePart, GopherHole, GopherHoleAgent, type GopherHoleAgentOptions, type GopherHoleOptions, type IncomingMessage, type InputMode, type JsonRpcError, JsonRpcErrorCodes, type JsonRpcRequest$1 as JsonRpcRequest, type JsonRpcResponse$1 as JsonRpcResponse, type Message, type MessageContext, type MessageHandler, 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 AgentTaskResult as TaskResult, type TaskState, type TaskStatus, type TaskStatusUpdateEvent, type TextPart, GopherHole as default, getTaskResponseText };
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
GopherHole: () => GopherHole,
|
|
24
|
+
GopherHoleAgent: () => GopherHoleAgent,
|
|
24
25
|
JsonRpcErrorCodes: () => JsonRpcErrorCodes,
|
|
25
26
|
default: () => index_default,
|
|
26
27
|
getTaskResponseText: () => getTaskResponseText
|
|
@@ -44,6 +45,168 @@ var JsonRpcErrorCodes = {
|
|
|
44
45
|
InvalidAgentCard: -32006
|
|
45
46
|
};
|
|
46
47
|
|
|
48
|
+
// src/agent.ts
|
|
49
|
+
var GopherHoleAgent = class {
|
|
50
|
+
constructor(options) {
|
|
51
|
+
this.card = options.card;
|
|
52
|
+
this.apiKey = options.apiKey;
|
|
53
|
+
this.onMessage = options.onMessage;
|
|
54
|
+
}
|
|
55
|
+
/** Get the agent card */
|
|
56
|
+
getCard() {
|
|
57
|
+
return this.card;
|
|
58
|
+
}
|
|
59
|
+
/** Verify authorization header */
|
|
60
|
+
verifyAuth(authHeader) {
|
|
61
|
+
if (!this.apiKey) return true;
|
|
62
|
+
return authHeader === `Bearer ${this.apiKey}`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Handle an incoming HTTP request
|
|
66
|
+
* Returns a Response object (works with Cloudflare Workers, Bun, etc.)
|
|
67
|
+
*/
|
|
68
|
+
async handleRequest(request) {
|
|
69
|
+
const url = new URL(request.url);
|
|
70
|
+
const corsHeaders = {
|
|
71
|
+
"Access-Control-Allow-Origin": "*",
|
|
72
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
73
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
74
|
+
};
|
|
75
|
+
if (request.method === "OPTIONS") {
|
|
76
|
+
return new Response(null, { headers: corsHeaders });
|
|
77
|
+
}
|
|
78
|
+
if (url.pathname === "/.well-known/agent.json" || url.pathname === "/agent.json") {
|
|
79
|
+
return Response.json(this.card, { headers: corsHeaders });
|
|
80
|
+
}
|
|
81
|
+
if (url.pathname === "/health") {
|
|
82
|
+
return Response.json(
|
|
83
|
+
{ status: "ok", agent: this.card.name, version: this.card.version || "1.0.0" },
|
|
84
|
+
{ headers: corsHeaders }
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (request.method === "POST" && (url.pathname === "/" || url.pathname === "/a2a")) {
|
|
88
|
+
if (this.apiKey && !this.verifyAuth(request.headers.get("Authorization"))) {
|
|
89
|
+
return Response.json(
|
|
90
|
+
{ jsonrpc: "2.0", error: { code: -32001, message: "Unauthorized" }, id: null },
|
|
91
|
+
{ status: 401, headers: corsHeaders }
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const body = await request.json();
|
|
96
|
+
const response = await this.handleJsonRpc(body);
|
|
97
|
+
return Response.json(response, { headers: corsHeaders });
|
|
98
|
+
} catch {
|
|
99
|
+
return Response.json(
|
|
100
|
+
{ jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null },
|
|
101
|
+
{ status: 400, headers: corsHeaders }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return new Response("Not Found", { status: 404, headers: corsHeaders });
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Handle a JSON-RPC request directly
|
|
109
|
+
*/
|
|
110
|
+
async handleJsonRpc(req) {
|
|
111
|
+
const { method, params = {}, id } = req;
|
|
112
|
+
switch (method) {
|
|
113
|
+
case "message/send":
|
|
114
|
+
return this.handleMessageSend(params, id);
|
|
115
|
+
case "tasks/get":
|
|
116
|
+
return {
|
|
117
|
+
jsonrpc: "2.0",
|
|
118
|
+
error: { code: -32601, message: "This agent does not support persistent tasks" },
|
|
119
|
+
id
|
|
120
|
+
};
|
|
121
|
+
case "tasks/cancel":
|
|
122
|
+
return {
|
|
123
|
+
jsonrpc: "2.0",
|
|
124
|
+
error: { code: -32601, message: "This agent does not support task cancellation" },
|
|
125
|
+
id
|
|
126
|
+
};
|
|
127
|
+
default:
|
|
128
|
+
return {
|
|
129
|
+
jsonrpc: "2.0",
|
|
130
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
131
|
+
id
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async handleMessageSend(params, id) {
|
|
136
|
+
const message = params.message;
|
|
137
|
+
if (!message?.parts) {
|
|
138
|
+
return {
|
|
139
|
+
jsonrpc: "2.0",
|
|
140
|
+
error: { code: -32602, message: "Invalid params: message with parts required" },
|
|
141
|
+
id
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const text = message.parts.filter((p) => p.kind === "text" || p.text).map((p) => p.text || "").join("\n").trim();
|
|
145
|
+
const config = params.configuration;
|
|
146
|
+
const ctx = {
|
|
147
|
+
message,
|
|
148
|
+
text,
|
|
149
|
+
taskId: params.taskId,
|
|
150
|
+
contextId: config?.contextId,
|
|
151
|
+
params
|
|
152
|
+
};
|
|
153
|
+
try {
|
|
154
|
+
const result = await this.onMessage(ctx);
|
|
155
|
+
if (typeof result === "string") {
|
|
156
|
+
const taskResult = this.createTaskResult(message, result, ctx.contextId);
|
|
157
|
+
return { jsonrpc: "2.0", result: taskResult, id };
|
|
158
|
+
}
|
|
159
|
+
return { jsonrpc: "2.0", result, id };
|
|
160
|
+
} catch (error) {
|
|
161
|
+
const errorMessage = error instanceof Error ? error.message : "Handler error";
|
|
162
|
+
return {
|
|
163
|
+
jsonrpc: "2.0",
|
|
164
|
+
error: { code: -32e3, message: errorMessage },
|
|
165
|
+
id
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Create a completed task result from a text response
|
|
171
|
+
*/
|
|
172
|
+
createTaskResult(originalMessage, responseText, contextId) {
|
|
173
|
+
return {
|
|
174
|
+
id: `task-${Date.now()}`,
|
|
175
|
+
contextId: contextId || `ctx-${Date.now()}`,
|
|
176
|
+
status: {
|
|
177
|
+
state: "completed",
|
|
178
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
179
|
+
},
|
|
180
|
+
messages: [
|
|
181
|
+
originalMessage,
|
|
182
|
+
{
|
|
183
|
+
role: "agent",
|
|
184
|
+
parts: [{ kind: "text", text: responseText }],
|
|
185
|
+
metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Helper to create a text message part
|
|
192
|
+
*/
|
|
193
|
+
static textPart(text) {
|
|
194
|
+
return { kind: "text", text };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Helper to create a file message part
|
|
198
|
+
*/
|
|
199
|
+
static filePart(uri, mimeType) {
|
|
200
|
+
return { kind: "file", uri, mimeType };
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Helper to create a data message part (base64)
|
|
204
|
+
*/
|
|
205
|
+
static dataPart(data, mimeType) {
|
|
206
|
+
return { kind: "data", data, mimeType };
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
47
210
|
// src/index.ts
|
|
48
211
|
function getTaskResponseText(task) {
|
|
49
212
|
if (task.artifacts?.length) {
|
|
@@ -96,6 +259,13 @@ var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
|
96
259
|
this.reconnectDelay = options.reconnectDelay ?? 1e3;
|
|
97
260
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10;
|
|
98
261
|
this.requestTimeout = options.requestTimeout ?? 3e4;
|
|
262
|
+
this.messageTimeout = options.messageTimeout ?? 3e4;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get the configured message timeout
|
|
266
|
+
*/
|
|
267
|
+
getMessageTimeout() {
|
|
268
|
+
return this.messageTimeout;
|
|
99
269
|
}
|
|
100
270
|
/**
|
|
101
271
|
* Update agent card (sends to hub if connected)
|
|
@@ -244,8 +414,41 @@ var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
|
244
414
|
const response = await this.rpc("tasks/cancel", { id: taskId });
|
|
245
415
|
return response;
|
|
246
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Respond to an incoming task via WebSocket (completes the task)
|
|
419
|
+
* Use this when you receive a 'message' event and want to send back a response
|
|
420
|
+
* that completes the original task.
|
|
421
|
+
*/
|
|
422
|
+
respond(taskId, text, options) {
|
|
423
|
+
if (!this.ws || this.ws.readyState !== 1) {
|
|
424
|
+
throw new Error("WebSocket not connected");
|
|
425
|
+
}
|
|
426
|
+
const response = {
|
|
427
|
+
type: "task_response",
|
|
428
|
+
taskId,
|
|
429
|
+
status: {
|
|
430
|
+
state: options?.status ?? "completed",
|
|
431
|
+
message: options?.message
|
|
432
|
+
},
|
|
433
|
+
artifact: {
|
|
434
|
+
artifactId: `response-${Date.now()}`,
|
|
435
|
+
mimeType: "text/plain",
|
|
436
|
+
parts: [{ kind: "text", text }]
|
|
437
|
+
},
|
|
438
|
+
lastChunk: true
|
|
439
|
+
};
|
|
440
|
+
this.ws.send(JSON.stringify(response));
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Respond with a failure to an incoming task
|
|
444
|
+
*/
|
|
445
|
+
respondError(taskId, errorMessage) {
|
|
446
|
+
this.respond(taskId, errorMessage, { status: "failed", message: errorMessage });
|
|
447
|
+
}
|
|
247
448
|
/**
|
|
248
449
|
* Reply to a message/task (sends back to the original caller)
|
|
450
|
+
* Note: This creates a NEW task via HTTP. For completing an existing task,
|
|
451
|
+
* use respond() instead.
|
|
249
452
|
*/
|
|
250
453
|
async reply(taskId, payload, toAgentId) {
|
|
251
454
|
if (!toAgentId) {
|
|
@@ -268,7 +471,8 @@ var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
|
268
471
|
return response;
|
|
269
472
|
}
|
|
270
473
|
/**
|
|
271
|
-
* Reply with text
|
|
474
|
+
* Reply with text (creates new task)
|
|
475
|
+
* Note: For completing an existing task, use respond() instead.
|
|
272
476
|
*/
|
|
273
477
|
async replyText(taskId, text) {
|
|
274
478
|
return this.reply(taskId, {
|
|
@@ -344,7 +548,7 @@ var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
|
344
548
|
if (this.ws?.readyState === 1) {
|
|
345
549
|
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
346
550
|
}
|
|
347
|
-
},
|
|
551
|
+
}, 15e3);
|
|
348
552
|
}
|
|
349
553
|
/**
|
|
350
554
|
* Stop ping interval
|
|
@@ -538,6 +742,7 @@ var index_default = GopherHole;
|
|
|
538
742
|
// Annotate the CommonJS export names for ESM import in node:
|
|
539
743
|
0 && (module.exports = {
|
|
540
744
|
GopherHole,
|
|
745
|
+
GopherHoleAgent,
|
|
541
746
|
JsonRpcErrorCodes,
|
|
542
747
|
getTaskResponseText
|
|
543
748
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -24,6 +24,168 @@ var JsonRpcErrorCodes = {
|
|
|
24
24
|
InvalidAgentCard: -32006
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
// src/agent.ts
|
|
28
|
+
var GopherHoleAgent = class {
|
|
29
|
+
constructor(options) {
|
|
30
|
+
this.card = options.card;
|
|
31
|
+
this.apiKey = options.apiKey;
|
|
32
|
+
this.onMessage = options.onMessage;
|
|
33
|
+
}
|
|
34
|
+
/** Get the agent card */
|
|
35
|
+
getCard() {
|
|
36
|
+
return this.card;
|
|
37
|
+
}
|
|
38
|
+
/** Verify authorization header */
|
|
39
|
+
verifyAuth(authHeader) {
|
|
40
|
+
if (!this.apiKey) return true;
|
|
41
|
+
return authHeader === `Bearer ${this.apiKey}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Handle an incoming HTTP request
|
|
45
|
+
* Returns a Response object (works with Cloudflare Workers, Bun, etc.)
|
|
46
|
+
*/
|
|
47
|
+
async handleRequest(request) {
|
|
48
|
+
const url = new URL(request.url);
|
|
49
|
+
const corsHeaders = {
|
|
50
|
+
"Access-Control-Allow-Origin": "*",
|
|
51
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
52
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
53
|
+
};
|
|
54
|
+
if (request.method === "OPTIONS") {
|
|
55
|
+
return new Response(null, { headers: corsHeaders });
|
|
56
|
+
}
|
|
57
|
+
if (url.pathname === "/.well-known/agent.json" || url.pathname === "/agent.json") {
|
|
58
|
+
return Response.json(this.card, { headers: corsHeaders });
|
|
59
|
+
}
|
|
60
|
+
if (url.pathname === "/health") {
|
|
61
|
+
return Response.json(
|
|
62
|
+
{ status: "ok", agent: this.card.name, version: this.card.version || "1.0.0" },
|
|
63
|
+
{ headers: corsHeaders }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (request.method === "POST" && (url.pathname === "/" || url.pathname === "/a2a")) {
|
|
67
|
+
if (this.apiKey && !this.verifyAuth(request.headers.get("Authorization"))) {
|
|
68
|
+
return Response.json(
|
|
69
|
+
{ jsonrpc: "2.0", error: { code: -32001, message: "Unauthorized" }, id: null },
|
|
70
|
+
{ status: 401, headers: corsHeaders }
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const body = await request.json();
|
|
75
|
+
const response = await this.handleJsonRpc(body);
|
|
76
|
+
return Response.json(response, { headers: corsHeaders });
|
|
77
|
+
} catch {
|
|
78
|
+
return Response.json(
|
|
79
|
+
{ jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null },
|
|
80
|
+
{ status: 400, headers: corsHeaders }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return new Response("Not Found", { status: 404, headers: corsHeaders });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Handle a JSON-RPC request directly
|
|
88
|
+
*/
|
|
89
|
+
async handleJsonRpc(req) {
|
|
90
|
+
const { method, params = {}, id } = req;
|
|
91
|
+
switch (method) {
|
|
92
|
+
case "message/send":
|
|
93
|
+
return this.handleMessageSend(params, id);
|
|
94
|
+
case "tasks/get":
|
|
95
|
+
return {
|
|
96
|
+
jsonrpc: "2.0",
|
|
97
|
+
error: { code: -32601, message: "This agent does not support persistent tasks" },
|
|
98
|
+
id
|
|
99
|
+
};
|
|
100
|
+
case "tasks/cancel":
|
|
101
|
+
return {
|
|
102
|
+
jsonrpc: "2.0",
|
|
103
|
+
error: { code: -32601, message: "This agent does not support task cancellation" },
|
|
104
|
+
id
|
|
105
|
+
};
|
|
106
|
+
default:
|
|
107
|
+
return {
|
|
108
|
+
jsonrpc: "2.0",
|
|
109
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
110
|
+
id
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async handleMessageSend(params, id) {
|
|
115
|
+
const message = params.message;
|
|
116
|
+
if (!message?.parts) {
|
|
117
|
+
return {
|
|
118
|
+
jsonrpc: "2.0",
|
|
119
|
+
error: { code: -32602, message: "Invalid params: message with parts required" },
|
|
120
|
+
id
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const text = message.parts.filter((p) => p.kind === "text" || p.text).map((p) => p.text || "").join("\n").trim();
|
|
124
|
+
const config = params.configuration;
|
|
125
|
+
const ctx = {
|
|
126
|
+
message,
|
|
127
|
+
text,
|
|
128
|
+
taskId: params.taskId,
|
|
129
|
+
contextId: config?.contextId,
|
|
130
|
+
params
|
|
131
|
+
};
|
|
132
|
+
try {
|
|
133
|
+
const result = await this.onMessage(ctx);
|
|
134
|
+
if (typeof result === "string") {
|
|
135
|
+
const taskResult = this.createTaskResult(message, result, ctx.contextId);
|
|
136
|
+
return { jsonrpc: "2.0", result: taskResult, id };
|
|
137
|
+
}
|
|
138
|
+
return { jsonrpc: "2.0", result, id };
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const errorMessage = error instanceof Error ? error.message : "Handler error";
|
|
141
|
+
return {
|
|
142
|
+
jsonrpc: "2.0",
|
|
143
|
+
error: { code: -32e3, message: errorMessage },
|
|
144
|
+
id
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Create a completed task result from a text response
|
|
150
|
+
*/
|
|
151
|
+
createTaskResult(originalMessage, responseText, contextId) {
|
|
152
|
+
return {
|
|
153
|
+
id: `task-${Date.now()}`,
|
|
154
|
+
contextId: contextId || `ctx-${Date.now()}`,
|
|
155
|
+
status: {
|
|
156
|
+
state: "completed",
|
|
157
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
158
|
+
},
|
|
159
|
+
messages: [
|
|
160
|
+
originalMessage,
|
|
161
|
+
{
|
|
162
|
+
role: "agent",
|
|
163
|
+
parts: [{ kind: "text", text: responseText }],
|
|
164
|
+
metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Helper to create a text message part
|
|
171
|
+
*/
|
|
172
|
+
static textPart(text) {
|
|
173
|
+
return { kind: "text", text };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Helper to create a file message part
|
|
177
|
+
*/
|
|
178
|
+
static filePart(uri, mimeType) {
|
|
179
|
+
return { kind: "file", uri, mimeType };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Helper to create a data message part (base64)
|
|
183
|
+
*/
|
|
184
|
+
static dataPart(data, mimeType) {
|
|
185
|
+
return { kind: "data", data, mimeType };
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
27
189
|
// src/index.ts
|
|
28
190
|
function getTaskResponseText(task) {
|
|
29
191
|
if (task.artifacts?.length) {
|
|
@@ -76,6 +238,13 @@ var GopherHole = class extends EventEmitter {
|
|
|
76
238
|
this.reconnectDelay = options.reconnectDelay ?? 1e3;
|
|
77
239
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10;
|
|
78
240
|
this.requestTimeout = options.requestTimeout ?? 3e4;
|
|
241
|
+
this.messageTimeout = options.messageTimeout ?? 3e4;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Get the configured message timeout
|
|
245
|
+
*/
|
|
246
|
+
getMessageTimeout() {
|
|
247
|
+
return this.messageTimeout;
|
|
79
248
|
}
|
|
80
249
|
/**
|
|
81
250
|
* Update agent card (sends to hub if connected)
|
|
@@ -224,8 +393,41 @@ var GopherHole = class extends EventEmitter {
|
|
|
224
393
|
const response = await this.rpc("tasks/cancel", { id: taskId });
|
|
225
394
|
return response;
|
|
226
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Respond to an incoming task via WebSocket (completes the task)
|
|
398
|
+
* Use this when you receive a 'message' event and want to send back a response
|
|
399
|
+
* that completes the original task.
|
|
400
|
+
*/
|
|
401
|
+
respond(taskId, text, options) {
|
|
402
|
+
if (!this.ws || this.ws.readyState !== 1) {
|
|
403
|
+
throw new Error("WebSocket not connected");
|
|
404
|
+
}
|
|
405
|
+
const response = {
|
|
406
|
+
type: "task_response",
|
|
407
|
+
taskId,
|
|
408
|
+
status: {
|
|
409
|
+
state: options?.status ?? "completed",
|
|
410
|
+
message: options?.message
|
|
411
|
+
},
|
|
412
|
+
artifact: {
|
|
413
|
+
artifactId: `response-${Date.now()}`,
|
|
414
|
+
mimeType: "text/plain",
|
|
415
|
+
parts: [{ kind: "text", text }]
|
|
416
|
+
},
|
|
417
|
+
lastChunk: true
|
|
418
|
+
};
|
|
419
|
+
this.ws.send(JSON.stringify(response));
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Respond with a failure to an incoming task
|
|
423
|
+
*/
|
|
424
|
+
respondError(taskId, errorMessage) {
|
|
425
|
+
this.respond(taskId, errorMessage, { status: "failed", message: errorMessage });
|
|
426
|
+
}
|
|
227
427
|
/**
|
|
228
428
|
* Reply to a message/task (sends back to the original caller)
|
|
429
|
+
* Note: This creates a NEW task via HTTP. For completing an existing task,
|
|
430
|
+
* use respond() instead.
|
|
229
431
|
*/
|
|
230
432
|
async reply(taskId, payload, toAgentId) {
|
|
231
433
|
if (!toAgentId) {
|
|
@@ -248,7 +450,8 @@ var GopherHole = class extends EventEmitter {
|
|
|
248
450
|
return response;
|
|
249
451
|
}
|
|
250
452
|
/**
|
|
251
|
-
* Reply with text
|
|
453
|
+
* Reply with text (creates new task)
|
|
454
|
+
* Note: For completing an existing task, use respond() instead.
|
|
252
455
|
*/
|
|
253
456
|
async replyText(taskId, text) {
|
|
254
457
|
return this.reply(taskId, {
|
|
@@ -324,7 +527,7 @@ var GopherHole = class extends EventEmitter {
|
|
|
324
527
|
if (this.ws?.readyState === 1) {
|
|
325
528
|
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
326
529
|
}
|
|
327
|
-
},
|
|
530
|
+
}, 15e3);
|
|
328
531
|
}
|
|
329
532
|
/**
|
|
330
533
|
* Stop ping interval
|
|
@@ -517,6 +720,7 @@ var GopherHole = class extends EventEmitter {
|
|
|
517
720
|
var index_default = GopherHole;
|
|
518
721
|
export {
|
|
519
722
|
GopherHole,
|
|
723
|
+
GopherHoleAgent,
|
|
520
724
|
JsonRpcErrorCodes,
|
|
521
725
|
index_default as default,
|
|
522
726
|
getTaskResponseText
|
package/package.json
CHANGED
package/src/agent.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GopherHoleAgent - Helper for building webhook-based A2A agents
|
|
3
|
+
*
|
|
4
|
+
* Use this to create agents that receive requests from the GopherHole hub
|
|
5
|
+
* via webhooks (e.g., Cloudflare Workers, Express servers).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentCard, AgentSkill } from './types';
|
|
9
|
+
|
|
10
|
+
// Re-export types for convenience
|
|
11
|
+
export type { AgentCard, AgentSkill };
|
|
12
|
+
|
|
13
|
+
// ============================================================
|
|
14
|
+
// TYPES
|
|
15
|
+
// ============================================================
|
|
16
|
+
|
|
17
|
+
export interface IncomingMessage {
|
|
18
|
+
role: 'user' | 'agent';
|
|
19
|
+
parts: AgentMessagePart[];
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AgentMessagePart {
|
|
24
|
+
kind: 'text' | 'file' | 'data';
|
|
25
|
+
text?: string;
|
|
26
|
+
mimeType?: string;
|
|
27
|
+
data?: string;
|
|
28
|
+
uri?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AgentTaskResult {
|
|
32
|
+
id: string;
|
|
33
|
+
contextId: string;
|
|
34
|
+
status: AgentTaskStatus;
|
|
35
|
+
messages: IncomingMessage[];
|
|
36
|
+
artifacts?: AgentArtifact[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AgentTaskStatus {
|
|
40
|
+
state: 'submitted' | 'working' | 'input-required' | 'completed' | 'failed' | 'canceled';
|
|
41
|
+
timestamp: string;
|
|
42
|
+
message?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AgentArtifact {
|
|
46
|
+
name?: string;
|
|
47
|
+
mimeType?: string;
|
|
48
|
+
parts?: AgentMessagePart[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface JsonRpcRequest {
|
|
52
|
+
jsonrpc: '2.0';
|
|
53
|
+
method: string;
|
|
54
|
+
params?: Record<string, unknown>;
|
|
55
|
+
id?: string | number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface JsonRpcResponse {
|
|
59
|
+
jsonrpc: '2.0';
|
|
60
|
+
result?: unknown;
|
|
61
|
+
error?: { code: number; message: string; data?: unknown };
|
|
62
|
+
id?: string | number | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================
|
|
66
|
+
// MESSAGE HANDLER TYPE
|
|
67
|
+
// ============================================================
|
|
68
|
+
|
|
69
|
+
export interface MessageContext {
|
|
70
|
+
/** The incoming message */
|
|
71
|
+
message: IncomingMessage;
|
|
72
|
+
/** Extracted text content from message parts */
|
|
73
|
+
text: string;
|
|
74
|
+
/** Task ID if provided */
|
|
75
|
+
taskId?: string;
|
|
76
|
+
/** Context ID if provided */
|
|
77
|
+
contextId?: string;
|
|
78
|
+
/** Full params from JSON-RPC request */
|
|
79
|
+
params: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type MessageHandler = (ctx: MessageContext) => Promise<string | AgentTaskResult> | string | AgentTaskResult;
|
|
83
|
+
|
|
84
|
+
// ============================================================
|
|
85
|
+
// AGENT CLASS
|
|
86
|
+
// ============================================================
|
|
87
|
+
|
|
88
|
+
export interface GopherHoleAgentOptions {
|
|
89
|
+
/** Agent card for discovery */
|
|
90
|
+
card: AgentCard;
|
|
91
|
+
/** API key for authentication (from GopherHole hub) */
|
|
92
|
+
apiKey?: string;
|
|
93
|
+
/** Handler for incoming messages */
|
|
94
|
+
onMessage: MessageHandler;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class GopherHoleAgent {
|
|
98
|
+
private card: AgentCard;
|
|
99
|
+
private apiKey?: string;
|
|
100
|
+
private onMessage: MessageHandler;
|
|
101
|
+
|
|
102
|
+
constructor(options: GopherHoleAgentOptions) {
|
|
103
|
+
this.card = options.card;
|
|
104
|
+
this.apiKey = options.apiKey;
|
|
105
|
+
this.onMessage = options.onMessage;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Get the agent card */
|
|
109
|
+
getCard(): AgentCard {
|
|
110
|
+
return this.card;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Verify authorization header */
|
|
114
|
+
verifyAuth(authHeader: string | null): boolean {
|
|
115
|
+
if (!this.apiKey) return true; // No auth configured
|
|
116
|
+
return authHeader === `Bearer ${this.apiKey}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Handle an incoming HTTP request
|
|
121
|
+
* Returns a Response object (works with Cloudflare Workers, Bun, etc.)
|
|
122
|
+
*/
|
|
123
|
+
async handleRequest(request: Request): Promise<Response> {
|
|
124
|
+
const url = new URL(request.url);
|
|
125
|
+
const corsHeaders = {
|
|
126
|
+
'Access-Control-Allow-Origin': '*',
|
|
127
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
128
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// CORS preflight
|
|
132
|
+
if (request.method === 'OPTIONS') {
|
|
133
|
+
return new Response(null, { headers: corsHeaders });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Agent card endpoints
|
|
137
|
+
if (url.pathname === '/.well-known/agent.json' || url.pathname === '/agent.json') {
|
|
138
|
+
return Response.json(this.card, { headers: corsHeaders });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Health check
|
|
142
|
+
if (url.pathname === '/health') {
|
|
143
|
+
return Response.json(
|
|
144
|
+
{ status: 'ok', agent: this.card.name, version: this.card.version || '1.0.0' },
|
|
145
|
+
{ headers: corsHeaders }
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// JSON-RPC endpoint
|
|
150
|
+
if (request.method === 'POST' && (url.pathname === '/' || url.pathname === '/a2a')) {
|
|
151
|
+
// Verify auth
|
|
152
|
+
if (this.apiKey && !this.verifyAuth(request.headers.get('Authorization'))) {
|
|
153
|
+
return Response.json(
|
|
154
|
+
{ jsonrpc: '2.0', error: { code: -32001, message: 'Unauthorized' }, id: null },
|
|
155
|
+
{ status: 401, headers: corsHeaders }
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const body = await request.json() as JsonRpcRequest;
|
|
161
|
+
const response = await this.handleJsonRpc(body);
|
|
162
|
+
return Response.json(response, { headers: corsHeaders });
|
|
163
|
+
} catch {
|
|
164
|
+
return Response.json(
|
|
165
|
+
{ jsonrpc: '2.0', error: { code: -32700, message: 'Parse error' }, id: null },
|
|
166
|
+
{ status: 400, headers: corsHeaders }
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return new Response('Not Found', { status: 404, headers: corsHeaders });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Handle a JSON-RPC request directly
|
|
176
|
+
*/
|
|
177
|
+
async handleJsonRpc(req: JsonRpcRequest): Promise<JsonRpcResponse> {
|
|
178
|
+
const { method, params = {}, id } = req;
|
|
179
|
+
|
|
180
|
+
switch (method) {
|
|
181
|
+
case 'message/send':
|
|
182
|
+
return this.handleMessageSend(params, id);
|
|
183
|
+
|
|
184
|
+
case 'tasks/get':
|
|
185
|
+
return {
|
|
186
|
+
jsonrpc: '2.0',
|
|
187
|
+
error: { code: -32601, message: 'This agent does not support persistent tasks' },
|
|
188
|
+
id,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
case 'tasks/cancel':
|
|
192
|
+
return {
|
|
193
|
+
jsonrpc: '2.0',
|
|
194
|
+
error: { code: -32601, message: 'This agent does not support task cancellation' },
|
|
195
|
+
id,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
default:
|
|
199
|
+
return {
|
|
200
|
+
jsonrpc: '2.0',
|
|
201
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
202
|
+
id,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private async handleMessageSend(
|
|
208
|
+
params: Record<string, unknown>,
|
|
209
|
+
id?: string | number
|
|
210
|
+
): Promise<JsonRpcResponse> {
|
|
211
|
+
const message = params.message as IncomingMessage | undefined;
|
|
212
|
+
|
|
213
|
+
if (!message?.parts) {
|
|
214
|
+
return {
|
|
215
|
+
jsonrpc: '2.0',
|
|
216
|
+
error: { code: -32602, message: 'Invalid params: message with parts required' },
|
|
217
|
+
id,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Extract text from message parts
|
|
222
|
+
const text = message.parts
|
|
223
|
+
.filter(p => p.kind === 'text' || p.text)
|
|
224
|
+
.map(p => p.text || '')
|
|
225
|
+
.join('\n')
|
|
226
|
+
.trim();
|
|
227
|
+
|
|
228
|
+
const config = params.configuration as Record<string, unknown> | undefined;
|
|
229
|
+
const ctx: MessageContext = {
|
|
230
|
+
message,
|
|
231
|
+
text,
|
|
232
|
+
taskId: params.taskId as string | undefined,
|
|
233
|
+
contextId: config?.contextId as string | undefined,
|
|
234
|
+
params,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const result = await this.onMessage(ctx);
|
|
239
|
+
|
|
240
|
+
// If handler returned a string, wrap it in a task result
|
|
241
|
+
if (typeof result === 'string') {
|
|
242
|
+
const taskResult = this.createTaskResult(message, result, ctx.contextId);
|
|
243
|
+
return { jsonrpc: '2.0', result: taskResult, id };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { jsonrpc: '2.0', result, id };
|
|
247
|
+
} catch (error) {
|
|
248
|
+
const errorMessage = error instanceof Error ? error.message : 'Handler error';
|
|
249
|
+
return {
|
|
250
|
+
jsonrpc: '2.0',
|
|
251
|
+
error: { code: -32000, message: errorMessage },
|
|
252
|
+
id,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Create a completed task result from a text response
|
|
259
|
+
*/
|
|
260
|
+
createTaskResult(
|
|
261
|
+
originalMessage: IncomingMessage,
|
|
262
|
+
responseText: string,
|
|
263
|
+
contextId?: string
|
|
264
|
+
): AgentTaskResult {
|
|
265
|
+
return {
|
|
266
|
+
id: `task-${Date.now()}`,
|
|
267
|
+
contextId: contextId || `ctx-${Date.now()}`,
|
|
268
|
+
status: {
|
|
269
|
+
state: 'completed',
|
|
270
|
+
timestamp: new Date().toISOString(),
|
|
271
|
+
},
|
|
272
|
+
messages: [
|
|
273
|
+
originalMessage,
|
|
274
|
+
{
|
|
275
|
+
role: 'agent',
|
|
276
|
+
parts: [{ kind: 'text', text: responseText }],
|
|
277
|
+
metadata: { generatedAt: new Date().toISOString() },
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Helper to create a text message part
|
|
285
|
+
*/
|
|
286
|
+
static textPart(text: string): AgentMessagePart {
|
|
287
|
+
return { kind: 'text', text };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Helper to create a file message part
|
|
292
|
+
*/
|
|
293
|
+
static filePart(uri: string, mimeType: string): AgentMessagePart {
|
|
294
|
+
return { kind: 'file', uri, mimeType };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Helper to create a data message part (base64)
|
|
299
|
+
*/
|
|
300
|
+
static dataPart(data: string, mimeType: string): AgentMessagePart {
|
|
301
|
+
return { kind: 'data', data, mimeType };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export default GopherHoleAgent;
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,22 @@ import { EventEmitter } from 'eventemitter3';
|
|
|
3
3
|
// Re-export types
|
|
4
4
|
export * from './types';
|
|
5
5
|
|
|
6
|
+
// Re-export agent helper for webhook-based agents
|
|
7
|
+
export {
|
|
8
|
+
GopherHoleAgent,
|
|
9
|
+
GopherHoleAgentOptions,
|
|
10
|
+
IncomingMessage,
|
|
11
|
+
AgentMessagePart,
|
|
12
|
+
AgentTaskResult,
|
|
13
|
+
AgentTaskStatus,
|
|
14
|
+
AgentArtifact,
|
|
15
|
+
MessageContext,
|
|
16
|
+
MessageHandler,
|
|
17
|
+
} from './agent';
|
|
18
|
+
|
|
19
|
+
// Convenience type aliases
|
|
20
|
+
export type { AgentTaskResult as TaskResult } from './agent';
|
|
21
|
+
|
|
6
22
|
export interface GopherHoleOptions {
|
|
7
23
|
/** API key (starts with gph_) */
|
|
8
24
|
apiKey: string;
|
|
@@ -18,6 +34,8 @@ export interface GopherHoleOptions {
|
|
|
18
34
|
maxReconnectAttempts?: number;
|
|
19
35
|
/** Default request timeout in ms (default: 30000) */
|
|
20
36
|
requestTimeout?: number;
|
|
37
|
+
/** Default message response timeout in ms (default: 30000) */
|
|
38
|
+
messageTimeout?: number;
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
/** Agent card configuration for registration */
|
|
@@ -162,6 +180,7 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
162
180
|
private reconnectDelay: number;
|
|
163
181
|
private maxReconnectAttempts: number;
|
|
164
182
|
private requestTimeout: number;
|
|
183
|
+
private messageTimeout: number;
|
|
165
184
|
private reconnectAttempts = 0;
|
|
166
185
|
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
167
186
|
private pingInterval: ReturnType<typeof setInterval> | null = null;
|
|
@@ -183,6 +202,14 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
183
202
|
this.reconnectDelay = options.reconnectDelay ?? 1000;
|
|
184
203
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10;
|
|
185
204
|
this.requestTimeout = options.requestTimeout ?? 30000;
|
|
205
|
+
this.messageTimeout = options.messageTimeout ?? 30000;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get the configured message timeout
|
|
210
|
+
*/
|
|
211
|
+
getMessageTimeout(): number {
|
|
212
|
+
return this.messageTimeout;
|
|
186
213
|
}
|
|
187
214
|
|
|
188
215
|
/**
|
|
@@ -362,8 +389,45 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
362
389
|
return response as Task;
|
|
363
390
|
}
|
|
364
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Respond to an incoming task via WebSocket (completes the task)
|
|
394
|
+
* Use this when you receive a 'message' event and want to send back a response
|
|
395
|
+
* that completes the original task.
|
|
396
|
+
*/
|
|
397
|
+
respond(taskId: string, text: string, options?: { status?: 'completed' | 'failed'; message?: string }): void {
|
|
398
|
+
if (!this.ws || this.ws.readyState !== 1) {
|
|
399
|
+
throw new Error('WebSocket not connected');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const response = {
|
|
403
|
+
type: 'task_response',
|
|
404
|
+
taskId,
|
|
405
|
+
status: {
|
|
406
|
+
state: options?.status ?? 'completed',
|
|
407
|
+
message: options?.message,
|
|
408
|
+
},
|
|
409
|
+
artifact: {
|
|
410
|
+
artifactId: `response-${Date.now()}`,
|
|
411
|
+
mimeType: 'text/plain',
|
|
412
|
+
parts: [{ kind: 'text', text }],
|
|
413
|
+
},
|
|
414
|
+
lastChunk: true,
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
this.ws.send(JSON.stringify(response));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Respond with a failure to an incoming task
|
|
422
|
+
*/
|
|
423
|
+
respondError(taskId: string, errorMessage: string): void {
|
|
424
|
+
this.respond(taskId, errorMessage, { status: 'failed', message: errorMessage });
|
|
425
|
+
}
|
|
426
|
+
|
|
365
427
|
/**
|
|
366
428
|
* Reply to a message/task (sends back to the original caller)
|
|
429
|
+
* Note: This creates a NEW task via HTTP. For completing an existing task,
|
|
430
|
+
* use respond() instead.
|
|
367
431
|
*/
|
|
368
432
|
async reply(taskId: string, payload: MessagePayload, toAgentId?: string): Promise<Task> {
|
|
369
433
|
// If toAgentId not provided, we need to figure out who to reply to
|
|
@@ -394,7 +458,8 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
394
458
|
}
|
|
395
459
|
|
|
396
460
|
/**
|
|
397
|
-
* Reply with text
|
|
461
|
+
* Reply with text (creates new task)
|
|
462
|
+
* Note: For completing an existing task, use respond() instead.
|
|
398
463
|
*/
|
|
399
464
|
async replyText(taskId: string, text: string): Promise<Task> {
|
|
400
465
|
return this.reply(taskId, {
|
|
@@ -480,7 +545,7 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
480
545
|
if (this.ws?.readyState === 1) { // OPEN
|
|
481
546
|
this.ws.send(JSON.stringify({ type: 'ping' }));
|
|
482
547
|
}
|
|
483
|
-
},
|
|
548
|
+
}, 15000); // 15s ping to keep connection alive
|
|
484
549
|
}
|
|
485
550
|
|
|
486
551
|
/**
|