@demigodmode/pi-web-agent 0.2.2 → 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 +119 -63
- 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 +1 -1
- package/dist/scripts/live-web-eval.d.ts +0 -1
- package/dist/scripts/live-web-eval.js +0 -411
- package/dist/src/cache/ttl-cache.d.ts +0 -8
- package/dist/src/cache/ttl-cache.js +0 -21
- package/dist/src/extension.d.ts +0 -2
- package/dist/src/extension.js +0 -155
- package/dist/src/extract/readability.d.ts +0 -8
- package/dist/src/extract/readability.js +0 -93
- package/dist/src/fetch/browser-resolution.d.ts +0 -15
- package/dist/src/fetch/browser-resolution.js +0 -55
- package/dist/src/fetch/headless-fetch.d.ts +0 -18
- package/dist/src/fetch/headless-fetch.js +0 -87
- package/dist/src/fetch/http-fetch.d.ts +0 -4
- package/dist/src/fetch/http-fetch.js +0 -50
- package/dist/src/orchestration/index.d.ts +0 -41
- package/dist/src/orchestration/index.js +0 -9
- package/dist/src/orchestration/research-orchestrator.d.ts +0 -43
- package/dist/src/orchestration/research-orchestrator.js +0 -87
- package/dist/src/orchestration/research-types.d.ts +0 -41
- package/dist/src/orchestration/research-types.js +0 -1
- package/dist/src/orchestration/research-worker.d.ts +0 -16
- package/dist/src/orchestration/research-worker.js +0 -131
- package/dist/src/search/duckduckgo.d.ts +0 -9
- package/dist/src/search/duckduckgo.js +0 -52
- package/dist/src/tools/web-explore.d.ts +0 -44
- package/dist/src/tools/web-explore.js +0 -50
- package/dist/src/tools/web-fetch-headless.d.ts +0 -6
- package/dist/src/tools/web-fetch-headless.js +0 -14
- package/dist/src/tools/web-fetch.d.ts +0 -6
- package/dist/src/tools/web-fetch.js +0 -14
- package/dist/src/tools/web-search.d.ts +0 -10
- package/dist/src/tools/web-search.js +0 -103
- package/dist/src/types.d.ts +0 -48
- package/dist/src/types.js +0 -7
- package/dist/tests/cache/ttl-cache.test.d.ts +0 -1
- package/dist/tests/cache/ttl-cache.test.js +0 -19
- package/dist/tests/contracts.test.d.ts +0 -1
- package/dist/tests/contracts.test.js +0 -65
- package/dist/tests/extension.test.d.ts +0 -1
- package/dist/tests/extension.test.js +0 -123
- package/dist/tests/extract/readability.test.d.ts +0 -1
- package/dist/tests/extract/readability.test.js +0 -79
- package/dist/tests/fetch/browser-resolution.test.d.ts +0 -1
- package/dist/tests/fetch/browser-resolution.test.js +0 -37
- package/dist/tests/fetch/headless-fetch.smoke.test.d.ts +0 -1
- package/dist/tests/fetch/headless-fetch.smoke.test.js +0 -17
- package/dist/tests/fetch/headless-fetch.test.d.ts +0 -1
- package/dist/tests/fetch/headless-fetch.test.js +0 -150
- package/dist/tests/fetch/http-fetch.test.d.ts +0 -1
- package/dist/tests/fetch/http-fetch.test.js +0 -129
- package/dist/tests/orchestration/research-orchestrator.test.d.ts +0 -1
- package/dist/tests/orchestration/research-orchestrator.test.js +0 -298
- package/dist/tests/orchestration/research-worker.test.d.ts +0 -1
- package/dist/tests/orchestration/research-worker.test.js +0 -171
- package/dist/tests/orchestration/research-workflow.test.d.ts +0 -1
- package/dist/tests/orchestration/research-workflow.test.js +0 -119
- package/dist/tests/package-manifest.test.d.ts +0 -1
- package/dist/tests/package-manifest.test.js +0 -29
- package/dist/tests/release-foundation.test.d.ts +0 -1
- package/dist/tests/release-foundation.test.js +0 -16
- package/dist/tests/release-script.test.d.ts +0 -1
- package/dist/tests/release-script.test.js +0 -72
- package/dist/tests/search/duckduckgo.test.d.ts +0 -1
- package/dist/tests/search/duckduckgo.test.js +0 -103
- package/dist/tests/tools/web-explore.test.d.ts +0 -1
- package/dist/tests/tools/web-explore.test.js +0 -163
- package/dist/tests/tools/web-fetch-headless.test.d.ts +0 -1
- package/dist/tests/tools/web-fetch-headless.test.js +0 -31
- package/dist/tests/tools/web-fetch.test.d.ts +0 -1
- package/dist/tests/tools/web-fetch.test.js +0 -27
- package/dist/tests/tools/web-search.test.d.ts +0 -1
- package/dist/tests/tools/web-search.test.js +0 -125
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -13
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
// Runtime-tested script module; TypeScript doesn't need declarations for this import.
|
|
3
|
-
// @ts-expect-error importing local .mjs helper exports for tests
|
|
4
|
-
import { inferVersionBump, parseUnreleasedSections, rewritePackageLockVersion } from '../scripts/release.mjs';
|
|
5
|
-
describe('release script helpers', () => {
|
|
6
|
-
it('returns major when Breaking has real entries', () => {
|
|
7
|
-
const unreleased = {
|
|
8
|
-
Added: [],
|
|
9
|
-
Changed: [],
|
|
10
|
-
Fixed: [],
|
|
11
|
-
Breaking: ['Removed deprecated install path']
|
|
12
|
-
};
|
|
13
|
-
expect(inferVersionBump(unreleased)).toBe('major');
|
|
14
|
-
});
|
|
15
|
-
it('returns minor when Added has entries and Breaking does not', () => {
|
|
16
|
-
const unreleased = {
|
|
17
|
-
Added: ['Added release automation'],
|
|
18
|
-
Changed: [],
|
|
19
|
-
Fixed: [],
|
|
20
|
-
Breaking: []
|
|
21
|
-
};
|
|
22
|
-
expect(inferVersionBump(unreleased)).toBe('minor');
|
|
23
|
-
});
|
|
24
|
-
it('returns patch when only fixes or changes exist', () => {
|
|
25
|
-
const unreleased = {
|
|
26
|
-
Added: [],
|
|
27
|
-
Changed: ['Adjusted workflow docs'],
|
|
28
|
-
Fixed: ['Fixed package metadata regression'],
|
|
29
|
-
Breaking: []
|
|
30
|
-
};
|
|
31
|
-
expect(inferVersionBump(unreleased)).toBe('patch');
|
|
32
|
-
});
|
|
33
|
-
it('parses the top unreleased sections from changelog text', () => {
|
|
34
|
-
const changelog = `# Changelog\n\n## Unreleased\n\n### Added\n- Added release automation\n\n### Changed\n- Tightened docs\n\n### Fixed\n- Fixed package shape\n\n### Breaking\n- None.\n\n## [0.1.0] - 2026-04-20\n`;
|
|
35
|
-
expect(parseUnreleasedSections(changelog)).toEqual({
|
|
36
|
-
Added: ['Added release automation'],
|
|
37
|
-
Changed: ['Tightened docs'],
|
|
38
|
-
Fixed: ['Fixed package shape'],
|
|
39
|
-
Breaking: []
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
it('rewrites the root package-lock version fields', () => {
|
|
43
|
-
const packageLock = {
|
|
44
|
-
name: '@demigodmode/pi-web-agent',
|
|
45
|
-
version: '0.1.0',
|
|
46
|
-
lockfileVersion: 3,
|
|
47
|
-
packages: {
|
|
48
|
-
'': {
|
|
49
|
-
name: '@demigodmode/pi-web-agent',
|
|
50
|
-
version: '0.1.0'
|
|
51
|
-
},
|
|
52
|
-
'node_modules/example': {
|
|
53
|
-
version: '1.2.3'
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
expect(JSON.parse(rewritePackageLockVersion(JSON.stringify(packageLock), '0.2.0'))).toEqual({
|
|
58
|
-
name: '@demigodmode/pi-web-agent',
|
|
59
|
-
version: '0.2.0',
|
|
60
|
-
lockfileVersion: 3,
|
|
61
|
-
packages: {
|
|
62
|
-
'': {
|
|
63
|
-
name: '@demigodmode/pi-web-agent',
|
|
64
|
-
version: '0.2.0'
|
|
65
|
-
},
|
|
66
|
-
'node_modules/example': {
|
|
67
|
-
version: '1.2.3'
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { buildSearchUrl, parseDuckDuckGoResults } from '../../src/search/duckduckgo.js';
|
|
4
|
-
describe('DuckDuckGo search parsing', () => {
|
|
5
|
-
it('builds a deterministic search URL', () => {
|
|
6
|
-
expect(buildSearchUrl('pi web agent')).toBe('https://html.duckduckgo.com/html/?q=pi+web+agent');
|
|
7
|
-
});
|
|
8
|
-
it('parses normalized title, url, and snippet records', () => {
|
|
9
|
-
const html = readFileSync('tests/fixtures/duckduckgo/basic-results.html', 'utf8');
|
|
10
|
-
const parsed = parseDuckDuckGoResults(html);
|
|
11
|
-
expect(parsed).toEqual({
|
|
12
|
-
results: [
|
|
13
|
-
{
|
|
14
|
-
title: 'Example Article',
|
|
15
|
-
url: 'https://example.com/article',
|
|
16
|
-
snippet: 'First example snippet.'
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
title: 'Another Result',
|
|
20
|
-
url: 'https://example.org/post',
|
|
21
|
-
snippet: 'Second example snippet.'
|
|
22
|
-
}
|
|
23
|
-
],
|
|
24
|
-
noResults: false,
|
|
25
|
-
hasResultContainers: true
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
it('decodes DuckDuckGo redirect urls into destination urls', () => {
|
|
29
|
-
const html = `
|
|
30
|
-
<div class="result">
|
|
31
|
-
<a class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fpi.dev%2F&rut=abc">pi.dev</a>
|
|
32
|
-
<a class="result__snippet">Pi docs</a>
|
|
33
|
-
</div>
|
|
34
|
-
`;
|
|
35
|
-
const parsed = parseDuckDuckGoResults(html);
|
|
36
|
-
expect(parsed).toEqual({
|
|
37
|
-
results: [
|
|
38
|
-
{
|
|
39
|
-
title: 'pi.dev',
|
|
40
|
-
url: 'https://pi.dev/',
|
|
41
|
-
snippet: 'Pi docs'
|
|
42
|
-
}
|
|
43
|
-
],
|
|
44
|
-
noResults: false,
|
|
45
|
-
hasResultContainers: true
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
it('falls back to the original url when redirect decoding fails', () => {
|
|
49
|
-
const html = `
|
|
50
|
-
<div class="result">
|
|
51
|
-
<a class="result__a" href="//duckduckgo.com/l/?uddg=%E0%A4%A&rut=abc">broken</a>
|
|
52
|
-
<a class="result__snippet">Broken redirect</a>
|
|
53
|
-
</div>
|
|
54
|
-
`;
|
|
55
|
-
const parsed = parseDuckDuckGoResults(html);
|
|
56
|
-
expect(parsed).toEqual({
|
|
57
|
-
results: [
|
|
58
|
-
{
|
|
59
|
-
title: 'broken',
|
|
60
|
-
url: '//duckduckgo.com/l/?uddg=%E0%A4%A&rut=abc',
|
|
61
|
-
snippet: 'Broken redirect'
|
|
62
|
-
}
|
|
63
|
-
],
|
|
64
|
-
noResults: false,
|
|
65
|
-
hasResultContainers: true
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
it('detects a no-results page separately from a parse failure', () => {
|
|
69
|
-
const html = `
|
|
70
|
-
<html>
|
|
71
|
-
<body>
|
|
72
|
-
<div class="results">
|
|
73
|
-
<div class="no-results">No results found for your search.</div>
|
|
74
|
-
</div>
|
|
75
|
-
</body>
|
|
76
|
-
</html>
|
|
77
|
-
`;
|
|
78
|
-
const parsed = parseDuckDuckGoResults(html);
|
|
79
|
-
expect(parsed).toEqual({
|
|
80
|
-
results: [],
|
|
81
|
-
noResults: true,
|
|
82
|
-
hasResultContainers: false
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
it('reports when the page does not match the expected results format', () => {
|
|
86
|
-
const html = `
|
|
87
|
-
<html>
|
|
88
|
-
<body>
|
|
89
|
-
<main>
|
|
90
|
-
<h1>Unexpected page</h1>
|
|
91
|
-
<p>Nothing here looks like a DuckDuckGo results page.</p>
|
|
92
|
-
</main>
|
|
93
|
-
</body>
|
|
94
|
-
</html>
|
|
95
|
-
`;
|
|
96
|
-
const parsed = parseDuckDuckGoResults(html);
|
|
97
|
-
expect(parsed).toEqual({
|
|
98
|
-
results: [],
|
|
99
|
-
noResults: false,
|
|
100
|
-
hasResultContainers: false
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { createWebExploreTool } from '../../src/tools/web-explore.js';
|
|
3
|
-
describe('web_explore tool', () => {
|
|
4
|
-
it('returns clean findings, sources, and no caveat when evidence is sufficient', async () => {
|
|
5
|
-
const webExplore = createWebExploreTool({
|
|
6
|
-
explore: vi.fn().mockResolvedValue({
|
|
7
|
-
decision: {
|
|
8
|
-
action: 'answer',
|
|
9
|
-
rationale: 'Enough evidence.',
|
|
10
|
-
approvedEvidence: [
|
|
11
|
-
{
|
|
12
|
-
title: 'Browsers | Playwright',
|
|
13
|
-
url: 'https://playwright.dev/docs/browsers',
|
|
14
|
-
sourceKind: 'official-docs',
|
|
15
|
-
method: 'http',
|
|
16
|
-
summary: 'Use channel for branded browsers.',
|
|
17
|
-
supports: ['Use channel']
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
title: 'BrowserType | Playwright',
|
|
21
|
-
url: 'https://playwright.dev/docs/api/class-browsertype',
|
|
22
|
-
sourceKind: 'official-api',
|
|
23
|
-
method: 'http',
|
|
24
|
-
summary: 'executablePath is supported but use at your own risk.',
|
|
25
|
-
supports: ['executablePath is risky']
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
evidence: [
|
|
30
|
-
{
|
|
31
|
-
title: 'Browsers | Playwright',
|
|
32
|
-
url: 'https://playwright.dev/docs/browsers',
|
|
33
|
-
sourceKind: 'official-docs',
|
|
34
|
-
method: 'http',
|
|
35
|
-
summary: 'Use channel for branded browsers.',
|
|
36
|
-
supports: ['Use channel']
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
title: 'BrowserType | Playwright',
|
|
40
|
-
url: 'https://playwright.dev/docs/api/class-browsertype',
|
|
41
|
-
sourceKind: 'official-api',
|
|
42
|
-
method: 'http',
|
|
43
|
-
summary: 'executablePath is supported but use at your own risk.',
|
|
44
|
-
supports: ['executablePath is risky']
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
workerPass: {
|
|
48
|
-
searchQueries: ['playwright installed edge executablePath vs channel'],
|
|
49
|
-
evidence: [],
|
|
50
|
-
gaps: [],
|
|
51
|
-
lowValueOutcomes: [],
|
|
52
|
-
suggestedHeadlessUrl: undefined,
|
|
53
|
-
exhaustedBudget: false
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
});
|
|
57
|
-
const result = await webExplore({
|
|
58
|
-
query: 'Find current docs or discussions about Playwright launching an installed Chrome or Edge executable instead of a bundled browser, then summarize the recommended approach.'
|
|
59
|
-
});
|
|
60
|
-
expect(result.status).toBe('ok');
|
|
61
|
-
expect(result.findings).toEqual([
|
|
62
|
-
'Use channel for branded Chrome or Edge when possible.',
|
|
63
|
-
'Treat executablePath as a fallback because Playwright documents it as use-at-your-own-risk.'
|
|
64
|
-
]);
|
|
65
|
-
expect(result.sources).toEqual([
|
|
66
|
-
{
|
|
67
|
-
title: 'Browsers | Playwright',
|
|
68
|
-
url: 'https://playwright.dev/docs/browsers'
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
title: 'BrowserType | Playwright',
|
|
72
|
-
url: 'https://playwright.dev/docs/api/class-browsertype'
|
|
73
|
-
}
|
|
74
|
-
]);
|
|
75
|
-
expect(result.caveat).toBeUndefined();
|
|
76
|
-
});
|
|
77
|
-
it('returns a caveat when only partial evidence is available', async () => {
|
|
78
|
-
const webExplore = createWebExploreTool({
|
|
79
|
-
explore: vi.fn().mockResolvedValue({
|
|
80
|
-
decision: {
|
|
81
|
-
action: 'research-again',
|
|
82
|
-
rationale: 'Evidence is partial.',
|
|
83
|
-
followupQuery: 'same query'
|
|
84
|
-
},
|
|
85
|
-
evidence: [
|
|
86
|
-
{
|
|
87
|
-
title: 'Coverage | Guide | Vitest',
|
|
88
|
-
url: 'https://vitest.dev/guide/coverage.html',
|
|
89
|
-
sourceKind: 'official-docs',
|
|
90
|
-
method: 'headless',
|
|
91
|
-
summary: 'Set coverage.provider to v8 and install @vitest/coverage-v8.',
|
|
92
|
-
supports: ['provider: v8', 'install @vitest/coverage-v8']
|
|
93
|
-
}
|
|
94
|
-
],
|
|
95
|
-
workerPass: {
|
|
96
|
-
searchQueries: ['vitest coverage docs'],
|
|
97
|
-
evidence: [],
|
|
98
|
-
gaps: [{ kind: 'needs-more-evidence', message: 'Only one strong source was gathered.' }],
|
|
99
|
-
lowValueOutcomes: [],
|
|
100
|
-
suggestedHeadlessUrl: undefined,
|
|
101
|
-
exhaustedBudget: false
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
});
|
|
105
|
-
const result = await webExplore({
|
|
106
|
-
query: 'Find the current Vitest coverage docs and tell me how to enable coverage with the V8 provider in a TypeScript project.'
|
|
107
|
-
});
|
|
108
|
-
expect(result.status).toBe('ok');
|
|
109
|
-
expect(result.findings).toEqual([
|
|
110
|
-
'Vitest coverage docs say to set coverage.provider to v8 and install @vitest/coverage-v8.'
|
|
111
|
-
]);
|
|
112
|
-
expect(result.sources).toEqual([
|
|
113
|
-
{
|
|
114
|
-
title: 'Coverage | Guide | Vitest',
|
|
115
|
-
url: 'https://vitest.dev/guide/coverage.html'
|
|
116
|
-
}
|
|
117
|
-
]);
|
|
118
|
-
expect(result.caveat).toBe('Evidence is partial, so this answer is based on the strongest source found so far.');
|
|
119
|
-
});
|
|
120
|
-
it('rejects empty exploration queries', async () => {
|
|
121
|
-
const webExplore = createWebExploreTool({
|
|
122
|
-
explore: vi.fn()
|
|
123
|
-
});
|
|
124
|
-
await expect(webExplore({ query: ' ' })).resolves.toMatchObject({
|
|
125
|
-
status: 'error',
|
|
126
|
-
error: { code: 'INVALID_QUERY' }
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
it('does not leak raw internal worker bookkeeping into visible output', async () => {
|
|
130
|
-
const webExplore = createWebExploreTool({
|
|
131
|
-
explore: vi.fn().mockResolvedValue({
|
|
132
|
-
decision: {
|
|
133
|
-
action: 'research-again',
|
|
134
|
-
rationale: 'Not enough evidence.',
|
|
135
|
-
followupQuery: 'same query'
|
|
136
|
-
},
|
|
137
|
-
evidence: [
|
|
138
|
-
{
|
|
139
|
-
title: 'Source A',
|
|
140
|
-
url: 'https://example.com/a',
|
|
141
|
-
sourceKind: 'community',
|
|
142
|
-
method: 'http',
|
|
143
|
-
summary: 'A concise summary.',
|
|
144
|
-
supports: ['detail a']
|
|
145
|
-
}
|
|
146
|
-
],
|
|
147
|
-
workerPass: {
|
|
148
|
-
searchQueries: ['q1'],
|
|
149
|
-
evidence: [],
|
|
150
|
-
gaps: [{ kind: 'needs-more-evidence', message: 'Need more.' }],
|
|
151
|
-
lowValueOutcomes: [{ kind: 'empty-search', message: 'No more results.' }],
|
|
152
|
-
suggestedHeadlessUrl: 'https://example.com/b',
|
|
153
|
-
exhaustedBudget: false
|
|
154
|
-
}
|
|
155
|
-
})
|
|
156
|
-
});
|
|
157
|
-
const result = await webExplore({ query: 'example query' });
|
|
158
|
-
expect(JSON.stringify(result)).not.toContain('searchQueries');
|
|
159
|
-
expect(JSON.stringify(result)).not.toContain('lowValueOutcomes');
|
|
160
|
-
expect(JSON.stringify(result)).not.toContain('suggestedHeadlessUrl');
|
|
161
|
-
expect(result.findings).toEqual(['A concise summary.']);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { createWebFetchHeadlessTool } from '../../src/tools/web-fetch-headless.js';
|
|
3
|
-
describe('web_fetch_headless tool', () => {
|
|
4
|
-
it('passes through headless fetch results', async () => {
|
|
5
|
-
const tool = createWebFetchHeadlessTool({
|
|
6
|
-
fetchPage: async () => ({
|
|
7
|
-
status: 'ok',
|
|
8
|
-
url: 'https://example.com',
|
|
9
|
-
metadata: { method: 'headless', cacheHit: false, browser: 'chrome', navigationMs: 1200 },
|
|
10
|
-
content: { text: 'Rendered text' }
|
|
11
|
-
})
|
|
12
|
-
});
|
|
13
|
-
const result = await tool({ url: 'https://example.com' });
|
|
14
|
-
expect(result).toMatchObject({
|
|
15
|
-
status: 'ok',
|
|
16
|
-
metadata: { method: 'headless', cacheHit: false, browser: 'chrome', navigationMs: 1200 },
|
|
17
|
-
content: { text: 'Rendered text' }
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
it('can be constructed without dependency arguments', () => {
|
|
21
|
-
expect(typeof createWebFetchHeadlessTool()).toBe('function');
|
|
22
|
-
});
|
|
23
|
-
it('rejects unsupported URL schemes before headless execution', async () => {
|
|
24
|
-
const tool = createWebFetchHeadlessTool();
|
|
25
|
-
const result = await tool({ url: 'file:///tmp/test.html' });
|
|
26
|
-
expect(result).toMatchObject({
|
|
27
|
-
status: 'unsupported',
|
|
28
|
-
error: { code: 'UNSUPPORTED_URL' }
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { createWebFetchTool } from '../../src/tools/web-fetch.js';
|
|
3
|
-
describe('web_fetch tool', () => {
|
|
4
|
-
it('rejects unsupported URL schemes before fetching', async () => {
|
|
5
|
-
const webFetch = createWebFetchTool({ fetchPage: vi.fn() });
|
|
6
|
-
await expect(webFetch({ url: 'file:///tmp/test.html' })).resolves.toMatchObject({
|
|
7
|
-
status: 'unsupported',
|
|
8
|
-
error: { code: 'UNSUPPORTED_URL' }
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
it('passes through honest fetch results', async () => {
|
|
12
|
-
const webFetch = createWebFetchTool({
|
|
13
|
-
fetchPage: vi.fn().mockResolvedValue({
|
|
14
|
-
status: 'needs_headless',
|
|
15
|
-
url: 'https://example.com',
|
|
16
|
-
metadata: { method: 'http', cacheHit: false },
|
|
17
|
-
error: { code: 'WEAK_EXTRACTION', message: 'not enough content' }
|
|
18
|
-
})
|
|
19
|
-
});
|
|
20
|
-
await expect(webFetch({ url: 'https://example.com' })).resolves.toMatchObject({
|
|
21
|
-
status: 'needs_headless'
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
it('can be constructed without dependency arguments', () => {
|
|
25
|
-
expect(typeof createWebFetchTool()).toBe('function');
|
|
26
|
-
});
|
|
27
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { createWebSearchTool } from '../../src/tools/web-search.js';
|
|
3
|
-
describe('web_search tool', () => {
|
|
4
|
-
it('returns discovery-only results from the search backend', async () => {
|
|
5
|
-
const search = createWebSearchTool({
|
|
6
|
-
searchHtml: vi.fn().mockResolvedValue(`
|
|
7
|
-
<div class="result">
|
|
8
|
-
<a class="result__a" href="https://example.com">Example</a>
|
|
9
|
-
<a class="result__snippet">Snippet</a>
|
|
10
|
-
</div>
|
|
11
|
-
`)
|
|
12
|
-
});
|
|
13
|
-
await expect(search({ query: 'example' })).resolves.toEqual({
|
|
14
|
-
status: 'ok',
|
|
15
|
-
results: [{ title: 'Example', url: 'https://example.com', snippet: 'Snippet' }],
|
|
16
|
-
metadata: { backend: 'duckduckgo', cacheHit: false }
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
it('serves repeated identical queries from cache', async () => {
|
|
20
|
-
const searchHtml = vi.fn().mockResolvedValue(`
|
|
21
|
-
<div class="result">
|
|
22
|
-
<a class="result__a" href="https://example.com">Example</a>
|
|
23
|
-
<a class="result__snippet">Snippet</a>
|
|
24
|
-
</div>
|
|
25
|
-
`);
|
|
26
|
-
const search = createWebSearchTool({ searchHtml });
|
|
27
|
-
const first = await search({ query: 'example' });
|
|
28
|
-
const second = await search({ query: 'example' });
|
|
29
|
-
expect(searchHtml).toHaveBeenCalledTimes(1);
|
|
30
|
-
expect(first.metadata.cacheHit).toBe(false);
|
|
31
|
-
expect(second.metadata.cacheHit).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
it('rejects empty queries', async () => {
|
|
34
|
-
const search = createWebSearchTool({ searchHtml: vi.fn() });
|
|
35
|
-
await expect(search({ query: ' ' })).resolves.toMatchObject({
|
|
36
|
-
status: 'error',
|
|
37
|
-
error: { code: 'INVALID_QUERY' }
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
it('returns NO_RESULTS when the backend page is valid but contains no usable results', async () => {
|
|
41
|
-
const search = createWebSearchTool({
|
|
42
|
-
searchHtml: vi.fn().mockResolvedValue(`
|
|
43
|
-
<html>
|
|
44
|
-
<body>
|
|
45
|
-
<div class="results">
|
|
46
|
-
<div class="no-results">No results found for your search.</div>
|
|
47
|
-
</div>
|
|
48
|
-
</body>
|
|
49
|
-
</html>
|
|
50
|
-
`)
|
|
51
|
-
});
|
|
52
|
-
await expect(search({ query: 'missing thing' })).resolves.toMatchObject({
|
|
53
|
-
status: 'error',
|
|
54
|
-
error: {
|
|
55
|
-
code: 'NO_RESULTS',
|
|
56
|
-
message: 'DuckDuckGo returned no usable results for this query.'
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
it('returns PARSE_FAILED when the backend page cannot be understood as search results', async () => {
|
|
61
|
-
const search = createWebSearchTool({
|
|
62
|
-
searchHtml: vi.fn().mockResolvedValue(`
|
|
63
|
-
<html>
|
|
64
|
-
<body>
|
|
65
|
-
<main>
|
|
66
|
-
<h1>Unexpected page</h1>
|
|
67
|
-
</main>
|
|
68
|
-
</body>
|
|
69
|
-
</html>
|
|
70
|
-
`)
|
|
71
|
-
});
|
|
72
|
-
await expect(search({ query: 'odd page' })).resolves.toMatchObject({
|
|
73
|
-
status: 'error',
|
|
74
|
-
error: {
|
|
75
|
-
code: 'PARSE_FAILED',
|
|
76
|
-
message: 'DuckDuckGo returned a page, but it did not match the expected results format.'
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
it('returns BLOCKED when the backend clearly looks blocked', async () => {
|
|
81
|
-
const search = createWebSearchTool({
|
|
82
|
-
searchHtml: vi.fn().mockRejectedValue(new Error('DuckDuckGo blocked the request with 403'))
|
|
83
|
-
});
|
|
84
|
-
await expect(search({ query: 'blocked query' })).resolves.toMatchObject({
|
|
85
|
-
status: 'error',
|
|
86
|
-
error: {
|
|
87
|
-
code: 'BLOCKED',
|
|
88
|
-
message: 'DuckDuckGo search appears to be blocked or rate limited.'
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
it('returns BLOCKED when a 200 response is really a challenge page', async () => {
|
|
93
|
-
const search = createWebSearchTool({
|
|
94
|
-
searchHtml: vi.fn().mockResolvedValue(`
|
|
95
|
-
<html>
|
|
96
|
-
<body>
|
|
97
|
-
<main>
|
|
98
|
-
<h1>Are you a robot?</h1>
|
|
99
|
-
<p>Please verify you are human to continue.</p>
|
|
100
|
-
</main>
|
|
101
|
-
</body>
|
|
102
|
-
</html>
|
|
103
|
-
`)
|
|
104
|
-
});
|
|
105
|
-
await expect(search({ query: 'challenge page' })).resolves.toMatchObject({
|
|
106
|
-
status: 'error',
|
|
107
|
-
error: {
|
|
108
|
-
code: 'BLOCKED',
|
|
109
|
-
message: 'DuckDuckGo search appears to be blocked or rate limited.'
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
it('returns FETCH_FAILED for generic backend failures', async () => {
|
|
114
|
-
const search = createWebSearchTool({
|
|
115
|
-
searchHtml: vi.fn().mockRejectedValue(new Error('socket hang up'))
|
|
116
|
-
});
|
|
117
|
-
await expect(search({ query: 'network issue' })).resolves.toMatchObject({
|
|
118
|
-
status: 'error',
|
|
119
|
-
error: {
|
|
120
|
-
code: 'FETCH_FAILED',
|
|
121
|
-
message: 'DuckDuckGo search request failed: socket hang up'
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
});
|
package/dist/vitest.config.d.ts
DELETED
package/dist/vitest.config.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vitest/config';
|
|
2
|
-
export default defineConfig({
|
|
3
|
-
test: {
|
|
4
|
-
environment: 'node',
|
|
5
|
-
include: ['tests/**/*.test.ts'],
|
|
6
|
-
coverage: {
|
|
7
|
-
provider: 'v8',
|
|
8
|
-
reporter: ['text', 'html'],
|
|
9
|
-
reportsDirectory: 'coverage',
|
|
10
|
-
exclude: ['scripts/**', 'dist/**', '.pi/**', 'docs/.vitepress/**', 'vitest.config.ts']
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
});
|