@demigodmode/pi-web-agent 0.2.2 → 0.4.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.
Files changed (117) hide show
  1. package/LICENSE +661 -661
  2. package/README.md +61 -5
  3. package/dist/commands/web-agent-config.d.ts +23 -0
  4. package/dist/commands/web-agent-config.js +249 -0
  5. package/dist/extension.js +30 -66
  6. package/dist/orchestration/answer-synthesizer.d.ts +8 -0
  7. package/dist/orchestration/answer-synthesizer.js +17 -0
  8. package/dist/orchestration/candidate-selector.d.ts +6 -0
  9. package/dist/orchestration/candidate-selector.js +24 -0
  10. package/dist/orchestration/evidence-ranker.d.ts +4 -0
  11. package/dist/orchestration/evidence-ranker.js +36 -0
  12. package/dist/orchestration/index.d.ts +6 -21
  13. package/dist/orchestration/query-planner.d.ts +7 -0
  14. package/dist/orchestration/query-planner.js +37 -0
  15. package/dist/orchestration/research-orchestrator.d.ts +7 -22
  16. package/dist/orchestration/research-orchestrator.js +185 -73
  17. package/dist/orchestration/research-types.d.ts +6 -0
  18. package/dist/orchestration/research-worker.js +8 -1
  19. package/dist/orchestration/stop-decider.d.ts +19 -0
  20. package/dist/orchestration/stop-decider.js +14 -0
  21. package/dist/presentation/config-store.d.ts +23 -0
  22. package/dist/presentation/config-store.js +64 -0
  23. package/dist/presentation/config.d.ts +7 -0
  24. package/dist/presentation/config.js +44 -0
  25. package/dist/presentation/explore-presentation.d.ts +3 -0
  26. package/dist/presentation/explore-presentation.js +56 -0
  27. package/dist/presentation/fetch-presentation.d.ts +5 -0
  28. package/dist/presentation/fetch-presentation.js +40 -0
  29. package/dist/presentation/search-presentation.d.ts +3 -0
  30. package/dist/presentation/search-presentation.js +30 -0
  31. package/dist/presentation/select-view.d.ts +2 -0
  32. package/dist/presentation/select-view.js +12 -0
  33. package/dist/presentation/types.d.ts +50 -0
  34. package/dist/presentation/types.js +1 -0
  35. package/dist/search/duckduckgo.d.ts +6 -1
  36. package/dist/search/duckduckgo.js +11 -1
  37. package/dist/tools/web-explore.d.ts +16 -16
  38. package/dist/tools/web-explore.js +21 -29
  39. package/dist/tools/web-fetch-headless.js +11 -2
  40. package/dist/tools/web-fetch.js +11 -2
  41. package/dist/tools/web-search.js +99 -12
  42. package/dist/types.d.ts +22 -0
  43. package/package.json +75 -75
  44. package/dist/scripts/live-web-eval.d.ts +0 -1
  45. package/dist/scripts/live-web-eval.js +0 -411
  46. package/dist/src/cache/ttl-cache.d.ts +0 -8
  47. package/dist/src/cache/ttl-cache.js +0 -21
  48. package/dist/src/extension.d.ts +0 -2
  49. package/dist/src/extension.js +0 -155
  50. package/dist/src/extract/readability.d.ts +0 -8
  51. package/dist/src/extract/readability.js +0 -93
  52. package/dist/src/fetch/browser-resolution.d.ts +0 -15
  53. package/dist/src/fetch/browser-resolution.js +0 -55
  54. package/dist/src/fetch/headless-fetch.d.ts +0 -18
  55. package/dist/src/fetch/headless-fetch.js +0 -87
  56. package/dist/src/fetch/http-fetch.d.ts +0 -4
  57. package/dist/src/fetch/http-fetch.js +0 -50
  58. package/dist/src/orchestration/index.d.ts +0 -41
  59. package/dist/src/orchestration/index.js +0 -9
  60. package/dist/src/orchestration/research-orchestrator.d.ts +0 -43
  61. package/dist/src/orchestration/research-orchestrator.js +0 -87
  62. package/dist/src/orchestration/research-types.d.ts +0 -41
  63. package/dist/src/orchestration/research-types.js +0 -1
  64. package/dist/src/orchestration/research-worker.d.ts +0 -16
  65. package/dist/src/orchestration/research-worker.js +0 -131
  66. package/dist/src/search/duckduckgo.d.ts +0 -9
  67. package/dist/src/search/duckduckgo.js +0 -52
  68. package/dist/src/tools/web-explore.d.ts +0 -44
  69. package/dist/src/tools/web-explore.js +0 -50
  70. package/dist/src/tools/web-fetch-headless.d.ts +0 -6
  71. package/dist/src/tools/web-fetch-headless.js +0 -14
  72. package/dist/src/tools/web-fetch.d.ts +0 -6
  73. package/dist/src/tools/web-fetch.js +0 -14
  74. package/dist/src/tools/web-search.d.ts +0 -10
  75. package/dist/src/tools/web-search.js +0 -103
  76. package/dist/src/types.d.ts +0 -48
  77. package/dist/src/types.js +0 -7
  78. package/dist/tests/cache/ttl-cache.test.d.ts +0 -1
  79. package/dist/tests/cache/ttl-cache.test.js +0 -19
  80. package/dist/tests/contracts.test.d.ts +0 -1
  81. package/dist/tests/contracts.test.js +0 -65
  82. package/dist/tests/extension.test.d.ts +0 -1
  83. package/dist/tests/extension.test.js +0 -123
  84. package/dist/tests/extract/readability.test.d.ts +0 -1
  85. package/dist/tests/extract/readability.test.js +0 -79
  86. package/dist/tests/fetch/browser-resolution.test.d.ts +0 -1
  87. package/dist/tests/fetch/browser-resolution.test.js +0 -37
  88. package/dist/tests/fetch/headless-fetch.smoke.test.d.ts +0 -1
  89. package/dist/tests/fetch/headless-fetch.smoke.test.js +0 -17
  90. package/dist/tests/fetch/headless-fetch.test.d.ts +0 -1
  91. package/dist/tests/fetch/headless-fetch.test.js +0 -150
  92. package/dist/tests/fetch/http-fetch.test.d.ts +0 -1
  93. package/dist/tests/fetch/http-fetch.test.js +0 -129
  94. package/dist/tests/orchestration/research-orchestrator.test.d.ts +0 -1
  95. package/dist/tests/orchestration/research-orchestrator.test.js +0 -298
  96. package/dist/tests/orchestration/research-worker.test.d.ts +0 -1
  97. package/dist/tests/orchestration/research-worker.test.js +0 -171
  98. package/dist/tests/orchestration/research-workflow.test.d.ts +0 -1
  99. package/dist/tests/orchestration/research-workflow.test.js +0 -119
  100. package/dist/tests/package-manifest.test.d.ts +0 -1
  101. package/dist/tests/package-manifest.test.js +0 -29
  102. package/dist/tests/release-foundation.test.d.ts +0 -1
  103. package/dist/tests/release-foundation.test.js +0 -16
  104. package/dist/tests/release-script.test.d.ts +0 -1
  105. package/dist/tests/release-script.test.js +0 -72
  106. package/dist/tests/search/duckduckgo.test.d.ts +0 -1
  107. package/dist/tests/search/duckduckgo.test.js +0 -103
  108. package/dist/tests/tools/web-explore.test.d.ts +0 -1
  109. package/dist/tests/tools/web-explore.test.js +0 -163
  110. package/dist/tests/tools/web-fetch-headless.test.d.ts +0 -1
  111. package/dist/tests/tools/web-fetch-headless.test.js +0 -31
  112. package/dist/tests/tools/web-fetch.test.d.ts +0 -1
  113. package/dist/tests/tools/web-fetch.test.js +0 -27
  114. package/dist/tests/tools/web-search.test.d.ts +0 -1
  115. package/dist/tests/tools/web-search.test.js +0 -125
  116. package/dist/vitest.config.d.ts +0 -2
  117. package/dist/vitest.config.js +0 -13
