gopherhole_openclaw_a2a 0.3.11 → 0.3.13
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/src/connection.d.ts +7 -0
- package/dist/src/connection.test.d.ts +5 -0
- package/dist/src/connection.test.js +224 -0
- package/package.json +5 -2
- package/src/connection.test.ts +298 -0
- package/src/connection.ts +8 -1
- package/tsconfig.json +1 -1
- package/vitest.config.ts +9 -0
package/dist/src/connection.d.ts
CHANGED
|
@@ -76,8 +76,15 @@ export declare class A2AConnectionManager {
|
|
|
76
76
|
discoverAgents(options?: {
|
|
77
77
|
query?: string;
|
|
78
78
|
category?: string;
|
|
79
|
+
tag?: string;
|
|
80
|
+
skillTag?: string;
|
|
81
|
+
contentMode?: string;
|
|
82
|
+
sort?: string;
|
|
83
|
+
owner?: string;
|
|
79
84
|
verified?: boolean;
|
|
80
85
|
limit?: number;
|
|
86
|
+
offset?: number;
|
|
87
|
+
scope?: string;
|
|
81
88
|
}): Promise<Array<{
|
|
82
89
|
id: string;
|
|
83
90
|
name: string;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for OpenClaw plugin discovery methods
|
|
3
|
+
* Tests the new discovery params: tag, skillTag, contentMode, sort, offset, scope
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { A2AConnectionManager } from './connection';
|
|
7
|
+
// Mock fetch
|
|
8
|
+
const mockFetch = vi.fn();
|
|
9
|
+
global.fetch = mockFetch;
|
|
10
|
+
// Mock @gopherhole/sdk
|
|
11
|
+
vi.mock('@gopherhole/sdk', () => ({
|
|
12
|
+
GopherHole: vi.fn().mockImplementation(() => ({
|
|
13
|
+
connect: vi.fn(),
|
|
14
|
+
disconnect: vi.fn(),
|
|
15
|
+
on: vi.fn(),
|
|
16
|
+
connected: false,
|
|
17
|
+
id: null,
|
|
18
|
+
})),
|
|
19
|
+
getTaskResponseText: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
describe('A2AConnectionManager Discovery', () => {
|
|
22
|
+
let manager;
|
|
23
|
+
const mockApiKey = 'gph_test_key';
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
manager = new A2AConnectionManager({
|
|
26
|
+
enabled: true,
|
|
27
|
+
apiKey: mockApiKey,
|
|
28
|
+
bridgeUrl: 'wss://test.gopherhole.ai/ws',
|
|
29
|
+
});
|
|
30
|
+
mockFetch.mockReset();
|
|
31
|
+
});
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
vi.clearAllMocks();
|
|
34
|
+
});
|
|
35
|
+
const mockDiscoverResponse = {
|
|
36
|
+
agents: [
|
|
37
|
+
{
|
|
38
|
+
id: 'agent-1',
|
|
39
|
+
name: 'Test Agent',
|
|
40
|
+
description: 'A test agent',
|
|
41
|
+
verified: true,
|
|
42
|
+
tenantName: 'TestTenant',
|
|
43
|
+
avgRating: 4.5,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
const setupMockRpcResponse = (result) => {
|
|
48
|
+
mockFetch.mockResolvedValueOnce({
|
|
49
|
+
ok: true,
|
|
50
|
+
json: () => Promise.resolve({ result }),
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
describe('discoverAgents() with new params', () => {
|
|
54
|
+
it('should call discover with tag param', async () => {
|
|
55
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
56
|
+
await manager.discoverAgents({ tag: 'ai' });
|
|
57
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
|
|
58
|
+
method: 'POST',
|
|
59
|
+
body: expect.stringContaining('"tag":"ai"'),
|
|
60
|
+
}));
|
|
61
|
+
});
|
|
62
|
+
it('should call discover with skillTag param', async () => {
|
|
63
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
64
|
+
await manager.discoverAgents({ skillTag: 'nlp' });
|
|
65
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
|
|
66
|
+
body: expect.stringContaining('"skillTag":"nlp"'),
|
|
67
|
+
}));
|
|
68
|
+
});
|
|
69
|
+
it('should call discover with contentMode param', async () => {
|
|
70
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
71
|
+
await manager.discoverAgents({ contentMode: 'text/markdown' });
|
|
72
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
|
|
73
|
+
body: expect.stringContaining('"contentMode":"text/markdown"'),
|
|
74
|
+
}));
|
|
75
|
+
});
|
|
76
|
+
it('should call discover with sort param', async () => {
|
|
77
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
78
|
+
await manager.discoverAgents({ sort: 'rating' });
|
|
79
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
|
|
80
|
+
body: expect.stringContaining('"sort":"rating"'),
|
|
81
|
+
}));
|
|
82
|
+
});
|
|
83
|
+
it('should call discover with offset param', async () => {
|
|
84
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
85
|
+
await manager.discoverAgents({ offset: 20 });
|
|
86
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
|
|
87
|
+
body: expect.stringContaining('"offset":20'),
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
it('should call discover with scope=tenant', async () => {
|
|
91
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
92
|
+
await manager.discoverAgents({ scope: 'tenant' });
|
|
93
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
|
|
94
|
+
body: expect.stringContaining('"scope":"tenant"'),
|
|
95
|
+
}));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('discoverAgents() with combined params', () => {
|
|
99
|
+
it('should combine multiple new params', async () => {
|
|
100
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
101
|
+
await manager.discoverAgents({
|
|
102
|
+
tag: 'ai',
|
|
103
|
+
skillTag: 'nlp',
|
|
104
|
+
sort: 'popular',
|
|
105
|
+
offset: 10,
|
|
106
|
+
});
|
|
107
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
108
|
+
expect(body.params.tag).toBe('ai');
|
|
109
|
+
expect(body.params.skillTag).toBe('nlp');
|
|
110
|
+
expect(body.params.sort).toBe('popular');
|
|
111
|
+
expect(body.params.offset).toBe(10);
|
|
112
|
+
});
|
|
113
|
+
it('should combine query with new params', async () => {
|
|
114
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
115
|
+
await manager.discoverAgents({
|
|
116
|
+
query: 'weather',
|
|
117
|
+
tag: 'api',
|
|
118
|
+
contentMode: 'application/json',
|
|
119
|
+
sort: 'recent',
|
|
120
|
+
limit: 25,
|
|
121
|
+
});
|
|
122
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
123
|
+
expect(body.params.query).toBe('weather');
|
|
124
|
+
expect(body.params.tag).toBe('api');
|
|
125
|
+
expect(body.params.contentMode).toBe('application/json');
|
|
126
|
+
expect(body.params.sort).toBe('recent');
|
|
127
|
+
expect(body.params.limit).toBe(25);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('discoverAgents() edge cases', () => {
|
|
131
|
+
it('should handle empty results', async () => {
|
|
132
|
+
setupMockRpcResponse({ agents: [] });
|
|
133
|
+
const result = await manager.discoverAgents({ tag: 'nonexistent' });
|
|
134
|
+
expect(result).toHaveLength(0);
|
|
135
|
+
});
|
|
136
|
+
it('should handle RPC errors gracefully', async () => {
|
|
137
|
+
mockFetch.mockResolvedValueOnce({
|
|
138
|
+
ok: true,
|
|
139
|
+
json: () => Promise.resolve({ error: { message: 'Not found' } }),
|
|
140
|
+
});
|
|
141
|
+
const result = await manager.discoverAgents({ tag: 'test' });
|
|
142
|
+
expect(result).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
it('should handle network errors', async () => {
|
|
145
|
+
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
146
|
+
const result = await manager.discoverAgents({ tag: 'test' });
|
|
147
|
+
expect(result).toEqual([]);
|
|
148
|
+
});
|
|
149
|
+
it('should return empty array when apiKey not configured', async () => {
|
|
150
|
+
const managerNoKey = new A2AConnectionManager({
|
|
151
|
+
enabled: true,
|
|
152
|
+
// No apiKey
|
|
153
|
+
});
|
|
154
|
+
const result = await managerNoKey.discoverAgents({ tag: 'test' });
|
|
155
|
+
expect(result).toEqual([]);
|
|
156
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('discoverAgents() sort values', () => {
|
|
160
|
+
it('should accept rating sort', async () => {
|
|
161
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
162
|
+
await manager.discoverAgents({ sort: 'rating' });
|
|
163
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
164
|
+
expect(body.params.sort).toBe('rating');
|
|
165
|
+
});
|
|
166
|
+
it('should accept popular sort', async () => {
|
|
167
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
168
|
+
await manager.discoverAgents({ sort: 'popular' });
|
|
169
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
170
|
+
expect(body.params.sort).toBe('popular');
|
|
171
|
+
});
|
|
172
|
+
it('should accept recent sort', async () => {
|
|
173
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
174
|
+
await manager.discoverAgents({ sort: 'recent' });
|
|
175
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
176
|
+
expect(body.params.sort).toBe('recent');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
describe('discoverAgents() response mapping', () => {
|
|
180
|
+
it('should map response correctly', async () => {
|
|
181
|
+
setupMockRpcResponse({
|
|
182
|
+
agents: [
|
|
183
|
+
{
|
|
184
|
+
id: 'agent-1',
|
|
185
|
+
name: 'Test Agent',
|
|
186
|
+
description: 'A test agent',
|
|
187
|
+
verified: true,
|
|
188
|
+
tenantName: 'TestTenant',
|
|
189
|
+
avgRating: 4.5,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
});
|
|
193
|
+
const result = await manager.discoverAgents({ query: 'test' });
|
|
194
|
+
expect(result).toEqual([
|
|
195
|
+
{
|
|
196
|
+
id: 'agent-1',
|
|
197
|
+
name: 'Test Agent',
|
|
198
|
+
description: 'A test agent',
|
|
199
|
+
verified: true,
|
|
200
|
+
tenantName: 'TestTenant',
|
|
201
|
+
avgRating: 4.5,
|
|
202
|
+
},
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe('listAvailableAgents()', () => {
|
|
207
|
+
it('should list available agents', async () => {
|
|
208
|
+
setupMockRpcResponse({
|
|
209
|
+
agents: [
|
|
210
|
+
{
|
|
211
|
+
id: 'agent-1',
|
|
212
|
+
name: 'Test Agent',
|
|
213
|
+
description: 'A test agent',
|
|
214
|
+
verified: true,
|
|
215
|
+
accessType: 'same-tenant',
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
});
|
|
219
|
+
const result = await manager.listAvailableAgents();
|
|
220
|
+
expect(result).toHaveLength(1);
|
|
221
|
+
expect(result[0].accessType).toBe('same-tenant');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gopherhole_openclaw_a2a",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.13",
|
|
4
4
|
"description": "GopherHole A2A plugin for OpenClaw - connect your AI agent to the GopherHole network",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc",
|
|
10
10
|
"clean": "rm -rf dist",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
11
13
|
"prepublishOnly": "npm run build"
|
|
12
14
|
},
|
|
13
15
|
"clawdbot": {
|
|
@@ -37,7 +39,8 @@
|
|
|
37
39
|
"devDependencies": {
|
|
38
40
|
"@types/node": "^25.5.0",
|
|
39
41
|
"@types/uuid": "^10.0.0",
|
|
40
|
-
"typescript": "^5.9.3"
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"vitest": "^1.6.0"
|
|
41
44
|
},
|
|
42
45
|
"peerDependencies": {
|
|
43
46
|
"openclaw": "*"
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for OpenClaw plugin discovery methods
|
|
3
|
+
* Tests the new discovery params: tag, skillTag, contentMode, sort, offset, scope
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { A2AConnectionManager } from './connection';
|
|
8
|
+
|
|
9
|
+
// Mock fetch
|
|
10
|
+
const mockFetch = vi.fn();
|
|
11
|
+
global.fetch = mockFetch;
|
|
12
|
+
|
|
13
|
+
// Mock @gopherhole/sdk
|
|
14
|
+
vi.mock('@gopherhole/sdk', () => ({
|
|
15
|
+
GopherHole: vi.fn().mockImplementation(() => ({
|
|
16
|
+
connect: vi.fn(),
|
|
17
|
+
disconnect: vi.fn(),
|
|
18
|
+
on: vi.fn(),
|
|
19
|
+
connected: false,
|
|
20
|
+
id: null,
|
|
21
|
+
})),
|
|
22
|
+
getTaskResponseText: vi.fn(),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
describe('A2AConnectionManager Discovery', () => {
|
|
26
|
+
let manager: A2AConnectionManager;
|
|
27
|
+
const mockApiKey = 'gph_test_key';
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
manager = new A2AConnectionManager({
|
|
31
|
+
enabled: true,
|
|
32
|
+
apiKey: mockApiKey,
|
|
33
|
+
bridgeUrl: 'wss://test.gopherhole.ai/ws',
|
|
34
|
+
});
|
|
35
|
+
mockFetch.mockReset();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
vi.clearAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const mockDiscoverResponse = {
|
|
43
|
+
agents: [
|
|
44
|
+
{
|
|
45
|
+
id: 'agent-1',
|
|
46
|
+
name: 'Test Agent',
|
|
47
|
+
description: 'A test agent',
|
|
48
|
+
verified: true,
|
|
49
|
+
tenantName: 'TestTenant',
|
|
50
|
+
avgRating: 4.5,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const setupMockRpcResponse = (result: any) => {
|
|
56
|
+
mockFetch.mockResolvedValueOnce({
|
|
57
|
+
ok: true,
|
|
58
|
+
json: () => Promise.resolve({ result }),
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
describe('discoverAgents() with new params', () => {
|
|
63
|
+
it('should call discover with tag param', async () => {
|
|
64
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
65
|
+
|
|
66
|
+
await manager.discoverAgents({ tag: 'ai' });
|
|
67
|
+
|
|
68
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
69
|
+
expect.any(String),
|
|
70
|
+
expect.objectContaining({
|
|
71
|
+
method: 'POST',
|
|
72
|
+
body: expect.stringContaining('"tag":"ai"'),
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should call discover with skillTag param', async () => {
|
|
78
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
79
|
+
|
|
80
|
+
await manager.discoverAgents({ skillTag: 'nlp' });
|
|
81
|
+
|
|
82
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
83
|
+
expect.any(String),
|
|
84
|
+
expect.objectContaining({
|
|
85
|
+
body: expect.stringContaining('"skillTag":"nlp"'),
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should call discover with contentMode param', async () => {
|
|
91
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
92
|
+
|
|
93
|
+
await manager.discoverAgents({ contentMode: 'text/markdown' });
|
|
94
|
+
|
|
95
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
96
|
+
expect.any(String),
|
|
97
|
+
expect.objectContaining({
|
|
98
|
+
body: expect.stringContaining('"contentMode":"text/markdown"'),
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should call discover with sort param', async () => {
|
|
104
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
105
|
+
|
|
106
|
+
await manager.discoverAgents({ sort: 'rating' });
|
|
107
|
+
|
|
108
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
109
|
+
expect.any(String),
|
|
110
|
+
expect.objectContaining({
|
|
111
|
+
body: expect.stringContaining('"sort":"rating"'),
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should call discover with offset param', async () => {
|
|
117
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
118
|
+
|
|
119
|
+
await manager.discoverAgents({ offset: 20 });
|
|
120
|
+
|
|
121
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
122
|
+
expect.any(String),
|
|
123
|
+
expect.objectContaining({
|
|
124
|
+
body: expect.stringContaining('"offset":20'),
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should call discover with scope=tenant', async () => {
|
|
130
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
131
|
+
|
|
132
|
+
await manager.discoverAgents({ scope: 'tenant' });
|
|
133
|
+
|
|
134
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
135
|
+
expect.any(String),
|
|
136
|
+
expect.objectContaining({
|
|
137
|
+
body: expect.stringContaining('"scope":"tenant"'),
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('discoverAgents() with combined params', () => {
|
|
144
|
+
it('should combine multiple new params', async () => {
|
|
145
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
146
|
+
|
|
147
|
+
await manager.discoverAgents({
|
|
148
|
+
tag: 'ai',
|
|
149
|
+
skillTag: 'nlp',
|
|
150
|
+
sort: 'popular',
|
|
151
|
+
offset: 10,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
155
|
+
expect(body.params.tag).toBe('ai');
|
|
156
|
+
expect(body.params.skillTag).toBe('nlp');
|
|
157
|
+
expect(body.params.sort).toBe('popular');
|
|
158
|
+
expect(body.params.offset).toBe(10);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should combine query with new params', async () => {
|
|
162
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
163
|
+
|
|
164
|
+
await manager.discoverAgents({
|
|
165
|
+
query: 'weather',
|
|
166
|
+
tag: 'api',
|
|
167
|
+
contentMode: 'application/json',
|
|
168
|
+
sort: 'recent',
|
|
169
|
+
limit: 25,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
173
|
+
expect(body.params.query).toBe('weather');
|
|
174
|
+
expect(body.params.tag).toBe('api');
|
|
175
|
+
expect(body.params.contentMode).toBe('application/json');
|
|
176
|
+
expect(body.params.sort).toBe('recent');
|
|
177
|
+
expect(body.params.limit).toBe(25);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('discoverAgents() edge cases', () => {
|
|
182
|
+
it('should handle empty results', async () => {
|
|
183
|
+
setupMockRpcResponse({ agents: [] });
|
|
184
|
+
|
|
185
|
+
const result = await manager.discoverAgents({ tag: 'nonexistent' });
|
|
186
|
+
|
|
187
|
+
expect(result).toHaveLength(0);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should handle RPC errors gracefully', async () => {
|
|
191
|
+
mockFetch.mockResolvedValueOnce({
|
|
192
|
+
ok: true,
|
|
193
|
+
json: () => Promise.resolve({ error: { message: 'Not found' } }),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const result = await manager.discoverAgents({ tag: 'test' });
|
|
197
|
+
|
|
198
|
+
expect(result).toEqual([]);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should handle network errors', async () => {
|
|
202
|
+
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
203
|
+
|
|
204
|
+
const result = await manager.discoverAgents({ tag: 'test' });
|
|
205
|
+
|
|
206
|
+
expect(result).toEqual([]);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should return empty array when apiKey not configured', async () => {
|
|
210
|
+
const managerNoKey = new A2AConnectionManager({
|
|
211
|
+
enabled: true,
|
|
212
|
+
// No apiKey
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const result = await managerNoKey.discoverAgents({ tag: 'test' });
|
|
216
|
+
|
|
217
|
+
expect(result).toEqual([]);
|
|
218
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('discoverAgents() sort values', () => {
|
|
223
|
+
it('should accept rating sort', async () => {
|
|
224
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
225
|
+
await manager.discoverAgents({ sort: 'rating' });
|
|
226
|
+
|
|
227
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
228
|
+
expect(body.params.sort).toBe('rating');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should accept popular sort', async () => {
|
|
232
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
233
|
+
await manager.discoverAgents({ sort: 'popular' });
|
|
234
|
+
|
|
235
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
236
|
+
expect(body.params.sort).toBe('popular');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should accept recent sort', async () => {
|
|
240
|
+
setupMockRpcResponse(mockDiscoverResponse);
|
|
241
|
+
await manager.discoverAgents({ sort: 'recent' });
|
|
242
|
+
|
|
243
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
244
|
+
expect(body.params.sort).toBe('recent');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('discoverAgents() response mapping', () => {
|
|
249
|
+
it('should map response correctly', async () => {
|
|
250
|
+
setupMockRpcResponse({
|
|
251
|
+
agents: [
|
|
252
|
+
{
|
|
253
|
+
id: 'agent-1',
|
|
254
|
+
name: 'Test Agent',
|
|
255
|
+
description: 'A test agent',
|
|
256
|
+
verified: true,
|
|
257
|
+
tenantName: 'TestTenant',
|
|
258
|
+
avgRating: 4.5,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const result = await manager.discoverAgents({ query: 'test' });
|
|
264
|
+
|
|
265
|
+
expect(result).toEqual([
|
|
266
|
+
{
|
|
267
|
+
id: 'agent-1',
|
|
268
|
+
name: 'Test Agent',
|
|
269
|
+
description: 'A test agent',
|
|
270
|
+
verified: true,
|
|
271
|
+
tenantName: 'TestTenant',
|
|
272
|
+
avgRating: 4.5,
|
|
273
|
+
},
|
|
274
|
+
]);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('listAvailableAgents()', () => {
|
|
279
|
+
it('should list available agents', async () => {
|
|
280
|
+
setupMockRpcResponse({
|
|
281
|
+
agents: [
|
|
282
|
+
{
|
|
283
|
+
id: 'agent-1',
|
|
284
|
+
name: 'Test Agent',
|
|
285
|
+
description: 'A test agent',
|
|
286
|
+
verified: true,
|
|
287
|
+
accessType: 'same-tenant',
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const result = await manager.listAvailableAgents();
|
|
293
|
+
|
|
294
|
+
expect(result).toHaveLength(1);
|
|
295
|
+
expect(result[0].accessType).toBe('same-tenant');
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|
package/src/connection.ts
CHANGED
|
@@ -423,8 +423,15 @@ export class A2AConnectionManager {
|
|
|
423
423
|
async discoverAgents(options?: {
|
|
424
424
|
query?: string;
|
|
425
425
|
category?: string;
|
|
426
|
-
|
|
426
|
+
tag?: string;
|
|
427
|
+
skillTag?: string;
|
|
428
|
+
contentMode?: string;
|
|
429
|
+
sort?: string;
|
|
430
|
+
owner?: string; // Filter by organization/tenant name
|
|
431
|
+
verified?: boolean; // Only show agents from verified organizations
|
|
427
432
|
limit?: number;
|
|
433
|
+
offset?: number;
|
|
434
|
+
scope?: string;
|
|
428
435
|
}): Promise<Array<{
|
|
429
436
|
id: string;
|
|
430
437
|
name: string;
|
package/tsconfig.json
CHANGED