@jaco-tech/dsh-web-search-searxng 0.2.0 → 0.2.2

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
@@ -13,15 +13,33 @@ Registers a `WebSearchProvider` into `ctx.web` so that the existing `web_search`
13
13
  npm install @jaco-tech/dsh-web-search-searxng
14
14
  ```
15
15
 
16
+ Requires DeepSeek Harness 0.2.1-rc.5. The tested DSH dependency set pins
17
+ `@deepseek-ai/dsh-web` and `@deepseek-ai/dsh-settings` exactly at `0.2.1-rc.5`.
18
+ `@deepseek-ai/cordis` is the deliberate exception: like the first-party DSH web plugins,
19
+ this package declares the compatible patch range `~4.0.5`. A consumer lockfile could in
20
+ principle resolve a different 4.0.x cordis patch and create a second framework copy; this
21
+ plugin's one-shot install is verified against cordis 4.0.5. Every DSH bump therefore needs
22
+ a release of this package with its framework peers re-pinned (`test/peer-pins.test.mjs`
23
+ enforces their presence and pin form). Use 0.1.x of this plugin with DSH 0.1.x.
24
+
16
25
  ## Configure
17
26
 
18
- Add to your DSH profile's `cordis.patch.yml`:
27
+ ### 1. Install the package
28
+
29
+ ```bash
30
+ npm install @jaco-tech/dsh-web-search-searxng
31
+ ```
32
+
33
+ ### 2. Add it to your DSH profile
34
+
35
+ Edit `~/.dsh/profiles/web/cordis.patch.yml` (create it if it doesn't exist) and add:
19
36
 
20
37
  ```yaml
21
38
  - insert:
22
39
  - id: web-search-searxng
23
40
  name: '@jaco-tech/dsh-web-search-searxng'
24
41
  config:
42
+ # Required: point at your SearXNG instance
25
43
  baseURL: 'http://your-searxng-instance:8888'
26
44
  # Optional filters:
27
45
  language: 'en'
@@ -29,7 +47,7 @@ Add to your DSH profile's `cordis.patch.yml`:
29
47
  timeRange: ''
30
48
  ```
31
49
 
32
- Optionally pin `ctx.web` to use this provider explicitly:
50
+ If you have multiple search providers registered, pin `ctx.web` to use this one:
33
51
 
34
52
  ```yaml
35
53
  - replace:
@@ -38,6 +56,22 @@ Optionally pin `ctx.web` to use this provider explicitly:
38
56
  searchProvider: searxng
39
57
  ```
40
58
 
59
+ ### 3. Restart DSH
60
+
61
+ Stop your running `dsh` process and start it again:
62
+
63
+ ```bash
64
+ npx @deepseek-ai/dsh web
65
+ ```
66
+
67
+ The plugin will be loaded automatically. The `web_search` tool in your agent will now use your SearXNG instance.
68
+
69
+ ### 4. (Optional) Configure via the Web UI
70
+
71
+ Once DSH is running, go to **Settings → Plugins** in the Web UI. You'll see a "web-search-searxng" card where you can edit the `baseURL` and filter settings without editing YAML files.
72
+
73
+ Every config field is volatile (DSH 0.2 settings model): a change is committed into the running plugin and the next search reads it, without reloading the plugin.
74
+
41
75
  ## How it works
42
76
 
43
77
  This is a [Cordis](https://github.com/cordiverse/cordis) plugin that implements the `WebSearchProvider` interface from `@deepseek-ai/dsh-web`:
@@ -49,7 +83,7 @@ export const inject = ["web"]
49
83
  export function apply(ctx, config) {
50
84
  ctx.web.registerSearchProvider({
51
85
  id: "searxng",
52
- available() { /* URL.canParse(config.baseURL) */ },
86
+ available() { /* URL.canParse(config.baseURL.get()) */ },
53
87
  async search(request, signal) {
54
88
  // GET /search?q=<query>&format=json
55
89
  // maps response to WebSearchResult
@@ -86,8 +120,27 @@ Requires the `NPM_TOKEN` secret to be set in the repository.
86
120
  ```bash
87
121
  npm install
88
122
  npm run build
