feat: treat bundled Tavily as a bundled provider
This commit is contained in:
parent
e2b7c4c6a3
commit
04769d7fe2
@ -23,6 +23,9 @@ const mocks = vi.hoisted(() => ({
|
||||
const loadOpenClawPlugins = vi.hoisted(() =>
|
||||
vi.fn(() => ({ searchProviders: [] as unknown[], plugins: [] as unknown[] })),
|
||||
);
|
||||
const loadPluginManifestRegistry = vi.hoisted(() =>
|
||||
vi.fn(() => ({ plugins: [] as unknown[], diagnostics: [] as unknown[] })),
|
||||
);
|
||||
const ensureOnboardingPluginInstalled = vi.hoisted(() =>
|
||||
vi.fn(async ({ cfg }: { cfg: OpenClawConfig }) => ({ cfg, installed: false })),
|
||||
);
|
||||
@ -55,6 +58,10 @@ vi.mock("../plugins/loader.js", () => ({
|
||||
loadOpenClawPlugins,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/manifest-registry.js", () => ({
|
||||
loadPluginManifestRegistry,
|
||||
}));
|
||||
|
||||
vi.mock("./onboarding/plugin-install.js", () => ({
|
||||
ensureOnboardingPluginInstalled,
|
||||
reloadOnboardingPluginRegistry,
|
||||
@ -144,6 +151,8 @@ describe("runConfigureWizard", () => {
|
||||
mocks.summarizeExistingConfig.mockReset();
|
||||
loadOpenClawPlugins.mockReset();
|
||||
loadOpenClawPlugins.mockReturnValue({ searchProviders: [], plugins: [] });
|
||||
loadPluginManifestRegistry.mockReset();
|
||||
loadPluginManifestRegistry.mockReturnValue({ plugins: [], diagnostics: [] });
|
||||
ensureOnboardingPluginInstalled.mockReset();
|
||||
ensureOnboardingPluginInstalled.mockImplementation(
|
||||
async ({ cfg }: { cfg: OpenClawConfig }) => ({
|
||||
@ -241,7 +250,6 @@ describe("runConfigureWizard", () => {
|
||||
tools: expect.objectContaining({
|
||||
web: expect.objectContaining({
|
||||
search: expect.objectContaining({
|
||||
provider: "tavily",
|
||||
enabled: true,
|
||||
}),
|
||||
}),
|
||||
@ -327,6 +335,7 @@ describe("runConfigureWizard", () => {
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(mocks.writeConfigFile.mock.calls[0]?.[0]?.tools?.web?.search?.provider).toBeUndefined();
|
||||
});
|
||||
|
||||
it("re-prompts invalid plugin config values during configure", async () => {
|
||||
@ -436,7 +445,7 @@ describe("runConfigureWizard", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("installs a plugin search provider from configure and continues setup", async () => {
|
||||
it("configures a bundled plugin search provider from configure without the external install step", async () => {
|
||||
loadOpenClawPlugins.mockImplementation(({ config }: { config: OpenClawConfig }) => {
|
||||
const enabled = config.plugins?.entries?.["tavily-search"]?.enabled === true;
|
||||
return enabled
|
||||
@ -482,6 +491,36 @@ describe("runConfigureWizard", () => {
|
||||
}
|
||||
: { searchProviders: [], plugins: [] };
|
||||
});
|
||||
loadPluginManifestRegistry.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "tavily-search",
|
||||
name: "Tavily Search",
|
||||
description: "Search the web using Tavily.",
|
||||
origin: "bundled",
|
||||
source: "/tmp/bundled/tavily-search",
|
||||
configSchema: {
|
||||
type: "object",
|
||||
required: ["apiKey"],
|
||||
properties: {
|
||||
apiKey: { type: "string", minLength: 1, pattern: "^tvly-\\S+$" },
|
||||
searchDepth: { type: "string", enum: ["basic", "advanced"] },
|
||||
},
|
||||
},
|
||||
configUiHints: {
|
||||
apiKey: {
|
||||
label: "Tavily API key",
|
||||
placeholder: "tvly-...",
|
||||
sensitive: true,
|
||||
},
|
||||
searchDepth: {
|
||||
label: "Search depth",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
diagnostics: [],
|
||||
});
|
||||
ensureOnboardingPluginInstalled.mockImplementation(
|
||||
async ({ cfg }: { cfg: OpenClawConfig }) => ({
|
||||
cfg: {
|
||||
@ -523,7 +562,7 @@ describe("runConfigureWizard", () => {
|
||||
mocks.clackConfirm.mockResolvedValueOnce(true).mockResolvedValueOnce(true);
|
||||
mocks.clackSelect.mockImplementation(async (params: { message: string }) => {
|
||||
if (params.message === "Choose active web search provider") {
|
||||
return "__install_plugin__";
|
||||
return "tavily";
|
||||
}
|
||||
if (params.message.startsWith("Search depth")) {
|
||||
return "advanced";
|
||||
@ -541,23 +580,8 @@ describe("runConfigureWizard", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(ensureOnboardingPluginInstalled).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
entry: expect.objectContaining({
|
||||
id: "tavily-search",
|
||||
install: expect.objectContaining({
|
||||
npmSpec: "@openclaw/tavily-search",
|
||||
localPath: "extensions/tavily-search",
|
||||
}),
|
||||
}),
|
||||
workspaceDir: "/tmp/configure-install-workspace",
|
||||
}),
|
||||
);
|
||||
expect(reloadOnboardingPluginRegistry).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: "/tmp/configure-install-workspace",
|
||||
}),
|
||||
);
|
||||
expect(ensureOnboardingPluginInstalled).not.toHaveBeenCalled();
|
||||
expect(reloadOnboardingPluginRegistry).not.toHaveBeenCalled();
|
||||
expect(mocks.writeConfigFile).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
tools: expect.objectContaining({
|
||||
|
||||
@ -182,6 +182,66 @@ describe("setupSearch", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("shows bundled plugin providers directly in the picker instead of the external install path", async () => {
|
||||
loadOpenClawPlugins.mockReturnValue({
|
||||
searchProviders: [],
|
||||
plugins: [],
|
||||
typedHooks: [],
|
||||
});
|
||||
loadPluginManifestRegistry.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "tavily-search",
|
||||
name: "Tavily Search",
|
||||
description: "Search the web using Tavily.",
|
||||
origin: "bundled",
|
||||
source: "/tmp/bundled/tavily-search",
|
||||
configSchema: {
|
||||
type: "object",
|
||||
required: ["apiKey"],
|
||||
properties: {
|
||||
apiKey: { type: "string", minLength: 1, pattern: "^tvly-\\S+$" },
|
||||
},
|
||||
},
|
||||
configUiHints: {
|
||||
apiKey: {
|
||||
label: "Tavily API key",
|
||||
placeholder: "tvly-...",
|
||||
sensitive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
diagnostics: [],
|
||||
});
|
||||
|
||||
const cfg: OpenClawConfig = {};
|
||||
const { prompter } = createPrompter({ selectValue: "__skip__" });
|
||||
await setupSearch(cfg, runtime, prompter);
|
||||
|
||||
const providerSelectCall = (prompter.select as ReturnType<typeof vi.fn>).mock.calls.find(
|
||||
(call) => call[0]?.message === "Choose active web search provider",
|
||||
);
|
||||
expect(providerSelectCall?.[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
value: "tavily",
|
||||
label: "Tavily Search",
|
||||
hint: expect.stringContaining("Bundled plugin"),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(providerSelectCall?.[0]?.options).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
value: "__install_plugin__",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses the updated configure-or-install action label", async () => {
|
||||
vi.stubEnv("BRAVE_API_KEY", "BSA-test-key");
|
||||
loadOpenClawPlugins.mockReturnValue({
|
||||
|
||||
@ -133,7 +133,13 @@ export function resolveInstallableSearchProviderPlugins(
|
||||
const loadedPluginProviderIds = new Set(
|
||||
providerEntries.filter((entry) => entry.kind === "plugin").map((entry) => entry.value),
|
||||
);
|
||||
return SEARCH_PROVIDER_PLUGIN_INSTALL_CATALOG.map((entry) => ({
|
||||
return SEARCH_PROVIDER_PLUGIN_INSTALL_CATALOG.filter((entry) => {
|
||||
const providerEntry = providerEntries.find(
|
||||
(providerEntry) =>
|
||||
providerEntry.kind === "plugin" && providerEntry.value === entry.providerId,
|
||||
);
|
||||
return providerEntry?.kind !== "plugin" || providerEntry.origin !== "bundled";
|
||||
}).map((entry) => ({
|
||||
...entry,
|
||||
description: loadedPluginProviderIds.has(entry.providerId)
|
||||
? `${entry.description} Already installed.`
|
||||
@ -570,6 +576,39 @@ export async function resolveSearchProviderPickerEntries(
|
||||
pluginEntries = [];
|
||||
}
|
||||
|
||||
try {
|
||||
loadPluginManifestRegistry({
|
||||
config,
|
||||
workspaceDir,
|
||||
cache: false,
|
||||
});
|
||||
const loadedPluginProviderIds = new Set(pluginEntries.map((entry) => entry.value));
|
||||
const bundledManifestEntries = SEARCH_PROVIDER_PLUGIN_INSTALL_CATALOG.map((installEntry) =>
|
||||
buildPluginSearchProviderEntryFromManifest({
|
||||
config,
|
||||
installEntry,
|
||||
workspaceDir,
|
||||
}),
|
||||
)
|
||||
.filter(
|
||||
(entry): entry is PluginSearchProviderEntry =>
|
||||
Boolean(entry) && entry.origin === "bundled" && !loadedPluginProviderIds.has(entry.value),
|
||||
)
|
||||
.map((entry) => {
|
||||
const pluginConfig = getPluginConfig(config, entry.pluginId);
|
||||
const validation = validatePluginSearchProviderConfig(entry, pluginConfig);
|
||||
return {
|
||||
...entry,
|
||||
configured: validation.ok,
|
||||
};
|
||||
});
|
||||
pluginEntries = [...pluginEntries, ...bundledManifestEntries].toSorted((left, right) =>
|
||||
left.label.localeCompare(right.label),
|
||||
);
|
||||
} catch {
|
||||
// Ignore manifest lookup failures and fall back to loaded entries only.
|
||||
}
|
||||
|
||||
return [...builtins, ...pluginEntries];
|
||||
}
|
||||
|
||||
@ -602,13 +641,13 @@ function buildPluginSearchProviderEntryFromManifest(params: {
|
||||
value: params.installEntry.providerId,
|
||||
label: params.installEntry.meta.label,
|
||||
hint: [
|
||||
params.installEntry.description || pluginRecord.description || "Plugin-provided web search",
|
||||
pluginRecord.description || "Plugin-provided web search",
|
||||
formatPluginSourceHint(pluginRecord.origin),
|
||||
].join(" · "),
|
||||
configured: false,
|
||||
pluginId: pluginRecord.id,
|
||||
origin: pluginRecord.origin,
|
||||
description: params.installEntry.description || pluginRecord.description,
|
||||
description: pluginRecord.description,
|
||||
docsUrl: undefined,
|
||||
configFieldOrder: undefined,
|
||||
configJsonSchema: pluginRecord.configSchema,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user