feat: add ZoomInfo tool

old-agentic
Travis Fischer 2025-02-21 02:29:04 +07:00
rodzic 05d25e211d
commit 562fe2b9c3
10 zmienionych plików z 751 dodań i 10 usunięć

Wyświetl plik

@ -82,7 +82,8 @@
"tools/weather",
"tools/wikidata",
"tools/wikipedia",
"tools/wolfram-alpha"
"tools/wolfram-alpha",
"tools/zoominfo"
]
}
],

Wyświetl plik

@ -10,25 +10,25 @@ title: Quick Start
</Info>
<Steps>
<Step title='Install core deps'>
<Step title='Install core deps (zod)'>
<CodeGroup>
```bash npm
npm install @agentic/core zod
npm install zod
```
```bash yarn
yarn add @agentic/core zod
yarn add zod
```
```bash pnpm
pnpm add @agentic/core zod
pnpm add zod
```
</CodeGroup>
</Step>
<Step title='Install AI tools'>
You can either install all of the AI tools via the convenience package `@agentic/stdlib`, or you can install them individually via their respective packages (`@agentic/weather`, `@agentic/twitter`, etc.).
You can either install all of the AI tools via `@agentic/stdlib`, or you can install them individually via their respective packages (`@agentic/weather`, `@agentic/twitter`, etc.).
<AccordionGroup>
<Accordion title="Install all AI tools">
@ -72,7 +72,7 @@ title: Quick Start
<Note>
There is no functional difference between using `@agentic/stdlib` versus using the individual tool packages directly. The only difference is if you want to optimize your install size (when running on serverless functions, for instance).
The default examples all use `@agentic/stdlib` because it provides a simpler DX.
The default examples all use `@agentic/stdlib` for simplicity.
</Note>
</Step>
@ -138,15 +138,15 @@ title: Quick Start
<Accordion title="Firebase Genkit">
<CodeGroup>
```bash npm
npm install @agentic/genkit @genkit-ai/ai @genkit-ai/core
npm install @agentic/genkit genkit
```
```bash yarn
yarn add @agentic/genkit @genkit-ai/ai @genkit-ai/core
yarn add @agentic/genkit genkit
```
```bash pnpm
pnpm add @agentic/genkit @genkit-ai/ai @genkit-ai/core
pnpm add @agentic/genkit genkit
```
</CodeGroup>

Wyświetl plik

@ -0,0 +1,40 @@
---
title: ZoomInfo
description: ZoomInfo provides a powerful API for B2B person and company data enrichment.
---
- package: `@agentic/zoominfo`
- exports: `class ZoomInfo`, `namespace zoominfo`
- env vars:
- `ZOOMINFO_USERNAME` and `ZOOMINFO_PASSWORD` for basic auth
- `ZOOMINFO_USERNAME`, `ZOOMINFO_CLIENT_ID`, and `ZOOMINFO_PRIVATE_KEY` for PKI auth
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/wolfram-alpha/src/zoominfo-client.ts)
- [zoominfo api docs](https://api-docs.zoominfo.com)
## Install
<CodeGroup>
```bash npm
npm install @agentic/zoominfo
```
```bash yarn
yarn add @agentic/zoominfo
```
```bash pnpm
pnpm add @agentic/zoominfo
```
</CodeGroup>
## Usage
```ts
import { ZoomInfoClient } from '@agentic/zoominfo'
const zoomInfo = new ZoomInfoClient()
const res = await zoomInfo.enrichContact({
emailAddress: 'travis@transitivebullsh.it'
})
```

Wyświetl plik

@ -0,0 +1,50 @@
{
"name": "@agentic/zoominfo",
"version": "7.3.2",
"description": "Agentic SDK for ZoomInfo.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/zoominfo"
},
"type": "module",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup --config ../../tsup.config.ts",
"dev": "tsup --config ../../tsup.config.ts --watch",
"clean": "del dist",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:typecheck": "tsc --noEmit"
},
"dependencies": {
"@agentic/core": "workspace:*",
"jsrsasign": "^11.1.0",
"ky": "^1.7.5",
"p-throttle": "^6.2.0"
},
"peerDependencies": {
"zod": "^3.23.8"
},
"devDependencies": {
"@agentic/tsconfig": "workspace:*",
"@types/jsrsasign": "^10.5.15"
},
"publishConfig": {
"access": "public"
}
}

