mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
feat(searxng): pass through image result urls
This commit is contained in:
@@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai
|
||||
- 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/SearXNG: pass through `img_src` image URLs from SearXNG image-category results. Supersedes #61416. Thanks @sghael.
|
||||
- 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.
|
||||
|
||||
@@ -112,6 +112,8 @@ key wins first).
|
||||
## Notes
|
||||
|
||||
- **JSON API** -- uses SearXNG's native `format=json` endpoint, not HTML scraping
|
||||
- **Image result URLs** -- image-category results include `img_src` when SearXNG
|
||||
returns a direct image URL
|
||||
- **No API key** -- works with any SearXNG instance out of the box
|
||||
- **Base URL validation** -- `baseUrl` must be a valid `http://` or `https://`
|
||||
URL; public hosts must use `https://`
|
||||
|
||||
@@ -39,6 +39,53 @@ describe("searxng client", () => {
|
||||
).toEqual([{ title: "One", url: "https://example.com/1", content: "A" }]);
|
||||
});
|
||||
|
||||
it("preserves img_src from image search results", () => {
|
||||
expect(
|
||||
__testing.parseSearxngResponseText(
|
||||
JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
title: "Kitten",
|
||||
url: "https://example.com/kitten",
|
||||
content: "A cute kitten",
|
||||
img_src: "https://cdn.example.com/kitten.jpg",
|
||||
},
|
||||
{
|
||||
title: "No Image",
|
||||
url: "https://example.com/text",
|
||||
content: "Text only",
|
||||
},
|
||||
{
|
||||
title: "Bad Image",
|
||||
url: "https://example.com/bad",
|
||||
img_src: { url: "https://cdn.example.com/bad.jpg" },
|
||||
},
|
||||
],
|
||||
}),
|
||||
10,
|
||||
),
|
||||
).toEqual([
|
||||
{
|
||||
title: "Kitten",
|
||||
url: "https://example.com/kitten",
|
||||
content: "A cute kitten",
|
||||
img_src: "https://cdn.example.com/kitten.jpg",
|
||||
},
|
||||
{
|
||||
title: "No Image",
|
||||
url: "https://example.com/text",
|
||||
content: "Text only",
|
||||
img_src: undefined,
|
||||
},
|
||||
{
|
||||
title: "Bad Image",
|
||||
url: "https://example.com/bad",
|
||||
content: undefined,
|
||||
img_src: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("drops malformed result rows instead of failing the whole response", () => {
|
||||
expect(
|
||||
__testing.parseSearxngResponseText(
|
||||
|
||||
@@ -40,6 +40,7 @@ type SearxngResult = {
|
||||
url: string;
|
||||
title: string;
|
||||
content?: string;
|
||||
img_src?: string;
|
||||
};
|
||||
|
||||
type SearxngResponse = {
|
||||
@@ -51,7 +52,12 @@ function normalizeSearxngResult(value: unknown): SearxngResult | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const candidate = value as { url?: unknown; title?: unknown; content?: unknown };
|
||||
const candidate = value as {
|
||||
url?: unknown;
|
||||
title?: unknown;
|
||||
content?: unknown;
|
||||
img_src?: unknown;
|
||||
};
|
||||
if (typeof candidate.url !== "string" || typeof candidate.title !== "string") {
|
||||
return null;
|
||||
}
|
||||
@@ -60,6 +66,7 @@ function normalizeSearxngResult(value: unknown): SearxngResult | null {
|
||||
url: candidate.url,
|
||||
title: candidate.title,
|
||||
content: typeof candidate.content === "string" ? candidate.content : undefined,
|
||||
img_src: typeof candidate.img_src === "string" ? candidate.img_src : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -254,6 +261,7 @@ export async function runSearxngSearch(params: {
|
||||
url: result.url,
|
||||
snippet: result.content ? wrapWebContent(result.content, "web_search") : "",
|
||||
siteName: resolveSiteName(result.url) || undefined,
|
||||
img_src: result.img_src || undefined,
|
||||
})),
|
||||
} satisfies Record<string, unknown>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user