@probelabs/probe 0.6.0-rc137 → 0.6.0-rc139

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/index.d.ts CHANGED
@@ -350,13 +350,21 @@ export interface QueryParams {
350
350
  */
351
351
  export interface ExtractParams {
352
352
  /** Files and line numbers or symbols to extract */
353
- files: string[];
353
+ files?: string[];
354
+ /** Path to a file containing unstructured text to extract file paths from */
355
+ inputFile?: string;
356
+ /** Content to pipe to stdin (e.g., git diff output). Alternative to inputFile or files. */
357
+ content?: string | Buffer;
354
358
  /** Path to search in */
355
359
  path?: string;
356
360
  /** Number of context lines */
357
361
  contextLines?: number;
358
362
  /** Output format */
359
- format?: 'markdown' | 'plain' | 'json';
363
+ format?: 'markdown' | 'plain' | 'json' | 'xml' | 'color' | 'outline-xml' | 'outline-diff';
364
+ /** Include test files */
365
+ allowTests?: boolean;
366
+ /** Return results as parsed JSON instead of string */
367
+ json?: boolean;
360
368
  /** Session ID */
361
369
  sessionId?: string;
362
370
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc137",
3
+ "version": "0.6.0-rc139",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -1270,6 +1270,9 @@ When troubleshooting:
1270
1270
  temperature: 0.3,
1271
1271
  });
1272
1272
 
1273
+ // Get the promise reference BEFORE consuming stream (doesn't lock it)
1274
+ const usagePromise = result.usage;
1275
+
1273
1276
  // Collect the streamed response - stream all content for now
1274
1277
  for await (const delta of result.textStream) {
1275
1278
  assistantResponseContent += delta;
@@ -1279,8 +1282,8 @@ When troubleshooting:
1279
1282
  }
1280
1283
  }
1281
1284
 
1282
- // Record token usage
1283
- const usage = await result.usage;
1285
+ // Record token usage - await the promise AFTER stream is consumed
1286
+ const usage = await usagePromise;
1284
1287
  if (usage) {
1285
1288
  this.tokenCounter.recordUsage(usage, result.experimental_providerMetadata);
1286
1289
  }
package/src/extract.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * @module extract
4
4
  */
5
5
 
6
- import { exec } from 'child_process';
6
+ import { exec, spawn } from 'child_process';
7
7
  import { promisify } from 'util';
8
8
  import { getBinaryPath, buildCliArgs, escapeString } from './utils.js';
9
9
 
