chore: import upstream snapshot with attribution
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) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,115 @@
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'
const SUGGESTION_PROPERTIES = {
name: { type: 'string', description: 'Suggestion value' },
count: { type: 'number', description: 'Number of records matching this value' },
meta: {
type: 'object',
description: 'Field-specific metadata (e.g., for `company`: id, website, industry)',
optional: true,
},
} as const satisfies Record<string, OutputProperty>
export const autocompleteTool: ToolConfig<PdlAutocompleteParams, PdlAutocompleteResponse> = {
id: 'pdl_autocomplete',
name: 'PDL Autocomplete',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
field: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Field to autocomplete: all_location, class, company, country, industry, location_name, major, region, role, school, sub_role, skill, title, website',
},
text: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search text prefix',
},
size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of suggestions to return (1-100, default 10)',
},
titlecase: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Return name fields in title case',
},
},
request: {
url: (params) => {
const qs = buildQueryString({
field: params.field,
text: params.text,
size: params.size,
titlecase: params.titlecase,
})
return `https://api.peopledatalabs.com/v5/autocomplete${qs}`
},
method: 'GET',
headers: (params) => ({
'X-Api-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as Record<string, unknown>
if (!response.ok) {
const error = (data.error as { message?: string })?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const items =
(data.data as { name: string; count: number; meta?: Record<string, unknown> }[]) ?? []
return {
success: true,
output: {
suggestions: items.map((item) => ({
name: item.name,
count: item.count ?? 0,
meta: item.meta ?? undefined,
})),
},
}
},
outputs: {
suggestions: {
type: 'array',
description: 'Autocomplete suggestions ordered by frequency',
items: { type: 'object', properties: SUGGESTION_PROPERTIES },
},
},
}
@@ -0,0 +1,138 @@
import type {
PdlBulkCompanyEnrichParams,
PdlBulkCompanyEnrichResponse,
PdlBulkCompanyResultItem,
} from '@/tools/peopledatalabs/types'
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 = {
status: { type: 'number', description: 'Per-record HTTP status (200 on match)' },
matched: { type: 'boolean', description: 'Whether this record was matched' },
likelihood: {
type: 'number',
description: 'Match likelihood (1-10), null if no match',
optional: true,
},
metadata: {
type: 'object',
description: 'Metadata echoed back from the request',
optional: true,
},
company: { type: 'object', description: 'Matched company record', optional: true },
} as const satisfies Record<string, OutputProperty>
export const bulkCompanyEnrichTool: ToolConfig<
PdlBulkCompanyEnrichParams,
PdlBulkCompanyEnrichResponse
> = {
id: 'pdl_bulk_company_enrich',
name: 'PDL Bulk Company Enrich',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
requests: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of request objects, max 100. Each item: { "params": { name | website | profile | ticker | pdl_id }, "metadata": {...optional...} }',
},
required: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Required-fields expression applied globally to every request',
},
},
request: {
url: () => 'https://api.peopledatalabs.com/v5/company/enrich/bulk',
method: 'POST',
headers: (params) => ({
'X-Api-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
let parsed: unknown
try {
parsed = JSON.parse(params.requests)
} catch {
throw new Error('`requests` must be valid JSON (an array of request objects)')
}
if (!Array.isArray(parsed)) {
throw new Error('`requests` must be a JSON array')
}
const body: Record<string, unknown> = { requests: parsed }
if (params.required) body.required = params.required
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
const error = (data as { error?: { message?: string } })?.error?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const items = (Array.isArray(data) ? data : []) as Array<Record<string, unknown>>
const results: PdlBulkCompanyResultItem[] = items.map((item) => {
const status = (item.status as number) ?? 0
const record = (item.data as Record<string, unknown>) ?? null
return {
status,
likelihood: (item.likelihood as number) ?? null,
matched: status === 200 && record !== null,
metadata: (item.metadata as Record<string, unknown>) ?? null,
company: record ? projectCompany(record) : null,
}
})
return { success: true, output: { results } }
},
outputs: {
results: {
type: 'array',
description: 'Per-record results in the same order as the input requests',
items: { type: 'object', properties: BULK_COMPANY_RESULT_PROPERTIES },
},
},
}
@@ -0,0 +1,138 @@
import type {
PdlBulkPersonEnrichParams,
PdlBulkPersonEnrichResponse,
PdlBulkPersonResultItem,
} from '@/tools/peopledatalabs/types'
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 = {
status: { type: 'number', description: 'Per-record HTTP status (200 on match)' },
matched: { type: 'boolean', description: 'Whether this record was matched' },
likelihood: {
type: 'number',
description: 'Match likelihood (1-10), null if no match',
optional: true,
},
metadata: {
type: 'object',
description: 'Metadata echoed back from the request',
optional: true,
},
person: { type: 'object', description: 'Matched person record', optional: true },
} as const satisfies Record<string, OutputProperty>
export const bulkPersonEnrichTool: ToolConfig<
PdlBulkPersonEnrichParams,
PdlBulkPersonEnrichResponse
> = {
id: 'pdl_bulk_person_enrich',
name: 'PDL Bulk Person Enrich',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
requests: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of request objects, max 100. Each item: { "params": { email | profile | first_name+last_name+company | ... }, "metadata": {...optional...} }',
},
required: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Required-fields expression applied globally to every request',
},
},
request: {
url: () => 'https://api.peopledatalabs.com/v5/person/bulk',
method: 'POST',
headers: (params) => ({
'X-Api-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
let parsed: unknown
try {
parsed = JSON.parse(params.requests)
} catch {
throw new Error('`requests` must be valid JSON (an array of request objects)')
}
if (!Array.isArray(parsed)) {
throw new Error('`requests` must be a JSON array')
}
const body: Record<string, unknown> = { requests: parsed }
if (params.required) body.required = params.required
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
const error = (data as { error?: { message?: string } })?.error?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const items = (Array.isArray(data) ? data : []) as Array<Record<string, unknown>>
const results: PdlBulkPersonResultItem[] = items.map((item) => {
const status = (item.status as number) ?? 0
const record = (item.data as Record<string, unknown>) ?? null
return {
status,
matched: status === 200 && record !== null,
likelihood: (item.likelihood as number) ?? null,
metadata: (item.metadata as Record<string, unknown>) ?? null,
person: record ? projectPerson(record) : null,
}
})
return { success: true, output: { results } }
},
outputs: {
results: {
type: 'array',
description: 'Per-record results in the same order as the input requests',
items: { type: 'object', properties: BULK_PERSON_RESULT_PROPERTIES },
},
},
}
@@ -0,0 +1,104 @@
import type { PdlCleanCompanyParams, PdlCleanCompanyResponse } 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'
export const cleanCompanyTool: ToolConfig<PdlCleanCompanyParams, PdlCleanCompanyResponse> = {
id: 'pdl_clean_company',
name: 'PDL Company Cleaner',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Raw company name to normalize',
},
website: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company website',
},
profile: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'LinkedIn company URL',
},
},
request: {
url: () => 'https://api.peopledatalabs.com/v5/company/clean',
method: 'POST',
headers: (params) => ({
'X-Api-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.name) body.name = params.name
if (params.website) body.website = params.website
if (params.profile) body.profile = params.profile
return body
},
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as Record<string, unknown>
const status = (data.status as number) ?? response.status
if (status === 404) {
return { success: true, output: { matched: false, company: null } }
}
if (!response.ok) {
const error = (data.error as { message?: string })?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const hasFields = data.name || data.website || data.id
return {
success: true,
output: {
matched: Boolean(hasFields),
company: hasFields ? projectCompany(data) : null,
},
}
},
outputs: {
matched: { type: 'boolean', description: 'Whether the input was matched to a known company' },
company: {
type: 'object',
description: 'Canonical company record',
optional: true,
properties: PDL_COMPANY_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,179 @@
import type { PdlCompanyEnrichParams, PdlCompanyEnrichResponse } 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'
export const companyEnrichTool: ToolConfig<PdlCompanyEnrichParams, PdlCompanyEnrichResponse> = {
id: 'pdl_company_enrich',
name: 'PDL Company Enrich',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company name',
},
website: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company website domain',
},
profile: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'LinkedIn company URL',
},
ticker: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Stock ticker',
},
pdl_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'PDL company ID',
},
location: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company location (helps disambiguate)',
},
locality: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'City',
},
region: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State/region',
},
country: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Country',
},
min_likelihood: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Minimum match likelihood (1-10)',
},
required: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Required-fields expression',
},
titlecase: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Return name fields in title case',
},
},
request: {
url: (params) => {
const qs = buildQueryString({
name: params.name,
website: params.website,
profile: params.profile,
ticker: params.ticker,
pdl_id: params.pdl_id,
location: params.location,
locality: params.locality,
region: params.region,
country: params.country,
min_likelihood: params.min_likelihood,
required: params.required,
titlecase: params.titlecase,
})
return `https://api.peopledatalabs.com/v5/company/enrich${qs}`
},
method: 'GET',
headers: (params) => ({
'X-Api-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as Record<string, unknown>
const status = (data.status as number) ?? response.status
if (status === 404) {
return { success: true, output: { matched: false, likelihood: null, company: null } }
}
if (!response.ok) {
const error = (data.error as { message?: string })?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const hasFields = data.name || data.website || data.id
return {
success: true,
output: {
matched: Boolean(hasFields),
likelihood: (data.likelihood as number) ?? null,
company: hasFields ? projectCompany(data) : null,
},
}
},
outputs: {
matched: { type: 'boolean', description: 'Whether a company record was matched' },
likelihood: {
type: 'number',
description: 'Match likelihood score (1-10), null if no match',
optional: true,
},
company: {
type: 'object',
description: 'Matched company record',
optional: true,
properties: PDL_COMPANY_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,148 @@
import type { PdlCompanySearchParams, PdlCompanySearchResponse } 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'
export const companySearchTool: ToolConfig<PdlCompanySearchParams, PdlCompanySearchResponse> = {
id: 'pdl_company_search',
name: 'PDL Company Search',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
sql: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
"PDL SQL query (e.g., \"SELECT * FROM company WHERE industry='computer software' AND size='51-200'\")",
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Elasticsearch DSL query as JSON string',
},
size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to return (1-100, default 1)',
},
scroll_token: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token returned from a prior response',
},
titlecase: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Return name fields in title case',
},
},
request: {
url: () => 'https://api.peopledatalabs.com/v5/company/search',
method: 'POST',
headers: (params) => ({
'X-Api-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.sql) body.sql = params.sql
if (params.query) {
try {
body.query = JSON.parse(params.query)
} catch {
body.query = params.query
}
}
if (params.size !== undefined) body.size = Number(params.size)
if (params.scroll_token) body.scroll_token = params.scroll_token
if (params.titlecase !== undefined) body.titlecase = params.titlecase
return body
},
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as Record<string, unknown>
const status = (data.status as number) ?? response.status
if (status === 404) {
return { success: true, output: { total: 0, scroll_token: null, results: [] } }
}
if (!response.ok) {
const error = (data.error as { message?: string })?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const records = (data.data as Record<string, unknown>[]) ?? []
return {
success: true,
output: {
total: (data.total as number) ?? records.length,
scroll_token: (data.scroll_token as string) ?? null,
results: records.map(projectCompany),
},
}
},
outputs: {
total: { type: 'number', description: 'Total matching companies in dataset' },
scroll_token: {
type: 'string',
description: 'Pagination token to fetch the next page; null if no more results',
optional: true,
},
results: {
type: 'array',
description: 'Company records matching the query',
items: { type: 'object', properties: PDL_COMPANY_OUTPUT_PROPERTIES },
},
},
}
+23
View File
@@ -0,0 +1,23 @@
import { autocompleteTool } from '@/tools/peopledatalabs/autocomplete'
import { bulkCompanyEnrichTool } from '@/tools/peopledatalabs/bulk_company_enrich'
import { bulkPersonEnrichTool } from '@/tools/peopledatalabs/bulk_person_enrich'
import { cleanCompanyTool } from '@/tools/peopledatalabs/company_clean'
import { companyEnrichTool } from '@/tools/peopledatalabs/company_enrich'
import { companySearchTool } from '@/tools/peopledatalabs/company_search'
import { cleanLocationTool } from '@/tools/peopledatalabs/location_clean'
import { personEnrichTool } from '@/tools/peopledatalabs/person_enrich'
import { personIdentifyTool } from '@/tools/peopledatalabs/person_identify'
import { personSearchTool } from '@/tools/peopledatalabs/person_search'
import { cleanSchoolTool } from '@/tools/peopledatalabs/school_clean'
export const pdlAutocompleteTool = autocompleteTool
export const pdlBulkCompanyEnrichTool = bulkCompanyEnrichTool
export const pdlBulkPersonEnrichTool = bulkPersonEnrichTool
export const pdlCleanCompanyTool = cleanCompanyTool
export const pdlCleanLocationTool = cleanLocationTool
export const pdlCleanSchoolTool = cleanSchoolTool
export const pdlCompanyEnrichTool = companyEnrichTool
export const pdlCompanySearchTool = companySearchTool
export const pdlPersonEnrichTool = personEnrichTool
export const pdlPersonIdentifyTool = personIdentifyTool
export const pdlPersonSearchTool = personSearchTool
@@ -0,0 +1,86 @@
import type { PdlCleanLocationParams, PdlCleanLocationResponse } 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'
export const cleanLocationTool: ToolConfig<PdlCleanLocationParams, PdlCleanLocationResponse> = {
id: 'pdl_clean_location',
name: 'PDL Location Cleaner',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
location: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Raw location string (e.g., "SF, CA")',
},
},
request: {
url: () => 'https://api.peopledatalabs.com/v5/location/clean',
method: 'POST',
headers: (params) => ({
'X-Api-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => ({ location: params.location }),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as Record<string, unknown>
const status = (data.status as number) ?? response.status
if (status === 404) {
return { success: true, output: { matched: false, location: null } }
}
if (!response.ok) {
const error = (data.error as { message?: string })?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const hasFields = data.name || data.locality || data.country
return {
success: true,
output: {
matched: Boolean(hasFields),
location: hasFields ? projectLocation(data) : null,
},
}
},
outputs: {
matched: { type: 'boolean', description: 'Whether the input was matched to a known location' },
location: {
type: 'object',
description: 'Canonical location record',
optional: true,
properties: PDL_LOCATION_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,189 @@
import type { PdlPersonEnrichParams, PdlPersonEnrichResponse } 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'
export const personEnrichTool: ToolConfig<PdlPersonEnrichParams, PdlPersonEnrichResponse> = {
id: 'pdl_person_enrich',
name: 'PDL Person Enrich',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email address to match',
},
phone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Phone number (E.164 format preferred)',
},
profile: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'LinkedIn profile URL',
},
lid: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'LinkedIn numeric ID',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Full name (use as an alternative to first_name + last_name)',
},
first_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'First name (use with last_name + company or location)',
},
last_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Last name',
},
company: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company name or website',
},
school: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'School name',
},
location: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Location name (city, region, or country)',
},
min_likelihood: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Minimum match likelihood (1-10)',
},
required: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Required-fields expression (e.g., "emails AND job_title")',
},
titlecase: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Return name fields in title case',
},
},
request: {
url: (params) => {
const qs = buildQueryString({
email: params.email,
phone: params.phone,
profile: params.profile,
lid: params.lid,
name: params.name,
first_name: params.first_name,
last_name: params.last_name,
company: params.company,
school: params.school,
location: params.location,
min_likelihood: params.min_likelihood,
required: params.required,
titlecase: params.titlecase,
})
return `https://api.peopledatalabs.com/v5/person/enrich${qs}`
},
method: 'GET',
headers: (params) => ({
'X-Api-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as Record<string, unknown>
const status = (data.status as number) ?? response.status
if (status === 404) {
return {
success: true,
output: { matched: false, likelihood: null, person: null },
}
}
if (!response.ok) {
const error = (data.error as { message?: string })?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const record = (data.data as Record<string, unknown>) ?? null
return {
success: true,
output: {
matched: record !== null,
likelihood: (data.likelihood as number) ?? null,
person: record ? projectPerson(record) : null,
},
}
},
outputs: {
matched: { type: 'boolean', description: 'Whether a person record was matched' },
likelihood: {
type: 'number',
description: 'Match likelihood score (1-10), null if no match',
optional: true,
},
person: {
type: 'object',
description: 'Matched person record',
optional: true,
properties: PDL_PERSON_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,249 @@
import type {
PdlPersonIdentifyParams,
PdlPersonIdentifyResponse,
} 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'
const IDENTIFY_MATCH_PROPERTIES = {
match_score: { type: 'number', description: 'Match confidence score (1-99)' },
matched_on: {
type: 'array',
description: 'Fields that drove the match (only when include_if_matched=true)',
optional: true,
items: { type: 'string', description: 'Field name' },
},
person: {
type: 'object',
description: 'Person record',
properties: PDL_PERSON_OUTPUT_PROPERTIES,
},
} as const satisfies Record<string, OutputProperty>
export const personIdentifyTool: ToolConfig<PdlPersonIdentifyParams, PdlPersonIdentifyResponse> = {
id: 'pdl_person_identify',
name: 'PDL Person Identify',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
email: { type: 'string', required: false, visibility: 'user-or-llm', description: 'Email' },
phone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Phone number',
},
profile: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'LinkedIn profile URL',
},
email_hash: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'SHA-256 email hash',
},
lid: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'LinkedIn numeric ID',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Full name',
},
first_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'First name',
},
middle_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Middle name',
},
last_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Last name',
},
company: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company name or website',
},
school: { type: 'string', required: false, visibility: 'user-or-llm', description: 'School' },
location: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Location',
},
street_address: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Street address',
},
locality: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'City',
},
region: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State/region',
},
country: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Country',
},
postal_code: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Postal code',
},
birth_date: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Birth date (YYYY-MM-DD)',
},
data_include: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to include in each match',
},
include_if_matched: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include `matched_on` for each match',
},
titlecase: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Return name fields in title case',
},
},
request: {
url: (params) => {
const qs = buildQueryString({
email: params.email,
phone: params.phone,
profile: params.profile,
email_hash: params.email_hash,
lid: params.lid,
name: params.name,
first_name: params.first_name,
middle_name: params.middle_name,
last_name: params.last_name,
company: params.company,
school: params.school,
location: params.location,
street_address: params.street_address,
locality: params.locality,
region: params.region,
country: params.country,
postal_code: params.postal_code,
birth_date: params.birth_date,
data_include: params.data_include,
include_if_matched: params.include_if_matched,
titlecase: params.titlecase,
})
return `https://api.peopledatalabs.com/v5/person/identify${qs}`
},
method: 'GET',
headers: (params) => ({
'X-Api-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as Record<string, unknown>
const status = (data.status as number) ?? response.status
if (status === 404) {
return { success: true, output: { matches: [] } }
}
if (!response.ok) {
const error = (data.error as { message?: string })?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const matches = (data.matches as Array<Record<string, unknown>>) ?? []
return {
success: true,
output: {
matches: matches.map((m) => ({
match_score: (m.match_score as number) ?? 0,
matched_on: (m.matched_on as string[]) ?? undefined,
person: projectPerson((m.data as Record<string, unknown>) ?? {}),
})),
},
}
},
outputs: {
matches: {
type: 'array',
description: 'Up to 20 candidate matches, ordered by score',
items: { type: 'object', properties: IDENTIFY_MATCH_PROPERTIES },
},
},
}
@@ -0,0 +1,156 @@
import type { PdlPersonSearchParams, PdlPersonSearchResponse } 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'
export const personSearchTool: ToolConfig<PdlPersonSearchParams, PdlPersonSearchResponse> = {
id: 'pdl_person_search',
name: 'PDL Person Search',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
sql: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
"PDL SQL query (e.g., \"SELECT * FROM person WHERE job_title='engineer' AND location_country='united states'\")",
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Elasticsearch DSL query as JSON string. Use either sql or query, not both.',
},
size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to return (1-100, default 1)',
},
scroll_token: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token returned from a prior response',
},
dataset: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Dataset filter: all, resume, email, phone, mobile_phone, street_address, consumer_social, developer (combinable with commas, exclude with `-` prefix)',
},
titlecase: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Return name fields in title case',
},
},
request: {
url: () => 'https://api.peopledatalabs.com/v5/person/search',
method: 'POST',
headers: (params) => ({
'X-Api-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.sql) body.sql = params.sql
if (params.query) {
try {
body.query = JSON.parse(params.query)
} catch {
body.query = params.query
}
}
if (params.size !== undefined) body.size = Number(params.size)
if (params.scroll_token) body.scroll_token = params.scroll_token
if (params.dataset) body.dataset = params.dataset
if (params.titlecase !== undefined) body.titlecase = params.titlecase
return body
},
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as Record<string, unknown>
const status = (data.status as number) ?? response.status
if (status === 404) {
return { success: true, output: { total: 0, scroll_token: null, results: [] } }
}
if (!response.ok) {
const error = (data.error as { message?: string })?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const records = (data.data as Record<string, unknown>[]) ?? []
return {
success: true,
output: {
total: (data.total as number) ?? records.length,
scroll_token: (data.scroll_token as string) ?? null,
results: records.map(projectPerson),
},
}
},
outputs: {
total: { type: 'number', description: 'Total matching records in dataset' },
scroll_token: {
type: 'string',
description: 'Pagination token to fetch the next page; null if no more results',
optional: true,
},
results: {
type: 'array',
description: 'Person records matching the query',
items: { type: 'object', properties: PDL_PERSON_OUTPUT_PROPERTIES },
},
},
}
@@ -0,0 +1,104 @@
import type { PdlCleanSchoolParams, PdlCleanSchoolResponse } 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'
export const cleanSchoolTool: ToolConfig<PdlCleanSchoolParams, PdlCleanSchoolResponse> = {
id: 'pdl_clean_school',
name: 'PDL School Cleaner',
description:
'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',
required: true,
visibility: 'user-only',
description: 'People Data Labs API key',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Raw school name to normalize',
},
website: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'School website',
},
profile: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'LinkedIn school URL',
},
},
request: {
url: () => 'https://api.peopledatalabs.com/v5/school/clean',
method: 'POST',
headers: (params) => ({
'X-Api-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.name) body.name = params.name
if (params.website) body.website = params.website
if (params.profile) body.profile = params.profile
return body
},
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as Record<string, unknown>
const status = (data.status as number) ?? response.status
if (status === 404) {
return { success: true, output: { matched: false, school: null } }
}
if (!response.ok) {
const error = (data.error as { message?: string })?.message
throw new Error(error || `People Data Labs error: ${response.status}`)
}
const hasFields = data.name || data.website || data.id
return {
success: true,
output: {
matched: Boolean(hasFields),
school: hasFields ? projectSchool(data) : null,
},
}
},
outputs: {
matched: { type: 'boolean', description: 'Whether the input was matched to a known school' },
school: {
type: 'object',
description: 'Canonical school record',
optional: true,
properties: PDL_SCHOOL_OUTPUT_PROPERTIES,
},
},
}
+504
View File
@@ -0,0 +1,504 @@
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
*/
export const PDL_PERSON_OUTPUT_PROPERTIES = {
id: { type: 'string', description: 'PDL person ID', optional: true },
full_name: { type: 'string', description: 'Full name', optional: true },
first_name: { type: 'string', description: 'First name', optional: true },
last_name: { type: 'string', description: 'Last name', optional: true },
gender: { type: 'string', description: 'Gender', optional: true },
birth_year: { type: 'number', description: 'Birth year', optional: true },
linkedin_url: { type: 'string', description: 'LinkedIn profile URL', optional: true },
linkedin_username: { type: 'string', description: 'LinkedIn username', optional: true },
twitter_url: { type: 'string', description: 'Twitter profile URL', optional: true },
github_url: { type: 'string', description: 'GitHub profile URL', optional: true },
facebook_url: { type: 'string', description: 'Facebook profile URL', optional: true },
work_email: { type: 'string', description: 'Primary work email', optional: true },
personal_emails: {
type: 'array',
description: 'Personal email addresses',
optional: true,
items: { type: 'string', description: 'Email address' },
},
emails: {
type: 'array',
description: 'All known email addresses',
optional: true,
items: { type: 'object', description: 'Email entry' },
},
phone_numbers: {
type: 'array',
description: 'Known phone numbers',
optional: true,
items: { type: 'string', description: 'Phone number' },
},
mobile_phone: { type: 'string', description: 'Mobile phone number', optional: true },
job_title: { type: 'string', description: 'Current job title', optional: true },
job_title_role: { type: 'string', description: 'Normalized job role', optional: true },
job_title_sub_role: {
type: 'string',
description: 'Normalized job sub-role',
optional: true,
},
job_title_levels: {
type: 'array',
description: 'Seniority levels (e.g., manager, director)',
optional: true,
items: { type: 'string', description: 'Level' },
},
job_company_name: { type: 'string', description: 'Current employer name', optional: true },
job_company_website: {
type: 'string',
description: 'Current employer website',
optional: true,
},
job_company_industry: {
type: 'string',
description: 'Current employer industry',
optional: true,
},
job_company_size: { type: 'string', description: 'Current employer size band', optional: true },
job_company_linkedin_url: {
type: 'string',
description: "Current employer's LinkedIn URL",
optional: true,
},
job_start_date: {
type: 'string',
description: 'Start date at current employer (YYYY-MM)',
optional: true,
},
location_name: { type: 'string', description: 'Full location name', optional: true },
location_locality: { type: 'string', description: 'City', optional: true },
location_region: { type: 'string', description: 'State/region', optional: true },
location_country: { type: 'string', description: 'Country', optional: true },
location_continent: { type: 'string', description: 'Continent', optional: true },
industry: { type: 'string', description: 'Industry', optional: true },
skills: {
type: 'array',
description: 'Skills',
optional: true,
items: { type: 'string', description: 'Skill name' },
},
interests: {
type: 'array',
description: 'Interests',
optional: true,
items: { type: 'string', description: 'Interest' },
},
experience: {
type: 'array',
description: 'Work history entries',
optional: true,
items: { type: 'object', description: 'Job experience' },
},
education: {
type: 'array',
description: 'Education history',
optional: true,
items: { type: 'object', description: 'Education entry' },
},
} as const satisfies Record<string, OutputProperty>
export const PDL_COMPANY_OUTPUT_PROPERTIES = {
id: { type: 'string', description: 'PDL company ID', optional: true },
name: { type: 'string', description: 'Company name', optional: true },
display_name: { type: 'string', description: 'Display name', optional: true },
website: { type: 'string', description: 'Website domain', optional: true },
ticker: { type: 'string', description: 'Stock ticker', optional: true },
type: { type: 'string', description: 'Company type (public, private, etc.)', optional: true },
industry: { type: 'string', description: 'Industry', optional: true },
size: { type: 'string', description: 'Employee size band', optional: true },
employee_count: { type: 'number', description: 'Estimated employee count', optional: true },
founded: { type: 'number', description: 'Year founded', optional: true },
headline: { type: 'string', description: 'Company headline/tagline', optional: true },
summary: { type: 'string', description: 'Company description', optional: true },
linkedin_url: { type: 'string', description: 'LinkedIn URL', optional: true },
linkedin_id: { type: 'string', description: 'LinkedIn ID', optional: true },
twitter_url: { type: 'string', description: 'Twitter URL', optional: true },
facebook_url: { type: 'string', description: 'Facebook URL', optional: true },
location_name: { type: 'string', description: 'HQ location name', optional: true },
location_locality: { type: 'string', description: 'HQ city', optional: true },
location_region: { type: 'string', description: 'HQ state/region', optional: true },
location_country: { type: 'string', description: 'HQ country', optional: true },
tags: {
type: 'array',
description: 'Company tags',
optional: true,
items: { type: 'string', description: 'Tag' },
},
tickers: {
type: 'array',
description: 'All stock tickers',
optional: true,
items: { type: 'string', description: 'Ticker' },
},
} as const satisfies Record<string, OutputProperty>
export interface PdlPersonRecord {
id?: string
full_name?: string
first_name?: string
last_name?: string
gender?: string
birth_year?: number
linkedin_url?: string
linkedin_username?: string
twitter_url?: string
github_url?: string
facebook_url?: string
work_email?: string
personal_emails?: string[]
emails?: unknown[]
phone_numbers?: string[]
mobile_phone?: string
job_title?: string
job_title_role?: string
job_title_sub_role?: string
job_title_levels?: string[]
job_company_name?: string
job_company_website?: string
job_company_industry?: string
job_company_size?: string
job_company_linkedin_url?: string
job_start_date?: string
location_name?: string
location_locality?: string
location_region?: string
location_country?: string
location_continent?: string
industry?: string
skills?: string[]
interests?: string[]
experience?: unknown[]
education?: unknown[]
}
export interface PdlCompanyRecord {
id?: string
name?: string
display_name?: string
website?: string
ticker?: string
type?: string
industry?: string
size?: string
employee_count?: number
founded?: number
headline?: string
summary?: string
linkedin_url?: string
linkedin_id?: string
twitter_url?: string
facebook_url?: string
location_name?: string
location_locality?: string
location_region?: string
location_country?: string
tags?: string[]
tickers?: string[]
}
export interface PdlPersonEnrichParams {
apiKey: string
email?: string
phone?: string
profile?: string
lid?: string
name?: string
first_name?: string
last_name?: string
company?: string
school?: string
location?: string
min_likelihood?: number
required?: string
titlecase?: boolean
}
export interface PdlPersonEnrichResponse extends ToolResponse {
output: {
matched: boolean
likelihood: number | null
person: PdlPersonRecord | null
}
}
export interface PdlPersonSearchParams {
apiKey: string
sql?: string
query?: string
size?: number
scroll_token?: string
dataset?: string
titlecase?: boolean
}
export interface PdlPersonSearchResponse extends ToolResponse {
output: {
total: number
scroll_token: string | null
results: PdlPersonRecord[]
}
}
export interface PdlCompanyEnrichParams {
apiKey: string
name?: string
website?: string
profile?: string
ticker?: string
pdl_id?: string
location?: string
locality?: string
region?: string
country?: string
min_likelihood?: number
required?: string
titlecase?: boolean
}
export interface PdlCompanyEnrichResponse extends ToolResponse {
output: {
matched: boolean
likelihood: number | null
company: PdlCompanyRecord | null
}
}
export interface PdlCompanySearchParams {
apiKey: string
sql?: string
query?: string
size?: number
scroll_token?: string
titlecase?: boolean
}
export interface PdlCompanySearchResponse extends ToolResponse {
output: {
total: number
scroll_token: string | null
results: PdlCompanyRecord[]
}
}
export interface PdlAutocompleteParams {
apiKey: string
field: string
text?: string
size?: number
titlecase?: boolean
}
interface PdlAutocompleteSuggestion {
name: string
count: number
meta?: Record<string, unknown>
}
export interface PdlAutocompleteResponse extends ToolResponse {
output: {
suggestions: PdlAutocompleteSuggestion[]
}
}
export interface PdlBulkPersonEnrichParams {
apiKey: string
requests: string
required?: string
}
export interface PdlBulkPersonResultItem {
status: number
likelihood: number | null
matched: boolean
metadata: Record<string, unknown> | null
person: PdlPersonRecord | null
}
export interface PdlBulkPersonEnrichResponse extends ToolResponse {
output: {
results: PdlBulkPersonResultItem[]
}
}
export interface PdlBulkCompanyEnrichParams {
apiKey: string
requests: string
required?: string
}
export interface PdlBulkCompanyResultItem {
status: number
likelihood: number | null
matched: boolean
metadata: Record<string, unknown> | null
company: PdlCompanyRecord | null
}
export interface PdlBulkCompanyEnrichResponse extends ToolResponse {
output: {
results: PdlBulkCompanyResultItem[]
}
}
export interface PdlPersonIdentifyParams {
apiKey: string
email?: string
phone?: string
profile?: string
email_hash?: string
lid?: string
name?: string
first_name?: string
middle_name?: string
last_name?: string
company?: string
school?: string
location?: string
street_address?: string
locality?: string
region?: string
country?: string
postal_code?: string
birth_date?: string
data_include?: string
include_if_matched?: boolean
titlecase?: boolean
}
interface PdlPersonIdentifyMatch {
match_score: number
matched_on?: string[]
person: PdlPersonRecord
}
export interface PdlPersonIdentifyResponse extends ToolResponse {
output: {
matches: PdlPersonIdentifyMatch[]
}
}
export interface PdlCleanCompanyParams {
apiKey: string
name?: string
website?: string
profile?: string
}
export interface PdlCleanCompanyResponse extends ToolResponse {
output: {
matched: boolean
company: PdlCompanyRecord | null
}
}
export interface PdlCleanLocationParams {
apiKey: string
location: string
}
export const PDL_LOCATION_OUTPUT_PROPERTIES = {
name: { type: 'string', description: 'Normalized location name', optional: true },
locality: { type: 'string', description: 'City', optional: true },
region: { type: 'string', description: 'State/region', optional: true },
subregion: { type: 'string', description: 'Subregion (e.g., county)', optional: true },
country: { type: 'string', description: 'Country', optional: true },
continent: { type: 'string', description: 'Continent', optional: true },
type: { type: 'string', description: 'Location type', optional: true },
geo: { type: 'string', description: 'Latitude,longitude string', optional: true },
} as const satisfies Record<string, OutputProperty>
export interface PdlLocationRecord {
name?: string
locality?: string
region?: string
subregion?: string
country?: string
continent?: string
type?: string
geo?: string
}
export interface PdlCleanLocationResponse extends ToolResponse {
output: {
matched: boolean
location: PdlLocationRecord | null
}
}
export interface PdlCleanSchoolParams {
apiKey: string
name?: string
website?: string
profile?: string
}
export const PDL_SCHOOL_OUTPUT_PROPERTIES = {
id: { type: 'string', description: 'PDL school ID', optional: true },
name: { type: 'string', description: 'School name', optional: true },
type: {
type: 'string',
description: 'School type (e.g., university, secondary)',
optional: true,
},
website: { type: 'string', description: 'Website domain', optional: true },
linkedin_url: { type: 'string', description: 'LinkedIn URL', optional: true },
linkedin_id: { type: 'string', description: 'LinkedIn ID', optional: true },
facebook_url: { type: 'string', description: 'Facebook URL', optional: true },
twitter_url: { type: 'string', description: 'Twitter URL', optional: true },
domain: { type: 'string', description: 'School domain', optional: true },
location_name: { type: 'string', description: 'Location name', optional: true },
location_locality: { type: 'string', description: 'City', optional: true },
location_region: { type: 'string', description: 'State/region', optional: true },
location_country: { type: 'string', description: 'Country', optional: true },
location_continent: { type: 'string', description: 'Continent', optional: true },
} as const satisfies Record<string, OutputProperty>
export interface PdlSchoolRecord {
id?: string
name?: string
type?: string
website?: string
linkedin_url?: string
linkedin_id?: string
facebook_url?: string
twitter_url?: string
domain?: string
location_name?: string
location_locality?: string
location_region?: string
location_country?: string
location_continent?: string
}
export interface PdlCleanSchoolResponse extends ToolResponse {
output: {
matched: boolean
school: PdlSchoolRecord | null
}
}
+161
View File
@@ -0,0 +1,161 @@
import type {
PdlCompanyRecord,
PdlLocationRecord,
PdlPersonRecord,
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.
*/
export function buildQueryString(params: Record<string, unknown>): string {
const search = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === null) continue
const str = String(value)
if (str.length === 0) continue
search.append(key, str)
}
const qs = search.toString()
return qs ? `?${qs}` : ''
}
/**
* Project a raw PDL person record onto our flat schema.
*/
export function projectPerson(raw: Record<string, unknown> | null | undefined): PdlPersonRecord {
const r = (raw ?? {}) as Record<string, unknown>
const location = (r.location ?? {}) as Record<string, unknown>
return {
id: (r.id as string) ?? undefined,
full_name: (r.full_name as string) ?? undefined,
first_name: (r.first_name as string) ?? undefined,
last_name: (r.last_name as string) ?? undefined,
gender: (r.gender as string) ?? undefined,
birth_year: (r.birth_year as number) ?? undefined,
linkedin_url: (r.linkedin_url as string) ?? undefined,
linkedin_username: (r.linkedin_username as string) ?? undefined,
twitter_url: (r.twitter_url as string) ?? undefined,
github_url: (r.github_url as string) ?? undefined,
facebook_url: (r.facebook_url as string) ?? undefined,
work_email: (r.work_email as string) ?? undefined,
personal_emails: (r.personal_emails as string[]) ?? undefined,
emails: (r.emails as unknown[]) ?? undefined,
phone_numbers: (r.phone_numbers as string[]) ?? undefined,
mobile_phone: (r.mobile_phone as string) ?? undefined,
job_title: (r.job_title as string) ?? undefined,
job_title_role: (r.job_title_role as string) ?? undefined,
job_title_sub_role: (r.job_title_sub_role as string) ?? undefined,
job_title_levels: (r.job_title_levels as string[]) ?? undefined,
job_company_name: (r.job_company_name as string) ?? undefined,
job_company_website: (r.job_company_website as string) ?? undefined,
job_company_industry: (r.job_company_industry as string) ?? undefined,
job_company_size: (r.job_company_size as string) ?? undefined,
job_company_linkedin_url: (r.job_company_linkedin_url as string) ?? undefined,
job_start_date: (r.job_start_date as string) ?? undefined,
location_name: (r.location_name as string) ?? (location.name as string) ?? undefined,
location_locality:
(r.location_locality as string) ?? (location.locality as string) ?? undefined,
location_region: (r.location_region as string) ?? (location.region as string) ?? undefined,
location_country: (r.location_country as string) ?? (location.country as string) ?? undefined,
location_continent:
(r.location_continent as string) ?? (location.continent as string) ?? undefined,
industry: (r.industry as string) ?? undefined,
skills: (r.skills as string[]) ?? undefined,
interests: (r.interests as string[]) ?? undefined,
experience: (r.experience as unknown[]) ?? undefined,
education: (r.education as unknown[]) ?? undefined,
}
}
/**
* Project a raw PDL company record onto our flat schema.
*/
export function projectCompany(raw: Record<string, unknown> | null | undefined): PdlCompanyRecord {
const r = (raw ?? {}) as Record<string, unknown>
const location = (r.location ?? {}) as Record<string, unknown>
return {
id: (r.id as string) ?? undefined,
name: (r.name as string) ?? undefined,
display_name: (r.display_name as string) ?? undefined,
website: (r.website as string) ?? undefined,
ticker: (r.ticker as string) ?? undefined,
type: (r.type as string) ?? undefined,
industry: (r.industry as string) ?? undefined,
size: (r.size as string) ?? undefined,
employee_count: (r.employee_count as number) ?? undefined,
founded: (r.founded as number) ?? undefined,
headline: (r.headline as string) ?? undefined,
summary: (r.summary as string) ?? undefined,
linkedin_url: (r.linkedin_url as string) ?? undefined,
linkedin_id: (r.linkedin_id as string) ?? undefined,
twitter_url: (r.twitter_url as string) ?? undefined,
facebook_url: (r.facebook_url as string) ?? undefined,
location_name: (r.location_name as string) ?? (location.name as string) ?? undefined,
location_locality:
(r.location_locality as string) ?? (location.locality as string) ?? undefined,
location_region: (r.location_region as string) ?? (location.region as string) ?? undefined,
location_country: (r.location_country as string) ?? (location.country as string) ?? undefined,
tags: (r.tags as string[]) ?? undefined,
tickers: (r.tickers as string[]) ?? undefined,
}
}
/**
* Project a raw PDL location record onto our flat schema.
*/
export function projectLocation(
raw: Record<string, unknown> | null | undefined
): PdlLocationRecord {
const r = (raw ?? {}) as Record<string, unknown>
return {
name: (r.name as string) ?? undefined,
locality: (r.locality as string) ?? undefined,
region: (r.region as string) ?? undefined,
subregion: (r.subregion as string) ?? undefined,
country: (r.country as string) ?? undefined,
continent: (r.continent as string) ?? undefined,
type: (r.type as string) ?? undefined,
geo: (r.geo as string) ?? undefined,
}
}
/**
* Project a raw PDL school record onto our flat schema.
*/
export function projectSchool(raw: Record<string, unknown> | null | undefined): PdlSchoolRecord {
const r = (raw ?? {}) as Record<string, unknown>
const location = (r.location ?? {}) as Record<string, unknown>
return {
id: (r.id as string) ?? undefined,
name: (r.name as string) ?? undefined,
type: (r.type as string) ?? undefined,
website: (r.website as string) ?? undefined,
linkedin_url: (r.linkedin_url as string) ?? undefined,
linkedin_id: (r.linkedin_id as string) ?? undefined,
facebook_url: (r.facebook_url as string) ?? undefined,
twitter_url: (r.twitter_url as string) ?? undefined,
domain: (r.domain as string) ?? undefined,
location_name: (r.location_name as string) ?? (location.name as string) ?? undefined,
location_locality:
(r.location_locality as string) ?? (location.locality as string) ?? undefined,
location_region: (r.location_region as string) ?? (location.region as string) ?? undefined,
location_country: (r.location_country as string) ?? (location.country as string) ?? undefined,
location_continent:
(r.location_continent as string) ?? (location.continent as string) ?? undefined,
}
}