@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 CHANGED
@@ -1,5 +1,8 @@
1
1
  # @240xu/dsh-websearch
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@240xu/dsh-websearch.svg)](https://www.npmjs.com/package/@240xu/dsh-websearch)
4
+ [![license](https://img.shields.io/badge/license-MIT-green.svg)](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
- # ── dsh-websearch v2.0: unified free web search ───────────────────────
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
- # web-search-deepseek stays disabled because the unified plugin supersedes it.
8
- # web.searchProvider pins the built-in web_search tool onto the unified provider.
9
- - id: unified-search
10
- name: "@deepseek-ai/dsh-websearch"
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: "@deepseek-ai/dsh-web-search-deepseek"
11
+ name: '@deepseek-ai/dsh-web-search-deepseek'
13
12
  disabled: true
14
13
  - id: web
15
- name: "@deepseek-ai/dsh-web"
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
- // Non-numeric input: keep the draft so the user can correct it.
174
- if (Number.isNaN(num)) { this.failed = true; continue; }
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 UnifiedSearchCard(props) {
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("li", { className: "wsc-card", style: { fontSize: 12, opacity: .6 }, children: "…" });
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("li", { className: "wsc-card", children: [
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.jsx("span", { className: "wsc-badge" + (state.keyState[b.keyEnv]?.configured ? " wsc-badgeOn" : ""), children: state.keyState[b.keyEnv]?.configured ? props.t("keySet") : props.t("keyUnset") }),
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.plugin.item", function* () {
299
- yield ctx.slots.register({
300
- name: "settings.plugin.item",
301
- key: PLUGIN_NS,
302
- locale: NS,
303
- inject: () => controller.inject(),
304
- }, UnifiedSearchCard);
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.2",
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",