@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/probeChat.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { ProbeAgent } from '@probelabs/probe/agent';
|
|
3
|
+
import { TokenUsageDisplay } from './tokenUsageDisplay.js';
|
|
4
|
+
import { writeFileSync, existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { TelemetryConfig } from './telemetry.js';
|
|
7
|
+
import { trace } from '@opentelemetry/api';
|
|
8
|
+
import { appTracer } from './appTracer.js';
|
|
9
|
+
|
|
10
|
+
// Parse and validate allowed folders from environment variable
|
|
11
|
+
const allowedFolders = process.env.ALLOWED_FOLDERS
|
|
12
|
+
? process.env.ALLOWED_FOLDERS.split(',').map(folder => folder.trim()).filter(Boolean)
|
|
13
|
+
: [];
|
|
14
|
+
|
|
15
|
+
// Validate folders exist on startup - will be handled by index.js in non-interactive mode
|
|
16
|
+
// This is kept for backward compatibility with direct ProbeChat usage
|
|
17
|
+
const validateFolders = () => {
|
|
18
|
+
if (allowedFolders.length > 0) {
|
|
19
|
+
for (const folder of allowedFolders) {
|
|
20
|
+
const exists = existsSync(folder);
|
|
21
|
+
// Only log if not in non-interactive mode or if in debug mode
|
|
22
|
+
if (process.env.PROBE_NON_INTERACTIVE !== '1' || process.env.DEBUG_CHAT === '1') {
|
|
23
|
+
console.log(`- ${folder} ${exists ? '✓' : '✗ (not found)'}`);
|
|
24
|
+
if (!exists) {
|
|
25
|
+
console.warn(`Warning: Folder "${folder}" does not exist or is not accessible`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
// Only log if not in non-interactive mode or if in debug mode
|
|
31
|
+
if (process.env.PROBE_NON_INTERACTIVE !== '1' || process.env.DEBUG_CHAT === '1') {
|
|
32
|
+
console.warn('No folders configured via ALLOWED_FOLDERS. Tools might default to current directory or require explicit paths.');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Only validate folders on startup if not in non-interactive mode
|
|
38
|
+
if (typeof process !== 'undefined' && !process.env.PROBE_CHAT_SKIP_FOLDER_VALIDATION) {
|
|
39
|
+
validateFolders();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extract image URLs from message text
|
|
44
|
+
* @param {string} message - The message text to analyze
|
|
45
|
+
* @param {boolean} debug - Whether to log debug information
|
|
46
|
+
* @returns {Array} Array of { url: string, cleanedMessage: string }
|
|
47
|
+
*/
|
|
48
|
+
function extractImageUrls(message, debug = false) {
|
|
49
|
+
// This function should be called within the session context, so it will inherit the trace ID
|
|
50
|
+
const tracer = trace.getTracer('probe-chat', '1.0.0');
|
|
51
|
+
return tracer.startActiveSpan('content.image.extract', (span) => {
|
|
52
|
+
try {
|
|
53
|
+
// Pattern to match image URLs and base64 data:
|
|
54
|
+
// 1. GitHub private-user-images URLs (always images, regardless of extension)
|
|
55
|
+
// 2. GitHub user-attachments/assets URLs (always images, regardless of extension)
|
|
56
|
+
// 3. URLs with common image extensions (PNG, JPG, JPEG, WebP, GIF)
|
|
57
|
+
// 4. Base64 data URLs (data:image/...)
|
|
58
|
+
// Updated to stop at quotes, spaces, or common HTML/XML delimiters
|
|
59
|
+
const imageUrlPattern = /(?:data:image\/[a-zA-Z]*;base64,[A-Za-z0-9+/=]+|https?:\/\/(?:(?:private-user-images\.githubusercontent\.com|github\.com\/user-attachments\/assets)\/[^\s"'<>]+|[^\s"'<>]+\.(?:png|jpg|jpeg|webp|gif)(?:\?[^\s"'<>]*)?))/gi;
|
|
60
|
+
|
|
61
|
+
span.setAttributes({
|
|
62
|
+
'message.length': message.length,
|
|
63
|
+
'debug.enabled': debug
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (debug) {
|
|
67
|
+
console.log(`[DEBUG] Scanning message for image URLs. Message length: ${message.length}`);
|
|
68
|
+
console.log(`[DEBUG] Image URL pattern: ${imageUrlPattern.toString()}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const urls = [];
|
|
72
|
+
let match;
|
|
73
|
+
|
|
74
|
+
while ((match = imageUrlPattern.exec(message)) !== null) {
|
|
75
|
+
urls.push(match[0]);
|
|
76
|
+
if (debug) {
|
|
77
|
+
console.log(`[DEBUG] Found image URL: ${match[0]}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Clean the message by removing found URLs
|
|
82
|
+
let cleanedMessage = message;
|
|
83
|
+
urls.forEach(url => {
|
|
84
|
+
cleanedMessage = cleanedMessage.replace(url, '').trim();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Clean up any remaining extra whitespace
|
|
88
|
+
cleanedMessage = cleanedMessage.replace(/\s+/g, ' ').trim();
|
|
89
|
+
|
|
90
|
+
span.setAttributes({
|
|
91
|
+
'images.found': urls.length,
|
|
92
|
+
'message.cleaned_length': cleanedMessage.length
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (debug) {
|
|
96
|
+
console.log(`[DEBUG] Extracted ${urls.length} image URLs`);
|
|
97
|
+
console.log(`[DEBUG] Cleaned message length: ${cleanedMessage.length}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { urls, cleanedMessage };
|
|
101
|
+
} finally {
|
|
102
|
+
span.end();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* ProbeChat class using ProbeAgent with MCP support
|
|
109
|
+
*/
|
|
110
|
+
export class ProbeChat {
|
|
111
|
+
/**
|
|
112
|
+
* Create a new ProbeChat instance
|
|
113
|
+
* @param {Object} options - Configuration options
|
|
114
|
+
* @param {string} [options.sessionId] - Optional session ID
|
|
115
|
+
* @param {boolean} [options.isNonInteractive=false] - Suppress internal logs if true
|
|
116
|
+
* @param {string} [options.customPrompt] - Custom prompt to replace the default system message
|
|
117
|
+
* @param {string} [options.promptType] - Predefined prompt type (architect, code-review, support)
|
|
118
|
+
* @param {boolean} [options.allowEdit=false] - Allow the use of the 'implement' tool
|
|
119
|
+
* @param {string} [options.provider] - Force specific AI provider
|
|
120
|
+
* @param {string} [options.model] - Override model name
|
|
121
|
+
* @param {boolean} [options.debug] - Enable debug mode
|
|
122
|
+
* @param {boolean} [options.enableMcp=false] - Enable MCP tool integration
|
|
123
|
+
* @param {Array} [options.mcpServers] - MCP server configurations
|
|
124
|
+
*/
|
|
125
|
+
constructor(options = {}) {
|
|
126
|
+
this.isNonInteractive = options.isNonInteractive || process.env.PROBE_NON_INTERACTIVE === '1';
|
|
127
|
+
this.debug = options.debug || process.env.DEBUG_CHAT === '1';
|
|
128
|
+
|
|
129
|
+
// Initialize ProbeAgent with MCP support
|
|
130
|
+
const agentOptions = {
|
|
131
|
+
...options,
|
|
132
|
+
path: allowedFolders.length > 0 ? allowedFolders[0] : process.cwd(),
|
|
133
|
+
enableMcp: options.enableMcp || process.env.ENABLE_MCP === '1',
|
|
134
|
+
mcpServers: options.mcpServers
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
this.agent = new ProbeAgent(agentOptions);
|
|
138
|
+
|
|
139
|
+
// Initialize telemetry and token display
|
|
140
|
+
this.telemetryConfig = new TelemetryConfig();
|
|
141
|
+
this.tokenUsage = new TokenUsageDisplay();
|
|
142
|
+
|
|
143
|
+
if (this.debug) {
|
|
144
|
+
console.log(`[DEBUG] ProbeChat initialized with MCP ${agentOptions.enableMcp ? 'enabled' : 'disabled'}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Answer a question using the agentic flow with optional image support
|
|
150
|
+
* @param {string} message - The user's question
|
|
151
|
+
* @param {Object} [options] - Optional configuration
|
|
152
|
+
* @param {string} [options.schema] - JSON schema for structured output
|
|
153
|
+
* @param {Array} [options.images] - Array of image data (base64 strings or URLs)
|
|
154
|
+
* @returns {Promise<string>} - The final answer
|
|
155
|
+
*/
|
|
156
|
+
async chat(message, options = {}) {
|
|
157
|
+
if (!message || typeof message !== 'string' || message.trim().length === 0) {
|
|
158
|
+
throw new Error('Message is required and must be a non-empty string');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Extract images from the message text if not provided in options
|
|
162
|
+
let images = options.images || [];
|
|
163
|
+
let cleanedMessage = message;
|
|
164
|
+
|
|
165
|
+
if (!images.length) {
|
|
166
|
+
const extracted = extractImageUrls(message, this.debug);
|
|
167
|
+
images = extracted.urls;
|
|
168
|
+
cleanedMessage = extracted.cleanedMessage;
|
|
169
|
+
|
|
170
|
+
if (this.debug && images.length > 0) {
|
|
171
|
+
console.log(`[DEBUG] Extracted ${images.length} images from message`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Use ProbeAgent to answer the question
|
|
176
|
+
const result = await this.agent.answer(cleanedMessage, images, options);
|
|
177
|
+
|
|
178
|
+
// Update token usage display
|
|
179
|
+
this.tokenUsage.updateFromTokenCounter(this.agent.tokenCounter);
|
|
180
|
+
|
|
181
|
+
if (!this.isNonInteractive) {
|
|
182
|
+
this.tokenUsage.display();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get session ID
|
|
190
|
+
*/
|
|
191
|
+
getSessionId() {
|
|
192
|
+
return this.agent.sessionId;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get usage summary for the current session
|
|
197
|
+
*/
|
|
198
|
+
getUsageSummary() {
|
|
199
|
+
return this.agent.tokenCounter.getUsageSummary();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Clear conversation history
|
|
204
|
+
*/
|
|
205
|
+
clearHistory() {
|
|
206
|
+
this.agent.clearHistory();
|
|
207
|
+
this.tokenUsage.clear();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Export conversation history
|
|
212
|
+
*/
|
|
213
|
+
exportHistory() {
|
|
214
|
+
return this.agent.history.map(msg => ({ ...msg }));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Save conversation history to file
|
|
219
|
+
*/
|
|
220
|
+
saveHistory(filename) {
|
|
221
|
+
if (!filename) {
|
|
222
|
+
filename = `probe-chat-history-${this.agent.sessionId}-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const historyData = {
|
|
226
|
+
sessionId: this.agent.sessionId,
|
|
227
|
+
timestamp: new Date().toISOString(),
|
|
228
|
+
messages: this.exportHistory(),
|
|
229
|
+
usage: this.getUsageSummary()
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
writeFileSync(filename, JSON.stringify(historyData, null, 2));
|
|
233
|
+
|
|
234
|
+
if (!this.isNonInteractive) {
|
|
235
|
+
console.log(`Conversation history saved to: ${filename}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return filename;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Cancel current request
|
|
243
|
+
*/
|
|
244
|
+
cancel() {
|
|
245
|
+
this.agent.cancel();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Clean up resources (including MCP connections)
|
|
250
|
+
*/
|
|
251
|
+
async cleanup() {
|
|
252
|
+
try {
|
|
253
|
+
await this.agent.cleanup();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
// Log the error but don't throw to ensure graceful cleanup
|
|
256
|
+
if (!this.isNonInteractive) {
|
|
257
|
+
console.warn('Warning during cleanup:', error.message);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Create the default instance using environment variables
|
|
264
|
+
export const chat = new ProbeChat({
|
|
265
|
+
enableMcp: process.env.ENABLE_MCP === '1',
|
|
266
|
+
debug: process.env.DEBUG_CHAT === '1'
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
export default chat;
|