@qzhike/agent-search 1.0.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/LICENSE +21 -0
- package/README.md +123 -0
- package/dist/adapters/brave-config.js +46 -0
- package/dist/adapters/brave.js +83 -0
- package/dist/adapters/deeproute-mirror.js +31 -0
- package/dist/adapters/exa-config.js +35 -0
- package/dist/adapters/exa.js +91 -0
- package/dist/adapters/jina-config.js +102 -0
- package/dist/adapters/jina-reader.js +83 -0
- package/dist/adapters/jina.js +90 -0
- package/dist/adapters/kimi.js +196 -0
- package/dist/adapters/metaso-config.js +35 -0
- package/dist/adapters/metaso.js +102 -0
- package/dist/adapters/minimax-config.js +54 -0
- package/dist/adapters/minimax.js +85 -0
- package/dist/adapters/provider-auth.js +18 -0
- package/dist/adapters/ragflow-config.js +63 -0
- package/dist/adapters/ragflow.js +170 -0
- package/dist/adapters/searxng-config.js +40 -0
- package/dist/adapters/searxng.js +86 -0
- package/dist/adapters/serper-config.js +35 -0
- package/dist/adapters/serper.js +80 -0
- package/dist/adapters/tavily-config.js +46 -0
- package/dist/adapters/tavily.js +98 -0
- package/dist/adapters/zai-config.js +39 -0
- package/dist/adapters/zai.js +91 -0
- package/dist/config-schema.js +221 -0
- package/dist/http.js +49 -0
- package/dist/index.js +84 -0
- package/dist/plugin-meta.js +11 -0
- package/dist/read-url.js +38 -0
- package/dist/result.js +326 -0
- package/dist/run-provider.js +42 -0
- package/dist/search-concurrent.js +23 -0
- package/dist/search-fallback.js +14 -0
- package/dist/search.js +43 -0
- package/dist/types.js +2 -0
- package/openclaw.plugin.json +547 -0
- package/package.json +25 -0
- package/skills/qzhike-agent-search/SKILL.md +90 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { configString } from "../http.js";
|
|
2
|
+
import { formatResultsAsContent } from "../result.js";
|
|
3
|
+
import { buildRagflowRequestHeaders, parseRagflowDatasetIds, resolveRagflowAuthMode, resolveRagflowBaseUrl, resolveRagflowRetrievalUrl, resolveRagflowTimeoutSeconds, } from "./ragflow-config.js";
|
|
4
|
+
const TRANSIENT_HTTP = new Set([429, 500, 502, 503, 504]);
|
|
5
|
+
const MAX_ATTEMPTS = 3;
|
|
6
|
+
export async function searchRagflow(config, query, count, signal) {
|
|
7
|
+
const provider = "ragflow";
|
|
8
|
+
const cfg = config.providers?.ragflow;
|
|
9
|
+
const auth = resolveRagflowAuthMode(cfg?.auth);
|
|
10
|
+
if (!auth) {
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
provider,
|
|
14
|
+
error: "invalid_auth",
|
|
15
|
+
message: 'Invalid providers.ragflow.auth. Use "direct" (RAGFlow API) or "deeproute" (Console mirror).',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const apiKey = configString(cfg?.apiKey);
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
provider,
|
|
23
|
+
error: "missing_api_key",
|
|
24
|
+
message: auth === "deeproute"
|
|
25
|
+
? "deeproute: set providers.ragflow.apiKey to your New API user sk (Bearer)."
|
|
26
|
+
: "direct: set providers.ragflow.apiKey to your RAGFlow API key.",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const baseUrl = resolveRagflowBaseUrl(cfg, auth);
|
|
30
|
+
if (!baseUrl) {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
provider,
|
|
34
|
+
error: "missing_base_url",
|
|
35
|
+
message: auth === "deeproute"
|
|
36
|
+
? "deeproute: set providers.ragflow.baseUrl to Console origin."
|
|
37
|
+
: "direct: set providers.ragflow.baseUrl to RAGFlow service root URL.",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const datasetIds = auth === "direct" ? parseRagflowDatasetIds(cfg?.datasetIds) : [];
|
|
41
|
+
if (auth === "direct" && datasetIds.length === 0) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
provider,
|
|
45
|
+
error: "missing_dataset_ids",
|
|
46
|
+
message: "direct: set providers.ragflow.datasetIds to comma-separated RAGFlow dataset IDs.",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const body = {
|
|
50
|
+
question: query,
|
|
51
|
+
page: 1,
|
|
52
|
+
page_size: cfg?.pageSize ?? 30,
|
|
53
|
+
similarity_threshold: cfg?.similarityThreshold ?? 0.2,
|
|
54
|
+
};
|
|
55
|
+
if (auth === "direct") {
|
|
56
|
+
body.dataset_ids = datasetIds;
|
|
57
|
+
}
|
|
58
|
+
if (typeof cfg?.topK === "number" && Number.isFinite(cfg.topK)) {
|
|
59
|
+
body.top_k = Math.floor(cfg.topK);
|
|
60
|
+
}
|
|
61
|
+
const url = resolveRagflowRetrievalUrl(baseUrl, auth);
|
|
62
|
+
const timeoutSeconds = resolveRagflowTimeoutSeconds(cfg);
|
|
63
|
+
const startedAt = Date.now();
|
|
64
|
+
try {
|
|
65
|
+
const data = await fetchRagflowWithRetry(url, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: buildRagflowRequestHeaders(auth, apiKey),
|
|
68
|
+
body: JSON.stringify(body),
|
|
69
|
+
timeoutSeconds,
|
|
70
|
+
signal,
|
|
71
|
+
});
|
|
72
|
+
const bizCode = data.code;
|
|
73
|
+
if (bizCode !== 0) {
|
|
74
|
+
const msg = typeof data.message === "string"
|
|
75
|
+
? data.message
|
|
76
|
+
: `RAGFlow code ${String(bizCode)}`;
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
provider,
|
|
80
|
+
error: "ragflow_business_error",
|
|
81
|
+
message: msg,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const chunks = Array.isArray(data.data?.chunks) ? data.data.chunks : [];
|
|
85
|
+
const results = chunks.slice(0, count).map((chunk, index) => ({
|
|
86
|
+
title: chunk.document_keyword?.trim() || `Chunk ${index + 1}`,
|
|
87
|
+
url: "",
|
|
88
|
+
snippet: chunk.content?.trim() ?? "",
|
|
89
|
+
siteName: chunk.document_id?.trim() || undefined,
|
|
90
|
+
}));
|
|
91
|
+
const content = formatResultsAsContent(results);
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
provider,
|
|
95
|
+
query,
|
|
96
|
+
tookMs: Date.now() - startedAt,
|
|
97
|
+
results,
|
|
98
|
+
citations: [],
|
|
99
|
+
content,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
provider,
|
|
106
|
+
error: "ragflow_request_failed",
|
|
107
|
+
message: err instanceof Error ? err.message : String(err),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function fetchRagflowWithRetry(url, options) {
|
|
112
|
+
let lastErr = "unknown error";
|
|
113
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
114
|
+
const controller = new AbortController();
|
|
115
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutSeconds * 1000);
|
|
116
|
+
const signals = [controller.signal];
|
|
117
|
+
if (options.signal) {
|
|
118
|
+
signals.push(options.signal);
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetch(url, {
|
|
122
|
+
method: options.method,
|
|
123
|
+
headers: options.headers,
|
|
124
|
+
body: options.body,
|
|
125
|
+
signal: signals.length > 1 ? AbortSignal.any(signals) : controller.signal,
|
|
126
|
+
});
|
|
127
|
+
const text = await response.text();
|
|
128
|
+
let data;
|
|
129
|
+
try {
|
|
130
|
+
data = text ? JSON.parse(text) : {};
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
lastErr = `Invalid JSON (${response.status})`;
|
|
134
|
+
if (TRANSIENT_HTTP.has(response.status) && attempt + 1 < MAX_ATTEMPTS) {
|
|
135
|
+
await sleep(2 ** attempt * 1000);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
throw new Error(lastErr);
|
|
139
|
+
}
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
lastErr =
|
|
142
|
+
typeof data.message === "string"
|
|
143
|
+
? data.message
|
|
144
|
+
: `HTTP ${response.status}: ${text.slice(0, 500)}`;
|
|
145
|
+
if (TRANSIENT_HTTP.has(response.status) && attempt + 1 < MAX_ATTEMPTS) {
|
|
146
|
+
await sleep(2 ** attempt * 1000);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
throw new Error(lastErr);
|
|
150
|
+
}
|
|
151
|
+
return data;
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
lastErr = err instanceof Error ? err.message : String(err);
|
|
155
|
+
if (attempt + 1 < MAX_ATTEMPTS && !options.signal?.aborted) {
|
|
156
|
+
await sleep(2 ** attempt * 1000);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
throw new Error(lastErr);
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
clearTimeout(timeout);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
throw new Error(lastErr);
|
|
166
|
+
}
|
|
167
|
+
function sleep(ms) {
|
|
168
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=ragflow.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { configString, trimTrailingSlashes } from "../http.js";
|
|
2
|
+
import { joinMirrorPath, resolveDeeprouteMirrorRoot, } from "./deeproute-mirror.js";
|
|
3
|
+
import { buildDeeprouteBearerHeaders, resolveProviderAuthMode, } from "./provider-auth.js";
|
|
4
|
+
export const SEARXNG_SEARCH_PATH = "/search";
|
|
5
|
+
export function resolveSearxngAuthMode(auth) {
|
|
6
|
+
return resolveProviderAuthMode(auth);
|
|
7
|
+
}
|
|
8
|
+
export function resolveSearxngBaseUrl(cfg, auth) {
|
|
9
|
+
const configured = configString(cfg?.baseUrl);
|
|
10
|
+
if (!configured) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
if (auth === "deeproute") {
|
|
14
|
+
return resolveDeeprouteMirrorRoot(configured, "searxng");
|
|
15
|
+
}
|
|
16
|
+
return trimTrailingSlashes(configured);
|
|
17
|
+
}
|
|
18
|
+
export function buildSearxngSearchUrl(baseUrl, query, categories, language) {
|
|
19
|
+
const path = baseUrl.endsWith(SEARXNG_SEARCH_PATH)
|
|
20
|
+
? baseUrl
|
|
21
|
+
: joinMirrorPath(baseUrl, SEARXNG_SEARCH_PATH);
|
|
22
|
+
const url = new URL(path);
|
|
23
|
+
url.searchParams.set("q", query);
|
|
24
|
+
url.searchParams.set("format", "json");
|
|
25
|
+
if (categories?.trim()) {
|
|
26
|
+
url.searchParams.set("categories", categories.trim());
|
|
27
|
+
}
|
|
28
|
+
if (language?.trim()) {
|
|
29
|
+
url.searchParams.set("language", language.trim());
|
|
30
|
+
}
|
|
31
|
+
return url.toString();
|
|
32
|
+
}
|
|
33
|
+
export function buildSearxngRequestHeaders(auth, apiKey) {
|
|
34
|
+
const headers = { Accept: "application/json" };
|
|
35
|
+
if (auth === "deeproute" && apiKey) {
|
|
36
|
+
Object.assign(headers, buildDeeprouteBearerHeaders(apiKey));
|
|
37
|
+
}
|
|
38
|
+
return headers;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=searxng-config.js.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { configString, fetchJson } from "../http.js";
|
|
2
|
+
import { formatResultsAsContent, resolveTimeoutSeconds } from "../result.js";
|
|
3
|
+
import { buildSearxngRequestHeaders, buildSearxngSearchUrl, resolveSearxngAuthMode, resolveSearxngBaseUrl, } from "./searxng-config.js";
|
|
4
|
+
export async function searchSearxng(config, query, count, signal) {
|
|
5
|
+
const provider = "searxng";
|
|
6
|
+
const cfg = config.providers?.searxng;
|
|
7
|
+
const auth = resolveSearxngAuthMode(cfg?.auth);
|
|
8
|
+
if (!auth) {
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
provider,
|
|
12
|
+
error: "invalid_auth",
|
|
13
|
+
message: 'Invalid providers.searxng.auth. Use "direct" (SearXNG instance) or "deeproute" (Console mirror).',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const apiKey = configString(cfg?.apiKey);
|
|
17
|
+
if (auth === "deeproute" && !apiKey) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
provider,
|
|
21
|
+
error: "missing_api_key",
|
|
22
|
+
message: "deeproute: set providers.searxng.apiKey to your New API user sk (Bearer).",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const baseUrl = resolveSearxngBaseUrl(cfg, auth);
|
|
26
|
+
if (!baseUrl) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
provider,
|
|
30
|
+
error: "missing_base_url",
|
|
31
|
+
message: auth === "deeproute"
|
|
32
|
+
? "deeproute: set providers.searxng.baseUrl (e.g. http://host:20316/api/mirror/searxng)."
|
|
33
|
+
: "direct: set providers.searxng.baseUrl to your SearXNG instance root.",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const url = buildSearxngSearchUrl(baseUrl, query, cfg?.categories, cfg?.language);
|
|
37
|
+
const startedAt = Date.now();
|
|
38
|
+
try {
|
|
39
|
+
const { data } = await fetchJson(url, {
|
|
40
|
+
method: "GET",
|
|
41
|
+
headers: buildSearxngRequestHeaders(auth, apiKey),
|
|
42
|
+
timeoutSeconds: resolveTimeoutSeconds(config),
|
|
43
|
+
signal,
|
|
44
|
+
});
|
|
45
|
+
const raw = Array.isArray(data.results) ? data.results : [];
|
|
46
|
+
const results = raw
|
|
47
|
+
.filter((entry) => entry.url && entry.title)
|
|
48
|
+
.slice(0, count)
|
|
49
|
+
.map((entry) => ({
|
|
50
|
+
title: entry.title ?? "",
|
|
51
|
+
url: entry.url ?? "",
|
|
52
|
+
snippet: entry.content ?? "",
|
|
53
|
+
siteName: safeHostname(entry.url),
|
|
54
|
+
}));
|
|
55
|
+
const citations = [...new Set(results.map((r) => r.url).filter(Boolean))];
|
|
56
|
+
return {
|
|
57
|
+
ok: true,
|
|
58
|
+
provider,
|
|
59
|
+
query,
|
|
60
|
+
tookMs: Date.now() - startedAt,
|
|
61
|
+
results,
|
|
62
|
+
citations,
|
|
63
|
+
content: formatResultsAsContent(results),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
provider,
|
|
70
|
+
error: "searxng_request_failed",
|
|
71
|
+
message: err instanceof Error ? err.message : String(err),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function safeHostname(url) {
|
|
76
|
+
if (!url) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return new URL(url).hostname;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=searxng.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { configString, trimTrailingSlashes } from "../http.js";
|
|
2
|
+
import { joinMirrorPath, resolveDeeprouteMirrorRoot, } from "./deeproute-mirror.js";
|
|
3
|
+
import { buildDeeprouteBearerHeaders, resolveProviderAuthMode, } from "./provider-auth.js";
|
|
4
|
+
export const DEFAULT_SERPER_DIRECT_BASE = "https://google.serper.dev";
|
|
5
|
+
export const SERPER_SEARCH_PATH = "/search";
|
|
6
|
+
export function resolveSerperAuthMode(auth) {
|
|
7
|
+
return resolveProviderAuthMode(auth);
|
|
8
|
+
}
|
|
9
|
+
export function resolveSerperSearchUrl(cfg, auth) {
|
|
10
|
+
const configured = configString(cfg?.baseUrl);
|
|
11
|
+
if (auth === "deeproute") {
|
|
12
|
+
if (!configured)
|
|
13
|
+
return undefined;
|
|
14
|
+
const root = resolveDeeprouteMirrorRoot(configured, "serper");
|
|
15
|
+
return joinMirrorPath(root, SERPER_SEARCH_PATH);
|
|
16
|
+
}
|
|
17
|
+
const base = configured
|
|
18
|
+
? trimTrailingSlashes(configured)
|
|
19
|
+
: DEFAULT_SERPER_DIRECT_BASE;
|
|
20
|
+
return joinMirrorPath(base, SERPER_SEARCH_PATH);
|
|
21
|
+
}
|
|
22
|
+
export function buildSerperRequestHeaders(auth, apiKey) {
|
|
23
|
+
const headers = {
|
|
24
|
+
Accept: "application/json",
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
};
|
|
27
|
+
if (auth === "deeproute") {
|
|
28
|
+
Object.assign(headers, buildDeeprouteBearerHeaders(apiKey));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
headers["X-API-KEY"] = apiKey;
|
|
32
|
+
}
|
|
33
|
+
return headers;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=serper-config.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { configString, fetchJson } from "../http.js";
|
|
2
|
+
import { formatResultsAsContent, resolveTimeoutSeconds } from "../result.js";
|
|
3
|
+
import { buildSerperRequestHeaders, resolveSerperAuthMode, resolveSerperSearchUrl, } from "./serper-config.js";
|
|
4
|
+
export async function searchSerper(config, query, count, signal) {
|
|
5
|
+
const provider = "serper";
|
|
6
|
+
const cfg = config.providers?.serper;
|
|
7
|
+
const auth = resolveSerperAuthMode(cfg?.auth);
|
|
8
|
+
if (!auth) {
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
provider,
|
|
12
|
+
error: "invalid_auth",
|
|
13
|
+
message: 'Invalid providers.serper.auth. Use "direct" (Serper API) or "deeproute" (Console mirror).',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const apiKey = configString(cfg?.apiKey);
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
provider,
|
|
21
|
+
error: "missing_api_key",
|
|
22
|
+
message: auth === "deeproute"
|
|
23
|
+
? "deeproute: set providers.serper.apiKey to your DeepRoute user sk (Bearer)."
|
|
24
|
+
: "direct: set providers.serper.apiKey to your Serper API key.",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const url = resolveSerperSearchUrl(cfg, auth);
|
|
28
|
+
if (!url) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
provider,
|
|
32
|
+
error: "missing_base_url",
|
|
33
|
+
message: "deeproute: set providers.serper.baseUrl to Console origin (e.g. http://host:20316).",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const startedAt = Date.now();
|
|
37
|
+
try {
|
|
38
|
+
const { data } = await fetchJson(url, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: buildSerperRequestHeaders(auth, apiKey),
|
|
41
|
+
body: { q: query, num: count, page: 1 },
|
|
42
|
+
timeoutSeconds: resolveTimeoutSeconds(config),
|
|
43
|
+
signal,
|
|
44
|
+
});
|
|
45
|
+
const raw = Array.isArray(data.organic) ? data.organic : [];
|
|
46
|
+
const results = raw.slice(0, count).map((entry) => ({
|
|
47
|
+
title: entry.title ?? "",
|
|
48
|
+
url: entry.link ?? "",
|
|
49
|
+
snippet: entry.snippet ?? "",
|
|
50
|
+
siteName: entry.link ? safeHostname(entry.link) : undefined,
|
|
51
|
+
}));
|
|
52
|
+
const citations = [...new Set(results.map((r) => r.url).filter(Boolean))];
|
|
53
|
+
return {
|
|
54
|
+
ok: true,
|
|
55
|
+
provider,
|
|
56
|
+
query,
|
|
57
|
+
tookMs: Date.now() - startedAt,
|
|
58
|
+
results,
|
|
59
|
+
citations,
|
|
60
|
+
content: formatResultsAsContent(results),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
provider,
|
|
67
|
+
error: "serper_request_failed",
|
|
68
|
+
message: err instanceof Error ? err.message : String(err),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function safeHostname(url) {
|
|
73
|
+
try {
|
|
74
|
+
return new URL(url).hostname;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=serper.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { configString, trimTrailingSlashes } from "../http.js";
|
|
2
|
+
import { joinMirrorPath, resolveDeeprouteMirrorRoot, } from "./deeproute-mirror.js";
|
|
3
|
+
import { buildDeeprouteBearerHeaders, resolveProviderAuthMode, } from "./provider-auth.js";
|
|
4
|
+
export const DEFAULT_TAVILY_DIRECT_BASE = "https://api.tavily.com";
|
|
5
|
+
export const TAVILY_SEARCH_PATH = "/search";
|
|
6
|
+
export function resolveTavilyAuthMode(auth) {
|
|
7
|
+
return resolveProviderAuthMode(auth);
|
|
8
|
+
}
|
|
9
|
+
export function resolveTavilyBaseUrl(cfg, auth) {
|
|
10
|
+
const configured = configString(cfg?.baseUrl);
|
|
11
|
+
if (configured) {
|
|
12
|
+
if (auth === "deeproute") {
|
|
13
|
+
return resolveDeeprouteMirrorRoot(configured, "tavily");
|
|
14
|
+
}
|
|
15
|
+
return trimTrailingSlashes(configured);
|
|
16
|
+
}
|
|
17
|
+
if (auth === "direct") {
|
|
18
|
+
return DEFAULT_TAVILY_DIRECT_BASE;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
export function resolveTavilySearchUrl(baseUrl, auth) {
|
|
23
|
+
if (auth === "deeproute") {
|
|
24
|
+
return joinMirrorPath(baseUrl, TAVILY_SEARCH_PATH);
|
|
25
|
+
}
|
|
26
|
+
const trimmed = trimTrailingSlashes(baseUrl || DEFAULT_TAVILY_DIRECT_BASE);
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(trimmed);
|
|
29
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}${TAVILY_SEARCH_PATH}`;
|
|
30
|
+
return url.toString();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return `${DEFAULT_TAVILY_DIRECT_BASE}${TAVILY_SEARCH_PATH}`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function buildTavilyRequestHeaders(auth, apiKey) {
|
|
37
|
+
const headers = {
|
|
38
|
+
Accept: "application/json",
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
};
|
|
41
|
+
if (auth === "deeproute") {
|
|
42
|
+
Object.assign(headers, buildDeeprouteBearerHeaders(apiKey));
|
|
43
|
+
}
|
|
44
|
+
return headers;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=tavily-config.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { configString, fetchJson } from "../http.js";
|
|
2
|
+
import { formatResultsAsContent, resolveTimeoutSeconds } from "../result.js";
|
|
3
|
+
import { buildTavilyRequestHeaders, resolveTavilyAuthMode, resolveTavilyBaseUrl, resolveTavilySearchUrl, } from "./tavily-config.js";
|
|
4
|
+
export async function searchTavily(config, query, count, signal, runOptions) {
|
|
5
|
+
const provider = "tavily";
|
|
6
|
+
const cfg = config.providers?.tavily;
|
|
7
|
+
const auth = resolveTavilyAuthMode(cfg?.auth);
|
|
8
|
+
if (!auth) {
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
provider,
|
|
12
|
+
error: "invalid_auth",
|
|
13
|
+
message: 'Invalid providers.tavily.auth. Use "direct" (Tavily API) or "deeproute" (Console mirror).',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const apiKey = configString(cfg?.apiKey);
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
provider,
|
|
21
|
+
error: "missing_api_key",
|
|
22
|
+
message: auth === "deeproute"
|
|
23
|
+
? "deeproute: set providers.tavily.apiKey to your New API user sk (Bearer)."
|
|
24
|
+
: "direct: set providers.tavily.apiKey to your Tavily API key.",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const baseUrl = resolveTavilyBaseUrl(cfg, auth);
|
|
28
|
+
if (!baseUrl) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
provider,
|
|
32
|
+
error: "missing_base_url",
|
|
33
|
+
message: "deeproute: set providers.tavily.baseUrl (e.g. http://host:20316/api/mirror/tavily).",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const body = {
|
|
37
|
+
query,
|
|
38
|
+
max_results: count,
|
|
39
|
+
};
|
|
40
|
+
if (auth === "direct") {
|
|
41
|
+
body.api_key = apiKey;
|
|
42
|
+
}
|
|
43
|
+
const searchDepth = runOptions?.tavily?.searchDepth ?? cfg?.searchDepth;
|
|
44
|
+
if (searchDepth) {
|
|
45
|
+
body.search_depth = searchDepth;
|
|
46
|
+
}
|
|
47
|
+
const includeAnswer = runOptions?.tavily?.includeAnswer ?? cfg?.includeAnswer;
|
|
48
|
+
if (includeAnswer) {
|
|
49
|
+
body.include_answer = true;
|
|
50
|
+
}
|
|
51
|
+
const url = resolveTavilySearchUrl(baseUrl, auth);
|
|
52
|
+
const startedAt = Date.now();
|
|
53
|
+
try {
|
|
54
|
+
const { data } = await fetchJson(url, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: buildTavilyRequestHeaders(auth, apiKey),
|
|
57
|
+
body,
|
|
58
|
+
timeoutSeconds: resolveTimeoutSeconds(config),
|
|
59
|
+
signal,
|
|
60
|
+
});
|
|
61
|
+
const raw = Array.isArray(data.results) ? data.results : [];
|
|
62
|
+
const results = raw.slice(0, count).map((entry) => ({
|
|
63
|
+
title: entry.title ?? "",
|
|
64
|
+
url: entry.url ?? "",
|
|
65
|
+
snippet: entry.content ?? "",
|
|
66
|
+
siteName: entry.url ? safeHostname(entry.url) : undefined,
|
|
67
|
+
}));
|
|
68
|
+
const citations = [...new Set(results.map((r) => r.url).filter(Boolean))];
|
|
69
|
+
const answer = typeof data.answer === "string" ? data.answer : undefined;
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
provider,
|
|
73
|
+
query,
|
|
74
|
+
tookMs: Date.now() - startedAt,
|
|
75
|
+
results,
|
|
76
|
+
citations,
|
|
77
|
+
answer,
|
|
78
|
+
content: formatResultsAsContent(results, answer),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
provider,
|
|
85
|
+
error: "tavily_request_failed",
|
|
86
|
+
message: err instanceof Error ? err.message : String(err),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function safeHostname(url) {
|
|
91
|
+
try {
|
|
92
|
+
return new URL(url).hostname;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=tavily.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { configString, trimTrailingSlashes } from "../http.js";
|
|
2
|
+
import { joinMirrorPath, resolveDeeprouteMirrorRoot, } from "./deeproute-mirror.js";
|
|
3
|
+
import { buildDeeprouteBearerHeaders, resolveProviderAuthMode, } from "./provider-auth.js";
|
|
4
|
+
export const DEFAULT_ZAI_DIRECT_BASE = "https://open.bigmodel.cn";
|
|
5
|
+
export const ZAI_SEARCH_PATH = "/api/paas/v4/web_search";
|
|
6
|
+
export function resolveZaiAuthMode(auth) {
|
|
7
|
+
return resolveProviderAuthMode(auth);
|
|
8
|
+
}
|
|
9
|
+
export function resolveZaiSearchUrl(cfg, auth) {
|
|
10
|
+
const configured = configString(cfg?.baseUrl);
|
|
11
|
+
if (auth === "deeproute") {
|
|
12
|
+
if (!configured)
|
|
13
|
+
return undefined;
|
|
14
|
+
const root = resolveDeeprouteMirrorRoot(configured, "zai");
|
|
15
|
+
return joinMirrorPath(root, ZAI_SEARCH_PATH);
|
|
16
|
+
}
|
|
17
|
+
const base = configured
|
|
18
|
+
? trimTrailingSlashes(configured)
|
|
19
|
+
: DEFAULT_ZAI_DIRECT_BASE;
|
|
20
|
+
return joinMirrorPath(base, ZAI_SEARCH_PATH);
|
|
21
|
+
}
|
|
22
|
+
export function buildZaiRequestHeaders(auth, apiKey) {
|
|
23
|
+
const headers = {
|
|
24
|
+
Accept: "application/json",
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
};
|
|
27
|
+
if (auth === "deeproute") {
|
|
28
|
+
Object.assign(headers, buildDeeprouteBearerHeaders(apiKey));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
32
|
+
}
|
|
33
|
+
return headers;
|
|
34
|
+
}
|
|
35
|
+
export function resolveZaiSearchEngine(cfg) {
|
|
36
|
+
const engine = configString(cfg?.searchEngine);
|
|
37
|
+
return engine || "search_pro";
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=zai-config.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { configString, fetchJson } from "../http.js";
|
|
2
|
+
import { formatResultsAsContent, resolveTimeoutSeconds } from "../result.js";
|
|
3
|
+
import { buildZaiRequestHeaders, resolveZaiAuthMode, resolveZaiSearchEngine, resolveZaiSearchUrl, } from "./zai-config.js";
|
|
4
|
+
export async function searchZai(config, query, count, signal) {
|
|
5
|
+
const provider = "zai";
|
|
6
|
+
const cfg = config.providers?.zai;
|
|
7
|
+
const auth = resolveZaiAuthMode(cfg?.auth);
|
|
8
|
+
if (!auth) {
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
provider,
|
|
12
|
+
error: "invalid_auth",
|
|
13
|
+
message: 'Invalid providers.zai.auth. Use "direct" or "deeproute".',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const apiKey = configString(cfg?.apiKey);
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
provider,
|
|
21
|
+
error: "missing_api_key",
|
|
22
|
+
message: auth === "deeproute"
|
|
23
|
+
? "deeproute: set providers.zai.apiKey to your DeepRoute user sk."
|
|
24
|
+
: "direct: set providers.zai.apiKey to your Zhipu API key.",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const url = resolveZaiSearchUrl(cfg, auth);
|
|
28
|
+
if (!url) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
provider,
|
|
32
|
+
error: "missing_base_url",
|
|
33
|
+
message: "deeproute: set providers.zai.baseUrl to Console origin (e.g. http://host:20316).",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const searchEngine = resolveZaiSearchEngine(cfg);
|
|
37
|
+
const body = {
|
|
38
|
+
search_query: query,
|
|
39
|
+
search_engine: searchEngine,
|
|
40
|
+
search_intent: true,
|
|
41
|
+
content_size: "high",
|
|
42
|
+
search_recency_filter: "noLimit",
|
|
43
|
+
};
|
|
44
|
+
if (searchEngine !== "search_pro_quark") {
|
|
45
|
+
body.count = Math.min(Math.max(count, 1), 50);
|
|
46
|
+
}
|
|
47
|
+
const startedAt = Date.now();
|
|
48
|
+
try {
|
|
49
|
+
const { data } = await fetchJson(url, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: buildZaiRequestHeaders(auth, apiKey),
|
|
52
|
+
body,
|
|
53
|
+
timeoutSeconds: resolveTimeoutSeconds(config),
|
|
54
|
+
signal,
|
|
55
|
+
});
|
|
56
|
+
const raw = Array.isArray(data.search_result) ? data.search_result : [];
|
|
57
|
+
const results = raw.slice(0, count).map((entry) => ({
|
|
58
|
+
title: entry.title ?? "",
|
|
59
|
+
url: entry.link ?? "",
|
|
60
|
+
snippet: entry.content ?? "",
|
|
61
|
+
siteName: entry.link ? safeHostname(entry.link) : undefined,
|
|
62
|
+
}));
|
|
63
|
+
const citations = [...new Set(results.map((r) => r.url).filter(Boolean))];
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
provider,
|
|
67
|
+
query,
|
|
68
|
+
tookMs: Date.now() - startedAt,
|
|
69
|
+
results,
|
|
70
|
+
citations,
|
|
71
|
+
content: formatResultsAsContent(results),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
provider,
|
|
78
|
+
error: "zai_request_failed",
|
|
79
|
+
message: err instanceof Error ? err.message : String(err),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function safeHostname(url) {
|
|
84
|
+
try {
|
|
85
|
+
return new URL(url).hostname;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=zai.js.map
|