Skip to content

Web-search connector (Google CSE) + extract_contacts step — enrichment (#99) - #111

Merged
serge-ivo merged 1 commit into
mainfrom
feat/99-web-search-connector
Aug 1, 2026
Merged

Web-search connector (Google CSE) + extract_contacts step — enrichment (#99)#111
serge-ivo merged 1 commit into
mainfrom
feat/99-web-search-connector

Conversation

@serge-ivo

Copy link
Copy Markdown
Contributor

Closes #99. Part of #94 / #84. Builds on #86 (connectorClient) + #95 (safeFetch/vault) + #96 (step catalog).

Adds a web-search connector (auth:token, grantModel:user, read-scoped) exposing web_search(query,[num],[cx]) → results[{title,link,snippet}] over the Google Custom Search JSON API. The API key is read from its own vault slot (user_api_keys provider web-search, separate from http) via connectorClient — never in inputs/schema/output (asserted, incl. no echo on upstream error). The cx (CSE id, not secret) is a tool input or WEB_SEARCH_CX env default. Wire via safeFetch (SSRF-guarded); dispatched through runRegistryTool for audit/grant; num capped at 10.

Adds a pure extract_contacts(items,[fields]) → {instagram,facebook,email,precision:'best-effort'} step (no I/O — reads text web_search already fetched) that pulls instagram/facebook/mailto from results.

Acceptance proof: a test composes web_searchextract_contacts (both via the registry) to populate instagram/facebook/email from a business name+suburb as pure config.

Notes: Google CSE only (not SerpAPI — acceptance doesn't need both); extracts from snippets/links (keeps the step pure, no per-page fetch), precision surfaced in output.

Verify: tsc clean · 864 api tests pass (+22, 0 regressions) · biome clean.

🤖 Generated with Claude Code

… enrichment (#99)

Enriching a lead with its socials/email needs web search, which the generic
http connector (#95) can't cleanly cover: the search API key must live in its
OWN vault slot, separate from the http connector's key. So this is a first-class
connector.

- web-search connector (auth:"token", grantModel:"user", read-only, no
  tokenEnv → connectorClient reads user_api_keys provider "web-search").
  Provides one web_search(query,[num],[cx]) → results[{title,link,snippet}]
  tool wrapping the Google Custom Search JSON API. The KEY comes from the vault
  and is injected onto the request URL only — never in inputs/schema/output.
  The CSE id (cx) is NOT secret: a tool input, else the WEB_SEARCH_CX env
  default. Every call goes through safeFetch (#95 SSRF guard), not a
  re-implemented fetch. Per-call result cap (max 10, default 5).
- extract_contacts step (steps.ts): pure, no I/O — scans web_search results for
  the first Instagram profile URL, Facebook page URL, and email (mailto:
  preferred), skipping non-profile/share/login paths → {instagram, facebook,
  email, precision:"best-effort"}. Accepts raw rows or the {results:[…]}
  envelope.
- Dispatched through runRegistryTool so audit/grant/scope come for free.

Proof (acceptance): given a business name+suburb, web_search + extract_contacts
populate instagram/facebook/email as pure config (test with a mocked CSE
response). Unit tests: web_search shape, key-from-vault-not-leaked (incl. no
key input in schema + no key echoed on upstream error), endpoint wiring,
num cap, cx override, SSRF path; extract_contacts link/email extraction +
non-profile/share filtering + field subset.

api tsc clean; api suite 864 passing (842 baseline + 22 new, 0 regressions).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[connectors] Web-search connector (Google Custom Search / SerpAPI) — enrichment

2 participants