@probelabs/probe 0.6.0-rc56
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 +528 -0
- package/bin/.gitkeep +0 -0
- package/bin/probe +0 -0
- package/build/mcp/index.js +499 -0
- package/build/mcp/index.ts +593 -0
- package/package.json +62 -0
- package/scripts/postinstall.js +127 -0
- package/src/cli.js +49 -0
- package/src/downloader.js +571 -0
- package/src/extract.js +147 -0
- package/src/index.js +55 -0
- package/src/mcp/index.ts +593 -0
- package/src/query.js +112 -0
- package/src/search.js +235 -0
- package/src/tools/common.js +224 -0
- package/src/tools/index.js +36 -0
- package/src/tools/langchain.js +88 -0
- package/src/tools/system-message.js +69 -0
- package/src/tools/vercel.js +215 -0
- package/src/utils/file-lister.js +193 -0
- package/src/utils.js +120 -0
package/src/query.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query functionality for the probe package
|
|
3
|
+
* @module query
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { getBinaryPath, buildCliArgs, escapeString } from './utils.js';
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Flag mapping for query options
|
|
14
|
+
* Maps option keys to command-line flags
|
|
15
|
+
*/
|
|
16
|
+
const QUERY_FLAG_MAP = {
|
|
17
|
+
language: '--language',
|
|
18
|
+
ignore: '--ignore',
|
|
19
|
+
allowTests: '--allow-tests',
|
|
20
|
+
maxResults: '--max-results',
|
|
21
|
+
format: '--format'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Query code in a specified directory using tree-sitter patterns
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} options - Query options
|
|
28
|
+
* @param {string} options.path - Path to search in
|
|
29
|
+
* @param {string} options.pattern - The ast-grep pattern to search for
|
|
30
|
+
* @param {string} [options.language] - Programming language to search in
|
|
31
|
+
* @param {string[]} [options.ignore] - Patterns to ignore
|
|
32
|
+
* @param {boolean} [options.allowTests] - Include test files
|
|
33
|
+
* @param {number} [options.maxResults] - Maximum number of results
|
|
34
|
+
* @param {string} [options.format] - Output format ('markdown', 'plain', 'json', 'color')
|
|
35
|
+
* @param {Object} [options.binaryOptions] - Options for getting the binary
|
|
36
|
+
* @param {boolean} [options.binaryOptions.forceDownload] - Force download even if binary exists
|
|
37
|
+
* @param {string} [options.binaryOptions.version] - Specific version to download
|
|
38
|
+
* @param {boolean} [options.json] - Return results as parsed JSON instead of string
|
|
39
|
+
* @returns {Promise<string|Object>} - Query results as string or parsed JSON
|
|
40
|
+
* @throws {Error} If the query fails
|
|
41
|
+
*/
|
|
42
|
+
export async function query(options) {
|
|
43
|
+
if (!options || !options.path) {
|
|
44
|
+
throw new Error('Path is required');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!options.pattern) {
|
|
48
|
+
throw new Error('Pattern is required');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get the binary path
|
|
52
|
+
const binaryPath = await getBinaryPath(options.binaryOptions || {});
|
|
53
|
+
|
|
54
|
+
// Build CLI arguments from options
|
|
55
|
+
const cliArgs = buildCliArgs(options, QUERY_FLAG_MAP);
|
|
56
|
+
|
|
57
|
+
// If json option is true, override format to json
|
|
58
|
+
if (options.json && !options.format) {
|
|
59
|
+
cliArgs.push('--format', 'json');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Add pattern and path as positional arguments
|
|
63
|
+
cliArgs.push(escapeString(options.pattern), escapeString(options.path));
|
|
64
|
+
|
|
65
|
+
// Create a single log record with all query parameters
|
|
66
|
+
let logMessage = `Query: pattern="${options.pattern}" path="${options.path}"`;
|
|
67
|
+
if (options.language) logMessage += ` language=${options.language}`;
|
|
68
|
+
if (options.maxResults) logMessage += ` maxResults=${options.maxResults}`;
|
|
69
|
+
if (options.allowTests) logMessage += " allowTests=true";
|
|
70
|
+
console.error(logMessage);
|
|
71
|
+
|
|
72
|
+
// Execute command
|
|
73
|
+
const command = `${binaryPath} query ${cliArgs.join(' ')}`;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const { stdout, stderr } = await execAsync(command);
|
|
77
|
+
|
|
78
|
+
if (stderr) {
|
|
79
|
+
console.error(`stderr: ${stderr}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Count results
|
|
83
|
+
let resultCount = 0;
|
|
84
|
+
|
|
85
|
+
// Try to count results from stdout
|
|
86
|
+
const lines = stdout.split('\n');
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
if (line.startsWith('```') && !line.includes('```language')) {
|
|
89
|
+
resultCount++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Log the results count
|
|
94
|
+
console.error(`Query results: ${resultCount} matches`);
|
|
95
|
+
|
|
96
|
+
// Parse JSON if requested or if format is json
|
|
97
|
+
if (options.json || options.format === 'json') {
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(stdout);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Error parsing JSON output:', error);
|
|
102
|
+
return stdout; // Fall back to string output
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return stdout;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Enhance error message with command details
|
|
109
|
+
const errorMessage = `Error executing query command: ${error.message}\nCommand: ${command}`;
|
|
110
|
+
throw new Error(errorMessage);
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/search.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search functionality for the probe package
|
|
3
|
+
* @module search
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { getBinaryPath, buildCliArgs, escapeString } from './utils.js';
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Flag mapping for search options
|
|
14
|
+
* Maps option keys to command-line flags
|
|
15
|
+
*/
|
|
16
|
+
const SEARCH_FLAG_MAP = {
|
|
17
|
+
filesOnly: '--files-only',
|
|
18
|
+
ignore: '--ignore',
|
|
19
|
+
excludeFilenames: '--exclude-filenames',
|
|
20
|
+
reranker: '--reranker',
|
|
21
|
+
frequencySearch: '--frequency',
|
|
22
|
+
exact: '--exact',
|
|
23
|
+
maxResults: '--max-results',
|
|
24
|
+
maxBytes: '--max-bytes',
|
|
25
|
+
maxTokens: '--max-tokens',
|
|
26
|
+
allowTests: '--allow-tests',
|
|
27
|
+
noMerge: '--no-merge',
|
|
28
|
+
mergeThreshold: '--merge-threshold',
|
|
29
|
+
session: '--session',
|
|
30
|
+
timeout: '--timeout',
|
|
31
|
+
language: '--language'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Search code in a specified directory
|
|
36
|
+
*
|
|
37
|
+
* @param {Object} options - Search options
|
|
38
|
+
* @param {string} options.path - Path to search in
|
|
39
|
+
* @param {string|string[]} options.query - Search query or queries
|
|
40
|
+
* @param {boolean} [options.filesOnly] - Only output file paths
|
|
41
|
+
* @param {string[]} [options.ignore] - Patterns to ignore
|
|
42
|
+
* @param {boolean} [options.excludeFilenames] - Exclude filenames from search
|
|
43
|
+
* @param {string} [options.reranker] - Reranking method ('hybrid', 'hybrid2', 'bm25', 'tfidf')
|
|
44
|
+
* @param {boolean} [options.frequencySearch] - Use frequency-based search
|
|
45
|
+
* @param {boolean} [options.exact] - Perform exact search without tokenization (case-insensitive)
|
|
46
|
+
* @param {number} [options.maxResults] - Maximum number of results
|
|
47
|
+
* @param {number} [options.maxBytes] - Maximum bytes to return
|
|
48
|
+
* @param {number} [options.maxTokens] - Maximum tokens to return
|
|
49
|
+
* @param {boolean} [options.allowTests] - Include test files
|
|
50
|
+
* @param {boolean} [options.noMerge] - Don't merge adjacent blocks
|
|
51
|
+
* @param {number} [options.mergeThreshold] - Merge threshold
|
|
52
|
+
* @param {string} [options.session] - Session ID for caching results
|
|
53
|
+
* @param {number} [options.timeout] - Timeout in seconds (default: 30)
|
|
54
|
+
* @param {string} [options.language] - Limit search to files of a specific programming language
|
|
55
|
+
* @param {Object} [options.binaryOptions] - Options for getting the binary
|
|
56
|
+
* @param {boolean} [options.binaryOptions.forceDownload] - Force download even if binary exists
|
|
57
|
+
* @param {string} [options.binaryOptions.version] - Specific version to download
|
|
58
|
+
* @param {boolean} [options.json] - Return results as parsed JSON instead of string
|
|
59
|
+
* @returns {Promise<string|Object>} - Search results as string or parsed JSON
|
|
60
|
+
* @throws {Error} If the search fails
|
|
61
|
+
*/
|
|
62
|
+
export async function search(options) {
|
|
63
|
+
if (!options || !options.path) {
|
|
64
|
+
throw new Error('Path is required');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!options.query) {
|
|
68
|
+
throw new Error('Query is required');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get the binary path
|
|
72
|
+
const binaryPath = await getBinaryPath(options.binaryOptions || {});
|
|
73
|
+
|
|
74
|
+
// Build CLI arguments from options
|
|
75
|
+
const cliArgs = buildCliArgs(options, SEARCH_FLAG_MAP);
|
|
76
|
+
|
|
77
|
+
// Add JSON format if requested
|
|
78
|
+
if (options.json) {
|
|
79
|
+
cliArgs.push('--format', 'json');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Set default maxTokens if not provided
|
|
83
|
+
if (!options.maxTokens) {
|
|
84
|
+
options.maxTokens = 10000;
|
|
85
|
+
cliArgs.push('--max-tokens', '10000');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Set default timeout if not provided
|
|
89
|
+
if (!options.timeout) {
|
|
90
|
+
options.timeout = 30;
|
|
91
|
+
cliArgs.push('--timeout', '30');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Ensure language is properly passed if provided
|
|
95
|
+
if (options.language) {
|
|
96
|
+
// Ensure language flag is in cliArgs
|
|
97
|
+
if (!cliArgs.includes('--language')) {
|
|
98
|
+
cliArgs.push('--language', options.language);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Ensure exact search is properly passed if enabled
|
|
103
|
+
if (options.exact) {
|
|
104
|
+
// Ensure exact flag is in cliArgs
|
|
105
|
+
if (!cliArgs.includes('--exact')) {
|
|
106
|
+
cliArgs.push('--exact');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add session ID from environment variable if not provided in options
|
|
111
|
+
if (!options.session && process.env.PROBE_SESSION_ID) {
|
|
112
|
+
options.session = process.env.PROBE_SESSION_ID;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Add query and path as positional arguments
|
|
116
|
+
const queries = Array.isArray(options.query) ? options.query : [options.query];
|
|
117
|
+
|
|
118
|
+
// Create a single log record with all search parameters (commented out for less verbose output)
|
|
119
|
+
let logMessage = `\nSearch: query="${queries[0]}" path="${options.path}"`;
|
|
120
|
+
if (options.maxResults) logMessage += ` maxResults=${options.maxResults}`;
|
|
121
|
+
logMessage += ` maxTokens=${options.maxTokens}`;
|
|
122
|
+
logMessage += ` timeout=${options.timeout}`;
|
|
123
|
+
if (options.allowTests) logMessage += " allowTests=true";
|
|
124
|
+
if (options.language) logMessage += ` language=${options.language}`;
|
|
125
|
+
if (options.exact) logMessage += " exact=true";
|
|
126
|
+
if (options.session) logMessage += ` session=${options.session}`;
|
|
127
|
+
console.error(logMessage);
|
|
128
|
+
// Create positional arguments array separate from flags
|
|
129
|
+
const positionalArgs = [];
|
|
130
|
+
|
|
131
|
+
if (queries.length > 0) {
|
|
132
|
+
// Escape the query to handle special characters
|
|
133
|
+
positionalArgs.push(escapeString(queries[0]));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Escape the path to handle spaces and special characters
|
|
137
|
+
positionalArgs.push(escapeString(options.path));
|
|
138
|
+
// Don't add the path to cliArgs, it should only be a positional argument
|
|
139
|
+
|
|
140
|
+
// Execute command with flags first, then positional arguments
|
|
141
|
+
const command = `${binaryPath} search ${cliArgs.join(' ')} ${positionalArgs.join(' ')}`;
|
|
142
|
+
|
|
143
|
+
// Debug logs to see the actual command with quotes and the path
|
|
144
|
+
// console.error(`Executing command: ${command}`);
|
|
145
|
+
// console.error(`Path being used: "${options.path}"`);
|
|
146
|
+
// console.error(`Escaped path: ${escapeString(options.path)}`);
|
|
147
|
+
// console.error(`Command flags: ${cliArgs.join(' ')}`);
|
|
148
|
+
// console.error(`Positional arguments: ${positionalArgs.join(' ')}`);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// Log before executing
|
|
152
|
+
// console.error(`About to execute command: ${command}`);
|
|
153
|
+
|
|
154
|
+
// Execute the command with options to preserve quotes and apply timeout
|
|
155
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
156
|
+
shell: true,
|
|
157
|
+
timeout: options.timeout * 1000 // Convert seconds to milliseconds
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Log after executing
|
|
161
|
+
// console.error(`Command executed successfully`);
|
|
162
|
+
|
|
163
|
+
if (stderr && process.env.DEBUG) {
|
|
164
|
+
console.error(`stderr: ${stderr}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Count results, tokens, and bytes
|
|
168
|
+
let resultCount = 0;
|
|
169
|
+
let tokenCount = 0;
|
|
170
|
+
let bytesCount = 0;
|
|
171
|
+
|
|
172
|
+
// Try to count results from stdout
|
|
173
|
+
const lines = stdout.split('\n');
|
|
174
|
+
for (const line of lines) {
|
|
175
|
+
if (line.startsWith('```') && !line.includes('```language')) {
|
|
176
|
+
resultCount++;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Look for the specific "Total bytes returned: X" line in the output
|
|
181
|
+
const totalBytesMatch = stdout.match(/Total bytes returned:\s*(\d+)/i);
|
|
182
|
+
if (totalBytesMatch && totalBytesMatch[1]) {
|
|
183
|
+
bytesCount = parseInt(totalBytesMatch[1], 10);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Look for the specific "Total tokens returned: X" line in the output
|
|
187
|
+
const totalTokensMatch = stdout.match(/Total tokens returned:\s*(\d+)/i);
|
|
188
|
+
if (totalTokensMatch && totalTokensMatch[1]) {
|
|
189
|
+
tokenCount = parseInt(totalTokensMatch[1], 10);
|
|
190
|
+
} else {
|
|
191
|
+
// Try other patterns if the specific format isn't found
|
|
192
|
+
const tokenMatch = stdout.match(/Tokens:?\s*(\d+)/i) ||
|
|
193
|
+
stdout.match(/(\d+)\s*tokens/i) ||
|
|
194
|
+
stdout.match(/token count:?\s*(\d+)/i);
|
|
195
|
+
|
|
196
|
+
if (tokenMatch && tokenMatch[1]) {
|
|
197
|
+
tokenCount = parseInt(tokenMatch[1], 10);
|
|
198
|
+
} else {
|
|
199
|
+
// If we still can't find the token count, use the default maxTokens value
|
|
200
|
+
// This is a fallback, but the command should be returning the actual count
|
|
201
|
+
tokenCount = options.maxTokens;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Log the results count, token count, and bytes count (commented out for less verbose output)
|
|
206
|
+
let resultsMessage = `\nSearch results: ${resultCount} matches, ${tokenCount} tokens`;
|
|
207
|
+
if (bytesCount > 0) {
|
|
208
|
+
resultsMessage += `, ${bytesCount} bytes`;
|
|
209
|
+
}
|
|
210
|
+
console.error(resultsMessage);
|
|
211
|
+
|
|
212
|
+
// Parse JSON if requested
|
|
213
|
+
if (options.json) {
|
|
214
|
+
try {
|
|
215
|
+
return JSON.parse(stdout);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error parsing JSON output:', error);
|
|
218
|
+
return stdout; // Fall back to string output
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return stdout;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
// Check if the error is a timeout
|
|
225
|
+
if (error.code === 'ETIMEDOUT' || error.killed) {
|
|
226
|
+
const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.\nCommand: ${command}`;
|
|
227
|
+
console.error(timeoutMessage);
|
|
228
|
+
throw new Error(timeoutMessage);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Enhance error message with command details
|
|
232
|
+
const errorMessage = `Error executing search command: ${error.message}\nCommand: ${command}`;
|
|
233
|
+
throw new Error(errorMessage);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common schemas and definitions for AI tools
|
|
3
|
+
* @module tools/common
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
// Common schemas for tool parameters (used for internal execution after XML parsing)
|
|
9
|
+
export const searchSchema = z.object({
|
|
10
|
+
query: z.string().describe('Search query with Elasticsearch syntax. Use + for important terms.'),
|
|
11
|
+
path: z.string().optional().default('.').describe('Path to search in. For dependencies use "go:github.com/owner/repo", "js:package_name", or "rust:cargo_name" etc.'),
|
|
12
|
+
allow_tests: z.boolean().optional().default(false).describe('Allow test files in search results'),
|
|
13
|
+
exact: z.boolean().optional().default(false).describe('Perform exact search without tokenization (case-insensitive)'),
|
|
14
|
+
maxResults: z.number().optional().describe('Maximum number of results to return'),
|
|
15
|
+
maxTokens: z.number().optional().default(10000).describe('Maximum number of tokens to return'),
|
|
16
|
+
language: z.string().optional().describe('Limit search to files of a specific programming language')
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const querySchema = z.object({
|
|
20
|
+
pattern: z.string().describe('AST pattern to search for. Use $NAME for variable names, $$$PARAMS for parameter lists, etc.'),
|
|
21
|
+
path: z.string().optional().default('.').describe('Path to search in'),
|
|
22
|
+
language: z.string().optional().default('rust').describe('Programming language to use for parsing'),
|
|
23
|
+
allow_tests: z.boolean().optional().default(false).describe('Allow test files in search results')
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const extractSchema = z.object({
|
|
27
|
+
file_path: z.string().optional().describe('Path to the file to extract from. Can include line numbers or symbol names'),
|
|
28
|
+
input_content: z.string().optional().describe('Text content to extract file paths from'),
|
|
29
|
+
line: z.number().optional().describe('Start line number to extract a specific code block'),
|
|
30
|
+
end_line: z.number().optional().describe('End line number for extracting a range of lines'),
|
|
31
|
+
allow_tests: z.boolean().optional().default(false).describe('Allow test files and test code blocks'),
|
|
32
|
+
context_lines: z.number().optional().default(10).describe('Number of context lines to include'),
|
|
33
|
+
format: z.string().optional().default('plain').describe('Output format (plain, markdown, json, color)')
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Schema for the new attempt_completion tool
|
|
37
|
+
export const attemptCompletionSchema = z.object({
|
|
38
|
+
result: z.string().describe('The final result of the task. Formulate this result in a way that is final and does not require further input from the user. Do not end your result with questions or offers for further assistance.'),
|
|
39
|
+
command: z.string().optional().describe('A CLI command to execute to show a live demo of the result to the user (e.g., `open index.html`). Do not use commands like `echo` or `cat` that merely print text.')
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// Tool descriptions for the system prompt (using XML format)
|
|
44
|
+
|
|
45
|
+
export const searchToolDefinition = `
|
|
46
|
+
## search
|
|
47
|
+
Description: Search code in the repository using Elasticsearch query syntax (except field based queries, e.g. "filename:..." NOT supported).
|
|
48
|
+
|
|
49
|
+
You need to focus on main keywords when constructing the query, and always use elastic search syntax like OR AND and brackets to group keywords.
|
|
50
|
+
Parameters:
|
|
51
|
+
- query: (required) Search query with Elasticsearch syntax. You can use + for important terms, and - for negation.
|
|
52
|
+
- path: (required) Path to search in. All dependencies located in /dep folder, under language sub folders, like this: "/dep/go/github.com/owner/repo", "/dep/js/package_name", or "/dep/rust/cargo_name" etc. YOU SHOULD ALWAYS provide FULL PATH when searching dependencies, including depency name.
|
|
53
|
+
- allow_tests: (optional, default: false) Allow test files in search results (true/false).
|
|
54
|
+
- exact: (optional, default: false) Perform exact pricise search. Use it when you already know function or struct name, or some other code block, and want exact match.
|
|
55
|
+
- maxResults: (optional) Maximum number of results to return (number).
|
|
56
|
+
- maxTokens: (optional, default: 10000) Maximum number of tokens to return (number).
|
|
57
|
+
- language: (optional) Limit search to files of a specific programming language (e.g., 'rust', 'js', 'python', 'go' etc.).
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
Usage Example:
|
|
61
|
+
|
|
62
|
+
<examples>
|
|
63
|
+
|
|
64
|
+
User: How to calculate the total amount in the payments module?
|
|
65
|
+
<search>
|
|
66
|
+
<query>calculate AND payment</query>
|
|
67
|
+
<path>src/utils</path>
|
|
68
|
+
<allow_tests>false</allow_tests>
|
|
69
|
+
</search>
|
|
70
|
+
|
|
71
|
+
User: How do the user authentication and authorization work?
|
|
72
|
+
<search>
|
|
73
|
+
<query>+user and (authentification OR authroization OR authz)</query>
|
|
74
|
+
<path>.</path>
|
|
75
|
+
<allow_tests>true</allow_tests>
|
|
76
|
+
<language>go</language>
|
|
77
|
+
</search>
|
|
78
|
+
|
|
79
|
+
User: Find all react imports in the project.
|
|
80
|
+
<search>
|
|
81
|
+
<query>import { react }</query>
|
|
82
|
+
<path>.</path>
|
|
83
|
+
<exact>true</exact>
|
|
84
|
+
<language>js</language>
|
|
85
|
+
</search>
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
User: Find how decompoud library works?
|
|
89
|
+
<search>
|
|
90
|
+
<query>import { react }</query>
|
|
91
|
+
<path>/dep/rust/decompound</path>
|
|
92
|
+
<language>rust</language>
|
|
93
|
+
</search>
|
|
94
|
+
|
|
95
|
+
</examples>
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
export const queryToolDefinition = `
|
|
99
|
+
## query
|
|
100
|
+
Description: Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.
|
|
101
|
+
Parameters:
|
|
102
|
+
- pattern: (required) AST pattern to search for. Use $NAME for variable names, $$$PARAMS for parameter lists, etc.
|
|
103
|
+
- path: (optional, default: '.') Path to search in.
|
|
104
|
+
- language: (optional, default: 'rust') Programming language to use for parsing.
|
|
105
|
+
- allow_tests: (optional, default: false) Allow test files in search results (true/false).
|
|
106
|
+
Usage Example:
|
|
107
|
+
|
|
108
|
+
<examples>
|
|
109
|
+
|
|
110
|
+
<query>
|
|
111
|
+
<pattern>function $FUNC($$$PARAMS) { $$$BODY }</pattern>
|
|
112
|
+
<path>src/parser</path>
|
|
113
|
+
<language>js</language>
|
|
114
|
+
</query>
|
|
115
|
+
|
|
116
|
+
</examples>
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
export const extractToolDefinition = `
|
|
120
|
+
## extract
|
|
121
|
+
Description: Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files. It can be used to read full files as well.
|
|
122
|
+
Full file extraction should be the LAST RESORT! Always prefer search.
|
|
123
|
+
|
|
124
|
+
Parameters:
|
|
125
|
+
- file_path: (required) Path to the file to extract from. Can include line numbers or symbol names (e.g., 'src/main.rs:10-20', 'src/utils.js#myFunction').
|
|
126
|
+
- line: (optional) Start line number to extract a specific code block. Use with end_line for ranges.
|
|
127
|
+
- end_line: (optional) End line number for extracting a range of lines.
|
|
128
|
+
- allow_tests: (optional, default: false) Allow test files and test code blocks (true/false).
|
|
129
|
+
Usage Example:
|
|
130
|
+
|
|
131
|
+
<examples>
|
|
132
|
+
|
|
133
|
+
User: How RankManager works
|
|
134
|
+
<extract>
|
|
135
|
+
<file_path>src/search/ranking.rs#RankManager</file_path>
|
|
136
|
+
</extract>
|
|
137
|
+
|
|
138
|
+
User: Lets read the whole file
|
|
139
|
+
<extract>
|
|
140
|
+
<file_path>src/search/ranking.rs</file_path>
|
|
141
|
+
</extract>
|
|
142
|
+
|
|
143
|
+
User: Read the first 10 lines of the file
|
|
144
|
+
<extract>
|
|
145
|
+
<file_path>src/search/ranking.rs</file_path>
|
|
146
|
+
<line>1</line>
|
|
147
|
+
<end_line>10</end_line>
|
|
148
|
+
</extract>
|
|
149
|
+
|
|
150
|
+
User: Read file inside the dependency
|
|
151
|
+
<extract>
|
|
152
|
+
<file_path>/dep/go/github.com/gorilla/mux/router.go</file_path>
|
|
153
|
+
</extract>
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
</examples>
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
export const attemptCompletionToolDefinition = `
|
|
160
|
+
## attempt_completion
|
|
161
|
+
Description: Use this tool ONLY when the task is fully complete and you have received confirmation of success for all previous tool uses. Presents the final result to the user.
|
|
162
|
+
Parameters:
|
|
163
|
+
- result: (required) The final result of the task. Formulate this result concisely and definitively. Do not end with questions or offers for further assistance. Ensure that answer fully addresses the user's request, and a clear and detailed maneer.
|
|
164
|
+
- command: (optional) A CLI command to demonstrate the result (e.g., 'open index.html'). Avoid simple print commands like 'echo'.
|
|
165
|
+
Usage Example:
|
|
166
|
+
<attempt_completion>
|
|
167
|
+
<result>I have refactored the search module according to the requirements and verified the tests pass.</result>
|
|
168
|
+
<command>cargo test --lib</command>
|
|
169
|
+
</attempt_completion>
|
|
170
|
+
`;
|
|
171
|
+
|
|
172
|
+
export const searchDescription = 'Search code in the repository using Elasticsearch-like query syntax. Use this tool first for any code-related questions.';
|
|
173
|
+
export const queryDescription = 'Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.';
|
|
174
|
+
export const extractDescription = 'Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files.';
|
|
175
|
+
|
|
176
|
+
// Simple XML parser helper
|
|
177
|
+
export function parseXmlToolCall(xmlString) {
|
|
178
|
+
const toolMatch = xmlString.match(/<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/);
|
|
179
|
+
if (!toolMatch) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const toolName = toolMatch[1];
|
|
184
|
+
const innerContent = toolMatch[2];
|
|
185
|
+
const params = {};
|
|
186
|
+
|
|
187
|
+
const paramRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
|
|
188
|
+
let paramMatch;
|
|
189
|
+
while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
|
|
190
|
+
const paramName = paramMatch[1];
|
|
191
|
+
let paramValue = paramMatch[2].trim();
|
|
192
|
+
|
|
193
|
+
// Basic type inference (can be improved)
|
|
194
|
+
if (paramValue.toLowerCase() === 'true') {
|
|
195
|
+
paramValue = true;
|
|
196
|
+
} else if (paramValue.toLowerCase() === 'false') {
|
|
197
|
+
paramValue = false;
|
|
198
|
+
} else if (!isNaN(paramValue) && paramValue.trim() !== '') {
|
|
199
|
+
// Check if it's potentially a number (handle integers and floats)
|
|
200
|
+
const num = Number(paramValue);
|
|
201
|
+
if (Number.isFinite(num)) { // Use Number.isFinite to avoid Infinity/NaN
|
|
202
|
+
paramValue = num;
|
|
203
|
+
}
|
|
204
|
+
// Keep as string if not a valid finite number
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
params[paramName] = paramValue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Special handling for attempt_completion where result might contain nested XML/code
|
|
211
|
+
if (toolName === 'attempt_completion') {
|
|
212
|
+
const resultMatch = innerContent.match(/<result>([\s\S]*?)<\/result>/);
|
|
213
|
+
if (resultMatch) {
|
|
214
|
+
params['result'] = resultMatch[1].trim(); // Keep result content as is
|
|
215
|
+
}
|
|
216
|
+
const commandMatch = innerContent.match(/<command>([\s\S]*?)<\/command>/);
|
|
217
|
+
if (commandMatch) {
|
|
218
|
+
params['command'] = commandMatch[1].trim();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
return { toolName, params };
|
|
224
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main tools module
|
|
3
|
+
* @module tools
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Export Vercel AI SDK tool generators
|
|
7
|
+
export { searchTool, queryTool, extractTool } from './vercel.js';
|
|
8
|
+
|
|
9
|
+
// Export LangChain tools
|
|
10
|
+
export { createSearchTool, createQueryTool, createExtractTool } from './langchain.js';
|
|
11
|
+
|
|
12
|
+
// Export common schemas
|
|
13
|
+
export {
|
|
14
|
+
searchSchema,
|
|
15
|
+
querySchema,
|
|
16
|
+
extractSchema,
|
|
17
|
+
attemptCompletionSchema,
|
|
18
|
+
attemptCompletionToolDefinition
|
|
19
|
+
} from './common.js';
|
|
20
|
+
|
|
21
|
+
// Export system message
|
|
22
|
+
export { DEFAULT_SYSTEM_MESSAGE } from './system-message.js';
|
|
23
|
+
|
|
24
|
+
// For backward compatibility, create and export pre-configured tools
|
|
25
|
+
import { searchTool as searchToolGenerator, queryTool as queryToolGenerator, extractTool as extractToolGenerator } from './vercel.js';
|
|
26
|
+
import { DEFAULT_SYSTEM_MESSAGE } from './system-message.js';
|
|
27
|
+
|
|
28
|
+
// Create default tool instances (for backward compatibility)
|
|
29
|
+
const tools = {
|
|
30
|
+
searchTool: searchToolGenerator(),
|
|
31
|
+
queryTool: queryToolGenerator(),
|
|
32
|
+
extractTool: extractToolGenerator(),
|
|
33
|
+
DEFAULT_SYSTEM_MESSAGE
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { tools };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools for LangChain
|
|
3
|
+
* @module tools/langchain
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { search } from '../search.js';
|
|
7
|
+
import { query } from '../query.js';
|
|
8
|
+
import { extract } from '../extract.js';
|
|
9
|
+
import { searchSchema, querySchema, extractSchema, searchDescription, queryDescription, extractDescription } from './common.js';
|
|
10
|
+
|
|
11
|
+
// LangChain tool for searching code
|
|
12
|
+
export function createSearchTool() {
|
|
13
|
+
return {
|
|
14
|
+
name: 'search',
|
|
15
|
+
description: searchDescription,
|
|
16
|
+
schema: searchSchema,
|
|
17
|
+
func: async ({ query: searchQuery, path, allow_tests, exact, maxResults, maxTokens = 10000, language }) => {
|
|
18
|
+
try {
|
|
19
|
+
const results = await search({
|
|
20
|
+
query: searchQuery,
|
|
21
|
+
path,
|
|
22
|
+
allow_tests,
|
|
23
|
+
exact,
|
|
24
|
+
json: false,
|
|
25
|
+
maxResults,
|
|
26
|
+
maxTokens,
|
|
27
|
+
language
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return results;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Error executing search command:', error);
|
|
33
|
+
return `Error executing search command: ${error.message}`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// LangChain tool for querying code
|
|
40
|
+
export function createQueryTool() {
|
|
41
|
+
return {
|
|
42
|
+
name: 'query',
|
|
43
|
+
description: queryDescription,
|
|
44
|
+
schema: querySchema,
|
|
45
|
+
func: async ({ pattern, path, language, allow_tests }) => {
|
|
46
|
+
try {
|
|
47
|
+
const results = await query({
|
|
48
|
+
pattern,
|
|
49
|
+
path,
|
|
50
|
+
language,
|
|
51
|
+
allow_tests,
|
|
52
|
+
json: false
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return results;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('Error executing query command:', error);
|
|
58
|
+
return `Error executing query command: ${error.message}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// LangChain tool for extracting code
|
|
65
|
+
export function createExtractTool() {
|
|
66
|
+
return {
|
|
67
|
+
name: 'extract',
|
|
68
|
+
description: extractDescription,
|
|
69
|
+
schema: extractSchema,
|
|
70
|
+
func: async ({ file_path, line, end_line, allow_tests, context_lines, format }) => {
|
|
71
|
+
try {
|
|
72
|
+
const files = [file_path];
|
|
73
|
+
|
|
74
|
+
const results = await extract({
|
|
75
|
+
files,
|
|
76
|
+
allowTests: allow_tests,
|
|
77
|
+
contextLines: context_lines,
|
|
78
|
+
format
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return results;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('Error executing extract command:', error);
|
|
84
|
+
return `Error executing extract command: ${error.message}`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|