From 562fe2b9c3a2a5c5d297b1c004f775217dd32196 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Fri, 21 Feb 2025 02:29:04 +0700 Subject: [PATCH] feat: add ZoomInfo tool --- legacy/docs/mint.json | 3 +- legacy/docs/quickstart.mdx | 18 +- legacy/docs/tools/zoominfo.mdx | 40 ++ legacy/packages/zoominfo/package.json | 50 ++ legacy/packages/zoominfo/readme.md | 24 + legacy/packages/zoominfo/src/index.ts | 1 + .../packages/zoominfo/src/zoominfo-client.ts | 586 ++++++++++++++++++ legacy/packages/zoominfo/tsconfig.json | 5 + legacy/pnpm-lock.yaml | 32 + legacy/readme.md | 2 + 10 files changed, 751 insertions(+), 10 deletions(-) create mode 100644 legacy/docs/tools/zoominfo.mdx create mode 100644 legacy/packages/zoominfo/package.json create mode 100644 legacy/packages/zoominfo/readme.md create mode 100644 legacy/packages/zoominfo/src/index.ts create mode 100644 legacy/packages/zoominfo/src/zoominfo-client.ts create mode 100644 legacy/packages/zoominfo/tsconfig.json diff --git a/legacy/docs/mint.json b/legacy/docs/mint.json index 3c8f4232..be7d837e 100644 --- a/legacy/docs/mint.json +++ b/legacy/docs/mint.json @@ -82,7 +82,8 @@ "tools/weather", "tools/wikidata", "tools/wikipedia", - "tools/wolfram-alpha" + "tools/wolfram-alpha", + "tools/zoominfo" ] } ], diff --git a/legacy/docs/quickstart.mdx b/legacy/docs/quickstart.mdx index 0c3cf723..569cbc4c 100644 --- a/legacy/docs/quickstart.mdx +++ b/legacy/docs/quickstart.mdx @@ -10,25 +10,25 @@ title: Quick Start - + ```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 ``` - 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.). @@ -72,7 +72,7 @@ title: Quick Start 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. @@ -138,15 +138,15 @@ title: Quick Start ```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 ``` diff --git a/legacy/docs/tools/zoominfo.mdx b/legacy/docs/tools/zoominfo.mdx new file mode 100644 index 00000000..4d97aa7f --- /dev/null +++ b/legacy/docs/tools/zoominfo.mdx @@ -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 + + +```bash npm +npm install @agentic/zoominfo +``` + +```bash yarn +yarn add @agentic/zoominfo +``` + +```bash pnpm +pnpm add @agentic/zoominfo +``` + + + +## Usage + +```ts +import { ZoomInfoClient } from '@agentic/zoominfo' + +const zoomInfo = new ZoomInfoClient() +const res = await zoomInfo.enrichContact({ + emailAddress: 'travis@transitivebullsh.it' +}) +``` diff --git a/legacy/packages/zoominfo/package.json b/legacy/packages/zoominfo/package.json new file mode 100644 index 00000000..2068b66f --- /dev/null +++ b/legacy/packages/zoominfo/package.json @@ -0,0 +1,50 @@ +{ + "name": "@agentic/zoominfo", + "version": "7.3.2", + "description": "Agentic SDK for ZoomInfo.", + "author": "Travis Fischer ", + "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" + } +} diff --git a/legacy/packages/zoominfo/readme.md b/legacy/packages/zoominfo/readme.md new file mode 100644 index 00000000..38781f32 --- /dev/null +++ b/legacy/packages/zoominfo/readme.md @@ -0,0 +1,24 @@ +

+ + Agentic + +

+ +

+ AI agent stdlib that works with any LLM and TypeScript AI SDK. +

+ +

+ Build Status + NPM + MIT License + Prettier Code Formatting +

+ +# 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) diff --git a/legacy/packages/zoominfo/src/index.ts b/legacy/packages/zoominfo/src/index.ts new file mode 100644 index 00000000..4b41ba66 --- /dev/null +++ b/legacy/packages/zoominfo/src/index.ts @@ -0,0 +1 @@ +export * from './zoominfo-client' diff --git a/legacy/packages/zoominfo/src/zoominfo-client.ts b/legacy/packages/zoominfo/src/zoominfo-client.ts new file mode 100644 index 00000000..2f59e15d --- /dev/null +++ b/legacy/packages/zoominfo/src/zoominfo-client.ts @@ -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 + 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 + 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 { + 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 { + 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 { + 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() + } + + @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() + } +} + +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 +} diff --git a/legacy/packages/zoominfo/tsconfig.json b/legacy/packages/zoominfo/tsconfig.json new file mode 100644 index 00000000..6c8d720c --- /dev/null +++ b/legacy/packages/zoominfo/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@agentic/tsconfig/base.json", + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/legacy/pnpm-lock.yaml b/legacy/pnpm-lock.yaml index c9377247..6d64a1d3 100644 --- a/legacy/pnpm-lock.yaml +++ b/legacy/pnpm-lock.yaml @@ -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 diff --git a/legacy/readme.md b/legacy/readme.md index 6cbaaa47..509146a7 100644 --- a/legacy/readme.md +++ b/legacy/readme.md @@ -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).