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
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:
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
extractProspeoError,
|
||||
type ProspeoAccountInformationParams,
|
||||
type ProspeoAccountInformationResponse,
|
||||
} from '@/tools/prospeo/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const accountInformationTool: ToolConfig<
|
||||
ProspeoAccountInformationParams,
|
||||
ProspeoAccountInformationResponse
|
||||
> = {
|
||||
id: 'prospeo_account_information',
|
||||
name: 'Prospeo Account Information',
|
||||
description:
|
||||
'Retrieve the current plan, remaining credits, and renewal date of your Prospeo account.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Prospeo API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.prospeo.io/account-information',
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'X-KEY': params.apiKey,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await extractProspeoError(response))
|
||||
}
|
||||
const data = await response.json()
|
||||
const r = data.response ?? {}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
current_plan: r.current_plan ?? null,
|
||||
current_team_members: r.current_team_members ?? null,
|
||||
remaining_credits: r.remaining_credits ?? null,
|
||||
used_credits: r.used_credits ?? null,
|
||||
next_quota_renewal_days: r.next_quota_renewal_days ?? null,
|
||||
next_quota_renewal_date: r.next_quota_renewal_date ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
current_plan: { type: 'string', description: 'Current Prospeo plan name', optional: true },
|
||||
current_team_members: {
|
||||
type: 'number',
|
||||
description: 'Number of team members in your team',
|
||||
optional: true,
|
||||
},
|
||||
remaining_credits: {
|
||||
type: 'number',
|
||||
description: 'Number of credits remaining',
|
||||
optional: true,
|
||||
},
|
||||
used_credits: { type: 'number', description: 'Number of credits already used', optional: true },
|
||||
next_quota_renewal_days: {
|
||||
type: 'number',
|
||||
description: 'Days until the next quota renewal',
|
||||
optional: true,
|
||||
},
|
||||
next_quota_renewal_date: {
|
||||
type: 'string',
|
||||
description: 'Date and time of the next quota renewal',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { prospeoHosting } from '@/tools/prospeo/hosting'
|
||||
import {
|
||||
extractProspeoError,
|
||||
type ProspeoBulkEnrichCompanyParams,
|
||||
type ProspeoBulkEnrichCompanyResponse,
|
||||
} from '@/tools/prospeo/types'
|
||||
import { parseDataArray } from '@/tools/prospeo/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const bulkEnrichCompanyTool: ToolConfig<
|
||||
ProspeoBulkEnrichCompanyParams,
|
||||
ProspeoBulkEnrichCompanyResponse
|
||||
> = {
|
||||
id: 'prospeo_bulk_enrich_company',
|
||||
name: 'Prospeo Bulk Enrich Company',
|
||||
description: 'Enrich up to 50 company records at once.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: prospeoHosting<ProspeoBulkEnrichCompanyParams>((_params, output) => {
|
||||
// Prospeo reports the exact credits spent for the batch in total_cost.
|
||||
if (typeof output.total_cost !== 'number') {
|
||||
throw new Error('Prospeo bulk enrich company response missing total_cost')
|
||||
}
|
||||
return output.total_cost
|
||||
}),
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Prospeo API key',
|
||||
},
|
||||
data: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of up to 50 company records to enrich. Each must include an "identifier" plus one of: company_website, company_linkedin_url, company_name, or company_id.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.prospeo.io/bulk-enrich-company',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'X-KEY': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({ data: parseDataArray(params.data) }),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await extractProspeoError(response))
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total_cost: data.total_cost ?? 0,
|
||||
matched: data.matched ?? [],
|
||||
not_matched: data.not_matched ?? [],
|
||||
invalid_datapoints: data.invalid_datapoints ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total_cost: { type: 'number', description: 'Total credits spent by the request' },
|
||||
matched: {
|
||||
type: 'array',
|
||||
description: 'Matched company records (identifier, company)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
identifier: {
|
||||
type: 'string',
|
||||
description: 'The identifier you submitted for this record',
|
||||
},
|
||||
company: { type: 'json', description: 'The matched company object', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
not_matched: {
|
||||
type: 'array',
|
||||
description: 'Identifiers of records we could not match',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
invalid_datapoints: {
|
||||
type: 'array',
|
||||
description: 'Identifiers of records that did not meet the minimum matching requirements',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { prospeoHosting } from '@/tools/prospeo/hosting'
|
||||
import {
|
||||
extractProspeoError,
|
||||
type ProspeoBulkEnrichPersonParams,
|
||||
type ProspeoBulkEnrichPersonResponse,
|
||||
} from '@/tools/prospeo/types'
|
||||
import { parseDataArray } from '@/tools/prospeo/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const bulkEnrichPersonTool: ToolConfig<
|
||||
ProspeoBulkEnrichPersonParams,
|
||||
ProspeoBulkEnrichPersonResponse
|
||||
> = {
|
||||
id: 'prospeo_bulk_enrich_person',
|
||||
name: 'Prospeo Bulk Enrich Person',
|
||||
description: 'Enrich up to 50 person records at once.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: prospeoHosting<ProspeoBulkEnrichPersonParams>((_params, output) => {
|
||||
// Prospeo reports the exact credits spent for the batch in total_cost.
|
||||
if (typeof output.total_cost !== 'number') {
|
||||
throw new Error('Prospeo bulk enrich person response missing total_cost')
|
||||
}
|
||||
return output.total_cost
|
||||
}),
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Prospeo API key',
|
||||
},
|
||||
data: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of up to 50 person records to enrich. Each must include an "identifier" plus one of: linkedin_url, email, person_id, or (first_name + last_name + company_*), or (full_name + company_*).',
|
||||
},
|
||||
only_verified_email: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return records with a verified email',
|
||||
},
|
||||
enrich_mobile: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Reveal mobile numbers (10 credits per match; email included)',
|
||||
},
|
||||
only_verified_mobile: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return records that have a mobile (implies enrich_mobile)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.prospeo.io/bulk-enrich-person',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'X-KEY': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = { data: parseDataArray(params.data) }
|
||||
if (params.only_verified_email !== undefined)
|
||||
body.only_verified_email = params.only_verified_email
|
||||
if (params.enrich_mobile !== undefined) body.enrich_mobile = params.enrich_mobile
|
||||
if (params.only_verified_mobile !== undefined)
|
||||
body.only_verified_mobile = params.only_verified_mobile
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await extractProspeoError(response))
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total_cost: data.total_cost ?? 0,
|
||||
matched: data.matched ?? [],
|
||||
not_matched: data.not_matched ?? [],
|
||||
invalid_datapoints: data.invalid_datapoints ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total_cost: { type: 'number', description: 'Total credits spent by the request' },
|
||||
matched: {
|
||||
type: 'array',
|
||||
description: 'Matched records (identifier, person, company)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
identifier: {
|
||||
type: 'string',
|
||||
description: 'The identifier you submitted for this record',
|
||||
},
|
||||
person: { type: 'json', description: 'The matched person object', optional: true },
|
||||
company: {
|
||||
type: 'json',
|
||||
description: 'The current company of the matched person',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
not_matched: {
|
||||
type: 'array',
|
||||
description: 'Identifiers of records we could not match given the filters',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
invalid_datapoints: {
|
||||
type: 'array',
|
||||
description: 'Identifiers of records that did not meet the minimum matching requirements',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { prospeoHosting } from '@/tools/prospeo/hosting'
|
||||
import {
|
||||
extractProspeoError,
|
||||
type ProspeoEnrichCompanyParams,
|
||||
type ProspeoEnrichCompanyResponse,
|
||||
} from '@/tools/prospeo/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const enrichCompanyTool: ToolConfig<
|
||||
ProspeoEnrichCompanyParams,
|
||||
ProspeoEnrichCompanyResponse
|
||||
> = {
|
||||
id: 'prospeo_enrich_company',
|
||||
name: 'Prospeo Enrich Company',
|
||||
description: 'Enrich a company with complete B2B data.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: prospeoHosting<ProspeoEnrichCompanyParams>((_params, output) => {
|
||||
// 1 credit per company match; no charge on a no-match or repeat enrichment.
|
||||
if (output.free_enrichment === true) return 0
|
||||
return output.company ? 1 : 0
|
||||
}),
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Prospeo API key',
|
||||
},
|
||||
company_website: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company website (e.g., "intercom.com")',
|
||||
},
|
||||
company_linkedin_url: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Company's public LinkedIn URL",
|
||||
},
|
||||
company_name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company name (use combined with website for best accuracy)',
|
||||
},
|
||||
company_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Prospeo company_id from a previously enriched company',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.prospeo.io/enrich-company',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'X-KEY': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const data: Record<string, unknown> = {}
|
||||
if (params.company_website) data.company_website = params.company_website
|
||||
if (params.company_linkedin_url) data.company_linkedin_url = params.company_linkedin_url
|
||||
if (params.company_name) data.company_name = params.company_name
|
||||
if (params.company_id) data.company_id = params.company_id
|
||||
return { data }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await extractProspeoError(response))
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
free_enrichment: data.free_enrichment ?? false,
|
||||
company: data.company ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
free_enrichment: {
|
||||
type: 'boolean',
|
||||
description: 'True if this enrichment was free (already enriched in the past)',
|
||||
},
|
||||
company: {
|
||||
type: 'json',
|
||||
description:
|
||||
'The matched company object including name, website, domain, industry, employee_count, location, social URLs, funding, and technology',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import { prospeoHosting } from '@/tools/prospeo/hosting'
|
||||
import {
|
||||
extractProspeoError,
|
||||
type ProspeoEnrichPersonParams,
|
||||
type ProspeoEnrichPersonResponse,
|
||||
} from '@/tools/prospeo/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const enrichPersonTool: ToolConfig<ProspeoEnrichPersonParams, ProspeoEnrichPersonResponse> =
|
||||
{
|
||||
id: 'prospeo_enrich_person',
|
||||
name: 'Prospeo Enrich Person',
|
||||
description: 'Enrich a person with complete B2B profile data, email address and mobile.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: prospeoHosting<ProspeoEnrichPersonParams>((_params, output) => {
|
||||
// No charge on a no-match or a repeat enrichment.
|
||||
if (output.free_enrichment === true) return 0
|
||||
const person = output.person as Record<string, unknown> | null
|
||||
if (!person) return 0
|
||||
// 10 credits when a mobile is revealed, otherwise 1 for the person match.
|
||||
const mobile = person.mobile as { revealed?: boolean } | undefined
|
||||
return mobile?.revealed ? 10 : 1
|
||||
}),
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Prospeo API key',
|
||||
},
|
||||
first_name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'First name of the person',
|
||||
},
|
||||
last_name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Last name of the person',
|
||||
},
|
||||
full_name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Full name of the person (alternative to first_name + last_name)',
|
||||
},
|
||||
linkedin_url: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Person's public LinkedIn URL",
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Work email of the person',
|
||||
},
|
||||
company_name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company name',
|
||||
},
|
||||
company_website: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company website',
|
||||
},
|
||||
company_linkedin_url: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Company's public LinkedIn URL",
|
||||
},
|
||||
person_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Prospeo person_id from a previous Search Person response',
|
||||
},
|
||||
only_verified_email: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return records with a verified email',
|
||||
},
|
||||
enrich_mobile: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Reveal mobile number (10 credits per match; email included)',
|
||||
},
|
||||
only_verified_mobile: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return records that have a mobile (implies enrich_mobile)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.prospeo.io/enrich-person',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'X-KEY': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const data: Record<string, unknown> = {}
|
||||
if (params.first_name) data.first_name = params.first_name
|
||||
if (params.last_name) data.last_name = params.last_name
|
||||
if (params.full_name) data.full_name = params.full_name
|
||||
if (params.linkedin_url) data.linkedin_url = params.linkedin_url
|
||||
if (params.email) data.email = params.email
|
||||
if (params.company_name) data.company_name = params.company_name
|
||||
if (params.company_website) data.company_website = params.company_website
|
||||
if (params.company_linkedin_url) data.company_linkedin_url = params.company_linkedin_url
|
||||
if (params.person_id) data.person_id = params.person_id
|
||||
|
||||
const body: Record<string, unknown> = { data }
|
||||
if (params.only_verified_email !== undefined)
|
||||
body.only_verified_email = params.only_verified_email
|
||||
if (params.enrich_mobile !== undefined) body.enrich_mobile = params.enrich_mobile
|
||||
if (params.only_verified_mobile !== undefined)
|
||||
body.only_verified_mobile = params.only_verified_mobile
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await extractProspeoError(response))
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
free_enrichment: data.free_enrichment ?? false,
|
||||
person: data.person ?? null,
|
||||
company: data.company ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
free_enrichment: {
|
||||
type: 'boolean',
|
||||
description: 'True if this enrichment was free (already enriched in the past)',
|
||||
},
|
||||
person: {
|
||||
type: 'json',
|
||||
description:
|
||||
'The matched person object including person_id, name, linkedin_url, current_job_title, job_history, mobile, email, location, and skills',
|
||||
optional: true,
|
||||
},
|
||||
company: {
|
||||
type: 'json',
|
||||
description:
|
||||
'The current company of the matched person including name, website, domain, industry, employee_count, location, social URLs, funding, and technology',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { ToolHostingConfig } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Env var prefix for Prospeo hosted keys. Provide keys as
|
||||
* `PROSPEO_API_KEY_COUNT` plus `PROSPEO_API_KEY_1..N`.
|
||||
*/
|
||||
export const PROSPEO_API_KEY_PREFIX = 'PROSPEO_API_KEY'
|
||||
|
||||
/**
|
||||
* Dollar cost of a single Prospeo credit.
|
||||
*
|
||||
* Prospeo charges per match: 1 credit per person/company match, 10 credits when
|
||||
* a mobile is revealed, and never on a no-match or a repeat enrichment. Based on
|
||||
* the $39/month Starter plan (1,000 credits ≈ $0.039/credit) — https://prospeo.io/pricing.
|
||||
*/
|
||||
export const PROSPEO_CREDIT_USD = 0.039
|
||||
|
||||
/**
|
||||
* Build a Prospeo `hosting` config. `getCredits` returns the number of Prospeo
|
||||
* credits the call consumed, derived from the tool's output (prefer the
|
||||
* API-reported `total_cost` for bulk endpoints; otherwise compute from the
|
||||
* `free`/`free_enrichment` flag and the match).
|
||||
*/
|
||||
export function prospeoHosting<P>(
|
||||
getCredits: (params: P, output: Record<string, unknown>) => number
|
||||
): ToolHostingConfig<P> {
|
||||
return {
|
||||
envKeyPrefix: PROSPEO_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'prospeo',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
getCost: (params, output) => {
|
||||
const credits = getCredits(params, output)
|
||||
return { cost: credits * PROSPEO_CREDIT_USD, metadata: { credits } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { accountInformationTool } from '@/tools/prospeo/account_information'
|
||||
import { bulkEnrichCompanyTool } from '@/tools/prospeo/bulk_enrich_company'
|
||||
import { bulkEnrichPersonTool } from '@/tools/prospeo/bulk_enrich_person'
|
||||
import { enrichCompanyTool } from '@/tools/prospeo/enrich_company'
|
||||
import { enrichPersonTool } from '@/tools/prospeo/enrich_person'
|
||||
import { searchCompanyTool } from '@/tools/prospeo/search_company'
|
||||
import { searchPersonTool } from '@/tools/prospeo/search_person'
|
||||
import { searchSuggestionsTool } from '@/tools/prospeo/search_suggestions'
|
||||
|
||||
export const prospeoAccountInformationTool = accountInformationTool
|
||||
export const prospeoEnrichPersonTool = enrichPersonTool
|
||||
export const prospeoEnrichCompanyTool = enrichCompanyTool
|
||||
export const prospeoBulkEnrichPersonTool = bulkEnrichPersonTool
|
||||
export const prospeoBulkEnrichCompanyTool = bulkEnrichCompanyTool
|
||||
export const prospeoSearchPersonTool = searchPersonTool
|
||||
export const prospeoSearchCompanyTool = searchCompanyTool
|
||||
export const prospeoSearchSuggestionsTool = searchSuggestionsTool
|
||||
@@ -0,0 +1,95 @@
|
||||
import { prospeoHosting } from '@/tools/prospeo/hosting'
|
||||
import {
|
||||
extractProspeoError,
|
||||
PROSPEO_PAGINATION_OUTPUT,
|
||||
type ProspeoSearchCompanyParams,
|
||||
type ProspeoSearchCompanyResponse,
|
||||
} from '@/tools/prospeo/types'
|
||||
import { parseFiltersObject } from '@/tools/prospeo/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const searchCompanyTool: ToolConfig<
|
||||
ProspeoSearchCompanyParams,
|
||||
ProspeoSearchCompanyResponse
|
||||
> = {
|
||||
id: 'prospeo_search_company',
|
||||
name: 'Prospeo Search Company',
|
||||
description: 'Search for companies using 20+ filters to build account lists.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: prospeoHosting<ProspeoSearchCompanyParams>((_params, output) => {
|
||||
// 1 credit per page that returns at least one result; free on 30-day dedup.
|
||||
if (output.free === true) return 0
|
||||
const results = output.results
|
||||
return Array.isArray(results) && results.length > 0 ? 1 : 0
|
||||
}),
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Prospeo API key',
|
||||
},
|
||||
filters: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter configuration object. See https://prospeo.io/api-docs/filters-documentation for all supported filters (e.g., company_industry, company_headcount_range, company_funding).',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (defaults to 1). Up to 1000 pages of 25 results.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.prospeo.io/search-company',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'X-KEY': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = { filters: parseFiltersObject(params.filters) }
|
||||
if (params.page !== undefined) body.page = params.page
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await extractProspeoError(response))
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
free: data.free ?? false,
|
||||
results: data.results ?? [],
|
||||
pagination: data.pagination ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
free: {
|
||||
type: 'boolean',
|
||||
description: 'True if the request was free due to 30-day result-set deduplication',
|
||||
},
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'Up to 25 matching companies',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
company: { type: 'json', description: 'Matched company object' },
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination: PROSPEO_PAGINATION_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { prospeoHosting } from '@/tools/prospeo/hosting'
|
||||
import {
|
||||
extractProspeoError,
|
||||
PROSPEO_PAGINATION_OUTPUT,
|
||||
type ProspeoSearchPersonParams,
|
||||
type ProspeoSearchPersonResponse,
|
||||
} from '@/tools/prospeo/types'
|
||||
import { parseFiltersObject } from '@/tools/prospeo/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const searchPersonTool: ToolConfig<ProspeoSearchPersonParams, ProspeoSearchPersonResponse> =
|
||||
{
|
||||
id: 'prospeo_search_person',
|
||||
name: 'Prospeo Search Person',
|
||||
description: 'Search for leads using 20+ filters to build targeted contact lists.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: prospeoHosting<ProspeoSearchPersonParams>((_params, output) => {
|
||||
// 1 credit per page that returns at least one result; free on 30-day dedup.
|
||||
if (output.free === true) return 0
|
||||
const results = output.results
|
||||
return Array.isArray(results) && results.length > 0 ? 1 : 0
|
||||
}),
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Prospeo API key',
|
||||
},
|
||||
filters: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter configuration object. See https://prospeo.io/api-docs/filters-documentation for all supported filters (e.g., person_seniority, company_industry, person_location).',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (defaults to 1). Up to 1000 pages of 25 results.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.prospeo.io/search-person',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'X-KEY': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = { filters: parseFiltersObject(params.filters) }
|
||||
if (params.page !== undefined) body.page = params.page
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await extractProspeoError(response))
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
free: data.free ?? false,
|
||||
results: data.results ?? [],
|
||||
pagination: data.pagination ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
free: {
|
||||
type: 'boolean',
|
||||
description: 'True if the request was free due to 30-day result-set deduplication',
|
||||
},
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'Up to 25 search results (person + company, no email/mobile)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
person: {
|
||||
type: 'json',
|
||||
description: 'Matched person (no email/mobile in search response)',
|
||||
},
|
||||
company: { type: 'json', description: 'Current company of the person', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination: PROSPEO_PAGINATION_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
extractProspeoError,
|
||||
type ProspeoSearchSuggestionsParams,
|
||||
type ProspeoSearchSuggestionsResponse,
|
||||
} from '@/tools/prospeo/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const searchSuggestionsTool: ToolConfig<
|
||||
ProspeoSearchSuggestionsParams,
|
||||
ProspeoSearchSuggestionsResponse
|
||||
> = {
|
||||
id: 'prospeo_search_suggestions',
|
||||
name: 'Prospeo Search Suggestions',
|
||||
description:
|
||||
'Free endpoint to retrieve valid location or job title values for use in Search filters. Provide exactly one of location_search or job_title_search.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Prospeo API key',
|
||||
},
|
||||
location_search: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query for location suggestions (minimum 2 characters). Mutually exclusive with job_title_search.',
|
||||
},
|
||||
job_title_search: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query for job title suggestions (minimum 2 characters). Mutually exclusive with location_search.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.prospeo.io/search-suggestions',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'X-KEY': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
if (!params.location_search && !params.job_title_search) {
|
||||
throw new Error(
|
||||
'Provide exactly one of location_search or job_title_search (minimum 2 characters).'
|
||||
)
|
||||
}
|
||||
if (params.location_search && params.job_title_search) {
|
||||
throw new Error(
|
||||
'location_search and job_title_search are mutually exclusive — provide only one.'
|
||||
)
|
||||
}
|
||||
const body: Record<string, unknown> = {}
|
||||
if (params.location_search) body.location_search = params.location_search
|
||||
if (params.job_title_search) body.job_title_search = params.job_title_search
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await extractProspeoError(response))
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
location_suggestions: data.location_suggestions ?? [],
|
||||
job_title_suggestions: data.job_title_suggestions ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
location_suggestions: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Location suggestions when using location_search (empty when searching job titles)',
|
||||
optional: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Formatted location name to use in filters' },
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'Location type (COUNTRY, STATE, CITY, or ZONE)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
job_title_suggestions: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Up to 25 job title suggestions ordered by popularity when using job_title_search (empty when searching locations)',
|
||||
optional: true,
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface ProspeoBaseParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface ProspeoPersonData {
|
||||
first_name?: string
|
||||
last_name?: string
|
||||
full_name?: string
|
||||
linkedin_url?: string
|
||||
email?: string
|
||||
company_name?: string
|
||||
company_website?: string
|
||||
company_linkedin_url?: string
|
||||
person_id?: string
|
||||
}
|
||||
|
||||
export interface ProspeoCompanyData {
|
||||
company_name?: string
|
||||
company_website?: string
|
||||
company_linkedin_url?: string
|
||||
company_id?: string
|
||||
}
|
||||
|
||||
export interface ProspeoPaginationOutput {
|
||||
current_page: number
|
||||
per_page: number
|
||||
total_page: number
|
||||
total_count: number
|
||||
}
|
||||
|
||||
export const PROSPEO_PAGINATION_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Pagination details',
|
||||
optional: true,
|
||||
properties: {
|
||||
current_page: { type: 'number', description: 'Current page number' },
|
||||
per_page: { type: 'number', description: 'Results per page' },
|
||||
total_page: { type: 'number', description: 'Total number of pages' },
|
||||
total_count: { type: 'number', description: 'Total number of matching records' },
|
||||
},
|
||||
}
|
||||
|
||||
/** Account Information */
|
||||
export interface ProspeoAccountInformationParams extends ProspeoBaseParams {}
|
||||
|
||||
export interface ProspeoAccountInformationResponse extends ToolResponse {
|
||||
output: {
|
||||
current_plan: string | null
|
||||
current_team_members: number | null
|
||||
remaining_credits: number | null
|
||||
used_credits: number | null
|
||||
next_quota_renewal_days: number | null
|
||||
next_quota_renewal_date: string | null
|
||||
}
|
||||
}
|
||||
|
||||
/** Enrich Person */
|
||||
export interface ProspeoEnrichPersonParams extends ProspeoBaseParams, ProspeoPersonData {
|
||||
only_verified_email?: boolean
|
||||
enrich_mobile?: boolean
|
||||
only_verified_mobile?: boolean
|
||||
}
|
||||
|
||||
export interface ProspeoEnrichPersonResponse extends ToolResponse {
|
||||
output: {
|
||||
free_enrichment: boolean
|
||||
person: Record<string, unknown> | null
|
||||
company: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
/** Enrich Company */
|
||||
export interface ProspeoEnrichCompanyParams extends ProspeoBaseParams, ProspeoCompanyData {}
|
||||
|
||||
export interface ProspeoEnrichCompanyResponse extends ToolResponse {
|
||||
output: {
|
||||
free_enrichment: boolean
|
||||
company: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
/** Bulk Enrich Person */
|
||||
export interface ProspeoBulkEnrichPersonParams extends ProspeoBaseParams {
|
||||
data: Array<ProspeoPersonData & { identifier: string }>
|
||||
only_verified_email?: boolean
|
||||
enrich_mobile?: boolean
|
||||
only_verified_mobile?: boolean
|
||||
}
|
||||
|
||||
export interface ProspeoBulkEnrichPersonResponse extends ToolResponse {
|
||||
output: {
|
||||
total_cost: number
|
||||
matched: Array<{
|
||||
identifier: string
|
||||
person: Record<string, unknown> | null
|
||||
company: Record<string, unknown> | null
|
||||
}>
|
||||
not_matched: string[]
|
||||
invalid_datapoints: string[]
|
||||
}
|
||||
}
|
||||
|
||||
/** Bulk Enrich Company */
|
||||
export interface ProspeoBulkEnrichCompanyParams extends ProspeoBaseParams {
|
||||
data: Array<ProspeoCompanyData & { identifier: string }>
|
||||
}
|
||||
|
||||
export interface ProspeoBulkEnrichCompanyResponse extends ToolResponse {
|
||||
output: {
|
||||
total_cost: number
|
||||
matched: Array<{
|
||||
identifier: string
|
||||
company: Record<string, unknown> | null
|
||||
}>
|
||||
not_matched: string[]
|
||||
invalid_datapoints: string[]
|
||||
}
|
||||
}
|
||||
|
||||
/** Search Person */
|
||||
export interface ProspeoSearchPersonParams extends ProspeoBaseParams {
|
||||
filters: Record<string, unknown> | string
|
||||
page?: number
|
||||
}
|
||||
|
||||
export interface ProspeoSearchPersonResponse extends ToolResponse {
|
||||
output: {
|
||||
free: boolean
|
||||
results: Array<{
|
||||
person: Record<string, unknown> | null
|
||||
company: Record<string, unknown> | null
|
||||
}>
|
||||
pagination: ProspeoPaginationOutput | null
|
||||
}
|
||||
}
|
||||
|
||||
/** Search Company */
|
||||
export interface ProspeoSearchCompanyParams extends ProspeoBaseParams {
|
||||
filters: Record<string, unknown> | string
|
||||
page?: number
|
||||
}
|
||||
|
||||
export interface ProspeoSearchCompanyResponse extends ToolResponse {
|
||||
output: {
|
||||
free: boolean
|
||||
results: Array<{
|
||||
company: Record<string, unknown> | null
|
||||
}>
|
||||
pagination: ProspeoPaginationOutput | null
|
||||
}
|
||||
}
|
||||
|
||||
/** Search Suggestions */
|
||||
export interface ProspeoSearchSuggestionsParams extends ProspeoBaseParams {
|
||||
location_search?: string
|
||||
job_title_search?: string
|
||||
}
|
||||
|
||||
export interface ProspeoSearchSuggestionsResponse extends ToolResponse {
|
||||
output: {
|
||||
location_suggestions: Array<{ name: string; type: string }>
|
||||
job_title_suggestions: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export type ProspeoResponse =
|
||||
| ProspeoAccountInformationResponse
|
||||
| ProspeoEnrichPersonResponse
|
||||
| ProspeoEnrichCompanyResponse
|
||||
| ProspeoBulkEnrichPersonResponse
|
||||
| ProspeoBulkEnrichCompanyResponse
|
||||
| ProspeoSearchPersonResponse
|
||||
| ProspeoSearchCompanyResponse
|
||||
| ProspeoSearchSuggestionsResponse
|
||||
|
||||
/**
|
||||
* Build a Prospeo API error message from a non-OK response payload.
|
||||
*/
|
||||
export async function extractProspeoError(response: Response): Promise<string> {
|
||||
try {
|
||||
const data = (await response.json()) as {
|
||||
error_code?: string
|
||||
filter_error?: string
|
||||
message?: string
|
||||
}
|
||||
const parts = [data.error_code, data.filter_error, data.message].filter(Boolean)
|
||||
if (parts.length > 0) return parts.join(': ')
|
||||
} catch {}
|
||||
return `Prospeo API error: ${response.status}`
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
export function parseDataArray(value: unknown): unknown[] {
|
||||
if (Array.isArray(value)) return value
|
||||
if (typeof value === 'string' && value.trim().length > 0) {
|
||||
try {
|
||||
const parsed = JSON.parse(value)
|
||||
return Array.isArray(parsed) ? parsed : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function parseFiltersObject(value: unknown): Record<string, unknown> {
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
return value as Record<string, unknown>
|
||||
}
|
||||
if (typeof value === 'string' && value.trim().length > 0) {
|
||||
try {
|
||||
const parsed = JSON.parse(value)
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, unknown>
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
Reference in New Issue
Block a user