@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 +56 -3
- package/lib/index.js +15 -14
- package/lib/types/index.d.ts +37 -8
- package/package.json +10 -9
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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 };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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: () =>
|
|
40
|
+
readonly resolveOptions: () => SearXngOptions;
|
|
41
41
|
readonly id = "searxng";
|
|
42
|
-
|
|
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
|
|
92
|
+
language: Volatile<string>;
|
|
75
93
|
/** Categories filter (e.g. "general", "news", "images"). Empty = no filter. */
|
|
76
|
-
categories
|
|
94
|
+
categories: Volatile<string>;
|
|
77
95
|
/** Time range filter (e.g. "day", "week", "month", "year"). Empty = no filter. */
|
|
78
|
-
timeRange
|
|
96
|
+
timeRange: Volatile<string>;
|
|
79
97
|
}
|
|
80
|
-
declare const Config: z<
|
|
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.
|
|
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": "
|
|
36
|
-
"@deepseek-ai/
|
|
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": "
|
|
39
|
+
"@deepseek-ai/schemastery": "~3.18.5"
|
|
41
40
|
},
|
|
42
41
|
"devDependencies": {
|
|
43
|
-
"@deepseek-ai/dsh
|
|
44
|
-
"@deepseek-ai/dsh-
|
|
45
|
-
"@deepseek-ai/
|
|
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
|
+
}
|