web-search-plus-plugin 1.3.5 → 1.4.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/index.ts +126 -156
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -4
package/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Type } from "@sinclair/typebox";
|
|
2
1
|
import { spawn } from "child_process";
|
|
3
2
|
import fs from "fs";
|
|
4
3
|
import path from "path";
|
|
@@ -71,8 +70,7 @@ function runPython(
|
|
|
71
70
|
if (!settled) {
|
|
72
71
|
settled = true;
|
|
73
72
|
clearTimeout(timer);
|
|
74
|
-
|
|
75
|
-
const safeMsg = err.code === "ENOENT" ? "python3 not found" : "Process error";
|
|
73
|
+
const safeMsg = (err as any).code === "ENOENT" ? "python3 not found" : "Process error";
|
|
76
74
|
resolve({ stdout: "", stderr: safeMsg, code: 1 });
|
|
77
75
|
}
|
|
78
76
|
});
|
|
@@ -82,162 +80,134 @@ function runPython(
|
|
|
82
80
|
const PLUGIN_DIR = getPluginDir();
|
|
83
81
|
const scriptPath = path.join(PLUGIN_DIR, "scripts", "search.py");
|
|
84
82
|
|
|
83
|
+
const PARAMETERS_SCHEMA = {
|
|
84
|
+
type: "object",
|
|
85
|
+
required: ["query"],
|
|
86
|
+
properties: {
|
|
87
|
+
query: { type: "string", description: "Search query" },
|
|
88
|
+
provider: {
|
|
89
|
+
type: "string",
|
|
90
|
+
enum: ["serper", "tavily", "querit", "exa", "perplexity", "you", "searxng", "auto"],
|
|
91
|
+
description: "Force a specific provider, or 'auto' for smart routing (default: auto)",
|
|
92
|
+
},
|
|
93
|
+
count: { type: "number", description: "Number of results (default: 5)" },
|
|
94
|
+
depth: {
|
|
95
|
+
type: "string",
|
|
96
|
+
enum: ["normal", "deep", "deep-reasoning"],
|
|
97
|
+
description: "Exa search depth: 'deep' synthesizes across sources (4-12s), 'deep-reasoning' for complex cross-reference analysis (12-50s). When provider is auto, depth may be auto-selected based on query complexity.",
|
|
98
|
+
},
|
|
99
|
+
time_range: {
|
|
100
|
+
type: "string",
|
|
101
|
+
enum: ["day", "week", "month", "year"],
|
|
102
|
+
description: "Filter results by recency. Applies to Serper (as tbs), Perplexity (as search_recency_filter), Tavily/You.com (as freshness). Useful for news and current events.",
|
|
103
|
+
},
|
|
104
|
+
include_domains: {
|
|
105
|
+
type: "array",
|
|
106
|
+
items: { type: "string" },
|
|
107
|
+
description: "Only include results from these domains (e.g. ['arxiv.org', 'github.com']). Supported by Tavily and Exa.",
|
|
108
|
+
},
|
|
109
|
+
exclude_domains: {
|
|
110
|
+
type: "array",
|
|
111
|
+
items: { type: "string" },
|
|
112
|
+
description: "Exclude results from these domains (e.g. ['reddit.com', 'pinterest.com']). Supported by Tavily and Exa.",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
85
117
|
export default function (api: any) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
api.registerTool(
|
|
105
|
-
{
|
|
106
|
-
name: "web_search_plus",
|
|
107
|
-
description:
|
|
108
|
-
"Search the web using multi-provider intelligent routing (Serper/Google, Tavily/Research, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG). Automatically selects the best provider based on query intent. Use for ALL web searches. Set depth='deep' for multi-source synthesis, 'deep-reasoning' for complex cross-document analysis.",
|
|
109
|
-
parameters: Type.Object({
|
|
110
|
-
query: Type.String({ description: "Search query" }),
|
|
111
|
-
provider: Type.Optional(
|
|
112
|
-
Type.Union(
|
|
113
|
-
[
|
|
114
|
-
Type.Literal("serper"),
|
|
115
|
-
Type.Literal("tavily"),
|
|
116
|
-
Type.Literal("querit"),
|
|
117
|
-
Type.Literal("exa"),
|
|
118
|
-
Type.Literal("perplexity"),
|
|
119
|
-
Type.Literal("you"),
|
|
120
|
-
Type.Literal("searxng"),
|
|
121
|
-
Type.Literal("auto"),
|
|
122
|
-
],
|
|
123
|
-
{
|
|
124
|
-
description:
|
|
125
|
-
"Force a specific provider, or 'auto' for smart routing (default: auto)",
|
|
126
|
-
},
|
|
127
|
-
),
|
|
128
|
-
),
|
|
129
|
-
count: Type.Optional(
|
|
130
|
-
Type.Number({ description: "Number of results (default: 5)" }),
|
|
131
|
-
),
|
|
132
|
-
depth: Type.Optional(
|
|
133
|
-
Type.Union(
|
|
134
|
-
[
|
|
135
|
-
Type.Literal("normal"),
|
|
136
|
-
Type.Literal("deep"),
|
|
137
|
-
Type.Literal("deep-reasoning"),
|
|
138
|
-
],
|
|
139
|
-
{
|
|
140
|
-
description:
|
|
141
|
-
"Exa search depth: 'deep' synthesizes across sources (4-12s), 'deep-reasoning' for complex cross-reference analysis (12-50s). When provider is auto, depth may be auto-selected based on query complexity.",
|
|
142
|
-
},
|
|
143
|
-
),
|
|
144
|
-
),
|
|
145
|
-
time_range: Type.Optional(
|
|
146
|
-
Type.Union(
|
|
147
|
-
[
|
|
148
|
-
Type.Literal("day"),
|
|
149
|
-
Type.Literal("week"),
|
|
150
|
-
Type.Literal("month"),
|
|
151
|
-
Type.Literal("year"),
|
|
152
|
-
],
|
|
153
|
-
{
|
|
154
|
-
description:
|
|
155
|
-
"Filter results by recency. Applies to Serper (as tbs), Perplexity (as search_recency_filter), Tavily/You.com (as freshness). Useful for news and current events.",
|
|
156
|
-
},
|
|
157
|
-
),
|
|
158
|
-
),
|
|
159
|
-
include_domains: Type.Optional(
|
|
160
|
-
Type.Array(Type.String(), {
|
|
161
|
-
description:
|
|
162
|
-
"Only include results from these domains (e.g. ['arxiv.org', 'github.com']). Supported by Tavily and Exa.",
|
|
163
|
-
}),
|
|
164
|
-
),
|
|
165
|
-
exclude_domains: Type.Optional(
|
|
166
|
-
Type.Array(Type.String(), {
|
|
167
|
-
description:
|
|
168
|
-
"Exclude results from these domains (e.g. ['reddit.com', 'pinterest.com']). Supported by Tavily and Exa.",
|
|
169
|
-
}),
|
|
170
|
-
),
|
|
171
|
-
}),
|
|
172
|
-
async execute(
|
|
173
|
-
_id: string,
|
|
174
|
-
params: {
|
|
175
|
-
query: string;
|
|
176
|
-
provider?: string;
|
|
177
|
-
count?: number;
|
|
178
|
-
depth?: string;
|
|
179
|
-
time_range?: string;
|
|
180
|
-
include_domains?: string[];
|
|
181
|
-
exclude_domains?: string[];
|
|
182
|
-
},
|
|
183
|
-
) {
|
|
184
|
-
if (!fs.existsSync(scriptPath)) {
|
|
185
|
-
return {
|
|
186
|
-
content: [{ type: "text", text: `Search failed: script not found at ${scriptPath}` }],
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const args = [scriptPath, "--query", params.query, "--compact"];
|
|
191
|
-
|
|
192
|
-
if (params.provider && params.provider !== "auto") {
|
|
193
|
-
args.push("--provider", params.provider);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (typeof params.count === "number" && Number.isFinite(params.count)) {
|
|
197
|
-
args.push("--max-results", String(Math.max(1, Math.floor(params.count))));
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (params.depth && params.depth !== "normal") {
|
|
201
|
-
args.push("--exa-depth", params.depth);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (params.time_range) {
|
|
205
|
-
args.push("--time-range", params.time_range);
|
|
206
|
-
args.push("--freshness", params.time_range);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (params.include_domains?.length) {
|
|
210
|
-
args.push("--include-domains", ...params.include_domains);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (params.exclude_domains?.length) {
|
|
214
|
-
args.push("--exclude-domains", ...params.exclude_domains);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const envPaths = [
|
|
218
|
-
path.join(PLUGIN_DIR, ".env"),
|
|
219
|
-
path.join(PLUGIN_DIR, "..", "web-search-plus", ".env"),
|
|
220
|
-
];
|
|
221
|
-
const fileEnv: Record<string, string> = {};
|
|
222
|
-
for (const envPath of envPaths) {
|
|
223
|
-
Object.assign(fileEnv, loadEnvFile(envPath));
|
|
224
|
-
}
|
|
225
|
-
const childEnv = { ...process.env, ...configEnv, ...fileEnv };
|
|
226
|
-
|
|
227
|
-
const result = await runPython(args, childEnv, 75000);
|
|
228
|
-
|
|
229
|
-
if (result.code !== 0) {
|
|
230
|
-
const stderr = sanitizeOutput(result.stderr.trim()) || "Unknown error";
|
|
231
|
-
return {
|
|
232
|
-
content: [{ type: "text", text: `Search failed (exit ${result.code}): ${stderr}` }],
|
|
233
|
-
};
|
|
234
|
-
}
|
|
118
|
+
// Bridge OpenClaw config fields to env vars expected by search.py
|
|
119
|
+
const configEnv: Record<string, string> = {};
|
|
120
|
+
const pluginConfig: Record<string, string> = (api.pluginConfig ?? {}) as Record<string, string>;
|
|
121
|
+
const configKeyMap: Record<string, string> = {
|
|
122
|
+
serperApiKey: "SERPER_API_KEY",
|
|
123
|
+
tavilyApiKey: "TAVILY_API_KEY",
|
|
124
|
+
queritApiKey: "QUERIT_API_KEY",
|
|
125
|
+
exaApiKey: "EXA_API_KEY",
|
|
126
|
+
perplexityApiKey: "PERPLEXITY_API_KEY",
|
|
127
|
+
kilocodeApiKey: "KILOCODE_API_KEY",
|
|
128
|
+
youApiKey: "YOU_API_KEY",
|
|
129
|
+
searxngInstanceUrl: "SEARXNG_INSTANCE_URL",
|
|
130
|
+
};
|
|
131
|
+
for (const [cfgKey, envKey] of Object.entries(configKeyMap)) {
|
|
132
|
+
const val = pluginConfig[cfgKey];
|
|
133
|
+
if (val && typeof val === "string") configEnv[envKey] = val;
|
|
134
|
+
}
|
|
235
135
|
|
|
136
|
+
api.registerTool(
|
|
137
|
+
{
|
|
138
|
+
name: "web_search_plus",
|
|
139
|
+
description:
|
|
140
|
+
"Search the web using multi-provider intelligent routing (Serper/Google, Tavily/Research, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG). Automatically selects the best provider based on query intent. Use for ALL web searches. Set depth='deep' for multi-source synthesis, 'deep-reasoning' for complex cross-document analysis.",
|
|
141
|
+
parameters: PARAMETERS_SCHEMA,
|
|
142
|
+
async execute(
|
|
143
|
+
_id: string,
|
|
144
|
+
params: {
|
|
145
|
+
query: string;
|
|
146
|
+
provider?: string;
|
|
147
|
+
count?: number;
|
|
148
|
+
depth?: string;
|
|
149
|
+
time_range?: string;
|
|
150
|
+
include_domains?: string[];
|
|
151
|
+
exclude_domains?: string[];
|
|
152
|
+
},
|
|
153
|
+
) {
|
|
154
|
+
if (!fs.existsSync(scriptPath)) {
|
|
236
155
|
return {
|
|
237
|
-
content: [{ type: "text", text:
|
|
156
|
+
content: [{ type: "text", text: `Search failed: script not found at ${scriptPath}` }],
|
|
238
157
|
};
|
|
239
|
-
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const args = [scriptPath, "--query", params.query, "--compact"];
|
|
161
|
+
|
|
162
|
+
if (params.provider && params.provider !== "auto") {
|
|
163
|
+
args.push("--provider", params.provider);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (typeof params.count === "number" && Number.isFinite(params.count)) {
|
|
167
|
+
args.push("--max-results", String(Math.max(1, Math.floor(params.count))));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (params.depth && params.depth !== "normal") {
|
|
171
|
+
args.push("--exa-depth", params.depth);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (params.time_range) {
|
|
175
|
+
args.push("--time-range", params.time_range);
|
|
176
|
+
args.push("--freshness", params.time_range);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (params.include_domains?.length) {
|
|
180
|
+
args.push("--include-domains", ...params.include_domains);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (params.exclude_domains?.length) {
|
|
184
|
+
args.push("--exclude-domains", ...params.exclude_domains);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const envPaths = [
|
|
188
|
+
path.join(PLUGIN_DIR, ".env"),
|
|
189
|
+
path.join(PLUGIN_DIR, "..", "web-search-plus", ".env"),
|
|
190
|
+
];
|
|
191
|
+
const fileEnv: Record<string, string> = {};
|
|
192
|
+
for (const envPath of envPaths) {
|
|
193
|
+
Object.assign(fileEnv, loadEnvFile(envPath));
|
|
194
|
+
}
|
|
195
|
+
const childEnv = { ...process.env, ...configEnv, ...fileEnv };
|
|
196
|
+
|
|
197
|
+
const result = await runPython(args, childEnv, 75000);
|
|
198
|
+
|
|
199
|
+
if (result.code !== 0) {
|
|
200
|
+
const stderr = sanitizeOutput(result.stderr.trim()) || "Unknown error";
|
|
201
|
+
return {
|
|
202
|
+
content: [{ type: "text", text: `Search failed (exit ${result.code}): ${stderr}` }],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: "text", text: sanitizeOutput(result.stdout.trim()) || "{}" }],
|
|
208
|
+
};
|
|
240
209
|
},
|
|
241
|
-
|
|
242
|
-
|
|
210
|
+
},
|
|
211
|
+
{ optional: true },
|
|
212
|
+
);
|
|
243
213
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "web-search-plus-plugin",
|
|
3
3
|
"name": "Web Search Plus",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.4.0",
|
|
5
5
|
"description": "Multi-provider web search (Serper/Google, Tavily, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG) with intelligent auto-routing",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web-search-plus-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "OpenClaw plugin: multi-provider web search (Serper/Google, Tavily, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG) with intelligent auto-routing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -16,9 +16,6 @@
|
|
|
16
16
|
"LICENSE"
|
|
17
17
|
],
|
|
18
18
|
"keywords": ["openclaw", "plugin", "search", "serper", "tavily", "querit", "exa", "exa-deep", "perplexity", "you", "searxng", "web-search", "auto-routing"],
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"@sinclair/typebox": "^0.34.0"
|
|
21
|
-
},
|
|
22
19
|
"peerDependencies": {
|
|
23
20
|
"openclaw": ">=2026.3.22"
|
|
24
21
|
},
|