@@ -1,131 +0,0 @@
1
- function classifySource(url) {
2
- if (url.includes('/docs/api/') || url.includes('/config/'))
3
- return 'official-api';
4
- if (url.includes('playwright.dev/docs') || url.includes('vitest.dev/guide/'))
5
- return 'official-docs';
6
- if (url.includes('learn.microsoft.com'))
7
- return 'official-docs';
8
- if (url.includes('github.com/') && url.includes('/issues/'))
9
- return 'issue-thread';
10
- if (url.includes('npmjs.com/package/'))
11
- return 'package-page';
12
- return 'community';
13
- }
14
- function summarizeText(text, maxLength = 180) {
15
- return text.replace(/\s+/g, ' ').trim().slice(0, maxLength);
16
- }
17
- function evidenceFromFetch(fetched, fallbackTitle) {
18
- const content = fetched.content;
19
- if (fetched.status !== 'ok' || !content)
20
- return null;
21
- const sourceKind = classifySource(fetched.url);
22
- if (sourceKind === 'package-page') {
23
- return null;
24
- }
25
- return {
26
- title: content.title ?? fallbackTitle,
27
- url: fetched.url,
28
- sourceKind,
29
- method: fetched.metadata.method,
30
- summary: summarizeText(content.text),
31
- supports: [summarizeText(content.text, 120)]
32
- };
33
- }
34
- function lowValueOutcomeFromFetch(fetched) {
35
- if (fetched.status !== 'ok' || !fetched.content)
36
- return null;
37
- if (classifySource(fetched.url) !== 'package-page')
38
- return null;
39
- return {
40
- kind: 'low-value-page',
41
- url: fetched.url,
42
- message: 'Fetched page did not add strong research evidence.'
43
- };
44
- }
45
- export function createResearchWorker({ search, fetchPage }) {
46
- return {
47
- async run({ query, maxSearchRounds, maxFetches }) {
48
- const searchQueries = [query];
49
- const evidence = [];
50
- const gaps = [];
51
- const lowValueOutcomes = [];
52
- let suggestedHeadlessUrl;
53
- if (maxSearchRounds <= 0 || maxFetches <= 0) {
54
- return {
55
- searchQueries: [],
56
- evidence,
57
- gaps: [{ kind: 'needs-more-evidence', message: 'Research worker budget was zero.' }],
58
- lowValueOutcomes,
59
- suggestedHeadlessUrl,
60
- exhaustedBudget: true
61
- };
62
- }
63
- const searchResult = await search({ query });
64
- if (searchResult.status !== 'ok') {
65
- return {
66
- searchQueries,
67
- evidence,
68
- gaps: [
69
- {
70
- kind: 'fetch-failed',
71
- message: searchResult.error?.message ?? 'Search failed during research worker pass.'
72
- }
73
- ],
74
- lowValueOutcomes,
75
- suggestedHeadlessUrl,
76
- exhaustedBudget: false
77
- };
78
- }
79
- if (searchResult.results.length === 0) {
80
- return {
81
- searchQueries,
82
- evidence,
83
- gaps,
84
- lowValueOutcomes: [
85
- {
86
- kind: 'empty-search',
87
- message: 'Search returned no results for this pass.'
88
- }
89
- ],
90
- suggestedHeadlessUrl,
91
- exhaustedBudget: false
92
- };
93
- }
94
- const candidates = searchResult.results.slice(0, maxFetches);
95
- for (const candidate of candidates) {
96
- const fetched = await fetchPage({ url: candidate.url });
97
- if (fetched.status === 'ok') {
98
- const parsedEvidence = evidenceFromFetch(fetched, candidate.title);
99
- if (parsedEvidence) {
100
- evidence.push(parsedEvidence);
101
- continue;
102
- }
103
- const lowValueOutcome = lowValueOutcomeFromFetch(fetched);
104
- if (lowValueOutcome) {
105
- lowValueOutcomes.push(lowValueOutcome);
106
- }
107
- continue;
108
- }
109
- if (fetched.status === 'needs_headless') {
110
- if (!suggestedHeadlessUrl) {
111
- suggestedHeadlessUrl = fetched.url;
112
- }
113
- gaps.push({ kind: 'fetch-failed', message: `HTTP fetch was weak for ${fetched.url}` });
114
- continue;
115
- }
116
- gaps.push({
117
- kind: 'fetch-failed',
118
- message: fetched.error?.message ?? `Fetch failed for ${candidate.url}`
119
- });
120
- }
121
- return {
122
- searchQueries,
123
- evidence,
124
- gaps,
125
- lowValueOutcomes,
126
- suggestedHeadlessUrl,
127
- exhaustedBudget: false
128
- };
129
- }
130
- };
131
- }
@@ -1,9 +0,0 @@
1
- import type { SearchResult } from '../types.js';
2
- export type ParsedDuckDuckGoResults = {
3
- results: SearchResult[];
4
- noResults: boolean;
5
- hasResultContainers: boolean;
6
- };
7
- export declare function buildSearchUrl(query: string): string;
8
- export declare function fetchDuckDuckGoHtml(query: string): Promise<string>;
9
- export declare function parseDuckDuckGoResults(html: string): ParsedDuckDuckGoResults;
@@ -1,52 +0,0 @@
1
- import * as cheerio from 'cheerio';
2
- function normalizeDuckDuckGoUrl(rawUrl) {
3
- try {
4
- const absolute = rawUrl.startsWith('//') ? `https:${rawUrl}` : rawUrl;
5
- const parsed = new URL(absolute);
6
- const isDuckDuckGoRedirect = parsed.hostname === 'duckduckgo.com' && parsed.pathname === '/l/';
7
- if (!isDuckDuckGoRedirect) {
8
- return rawUrl;
9
- }
10
- const target = parsed.searchParams.get('uddg');
11
- if (!target) {
12
- return rawUrl;
13
- }
14
- return decodeURIComponent(target);
15
- }
16
- catch {
17
- return rawUrl;
18
- }
19
- }
20
- export function buildSearchUrl(query) {
21
- const params = new URLSearchParams({ q: query });
22
- return `https://html.duckduckgo.com/html/?${params.toString()}`;
23
- }
24
- export async function fetchDuckDuckGoHtml(query) {
25
- const response = await fetch(buildSearchUrl(query));
26
- if (!response.ok) {
27
- throw new Error(`DuckDuckGo request failed with ${response.status}`);
28
- }
29
- return response.text();
30
- }
31
- export function parseDuckDuckGoResults(html) {
32
- const $ = cheerio.load(html);
33
- const resultContainers = $('.result');
34
- const results = resultContainers
35
- .map((_, element) => {
36
- const title = $(element).find('.result__a').first().text().trim();
37
- const url = normalizeDuckDuckGoUrl($(element).find('.result__a').first().attr('href')?.trim() ?? '');
38
- const snippet = $(element).find('.result__snippet').first().text().trim();
39
- return title && url ? { title, url, snippet } : null;
40
- })
41
- .get()
42
- .filter((value) => value !== null);
43
- const text = $.text().toLowerCase();
44
- const noResults = text.includes('no results found') ||
45
- text.includes('no more results') ||
46
- text.includes('did not match any documents');
47
- return {
48
- results,
49
- noResults,
50
- hasResultContainers: resultContainers.length > 0
51
- };
52
- }
@@ -1,44 +0,0 @@
1
- import type { ResearchEvidence } from '../orchestration/research-types.js';
2
- export declare function createWebExploreTool({ explore }?: {
3
- explore?: {
4
- run: (input: {
5
- query: string;
6
- }) => Promise<{
7
- decision: {
8
- action: 'answer' | 'research-again' | 'escalate-headless';
9
- };
10
- evidence: ResearchEvidence[];
11
- workerPass: unknown;
12
- }>;
13
- } | ((input: {
14
- query: string;
15
- }) => Promise<{
16
- decision: {
17
- action: 'answer' | 'research-again' | 'escalate-headless';
18
- };
19
- evidence: ResearchEvidence[];
20
- workerPass: unknown;
21
- }>);
22
- }): ({ query }: {
23
- query: string;
24
- }) => Promise<{
25
- status: "error";
26
- findings: never[];
27
- sources: never[];
28
- error: {
29
- code: string;
30
- message: string;
31
- };
32
- caveat?: undefined;
33
- text?: undefined;
34
- } | {
35
- status: "ok";
36
- findings: string[];
37
- sources: {
38
- title: string;
39
- url: string;
40
- }[];
41
- caveat: string | undefined;
42
- text: string;
43
- error?: undefined;
44
- }>;
@@ -1,50 +0,0 @@
1
- import { createResearchWorkflow } from '../orchestration/index.js';
2
- function findingFromEvidence(evidence, index) {
3
- if (evidence.summary.includes('Use channel')) {
4
- return 'Use channel for branded Chrome or Edge when possible.';
5
- }
6
- if (evidence.summary.includes('use at your own risk') || evidence.summary.includes('risky')) {
7
- return 'Treat executablePath as a fallback because Playwright documents it as use-at-your-own-risk.';
8
- }
9
- if (evidence.summary.includes('coverage.provider to v8') ||
10
- evidence.summary.includes('@vitest/coverage-v8')) {
11
- return 'Vitest coverage docs say to set coverage.provider to v8 and install @vitest/coverage-v8.';
12
- }
13
- return evidence.summary || `Finding ${index + 1}`;
14
- }
15
- function formatExploreText({ findings, sources, caveat }) {
16
- const findingLines = findings.map((finding) => `- ${finding}`).join('\n');
17
- const sourceLines = sources.map((source) => `- ${source.title}: ${source.url}`).join('\n');
18
- const caveatBlock = caveat ? `\n\nCaveat\n${caveat}` : '';
19
- return `Findings\n${findingLines}\n\nSources\n${sourceLines}${caveatBlock}`;
20
- }
21
- export function createWebExploreTool({ explore = createResearchWorkflow() } = {}) {
22
- const runExplore = typeof explore === 'function' ? explore : explore.run.bind(explore);
23
- return async function webExplore({ query }) {
24
- const normalizedQuery = query.trim();
25
- if (!normalizedQuery) {
26
- return {
27
- status: 'error',
28
- findings: [],
29
- sources: [],
30
- error: { code: 'INVALID_QUERY', message: 'Query must not be empty.' }
31
- };
32
- }
33
- const result = await runExplore({ query: normalizedQuery });
34
- const findings = result.evidence.slice(0, 5).map(findingFromEvidence);
35
- const sources = result.evidence.slice(0, 4).map((item) => ({
36
- title: item.title,
37
- url: item.url
38
- }));
39
- const caveat = result.decision.action === 'answer'
40
- ? undefined
41
- : 'Evidence is partial, so this answer is based on the strongest source found so far.';
42
- return {
43
- status: 'ok',
44
- findings,
45
- sources,
46
- caveat,
47
- text: formatExploreText({ findings, sources, caveat })
48
- };
49
- };
50
- }
@@ -1,6 +0,0 @@
1
- import type { WebFetchHeadlessResponse } from '../types.js';
2
- export declare function createWebFetchHeadlessTool({ fetchPage }?: {
3
- fetchPage?: (url: string) => Promise<WebFetchHeadlessResponse>;
4
- }): ({ url }: {
5
- url: string;
6
- }) => Promise<WebFetchHeadlessResponse>;
@@ -1,14 +0,0 @@
1
- import { headlessFetch } from '../fetch/headless-fetch.js';
2
- export function createWebFetchHeadlessTool({ fetchPage = headlessFetch } = {}) {
3
- return async function webFetchHeadless({ url }) {
4
- if (!/^https?:\/\//.test(url)) {
5
- return {
6
- status: 'unsupported',
7
- url,
8
- metadata: { method: 'headless', cacheHit: false },
9
- error: { code: 'UNSUPPORTED_URL', message: 'Only http and https URLs are supported.' }
10
- };
11
- }
12
- return fetchPage(url);
13
- };
14
- }
@@ -1,6 +0,0 @@
1
- import type { WebFetchResponse } from '../types.js';
2
- export declare function createWebFetchTool({ fetchPage }?: {
3
- fetchPage?: (url: string) => Promise<WebFetchResponse>;
4
- }): ({ url }: {
5
- url: string;
6
- }) => Promise<WebFetchResponse>;
@@ -1,14 +0,0 @@
1
- import { createHttpFetcher } from '../fetch/http-fetch.js';
2
- export function createWebFetchTool({ fetchPage = createHttpFetcher() } = {}) {
3
- return async function webFetch({ url }) {
4
- if (!/^https?:\/\//.test(url)) {
5
- return {
6
- status: 'unsupported',
7
- url,
8
- metadata: { method: 'http', cacheHit: false },
9
- error: { code: 'UNSUPPORTED_URL', message: 'Only http and https URLs are supported.' }
10
- };
11
- }
12
- return fetchPage(url);
13
- };
14
- }
@@ -1,10 +0,0 @@
1
- import type { WebSearchResponse } from '../types.js';
2
- export declare function createWebSearchTool({ searchHtml, cache }?: {
3
- searchHtml?: (query: string) => Promise<string>;
4
- cache?: {
5
- get(key: string): WebSearchResponse | undefined;
6
- set(key: string, value: WebSearchResponse): void;
7
- };
8
- }): ({ query }: {
9
- query: string;
10
- }) => Promise<WebSearchResponse>;
@@ -1,103 +0,0 @@
1
- import { createCacheKey, createTtlCache } from '../cache/ttl-cache.js';
2
- import { fetchDuckDuckGoHtml, parseDuckDuckGoResults } from '../search/duckduckgo.js';
3
- function classifySearchFailure(error) {
4
- const rawMessage = error instanceof Error ? error.message : 'Unknown search failure.';
5
- const normalized = rawMessage.toLowerCase();
6
- if (normalized.includes('blocked') ||
7
- normalized.includes('rate limit') ||
8
- normalized.includes('rate-limit') ||
9
- normalized.includes('403') ||
10
- normalized.includes('429') ||
11
- normalized.includes('captcha') ||
12
- normalized.includes('challenge')) {
13
- return {
14
- code: 'BLOCKED',
15
- message: 'DuckDuckGo search appears to be blocked or rate limited.'
16
- };
17
- }
18
- return {
19
- code: 'FETCH_FAILED',
20
- message: `DuckDuckGo search request failed: ${rawMessage}`
21
- };
22
- }
23
- function htmlLooksBlocked(html) {
24
- const normalized = html.toLowerCase();
25
- return (normalized.includes('captcha') ||
26
- normalized.includes('challenge') ||
27
- normalized.includes('verify you are human') ||
28
- normalized.includes('are you a robot') ||
29
- normalized.includes('unusual traffic'));
30
- }
31
- export function createWebSearchTool({ searchHtml = fetchDuckDuckGoHtml, cache = createTtlCache({ ttlMs: 30_000 }) } = {}) {
32
- return async function webSearch({ query }) {
33
- const normalizedQuery = query.trim();
34
- if (!normalizedQuery) {
35
- return {
36
- status: 'error',
37
- results: [],
38
- metadata: { backend: 'duckduckgo', cacheHit: false },
39
- error: { code: 'INVALID_QUERY', message: 'Query must not be empty.' }
40
- };
41
- }
42
- const cacheKey = createCacheKey(['web_search', normalizedQuery]);
43
- const cached = cache.get(cacheKey);
44
- if (cached) {
45
- return {
46
- ...cached,
47
- metadata: { ...cached.metadata, cacheHit: true }
48
- };
49
- }
50
- try {
51
- const html = await searchHtml(normalizedQuery);
52
- const parsed = parseDuckDuckGoResults(html);
53
- if (parsed.results.length > 0) {
54
- const result = {
55
- status: 'ok',
56
- results: parsed.results,
57
- metadata: { backend: 'duckduckgo', cacheHit: false }
58
- };
59
- cache.set(cacheKey, result);
60
- return result;
61
- }
62
- if (parsed.noResults) {
63
- return {
64
- status: 'error',
65
- results: [],
66
- metadata: { backend: 'duckduckgo', cacheHit: false },
67
- error: {
68
- code: 'NO_RESULTS',
69
- message: 'DuckDuckGo returned no usable results for this query.'
70
- }
71
- };
72
- }
73
- if (htmlLooksBlocked(html)) {
74
- return {
75
- status: 'error',
76
- results: [],
77
- metadata: { backend: 'duckduckgo', cacheHit: false },
78
- error: {
79
- code: 'BLOCKED',
80
- message: 'DuckDuckGo search appears to be blocked or rate limited.'
81
- }
82
- };
83
- }
84
- return {
85
- status: 'error',
86
- results: [],
87
- metadata: { backend: 'duckduckgo', cacheHit: false },
88
- error: {
89
- code: 'PARSE_FAILED',
90
- message: 'DuckDuckGo returned a page, but it did not match the expected results format.'
91
- }
92
- };
93
- }
94
- catch (error) {
95
- return {
96
- status: 'error',
97
- results: [],
98
- metadata: { backend: 'duckduckgo', cacheHit: false },
99
- error: classifySearchFailure(error)
100
- };
101
- }
102
- };
103
- }
@@ -1,48 +0,0 @@
1
- export declare const TOOL_STATUSES: readonly ["ok", "needs_headless", "blocked", "unsupported", "error"];
2
- export type ToolStatus = (typeof TOOL_STATUSES)[number];
3
- export type SearchResult = {
4
- title: string;
5
- url: string;
6
- snippet: string;
7
- };
8
- export type ToolError = {
9
- code: string;
10
- message: string;
11
- };
12
- export type SearchMetadata = {
13
- backend: 'duckduckgo';
14
- cacheHit: boolean;
15
- };
16
- export type FetchMetadata = {
17
- method: 'http' | 'headless';
18
- cacheHit: boolean;
19
- contentType?: string;
20
- truncated?: boolean;
21
- browser?: 'configured' | 'chrome' | 'edge';
22
- navigationMs?: number;
23
- };
24
- export type ExtractedContent = {
25
- title?: string;
26
- byline?: string;
27
- text: string;
28
- };
29
- export type WebSearchResponse = {
30
- status: 'ok' | 'error';
31
- results: SearchResult[];
32
- metadata: SearchMetadata;
33
- error?: ToolError;
34
- };
35
- export type WebFetchResponse = {
36
- status: ToolStatus;
37
- url: string;
38
- content?: ExtractedContent;
39
- metadata: FetchMetadata;
40
- error?: ToolError;
41
- };
42
- export type WebFetchHeadlessResponse = {
43
- status: Exclude<ToolStatus, 'needs_headless'>;
44
- url: string;
45
- content?: ExtractedContent;
46
- metadata: FetchMetadata;
47
- error?: ToolError;
48
- };
package/dist/src/types.js DELETED
@@ -1,7 +0,0 @@
1
- export const TOOL_STATUSES = [
2
- 'ok',
3
- 'needs_headless',
4
- 'blocked',
5
- 'unsupported',
6
- 'error'
7
- ];
@@ -1 +0,0 @@
1
- export {};
@@ -1,19 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { createTtlCache } from '../../src/cache/ttl-cache.js';
3
- describe('TTL cache', () => {
4
- it('returns cached values before expiry', () => {
5
- let now = 1_000;
6
- const cache = createTtlCache({ ttlMs: 100, now: () => now });
7
- cache.set('key', 'value');
8
- expect(cache.get('key')).toBe('value');
9
- now = 1_050;
10
- expect(cache.get('key')).toBe('value');
11
- });
12
- it('drops cached values after expiry', () => {
13
- let now = 1_000;
14
- const cache = createTtlCache({ ttlMs: 100, now: () => now });
15
- cache.set('key', 'value');
16
- now = 1_101;
17
- expect(cache.get('key')).toBeUndefined();
18
- });
19
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,65 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { TOOL_STATUSES } from '../src/types.js';
3
- import extension from '../src/extension.js';
4
- describe('shared tool contracts', () => {
5
- it('exposes the allowed tool statuses', () => {
6
- expect(TOOL_STATUSES).toEqual([
7
- 'ok',
8
- 'needs_headless',
9
- 'blocked',
10
- 'unsupported',
11
- 'error'
12
- ]);
13
- });
14
- it('allows normalized search results', () => {
15
- const result = {
16
- title: 'Example',
17
- url: 'https://example.com',
18
- snippet: 'Example snippet'
19
- };
20
- expect(result.url).toContain('https://');
21
- });
22
- it('shapes search and fetch responses around status + metadata', () => {
23
- const search = {
24
- status: 'ok',
25
- results: [],
26
- metadata: { backend: 'duckduckgo', cacheHit: false }
27
- };
28
- const fetch = {
29
- status: 'needs_headless',
30
- url: 'https://example.com',
31
- metadata: { method: 'http', cacheHit: false }
32
- };
33
- const headless = {
34
- status: 'error',
35
- url: 'https://example.com',
36
- metadata: { method: 'headless', cacheHit: false },
37
- error: { code: 'NOT_IMPLEMENTED', message: 'stub' }
38
- };
39
- expect(search.status).toBe('ok');
40
- expect(fetch.status).toBe('needs_headless');
41
- expect(headless.metadata.method).toBe('headless');
42
- });
43
- });
44
- describe('extension surface', () => {
45
- it('exports a Pi extension function', () => {
46
- expect(typeof extension).toBe('function');
47
- });
48
- });
49
- describe('headless metadata', () => {
50
- it('allows headless metadata to carry browser and navigation timing', () => {
51
- const headless = {
52
- status: 'ok',
53
- url: 'https://example.com',
54
- metadata: {
55
- method: 'headless',
56
- cacheHit: false,
57
- browser: 'chrome',
58
- navigationMs: 1500
59
- },
60
- content: { text: 'Rendered text' }
61
- };
62
- expect(headless.metadata.browser).toBe('chrome');
63
- expect(headless.metadata.navigationMs).toBe(1500);
64
- });
65
- });
@@ -1 +0,0 @@
1
- export {};