mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-04-27 15:07:41 +00:00
Improve error messages
This commit is contained in:
parent
b5f1fa7ded
commit
bda349ea29
3 changed files with 137 additions and 72 deletions
|
|
@ -45,9 +45,40 @@ export async function imageToolHandler(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If success is true, data should be present
|
||||||
|
if (!swiftResponse.data) {
|
||||||
|
logger.error('Swift CLI reported success but no data was returned.');
|
||||||
|
return {
|
||||||
|
content: [{
|
||||||
|
type: 'text',
|
||||||
|
text: 'Image capture failed: Invalid response from capture utility (no data).'
|
||||||
|
}],
|
||||||
|
isError: true,
|
||||||
|
_meta: {
|
||||||
|
backend_error_code: 'INVALID_RESPONSE_NO_DATA'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const data = swiftResponse.data as ImageCaptureData;
|
const data = swiftResponse.data as ImageCaptureData;
|
||||||
const content: any[] = [];
|
const content: any[] = [];
|
||||||
|
|
||||||
|
// Check for errors related to the user-specified path if provided
|
||||||
|
// The original logic relied on savedFile.error, which doesn't exist.
|
||||||
|
// If the CLI fails to save to a user-specified path, it should ideally be reflected
|
||||||
|
// in swiftResponse.success or swiftResponse.messages. For now, this specific check is removed.
|
||||||
|
/*
|
||||||
|
if (input.path && data.saved_files?.length > 0) {
|
||||||
|
for (const savedFile of data.saved_files) {
|
||||||
|
if (savedFile.path.startsWith(input.path) && savedFile.error) { // savedFile.error is the issue
|
||||||
|
userPathSaveError = `Peekaboo Warning: Failed to save to user-specified path '${input.path}'. CLI Error: ${savedFile.error}`;
|
||||||
|
logger.warn({ userPath: input.path, fileError: savedFile.error }, 'Error saving to user-specified path');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Add text summary
|
// Add text summary
|
||||||
const summary = generateImageCaptureSummary(data, input);
|
const summary = generateImageCaptureSummary(data, input);
|
||||||
|
|
@ -55,10 +86,33 @@ export async function imageToolHandler(
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: summary
|
text: summary
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If there was an error saving to the user-specified path, add it to the content
|
||||||
|
// This part is removed as userPathSaveError logic is removed.
|
||||||
|
/*
|
||||||
|
if (userPathSaveError) {
|
||||||
|
content.push({
|
||||||
|
type: 'text',
|
||||||
|
text: userPathSaveError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Add image data if requested
|
// Add image data if requested
|
||||||
if (input.return_data && data.saved_files?.length > 0) {
|
if (input.return_data && data.saved_files?.length > 0) {
|
||||||
for (const savedFile of data.saved_files) {
|
for (const savedFile of data.saved_files) {
|
||||||
|
// If swiftResponse.success is true, assume individual files were processed correctly by CLI
|
||||||
|
// unless specific messages indicate otherwise. The savedFile.error check is removed.
|
||||||
|
/*
|
||||||
|
if (savedFile.error) { // savedFile.error is the issue
|
||||||
|
logger.warn({ path: savedFile.path, error: savedFile.error }, 'Skipping base64 conversion for file with reported error by CLI');
|
||||||
|
content.push({
|
||||||
|
type: 'text',
|
||||||
|
text: `Peekaboo Warning: Could not process file '${savedFile.path}' due to CLI error: ${savedFile.error}`
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
const imageBase64 = await readImageAsBase64(savedFile.path);
|
const imageBase64 = await readImageAsBase64(savedFile.path);
|
||||||
content.push({
|
content.push({
|
||||||
|
|
@ -76,7 +130,7 @@ export async function imageToolHandler(
|
||||||
logger.error({ error, path: savedFile.path }, 'Failed to read image file');
|
logger.error({ error, path: savedFile.path }, 'Failed to read image file');
|
||||||
content.push({
|
content.push({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: `Warning: Could not read image data from ${savedFile.path}`
|
text: `Warning: Could not read image data from ${savedFile.path}. Error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +146,7 @@ export async function imageToolHandler(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content,
|
content,
|
||||||
saved_files: data.saved_files
|
saved_files: data.saved_files // Ensure this matches the expected return type, possibly ImageToolOutput
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -94,12 +94,21 @@ export async function executeSwiftCli(
|
||||||
|
|
||||||
if (exitCode !== 0 || !stdout.trim()) {
|
if (exitCode !== 0 || !stdout.trim()) {
|
||||||
logger.error({ exitCode, stdout, stderr }, 'Swift CLI execution failed');
|
logger.error({ exitCode, stdout, stderr }, 'Swift CLI execution failed');
|
||||||
|
|
||||||
|
// Prioritize stderr for the main message if available
|
||||||
|
const errorMessage = stderr.trim()
|
||||||
|
? `Peekaboo CLI Error: ${stderr.trim()}`
|
||||||
|
: `Swift CLI execution failed (exit code: ${exitCode})`;
|
||||||
|
const errorDetails = stderr.trim() && stdout.trim()
|
||||||
|
? `Stdout: ${stdout.trim()}`
|
||||||
|
: stderr.trim() ? '' : (stdout.trim() || 'No output received');
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
message: `Swift CLI execution failed (exit code: ${exitCode})`,
|
message: errorMessage,
|
||||||
code: 'SWIFT_CLI_EXECUTION_ERROR',
|
code: 'SWIFT_CLI_EXECUTION_ERROR',
|
||||||
details: stderr || stdout || 'No output received'
|
details: errorDetails
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -176,10 +176,78 @@ describe('Image Tool', () => {
|
||||||
expect(result.isError).toBeUndefined();
|
expect(result.isError).toBeUndefined();
|
||||||
expect(result.content).toEqual(expect.arrayContaining([
|
expect(result.content).toEqual(expect.arrayContaining([
|
||||||
expect.objectContaining({ type: 'text', text: expect.stringContaining('Captured 1 image') }),
|
expect.objectContaining({ type: 'text', text: expect.stringContaining('Captured 1 image') }),
|
||||||
expect.objectContaining({ type: 'text', text: 'Warning: Could not read image data from /tmp/fail.png' })
|
expect.objectContaining({ type: 'text', text: 'Warning: Could not read image data from /tmp/fail.png. Error: Read failed' })
|
||||||
]));
|
]));
|
||||||
expect(result.saved_files).toEqual([mockSavedFile]);
|
expect(result.saved_files).toEqual([mockSavedFile]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle empty saved_files array', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
success: true,
|
||||||
|
data: { saved_files: [] }
|
||||||
|
};
|
||||||
|
mockExecuteSwiftCli.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await imageToolHandler({
|
||||||
|
format: 'png',
|
||||||
|
return_data: false,
|
||||||
|
capture_focus: 'background'
|
||||||
|
}, mockContext);
|
||||||
|
|
||||||
|
expect(result.content[0].text).toBe('Image capture completed but no files were saved.');
|
||||||
|
expect(result.saved_files).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle malformed Swift CLI response', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
success: true,
|
||||||
|
data: null // Invalid data, triggers the new check in imageToolHandler
|
||||||
|
};
|
||||||
|
mockExecuteSwiftCli.mockResolvedValue(mockResponse as any);
|
||||||
|
|
||||||
|
const result = await imageToolHandler({
|
||||||
|
format: 'png',
|
||||||
|
return_data: false,
|
||||||
|
capture_focus: 'background'
|
||||||
|
}, mockContext);
|
||||||
|
|
||||||
|
expect(result.isError).toBe(true);
|
||||||
|
expect(result.content[0].text).toContain('Image capture failed: Invalid response from capture utility (no data).');
|
||||||
|
expect(result._meta?.backend_error_code).toBe('INVALID_RESPONSE_NO_DATA');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle partial failures when reading multiple images', async () => {
|
||||||
|
const mockFiles: SavedFile[] = [
|
||||||
|
{ path: '/tmp/img1.png', mime_type: 'image/png', item_label: 'Image 1' },
|
||||||
|
{ path: '/tmp/img2.jpg', mime_type: 'image/jpeg', item_label: 'Image 2' }
|
||||||
|
];
|
||||||
|
const mockResponse = {
|
||||||
|
success: true,
|
||||||
|
data: { saved_files: mockFiles }
|
||||||
|
};
|
||||||
|
mockExecuteSwiftCli.mockResolvedValue(mockResponse);
|
||||||
|
mockReadImageAsBase64
|
||||||
|
.mockResolvedValueOnce('base64data1')
|
||||||
|
.mockRejectedValueOnce(new Error('Read failed'));
|
||||||
|
|
||||||
|
const result = await imageToolHandler({
|
||||||
|
format: 'png',
|
||||||
|
return_data: true,
|
||||||
|
capture_focus: 'background'
|
||||||
|
}, mockContext);
|
||||||
|
|
||||||
|
expect(result.content).toEqual(expect.arrayContaining([
|
||||||
|
expect.objectContaining({ type: 'text', text: expect.stringContaining('Captured 2 images')}), // Summary text
|
||||||
|
expect.objectContaining({
|
||||||
|
type: 'image',
|
||||||
|
data: 'base64data1'
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
type: 'text',
|
||||||
|
text: 'Warning: Could not read image data from /tmp/img2.jpg. Error: Read failed'
|
||||||
|
})
|
||||||
|
]));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('buildSwiftCliArgs', () => {
|
describe('buildSwiftCliArgs', () => {
|
||||||
|
|
@ -390,40 +458,6 @@ describe('Image Tool', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle empty saved_files array', async () => {
|
|
||||||
const mockResponse = {
|
|
||||||
success: true,
|
|
||||||
data: { saved_files: [] }
|
|
||||||
};
|
|
||||||
mockExecuteSwiftCli.mockResolvedValue(mockResponse);
|
|
||||||
|
|
||||||
const result = await imageToolHandler({
|
|
||||||
format: 'png',
|
|
||||||
return_data: false,
|
|
||||||
capture_focus: 'background'
|
|
||||||
}, mockContext);
|
|
||||||
|
|
||||||
expect(result.content[0].text).toContain('Captured 0 images');
|
|
||||||
expect(result.saved_files).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle malformed Swift CLI response', async () => {
|
|
||||||
const mockResponse = {
|
|
||||||
success: true,
|
|
||||||
data: null // Invalid data
|
|
||||||
};
|
|
||||||
mockExecuteSwiftCli.mockResolvedValue(mockResponse as any);
|
|
||||||
|
|
||||||
const result = await imageToolHandler({
|
|
||||||
format: 'png',
|
|
||||||
return_data: false,
|
|
||||||
capture_focus: 'background'
|
|
||||||
}, mockContext);
|
|
||||||
|
|
||||||
expect(result.isError).toBe(true);
|
|
||||||
expect(result.content[0].text).toContain('Invalid response from image capture');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle multiple saved files', async () => {
|
it('should handle multiple saved files', async () => {
|
||||||
const mockFiles: SavedFile[] = [
|
const mockFiles: SavedFile[] = [
|
||||||
{ path: '/tmp/screen1.png', mime_type: 'image/png', item_label: 'Screen 1' },
|
{ path: '/tmp/screen1.png', mime_type: 'image/png', item_label: 'Screen 1' },
|
||||||
|
|
@ -484,38 +518,6 @@ describe('Image Tool', () => {
|
||||||
expect(mockReadImageAsBase64).toHaveBeenCalledTimes(2);
|
expect(mockReadImageAsBase64).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle partial failures when reading multiple images', async () => {
|
|
||||||
const mockFiles: SavedFile[] = [
|
|
||||||
{ path: '/tmp/img1.png', mime_type: 'image/png', item_label: 'Image 1' },
|
|
||||||
{ path: '/tmp/img2.jpg', mime_type: 'image/jpeg', item_label: 'Image 2' }
|
|
||||||
];
|
|
||||||
const mockResponse = {
|
|
||||||
success: true,
|
|
||||||
data: { saved_files: mockFiles }
|
|
||||||
};
|
|
||||||
mockExecuteSwiftCli.mockResolvedValue(mockResponse);
|
|
||||||
mockReadImageAsBase64
|
|
||||||
.mockResolvedValueOnce('base64data1')
|
|
||||||
.mockRejectedValueOnce(new Error('Read failed'));
|
|
||||||
|
|
||||||
const result = await imageToolHandler({
|
|
||||||
format: 'png',
|
|
||||||
return_data: true,
|
|
||||||
capture_focus: 'background'
|
|
||||||
}, mockContext);
|
|
||||||
|
|
||||||
expect(result.content).toEqual(expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
type: 'image',
|
|
||||||
data: 'base64data1'
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
type: 'text',
|
|
||||||
text: 'Warning: Could not read image data from /tmp/img2.jpg'
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle Swift CLI timeout errors', async () => {
|
it('should handle Swift CLI timeout errors', async () => {
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
@ -534,7 +536,7 @@ describe('Image Tool', () => {
|
||||||
|
|
||||||
expect(result.isError).toBe(true);
|
expect(result.isError).toBe(true);
|
||||||
expect(result.content[0].text).toContain('Command timed out');
|
expect(result.content[0].text).toContain('Command timed out');
|
||||||
expect(result._meta.backend_error_code).toBe('TIMEOUT');
|
expect(result._meta?.backend_error_code).toBe('TIMEOUT');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle window_specifier with both title and index', async () => {
|
it('should handle window_specifier with both title and index', async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue