Files
xiaoshuai d6e29cbbad searxng-search: record that no domestic-only engine set works here
Asked for a domestic-only default, so every general-web index SearXNG ships
for China was added to keep_only, the container was restarted, and each was
measured over 5 runs per query. None of them is usable. The default stays
bing,yandex.

  engine          en hits  zh hits  outcome
  360search         5/5      5/5   reliable, 0% relevant
  quark             0/5      0/5   rate-limited dead after two good responses
  baidu              -        0   CAPTCHA, 1h IP suspension
  sogou              -        0   upstream AttributeError
  baidu kaifa        -      0/10   code index: "opencode v2" matches OpenCV2
  bilibili           -        -   video titles, not a web index
  chinaso news       -        -   falls through to the default engine set

quark was the one that looked like the fix. On its first two requests it
returned the exact article being asked about as result #1 on both the Chinese
and the English query -- better than anything else measured -- and then answered
0 results on 5 consecutive runs of each. Had it not been run 5 times, it would
have looked like the obvious upgrade.

baidu kaifa is worth remembering as a category error: it is a code search
index, so it matches "opencode v2" against OpenCV2 and returns CSDN blog posts
at 0% relevance. It is reachable and does not CAPTCHA, which makes it look
healthy in /config while being useless.

The foreign engines that were reachable (brave, duckduckgo, google, qwant,
wikipedia) were added at the same time and all returned 0, consistent with the
network timeouts already recorded in settings.yml. mojeek and startpage are not
present in this SearXNG release at all, so the earlier note calling them
"unreachable" was imprecise.

settings.yml was restored from a backup and diffed byte-identical before this
commit; the probe engines never left the instance.

cn.bing.com is the only dependable endpoint and is a China-hosted Bing, so bing
counts as the domestic option. Its one real limitation stands: it ignores the
site: operator, so site-restricted search is unavailable through this instance.

Also fixes a duplicated `bing` row left in the README engine table by the
previous commit.
2026-10-08 13:44:46 +08:00

10 KiB
Raw Permalink Blame History

searxng-search

An OpenCode V2 plugin that adds a searxng_search tool backed by a self-hosted SearXNG instance.

It does not replace OpenCode's built-in websearch provider. It registers one extra, opt-in tool, so nothing existing changes behaviour. Replacing a built-in provider means taking over a key the user chose; adding a tool does not.

Requirements

  1. A reachable SearXNG instance with json in search.formats (disabled upstream by default). See SEARXNG-SETUP.md.

  2. @opencode/plugin installed at your exact OpenCode version:

    opencode --version                              # e.g. v2.0.24
    cd ~/.config/opencode
    npm i @opencode/plugin@2.0.24                   # must match
    
  3. A service restart. The running service caches module resolution, so a freshly installed package is invisible until then:

    opencode service restart
    

Check the log if the tool does not appear:

grep -i "loading plugin" ~/.local/share/opencode/log/opencode.log | tail

A line like this is good:

loading plugin id=/home/you/.config/opencode/plugins/searxng-search

A Cannot find package '@opencode/plugin' error means the package is missing, not that the docs are wrong. See the stale-package trap in ../../skills/opencode-plugins/SKILL.md.

How it is loaded

By directory discovery, not by config. OpenCode loads every immediate subdirectory of ~/.config/opencode/plugins/ as a plugin package and resolves an entrypoint inside it. This directory has no package.json, so the entrypoint falls back to index.ts:

loading plugin id=/home/you/.config/opencode/plugins/searxng-search
            entrypoint=file:///.../plugins/searxng-search/index.ts

Sibling files such as this README.md do not affect that resolution — verified by restarting the service with the docs in place. There is deliberately nothing to add to opencode.jsonc.

That has a consequence worth knowing before you try to configure it:

ctx.options is always {} for a discovered plugin.

Options reach a plugin only through an object entry in the plugins array in opencode.json(c), which is keyed by package or path. A directory that was found by scanning has no such entry, so it receives no options at all. This was verified by writing JSON.stringify(ctx.options) to a file from inside setup() — the file contained {}.

The baseUrl / engines / timeoutMs / maxSnippetChars options in PluginConfig are therefore inert as shipped. Two working alternatives:

Approach How
Environment variable (easiest) export SEARXNG_URL=http://host:39217 before starting OpenCode
Register explicitly Move this directory out of plugins/, then add a {"package": "<path>", "options": {...}} entry to opencode.jsonc

To keep the engines default editable without moving anything, change DEFAULT_ENGINES in index.ts and push — see "Push every change" in ../../skills/opencode-plugins/SKILL.md.

If you do register it explicitly, remove the directory from plugins/ first. Otherwise the plugin loads twice: once by discovery, once by config, and the tool is registered against the same name twice.

The tool

Effective name: searxng_search (tool name + _ + namespace).

Argument Type Notes
query string, required The search query.
engines string Comma-separated. Omit for the instance default.
category string Only sent when you pass it — see the additive trap below.
language string e.g. zh-CN, en-US. Omit for the instance default.
safesearch 0 | 1 | 2 Omit for the instance setting.
pageno number From 1. Engines without paging are dropped above page 1.
time_range string day, week, month, year. Engines without it are dropped entirely.

