mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:10:44 +00:00
feat(searxng): show setup JSON format note
This commit is contained in:
@@ -26,6 +26,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/models: keep legacy CLI runtime model refs such as `claude-cli/*` in the configured allowlist after canonical runtime migration, so cron `payload.model` overrides keep working. Fixes #75753. Thanks @RyanSandoval.
|
||||
- Plugin SDK: re-export `isPrivateIpAddress` from `plugin-sdk/ssrf-runtime`, restoring source-checkout builds for SearXNG and Firecrawl private-network guards. Thanks @vincentkoc.
|
||||
- CLI/directory: report unsupported directory operations for installed channel plugins instead of prompting to reinstall the plugin when it lacks a directory adapter. Fixes #75770. Thanks @lawong888.
|
||||
- Web search/SearXNG: show the JSON API `search.formats` prerequisite during SearXNG setup before prompting for the base URL. Supersedes #65592. Thanks @evanpaul14.
|
||||
- Web search: keep public provider requests on the strict SSRF guard and reserve private-network access for explicit self-hosted SearXNG/Firecrawl endpoints. Fixes #74357 and supersedes #74360. Thanks @fede-kamel.
|
||||
- Web search/Firecrawl: allow self-hosted private/internal Firecrawl `baseUrl` endpoints, including HTTP for private targets, while keeping hosted Firecrawl on the strict official endpoint. Fixes #63877 and supersedes #59666, #63941, and #74013. Thanks @jhthompson12, @jzakirov, @Mlightsnow, and @shad0wca7.
|
||||
- Providers/OpenRouter: strip trailing assistant prefill turns from verified OpenRouter Anthropic model requests when reasoning is enabled, so Claude 4.6 routes no longer fail with Anthropic's prefill rejection through the OpenAI-compatible adapter. Fixes #75395. Thanks @sbmilburn.
|
||||
|
||||
@@ -145,6 +145,13 @@ describe("searxng web search provider", () => {
|
||||
expect(resolveSearxngLanguage(config)).toBe("de");
|
||||
});
|
||||
|
||||
it("exposes a credentialNote with JSON format guidance", () => {
|
||||
const provider = createSearxngWebSearchProvider();
|
||||
|
||||
expect(provider.credentialNote).toContain("json format enabled");
|
||||
expect(provider.credentialNote).toContain("search.formats");
|
||||
});
|
||||
|
||||
it("persists base URL to plugin config via setConfiguredCredentialValue", () => {
|
||||
const provider = createSearxngWebSearchProvider();
|
||||
const config = {} as Record<string, unknown>;
|
||||
|
||||
@@ -56,6 +56,10 @@ export function createSearxngWebSearchProvider(): WebSearchProviderPlugin {
|
||||
configuredCredential: { pluginId: "searxng", field: "baseUrl" },
|
||||
selectionPluginId: "searxng",
|
||||
}),
|
||||
credentialNote: [
|
||||
"For the SearXNG JSON API to work, make sure your SearXNG instance",
|
||||
"has the json format enabled in its settings.yml under search.formats.",
|
||||
].join("\n"),
|
||||
createTool: (ctx) => ({
|
||||
description:
|
||||
"Search the web using a self-hosted SearXNG instance. Returns titles, URLs, and snippets.",
|
||||
|
||||
@@ -16,6 +16,7 @@ const mockGrokProvider = vi.hoisted(() => ({
|
||||
envVars: ["XAI_API_KEY"],
|
||||
onboardingScopes: ["text-inference"],
|
||||
credentialPath: "plugins.entries.xai.config.webSearch.apiKey",
|
||||
credentialNote: "Configure Grok web search prerequisites before entering the credential.",
|
||||
getCredentialValue: (search?: Record<string, unknown>) => search?.apiKey,
|
||||
setCredentialValue: (searchConfigTarget: Record<string, unknown>, value: unknown) => {
|
||||
searchConfigTarget.apiKey = value;
|
||||
@@ -128,6 +129,79 @@ describe("runSearchSetupFlow", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows provider credential notes before plaintext credential prompts", async () => {
|
||||
const select = vi.fn().mockResolvedValueOnce("grok").mockResolvedValueOnce("no");
|
||||
const text = vi.fn().mockResolvedValue("xai-test-key");
|
||||
const note = vi.fn(async () => {});
|
||||
const prompter = createWizardPrompter({
|
||||
note: note as never,
|
||||
select: select as never,
|
||||
text: text as never,
|
||||
});
|
||||
|
||||
await runSearchSetupFlow({ plugins: { allow: ["xai"] } }, createNonExitingRuntime(), prompter);
|
||||
|
||||
expect(note).toHaveBeenCalledWith(mockGrokProvider.credentialNote, mockGrokProvider.label);
|
||||
expect(text).toHaveBeenCalledTimes(1);
|
||||
expect(note.mock.invocationCallOrder[1]).toBeLessThan(text.mock.invocationCallOrder[0]);
|
||||
});
|
||||
|
||||
it("shows provider credential notes before SecretRef setup notes", async () => {
|
||||
const select = vi.fn().mockResolvedValueOnce("grok").mockResolvedValueOnce("no");
|
||||
const note = vi.fn(async () => {});
|
||||
const prompter = createWizardPrompter({
|
||||
note: note as never,
|
||||
select: select as never,
|
||||
});
|
||||
|
||||
await runSearchSetupFlow({ plugins: { allow: ["xai"] } }, createNonExitingRuntime(), prompter, {
|
||||
secretInputMode: "ref",
|
||||
});
|
||||
|
||||
expect(note).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
mockGrokProvider.credentialNote,
|
||||
mockGrokProvider.label,
|
||||
);
|
||||
expect(note).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.stringContaining("Secret references enabled"),
|
||||
"Web search",
|
||||
);
|
||||
});
|
||||
|
||||
it("skips provider credential notes in quickstart fast path", async () => {
|
||||
const select = vi.fn().mockResolvedValueOnce("grok").mockResolvedValueOnce("no");
|
||||
const note = vi.fn(async () => {});
|
||||
const prompter = createWizardPrompter({
|
||||
note: note as never,
|
||||
select: select as never,
|
||||
});
|
||||
|
||||
await runSearchSetupFlow(
|
||||
{
|
||||
plugins: {
|
||||
allow: ["xai"],
|
||||
entries: {
|
||||
xai: {
|
||||
enabled: true,
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "xai-test-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
createNonExitingRuntime(),
|
||||
prompter,
|
||||
{ quickstartDefaults: true },
|
||||
);
|
||||
|
||||
expect(note).not.toHaveBeenCalledWith(mockGrokProvider.credentialNote, mockGrokProvider.label);
|
||||
});
|
||||
|
||||
it("preserves disabled web_search state while still allowing provider-owned x_search setup", async () => {
|
||||
const select = vi
|
||||
.fn()
|
||||
|
||||
@@ -434,6 +434,10 @@ export async function runSearchSetupFlow(
|
||||
});
|
||||
}
|
||||
|
||||
if (entry.credentialNote) {
|
||||
await prompter.note(entry.credentialNote, entry.label);
|
||||
}
|
||||
|
||||
const useSecretRefMode = opts?.secretInputMode === "ref"; // pragma: allowlist secret
|
||||
if (useSecretRefMode) {
|
||||
if (keyConfigured) {
|
||||
|
||||
@@ -92,6 +92,8 @@ export type WebSearchProviderPlugin = {
|
||||
placeholder: string;
|
||||
signupUrl: string;
|
||||
docsUrl?: string;
|
||||
/** Optional note shown before credential collection for provider-specific prerequisites. */
|
||||
credentialNote?: string;
|
||||
autoDetectOrder?: number;
|
||||
credentialPath: string;
|
||||
inactiveSecretPaths?: string[];
|
||||
|
||||
Reference in New Issue
Block a user