feat(FN-3901): add builtin research web-search provider as default
The merge adds a builtin web-search research provider as the default, replacing the need for external research provider configuration and removing the setup gate for builtin defaults. Dashboard settings UX for research defaults was aligned (ResearchView, SettingsModal), CLI research commands were up Fusion-Task-Id: FN-3901
This commit is contained in:
@@ -1445,6 +1445,31 @@ describe("fn pi extension (runnable structured-output regression slice)", () =>
|
||||
});
|
||||
|
||||
describe("research tools", () => {
|
||||
it("fn_research_run treats builtin as configured when no provider is explicitly set", async () => {
|
||||
const store = new TaskStore(tmpDir);
|
||||
await store.init();
|
||||
await store.updateGlobalSettings({
|
||||
researchGlobalEnabled: true,
|
||||
experimentalFeatures: { researchView: true } as Record<string, boolean>,
|
||||
});
|
||||
await store.updateSettings({
|
||||
researchEnabled: true,
|
||||
researchSettings: { enabled: true },
|
||||
});
|
||||
|
||||
const tool = api.tools.get("fn_research_run")!;
|
||||
const result = await tool.execute(
|
||||
"research-run-builtin",
|
||||
{ query: "builtin default" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.details.setup).toBeNull();
|
||||
expect(result.details.status).toBe("queued");
|
||||
});
|
||||
|
||||
it("fn_research_list status parameter matches RESEARCH_RUN_STATUSES", () => {
|
||||
const tool = api.tools.get("fn_research_list") as any;
|
||||
const statusSchema = tool.parameters.properties.status;
|
||||
|
||||
@@ -88,6 +88,24 @@ describe("research extension tools", () => {
|
||||
expect(retryResult.details.setup.code).toBe("feature-disabled");
|
||||
});
|
||||
|
||||
it("treats builtin as configured when no provider is explicitly set", async () => {
|
||||
const store = new TaskStore(tmpDir);
|
||||
await store.init();
|
||||
await store.updateGlobalSettings({
|
||||
experimentalFeatures: { researchView: true } as Record<string, boolean>,
|
||||
researchGlobalEnabled: true,
|
||||
});
|
||||
await store.updateSettings({
|
||||
researchSettings: { enabled: true },
|
||||
});
|
||||
|
||||
const runTool = api.tools.get("fn_research_run")!;
|
||||
const result = await runTool.execute("call-builtin", { query: "fusion" }, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
expect(result.details.setup).toBeNull();
|
||||
expect(result.details.status).toBe("queued");
|
||||
});
|
||||
|
||||
it("returns actionable missing-credentials response", async () => {
|
||||
const store = new TaskStore(tmpDir);
|
||||
await store.init();
|
||||
|
||||
@@ -82,6 +82,21 @@ describe("research commands", () => {
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("Created research run"));
|
||||
});
|
||||
|
||||
it("creates a run when provider is unset by defaulting to builtin", async () => {
|
||||
storeMock.getSettings.mockResolvedValueOnce({ researchSettings: { enabled: true } });
|
||||
resolveResearchSettingsMock.mockReturnValueOnce({
|
||||
enabled: true,
|
||||
searchProvider: "builtin",
|
||||
limits: { maxConcurrentRuns: 2, maxSourcesPerRun: 5, requestTimeoutMs: 1000, maxDurationMs: 5000 },
|
||||
});
|
||||
providerRegistryMock.mockReturnValueOnce({ getAvailableProviders: () => ["web-search"], getProvider: () => ({ type: "web-search" }) });
|
||||
|
||||
await runResearchCreate({ query: "hello builtin" });
|
||||
|
||||
expect(orchestratorMock.createRun).toHaveBeenCalled();
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("lists runs as json", async () => {
|
||||
await runResearchList({ json: true, status: "completed", limit: 3 });
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('"runs"'));
|
||||
|
||||
@@ -42,7 +42,8 @@ async function getStore(projectName?: string): Promise<TaskStore> {
|
||||
}
|
||||
|
||||
function hasProviderCredentials(settings: Awaited<ReturnType<TaskStore["getSettings"]>>, providerId: string | undefined): boolean {
|
||||
if (!providerId) return false;
|
||||
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);
|
||||
@@ -57,11 +58,8 @@ async function getResearchRuntime(store: TaskStore) {
|
||||
throw new Error("feature-disabled: Research is disabled in settings.");
|
||||
}
|
||||
|
||||
const configuredProvider = (resolved.searchProvider as string | undefined) ?? settings.researchGlobalWebSearchProvider;
|
||||
if (!configuredProvider) {
|
||||
throw new Error("provider-unavailable: Research providers are not configured. Add provider credentials in settings.");
|
||||
}
|
||||
if (!hasProviderCredentials(settings, configuredProvider)) {
|
||||
const configuredProvider = (resolved.searchProvider as string | undefined) ?? settings.researchGlobalWebSearchProvider ?? "builtin";
|
||||
if (configuredProvider !== "builtin" && configuredProvider !== "none" && !hasProviderCredentials(settings, configuredProvider)) {
|
||||
throw new Error(`missing-credentials: ${configuredProvider} credentials are missing. Configure Authentication and Research defaults in settings.`);
|
||||
}
|
||||
|
||||
|
||||
@@ -218,20 +218,20 @@ async function getResearchAvailability(store: TaskStore): Promise<{ ok: boolean;
|
||||
return { ok: false, code: "feature-disabled", message: "Research is disabled in settings." };
|
||||
}
|
||||
|
||||
const backend = (resolved.searchProvider as string | undefined) ?? settings.researchGlobalWebSearchProvider;
|
||||
const configured = backend === "searxng"
|
||||
? Boolean(settings.researchGlobalSearxngUrl)
|
||||
: backend === "brave"
|
||||
? Boolean(settings.researchGlobalBraveApiKey)
|
||||
: backend === "google"
|
||||
? Boolean(settings.researchGlobalGoogleSearchApiKey && settings.researchGlobalGoogleSearchCx)
|
||||
: backend === "tavily"
|
||||
? Boolean(settings.researchGlobalTavilyApiKey)
|
||||
: false;
|
||||
|
||||
if (!backend) {
|
||||
return { ok: false, code: "provider-unavailable", message: "Research provider is not configured. Set research provider credentials in Settings." };
|
||||
}
|
||||
const backend = (resolved.searchProvider as string | undefined) ?? settings.researchGlobalWebSearchProvider ?? "builtin";
|
||||
const configured = backend === "builtin"
|
||||
? true
|
||||
: backend === "none"
|
||||
? false
|
||||
: backend === "searxng"
|
||||
? Boolean(settings.researchGlobalSearxngUrl)
|
||||
: backend === "brave"
|
||||
? Boolean(settings.researchGlobalBraveApiKey)
|
||||
: backend === "google"
|
||||
? Boolean(settings.researchGlobalGoogleSearchApiKey && settings.researchGlobalGoogleSearchCx)
|
||||
: backend === "tavily"
|
||||
? Boolean(settings.researchGlobalTavilyApiKey)
|
||||
: false;
|
||||
|
||||
if (!configured) {
|
||||
return { ok: false, code: "missing-credentials", message: `Missing credentials for ${backend}. Add provider keys in Authentication and verify Research defaults.` };
|
||||
|
||||
Reference in New Issue
Block a user