@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
|
@@ -18,6 +18,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
18
18
|
import { spawn } from "node:child_process";
|
|
19
19
|
import { existsSync, readFileSync } from "node:fs";
|
|
20
20
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
21
|
+
import { isIP } from "node:net";
|
|
21
22
|
import { homedir, hostname, tmpdir } from "node:os";
|
|
22
23
|
import { join } from "node:path";
|
|
23
24
|
function getWebSearchConfigDir() {
|
|
@@ -41,6 +42,10 @@ function getWebSearchConfigDir() {
|
|
|
41
42
|
function getWebSearchConfigPath() {
|
|
42
43
|
return join(getWebSearchConfigDir(), "web-search.json");
|
|
43
44
|
}
|
|
45
|
+
function isLoopbackHostname(hostnameValue) {
|
|
46
|
+
const normalized = hostnameValue.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
|
|
47
|
+
return normalized === "localhost" || normalized === "::1" || isIP(normalized) === 4 && normalized.split(".", 1)[0] === "127";
|
|
48
|
+
}
|
|
44
49
|
function resolveApiBaseUrl(options) {
|
|
45
50
|
const fromEnvironment = options.environmentValue !== void 0;
|
|
46
51
|
const value = fromEnvironment ? options.environmentValue : options.configuredValue;
|
|
@@ -55,7 +60,7 @@ function resolveApiBaseUrl(options) {
|
|
|
55
60
|
} catch {
|
|
56
61
|
throw new Error(`${source} must be an absolute HTTP(S) URL`);
|
|
57
62
|
}
|
|
58
|
-
if (url.protocol !== "https:") {
|
|
63
|
+
if (url.protocol !== "https:" && (url.protocol !== "http:" || !isLoopbackHostname(url.hostname))) {
|
|
59
64
|
throw new Error(`${source} must be an absolute HTTPS URL`);
|
|
60
65
|
}
|
|
61
66
|
if (url.username || url.password) {
|
|
@@ -5265,7 +5270,6 @@ var init_ssrf_protection = __esm({
|
|
|
5265
5270
|
|
|
5266
5271
|
// firecrawl.ts
|
|
5267
5272
|
import { existsSync as existsSync21, readFileSync as readFileSync22 } from "node:fs";
|
|
5268
|
-
import net2 from "node:net";
|
|
5269
5273
|
function loadConfig14() {
|
|
5270
5274
|
if (cachedConfig15) return cachedConfig15;
|
|
5271
5275
|
if (!existsSync21(CONFIG_PATH16)) {
|
|
@@ -5370,12 +5374,6 @@ function ssrfOptions(options) {
|
|
|
5370
5374
|
...options?.lookup ? { lookup: options.lookup } : {}
|
|
5371
5375
|
};
|
|
5372
5376
|
}
|
|
5373
|
-
function isLoopbackApiUrl(url) {
|
|
5374
|
-
const hostname2 = url.hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
5375
|
-
if (hostname2 === "localhost" || hostname2 === "::1") return true;
|
|
5376
|
-
if (net2.isIP(hostname2) !== 4) return false;
|
|
5377
|
-
return hostname2.split(".")[0] === "127";
|
|
5378
|
-
}
|
|
5379
5377
|
function firecrawlApiSsrfOptions(options, allowLoopback) {
|
|
5380
5378
|
return { ...ssrfOptions(options), allowLoopback };
|
|
5381
5379
|
}
|
|
@@ -5390,7 +5388,7 @@ function withoutSensitiveHeaders(headers) {
|
|
|
5390
5388
|
return next;
|
|
5391
5389
|
}
|
|
5392
5390
|
async function fetchFirecrawlApi(url, init, options) {
|
|
5393
|
-
const allowLoopback =
|
|
5391
|
+
const allowLoopback = isLoopbackHostname(new URL(url).hostname);
|
|
5394
5392
|
let current = await validateRemoteUrl(url, firecrawlApiSsrfOptions(options, allowLoopback));
|
|
5395
5393
|
let headers = init.headers;
|
|
5396
5394
|
for (let redirects = 0; redirects <= DEFAULT_MAX_REDIRECTS2; redirects++) {
|
|
@@ -6330,20 +6328,20 @@ var init_rsc_extract = __esm({
|
|
|
6330
6328
|
});
|
|
6331
6329
|
|
|
6332
6330
|
// datalab-pdf-extract.ts
|
|
6333
|
-
import { existsSync as
|
|
6331
|
+
import { existsSync as existsSync40, readFileSync as readFileSync43 } from "node:fs";
|
|
6334
6332
|
function loadConfig31() {
|
|
6335
6333
|
if (cachedConfig32) return cachedConfig32;
|
|
6336
|
-
if (!
|
|
6334
|
+
if (!existsSync40(CONFIG_PATH35)) {
|
|
6337
6335
|
cachedConfig32 = {};
|
|
6338
6336
|
return cachedConfig32;
|
|
6339
6337
|
}
|
|
6340
|
-
const rawText =
|
|
6338
|
+
const rawText = readFileSync43(CONFIG_PATH35, "utf-8");
|
|
6341
6339
|
try {
|
|
6342
6340
|
cachedConfig32 = JSON.parse(rawText);
|
|
6343
6341
|
return cachedConfig32;
|
|
6344
6342
|
} catch (err) {
|
|
6345
6343
|
const message = err instanceof Error ? err.message : String(err);
|
|
6346
|
-
throw new Error(`Failed to parse ${
|
|
6344
|
+
throw new Error(`Failed to parse ${CONFIG_PATH35}: ${message}`);
|
|
6347
6345
|
}
|
|
6348
6346
|
}
|
|
6349
6347
|
function normalizeString(value) {
|
|
@@ -6690,7 +6688,7 @@ async function readResponseText(response) {
|
|
|
6690
6688
|
function datalabFilename(title) {
|
|
6691
6689
|
return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60) || "document";
|
|
6692
6690
|
}
|
|
6693
|
-
var DEFAULT_API_HOST2, API_PREFIX, DEFAULT_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, MAX_RESPONSE_BYTES, MAX_ERROR_BODY_BYTES, CLEANUP_TIMEOUT_MS,
|
|
6691
|
+
var DEFAULT_API_HOST2, API_PREFIX, DEFAULT_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, MAX_RESPONSE_BYTES, MAX_ERROR_BODY_BYTES, CLEANUP_TIMEOUT_MS, CONFIG_PATH35, DATALAB_MODE_VALUES, DATALAB_LOCATION_VALUES, DEFAULT_DATALAB_MODE, DEFAULT_PROCESSING_LOCATION, DEFAULT_DATALAB_TIMEOUT_MS, cachedConfig32;
|
|
6694
6692
|
var init_datalab_pdf_extract = __esm({
|
|
6695
6693
|
"datalab-pdf-extract.ts"() {
|
|
6696
6694
|
init_credential_source();
|
|
@@ -6702,7 +6700,7 @@ var init_datalab_pdf_extract = __esm({
|
|
|
6702
6700
|
MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
6703
6701
|
MAX_ERROR_BODY_BYTES = 300;
|
|
6704
6702
|
CLEANUP_TIMEOUT_MS = 5e3;
|
|
6705
|
-
|
|
6703
|
+
CONFIG_PATH35 = getWebSearchConfigPath();
|
|
6706
6704
|
DATALAB_MODE_VALUES = /* @__PURE__ */ new Set([
|
|
6707
6705
|
"fast",
|
|
6708
6706
|
"balanced",
|
|
@@ -6805,12 +6803,12 @@ var init_gemini_pdf_extract = __esm({
|
|
|
6805
6803
|
});
|
|
6806
6804
|
|
|
6807
6805
|
// pdf-extract.ts
|
|
6808
|
-
import { existsSync as
|
|
6806
|
+
import { existsSync as existsSync41, readFileSync as readFileSync44 } from "node:fs";
|
|
6809
6807
|
import { writeFile as writeFile2, mkdir } from "node:fs/promises";
|
|
6810
|
-
import { join as
|
|
6808
|
+
import { join as join8, basename as basename3 } from "node:path";
|
|
6811
6809
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
6812
6810
|
function loadPDFConfig() {
|
|
6813
|
-
if (!
|
|
6811
|
+
if (!existsSync41(CONFIG_PATH36)) {
|
|
6814
6812
|
return {
|
|
6815
6813
|
enabled: true,
|
|
6816
6814
|
maxSizeMB: DEFAULT_PDF_MAX_SIZE_MB,
|
|
@@ -6820,13 +6818,13 @@ function loadPDFConfig() {
|
|
|
6820
6818
|
datalabTimeoutMs: DEFAULT_DATALAB_TIMEOUT_MS
|
|
6821
6819
|
};
|
|
6822
6820
|
}
|
|
6823
|
-
const rawText =
|
|
6821
|
+
const rawText = readFileSync44(CONFIG_PATH36, "utf-8");
|
|
6824
6822
|
let raw;
|
|
6825
6823
|
try {
|
|
6826
6824
|
raw = JSON.parse(rawText);
|
|
6827
6825
|
} catch (err) {
|
|
6828
6826
|
const message = err instanceof Error ? err.message : String(err);
|
|
6829
|
-
throw new Error(`Failed to parse ${
|
|
6827
|
+
throw new Error(`Failed to parse ${CONFIG_PATH36}: ${message}`);
|
|
6830
6828
|
}
|
|
6831
6829
|
const root = raw && typeof raw === "object" ? raw : {};
|
|
6832
6830
|
const pdf = root.pdf && typeof root.pdf === "object" ? root.pdf : {};
|
|
@@ -6984,7 +6982,7 @@ async function writeMarkdownResult(options) {
|
|
|
6984
6982
|
}
|
|
6985
6983
|
const content = lines.join("\n");
|
|
6986
6984
|
const outputFilename = options.filename || sanitizeFilename(options.title) + ".md";
|
|
6987
|
-
const outputPath =
|
|
6985
|
+
const outputPath = join8(options.outputDir, outputFilename);
|
|
6988
6986
|
await mkdir(options.outputDir, { recursive: true });
|
|
6989
6987
|
await writeFile2(outputPath, content, "utf-8");
|
|
6990
6988
|
return {
|
|
@@ -7035,7 +7033,7 @@ function isPDF(url, contentType) {
|
|
|
7035
7033
|
return false;
|
|
7036
7034
|
}
|
|
7037
7035
|
}
|
|
7038
|
-
var PDF_PROVIDER_VALUES, DEFAULT_PDF_MAX_SIZE_MB, MAX_PDF_MAX_SIZE_MB, MAX_DATALAB_TIMEOUT_MS, DEFAULT_MAX_PAGES, DEFAULT_OUTPUT_DIR,
|
|
7036
|
+
var PDF_PROVIDER_VALUES, DEFAULT_PDF_MAX_SIZE_MB, MAX_PDF_MAX_SIZE_MB, MAX_DATALAB_TIMEOUT_MS, DEFAULT_MAX_PAGES, DEFAULT_OUTPUT_DIR, CONFIG_PATH36, PAGE_MARKER_PATTERN2;
|
|
7039
7037
|
var init_pdf_extract = __esm({
|
|
7040
7038
|
"pdf-extract.ts"() {
|
|
7041
7039
|
init_credential_source();
|
|
@@ -7053,33 +7051,33 @@ var init_pdf_extract = __esm({
|
|
|
7053
7051
|
MAX_PDF_MAX_SIZE_MB = 50;
|
|
7054
7052
|
MAX_DATALAB_TIMEOUT_MS = 3e5;
|
|
7055
7053
|
DEFAULT_MAX_PAGES = 100;
|
|
7056
|
-
DEFAULT_OUTPUT_DIR =
|
|
7057
|
-
|
|
7054
|
+
DEFAULT_OUTPUT_DIR = join8(tmpdir3(), "pi-web-pdf");
|
|
7055
|
+
CONFIG_PATH36 = getWebSearchConfigPath();
|
|
7058
7056
|
PAGE_MARKER_PATTERN2 = /^<!-- Page (\d+) -->$/gm;
|
|
7059
7057
|
}
|
|
7060
7058
|
});
|
|
7061
7059
|
|
|
7062
7060
|
// github-issue-pr.ts
|
|
7063
7061
|
import { execFile as execFile4 } from "node:child_process";
|
|
7064
|
-
import { existsSync as
|
|
7062
|
+
import { existsSync as existsSync42, readFileSync as readFileSync45 } from "node:fs";
|
|
7065
7063
|
function loadConfig32() {
|
|
7066
7064
|
if (cachedConfig33) return cachedConfig33;
|
|
7067
7065
|
const defaults2 = { enabled: true };
|
|
7068
|
-
if (!
|
|
7066
|
+
if (!existsSync42(CONFIG_PATH37)) {
|
|
7069
7067
|
cachedConfig33 = defaults2;
|
|
7070
7068
|
return cachedConfig33;
|
|
7071
7069
|
}
|
|
7072
7070
|
let parsed;
|
|
7073
7071
|
try {
|
|
7074
|
-
parsed = JSON.parse(
|
|
7072
|
+
parsed = JSON.parse(readFileSync45(CONFIG_PATH37, "utf-8"));
|
|
7075
7073
|
} catch (err) {
|
|
7076
7074
|
const message = err instanceof Error ? err.message : String(err);
|
|
7077
|
-
throw new Error(`Failed to parse ${
|
|
7075
|
+
throw new Error(`Failed to parse ${CONFIG_PATH37}: ${message}`);
|
|
7078
7076
|
}
|
|
7079
7077
|
const root = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
7080
7078
|
const value = root.githubPrIssue;
|
|
7081
7079
|
if (value !== void 0 && (typeof value !== "object" || value === null || Array.isArray(value))) {
|
|
7082
|
-
throw new Error(`githubPrIssue in ${
|
|
7080
|
+
throw new Error(`githubPrIssue in ${CONFIG_PATH37} must be an object`);
|
|
7083
7081
|
}
|
|
7084
7082
|
const enabled = typeof value?.enabled === "boolean" ? value.enabled : true;
|
|
7085
7083
|
cachedConfig33 = { enabled };
|
|
@@ -7647,13 +7645,13 @@ async function extractGitHubIssuePr(url, signal, options) {
|
|
|
7647
7645
|
if ("data" in fallback) return renderGitHubPrIssue(fallback.data);
|
|
7648
7646
|
return fallback;
|
|
7649
7647
|
}
|
|
7650
|
-
var
|
|
7648
|
+
var CONFIG_PATH37, GH_TIMEOUT_MS, GH_TOTAL_TIMEOUT_MS, MAX_DOC_CHARS, BODY_INLINE_CHARS, COMMENT_INLINE_CHARS, MAX_INLINE_COMMENTS, MAX_INLINE_REVIEWS, MAX_INLINE_CHECKS, MAX_INLINE_FILES, MAX_INLINE_COMMITS, MAX_INLINE_REVIEW_THREADS, REST_HEADERS, PR_SUBPATHS, PR_FIELDS, PR_CORE_FIELDS, ISSUE_FIELDS, ISSUE_CORE_FIELDS, cachedConfig33;
|
|
7651
7649
|
var init_github_issue_pr = __esm({
|
|
7652
7650
|
"github-issue-pr.ts"() {
|
|
7653
7651
|
init_github_api();
|
|
7654
7652
|
init_ssrf_protection();
|
|
7655
7653
|
init_utils();
|
|
7656
|
-
|
|
7654
|
+
CONFIG_PATH37 = getWebSearchConfigPath();
|
|
7657
7655
|
GH_TIMEOUT_MS = 1e4;
|
|
7658
7656
|
GH_TOTAL_TIMEOUT_MS = 2e4;
|
|
7659
7657
|
MAX_DOC_CHARS = 15e4;
|
|
@@ -7739,15 +7737,15 @@ var init_github_issue_pr = __esm({
|
|
|
7739
7737
|
});
|
|
7740
7738
|
|
|
7741
7739
|
// feature-config.ts
|
|
7742
|
-
import { existsSync as
|
|
7740
|
+
import { existsSync as existsSync43, readFileSync as readFileSync46 } from "node:fs";
|
|
7743
7741
|
function loadFeatureConfig() {
|
|
7744
|
-
if (!
|
|
7742
|
+
if (!existsSync43(CONFIG_PATH38)) return {};
|
|
7745
7743
|
try {
|
|
7746
|
-
const raw = JSON.parse(
|
|
7744
|
+
const raw = JSON.parse(readFileSync46(CONFIG_PATH38, "utf-8"));
|
|
7747
7745
|
return raw && typeof raw === "object" ? raw : {};
|
|
7748
7746
|
} catch (err) {
|
|
7749
7747
|
const message = err instanceof Error ? err.message : String(err);
|
|
7750
|
-
throw new Error(`Failed to parse ${
|
|
7748
|
+
throw new Error(`Failed to parse ${CONFIG_PATH38}: ${message}`);
|
|
7751
7749
|
}
|
|
7752
7750
|
}
|
|
7753
7751
|
function isImageEnabled() {
|
|
@@ -7760,17 +7758,17 @@ function canAttachImages() {
|
|
|
7760
7758
|
return false;
|
|
7761
7759
|
}
|
|
7762
7760
|
}
|
|
7763
|
-
var
|
|
7761
|
+
var CONFIG_PATH38;
|
|
7764
7762
|
var init_feature_config = __esm({
|
|
7765
7763
|
"feature-config.ts"() {
|
|
7766
7764
|
init_utils();
|
|
7767
|
-
|
|
7765
|
+
CONFIG_PATH38 = getWebSearchConfigPath();
|
|
7768
7766
|
}
|
|
7769
7767
|
});
|
|
7770
7768
|
|
|
7771
7769
|
// youtube-extract.ts
|
|
7772
7770
|
import { execFileSync } from "node:child_process";
|
|
7773
|
-
import { existsSync as
|
|
7771
|
+
import { existsSync as existsSync44, readFileSync as readFileSync47 } from "node:fs";
|
|
7774
7772
|
function errorMessage25(err) {
|
|
7775
7773
|
return err instanceof Error ? err.message : String(err);
|
|
7776
7774
|
}
|
|
@@ -7791,17 +7789,17 @@ function normalizeEnabled2(value, fallback) {
|
|
|
7791
7789
|
}
|
|
7792
7790
|
function loadYouTubeConfig() {
|
|
7793
7791
|
if (cachedConfig34) return cachedConfig34;
|
|
7794
|
-
if (!
|
|
7792
|
+
if (!existsSync44(CONFIG_PATH39)) {
|
|
7795
7793
|
cachedConfig34 = { ...defaults };
|
|
7796
7794
|
return cachedConfig34;
|
|
7797
7795
|
}
|
|
7798
|
-
const rawText =
|
|
7796
|
+
const rawText = readFileSync47(CONFIG_PATH39, "utf-8");
|
|
7799
7797
|
let raw;
|
|
7800
7798
|
try {
|
|
7801
7799
|
raw = JSON.parse(rawText);
|
|
7802
7800
|
} catch (err) {
|
|
7803
7801
|
const message = err instanceof Error ? err.message : String(err);
|
|
7804
|
-
throw new Error(`Failed to parse ${
|
|
7802
|
+
throw new Error(`Failed to parse ${CONFIG_PATH39}: ${message}`);
|
|
7805
7803
|
}
|
|
7806
7804
|
const yt = raw.youtube ?? {};
|
|
7807
7805
|
cachedConfig34 = {
|
|
@@ -8003,7 +8001,7 @@ ${answer}
|
|
|
8003
8001
|
return null;
|
|
8004
8002
|
}
|
|
8005
8003
|
}
|
|
8006
|
-
var
|
|
8004
|
+
var CONFIG_PATH39, YOUTUBE_PROMPT, YOUTUBE_REGEX, defaults, cachedConfig34;
|
|
8007
8005
|
var init_youtube_extract = __esm({
|
|
8008
8006
|
"youtube-extract.ts"() {
|
|
8009
8007
|
init_activity();
|
|
@@ -8013,7 +8011,7 @@ var init_youtube_extract = __esm({
|
|
|
8013
8011
|
init_perplexity();
|
|
8014
8012
|
init_extract();
|
|
8015
8013
|
init_utils();
|
|
8016
|
-
|
|
8014
|
+
CONFIG_PATH39 = getWebSearchConfigPath();
|
|
8017
8015
|
YOUTUBE_PROMPT = `Extract the complete content of this YouTube video. Include:
|
|
8018
8016
|
1. Video title, channel name, and duration
|
|
8019
8017
|
2. A brief summary (2-3 sentences)
|
|
@@ -8125,24 +8123,23 @@ URL: `;
|
|
|
8125
8123
|
});
|
|
8126
8124
|
|
|
8127
8125
|
// crawl4ai.ts
|
|
8128
|
-
import { existsSync as
|
|
8129
|
-
import net3 from "node:net";
|
|
8126
|
+
import { existsSync as existsSync45, readFileSync as readFileSync48 } from "node:fs";
|
|
8130
8127
|
function loadConfig33() {
|
|
8131
8128
|
if (cachedConfig35) return cachedConfig35;
|
|
8132
|
-
if (!
|
|
8129
|
+
if (!existsSync45(CONFIG_PATH40)) {
|
|
8133
8130
|
cachedConfig35 = {};
|
|
8134
8131
|
return cachedConfig35;
|
|
8135
8132
|
}
|
|
8136
|
-
const raw =
|
|
8133
|
+
const raw = readFileSync48(CONFIG_PATH40, "utf8");
|
|
8137
8134
|
let parsed;
|
|
8138
8135
|
try {
|
|
8139
8136
|
parsed = JSON.parse(raw);
|
|
8140
8137
|
} catch (err) {
|
|
8141
8138
|
const message = err instanceof Error ? err.message : String(err);
|
|
8142
|
-
throw new Error(`Failed to parse ${
|
|
8139
|
+
throw new Error(`Failed to parse ${CONFIG_PATH40}: ${message}`);
|
|
8143
8140
|
}
|
|
8144
8141
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8145
|
-
throw new Error(`Invalid config in ${
|
|
8142
|
+
throw new Error(`Invalid config in ${CONFIG_PATH40}: expected a JSON object`);
|
|
8146
8143
|
}
|
|
8147
8144
|
cachedConfig35 = parsed;
|
|
8148
8145
|
return cachedConfig35;
|
|
@@ -8155,13 +8152,13 @@ function normalizeBaseUrl4(value) {
|
|
|
8155
8152
|
try {
|
|
8156
8153
|
parsed = new URL(trimmed);
|
|
8157
8154
|
} catch {
|
|
8158
|
-
throw new Error(`Invalid Crawl4AI base URL in ${
|
|
8155
|
+
throw new Error(`Invalid Crawl4AI base URL in ${CONFIG_PATH40}: expected an HTTP or HTTPS URL`);
|
|
8159
8156
|
}
|
|
8160
8157
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
8161
|
-
throw new Error(`Invalid Crawl4AI base URL in ${
|
|
8158
|
+
throw new Error(`Invalid Crawl4AI base URL in ${CONFIG_PATH40}: expected an HTTP or HTTPS URL`);
|
|
8162
8159
|
}
|
|
8163
8160
|
if (parsed.username || parsed.password) {
|
|
8164
|
-
throw new Error(`Invalid Crawl4AI base URL in ${
|
|
8161
|
+
throw new Error(`Invalid Crawl4AI base URL in ${CONFIG_PATH40}: URL credentials are not allowed`);
|
|
8165
8162
|
}
|
|
8166
8163
|
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
8167
8164
|
parsed.search = "";
|
|
@@ -8176,7 +8173,7 @@ function requireBaseUrl3() {
|
|
|
8176
8173
|
if (!baseUrl) {
|
|
8177
8174
|
throw new Error(
|
|
8178
8175
|
`Crawl4AI base URL not configured. Either:
|
|
8179
|
-
1. Set crawl4aiBaseUrl in ${
|
|
8176
|
+
1. Set crawl4aiBaseUrl in ${CONFIG_PATH40}
|
|
8180
8177
|
2. Set CRAWL4AI_BASE_URL environment variable`
|
|
8181
8178
|
);
|
|
8182
8179
|
}
|
|
@@ -8206,12 +8203,6 @@ function ssrfOptions2(options) {
|
|
|
8206
8203
|
...options?.lookup ? { lookup: options.lookup } : {}
|
|
8207
8204
|
};
|
|
8208
8205
|
}
|
|
8209
|
-
function isLoopbackApiUrl2(url) {
|
|
8210
|
-
const hostname2 = url.hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
8211
|
-
if (hostname2 === "localhost" || hostname2 === "::1") return true;
|
|
8212
|
-
if (net3.isIP(hostname2) !== 4) return false;
|
|
8213
|
-
return hostname2.split(".")[0] === "127";
|
|
8214
|
-
}
|
|
8215
8206
|
function firstHeadingTitle(markdown) {
|
|
8216
8207
|
return /^[ \t]*#[ \t]+(\S.*?)[ \t\r]*$/m.exec(markdown)?.[1] ?? "";
|
|
8217
8208
|
}
|
|
@@ -8237,7 +8228,7 @@ async function extractWithCrawl4ai(url, signal, options) {
|
|
|
8237
8228
|
try {
|
|
8238
8229
|
const response = await fetchRemoteUrl(requestUrl, init, {
|
|
8239
8230
|
...ssrf,
|
|
8240
|
-
allowLoopback:
|
|
8231
|
+
allowLoopback: isLoopbackHostname(requestUrl.hostname),
|
|
8241
8232
|
onRedirect: ({ to, init: redirectInit, response: response2 }) => {
|
|
8242
8233
|
if (to.origin !== requestUrl.origin) {
|
|
8243
8234
|
throw new Error(`Crawl4AI refused cross-origin redirect to ${to.origin}`);
|
|
@@ -8281,14 +8272,14 @@ async function extractWithCrawl4ai(url, signal, options) {
|
|
|
8281
8272
|
throw err;
|
|
8282
8273
|
}
|
|
8283
8274
|
}
|
|
8284
|
-
var
|
|
8275
|
+
var CONFIG_PATH40, EXTRACT_TIMEOUT_MS2, MARKDOWN_FILTER, cachedConfig35;
|
|
8285
8276
|
var init_crawl4ai = __esm({
|
|
8286
8277
|
"crawl4ai.ts"() {
|
|
8287
8278
|
init_activity();
|
|
8288
8279
|
init_credential_source();
|
|
8289
8280
|
init_ssrf_protection();
|
|
8290
8281
|
init_utils();
|
|
8291
|
-
|
|
8282
|
+
CONFIG_PATH40 = getWebSearchConfigPath();
|
|
8292
8283
|
EXTRACT_TIMEOUT_MS2 = 6e4;
|
|
8293
8284
|
MARKDOWN_FILTER = "fit";
|
|
8294
8285
|
cachedConfig35 = null;
|
|
@@ -8296,7 +8287,7 @@ var init_crawl4ai = __esm({
|
|
|
8296
8287
|
});
|
|
8297
8288
|
|
|
8298
8289
|
// brightdata-unlocker.ts
|
|
8299
|
-
import { existsSync as
|
|
8290
|
+
import { existsSync as existsSync46, readFileSync as readFileSync49 } from "node:fs";
|
|
8300
8291
|
function parseFailureDetail(err) {
|
|
8301
8292
|
const message = err instanceof Error ? err.message : String(err);
|
|
8302
8293
|
const position = message.match(/at position \d+(?: \(line \d+ column \d+\))?/);
|
|
@@ -8304,19 +8295,19 @@ function parseFailureDetail(err) {
|
|
|
8304
8295
|
}
|
|
8305
8296
|
function loadConfig34() {
|
|
8306
8297
|
if (cachedConfig36) return cachedConfig36;
|
|
8307
|
-
if (!
|
|
8298
|
+
if (!existsSync46(CONFIG_PATH41)) {
|
|
8308
8299
|
cachedConfig36 = {};
|
|
8309
8300
|
return cachedConfig36;
|
|
8310
8301
|
}
|
|
8311
|
-
const raw =
|
|
8302
|
+
const raw = readFileSync49(CONFIG_PATH41, "utf8");
|
|
8312
8303
|
let parsed;
|
|
8313
8304
|
try {
|
|
8314
8305
|
parsed = JSON.parse(raw);
|
|
8315
8306
|
} catch (err) {
|
|
8316
|
-
throw new Error(`Failed to parse ${
|
|
8307
|
+
throw new Error(`Failed to parse ${CONFIG_PATH41}: ${parseFailureDetail(err)}`);
|
|
8317
8308
|
}
|
|
8318
8309
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8319
|
-
throw new Error(`Invalid config in ${
|
|
8310
|
+
throw new Error(`Invalid config in ${CONFIG_PATH41}: expected a JSON object`);
|
|
8320
8311
|
}
|
|
8321
8312
|
cachedConfig36 = parsed;
|
|
8322
8313
|
return cachedConfig36;
|
|
@@ -8331,7 +8322,7 @@ function zoneSetting() {
|
|
|
8331
8322
|
const fromEnv = process.env.BRIGHTDATA_UNLOCKER_ZONE;
|
|
8332
8323
|
if (typeof fromEnv === "string" && fromEnv.trim()) return { raw: fromEnv.trim(), label: "BRIGHTDATA_UNLOCKER_ZONE" };
|
|
8333
8324
|
const configured = loadConfig34().brightdataUnlockerZone;
|
|
8334
|
-
if (typeof configured === "string" && configured.trim()) return { raw: configured.trim(), label: `brightdataUnlockerZone in ${
|
|
8325
|
+
if (typeof configured === "string" && configured.trim()) return { raw: configured.trim(), label: `brightdataUnlockerZone in ${CONFIG_PATH41}` };
|
|
8335
8326
|
return null;
|
|
8336
8327
|
}
|
|
8337
8328
|
function getZone() {
|
|
@@ -8349,7 +8340,7 @@ function requireZone() {
|
|
|
8349
8340
|
}
|
|
8350
8341
|
throw new Error(
|
|
8351
8342
|
`Bright Data Web Unlocker zone not configured. Either:
|
|
8352
|
-
1. Set brightdataUnlockerZone in ${
|
|
8343
|
+
1. Set brightdataUnlockerZone in ${CONFIG_PATH41}
|
|
8353
8344
|
2. Set BRIGHTDATA_UNLOCKER_ZONE environment variable
|
|
8354
8345
|
The zone must be of type "unblocker"; a SERP zone will not serve Web Unlocker requests.`
|
|
8355
8346
|
);
|
|
@@ -8364,7 +8355,7 @@ async function getApiKey26(signal) {
|
|
|
8364
8355
|
if (!apiKey) {
|
|
8365
8356
|
throw new Error(
|
|
8366
8357
|
`Bright Data API key not found. Either:
|
|
8367
|
-
1. Create ${
|
|
8358
|
+
1. Create ${CONFIG_PATH41} with { "brightdataApiKey": "your-key" }
|
|
8368
8359
|
2. Set BRIGHTDATA_API_KEY environment variable
|
|
8369
8360
|
Get a key at https://brightdata.com/cp/setting/users`
|
|
8370
8361
|
);
|
|
@@ -8473,14 +8464,14 @@ async function extractWithBrightDataUnlocker(url, signal, options) {
|
|
|
8473
8464
|
if (!content) return null;
|
|
8474
8465
|
return { url, title: headingTitle(content), content, error: null };
|
|
8475
8466
|
}
|
|
8476
|
-
var
|
|
8467
|
+
var CONFIG_PATH41, BRIGHTDATA_REQUEST_URL, EXTRACT_TIMEOUT_MS3, DEFAULT_MAX_REDIRECTS3, REDIRECT_STATUSES3, ZONE_PATTERN2, cachedConfig36;
|
|
8477
8468
|
var init_brightdata_unlocker = __esm({
|
|
8478
8469
|
"brightdata-unlocker.ts"() {
|
|
8479
8470
|
init_activity();
|
|
8480
8471
|
init_credential_source();
|
|
8481
8472
|
init_ssrf_protection();
|
|
8482
8473
|
init_utils();
|
|
8483
|
-
|
|
8474
|
+
CONFIG_PATH41 = getWebSearchConfigPath();
|
|
8484
8475
|
BRIGHTDATA_REQUEST_URL = "https://api.brightdata.com/request";
|
|
8485
8476
|
EXTRACT_TIMEOUT_MS3 = 6e4;
|
|
8486
8477
|
DEFAULT_MAX_REDIRECTS3 = 5;
|
|
@@ -8492,9 +8483,9 @@ var init_brightdata_unlocker = __esm({
|
|
|
8492
8483
|
|
|
8493
8484
|
// video-extract.ts
|
|
8494
8485
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
8495
|
-
import { existsSync as
|
|
8486
|
+
import { existsSync as existsSync47, readFileSync as readFileSync50, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
|
|
8496
8487
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
8497
|
-
import { resolve, extname as extname2, basename as basename4, join as
|
|
8488
|
+
import { resolve, extname as extname2, basename as basename4, join as join9, dirname as dirname3 } from "node:path";
|
|
8498
8489
|
function shouldRethrow3(err) {
|
|
8499
8490
|
const message = err instanceof Error ? err.message : String(err);
|
|
8500
8491
|
return message.startsWith("Failed to parse ");
|
|
@@ -8513,17 +8504,17 @@ function normalizeMaxSizeMB(value, fallback) {
|
|
|
8513
8504
|
}
|
|
8514
8505
|
function loadVideoConfig() {
|
|
8515
8506
|
if (cachedVideoConfig) return cachedVideoConfig;
|
|
8516
|
-
if (!
|
|
8507
|
+
if (!existsSync47(CONFIG_PATH42)) {
|
|
8517
8508
|
cachedVideoConfig = { ...VIDEO_CONFIG_DEFAULTS };
|
|
8518
8509
|
return cachedVideoConfig;
|
|
8519
8510
|
}
|
|
8520
|
-
const rawText =
|
|
8511
|
+
const rawText = readFileSync50(CONFIG_PATH42, "utf-8");
|
|
8521
8512
|
let raw;
|
|
8522
8513
|
try {
|
|
8523
8514
|
raw = JSON.parse(rawText);
|
|
8524
8515
|
} catch (err) {
|
|
8525
8516
|
const message = err instanceof Error ? err.message : String(err);
|
|
8526
|
-
throw new Error(`Failed to parse ${
|
|
8517
|
+
throw new Error(`Failed to parse ${CONFIG_PATH42}: ${message}`);
|
|
8527
8518
|
}
|
|
8528
8519
|
const v = raw.video ?? {};
|
|
8529
8520
|
cachedVideoConfig = {
|
|
@@ -8569,14 +8560,14 @@ function isVideoFile(input) {
|
|
|
8569
8560
|
}
|
|
8570
8561
|
function resolveFilePath(filePath) {
|
|
8571
8562
|
const absolutePath = resolve(filePath);
|
|
8572
|
-
if (
|
|
8573
|
-
const dir =
|
|
8563
|
+
if (existsSync47(absolutePath)) return absolutePath;
|
|
8564
|
+
const dir = dirname3(absolutePath);
|
|
8574
8565
|
const base = basename4(absolutePath);
|
|
8575
|
-
if (!
|
|
8566
|
+
if (!existsSync47(dir)) return null;
|
|
8576
8567
|
try {
|
|
8577
8568
|
const normalizedBase = normalizeSpaces(base);
|
|
8578
8569
|
const match = readdirSync4(dir).find((f) => normalizeSpaces(f) === normalizedBase);
|
|
8579
|
-
return match ?
|
|
8570
|
+
return match ? join9(dir, match) : null;
|
|
8580
8571
|
} catch {
|
|
8581
8572
|
return null;
|
|
8582
8573
|
}
|
|
@@ -8770,7 +8761,7 @@ function deleteGeminiFile(fileName, apiKey) {
|
|
|
8770
8761
|
function extractVideoTitle(text2, filePath) {
|
|
8771
8762
|
return extractHeadingTitle(text2) ?? basename4(filePath, extname2(filePath));
|
|
8772
8763
|
}
|
|
8773
|
-
var
|
|
8764
|
+
var CONFIG_PATH42, DEFAULT_VIDEO_PROMPT, VIDEO_EXTENSIONS, VIDEO_CONFIG_DEFAULTS, cachedVideoConfig;
|
|
8774
8765
|
var init_video_extract = __esm({
|
|
8775
8766
|
"video-extract.ts"() {
|
|
8776
8767
|
init_activity();
|
|
@@ -8779,7 +8770,7 @@ var init_video_extract = __esm({
|
|
|
8779
8770
|
init_gemini_api();
|
|
8780
8771
|
init_extract();
|
|
8781
8772
|
init_utils();
|
|
8782
|
-
|
|
8773
|
+
CONFIG_PATH42 = getWebSearchConfigPath();
|
|
8783
8774
|
DEFAULT_VIDEO_PROMPT = `Extract the complete content of this video. Include:
|
|
8784
8775
|
1. Video title (infer from content if not explicit), duration
|
|
8785
8776
|
2. A brief summary (2-3 sentences)
|
|
@@ -9264,13 +9255,13 @@ __export(extract_exports, {
|
|
|
9264
9255
|
readPDFResponseBuffer: () => readPDFResponseBuffer,
|
|
9265
9256
|
resolveFetchTimeoutMs: () => resolveFetchTimeoutMs
|
|
9266
9257
|
});
|
|
9267
|
-
import { existsSync as
|
|
9258
|
+
import { existsSync as existsSync48, readFileSync as readFileSync51 } from "node:fs";
|
|
9268
9259
|
import pLimit from "p-limit";
|
|
9269
9260
|
function loadFetchTimeoutMs() {
|
|
9270
|
-
if (!
|
|
9261
|
+
if (!existsSync48(WEB_SEARCH_CONFIG_PATH3)) return DEFAULT_TIMEOUT_MS3;
|
|
9271
9262
|
let raw;
|
|
9272
9263
|
try {
|
|
9273
|
-
raw = JSON.parse(
|
|
9264
|
+
raw = JSON.parse(readFileSync51(WEB_SEARCH_CONFIG_PATH3, "utf-8"));
|
|
9274
9265
|
} catch (err) {
|
|
9275
9266
|
const message = err instanceof Error ? err.message : String(err);
|
|
9276
9267
|
throw new Error(`Failed to parse ${WEB_SEARCH_CONFIG_PATH3}: ${message}`);
|
|
@@ -9396,12 +9387,12 @@ async function fetchAuthenticatedRemoteUrl(url, init, validationOptions, profile
|
|
|
9396
9387
|
throw new Error(`Too many redirects fetching ${current.toString()}`);
|
|
9397
9388
|
}
|
|
9398
9389
|
function loadFetchRouting() {
|
|
9399
|
-
if (!
|
|
9390
|
+
if (!existsSync48(WEB_SEARCH_CONFIG_PATH3)) {
|
|
9400
9391
|
return { providers: DEFAULT_FETCH_PROVIDER_ORDER, allowRemoteHostedProviders: false };
|
|
9401
9392
|
}
|
|
9402
9393
|
let raw;
|
|
9403
9394
|
try {
|
|
9404
|
-
const parsed = JSON.parse(
|
|
9395
|
+
const parsed = JSON.parse(readFileSync51(WEB_SEARCH_CONFIG_PATH3, "utf-8"));
|
|
9405
9396
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9406
9397
|
throw new Error("expected a JSON object");
|
|
9407
9398
|
}
|
|
@@ -10461,7 +10452,7 @@ var init_extract = __esm({
|
|
|
10461
10452
|
|
|
10462
10453
|
// index.ts
|
|
10463
10454
|
import { Box, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
10464
|
-
import { Type } from "typebox";
|
|
10455
|
+
import { Type as Type2 } from "typebox";
|
|
10465
10456
|
import pLimit2 from "p-limit";
|
|
10466
10457
|
|
|
10467
10458
|
// fetch-params.ts
|
|
@@ -11048,7 +11039,7 @@ init_gemini_api();
|
|
|
11048
11039
|
init_gemini_adc();
|
|
11049
11040
|
init_gemini_web();
|
|
11050
11041
|
init_perplexity();
|
|
11051
|
-
import { existsSync as
|
|
11042
|
+
import { existsSync as existsSync39, readFileSync as readFileSync40 } from "node:fs";
|
|
11052
11043
|
|
|
11053
11044
|
// exa.ts
|
|
11054
11045
|
init_activity();
|
|
@@ -15048,6 +15039,118 @@ async function searchWithSerply(query, options = {}) {
|
|
|
15048
15039
|
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
15049
15040
|
}
|
|
15050
15041
|
|
|
15042
|
+
// baizhi.ts
|
|
15043
|
+
init_activity();
|
|
15044
|
+
init_credential_source();
|
|
15045
|
+
init_domain_filter_normalization();
|
|
15046
|
+
init_search_result_count_normalization();
|
|
15047
|
+
init_utils();
|
|
15048
|
+
import { existsSync as existsSync36, readFileSync as readFileSync37 } from "node:fs";
|
|
15049
|
+
import { isIP as isIP2 } from "node:net";
|
|
15050
|
+
var MCP_URL = "https://agent-toolkit.app.baizhi.cloud/mcp";
|
|
15051
|
+
var SEARCH_TIMEOUT_MS25 = 6e4;
|
|
15052
|
+
var CONFIG_PATH31 = getWebSearchConfigPath();
|
|
15053
|
+
function credentialOptions() {
|
|
15054
|
+
let config = {};
|
|
15055
|
+
if (existsSync36(CONFIG_PATH31)) {
|
|
15056
|
+
try {
|
|
15057
|
+
const parsed = JSON.parse(readFileSync37(CONFIG_PATH31, "utf8"));
|
|
15058
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error();
|
|
15059
|
+
config = parsed;
|
|
15060
|
+
} catch {
|
|
15061
|
+
throw new Error(`Invalid configuration in ${CONFIG_PATH31}: expected a JSON object`);
|
|
15062
|
+
}
|
|
15063
|
+
}
|
|
15064
|
+
return { provider: "Baizhi", configuredValue: config.baizhiApiKey, environmentValue: process.env.BAIZHI_API_KEY };
|
|
15065
|
+
}
|
|
15066
|
+
function isBaizhiAvailable() {
|
|
15067
|
+
return hasCredentialSource(credentialOptions());
|
|
15068
|
+
}
|
|
15069
|
+
function searchArguments(query, options) {
|
|
15070
|
+
if (!query.trim()) throw new Error("Baizhi search query must not be empty");
|
|
15071
|
+
const domains = [];
|
|
15072
|
+
const excludeDomains = [];
|
|
15073
|
+
for (const raw of options.domainFilter ?? []) {
|
|
15074
|
+
const bare = raw.trim().replace(/^-/, "").trim();
|
|
15075
|
+
const domain = isIP2(bare) ? bare : normalizeDomain(raw);
|
|
15076
|
+
if (!domain) throw new Error("Baizhi domain filter must contain a valid domain");
|
|
15077
|
+
const target = raw.trim().startsWith("-") ? excludeDomains : domains;
|
|
15078
|
+
if (!target.includes(domain)) target.push(domain);
|
|
15079
|
+
}
|
|
15080
|
+
return {
|
|
15081
|
+
query: query.trim(),
|
|
15082
|
+
count: normalizeSearchResultCount(options.numResults),
|
|
15083
|
+
need_summary: false,
|
|
15084
|
+
// The published discovery contract defaults to month, not unrestricted time.
|
|
15085
|
+
time_range: options.recencyFilter ?? "month",
|
|
15086
|
+
...domains.length || excludeDomains.length ? { filter: {
|
|
15087
|
+
...domains.length ? { domains } : {},
|
|
15088
|
+
...excludeDomains.length ? { exclude_domains: excludeDomains } : {}
|
|
15089
|
+
} } : {}
|
|
15090
|
+
};
|
|
15091
|
+
}
|
|
15092
|
+
async function searchWithBaizhi(query, options = {}) {
|
|
15093
|
+
const args = searchArguments(query, options);
|
|
15094
|
+
const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS25);
|
|
15095
|
+
const signal = options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal;
|
|
15096
|
+
if (signal.aborted) throw new Error("Aborted");
|
|
15097
|
+
const apiKey = await resolveCredential({ ...credentialOptions(), signal });
|
|
15098
|
+
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/");
|
|
15099
|
+
let headers;
|
|
15100
|
+
try {
|
|
15101
|
+
headers = new Headers({ Authorization: `Bearer ${apiKey}` });
|
|
15102
|
+
} catch {
|
|
15103
|
+
throw new Error("Baizhi credential resolution failed: invalid-header-value");
|
|
15104
|
+
}
|
|
15105
|
+
const [{ Client }, { StreamableHTTPClientTransport }] = await Promise.all([
|
|
15106
|
+
import("@modelcontextprotocol/sdk/client/index.js"),
|
|
15107
|
+
import("@modelcontextprotocol/sdk/client/streamableHttp.js")
|
|
15108
|
+
]);
|
|
15109
|
+
let cleaningUp = false;
|
|
15110
|
+
const transport = new StreamableHTTPClientTransport(new URL(MCP_URL), {
|
|
15111
|
+
requestInit: { headers },
|
|
15112
|
+
fetch: async (url, init) => {
|
|
15113
|
+
if (String(url) !== MCP_URL) throw new Error("Baizhi endpoint changed unexpectedly");
|
|
15114
|
+
const requestSignal15 = cleaningUp ? AbortSignal.timeout(1500) : signal;
|
|
15115
|
+
return fetch(url, { ...init, redirect: "error", signal: !cleaningUp && init?.signal ? AbortSignal.any([init.signal, requestSignal15]) : requestSignal15 });
|
|
15116
|
+
},
|
|
15117
|
+
reconnectionOptions: { maxRetries: 0, initialReconnectionDelay: 1e3, maxReconnectionDelay: 1e3, reconnectionDelayGrowFactor: 1 }
|
|
15118
|
+
});
|
|
15119
|
+
const client = new Client({ name: "pi-web-access-baizhi", version: "1.0.0" });
|
|
15120
|
+
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
15121
|
+
try {
|
|
15122
|
+
await client.connect(transport, { signal, timeout: SEARCH_TIMEOUT_MS25 });
|
|
15123
|
+
const result = await client.callTool({ name: "websearch_search", arguments: args }, void 0, { signal, timeout: SEARCH_TIMEOUT_MS25 });
|
|
15124
|
+
if (result.isError) throw new Error("Baizhi search tool returned an error");
|
|
15125
|
+
const parts = [];
|
|
15126
|
+
if (result.structuredContent !== void 0) parts.push(JSON.stringify(result.structuredContent, null, 2));
|
|
15127
|
+
for (const item of Array.isArray(result.content) ? result.content : []) {
|
|
15128
|
+
if (item.type === "text" && typeof item.text === "string" && item.text.trim()) parts.push(item.text);
|
|
15129
|
+
}
|
|
15130
|
+
if (!parts.length) throw new Error("Baizhi returned invalid response: no search text or structured content");
|
|
15131
|
+
const answer = redactCredential(redactCredential(parts.join("\n\n"), apiKey), JSON.stringify(apiKey).slice(1, -1));
|
|
15132
|
+
activityMonitor.logComplete(activityId, 200);
|
|
15133
|
+
return { answer, results: [] };
|
|
15134
|
+
} catch (err) {
|
|
15135
|
+
let message;
|
|
15136
|
+
if (options.signal?.aborted) message = "Aborted";
|
|
15137
|
+
else if (timeoutSignal.aborted) message = "Baizhi request timed out after 60s";
|
|
15138
|
+
else {
|
|
15139
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
15140
|
+
message = typeof code === "number" && Number.isInteger(code) && code >= 400 && code <= 599 ? `Baizhi HTTP ${code} request failed; check your API key, account and service status` : err instanceof TypeError ? "Baizhi network request failed" : err instanceof SyntaxError || code === -32700 || code === -32602 ? "Baizhi returned invalid response" : err instanceof Error && /^Baizhi (search tool returned an error|returned invalid response:)/.test(err.message) ? err.message : "Baizhi MCP request failed; check your connection, API key and account status";
|
|
15141
|
+
}
|
|
15142
|
+
activityMonitor.logError(activityId, message);
|
|
15143
|
+
throw new Error(message);
|
|
15144
|
+
} finally {
|
|
15145
|
+
cleaningUp = true;
|
|
15146
|
+
try {
|
|
15147
|
+
await transport.terminateSession();
|
|
15148
|
+
} catch {
|
|
15149
|
+
}
|
|
15150
|
+
await client.close();
|
|
15151
|
+
}
|
|
15152
|
+
}
|
|
15153
|
+
|
|
15051
15154
|
// valyu.ts
|
|
15052
15155
|
init_activity();
|
|
15053
15156
|
init_domain_filter_normalization();
|
|
@@ -15055,29 +15158,29 @@ init_search_answer_formatting();
|
|
|
15055
15158
|
init_search_result_count_normalization();
|
|
15056
15159
|
init_credential_source();
|
|
15057
15160
|
init_utils();
|
|
15058
|
-
import { existsSync as
|
|
15161
|
+
import { existsSync as existsSync37, readFileSync as readFileSync38 } from "node:fs";
|
|
15059
15162
|
var VALYU_SEARCH_URL = "https://api.valyu.ai/v1/search";
|
|
15060
|
-
var
|
|
15061
|
-
var
|
|
15163
|
+
var CONFIG_PATH32 = getWebSearchConfigPath();
|
|
15164
|
+
var SEARCH_TIMEOUT_MS26 = 6e4;
|
|
15062
15165
|
var MAX_SNIPPET_CHARS = 2500;
|
|
15063
15166
|
var MAX_CONTENT_CHARS = 4e3;
|
|
15064
15167
|
var cachedConfig30 = null;
|
|
15065
15168
|
function loadConfig29() {
|
|
15066
15169
|
if (cachedConfig30) return cachedConfig30;
|
|
15067
|
-
if (!
|
|
15170
|
+
if (!existsSync37(CONFIG_PATH32)) {
|
|
15068
15171
|
cachedConfig30 = {};
|
|
15069
15172
|
return cachedConfig30;
|
|
15070
15173
|
}
|
|
15071
|
-
const raw =
|
|
15174
|
+
const raw = readFileSync38(CONFIG_PATH32, "utf-8");
|
|
15072
15175
|
let parsed;
|
|
15073
15176
|
try {
|
|
15074
15177
|
parsed = JSON.parse(raw);
|
|
15075
15178
|
} catch (err) {
|
|
15076
15179
|
const message = err instanceof Error ? err.message : String(err);
|
|
15077
|
-
throw new Error(`Failed to parse ${
|
|
15180
|
+
throw new Error(`Failed to parse ${CONFIG_PATH32}: ${message}`);
|
|
15078
15181
|
}
|
|
15079
15182
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15080
|
-
throw new Error(`Invalid config in ${
|
|
15183
|
+
throw new Error(`Invalid config in ${CONFIG_PATH32}: expected a JSON object`);
|
|
15081
15184
|
}
|
|
15082
15185
|
cachedConfig30 = parsed;
|
|
15083
15186
|
return cachedConfig30;
|
|
@@ -15095,7 +15198,7 @@ async function requireApiKey12(signal) {
|
|
|
15095
15198
|
if (!apiKey) {
|
|
15096
15199
|
throw new Error(
|
|
15097
15200
|
`Valyu API key not found. Either:
|
|
15098
|
-
1. Create ${
|
|
15201
|
+
1. Create ${CONFIG_PATH32} with { "valyuApiKey": "your-key" }
|
|
15099
15202
|
2. Set VALYU_API_KEY environment variable
|
|
15100
15203
|
Get a key at https://platform.valyu.ai`
|
|
15101
15204
|
);
|
|
@@ -15152,7 +15255,7 @@ async function searchWithValyu(query, options = {}) {
|
|
|
15152
15255
|
method: "POST",
|
|
15153
15256
|
headers: { "x-api-key": apiKey, "Content-Type": "application/json", Accept: "application/json" },
|
|
15154
15257
|
body: JSON.stringify({ query, max_num_results: numResults, ...mapDomainFilter8(options.domainFilter), ...startDate ? { start_date: startDate } : {} }),
|
|
15155
|
-
signal: options.signal ? AbortSignal.any([AbortSignal.timeout(
|
|
15258
|
+
signal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS26), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS26)
|
|
15156
15259
|
});
|
|
15157
15260
|
} catch (err) {
|
|
15158
15261
|
const message = errorMessage21(err);
|
|
@@ -15209,7 +15312,7 @@ init_search_result_count_normalization();
|
|
|
15209
15312
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
15210
15313
|
var KIMI_SEARCH_URL = "https://api.kimi.com/coding/v1/search";
|
|
15211
15314
|
var KIMI_PROVIDERS = ["kimi-coding", "kimi-code"];
|
|
15212
|
-
var
|
|
15315
|
+
var SEARCH_TIMEOUT_MS27 = 3e4;
|
|
15213
15316
|
function buildRequestHeaders(auth) {
|
|
15214
15317
|
const headers = new Headers();
|
|
15215
15318
|
for (const [name, value] of Object.entries(auth.headers)) {
|
|
@@ -15324,7 +15427,7 @@ async function searchWithKimi(query, options = {}, ctx) {
|
|
|
15324
15427
|
throw new Error("Kimi Code web search unavailable. Run /login kimi-coding to authenticate a Kimi Code Plan account.");
|
|
15325
15428
|
}
|
|
15326
15429
|
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
15327
|
-
const timeoutSignal = AbortSignal.timeout(
|
|
15430
|
+
const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS27);
|
|
15328
15431
|
const requestSignal15 = options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal;
|
|
15329
15432
|
try {
|
|
15330
15433
|
const response = await fetch(KIMI_SEARCH_URL, {
|
|
@@ -15376,30 +15479,30 @@ init_activity();
|
|
|
15376
15479
|
init_domain_filter_normalization();
|
|
15377
15480
|
init_credential_source();
|
|
15378
15481
|
init_utils();
|
|
15379
|
-
import { existsSync as
|
|
15482
|
+
import { existsSync as existsSync38, readFileSync as readFileSync39 } from "node:fs";
|
|
15380
15483
|
var MISTRAL_CONVERSATIONS_URL = "https://api.mistral.ai/v1/conversations";
|
|
15381
|
-
var
|
|
15382
|
-
var
|
|
15484
|
+
var CONFIG_PATH33 = getWebSearchConfigPath();
|
|
15485
|
+
var SEARCH_TIMEOUT_MS28 = 6e4;
|
|
15383
15486
|
var DEFAULT_SEARCH_MODEL = "mistral-small-latest";
|
|
15384
15487
|
var DEFAULT_SEARCH_TOOL = "web_search";
|
|
15385
15488
|
var MAX_RESULTS = 20;
|
|
15386
15489
|
var cachedConfig31 = null;
|
|
15387
15490
|
function loadConfig30() {
|
|
15388
15491
|
if (cachedConfig31) return cachedConfig31;
|
|
15389
|
-
if (!
|
|
15492
|
+
if (!existsSync38(CONFIG_PATH33)) {
|
|
15390
15493
|
cachedConfig31 = {};
|
|
15391
15494
|
return cachedConfig31;
|
|
15392
15495
|
}
|
|
15393
|
-
const raw =
|
|
15496
|
+
const raw = readFileSync39(CONFIG_PATH33, "utf-8");
|
|
15394
15497
|
let parsed;
|
|
15395
15498
|
try {
|
|
15396
15499
|
parsed = JSON.parse(raw);
|
|
15397
15500
|
} catch (err) {
|
|
15398
15501
|
const message = err instanceof Error ? err.message : String(err);
|
|
15399
|
-
throw new Error(`Failed to parse ${
|
|
15502
|
+
throw new Error(`Failed to parse ${CONFIG_PATH33}: ${message}`);
|
|
15400
15503
|
}
|
|
15401
15504
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15402
|
-
throw new Error(`Invalid config in ${
|
|
15505
|
+
throw new Error(`Invalid config in ${CONFIG_PATH33}: expected a JSON object`);
|
|
15403
15506
|
}
|
|
15404
15507
|
cachedConfig31 = parsed;
|
|
15405
15508
|
return cachedConfig31;
|
|
@@ -15407,14 +15510,14 @@ function loadConfig30() {
|
|
|
15407
15510
|
function resolveSearchModel(value) {
|
|
15408
15511
|
if (value === void 0) return DEFAULT_SEARCH_MODEL;
|
|
15409
15512
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
15410
|
-
throw new Error(`mistralSearchModel in ${
|
|
15513
|
+
throw new Error(`mistralSearchModel in ${CONFIG_PATH33} must be a non-empty string`);
|
|
15411
15514
|
}
|
|
15412
15515
|
return value.trim();
|
|
15413
15516
|
}
|
|
15414
15517
|
function resolveSearchTool(value) {
|
|
15415
15518
|
if (value === void 0) return DEFAULT_SEARCH_TOOL;
|
|
15416
15519
|
if (value !== "web_search" && value !== "web_search_premium") {
|
|
15417
|
-
throw new Error(`mistralSearchTool in ${
|
|
15520
|
+
throw new Error(`mistralSearchTool in ${CONFIG_PATH33} must be either web_search or web_search_premium`);
|
|
15418
15521
|
}
|
|
15419
15522
|
return value;
|
|
15420
15523
|
}
|
|
@@ -15528,7 +15631,7 @@ async function getApiKey25(signal) {
|
|
|
15528
15631
|
if (!apiKey) {
|
|
15529
15632
|
throw new Error(
|
|
15530
15633
|
`Mistral API key not found. Either:
|
|
15531
|
-
1. Create ${
|
|
15634
|
+
1. Create ${CONFIG_PATH33} with { "mistralApiKey": "your-key" }
|
|
15532
15635
|
2. Set MISTRAL_API_KEY environment variable
|
|
15533
15636
|
Get a key at https://console.mistral.ai/api-keys`
|
|
15534
15637
|
);
|
|
@@ -15555,7 +15658,7 @@ async function searchWithMistral(query, options = {}) {
|
|
|
15555
15658
|
const tool = resolveSearchTool(config.mistralSearchTool);
|
|
15556
15659
|
const numResults = normalizeCount4(options.numResults);
|
|
15557
15660
|
const apiKey = await getApiKey25(options.signal);
|
|
15558
|
-
const requestSignal15 = options.signal ? AbortSignal.any([AbortSignal.timeout(
|
|
15661
|
+
const requestSignal15 = options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS28), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS28);
|
|
15559
15662
|
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
15560
15663
|
try {
|
|
15561
15664
|
const response = await fetch(MISTRAL_CONVERSATIONS_URL, {
|
|
@@ -15602,7 +15705,7 @@ async function searchWithMistral(query, options = {}) {
|
|
|
15602
15705
|
|
|
15603
15706
|
// gemini-search.ts
|
|
15604
15707
|
init_utils();
|
|
15605
|
-
var RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply", "valyu", "bocha", "xcrawl"];
|
|
15708
|
+
var RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply", "valyu", "bocha", "xcrawl", "baizhi"];
|
|
15606
15709
|
var SEARCH_PROVIDERS = ["auto", "all", ...RESOLVED_SEARCH_PROVIDERS];
|
|
15607
15710
|
var SearchProviderError = class extends Error {
|
|
15608
15711
|
provider;
|
|
@@ -15618,18 +15721,18 @@ var SearchProviderError = class extends Error {
|
|
|
15618
15721
|
this.causeError = cause;
|
|
15619
15722
|
}
|
|
15620
15723
|
};
|
|
15621
|
-
var
|
|
15724
|
+
var CONFIG_PATH34 = getWebSearchConfigPath();
|
|
15622
15725
|
var DEFAULT_SEARCH_MODEL2 = "gemini-3.6-flash";
|
|
15623
15726
|
var ALL_SEARCH_PROVIDERS = ["searxng", "openai", "exa", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "ollama", "perplexity", "gemini", "bocha"];
|
|
15624
15727
|
var VALID_ROUTING_KINDS = ["transient", "quota", "network", "invalid-response", "unsupported"];
|
|
15625
15728
|
var cachedSearchConfig = null;
|
|
15626
15729
|
function getSearchConfig() {
|
|
15627
15730
|
if (cachedSearchConfig) return cachedSearchConfig;
|
|
15628
|
-
if (!
|
|
15731
|
+
if (!existsSync39(CONFIG_PATH34)) {
|
|
15629
15732
|
cachedSearchConfig = { searchProvider: "auto", searchProviderConfigured: false };
|
|
15630
15733
|
return cachedSearchConfig;
|
|
15631
15734
|
}
|
|
15632
|
-
const rawText =
|
|
15735
|
+
const rawText = readFileSync40(CONFIG_PATH34, "utf-8");
|
|
15633
15736
|
let raw;
|
|
15634
15737
|
try {
|
|
15635
15738
|
const parsed = JSON.parse(rawText);
|
|
@@ -15639,25 +15742,25 @@ function getSearchConfig() {
|
|
|
15639
15742
|
raw = parsed;
|
|
15640
15743
|
} catch (err) {
|
|
15641
15744
|
const message = err instanceof Error ? err.message : String(err);
|
|
15642
|
-
throw new Error(`Failed to parse ${
|
|
15745
|
+
throw new Error(`Failed to parse ${CONFIG_PATH34}: ${message}`);
|
|
15643
15746
|
}
|
|
15644
15747
|
const searchModel = normalizeSearchModel(raw.searchModel);
|
|
15645
15748
|
const webSearch = raw.webSearch;
|
|
15646
15749
|
if (webSearch !== void 0 && (!webSearch || typeof webSearch !== "object" || Array.isArray(webSearch))) {
|
|
15647
|
-
throw new Error(`webSearch in ${
|
|
15750
|
+
throw new Error(`webSearch in ${CONFIG_PATH34} must be an object`);
|
|
15648
15751
|
}
|
|
15649
|
-
const allowedProviders = webSearch && Object.hasOwn(webSearch, "allowedProviders") ? normalizeResolvedProviderList(webSearch.allowedProviders, `webSearch.allowedProviders in ${
|
|
15752
|
+
const allowedProviders = webSearch && Object.hasOwn(webSearch, "allowedProviders") ? normalizeResolvedProviderList(webSearch.allowedProviders, `webSearch.allowedProviders in ${CONFIG_PATH34}`) : void 0;
|
|
15650
15753
|
const searchProviderConfigured = Object.hasOwn(raw, "searchProvider") || Object.hasOwn(raw, "provider");
|
|
15651
|
-
const searchProvider = normalizeSearchProviderSelection(raw.searchProvider ?? raw.provider, `provider in ${
|
|
15754
|
+
const searchProvider = normalizeSearchProviderSelection(raw.searchProvider ?? raw.provider, `provider in ${CONFIG_PATH34}`);
|
|
15652
15755
|
const searchRouting = Object.hasOwn(raw, "searchRouting") ? normalizeSearchRouting(raw.searchRouting) : void 0;
|
|
15653
15756
|
if (allowedProviders) {
|
|
15654
15757
|
if (Object.hasOwn(raw, "searchProvider")) {
|
|
15655
|
-
assertSearchProviderSelectionAllowed(normalizeSearchProviderSelection(raw.searchProvider), `searchProvider in ${
|
|
15758
|
+
assertSearchProviderSelectionAllowed(normalizeSearchProviderSelection(raw.searchProvider), `searchProvider in ${CONFIG_PATH34}`, allowedProviders);
|
|
15656
15759
|
}
|
|
15657
15760
|
if (Object.hasOwn(raw, "provider")) {
|
|
15658
|
-
assertSearchProviderSelectionAllowed(normalizeSearchProviderSelection(raw.provider), `provider in ${
|
|
15761
|
+
assertSearchProviderSelectionAllowed(normalizeSearchProviderSelection(raw.provider), `provider in ${CONFIG_PATH34}`, allowedProviders);
|
|
15659
15762
|
}
|
|
15660
|
-
if (searchRouting) assertSearchProviderSelectionAllowed(searchRouting.providers, `searchRouting.providers in ${
|
|
15763
|
+
if (searchRouting) assertSearchProviderSelectionAllowed(searchRouting.providers, `searchRouting.providers in ${CONFIG_PATH34}`, allowedProviders);
|
|
15661
15764
|
}
|
|
15662
15765
|
cachedSearchConfig = {
|
|
15663
15766
|
searchProvider,
|
|
@@ -15670,21 +15773,21 @@ function getSearchConfig() {
|
|
|
15670
15773
|
}
|
|
15671
15774
|
function normalizeSearchRouting(value) {
|
|
15672
15775
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
15673
|
-
throw new Error(`searchRouting in ${
|
|
15776
|
+
throw new Error(`searchRouting in ${CONFIG_PATH34} must be an object`);
|
|
15674
15777
|
}
|
|
15675
15778
|
const raw = value;
|
|
15676
|
-
const providers = normalizeResolvedProviderList(raw.providers, `searchRouting.providers in ${
|
|
15779
|
+
const providers = normalizeResolvedProviderList(raw.providers, `searchRouting.providers in ${CONFIG_PATH34}`);
|
|
15677
15780
|
const useCurrentModel = raw.useCurrentModel;
|
|
15678
15781
|
if (useCurrentModel !== void 0 && typeof useCurrentModel !== "boolean") {
|
|
15679
|
-
throw new Error(`searchRouting.useCurrentModel in ${
|
|
15782
|
+
throw new Error(`searchRouting.useCurrentModel in ${CONFIG_PATH34} must be a boolean`);
|
|
15680
15783
|
}
|
|
15681
15784
|
if (!Array.isArray(raw.fallbackOn) || raw.fallbackOn.length === 0) {
|
|
15682
|
-
throw new Error(`searchRouting.fallbackOn in ${
|
|
15785
|
+
throw new Error(`searchRouting.fallbackOn in ${CONFIG_PATH34} must be a non-empty array`);
|
|
15683
15786
|
}
|
|
15684
15787
|
const fallbackOn = [];
|
|
15685
15788
|
for (const kind of raw.fallbackOn) {
|
|
15686
15789
|
if (typeof kind !== "string" || !VALID_ROUTING_KINDS.includes(kind)) {
|
|
15687
|
-
throw new Error(`searchRouting.fallbackOn in ${
|
|
15790
|
+
throw new Error(`searchRouting.fallbackOn in ${CONFIG_PATH34} may only contain transient, quota, network, invalid-response, or unsupported`);
|
|
15688
15791
|
}
|
|
15689
15792
|
if (!fallbackOn.includes(kind)) {
|
|
15690
15793
|
fallbackOn.push(kind);
|
|
@@ -15744,18 +15847,10 @@ function errorMessage23(err) {
|
|
|
15744
15847
|
function isAbortError2(err) {
|
|
15745
15848
|
return errorMessage23(err).toLowerCase().includes("abort");
|
|
15746
15849
|
}
|
|
15747
|
-
function shouldTryOpenAIInAuto(options) {
|
|
15748
|
-
if (options.recencyFilter) return false;
|
|
15749
|
-
if (typeof options.numResults === "number" && Number.isFinite(options.numResults) && Math.floor(options.numResults) !== 5) {
|
|
15750
|
-
return false;
|
|
15751
|
-
}
|
|
15752
|
-
return true;
|
|
15753
|
-
}
|
|
15754
15850
|
function isOpenAICodexSelected(ctx) {
|
|
15755
15851
|
return ctx?.model?.provider === "openai-codex";
|
|
15756
15852
|
}
|
|
15757
15853
|
async function tryOpenAIInAuto(query, options, fallbackErrors) {
|
|
15758
|
-
if (!shouldTryOpenAIInAuto(options)) return null;
|
|
15759
15854
|
try {
|
|
15760
15855
|
if (await isOpenAISearchAvailable(options.extensionContext)) {
|
|
15761
15856
|
const result = await searchWithOpenAI(query, options, options.extensionContext);
|
|
@@ -15865,6 +15960,7 @@ async function searchWithResolvedProvider(provider, query, options, useCurrentMo
|
|
|
15865
15960
|
if (provider === "serpapi") return { ...await searchWithSerpApi(query, options), provider };
|
|
15866
15961
|
if (provider === "serper") return { ...await searchWithSerper(query, options), provider };
|
|
15867
15962
|
if (provider === "serply") return { ...await searchWithSerply(query, options), provider };
|
|
15963
|
+
if (provider === "baizhi") return { ...await searchWithBaizhi(query, options), provider };
|
|
15868
15964
|
if (provider === "valyu") return { ...await searchWithValyu(query, options), provider };
|
|
15869
15965
|
if (provider === "xcrawl") return { ...await searchWithXCrawl(query, options), provider };
|
|
15870
15966
|
if (provider === "perplexity") return { ...await searchWithPerplexity(query, options), provider };
|
|
@@ -15876,7 +15972,7 @@ async function searchWithResolvedProvider(provider, query, options, useCurrentMo
|
|
|
15876
15972
|
if (result2) return { ...result2, provider };
|
|
15877
15973
|
throw new Error(
|
|
15878
15974
|
`Gemini search unavailable. Either:
|
|
15879
|
-
1. Configure geminiApiKey in ${
|
|
15975
|
+
1. Configure geminiApiKey in ${CONFIG_PATH34} or set GEMINI_API_KEY
|
|
15880
15976
|
2. Set GOOGLE_GEMINI_BASE_URL + CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing
|
|
15881
15977
|
3. Set geminiAuth to "adc" in web-search.json with a Google Cloud ADC + project/location
|
|
15882
15978
|
4. Sign into gemini.google.com in a supported Chromium-based browser`
|
|
@@ -15912,6 +16008,7 @@ async function isResolvedProviderAvailable(provider, options, useCurrentModel =
|
|
|
15912
16008
|
if (provider === "serpapi") return isSerpApiAvailable();
|
|
15913
16009
|
if (provider === "serper") return isSerperAvailable();
|
|
15914
16010
|
if (provider === "serply") return isSerplyAvailable();
|
|
16011
|
+
if (provider === "baizhi") return isBaizhiAvailable();
|
|
15915
16012
|
if (provider === "valyu") return isValyuAvailable();
|
|
15916
16013
|
if (provider === "xcrawl") return isXcrawlAvailable();
|
|
15917
16014
|
if (provider === "perplexity") return isPerplexityAvailable();
|
|
@@ -15951,6 +16048,7 @@ function providerLabel(provider) {
|
|
|
15951
16048
|
if (provider === "serpapi") return "SerpApi";
|
|
15952
16049
|
if (provider === "serper") return "Serper";
|
|
15953
16050
|
if (provider === "serply") return "Serply";
|
|
16051
|
+
if (provider === "baizhi") return "Baizhi";
|
|
15954
16052
|
if (provider === "valyu") return "Valyu";
|
|
15955
16053
|
return provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
15956
16054
|
}
|
|
@@ -16228,7 +16326,7 @@ async function search(query, options = {}) {
|
|
|
16228
16326
|
throw new Error(
|
|
16229
16327
|
`No search provider available. Either:
|
|
16230
16328
|
1. Use /login to sign in with a Codex subscription for OpenAI web search
|
|
16231
|
-
2. Set openaiApiKey, braveApiKey, parallelApiKey, tinyfishApiKey, search1apiApiKey, searchinfinityApiKey, queritApiKey, tavilyApiKey, firecrawlBaseUrl, jinaApiKey, serpdiveApiKey, kagiApiKey, ollamaApiKey, searxngBaseUrl, perplexityApiKey, exaApiKey, geminiApiKey, bochaApiKey, or cloudflareApiKey in ${
|
|
16329
|
+
2. Set openaiApiKey, braveApiKey, parallelApiKey, tinyfishApiKey, search1apiApiKey, searchinfinityApiKey, queritApiKey, tavilyApiKey, firecrawlBaseUrl, jinaApiKey, serpdiveApiKey, kagiApiKey, ollamaApiKey, searxngBaseUrl, perplexityApiKey, exaApiKey, geminiApiKey, bochaApiKey, or cloudflareApiKey in ${CONFIG_PATH34}
|
|
16232
16330
|
3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TINYFISH_API_KEY, SEARCH1API_KEY, SEARCHINFINITY_API_KEY, QUERIT_API_KEY, TAVILY_API_KEY, FIRECRAWL_BASE_URL, JINA_API_KEY, SERPDIVE_API_KEY, KAGI_API_KEY, BOCHA_API_KEY, OLLAMA_API_KEY, SEARXNG_BASE_URL, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars
|
|
16233
16331
|
4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing
|
|
16234
16332
|
5. Sign into gemini.google.com in a supported Chromium-based browser
|
|
@@ -16377,7 +16475,7 @@ init_utils();
|
|
|
16377
16475
|
|
|
16378
16476
|
// storage.ts
|
|
16379
16477
|
init_utils();
|
|
16380
|
-
import { closeSync as closeSync2, constants, fchmodSync, fstatSync as fstatSync2, fsyncSync, lstatSync as lstatSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as
|
|
16478
|
+
import { closeSync as closeSync2, constants, fchmodSync, fstatSync as fstatSync2, fsyncSync, lstatSync as lstatSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync41, readdirSync as readdirSync3, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
16381
16479
|
import { randomBytes } from "node:crypto";
|
|
16382
16480
|
import { join as join6 } from "node:path";
|
|
16383
16481
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -16697,7 +16795,7 @@ function readCachedFetchData(data, now = Date.now()) {
|
|
|
16697
16795
|
const opened = openRegularFile(path);
|
|
16698
16796
|
fd = opened.fd;
|
|
16699
16797
|
enforceFileMode(fd);
|
|
16700
|
-
const parsed = JSON.parse(
|
|
16798
|
+
const parsed = JSON.parse(readFileSync41(fd, "utf8"));
|
|
16701
16799
|
if (!isValidStoredData(parsed) || parsed.type !== "fetch" || parsed.id !== data.id || !isInlineFetchData(parsed)) {
|
|
16702
16800
|
return unavailableFetchData(data, "Cached fetched content is invalid");
|
|
16703
16801
|
}
|
|
@@ -16847,6 +16945,7 @@ function buildProviderButtons(available, selected, hasInitialQueries) {
|
|
|
16847
16945
|
{ value: "serpapi", label: "SerpApi", available: available.serpapi },
|
|
16848
16946
|
{ value: "serper", label: "Serper", available: available.serper },
|
|
16849
16947
|
{ value: "serply", label: "Serply", available: available.serply },
|
|
16948
|
+
{ value: "baizhi", label: "Baizhi", available: available.baizhi },
|
|
16850
16949
|
{ value: "valyu", label: "Valyu", available: available.valyu }
|
|
16851
16950
|
];
|
|
16852
16951
|
return providers.filter((p) => p.available).map((p) => {
|
|
@@ -18257,7 +18356,7 @@ var SCRIPT = `(function() {
|
|
|
18257
18356
|
var token = DATA.sessionToken;
|
|
18258
18357
|
var timeoutSec = DATA.timeout;
|
|
18259
18358
|
var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
|
|
18260
|
-
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"];
|
|
18359
|
+
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"];
|
|
18261
18360
|
var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
|
|
18262
18361
|
var workflow = "summary-review";
|
|
18263
18362
|
var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
|
|
@@ -18492,6 +18591,7 @@ var SCRIPT = `(function() {
|
|
|
18492
18591
|
if (provider === "serpapi") return "SerpApi";
|
|
18493
18592
|
if (provider === "serper") return "Serper";
|
|
18494
18593
|
if (provider === "serply") return "Serply";
|
|
18594
|
+
if (provider === "baizhi") return "Baizhi";
|
|
18495
18595
|
if (provider === "valyu") return "Valyu";
|
|
18496
18596
|
return "Unknown";
|
|
18497
18597
|
}
|
|
@@ -20599,6 +20699,7 @@ function startCuratorServer(options, callbacks) {
|
|
|
20599
20699
|
if (provider === "serpbase") return availableProviders.serpbase;
|
|
20600
20700
|
if (provider === "serper") return availableProviders.serper;
|
|
20601
20701
|
if (provider === "serply") return availableProviders.serply;
|
|
20702
|
+
if (provider === "baizhi") return availableProviders.baizhi;
|
|
20602
20703
|
if (provider === "valyu") return availableProviders.valyu;
|
|
20603
20704
|
return false;
|
|
20604
20705
|
}
|
|
@@ -21378,8 +21479,8 @@ import { randomUUID as randomUUID3 } from "node:crypto";
|
|
|
21378
21479
|
import { execFileSync as execFileSync3, spawn as spawn3 } from "node:child_process";
|
|
21379
21480
|
import { createRequire } from "node:module";
|
|
21380
21481
|
import { platform } from "node:os";
|
|
21381
|
-
import { existsSync as
|
|
21382
|
-
import { join as
|
|
21482
|
+
import { existsSync as existsSync49, readFileSync as readFileSync52, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
21483
|
+
import { join as join10 } from "node:path";
|
|
21383
21484
|
init_gemini_api();
|
|
21384
21485
|
init_gemini_web();
|
|
21385
21486
|
init_gemini_web_config();
|
|
@@ -21659,9 +21760,118 @@ function getResearchArtifact(id) {
|
|
|
21659
21760
|
return data.artifact;
|
|
21660
21761
|
}
|
|
21661
21762
|
|
|
21763
|
+
// tool-activation.ts
|
|
21764
|
+
import { buildSessionContext } from "@earendil-works/pi-coding-agent";
|
|
21765
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
21766
|
+
import { readFileSync as readFileSync42 } from "node:fs";
|
|
21767
|
+
import { fileURLToPath } from "node:url";
|
|
21768
|
+
import { Type } from "typebox";
|
|
21769
|
+
var LOADER_NAME = "web_enable";
|
|
21770
|
+
var CAPABILITY_LABELS = {
|
|
21771
|
+
search: "web search",
|
|
21772
|
+
"source-check": "source checking",
|
|
21773
|
+
fetch: "content fetching",
|
|
21774
|
+
"stored-content": "stored-result retrieval"
|
|
21775
|
+
};
|
|
21776
|
+
function supportsDynamicTools(pi) {
|
|
21777
|
+
if (typeof pi.getAllTools !== "function" || typeof pi.getActiveTools !== "function" || typeof pi.setActiveTools !== "function") return false;
|
|
21778
|
+
try {
|
|
21779
|
+
const packagePath = join7(dirname2(fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"))), "..", "package.json");
|
|
21780
|
+
const [major, minor, patch] = JSON.parse(readFileSync42(packagePath, "utf8")).version.split(".").map(Number);
|
|
21781
|
+
return major > 0 || minor > 86 || minor === 86 && patch >= 1;
|
|
21782
|
+
} catch {
|
|
21783
|
+
return false;
|
|
21784
|
+
}
|
|
21785
|
+
}
|
|
21786
|
+
function hasToolDeclarations(messages) {
|
|
21787
|
+
return messages.some((message) => message && typeof message === "object" && ("toolsAdded" in message || "toolsRemoved" in message));
|
|
21788
|
+
}
|
|
21789
|
+
async function currentTranscriptToolNames(messages) {
|
|
21790
|
+
const moduleName = "@earendil-works/pi-ai/utils/transcript";
|
|
21791
|
+
const { getCurrentTools } = await import(moduleName);
|
|
21792
|
+
return getCurrentTools(messages).map((tool) => tool.name);
|
|
21793
|
+
}
|
|
21794
|
+
function registerWebToolActivation(pi, tools) {
|
|
21795
|
+
if (tools.length === 0) return;
|
|
21796
|
+
if (!supportsDynamicTools(pi)) {
|
|
21797
|
+
console.warn("[pi-web-access] Dynamic tool activation requires Pi 0.86.1 or newer; web tools remain eagerly available.");
|
|
21798
|
+
return;
|
|
21799
|
+
}
|
|
21800
|
+
const names = tools.map((tool) => tool.name);
|
|
21801
|
+
const capabilities = tools.map((tool) => CAPABILITY_LABELS[tool.capability]).join(", ");
|
|
21802
|
+
const parameters = Type.Object({}, { additionalProperties: false });
|
|
21803
|
+
pi.registerTool({
|
|
21804
|
+
name: LOADER_NAME,
|
|
21805
|
+
label: "Enable Web Access",
|
|
21806
|
+
description: "Enable configured pi-web-access tools for web research and content retrieval. Does not search or fetch. Enabled tools are available on the next model request; disabled capabilities remain unavailable.",
|
|
21807
|
+
promptSnippet: `pi-web-access is configured for ${capabilities}. Call web_enable to activate these tools; use them on the next model request.`,
|
|
21808
|
+
parameters,
|
|
21809
|
+
async execute() {
|
|
21810
|
+
const registered = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
21811
|
+
const unavailable = names.filter((name) => !registered.has(name));
|
|
21812
|
+
if (unavailable.length > 0) {
|
|
21813
|
+
return {
|
|
21814
|
+
isError: true,
|
|
21815
|
+
content: [{ type: "text", text: `Cannot enable unavailable tools: ${unavailable.join(", ")}.` }],
|
|
21816
|
+
details: { unavailable }
|
|
21817
|
+
};
|
|
21818
|
+
}
|
|
21819
|
+
try {
|
|
21820
|
+
pi.setActiveTools([.../* @__PURE__ */ new Set([...pi.getActiveTools(), ...names])]);
|
|
21821
|
+
} catch (error) {
|
|
21822
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21823
|
+
return {
|
|
21824
|
+
isError: true,
|
|
21825
|
+
content: [{ type: "text", text: `Activation failed: ${message}.` }],
|
|
21826
|
+
details: { error: message }
|
|
21827
|
+
};
|
|
21828
|
+
}
|
|
21829
|
+
const active = new Set(pi.getActiveTools());
|
|
21830
|
+
const missing = names.filter((name) => !active.has(name));
|
|
21831
|
+
return missing.length > 0 ? {
|
|
21832
|
+
isError: true,
|
|
21833
|
+
content: [{ type: "text", text: `Tools still inactive after activation: ${missing.join(", ")}.` }],
|
|
21834
|
+
details: { missing }
|
|
21835
|
+
} : {
|
|
21836
|
+
content: [{ type: "text", text: `Enabled: ${names.join(", ")}.` }],
|
|
21837
|
+
details: { enabled: names }
|
|
21838
|
+
};
|
|
21839
|
+
}
|
|
21840
|
+
});
|
|
21841
|
+
function loaderAvailable() {
|
|
21842
|
+
return pi.getAllTools().some((tool) => tool.name === LOADER_NAME);
|
|
21843
|
+
}
|
|
21844
|
+
let warned = false;
|
|
21845
|
+
async function selectFromSession(ctx) {
|
|
21846
|
+
if (!loaderAvailable()) return;
|
|
21847
|
+
try {
|
|
21848
|
+
const messages = buildSessionContext(ctx.sessionManager.getBranch()).messages;
|
|
21849
|
+
const recorded = hasToolDeclarations(messages) ? new Set(await currentTranscriptToolNames(messages)) : messages.length > 0 ? new Set(names) : /* @__PURE__ */ new Set();
|
|
21850
|
+
const heavy = new Set(names);
|
|
21851
|
+
const active = pi.getActiveTools().filter((name) => !heavy.has(name));
|
|
21852
|
+
for (const name of names) if (recorded.has(name)) active.push(name);
|
|
21853
|
+
pi.setActiveTools([.../* @__PURE__ */ new Set([...active, LOADER_NAME])]);
|
|
21854
|
+
} catch (error) {
|
|
21855
|
+
if (!warned) {
|
|
21856
|
+
warned = true;
|
|
21857
|
+
console.warn(`[pi-web-access] Keeping web tools eagerly available because activation setup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
21858
|
+
}
|
|
21859
|
+
}
|
|
21860
|
+
}
|
|
21861
|
+
pi.on("session_start", (_event, ctx) => selectFromSession(ctx));
|
|
21862
|
+
pi.on("session_tree", (_event, ctx) => selectFromSession(ctx));
|
|
21863
|
+
pi.on("before_agent_start", () => {
|
|
21864
|
+
if (!loaderAvailable() || pi.getActiveTools().includes(LOADER_NAME)) return;
|
|
21865
|
+
try {
|
|
21866
|
+
pi.setActiveTools([...pi.getActiveTools(), LOADER_NAME]);
|
|
21867
|
+
} catch {
|
|
21868
|
+
}
|
|
21869
|
+
});
|
|
21870
|
+
}
|
|
21871
|
+
|
|
21662
21872
|
// index.ts
|
|
21663
21873
|
function StringEnum(values, options) {
|
|
21664
|
-
return
|
|
21874
|
+
return Type2.Unsafe({
|
|
21665
21875
|
type: "string",
|
|
21666
21876
|
enum: values,
|
|
21667
21877
|
...options?.description && { description: options.description },
|
|
@@ -21712,17 +21922,17 @@ function parseConfigRoot(raw) {
|
|
|
21712
21922
|
return parsed;
|
|
21713
21923
|
}
|
|
21714
21924
|
function loadConfig35() {
|
|
21715
|
-
if (!
|
|
21716
|
-
return parseConfigRoot(
|
|
21925
|
+
if (!existsSync49(WEB_SEARCH_CONFIG_PATH4)) return {};
|
|
21926
|
+
return parseConfigRoot(readFileSync52(WEB_SEARCH_CONFIG_PATH4, "utf-8"));
|
|
21717
21927
|
}
|
|
21718
21928
|
function saveConfig(updates) {
|
|
21719
21929
|
let config = {};
|
|
21720
|
-
if (
|
|
21721
|
-
config = parseConfigRoot(
|
|
21930
|
+
if (existsSync49(WEB_SEARCH_CONFIG_PATH4)) {
|
|
21931
|
+
config = parseConfigRoot(readFileSync52(WEB_SEARCH_CONFIG_PATH4, "utf-8"));
|
|
21722
21932
|
}
|
|
21723
21933
|
Object.assign(config, updates);
|
|
21724
21934
|
const dir = getWebSearchConfigDir();
|
|
21725
|
-
if (!
|
|
21935
|
+
if (!existsSync49(dir)) mkdirSync3(dir, { recursive: true });
|
|
21726
21936
|
writeFileSync3(WEB_SEARCH_CONFIG_PATH4, JSON.stringify(config, null, 2) + "\n");
|
|
21727
21937
|
}
|
|
21728
21938
|
var DEFAULT_TOOL_NAMES = {
|
|
@@ -21771,9 +21981,9 @@ function curatorResultIndexCapacity(queryCount) {
|
|
|
21771
21981
|
return queryCount * RESOLVED_SEARCH_PROVIDERS.length;
|
|
21772
21982
|
}
|
|
21773
21983
|
function searchProviderSchema(description, allowedProviders) {
|
|
21774
|
-
return
|
|
21984
|
+
return Type2.Union([
|
|
21775
21985
|
StringEnum(["auto", "all", ...allowedProviders]),
|
|
21776
|
-
|
|
21986
|
+
Type2.Array(StringEnum([...allowedProviders]), { minItems: 1 })
|
|
21777
21987
|
], { description });
|
|
21778
21988
|
}
|
|
21779
21989
|
function isToolEnabled(config, key) {
|
|
@@ -21809,6 +22019,7 @@ function resolveToolNames(config) {
|
|
|
21809
22019
|
const seen = /* @__PURE__ */ new Map();
|
|
21810
22020
|
for (const key of registeredKeys) {
|
|
21811
22021
|
const name = names[key];
|
|
22022
|
+
if (name === "web_enable") throw new Error(`toolNames.${key} in ${WEB_SEARCH_CONFIG_PATH4} uses reserved loader name web_enable`);
|
|
21812
22023
|
const previous = seen.get(name);
|
|
21813
22024
|
if (previous) throw new Error(`toolNames.${key} duplicates toolNames.${previous} in ${WEB_SEARCH_CONFIG_PATH4}`);
|
|
21814
22025
|
seen.set(name, key);
|
|
@@ -21933,6 +22144,7 @@ async function getProviderAvailability(ctx) {
|
|
|
21933
22144
|
serpapi: allowedProviders.has("serpapi") && isSerpApiAvailable(),
|
|
21934
22145
|
serper: allowedProviders.has("serper") && isSerperAvailable(),
|
|
21935
22146
|
serply: allowedProviders.has("serply") && isSerplyAvailable(),
|
|
22147
|
+
baizhi: allowedProviders.has("baizhi") && isBaizhiAvailable(),
|
|
21936
22148
|
valyu: allowedProviders.has("valyu") && isValyuAvailable()
|
|
21937
22149
|
};
|
|
21938
22150
|
return {
|
|
@@ -22011,10 +22223,11 @@ var pendingCurates = /* @__PURE__ */ new Map();
|
|
|
22011
22223
|
var activeCurators = /* @__PURE__ */ new Map();
|
|
22012
22224
|
var glimpseWins = /* @__PURE__ */ new Map();
|
|
22013
22225
|
var DEFAULT_MAX_INLINE_CONTENT_CHARS = 3e4;
|
|
22226
|
+
var MIN_INLINE_CONTENT_CHARS = 1e3;
|
|
22014
22227
|
var MAX_INLINE_CONTENT_CHARS = 2e5;
|
|
22015
22228
|
function getMaxInlineContentChars(config = loadConfig35()) {
|
|
22016
22229
|
const value = config.maxInlineContentChars;
|
|
22017
|
-
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value
|
|
22230
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value < MIN_INLINE_CONTENT_CHARS) {
|
|
22018
22231
|
return DEFAULT_MAX_INLINE_CONTENT_CHARS;
|
|
22019
22232
|
}
|
|
22020
22233
|
return Math.min(value, MAX_INLINE_CONTENT_CHARS);
|
|
@@ -22137,6 +22350,10 @@ function hasFullInlineCoverage(urls, inlineContent) {
|
|
|
22137
22350
|
function formatFullResults(queryData) {
|
|
22138
22351
|
let output = `## Results for: "${queryData.query}"
|
|
22139
22352
|
|
|
22353
|
+
`;
|
|
22354
|
+
const providers = queryData.providers ?? (queryData.provider ? [queryData.provider] : []);
|
|
22355
|
+
if (providers.length > 0) output += `**Provider${providers.length === 1 ? "" : "s"}:** ${providers.join(", ")}
|
|
22356
|
+
|
|
22140
22357
|
`;
|
|
22141
22358
|
if (queryData.answer) {
|
|
22142
22359
|
output += `${queryData.answer}
|
|
@@ -22147,12 +22364,33 @@ function formatFullResults(queryData) {
|
|
|
22147
22364
|
}
|
|
22148
22365
|
for (const r of queryData.results) {
|
|
22149
22366
|
output += `### ${r.title}
|
|
22150
|
-
${r.url}
|
|
22367
|
+
${r.url}${r.snippet ? `
|
|
22368
|
+
|
|
22369
|
+
${r.snippet}` : ""}
|
|
22151
22370
|
|
|
22152
22371
|
`;
|
|
22153
22372
|
}
|
|
22154
22373
|
return output;
|
|
22155
22374
|
}
|
|
22375
|
+
function boundSearchPresentation(text2, guidance, truncationGuidance, maxChars) {
|
|
22376
|
+
const fullText = `${text2}${guidance}`;
|
|
22377
|
+
if (fullText.length <= maxChars) {
|
|
22378
|
+
return { text: fullText, truncated: false, originalChars: text2.length, returnedChars: text2.length, omittedChars: 0 };
|
|
22379
|
+
}
|
|
22380
|
+
const marker = `
|
|
22381
|
+
|
|
22382
|
+
---
|
|
22383
|
+
[Output truncated.]${truncationGuidance}`;
|
|
22384
|
+
const prefixLength = maxChars - marker.length;
|
|
22385
|
+
const bounded = `${text2.slice(0, prefixLength)}${marker}`;
|
|
22386
|
+
return {
|
|
22387
|
+
text: bounded,
|
|
22388
|
+
truncated: true,
|
|
22389
|
+
originalChars: text2.length,
|
|
22390
|
+
returnedChars: prefixLength,
|
|
22391
|
+
omittedChars: text2.length - prefixLength
|
|
22392
|
+
};
|
|
22393
|
+
}
|
|
22156
22394
|
function abortPendingFetches() {
|
|
22157
22395
|
for (const controller of pendingFetches.values()) {
|
|
22158
22396
|
controller.abort();
|
|
@@ -22232,8 +22470,8 @@ function findGlimpseMjs() {
|
|
|
22232
22470
|
}
|
|
22233
22471
|
try {
|
|
22234
22472
|
const globalRoot = execFileSync3("npm", ["root", "-g"], { encoding: "utf-8" }).trim();
|
|
22235
|
-
const entry =
|
|
22236
|
-
if (
|
|
22473
|
+
const entry = join10(globalRoot, "glimpseui", "src", "glimpse.mjs");
|
|
22474
|
+
if (existsSync49(entry)) return entry;
|
|
22237
22475
|
} catch {
|
|
22238
22476
|
}
|
|
22239
22477
|
return null;
|
|
@@ -22627,6 +22865,7 @@ function index_default(pi) {
|
|
|
22627
22865
|
const sc = opts.results.filter((r) => !r.error).length;
|
|
22628
22866
|
const tr = opts.results.reduce((sum, r) => sum + r.results.length, 0);
|
|
22629
22867
|
const hasApprovedSummary = typeof opts.approvedSummary === "string" && opts.approvedSummary.trim().length > 0;
|
|
22868
|
+
const maxInlineContentChars = getMaxInlineContentChars(initConfig);
|
|
22630
22869
|
let output = "";
|
|
22631
22870
|
if (hasApprovedSummary) {
|
|
22632
22871
|
output = opts.approvedSummary.trim();
|
|
@@ -22634,6 +22873,15 @@ function index_default(pi) {
|
|
|
22634
22873
|
if (opts.curated) {
|
|
22635
22874
|
output += "[These results were manually curated by the user in the browser. Use them as-is \u2014 do not re-search or discard.]\n\n";
|
|
22636
22875
|
}
|
|
22876
|
+
const providerNames = opts.results.map((result) => {
|
|
22877
|
+
const providers = result.providers ?? (result.provider ? [result.provider] : []);
|
|
22878
|
+
return providers.join(", ") || "unknown";
|
|
22879
|
+
});
|
|
22880
|
+
output += opts.results.length === 1 ? `**Provider:** ${providerNames[0]}
|
|
22881
|
+
|
|
22882
|
+
` : `**Providers used:** ${providerNames.map((name, index) => `Query ${index + 1}: ${name}`).join("; ")}
|
|
22883
|
+
|
|
22884
|
+
`;
|
|
22637
22885
|
const duplicateQueries = opts.curated ? duplicateQuerySet(opts.results) : /* @__PURE__ */ new Set();
|
|
22638
22886
|
for (const { query, answer, results, error, provider } of opts.results) {
|
|
22639
22887
|
if (opts.queryList.length > 1) {
|
|
@@ -22658,26 +22906,35 @@ function index_default(pi) {
|
|
|
22658
22906
|
urls: opts.inlineContent
|
|
22659
22907
|
};
|
|
22660
22908
|
pi.appendEntry("web-search-results", storeFetchedContentResult(fetchId, data));
|
|
22661
|
-
if (!hasApprovedSummary) {
|
|
22662
|
-
output += `---
|
|
22663
|
-
Full content for ${opts.inlineContent.length} sources available [${fetchId}].`;
|
|
22664
|
-
}
|
|
22665
22909
|
} else if (opts.includeContent) {
|
|
22666
22910
|
fetchId = startBackgroundFetch(opts.urls, opts.proxy);
|
|
22667
|
-
if (fetchId && !hasApprovedSummary) {
|
|
22668
|
-
output += `---
|
|
22669
|
-
Content fetching in background [${fetchId}]. Will notify when ready.`;
|
|
22670
|
-
}
|
|
22671
22911
|
}
|
|
22672
22912
|
const searchId = storeAndPublishSearch(opts.results);
|
|
22673
22913
|
const isBackgroundFetch = fetchId !== null && !hasInlineReady;
|
|
22674
|
-
|
|
22675
|
-
|
|
22914
|
+
const unboundedPresentation = output.trim();
|
|
22915
|
+
const buildGuidance = (forTruncation) => {
|
|
22916
|
+
let value = "";
|
|
22917
|
+
if (hasInlineReady && opts.inlineContent && fetchId) {
|
|
22918
|
+
value += `
|
|
22676
22919
|
---
|
|
22677
|
-
|
|
22678
|
-
|
|
22920
|
+
Full content for ${opts.inlineContent.length} sources is ready as responseId "${fetchId}". `;
|
|
22921
|
+
value += getSearchContentEnabled ? `Use ${toolNames.getSearchContent}({ responseId: "${fetchId}", urlIndex: 0, offset: 0, limit: ${maxInlineContentChars} }) to retrieve the first bounded page.` : forTruncation ? `Enable ${toolNames.getSearchContent} to retrieve the full stored content.` : "";
|
|
22922
|
+
} else if (isBackgroundFetch && fetchId) {
|
|
22923
|
+
value += `
|
|
22924
|
+
---
|
|
22925
|
+
Content fetching in background as responseId "${fetchId}". Will notify when ready.`;
|
|
22926
|
+
}
|
|
22927
|
+
if (getSearchContentEnabled || forTruncation) {
|
|
22928
|
+
value += `
|
|
22929
|
+
---
|
|
22930
|
+
Full search results are stored as responseId "${searchId}". `;
|
|
22931
|
+
value += getSearchContentEnabled ? `Use ${toolNames.getSearchContent}({ responseId: "${searchId}", queryIndex: 0, offset: 0, limit: ${maxInlineContentChars} }) to retrieve the first bounded page${opts.results.length > 1 ? `; repeat with queryIndex 1 through ${opts.results.length - 1}` : ""}.` : `Enable ${toolNames.getSearchContent} to retrieve the full stored results.`;
|
|
22932
|
+
}
|
|
22933
|
+
return value;
|
|
22934
|
+
};
|
|
22935
|
+
const presentation = hasApprovedSummary ? { text: unboundedPresentation, truncated: false, originalChars: unboundedPresentation.length, returnedChars: unboundedPresentation.length, omittedChars: 0 } : boundSearchPresentation(unboundedPresentation, buildGuidance(false), buildGuidance(true), maxInlineContentChars);
|
|
22679
22936
|
return {
|
|
22680
|
-
content: [{ type: "text", text:
|
|
22937
|
+
content: [{ type: "text", text: presentation.text }],
|
|
22681
22938
|
details: {
|
|
22682
22939
|
queries: opts.queryList,
|
|
22683
22940
|
queryCount: opts.queryList.length,
|
|
@@ -22687,6 +22944,14 @@ Results stored as responseId "${searchId}". Use ${toolNames.getSearchContent}({
|
|
|
22687
22944
|
fetchId,
|
|
22688
22945
|
fetchUrls: isBackgroundFetch ? opts.urls : void 0,
|
|
22689
22946
|
searchId,
|
|
22947
|
+
queryProviders: opts.results.map((result) => ({
|
|
22948
|
+
query: result.query,
|
|
22949
|
+
providers: result.providers ?? (result.provider ? [result.provider] : [])
|
|
22950
|
+
})),
|
|
22951
|
+
truncated: presentation.truncated,
|
|
22952
|
+
originalChars: presentation.originalChars,
|
|
22953
|
+
returnedChars: presentation.returnedChars,
|
|
22954
|
+
omittedChars: presentation.omittedChars,
|
|
22690
22955
|
...opts.curated ? {
|
|
22691
22956
|
curated: true,
|
|
22692
22957
|
curatedFrom: opts.curatedFrom,
|
|
@@ -23011,24 +23276,24 @@ Open manually: ${handle.url}` }],
|
|
|
23011
23276
|
if (webSearchEnabled) pi.registerTool({
|
|
23012
23277
|
name: toolNames.webSearch,
|
|
23013
23278
|
label: "Web Search",
|
|
23014
|
-
description: `Search the web with ${allowedSearchProviders.map(providerLabel).join(", ")}. Provider arrays run simultaneously; ${allPolicyDescription}.
|
|
23279
|
+
description: `Search the web with ${allowedSearchProviders.map(providerLabel).join(", ")}. Provider arrays run simultaneously; ${allPolicyDescription}. The default workflow is none: it returns bounded source-linked search results or provider answers without a curator or generated summary, identifies the providers used, and stores full results for retrieval by responseId. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query. When includeContent is true, full page content is fetched in the background. Set workflow to "summary-review" to open the curator with an auto-generated summary draft or "auto-summary" to generate a summary without the browser curator. The configured provider is used when provider is omitted or set to auto; omit provider unless explicitly overriding it.`,
|
|
23015
23280
|
promptSnippet: "Use for web research questions. Prefer {queries:[...]} with 2-4 varied angles over a single query for broader coverage. Omit provider unless explicitly overriding the configured default.",
|
|
23016
|
-
parameters:
|
|
23017
|
-
query:
|
|
23018
|
-
queries:
|
|
23019
|
-
numResults:
|
|
23020
|
-
includeContent:
|
|
23021
|
-
recencyFilter:
|
|
23281
|
+
parameters: Type2.Object({
|
|
23282
|
+
query: Type2.Optional(Type2.String({ description: "Single search query. For research tasks, prefer 'queries' with multiple varied angles instead." })),
|
|
23283
|
+
queries: Type2.Optional(Type2.Array(Type2.String(), { description: "Multiple queries searched concurrently (up to three at a time), each returning source-linked search results or a provider answer. Prefer this for research \u2014 vary phrasing, scope, and angle across 2-4 queries to maximize coverage. Good: ['React vs Vue performance benchmarks 2026', 'React vs Vue developer experience comparison', 'React ecosystem size vs Vue ecosystem']. Bad: ['React vs Vue', 'React vs Vue comparison', 'React vs Vue review'] (too similar, redundant results)." })),
|
|
23284
|
+
numResults: Type2.Optional(Type2.Integer({ minimum: 1, maximum: 20, description: "Results per query (default: 5, max: 20)" })),
|
|
23285
|
+
includeContent: Type2.Optional(Type2.Boolean({ description: "Fetch full page content (async)" })),
|
|
23286
|
+
recencyFilter: Type2.Optional(
|
|
23022
23287
|
StringEnum(["day", "week", "month", "year"], { description: "Filter by recency" })
|
|
23023
23288
|
),
|
|
23024
|
-
domainFilter:
|
|
23025
|
-
provider:
|
|
23026
|
-
workflow:
|
|
23289
|
+
domainFilter: Type2.Optional(Type2.Array(Type2.String(), { description: "Limit to domains (prefix with - to exclude)" })),
|
|
23290
|
+
provider: Type2.Optional(searchProviderSchema(`Search provider or non-empty list of allowed providers to search simultaneously; ${allPolicyDescription}; omit this field to use the configured provider, or use auto when none is configured`, allowedSearchProviders)),
|
|
23291
|
+
workflow: Type2.Optional(
|
|
23027
23292
|
StringEnum(["none", "summary-review", "auto-summary"], {
|
|
23028
23293
|
description: "Search workflow mode: none = no curator (default), summary-review = open curator with auto summary draft, auto-summary = generate summary without opening curator"
|
|
23029
23294
|
})
|
|
23030
23295
|
),
|
|
23031
|
-
proxy:
|
|
23296
|
+
proxy: Type2.Optional(Type2.String({
|
|
23032
23297
|
description: "http(s) or socks proxy URL (e.g. http://host:port or socks5h://host:port) used for every outbound request in this call (search APIs and content fetches). Node fetch ignores HTTP(S)_PROXY env vars, so set this (or `proxy` in web-search.json) when direct access is blocked; empty string forces direct access."
|
|
23033
23298
|
}))
|
|
23034
23299
|
}),
|
|
@@ -23245,7 +23510,7 @@ Open manually: ${handle.url}` }],
|
|
|
23245
23510
|
details: { phase: "search", progress: completedSearches / queryList.length, currentQuery: query }
|
|
23246
23511
|
});
|
|
23247
23512
|
try {
|
|
23248
|
-
const { answer, results, inlineContent, provider } = await search(query, {
|
|
23513
|
+
const { answer, results, inlineContent, provider, providerResponses } = await search(query, {
|
|
23249
23514
|
provider: resolvedProvider,
|
|
23250
23515
|
numResults: params.numResults,
|
|
23251
23516
|
recencyFilter,
|
|
@@ -23254,7 +23519,8 @@ Open manually: ${handle.url}` }],
|
|
|
23254
23519
|
signal,
|
|
23255
23520
|
extensionContext: ctx
|
|
23256
23521
|
});
|
|
23257
|
-
|
|
23522
|
+
const providers = providerResponses?.map((response) => response.provider) ?? [provider];
|
|
23523
|
+
return { result: { query, answer, results, error: null, provider, providers }, inlineContent };
|
|
23258
23524
|
} catch (err) {
|
|
23259
23525
|
if (signal?.aborted || isAbortError7(err)) throw err;
|
|
23260
23526
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -23524,15 +23790,15 @@ Open manually: ${handle.url}` }],
|
|
|
23524
23790
|
label: "Source Check",
|
|
23525
23791
|
description: "Gather web sources for a claim and return a bounded machine-readable research artifact with exact passage citations for manual review.",
|
|
23526
23792
|
promptSnippet: "Gather structured source evidence and passage-level citations for manual semantic review of a claim.",
|
|
23527
|
-
parameters:
|
|
23528
|
-
claim:
|
|
23529
|
-
queries:
|
|
23530
|
-
numResults:
|
|
23531
|
-
fetchContent:
|
|
23532
|
-
recencyFilter:
|
|
23533
|
-
domainFilter:
|
|
23534
|
-
provider:
|
|
23535
|
-
proxy:
|
|
23793
|
+
parameters: Type2.Object({
|
|
23794
|
+
claim: Type2.String({ description: "The assertion to gather web sources for." }),
|
|
23795
|
+
queries: Type2.Optional(Type2.Array(Type2.String(), { description: "Search queries (default: the claim)." })),
|
|
23796
|
+
numResults: Type2.Optional(Type2.Integer({ minimum: 1, maximum: 20, description: "Results per query (default: 5, max: 20)." })),
|
|
23797
|
+
fetchContent: Type2.Optional(Type2.Boolean({ description: "Fetch up to 5 result pages for exact passage extraction." })),
|
|
23798
|
+
recencyFilter: Type2.Optional(StringEnum(["day", "week", "month", "year"], { description: "Filter by recency." })),
|
|
23799
|
+
domainFilter: Type2.Optional(Type2.Array(Type2.String(), { description: "Limit to domains; prefix with - to exclude." })),
|
|
23800
|
+
provider: Type2.Optional(searchProviderSchema(`Search provider or non-empty list of allowed providers to search simultaneously; ${allPolicyDescription}`, allowedSearchProviders)),
|
|
23801
|
+
proxy: Type2.Optional(Type2.String({
|
|
23536
23802
|
description: "http(s) or socks proxy URL (e.g. http://host:port or socks5h://host:port) used for every outbound request in this call (search APIs and result-page fetches). Empty string forces direct access."
|
|
23537
23803
|
}))
|
|
23538
23804
|
}),
|
|
@@ -23613,38 +23879,38 @@ Open manually: ${handle.url}` }],
|
|
|
23613
23879
|
label: "Fetch Content",
|
|
23614
23880
|
description: `Fetch URL(s). Available modes: ${fetchModeDescription}. Direct image URLs return resized image content when supported by the selected mode. Supports YouTube transcripts, GitHub repositories, PDFs, and local videos when supported by the selected mode. ${fetchContentStorageNote}`,
|
|
23615
23881
|
promptSnippet: "Use to fetch URL content, direct images, GitHub repos, and videos.",
|
|
23616
|
-
parameters:
|
|
23617
|
-
url:
|
|
23618
|
-
urls:
|
|
23619
|
-
forceClone:
|
|
23882
|
+
parameters: Type2.Object({
|
|
23883
|
+
url: Type2.Optional(Type2.String({ description: "Single URL to fetch" })),
|
|
23884
|
+
urls: Type2.Optional(Type2.Array(Type2.String(), { description: "Multiple URLs (parallel)" })),
|
|
23885
|
+
forceClone: Type2.Optional(Type2.Boolean({
|
|
23620
23886
|
description: "Force cloning large GitHub repositories that exceed the size threshold"
|
|
23621
23887
|
})),
|
|
23622
|
-
prompt:
|
|
23888
|
+
prompt: Type2.Optional(Type2.String({
|
|
23623
23889
|
description: fetchModeConfig.allowedModes.includes("answer") ? "Question or instruction for video analysis, or the page-local question required by answer mode." : "Question or instruction for video analysis."
|
|
23624
23890
|
})),
|
|
23625
|
-
mode:
|
|
23891
|
+
mode: Type2.Optional(StringEnum(fetchModeConfig.allowedModes, {
|
|
23626
23892
|
description: `Fetch mode. ${fetchModeDescription}.`
|
|
23627
23893
|
})),
|
|
23628
23894
|
...fetchModeConfig.allowedModes.includes("answer") ? {
|
|
23629
|
-
answerModel:
|
|
23895
|
+
answerModel: Type2.Optional(Type2.String({
|
|
23630
23896
|
description: "Optional provider/model-id override for answer mode. Defaults to fetch.answerProvider + fetch.answerModel when configured, otherwise the current Pi model."
|
|
23631
23897
|
}))
|
|
23632
23898
|
} : {},
|
|
23633
|
-
timestamp:
|
|
23899
|
+
timestamp: Type2.Optional(Type2.String({
|
|
23634
23900
|
description: "Extract video frame(s) at a timestamp or time range. Single: '1:23:45', '23:45', or '85' (seconds). Range: '23:41-25:00' extracts evenly-spaced frames across that span (default 6). Use frames with ranges to control density; single+frames uses a fixed 5s interval. YouTube requires yt-dlp + ffmpeg; local videos require ffmpeg. Use a range when you know the approximate area but not the exact moment \u2014 you'll get a contact sheet to visually identify the right frame."
|
|
23635
23901
|
})),
|
|
23636
|
-
frames:
|
|
23902
|
+
frames: Type2.Optional(Type2.Integer({
|
|
23637
23903
|
minimum: 1,
|
|
23638
23904
|
maximum: 12,
|
|
23639
23905
|
description: "Number of frames to extract. Use with timestamp range for custom density, with single timestamp to get N frames at 5s intervals, or alone to sample across the entire video. Requires yt-dlp + ffmpeg for YouTube, ffmpeg for local video."
|
|
23640
23906
|
})),
|
|
23641
|
-
model:
|
|
23907
|
+
model: Type2.Optional(Type2.String({
|
|
23642
23908
|
description: "Override the Gemini model for video/YouTube analysis (e.g. 'gemini-3.6-flash'). Defaults to config or gemini-3.6-flash."
|
|
23643
23909
|
})),
|
|
23644
|
-
auth:
|
|
23910
|
+
auth: Type2.Optional(Type2.Union([Type2.String(), Type2.Boolean()], {
|
|
23645
23911
|
description: "Opt into an authFetch profile for local browser-cookie fetching. Use a profile name, or true only when exactly one profile exists."
|
|
23646
23912
|
})),
|
|
23647
|
-
proxy:
|
|
23913
|
+
proxy: Type2.Optional(Type2.String({
|
|
23648
23914
|
description: "http(s) or socks proxy URL (e.g. http://host:port or socks5h://host:port) used for this fetch. Needed when the target is unreachable directly; localhost and NO_PROXY hosts always bypass the proxy. Empty string forces direct access."
|
|
23649
23915
|
}))
|
|
23650
23916
|
}),
|
|
@@ -23924,21 +24190,21 @@ Use ${toolNames.getSearchContent}({ responseId: "${responseId}", urlIndex: 0 })
|
|
|
23924
24190
|
pi.registerTool({
|
|
23925
24191
|
name: toolNames.getSearchContent,
|
|
23926
24192
|
label: "Get Search Content",
|
|
23927
|
-
description: `Retrieve bounded
|
|
24193
|
+
description: `Retrieve bounded pages of full stored search results or fetched content, or find matching passages, from a previous ${storedContentSources} call.`,
|
|
23928
24194
|
promptSnippet: `Use after ${storedContentSources} to retrieve stored content via responseId. Use findText to locate passages without paging through the full content.`,
|
|
23929
|
-
parameters:
|
|
23930
|
-
responseId:
|
|
23931
|
-
query:
|
|
23932
|
-
queryIndex:
|
|
23933
|
-
url:
|
|
23934
|
-
urlIndex:
|
|
23935
|
-
offset:
|
|
23936
|
-
limit:
|
|
23937
|
-
findText:
|
|
23938
|
-
|
|
23939
|
-
|
|
24195
|
+
parameters: Type2.Object({
|
|
24196
|
+
responseId: Type2.String({ description: `The responseId from ${storedContentSources}` }),
|
|
24197
|
+
query: Type2.Optional(Type2.String({ description: searchQueryDescription })),
|
|
24198
|
+
queryIndex: Type2.Optional(Type2.Integer({ minimum: 0, description: "Get content for query at index" })),
|
|
24199
|
+
url: Type2.Optional(Type2.String({ description: "Get content for this URL" })),
|
|
24200
|
+
urlIndex: Type2.Optional(Type2.Integer({ minimum: 0, description: "Get content for URL at index" })),
|
|
24201
|
+
offset: Type2.Optional(Type2.Integer({ minimum: 0, description: "Character offset in stored search or fetched URL content (default 0). Ignored when findText is supplied." })),
|
|
24202
|
+
limit: Type2.Optional(Type2.Integer({ minimum: 1, maximum: maxInlineContentChars, description: "Requested maximum stored-content characters (default and max use maxInlineContentChars). Search-page continuation guidance shares the global output cap and may reduce returnedChars. Ignored when findText is supplied." })),
|
|
24203
|
+
findText: Type2.Optional(Type2.Union([
|
|
24204
|
+
Type2.String({ minLength: 1, maxLength: 500 }),
|
|
24205
|
+
Type2.Array(Type2.String({ minLength: 1, maxLength: 500 }), { minItems: 1, maxItems: 10 })
|
|
23940
24206
|
], { description: "Text or texts to find in the selected stored content. When supplied, offset and limit are ignored." })),
|
|
23941
|
-
findMode:
|
|
24207
|
+
findMode: Type2.Optional(StringEnum(["exact", "case-insensitive", "fuzzy"], { description: "Matching mode for findText (default: case-insensitive). Requires findText." }))
|
|
23942
24208
|
}),
|
|
23943
24209
|
async execute(_toolCallId, rawParams) {
|
|
23944
24210
|
const params = normalizeGetSearchContentParams(rawParams);
|
|
@@ -24045,10 +24311,10 @@ Use ${toolNames.getSearchContent}({ responseId: "${responseId}", urlIndex: 0 })
|
|
|
24045
24311
|
if (params.findText !== void 0) {
|
|
24046
24312
|
try {
|
|
24047
24313
|
const found = findContent(fullResults, normalizeFindQueries(params.findText), params.findMode ?? "case-insensitive");
|
|
24048
|
-
const { text:
|
|
24314
|
+
const { text: text3, ...findDetails } = found;
|
|
24049
24315
|
return {
|
|
24050
|
-
content: [{ type: "text", text:
|
|
24051
|
-
details: { query: queryData.query, resultCount: queryData.results.length, findMode: params.findMode ?? "case-insensitive", ...findDetails }
|
|
24316
|
+
content: [{ type: "text", text: text3 }],
|
|
24317
|
+
details: { responseId: params.responseId, query: queryData.query, resultCount: queryData.results.length, contentLength: fullResults.length, findMode: params.findMode ?? "case-insensitive", ...findDetails }
|
|
24052
24318
|
};
|
|
24053
24319
|
} catch (err) {
|
|
24054
24320
|
const error = err instanceof Error ? err.message : String(err);
|
|
@@ -24058,9 +24324,56 @@ Use ${toolNames.getSearchContent}({ responseId: "${responseId}", urlIndex: 0 })
|
|
|
24058
24324
|
};
|
|
24059
24325
|
}
|
|
24060
24326
|
}
|
|
24327
|
+
const offset = params.offset ?? 0;
|
|
24328
|
+
const limit = params.limit ?? maxInlineContentChars;
|
|
24329
|
+
if (!Number.isInteger(offset) || offset < 0) {
|
|
24330
|
+
return {
|
|
24331
|
+
content: [{ type: "text", text: `Invalid offset: received ${formatInputValue(offset)} for query ${formatInputValue(queryData.query)}; offset must be a non-negative integer. Use 0 or a larger integer.` }],
|
|
24332
|
+
details: { error: "Invalid offset", offset }
|
|
24333
|
+
};
|
|
24334
|
+
}
|
|
24335
|
+
if (!Number.isInteger(limit) || limit <= 0 || limit > maxInlineContentChars) {
|
|
24336
|
+
return {
|
|
24337
|
+
content: [{ type: "text", text: `Invalid limit: received ${formatInputValue(limit)} for query ${formatInputValue(queryData.query)}; limit must be an integer from 1 to ${maxInlineContentChars}. Use a value in that range.` }],
|
|
24338
|
+
details: { error: "Invalid limit", limit, maxLimit: maxInlineContentChars }
|
|
24339
|
+
};
|
|
24340
|
+
}
|
|
24341
|
+
if (offset > fullResults.length) {
|
|
24342
|
+
return {
|
|
24343
|
+
content: [{ type: "text", text: `Offset ${offset} is out of range for query ${formatInputValue(queryData.query)} in responseId ${formatInputValue(params.responseId)}. Received offset ${offset}; valid range is 0-${fullResults.length}. Use an offset within that range.` }],
|
|
24344
|
+
details: { error: "Offset out of range", offset, contentLength: fullResults.length }
|
|
24345
|
+
};
|
|
24346
|
+
}
|
|
24347
|
+
const queryIndex = data.queries.indexOf(queryData);
|
|
24348
|
+
let returnedChars = Math.min(limit, fullResults.length - offset);
|
|
24349
|
+
let endOffset = offset + returnedChars;
|
|
24350
|
+
let continuation = "";
|
|
24351
|
+
while (endOffset < fullResults.length) {
|
|
24352
|
+
continuation = `
|
|
24353
|
+
|
|
24354
|
+
---
|
|
24355
|
+
Showing chars ${offset}-${endOffset} of ${fullResults.length}. Use ${toolNames.getSearchContent}({ responseId: "${params.responseId}", queryIndex: ${queryIndex}, offset: ${endOffset}, limit: ${limit} }) for the next slice.`;
|
|
24356
|
+
const overflow = returnedChars + continuation.length - maxInlineContentChars;
|
|
24357
|
+
if (overflow <= 0) break;
|
|
24358
|
+
returnedChars -= overflow;
|
|
24359
|
+
endOffset = offset + returnedChars;
|
|
24360
|
+
}
|
|
24361
|
+
const resultSlice = fullResults.slice(offset, endOffset);
|
|
24362
|
+
const hasMore = endOffset < fullResults.length;
|
|
24363
|
+
const text2 = `${resultSlice}${hasMore ? continuation : ""}`;
|
|
24061
24364
|
return {
|
|
24062
|
-
content: [{ type: "text", text:
|
|
24063
|
-
details: {
|
|
24365
|
+
content: [{ type: "text", text: text2 }],
|
|
24366
|
+
details: {
|
|
24367
|
+
responseId: params.responseId,
|
|
24368
|
+
query: queryData.query,
|
|
24369
|
+
resultCount: queryData.results.length,
|
|
24370
|
+
contentLength: fullResults.length,
|
|
24371
|
+
offset,
|
|
24372
|
+
limit,
|
|
24373
|
+
returnedChars,
|
|
24374
|
+
nextOffset: hasMore ? endOffset : null,
|
|
24375
|
+
truncated: hasMore
|
|
24376
|
+
}
|
|
24064
24377
|
};
|
|
24065
24378
|
}
|
|
24066
24379
|
if (data.type === "fetch" && data.urls) {
|
|
@@ -24222,6 +24535,13 @@ Showing chars ${offset}-${endOffset} of ${urlData.content.length}.`;
|
|
|
24222
24535
|
}
|
|
24223
24536
|
});
|
|
24224
24537
|
}
|
|
24538
|
+
const activationTools = [
|
|
24539
|
+
...webSearchEnabled ? [{ name: toolNames.webSearch, capability: "search" }] : [],
|
|
24540
|
+
...sourceCheckEnabled ? [{ name: toolNames.sourceCheck, capability: "source-check" }] : [],
|
|
24541
|
+
...fetchContentEnabled ? [{ name: toolNames.fetchContent, capability: "fetch" }] : [],
|
|
24542
|
+
...getSearchContentEnabled ? [{ name: toolNames.getSearchContent, capability: "stored-content" }] : []
|
|
24543
|
+
];
|
|
24544
|
+
registerWebToolActivation(pi, activationTools);
|
|
24225
24545
|
if (isCommandEnabled(initConfig, "websearch")) pi.registerCommand("websearch", {
|
|
24226
24546
|
description: "Open web search curator",
|
|
24227
24547
|
handler: async (args, ctx) => {
|