Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions app/lib/runtime/message-parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,58 @@ describe('StreamingMessageParser', () => {
runTest(input, expected);
});
});

describe('file action with missing filePath', () => {
it('should not crash when filePath attribute is missing from file action', () => {
const callbacks = {
onArtifactOpen: vi.fn(),
onArtifactClose: vi.fn(),
onActionOpen: vi.fn(),
onActionClose: vi.fn(),
};

const parser = new StreamingMessageParser({
artifactElement: () => '',
callbacks,
});

// This input has a file action without filePath - previously caused
// "TypeError: Cannot read properties of undefined (reading 'endsWith')"
const input =
'Before <boltArtifact title="Some title" id="artifact_1"><boltAction type="file">some file content</boltAction></boltArtifact> After';

expect(() => parser.parse('message_1', input)).not.toThrow();

expect(callbacks.onActionOpen).toHaveBeenCalledTimes(1);
expect(callbacks.onActionClose).toHaveBeenCalledTimes(1);
});

it('should not crash during streaming when filePath attribute is missing', () => {
const callbacks = {
onArtifactOpen: vi.fn(),
onArtifactClose: vi.fn(),
onActionOpen: vi.fn(),
onActionStream: vi.fn(),
onActionClose: vi.fn(),
};

const parser = new StreamingMessageParser({
artifactElement: () => '',
callbacks,
});

// Simulate streaming: first chunk has the action open but no close tag yet
const chunk1 =
'Before <boltArtifact title="Some title" id="artifact_1"><boltAction type="file">partial content';

expect(() => parser.parse('message_1', chunk1)).not.toThrow();

// Second chunk completes the action
const chunk2 = chunk1 + ' more content</boltAction></boltArtifact> After';

expect(() => parser.parse('message_1', chunk2)).not.toThrow();
});
});
});

describe('EnhancedStreamingMessageParser', () => {
Expand Down
6 changes: 3 additions & 3 deletions app/lib/runtime/message-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export class StreamingMessageParser {

if ('type' in currentAction && currentAction.type === 'file') {
// Remove markdown code block syntax if present and file is not markdown
if (!currentAction.filePath.endsWith('.md')) {
if (!currentAction.filePath?.endsWith('.md')) {
content = cleanoutMarkdownSyntax(content);
content = cleanEscapedTags(content);
}
Expand Down Expand Up @@ -182,7 +182,7 @@ export class StreamingMessageParser {
if ('type' in currentAction && currentAction.type === 'file') {
let content = input.slice(i);

if (!currentAction.filePath.endsWith('.md')) {
if (!currentAction.filePath?.endsWith('.md')) {
content = cleanoutMarkdownSyntax(content);
content = cleanEscapedTags(content);
}
Expand Down Expand Up @@ -366,7 +366,7 @@ export class StreamingMessageParser {
(actionAttributes as SupabaseAction).filePath = filePath;
}
} else if (actionType === 'file') {
const filePath = this.#extractAttribute(actionTag, 'filePath') as string;
const filePath = this.#extractAttribute(actionTag, 'filePath') ?? '';

if (!filePath) {
logger.debug('File path not specified');
Expand Down