@demigodmode/pi-web-agent 1.5.1 → 1.6.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/CHANGELOG.md +31 -0
- package/README.md +6 -4
- package/dist/backends/config.d.ts +1 -1
- package/dist/backends/config.js +4 -3
- package/dist/backends/doctor.js +39 -0
- package/dist/backends/factory.d.ts +2 -0
- package/dist/backends/factory.js +8 -1
- package/dist/commands/web-agent-config.d.ts +2 -0
- package/dist/commands/web-agent-config.js +72 -38
- package/dist/search/youcom.d.ts +7 -0
- package/dist/search/youcom.js +81 -0
- package/dist/types.d.ts +2 -2
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,37 @@ The format is intentionally simple and release-oriented.
|
|
|
18
18
|
### Breaking
|
|
19
19
|
- None.
|
|
20
20
|
|
|
21
|
+
## [1.6.1] - 2026-07-05
|
|
22
|
+
### Added
|
|
23
|
+
- None.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- None.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Setting a SearXNG URL from **Settings → Backends** now actually saves, and switches the search provider to `searxng` if it wasn't already selected. Previously the URL was silently discarded unless SearXNG was already the active provider. Reported by @Josephur in #33.
|
|
30
|
+
- Same fix applied on the fetch side: setting a Firecrawl URL now switches the fetch provider to `firecrawl` too, instead of leaving it stuck on `http` until you flip it manually.
|
|
31
|
+
- Editing the SearXNG/Firecrawl base URL no longer opens a separate nested input prompt on top of the settings list (which could leave the session unresponsive to further input). It's now an inline field in the same list, and pressing Esc there only cancels the field instead of closing the whole settings screen and discarding unrelated pending changes.
|
|
32
|
+
- The inline URL field now starts with the cursor at the end of the pre-filled value, so typing right away appends instead of inserting at the front of the string.
|
|
33
|
+
|
|
34
|
+
### Breaking
|
|
35
|
+
- None.
|
|
36
|
+
|
|
37
|
+
## [1.6.0] - 2026-07-05
|
|
38
|
+
### Added
|
|
39
|
+
- You.com is now available as a hosted search backend for `web_explore`, selectable alongside DuckDuckGo, SearXNG, and Brave. Set `YDC_API_KEY` in the environment and choose `youcom` from **Settings → Backends**. You.com handles source discovery only; `web_explore` still fetches pages, ranks evidence, handles caveats, and synthesizes the answer itself. Contributed by @ydc-oss-bot in #32 - first outside PR this project has had.
|
|
40
|
+
- `/web-agent settings` now treats You.com as a first-class search backend (with optional DuckDuckGo fallback) while keeping API keys out of config files via `YDC_API_KEY`.
|
|
41
|
+
- `/web-agent doctor` now reports You.com setup status, warns when `YDC_API_KEY` is missing, and validates configured You.com access when a key is present.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- None.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- None.
|
|
48
|
+
|
|
49
|
+
### Breaking
|
|
50
|
+
- None.
|
|
51
|
+
|
|
21
52
|
## [1.5.1] - 2026-06-22
|
|
22
53
|
### Added
|
|
23
54
|
- None.
|
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Headless rendering first tries a detectable Chromium-family browser: Chrome, Chr
|
|
|
27
27
|
Later on, update installed packages with:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
pi update
|
|
30
|
+
pi update --extensions
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Docs
|
|
@@ -109,7 +109,9 @@ Example:
|
|
|
109
109
|
}
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
Backend config is also supported. Defaults remain DuckDuckGo search, plain HTTP fetch, and local-browser headless fallback with managed Chromium fallback configured. If you have a Brave Search API key,
|
|
112
|
+
Backend config is also supported. Defaults remain DuckDuckGo search, plain HTTP fetch, and local-browser headless fallback with managed Chromium fallback configured. If you have a Brave Search or You.com API key, either can be selected as a hosted search backend while `web_explore` still handles page reading, ranking, and caveats itself.
|
|
113
|
+
|
|
114
|
+
Search backends: DuckDuckGo (default), SearXNG (self-hosted), Brave Search (hosted), You.com (hosted).
|
|
113
115
|
|
|
114
116
|
Backend settings can be changed from:
|
|
115
117
|
|
|
@@ -117,9 +119,9 @@ Backend settings can be changed from:
|
|
|
117
119
|
/web-agent settings
|
|
118
120
|
```
|
|
119
121
|
|
|
120
|
-
Choose **Backends** to edit search/fetch providers, fallback behavior, and SearXNG or Firecrawl base URLs interactively. Brave Search uses `PI_WEB_AGENT_BRAVE_API_KEY`. Firecrawl API keys should also stay in environment variables rather than being written into config files.
|
|
122
|
+
Choose **Backends** to edit search/fetch providers, fallback behavior, and SearXNG or Firecrawl base URLs interactively. Brave Search uses `PI_WEB_AGENT_BRAVE_API_KEY` and You.com uses `YDC_API_KEY`. Firecrawl API keys should also stay in environment variables rather than being written into config files.
|
|
121
123
|
|
|
122
|
-
For the full backend config shape, including SearXNG, Brave, Firecrawl, and fallback behavior, see:
|
|
124
|
+
For the full backend config shape, including SearXNG, Brave, You.com, Firecrawl, and fallback behavior, see:
|
|
123
125
|
|
|
124
126
|
- https://demigodmode.github.io/pi-web-agent/self-hosted-backends
|
|
125
127
|
|
|
@@ -8,7 +8,7 @@ export type FirecrawlOptions = {
|
|
|
8
8
|
onlyMainContent?: boolean;
|
|
9
9
|
};
|
|
10
10
|
export type SearchBackendConfig = {
|
|
11
|
-
provider: 'duckduckgo' | 'searxng' | 'brave';
|
|
11
|
+
provider: 'duckduckgo' | 'searxng' | 'brave' | 'youcom';
|
|
12
12
|
baseUrl?: string;
|
|
13
13
|
fallback?: 'duckduckgo';
|
|
14
14
|
options?: SearxngOptions;
|
package/dist/backends/config.js
CHANGED
|
@@ -40,7 +40,8 @@ export function extractBackendConfigOverride(file) {
|
|
|
40
40
|
const override = {};
|
|
41
41
|
if (backends?.search?.provider === 'duckduckgo' ||
|
|
42
42
|
backends?.search?.provider === 'searxng' ||
|
|
43
|
-
backends?.search?.provider === 'brave'
|
|
43
|
+
backends?.search?.provider === 'brave' ||
|
|
44
|
+
backends?.search?.provider === 'youcom') {
|
|
44
45
|
override.search = { provider: backends.search.provider };
|
|
45
46
|
if (backends.search.provider === 'searxng' && typeof backends.search.baseUrl === 'string') {
|
|
46
47
|
override.search.baseUrl = backends.search.baseUrl;
|
|
@@ -84,8 +85,8 @@ export function validateBackendConfig(config) {
|
|
|
84
85
|
if (config.fetch.provider === 'firecrawl' && !config.fetch.baseUrl) {
|
|
85
86
|
issues.push('fetch provider firecrawl requires backends.fetch.baseUrl');
|
|
86
87
|
}
|
|
87
|
-
if (config.search.fallback === 'duckduckgo' && config.search.provider !== 'searxng' && config.search.provider !== 'brave') {
|
|
88
|
-
issues.push('search fallback duckduckgo is only supported when search provider is searxng or
|
|
88
|
+
if (config.search.fallback === 'duckduckgo' && config.search.provider !== 'searxng' && config.search.provider !== 'brave' && config.search.provider !== 'youcom') {
|
|
89
|
+
issues.push('search fallback duckduckgo is only supported when search provider is searxng, brave, or youcom');
|
|
89
90
|
}
|
|
90
91
|
if (config.fetch.fallback === 'http' && config.fetch.provider !== 'firecrawl') {
|
|
91
92
|
issues.push('fetch fallback http is only supported when fetch provider is firecrawl');
|
package/dist/backends/doctor.js
CHANGED
|
@@ -12,6 +12,9 @@ function braveDoctorUrl() {
|
|
|
12
12
|
url.searchParams.set('count', '1');
|
|
13
13
|
return url.toString();
|
|
14
14
|
}
|
|
15
|
+
function youcomDoctorBody() {
|
|
16
|
+
return JSON.stringify({ query: 'pi-web-agent-doctor', max_results: 1 });
|
|
17
|
+
}
|
|
15
18
|
function searxngDoctorUrl(baseUrl, options = {}) {
|
|
16
19
|
const url = new URL('/search', baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);
|
|
17
20
|
url.searchParams.set('q', 'pi-web-agent-doctor');
|
|
@@ -69,6 +72,42 @@ export async function checkBackendHealth(config, { fetchImpl = fetch, timeoutMs
|
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
}
|
|
75
|
+
else if (config.search.provider === 'youcom') {
|
|
76
|
+
const apiKey = process.env.YDC_API_KEY;
|
|
77
|
+
if (!apiKey?.trim()) {
|
|
78
|
+
lines.push('search backend: youcom warning (missing YDC_API_KEY)');
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const timeout = withTimeout(timeoutMs);
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetchImpl('https://api.you.com/v1/agents/search', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: {
|
|
86
|
+
Accept: 'application/json',
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
'X-API-Key': apiKey
|
|
89
|
+
},
|
|
90
|
+
body: youcomDoctorBody(),
|
|
91
|
+
signal: timeout.signal
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
lines.push(`search backend: youcom warning (HTTP ${response.status})`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const json = (await response.json());
|
|
98
|
+
lines.push(Array.isArray(json.results)
|
|
99
|
+
? 'search backend: youcom ok'
|
|
100
|
+
: 'search backend: youcom warning (unexpected response)');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
lines.push(`search backend: youcom warning (${message(error)})`);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
timeout.done();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
72
111
|
else if (!config.search.baseUrl) {
|
|
73
112
|
lines.push('search backend: searxng warning (missing baseUrl)');
|
|
74
113
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createFirecrawlFetcher } from '../fetch/firecrawl-fetch.js';
|
|
2
2
|
import { createBraveSearchTool } from '../search/brave.js';
|
|
3
|
+
import { createYouComSearchTool } from '../search/youcom.js';
|
|
3
4
|
import { createSearxngSearchTool } from '../search/searxng.js';
|
|
4
5
|
import { createWebFetchHeadlessTool } from '../tools/web-fetch-headless.js';
|
|
5
6
|
import { createWebFetchTool } from '../tools/web-fetch.js';
|
|
@@ -21,6 +22,7 @@ export type BackendFactoryDeps = {
|
|
|
21
22
|
createDuckDuckGoSearch?: typeof createWebSearchTool;
|
|
22
23
|
createSearxngSearch?: typeof createSearxngSearchTool;
|
|
23
24
|
createBraveSearch?: typeof createBraveSearchTool;
|
|
25
|
+
createYouComSearch?: typeof createYouComSearchTool;
|
|
24
26
|
createHttpFetch?: typeof createWebFetchTool;
|
|
25
27
|
createFirecrawlFetch?: typeof createFirecrawlFetcher;
|
|
26
28
|
createHeadlessFetch?: typeof createWebFetchHeadlessTool;
|
package/dist/backends/factory.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createFirecrawlFetcher } from '../fetch/firecrawl-fetch.js';
|
|
2
2
|
import { createBraveSearchTool } from '../search/brave.js';
|
|
3
|
+
import { createYouComSearchTool } from '../search/youcom.js';
|
|
3
4
|
import { createSearxngSearchTool } from '../search/searxng.js';
|
|
4
5
|
import { buildFetchPresentation } from '../presentation/fetch-presentation.js';
|
|
5
6
|
import { buildSearchPresentation } from '../presentation/search-presentation.js';
|
|
@@ -73,6 +74,7 @@ export function createBackendSet(config = DEFAULT_BACKEND_CONFIG, deps = {}) {
|
|
|
73
74
|
const createDuckDuckGoSearch = deps.createDuckDuckGoSearch ?? createWebSearchTool;
|
|
74
75
|
const createSearxngSearch = deps.createSearxngSearch ?? createSearxngSearchTool;
|
|
75
76
|
const createBraveSearch = deps.createBraveSearch ?? createBraveSearchTool;
|
|
77
|
+
const createYouComSearch = deps.createYouComSearch ?? createYouComSearchTool;
|
|
76
78
|
const createHttpFetch = deps.createHttpFetch ?? createWebFetchTool;
|
|
77
79
|
const createFirecrawlFetch = deps.createFirecrawlFetch ?? createFirecrawlFetcher;
|
|
78
80
|
const createHeadlessFetch = deps.createHeadlessFetch ?? createWebFetchHeadlessTool;
|
|
@@ -82,13 +84,18 @@ export function createBackendSet(config = DEFAULT_BACKEND_CONFIG, deps = {}) {
|
|
|
82
84
|
: invalidSearxngSearch()
|
|
83
85
|
: config.search.provider === 'brave'
|
|
84
86
|
? createBraveSearch({ apiKey: process.env.PI_WEB_AGENT_BRAVE_API_KEY })
|
|
85
|
-
:
|
|
87
|
+
: config.search.provider === 'youcom'
|
|
88
|
+
? createYouComSearch({ apiKey: process.env.YDC_API_KEY })
|
|
89
|
+
: createDuckDuckGoSearch();
|
|
86
90
|
if (config.search.provider === 'searxng' && config.search.fallback === 'duckduckgo') {
|
|
87
91
|
search = withSearchFallback(search, createDuckDuckGoSearch(), 'searxng');
|
|
88
92
|
}
|
|
89
93
|
if (config.search.provider === 'brave' && config.search.fallback === 'duckduckgo') {
|
|
90
94
|
search = withSearchFallback(search, createDuckDuckGoSearch(), 'brave');
|
|
91
95
|
}
|
|
96
|
+
if (config.search.provider === 'youcom' && config.search.fallback === 'duckduckgo') {
|
|
97
|
+
search = withSearchFallback(search, createDuckDuckGoSearch(), 'youcom');
|
|
98
|
+
}
|
|
92
99
|
const httpFetch = createHttpFetch();
|
|
93
100
|
let fetchPage = config.fetch.provider === 'firecrawl'
|
|
94
101
|
? config.fetch.baseUrl
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BackendConfig, type BackendConfigOverride } from '../backends/config.js';
|
|
2
2
|
import { type ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
3
|
+
import { type Component } from '@earendil-works/pi-tui';
|
|
3
4
|
import { loadPresentationConfigLayers, type LoadedPresentationConfig } from '../presentation/config-store.js';
|
|
4
5
|
import { type BrowserResolutionResult } from '../fetch/browser-resolution.js';
|
|
5
6
|
import type { PresentationConfig, PresentationConfigOverride, PresentationScope } from '../presentation/types.js';
|
|
@@ -32,6 +33,7 @@ export declare function validateBackendUrl(value: string): {
|
|
|
32
33
|
ok: false;
|
|
33
34
|
message: string;
|
|
34
35
|
};
|
|
36
|
+
export declare function createBackendUrlEditor(theme: any, label: string, placeholderUrl: string, onOpenChange?: (open: boolean) => void): (currentValue: string, done: (selectedValue?: string) => void) => Component;
|
|
35
37
|
export declare function getInheritedConfigForScope(loaded: Awaited<LoadedPresentationConfig>, scope: PresentationScope): PresentationConfig;
|
|
36
38
|
export declare function getScopeDisplayConfig(loaded: Awaited<LoadedPresentationConfig>, scope: PresentationScope): PresentationConfig;
|
|
37
39
|
export declare function getInheritedBackendsForScope(loaded: Awaited<LoadedPresentationConfig>, scope: PresentationScope): BackendConfig;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_BACKEND_CONFIG, mergeBackendConfigLayers, validateBackendConfig } from '../backends/config.js';
|
|
2
2
|
import { checkBackendHealth } from '../backends/doctor.js';
|
|
3
3
|
import { DynamicBorder, getSettingsListTheme } from '@earendil-works/pi-coding-agent';
|
|
4
|
-
import { Container, SelectList, SettingsList, Text } from '@earendil-works/pi-tui';
|
|
4
|
+
import { Container, Input, SelectList, SettingsList, Text } from '@earendil-works/pi-tui';
|
|
5
5
|
import { DEFAULT_PRESENTATION_CONFIG, mergePresentationConfigLayers, resolvePresentationMode } from '../presentation/config.js';
|
|
6
6
|
import { loadPresentationConfigLayers, resetPresentationConfigScope, saveBackendConfigScope, savePresentationConfigScope } from '../presentation/config-store.js';
|
|
7
7
|
import { resolveBrowserExecutable } from '../fetch/browser-resolution.js';
|
|
@@ -112,7 +112,49 @@ function buildPresentationSettingsItems(scope, config) {
|
|
|
112
112
|
}))
|
|
113
113
|
];
|
|
114
114
|
}
|
|
115
|
-
function
|
|
115
|
+
export function createBackendUrlEditor(theme, label, placeholderUrl, onOpenChange) {
|
|
116
|
+
return (currentValue, done) => {
|
|
117
|
+
onOpenChange?.(true);
|
|
118
|
+
const initialValue = currentValue && currentValue !== 'not set' ? currentValue : placeholderUrl;
|
|
119
|
+
const container = new Container();
|
|
120
|
+
const hint = new Text(theme.fg('muted', `${label} · enter to save · empty clears · esc cancels`), 1, 0);
|
|
121
|
+
const input = new Input();
|
|
122
|
+
input.setValue(initialValue);
|
|
123
|
+
input.handleInput('\x1b[F'); // move cursor to end; setValue leaves it at 0
|
|
124
|
+
input.focused = true;
|
|
125
|
+
const errorText = new Text('', 1, 0);
|
|
126
|
+
const finish = (selectedValue) => {
|
|
127
|
+
onOpenChange?.(false);
|
|
128
|
+
done(selectedValue);
|
|
129
|
+
};
|
|
130
|
+
const showError = (message) => {
|
|
131
|
+
errorText.setText(theme.fg('warning', message));
|
|
132
|
+
container.invalidate();
|
|
133
|
+
};
|
|
134
|
+
input.onSubmit = (value) => {
|
|
135
|
+
if (!value.trim()) {
|
|
136
|
+
finish('');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const validated = validateBackendUrl(value);
|
|
140
|
+
if (!validated.ok) {
|
|
141
|
+
showError(validated.message);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
finish(validated.value);
|
|
145
|
+
};
|
|
146
|
+
input.onEscape = () => finish(undefined);
|
|
147
|
+
container.addChild(hint);
|
|
148
|
+
container.addChild(input);
|
|
149
|
+
container.addChild(errorText);
|
|
150
|
+
return {
|
|
151
|
+
render: (width) => container.render(width),
|
|
152
|
+
invalidate: () => container.invalidate(),
|
|
153
|
+
handleInput: (data) => input.handleInput(data)
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function buildBackendSettingsItems(scope, backends, theme, onUrlEditorOpenChange) {
|
|
116
158
|
return [
|
|
117
159
|
{
|
|
118
160
|
id: 'scope',
|
|
@@ -124,19 +166,19 @@ function buildBackendSettingsItems(scope, backends) {
|
|
|
124
166
|
id: 'backend:search:provider',
|
|
125
167
|
label: 'Search backend',
|
|
126
168
|
currentValue: backends.search.provider,
|
|
127
|
-
values: ['duckduckgo', 'searxng', 'brave']
|
|
169
|
+
values: ['duckduckgo', 'searxng', 'brave', 'youcom']
|
|
128
170
|
},
|
|
129
171
|
{
|
|
130
172
|
id: 'backend:search:baseUrl',
|
|
131
173
|
label: 'SearXNG URL',
|
|
132
174
|
currentValue: backends.search.baseUrl ?? 'not set',
|
|
133
|
-
|
|
175
|
+
submenu: createBackendUrlEditor(theme, 'SearXNG base URL', 'http://localhost:8080', onUrlEditorOpenChange)
|
|
134
176
|
},
|
|
135
177
|
{
|
|
136
178
|
id: 'backend:search:fallback',
|
|
137
179
|
label: 'Search fallback',
|
|
138
|
-
currentValue: backends.search.provider === 'searxng' || backends.search.provider === 'brave' ? backends.search.fallback ?? 'off' : 'off',
|
|
139
|
-
values: backends.search.provider === 'searxng' || backends.search.provider === 'brave' ? ['off', 'duckduckgo'] : ['off']
|
|
180
|
+
currentValue: backends.search.provider === 'searxng' || backends.search.provider === 'brave' || backends.search.provider === 'youcom' ? backends.search.fallback ?? 'off' : 'off',
|
|
181
|
+
values: backends.search.provider === 'searxng' || backends.search.provider === 'brave' || backends.search.provider === 'youcom' ? ['off', 'duckduckgo'] : ['off']
|
|
140
182
|
},
|
|
141
183
|
{
|
|
142
184
|
id: 'backend:secret:brave',
|
|
@@ -144,6 +186,12 @@ function buildBackendSettingsItems(scope, backends) {
|
|
|
144
186
|
currentValue: 'env var',
|
|
145
187
|
values: ['env var']
|
|
146
188
|
},
|
|
189
|
+
{
|
|
190
|
+
id: 'backend:secret:youcom',
|
|
191
|
+
label: 'You.com API key',
|
|
192
|
+
currentValue: 'env var',
|
|
193
|
+
values: ['env var']
|
|
194
|
+
},
|
|
147
195
|
{
|
|
148
196
|
id: 'backend:fetch:provider',
|
|
149
197
|
label: 'Fetch backend',
|
|
@@ -154,7 +202,7 @@ function buildBackendSettingsItems(scope, backends) {
|
|
|
154
202
|
id: 'backend:fetch:baseUrl',
|
|
155
203
|
label: 'Firecrawl URL',
|
|
156
204
|
currentValue: backends.fetch.baseUrl ?? 'not set',
|
|
157
|
-
|
|
205
|
+
submenu: createBackendUrlEditor(theme, 'Firecrawl base URL', 'http://localhost:3002', onUrlEditorOpenChange)
|
|
158
206
|
},
|
|
159
207
|
{
|
|
160
208
|
id: 'backend:fetch:fallback',
|
|
@@ -255,7 +303,7 @@ export function applySettingsValue(state, id, newValue) {
|
|
|
255
303
|
}
|
|
256
304
|
currentDraft.tools = nextTools;
|
|
257
305
|
}
|
|
258
|
-
if (id === 'backend:search:provider' && (newValue === 'duckduckgo' || newValue === 'searxng' || newValue === 'brave')) {
|
|
306
|
+
if (id === 'backend:search:provider' && (newValue === 'duckduckgo' || newValue === 'searxng' || newValue === 'brave' || newValue === 'youcom')) {
|
|
259
307
|
currentBackends.search.provider = newValue;
|
|
260
308
|
if (newValue !== 'searxng') {
|
|
261
309
|
delete currentBackends.search.baseUrl;
|
|
@@ -266,7 +314,7 @@ export function applySettingsValue(state, id, newValue) {
|
|
|
266
314
|
}
|
|
267
315
|
}
|
|
268
316
|
if (id === 'backend:search:fallback') {
|
|
269
|
-
if (newValue === 'duckduckgo' && (currentBackends.search.provider === 'searxng' || currentBackends.search.provider === 'brave')) {
|
|
317
|
+
if (newValue === 'duckduckgo' && (currentBackends.search.provider === 'searxng' || currentBackends.search.provider === 'brave' || currentBackends.search.provider === 'youcom')) {
|
|
270
318
|
currentBackends.search.fallback = 'duckduckgo';
|
|
271
319
|
}
|
|
272
320
|
else {
|
|
@@ -274,7 +322,8 @@ export function applySettingsValue(state, id, newValue) {
|
|
|
274
322
|
}
|
|
275
323
|
}
|
|
276
324
|
if (id === 'backend:search:baseUrl') {
|
|
277
|
-
if (
|
|
325
|
+
if (newValue.trim()) {
|
|
326
|
+
currentBackends.search.provider = 'searxng';
|
|
278
327
|
currentBackends.search.baseUrl = newValue.trim();
|
|
279
328
|
}
|
|
280
329
|
else {
|
|
@@ -297,6 +346,7 @@ export function applySettingsValue(state, id, newValue) {
|
|
|
297
346
|
}
|
|
298
347
|
if (id === 'backend:fetch:baseUrl') {
|
|
299
348
|
if (newValue.trim()) {
|
|
349
|
+
currentBackends.fetch.provider = 'firecrawl';
|
|
300
350
|
currentBackends.fetch.baseUrl = newValue.trim();
|
|
301
351
|
}
|
|
302
352
|
else {
|
|
@@ -488,43 +538,20 @@ async function openPresentationSettingsUi(ctx, loaded, initialScope) {
|
|
|
488
538
|
});
|
|
489
539
|
}
|
|
490
540
|
async function openBackendSettingsUi(ctx, loaded, initialScope) {
|
|
491
|
-
return ctx.ui.custom((
|
|
541
|
+
return ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
492
542
|
let state = createSettingsDraftState(loaded, initialScope);
|
|
493
543
|
let settingsList;
|
|
544
|
+
let urlEditorOpen = false;
|
|
494
545
|
const container = new Container();
|
|
495
546
|
container.addChild(new Text(theme.fg('accent', theme.bold('pi-web-agent · backends')), 1, 1));
|
|
496
547
|
container.addChild(new Text(theme.fg('muted', 'Ctrl+S save · Ctrl+R reset scope · Esc cancel · API keys stay in env vars'), 1, 2));
|
|
497
|
-
const editUrl = async (id) => {
|
|
498
|
-
const isSearchUrl = id === 'backend:search:baseUrl';
|
|
499
|
-
const label = isSearchUrl ? 'SearXNG base URL' : 'Firecrawl base URL';
|
|
500
|
-
const currentValue = isSearchUrl ? state.backends.search.baseUrl : state.backends.fetch.baseUrl;
|
|
501
|
-
const entered = await ctx.ui.input(label, currentValue ?? (isSearchUrl ? 'http://localhost:8080' : 'http://localhost:3002'));
|
|
502
|
-
if (entered === undefined)
|
|
503
|
-
return;
|
|
504
|
-
if (!entered.trim()) {
|
|
505
|
-
state = applySettingsValue(state, id, '');
|
|
506
|
-
rebuildSettingsList();
|
|
507
|
-
tui.requestRender?.();
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
const validated = validateBackendUrl(entered);
|
|
511
|
-
if (!validated.ok) {
|
|
512
|
-
ctx.ui.notify(validated.message, 'warning');
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
state = applySettingsValue(state, id, validated.value);
|
|
516
|
-
rebuildSettingsList();
|
|
517
|
-
tui.requestRender?.();
|
|
518
|
-
};
|
|
519
548
|
const rebuildSettingsList = () => {
|
|
520
549
|
if (settingsList) {
|
|
521
550
|
container.removeChild(settingsList);
|
|
522
551
|
}
|
|
523
|
-
settingsList = new SettingsList(buildBackendSettingsItems(state.scope, state.backends
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
552
|
+
settingsList = new SettingsList(buildBackendSettingsItems(state.scope, state.backends, theme, (open) => {
|
|
553
|
+
urlEditorOpen = open;
|
|
554
|
+
}), 12, getSettingsListTheme(), (id, newValue) => {
|
|
528
555
|
state = applySettingsValue(state, id, newValue);
|
|
529
556
|
rebuildSettingsList();
|
|
530
557
|
container.invalidate();
|
|
@@ -536,6 +563,13 @@ async function openBackendSettingsUi(ctx, loaded, initialScope) {
|
|
|
536
563
|
render: (width) => container.render(width),
|
|
537
564
|
invalidate: () => container.invalidate(),
|
|
538
565
|
handleInput: (data) => {
|
|
566
|
+
// While the inline URL editor is open, let it (and its own Escape/Enter
|
|
567
|
+
// handling) consume input first — otherwise these shortcuts close the
|
|
568
|
+
// whole settings UI out from under an in-progress edit.
|
|
569
|
+
if (urlEditorOpen) {
|
|
570
|
+
settingsList.handleInput?.(data);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
539
573
|
const shortcut = handleSettingsShortcut(JSON.stringify(data).slice(1, -1));
|
|
540
574
|
if (shortcut?.action === 'cancel') {
|
|
541
575
|
done({ action: 'cancel' });
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { buildSearchPresentation } from '../presentation/search-presentation.js';
|
|
2
|
+
const YOUCOM_SEARCH_URL = 'https://api.you.com/v1/agents/search';
|
|
3
|
+
function resultWithPresentation(result) {
|
|
4
|
+
return { ...result, presentation: buildSearchPresentation(result) };
|
|
5
|
+
}
|
|
6
|
+
function normalizeResults(response) {
|
|
7
|
+
return (response.results ?? []).flatMap((item) => {
|
|
8
|
+
if (typeof item.title !== 'string' || typeof item.url !== 'string') {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
title: item.title,
|
|
14
|
+
url: item.url,
|
|
15
|
+
snippet: typeof item.snippet === 'string' ? item.snippet : ''
|
|
16
|
+
}
|
|
17
|
+
];
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function createYouComSearchTool({ apiKey, fetchImpl = fetch }) {
|
|
21
|
+
return async function youComSearch({ query }) {
|
|
22
|
+
const normalizedQuery = query.trim();
|
|
23
|
+
if (!normalizedQuery) {
|
|
24
|
+
return resultWithPresentation({
|
|
25
|
+
status: 'error',
|
|
26
|
+
results: [],
|
|
27
|
+
metadata: { backend: 'youcom', cacheHit: false },
|
|
28
|
+
error: { code: 'INVALID_QUERY', message: 'Query must not be empty.' }
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (!apiKey?.trim()) {
|
|
32
|
+
return resultWithPresentation({
|
|
33
|
+
status: 'error',
|
|
34
|
+
results: [],
|
|
35
|
+
metadata: { backend: 'youcom', cacheHit: false },
|
|
36
|
+
error: {
|
|
37
|
+
code: 'BACKEND_CONFIG_INVALID',
|
|
38
|
+
message: 'You.com search requires YDC_API_KEY.'
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetchImpl(YOUCOM_SEARCH_URL, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
Accept: 'application/json',
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
'X-API-Key': apiKey
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({ query: normalizedQuery, max_results: 10 })
|
|
51
|
+
});
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`HTTP ${response.status}`);
|
|
54
|
+
}
|
|
55
|
+
const parsed = (await response.json());
|
|
56
|
+
const results = normalizeResults(parsed);
|
|
57
|
+
if (results.length === 0) {
|
|
58
|
+
return resultWithPresentation({
|
|
59
|
+
status: 'error',
|
|
60
|
+
results: [],
|
|
61
|
+
metadata: { backend: 'youcom', cacheHit: false },
|
|
62
|
+
error: { code: 'NO_RESULTS', message: 'You.com returned no usable results for this query.' }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return resultWithPresentation({
|
|
66
|
+
status: 'ok',
|
|
67
|
+
results,
|
|
68
|
+
metadata: { backend: 'youcom', cacheHit: false }
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
73
|
+
return resultWithPresentation({
|
|
74
|
+
status: 'error',
|
|
75
|
+
results: [],
|
|
76
|
+
metadata: { backend: 'youcom', cacheHit: false },
|
|
77
|
+
error: { code: 'FETCH_FAILED', message: `You.com search request failed: ${rawMessage}` }
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -11,9 +11,9 @@ export type ToolError = {
|
|
|
11
11
|
message: string;
|
|
12
12
|
};
|
|
13
13
|
export type SearchMetadata = {
|
|
14
|
-
backend: 'duckduckgo' | 'searxng' | 'brave';
|
|
14
|
+
backend: 'duckduckgo' | 'searxng' | 'brave' | 'youcom';
|
|
15
15
|
cacheHit: boolean;
|
|
16
|
-
fallbackFrom?: 'searxng' | 'brave';
|
|
16
|
+
fallbackFrom?: 'searxng' | 'brave' | 'youcom';
|
|
17
17
|
fallbackReason?: string;
|
|
18
18
|
};
|
|
19
19
|
export type FetchMetadata = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demigodmode/pi-web-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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",
|
|
@@ -21,7 +21,12 @@
|
|
|
21
21
|
"pi",
|
|
22
22
|
"extension",
|
|
23
23
|
"web-search",
|
|
24
|
-
"web-fetch"
|
|
24
|
+
"web-fetch",
|
|
25
|
+
"duckduckgo",
|
|
26
|
+
"searxng",
|
|
27
|
+
"brave-search",
|
|
28
|
+
"you-com",
|
|
29
|
+
"firecrawl"
|
|
25
30
|
],
|
|
26
31
|
"repository": {
|
|
27
32
|
"type": "git",
|