web-search-plus-plugin 1.2.3 → 1.3.1
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 +176 -169
- package/openclaw.plugin.json +1 -2
- package/package.json +7 -1
package/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
1
2
|
import { Type } from "@sinclair/typebox";
|
|
2
3
|
import { spawnSync } from "child_process";
|
|
3
4
|
import fs from "fs";
|
|
@@ -34,185 +35,191 @@ function loadEnvFile(envPath: string): Record<string, string> {
|
|
|
34
35
|
const PLUGIN_DIR = getPluginDir();
|
|
35
36
|
const scriptPath = path.join(PLUGIN_DIR, "scripts", "search.py");
|
|
36
37
|
|
|
37
|
-
export default
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
|
|
38
|
+
export default definePluginEntry({
|
|
39
|
+
id: "web-search-plus-plugin",
|
|
40
|
+
name: "Web Search Plus",
|
|
41
|
+
description:
|
|
42
|
+
"Multi-provider web search (Serper/Google, Tavily, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG) with intelligent auto-routing",
|
|
43
|
+
register(api) {
|
|
44
|
+
// Bridge OpenClaw config fields to env vars expected by search.py
|
|
45
|
+
const configEnv: Record<string, string> = {};
|
|
46
|
+
const pluginConfig: Record<string, string> = (api.pluginConfig ?? {}) as Record<string, string>;
|
|
47
|
+
const configKeyMap: Record<string, string> = {
|
|
48
|
+
serperApiKey: "SERPER_API_KEY",
|
|
49
|
+
tavilyApiKey: "TAVILY_API_KEY",
|
|
50
|
+
queritApiKey: "QUERIT_API_KEY",
|
|
51
|
+
exaApiKey: "EXA_API_KEY",
|
|
52
|
+
perplexityApiKey: "PERPLEXITY_API_KEY",
|
|
53
|
+
kilocodeApiKey: "KILOCODE_API_KEY",
|
|
54
|
+
youApiKey: "YOU_API_KEY",
|
|
55
|
+
searxngInstanceUrl: "SEARXNG_INSTANCE_URL",
|
|
56
|
+
};
|
|
57
|
+
for (const [cfgKey, envKey] of Object.entries(configKeyMap)) {
|
|
58
|
+
const val = pluginConfig[cfgKey];
|
|
59
|
+
if (val && typeof val === "string") configEnv[envKey] = val;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
api.registerTool(
|
|
63
|
+
{
|
|
64
|
+
name: "web_search_plus",
|
|
65
|
+
description:
|
|
66
|
+
"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.",
|
|
67
|
+
parameters: Type.Object({
|
|
68
|
+
query: Type.String({ description: "Search query" }),
|
|
69
|
+
provider: Type.Optional(
|
|
70
|
+
Type.Union(
|
|
71
|
+
[
|
|
72
|
+
Type.Literal("serper"),
|
|
73
|
+
Type.Literal("tavily"),
|
|
74
|
+
Type.Literal("querit"),
|
|
75
|
+
Type.Literal("exa"),
|
|
76
|
+
Type.Literal("perplexity"),
|
|
77
|
+
Type.Literal("you"),
|
|
78
|
+
Type.Literal("searxng"),
|
|
79
|
+
Type.Literal("auto"),
|
|
80
|
+
],
|
|
81
|
+
{
|
|
82
|
+
description:
|
|
83
|
+
"Force a specific provider, or 'auto' for smart routing (default: auto)",
|
|
84
|
+
},
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
count: Type.Optional(
|
|
88
|
+
Type.Number({ description: "Number of results (default: 5)" }),
|
|
89
|
+
),
|
|
90
|
+
depth: Type.Optional(
|
|
91
|
+
Type.Union(
|
|
92
|
+
[
|
|
93
|
+
Type.Literal("normal"),
|
|
94
|
+
Type.Literal("deep"),
|
|
95
|
+
Type.Literal("deep-reasoning"),
|
|
96
|
+
],
|
|
97
|
+
{
|
|
98
|
+
description:
|
|
99
|
+
"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.",
|
|
100
|
+
},
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
time_range: Type.Optional(
|
|
104
|
+
Type.Union(
|
|
105
|
+
[
|
|
106
|
+
Type.Literal("day"),
|
|
107
|
+
Type.Literal("week"),
|
|
108
|
+
Type.Literal("month"),
|
|
109
|
+
Type.Literal("year"),
|
|
110
|
+
],
|
|
111
|
+
{
|
|
112
|
+
description:
|
|
113
|
+
"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.",
|
|
114
|
+
},
|
|
115
|
+
),
|
|
79
116
|
),
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
Type.Number({ description: "Number of results (default: 5)" }),
|
|
83
|
-
),
|
|
84
|
-
depth: Type.Optional(
|
|
85
|
-
Type.Union(
|
|
86
|
-
[
|
|
87
|
-
Type.Literal("normal"),
|
|
88
|
-
Type.Literal("deep"),
|
|
89
|
-
Type.Literal("deep-reasoning"),
|
|
90
|
-
],
|
|
91
|
-
{
|
|
117
|
+
include_domains: Type.Optional(
|
|
118
|
+
Type.Array(Type.String(), {
|
|
92
119
|
description:
|
|
93
|
-
"
|
|
94
|
-
},
|
|
120
|
+
"Only include results from these domains (e.g. ['arxiv.org', 'github.com']). Supported by Tavily and Exa.",
|
|
121
|
+
}),
|
|
95
122
|
),
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Type.Union(
|
|
99
|
-
[
|
|
100
|
-
Type.Literal("day"),
|
|
101
|
-
Type.Literal("week"),
|
|
102
|
-
Type.Literal("month"),
|
|
103
|
-
Type.Literal("year"),
|
|
104
|
-
],
|
|
105
|
-
{
|
|
123
|
+
exclude_domains: Type.Optional(
|
|
124
|
+
Type.Array(Type.String(), {
|
|
106
125
|
description:
|
|
107
|
-
"
|
|
108
|
-
},
|
|
126
|
+
"Exclude results from these domains (e.g. ['reddit.com', 'pinterest.com']). Supported by Tavily and Exa.",
|
|
127
|
+
}),
|
|
109
128
|
),
|
|
110
|
-
),
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
count
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
},
|
|
135
|
-
) {
|
|
136
|
-
const args = [scriptPath, "--query", params.query, "--compact"];
|
|
137
|
-
|
|
138
|
-
if (params.provider && params.provider !== "auto") {
|
|
139
|
-
args.push("--provider", params.provider);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (typeof params.count === "number" && Number.isFinite(params.count)) {
|
|
143
|
-
args.push(
|
|
144
|
-
"--max-results",
|
|
145
|
-
String(Math.max(1, Math.floor(params.count))),
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (params.depth && params.depth !== "normal") {
|
|
150
|
-
args.push("--exa-depth", params.depth);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (params.time_range) {
|
|
154
|
-
args.push("--time-range", params.time_range);
|
|
155
|
-
args.push("--freshness", params.time_range);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (params.include_domains?.length) {
|
|
159
|
-
args.push("--include-domains", ...params.include_domains);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (params.exclude_domains?.length) {
|
|
163
|
-
args.push("--exclude-domains", ...params.exclude_domains);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const envPaths = [
|
|
167
|
-
path.join(PLUGIN_DIR, ".env"),
|
|
168
|
-
path.join(PLUGIN_DIR, "..", "web-search-plus", ".env"),
|
|
169
|
-
];
|
|
170
|
-
const fileEnv: Record<string, string> = {};
|
|
171
|
-
for (const envPath of envPaths) {
|
|
172
|
-
Object.assign(fileEnv, loadEnvFile(envPath));
|
|
173
|
-
}
|
|
174
|
-
const childEnv = { ...process.env, ...configEnv, ...fileEnv };
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const child = spawnSync("python3", args, {
|
|
178
|
-
timeout: 75000,
|
|
179
|
-
env: childEnv,
|
|
180
|
-
shell: false,
|
|
181
|
-
encoding: "utf8",
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
if (child.error) {
|
|
185
|
-
return {
|
|
186
|
-
content: [
|
|
187
|
-
{ type: "text", text: `Search failed: ${child.error.message}` },
|
|
188
|
-
],
|
|
189
|
-
};
|
|
129
|
+
}),
|
|
130
|
+
async execute(
|
|
131
|
+
_id: string,
|
|
132
|
+
params: {
|
|
133
|
+
query: string;
|
|
134
|
+
provider?: string;
|
|
135
|
+
count?: number;
|
|
136
|
+
depth?: string;
|
|
137
|
+
time_range?: string;
|
|
138
|
+
include_domains?: string[];
|
|
139
|
+
exclude_domains?: string[];
|
|
140
|
+
},
|
|
141
|
+
) {
|
|
142
|
+
const args = [scriptPath, "--query", params.query, "--compact"];
|
|
143
|
+
|
|
144
|
+
if (params.provider && params.provider !== "auto") {
|
|
145
|
+
args.push("--provider", params.provider);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (typeof params.count === "number" && Number.isFinite(params.count)) {
|
|
149
|
+
args.push(
|
|
150
|
+
"--max-results",
|
|
151
|
+
String(Math.max(1, Math.floor(params.count))),
|
|
152
|
+
);
|
|
190
153
|
}
|
|
191
154
|
|
|
192
|
-
if (
|
|
193
|
-
|
|
155
|
+
if (params.depth && params.depth !== "normal") {
|
|
156
|
+
args.push("--exa-depth", params.depth);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (params.time_range) {
|
|
160
|
+
args.push("--time-range", params.time_range);
|
|
161
|
+
args.push("--freshness", params.time_range);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (params.include_domains?.length) {
|
|
165
|
+
args.push("--include-domains", ...params.include_domains);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (params.exclude_domains?.length) {
|
|
169
|
+
args.push("--exclude-domains", ...params.exclude_domains);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const envPaths = [
|
|
173
|
+
path.join(PLUGIN_DIR, ".env"),
|
|
174
|
+
path.join(PLUGIN_DIR, "..", "web-search-plus", ".env"),
|
|
175
|
+
];
|
|
176
|
+
const fileEnv: Record<string, string> = {};
|
|
177
|
+
for (const envPath of envPaths) {
|
|
178
|
+
Object.assign(fileEnv, loadEnvFile(envPath));
|
|
179
|
+
}
|
|
180
|
+
const childEnv = { ...process.env, ...configEnv, ...fileEnv };
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const child = spawnSync("python3", args, {
|
|
184
|
+
timeout: 75000,
|
|
185
|
+
env: childEnv,
|
|
186
|
+
shell: false,
|
|
187
|
+
encoding: "utf8",
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (child.error) {
|
|
191
|
+
return {
|
|
192
|
+
content: [
|
|
193
|
+
{ type: "text", text: `Search failed: ${child.error.message}` },
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (child.status !== 0) {
|
|
199
|
+
const stderr = child.stderr?.trim() || "Unknown error";
|
|
200
|
+
return {
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: "text",
|
|
204
|
+
text: `Search failed (exit ${child.status}): ${stderr}`,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: "text", text: child.stdout?.trim() || "{}" }],
|
|
212
|
+
};
|
|
213
|
+
} catch (err: any) {
|
|
194
214
|
return {
|
|
195
215
|
content: [
|
|
196
|
-
{
|
|
197
|
-
type: "text",
|
|
198
|
-
text: `Search failed (exit ${child.status}): ${stderr}`,
|
|
199
|
-
},
|
|
216
|
+
{ type: "text", text: `Search failed: ${err?.message ?? err}` },
|
|
200
217
|
],
|
|
201
218
|
};
|
|
202
219
|
}
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
content: [{ type: "text", text: child.stdout?.trim() || "{}" }],
|
|
206
|
-
};
|
|
207
|
-
} catch (err: any) {
|
|
208
|
-
return {
|
|
209
|
-
content: [
|
|
210
|
-
{ type: "text", text: `Search failed: ${err?.message ?? err}` },
|
|
211
|
-
],
|
|
212
|
-
};
|
|
213
|
-
}
|
|
220
|
+
},
|
|
214
221
|
},
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
222
|
+
{ optional: true },
|
|
223
|
+
);
|
|
224
|
+
},
|
|
225
|
+
});
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "web-search-plus-plugin",
|
|
3
|
-
"kind": "skill",
|
|
4
3
|
"name": "Web Search Plus",
|
|
5
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.1",
|
|
6
5
|
"description": "Multi-provider web search (Serper/Google, Tavily, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG) with intelligent auto-routing",
|
|
7
6
|
"configSchema": {
|
|
8
7
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web-search-plus-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
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",
|
|
7
|
+
"openclaw": {
|
|
8
|
+
"extensions": ["./index.ts"]
|
|
9
|
+
},
|
|
7
10
|
"files": [
|
|
8
11
|
"index.ts",
|
|
9
12
|
"openclaw.plugin.json",
|
|
@@ -13,6 +16,9 @@
|
|
|
13
16
|
"LICENSE"
|
|
14
17
|
],
|
|
15
18
|
"keywords": ["openclaw", "plugin", "search", "serper", "tavily", "querit", "exa", "exa-deep", "perplexity", "you", "searxng", "web-search", "auto-routing"],
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"openclaw": ">=2026.3.22"
|
|
21
|
+
},
|
|
16
22
|
"repository": {
|
|
17
23
|
"type": "git",
|
|
18
24
|
"url": "https://github.com/robbyczgw-cla/web-search-plus-plugin"
|