Skip to main content
Glama
error-enhancer.tsβ€’8.54 kB
/** * Enhances API errors with helpful suggestions for value mismatches */ import { ValueMatchError } from '../errors/value-match-error.js'; import { findBestValueMatch, ValueMatchResult as ValueMatcherValueMatchResult, } from './value-matcher.js'; import axios from 'axios'; import { createScopedLogger } from './logger.js'; import type { UnknownObject } from './types/common.js'; // Known valid values for select fields - this should ideally come from Attio API const KNOWN_FIELD_VALUES: Record<string, string[]> = { // Note: type_persona removed as it doesn't exist in Attio API // Custom fields should be discovered via CLI or auto-discovery categories: [ 'B2B', 'B2C', 'SaaS', 'Healthcare', 'Technology', 'Finance', 'Education', 'Retail', 'Manufacturing', 'Hospitality', 'Real Estate', 'Professional Services', ], stage: [ 'Lead', 'Prospect', 'Opportunity', 'Customer', 'Churned', 'Qualified', 'Engaged', 'Negotiation', ], }; /** * Extracts field and value information from an API error */ interface ErrorContext { fieldSlug?: string; searchValue?: string; errorMessage: string; } /** * Parse API error to extract context */ function parseApiError(error: UnknownObject): ErrorContext { // Use imported createScopedLogger const log = createScopedLogger('utils.error-enhancer', 'parseApiError'); log.debug('ENTERING parseApiError'); log.debug('Error type info', { type: typeof error, ctor: error?.constructor?.name, }); if (error && typeof error === 'object') { log.debug('Error keys', { keys: Object.keys(error) }); log.debug('Error message from input', { message: error.message }); } const isAxios = axios.isAxiosError(error); log.debug('axios.isAxiosError result', { isAxios }); let responseExists = false; let responseDataExists = false; let responseDataContent = 'N/A'; if (isAxios && error.response) { responseExists = true; log.debug('Response exists. Keys', { keys: Object.keys(error.response) }); if (error.response.data) { responseDataExists = true; try { responseDataContent = JSON.stringify(error.response.data); } catch { // Failed to stringify response data responseDataContent = 'Error stringifying response.data'; } log.debug('Response data exists', { type: typeof error.response.data, content: responseDataContent, }); } else { log.debug('Response data is falsy'); } } else { log.debug('Response is falsy or not an Axios error for this check'); log.debug('Response data is falsy (no response)'); } log.debug('Final check before condition', { isAxios, responseExists, responseDataExists, }); if ( isAxios && responseExists && responseDataExists && error.response && error.response.data ) { // Use imported createScopedLogger const log = createScopedLogger('utils.error-enhancer', 'parseApiError'); log.debug('Condition met: processing error.response.data'); const data = error.response.data; const message = typeof data.message === 'string' ? data.message : ''; const pathArray = Array.isArray(data.path) && data.path.length > 0 ? data.path : []; const path = typeof pathArray[0] === 'string' ? pathArray[0] : undefined; log.debug('Extracted from response.data', { message, path }); if ( message.includes('Unknown select option name') || message.includes('Unknown multi-select option names') ) { const valueMatch = message.match(/constraint: (.*)/); const extractedValue = valueMatch && typeof valueMatch[1] === 'string' ? valueMatch[1].split(',')[0].trim() : undefined; console.error( `[enhancer-parseApiError] --- RETURNING ValueMismatch context: field='${path}', value='${extractedValue}' ---` ); return { fieldSlug: path, searchValue: extractedValue, errorMessage: message, }; } createScopedLogger('utils.error-enhancer', 'parseApiError').debug( 'Returning Non-ValueMismatch Axios context', { field: path } ); return { fieldSlug: path, errorMessage: message }; } createScopedLogger('utils.error-enhancer', 'parseApiError').debug( 'Condition not met' ); const genericErrorMessage = error && typeof error.message === 'string' ? error.message : 'Unknown error'; createScopedLogger('utils.error-enhancer', 'parseApiError').debug( 'Returning generic message', { genericErrorMessage } ); return { errorMessage: genericErrorMessage }; } /** * Enhance an API error with value suggestions if applicable */ export function enhanceApiError(error: UnknownObject): Error { // Use imported createScopedLogger const log = createScopedLogger('utils.error-enhancer', 'enhanceApiError'); log.debug('Called with error', { type: error?.constructor?.name, isAxiosError: !!error?.isAxiosError, }); log.debug('Attempting to enhance error', { message: (error as UnknownObject)?.message, responseData: ((error as UnknownObject)?.response as UnknownObject)?.data, }); const mismatchCheck = isValueMismatchError(error); log.debug('Mismatch check result', mismatchCheck); if ( mismatchCheck.isMismatch && mismatchCheck.fieldSlug && mismatchCheck.searchValue ) { const fieldSlug = mismatchCheck.fieldSlug; const searchValue = mismatchCheck.searchValue; const knownValues = KNOWN_FIELD_VALUES[fieldSlug]; // Already checked in isValueMismatchError, but good for clarity log.debug('Value mismatch confirmed', { fieldSlug, searchValue }); if (knownValues) { log.debug('Attempting to find best match', { fieldSlug, searchValue }); const matchResult: ValueMatcherValueMatchResult = findBestValueMatch( searchValue, knownValues ); log.debug( 'Match result from findBestValueMatch', matchResult as unknown as UnknownObject ); log.debug('Returning NEW ValueMatchError'); return new ValueMatchError( fieldSlug, searchValue, matchResult, error as unknown as Error // Pass original Axios error for reference ); } } else { log.debug('No actionable mismatch', { errorMessage: mismatchCheck.errorMessage, }); } log.debug('No specific enhancement applied; returning original error'); return error as unknown as Error; // Fallback: return original error if not enhanced } /** * Check if an error is a value mismatch that we can enhance */ export function isValueMismatchError(error: UnknownObject): { isMismatch: boolean; fieldSlug?: string; searchValue?: string; errorMessage?: string; } { const context = parseApiError(error); createScopedLogger('utils.error-enhancer', 'isValueMismatchError').debug( 'Context from parseApiError', context as unknown as UnknownObject ); if ( context.fieldSlug && context.searchValue && KNOWN_FIELD_VALUES[context.fieldSlug] ) { const knownValues = KNOWN_FIELD_VALUES[context.fieldSlug]; if ( knownValues && !knownValues.some( (v) => v.toLowerCase() === context.searchValue?.toLowerCase() ) ) { createScopedLogger('utils.error-enhancer', 'isValueMismatchError').debug( 'Mismatch found', { fieldSlug: context.fieldSlug, searchValue: context.searchValue } ); return { isMismatch: true, fieldSlug: context.fieldSlug, searchValue: context.searchValue, errorMessage: context.errorMessage, }; } } createScopedLogger('utils.error-enhancer', 'isValueMismatchError').debug( 'No mismatch or field/value not in KNOWN_FIELD_VALUES' ); return { isMismatch: false, errorMessage: context.errorMessage }; } /** * Get valid values for a field (if known) */ export function getKnownFieldValues(fieldSlug: string): string[] | null { return KNOWN_FIELD_VALUES[fieldSlug] || null; } /** * Add or update known field values * This could be populated from Attio API discovery */ export function updateKnownFieldValues( fieldSlug: string, values: string[] ): void { KNOWN_FIELD_VALUES[fieldSlug] = values; } // Remove locally defined ValueMatchResult and similarity functions if they were causing issues // interface ValueMatchResult { ... } // function similarity(...) { ... } // function editDistance(...) { ... }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server