@240xu/dsh-websearch 2.0.2 → 2.0.4
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 +7 -0
- package/examples/cordis.patch.yml +19 -11
- package/lib/client.js +43 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @240xu/dsh-websearch
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@240xu/dsh-websearch)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
3
6
|
> Aggregated web search provider for [DeepSeek Harness](https://github.com/deepseek-ai/dsh) — one plugin id `unified` that fans out to eleven backends concurrently, merges + URL-dedups results, and stays usable even when some backends go down.
|
|
4
7
|
|
|
5
8
|
A pure-Cordis drop-in: registers ONE provider at `ctx.web` so the `dsh-web` selection rule never fires `WEB_PROVIDER_AMBIGUOUS`. Zero-config search out of the box (Exa + Parallel + DuckDuckGo + SearXNG are keyless); API-key backends (DeepSeek / Anthropic / OpenAI / Brave / Tavily / Serper / Mojeek) auto-activate once their key is supplied via the credentials service or env.
|
|
@@ -57,6 +60,10 @@ pnpm install
|
|
|
57
60
|
|
|
58
61
|
## Settings | 配置
|
|
59
62
|
|
|
63
|
+
> v2.0.3 起,配置界面位于 **Settings → 左侧栏「搜索 / Web Search」**(顶级分区,与通用/模型/插件同级)。
|
|
64
|
+
> 每个 API Key 字段旁有 **「获取 Key ↗」** 直达对应平台控制台;保存即写入凭证库并立即生效。
|
|
65
|
+
|
|
66
|
+
|
|
60
67
|
插件向 DSH 设置面板注册 `unified-search` namespace(Settings → Unified Search),全部字段扁平化、开箱可编辑:
|
|
61
68
|
|
|
62
69
|
**全局**
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Registers provider id "unified" that fans out to 11 backends concurrently:
|
|
3
|
-
# keyless: exa, parallel, ddg, searxng
|
|
4
|
-
# key-gated: deepseek, anthropic, openai, brave, tavily, serper, mojeek
|
|
5
|
-
# (activate when their API key is set via Settings > Unified Search)
|
|
1
|
+
# dsh-websearch v2.0.3 — user-side profile patch example
|
|
6
2
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
# The plugin itself is registered by its own bundle manifest
|
|
4
|
+
# (dsh.bundle in package.json + cordis.patch.yml at the repo root),
|
|
5
|
+
# so after installing it there is NOTHING to add for the plugin row.
|
|
6
|
+
#
|
|
7
|
+
# What you DO need in your profile patch: pin the built-in web_search
|
|
8
|
+
# tool onto the unified provider and disable the superseded official one.
|
|
9
|
+
|
|
11
10
|
- id: web-search-deepseek
|
|
12
|
-
name:
|
|
11
|
+
name: '@deepseek-ai/dsh-web-search-deepseek'
|
|
13
12
|
disabled: true
|
|
14
13
|
- id: web
|
|
15
|
-
name:
|
|
14
|
+
name: '@deepseek-ai/dsh-web'
|
|
16
15
|
config:
|
|
17
16
|
searchProvider: unified
|
|
17
|
+
|
|
18
|
+
# Install:
|
|
19
|
+
# dsh plugin --profile web add github:240xu/dsh-websearch
|
|
20
|
+
# or (after npm publish):
|
|
21
|
+
# dsh plugin --profile web add @240xu/dsh-websearch
|
|
22
|
+
#
|
|
23
|
+
# Then open Settings -> Web Search to toggle backends, store API keys
|
|
24
|
+
# (each field links straight to the provider's key dashboard), and tune
|
|
25
|
+
# concurrency / timeouts.
|
package/lib/client.js
CHANGED
|
@@ -26,7 +26,19 @@ window.__ModuleLoader__.load({
|
|
|
26
26
|
];
|
|
27
27
|
const GLOBAL_FIELDS = ["numResults", "concurrency", "backendTimeoutMs"];
|
|
28
28
|
|
|
29
|
+
/** Where the user goes to get/rotate each key. */
|
|
30
|
+
const KEY_URLS = {
|
|
31
|
+
brave: "https://api-dashboard.search.brave.com/app/keys",
|
|
32
|
+
tavily: "https://app.tavily.com/home",
|
|
33
|
+
serper: "https://serper.dev/api-key",
|
|
34
|
+
mojeek: "https://www.mojeek.com/services/search/web-search-api/",
|
|
35
|
+
deepseek: "https://platform.deepseek.com/api_keys",
|
|
36
|
+
anthropic: "https://console.anthropic.com/settings/keys",
|
|
37
|
+
openai: "https://platform.openai.com/api-keys",
|
|
38
|
+
};
|
|
39
|
+
|
|
29
40
|
const zh = {
|
|
41
|
+
nav: "搜索",
|
|
30
42
|
title: "统一搜索 (Unified Search)",
|
|
31
43
|
description: "聚合网络搜索:11 个后端并发 fan-out、URL 去重合并。开关后端、填 API Key、调参数,保存即生效。",
|
|
32
44
|
save: "保存",
|
|
@@ -38,6 +50,7 @@ window.__ModuleLoader__.load({
|
|
|
38
50
|
reset: "重置",
|
|
39
51
|
overridden: "已自定义",
|
|
40
52
|
keyLabel: "{id} API Key",
|
|
53
|
+
getKey: "获取 Key ↗",
|
|
41
54
|
keyHint: "填入后保存到凭证库(对应环境变量引用);留空表示不修改",
|
|
42
55
|
keySet: "已配置",
|
|
43
56
|
keyUnset: "未配置",
|
|
@@ -45,6 +58,7 @@ window.__ModuleLoader__.load({
|
|
|
45
58
|
globalsTitle: "全局参数",
|
|
46
59
|
};
|
|
47
60
|
const en = {
|
|
61
|
+
nav: "Web Search",
|
|
48
62
|
title: "Unified Search",
|
|
49
63
|
description: "Aggregated web search: 11 backends fan out concurrently, URL-deduped. Toggle backends, set API keys, tune globals — saves apply immediately.",
|
|
50
64
|
save: "Save",
|
|
@@ -56,6 +70,7 @@ window.__ModuleLoader__.load({
|
|
|
56
70
|
reset: "Reset",
|
|
57
71
|
overridden: "customized",
|
|
58
72
|
keyLabel: "{id} API Key",
|
|
73
|
+
getKey: "Get key ↗",
|
|
59
74
|
keyHint: "Stored into the credential store on save; leave blank to keep current",
|
|
60
75
|
keySet: "configured",
|
|
61
76
|
keyUnset: "not set",
|
|
@@ -170,8 +185,15 @@ window.__ModuleLoader__.load({
|
|
|
170
185
|
if (staged.clear || staged.text === "") { await this.scope.unset(field); doneFields.push(field); continue; }
|
|
171
186
|
if (GLOBAL_FIELDS.includes(field)) {
|
|
172
187
|
const num = Number(staged.text);
|
|
173
|
-
//
|
|
174
|
-
|
|
188
|
+
// Mirror the server schema bounds client-side so bad input is
|
|
189
|
+
// caught in the UI instead of a failed Host round-trip.
|
|
190
|
+
const BOUNDS = {
|
|
191
|
+
numResults: [1, 50],
|
|
192
|
+
concurrency: [1, 10],
|
|
193
|
+
backendTimeoutMs: [1000, 120000],
|
|
194
|
+
};
|
|
195
|
+
const [lo, hi] = BOUNDS[field] ?? [-Infinity, Infinity];
|
|
196
|
+
if (Number.isNaN(num) || num < lo || num > hi) { this.failed = true; continue; }
|
|
175
197
|
await this.scope.set(field, num);
|
|
176
198
|
doneFields.push(field);
|
|
177
199
|
} else {
|
|
@@ -226,17 +248,17 @@ window.__ModuleLoader__.load({
|
|
|
226
248
|
}
|
|
227
249
|
|
|
228
250
|
/** The card root. */
|
|
229
|
-
function
|
|
251
|
+
function UnifiedSearchSection(props) {
|
|
230
252
|
const state = props.useUnifiedSearchCard((snapshot) => snapshot);
|
|
231
253
|
const card = { edit: props.edit, setToggle: props.setToggle, reset: props.reset, editKey: props.editKey, save: props.save, discard: props.discard };
|
|
232
254
|
// Occupants of settings.plugin.item render inside the tab's <ul>; use <li>.
|
|
233
255
|
if (!state.ready) {
|
|
234
|
-
return react_jsx_runtime.jsx("
|
|
256
|
+
return react_jsx_runtime.jsx("div", { className: "wsc-card", style: { fontSize: 12, opacity: .6 }, children: "…" });
|
|
235
257
|
}
|
|
236
258
|
|
|
237
259
|
const toggleField = (id) => "enable" + id.charAt(0).toUpperCase() + id.slice(1);
|
|
238
260
|
|
|
239
|
-
return react_jsx_runtime.jsxs("
|
|
261
|
+
return react_jsx_runtime.jsxs("div", { className: "wsc-card", children: [
|
|
240
262
|
react_jsx_runtime.jsx("p", { style: { margin: 0, fontSize: 12, opacity: .65 }, children: props.t("description") }),
|
|
241
263
|
react_jsx_runtime.jsx("h4", { className: "wsc-sectionTitle", children: props.t("togglesTitle") }),
|
|
242
264
|
react_jsx_runtime.jsx("div", { className: "wsc-toggles", children: BACKENDS.map((b) =>
|
|
@@ -266,7 +288,14 @@ window.__ModuleLoader__.load({
|
|
|
266
288
|
react_jsx_runtime.jsxs("div", { children: [
|
|
267
289
|
react_jsx_runtime.jsxs("div", { className: "wsc-fieldHead", children: [
|
|
268
290
|
react_jsx_runtime.jsx("span", { className: "wsc-label", children: props.t("keyLabel", { id: b.id }) }),
|
|
269
|
-
react_jsx_runtime.
|
|
291
|
+
react_jsx_runtime.jsxs("span", { className: "wsc-badges", style: { display: "flex", gap: 6, alignItems: "center" }, children: [
|
|
292
|
+
KEY_URLS[b.id] && react_jsx_runtime.jsx("a", {
|
|
293
|
+
href: KEY_URLS[b.id], target: "_blank", rel: "noreferrer",
|
|
294
|
+
style: { fontSize: 11, color: "var(--dsw-alias-accent-primary)", textDecoration: "none" },
|
|
295
|
+
children: props.t("getKey"),
|
|
296
|
+
}),
|
|
297
|
+
react_jsx_runtime.jsx("span", { className: "wsc-badge" + (state.keyState[b.keyEnv]?.configured ? " wsc-badgeOn" : ""), children: state.keyState[b.keyEnv]?.configured ? props.t("keySet") : props.t("keyUnset") }),
|
|
298
|
+
] }),
|
|
270
299
|
] }),
|
|
271
300
|
react_jsx_runtime.jsx("input", {
|
|
272
301
|
className: "wsc-keyInput", type: "password", autoComplete: "off",
|
|
@@ -295,14 +324,14 @@ window.__ModuleLoader__.load({
|
|
|
295
324
|
ctx.get("remote"),
|
|
296
325
|
);
|
|
297
326
|
ctx.effect(() => ctx.remote.$on("credentials/updated", () => controller.refreshAllCredentials()), "websearch: credential invalidations");
|
|
298
|
-
ctx.slots.inject("settings.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
});
|
|
327
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
328
|
+
name: "settings.section",
|
|
329
|
+
id: "websearch",
|
|
330
|
+
order: 16,
|
|
331
|
+
label: () => t("nav"),
|
|
332
|
+
locale: NS,
|
|
333
|
+
inject: () => controller.inject(),
|
|
334
|
+
}, UnifiedSearchSection));
|
|
306
335
|
controller.refreshAllCredentials();
|
|
307
336
|
}
|
|
308
337
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@240xu/dsh-websearch",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Unified web search provider for DeepSeek Harness (ctx.web): 11 backends — Exa + Parallel + DDG + SearXNG (keyless), DeepSeek + Anthropic + OpenAI + Brave + Tavily + Serper + Mojeek (key-gated via Settings credentials). Registers provider id \"unified\" for zero-config web_search tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|