@herbertgao/pi-extensions 2026.9.9 → 2026.9.11
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/README.md +1 -0
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -2
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +12 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +427 -120
- package/node_modules/@herbertgao/pi-subagents/docs/rpc.md +184 -0
- package/node_modules/@herbertgao/pi-subagents/docs/workflows.md +466 -0
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +6 -6
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/compose.js +52 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/fan-out-audit.js +56 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/gated-fix.js +60 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/lib/count-child.js +30 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/review-panel.js +68 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/structured-findings.js +81 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +12 -10
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +52 -12
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +837 -146
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +213 -39
- package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +73 -14
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +101 -47
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +2249 -914
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +13 -0
- package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +215 -0
- package/node_modules/@herbertgao/pi-subagents/src/mention.ts +147 -0
- package/node_modules/@herbertgao/pi-subagents/src/model-resolver.ts +9 -1
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +40 -26
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +18 -8
- package/node_modules/@herbertgao/pi-subagents/src/prompts.ts +46 -9
- package/node_modules/@herbertgao/pi-subagents/src/schedule.ts +21 -16
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +137 -7
- package/node_modules/@herbertgao/pi-subagents/src/structured-output.ts +136 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +126 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-mention.ts +274 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +20 -5
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +10 -4
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +167 -22
- package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-card.ts +555 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-dialog.ts +1304 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-menu.ts +226 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/collisions.ts +122 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/entry.ts +47 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/host.ts +463 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/journal.ts +164 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/json-schema.ts +142 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/meta.ts +401 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/progress.ts +622 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/runtime.ts +1399 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/saved.ts +230 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/task.ts +333 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/tool-description.ts +200 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/worker-source.ts +781 -0
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +97 -95
- package/node_modules/@herbertgao/pi-subagents/src/xml.ts +13 -0
- package/node_modules/@herbertgao/resume-from/package.json +1 -1
- package/node_modules/@herbertgao/sol-pi/README.md +3 -3
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
- package/node_modules/@herbertgao/sol-pi/agents-install.md +4 -4
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +6 -6
- package/node_modules/@herbertgao/sol-pi/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/dist/index.ts +39 -89
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -87
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +8 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +40 -37
- package/node_modules/@narumitw/pi-caffeinate/README.md +21 -66
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +10 -41
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +50 -51
- package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +637 -663
- package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +114 -120
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +29 -29
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +108 -126
- package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +124 -128
- package/node_modules/pi-multi-account/CHANGELOG.md +1209 -0
- package/node_modules/pi-multi-account/CONTRIBUTING.md +61 -0
- package/node_modules/pi-multi-account/LICENSE +21 -0
- package/node_modules/pi-multi-account/README.md +197 -0
- package/node_modules/pi-multi-account/SECURITY.md +27 -0
- package/node_modules/pi-multi-account/auth-file-transaction.ts +56 -0
- package/node_modules/pi-multi-account/child-usability.ts +233 -0
- package/node_modules/pi-multi-account/compaction-summary.ts +32 -0
- package/node_modules/pi-multi-account/completion-route-planner.ts +224 -0
- package/node_modules/pi-multi-account/context-guard.ts +420 -0
- package/node_modules/pi-multi-account/cursor/LICENSE +21 -0
- package/node_modules/pi-multi-account/cursor/NOTICE +2 -0
- package/node_modules/pi-multi-account/cursor/auth.ts +165 -0
- package/node_modules/pi-multi-account/cursor/bridge-handle.ts +155 -0
- package/node_modules/pi-multi-account/cursor/conversation-registry.ts +104 -0
- package/node_modules/pi-multi-account/cursor/cursor-models-raw.json +611 -0
- package/node_modules/pi-multi-account/cursor/cursor-shared.ts +192 -0
- package/node_modules/pi-multi-account/cursor/h2-bridge.mjs +175 -0
- package/node_modules/pi-multi-account/cursor/index.ts +572 -0
- package/node_modules/pi-multi-account/cursor/message-parsing.ts +323 -0
- package/node_modules/pi-multi-account/cursor/prompt-usage.ts +53 -0
- package/node_modules/pi-multi-account/cursor/proto/agent_pb.ts +15294 -0
- package/node_modules/pi-multi-account/cursor/proxy.ts +2510 -0
- package/node_modules/pi-multi-account/cursor/session-lifecycle.ts +40 -0
- package/node_modules/pi-multi-account/cursor/sse-keepalive.ts +24 -0
- package/node_modules/pi-multi-account/cursor/stream-lifecycle.ts +193 -0
- package/node_modules/pi-multi-account/cursor/upstream-watchdog.ts +88 -0
- package/node_modules/pi-multi-account/cursor-bridge.ts +240 -0
- package/node_modules/pi-multi-account/cursor-model-name.ts +12 -0
- package/node_modules/pi-multi-account/index.ts +11825 -0
- package/node_modules/pi-multi-account/model-catalog.ts +354 -0
- package/node_modules/pi-multi-account/package.json +101 -0
- package/node_modules/pi-multi-account/pi-contract.ts +281 -0
- package/node_modules/pi-multi-account/provider-payload-stream.ts +44 -0
- package/node_modules/pi-multi-account/provider-priority.ts +189 -0
- package/node_modules/pi-multi-account/slot-proxy-auth.ts +167 -0
- package/node_modules/pi-multi-account/slot-proxy.ts +344 -0
- package/node_modules/pi-multi-account/state-file-transaction.ts +67 -0
- package/node_modules/pi-multi-account/usage.ts +1099 -0
- package/node_modules/pi-typesafe/README.md +6 -2
- package/node_modules/pi-typesafe/dist/client.d.ts +11 -0
- package/node_modules/pi-typesafe/dist/client.js +45 -10
- package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/package.json +2 -2
- package/node_modules/pi-web-access/CHANGELOG.md +36 -0
- package/node_modules/pi-web-access/README.md +75 -18
- package/node_modules/pi-web-access/anysearch.ts +4 -15
- package/node_modules/pi-web-access/bocha.ts +3 -22
- package/node_modules/pi-web-access/brave.ts +3 -21
- package/node_modules/pi-web-access/brightdata.ts +5 -32
- package/node_modules/pi-web-access/content-find.ts +168 -53
- package/node_modules/pi-web-access/curator-page.ts +4 -1
- package/node_modules/pi-web-access/curator-run.ts +2 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +24620 -0
- package/node_modules/pi-web-access/domain-filter-normalization.ts +14 -0
- package/node_modules/pi-web-access/duckduckgo.ts +3 -21
- package/node_modules/pi-web-access/extract.ts +3 -1
- package/node_modules/pi-web-access/firecrawl.ts +5 -29
- package/node_modules/pi-web-access/gemini-search.ts +81 -32
- package/node_modules/pi-web-access/index.ts +149 -148
- package/node_modules/pi-web-access/jina-search.ts +4 -15
- package/node_modules/pi-web-access/kagi.ts +4 -13
- package/node_modules/pi-web-access/kimi-search.ts +5 -30
- package/node_modules/pi-web-access/mistral-search.ts +1 -15
- package/node_modules/pi-web-access/ollama.ts +2 -7
- package/node_modules/pi-web-access/openai-search.ts +174 -36
- package/node_modules/pi-web-access/opencode-session-headers.ts +24 -0
- package/node_modules/pi-web-access/package.json +10 -4
- package/node_modules/pi-web-access/page-query.ts +10 -2
- package/node_modules/pi-web-access/parallel.ts +1 -15
- package/node_modules/pi-web-access/pdf-extract.ts +3 -0
- package/node_modules/pi-web-access/querit.ts +5 -29
- package/node_modules/pi-web-access/search-answer-formatting.ts +11 -0
- package/node_modules/pi-web-access/search-result-count-normalization.ts +4 -0
- package/node_modules/pi-web-access/search1api.ts +5 -29
- package/node_modules/pi-web-access/searchinfinity.ts +5 -29
- package/node_modules/pi-web-access/searxng.ts +3 -21
- package/node_modules/pi-web-access/serpapi.ts +5 -28
- package/node_modules/pi-web-access/serpbase.ts +3 -22
- package/node_modules/pi-web-access/serpdive.ts +3 -21
- package/node_modules/pi-web-access/serper.ts +5 -28
- package/node_modules/pi-web-access/serply.ts +197 -0
- package/node_modules/pi-web-access/source-check.ts +11 -47
- package/node_modules/pi-web-access/summary-review.ts +7 -3
- package/node_modules/pi-web-access/tavily.ts +3 -21
- package/node_modules/pi-web-access/tinyfish.ts +5 -29
- package/node_modules/pi-web-access/utils.ts +9 -1
- package/node_modules/pi-web-access/valyu.ts +5 -28
- package/node_modules/pi-web-access/xai-search.ts +1 -15
- package/node_modules/pi-web-access/xcrawl.ts +5 -32
- package/package.json +17 -11
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
3
4
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
4
5
|
import type { ExtractedContent, ExtractOptions } from "./extract.ts";
|
|
5
6
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
7
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
8
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
6
9
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
7
10
|
|
|
8
11
|
const QUERIT_SEARCH_URL = "https://api.querit.ai/v1/search";
|
|
@@ -123,26 +126,6 @@ function requestSignal(signal: AbortSignal | undefined, timeoutMs: number): Abor
|
|
|
123
126
|
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
124
127
|
}
|
|
125
128
|
|
|
126
|
-
function normalizeNumResults(value: number | undefined): number {
|
|
127
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
128
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function normalizeDomain(value: string): string | null {
|
|
132
|
-
let input = value.trim().toLowerCase();
|
|
133
|
-
if (!input) return null;
|
|
134
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
135
|
-
if (!input) return null;
|
|
136
|
-
try {
|
|
137
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
138
|
-
input = parsed.hostname;
|
|
139
|
-
} catch {
|
|
140
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
141
|
-
}
|
|
142
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
143
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
129
|
function mapDomainFilter(domainFilter: string[] | undefined): { include: string[]; exclude: string[] } {
|
|
147
130
|
const include: string[] = [];
|
|
148
131
|
const exclude: string[] = [];
|
|
@@ -176,7 +159,7 @@ function buildSearchBody(query: string, options: QueritSearchOptions): Record<st
|
|
|
176
159
|
if (date) filters.timeRange = { date };
|
|
177
160
|
return {
|
|
178
161
|
query,
|
|
179
|
-
count:
|
|
162
|
+
count: normalizeSearchResultCount(options.numResults),
|
|
180
163
|
...(Object.keys(filters).length > 0 ? { filters } : {}),
|
|
181
164
|
};
|
|
182
165
|
}
|
|
@@ -273,13 +256,6 @@ function mapSearchResults(data: QueritSearchResponse): SearchResponse["results"]
|
|
|
273
256
|
});
|
|
274
257
|
}
|
|
275
258
|
|
|
276
|
-
function buildAnswer(results: SearchResponse["results"]): string {
|
|
277
|
-
return results.map((result) => {
|
|
278
|
-
if (result.snippet) return `${result.snippet}\nSource: ${result.title} (${result.url})`;
|
|
279
|
-
return `Source: ${result.title} (${result.url})`;
|
|
280
|
-
}).join("\n\n");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
259
|
function mapContentResult(result: QueritContentResult | undefined, requestedUrl: string): ExtractedContent | null {
|
|
284
260
|
if (!result || typeof result.content !== "string" || result.content.trim().length === 0) return null;
|
|
285
261
|
const metadata = result.extrasMeta;
|
|
@@ -365,7 +341,7 @@ export async function searchWithQuerit(
|
|
|
365
341
|
);
|
|
366
342
|
assertApiSuccess("Search", data);
|
|
367
343
|
const results = mapSearchResults(data);
|
|
368
|
-
const response: SearchResponse = { answer:
|
|
344
|
+
const response: SearchResponse = { answer: formatSearchResultsAsAnswer(results), results };
|
|
369
345
|
if (options.includeContent && results.length > 0) {
|
|
370
346
|
const inlineContent = await fetchInlineContent(results.map((result) => result.url), apiKey, options.signal);
|
|
371
347
|
if (inlineContent.length > 0) response.inlineContent = inlineContent;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface AnswerResult {
|
|
2
|
+
title: string;
|
|
3
|
+
url: string;
|
|
4
|
+
snippet: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatSearchResultsAsAnswer(results: readonly AnswerResult[]): string {
|
|
8
|
+
return results.map((result) => result.snippet
|
|
9
|
+
? `${result.snippet}\nSource: ${result.title} (${result.url})`
|
|
10
|
+
: `Source: ${result.title} (${result.url})`).join("\n\n");
|
|
11
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
3
4
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
4
5
|
import type { ExtractedContent, ExtractOptions } from "./extract.ts";
|
|
5
6
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
7
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
8
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
6
9
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
7
10
|
|
|
8
11
|
const SEARCH1API_SEARCH_URL = "https://api.search1api.com/search";
|
|
@@ -102,26 +105,6 @@ function requestSignal(signal: AbortSignal | undefined, timeoutMs: number): Abor
|
|
|
102
105
|
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
103
106
|
}
|
|
104
107
|
|
|
105
|
-
function normalizeNumResults(value: number | undefined): number {
|
|
106
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
107
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function normalizeDomain(value: string): string | null {
|
|
111
|
-
let input = value.trim().toLowerCase();
|
|
112
|
-
if (!input) return null;
|
|
113
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
114
|
-
if (!input) return null;
|
|
115
|
-
try {
|
|
116
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
117
|
-
input = parsed.hostname;
|
|
118
|
-
} catch {
|
|
119
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
120
|
-
}
|
|
121
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
122
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
108
|
function mapDomainFilter(domainFilter: string[] | undefined): { includeSites: string[]; excludeSites: string[] } {
|
|
126
109
|
const includeSites: string[] = [];
|
|
127
110
|
const excludeSites: string[] = [];
|
|
@@ -135,7 +118,7 @@ function mapDomainFilter(domainFilter: string[] | undefined): { includeSites: st
|
|
|
135
118
|
}
|
|
136
119
|
|
|
137
120
|
function buildSearchBody(query: string, options: Search1APISearchOptions): Record<string, unknown> {
|
|
138
|
-
const numResults =
|
|
121
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
139
122
|
const { includeSites, excludeSites } = mapDomainFilter(options.domainFilter);
|
|
140
123
|
return {
|
|
141
124
|
query,
|
|
@@ -215,13 +198,6 @@ function mapInlineContent(results: Search1APISearchResult[] | undefined): Extrac
|
|
|
215
198
|
});
|
|
216
199
|
}
|
|
217
200
|
|
|
218
|
-
function buildAnswer(results: SearchResponse["results"]): string {
|
|
219
|
-
return results.map((result) => {
|
|
220
|
-
if (result.snippet) return `${result.snippet}\nSource: ${result.title} (${result.url})`;
|
|
221
|
-
return `Source: ${result.title} (${result.url})`;
|
|
222
|
-
}).join("\n\n");
|
|
223
|
-
}
|
|
224
|
-
|
|
225
201
|
export async function searchWithSearch1API(
|
|
226
202
|
query: string,
|
|
227
203
|
options: Search1APISearchOptions = {},
|
|
@@ -238,7 +214,7 @@ export async function searchWithSearch1API(
|
|
|
238
214
|
options.signal,
|
|
239
215
|
);
|
|
240
216
|
const results = mapSearchResults(data.results);
|
|
241
|
-
const response: SearchResponse = { answer:
|
|
217
|
+
const response: SearchResponse = { answer: formatSearchResultsAsAnswer(results), results };
|
|
242
218
|
if (options.includeContent) {
|
|
243
219
|
const inlineContent = mapInlineContent(data.results);
|
|
244
220
|
if (inlineContent.length > 0) response.inlineContent = inlineContent;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
3
4
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
4
5
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
6
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
7
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
5
8
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
6
9
|
|
|
7
10
|
const SEARCHINFINITY_SEARCH_URL = "https://torchlight.byteintlapi.com/search_api/web_search";
|
|
@@ -109,26 +112,6 @@ function requestSignal(signal: AbortSignal | undefined, timeoutMs: number): Abor
|
|
|
109
112
|
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
function normalizeNumResults(value: number | undefined): number {
|
|
113
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
114
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function normalizeDomain(value: string): string | null {
|
|
118
|
-
let input = value.trim().toLowerCase();
|
|
119
|
-
if (!input) return null;
|
|
120
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
121
|
-
if (!input) return null;
|
|
122
|
-
try {
|
|
123
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
124
|
-
input = parsed.hostname;
|
|
125
|
-
} catch {
|
|
126
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
127
|
-
}
|
|
128
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
129
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
115
|
function mapRecencyFilter(recency: SearchOptions["recencyFilter"]): string | undefined {
|
|
133
116
|
if (recency === "day") return "OneDay";
|
|
134
117
|
if (recency === "week") return "OneWeek";
|
|
@@ -153,7 +136,7 @@ function buildSearchBody(query: string, options: SearchinfinitySearchOptions): R
|
|
|
153
136
|
const timeRange = mapRecencyFilter(options.recencyFilter);
|
|
154
137
|
return {
|
|
155
138
|
Query: query,
|
|
156
|
-
Count:
|
|
139
|
+
Count: normalizeSearchResultCount(options.numResults),
|
|
157
140
|
...(Object.keys(filter).length > 0 ? { Filter: filter } : {}),
|
|
158
141
|
...(timeRange ? { TimeRange: timeRange } : {}),
|
|
159
142
|
};
|
|
@@ -237,13 +220,6 @@ function mapSearchResults(results: SearchinfinityWebResult[] | undefined): Searc
|
|
|
237
220
|
});
|
|
238
221
|
}
|
|
239
222
|
|
|
240
|
-
function buildAnswer(results: SearchResponse["results"]): string {
|
|
241
|
-
return results.map((result) => {
|
|
242
|
-
if (result.snippet) return `${result.snippet}\nSource: ${result.title} (${result.url})`;
|
|
243
|
-
return `Source: ${result.title} (${result.url})`;
|
|
244
|
-
}).join("\n\n");
|
|
245
|
-
}
|
|
246
|
-
|
|
247
223
|
export async function searchWithSearchinfinity(
|
|
248
224
|
query: string,
|
|
249
225
|
options: SearchinfinitySearchOptions = {},
|
|
@@ -253,7 +229,7 @@ export async function searchWithSearchinfinity(
|
|
|
253
229
|
try {
|
|
254
230
|
const data = await searchinfinityJsonRequest(apiKey, buildSearchBody(query, options), options.signal);
|
|
255
231
|
const results = mapSearchResults(data.Result?.WebResults);
|
|
256
|
-
const response: SearchResponse = { answer:
|
|
232
|
+
const response: SearchResponse = { answer: formatSearchResultsAsAnswer(results), results };
|
|
257
233
|
activityMonitor.logComplete(activityId, 200);
|
|
258
234
|
return response;
|
|
259
235
|
} catch (err) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
3
4
|
import { fetchRemoteUrl, loadSsrfConfig } from "./ssrf-protection.ts";
|
|
4
5
|
import type { SearchOptions, SearchResult, SearchResponse } from "./perplexity.ts";
|
|
6
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
5
7
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
6
8
|
|
|
7
9
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
@@ -121,26 +123,6 @@ function requireBaseUrl(): string {
|
|
|
121
123
|
return baseUrl;
|
|
122
124
|
}
|
|
123
125
|
|
|
124
|
-
function normalizeCount(value: number | undefined): number {
|
|
125
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
126
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function normalizeDomain(value: string): string | null {
|
|
130
|
-
let input = value.trim().toLowerCase();
|
|
131
|
-
if (!input) return null;
|
|
132
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
133
|
-
if (!input) return null;
|
|
134
|
-
try {
|
|
135
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
136
|
-
input = parsed.hostname;
|
|
137
|
-
} catch {
|
|
138
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
139
|
-
}
|
|
140
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
141
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
126
|
function normalizeDomainFilters(domainFilter: string[] | undefined): NormalizedDomainFilters {
|
|
145
127
|
const filters: NormalizedDomainFilters = { allowed: [], blocked: [] };
|
|
146
128
|
for (const raw of domainFilter ?? []) {
|
|
@@ -194,7 +176,7 @@ export function isSearXNGAvailable(): boolean {
|
|
|
194
176
|
|
|
195
177
|
export async function searchWithSearXNG(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
196
178
|
const baseUrl = requireBaseUrl();
|
|
197
|
-
const numResults =
|
|
179
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
198
180
|
const filters = normalizeDomainFilters(options.domainFilter);
|
|
199
181
|
const searchQuery = buildSearXNGQuery(query, filters);
|
|
200
182
|
const url = new URL(`${baseUrl}/search`);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
3
4
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
5
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
6
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
4
7
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
8
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
6
9
|
|
|
@@ -69,31 +72,11 @@ async function requireApiKey(signal?: AbortSignal): Promise<string> {
|
|
|
69
72
|
return apiKey;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
function normalizeCount(value: number | undefined): number {
|
|
73
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
74
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
75
|
interface DomainFilters {
|
|
78
76
|
include: string[];
|
|
79
77
|
exclude: string[];
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
function normalizeDomain(value: string): string | null {
|
|
83
|
-
let input = value.trim().toLowerCase();
|
|
84
|
-
if (!input) return null;
|
|
85
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
86
|
-
if (!input) return null;
|
|
87
|
-
try {
|
|
88
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
89
|
-
input = parsed.hostname;
|
|
90
|
-
} catch {
|
|
91
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
92
|
-
}
|
|
93
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
94
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
80
|
function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
|
|
98
81
|
const filters: DomainFilters = { include: [], exclude: [] };
|
|
99
82
|
for (const raw of domainFilter ?? []) {
|
|
@@ -142,19 +125,13 @@ function parseResponse(value: unknown): SerpApiOrganicResult[] {
|
|
|
142
125
|
return envelope.organic_results as SerpApiOrganicResult[];
|
|
143
126
|
}
|
|
144
127
|
|
|
145
|
-
function buildAnswer(results: SearchResponse["results"]): string {
|
|
146
|
-
return results.map((result) => result.snippet
|
|
147
|
-
? `${result.snippet}\nSource: ${result.title} (${result.url})`
|
|
148
|
-
: `Source: ${result.title} (${result.url})`).join("\n\n");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
128
|
export function isSerpApiAvailable(): boolean {
|
|
152
129
|
return hasCredentialSource({ provider: "SerpApi", configuredValue: loadConfig().serpapiApiKey, environmentValue: process.env.SERPAPI_KEY });
|
|
153
130
|
}
|
|
154
131
|
|
|
155
132
|
export async function searchWithSerpApi(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
156
133
|
const apiKey = await requireApiKey(options.signal);
|
|
157
|
-
const numResults =
|
|
134
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
158
135
|
const filters = parseDomainFilter(options.domainFilter);
|
|
159
136
|
const requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;
|
|
160
137
|
const url = new URL(SERPAPI_SEARCH_URL);
|
|
@@ -216,5 +193,5 @@ export async function searchWithSerpApi(query: string, options: SearchOptions =
|
|
|
216
193
|
});
|
|
217
194
|
if (results.length >= numResults) break;
|
|
218
195
|
}
|
|
219
|
-
return { answer:
|
|
196
|
+
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
220
197
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
3
4
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
5
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
4
6
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
7
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
6
8
|
|
|
@@ -85,21 +87,6 @@ function normalizeCount(value: number | undefined): number {
|
|
|
85
87
|
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
function normalizeDomain(value: string): string | null {
|
|
89
|
-
let input = value.trim().toLowerCase();
|
|
90
|
-
if (!input) return null;
|
|
91
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
92
|
-
if (!input) return null;
|
|
93
|
-
try {
|
|
94
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
95
|
-
input = parsed.hostname;
|
|
96
|
-
} catch {
|
|
97
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
98
|
-
}
|
|
99
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
100
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
90
|
interface DomainFilters {
|
|
104
91
|
include: string[];
|
|
105
92
|
exclude: string[];
|
|
@@ -162,12 +149,6 @@ function parseResponse(value: unknown): SerpBaseResponse {
|
|
|
162
149
|
return { ...envelope, organic_results: organic };
|
|
163
150
|
}
|
|
164
151
|
|
|
165
|
-
function buildAnswer(results: SearchResponse["results"]): string {
|
|
166
|
-
return results.map((result) => result.snippet
|
|
167
|
-
? `${result.snippet}\nSource: ${result.title} (${result.url})`
|
|
168
|
-
: `Source: ${result.title} (${result.url})`).join("\n\n");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
152
|
export function isSerpBaseAvailable(): boolean {
|
|
172
153
|
return hasCredentialSource({ provider: "SerpBase", configuredValue: loadConfig().serpbaseApiKey, environmentValue: process.env.SERPBASE_API_KEY });
|
|
173
154
|
}
|
|
@@ -224,5 +205,5 @@ export async function searchWithSerpBase(query: string, options: SearchOptions =
|
|
|
224
205
|
});
|
|
225
206
|
if (results.length >= numResults) break;
|
|
226
207
|
}
|
|
227
|
-
return { answer:
|
|
208
|
+
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
228
209
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
3
4
|
import type { ExtractedContent } from "./extract.ts";
|
|
4
5
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
6
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
5
7
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
6
8
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
7
9
|
|
|
@@ -93,26 +95,6 @@ function resolveModel(): SerpdiveModel {
|
|
|
93
95
|
return (MODELS as readonly string[]).includes(value) ? value as SerpdiveModel : DEFAULT_MODEL;
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
function normalizeCount(value: number | undefined): number {
|
|
97
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
98
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function normalizeDomain(value: string): string | null {
|
|
102
|
-
let input = value.trim().toLowerCase();
|
|
103
|
-
if (!input) return null;
|
|
104
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
105
|
-
if (!input) return null;
|
|
106
|
-
try {
|
|
107
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
108
|
-
input = parsed.hostname;
|
|
109
|
-
} catch {
|
|
110
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
111
|
-
}
|
|
112
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
113
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
98
|
interface DomainFilters {
|
|
117
99
|
include: string[];
|
|
118
100
|
exclude: string[];
|
|
@@ -232,7 +214,7 @@ export function isSerpdiveAvailable(): boolean {
|
|
|
232
214
|
|
|
233
215
|
export async function searchWithSerpdive(query: string, options: SerpdiveSearchOptions = {}): Promise<SearchResponse> {
|
|
234
216
|
const apiKey = await requireApiKey(options.signal);
|
|
235
|
-
const numResults =
|
|
217
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
236
218
|
const filters = parseDomainFilter(options.domainFilter);
|
|
237
219
|
const model = resolveModel();
|
|
238
220
|
const body: Record<string, unknown> = {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
3
4
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
5
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
6
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
4
7
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
8
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
6
9
|
|
|
@@ -69,31 +72,11 @@ async function requireApiKey(signal?: AbortSignal): Promise<string> {
|
|
|
69
72
|
return apiKey;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
function normalizeCount(value: number | undefined): number {
|
|
73
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
74
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
75
|
interface DomainFilters {
|
|
78
76
|
include: string[];
|
|
79
77
|
exclude: string[];
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
function normalizeDomain(value: string): string | null {
|
|
83
|
-
let input = value.trim().toLowerCase();
|
|
84
|
-
if (!input) return null;
|
|
85
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
86
|
-
if (!input) return null;
|
|
87
|
-
try {
|
|
88
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
89
|
-
input = parsed.hostname;
|
|
90
|
-
} catch {
|
|
91
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
92
|
-
}
|
|
93
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
94
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
80
|
function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
|
|
98
81
|
const filters: DomainFilters = { include: [], exclude: [] };
|
|
99
82
|
for (const raw of domainFilter ?? []) {
|
|
@@ -141,19 +124,13 @@ function parseResponse(value: unknown): SerperResult[] {
|
|
|
141
124
|
return envelope.organic as SerperResult[];
|
|
142
125
|
}
|
|
143
126
|
|
|
144
|
-
function buildAnswer(results: SearchResponse["results"]): string {
|
|
145
|
-
return results.map((result) => result.snippet
|
|
146
|
-
? `${result.snippet}\nSource: ${result.title} (${result.url})`
|
|
147
|
-
: `Source: ${result.title} (${result.url})`).join("\n\n");
|
|
148
|
-
}
|
|
149
|
-
|
|
150
127
|
export function isSerperAvailable(): boolean {
|
|
151
128
|
return hasCredentialSource({ provider: "Serper", configuredValue: loadConfig().serperApiKey, environmentValue: process.env.SERPER_API_KEY });
|
|
152
129
|
}
|
|
153
130
|
|
|
154
131
|
export async function searchWithSerper(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
155
132
|
const apiKey = await requireApiKey(options.signal);
|
|
156
|
-
const numResults =
|
|
133
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
157
134
|
const filters = parseDomainFilter(options.domainFilter);
|
|
158
135
|
const requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;
|
|
159
136
|
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
@@ -207,5 +184,5 @@ export async function searchWithSerper(query: string, options: SearchOptions = {
|
|
|
207
184
|
});
|
|
208
185
|
if (results.length >= numResults) break;
|
|
209
186
|
}
|
|
210
|
-
return { answer:
|
|
187
|
+
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
211
188
|
}
|