At setup() the plugin reads the instance's /config and bakes what the instance actually supports into each argument's description, so the model is told which engines lose paging or time filtering instead of discovering it by returning fewer results.

Two SearXNG behaviours that silently mislead

Both are handled, both are worth understanding because they fail silently.

1. categories is additive, not a filter. If you pass both engines and category, SearXNG takes the union: the named engines plus every engine enabled by default in that category. A careful engines=bing,yandex becomes bing,yandex + whatever the category contributes. The plugin therefore sends categories only when the caller explicitly asked for a category, never alongside an engine list it chose itself.

2. An unknown engine name falls back instead of erroring. If engines= names something the active engine list does not contain, SearXNG ignores the name and searches with the default engine set. No error, no unresponsive_engines entry, and results that look like a success. Verified on this instance:

engines=bing       -> bing x10
engines=360search  -> 360search x7
engines=baidu      -> 0 results, unresponsive_engines empty
engines=mojeek     -> bing x10     <-- fell back, mojeek is not active
engines=duckduckgo -> bing x10     <-- fell back
engines=nosuchengine -> bing x10   <-- typo: also fell back, no warning

Three signatures, and only the third means the engine was really used:

Observation Meaning
0 results and empty unresponsive_engines Engine is disabled or not in the active list
Results, but none of them from the requested name Fell back to the default engine set
Results including the requested name Genuinely used

/config is not a reliable way to check this. On this instance it reports disabled: false for every engine while baidu and sogou are in fact being dropped, because the listing reflects upstream defaults rather than the disabled: true overrides in settings.yml. Diagnose with the table above.

Measured state of the paired instance

SearXNG 2026.10.7-6671d89be, docker.io/searxng/searxng:latest (built 2026-10-07), http://127.0.0.1:39217.

Engine Status
bing Active and reliable, 5/5 non-empty runs on both query types. Needs base_url: https://cn.bing.com; www.bing.com blocks scraping from this network. Default.
yandex Active and reliable, 5/5, 15 results per run. Best measured relevance here; first result for a V1→V2 migration query is the migration doc itself. Default.
naver Reliable, 5/5. Strongest on English (migration doc ranked #1) but 0/9 on a Chinese query, so it is left out of the defaults and worth passing explicitly for English lookups.
360search Reliable but returns 4–5 results at 0% relevance. It splits the query on "V1"/"V2" and answers with Android and CANN version-difference articles. Removed from the defaults.
quark Unusable. Returned 11/11 relevant results twice, then 0 results on 5 consecutive runs of each query. Rate-limited after a couple of requests.
baidu Disabled. Answers with a CAPTCHA and suspends the client IP for an hour.
sogou Disabled. The upstream engine raises AttributeError at searx/engines/sogou.py:56 (resp.next_request) in this release.

bing supports neither paging nor time_range, so on this instance pageno > 1 and time_range both narrow the result to nothing useful. The tool says so in the argument descriptions rather than returning a silently short list.

There is no working domestic-only engine set

Stated plainly because it was measured, not assumed. Every general-web index SearXNG ships for China was added to keep_only, the container was restarted, and each was measured over 5 runs per query:

Engine en hits zh hits Outcome
360search 5/5 5/5 Reliable, 0% relevant
quark 0/5 0/5 Rate-limited dead after two good responses
baidu — 0 CAPTCHA, 1-hour IP suspension
sogou — 0 Upstream AttributeError
baidu kaifa — 0/10 A code index: matches "opencode v2" as OpenCV2 and returns CSDN blog posts
bilibili — — Video titles, not a web index
chinaso news — — Falls through to the default engine set
brave, duckduckgo, google, qwant, wikipedia 0 0 Unreachable from this network
mojeek, startpage — — Not present in this SearXNG release

quark is the trap. On its first two requests it returned the exact article being asked about as result #1 on both queries — better than anything else measured here — and then went permanently dark. Do not reinstate it on the strength of a first impression.

cn.bing.com is the only dependable endpoint, and it is a China-hosted Bing, so bing serves as the domestic option. Its one real limitation is that it ignores the site: operator completely: site:opencode.ai v2 migration returns 0 results from opencode.ai, while the same query without site: returns 3. Site-restricted search is not available through this instance — fetch the docs index and follow links instead.

Troubleshooting

Symptom Check
Tool absent from the catalog grep -i "loading plugin" ~/.local/share/opencode/log/opencode.log | tail
Cannot find package '@opencode/plugin' Package missing or wrong version. Match opencode --version exactly.
Installed it but still missing opencode service restart — the service caches module resolution.
fetch failed / no results at all curl -s "http://127.0.0.1:39217/config" | head -c 200 — is it up?
JSON format rejected json must be in search.formats in settings.yml.
Results from an unexpected engine The fallback in the table above. Check which engines actually appear.
Everything works, then stops after an edit The running service loaded the old file. Restart it.