@coldiq/mcp 0.3.9 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  import { executeWithFallback, isExecutionError } from '../executor.js';
3
3
  import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js';
4
+ import { compactCompaniesPayload } from '../utils/compact-companies.js';
4
5
  export const searchCompaniesName = 'search_companies';
5
- export const searchCompaniesDescription = 'Search B2B companies by industry, size, geography, founding year, tech stack, funding, revenue, exclusion lists, and hiring signals. Routing is automatic: strict firmographic filters (revenue, employee bands, exclusion lists) get high-precision providers first; tech-stack and funding-stage filters route to specialized providers; loose keyword/geo searches fall back to broad providers; a LinkedIn Sales Navigator search URL enables URL-based discovery as a final fallback. For buying-intent topic discovery (find companies associated with intent keywords like "sales automation" or "lead generation"), pass `keywords` with `use_providers: ["theirstack"]` — this routes to TheirStack\'s intent-keyword index instead of the default full-text match. ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool. Default limit: 25.';
6
+ export const searchCompaniesDescription = 'Search B2B companies by industry, size, geography, founding year, tech stack, funding, revenue, exclusion lists, and hiring signals. Routing is automatic: strict firmographic filters (revenue, employee bands, exclusion lists) get high-precision providers first; tech-stack and funding-stage filters route to specialized providers; loose keyword/geo searches fall back to broad providers; a LinkedIn Sales Navigator search URL enables URL-based discovery as a final fallback. For buying-intent topic discovery (find companies associated with intent keywords like "sales automation" or "lead generation"), pass `keywords` with `use_providers: ["theirstack"]` — this routes to TheirStack\'s intent-keyword index instead of the default full-text match. ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool. Default limit: 25. ' +
7
+ 'Response is compact by default (name, domain, website, linkedin_url, employees, revenue, industry, country, city, founded_year, categories) — everything needed to act on a company or feed it into find_people. Set fields="verbose" only when you specifically need the full firmographic record (funding history, technologies, keywords, NAICS codes, all socials, descriptions).';
6
8
  export const searchCompaniesSchema = {
7
9
  keywords: z.array(z.string()).optional().describe('Free-text topics, business models, or themes — e.g. ["SaaS", "fintech", "cybersecurity"]. Best for most discovery: matched broadly across company name, description, and tags. Prefer this over `industries` for terms like "SaaS" that are business models rather than formal industry categories.'),
8
10
  countries: z.array(z.string()).optional().describe('2-letter ISO country codes (e.g. ["FR", "US"])'),
@@ -27,10 +29,12 @@ export const searchCompaniesSchema = {
27
29
  min_workforce_growth_pct: z.number().optional().describe('Minimum workforce growth % over the past 12 months (e.g. 10 for 10%)'),
28
30
  linkedin_search_url: z.string().optional().describe('LinkedIn Sales Navigator company search URL — when provided, enables URL-based prospect discovery as a final fallback. Most users should leave this unset.'),
29
31
  limit: z.number().min(1).max(500).default(25).describe('Max results to return (default: 25, max: 500). Pulls above 100 auto-paginate the underlying provider; on Apollo each 100 companies costs 1 credit, billed only for pages actually returned.'),
32
+ fields: z.enum(['compact', 'verbose']).default('compact').describe('Response shape. "compact" (default) returns a small per-company record: name, domain, website, linkedin_url, employees, revenue, industry, country, city, founded_year, categories — plus total. "verbose" returns the raw provider passthrough (funding-round history, technologies, keyword arrays, NAICS codes, all socials, descriptions) — only use when you specifically need those fields.'),
30
33
  use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('search_companies').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
31
34
  };
32
35
  export async function searchCompaniesHandler(input) {
33
- const { use_providers: rawUseProviders, ...restInput } = input;
36
+ const { use_providers: rawUseProviders, fields: rawFields, ...restInput } = input;
37
+ const fields = rawFields === 'verbose' ? 'verbose' : 'compact';
34
38
  const resolved = resolvePreferredProviders('search_companies', restInput, rawUseProviders);
35
39
  if (!resolved.ok) {
36
40
  return { content: [{ type: 'text', text: JSON.stringify(resolved.error) }], isError: true };
@@ -46,6 +50,9 @@ export async function searchCompaniesHandler(input) {
46
50
  ;
47
51
  result._meta.total_entries = totalEntries;
48
52
  }
49
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
53
+ const payload = fields === 'compact'
54
+ ? { ...result, data: compactCompaniesPayload(result.data) }
55
+ : result;
56
+ return { content: [{ type: 'text', text: JSON.stringify(payload) }] };
50
57
  }
51
58
  //# sourceMappingURL=search-companies.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-companies.js","sourceRoot":"","sources":["../../src/tools/search-companies.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"search-companies.js","sourceRoot":"","sources":["../../src/tools/search-companies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface CompactCompany {
2
+ name?: string;
3
+ domain?: string;
4
+ website?: string;
5
+ linkedin_url?: string;
6
+ employees?: number | string;
7
+ revenue?: number | string;
8
+ industry?: string;
9
+ country?: string;
10
+ city?: string;
11
+ founded_year?: number | string;
12
+ categories?: string[];
13
+ }
14
+ export declare function extractCompaniesArray(data: unknown): unknown[];
15
+ export declare function normalizeCompany(record: unknown): CompactCompany | null;
16
+ export interface CompactCompaniesPayload {
17
+ companies: CompactCompany[];
18
+ total?: number;
19
+ }
20
+ export declare function compactCompaniesPayload(data: unknown): CompactCompaniesPayload;
21
+ //# sourceMappingURL=compact-companies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-companies.d.ts","sourceRoot":"","sources":["../../src/utils/compact-companies.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB;AA2DD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,EAAE,CAmB9D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CA4EvE;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,cAAc,EAAE,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,uBAAuB,CAkB9E"}
@@ -0,0 +1,198 @@
1
+ // Normalize search_companies responses into a small, predictable shape.
2
+ //
3
+ // The search_companies waterfall fans across many providers, each with a
4
+ // different payload shape (CompanyEnrich nests at data.items[], Apollo at
5
+ // data.organizations[] + pagination, FullEnrich at data.companies[], others at
6
+ // data.data[]/results[]). The raw provider object per company is heavy
7
+ // (~3-4KB: full funding-round history, technologies, ~30-item keyword arrays,
8
+ // NAICS codes, every social URL, SEO descriptions, logos). Compact mode keeps
9
+ // only the fields agents act on downstream — company identity (name, domain,
10
+ // website, linkedin_url, used by find_people/find_emails) plus the firmographic
11
+ // signals a user filters on (employees, revenue, industry, country, city,
12
+ // founded_year, categories). It changes how many descriptive fields ride along,
13
+ // never which companies are returned. Verbose mode keeps the raw passthrough.
14
+ function getPath(obj, path) {
15
+ if (obj == null)
16
+ return undefined;
17
+ let cur = obj;
18
+ for (const part of path.split('.')) {
19
+ if (cur == null)
20
+ return undefined;
21
+ if (Array.isArray(cur)) {
22
+ const idx = Number(part);
23
+ if (!Number.isInteger(idx))
24
+ return undefined;
25
+ cur = cur[idx];
26
+ }
27
+ else if (typeof cur === 'object') {
28
+ cur = cur[part];
29
+ }
30
+ else {
31
+ return undefined;
32
+ }
33
+ }
34
+ return cur;
35
+ }
36
+ function pick(obj, paths) {
37
+ for (const p of paths) {
38
+ const v = getPath(obj, p);
39
+ if (v !== undefined && v !== null && v !== '')
40
+ return v;
41
+ }
42
+ return undefined;
43
+ }
44
+ function asString(v) {
45
+ if (typeof v === 'string')
46
+ return v.length > 0 ? v : undefined;
47
+ if (typeof v === 'number')
48
+ return String(v);
49
+ return undefined;
50
+ }
51
+ function asStringOrNumber(v) {
52
+ if (typeof v === 'number')
53
+ return v;
54
+ if (typeof v === 'string')
55
+ return v.length > 0 ? v : undefined;
56
+ return undefined;
57
+ }
58
+ function isLinkedInUrl(v) {
59
+ if (typeof v !== 'string' || v.length === 0)
60
+ return false;
61
+ return v.toLowerCase().includes('linkedin.com/');
62
+ }
63
+ function pickLinkedIn(obj, paths) {
64
+ for (const p of paths) {
65
+ const v = getPath(obj, p);
66
+ if (isLinkedInUrl(v))
67
+ return v;
68
+ }
69
+ return undefined;
70
+ }
71
+ // Probe the keys providers use to hold the companies array. Covers the flat
72
+ // shapes (CompanyEnrich `items`, FullEnrich/BlitzAPI `companies`, Apollo
73
+ // `organizations`, generic `data`/`results`, AI-Ark `content`) and the one-level
74
+ // nested shape LinkUp uses (`data.companies[]`). Keys are kept in sync with the
75
+ // `hasResult` checks in registry.ts so compact never returns [] for a provider
76
+ // that actually matched.
77
+ export function extractCompaniesArray(data) {
78
+ if (data == null)
79
+ return [];
80
+ if (Array.isArray(data))
81
+ return data;
82
+ if (typeof data !== 'object')
83
+ return [];
84
+ const d = data;
85
+ for (const key of ['items', 'companies', 'organizations', 'data', 'results', 'content']) {
86
+ const v = d[key];
87
+ if (Array.isArray(v))
88
+ return v;
89
+ }
90
+ // LinkUp (search / fundraising / hiring) wraps results under data.companies[].
91
+ const inner = d.data;
92
+ if (inner && typeof inner === 'object' && !Array.isArray(inner)) {
93
+ const di = inner;
94
+ for (const key of ['companies', 'organizations', 'results', 'items', 'content']) {
95
+ const v = di[key];
96
+ if (Array.isArray(v))
97
+ return v;
98
+ }
99
+ }
100
+ return [];
101
+ }
102
+ export function normalizeCompany(record) {
103
+ if (!record || typeof record !== 'object' || Array.isArray(record))
104
+ return null;
105
+ const r = record;
106
+ const out = {};
107
+ const name = asString(pick(r, ['name', 'company_name', 'organization.name', 'companyName']));
108
+ if (name)
109
+ out.name = name;
110
+ const domain = asString(pick(r, ['domain', 'primary_domain', 'website_domain', 'organization.primary_domain']));
111
+ if (domain)
112
+ out.domain = domain;
113
+ const website = asString(pick(r, ['website', 'website_url', 'organization.website_url', 'url']));
114
+ if (website)
115
+ out.website = website;
116
+ // LinkedIn URL is strict-validated: a wrong value silently breaks downstream
117
+ // find_people LinkedIn-driven lookups.
118
+ const linkedin = pickLinkedIn(r, [
119
+ 'socials.linkedin_url',
120
+ 'linkedin_url',
121
+ 'organization.linkedin_url',
122
+ 'social_profiles.linkedin.url',
123
+ ]);
124
+ if (linkedin)
125
+ out.linkedin_url = linkedin;
126
+ const employees = asStringOrNumber(pick(r, [
127
+ 'employees',
128
+ 'employee_count',
129
+ 'employees_on_linkedin', // BlitzAPI
130
+ 'estimated_num_employees',
131
+ 'num_employees',
132
+ 'organization.estimated_num_employees',
133
+ 'size',
134
+ 'headcount',
135
+ ]));
136
+ if (employees !== undefined)
137
+ out.employees = employees;
138
+ const revenue = asStringOrNumber(pick(r, [
139
+ 'revenue',
140
+ 'estimated_revenue',
141
+ 'annual_revenue',
142
+ 'organization.annual_revenue',
143
+ ]));
144
+ if (revenue !== undefined)
145
+ out.revenue = revenue;
146
+ const industry = asString(pick(r, ['industry', 'organization.industry']));
147
+ if (industry)
148
+ out.industry = industry;
149
+ const country = asString(pick(r, [
150
+ 'location.country.name',
151
+ 'location.country.code',
152
+ 'country',
153
+ 'organization.country',
154
+ 'location_country',
155
+ ]));
156
+ if (country)
157
+ out.country = country;
158
+ const city = asString(pick(r, [
159
+ 'location.city.name',
160
+ 'city',
161
+ 'organization.city',
162
+ 'location_city',
163
+ ]));
164
+ if (city)
165
+ out.city = city;
166
+ const foundedYear = asStringOrNumber(pick(r, ['founded_year', 'organization.founded_year', 'foundation_date']));
167
+ if (foundedYear !== undefined)
168
+ out.founded_year = foundedYear;
169
+ const categories = getPath(r, 'categories');
170
+ if (Array.isArray(categories)) {
171
+ const cats = categories.filter((c) => typeof c === 'string' && c.length > 0);
172
+ if (cats.length > 0)
173
+ out.categories = cats;
174
+ }
175
+ // Drop records that resolved to nothing meaningful.
176
+ if (Object.keys(out).length === 0)
177
+ return null;
178
+ return out;
179
+ }
180
+ export function compactCompaniesPayload(data) {
181
+ const arr = extractCompaniesArray(data);
182
+ const companies = [];
183
+ for (const r of arr) {
184
+ const c = normalizeCompany(r);
185
+ if (c)
186
+ companies.push(c);
187
+ }
188
+ const out = { companies };
189
+ // Preserve the total-addressable-market count when the provider exposes one
190
+ // (CompanyEnrich: data.totalItems; Apollo: data.pagination.total_entries).
191
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
192
+ const total = pick(data, ['totalItems', 'total', 'pagination.total_entries', 'pagination.total']);
193
+ if (typeof total === 'number')
194
+ out.total = total;
195
+ }
196
+ return out;
197
+ }
198
+ //# sourceMappingURL=compact-companies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-companies.js","sourceRoot":"","sources":["../../src/utils/compact-companies.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coldiq/mcp",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/registry.ts CHANGED
@@ -1179,10 +1179,16 @@ const findPeopleProviders: ProviderEntry[] = [
1179
1179
  },