123
+ npm test # hermetic: loads lib/ into a real cordis Context next to dsh-web's ctx.web
89
124
  ```
90
125
 
126
+ Verify a one-shot scratch install of DSH plus this package's `npm pack` tarball:
127
+
128
+ ```bash
129
+ repo=$PWD
130
+ scratch=$(mktemp -d)
131
+ tarball=$(npm pack --pack-destination "$scratch" --silent)
132
+ printf '%s\n' \
133
+ "{\"private\":true,\"dependencies\":{\"@deepseek-ai/dsh\":\"0.2.1-rc.5\",\"@jaco-tech/dsh-web-search-searxng\":\"file:./$tarball\"}}" \
134
+ > "$scratch/package.json"
135
+ (cd "$scratch" && npm install --no-audit --no-fund)
136
+ node "$repo/test/installed-tree-identity.mjs" "$scratch"
137
+ ```
138
+
139
+ The final check is hermetic (it only reads the installed tree). It requires exactly one
140
+ installed copy each of `@deepseek-ai/dsh-web`,
141
+ `@deepseek-ai/dsh-settings`, and `@deepseek-ai/cordis`, and requires
142
+ `@deepseek-ai/cordis-plugin-group` to be present.
143
+
91
144
  ## License
92
145
 
93
146
  MIT
package/lib/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { WebError } from "@deepseek-ai/dsh-web";
3
- import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
4
3
  //#endregion
5
4
  //#region provider
6
5
  /**
@@ -55,6 +54,11 @@ function mapSearXngResponse(body, maxResults) {
55
54
  class SearXngSearchProvider {
56
55
  resolveOptions;
57
56
  id = SEARXNG_PROVIDER_ID;
57
+ /**
58
+ * @param resolveOptions - the options for the NEXT operation, snapshotted
59
+ * once at each operation's entry, so a settings change applies to the next
60
+ * search without re-registering the provider.
61
+ */
58
62
  constructor(resolveOptions) {
59
63
  this.resolveOptions = resolveOptions;
60
64
  }
@@ -138,20 +142,18 @@ const name = "web-search-searxng";
138
142
  /** The web seam this provider registers into. */
139
143
  const inject = ["web"];
140
144
  const Config = z.object({
141
- baseURL: z.string().required(),
142
- language: z.string().default(""),
143
- categories: z.string().default(""),
144
- timeRange: z.string().default(""),
145
+ baseURL: z.string().required().volatile(),
146
+ language: z.string().default("").volatile(),
147
+ categories: z.string().default("").volatile(),
148
+ timeRange: z.string().default("").volatile(),
145
149
  });
146
- /** Settings namespace for the DSH Web UI (Settings → Plugins). */
147
- const WEB_SEARCH_SEARXNG_SETTINGS_NAMESPACE = settingsNamespace("web-search-searxng");
148
150
  function apply(ctx, config) {
149
- let current = () => config;
150
- installSettingsSection(ctx, WEB_SEARCH_SEARXNG_SETTINGS_NAMESPACE, Config, config, {
151
- setSource: (source) => { current = source; },
152
- onChange: () => { },
153
- });
154
- ctx.web.registerSearchProvider(new SearXngSearchProvider(() => current()));
151
+ ctx.web.registerSearchProvider(new SearXngSearchProvider(() => ({
152
+ baseURL: config.baseURL.get(),
153
+ language: config.language.get(),
154
+ categories: config.categories.get(),
155
+ timeRange: config.timeRange.get(),
156
+ })));
155
157
  }
156
158
  //#endregion
157
159
  //#region helpers
@@ -165,5 +167,4 @@ function aborted(signal, fallback) {
165
167
  function isAbortError(error) {
166
168
  return error instanceof DOMException && error.name === "AbortError";
167
169
  }
168
- //#endregion
169
170
  export { Config, SEARXNG_PROVIDER_ID, SearXngSearchProvider, apply, inject, name, mapSearXngResponse };
@@ -1,5 +1,5 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
- import type { Context } from "@deepseek-ai/cordis";
2
+ import type { Context, Volatile } from "@deepseek-ai/cordis";
3
3
  import type { WebSearchProvider, WebSearchRequest, WebSearchResult } from "@deepseek-ai/dsh-web";
4
4
  interface SearXngResultItem {
5
5
  url: string;
@@ -37,9 +37,14 @@ declare function mapSearXngResponse(body: SearXngResponseBody, maxResults?: numb
37
37
  * seam's normalized `WebSearchResult`. No auth, no API key, no model calls.
38
38
  */
39
39
  declare class SearXngSearchProvider implements WebSearchProvider {
40
- readonly resolveOptions: () => Config;
40
+ readonly resolveOptions: () => SearXngOptions;
41
41
  readonly id = "searxng";
42
- constructor(resolveOptions: () => Config);
42
+ /**
43
+ * @param resolveOptions - the options for the NEXT operation, snapshotted
44
+ * once at each operation's entry, so a settings change applies to the next
45
+ * search without re-registering the provider.
46
+ */
47
+ constructor(resolveOptions: () => SearXngOptions);
43
48
  available(): boolean;
44
49
  search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
45
50
  }
@@ -64,19 +69,43 @@ declare class SearXngSearchProvider implements WebSearchProvider {
64
69
  declare const name = "web-search-searxng";
65
70
  /** The web seam this provider registers into. */
66
71
  declare const inject: string[];
72
+ /** One search's resolved options. */
73
+ interface SearXngOptions {
74
+ /** Base URL of the SearXNG instance (without trailing /search). */
75
+ baseURL: string;
76
+ /** Language filter (e.g. "en", "nl", "fr"). Empty = no filter. */
77
+ language: string;
78
+ /** Categories filter (e.g. "general", "news", "images"). Empty = no filter. */
79
+ categories: string;
80
+ /** Time range filter (e.g. "day", "week", "month", "year"). Empty = no filter. */
81
+ timeRange: string;
82
+ }
67
83
  /**
68
84
  * Plugin configuration. `baseURL` is required (point at your SearXNG instance).
85
+ * Every field is volatile: DSH commits a settings change into the running
86
+ * plugin, and the next search reads it.
69
87
  */
70
88
  interface Config {
71
89
  /** Base URL of the SearXNG instance (without trailing /search). */
72
- baseURL: string;
90
+ baseURL: Volatile<string>;
73
91
  /** Language filter (e.g. "en", "nl", "fr"). Empty = no filter. */
74
- language?: string;
92
+ language: Volatile<string>;
75
93
  /** Categories filter (e.g. "general", "news", "images"). Empty = no filter. */
76
- categories?: string;
94
+ categories: Volatile<string>;
77
95
  /** Time range filter (e.g. "day", "week", "month", "year"). Empty = no filter. */
78
- timeRange?: string;
96
+ timeRange: Volatile<string>;
79
97
  }
80
- declare const Config: z<Config>;
98
+ declare const Config: z<Schemastery.ObjectS<NoInfer<{
99
+ baseURL: z<string, string, "volatile-defined">;
100
+ language: z<string, string, "volatile-defined">;
101
+ categories: z<string, string, "volatile-defined">;
102
+ timeRange: z<string, string, "volatile-defined">;
103
+ }>>, Schemastery.ObjectT<NoInfer<{
104
+ baseURL: z<string, string, "volatile-defined">;
105
+ language: z<string, string, "volatile-defined">;
106
+ categories: z<string, string, "volatile-defined">;
107
+ timeRange: z<string, string, "volatile-defined">;
108
+ }>>, "plain">;
81
109
  declare function apply(ctx: Context, config: Config): void;
110
+ export type { SearXngOptions };
82
111
  export { Config, SEARXNG_PROVIDER_ID, SearXngSearchProvider, apply, inject, name, mapSearXngResponse };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jaco-tech/dsh-web-search-searxng",
3
3
  "description": "SearXNG-backed search provider for the DeepSeek Harness web capability seam (ctx.web). No auth, no API key.",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,21 +32,22 @@
32
32
  "cordis-plugin"
33
33
  ],
34
34
  "peerDependencies": {
35
- "@deepseek-ai/dsh-web": "^0.1.0-rc.7",
36
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
37
- "@deepseek-ai/cordis": "^4.0.1"
35
+ "@deepseek-ai/dsh-web": "0.2.1-rc.5",
36
+ "@deepseek-ai/cordis": "~4.0.5"
38
37
  },
39
38
  "dependencies": {
40
- "@deepseek-ai/schemastery": "^3.18.1"
39
+ "@deepseek-ai/schemastery": "~3.18.5"
41
40
  },
42
41
  "devDependencies": {
43
- "@deepseek-ai/dsh-web": "^0.1.0-rc.7",
44
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
45
- "@deepseek-ai/cordis": "^4.0.1",
42
+ "@deepseek-ai/dsh": "0.2.1-rc.5",
43
+ "@deepseek-ai/dsh-web": "0.2.1-rc.5",
44
+ "@deepseek-ai/dsh-settings": "0.2.1-rc.5",
45
+ "@deepseek-ai/cordis": "~4.0.5",
46
46
  "typescript": "^5.7.0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsc",
50
+ "test": "npm run build && node --test test/peer-pins.test.mjs test/smoke.test.mjs",
50
51
  "prepublishOnly": "tsc"
51
52
  }
52
- }
53
+ }