@@ -22,13 +22,14 @@ const EXTRACT_FLAG_MAP = {
22
22
 
23
23
  /**
24
24
  * Extract code blocks from files
25
- *
25
+ *
26
26
  * @param {Object} options - Extract options
27
27
  * @param {string[]} [options.files] - Files to extract from (can include line numbers with colon, e.g., "/path/to/file.rs:10")
28
28
  * @param {string} [options.inputFile] - Path to a file containing unstructured text to extract file paths from
29
+ * @param {string|Buffer} [options.content] - Content to pipe to stdin (e.g., git diff output). Alternative to inputFile.
29
30
  * @param {boolean} [options.allowTests] - Include test files
30
31
  * @param {number} [options.contextLines] - Number of context lines to include
31
- * @param {string} [options.format] - Output format ('markdown', 'plain', 'json')
32
+ * @param {string} [options.format] - Output format ('markdown', 'plain', 'json', 'xml', 'color', 'outline-xml', 'outline-diff')
32
33
  * @param {Object} [options.binaryOptions] - Options for getting the binary
33
34
  * @param {boolean} [options.binaryOptions.forceDownload] - Force download even if binary exists
34
35
  * @param {string} [options.binaryOptions.version] - Specific version to download
@@ -41,16 +42,22 @@ export async function extract(options) {
41
42
  throw new Error('Options object is required');
42
43
  }
43
44
 
44
- // Either files or inputFile must be provided
45
- if ((!options.files || !Array.isArray(options.files) || options.files.length === 0) && !options.inputFile) {
46
- throw new Error('Either files array or inputFile must be provided');
45
+ // Either files, inputFile, or content must be provided
46
+ const hasFiles = options.files && Array.isArray(options.files) && options.files.length > 0;
47
+ const hasInputFile = !!options.inputFile;
48
+ const hasContent = options.content !== undefined && options.content !== null;
49
+
50
+ if (!hasFiles && !hasInputFile && !hasContent) {
51
+ throw new Error('Either files array, inputFile, or content must be provided');
47
52
  }
48
53
 
49
54
  // Get the binary path
50
55
  const binaryPath = await getBinaryPath(options.binaryOptions || {});
51
56
 
52
- // Build CLI arguments from options
53
- const cliArgs = buildCliArgs(options, EXTRACT_FLAG_MAP);
57
+ // Build CLI arguments from options (excluding content which goes via stdin)
58
+ const filteredOptions = { ...options };
59
+ delete filteredOptions.content;
60
+ const cliArgs = buildCliArgs(filteredOptions, EXTRACT_FLAG_MAP);
54
61
 
55
62
  // If json option is true, override format to json
56
63
  if (options.json && !options.format) {
@@ -58,7 +65,7 @@ export async function extract(options) {
58
65
  }
59
66
 
60
67
  // Add files as positional arguments if provided
61
- if (options.files && Array.isArray(options.files) && options.files.length > 0) {
68
+ if (hasFiles) {
62
69
  for (const file of options.files) {
63
70
  cliArgs.push(escapeString(file));
64
71
  }
@@ -71,6 +78,7 @@ export async function extract(options) {
71
78
  logMessage += ` files="${options.files.join(', ')}"`;
72
79
  }
73
80
  if (options.inputFile) logMessage += ` inputFile="${options.inputFile}"`;
81
+ if (options.content) logMessage += ` content=(${typeof options.content === 'string' ? options.content.length : options.content.byteLength} bytes)`;
74
82
  if (options.allowTests) logMessage += " allowTests=true";
75
83
  if (options.contextLines) logMessage += ` contextLines=${options.contextLines}`;
76
84
  if (options.format) logMessage += ` format=${options.format}`;
@@ -78,7 +86,12 @@ export async function extract(options) {
78
86
  console.error(logMessage);
79
87
  }
80
88
 
81
- // Execute command
89
+ // If content is provided, use spawn with stdin piping
90
+ if (hasContent) {
91
+ return extractWithStdin(binaryPath, cliArgs, options.content, options);
92
+ }
93
+
94
+ // Otherwise use exec for simple command execution
82
95
  const command = `${binaryPath} extract ${cliArgs.join(' ')}`;
83
96
 
84
97
  try {
@@ -88,62 +101,132 @@ export async function extract(options) {
88
101
  console.error(`stderr: ${stderr}`);
89
102
  }
90
103
 
91
- // Parse the output to extract token usage information
92
- let tokenUsage = {
93
- requestTokens: 0,
94
- responseTokens: 0,
95
- totalTokens: 0
96
- };
97
-
98
- // Calculate approximate request tokens
99
- if (options.files && Array.isArray(options.files)) {
100
- tokenUsage.requestTokens = options.files.join(' ').length / 4;
101
- } else if (options.inputFile) {
102
- tokenUsage.requestTokens = options.inputFile.length / 4;
103
- }
104
+ return processExtractOutput(stdout, options);
105
+ } catch (error) {
106
+ // Enhance error message with command details
107
+ const errorMessage = `Error executing extract command: ${error.message}\nCommand: ${command}`;
108
+ throw new Error(errorMessage);
109
+ }
110
+ }
104
111
 
105
- // Try to extract token information from the output
106
- if (stdout.includes('Total tokens returned:')) {
107
- const tokenMatch = stdout.match(/Total tokens returned: (\d+)/);
108
- if (tokenMatch && tokenMatch[1]) {
109
- tokenUsage.responseTokens = parseInt(tokenMatch[1], 10);
110
- tokenUsage.totalTokens = tokenUsage.requestTokens + tokenUsage.responseTokens;
112
+ /**
113
+ * Extract with content piped to stdin
114
+ * @private
115
+ */
116
+ function extractWithStdin(binaryPath, cliArgs, content, options) {
117
+ return new Promise((resolve, reject) => {
118
+ const process = spawn(binaryPath, ['extract', ...cliArgs], {
119
+ stdio: ['pipe', 'pipe', 'pipe']
120
+ });
121
+
122
+ let stdout = '';
123
+ let stderr = '';
124
+
125
+ // Collect stdout
126
+ process.stdout.on('data', (data) => {
127
+ stdout += data.toString();
128
+ });
129
+
130
+ // Collect stderr
131
+ process.stderr.on('data', (data) => {
132
+ stderr += data.toString();
133
+ });
134
+
135
+ // Handle process exit
136
+ process.on('close', (code) => {
137
+ if (stderr && process.env.DEBUG === '1') {
138
+ console.error(`stderr: ${stderr}`);
111
139
  }
112
- }
113
-
114
- // Add token usage information to the output
115
- let output = stdout;
116
140
 
117
- // Add token usage information at the end if not already present
118
- if (!output.includes('Token Usage:')) {
119
- output += `\nToken Usage:\n Request tokens: ${tokenUsage.requestTokens}\n Response tokens: ${tokenUsage.responseTokens}\n Total tokens: ${tokenUsage.totalTokens}\n`;
120
- }
141
+ if (code !== 0) {
142
+ reject(new Error(`Extract command failed with exit code ${code}: ${stderr}`));
143
+ return;
144
+ }
121
145
 
122
- // Parse JSON if requested or if format is json
123
- if (options.json || options.format === 'json') {
124
146
  try {
125
- const jsonOutput = JSON.parse(stdout);
126
-
127
- // Add token usage to JSON output
128
- if (!jsonOutput.token_usage) {
129
- jsonOutput.token_usage = {
130
- request_tokens: tokenUsage.requestTokens,
131
- response_tokens: tokenUsage.responseTokens,
132
- total_tokens: tokenUsage.totalTokens
133
- };
134
- }
135
-
136
- return jsonOutput;
147
+ const result = processExtractOutput(stdout, options);
148
+ resolve(result);
137
149
  } catch (error) {
138
- console.error('Error parsing JSON output:', error);
139
- return output; // Fall back to string output with token usage
150
+ reject(error);
140
151
  }
152
+ });
153
+
154
+ // Handle errors
155
+ process.on('error', (error) => {
156
+ reject(new Error(`Failed to spawn extract process: ${error.message}`));
157
+ });
158
+
159
+ // Write content to stdin and close
160
+ if (typeof content === 'string') {
161
+ process.stdin.write(content);
162
+ } else {
163
+ process.stdin.write(content);
141
164
  }
165
+ process.stdin.end();
166
+ });
167
+ }
142
168
 
143
- return output;
144
- } catch (error) {
145
- // Enhance error message with command details
146
- const errorMessage = `Error executing extract command: ${error.message}\nCommand: ${command}`;
147
- throw new Error(errorMessage);
169
+ /**
170
+ * Process extract output and add token usage information
171
+ * @private
172
+ */
173
+ function processExtractOutput(stdout, options) {
174
+ // Parse the output to extract token usage information
175
+ let tokenUsage = {
176
+ requestTokens: 0,
177
+ responseTokens: 0,
178
+ totalTokens: 0
179
+ };
180
+
181
+ // Calculate approximate request tokens
182
+ if (options.files && Array.isArray(options.files)) {
183
+ tokenUsage.requestTokens = options.files.join(' ').length / 4;
184
+ } else if (options.inputFile) {
185
+ tokenUsage.requestTokens = options.inputFile.length / 4;
186
+ } else if (options.content) {
187
+ const contentLength = typeof options.content === 'string'
188
+ ? options.content.length
189
+ : options.content.byteLength;
190
+ tokenUsage.requestTokens = contentLength / 4;
191
+ }
192
+
193
+ // Try to extract token information from the output
194
+ if (stdout.includes('Total tokens returned:')) {
195
+ const tokenMatch = stdout.match(/Total tokens returned: (\d+)/);
196
+ if (tokenMatch && tokenMatch[1]) {
197
+ tokenUsage.responseTokens = parseInt(tokenMatch[1], 10);
198
+ tokenUsage.totalTokens = tokenUsage.requestTokens + tokenUsage.responseTokens;
199
+ }
200
+ }
201
+
202
+ // Add token usage information to the output
203
+ let output = stdout;
204
+
205
+ // Add token usage information at the end if not already present
206
+ if (!output.includes('Token Usage:')) {
207
+ output += `\nToken Usage:\n Request tokens: ${tokenUsage.requestTokens}\n Response tokens: ${tokenUsage.responseTokens}\n Total tokens: ${tokenUsage.totalTokens}\n`;
208
+ }
209
+
210
+ // Parse JSON if requested or if format is json
211
+ if (options.json || options.format === 'json') {
212
+ try {
213
+ const jsonOutput = JSON.parse(stdout);
214
+
215
+ // Add token usage to JSON output
216
+ if (!jsonOutput.token_usage) {
217
+ jsonOutput.token_usage = {
218
+ request_tokens: tokenUsage.requestTokens,
219
+ response_tokens: tokenUsage.responseTokens,
220
+ total_tokens: tokenUsage.totalTokens
221
+ };
222
+ }
223
+
224
+ return jsonOutput;
225
+ } catch (error) {
226
+ console.error('Error parsing JSON output:', error);
227
+ return output; // Fall back to string output with token usage
228
+ }
148
229
  }
149
- }
230
+
231
+ return output;
232
+ }