diff --git a/apps/sim/tools/error-extractors.test.ts b/apps/sim/tools/error-extractors.test.ts index 29f2315bdf2..cc05f3f78ce 100644 --- a/apps/sim/tools/error-extractors.test.ts +++ b/apps/sim/tools/error-extractors.test.ts @@ -362,4 +362,46 @@ describe('Error Extractors', () => { ) }) }) + + describe('wiza-errors', () => { + it('extracts the message nested under status', () => { + const errorInfo: ErrorInfo = { + status: 400, + statusText: 'Bad Request', + data: { status: { code: 400, message: 'The size parameter is not allowed.' } }, + } + + expect(extractErrorMessage(errorInfo, ErrorExtractorId.WIZA_ERRORS)).toBe( + 'The size parameter is not allowed.' + ) + }) + + it('falls back to a top-level message', () => { + const errorInfo: ErrorInfo = { status: 401, data: { message: 'Unauthorized' } } + + expect(extractErrorMessage(errorInfo, ErrorExtractorId.WIZA_ERRORS)).toBe('Unauthorized') + }) + + it('keeps plain-text bodies', () => { + const errorInfo: ErrorInfo = { status: 502, data: 'Bad gateway' } + + expect(extractErrorMessage(errorInfo, ErrorExtractorId.WIZA_ERRORS)).toBe('Bad gateway') + }) + + it('ignores a non-string top-level message', () => { + const errorInfo: ErrorInfo = { status: 422, data: { message: ['bad filter'] } } + + expect(extractErrorMessage(errorInfo, ErrorExtractorId.WIZA_ERRORS)).toBe( + 'Request failed with status 422' + ) + }) + + it('falls back to the status when Wiza sends an empty message', () => { + const errorInfo: ErrorInfo = { status: 400, data: { status: { code: 400, message: '' } } } + + expect(extractErrorMessage(errorInfo, ErrorExtractorId.WIZA_ERRORS)).toBe( + 'Request failed with status 400' + ) + }) + }) }) diff --git a/apps/sim/tools/error-extractors.ts b/apps/sim/tools/error-extractors.ts index 1177c38c642..057a49f85b4 100644 --- a/apps/sim/tools/error-extractors.ts +++ b/apps/sim/tools/error-extractors.ts @@ -514,6 +514,20 @@ const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [ return parts.length > 0 ? parts.join(': ') : undefined }, }, + { + id: 'wiza-errors', + description: + 'Wiza API error envelope: {status: {code, message}}, plus plain-text bodies. The message is nested under status, so the generic extractors miss it', + examples: ['Wiza'], + extract: (errorInfo) => { + const data = errorInfo?.data + const candidates = [data, data?.status?.message, data?.message] + for (const candidate of candidates) { + if (typeof candidate === 'string' && candidate.trim()) return candidate.trim() + } + return undefined + }, + }, { id: 'crunchbase-errors', description: @@ -680,6 +694,7 @@ export const ErrorExtractorId = { POSTHOG_ERRORS: 'posthog-errors', QUICKBOOKS_FAULT: 'quickbooks-fault', PROSPEO_ERRORS: 'prospeo-errors', + WIZA_ERRORS: 'wiza-errors', CRUNCHBASE_ERRORS: 'crunchbase-errors', PITCHBOOK_ERRORS: 'pitchbook-errors', SPLUNK_ERRORS: 'splunk-errors', diff --git a/apps/sim/tools/wiza/company_enrichment.ts b/apps/sim/tools/wiza/company_enrichment.ts index 39089404f1c..e7da372122d 100644 --- a/apps/sim/tools/wiza/company_enrichment.ts +++ b/apps/sim/tools/wiza/company_enrichment.ts @@ -1,3 +1,4 @@ +import { ErrorExtractorId } from '@/tools/error-extractors' import type { ToolConfig } from '@/tools/types' import { wizaHosting } from '@/tools/wiza/hosting' import type { WizaCompanyEnrichmentParams, WizaCompanyEnrichmentResponse } from '@/tools/wiza/types' @@ -11,6 +12,7 @@ export const wizaCompanyEnrichmentTool: ToolConfig< description: 'Enrich a company by name, domain, LinkedIn ID, or LinkedIn slug with detailed firmographic data', version: '1.0.0', + errorExtractor: ErrorExtractorId.WIZA_ERRORS, hosting: wizaHosting((_params, output) => { // 2 API credits per successful company match; no charge on a no-match. diff --git a/apps/sim/tools/wiza/get_credits.ts b/apps/sim/tools/wiza/get_credits.ts index 3ff8f02c2e7..676306ae5c8 100644 --- a/apps/sim/tools/wiza/get_credits.ts +++ b/apps/sim/tools/wiza/get_credits.ts @@ -1,3 +1,4 @@ +import { ErrorExtractorId } from '@/tools/error-extractors' import type { ToolConfig } from '@/tools/types' import type { WizaGetCreditsParams, WizaGetCreditsResponse } from '@/tools/wiza/types' @@ -6,6 +7,7 @@ export const wizaGetCreditsTool: ToolConfig((_params, output) => { let credits = 0 @@ -238,9 +240,22 @@ export const wizaIndividualRevealTool: ToolConfig< consecutiveErrors += 1 if (consecutiveErrors >= MAX_CONSECUTIVE_POLL_ERRORS) { const errorText = await statusResponse.text().catch(() => '') + let errorData: unknown + try { + errorData = JSON.parse(errorText) + } catch { + errorData = errorText + } return { success: false, - error: `Wiza API error: ${statusResponse.status} - ${errorText}`, + error: extractErrorMessage( + { + status: statusResponse.status, + statusText: statusResponse.statusText, + data: errorData, + }, + ErrorExtractorId.WIZA_ERRORS + ), output: result.output, } } diff --git a/apps/sim/tools/wiza/prospect_search.ts b/apps/sim/tools/wiza/prospect_search.ts index 5031a76df62..34e7ae6bb69 100644 --- a/apps/sim/tools/wiza/prospect_search.ts +++ b/apps/sim/tools/wiza/prospect_search.ts @@ -1,4 +1,5 @@ import { isRecordLike } from '@sim/utils/object' +import { ErrorExtractorId } from '@/tools/error-extractors' import type { ToolConfig } from '@/tools/types' import { wizaHosting } from '@/tools/wiza/hosting' import type { WizaProspectSearchParams, WizaProspectSearchResponse } from '@/tools/wiza/types' @@ -11,6 +12,7 @@ export const wizaProspectSearchTool: ToolConfig< name: 'Wiza Prospect Search', description: "Search Wiza's database of prospects using person, company, and financial filters", version: '1.0.0', + errorExtractor: ErrorExtractorId.WIZA_ERRORS, hosting: wizaHosting(() => { // Prospect search returns profiles without contact data and consumes no credits;