@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,347 @@
|
|
|
1
|
+
# Agent Client Protocol (ACP) Implementation
|
|
2
|
+
|
|
3
|
+
This directory contains the implementation of the Agent Client Protocol (ACP) for the Probe AI agent. ACP is a standardized protocol for communication between code editors and AI coding agents, developed by Zed Industries.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The ACP implementation enables Probe to work as an AI coding agent compatible with ACP-enabled editors like Zed and Neovim. It provides a JSON-RPC 2.0 based communication protocol with rich features including:
|
|
8
|
+
|
|
9
|
+
- Session management for conversation contexts
|
|
10
|
+
- Tool execution with lifecycle tracking
|
|
11
|
+
- Streaming responses and notifications
|
|
12
|
+
- Permission system for code modifications
|
|
13
|
+
- Rich content types (text, images, resources)
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
### Core Components
|
|
18
|
+
|
|
19
|
+
- **`types.js`** - Protocol constants, types, and utility functions
|
|
20
|
+
- **`connection.js`** - JSON-RPC 2.0 communication over stdio
|
|
21
|
+
- **`server.js`** - Main ACP server implementation with session management
|
|
22
|
+
- **`tools.js`** - Tool integration and execution lifecycle management
|
|
23
|
+
- **`index.js`** - Module exports
|
|
24
|
+
|
|
25
|
+
### Protocol Flow
|
|
26
|
+
|
|
27
|
+
1. **Initialization**: Client negotiates protocol version and capabilities
|
|
28
|
+
2. **Session Management**: Create/load conversation contexts
|
|
29
|
+
3. **Tool Execution**: Execute code search/analysis tools with progress tracking
|
|
30
|
+
4. **AI Interaction**: Process user prompts using ProbeAgent
|
|
31
|
+
5. **Cleanup**: Handle disconnection and resource cleanup
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Starting ACP Server
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Start as ACP server
|
|
39
|
+
probe agent --acp
|
|
40
|
+
|
|
41
|
+
# With custom configuration
|
|
42
|
+
probe agent --acp --provider anthropic --path ./src --allow-edit
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Environment Variables
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
ANTHROPIC_API_KEY=your_key_here
|
|
49
|
+
OPENAI_API_KEY=your_key_here
|
|
50
|
+
GOOGLE_API_KEY=your_key_here
|
|
51
|
+
DEBUG=1 # Enable debug logging
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Protocol Messages
|
|
55
|
+
|
|
56
|
+
### Initialize Request
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"jsonrpc": "2.0",
|
|
61
|
+
"method": "initialize",
|
|
62
|
+
"params": {
|
|
63
|
+
"protocolVersion": "1"
|
|
64
|
+
},
|
|
65
|
+
"id": 1
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Initialize Response
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"jsonrpc": "2.0",
|
|
74
|
+
"id": 1,
|
|
75
|
+
"result": {
|
|
76
|
+
"protocolVersion": "1",
|
|
77
|
+
"serverInfo": {
|
|
78
|
+
"name": "probe-agent-acp",
|
|
79
|
+
"version": "1.0.0",
|
|
80
|
+
"description": "Probe AI agent with code search capabilities"
|
|
81
|
+
},
|
|
82
|
+
"capabilities": {
|
|
83
|
+
"tools": [
|
|
84
|
+
{
|
|
85
|
+
"name": "search",
|
|
86
|
+
"description": "Search for code patterns and content",
|
|
87
|
+
"kind": "search"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"sessionManagement": true,
|
|
91
|
+
"streaming": true,
|
|
92
|
+
"permissions": false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### New Session Request
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"jsonrpc": "2.0",
|
|
103
|
+
"method": "newSession",
|
|
104
|
+
"params": {
|
|
105
|
+
"sessionId": "optional-custom-id",
|
|
106
|
+
"mode": "normal"
|
|
107
|
+
},
|
|
108
|
+
"id": 2
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Prompt Request
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"jsonrpc": "2.0",
|
|
117
|
+
"method": "prompt",
|
|
118
|
+
"params": {
|
|
119
|
+
"sessionId": "session-123",
|
|
120
|
+
"message": "How does authentication work in this codebase?"
|
|
121
|
+
},
|
|
122
|
+
"id": 3
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Prompt Response
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"jsonrpc": "2.0",
|
|
131
|
+
"id": 3,
|
|
132
|
+
"result": {
|
|
133
|
+
"content": [
|
|
134
|
+
{
|
|
135
|
+
"type": "text",
|
|
136
|
+
"text": "Authentication in this codebase uses JWT tokens..."
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"sessionId": "session-123",
|
|
140
|
+
"timestamp": "2025-01-15T10:30:00Z"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Tool Integration
|
|
146
|
+
|
|
147
|
+
### Available Tools
|
|
148
|
+
|
|
149
|
+
1. **Search Tool** (`search`)
|
|
150
|
+
- Flexible text search with stemming
|
|
151
|
+
- Supports regex patterns and elastic search syntax
|
|
152
|
+
- Parameters: `query`, `path`, `max_results`, `allow_tests`
|
|
153
|
+
|
|
154
|
+
2. **Query Tool** (`query`)
|
|
155
|
+
- AST-based structural pattern matching
|
|
156
|
+
- Language-aware code structure search
|
|
157
|
+
- Parameters: `pattern`, `path`, `language`, `max_results`
|
|
158
|
+
|
|
159
|
+
3. **Extract Tool** (`extract`)
|
|
160
|
+
- Extract specific code blocks from files
|
|
161
|
+
- Line-based extraction with context
|
|
162
|
+
- Parameters: `files`, `context_lines`, `allow_tests`, `format`
|
|
163
|
+
|
|
164
|
+
### Tool Call Lifecycle
|
|
165
|
+
|
|
166
|
+
Tools execute with full lifecycle tracking:
|
|
167
|
+
|
|
168
|
+
1. **Pending** - Tool call queued for execution
|
|
169
|
+
2. **In Progress** - Tool is actively executing
|
|
170
|
+
3. **Completed** - Tool finished successfully with results
|
|
171
|
+
4. **Failed** - Tool execution failed with error
|
|
172
|
+
|
|
173
|
+
Progress is communicated via notifications:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"jsonrpc": "2.0",
|
|
178
|
+
"method": "toolCallProgress",
|
|
179
|
+
"params": {
|
|
180
|
+
"sessionId": "session-123",
|
|
181
|
+
"toolCallId": "tool-456",
|
|
182
|
+
"status": "completed",
|
|
183
|
+
"result": "search results here..."
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Session Management
|
|
189
|
+
|
|
190
|
+
### Session Types
|
|
191
|
+
|
|
192
|
+
- **Normal Mode**: Standard AI interaction
|
|
193
|
+
- **Planning Mode**: Strategic planning and design discussions
|
|
194
|
+
|
|
195
|
+
### Session Features
|
|
196
|
+
|
|
197
|
+
- Persistent conversation history
|
|
198
|
+
- Tool call tracking and status
|
|
199
|
+
- Session metadata (creation time, last update)
|
|
200
|
+
- Resource cleanup on disconnect
|
|
201
|
+
|
|
202
|
+
### Session Operations
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
// Create new session
|
|
206
|
+
await server.handleNewSession({
|
|
207
|
+
sessionId: 'custom-id', // optional
|
|
208
|
+
mode: 'normal' // optional
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Load existing session
|
|
212
|
+
await server.handleLoadSession({
|
|
213
|
+
sessionId: 'existing-id'
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Set session mode
|
|
217
|
+
await server.handleSetSessionMode({
|
|
218
|
+
sessionId: 'session-id',
|
|
219
|
+
mode: 'planning'
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Error Handling
|
|
224
|
+
|
|
225
|
+
The implementation follows JSON-RPC 2.0 error handling:
|
|
226
|
+
|
|
227
|
+
### Standard Error Codes
|
|
228
|
+
|
|
229
|
+
- `-32700` Parse Error - Invalid JSON
|
|
230
|
+
- `-32600` Invalid Request - Invalid JSON-RPC format
|
|
231
|
+
- `-32601` Method Not Found - Unknown method
|
|
232
|
+
- `-32602` Invalid Params - Invalid parameters
|
|
233
|
+
- `-32603` Internal Error - Server error
|
|
234
|
+
|
|
235
|
+
### Custom Error Codes
|
|
236
|
+
|
|
237
|
+
- `-32001` Unsupported Protocol Version
|
|
238
|
+
- `-32002` Session Not Found
|
|
239
|
+
- `-32003` Permission Denied
|
|
240
|
+
- `-32004` Tool Execution Failed
|
|
241
|
+
|
|
242
|
+
### Error Response Example
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"jsonrpc": "2.0",
|
|
247
|
+
"id": 1,
|
|
248
|
+
"error": {
|
|
249
|
+
"code": -32602,
|
|
250
|
+
"message": "Invalid params: sessionId required",
|
|
251
|
+
"data": {
|
|
252
|
+
"received": {},
|
|
253
|
+
"expected": "object with sessionId field"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Testing
|
|
260
|
+
|
|
261
|
+
The implementation includes comprehensive tests:
|
|
262
|
+
|
|
263
|
+
- **Unit Tests** - Individual component testing
|
|
264
|
+
- **Integration Tests** - Full protocol flow testing
|
|
265
|
+
- **Error Handling Tests** - Edge cases and error conditions
|
|
266
|
+
- **Mock Testing** - Isolated testing with mocked dependencies
|
|
267
|
+
|
|
268
|
+
Run tests:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
npm test npm/src/agent/acp/
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Development
|
|
275
|
+
|
|
276
|
+
### Adding New Methods
|
|
277
|
+
|
|
278
|
+
1. Add method constant to `types.js`
|
|
279
|
+
2. Implement handler in `server.js`
|
|
280
|
+
3. Add to request routing in `handleRequest()`
|
|
281
|
+
4. Write tests for the new method
|
|
282
|
+
|
|
283
|
+
### Adding New Tools
|
|
284
|
+
|
|
285
|
+
1. Implement tool in ProbeAgent's tool system
|
|
286
|
+
2. Add tool definition to `ACPToolManager.getToolDefinitions()`
|
|
287
|
+
3. Add execution case in `executeProbeTool()`
|
|
288
|
+
4. Update capabilities in server
|
|
289
|
+
|
|
290
|
+
### Debugging
|
|
291
|
+
|
|
292
|
+
Enable debug mode for detailed logging:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
DEBUG=1 probe agent --acp
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Debug logs include:
|
|
299
|
+
- Message send/receive
|
|
300
|
+
- Tool execution progress
|
|
301
|
+
- Session lifecycle events
|
|
302
|
+
- Error details and stack traces
|
|
303
|
+
|
|
304
|
+
## Compatibility
|
|
305
|
+
|
|
306
|
+
### Protocol Versions
|
|
307
|
+
|
|
308
|
+
- **ACP v1** - Full support for core protocol features
|
|
309
|
+
- Backwards compatible with future minor versions
|
|
310
|
+
- Forward compatible design for protocol evolution
|
|
311
|
+
|
|
312
|
+
### Editor Integration
|
|
313
|
+
|
|
314
|
+
- **Zed** - Native ACP support
|
|
315
|
+
- **Neovim** - Via ACP plugins (CodeCompanion, Avante.nvim)
|
|
316
|
+
- **VS Code** - Potential future support
|
|
317
|
+
- **Other Editors** - Any editor implementing ACP client
|
|
318
|
+
|
|
319
|
+
### AI Model Support
|
|
320
|
+
|
|
321
|
+
- **Anthropic Claude** - Full support (Haiku, Sonnet, Opus)
|
|
322
|
+
- **OpenAI GPT** - Full support (GPT-4, GPT-4 Turbo)
|
|
323
|
+
- **Google Gemini** - Full support (Pro, Ultra)
|
|
324
|
+
- Model selection via `--provider` and `--model` flags
|
|
325
|
+
|
|
326
|
+
## Migration from MCP
|
|
327
|
+
|
|
328
|
+
For users migrating from MCP to ACP:
|
|
329
|
+
|
|
330
|
+
### Similarities
|
|
331
|
+
- Both use JSON-RPC for communication
|
|
332
|
+
- Similar tool execution concepts
|
|
333
|
+
- Maintain conversation context
|
|
334
|
+
|
|
335
|
+
### Key Differences
|
|
336
|
+
- ACP has richer session management
|
|
337
|
+
- Tool lifecycle tracking with status updates
|
|
338
|
+
- More granular permission system
|
|
339
|
+
- Enhanced content types and metadata
|
|
340
|
+
|
|
341
|
+
### Migration Steps
|
|
342
|
+
1. Update client to use ACP protocol
|
|
343
|
+
2. Change startup flag from `--mcp` to `--acp`
|
|
344
|
+
3. Adapt to new message formats (mostly compatible)
|
|
345
|
+
4. Leverage enhanced session and tool features
|
|
346
|
+
|
|
347
|
+
The ACP implementation maintains feature parity with MCP while adding enhanced capabilities for modern AI-assisted coding workflows.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// ACP Connection - handles JSON-RPC 2.0 communication over stdio
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { validateMessage, createResponse, createError, ErrorCode } from './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ACP Connection class for handling bidirectional JSON-RPC communication
|
|
7
|
+
*/
|
|
8
|
+
export class ACPConnection extends EventEmitter {
|
|
9
|
+
constructor(inputStream = process.stdin, outputStream = process.stdout) {
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
this.inputStream = inputStream;
|
|
13
|
+
this.outputStream = outputStream;
|
|
14
|
+
this.buffer = '';
|
|
15
|
+
this.messageId = 1;
|
|
16
|
+
this.pendingRequests = new Map();
|
|
17
|
+
this.isConnected = false;
|
|
18
|
+
this.debug = process.env.DEBUG === '1';
|
|
19
|
+
|
|
20
|
+
this.setupStreams();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Setup input/output streams
|
|
25
|
+
*/
|
|
26
|
+
setupStreams() {
|
|
27
|
+
this.inputStream.setEncoding('utf8');
|
|
28
|
+
this.inputStream.on('data', this.handleData.bind(this));
|
|
29
|
+
this.inputStream.on('end', () => {
|
|
30
|
+
this.isConnected = false;
|
|
31
|
+
this.emit('disconnect');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.inputStream.on('error', (error) => {
|
|
35
|
+
if (this.debug) {
|
|
36
|
+
console.error('[ACP] Input stream error:', error);
|
|
37
|
+
}
|
|
38
|
+
this.emit('error', error);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this.outputStream.on('error', (error) => {
|
|
42
|
+
if (this.debug) {
|
|
43
|
+
console.error('[ACP] Output stream error:', error);
|
|
44
|
+
}
|
|
45
|
+
this.emit('error', error);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Start the connection
|
|
51
|
+
*/
|
|
52
|
+
start() {
|
|
53
|
+
this.isConnected = true;
|
|
54
|
+
this.emit('connect');
|
|
55
|
+
|
|
56
|
+
if (this.debug) {
|
|
57
|
+
console.error('[ACP] Connection started');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Handle incoming data
|
|
63
|
+
*/
|
|
64
|
+
handleData(chunk) {
|
|
65
|
+
this.buffer += chunk;
|
|
66
|
+
|
|
67
|
+
// Process complete messages (separated by newlines)
|
|
68
|
+
const lines = this.buffer.split('\n');
|
|
69
|
+
this.buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
70
|
+
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
if (line.trim()) {
|
|
73
|
+
try {
|
|
74
|
+
const message = JSON.parse(line);
|
|
75
|
+
this.handleMessage(message);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (this.debug) {
|
|
78
|
+
console.error('[ACP] Failed to parse message:', line, error);
|
|
79
|
+
}
|
|
80
|
+
this.sendError(null, ErrorCode.PARSE_ERROR, 'Parse error');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handle a parsed JSON-RPC message
|
|
88
|
+
*/
|
|
89
|
+
handleMessage(message) {
|
|
90
|
+
const validation = validateMessage(message);
|
|
91
|
+
if (!validation.valid) {
|
|
92
|
+
if (this.debug) {
|
|
93
|
+
console.error('[ACP] Invalid message:', validation.error, message);
|
|
94
|
+
}
|
|
95
|
+
this.sendError(message.id || null, ErrorCode.INVALID_REQUEST, validation.error);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.debug) {
|
|
100
|
+
console.error('[ACP] Received message:', JSON.stringify(message));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Handle response to our request
|
|
104
|
+
if (message.id && (message.result !== undefined || message.error !== undefined)) {
|
|
105
|
+
this.handleResponse(message);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Handle request or notification
|
|
110
|
+
if (message.method) {
|
|
111
|
+
if (message.id !== undefined) {
|
|
112
|
+
// Request - needs response
|
|
113
|
+
this.emit('request', message);
|
|
114
|
+
} else {
|
|
115
|
+
// Notification - no response needed
|
|
116
|
+
this.emit('notification', message);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Handle response to our request
|
|
123
|
+
*/
|
|
124
|
+
handleResponse(message) {
|
|
125
|
+
const { id, result, error } = message;
|
|
126
|
+
const pendingRequest = this.pendingRequests.get(id);
|
|
127
|
+
|
|
128
|
+
if (pendingRequest) {
|
|
129
|
+
this.pendingRequests.delete(id);
|
|
130
|
+
|
|
131
|
+
if (error) {
|
|
132
|
+
pendingRequest.reject(new Error(`RPC Error ${error.code}: ${error.message}`));
|
|
133
|
+
} else {
|
|
134
|
+
pendingRequest.resolve(result);
|
|
135
|
+
}
|
|
136
|
+
} else if (this.debug) {
|
|
137
|
+
console.error('[ACP] Received response for unknown request ID:', id);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Send a message
|
|
143
|
+
*/
|
|
144
|
+
sendMessage(message) {
|
|
145
|
+
if (!this.isConnected) {
|
|
146
|
+
throw new Error('Connection not established');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const json = JSON.stringify(message);
|
|
150
|
+
|
|
151
|
+
if (this.debug) {
|
|
152
|
+
console.error('[ACP] Sending message:', json);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.outputStream.write(json + '\n');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Send a request and wait for response
|
|
160
|
+
*/
|
|
161
|
+
async sendRequest(method, params = null) {
|
|
162
|
+
const id = this.messageId++;
|
|
163
|
+
const message = {
|
|
164
|
+
jsonrpc: '2.0',
|
|
165
|
+
method,
|
|
166
|
+
id
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (params !== null) {
|
|
170
|
+
message.params = params;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
// Store the pending request
|
|
175
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
176
|
+
|
|
177
|
+
// Send the message
|
|
178
|
+
this.sendMessage(message);
|
|
179
|
+
|
|
180
|
+
// Set timeout
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
if (this.pendingRequests.has(id)) {
|
|
183
|
+
this.pendingRequests.delete(id);
|
|
184
|
+
reject(new Error('Request timeout'));
|
|
185
|
+
}
|
|
186
|
+
}, 30000); // 30 second timeout
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Send a notification (no response expected)
|
|
192
|
+
*/
|
|
193
|
+
sendNotification(method, params = null) {
|
|
194
|
+
const message = {
|
|
195
|
+
jsonrpc: '2.0',
|
|
196
|
+
method
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
if (params !== null) {
|
|
200
|
+
message.params = params;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.sendMessage(message);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Send a response to a request
|
|
208
|
+
*/
|
|
209
|
+
sendResponse(id, result) {
|
|
210
|
+
const response = createResponse(id, result);
|
|
211
|
+
this.sendMessage(response);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Send an error response
|
|
216
|
+
*/
|
|
217
|
+
sendError(id, code, message, data = null) {
|
|
218
|
+
const error = createError(code, message, data);
|
|
219
|
+
const response = createResponse(id, null, error);
|
|
220
|
+
this.sendMessage(response);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Close the connection
|
|
225
|
+
*/
|
|
226
|
+
close() {
|
|
227
|
+
this.isConnected = false;
|
|
228
|
+
|
|
229
|
+
// Reject all pending requests
|
|
230
|
+
for (const [id, pendingRequest] of this.pendingRequests) {
|
|
231
|
+
pendingRequest.reject(new Error('Connection closed'));
|
|
232
|
+
}
|
|
233
|
+
this.pendingRequests.clear();
|
|
234
|
+
|
|
235
|
+
this.emit('disconnect');
|
|
236
|
+
}
|
|
237
|
+
}
|