From 37593a0f82eb6c5d5305054f2821f6e332e8b32e Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 22 Aug 2026 10:54:09 +0300 Subject: [PATCH 1/2] fix(search): fall back to duckduckgo-free when no search provider is configured POST /v1/search returned 400 'No credentials configured for any search provider' out of the box because the auto-select path only ever promoted fallback-only providers (duckduckgo-free, searxng-search) to the alternate slot, never to the primary one. When no paid provider had credentials the error was returned before the last-resort alternate step could run. Promote a fallback-only provider to primary as a last resort before failing, mirroring executeWebSearch(): duckduckgo-free runs with empty credentials, searxng-search only when explicitly configured, honoring blockedProviders. --- src/app/api/v1/search/route.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/app/api/v1/search/route.ts b/src/app/api/v1/search/route.ts index adb888dff778..3b8432d83bf6 100644 --- a/src/app/api/v1/search/route.ts +++ b/src/app/api/v1/search/route.ts @@ -56,7 +56,7 @@ export async function OPTIONS() { * GET /v1/search — list available search providers */ export async function GET() { - const settings = await getSettings().catch(() => ({} as any)); + const settings = await getSettings().catch(() => ({}) as any); const blockedProviders = settings?.blockedProviders || []; const providers = getAllSearchProviders().filter( (p) => !isProviderBlockedByIdOrAlias(p.id, blockedProviders) @@ -141,7 +141,7 @@ async function postHandler(request: Request, context: unknown) { const policy = await enforceApiKeyPolicy(request, "search"); if (policy.rejection) return policy.rejection; - const settings = await getSettings().catch(() => ({} as any)); + const settings = await getSettings().catch(() => ({}) as any); const blockedProviders = settings?.blockedProviders || []; // Resolve provider and credentials @@ -249,6 +249,34 @@ async function postHandler(request: Request, context: unknown) { } } + // Last resort before failing: promote a fallback-only free provider (e.g. + // duckduckgo-free) to the primary pick so out-of-the-box search works when + // no credentialed provider is configured at all. + if (!credentials) { + const fallbackProviders = Object.values(SEARCH_PROVIDERS) + .filter( + (provider) => + provider.fallbackOnly && + supportsSearchType(provider, body.search_type) && + !isProviderBlockedByIdOrAlias(provider.id, blockedProviders) + ) + .sort((a, b) => a.costPerQuery - b.costPerQuery); + + for (const fallbackProvider of fallbackProviders) { + providerConfig = fallbackProvider; + if (fallbackProvider.id === "duckduckgo-free") { + credentials = {}; + break; + } + const fallbackCreds = await resolveSearchCredentials(fallbackProvider.id); + if (isAllRateLimitedCredentials(fallbackCreds)) continue; + if (fallbackCreds) { + credentials = fallbackCreds; + break; + } + } + } + if (!credentials) { if (firstRateLimitedCredentials) { return rateLimitedProviderResponse( From c4056442fd370acbc909f46742da72f65a011e4f Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 21 Aug 2026 00:40:21 +0300 Subject: [PATCH 2/2] fix(logo): suppress hydration warning for Dark Reader extension The Dark Reader browser extension injects data-darkreader-* attributes and --darkreader-* CSS variables into SVG elements after React hydration, causing a hydration mismatch warning. Adding suppressHydrationWarning to the root SVG element is the standard React pattern to suppress this browser-extension-induced hydration mismatch. Fixes: Console warning about mismatched data-darkreader-inline-stroke and --darkreader-inline-stroke attributes on OmniRouteLogo lines. --- src/shared/components/OmniRouteLogo.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/components/OmniRouteLogo.tsx b/src/shared/components/OmniRouteLogo.tsx index 32a09cb6fa8a..a6520fa6802d 100644 --- a/src/shared/components/OmniRouteLogo.tsx +++ b/src/shared/components/OmniRouteLogo.tsx @@ -16,6 +16,7 @@ export default function OmniRouteLogo({ size = 20, className = "" }: OmniRouteLo fill="none" xmlns="http://www.w3.org/2000/svg" className={className} + suppressHydrationWarning > {/* Central node */}