pi-unsloth-webtools 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +101 -0
- package/ROADMAP.md +77 -0
- package/engines.ts +855 -0
- package/entities.ts +2399 -0
- package/html-to-md.ts +1085 -0
- package/index.ts +86 -0
- package/package.json +64 -0
- package/pdf.ts +717 -0
- package/web-access.ts +375 -0
- package/web-fetch.ts +689 -0
- package/web-search.ts +112 -0
package/web-search.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { checkUrlAccess, scopeSearchQuery, type WebsitePolicy } from "./web-access.ts";
|
|
2
|
+
import {
|
|
3
|
+
autoTextSearch,
|
|
4
|
+
EmptySweepError,
|
|
5
|
+
SearchCancelled,
|
|
6
|
+
SearchTimeoutError,
|
|
7
|
+
type SearchResult,
|
|
8
|
+
} from "./engines.ts";
|
|
9
|
+
|
|
10
|
+
export { EmptySweepError, SearchCancelled, SearchTimeoutError } from "./engines.ts";
|
|
11
|
+
|
|
12
|
+
const SEARCH_TIMEOUT_MS = 300_000;
|
|
13
|
+
const MAX_RESULTS = 5;
|
|
14
|
+
|
|
15
|
+
export const EMPTY_SEARCH_RESULTS = [
|
|
16
|
+
"No results found.",
|
|
17
|
+
"No results found within the website access limits.",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export type SearchClient = (
|
|
21
|
+
query: string,
|
|
22
|
+
maxResults: number,
|
|
23
|
+
signal?: AbortSignal,
|
|
24
|
+
) => Promise<SearchResult[]>;
|
|
25
|
+
|
|
26
|
+
export async function ddgSearch(
|
|
27
|
+
query: string,
|
|
28
|
+
maxResults = MAX_RESULTS,
|
|
29
|
+
signal?: AbortSignal,
|
|
30
|
+
): Promise<SearchResult[]> {
|
|
31
|
+
if (signal?.aborted) throw new SearchCancelled();
|
|
32
|
+
return autoTextSearch(query, maxResults, SEARCH_TIMEOUT_MS, signal);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const POLICY_OVERFETCH = 4;
|
|
36
|
+
|
|
37
|
+
export interface WebSearchOptions {
|
|
38
|
+
maxResults?: number;
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
websitePolicy?: WebsitePolicy | null;
|
|
42
|
+
client?: SearchClient;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function webSearch(
|
|
46
|
+
query: string | undefined,
|
|
47
|
+
options: WebSearchOptions = {},
|
|
48
|
+
): Promise<string> {
|
|
49
|
+
const maxResults = options.maxResults ?? 5;
|
|
50
|
+
const timeoutMs = options.timeoutMs ?? SEARCH_TIMEOUT_MS;
|
|
51
|
+
const signal = options.signal;
|
|
52
|
+
const policy = options.websitePolicy ?? null;
|
|
53
|
+
const client = options.client ?? ddgSearch;
|
|
54
|
+
|
|
55
|
+
if (!query || !query.trim()) return "No query provided.";
|
|
56
|
+
if (signal?.aborted) return "Search cancelled.";
|
|
57
|
+
try {
|
|
58
|
+
const effectiveQuery = scopeSearchQuery(query, policy);
|
|
59
|
+
const restricted = Boolean(
|
|
60
|
+
(policy?.allowedDomains?.length ?? 0) > 0 ||
|
|
61
|
+
(policy?.blockedDomains?.length ?? 0) > 0,
|
|
62
|
+
);
|
|
63
|
+
const wanted = restricted ? maxResults * POLICY_OVERFETCH : maxResults;
|
|
64
|
+
const results = await client(effectiveQuery, wanted, signal);
|
|
65
|
+
if (signal?.aborted) return "Search cancelled.";
|
|
66
|
+
if (!results.length) return EMPTY_SEARCH_RESULTS[0];
|
|
67
|
+
const parts: string[] = [];
|
|
68
|
+
for (const result of results) {
|
|
69
|
+
if (parts.length >= maxResults) break;
|
|
70
|
+
const href = String(result.href ?? "").trim();
|
|
71
|
+
if (href && !checkUrlAccess(href, policy)[0]) continue;
|
|
72
|
+
const title = String(result.title ?? "").replace(/\s+/g, " ");
|
|
73
|
+
const snippet = String(result.body ?? "").replace(/\s+/g, " ");
|
|
74
|
+
parts.push(`Title: ${title}\nURL: ${href}\nSnippet: ${snippet}`);
|
|
75
|
+
}
|
|
76
|
+
if (!parts.length) return EMPTY_SEARCH_RESULTS[1];
|
|
77
|
+
const text = parts.join("\n\n---\n\n");
|
|
78
|
+
return (
|
|
79
|
+
text +
|
|
80
|
+
"\n\n---\n\nIMPORTANT: These are only short snippets. " +
|
|
81
|
+
'To get the full page content, call web_search with the url parameter (e.g. {"url": "<URL>"}).'
|
|
82
|
+
);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return searchFailureMessage(err, timeoutMs);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function searchFailureMessage(exc: unknown, timeoutMs = SEARCH_TIMEOUT_MS): string {
|
|
89
|
+
if (exc instanceof SearchCancelled) return "Search cancelled.";
|
|
90
|
+
if (exc instanceof SearchTimeoutError) {
|
|
91
|
+
return `Search failed: the search engines did not respond within ${Math.round(timeoutMs / 1000)}s.`;
|
|
92
|
+
}
|
|
93
|
+
if (exc instanceof EmptySweepError || (exc instanceof Error && exc.message.includes("No results found"))) {
|
|
94
|
+
return EMPTY_SEARCH_RESULTS[0];
|
|
95
|
+
}
|
|
96
|
+
return `Search failed: ${exc instanceof Error ? exc.message : String(exc)}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function formatSearchResults(results: SearchResult[]): string {
|
|
100
|
+
const parts = results.map((result) => {
|
|
101
|
+
const title = result.title.replace(/\s+/g, " ");
|
|
102
|
+
const href = result.href.trim();
|
|
103
|
+
const snippet = result.body.replace(/\s+/g, " ");
|
|
104
|
+
return `Title: ${title}\nURL: ${href}\nSnippet: ${snippet}`;
|
|
105
|
+
});
|
|
106
|
+
const text = parts.join("\n\n---\n\n");
|
|
107
|
+
return (
|
|
108
|
+
text +
|
|
109
|
+
"\n\n---\n\nIMPORTANT: These are only short snippets. " +
|
|
110
|
+
'To get the full page content, call web_search with the url parameter (e.g. {"url": "<URL>"}).'
|
|
111
|
+
);
|
|
112
|
+
}
|