@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,311 @@
|
|
|
1
|
+
// Tests for ACP Connection
|
|
2
|
+
import { jest } from '@jest/globals';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { ACPConnection } from './connection.js';
|
|
5
|
+
import { ErrorCode, RequestMethod } from './types.js';
|
|
6
|
+
|
|
7
|
+
// Mock streams
|
|
8
|
+
class MockStream extends EventEmitter {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.encoding = null;
|
|
12
|
+
this.writtenData = [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setEncoding(encoding) {
|
|
16
|
+
this.encoding = encoding;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
write(data) {
|
|
20
|
+
this.writtenData.push(data);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('ACPConnection', () => {
|
|
26
|
+
let inputStream, outputStream, connection;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
inputStream = new MockStream();
|
|
30
|
+
outputStream = new MockStream();
|
|
31
|
+
connection = new ACPConnection(inputStream, outputStream);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
// Clean up fake timers if they were used
|
|
36
|
+
if (jest.isMockFunction(setTimeout)) {
|
|
37
|
+
jest.useRealTimers();
|
|
38
|
+
}
|
|
39
|
+
connection.close();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('initialization', () => {
|
|
43
|
+
test('should initialize with correct default values', () => {
|
|
44
|
+
expect(connection.isConnected).toBe(false);
|
|
45
|
+
expect(connection.messageId).toBe(1);
|
|
46
|
+
expect(connection.buffer).toBe('');
|
|
47
|
+
expect(connection.pendingRequests.size).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should setup streams correctly', () => {
|
|
51
|
+
connection.start();
|
|
52
|
+
expect(connection.isConnected).toBe(true);
|
|
53
|
+
expect(inputStream.encoding).toBe('utf8');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('message handling', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
connection.start();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('should parse valid JSON-RPC messages', (done) => {
|
|
63
|
+
const message = {
|
|
64
|
+
jsonrpc: '2.0',
|
|
65
|
+
method: RequestMethod.INITIALIZE,
|
|
66
|
+
id: 1,
|
|
67
|
+
params: { protocolVersion: '1' }
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
connection.on('request', (receivedMessage) => {
|
|
71
|
+
expect(receivedMessage).toEqual(message);
|
|
72
|
+
done();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
inputStream.emit('data', JSON.stringify(message) + '\n');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should handle invalid JSON gracefully', () => {
|
|
79
|
+
inputStream.emit('data', 'invalid json\n');
|
|
80
|
+
|
|
81
|
+
// Should send parse error
|
|
82
|
+
expect(outputStream.writtenData).toHaveLength(1);
|
|
83
|
+
const response = JSON.parse(outputStream.writtenData[0]);
|
|
84
|
+
expect(response.error.code).toBe(ErrorCode.PARSE_ERROR);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('should handle notifications', (done) => {
|
|
88
|
+
const notification = {
|
|
89
|
+
jsonrpc: '2.0',
|
|
90
|
+
method: 'someNotification',
|
|
91
|
+
params: { data: 'test' }
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
connection.on('notification', (receivedMessage) => {
|
|
95
|
+
expect(receivedMessage).toEqual(notification);
|
|
96
|
+
done();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
inputStream.emit('data', JSON.stringify(notification) + '\n');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should validate message format', () => {
|
|
103
|
+
const invalidMessage = {
|
|
104
|
+
jsonrpc: '1.0', // Wrong version
|
|
105
|
+
method: 'test'
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
inputStream.emit('data', JSON.stringify(invalidMessage) + '\n');
|
|
109
|
+
|
|
110
|
+
// Should send invalid request error
|
|
111
|
+
expect(outputStream.writtenData).toHaveLength(1);
|
|
112
|
+
const response = JSON.parse(outputStream.writtenData[0]);
|
|
113
|
+
expect(response.error.code).toBe(ErrorCode.INVALID_REQUEST);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('sending messages', () => {
|
|
118
|
+
beforeEach(() => {
|
|
119
|
+
connection.start();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('should send requests with auto-incrementing IDs', async () => {
|
|
123
|
+
const params = { test: 'data' };
|
|
124
|
+
|
|
125
|
+
// Don't await - we'll resolve it manually
|
|
126
|
+
const requestPromise = connection.sendRequest('testMethod', params);
|
|
127
|
+
|
|
128
|
+
// Check that request was sent
|
|
129
|
+
expect(outputStream.writtenData).toHaveLength(1);
|
|
130
|
+
const sentMessage = JSON.parse(outputStream.writtenData[0]);
|
|
131
|
+
expect(sentMessage).toEqual({
|
|
132
|
+
jsonrpc: '2.0',
|
|
133
|
+
method: 'testMethod',
|
|
134
|
+
params,
|
|
135
|
+
id: 1
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Simulate response
|
|
139
|
+
const response = {
|
|
140
|
+
jsonrpc: '2.0',
|
|
141
|
+
id: 1,
|
|
142
|
+
result: { success: true }
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
inputStream.emit('data', JSON.stringify(response) + '\n');
|
|
146
|
+
|
|
147
|
+
const result = await requestPromise;
|
|
148
|
+
expect(result).toEqual({ success: true });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('should send notifications without ID', () => {
|
|
152
|
+
const params = { notification: 'data' };
|
|
153
|
+
|
|
154
|
+
connection.sendNotification('testNotification', params);
|
|
155
|
+
|
|
156
|
+
expect(outputStream.writtenData).toHaveLength(1);
|
|
157
|
+
const sentMessage = JSON.parse(outputStream.writtenData[0]);
|
|
158
|
+
expect(sentMessage).toEqual({
|
|
159
|
+
jsonrpc: '2.0',
|
|
160
|
+
method: 'testNotification',
|
|
161
|
+
params
|
|
162
|
+
});
|
|
163
|
+
expect(sentMessage.id).toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('should send responses', () => {
|
|
167
|
+
const result = { data: 'test' };
|
|
168
|
+
|
|
169
|
+
connection.sendResponse(123, result);
|
|
170
|
+
|
|
171
|
+
expect(outputStream.writtenData).toHaveLength(1);
|
|
172
|
+
const sentMessage = JSON.parse(outputStream.writtenData[0]);
|
|
173
|
+
expect(sentMessage).toEqual({
|
|
174
|
+
jsonrpc: '2.0',
|
|
175
|
+
id: 123,
|
|
176
|
+
result
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('should send errors', () => {
|
|
181
|
+
connection.sendError(456, ErrorCode.INTERNAL_ERROR, 'Test error', { detail: 'test' });
|
|
182
|
+
|
|
183
|
+
expect(outputStream.writtenData).toHaveLength(1);
|
|
184
|
+
const sentMessage = JSON.parse(outputStream.writtenData[0]);
|
|
185
|
+
expect(sentMessage).toEqual({
|
|
186
|
+
jsonrpc: '2.0',
|
|
187
|
+
id: 456,
|
|
188
|
+
error: {
|
|
189
|
+
code: ErrorCode.INTERNAL_ERROR,
|
|
190
|
+
message: 'Test error',
|
|
191
|
+
data: { detail: 'test' }
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('request/response handling', () => {
|
|
198
|
+
beforeEach(() => {
|
|
199
|
+
connection.start();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('should handle response errors', async () => {
|
|
203
|
+
const requestPromise = connection.sendRequest('testMethod');
|
|
204
|
+
|
|
205
|
+
const errorResponse = {
|
|
206
|
+
jsonrpc: '2.0',
|
|
207
|
+
id: 1,
|
|
208
|
+
error: {
|
|
209
|
+
code: ErrorCode.METHOD_NOT_FOUND,
|
|
210
|
+
message: 'Method not found'
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
inputStream.emit('data', JSON.stringify(errorResponse) + '\n');
|
|
215
|
+
|
|
216
|
+
await expect(requestPromise).rejects.toThrow('RPC Error -32601: Method not found');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('should timeout requests', async () => {
|
|
220
|
+
// Set up fake timers before creating the promise
|
|
221
|
+
jest.useFakeTimers();
|
|
222
|
+
|
|
223
|
+
const requestPromise = connection.sendRequest('testMethod');
|
|
224
|
+
|
|
225
|
+
// Fast-forward time past the timeout
|
|
226
|
+
jest.advanceTimersByTime(30000);
|
|
227
|
+
|
|
228
|
+
await expect(requestPromise).rejects.toThrow('Request timeout');
|
|
229
|
+
|
|
230
|
+
jest.useRealTimers();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('connection management', () => {
|
|
235
|
+
test('should handle disconnection', (done) => {
|
|
236
|
+
connection.start();
|
|
237
|
+
|
|
238
|
+
connection.on('disconnect', () => {
|
|
239
|
+
expect(connection.isConnected).toBe(false);
|
|
240
|
+
done();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
inputStream.emit('end');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('should reject pending requests on close', async () => {
|
|
247
|
+
connection.start();
|
|
248
|
+
const requestPromise = connection.sendRequest('testMethod');
|
|
249
|
+
|
|
250
|
+
connection.close();
|
|
251
|
+
|
|
252
|
+
await expect(requestPromise).rejects.toThrow('Connection closed');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('should handle stream errors', (done) => {
|
|
256
|
+
connection.start();
|
|
257
|
+
|
|
258
|
+
connection.on('error', (error) => {
|
|
259
|
+
expect(error.message).toBe('Test error');
|
|
260
|
+
done();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
inputStream.emit('error', new Error('Test error'));
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('buffering', () => {
|
|
268
|
+
beforeEach(() => {
|
|
269
|
+
connection.start();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('should handle partial messages', (done) => {
|
|
273
|
+
const message = {
|
|
274
|
+
jsonrpc: '2.0',
|
|
275
|
+
method: 'test',
|
|
276
|
+
params: { data: 'test' }
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
connection.on('notification', (receivedMessage) => {
|
|
280
|
+
expect(receivedMessage).toEqual(message);
|
|
281
|
+
done();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const jsonString = JSON.stringify(message) + '\n';
|
|
285
|
+
|
|
286
|
+
// Send message in chunks
|
|
287
|
+
inputStream.emit('data', jsonString.substring(0, 10));
|
|
288
|
+
inputStream.emit('data', jsonString.substring(10, 20));
|
|
289
|
+
inputStream.emit('data', jsonString.substring(20));
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('should handle multiple messages in one chunk', () => {
|
|
293
|
+
const messages = [
|
|
294
|
+
{ jsonrpc: '2.0', method: 'test1' },
|
|
295
|
+
{ jsonrpc: '2.0', method: 'test2' }
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
const receivedMessages = [];
|
|
299
|
+
connection.on('notification', (message) => {
|
|
300
|
+
receivedMessages.push(message);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const chunk = messages.map(msg => JSON.stringify(msg)).join('\n') + '\n';
|
|
304
|
+
inputStream.emit('data', chunk);
|
|
305
|
+
|
|
306
|
+
expect(receivedMessages).toHaveLength(2);
|
|
307
|
+
expect(receivedMessages[0].method).toBe('test1');
|
|
308
|
+
expect(receivedMessages[1].method).toBe('test2');
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple ACP Client Example
|
|
5
|
+
*
|
|
6
|
+
* This example demonstrates how to communicate with the Probe ACP server
|
|
7
|
+
* using basic JSON-RPC 2.0 messages over stdio.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node simple-client.js
|
|
11
|
+
*
|
|
12
|
+
* Make sure to start the ACP server first:
|
|
13
|
+
* probe agent --acp
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawn } from 'child_process';
|
|
17
|
+
import { EventEmitter } from 'events';
|
|
18
|
+
|
|
19
|
+
class SimpleACPClient extends EventEmitter {
|
|
20
|
+
constructor() {
|
|
21
|
+
super();
|
|
22
|
+
this.messageId = 1;
|
|
23
|
+
this.pendingRequests = new Map();
|
|
24
|
+
this.sessionId = null;
|
|
25
|
+
this.server = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async start() {
|
|
29
|
+
console.log('š Starting Probe ACP server...');
|
|
30
|
+
|
|
31
|
+
// Spawn the ACP server
|
|
32
|
+
this.server = spawn('node', ['../../../index.js', '--acp'], {
|
|
33
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Handle server output (our responses)
|
|
37
|
+
this.server.stdout.on('data', (data) => {
|
|
38
|
+
const lines = data.toString().split('\n').filter(line => line.trim());
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
try {
|
|
41
|
+
const message = JSON.parse(line);
|
|
42
|
+
this.handleMessage(message);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('ā Failed to parse server message:', line);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Handle server errors
|
|
50
|
+
this.server.stderr.on('data', (data) => {
|
|
51
|
+
const output = data.toString();
|
|
52
|
+
if (output.includes('[ACP]') || output.includes('DEBUG')) {
|
|
53
|
+
console.log('š Server:', output.trim());
|
|
54
|
+
} else {
|
|
55
|
+
console.error('ā ļø Server error:', output.trim());
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this.server.on('close', (code) => {
|
|
60
|
+
console.log(`š“ Server closed with code ${code}`);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Wait a bit for server to start
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
65
|
+
console.log('ā
Server started\n');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
handleMessage(message) {
|
|
69
|
+
if (message.id && this.pendingRequests.has(message.id)) {
|
|
70
|
+
// This is a response to our request
|
|
71
|
+
const { resolve, reject } = this.pendingRequests.get(message.id);
|
|
72
|
+
this.pendingRequests.delete(message.id);
|
|
73
|
+
|
|
74
|
+
if (message.error) {
|
|
75
|
+
reject(new Error(`${message.error.code}: ${message.error.message}`));
|
|
76
|
+
} else {
|
|
77
|
+
resolve(message.result);
|
|
78
|
+
}
|
|
79
|
+
} else if (message.method) {
|
|
80
|
+
// This is a notification from the server
|
|
81
|
+
console.log(`šØ Notification: ${message.method}`, message.params);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async sendRequest(method, params = null) {
|
|
86
|
+
const id = this.messageId++;
|
|
87
|
+
const message = {
|
|
88
|
+
jsonrpc: '2.0',
|
|
89
|
+
method,
|
|
90
|
+
id
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (params !== null) {
|
|
94
|
+
message.params = params;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
99
|
+
|
|
100
|
+
// Send message to server
|
|
101
|
+
this.server.stdin.write(JSON.stringify(message) + '\n');
|
|
102
|
+
|
|
103
|
+
// Timeout after 30 seconds
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
if (this.pendingRequests.has(id)) {
|
|
106
|
+
this.pendingRequests.delete(id);
|
|
107
|
+
reject(new Error('Request timeout'));
|
|
108
|
+
}
|
|
109
|
+
}, 30000);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async initialize() {
|
|
114
|
+
console.log('š§ Initializing ACP protocol...');
|
|
115
|
+
const result = await this.sendRequest('initialize', {
|
|
116
|
+
protocolVersion: '1'
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
console.log('ā
Protocol initialized');
|
|
120
|
+
console.log(` Server: ${result.serverInfo.name} v${result.serverInfo.version}`);
|
|
121
|
+
console.log(` Capabilities: ${result.capabilities.tools.length} tools, sessions: ${result.capabilities.sessionManagement}`);
|
|
122
|
+
console.log();
|
|
123
|
+
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async createSession() {
|
|
128
|
+
console.log('š Creating new session...');
|
|
129
|
+
const result = await this.sendRequest('newSession', {});
|
|
130
|
+
|
|
131
|
+
this.sessionId = result.sessionId;
|
|
132
|
+
console.log(`ā
Session created: ${this.sessionId}`);
|
|
133
|
+
console.log();
|
|
134
|
+
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async sendPrompt(message) {
|
|
139
|
+
if (!this.sessionId) {
|
|
140
|
+
throw new Error('No active session. Create a session first.');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log(`š¬ Sending prompt: "${message}"`);
|
|
144
|
+
console.log('š¤ AI is thinking...\n');
|
|
145
|
+
|
|
146
|
+
const result = await this.sendRequest('prompt', {
|
|
147
|
+
sessionId: this.sessionId,
|
|
148
|
+
message
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Extract text from content blocks
|
|
152
|
+
const text = result.content
|
|
153
|
+
.filter(block => block.type === 'text')
|
|
154
|
+
.map(block => block.text)
|
|
155
|
+
.join('\n');
|
|
156
|
+
|
|
157
|
+
console.log('š¤ AI Response:');
|
|
158
|
+
console.log('ā'.repeat(50));
|
|
159
|
+
console.log(text);
|
|
160
|
+
console.log('ā'.repeat(50));
|
|
161
|
+
console.log();
|
|
162
|
+
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async close() {
|
|
167
|
+
console.log('š“ Closing connection...');
|
|
168
|
+
if (this.server) {
|
|
169
|
+
this.server.stdin.end();
|
|
170
|
+
this.server.kill();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Example usage
|
|
176
|
+
async function main() {
|
|
177
|
+
const client = new SimpleACPClient();
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
await client.start();
|
|
181
|
+
await client.initialize();
|
|
182
|
+
await client.createSession();
|
|
183
|
+
|
|
184
|
+
// Example conversations
|
|
185
|
+
await client.sendPrompt("What files are in this project?");
|
|
186
|
+
|
|
187
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
188
|
+
|
|
189
|
+
await client.sendPrompt("Find all functions that handle HTTP requests");
|
|
190
|
+
|
|
191
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
192
|
+
|
|
193
|
+
await client.sendPrompt("Show me the main entry point of this application");
|
|
194
|
+
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('ā Error:', error.message);
|
|
197
|
+
} finally {
|
|
198
|
+
await client.close();
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Handle Ctrl+C gracefully
|
|
204
|
+
process.on('SIGINT', async () => {
|
|
205
|
+
console.log('\nš Goodbye!');
|
|
206
|
+
process.exit(0);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Run the example
|
|
210
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
211
|
+
main().catch(console.error);
|
|
212
|
+
}
|