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/plugins/querit-search.ts
CHANGED
|
@@ -122,8 +122,12 @@ type SearchResult = ReturnType<typeof toResult>
|
|
|
122
122
|
//
|
|
123
123
|
// QUERIT_CACHE_TTL ms to reuse a response for the same query (0 disables)
|
|
124
124
|
// QUERIT_COUNT results per search; Querit's own limit applies
|
|
125
|
-
//
|
|
125
|
+
// QUERIT_TIMEOUT ms to wait before failing (0 removes the deadline)
|
|
126
|
+
// OSP_QUERIT_API_KEY required; from https://querit.ai
|
|
126
127
|
const CACHE_TTL = envInt("QUERIT_CACHE_TTL", 600_000)
|
|
128
|
+
// OpenCode's own providers each bounded themselves at 25s, and the host adds
|
|
129
|
+
// no deadline of its own around `execute`, so this is the only one that exists.
|
|
130
|
+
const SEARCH_TIMEOUT = envInt("QUERIT_TIMEOUT", 25_000)
|
|
127
131
|
const SEARCH_SIZE = envInt("QUERIT_COUNT", 8)
|
|
128
132
|
|
|
129
133
|
const CACHE_LIMIT = 200
|
|
@@ -148,6 +152,14 @@ function writeCache(queryKey: string, results: SearchResult[]): void {
|
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
154
|
|
|
155
|
+
// A hung upstream must not hang the agent: the deadline bounds it, and the
|
|
156
|
+
// thunk's signal carries host cancellation into the socket. `timeout <= 0`
|
|
157
|
+
// removes the deadline but never the cancellation, because
|
|
158
|
+
// `AbortSignal.timeout(0)` aborts instantly rather than "never".
|
|
159
|
+
function timedSignal(signal: AbortSignal, timeout: number): AbortSignal {
|
|
160
|
+
return timeout > 0 ? AbortSignal.any([signal, AbortSignal.timeout(timeout)]) : signal
|
|
161
|
+
}
|
|
162
|
+
|
|
151
163
|
export const effect = (ctx: Plugin.Context) =>
|
|
152
164
|
Effect.gen(function* () {
|
|
153
165
|
// Make "Querit" show up in /connect with an API-key method.
|
|
@@ -161,7 +173,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
161
173
|
})
|
|
162
174
|
editor.method.update({
|
|
163
175
|
integrationID: ID,
|
|
164
|
-
method: { type: "env", names: ["
|
|
176
|
+
method: { type: "env", names: ["OSP_QUERIT_API_KEY"] },
|
|
165
177
|
})
|
|
166
178
|
})
|
|
167
179
|
|
|
@@ -181,7 +193,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
181
193
|
const credential = connection
|
|
182
194
|
? yield* soften(ctx.integration.connection.resolve(connection))
|
|
183
195
|
: undefined
|
|
184
|
-
return credential?.type === "key" ? credential.key : process.env.
|
|
196
|
+
return credential?.type === "key" ? credential.key : process.env.OSP_QUERIT_API_KEY
|
|
185
197
|
})
|
|
186
198
|
|
|
187
199
|
// Querit is bound to websearch only while a key exists. Deleting the key
|
|
@@ -205,12 +217,12 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
205
217
|
const key = yield* resolveKey()
|
|
206
218
|
if (!key) {
|
|
207
219
|
return yield* Effect.fail(
|
|
208
|
-
new Error("Connect Querit in /connect, or set
|
|
220
|
+
new Error("Connect Querit in /connect, or set OSP_QUERIT_API_KEY"),
|
|
209
221
|
)
|
|
210
222
|
}
|
|
211
223
|
|
|
212
224
|
const response = yield* Effect.tryPromise({
|
|
213
|
-
try: () =>
|
|
225
|
+
try: (signal) =>
|
|
214
226
|
fetch("https://api.querit.ai/v1/search", {
|
|
215
227
|
method: "POST",
|
|
216
228
|
headers: {
|
|
@@ -219,8 +231,14 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
219
231
|
Authorization: "Bearer " + key,
|
|
220
232
|
},
|
|
221
233
|
body: JSON.stringify({ query: queryKey, count: SEARCH_SIZE }),
|
|
234
|
+
signal: timedSignal(signal, SEARCH_TIMEOUT),
|
|
222
235
|
}),
|
|
223
|
-
|
|
236
|
+
// A timeout is its own failure: naming it stops the user hunting
|
|
237
|
+
// for a key problem that was never there.
|
|
238
|
+
catch: (error) =>
|
|
239
|
+
error instanceof Error && error.name === "TimeoutError"
|
|
240
|
+
? new Error("Querit search timed out after " + SEARCH_TIMEOUT + "ms")
|
|
241
|
+
: new Error("Querit request failed: " + String(error)),
|
|
224
242
|
})
|
|
225
243
|
|
|
226
244
|
if (!response.ok) {
|
|
@@ -111,11 +111,15 @@ type SearchResult = ReturnType<typeof toResult>
|
|
|
111
111
|
// SERPAPI_GL country code for results, e.g. "us", "cn"
|
|
112
112
|
// SERPAPI_HL language code for results, e.g. "en", "zh-cn"
|
|
113
113
|
// SERPAPI_TBS advanced filters, e.g. "qdr:d" (past day), "qdr:y"
|
|
114
|
-
//
|
|
114
|
+
// SERPAPI_TIMEOUT ms to wait before failing (0 removes the deadline)
|
|
115
|
+
// OSP_SERPAPI_API_KEY required; from https://serpapi.com
|
|
115
116
|
//
|
|
116
117
|
// There is deliberately no result-count knob: Google withdrew the `num`
|
|
117
118
|
// parameter, so SerpApi cannot honour one and a count would be a lie.
|
|
118
119
|
const CACHE_TTL = envInt("SERPAPI_CACHE_TTL", 600_000)
|
|
120
|
+
// OpenCode's own providers each bounded themselves at 25s, and the host adds
|
|
121
|
+
// no deadline of its own around `execute`, so this is the only one that exists.
|
|
122
|
+
const SEARCH_TIMEOUT = envInt("SERPAPI_TIMEOUT", 25_000)
|
|
119
123
|
const GL = envStr("SERPAPI_GL")
|
|
120
124
|
const HL = envStr("SERPAPI_HL")
|
|
121
125
|
const TBS = envStr("SERPAPI_TBS")
|
|
@@ -142,6 +146,14 @@ function writeCache(queryKey: string, results: SearchResult[]): void {
|
|
|
142
146
|
}
|
|
143
147
|
}
|
|
144
148
|
|
|
149
|
+
// A hung upstream must not hang the agent: the deadline bounds it, and the
|
|
150
|
+
// thunk's signal carries host cancellation into the socket. `timeout <= 0`
|
|
151
|
+
// removes the deadline but never the cancellation, because
|
|
152
|
+
// `AbortSignal.timeout(0)` aborts instantly rather than "never".
|
|
153
|
+
function timedSignal(signal: AbortSignal, timeout: number): AbortSignal {
|
|
154
|
+
return timeout > 0 ? AbortSignal.any([signal, AbortSignal.timeout(timeout)]) : signal
|
|
155
|
+
}
|
|
156
|
+
|
|
145
157
|
export const effect = (ctx: Plugin.Context) =>
|
|
146
158
|
Effect.gen(function* () {
|
|
147
159
|
// Make "SerpApi" show up in /connect with an API-key method.
|
|
@@ -155,7 +167,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
155
167
|
})
|
|
156
168
|
editor.method.update({
|
|
157
169
|
integrationID: ID,
|
|
158
|
-
method: { type: "env", names: ["
|
|
170
|
+
method: { type: "env", names: ["OSP_SERPAPI_API_KEY"] },
|
|
159
171
|
})
|
|
160
172
|
})
|
|
161
173
|
|
|
@@ -175,7 +187,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
175
187
|
const credential = connection
|
|
176
188
|
? yield* soften(ctx.integration.connection.resolve(connection))
|
|
177
189
|
: undefined
|
|
178
|
-
return credential?.type === "key" ? credential.key : process.env.
|
|
190
|
+
return credential?.type === "key" ? credential.key : process.env.OSP_SERPAPI_API_KEY
|
|
179
191
|
})
|
|
180
192
|
|
|
181
193
|
// SerpApi is bound to websearch only while a key exists. Deleting the key
|
|
@@ -199,7 +211,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
199
211
|
const key = yield* resolveKey()
|
|
200
212
|
if (!key) {
|
|
201
213
|
return yield* Effect.fail(
|
|
202
|
-
new Error("Connect SerpApi in /connect, or set
|
|
214
|
+
new Error("Connect SerpApi in /connect, or set OSP_SERPAPI_API_KEY"),
|
|
203
215
|
)
|
|
204
216
|
}
|
|
205
217
|
|
|
@@ -212,13 +224,19 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
212
224
|
if (TBS) url.searchParams.set("tbs", TBS)
|
|
213
225
|
|
|
214
226
|
const response = yield* Effect.tryPromise({
|
|
215
|
-
try: () =>
|
|
227
|
+
try: (signal) =>
|
|
216
228
|
fetch(url, {
|
|
217
229
|
headers: {
|
|
218
230
|
Accept: "application/json",
|
|
219
231
|
},
|
|
232
|
+
signal: timedSignal(signal, SEARCH_TIMEOUT),
|
|
220
233
|
}),
|
|
221
|
-
|
|
234
|
+
// A timeout is its own failure: naming it stops the user hunting
|
|
235
|
+
// for a key problem that was never there.
|
|
236
|
+
catch: (error) =>
|
|
237
|
+
error instanceof Error && error.name === "TimeoutError"
|
|
238
|
+
? new Error("SerpApi search timed out after " + SEARCH_TIMEOUT + "ms")
|
|
239
|
+
: new Error("SerpApi request failed: " + String(error)),
|
|
222
240
|
})
|
|
223
241
|
|
|
224
242
|
if (!response.ok) {
|
package/plugins/serper-search.ts
CHANGED
|
@@ -112,8 +112,12 @@ type SearchResult = ReturnType<typeof toResult>
|
|
|
112
112
|
// SERPER_HL language code for results, e.g. "en", "zh-cn"
|
|
113
113
|
// SERPER_TBS time filter: "qdr:h" (hour) "qdr:d" (day) "qdr:w" (week)
|
|
114
114
|
// "qdr:m" (month) "qdr:y" (year)
|
|
115
|
-
//
|
|
115
|
+
// SERPER_TIMEOUT ms to wait before failing (0 removes the deadline)
|
|
116
|
+
// OSP_SERPER_API_KEY required; from https://serper.dev
|
|
116
117
|
const CACHE_TTL = envInt("SERPER_CACHE_TTL", 600_000)
|
|
118
|
+
// OpenCode's own providers each bounded themselves at 25s, and the host adds
|
|
119
|
+
// no deadline of its own around `execute`, so this is the only one that exists.
|
|
120
|
+
const SEARCH_TIMEOUT = envInt("SERPER_TIMEOUT", 25_000)
|
|
117
121
|
const SEARCH_SIZE = Math.min(Math.max(envInt("SERPER_NUM", 10), 1), 100)
|
|
118
122
|
const GL = envStr("SERPER_GL")
|
|
119
123
|
const HL = envStr("SERPER_HL")
|
|
@@ -141,6 +145,14 @@ function writeCache(queryKey: string, results: SearchResult[]): void {
|
|
|
141
145
|
}
|
|
142
146
|
}
|
|
143
147
|
|
|
148
|
+
// A hung upstream must not hang the agent: the deadline bounds it, and the
|
|
149
|
+
// thunk's signal carries host cancellation into the socket. `timeout <= 0`
|
|
150
|
+
// removes the deadline but never the cancellation, because
|
|
151
|
+
// `AbortSignal.timeout(0)` aborts instantly rather than "never".
|
|
152
|
+
function timedSignal(signal: AbortSignal, timeout: number): AbortSignal {
|
|
153
|
+
return timeout > 0 ? AbortSignal.any([signal, AbortSignal.timeout(timeout)]) : signal
|
|
154
|
+
}
|
|
155
|
+
|
|
144
156
|
export const effect = (ctx: Plugin.Context) =>
|
|
145
157
|
Effect.gen(function* () {
|
|
146
158
|
// Make "Serper" show up in /connect with an API-key method.
|
|
@@ -154,7 +166,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
154
166
|
})
|
|
155
167
|
editor.method.update({
|
|
156
168
|
integrationID: ID,
|
|
157
|
-
method: { type: "env", names: ["
|
|
169
|
+
method: { type: "env", names: ["OSP_SERPER_API_KEY"] },
|
|
158
170
|
})
|
|
159
171
|
})
|
|
160
172
|
|
|
@@ -174,7 +186,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
174
186
|
const credential = connection
|
|
175
187
|
? yield* soften(ctx.integration.connection.resolve(connection))
|
|
176
188
|
: undefined
|
|
177
|
-
return credential?.type === "key" ? credential.key : process.env.
|
|
189
|
+
return credential?.type === "key" ? credential.key : process.env.OSP_SERPER_API_KEY
|
|
178
190
|
})
|
|
179
191
|
|
|
180
192
|
// Serper is bound to websearch only while a key exists. Deleting the key
|
|
@@ -198,12 +210,12 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
198
210
|
const key = yield* resolveKey()
|
|
199
211
|
if (!key) {
|
|
200
212
|
return yield* Effect.fail(
|
|
201
|
-
new Error("Connect Serper in /connect, or set
|
|
213
|
+
new Error("Connect Serper in /connect, or set OSP_SERPER_API_KEY"),
|
|
202
214
|
)
|
|
203
215
|
}
|
|
204
216
|
|
|
205
217
|
const response = yield* Effect.tryPromise({
|
|
206
|
-
try: () =>
|
|
218
|
+
try: (signal) =>
|
|
207
219
|
fetch("https://google.serper.dev/search", {
|
|
208
220
|
method: "POST",
|
|
209
221
|
headers: {
|
|
@@ -218,8 +230,14 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
218
230
|
...(HL ? { hl: HL } : {}),
|
|
219
231
|
...(TBS ? { tbs: TBS } : {}),
|
|
220
232
|
}),
|
|
233
|
+
signal: timedSignal(signal, SEARCH_TIMEOUT),
|
|
221
234
|
}),
|
|
222
|
-
|
|
235
|
+
// A timeout is its own failure: naming it stops the user hunting
|
|
236
|
+
// for a key problem that was never there.
|
|
237
|
+
catch: (error) =>
|
|
238
|
+
error instanceof Error && error.name === "TimeoutError"
|
|
239
|
+
? new Error("Serper search timed out after " + SEARCH_TIMEOUT + "ms")
|
|
240
|
+
: new Error("Serper request failed: " + String(error)),
|
|
223
241
|
})
|
|
224
242
|
|
|
225
243
|
if (!response.ok) {
|
package/plugins/youcom-search.ts
CHANGED
|
@@ -140,8 +140,12 @@ type SearchResult = ReturnType<typeof toResult>
|
|
|
140
140
|
// YOUCOM_LANGUAGE result language, e.g. "EN", "ZH-HANS"
|
|
141
141
|
// YOUCOM_COUNTRY geographic focus, e.g. "US", "CN"
|
|
142
142
|
// YOUCOM_FRESHNESS "day" | "week" | "month" | "year" or "YYYY-MM-DDtoYYYY-MM-DD"
|
|
143
|
-
//
|
|
143
|
+
// YOUCOM_TIMEOUT ms to wait before failing (0 removes the deadline)
|
|
144
|
+
// OSP_YDC_API_KEY required; You.com's canonical env var, shared with its SDKs
|
|
144
145
|
const CACHE_TTL = envInt("YOUCOM_CACHE_TTL", 600_000)
|
|
146
|
+
// OpenCode's own providers each bounded themselves at 25s, and the host adds
|
|
147
|
+
// no deadline of its own around `execute`, so this is the only one that exists.
|
|
148
|
+
const SEARCH_TIMEOUT = envInt("YOUCOM_TIMEOUT", 25_000)
|
|
145
149
|
const SEARCH_SIZE = Math.min(Math.max(envInt("YOUCOM_COUNT", 10), 1), 100)
|
|
146
150
|
const EXTRACTION = ["none", "highlights", "full_page"].includes(envStr("YOUCOM_EXTRACTION"))
|
|
147
151
|
? envStr("YOUCOM_EXTRACTION")
|
|
@@ -172,6 +176,14 @@ function writeCache(queryKey: string, results: SearchResult[]): void {
|
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
|
|
179
|
+
// A hung upstream must not hang the agent: the deadline bounds it, and the
|
|
180
|
+
// thunk's signal carries host cancellation into the socket. `timeout <= 0`
|
|
181
|
+
// removes the deadline but never the cancellation, because
|
|
182
|
+
// `AbortSignal.timeout(0)` aborts instantly rather than "never".
|
|
183
|
+
function timedSignal(signal: AbortSignal, timeout: number): AbortSignal {
|
|
184
|
+
return timeout > 0 ? AbortSignal.any([signal, AbortSignal.timeout(timeout)]) : signal
|
|
185
|
+
}
|
|
186
|
+
|
|
175
187
|
export const effect = (ctx: Plugin.Context) =>
|
|
176
188
|
Effect.gen(function* () {
|
|
177
189
|
// Make "You.com" show up in /connect with an API-key method.
|
|
@@ -185,7 +197,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
185
197
|
})
|
|
186
198
|
editor.method.update({
|
|
187
199
|
integrationID: ID,
|
|
188
|
-
method: { type: "env", names: ["
|
|
200
|
+
method: { type: "env", names: ["OSP_YDC_API_KEY"] },
|
|
189
201
|
})
|
|
190
202
|
})
|
|
191
203
|
|
|
@@ -205,7 +217,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
205
217
|
const credential = connection
|
|
206
218
|
? yield* soften(ctx.integration.connection.resolve(connection))
|
|
207
219
|
: undefined
|
|
208
|
-
return credential?.type === "key" ? credential.key : process.env.
|
|
220
|
+
return credential?.type === "key" ? credential.key : process.env.OSP_YDC_API_KEY
|
|
209
221
|
})
|
|
210
222
|
|
|
211
223
|
// You.com is bound to websearch only while a key exists. Deleting the key
|
|
@@ -229,7 +241,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
229
241
|
const key = yield* resolveKey()
|
|
230
242
|
if (!key) {
|
|
231
243
|
return yield* Effect.fail(
|
|
232
|
-
new Error("Connect You.com in /connect, or set
|
|
244
|
+
new Error("Connect You.com in /connect, or set OSP_YDC_API_KEY"),
|
|
233
245
|
)
|
|
234
246
|
}
|
|
235
247
|
|
|
@@ -244,7 +256,7 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
244
256
|
: undefined
|
|
245
257
|
|
|
246
258
|
const response = yield* Effect.tryPromise({
|
|
247
|
-
try: () =>
|
|
259
|
+
try: (signal) =>
|
|
248
260
|
fetch("https://ydc-index.io/v1/search", {
|
|
249
261
|
method: "POST",
|
|
250
262
|
headers: {
|
|
@@ -260,8 +272,14 @@ export const effect = (ctx: Plugin.Context) =>
|
|
|
260
272
|
...(FRESHNESS ? { freshness: FRESHNESS } : {}),
|
|
261
273
|
...(extraction ? { extraction } : {}),
|
|
262
274
|
}),
|
|
275
|
+
signal: timedSignal(signal, SEARCH_TIMEOUT),
|
|
263
276
|
}),
|
|
264
|
-
|
|
277
|
+
// A timeout is its own failure: naming it stops the user hunting
|
|
278
|
+
// for a key problem that was never there.
|
|
279
|
+
catch: (error) =>
|
|
280
|
+
error instanceof Error && error.name === "TimeoutError"
|
|
281
|
+
? new Error("You.com search timed out after " + SEARCH_TIMEOUT + "ms")
|
|
282
|
+
: new Error("You.com request failed: " + String(error)),
|
|
265
283
|
})
|
|
266
284
|
|
|
267
285
|
if (!response.ok) {
|