create_image_edit
Edit and generate images using text prompts with OpenAI's GPT Image 1 model. Define inputs like image source, prompt, and settings such as size, quality, and background for precise image customization.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| background | No | ||
| image | Yes | ||
| mask | No | ||
| n | No | ||
| prompt | Yes | ||
| quality | No | ||
| size | No | ||
| user | No |
Implementation Reference
- src/index.ts:400-802 (handler)The main execution logic for the 'create_image_edit' tool. Converts base64 or file path inputs to temp files, invokes OpenAI's image edit API via curl, processes the base64 response images, saves them to disk, and returns a rich formatted response with images and metadata.async (args: CreateImageEditArgs, extra: any) => { try { // The OpenAI SDK expects 'image' and 'mask' to be Node.js ReadStream or Blob. // Since we are receiving base64 strings from the client, we need to convert them. // This is a simplified approach. A robust solution might involve handling file uploads // or different data formats depending on the client's capabilities. // For this implementation, we'll assume base64 and convert to Buffer, which the SDK might accept // or require further processing depending on its exact requirements for file-like objects. // NOTE: The OpenAI SDK's `images.edit` method specifically expects `File` or `Blob` in browser // environments and `ReadableStream` or `Buffer` in Node.js. Converting base64 to Buffer is // the most straightforward approach for a Node.js server receiving base64. // Process image input which can be file paths or base64 strings const imageFiles = []; // Handle different image input formats if (Array.isArray(args.image)) { // Handle array of strings or objects for (const img of args.image) { if (typeof img === 'string') { // Base64 string - create a temporary file const tempFile = path.join(os.tmpdir(), `image-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`); const base64Data = img.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync(tempFile, Buffer.from(base64Data, 'base64')); imageFiles.push(tempFile); } else { // Object with filePath - use the file directly imageFiles.push(img.filePath); } } } else if (typeof args.image === 'string') { // Single base64 string - create a temporary file const tempFile = path.join(os.tmpdir(), `image-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`); const base64Data = args.image.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync(tempFile, Buffer.from(base64Data, 'base64')); imageFiles.push(tempFile); } else { // Single object with filePath - use the file directly imageFiles.push(args.image.filePath); } // Process mask input which can be a file path or base64 string let maskFile = undefined; if (args.mask) { if (typeof args.mask === 'string') { // Mask is a base64 string - create a temporary file const tempFile = path.join(os.tmpdir(), `mask-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`); const base64Data = args.mask.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync(tempFile, Buffer.from(base64Data, 'base64')); maskFile = tempFile; } else { // Mask is an object with filePath - use the file directly maskFile = args.mask.filePath; } } // Use a direct curl command to call the OpenAI API // This is more reliable than using the SDK for file uploads // Create a temporary file to store the response const tempResponseFile = path.join(os.tmpdir(), `response-${Date.now()}.json`); // Build the curl command let curlCommand = `curl -s -X POST "https://api.openai.com/v1/images/edits" -H "Authorization: Bearer ${process.env.OPENAI_API_KEY}"`; // Add the model curlCommand += ` -F "model=gpt-image-1"`; // Add the prompt curlCommand += ` -F "prompt=${args.prompt}"`; // Add the images for (const imageFile of imageFiles) { curlCommand += ` -F "image[]=@${imageFile}"`; } // Add the mask if it exists if (maskFile) { curlCommand += ` -F "mask=@${maskFile}"`; } // Add other parameters if (args.n) curlCommand += ` -F "n=${args.n}"`; if (args.size) curlCommand += ` -F "size=${args.size}"`; if (args.quality) curlCommand += ` -F "quality=${args.quality}"`; if (args.background) curlCommand += ` -F "background=${args.background}"`; if (args.user) curlCommand += ` -F "user=${args.user}"`; // Add output redirection curlCommand += ` > "${tempResponseFile}"`; // Execute the curl command // Use execSync to run the curl command try { console.error(`Executing curl command to edit image...`); execSync(curlCommand, { stdio: ['pipe', 'pipe', 'inherit'] }); console.error(`Curl command executed successfully.`); } catch (error: any) { console.error(`Error executing curl command: ${error.message}`); throw new Error(`Failed to edit image: ${error.message}`); } // Read the response from the temporary file let responseJson; try { responseJson = fs.readFileSync(tempResponseFile, 'utf8'); console.error(`Response file read successfully.`); } catch (error: any) { console.error(`Error reading response file: ${error.message}`); throw new Error(`Failed to read response file: ${error.message}`); } // Parse the response let responseData; try { responseData = JSON.parse(responseJson); console.error(`Response parsed successfully.`); // Check if the response contains an error if (responseData.error) { console.error(`OpenAI API returned an error:`, responseData.error); const errorMessage = responseData.error.message || 'Unknown API error'; const errorType = responseData.error.type || 'api_error'; const errorCode = responseData.error.code || responseData.error.status || 'unknown'; throw { message: errorMessage, type: errorType, code: errorCode, response: { data: responseData } }; } } catch (error: any) { // If the error is from our API error check, rethrow it if (error.response && error.response.data) { throw error; } console.error(`Error parsing response: ${error.message}`); throw new Error(`Failed to parse response: ${error.message}`); } // Delete the temporary response file try { fs.unlinkSync(tempResponseFile); console.error(`Temporary response file deleted.`); } catch (error: any) { console.error(`Error deleting temporary file: ${error.message}`); // Don't throw an error here, just log it } // Clean up temporary files try { // Delete temporary image files for (const imageFile of imageFiles) { // Only delete files we created (temporary files in the os.tmpdir directory) if (imageFile.startsWith(os.tmpdir())) { try { fs.unlinkSync(imageFile); } catch (e) { /* ignore errors */ } } } // Delete temporary mask file if (maskFile && maskFile.startsWith(os.tmpdir())) { try { fs.unlinkSync(maskFile); } catch (e) { /* ignore errors */ } } } catch (cleanupError) { console.error("Error cleaning up temporary files:", cleanupError); } // No need for a Response-like object anymore since we're using fetch directly // Save images to disk and create response with file paths const savedImages = []; const imageContents = []; const format = "png"; // Assuming png for edits based on common practice if (responseData.data && responseData.data.length > 0) { for (const item of responseData.data) { if (item.b64_json) { // Save the image to disk const imagePath = saveImageToDisk(item.b64_json, format); // Add the saved image info to our response savedImages.push({ path: imagePath, format: format }); // Also include the image content for compatibility imageContents.push({ type: "image" as const, data: item.b64_json, mimeType: `image/${format}` }); } else if (item.url) { console.error(`Image URL: ${item.url}`); console.error("The gpt-image-1 model returned a URL instead of base64 data."); console.error("To view the image, open the URL in your browser."); // Add the URL info to our response savedImages.push({ url: item.url, format: format }); // Include a text message about the URL in the content imageContents.push({ type: "text" as const, text: `Image available at URL: ${item.url}` }); } } } // Create a beautifully formatted response with emojis and details const formatSize = (size: string | undefined) => size || "1024x1024"; const formatQuality = (quality: string | undefined) => quality || "high"; // Get source image information let sourceImageInfo = ""; if (Array.isArray(args.image)) { // Handle array of strings or objects sourceImageInfo = args.image.map((img, index) => { if (typeof img === 'string') { return ` ${index + 1}. Base64 encoded image`; } else { return ` ${index + 1}. ${img.filePath}`; } }).join('\n'); } else if (typeof args.image === 'string') { sourceImageInfo = " Base64 encoded image"; } else { sourceImageInfo = ` ${args.image.filePath}`; } // Get mask information let maskInfo = ""; if (args.mask) { if (typeof args.mask === 'string') { maskInfo = "π **Mask**: Base64 encoded mask applied"; } else { maskInfo = `π **Mask**: Mask from ${args.mask.filePath} applied`; } } // Create a beautiful formatted message const formattedMessage = ` βοΈ **Image Edit Complete!** ποΈ β¨ **Edit Prompt**: "${args.prompt}" πΌοΈ **Source Image${imageFiles.length > 1 ? 's' : ''}**: ${sourceImageInfo} ${maskInfo} π **Edit Parameters**: β’ Size: ${formatSize(args.size)} β’ Quality: ${formatQuality(args.quality)} β’ Number of Results: ${args.n || 1} ${args.background ? `β’ Background: ${args.background}` : ''} π **Edited ${savedImages.length} Image${savedImages.length > 1 ? 's' : ''}**: ${savedImages.map((img, index) => ` ${index + 1}. ${img.path || img.url}`).join('\n')} ${responseData.usage ? `β‘ **Token Usage**: β’ Total Tokens: ${responseData.usage.total_tokens} β’ Input Tokens: ${responseData.usage.input_tokens} β’ Output Tokens: ${responseData.usage.output_tokens}` : ''} π You can find your edited image${savedImages.length > 1 ? 's' : ''} at the path${savedImages.length > 1 ? 's' : ''} above! `; // Return both the image content and the saved file paths with the beautiful message return { content: [ { type: "text" as const, text: formattedMessage }, ...imageContents ], ...(responseData.usage && { _meta: { usage: { totalTokens: responseData.usage.total_tokens, inputTokens: responseData.usage.input_tokens, outputTokens: responseData.usage.output_tokens, }, savedImages: savedImages } }) }; } catch (error: any) { // Log the full error for debugging console.error("Error creating image edit:", error); // Extract detailed error information const errorCode = error.status || error.code || 'Unknown'; const errorType = error.type || 'Error'; const errorMessage = error.message || 'An unknown error occurred'; // Check for specific error types and provide more helpful messages let detailedError = ''; let suggestedFix = ''; // Handle file-related errors if (errorMessage.includes('ENOENT') || errorMessage.includes('no such file')) { detailedError = '\nπ **Details**: The specified image or mask file could not be found'; suggestedFix = '\nπ‘ **Suggestion**: Verify that the file path is correct and the file exists'; } // Handle permission errors else if (errorMessage.includes('EACCES') || errorMessage.includes('permission denied')) { detailedError = '\nπ **Details**: Permission denied when trying to access the file'; suggestedFix = '\nπ‘ **Suggestion**: Check file permissions or try running with elevated privileges'; } // Handle curl errors else if (errorMessage.includes('curl')) { detailedError = '\nπ **Details**: Error occurred while sending the request to OpenAI API'; suggestedFix = '\nπ‘ **Suggestion**: Check your internet connection and API key'; } // Handle OpenAI API errors else if (error.response) { try { const responseData = error.response.data || {}; if (responseData.error) { detailedError = `\nπ **Details**: ${responseData.error.message || 'No additional details available'}`; // Add parameter errors if available if (responseData.error.param) { detailedError += `\nπ **Parameter**: ${responseData.error.param}`; } // Add code if available if (responseData.error.code) { detailedError += `\nπ’ **Error Code**: ${responseData.error.code}`; } // Add type if available if (responseData.error.type) { detailedError += `\nπ **Error Type**: ${responseData.error.type}`; } // Provide suggestions based on error type if (responseData.error.type === 'invalid_request_error') { suggestedFix = '\nπ‘ **Suggestion**: Check that your image format is supported (PNG, JPEG) and the prompt is valid'; } else if (responseData.error.type === 'authentication_error') { suggestedFix = '\nπ‘ **Suggestion**: Verify your OpenAI API key is correct and has access to the gpt-image-1 model'; } } } catch (parseError) { detailedError = '\nπ **Details**: Could not parse error details from API response'; } } // If we have a JSON response with an error, try to extract it if (errorMessage.includes('{') && errorMessage.includes('}')) { try { const jsonStartIndex = errorMessage.indexOf('{'); const jsonEndIndex = errorMessage.lastIndexOf('}') + 1; const jsonStr = errorMessage.substring(jsonStartIndex, jsonEndIndex); const jsonError = JSON.parse(jsonStr); if (jsonError.error) { detailedError = `\nπ **Details**: ${jsonError.error.message || 'No additional details available'}`; if (jsonError.error.code) { detailedError += `\nπ’ **Error Code**: ${jsonError.error.code}`; } if (jsonError.error.type) { detailedError += `\nπ **Error Type**: ${jsonError.error.type}`; } } } catch (e) { // If we can't parse JSON from the error message, just continue } } // Construct a comprehensive error message const fullErrorMessage = `β **Image Edit Failed**\n\nβ οΈ **Error ${errorCode}**: ${errorType} - ${errorMessage}${detailedError}${suggestedFix}\n\nπ Please try again with a different prompt, image, or parameters.`; // Return the detailed error to the client return { content: [{ type: "text", text: fullErrorMessage }], isError: true, _meta: { error: { code: errorCode, type: errorType, message: errorMessage, details: detailedError.replace(/\nπ \*\*Details\*\*: /, ''), suggestion: suggestedFix.replace(/\nπ‘ \*\*Suggestion\*\*: /, ''), raw: JSON.stringify(error, Object.getOwnPropertyNames(error)) } } }; } }
- src/index.ts:365-391 (schema)Zod schema defining the input parameters for the create_image_edit tool, including flexible image and mask inputs (base64 string, array, or file path objects), prompt, and optional parameters like n, size, quality.const createImageEditSchema = z.object({ image: z.union([ z.string(), // Can be base64 encoded image string z.array(z.string()), // Can be array of base64 encoded image strings z.object({ // Can be an object with a file path filePath: z.string(), isBase64: z.boolean().optional().default(false) }), z.array(z.object({ // Can be an array of objects with file paths filePath: z.string(), isBase64: z.boolean().optional().default(false) })) ]), prompt: z.string().max(32000, "Prompt exceeds maximum length for gpt-image-1."), background: z.enum(["transparent", "opaque", "auto"]).optional(), mask: z.union([ z.string(), // Can be base64 encoded mask string z.object({ // Can be an object with a file path filePath: z.string(), isBase64: z.boolean().optional().default(false) }) ]).optional(), n: z.number().int().min(1).max(10).optional(), quality: z.enum(["high", "medium", "low", "auto"]).optional(), size: z.enum(["1024x1024", "1536x1024", "1024x1536", "auto"]).optional(), user: z.string().optional() });
- src/index.ts:394-803 (registration)Registration of the 'create_image_edit' tool with the MCP server using server.tool(), providing name, input schema, title metadata, and the handler function.server.tool( "create_image_edit", createImageEditSchema.shape, { title: "Edit existing images using OpenAI's gpt-image-1 model" }, async (args: CreateImageEditArgs, extra: any) => { try { // The OpenAI SDK expects 'image' and 'mask' to be Node.js ReadStream or Blob. // Since we are receiving base64 strings from the client, we need to convert them. // This is a simplified approach. A robust solution might involve handling file uploads // or different data formats depending on the client's capabilities. // For this implementation, we'll assume base64 and convert to Buffer, which the SDK might accept // or require further processing depending on its exact requirements for file-like objects. // NOTE: The OpenAI SDK's `images.edit` method specifically expects `File` or `Blob` in browser // environments and `ReadableStream` or `Buffer` in Node.js. Converting base64 to Buffer is // the most straightforward approach for a Node.js server receiving base64. // Process image input which can be file paths or base64 strings const imageFiles = []; // Handle different image input formats if (Array.isArray(args.image)) { // Handle array of strings or objects for (const img of args.image) { if (typeof img === 'string') { // Base64 string - create a temporary file const tempFile = path.join(os.tmpdir(), `image-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`); const base64Data = img.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync(tempFile, Buffer.from(base64Data, 'base64')); imageFiles.push(tempFile); } else { // Object with filePath - use the file directly imageFiles.push(img.filePath); } } } else if (typeof args.image === 'string') { // Single base64 string - create a temporary file const tempFile = path.join(os.tmpdir(), `image-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`); const base64Data = args.image.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync(tempFile, Buffer.from(base64Data, 'base64')); imageFiles.push(tempFile); } else { // Single object with filePath - use the file directly imageFiles.push(args.image.filePath); } // Process mask input which can be a file path or base64 string let maskFile = undefined; if (args.mask) { if (typeof args.mask === 'string') { // Mask is a base64 string - create a temporary file const tempFile = path.join(os.tmpdir(), `mask-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`); const base64Data = args.mask.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync(tempFile, Buffer.from(base64Data, 'base64')); maskFile = tempFile; } else { // Mask is an object with filePath - use the file directly maskFile = args.mask.filePath; } } // Use a direct curl command to call the OpenAI API // This is more reliable than using the SDK for file uploads // Create a temporary file to store the response const tempResponseFile = path.join(os.tmpdir(), `response-${Date.now()}.json`); // Build the curl command let curlCommand = `curl -s -X POST "https://api.openai.com/v1/images/edits" -H "Authorization: Bearer ${process.env.OPENAI_API_KEY}"`; // Add the model curlCommand += ` -F "model=gpt-image-1"`; // Add the prompt curlCommand += ` -F "prompt=${args.prompt}"`; // Add the images for (const imageFile of imageFiles) { curlCommand += ` -F "image[]=@${imageFile}"`; } // Add the mask if it exists if (maskFile) { curlCommand += ` -F "mask=@${maskFile}"`; } // Add other parameters if (args.n) curlCommand += ` -F "n=${args.n}"`; if (args.size) curlCommand += ` -F "size=${args.size}"`; if (args.quality) curlCommand += ` -F "quality=${args.quality}"`; if (args.background) curlCommand += ` -F "background=${args.background}"`; if (args.user) curlCommand += ` -F "user=${args.user}"`; // Add output redirection curlCommand += ` > "${tempResponseFile}"`; // Execute the curl command // Use execSync to run the curl command try { console.error(`Executing curl command to edit image...`); execSync(curlCommand, { stdio: ['pipe', 'pipe', 'inherit'] }); console.error(`Curl command executed successfully.`); } catch (error: any) { console.error(`Error executing curl command: ${error.message}`); throw new Error(`Failed to edit image: ${error.message}`); } // Read the response from the temporary file let responseJson; try { responseJson = fs.readFileSync(tempResponseFile, 'utf8'); console.error(`Response file read successfully.`); } catch (error: any) { console.error(`Error reading response file: ${error.message}`); throw new Error(`Failed to read response file: ${error.message}`); } // Parse the response let responseData; try { responseData = JSON.parse(responseJson); console.error(`Response parsed successfully.`); // Check if the response contains an error if (responseData.error) { console.error(`OpenAI API returned an error:`, responseData.error); const errorMessage = responseData.error.message || 'Unknown API error'; const errorType = responseData.error.type || 'api_error'; const errorCode = responseData.error.code || responseData.error.status || 'unknown'; throw { message: errorMessage, type: errorType, code: errorCode, response: { data: responseData } }; } } catch (error: any) { // If the error is from our API error check, rethrow it if (error.response && error.response.data) { throw error; } console.error(`Error parsing response: ${error.message}`); throw new Error(`Failed to parse response: ${error.message}`); } // Delete the temporary response file try { fs.unlinkSync(tempResponseFile); console.error(`Temporary response file deleted.`); } catch (error: any) { console.error(`Error deleting temporary file: ${error.message}`); // Don't throw an error here, just log it } // Clean up temporary files try { // Delete temporary image files for (const imageFile of imageFiles) { // Only delete files we created (temporary files in the os.tmpdir directory) if (imageFile.startsWith(os.tmpdir())) { try { fs.unlinkSync(imageFile); } catch (e) { /* ignore errors */ } } } // Delete temporary mask file if (maskFile && maskFile.startsWith(os.tmpdir())) { try { fs.unlinkSync(maskFile); } catch (e) { /* ignore errors */ } } } catch (cleanupError) { console.error("Error cleaning up temporary files:", cleanupError); } // No need for a Response-like object anymore since we're using fetch directly // Save images to disk and create response with file paths const savedImages = []; const imageContents = []; const format = "png"; // Assuming png for edits based on common practice if (responseData.data && responseData.data.length > 0) { for (const item of responseData.data) { if (item.b64_json) { // Save the image to disk const imagePath = saveImageToDisk(item.b64_json, format); // Add the saved image info to our response savedImages.push({ path: imagePath, format: format }); // Also include the image content for compatibility imageContents.push({ type: "image" as const, data: item.b64_json, mimeType: `image/${format}` }); } else if (item.url) { console.error(`Image URL: ${item.url}`); console.error("The gpt-image-1 model returned a URL instead of base64 data."); console.error("To view the image, open the URL in your browser."); // Add the URL info to our response savedImages.push({ url: item.url, format: format }); // Include a text message about the URL in the content imageContents.push({ type: "text" as const, text: `Image available at URL: ${item.url}` }); } } } // Create a beautifully formatted response with emojis and details const formatSize = (size: string | undefined) => size || "1024x1024"; const formatQuality = (quality: string | undefined) => quality || "high"; // Get source image information let sourceImageInfo = ""; if (Array.isArray(args.image)) { // Handle array of strings or objects sourceImageInfo = args.image.map((img, index) => { if (typeof img === 'string') { return ` ${index + 1}. Base64 encoded image`; } else { return ` ${index + 1}. ${img.filePath}`; } }).join('\n'); } else if (typeof args.image === 'string') { sourceImageInfo = " Base64 encoded image"; } else { sourceImageInfo = ` ${args.image.filePath}`; } // Get mask information let maskInfo = ""; if (args.mask) { if (typeof args.mask === 'string') { maskInfo = "π **Mask**: Base64 encoded mask applied"; } else { maskInfo = `π **Mask**: Mask from ${args.mask.filePath} applied`; } } // Create a beautiful formatted message const formattedMessage = ` βοΈ **Image Edit Complete!** ποΈ β¨ **Edit Prompt**: "${args.prompt}" πΌοΈ **Source Image${imageFiles.length > 1 ? 's' : ''}**: ${sourceImageInfo} ${maskInfo} π **Edit Parameters**: β’ Size: ${formatSize(args.size)} β’ Quality: ${formatQuality(args.quality)} β’ Number of Results: ${args.n || 1} ${args.background ? `β’ Background: ${args.background}` : ''} π **Edited ${savedImages.length} Image${savedImages.length > 1 ? 's' : ''}**: ${savedImages.map((img, index) => ` ${index + 1}. ${img.path || img.url}`).join('\n')} ${responseData.usage ? `β‘ **Token Usage**: β’ Total Tokens: ${responseData.usage.total_tokens} β’ Input Tokens: ${responseData.usage.input_tokens} β’ Output Tokens: ${responseData.usage.output_tokens}` : ''} π You can find your edited image${savedImages.length > 1 ? 's' : ''} at the path${savedImages.length > 1 ? 's' : ''} above! `; // Return both the image content and the saved file paths with the beautiful message return { content: [ { type: "text" as const, text: formattedMessage }, ...imageContents ], ...(responseData.usage && { _meta: { usage: { totalTokens: responseData.usage.total_tokens, inputTokens: responseData.usage.input_tokens, outputTokens: responseData.usage.output_tokens, }, savedImages: savedImages } }) }; } catch (error: any) { // Log the full error for debugging console.error("Error creating image edit:", error); // Extract detailed error information const errorCode = error.status || error.code || 'Unknown'; const errorType = error.type || 'Error'; const errorMessage = error.message || 'An unknown error occurred'; // Check for specific error types and provide more helpful messages let detailedError = ''; let suggestedFix = ''; // Handle file-related errors if (errorMessage.includes('ENOENT') || errorMessage.includes('no such file')) { detailedError = '\nπ **Details**: The specified image or mask file could not be found'; suggestedFix = '\nπ‘ **Suggestion**: Verify that the file path is correct and the file exists'; } // Handle permission errors else if (errorMessage.includes('EACCES') || errorMessage.includes('permission denied')) { detailedError = '\nπ **Details**: Permission denied when trying to access the file'; suggestedFix = '\nπ‘ **Suggestion**: Check file permissions or try running with elevated privileges'; } // Handle curl errors else if (errorMessage.includes('curl')) { detailedError = '\nπ **Details**: Error occurred while sending the request to OpenAI API'; suggestedFix = '\nπ‘ **Suggestion**: Check your internet connection and API key'; } // Handle OpenAI API errors else if (error.response) { try { const responseData = error.response.data || {}; if (responseData.error) { detailedError = `\nπ **Details**: ${responseData.error.message || 'No additional details available'}`; // Add parameter errors if available if (responseData.error.param) { detailedError += `\nπ **Parameter**: ${responseData.error.param}`; } // Add code if available if (responseData.error.code) { detailedError += `\nπ’ **Error Code**: ${responseData.error.code}`; } // Add type if available if (responseData.error.type) { detailedError += `\nπ **Error Type**: ${responseData.error.type}`; } // Provide suggestions based on error type if (responseData.error.type === 'invalid_request_error') { suggestedFix = '\nπ‘ **Suggestion**: Check that your image format is supported (PNG, JPEG) and the prompt is valid'; } else if (responseData.error.type === 'authentication_error') { suggestedFix = '\nπ‘ **Suggestion**: Verify your OpenAI API key is correct and has access to the gpt-image-1 model'; } } } catch (parseError) { detailedError = '\nπ **Details**: Could not parse error details from API response'; } } // If we have a JSON response with an error, try to extract it if (errorMessage.includes('{') && errorMessage.includes('}')) { try { const jsonStartIndex = errorMessage.indexOf('{'); const jsonEndIndex = errorMessage.lastIndexOf('}') + 1; const jsonStr = errorMessage.substring(jsonStartIndex, jsonEndIndex); const jsonError = JSON.parse(jsonStr); if (jsonError.error) { detailedError = `\nπ **Details**: ${jsonError.error.message || 'No additional details available'}`; if (jsonError.error.code) { detailedError += `\nπ’ **Error Code**: ${jsonError.error.code}`; } if (jsonError.error.type) { detailedError += `\nπ **Error Type**: ${jsonError.error.type}`; } } } catch (e) { // If we can't parse JSON from the error message, just continue } } // Construct a comprehensive error message const fullErrorMessage = `β **Image Edit Failed**\n\nβ οΈ **Error ${errorCode}**: ${errorType} - ${errorMessage}${detailedError}${suggestedFix}\n\nπ Please try again with a different prompt, image, or parameters.`; // Return the detailed error to the client return { content: [{ type: "text", text: fullErrorMessage }], isError: true, _meta: { error: { code: errorCode, type: errorType, message: errorMessage, details: detailedError.replace(/\nπ \*\*Details\*\*: /, ''), suggestion: suggestedFix.replace(/\nπ‘ \*\*Suggestion\*\*: /, ''), raw: JSON.stringify(error, Object.getOwnPropertyNames(error)) } } }; } } );