@tianshu-ai/tianshu 0.4.21 → 0.4.23
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/package.json +1 -1
- package/packages/server/builtinConfig/plugins/web-search/manifest.json +5 -53
- package/packages/server/builtinConfig/plugins/web-search/skills/web-search-howto.md +4 -7
- package/packages/web/dist/assets/{bundle-web-hHh7gz2L.js → bundle-web-B7eyKI4Y.js} +1 -1
- package/packages/web/dist/assets/index-D97NmBlG.css +2 -0
- package/packages/web/dist/assets/{index-B6oq6ed_.js → index-GoKNQUL6.js} +4 -4
- package/packages/web/dist/index.html +2 -2
- package/plugins/web-search/dist/server.d.ts.map +1 -1
- package/plugins/web-search/dist/server.js +18 -56
- package/plugins/web-search/dist/server.js.map +1 -1
- package/plugins/web-search/dist/tools/index.d.ts +2 -2
- package/plugins/web-search/dist/tools/index.d.ts.map +1 -1
- package/plugins/web-search/dist/tools/index.js +1 -1
- package/plugins/web-search/dist/tools/index.js.map +1 -1
- package/plugins/web-search/dist/tools/providers.d.ts +15 -20
- package/plugins/web-search/dist/tools/providers.d.ts.map +1 -1
- package/plugins/web-search/dist/tools/providers.js +24 -248
- package/plugins/web-search/dist/tools/providers.js.map +1 -1
- package/plugins/web-search/dist/tools/web-search.d.ts +4 -25
- package/plugins/web-search/dist/tools/web-search.d.ts.map +1 -1
- package/plugins/web-search/dist/tools/web-search.js +48 -203
- package/plugins/web-search/dist/tools/web-search.js.map +1 -1
- package/plugins/web-search/manifest.json +5 -53
- package/plugins/web-search/package.json +1 -1
- package/plugins/web-search/skills/web-search-howto.md +4 -7
- package/packages/web/dist/assets/index-DWOCI_kD.css +0 -2
|
@@ -1,34 +1,13 @@
|
|
|
1
1
|
import type { AgentTool } from "@tianshu-ai/plugin-sdk";
|
|
2
|
+
import { type HostedBackend } from "./providers.js";
|
|
2
3
|
import { ProviderHealth } from "./health.js";
|
|
3
|
-
/** Which search scheme the operator picked. The plugin supports
|
|
4
|
-
* two key-free schemes (hosted MCP + self-hosted SearXNG) and two
|
|
5
|
-
* key-based ones (Tavily + Brave) kept from the original plugin. */
|
|
6
|
-
export type SearchScheme = "hosted" | "searxng" | "tavily" | "brave";
|
|
7
4
|
export interface WebSearchPluginConfig {
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
/** Backend for the hosted MCP scheme. */
|
|
11
|
-
hostedBackend?: "exa" | "parallel";
|
|
12
|
-
/** Optional key for the hosted backend (higher limits; not
|
|
13
|
-
* required — the free tier works anonymously). */
|
|
14
|
-
exaApiKey?: string;
|
|
15
|
-
parallelApiKey?: string;
|
|
16
|
-
/** Base URL of a self-hosted SearXNG instance. */
|
|
17
|
-
searxngBaseUrl?: string;
|
|
18
|
-
tavilyApiKey?: string;
|
|
19
|
-
braveApiKey?: string;
|
|
5
|
+
/** Which hosted MCP endpoint to query. Default "exa". */
|
|
6
|
+
backend?: HostedBackend;
|
|
20
7
|
/** Optional default timeout override (ms). 8000 if unset. */
|
|
21
8
|
timeoutMs?: number;
|
|
22
9
|
}
|
|
23
10
|
export declare function buildWebSearchTool(cfg: WebSearchPluginConfig, health: ProviderHealth): AgentTool;
|
|
24
|
-
/** Read raw `pluginConfig`
|
|
25
|
-
* shape this plugin actually uses. Plugin's `activate()` calls
|
|
26
|
-
* this once at startup. */
|
|
11
|
+
/** Read raw `pluginConfig` into the typed shape. */
|
|
27
12
|
export declare function readWebSearchConfig(raw: unknown): WebSearchPluginConfig;
|
|
28
|
-
/** Resolve the effective scheme. Explicit `scheme` wins; otherwise
|
|
29
|
-
* infer from what's configured, preferring the key-free hosted
|
|
30
|
-
* scheme so a fresh install works out of the box. */
|
|
31
|
-
export declare function effectiveScheme(cfg: WebSearchPluginConfig): SearchScheme;
|
|
32
|
-
/** True when the effective scheme has everything it needs to run. */
|
|
33
|
-
export declare function isConfigured(cfg: WebSearchPluginConfig): boolean;
|
|
34
13
|
//# sourceMappingURL=web-search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-search.d.ts","sourceRoot":"","sources":["../../src/tools/web-search.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"web-search.d.ts","sourceRoot":"","sources":["../../src/tools/web-search.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAoB,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAEL,KAAK,aAAa,EAGnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,qBAAqB,EAC1B,MAAM,EAAE,cAAc,GACrB,SAAS,CA0GX;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,CAWvE"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// `web_search(query, count?, language?, freshness
|
|
1
|
+
// `web_search(query, count?, language?, freshness?)`.
|
|
2
2
|
//
|
|
3
|
-
// One
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// host re-activates the plugin when config changes, so the keys
|
|
7
|
-
// stay current without per-call config plumbing.
|
|
3
|
+
// One key-free backend: the hosted MCP endpoints. The operator's
|
|
4
|
+
// only choice is which one — Exa or Parallel (config `backend`).
|
|
5
|
+
// No API keys, no self-hosting, no scheme selection.
|
|
8
6
|
//
|
|
9
7
|
// Tenant config shape:
|
|
10
8
|
//
|
|
@@ -12,33 +10,26 @@
|
|
|
12
10
|
// "plugins": {
|
|
13
11
|
// "web-search": {
|
|
14
12
|
// "config": {
|
|
15
|
-
// "
|
|
16
|
-
// "
|
|
17
|
-
// "preferredProvider": "tavily" | "brave" // optional
|
|
13
|
+
// "backend": "exa" | "parallel", // default "exa"
|
|
14
|
+
// "timeoutMs": 8000 // optional
|
|
18
15
|
// }
|
|
19
16
|
// }
|
|
20
17
|
// }
|
|
21
18
|
// }
|
|
22
|
-
//
|
|
23
|
-
// If both keys are configured we prefer `preferredProvider` (or
|
|
24
|
-
// "tavily" by default) and fall back to the other one ONLY when
|
|
25
|
-
// the first returns a clearly transient error (network reset, 5xx,
|
|
26
|
-
// 429, or zero results when both providers are available). Bad
|
|
27
|
-
// keys (401/403) don't trigger fallback — that's a config problem
|
|
28
|
-
// the user needs to fix, not a runtime hiccup.
|
|
29
19
|
import { Type } from "typebox";
|
|
30
|
-
import {
|
|
20
|
+
import { hostedProvider, } from "./providers.js";
|
|
31
21
|
export function buildWebSearchTool(cfg, health) {
|
|
22
|
+
const backend = cfg.backend ?? "exa";
|
|
32
23
|
return {
|
|
33
24
|
schema: {
|
|
34
25
|
name: "web_search",
|
|
35
26
|
description: "Search the web for up-to-date information. Returns a JSON " +
|
|
36
|
-
"array of `{ title, url, content, publishedDate }` objects
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
27
|
+
"array of `{ title, url, content, publishedDate }` objects. " +
|
|
28
|
+
"Key-free \u2014 backed by a hosted search endpoint (Exa or " +
|
|
29
|
+
"Parallel, chosen by the host). Use this for current events, " +
|
|
30
|
+
"recent docs, anything that may have changed since training. " +
|
|
31
|
+
"Don't loop many tiny searches \u2014 refine the query and " +
|
|
32
|
+
"increase `count`. Use web_fetch to read a result's full page.",
|
|
42
33
|
parameters: Type.Object({
|
|
43
34
|
query: Type.String({
|
|
44
35
|
description: "Free-text search query, 1-300 chars.",
|
|
@@ -49,8 +40,7 @@ export function buildWebSearchTool(cfg, health) {
|
|
|
49
40
|
maximum: 20,
|
|
50
41
|
})),
|
|
51
42
|
language: Type.Optional(Type.String({
|
|
52
|
-
description: "Language hint, ISO 639-1 or locale (e.g. \"zh-CN\", \"en\").
|
|
53
|
-
"Only applied by providers that accept it.",
|
|
43
|
+
description: "Language hint, ISO 639-1 or locale (e.g. \"zh-CN\", \"en\").",
|
|
54
44
|
})),
|
|
55
45
|
freshness: Type.Optional(Type.Union([
|
|
56
46
|
Type.Literal("day"),
|
|
@@ -58,20 +48,13 @@ export function buildWebSearchTool(cfg, health) {
|
|
|
58
48
|
Type.Literal("month"),
|
|
59
49
|
Type.Literal("year"),
|
|
60
50
|
], {
|
|
61
|
-
description: "Recency filter. Default is no filter (
|
|
62
|
-
})),
|
|
63
|
-
provider: Type.Optional(Type.Union([
|
|
64
|
-
Type.Literal("hosted"),
|
|
65
|
-
Type.Literal("searxng"),
|
|
66
|
-
Type.Literal("tavily"),
|
|
67
|
-
Type.Literal("brave"),
|
|
68
|
-
], {
|
|
69
|
-
description: "Force a specific provider. Default uses the scheme the host configured.",
|
|
51
|
+
description: "Recency filter. Default is no filter (ranked by relevance).",
|
|
70
52
|
})),
|
|
71
53
|
}),
|
|
72
54
|
},
|
|
73
55
|
available(_ctx) {
|
|
74
|
-
|
|
56
|
+
// Always available — the hosted backend needs no credential.
|
|
57
|
+
return true;
|
|
75
58
|
},
|
|
76
59
|
async execute(rawArgs, _ctx) {
|
|
77
60
|
const args = rawArgs;
|
|
@@ -85,43 +68,18 @@ export function buildWebSearchTool(cfg, health) {
|
|
|
85
68
|
text: "query too long (max 300 chars). Trim or split into separate searches.",
|
|
86
69
|
};
|
|
87
70
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"Search and pick a scheme: \"hosted\" (key-free, Exa/Parallel), " +
|
|
94
|
-
"\"searxng\" (set your instance URL), or \"tavily\"/\"brave\" " +
|
|
95
|
-
"(set an API key).",
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
// Apply health cache: skip providers we already know are
|
|
99
|
-
// dead (bad key). The agent gets to see the cached reason
|
|
100
|
-
// in the error trail, so a stale cache shows up loudly.
|
|
101
|
-
const skipped = [];
|
|
102
|
-
const order = fullOrder.filter(({ provider }) => {
|
|
103
|
-
const dead = health.isDead(provider.name);
|
|
104
|
-
if (dead) {
|
|
105
|
-
skipped.push({
|
|
106
|
-
provider: provider.name,
|
|
107
|
-
status: dead.status,
|
|
108
|
-
message: `cached as dead since ${new Date(dead.deadSince).toISOString()}: ${dead.message}`,
|
|
109
|
-
});
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
return true;
|
|
113
|
-
});
|
|
114
|
-
if (order.length === 0) {
|
|
71
|
+
// Health cache: skip the backend if a prior auth failure
|
|
72
|
+
// parked it (rare for the free endpoints, but the trail is
|
|
73
|
+
// useful if it ever happens).
|
|
74
|
+
const dead = health.isDead("hosted");
|
|
75
|
+
if (dead) {
|
|
115
76
|
return {
|
|
116
77
|
ok: false,
|
|
117
|
-
text: `web_search:
|
|
118
|
-
`
|
|
119
|
-
`Settings
|
|
120
|
-
`/api/p/web-search/health/reset
|
|
121
|
-
|
|
122
|
-
.map((s) => `- ${s.provider} (status ${s.status}): ${s.message}`)
|
|
123
|
-
.join("\n"),
|
|
124
|
-
data: { skipped },
|
|
78
|
+
text: `web_search: the hosted backend is in the dead-cache ` +
|
|
79
|
+
`(status ${dead.status}: ${dead.message}). Reset it in ` +
|
|
80
|
+
`Settings \u2192 Plugins \u2192 Web Search or POST ` +
|
|
81
|
+
`/api/p/web-search/health/reset.`,
|
|
82
|
+
data: { dead },
|
|
125
83
|
};
|
|
126
84
|
}
|
|
127
85
|
const opts = {
|
|
@@ -130,148 +88,36 @@ export function buildWebSearchTool(cfg, health) {
|
|
|
130
88
|
freshness: args.freshness,
|
|
131
89
|
timeoutMs: cfg.timeoutMs ?? 8000,
|
|
132
90
|
};
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// return nothing on Brave but plenty on Tavily.
|
|
141
|
-
errors.push({
|
|
142
|
-
provider: provider.name,
|
|
143
|
-
status: 200,
|
|
144
|
-
message: "zero results",
|
|
145
|
-
});
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
return formatSuccess(provider.name, query, out.results, errors);
|
|
149
|
-
}
|
|
150
|
-
const e = out.error;
|
|
151
|
-
errors.push(e);
|
|
152
|
-
// Auth errors: park the provider in the health cache so
|
|
153
|
-
// future calls skip it without paying the round-trip.
|
|
154
|
-
// Only auth gets cached — 5xx / network / 429 are usually
|
|
155
|
-
// transient and would bake a bad cache entry.
|
|
156
|
-
if (e.status === 401 || e.status === 403) {
|
|
157
|
-
health.markDead(provider.name, e.status, e.message);
|
|
158
|
-
// Don't fall back on auth errors — the operator needs to
|
|
159
|
-
// fix the key. (Falling back to a probably-also-broken
|
|
160
|
-
// sibling key just papers over the real problem.)
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
91
|
+
const out = await hostedProvider.search(query, backend, opts);
|
|
92
|
+
if ("results" in out) {
|
|
93
|
+
return formatSuccess(backend, query, out.results);
|
|
94
|
+
}
|
|
95
|
+
const e = out.error;
|
|
96
|
+
if (e.status === 401 || e.status === 403) {
|
|
97
|
+
health.markDead("hosted", e.status, e.message);
|
|
163
98
|
}
|
|
164
99
|
return {
|
|
165
100
|
ok: false,
|
|
166
|
-
text: formatFailure(query,
|
|
167
|
-
data: { query,
|
|
101
|
+
text: formatFailure(query, e),
|
|
102
|
+
data: { query, error: e },
|
|
168
103
|
};
|
|
169
104
|
},
|
|
170
105
|
};
|
|
171
106
|
}
|
|
172
|
-
/** Read raw `pluginConfig`
|
|
173
|
-
* shape this plugin actually uses. Plugin's `activate()` calls
|
|
174
|
-
* this once at startup. */
|
|
107
|
+
/** Read raw `pluginConfig` into the typed shape. */
|
|
175
108
|
export function readWebSearchConfig(raw) {
|
|
176
109
|
if (!raw || typeof raw !== "object")
|
|
177
110
|
return {};
|
|
178
111
|
const r = raw;
|
|
179
|
-
const str = (k) => typeof r[k] === "string" && r[k] ? r[k] : undefined;
|
|
180
|
-
const scheme = r.scheme === "hosted" ||
|
|
181
|
-
r.scheme === "searxng" ||
|
|
182
|
-
r.scheme === "tavily" ||
|
|
183
|
-
r.scheme === "brave"
|
|
184
|
-
? r.scheme
|
|
185
|
-
: undefined;
|
|
186
112
|
return {
|
|
187
|
-
|
|
188
|
-
hostedBackend: r.hostedBackend === "parallel" || r.hostedBackend === "exa"
|
|
189
|
-
? r.hostedBackend
|
|
190
|
-
: undefined,
|
|
191
|
-
exaApiKey: str("exaApiKey"),
|
|
192
|
-
parallelApiKey: str("parallelApiKey"),
|
|
193
|
-
searxngBaseUrl: str("searxngBaseUrl"),
|
|
194
|
-
tavilyApiKey: str("tavilyApiKey"),
|
|
195
|
-
braveApiKey: str("braveApiKey"),
|
|
113
|
+
backend: r.backend === "parallel" || r.backend === "exa" ? r.backend : undefined,
|
|
196
114
|
timeoutMs: typeof r.timeoutMs === "number" && Number.isFinite(r.timeoutMs)
|
|
197
115
|
? r.timeoutMs
|
|
198
116
|
: undefined,
|
|
199
117
|
};
|
|
200
118
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
* scheme so a fresh install works out of the box. */
|
|
204
|
-
export function effectiveScheme(cfg) {
|
|
205
|
-
if (cfg.scheme)
|
|
206
|
-
return cfg.scheme;
|
|
207
|
-
if (cfg.searxngBaseUrl)
|
|
208
|
-
return "searxng";
|
|
209
|
-
if (cfg.tavilyApiKey)
|
|
210
|
-
return "tavily";
|
|
211
|
-
if (cfg.braveApiKey)
|
|
212
|
-
return "brave";
|
|
213
|
-
return "hosted"; // key-free default
|
|
214
|
-
}
|
|
215
|
-
/** True when the effective scheme has everything it needs to run. */
|
|
216
|
-
export function isConfigured(cfg) {
|
|
217
|
-
switch (effectiveScheme(cfg)) {
|
|
218
|
-
case "hosted":
|
|
219
|
-
return true; // key-free
|
|
220
|
-
case "searxng":
|
|
221
|
-
return Boolean(cfg.searxngBaseUrl);
|
|
222
|
-
case "tavily":
|
|
223
|
-
return Boolean(cfg.tavilyApiKey);
|
|
224
|
-
case "brave":
|
|
225
|
-
return Boolean(cfg.braveApiKey);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/** Pack the hosted provider's opaque `key` blob (backend + optional
|
|
229
|
-
* api key). See hostedProvider in providers.ts. */
|
|
230
|
-
function hostedKey(cfg) {
|
|
231
|
-
const backend = cfg.hostedBackend ?? "exa";
|
|
232
|
-
const apiKey = backend === "parallel" ? cfg.parallelApiKey : cfg.exaApiKey;
|
|
233
|
-
return JSON.stringify({ backend, apiKey });
|
|
234
|
-
}
|
|
235
|
-
/** Decide which providers to try, in order. Returns at most two
|
|
236
|
-
* entries (the configured pair, preferred first). */
|
|
237
|
-
function providerOrder(cfg, override) {
|
|
238
|
-
// Build the entry for a single scheme, or null when that scheme
|
|
239
|
-
// isn't usable (missing credential).
|
|
240
|
-
const entryFor = (scheme) => {
|
|
241
|
-
switch (scheme) {
|
|
242
|
-
case "hosted":
|
|
243
|
-
return { provider: hostedProvider, key: hostedKey(cfg) };
|
|
244
|
-
case "searxng":
|
|
245
|
-
return cfg.searxngBaseUrl
|
|
246
|
-
? { provider: searxngProvider, key: cfg.searxngBaseUrl }
|
|
247
|
-
: null;
|
|
248
|
-
case "tavily":
|
|
249
|
-
return cfg.tavilyApiKey
|
|
250
|
-
? { provider: tavilyProvider, key: cfg.tavilyApiKey }
|
|
251
|
-
: null;
|
|
252
|
-
case "brave":
|
|
253
|
-
return cfg.braveApiKey
|
|
254
|
-
? { provider: braveProvider, key: cfg.braveApiKey }
|
|
255
|
-
: null;
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
// Explicit per-call override: honour it if usable, else fall
|
|
259
|
-
// through to the configured scheme.
|
|
260
|
-
if (override) {
|
|
261
|
-
const e = entryFor(override);
|
|
262
|
-
if (e)
|
|
263
|
-
return [e];
|
|
264
|
-
}
|
|
265
|
-
// No cross-scheme fallback: the operator picked a scheme, we use
|
|
266
|
-
// it. (Mixing a self-hosted SearXNG with a paid Tavily fallback
|
|
267
|
-
// would be surprising.) Return the single effective scheme.
|
|
268
|
-
const e = entryFor(effectiveScheme(cfg));
|
|
269
|
-
return e ? [e] : [];
|
|
270
|
-
}
|
|
271
|
-
function formatSuccess(provider, query, results, priorErrors) {
|
|
272
|
-
const head = priorErrors.length === 0
|
|
273
|
-
? `${results.length} result${results.length === 1 ? "" : "s"} from ${provider} for "${query}":`
|
|
274
|
-
: `${results.length} result${results.length === 1 ? "" : "s"} from ${provider} (after ${priorErrors.length} fallback attempt${priorErrors.length === 1 ? "" : "s"}) for "${query}":`;
|
|
119
|
+
function formatSuccess(backend, query, results) {
|
|
120
|
+
const head = `${results.length} result${results.length === 1 ? "" : "s"} from ${backend} for "${query}":`;
|
|
275
121
|
const lines = results.map((r, i) => {
|
|
276
122
|
const date = r.publishedDate ? ` (${r.publishedDate})` : "";
|
|
277
123
|
const snippet = (r.content ?? "").trim().slice(0, 280);
|
|
@@ -280,18 +126,17 @@ function formatSuccess(provider, query, results, priorErrors) {
|
|
|
280
126
|
return {
|
|
281
127
|
ok: true,
|
|
282
128
|
text: [head, "", ...lines].join("\n"),
|
|
283
|
-
data: { query, provider, results },
|
|
129
|
+
data: { query, provider: backend, results },
|
|
284
130
|
};
|
|
285
131
|
}
|
|
286
|
-
function formatFailure(query,
|
|
287
|
-
const lines = errors.map((e) => `- ${e.provider} (status ${e.status}): ${e.message}`);
|
|
132
|
+
function formatFailure(query, error) {
|
|
288
133
|
return [
|
|
289
|
-
`web_search failed for "${query}"
|
|
290
|
-
|
|
134
|
+
`web_search failed for "${query}":`,
|
|
135
|
+
`- ${error.provider} (status ${error.status}): ${error.message}`,
|
|
291
136
|
"",
|
|
292
|
-
"
|
|
293
|
-
"
|
|
294
|
-
"
|
|
137
|
+
"The hosted endpoint may be rate-limiting or temporarily down. " +
|
|
138
|
+
"Retry shortly, or switch the backend (Exa \u2194 Parallel) in " +
|
|
139
|
+
"Settings \u2192 Plugins \u2192 Web Search.",
|
|
295
140
|
].join("\n");
|
|
296
141
|
}
|
|
297
142
|
//# sourceMappingURL=web-search.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-search.js","sourceRoot":"","sources":["../../src/tools/web-search.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"web-search.js","sourceRoot":"","sources":["../../src/tools/web-search.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "web-search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"displayName": "Web Search",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Key-free web search + single-page fetch for agents. Search runs against a hosted endpoint (Exa or Parallel) with no API key. web_fetch reads a page to markdown, also no config.",
|
|
6
6
|
"author": "tianshu-ai",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"permissions": ["network.external"],
|
|
@@ -26,63 +26,15 @@
|
|
|
26
26
|
"configSchema": {
|
|
27
27
|
"fields": [
|
|
28
28
|
{
|
|
29
|
-
"key": "
|
|
30
|
-
"label": "Search
|
|
31
|
-
"kind": "select",
|
|
32
|
-
"default": "hosted",
|
|
33
|
-
"options": [
|
|
34
|
-
{ "label": "Hosted MCP (key-free: Exa / Parallel)", "value": "hosted" },
|
|
35
|
-
{ "label": "SearXNG (self-hosted)", "value": "searxng" },
|
|
36
|
-
{ "label": "Tavily (API key)", "value": "tavily" },
|
|
37
|
-
{ "label": "Brave (API key)", "value": "brave" }
|
|
38
|
-
],
|
|
39
|
-
"description": "Which backend web_search uses. \"hosted\" needs no key (uses Exa/Parallel free MCP endpoints). \"searxng\" needs a base URL below. \"tavily\"/\"brave\" need the matching API key. web_fetch always works regardless of this setting."
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"key": "hostedBackend",
|
|
43
|
-
"label": "Hosted backend",
|
|
29
|
+
"key": "backend",
|
|
30
|
+
"label": "Search backend",
|
|
44
31
|
"kind": "select",
|
|
45
32
|
"default": "exa",
|
|
46
33
|
"options": [
|
|
47
34
|
{ "label": "Exa", "value": "exa" },
|
|
48
35
|
{ "label": "Parallel", "value": "parallel" }
|
|
49
36
|
],
|
|
50
|
-
"description": "
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"key": "searxngBaseUrl",
|
|
54
|
-
"label": "SearXNG base URL",
|
|
55
|
-
"kind": "string",
|
|
56
|
-
"placeholder": "https://searx.example.com",
|
|
57
|
-
"description": "Only used when scheme = searxng. Base URL of your SearXNG instance (its JSON API must be enabled: `search.formats: [json]` in settings.yml). No API key needed."
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"key": "exaApiKey",
|
|
61
|
-
"label": "Exa API key (optional)",
|
|
62
|
-
"kind": "secret",
|
|
63
|
-
"placeholder": "exa-...",
|
|
64
|
-
"description": "Optional. Only used when scheme = hosted and backend = Exa. Raises limits; the free endpoint works without it. Stored under <tenant>/secrets/, never returned to the browser."
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
"key": "parallelApiKey",
|
|
68
|
-
"label": "Parallel API key (optional)",
|
|
69
|
-
"kind": "secret",
|
|
70
|
-
"placeholder": "...",
|
|
71
|
-
"description": "Optional. Only used when scheme = hosted and backend = Parallel. Raises limits; the free endpoint works without it."
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"key": "tavilyApiKey",
|
|
75
|
-
"label": "Tavily API key",
|
|
76
|
-
"kind": "secret",
|
|
77
|
-
"placeholder": "tvly-...",
|
|
78
|
-
"description": "Only used when scheme = tavily. Sign up at tavily.com (free tier 1000 searches/month). Stored under <tenant>/secrets/, never returned to the browser."
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"key": "braveApiKey",
|
|
82
|
-
"label": "Brave Search API key",
|
|
83
|
-
"kind": "secret",
|
|
84
|
-
"placeholder": "BSA...",
|
|
85
|
-
"description": "Only used when scheme = brave. Get a free key at api-dashboard.search.brave.com (2000/month)."
|
|
37
|
+
"description": "Which hosted endpoint web_search queries. Both are key-free (anonymous free tier). Exa returns highlighted excerpts; Parallel returns ranked results. web_fetch is unaffected."
|
|
86
38
|
},
|
|
87
39
|
{
|
|
88
40
|
"key": "timeoutMs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tianshu-builtin/plugin-web-search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Web search + fetch for tianshu agents. Schemes: key-free hosted MCP (Exa/Parallel), self-hosted SearXNG, or keyed Tavily/Brave.",
|
|
6
6
|
"type": "module",
|
|
@@ -9,10 +9,9 @@ scope: worker
|
|
|
9
9
|
This plugin gives you two tools:
|
|
10
10
|
|
|
11
11
|
- **`web_search`** — returns a JSON array of `{title, url,
|
|
12
|
-
content, publishedDate}` results.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
shape regardless.
|
|
12
|
+
content, publishedDate}` results. Key-free: runs against a
|
|
13
|
+
hosted endpoint (Exa or Parallel; the host picks which). You
|
|
14
|
+
don't configure or choose anything at call time.
|
|
16
15
|
- **`web_fetch`** — fetches ONE URL and returns its readable
|
|
17
16
|
body as markdown or text. No API key, no JavaScript. Use it
|
|
18
17
|
to read a page you got from a search result.
|
|
@@ -43,9 +42,7 @@ snippet is insufficient.
|
|
|
43
42
|
Biases engines toward language-specific sources. Skip this
|
|
44
43
|
for technical English-language queries; technical content is
|
|
45
44
|
usually English regardless of locale.
|
|
46
|
-
|
|
47
|
-
`"brave"` if you want a specific backend. Default is the
|
|
48
|
-
scheme the host configured. Usually leave it unset.
|
|
45
|
+
|
|
49
46
|
|
|
50
47
|
## Anti-patterns
|
|
51
48
|
|