@gopherhole/sdk 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +113 -3
- package/dist/index.d.ts +113 -3
- package/dist/index.js +165 -1
- package/dist/index.mjs +164 -1
- package/package.json +1 -1
- package/src/agent.ts +305 -0
- package/src/index.ts +17 -1
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;
|
|
@@ -534,4 +644,4 @@ interface RatingResult {
|
|
|
534
644
|
ratingCount: number;
|
|
535
645
|
}
|
|
536
646
|
|
|
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 };
|
|
647
|
+
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;
|
|
@@ -534,4 +644,4 @@ interface RatingResult {
|
|
|
534
644
|
ratingCount: number;
|
|
535
645
|
}
|
|
536
646
|
|
|
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 };
|
|
647
|
+
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) {
|
|
@@ -344,7 +507,7 @@ var GopherHole = class extends import_eventemitter3.EventEmitter {
|
|
|
344
507
|
if (this.ws?.readyState === 1) {
|
|
345
508
|
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
346
509
|
}
|
|
347
|
-
},
|
|
510
|
+
}, 15e3);
|
|
348
511
|
}
|
|
349
512
|
/**
|
|
350
513
|
* Stop ping interval
|
|
@@ -538,6 +701,7 @@ var index_default = GopherHole;
|
|
|
538
701
|
// Annotate the CommonJS export names for ESM import in node:
|
|
539
702
|
0 && (module.exports = {
|
|
540
703
|
GopherHole,
|
|
704
|
+
GopherHoleAgent,
|
|
541
705
|
JsonRpcErrorCodes,
|
|
542
706
|
getTaskResponseText
|
|
543
707
|
});
|
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) {
|
|
@@ -324,7 +486,7 @@ var GopherHole = class extends EventEmitter {
|
|
|
324
486
|
if (this.ws?.readyState === 1) {
|
|
325
487
|
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
326
488
|
}
|
|
327
|
-
},
|
|
489
|
+
}, 15e3);
|
|
328
490
|
}
|
|
329
491
|
/**
|
|
330
492
|
* Stop ping interval
|
|
@@ -517,6 +679,7 @@ var GopherHole = class extends EventEmitter {
|
|
|
517
679
|
var index_default = GopherHole;
|
|
518
680
|
export {
|
|
519
681
|
GopherHole,
|
|
682
|
+
GopherHoleAgent,
|
|
520
683
|
JsonRpcErrorCodes,
|
|
521
684
|
index_default as default,
|
|
522
685
|
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;
|
|
@@ -480,7 +496,7 @@ export class GopherHole extends EventEmitter<EventMap> {
|
|
|
480
496
|
if (this.ws?.readyState === 1) { // OPEN
|
|
481
497
|
this.ws.send(JSON.stringify({ type: 'ping' }));
|
|
482
498
|
}
|
|
483
|
-
},
|
|
499
|
+
}, 15000); // 15s ping to keep connection alive
|
|
484
500
|
}
|
|
485
501
|
|
|
486
502
|
/**
|