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,136 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
ZoomInfoEnrichCompaniesParams,
|
||||
ZoomInfoEnrichCompaniesResponse,
|
||||
} from '@/tools/zoominfo/types'
|
||||
import {
|
||||
buildProxyBody,
|
||||
extractDataArray,
|
||||
parseCsvOrJson,
|
||||
parseJsonField,
|
||||
transformZoomInfoEnvelope,
|
||||
ZOOMINFO_PROXY_URL,
|
||||
} from '@/tools/zoominfo/utils'
|
||||
|
||||
/**
|
||||
* Default output fields used when the caller does not specify any. ZoomInfo's
|
||||
* CompanyEnrich schema requires `outputFields`, so we send a useful firmographic
|
||||
* set rather than letting the request fail. All values are valid CompanyEnrich fields.
|
||||
*/
|
||||
const DEFAULT_COMPANY_OUTPUT_FIELDS = [
|
||||
'id',
|
||||
'name',
|
||||
'website',
|
||||
'domainList',
|
||||
'ticker',
|
||||
'revenue',
|
||||
'revenueRange',
|
||||
'employeeCount',
|
||||
'employeeRange',
|
||||
'primaryIndustry',
|
||||
'industries',
|
||||
'street',
|
||||
'city',
|
||||
'state',
|
||||
'zipCode',
|
||||
'country',
|
||||
'phone',
|
||||
'foundedYear',
|
||||
'companyStatus',
|
||||
'socialMediaUrls',
|
||||
'logo',
|
||||
'description',
|
||||
]
|
||||
|
||||
export const zoominfoEnrichCompaniesTool: ToolConfig<
|
||||
ZoomInfoEnrichCompaniesParams,
|
||||
ZoomInfoEnrichCompaniesResponse
|
||||
> = {
|
||||
id: 'zoominfo_enrich_companies',
|
||||
name: 'ZoomInfo Enrich Companies',
|
||||
description:
|
||||
'Enrich up to 25 companies in one request with detailed firmographics, industry, financials, and more.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
clientId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client ID',
|
||||
},
|
||||
clientSecret: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client secret',
|
||||
},
|
||||
matchCompanyInput: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array (1-25 items) of company matching criteria, e.g. [{"companyName":"Acme","companyWebsite":"acme.com"}]',
|
||||
},
|
||||
outputFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array or comma-separated list of fields to return (e.g. ["id","name","website","revenue","employeeCount"]). Defaults to a standard firmographic set if omitted.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: ZOOMINFO_PROXY_URL,
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => {
|
||||
const matchCompanyInput = parseJsonField<unknown>(
|
||||
params.matchCompanyInput,
|
||||
'matchCompanyInput'
|
||||
)
|
||||
if (!Array.isArray(matchCompanyInput) || matchCompanyInput.length === 0) {
|
||||
throw new Error('matchCompanyInput must be a non-empty JSON array')
|
||||
}
|
||||
if (matchCompanyInput.length > 25) {
|
||||
throw new Error('matchCompanyInput supports a maximum of 25 entries per request')
|
||||
}
|
||||
|
||||
const outputFields = parseCsvOrJson(params.outputFields, 'outputFields')
|
||||
const attributes: Record<string, unknown> = {
|
||||
matchCompanyInput,
|
||||
outputFields: outputFields ?? DEFAULT_COMPANY_OUTPUT_FIELDS,
|
||||
}
|
||||
|
||||
return {
|
||||
...buildProxyBody(params),
|
||||
path: '/data/v1/companies/enrich',
|
||||
method: 'POST',
|
||||
body: {
|
||||
data: {
|
||||
type: 'CompanyEnrich',
|
||||
attributes,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const { data } = await transformZoomInfoEnvelope(response)
|
||||
const results = extractDataArray(data)
|
||||
return {
|
||||
success: true,
|
||||
output: { results },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'Enrichment results, one per input with match status and attributes',
|
||||
items: { type: 'json' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
ZoomInfoEnrichContactsParams,
|
||||
ZoomInfoEnrichContactsResponse,
|
||||
} from '@/tools/zoominfo/types'
|
||||
import {
|
||||
buildProxyBody,
|
||||
extractDataArray,
|
||||
parseCsvOrJson,
|
||||
parseJsonField,
|
||||
transformZoomInfoEnvelope,
|
||||
ZOOMINFO_PROXY_URL,
|
||||
} from '@/tools/zoominfo/utils'
|
||||
|
||||
/**
|
||||
* Default output fields used when the caller does not specify any. ZoomInfo's
|
||||
* ContactEnrich schema requires `outputFields`, so we send a useful contact set
|
||||
* rather than letting the request fail. All values are valid ContactEnrich fields.
|
||||
*/
|
||||
const DEFAULT_CONTACT_OUTPUT_FIELDS = [
|
||||
'id',
|
||||
'firstName',
|
||||
'lastName',
|
||||
'email',
|
||||
'phone',
|
||||
'mobilePhone',
|
||||
'jobTitle',
|
||||
'jobFunction',
|
||||
'managementLevel',
|
||||
'city',
|
||||
'state',
|
||||
'country',
|
||||
'contactAccuracyScore',
|
||||
'validDate',
|
||||
'lastUpdatedDate',
|
||||
'companyId',
|
||||
'companyName',
|
||||
'companyWebsite',
|
||||
'companyPhone',
|
||||
]
|
||||
|
||||
export const zoominfoEnrichContactsTool: ToolConfig<
|
||||
ZoomInfoEnrichContactsParams,
|
||||
ZoomInfoEnrichContactsResponse
|
||||
> = {
|
||||
id: 'zoominfo_enrich_contacts',
|
||||
name: 'ZoomInfo Enrich Contacts',
|
||||
description:
|
||||
'Enrich up to 25 contacts in one request with verified emails, phone numbers, job details, and more.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
clientId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client ID',
|
||||
},
|
||||
clientSecret: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client secret',
|
||||
},
|
||||
matchPersonInput: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array (1-25 items) of contact matching criteria, e.g. [{"firstName":"Jane","lastName":"Doe","companyName":"Acme"}]',
|
||||
},
|
||||
outputFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array or comma-separated list of fields to return (e.g. ["id","firstName","email","phone","jobTitle"]). Defaults to a standard contact set if omitted.',
|
||||
},
|
||||
requiredFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array or comma-separated list of fields that must exist in results (e.g. ["email"])',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: ZOOMINFO_PROXY_URL,
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => {
|
||||
const matchPersonInput = parseJsonField<unknown>(params.matchPersonInput, 'matchPersonInput')
|
||||
if (!Array.isArray(matchPersonInput) || matchPersonInput.length === 0) {
|
||||
throw new Error('matchPersonInput must be a non-empty JSON array')
|
||||
}
|
||||
if (matchPersonInput.length > 25) {
|
||||
throw new Error('matchPersonInput supports a maximum of 25 entries per request')
|
||||
}
|
||||
|
||||
const outputFields = parseCsvOrJson(params.outputFields, 'outputFields')
|
||||
const attributes: Record<string, unknown> = {
|
||||
matchPersonInput,
|
||||
outputFields: outputFields ?? DEFAULT_CONTACT_OUTPUT_FIELDS,
|
||||
}
|
||||
const requiredFields = parseCsvOrJson(params.requiredFields, 'requiredFields')
|
||||
if (requiredFields) attributes.requiredFields = requiredFields
|
||||
|
||||
return {
|
||||
...buildProxyBody(params),
|
||||
path: '/data/v1/contacts/enrich',
|
||||
method: 'POST',
|
||||
body: {
|
||||
data: {
|
||||
type: 'ContactEnrich',
|
||||
attributes,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const { data } = await transformZoomInfoEnvelope(response)
|
||||
const results = extractDataArray(data)
|
||||
return {
|
||||
success: true,
|
||||
output: { results },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'Enrichment results, one per input with match status and attributes',
|
||||
items: { type: 'json' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export { zoominfoEnrichCompaniesTool } from '@/tools/zoominfo/enrich_companies'
|
||||
export { zoominfoEnrichContactsTool } from '@/tools/zoominfo/enrich_contacts'
|
||||
export { zoominfoSearchCompaniesTool } from '@/tools/zoominfo/search_companies'
|
||||
export { zoominfoSearchContactsTool } from '@/tools/zoominfo/search_contacts'
|
||||
export { zoominfoSearchIntentTool } from '@/tools/zoominfo/search_intent'
|
||||
export { zoominfoSearchNewsTool } from '@/tools/zoominfo/search_news'
|
||||
@@ -0,0 +1,213 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
ZoomInfoSearchCompaniesParams,
|
||||
ZoomInfoSearchCompaniesResponse,
|
||||
} from '@/tools/zoominfo/types'
|
||||
import {
|
||||
buildProxyBody,
|
||||
extractDataArray,
|
||||
extractPagination,
|
||||
paginationOutputProperties,
|
||||
parseCsvOrJson,
|
||||
toCsvStringOrUndefined,
|
||||
toNumberOrUndefined,
|
||||
transformZoomInfoEnvelope,
|
||||
ZOOMINFO_PROXY_URL,
|
||||
} from '@/tools/zoominfo/utils'
|
||||
|
||||
export const zoominfoSearchCompaniesTool: ToolConfig<
|
||||
ZoomInfoSearchCompaniesParams,
|
||||
ZoomInfoSearchCompaniesResponse
|
||||
> = {
|
||||
id: 'zoominfo_search_companies',
|
||||
name: 'ZoomInfo Search Companies',
|
||||
description: 'Search the ZoomInfo company database by name, industry, location, and size.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
clientId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client ID',
|
||||
},
|
||||
clientSecret: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client secret',
|
||||
},
|
||||
companyName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company name to search for',
|
||||
},
|
||||
companyWebsite: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company website (comma-separated for multiple)',
|
||||
},
|
||||
companyTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Stock ticker symbols — JSON array, comma-separated list, or single ticker. Sent to the API as an array.',
|
||||
},
|
||||
industryCodes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Industry codes — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country name',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'State or province',
|
||||
},
|
||||
metroRegion: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'US/Canada metro region',
|
||||
},
|
||||
revenueMin: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum annual revenue in thousands USD',
|
||||
},
|
||||
revenueMax: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum annual revenue in thousands USD',
|
||||
},
|
||||
employeeRangeMin: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum employee count',
|
||||
},
|
||||
employeeRangeMax: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum employee count',
|
||||
},
|
||||
excludeDefunctCompanies: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Exclude inactive companies',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (1-based)',
|
||||
},
|
||||
rpp: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (1-100, default 25)',
|
||||
},
|
||||
sortBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Field to sort by',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order (asc or desc)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: ZOOMINFO_PROXY_URL,
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => {
|
||||
const attributes: Record<string, unknown> = {}
|
||||
if (params.companyName) attributes.companyName = params.companyName
|
||||
if (params.companyWebsite) attributes.companyWebsite = params.companyWebsite
|
||||
const companyTicker = parseCsvOrJson(params.companyTicker, 'companyTicker')
|
||||
if (companyTicker) attributes.companyTicker = companyTicker
|
||||
const industryCodes = toCsvStringOrUndefined(params.industryCodes, 'industryCodes')
|
||||
if (industryCodes) attributes.industryCodes = industryCodes
|
||||
if (params.country) attributes.country = params.country
|
||||
if (params.state) attributes.state = params.state
|
||||
if (params.metroRegion) attributes.metroRegion = params.metroRegion
|
||||
const revenueMin = toNumberOrUndefined(params.revenueMin)
|
||||
if (revenueMin !== undefined) attributes.revenueMin = revenueMin
|
||||
const revenueMax = toNumberOrUndefined(params.revenueMax)
|
||||
if (revenueMax !== undefined) attributes.revenueMax = revenueMax
|
||||
const employeeRangeMin = toNumberOrUndefined(params.employeeRangeMin)
|
||||
if (employeeRangeMin !== undefined) attributes.employeeRangeMin = String(employeeRangeMin)
|
||||
const employeeRangeMax = toNumberOrUndefined(params.employeeRangeMax)
|
||||
if (employeeRangeMax !== undefined) attributes.employeeRangeMax = String(employeeRangeMax)
|
||||
if (params.excludeDefunctCompanies !== undefined) {
|
||||
attributes.excludeDefunctCompanies = params.excludeDefunctCompanies
|
||||
}
|
||||
|
||||
const query: Record<string, string | number> = {}
|
||||
const page = toNumberOrUndefined(params.page)
|
||||
const rpp = toNumberOrUndefined(params.rpp)
|
||||
if (page !== undefined) query['page[number]'] = page
|
||||
if (rpp !== undefined) query['page[size]'] = rpp
|
||||
if (params.sortBy) {
|
||||
const order = params.sortOrder === 'desc' ? '-' : ''
|
||||
query.sort = `${order}${params.sortBy}`
|
||||
}
|
||||
|
||||
return {
|
||||
...buildProxyBody(params),
|
||||
path: '/data/v1/companies/search',
|
||||
method: 'POST',
|
||||
query: Object.keys(query).length > 0 ? query : undefined,
|
||||
body: {
|
||||
data: {
|
||||
type: 'CompanySearch',
|
||||
attributes,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const { data } = await transformZoomInfoEnvelope(response)
|
||||
const companies = extractDataArray(data)
|
||||
const pagination = extractPagination(data)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
companies,
|
||||
...pagination,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
companies: {
|
||||
type: 'array',
|
||||
description: 'Matching companies',
|
||||
items: { type: 'json' },
|
||||
},
|
||||
...paginationOutputProperties,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
ZoomInfoSearchContactsParams,
|
||||
ZoomInfoSearchContactsResponse,
|
||||
} from '@/tools/zoominfo/types'
|
||||
import {
|
||||
buildProxyBody,
|
||||
extractDataArray,
|
||||
extractPagination,
|
||||
paginationOutputProperties,
|
||||
toCsvStringOrUndefined,
|
||||
toNumberOrUndefined,
|
||||
transformZoomInfoEnvelope,
|
||||
ZOOMINFO_PROXY_URL,
|
||||
} from '@/tools/zoominfo/utils'
|
||||
|
||||
export const zoominfoSearchContactsTool: ToolConfig<
|
||||
ZoomInfoSearchContactsParams,
|
||||
ZoomInfoSearchContactsResponse
|
||||
> = {
|
||||
id: 'zoominfo_search_contacts',
|
||||
name: 'ZoomInfo Search Contacts',
|
||||
description:
|
||||
'Search ZoomInfo for contacts (people) by name, job title, company, and other filters. Does not return emails or phone numbers — use Enrich Contacts for engagement data.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
clientId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client ID',
|
||||
},
|
||||
clientSecret: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client secret',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'First name',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Last name',
|
||||
},
|
||||
fullName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Full name',
|
||||
},
|
||||
emailAddress: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address',
|
||||
},
|
||||
jobTitle: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Job title',
|
||||
},
|
||||
managementLevel: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Management level — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
|
||||
},
|
||||
department: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Department — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
|
||||
},
|
||||
companyId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ZoomInfo company ID',
|
||||
},
|
||||
companyName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company name',
|
||||
},
|
||||
contactAccuracyScoreMin: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum accuracy score (70-99)',
|
||||
},
|
||||
requiredFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Fields that must exist in results — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
|
||||
},
|
||||
excludePartialProfiles: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Exclude partial profiles',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (1-based)',
|
||||
},
|
||||
rpp: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (1-100, default 25)',
|
||||
},
|
||||
sortBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Field to sort by',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order (asc or desc)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: ZOOMINFO_PROXY_URL,
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => {
|
||||
const attributes: Record<string, unknown> = {}
|
||||
if (params.firstName) attributes.firstName = params.firstName
|
||||
if (params.lastName) attributes.lastName = params.lastName
|
||||
if (params.fullName) attributes.fullName = params.fullName
|
||||
if (params.emailAddress) attributes.emailAddress = params.emailAddress
|
||||
if (params.jobTitle) attributes.jobTitle = params.jobTitle
|
||||
const managementLevel = toCsvStringOrUndefined(params.managementLevel, 'managementLevel')
|
||||
if (managementLevel) attributes.managementLevel = managementLevel
|
||||
const department = toCsvStringOrUndefined(params.department, 'department')
|
||||
if (department) attributes.department = department
|
||||
if (params.companyId) attributes.companyId = params.companyId
|
||||
if (params.companyName) attributes.companyName = params.companyName
|
||||
const minScore = toNumberOrUndefined(params.contactAccuracyScoreMin)
|
||||
if (minScore !== undefined) attributes.contactAccuracyScoreMin = String(minScore)
|
||||
const required = toCsvStringOrUndefined(params.requiredFields, 'requiredFields')
|
||||
if (required) attributes.requiredFields = required
|
||||
if (params.excludePartialProfiles !== undefined) {
|
||||
attributes.excludePartialProfiles = params.excludePartialProfiles
|
||||
}
|
||||
|
||||
const query: Record<string, string | number> = {}
|
||||
const page = toNumberOrUndefined(params.page)
|
||||
const rpp = toNumberOrUndefined(params.rpp)
|
||||
if (page !== undefined) query['page[number]'] = page
|
||||
if (rpp !== undefined) query['page[size]'] = rpp
|
||||
if (params.sortBy) {
|
||||
const order = params.sortOrder === 'desc' ? '-' : ''
|
||||
query.sort = `${order}${params.sortBy}`
|
||||
}
|
||||
|
||||
return {
|
||||
...buildProxyBody(params),
|
||||
path: '/data/v1/contacts/search',
|
||||
method: 'POST',
|
||||
query: Object.keys(query).length > 0 ? query : undefined,
|
||||
body: {
|
||||
data: {
|
||||
type: 'ContactSearch',
|
||||
attributes,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const { data } = await transformZoomInfoEnvelope(response)
|
||||
const contacts = extractDataArray(data)
|
||||
const pagination = extractPagination(data)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contacts,
|
||||
...pagination,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
contacts: {
|
||||
type: 'array',
|
||||
description: 'Matching contacts (without emails or phone numbers)',
|
||||
items: { type: 'json' },
|
||||
},
|
||||
...paginationOutputProperties,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
ZoomInfoSearchIntentParams,
|
||||
ZoomInfoSearchIntentResponse,
|
||||
} from '@/tools/zoominfo/types'
|
||||
import {
|
||||
buildProxyBody,
|
||||
extractDataArray,
|
||||
extractPagination,
|
||||
paginationOutputProperties,
|
||||
parseCsvOrJson,
|
||||
toCsvStringOrUndefined,
|
||||
toNumberOrUndefined,
|
||||
transformZoomInfoEnvelope,
|
||||
ZOOMINFO_PROXY_URL,
|
||||
} from '@/tools/zoominfo/utils'
|
||||
|
||||
export const zoominfoSearchIntentTool: ToolConfig<
|
||||
ZoomInfoSearchIntentParams,
|
||||
ZoomInfoSearchIntentResponse
|
||||
> = {
|
||||
id: 'zoominfo_search_intent',
|
||||
name: 'ZoomInfo Search Intent',
|
||||
description: 'Search for companies showing intent signals on specific topics.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
clientId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client ID',
|
||||
},
|
||||
clientSecret: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client secret',
|
||||
},
|
||||
topics: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Up to 50 intent topics as JSON array or comma-separated list (e.g. ["CRM Software","Marketing Automation"])',
|
||||
},
|
||||
signalStartDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Earliest signal date (YYYY-MM-DD)',
|
||||
},
|
||||
signalEndDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Latest signal date (YYYY-MM-DD)',
|
||||
},
|
||||
signalScoreMin: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum signal score (60-100)',
|
||||
},
|
||||
signalScoreMax: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum signal score (60-100)',
|
||||
},
|
||||
audienceStrengthMin: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum audience strength (A-E, A is largest)',
|
||||
},
|
||||
audienceStrengthMax: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum audience strength (A-E, A is largest)',
|
||||
},
|
||||
findRecommendedContacts: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include recommended contacts (default true)',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country filter',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'State filter',
|
||||
},
|
||||
industryCodes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Industry codes — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (1-based)',
|
||||
},
|
||||
rpp: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (1-100, default 25)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: ZOOMINFO_PROXY_URL,
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => {
|
||||
const topics = parseCsvOrJson(params.topics, 'topics')
|
||||
if (!topics || topics.length === 0) {
|
||||
throw new Error('topics is required')
|
||||
}
|
||||
const attributes: Record<string, unknown> = { topics }
|
||||
if (params.signalStartDate) attributes.signalStartDate = params.signalStartDate
|
||||
if (params.signalEndDate) attributes.signalEndDate = params.signalEndDate
|
||||
const scoreMin = toNumberOrUndefined(params.signalScoreMin)
|
||||
if (scoreMin !== undefined) attributes.signalScoreMin = scoreMin
|
||||
const scoreMax = toNumberOrUndefined(params.signalScoreMax)
|
||||
if (scoreMax !== undefined) attributes.signalScoreMax = scoreMax
|
||||
if (params.audienceStrengthMin) attributes.audienceStrengthMin = params.audienceStrengthMin
|
||||
if (params.audienceStrengthMax) attributes.audienceStrengthMax = params.audienceStrengthMax
|
||||
if (params.findRecommendedContacts !== undefined) {
|
||||
attributes.findRecommendedContacts = params.findRecommendedContacts
|
||||
}
|
||||
if (params.country) attributes.country = params.country
|
||||
if (params.state) attributes.state = params.state
|
||||
const industryCodes = toCsvStringOrUndefined(params.industryCodes, 'industryCodes')
|
||||
if (industryCodes) attributes.industryCodes = industryCodes
|
||||
|
||||
const query: Record<string, string | number> = {}
|
||||
const page = toNumberOrUndefined(params.page)
|
||||
const rpp = toNumberOrUndefined(params.rpp)
|
||||
if (page !== undefined) query['page[number]'] = page
|
||||
if (rpp !== undefined) query['page[size]'] = rpp
|
||||
|
||||
return {
|
||||
...buildProxyBody(params),
|
||||
path: '/data/v1/intent/search',
|
||||
method: 'POST',
|
||||
query: Object.keys(query).length > 0 ? query : undefined,
|
||||
body: {
|
||||
data: {
|
||||
type: 'IntentSearch',
|
||||
attributes,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const { data } = await transformZoomInfoEnvelope(response)
|
||||
const signals = extractDataArray(data)
|
||||
const pagination = extractPagination(data)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
signals,
|
||||
...pagination,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
signals: {
|
||||
type: 'array',
|
||||
description: 'Intent signals with topic, score, audience strength, and company',
|
||||
items: { type: 'json' },
|
||||
},
|
||||
...paginationOutputProperties,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ZoomInfoSearchNewsParams, ZoomInfoSearchNewsResponse } from '@/tools/zoominfo/types'
|
||||
import {
|
||||
buildProxyBody,
|
||||
extractDataArray,
|
||||
extractPagination,
|
||||
paginationOutputProperties,
|
||||
parseCsvOrJson,
|
||||
toNumberOrUndefined,
|
||||
transformZoomInfoEnvelope,
|
||||
ZOOMINFO_PROXY_URL,
|
||||
} from '@/tools/zoominfo/utils'
|
||||
|
||||
export const zoominfoSearchNewsTool: ToolConfig<
|
||||
ZoomInfoSearchNewsParams,
|
||||
ZoomInfoSearchNewsResponse
|
||||
> = {
|
||||
id: 'zoominfo_search_news',
|
||||
name: 'ZoomInfo Search News',
|
||||
description: 'Search ZoomInfo news articles by category, URL, or date range.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
clientId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client ID',
|
||||
},
|
||||
clientSecret: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ZoomInfo OAuth client secret',
|
||||
},
|
||||
categories: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'News categories as JSON array or comma-separated list',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'News source URLs as JSON array or comma-separated list',
|
||||
},
|
||||
pageDateMin: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Earliest publish date (YYYY-MM-DD)',
|
||||
},
|
||||
pageDateMax: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Latest publish date (YYYY-MM-DD)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (1-based)',
|
||||
},
|
||||
rpp: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (1-100, default 25)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: ZOOMINFO_PROXY_URL,
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => {
|
||||
const attributes: Record<string, unknown> = {}
|
||||
const categories = parseCsvOrJson(params.categories, 'categories')
|
||||
if (categories) attributes.categories = categories
|
||||
const urls = parseCsvOrJson(params.url, 'url')
|
||||
if (urls) attributes.url = urls
|
||||
if (params.pageDateMin) attributes.pageDateMin = params.pageDateMin
|
||||
if (params.pageDateMax) attributes.pageDateMax = params.pageDateMax
|
||||
|
||||
if (Object.keys(attributes).length === 0) {
|
||||
throw new Error('Provide at least one of: categories, url, pageDateMin, pageDateMax')
|
||||
}
|
||||
|
||||
const query: Record<string, string | number> = {}
|
||||
const page = toNumberOrUndefined(params.page)
|
||||
const rpp = toNumberOrUndefined(params.rpp)
|
||||
if (page !== undefined) query['page[number]'] = page
|
||||
if (rpp !== undefined) query['page[size]'] = rpp
|
||||
|
||||
return {
|
||||
...buildProxyBody(params),
|
||||
path: '/data/v1/news/search',
|
||||
method: 'POST',
|
||||
query: Object.keys(query).length > 0 ? query : undefined,
|
||||
body: {
|
||||
data: {
|
||||
type: 'NewsSearch',
|
||||
attributes,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const { data } = await transformZoomInfoEnvelope(response)
|
||||
const articles = extractDataArray(data)
|
||||
const pagination = extractPagination(data)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
articles,
|
||||
...pagination,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
articles: {
|
||||
type: 'array',
|
||||
description: 'News articles matching the filters',
|
||||
items: { type: 'json' },
|
||||
},
|
||||
...paginationOutputProperties,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface ZoomInfoBaseParams {
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
}
|
||||
|
||||
export interface ZoomInfoSearchCompaniesParams extends ZoomInfoBaseParams {
|
||||
companyName?: string
|
||||
companyWebsite?: string
|
||||
companyTicker?: string
|
||||
industryCodes?: string
|
||||
country?: string
|
||||
state?: string
|
||||
metroRegion?: string
|
||||
revenueMin?: number
|
||||
revenueMax?: number
|
||||
employeeRangeMin?: number
|
||||
employeeRangeMax?: number
|
||||
excludeDefunctCompanies?: boolean
|
||||
page?: number
|
||||
rpp?: number
|
||||
sortBy?: string
|
||||
sortOrder?: string
|
||||
}
|
||||
|
||||
export interface ZoomInfoSearchContactsParams extends ZoomInfoBaseParams {
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
fullName?: string
|
||||
emailAddress?: string
|
||||
jobTitle?: string
|
||||
managementLevel?: string
|
||||
department?: string
|
||||
companyId?: string
|
||||
companyName?: string
|
||||
contactAccuracyScoreMin?: number
|
||||
requiredFields?: string
|
||||
excludePartialProfiles?: boolean
|
||||
page?: number
|
||||
rpp?: number
|
||||
sortBy?: string
|
||||
sortOrder?: string
|
||||
}
|
||||
|
||||
export interface ZoomInfoEnrichCompaniesParams extends ZoomInfoBaseParams {
|
||||
matchCompanyInput: string
|
||||
outputFields?: string
|
||||
}
|
||||
|
||||
export interface ZoomInfoEnrichContactsParams extends ZoomInfoBaseParams {
|
||||
matchPersonInput: string
|
||||
outputFields?: string
|
||||
requiredFields?: string
|
||||
}
|
||||
|
||||
export interface ZoomInfoSearchIntentParams extends ZoomInfoBaseParams {
|
||||
topics: string
|
||||
signalStartDate?: string
|
||||
signalEndDate?: string
|
||||
signalScoreMin?: number
|
||||
signalScoreMax?: number
|
||||
audienceStrengthMin?: string
|
||||
audienceStrengthMax?: string
|
||||
findRecommendedContacts?: boolean
|
||||
country?: string
|
||||
state?: string
|
||||
industryCodes?: string
|
||||
page?: number
|
||||
rpp?: number
|
||||
}
|
||||
|
||||
export interface ZoomInfoSearchNewsParams extends ZoomInfoBaseParams {
|
||||
categories?: string
|
||||
url?: string
|
||||
pageDateMin?: string
|
||||
pageDateMax?: string
|
||||
page?: number
|
||||
rpp?: number
|
||||
}
|
||||
|
||||
export interface ZoomInfoSearchCompaniesResponse extends ToolResponse {
|
||||
output: {
|
||||
companies: Array<Record<string, unknown>>
|
||||
totalResults: number | null
|
||||
currentPage: number | null
|
||||
totalPages: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ZoomInfoSearchContactsResponse extends ToolResponse {
|
||||
output: {
|
||||
contacts: Array<Record<string, unknown>>
|
||||
totalResults: number | null
|
||||
currentPage: number | null
|
||||
totalPages: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ZoomInfoEnrichCompaniesResponse extends ToolResponse {
|
||||
output: {
|
||||
results: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ZoomInfoEnrichContactsResponse extends ToolResponse {
|
||||
output: {
|
||||
results: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ZoomInfoSearchIntentResponse extends ToolResponse {
|
||||
output: {
|
||||
signals: Array<Record<string, unknown>>
|
||||
totalResults: number | null
|
||||
currentPage: number | null
|
||||
totalPages: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ZoomInfoSearchNewsResponse extends ToolResponse {
|
||||
output: {
|
||||
articles: Array<Record<string, unknown>>
|
||||
totalResults: number | null
|
||||
currentPage: number | null
|
||||
totalPages: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export type ZoomInfoResponse =
|
||||
| ZoomInfoSearchCompaniesResponse
|
||||
| ZoomInfoSearchContactsResponse
|
||||
| ZoomInfoEnrichCompaniesResponse
|
||||
| ZoomInfoEnrichContactsResponse
|
||||
| ZoomInfoSearchIntentResponse
|
||||
| ZoomInfoSearchNewsResponse
|
||||
@@ -0,0 +1,136 @@
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import type { OutputProperty } from '@/tools/types'
|
||||
import type { ZoomInfoBaseParams } from '@/tools/zoominfo/types'
|
||||
|
||||
export const ZOOMINFO_PROXY_URL = '/api/tools/zoominfo/proxy'
|
||||
|
||||
export interface ZoomInfoProxyEnvelope {
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
path: string
|
||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
||||
query?: Record<string, string | number | boolean>
|
||||
body?: unknown
|
||||
}
|
||||
|
||||
export function buildProxyBody(
|
||||
params: ZoomInfoBaseParams
|
||||
): Pick<ZoomInfoProxyEnvelope, 'clientId' | 'clientSecret'> {
|
||||
return {
|
||||
clientId: params.clientId,
|
||||
clientSecret: params.clientSecret,
|
||||
}
|
||||
}
|
||||
|
||||
export function parseJsonField<T>(value: unknown, fieldName: string): T {
|
||||
if (typeof value !== 'string') return value as T
|
||||
const trimmed = value.trim()
|
||||
if (!trimmed) {
|
||||
throw new Error(`${fieldName} is required`)
|
||||
}
|
||||
try {
|
||||
return JSON.parse(trimmed) as T
|
||||
} catch (error) {
|
||||
throw new Error(`${fieldName} must be valid JSON: ${getErrorMessage(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a JSON-array string, real array, or comma-separated string into a
|
||||
* single comma-separated string. Use for ZoomInfo attributes that the docs
|
||||
* describe as a scalar string accepting a comma-separated list
|
||||
* (e.g. industryCodes, managementLevel, department).
|
||||
*/
|
||||
export function toCsvStringOrUndefined(value: unknown, fieldName: string): string | undefined {
|
||||
const arr = parseCsvOrJson(value, fieldName)
|
||||
if (!arr || arr.length === 0) return undefined
|
||||
return arr.join(',')
|
||||
}
|
||||
|
||||
export function parseCsvOrJson(value: unknown, fieldName: string): string[] | undefined {
|
||||
if (value === undefined || value === null) return undefined
|
||||
if (Array.isArray(value)) return value.map(String)
|
||||
if (typeof value !== 'string') return undefined
|
||||
const trimmed = value.trim()
|
||||
if (!trimmed) return undefined
|
||||
if (trimmed.startsWith('[')) {
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed)
|
||||
if (!Array.isArray(parsed)) {
|
||||
throw new Error(`${fieldName} JSON must be an array of strings`)
|
||||
}
|
||||
return parsed.map(String)
|
||||
} catch (error) {
|
||||
throw new Error(`${fieldName} must be valid JSON: ${getErrorMessage(error)}`)
|
||||
}
|
||||
}
|
||||
return trimmed
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
export function toNumberOrUndefined(value: unknown): number | undefined {
|
||||
if (value === undefined || value === null || value === '') return undefined
|
||||
const n = Number(value)
|
||||
return Number.isFinite(n) ? n : undefined
|
||||
}
|
||||
|
||||
export async function transformZoomInfoEnvelope(
|
||||
response: Response
|
||||
): Promise<{ status: number; data: unknown }> {
|
||||
const data = (await response.json()) as
|
||||
| { success: true; output: { status: number; data: unknown } }
|
||||
| { success: false; error?: string; status?: number }
|
||||
if (!('success' in data) || data.success === false) {
|
||||
const errMessage = 'error' in data && data.error ? data.error : 'ZoomInfo request failed'
|
||||
throw new Error(errMessage)
|
||||
}
|
||||
return { status: data.output.status, data: data.output.data }
|
||||
}
|
||||
|
||||
export const paginationOutputProperties: Record<string, OutputProperty> = {
|
||||
totalResults: {
|
||||
type: 'number',
|
||||
description: 'Total number of matching results across all pages',
|
||||
optional: true,
|
||||
},
|
||||
currentPage: {
|
||||
type: 'number',
|
||||
description: 'Current page number',
|
||||
optional: true,
|
||||
},
|
||||
totalPages: {
|
||||
type: 'number',
|
||||
description: 'Total number of pages available',
|
||||
optional: true,
|
||||
},
|
||||
}
|
||||
|
||||
export function extractPagination(payload: unknown): {
|
||||
totalResults: number | null
|
||||
currentPage: number | null
|
||||
totalPages: number | null
|
||||
} {
|
||||
if (payload && typeof payload === 'object') {
|
||||
const meta = (payload as Record<string, unknown>).meta as
|
||||
| { totalResults?: unknown; page?: { number?: unknown; total?: unknown } }
|
||||
| undefined
|
||||
if (meta) {
|
||||
const totalResults = typeof meta.totalResults === 'number' ? meta.totalResults : null
|
||||
const currentPage =
|
||||
meta.page && typeof meta.page.number === 'number' ? meta.page.number : null
|
||||
const totalPages = meta.page && typeof meta.page.total === 'number' ? meta.page.total : null
|
||||
return { totalResults, currentPage, totalPages }
|
||||
}
|
||||
}
|
||||
return { totalResults: null, currentPage: null, totalPages: null }
|
||||
}
|
||||
|
||||
export function extractDataArray(payload: unknown): Array<Record<string, unknown>> {
|
||||
if (payload && typeof payload === 'object') {
|
||||
const data = (payload as Record<string, unknown>).data
|
||||
if (Array.isArray(data)) return data as Array<Record<string, unknown>>
|
||||
}
|
||||
return []
|
||||
}
|
||||
Reference in New Issue
Block a user