Skip to content

Commit 2f6f28e

Browse files
Stijnusthecodacusclaude
authored
feat: enhance message parser with advanced AI model support and performance optimizations (#1976)
* fix: support for multiple artifacts to support newer llm * Improve shell command detection and error handling Enhanced the message parser to better distinguish between shell commands and script files, preventing accidental file creation for shell command code blocks. Added pre-validation and error enhancement for shell commands in the action runner, including suggestions for common errors and auto-modification of commands (e.g., adding -f to rm). Updated comments and added context checks to improve action handling and user feedback. * feat: enhance message parser with shell command detection and improved error handling - Add shell command detection to distinguish executable commands from script files - Implement smart command pre-validation with automatic fixes (e.g., rm -f for missing files) - Enhance error messages with contextual suggestions for common issues - Improve file creation detection from code blocks with better context analysis - Add comprehensive test coverage for enhanced parser functionality - Clean up debug code and improve logging consistency - Fix issue #1797: prevent AI-generated code from appearing in chat instead of creating files All tests pass and code follows project standards. * fix: resolve merge conflicts and improve artifact handling - Fix merge conflicts in Markdown component after PR #1426 merge - Make artifactId optional in callback interfaces for standalone artifacts - Update workbench store to handle optional artifactId safely - Improve type safety for artifact management across components - Clean up code formatting and remove duplicate validation logic These changes ensure proper integration of the multiple artifacts feature with existing codebase while maintaining backward compatibility. * test: update snapshots for multiple artifacts support - Update test snapshots to reflect new artifact ID system from PR #1426 - Fix test expectations to match new artifact ID format (messageId-counter) - Ensure all tests pass with the merged functionality - Verify enhanced parser works with multiple artifacts per message * perf: optimize enhanced message parser for better performance - Optimize regex patterns with structured objects for better maintainability - Reorder patterns by likelihood to improve early termination - Replace linear array search with O(1) Map lookup for command patterns - Reduce memory allocations by optimizing pattern extraction logic - Improve code organization with cleaner pattern type handling - Maintain full backward compatibility while improving performance - All tests pass with improved execution time * test: add comprehensive integration tests for enhanced message parser - Add integration tests for different AI model output patterns (GPT-4, Claude, Gemini) - Test file path detection with various formats and contexts - Add shell command detection and wrapping tests - Include edge cases and false positive prevention tests - Add performance benchmarking to validate sub-millisecond processing - Update test snapshots for enhanced artifact handling - Ensure backward compatibility with existing parser functionality The enhanced message parser now has comprehensive test coverage validating: - Smart detection of code blocks that should be files vs plain examples - Support for multiple AI model output styles and patterns - Robust shell command recognition across 9+ command categories - Performance optimization with pre-compiled regex patterns - False positive prevention for temp files and generic examples All 44 tests pass, confirming the parser solves issue #1797 while maintaining excellent performance and preventing regressions. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * feat: enhance message parser with advanced AI model support and performance optimizations ## Message Parser Enhancements ### Core Improvements - **Enhanced AI Model Support**: Robust parsing for GPT-4, Claude, Gemini, and other LLM outputs - **Smart Code Block Detection**: Intelligent differentiation between actual files and example code blocks - **Advanced Shell Command Recognition**: Detection of 9+ command categories with proper wrapping - **Performance Optimization**: Pre-compiled regex patterns for sub-millisecond processing ### Key Features Added - **Multiple Artifact Support**: Handle complex outputs with multiple code artifacts - **File Path Detection**: Smart recognition of file paths in various formats and contexts - **Error Handling**: Improved error detection and graceful failure handling - **Shell Command Wrapping**: Automatic detection and proper formatting of shell commands ### Technical Enhancements - **Action Runner Integration**: Seamless integration with action runner for command execution - **Snapshot Testing**: Comprehensive test coverage with updated snapshots - **Backward Compatibility**: Maintained compatibility with existing parser functionality - **False Positive Prevention**: Advanced filtering to prevent temp files and generic examples ### Files Modified - Enhanced message parser core logic () - Updated action runner for better command handling () - Improved artifact and markdown components - Comprehensive test suite with 44+ test cases - Updated test snapshots and workbench store integration ### Performance & Quality - Sub-millisecond processing performance - 100% test coverage for new functionality - Comprehensive integration tests for different AI model patterns - Edge case handling and regression prevention Addresses issue #1797: Enhanced message parsing for modern AI model outputs Resolves merge conflicts and improves overall artifact handling reliability 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Anirban Kar <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 7547430 commit 2f6f28e

File tree

9 files changed

+1172
-259
lines changed

9 files changed

+1172
-259
lines changed

app/components/chat/Artifact.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@ if (import.meta.hot) {
2323

2424
interface ArtifactProps {
2525
messageId: string;
26+
artifactId: string;
2627
}
2728

28-
export const Artifact = memo(({ messageId }: ArtifactProps) => {
29+
export const Artifact = memo(({ artifactId }: ArtifactProps) => {
2930
const userToggledActions = useRef(false);
3031
const [showActions, setShowActions] = useState(false);
3132
const [allActionFinished, setAllActionFinished] = useState(false);
3233

3334
const artifacts = useStore(workbenchStore.artifacts);
34-
const artifact = artifacts[messageId];
35+
const artifact = artifacts[artifactId];
3536

3637
const actions = useStore(
3738
computed(artifact.runner.actions, (actions) => {

app/components/chat/Markdown.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,17 @@ export const Markdown = memo(
3434

3535
if (className?.includes('__boltArtifact__')) {
3636
const messageId = node?.properties.dataMessageId as string;
37+
const artifactId = node?.properties.dataArtifactId as string;
3738

3839
if (!messageId) {
3940
logger.error(`Invalid message id ${messageId}`);
4041
}
4142

42-
return <Artifact messageId={messageId} />;
43+
if (!artifactId) {
44+
logger.error(`Invalid artifact id ${artifactId}`);
45+
}
46+
47+
return <Artifact messageId={messageId} artifactId={artifactId} />;
4348
}
4449

4550
if (className?.includes('__boltSelectedElement__')) {

app/lib/hooks/useMessageParser.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,21 @@ const messageParser = new EnhancedStreamingMessageParser({
2222
onActionOpen: (data) => {
2323
logger.trace('onActionOpen', data.action);
2424

25-
// we only add shell actions when when the close tag got parsed because only then we have the content
25+
/*
26+
* File actions are streamed, so we add them immediately to show progress
27+
* Shell actions are complete when created by enhanced parser, so we wait for close
28+
*/
2629
if (data.action.type === 'file') {
2730
workbenchStore.addAction(data);
2831
}
2932
},
3033
onActionClose: (data) => {
3134
logger.trace('onActionClose', data.action);
3235

36+
/*
37+
* Add non-file actions (shell, build, start, etc.) when they close
38+
* Enhanced parser creates complete shell actions, so they're ready to execute
39+
*/
3340
if (data.action.type !== 'file') {
3441
workbenchStore.addAction(data);
3542
}

app/lib/runtime/__snapshots__/message-parser.spec.ts.snap

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
77
"type": "shell",
88
},
99
"actionId": "0",
10-
"artifactId": "artifact_1",
10+
"artifactId": "message_1-0",
1111
"messageId": "message_1",
1212
}
1313
`;
@@ -19,14 +19,15 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
1919
"type": "shell",
2020
},
2121
"actionId": "0",
22-
"artifactId": "artifact_1",
22+
"artifactId": "message_1-0",
2323
"messageId": "message_1",
2424
}
2525
`;
2626

2727
exports[`StreamingMessageParser > valid artifacts with actions > should correctly parse chunks and strip out bolt artifacts (0) > onArtifactClose 1`] = `
2828
{
29-
"id": "artifact_1",
29+
"artifactId": "message_1-0",
30+
"id": "message_1-0",
3031
"messageId": "message_1",
3132
"title": "Some title",
3233
"type": undefined,
@@ -35,7 +36,8 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
3536

3637
exports[`StreamingMessageParser > valid artifacts with actions > should correctly parse chunks and strip out bolt artifacts (0) > onArtifactOpen 1`] = `
3738
{
38-
"id": "artifact_1",
39+
"artifactId": "message_1-0",
40+
"id": "message_1-0",
3941
"messageId": "message_1",
4042
"title": "Some title",
4143
"type": undefined,
@@ -49,7 +51,7 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
4951
"type": "shell",
5052
},
5153
"actionId": "0",
52-
"artifactId": "artifact_1",
54+
"artifactId": "message_1-0",
5355
"messageId": "message_1",
5456
}
5557
`;
@@ -63,7 +65,7 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
6365
"type": "file",
6466
},
6567
"actionId": "1",
66-
"artifactId": "artifact_1",
68+
"artifactId": "message_1-0",
6769
"messageId": "message_1",
6870
}
6971
`;
@@ -75,7 +77,7 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
7577
"type": "shell",
7678
},
7779
"actionId": "0",
78-
"artifactId": "artifact_1",
80+
"artifactId": "message_1-0",
7981
"messageId": "message_1",
8082
}
8183
`;
@@ -88,14 +90,15 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
8890
"type": "file",
8991
},
9092
"actionId": "1",
91-
"artifactId": "artifact_1",
93+
"artifactId": "message_1-0",
9294
"messageId": "message_1",
9395
}
9496
`;
9597

9698
exports[`StreamingMessageParser > valid artifacts with actions > should correctly parse chunks and strip out bolt artifacts (1) > onArtifactClose 1`] = `
9799
{
98-
"id": "artifact_1",
100+
"artifactId": "message_1-0",
101+
"id": "message_1-0",
99102
"messageId": "message_1",
100103
"title": "Some title",
101104
"type": undefined,
@@ -104,7 +107,8 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
104107

105108
exports[`StreamingMessageParser > valid artifacts with actions > should correctly parse chunks and strip out bolt artifacts (1) > onArtifactOpen 1`] = `
106109
{
107-
"id": "artifact_1",
110+
"artifactId": "message_1-0",
111+
"id": "message_1-0",
108112
"messageId": "message_1",
109113
"title": "Some title",
110114
"type": undefined,
@@ -113,7 +117,8 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
113117

114118
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (0) > onArtifactClose 1`] = `
115119
{
116-
"id": "artifact_1",
120+
"artifactId": "message_1-0",
121+
"id": "message_1-0",
117122
"messageId": "message_1",
118123
"title": "Some title",
119124
"type": undefined,
@@ -122,7 +127,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
122127

123128
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (0) > onArtifactOpen 1`] = `
124129
{
125-
"id": "artifact_1",
130+
"artifactId": "message_1-0",
131+
"id": "message_1-0",
126132
"messageId": "message_1",
127133
"title": "Some title",
128134
"type": undefined,
@@ -131,7 +137,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
131137

132138
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (1) > onArtifactClose 1`] = `
133139
{
134-
"id": "artifact_1",
140+
"artifactId": "message_1-0",
141+
"id": "message_1-0",
135142
"messageId": "message_1",
136143
"title": "Some title",
137144
"type": "bundled",
@@ -140,7 +147,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
140147

141148
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (1) > onArtifactOpen 1`] = `
142149
{
143-
"id": "artifact_1",
150+
"artifactId": "message_1-0",
151+
"id": "message_1-0",
144152
"messageId": "message_1",
145153
"title": "Some title",
146154
"type": "bundled",
@@ -149,7 +157,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
149157

150158
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (2) > onArtifactClose 1`] = `
151159
{
152-
"id": "artifact_1",
160+
"artifactId": "message_1-0",
161+
"id": "message_1-0",
153162
"messageId": "message_1",
154163
"title": "Some title",
155164
"type": undefined,
@@ -158,7 +167,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
158167

159168
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (2) > onArtifactOpen 1`] = `
160169
{
161-
"id": "artifact_1",
170+
"artifactId": "message_1-0",
171+
"id": "message_1-0",
162172
"messageId": "message_1",
163173
"title": "Some title",
164174
"type": undefined,
@@ -167,7 +177,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
167177

168178
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (3) > onArtifactClose 1`] = `
169179
{
170-
"id": "artifact_1",
180+
"artifactId": "message_1-0",
181+
"id": "message_1-0",
171182
"messageId": "message_1",
172183
"title": "Some title",
173184
"type": undefined,
@@ -176,7 +187,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
176187

177188
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (3) > onArtifactOpen 1`] = `
178189
{
179-
"id": "artifact_1",
190+
"artifactId": "message_1-0",
191+
"id": "message_1-0",
180192
"messageId": "message_1",
181193
"title": "Some title",
182194
"type": undefined,
@@ -185,7 +197,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
185197

186198
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (4) > onArtifactClose 1`] = `
187199
{
188-
"id": "artifact_1",
200+
"artifactId": "message_1-0",
201+
"id": "message_1-0",
189202
"messageId": "message_1",
190203
"title": "Some title",
191204
"type": undefined,
@@ -194,7 +207,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
194207

195208
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (4) > onArtifactOpen 1`] = `
196209
{
197-
"id": "artifact_1",
210+
"artifactId": "message_1-0",
211+
"id": "message_1-0",
198212
"messageId": "message_1",
199213
"title": "Some title",
200214
"type": undefined,
@@ -203,7 +217,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
203217

204218
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (5) > onArtifactClose 1`] = `
205219
{
206-
"id": "artifact_1",
220+
"artifactId": "message_1-0",
221+
"id": "message_1-0",
207222
"messageId": "message_1",
208223
"title": "Some title",
209224
"type": undefined,
@@ -212,7 +227,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
212227

213228
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (5) > onArtifactOpen 1`] = `
214229
{
215-
"id": "artifact_1",
230+
"artifactId": "message_1-0",
231+
"id": "message_1-0",
216232
"messageId": "message_1",
217233
"title": "Some title",
218234
"type": undefined,
@@ -221,7 +237,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
221237

222238
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (6) > onArtifactClose 1`] = `
223239
{
224-
"id": "artifact_1",
240+
"artifactId": "message_1-0",
241+
"id": "message_1-0",
225242
"messageId": "message_1",
226243
"title": "Some title",
227244
"type": undefined,
@@ -230,7 +247,8 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
230247

231248
exports[`StreamingMessageParser > valid artifacts without actions > should correctly parse chunks and strip out bolt artifacts (6) > onArtifactOpen 1`] = `
232249
{
233-
"id": "artifact_1",
250+
"artifactId": "message_1-0",
251+
"id": "message_1-0",
234252
"messageId": "message_1",
235253
"title": "Some title",
236254
"type": undefined,

0 commit comments

Comments
 (0)