gopherhole_openclaw_a2a 0.1.3 → 0.2.0

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.js CHANGED
@@ -3,6 +3,8 @@
3
3
  * Enables Clawdbot to communicate with other AI agents via A2A protocol
4
4
  */
5
5
  import { a2aPlugin, setA2ARuntime, getA2AConnectionManager } from './src/channel.js';
6
+ import { readFileSync } from 'fs';
7
+ import { extname } from 'path';
6
8
  const plugin = {
7
9
  id: 'gopherhole_openclaw_a2a',
8
10
  name: 'A2A Protocol',
@@ -29,7 +31,11 @@ const plugin = {
29
31
  },
30
32
  message: {
31
33
  type: 'string',
32
- description: 'Message to send (for send action)',
34
+ description: 'Text message to send (for send action)',
35
+ },
36
+ image: {
37
+ type: 'string',
38
+ description: 'Path to image file to send (for send action)',
33
39
  },
34
40
  },
35
41
  required: ['action'],
@@ -38,6 +44,7 @@ const plugin = {
38
44
  const action = params.action;
39
45
  const agentId = params.agentId;
40
46
  const message = params.message;
47
+ const imagePath = params.image;
41
48
  const manager = getA2AConnectionManager();
42
49
  if (!manager) {
43
50
  return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: 'A2A channel not running' }) }] };
@@ -47,22 +54,52 @@ const plugin = {
47
54
  return { content: [{ type: 'text', text: JSON.stringify({ status: 'ok', agents }) }] };
48
55
  }
49
56
  if (action === 'send') {
50
- if (!agentId || !message) {
51
- return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: 'agentId and message required for send action' }) }] };
57
+ if (!agentId || (!message && !imagePath)) {
58
+ return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: 'agentId and (message or image) required for send action' }) }] };
52
59
  }
53
60
  try {
54
- // Use sendViaGopherHole for remote agents (routes through the hub)
55
- // Use sendMessage for direct connections
56
61
  const isGopherHoleConnected = manager.isGopherHoleConnected();
57
62
  const isDirectConnection = manager.isConnected(agentId) && agentId !== 'gopherhole';
63
+ // Build parts array
64
+ const parts = [];
65
+ // Add text part if message provided
66
+ if (message) {
67
+ parts.push({ kind: 'text', text: message });
68
+ }
69
+ // Add image part if image path provided
70
+ if (imagePath) {
71
+ try {
72
+ const imageData = readFileSync(imagePath);
73
+ const base64Data = imageData.toString('base64');
74
+ const ext = extname(imagePath).toLowerCase();
75
+ const mimeTypes = {
76
+ '.png': 'image/png',
77
+ '.jpg': 'image/jpeg',
78
+ '.jpeg': 'image/jpeg',
79
+ '.gif': 'image/gif',
80
+ '.webp': 'image/webp',
81
+ '.svg': 'image/svg+xml',
82
+ };
83
+ const mimeType = mimeTypes[ext] || 'application/octet-stream';
84
+ parts.push({ kind: 'image', data: base64Data, mimeType });
85
+ }
86
+ catch (imgErr) {
87
+ return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: `Failed to read image: ${imgErr.message}` }) }] };
88
+ }
89
+ }
58
90
  let response;
59
91
  if (isDirectConnection) {
60
- // Direct WebSocket connection to the agent
61
- response = await manager.sendMessage(agentId, message);
92
+ // Direct WebSocket - only supports text for now
93
+ if (message) {
94
+ response = await manager.sendMessage(agentId, message);
95
+ }
96
+ else {
97
+ return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: 'Direct connections only support text messages' }) }] };
98
+ }
62
99
  }
63
100
  else if (isGopherHoleConnected) {
64
- // Route through GopherHole hub
65
- response = await manager.sendViaGopherHole(agentId, message);
101
+ // Route through GopherHole hub with multi-part support
102
+ response = await manager.sendPartsViaGopherHole(agentId, parts);
66
103
  }
67
104
  else {
68
105
  return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: `Cannot reach agent ${agentId} - no direct connection or GopherHole` }) }] };