1180
1180
  async: {
1181
1181
  pollEndpoint: (id: string) => `/leadsfactory/contact-finder/searches/${id}`,
1182
- // Growing backoff: fast early probes, then steady growth bounded by timeoutMs.
1183
- // Schedule: 2s, 5s, 12s, 20s, 28s, 36s, +8s per attempt thereafter.
1182
+ // Fast early probes, then a flat 12s ceiling. LeadsFactory streams results
1183
+ // for minutes; the flat ceiling bounds completion-detection lag to ≤12s
1184
+ // (the old unbounded +8s/attempt growth reached 44–72s gaps, so a search
1185
+ // that finished mid-interval sat unnoticed for up to ~a minute). Poll
1186
+ // responses aren't billed and 12s is gentler than the 2s/5s front probes,
1187
+ // so this only removes idle waiting — the returned data is unchanged (we
1188
+ // still only return once status flips to SUCCESSFUL/FAILED).
1189
+ // Schedule: 2s, 5s, then 12s flat.
1184
1190
  pollIntervalMs: (attempt) =>
1185
- attempt === 1 ? 2000 : attempt === 2 ? 5000 : 12000 + (attempt - 3) * 8000,
1191
+ attempt === 1 ? 2000 : attempt === 2 ? 5000 : 12000,
1186
1192
  timeoutMs: 300_000, // 5 minutes
1187
1193
  isComplete: (data) => {
1188
1194
  const d = data as Record<string, unknown>
@@ -1,11 +1,13 @@
1
1
  import { z } from 'zod'
2
2
  import { executeWithFallback, isExecutionError } from '../executor.js'
3
3
  import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
4
+ import { compactCompaniesPayload } from '../utils/compact-companies.js'
4
5
 
5
6
  export const searchCompaniesName = 'search_companies'
6
7
 
7
8
  export const searchCompaniesDescription =
8
- 'Search B2B companies by industry, size, geography, founding year, tech stack, funding, revenue, exclusion lists, and hiring signals. Routing is automatic: strict firmographic filters (revenue, employee bands, exclusion lists) get high-precision providers first; tech-stack and funding-stage filters route to specialized providers; loose keyword/geo searches fall back to broad providers; a LinkedIn Sales Navigator search URL enables URL-based discovery as a final fallback. For buying-intent topic discovery (find companies associated with intent keywords like "sales automation" or "lead generation"), pass `keywords` with `use_providers: ["theirstack"]` — this routes to TheirStack\'s intent-keyword index instead of the default full-text match. ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool. Default limit: 25.'
9
+ 'Search B2B companies by industry, size, geography, founding year, tech stack, funding, revenue, exclusion lists, and hiring signals. Routing is automatic: strict firmographic filters (revenue, employee bands, exclusion lists) get high-precision providers first; tech-stack and funding-stage filters route to specialized providers; loose keyword/geo searches fall back to broad providers; a LinkedIn Sales Navigator search URL enables URL-based discovery as a final fallback. For buying-intent topic discovery (find companies associated with intent keywords like "sales automation" or "lead generation"), pass `keywords` with `use_providers: ["theirstack"]` — this routes to TheirStack\'s intent-keyword index instead of the default full-text match. ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool. Default limit: 25. ' +
10
+ 'Response is compact by default (name, domain, website, linkedin_url, employees, revenue, industry, country, city, founded_year, categories) — everything needed to act on a company or feed it into find_people. Set fields="verbose" only when you specifically need the full firmographic record (funding history, technologies, keywords, NAICS codes, all socials, descriptions).'
9
11
 
10
12
  export const searchCompaniesSchema = {
11
13
  keywords: z.array(z.string()).optional().describe('Free-text topics, business models, or themes — e.g. ["SaaS", "fintech", "cybersecurity"]. Best for most discovery: matched broadly across company name, description, and tags. Prefer this over `industries` for terms like "SaaS" that are business models rather than formal industry categories.'),
@@ -31,11 +33,13 @@ export const searchCompaniesSchema = {
31
33
  min_workforce_growth_pct: z.number().optional().describe('Minimum workforce growth % over the past 12 months (e.g. 10 for 10%)'),
32
34
  linkedin_search_url: z.string().optional().describe('LinkedIn Sales Navigator company search URL — when provided, enables URL-based prospect discovery as a final fallback. Most users should leave this unset.'),
33
35
  limit: z.number().min(1).max(500).default(25).describe('Max results to return (default: 25, max: 500). Pulls above 100 auto-paginate the underlying provider; on Apollo each 100 companies costs 1 credit, billed only for pages actually returned.'),
36
+ fields: z.enum(['compact', 'verbose']).default('compact').describe('Response shape. "compact" (default) returns a small per-company record: name, domain, website, linkedin_url, employees, revenue, industry, country, city, founded_year, categories — plus total. "verbose" returns the raw provider passthrough (funding-round history, technologies, keyword arrays, NAICS codes, all socials, descriptions) — only use when you specifically need those fields.'),
34
37
  use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('search_companies').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
35
38
  }
36
39
 
37
40
  export async function searchCompaniesHandler(input: Record<string, unknown>) {
38
- const { use_providers: rawUseProviders, ...restInput } = input
41
+ const { use_providers: rawUseProviders, fields: rawFields, ...restInput } = input
42
+ const fields: 'compact' | 'verbose' = rawFields === 'verbose' ? 'verbose' : 'compact'
39
43
  const resolved = resolvePreferredProviders('search_companies', restInput, rawUseProviders)
40
44
  if (!resolved.ok) {
41
45
  return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error) }], isError: true }
@@ -50,5 +54,8 @@ export async function searchCompaniesHandler(input: Record<string, unknown>) {
50
54
  if (typeof totalEntries === 'number') {
51
55
  ;(result._meta as Record<string, unknown>).total_entries = totalEntries
52
56
  }
53
- return { content: [{ type: 'text' as const, text: JSON.stringify(result) }] }
57
+ const payload = fields === 'compact'
58
+ ? { ...result, data: compactCompaniesPayload(result.data) }
59
+ : result
60
+ return { content: [{ type: 'text' as const, text: JSON.stringify(payload) }] }
54
61
  }
@@ -0,0 +1,208 @@
1
+ // Normalize search_companies responses into a small, predictable shape.
2
+ //
3
+ // The search_companies waterfall fans across many providers, each with a
4
+ // different payload shape (CompanyEnrich nests at data.items[], Apollo at
5
+ // data.organizations[] + pagination, FullEnrich at data.companies[], others at
6
+ // data.data[]/results[]). The raw provider object per company is heavy
7
+ // (~3-4KB: full funding-round history, technologies, ~30-item keyword arrays,
8
+ // NAICS codes, every social URL, SEO descriptions, logos). Compact mode keeps
9
+ // only the fields agents act on downstream — company identity (name, domain,
10
+ // website, linkedin_url, used by find_people/find_emails) plus the firmographic
11
+ // signals a user filters on (employees, revenue, industry, country, city,
12
+ // founded_year, categories). It changes how many descriptive fields ride along,
13
+ // never which companies are returned. Verbose mode keeps the raw passthrough.
14
+
15
+ export interface CompactCompany {
16
+ name?: string
17
+ domain?: string
18
+ website?: string
19
+ linkedin_url?: string
20
+ employees?: number | string
21
+ revenue?: number | string
22
+ industry?: string
23
+ country?: string
24
+ city?: string
25
+ founded_year?: number | string
26
+ categories?: string[]
27
+ }
28
+
29
+ function getPath(obj: unknown, path: string): unknown {
30
+ if (obj == null) return undefined
31
+ let cur: unknown = obj
32
+ for (const part of path.split('.')) {
33
+ if (cur == null) return undefined
34
+ if (Array.isArray(cur)) {
35
+ const idx = Number(part)
36
+ if (!Number.isInteger(idx)) return undefined
37
+ cur = cur[idx]
38
+ } else if (typeof cur === 'object') {
39
+ cur = (cur as Record<string, unknown>)[part]
40
+ } else {
41
+ return undefined
42
+ }
43
+ }
44
+ return cur
45
+ }
46
+
47
+ function pick(obj: unknown, paths: string[]): unknown {
48
+ for (const p of paths) {
49
+ const v = getPath(obj, p)
50
+ if (v !== undefined && v !== null && v !== '') return v
51
+ }
52
+ return undefined
53
+ }
54
+
55
+ function asString(v: unknown): string | undefined {
56
+ if (typeof v === 'string') return v.length > 0 ? v : undefined
57
+ if (typeof v === 'number') return String(v)
58
+ return undefined
59
+ }
60
+
61
+ function asStringOrNumber(v: unknown): number | string | undefined {
62
+ if (typeof v === 'number') return v
63
+ if (typeof v === 'string') return v.length > 0 ? v : undefined
64
+ return undefined
65
+ }
66
+
67
+ function isLinkedInUrl(v: unknown): v is string {
68
+ if (typeof v !== 'string' || v.length === 0) return false
69
+ return v.toLowerCase().includes('linkedin.com/')
70
+ }
71
+
72
+ function pickLinkedIn(obj: unknown, paths: string[]): string | undefined {
73
+ for (const p of paths) {
74
+ const v = getPath(obj, p)
75
+ if (isLinkedInUrl(v)) return v
76
+ }
77
+ return undefined
78
+ }
79
+
80
+ // Probe the keys providers use to hold the companies array. Covers the flat
81
+ // shapes (CompanyEnrich `items`, FullEnrich/BlitzAPI `companies`, Apollo
82
+ // `organizations`, generic `data`/`results`, AI-Ark `content`) and the one-level
83
+ // nested shape LinkUp uses (`data.companies[]`). Keys are kept in sync with the
84
+ // `hasResult` checks in registry.ts so compact never returns [] for a provider
85
+ // that actually matched.
86
+ export function extractCompaniesArray(data: unknown): unknown[] {
87
+ if (data == null) return []
88
+ if (Array.isArray(data)) return data
89
+ if (typeof data !== 'object') return []
90
+ const d = data as Record<string, unknown>
91
+ for (const key of ['items', 'companies', 'organizations', 'data', 'results', 'content']) {
92
+ const v = d[key]
93
+ if (Array.isArray(v)) return v
94
+ }
95
+ // LinkUp (search / fundraising / hiring) wraps results under data.companies[].
96
+ const inner = d.data
97
+ if (inner && typeof inner === 'object' && !Array.isArray(inner)) {
98
+ const di = inner as Record<string, unknown>
99
+ for (const key of ['companies', 'organizations', 'results', 'items', 'content']) {
100
+ const v = di[key]
101
+ if (Array.isArray(v)) return v
102
+ }
103
+ }
104
+ return []
105
+ }
106
+
107
+ export function normalizeCompany(record: unknown): CompactCompany | null {
108
+ if (!record || typeof record !== 'object' || Array.isArray(record)) return null
109
+ const r = record as Record<string, unknown>
110
+ const out: CompactCompany = {}
111
+
112
+ const name = asString(pick(r, ['name', 'company_name', 'organization.name', 'companyName']))
113
+ if (name) out.name = name
114
+
115
+ const domain = asString(pick(r, ['domain', 'primary_domain', 'website_domain', 'organization.primary_domain']))
116
+ if (domain) out.domain = domain
117
+
118
+ const website = asString(pick(r, ['website', 'website_url', 'organization.website_url', 'url']))
119
+ if (website) out.website = website
120
+
121
+ // LinkedIn URL is strict-validated: a wrong value silently breaks downstream
122
+ // find_people LinkedIn-driven lookups.
123
+ const linkedin = pickLinkedIn(r, [
124
+ 'socials.linkedin_url',
125
+ 'linkedin_url',
126
+ 'organization.linkedin_url',
127
+ 'social_profiles.linkedin.url',
128
+ ])
129
+ if (linkedin) out.linkedin_url = linkedin
130
+
131
+ const employees = asStringOrNumber(pick(r, [
132
+ 'employees',
133
+ 'employee_count',
134
+ 'employees_on_linkedin', // BlitzAPI
135
+ 'estimated_num_employees',
136
+ 'num_employees',
137
+ 'organization.estimated_num_employees',
138
+ 'size',
139
+ 'headcount',
140
+ ]))
141
+ if (employees !== undefined) out.employees = employees
142
+
143
+ const revenue = asStringOrNumber(pick(r, [
144
+ 'revenue',
145
+ 'estimated_revenue',
146
+ 'annual_revenue',
147
+ 'organization.annual_revenue',
148
+ ]))
149
+ if (revenue !== undefined) out.revenue = revenue
150
+
151
+ const industry = asString(pick(r, ['industry', 'organization.industry']))
152
+ if (industry) out.industry = industry
153
+
154
+ const country = asString(pick(r, [
155
+ 'location.country.name',
156
+ 'location.country.code',
157
+ 'country',
158
+ 'organization.country',
159
+ 'location_country',
160
+ ]))
161
+ if (country) out.country = country
162
+
163
+ const city = asString(pick(r, [
164
+ 'location.city.name',
165
+ 'city',
166
+ 'organization.city',
167
+ 'location_city',
168
+ ]))
169
+ if (city) out.city = city
170
+
171
+ const foundedYear = asStringOrNumber(pick(r, ['founded_year', 'organization.founded_year', 'foundation_date']))
172
+ if (foundedYear !== undefined) out.founded_year = foundedYear
173
+
174
+ const categories = getPath(r, 'categories')
175
+ if (Array.isArray(categories)) {
176
+ const cats = categories.filter((c): c is string => typeof c === 'string' && c.length > 0)
177
+ if (cats.length > 0) out.categories = cats
178
+ }
179
+
180
+ // Drop records that resolved to nothing meaningful.
181
+ if (Object.keys(out).length === 0) return null
182
+ return out
183
+ }
184
+
185
+ export interface CompactCompaniesPayload {
186
+ companies: CompactCompany[]
187
+ total?: number
188
+ }
189
+
190
+ export function compactCompaniesPayload(data: unknown): CompactCompaniesPayload {
191
+ const arr = extractCompaniesArray(data)
192
+ const companies: CompactCompany[] = []
193
+ for (const r of arr) {
194
+ const c = normalizeCompany(r)
195
+ if (c) companies.push(c)
196
+ }
197
+
198
+ const out: CompactCompaniesPayload = { companies }
199
+
200
+ // Preserve the total-addressable-market count when the provider exposes one
201
+ // (CompanyEnrich: data.totalItems; Apollo: data.pagination.total_entries).
202
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
203
+ const total = pick(data, ['totalItems', 'total', 'pagination.total_entries', 'pagination.total'])
204
+ if (typeof total === 'number') out.total = total
205
+ }
206
+
207
+ return out
208
+ }
@@ -47,7 +47,7 @@ describe('MCP polling interval schedules', () => {
47
47
  expect(30_000 / 1500).toBeGreaterThanOrEqual(20)
48
48
  })
