@probelabs/probe 0.6.0-rc204 → 0.6.0-rc206
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-rc206-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc206-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc206-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc206-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc206-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +53 -10
- package/build/agent/index.js +181 -18
- package/build/agent/outputTruncator.js +108 -0
- package/cjs/agent/ProbeAgent.cjs +6592 -7508
- package/cjs/index.cjs +6617 -7533
- package/package.json +2 -2
- package/src/agent/ProbeAgent.js +53 -10
- package/src/agent/outputTruncator.js +108 -0
- package/bin/binaries/probe-v0.6.0-rc204-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc204-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc204-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc204-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc204-x86_64-unknown-linux-musl.tar.gz +0 -0
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-rc206",
|
|
4
4
|
"description": "Node.js wrapper for the probe code search tool",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"@ai-sdk/openai": "^2.0.10",
|
|
80
80
|
"@anthropic-ai/claude-agent-sdk": "^0.1.46",
|
|
81
81
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
82
|
-
"@probelabs/maid": "^0.0.
|
|
82
|
+
"@probelabs/maid": "^0.0.23",
|
|
83
83
|
"adm-zip": "^0.5.16",
|
|
84
84
|
"ai": "^5.0.0",
|
|
85
85
|
"ajv": "^8.17.1",
|
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -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,
|
|
@@ -145,6 +146,7 @@ export class ProbeAgent {
|
|
|
145
146
|
* @param {boolean} [options.fallback.stopOnSuccess=true] - Stop on first success
|
|
146
147
|
* @param {number} [options.fallback.maxTotalAttempts=10] - Maximum total attempts across all providers
|
|
147
148
|
* @param {string} [options.completionPrompt] - Custom prompt to run after attempt_completion for validation/review (runs before mermaid/JSON validation)
|
|
149
|
+
* @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
150
|
*/
|
|
149
151
|
constructor(options = {}) {
|
|
150
152
|
// Basic configuration
|
|
@@ -237,6 +239,9 @@ export class ProbeAgent {
|
|
|
237
239
|
// Initialize token counter
|
|
238
240
|
this.tokenCounter = new TokenCounter();
|
|
239
241
|
|
|
242
|
+
// Maximum output tokens for tool results (truncate if exceeded)
|
|
243
|
+
this.maxOutputTokens = getMaxOutputTokens(options.maxOutputTokens);
|
|
244
|
+
|
|
240
245
|
if (this.debug) {
|
|
241
246
|
console.log(`[DEBUG] Generated session ID for agent: ${this.sessionId}`);
|
|
242
247
|
console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
|
|
@@ -2882,7 +2887,24 @@ Follow these instructions carefully:
|
|
|
2882
2887
|
// Execute MCP tool through the bridge
|
|
2883
2888
|
const executionResult = await this.mcpBridge.mcpTools[toolName].execute(params);
|
|
2884
2889
|
|
|
2885
|
-
|
|
2890
|
+
let toolResultContent = typeof executionResult === 'string' ? executionResult : JSON.stringify(executionResult, null, 2);
|
|
2891
|
+
|
|
2892
|
+
// Truncate if output exceeds token limit
|
|
2893
|
+
try {
|
|
2894
|
+
const truncateResult = await truncateIfNeeded(toolResultContent, this.tokenCounter, this.sessionId, this.maxOutputTokens);
|
|
2895
|
+
if (truncateResult.truncated) {
|
|
2896
|
+
toolResultContent = truncateResult.content;
|
|
2897
|
+
if (this.debug) {
|
|
2898
|
+
console.log(`[DEBUG] Tool output truncated: ${truncateResult.originalTokens} tokens -> saved to ${truncateResult.tempFilePath || 'N/A'}`);
|
|
2899
|
+
if (truncateResult.error) {
|
|
2900
|
+
console.log(`[DEBUG] Truncation file error: ${truncateResult.error}`);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
} catch (truncateError) {
|
|
2905
|
+
// If truncation fails entirely, log and continue with original content
|
|
2906
|
+
console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
|
|
2907
|
+
}
|
|
2886
2908
|
|
|
2887
2909
|
// Log MCP tool result in debug mode
|
|
2888
2910
|
if (this.debug) {
|
|
@@ -3059,10 +3081,28 @@ Follow these instructions carefully:
|
|
|
3059
3081
|
|
|
3060
3082
|
// Add assistant response and tool result to conversation
|
|
3061
3083
|
currentMessages.push({ role: 'assistant', content: assistantResponseContent });
|
|
3062
|
-
|
|
3063
|
-
|
|
3084
|
+
|
|
3085
|
+
let toolResultContent = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
|
|
3086
|
+
|
|
3087
|
+
// Truncate if output exceeds token limit
|
|
3088
|
+
try {
|
|
3089
|
+
const truncateResult = await truncateIfNeeded(toolResultContent, this.tokenCounter, this.sessionId, this.maxOutputTokens);
|
|
3090
|
+
if (truncateResult.truncated) {
|
|
3091
|
+
toolResultContent = truncateResult.content;
|
|
3092
|
+
if (this.debug) {
|
|
3093
|
+
console.log(`[DEBUG] Tool output truncated: ${truncateResult.originalTokens} tokens -> saved to ${truncateResult.tempFilePath || 'N/A'}`);
|
|
3094
|
+
if (truncateResult.error) {
|
|
3095
|
+
console.log(`[DEBUG] Truncation file error: ${truncateResult.error}`);
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
} catch (truncateError) {
|
|
3100
|
+
// If truncation fails entirely, log and continue with original content
|
|
3101
|
+
console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3064
3104
|
const toolResultMessage = `<tool_result>\n${toolResultContent}\n</tool_result>`;
|
|
3065
|
-
|
|
3105
|
+
|
|
3066
3106
|
currentMessages.push({
|
|
3067
3107
|
role: 'user',
|
|
3068
3108
|
content: toolResultMessage
|
|
@@ -3146,10 +3186,13 @@ Remember: Use proper XML format with BOTH opening and closing tags:
|
|
|
3146
3186
|
|
|
3147
3187
|
Available tools: ${validTools.join(', ')}
|
|
3148
3188
|
|
|
3149
|
-
|
|
3150
|
-
<
|
|
3189
|
+
To complete with a direct answer:
|
|
3190
|
+
<attempt_completion>Your final answer here</attempt_completion>
|
|
3191
|
+
|
|
3192
|
+
Or if your previous response already contains a complete, direct answer (not a thinking block or JSON):
|
|
3193
|
+
<attempt_complete></attempt_complete>
|
|
3151
3194
|
|
|
3152
|
-
|
|
3195
|
+
Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant message as the final answer. Only use this if that message was already a valid, complete response to the user's question.`;
|
|
3153
3196
|
}
|
|
3154
3197
|
|
|
3155
3198
|
currentMessages.push({
|
|
@@ -4062,9 +4105,9 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
4062
4105
|
return true;
|
|
4063
4106
|
}
|
|
4064
4107
|
|
|
4065
|
-
// Empty attempt_complete reminders
|
|
4066
|
-
if (content.includes('
|
|
4067
|
-
content.includes('
|
|
4108
|
+
// Empty attempt_complete reminders (legacy and new format)
|
|
4109
|
+
if (content.includes('<attempt_complete></attempt_complete>') &&
|
|
4110
|
+
content.includes('reuses your PREVIOUS assistant message')) {
|
|
4068
4111
|
return true;
|
|
4069
4112
|
}
|
|
4070
4113
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { writeFile, mkdir } from 'fs/promises';
|
|
2
|
+
import { tmpdir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 20000;
|
|
7
|
+
const CHARS_PER_TOKEN = 4; // Conservative approximation
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate and normalize a token limit value.
|
|
11
|
+
* Returns the default if the value is invalid (NaN, negative, zero).
|
|
12
|
+
* @param {any} value - The value to validate
|
|
13
|
+
* @returns {number} A valid positive token limit
|
|
14
|
+
*/
|
|
15
|
+
function validateTokenLimit(value) {
|
|
16
|
+
const num = Number(value);
|
|
17
|
+
if (isNaN(num) || num <= 0) {
|
|
18
|
+
return DEFAULT_MAX_OUTPUT_TOKENS;
|
|
19
|
+
}
|
|
20
|
+
return num;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the maximum output tokens limit based on priority:
|
|
25
|
+
* 1. Constructor value (if provided and valid)
|
|
26
|
+
* 2. Environment variable PROBE_MAX_OUTPUT_TOKENS (if valid)
|
|
27
|
+
* 3. Default (20000)
|
|
28
|
+
* @param {number|undefined} constructorValue - Value passed to ProbeAgent constructor
|
|
29
|
+
* @returns {number} The maximum output tokens limit (always a valid positive number)
|
|
30
|
+
*/
|
|
31
|
+
export function getMaxOutputTokens(constructorValue) {
|
|
32
|
+
if (constructorValue !== undefined && constructorValue !== null) {
|
|
33
|
+
const validated = validateTokenLimit(constructorValue);
|
|
34
|
+
// Only use constructor value if it was valid; otherwise fall through to env/default
|
|
35
|
+
if (validated !== DEFAULT_MAX_OUTPUT_TOKENS || Number(constructorValue) === DEFAULT_MAX_OUTPUT_TOKENS) {
|
|
36
|
+
return validated;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (process.env.PROBE_MAX_OUTPUT_TOKENS) {
|
|
40
|
+
return validateTokenLimit(process.env.PROBE_MAX_OUTPUT_TOKENS);
|
|
41
|
+
}
|
|
42
|
+
return DEFAULT_MAX_OUTPUT_TOKENS;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Truncate tool output if it exceeds the token limit.
|
|
47
|
+
* When truncated, saves full output to a temp file and returns a message with the file path.
|
|
48
|
+
* If file system operations fail, returns truncated content without file reference.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} content - The tool output content to potentially truncate
|
|
51
|
+
* @param {Object} tokenCounter - TokenCounter instance with countTokens method
|
|
52
|
+
* @param {string} sessionId - Session ID for naming temp files
|
|
53
|
+
* @param {number} maxTokens - Maximum tokens allowed (defaults to 20000)
|
|
54
|
+
* @returns {Promise<{truncated: boolean, content: string, tempFilePath?: string, originalTokens?: number, error?: string}>}
|
|
55
|
+
*/
|
|
56
|
+
export async function truncateIfNeeded(content, tokenCounter, sessionId, maxTokens) {
|
|
57
|
+
const limit = validateTokenLimit(maxTokens);
|
|
58
|
+
const tokenCount = tokenCounter.countTokens(content);
|
|
59
|
+
|
|
60
|
+
if (tokenCount <= limit) {
|
|
61
|
+
return { truncated: false, content };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Truncate to approximately maxTokens worth of characters
|
|
65
|
+
const maxChars = limit * CHARS_PER_TOKEN;
|
|
66
|
+
const truncatedContent = content.substring(0, maxChars);
|
|
67
|
+
|
|
68
|
+
// Try to write full output to temp file
|
|
69
|
+
let tempFilePath = null;
|
|
70
|
+
let fileError = null;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const tempDir = join(tmpdir(), 'probe-output');
|
|
74
|
+
await mkdir(tempDir, { recursive: true });
|
|
75
|
+
tempFilePath = join(tempDir, `tool-output-${sessionId || 'unknown'}-${randomUUID()}.txt`);
|
|
76
|
+
await writeFile(tempFilePath, content, 'utf8');
|
|
77
|
+
} catch (err) {
|
|
78
|
+
fileError = err.message || 'Unknown file system error';
|
|
79
|
+
tempFilePath = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let message;
|
|
83
|
+
if (tempFilePath) {
|
|
84
|
+
message = `Output exceeded maximum size (${tokenCount} tokens, limit: ${limit}).
|
|
85
|
+
Full output saved to: ${tempFilePath}
|
|
86
|
+
|
|
87
|
+
--- Truncated Output (first ${limit} tokens approx) ---
|
|
88
|
+
${truncatedContent}
|
|
89
|
+
...
|
|
90
|
+
--- End of Truncated Output ---`;
|
|
91
|
+
} else {
|
|
92
|
+
message = `Output exceeded maximum size (${tokenCount} tokens, limit: ${limit}).
|
|
93
|
+
Warning: Could not save full output to file (${fileError}).
|
|
94
|
+
|
|
95
|
+
--- Truncated Output (first ${limit} tokens approx) ---
|
|
96
|
+
${truncatedContent}
|
|
97
|
+
...
|
|
98
|
+
--- End of Truncated Output ---`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
truncated: true,
|
|
103
|
+
content: message,
|
|
104
|
+
tempFilePath: tempFilePath || undefined,
|
|
105
|
+
originalTokens: tokenCount,
|
|
106
|
+
error: fileError || undefined
|
|
107
|
+
};
|
|
108
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|