From 5ccf4495c0f64ca83f97953e7b43029974d54cda Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Thu, 1 Oct 2026 11:31:51 -0700 Subject: [PATCH 1/3] fix(wiza): surface Wiza's nested error messages --- apps/sim/tools/error-extractors.test.ts | 28 +++++++++++++++++++++++ apps/sim/tools/error-extractors.ts | 12 ++++++++++ apps/sim/tools/wiza/company_enrichment.ts | 2 ++ apps/sim/tools/wiza/get_credits.ts | 2 ++ apps/sim/tools/wiza/individual_reveal.ts | 2 ++ apps/sim/tools/wiza/prospect_search.ts | 2 ++ 6 files changed, 48 insertions(+) diff --git a/apps/sim/tools/error-extractors.test.ts b/apps/sim/tools/error-extractors.test.ts index 29f2315bdf2..081570e0df3 100644 --- a/apps/sim/tools/error-extractors.test.ts +++ b/apps/sim/tools/error-extractors.test.ts @@ -362,4 +362,32 @@ 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('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..bdd87f5fd7f 100644 --- a/apps/sim/tools/error-extractors.ts +++ b/apps/sim/tools/error-extractors.ts @@ -514,6 +514,17 @@ const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [ return parts.length > 0 ? parts.join(': ') : undefined }, }, + { + id: 'wiza-errors', + description: + 'Wiza API error envelope: {status: {code, message}}. The message is nested under status, so the generic extractors miss it', + examples: ['Wiza'], + extract: (errorInfo) => { + const message = errorInfo?.data?.status?.message + if (typeof message === 'string' && message.trim()) return message.trim() + return errorInfo?.data?.message + }, + }, { id: 'crunchbase-errors', description: @@ -680,6 +691,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 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; From 56f5b0bf857cce1632ba51eaf53b8e4a469a34c9 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Thu, 1 Oct 2026 11:40:22 -0700 Subject: [PATCH 2/3] fix(wiza): keep plain-text errors and parse reveal polling failures --- apps/sim/tools/error-extractors.test.ts | 6 ++++++ apps/sim/tools/error-extractors.ts | 8 +++++--- apps/sim/tools/wiza/individual_reveal.ts | 17 +++++++++++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/sim/tools/error-extractors.test.ts b/apps/sim/tools/error-extractors.test.ts index 081570e0df3..ebf7d394502 100644 --- a/apps/sim/tools/error-extractors.test.ts +++ b/apps/sim/tools/error-extractors.test.ts @@ -382,6 +382,12 @@ describe('Error Extractors', () => { 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('falls back to the status when Wiza sends an empty message', () => { const errorInfo: ErrorInfo = { status: 400, data: { status: { code: 400, message: '' } } } diff --git a/apps/sim/tools/error-extractors.ts b/apps/sim/tools/error-extractors.ts index bdd87f5fd7f..fadae3bfd14 100644 --- a/apps/sim/tools/error-extractors.ts +++ b/apps/sim/tools/error-extractors.ts @@ -517,12 +517,14 @@ const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [ { id: 'wiza-errors', description: - 'Wiza API error envelope: {status: {code, message}}. The message is nested under status, so the generic extractors miss it', + '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 message = errorInfo?.data?.status?.message + const data = errorInfo?.data + if (typeof data === 'string') return data.trim() || undefined + const message = data?.status?.message if (typeof message === 'string' && message.trim()) return message.trim() - return errorInfo?.data?.message + return data?.message }, }, { diff --git a/apps/sim/tools/wiza/individual_reveal.ts b/apps/sim/tools/wiza/individual_reveal.ts index 360850a56a6..ad410336f3c 100644 --- a/apps/sim/tools/wiza/individual_reveal.ts +++ b/apps/sim/tools/wiza/individual_reveal.ts @@ -1,5 +1,5 @@ import { sleep } from '@sim/utils/helpers' -import { ErrorExtractorId } from '@/tools/error-extractors' +import { ErrorExtractorId, extractErrorMessage } from '@/tools/error-extractors' import type { ToolConfig } from '@/tools/types' import { wizaHosting } from '@/tools/wiza/hosting' import type { @@ -240,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, } } From 8248103908afaa16273ab681a292bb7194991097 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Thu, 1 Oct 2026 11:50:17 -0700 Subject: [PATCH 3/3] fix(wiza): only accept trimmed string messages in error extractor --- apps/sim/tools/error-extractors.test.ts | 8 ++++++++ apps/sim/tools/error-extractors.ts | 9 +++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/sim/tools/error-extractors.test.ts b/apps/sim/tools/error-extractors.test.ts index ebf7d394502..cc05f3f78ce 100644 --- a/apps/sim/tools/error-extractors.test.ts +++ b/apps/sim/tools/error-extractors.test.ts @@ -388,6 +388,14 @@ describe('Error Extractors', () => { 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: '' } } } diff --git a/apps/sim/tools/error-extractors.ts b/apps/sim/tools/error-extractors.ts index fadae3bfd14..057a49f85b4 100644 --- a/apps/sim/tools/error-extractors.ts +++ b/apps/sim/tools/error-extractors.ts @@ -521,10 +521,11 @@ const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [ examples: ['Wiza'], extract: (errorInfo) => { const data = errorInfo?.data - if (typeof data === 'string') return data.trim() || undefined - const message = data?.status?.message - if (typeof message === 'string' && message.trim()) return message.trim() - return data?.message + const candidates = [data, data?.status?.message, data?.message] + for (const candidate of candidates) { + if (typeof candidate === 'string' && candidate.trim()) return candidate.trim() + } + return undefined }, }, {