49
49
 
50
- it('LeadsFactory find-people: shorter front, ≥7 polls in 5 minutes', () => {
50
+ it('LeadsFactory find-people: fast front probes, then flat 12s ceiling', () => {
51
51
  const providers = getProviders('find_people')
52
52
  const lf = providers.find((p) => p.id === 'leadsfactory')!
53
53
  expect(lf?.async).toBeDefined()
@@ -55,7 +55,13 @@ describe('MCP polling interval schedules', () => {
55
55
  expect(fn(1)).toBe(2000) // faster first probe vs old 3000
56
56
  expect(fn(2)).toBe(5000)
57
57
  expect(fn(3)).toBe(12000)
58
- // Validates ≥7 polls still fit in 5 min (guard against overly aggressive growth)
59
- expect(evalPollSchedule(fn, 300_000).length).toBeGreaterThanOrEqual(7)
58
+ // Flat ceiling: never grows past 12s, so completion-detection lag is bounded
59
+ // to ≤12s (the old +8s/attempt growth reached 44–72s gaps).
60
+ expect(fn(7)).toBe(12000)
61
+ expect(fn(20)).toBe(12000)
62
+ const schedule = evalPollSchedule(fn, 300_000)
63
+ expect(Math.max(...schedule)).toBe(12000) // never exceeds the ceiling
64
+ // Validates ≥7 polls still fit in 5 min
65
+ expect(schedule.length).toBeGreaterThanOrEqual(7)
60
66
  })
61
67
  })
@@ -551,20 +551,22 @@ describe('registry', () => {
551
551
  expect((result.body as Record<string, unknown>).company_linkedin_urls).toBeUndefined()
552
552
  })
553
553
 
554
- it('LeadsFactory has async config with continuous backoff schedule', () => {
554
+ it('LeadsFactory has async config with capped backoff schedule', () => {
555
555
  const providers = getProviders('find_people')
556
556
  const lf = providers.find((p) => p.id === 'leadsfactory')!
557
557
  expect(lf.async).toBeDefined()
558
558
  expect(lf.async!.timeoutMs).toBe(300_000)
559
- // Function-form pollIntervalMs: fast first probe, then continuously growing backoff.
559
+ // Function-form pollIntervalMs: fast first probes, then a flat 12s ceiling
560
+ // so completion-detection lag stays bounded (≤12s) instead of growing
561
+ // unbounded into 44–72s gaps.
560
562
  const sched = lf.async!.pollIntervalMs
561
563
  expect(typeof sched).toBe('function')
562
564
  const fn = sched as (attempt: number) => number
563
565
  expect(fn(1)).toBe(2000)
564
566
  expect(fn(2)).toBe(5000)
565
567
  expect(fn(3)).toBe(12000)
566
- expect(fn(4)).toBe(20000)
567
- expect(fn(8)).toBe(52000)
568
+ expect(fn(4)).toBe(12000)
569
+ expect(fn(8)).toBe(12000)
568
570
  // At least 7 polls must fit inside the 5-minute timeout — guards against
569
571
  // future tuning that would make the ramp so steep we only get 1–2 polls.
570
572
  let cumulative = 0