@herbertgao/pi-extensions 2026.9.13 → 2026.9.14
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/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +4 -4
- package/node_modules/@herbertgao/resume-from/package.json +2 -2
- package/node_modules/@herbertgao/sol-pi/package.json +5 -5
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +2 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +26 -0
- package/node_modules/pi-mcp-adapter/README.md +39 -9
- package/node_modules/pi-mcp-adapter/cli.js +33 -22
- package/node_modules/pi-mcp-adapter/commands.ts +3 -3
- package/node_modules/pi-mcp-adapter/config.ts +9 -1
- package/node_modules/pi-mcp-adapter/dist/config.js +9 -1
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +1 -2
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +59 -32
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +1 -1
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +33 -4
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +148 -28
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1 -2
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +7 -1
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +51 -38
- package/node_modules/pi-mcp-adapter/jev-client.ts +58 -31
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +2 -2
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +144 -31
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-code.ts +6 -6
- package/node_modules/pi-mcp-adapter/package.json +5 -5
- package/node_modules/pi-mcp-adapter/semantic-search.ts +3 -3
- package/node_modules/pi-mcp-adapter/types.ts +7 -1
- package/node_modules/pi-multi-account/CHANGELOG.md +6 -0
- package/node_modules/pi-multi-account/README.md +6 -5
- package/node_modules/pi-multi-account/index.ts +1 -1
- package/node_modules/pi-multi-account/package.json +3 -3
- package/node_modules/pi-typesafe/dist/backends.d.ts +10 -0
- package/node_modules/pi-typesafe/dist/backends.js +26 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +1 -1
- package/node_modules/pi-typesafe/dist/client.js +10 -6
- package/node_modules/pi-typesafe/dist/errors.d.ts +10 -2
- package/node_modules/pi-typesafe/dist/errors.js +28 -7
- package/node_modules/pi-typesafe/dist/extension.js +2 -1
- package/node_modules/pi-typesafe/package.json +4 -4
- package/node_modules/pi-web-access/CHANGELOG.md +22 -0
- package/node_modules/pi-web-access/README.md +21 -8
- package/node_modules/pi-web-access/baizhi.ts +124 -0
- package/node_modules/pi-web-access/crawl4ai.ts +2 -10
- package/node_modules/pi-web-access/curator-page.ts +3 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +540 -220
- package/node_modules/pi-web-access/firecrawl.ts +2 -10
- package/node_modules/pi-web-access/gemini-search.ts +5 -10
- package/node_modules/pi-web-access/index.ts +134 -23
- package/node_modules/pi-web-access/package.json +5 -1
- package/node_modules/pi-web-access/storage.ts +1 -0
- package/node_modules/pi-web-access/tool-activation.ts +134 -0
- package/node_modules/pi-web-access/utils.ts +9 -1
- package/package.json +13 -13
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
import { activityMonitor } from "./activity.ts";
|
|
4
|
+
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
6
|
+
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
7
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
8
|
+
import { getWebSearchConfigPath } from "./utils.ts";
|
|
9
|
+
|
|
10
|
+
const MCP_URL = "https://agent-toolkit.app.baizhi.cloud/mcp";
|
|
11
|
+
const SEARCH_TIMEOUT_MS = 60_000;
|
|
12
|
+
const CONFIG_PATH = getWebSearchConfigPath();
|
|
13
|
+
|
|
14
|
+
function credentialOptions() {
|
|
15
|
+
let config: Record<string, unknown> = {};
|
|
16
|
+
if (existsSync(CONFIG_PATH)) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed: unknown = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
19
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error();
|
|
20
|
+
config = parsed as Record<string, unknown>;
|
|
21
|
+
} catch {
|
|
22
|
+
// JSON parser errors may include a fragment containing a literal key.
|
|
23
|
+
throw new Error(`Invalid configuration in ${CONFIG_PATH}: expected a JSON object`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { provider: "Baizhi", configuredValue: config.baizhiApiKey, environmentValue: process.env.BAIZHI_API_KEY };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isBaizhiAvailable(): boolean {
|
|
30
|
+
return hasCredentialSource(credentialOptions());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function searchArguments(query: string, options: SearchOptions): Record<string, unknown> {
|
|
34
|
+
if (!query.trim()) throw new Error("Baizhi search query must not be empty");
|
|
35
|
+
const domains: string[] = [];
|
|
36
|
+
const excludeDomains: string[] = [];
|
|
37
|
+
for (const raw of options.domainFilter ?? []) {
|
|
38
|
+
const bare = raw.trim().replace(/^-/, "").trim();
|
|
39
|
+
const domain = isIP(bare) ? bare : normalizeDomain(raw);
|
|
40
|
+
if (!domain) throw new Error("Baizhi domain filter must contain a valid domain");
|
|
41
|
+
const target = raw.trim().startsWith("-") ? excludeDomains : domains;
|
|
42
|
+
if (!target.includes(domain)) target.push(domain);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
query: query.trim(),
|
|
46
|
+
count: normalizeSearchResultCount(options.numResults),
|
|
47
|
+
need_summary: false,
|
|
48
|
+
// The published discovery contract defaults to month, not unrestricted time.
|
|
49
|
+
time_range: options.recencyFilter ?? "month",
|
|
50
|
+
...(domains.length || excludeDomains.length ? { filter: {
|
|
51
|
+
...(domains.length ? { domains } : {}),
|
|
52
|
+
...(excludeDomains.length ? { exclude_domains: excludeDomains } : {}),
|
|
53
|
+
} } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function searchWithBaizhi(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
58
|
+
const args = searchArguments(query, options);
|
|
59
|
+
const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
|
|
60
|
+
const signal = options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal;
|
|
61
|
+
if (signal.aborted) throw new Error("Aborted");
|
|
62
|
+
const apiKey = await resolveCredential({ ...credentialOptions(), signal });
|
|
63
|
+
if (!apiKey) throw new Error("Baizhi API key not found. Set BAIZHI_API_KEY or baizhiApiKey in web-search.json. Get your own key at https://agent-toolkit.app.baizhi.cloud/");
|
|
64
|
+
let headers: Headers;
|
|
65
|
+
try { headers = new Headers({ Authorization: `Bearer ${apiKey}` }); }
|
|
66
|
+
catch { throw new Error("Baizhi credential resolution failed: invalid-header-value"); }
|
|
67
|
+
// Load the protocol client only when this explicit provider is selected.
|
|
68
|
+
const [{ Client }, { StreamableHTTPClientTransport }] = await Promise.all([
|
|
69
|
+
import("@modelcontextprotocol/sdk/client/index.js"),
|
|
70
|
+
import("@modelcontextprotocol/sdk/client/streamableHttp.js"),
|
|
71
|
+
]);
|
|
72
|
+
let cleaningUp = false;
|
|
73
|
+
const transport = new StreamableHTTPClientTransport(new URL(MCP_URL), {
|
|
74
|
+
requestInit: { headers },
|
|
75
|
+
fetch: async (url, init) => {
|
|
76
|
+
// Fixed endpoint, no redirects: neither credentials nor session IDs can
|
|
77
|
+
// be forwarded to another origin (including MCP discovery redirects).
|
|
78
|
+
if (String(url) !== MCP_URL) throw new Error("Baizhi endpoint changed unexpectedly");
|
|
79
|
+
const requestSignal = cleaningUp ? AbortSignal.timeout(1_500) : signal;
|
|
80
|
+
return fetch(url, { ...init, redirect: "error", signal: !cleaningUp && init?.signal ? AbortSignal.any([init.signal, requestSignal]) : requestSignal });
|
|
81
|
+
},
|
|
82
|
+
reconnectionOptions: { maxRetries: 0, initialReconnectionDelay: 1_000, maxReconnectionDelay: 1_000, reconnectionDelayGrowFactor: 1 },
|
|
83
|
+
});
|
|
84
|
+
const client = new Client({ name: "pi-web-access-baizhi", version: "1.0.0" });
|
|
85
|
+
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
86
|
+
try {
|
|
87
|
+
await client.connect(transport, { signal, timeout: SEARCH_TIMEOUT_MS });
|
|
88
|
+
const result = await client.callTool({ name: "websearch_search", arguments: args }, undefined, { signal, timeout: SEARCH_TIMEOUT_MS });
|
|
89
|
+
if (result.isError) throw new Error("Baizhi search tool returned an error");
|
|
90
|
+
// The public discovery snapshot documents inputs but no outputSchema.
|
|
91
|
+
// Preserve the MCP payload instead of guessing result item field names.
|
|
92
|
+
const parts: string[] = [];
|
|
93
|
+
if (result.structuredContent !== undefined) parts.push(JSON.stringify(result.structuredContent, null, 2));
|
|
94
|
+
for (const item of Array.isArray(result.content) ? result.content : []) {
|
|
95
|
+
if (item.type === "text" && typeof item.text === "string" && item.text.trim()) parts.push(item.text);
|
|
96
|
+
}
|
|
97
|
+
if (!parts.length) throw new Error("Baizhi returned invalid response: no search text or structured content");
|
|
98
|
+
const answer = redactCredential(redactCredential(parts.join("\n\n"), apiKey), JSON.stringify(apiKey).slice(1, -1));
|
|
99
|
+
activityMonitor.logComplete(activityId, 200);
|
|
100
|
+
return { answer, results: [] };
|
|
101
|
+
} catch (err) {
|
|
102
|
+
let message: string;
|
|
103
|
+
if (options.signal?.aborted) message = "Aborted";
|
|
104
|
+
else if (timeoutSignal.aborted) message = "Baizhi request timed out after 60s";
|
|
105
|
+
else {
|
|
106
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : undefined;
|
|
107
|
+
// Never expose remote HTTP/JSON-RPC bodies or nested SDK errors: they
|
|
108
|
+
// can echo credentials in plain, encoded or transformed form.
|
|
109
|
+
message = typeof code === "number" && Number.isInteger(code) && code >= 400 && code <= 599
|
|
110
|
+
? `Baizhi HTTP ${code} request failed; check your API key, account and service status`
|
|
111
|
+
: err instanceof TypeError ? "Baizhi network request failed"
|
|
112
|
+
: err instanceof SyntaxError || code === -32700 || code === -32602 ? "Baizhi returned invalid response"
|
|
113
|
+
: err instanceof Error && /^Baizhi (search tool returned an error|returned invalid response:)/.test(err.message) ? err.message
|
|
114
|
+
: "Baizhi MCP request failed; check your connection, API key and account status";
|
|
115
|
+
}
|
|
116
|
+
activityMonitor.logError(activityId, message);
|
|
117
|
+
throw new Error(message);
|
|
118
|
+
} finally {
|
|
119
|
+
cleaningUp = true;
|
|
120
|
+
// Best effort, separately bounded DELETE; always close the local streams.
|
|
121
|
+
try { await transport.terminateSession(); } catch { /* Server may not support DELETE. */ }
|
|
122
|
+
await client.close();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import net from "node:net";
|
|
3
2
|
import { activityMonitor } from "./activity.ts";
|
|
4
3
|
import { redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
4
|
import type { ExtractedContent, ExtractOptions } from "./extract.ts";
|
|
6
5
|
import { fetchRemoteUrl, loadSsrfConfig, validateRemoteUrl, type Lookup, type SsrfConfig } from "./ssrf-protection.ts";
|
|
7
|
-
import { getWebSearchConfigPath } from "./utils.ts";
|
|
6
|
+
import { getWebSearchConfigPath, isLoopbackHostname } from "./utils.ts";
|
|
8
7
|
|
|
9
8
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
10
9
|
const EXTRACT_TIMEOUT_MS = 60_000;
|
|
@@ -113,13 +112,6 @@ function ssrfOptions(options?: Crawl4aiExtractOptions): SsrfConfig & { lookup?:
|
|
|
113
112
|
};
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
function isLoopbackApiUrl(url: URL): boolean {
|
|
117
|
-
const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
118
|
-
if (hostname === "localhost" || hostname === "::1") return true;
|
|
119
|
-
if (net.isIP(hostname) !== 4) return false;
|
|
120
|
-
return hostname.split(".")[0] === "127";
|
|
121
|
-
}
|
|
122
|
-
|
|
123
115
|
function firstHeadingTitle(markdown: string): string {
|
|
124
116
|
return /^[ \t]*#[ \t]+(\S.*?)[ \t\r]*$/m.exec(markdown)?.[1] ?? "";
|
|
125
117
|
}
|
|
@@ -151,7 +143,7 @@ export async function extractWithCrawl4ai(
|
|
|
151
143
|
try {
|
|
152
144
|
const response = await fetchRemoteUrl(requestUrl, init, {
|
|
153
145
|
...ssrf,
|
|
154
|
-
allowLoopback:
|
|
146
|
+
allowLoopback: isLoopbackHostname(requestUrl.hostname),
|
|
155
147
|
onRedirect: ({ to, init: redirectInit, response }) => {
|
|
156
148
|
if (to.origin !== requestUrl.origin) {
|
|
157
149
|
throw new Error(`Crawl4AI refused cross-origin redirect to ${to.origin}`);
|
|
@@ -46,6 +46,7 @@ function buildProviderButtons(
|
|
|
46
46
|
{ value: "serpapi", label: "SerpApi", available: available.serpapi },
|
|
47
47
|
{ value: "serper", label: "Serper", available: available.serper },
|
|
48
48
|
{ value: "serply", label: "Serply", available: available.serply },
|
|
49
|
+
{ value: "baizhi", label: "Baizhi", available: available.baizhi },
|
|
49
50
|
{ value: "valyu", label: "Valyu", available: available.valyu },
|
|
50
51
|
];
|
|
51
52
|
|
|
@@ -1473,7 +1474,7 @@ const SCRIPT = `(function() {
|
|
|
1473
1474
|
var token = DATA.sessionToken;
|
|
1474
1475
|
var timeoutSec = DATA.timeout;
|
|
1475
1476
|
var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
|
|
1476
|
-
var providers = ["auto", "all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply", "valyu"];
|
|
1477
|
+
var providers = ["auto", "all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply", "valyu", "baizhi"];
|
|
1477
1478
|
var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
|
|
1478
1479
|
var workflow = "summary-review";
|
|
1479
1480
|
var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
|
|
@@ -1708,6 +1709,7 @@ const SCRIPT = `(function() {
|
|
|
1708
1709
|
if (provider === "serpapi") return "SerpApi";
|
|
1709
1710
|
if (provider === "serper") return "Serper";
|
|
1710
1711
|
if (provider === "serply") return "Serply";
|
|
1712
|
+
if (provider === "baizhi") return "Baizhi";
|
|
1711
1713
|
if (provider === "valyu") return "Valyu";
|
|
1712
1714
|
return "Unknown";
|
|
1713
1715
|
}
|
|
@@ -305,6 +305,7 @@ export function startCuratorServer(
|
|
|
305
305
|
if (provider === "serpbase") return availableProviders.serpbase;
|
|
306
306
|
if (provider === "serper") return availableProviders.serper;
|
|
307
307
|
if (provider === "serply") return availableProviders.serply;
|
|
308
|
+
if (provider === "baizhi") return availableProviders.baizhi;
|
|
308
309
|
if (provider === "valyu") return availableProviders.valyu;
|
|
309
310
|
return false;
|
|
310
311
|
}
|