Skip to content

Commit 8eb19f2

Browse files
kwonah0claude
andcommitted
feat: enhance marker-based extraction with advanced features
- Improve marker extraction with case-insensitive fallback search - Add proper end marker search after start marker position - Enhanced logging with extraction details and preview - Add comprehensive test suites for marker functionality - Support inline markers at any position in text - Handle nested markers correctly (extract first pair) - Update package version to 1.3.2 for consistency Tests verify: - Basic and inline marker extraction - Case-insensitive marker matching - Custom marker support (### START ### / ### END ###) - Real-world LLM output scenarios - Fallback behavior when markers are missing - Multi-line content extraction - Shell command integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 52b56e0 commit 8eb19f2

File tree

5 files changed

+348
-9
lines changed

5 files changed

+348
-9
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dtui2-react",
3-
"version": "1.1.0",
3+
"version": "1.3.2",
44
"main": "electron/main.js",
55
"scripts": {
66
"dev": "vite",

src/agents/ElectronShellAIAgent.ts

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,51 @@ export class ElectronShellAIAgent implements AIAgent {
112112
}
113113

114114
const { startMarker, endMarker } = this.outputConfig.extraction;
115-
const startIndex = text.indexOf(startMarker);
116-
const endIndex = text.indexOf(endMarker);
117115

118-
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
119-
const extracted = text.substring(startIndex + startMarker.length, endIndex).trim();
120-
console.log('✅ Extracted response between markers:', { startIndex, endIndex, extracted: extracted.slice(0, 100) + '...' });
121-
return extracted;
116+
// Find start marker (case-insensitive search as fallback)
117+
let startIndex = text.indexOf(startMarker);
118+
if (startIndex === -1) {
119+
// Try case-insensitive search
120+
const lowerText = text.toLowerCase();
121+
const lowerStartMarker = startMarker.toLowerCase();
122+
startIndex = lowerText.indexOf(lowerStartMarker);
123+
if (startIndex !== -1) {
124+
console.log('🔍 Found start marker with case-insensitive search');
125+
}
122126
}
123127

124-
console.log('⚠️ Markers not found, using fallback (full output)');
125-
return text;
128+
if (startIndex === -1) {
129+
console.log('⚠️ Start marker not found, using fallback (full output)');
130+
return text;
131+
}
132+
133+
// Find end marker after start marker
134+
const searchStart = startIndex + startMarker.length;
135+
let endIndex = text.indexOf(endMarker, searchStart);
136+
if (endIndex === -1) {
137+
// Try case-insensitive search for end marker
138+
const lowerText = text.toLowerCase();
139+
const lowerEndMarker = endMarker.toLowerCase();
140+
endIndex = lowerText.indexOf(lowerEndMarker, searchStart);
141+
if (endIndex !== -1) {
142+
console.log('🔍 Found end marker with case-insensitive search');
143+
}
144+
}
145+
146+
if (endIndex === -1) {
147+
console.log('⚠️ End marker not found, using fallback (full output)');
148+
return text;
149+
}
150+
151+
const extracted = text.substring(startIndex + startMarker.length, endIndex).trim();
152+
console.log('✅ Extracted response between markers:', {
153+
startIndex,
154+
endIndex,
155+
extractedLength: extracted.length,
156+
preview: extracted.slice(0, 100) + (extracted.length > 100 ? '...' : '')
157+
});
158+
159+
return extracted;
126160
};
127161

128162
if (result.success) {

test-marker-advanced.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env node
2+
3+
// Test advanced marker extraction functionality
4+
const testCases = [
5+
{
6+
name: "Case insensitive markers",
7+
text: "Some text\n<response>\nExtracted content\n</response>\nMore text",
8+
config: { enabled: true, startMarker: '<RESPONSE>', endMarker: '</RESPONSE>' },
9+
expected: "Extracted content"
10+
},
11+
{
12+
name: "Mixed case markers",
13+
text: "Text <Response>Mixed case content</RESPONSE> end",
14+
config: { enabled: true, startMarker: '<RESPONSE>', endMarker: '</RESPONSE>' },
15+
expected: "Mixed case content"
16+
},
17+
{
18+
name: "Nested similar markers",
19+
text: "A<RESPONSE>First<RESPONSE>Nested</RESPONSE>End</RESPONSE>B",
20+
config: { enabled: true, startMarker: '<RESPONSE>', endMarker: '</RESPONSE>' },
21+
expected: "First<RESPONSE>Nested"
22+
},
23+
{
24+
name: "Custom markers",
25+
text: "Prefix ### START ###\nCustom extracted content\n### END ### Suffix",
26+
config: { enabled: true, startMarker: '### START ###', endMarker: '### END ###' },
27+
expected: "Custom extracted content"
28+
},
29+
{
30+
name: "Markers in middle of lines",
31+
text: "Some long line with <RESPONSE>inline content</RESPONSE> and more text after",
32+
config: { enabled: true, startMarker: '<RESPONSE>', endMarker: '</RESPONSE>' },
33+
expected: "inline content"
34+
},
35+
{
36+
name: "Empty extraction",
37+
text: "Text <RESPONSE></RESPONSE> more text",
38+
config: { enabled: true, startMarker: '<RESPONSE>', endMarker: '</RESPONSE>' },
39+
expected: ""
40+
},
41+
{
42+
name: "Whitespace only extraction",
43+
text: "Text <RESPONSE> \n\n </RESPONSE> more text",
44+
config: { enabled: true, startMarker: '<RESPONSE>', endMarker: '</RESPONSE>' },
45+
expected: ""
46+
},
47+
{
48+
name: "Real-world LLM output",
49+
text: `Here's your analysis:
50+
51+
The code looks good, but I found some issues:
52+
53+
<RESPONSE>
54+
1. Memory leak in line 42
55+
2. Missing error handling in function processData()
56+
3. Deprecated API usage in networking module
57+
58+
Suggested fixes:
59+
- Add try-catch blocks
60+
- Update to latest API version
61+
- Use proper cleanup in destructors
62+
</RESPONSE>
63+
64+
Let me know if you need more details!`,
65+
config: { enabled: true, startMarker: '<RESPONSE>', endMarker: '</RESPONSE>' },
66+
expected: `1. Memory leak in line 42
67+
2. Missing error handling in function processData()
68+
3. Deprecated API usage in networking module
69+
70+
Suggested fixes:
71+
- Add try-catch blocks
72+
- Update to latest API version
73+
- Use proper cleanup in destructors`
74+
}
75+
];
76+
77+
function extractResponse(text, config) {
78+
if (!config.enabled) {
79+
return text;
80+
}
81+
82+
const { startMarker, endMarker } = config;
83+
84+
// Find start marker (case-insensitive search as fallback)
85+
let startIndex = text.indexOf(startMarker);
86+
if (startIndex === -1) {
87+
// Try case-insensitive search
88+
const lowerText = text.toLowerCase();
89+
const lowerStartMarker = startMarker.toLowerCase();
90+
startIndex = lowerText.indexOf(lowerStartMarker);
91+
if (startIndex !== -1) {
92+
console.log('🔍 Found start marker with case-insensitive search');
93+
}
94+
}
95+
96+
if (startIndex === -1) {
97+
console.log('⚠️ Start marker not found, using fallback (full output)');
98+
return text;
99+
}
100+
101+
// Find end marker after start marker
102+
const searchStart = startIndex + startMarker.length;
103+
let endIndex = text.indexOf(endMarker, searchStart);
104+
if (endIndex === -1) {
105+
// Try case-insensitive search for end marker
106+
const lowerText = text.toLowerCase();
107+
const lowerEndMarker = endMarker.toLowerCase();
108+
endIndex = lowerText.indexOf(lowerEndMarker, searchStart);
109+
if (endIndex !== -1) {
110+
console.log('🔍 Found end marker with case-insensitive search');
111+
}
112+
}
113+
114+
if (endIndex === -1) {
115+
console.log('⚠️ End marker not found, using fallback (full output)');
116+
return text;
117+
}
118+
119+
const extracted = text.substring(startIndex + startMarker.length, endIndex).trim();
120+
console.log('✅ Extracted:', {
121+
startIndex,
122+
endIndex,
123+
extractedLength: extracted.length,
124+
preview: extracted.slice(0, 50) + (extracted.length > 50 ? '...' : '')
125+
});
126+
127+
return extracted;
128+
}
129+
130+
console.log('🔬 Testing advanced marker extraction functionality\n');
131+
132+
testCases.forEach((testCase, index) => {
133+
console.log(`Test ${index + 1}: ${testCase.name}`);
134+
console.log(`Markers: "${testCase.config.startMarker}" ... "${testCase.config.endMarker}"`);
135+
136+
const result = extractResponse(testCase.text, testCase.config);
137+
const passed = result === testCase.expected;
138+
139+
console.log(`Expected (${testCase.expected.length} chars): ${JSON.stringify(testCase.expected.slice(0, 100))}`);
140+
console.log(`Got (${result.length} chars): ${JSON.stringify(result.slice(0, 100))}`);
141+
console.log(`${passed ? '✅ PASS' : '❌ FAIL'}\n`);
142+
143+
if (!passed) {
144+
console.log('📋 Full comparison:');
145+
console.log('Expected:', testCase.expected);
146+
console.log('Got:', result);
147+
console.log('---\n');
148+
}
149+
});

test-marker.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env node
2+
3+
// Test marker extraction functionality
4+
const testCases = [
5+
{
6+
name: "Basic marker extraction",
7+
text: "Some prefix text\n<RESPONSE>\nThis is the response\n</RESPONSE>\nSome suffix text",
8+
expected: "This is the response"
9+
},
10+
{
11+
name: "Inline markers",
12+
text: "Here is the answer: <RESPONSE>42</RESPONSE> and that's it.",
13+
expected: "42"
14+
},
15+
{
16+
name: "Markers with extra whitespace",
17+
text: "Before\n <RESPONSE> \n Extracted content \n </RESPONSE> \nAfter",
18+
expected: "Extracted content"
19+
},
20+
{
21+
name: "Multiline content",
22+
text: "Prefix<RESPONSE>\nLine 1\nLine 2\nLine 3\n</RESPONSE>Suffix",
23+
expected: "Line 1\nLine 2\nLine 3"
24+
},
25+
{
26+
name: "No markers",
27+
text: "Just plain text without any markers",
28+
expected: "Just plain text without any markers"
29+
},
30+
{
31+
name: "Only start marker",
32+
text: "Text <RESPONSE> more text but no end",
33+
expected: "Text <RESPONSE> more text but no end"
34+
},
35+
{
36+
name: "Multiple marker pairs (should take first)",
37+
text: "A<RESPONSE>first</RESPONSE>B<RESPONSE>second</RESPONSE>C",
38+
expected: "first"
39+
}
40+
];
41+
42+
function extractResponse(text, config = { enabled: true, startMarker: '<RESPONSE>', endMarker: '</RESPONSE>' }) {
43+
if (!config.enabled) {
44+
return text;
45+
}
46+
47+
const { startMarker, endMarker } = config;
48+
const startIndex = text.indexOf(startMarker);
49+
const endIndex = text.indexOf(endMarker);
50+
51+
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
52+
const extracted = text.substring(startIndex + startMarker.length, endIndex).trim();
53+
console.log('✅ Extracted:', { startIndex, endIndex, extracted: extracted.slice(0, 50) + (extracted.length > 50 ? '...' : '') });
54+
return extracted;
55+
}
56+
57+
console.log('⚠️ Markers not found, using fallback');
58+
return text;
59+
}
60+
61+
console.log('🧪 Testing marker extraction functionality\n');
62+
63+
testCases.forEach((testCase, index) => {
64+
console.log(`Test ${index + 1}: ${testCase.name}`);
65+
console.log(`Input: ${JSON.stringify(testCase.text)}`);
66+
67+
const result = extractResponse(testCase.text);
68+
const passed = result === testCase.expected;
69+
70+
console.log(`Expected: ${JSON.stringify(testCase.expected)}`);
71+
console.log(`Got: ${JSON.stringify(result)}`);
72+
console.log(`${passed ? '✅ PASS' : '❌ FAIL'}\n`);
73+
});

test-shell-marker.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env node
2+
3+
// Test shell command with markers
4+
console.log('🧪 Testing shell command with marker extraction\n');
5+
6+
// Simulate shell command that outputs with markers
7+
const testCommands = [
8+
{
9+
name: "Echo with markers",
10+
command: 'echo "Before text <RESPONSE>This is the extracted answer</RESPONSE> After text"'
11+
},
12+
{
13+
name: "Multi-line with markers",
14+
command: `echo "Analysis results:
15+
<RESPONSE>
16+
- Issue 1: Memory leak
17+
- Issue 2: Missing validation
18+
- Issue 3: Performance bottleneck
19+
</RESPONSE>
20+
End of analysis."`
21+
},
22+
{
23+
name: "No markers (fallback test)",
24+
command: 'echo "This is just plain output without any special markers"'
25+
}
26+
];
27+
28+
testCommands.forEach((test, index) => {
29+
console.log(`\n=== Test ${index + 1}: ${test.name} ===`);
30+
console.log(`Command: ${test.command}`);
31+
console.log('Output:');
32+
33+
// Execute the command
34+
const { execSync } = require('child_process');
35+
try {
36+
const output = execSync(test.command, { encoding: 'utf8' });
37+
console.log(output);
38+
39+
// Apply marker extraction
40+
const extractResponse = (text) => {
41+
const startMarker = '<RESPONSE>';
42+
const endMarker = '</RESPONSE>';
43+
44+
let startIndex = text.indexOf(startMarker);
45+
if (startIndex === -1) {
46+
const lowerText = text.toLowerCase();
47+
startIndex = lowerText.indexOf(startMarker.toLowerCase());
48+
}
49+
50+
if (startIndex === -1) {
51+
console.log('⚠️ No start marker found, using full output');
52+
return text;
53+
}
54+
55+
const searchStart = startIndex + startMarker.length;
56+
let endIndex = text.indexOf(endMarker, searchStart);
57+
if (endIndex === -1) {
58+
const lowerText = text.toLowerCase();
59+
endIndex = lowerText.indexOf(endMarker.toLowerCase(), searchStart);
60+
}
61+
62+
if (endIndex === -1) {
63+
console.log('⚠️ No end marker found, using full output');
64+
return text;
65+
}
66+
67+
const extracted = text.substring(startIndex + startMarker.length, endIndex).trim();
68+
console.log('✅ Extracted content between markers');
69+
return extracted;
70+
};
71+
72+
const extractedOutput = extractResponse(output);
73+
console.log('\n📦 Final extracted output:');
74+
console.log('---');
75+
console.log(extractedOutput);
76+
console.log('---');
77+
78+
} catch (error) {
79+
console.error('❌ Command failed:', error.message);
80+
}
81+
});
82+
83+
console.log('\n🎯 Marker extraction testing complete!');

0 commit comments

Comments
 (0)