@@ -1,54 +1,55 @@
1
1
  /**
2
2
  * A2A Connection Manager
3
- * Handles WebSocket connections to other agents and the bridge
3
+ * Uses @gopherhole/sdk for GopherHole hub connectivity
4
4
  */
5
+ import { GopherHole } from '@gopherhole/sdk';
5
6
  import type { A2AMessage, A2AResponse, A2AChannelConfig } from './types.js';
6
7
  export type MessageHandler = (agentId: string, message: A2AMessage) => Promise<void>;
7
8
  export declare class A2AConnectionManager {
8
- private connections;
9
- private pendingRequests;
10
- private reconnectTimers;
9
+ private gopherhole;
11
10
  private messageHandler;
12
11
  private config;
13
12
  private agentId;
13
+ private connected;
14
14
  constructor(config: A2AChannelConfig);
15
15
  setMessageHandler(handler: MessageHandler): void;
16
16
  start(): Promise<void>;
17
17
  private connectToGopherHole;
18
- private establishGopherHoleConnection;
18
+ private handleIncomingMessage;
19
19
  stop(): Promise<void>;
20
- private connectToAgent;
21
- private establishConnection;
22
- private sendAgentAnnounce;
23
- private scheduleReconnect;
24
- private handleMessage;
25
20
  /**
26
- * Resolve a GopherHole task response - extract text from artifacts
21
+ * Send a message to another agent via GopherHole and wait for response
27
22
  */
28
- private resolveGopherHoleTask;
23
+ sendMessage(targetAgentId: string, text: string, _contextId?: string): Promise<A2AResponse>;
29
24
  /**
30
- * Send a message to another agent and wait for response
25
+ * Send a multi-part message via GopherHole hub
26
+ * Supports text, images, and other MIME types
31
27
  */
32
- sendMessage(agentId: string, text: string, contextId?: string): Promise<A2AResponse>;
28
+ sendPartsViaGopherHole(targetAgentId: string, parts: Array<{
29
+ kind: string;
30
+ text?: string;
31
+ data?: string;
32
+ mimeType?: string;
33
+ }>, contextId?: string): Promise<A2AResponse>;
33
34
  /**
34
- * Send a response to an incoming message
35
+ * Send a response to an incoming message via GopherHole
36
+ * Uses SDK's respond() method to complete the original task
35
37
  */
36
- sendResponse(agentId: string, taskId: string, text: string, contextId?: string): void;
38
+ sendResponseViaGopherHole(_targetAgentId: string, taskId: string, text: string, _contextId?: string): void;
37
39
  /**
38
- * Send a response to an agent via GopherHole (for replying to incoming messages)
40
+ * Legacy alias for sendPartsViaGopherHole with text-only
39
41
  */
40
- sendResponseViaGopherHole(targetAgentId: string, taskId: string, text: string, contextId?: string): void;
42
+ sendViaGopherHole(targetAgentId: string, text: string, contextId?: string): Promise<A2AResponse>;
41
43
  /**
42
- * Send a message to a remote agent via GopherHole
43
- * Note: targetAgentId must be the actual agent ID (e.g., "agent-70153299")
44
+ * Legacy sendResponse (routes to GopherHole)
44
45
  */
45
- sendViaGopherHole(targetAgentId: string, text: string, contextId?: string): Promise<A2AResponse>;
46
+ sendResponse(agentId: string, taskId: string, text: string, contextId?: string): void;
46
47
  /**
47
48
  * Check if GopherHole is connected
48
49
  */
49
50
  isGopherHoleConnected(): boolean;
50
51
  /**
51
- * List connected agents
52
+ * List connected agents (just GopherHole for now)
52
53
  */
53
54
  listAgents(): Array<{
54
55
  id: string;
@@ -59,4 +60,8 @@ export declare class A2AConnectionManager {
59
60
  * Check if an agent is connected
60
61
  */
61
62
  isConnected(agentId: string): boolean;
63
+ /**
64
+ * Get the underlying GopherHole SDK instance (for advanced usage)
65
+ */
66
+ getSDK(): GopherHole | null;
62
67
  }