gopherhole_openclaw_a2a 0.3.14 → 0.4.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.d.ts +3 -3
- package/dist/index.js +1 -1
- package/package.json +15 -7
- package/A2A-FIX-GUIDE.md +0 -122
- package/index.ts +0 -232
- package/src/channel.ts +0 -385
- package/src/connection.test.ts +0 -298
- package/src/connection.ts +0 -533
- package/src/gateway-client.ts +0 -328
- package/src/logger.ts +0 -118
- package/src/types.ts +0 -75
- package/test-image.mjs +0 -29
- package/test-image2.mjs +0 -37
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -9
- /package/{clawdbot.plugin.json → openclaw.plugin.json} +0 -0
package/src/gateway-client.ts
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gateway WebSocket Client
|
|
3
|
-
* Sends messages to the Clawdbot gateway via its WebSocket protocol
|
|
4
|
-
*
|
|
5
|
-
* chat.send is non-blocking - it returns a runId immediately and
|
|
6
|
-
* streams the response via chat events. We accumulate deltas and
|
|
7
|
-
* resolve when we get state: "final".
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import WebSocket from 'ws';
|
|
11
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
12
|
-
import { readFileSync } from 'fs';
|
|
13
|
-
import { join } from 'path';
|
|
14
|
-
import { homedir } from 'os';
|
|
15
|
-
|
|
16
|
-
interface PendingRequest {
|
|
17
|
-
resolve: (result: unknown) => void;
|
|
18
|
-
reject: (error: Error) => void;
|
|
19
|
-
timeout: NodeJS.Timeout;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface PendingChat {
|
|
23
|
-
resolve: (result: { text: string }) => void;
|
|
24
|
-
reject: (error: Error) => void;
|
|
25
|
-
timeout: NodeJS.Timeout;
|
|
26
|
-
latestText: string; // Each delta is the full message so far, not incremental
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let ws: WebSocket | null = null;
|
|
30
|
-
let pendingRequests: Map<string, PendingRequest> = new Map();
|
|
31
|
-
let pendingChats: Map<string, PendingChat> = new Map(); // keyed by runId
|
|
32
|
-
let connected = false;
|
|
33
|
-
let handshakeComplete = false;
|
|
34
|
-
|
|
35
|
-
function getGatewayToken(): string | null {
|
|
36
|
-
try {
|
|
37
|
-
const configPath = join(homedir(), '.clawdbot', 'clawdbot.json');
|
|
38
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
39
|
-
return config?.gateway?.auth?.token ?? null;
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function connectToGateway(port = 18789): Promise<void> {
|
|
46
|
-
if (ws && connected && handshakeComplete) return;
|
|
47
|
-
|
|
48
|
-
const token = getGatewayToken();
|
|
49
|
-
|
|
50
|
-
return new Promise((resolve, reject) => {
|
|
51
|
-
ws = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
52
|
-
|
|
53
|
-
const timeout = setTimeout(() => {
|
|
54
|
-
ws?.terminate();
|
|
55
|
-
reject(new Error('Gateway connection timeout'));
|
|
56
|
-
}, 10000);
|
|
57
|
-
|
|
58
|
-
ws.on('open', () => {
|
|
59
|
-
console.log('[a2a] Connected to gateway WebSocket, sending handshake...');
|
|
60
|
-
connected = true;
|
|
61
|
-
|
|
62
|
-
// Send connect handshake
|
|
63
|
-
const connectId = uuidv4();
|
|
64
|
-
|
|
65
|
-
const connectFrame = {
|
|
66
|
-
type: 'req',
|
|
67
|
-
id: connectId,
|
|
68
|
-
method: 'connect',
|
|
69
|
-
params: {
|
|
70
|
-
minProtocol: 3,
|
|
71
|
-
maxProtocol: 3,
|
|
72
|
-
client: {
|
|
73
|
-
id: 'gateway-client',
|
|
74
|
-
displayName: 'A2A Channel Plugin',
|
|
75
|
-
version: '0.3.4',
|
|
76
|
-
platform: process.platform,
|
|
77
|
-
mode: 'backend',
|
|
78
|
-
},
|
|
79
|
-
caps: [],
|
|
80
|
-
auth: token ? { token } : undefined,
|
|
81
|
-
role: 'operator',
|
|
82
|
-
scopes: ['operator.admin'],
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
pendingRequests.set(connectId, {
|
|
87
|
-
resolve: () => {
|
|
88
|
-
clearTimeout(timeout);
|
|
89
|
-
handshakeComplete = true;
|
|
90
|
-
console.log('[a2a] Gateway handshake complete');
|
|
91
|
-
resolve();
|
|
92
|
-
},
|
|
93
|
-
reject: (err) => {
|
|
94
|
-
clearTimeout(timeout);
|
|
95
|
-
reject(err);
|
|
96
|
-
},
|
|
97
|
-
timeout: setTimeout(() => {
|
|
98
|
-
pendingRequests.delete(connectId);
|
|
99
|
-
reject(new Error('Gateway handshake timeout'));
|
|
100
|
-
}, 5000),
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
ws!.send(JSON.stringify(connectFrame));
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
ws.on('message', (data) => {
|
|
107
|
-
try {
|
|
108
|
-
const msg = JSON.parse(data.toString());
|
|
109
|
-
|
|
110
|
-
// Log all non-tick messages for debugging
|
|
111
|
-
if (msg.type !== 'tick') {
|
|
112
|
-
console.log(`[a2a] Gateway msg: type=${msg.type}, event=${msg.event ?? 'n/a'}, id=${msg.id ?? 'n/a'}`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Handle response frames (for RPC calls)
|
|
116
|
-
if (msg.type === 'res' && msg.id && pendingRequests.has(msg.id)) {
|
|
117
|
-
const pending = pendingRequests.get(msg.id)!;
|
|
118
|
-
clearTimeout(pending.timeout);
|
|
119
|
-
pendingRequests.delete(msg.id);
|
|
120
|
-
|
|
121
|
-
if (msg.error) {
|
|
122
|
-
pending.reject(new Error(msg.error.message || 'RPC error'));
|
|
123
|
-
} else {
|
|
124
|
-
// Protocol uses 'payload' not 'result'
|
|
125
|
-
pending.resolve(msg.payload);
|
|
126
|
-
}
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Handle chat events (streaming responses)
|
|
131
|
-
if (msg.type === 'event' && msg.event === 'chat') {
|
|
132
|
-
handleChatEvent(msg.payload);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Handle tick frames (keepalive)
|
|
137
|
-
if (msg.type === 'tick') {
|
|
138
|
-
// Could respond with tick ack if needed
|
|
139
|
-
}
|
|
140
|
-
} catch (err) {
|
|
141
|
-
console.error('[a2a] Failed to parse gateway message:', err);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
ws.on('close', () => {
|
|
146
|
-
connected = false;
|
|
147
|
-
handshakeComplete = false;
|
|
148
|
-
ws = null;
|
|
149
|
-
console.log('[a2a] Disconnected from gateway WebSocket');
|
|
150
|
-
|
|
151
|
-
// Reject all pending chats
|
|
152
|
-
for (const [runId, pending] of pendingChats) {
|
|
153
|
-
clearTimeout(pending.timeout);
|
|
154
|
-
pending.reject(new Error('Gateway connection closed'));
|
|
155
|
-
}
|
|
156
|
-
pendingChats.clear();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
ws.on('error', (err) => {
|
|
160
|
-
clearTimeout(timeout);
|
|
161
|
-
console.error('[a2a] Gateway WebSocket error:', err.message);
|
|
162
|
-
reject(err);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function extractTextContent(content: unknown): string {
|
|
168
|
-
if (typeof content === 'string') {
|
|
169
|
-
return content;
|
|
170
|
-
}
|
|
171
|
-
if (Array.isArray(content)) {
|
|
172
|
-
// Content blocks array: [{ type: 'text', text: '...' }, ...]
|
|
173
|
-
return content
|
|
174
|
-
.filter((block: unknown) =>
|
|
175
|
-
typeof block === 'object' && block !== null &&
|
|
176
|
-
(block as Record<string, unknown>).type === 'text'
|
|
177
|
-
)
|
|
178
|
-
.map((block: unknown) => (block as { text?: string }).text ?? '')
|
|
179
|
-
.join('');
|
|
180
|
-
}
|
|
181
|
-
if (typeof content === 'object' && content !== null) {
|
|
182
|
-
// Single content block or unknown structure
|
|
183
|
-
const obj = content as Record<string, unknown>;
|
|
184
|
-
if (obj.text && typeof obj.text === 'string') {
|
|
185
|
-
return obj.text;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return '';
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function handleChatEvent(payload: {
|
|
192
|
-
runId: string;
|
|
193
|
-
sessionKey: string;
|
|
194
|
-
seq: number;
|
|
195
|
-
state: 'delta' | 'final' | 'aborted' | 'error';
|
|
196
|
-
message?: { role?: string; content?: unknown };
|
|
197
|
-
errorMessage?: string;
|
|
198
|
-
}): void {
|
|
199
|
-
const pending = pendingChats.get(payload.runId);
|
|
200
|
-
if (!pending) {
|
|
201
|
-
// Not a chat we're tracking
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Detailed logging for debugging relay issues
|
|
206
|
-
console.log(`[a2a] Chat event: runId=${payload.runId}, state=${payload.state}, seq=${payload.seq}, role=${payload.message?.role}`);
|
|
207
|
-
|
|
208
|
-
if (payload.state === 'delta' || payload.state === 'final') {
|
|
209
|
-
console.log(`[a2a] Chat content (${payload.state}): ${JSON.stringify(payload.message?.content)?.slice(0, 300)}`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (payload.state === 'delta') {
|
|
213
|
-
// Each delta contains the full message so far, not incremental
|
|
214
|
-
if (payload.message?.role === 'assistant' && payload.message?.content) {
|
|
215
|
-
const text = extractTextContent(payload.message.content);
|
|
216
|
-
if (text) {
|
|
217
|
-
pending.latestText = text;
|
|
218
|
-
console.log(`[a2a] Updated latestText (delta): "${text.slice(0, 100)}..." (len=${text.length})`);
|
|
219
|
-
}
|
|
220
|
-
} else {
|
|
221
|
-
console.log(`[a2a] Skipping delta - role=${payload.message?.role}, hasContent=${!!payload.message?.content}`);
|
|
222
|
-
}
|
|
223
|
-
} else if (payload.state === 'final') {
|
|
224
|
-
// Final message - resolve with latest text
|
|
225
|
-
clearTimeout(pending.timeout);
|
|
226
|
-
pendingChats.delete(payload.runId);
|
|
227
|
-
|
|
228
|
-
// Use final content if available, otherwise use latest delta
|
|
229
|
-
if (payload.message?.role === 'assistant' && payload.message?.content) {
|
|
230
|
-
const text = extractTextContent(payload.message.content);
|
|
231
|
-
if (text) {
|
|
232
|
-
pending.latestText = text;
|
|
233
|
-
console.log(`[a2a] Updated latestText (final): "${text.slice(0, 100)}..." (len=${text.length})`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
console.log(`[a2a] Chat complete - resolving with: "${pending.latestText.slice(0, 150)}..." (total ${pending.latestText.length} chars)`);
|
|
238
|
-
pending.resolve({ text: pending.latestText });
|
|
239
|
-
} else if (payload.state === 'error' || payload.state === 'aborted') {
|
|
240
|
-
clearTimeout(pending.timeout);
|
|
241
|
-
pendingChats.delete(payload.runId);
|
|
242
|
-
console.error(`[a2a] Chat ${payload.state}: ${payload.errorMessage}`);
|
|
243
|
-
pending.reject(new Error(payload.errorMessage || `Chat ${payload.state}`));
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export async function callGateway(method: string, params: Record<string, unknown>, timeoutMs = 300000): Promise<unknown> {
|
|
248
|
-
if (!ws || !connected || !handshakeComplete) {
|
|
249
|
-
await connectToGateway();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const id = uuidv4();
|
|
253
|
-
|
|
254
|
-
return new Promise((resolve, reject) => {
|
|
255
|
-
const timeout = setTimeout(() => {
|
|
256
|
-
pendingRequests.delete(id);
|
|
257
|
-
reject(new Error('Gateway request timeout'));
|
|
258
|
-
}, timeoutMs);
|
|
259
|
-
|
|
260
|
-
pendingRequests.set(id, { resolve, reject, timeout });
|
|
261
|
-
|
|
262
|
-
const frame = {
|
|
263
|
-
type: 'req',
|
|
264
|
-
id,
|
|
265
|
-
method,
|
|
266
|
-
params,
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
ws!.send(JSON.stringify(frame));
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Send a chat message and wait for the full response.
|
|
275
|
-
* chat.send is non-blocking - returns runId immediately, response streams via events.
|
|
276
|
-
*/
|
|
277
|
-
export async function sendChatMessage(
|
|
278
|
-
sessionKey: string,
|
|
279
|
-
message: string,
|
|
280
|
-
timeoutMs = 300000
|
|
281
|
-
): Promise<{ text: string }> {
|
|
282
|
-
if (!ws || !connected || !handshakeComplete) {
|
|
283
|
-
await connectToGateway();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const idempotencyKey = uuidv4();
|
|
287
|
-
|
|
288
|
-
// First, send the chat.send request
|
|
289
|
-
console.log(`[a2a] Sending chat.send: sessionKey=${sessionKey}, idempotencyKey=${idempotencyKey}`);
|
|
290
|
-
|
|
291
|
-
const result = await callGateway('chat.send', {
|
|
292
|
-
sessionKey,
|
|
293
|
-
message,
|
|
294
|
-
idempotencyKey,
|
|
295
|
-
}) as { runId: string; status: string } | undefined;
|
|
296
|
-
|
|
297
|
-
console.log(`[a2a] chat.send raw result:`, JSON.stringify(result));
|
|
298
|
-
|
|
299
|
-
if (!result.runId) {
|
|
300
|
-
throw new Error('chat.send did not return a runId');
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Now wait for chat events to complete
|
|
304
|
-
return new Promise((resolve, reject) => {
|
|
305
|
-
const timeout = setTimeout(() => {
|
|
306
|
-
pendingChats.delete(result.runId);
|
|
307
|
-
reject(new Error('Chat response timeout'));
|
|
308
|
-
}, timeoutMs);
|
|
309
|
-
|
|
310
|
-
pendingChats.set(result.runId, {
|
|
311
|
-
resolve,
|
|
312
|
-
reject,
|
|
313
|
-
timeout,
|
|
314
|
-
latestText: '',
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export function disconnectFromGateway(): void {
|
|
320
|
-
if (ws) {
|
|
321
|
-
ws.close();
|
|
322
|
-
ws = null;
|
|
323
|
-
connected = false;
|
|
324
|
-
handshakeComplete = false;
|
|
325
|
-
}
|
|
326
|
-
pendingChats.clear();
|
|
327
|
-
pendingRequests.clear();
|
|
328
|
-
}
|
package/src/logger.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A2A Plugin Logger
|
|
3
|
-
* Writes to both console and a dedicated log file for dashboard viewing
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { appendFileSync, mkdirSync, existsSync } from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import { homedir } from 'os';
|
|
9
|
-
|
|
10
|
-
const LOG_DIR = join(homedir(), '.clawdbot', 'logs');
|
|
11
|
-
const A2A_LOG_FILE = join(LOG_DIR, 'a2a.log');
|
|
12
|
-
|
|
13
|
-
// Ensure log directory exists
|
|
14
|
-
try {
|
|
15
|
-
if (!existsSync(LOG_DIR)) {
|
|
16
|
-
mkdirSync(LOG_DIR, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
} catch {
|
|
19
|
-
// Ignore if we can't create the directory
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface A2ALogEntry {
|
|
23
|
-
timestamp: string;
|
|
24
|
-
level: 'info' | 'warn' | 'error' | 'debug';
|
|
25
|
-
event: string;
|
|
26
|
-
taskId?: string;
|
|
27
|
-
from?: string;
|
|
28
|
-
to?: string;
|
|
29
|
-
message?: string;
|
|
30
|
-
data?: Record<string, unknown>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function writeLog(entry: A2ALogEntry): void {
|
|
34
|
-
const line = JSON.stringify(entry) + '\n';
|
|
35
|
-
|
|
36
|
-
// Console output (colored)
|
|
37
|
-
const prefix = `[a2a:${entry.event}]`;
|
|
38
|
-
const msg = entry.message || '';
|
|
39
|
-
|
|
40
|
-
switch (entry.level) {
|
|
41
|
-
case 'error':
|
|
42
|
-
console.error(prefix, msg, entry.data || '');
|
|
43
|
-
break;
|
|
44
|
-
case 'warn':
|
|
45
|
-
console.warn(prefix, msg, entry.data || '');
|
|
46
|
-
break;
|
|
47
|
-
default:
|
|
48
|
-
console.log(prefix, msg, entry.data || '');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// File output (JSON lines)
|
|
52
|
-
try {
|
|
53
|
-
appendFileSync(A2A_LOG_FILE, line);
|
|
54
|
-
} catch {
|
|
55
|
-
// Ignore file write errors
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const a2aLog = {
|
|
60
|
-
info(event: string, message: string, data?: Record<string, unknown>): void {
|
|
61
|
-
writeLog({ timestamp: new Date().toISOString(), level: 'info', event, message, data });
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
warn(event: string, message: string, data?: Record<string, unknown>): void {
|
|
65
|
-
writeLog({ timestamp: new Date().toISOString(), level: 'warn', event, message, data });
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
error(event: string, message: string, data?: Record<string, unknown>): void {
|
|
69
|
-
writeLog({ timestamp: new Date().toISOString(), level: 'error', event, message, data });
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
// Structured event logging for dashboard
|
|
73
|
-
messageReceived(from: string, taskId: string | undefined, text: string): void {
|
|
74
|
-
writeLog({
|
|
75
|
-
timestamp: new Date().toISOString(),
|
|
76
|
-
level: 'info',
|
|
77
|
-
event: 'message_received',
|
|
78
|
-
from,
|
|
79
|
-
taskId,
|
|
80
|
-
message: text.slice(0, 200),
|
|
81
|
-
data: { textLength: text.length },
|
|
82
|
-
});
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
messageProcessing(taskId: string | undefined, sessionKey: string): void {
|
|
86
|
-
writeLog({
|
|
87
|
-
timestamp: new Date().toISOString(),
|
|
88
|
-
level: 'info',
|
|
89
|
-
event: 'message_processing',
|
|
90
|
-
taskId,
|
|
91
|
-
data: { sessionKey },
|
|
92
|
-
});
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
responseCaptured(taskId: string | undefined, text: string): void {
|
|
96
|
-
writeLog({
|
|
97
|
-
timestamp: new Date().toISOString(),
|
|
98
|
-
level: 'info',
|
|
99
|
-
event: 'response_captured',
|
|
100
|
-
taskId,
|
|
101
|
-
message: text.slice(0, 200),
|
|
102
|
-
data: { textLength: text.length },
|
|
103
|
-
});
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
responseSent(taskId: string, to: string, success: boolean): void {
|
|
107
|
-
writeLog({
|
|
108
|
-
timestamp: new Date().toISOString(),
|
|
109
|
-
level: success ? 'info' : 'error',
|
|
110
|
-
event: 'response_sent',
|
|
111
|
-
taskId,
|
|
112
|
-
to,
|
|
113
|
-
data: { success },
|
|
114
|
-
});
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export const A2A_LOG_FILE_PATH = A2A_LOG_FILE;
|
package/src/types.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A2A Protocol Types
|
|
3
|
-
* Compatible with @gopherhole/sdk
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface A2AMessage {
|
|
7
|
-
type: 'message' | 'response' | 'chunk' | 'status';
|
|
8
|
-
taskId: string;
|
|
9
|
-
contextId?: string;
|
|
10
|
-
from?: string;
|
|
11
|
-
content?: {
|
|
12
|
-
parts: Array<{ kind: string; text?: string; data?: unknown; mimeType?: string }>;
|
|
13
|
-
};
|
|
14
|
-
status?: 'working' | 'completed' | 'failed' | 'canceled';
|
|
15
|
-
error?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface A2ASkill {
|
|
19
|
-
id: string;
|
|
20
|
-
name: string;
|
|
21
|
-
description?: string;
|
|
22
|
-
tags?: string[];
|
|
23
|
-
examples?: string[];
|
|
24
|
-
inputModes?: string[];
|
|
25
|
-
outputModes?: string[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface A2AAgentCard {
|
|
29
|
-
name: string;
|
|
30
|
-
description?: string;
|
|
31
|
-
url?: string;
|
|
32
|
-
version?: string;
|
|
33
|
-
skills?: A2ASkill[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface A2AResponse {
|
|
37
|
-
text: string;
|
|
38
|
-
status: string;
|
|
39
|
-
from?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* A2A Channel Config (flat structure)
|
|
44
|
-
*
|
|
45
|
-
* Example:
|
|
46
|
-
* {
|
|
47
|
-
* "channels": {
|
|
48
|
-
* "a2a": {
|
|
49
|
-
* "enabled": true,
|
|
50
|
-
* "bridgeUrl": "wss://hub.gopherhole.ai/ws",
|
|
51
|
-
* "apiKey": "gph_your_api_key"
|
|
52
|
-
* }
|
|
53
|
-
* }
|
|
54
|
-
* }
|
|
55
|
-
*/
|
|
56
|
-
export interface A2AChannelConfig {
|
|
57
|
-
enabled?: boolean;
|
|
58
|
-
bridgeUrl?: string; // WebSocket URL (default: wss://hub.gopherhole.ai/ws)
|
|
59
|
-
apiKey?: string; // GopherHole API key (gph_...)
|
|
60
|
-
agentId?: string; // Our agent ID (default: "openclaw")
|
|
61
|
-
agentName?: string; // Display name for agent card
|
|
62
|
-
agentCard?: A2AAgentCard; // Custom agent card (overrides defaults)
|
|
63
|
-
reconnectIntervalMs?: number; // Reconnect delay (default: 5000)
|
|
64
|
-
requestTimeoutMs?: number; // Request timeout (default: 180000)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface ResolvedA2AAccount {
|
|
68
|
-
accountId: string;
|
|
69
|
-
name: string;
|
|
70
|
-
enabled: boolean;
|
|
71
|
-
configured: boolean;
|
|
72
|
-
agentId: string;
|
|
73
|
-
bridgeUrl: string | null;
|
|
74
|
-
config: A2AChannelConfig;
|
|
75
|
-
}
|
package/test-image.mjs
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { GopherHole } from '@gopherhole/sdk';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
3
|
-
|
|
4
|
-
const gph = new GopherHole({
|
|
5
|
-
apiKey: 'gph_a3ed7c3f30e5415e9dc92b72c1c05b78',
|
|
6
|
-
hubUrl: 'wss://gopherhole.ai/ws',
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
await gph.connect();
|
|
10
|
-
console.log('Connected, sending image...');
|
|
11
|
-
|
|
12
|
-
const imagePath = '/Users/brettwaterson/.marketclaw/images/1771822365256-A3kAAzoE.jpg';
|
|
13
|
-
const imageData = readFileSync(imagePath).toString('base64');
|
|
14
|
-
console.log('Image size:', imageData.length, 'chars');
|
|
15
|
-
|
|
16
|
-
const task = await gph.send('agent-70153299', {
|
|
17
|
-
role: 'agent',
|
|
18
|
-
parts: [
|
|
19
|
-
{ kind: 'text', text: 'What do you see in this image? Please describe it.' },
|
|
20
|
-
{ kind: 'data', mimeType: 'image/jpeg', data: imageData },
|
|
21
|
-
],
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
console.log('Task created:', task.id);
|
|
25
|
-
|
|
26
|
-
const completed = await gph.waitForTask(task.id, { maxWaitMs: 60000 });
|
|
27
|
-
console.log('Response:', completed.artifacts?.[0]?.parts?.[0]?.text || JSON.stringify(completed));
|
|
28
|
-
|
|
29
|
-
gph.disconnect();
|
package/test-image2.mjs
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { GopherHole } from '@gopherhole/sdk';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
3
|
-
|
|
4
|
-
const gph = new GopherHole({
|
|
5
|
-
apiKey: 'gph_a3ed7c3f30e5415e9dc92b72c1c05b78',
|
|
6
|
-
hubUrl: 'wss://gopherhole.ai/ws',
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
await gph.connect();
|
|
10
|
-
console.log('Connected as:', gph.id);
|
|
11
|
-
|
|
12
|
-
const imagePath = '/Users/brettwaterson/.marketclaw/images/1771822365256-A3kAAzoE.jpg';
|
|
13
|
-
const imageData = readFileSync(imagePath).toString('base64');
|
|
14
|
-
console.log('Image size:', imageData.length, 'chars (base64)');
|
|
15
|
-
|
|
16
|
-
const payload = {
|
|
17
|
-
role: 'agent',
|
|
18
|
-
parts: [
|
|
19
|
-
{ kind: 'text', text: 'Describe this image please.' },
|
|
20
|
-
{ kind: 'data', mimeType: 'image/jpeg', data: imageData },
|
|
21
|
-
],
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
console.log('Sending payload with', payload.parts.length, 'parts');
|
|
25
|
-
console.log('Part 0:', { kind: payload.parts[0].kind, hasText: !!payload.parts[0].text });
|
|
26
|
-
console.log('Part 1:', { kind: payload.parts[1].kind, mimeType: payload.parts[1].mimeType, dataLen: payload.parts[1].data?.length });
|
|
27
|
-
|
|
28
|
-
const task = await gph.send('agent-70153299', payload);
|
|
29
|
-
console.log('Task:', task.id, 'Status:', task.status?.state);
|
|
30
|
-
|
|
31
|
-
const completed = await gph.waitForTask(task.id, { maxWaitMs: 60000 });
|
|
32
|
-
console.log('Final status:', completed.status?.state);
|
|
33
|
-
|
|
34
|
-
const response = completed.artifacts?.[0]?.parts?.[0]?.text;
|
|
35
|
-
console.log('Response:', response?.slice(0, 300) || 'No text in response');
|
|
36
|
-
|
|
37
|
-
gph.disconnect();
|
package/tsconfig.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"outDir": "dist",
|
|
8
|
-
"rootDir": ".",
|
|
9
|
-
"strict": false,
|
|
10
|
-
"noImplicitAny": false,
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"skipLibCheck": true,
|
|
13
|
-
"forceConsistentCasingInFileNames": true
|
|
14
|
-
},
|
|
15
|
-
"include": ["index.ts", "src/**/*.ts"],
|
|
16
|
-
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
17
|
-
}
|
package/vitest.config.ts
DELETED
|
File without changes
|