chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { wizaHosting } from '@/tools/wiza/hosting'
|
||||
import type { WizaCompanyEnrichmentParams, WizaCompanyEnrichmentResponse } from '@/tools/wiza/types'
|
||||
|
||||
export const wizaCompanyEnrichmentTool: ToolConfig<
|
||||
WizaCompanyEnrichmentParams,
|
||||
WizaCompanyEnrichmentResponse
|
||||
> = {
|
||||
id: 'wiza_company_enrichment',
|
||||
name: 'Wiza Company Enrichment',
|
||||
description:
|
||||
'Enrich a company by name, domain, LinkedIn ID, or LinkedIn slug with detailed firmographic data',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: wizaHosting<WizaCompanyEnrichmentParams>((_params, output) => {
|
||||
// 2 API credits per successful company match; no charge on a no-match.
|
||||
return output.company_name || output.company_domain || output.domain ? 2 : 0
|
||||
}),
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Wiza API key',
|
||||
},
|
||||
company_name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company name (e.g., "Wiza")',
|
||||
},
|
||||
company_domain: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company domain (e.g., "wiza.co")',
|
||||
},
|
||||
company_linkedin_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company LinkedIn ID',
|
||||
},
|
||||
company_linkedin_slug: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company LinkedIn slug from the URL',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://wiza.co/api/company_enrichments',
|
||||
method: 'POST',
|
||||
headers: (params: WizaCompanyEnrichmentParams) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: WizaCompanyEnrichmentParams) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
if (params.company_name) body.company_name = params.company_name
|
||||
if (params.company_domain) body.company_domain = params.company_domain
|
||||
if (params.company_linkedin_id) body.company_linkedin_id = params.company_linkedin_id
|
||||
if (params.company_linkedin_slug) body.company_linkedin_slug = params.company_linkedin_slug
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Wiza API error: ${response.status} - ${errorText}`)
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
const d = json.data ?? {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
company_name: d.company_name ?? null,
|
||||
company_domain: d.company_domain ?? null,
|
||||
domain: d.domain ?? null,
|
||||
company_industry: d.company_industry ?? null,
|
||||
company_size: d.company_size ?? null,
|
||||
company_size_range: d.company_size_range ?? null,
|
||||
company_founded: d.company_founded ?? null,
|
||||
company_revenue_range: d.company_revenue_range ?? null,
|
||||
company_funding: d.company_funding ?? null,
|
||||
company_type: d.company_type ?? null,
|
||||
company_description: d.company_description ?? null,
|
||||
company_ticker: d.company_ticker ?? null,
|
||||
company_last_funding_round: d.company_last_funding_round ?? null,
|
||||
company_last_funding_amount: d.company_last_funding_amount ?? null,
|
||||
company_last_funding_at: d.company_last_funding_at ?? null,
|
||||
company_location: d.company_location ?? null,
|
||||
company_twitter: d.company_twitter ?? null,
|
||||
company_facebook: d.company_facebook ?? null,
|
||||
company_linkedin: d.company_linkedin ?? null,
|
||||
company_linkedin_id: d.company_linkedin_id ?? null,
|
||||
company_street: d.company_street ?? null,
|
||||
company_locality: d.company_locality ?? null,
|
||||
company_region: d.company_region ?? null,
|
||||
company_postal_code: d.company_postal_code ?? null,
|
||||
company_country: d.company_country ?? null,
|
||||
credits: d.credits ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
company_name: { type: 'string', description: 'Company name', optional: true },
|
||||
company_domain: { type: 'string', description: 'Company domain', optional: true },
|
||||
domain: { type: 'string', description: 'Domain', optional: true },
|
||||
company_industry: { type: 'string', description: 'Industry', optional: true },
|
||||
company_size: { type: 'number', description: 'Employee count', optional: true },
|
||||
company_size_range: { type: 'string', description: 'Headcount range', optional: true },
|
||||
company_founded: { type: 'number', description: 'Year founded', optional: true },
|
||||
company_revenue_range: { type: 'string', description: 'Revenue range', optional: true },
|
||||
company_funding: { type: 'string', description: 'Total funding', optional: true },
|
||||
company_type: { type: 'string', description: 'Company type', optional: true },
|
||||
company_description: { type: 'string', description: 'Description', optional: true },
|
||||
company_ticker: { type: 'string', description: 'Stock ticker', optional: true },
|
||||
company_last_funding_round: {
|
||||
type: 'string',
|
||||
description: 'Last funding round',
|
||||
optional: true,
|
||||
},
|
||||
company_last_funding_amount: {
|
||||
type: 'string',
|
||||
description: 'Last funding amount',
|
||||
optional: true,
|
||||
},
|
||||
company_last_funding_at: { type: 'string', description: 'Last funding date', optional: true },
|
||||
company_location: { type: 'string', description: 'Full location string', optional: true },
|
||||
company_twitter: { type: 'string', description: 'Twitter URL', optional: true },
|
||||
company_facebook: { type: 'string', description: 'Facebook URL', optional: true },
|
||||
company_linkedin: { type: 'string', description: 'LinkedIn URL', optional: true },
|
||||
company_linkedin_id: { type: 'string', description: 'LinkedIn ID', optional: true },
|
||||
company_street: { type: 'string', description: 'Street address', optional: true },
|
||||
company_locality: { type: 'string', description: 'City', optional: true },
|
||||
company_region: { type: 'string', description: 'State/region', optional: true },
|
||||
company_postal_code: { type: 'string', description: 'Postal code', optional: true },
|
||||
company_country: { type: 'string', description: 'Country', optional: true },
|
||||
credits: {
|
||||
type: 'json',
|
||||
description: 'Credits deducted for this enrichment (api_credits: { total, company_credits })',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WizaGetCreditsParams, WizaGetCreditsResponse } from '@/tools/wiza/types'
|
||||
|
||||
export const wizaGetCreditsTool: ToolConfig<WizaGetCreditsParams, WizaGetCreditsResponse> = {
|
||||
id: 'wiza_get_credits',
|
||||
name: 'Wiza Get Credits',
|
||||
description: 'Retrieve the remaining credits on your Wiza account',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Wiza API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://wiza.co/api/meta/credits',
|
||||
method: 'GET',
|
||||
headers: (params: WizaGetCreditsParams) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Wiza API error: ${response.status} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const credits = data.credits ?? {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
email_credits: credits.email_credits ?? null,
|
||||
phone_credits: credits.phone_credits ?? null,
|
||||
export_credits: credits.export_credits ?? null,
|
||||
api_credits: credits.api_credits ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
email_credits: {
|
||||
type: 'json',
|
||||
description: 'Remaining email credits (number or "unlimited")',
|
||||
optional: true,
|
||||
},
|
||||
phone_credits: {
|
||||
type: 'json',
|
||||
description: 'Remaining phone credits (number or "unlimited")',
|
||||
optional: true,
|
||||
},
|
||||
export_credits: { type: 'number', description: 'Remaining export credits', optional: true },
|
||||
api_credits: { type: 'number', description: 'Remaining API credits', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { ToolHostingConfig } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Env var prefix for Wiza hosted keys. Provide keys as `WIZA_API_KEY_COUNT`
|
||||
* plus `WIZA_API_KEY_1..N`.
|
||||
*/
|
||||
export const WIZA_API_KEY_PREFIX = 'WIZA_API_KEY'
|
||||
|
||||
/**
|
||||
* Dollar cost of a single Wiza API credit.
|
||||
*
|
||||
* Wiza meters API usage in credits at a documented $0.025/credit (2,000-credit
|
||||
* minimum) — https://help.wiza.co/en/articles/13551713-how-to-purchase-api-credits.
|
||||
* Credits are deducted only when data is successfully returned: 2 credits per
|
||||
* valid email, 5 credits per phone, 2 credits per company enrichment.
|
||||
*/
|
||||
export const WIZA_CREDIT_USD = 0.025
|
||||
|
||||
/**
|
||||
* Build a Wiza `hosting` config. `getCredits` returns the number of Wiza API
|
||||
* credits the call consumed, derived from the tool's output.
|
||||
*/
|
||||
export function wizaHosting<P>(
|
||||
getCredits: (params: P, output: Record<string, unknown>) => number
|
||||
): ToolHostingConfig<P> {
|
||||
return {
|
||||
envKeyPrefix: WIZA_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'wiza',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
getCost: (params, output) => {
|
||||
const credits = getCredits(params, output)
|
||||
return { cost: credits * WIZA_CREDIT_USD, metadata: { credits } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export { wizaCompanyEnrichmentTool } from './company_enrichment'
|
||||
export { wizaGetCreditsTool } from './get_credits'
|
||||
export { wizaIndividualRevealTool } from './individual_reveal'
|
||||
export { wizaProspectSearchTool } from './prospect_search'
|
||||
export type * from './types'
|
||||
@@ -0,0 +1,330 @@
|
||||
import { sleep } from '@sim/utils/helpers'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { wizaHosting } from '@/tools/wiza/hosting'
|
||||
import type {
|
||||
WizaIndividualRevealData,
|
||||
WizaIndividualRevealParams,
|
||||
WizaIndividualRevealResponse,
|
||||
} from '@/tools/wiza/types'
|
||||
|
||||
const POLL_INTERVAL_MS = 2000
|
||||
const MAX_POLL_TIME_MS = 120000
|
||||
/** Tolerate brief Wiza outages while polling before giving up on an already-started reveal. */
|
||||
const MAX_CONSECUTIVE_POLL_ERRORS = 3
|
||||
|
||||
/** Whether a reveal payload has reached a terminal state and no longer needs polling. */
|
||||
function isTerminalReveal(d: { status?: string | null; is_complete?: boolean | null }): boolean {
|
||||
return d.status === 'finished' || d.status === 'failed' || d.is_complete === true
|
||||
}
|
||||
|
||||
/** Map a Wiza individual-reveal payload (`data` object) to the tool output shape. */
|
||||
function mapRevealData(d: Record<string, unknown>): WizaIndividualRevealData {
|
||||
const emails = Array.isArray(d.emails) ? (d.emails as Record<string, unknown>[]) : []
|
||||
const phones = Array.isArray(d.phones) ? (d.phones as Record<string, unknown>[]) : []
|
||||
return {
|
||||
id: (d.id as number) ?? null,
|
||||
status: (d.status as string) ?? null,
|
||||
is_complete: (d.is_complete as boolean) ?? null,
|
||||
name: (d.name as string) ?? null,
|
||||
company: (d.company as string) ?? null,
|
||||
enrichment_level: (d.enrichment_level as string) ?? null,
|
||||
linkedin_profile_url: (d.linkedin_profile_url as string) ?? null,
|
||||
title: (d.title as string) ?? null,
|
||||
location: (d.location as string) ?? null,
|
||||
email: (d.email as string) ?? null,
|
||||
email_type: (d.email_type as string) ?? null,
|
||||
email_status: (d.email_status as string) ?? null,
|
||||
emails: emails.map((e) => ({
|
||||
email: (e.email as string) ?? null,
|
||||
email_type: (e.email_type as string) ?? null,
|
||||
email_status: (e.email_status as string) ?? null,
|
||||
})),
|
||||
mobile_phone: (d.mobile_phone as string) ?? null,
|
||||
phone_number: (d.phone_number as string) ?? null,
|
||||
phone_status: (d.phone_status as string) ?? null,
|
||||
phones: phones.map((p) => ({
|
||||
number: (p.number as string) ?? null,
|
||||
pretty_number: (p.pretty_number as string) ?? null,
|
||||
type: (p.type as string) ?? null,
|
||||
})),
|
||||
company_size: (d.company_size as number) ?? null,
|
||||
company_size_range: (d.company_size_range as string) ?? null,
|
||||
company_type: (d.company_type as string) ?? null,
|
||||
company_domain: (d.company_domain as string) ?? null,
|
||||
company_locality: (d.company_locality as string) ?? null,
|
||||
company_region: (d.company_region as string) ?? null,
|
||||
company_country: (d.company_country as string) ?? null,
|
||||
company_street: (d.company_street as string) ?? null,
|
||||
company_postal_code: (d.company_postal_code as string) ?? null,
|
||||
company_founded: (d.company_founded as number) ?? null,
|
||||
company_funding: (d.company_funding as string) ?? null,
|
||||
company_revenue: (d.company_revenue as string) ?? null,
|
||||
company_industry: (d.company_industry as string) ?? null,
|
||||
company_subindustry: (d.company_subindustry as string) ?? null,
|
||||
company_linkedin: (d.company_linkedin as string) ?? null,
|
||||
company_location: (d.company_location as string) ?? null,
|
||||
company_description: (d.company_description as string) ?? null,
|
||||
credits: (d.credits as Record<string, unknown>) ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
export const wizaIndividualRevealTool: ToolConfig<
|
||||
WizaIndividualRevealParams,
|
||||
WizaIndividualRevealResponse
|
||||
> = {
|
||||
id: 'wiza_individual_reveal',
|
||||
name: 'Wiza Individual Reveal',
|
||||
description:
|
||||
'Reveal a contact via LinkedIn URL, name + company/domain, or email. Starts the reveal and polls until it resolves. Uses 2 credits per valid email and 5 credits per phone, charged only on success.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: wizaHosting<WizaIndividualRevealParams>((_params, output) => {
|
||||
let credits = 0
|
||||
const emails = Array.isArray(output.emails)
|
||||
? (output.emails as { email_status?: string }[])
|
||||
: []
|
||||
const emailValid =
|
||||
output.email_status === 'valid' || emails.some((e) => e.email_status === 'valid')
|
||||
// 2 credits when at least one valid email is returned.
|
||||
if (emailValid) credits += 2
|
||||
const phones = Array.isArray(output.phones) ? output.phones : []
|
||||
const phoneFound = Boolean(output.mobile_phone || output.phone_number || phones.length > 0)
|
||||
// 5 credits when at least one phone is returned.
|
||||
if (phoneFound) credits += 5
|
||||
return credits
|
||||
}),
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Wiza API key',
|
||||
},
|
||||
enrichment_level: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Enrichment depth: none, partial, phone, or full',
|
||||
},
|
||||
profile_url: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'LinkedIn profile URL (e.g., https://linkedin.com/in/johndoe)',
|
||||
},
|
||||
full_name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Full name (used with company or domain)',
|
||||
},
|
||||
company: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company name (used with full_name)',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company domain (used with full_name)',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address (use alone or with other identifiers)',
|
||||
},
|
||||
accept_work: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to accept work emails (email_options)',
|
||||
},
|
||||
accept_personal: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to accept personal emails (email_options)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://wiza.co/api/individual_reveals',
|
||||
method: 'POST',
|
||||
headers: (params: WizaIndividualRevealParams) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: WizaIndividualRevealParams) => {
|
||||
const individual: Record<string, unknown> = {}
|
||||
if (params.profile_url) individual.profile_url = params.profile_url
|
||||
if (params.full_name) individual.full_name = params.full_name
|
||||
if (params.company) individual.company = params.company
|
||||
if (params.domain) individual.domain = params.domain
|
||||
if (params.email) individual.email = params.email
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
individual_reveal: individual,
|
||||
enrichment_level: params.enrichment_level,
|
||||
}
|
||||
|
||||
if (params.accept_work !== undefined || params.accept_personal !== undefined) {
|
||||
const emailOptions: Record<string, unknown> = {}
|
||||
if (params.accept_work !== undefined) emailOptions.accept_work = params.accept_work
|
||||
if (params.accept_personal !== undefined) {
|
||||
emailOptions.accept_personal = params.accept_personal
|
||||
}
|
||||
body.email_options = emailOptions
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Wiza API error: ${response.status} - ${errorText}`)
|
||||
}
|
||||
const json = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: mapRevealData(json.data ?? {}),
|
||||
}
|
||||
},
|
||||
|
||||
postProcess: async (result, params) => {
|
||||
if (!result.success) return result
|
||||
|
||||
// Wiza can resolve synchronously (e.g. a cache hit) — the initial POST payload is
|
||||
// already mapped, so skip polling when it is terminal.
|
||||
if (isTerminalReveal(result.output)) {
|
||||
return { success: result.output.status !== 'failed', output: result.output }
|
||||
}
|
||||
|
||||
const revealId = result.output.id
|
||||
if (revealId == null) {
|
||||
// Return an explicit failure rather than throwing: a thrown error here is swallowed
|
||||
// by the executor and masked as the queued (incomplete) success result.
|
||||
return {
|
||||
success: false,
|
||||
error: 'Wiza individual reveal did not return an id',
|
||||
output: result.output,
|
||||
}
|
||||
}
|
||||
|
||||
let elapsedTime = 0
|
||||
let consecutiveErrors = 0
|
||||
while (elapsedTime < MAX_POLL_TIME_MS) {
|
||||
await sleep(POLL_INTERVAL_MS)
|
||||
elapsedTime += POLL_INTERVAL_MS
|
||||
|
||||
const statusResponse = await fetch(
|
||||
`https://wiza.co/api/individual_reveals/${encodeURIComponent(String(revealId))}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusResponse.ok) {
|
||||
// The reveal is already started (and billed by Wiza), so tolerate brief outages and
|
||||
// retry rather than aborting the whole window on a single transient 5xx/429.
|
||||
consecutiveErrors += 1
|
||||
if (consecutiveErrors >= MAX_CONSECUTIVE_POLL_ERRORS) {
|
||||
const errorText = await statusResponse.text().catch(() => '')
|
||||
return {
|
||||
success: false,
|
||||
error: `Wiza API error: ${statusResponse.status} - ${errorText}`,
|
||||
output: result.output,
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
consecutiveErrors = 0
|
||||
|
||||
const json = await statusResponse.json()
|
||||
const data = json.data ?? {}
|
||||
|
||||
if (isTerminalReveal(data)) {
|
||||
return {
|
||||
success: data.status !== 'failed',
|
||||
output: mapRevealData(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: 'Wiza individual reveal did not complete within the polling window',
|
||||
output: result.output,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Reveal ID' },
|
||||
status: { type: 'string', description: 'queued | resolving | finished | failed' },
|
||||
is_complete: { type: 'boolean', description: 'Whether the reveal has completed' },
|
||||
name: { type: 'string', description: 'Full name', optional: true },
|
||||
company: { type: 'string', description: 'Company name', optional: true },
|
||||
enrichment_level: { type: 'string', description: 'Enrichment level used', optional: true },
|
||||
linkedin_profile_url: { type: 'string', description: 'LinkedIn URL', optional: true },
|
||||
title: { type: 'string', description: 'Job title', optional: true },
|
||||
location: { type: 'string', description: 'Location', optional: true },
|
||||
email: { type: 'string', description: 'Primary email', optional: true },
|
||||
email_type: { type: 'string', description: 'Email type', optional: true },
|
||||
email_status: { type: 'string', description: 'valid | risky | unfound', optional: true },
|
||||
emails: {
|
||||
type: 'array',
|
||||
description: 'All emails found',
|
||||
optional: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: { type: 'string' },
|
||||
email_type: { type: 'string' },
|
||||
email_status: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
mobile_phone: { type: 'string', description: 'Mobile phone', optional: true },
|
||||
phone_number: { type: 'string', description: 'Direct/office phone', optional: true },
|
||||
phone_status: { type: 'string', description: 'found | unfound', optional: true },
|
||||
phones: {
|
||||
type: 'array',
|
||||
description: 'All phones found',
|
||||
optional: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'string' },
|
||||
pretty_number: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
company_size: { type: 'number', description: 'Employee count', optional: true },
|
||||
company_size_range: { type: 'string', description: 'Headcount range', optional: true },
|
||||
company_type: { type: 'string', description: 'Company type', optional: true },
|
||||
company_domain: { type: 'string', description: 'Company domain', optional: true },
|
||||
company_locality: { type: 'string', description: 'City', optional: true },
|
||||
company_region: { type: 'string', description: 'State/region', optional: true },
|
||||
company_country: { type: 'string', description: 'Country', optional: true },
|
||||
company_street: { type: 'string', description: 'Street', optional: true },
|
||||
company_postal_code: { type: 'string', description: 'Postal code', optional: true },
|
||||
company_founded: { type: 'number', description: 'Year founded', optional: true },
|
||||
company_funding: { type: 'string', description: 'Funding total', optional: true },
|
||||
company_revenue: { type: 'string', description: 'Revenue', optional: true },
|
||||
company_industry: { type: 'string', description: 'Industry', optional: true },
|
||||
company_subindustry: { type: 'string', description: 'Subindustry', optional: true },
|
||||
company_linkedin: { type: 'string', description: 'Company LinkedIn URL', optional: true },
|
||||
company_location: { type: 'string', description: 'Full company location', optional: true },
|
||||
company_description: { type: 'string', description: 'Company description', optional: true },
|
||||
credits: { type: 'json', description: 'Credits consumed by the reveal', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { wizaHosting } from '@/tools/wiza/hosting'
|
||||
import type { WizaProspectSearchParams, WizaProspectSearchResponse } from '@/tools/wiza/types'
|
||||
|
||||
export const wizaProspectSearchTool: ToolConfig<
|
||||
WizaProspectSearchParams,
|
||||
WizaProspectSearchResponse
|
||||
> = {
|
||||
id: 'wiza_prospect_search',
|
||||
name: 'Wiza Prospect Search',
|
||||
description: "Search Wiza's database of prospects using person, company, and financial filters",
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: wizaHosting<WizaProspectSearchParams>(() => {
|
||||
// Prospect search returns profiles without contact data and consumes no credits;
|
||||
// Wiza charges only on reveal/enrichment.
|
||||
return 0
|
||||
}),
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Wiza API key',
|
||||
},
|
||||
size: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of sample profiles to return (0-30, default 0)',
|
||||
},
|
||||
filters: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Full filters object (overrides individual filter params if provided)',
|
||||
},
|
||||
first_name: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Exact first names to match (e.g., ["John", "Jane"])',
|
||||
},
|
||||
last_name: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Exact last names to match',
|
||||
},
|
||||
job_title: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Job titles to include/exclude (e.g., [{"v":"CEO","s":"i"},{"v":"CTO","s":"e"}])',
|
||||
},
|
||||
job_title_level: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Seniority levels (e.g., ["cxo", "director", "manager"])',
|
||||
},
|
||||
job_role: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Job role categories (e.g., ["sales", "engineering", "marketing"])',
|
||||
},
|
||||
job_sub_role: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Detailed role categories (e.g., ["software", "product"])',
|
||||
},
|
||||
location: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Person's location filters (city/state/country with include/exclude)",
|
||||
},
|
||||
skill: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Professional skills (e.g., ["python", "marketing"])',
|
||||
},
|
||||
school: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Educational institutions',
|
||||
},
|
||||
major: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Field of study',
|
||||
},
|
||||
linkedin_slug: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'LinkedIn profile slugs',
|
||||
},
|
||||
job_company: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Current company filters (include/exclude)',
|
||||
},
|
||||
past_company: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Past company filters',
|
||||
},
|
||||
company_location: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company HQ location filters',
|
||||
},
|
||||
company_industry: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company industry filters (include/exclude)',
|
||||
},
|
||||
company_size: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company headcount brackets (e.g., ["1-10", "11-50", "51-200"])',
|
||||
},
|
||||
company_type: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company type (e.g., ["private", "public", "educational"])',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://wiza.co/api/prospects/search',
|
||||
method: 'POST',
|
||||
headers: (params: WizaProspectSearchParams) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: WizaProspectSearchParams) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
|
||||
if (typeof params.size === 'number') {
|
||||
body.size = Math.max(0, Math.min(params.size, 30))
|
||||
}
|
||||
|
||||
if (
|
||||
params.filters &&
|
||||
typeof params.filters === 'object' &&
|
||||
!Array.isArray(params.filters) &&
|
||||
Object.keys(params.filters).length > 0
|
||||
) {
|
||||
body.filters = params.filters
|
||||
return body
|
||||
}
|
||||
|
||||
const filters: Record<string, unknown> = {}
|
||||
const arrayKeys: Array<keyof WizaProspectSearchParams> = [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'job_title',
|
||||
'job_title_level',
|
||||
'job_role',
|
||||
'job_sub_role',
|
||||
'location',
|
||||
'skill',
|
||||
'school',
|
||||
'major',
|
||||
'linkedin_slug',
|
||||
'job_company',
|
||||
'past_company',
|
||||
'company_location',
|
||||
'company_industry',
|
||||
'company_size',
|
||||
'company_type',
|
||||
]
|
||||
|
||||
for (const key of arrayKeys) {
|
||||
const value = params[key]
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
filters[key as string] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(filters).length > 0) {
|
||||
body.filters = filters
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Wiza API error: ${response.status} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const payload = data.data ?? {}
|
||||
const profiles = Array.isArray(payload.profiles) ? payload.profiles : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total: payload.total ?? 0,
|
||||
profiles: profiles.map((p: Record<string, unknown>) => ({
|
||||
full_name: (p.full_name as string) ?? null,
|
||||
linkedin_url: (p.linkedin_url as string) ?? null,
|
||||
industry: (p.industry as string) ?? null,
|
||||
job_title: (p.job_title as string) ?? null,
|
||||
job_title_role: (p.job_title_role as string) ?? null,
|
||||
job_title_sub_role: (p.job_title_sub_role as string) ?? null,
|
||||
job_company_name: (p.job_company_name as string) ?? null,
|
||||
job_company_website: (p.job_company_website as string) ?? null,
|
||||
location_name: (p.location_name as string) ?? null,
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total: { type: 'number', description: 'Total number of matching prospects' },
|
||||
profiles: {
|
||||
type: 'array',
|
||||
description: 'Sample profiles matching the filter criteria',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
full_name: { type: 'string' },
|
||||
linkedin_url: { type: 'string' },
|
||||
industry: { type: 'string' },
|
||||
job_title: { type: 'string' },
|
||||
job_title_role: { type: 'string' },
|
||||
job_title_sub_role: { type: 'string' },
|
||||
job_company_name: { type: 'string' },
|
||||
job_company_website: { type: 'string' },
|
||||
location_name: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface WizaGetCreditsParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface WizaGetCreditsResponse extends ToolResponse {
|
||||
output: {
|
||||
email_credits: number | string | null
|
||||
phone_credits: number | string | null
|
||||
export_credits: number | null
|
||||
api_credits: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface WizaIncludeExcludeFilter {
|
||||
v: string
|
||||
s: 'i' | 'e'
|
||||
}
|
||||
|
||||
export interface WizaLocationFilter {
|
||||
v: string | { country?: string; state?: string; city?: string }
|
||||
b: 'country' | 'state' | 'city'
|
||||
s: 'i' | 'e'
|
||||
}
|
||||
|
||||
export interface WizaProspectSearchParams {
|
||||
apiKey: string
|
||||
size?: number
|
||||
filters?: Record<string, unknown>
|
||||
first_name?: string[]
|
||||
last_name?: string[]
|
||||
job_title?: WizaIncludeExcludeFilter[]
|
||||
job_title_level?: string[]
|
||||
job_role?: string[]
|
||||
job_sub_role?: string[]
|
||||
location?: WizaLocationFilter[]
|
||||
skill?: string[]
|
||||
school?: string[]
|
||||
major?: string[]
|
||||
linkedin_slug?: string[]
|
||||
job_company?: WizaIncludeExcludeFilter[]
|
||||
past_company?: WizaIncludeExcludeFilter[]
|
||||
company_location?: WizaLocationFilter[]
|
||||
company_industry?: WizaIncludeExcludeFilter[]
|
||||
company_size?: string[]
|
||||
company_type?: string[]
|
||||
}
|
||||
|
||||
interface WizaProspectProfile {
|
||||
full_name: string | null
|
||||
linkedin_url: string | null
|
||||
industry: string | null
|
||||
job_title: string | null
|
||||
job_title_role: string | null
|
||||
job_title_sub_role: string | null
|
||||
job_company_name: string | null
|
||||
job_company_website: string | null
|
||||
location_name: string | null
|
||||
}
|
||||
|
||||
export interface WizaProspectSearchResponse extends ToolResponse {
|
||||
output: {
|
||||
total: number
|
||||
profiles: WizaProspectProfile[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface WizaCompanyEnrichmentParams {
|
||||
apiKey: string
|
||||
company_name?: string
|
||||
company_domain?: string
|
||||
company_linkedin_id?: string
|
||||
company_linkedin_slug?: string
|
||||
}
|
||||
|
||||
export interface WizaCompanyEnrichmentResponse extends ToolResponse {
|
||||
output: {
|
||||
company_name: string | null
|
||||
company_domain: string | null
|
||||
domain: string | null
|
||||
company_industry: string | null
|
||||
company_size: number | null
|
||||
company_size_range: string | null
|
||||
company_founded: number | null
|
||||
company_revenue_range: string | null
|
||||
company_funding: string | null
|
||||
company_type: string | null
|
||||
company_description: string | null
|
||||
company_ticker: string | null
|
||||
company_last_funding_round: string | null
|
||||
company_last_funding_amount: string | null
|
||||
company_last_funding_at: string | null
|
||||
company_location: string | null
|
||||
company_twitter: string | null
|
||||
company_facebook: string | null
|
||||
company_linkedin: string | null
|
||||
company_linkedin_id: string | null
|
||||
company_street: string | null
|
||||
company_locality: string | null
|
||||
company_region: string | null
|
||||
company_postal_code: string | null
|
||||
company_country: string | null
|
||||
credits: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface WizaIndividualRevealParams {
|
||||
apiKey: string
|
||||
enrichment_level: 'none' | 'partial' | 'phone' | 'full'
|
||||
profile_url?: string
|
||||
full_name?: string
|
||||
company?: string
|
||||
domain?: string
|
||||
email?: string
|
||||
accept_work?: boolean
|
||||
accept_personal?: boolean
|
||||
}
|
||||
|
||||
export interface WizaIndividualRevealData {
|
||||
id: number | null
|
||||
status: string | null
|
||||
is_complete: boolean | null
|
||||
name: string | null
|
||||
company: string | null
|
||||
enrichment_level: string | null
|
||||
linkedin_profile_url: string | null
|
||||
title: string | null
|
||||
location: string | null
|
||||
email: string | null
|
||||
email_type: string | null
|
||||
email_status: string | null
|
||||
emails: Array<{
|
||||
email: string | null
|
||||
email_type: string | null
|
||||
email_status: string | null
|
||||
}>
|
||||
mobile_phone: string | null
|
||||
phone_number: string | null
|
||||
phone_status: string | null
|
||||
phones: Array<{
|
||||
number: string | null
|
||||
pretty_number: string | null
|
||||
type: string | null
|
||||
}>
|
||||
company_size: number | null
|
||||
company_size_range: string | null
|
||||
company_type: string | null
|
||||
company_domain: string | null
|
||||
company_locality: string | null
|
||||
company_region: string | null
|
||||
company_country: string | null
|
||||
company_street: string | null
|
||||
company_postal_code: string | null
|
||||
company_founded: number | null
|
||||
company_funding: string | null
|
||||
company_revenue: string | null
|
||||
company_industry: string | null
|
||||
company_subindustry: string | null
|
||||
company_linkedin: string | null
|
||||
company_location: string | null
|
||||
company_description: string | null
|
||||
credits: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export interface WizaIndividualRevealResponse extends ToolResponse {
|
||||
output: WizaIndividualRevealData
|
||||
}
|
||||
|
||||
export type WizaResponse =
|
||||
| WizaGetCreditsResponse
|
||||
| WizaProspectSearchResponse
|
||||
| WizaCompanyEnrichmentResponse
|
||||
| WizaIndividualRevealResponse
|
||||
Reference in New Issue
Block a user