@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/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();
@@ -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.