opencode-search-plugin 2.1.1 → 3.0.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 +87 -46
- package/README.zh-CN.md +85 -44
- package/index.ts +5 -1
- package/package.json +5 -2
- package/plugins/anysearch-search.ts +24 -6
- package/plugins/bocha-search.ts +392 -0
- package/plugins/brave-search.ts +25 -6
- package/plugins/deepseek-search.ts +24 -6
- package/plugins/jina-search.ts +31 -6
- package/plugins/keenable-search.ts +25 -7
- package/plugins/linkup-search.ts +24 -6
- package/plugins/metaso-search.ts +24 -6
- package/plugins/ollama-search.ts +253 -0
- package/plugins/querit-search.ts +24 -6
- package/plugins/serpapi-search.ts +24 -6
- package/plugins/serper-search.ts +24 -6
- package/plugins/youcom-search.ts +24 -6
package/README.md
CHANGED
|
@@ -2,24 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
**English** · [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/opencode-search-plugin)
|
|
6
|
+
[](https://www.npmjs.com/package/opencode-search-plugin)
|
|
7
|
+
[](LICENSE)
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
**Web search for OpenCode that works out of the box, no API key needed.**
|
|
10
|
+
|
|
11
|
+
One command adds thirteen search providers to OpenCode v2, growing the pool from five to eighteen. AnySearch and Keenable work with no setup at all; the other eleven only activate once you add a key.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
opencode plugin add opencode-search-plugin
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Install it, then just ask OpenCode something that needs the web.
|
|
18
|
+
|
|
19
|
+
<!-- TODO: replace this comment with a 10-second GIF once you record one:
|
|
20
|
+
ask a question → the websearch tool fires → results come back -->
|
|
21
|
+
|
|
22
|
+
Thirteen providers in one package, use whichever you like:
|
|
23
|
+
|
|
24
|
+
[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/) · [Querit](https://querit.ai/) · [SerpApi](https://serpapi.com/) · [Serper](https://serper.dev/) · [You.com](https://you.com/)
|
|
8
25
|
|
|
9
26
|
## Why this exists
|
|
10
27
|
|
|
11
|
-
OpenCode v2 ships with five web search providers built in: `exa`, `firecrawl`, `parallel`, `tavily` and `tinyfish`. Outside that set, a much wider field of search APIs offers free quotas or free tiers
|
|
28
|
+
OpenCode v2 ships with five web search providers built in: `exa`, `firecrawl`, `parallel`, `tavily` and `tinyfish`. Outside that set, a much wider field of search APIs offers free quotas or free tiers, enough to run an agent's searches without paying for the privilege.
|
|
12
29
|
|
|
13
|
-
This repository adds plugins for
|
|
30
|
+
This repository adds plugins for thirteen more of them, taking the pool from five providers to eighteen. AnySearch and Keenable run without a key at all; the other eleven bind only when you supply one, so installing this package never routes a query to a provider that would immediately fail. Which of them are worth your time depends on the free tier each one currently offers, so check a provider's pricing before you rely on it.
|
|
14
31
|
|
|
15
32
|
## Install
|
|
16
33
|
|
|
17
|
-
Three equivalent ways to install; pick one. **npm is recommended**: a version published to the registry is immutable, so a pinned `@
|
|
34
|
+
Three equivalent ways to install; pick one. **npm is recommended**: a version published to the registry is immutable, so a pinned `@3.0.0` always installs the same bytes, whereas a GitHub tag can in principle be moved by a maintainer to point at a different commit. The first two install the same complete package (all thirteen); use the third for one or two files only.
|
|
18
35
|
|
|
19
36
|
### 1. From npm (recommended)
|
|
20
37
|
|
|
21
38
|
```sh
|
|
22
|
-
# add @
|
|
39
|
+
# add @3.0.0 to pin
|
|
23
40
|
opencode plugin add opencode-search-plugin
|
|
24
41
|
```
|
|
25
42
|
|
|
@@ -35,7 +52,7 @@ or declare it in `opencode.json(c)`:
|
|
|
35
52
|
### 2. From GitHub
|
|
36
53
|
|
|
37
54
|
```sh
|
|
38
|
-
# add #
|
|
55
|
+
# add #v3.0.0 to pin
|
|
39
56
|
opencode plugin add github:guotree/opencode-search-plugin
|
|
40
57
|
```
|
|
41
58
|
|
|
@@ -67,7 +84,7 @@ For a location that is not auto-discovered, list it explicitly:
|
|
|
67
84
|
|
|
68
85
|
### Skip the services you have no key for
|
|
69
86
|
|
|
70
|
-
Loading the package registers all
|
|
87
|
+
Loading the package registers all thirteen, but a provider without a key never joins the search pool; see [Keys](#keys). To keep some out of the registry entirely, use either or both of these optional options:
|
|
71
88
|
|
|
72
89
|
```jsonc
|
|
73
90
|
{
|
|
@@ -80,8 +97,8 @@ Loading the package registers all eleven, but a provider without a key never joi
|
|
|
80
97
|
}
|
|
81
98
|
```
|
|
82
99
|
|
|
83
|
-
- `disable` is a deny-list: it never registers the IDs it names
|
|
84
|
-
- `enable` is an allow-list: only the IDs it names are registered, and everything else is skipped
|
|
100
|
+
- `disable` is a deny-list: it never registers the IDs it names (the provider IDs in the table below). AnySearch and Keenable need no key, so `disable` is the only way to keep those two out of the pool.
|
|
101
|
+
- `enable` is an allow-list: only the IDs it names are registered, and everything else is skipped. `"enable": ["osp-brave", "osp-jina"]` runs those two alone.
|
|
85
102
|
|
|
86
103
|
When both are set, **`disable` wins on any overlap**. A per-provider exclusion is never overridden by a bulk allow-list:
|
|
87
104
|
|
|
@@ -96,7 +113,7 @@ When both are set, **`disable` wins on any overlap**. A per-provider exclusion i
|
|
|
96
113
|
}
|
|
97
114
|
```
|
|
98
115
|
|
|
99
|
-
Read as set arithmetic, the registered pool is `enable − disable`. The overlap drops out; a `disable` ID the allow-list never contained was never a candidate to begin with
|
|
116
|
+
Read as set arithmetic, the registered pool is `enable − disable`. The overlap drops out; a `disable` ID the allow-list never contained was never a candidate to begin with, redundant but harmless and unreported. The overlap is the only redundancy that earns a warning, and the log names the IDs in it.
|
|
100
117
|
|
|
101
118
|
```text
|
|
102
119
|
enable = {osp-brave, osp-jina, osp-serper, osp-keenable, osp-linkup}
|
|
@@ -104,7 +121,7 @@ disable = {osp-keenable, osp-linkup, osp-youcom}
|
|
|
104
121
|
pool = osp-brave, osp-jina -- enable minus disable
|
|
105
122
|
```
|
|
106
123
|
|
|
107
|
-
Configuration that cannot take effect is reported in the log rather than silently dropped: an ID this package does not register (a typo) is named, as is an option that is not an array. An `enable` list that ends up naming no known provider leaves the pool empty
|
|
124
|
+
Configuration that cannot take effect is reported in the log rather than silently dropped: an ID this package does not register (a typo) is named, as is an option that is not an array. An `enable` list that ends up naming no known provider leaves the pool empty; the log says so.
|
|
108
125
|
|
|
109
126
|
## Upgrade
|
|
110
127
|
|
|
@@ -117,7 +134,7 @@ opencode plugin update github:guotree/opencode-search-plugin
|
|
|
117
134
|
opencode plugin update
|
|
118
135
|
```
|
|
119
136
|
|
|
120
|
-
The target is the specifier that was configured: an npm install is named by its package name, a GitHub install answers only to its `github:` form
|
|
137
|
+
The target is the specifier that was configured: an npm install is named by its package name, a GitHub install answers only to its `github:` form; naming it by its bare package name fails with `Plugin is not configured`. Run `opencode plugin check` first to see what is outdated. An install without a pinned version follows new releases; a pinned one (`@3.0.0` on npm, `#v3.0.0` on GitHub) stays on its pin until you re-run `plugin add` with a newer pin or edit the specifier in `opencode.json(c)`. A locally copied file is upgraded by copying the new version over the old one.
|
|
121
138
|
|
|
122
139
|
## Uninstall
|
|
123
140
|
|
|
@@ -128,18 +145,20 @@ opencode plugin remove opencode-search-plugin
|
|
|
128
145
|
opencode plugin remove github:guotree/opencode-search-plugin
|
|
129
146
|
```
|
|
130
147
|
|
|
131
|
-
The same rule: pass the specifier you installed with. The command edits the global configuration; if the plugin is instead a `plugins` entry in `opencode.json(c)` or a locally copied file, delete the entry or the copied `.ts` file. Saved `/connect` credentials are untouched
|
|
148
|
+
The same rule: pass the specifier you installed with. The command edits the global configuration; if the plugin is instead a `plugins` entry in `opencode.json(c)` or a locally copied file, delete the entry or the copied `.ts` file. Saved `/connect` credentials are untouched; a reinstall finds them again. To turn off a few providers instead of the whole plugin, use `enable`/`disable` above.
|
|
132
149
|
|
|
133
150
|
## Keys
|
|
134
151
|
|
|
135
152
|
Every provider except AnySearch and Keenable needs an API key. Give it one of two ways:
|
|
136
153
|
|
|
137
|
-
- **`/connect`** inside OpenCode
|
|
138
|
-
- **Environment variable
|
|
154
|
+
- **`/connect`** inside OpenCode: stored per provider, rotated without a restart, survives environment changes.
|
|
155
|
+
- **Environment variable**: read as a fallback, so it works in headless and CI contexts.
|
|
156
|
+
|
|
157
|
+
Environment variables are namespaced `OSP_<PROVIDER>_API_KEY` (say `OSP_BRAVE_API_KEY`), so the key this plugin reads never collides with the same vendor name exported for another tool. A credential stored in `/connect` outranks the environment either way.
|
|
139
158
|
|
|
140
159
|
A provider with no key stays unbound: it is absent from the websearch pool, so `"provider": "random"` can never route a query to a service that would immediately fail.
|
|
141
160
|
|
|
142
|
-
AnySearch and Keenable are the exceptions. Both have an anonymous tier, so they are always registered and only attach their credential header when a key exists. For Keenable the two tiers draw on separate budgets: a key spends the free allowance of 100,000 requests a month, while a keyless call goes to the `/public` endpoint instead and is metered by rate
|
|
161
|
+
AnySearch and Keenable are the exceptions. Both have an anonymous tier, so they are always registered and only attach their credential header when a key exists. For Keenable the two tiers draw on separate budgets: a key spends the free allowance of 100,000 requests a month, while a keyless call goes to the `/public` endpoint instead and is metered by rate: 1,000 requests an hour and 10 a second per IP, on a pool shared with everyone else on your egress address. A key is what makes that shared headroom irrelevant.
|
|
143
162
|
|
|
144
163
|
Once keys are in place, pick how OpenCode chooses among them:
|
|
145
164
|
|
|
@@ -151,33 +170,36 @@ Once keys are in place, pick how OpenCode chooses among them:
|
|
|
151
170
|
|
|
152
171
|
## Providers
|
|
153
172
|
|
|
154
|
-
Every ID below carries an `osp-` prefix
|
|
173
|
+
Every ID below carries an `osp-` prefix; it is this package's namespace. OpenCode's own providers register bare vendor names (`exa`, `tavily`), and a vendor's official plugin would do the same, so the prefix is what keeps those entries from colliding with ours, both in the websearch pool and in the `/connect` table, where credentials are keyed by integration ID. Use the prefixed form verbatim in `websearch.provider` and in `enable`/`disable`.
|
|
155
174
|
|
|
156
|
-
The namespace also shows on the display name: `/connect` lists each of this package's entries with an `(osp)` marker
|
|
175
|
+
The namespace also shows on the display name: `/connect` lists each of this package's entries with an `(osp)` marker (`DeepSeek (osp)` beside a built-in `DeepSeek`), so a key saved here cannot be mistaken for a built-in provider's. The websearch pool keeps the plain provider name, since the pool holds only search providers and the collision does not exist there.
|
|
157
176
|
|
|
158
177
|
| Provider | ID | Key (env / `/connect`) | Anonymous | Notes |
|
|
159
178
|
| --- | --- | --- | --- | --- |
|
|
160
|
-
| AnySearch | `osp-anysearch` | `
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
179
|
+
| AnySearch | `osp-anysearch` | `OSP_ANYSEARCH_API_KEY` | yes | Free tier per IP; intent router, `cn`/`intl` zones |
|
|
180
|
+
| Bocha | `osp-bocha` | `OSP_BOCHA_API_KEY` | no | Bing-compatible results; billed per search, and by the token when a summary is asked for |
|
|
181
|
+
| Brave Search | `osp-brave` | `OSP_BRAVE_SEARCH_API_KEY` | no | Independent index, 20 results max |
|
|
182
|
+
| DeepSeek | `osp-deepseek` | `OSP_DEEPSEEK_API_KEY` | no | In-house index; $0.75 per 1,000 successful requests; 5 QPS cap |
|
|
183
|
+
| Jina | `osp-jina` | `OSP_JINA_API_KEY` | no | Page content is opt-in because it is token-billed |
|
|
184
|
+
| Keenable | `osp-keenable` | `OSP_KEENABLE_API_KEY` | yes | 100,000 requests a month with a key; keyless tier is per IP |
|
|
185
|
+
| Linkup | `osp-linkup` | `OSP_LINKUP_API_KEY` | no | `flash`/`fast`/`standard`/`deep` reasoning depth |
|
|
186
|
+
| Metaso | `osp-metaso` | `OSP_METASO_API_KEY` | no | Billed per search, not per result |
|
|
187
|
+
| Ollama | `osp-ollama` | `OSP_OLLAMA_API_KEY` | no | Free personal tier; each hit carries the full page as markdown, so results are context-heavy, capped at 10, and carry no dates |
|
|
188
|
+
| Querit | `osp-querit` | `OSP_QUERIT_API_KEY` | no | Falls back to sentence matches when a snippet is missing |
|
|
189
|
+
| SerpApi | `osp-serpapi` | `OSP_SERPAPI_API_KEY` | no | Google via SerpApi; also has its own free 1-hour server cache |
|
|
190
|
+
| Serper | `osp-serper` | `OSP_SERPER_API_KEY` | no | Google via Serper; billed per query in credits |
|
|
191
|
+
| You.com | `osp-youcom` | `OSP_YDC_API_KEY` | no | Web and news merged; extraction modes |
|
|
171
192
|
|
|
172
193
|
## Configuration
|
|
173
194
|
|
|
174
|
-
Knobs are environment variables, read once when OpenCode starts
|
|
195
|
+
Knobs are environment variables, read once when OpenCode starts; change one and restart. Defaults match what each API documents, so leaving them unset is the right starting point.
|
|
175
196
|
|
|
176
|
-
### Shared by all
|
|
197
|
+
### Shared by all thirteen
|
|
177
198
|
|
|
178
199
|
| Variable | Default | Meaning |
|
|
179
200
|
| --- | --- | --- |
|
|
180
201
|
| `<PROVIDER>_CACHE_TTL` | `600000` | Milliseconds to reuse a response for the same normalized query. `0` disables caching. |
|
|
202
|
+
| `<PROVIDER>_TIMEOUT` | `25000` | Milliseconds to wait for the provider before failing. `0` removes the deadline but keeps cancellation. |
|
|
181
203
|
|
|
182
204
|
### AnySearch
|
|
183
205
|
|
|
@@ -188,7 +210,15 @@ Knobs are environment variables, read once when OpenCode starts — change one a
|
|
|
188
210
|
| `ANYSEARCH_ZONE` | — | `cn` or `intl` |
|
|
189
211
|
| `ANYSEARCH_LANGUAGE` | — | Preferred result language, e.g. `zh-CN` |
|
|
190
212
|
| `ANYSEARCH_FORMAT` | `markdown` | `markdown` or `json` for the content field |
|
|
191
|
-
| `
|
|
213
|
+
| `OSP_ANYSEARCH_API_KEY` | — | Optional; anonymous access works without it |
|
|
214
|
+
|
|
215
|
+
### Bocha
|
|
216
|
+
|
|
217
|
+
| Variable | Default | Meaning |
|
|
218
|
+
| --- | --- | --- |
|
|
219
|
+
| `BOCHA_COUNT` | `10` | Results per search, clamped to 1–50 (Bocha's maximum) |
|
|
220
|
+
| `BOCHA_FRESHNESS` | — | `noLimit`, `oneDay`, `oneWeek`, `oneMonth`, `oneYear`, a `YYYY-MM-DD` date, or a `YYYY-MM-DD..YYYY-MM-DD` range. Unset omits the parameter, which the docs recommend: Bocha rewrites time ranges itself, and a pinned range often finds nothing within it |
|
|
221
|
+
| `BOCHA_SUMMARY` | off | `true` swaps each snippet for a generated summary — more context to read, at the higher per-call price |
|
|
192
222
|
|
|
193
223
|
### Brave Search
|
|
194
224
|
|
|
@@ -210,9 +240,10 @@ Knobs are environment variables, read once when OpenCode starts — change one a
|
|
|
210
240
|
| Variable | Default | Meaning |
|
|
211
241
|
| --- | --- | --- |
|
|
212
242
|
| `JINA_NUM` | `0` | Results per search, 1–100. `0` omits the parameter, which keeps Jina's faster and wider result types |
|
|
213
|
-
| `JINA_INCLUDE_CONTENT` | off | `true` returns each hit's full page markdown
|
|
243
|
+
| `JINA_INCLUDE_CONTENT` | off | `true` returns each hit's full page markdown, far more context and billed tokens |
|
|
214
244
|
| `JINA_GL` / `JINA_HL` | — | Country / language code |
|
|
215
245
|
| `JINA_LOCATION` | — | City-level origin to simulate, e.g. `Berlin` |
|
|
246
|
+
| `JINA_TIMEOUT` | `25000`, or `60000` with `JINA_INCLUDE_CONTENT` | Only provider whose deadline varies by mode: fetching whole pages is legitimately slower than a snippet search |
|
|
216
247
|
|
|
217
248
|
### Keenable
|
|
218
249
|
|
|
@@ -221,7 +252,7 @@ Knobs are environment variables, read once when OpenCode starts — change one a
|
|
|
221
252
|
| `KEENABLE_MAX_RESULTS` | `10` | Results per search, clamped to 1–50 |
|
|
222
253
|
| `KEENABLE_MODE` | — | `pro` (deeper retrieval) or `realtime` (fastest); unset leaves Keenable's own default |
|
|
223
254
|
| `KEENABLE_TITLE` | `opencode-search-plugin` | App name sent as `X-Keenable-Title`, which the keyless endpoint requires to attribute the call |
|
|
224
|
-
| `
|
|
255
|
+
| `OSP_KEENABLE_API_KEY` | — | Optional; keyless access works without it |
|
|
225
256
|
|
|
226
257
|
### Linkup
|
|
227
258
|
|
|
@@ -237,6 +268,12 @@ Knobs are environment variables, read once when OpenCode starts — change one a
|
|
|
237
268
|
| `METASO_SEARCH_SIZE` | `20` | Results per search, clamped to 1–100 |
|
|
238
269
|
| `METASO_INCLUDE_RAW_CONTENT` | off | `true` returns every hit's full source text at the same search cost, but far more context tokens |
|
|
239
270
|
|
|
271
|
+
### Ollama
|
|
272
|
+
|
|
273
|
+
| Variable | Default | Meaning |
|
|
274
|
+
| --- | --- | --- |
|
|
275
|
+
| `OLLAMA_MAX_RESULTS` | `5` | Results per search, clamped to 1–10 (the API's maximum). `content` is a whole page of markdown rather than a snippet, so even the default 5 results run to thousands of characters of context |
|
|
276
|
+
|
|
240
277
|
### Querit
|
|
241
278
|
|
|
242
279
|
| Variable | Default | Meaning |
|
|
@@ -272,12 +309,12 @@ There is deliberately no result-count knob: Google withdrew the `num` parameter,
|
|
|
272
309
|
## How a query flows
|
|
273
310
|
|
|
274
311
|
1. OpenCode asks the selected provider for the query.
|
|
275
|
-
2. The normalized query (trimmed, whitespace collapsed) is checked against an in-memory cache. A hit returns immediately
|
|
312
|
+
2. The normalized query (trimmed, whitespace collapsed) is checked against an in-memory cache. A hit returns immediately; repeat queries are free and instant.
|
|
276
313
|
3. On a miss the key is resolved again, so a key rotated in `/connect` applies to the very next search.
|
|
277
|
-
4. The request goes out
|
|
314
|
+
4. The request goes out under a deadline (`<PROVIDER>_TIMEOUT`, default 25s). OpenCode applies no timeout of its own around a search, so without this a provider that accepted the connection and then went silent would hang the whole turn. Failures surface the HTTP status and response body, or name the timeout when that is what happened.
|
|
278
315
|
5. The response is schema-decoded, mapped to `{ url, title, content, time }`, and cached.
|
|
279
316
|
|
|
280
|
-
Dates are parsed from relative phrases (`3 hours ago`, `3小时前`), absolute formats (`Sep 30, 2026`, `31 Mar 2026`), and ISO 8601. Strings whose lenient parsing would invent a wrong year
|
|
317
|
+
Dates are parsed from relative phrases (`3 hours ago`, `3小时前`), absolute formats (`Sep 30, 2026`, `31 Mar 2026`), and ISO 8601. Strings whose lenient parsing would invent a wrong year (`Nov 1`, which `Date.parse` reads as 2001) are dropped rather than reported as a bogus timestamp.
|
|
281
318
|
|
|
282
319
|
## Troubleshooting
|
|
283
320
|
|
|
@@ -293,11 +330,15 @@ opencode api get "/api/websearch/provider"
|
|
|
293
330
|
|
|
294
331
|
Also worth checking:
|
|
295
332
|
|
|
296
|
-
- **A provider is missing from the pool
|
|
297
|
-
- **A search fails with `Keenable rate limited the request (HTTP 429)
|
|
298
|
-
- **A search fails with `
|
|
299
|
-
- **
|
|
300
|
-
- **
|
|
333
|
+
- **A provider is missing from the pool**: it has no key, or `disable` (or a narrow `enable`) filtered it out. Run `/connect` or export its variable, then restart; credential changes made in `/connect` rebind without one. A name that should be there but is not is often a typo: the log flags unknown IDs and any `enable`/`disable` overlap.
|
|
334
|
+
- **A search fails with `Keenable rate limited the request (HTTP 429)`**: the keyless pool is capped at 1,000 requests an hour and 10 a second per IP, shared with the rest of your egress address. Set `OSP_KEENABLE_API_KEY` to draw on your own allowance instead.
|
|
335
|
+
- **A 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 it instead holds a bearer token for an Ollama-compatible proxy (a documented OpenCode config shape), store the real key in `/connect` for **Ollama Search (osp)** — a /connect credential outranks the environment.
|
|
336
|
+
- **A search fails with `Bocha balance is exhausted (HTTP 403)`**: the account balance is spent; top up at [open.bocha.cn](https://open.bocha.cn). The message carries Bocha's own `message` text and a `log_id` to quote to their support.
|
|
337
|
+
- **A search fails with `Bocha rate limited the request (HTTP 429)`**: the per-account request cap scales with the total recharge amount, so a small balance means a small ceiling. The report includes a `retry after Ns` hint when Bocha sent one.
|
|
338
|
+
- **A search fails with `Connect X in /connect, or set OSP_X_API_KEY`**: the key disappeared between binding and searching. Reconnect or re-export.
|
|
339
|
+
- **A search fails with `X search timed out after 25000ms`**: the provider accepted the request and never answered within the deadline. Retrying usually works; if a provider is consistently slow for you, raise its `<PROVIDER>_TIMEOUT`. This is a deliberate bound: without one a stalled provider hangs the whole turn, because OpenCode applies no timeout of its own around a search.
|
|
340
|
+
- **Everything is slow or burning credits**: leave `*_CACHE_TTL` at its default. Repeated queries within the window are served from memory.
|
|
341
|
+
- **Nothing happens after editing a plugin file**: OpenCode watches plugins and reloads them; if it does not, `opencode service restart`.
|
|
301
342
|
|
|
302
343
|
Logs live at `~/.local/share/opencode/log/opencode.log`; filter for `loading plugin` to see every plugin OpenCode resolved, with its entrypoint. Warnings this plugin emits about `enable`/`disable` land in the same file, carrying a `[opencode-search-plugin]` prefix.
|
|
303
344
|
|
|
@@ -308,10 +349,10 @@ npm install
|
|
|
308
349
|
npm run check # typecheck + tests
|
|
309
350
|
```
|
|
310
351
|
|
|
311
|
-
- `npm run typecheck
|
|
312
|
-
- `npm test
|
|
352
|
+
- `npm run typecheck`: strict TypeScript across every plugin and test.
|
|
353
|
+
- `npm test`: the shared provider contract, per-provider behaviour, date parsing, aggregate registration, and repository-structure guards.
|
|
313
354
|
|
|
314
|
-
`@opencode/plugin` and `effect` are declared as **devDependencies**, not `dependencies`. OpenCode provides both to plugins at runtime
|
|
355
|
+
`@opencode/plugin` and `effect` are declared as **devDependencies**, not `dependencies`. OpenCode provides both to plugins at runtime; that is also what lets a single `.ts` file run from `~/.config/opencode/plugins/` with no `node_modules` at all. Declaring them as runtime dependencies would make `opencode plugin add` install a 127 MB tree (`effect` alone is 47 MB, and `@opencode/plugin` pulls in `@aws-sdk`, `google-auth-library` and `@opentelemetry`) to reach 100 KB of plugin source; as devDependencies they are present for typechecking and tests, and absent from a consumer's install.
|
|
315
356
|
|
|
316
357
|
The structure tests exist because each file must remain independently copyable: they fail if a plugin starts importing a sibling module, if the shared blocks stop being byte-identical copies, or if a provider's IDs drift apart. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
317
358
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,24 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
# opencode-search-plugin
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/opencode-search-plugin)
|
|
6
|
+
[](https://www.npmjs.com/package/opencode-search-plugin)
|
|
7
|
+
[](LICENSE)
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
**不用 API key,装上就能搜。**
|
|
10
|
+
|
|
11
|
+
一条命令,给 OpenCode v2 加上十三个搜索 provider,搜索池从五家扩到十八家。AnySearch 和 Keenable 不用 key 直接能用;其余十一家等你配好 key 才会启用。
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
opencode plugin add opencode-search-plugin
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
装完就能问 OpenCode 需要联网的问题。
|
|
18
|
+
|
|
19
|
+
<!-- TODO: 录一段 10 秒 GIF 后替换本注释:
|
|
20
|
+
提问 → websearch 工具触发 → 结果返回 -->
|
|
21
|
+
|
|
22
|
+
十三个 provider 装一次全有,想用哪家用哪家:
|
|
23
|
+
|
|
24
|
+
[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/) · [Querit](https://querit.ai/) · [SerpApi](https://serpapi.com/) · [Serper](https://serper.dev/) · [You.com](https://you.com/)
|
|
8
25
|
|
|
9
26
|
## 为什么做这个插件
|
|
10
27
|
|
|
11
28
|
OpenCode v2 内置了五个网页搜索 provider:`exa`、`firecrawl`、`parallel`、`tavily`、`tinyfish`。在这个范围之外,还有大量搜索 API 提供免费额度或免费层级,足以支撑 agent 的日常检索而不必为此付费。
|
|
12
29
|
|
|
13
|
-
|
|
30
|
+
本仓库为其中十三家补上了插件,把可用池从五个 provider 扩到十八个。AnySearch 和 Keenable 无需任何 key 即可使用;其余十一家只在你提供 key 之后才会绑定,因此装上这个包也不会把查询路由到一个注定失败的服务上。至于哪几家值得用,取决于它们各自当前的免费额度,长期依赖某家之前建议先确认它的定价。
|
|
14
31
|
|
|
15
32
|
## 安装
|
|
16
33
|
|
|
17
|
-
三种方式等价,任选其一。**推荐 npm**:已发布到 registry 的版本不可变,固定 `@
|
|
34
|
+
三种方式等价,任选其一。**推荐 npm**:已发布到 registry 的版本不可变,固定 `@3.0.0` 后每次安装都拿到同一份字节;GitHub 的 tag 在理论上可以被维护者移动、指向别的 commit。前两种装的是同一个完整包(十三个全有),只想用一两个就用第三种。
|
|
18
35
|
|
|
19
36
|
### 1. 从 npm 安装(推荐)
|
|
20
37
|
|
|
21
38
|
```sh
|
|
22
|
-
# 追加 @
|
|
39
|
+
# 追加 @3.0.0 可固定到该版本
|
|
23
40
|
opencode plugin add opencode-search-plugin
|
|
24
41
|
```
|
|
25
42
|
|
|
@@ -35,7 +52,7 @@ opencode plugin add opencode-search-plugin
|
|
|
35
52
|
### 2. 从 GitHub 安装
|
|
36
53
|
|
|
37
54
|
```sh
|
|
38
|
-
# 追加 #
|
|
55
|
+
# 追加 #v3.0.0 可固定到该 tag
|
|
39
56
|
opencode plugin add github:guotree/opencode-search-plugin
|
|
40
57
|
```
|
|
41
58
|
|
|
@@ -67,7 +84,7 @@ opencode plugin add github:guotree/opencode-search-plugin
|
|
|
67
84
|
|
|
68
85
|
### 跳过你没有 key 的服务
|
|
69
86
|
|
|
70
|
-
|
|
87
|
+
装包会注册全部十三家,但没有 key 的 provider 永远不会进入搜索池(见下文[密钥](#密钥))。想让它们连注册表都不出现,可以单独或同时使用下面两个可选选项:
|
|
71
88
|
|
|
72
89
|
```jsonc
|
|
73
90
|
{
|
|
@@ -81,7 +98,7 @@ opencode plugin add github:guotree/opencode-search-plugin
|
|
|
81
98
|
```
|
|
82
99
|
|
|
83
100
|
- `disable` 是黑名单:它列出的 provider ID(见下文[支持的服务](#支持的服务)的表格)永远不会注册。AnySearch 和 Keenable 不需要 key,想把这两家也排除在搜索池之外,只能用 `disable`。
|
|
84
|
-
- `enable` 是白名单:只注册列出的 ID
|
|
101
|
+
- `enable` 是白名单:只注册列出的 ID,其余一律跳过;`"enable": ["osp-brave", "osp-jina"]` 只跑这两家。
|
|
85
102
|
|
|
86
103
|
两者同时配置时,**交集由 `disable` 胜出**:逐家的排除绝不会被批量白名单覆盖。
|
|
87
104
|
|
|
@@ -96,7 +113,7 @@ opencode plugin add github:guotree/opencode-search-plugin
|
|
|
96
113
|
}
|
|
97
114
|
```
|
|
98
115
|
|
|
99
|
-
用集合运算来读:实际注册的池 = `enable − disable`。交集被剔除;`disable` 里不在 `enable` 中的 ID
|
|
116
|
+
用集合运算来读:实际注册的池 = `enable − disable`。交集被剔除;`disable` 里不在 `enable` 中的 ID 从一开始就不在候选内,属于无害的冗余,不报警告。唯一会触发警告的冗余是交集,日志会点出其中的 ID。
|
|
100
117
|
|
|
101
118
|
```text
|
|
102
119
|
enable = {osp-brave, osp-jina, osp-serper, osp-keenable, osp-linkup}
|
|
@@ -104,7 +121,7 @@ disable = {osp-keenable, osp-linkup, osp-youcom}
|
|
|
104
121
|
pool = osp-brave, osp-jina -- enable 减 disable
|
|
105
122
|
```
|
|
106
123
|
|
|
107
|
-
无法生效的配置会在日志里提示,而不是被悄悄丢弃:写错的 ID(本包没注册的名字)会被点名,选项不是数组也会被点名。`enable` 如果最终一个已知 provider
|
|
124
|
+
无法生效的配置会在日志里提示,而不是被悄悄丢弃:写错的 ID(本包没注册的名字)会被点名,选项不是数组也会被点名。`enable` 如果最终一个已知 provider 都没匹配上,搜索池会是空的,日志里会写明这一点。
|
|
108
125
|
|
|
109
126
|
## 升级
|
|
110
127
|
|
|
@@ -117,7 +134,7 @@ opencode plugin update github:guotree/opencode-search-plugin
|
|
|
117
134
|
opencode plugin update
|
|
118
135
|
```
|
|
119
136
|
|
|
120
|
-
升级目标用的是配置时的说明符:npm 安装用包名,GitHub 安装只认自己的 `github:`
|
|
137
|
+
升级目标用的是配置时的说明符:npm 安装用包名,GitHub 安装只认自己的 `github:` 形式,用裸包名会报 `Plugin is not configured`。先跑 `opencode plugin check` 查看有哪些过期。未固定版本的安装会跟上新版本;固定过的会停在原地(npm 形式是 `@3.0.0`,GitHub 形式是 `#v3.0.0`),直到你用新 pin 重新执行 `plugin add`,或直接修改 `opencode.json(c)` 里的说明符。本地复制的文件用新版本覆盖旧文件即可。
|
|
121
138
|
|
|
122
139
|
## 卸载
|
|
123
140
|
|
|
@@ -128,18 +145,20 @@ opencode plugin remove opencode-search-plugin
|
|
|
128
145
|
opencode plugin remove github:guotree/opencode-search-plugin
|
|
129
146
|
```
|
|
130
147
|
|
|
131
|
-
同上:传入安装时使用的说明符。该命令修改的是全局配置;若插件是写在 `opencode.json(c)` 的 `plugins` 条目里或本地复制的文件,删掉对应条目、或删掉复制的 `.ts` 文件即可。`/connect`
|
|
148
|
+
同上:传入安装时使用的说明符。该命令修改的是全局配置;若插件是写在 `opencode.json(c)` 的 `plugins` 条目里或本地复制的文件,删掉对应条目、或删掉复制的 `.ts` 文件即可。`/connect` 里保存的密钥不受影响,重新装回就能继续用。只想关掉其中几家而不是整个插件,用上文的 `enable`/`disable`。
|
|
132
149
|
|
|
133
150
|
## 密钥
|
|
134
151
|
|
|
135
152
|
除 AnySearch 和 Keenable 外,每家服务都需要 API key。两种给法二选一:
|
|
136
153
|
|
|
137
|
-
- **OpenCode 内的 `/connect
|
|
138
|
-
-
|
|
154
|
+
- **OpenCode 内的 `/connect`**:按 provider 存储,轮换后无需重启,也不受环境变量变动影响。
|
|
155
|
+
- **环境变量**:作为兜底读取,无界面和 CI 场景下也能工作。
|
|
156
|
+
|
|
157
|
+
环境变量带命名空间前缀,形如 `OSP_<PROVIDER>_API_KEY`(例如 `OSP_BRAVE_API_KEY`),因此本插件读的 key 不会和其他工具导出的同厂商变量名撞车。存在 `/connect` 里的凭据无论如何都优先于环境变量。
|
|
139
158
|
|
|
140
159
|
没有 key 的 provider 会保持未绑定状态:它不在 websearch 池中,所以 `"provider": "random"` 绝不会把查询路由到一个注定失败的服务上。
|
|
141
160
|
|
|
142
|
-
AnySearch 和 Keenable 是例外。两家都有匿名层级,因此始终注册,只有存在 key 时才附带各自的凭据头。Keenable 的两层额度互不通用:有 key 时消耗每月 100,000 次的免费额度,无 key 的请求则改走 `/public`
|
|
161
|
+
AnySearch 和 Keenable 是例外。两家都有匿名层级,因此始终注册,只有存在 key 时才附带各自的凭据头。Keenable 的两层额度互不通用:有 key 时消耗每月 100,000 次的免费额度,无 key 的请求则改走 `/public` 端点,按速率限制:每个 IP 每小时 1,000 次、每秒 10 次,且与同一出口地址上的所有人共用。想摆脱这个共享池的不确定性,就得配一把 key。
|
|
143
162
|
|
|
144
163
|
密钥就位后,再决定 OpenCode 如何在它们之间做选择:
|
|
145
164
|
|
|
@@ -151,33 +170,36 @@ AnySearch 和 Keenable 是例外。两家都有匿名层级,因此始终注册
|
|
|
151
170
|
|
|
152
171
|
## 支持的服务
|
|
153
172
|
|
|
154
|
-
下表每个 ID 都带 `osp-`
|
|
173
|
+
下表每个 ID 都带 `osp-` 前缀,这是本包的命名空间。OpenCode 内置 provider 用的是裸厂商名(`exa`、`tavily`),厂商自己的插件也会注册裸名(Keenable 就已经有一个),所以前缀是避免撞名的唯一保障:既避开搜索池里的重复,也避开 `/connect` 里按 integration ID 存凭据的那张表。在 `websearch.provider` 和 `enable`/`disable` 里请原样使用带前缀的 ID。
|
|
155
174
|
|
|
156
|
-
命名空间同样体现在显示名上:`/connect` 里本包的每个条目都带 `(osp)`
|
|
175
|
+
命名空间同样体现在显示名上:`/connect` 里本包的每个条目都带 `(osp)` 标记(内置的 `DeepSeek` 旁边是 `DeepSeek (osp)`),存在这里的 key 不会和内置 provider 的混淆。搜索池里仍用纯品牌名,因为池中只有搜索 provider,不存在撞名问题。
|
|
157
176
|
|
|
158
177
|
| 服务 | ID | 密钥(环境变量 / `/connect`) | 匿名可用 | 说明 |
|
|
159
178
|
| --- | --- | --- | --- | --- |
|
|
160
|
-
| AnySearch | `osp-anysearch` | `
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
179
|
+
| AnySearch | `osp-anysearch` | `OSP_ANYSEARCH_API_KEY` | 是 | 按 IP 的免费额度;意图路由,`cn` / `intl` 分区 |
|
|
180
|
+
| Bocha | `osp-bocha` | `OSP_BOCHA_API_KEY` | 否 | 结果格式兼容 Bing;按搜索次数计费,要求摘要时另按 token 计费 |
|
|
181
|
+
| Brave Search | `osp-brave` | `OSP_BRAVE_SEARCH_API_KEY` | 否 | 独立索引,最多 20 条结果 |
|
|
182
|
+
| DeepSeek | `osp-deepseek` | `OSP_DEEPSEEK_API_KEY` | 否 | 自研搜索引擎;每千次成功请求 5 元;限速 5 QPS |
|
|
183
|
+
| Jina | `osp-jina` | `OSP_JINA_API_KEY` | 否 | 页面正文按 token 计费,因此默认不取 |
|
|
184
|
+
| Keenable | `osp-keenable` | `OSP_KEENABLE_API_KEY` | 是 | 有 key 每月 100,000 次免费请求;无 key 层级按 IP 限速 |
|
|
185
|
+
| Linkup | `osp-linkup` | `OSP_LINKUP_API_KEY` | 否 | `flash` / `fast` / `standard` / `deep` 四档推理深度 |
|
|
186
|
+
| Metaso | `osp-metaso` | `OSP_METASO_API_KEY` | 否 | 按次搜索计费,不按结果条数 |
|
|
187
|
+
| Ollama | `osp-ollama` | `OSP_OLLAMA_API_KEY` | 否 | 个人档免费;每条 content 是整页 markdown,上下文偏重;上限 10 条,且不带日期 |
|
|
188
|
+
| Querit | `osp-querit` | `OSP_QUERIT_API_KEY` | 否 | 摘要缺失时回退到句子级匹配 |
|
|
189
|
+
| SerpApi | `osp-serpapi` | `OSP_SERPAPI_API_KEY` | 否 | 经 SerpApi 调用 Google;另有其自带的免费 1 小时服务端缓存 |
|
|
190
|
+
| Serper | `osp-serper` | `OSP_SERPER_API_KEY` | 否 | 经 Serper 调用 Google;按查询消耗积分计费 |
|
|
191
|
+
| You.com | `osp-youcom` | `OSP_YDC_API_KEY` | 否 | 网页与新闻结果合并;多种正文抽取模式 |
|
|
171
192
|
|
|
172
193
|
## 配置
|
|
173
194
|
|
|
174
|
-
所有配置项都是环境变量,OpenCode
|
|
195
|
+
所有配置项都是环境变量,OpenCode 启动时读取一次,改了要重启。默认值取自各家 API 文档,所以一开始什么都不设就是合理的起点。
|
|
175
196
|
|
|
176
|
-
###
|
|
197
|
+
### 十三家通用
|
|
177
198
|
|
|
178
199
|
| 变量 | 默认值 | 含义 |
|
|
179
200
|
| --- | --- | --- |
|
|
180
201
|
| `<PROVIDER>_CACHE_TTL` | `600000` | 同一条归一化查询可复用响应的毫秒数。`0` 关闭缓存。 |
|
|
202
|
+
| `<PROVIDER>_TIMEOUT` | `25000` | 等待 provider 返回的毫秒数,超时即失败。`0` 取消该期限,但保留取消能力。 |
|
|
181
203
|
|
|
182
204
|
### AnySearch
|
|
183
205
|
|
|
@@ -188,7 +210,15 @@ AnySearch 和 Keenable 是例外。两家都有匿名层级,因此始终注册
|
|
|
188
210
|
| `ANYSEARCH_ZONE` | — | `cn` 或 `intl` |
|
|
189
211
|
| `ANYSEARCH_LANGUAGE` | — | 首选结果语言,例如 `zh-CN` |
|
|
190
212
|
| `ANYSEARCH_FORMAT` | `markdown` | content 字段用 `markdown` 还是 `json` |
|
|
191
|
-
| `
|
|
213
|
+
| `OSP_ANYSEARCH_API_KEY` | — | 可选;不设也能匿名使用 |
|
|
214
|
+
|
|
215
|
+
### Bocha
|
|
216
|
+
|
|
217
|
+
| 变量 | 默认值 | 含义 |
|
|
218
|
+
| --- | --- | --- |
|
|
219
|
+
| `BOCHA_COUNT` | `10` | 每次搜索的结果数,限制在 1–50(博查的上限) |
|
|
220
|
+
| `BOCHA_FRESHNESS` | — | `noLimit`、`oneDay`、`oneWeek`、`oneMonth`、`oneYear`、`YYYY-MM-DD` 单日,或 `YYYY-MM-DD..YYYY-MM-DD` 区间。不设即不传该参数,这也是文档的建议:博查会自行改写时间范围,而指定区间往往在该区间内找不到结果 |
|
|
221
|
+
| `BOCHA_SUMMARY` | 关 | `true` 用生成的摘要替换每条 snippet——上下文更多,单次调用的价格也更高 |
|
|
192
222
|
|
|
193
223
|
### Brave Search
|
|
194
224
|
|
|
@@ -210,9 +240,10 @@ AnySearch 和 Keenable 是例外。两家都有匿名层级,因此始终注册
|
|
|
210
240
|
| 变量 | 默认值 | 含义 |
|
|
211
241
|
| --- | --- | --- |
|
|
212
242
|
| `JINA_NUM` | `0` | 每次搜索的结果数,1–100。`0` 表示不传该参数,从而保留 Jina 更快、类型更全的结果 |
|
|
213
|
-
| `JINA_INCLUDE_CONTENT` | 关 | `true` 返回每条命中的整页 markdown
|
|
243
|
+
| `JINA_INCLUDE_CONTENT` | 关 | `true` 返回每条命中的整页 markdown,上下文和计费 token 都会大幅增加 |
|
|
214
244
|
| `JINA_GL` / `JINA_HL` | — | 国家 / 语言代码 |
|
|
215
245
|
| `JINA_LOCATION` | — | 模拟的城市级来源,例如 `Berlin` |
|
|
246
|
+
| `JINA_TIMEOUT` | `25000`;开启 `JINA_INCLUDE_CONTENT` 时为 `60000` | 唯一一个默认值随模式变化的 provider:抓取整页本来就比取摘要慢得多 |
|
|
216
247
|
|
|
217
248
|
### Keenable
|
|
218
249
|
|
|
@@ -221,7 +252,7 @@ AnySearch 和 Keenable 是例外。两家都有匿名层级,因此始终注册
|
|
|
221
252
|
| `KEENABLE_MAX_RESULTS` | `10` | 每次搜索的结果数,限制在 1–50 |
|
|
222
253
|
| `KEENABLE_MODE` | — | `pro`(更深的检索)或 `realtime`(最快);不设则沿用 Keenable 自己的默认值 |
|
|
223
254
|
| `KEENABLE_TITLE` | `opencode-search-plugin` | 作为 `X-Keenable-Title` 发出的应用名,无 key 端点靠它归属调用方 |
|
|
224
|
-
| `
|
|
255
|
+
| `OSP_KEENABLE_API_KEY` | — | 可选;不设也能匿名使用 |
|
|
225
256
|
|
|
226
257
|
### Linkup
|
|
227
258
|
|
|
@@ -237,6 +268,12 @@ AnySearch 和 Keenable 是例外。两家都有匿名层级,因此始终注册
|
|
|
237
268
|
| `METASO_SEARCH_SIZE` | `20` | 每次搜索的结果数,限制在 1–100 |
|
|
238
269
|
| `METASO_INCLUDE_RAW_CONTENT` | 关 | `true` 返回每条命中的完整原文,搜索成本不变,但上下文 token 大幅增加 |
|
|
239
270
|
|
|
271
|
+
### Ollama
|
|
272
|
+
|
|
273
|
+
| 变量 | 默认值 | 含义 |
|
|
274
|
+
| --- | --- | --- |
|
|
275
|
+
| `OLLAMA_MAX_RESULTS` | `5` | 每次搜索的结果数,限制在 1–10(API 的上限)。`content` 是整页 markdown 而非 snippet,即使默认 5 条也是数千字符的上下文 |
|
|
276
|
+
|
|
240
277
|
### Querit
|
|
241
278
|
|
|
242
279
|
| 变量 | 默认值 | 含义 |
|
|
@@ -272,12 +309,12 @@ AnySearch 和 Keenable 是例外。两家都有匿名层级,因此始终注册
|
|
|
272
309
|
## 一次查询的完整流程
|
|
273
310
|
|
|
274
311
|
1. OpenCode 向选定的 provider 发起查询。
|
|
275
|
-
2.
|
|
312
|
+
2. 归一化后的查询(去首尾、压缩空白)先查内存缓存;命中则直接返回,重复查询免费且即时。
|
|
276
313
|
3. 未命中时重新解析密钥,因此在 `/connect` 里轮换的 key 立即对下一次搜索生效。
|
|
277
|
-
4.
|
|
314
|
+
4. 发出请求,且带期限(`<PROVIDER>_TIMEOUT`,默认 25 秒)。OpenCode 自身并不给搜索加任何超时,所以没有这道期限的话,一个接受了连接却不再应答的 provider 会挂住整轮对话。失败时会带上 HTTP 状态码和响应体;如果是超时,报错会直接点明是超时。
|
|
278
315
|
5. 响应经 schema 解码,映射为 `{ url, title, content, time }`,然后写入缓存。
|
|
279
316
|
|
|
280
|
-
日期解析支持相对表述(`3 hours ago`、`3小时前`)、绝对格式(`Sep 30, 2026`、`31 Mar 2026`)和 ISO 8601
|
|
317
|
+
日期解析支持相对表述(`3 hours ago`、`3小时前`)、绝对格式(`Sep 30, 2026`、`31 Mar 2026`)和 ISO 8601。凡是宽松解析会凭空造出错误年份的字符串(比如 `Nov 1`,`Date.parse` 会读成 2001 年)一律丢弃,而不是返回一个错误的时间戳。
|
|
281
318
|
|
|
282
319
|
## 排错
|
|
283
320
|
|
|
@@ -293,11 +330,15 @@ opencode api get "/api/websearch/provider"
|
|
|
293
330
|
|
|
294
331
|
另外几个值得检查的地方:
|
|
295
332
|
|
|
296
|
-
-
|
|
297
|
-
-
|
|
298
|
-
-
|
|
299
|
-
-
|
|
300
|
-
-
|
|
333
|
+
- 某家 provider 不在池里:它没有 key,或者被 `disable`(或收窄后的 `enable`)过滤掉了。执行 `/connect` 或导出对应环境变量,然后重启;在 `/connect` 里做的凭据变更会自动重绑,不需要重启。本该在却不在的名字多半是拼写错误:日志会点出无法识别的 ID 和 `enable`/`disable` 的交集。
|
|
334
|
+
- 搜索报错 `Keenable rate limited the request (HTTP 429)`:无 key 层级每个 IP 每小时 1,000 次、每秒 10 次,且与同一出口地址上的其他人共用。改用 `OSP_KEENABLE_API_KEY` 就会记到你自己的额度上。
|
|
335
|
+
- 搜索报错 `Ollama API error 401`:`OSP_OLLAMA_API_KEY` 必须是 [ollama.com/settings/keys](https://ollama.com/settings/keys) 的账号 key。如果它存的是某个 Ollama 兼容代理的 token(OpenCode 官方文档里就有这种配置形状),到 `/connect` 给 **Ollama Search (osp)** 存一把真 key——/connect 凭据优先于环境变量。
|
|
336
|
+
- 搜索报错 `Bocha balance is exhausted (HTTP 403)`:账户余额用尽;到 [open.bocha.cn](https://open.bocha.cn) 充值。报错会带上博查自己的 `message` 文本和一个 `log_id`,联系他们客服时请附上。
|
|
337
|
+
- 搜索报错 `Bocha rate limited the request (HTTP 429)`:单账户的请求上限与总充值金额挂钩,余额少则天花板低。博查返回了 `Retry-After` 时,报错里会带上 `retry after Ns` 提示。
|
|
338
|
+
- 搜索报错 `Connect X in /connect, or set OSP_X_API_KEY`:key 在绑定和搜索之间消失了。重新连接或重新导出。
|
|
339
|
+
- 搜索报错 `X search timed out after 25000ms`:provider 接受了请求,但在期限内没有返回。重试通常就好;如果某家 provider 在你这边一贯偏慢,调大它的 `<PROVIDER>_TIMEOUT`。这道期限是刻意加的:没有它,一个卡住的 provider 会挂住整轮对话,因为 OpenCode 自身并不给搜索设置任何超时。
|
|
340
|
+
- 一切都很慢,或者积分烧得飞快:把 `*_CACHE_TTL` 保持在默认值。时间窗内的重复查询由内存直接返回。
|
|
341
|
+
- 改了插件文件却没反应:OpenCode 会监视插件并自动重载;如果没有,执行 `opencode service restart`。
|
|
301
342
|
|
|
302
343
|
日志在 `~/.local/share/opencode/log/opencode.log`;过滤 `loading plugin` 可以看到 OpenCode 解析到的每个插件及其入口文件。本插件关于 `enable`/`disable` 的警告也落在同一个文件里,带 `[opencode-search-plugin]` 前缀。
|
|
303
344
|
|
|
@@ -308,10 +349,10 @@ npm install
|
|
|
308
349
|
npm run check # 类型检查 + 测试
|
|
309
350
|
```
|
|
310
351
|
|
|
311
|
-
- `npm run typecheck
|
|
312
|
-
- `npm test
|
|
352
|
+
- `npm run typecheck`:对所有插件和测试做严格 TypeScript 检查。
|
|
353
|
+
- `npm test`:覆盖通用 provider 契约、各家行为、日期解析、聚合注册,以及仓库结构守卫。
|
|
313
354
|
|
|
314
|
-
`@opencode/plugin` 和 `effect` 声明为 **devDependencies**,而不是 `dependencies`。这两个模块由 OpenCode
|
|
355
|
+
`@opencode/plugin` 和 `effect` 声明为 **devDependencies**,而不是 `dependencies`。这两个模块由 OpenCode 在运行时提供,这也是单个 `.ts` 文件可以放进 `~/.config/opencode/plugins/` 且完全不需要 `node_modules` 的原因。若写成运行时依赖,`opencode plugin add` 会为了 100 KB 的插件源码安装 127 MB 的依赖树(仅 `effect` 就 47 MB,`@opencode/plugin` 还会拖进 `@aws-sdk`、`google-auth-library` 和 `@opentelemetry`);放进 devDependencies 后,类型检查和测试照常可用,而使用者安装时拿不到这份依赖树。
|
|
315
356
|
|
|
316
357
|
存在结构测试,是因为每个文件必须保持可独立复制:一旦某个插件开始 import 同级模块、共享代码块不再是逐字相同的副本、或者某家的 ID 出现偏移,测试就会失败。详见 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
317
358
|
|