feat(FN-4135): add always-on web search with locked provider setting
Adds always-on web search capability to the research system, locking the web search provider in settings UI and updating the ResearchView to surface web search as a persistent toggle with agent tool integration, plus corresponding docs and test coverage. Fusion-Task-Id: FN-4135
This commit is contained in:
5
.changeset/fn-4135-always-on-web-search.md
Normal file
5
.changeset/fn-4135-always-on-web-search.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Keep web search always enabled in the Research view and remove the `none` web-search provider option plus the per-project Web Search source toggle from settings.
|
||||
@@ -53,7 +53,7 @@ This also reveals the **Research Defaults** and **Research** settings sections i
|
||||
|
||||
### 2. Built-in web search is the default
|
||||
|
||||
By default, `researchGlobalWebSearchProvider` resolves to `"builtin"`. Search and fetch run through the agent runtime's native `WebSearch` and `WebFetch` tools, so no API key is required for baseline usage.
|
||||
By default, `researchGlobalWebSearchProvider` resolves to `"builtin"`. Search and fetch run through the agent runtime's native `WebSearch` and `WebFetch` tools, so no API key is required for baseline usage, and web search stays enabled even if older persisted settings still contain `enabledSources.webSearch: false`.
|
||||
|
||||
### 3. Optional: external search backends
|
||||
|
||||
@@ -66,7 +66,6 @@ You can opt into external providers in global settings:
|
||||
| `"brave"` | `researchGlobalBraveApiKey` — Brave Search API key |
|
||||
| `"google"` | `researchGlobalGoogleSearchApiKey` + `researchGlobalGoogleSearchCx` — Google Custom Search credentials |
|
||||
| `"tavily"` | `researchGlobalTavilyApiKey` — Tavily API key |
|
||||
| `"none"` | Disables web search (Page Fetch, Local Docs, GitHub, and LLM synthesis can still run) |
|
||||
|
||||
API keys are stored through Fusion's auth credential pipeline (`/api/auth/api-key`), not in settings JSON directly.
|
||||
|
||||
@@ -304,7 +303,7 @@ Before creating runs, `fn_research_run` checks:
|
||||
2. Web search is available (`"builtin"` by default, or an explicitly configured external backend)
|
||||
3. Required API keys are present for external providers
|
||||
|
||||
If a check fails, the tool returns an actionable error with setup guidance instead of crashing. In practice, with the default `"builtin"` backend, provider-setup errors are mostly limited to explicit external-provider selections or explicit `"none"` opt-out.
|
||||
If a check fails, the tool returns an actionable error with setup guidance instead of crashing. In practice, with the default `"builtin"` backend, provider-setup errors are mostly limited to explicit external-provider selections.
|
||||
|
||||
### Best practices for agents
|
||||
|
||||
@@ -437,7 +436,7 @@ When all retries are exhausted, the run transitions to `retry_exhausted`.
|
||||
| Symptom | Cause | Resolution |
|
||||
|---|---|---|
|
||||
| "Research is disabled in settings" | `researchGlobalEnabled` or `researchSettings.enabled` is `false` | Enable in Settings → Research |
|
||||
| "Research provider is not configured" | Web search was explicitly disabled (`researchGlobalWebSearchProvider: "none"`) or external provider setup is incomplete | Re-enable builtin search, or finish configuring your selected external provider in Settings |
|
||||
| "Research provider is not configured" | External provider setup is incomplete | Switch back to builtin search or finish configuring your selected external provider in Settings |
|
||||
| "Missing API key for {provider}" | Auth credential not found | Configure provider credentials in Settings → Authentication |
|
||||
| Run stuck in `queued` | Engine not running or no available concurrency slots | Start the project engine; check `maxConcurrentRuns` |
|
||||
| Run times out | Provider slow or `maxDurationMs` too low | Increase timeout in project research settings |
|
||||
|
||||
@@ -92,7 +92,7 @@ Fusion automatically falls back to ntfy's JSON publish format when a notificatio
|
||||
| `researchGlobalDefaultTimeout` | `number` | `300000` | Default timeout for end-to-end research runs in milliseconds (5 minutes). |
|
||||
| `researchGlobalMaxSourcesPerRun` | `number` | `20` | Maximum number of sources per research run. |
|
||||
| `researchGlobalMaxSynthesisRounds` | `number` | `2` | Maximum synthesis rounds per research run. |
|
||||
| `researchGlobalWebSearchProvider` | `"builtin" \| "searxng" \| "brave" \| "google" \| "tavily" \| "none"` | `"builtin"` | Web search backend for research. Default: `"builtin"` (uses agent-native WebSearch/WebFetch tools with no API key requirement). |
|
||||
| `researchGlobalWebSearchProvider` | `"builtin" \| "searxng" \| "brave" \| "google" \| "tavily"` | `"builtin"` | Web search backend for research. Default: `"builtin"` (uses agent-native WebSearch/WebFetch tools with no API key requirement). Web search itself is always enabled. |
|
||||
| `researchGlobalSearxngUrl` | `string` | `undefined` | SearXNG instance URL (required when provider is `"searxng"`). |
|
||||
| `researchGlobalBraveApiKey` | `string` | `undefined` | Brave Search API key (required when provider is `"brave"`). |
|
||||
| `researchGlobalGoogleSearchApiKey` | `string` | `undefined` | Google Custom Search API key (required when provider is `"google"`). |
|
||||
|
||||
@@ -43,7 +43,6 @@ async function getStore(projectName?: string): Promise<TaskStore> {
|
||||
|
||||
function hasProviderCredentials(settings: Awaited<ReturnType<TaskStore["getSettings"]>>, providerId: string | undefined): boolean {
|
||||
if (!providerId || providerId === "builtin") return true;
|
||||
if (providerId === "none") return false;
|
||||
if (providerId === "searxng") return Boolean(settings.researchGlobalSearxngUrl);
|
||||
if (providerId === "brave") return Boolean(settings.researchGlobalBraveApiKey);
|
||||
if (providerId === "google") return Boolean(settings.researchGlobalGoogleSearchApiKey && settings.researchGlobalGoogleSearchCx);
|
||||
@@ -59,7 +58,7 @@ async function getResearchRuntime(store: TaskStore) {
|
||||
}
|
||||
|
||||
const configuredProvider = (resolved.searchProvider as string | undefined) ?? settings.researchGlobalWebSearchProvider ?? "builtin";
|
||||
if (configuredProvider !== "builtin" && configuredProvider !== "none" && !hasProviderCredentials(settings, configuredProvider)) {
|
||||
if (configuredProvider !== "builtin" && !hasProviderCredentials(settings, configuredProvider)) {
|
||||
throw new Error(`missing-credentials: ${configuredProvider} credentials are missing. Configure Authentication and Research defaults in settings.`);
|
||||
}
|
||||
|
||||
|
||||
@@ -240,9 +240,7 @@ async function getResearchAvailability(store: TaskStore): Promise<{ ok: boolean;
|
||||
const backend = (resolved.searchProvider as string | undefined) ?? settings.researchGlobalWebSearchProvider ?? "builtin";
|
||||
const configured = backend === "builtin"
|
||||
? true
|
||||
: backend === "none"
|
||||
? false
|
||||
: backend === "searxng"
|
||||
: backend === "searxng"
|
||||
? Boolean(settings.researchGlobalSearxngUrl)
|
||||
: backend === "brave"
|
||||
? Boolean(settings.researchGlobalBraveApiKey)
|
||||
|
||||
@@ -95,12 +95,25 @@ describe("resolveResearchSettings", () => {
|
||||
expect(resolved.searchProvider).toBe("brave");
|
||||
});
|
||||
|
||||
it("honors explicit legacy global search provider opt-out", () => {
|
||||
it("forces web search on even when persisted settings disable it", () => {
|
||||
const resolved = resolveResearchSettings({
|
||||
researchGlobalWebSearchProvider: "none",
|
||||
researchGlobalDefaults: {
|
||||
enabledSources: {
|
||||
webSearch: false,
|
||||
pageFetch: true,
|
||||
github: false,
|
||||
localDocs: true,
|
||||
llmSynthesis: true,
|
||||
},
|
||||
},
|
||||
researchSettings: {
|
||||
enabledSources: {
|
||||
webSearch: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved.searchProvider).toBe("none");
|
||||
expect(resolved.enabledSources.webSearch).toBe(true);
|
||||
});
|
||||
|
||||
it("supports null-as-delete semantics for researchSettings object", () => {
|
||||
|
||||
@@ -22,8 +22,7 @@ export interface ResolvedResearchSettings {
|
||||
|
||||
const DEFAULT_SEARCH_PROVIDER = "builtin";
|
||||
|
||||
const FALLBACK_SOURCES: ResearchEnabledSources = {
|
||||
webSearch: true,
|
||||
const FALLBACK_SOURCES: Omit<ResearchEnabledSources, "webSearch"> = {
|
||||
pageFetch: true,
|
||||
github: false,
|
||||
localDocs: true,
|
||||
@@ -44,10 +43,7 @@ export function resolveResearchSettings(settings: Partial<Settings> | undefined)
|
||||
synthesisProvider: projectSettings?.synthesisProvider ?? globalDefaults?.synthesisProvider,
|
||||
synthesisModelId: projectSettings?.synthesisModelId ?? globalDefaults?.synthesisModelId,
|
||||
enabledSources: {
|
||||
webSearch:
|
||||
projectSettings?.enabledSources?.webSearch ??
|
||||
globalDefaults?.enabledSources?.webSearch ??
|
||||
FALLBACK_SOURCES.webSearch,
|
||||
webSearch: true,
|
||||
pageFetch:
|
||||
projectSettings?.enabledSources?.pageFetch ??
|
||||
globalDefaults?.enabledSources?.pageFetch ??
|
||||
|
||||
@@ -1531,7 +1531,7 @@ export interface DaemonTokenSettings {
|
||||
* The dashboard UI shows these under a "Global" section.
|
||||
*/
|
||||
/** Web search backend for auto-research provider. */
|
||||
export type WebSearchBackend = "builtin" | "searxng" | "brave" | "google" | "tavily" | "none";
|
||||
export type WebSearchBackend = "builtin" | "searxng" | "brave" | "google" | "tavily";
|
||||
|
||||
export interface ResearchEnabledSources {
|
||||
webSearch: boolean;
|
||||
@@ -1858,7 +1858,7 @@ export interface GlobalSettings {
|
||||
/** Default maximum number of synthesis rounds per run.
|
||||
* Default: 2. */
|
||||
researchGlobalMaxSynthesisRounds?: number;
|
||||
/** Web search backend for auto-research. Default: "builtin". */
|
||||
/** Web search backend for auto-research. Default: "builtin"; web search itself cannot be disabled. */
|
||||
researchGlobalWebSearchProvider?: WebSearchBackend;
|
||||
/** SearXNG instance URL (required when researchGlobalWebSearchProvider is "searxng"). */
|
||||
researchGlobalSearxngUrl?: string;
|
||||
|
||||
@@ -57,7 +57,6 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
|
||||
} = useResearch({ projectId });
|
||||
const [query, setQuery] = useState("");
|
||||
const [effectiveSettings, setEffectiveSettings] = useState(() => resolveResearchSettings(undefined));
|
||||
const [rawSettings, setRawSettings] = useState<Partial<Settings>>({});
|
||||
const [authProviders, setAuthProviders] = useState<Array<{ id: string; authenticated: boolean }>>([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [selectedProviders, setSelectedProviders] = useState<ResearchProviderOption[]>([]);
|
||||
@@ -67,8 +66,8 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
|
||||
const providerOptions = availability.supportedProviders ?? DEFAULT_PROVIDERS;
|
||||
const selectedSearchProvider = effectiveSettings.searchProvider;
|
||||
const isProviderEnabled = (provider: ResearchProviderOption) => {
|
||||
if (provider === "web-search" && selectedSearchProvider === "none") {
|
||||
return false;
|
||||
if (provider === "web-search") {
|
||||
return true;
|
||||
}
|
||||
return effectiveSettings.enabledSources[PROVIDER_TO_SOURCE_KEY[provider]];
|
||||
};
|
||||
@@ -92,7 +91,6 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
|
||||
])
|
||||
.then(([settings, authStatus]) => {
|
||||
if (cancelled) return;
|
||||
setRawSettings(settings);
|
||||
setEffectiveSettings(resolveResearchSettings(settings));
|
||||
setAuthProviders(
|
||||
authStatus.providers
|
||||
@@ -127,7 +125,6 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
|
||||
|
||||
const supportedExportFormats = availability.supportedExportFormats ?? ["markdown", "json", "html"];
|
||||
|
||||
const webSearchExplicitlyDisabled = rawSettings.researchGlobalWebSearchProvider === "none" || selectedSearchProvider === "none";
|
||||
const apiKeyProviderAuth = useMemo(() => new Map(authProviders.map((provider) => [provider.id, provider.authenticated])), [authProviders]);
|
||||
const requiredCredentialProviders = useMemo(() => {
|
||||
const required = new Set<string>();
|
||||
@@ -156,9 +153,6 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
|
||||
settingsSection: "research-project" as SectionId,
|
||||
};
|
||||
}
|
||||
if (webSearchExplicitlyDisabled) {
|
||||
return null;
|
||||
}
|
||||
if (missingCredentialProvider) {
|
||||
return {
|
||||
reason: `Missing API key for ${missingCredentialProvider}.`,
|
||||
@@ -167,7 +161,7 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}, [availability.available, availability.reason, availability.setupInstructions, effectiveSettings.enabled, missingCredentialProvider, webSearchExplicitlyDisabled]);
|
||||
}, [availability.available, availability.reason, availability.setupInstructions, effectiveSettings.enabled, missingCredentialProvider]);
|
||||
|
||||
const runAction = async (key: string, action: () => Promise<unknown>, successMessage: string) => {
|
||||
setActionLoading(key);
|
||||
@@ -256,16 +250,6 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{webSearchExplicitlyDisabled ? (
|
||||
<div className="research-view__state card" data-testid="research-state-web-search-disabled">
|
||||
<p>Web search is disabled. Page Fetch, Local Docs, GitHub and LLM Synthesis still work.</p>
|
||||
<div className="research-view__actions">
|
||||
<button className="btn btn-primary" type="button" onClick={() => onOpenSettings?.("research-global")}>
|
||||
Re-enable Web Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="research-view__layout">
|
||||
<aside className="research-view__sidebar card">
|
||||
<div className="research-view__form">
|
||||
@@ -276,24 +260,28 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
|
||||
<div className="form-group">
|
||||
<label>Providers</label>
|
||||
<div className="research-view__providers">
|
||||
{providerOptions.map((provider) => (
|
||||
<label key={provider} className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedProviders.includes(provider)}
|
||||
disabled={!isProviderEnabled(provider)}
|
||||
onChange={() => {
|
||||
if (!isProviderEnabled(provider)) {
|
||||
return;
|
||||
}
|
||||
setSelectedProviders((current) =>
|
||||
current.includes(provider) ? current.filter((entry) => entry !== provider) : [...current, provider],
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<span>{PROVIDER_LABELS[provider] ?? provider}</span>
|
||||
</label>
|
||||
))}
|
||||
{providerOptions.map((provider) => {
|
||||
const providerEnabled = isProviderEnabled(provider);
|
||||
const providerLocked = provider === "web-search";
|
||||
return (
|
||||
<label key={provider} className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={providerLocked || selectedProviders.includes(provider)}
|
||||
disabled={providerLocked || !providerEnabled}
|
||||
onChange={() => {
|
||||
if (providerLocked || !providerEnabled) {
|
||||
return;
|
||||
}
|
||||
setSelectedProviders((current) =>
|
||||
current.includes(provider) ? current.filter((entry) => entry !== provider) : [...current, provider],
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<span>{PROVIDER_LABELS[provider] ?? provider}{providerLocked ? " (always on)" : ""}</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn btn-primary" type="button" disabled={!query.trim() || submitting} onClick={() => void handleCreateRun()}>
|
||||
|
||||
@@ -4298,8 +4298,7 @@ export function SettingsModal({
|
||||
resolvedProvider === "searxng" ||
|
||||
resolvedProvider === "brave" ||
|
||||
resolvedProvider === "google" ||
|
||||
resolvedProvider === "tavily" ||
|
||||
resolvedProvider === "none";
|
||||
resolvedProvider === "tavily";
|
||||
const selectedCredentialProvider =
|
||||
resolvedProvider === "brave" || resolvedProvider === "tavily" ? resolvedProvider : null;
|
||||
const hasMissingResearchCredential = selectedCredentialProvider
|
||||
@@ -4352,7 +4351,6 @@ export function SettingsModal({
|
||||
<option value="brave">Brave</option>
|
||||
<option value="google">Google Custom Search</option>
|
||||
<option value="tavily">Tavily</option>
|
||||
<option value="none">None (disable web search)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
@@ -4451,8 +4449,16 @@ export function SettingsModal({
|
||||
<div className="form-group">
|
||||
<label>Enabled Sources</label>
|
||||
<div className="settings-research-source-grid">
|
||||
<div>
|
||||
<label htmlFor="research-project-source-webSearch" className="checkbox-label">
|
||||
<input id="research-project-source-webSearch" type="checkbox" checked disabled readOnly />
|
||||
Web Search
|
||||
</label>
|
||||
<div className="settings-field-help">
|
||||
Always on. The resolver ignores any older persisted <code>enabledSources.webSearch=false</code> value.
|
||||
</div>
|
||||
</div>
|
||||
{[
|
||||
["webSearch", "Web Search"],
|
||||
["pageFetch", "Page Fetch"],
|
||||
["github", "GitHub"],
|
||||
["localDocs", "Local Docs"],
|
||||
|
||||
@@ -134,19 +134,14 @@ describe("ResearchView", () => {
|
||||
expect(await screen.findByTestId("research-state-empty")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows friendly web-search disabled state when provider is none", async () => {
|
||||
const onOpenSettings = vi.fn();
|
||||
mockFetchSettings.mockResolvedValue({ researchGlobalWebSearchProvider: "none" });
|
||||
it("does not render a web-search disabled state when the provider setting is absent", async () => {
|
||||
mockFetchSettings.mockResolvedValue({ researchSettings: { enabled: true } });
|
||||
mockFetchAuthStatus.mockResolvedValue({ providers: [] });
|
||||
|
||||
render(<ResearchView projectId="p1" onOpenSettings={onOpenSettings} />);
|
||||
render(<ResearchView projectId="p1" />);
|
||||
|
||||
expect(await screen.findByTestId("research-state-web-search-disabled")).toHaveTextContent(
|
||||
"Web search is disabled. Page Fetch, Local Docs, GitHub and LLM Synthesis still work.",
|
||||
);
|
||||
expect(screen.getByRole("checkbox", { name: "Web Search" })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Re-enable Web Search" }));
|
||||
expect(onOpenSettings).toHaveBeenCalledWith("research-global");
|
||||
await screen.findByLabelText("Query");
|
||||
expect(screen.queryByTestId("research-state-web-search-disabled")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders selected run details, citations, and history", async () => {
|
||||
@@ -389,19 +384,19 @@ describe("ResearchView", () => {
|
||||
});
|
||||
mockFetchAuthStatus.mockResolvedValue({ providers: [{ id: "openrouter", type: "api_key", authenticated: true }] });
|
||||
render(<ResearchView projectId="p1" />);
|
||||
expect(await screen.findByText("Web Search")).toBeInTheDocument();
|
||||
expect(await screen.findByText("Web Search (always on)")).toBeInTheDocument();
|
||||
expect(screen.getByText("Page Fetch")).toBeInTheDocument();
|
||||
expect(screen.getByText("LLM Synthesis")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("disables provider checkboxes when sources are disabled in settings", async () => {
|
||||
it("keeps web search visibly locked on when other sources are disabled in settings", async () => {
|
||||
mockFetchSettings.mockResolvedValue({
|
||||
...configuredResearchSettings,
|
||||
researchGlobalDefaults: {
|
||||
...configuredResearchSettings.researchGlobalDefaults,
|
||||
enabledSources: {
|
||||
webSearch: false,
|
||||
pageFetch: true,
|
||||
pageFetch: false,
|
||||
github: false,
|
||||
localDocs: true,
|
||||
llmSynthesis: true,
|
||||
@@ -417,13 +412,14 @@ describe("ResearchView", () => {
|
||||
|
||||
render(<ResearchView projectId="p1" />);
|
||||
|
||||
const webSearch = (await screen.findByLabelText("Web Search")) as HTMLInputElement;
|
||||
const webSearch = (await screen.findByLabelText("Web Search (always on)")) as HTMLInputElement;
|
||||
const pageFetch = screen.getByLabelText("Page Fetch") as HTMLInputElement;
|
||||
expect(webSearch.disabled).toBe(true);
|
||||
expect(pageFetch.disabled).toBe(false);
|
||||
expect(webSearch.checked).toBe(true);
|
||||
expect(pageFetch.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it("submits only enabled providers", async () => {
|
||||
it("submits only enabled providers while always including web search", async () => {
|
||||
const createRun = vi.fn().mockResolvedValue({ run: { id: "RR-2" } });
|
||||
mockFetchSettings.mockResolvedValue({
|
||||
...configuredResearchSettings,
|
||||
@@ -450,7 +446,7 @@ describe("ResearchView", () => {
|
||||
fireEvent.click(screen.getByText("Create Run"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createRun).toHaveBeenCalledWith(expect.objectContaining({ providers: ["page-fetch", "llm-synthesis"] }));
|
||||
expect(createRun).toHaveBeenCalledWith(expect.objectContaining({ providers: ["web-search", "page-fetch", "llm-synthesis"] }));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3411,6 +3411,17 @@ describe("SettingsModal", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("shows web search as always on in project research settings", async () => {
|
||||
renderModal();
|
||||
await waitForSettingsModalReady();
|
||||
await openResearchProjectSection();
|
||||
|
||||
const webSearch = await screen.findByLabelText("Web Search");
|
||||
expect(webSearch).toBeChecked();
|
||||
expect(webSearch).toBeDisabled();
|
||||
expect(screen.getByText(/Always on\./i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("saves project research settings through updateSettings only", async () => {
|
||||
renderModal();
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
@@ -962,7 +962,7 @@ describe("createResearchTools", () => {
|
||||
researchGlobalMaxConcurrentRuns: 2,
|
||||
researchGlobalDefaultTimeout: 30_000,
|
||||
researchGlobalMaxSynthesisRounds: 2,
|
||||
researchGlobalWebSearchProvider: "none",
|
||||
researchGlobalWebSearchProvider: "builtin",
|
||||
researchSettings: { enabled: true },
|
||||
};
|
||||
|
||||
|
||||
@@ -11,11 +11,6 @@ describe("ResearchProviderRegistry", () => {
|
||||
expect(registry.isProviderAvailable("github")).toBe(false);
|
||||
});
|
||||
|
||||
it("honors explicit none by disabling web-search", () => {
|
||||
const registry = new ResearchProviderRegistry({ researchGlobalWebSearchProvider: "none" }, process.cwd());
|
||||
expect(registry.isProviderAvailable("web-search")).toBe(false);
|
||||
});
|
||||
|
||||
it("requires credentials for explicit external providers", () => {
|
||||
const registry = new ResearchProviderRegistry({ researchGlobalWebSearchProvider: "tavily" }, process.cwd());
|
||||
expect(registry.isProviderAvailable("web-search")).toBe(false);
|
||||
@@ -37,7 +32,7 @@ describe("ResearchProviderRegistry", () => {
|
||||
});
|
||||
|
||||
it("refreshes providers after settings changes", () => {
|
||||
const registry = new ResearchProviderRegistry({ researchGlobalWebSearchProvider: "none" }, process.cwd());
|
||||
const registry = new ResearchProviderRegistry({ researchGlobalWebSearchProvider: "tavily" }, process.cwd());
|
||||
expect(registry.isProviderAvailable("web-search")).toBe(false);
|
||||
|
||||
registry.refreshSettings({ researchGlobalWebSearchProvider: "tavily", researchGlobalTavilyApiKey: "key" });
|
||||
|
||||
@@ -28,7 +28,6 @@ describe("WebSearchProvider", () => {
|
||||
|
||||
it("validates configuration for each backend", () => {
|
||||
expect(new WebSearchProvider({ backend: "builtin", projectRoot: process.cwd() }).isConfigured()).toBe(true);
|
||||
expect(new WebSearchProvider({ backend: "none" }).isConfigured()).toBe(false);
|
||||
expect(new WebSearchProvider({ backend: "searxng", searxngUrl: "https://sx" }).isConfigured()).toBe(true);
|
||||
expect(new WebSearchProvider({ backend: "brave", braveApiKey: "k" }).isConfigured()).toBe(true);
|
||||
expect(new WebSearchProvider({ backend: "google", googleApiKey: "k", googleCx: "cx" }).isConfigured()).toBe(true);
|
||||
|
||||
@@ -35,7 +35,6 @@ export class WebSearchProvider implements ResearchProvider {
|
||||
isConfigured(): boolean {
|
||||
const backend = this.options.backend ?? "builtin";
|
||||
if (backend === "builtin") return true;
|
||||
if (backend === "none") return false;
|
||||
if (backend === "searxng") return Boolean(this.options.searxngUrl);
|
||||
if (backend === "brave") return Boolean(this.options.braveApiKey);
|
||||
if (backend === "google") return Boolean(this.options.googleApiKey && this.options.googleCx);
|
||||
|
||||
Reference in New Issue
Block a user