opencode-search-plugin 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -128
- package/README.zh-CN.md +154 -135
- package/index.ts +3 -1
- package/package.json +3 -2
- package/plugins/zhipu-search.ts +426 -0
package/README.md
CHANGED
|
@@ -6,21 +6,21 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/opencode-search-plugin)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
**Web search for OpenCode that works out of the box, no API key
|
|
9
|
+
**Web search for OpenCode that works out of the box, no API key required.**
|
|
10
10
|
|
|
11
|
-
AnySearch and Keenable work
|
|
11
|
+
AnySearch and Keenable work immediately without any configuration. Other providers activate only when an API key is configured, ensuring you are not billed for unused services.
|
|
12
12
|
|
|
13
|
-
OpenCode
|
|
13
|
+
When OpenCode operates in `random` mode, it automatically fails over to the next available provider if one encounters an HTTP 429 rate limit. Providers without configured keys are excluded from the pool, preventing requests to unconfigured services.
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
16
|
opencode plugin add opencode-search-plugin
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
After installation, ask OpenCode any question that requires a web search.
|
|
20
20
|
|
|
21
21
|
## Skip the provider prompt
|
|
22
22
|
|
|
23
|
-
OpenCode
|
|
23
|
+
On your first search, OpenCode prompts you to select a search provider. Set `websearch.provider` in your configuration to bypass this prompt:
|
|
24
24
|
|
|
25
25
|
```jsonc
|
|
26
26
|
{
|
|
@@ -29,37 +29,37 @@ OpenCode asks to pick a search provider on your first search. You can set `webse
|
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
`"random"` uses one provider per session
|
|
32
|
+
`"random"` uses one provider per session and automatically fails over to another if rate-limited. You can also pin a specific provider using its ID (such as `"osp-brave"`), or disable web search with `false`.
|
|
33
33
|
|
|
34
34
|
<!-- TODO: replace this comment with a 10-second GIF:
|
|
35
35
|
set websearch.provider -> ask a question -> provider responds with sources -->
|
|
36
36
|
|
|
37
37
|
## Supported providers
|
|
38
38
|
|
|
39
|
-
[AnySearch](https://www.anysearch.com/) · [Bocha](https://bocha.ai/) · [Brave Search](https://brave.com/search/api/) · [DeepSeek](https://api-docs.deepseek.com/guides/search/) · [Jina](https://jina.ai/) · [Keenable](https://keenable.ai/) · [Linkup](https://www.linkup.so/) · [Metaso](https://metaso.cn/) · [Ollama](https://ollama.com/) · [Perplexity](https://www.perplexity.ai/) · [Querit](https://querit.ai/) · [SerpApi](https://serpapi.com/) · [Serper](https://serper.dev/) · [You.com](https://you.com/)
|
|
39
|
+
[AnySearch](https://www.anysearch.com/) · [Bocha](https://bocha.ai/) · [Brave Search](https://brave.com/search/api/) · [DeepSeek](https://api-docs.deepseek.com/guides/search/) · [Jina](https://jina.ai/) · [Keenable](https://keenable.ai/) · [Linkup](https://www.linkup.so/) · [Metaso](https://metaso.cn/) · [Ollama](https://ollama.com/) · [Perplexity](https://www.perplexity.ai/) · [Querit](https://querit.ai/) · [SerpApi](https://serpapi.com/) · [Serper](https://serper.dev/) · [You.com](https://you.com/) · [Zhipu](https://bigmodel.cn/)
|
|
40
40
|
|
|
41
41
|
## Why this exists
|
|
42
42
|
|
|
43
|
-
OpenCode v2 ships with built-in search providers: `exa`, `firecrawl`, `parallel`, `tavily`, and `tinyfish`.
|
|
43
|
+
OpenCode v2 ships with several built-in search providers: `exa`, `firecrawl`, `parallel`, `tavily`, and `tinyfish`. In addition, many external search APIs offer free tiers or generous free quotas.
|
|
44
44
|
|
|
45
|
-
This plugin
|
|
45
|
+
This plugin integrates those providers into OpenCode:
|
|
46
46
|
|
|
47
47
|
- AnySearch and Keenable work immediately without an API key or account.
|
|
48
|
-
-
|
|
49
|
-
- OpenCode's `random` mode
|
|
48
|
+
- Multiple providers offer free tiers, making it practical to maintain a low-cost backup pool.
|
|
49
|
+
- Expands OpenCode's `random` mode pool: when a provider returns an HTTP 429, OpenCode retries with another to prevent search interruptions.
|
|
50
50
|
|
|
51
51
|
## Install
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Choose your preferred installation method. **npm is recommended**: published versions are immutable, so pinning with `@<version>` guarantees a consistent build.
|
|
54
54
|
|
|
55
55
|
### 1. From npm (recommended)
|
|
56
56
|
|
|
57
57
|
```sh
|
|
58
|
-
#
|
|
58
|
+
# Append @<version> to pin a specific version
|
|
59
59
|
opencode plugin add opencode-search-plugin
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Or declare it in `opencode.json(c)`:
|
|
63
63
|
|
|
64
64
|
```jsonc
|
|
65
65
|
{
|
|
@@ -71,11 +71,11 @@ or declare it in `opencode.json(c)`:
|
|
|
71
71
|
### 2. From GitHub
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
|
-
#
|
|
74
|
+
# Append #v<version> to pin a specific tag
|
|
75
75
|
opencode plugin add github:guotree/opencode-search-plugin
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
Or declare it in `opencode.json(c)`:
|
|
79
79
|
|
|
80
80
|
```jsonc
|
|
81
81
|
{
|
|
@@ -86,24 +86,24 @@ or declare it in `opencode.json(c)`:
|
|
|
86
86
|
|
|
87
87
|
### 3. By copying a file
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
Each provider plugin is a self-contained `.ts` file with no relative imports and no build step. Place it in one of OpenCode's auto-discovery directories:
|
|
90
90
|
|
|
91
91
|
```text
|
|
92
92
|
~/.config/opencode/plugins/ # global
|
|
93
|
-
<project>/.opencode/plugins/ #
|
|
93
|
+
<project>/.opencode/plugins/ # project-scoped
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
For
|
|
96
|
+
For directories outside the auto-discovery paths, specify the file path explicitly:
|
|
97
97
|
|
|
98
98
|
```jsonc
|
|
99
99
|
{ "plugins": ["./plugins/brave-search.ts"] }
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
> Do not install the package *and* copy
|
|
102
|
+
> Do not install the package *and* copy individual plugin files. Both register the same provider IDs, which will duplicate entries in the search pool.
|
|
103
103
|
|
|
104
104
|
### Skip providers you don't use
|
|
105
105
|
|
|
106
|
-
Installing the package registers all providers, but
|
|
106
|
+
Installing the package registers all providers, but providers without configured API keys remain inactive (see [Keys](#keys)). To completely exclude or restrict providers, use `disable` or `enable`:
|
|
107
107
|
|
|
108
108
|
```jsonc
|
|
109
109
|
{
|
|
@@ -116,200 +116,205 @@ Installing the package registers all providers, but any provider without an API
|
|
|
116
116
|
}
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
- `disable`:
|
|
120
|
-
- `enable`:
|
|
119
|
+
- `disable`: Deny-list of provider IDs (from the table below). Because AnySearch and Keenable require no key, `disable` is the only way to remove them from the pool.
|
|
120
|
+
- `enable`: Allow-list of provider IDs. Only the listed IDs are registered. For example, `"enable": ["osp-brave", "osp-jina"]` activates only those two.
|
|
121
121
|
|
|
122
|
-
If
|
|
122
|
+
If both are specified, **`disable` takes precedence on any overlap**:
|
|
123
123
|
|
|
124
124
|
```jsonc
|
|
125
125
|
{
|
|
126
126
|
"options": {
|
|
127
127
|
"enable": ["osp-brave", "osp-jina", "osp-serper"],
|
|
128
128
|
"disable": ["osp-serper", "osp-youcom"]
|
|
129
|
-
// osp-serper
|
|
130
|
-
// osp-youcom
|
|
129
|
+
// osp-serper is excluded: disable overrides enable.
|
|
130
|
+
// osp-youcom was not in enable, so it is safely ignored.
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
The active
|
|
135
|
+
The active set is computed as `enable - disable`. Overlapping IDs are dropped, and invalid or unrecognized IDs produce a warning in the logs rather than failing silently.
|
|
136
136
|
|
|
137
137
|
## Upgrade
|
|
138
138
|
|
|
139
139
|
```sh
|
|
140
|
-
#
|
|
140
|
+
# Installed from npm
|
|
141
141
|
opencode plugin update opencode-search-plugin
|
|
142
|
-
#
|
|
142
|
+
# Installed from GitHub
|
|
143
143
|
opencode plugin update github:guotree/opencode-search-plugin
|
|
144
|
-
#
|
|
144
|
+
# Update all outdated plugins
|
|
145
145
|
opencode plugin update
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
The target
|
|
148
|
+
The update target must match the configured plugin specifier:
|
|
149
|
+
- For npm installations, use the package name (`opencode-search-plugin`).
|
|
150
|
+
- For GitHub installations, use the full `github:` specifier (using a bare package name will return `Plugin is not configured`).
|
|
151
|
+
|
|
152
|
+
Run `opencode plugin check` first to inspect available updates. Unpinned installations update to the latest release. Pinned installations (`@<version>` on npm, `#v<version>` on GitHub) remain at their pinned version until you edit the specifier in `opencode.json(c)` or run `plugin add` with a new version. For locally copied files, replace the existing `.ts` file with the newer version.
|
|
149
153
|
|
|
150
154
|
## Uninstall
|
|
151
155
|
|
|
152
156
|
```sh
|
|
153
|
-
#
|
|
157
|
+
# Installed from npm
|
|
154
158
|
opencode plugin remove opencode-search-plugin
|
|
155
|
-
#
|
|
159
|
+
# Installed from GitHub
|
|
156
160
|
opencode plugin remove github:guotree/opencode-search-plugin
|
|
157
161
|
```
|
|
158
162
|
|
|
159
|
-
|
|
163
|
+
Pass the same specifier used during installation. This command updates global configuration. If the plugin was configured as an entry in `opencode.json(c)` or as a local file, remove the configuration entry or delete the copied `.ts` file. Saved `/connect` credentials are preserved and will be recognized upon reinstallation. To deactivate specific providers without removing the entire plugin, use `enable` / `disable` as described above.
|
|
160
164
|
|
|
161
165
|
## Keys
|
|
162
166
|
|
|
163
|
-
Every provider except AnySearch and Keenable
|
|
167
|
+
Every provider except AnySearch and Keenable requires an API key. You can provide credentials in one of two ways:
|
|
164
168
|
|
|
165
|
-
- **`/connect
|
|
166
|
-
- **Environment
|
|
169
|
+
- **`/connect` in OpenCode**: Stored per provider, rotatable without restarting OpenCode, and unaffected by environment changes.
|
|
170
|
+
- **Environment variables**: Read as a fallback for headless, automated, or CI environments.
|
|
167
171
|
|
|
168
|
-
Environment variables
|
|
172
|
+
Environment variables use the `OSP_<PROVIDER>_API_KEY` namespace (e.g., `OSP_BRAVE_API_KEY`) to prevent naming collisions with other tools that export vendor variables. Credentials stored in `/connect` always take precedence over environment variables.
|
|
169
173
|
|
|
170
|
-
A provider
|
|
174
|
+
A provider without a configured key remains unbound: it is omitted from the search pool, ensuring `"provider": "random"` never routes queries to a service that would immediately fail.
|
|
171
175
|
|
|
172
|
-
AnySearch and Keenable are
|
|
176
|
+
AnySearch and Keenable are exceptions. Both include an anonymous tier and are registered by default, attaching credentials only when a key is provided. For Keenable, the two tiers use separate quotas: configuring a key provides 100,000 free requests per month, whereas keyless requests route to the `/public` endpoint and are rate-limited to 1,000 requests/hour and 10 requests/second per IP (shared across all users on that egress address). Configuring an API key removes dependence on that shared IP rate limit.
|
|
173
177
|
|
|
174
|
-
With keys
|
|
178
|
+
With keys configured, `websearch.provider` controls routing: `"random"` distributes queries across all bound providers (see [Skip the provider prompt](#skip-the-provider-prompt)), a specific ID such as `"osp-brave"` pins that provider, and `false` disables web search entirely.
|
|
175
179
|
|
|
176
180
|
## Providers
|
|
177
181
|
|
|
178
|
-
Each ID
|
|
182
|
+
Each provider ID carries an `osp-` prefix to prevent name collisions with OpenCode's built-in providers or official vendor plugins (which use bare names like `exa` or `tavily`). Use the prefixed ID in `websearch.provider` and in `enable` / `disable` options.
|
|
179
183
|
|
|
180
|
-
In OpenCode's `/connect` settings, each provider is labeled with an `(osp)` suffix (e.g
|
|
184
|
+
In OpenCode's `/connect` settings, each provider is labeled with an `(osp)` suffix (e.g., `DeepSeek (osp)`) to distinguish it from built-in integrations. The search pool itself displays the clean provider name without the suffix.
|
|
181
185
|
|
|
182
186
|
| Provider | ID | Key (env / `/connect`) | Anonymous | Notes |
|
|
183
187
|
| --- | --- | --- | --- | --- |
|
|
184
|
-
| AnySearch | `osp-anysearch` | `OSP_ANYSEARCH_API_KEY` | yes | Free tier per IP; intent
|
|
185
|
-
| Bocha | `osp-bocha` | `OSP_BOCHA_API_KEY` | no | Bing-compatible results; billed per search, and by
|
|
188
|
+
| AnySearch | `osp-anysearch` | `OSP_ANYSEARCH_API_KEY` | yes | Free tier per IP; intent routing, `cn` / `intl` zones |
|
|
189
|
+
| Bocha | `osp-bocha` | `OSP_BOCHA_API_KEY` | no | Bing-compatible results; billed per search, and by token when summaries are requested |
|
|
186
190
|
| Brave Search | `osp-brave` | `OSP_BRAVE_API_KEY` | no | Independent index, 20 results max |
|
|
187
|
-
| DeepSeek | `osp-deepseek` | `OSP_DEEPSEEK_API_KEY` | no | In-house index; $0.75 per 1,000 successful requests; 5 QPS
|
|
188
|
-
| Jina | `osp-jina` | `OSP_JINA_API_KEY` | no |
|
|
189
|
-
| Keenable | `osp-keenable` | `OSP_KEENABLE_API_KEY` | yes | 100,000 requests
|
|
190
|
-
| Linkup | `osp-linkup` | `OSP_LINKUP_API_KEY` | no | `flash
|
|
191
|
+
| DeepSeek | `osp-deepseek` | `OSP_DEEPSEEK_API_KEY` | no | In-house index; $0.75 per 1,000 successful requests; 5 QPS rate limit |
|
|
192
|
+
| Jina | `osp-jina` | `OSP_JINA_API_KEY` | no | Full-page content is opt-in due to token-based billing |
|
|
193
|
+
| Keenable | `osp-keenable` | `OSP_KEENABLE_API_KEY` | yes | 100,000 free requests/month with a key; keyless tier is rate-limited per IP |
|
|
194
|
+
| Linkup | `osp-linkup` | `OSP_LINKUP_API_KEY` | no | Four reasoning depths: `flash`, `fast`, `standard`, `deep` |
|
|
191
195
|
| Metaso | `osp-metaso` | `OSP_METASO_API_KEY` | no | Billed per search, not per result |
|
|
192
|
-
| Ollama | `osp-ollama` | `OSP_OLLAMA_API_KEY` | no | Free personal tier;
|
|
193
|
-
| Perplexity | `osp-perplexity` | `OSP_PERPLEXITY_API_KEY` | no | Search API returns
|
|
196
|
+
| Ollama | `osp-ollama` | `OSP_OLLAMA_API_KEY` | no | Free personal tier; returns full-page markdown per result (up to 10 results, no publication dates) |
|
|
197
|
+
| Perplexity | `osp-perplexity` | `OSP_PERPLEXITY_API_KEY` | no | Search API returns direct search results without generated answers; $5.00 / 1,000 requests ($1.00 with `fast`) |
|
|
194
198
|
| Querit | `osp-querit` | `OSP_QUERIT_API_KEY` | no | Falls back to sentence matches when a snippet is missing |
|
|
195
|
-
| SerpApi | `osp-serpapi` | `OSP_SERPAPI_API_KEY` | no | Google via SerpApi;
|
|
199
|
+
| SerpApi | `osp-serpapi` | `OSP_SERPAPI_API_KEY` | no | Google via SerpApi; includes SerpApi's free 1-hour server-side cache |
|
|
196
200
|
| Serper | `osp-serper` | `OSP_SERPER_API_KEY` | no | Google via Serper; billed per query in credits |
|
|
197
|
-
| You.com | `osp-youcom` | `OSP_YDC_API_KEY` | no |
|
|
201
|
+
| You.com | `osp-youcom` | `OSP_YDC_API_KEY` | no | Combined web and news results; multiple extraction modes |
|
|
202
|
+
| Zhipu | `osp-zhipu` | `OSP_ZHIPU_API_KEY` | no | Standalone search endpoint, no model call; billed per search from ¥0.01 (Search-Std), no free tier; four engines including Sogou and Quark; `cn` / `intl` sites both supported |
|
|
198
203
|
|
|
199
204
|
## Configuration
|
|
200
205
|
|
|
201
|
-
Configuration options are
|
|
206
|
+
Configuration options are loaded as environment variables on startup. If you change a setting, restart OpenCode for it to take effect. Default values align with upstream API documentation recommendations.
|
|
202
207
|
|
|
203
208
|
### Shared by every provider
|
|
204
209
|
|
|
205
|
-
| Variable | Default |
|
|
210
|
+
| Variable | Default | Description |
|
|
206
211
|
| --- | --- | --- |
|
|
207
|
-
| `<PROVIDER>_CACHE_TTL` | `600000` |
|
|
208
|
-
| `<PROVIDER>_TIMEOUT` | `25000` |
|
|
212
|
+
| `<PROVIDER>_CACHE_TTL` | `600000` | Duration in milliseconds to reuse cached responses for identical normalized queries (`0` disables caching) |
|
|
213
|
+
| `<PROVIDER>_TIMEOUT` | `25000` | Timeout in milliseconds before failing a request (`0` removes timeout while preserving cancellation) |
|
|
209
214
|
|
|
210
215
|
### AnySearch
|
|
211
216
|
|
|
212
|
-
| Variable | Default |
|
|
217
|
+
| Variable | Default | Description |
|
|
213
218
|
| --- | --- | --- |
|
|
214
|
-
| `ANYSEARCH_MAX_RESULTS` | `10` | Results per search, clamped to
|
|
215
|
-
| `ANYSEARCH_TAG` | — |
|
|
216
|
-
| `ANYSEARCH_ZONE` | — | `cn` or `intl` |
|
|
217
|
-
| `ANYSEARCH_LANGUAGE` | — | Preferred result language, e.g
|
|
218
|
-
| `ANYSEARCH_FORMAT` | `markdown` | `markdown` or `json`
|
|
219
|
-
| `OSP_ANYSEARCH_API_KEY` | — | Optional; anonymous access
|
|
219
|
+
| `ANYSEARCH_MAX_RESULTS` | `10` | Results per search, clamped to 1–10 |
|
|
220
|
+
| `ANYSEARCH_TAG` | — | Pins the intent router to a sub-domain, e.g., `code.doc` |
|
|
221
|
+
| `ANYSEARCH_ZONE` | — | Geographic zone: `cn` or `intl` |
|
|
222
|
+
| `ANYSEARCH_LANGUAGE` | — | Preferred result language, e.g., `zh-CN` |
|
|
223
|
+
| `ANYSEARCH_FORMAT` | `markdown` | Content format: `markdown` or `json` |
|
|
224
|
+
| `OSP_ANYSEARCH_API_KEY` | — | Optional; anonymous access is used when omitted |
|
|
220
225
|
|
|
221
226
|
### Bocha
|
|
222
227
|
|
|
223
|
-
| Variable | Default |
|
|
228
|
+
| Variable | Default | Description |
|
|
224
229
|
| --- | --- | --- |
|
|
225
230
|
| `BOCHA_COUNT` | `10` | Results per search, clamped to 1–50 (Bocha's maximum) |
|
|
226
|
-
| `BOCHA_FRESHNESS` | — | `noLimit`, `oneDay`, `oneWeek`, `oneMonth`, `oneYear`,
|
|
227
|
-
| `BOCHA_SUMMARY` | off | `true`
|
|
231
|
+
| `BOCHA_FRESHNESS` | — | Freshness filter: `noLimit`, `oneDay`, `oneWeek`, `oneMonth`, `oneYear`, `YYYY-MM-DD`, or `YYYY-MM-DD..YYYY-MM-DD`. Omitting this parameter is recommended by Bocha, as upstream query rewriting may yield empty results for fixed ranges |
|
|
232
|
+
| `BOCHA_SUMMARY` | `off` | `true` replaces snippets with generated summaries (provides more context at higher per-request cost) |
|
|
228
233
|
|
|
229
234
|
### Brave Search
|
|
230
235
|
|
|
231
|
-
| Variable | Default |
|
|
236
|
+
| Variable | Default | Description |
|
|
232
237
|
| --- | --- | --- |
|
|
233
238
|
| `BRAVE_NUM` | `8` | Results per search, clamped to 1–20 (Brave's maximum) |
|
|
234
239
|
| `BRAVE_COUNTRY` | — | Two-letter country code, or `ALL` |
|
|
235
|
-
| `BRAVE_LANG` | — | Result language, e.g
|
|
236
|
-
| `BRAVE_FRESHNESS` | — | `pd` (24h), `pw` (7d), `pm` (31d), `py` (365d), or `YYYY-MM-DDtoYYYY-MM-DD` |
|
|
240
|
+
| `BRAVE_LANG` | — | Result language code, e.g., `en`, `zh-hans`, `pt-br` |
|
|
241
|
+
| `BRAVE_FRESHNESS` | — | Time filter: `pd` (24h), `pw` (7d), `pm` (31d), `py` (365d), or `YYYY-MM-DDtoYYYY-MM-DD` |
|
|
237
242
|
|
|
238
243
|
### DeepSeek
|
|
239
244
|
|
|
240
|
-
| Variable | Default |
|
|
245
|
+
| Variable | Default | Description |
|
|
241
246
|
| --- | --- | --- |
|
|
242
|
-
| `DEEPSEEK_COUNT` | `10` | Results per search, clamped to 1–50 (DeepSeek's maximum).
|
|
247
|
+
| `DEEPSEEK_COUNT` | `10` | Results per search, clamped to 1–50 (DeepSeek's maximum). Billed per request; additional results incur no extra charge |
|
|
243
248
|
|
|
244
249
|
### Jina
|
|
245
250
|
|
|
246
|
-
| Variable | Default |
|
|
251
|
+
| Variable | Default | Description |
|
|
247
252
|
| --- | --- | --- |
|
|
248
|
-
| `JINA_NUM` | `0` | Results per search
|
|
249
|
-
| `JINA_INCLUDE_CONTENT` | off | `true` returns
|
|
253
|
+
| `JINA_NUM` | `0` | Results per search (1–100). `0` omits the parameter, preserving Jina's faster and broader result types |
|
|
254
|
+
| `JINA_INCLUDE_CONTENT` | `off` | `true` returns full-page markdown per hit, significantly increasing context and billed tokens |
|
|
250
255
|
| `JINA_GL` / `JINA_HL` | — | Country / language code |
|
|
251
|
-
| `JINA_LOCATION` | — |
|
|
252
|
-
| `JINA_TIMEOUT` | `25000
|
|
256
|
+
| `JINA_LOCATION` | — | Simulated city-level location, e.g., `Berlin` |
|
|
257
|
+
| `JINA_TIMEOUT` | `25000` (`60000` with `JINA_INCLUDE_CONTENT`) | Request timeout in milliseconds; full-page fetching uses a higher default timeout |
|
|
253
258
|
|
|
254
259
|
### Keenable
|
|
255
260
|
|
|
256
|
-
| Variable | Default |
|
|
261
|
+
| Variable | Default | Description |
|
|
257
262
|
| --- | --- | --- |
|
|
258
263
|
| `KEENABLE_MAX_RESULTS` | `10` | Results per search, clamped to 1–50 |
|
|
259
|
-
| `KEENABLE_MODE` | — | `pro` (deeper retrieval) or `realtime` (
|
|
260
|
-
| `KEENABLE_TITLE` | `opencode-search-plugin` |
|
|
261
|
-
| `OSP_KEENABLE_API_KEY` | — | Optional; keyless access
|
|
264
|
+
| `KEENABLE_MODE` | — | Search mode: `pro` (deeper retrieval) or `realtime` (faster); unset uses Keenable's default |
|
|
265
|
+
| `KEENABLE_TITLE` | `opencode-search-plugin` | Application identifier sent as `X-Keenable-Title`, required by the keyless endpoint for attribution |
|
|
266
|
+
| `OSP_KEENABLE_API_KEY` | — | Optional; keyless access is used when omitted |
|
|
262
267
|
|
|
263
268
|
### Linkup
|
|
264
269
|
|
|
265
|
-
| Variable | Default |
|
|
270
|
+
| Variable | Default | Description |
|
|
266
271
|
| --- | --- | --- |
|
|
267
|
-
| `LINKUP_DEPTH` | `fast` | `flash`, `fast`, `standard`, or `deep` |
|
|
272
|
+
| `LINKUP_DEPTH` | `fast` | Search depth: `flash`, `fast`, `standard`, or `deep` |
|
|
268
273
|
| `LINKUP_MAX_RESULTS` | `10` | Results per search, clamped to 1–100 |
|
|
269
274
|
|
|
270
275
|
### Metaso
|
|
271
276
|
|
|
272
|
-
| Variable | Default |
|
|
277
|
+
| Variable | Default | Description |
|
|
273
278
|
| --- | --- | --- |
|
|
274
279
|
| `METASO_SEARCH_SIZE` | `20` | Results per search, clamped to 1–100 |
|
|
275
|
-
| `METASO_INCLUDE_RAW_CONTENT` | off | `true` returns
|
|
280
|
+
| `METASO_INCLUDE_RAW_CONTENT` | `off` | `true` returns full source text per hit without extra search cost, but substantially increases context tokens |
|
|
276
281
|
|
|
277
282
|
### Ollama
|
|
278
283
|
|
|
279
|
-
| Variable | Default |
|
|
284
|
+
| Variable | Default | Description |
|
|
280
285
|
| --- | --- | --- |
|
|
281
|
-
| `OLLAMA_MAX_RESULTS` | `5` | Results per search, clamped to 1–10 (
|
|
286
|
+
| `OLLAMA_MAX_RESULTS` | `5` | Results per search, clamped to 1–10 (API maximum). Because each result includes full-page markdown rather than snippets, the default 5 results provide extensive context |
|
|
282
287
|
|
|
283
288
|
### Perplexity
|
|
284
289
|
|
|
285
|
-
| Variable | Default |
|
|
290
|
+
| Variable | Default | Description |
|
|
286
291
|
| --- | --- | --- |
|
|
287
|
-
| `PERPLEXITY_MAX_RESULTS` | `10` | Results per search, clamped to 1–20
|
|
288
|
-
| `PERPLEXITY_SEARCH_TYPE` | — | `web` (
|
|
289
|
-
| `PERPLEXITY_RECENCY` | — | `hour`, `day`, `week`, `month`, or `year` |
|
|
290
|
-
| `PERPLEXITY_COUNTRY` | — | Two-letter ISO 3166-1 country code, e.g
|
|
291
|
-
| `PERPLEXITY_LANGUAGE` | — | ISO 639-1 codes,
|
|
292
|
+
| `PERPLEXITY_MAX_RESULTS` | `10` | Results per search, clamped to 1–20 (web search supports up to 20; `search_type=people` accepts up to 50) |
|
|
293
|
+
| `PERPLEXITY_SEARCH_TYPE` | — | Search type: `web` (API default, $5.00 / 1,000 requests), `fast` ($1.00 / 1,000 requests, lower latency), or `people`; unset omits the parameter |
|
|
294
|
+
| `PERPLEXITY_RECENCY` | — | Recency filter: `hour`, `day`, `week`, `month`, or `year` |
|
|
295
|
+
| `PERPLEXITY_COUNTRY` | — | Two-letter ISO 3166-1 country code, e.g., `US` |
|
|
296
|
+
| `PERPLEXITY_LANGUAGE` | — | Comma-separated ISO 639-1 language codes, e.g., `en,zh` (maximum of 10 codes sent, adhering to the stricter quickstart limit) |
|
|
292
297
|
|
|
293
|
-
There is
|
|
298
|
+
There is intentionally no domain or source filter configuration: the Perplexity API provides no dedicated toggle for news, Reddit, or academic search. Upstream documentation recommends using `search_domain_filter` allowlists per query rather than applying a global configuration.
|
|
294
299
|
|
|
295
300
|
### Querit
|
|
296
301
|
|
|
297
|
-
| Variable | Default |
|
|
302
|
+
| Variable | Default | Description |
|
|
298
303
|
| --- | --- | --- |
|
|
299
304
|
| `QUERIT_COUNT` | `8` | Results per search |
|
|
300
305
|
|
|
301
306
|
### SerpApi
|
|
302
307
|
|
|
303
|
-
| Variable | Default |
|
|
308
|
+
| Variable | Default | Description |
|
|
304
309
|
| --- | --- | --- |
|
|
305
310
|
| `SERPAPI_GL` / `SERPAPI_HL` | — | Country / language code |
|
|
306
|
-
| `SERPAPI_TBS` | — | Advanced filter, e.g
|
|
311
|
+
| `SERPAPI_TBS` | — | Advanced search filter, e.g., `qdr:d` (past day), `qdr:y` (past year) |
|
|
307
312
|
|
|
308
|
-
There is
|
|
313
|
+
There is intentionally no result-count configuration: Google deprecated the `num` parameter, so SerpApi no longer supports it.
|
|
309
314
|
|
|
310
315
|
### Serper
|
|
311
316
|
|
|
312
|
-
| Variable | Default |
|
|
317
|
+
| Variable | Default | Description |
|
|
313
318
|
| --- | --- | --- |
|
|
314
319
|
| `SERPER_NUM` | `10` | Results per search, clamped to 1–100 |
|
|
315
320
|
| `SERPER_GL` / `SERPER_HL` | — | Country / language code |
|
|
@@ -317,48 +322,62 @@ There is deliberately no result-count knob: Google withdrew the `num` parameter,
|
|
|
317
322
|
|
|
318
323
|
### You.com
|
|
319
324
|
|
|
320
|
-
| Variable | Default |
|
|
325
|
+
| Variable | Default | Description |
|
|
321
326
|
| --- | --- | --- |
|
|
322
327
|
| `YOUCOM_COUNT` | `10` | Results per section (web and news each), clamped to 1–100 |
|
|
323
|
-
| `YOUCOM_EXTRACTION` | `highlights` | `highlights` returns answering passages, `none` returns
|
|
324
|
-
| `YOUCOM_LANGUAGE` / `YOUCOM_COUNTRY` | — | e.g
|
|
325
|
-
| `YOUCOM_FRESHNESS` | — | `day`, `week`, `month`, `year`, or `YYYY-MM-DDtoYYYY-MM-DD` |
|
|
328
|
+
| `YOUCOM_EXTRACTION` | `highlights` | Extraction mode: `highlights` returns answering passages, `none` returns raw snippets, and `full_page` fetches full pages and bills per fetched page |
|
|
329
|
+
| `YOUCOM_LANGUAGE` / `YOUCOM_COUNTRY` | — | Language / country code, e.g., `EN` / `US` |
|
|
330
|
+
| `YOUCOM_FRESHNESS` | — | Freshness filter: `day`, `week`, `month`, `year`, or `YYYY-MM-DDtoYYYY-MM-DD` |
|
|
331
|
+
|
|
332
|
+
### Zhipu
|
|
333
|
+
|
|
334
|
+
| Variable | Default | Description |
|
|
335
|
+
| --- | --- | --- |
|
|
336
|
+
| `ZHIPU_COUNT` | `10` | Results per search, clamped to 1–50 (Zhipu's maximum) |
|
|
337
|
+
| `ZHIPU_ENGINE` | `search_std` | Search engine: `search_std` (¥0.01/search), `search_pro` (¥0.03, higher recall), `search_pro_sogou` / `search_pro_quark` (¥0.05 each). On `ZHIPU_SITE=intl` the only documented engine is `search-prime` ($0.01/search), which is also that site's default |
|
|
338
|
+
| `ZHIPU_RECENCY` | — | Time filter: `oneDay`, `oneWeek`, `oneMonth`, `oneYear`, or `noLimit`. Omitting it leaves the API's `noLimit` default in place |
|
|
339
|
+
| `ZHIPU_CONTENT_SIZE` | — | Excerpt length: `medium` (the API default) or `high`, which sends fuller excerpts at the same per-search price |
|
|
340
|
+
| `ZHIPU_SITE` | `cn` | Endpoint: `cn` for `open.bigmodel.cn`, `intl` for `api.z.ai`. The two are separate account systems -- keys, engines and prices are not interchangeable |
|
|
341
|
+
|
|
342
|
+
The default `search_std` is the cheapest engine; `search_pro` costs three times as much for higher recall, and the Sogou / Quark engines trade a higher price for their own source pools (Sogou covers the Tencent ecosystem and Zhihu; Quark skews recent). Every dispatch is billed -- there is no free tier -- so repeat queries ride the shared cache. Queries are cut to the API's 70-character `search_query` ceiling before dispatch, `count` is omitted on `search_pro_quark` (whose spec row leaves the field out), and on `search_pro_sogou` it is snapped to the nearest of 10/20/30/40/50.
|
|
326
343
|
|
|
327
344
|
## How a query flows
|
|
328
345
|
|
|
329
|
-
1. OpenCode
|
|
330
|
-
2. The normalized query (trimmed
|
|
331
|
-
3. On a cache miss, the API key is resolved
|
|
332
|
-
4. The request
|
|
333
|
-
5. The response is schema-validated, mapped to `{ url, title, content, time }`, and cached for
|
|
346
|
+
1. OpenCode issues a search request to the active provider.
|
|
347
|
+
2. The normalized query (trimmed of leading/trailing whitespace with internal whitespace collapsed) is checked against an in-memory cache. Cache hits return immediately without an external API request.
|
|
348
|
+
3. On a cache miss, the API key is freshly resolved so that keys rotated in `/connect` take effect immediately.
|
|
349
|
+
4. The request is dispatched with an abort timeout (`<PROVIDER>_TIMEOUT`, default 25s) to prevent unresponsive upstreams from hanging the session.
|
|
350
|
+
5. The response is schema-validated, mapped to `{ url, title, content, time }`, and cached for subsequent identical queries.
|
|
334
351
|
|
|
335
|
-
|
|
352
|
+
Publication dates are parsed from relative expressions (`3 hours ago`, `3小时前`), absolute dates (`Sep 30, 2026`, `31 Mar 2026`), and ISO 8601 timestamps. Incomplete date strings that could cause lenient parsers to infer an incorrect year (such as `Nov 1`) are ignored.
|
|
336
353
|
|
|
337
354
|
## Troubleshooting
|
|
338
355
|
|
|
339
356
|
```sh
|
|
340
|
-
#
|
|
357
|
+
# Check if the plugin is loaded and view its status
|
|
341
358
|
opencode plugin list
|
|
342
359
|
|
|
343
|
-
#
|
|
344
|
-
# A provider
|
|
345
|
-
# AnySearch and Keenable are always
|
|
360
|
+
# List providers currently active in the search pool
|
|
361
|
+
# A provider omitted here either lacks an API key or was excluded by enable/disable
|
|
362
|
+
# AnySearch and Keenable are always included since neither requires a key
|
|
346
363
|
opencode api get "/api/websearch/provider"
|
|
347
364
|
```
|
|
348
365
|
|
|
349
|
-
|
|
366
|
+
Common issues to check:
|
|
350
367
|
|
|
351
|
-
- **
|
|
352
|
-
- **
|
|
353
|
-
- **
|
|
354
|
-
- **
|
|
355
|
-
- **
|
|
356
|
-
- **
|
|
357
|
-
- **
|
|
358
|
-
- **
|
|
359
|
-
- **
|
|
368
|
+
- **Provider is missing from the search pool**: The provider lacks an API key or was excluded by `disable` (or omitted from `enable`). Configure the key in `/connect` or export its environment variable and restart OpenCode (credentials updated in `/connect` rebind immediately without restarting). If a configured provider is missing, check the logs for typos: unrecognized IDs and conflicts between `enable` and `disable` are flagged on startup.
|
|
369
|
+
- **Search fails with `Keenable rate limited the request (HTTP 429)`**: The keyless tier is limited to 1,000 requests/hour and 10 requests/second per IP, shared across all users on that egress address. Configure `OSP_KEENABLE_API_KEY` to use your dedicated free monthly quota instead.
|
|
370
|
+
- **Search fails with `Ollama API error 401`**: `OSP_OLLAMA_API_KEY` must be an ollama.com account key from [ollama.com/settings/keys](https://ollama.com/settings/keys). If your environment variable holds a bearer token for a local or custom Ollama proxy, configure the API key in `/connect` under **Ollama Search (osp)** instead, which takes precedence over the environment variable.
|
|
371
|
+
- **Search fails with `Bocha balance is exhausted (HTTP 403)`**: The account balance is exhausted; recharge at [open.bocha.cn](https://open.bocha.cn). The error message includes Bocha's response message and a `log_id` for contacting support.
|
|
372
|
+
- **Search fails with `Bocha rate limited the request (HTTP 429)`**: Bocha's per-account request limit scales with total recharge amount. When returned by Bocha, a `retry after Ns` hint is included in the error message.
|
|
373
|
+
- **Search fails with `Zhipu rejected the API key (HTTP 401)`**: The key most likely belongs to the other site. The `cn` site ([open.bigmodel.cn](https://open.bigmodel.cn), `ZHIPU_SITE=cn`) and the `intl` site ([api.z.ai](https://api.z.ai), `ZHIPU_SITE=intl`) are separate account systems whose keys are not interchangeable. Set `ZHIPU_SITE` to match where the key was issued, or get a key from [bigmodel.cn](https://bigmodel.cn/usercenter/proj-mgmt/apikeys).
|
|
374
|
+
- **Search fails with `Zhipu rate limited the request (HTTP 429)`**: Concurrent searches are capped per account, reported by the API as code 1701. Zhipu attaches a `retry after Ns` hint when it has one; waiting it out or running fewer searches at once clears the refusal.
|
|
375
|
+
- **Search fails with `Connect X in /connect, or set OSP_X_API_KEY`**: The credentials were removed or became unavailable between provider registration and search execution. Reconfigure the key in `/connect` or export the environment variable.
|
|
376
|
+
- **Search fails with `X search timed out after 25000ms`**: The upstream provider did not respond within the deadline. Retrying often succeeds. If a provider is consistently slow in your network environment, increase its `<PROVIDER>_TIMEOUT`. This deadline prevents a hanging upstream connection from blocking the entire session, as OpenCode does not enforce its own search timeout.
|
|
377
|
+
- **Queries are slow or consuming excessive credits**: Keep `*_CACHE_TTL` at its default value. Identical queries within the cache TTL window are served from memory without network calls or credit consumption.
|
|
378
|
+
- **Changes to a local plugin file do not take effect**: OpenCode watches plugins and reloads them automatically. If changes are not picked up, run `opencode service restart`.
|
|
360
379
|
|
|
361
|
-
Logs
|
|
380
|
+
Logs are located at `~/.local/share/opencode/log/opencode.log`. Filter for `loading plugin` to inspect each resolved plugin and its entry point. Plugin warnings (such as `enable` / `disable` conflicts) are written to this file with an `[opencode-search-plugin]` prefix.
|
|
362
381
|
|
|
363
382
|
## Development
|
|
364
383
|
|
|
@@ -367,12 +386,12 @@ npm install
|
|
|
367
386
|
npm run check # typecheck + tests
|
|
368
387
|
```
|
|
369
388
|
|
|
370
|
-
- `npm run typecheck`:
|
|
371
|
-
- `npm test`: the shared provider contract,
|
|
389
|
+
- `npm run typecheck`: Strict TypeScript checking across all plugins and tests.
|
|
390
|
+
- `npm test`: Runs the shared provider contract, individual provider behaviors, date parsing, aggregate registration, and repository structure guards.
|
|
372
391
|
|
|
373
|
-
`@opencode/plugin` and `effect` are declared as **devDependencies**, not `dependencies`. OpenCode provides both
|
|
392
|
+
`@opencode/plugin` and `effect` are declared as **devDependencies**, not `dependencies`. OpenCode provides both packages at runtime, which allows standalone `.ts` files to run directly from `~/.config/opencode/plugins/` without a local `node_modules` directory. Declaring them as runtime dependencies would cause `opencode plugin add` to download a 127 MB dependency tree (`effect` alone is 47 MB, and `@opencode/plugin` pulls in `@aws-sdk`, `google-auth-library`, and `@opentelemetry`) for 100 KB of plugin source code. Declaring them as devDependencies keeps them available for typechecking and testing while omitting them from consumer installations.
|
|
374
393
|
|
|
375
|
-
|
|
394
|
+
Structure tests enforce that each plugin file remains independently copyable. They will fail if a plugin imports a sibling module, if shared blocks diverge from their canonical byte-for-byte implementation, or if provider IDs become inconsistent. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
376
395
|
|
|
377
396
|
## License
|
|
378
397
|
|