Skip to content

feat(search): free DuckDuckGo web search as last-resort provider (FCC port, Fase 6) - #4136

Merged
diegosouzapw merged 7 commits into
release/v3.8.29from
feat/fcc-web-search
Jun 18, 2026
Merged

diegosouzapw merged 7 commits into
release/v3.8.29from
feat/fcc-web-search

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Resumo

Adiciona um provider de web search grátis, sem API key — duckduckgo-free (scraping do lite.duckduckgo.com) — como último recurso (fallback) do /v1/search. É a Fase 6 do porte free-claude-code→OmniRoute (docs/superpowers/plans/2026-06-17-fcc-portable-features.md): web search funciona out-of-the-box (grátis) quando nenhum provider com chave está configurado.

Comportamento de seleção (decisão do operador: "último recurso"):

  • duckduckgo-free é fallbackOnly → nunca auto-selecionado sobre um provider pago configurado.
  • Provedores pagos com credencial sempre vencem; searxng (self-hosted) também.
  • Usado só quando nenhum provider com credencial está disponível (via o passo last-resort do failover), ou quando pedido explicitamente (provider: "duckduckgo-free").
  • cost 0, sem API key (authType: "none").

O que entra

  • open-sse/services/freeWebSearch.ts (novo): parseDuckDuckGoLite(html) (parser do HTML lite, aterrado no markup REAL capturado) + freeWebSearch() (fetch via safeOutboundFetch com guard public-only).
  • open-sse/config/searchRegistry.ts: flag fallbackOnly + entrada duckduckgo-free; selectProvider() exclui fallbackOnly da auto-seleção.
  • src/app/api/v1/search/route.ts: loops de auto-seleção e de alternate excluem fallbackOnly; novo passo last-resort que só usa um fallbackOnly quando nenhum provider com credencial foi achado.
  • open-sse/handlers/search.ts: tryDuckDuckGoFreeProvider() (caminho HTML dedicado, espelha o special-case tryZaiMCPProvider — o lite retorna HTML, não o JSON que o fluxo genérico espera).
  • src/shared/validation/schemas/apiV1.ts: duckduckgo-free no enum de providers (seleção explícita).

Segurança (endereçada na revisão independente)

  • ⭐ ReDoS: o parser roda regex sobre HTML externo → captures [\s\S]{0,N}? hard-bounded + HTML truncado a 256KB (CLAUDE.md PII §1). Teste de regressão prova retorno <1s em input adversarial.
  • ⭐ URLs: resolveResultUrl só retorna http(s):// — descarta javascript:/data:/file:/IP-interno antes de chegar ao LLM/cliente.
  • ⭐ SSRF: safeOutboundFetch com guard: "public-only" + allowRedirect: false (o guard só valida a URL inicial; sem seguir 3xx para hosts internos).

Validação (Hard Rule #18)

  • TDD — tests/unit/free-web-search.test.ts (9: parser fiel ao HTML real, <b>/entidades, decode uddg, protocol-relative, scheme-filter, ReDoS-bound), tests/unit/search-registry.test.ts (+fallbackOnly excluído do auto-select / explícito ainda funciona), tests/unit/search-handler-duckduckgo.test.ts (path explícito + failover searxng→DDG via fetch mockado retornando HTML real). 54/54.
  • Gates: typecheck:core exit 0, ESLint 0 erros / 0 warnings novos (paridade de any com origin), check:file-size (rebaseline search.ts 1442→1546 justificado), check:cycles, check:any-budget:t11, check:docs-sync — verdes.
  • Revisão independente adversarial: 1 blocker (ReDoS) + 2 concerns todos corrigidos.

Nota

  • A server-tool nativa do Claude (web_search_20250305) Claude→Claude continua em passthrough (não tocada). O DDG-free flui naturalmente pelo fallback tool omniroute_web_search→/v1/search quando esse caminho é usado.

Test Plan

  • node --import tsx/esm --test tests/unit/free-web-search.test.ts tests/unit/search-registry.test.ts tests/unit/search-handler-duckduckgo.test.ts → 54/54
  • npm run typecheck:core && npm run check:file-size && npm run check:any-budget:t11 → verdes

🤖 Generated with Claude Code

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

# Conflicts:
#	config/quality/file-size-baseline.json
The GET /v1/search registry-listing test still asserted 12 providers
and the pre-duckduckgo id list. duckduckgo-free is registered last in
SEARCH_PROVIDERS, so getAllSearchProviders() returns it at the tail —
update the count and append the id to the deepEqual.
@diegosouzapw
diegosouzapw merged commit d828e49 into release/v3.8.29 Jun 18, 2026
4 checks passed
@diegosouzapw diegosouzapw mentioned this pull request Jun 19, 2026
@diegosouzapw
diegosouzapw deleted the feat/fcc-web-search branch June 19, 2026 12:06
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
… port, Fase 6) (cheaper-inference#4136)

Free DuckDuckGo web search as last-resort provider (FCC port, Fase 6). Integrado em release/v3.8.29: conflito de file-size-baseline.json resolvido (união dos rebaselines duckduckgo_free + stream_recovery) e teste de contagem de providers atualizado 12->13.
diegosouzapw added a commit that referenced this pull request Oct 13, 2026
…nslateState

stream.ts is frozen in the file-size baseline. The initial translate state,
including the model-family-scoped signature namespace (#4136), is now built
by initTranslateStreamState, with a direct unit test.
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.

1 participant