Skip to content

🧭 feat: Add Tavily Search And Scraper Providers - #135

Merged
danny-avila merged 21 commits into
devfrom
danny-avila/pr-85-tavily
May 4, 2026
Merged

danny-avila merged 21 commits into
devfrom
danny-avila/pr-85-tavily

Conversation

@danny-avila

@danny-avila danny-avila commented May 3, 2026 •

Copy link
Copy Markdown
Collaborator

I continued #85 on a maintainer-owned branch because the contributor fork rejected maintainer pushes with HTTP 403, and rebased the Tavily work onto latest origin/dev.

  • Add Tavily as a search provider and scraper provider for the web search tool.
  • Implement Tavily Extract batching, failure handling, metadata extraction, favicon propagation, and per-call scraper option overrides.
  • Support Tavily string modes for includeAnswer and includeRawContent, and map Tavily answers/images into the existing search result shape.
  • Add focused Tavily search/scraper tests covering request payloads, mixed extraction results, batching, metadata, and missing-key behavior.

Change Type

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)

Testing

  • NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand
  • npx tsc --noEmit

Test Configuration:

  • Node.js: local workspace Node
  • Base branch: origin/dev

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

MayRamati and others added 7 commits May 4, 2026 06:08
- Replace per-provider union types with `AnyScraperResponse` named type
- Add optional `scrapeUrls` batch method to `BaseScraper` interface
- Rename `raw_content` to `rawContent` in `TavilyScrapeResponse` (camelCase convention)
- Add `TavilySearchResult` and `TavilyExtractResult` named types
- Split `TAVILY_API_URL` into separate `tavilySearchUrl` and `tavilyExtractUrl` fields
- Add configurable `searchDepth` to `SearchConfig`
- Flatten `TavilyConfig` into `SearchToolConfig` via `SearchConfig` extension
Major fixes:
- Wrap `new URL().hostname` in try/catch to prevent single bad URL from
  discarding entire result set (Finding #1)
- Remove Tavily from country schema — Tavily API does not support
  country filtering (Finding #2)
- Split shared `TAVILY_API_URL` into `TAVILY_SEARCH_URL` and
  `TAVILY_EXTRACT_URL` to avoid env var collision (Finding #3)
- Reduce default timeout from 60s to 15s, matching other providers
  (Finding #4)
- Implement batch URL extraction via `scrapeUrls` — Tavily Extract
  supports up to 20 URLs per request (Finding #5)
- Remove dead `topStories` computation; let `news` flow through the
  existing `executeParallelSearches` merge pipeline (Finding #6)
- Use `scraper.extractMetadata()` abstraction instead of runtime
  `'metadata' in` check (Finding #8)
- Read `failed_results` from Tavily Extract API for actionable error
  messages (Finding #12)
- Add JSDoc for `'h'` → `'day'` time range approximation (Finding #13)
- Make `search_depth` configurable via `searchDepth` config (Finding #14)
- Hoist `TAVILY_TIME_RANGE_MAP` to module scope (Finding #15)
- Use batch-aware `scrapeMany` that delegates to `scrapeUrls` when
  available on the scraper instance
Covers constructor defaults, env var handling, single URL scraping,
batch scraping with chunking, mixed success/failure, extractContent,
and extractMetadata — 18 tests total.
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 77318d8a6a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/search.ts Outdated
Comment on lines +449 to +455
const getSources = async ({
query,
date,
numResults = 8,
type,
news,
}: t.GetSourcesParams): Promise<t.SearchResult> => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Forward country filter to Tavily requests

The new Tavily provider drops localization input because getSources does not read country from GetSourcesParams, so the payload can never include a country constraint. executeParallelSearches still passes country for all searches, so users switching from Serper/SearXNG lose expected locale targeting with Tavily and get broader/global results instead. Tavily’s Search API supports a country field, so this is a functional regression for country-scoped queries.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 23f8a05. Tavily search now forwards the normalized country filter in the API payload, and the Tavily tool schema exposes the country field when Tavily is selected. Covered by the updated Tavily search unit test and verified with NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand plus npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23f8a05c3b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/tool.ts
Comment on lines +369 to 370
if (searchProvider === 'serper' || searchProvider === 'tavily') {
schemaProperties.country = countrySchema;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid exposing Serper-style country codes for Tavily

This change enables country for Tavily by reusing countrySchema, whose prompt text tells the model to send 2-letter codes (e.g. us, de). Tavily Search expects full country names (e.g. united states), so requests generated from this schema are likely to be ignored or fail to localize correctly once routed through Tavily. Please add a Tavily-specific country schema (or translate codes to Tavily country names) before enabling this field for that provider.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1d661de. I kept the shared two-letter country schema exposed for Tavily, but translate those inputs to Tavily country-name values before building the request payload, including uk -> united kingdom. The Tavily search test now expects US to be sent as united states.

Comment thread src/tools/search/search.ts Outdated
Comment on lines +481 to +483
const normalizedCountry = country?.trim().toLowerCase();
if (normalizedCountry != null && normalizedCountry !== '') {
payload.country = normalizedCountry;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Omit Tavily country when topic is not general

The request builder always sets payload.country when a country is provided, but Tavily documents country as valid only for topic: "general". In this tool flow, news searches call Tavily with type: 'news' (thus topic: 'news') while still forwarding country, which can cause request validation errors or dropped news results whenever users include country filtering.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1d661de. Tavily country is now added only when the resolved topic is general, so news-topic searches omit country. Covered by a new Tavily unit test that calls getSources with news: true and asserts the payload has topic: news without country. Local verification: NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d661dee1d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/search.ts Outdated
Comment on lines +499 to +504
const payload: Record<string, unknown> = {
query,
search_depth: searchDepth,
topic,
max_results: Math.min(Math.max(1, maxResults), 20),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor safeSearch when calling Tavily Search API

The Tavily integration drops the safeSearch setting entirely: getSources never reads it and the request payload never sets Tavily’s safe_search field, so searches run with Tavily’s default safe_search=false even when callers configure moderation (including the tool’s default safe mode). This is a functional regression for safety-sensitive deployments using searchProvider: 'tavily', because unsafe/adult results are no longer filtered when they were expected to be.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c4560e. Tavily getSources now reads safeSearch and sends safe_search in the request payload, defaulting to enabled unless callers explicitly pass safeSearch: 0. The Tavily search unit test covers safe_search: true for moderated search and safe_search: false for explicit off. Local verification: NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c4560ee85

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/search.ts Outdated
search_depth: searchDepth,
topic,
max_results: Math.min(Math.max(1, maxResults), 20),
safe_search: (safeSearch ?? 1) !== 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Omit safe_search for unsupported Tavily search depths

Always setting safe_search in the Tavily payload can break valid configurations: this field is enterprise-only and explicitly unsupported for fast/ultra-fast depths, but the new code sends it for every request (including when searchDepth is fast or ultra-fast). In those cases Tavily can reject the request, causing the entire search call to fail instead of returning results.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 36b1a39. Tavily safe_search is now only included for supported search depths; fast and ultra-fast omit the field so those configurations remain valid. Added a Tavily unit test for searchDepth: fast that asserts safe_search is not sent. Local verification: NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36b1a396bc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/search.ts Outdated
Comment on lines +440 to +443
if (/^[a-z]{2}$/.test(normalizedCountry)) {
return TAVILY_REGION_NAMES.of(
normalizedCountry.toUpperCase()
)?.toLowerCase();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Map ISO country codes to Tavily enum values

The 2-letter country normalization currently relies on Intl.DisplayNames, which can emit names that do not match Tavily’s documented country enum (for example cz becomes czechia, while Tavily expects czech republic). In those cases, localized searches can fail with 400s or silently lose localization, so valid user inputs become unreliable depending on the country code.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee25701. Country normalization now includes Tavily-specific enum aliases for ISO codes whose Intl display names differ from Tavily values, including cz -> czech republic, plus punctuation/diacritic normalization for display-name fallbacks. Added a Tavily request test that sends country: CZ and asserts country: czech republic.

Comment thread src/tools/search/tavily-scraper.ts Outdated
Comment on lines +98 to +99
const effectiveTimeout = options.timeout ?? this.timeout;
payload.timeout = Math.min(Math.max(effectiveTimeout / 1000, 1), 60);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Tavily extract depth-based timeout defaults

This always sends a timeout value derived from the client timeout, including the default 15000ms (15s). Tavily’s Extract API applies depth-aware defaults when timeout is omitted (10s for basic, 30s for advanced), so this overrides advanced requests to a shorter timeout and can cause avoidable extraction failures on slower pages.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee25701. Tavily extract payloads now omit timeout when no timeout was explicitly configured, preserving Tavily depth-aware defaults for basic/advanced extraction while still keeping the local axios timeout. Explicit per-call/configured timeouts continue to be sent. Added a Tavily scraper test for advanced extraction that asserts payload.timeout is omitted. Local verification: NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee25701ca4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/search.ts Outdated
Comment on lines +523 to +525
if (searchDepth !== 'fast' && searchDepth !== 'ultra-fast') {
payload.safe_search = (safeSearch ?? 1) !== 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid sending Tavily enterprise-only safe_search by default

This path always injects safe_search for Tavily when search_depth is basic/advanced, and with the tool defaults that means every Tavily request sends safe_search: true unless callers explicitly override it. Tavily documents safe_search as an enterprise-only option, so standard-plan keys can be rejected for otherwise valid searches; gate this field behind an explicit Tavily option (or omit it unless the caller opts in) to keep baseline Tavily usage working.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 21802df. Tavily safe_search is now gated behind the explicit tavilySearchOptions.safeSearch flag and is no longer sent from the generic safeSearch default, so standard Tavily requests omit the enterprise-only field. The unsupported-depth guard still applies for fast/ultra-fast. Updated tests cover explicit opt-in, default omission, and fast-depth omission. Local verification: NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 21802df699

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/search.ts Outdated
Comment on lines +564 to +565
if (options?.chunksPerSource != null) {
payload.chunks_per_source = options.chunksPerSource;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate Tavily chunks_per_source by advanced search depth

Only advanced Tavily searches accept chunks_per_source, but this code always forwards chunksPerSource whenever it is set in config. With the default search_depth of basic, requests that include this field can be rejected by Tavily, turning valid searches into provider errors for users who enable chunk tuning without also forcing advanced depth.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7996aec. Tavily search now only forwards chunks_per_source when searchDepth is advanced; basic/fast/ultra-fast omit it. Added a unit test that verifies basic omits chunks_per_source while advanced sends it.

Comment thread src/tools/search/tavily-scraper.ts Outdated
Comment on lines +93 to +95
if (chunksPerSource != null) {
payload.chunks_per_source = chunksPerSource;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove unsupported chunks_per_source from Tavily extract calls

The Tavily scraper exposes chunksPerSource and unconditionally sends chunks_per_source, but this extract flow never sends a query field. Tavily Extract only supports chunk selection for query-reranked extraction, so enabling tavilyScraperOptions.chunksPerSource can cause every scrape in that batch to fail with API validation errors.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7996aec. Tavily extract no longer exposes or sends chunks_per_source because this scraper flow does not provide a query for query-reranked extraction. Updated the scraper option test to assert chunks_per_source is absent. Local verification: NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7996aeca31

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/search.ts Outdated
Comment on lines +523 to +527
options?.safeSearch != null &&
searchDepth !== 'fast' &&
searchDepth !== 'ultra-fast'
) {
payload.safe_search = options.safeSearch;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Respect safeSearch config for Tavily queries

executeParallelSearches passes safeSearch on every search request, but the Tavily path only reads options?.safeSearch from static tavilySearchOptions. As a result, createSearchTool({ safeSearch: ... }) is ignored for Tavily unless callers also duplicate that setting in tavilySearchOptions, which silently drops the expected safety filtering behavior.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 88fef38. Explicit createSearchTool({ safeSearch }) now becomes tavilySearchOptions.safeSearch for Tavily, so configured safety filtering is honored while the default still omits the enterprise-only safe_search field. Added a high-level search tool test that asserts safe_search is sent when safeSearch: 2 is explicit.

Comment thread src/tools/search/search.ts Outdated
organic: organicResults,
images: imageResults,
topStories: [],
videos: [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip Tavily video mode requests that cannot populate videos

The Tavily mapper always emits videos: [], and this implementation never handles type === 'videos', so a videos subquery still incurs an extra Tavily request but cannot contribute video results. In the videos: true flow this adds cost and latency with no user-visible data unless a video-specific mapping is implemented or the call is skipped.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 88fef38. Tavily search tools now skip video subqueries because the Tavily mapper does not produce video results. The same high-level test passes videos: true and asserts only one Tavily API call is made. Local verification: NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88fef383c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/tavily-scraper.ts Outdated
config.apiUrl ??
process.env.TAVILY_EXTRACT_URL ??
'https://api.tavily.com/extract';
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Increase default Tavily extract timeout for advanced mode

The new scraper hardcodes a 15s client timeout (DEFAULT_TIMEOUT) regardless of extractDepth, but Tavily advanced extraction allows significantly longer processing when no explicit per-request timeout is set. With extractDepth: 'advanced' and default settings, requests that legitimately take longer than 15s will be aborted client-side and reported as failures even though the provider would still return successfully, which can make advanced scraping unreliable on heavier pages.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f9b66d3. Tavily extract now uses a 30s client timeout for advanced extraction defaults, including per-call advanced overrides, while still omitting the Tavily payload timeout unless the user explicitly configures one. Covered by the focused Tavily scraper tests and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9b66d3055

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/tool.ts
Comment on lines +363 to +366
searchProvider === 'tavily' && config.safeSearch != null
? {
safeSearch: config.safeSearch !== 0,
...tavilySearchOptions,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply default safeSearch when using Tavily provider

createSearchTool sets safeSearch = 1 by default, but this value is never propagated to Tavily unless config.safeSearch is explicitly set. In the common case where callers rely on defaults, effectiveTavilySearchOptions stays undefined and Tavily requests omit safe_search, falling back to Tavily’s default (false). That makes Tavily return less-filtered content than other providers under the same default tool config.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am leaving this unchanged. Tavily documents safe_search as enterprise-only and unsupported for fast/ultra-fast search depths (https://docs.tavily.com/documentation/api-reference/endpoint/search), so propagating the default tool safeSearch value would make baseline Tavily usage fail for standard-plan keys. The current behavior still honors explicit safety config: callers can set safeSearch on the tool/config or tavilySearchOptions.safeSearch, and the Tavily payload sends safe_search only for supported depths.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9b66d3055

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/search.ts Outdated
Comment on lines +455 to +459
if (/^[a-z]{2}$/.test(normalizedCountry)) {
const regionName = TAVILY_REGION_NAMES.of(normalizedCountry.toUpperCase());
return regionName != null
? normalizeTavilyCountryName(regionName)
: undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate Tavily country mapping before setting payload

The 2-letter country path currently converts any ISO code via Intl.DisplayNames and forwards it directly to Tavily, but Tavily only accepts a fixed country enum. Codes like CD map to values such as congo - kinshasa (and other non-enum names), which can make otherwise valid searches fail with a request error once payload.country is set. This should be gated by an allowlist of Tavily-supported country values (or omitted when the mapped name is not supported).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 897cf8f. Tavily country normalization now validates the mapped value against the supported Tavily country enum before adding payload.country; unsupported ISO mappings such as CD are omitted instead of being forwarded. Added a Tavily unit test for the unsupported ISO path. Local verification: NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 897cf8f069

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/tool.ts
Comment on lines +409 to +413
scraperInstance = createTavilyScraper({
...tavilyScraperOptions,
apiKey: tavilyApiKey ?? process.env.TAVILY_API_KEY,
apiUrl: tavilyExtractUrl,
timeout: scraperTimeout ?? tavilyScraperOptions?.timeout,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve scraper-specific Tavily endpoint and key overrides

Do not overwrite tavilyScraperOptions connection fields with possibly undefined top-level values. In this object literal, apiUrl: tavilyExtractUrl and apiKey: tavilyApiKey ?? process.env.TAVILY_API_KEY run after ...tavilyScraperOptions, so a caller-provided tavilyScraperOptions.apiUrl/apiKey is discarded whenever the top-level fields are unset. In deployments that route Extract through a custom proxy or per-scraper credential, the scraper silently falls back to the default endpoint/env key and requests fail or go to the wrong backend.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 35a50b5. Tavily scraper-level apiKey and apiUrl now take precedence over top-level Tavily values when creating the scraper, so custom extract proxies and per-scraper credentials are preserved. Added a search-tool regression test that verifies search uses the top-level key while extract uses the scraper override URL/key. Local verification: NODE_OPTIONS="--experimental-vm-modules" npx jest src/tools/search/tavily.test.ts --runInBand and npx tsc --noEmit.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 68fcec60da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/tavily-search.ts Outdated
Comment on lines +293 to +295
const topic =
options?.topic ??
(news === true || type === 'news' ? 'news' : 'general');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prioritize per-request news mode over default Tavily topic

Make the request-level news intent take precedence over tavilySearchOptions.topic. Right now options?.topic is chosen first, so if a tool is configured with topic: 'finance' (or 'general'), a later type: 'news' request is silently downgraded and no news items are produced/mapped for that call. This breaks the news flag path in executeParallelSearches for those configurations and causes users to request news but receive non-news search behavior.

Useful? React with 👍 / 👎.

Comment thread src/tools/search/tool.ts Outdated
Comment on lines +365 to +366
safeSearch: config.safeSearch !== 0,
...tavilySearchOptions,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let top-level safeSearch override Tavily option values

Ensure explicit safeSearch in createSearchTool wins over tavilySearchOptions.safeSearch. The current merge order sets safeSearch and then spreads tavilySearchOptions, so any safeSearch inside provider options overrides the top-level setting. In practice, configurations that set safeSearch: 0 (or 2) can be silently inverted by inherited Tavily options, producing unexpected filtering behavior at runtime.

Useful? React with 👍 / 👎.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

1 similar comment
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d1c305d46

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/search/tool.ts
Comment on lines +363 to +367
searchProvider === 'tavily' && config.safeSearch != null
? {
...tavilySearchOptions,
safeSearch: config.safeSearch !== 0,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate default safeSearch to Tavily options

createSearchTool assigns safeSearch = 1 by default, but this block only forwards a Tavily safeSearch value when config.safeSearch is explicitly provided. In the common case where callers rely on defaults, Tavily requests are emitted without safe_search, so filtering behavior differs from the tool’s configured default and from other providers unless users manually set safeSearch every time.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this intentionally unchanged. Tavily safe_search is enterprise-only, and earlier review feedback required avoiding default propagation so basic/non-enterprise Tavily configs do not break. The code still honors explicit safeSearch values: Tavily-specific tavilySearchOptions.safeSearch is forwarded, and an explicitly provided top-level safeSearch now takes precedence over that provider option. The default tool value is intentionally not sent to Tavily.

@danny-avila
danny-avila merged commit 8d220a8 into dev May 4, 2026
4 checks passed
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.

3 participants