@probelabs/probe-chat 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 +338 -0
- package/TRACING.md +226 -0
- package/appTracer.js +947 -0
- package/auth.js +76 -0
- package/bin/probe-chat.js +13 -0
- package/cancelRequest.js +84 -0
- package/fileSpanExporter.js +183 -0
- package/implement/README.md +228 -0
- package/implement/backends/AiderBackend.js +750 -0
- package/implement/backends/BaseBackend.js +276 -0
- package/implement/backends/ClaudeCodeBackend.js +767 -0
- package/implement/backends/MockBackend.js +237 -0
- package/implement/backends/registry.js +85 -0
- package/implement/core/BackendManager.js +567 -0
- package/implement/core/ImplementTool.js +354 -0
- package/implement/core/config.js +428 -0
- package/implement/core/timeouts.js +58 -0
- package/implement/core/utils.js +496 -0
- package/implement/types/BackendTypes.js +126 -0
- package/index.html +3751 -0
- package/index.js +582 -0
- package/logo.png +0 -0
- package/package.json +101 -0
- package/probeChat.js +269 -0
- package/probeTool.js +714 -0
- package/storage/JsonChatStorage.js +476 -0
- package/telemetry.js +287 -0
- package/test/integration/chatFlows.test.js +320 -0
- package/test/integration/toolCalling.test.js +471 -0
- package/test/mocks/mockLLMProvider.js +269 -0
- package/test/test-backends.js +90 -0
- package/test/testUtils.js +530 -0
- package/test/unit/backendTimeout.test.js +161 -0
- package/test/verify-tests.js +118 -0
- package/tokenCounter.js +419 -0
- package/tokenUsageDisplay.js +134 -0
- package/tools.js +186 -0
- package/webServer.js +1103 -0
package/auth.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Basic authentication middleware
|
|
5
|
+
* Checks for valid username and password in the Authorization header
|
|
6
|
+
* Can be enabled/disabled via environment variables
|
|
7
|
+
*/
|
|
8
|
+
export function authMiddleware(req, res, next) {
|
|
9
|
+
// Check if authentication is enabled
|
|
10
|
+
const AUTH_ENABLED = process.env.AUTH_ENABLED === '1';
|
|
11
|
+
|
|
12
|
+
// If authentication is not enabled, skip authentication check
|
|
13
|
+
if (!AUTH_ENABLED) {
|
|
14
|
+
return next(req, res);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Get configured username and password from environment variables
|
|
18
|
+
const AUTH_USERNAME = process.env.AUTH_USERNAME || 'admin';
|
|
19
|
+
const AUTH_PASSWORD = process.env.AUTH_PASSWORD || 'password';
|
|
20
|
+
|
|
21
|
+
// Check if request has Authorization header
|
|
22
|
+
const authHeader = req.headers.authorization;
|
|
23
|
+
|
|
24
|
+
if (!authHeader) {
|
|
25
|
+
// No Authorization header, return 401 Unauthorized
|
|
26
|
+
res.writeHead(401, {
|
|
27
|
+
'Content-Type': 'text/plain',
|
|
28
|
+
'WWW-Authenticate': 'Basic realm="Probe Code Search"'
|
|
29
|
+
});
|
|
30
|
+
res.end('Authentication required');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Parse Authorization header
|
|
35
|
+
try {
|
|
36
|
+
// Basic auth format: "Basic base64(username:password)"
|
|
37
|
+
const authParts = authHeader.split(' ');
|
|
38
|
+
if (authParts.length !== 2 || authParts[0] !== 'Basic') {
|
|
39
|
+
throw new Error('Invalid Authorization header format');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Decode base64 credentials
|
|
43
|
+
const credentials = Buffer.from(authParts[1], 'base64').toString('utf-8');
|
|
44
|
+
const [username, password] = credentials.split(':');
|
|
45
|
+
|
|
46
|
+
// Check if credentials match
|
|
47
|
+
if (username === AUTH_USERNAME && password === AUTH_PASSWORD) {
|
|
48
|
+
// Authentication successful, proceed to next middleware
|
|
49
|
+
return next(req, res);
|
|
50
|
+
} else {
|
|
51
|
+
// Invalid credentials, return 401 Unauthorized
|
|
52
|
+
res.writeHead(401, {
|
|
53
|
+
'Content-Type': 'text/plain',
|
|
54
|
+
'WWW-Authenticate': 'Basic realm="Probe Code Search"'
|
|
55
|
+
});
|
|
56
|
+
res.end('Invalid credentials');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// Error parsing Authorization header, return 400 Bad Request
|
|
61
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
62
|
+
res.end('Invalid Authorization header');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Apply authentication middleware to a request handler
|
|
69
|
+
* @param {Function} handler - The request handler function
|
|
70
|
+
* @returns {Function} - A new handler function with authentication
|
|
71
|
+
*/
|
|
72
|
+
export function withAuth(handler) {
|
|
73
|
+
return (req, res) => {
|
|
74
|
+
authMiddleware(req, res, () => handler(req, res));
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @probelabs/probe-chat CLI
|
|
5
|
+
* Command-line interface for Probe code search chat
|
|
6
|
+
*
|
|
7
|
+
* This is a thin wrapper around the main functionality in index.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { main } from '../index.js';
|
|
11
|
+
|
|
12
|
+
// Execute the main function
|
|
13
|
+
main();
|
package/cancelRequest.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Map to store active requests by session ID
|
|
2
|
+
const activeRequests = new Map();
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Register a request as active
|
|
6
|
+
* @param {string} sessionId - The session ID
|
|
7
|
+
* @param {Object} requestData - Data about the request (can include abort functions, etc.)
|
|
8
|
+
*/
|
|
9
|
+
export function registerRequest(sessionId, requestData) {
|
|
10
|
+
if (!sessionId) {
|
|
11
|
+
console.warn('Attempted to register request without session ID');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
console.log(`Registering request for session: ${sessionId}`);
|
|
16
|
+
activeRequests.set(sessionId, requestData);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Cancel a request by session ID
|
|
21
|
+
* @param {string} sessionId - The session ID
|
|
22
|
+
* @returns {boolean} - Whether the cancellation was successful
|
|
23
|
+
*/
|
|
24
|
+
export function cancelRequest(sessionId) {
|
|
25
|
+
if (!sessionId) {
|
|
26
|
+
console.warn('Attempted to cancel request without session ID');
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const requestData = activeRequests.get(sessionId);
|
|
31
|
+
if (!requestData) {
|
|
32
|
+
console.warn(`No active request found for session: ${sessionId}`);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(`Cancelling request for session: ${sessionId}`);
|
|
37
|
+
|
|
38
|
+
// Call the abort function if it exists
|
|
39
|
+
if (typeof requestData.abort === 'function') {
|
|
40
|
+
try {
|
|
41
|
+
requestData.abort();
|
|
42
|
+
console.log(`Successfully aborted request for session: ${sessionId}`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(`Error aborting request for session ${sessionId}:`, error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Remove the request from the active requests map
|
|
49
|
+
activeRequests.delete(sessionId);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if a request is active
|
|
55
|
+
* @param {string} sessionId - The session ID
|
|
56
|
+
* @returns {boolean} - Whether the request is active
|
|
57
|
+
*/
|
|
58
|
+
export function isRequestActive(sessionId) {
|
|
59
|
+
return activeRequests.has(sessionId);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all active requests
|
|
64
|
+
* @returns {Map} - Map of all active requests
|
|
65
|
+
*/
|
|
66
|
+
export function getActiveRequests() {
|
|
67
|
+
return activeRequests;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Clear a request from the active requests map
|
|
72
|
+
* @param {string} sessionId - The session ID
|
|
73
|
+
*/
|
|
74
|
+
export function clearRequest(sessionId) {
|
|
75
|
+
if (!sessionId) {
|
|
76
|
+
console.warn('Attempted to clear request without session ID');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (activeRequests.has(sessionId)) {
|
|
81
|
+
console.log(`Clearing request for session: ${sessionId}`);
|
|
82
|
+
activeRequests.delete(sessionId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { createWriteStream } from 'fs';
|
|
2
|
+
import corePkg from '@opentelemetry/core';
|
|
3
|
+
|
|
4
|
+
const { ExportResultCode } = corePkg;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* File exporter for OpenTelemetry spans
|
|
8
|
+
* Exports spans to a file in JSON Lines format (one JSON object per line)
|
|
9
|
+
* Following the OTLP JSON format specification
|
|
10
|
+
*/
|
|
11
|
+
export class FileSpanExporter {
|
|
12
|
+
constructor(filePath = './traces.jsonl') {
|
|
13
|
+
this.filePath = filePath;
|
|
14
|
+
this.stream = createWriteStream(filePath, { flags: 'a' });
|
|
15
|
+
this.stream.on('error', (error) => {
|
|
16
|
+
console.error(`[FileSpanExporter] Stream error: ${error.message}`);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Export spans to file
|
|
22
|
+
* @param {ReadableSpan[]} spans - Array of spans to export
|
|
23
|
+
* @param {function} resultCallback - Callback to call with the export result
|
|
24
|
+
*/
|
|
25
|
+
export(spans, resultCallback) {
|
|
26
|
+
if (!spans || spans.length === 0) {
|
|
27
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const timestamp = Date.now();
|
|
33
|
+
|
|
34
|
+
spans.forEach((span, index) => {
|
|
35
|
+
// Debug: Log first span's properties to understand structure
|
|
36
|
+
if (index === 0 && process.env.DEBUG_CHAT === '1') {
|
|
37
|
+
console.log('[FileSpanExporter] First span properties:');
|
|
38
|
+
const keys = Object.getOwnPropertyNames(span);
|
|
39
|
+
keys.forEach(key => {
|
|
40
|
+
if (key.toLowerCase().includes('parent') || key === '_spanContext' || key === 'parentContext') {
|
|
41
|
+
console.log(` ${key}:`, span[key]);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Extract parent span ID - check various possible properties
|
|
47
|
+
let parentSpanId = undefined;
|
|
48
|
+
|
|
49
|
+
// Check if there's a parent span context in the span
|
|
50
|
+
if (span.parentSpanContext) {
|
|
51
|
+
parentSpanId = span.parentSpanContext.spanId;
|
|
52
|
+
} else if (span._parentSpanContext) {
|
|
53
|
+
parentSpanId = span._parentSpanContext.spanId;
|
|
54
|
+
} else if (span.parent) {
|
|
55
|
+
parentSpanId = span.parent.spanId;
|
|
56
|
+
} else if (span._parent) {
|
|
57
|
+
parentSpanId = span._parent.spanId;
|
|
58
|
+
} else if (span._parentId) {
|
|
59
|
+
parentSpanId = span._parentId;
|
|
60
|
+
} else if (span.parentSpanId) {
|
|
61
|
+
parentSpanId = span.parentSpanId;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Convert span to OTLP JSON format
|
|
65
|
+
const spanData = {
|
|
66
|
+
traceId: span.spanContext().traceId,
|
|
67
|
+
spanId: span.spanContext().spanId,
|
|
68
|
+
parentSpanId: parentSpanId,
|
|
69
|
+
name: span.name,
|
|
70
|
+
kind: span.kind,
|
|
71
|
+
startTimeUnixNano: span.startTime[0] * 1_000_000_000 + span.startTime[1],
|
|
72
|
+
endTimeUnixNano: span.endTime[0] * 1_000_000_000 + span.endTime[1],
|
|
73
|
+
attributes: this.convertAttributes(span.attributes),
|
|
74
|
+
status: span.status,
|
|
75
|
+
events: span.events?.map(event => ({
|
|
76
|
+
timeUnixNano: event.time[0] * 1_000_000_000 + event.time[1],
|
|
77
|
+
name: event.name,
|
|
78
|
+
attributes: this.convertAttributes(event.attributes),
|
|
79
|
+
})) || [],
|
|
80
|
+
links: span.links?.map(link => ({
|
|
81
|
+
traceId: link.context.traceId,
|
|
82
|
+
spanId: link.context.spanId,
|
|
83
|
+
attributes: this.convertAttributes(link.attributes),
|
|
84
|
+
})) || [],
|
|
85
|
+
resource: {
|
|
86
|
+
attributes: this.convertAttributes(span.resource?.attributes || {}),
|
|
87
|
+
},
|
|
88
|
+
instrumentationLibrary: {
|
|
89
|
+
name: span.instrumentationLibrary?.name || 'unknown',
|
|
90
|
+
version: span.instrumentationLibrary?.version || 'unknown',
|
|
91
|
+
},
|
|
92
|
+
timestamp,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Write as JSON Lines format (one JSON object per line)
|
|
96
|
+
this.stream.write(JSON.stringify(spanData) + '\n');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(`[FileSpanExporter] Export error: ${error.message}`);
|
|
102
|
+
resultCallback({
|
|
103
|
+
code: ExportResultCode.FAILED,
|
|
104
|
+
error: error
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Convert OpenTelemetry attributes to plain object
|
|
111
|
+
* @param {Object} attributes - OpenTelemetry attributes
|
|
112
|
+
* @returns {Object} Plain object with string values
|
|
113
|
+
*/
|
|
114
|
+
convertAttributes(attributes) {
|
|
115
|
+
if (!attributes) return {};
|
|
116
|
+
|
|
117
|
+
const result = {};
|
|
118
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
119
|
+
// Convert all values to strings for JSON compatibility
|
|
120
|
+
if (typeof value === 'object' && value !== null) {
|
|
121
|
+
result[key] = JSON.stringify(value);
|
|
122
|
+
} else {
|
|
123
|
+
result[key] = String(value);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Shutdown the exporter
|
|
131
|
+
* @returns {Promise<void>}
|
|
132
|
+
*/
|
|
133
|
+
async shutdown() {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
if (this.stream) {
|
|
136
|
+
this.stream.end(() => {
|
|
137
|
+
console.log(`[FileSpanExporter] File stream closed: ${this.filePath}`);
|
|
138
|
+
resolve();
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
resolve();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Force flush any pending spans
|
|
148
|
+
* @returns {Promise<void>}
|
|
149
|
+
*/
|
|
150
|
+
async forceFlush() {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
if (this.stream) {
|
|
153
|
+
// CRITICAL FIX: Force the stream to flush all buffered data
|
|
154
|
+
// Use both drain event and explicit cork/uncork to ensure data is written
|
|
155
|
+
const flushTimeout = setTimeout(() => {
|
|
156
|
+
console.warn('[FileSpanExporter] Flush timeout after 5 seconds');
|
|
157
|
+
resolve();
|
|
158
|
+
}, 5000);
|
|
159
|
+
|
|
160
|
+
// Uncork the stream to force buffered writes
|
|
161
|
+
if (this.stream.writableCorked) {
|
|
162
|
+
this.stream.uncork();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If there's buffered data, wait for drain event
|
|
166
|
+
if (this.stream.writableNeedDrain) {
|
|
167
|
+
this.stream.once('drain', () => {
|
|
168
|
+
clearTimeout(flushTimeout);
|
|
169
|
+
resolve();
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
// No buffered data, but still give it a moment to ensure writes complete
|
|
173
|
+
setImmediate(() => {
|
|
174
|
+
clearTimeout(flushTimeout);
|
|
175
|
+
resolve();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
resolve();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Probe Chat Implementation Tool - Pluggable Backend System
|
|
2
|
+
|
|
3
|
+
The Probe Chat Implementation Tool now supports multiple AI-powered code implementation backends through a flexible, pluggable architecture. This allows you to choose between different AI coding assistants based on your needs, API availability, and preferences.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
### Using Different Backends
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Use the default backend (aider)
|
|
11
|
+
probe-chat --allow-edit
|
|
12
|
+
|
|
13
|
+
# Use Claude Code backend
|
|
14
|
+
probe-chat --allow-edit --implement-tool-backend claude-code
|
|
15
|
+
|
|
16
|
+
# Configure fallback backends
|
|
17
|
+
probe-chat --allow-edit --implement-tool-backend claude-code --implement-tool-fallbacks aider
|
|
18
|
+
|
|
19
|
+
# List available backends
|
|
20
|
+
probe-chat --implement-tool-list-backends
|
|
21
|
+
|
|
22
|
+
# Get detailed info about a backend
|
|
23
|
+
probe-chat --implement-tool-backend-info claude-code
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 📋 Available Backends
|
|
27
|
+
|
|
28
|
+
### Aider Backend (Default)
|
|
29
|
+
- **Description**: AI pair programming in your terminal
|
|
30
|
+
- **Strengths**: Battle-tested, supports many models, git integration
|
|
31
|
+
- **Requirements**: Python 3.8+, `pip install aider-chat`
|
|
32
|
+
- **API Keys**: Requires one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY
|
|
33
|
+
|
|
34
|
+
### Claude Code Backend
|
|
35
|
+
- **Description**: Advanced AI coding assistant powered by Claude
|
|
36
|
+
- **Strengths**: Latest Claude models, sophisticated code understanding, MCP tools
|
|
37
|
+
- **Requirements**: Node.js 18+, `npm install -g @anthropic-ai/claude-code`
|
|
38
|
+
- **API Keys**: Requires ANTHROPIC_API_KEY
|
|
39
|
+
- **Cross-Platform**: Full support for Windows, macOS, Linux, and WSL
|
|
40
|
+
|
|
41
|
+
## ⚙️ Configuration
|
|
42
|
+
|
|
43
|
+
### Environment Variables
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Backend Selection
|
|
47
|
+
export IMPLEMENT_TOOL_BACKEND=claude-code # Choose primary backend
|
|
48
|
+
export IMPLEMENT_TOOL_FALLBACKS=aider,claude-code # Comma-separated fallbacks
|
|
49
|
+
export IMPLEMENT_TOOL_TIMEOUT=1200 # Timeout in seconds (min: 60, max: 3600, default: 1200)
|
|
50
|
+
|
|
51
|
+
# Aider Configuration
|
|
52
|
+
export AIDER_MODEL=gpt-4 # Model for aider
|
|
53
|
+
export AIDER_AUTO_COMMIT=false # Auto-commit changes
|
|
54
|
+
export AIDER_TIMEOUT=300000 # Aider-specific timeout (deprecated - use IMPLEMENT_TOOL_TIMEOUT)
|
|
55
|
+
|
|
56
|
+
# Claude Code Configuration
|
|
57
|
+
export CLAUDE_CODE_MODEL=claude-3-5-sonnet-20241022 # Claude model
|
|
58
|
+
export CLAUDE_CODE_MAX_TOKENS=8000 # Max tokens
|
|
59
|
+
export CLAUDE_CODE_TEMPERATURE=0.3 # Temperature (0-2)
|
|
60
|
+
export CLAUDE_CODE_MAX_TURNS=10 # Max conversation turns
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Configuration File
|
|
64
|
+
|
|
65
|
+
Create `implement-config.json` in your project root:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"implement": {
|
|
70
|
+
"defaultBackend": "claude-code",
|
|
71
|
+
"fallbackBackends": ["aider"],
|
|
72
|
+
"selectionStrategy": "auto",
|
|
73
|
+
"timeout": 300000
|
|
74
|
+
},
|
|
75
|
+
"backends": {
|
|
76
|
+
"aider": {
|
|
77
|
+
"model": "gpt-4",
|
|
78
|
+
"autoCommit": false,
|
|
79
|
+
"additionalArgs": ["--no-auto-commits"]
|
|
80
|
+
},
|
|
81
|
+
"claude-code": {
|
|
82
|
+
"model": "claude-3-5-sonnet-20241022",
|
|
83
|
+
"maxTokens": 8000,
|
|
84
|
+
"temperature": 0.3,
|
|
85
|
+
"tools": ["edit", "search", "bash"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 🔄 Backend Selection Strategies
|
|
92
|
+
|
|
93
|
+
The system supports three selection strategies:
|
|
94
|
+
|
|
95
|
+
1. **auto** (default): Tries backends in order of availability
|
|
96
|
+
2. **preference**: Uses only the specified backend, fails if unavailable
|
|
97
|
+
3. **capability**: Selects backend based on language and feature support
|
|
98
|
+
|
|
99
|
+
## 🛠️ CLI Options
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Backend selection
|
|
103
|
+
--implement-tool-backend <name> # Choose backend (aider, claude-code)
|
|
104
|
+
--implement-tool-fallbacks <names> # Comma-separated fallback backends
|
|
105
|
+
--implement-tool-timeout <ms> # Implementation timeout
|
|
106
|
+
--implement-tool-config <path> # Path to config file
|
|
107
|
+
|
|
108
|
+
# Information
|
|
109
|
+
--implement-tool-list-backends # List all available backends
|
|
110
|
+
--implement-tool-backend-info <name> # Show backend details
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 📊 Backend Comparison
|
|
114
|
+
|
|
115
|
+
| Feature | Aider | Claude Code |
|
|
116
|
+
|---------|-------|-------------|
|
|
117
|
+
| **Languages** | Python, JS, TS, Go, Rust, Java, C/C++, C#, Ruby, PHP | JS, TS, Python, Rust, Go, Java, C++, C#, Ruby, PHP, Swift |
|
|
118
|
+
| **Streaming Output** | ✅ | ✅ |
|
|
119
|
+
| **Direct File Edit** | ✅ | ✅ |
|
|
120
|
+
| **Test Generation** | ❌ | ✅ |
|
|
121
|
+
| **Plan Generation** | ❌ | ✅ |
|
|
122
|
+
| **Rollback Support** | ✅ | ❌ |
|
|
123
|
+
| **Max Sessions** | 3 | 5 |
|
|
124
|
+
|
|
125
|
+
## 🔧 Advanced Usage
|
|
126
|
+
|
|
127
|
+
### Custom Backend Configuration
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
// In your code
|
|
131
|
+
const implementTool = createImplementTool({
|
|
132
|
+
enabled: true,
|
|
133
|
+
backendConfig: {
|
|
134
|
+
defaultBackend: 'claude-code',
|
|
135
|
+
fallbackBackends: ['aider'],
|
|
136
|
+
backends: {
|
|
137
|
+
'claude-code': {
|
|
138
|
+
apiKey: process.env.MY_CLAUDE_KEY,
|
|
139
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
140
|
+
systemPrompt: 'You are an expert TypeScript developer...'
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Programmatic Backend Selection
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
// Execute with specific backend
|
|
151
|
+
const result = await implementTool.execute({
|
|
152
|
+
task: 'Refactor this function to use async/await',
|
|
153
|
+
backend: 'claude-code', // Force specific backend
|
|
154
|
+
generateTests: true, // Backend-specific option
|
|
155
|
+
sessionId: 'my-session-123'
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## 🚨 Troubleshooting
|
|
160
|
+
|
|
161
|
+
### Common Issues
|
|
162
|
+
|
|
163
|
+
1. **Backend not available**
|
|
164
|
+
- Check if required dependencies are installed
|
|
165
|
+
- Verify API keys are set correctly
|
|
166
|
+
- Run `probe-chat --implement-tool-backend-info <name>` for diagnostics
|
|
167
|
+
- On Windows: Claude Code may be installed in WSL, which is automatically detected
|
|
168
|
+
|
|
169
|
+
2. **Timeout errors**
|
|
170
|
+
- Increase timeout: `--implement-tool-timeout 600000` (10 minutes)
|
|
171
|
+
- Check network connectivity
|
|
172
|
+
- Consider using a different backend
|
|
173
|
+
|
|
174
|
+
3. **API key issues**
|
|
175
|
+
- Ensure keys are exported in your environment
|
|
176
|
+
- Check key validity with the provider
|
|
177
|
+
- Verify key permissions
|
|
178
|
+
|
|
179
|
+
### Debug Mode
|
|
180
|
+
|
|
181
|
+
Enable debug logging to troubleshoot issues:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
DEBUG_CHAT=1 probe-chat --allow-edit --implement-tool-backend claude-code
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 🔐 Security Considerations
|
|
188
|
+
|
|
189
|
+
- API keys are never logged or exposed
|
|
190
|
+
- File access is restricted to the working directory
|
|
191
|
+
- All changes are made locally until explicitly committed
|
|
192
|
+
- Review all AI-generated changes before committing
|
|
193
|
+
|
|
194
|
+
## 🤝 Contributing
|
|
195
|
+
|
|
196
|
+
To add a new backend:
|
|
197
|
+
|
|
198
|
+
1. Extend the `BaseBackend` class
|
|
199
|
+
2. Implement required methods
|
|
200
|
+
3. Register in `backends/registry.js`
|
|
201
|
+
4. Add configuration schema
|
|
202
|
+
5. Update documentation
|
|
203
|
+
|
|
204
|
+
See `implement/backends/BaseBackend.js` for the interface definition.
|
|
205
|
+
|
|
206
|
+
## 📝 Migration from Legacy System
|
|
207
|
+
|
|
208
|
+
The new system is backward compatible. Your existing workflows will continue to work:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Old way (still works, uses aider backend)
|
|
212
|
+
probe-chat --allow-edit
|
|
213
|
+
|
|
214
|
+
# New way (explicit backend selection)
|
|
215
|
+
probe-chat --allow-edit --implement-tool-backend aider
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
No changes are required to existing scripts or workflows. The system defaults to aider backend for compatibility.
|
|
219
|
+
|
|
220
|
+
## 🔗 Related Documentation
|
|
221
|
+
|
|
222
|
+
- [Probe Chat Documentation](../../README.md)
|
|
223
|
+
- [Aider Documentation](https://aider.chat/)
|
|
224
|
+
- [Claude Code SDK](https://docs.anthropic.com/en/docs/claude-code)
|
|
225
|
+
|
|
226
|
+
## 📄 License
|
|
227
|
+
|
|
228
|
+
This pluggable backend system is part of Probe Chat and follows the same Apache-2.0 license.
|