@probelabs/probe 0.6.0-rc161 → 0.6.0-rc163
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 +89 -0
- package/build/agent/ProbeAgent.js +361 -97
- package/build/agent/contextCompactor.js +271 -0
- package/build/agent/index.js +854 -94
- package/build/agent/schemaUtils.js +81 -0
- package/build/agent/xmlParsingUtils.js +24 -4
- package/build/tools/common.js +16 -1
- package/cjs/agent/ProbeAgent.cjs +889 -144
- package/cjs/index.cjs +889 -144
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +361 -97
- package/src/agent/contextCompactor.js +271 -0
- package/src/agent/index.js +30 -1
- package/src/agent/schemaUtils.js +81 -0
- package/src/agent/xmlParsingUtils.js +24 -4
- package/src/tools/common.js +16 -1
|
@@ -7,6 +7,80 @@ import { createMessagePreview } from '../tools/common.js';
|
|
|
7
7
|
import { validate, fixText, extractMermaidBlocks } from '@probelabs/maid';
|
|
8
8
|
import Ajv from 'ajv';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Generate an example JSON object from a JSON schema
|
|
12
|
+
* @param {Object|string} schema - JSON schema object or string
|
|
13
|
+
* @param {Object} options - Options for generation
|
|
14
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
15
|
+
* @returns {Object|null} - Example object, or null if schema cannot be processed
|
|
16
|
+
*/
|
|
17
|
+
export function generateExampleFromSchema(schema, options = {}) {
|
|
18
|
+
const { debug = false } = options;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const parsedSchema = typeof schema === 'string' ? JSON.parse(schema) : schema;
|
|
22
|
+
|
|
23
|
+
if (parsedSchema.type !== 'object' || !parsedSchema.properties) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const exampleObj = {};
|
|
28
|
+
for (const [key, value] of Object.entries(parsedSchema.properties)) {
|
|
29
|
+
if (value.type === 'boolean') {
|
|
30
|
+
exampleObj[key] = false;
|
|
31
|
+
} else if (value.type === 'number') {
|
|
32
|
+
exampleObj[key] = 0;
|
|
33
|
+
} else if (value.type === 'string') {
|
|
34
|
+
exampleObj[key] = value.description || 'your answer here';
|
|
35
|
+
} else if (value.type === 'array') {
|
|
36
|
+
exampleObj[key] = [];
|
|
37
|
+
} else {
|
|
38
|
+
exampleObj[key] = {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return exampleObj;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
if (debug) {
|
|
45
|
+
console.error('[DEBUG] generateExampleFromSchema: Failed to parse schema:', e.message);
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate schema instructions for AI to follow
|
|
53
|
+
* @param {Object|string} schema - JSON schema object or string
|
|
54
|
+
* @param {Object} options - Options for generation
|
|
55
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
56
|
+
* @returns {string} - Formatted schema instructions
|
|
57
|
+
*/
|
|
58
|
+
export function generateSchemaInstructions(schema, options = {}) {
|
|
59
|
+
const { debug = false } = options;
|
|
60
|
+
|
|
61
|
+
let instructions = '\n\nIMPORTANT: When you provide your final answer using attempt_completion, you MUST format it as valid JSON matching this schema:\n\n';
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const parsedSchema = typeof schema === 'string' ? JSON.parse(schema) : schema;
|
|
65
|
+
instructions += `${JSON.stringify(parsedSchema, null, 2)}\n\n`;
|
|
66
|
+
|
|
67
|
+
// Generate example using helper function
|
|
68
|
+
const exampleObj = generateExampleFromSchema(parsedSchema, { debug });
|
|
69
|
+
if (exampleObj) {
|
|
70
|
+
instructions += `Example:\n<attempt_completion>\n${JSON.stringify(exampleObj, null, 2)}\n</attempt_completion>\n\n`;
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
if (debug) {
|
|
74
|
+
console.error('[DEBUG] generateSchemaInstructions: Failed to parse schema:', e.message);
|
|
75
|
+
}
|
|
76
|
+
instructions += `${schema}\n\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
instructions += 'Your response inside attempt_completion must be ONLY valid JSON - no plain text, no explanations, no markdown.';
|
|
80
|
+
|
|
81
|
+
return instructions;
|
|
82
|
+
}
|
|
83
|
+
|
|
10
84
|
/**
|
|
11
85
|
* Recursively apply additionalProperties: false to all object schemas
|
|
12
86
|
* This ensures strict validation at all nesting levels
|
|
@@ -100,6 +174,13 @@ export function decodeHtmlEntities(text) {
|
|
|
100
174
|
/**
|
|
101
175
|
* Clean AI response by extracting JSON content when response contains JSON
|
|
102
176
|
* Only processes responses that contain JSON structures { or [
|
|
177
|
+
*
|
|
178
|
+
* NOTE: This function handles both JSON extraction AND content stripping.
|
|
179
|
+
* Future improvement: Consider splitting into separate functions for better separation of concerns:
|
|
180
|
+
* - extractJsonContent() - Find and extract JSON from response
|
|
181
|
+
* - stripNonJsonContent() - Remove explanatory text while preserving validation-relevant content
|
|
182
|
+
* This would allow validation to run on full responses before content is discarded.
|
|
183
|
+
*
|
|
103
184
|
* @param {string} response - Raw AI response
|
|
104
185
|
* @returns {string} - Cleaned response with JSON boundaries extracted if applicable
|
|
105
186
|
*/
|
|
@@ -59,10 +59,30 @@ export function extractThinkingContent(xmlString) {
|
|
|
59
59
|
export function checkAttemptCompleteRecovery(cleanedXmlString, validTools = []) {
|
|
60
60
|
// Check for <attempt_completion> with content (with or without closing tag)
|
|
61
61
|
// This handles: "<attempt_completion>content" or "<attempt_completion>content</attempt_completion>"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
|
|
63
|
+
// IMPORTANT: Use greedy match ([\s\S]*) instead of non-greedy ([\s\S]*?) to handle cases
|
|
64
|
+
// where the content contains the string "</attempt_completion>" (e.g., in regex patterns or code examples).
|
|
65
|
+
// We want to find the LAST occurrence of </attempt_completion>, not the first one.
|
|
66
|
+
const openTagIndex = cleanedXmlString.indexOf('<attempt_completion>');
|
|
67
|
+
if (openTagIndex !== -1) {
|
|
68
|
+
const afterOpenTag = cleanedXmlString.substring(openTagIndex + '<attempt_completion>'.length);
|
|
69
|
+
const closeTagIndex = cleanedXmlString.lastIndexOf('</attempt_completion>');
|
|
70
|
+
|
|
71
|
+
let content;
|
|
72
|
+
let hasClosingTag = false;
|
|
73
|
+
|
|
74
|
+
if (closeTagIndex !== -1 && closeTagIndex >= openTagIndex + '<attempt_completion>'.length) {
|
|
75
|
+
// Found a closing tag at or after the opening tag - extract content between them
|
|
76
|
+
content = cleanedXmlString.substring(
|
|
77
|
+
openTagIndex + '<attempt_completion>'.length,
|
|
78
|
+
closeTagIndex
|
|
79
|
+
).trim();
|
|
80
|
+
hasClosingTag = true;
|
|
81
|
+
} else {
|
|
82
|
+
// No closing tag - use content from opening tag to end of string
|
|
83
|
+
content = afterOpenTag.trim();
|
|
84
|
+
hasClosingTag = false;
|
|
85
|
+
}
|
|
66
86
|
|
|
67
87
|
if (content) {
|
|
68
88
|
// If there's content after the tag, use it as the result
|
package/build/tools/common.js
CHANGED
|
@@ -384,7 +384,22 @@ export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
|
|
|
384
384
|
continue; // Tool not found, try next tool
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
|
|
387
|
+
// For attempt_completion, use lastIndexOf to find the LAST occurrence of closing tag
|
|
388
|
+
// This prevents issues where the content contains the closing tag string (e.g., in regex patterns)
|
|
389
|
+
// For other tools, use indexOf from the opening tag position
|
|
390
|
+
let closeIndex;
|
|
391
|
+
if (toolName === 'attempt_completion') {
|
|
392
|
+
// Find the last occurrence of the closing tag in the entire string
|
|
393
|
+
// This assumes attempt_completion doesn't have nested tags of the same name
|
|
394
|
+
closeIndex = xmlString.lastIndexOf(closeTag);
|
|
395
|
+
// Make sure the closing tag is after the opening tag
|
|
396
|
+
if (closeIndex !== -1 && closeIndex <= openIndex + openTag.length) {
|
|
397
|
+
closeIndex = -1; // Invalid, treat as no closing tag
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
closeIndex = xmlString.indexOf(closeTag, openIndex + openTag.length);
|
|
401
|
+
}
|
|
402
|
+
|
|
388
403
|
let hasClosingTag = closeIndex !== -1;
|
|
389
404
|
|
|
390
405
|
// If no closing tag found, use content until end of string
|