@probelabs/probe 0.6.0-rc103 → 0.6.0-rc104
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/build/agent/ProbeAgent.js +78 -9
- package/build/agent/index.js +27924 -422
- package/build/agent/probeTool.js +23 -5
- package/build/agent/tools.js +57 -5
- package/build/utils.js +1 -1
- package/cjs/agent/ProbeAgent.cjs +27762 -269
- package/cjs/index.cjs +27829 -336
- package/package.json +2 -1
- package/src/agent/ProbeAgent.js +78 -9
- package/src/agent/index.js +14 -32
- package/src/agent/probeTool.js +23 -5
- package/src/agent/tools.js +57 -5
- package/src/utils.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probelabs/probe",
|
|
3
|
-
"version": "0.6.0-
|
|
3
|
+
"version": "0.6.0-rc104",
|
|
4
4
|
"description": "Node.js wrapper for the probe code search tool",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"author": "probelabs",
|
|
69
69
|
"license": "ISC",
|
|
70
70
|
"dependencies": {
|
|
71
|
+
"@ai-sdk/amazon-bedrock": "^1.0.8",
|
|
71
72
|
"@ai-sdk/anthropic": "^2.0.8",
|
|
72
73
|
"@ai-sdk/google": "^2.0.14",
|
|
73
74
|
"@ai-sdk/openai": "^2.0.10",
|
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
3
3
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
4
4
|
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
5
|
+
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
|
|
5
6
|
import { streamText } from 'ai';
|
|
6
7
|
import { randomUUID } from 'crypto';
|
|
7
8
|
import { EventEmitter } from 'events';
|
|
@@ -91,8 +92,14 @@ export class ProbeAgent {
|
|
|
91
92
|
this.maxResponseTokens = options.maxResponseTokens || parseInt(process.env.MAX_RESPONSE_TOKENS || '0', 10) || null;
|
|
92
93
|
this.disableMermaidValidation = !!options.disableMermaidValidation;
|
|
93
94
|
|
|
94
|
-
// Search configuration
|
|
95
|
-
|
|
95
|
+
// Search configuration - support both path (single) and allowedFolders (array)
|
|
96
|
+
if (options.allowedFolders && Array.isArray(options.allowedFolders)) {
|
|
97
|
+
this.allowedFolders = options.allowedFolders;
|
|
98
|
+
} else if (options.path) {
|
|
99
|
+
this.allowedFolders = [options.path];
|
|
100
|
+
} else {
|
|
101
|
+
this.allowedFolders = [process.cwd()];
|
|
102
|
+
}
|
|
96
103
|
|
|
97
104
|
// API configuration
|
|
98
105
|
this.clientApiProvider = options.provider || null;
|
|
@@ -186,12 +193,18 @@ export class ProbeAgent {
|
|
|
186
193
|
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
187
194
|
const openaiApiKey = process.env.OPENAI_API_KEY;
|
|
188
195
|
const googleApiKey = process.env.GOOGLE_API_KEY;
|
|
196
|
+
const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
|
197
|
+
const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
198
|
+
const awsRegion = process.env.AWS_REGION;
|
|
199
|
+
const awsSessionToken = process.env.AWS_SESSION_TOKEN;
|
|
200
|
+
const awsApiKey = process.env.AWS_BEDROCK_API_KEY;
|
|
189
201
|
|
|
190
202
|
// Get custom API URLs if provided
|
|
191
203
|
const llmBaseUrl = process.env.LLM_BASE_URL;
|
|
192
204
|
const anthropicApiUrl = process.env.ANTHROPIC_API_URL || llmBaseUrl;
|
|
193
205
|
const openaiApiUrl = process.env.OPENAI_API_URL || llmBaseUrl;
|
|
194
206
|
const googleApiUrl = process.env.GOOGLE_API_URL || llmBaseUrl;
|
|
207
|
+
const awsBedrockBaseUrl = process.env.AWS_BEDROCK_BASE_URL || llmBaseUrl;
|
|
195
208
|
|
|
196
209
|
// Get model override if provided
|
|
197
210
|
const modelName = process.env.MODEL_NAME;
|
|
@@ -200,7 +213,12 @@ export class ProbeAgent {
|
|
|
200
213
|
const forceProvider = this.clientApiProvider || (process.env.FORCE_PROVIDER ? process.env.FORCE_PROVIDER.toLowerCase() : null);
|
|
201
214
|
|
|
202
215
|
if (this.debug) {
|
|
203
|
-
|
|
216
|
+
const hasAwsCredentials = !!(awsAccessKeyId && awsSecretAccessKey && awsRegion);
|
|
217
|
+
const hasAwsApiKey = !!awsApiKey;
|
|
218
|
+
console.log(`[DEBUG] Available API keys: Anthropic=${!!anthropicApiKey}, OpenAI=${!!openaiApiKey}, Google=${!!googleApiKey}, AWS Bedrock=${hasAwsCredentials || hasAwsApiKey}`);
|
|
219
|
+
if (hasAwsCredentials) console.log(`[DEBUG] AWS credentials: AccessKey=${!!awsAccessKeyId}, SecretKey=${!!awsSecretAccessKey}, Region=${awsRegion}, SessionToken=${!!awsSessionToken}`);
|
|
220
|
+
if (hasAwsApiKey) console.log(`[DEBUG] AWS API Key provided`);
|
|
221
|
+
if (awsBedrockBaseUrl) console.log(`[DEBUG] AWS Bedrock base URL: ${awsBedrockBaseUrl}`);
|
|
204
222
|
console.log(`[DEBUG] Force provider: ${forceProvider || '(not set)'}`);
|
|
205
223
|
if (modelName) console.log(`[DEBUG] Model override: ${modelName}`);
|
|
206
224
|
}
|
|
@@ -216,6 +234,9 @@ export class ProbeAgent {
|
|
|
216
234
|
} else if (forceProvider === 'google' && googleApiKey) {
|
|
217
235
|
this.initializeGoogleModel(googleApiKey, googleApiUrl, modelName);
|
|
218
236
|
return;
|
|
237
|
+
} else if (forceProvider === 'bedrock' && ((awsAccessKeyId && awsSecretAccessKey && awsRegion) || awsApiKey)) {
|
|
238
|
+
this.initializeBedrockModel(awsAccessKeyId, awsSecretAccessKey, awsRegion, awsSessionToken, awsApiKey, awsBedrockBaseUrl, modelName);
|
|
239
|
+
return;
|
|
219
240
|
}
|
|
220
241
|
console.warn(`WARNING: Forced provider "${forceProvider}" selected but required API key is missing or invalid! Falling back to auto-detection.`);
|
|
221
242
|
}
|
|
@@ -227,8 +248,10 @@ export class ProbeAgent {
|
|
|
227
248
|
this.initializeOpenAIModel(openaiApiKey, openaiApiUrl, modelName);
|
|
228
249
|
} else if (googleApiKey) {
|
|
229
250
|
this.initializeGoogleModel(googleApiKey, googleApiUrl, modelName);
|
|
251
|
+
} else if ((awsAccessKeyId && awsSecretAccessKey && awsRegion) || awsApiKey) {
|
|
252
|
+
this.initializeBedrockModel(awsAccessKeyId, awsSecretAccessKey, awsRegion, awsSessionToken, awsApiKey, awsBedrockBaseUrl, modelName);
|
|
230
253
|
} else {
|
|
231
|
-
throw new Error('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, or
|
|
254
|
+
throw new Error('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION), or AWS_BEDROCK_API_KEY environment variables.');
|
|
232
255
|
}
|
|
233
256
|
}
|
|
234
257
|
|
|
@@ -281,6 +304,46 @@ export class ProbeAgent {
|
|
|
281
304
|
}
|
|
282
305
|
}
|
|
283
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Initialize AWS Bedrock model
|
|
309
|
+
*/
|
|
310
|
+
initializeBedrockModel(accessKeyId, secretAccessKey, region, sessionToken, apiKey, baseURL, modelName) {
|
|
311
|
+
// Build configuration object, only including defined values
|
|
312
|
+
const config = {};
|
|
313
|
+
|
|
314
|
+
// Authentication - prefer API key if provided, otherwise use AWS credentials
|
|
315
|
+
if (apiKey) {
|
|
316
|
+
config.apiKey = apiKey;
|
|
317
|
+
} else if (accessKeyId && secretAccessKey) {
|
|
318
|
+
config.accessKeyId = accessKeyId;
|
|
319
|
+
config.secretAccessKey = secretAccessKey;
|
|
320
|
+
if (sessionToken) {
|
|
321
|
+
config.sessionToken = sessionToken;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Region is required for AWS credentials but optional for API key
|
|
326
|
+
if (region) {
|
|
327
|
+
config.region = region;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Optional base URL
|
|
331
|
+
if (baseURL) {
|
|
332
|
+
config.baseURL = baseURL;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.provider = createAmazonBedrock(config);
|
|
336
|
+
this.model = modelName || 'anthropic.claude-sonnet-4-20250514-v1:0';
|
|
337
|
+
this.apiType = 'bedrock';
|
|
338
|
+
|
|
339
|
+
if (this.debug) {
|
|
340
|
+
const authMethod = apiKey ? 'API Key' : 'AWS Credentials';
|
|
341
|
+
const regionInfo = region ? ` (Region: ${region})` : '';
|
|
342
|
+
const baseUrlInfo = baseURL ? ` (Base URL: ${baseURL})` : '';
|
|
343
|
+
console.log(`Using AWS Bedrock API with model: ${this.model}${regionInfo} [Auth: ${authMethod}]${baseUrlInfo}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
284
347
|
/**
|
|
285
348
|
* Process assistant response content and detect/load image references
|
|
286
349
|
* @param {string} content - The assistant's response content
|
|
@@ -1060,8 +1123,12 @@ When troubleshooting:
|
|
|
1060
1123
|
} else if (this.toolImplementations[toolName]) {
|
|
1061
1124
|
// Execute native tool
|
|
1062
1125
|
try {
|
|
1063
|
-
// Add sessionId to params for tool execution
|
|
1064
|
-
const toolParams = {
|
|
1126
|
+
// Add sessionId and workingDirectory to params for tool execution
|
|
1127
|
+
const toolParams = {
|
|
1128
|
+
...params,
|
|
1129
|
+
sessionId: this.sessionId,
|
|
1130
|
+
workingDirectory: (this.allowedFolders && this.allowedFolders[0]) || process.cwd()
|
|
1131
|
+
};
|
|
1065
1132
|
|
|
1066
1133
|
// Emit tool start event
|
|
1067
1134
|
this.events.emit('toolCall', {
|
|
@@ -1214,8 +1281,10 @@ Remember: Use proper XML format with BOTH opening and closing tags:
|
|
|
1214
1281
|
<parameter>value</parameter>
|
|
1215
1282
|
</tool_name>
|
|
1216
1283
|
|
|
1217
|
-
Or for quick completion if your previous response was already correct:
|
|
1218
|
-
<attempt_complete
|
|
1284
|
+
Or for quick completion if your previous response was already correct and complete:
|
|
1285
|
+
<attempt_complete>
|
|
1286
|
+
|
|
1287
|
+
IMPORTANT: When using <attempt_complete>, this must be the ONLY content in your response. No additional text, explanations, or other content should be included. This tag signals to reuse your previous response as the final answer.`;
|
|
1219
1288
|
}
|
|
1220
1289
|
|
|
1221
1290
|
currentMessages.push({
|
|
@@ -1733,4 +1802,4 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
1733
1802
|
console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
|
|
1734
1803
|
}
|
|
1735
1804
|
}
|
|
1736
|
-
}
|
|
1805
|
+
}
|
package/src/agent/index.js
CHANGED
|
@@ -112,6 +112,7 @@ function parseArgs() {
|
|
|
112
112
|
acp: false,
|
|
113
113
|
question: null,
|
|
114
114
|
path: null,
|
|
115
|
+
allowedFolders: null,
|
|
115
116
|
prompt: null,
|
|
116
117
|
systemPrompt: null,
|
|
117
118
|
schema: null,
|
|
@@ -145,6 +146,8 @@ function parseArgs() {
|
|
|
145
146
|
config.allowEdit = true;
|
|
146
147
|
} else if (arg === '--path' && i + 1 < args.length) {
|
|
147
148
|
config.path = args[++i];
|
|
149
|
+
} else if (arg === '--allowed-folders' && i + 1 < args.length) {
|
|
150
|
+
config.allowedFolders = args[++i].split(',').map(dir => dir.trim());
|
|
148
151
|
} else if (arg === '--prompt' && i + 1 < args.length) {
|
|
149
152
|
config.prompt = args[++i];
|
|
150
153
|
} else if (arg === '--system-prompt' && i + 1 < args.length) {
|
|
@@ -201,6 +204,7 @@ Usage:
|
|
|
201
204
|
|
|
202
205
|
Options:
|
|
203
206
|
--path <dir> Search directory (default: current)
|
|
207
|
+
--allowed-folders <dirs> Comma-separated list of allowed directories for file operations
|
|
204
208
|
--prompt <type> Persona: code-explorer, engineer, code-review, support, architect
|
|
205
209
|
--system-prompt <text|file> Custom system prompt (text or file path)
|
|
206
210
|
--schema <schema|file> Output schema (JSON, XML, any format - text or file path)
|
|
@@ -296,17 +300,22 @@ class ProbeAgentMcpServer {
|
|
|
296
300
|
tools: [
|
|
297
301
|
{
|
|
298
302
|
name: 'search_code',
|
|
299
|
-
description: "
|
|
303
|
+
description: "AI agent that answers free-form questions about codebases. Ask detailed questions in natural language.",
|
|
300
304
|
inputSchema: {
|
|
301
305
|
type: 'object',
|
|
302
306
|
properties: {
|
|
303
307
|
query: {
|
|
304
308
|
type: 'string',
|
|
305
|
-
description: '
|
|
309
|
+
description: 'A detailed, free-form question about the codebase in natural language. Be specific and descriptive. Example: "How does the authentication system work and where is user session management implemented?"',
|
|
306
310
|
},
|
|
307
311
|
path: {
|
|
308
312
|
type: 'string',
|
|
309
|
-
description: '
|
|
313
|
+
description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
|
|
314
|
+
},
|
|
315
|
+
allowed_folders: {
|
|
316
|
+
type: 'array',
|
|
317
|
+
items: { type: 'string' },
|
|
318
|
+
description: 'Optional list of allowed directories for file operations. Defaults to current directory if not specified.',
|
|
310
319
|
},
|
|
311
320
|
prompt: {
|
|
312
321
|
type: 'string',
|
|
@@ -315,34 +324,6 @@ class ProbeAgentMcpServer {
|
|
|
315
324
|
system_prompt: {
|
|
316
325
|
type: 'string',
|
|
317
326
|
description: 'Optional custom system prompt (text or file path).',
|
|
318
|
-
},
|
|
319
|
-
provider: {
|
|
320
|
-
type: 'string',
|
|
321
|
-
description: 'Optional AI provider to force: anthropic, openai, google.',
|
|
322
|
-
},
|
|
323
|
-
model: {
|
|
324
|
-
type: 'string',
|
|
325
|
-
description: 'Optional model name override.',
|
|
326
|
-
},
|
|
327
|
-
allow_edit: {
|
|
328
|
-
type: 'boolean',
|
|
329
|
-
description: 'Enable code modification capabilities.',
|
|
330
|
-
},
|
|
331
|
-
max_iterations: {
|
|
332
|
-
type: 'number',
|
|
333
|
-
description: 'Maximum number of tool iterations (default: 30).',
|
|
334
|
-
},
|
|
335
|
-
max_response_tokens: {
|
|
336
|
-
type: 'number',
|
|
337
|
-
description: 'Maximum tokens for AI response (overrides model defaults).',
|
|
338
|
-
},
|
|
339
|
-
schema: {
|
|
340
|
-
type: 'string',
|
|
341
|
-
description: 'Optional output schema (JSON, XML, or any format - text or file path).',
|
|
342
|
-
},
|
|
343
|
-
no_mermaid_validation: {
|
|
344
|
-
type: 'boolean',
|
|
345
|
-
description: 'Disable automatic mermaid diagram validation and fixing.',
|
|
346
327
|
}
|
|
347
328
|
},
|
|
348
329
|
required: ['query']
|
|
@@ -404,7 +385,7 @@ class ProbeAgentMcpServer {
|
|
|
404
385
|
|
|
405
386
|
// Create agent with configuration
|
|
406
387
|
const agentConfig = {
|
|
407
|
-
path: args.path || process.cwd(),
|
|
388
|
+
path: args.path || (args.allowed_folders && args.allowed_folders[0]) || process.cwd(),
|
|
408
389
|
promptType: args.prompt || 'code-explorer',
|
|
409
390
|
customPrompt: systemPrompt,
|
|
410
391
|
provider: args.provider,
|
|
@@ -643,6 +624,7 @@ async function main() {
|
|
|
643
624
|
// Create and configure agent
|
|
644
625
|
const agentConfig = {
|
|
645
626
|
path: config.path,
|
|
627
|
+
allowedFolders: config.allowedFolders,
|
|
646
628
|
promptType: config.prompt,
|
|
647
629
|
customPrompt: systemPrompt,
|
|
648
630
|
allowEdit: config.allowEdit,
|
package/src/agent/probeTool.js
CHANGED
|
@@ -207,14 +207,24 @@ export function createWrappedTools(baseTools) {
|
|
|
207
207
|
// Simple file listing tool
|
|
208
208
|
export const listFilesTool = {
|
|
209
209
|
execute: async (params) => {
|
|
210
|
-
const { directory = '.' } = params;
|
|
210
|
+
const { directory = '.', workingDirectory } = params;
|
|
211
|
+
|
|
212
|
+
// Use the provided working directory, or fall back to process.cwd()
|
|
213
|
+
const baseCwd = workingDirectory || process.cwd();
|
|
214
|
+
|
|
215
|
+
// Security: Validate path to prevent traversal attacks
|
|
216
|
+
const secureBaseDir = path.resolve(baseCwd);
|
|
217
|
+
const targetDir = path.resolve(secureBaseDir, directory);
|
|
218
|
+
if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
|
|
219
|
+
throw new Error('Path traversal attempt detected. Access denied.');
|
|
220
|
+
}
|
|
211
221
|
|
|
212
222
|
try {
|
|
213
223
|
const files = await listFilesByLevel({
|
|
214
|
-
directory,
|
|
224
|
+
directory: targetDir,
|
|
215
225
|
maxFiles: 100,
|
|
216
226
|
respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === '',
|
|
217
|
-
cwd:
|
|
227
|
+
cwd: secureBaseDir
|
|
218
228
|
});
|
|
219
229
|
|
|
220
230
|
return files;
|
|
@@ -227,15 +237,23 @@ export const listFilesTool = {
|
|
|
227
237
|
// Simple file search tool
|
|
228
238
|
export const searchFilesTool = {
|
|
229
239
|
execute: async (params) => {
|
|
230
|
-
const { pattern, directory = '.', recursive = true } = params;
|
|
240
|
+
const { pattern, directory = '.', recursive = true, workingDirectory } = params;
|
|
231
241
|
|
|
232
242
|
if (!pattern) {
|
|
233
243
|
throw new Error('Pattern is required for file search');
|
|
234
244
|
}
|
|
235
245
|
|
|
246
|
+
// Security: Validate path to prevent traversal attacks
|
|
247
|
+
const baseCwd = workingDirectory || process.cwd();
|
|
248
|
+
const secureBaseDir = path.resolve(baseCwd);
|
|
249
|
+
const targetDir = path.resolve(secureBaseDir, directory);
|
|
250
|
+
if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
|
|
251
|
+
throw new Error('Path traversal attempt detected. Access denied.');
|
|
252
|
+
}
|
|
253
|
+
|
|
236
254
|
try {
|
|
237
255
|
const options = {
|
|
238
|
-
cwd:
|
|
256
|
+
cwd: targetDir,
|
|
239
257
|
ignore: ['node_modules/**', '.git/**'],
|
|
240
258
|
absolute: false
|
|
241
259
|
};
|
package/src/agent/tools.js
CHANGED
|
@@ -141,12 +141,45 @@ export function parseXmlToolCallWithThinking(xmlString, validTools) {
|
|
|
141
141
|
// Remove thinking tags and their content from the XML string
|
|
142
142
|
let cleanedXmlString = xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
|
|
143
143
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
// Enhanced recovery logic for attempt_complete shorthand
|
|
145
|
+
// Check for various forms of attempt_complete tags:
|
|
146
|
+
// 1. Perfect shorthand: <attempt_complete>
|
|
147
|
+
// 2. Self-closing: <attempt_complete/>
|
|
148
|
+
// 3. Empty with closing tag: <attempt_complete></attempt_complete>
|
|
149
|
+
// 4. Incomplete: <attempt_complete (missing closing bracket)
|
|
150
|
+
// 5. With trailing content that should be ignored
|
|
151
|
+
const attemptCompletePatterns = [
|
|
152
|
+
// Standard shorthand with optional whitespace
|
|
153
|
+
/^<attempt_complete>\s*$/,
|
|
154
|
+
// Empty with proper closing tag (common case from the logs)
|
|
155
|
+
/^<attempt_complete>\s*<\/attempt_complete>\s*$/,
|
|
156
|
+
// Self-closing variant
|
|
157
|
+
/^<attempt_complete\s*\/>\s*$/,
|
|
158
|
+
// Incomplete opening tag (missing closing bracket)
|
|
159
|
+
/^<attempt_complete\s*$/,
|
|
160
|
+
// With trailing content (extract just the tag part) - must come after empty tag pattern
|
|
161
|
+
/^<attempt_complete>(.*)$/s,
|
|
162
|
+
// Self-closing with trailing content
|
|
163
|
+
/^<attempt_complete\s*\/>(.*)$/s
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
for (const pattern of attemptCompletePatterns) {
|
|
167
|
+
const match = cleanedXmlString.match(pattern);
|
|
168
|
+
if (match) {
|
|
169
|
+
// Convert any form of attempt_complete to the standard format
|
|
170
|
+
return {
|
|
171
|
+
toolName: 'attempt_completion',
|
|
172
|
+
params: { result: '__PREVIOUS_RESPONSE__' }
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Additional recovery: check if the string contains attempt_complete anywhere
|
|
178
|
+
// and treat the entire response as a completion signal if no other tool tags are found
|
|
179
|
+
if (cleanedXmlString.includes('<attempt_complete') && !hasOtherToolTags(cleanedXmlString, validTools)) {
|
|
180
|
+
// This handles malformed cases where attempt_complete appears but is broken
|
|
148
181
|
return {
|
|
149
|
-
toolName: 'attempt_completion',
|
|
182
|
+
toolName: 'attempt_completion',
|
|
150
183
|
params: { result: '__PREVIOUS_RESPONSE__' }
|
|
151
184
|
};
|
|
152
185
|
}
|
|
@@ -160,4 +193,23 @@ export function parseXmlToolCallWithThinking(xmlString, validTools) {
|
|
|
160
193
|
}
|
|
161
194
|
|
|
162
195
|
return parsedTool;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Helper function to check if the XML string contains other tool tags
|
|
200
|
+
* @param {string} xmlString - The XML string to check
|
|
201
|
+
* @param {string[]} validTools - List of valid tool names
|
|
202
|
+
* @returns {boolean} - True if other tool tags are found
|
|
203
|
+
*/
|
|
204
|
+
function hasOtherToolTags(xmlString, validTools = []) {
|
|
205
|
+
const defaultTools = ['search', 'query', 'extract', 'listFiles', 'searchFiles', 'implement', 'attempt_completion'];
|
|
206
|
+
const toolsToCheck = validTools.length > 0 ? validTools : defaultTools;
|
|
207
|
+
|
|
208
|
+
// Check for any tool tags other than attempt_complete variants
|
|
209
|
+
for (const tool of toolsToCheck) {
|
|
210
|
+
if (tool !== 'attempt_completion' && xmlString.includes(`<${tool}`)) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
163
215
|
}
|
package/src/utils.js
CHANGED
|
@@ -43,7 +43,7 @@ export async function getBinaryPath(options = {}) {
|
|
|
43
43
|
|
|
44
44
|
// Check bin directory
|
|
45
45
|
const isWindows = process.platform === 'win32';
|
|
46
|
-
const binaryName = isWindows ? 'probe.exe' : 'probe';
|
|
46
|
+
const binaryName = isWindows ? 'probe.exe' : 'probe-binary';
|
|
47
47
|
const binaryPath = path.join(binDir, binaryName);
|
|
48
48
|
|
|
49
49
|
if (fs.existsSync(binaryPath) && !forceDownload) {
|