chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

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,368 @@
import { sleep } from '@sim/utils/helpers'
import { dropcontactHosting } from '@/tools/dropcontact/hosting'
import type {
DropcontactEmailEntry,
DropcontactEnrichContactParams,
DropcontactEnrichContactResponse,
DropcontactEnrichedContact,
} from '@/tools/dropcontact/types'
import type { ToolConfig } from '@/tools/types'
const POLL_INTERVAL_MS = 5000
const MAX_POLL_TIME_MS = 120000
/**
* Map the first contact from the Dropcontact poll result data array to the
* flat tool output shape.
*
* @param contact - Raw contact object from the Dropcontact API poll response
* @returns Structured output matching `DropcontactEnrichContactResponse.output`
*/
function mapContactData(
requestId: string | null,
contact: DropcontactEnrichedContact
): DropcontactEnrichContactResponse['output'] {
const emailEntries = Array.isArray(contact.email)
? (contact.email as DropcontactEmailEntry[])
: null
const firstEmail = emailEntries?.[0] ?? null
return {
request_id: requestId,
email_found: Boolean(firstEmail?.email),
email: firstEmail?.email ?? null,
emails: emailEntries,
qualification: firstEmail?.qualification ?? null,
first_name: contact.first_name ?? null,
last_name: contact.last_name ?? null,
full_name: contact.full_name ?? null,
civility: contact.civility ?? null,
phone: contact.phone ?? null,
mobile_phone: contact.mobile_phone ?? null,
company: contact.company ?? null,
website: contact.website ?? null,
company_linkedin: contact.company_linkedin ?? null,
linkedin: contact.linkedin ?? null,
country: contact.country ?? null,
siren: contact.siren ?? null,
siret: contact.siret ?? null,
siret_address: contact.siret_address ?? null,
siret_zip: contact.siret_zip ?? null,
siret_city: contact.siret_city ?? null,
vat: contact.vat ?? null,
nb_employees: contact.nb_employees ?? null,
employee_count: contact.employee_count ?? null,
naf5_code: contact.naf5_code ?? null,
naf5_des: contact.naf5_des ?? null,
industry: contact.industry ?? null,
job: contact.job ?? null,
job_level: contact.job_level ?? null,
job_function: contact.job_function ?? null,
company_turnover: contact.company_turnover ?? null,
company_results: contact.company_results ?? null,
}
}
export const dropcontactEnrichContactTool: ToolConfig<
DropcontactEnrichContactParams,
DropcontactEnrichContactResponse
> = {
id: 'dropcontact_enrich_contact',
name: 'Dropcontact Enrich Contact',
description:
'Enrich a contact with verified B2B email, phone, company data, and LinkedIn info via Dropcontact. Submits an async enrichment request, then polls until the result is ready (up to 2 minutes). Charges 1 credit only when a verified email is returned. Provide at least one of: email, first_name+last_name+company, full_name+company, or linkedin URL.',
version: '1.0.0',
hosting: dropcontactHosting<DropcontactEnrichContactParams>((_params, output) => {
// 1 credit per contact when a verified email is found.
// Source: https://developer.dropcontact.com (retrieved 2026-05)
return output.email_found === true ? 1 : 0
}),
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Dropcontact API key (X-Access-Token)',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email address of the contact to enrich',
},
first_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'First name of the contact',
},
last_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Last name of the contact',
},
full_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Full name (alternative to first_name + last_name)',
},
company: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company name',
},
website: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company website (e.g. acme.com)',
},
num_siren: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'French company SIREN number',
},
phone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Phone number',
},
linkedin: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'LinkedIn profile URL',
},
country: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Country code (ISO 3166-1 alpha-2, e.g. "US", "FR")',
},
siren: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include SIREN/SIRET enrichment (France only)',
},
language: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language for returned data (e.g. "en", "fr")',
},
},
request: {
// Submit endpoint: POST https://api.dropcontact.com/v1/enrich/all
// Source: https://developer.dropcontact.com (retrieved 2026-05)
url: 'https://api.dropcontact.com/v1/enrich/all',
method: 'POST',
headers: (params: DropcontactEnrichContactParams) => ({
'X-Access-Token': params.apiKey,
'Content-Type': 'application/json',
}),
body: (params: DropcontactEnrichContactParams) => {
const contact: Record<string, unknown> = {}
if (params.email) contact.email = params.email
if (params.first_name) contact.first_name = params.first_name
if (params.last_name) contact.last_name = params.last_name
if (params.full_name) contact.full_name = params.full_name
if (params.company) contact.company = params.company
if (params.website) contact.website = params.website
if (params.num_siren) contact.num_siren = params.num_siren
if (params.phone) contact.phone = params.phone
if (params.linkedin) contact.linkedin = params.linkedin
if (params.country) contact.country = params.country
const body: Record<string, unknown> = { data: [contact] }
if (params.siren !== undefined) body.siren = params.siren
if (params.language) body.language = params.language
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Dropcontact API error: ${response.status} - ${errorText}`)
}
const json = await response.json()
if (json.error) {
throw new Error(`Dropcontact API error: ${String(json.reason ?? json.error)}`)
}
// Submit response includes request_id; enrichment is async
return {
success: true,
output: {
request_id: (json.request_id as string) ?? null,
email_found: false,
email: null,
emails: null,
qualification: null,
first_name: null,
last_name: null,
full_name: null,
civility: null,
phone: null,
mobile_phone: null,
company: null,
website: null,
company_linkedin: null,
linkedin: null,
country: null,
siren: null,
siret: null,
siret_address: null,
siret_zip: null,
siret_city: null,
vat: null,
nb_employees: null,
employee_count: null,
naf5_code: null,
naf5_des: null,
industry: null,
job: null,
job_level: null,
job_function: null,
company_turnover: null,
company_results: null,
},
}
},
postProcess: async (result, params) => {
if (!result.success) return result
const requestId = result.output.request_id
if (!requestId) {
throw new Error('Dropcontact enrichment did not return a request_id')
}
let elapsedTime = 0
while (elapsedTime < MAX_POLL_TIME_MS) {
await sleep(POLL_INTERVAL_MS)
elapsedTime += POLL_INTERVAL_MS
// Poll endpoint: GET https://api.dropcontact.com/v1/enrich/all/{request_id}
// Source: https://developer.dropcontact.com (retrieved 2026-05)
const pollResponse = await fetch(
`https://api.dropcontact.com/v1/enrich/all/${encodeURIComponent(requestId)}`,
{
headers: {
'X-Access-Token': params.apiKey,
'Content-Type': 'application/json',
},
}
)
if (!pollResponse.ok) {
const errorText = await pollResponse.text()
throw new Error(`Dropcontact poll error: ${pollResponse.status} - ${errorText}`)
}
const json = await pollResponse.json()
// Error state: { error: true|string, reason?: string }
if (json.error) {
throw new Error(`Dropcontact enrichment failed: ${String(json.reason ?? json.error)}`)
}
// Pending: { success: false, error: false, reason: "Request not ready yet..." }
if (!json.success) continue
// Ready: { success: true, data: [...], error: false }
const contacts = Array.isArray(json.data) ? json.data : []
const contact = (contacts[0] ?? {}) as DropcontactEnrichedContact
return {
success: true,
output: mapContactData(requestId, contact),
}
}
throw new Error('Dropcontact enrichment did not complete within the polling window')
},
outputs: {
request_id: { type: 'string', description: 'Dropcontact async request ID', optional: true },
email_found: { type: 'boolean', description: 'Whether a verified email was found' },
email: { type: 'string', description: 'Primary verified email address', optional: true },
emails: {
type: 'array',
description: 'All email addresses returned (each with email and qualification)',
optional: true,
items: {
type: 'object',
properties: {
email: { type: 'string', description: 'Email address' },
qualification: {
type: 'string',
description: 'Email qualification (e.g. nominative@pro)',
},
},
},
},
qualification: {
type: 'string',
description: 'Primary email qualification (e.g. nominative@pro, catch_all@pro)',
optional: true,
},
first_name: { type: 'string', description: 'First name', optional: true },
last_name: { type: 'string', description: 'Last name', optional: true },
full_name: { type: 'string', description: 'Full name', optional: true },
civility: { type: 'string', description: 'Civility (Mr, Mrs, etc.)', optional: true },
phone: { type: 'string', description: 'Phone number', optional: true },
mobile_phone: { type: 'string', description: 'Mobile phone number', optional: true },
company: { type: 'string', description: 'Company name', optional: true },
website: { type: 'string', description: 'Company website', optional: true },
company_linkedin: { type: 'string', description: 'Company LinkedIn URL', optional: true },
linkedin: { type: 'string', description: 'Personal LinkedIn URL', optional: true },
country: { type: 'string', description: 'Country code (ISO 3166-1 alpha-2)', optional: true },
siren: { type: 'string', description: 'French SIREN number', optional: true },
siret: { type: 'string', description: 'French SIRET number', optional: true },
siret_address: { type: 'string', description: 'SIRET registered address', optional: true },
siret_zip: { type: 'string', description: 'SIRET registered postal code', optional: true },
siret_city: { type: 'string', description: 'SIRET registered city', optional: true },
vat: { type: 'string', description: 'VAT number', optional: true },
nb_employees: { type: 'string', description: 'Employee count range', optional: true },
employee_count: {
type: 'number',
description: 'Exact employee count (Growth plan and above)',
optional: true,
},
naf5_code: { type: 'string', description: 'NAF/APE code (France)', optional: true },
naf5_des: {
type: 'string',
description: 'NAF/APE code description (France)',
optional: true,
},
industry: { type: 'string', description: 'Industry classification', optional: true },
job: { type: 'string', description: 'Job title', optional: true },
job_level: {
type: 'string',
description: 'Job seniority level (e.g. C-level, Director)',
optional: true,
},
job_function: {
type: 'string',
description: 'Job function (e.g. Sales, Engineering)',
optional: true,
},
company_turnover: {
type: 'string',
description: 'Company revenue/turnover range',
optional: true,
},
company_results: { type: 'string', description: 'Company net results', optional: true },
},
}
+49
View File
@@ -0,0 +1,49 @@
import type { ToolHostingConfig } from '@/tools/types'
/**
* Env var prefix for Dropcontact hosted keys. Provide keys as
* `DROPCONTACT_API_KEY_COUNT` plus `DROPCONTACT_API_KEY_1..N`.
*/
export const DROPCONTACT_API_KEY_PREFIX = 'DROPCONTACT_API_KEY'
/**
* Dollar cost of a single Dropcontact credit.
*
* Dropcontact's Starter plan is €79/month for 500 credits (≈ $0.158/credit at
* parity). Credits are only deducted when a verified business email is
* successfully returned; no charge if no email is found.
*
* Pricing source: https://www.dropcontact.com/pricing (retrieved 2026-05)
*
* NOTE: This is an approximation based on the Starter plan rate. Actual
* per-credit cost varies by plan tier and currency. A human should verify
* before deploying hosted-key billing.
*/
export const DROPCONTACT_CREDIT_USD = 0.17
/**
* Build a Dropcontact `hosting` config. `getCredits` returns the number of
* Dropcontact credits the call consumed, derived from the tool's final output.
*/
export function dropcontactHosting<P>(
getCredits: (params: P, output: Record<string, unknown>) => number
): ToolHostingConfig<P> {
return {
envKeyPrefix: DROPCONTACT_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'dropcontact',
pricing: {
type: 'custom',
getCost: (params, output) => {
const credits = getCredits(params, output)
return { cost: credits * DROPCONTACT_CREDIT_USD, metadata: { credits } }
},
},
rateLimit: {
// Dropcontact rate limit: 60 requests per second = 3600 requests per minute
// Source: https://developer.dropcontact.com (retrieved 2026-05)
mode: 'per_request',
requestsPerMinute: 3600,
},
}
}
+5
View File
@@ -0,0 +1,5 @@
export * from './types'
import { dropcontactEnrichContactTool } from '@/tools/dropcontact/enrich_contact'
export { dropcontactEnrichContactTool }
+140
View File
@@ -0,0 +1,140 @@
import type { OutputProperty, ToolResponse } from '@/tools/types'
export interface DropcontactBaseParams {
apiKey: string
}
// ---------------------------------------------------------------------------
// Shared output property constants
// ---------------------------------------------------------------------------
export const DROPCONTACT_EMAIL_ITEM_OUTPUT_PROPERTIES = {
email: { type: 'string', description: 'Email address' },
qualification: {
type: 'string',
description:
'Email qualification in the format <local>@<domain>, e.g. nominative@pro, catch_all@pro, generic@perso',
},
} as const satisfies Record<string, OutputProperty>
export const DROPCONTACT_EMAILS_OUTPUT: OutputProperty = {
type: 'array',
description: 'All email addresses found for the contact',
items: {
type: 'object',
properties: DROPCONTACT_EMAIL_ITEM_OUTPUT_PROPERTIES,
},
}
// ---------------------------------------------------------------------------
// Enrich Contact (single-contact async enrichment)
// ---------------------------------------------------------------------------
export interface DropcontactEnrichContactParams extends DropcontactBaseParams {
/** Email address of the contact to enrich */
email?: string
/** First name of the contact */
first_name?: string
/** Last name of the contact */
last_name?: string
/** Full name (alternative to first_name + last_name) */
full_name?: string
/** Company name */
company?: string
/** Company website (e.g. acme.com) */
website?: string
/** French company SIREN number */
num_siren?: string
/** Phone number */
phone?: string
/** LinkedIn profile URL */
linkedin?: string
/** Country code (ISO 3166-1 alpha-2) */
country?: string
/** Whether to include SIREN/SIRET enrichment (France only) */
siren?: boolean
/** Language for returned data (e.g. "en", "fr") */
language?: string
}
/** Per-contact email entry returned by the Dropcontact API */
export interface DropcontactEmailEntry {
email: string
qualification: string
}
/** Enriched contact data returned in the poll result */
export interface DropcontactEnrichedContact {
civility: string | null
first_name: string | null
last_name: string | null
full_name: string | null
email: DropcontactEmailEntry[] | null
phone: string | null
mobile_phone: string | null
company: string | null
website: string | null
company_linkedin: string | null
linkedin: string | null
country: string | null
siren: string | null
siret: string | null
siret_address: string | null
siret_zip: string | null
siret_city: string | null
vat: string | null
nb_employees: string | null
employee_count: number | null
naf5_code: string | null
naf5_des: string | null
industry: string | null
job: string | null
job_level: string | null
job_function: string | null
company_turnover: string | null
company_results: string | null
}
export interface DropcontactEnrichContactResponse extends ToolResponse {
output: {
request_id: string | null
/** Whether the enrichment returned a verified email */
email_found: boolean
/** First verified email address, if any */
email: string | null
/** All emails returned by Dropcontact */
emails: DropcontactEmailEntry[] | null
/** Email qualification (e.g. nominative@pro) */
qualification: string | null
first_name: string | null
last_name: string | null
full_name: string | null
civility: string | null
phone: string | null
mobile_phone: string | null
company: string | null
website: string | null
company_linkedin: string | null
linkedin: string | null
country: string | null
siren: string | null
siret: string | null
siret_address: string | null
siret_zip: string | null
siret_city: string | null
vat: string | null
nb_employees: string | null
employee_count: number | null
naf5_code: string | null
naf5_des: string | null
industry: string | null
job: string | null
job_level: string | null
job_function: string | null
company_turnover: string | null
company_results: string | null
}
}
/** Discriminated union of all Dropcontact tool responses */
export type DropcontactResponse = DropcontactEnrichContactResponse