pi-unsloth-webtools 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/cache.ts +9 -1
- package/engines.ts +3 -0
- package/index.ts +7 -4
- package/package.json +2 -2
- package/pdf.ts +2 -1
- package/user-agents.ts +6 -7
- package/web-fetch.ts +9 -3
- package/web-search.ts +3 -2
package/README.md
CHANGED
|
@@ -174,10 +174,13 @@ Optional settings in `~/.pi/agent/settings.json` or `.pi/settings.json` (project
|
|
|
174
174
|
|---|---|---|
|
|
175
175
|
| `unslothWebTools.maxResults` | `5` | Default `maxResults` for `web_search` (clamped 1-20) |
|
|
176
176
|
| `unslothWebTools.maxChars` / `webFetch.maxChars` / `smartFetchDefaultMaxChars` | tool param | Default `maxChars` for `web_fetch` and `web_search` url mode |
|
|
177
|
-
| `unslothWebTools.timeoutMs` / `webFetch.timeoutMs` / `smartFetchDefaultTimeoutMs` | `60000` | Default `timeoutMs` (>=1000) |
|
|
177
|
+
| `unslothWebTools.timeoutMs` / `webFetch.timeoutMs` / `smartFetchDefaultTimeoutMs` | `60000` fetch, `300000` search | Default `timeoutMs` when the tool param is absent (>=1000). Fetch and `web_search` url mode fall back to 60000; `web_search` query mode falls back to 300000 |
|
|
178
178
|
| `webSearch.maxResults` / `smartWebSearch.resultsPerQuery` | same as above | Legacy aliases for `maxResults` |
|
|
179
|
+
| `websitePolicy` | none | Not read from settings. Tools run unrestricted by default; `websitePolicy` is a programmatic option the host passes to `webSearch` / `fetchPageText` |
|
|
179
180
|
|
|
180
|
-
Tool params always win over file defaults.
|
|
181
|
+
Tool params always win over file defaults. Search dedup also strips default ports, so `https://example.com:443/a` and `https://example.com/a` collapse.
|
|
182
|
+
|
|
183
|
+
Environment overrides: `PI_UNSLOTH_CACHE_DIR` changes the fetch cache directory, `PI_UNSLOTH_WEBTOOLS_STATS` opts into append-only sweep stats JSONL, `PI_CODING_AGENT_DIR` / `PI_AGENT_DIR` change the global settings directory. Cache entries live 1 hour and stale copies are served only after a network failure.
|
|
181
184
|
|
|
182
185
|
## Troubleshooting
|
|
183
186
|
|
package/cache.ts
CHANGED
|
@@ -98,9 +98,17 @@ async function prune(): Promise<void> {
|
|
|
98
98
|
} catch {
|
|
99
99
|
return;
|
|
100
100
|
}
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
const tmpFiles = files.filter((f) => f.includes(".tmp."));
|
|
103
|
+
for (const file of tmpFiles) {
|
|
104
|
+
try {
|
|
105
|
+
const full = join(dir, file);
|
|
106
|
+
const s = await stat(full);
|
|
107
|
+
if (now - s.mtimeMs >= CACHE_TTL_MS) await unlinkSafe(full);
|
|
108
|
+
} catch {}
|
|
109
|
+
}
|
|
101
110
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
102
111
|
if (!jsonFiles.length) return;
|
|
103
|
-
const now = Date.now();
|
|
104
112
|
const entries: { file: string; size: number; timestamp: number }[] = [];
|
|
105
113
|
let total = 0;
|
|
106
114
|
for (const file of jsonFiles) {
|
package/engines.ts
CHANGED
|
@@ -82,6 +82,9 @@ export function canonicalizeHref(href: string): string {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
url.hash = "";
|
|
85
|
+
if ((url.protocol === "http:" && url.port === "80") || (url.protocol === "https:" && url.port === "443")) {
|
|
86
|
+
url.port = "";
|
|
87
|
+
}
|
|
85
88
|
return url.toString();
|
|
86
89
|
} catch {
|
|
87
90
|
return href;
|
package/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { defineTool, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { webSearch as defaultWebSearch } from "./web-search.ts";
|
|
3
|
+
import { SEARCH_TIMEOUT_MS, webSearch as defaultWebSearch } from "./web-search.ts";
|
|
4
4
|
import { DEFAULT_FETCH_TIMEOUT_MS, fetchPageText as defaultFetchPageText } from "./web-fetch.ts";
|
|
5
|
-
import { loadDefaultFetchSettings } from "./settings.ts";
|
|
5
|
+
import { loadDefaultFetchSettings, loadDefaultFetchTimeoutMs } from "./settings.ts";
|
|
6
6
|
|
|
7
7
|
function positiveNumber(value: unknown): number | undefined {
|
|
8
8
|
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
@@ -107,11 +107,14 @@ export function createWebTools(deps: WebToolsDeps = {}) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
onUpdate?.({ content: [{ type: "text", text: "Searching the web..." }], details: {} });
|
|
110
|
+
const timeoutParam = positiveNumber(params.timeoutMs);
|
|
111
|
+
const searchCwd = (_ctx as ExtensionContext | undefined)?.cwd;
|
|
112
|
+
const searchTimeoutMs = timeoutParam ?? (await loadDefaultFetchTimeoutMs(searchCwd)) ?? SEARCH_TIMEOUT_MS;
|
|
110
113
|
const text = await webSearch(params.query, {
|
|
111
114
|
signal: signal ?? undefined,
|
|
112
|
-
timeoutMs:
|
|
115
|
+
timeoutMs: searchTimeoutMs,
|
|
113
116
|
maxResults: positiveNumber(params.maxResults),
|
|
114
|
-
cwd:
|
|
117
|
+
cwd: searchCwd,
|
|
115
118
|
});
|
|
116
119
|
return { content: [{ type: "text", text }], details: {} };
|
|
117
120
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-unsloth-webtools",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pi extension: web_search and web_fetch tools ported from the Unsloth Studio codebase (DuckDuckGo search, SSRF-safe direct fetching, HTML-to-Markdown extraction)",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"test:smoke": "vitest run test/smoke.test.ts",
|
|
56
56
|
"typecheck": "tsc --noEmit",
|
|
57
57
|
"check:package": "node -e \"const fs=require('fs');const pkg=require('./package.json');const listed=new Set(pkg.files);const bad=[];for(const f of pkg.files){if(!fs.existsSync(f))bad.push('missing file: '+f)}for(const f of fs.readdirSync('.').filter(f=>f.endsWith('.ts'))){if(!listed.has(f))bad.push('unlisted source: '+f)}if(bad.length){console.error(bad.join('\\n'));process.exit(1)}\"",
|
|
58
|
-
"prepublishOnly": "npm run typecheck && npm run test:unit && npm run check:package && npm run check:publint",
|
|
58
|
+
"prepublishOnly": "npm run typecheck && npm run lint && npm run test:unit && npm run check:package && npm run check:publint",
|
|
59
59
|
"lint": "oxlint .",
|
|
60
60
|
"check:publint": "publint",
|
|
61
61
|
"check:attw": "attw --pack"
|
package/pdf.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { inflateSync } from "node:zlib";
|
|
3
3
|
|
|
4
4
|
export const MAX_WEB_PDF_PAGES = 50;
|
|
5
|
+
const MAX_PDF_INFLATE_BYTES = 64 * 1024 * 1024;
|
|
5
6
|
|
|
6
7
|
export class PdfParseError extends Error {
|
|
7
8
|
constructor() {
|
|
@@ -786,7 +787,7 @@ async function extractPdfTextFallback(data: Buffer): Promise<string> {
|
|
|
786
787
|
let bytes = raw;
|
|
787
788
|
if (isFlate) {
|
|
788
789
|
try {
|
|
789
|
-
bytes = inflateSync(raw);
|
|
790
|
+
bytes = inflateSync(raw, { maxOutputLength: MAX_PDF_INFLATE_BYTES });
|
|
790
791
|
} catch {
|
|
791
792
|
continue;
|
|
792
793
|
}
|
package/user-agents.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
const USER_AGENTS = [
|
|
2
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
3
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
4
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
5
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:
|
|
6
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:
|
|
7
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/
|
|
2
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
|
|
3
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
|
|
4
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
|
|
5
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
|
|
6
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0",
|
|
7
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15",
|
|
8
8
|
];
|
|
9
|
-
|
|
10
9
|
export function randomUserAgent(): string {
|
|
11
10
|
return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
|
|
12
11
|
}
|
package/web-fetch.ts
CHANGED
|
@@ -631,8 +631,14 @@ export function requestHop(opts: HopOptions): Promise<HopResponse> {
|
|
|
631
631
|
headers: opts.headers,
|
|
632
632
|
timeout: opts.inactivityMs,
|
|
633
633
|
servername: url.protocol === "https:" ? url.hostname : undefined,
|
|
634
|
-
lookup: (
|
|
635
|
-
|
|
634
|
+
lookup: ((_hostname: string, _options: unknown, _callback: unknown) => {
|
|
635
|
+
const done = (typeof _options === "function" ? _options : _callback) as (err: unknown, address: unknown, family?: unknown) => void;
|
|
636
|
+
if ((_options as { all?: boolean } | undefined)?.all) {
|
|
637
|
+
done(null, [{ address: opts.pinnedIp, family: opts.family }]);
|
|
638
|
+
} else {
|
|
639
|
+
done(null, opts.pinnedIp, opts.family);
|
|
640
|
+
}
|
|
641
|
+
}) as typeof options.lookup,
|
|
636
642
|
};
|
|
637
643
|
let settled = false;
|
|
638
644
|
let resRef: IncomingMessage | null = null;
|
|
@@ -798,7 +804,7 @@ export async function fetchUrlRaw(
|
|
|
798
804
|
url: string,
|
|
799
805
|
options: RawFetchOptions = {},
|
|
800
806
|
): Promise<RawFetchResult> {
|
|
801
|
-
const timeoutMs = options.timeoutMs ??
|
|
807
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
802
808
|
const now = options.nowMs ?? Date.now;
|
|
803
809
|
const deadline = options.deadlineMs ?? now() + timeoutMs;
|
|
804
810
|
const signal = options.signal;
|
package/web-search.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
export { EmptySweepError, SearchCancelled, SearchTimeoutError } from "./engines.ts";
|
|
13
13
|
|
|
14
|
-
const SEARCH_TIMEOUT_MS = 300_000;
|
|
14
|
+
export const SEARCH_TIMEOUT_MS = 300_000;
|
|
15
15
|
const MAX_RESULTS = 5;
|
|
16
16
|
|
|
17
17
|
export const EMPTY_SEARCH_RESULTS = [
|
|
@@ -56,7 +56,8 @@ export async function webSearch(
|
|
|
56
56
|
const saneMax = Number.isFinite(rawMaxResults) ? Math.floor(rawMaxResults as number) : MAX_RESULTS;
|
|
57
57
|
const maxResults = Math.min(20, Math.max(1, saneMax));
|
|
58
58
|
const rawTimeout = options.timeoutMs ?? SEARCH_TIMEOUT_MS;
|
|
59
|
-
const
|
|
59
|
+
const flooredTimeout = Number.isFinite(rawTimeout) ? Math.floor(rawTimeout as number) : SEARCH_TIMEOUT_MS;
|
|
60
|
+
const timeoutMs = flooredTimeout >= 1 ? flooredTimeout : SEARCH_TIMEOUT_MS;
|
|
60
61
|
const signal = options.signal;
|
|
61
62
|
const policy = options.websitePolicy ?? null;
|
|
62
63
|
const client = options.client ?? ddgSearch;
|