Wyświetl plik

@ -0,0 +1,24 @@
<p align="center">
<a href="https://agentic.so">
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
</a>
</p>
<p align="center">
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
</p>
<p align="center">
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
</p>
# Agentic
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
## License
MIT © [Travis Fischer](https://x.com/transitive_bs)

Wyświetl plik

@ -0,0 +1 @@
export * from './zoominfo-client'

Wyświetl plik

@ -0,0 +1,586 @@
import {
aiFunction,
AIFunctionsProvider,
assert,
getEnv,
throttleKy
} from '@agentic/core'
import { KJUR } from 'jsrsasign'
import defaultKy, { type KyInstance } from 'ky'
import pThrottle from 'p-throttle'
import { z } from 'zod'
export namespace zoominfo {
export const API_BASE_URL = 'https://api.zoominfo.com'
// Access tokens expire after 60 minutes, so renew them every 55 minutes.
export const ACCESS_TOKEN_EXPIRATION_MS = 55 * 60 * 1000
// Allow up to 25 requests per second by default.
// https://api-docs.zoominfo.com/#rate-and-usage-limits
export const throttle = pThrottle({
limit: 25,
interval: 1000
})
export interface EnrichContactOptions {
personId?: string
emailAddress?: string
hashedEmail?: string
phone?: string
firstName?: string
lastName?: string
companyId?: string
companyName?: string
fullName?: string
jobTitle?: string
externalURL?: string
lastUpdatedDateAfter?: string
validDateAfter?: string
contactAccuracyScoreMin?: number
}
export interface EnrichCompanyOptions {
companyId?: string // Unique ZoomInfo identifier for a company
companyName?: string // Company name
companyWebsite?: string // Company website URL in http://www.example.com format
companyTicker?: string // Company stock ticker symbol
companyPhone?: string // Phone number of the company headquarters
companyFax?: string // Fax number of the company headquarters
companyStreet?: string // Street address for the company's primary address
companyCity?: string // City for the company's primary address
companyState?: string // Company state (U.S.) or province (Canada). You can use free text state or province names (e.g., "new hampshire"), the two-letter common abbreviation for a U.S. state (e.g., "nh"), or values provided in the State lookup endpoint.
companyZipCode?: string // Zip Code or Postal Code for the company's primary address
companyCountry?: string // Country for the company's primary address. You can use free text or see the Country lookup endpoint for values.
ipAddress?: string // IP address associated with the company
}
export interface EnrichContactResponse {
success: boolean
data: {
outputFields: string[][]
result: EnrichContactResult[]
}
}
export interface EnrichContactResult {
input: Partial<EnrichContactOptions>
data: EnrichedContact[]
}
export interface EnrichedContact {
id: number
firstName: string
middleName: string
lastName: string
email: string
hasCanadianEmail: string
phone: string
directPhoneDoNotCall: boolean
street: string
city: string
region: string
metroArea: string
zipCode: string
state: string
country: string
personHasMoved: string
withinEu: boolean
withinCalifornia: boolean
withinCanada: boolean
lastUpdatedDate: string
noticeProvidedDate: string
salutation: string
suffix: string
jobTitle: string
jobFunction: JobFunction[]
education: Education[]
hashedEmails: string[]
picture: string
mobilePhoneDoNotCall: boolean
externalUrls: ExternalUrl[]
contactAccuracyScore: number
isDefunct: boolean
employmentHistory: EmploymentHistory[]
managementLevel: string[]
locationCompanyId: number
company: Company
}
export interface JobFunction {
name: string
department: string
}
export interface Education {
school: string
educationDegree: EducationDegree
}
export interface EducationDegree {
degree: string
areaOfStudy: string
}
export interface ExternalUrl {
type: string
url: string
}
export interface EmploymentHistory {
jobTitle: string
managementLevel: string[]
fromDate: string
toDate: string
company: {
companyId: number
companyName: string
companyPhone?: string
companyWebsite?: string
}
}
export interface Company {
id: number
name: string
type: string
division: string
descriptionList: DescriptionList[]
phone: string
fax: string
street: string
city: string
state: string
zipCode: string
country: string
logo: string
sicCodes: Code[]
naicsCodes: Code[]
website: string
revenue: string
revenueNumeric: number
employeeCount: number
ticker: string
ranking: string[]
socialMediaUrls: any[]
primaryIndustry: string[]
industries: string[]
revenueRange: string
employeeRange: string
}
export interface DescriptionList {
description: string
}
export interface Code {
id: string
name: string
}
export interface EnrichCompanyResponse {
success: boolean
data: {
outputFields: string[][]
result: EnrichCompanyResult[]
}
}
export interface EnrichCompanyResult {
input: Partial<EnrichCompanyOptions>
data: EnrichedCompany[]
}
export interface EnrichedCompany {
id: number
ticker: string
name: string
website: string
domainList: string[]
logo: string
socialMediaUrls: SocialMediaUrl[]
revenue: number
employeeCount: number
numberOfContactsInZoomInfo: number
phone: string
fax: string
street: string
city: string
state: string
zipCode: string
country: string
continent: string
companyStatus: string
companyStatusDate: string
descriptionList: DescriptionList[]
sicCodes: Code[]
naicsCodes: Code[]
competitors: Competitor[]
ultimateParentId: number
ultimateParentName: string
ultimateParentRevenue: number
ultimateParentEmployees: number
subUnitCodes: any[]
subUnitType: string
subUnitIndustries: string[]
primaryIndustry: string[]
industries: string[]
parentId: number
parentName: string
locationCount: number
alexaRank: string
metroArea: string
lastUpdatedDate: string
createdDate: string
certificationDate: string
certified: boolean
hashtags: Hashtag[]
products: any[]
techAttributes: TechAttribute[]
revenueRange: string
employeeRange: string
companyFunding: CompanyFunding[]
recentFundingAmount: number
recentFundingDate: string
totalFundingAmount: number
employeeGrowth: EmployeeGrowth
}
export interface SocialMediaUrl {
type: string
url: string
followerCount: string
}
export interface DescriptionList {
description: string
}
export interface Competitor {
rank: number
id: number
name: string
website: string
employeeCount: number
}
export interface Hashtag {
tag: string
external_id: any
searchString: string
displayLabel: string
description: string
group: string
score: any
priority?: number
parentCategory: string
displayScore: string
inverseScoreBase?: number
scoreMultipler: any
scoreUnit: string
hidden: boolean
label: string
categorizedFlag: boolean
}
export interface TechAttribute {
tag: string
categoryParent: string
category: string
vendor: string
product: string
attribute: string
website: string
logo?: string
domain?: string
createdTime: string
modifiedTime: string
description: string
}
export interface CompanyFunding {
date: string
type: string
amount: number
}
export interface EmployeeGrowth {
oneYearGrowthRate: string
twoYearGrowthRate: string
employeeGrowthDataPoints: EmployeeGrowthDataPoint[]
}
export interface EmployeeGrowthDataPoint {
label: string
employeeCount: number
}
}
/**
* ZoomInfo ia a robust B2B entity enrichment API.
*
* @see https://api-docs.zoominfo.com
*/
export class ZoomInfoClient extends AIFunctionsProvider {
protected readonly ky: KyInstance
protected readonly apiBaseUrl: string
protected readonly username: string
protected readonly password: string | undefined
protected readonly clientId: string | undefined
protected readonly privateKey: string | undefined
protected accessToken: string | undefined
protected accessTokenDateMS: number | undefined
constructor({
username = getEnv('ZOOMINFO_USERNAME'),
password = getEnv('ZOOMINFO_PASSWORD'),
clientId = getEnv('ZOOMINFO_CLIENT_ID'),
privateKey = getEnv('ZOOMINFO_PRIVATE_KEY'),
apiBaseUrl = zoominfo.API_BASE_URL,
timeoutMs = 60_000,
throttle = true,
ky = defaultKy
}: {
username?: string
password?: string
clientId?: string
privateKey?: string
apiBaseUrl?: string
apiKnowledgeGraphBaseUrl?: string
timeoutMs?: number
throttle?: boolean
ky?: KyInstance
} = {}) {
assert(
username,
`ZoomInfoClient missing required "username" (defaults to "ZOOMINFO_USERNAME")`
)
assert(
password || (clientId && privateKey),
`ZoomInfoClient missing required "password" for basic auth or "clientId" and "privateKey" for PKI auth (defaults to "ZOOMINFO_PASSWORD", "ZOOMINFO_CLIENT_ID", and "ZOOMINFO_PRIVATE_KEY")`
)
super()
this.username = username
this.password = password
this.clientId = clientId
this.privateKey = privateKey
this.apiBaseUrl = apiBaseUrl
const throttledKy = throttle ? throttleKy(ky, zoominfo.throttle) : ky
this.ky = throttledKy.extend({
prefixUrl: apiBaseUrl,
timeout: timeoutMs
})
}
async authenticate({
force = false
}: { force?: boolean } = {}): Promise<void> {
if (
!force &&
this.accessToken &&
this.accessTokenDateMS! + zoominfo.ACCESS_TOKEN_EXPIRATION_MS < Date.now()
) {
// Access token is still valid.
return
}
if (this.username && this.password) {
this.accessTokenDateMS = Date.now()
this.accessToken = await this.getAccessTokenViaBasicAuth({
username: this.username,
password: this.password
})
assert(
this.accessToken,
'ZoomInfo failed to get access token via basic auth'
)
return
}
if (this.username && this.clientId && this.privateKey) {
this.accessTokenDateMS = Date.now()
this.accessToken = await this.getAccessTokenViaPKI({
username: this.username,
clientId: this.clientId,
privateKey: this.privateKey
})
assert(
this.accessToken,
'ZoomInfo failed to get access token via PKI auth'
)
return
}
throw new Error(
'ZoomInfoClient missing required authentication credentials'
)
}
async getAccessTokenViaBasicAuth({
username,
password
}: {
username: string
password: string
}): Promise<string> {
const res = await this.ky
.post('authenticate', {
json: {
username,
password
}
})
.json<{ data: { jwt: string } }>()
return res.data.jwt
}
async getAccessTokenViaPKI({
username,
clientId,
privateKey
}: {
username: string
clientId: string
privateKey: string
}): Promise<string> {
const dtNow = Date.now()
const header = {
typ: 'JWT',
alg: 'RS256'
}
const data = {
iss: 'zoominfo-api-auth-client-nodejs',
aud: 'enterprise_api',
username,
client_id: clientId,
iat: getIAT(dtNow),
exp: getEXP(dtNow)
}
const sHeader = JSON.stringify(header)
const sPayload = JSON.stringify(data)
const clientJWT = KJUR.jws.JWS.sign(
header.alg,
sHeader,
sPayload,
privateKey
)
const res = await this.ky
.post('authenticate', {
headers: {
Authorization: `Bearer ${clientJWT}`
}
})
.json<{ data: { jwt: string } }>()
return res.data.jwt
}
@aiFunction({
name: 'zoominfo_enrich_contact',
description: `Attempts to enrich a person contact with ZoomInfo data. To match a contact, you must use one of the following combinations of parameters to construct your input:
personId OR emailAddress OR hashedEmail OR phone. Because these values are unique to a single person, you can use any one of these values to search without providing any additional parameters. You can optionally combine one of these values with a companyId/companyName.
firstName AND lastName AND companyId/companyName. Combining these values effectively results in a unique person.
fullName AND companyId/companyName. Combining these values effectively results in a unique person.`,
inputSchema: z.object({
firstName: z.string().optional().describe('First name of the person.'),
lastName: z.string().optional().describe('Last name of the person.'),
companyId: z
.string()
.optional()
.describe("Unique ZoomInfo identifier of the person's company."),
companyName: z
.string()
.optional()
.describe(
'Name of the company where the contact works, or has worked.'
),
personId: z
.string()
.optional()
.describe('Unique ZoomInfo identifier of the person.'),
emailAddress: z.string().optional(),
hashedEmail: z.string().optional(),
phone: z.string().optional(),
fullName: z.string().optional(),
jobTitle: z.string().optional(),
externalURL: z.string().optional(),
lastUpdatedDateAfter: z.string().optional(),
validDateAfter: z.string().optional(),
contactAccuracyScoreMin: z.number().optional()
})
})
async enrichContact(opts: zoominfo.EnrichContactOptions) {
await this.authenticate()
return this.ky
.post('enrich/contact', {
json: opts,
headers: {
Authorization: `Bearer ${this.accessToken}`
}
})
.json<zoominfo.EnrichContactResponse>()
}
@aiFunction({
name: 'zoominfo_enrich_company',
description:
'Attempts to enrich a company with ZoomInfo data. To match a company, you should ideally provide the `companyName` and `companyWebsite`.',
inputSchema: z.object({
companyId: z
.string()
.optional()
.describe('Unique ZoomInfo identifier of company.'),
companyName: z.string().optional().describe('Name of the company.'),
companyWebsite: z.string().optional(),
companyTicker: z.string().optional(),
companyPhone: z.string().optional(),
companyFax: z.string().optional(),
companyStreet: z.string().optional(),
companyCity: z.string().optional(),
companyState: z.string().optional(),
companyZipCode: z.string().optional(),
companyCountry: z.string().optional(),
ipAddress: z.string().optional()
})
})
async enrichCompany(opts: zoominfo.EnrichCompanyOptions) {
await this.authenticate()
return this.ky
.post('enrich/company', {
json: opts,
headers: {
Authorization: `Bearer ${this.accessToken}`
}
})
.json<zoominfo.EnrichCompanyResponse>()
}
}
function getIAT(dtNow: number) {
const iat = Math.floor(dtNow / 1000)
return iat - 60
}
function getEXP(dtNow: number) {
const exp = Math.floor(dtNow / 1000) + 5 * 60
return exp - 60
}

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@agentic/tsconfig/base.json",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -1038,6 +1038,28 @@ importers:
specifier: workspace:*
version: link:../tsconfig
packages/zoominfo:
dependencies:
'@agentic/core':
specifier: workspace:*
version: link:../core
jsrsasign:
specifier: ^11.1.0
version: 11.1.0
ky:
specifier: ^1.7.5
version: 1.7.5
p-throttle:
specifier: ^6.2.0
version: 6.2.0
devDependencies:
'@agentic/tsconfig':
specifier: workspace:*
version: link:../tsconfig
'@types/jsrsasign':
specifier: ^10.5.15
version: 10.5.15
packages:
'@ai-sdk/openai@1.1.13':
@ -2581,6 +2603,9 @@ packages:
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
'@types/jsrsasign@10.5.15':
resolution: {integrity: sha512-3stUTaSRtN09PPzVWR6aySD9gNnuymz+WviNHoTb85dKu+BjaV4uBbWWGykBBJkfwPtcNZVfTn2lbX00U+yhpQ==}
'@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
@ -4544,6 +4569,9 @@ packages:
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
engines: {node: '>=12', npm: '>=6'}
jsrsasign@11.1.0:
resolution: {integrity: sha512-Ov74K9GihaK9/9WncTe1mPmvrO7Py665TUfUKvraXBpu+xcTWitrtuOwcjf4KMU9maPaYn0OuaWy0HOzy/GBXg==}
jsx-ast-utils@3.3.5:
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
engines: {node: '>=4.0'}
@ -8764,6 +8792,8 @@ snapshots:
'@types/json5@0.0.29': {}
'@types/jsrsasign@10.5.15': {}
'@types/lodash-es@4.17.12':
dependencies:
'@types/lodash': 4.17.15
@ -11035,6 +11065,8 @@ snapshots:
ms: 2.1.3
semver: 7.7.1
jsrsasign@11.1.0: {}
jsx-ast-utils@3.3.5:
dependencies:
array-includes: 3.1.8

Wyświetl plik

@ -26,6 +26,7 @@
- [Firebase Genkit](#firebase-genkit)
- [Dexa Dexter](#dexa-dexter)
- [OpenAI](#openai)
- [GenAIScript](#genaiscript)
- [Tools](#tools)
- [Contributors](#contributors)
- [License](#license)
@ -177,6 +178,7 @@ Full docs are available at [agentic.so](https://agentic.so).
| [Wikidata](https://www.wikidata.org/wiki/Wikidata:Data_access) | `@agentic/wikidata` | [docs](https://agentic.so/tools/wikidata) | Basic Wikidata client. |
| [Wikipedia](https://www.mediawiki.org/wiki/API) | `@agentic/wikipedia` | [docs](https://agentic.so/tools/wikipedia) | Wikipedia page search and summaries. |
| [Wolfram Alpha](https://products.wolframalpha.com/llm-api/documentation) | `@agentic/wolfram-alpha` | [docs](https://agentic.so/tools/wolfram-alpha) | Wolfram Alpha LLM API client for answering computational, mathematical, and scientific questions. |
| [ZoomInfo](https://api-docs.zoominfo.com) | `@agentic/zoominfo` | [docs](https://agentic.so/tools/zoominfo) | Powerful B2B person and company data enrichment. |
For more details, see the [docs](https://agentic.so).