@demigodmode/pi-web-agent 0.2.1 → 0.3.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 +65 -145
- package/dist/commands/web-agent-config.d.ts +23 -0
- package/dist/commands/web-agent-config.js +254 -0
- package/dist/extension.js +113 -4
- package/dist/presentation/config-store.d.ts +23 -0
- package/dist/presentation/config-store.js +64 -0
- package/dist/presentation/config.d.ts +7 -0
- package/dist/presentation/config.js +44 -0
- package/dist/presentation/explore-presentation.d.ts +3 -0
- package/dist/presentation/explore-presentation.js +34 -0
- package/dist/presentation/fetch-presentation.d.ts +5 -0
- package/dist/presentation/fetch-presentation.js +40 -0
- package/dist/presentation/search-presentation.d.ts +3 -0
- package/dist/presentation/search-presentation.js +30 -0
- package/dist/presentation/select-view.d.ts +2 -0
- package/dist/presentation/select-view.js +12 -0
- package/dist/presentation/types.d.ts +50 -0
- package/dist/presentation/types.js +1 -0
- package/dist/search/duckduckgo.d.ts +6 -1
- package/dist/search/duckduckgo.js +11 -1
- package/dist/tools/web-explore.d.ts +6 -16
- package/dist/tools/web-explore.js +12 -10
- package/dist/tools/web-fetch-headless.js +11 -2
- package/dist/tools/web-fetch.js +11 -2
- package/dist/tools/web-search.js +99 -12
- package/dist/types.d.ts +15 -0
- package/package.json +5 -1
package/dist/tools/web-fetch.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { createHttpFetcher } from '../fetch/http-fetch.js';
|
|
2
|
+
import { buildFetchPresentation } from '../presentation/fetch-presentation.js';
|
|
2
3
|
export function createWebFetchTool({ fetchPage = createHttpFetcher() } = {}) {
|
|
3
4
|
return async function webFetch({ url }) {
|
|
4
5
|
if (!/^https?:\/\//.test(url)) {
|
|
5
|
-
|
|
6
|
+
const result = {
|
|
6
7
|
status: 'unsupported',
|
|
7
8
|
url,
|
|
8
9
|
metadata: { method: 'http', cacheHit: false },
|
|
9
10
|
error: { code: 'UNSUPPORTED_URL', message: 'Only http and https URLs are supported.' }
|
|
10
11
|
};
|
|
12
|
+
return {
|
|
13
|
+
...result,
|
|
14
|
+
presentation: buildFetchPresentation(result)
|
|
15
|
+
};
|
|
11
16
|
}
|
|
12
|
-
|
|
17
|
+
const result = await fetchPage(url);
|
|
18
|
+
return {
|
|
19
|
+
...result,
|
|
20
|
+
presentation: buildFetchPresentation(result)
|
|
21
|
+
};
|
|
13
22
|
};
|
|
14
23
|
}
|
package/dist/tools/web-search.js
CHANGED
|
@@ -1,43 +1,130 @@
|
|
|
1
1
|
import { createCacheKey, createTtlCache } from '../cache/ttl-cache.js';
|
|
2
|
+
import { buildSearchPresentation } from '../presentation/search-presentation.js';
|
|
2
3
|
import { fetchDuckDuckGoHtml, parseDuckDuckGoResults } from '../search/duckduckgo.js';
|
|
4
|
+
function classifySearchFailure(error) {
|
|
5
|
+
const rawMessage = error instanceof Error ? error.message : 'Unknown search failure.';
|
|
6
|
+
const normalized = rawMessage.toLowerCase();
|
|
7
|
+
if (normalized.includes('blocked') ||
|
|
8
|
+
normalized.includes('rate limit') ||
|
|
9
|
+
normalized.includes('rate-limit') ||
|
|
10
|
+
normalized.includes('403') ||
|
|
11
|
+
normalized.includes('429') ||
|
|
12
|
+
normalized.includes('captcha') ||
|
|
13
|
+
normalized.includes('challenge')) {
|
|
14
|
+
return {
|
|
15
|
+
code: 'BLOCKED',
|
|
16
|
+
message: 'DuckDuckGo search appears to be blocked or rate limited.'
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
code: 'FETCH_FAILED',
|
|
21
|
+
message: `DuckDuckGo search request failed: ${rawMessage}`
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function htmlLooksBlocked(html) {
|
|
25
|
+
const normalized = html.toLowerCase();
|
|
26
|
+
return (normalized.includes('captcha') ||
|
|
27
|
+
normalized.includes('challenge') ||
|
|
28
|
+
normalized.includes('verify you are human') ||
|
|
29
|
+
normalized.includes('are you a robot') ||
|
|
30
|
+
normalized.includes('unusual traffic'));
|
|
31
|
+
}
|
|
3
32
|
export function createWebSearchTool({ searchHtml = fetchDuckDuckGoHtml, cache = createTtlCache({ ttlMs: 30_000 }) } = {}) {
|
|
4
33
|
return async function webSearch({ query }) {
|
|
5
34
|
const normalizedQuery = query.trim();
|
|
6
35
|
if (!normalizedQuery) {
|
|
7
|
-
|
|
36
|
+
const result = {
|
|
8
37
|
status: 'error',
|
|
9
38
|
results: [],
|
|
10
39
|
metadata: { backend: 'duckduckgo', cacheHit: false },
|
|
11
40
|
error: { code: 'INVALID_QUERY', message: 'Query must not be empty.' }
|
|
12
41
|
};
|
|
42
|
+
return {
|
|
43
|
+
...result,
|
|
44
|
+
presentation: buildSearchPresentation(result)
|
|
45
|
+
};
|
|
13
46
|
}
|
|
14
47
|
const cacheKey = createCacheKey(['web_search', normalizedQuery]);
|
|
15
48
|
const cached = cache.get(cacheKey);
|
|
16
49
|
if (cached) {
|
|
17
|
-
|
|
50
|
+
const result = {
|
|
18
51
|
...cached,
|
|
19
52
|
metadata: { ...cached.metadata, cacheHit: true }
|
|
20
53
|
};
|
|
54
|
+
return {
|
|
55
|
+
...result,
|
|
56
|
+
presentation: buildSearchPresentation(result)
|
|
57
|
+
};
|
|
21
58
|
}
|
|
22
59
|
try {
|
|
23
60
|
const html = await searchHtml(normalizedQuery);
|
|
61
|
+
const parsed = parseDuckDuckGoResults(html);
|
|
62
|
+
if (parsed.results.length > 0) {
|
|
63
|
+
const result = {
|
|
64
|
+
status: 'ok',
|
|
65
|
+
results: parsed.results,
|
|
66
|
+
metadata: { backend: 'duckduckgo', cacheHit: false }
|
|
67
|
+
};
|
|
68
|
+
cache.set(cacheKey, result);
|
|
69
|
+
return {
|
|
70
|
+
...result,
|
|
71
|
+
presentation: buildSearchPresentation(result)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (parsed.noResults) {
|
|
75
|
+
const result = {
|
|
76
|
+
status: 'error',
|
|
77
|
+
results: [],
|
|
78
|
+
metadata: { backend: 'duckduckgo', cacheHit: false },
|
|
79
|
+
error: {
|
|
80
|
+
code: 'NO_RESULTS',
|
|
81
|
+
message: 'DuckDuckGo returned no usable results for this query.'
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
...result,
|
|
86
|
+
presentation: buildSearchPresentation(result)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (htmlLooksBlocked(html)) {
|
|
90
|
+
const result = {
|
|
91
|
+
status: 'error',
|
|
92
|
+
results: [],
|
|
93
|
+
metadata: { backend: 'duckduckgo', cacheHit: false },
|
|
94
|
+
error: {
|
|
95
|
+
code: 'BLOCKED',
|
|
96
|
+
message: 'DuckDuckGo search appears to be blocked or rate limited.'
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
...result,
|
|
101
|
+
presentation: buildSearchPresentation(result)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
24
104
|
const result = {
|
|
25
|
-
status: '
|
|
26
|
-
results:
|
|
27
|
-
metadata: { backend: 'duckduckgo', cacheHit: false }
|
|
105
|
+
status: 'error',
|
|
106
|
+
results: [],
|
|
107
|
+
metadata: { backend: 'duckduckgo', cacheHit: false },
|
|
108
|
+
error: {
|
|
109
|
+
code: 'PARSE_FAILED',
|
|
110
|
+
message: 'DuckDuckGo returned a page, but it did not match the expected results format.'
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
...result,
|
|
115
|
+
presentation: buildSearchPresentation(result)
|
|
28
116
|
};
|
|
29
|
-
cache.set(cacheKey, result);
|
|
30
|
-
return result;
|
|
31
117
|
}
|
|
32
118
|
catch (error) {
|
|
33
|
-
|
|
119
|
+
const result = {
|
|
34
120
|
status: 'error',
|
|
35
121
|
results: [],
|
|
36
122
|
metadata: { backend: 'duckduckgo', cacheHit: false },
|
|
37
|
-
error:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
123
|
+
error: classifySearchFailure(error)
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
...result,
|
|
127
|
+
presentation: buildSearchPresentation(result)
|
|
41
128
|
};
|
|
42
129
|
}
|
|
43
130
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PresentationEnvelope } from './presentation/types.js';
|
|
1
2
|
export declare const TOOL_STATUSES: readonly ["ok", "needs_headless", "blocked", "unsupported", "error"];
|
|
2
3
|
export type ToolStatus = (typeof TOOL_STATUSES)[number];
|
|
3
4
|
export type SearchResult = {
|
|
@@ -30,6 +31,7 @@ export type WebSearchResponse = {
|
|
|
30
31
|
status: 'ok' | 'error';
|
|
31
32
|
results: SearchResult[];
|
|
32
33
|
metadata: SearchMetadata;
|
|
34
|
+
presentation?: PresentationEnvelope;
|
|
33
35
|
error?: ToolError;
|
|
34
36
|
};
|
|
35
37
|
export type WebFetchResponse = {
|
|
@@ -37,6 +39,7 @@ export type WebFetchResponse = {
|
|
|
37
39
|
url: string;
|
|
38
40
|
content?: ExtractedContent;
|
|
39
41
|
metadata: FetchMetadata;
|
|
42
|
+
presentation?: PresentationEnvelope;
|
|
40
43
|
error?: ToolError;
|
|
41
44
|
};
|
|
42
45
|
export type WebFetchHeadlessResponse = {
|
|
@@ -44,5 +47,17 @@ export type WebFetchHeadlessResponse = {
|
|
|
44
47
|
url: string;
|
|
45
48
|
content?: ExtractedContent;
|
|
46
49
|
metadata: FetchMetadata;
|
|
50
|
+
presentation?: PresentationEnvelope;
|
|
51
|
+
error?: ToolError;
|
|
52
|
+
};
|
|
53
|
+
export type WebExploreResponse = {
|
|
54
|
+
status: 'ok' | 'error';
|
|
55
|
+
findings: string[];
|
|
56
|
+
sources: Array<{
|
|
57
|
+
title: string;
|
|
58
|
+
url: string;
|
|
59
|
+
}>;
|
|
60
|
+
caveat?: string;
|
|
61
|
+
presentation?: PresentationEnvelope;
|
|
47
62
|
error?: ToolError;
|
|
48
63
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demigodmode/pi-web-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Pi package for reliable web access with explicit search, fetch, and headless boundaries.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/extension.js",
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
"test": "vitest run --coverage",
|
|
41
41
|
"test:watch": "vitest",
|
|
42
42
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
43
|
+
"docs:dev": "vitepress dev docs",
|
|
44
|
+
"docs:build": "vitepress build docs",
|
|
45
|
+
"docs:preview": "vitepress preview docs",
|
|
43
46
|
"eval:live": "npm run build:dev && node dist/scripts/live-web-eval.js",
|
|
44
47
|
"release:dry-run": "node scripts/release.mjs --dry-run",
|
|
45
48
|
"release": "node scripts/release.mjs"
|
|
@@ -62,6 +65,7 @@
|
|
|
62
65
|
"@types/node": "^24.0.0",
|
|
63
66
|
"@vitest/coverage-v8": "^3.2.4",
|
|
64
67
|
"typescript": "^5.8.0",
|
|
68
|
+
"vitepress": "^1.6.4",
|
|
65
69
|
"vitest": "^3.2.0"
|
|
66
70
|
},
|
|
67
71
|
"peerDependencies": {
|