@probelabs/probe 0.6.0-rc100
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/README.md +583 -0
- package/bin/.gitkeep +0 -0
- package/bin/probe +158 -0
- package/bin/probe-binary +0 -0
- package/build/agent/ProbeAgent.d.ts +199 -0
- package/build/agent/ProbeAgent.js +1486 -0
- package/build/agent/acp/README.md +347 -0
- package/build/agent/acp/connection.js +237 -0
- package/build/agent/acp/connection.test.js +311 -0
- package/build/agent/acp/examples/simple-client.js +212 -0
- package/build/agent/acp/examples/tool-lifecycle.js +230 -0
- package/build/agent/acp/final-test.js +173 -0
- package/build/agent/acp/index.js +5 -0
- package/build/agent/acp/integration.test.js +385 -0
- package/build/agent/acp/manual-test.js +410 -0
- package/build/agent/acp/protocol-test.js +190 -0
- package/build/agent/acp/server.js +448 -0
- package/build/agent/acp/server.test.js +371 -0
- package/build/agent/acp/test-runner.js +216 -0
- package/build/agent/acp/test-utils/README.md +315 -0
- package/build/agent/acp/test-utils/acp-tester.js +484 -0
- package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/build/agent/acp/tools.js +368 -0
- package/build/agent/acp/tools.test.js +334 -0
- package/build/agent/acp/types.js +218 -0
- package/build/agent/acp/types.test.js +327 -0
- package/build/agent/appTracer.js +360 -0
- package/build/agent/fileSpanExporter.js +169 -0
- package/build/agent/index.js +7426 -0
- package/build/agent/mcp/client.js +338 -0
- package/build/agent/mcp/config.js +313 -0
- package/build/agent/mcp/index.js +64 -0
- package/build/agent/mcp/xmlBridge.js +371 -0
- package/build/agent/mockProvider.js +53 -0
- package/build/agent/probeTool.js +257 -0
- package/build/agent/schemaUtils.js +1726 -0
- package/build/agent/simpleTelemetry.js +267 -0
- package/build/agent/telemetry.js +225 -0
- package/build/agent/tokenCounter.js +395 -0
- package/build/agent/tools.js +163 -0
- package/build/cli.js +49 -0
- package/build/delegate.js +267 -0
- package/build/directory-resolver.js +237 -0
- package/build/downloader.js +750 -0
- package/build/extract.js +149 -0
- package/build/index.js +70 -0
- package/build/mcp/index.js +514 -0
- package/build/mcp/index.ts +608 -0
- package/build/query.js +116 -0
- package/build/search.js +247 -0
- package/build/tools/common.js +410 -0
- package/build/tools/index.js +40 -0
- package/build/tools/langchain.js +88 -0
- package/build/tools/system-message.js +121 -0
- package/build/tools/vercel.js +271 -0
- package/build/utils/file-lister.js +193 -0
- package/build/utils.js +128 -0
- package/cjs/agent/ProbeAgent.cjs +5829 -0
- package/cjs/index.cjs +6217 -0
- package/cjs/package.json +3 -0
- package/index.d.ts +401 -0
- package/package.json +114 -0
- package/scripts/postinstall.js +172 -0
- package/src/agent/ProbeAgent.d.ts +199 -0
- package/src/agent/ProbeAgent.js +1486 -0
- package/src/agent/acp/README.md +347 -0
- package/src/agent/acp/connection.js +237 -0
- package/src/agent/acp/connection.test.js +311 -0
- package/src/agent/acp/examples/simple-client.js +212 -0
- package/src/agent/acp/examples/tool-lifecycle.js +230 -0
- package/src/agent/acp/final-test.js +173 -0
- package/src/agent/acp/index.js +5 -0
- package/src/agent/acp/integration.test.js +385 -0
- package/src/agent/acp/manual-test.js +410 -0
- package/src/agent/acp/protocol-test.js +190 -0
- package/src/agent/acp/server.js +448 -0
- package/src/agent/acp/server.test.js +371 -0
- package/src/agent/acp/test-runner.js +216 -0
- package/src/agent/acp/test-utils/README.md +315 -0
- package/src/agent/acp/test-utils/acp-tester.js +484 -0
- package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/src/agent/acp/tools.js +368 -0
- package/src/agent/acp/tools.test.js +334 -0
- package/src/agent/acp/types.js +218 -0
- package/src/agent/acp/types.test.js +327 -0
- package/src/agent/appTracer.js +360 -0
- package/src/agent/fileSpanExporter.js +169 -0
- package/src/agent/index.js +813 -0
- package/src/agent/mcp/client.js +338 -0
- package/src/agent/mcp/config.js +313 -0
- package/src/agent/mcp/index.js +64 -0
- package/src/agent/mcp/xmlBridge.js +371 -0
- package/src/agent/mockProvider.js +53 -0
- package/src/agent/probeTool.js +257 -0
- package/src/agent/schemaUtils.js +1726 -0
- package/src/agent/simpleTelemetry.js +267 -0
- package/src/agent/telemetry.js +225 -0
- package/src/agent/tokenCounter.js +395 -0
- package/src/agent/tools.js +163 -0
- package/src/cli.js +49 -0
- package/src/delegate.js +267 -0
- package/src/directory-resolver.js +237 -0
- package/src/downloader.js +750 -0
- package/src/extract.js +149 -0
- package/src/index.js +70 -0
- package/src/mcp/index.ts +608 -0
- package/src/query.js +116 -0
- package/src/search.js +247 -0
- package/src/tools/common.js +410 -0
- package/src/tools/index.js +40 -0
- package/src/tools/langchain.js +88 -0
- package/src/tools/system-message.js +121 -0
- package/src/tools/vercel.js +271 -0
- package/src/utils/file-lister.js +193 -0
- package/src/utils.js +128 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// Tests for ACP Server
|
|
2
|
+
import { jest } from '@jest/globals';
|
|
3
|
+
import { ACPServer } from './server.js';
|
|
4
|
+
import { ACPConnection } from './connection.js';
|
|
5
|
+
import { ACP_PROTOCOL_VERSION, RequestMethod, SessionMode, ErrorCode } from './types.js';
|
|
6
|
+
|
|
7
|
+
// Mock manually handled below
|
|
8
|
+
|
|
9
|
+
describe('ACPServer', () => {
|
|
10
|
+
let server;
|
|
11
|
+
let mockConnection;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Reset mocks
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
|
|
17
|
+
// Create mock connection
|
|
18
|
+
mockConnection = {
|
|
19
|
+
start: jest.fn(),
|
|
20
|
+
sendResponse: jest.fn(),
|
|
21
|
+
sendError: jest.fn(),
|
|
22
|
+
sendNotification: jest.fn(),
|
|
23
|
+
on: jest.fn()
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Mock the ACPConnection constructor
|
|
27
|
+
ACPConnection.mockImplementation(() => mockConnection);
|
|
28
|
+
|
|
29
|
+
server = new ACPServer({
|
|
30
|
+
debug: true,
|
|
31
|
+
provider: 'anthropic',
|
|
32
|
+
path: '/test/path'
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('initialization', () => {
|
|
37
|
+
test('should create server with default options', () => {
|
|
38
|
+
const defaultServer = new ACPServer();
|
|
39
|
+
expect(defaultServer.options.debug).toBe(false);
|
|
40
|
+
expect(defaultServer.sessions.size).toBe(0);
|
|
41
|
+
expect(defaultServer.initialized).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('should create server with custom options', () => {
|
|
45
|
+
expect(server.options.debug).toBe(true);
|
|
46
|
+
expect(server.options.provider).toBe('anthropic');
|
|
47
|
+
expect(server.options.path).toBe('/test/path');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should have correct capabilities', () => {
|
|
51
|
+
const capabilities = server.getCapabilities();
|
|
52
|
+
expect(capabilities.tools).toHaveLength(3);
|
|
53
|
+
expect(capabilities.tools[0].name).toBe('search');
|
|
54
|
+
expect(capabilities.tools[1].name).toBe('query');
|
|
55
|
+
expect(capabilities.tools[2].name).toBe('extract');
|
|
56
|
+
expect(capabilities.sessionManagement).toBe(true);
|
|
57
|
+
expect(capabilities.streaming).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('server startup', () => {
|
|
62
|
+
test('should start server and setup connection', async () => {
|
|
63
|
+
await server.start();
|
|
64
|
+
|
|
65
|
+
expect(ACPConnection).toHaveBeenCalledWith(process.stdin, process.stderr);
|
|
66
|
+
expect(mockConnection.on).toHaveBeenCalledWith('request', expect.any(Function));
|
|
67
|
+
expect(mockConnection.on).toHaveBeenCalledWith('notification', expect.any(Function));
|
|
68
|
+
expect(mockConnection.on).toHaveBeenCalledWith('error', expect.any(Function));
|
|
69
|
+
expect(mockConnection.on).toHaveBeenCalledWith('disconnect', expect.any(Function));
|
|
70
|
+
expect(mockConnection.start).toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('initialize request', () => {
|
|
75
|
+
test('should handle valid initialize request', async () => {
|
|
76
|
+
const params = { protocolVersion: ACP_PROTOCOL_VERSION };
|
|
77
|
+
|
|
78
|
+
const result = await server.handleInitialize(params);
|
|
79
|
+
|
|
80
|
+
expect(result).toEqual({
|
|
81
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
82
|
+
serverInfo: {
|
|
83
|
+
name: 'probe-agent-acp',
|
|
84
|
+
version: '1.0.0',
|
|
85
|
+
description: 'Probe AI agent with code search capabilities'
|
|
86
|
+
},
|
|
87
|
+
capabilities: server.capabilities
|
|
88
|
+
});
|
|
89
|
+
expect(server.initialized).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should reject invalid protocol version', async () => {
|
|
93
|
+
const params = { protocolVersion: '2.0' };
|
|
94
|
+
|
|
95
|
+
await expect(server.handleInitialize(params)).rejects.toThrow('Unsupported protocol version: 2.0');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('should require protocolVersion parameter', async () => {
|
|
99
|
+
await expect(server.handleInitialize({})).rejects.toThrow('Invalid params: protocolVersion required');
|
|
100
|
+
await expect(server.handleInitialize(null)).rejects.toThrow('Invalid params: protocolVersion required');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('session management', () => {
|
|
105
|
+
test('should create new session', async () => {
|
|
106
|
+
const result = await server.handleNewSession({});
|
|
107
|
+
|
|
108
|
+
expect(result.sessionId).toBeDefined();
|
|
109
|
+
expect(result.mode).toBe(SessionMode.NORMAL);
|
|
110
|
+
expect(result.createdAt).toBeDefined();
|
|
111
|
+
expect(server.sessions.has(result.sessionId)).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('should create session with custom ID and mode', async () => {
|
|
115
|
+
const customId = 'test-session-123';
|
|
116
|
+
const params = {
|
|
117
|
+
sessionId: customId,
|
|
118
|
+
mode: SessionMode.PLANNING
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const result = await server.handleNewSession(params);
|
|
122
|
+
|
|
123
|
+
expect(result.sessionId).toBe(customId);
|
|
124
|
+
expect(result.mode).toBe(SessionMode.PLANNING);
|
|
125
|
+
expect(server.sessions.has(customId)).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should load existing session', async () => {
|
|
129
|
+
// Create a session first
|
|
130
|
+
const createResult = await server.handleNewSession({});
|
|
131
|
+
const sessionId = createResult.sessionId;
|
|
132
|
+
|
|
133
|
+
// Load the session
|
|
134
|
+
const loadResult = await server.handleLoadSession({ sessionId });
|
|
135
|
+
|
|
136
|
+
expect(loadResult.id).toBe(sessionId);
|
|
137
|
+
expect(loadResult.mode).toBe(SessionMode.NORMAL);
|
|
138
|
+
expect(loadResult.historyLength).toBe(0);
|
|
139
|
+
expect(loadResult.toolCallsCount).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('should fail to load non-existent session', async () => {
|
|
143
|
+
const params = { sessionId: 'non-existent' };
|
|
144
|
+
|
|
145
|
+
await expect(server.handleLoadSession(params)).rejects.toThrow('Session not found: non-existent');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('should set session mode', async () => {
|
|
149
|
+
// Create a session first
|
|
150
|
+
const createResult = await server.handleNewSession({});
|
|
151
|
+
const sessionId = createResult.sessionId;
|
|
152
|
+
|
|
153
|
+
// Set mode
|
|
154
|
+
const result = await server.handleSetSessionMode({
|
|
155
|
+
sessionId,
|
|
156
|
+
mode: SessionMode.PLANNING
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result.success).toBe(true);
|
|
160
|
+
expect(mockConnection.sendNotification).toHaveBeenCalledWith(
|
|
161
|
+
'sessionUpdated',
|
|
162
|
+
{ sessionId, mode: SessionMode.PLANNING }
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const session = server.sessions.get(sessionId);
|
|
166
|
+
expect(session.mode).toBe(SessionMode.PLANNING);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('request handling', () => {
|
|
171
|
+
test('should handle requests and send responses', async () => {
|
|
172
|
+
const message = {
|
|
173
|
+
method: RequestMethod.INITIALIZE,
|
|
174
|
+
params: { protocolVersion: ACP_PROTOCOL_VERSION },
|
|
175
|
+
id: 1
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
await server.handleRequest(message);
|
|
179
|
+
|
|
180
|
+
expect(mockConnection.sendResponse).toHaveBeenCalledWith(1, expect.objectContaining({
|
|
181
|
+
protocolVersion: ACP_PROTOCOL_VERSION
|
|
182
|
+
}));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('should handle unknown methods', async () => {
|
|
186
|
+
const message = {
|
|
187
|
+
method: 'unknownMethod',
|
|
188
|
+
params: {},
|
|
189
|
+
id: 2
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
await server.handleRequest(message);
|
|
193
|
+
|
|
194
|
+
expect(mockConnection.sendError).toHaveBeenCalledWith(
|
|
195
|
+
2,
|
|
196
|
+
ErrorCode.METHOD_NOT_FOUND,
|
|
197
|
+
'Unknown method: unknownMethod'
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('should handle request errors', async () => {
|
|
202
|
+
const message = {
|
|
203
|
+
method: RequestMethod.LOAD_SESSION,
|
|
204
|
+
params: { sessionId: 'invalid' },
|
|
205
|
+
id: 3
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
await server.handleRequest(message);
|
|
209
|
+
|
|
210
|
+
expect(mockConnection.sendError).toHaveBeenCalledWith(
|
|
211
|
+
3,
|
|
212
|
+
ErrorCode.INTERNAL_ERROR,
|
|
213
|
+
'Session not found: invalid'
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('prompt handling', () => {
|
|
219
|
+
let mockAgent;
|
|
220
|
+
|
|
221
|
+
beforeEach(async () => {
|
|
222
|
+
// Import and mock ProbeAgent
|
|
223
|
+
const { ProbeAgent } = await import('../ProbeAgent.js');
|
|
224
|
+
mockAgent = {
|
|
225
|
+
answer: jest.fn().mockResolvedValue('Test response'),
|
|
226
|
+
cancel: jest.fn()
|
|
227
|
+
};
|
|
228
|
+
ProbeAgent.mockImplementation(() => mockAgent);
|
|
229
|
+
|
|
230
|
+
// Create a session
|
|
231
|
+
await server.handleNewSession({ sessionId: 'test-session' });
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('should handle prompt request successfully', async () => {
|
|
235
|
+
const params = {
|
|
236
|
+
sessionId: 'test-session',
|
|
237
|
+
message: 'Test question'
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const result = await server.handlePrompt(params);
|
|
241
|
+
|
|
242
|
+
expect(mockAgent.answer).toHaveBeenCalledWith('Test question');
|
|
243
|
+
expect(result).toEqual({
|
|
244
|
+
content: [{ type: 'text', text: 'Test response' }],
|
|
245
|
+
sessionId: 'test-session',
|
|
246
|
+
timestamp: expect.any(String)
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Check that history was updated
|
|
250
|
+
const session = server.sessions.get('test-session');
|
|
251
|
+
expect(session.history).toHaveLength(2);
|
|
252
|
+
expect(session.history[0].role).toBe('user');
|
|
253
|
+
expect(session.history[1].role).toBe('assistant');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('should handle prompt errors gracefully', async () => {
|
|
257
|
+
mockAgent.answer.mockRejectedValue(new Error('AI Error'));
|
|
258
|
+
|
|
259
|
+
const params = {
|
|
260
|
+
sessionId: 'test-session',
|
|
261
|
+
message: 'Test question'
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const result = await server.handlePrompt(params);
|
|
265
|
+
|
|
266
|
+
expect(result).toEqual({
|
|
267
|
+
content: [{ type: 'text', text: 'Error: AI Error' }],
|
|
268
|
+
sessionId: 'test-session',
|
|
269
|
+
timestamp: expect.any(String),
|
|
270
|
+
error: true
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('should require sessionId and message', async () => {
|
|
275
|
+
await expect(server.handlePrompt({})).rejects.toThrow('Invalid params: sessionId and message required');
|
|
276
|
+
await expect(server.handlePrompt({ sessionId: 'test' })).rejects.toThrow('Invalid params: sessionId and message required');
|
|
277
|
+
await expect(server.handlePrompt({ message: 'test' })).rejects.toThrow('Invalid params: sessionId and message required');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('should require valid session', async () => {
|
|
281
|
+
const params = {
|
|
282
|
+
sessionId: 'invalid-session',
|
|
283
|
+
message: 'Test question'
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
await expect(server.handlePrompt(params)).rejects.toThrow('Session not found: invalid-session');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('cancel handling', () => {
|
|
291
|
+
let mockAgent;
|
|
292
|
+
|
|
293
|
+
beforeEach(async () => {
|
|
294
|
+
const { ProbeAgent } = await import('../ProbeAgent.js');
|
|
295
|
+
mockAgent = {
|
|
296
|
+
answer: jest.fn().mockResolvedValue('Test response'),
|
|
297
|
+
cancel: jest.fn()
|
|
298
|
+
};
|
|
299
|
+
ProbeAgent.mockImplementation(() => mockAgent);
|
|
300
|
+
|
|
301
|
+
// Create a session and trigger agent creation
|
|
302
|
+
await server.handleNewSession({ sessionId: 'test-session' });
|
|
303
|
+
await server.handlePrompt({
|
|
304
|
+
sessionId: 'test-session',
|
|
305
|
+
message: 'Test question'
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('should cancel session operations', async () => {
|
|
310
|
+
const result = await server.handleCancel({ sessionId: 'test-session' });
|
|
311
|
+
|
|
312
|
+
expect(mockAgent.cancel).toHaveBeenCalled();
|
|
313
|
+
expect(result.success).toBe(true);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('should handle cancel for session without agent', async () => {
|
|
317
|
+
await server.handleNewSession({ sessionId: 'new-session' });
|
|
318
|
+
|
|
319
|
+
const result = await server.handleCancel({ sessionId: 'new-session' });
|
|
320
|
+
expect(result.success).toBe(true);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test('should require sessionId for cancel', async () => {
|
|
324
|
+
await expect(server.handleCancel({})).rejects.toThrow('Invalid params: sessionId required');
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('disconnect handling', () => {
|
|
329
|
+
let mockAgent;
|
|
330
|
+
|
|
331
|
+
beforeEach(async () => {
|
|
332
|
+
const { ProbeAgent } = await import('../ProbeAgent.js');
|
|
333
|
+
mockAgent = {
|
|
334
|
+
answer: jest.fn().mockResolvedValue('Test response'),
|
|
335
|
+
cancel: jest.fn()
|
|
336
|
+
};
|
|
337
|
+
ProbeAgent.mockImplementation(() => mockAgent);
|
|
338
|
+
|
|
339
|
+
// Create sessions with agents
|
|
340
|
+
await server.handleNewSession({ sessionId: 'session1' });
|
|
341
|
+
await server.handleNewSession({ sessionId: 'session2' });
|
|
342
|
+
await server.handlePrompt({ sessionId: 'session1', message: 'test' });
|
|
343
|
+
await server.handlePrompt({ sessionId: 'session2', message: 'test' });
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('should clean up sessions and cancel agents on disconnect', () => {
|
|
347
|
+
expect(server.sessions.size).toBe(2);
|
|
348
|
+
|
|
349
|
+
server.handleDisconnect();
|
|
350
|
+
|
|
351
|
+
expect(mockAgent.cancel).toHaveBeenCalledTimes(2);
|
|
352
|
+
expect(server.sessions.size).toBe(0);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe('stats', () => {
|
|
357
|
+
test('should return server statistics', async () => {
|
|
358
|
+
await server.handleNewSession({});
|
|
359
|
+
await server.handleNewSession({});
|
|
360
|
+
server.initialized = true;
|
|
361
|
+
|
|
362
|
+
const stats = server.getStats();
|
|
363
|
+
|
|
364
|
+
expect(stats).toEqual({
|
|
365
|
+
sessions: 2,
|
|
366
|
+
initialized: true,
|
|
367
|
+
capabilities: server.capabilities
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple test runner for ACP implementation
|
|
5
|
+
* Tests basic functionality without requiring a full test framework
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ACPServer } from './server.js';
|
|
9
|
+
import { ACPConnection } from './connection.js';
|
|
10
|
+
import { validateMessage, createMessage, createResponse, ACP_PROTOCOL_VERSION } from './types.js';
|
|
11
|
+
import { EventEmitter } from 'events';
|
|
12
|
+
|
|
13
|
+
class TestRunner {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.tests = [];
|
|
16
|
+
this.passed = 0;
|
|
17
|
+
this.failed = 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test(name, fn) {
|
|
21
|
+
this.tests.push({ name, fn });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
assert(condition, message) {
|
|
25
|
+
if (!condition) {
|
|
26
|
+
throw new Error(message);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
assertEqual(actual, expected, message = '') {
|
|
31
|
+
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
32
|
+
throw new Error(`${message}\nExpected: ${JSON.stringify(expected)}\nActual: ${JSON.stringify(actual)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async run() {
|
|
37
|
+
console.log('🧪 Running ACP Tests');
|
|
38
|
+
console.log('=' .repeat(40));
|
|
39
|
+
|
|
40
|
+
for (const { name, fn } of this.tests) {
|
|
41
|
+
try {
|
|
42
|
+
await fn();
|
|
43
|
+
console.log(`✅ ${name}`);
|
|
44
|
+
this.passed++;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.log(`❌ ${name}`);
|
|
47
|
+
console.log(` Error: ${error.message}`);
|
|
48
|
+
this.failed++;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log('\n📊 Test Results');
|
|
53
|
+
console.log('-'.repeat(20));
|
|
54
|
+
console.log(`Passed: ${this.passed}`);
|
|
55
|
+
console.log(`Failed: ${this.failed}`);
|
|
56
|
+
console.log(`Total: ${this.tests.length}`);
|
|
57
|
+
|
|
58
|
+
if (this.failed > 0) {
|
|
59
|
+
process.exit(1);
|
|
60
|
+
} else {
|
|
61
|
+
console.log('\n🎉 All tests passed!');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const runner = new TestRunner();
|
|
67
|
+
|
|
68
|
+
// Test types and utilities
|
|
69
|
+
runner.test('Message validation', () => {
|
|
70
|
+
// Valid messages
|
|
71
|
+
runner.assert(validateMessage({ jsonrpc: '2.0', method: 'test' }).valid, 'Valid notification should pass');
|
|
72
|
+
runner.assert(validateMessage({ jsonrpc: '2.0', method: 'test', id: 1 }).valid, 'Valid request should pass');
|
|
73
|
+
runner.assert(validateMessage({ jsonrpc: '2.0', id: 1, result: {} }).valid, 'Valid response should pass');
|
|
74
|
+
|
|
75
|
+
// Invalid messages
|
|
76
|
+
runner.assert(!validateMessage(null).valid, 'Null should fail');
|
|
77
|
+
runner.assert(!validateMessage({ jsonrpc: '1.0', method: 'test' }).valid, 'Wrong version should fail');
|
|
78
|
+
runner.assert(!validateMessage({ jsonrpc: '2.0', id: 1 }).valid, 'Response without result/error should fail');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
runner.test('Message creation', () => {
|
|
82
|
+
const message = createMessage('test', { param: 'value' }, 123);
|
|
83
|
+
runner.assertEqual(message, {
|
|
84
|
+
jsonrpc: '2.0',
|
|
85
|
+
method: 'test',
|
|
86
|
+
params: { param: 'value' },
|
|
87
|
+
id: 123
|
|
88
|
+
}, 'Message creation should work correctly');
|
|
89
|
+
|
|
90
|
+
const response = createResponse(456, { success: true });
|
|
91
|
+
runner.assertEqual(response, {
|
|
92
|
+
jsonrpc: '2.0',
|
|
93
|
+
id: 456,
|
|
94
|
+
result: { success: true }
|
|
95
|
+
}, 'Response creation should work correctly');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Test server capabilities
|
|
99
|
+
runner.test('Server capabilities', () => {
|
|
100
|
+
const server = new ACPServer();
|
|
101
|
+
const capabilities = server.getCapabilities();
|
|
102
|
+
|
|
103
|
+
runner.assert(Array.isArray(capabilities.tools), 'Should have tools array');
|
|
104
|
+
runner.assert(capabilities.tools.length === 3, 'Should have 3 tools');
|
|
105
|
+
runner.assert(capabilities.sessionManagement === true, 'Should support sessions');
|
|
106
|
+
runner.assert(capabilities.streaming === true, 'Should support streaming');
|
|
107
|
+
|
|
108
|
+
const toolNames = capabilities.tools.map(t => t.name);
|
|
109
|
+
runner.assert(toolNames.includes('search'), 'Should have search tool');
|
|
110
|
+
runner.assert(toolNames.includes('query'), 'Should have query tool');
|
|
111
|
+
runner.assert(toolNames.includes('extract'), 'Should have extract tool');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Test server initialization
|
|
115
|
+
runner.test('Server initialization', async () => {
|
|
116
|
+
const server = new ACPServer({ debug: false });
|
|
117
|
+
|
|
118
|
+
// Test valid initialization
|
|
119
|
+
const result = await server.handleInitialize({
|
|
120
|
+
protocolVersion: ACP_PROTOCOL_VERSION
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
runner.assertEqual(result.protocolVersion, ACP_PROTOCOL_VERSION, 'Should return correct version');
|
|
124
|
+
runner.assert(result.serverInfo.name === 'probe-agent-acp', 'Should have correct server name');
|
|
125
|
+
runner.assert(server.initialized === true, 'Server should be initialized');
|
|
126
|
+
|
|
127
|
+
// Test invalid protocol version
|
|
128
|
+
try {
|
|
129
|
+
await server.handleInitialize({ protocolVersion: '2.0' });
|
|
130
|
+
runner.assert(false, 'Should reject invalid protocol version');
|
|
131
|
+
} catch (error) {
|
|
132
|
+
runner.assert(error.message.includes('Unsupported protocol version'), 'Should have version error');
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Test session management
|
|
137
|
+
runner.test('Session management', async () => {
|
|
138
|
+
const server = new ACPServer({ debug: false });
|
|
139
|
+
|
|
140
|
+
// Create session
|
|
141
|
+
const createResult = await server.handleNewSession({});
|
|
142
|
+
runner.assert(createResult.sessionId, 'Should return session ID');
|
|
143
|
+
runner.assert(createResult.mode === 'normal', 'Should default to normal mode');
|
|
144
|
+
runner.assert(server.sessions.has(createResult.sessionId), 'Should store session');
|
|
145
|
+
|
|
146
|
+
// Load session
|
|
147
|
+
const loadResult = await server.handleLoadSession({
|
|
148
|
+
sessionId: createResult.sessionId
|
|
149
|
+
});
|
|
150
|
+
runner.assertEqual(loadResult.id, createResult.sessionId, 'Should load correct session');
|
|
151
|
+
|
|
152
|
+
// Set session mode
|
|
153
|
+
const modeResult = await server.handleSetSessionMode({
|
|
154
|
+
sessionId: createResult.sessionId,
|
|
155
|
+
mode: 'planning'
|
|
156
|
+
});
|
|
157
|
+
runner.assert(modeResult.success === true, 'Should set mode successfully');
|
|
158
|
+
|
|
159
|
+
const session = server.sessions.get(createResult.sessionId);
|
|
160
|
+
runner.assertEqual(session.mode, 'planning', 'Should update session mode');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Test connection (basic functionality)
|
|
164
|
+
runner.test('Connection basics', () => {
|
|
165
|
+
class MockStream extends EventEmitter {
|
|
166
|
+
constructor() {
|
|
167
|
+
super();
|
|
168
|
+
this.encoding = null;
|
|
169
|
+
this.writtenData = [];
|
|
170
|
+
}
|
|
171
|
+
setEncoding(enc) { this.encoding = enc; }
|
|
172
|
+
write(data) { this.writtenData.push(data); return true; }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const input = new MockStream();
|
|
176
|
+
const output = new MockStream();
|
|
177
|
+
const connection = new ACPConnection(input, output);
|
|
178
|
+
|
|
179
|
+
connection.start();
|
|
180
|
+
runner.assert(connection.isConnected === true, 'Should be connected');
|
|
181
|
+
|
|
182
|
+
// Test message sending
|
|
183
|
+
connection.sendNotification('test', { data: 'value' });
|
|
184
|
+
runner.assert(output.writtenData.length === 1, 'Should send notification');
|
|
185
|
+
|
|
186
|
+
const sent = JSON.parse(output.writtenData[0]);
|
|
187
|
+
runner.assertEqual(sent.method, 'test', 'Should send correct method');
|
|
188
|
+
runner.assertEqual(sent.params, { data: 'value' }, 'Should send correct params');
|
|
189
|
+
runner.assert(!sent.id, 'Notification should not have ID');
|
|
190
|
+
|
|
191
|
+
connection.close();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Test error handling
|
|
195
|
+
runner.test('Error handling', async () => {
|
|
196
|
+
const server = new ACPServer({ debug: false });
|
|
197
|
+
|
|
198
|
+
// Test missing session
|
|
199
|
+
try {
|
|
200
|
+
await server.handleLoadSession({ sessionId: 'nonexistent' });
|
|
201
|
+
runner.assert(false, 'Should fail for nonexistent session');
|
|
202
|
+
} catch (error) {
|
|
203
|
+
runner.assert(error.message.includes('Session not found'), 'Should have session error');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Test missing parameters
|
|
207
|
+
try {
|
|
208
|
+
await server.handlePrompt({});
|
|
209
|
+
runner.assert(false, 'Should require sessionId and message');
|
|
210
|
+
} catch (error) {
|
|
211
|
+
runner.assert(error.message.includes('Invalid params'), 'Should have params error');
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Run all tests
|
|
216
|
+
runner.run().catch(console.error);
|