/**
* Error Formatting Utility
*/
import { MCPError, ErrorCode } from '../types/mcp-tools.js';
import { API_KEY_ENV_VAR } from './auth.js';
// Local constants to avoid circular dependency with validation.ts
const SUPPORTED_FORMATS = '.mp3, .wav, .flac, .ogg, .m4a';
const MAX_FILE_SIZE = 100;
/**
* Error messages and suggestions by error code
*/
const ERROR_DEFINITIONS: Record<
ErrorCode,
{ message: string; suggestion: string }
> = {
MISSING_API_KEY: {
message: `Missing API key. Set ${API_KEY_ENV_VAR} environment variable.`,
suggestion: `Get your API key from https://app.ircamamplify.io and set: export ${API_KEY_ENV_VAR}="your-key"`,
},
INVALID_API_KEY: {
message: `Invalid API key. Check ${API_KEY_ENV_VAR} environment variable.`,
suggestion:
'Verify your key at https://app.ircamamplify.io. Keys may expire or be revoked.',
},
INVALID_URL: {
message: 'Cannot access URL. Ensure the URL is public and accessible.',
suggestion:
'The URL must be publicly accessible without authentication and point directly to an audio file.',
},
UNSUPPORTED_FORMAT: {
message: `Unsupported format. Accepted: ${SUPPORTED_FORMATS}.`,
suggestion: `Convert your audio to one of the supported formats: ${SUPPORTED_FORMATS}`,
},
FILE_TOO_LARGE: {
message: `File exceeds maximum size of ${MAX_FILE_SIZE}MB.`,
suggestion: `Reduce file size or use a shorter audio clip. Maximum size: ${MAX_FILE_SIZE}MB.`,
},
RATE_LIMITED: {
message: 'Rate limit reached. Retry after a few seconds.',
suggestion: 'Wait for the suggested retry time before making another request.',
},
SERVICE_UNAVAILABLE: {
message: 'IRCAM Amplify service unavailable. Please retry later.',
suggestion:
'The service is temporarily unavailable. Wait a few minutes and try again.',
},
JOB_NOT_FOUND: {
message: 'Job not found or expired. Results are only available for 24 hours.',
suggestion:
'Check the job ID is correct. Job results expire after 24 hours.',
},
JOB_FAILED: {
message: 'Job processing failed.',
suggestion:
'Check the audio file is valid and try again. If the problem persists, contact support.',
},
UNKNOWN_ERROR: {
message: 'An unexpected error occurred.',
suggestion: 'Try again. If the problem persists, contact support.',
},
};
/**
* Create a formatted MCPError
*/
export function formatError(
code: ErrorCode,
customMessage?: string,
retryAfter?: number
): MCPError {
const definition = ERROR_DEFINITIONS[code];
const error: MCPError = {
code,
message: customMessage || definition.message,
suggestion: definition.suggestion,
};
if (retryAfter && retryAfter > 0) {
error.retry_after = retryAfter;
error.message = `${error.message} Retry after ${retryAfter} seconds.`;
}
return error;
}
/**
* Create error from HTTP response
*/
export function formatHttpError(
status: number,
responseMessage?: string,
retryAfter?: number
): MCPError {
let code: ErrorCode;
switch (status) {
case 401:
code = 'INVALID_API_KEY';
break;
case 400:
// Check if it's a format error based on message
if (responseMessage?.toLowerCase().includes('format')) {
code = 'UNSUPPORTED_FORMAT';
} else {
code = 'INVALID_URL';
}
break;
case 413:
code = 'FILE_TOO_LARGE';
break;
case 429:
code = 'RATE_LIMITED';
break;
case 404:
code = 'JOB_NOT_FOUND';
break;
case 500:
code = 'JOB_FAILED';
break;
case 502:
case 503:
case 504:
code = 'SERVICE_UNAVAILABLE';
break;
default:
code = 'UNKNOWN_ERROR';
}
return formatError(code, responseMessage, retryAfter);
}
/**
* Check if error is a MCPError
*/
export function isMCPError(error: unknown): error is MCPError {
return (
typeof error === 'object' &&
error !== null &&
'code' in error &&
'message' in error &&
typeof (error as MCPError).code === 'string' &&
typeof (error as MCPError).message === 'string'
);
}
/**
* Convert any error to MCPError
*/
export function toMCPError(error: unknown): MCPError {
if (isMCPError(error)) {
return error;
}
if (error instanceof Error) {
return formatError('UNKNOWN_ERROR', error.message);
}
return formatError('UNKNOWN_ERROR', String(error));
}
/**
* Format error for MCP tool response
*/
export function formatToolError(error: MCPError): {
isError: true;
content: Array<{ type: 'text'; text: string }>;
} {
const parts: string[] = [
`Error: ${error.message}`,
];
if (error.suggestion) {
parts.push(`Suggestion: ${error.suggestion}`);
}
if (error.retry_after) {
parts.push(`Retry after: ${error.retry_after} seconds`);
}
return {
isError: true,
content: [
{
type: 'text',
text: parts.join('\n'),
},
],
};
}