mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(hosted-keys): add Hunter.io and People Data Labs hosted key support (#4742)
* fix(db): disable statement_timeout for migrations * fix(ci): route migration workflow through guarded migrate.ts * feat(hosted-keys): add Hunter.io and People Data Labs hosted key support
This commit is contained in:
@@ -62,6 +62,15 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener
|
||||
# COHERE_API_KEY= # Cohere API key for the Knowledge block reranker (rerank-v4.0-pro/-fast, rerank-v3.5). Alternatively set COHERE_API_KEY_1/2/3 for rotation.
|
||||
# NEXT_PUBLIC_COHERE_CONFIGURED=true # Set when COHERE_API_KEY (or rotation keys) are pre-configured above. Hides the Cohere API Key field on the Knowledge block UI.
|
||||
|
||||
# Hosted tool API keys (Optional - lets Sim supply the key so users don't have to bring their own).
|
||||
# Each provider reads `{PREFIX}_COUNT` then `{PREFIX}_1..N`, distributing requests round-robin across the keys.
|
||||
# HUNTER_API_KEY_COUNT=2 # Number of Hunter.io keys for hosted Hunter blocks
|
||||
# HUNTER_API_KEY_1= # Hunter.io API key #1
|
||||
# HUNTER_API_KEY_2= # Hunter.io API key #2
|
||||
# PEOPLEDATALABS_API_KEY_COUNT=2 # Number of People Data Labs keys for hosted PDL blocks
|
||||
# PEOPLEDATALABS_API_KEY_1= # People Data Labs API key #1
|
||||
# PEOPLEDATALABS_API_KEY_2= # People Data Labs API key #2
|
||||
|
||||
# Admin API (Optional - for self-hosted GitOps)
|
||||
# ADMIN_API_KEY= # Use `openssl rand -hex 32` to generate. Enables admin API for workflow export/import.
|
||||
# Usage: curl -H "x-admin-key: your_key" https://your-instance/api/v1/admin/workspaces
|
||||
|
||||
@@ -23,12 +23,14 @@ import {
|
||||
FireworksIcon,
|
||||
GeminiIcon,
|
||||
GoogleIcon,
|
||||
HunterIOIcon,
|
||||
ImageIcon,
|
||||
JinaAIIcon,
|
||||
LinkupIcon,
|
||||
MistralIcon,
|
||||
OpenAIIcon,
|
||||
ParallelIcon,
|
||||
PeopleDataLabsIcon,
|
||||
PerplexityIcon,
|
||||
SerperIcon,
|
||||
} from '@/components/icons'
|
||||
@@ -156,6 +158,20 @@ const PROVIDERS: {
|
||||
description: 'Brand assets, logos, colors, and company info',
|
||||
placeholder: 'Enter your Brandfetch API key',
|
||||
},
|
||||
{
|
||||
id: 'hunter',
|
||||
name: 'Hunter',
|
||||
icon: HunterIOIcon,
|
||||
description: 'Email finder, verification, and domain search',
|
||||
placeholder: 'Enter your Hunter.io API key',
|
||||
},
|
||||
{
|
||||
id: 'peopledatalabs',
|
||||
name: 'People Data Labs',
|
||||
icon: PeopleDataLabsIcon,
|
||||
description: 'Person and company enrichment, search, and identity',
|
||||
placeholder: 'Enter your People Data Labs API key',
|
||||
},
|
||||
]
|
||||
|
||||
export function BYOK() {
|
||||
|
||||
@@ -256,6 +256,7 @@ Return ONLY the search query text - no explanations.`,
|
||||
required: true,
|
||||
placeholder: 'Enter your Hunter.io API key',
|
||||
password: true,
|
||||
hideWhenHosted: true,
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
|
||||
@@ -397,6 +397,7 @@ export const PeopleDataLabsBlock: BlockConfig<PdlPersonEnrichResponse> = {
|
||||
placeholder: 'Enter your People Data Labs API key',
|
||||
password: true,
|
||||
required: true,
|
||||
hideWhenHosted: true,
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ export const byokProviderIdSchema = z.enum([
|
||||
'parallel_ai',
|
||||
'brandfetch',
|
||||
'cohere',
|
||||
'hunter',
|
||||
'peopledatalabs',
|
||||
])
|
||||
|
||||
export const byokKeySchema = z.object({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { HunterEnrichmentParams, HunterEnrichmentResponse } from '@/tools/hunter/types'
|
||||
import { HUNTER_API_KEY_PREFIX } from '@/tools/hunter/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const companiesFindTool: ToolConfig<HunterEnrichmentParams, HunterEnrichmentResponse> = {
|
||||
@@ -7,6 +8,18 @@ export const companiesFindTool: ToolConfig<HunterEnrichmentParams, HunterEnrichm
|
||||
description: 'Enriches company data using domain name.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: HUNTER_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'hunter',
|
||||
// Companies Find (enrichment) is free on Hunter — no credits consumed.
|
||||
pricing: { type: 'per_request', cost: 0 },
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HunterDiscoverParams, HunterDiscoverResponse } from '@/tools/hunter/types'
|
||||
import { DISCOVER_RESULTS_OUTPUT } from '@/tools/hunter/types'
|
||||
import { DISCOVER_RESULTS_OUTPUT, HUNTER_API_KEY_PREFIX } from '@/tools/hunter/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const discoverTool: ToolConfig<HunterDiscoverParams, HunterDiscoverResponse> = {
|
||||
@@ -8,6 +8,18 @@ export const discoverTool: ToolConfig<HunterDiscoverParams, HunterDiscoverRespon
|
||||
description: 'Returns companies matching a set of criteria using Hunter.io AI-powered search.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: HUNTER_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'hunter',
|
||||
// Discover is free on Hunter — no credits consumed.
|
||||
pricing: { type: 'per_request', cost: 0 },
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 30,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
query: {
|
||||
type: 'string',
|
||||
|
||||
@@ -3,7 +3,11 @@ import type {
|
||||
HunterDomainSearchResponse,
|
||||
HunterEmail,
|
||||
} from '@/tools/hunter/types'
|
||||
import { EMAILS_OUTPUT } from '@/tools/hunter/types'
|
||||
import {
|
||||
EMAILS_OUTPUT,
|
||||
HUNTER_API_KEY_PREFIX,
|
||||
HUNTER_SEARCH_CREDIT_USD,
|
||||
} from '@/tools/hunter/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const domainSearchTool: ToolConfig<HunterDomainSearchParams, HunterDomainSearchResponse> = {
|
||||
@@ -12,6 +16,28 @@ export const domainSearchTool: ToolConfig<HunterDomainSearchParams, HunterDomain
|
||||
description: 'Returns all the email addresses found using one given domain name, with sources.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: HUNTER_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'hunter',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
getCost: (_params, output) => {
|
||||
const emails = output.emails
|
||||
if (!Array.isArray(emails)) {
|
||||
throw new Error('Hunter domain search response missing emails, cannot determine cost')
|
||||
}
|
||||
// Hunter counts one search credit only when a call returns at least one result.
|
||||
const cost = emails.length > 0 ? HUNTER_SEARCH_CREDIT_USD : 0
|
||||
return { cost, metadata: { credits: cost > 0 ? 1 : 0, emails: emails.length } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HunterEmailCountParams, HunterEmailCountResponse } from '@/tools/hunter/types'
|
||||
import { DEPARTMENT_OUTPUT, SENIORITY_OUTPUT } from '@/tools/hunter/types'
|
||||
import { DEPARTMENT_OUTPUT, HUNTER_API_KEY_PREFIX, SENIORITY_OUTPUT } from '@/tools/hunter/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const emailCountTool: ToolConfig<HunterEmailCountParams, HunterEmailCountResponse> = {
|
||||
@@ -8,6 +8,18 @@ export const emailCountTool: ToolConfig<HunterEmailCountParams, HunterEmailCount
|
||||
description: 'Returns the total number of email addresses found for a domain or company.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: HUNTER_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'hunter',
|
||||
// Email Count is free on Hunter — no credits consumed.
|
||||
pricing: { type: 'per_request', cost: 0 },
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { HunterEmailFinderParams, HunterEmailFinderResponse } from '@/tools/hunter/types'
|
||||
import { SOURCES_OUTPUT, VERIFICATION_OUTPUT } from '@/tools/hunter/types'
|
||||
import {
|
||||
HUNTER_API_KEY_PREFIX,
|
||||
HUNTER_SEARCH_CREDIT_USD,
|
||||
SOURCES_OUTPUT,
|
||||
VERIFICATION_OUTPUT,
|
||||
} from '@/tools/hunter/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const emailFinderTool: ToolConfig<HunterEmailFinderParams, HunterEmailFinderResponse> = {
|
||||
@@ -9,6 +14,25 @@ export const emailFinderTool: ToolConfig<HunterEmailFinderParams, HunterEmailFin
|
||||
'Finds the most likely email address for a person given their name and company domain.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: HUNTER_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'hunter',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
getCost: (_params, output) => {
|
||||
// Hunter counts one search credit only when an email is found.
|
||||
const found = typeof output.email === 'string' && output.email.length > 0
|
||||
const cost = found ? HUNTER_SEARCH_CREDIT_USD : 0
|
||||
return { cost, metadata: { credits: found ? 1 : 0 } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { HunterEmailVerifierParams, HunterEmailVerifierResponse } from '@/tools/hunter/types'
|
||||
import { SOURCES_OUTPUT } from '@/tools/hunter/types'
|
||||
import {
|
||||
HUNTER_API_KEY_PREFIX,
|
||||
HUNTER_VERIFICATION_CREDIT_USD,
|
||||
SOURCES_OUTPUT,
|
||||
} from '@/tools/hunter/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const emailVerifierTool: ToolConfig<HunterEmailVerifierParams, HunterEmailVerifierResponse> =
|
||||
@@ -10,6 +14,24 @@ export const emailVerifierTool: ToolConfig<HunterEmailVerifierParams, HunterEmai
|
||||
'Verifies the deliverability of an email address and provides detailed verification status.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: HUNTER_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'hunter',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
// The verifier always consumes one verification (0.5 credit), regardless of result.
|
||||
getCost: () => ({
|
||||
cost: HUNTER_VERIFICATION_CREDIT_USD,
|
||||
metadata: { verifications: 1 },
|
||||
}),
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
// Common types for Hunter.io tools
|
||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Env var prefix for Sim-hosted Hunter.io keys.
|
||||
* Resolved at runtime as `HUNTER_API_KEY_COUNT` + `HUNTER_API_KEY_1..N`.
|
||||
*/
|
||||
export const HUNTER_API_KEY_PREFIX = 'HUNTER_API_KEY'
|
||||
|
||||
/**
|
||||
* Dollar cost per Hunter.io credit when billing hosted-key usage.
|
||||
*
|
||||
* Hunter does not report per-call credit consumption in its responses, so cost is
|
||||
* derived from the response: a "search" credit is consumed only when a call returns
|
||||
* at least one result; a verification consumes half a credit.
|
||||
*
|
||||
* Estimate based on the Growth plan ($149/mo for 10,000 credits ≈ $0.015/credit) —
|
||||
* see https://hunter.io/pricing. Tune if Sim's plan rate changes.
|
||||
*/
|
||||
export const HUNTER_SEARCH_CREDIT_USD = 0.015
|
||||
export const HUNTER_VERIFICATION_CREDIT_USD = 0.0075
|
||||
|
||||
/**
|
||||
* Shared output property definitions for Hunter.io API responses.
|
||||
* These are reusable across all Hunter tools to ensure consistency.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PdlAutocompleteParams, PdlAutocompleteResponse } from '@/tools/peopledatalabs/types'
|
||||
import { PEOPLEDATALABS_API_KEY_PREFIX } from '@/tools/peopledatalabs/types'
|
||||
import { buildQueryString } from '@/tools/peopledatalabs/utils'
|
||||
import type { OutputProperty, ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -19,6 +20,18 @@ export const autocompleteTool: ToolConfig<PdlAutocompleteParams, PdlAutocomplete
|
||||
'Get autocomplete suggestions for a PDL field (title, skill, company, industry, location, school, major, role, sub_role).',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
// Autocomplete is free on PDL (rate-limited only) — no credits consumed.
|
||||
pricing: { type: 'per_request', cost: 0 },
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -3,7 +3,8 @@ import type {
|
||||
PdlBulkCompanyEnrichResponse,
|
||||
PdlBulkCompanyResultItem,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { projectCompany } from '@/tools/peopledatalabs/utils'
|
||||
import { PDL_CREDIT_USD, PEOPLEDATALABS_API_KEY_PREFIX } from '@/tools/peopledatalabs/types'
|
||||
import { countBulkMatched, projectCompany } from '@/tools/peopledatalabs/utils'
|
||||
import type { OutputProperty, ToolConfig } from '@/tools/types'
|
||||
|
||||
const BULK_COMPANY_RESULT_PROPERTIES = {
|
||||
@@ -32,6 +33,31 @@ export const bulkCompanyEnrichTool: ToolConfig<
|
||||
'Enrich up to 100 companies in a single call. Provide a JSON array of request objects, each with a `params` object. Reference: https://docs.peopledatalabs.com/docs/bulk-company-enrichment-api',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
// PDL charges 1 credit per matched record; unmatched records in the batch are free.
|
||||
getCost: (_params, output) => {
|
||||
const matched = countBulkMatched(output)
|
||||
return { cost: matched * PDL_CREDIT_USD, metadata: { credits: matched } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'custom',
|
||||
requestsPerMinute: 10,
|
||||
dimensions: [
|
||||
{
|
||||
name: 'credits',
|
||||
limitPerMinute: 600,
|
||||
extractUsage: (_params, response) => countBulkMatched(response),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -3,7 +3,8 @@ import type {
|
||||
PdlBulkPersonEnrichResponse,
|
||||
PdlBulkPersonResultItem,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { projectPerson } from '@/tools/peopledatalabs/utils'
|
||||
import { PDL_CREDIT_USD, PEOPLEDATALABS_API_KEY_PREFIX } from '@/tools/peopledatalabs/types'
|
||||
import { countBulkMatched, projectPerson } from '@/tools/peopledatalabs/utils'
|
||||
import type { OutputProperty, ToolConfig } from '@/tools/types'
|
||||
|
||||
const BULK_PERSON_RESULT_PROPERTIES = {
|
||||
@@ -32,6 +33,31 @@ export const bulkPersonEnrichTool: ToolConfig<
|
||||
'Enrich up to 100 person records in a single call. Provide a JSON array of request objects, each with a `params` object (and optional `metadata` echoed back). Reference: https://docs.peopledatalabs.com/docs/bulk-person-enrichment-api',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
// PDL charges 1 credit per matched record; unmatched records in the batch are free.
|
||||
getCost: (_params, output) => {
|
||||
const matched = countBulkMatched(output)
|
||||
return { cost: matched * PDL_CREDIT_USD, metadata: { credits: matched } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'custom',
|
||||
requestsPerMinute: 10,
|
||||
dimensions: [
|
||||
{
|
||||
name: 'credits',
|
||||
limitPerMinute: 600,
|
||||
extractUsage: (_params, response) => countBulkMatched(response),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { PdlCleanCompanyParams, PdlCleanCompanyResponse } from '@/tools/peopledatalabs/types'
|
||||
import { PDL_COMPANY_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
|
||||
import {
|
||||
PDL_COMPANY_OUTPUT_PROPERTIES,
|
||||
PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { projectCompany } from '@/tools/peopledatalabs/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -10,6 +13,18 @@ export const cleanCompanyTool: ToolConfig<PdlCleanCompanyParams, PdlCleanCompany
|
||||
'Normalize a company string into a canonical company record. Provide at least one of name, website, or profile (LinkedIn URL).',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
// Cleaner APIs are free on PDL (rate-limited only) — no credits consumed.
|
||||
pricing: { type: 'per_request', cost: 0 },
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { PdlCompanyEnrichParams, PdlCompanyEnrichResponse } from '@/tools/peopledatalabs/types'
|
||||
import { PDL_COMPANY_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
|
||||
import {
|
||||
PDL_COMPANY_OUTPUT_PROPERTIES,
|
||||
PDL_CREDIT_USD,
|
||||
PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { buildQueryString, projectCompany } from '@/tools/peopledatalabs/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -10,6 +14,24 @@ export const companyEnrichTool: ToolConfig<PdlCompanyEnrichParams, PdlCompanyEnr
|
||||
'Enrich a single company using People Data Labs. Match by name, website, LinkedIn URL, ticker, or PDL ID.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
// PDL charges 1 credit per matched request; misses (404) are free.
|
||||
getCost: (_params, output) => {
|
||||
const cost = output.matched === true ? PDL_CREDIT_USD : 0
|
||||
return { cost, metadata: { credits: output.matched === true ? 1 : 0 } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { PdlCompanySearchParams, PdlCompanySearchResponse } from '@/tools/peopledatalabs/types'
|
||||
import { PDL_COMPANY_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
|
||||
import {
|
||||
PDL_COMPANY_OUTPUT_PROPERTIES,
|
||||
PDL_CREDIT_USD,
|
||||
PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { projectCompany } from '@/tools/peopledatalabs/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -10,6 +14,35 @@ export const companySearchTool: ToolConfig<PdlCompanySearchParams, PdlCompanySea
|
||||
'Search the People Data Labs company dataset using SQL or Elasticsearch DSL. Returns up to 100 matching companies per call.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
// PDL Search charges 1 credit per company returned (up to the requested size).
|
||||
getCost: (_params, output) => {
|
||||
const results = output.results
|
||||
if (!Array.isArray(results)) {
|
||||
throw new Error('PDL company search response missing results, cannot determine cost')
|
||||
}
|
||||
return { cost: results.length * PDL_CREDIT_USD, metadata: { credits: results.length } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'custom',
|
||||
requestsPerMinute: 30,
|
||||
dimensions: [
|
||||
{
|
||||
name: 'credits',
|
||||
limitPerMinute: 600,
|
||||
extractUsage: (_params, response) =>
|
||||
Array.isArray(response.results) ? response.results.length : 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { PdlCleanLocationParams, PdlCleanLocationResponse } from '@/tools/peopledatalabs/types'
|
||||
import { PDL_LOCATION_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
|
||||
import {
|
||||
PDL_LOCATION_OUTPUT_PROPERTIES,
|
||||
PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { projectLocation } from '@/tools/peopledatalabs/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -10,6 +13,18 @@ export const cleanLocationTool: ToolConfig<PdlCleanLocationParams, PdlCleanLocat
|
||||
'Normalize a freeform location string into a structured locality/region/country record with coordinates.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
// Cleaner APIs are free on PDL (rate-limited only) — no credits consumed.
|
||||
pricing: { type: 'per_request', cost: 0 },
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { PdlPersonEnrichParams, PdlPersonEnrichResponse } from '@/tools/peopledatalabs/types'
|
||||
import { PDL_PERSON_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
|
||||
import {
|
||||
PDL_CREDIT_USD,
|
||||
PDL_PERSON_OUTPUT_PROPERTIES,
|
||||
PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { buildQueryString, projectPerson } from '@/tools/peopledatalabs/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -10,6 +14,24 @@ export const personEnrichTool: ToolConfig<PdlPersonEnrichParams, PdlPersonEnrich
|
||||
'Enrich a single person profile using People Data Labs. Match by email, phone, LinkedIn URL, or name + company/location. Returns work history, contact details, location, and skills.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
// PDL charges 1 credit per matched request; misses (404) are free.
|
||||
getCost: (_params, output) => {
|
||||
const cost = output.matched === true ? PDL_CREDIT_USD : 0
|
||||
return { cost, metadata: { credits: output.matched === true ? 1 : 0 } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -2,7 +2,11 @@ import type {
|
||||
PdlPersonIdentifyParams,
|
||||
PdlPersonIdentifyResponse,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { PDL_PERSON_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
|
||||
import {
|
||||
PDL_CREDIT_USD,
|
||||
PDL_PERSON_OUTPUT_PROPERTIES,
|
||||
PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { buildQueryString, projectPerson } from '@/tools/peopledatalabs/utils'
|
||||
import type { OutputProperty, ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -28,6 +32,28 @@ export const personIdentifyTool: ToolConfig<PdlPersonIdentifyParams, PdlPersonId
|
||||
'Return up to 20 candidate person matches with confidence scores. Useful when you want to see all plausible matches rather than the single best one. Reference: https://docs.peopledatalabs.com/docs/identify-api-quickstart',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
// PDL Identify charges 1 credit per call that returns matches, regardless of count.
|
||||
getCost: (_params, output) => {
|
||||
const matches = output.matches
|
||||
if (!Array.isArray(matches)) {
|
||||
throw new Error('PDL identify response missing matches, cannot determine cost')
|
||||
}
|
||||
const cost = matches.length > 0 ? PDL_CREDIT_USD : 0
|
||||
return { cost, metadata: { credits: matches.length > 0 ? 1 : 0 } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { PdlPersonSearchParams, PdlPersonSearchResponse } from '@/tools/peopledatalabs/types'
|
||||
import { PDL_PERSON_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
|
||||
import {
|
||||
PDL_CREDIT_USD,
|
||||
PDL_PERSON_OUTPUT_PROPERTIES,
|
||||
PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { projectPerson } from '@/tools/peopledatalabs/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -10,6 +14,35 @@ export const personSearchTool: ToolConfig<PdlPersonSearchParams, PdlPersonSearch
|
||||
'Search the People Data Labs person dataset using SQL or Elasticsearch DSL. Returns up to 100 matching records per call.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
// PDL Search charges 1 credit per profile returned (up to the requested size).
|
||||
getCost: (_params, output) => {
|
||||
const results = output.results
|
||||
if (!Array.isArray(results)) {
|
||||
throw new Error('PDL person search response missing results, cannot determine cost')
|
||||
}
|
||||
return { cost: results.length * PDL_CREDIT_USD, metadata: { credits: results.length } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'custom',
|
||||
requestsPerMinute: 30,
|
||||
dimensions: [
|
||||
{
|
||||
name: 'credits',
|
||||
limitPerMinute: 600,
|
||||
extractUsage: (_params, response) =>
|
||||
Array.isArray(response.results) ? response.results.length : 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { PdlCleanSchoolParams, PdlCleanSchoolResponse } from '@/tools/peopledatalabs/types'
|
||||
import { PDL_SCHOOL_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
|
||||
import {
|
||||
PDL_SCHOOL_OUTPUT_PROPERTIES,
|
||||
PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
import { projectSchool } from '@/tools/peopledatalabs/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -10,6 +13,18 @@ export const cleanSchoolTool: ToolConfig<PdlCleanSchoolParams, PdlCleanSchoolRes
|
||||
'Normalize a school string into a canonical school record. Provide at least one of name, website, or profile (LinkedIn URL).',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'peopledatalabs',
|
||||
// Cleaner APIs are free on PDL (rate-limited only) — no credits consumed.
|
||||
pricing: { type: 'per_request', cost: 0 },
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Env var prefix for Sim-hosted People Data Labs keys.
|
||||
* Resolved at runtime as `PEOPLEDATALABS_API_KEY_COUNT` + `PEOPLEDATALABS_API_KEY_1..N`.
|
||||
*/
|
||||
export const PEOPLEDATALABS_API_KEY_PREFIX = 'PEOPLEDATALABS_API_KEY'
|
||||
|
||||
/**
|
||||
* Dollar cost per People Data Labs credit when billing hosted-key usage.
|
||||
*
|
||||
* PDL does not report credits in its responses, so cost is derived from match counts:
|
||||
* enrich/identify consume 1 credit per matched request, search consumes 1 credit per
|
||||
* profile returned, and bulk consumes 1 credit per matched record. Misses (HTTP 404)
|
||||
* are not charged. Cleaner and autocomplete endpoints are free (rate-limited only).
|
||||
*
|
||||
* Estimate based on the self-serve Pro plan (~$0.28/credit) — see
|
||||
* https://www.peopledatalabs.com/pricing. Tune if Sim's plan rate changes.
|
||||
*/
|
||||
export const PDL_CREDIT_USD = 0.28
|
||||
|
||||
/**
|
||||
* Output property definitions for People Data Labs API responses.
|
||||
* Reference: https://docs.peopledatalabs.com/docs
|
||||
|
||||
@@ -5,6 +5,20 @@ import type {
|
||||
PdlSchoolRecord,
|
||||
} from '@/tools/peopledatalabs/types'
|
||||
|
||||
/**
|
||||
* Count matched records in a bulk enrich output `results` array.
|
||||
* PDL charges one credit per matched record, so this drives both hosted-key
|
||||
* cost and the post-execution credit rate-limit dimension.
|
||||
*/
|
||||
export function countBulkMatched(output: Record<string, unknown>): number {
|
||||
const results = output.results
|
||||
if (!Array.isArray(results)) return 0
|
||||
return results.reduce((count, item) => {
|
||||
const matched = (item as { matched?: unknown }).matched
|
||||
return matched === true ? count + 1 : count
|
||||
}, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a query string from non-empty params.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,8 @@ export type BYOKProviderId =
|
||||
| 'brandfetch'
|
||||
| 'parallel_ai'
|
||||
| 'cohere'
|
||||
| 'hunter'
|
||||
| 'peopledatalabs'
|
||||
|
||||
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user