@probelabs/probe 0.6.0-rc205 → 0.6.0-rc207
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/bin/binaries/probe-v0.6.0-rc207-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +188 -6
- package/build/agent/index.js +239 -16
- package/build/agent/outputTruncator.js +108 -0
- package/build/tools/common.js +31 -0
- package/cjs/agent/ProbeAgent.cjs +6657 -7513
- package/cjs/index.cjs +6682 -7538
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +188 -6
- package/src/agent/outputTruncator.js +108 -0
- package/src/tools/common.js +31 -0
- package/bin/binaries/probe-v0.6.0-rc205-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc205-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc205-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc205-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc205-x86_64-unknown-linux-musl.tar.gz +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -70,6 +70,7 @@ import { RetryManager, createRetryManagerFromEnv } from './RetryManager.js';
|
|
|
70
70
|
import { FallbackManager, createFallbackManagerFromEnv, buildFallbackProvidersFromEnv } from './FallbackManager.js';
|
|
71
71
|
import { handleContextLimitError } from './contextCompactor.js';
|
|
72
72
|
import { formatErrorForAI, ParameterError } from '../utils/error-types.js';
|
|
73
|
+
import { truncateIfNeeded, getMaxOutputTokens } from './outputTruncator.js';
|
|
73
74
|
import {
|
|
74
75
|
TaskManager,
|
|
75
76
|
createTaskTool,
|
|
@@ -90,6 +91,45 @@ const MAX_TOOL_ITERATIONS = (() => {
|
|
|
90
91
|
})();
|
|
91
92
|
const MAX_HISTORY_MESSAGES = 100;
|
|
92
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Extract tool name from wrapped_tool:toolName format
|
|
96
|
+
* @param {string} wrappedToolError - Error string in format 'wrapped_tool:toolName'
|
|
97
|
+
* @returns {string} The extracted tool name or 'unknown' if format is invalid
|
|
98
|
+
*/
|
|
99
|
+
function extractWrappedToolName(wrappedToolError) {
|
|
100
|
+
if (!wrappedToolError || typeof wrappedToolError !== 'string') {
|
|
101
|
+
return 'unknown';
|
|
102
|
+
}
|
|
103
|
+
const colonIndex = wrappedToolError.indexOf(':');
|
|
104
|
+
return colonIndex !== -1 ? wrappedToolError.slice(colonIndex + 1) : 'unknown';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check if an error indicates a wrapped tool format error
|
|
109
|
+
* @param {string|null} error - Error from detectUnrecognizedToolCall
|
|
110
|
+
* @returns {boolean} True if it's a wrapped tool error
|
|
111
|
+
*/
|
|
112
|
+
function isWrappedToolError(error) {
|
|
113
|
+
return error && typeof error === 'string' && error.startsWith('wrapped_tool:');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create error message for wrapped tool format issues
|
|
118
|
+
* @param {string} wrappedToolName - The tool name that was incorrectly wrapped
|
|
119
|
+
* @returns {string} User-friendly error message with correct format instructions
|
|
120
|
+
*/
|
|
121
|
+
function createWrappedToolErrorMessage(wrappedToolName) {
|
|
122
|
+
return `Your response contained an incorrectly formatted tool call (${wrappedToolName} wrapped in XML tags). This cannot be used.
|
|
123
|
+
|
|
124
|
+
Please use the CORRECT format:
|
|
125
|
+
|
|
126
|
+
<${wrappedToolName}>
|
|
127
|
+
Your content here
|
|
128
|
+
</${wrappedToolName}>
|
|
129
|
+
|
|
130
|
+
Do NOT wrap in other tags like <api_call>, <tool_name>, <function>, etc.`;
|
|
131
|
+
}
|
|
132
|
+
|
|
93
133
|
// Supported image file extensions (imported from shared config)
|
|
94
134
|
|
|
95
135
|
// Maximum image file size (20MB) to prevent OOM attacks
|
|
@@ -145,6 +185,7 @@ export class ProbeAgent {
|
|
|
145
185
|
* @param {boolean} [options.fallback.stopOnSuccess=true] - Stop on first success
|
|
146
186
|
* @param {number} [options.fallback.maxTotalAttempts=10] - Maximum total attempts across all providers
|
|
147
187
|
* @param {string} [options.completionPrompt] - Custom prompt to run after attempt_completion for validation/review (runs before mermaid/JSON validation)
|
|
188
|
+
* @param {number} [options.maxOutputTokens] - Maximum tokens for tool output before truncation (default: 20000, can also be set via PROBE_MAX_OUTPUT_TOKENS env var)
|
|
148
189
|
*/
|
|
149
190
|
constructor(options = {}) {
|
|
150
191
|
// Basic configuration
|
|
@@ -237,6 +278,9 @@ export class ProbeAgent {
|
|
|
237
278
|
// Initialize token counter
|
|
238
279
|
this.tokenCounter = new TokenCounter();
|
|
239
280
|
|
|
281
|
+
// Maximum output tokens for tool results (truncate if exceeded)
|
|
282
|
+
this.maxOutputTokens = getMaxOutputTokens(options.maxOutputTokens);
|
|
283
|
+
|
|
240
284
|
if (this.debug) {
|
|
241
285
|
console.log(`[DEBUG] Generated session ID for agent: ${this.sessionId}`);
|
|
242
286
|
console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
|
|
@@ -2537,6 +2581,11 @@ Follow these instructions carefully:
|
|
|
2537
2581
|
}
|
|
2538
2582
|
}
|
|
2539
2583
|
|
|
2584
|
+
// Circuit breaker for repeated format errors
|
|
2585
|
+
let lastFormatErrorType = null;
|
|
2586
|
+
let sameFormatErrorCount = 0;
|
|
2587
|
+
const MAX_REPEATED_FORMAT_ERRORS = 3;
|
|
2588
|
+
|
|
2540
2589
|
// Tool iteration loop (only for non-CLI engines like Vercel/Anthropic/OpenAI)
|
|
2541
2590
|
while (currentIteration < maxIterations && !completionAttempted) {
|
|
2542
2591
|
currentIteration++;
|
|
@@ -2830,7 +2879,28 @@ Follow these instructions carefully:
|
|
|
2830
2879
|
);
|
|
2831
2880
|
|
|
2832
2881
|
if (lastAssistantMessage) {
|
|
2833
|
-
|
|
2882
|
+
const prevContent = lastAssistantMessage.content;
|
|
2883
|
+
|
|
2884
|
+
// Check for patterns indicating a failed/wrapped tool call attempt
|
|
2885
|
+
// Use detectUnrecognizedToolCall for consistent detection logic
|
|
2886
|
+
const wrappedToolError = detectUnrecognizedToolCall(prevContent, validTools);
|
|
2887
|
+
|
|
2888
|
+
if (isWrappedToolError(wrappedToolError)) {
|
|
2889
|
+
// Previous response was a broken tool call attempt - don't reuse it
|
|
2890
|
+
const wrappedToolName = extractWrappedToolName(wrappedToolError);
|
|
2891
|
+
if (this.debug) {
|
|
2892
|
+
console.log(`[DEBUG] Previous response contains wrapped tool '${wrappedToolName}' - rejecting for __PREVIOUS_RESPONSE__`);
|
|
2893
|
+
}
|
|
2894
|
+
currentMessages.push({ role: 'assistant', content: assistantResponseContent });
|
|
2895
|
+
currentMessages.push({
|
|
2896
|
+
role: 'user',
|
|
2897
|
+
content: createWrappedToolErrorMessage(wrappedToolName)
|
|
2898
|
+
});
|
|
2899
|
+
completionAttempted = false;
|
|
2900
|
+
continue; // Don't use broken response, continue the loop
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
finalResult = prevContent;
|
|
2834
2904
|
if (this.debug) console.log(`[DEBUG] Using previous response as completion: ${finalResult.substring(0, 100)}...`);
|
|
2835
2905
|
} else {
|
|
2836
2906
|
finalResult = 'Error: No previous response found to use as completion.';
|
|
@@ -2882,7 +2952,24 @@ Follow these instructions carefully:
|
|
|
2882
2952
|
// Execute MCP tool through the bridge
|
|
2883
2953
|
const executionResult = await this.mcpBridge.mcpTools[toolName].execute(params);
|
|
2884
2954
|
|
|
2885
|
-
|
|
2955
|
+
let toolResultContent = typeof executionResult === 'string' ? executionResult : JSON.stringify(executionResult, null, 2);
|
|
2956
|
+
|
|
2957
|
+
// Truncate if output exceeds token limit
|
|
2958
|
+
try {
|
|
2959
|
+
const truncateResult = await truncateIfNeeded(toolResultContent, this.tokenCounter, this.sessionId, this.maxOutputTokens);
|
|
2960
|
+
if (truncateResult.truncated) {
|
|
2961
|
+
toolResultContent = truncateResult.content;
|
|
2962
|
+
if (this.debug) {
|
|
2963
|
+
console.log(`[DEBUG] Tool output truncated: ${truncateResult.originalTokens} tokens -> saved to ${truncateResult.tempFilePath || 'N/A'}`);
|
|
2964
|
+
if (truncateResult.error) {
|
|
2965
|
+
console.log(`[DEBUG] Truncation file error: ${truncateResult.error}`);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
} catch (truncateError) {
|
|
2970
|
+
// If truncation fails entirely, log and continue with original content
|
|
2971
|
+
console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
|
|
2972
|
+
}
|
|
2886
2973
|
|
|
2887
2974
|
// Log MCP tool result in debug mode
|
|
2888
2975
|
if (this.debug) {
|
|
@@ -3059,10 +3146,28 @@ Follow these instructions carefully:
|
|
|
3059
3146
|
|
|
3060
3147
|
// Add assistant response and tool result to conversation
|
|
3061
3148
|
currentMessages.push({ role: 'assistant', content: assistantResponseContent });
|
|
3062
|
-
|
|
3063
|
-
|
|
3149
|
+
|
|
3150
|
+
let toolResultContent = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
|
|
3151
|
+
|
|
3152
|
+
// Truncate if output exceeds token limit
|
|
3153
|
+
try {
|
|
3154
|
+
const truncateResult = await truncateIfNeeded(toolResultContent, this.tokenCounter, this.sessionId, this.maxOutputTokens);
|
|
3155
|
+
if (truncateResult.truncated) {
|
|
3156
|
+
toolResultContent = truncateResult.content;
|
|
3157
|
+
if (this.debug) {
|
|
3158
|
+
console.log(`[DEBUG] Tool output truncated: ${truncateResult.originalTokens} tokens -> saved to ${truncateResult.tempFilePath || 'N/A'}`);
|
|
3159
|
+
if (truncateResult.error) {
|
|
3160
|
+
console.log(`[DEBUG] Truncation file error: ${truncateResult.error}`);
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
} catch (truncateError) {
|
|
3165
|
+
// If truncation fails entirely, log and continue with original content
|
|
3166
|
+
console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3064
3169
|
const toolResultMessage = `<tool_result>\n${toolResultContent}\n</tool_result>`;
|
|
3065
|
-
|
|
3170
|
+
|
|
3066
3171
|
currentMessages.push({
|
|
3067
3172
|
role: 'user',
|
|
3068
3173
|
content: toolResultMessage
|
|
@@ -3125,7 +3230,32 @@ Follow these instructions carefully:
|
|
|
3125
3230
|
const unrecognizedTool = detectUnrecognizedToolCall(assistantResponseContent, validTools);
|
|
3126
3231
|
|
|
3127
3232
|
let reminderContent;
|
|
3128
|
-
if (unrecognizedTool) {
|
|
3233
|
+
if (isWrappedToolError(unrecognizedTool)) {
|
|
3234
|
+
// AI wrapped a valid tool name in arbitrary XML tags - provide clear format error
|
|
3235
|
+
const wrappedToolName = extractWrappedToolName(unrecognizedTool);
|
|
3236
|
+
if (this.debug) {
|
|
3237
|
+
console.log(`[DEBUG] Detected wrapped tool '${wrappedToolName}' in assistant response - wrong XML format.`);
|
|
3238
|
+
}
|
|
3239
|
+
const toolError = new ParameterError(
|
|
3240
|
+
`Tool '${wrappedToolName}' found but in WRONG FORMAT - do not wrap tools in other XML tags.`,
|
|
3241
|
+
{
|
|
3242
|
+
suggestion: `Use the tool tag DIRECTLY without any wrapper:
|
|
3243
|
+
|
|
3244
|
+
CORRECT FORMAT:
|
|
3245
|
+
<${wrappedToolName}>
|
|
3246
|
+
<param>value</param>
|
|
3247
|
+
</${wrappedToolName}>
|
|
3248
|
+
|
|
3249
|
+
WRONG (what you did - do not wrap in other tags):
|
|
3250
|
+
<api_call><tool_name>${wrappedToolName}</tool_name>...</api_call>
|
|
3251
|
+
<function>${wrappedToolName}</function>
|
|
3252
|
+
<call name="${wrappedToolName}">...</call>
|
|
3253
|
+
|
|
3254
|
+
Remove ALL wrapper tags and use <${wrappedToolName}> directly as the outermost tag.`
|
|
3255
|
+
}
|
|
3256
|
+
);
|
|
3257
|
+
reminderContent = `<tool_result>\n${formatErrorForAI(toolError)}\n</tool_result>`;
|
|
3258
|
+
} else if (unrecognizedTool) {
|
|
3129
3259
|
// AI tried to use a tool that's not available - provide clear error
|
|
3130
3260
|
if (this.debug) {
|
|
3131
3261
|
console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
|
|
@@ -3135,6 +3265,33 @@ Follow these instructions carefully:
|
|
|
3135
3265
|
});
|
|
3136
3266
|
reminderContent = `<tool_result>\n${formatErrorForAI(toolError)}\n</tool_result>`;
|
|
3137
3267
|
} else {
|
|
3268
|
+
// No tool call detected at all - check if this is the last iteration
|
|
3269
|
+
// On the last iteration, if the AI gave a substantive response without using
|
|
3270
|
+
// attempt_completion, accept it as the final answer rather than losing the content
|
|
3271
|
+
if (currentIteration >= maxIterations) {
|
|
3272
|
+
// Clean up the response - remove thinking tags
|
|
3273
|
+
let cleanedResponse = assistantResponseContent;
|
|
3274
|
+
// Remove <thinking>...</thinking> blocks
|
|
3275
|
+
cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '').trim();
|
|
3276
|
+
// Also remove unclosed thinking tags
|
|
3277
|
+
cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*$/gi, '').trim();
|
|
3278
|
+
|
|
3279
|
+
// Only use if there's substantial content (not just a failed tool call attempt)
|
|
3280
|
+
const hasSubstantialContent = cleanedResponse.length > 50 &&
|
|
3281
|
+
!cleanedResponse.includes('<api_call>') &&
|
|
3282
|
+
!cleanedResponse.includes('<tool_name>') &&
|
|
3283
|
+
!cleanedResponse.includes('<function>');
|
|
3284
|
+
|
|
3285
|
+
if (hasSubstantialContent) {
|
|
3286
|
+
if (this.debug) {
|
|
3287
|
+
console.log(`[DEBUG] Max iterations reached - accepting AI response as final answer (${cleanedResponse.length} chars)`);
|
|
3288
|
+
}
|
|
3289
|
+
finalResult = cleanedResponse;
|
|
3290
|
+
completionAttempted = true;
|
|
3291
|
+
break;
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3138
3295
|
// Standard reminder - no tool call detected at all
|
|
3139
3296
|
reminderContent = `Please use one of the available tools to help answer the question, or use attempt_completion if you have enough information to provide a final answer.
|
|
3140
3297
|
|
|
@@ -3166,6 +3323,31 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
|
|
|
3166
3323
|
console.log(`[DEBUG] No tool call detected in assistant response. Prompting for tool use.`);
|
|
3167
3324
|
}
|
|
3168
3325
|
}
|
|
3326
|
+
|
|
3327
|
+
// Circuit breaker: track repeated format errors and break early
|
|
3328
|
+
// For wrapped_tool errors, track them as a category (any wrapped_tool counts)
|
|
3329
|
+
// For other errors, track the exact error type
|
|
3330
|
+
if (unrecognizedTool) {
|
|
3331
|
+
const isWrapped = isWrappedToolError(unrecognizedTool);
|
|
3332
|
+
const errorCategory = isWrapped ? 'wrapped_tool' : unrecognizedTool;
|
|
3333
|
+
|
|
3334
|
+
if (errorCategory === lastFormatErrorType) {
|
|
3335
|
+
sameFormatErrorCount++;
|
|
3336
|
+
if (sameFormatErrorCount >= MAX_REPEATED_FORMAT_ERRORS) {
|
|
3337
|
+
const errorDesc = isWrapped ? 'wrapped tool format' : unrecognizedTool;
|
|
3338
|
+
console.error(`[ERROR] Format error category '${errorCategory}' repeated ${sameFormatErrorCount} times. Breaking loop early to prevent infinite iteration.`);
|
|
3339
|
+
finalResult = `Error: Unable to complete request. The AI model repeatedly used incorrect tool call format (${errorDesc}). Please try rephrasing your question or using a different model.`;
|
|
3340
|
+
break;
|
|
3341
|
+
}
|
|
3342
|
+
} else {
|
|
3343
|
+
lastFormatErrorType = errorCategory;
|
|
3344
|
+
sameFormatErrorCount = 1;
|
|
3345
|
+
}
|
|
3346
|
+
} else {
|
|
3347
|
+
// Reset counter if it's a different kind of "no tool call" situation
|
|
3348
|
+
lastFormatErrorType = null;
|
|
3349
|
+
sameFormatErrorCount = 0;
|
|
3350
|
+
}
|
|
3169
3351
|
}
|
|
3170
3352
|
|
|
3171
3353
|
// Keep message history manageable
|